dsh-code-server-app 0.1.43 → 0.2.0

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-code-server-app",
3
- "version": "0.1.43",
4
- "description": "Integrate code-server (VS Code in the browser) into DSH Web and Desktop: right-sidebar tab (DSH >= 0.1.5-alpha.1) with a floating-ball fallback for older hosts, over the Connection /api channel (ctx.connection.fetch; no webServer dependency) with host-side process lifecycle.",
3
+ "version": "0.2.0",
4
+ "description": "VS Code (from a code-server release) inside DSH Web and Desktop: right-sidebar tab with a floating-ball fallback, driven by the plugin's own launcher over the in-process VS Code server (lib/launcher.mjs). Two serving modes: loopback port (default) or same-origin mount on DSH's own webServer (/code-server, protected by ctx.connection.requestRejection). No code-server Node layer, no argon2, no C++ toolchain.",
5
5
  "homepage": "https://github.com/jinsiyu/dsh-code-server-app",
6
6
  "repository": {
7
7
  "type": "git",
@@ -40,11 +40,14 @@
40
40
  "files": [
41
41
  "lib/index.js",
42
42
  "lib/client.js",
43
+ "lib/launcher.mjs",
44
+ "lib/serve-dsh.mjs",
43
45
  "lib/vendor.js",
44
46
  "lib/native.js",
45
47
  "assets/favicon.ico",
46
48
  "assets/favicon.svg",
47
49
  "assets/extensions/dshcs-open-file",
50
+ "scripts/vendor-vscode-server.mjs",
48
51
  "scripts/vendor-code-server.mjs",
49
52
  "scripts/vendor-repacks.mjs",
50
53
  "vendor/VENDOR.json",
@@ -59,7 +62,7 @@
59
62
  },
60
63
  "license": "MIT",
61
64
  "dependencies": {
62
- "@jinsiyu/dshcs-code-server": "4.136.2",
65
+ "@jinsiyu/dshcs-vscode-server": "4.136.2",
63
66
  "@microsoft/1ds-core-js": "3.2.13",
64
67
  "@microsoft/1ds-post-js": "3.2.13",
65
68
  "@vscode/iconv-lite-umd": "0.7.1",
@@ -97,13 +100,14 @@
97
100
  "zod": "4.4.3"
98
101
  },
99
102
  "optionalDependencies": {
100
- "@jinsiyu/dsh-code-server-runtime-win32-arm64": "^0.1.38",
101
- "@jinsiyu/dsh-code-server-runtime-win32-x64": "^0.1.38"
103
+ "@jinsiyu/dsh-code-server-runtime-win32-arm64": "^0.2.0",
104
+ "@jinsiyu/dsh-code-server-runtime-win32-x64": "^0.2.0"
102
105
  },
103
106
  "scripts": {
107
+ "vendor:vscode": "node scripts/vendor-vscode-server.mjs",
108
+ "vendor:latest": "node scripts/vendor-vscode-server.mjs --force",
109
+ "vendor:check": "node scripts/vendor-vscode-server.mjs --check",
104
110
  "vendor:code-server": "node scripts/vendor-code-server.mjs",
105
- "vendor:latest": "node scripts/vendor-code-server.mjs --force",
106
- "vendor:check": "node scripts/vendor-code-server.mjs --check",
107
111
  "repack:build": "node scripts/vendor-repacks.mjs",
108
112
  "publish:repacks": "node scripts/publish-repacks.mjs",
109
113
  "publish:plugin": "node scripts/publish-plugin.mjs",
@@ -1,20 +1,20 @@
1
- // scripts/vendor-repacks.mjs — 打包期把 code-server 本体与「pnpm 拒绝安装」的原生包全部打成可安装的预编译包。
1
+ // scripts/vendor-repacks.mjs — 打包期把「VS Code 树」与「pnpm 拒绝安装」的原生包全部打成可安装的预编译包。
2
2
  //
3
3
  // 为什么:pnpm 认为「含安装脚本」或「含 binding.gyp / .hooks」的包需要构建,必须由宿主
4
- // pnpm-workspace.yaml 的 allowBuilds 批准,否则 dsh plugin add 直接 exit 1。code-server 本体、
5
- // argon2 VS Code 内部依赖里的一批原生包都属于这一类。
4
+ // pnpm-workspace.yaml 的 allowBuilds 批准,否则 dsh plugin add 直接 exit 1。VS Code 内部依赖里
5
+ // 的一批原生包都属于这一类;树本身则因为 npm 打包永远排除**包根** node_modules 而必须重打包。
6
6
  //
7
7
  // 产物(全部由本脚本统一生成,再由 scripts/publish-repacks.mjs 统一发布):
8
- // ① @<scope>/dshcs-code-server@<code-server 版本> 平台无关:vendor/code-server 的树
9
- // (out/ + lib/vscode + 自带运行时依赖),**去掉 node_modules/argon2**;
10
- // @<scope>/dshcs-argon2-<platform>-<arch>@<argon2 版本> 平台专属:argon2 + 该架构编译好的 .node
11
- // (与其它原生包同一套规则:删 scripts/files/binding.gyp、删 prebuilds 统一走 build/Release);
12
- // ③ @<scope>/dshcs-<名字>[-<platform>-<arch>]@<版本> VS Code 内部依赖里需要构建的包
8
+ // ① @<scope>/dshcs-vscode-server@<code-server 版本> 平台无关:vendor/vscode 的树
9
+ // (lib/vscode + out/browser + src/browser);**不含** code-server 的 out/node 与其 136 个依赖
10
+ // —— 那层由插件自带的 lib/launcher.mjs 取代(见 docs/analysis-code-server-as-dsh-plugin.md);
11
+ // @<scope>/dshcs-<名字>[-<platform>-<arch>]@<版本> VS Code 内部依赖里需要构建的包
13
12
  // (node-pty / @vscode/sqlite3 / kerberos / koffi / ssh2 / …),
14
13
  // 依赖链上命中它们的包也一并重打包(含 optionalDependencies,如 @vscode/proxy-agent → windows-ca-certs);
15
- // @<scope>/dsh-code-server-runtime-<platform>-<arch>@<插件版本> 平台聚合包:
16
- // dependencies 用 npm: 别名把 ②③ 装回**原始名字**(argon2 / node-pty / @vscode/sqlite3 / …),
17
- // 这样 code-server 与 VS Code 的 require 不用改,插件只需按 os/cpu 声明 ①(常规依赖)+ (可选依赖)。
14
+ // @<scope>/dsh-code-server-runtime-<platform>-<arch>@<插件版本> 平台聚合包:
15
+ // dependencies 用 npm: 别名把 装回**原始名字**(node-pty / @vscode/sqlite3 / …),
16
+ // 这样 VS Code 的 require/import 不用改,插件只需按 os/cpu 声明 ①(常规依赖)+ (可选依赖)。
17
+ // (argon2 已随 code-server 服务层一起移除:auth=none 不再需要口令哈希。)
18
18
  //
19
19
  // 结果:pnpm install 不再遇到任何带构建信号的包 → 无需 allowBuilds、无需「安装环境」步骤。
20
20
  //
@@ -50,7 +50,7 @@ const FROM = argValue('--from');
50
50
  const SCOPE = argValue('--scope') ?? '@jinsiyu';
51
51
  const DO_PACK = process.argv.includes('--pack');
52
52
  // --reuse:复用 repack/build 里已有的原生包(不重新 analyze/交叉编译),只重建
53
- // code-server 本体包、argon2 平台包、平台聚合包与插件 package.json。
53
+ // VS Code 树包、平台聚合包与插件 package.json。
54
54
  // 适用于「原生包没变、只调整打包结构」的场景(本机已无可分析的完整源树时也用它)。
55
55
  const REUSE = process.argv.includes('--reuse');
56
56
  const TARGETS = argValues('--target');
@@ -277,12 +277,13 @@ function prepareCrossTree(specs, target) {
277
277
  * 1) npm install code-server@<内置版本> --ignore-scripts(只解包);
278
278
  * 2) 在 lib/vscode 与 lib/vscode/extensions 里 npm install --ignore-scripts(解包内部依赖,约 1GB);
279
279
  * 3) 缺 Spectre 库时先降级 gyp,再 npm rebuild(编译本机原生包)。
