Files
claude-code-source-code/scripts/stub-modules.mjs
sanbuphy 1b77a70b18 Add build infrastructure and QUICKSTART guide
- Build script (scripts/build.mjs) using esbuild with iterative stub creation
- Stubs for Bun compile-time intrinsics (feature(), MACRO, bun:bundle)
- Stub modules for feature-gated internal code paths
- QUICKSTART.md with 3 build options (pre-built, esbuild, Bun)
- tsconfig.json, package.json for build tooling

Build reaches ~95% — 108 remaining feature-gated modules need Bun runtime
for full dead code elimination. See QUICKSTART.md for details.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-31 18:12:54 +08:00

159 lines
5.6 KiB
JavaScript

#!/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')
}