#!/usr/bin/env node /** * stub-modules.mjs — Create stub files for all missing feature-gated modules * * Run: node scripts/stub-modules.mjs * Then: npx esbuild build-src/entry.ts --bundle --platform=node --packages=external ... * * Reads esbuild errors, resolves each relative import to its correct absolute * path inside build-src/src/, and creates an empty stub. */ import { readFile, writeFile, mkdir, stat } from 'node:fs/promises' import { join, dirname, resolve } from 'node:path' import { execSync } from 'node:child_process' const ROOT = join(import.meta.dirname, '..') const BUILD_SRC = join(ROOT, 'build-src', 'src') async function exists(p) { try { await stat(p); return true } catch { return false } } // Parse all missing modules from esbuild output const out = execSync( `npx esbuild "${join(ROOT, 'build-src', 'entry.ts')}" ` + `--bundle --platform=node --packages=external ` + `--external:'bun:*' --log-level=error --log-limit=0 ` + `--outfile=/dev/null 2>&1 || true`, { cwd: ROOT, shell: true, encoding: 'utf8', maxBuffer: 50 * 1024 * 1024 } ) const missingRe = /Could not resolve "([^"]+)"/g const errors = [...out.matchAll(/\s+(\S+:\d+:\d+):\s/g)].map(m => m[1]) const moduleFiles = new Map() // module → set of importing files let match while ((match = missingRe.exec(out)) !== null) { const mod = match[1] if (mod.startsWith('node:') || mod.startsWith('bun:') || mod.startsWith('/')) continue moduleFiles.set(mod, new Set()) } // Now resolve each relative module path to its absolute path // by finding which source file imports it const importRe = /(\S+:\d+:\d+):\s*\d+.*require\(["']([^"']+)["']\)|import.*from\s*["']([^"']+)["']/g let stubCount = 0 const created = new Set() for (const [mod] of moduleFiles) { // For relative imports, we need to find the importing file to resolve the path // Search for the import in the build-src const escapedMod = mod.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') const grepResult = execSync( `grep -rl "${escapedMod}" "${BUILD_SRC}" 2>/dev/null || true`, { encoding: 'utf8', maxBuffer: 10 * 1024 * 1024, shell: true } ).trim() const importers = grepResult.split('\n').filter(Boolean) for (const importer of importers) { const importerDir = dirname(importer) const absPath = resolve(importerDir, mod) // Check if it's a .d.ts type file — just create empty if (mod.endsWith('.d.ts')) { if (!created.has(absPath)) { await mkdir(dirname(absPath), { recursive: true }).catch(() => {}) if (!await exists(absPath)) { await writeFile(absPath, '// Type stub\nexport {}\n', 'utf8') stubCount++ created.add(absPath) } } continue } // Text assets (.txt, .md) if (/\.(txt|md)$/.test(mod)) { if (!created.has(absPath)) { await mkdir(dirname(absPath), { recursive: true }).catch(() => {}) if (!await exists(absPath)) { await writeFile(absPath, '', 'utf8') stubCount++ created.add(absPath) } } continue } // JS/TS modules if (/\.[tj]sx?$/.test(mod)) { if (!created.has(absPath)) { await mkdir(dirname(absPath), { recursive: true }).catch(() => {}) if (!await exists(absPath)) { const name = mod.split('/').pop().replace(/\.[tj]sx?$/, '') const safeName = name.replace(/[^a-zA-Z0-9_$]/g, '_') || 'stub' await writeFile(absPath, `// Auto-generated stub for feature-gated module: ${mod}\nexport default function ${safeName}() { return null }\nexport const ${safeName} = () => null\n`, 'utf8') stubCount++ created.add(absPath) } } } } // Also try resolving from src root for modules starting with ../ if (mod.startsWith('../')) { // Try from several likely locations for (const prefix of ['src', 'src/commands', 'src/components', 'src/services', 'src/tools', 'src/utils']) { const absPath = join(ROOT, 'build-src', prefix, mod) if (!created.has(absPath)) { await mkdir(dirname(absPath), { recursive: true }).catch(() => {}) if (!await exists(absPath) && (/\.[tj]sx?$/.test(mod))) { const name = mod.split('/').pop().replace(/\.[tj]sx?$/, '') const safeName = name.replace(/[^a-zA-Z0-9_$]/g, '_') || 'stub' await writeFile(absPath, `// Auto-generated stub for: ${mod}\nexport default function ${safeName}() { return null }\nexport const ${safeName} = () => null\n`, 'utf8') stubCount++ created.add(absPath) } } } } } console.log(`āœ… Created ${stubCount} stubs for ${moduleFiles.size} missing modules`) // Now try the build console.log('\nšŸ”Ø Attempting esbuild bundle...\n') try { const OUT = join(ROOT, 'dist', 'cli.js') await mkdir(dirname(OUT), { recursive: true }) execSync([ 'npx esbuild', `"${join(ROOT, 'build-src', 'entry.ts')}"`, '--bundle', '--platform=node', '--target=node18', '--format=esm', `--outfile="${OUT}"`, '--packages=external', '--external:bun:*', '--banner:js=$\'#!/usr/bin/env node\\n// Claude Code v2.1.88 (built from source)\\n// Copyright (c) Anthropic PBC. All rights reserved.\\n\'', '--allow-overwrite', '--log-level=warning', '--sourcemap', ].join(' '), { cwd: ROOT, stdio: 'inherit', shell: true, }) const size = (await stat(OUT)).size console.log(`\nāœ… Build succeeded: ${OUT}`) console.log(` Size: ${(size / 1024 / 1024).toFixed(1)}MB`) console.log(` Usage: node ${OUT} --version`) } catch (e) { console.error('\nāŒ Build still has errors. Run again to iterate:') console.error(' node scripts/stub-modules.mjs') }