280
+ * 注意:只有 lib/vscode 的依赖会被 analyze 用到 —— code-server 自己的 136 个依赖已随服务层移除。
280
281
  * @returns {string} 源树根 */
281
282
  function prepareSourceTree() {
282
- const vendorVersion = readJson(join(pkgRoot, 'vendor', 'VENDOR.json'))?.codeServerVersion
283
+ const vendorVersion = readJson(join(pkgRoot, 'vendor', 'vscode', 'package.json'))?.version
283
284
  ?? readJson(join(pkgRoot, 'vendor', 'code-server', 'package.json'))?.version;
284
285
  if (typeof vendorVersion !== 'string' || vendorVersion === '') {
285
- throw new Error('缺少 vendor/code-server;先运行 `node scripts/vendor-code-server.mjs`');
286
+ throw new Error('缺少 vendor/vscode;先运行 `node scripts/vendor-vscode-server.mjs`');
286
287
  }
287
288
  const tmp = join(pkgRoot, '.vendor-tmp', `repack-src-${process.pid}`);
288
289
  rmSync(tmp, { recursive: true, force: true });
@@ -325,86 +326,48 @@ function peMachine(file) {
325
326
  } catch { return null; }
326
327
  }
327
328
 
328
- function nodeGypCli() {
329
- const cli = join(process.env.APPDATA ?? '', 'npm', 'node_modules', 'node-gyp', 'bin', 'node-gyp.js');
330
- if (!existsSync(cli)) throw new Error('找不到 node-gyp(需全局安装 node-gyp)');
331
- return cli;
332
- }
333
-
334
- /** code-server 本体(平台无关):vendor/code-server repack/build/code-server/。
335
- * 去掉平台专属的 node_modules/argon2 —— 它由 @<scope>/dshcs-argon2-<platform>-<arch> 按架构提供。 */
336
- function buildCodeServerPackage() {
337
- const vendorTree = join(pkgRoot, 'vendor', 'code-server');
338
- if (!existsSync(join(vendorTree, 'out', 'node', 'entry.js'))) {
339
- throw new Error('缺少 vendor/code-server;先运行 `node scripts/vendor-code-server.mjs`');
329
+ /** ① VS Code 树(平台无关):vendor/vscode → repack/build/vscode/vscode/。
330
+ * 排除 VS Code 的「内部依赖目录」(lib/vscode/node_modules、lib/vscode/extensions/node_modules)
331
+ * —— 它们由包管理器安装 + 平台聚合包按架构提供,树里只留 lib/vscode 本体与静态资源。
332
+ * 不含 code-server 的 out/node 与服务层依赖(已由 lib/launcher.mjs 取代)。 */
333
+ function buildVscodeServerPackage() {
334
+ const vendorTree = join(pkgRoot, 'vendor', 'vscode');
335
+ if (!existsSync(join(vendorTree, 'lib', 'vscode', 'out', 'server-main.js'))) {
336
+ throw new Error('缺少 vendor/vscode;先运行 `node scripts/vendor-vscode-server.mjs`');
340
337
  }
341
338
  const version = readJson(join(vendorTree, 'package.json'))?.version;
342
- if (typeof version !== 'string') throw new Error('读不到 code-server 版本');
343
- const dir = join(OUT, 'build', 'code-server');
339
+ if (typeof version !== 'string') throw new Error('读不到树版本(vendor/vscode/package.json)');
340
+ const dir = join(OUT, 'build', 'vscode');
344
341
  rmSync(dir, { recursive: true, force: true });
345
342
  mkdirSync(dir, { recursive: true });
346
- console.log(`[repack] code-server 本体(平台无关)→ ${relative(pkgRoot, join(dir, 'code-server'))}(argon2 由平台包提供)`);
347
- cpSync(vendorTree, join(dir, 'code-server'), {
343
+ const inner = [
344
+ join('lib', 'vscode', 'node_modules'),
345
+ join('lib', 'vscode', 'extensions', 'node_modules'),
346
+ ].map((p) => p.replaceAll('\\', '/'));
347
+ console.log(`[repack] VS Code 树(平台无关)→ ${relative(pkgRoot, join(dir, 'vscode'))}`);
348
+ cpSync(vendorTree, join(dir, 'vscode'), {
348
349
  recursive: true,
349
350
  dereference: false,
350
351
  maxRetries: 6,
351
352
  retryDelay: 250,
353
+ filter: (source) => {
354
+ const rel = source.slice(vendorTree.length + 1).replaceAll('\\', '/');
355
+ return !inner.some((p) => rel === p || rel.startsWith(`${p}/`));
356
+ },
352
357
  });
353
- // 删掉平台专属的 argon2(整目录,避免嵌套解析遮蔽 <profile>/node_modules/argon2)
354
- rmSync(join(dir, 'code-server', 'node_modules', 'argon2'), { recursive: true, force: true });
358
+ const name = `${SCOPE}/dshcs-vscode-server`;
355
359
  writeFileSync(join(dir, 'package.json'), JSON.stringify({
356
- name: `${SCOPE}/dshcs-code-server`,
360
+ name,
357
361
  version,
358
- description: `code-server ${version} (out/ + lib/vscode + its runtime dependencies), repacked so pnpm installs it `
359
- + `without any build script. The platform-specific argon2 binary is provided by ${SCOPE}/dshcs-argon2-<platform>-<arch>.`,
360
- files: ['code-server'],
362
+ description: `The VS Code server tree from code-server ${version} (lib/vscode + browser assets), repacked so pnpm `
363
+ + 'installs it without any build script. The code-server Node layer is replaced by the plugin launcher.',
364
+ files: ['vscode'],
361
365
  license: 'MIT',
362
366
  repository: readJson(join(pkgRoot, 'package.json'))?.repository ?? undefined,
363
367
  }, null, 2) + '\n', 'utf8');
364
- const name = `${SCOPE}/dshcs-code-server`;
365
368
  return { dir, name, version, file: tgzName(name, version) };
366
369
  }
367
370
 
368
- /** ② argon2 平台二进制包(每个目标一个):@<scope>/dshcs-argon2-<target>@<argon2 版本>。
369
- * 与其它原生包同一套规则:删 scripts/files/binding.gyp,保留 build/Release 里的 .node;
370
- * prebuilds/ 一律删掉(统一走 build/Release,避免 node-gyp-build 在 win32-x64 选到上游 .glibc 命名文件)。 */
371
- function buildArgon2Package(target) {
372
- const [platform, arch] = target.split('-');
373
- const src = join(pkgRoot, 'vendor', 'code-server', 'node_modules', 'argon2');
374
- if (!existsSync(join(src, 'package.json'))) throw new Error('缺少 vendor/code-server/node_modules/argon2');
375
- const version = readJson(join(src, 'package.json')).version;
376
- const dir = join(OUT, 'build', `argon2-${target}`);
377
- rmSync(dir, { recursive: true, force: true });
378
- mkdirSync(dirname(dir), { recursive: true });
379
- cpSync(src, dir, { recursive: true, maxRetries: 6, retryDelay: 250 }); // dest 不存在 → 内容平铺到 dir
380
- rmSync(join(dir, 'build'), { recursive: true, force: true }); // 重新编译,避免带走 host 架构产物
381
-
382
- console.log(`[repack] argon2 ${target}: node-gyp rebuild --arch=${arch} …`);
383
- const res = spawnSync(process.execPath, [nodeGypCli(), 'rebuild', `--arch=${arch}`], { cwd: dir, stdio: 'inherit' });
384
- if ((res.status ?? 1) !== 0) throw new Error(`argon2 ${target} 编译失败 (${res.status})`);
385
- // 编译完成后才剥掉构建信号:删 prebuilds(统一走 build/Release)、binding.gyp、中间产物
386
- rmSync(join(dir, 'prebuilds'), { recursive: true, force: true });
387
- rmSync(join(dir, 'binding.gyp'), { force: true });
388
- rmSync(join(dir, '.npmignore'), { force: true });
389
- rmSync(join(dir, 'build', 'gyp'), { recursive: true, force: true }); // 中间产物,别进包
390
- const bin = join(dir, 'build', 'Release', 'argon2.node');
391
- if (!existsSync(bin)) throw new Error(`argon2 ${target} 缺 build/Release/argon2.node`);
392
- const want = arch === 'x64' ? '0x8664' : arch === 'arm64' ? '0xaa64' : null;
393
- const machine = peMachine(bin);
394
- if (want !== null && machine !== want) throw new Error(`argon2 ${target} PE machine=${machine},期望 ${want}`);
395
-
396
- const manifest = readJson(join(dir, 'package.json'));
397
- delete manifest.scripts;
398
- delete manifest.files;
399
- manifest.name = `${SCOPE}/dshcs-argon2-${target}`;
400
- manifest.version = version;
401
- manifest.os = [platform];
402
- manifest.cpu = [arch];
403
- writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest, null, 2) + '\n', 'utf8');
404
- console.log(` ✓ argon2 ${target} → ${machine} (${manifest.name}@${version})`);
405
- return { dir, name: manifest.name, version, file: tgzName(manifest.name, version) };
406
- }
407
-
408
371
  /** 从已安装的 profile 的 node_modules 还原 host 平台映射(别名目录名 = 原始包名,清单名 = 重打包名)。
409
372
  * 聚合包清单丢失时的兜底(例如上次构建中途失败)。 */
410
373
  function hostMapFromProfile() {
@@ -425,7 +388,9 @@ function hostMapFromProfile() {
425
388
  if (ent.name.startsWith('@')) { visit(full, prefix + ent.name + '/'); continue; }
426
389
  const m = readJson(join(full, 'package.json'));
427
390
  if (m === null || typeof m.name !== 'string') continue;
428
- if (!/^@[^/]+\/dshcs-/.test(m.name) || /dshcs-code-server/.test(m.name)) continue;
391
+ // argon2 已随 code-server 服务层移除:旧 profile 的别名表里可能仍有它
392
+ if (ent.name === 'argon2' || /dshcs-argon2/.test(m.name)) continue;
393
+ if (!/^@[^/]+\/dshcs-/.test(m.name) || /dshcs-(code-server|vscode-server)/.test(m.name)) continue;
429
394
  map.set(`${prefix}${ent.name}`, { pkgName: m.name, version: m.version });
430
395
  }
431
396
  };
@@ -449,7 +414,8 @@ function reuseExisting(targets, hostKey) {
449
414
  if (agg === null) continue;
450
415
  const map = new Map();
451
416
  for (const [orig, spec] of Object.entries(agg.dependencies ?? {})) {
452
- if (orig === 'argon2') continue; // argon2 由本脚本重建
417
+ // 旧的聚合包清单里可能仍带 argon2(已随 code-server 服务层移除)
418
+ if (orig === 'argon2') continue;
453
419
  const hit = /^npm:(.+)@([^@]+)$/.exec(spec);
454
420
  if (hit === null) throw new Error(`无法解析聚合包依赖 ${orig}: ${spec}`);
455
421
  map.set(orig, { pkgName: hit[1], version: hit[2] });
@@ -495,10 +461,10 @@ function reuseExisting(targets, hostKey) {
495
461
  for (const [orig, info] of hostSet) {
496
462
  repack.set(orig, { pkg: { version: info.version }, platformSpecific: info.pkgName.endsWith(`-${knownTarget}`) });
497
463
  }
498
- // 已有原生包目录 → plan 项;顺带清掉将被重建的 code-server / argon2 目录
464
+ // 已有原生包目录 → plan 项;顺带清掉将被重建的树目录(旧 code-server / argon2 目录一并清理)
499
465
  const reusedItems = [];
500
466
  for (const ent of readdirSync(buildRoot)) {
501
- if (/^code-server/.test(ent) || /^argon2-/.test(ent)) {
467
+ if (ent === 'vscode' || /^code-server/.test(ent) || /^argon2-/.test(ent)) {
502
468
  rmSync(join(buildRoot, ent), { recursive: true, force: true });
503
469
  continue;
504
470
  }
@@ -573,6 +539,21 @@ function main() {
573
539
  const [platform, arch] = pkg.pkgName.match(/-(win32|darwin|linux)-(arm64|x64)$/).slice(1);
574
540
  m.os = [platform];
575
541
  m.cpu = [arch];
542
+ if (platform === 'win32') {
543
+ // 交叉编译产物静态校验:.node 的 PE machine 必须是目标架构(0x8664=x64 / 0xaa64=arm64)
544
+ const want = arch === 'x64' ? '0x8664' : '0xaa64';
545
+ const files = nodeFilesOf(dir);
546
+ const wrong = files
547
+ .map((file) => [file, peMachine(file)])
548
+ .filter(([, machine]) => machine !== null && machine !== want);
549
+ if (files.length > 0 && wrong.length === files.length) {
550
+ throw new Error(`${pkg.pkgName}: 所有 .node 架构都不是 ${want}`
551
+ + `(${wrong.map(([file, machine]) => `${relative(dir, file)}=${machine}`).join(', ')})`);
552
+ }
553
+ if (wrong.length > 0) {
554
+ console.warn(` ⚠ ${pkg.pkgName}: ${wrong.length} 个 .node 架构不符(期望 ${want})`);
555
+ }
556
+ }
576
557
  }
577
558
  for (const fld of ['dependencies', 'optionalDependencies']) {
578
559
  if (!m[fld]) continue;
@@ -588,15 +569,9 @@ function main() {
588
569
  const plan = [];
589
570
  // 0) 已打包的原生包(--reuse 模式)
590
571
  for (const item of reusedItems) plan.push(item);
591
- // 1) code-server 本体(平台无关,1 个包)+ argon2 平台二进制包(每目标 1 个,与其它原生包同一套规则)
592
- const codeServerPkg = buildCodeServerPackage();
593
- plan.push({ dir: codeServerPkg.dir, file: codeServerPkg.file });
594
- const argon2ByTarget = new Map();
595
- for (const target of targets) {
596
- const pkg = buildArgon2Package(target);
597
- argon2ByTarget.set(target, pkg);
598
- plan.push({ dir: pkg.dir, file: pkg.file });
599
- }
572
+ // 1) VS Code 树(平台无关,1 个包)
573
+ const vscodePkg = buildVscodeServerPackage();
574
+ plan.push({ dir: vscodePkg.dir, file: vscodePkg.file });
600
575
  // 2) 全平台重打包包(从 host 树;--reuse 时用已打包目录)
601
576
  if (!REUSE) {
602
577
  for (const [name, r] of repack) {
@@ -624,7 +599,7 @@ function main() {
624
599
  }
625
600
  if (crossTree !== null) rmSync(crossTree, { recursive: true, force: true });
626
601
  }
627
- // 3) 聚合包(每平台一个):dependencies 用 npm: 别名把重打包包装回原名(含 argon2)
602
+ // 3) 聚合包(每平台一个):dependencies 用 npm: 别名把重打包包装回原名
628
603
  for (const target of targets) {
629
604
  const map = byTarget.get(target);
630
605
  const aggName = `${SCOPE}/dsh-code-server-runtime-${target}`;
@@ -632,13 +607,11 @@ function main() {
632
607
  mkdirSync(aggDir, { recursive: true });
633
608
  const deps = {};
634
609
  for (const [name] of repack) deps[name] = `npm:${map.get(name).pkgName}@${map.get(name).version}`;
635
- // argon2:code-server 本体包不带原生二进制,由这里按平台装回原名 "argon2"
636
- const argon2 = argon2ByTarget.get(target);
637
- if (argon2 !== undefined) deps.argon2 = `npm:${argon2.name}@${argon2.version}`;
638
610
  writeFileSync(join(aggDir, 'package.json'), JSON.stringify({
639
611
  name: aggName,
640
612
  version: pluginVersion,
641
- description: `Prebuilt native modules for code-server on ${target} (argon2, node-pty, @vscode/sqlite3, kerberos, …), `
613
+ description: `Prebuilt native modules for the VS Code server tree on ${target} `
614
+ + '(node-pty, @vscode/sqlite3, kerberos, @vscode/spdlog, …), '
642
615
  + 'so that installing the VS Code inner dependencies needs no build approval or C++ toolchain.',
643
616
  os: [target.split('-')[0]],
644
617
  cpu: [target.split('-')[1]],
@@ -652,19 +625,19 @@ function main() {
652
625
  writeFileSync(join(OUT, 'pack-plan.json'), JSON.stringify(plan, null, 2) + '\n', 'utf8');
653
626
  console.log(`[repack] 生成 ${plan.length} 个待打包目录 → repack/ (计划:repack/pack-plan.json)`);
654
627
 
655
- // 4) 改写插件 package.json 的依赖:纯 JS 直装集 + code-server 本体包(平台无关);
656
- // 平台专属的(16 个原生包 + argon2)通过聚合包按 os/cpu 自动选。
628
+ // 4) 改写插件 package.json 的依赖:纯 JS 直装集 + VS Code 树包(平台无关);
629
+ // 平台专属的(原生包)通过聚合包按 os/cpu 自动选。
657
630
  const pkgFile = join(pkgRoot, 'package.json');
658
631
  const pluginPkg = readJson(pkgFile);
659
632
  pluginPkg.dependencies = Object.fromEntries([
660
- [codeServerPkg.name, codeServerPkg.version],
633
+ [vscodePkg.name, vscodePkg.version],
661
634
  ...declare.map((d) => [d.name, d.version]),
662
635
  ].sort(([a], [b]) => a.localeCompare(b)));
663
636
  pluginPkg.optionalDependencies = Object.fromEntries(targets
664
637
  .map((t) => [`${SCOPE}/dsh-code-server-runtime-${t}`, `^${pluginVersion}`]));
665
638
  writeFileSync(pkgFile, JSON.stringify(pluginPkg, null, 2) + '\n', 'utf8');
666
639
  console.log(`[repack] 已写入 package.json:dependencies ${pluginPkg.dependencies ? Object.keys(pluginPkg.dependencies).length : 0} 个`
667
- + `(code-server 本体 + 纯 JS),optionalDependencies ${targets.length} 个平台聚合包`);
640
+ + `(VS Code + 纯 JS),optionalDependencies ${targets.length} 个平台聚合包`);
668
641
 
669
642
  // 5) 可选:直接打包
670
643
  if (DO_PACK) {
@@ -0,0 +1,278 @@
1
+ // scripts/vendor-vscode-server.mjs — 打包期准备「VS Code 树」(取代旧的 vendor-code-server.mjs 全量树)。
2
+ //
3
+ // 模型(重构后):
4
+ // code-server 的 Node 服务层(out/node/**, 136 个依赖)不再随包发布 —— 它由插件自带的
5
+ // lib/launcher.mjs 取代(见 docs/analysis-code-server-as-dsh-plugin.md)。本脚本只保留:
6
+ // lib/vscode/** 已由 code-server 打过补丁的 VS Code server + 内置扩展(≈197MB)
7
+ // out/browser/** serviceWorker.js
8
+ // src/browser/** favicon / PWA 图标 / robots.txt
9
+ // LICENSE, ThirdPartyNotices.txt
10
+ // 并生成树根 package.json(版本 = 上游 code-server 版本,便于版本比对)。
11
+ //
12
+ // 产物:
13
+ // vendor/vscode/ 本地开发树(--dev-links 时把 VS Code 内部依赖用 junction 补上,可直接跑)
14
+ // vendor/VENDOR.json 版本与来源元数据
15
+ //
16
+ // 用法:
17
+ // node scripts/vendor-vscode-server.mjs [--check] [--force]
18
+ // node scripts/vendor-vscode-server.mjs --from <code-server 树> # 从现有树复制(最快)
19
+ // node scripts/vendor-vscode-server.mjs --version 4.136.2 # 从 registry 安装
20
+ // node scripts/vendor-vscode-server.mjs --dev-links # 额外补 lib/vscode/node_modules
21
+ import { spawnSync } from 'node:child_process';
22
+ import {
23
+ cpSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync,
24
+ rmSync, statSync, symlinkSync, writeFileSync,
25
+ } from 'node:fs';
26
+ import { dirname, join, resolve } from 'node:path';
27
+ import { fileURLToPath } from 'node:url';
28
+ import { homedir } from 'node:os';
29
+
30
+ const here = dirname(fileURLToPath(import.meta.url));
31
+ const pkgRoot = resolve(here, '..');
32
+ const vendorDir = join(pkgRoot, 'vendor');
33
+ const vendorTree = join(vendorDir, 'vscode');
34
+ const legacyTree = join(vendorDir, 'code-server'); // 旧全量树(回退用,不再生成)
35
+ const workRoot = join(pkgRoot, '.vendor-tmp');
36
+ const npmCache = join(pkgRoot, '.npm-cache');
37
+
38
+ function argValues(name) {
39
+ const out = [];
40
+ for (let i = 0; i < process.argv.length - 1; i += 1) {
41
+ if (process.argv[i] !== name) continue;
42
+ for (const part of String(process.argv[i + 1]).split(',')) {
43
+ const v = part.trim();
44
+ if (v !== '') out.push(v);
45
+ }
46
+ }
47
+ return out;
48
+ }
49
+ function argValue(name) {
50
+ const all = argValues(name);
51
+ return all.length > 0 ? all[all.length - 1] : null;
52
+ }
53
+ const FORCE = process.argv.includes('--force');
54
+ const DEV_LINKS = process.argv.includes('--dev-links');
55
+ const FROM = argValue('--from');
56
+ const PINNED = argValue('--version') || process.env.DSHCS_CODE_SERVER_VERSION || null;
57
+
58
+ function run(cmd, args, cwd, extraEnv) {
59
+ const useShell = process.platform === 'win32' && /\.(cmd|bat)$/i.test(cmd);
60
+ console.log(`[vendor] $ ${cmd} ${args.join(' ')}${cwd ? ` (cwd=${cwd})` : ''}`);
61
+ const res = spawnSync(cmd, args, {
62
+ cwd, stdio: 'inherit', shell: useShell,
63
+ env: extraEnv ? { ...process.env, ...extraEnv } : process.env,
64
+ });
65
+ if (res.status !== 0) throw new Error(`${cmd} ${args.join(' ')} failed (${res.status})`);
66
+ }
67
+
68
+ function npm(args, cwd) {
69
+ const env = { npm_config_cache: npmCache, npm_config_update_notifier: 'false' };
70
+ const cli = join(dirname(process.execPath), 'node_modules', 'npm', 'bin', 'npm-cli.js');
71
+ if (existsSync(cli)) run(process.execPath, [cli, ...args], cwd, env);
72
+ else run(process.platform === 'win32' ? 'npm.cmd' : 'npm', args, cwd, env);
73
+ }
74
+
75
+ const readJson = (file) => { try { return JSON.parse(readFileSync(file, 'utf8')); } catch { return null; } };
76
+
77
+ async function latestVersion() {
78
+ const res = await fetch('https://registry.npmjs.org/code-server/latest');
79
+ if (!res.ok) throw new Error(`registry responded ${res.status}`);
80
+ const json = await res.json();
81
+ if (typeof json.version !== 'string') throw new Error('registry payload has no version');
82
+ return json.version;
83
+ }
84
+
85
+ function sizeMB(dir) {
86
+ let total = 0;
87
+ const stack = [dir];
88
+ while (stack.length > 0) {
89
+ const cur = stack.pop();
90
+ let entries;
91
+ try { entries = readdirSync(cur, { withFileTypes: true }); } catch { continue; }
92
+ for (const ent of entries) {
93
+ const full = join(cur, ent.name);
94
+ if (ent.isDirectory()) stack.push(full);
95
+ else if (ent.isFile()) { try { total += statSync(full).size; } catch { /* ignore */ } }
96
+ }
97
+ }
98
+ return Math.round((total / 1024 / 1024) * 10) / 10;
99
+ }
100
+
101
+ /** 默认源树:已安装 profile 里最新的 dshcs-code-server 树 → 旧 vendor 树。 */
102
+ function defaultSourceTree() {
103
+ const dshHome = process.env.DSH_HOME || join(process.env.USERPROFILE ?? homedir(), '.dsh');
104
+ const profiles = join(dshHome, 'profiles');
105
+ const cands = [];
106
+ try {
107
+ for (const name of readdirSync(profiles)) {
108
+ cands.push(join(profiles, name, 'node_modules', '@jinsiyu', 'dshcs-code-server', 'code-server'));
109
+ }
110
+ } catch { /* ignore */ }
111
+ cands.push(legacyTree);
112
+ for (const c of cands) if (existsSync(join(c, 'lib', 'vscode', 'out', 'server-main.js'))) return c;
113
+ return null;
114
+ }
115
+
116
+ /** 复制策略:只留服务 IDE 必需的部分。
117
+ * keep: lib/vscode/**、out/browser/**、src/browser/**(除 pages)、LICENSE、ThirdPartyNotices.txt、README.md
118
+ * 排除: node_modules/**、out/node/**、out/common/**、src/browser/pages/**、lib/vscode/(extensions/)node_modules/** */
119
+ function copySlim(src, dest) {
120
+ rmSync(dest, { recursive: true, force: true });
121
+ mkdirSync(dest, { recursive: true });
122
+ const dropDirs = [
123
+ 'node_modules',
124
+ join('out', 'node'),
125
+ join('out', 'common'),
126
+ join('src', 'browser', 'pages'),
127
+ join('lib', 'vscode', 'node_modules'),
128
+ join('lib', 'vscode', 'extensions', 'node_modules'),
129
+ ].map((p) => p.replaceAll('\\', '/'));
130
+ const keepFiles = new Set(['LICENSE', 'ThirdPartyNotices.txt', 'README.md']);
131
+ const keepRootDirs = new Set(['lib', 'out', 'src']);
132
+ cpSync(src, dest, {
133
+ recursive: true,
134
+ dereference: false,
135
+ maxRetries: 6,
136
+ retryDelay: 250,
137
+ filter: (source) => {
138
+ const rel = source.slice(src.length + 1).replaceAll('\\', '/');
139
+ if (rel === '') return true;
140
+ if (dropDirs.some((d) => rel === d || rel.startsWith(`${d}/`))) return false;
141
+ const top = rel.split('/')[0];
142
+ if (rel.includes('/')) return keepRootDirs.has(top);
143
+ return keepRootDirs.has(top) || keepFiles.has(top);
144
+ },
145
+ });
146
+ }
147
+
148
+ /** 把源树的 VS Code 内部依赖 junction 复刻到目标树(开发期直接可跑用)。
149
+ * 必须用 type:'junction'(Windows 下 symlink 需要权限,cpSync 复制链接会 EPERM)。 */
150
+ function linkInnerDeps(srcTree, destTree) {
151
+ const pairs = [
152
+ [join('lib', 'vscode', 'node_modules'), join('lib', 'vscode', 'package.json')],
153
+ [join('lib', 'vscode', 'extensions', 'node_modules'), join('lib', 'vscode', 'extensions', 'package.json')],
154
+ ];
155
+ let linked = 0;
156
+ const failed = [];
157
+ for (const [relNm] of pairs) {
158
+ const srcNm = join(srcTree, relNm);
159
+ const dstNm = join(destTree, relNm);
160
+ if (!existsSync(srcNm)) continue;
161
+ mkdirSync(dstNm, { recursive: true });
162
+ const entries = [];
163
+ for (const ent of readdirSync(srcNm, { withFileTypes: true })) {
164
+ if (ent.name.startsWith('.')) continue;
165
+ if (ent.isDirectory() && ent.name.startsWith('@')) {
166
+ for (const sub of readdirSync(join(srcNm, ent.name), { withFileTypes: true })) {
167
+ if (sub.name.startsWith('.')) continue;
168
+ entries.push(`${ent.name}/${sub.name}`);
169
+ }
170
+ } else {
171
+ entries.push(ent.name);
172
+ }
173
+ }
174
+ for (const name of entries) {
175
+ const from = join(srcNm, name);
176
+ const to = join(dstNm, name);
177
+ let target = from;
178
+ try { if (lstatSync(from).isSymbolicLink()) target = readlinkSync(from); } catch { /* use as-is */ }
179
+ try {
180
+ mkdirSync(dirname(to), { recursive: true });
181
+ rmSync(to, { recursive: true, force: true });
182
+ symlinkSync(target, to, 'junction');
183
+ linked += 1;
184
+ } catch (error) {
185
+ failed.push(`${name}: ${error.code ?? error.message}`);
186
+ }
187
+ }
188
+ }
189
+ return { linked, failed };
190
+ }
191
+
192
+ async function main() {
193
+ const existing = readJson(join(vendorTree, 'package.json'))?.version ?? null;
194
+
195
+ if (process.argv.includes('--check')) {
196
+ const latest = await latestVersion();
197
+ console.log(`[vendor] 内置 VS Code 树: ${existing ?? '(无)'} | 上游 code-server latest: ${latest}`);
198
+ console.log(existing === latest ? '[vendor] 已是最新' : '[vendor] 有新版本:pnpm run vendor:latest → repack:build → publish:repacks → 版本 +1 → pnpm pack');
199
+ return;
200
+ }
201
+
202
+ const skip = existing !== null && !FORCE && FROM === null && PINNED === null;
203
+ let codeServerVersion = existing;
204
+ let sourceKind = 'registry';
205
+ let sourceTree = null;
206
+
207
+ if (skip) {
208
+ console.log(`[vendor] vendor/vscode 已存在(code-server@${existing});跳过(用 --force 重建)`);
209
+ } else if (FROM !== null || (sourceTree = defaultSourceTree()) !== null) {
210
+ const src = FROM ?? sourceTree;
211
+ codeServerVersion = readJson(join(src, 'package.json'))?.version ?? null;
212
+ if (codeServerVersion === null) throw new Error(`--from ${src} 不是一棵 code-server 树`);
213
+ console.log(`[vendor] 源树 ${src}(code-server@${codeServerVersion})→ vendor/vscode/`);
214
+ copySlim(src, vendorTree);
215
+ sourceKind = FROM !== null ? `snapshot:${src}` : `local:${src}`;
216
+ } else {
217
+ codeServerVersion = PINNED ?? await latestVersion();
218
+ console.log(`[vendor] 从 registry 准备 code-server@${codeServerVersion} …`);
219
+ const tmp = join(workRoot, `source-${process.pid}`);
220
+ rmSync(tmp, { recursive: true, force: true });
221
+ mkdirSync(tmp, { recursive: true });
222
+ try {
223
+ npm(['install', `code-server@${codeServerVersion}`, '--ignore-scripts', '--omit=dev',
224
+ '--no-audit', '--no-fund', '--no-save', '--prefix', tmp], tmp);
225
+ const tree = join(tmp, 'node_modules', 'code-server');
226
+ if (!existsSync(join(tree, 'lib', 'vscode', 'out', 'server-main.js'))) throw new Error('code-server 安装不完整');
227
+ copySlim(tree, vendorTree);
228
+ } finally {
229
+ rmSync(tmp, { recursive: true, force: true });
230
+ }
231
+ }
232
+
233
+ const vscodePkg = readJson(join(vendorTree, 'lib', 'vscode', 'package.json')) ?? {};
234
+ const product = readJson(join(vendorTree, 'lib', 'vscode', 'product.json')) ?? {};
235
+ const productPath = `${product.quality ?? 'oss'}-${product.commit ?? 'dev'}`;
236
+ const layout = { name: 'dshcs-vscode-tree', private: true, version: codeServerVersion };
237
+ layout.vscodeVersion = vscodePkg.version ?? null;
238
+ layout.productPath = productPath;
239
+ writeFileSync(join(vendorTree, 'package.json'), JSON.stringify(layout, null, 2) + '\n', 'utf8');
240
+ writeFileSync(join(vendorTree, 'VENDOR-TREE.json'), JSON.stringify({
241
+ codeServerVersion,
242
+ vscodeVersion: vscodePkg.version ?? null,
243
+ productPath,
244
+ preparedAt: new Date().toISOString(),
245
+ source: sourceKind,
246
+ }, null, 2) + '\n', 'utf8');
247
+
248
+ let links = { linked: 0, failed: [] };
249
+ if (DEV_LINKS) {
250
+ const src = FROM ?? defaultSourceTree();
251
+ if (src === null) console.warn('[vendor] --dev-links: 找不到可复刻 junction 的源树,跳过');
252
+ else {
253
+ links = linkInnerDeps(src, vendorTree);
254
+ console.log(`[vendor] dev: 补 VS Code 内部依赖 junction ${links.linked} 个${links.failed.length > 0 ? `(失败 ${links.failed.length}: ${links.failed.slice(0, 5).join(', ')})` : ''}`);
255
+ }
256
+ }
257
+
258
+ const meta = {
259
+ codeServerVersion,
260
+ vscodeVersion: vscodePkg.version ?? null,
261
+ productPath,
262
+ layout: 'vscode-only',
263
+ preparedAt: new Date().toISOString(),
264
+ source: sourceKind,
265
+ node: process.version,
266
+ platform: process.platform,
267
+ arch: process.arch,
268
+ sizeMB: sizeMB(vendorTree),
269
+ };
270
+ writeFileSync(join(vendorDir, 'VENDOR.json'), JSON.stringify(meta, null, 2) + '\n', 'utf8');
271
+ console.log(`[vendor] 完成:vendor/vscode (code-server@${codeServerVersion}, VS Code ${meta.vscodeVersion}, ${meta.sizeMB} MB, productPath=${productPath})`);
272
+ if (!DEV_LINKS) {
273
+ console.log('[vendor] 提示:本树不含 VS Code 内部依赖;运行时由 lib/native.js 的 ensureRuntimeLayout() 补 junction'
274
+ + '(开发期可直接 `--dev-links` 复刻)。');
275
+ }
276
+ }
277
+
278
+ await main();
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "codeServerVersion": "4.136.2",
3
- "preparedAt": "2026-09-10T07:13:09.707Z",
3
+ "vscodeVersion": "1.136.1",
4
+ "productPath": "stable-8d5f383f301ca20681f5b6606b8207d9dc87bdd8",
5
+ "layout": "vscode-only",
6
+ "preparedAt": "2026-09-10T08:30:15.204Z",
4
7
  "source": "registry",
5
8
  "node": "v24.13.1",
6
9
  "platform": "win32",
7
10
  "arch": "arm64",
8
- "sizeMB": 232.3
11
+ "sizeMB": 197.2
9
12
  }