dsh-code-server-app 0.3.47 → 0.3.50

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
@@ -364,13 +364,25 @@ Diagnostics: `GET /api/code-server/status` exposes
364
364
  ## code-server workspace and process lifecycle
365
365
 
366
366
  - code-server's workspace **follows the active DSH session/workspace**: switching sessions/workspaces while the IDE is open moves code-server to the new directory
367
- (resolution order: current session cwd → session's workspace.path → recentWorkspace.path → first workspace.path);
367
+ (resolution order: current session cwd → session's `workspace.path` → workspace of the most recently active session → first workspace.path;
368
+ **where "the current session" comes from depends on the DSH version**: ≥ 0.1.6-alpha.2 reads the session-scoped standard prop `sessionId`,
369
+ ≤ 0.1.6-alpha.1 falls back to `current` on the session-list snapshot — see the 0.3.48 bullet below; the logic lives in `src/workspace.js`
370
+ and the contract for both shapes is pinned by `scripts/test-client-bundle-cwd.mjs` directly against the built bundle);
368
371
  the opened directory is shown inside code-server (`?folder=<cwd>`, the page reloads when following a switch);
369
372
  implementation note: the iframe `src` must carry `?folder=<cwd>` — code-server's front-end remembers the "last workspace" and restores it by itself;
370
373
  a bare root URL only shows the previously opened directory and does not follow switches (verified locally).
371
374
  **Windows path format (verified)**: the `folder` parameter must start with `/` and use forward slashes only, e.g. `/C:/Users/User/Desktop/biss`;
372
375
  a bare Windows path (`C:\...`) is parsed as a URI scheme and the drive letter is stripped (page shows `\Users\User\...` with an empty file tree),
373
376
  while `file:///C:/...` reports "Workspace does not exist".
377
+ - **0.3.48 fixes "opening Code Server no longer opens the matching workspace"**: DSH **0.1.6-alpha.2** removed `current`
378
+ from `SessionListState` (upstream refactor: view selection remains outside the Controller), while 0.3.46 and earlier read
379
+ the current session from `useSessions(s => s).current` — so the cwd was always undefined, the client **stopped sending `cwd`**
380
+ to the host, and the IDE started with an **empty workspace** (measured locally: `cwd`/`launchCwd` both empty in
381
+ `$DSH_HOME/code-server/pid.json`, with nothing visible in the UI). Since 0.3.48 it reads the session-scoped standard prop
382
+ `sessionId` (the same source DSH's own right-sidebar tab uses — `ui-deliverables`' ReviewTab does
383
+ `useSessions(s => s.byId[sessionId]?.cwd)`), keeping the old `current` as a backward-compatible fallback; when neither
384
+ source resolves, it **does not guess a directory** (no cwd is sent, the workbench keeps its current one) and logs a
385
+ `[code-server] 未能解析当前工作区目录…` warning — the silence is exactly what made this bug hard to find.
374
386
  - **The switch is lightweight (since 0.2.12)**: a running instance is **not restarted** when the workspace changes — the host
375
387
  only updates `state.cwd` and the workbench re-navigates with the new `?folder=` (the workspace directory was always the
376
388
  client URL's business; the process cwd only affects the server's own relative-path resolution at spawn time). The switch is
@@ -465,8 +477,95 @@ Both workflows live in `.github/workflows/`, and the regression list exists exac
465
477
  | Workflow | Trigger | What it does |
466
478
  |---|---|---|
467
479
  | `ci.yml` | push to `main` / PR / manual | `ubuntu-latest` + `windows-latest` matrix: `pnpm install --frozen-lockfile` → `build:client` → `build:webview` → `pnpm test` (the whole suite) → `vendor:check` (report only) → upload `lib/client.js` and the panel assets |
468
- | `release.yml` | push a `v<version>` tag / manual (rehearsal, never publishes) | prepares `vendor/vscode` **at the version pinned in `dependencies`** → builds → full suite → `pnpm pack` → verifies the tarball manifest → **really installs it** (deploys a real DSH on the runner, `dsh plugin --profile web add <tgz>`, then `test:installed` + `dump-config` assertions) → publishes to npm **`next`** → creates a GitHub Release with the tgz attached |
469
- | `linux-repack-probe.yml` | push to this file / manual | **feasibility probe (never publishes)**: on Linux, builds the platform-specific repack packages per target (`linux-x64` → `ubuntu-latest`, `linux-arm64` → `ubuntu-24.04-arm`) and reports which modules really produce a `.node` and which are Windows-only. It runs the existing `vendor-repacks.mjs` itself; all writes happen in a copy of the repo under `$RUNNER_TEMP` |
480
+ | `release.yml` | push a `v<version>` tag / manual (rehearsal, never publishes) | prepares `vendor/vscode` **at the version pinned in `dependencies`** → builds → full suite → `pnpm pack` → verifies the tarball manifest → **really installs it twice** (windows-latest proves the 16 win32 sub-packages, ubuntu-latest the 10 Linux ones: each deploys a real DSH, installs via the official path `dsh plugin --profile web add <tgz>`, then runs the `test:installed` + `dump-config` assertions; both legs must pass before anything is published) → publishes to npm **`next`** → creates a GitHub Release with the tgz attached |
481
+ | `linux-repack-probe.yml` | push to this file / manual | **feasibility probe (never publishes; superseded by the Linux legs of `repacks.yml`)**: on Linux, builds the platform-specific repack packages per target (`linux-x64` → `ubuntu-latest`, `linux-arm64` → `ubuntu-24.04-arm`) and reports which modules really produce a `.node` and which are Windows-only. It runs the existing `vendor-repacks.mjs` itself; all writes happen in a copy of the repo under `$RUNNER_TEMP`. **Note**: it emits one notice per module, which hits GitHub's ~20-annotations-per-check-run cap and leaves only the tail; for the full verdict use the Linux legs of `repacks.yml` (one summary line per target) |
482
+ | `repacks.yml` | manual (`publish` and `probe_oidc` both default to **false**, the four `build_*` legs default to **true**) / push to this file / push `.github/oidc-probe.enabled` | **builds and publishes the platform-specific sub-packages** (`@jinsiyu/dshcs-*`): one host-architecture runner per target (`win32-x64` → `windows-latest`, `win32-arm64` → `windows-11-arm`, `linux-x64` → `ubuntu-latest`, `linux-arm64` → `ubuntu-24.04-arm`); by default it only builds and uploads `repack/tgz/*.tgz`, and only publishes to npm (default `next`) when `publish` is checked. Ownership and ordering (**five legs, disjoint sets**): the `independent` leg runs **first** (windows-latest; it produces the **VS Code tree package + the 8 platform-independent repacks**, which are the same artifact for all four targets and are therefore published only once); the four platform-specific legs `needs: independent`, build with `--skip-independent` and publish with `--only <their own target>` ⇒ a broken base layer blocks the rest (no half-published state) and no package name is ever published twice. **Auth**: with no `NPM_TOKEN` it uses OIDC (per-package trust entries, all with workflow `repacks.yml` — see below). The Linux legs additionally verify that the `lib/vendored.json` / `package.json` they generate match the committed ones (the platform policy is meant to be host-independent). A `probe-oidc` job additionally does a **staged-only** probe of that OIDC route, so the channel can be proven without publishing anything real |
483
+
484
+ ### Linux support (x64 / arm64): what changed, what is still missing
485
+
486
+ Supporting Linux is not mainly about "compiling a few more packages" — it is about replacing
487
+ **host scanning** with an **explicit platform policy**:
488
+
489
+ - Upstream packages barely declare `os`/`cpu` (of the 16 modules, only `@vscode/windows-ca-certs` does),
490
+ and the tree manifest lists all 8 native modules as ordinary `dependencies` — so on Linux npm installs
491
+ the Windows-only ones anyway. `analyze()` classifies by "does this host have a `.node`", so **the
492
+ classification drifts with the host**: on Linux `windows-registry` looks platform-independent and would be
493
+ written into `dependencies`, which then makes the Windows runtime look for a `-win32-*` sub-package that
494
+ is not there.
495
+ - Therefore `scripts/repack-platforms.json` is the single, human-reviewed declaration: each module's
496
+ `platform` (does it need per-platform packaging) and `targets` (which targets have a sub-package). The
497
+ generator only reads it, and derives from it: the per-module `targets` in `lib/vendored.json` (at runtime
498
+ `lib/native.js` uses them to **skip modules that do not apply to this platform**, instead of reporting
499
+ `dshcs-vscode-windows-registry-linux-x64` — a name that can never exist — as missing) and the plugin's
500
+ `optionalDependencies` (no longer blindly module × every target).
501
+ - The generator also now: keeps table entries it cannot see on this host (building only the host target with
502
+ `--target` must not drop the other platforms' modules), **skips** a per-platform package when no `.node`
503
+ was produced for that target (rather than publishing an empty shell), and validates ELF `e_machine` for
504
+ Linux targets (mirroring the PE machine check for win32).
505
+ - **Version policy (independent of host *and* of when you run it)**: the version recorded for each module in
506
+ `lib/vendored.json` is **the one we have actually published to the registry**; upstream drift never changes
507
+ it silently. Two measured cases with the same `code-server@4.137.0`: `kerberos` resolves to upstream `2.1.1`
508
+ while we published `2.1.1-dshcs.1`, and `@vscode/proxy-agent` resolved to `0.44.0` for the maintainer but to
509
+ `0.45.0` on a fresh install today (the dependency range allows drift, and `0.45.0` was never published for
510
+ our sub-package). Writing the tree's version would point the plugin's dependency at a version that does not
511
+ exist — an install that simply fails, on a different host or another day.
512
+ **To adopt a newer upstream version**: pin `"version"` for that module in `scripts/repack-platforms.json`,
513
+ re-run `vendor-repacks.mjs`, publish the new sub-packages, then refresh the dependency table and
514
+ `pnpm-lock.yaml`. The generator always prints such drift (`· <module>: 沿用表里已发布的版本 …`), so follow
515
+ that line.
516
+ > Keep the two dependency classes apart: the rule above governs **our own repack sub-packages**
517
+ > (`@jinsiyu/dshcs-*`, whose version must be one we published). **Upstream pure-JS direct dependencies**
518
+ > (the `declare` set: `cookie`, `ws`, `@vscode/proxy-agent`, …) take their version from the source tree —
519
+ > whatever the tree installed came from npm, so following the tree is safe. Differences there are
520
+ > **time**-related (a fresh install of the same commit on another day can differ), which is why
521
+ > `repacks.yml` reports them as a notice rather than a warning.
522
+ - **System headers needed to build the native packages on Linux**: `kerberos` needs the GSSAPI headers
523
+ (`gssapi/gssapi.h`), so both Linux legs run `sudo apt-get install -y libkrb5-dev` and assert the header is
524
+ present. Without it `make` fails outright and the `kerberos` sub-package cannot be produced (exposed by the
525
+ hard gate on 2026-09-16). Do the same before re-packing locally on Linux.
526
+ - **Measured on Linux** (both legs really compile; the verdict line reads
527
+ `[repack] linux-x64: 平台专属产出 5 个(…)`): five modules produce a `.node` —
528
+ `@vscode/deviceid`, `@vscode/native-watchdog`, `@vscode/spdlog`, `@vscode/sqlite3`, `kerberos`;
529
+ `windows-ca-certs` / `windows-process-tree` / `windows-registry` are Windows-only (whitelisted for
530
+ `win32-*` and reported as "excluded by the whitelist" on Linux). The earlier manual probe
531
+ `linux-repack-probe.yml` has been **removed**: its job (build without publishing) is now done by these two
532
+ legs, and its per-module notices hit GitHub's ~20-annotations-per-check-run cap, so only the tail was
533
+ readable.
534
+
535
+ **Linux go-live status (updated 2026-09-17)**:
536
+
537
+ 1. ✅ **The 10 Linux sub-packages are published** (5 modules × x64/arm64). A trust entry cannot exist before
538
+ the package does, so the maintainer did the first publish locally with 2FA (`npm login --auth-type=web`,
539
+ then one `npm publish <tgz>` per package). Versions match `lib/vendored.json` exactly, and
540
+ `os=linux` / `cpu=x64|arm64` were verified against the registry.
541
+ 2. ⏳ **Add one trust entry per new package name** (one command each; browser confirmation is enough,
542
+ **no OTP needed**):
543
+
544
+ ```powershell
545
+ $env:npm_config_auth_type = 'web'; npm login # skip if already logged in
546
+ $names = @(
547
+ '@jinsiyu/dshcs-kerberos-linux-arm64','@jinsiyu/dshcs-kerberos-linux-x64',
548
+ '@jinsiyu/dshcs-vscode-deviceid-linux-arm64','@jinsiyu/dshcs-vscode-deviceid-linux-x64',
549
+ '@jinsiyu/dshcs-vscode-native-watchdog-linux-arm64','@jinsiyu/dshcs-vscode-native-watchdog-linux-x64',
550
+ '@jinsiyu/dshcs-vscode-spdlog-linux-arm64','@jinsiyu/dshcs-vscode-spdlog-linux-x64',
551
+ '@jinsiyu/dshcs-vscode-sqlite3-linux-arm64','@jinsiyu/dshcs-vscode-sqlite3-linux-x64')
552
+ foreach ($n in $names) {
553
+ npm trust github $n --file repacks.yml --repo jinsiyu/dsh-code-server-app --allow-publish -y
554
+ }
555
+ ```
556
+ Afterwards you can **delete `NPM_TOKEN`** from the repository secrets — CI then uses OIDC and will no
557
+ longer hit the "token without bypass 2FA ⇒ EOTP" trap (which only mattered for first-publishing a name).
558
+ 3. ✅ **The dependency table is wired up**: `publishedTargets` in `scripts/repack-platforms.json` now includes
559
+ `linux-*`; `package.json` has **26** `optionalDependencies` (16 win32 + 10 linux, derived from
560
+ "per-module whitelist ∩ published targets", verified item by item by `test-vendored-table.mjs`);
561
+ `pnpm-lock.yaml` was refreshed (10 additions, nothing else changed); and the 10 `@version` pairs were
562
+ added to `minimumReleaseAgeExclude` in `pnpm-workspace.yaml` (freshly published packages are held back
563
+ by the supply-chain cooldown otherwise).
564
+
565
+ > What remains is our own release flow: bump the plugin version → `pnpm pack` → install once locally via
566
+ > `dsh plugin --profile web add <tgz>` → tag it and let `release.yml` publish. On Linux, `lib/native.js`
567
+ > then links the `-linux-*` sub-packages back to their original names through the same junction path
568
+ > Windows already uses.
470
569
 
471
570
  The regression suite (also the single list CI uses) is:
472
571
 
@@ -527,6 +626,63 @@ One-time setup (repository / account side, no files involved):
527
626
  - Alternative: add an `NPM_TOKEN` repository secret and set the repository variable
528
627
  `NPM_AUTH_MODE=token` (that mode does not attach provenance). Note npm is restricting legacy
529
628
  2FA-bypassing tokens, so OIDC is the durable option — once it works, delete the token.
629
+
630
+ **Authentication for `repacks.yml` (the platform-specific sub-packages)** — two routes; the workflow picks
631
+ one itself (with no `NPM_TOKEN` it uses OIDC):
632
+
633
+ - **A. `NPM_TOKEN` (least friction today)**
634
+ 1. npmjs.com → avatar → **Access Tokens** → **Generate New Token** → **Granular Access Token**;
635
+ 2. any name (e.g. `github-actions-repacks`) and an expiry (90 days is fine);
636
+ 3. **Packages and scopes** = **Read and write**, tick only the **`@jinsiyu`** scope (not "All packages");
637
+ 4. you **must** tick **"Bypass two-factor authentication (2FA)"** — otherwise an unattended publish
638
+ stalls waiting for a one-time password;
639
+ 5. the token is shown **once** — copy it immediately;
640
+ 6. GitHub repository → Settings → Secrets and variables → **Actions** → **New repository secret**,
641
+ the name **must** be `NPM_TOKEN` (that is what the workflow reads).
642
+ ⚠️ Per npm's announcements: since 2026-07-31 bypass-2FA tokens can no longer perform account/package
643
+ management, and **from January 2027 they lose direct publish** (only reading private packages plus staging
644
+ a publish, which a maintainer approves with 2FA) — so plan to move to B.
645
+ - **How to read a failed publish** (`publish-repacks.mjs` first runs a token health check: it prints only
646
+ the token's length and shape, never its content, then uses `npm whoami` to prove it authenticates):
647
+ · `E401` / `ENEEDAUTH` ⇒ **the token value is wrong**: surrounding quotes or a trailing newline, a
648
+ truncated paste, or a revoked token ⇒ generate a fresh one and paste it again (a granular token looks
649
+ like `npm_…` followed by a long random string; a classic one is a 36-character UUID);
650
+ · `npm whoami` succeeds but publishing returns **`EOTP` (This operation requires a one-time password)**
651
+ ⇒ **the value is fine**, what is missing is a permission attribute: the token does not have step 4's
652
+ **"Bypass two-factor authentication (2FA)"** ticked. npm also requires 2FA for the **first publish of a
653
+ new package name**, and a trust entry cannot exist before the package does ⇒ the first publish has to be
654
+ done locally with `npm publish <tgz> --access public --tag next` and an OTP (publish only that target's
655
+ own `-<target>` packages; do not re-publish the already-published tree package), after which one trust
656
+ entry per new name switches those packages to OIDC.
657
+ - **B. per-package trusted publishing (the durable route)**: npm trust entries are **per package**
658
+ (since 2026-09 a package may have several, but there is **no scope-level** entry), so these 25 packages
659
+ need 25 entries. Generate the commands, then run them one by one after a single browser authorization:
660
+ ```powershell
661
+ node -e "const t=require('./lib/vendored.json');const p=require('./package.json');const tree=Object.keys(p.dependencies).find(n=>n.endsWith('/dshcs-vscode-server'));const names=[tree,t.modules.flatMap(m=>m.platform?t.targets.map(x=>m.package+'-'+x):m.package)];require('fs').writeFileSync('trust-all.txt',names.flat().map(n=>'npm trust github '+n+' --file repacks.yml --allow-publish -y').join('\n')+'\n')"
662
+ Get-Content trust-all.txt | ForEach-Object { Invoke-Expression $_ }
663
+ ```
664
+ Afterwards delete `NPM_TOKEN` (the workflow then uses OIDC). npm also lets each entry be **staging-only**
665
+ (a version only goes live after you approve it with 2FA) — safer, but each batch then needs manual
666
+ approvals for several versions.
667
+ Verify with `npm trust list <package>`, which should show `file: repacks.yml` and
668
+ `repository: jinsiyu/dsh-code-server-app` (all 25 packages are required — a missing one fails at publish
669
+ time with "no matching trust configuration", and that line shows up as an annotation in the run without
670
+ needing a token).
671
+ - **Verifying that route (without waiting for a real publish)**: `repacks.yml` has a `probe-oidc` job that does a
672
+ **staged** publish for four real sub-package names (`vscode-fs-copyfile`, `node-pty`,
673
+ `kerberos-win32-arm64`, `vscode-server`), with versions like `2.0.1-oidc-probe.<run>`.
674
+ This only works from CI: OIDC tokens are minted at run time, and a trust entry matches on
675
+ repository + **workflow filename** + package name — which is also why the probe has to live in
676
+ `repacks.yml` itself. `npm stage publish` follows exactly the same auth path as `npm publish`, but the
677
+ version lands in the staging queue and **never in the registry's published version list** (the script
678
+ re-checks that with `npm view <pkg> versions`), so no version number is consumed and no dependency
679
+ resolution changes. Trigger it either from Actions → repacks → Run workflow with `probe_oidc` checked, or
680
+ token-free by committing the sentinel `.github/oidc-probe.enabled` and pushing. Results are emitted as
681
+ `::notice::` / `::error::` annotations, readable on a public repo without logging in
682
+ (`GET /repos/jinsiyu/dsh-code-server-app/check-runs/<id>/annotations`). Afterwards **reject** the staged
683
+ probe versions (`npm stage list`, then `npm stage reject <id>`; needs 2FA on your machine) — do **not**
684
+ approve, since approving is what would turn a probe into a real version. Delete the sentinel file to
685
+ return to "no automatic probe".
530
686
  - **Optional** repository variable `DSH_UI_VERSION` = the version of `@deepseek-ai/dsh-web-frontend` in the current
531
687
  deployment: when set, `release.yml` enforces that the panel renderer matches the deployed UI (the local
532
688
  `build:webview` always checks this; a runner has no DSH deployment).
@@ -550,6 +706,13 @@ Things you must know:
550
706
  tests throw `schemastery not found`. It never ships to users (devDependencies are not installed for a
551
707
  dependency). A CI job that actually deploys DSH is possible (`@deepseek-ai/dsh` is public on npm), but it
552
708
  pulls the whole harness (~1.3GB profile), so it belongs in a slower job, not on every push.
709
+ - The reverse case — an **installed copy** (not a repo checkout) has no repo `node_modules` — is why the
710
+ deployment-layout table in `lib/dsh-resolve.mjs` must cover every real layout: since 0.3.49 it knows the
711
+ running deployment (`argv[1]`/`execPath` resolution), `%APPDATA%\npm`, `npm --prefix` (`~/.npm-global`),
712
+ pnpm global, nvm, system `/usr/local|/usr`, and the `$DSH_HOME` profile level. The 0.3.48 Linux install-smoke
713
+ leg (CLI installed into `~/.npm-global`) failed precisely because that table was too narrow (the smoke threw
714
+ `schemastery not found` and `publish` was skipped); regression: `scripts/test-dsh-resolve.mjs` builds each
715
+ layout in a temp directory, since a developer machine never hits them by accident.
553
716
  - The first release must use a **version that has never been published** (npm versions are immutable).
554
717
  `release.yml` supports a `workflow_dispatch` **rehearsal** (full pipeline, nothing published) — run it once
555
718
  before pushing a real tag.
package/README.md CHANGED
@@ -343,13 +343,24 @@ host 反向请求不到它。所以编辑器状态只能在扩展主动发起的
343
343
  ## code-server 服务目录与进程生命周期
344
344
 
345
345
  - code-server 服务目录**跟随活动工作区/会话**:打开期间切换 DSH 会话/工作区,code-server 自动切到新目录
346
- (解析优先级:当前会话 cwd → 会话所属 workspace.path → recentWorkspace.path → 首个 workspace.path);
346
+ (解析优先级:当前会话 cwd → 会话所属 `workspace.path` → 最近活跃会话所属 workspace → 首个 workspace.path;
347
+ **"当前会话"的信源随 DSH 版本而变**:≥ 0.1.6-alpha.2 读会话作用域标准 prop `sessionId`,
348
+ ≤ 0.1.6-alpha.1 退回会话列表快照上的 `current` —— 详见下面 0.3.48 那条;纯逻辑在 `src/workspace.js`,
349
+ 两版形状的契约由 `scripts/test-client-bundle-cwd.mjs` 直接对构建产物钉住);
347
350
  打开目录显示在 code-server 页面内(`?folder=<cwd>`,跟随切换时页面自动重新加载);
348
351
  实现要点:iframe src 必须带 `?folder=<cwd>`——code-server 前端会记住“最近工作区”并自行恢复,
349
352
  仅用裸根 URL 只会显示上一次打开的目录、不会跟随切换(本机实测确认)。
350
353
  **Windows 路径格式(实测)**:folder 参数必须以 `/` 开头且全部正斜杠,形如 `/C:/Users/User/Desktop/biss`;
351
354
  裸 Windows 路径(`C:\...`)会被前端当 URI scheme 而剥掉盘符(页面显示 `\Users\User\...` 且文件树为空),
352
355
  `file:///C:/...` 形式则报 “Workspace does not exist”。
356
+ - **0.3.48 修的是"打开 Code Server 不打开对应工作区"**:DSH **0.1.6-alpha.2** 把 `current` 从 `SessionListState`
357
+ 移出了会话列表 store(refactor 原话:view selection remains outside the Controller),而 0.3.46 及更早版本
358
+ 正是从 `useSessions(s => s).current` 里取"当前会话" ⇒ cwd 恒为 undefined ⇒ 客户端**不再向宿主发 cwd**
359
+ ⇒ IDE 以**空工作区**启动(本机实测:`$DSH_HOME/code-server/pid.json` 里 `cwd`/`launchCwd` 双空,
360
+ UI 上看不出任何报错)。0.3.48 起改读会话作用域标准 prop `sessionId`(与官方右侧栏标签
361
+ `ui-deliverables` 的 ReviewTab 同一信源:`useSessions(s => s.byId[sessionId]?.cwd)`),旧的 `current`
362
+ 作为向后兼容兜底保留;两条信源都拿不到时**不猜目录**(不发 cwd,workbench 保持当前目录),
363
+ 并在控制台留一条 `[code-server] 未能解析当前工作区目录…` 警告 —— 这个坑当初就是"静默"才难查。
353
364
  - **切换是"轻量"的(0.2.12 起)**:运行中切工作区**不重启 IDE 进程**,host 只把 `state.cwd` 改成新目录,
354
365
  由 workbench 拿新的 `?folder=` 重新导航(工作区目录本来就由客户端 URL 决定,进程 cwd 只影响它自己
355
366
  spawn 时的相对路径解析)。因此切换**不再丢**扩展宿主/后台任务/服务端状态,也快得多。
@@ -428,9 +439,9 @@ pnpm run promote -- <version>
428
439
  | **指定版本** | `pnpm run vendor:vscode -- --version 4.137.0` |
429
440
  | **从已装好的树快照** | `pnpm run vendor:vscode -- --from <code-server 目录>`(秒级) |
430
441
  | **开发期让树可直接跑** | `pnpm run vendor:vscode -- --dev-links`(额外把 `lib/vscode/node_modules` 用 junction 补上) |
431
- | **完整重打子包** | `pnpm run repack:build -- --target win32-arm64,win32-x64 --pack`(不给 `--from` 会自动 npm install 解包 + 编译,耗时) |
442
+ | **完整重打子包** | `pnpm run repack:build -- --target win32-arm64,win32-x64 --pack`(不给 `--from` 会自动 npm install 解包 + 编译,耗时)。`--target` 只决定**本次在这台机器上构建哪些目标**(原生包要现编,所以 Linux 目标在 Linux 机器或 `repacks.yml` 的 Linux 腿上构建);产品覆盖哪些目标、每模块在哪些目标上有子包,由 `scripts/repack-platforms.json` 决定 |
432
443
  | **只重打树包 + 依赖表** | `node scripts/vendor-repacks.mjs --reuse --target win32-arm64,win32-x64 --pack`(复用 `repack/build` 里已有的原生包,不重新分析源树;顺带重写 `lib/vendored.json` 与插件依赖表) |
433
- | **发布子包** | `pnpm run publish:repacks`(`--dry-run` 预览;`--only <子串>` 过滤;`--otp <code>` / `--limit N` 应对 2FA) |
444
+ | **发布子包** | 推荐用 CI:`.github/workflows/repacks.yml`(Actions → repacks → Run workflow,勾 `publish`);本地等价命令 `pnpm run publish:repacks`(`--dry-run` 预览;`--only <子串>` 过滤;`--otp <code>` / `--limit N` 应对 2FA;默认跳过已存在的版本,可反复重跑) |
434
445
  | **发布插件本体** | `pnpm run publish:plugin`(发布**已验证过的那份 tarball**,不会重新打包;默认 dist-tag = `next`) |
435
446
  | **推进 latest** | `pnpm run promote -- <version>`(用户重启确认无误后;`--dry-run` 先看当前标签) |
436
447
  | **只报告版本** | `pnpm run vendor:check` |
@@ -451,8 +462,12 @@ pnpm test:bridge-extension # 编辑器桥扩展侧纯逻辑:未保存缓冲区
451
462
  pnpm test:webview # 面板 webview 产物:官方渲染器与令牌打包、版本一致、/approve 的四条约束(先跑 build:webview)
452
463
  pnpm test:launcher-routes # launcher 的 HTTP 面(起真进程,较慢)
453
464
  pnpm test:workspace-switch # 切工作区不重启进程
465
+ pnpm test:workspace-cwd # "当前工作区目录"解析:DSH 0.1.6-alpha.2(sessionId)与旧版(current)两套形状
466
+ pnpm test:client-cwd # 同一件事但直接对**构建产物** lib/client.js 验(注册出来的 body 真发不发 cwd、URL 带不带 folder;先跑 build:client)
467
+ pnpm test:client-seat # 设置卡住哪个座位:插件页 plugins.bundle.config(DSH ≥ 0.1.6-alpha.2)vs settings.plugin.item(≤ alpha.1;新版已退役)
454
468
  pnpm test:fullscreen # 打开标签即全屏
455
469
  pnpm test:vendored # 重打包表 ↔ 插件依赖表一致(无 npm: 别名 / 无聚合包 / vendored.json 进了 files)
470
+ pnpm test:dsh-resolve # 部署位置表:各平台全局装布局(npm --prefix / nvm / pnpm global / %APPDATA%)都能找到 DSH 部署
456
471
  pnpm test:installed # 安装冒烟:对**已装进 profile 的产物**做断言(默认 <DSH_HOME>/profiles/web)
457
472
  # files 白名单每条都在 / 重打包包在当前平台齐全 / 原生模块无缺失 /
458
473
  # 已安装副本能 import / 树在位 —— 仓库回归看不出这一类
@@ -466,6 +481,12 @@ pnpm test:installed # 安装冒烟:对**已装进 profile 的产物**
466
481
  > 从 DSH 部署里取它(生产里就是 profile 中 hoist 的那一份),而干净 clone / CI runner 上没有 DSH,
467
482
  > 不钉一份的话 `apply` 类测试会直接抛 `schemastery not found`。它不进发布物(devDependencies
468
483
  > 不会给使用者安装)。
484
+ > **反过来在"已安装副本"里就取不到了**(那不是仓库目录):`lib/dsh-resolve.mjs` 的部署位置表
485
+ > 得把用户真实布局都覆盖上 —— 0.3.49 起包括"正在跑的部署(`argv[1]`/`execPath` 向上解析)、
486
+ > `%APPDATA%\npm`、`npm --prefix`(`~/.npm-global`)、pnpm global、nvm、系统 `/usr/local|/usr`、
487
+ > 以及 `$DSH_HOME` 的 profile 层"。0.3.48 的 Linux 安装冒烟腿(CLI 装到 `~/.npm-global`)就是
488
+ > 栽在这张表太窄上(冒烟 ④ 抛 `schemastery not found`,publish 被 skip);回归见
489
+ > `scripts/test-dsh-resolve.mjs`(造一整套临时布局逐个验,本机不会自然碰到那些布局)。
469
490
 
470
491
  ## GitHub Actions(CI + 打 tag 发布)
471
492
 
@@ -474,8 +495,78 @@ pnpm test:installed # 安装冒烟:对**已装进 profile 的产物**
474
495
  | 工作流 | 触发 | 做什么 |
475
496
  |---|---|---|
476
497
  | `ci.yml` | push `main` / PR / 手动 | `ubuntu-latest` + `windows-latest` 双平台:`pnpm install --frozen-lockfile` → `build:client` → `build:webview` → `pnpm test`(全套回归)→ `vendor:check` 只报告版本差 → 上传 `lib/client.js` 与面板产物 |
477
- | `release.yml` | 推 `v<version>` 标签 / 手动(演练,不发布) | 按 `dependencies` 钉的版本准备 `vendor/vscode` → 构建 → 全套回归 → `pnpm pack` → 校验 tarball 清单 → **真装一遍**(在 runner 上部署真 DSH,`dsh plugin --profile web add <tgz>`,再跑 `test:installed` + `dump-config` 断言)→ 发 npm **`next`** → 建 GitHub Release(附 tgz) |
478
- | `linux-repack-probe.yml` | push 本文件 / 手动 | **可行性探针(不发布)**:在 Linux 上按目标(`linux-x64` → `ubuntu-latest`、`linux-arm64` → `ubuntu-24.04-arm`)试编平台专属重打包包,输出「哪些模块真能编出 `.node`、哪些是 Windows-only」。跑的是现有 `vendor-repacks.mjs` 本尊,改动只发生在 `$RUNNER_TEMP` 的仓库副本里 |
498
+ | `release.yml` | 推 `v<version>` 标签 / 手动(演练,不发布) | 按 `dependencies` 钉的版本准备 `vendor/vscode` → 构建 → 全套回归 → `pnpm pack` → 校验 tarball 清单 → **真装两遍**(windows-latest 验 win32 的 16 个子包、ubuntu-latest 验 Linux 的 10 个:各部署一份真 DSH,走官方路径 `dsh plugin --profile web add <tgz>`,再跑 `test:installed` + `dump-config` 断言;两条腿都过才允许发布)→ 发 npm **`next`** → 建 GitHub Release(附 tgz) |
499
+ | `repacks.yml` | 手动(`publish` / `probe_oidc` 默认 **false**,四条腿的 `build_*` 默认 **true**)/ push 本文件 / push `.github/oidc-probe.enabled` | **平台专属子包(`@jinsiyu/dshcs-*`)的构建与发布**:同架构宿主 runner 各打一条(`win32-x64` → `windows-latest`、`win32-arm64` → `windows-11-arm`、`linux-x64` → `ubuntu-latest`、`linux-arm64` → `ubuntu-24.04-arm`),默认只构建 + 传 `repack/tgz/*.tgz`(**不发布**,所以它同时就是 Linux 可行性验证的正式位置);勾上 `publish` 才发 npm(默认 `next`)。发布归属与顺序(**五条腿、集合不相交**):**先跑** `independent`(windows-latest,产 **VS Code 树包 + 8 个平台无关重打包包** —— 它们在四个目标上是同一份产物,所以只发这一次);四条平台专属腿 `needs: independent`、构建带 `--skip-independent`、发布带 `--only <自己的目标>` ⇒ 基础层出问题时后面不会发出"半套"子包,也不会有人重复发同一个包名。**认证**:没配 `NPM_TOKEN` 就走 OIDC(per-package Trusted Publisher,workflow 都填 `repacks.yml`,见下)。Linux 腿还会顺带校验「Linux 上生成的 `lib/vendored.json` / `package.json` 与仓库里的一致」(平台政策应当宿主无关)。额外有一个 `probe-oidc` job:对几个真实子包名做**只暂存、不发正式版**的巡检,用来证明这条 OIDC 通道真的可用 |
500
+
501
+ ### Linux 适配(x64 / arm64):改了什么、还差什么
502
+
503
+ 支持 Linux 的关键不是「多编几个包」,而是**把平台政策从"宿主扫描"改成"显式声明"**:
504
+
505
+ - 上游包基本不写 `os`/`cpu`(实测 16 个模块里只有 `@vscode/windows-ca-certs` 写了),而树清单把这 8 个
506
+ 原生模块全放在普通 `dependencies` ⇒ Linux 上照样会装出 Windows-only 的包;`analyze()` 又是按
507
+ 「宿主有没有 `.node`」分类的 ⇒ **换宿主平台,分类会漂移**(Linux 上 `windows-registry` 会被判成
508
+ 平台无关、写进 `dependencies`,于是 Windows 运行时反而找不到 `-win32-*` 子包)。
509
+ - 所以新增 `scripts/repack-platforms.json` 作为**人工评审的唯一声明**:每个模块的 `platform`(要不要按平台
510
+ 分包)与 `targets`(在哪些目标上有子包)。生成器只读不写,并据此产出:
511
+ `lib/vendored.json` 的每模块 `targets`(运行时 `lib/native.js` 据此**跳过本平台不适用的模块**,
512
+ 不再把 `dshcs-vscode-windows-registry-linux-x64` 这种永远不会存在的包报成缺包)、以及插件
513
+ `optionalDependencies`(不再盲目 × 全部目标)。
514
+ - 生成器同时:保留本次分析看不到的条目(用 `--target` 只构建本机目标时,别的平台的模块必须原样留下)、
515
+ 平台专属包在该目标上编不出 `.node` 时**跳过而不是发空壳**、Linux 目标补 ELF `e_machine` 校验
516
+ (与 win32 的 PE machine 校验对称)。
517
+ - **版本政策(宿主与时点都无关)**:`lib/vendored.json` 里每个模块的版本 = **registry 上我们已经发布的那个号**,
518
+ 上游版本漂移**不会**自动改它。实测两例:同一个 `code-server@4.137.0`,`kerberos` 源树里是上游 `2.1.1`
519
+ 而我们发布的是 `2.1.1-dshcs.1`(聚合包时代的后缀);`@vscode/proxy-agent` 维护者当时装到 `0.44.0`、
520
+ 今天全新装到 `0.45.0`(依赖范围允许漂移,而 `0.45.0` 这个子包我们根本没发过)。照源树写,换个宿主平台
521
+ 或换一天重跑,插件依赖就会指向不存在的版本号、装上去直接解析失败。
522
+ **要让插件升到新的上游版本**:在 `scripts/repack-platforms.json` 里给该模块钉 `"version"` → 重跑
523
+ `vendor-repacks.mjs` → 发布新子包 → 刷新依赖表与 `pnpm-lock.yaml`。生成器每次都会把漂移打出来
524
+ (`· <模块>:沿用表里已发布的版本 …(源树里是 …)`),照着那行做即可。
525
+ > 要区分两类依赖:上面这条只管**我们自己的重打包子包**(`@jinsiyu/dshcs-*`,版本必须是发布过的号);
526
+ > 而**上游纯 JS 直装依赖**(`declare` 那批:`cookie` / `ws` / `@vscode/proxy-agent` …)的版本取自源树
527
+ > —— 树里的版本一定来自 npm,跟着树走是安全的。所以后者的差异是**时间**相关(同一 commit 换一天
528
+ > 全新装就可能不同),CI 的「生成表与仓库一致吗」把它当 notice 报,不当 warning。
529
+ - **Linux 上构建原生包的系统依赖**:`kerberos` 要 GSSAPI 头(`gssapi/gssapi.h`)⇒ 两条 Linux 腿都会先
530
+ 跑 `sudo apt-get install -y libkrb5-dev` 并校验头文件在位。缺它时 `make` 直接失败、`kerberos` 的子包
531
+ 产不出来(2026-09-16 第一次跑硬闸门时暴露)。本地在 Linux 上重打时同样要先装它。
532
+ - **Linux 上实测**(`linux-x64` / `linux-arm64` 两条腿各真编译一遍,结论行:
533
+ `[repack] linux-x64: 平台专属产出 5 个(…)`):这 5 个模块编出了 `.node` ——
534
+ `@vscode/deviceid` / `@vscode/native-watchdog` / `@vscode/spdlog` / `@vscode/sqlite3` / `kerberos`;
535
+ `windows-ca-certs` / `windows-process-tree` / `windows-registry` 是 Windows-only(白名单里只给 win32),
536
+ 在 Linux 上被白名单排除(一次运行里报「白名单排除 N 个」)。
537
+ 早先那个手工探针 `linux-repack-probe.yml` **已删除**:它的职责(不发布地试编)现在由这两条腿承担,
538
+ 而它按模块发 notice 会撞上「每个 check run 约 20 条 annotation」的上限、结论只能读到尾部。
539
+
540
+ **Linux 上线现状(2026-09-17 更新)**:
541
+
542
+ 1. ✅ **10 个 Linux 子包已首发**(5 个模块 × x64/arm64)。新包名没法预先建 Trusted Publisher,
543
+ 所以首发由维护者本机带 2FA 完成(`npm login --auth-type=web` → 逐个 `npm publish <tgz>`),
544
+ 版本与 `lib/vendored.json` 逐字一致,`os=linux` / `cpu=x64|arm64` 都在 registry 上核对过。
545
+ 2. ⏳ **给这 10 个新包名各加一条信任关系**(一条命令一条,浏览器确认即可,**不需要 OTP**):
546
+
547
+ ```powershell
548
+ $env:npm_config_auth_type = 'web'; npm login # 已登录可跳过
549
+ $names = @(
550
+ '@jinsiyu/dshcs-kerberos-linux-arm64','@jinsiyu/dshcs-kerberos-linux-x64',
551
+ '@jinsiyu/dshcs-vscode-deviceid-linux-arm64','@jinsiyu/dshcs-vscode-deviceid-linux-x64',
552
+ '@jinsiyu/dshcs-vscode-native-watchdog-linux-arm64','@jinsiyu/dshcs-vscode-native-watchdog-linux-x64',
553
+ '@jinsiyu/dshcs-vscode-spdlog-linux-arm64','@jinsiyu/dshcs-vscode-spdlog-linux-x64',
554
+ '@jinsiyu/dshcs-vscode-sqlite3-linux-arm64','@jinsiyu/dshcs-vscode-sqlite3-linux-x64')
555
+ foreach ($n in $names) {
556
+ npm trust github $n --file repacks.yml --repo jinsiyu/dsh-code-server-app --allow-publish -y
557
+ }
558
+ ```
559
+ 加完就可以把仓库 Secrets 里的 **`NPM_TOKEN` 删掉** —— CI 之后走 OIDC,不会再撞
560
+ "token 没勾 bypass 2FA ⇒ EOTP" 那个坑(它只在首发新包名时才是必需的)。
561
+ 3. ✅ **依赖表已接线**:`scripts/repack-platforms.json` 的 `publishedTargets` 已含 `linux-*`;
562
+ `package.json` 的 `optionalDependencies` 现在是 **26 项**(16 win32 + 10 linux,由
563
+ 「每模块白名单 ∩ 已发布目标」公式决定,`test-vendored-table.mjs` 会逐项校验);
564
+ `pnpm-lock.yaml` 已刷新(只新增 10 条,无其它改动);`pnpm-workspace.yaml` 的
565
+ `minimumReleaseAgeExclude` 也补了这 10 个 `@版本`(新发布的包会被供应链冷却期挡住)。
566
+
567
+ > 再下一步就是我们自己的发版流程:bump 插件版本 → `pnpm pack` → 本机 `dsh plugin --profile web add <tgz>`
568
+ > 确认 → 打 tag 走 `release.yml`。Linux 用户装到这个版本后,`lib/native.js` 会自动把
569
+ > `-linux-*` 子包按原名补成 junction(与 Windows 同一条路径)。
479
570
 
480
571
  **dist-tag 政策不变**:`release.yml` 只发 `next`,绝不碰 `latest`;`latest` 仍由 `pnpm run promote -- <version>`
481
572
  在重启 dsh web 确认无误后手动推进(README 上方「打包」一节)。
@@ -517,6 +608,60 @@ pnpm run promote -- 0.3.47 # 4) 确认无误后推 latest(手动
517
608
  - 备选:仓库 Secrets 加 `NPM_TOKEN`,并设仓库 Variables `NPM_AUTH_MODE=token`(该模式不发
518
609
  provenance)。注意 npm 正在收紧"绕过 2FA 的旧式 token"(见 registry 的
519
610
  bypass2fa-deprecation 提示),所以 OIDC 是更长期的做法;它一旦可用就该把 NPM_TOKEN 删掉。
611
+ - **`repacks.yml`(平台专属子包)的认证** —— 两条路,workflow 自己判断(没配 `NPM_TOKEN` 就走 OIDC):
612
+ - **A. `NPM_TOKEN`(目前最省事)**
613
+ 1. npmjs.com → 头像 → **Access Tokens** → **Generate New Token** → **Granular Access Token**;
614
+ 2. 名字随意(如 `github-actions-repacks`),给一个有效期(如 90 天);
615
+ 3. **Packages and scopes** 选 **Read and write**,只勾 **`@jinsiyu`** 作用域(别选 All packages);
616
+ 4. **必须勾 "Bypass two-factor authentication (2FA)"** —— 否则无人值守发布会卡在要一次性口令;
617
+ 5. 生成后令牌**只显示一次**,立刻复制;
618
+ 6. GitHub 仓库 → Settings → Secrets and variables → **Actions** → **New repository secret**,
619
+ 名字**必须**是 `NPM_TOKEN`(workflow 里读的就是它),值粘贴令牌。
620
+ ⚠️ npm 官方已公告:2026-07-31 起 bypass-2FA 令牌**不能再做账号/包管理类操作**,并且
621
+ **2027-01 起将失去直接发布**(只剩读取私有包 + 暂存发布,要维护者用 2FA 批准)⇒ 长期请迁到 B。
622
+ - **发布失败怎么读**(`publish-repacks.mjs` 会先做一次 token 体检:只打印长度与形状、绝不打印内容,
623
+ 再用 `npm whoami` 确认能不能认证):
624
+ · `E401` / `ENEEDAUTH` ⇒ **token 值不对**:带了引号或末尾换行、复制被截断、或已被撤销 ⇒ 重新生成再粘贴
625
+ (Granular token 形如 `npm_…`,长随机串;classic token 是 36 位 UUID);
626
+ · `npm whoami` 通过、但发布报 **`EOTP`(This operation requires a one-time password)** ⇒ **值是对的**,
627
+ 缺的是权限属性:该 token 没勾第 4 步的 **"Bypass two-factor authentication (2FA)"**;
628
+ 另外 npm 对**首次发布一个新包名**本身就要求 2FA,而新包名没法预先建 Trusted Publisher
629
+ ⇒ 首发只能本机 `npm publish <tgz> --access public --tag next` 带 OTP 走一次(只发该目标自己的
630
+ `-<目标>` 包,别重发已存在的树包),之后给这些包名各加一条信任关系即可转 OIDC。
631
+ - **B. per-package trusted publishing(长期方案)**:npm 的信任关系是**按包**配的(2026-09 起一个包
632
+ 可以配多条,但**没有作用域级**),有多少个子包就要多少条(win32 阶段是 25 条;Linux 上线后按
633
+ `lib/vendored.json` 里每模块的 `targets` 增加)。先生成命令,再在浏览器授权一次后逐条执行:
634
+ ```powershell
635
+ node -e "const t=require('./lib/vendored.json');const p=require('./package.json');const tree=Object.keys(p.dependencies).find(n=>n.endsWith('/dshcs-vscode-server'));const names=[tree,...t.modules.flatMap(m=>m.platform?m.targets.map(x=>m.package+'-'+x):[m.package])];require('fs').writeFileSync('trust-all.txt',names.map(n=>'npm trust github '+n+' --file repacks.yml --allow-publish -y').join('\n')+'\n')"
636
+ Get-Content trust-all.txt | ForEach-Object { Invoke-Expression $_ }
637
+ ```
638
+ > 注意这里用的是**每模块**的 `targets`(不是「模块 × 全部目标」):`@vscode/windows-registry`
639
+ > 只在 `win32-*` 有子包,`-linux-*` 的包名永远不会存在 —— 给不存在的包建信任关系只会白跑一遍。
640
+ 配完把 `NPM_TOKEN` 删掉即可(workflow 会自动走 OIDC)。npm 也允许把每条配置设成**只允许暂存发布**
641
+ (版本要你 2FA 批准才生效)—— 更安全,但每批子包都要你手动批准多个版本,按需取舍。
642
+ 核对:对任意子包执行 `npm trust list <包名>`,应显示 `file: repacks.yml` 与
643
+ `repository: jinsiyu/dsh-code-server-app`(25 个子包一个都不能少 —— 漏掉的那个包发布时会报
644
+ "没有匹配的信任配置",而那一行会以 annotation 出现在 run 里,不需要 token 就能读)。
645
+ - **验证这条通道(不必等真发布)**:`repacks.yml` 里有一个 `probe-oidc` job ——
646
+ 对 4 个真实子包名(`vscode-fs-copyfile` / `node-pty` / `kerberos-win32-arm64` / `vscode-server`)
647
+ 各做一次 **staged(暂存)发布**,版本形如 `2.0.1-oidc-probe.<run>`。
648
+ 为什么必须放 CI 里:OIDC 令牌只在运行时签发,信任关系按「仓库 + **workflow 文件名** + 包名」匹配 ⇒
649
+ 离线验证不了;也正因为后者,巡检只能由 `repacks.yml` 这个文件发起(换个文件名就会被 npm 拒)。
650
+ 为什么用 `npm stage publish` 而不是 `npm publish`:`npm stage` 走的是**同一条**认证路径,但版本进的是
651
+ 暂存队列,**不进** registry 的正式版本列表(脚本自己会用 `npm view <pkg> versions` 复核一遍),
652
+ 所以不会占用任何版本号、也不影响依赖解析。
653
+ 触发方式(两种,第二种不需要任何令牌):
654
+ ```powershell
655
+ # ① Actions → repacks → Run workflow,勾上 probe_oidc
656
+ # ② 提交哨兵文件后 push(工作流会跑一遍构建演练 + 巡检)
657
+ New-Item .github/oidc-probe.enabled -ItemType File -Force
658
+ git add .github/oidc-probe.enabled; git commit -m "chore: OIDC 通道巡检"; git push
659
+ ```
660
+ 结果怎么读:每个包成功/失败都会发 `::notice::` / `::error::` annotation,公开仓库**不需要登录**
661
+ 就能读(`GET /repos/jinsiyu/dsh-code-server-app/check-runs/<id>/annotations`);job 摘要里也有一张表。
662
+ 收尾:把暂存的探针版本**reject** 掉(`npm stage list` 看 id、`npm stage reject <id>`,需要你本机的 2FA;
663
+ npm 网页上也有对应的 staged 列表)——**不要 approve**,approve 才会让它变成正式版本。
664
+ 验证完删掉哨兵文件,工作流就恢复"不自动巡检"。
520
665
  - **可选**仓库 Variables `DSH_UI_VERSION` = 当前部署里 `@deepseek-ai/dsh-web-frontend` 的版本:设了之后
521
666
  `release.yml` 会强制面板渲染器版本与部署一致(本机 `build:webview` 本来就会比,runner 上没有 DSH 部署)。
522
667
 
@@ -558,8 +703,10 @@ dsh plugin --profile web add C:\Users\User\Desktop\dsh-code-server-app\dsh-code-
558
703
  平台无关的 8 个(`node-pty` / `koffi` / `ssh2` / `cpu-features` / `@parcel/watcher` /
559
704
  `@vscode/fs-copyfile` / `@vscode/proxy-agent` / `@microsoft/mxc-sdk`)写进插件 `dependencies`(真名);
560
705
  平台专属的 8 个(`@vscode/sqlite3` / `spdlog` / `kerberos` / `deviceid` / `native-watchdog` /
561
- `windows-registry` / `windows-process-tree` / `windows-ca-certs`)按 win32-arm64 与 win32-x64
562
- 各一份写进 `optionalDependencies`(真名 + 包自带 os/cpu)→ 一条命令自动选对架构;
706
+ `windows-registry` / `windows-process-tree` / `windows-ca-certs`)**按 `scripts/repack-platforms.json`
707
+ 里每模块的 `targets`** 写进 `optionalDependencies`(真名 + 包自带 os/cpu)→ 一条命令自动选对架构。
708
+ 其中 `windows-*` 三个是 Windows-only(只发 `win32-*`),其余五个目标里包含 `linux-x64` / `linux-arm64`
709
+ —— **哪个模块在哪些目标上有子包,只认这份声明**(上游不写 os/cpu,宿主扫描会漂移,详见「Linux 适配」一节);
563
710
  **原始名字**由 `lib/native.js` 运行时补 junction 还原(见下「运行时布局自愈」);
564
711
  - 因此依赖图里**没有任何带 pre/install/postinstall 或 binding.gyp 的包** →
565
712
  不需要 profile 的 `allowBuilds`、不执行任何构建、**使用者机器不需要 C++ 工具链**;
@@ -580,7 +727,9 @@ dsh plugin --profile web add C:\Users\User\Desktop\dsh-code-server-app\dsh-code-
580
727
  复制已编译的包目录 → **删除 `scripts` / `files` / `binding.gyp` / `.hooks` / `.npmignore`**
581
728
  (保留编译好的 `.node` 与全部运行时文件)→ 依赖里的同集包改成 `npm:` 别名 → 平台专属的加
582
729
  `os`/`cpu` 与 `-<platform>-<arch>` 后缀;win32 目标还会校验 `.node` 的 PE machine
583
- (0x8664=x64 / 0xaa64=arm64),防交叉编译产物装错架构;
730
+ (0x8664=x64 / 0xaa64=arm64)、Linux 目标校验 ELF `e_machine`(0x3e=x86-64 / 0xb7=AArch64),
731
+ 防交叉编译/串架构的产物被发出去;平台专属包在该目标上**没有编出 `.node` 就不产出**
732
+ (宁可在结论行里报出来,也不发一个装不起来的空壳);
584
733
  - **原始名字怎么还原**(0.3.45 起):重打包包的真名是 `@<scope>/dshcs-<名字>`,而 VS Code `import` 的是
585
734
  `node-pty` / `@vscode/sqlite3` 这类**原名**;打包期把「原名 → 真名」写进 `lib/vendored.json`(随插件发布),
586
735
  运行时由 `lib/native.js` 在 `<树>/node_modules/<原名>` 补 junction 指向真名包(幂等、可自愈)。
@@ -657,6 +806,25 @@ dsh plugin --profile web add C:\Users\User\Desktop\dsh-code-server-app
657
806
 
658
807
  ### Windows 原生构建要点(打包期,本机实测 ARM64)
659
808
 
809
+ - **MSVC 平台工具集必须齐**(MSB8020 `无法找到 v145 的生成工具`):VS Code 那批 `@vscode/*` 原生包在
810
+ binding.gyp 里写死了工具集版本,机器上的 VS 缺那一套就直接报 `npm error … MSB8020`,而**整条链以前
811
+ 会带着这个错跑完并"成功"**(见下「硬闸门」)。装法(需要管理员;ARM64 主机加 `VC.v145.ARM64`,
812
+ x64/x86 主机加 `VC.v145.x86.x64`):
813
+ ```pwsh
814
+ $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
815
+ $vc = & $vswhere -products * -latest -property installationPath
816
+ & "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vs_installer.exe" modify `
817
+ --installPath $vc --add Microsoft.VisualStudio.Component.VC.v145.ARM64 `
818
+ --add Microsoft.VisualStudio.Component.VC.v145.x86.x64 --quiet --norestart --nocache
819
+ ```
820
+ 先看看装了什么:`Get-ChildItem "$vc\VC\Tools\MSVC"`。**CI 上不需要这一步** —— runner 镜像里工具集是齐的
821
+ (`repacks.yml` 四条腿本来就编得出 `sqlite3 / spdlog / native-watchdog / …`);
822
+ - **硬闸门(0.3.48 起)**:`vendor-repacks.mjs` 收尾会核对「`scripts/repack-platforms.json` 里承诺的
823
+ 『模块×目标』是否都真的产出了子包」,缺一个就抛错退出。以前它是**静默退化**的:`npm rebuild` 的失败
824
+ 被容错吞掉 ⇒ 那些模块被判成"平台无关"、打成不带目标后缀的包,而依赖表里照旧写着「每目标一份」⇒
825
+ CI 绿着发出去一套装不起来的东西(2026-09-16 由"为什么日志里有 npm error 仍然通过"暴露)。
826
+ 现在原生包是**逐个 rebuild** 的(整树 rebuild 会在第一个失败处中断、级联坑掉后面的包),失败的包
827
+ 逐条打印;若本机确实编不出来,就用 CI 腿或修工具集,别让它带着缺口发出去;
660
828
  - **VS 需 Spectre 缓解库组件**(MSB8040):Visual Studio Installer → 单个组件 →
661
829
  "MSVC v14x Spectre-mitigated libs",**ARM64 与 x86/x64 要分别安装**(只装一个架构会缺另一个)。
662
830
  - **node-gyp 13.x**(旧版 9.x 不识别 VS 2026):`npm install -g node-gyp@latest`。
@@ -697,9 +865,19 @@ host 探测顺序:`@jinsiyu/dshcs-vscode-server/vscode`(**0.2.0+ 正式布局**)
697
865
  插件包内 `vendor/vscode` > 插件包内 `vendor/code-server`(开发期)。旧安装根
698
866
  `<profile>\.code-server-app` 只在启动日志里提示可删除,不再被使用。
699
867
 
700
- ## 设置卡片(设置 → 插件 → Code Server)
868
+ ## 设置卡片(0.3.50 起:插件页;更早:设置 → 插件 → Code Server)
869
+
870
+ **位置随 DSH 版本而变**(两条腿都注册,谁被声明谁生效,不会出现两份):
871
+
872
+ | DSH | 座位 | 长什么样 |
873
+ |---|---|---|
874
+ | **≥ 0.1.6-alpha.2** | `plugins.bundle.config`,**键 = 包名** `dsh-code-server-app` | 插件页 → 找到 `dsh-code-server-app` → 打开该插件页面,设置区在**描述与各行之间**(页面自己画标题/图标/面包屑,我们只出表单 + 保存控件) |
875
+ | ≤ 0.1.6-alpha.1 | `settings.plugin.item`(key `code-server`) | 设置 → 插件 → Code Server 的自绘可折叠卡片(该插槽在新版**已退役**) |
876
+
877
+ > 为什么必须跟着搬:上游 agent note《插件页上的插件配置》把配置从设置页搬到插件页,并**删掉了
878
+ > `settings.plugin.item`**;插件页只在 `ledger.bundles.has(包名)` 时才渲染那块区域 —— 键写错或还用旧座位,
879
+ > 表现是**设置卡静默消失**(无报错、无日志)。回归:`pnpm test:client-seat`(对构建产物验两个座位与两种视图)。
701
880
 
702
- 参照 dsh-auto-open-web 的自绘卡片模式,注册在 `settings.plugin.item` 插槽,
703
881
  数据经官方 settings 域(`settingsScope`,命名空间 `code-server`)持久化到官方 settings 文档:
704
882
 
705
883
  | 键 | 默认 | 说明 |
@@ -12,7 +12,7 @@
12
12
  | react | 18.3.1 | MIT |
13
13
  | react-dom | 18.3.1 | MIT |
14
14
 
15
- 生成时间:2026-09-16T15:37:21.355Z
15
+ 生成时间:2026-09-18T11:01:15.067Z
16
16
  渲染器:@deepseek-ai/dsh-client-ui-primitives@0.1.6-alpha.1(DSH 部署的界面版本:未知)
17
17
  设计令牌:@deepseek-ai/dsh-client-ui-theme@0.1.6-alpha.1(11 段)
18
18
  KaTeX:已打包(公式排版与 DSH 界面一致)