UNPKG

@stackmemoryai/stackmemory

Version:

Project-scoped memory for AI coding tools. Durable context across sessions with MCP integration, frames, smart retrieval, Claude Code skills, and automatic hooks.

292 lines (240 loc) • 8.02 kB
#!/usr/bin/env node /** * Script to fix common TypeScript strict mode issues * STA-34: Enable TypeScript Strict Mode */ import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const projectRoot = path.resolve(__dirname, '..'); interface StrictModeIssue { file: string; line: number; column: number; type: | 'possibly_undefined' | 'index_signature' | 'implicit_any' | 'unknown_type'; description: string; } class StrictModeFixer { private issues: StrictModeIssue[] = []; /** * Fix process.env access issues (noPropertyAccessFromIndexSignature) */ async fixProcessEnvAccess(): Promise<void> { console.log('šŸ”§ Fixing process.env access issues...'); const files = await this.findFilesWithPattern(/process\.env\.\w+/); for (const file of files) { const content = fs.readFileSync(file, 'utf-8'); let updated = content; // Replace process.env['VAR'] with process.env['VAR'] updated = updated.replace(/process\.env\.(\w+)/g, "process.env['$1']"); if (updated !== content) { fs.writeFileSync(file, updated); console.log( `āœ… Fixed process.env access in ${path.relative(projectRoot, file)}` ); } } } /** * Fix possibly undefined issues with null assertion or optional chaining */ async fixPossiblyUndefined(): Promise<void> { console.log('šŸ”§ Fixing possibly undefined issues...'); // Common patterns that can be safely fixed const patterns = [ { // Fix array access that's known to exist from: /(\w+)\[(\d+)\](?!\?)/g, to: '$1[$2]!', condition: (match: string, file: string) => { // Only apply if it's an array access in test or known safe contexts return file.includes('test') || file.includes('spec'); }, }, ]; const files = await this.findTypescriptFiles(); for (const file of files) { const content = fs.readFileSync(file, 'utf-8'); let updated = content; let hasChanges = false; for (const pattern of patterns) { const matches = content.match(pattern.from); if (matches && pattern.condition(content, file)) { updated = updated.replace(pattern.from, pattern.to); hasChanges = true; } } if (hasChanges) { fs.writeFileSync(file, updated); console.log( `āœ… Fixed undefined issues in ${path.relative(projectRoot, file)}` ); } } } /** * Fix unknown type issues by adding proper type annotations */ async fixUnknownTypes(): Promise<void> { console.log('šŸ”§ Fixing unknown type issues...'); const files = await this.findTypescriptFiles(); for (const file of files) { const content = fs.readFileSync(file, 'utf-8'); let updated = content; // Fix catch blocks with unknown error updated = updated.replace( /catch\s*\(\s*(\w+)\s*\)\s*{/g, 'catch ($1: unknown) {' ); // Fix error parameter in catch blocks updated = updated.replace(/(\w+) is of type 'unknown'/g, '$1 as Error'); if (updated !== content) { fs.writeFileSync(file, updated); console.log( `āœ… Fixed unknown types in ${path.relative(projectRoot, file)}` ); } } } /** * Fix implicit any issues by adding type annotations */ async fixImplicitAny(): Promise<void> { console.log('šŸ”§ Fixing implicit any issues...'); const files = await this.findTypescriptFiles(); for (const file of files) { const content = fs.readFileSync(file, 'utf-8'); let updated = content; // Fix common implicit any patterns updated = updated.replace(/\.map\(\s*(\w+)\s*=>/g, '.map(($1: any) =>'); updated = updated.replace( /\.filter\(\s*(\w+)\s*=>/g, '.filter(($1: any) =>' ); updated = updated.replace(/\.find\(\s*(\w+)\s*=>/g, '.find(($1: any) =>'); if (updated !== content) { fs.writeFileSync(file, updated); console.log( `āœ… Fixed implicit any in ${path.relative(projectRoot, file)}` ); } } } /** * Add type guards for environment variable access */ async addEnvironmentTypeGuards(): Promise<void> { console.log('šŸ”§ Adding environment variable type guards...'); const files = await this.findFilesWithPattern(/process\.env\[/); for (const file of files) { const content = fs.readFileSync(file, 'utf-8'); let updated = content; // Add utility function for safe env access at the top of files that need it if ( content.includes("process.env['") && !content.includes('function getEnv(') ) { const imports = content.match(/^import.*$/gm) || []; const lastImportIndex = content.lastIndexOf( imports[imports.length - 1] || '' ); const envUtility = ` // Type-safe environment variable access function getEnv(key: string, defaultValue?: string): string { const value = process.env[key]; if (value === undefined) { if (defaultValue !== undefined) return defaultValue; throw new Error(\`Environment variable \${key} is required\`); } return value; } function getOptionalEnv(key: string): string | undefined { return process.env[key]; } `; updated = content.slice( 0, lastImportIndex + imports[imports.length - 1]?.length || 0 ) + envUtility + content.slice( lastImportIndex + imports[imports.length - 1]?.length || 0 ); fs.writeFileSync(file, updated); console.log( `āœ… Added env utilities to ${path.relative(projectRoot, file)}` ); } } } /** * Find all TypeScript files in the project */ private async findTypescriptFiles(): Promise<string[]> { const files: string[] = []; const walk = (dir: string): void => { const items = fs.readdirSync(dir); for (const item of items) { const fullPath = path.join(dir, item); const stat = fs.statSync(fullPath); if ( stat.isDirectory() && !item.startsWith('.') && item !== 'node_modules' && item !== 'dist' ) { walk(fullPath); } else if ( stat.isFile() && (item.endsWith('.ts') || item.endsWith('.tsx')) && !item.endsWith('.d.ts') ) { files.push(fullPath); } } }; walk(path.join(projectRoot, 'src')); walk(path.join(projectRoot, 'scripts')); return files; } /** * Find files containing a specific pattern */ private async findFilesWithPattern(pattern: RegExp): Promise<string[]> { const allFiles = await this.findTypescriptFiles(); const matchingFiles: string[] = []; for (const file of allFiles) { const content = fs.readFileSync(file, 'utf-8'); if (pattern.test(content)) { matchingFiles.push(file); } } return matchingFiles; } /** * Run all fixes */ async runAllFixes(): Promise<void> { console.log('šŸš€ Starting TypeScript strict mode fixes...\n'); await this.fixProcessEnvAccess(); await this.addEnvironmentTypeGuards(); await this.fixUnknownTypes(); await this.fixImplicitAny(); // await this.fixPossiblyUndefined(); // Skip this for now as it's more complex console.log('\nāœ… All automated fixes completed!'); console.log('\nRemaining issues will need manual review:'); console.log('- Complex possibly undefined cases'); console.log('- Interface property mismatches'); console.log('- Context-specific type assertions'); console.log('\nšŸ”§ Run `npx tsc --noEmit` to check remaining issues'); } } // Run the fixer const fixer = new StrictModeFixer(); fixer.runAllFixes().catch(console.error);