forked from sanbuphy/learn-coding-agent
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstub-modules.mjs
More file actions
158 lines (139 loc) · 5.6 KB
/
stub-modules.mjs
File metadata and controls
158 lines (139 loc) · 5.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#!/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')
}