dsh-code-server-app 0.1.32 → 0.1.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,449 @@
1
+ // scripts/vendor-repacks.mjs — 打包期把「pnpm 拒绝安装」的包重打包成可安装的预编译包。
2
+ //
3
+ // 为什么:pnpm 认为「含安装脚本」或「含 binding.gyp / .hooks」的包需要构建,必须由宿主
4
+ // pnpm-workspace.yaml 的 allowBuilds 批准,否则 dsh plugin add 直接 exit 1。VS Code 内部
5
+ // 依赖里有一批这样的包(含原生模块),既不能在 profile 里声明为依赖,也不能靠 pnpm 装。
6
+ //
7
+ // 做法(实测可行,见下):
8
+ // 1. 从一棵**已完整安装**的 code-server 树出发,算出 needsRepack 闭包(带构建信号,以及依赖链
9
+ // 上命中它们的包 —— 含 optionalDependencies,例如 @vscode/proxy-agent → @vscode/windows-ca-certs);
10
+ // 2. 每个命中包重新打包成 @<scope>/dshcs-<名字>:
11
+ // - 删掉全部 scripts / files 字段、binding.gyp、.hooks、.npmignore(全量打包,保住 build/*.node);
12
+ // - 依赖里命中重打包集的,改成 npm: 别名;
13
+ // - 含「非 prebuilds 的 .node」的包视为平台专属 → 名字带 -<platform>-<arch>,并写入 os/cpu;
14
+ // 3. 生成平台聚合包 @<scope>/dsh-code-server-runtime-<platform>-<arch>,dependencies 用 npm: 别名
15
+ // 把重打包包装回**原始名字**(node-pty / @vscode/sqlite3 / …),这样 VS Code 的 require 不用改;
16
+ // 4. 把「纯 JS 直装集」写进插件 package.json 的 dependencies,把两个平台聚合包写进 optionalDependencies。
17
+ //
18
+ // 结果:pnpm install 不再遇到任何带构建信号的包 → 无需 allowBuilds、无需「安装环境」步骤。
19
+ //
20
+ // 用法:
21
+ // node scripts/vendor-repacks.mjs [--from <已完整安装的 code-server 树>] \
22
+ // [--target win32-arm64,win32-x64] [--scope @jinsiyu] [--pack]
23
+ // 不给 --from 时自动准备源树(按 vendor/VENDOR.json 的版本 npm install --ignore-scripts,
24
+ // 再在 lib/vscode 里解包 + rebuild;耗时且需要工具链,维护者换版本时用)。
25
+ // --pack 会用 npm pack 生成 repack/tgz/*.tgz(随后用 scripts/publish-repacks.mjs 发布)。
26
+ import { existsSync, readFileSync, readdirSync, mkdirSync, rmSync, writeFileSync, cpSync } from 'node:fs';
27
+ import { dirname, join, relative, resolve } from 'node:path';
28
+ import { fileURLToPath } from 'node:url';
29
+ import { spawnSync } from 'node:child_process';
30
+
31
+ const here = dirname(fileURLToPath(import.meta.url));
32
+ const pkgRoot = resolve(here, '..');
33
+ const OUT = join(pkgRoot, 'repack');
34
+ const npmCache = join(pkgRoot, '.npm-cache');
35
+
36
+ function argValues(name) {
37
+ const out = [];
38
+ for (let i = 0; i < process.argv.length - 1; i += 1) {
39
+ if (process.argv[i] !== name) continue;
40
+ for (const part of String(process.argv[i + 1]).split(',')) {
41
+ const v = part.trim();
42
+ if (v !== '') out.push(v);
43
+ }
44
+ }
45
+ return out;
46
+ }
47
+ const argValue = (n) => { const a = argValues(n); return a.length > 0 ? a[a.length - 1] : null; };
48
+ const FROM = argValue('--from');
49
+ const SCOPE = argValue('--scope') ?? '@jinsiyu';
50
+ const DO_PACK = process.argv.includes('--pack');
51
+ const TARGETS = argValues('--target');
52
+ // Copilot 整树排除:620MB、依赖链里还有带脚本的包,且 code-server 里用不到。
53
+ const EXCLUDE = [/^@github\//, /^@vscode\/copilot-api$/];
54
+
55
+ const readJson = (p) => { try { return JSON.parse(readFileSync(p, 'utf8')); } catch { return null; } };
56
+ const flat = (n) => n.replace(/^@/, '').replaceAll('/', '-');
57
+ const excluded = (n) => EXCLUDE.some((re) => re.test(n));
58
+ const tgzName = (pkgName, version) => `${flat(pkgName)}-${version}.tgz`;
59
+
60
+ function run(cmd, args, cwd, env) {
61
+ const useShell = process.platform === 'win32' && /\.(cmd|bat)$/i.test(cmd);
62
+ const res = spawnSync(cmd, args, { cwd, stdio: 'inherit', shell: useShell, env: { ...process.env, ...env } });
63
+ if (res.status !== 0) throw new Error(`${cmd} ${args.join(' ')} failed (${res.status})`);
64
+ }
65
+ function npm(args, cwd) {
66
+ const env = { npm_config_cache: npmCache, npm_config_update_notifier: 'false' };
67
+ const cli = join(dirname(process.execPath), 'node_modules', 'npm', 'bin', 'npm-cli.js');
68
+ if (existsSync(cli)) run(process.execPath, [cli, ...args], cwd, env);
69
+ else run(process.platform === 'win32' ? 'npm.cmd' : 'npm', args, cwd, env);
70
+ }
71
+
72
+ /** 收集树里的所有包(顶层 + 嵌套)。 */
73
+ function collect(dir, out = new Map(), depth = 0) {
74
+ if (depth > 5 || !existsSync(dir)) return out;
75
+ for (const ent of readdirSync(dir, { withFileTypes: true })) {
76
+ if (!ent.isDirectory() || ent.name === '.bin' || ent.name === '.cache') continue;
77
+ const full = join(dir, ent.name);
78
+ if (ent.name.startsWith('@')) { collect(full, out, depth + 1); continue; }
79
+ const m = readJson(join(full, 'package.json'));
80
+ if (m === null) { collect(full, out, depth + 1); continue; }
81
+ out.set(full, { dir: full, name: m.name, version: m.version, manifest: m });
82
+ const nested = join(full, 'node_modules');
83
+ if (existsSync(nested)) collect(nested, out, depth + 1);
84
+ }
85
+ return out;
86
+ }
87
+ function resolveDep(fromDir, depName) {
88
+ let cur = fromDir;
89
+ for (let i = 0; i < 6; i += 1) {
90
+ const cand = join(cur, 'node_modules', depName, 'package.json');
91
+ if (existsSync(cand)) return dirname(cand);
92
+ const parent = dirname(cur);
93
+ if (parent === cur) break;
94
+ cur = parent;
95
+ }
96
+ return null;
97
+ }
98
+ function hasBuildSignal(pkg) {
99
+ const s = pkg.manifest.scripts ?? {};
100
+ if (s.preinstall || s.install || s.postinstall) return true;
101
+ return existsSync(join(pkg.dir, 'binding.gyp')) || existsSync(join(pkg.dir, '.hooks'));
102
+ }
103
+ function isPlatformSpecific(pkg) {
104
+ const stack = [pkg.dir];
105
+ while (stack.length > 0) {
106
+ const cur = stack.pop();
107
+ let entries; try { entries = readdirSync(cur, { withFileTypes: true }); } catch { continue; }
108
+ for (const e of entries) {
109
+ const full = join(cur, e.name);
110
+ const rel = relative(pkg.dir, full).replaceAll('\\', '/');
111
+ if (e.isDirectory()) {
112
+ if (e.name === 'node_modules' || rel === 'prebuilds' || rel.startsWith('prebuilds/')) continue;
113
+ stack.push(full);
114
+ } else if (e.name.endsWith('.node') && !rel.startsWith('prebuilds/')) return true;
115
+ }
116
+ }
117
+ return false;
118
+ }
119
+
120
+ /** 分析一棵树:返回 { repack: Map(name -> {dir,pkg,platformSpecific}), declare: [{name,version}] }。 */
121
+ function analyze(tree) {
122
+ const VS = join(tree, 'lib', 'vscode');
123
+ const packages = collect(join(VS, 'node_modules'));
124
+ const vsManifest = readJson(join(VS, 'package.json'));
125
+ const memo = new Map();
126
+ function needsRepack(dir) {
127
+ if (memo.has(dir)) return memo.get(dir);
128
+ memo.set(dir, false); // 防环
129
+ const pkg = packages.get(dir);
130
+ if (!pkg || excluded(pkg.name)) return false;
131
+ let result = hasBuildSignal(pkg);
132
+ if (!result) {
133
+ const deps = { ...(pkg.manifest.dependencies ?? {}), ...(pkg.manifest.optionalDependencies ?? {}) };
134
+ for (const depName of Object.keys(deps)) {
135
+ if (excluded(depName)) continue;
136
+ const d = resolveDep(pkg.dir, depName);
137
+ if (d !== null && needsRepack(d)) { result = true; break; }
138
+ }
139
+ }
140
+ memo.set(dir, result);
141
+ return result;
142
+ }
143
+
144
+ const repack = new Map();
145
+ const declare = [];
146
+ for (const name of Object.keys(vsManifest.dependencies ?? {})) {
147
+ if (excluded(name)) { console.log(` [排除 Copilot] ${name}`); continue; }
148
+ const dir = resolveDep(VS, name);
149
+ if (dir === null) { console.warn(` ⚠ 找不到 ${name}`); continue; }
150
+ if (needsRepack(dir)) {
151
+ const pkg = packages.get(dir);
152
+ repack.set(name, { dir, pkg, platformSpecific: isPlatformSpecific(pkg) });
153
+ } else {
154
+ declare.push({ name, version: packages.get(dir).version });
155
+ }
156
+ }
157
+ for (const [dir, pkg] of packages) {
158
+ if (excluded(pkg.name) || repack.has(pkg.name)) continue;
159
+ if (needsRepack(dir)) repack.set(pkg.name, { dir, pkg, platformSpecific: isPlatformSpecific(pkg) });
160
+ }
161
+ const extNm = join(VS, 'extensions', 'node_modules');
162
+ for (const ent of (existsSync(extNm) ? readdirSync(extNm, { withFileTypes: true }) : [])) {
163
+ if (!ent.isDirectory() || ent.name.startsWith('.')) continue;
164
+ const m = readJson(join(ent.isDirectory() ? join(extNm, ent.name) : '', 'package.json'));
165
+ if (m !== null) declare.push({ name: m.name, version: m.version });
166
+ }
167
+ return { repack, declare };
168
+ }
169
+
170
+ /** 该架构是否装了 MSVC 的 Spectre 缓解库(lib\spectre\<arch>)。
171
+ * VS Code 的 @vscode/* 原生包在 binding.gyp 里写死 SpectreMitigation: Spectre,
172
+ * 缺库时 MSBuild 直接报 MSB8040(此机只装了 arm64/arm64ec)。 */
173
+ function spectreLibsFor(arch) {
174
+ for (const root of [process.env['ProgramFiles'], process.env['ProgramFiles(x86)']].filter(Boolean)) {
175
+ const vsRoot = join(root, 'Microsoft Visual Studio');
176
+ if (!existsSync(vsRoot)) continue;
177
+ for (const ver of readdirSync(vsRoot)) {
178
+ const verDir = join(vsRoot, ver);
179
+ let editions; try { editions = readdirSync(verDir); } catch { continue; }
180
+ for (const ed of editions) {
181
+ const msvcRoot = join(verDir, ed, 'VC', 'Tools', 'MSVC');
182
+ if (!existsSync(msvcRoot)) continue;
183
+ for (const tools of readdirSync(msvcRoot)) {
184
+ if (existsSync(join(msvcRoot, tools, 'lib', 'spectre', arch))) return true;
185
+ }
186
+ }
187
+ }
188
+ }
189
+ return false;
190
+ }
191
+
192
+ /** 把树里所有 *.gyp / *.gypi 的 SpectreMitigation 降级为 false(仅关掉 Spectre 加固,不影响功能)。
193
+ * 注意:除 binding.gyp 外,依赖自带的 deps/*.gyp 也会设它(如 @vscode/sqlite3/deps/sqlite3.gyp)。 */
194
+ function relaxSpectre(treeRoot) {
195
+ let patched = 0;
196
+ const stack = [treeRoot];
197
+ while (stack.length > 0) {
198
+ const cur = stack.pop();
199
+ let entries; try { entries = readdirSync(cur, { withFileTypes: true }); } catch { continue; }
200
+ for (const e of entries) {
201
+ const full = join(cur, e.name);
202
+ if (e.isDirectory()) { stack.push(full); continue; }
203
+ if (!/\.gypi?$/.test(e.name)) continue;
204
+ const src = readFileSync(full, 'utf8');
205
+ const out = src.replace(/(["']SpectreMitigation["']\s*:\s*)["']Spectre["']/g, '$1"false"');
206
+ if (out !== src) { writeFileSync(full, out, 'utf8'); patched += 1; }
207
+ }
208
+ }
209
+ return patched;
210
+ }
211
+
212
+ /** 列出某个包目录下的 .node 文件(跳过嵌套 node_modules)。 */
213
+ function nodeFilesOf(dir) {
214
+ const nodes = [];
215
+ const stack = [dir];
216
+ while (stack.length > 0) {
217
+ const cur = stack.pop();
218
+ let entries; try { entries = readdirSync(cur, { withFileTypes: true }); } catch { continue; }
219
+ for (const e of entries) {
220
+ if (e.isDirectory()) { if (e.name !== 'node_modules') stack.push(join(cur, e.name)); }
221
+ else if (e.name.endsWith('.node')) nodes.push(join(cur, e.name));
222
+ }
223
+ }
224
+ return nodes;
225
+ }
226
+
227
+ /** 准备另一平台的一棵「只含平台专属包」的安装(用 --os/--cpu 拉取 + npm rebuild 交叉编译)。 */
228
+ function prepareCrossTree(specs, target) {
229
+ const [platform, arch] = target.split('-');
230
+ const tmp = join(pkgRoot, '.vendor-tmp', `cross-repack-${target}-${process.pid}`);
231
+ rmSync(tmp, { recursive: true, force: true });
232
+ mkdirSync(tmp, { recursive: true });
233
+ writeFileSync(join(tmp, 'package.json'), JSON.stringify({ name: 'cross', private: true, version: '0.0.0' }, null, 2) + '\n', 'utf8');
234
+ const names = specs.map((s) => `${s.name}@${s.version}`);
235
+ console.log(`[repack] 拉取 ${target} 平台专属包(${names.length} 个)…`);
236
+ npm(['install', '--ignore-scripts', '--no-audit', '--no-fund', '--no-save',
237
+ `--os=${platform}`, `--cpu=${arch}`, ...names], tmp);
238
+ console.log(`[repack] 交叉编译 ${target}: ${specs.map((s) => s.name).join(', ')}`);
239
+ const buildOne = (spec) => {
240
+ try {
241
+ npm(['rebuild', `--arch=${arch}`, spec.name], tmp);
242
+ } catch (e) {
243
+ console.warn(`[repack] ${target}: ${spec.name} 编译失败(${e.message})`);
244
+ }
245
+ return nodeFilesOf(join(tmp, 'node_modules', spec.name)).length > 0;
246
+ };
247
+ const failed = [];
248
+ let relaxed = false;
249
+ for (const spec of specs) {
250
+ if (!buildOne(spec)) failed.push(spec);
251
+ }
252
+ // 兜底:某些机器只装了部分架构的 Spectre 缓解库(MSB8040)→ 降级 gyp 后重试失败的包。
253
+ if (failed.length > 0) {
254
+ const n = relaxSpectre(join(tmp, 'node_modules'));
255
+ relaxed = n > 0;
256
+ console.log(`[repack] ${target}: ${failed.length} 个包未产出二进制 → 降级 ${n} 个 gyp 的 SpectreMitigation 后重试`);
257
+ const retry = failed.splice(0, failed.length);
258
+ for (const spec of retry) {
259
+ if (!buildOne(spec)) failed.push(spec);
260
+ }
261
+ }
262
+ for (const spec of specs) {
263
+ const nodes = nodeFilesOf(join(tmp, 'node_modules', spec.name));
264
+ if (nodes.length === 0) console.warn(` ⚠ ${target}: ${spec.name} 未生成 .node 二进制`);
265
+ else console.log(` ✓ ${target}: ${spec.name} → ${nodes.map((p) => relative(tmp, p).replaceAll('\\', '/')).join(', ')}`);
266
+ }
267
+ if (relaxed) console.log(`[repack] ${target}: 注意——部分二进制未启用 Spectre 加固(该架构缺少缓解库)`);
268
+ return tmp;
269
+ }
270
+
271
+ /** 没有 --from 时自动准备一棵「完整安装」的源树(维护者换 code-server 版本时用):
272
+ * 1) npm install code-server@<内置版本> --ignore-scripts(只解包);
273
+ * 2) 在 lib/vscode 与 lib/vscode/extensions 里 npm install --ignore-scripts(解包内部依赖,约 1GB);
274
+ * 3) 缺 Spectre 库时先降级 gyp,再 npm rebuild(编译本机原生包)。
275
+ * @returns {string} 源树根 */
276
+ function prepareSourceTree() {
277
+ const vendorVersion = readJson(join(pkgRoot, 'vendor', 'VENDOR.json'))?.codeServerVersion
278
+ ?? readJson(join(pkgRoot, 'vendor', 'code-server', 'package.json'))?.version;
279
+ if (typeof vendorVersion !== 'string' || vendorVersion === '') {
280
+ throw new Error('缺少 vendor/code-server;先运行 `node scripts/vendor-code-server.mjs`');
281
+ }
282
+ const tmp = join(pkgRoot, '.vendor-tmp', `repack-src-${process.pid}`);
283
+ rmSync(tmp, { recursive: true, force: true });
284
+ mkdirSync(tmp, { recursive: true });
285
+ console.log(`[repack] 准备完整源树 code-server@${vendorVersion} → ${tmp}`);
286
+ npm(['install', `code-server@${vendorVersion}`, '--ignore-scripts', '--omit=dev',
287
+ '--no-audit', '--no-fund', '--no-save'], tmp);
288
+ const tree = join(tmp, 'node_modules', 'code-server');
289
+ const innerDirs = [join('lib', 'vscode'), join('lib', 'vscode', 'extensions')]
290
+ .map((rel) => join(tree, rel))
291
+ .filter((dir) => existsSync(join(dir, 'package.json')));
292
+ for (const dir of innerDirs) {
293
+ console.log(`[repack] 解包内部依赖(不执行脚本): ${relative(pkgRoot, dir)}`);
294
+ npm(['install', '--ignore-scripts', '--omit=dev', '--no-audit', '--no-fund'], dir);
295
+ }
296
+ if (!spectreLibsFor(process.arch)) {
297
+ const n = relaxSpectre(tree);
298
+ if (n > 0) console.log(`[repack] ${process.arch}: 未安装 Spectre 缓解库 → 已把 ${n} 个 gyp 的 SpectreMitigation 降级为 false`);
299
+ }
300
+ for (const dir of innerDirs) {
301
+ if (!existsSync(join(dir, 'node_modules'))) continue;
302
+ console.log(`[repack] 编译内部依赖原生包: ${relative(pkgRoot, dir)}`);
303
+ try {
304
+ npm(['rebuild'], dir);
305
+ } catch (e) {
306
+ console.warn(`[repack] ${relative(pkgRoot, dir)} rebuild 失败(${e.message});后续按缺失处理`);
307
+ }
308
+ }
309
+ return tree;
310
+ }
311
+
312
+ function main() {
313
+ const autoSource = FROM === null;
314
+ const sourceTree = autoSource ? prepareSourceTree() : FROM;
315
+ const hostKey = `${process.platform}-${process.arch}`;
316
+ const targets = TARGETS.length > 0 ? TARGETS : [hostKey];
317
+ const pluginVersion = readJson(join(pkgRoot, 'package.json'))?.version ?? '0.0.0';
318
+
319
+ const { repack, declare } = analyze(sourceTree);
320
+ console.log(`\n重打包集(${repack.size}):`);
321
+ for (const [name, r] of [...repack].sort()) {
322
+ console.log(` ${name}@${r.pkg.version} ${r.platformSpecific ? '[平台专属]' : '[全平台]'}`);
323
+ }
324
+ console.log(`\n直装集(${declare.length}): ${declare.map((d) => d.name).join(', ')}\n`);
325
+
326
+ rmSync(OUT, { recursive: true, force: true });
327
+ mkdirSync(OUT, { recursive: true });
328
+
329
+ // 每个目标的包名映射
330
+ const byTarget = new Map();
331
+ for (const target of targets) {
332
+ const suffix = `-${target}`;
333
+ const map = new Map(); // 原包名 -> { pkgName, version }
334
+ for (const [name, r] of repack) {
335
+ const pkgName = `${SCOPE}/dshcs-${flat(name)}${r.platformSpecific ? suffix : ''}`;
336
+ map.set(name, { pkgName, version: r.pkg.version, platformSpecific: r.platformSpecific });
337
+ }
338
+ byTarget.set(target, map);
339
+ }
340
+ const hostMap = byTarget.get(hostKey) ?? byTarget.get(targets[0]);
341
+
342
+ // 1) 全平台重打包包(从 host 树)
343
+ const buildDir = join(OUT, 'build');
344
+ const writeRepack = (srcDir, pkg, outName) => {
345
+ const dir = join(buildDir, outName);
346
+ mkdirSync(dirname(dir), { recursive: true });
347
+ cpSync(srcDir, dir, { recursive: true, maxRetries: 6, retryDelay: 250 });
348
+ rmSync(join(dir, 'binding.gyp'), { force: true });
349
+ rmSync(join(dir, '.hooks'), { recursive: true, force: true });
350
+ rmSync(join(dir, '.npmignore'), { force: true });
351
+ const m = readJson(join(dir, 'package.json'));
352
+ delete m.scripts;
353
+ delete m.files;
354
+ m.name = pkg.pkgName;
355
+ m.version = pkg.version;
356
+ if (pkg.platformSpecific) {
357
+ const [platform, arch] = pkg.pkgName.match(/-(win32|darwin|linux)-(arm64|x64)$/).slice(1);
358
+ m.os = [platform];
359
+ m.cpu = [arch];
360
+ }
361
+ for (const fld of ['dependencies', 'optionalDependencies']) {
362
+ if (!m[fld]) continue;
363
+ for (const depName of Object.keys(m[fld])) {
364
+ const dep = hostMap.get(depName);
365
+ if (dep) m[fld][depName] = `npm:${dep.pkgName}@${dep.version}`;
366
+ }
367
+ }
368
+ writeFileSync(join(dir, 'package.json'), JSON.stringify(m, null, 2) + '\n', 'utf8');
369
+ return dir;
370
+ };
371
+
372
+ const plan = [];
373
+ for (const [name, r] of repack) {
374
+ if (r.platformSpecific) continue; // 平台专属的按目标处理
375
+ const dir = writeRepack(r.dir, hostMap.get(name), flat(name));
376
+ plan.push({ dir, file: tgzName(hostMap.get(name).pkgName, r.pkg.version) });
377
+ }
378
+ // 2) 平台专属包:host 用现成树,其它目标用交叉安装树
379
+ for (const target of targets) {
380
+ const map = byTarget.get(target);
381
+ const specs = [...repack].filter(([, r]) => r.platformSpecific).map(([name, r]) => ({ name, version: r.pkg.version }));
382
+ let crossTree = null;
383
+ if (target !== hostKey && specs.length > 0) crossTree = prepareCrossTree(specs, target);
384
+ for (const [name, r] of repack) {
385
+ if (!r.platformSpecific) continue;
386
+ const info = map.get(name);
387
+ const srcDir = target === hostKey ? r.dir : join(crossTree, 'node_modules', name);
388
+ if (!existsSync(join(srcDir, 'package.json'))) {
389
+ console.warn(` ⚠ ${target}: 缺少 ${name},跳过`);
390
+ continue;
391
+ }
392
+ const dir = writeRepack(srcDir, info, `${flat(name)}-${target}`);
393
+ plan.push({ dir, file: tgzName(info.pkgName, r.pkg.version) });
394
+ }
395
+ if (crossTree !== null) rmSync(crossTree, { recursive: true, force: true });
396
+ }
397
+ // 3) 聚合包(每平台一个):dependencies 用 npm: 别名把重打包包装回原名
398
+ for (const target of targets) {
399
+ const map = byTarget.get(target);
400
+ const aggName = `${SCOPE}/dsh-code-server-runtime-${target}`;
401
+ const aggDir = join(OUT, 'aggregator', target);
402
+ mkdirSync(aggDir, { recursive: true });
403
+ const deps = {};
404
+ for (const [name] of repack) deps[name] = `npm:${map.get(name).pkgName}@${map.get(name).version}`;
405
+ writeFileSync(join(aggDir, 'package.json'), JSON.stringify({
406
+ name: aggName,
407
+ version: pluginVersion,
408
+ description: `Prebuilt native modules for code-server on ${target} (node-pty, @vscode/sqlite3, kerberos, …), `
409
+ + 'so that installing the VS Code inner dependencies needs no build approval or C++ toolchain.',
410
+ os: [target.split('-')[0]],
411
+ cpu: [target.split('-')[1]],
412
+ dependencies: deps,
413
+ license: 'MIT',
414
+ repository: readJson(join(pkgRoot, 'package.json'))?.repository ?? undefined,
415
+ }, null, 2) + '\n', 'utf8');
416
+ plan.push({ dir: aggDir, file: tgzName(aggName, pluginVersion), aggregator: true, target });
417
+ }
418
+
419
+ writeFileSync(join(OUT, 'pack-plan.json'), JSON.stringify(plan, null, 2) + '\n', 'utf8');
420
+ console.log(`[repack] 生成 ${plan.length} 个待打包目录 → repack/ (计划:repack/pack-plan.json)`);
421
+
422
+ // 4) 改写插件 package.json 的依赖
423
+ const pkgFile = join(pkgRoot, 'package.json');
424
+ const pluginPkg = readJson(pkgFile);
425
+ pluginPkg.dependencies = Object.fromEntries(declare.map((d) => [d.name, d.version]).sort(([a], [b]) => a.localeCompare(b)));
426
+ pluginPkg.optionalDependencies = Object.fromEntries(targets
427
+ .map((t) => [`${SCOPE}/dsh-code-server-runtime-${t}`, `^${pluginVersion}`]));
428
+ writeFileSync(pkgFile, JSON.stringify(pluginPkg, null, 2) + '\n', 'utf8');
429
+ console.log(`[repack] 已写入 package.json:dependencies ${declare.length} 个(纯 JS),optionalDependencies ${targets.length} 个平台聚合包`);
430
+
431
+ // 5) 可选:直接打包
432
+ if (DO_PACK) {
433
+ mkdirSync(join(OUT, 'tgz'), { recursive: true });
434
+ for (const item of plan) {
435
+ console.log(`[repack] npm pack ${item.file}`);
436
+ npm(['pack', '--pack-destination', join(OUT, 'tgz')], item.dir);
437
+ }
438
+ console.log(`[repack] tarball → repack/tgz/`);
439
+ } else {
440
+ console.log('[repack] 未加 --pack;在普通终端里执行 `node scripts/vendor-repacks.mjs --pack` 生成 tarball');
441
+ }
442
+
443
+ if (autoSource) {
444
+ rmSync(sourceTree, { recursive: true, force: true });
445
+ console.log('[repack] 已清理自动准备的源树');
446
+ }
447
+ }
448
+
449
+ main();
@@ -0,0 +1,9 @@
1
+ {
2
+ "codeServerVersion": "4.136.2",
3
+ "preparedAt": "2026-09-09T19:27:24.058Z",
4
+ "source": "registry",
5
+ "node": "v24.13.1",
6
+ "platform": "win32",
7
+ "arch": "arm64",
8
+ "sizeMB": 232.3
9
+ }