Instruction file imported from mikepsinn/disease-eradication-plan (
.github/instructions/typescript.instructions.md). Copyright stays with the author.
TypeScript Scripts Instructions
Standards for TypeScript scripts, automation tools, and utilities.
Execution
ALWAYS use tsx to run TypeScript files, not ts-node:
✅ Correct:
npx tsx scripts/review/review.ts
❌ Wrong:
npx ts-node scripts/review/review.ts
Code Style
Module System
Use ES modules (as configured in package.json):
// Correct
import { readFile } from 'fs/promises';
import yaml from 'js-yaml';
// Wrong - don't use require()
const fs = require('fs');
TypeScript Configuration
Follow settings in tsconfig.json:
- Target: ES2022
- Module: ES2022
- Strict mode: Enabled
- Module resolution: bundler
Type Safety
Always use proper types, avoid any:
// Correct
interface Parameter {
value: number;
unit: string;
source_type: 'external' | 'calculated' | 'definition';
}
// Wrong
let param: any = { ... };
File Organization
Script Structure
Organize scripts logically:
scripts/
├── review/ # Content review tools
├── images/ # Image generation/processing
├── chat/ # Chat and AI tools
└── lib/ # Shared utilities
Import Paths
Use relative imports for project files:
import { loadYamlFile } from '../lib/yaml-utils.js';
Note: Include .js extension for ES modules (TypeScript resolves correctly).
Common Patterns
File Operations
import { readFile, writeFile } from 'fs/promises';
import { join } from 'path';
// Use async/await
const content = await readFile(filePath, 'utf-8');
await writeFile(outputPath, content);
YAML Processing
import yaml from 'js-yaml';
const data = yaml.load(await readFile(yamlPath, 'utf-8'));
Command-line Scripts
Use yargs for argument parsing:
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
const argv = yargs(hideBin(process.argv))
.option('force', {
alias: 'f',
type: 'boolean',
description: 'Force operation'
})
.parseSync();
Error Handling
Always handle errors appropriately:
try {
const result = await riskyOperation();
console.log('Success:', result);
} catch (error) {
console.error('Error:', error.message);
process.exit(1);
}
Console Output
Use Clear Messages
// Good
console.log('✓ Generated variables for 42 parameters');
console.error('✗ Failed to parse YAML file');
// Avoid Unicode on Windows Python scripts, but OK in Node.js
Progress Indicators
For long operations:
console.log('Processing files...');
let processed = 0;
for (const file of files) {
await processFile(file);
processed++;
console.log(`Progress: ${processed}/${files.length}`);
}
Working with Parameters
Reading Variables
import yaml from 'js-yaml';
import { readFile } from 'fs/promises';
const variables = yaml.load(
await readFile('_variables.yml', 'utf-8')
) as Record<string, any>;
Parameter Name Conversion
Convert between Python and QMD naming:
// Python: GLOBAL_ANNUAL_WAR_COST
// QMD: global_annual_war_cost
function toVarName(pythonName: string): string {
return pythonName.toLowerCase();
}
Quarto Integration
Rendering
Use Python script, don't call quarto directly in TS:
import { exec } from 'child_process';
import { promisify } from 'util';
const execAsync = promisify(exec);
// Use the Python wrapper
await execAsync('python scripts/render-quarto.py book --preview');
File Paths
Always use absolute paths or proper relative paths:
import { resolve } from 'path';
const projectRoot = process.cwd();
const qmdPath = resolve(projectRoot, 'knowledge', 'intro.qmd');
Testing
Unit Tests (Jest)
Place tests alongside code or in __tests__:
// scripts/lib/__tests__/yaml-utils.test.ts
import { loadYamlFile } from '../yaml-utils.js';
describe('loadYamlFile', () => {
it('should load valid YAML', async () => {
const result = await loadYamlFile('test.yml');
expect(result).toBeDefined();
});
});
Run Tests
npm test # Run all tests
npm run test:watch # Watch mode
npm run test:coverage # With coverage
Dependencies
Adding New Dependencies
- Check if it's in the ecosystem:
npm search package-name - Install:
pnpm add package-name - Use proper imports
- Document in code why it's needed
Common Dependencies
gray-matter: Frontmatter parsingglob: File pattern matchingjs-yaml: YAML processingyargs: CLI argument parsingsharp: Image processingchokidar: File watching
Script Best Practices
- Single responsibility: Each script does one thing well
- Reusable functions: Extract to
lib/if used multiple times - Error messages: Clear and actionable
- Documentation: Add JSDoc comments for complex functions
- Dry run mode: Add
--dry-runflag for destructive operations
Example Script Template
#!/usr/bin/env node
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
async function main() {
const argv = yargs(hideBin(process.argv))
.option('input', {
alias: 'i',
type: 'string',
description: 'Input file path',
demandOption: true
})
.option('dry-run', {
type: 'boolean',
description: 'Preview changes without applying',
default: false
})
.parseSync();
try {
console.log('Starting process...');
// Your logic here
console.log('✓ Complete');
} catch (error) {
console.error('✗ Error:', error.message);
process.exit(1);
}
}
main();
Linting and Formatting
Scripts should pass TypeScript compiler checks:
npx tsc --noEmit # Check types without building
No automatic formatters are enforced, but maintain consistency with existing code style.