dsh-code-server-app 0.3.44 → 0.3.46

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/README.en.md CHANGED
@@ -10,7 +10,7 @@
10
10
  > - Microsoft **open-source** extensions (Python, TypeScript debugger, ESLint, …) are mirrored on Open VSX and install normally by search;
11
11
  > - **If you need a proprietary Microsoft extension**: download the `.vsix` from the Marketplace page and install it manually with `code-server --install-extension <file>` (or drop it into `--extensions-dir`).
12
12
 
13
- A static profile plugin (npm package with host + client bundle) that ships the **VS Code server tree** from a [code-server](https://github.com/coder/code-server) release as a **platform-independent dependency package** (pack-time artifact `vendor/vscode` → `@jinsiyu/dshcs-vscode-server`, no install scripts, no postinstall). The code-server **Node service layer is replaced by the plugin's own `lib/launcher.mjs`**: it drives `<tree>/lib/vscode/out/server-main.js` (`loadCodeWithNls()` / `createServer()` / `handleRequest()` / `handleUpgrade()`) directly and re-adds the few HTTP endpoints code-server used to provide (`/healthz`, `/manifest.json`, `/_static/*`, `/proxy/:port`). The 16 native modules (node-pty / @vscode/sqlite3 / spdlog / …) come from `@jinsiyu/dshcs-*-win32-<arch>` platform packages selected automatically per architecture by the platform aggregator. VS Code's inner dependencies and the prebuilt native modules are **all installed by the package manager together with the plugin** — no global npm install, no `bin` configuration, no profile config changes, no second install command, **no argon2/C++ toolchain**.
13
+ A static profile plugin (npm package with host + client bundle) that ships the **VS Code server tree** from a [code-server](https://github.com/coder/code-server) release as a **platform-independent dependency package** (pack-time artifact `vendor/vscode` → `@jinsiyu/dshcs-vscode-server`, no install scripts, no postinstall). The code-server **Node service layer is replaced by the plugin's own `lib/launcher.mjs`**: it drives `<tree>/lib/vscode/out/server-main.js` (`loadCodeWithNls()` / `createServer()` / `handleRequest()` / `handleUpgrade()`) directly and re-adds the few HTTP endpoints code-server used to provide (`/healthz`, `/manifest.json`, `/_static/*`, `/proxy/:port`). The 16 native modules (node-pty / @vscode/sqlite3 / spdlog / …) come from `@jinsiyu/dshcs-*` sub-packages declared **directly on the plugin's dependency table** under their real names (os/cpu-gated per target), with the original import names restored by runtime junctions. VS Code's inner dependencies and the prebuilt native modules are **all installed by the package manager together with the plugin** — no global npm install, no `bin` configuration, no profile config changes, no second install command, **no argon2/C++ toolchain**.
14
14
 
15
15
  > Since 0.3.22 the ask panel renders the session's new content with **DSH's own Markdown renderer** (the same
16
16
  > renderer and design tokens as the DSH UI; new content only) and can **answer approval requests in place**
@@ -390,11 +390,12 @@ Diagnostics: `GET /api/code-server/status` exposes
390
390
  "Install the plugin (script-free install; code-server bundled)" below — `pnpm install` → `pnpm run build:client` →
391
391
  `pnpm run vendor:vscode` → `pnpm pack` + `dsh plugin --profile web add`.
392
392
 
393
- > Verified locally (BM: Windows 11 ARM64): the whole tree/dependency chain is supplied by per-platform
394
- > sub-packages — the tree package `@jinsiyu/dshcs-vscode-server` (currently 4.137.0, a 50.8 MB tarball),
395
- > the pure-JS inner dependencies directly in the plugin's `dependencies`, and 16 prebuilt native modules
396
- > re-exported under their original names by the aggregator `@jinsiyu/dsh-code-server-runtime-win32-<arch>`
397
- > (picked by os/cpu) → healthz 200 → stopped → fully recycled.
393
+ > Verified locally (BM: Windows 11 ARM64): the whole tree/dependency chain hangs directly off the plugin's
394
+ > dependency table — the tree package `@jinsiyu/dshcs-vscode-server` (currently 4.137.0, a 50.8 MB tarball),
395
+ > the pure-JS inner dependencies plus the 8 platform-independent repacks in `dependencies`, and the 8
396
+ > platform-specific repacks (win32-arm64 / win32-x64) in `optionalDependencies` with their own os/cpu gates;
397
+ > the original names are restored by junctions created at runtime (`lib/native.js`)
398
+ > → healthz 200 → stopped → fully recycled.
398
399
  > (The 0.1.37-era "one big platform package" layout is gone — see "Upgrading the VS Code tree" below.)
399
400
 
400
401
  ## Packaging (how to build the tarball)
@@ -419,7 +420,7 @@ pnpm run promote -- <version>
419
420
  > `pnpm run promote -- <version>` (= `npm dist-tag add dsh-code-server-app@<version> latest`)
420
421
  > **after the user restarts `dsh web` and confirms it works**. That way
421
422
  > `dsh plugin add dsh-code-server-app` (no version) — and anything else resolving `latest` — never picks up an
422
- > unverified build. Sub-packages (`@jinsiyu/dshcs-*`, the aggregators) are referenced by exact/caret versions,
423
+ > unverified build. Sub-packages (`@jinsiyu/dshcs-*`) are referenced by exact versions,
423
424
  > so their dist-tags do not affect resolution, but they default to `next` as well.
424
425
  > Inspect the current tags with `npm dist-tag ls dsh-code-server-app`.
425
426
 
@@ -436,8 +437,8 @@ pnpm run promote -- <version>
436
437
  | Sub-package | Content | os/cpu |
437
438
  |---|---|---|
438
439
  | `@jinsiyu/dshcs-vscode-server@<code-server version>` | the trimmed VS Code tree (`lib/vscode` + `out/browser` + `src/browser`; **without** code-server's `out/node` and its 136 runtime deps) | platform-independent |
439
- | `@jinsiyu/dshcs-<name>[-win32-<arch>]` ×24 | the VS Code inner packages that need building (node-pty / @vscode/sqlite3 / kerberos / koffi / ssh2 / …) | gated when platform-specific |
440
- | `@jinsiyu/dsh-code-server-runtime-win32-<arch>` | platform aggregator: its `dependencies` map those 16 natives back to their original names via `npm:` aliases | win32-<arch> |
440
+ | `@jinsiyu/dshcs-<name>[-win32-<arch>]` ×16 | the VS Code inner packages that need building (node-pty / @vscode/sqlite3 / kerberos / koffi / ssh2 / …) | gated when platform-specific |
441
+ | `lib/vendored.json` (**not** a package) | the "original name → repack sub-package" table shipped inside the plugin; `lib/native.js` uses it to create the junctions. Since 0.3.45 there is **no platform aggregator** | — |
441
442
 
442
443
  | Goal | Command |
443
444
  |---|---|
@@ -445,7 +446,7 @@ pnpm run promote -- <version>
445
446
  | **Pin a version** | `pnpm run vendor:vscode -- --version 4.137.0` |
446
447
  | **Snapshot from an existing tree** | `pnpm run vendor:vscode -- --from <code-server dir>` (seconds) |
447
448
  | **Rebuild every sub-package** | `pnpm run repack:build -- --target win32-arm64,win32-x64 --pack` (without `--from` it npm-installs and compiles the source tree itself — slow) |
448
- | **Rebuild only the tree/aggregator packages** | `node scripts/vendor-repacks.mjs --reuse --target win32-arm64,win32-x64 --pack` (reuses the natives already in `repack/build`) |
449
+ | **Rebuild only the tree + dependency table** | `node scripts/vendor-repacks.mjs --reuse --target win32-arm64,win32-x64 --pack` (reuses the natives already in `repack/build`; also rewrites `lib/vendored.json` and the plugin dependency table) |
449
450
  | **Publish sub-packages** | `pnpm run publish:repacks` (`--dry-run` to preview; `--only <substr>` to filter; `--otp <code>` / `--limit N` for 2FA) |
450
451
  | **Publish the plugin itself** | `pnpm run publish:plugin` (publishes the exact tarball that was verified; no re-packing; default dist-tag `next`) |
451
452
  | **Promote `latest`** | `pnpm run promote -- <version>` (only after the user restarted and confirmed; `--dry-run` shows the current tags first) |
@@ -474,10 +475,13 @@ The main package is only **~110KB** (the plugin's own code plus the launcher); e
474
475
  `@jinsiyu/dshcs-code-server/code-server` is still recognised as a fallback);
475
476
  - the **pure-JS part** of VS Code's inner dependencies (35 packages: xterm / katex / typescript / ws / tar …) is
476
477
  declared in the plugin's `dependencies` and installed by pnpm into the profile's `node_modules` (hoisted);
477
- - the **binary part** comes entirely from `@jinsiyu/dshcs-*` platform packages: 16 native packages mapped back to their
478
- **original names** (`node-pty` / `@vscode/sqlite3` / `@vscode/spdlog` / …) by the **platform aggregator**
479
- `@jinsiyu/dsh-code-server-runtime-win32-<arch>` using `npm:` aliases; the aggregators sit in the plugin's
480
- `optionalDependencies`, so pnpm auto-selects the right platform;
478
+ - the **binary part** comes entirely from `@jinsiyu/dshcs-*` sub-packages, declared **directly on the plugin's own
479
+ dependency table** (since 0.3.45): the 8 platform-independent repacks (`node-pty` / `koffi` / `ssh2` /
480
+ `cpu-features` / `@parcel/watcher` / `@vscode/fs-copyfile` / `@vscode/proxy-agent` / `@microsoft/mxc-sdk`) go into
481
+ `dependencies` under their real names; the 8 platform-specific ones (`@vscode/sqlite3` / `spdlog` / `kerberos` /
482
+ `deviceid` / `native-watchdog` / `windows-registry` / `windows-process-tree` / `windows-ca-certs`) go into
483
+ `optionalDependencies` once per target (real names + their own os/cpu gates), so one command picks the right arch;
484
+ the **original names** are restored at runtime by junctions created from `lib/vendored.json`;
481
485
  - consequently the dependency graph contains **no package with pre/install/postinstall or a `binding.gyp`** →
482
486
  no profile `allowBuilds`, no build script ever runs, and **the user machine needs no C++ toolchain**;
483
487
  - **upgrading the plugin no longer re-downloads the tree**: the tree package is cached by version
@@ -500,10 +504,15 @@ The main package is only **~110KB** (the plugin's own code plus the launcher); e
500
504
  **removed** (the built `.node` and every runtime file stay) → sibling packages in its dependency list become `npm:`
501
505
  aliases → platform-specific ones get `os`/`cpu` plus a `-<platform>-<arch>` suffix. For win32 targets the script also
502
506
  verifies each `.node` PE machine (0x8664=x64 / 0xaa64=arm64) so a cross-compiled artifact cannot ship the wrong arch;
503
- - **the platform aggregator** maps those repacks back to their original names (e.g.
504
- `"node-pty": "npm:@jinsiyu/dshcs-node-pty@1.2.0-beta.15"`), so VS Code's `import('node-pty')` needs no change; the
505
- aggregator is itself `os`/`cpu` gated, and the plugin declares both win32-arm64 and win32-x64 in
506
- `optionalDependencies`, so one command picks the right one;
507
+ - **how the original names come back** (since 0.3.45): a repack's real name is `@<scope>/dshcs-<name>` while VS Code
508
+ imports `node-pty` / `@vscode/sqlite3`; pack time writes the "original name → real name" table into
509
+ `lib/vendored.json` (shipped with the plugin) and `lib/native.js` creates `<tree>/node_modules/<original name>`
510
+ junctions to the real directories (idempotent, self-healing).
511
+ **Why the old "platform aggregator + `npm:` aliases" is gone**: pnpm's incremental hoisted install drops those
512
+ aliased packages when they sit inside an **optional subtree** (measured: 9 of 16 missing) while dsh-desktop
513
+ validates the dependency graph right after the install ⇒ the first install always failed with `requires missing`;
514
+ with real-name direct dependencies the same install command plus the validator's own predicate passes end to end
515
+ (reproduction in `docs/desktop-first-install-root-cause.md`);
507
516
  - **resolution path**: the host finds the tree with `require.resolve('@jinsiyu/dshcs-vscode-server/package.json')`
508
517
  (then the inner `vscode/` directory) and the entry is `vscode/lib/vscode/out/server-main.js`; VS Code's inner deps are
509
518
  resolved upwards from that root (`vscode/lib/vscode/node_modules` → package `node_modules` → `<profile>/node_modules`).
@@ -511,8 +520,8 @@ The main package is only **~110KB** (the plugin's own code plus the launcher); e
511
520
  - **runtime layout self-healing** (`ensureRuntimeLayout()` in `lib/native.js`, idempotent, run **at activation before
512
521
  `envCheck` and again before every start**): the host adds two kinds of **junctions** (Windows junctions / POSIX dir
513
522
  symlinks) into the tree:
514
- 1. `ensureAliasLinks()`: re-links the native aliases the aggregator carries into `<tree>/node_modules` — pnpm nests
515
- `os`/`cpu`-gated packages under the aggregator's own `node_modules`, and `lib/vscode/out/server-main.js` uses
523
+ 1. `ensureAliasLinks()`: links the 16 **original names** listed in `lib/vendored.json` into `<tree>/node_modules`
524
+ — the real-name packages live in the plugin's dependency graph, and `lib/vscode/out/server-main.js` uses
516
525
  **ESM imports** (ESM ignores `NODE_PATH`), so a missing link means an immediate 500;
517
526
  2. `ensureInnerModuleLinks()`: restores VS Code's **inner dependency directories**
518
527
  `lib/vscode/node_modules` and `lib/vscode/extensions/node_modules` from the two `package.json` files — the trimmed
package/README.md CHANGED
@@ -15,7 +15,7 @@
15
15
  无安装脚本、无 postinstall);**code-server 的 Node 服务层已由插件自带的 `lib/launcher.mjs` 取代**
16
16
  (它直接驱动 `<树>/lib/vscode/out/server-main.js` 的 `loadCodeWithNls()` / `createServer()` / `handleRequest()` /
17
17
  `handleUpgrade()`,并补上 `/healthz`、`/manifest.json`、`/_static/*`、`/proxy/:port` 这几条 code-server 原本提供的 HTTP 面);
18
- 原生模块(node-pty / @vscode/sqlite3 / spdlog …)由 `@jinsiyu/dshcs-*-win32-<架构>` 平台包经聚合包按架构自动选中 ——
18
+ 原生模块(node-pty / @vscode/sqlite3 / spdlog …)由 `@jinsiyu/dshcs-*` 子包按**真名直接挂在插件依赖上**、按 os/cpu 自动选中 ——
19
19
  **无需全局 npm 安装、无需配置 `bin`、无需改 profile 配置、无需第二条安装命令、无需 argon2/C++ 工具链**。
20
20
 
21
21
  > 0.2.0 起:**argon2 与 code-server 的 136 个运行时依赖(express / proxy-agent / js-yaml / pem / limiter …)
@@ -365,10 +365,11 @@ host 反向请求不到它。所以编辑器状态只能在扩展主动发起的
365
365
  "打包(如何出包)"执行 `pnpm install` → `pnpm run build:client` → `pnpm run vendor:vscode` →
366
366
  (发布预编译原生包)→ `pnpm pack` + `dsh plugin --profile web add` 即可。
367
367
 
368
- > 本机(BM: Windows 11 ARM64)实测:树/依赖全链路是"平台子包 + 聚合包"供给 ——
369
- > 树包 `@jinsiyu/dshcs-vscode-server`(当前 4.137.0,50.8 MB tgz)、纯 JS 内部依赖直接进插件
370
- > `dependencies`、16 个预编译原生包经平台聚合包 `@jinsiyu/dsh-code-server-runtime-win32-<arch>`
371
- > (按 os/cpu 自动选)装回原名 → healthz 200 → 停止 → 回收全链路验证。
368
+ > 本机(BM: Windows 11 ARM64)实测:树/依赖全链路是"平台子包直挂插件依赖"供给 ——
369
+ > 树包 `@jinsiyu/dshcs-vscode-server`(当前 4.137.0,50.8 MB tgz)、纯 JS 内部依赖与 8 个平台无关
370
+ > 重打包包直接进插件 `dependencies`、8 个平台专属重打包包按 win32-arm64/x64 进
371
+ > `optionalDependencies`(包自带 os/cpu 自动选),原始名字由 `lib/native.js` 补 junction 还原
372
+ > → healthz 200 → 停止 → 回收全链路验证。
372
373
  > (0.1.37 时代的主包形态已废弃,见下方"升级 VS Code 树"。)
373
374
 
374
375
  ## 打包(如何出包)
@@ -398,8 +399,18 @@ pnpm run promote -- <version>
398
399
  > `latest` 只保留「最近一个确认无 bug 的版本」,由 `pnpm run promote -- <version>`
399
400
  > (= `npm dist-tag add dsh-code-server-app@<version> latest`)在**用户重启 dsh web 确认无误后**才推进。
400
401
  > 这样 `dsh plugin add dsh-code-server-app`(不带版本)和任何按 latest 安装的流程都不会拿到未验证的版本。
401
- > 子包(`@jinsiyu/dshcs-*`、聚合包)被依赖以精确/插入符版本引用,dist-tag 不影响解析,但同样默认发 `next`。
402
+ > 子包(`@jinsiyu/dshcs-*`)被依赖以**精确版本**引用(平台专属的按目标各钉一份),dist-tag 不影响解析,但同样默认发 `next`。
402
403
  > 查看当前标签:`npm dist-tag ls dsh-code-server-app`。
404
+ >
405
+ > **desktop profile 不走命令行安装**(2026-09-13 起的约定):对 desktop 只做 `pnpm pack` + `publish:plugin`(发 `next`),
406
+ > 由用户在 DSH Desktop 里用**官方安装方式**自行安装;不要再把 tarball 文件级覆盖进 `~/.dsh/profiles/desktop` ——
407
+ > 那条路会绕过 desktop 应用自己的依赖闭包检查,把真实的解析问题掩盖成"装上了但行为怪"。
408
+ > web profile 仍可照旧安装验证。
409
+ >
410
+ > **0.3.45 起没有平台聚合包**,`requires missing @microsoft/mxc-sdk@npm:…` 那类报错不会再出现。根因(实测):
411
+ > pnpm 的**增量 hoisted 安装**会漏链「可选子树里的 `npm:` 别名包」,16 个里漏 9 个(第一个就是 mxc-sdk),而
412
+ > dsh-desktop 在 `pnpm add` 之后**立刻**校验依赖图 ⇒ 首次安装必失败;重启后应用走「删 node_modules + 完整安装」
413
+ > 才补齐 ⇒ 就是你看到的"重启自己装好了"。复现命令与两条修法见 `docs/desktop-first-install-root-cause.md`。
403
414
 
404
415
  `repack:build`(`scripts/vendor-repacks.mjs`)是**唯一的子包产出脚本**,一次生成:
405
416
 
@@ -407,7 +418,7 @@ pnpm run promote -- <version>
407
418
  |---|---|---|
408
419
  | `@jinsiyu/dshcs-vscode-server@<code-server 版本>` | 精简 VS Code 树(`lib/vscode` + `out/browser` + `src/browser`,**不含** code-server 的 `out/node` 与 136 个运行时依赖) | 平台无关 |
409
420
  | `@jinsiyu/dshcs-<名字>[-win32-<arch>]` ×16 | VS Code 内部依赖里需要构建的原生包(node-pty / @vscode/sqlite3 / kerberos / koffi / ssh2 / spdlog / …) | 平台专属带 os/cpu |
410
- | `@jinsiyu/dsh-code-server-runtime-win32-<arch>` | 平台聚合包:`dependencies` 用 `npm:` 别名把上面 16 个原生包装回原始名字 | win32-<arch> |
421
+ | `lib/vendored.json`(**不是包**) | 「原名 → 重打包子包」表,随插件发布;运行时由 `lib/native.js` 据此补 junction。0.3.45 起**不再产出平台聚合包** | — |
411
422
 
412
423
  > argon2 已随 code-server 服务层一起移除(0.2.0):`auth` 固定 `none`,需要对外访问请用 `serve: dsh`。
413
424
 
@@ -418,7 +429,7 @@ pnpm run promote -- <version>
418
429
  | **从已装好的树快照** | `pnpm run vendor:vscode -- --from <code-server 目录>`(秒级) |
419
430
  | **开发期让树可直接跑** | `pnpm run vendor:vscode -- --dev-links`(额外把 `lib/vscode/node_modules` 用 junction 补上) |
420
431
  | **完整重打子包** | `pnpm run repack:build -- --target win32-arm64,win32-x64 --pack`(不给 `--from` 会自动 npm install 解包 + 编译,耗时) |
421
- | **只重打树包/聚合包** | `node scripts/vendor-repacks.mjs --reuse --target win32-arm64,win32-x64 --pack`(复用 `repack/build` 里已有的原生包,不重新分析源树) |
432
+ | **只重打树包 + 依赖表** | `node scripts/vendor-repacks.mjs --reuse --target win32-arm64,win32-x64 --pack`(复用 `repack/build` 里已有的原生包,不重新分析源树;顺带重写 `lib/vendored.json` 与插件依赖表) |
422
433
  | **发布子包** | `pnpm run publish:repacks`(`--dry-run` 预览;`--only <子串>` 过滤;`--otp <code>` / `--limit N` 应对 2FA) |
423
434
  | **发布插件本体** | `pnpm run publish:plugin`(发布**已验证过的那份 tarball**,不会重新打包;默认 dist-tag = `next`) |
424
435
  | **推进 latest** | `pnpm run promote -- <version>`(用户重启确认无误后;`--dry-run` 先看当前标签) |
@@ -439,6 +450,7 @@ pnpm test:webview # 面板 webview 产物:官方渲染器与令牌打
439
450
  pnpm test:launcher-routes # launcher 的 HTTP 面(起真进程,较慢)
440
451
  pnpm test:workspace-switch # 切工作区不重启进程
441
452
  pnpm test:fullscreen # 打开标签即全屏
453
+ pnpm test:vendored # 重打包表 ↔ 插件依赖表一致(无 npm: 别名 / 无聚合包 / vendored.json 进了 files)
442
454
  ```
443
455
 
444
456
  > `test:bridge-routes` 会把 `DSH_HOME` 指向临时目录(否则它会 adopt 开发机上正在跑的那个实例,
@@ -461,9 +473,13 @@ dsh plugin --profile web add C:\Users\User\Desktop\dsh-code-server-app\dsh-code-
461
473
  `<profile>\node_modules\@jinsiyu\dshcs-vscode-server\vscode`;
462
474
  - VS Code 内部依赖里**纯 JS 的部分**(35 个:xterm / katex / typescript / ws / tar …)也写在插件
463
475
  `dependencies`,由 pnpm 装到 profile 的 `node_modules`(hoisted);
464
- - **二进制部分**全部由 `@jinsiyu/dshcs-*` 平台包提供(每个平台各一份,os/cpu 限定):
465
- 16 个原生包经**平台聚合包** `@jinsiyu/dsh-code-server-runtime-win32-<arch>` 用 `npm:` 别名装回**原始名字**
466
- (`node-pty` / `@vscode/sqlite3` / `@vscode/spdlog` / …),聚合包挂在插件 `optionalDependencies` → pnpm 按架构自动选;
476
+ - **二进制部分**全部由 `@jinsiyu/dshcs-*` 子包提供,且**直接挂在插件依赖上**(0.3.45 起):
477
+ 平台无关的 8 个(`node-pty` / `koffi` / `ssh2` / `cpu-features` / `@parcel/watcher` /
478
+ `@vscode/fs-copyfile` / `@vscode/proxy-agent` / `@microsoft/mxc-sdk`)写进插件 `dependencies`(真名);
479
+ 平台专属的 8 个(`@vscode/sqlite3` / `spdlog` / `kerberos` / `deviceid` / `native-watchdog` /
480
+ `windows-registry` / `windows-process-tree` / `windows-ca-certs`)按 win32-arm64 与 win32-x64
481
+ 各一份写进 `optionalDependencies`(真名 + 包自带 os/cpu)→ 一条命令自动选对架构;
482
+ **原始名字**由 `lib/native.js` 运行时补 junction 还原(见下「运行时布局自愈」);
467
483
  - 因此依赖图里**没有任何带 pre/install/postinstall 或 binding.gyp 的包** →
468
484
  不需要 profile 的 `allowBuilds`、不执行任何构建、**使用者机器不需要 C++ 工具链**;
469
485
  - **升级插件不再重下树**:树包版本按上游 code-server 版本缓存,pnpm 直接复用(约 60MB,解包 ≈197MB)。
@@ -484,18 +500,21 @@ dsh plugin --profile web add C:\Users\User\Desktop\dsh-code-server-app\dsh-code-
484
500
  (保留编译好的 `.node` 与全部运行时文件)→ 依赖里的同集包改成 `npm:` 别名 → 平台专属的加
485
501
  `os`/`cpu` 与 `-<platform>-<arch>` 后缀;win32 目标还会校验 `.node` 的 PE machine
486
502
  (0x8664=x64 / 0xaa64=arm64),防交叉编译产物装错架构;
487
- - **平台聚合包**把重打包包按原始名字装回去(如 `"node-pty": "npm:@jinsiyu/dshcs-node-pty@1.2.0-beta.15"`),
488
- 于是 VS Code 的 `import('node-pty')` 不用改;聚合包本身 `os`/`cpu` 限定,
489
- 插件 `optionalDependencies` 同时声明 win32-arm64 与 win32-x64 两份 → 一条命令自动选对;
503
+ - **原始名字怎么还原**(0.3.45 起):重打包包的真名是 `@<scope>/dshcs-<名字>`,而 VS Code `import` 的是
504
+ `node-pty` / `@vscode/sqlite3` 这类**原名**;打包期把「原名 → 真名」写进 `lib/vendored.json`(随插件发布),
505
+ 运行时由 `lib/native.js` 在 `<树>/node_modules/<原名>` 补 junction 指向真名包(幂等、可自愈)。
506
+ **为什么不再用「平台聚合包 + `npm:` 别名」**:pnpm 的增量 hoisted 安装会漏链**可选子树**里的别名包
507
+ (实测 16 个漏 9 个),而 dsh-desktop 安装后立刻校验依赖图 ⇒ 首次安装必报 requires missing;
508
+ 改成真名直接依赖后,同一条安装命令 + 校验器判据实测全部通过(复现见 `docs/desktop-first-install-root-cause.md`);
490
509
  - **解析路径**:host 用 `require.resolve('@jinsiyu/dshcs-vscode-server/package.json')` 找到运行根
491
510
  (包内子目录 `vscode/`),入口 `vscode/lib/vscode/out/server-main.js`;VS Code 内部依赖从该运行根向上查找
492
511
  (`vscode/lib/vscode/node_modules` → 包 `node_modules` → `<profile>/node_modules`)。
493
512
  (旧全量树 `@jinsiyu/dshcs-code-server/code-server` 仍作为回退被识别。)
494
513
  - **运行时布局自愈**(`lib/native.js` 的 `ensureRuntimeLayout()`,**激活时(先于 envCheck)与每次启动前**幂等执行):
495
514
  host 会在 VS Code 树里补两类 **junction**(Windows junction / POSIX 目录软链):
496
- 1. `ensureAliasLinks()`:把聚合包带回的原生别名补到 `<树>/node_modules`
497
- —— pnpm 会把 `os`/`cpu` 限定的包**嵌套装在聚合包自己的 node_modules 下**,而 VS Code 的
498
- `lib/vscode/out/server-main.js` 用 **ESM import**(ESM 不认 `NODE_PATH`),缺了就直接 500;
515
+ 1. `ensureAliasLinks()`:按 `lib/vendored.json` 把 16 个**原始名字**补到 `<树>/node_modules`
516
+ —— 真名子包装在插件依赖图里,而 VS Code 的 `lib/vscode/out/server-main.js` 用 **ESM import**
517
+ (ESM 不认 `NODE_PATH`),缺了就直接 500;
499
518
  2. `ensureInnerModuleLinks()`:把 VS Code 的**内部依赖目录** `lib/vscode/node_modules` 与
500
519
  `lib/vscode/extensions/node_modules` 按两个 `package.json` 的 `dependencies` 补回老布局
501
520
  —— 精简树里没有这两个目录,**用显式路径拼依赖的代码**
@@ -531,7 +550,7 @@ dsh plugin --profile web add C:\Users\User\Desktop\dsh-code-server-app
531
550
 
532
551
  > 源码路径以 `link:` 安装。开发机上没有 `vendor/vscode` 时先 `pnpm run vendor:vscode -- --dev-links`;
533
552
  > 没有平台子包时 host 会回退到包内 `vendor/code-server`(两种布局都支持)。
534
- > 依赖(内部 JS 依赖 + 平台子包 + 聚合包)同样由 pnpm 安装 —— 本地未发布的 `@jinsiyu/*` 需先发布,
553
+ > 依赖(内部 JS 依赖 + 重打包子包)同样由 pnpm 安装 —— 本地未发布的 `@jinsiyu/*` 需先发布,
535
554
  > 或把 `repack/tgz/*.tgz` 以 `file:` 依赖临时装进 profile(见 `.tmp-verify.mjs`)。
536
555
  >
537
556
  > **改动 client bundle**:编辑 `src/factory.js` 后执行 `pnpm run build:client`
@@ -582,7 +601,8 @@ dsh plugin --profile web add C:\Users\User\Desktop\dsh-code-server-app
582
601
  - **换版本后重新出子包并发布**(全部由同一个脚本):
583
602
  1. `pnpm run repack:build -- --target win32-arm64,win32-x64 --pack` → 新的树包
584
603
  (`@jinsiyu/dshcs-vscode-server@<新版本>`)以及按新内部依赖重建的原生包
585
- (脚本会把新的「纯 JS 直装集」写进插件 `dependencies`、更新两个聚合包版本);
604
+ (脚本会把新的「纯 JS 直装集」写进插件 `dependencies`,并把 16 个重打包包按真名写进
605
+ `dependencies` / `optionalDependencies`、重写 `lib/vendored.json`);
586
606
  2. `pnpm run publish:repacks` → 发布;然后 bump 插件版本 → `pnpm pack` → 发布插件。
587
607
  - `productPath`(`<quality>-<commit>`,客户端 WS 路径的组成)**从 `lib/vscode/product.json` 现算**,
588
608
  升级树后无需改代码 —— 但也意味着切版本后必须重启 dsh web(路由在激活期注册)。
@@ -612,7 +632,7 @@ host 探测顺序:`@jinsiyu/dshcs-vscode-server/vscode`(**0.2.0+ 正式布局**)
612
632
 
613
633
  0.2.7 起卡片**没有**「入口」「依赖安装」「环境检测」三行:入口在右侧栏「开始」页的 Code Server 入口框(或官方的文件点击),
614
634
  诊断信息不再进 UI —— `/api/code-server/status` 的 `env` 字段仍返回
615
- 树版本 / `productPath` / server 入口、VS Code 内部依赖、**预编译原生包**(平台聚合包名 + 已解析模块数),需要时用脚本查或看 host 日志。
635
+ 树版本 / `productPath` / server 入口、VS Code 内部依赖、**预编译原生包**(重打包子包名 + 已解析模块数),需要时用脚本查或看 host 日志。
616
636
 
617
637
  > 卡片改动经 `scope.watch` 实时生效(host 端 status API 同步返回 `keepResident`、`claimExtensions` 与
618
638
  > `fullscreenOnOpen`,客户端立即生效);无需重启 dsh。**新增设置键后首次使用前需重启 dsh web**,
package/lib/index.js CHANGED
@@ -35,7 +35,7 @@ import {
35
35
  vsRoot,
36
36
  vsServerEntry,
37
37
  } from './vendor.js';
38
- import { aliasNodePathDirs, ensureRuntimeLayout, resolveRuntime, runtimePackageName, verifyNatives } from './native.js';
38
+ import { ensureRuntimeLayout, nativeRuntimeStatus } from './native.js';
39
39
  import { MOUNT_PATH, mountOnWebServer } from './serve-dsh.mjs';
40
40
  import { DEFAULT_CLAIM_EXTENSIONS, describeClaimPolicy, normalizeClaimExtensions } from './claim-types.js';
41
41
  import {
@@ -427,13 +427,12 @@ function envCheck() {
427
427
  const root = vsRoot();
428
428
  const entry = vsServerEntry();
429
429
  const inner = checkInnerDeps();
430
- const runtime = resolveRuntime();
431
- const natives = verifyNatives(runtime !== null ? runtime.modules : []);
430
+ const natives = nativeRuntimeStatus();
432
431
  const installed = root !== null ? readTreeVersion(root) : null;
433
432
  const vendored = vendoredVersion();
434
433
  return {
435
434
  ok: entry !== null && inner.missing.length === 0
436
- && runtime !== null && natives.missing.length === 0,
435
+ && natives.resolved > 0 && natives.missing.length === 0,
437
436
  tree: root !== null ? root.replace(/\\/g, '/') : null,
438
437
  entry: entry !== null ? entry.replace(/\\/g, '/') : null,
439
438
  productPath: productPath(root),
@@ -442,16 +441,14 @@ function envCheck() {
442
441
  treeVersion: installed,
443
442
  vendored,
444
443
  upToDate: installed !== null && vendored !== null && installed === vendored,
445
- // 本平台预编译原生产物(平台聚合包 + 它带回原始名字的原生模块)
446
- nativeRuntime: runtime !== null
447
- ? {
448
- name: runtime.name,
449
- source: 'package',
450
- version: runtime.version,
451
- packages: natives.resolved.length,
452
- missing: natives.missing,
453
- }
454
- : { name: runtimePackageName(), source: null, version: null, packages: 0, missing: [] },
444
+ // 本平台预编译原生产物(重打包子包按真名装进依赖图,运行时按原名补 junction)
445
+ nativeRuntime: {
446
+ name: natives.name,
447
+ source: natives.source,
448
+ version: natives.version,
449
+ packages: natives.resolved,
450
+ missing: natives.missing,
451
+ },
455
452
  node: process.version,
456
453
  platform: process.platform,
457
454
  arch: process.arch,
@@ -1650,14 +1647,9 @@ export async function apply(ctx, config) {
1650
1647
  // ?v= 缓存击穿标记:按"插件版本 + VS Code 树"生成 —— 只在真升级时让渲染器丢掉旧 bundle,
1651
1648
  // 平时正常命中缓存(workbench.js 有 18MB,每次启动都重拉不划算)。
1652
1649
  env.DSHCS_HTML_TAG = `${pluginVersion()}-${productPath() ?? 'dev'}`.replace(/[^A-Za-z0-9._-]/g, '');
1653
- // pnpm 会把带 os/cpu 限定的原生包嵌套装在平台聚合包下(如 <profile>/node_modules/@<scope>/
1654
- // dsh-code-server-runtime-<平台>-<架构>/node_modules/@vscode/spdlog),那些目录不在 VS Code
1655
- // 树的向上查找链里 → 用 NODE_PATH 让子进程的 require 找得到(CJS;ESM 由 ensureAliasLinks 的 junction 负责)。
1656
- const nodePathDirs = aliasNodePathDirs();
1657
- if (nodePathDirs.length > 0) {
1658
- const existing = typeof env.NODE_PATH === 'string' && env.NODE_PATH !== '' ? env.NODE_PATH.split(path.delimiter) : [];
1659
- env.NODE_PATH = [...new Set([...nodePathDirs, ...existing])].join(path.delimiter);
1660
- }
1650
+ // 0.3.45 起不再需要给子进程加 NODE_PATH:重打包原生包按**真名**挂在插件依赖上,而它们要的
1651
+ // **原始名字**(node-pty / @vscode/sqlite3 …)由 ensureRuntimeLayout() 在树里补 junction ——
1652
+ // 目录链同时满足 ESM import 与 CJS require,NODE_PATH 只对 CJS 生效且属兜底。
1661
1653
 
1662
1654
  let proc;
1663
1655
  try {
@@ -1781,7 +1773,7 @@ export async function apply(ctx, config) {
1781
1773
  parts.push(`内部依赖未装全(缺 ${missing.length} 个:${missing.slice(0, 5).join(', ')}${missing.length > 5 ? ' …' : ''})`);
1782
1774
  }
1783
1775
  if (env.nativeRuntime !== null && env.nativeRuntime.source === null) {
1784
- parts.push(`平台预编译包未安装(${env.nativeRuntime.name})`);
1776
+ parts.push(`预编译原生模块未安装(${env.nativeRuntime.name})`);
1785
1777
  } else if (Array.isArray(env.nativeRuntime?.missing) && env.nativeRuntime.missing.length > 0) {
1786
1778
  parts.push(`原生模块解析失败:${env.nativeRuntime.missing.slice(0, 5).join(', ')}`);
1787
1779
  }
package/lib/native.js CHANGED
@@ -1,90 +1,93 @@
1
- // lib/native.js — 平台聚合包(预编译原生模块)的定位与校验。
1
+ // lib/native.js — 预编译原生模块(重打包子包)的定位、校验与目录链还原。
2
2
  //
3
- // 模型(0.1.36 起):VS Code 内部依赖里那批「pnpm 拒绝安装」的原生包(node-pty /
4
- // @vscode/sqlite3 / kerberos / koffi / ssh2 / cpu-features / @parcel/watcher /
5
- // @vscode/windows-* …)在打包期由 scripts/vendor-repacks.mjs 重打包成
6
- // @<scope>/dshcs-<名字>[-<平台>-<架构>],再由平台聚合包
7
- // @<scope>/dsh-code-server-runtime-<平台>-<架构>
8
- // 用 npm: 别名把它们装回**原始名字**。聚合包挂在插件 package.json 的
9
- // optionalDependencies 上(os/cpu 限定)→ pnpm 一条命令按架构自动选中。
3
+ // 模型(0.3.45 起,取代 0.1.36 的「平台聚合包 + npm: 别名」):
4
+ // VS Code 内部依赖里那批「pnpm 拒绝安装」的原生包(node-pty / @vscode/sqlite3 / kerberos /
5
+ // koffi / ssh2 / cpu-features / @parcel/watcher / @vscode/windows-* …)在打包期由
6
+ // scripts/vendor-repacks.mjs 重打包成 @<scope>/dshcs-<名字>[-<平台>-<架构>],并**直接挂在插件
7
+ // 依赖上**:
8
+ // · 平台无关的重打包包 → 插件 `dependencies`(真名,如 @<scope>/dshcs-node-pty);
9
+ // · 平台专属的重打包包 → 插件 `optionalDependencies`(真名 + 包自带 os/cpu,每平台一份,
10
+ // 包管理器按架构自动选中,不匹配的在 lockfile 里是 optional ⇒ 缺失是允许的)。
10
11
  //
11
- // 本模块只做三件事:
12
- // 1. 找到本平台聚合包(以及它的依赖清单 = 期望可解析的原生模块名);
13
- // 2. 从 code-server 运行位置出发校验这些模块是否真的能 require 到;
14
- // 3. 必要时在 code-server 树里补齐别名 junction(ESM import 不认 NODE_PATH,只能靠目录链)。
12
+ // 为什么不沿用聚合包:聚合包把 16 个包挂在**可选子树**里,靠 `npm:` 别名还原原始名字;
13
+ // 而 dsh-desktop 的依赖图校验器在 `pnpm add` 之后立刻校验,偏偏 pnpm 的增量 hoisted 安装会
14
+ // 漏链这类「可选子树里的别名包」(实测 16 个漏 9 个,报 requires missing @microsoft/mxc-sdk)。
15
+ // 直接依赖(真名)在同一套设置下 100% 装上 ⇒ 改用直接依赖 + 运行时补 junction。
16
+ // 实测证据与复现命令见 docs/desktop-first-install-root-cause.md。
17
+ //
18
+ // 「原名 → 真包」的表由打包期写进 lib/vendored.json(随包发布),本模块运行时只读它。
19
+ //
20
+ // 本模块只做四件事:
21
+ // 1. 读 lib/vendored.json → 本平台期望可解析的「原名 → 真包」表;
22
+ // 2. 从 code-server 运行位置出发校验这些原名是否真的能 require/import 到;
23
+ // 3. 解析不到的在树里补 junction(ESM 不认 NODE_PATH,只认目录链);
24
+ // 4. 给环境检测卡片一份状态汇总。
15
25
  import { createRequire } from 'node:module';
16
26
  import { existsSync, readFileSync, mkdirSync, symlinkSync, lstatSync, rmSync } from 'node:fs';
17
27
  import { dirname, join } from 'node:path';
18
- import { PACKAGE_ROOT, codeServerRoot, profileRootOf } from './vendor.js';
28
+ import { PACKAGE_ROOT, codeServerRoot } from './vendor.js';
19
29
 
20
30
  const require_ = createRequire(import.meta.url);
31
+ /** 打包期由 scripts/vendor-repacks.mjs 写出的重打包表。 */
32
+ const VENDORED_FILE = join(PACKAGE_ROOT, 'lib', 'vendored.json');
21
33
 
22
34
  /** 平台目录名,如 win32-arm64。 */
23
35
  export function platformKey(platform = process.platform, arch = process.arch) {
24
36
  return `${platform}-${arch}`;
25
37
  }
26
38
 
27
- /** 本平台聚合包名(取插件 optionalDependencies 中匹配 <平台>-<架构> 的那个)。 */
28
- export function runtimePackageName(platform = process.platform, arch = process.arch) {
29
- const key = platformKey(platform, arch);
39
+ /** 读重打包表。
40
+ * @returns {{scope:string, targets:string[], modules:{alias:string,package:string,version?:string,platform?:boolean}[]}} */
41
+ export function readVendoredTable() {
30
42
  try {
31
- const pkg = JSON.parse(readFileSync(join(PACKAGE_ROOT, 'package.json'), 'utf8'));
32
- const declared = { ...(pkg.optionalDependencies ?? {}), ...(pkg.dependencies ?? {}) };
33
- const hit = Object.keys(declared).find((name) => /^@[^/]+\/dsh-code-server-runtime-/.test(name) && name.endsWith(`-${key}`));
34
- if (hit !== undefined) return hit;
35
- } catch { /* 回退默认 scope */ }
36
- return `@jinsiyu/dsh-code-server-runtime-${key}`;
43
+ const doc = JSON.parse(readFileSync(VENDORED_FILE, 'utf8'));
44
+ const modules = Array.isArray(doc?.modules)
45
+ ? doc.modules.filter((m) => m !== null && typeof m === 'object'
46
+ && typeof m.alias === 'string' && m.alias !== ''
47
+ && typeof m.package === 'string' && m.package !== '')
48
+ : [];
49
+ return {
50
+ scope: typeof doc?.scope === 'string' && doc.scope !== '' ? doc.scope : '@jinsiyu',
51
+ targets: Array.isArray(doc?.targets) ? doc.targets.filter((t) => typeof t === 'string') : [],
52
+ modules,
53
+ };
54
+ } catch {
55
+ return { scope: '@jinsiyu', targets: [], modules: [] };
56
+ }
37
57
  }
38
58
 
39
- /** 定位已安装的平台聚合包。
40
- * @returns {{name:string, version:string|null, root:string, modules:string[]}|null} */
41
- export function resolveRuntime() {
42
- const name = runtimePackageName();
43
- const candidates = [];
44
- try {
45
- candidates.push(dirname(require_.resolve(`${name}/package.json`)));
46
- } catch { /* 未安装 */ }
47
- const profileRoot = profileRootOf();
48
- for (const root of [join(PACKAGE_ROOT, 'node_modules'), profileRoot !== null ? join(profileRoot, 'node_modules') : null, PACKAGE_ROOT]) {
49
- if (root !== null) candidates.push(join(root, name));
50
- }
51
- for (const root of candidates) {
52
- const manifestPath = join(root, 'package.json');
53
- if (!existsSync(manifestPath)) continue;
54
- try {
55
- const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
56
- const modules = Object.keys({ ...(manifest.dependencies ?? {}), ...(manifest.optionalDependencies ?? {}) });
57
- return { name, version: typeof manifest.version === 'string' ? manifest.version : null, root, modules };
58
- } catch { /* 尝试下一个 */ }
59
- }
60
- return null;
59
+ /** 某个条目在指定平台的**真包名**(平台专属的按 `-<平台>-<架构>` 拼后缀)。 */
60
+ export function vendoredPackageName(entry, platform = process.platform, arch = process.arch) {
61
+ return entry.platform === true ? `${entry.package}-${platformKey(platform, arch)}` : entry.package;
62
+ }
63
+
64
+ /** 本平台期望装在依赖图里的重打包子包(原名 + 真名),按原名排序。
65
+ * @returns {{alias:string, packageName:string, version:string|null, platform:boolean}[]} */
66
+ export function vendoredEntries(platform = process.platform, arch = process.arch) {
67
+ return readVendoredTable().modules
68
+ .map((m) => ({
69
+ alias: m.alias,
70
+ packageName: vendoredPackageName(m, platform, arch),
71
+ version: typeof m.version === 'string' ? m.version : null,
72
+ platform: m.platform === true,
73
+ }))
74
+ .sort((a, b) => a.alias.localeCompare(b.alias));
61
75
  }
62
76
 
63
- /** 解析原生/别名模块时的锚点目录:
77
+ /** 解析原生模块时的锚点目录:
64
78
  * ① code-server 运行根(树自带 node_modules);
65
- * ② 平台聚合包目录 —— pnpm 会把带 os/cpu 限定的包**嵌套装在聚合包下**
66
- * (`<profile>/node_modules/@<scope>/dsh-code-server-runtime-<平台>-<架构>/node_modules/<别名>`),
67
- * 既不在 profile 根也不在 code-server 树里,所以要单独作为锚点(运行时用 NODE_PATH 覆盖同一批目录)。
68
- * @returns {string[]} */
79
+ * ② 插件包根 —— 重打包子包挂在插件依赖上(profile 根或插件自己的 node_modules),
80
+ * 从插件清单出发的解析链才看得到它们。 */
69
81
  export function nativeSearchRoots() {
70
82
  const roots = [];
71
83
  const cs = codeServerRoot();
72
84
  if (cs !== null) roots.push(cs);
73
- const runtime = resolveRuntime();
74
- if (runtime !== null) roots.push(runtime.root);
85
+ roots.push(PACKAGE_ROOT);
75
86
  return roots;
76
87
  }
77
88
 
78
- /** 运行时需要加进 NODE_PATH 的目录(平台聚合包下嵌套的别名包)。
79
- * @returns {string[]} */
80
- export function aliasNodePathDirs() {
81
- const runtime = resolveRuntime();
82
- if (runtime === null) return [];
83
- const dir = join(runtime.root, 'node_modules');
84
- return existsSync(dir) ? [dir] : [];
85
- }
86
-
87
89
  /** 解析某个模块的包目录(依次尝试各锚点;先 `<name>/package.json`,再模块入口)。
90
+ * @param {string} name 真包名(重打包子包名),如 `@jinsiyu/dshcs-node-pty`
88
91
  * @returns {string|null} */
89
92
  export function resolveNativeDir(name) {
90
93
  for (const root of nativeSearchRoots()) {
@@ -101,22 +104,23 @@ export function resolveNativeDir(name) {
101
104
  return null;
102
105
  }
103
106
 
104
- /** 从 code-server 运行位置(与聚合包目录)校验原生模块能否解析。
105
- * 先试 `<name>/package.json`(ESM-only 包没有 require 入口,如 @microsoft/mxc-sdk),
106
- * 再退回 `<name>` 本身。
107
- * @param {string[]} modules 期望的原生模块名(通常来自聚合包依赖清单)
107
+ /** 从 code-server 运行位置校验原生模块能否解析(名字用「原名」,即 VS Code 实际 import 的名字)。
108
+ * 先试 `<name>/package.json`(ESM-only 包没有 require 入口,如 @microsoft/mxc-sdk),再退回 `<name>` 本身。
109
+ * @param {string[]} modules 期望的原生模块名(别名)
108
110
  * @returns {{resolved:string[], missing:string[]}} */
109
111
  export function verifyNatives(modules) {
112
+ const csRoot = codeServerRoot();
110
113
  const resolved = [];
111
114
  const missing = [];
112
115
  for (const name of modules) {
113
- if (resolveNativeDir(name) !== null) resolved.push(name);
116
+ const ok = csRoot !== null && resolvesFromRoot(csRoot, name);
117
+ if (ok) resolved.push(name);
114
118
  else missing.push(name);
115
119
  }
116
120
  return { resolved, missing };
117
121
  }
118
122
 
119
- /** 某个模块能否**只靠 code-server 运行根**(含其祖先 node_modules)解析。 */
123
+ /** 某个名字能否**只靠 code-server 运行根**(含其祖先 node_modules)解析。 */
120
124
  function resolvesFromRoot(root, name) {
121
125
  try {
122
126
  const from = createRequire(join(root, 'package.json'));
@@ -130,34 +134,33 @@ function resolvesFromRoot(root, name) {
130
134
  return false;
131
135
  }
132
136
 
133
- /** 确保聚合包带回的别名模块能被 **code-server 树**解析。
137
+ /** 确保重打包子包能被 **code-server 树**按**原始名字**解析。
134
138
  *
135
- * 为什么必须做:pnpm 会把带 `os`/`cpu` 限定的包**嵌套装在聚合包自己的 node_modules 下**
136
- * (如 `<profile>/node_modules/@<scope>/dsh-code-server-runtime-win32-arm64/node_modules/@vscode/windows-registry`),
137
- * 而 VS Code 的 `lib/vscode/out/server-main.js` 用 **ESM `import`** 加载这些包 —— ESM 不认
138
- * `NODE_PATH`(那是 CJS 的兜底),只认目录链。所以这里在 `<codeServerRoot>/node_modules` 下
139
- * 为每个「从树里解析不到」的别名补一个 junction,指向聚合包内的真实目录。
139
+ * 为什么必须做:重打包子包的真名是 `@<scope>/dshcs-<名字>`,而 VS Code 的
140
+ * `lib/vscode/out/server-main.js` 用 **ESM `import`** 加载 `node-pty` / `@vscode/sqlite3` 这类原名 ——
141
+ * ESM 不认 `NODE_PATH`(那是 CJS 的兜底),只认目录链。所以这里在 `<树根>/node_modules` 下为每个
142
+ * 「从树里解析不到」的原名补一个 junction,指向插件依赖图里的真实目录。
140
143
  * 幂等、可重复调用;插件重装后由下次激活/启动自愈。
141
144
  * @returns {{created:string[], failed:string[]}} */
142
145
  export function ensureAliasLinks() {
143
- const runtime = resolveRuntime();
144
146
  const csRoot = codeServerRoot();
145
- if (runtime === null || csRoot === null) return { created: [], failed: [] };
147
+ if (csRoot === null) return { created: [], failed: [] };
146
148
  const linkRoot = join(csRoot, 'node_modules');
147
149
  const created = [];
148
150
  const failed = [];
149
- for (const alias of runtime.modules) {
150
- if (resolvesFromRoot(csRoot, alias)) continue; // 目录链已能看到(profile 根或树内),无需补
151
- const target = resolveNativeDir(alias);
152
- if (target === null) continue; // 该平台包没装上 → 交给 envCheck 报缺
153
- const link = join(linkRoot, alias);
151
+ for (const entry of vendoredEntries()) {
152
+ if (resolvesFromRoot(csRoot, entry.alias)) continue; // 目录链已能看到(profile 根或树内),无需补
153
+ const target = resolveNativeDir(entry.packageName);
154
+ if (target === null) continue; // 该平台子包没装上 → 交给 envCheck 报缺
155
+ const link = join(linkRoot, entry.alias);
154
156
  try {
155
- if (existsSync(link)) continue;
157
+ if (existsSync(join(link, 'package.json'))) continue; // 链接有效(真实目录或有效 junction)
158
+ if (pathEntryExists(link)) rmSync(link, { recursive: true, force: true }); // 断链(树被重装过)→ 清掉重建
156
159
  mkdirSync(dirname(link), { recursive: true });
157
160
  symlinkSync(target, link, process.platform === 'win32' ? 'junction' : 'dir');
158
- created.push(alias);
161
+ created.push(entry.alias);
159
162
  } catch (error) {
160
- failed.push(`${alias}: ${error && error.message ? error.message : String(error)}`);
163
+ failed.push(`${entry.alias}: ${error && error.message ? error.message : String(error)}`);
161
164
  }
162
165
  }
163
166
  return { created, failed };
@@ -251,7 +254,7 @@ export function ensureInnerModuleLinks() {
251
254
  return { created, failed };
252
255
  }
253
256
 
254
- /** 一次性补齐两类链接:VS Code 内部依赖目录 + 聚合包别名。 */
257
+ /** 一次性补齐两类链接:VS Code 内部依赖目录 + 重打包子包的原始名字。 */
255
258
  export function ensureRuntimeLayout() {
256
259
  const inner = ensureInnerModuleLinks();
257
260
  const aliases = ensureAliasLinks();
@@ -260,3 +263,28 @@ export function ensureRuntimeLayout() {
260
263
  failed: [...inner.failed, ...aliases.failed],
261
264
  };
262
265
  }
266
+
267
+ /** 本平台预编译原生包的状态汇总(环境检测卡片用)。
268
+ * @returns {{name:string, source:'package'|null, version:string|null, modules:number, packages:number,
269
+ * resolved:number, missing:string[], missingPackages:string[]}} */
270
+ export function nativeRuntimeStatus(platform = process.platform, arch = process.arch) {
271
+ const table = readVendoredTable();
272
+ const entries = vendoredEntries(platform, arch);
273
+ const installed = [];
274
+ const missingPackages = [];
275
+ for (const entry of entries) {
276
+ if (resolveNativeDir(entry.packageName) !== null) installed.push(entry);
277
+ else missingPackages.push(entry);
278
+ }
279
+ const { resolved, missing } = verifyNatives(entries.map((entry) => entry.alias));
280
+ return {
281
+ name: `${table.scope}/dshcs-*-${platformKey(platform, arch)}`,
282
+ source: installed.length > 0 ? 'package' : null,
283
+ version: null,
284
+ modules: entries.length,
285
+ packages: installed.length,
286
+ resolved: resolved.length,
287
+ missing,
288
+ missingPackages: missingPackages.map((entry) => entry.packageName),
289
+ };
290
+ }
package/lib/vendor.js CHANGED
@@ -10,7 +10,9 @@
10
10
  // - VS Code 内部依赖(约 1GB)与需编译的原生模块仍由包管理器安装:
11
11
  // 纯 JS 部分写在插件 package.json 的 dependencies;
12
12
  // pnpm 拒绝安装的原生包由 scripts/vendor-repacks.mjs 重打包成 @<scope>/dshcs-*,
13
- // 再由平台聚合包 @<scope>/dsh-code-server-runtime-<platform>-<arch> 用 npm: 别名装回原名字;
13
+ // 并**直接挂在插件依赖上**(平台无关的 → dependencies;平台专属的 → optionalDependencies,
14
+ // 包自带 os/cpu 按架构自动选)。原始名字由 lib/native.js 按 lib/vendored.json 补 junction ——
15
+ // 0.3.45 起不再有平台聚合包,原因见 docs/desktop-first-install-root-cause.md;
14
16
  // - 插件包内 `vendor/vscode/` 只作为**开发期/兼容**回退(发布包不含 vendor)。
15
17
  import { createRequire } from 'node:module';
16
18
  import { existsSync, readFileSync } from 'node:fs';
@@ -0,0 +1,99 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "generatedBy": "scripts/vendor-repacks.mjs",
4
+ "scope": "@jinsiyu",
5
+ "targets": [
6
+ "win32-arm64",
7
+ "win32-x64"
8
+ ],
9
+ "modules": [
10
+ {
11
+ "alias": "@microsoft/mxc-sdk",
12
+ "package": "@jinsiyu/dshcs-microsoft-mxc-sdk",
13
+ "version": "0.8.0"
14
+ },
15
+ {
16
+ "alias": "@parcel/watcher",
17
+ "package": "@jinsiyu/dshcs-parcel-watcher",
18
+ "version": "2.5.6"
19
+ },
20
+ {
21
+ "alias": "@vscode/deviceid",
22
+ "package": "@jinsiyu/dshcs-vscode-deviceid",
23
+ "version": "0.1.5",
24
+ "platform": true
25
+ },
26
+ {
27
+ "alias": "@vscode/fs-copyfile",
28
+ "package": "@jinsiyu/dshcs-vscode-fs-copyfile",
29
+ "version": "2.0.0"
30
+ },
31
+ {
32
+ "alias": "@vscode/native-watchdog",
33
+ "package": "@jinsiyu/dshcs-vscode-native-watchdog",
34
+ "version": "1.4.6",
35
+ "platform": true
36
+ },
37
+ {
38
+ "alias": "@vscode/proxy-agent",
39
+ "package": "@jinsiyu/dshcs-vscode-proxy-agent",
40
+ "version": "0.44.0"
41
+ },
42
+ {
43
+ "alias": "@vscode/spdlog",
44
+ "package": "@jinsiyu/dshcs-vscode-spdlog",
45
+ "version": "0.15.8",
46
+ "platform": true
47
+ },
48
+ {
49
+ "alias": "@vscode/sqlite3",
50
+ "package": "@jinsiyu/dshcs-vscode-sqlite3",
51
+ "version": "5.1.12-vscode",
52
+ "platform": true
53
+ },
54
+ {
55
+ "alias": "@vscode/windows-ca-certs",
56
+ "package": "@jinsiyu/dshcs-vscode-windows-ca-certs",
57
+ "version": "0.3.4",
58
+ "platform": true
59
+ },
60
+ {
61
+ "alias": "@vscode/windows-process-tree",
62
+ "package": "@jinsiyu/dshcs-vscode-windows-process-tree",
63
+ "version": "0.8.0",
64
+ "platform": true
65
+ },
66
+ {
67
+ "alias": "@vscode/windows-registry",
68
+ "package": "@jinsiyu/dshcs-vscode-windows-registry",
69
+ "version": "1.2.0",
70
+ "platform": true
71
+ },
72
+ {
73
+ "alias": "cpu-features",
74
+ "package": "@jinsiyu/dshcs-cpu-features",
75
+ "version": "0.0.10"
76
+ },
77
+ {
78
+ "alias": "kerberos",
79
+ "package": "@jinsiyu/dshcs-kerberos",
80
+ "version": "2.1.1-dshcs.1",
81
+ "platform": true
82
+ },
83
+ {
84
+ "alias": "koffi",
85
+ "package": "@jinsiyu/dshcs-koffi",
86
+ "version": "3.1.2"
87
+ },
88
+ {
89
+ "alias": "node-pty",
90
+ "package": "@jinsiyu/dshcs-node-pty",
91
+ "version": "1.2.0-beta.15"
92
+ },
93
+ {
94
+ "alias": "ssh2",
95
+ "package": "@jinsiyu/dshcs-ssh2",
96
+ "version": "1.17.0"
97
+ }
98
+ ]
99
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-code-server-app",
3
- "version": "0.3.44",
3
+ "version": "0.3.46",
4
4
  "description": "VS Code (from a code-server release) inside DSH: a right-sidebar tab driven by the plugin's own launcher over the in-process VS Code server (lib/launcher.mjs). Since 0.3.0 the bundle also ships an editor bridge (assets/extensions/dshcs-editor-bridge): a read-only channel between the in-tree VS Code extension host and DSH, giving the agent what only the editor knows (unsaved buffers, language-server diagnostics, the active selection) and letting editor gestures drive the session. Since 0.3.22 the ask panel renders that session's new content with DSH's own Markdown renderer (pinned to the deployed DSH UI version); since 0.3.23 it is a full-width dialog with collapsible thinking rows and can answer approval requests in place — the bridge's only non-read-only route (POST /approve, constrained to pending in-process requests and the outcomes allowed-once/rejected; panel-first for 5 minutes, handed back to the official card the moment the panel closes). The tab claims DSH file addresses (dsh-resource://file/**) by file type (setting claimExtensions), so the product's own produced-file chips, delivered-file previews and inline prose mentions open in the workbench. The IDE is a resident surface moved with Element.moveBefore instead of being remounted, so switching sidebar tabs no longer reloads it. Opening the tab switches the right sidebar to fullscreen by default (setting fullscreenOnOpen). Following a workspace switch is lightweight: the workbench re-navigates with the new ?folder= and the IDE process is not restarted (since 0.2.12). Requires a DSH with the right-sidebar services (sidebarRightTabs/sidebarRight, >= 0.1.5-alpha.1); older DSH versions get a single upgrade notice on the settings page and no other UI. 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": {
@@ -50,6 +50,7 @@
50
50
  "lib/serve-dsh.mjs",
51
51
  "lib/vendor.js",
52
52
  "lib/native.js",
53
+ "lib/vendored.json",
53
54
  "assets/favicon.ico",
54
55
  "assets/favicon.svg",
55
56
  "assets/extensions/dshcs-open-file",
@@ -63,6 +64,28 @@
63
64
  "README.md",
64
65
  "README.en.md"
65
66
  ],
67
+ "scripts": {
68
+ "vendor:vscode": "node scripts/vendor-vscode-server.mjs",
69
+ "vendor:latest": "node scripts/vendor-vscode-server.mjs --force",
70
+ "vendor:check": "node scripts/vendor-vscode-server.mjs --check",
71
+ "vendor:code-server": "node scripts/vendor-code-server.mjs",
72
+ "repack:build": "node scripts/vendor-repacks.mjs",
73
+ "publish:repacks": "node scripts/publish-repacks.mjs",
74
+ "publish:plugin": "node scripts/publish-plugin.mjs",
75
+ "promote": "node scripts/promote.mjs",
76
+ "prepack": "node scripts/vendor-vscode-server.mjs && node scripts/build-webview.mjs",
77
+ "build:client": "node scripts/build-client.mjs",
78
+ "build:webview": "node scripts/build-webview.mjs",
79
+ "test:apply": "node scripts/test-plugin-apply.mjs",
80
+ "test:launcher-routes": "node scripts/test-launcher-routes.mjs",
81
+ "test:fullscreen": "node scripts/test-sidebar-fullscreen.mjs",
82
+ "test:claim-types": "node scripts/test-claim-types.mjs",
83
+ "test:workspace-switch": "node scripts/test-workspace-switch.mjs",
84
+ "test:bridge-routes": "node scripts/test-bridge-routes.mjs",
85
+ "test:bridge-extension": "node scripts/test-bridge-extension.mjs",
86
+ "test:webview": "node scripts/test-webview-bundle.mjs",
87
+ "test:vendored": "node scripts/test-vendored-table.mjs"
88
+ },
66
89
  "devDependencies": {
67
90
  "@deepseek-ai/dsh-client-ui-primitives": "0.1.5-rc.2",
68
91
  "@deepseek-ai/dsh-client-ui-theme": "0.1.5-rc.2",
@@ -89,6 +112,14 @@
89
112
  },
90
113
  "license": "MIT",
91
114
  "dependencies": {
115
+ "@jinsiyu/dshcs-cpu-features": "0.0.10",
116
+ "@jinsiyu/dshcs-koffi": "3.1.2",
117
+ "@jinsiyu/dshcs-microsoft-mxc-sdk": "0.8.0",
118
+ "@jinsiyu/dshcs-node-pty": "1.2.0-beta.15",
119
+ "@jinsiyu/dshcs-parcel-watcher": "2.5.6",
120
+ "@jinsiyu/dshcs-ssh2": "1.17.0",
121
+ "@jinsiyu/dshcs-vscode-fs-copyfile": "2.0.0",
122
+ "@jinsiyu/dshcs-vscode-proxy-agent": "0.44.0",
92
123
  "@jinsiyu/dshcs-vscode-server": "4.137.0",
93
124
  "@microsoft/1ds-core-js": "3.2.13",
94
125
  "@microsoft/1ds-post-js": "3.2.13",
@@ -107,17 +138,21 @@
107
138
  "@xterm/addon-webgl": "0.20.0-beta.299",
108
139
  "@xterm/headless": "6.1.0-beta.302",
109
140
  "@xterm/xterm": "6.1.0-beta.303",
141
+ "bindings": "1.5.0",
110
142
  "cookie": "0.7.2",
111
143
  "detect-libc": "2.1.2",
144
+ "fs-extra": "11.4.0",
112
145
  "http-proxy-agent": "7.0.0",
113
146
  "https-proxy-agent": "7.0.2",
114
147
  "jschardet": "3.1.4",
115
148
  "minimist": "1.2.8",
149
+ "mkdirp": "1.0.4",
116
150
  "node-addon-api": "6.1.0",
117
151
  "tar": "7.5.22",
118
152
  "tas-client": "0.4.3",
119
153
  "tslib": "2.8.1",
120
154
  "typescript": "6.0.3",
155
+ "uuid": "14.0.2",
121
156
  "vscode-oniguruma": "1.7.0",
122
157
  "vscode-regexpp": "3.1.0",
123
158
  "vscode-textmate": "9.3.2",
@@ -127,27 +162,21 @@
127
162
  "zod": "4.4.3"
128
163
  },
129
164
  "optionalDependencies": {
130
- "@jinsiyu/dsh-code-server-runtime-win32-arm64": "^0.3.10",
131
- "@jinsiyu/dsh-code-server-runtime-win32-x64": "^0.3.10"
132
- },
133
- "scripts": {
134
- "vendor:vscode": "node scripts/vendor-vscode-server.mjs",
135
- "vendor:latest": "node scripts/vendor-vscode-server.mjs --force",
136
- "vendor:check": "node scripts/vendor-vscode-server.mjs --check",
137
- "vendor:code-server": "node scripts/vendor-code-server.mjs",
138
- "repack:build": "node scripts/vendor-repacks.mjs",
139
- "publish:repacks": "node scripts/publish-repacks.mjs",
140
- "publish:plugin": "node scripts/publish-plugin.mjs",
141
- "promote": "node scripts/promote.mjs",
142
- "build:client": "node scripts/build-client.mjs",
143
- "build:webview": "node scripts/build-webview.mjs",
144
- "test:apply": "node scripts/test-plugin-apply.mjs",
145
- "test:launcher-routes": "node scripts/test-launcher-routes.mjs",
146
- "test:fullscreen": "node scripts/test-sidebar-fullscreen.mjs",
147
- "test:claim-types": "node scripts/test-claim-types.mjs",
148
- "test:workspace-switch": "node scripts/test-workspace-switch.mjs",
149
- "test:bridge-routes": "node scripts/test-bridge-routes.mjs",
150
- "test:bridge-extension": "node scripts/test-bridge-extension.mjs",
151
- "test:webview": "node scripts/test-webview-bundle.mjs"
165
+ "@jinsiyu/dshcs-kerberos-win32-arm64": "2.1.1-dshcs.1",
166
+ "@jinsiyu/dshcs-kerberos-win32-x64": "2.1.1-dshcs.1",
167
+ "@jinsiyu/dshcs-vscode-deviceid-win32-arm64": "0.1.5",
168
+ "@jinsiyu/dshcs-vscode-deviceid-win32-x64": "0.1.5",
169
+ "@jinsiyu/dshcs-vscode-native-watchdog-win32-arm64": "1.4.6",
170
+ "@jinsiyu/dshcs-vscode-native-watchdog-win32-x64": "1.4.6",
171
+ "@jinsiyu/dshcs-vscode-spdlog-win32-arm64": "0.15.8",
172
+ "@jinsiyu/dshcs-vscode-spdlog-win32-x64": "0.15.8",
173
+ "@jinsiyu/dshcs-vscode-sqlite3-win32-arm64": "5.1.12-vscode",
174
+ "@jinsiyu/dshcs-vscode-sqlite3-win32-x64": "5.1.12-vscode",
175
+ "@jinsiyu/dshcs-vscode-windows-ca-certs-win32-arm64": "0.3.4",
176
+ "@jinsiyu/dshcs-vscode-windows-ca-certs-win32-x64": "0.3.4",
177
+ "@jinsiyu/dshcs-vscode-windows-process-tree-win32-arm64": "0.8.0",
178
+ "@jinsiyu/dshcs-vscode-windows-process-tree-win32-x64": "0.8.0",
179
+ "@jinsiyu/dshcs-vscode-windows-registry-win32-arm64": "1.2.0",
180
+ "@jinsiyu/dshcs-vscode-windows-registry-win32-x64": "1.2.0"
152
181
  }
153
- }
182
+ }
@@ -9,8 +9,10 @@
9
9
  // + src/browser),不含 code-server 的 out/node 与它的 136 个依赖(那层由 lib/launcher.mjs 取代);
10
10
  // ② @<scope>/dshcs-<名字>[-<platform>-<arch>]@<版本> 需要构建的原生包(node-pty / @vscode/sqlite3 /
11
11
  // kerberos / koffi / ssh2 / …),依赖链上命中它们的包也一并重打包(含 optionalDependencies);
12
- // ③ @<scope>/dsh-code-server-runtime-<platform>-<arch>@<插件版本> 平台聚合包:用 npm: 别名把 ② 装回
13
- // 原始名字,VS Code 的 require/import 无需改动;插件按 os/cpu 声明 ①(常规)+ ③(可选)依赖。
12
+ // ③ 插件依赖表 + lib/vendored.json —— **0.3.45 起不再有平台聚合包**:平台无关的重打包包写进插件
13
+ // `dependencies`(真名),平台专属的写进 `optionalDependencies`(真名 + 包自带 os/cpu,每目标一份);
14
+ // 原始名字(node-pty / @vscode/sqlite3 …)由 lib/native.js 在运行时按 lib/vendored.json 补 junction。
15
+ // 为什么弃用「平台聚合包 + npm: 别名」:见 docs/desktop-first-install-root-cause.md。
14
16
  //
15
17
  // 用法:node scripts/vendor-repacks.mjs [--from <已完整安装的 code-server 树>]
16
18
  // [--target win32-arm64,win32-x64] [--scope @jinsiyu] [--pack]
@@ -395,63 +397,15 @@ function hostMapFromProfile() {
395
397
  return best;
396
398
  }
397
399
 
398
- /** --reuse:从现有 repack/build + 聚合包清单还原「重打包集」,不重新分析源树。
399
- * @returns {{repack: Map<string,{pkg:{version:string},platformSpecific:boolean}>, declare:{name:string,version:string}[], reusedItems:{dir:string,file:string}[]}} */
400
- function reuseExisting(targets, hostKey) {
400
+ /** --reuse:从现有 repack/build + 重打包表还原「重打包集」,不重新分析源树。
401
+ * @returns {{modules:{alias:string,package:string,version:string,platform:boolean}[],
402
+ * declare:{name:string,version:string}[], reusedItems:{dir:string,file:string}[]}} */
403
+ function reuseExisting(targets) {
401
404
  const buildRoot = join(OUT, 'build');
402
405
  if (!existsSync(buildRoot)) throw new Error('--reuse 需要 repack/build 已存在(先跑一次完整流程)');
403
- const byTarget = new Map();
404
- for (const target of targets) {
405
- const agg = readJson(join(OUT, 'aggregator', target, 'package.json'));
406
- if (agg === null) continue;
407
- const map = new Map();
408
- for (const [orig, spec] of Object.entries(agg.dependencies ?? {})) {
409
- // 旧的聚合包清单里可能仍带 argon2(已随 code-server 服务层移除)
410
- if (orig === 'argon2') continue;
411
- const hit = /^npm:(.+)@([^@]+)$/.exec(spec);
412
- if (hit === null) throw new Error(`无法解析聚合包依赖 ${orig}: ${spec}`);
413
- map.set(orig, { pkgName: hit[1], version: hit[2] });
414
- }
415
- byTarget.set(target, map);
416
- }
417
- if (byTarget.size === 0) {
418
- const base = hostMapFromProfile();
419
- if (base === null) {
420
- throw new Error('--reuse 需要 repack/aggregator/*/package.json 或一个已安装的 profile 用于还原映射');
421
- }
422
- byTarget.set(hostKey, base);
423
- } else {
424
- // 与已安装 profile 的别名取并集:聚合包清单可能在上一次失败的重建里丢过条目
425
- const hostTarget = byTarget.has(hostKey) ? hostKey : targets[0];
426
- const base = hostMapFromProfile();
427
- if (base !== null) {
428
- const merged = new Map(byTarget.get(hostTarget));
429
- let added = 0;
430
- for (const [orig, info] of base) {
431
- if (!merged.has(orig)) { merged.set(orig, info); added += 1; }
432
- }
433
- if (added > 0) console.log(`[repack] --reuse:聚合包清单缺 ${added} 个条目 → 已用 profile 别名补齐`);
434
- byTarget.set(hostTarget, merged);
435
- }
436
- }
437
- const known = byTarget.get(hostKey) ?? byTarget.get(targets[0]);
438
- // 其余目标:按后缀替换推导(平台专属包名只在结尾的 -<platform>-<arch> 上不同)
439
- const knownTarget = byTarget.has(hostKey) ? hostKey : targets[0];
440
- for (const target of targets) {
441
- if (byTarget.has(target)) continue;
442
- const map = new Map();
443
- for (const [orig, info] of known) {
444
- const pkgName = info.pkgName.endsWith(`-${knownTarget}`)
445
- ? `${info.pkgName.slice(0, -knownTarget.length)}${target}`
446
- : info.pkgName;
447
- map.set(orig, { pkgName, version: info.version });
448
- }
449
- byTarget.set(target, map);
450
- }
451
- const hostSet = byTarget.get(knownTarget);
452
- const repack = new Map();
453
- for (const [orig, info] of hostSet) {
454
- repack.set(orig, { pkg: { version: info.version }, platformSpecific: info.pkgName.endsWith(`-${knownTarget}`) });
406
+ const modules = readModules(targets);
407
+ if (modules.length === 0) {
408
+ throw new Error('--reuse 需要 lib/vendored.json、repack/aggregator/*/package.json 或一个已安装的 profile 用于还原重打包表');
455
409
  }
456
410
  // 已有原生包目录 → plan 项;顺带清掉将被重建的树目录(旧 code-server / argon2 目录一并清理)
457
411
  const reusedItems = [];
@@ -464,50 +418,168 @@ function reuseExisting(targets, hostKey) {
464
418
  if (m === null) continue;
465
419
  reusedItems.push({ dir: join(buildRoot, ent), file: tgzName(m.name, m.version) });
466
420
  }
467
- // 纯 JS 直装集:插件现有 dependencies 里非 @jinsiyu 的项
421
+ // 纯 JS 直装集:插件现有 dependencies 里既不是树包、也不是重打包子包的项
422
+ const vendoredNames = new Set(modules.map((m) => m.package));
468
423
  const pluginPkg = readJson(join(pkgRoot, 'package.json'));
469
424
  const declare = Object.entries(pluginPkg.dependencies ?? {})
470
- .filter(([name]) => !/^@jinsiyu\//.test(name))
425
+ .filter(([name]) => !vendoredNames.has(name) && !/\/(?:dshcs-vscode-server|dshcs-code-server)$/.test(name))
471
426
  .map(([name, version]) => ({ name, version }));
472
427
  rmSync(join(OUT, 'aggregator'), { recursive: true, force: true });
473
- console.log(`[repack] --reuse:复用 ${reusedItems.length} 个已打包目录、${repack.size} 个重打包条目、${declare.length} 个纯 JS 直装依赖`);
474
- return { repack, declare, reusedItems };
428
+ console.log(`[repack] --reuse:复用 ${reusedItems.length} 个已打包目录、${modules.length} 个重打包条目、${declare.length} 个纯 JS 直装依赖`);
429
+ return { modules, declare, reusedItems };
430
+ }
431
+
432
+ /** 重打包子包的平台后缀(平台专属包才有),如 `@<scope>/dshcs-kerberos-win32-arm64`。 */
433
+ const PLATFORM_SUFFIX = /-(win32|darwin|linux)-(arm64|x64)$/;
434
+
435
+ /** 由「原名 + 真包名 + 版本」得到重打包条目(平台专属的存**基名**,运行时按目标拼后缀)。 */
436
+ function moduleEntry(alias, pkgName, version) {
437
+ const hit = PLATFORM_SUFFIX.exec(pkgName);
438
+ return hit === null
439
+ ? { alias, package: pkgName, version, platform: false }
440
+ : { alias, package: pkgName.slice(0, -hit[0].length), version, platform: true };
441
+ }
442
+
443
+ /** 读「原名 → 重打包子包」表:优先 lib/vendored.json(0.3.45 起),
444
+ * 否则从平台聚合包清单(0.3.44 及更早)或已安装 profile 迁移,再否则返回空表。 */
445
+ function readModules(targets) {
446
+ const vendoredFile = join(pkgRoot, 'lib', 'vendored.json');
447
+ const doc = readJson(vendoredFile);
448
+ if (doc !== null && Array.isArray(doc.modules) && doc.modules.length > 0) {
449
+ const modules = doc.modules
450
+ .filter((m) => m !== null && typeof m === 'object' && typeof m.alias === 'string' && typeof m.package === 'string')
451
+ .map((m) => ({
452
+ alias: m.alias,
453
+ package: m.package,
454
+ version: typeof m.version === 'string' ? m.version : null,
455
+ platform: m.platform === true,
456
+ }))
457
+ .sort((a, b) => a.alias.localeCompare(b.alias));
458
+ if (modules.length > 0) {
459
+ console.log(`[repack] 读 lib/vendored.json:${modules.length} 个重打包条目`);
460
+ return modules;
461
+ }
462
+ }
463
+ // 迁移:旧的聚合包清单(每个目标一份,dependencies 里是 npm: 别名)
464
+ let source = null;
465
+ for (const target of targets) {
466
+ const agg = readJson(join(OUT, 'aggregator', target, 'package.json'));
467
+ if (agg === null) continue;
468
+ const map = new Map();
469
+ for (const [orig, spec] of Object.entries(agg.dependencies ?? {})) {
470
+ // 旧的聚合包清单里可能仍带 argon2(已随 code-server 服务层移除)
471
+ if (orig === 'argon2') continue;
472
+ const hit = /^npm:(.+)@([^@]+)$/.exec(spec);
473
+ if (hit === null) throw new Error(`无法解析聚合包依赖 ${orig}: ${spec}`);
474
+ map.set(orig, { pkgName: hit[1], version: hit[2] });
475
+ }
476
+ if (map.size > 0) { source = { label: `repack/aggregator/${target}`, map }; break; }
477
+ }
478
+ // 与已安装 profile 的别名取并集:聚合包清单可能在上一次失败的重建里丢过条目
479
+ const base = hostMapFromProfile();
480
+ if (source === null && base === null) return [];
481
+ const merged = new Map(source !== null ? source.map : []);
482
+ if (base !== null) {
483
+ let added = 0;
484
+ for (const [orig, info] of base) {
485
+ if (!merged.has(orig)) { merged.set(orig, info); added += 1; }
486
+ }
487
+ if (added > 0) console.log(`[repack] 重打包表缺 ${added} 个条目 → 已用 profile 别名补齐`);
488
+ }
489
+ const modules = [...merged]
490
+ .map(([alias, info]) => moduleEntry(alias, info.pkgName, info.version))
491
+ .sort((a, b) => a.alias.localeCompare(b.alias));
492
+ console.log(`[repack] 迁移 ${modules.length} 个重打包条目(${source !== null ? source.label : 'profile 别名'})→ 将写入 lib/vendored.json`);
493
+ return modules;
494
+ }
495
+
496
+ /** 平台专属重打包包(写在插件 `optionalDependencies`)自带的**注册表依赖** —— 必须同时提成插件
497
+ * 自己的直接依赖。原因(0.3.46 实测,复现与证据见 docs/desktop-first-install-root-cause.md 第 5 节):
498
+ * pnpm 的增量 hoisted 安装会把「optional 子树里 depth≥2」的依赖整支丢进 `node_modules/.modules.yaml`
499
+ * 的 `skipped`(`bindings` / `fs-extra` / `uuid` / `mkdirp` 全在内),而 dsh-desktop 在 `pnpm add`
500
+ * 之后**立刻**用 `require.resolve.paths()` 校验依赖图 ⇒ 首次安装报
501
+ * `@jinsiyu/dshcs-kerberos-win32-arm64 requires missing bindings@^1.5.0`;
502
+ * 更糟的是 `pnpm install --frozen-lockfile` 会认为 «Already up to date»(锁文件本身也缺这些条目),
503
+ * 于是 profile 永远修不好,只能靠删 node_modules 重装。提到插件根依赖后它们落在 profile 根
504
+ * `node_modules`,校验器与运行时都能解析到。这些都是纯 JS、无 ABI 约束,版本在此钉死;
505
+ * 将来平台专属包新增注册表依赖时 verifyOptionalSubtreeDeps() 会在构建期直接报错。 */
506
+ const OPTIONAL_SUBTREE_DEPS = {
507
+ bindings: '1.5.0',
508
+ mkdirp: '1.0.4',
509
+ 'fs-extra': '11.4.0',
510
+ uuid: '14.0.2',
511
+ };
512
+
513
+ /** 构建期闸门:平台专属重打包清单里的每个非别名依赖都必须在 OPTIONAL_SUBTREE_DEPS 里。
514
+ * @param dirs - 已生成的平台专属重打包目录。 */
515
+ function verifyOptionalSubtreeDeps(dirs) {
516
+ const seen = new Set();
517
+ for (const dir of dirs) {
518
+ const m = readJson(join(dir, 'package.json'));
519
+ if (m === null) continue;
520
+ for (const fld of ['dependencies', 'optionalDependencies']) {
521
+ for (const [name, spec] of Object.entries(m[fld] ?? {})) {
522
+ if (String(spec).startsWith('npm:')) continue; // 已重打包(真名子包),不走注册表
523
+ seen.add(name);
524
+ if (!Object.hasOwn(OPTIONAL_SUBTREE_DEPS, name)) {
525
+ throw new Error(`${m.name}: 注册表依赖 ${name}@${spec} 不在 OPTIONAL_SUBTREE_DEPS 里 ——`
526
+ + ' 它位于 optional 子树(depth≥2),pnpm 的增量 hoisted 安装会丢包,'
527
+ + ' dsh-desktop 安装后立刻校验必报 requires missing;请把它提为插件直接依赖'
528
+ + '(见 docs/desktop-first-install-root-cause.md 第 5 节)');
529
+ }
530
+ }
531
+ }
532
+ }
533
+ const unused = Object.keys(OPTIONAL_SUBTREE_DEPS).filter((name) => !seen.has(name));
534
+ if (unused.length > 0) {
535
+ console.warn(` ⚠ OPTIONAL_SUBTREE_DEPS 里的 ${unused.join(', ')} 已不再被任何平台专属包需要,可以删掉`);
536
+ }
475
537
  }
476
538
 
477
539
  function main() {
478
540
  const hostKey = `${process.platform}-${process.arch}`;
479
541
  const targets = TARGETS.length > 0 ? TARGETS : [hostKey];
480
- const pluginVersion = readJson(join(pkgRoot, 'package.json'))?.version ?? '0.0.0';
481
542
 
482
- let repack;
543
+ let repack = null;
544
+ let modules;
483
545
  let declare;
484
546
  let reusedItems = [];
485
547
  let autoSourceTree = null;
486
548
  if (REUSE) {
487
- ({ repack, declare, reusedItems } = reuseExisting(targets, hostKey));
549
+ ({ modules, declare, reusedItems } = reuseExisting(targets));
488
550
  } else {
489
551
  const autoSource = FROM === null;
490
552
  const sourceTree = autoSource ? prepareSourceTree() : FROM;
491
553
  if (autoSource) autoSourceTree = sourceTree;
492
554
  ({ repack, declare } = analyze(sourceTree));
555
+ modules = [...repack]
556
+ .map(([name, r]) => ({
557
+ alias: name,
558
+ package: `${SCOPE}/dshcs-${flat(name)}`,
559
+ version: r.pkg.version,
560
+ platform: r.platformSpecific,
561
+ }))
562
+ .sort((a, b) => a.alias.localeCompare(b.alias));
493
563
  rmSync(OUT, { recursive: true, force: true });
494
564
  }
495
- console.log(`\n重打包集(${repack.size}):`);
496
- for (const [name, r] of [...repack].sort()) {
497
- console.log(` ${name}@${r.pkg.version} ${r.platformSpecific ? '[平台专属]' : '[全平台]'}`);
565
+ console.log(`\n重打包集(${modules.length}):`);
566
+ for (const m of modules) {
567
+ console.log(` ${m.alias}@${m.version} → ${m.package}${m.platform ? `-<平台>` : ''} ${m.platform ? '[平台专属]' : '[全平台]'}`);
498
568
  }
499
569
  console.log(`\n直装集(${declare.length}): ${declare.map((d) => d.name).join(', ')}\n`);
500
570
 
501
571
  mkdirSync(join(OUT, 'build'), { recursive: true });
502
572
 
503
- // 每个目标的包名映射
573
+ // 每个目标的包名映射:平台专属的按目标拼后缀,平台无关的到处同名
504
574
  const byTarget = new Map();
505
575
  for (const target of targets) {
506
- const suffix = `-${target}`;
507
- const map = new Map(); // 原包名 -> { pkgName, version }
508
- for (const [name, r] of repack) {
509
- const pkgName = `${SCOPE}/dshcs-${flat(name)}${r.platformSpecific ? suffix : ''}`;
510
- map.set(name, { pkgName, version: r.pkg.version, platformSpecific: r.platformSpecific });
576
+ const map = new Map(); // 原包名 -> { pkgName, version, platformSpecific }
577
+ for (const m of modules) {
578
+ map.set(m.alias, {
579
+ pkgName: `${m.package}${m.platform ? `-${target}` : ''}`,
580
+ version: m.version,
581
+ platformSpecific: m.platform,
582
+ });
511
583
  }
512
584
  byTarget.set(target, map);
513
585
  }
@@ -581,6 +653,7 @@ function main() {
581
653
  }
582
654
  }
583
655
  // 3) 平台专属包:host 用现成树,其它目标用交叉安装树(--reuse 时用已打包目录)
656
+ const platformDirs = [];
584
657
  if (!REUSE) for (const target of targets) {
585
658
  const map = byTarget.get(target);
586
659
  const specs = [...repack].filter(([, r]) => r.platformSpecific).map(([name, r]) => ({ name, version: r.pkg.version }));
@@ -595,49 +668,58 @@ function main() {
595
668
  continue;
596
669
  }
597
670
  const dir = writeRepack(srcDir, info, `${flat(name)}-${target}`);
671
+ platformDirs.push(dir);
598
672
  plan.push({ dir, file: tgzName(info.pkgName, r.pkg.version) });
599
673
  }
600
674
  if (crossTree !== null) rmSync(crossTree, { recursive: true, force: true });
601
675
  }
602
- // 3) 聚合包(每平台一个):dependencies 用 npm: 别名把重打包包装回原名
603
- for (const target of targets) {
604
- const map = byTarget.get(target);
605
- const aggName = `${SCOPE}/dsh-code-server-runtime-${target}`;
606
- const aggDir = join(OUT, 'aggregator', target);
607
- mkdirSync(aggDir, { recursive: true });
608
- const deps = {};
609
- for (const [name] of repack) deps[name] = `npm:${map.get(name).pkgName}@${map.get(name).version}`;
610
- writeFileSync(join(aggDir, 'package.json'), JSON.stringify({
611
- name: aggName,
612
- version: pluginVersion,
613
- description: `Prebuilt native modules for the VS Code server tree on ${target} `
614
- + '(node-pty, @vscode/sqlite3, kerberos, @vscode/spdlog, …), '
615
- + 'so that installing the VS Code inner dependencies needs no build approval or C++ toolchain.',
616
- os: [target.split('-')[0]],
617
- cpu: [target.split('-')[1]],
618
- dependencies: deps,
619
- license: 'MIT',
620
- repository: readJson(join(pkgRoot, 'package.json'))?.repository ?? undefined,
621
- }, null, 2) + '\n', 'utf8');
622
- plan.push({ dir: aggDir, file: tgzName(aggName, pluginVersion), aggregator: true, target });
623
- }
676
+ // 3b) --reuse 时平台专属目录已存在,就地复查闸门
677
+ if (REUSE) for (const item of reusedItems) {
678
+ if (PLATFORM_SUFFIX.test(String(readJson(join(item.dir, 'package.json'))?.name ?? ''))) platformDirs.push(item.dir);
679
+ }
680
+ verifyOptionalSubtreeDeps(platformDirs);
681
+ // 3) 重打包表(lib/vendored.json,随插件发布):运行时用它把真名子包补成原始名字
682
+ const vendoredDoc = {
683
+ schemaVersion: 1,
684
+ generatedBy: 'scripts/vendor-repacks.mjs',
685
+ scope: SCOPE,
686
+ targets,
687
+ modules: modules.map((m) => {
688
+ const entry = { alias: m.alias, package: m.package, version: m.version };
689
+ if (m.platform) entry.platform = true;
690
+ return entry;
691
+ }),
692
+ };
693
+ writeFileSync(join(pkgRoot, 'lib', 'vendored.json'), `${JSON.stringify(vendoredDoc, null, 2)}\n`, 'utf8');
694
+ const independent = modules.filter((m) => !m.platform);
695
+ const specific = modules.filter((m) => m.platform);
696
+ console.log(`[repack] 重打包表 → lib/vendored.json(${modules.length} 个:全平台 ${independent.length} / 平台专属 ${specific.length})`);
624
697
 
625
698
  writeFileSync(join(OUT, 'pack-plan.json'), JSON.stringify(plan, null, 2) + '\n', 'utf8');
626
699
  console.log(`[repack] 生成 ${plan.length} 个待打包目录 → repack/ (计划:repack/pack-plan.json)`);
627
700
 
628
- // 4) 改写插件 package.json 的依赖:纯 JS 直装集 + VS Code 树包(平台无关);
629
- // 平台专属的(原生包)通过聚合包按 os/cpu 自动选。
701
+ // 4) 改写插件 package.json 的依赖:树 + 纯 JS 直装集 + **全平台**重打包包写 dependencies(真名);
702
+ // 平台专属的重打包包按目标写 optionalDependencies(真名 + 包自带 os/cpu,包管理器按架构自动选),
703
+ // 它们自己的注册表依赖再由 OPTIONAL_SUBTREE_DEPS 提到 dependencies(否则 pnpm 丢包,见该表注释)。
704
+ // 这样每个包在依赖图里都是「根项目的直接依赖」,不依赖 pnpm 对可选子树别名的处理
705
+ // (见 docs/desktop-first-install-root-cause.md)。
630
706
  const pkgFile = join(pkgRoot, 'package.json');
631
707
  const pluginPkg = readJson(pkgFile);
632
708
  pluginPkg.dependencies = Object.fromEntries([
633
709
  [vscodePkg.name, vscodePkg.version],
634
710
  ...declare.map((d) => [d.name, d.version]),
711
+ ...independent.map((m) => [m.package, m.version]),
712
+ ...Object.entries(OPTIONAL_SUBTREE_DEPS),
635
713
  ].sort(([a], [b]) => a.localeCompare(b)));
636
714
  pluginPkg.optionalDependencies = Object.fromEntries(targets
637
- .map((t) => [`${SCOPE}/dsh-code-server-runtime-${t}`, `^${pluginVersion}`]));
715
+ .flatMap((t) => specific.map((m) => [`${m.package}-${t}`, m.version]))
716
+ .sort(([a], [b]) => a.localeCompare(b)));
638
717
  writeFileSync(pkgFile, JSON.stringify(pluginPkg, null, 2) + '\n', 'utf8');
639
- console.log(`[repack] 已写入 package.json:dependencies ${pluginPkg.dependencies ? Object.keys(pluginPkg.dependencies).length : 0} 个`
640
- + `(VS Code 树 + 纯 JS),optionalDependencies ${targets.length} 个平台聚合包`);
718
+ console.log(`[repack] 已写入 package.json:dependencies ${Object.keys(pluginPkg.dependencies).length} 个`
719
+ + `(VS Code 树 + 纯 JS + ${independent.length} 个全平台重打包包`
720
+ + ` + ${Object.keys(OPTIONAL_SUBTREE_DEPS).length} 个 optional 子树兜底依赖),`
721
+ + `optionalDependencies ${Object.keys(pluginPkg.optionalDependencies).length} 个`
722
+ + `(${specific.length} 个平台专属重打包包 × ${targets.length} 个目标)`);
641
723
 
642
724
  // 5) 可选:直接打包
643
725
  if (DO_PACK) {