Merge pull request #6005 from deepseek-harness/worktree-tsprofile
perf(build): parallelize build, split build:artifacts/build:check, and cut tsdown and client check time
This commit is contained in:
commit
9cac4beb05
206 files changed
+2183
-835
No files matched your search
+2
-2
@@ -33,8 +33,8 @@
|
||||
en: a244f6a88fdabf3d
|
||||
zh: e4feedb7297f4865
|
||||
/agent-note-typert-gateway-targeted-method-calls/three-artifact-kinds-and-two-typescript-programs:
|
||||
en: 6ea4bddcf35a834d
|
||||
zh: 4af474c6818b1cf0
|
||||
en: a5134745815f762f
|
||||
zh: 99b6a905abd58e6a
|
||||
/agent-note-typert-gateway-targeted-method-calls/the-remote-package-entry:
|
||||
en: 75e977fe0fb71d59
|
||||
zh: 00760c79da173cb7
|
||||
|
||||
@@ -215,7 +215,7 @@ Host lib build
|
||||
→ Vite 构建 Web
|
||||
```
|
||||
|
||||
The existing top-level `build` still runs `build:lib` before `build:web`, but `build:lib` must complete the Host and Remote artifacts before starting Client TypeScript compilation. A clean build must not depend on stale `.d.ts` files from an earlier build.
|
||||
The top-level `build` runs the step graph in `scripts/build.ts`, which completes the Host bundle and the Remote artifacts before starting Client TypeScript compilation. `build:lib` remains the complete serial lib build for `build:bench`, and its Host half `build:lib:host` serves `typecheck` and the vendor release workflows. A clean build must not depend on stale `.d.ts` files from an earlier build.
|
||||
|
||||
Compiler-backed repository gates that resolve the consumer surface have the same prerequisite even when their primary inputs are source files. The public `typecheck`, `lint`, and `doc-typecheck` commands run the Host contract pass first. The gate scheduler may use their `*:contracts-ready` variants only after an explicit Typert-contract or complete-build dependency, so parallel lanes neither read missing declarations nor run concurrent generators against the same outputs.
|
||||
|
||||
|
||||
@@ -215,7 +215,7 @@ Host lib build
|
||||
→ Vite 构建 Web
|
||||
```
|
||||
|
||||
现有顶层 `build` 仍表现为先 `build:lib`、再 `build:web`,但 `build:lib` 内部必须先完成 Host 与 Remote artifact,再启动 Client TypeScript 编译。一次干净构建不能依赖上次残留的 `.d.ts`。
|
||||
顶层 `build` 运行 `scripts/build.ts` 中的步骤图,先完成 Host bundle 与 Remote artifact,再启动 Client TypeScript 编译。`build:lib` 仍是 `build:bench` 使用的完整串行 lib 构建,其 Host 部分 `build:lib:host` 供 `typecheck` 与 vendor 发布 workflow 使用。一次干净构建不能依赖上次残留的 `.d.ts`。
|
||||
|
||||
即使主要输入是源文件,需要通过编译器解析消费方 surface 的仓库门禁也有相同的前置条件。公共 `typecheck`、`lint` 和 `doc-typecheck` 命令会先执行 Host 约定 pass。门禁调度器仅可在显式的 Typert 约定依赖或完整构建依赖完成后使用对应的 `*:contracts-ready` 变体,使并行 lane 既不会读取缺失的声明,也不会针对同一输出并发运行多个生成器。
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
en: e5c984fa32cc5e69
|
||||
zh: 25d5742b2106d830
|
||||
/agent-note-pr-artifact-builds-and-aggregate-typescript-checks/decision:
|
||||
en: 6e84bceaba4573ca
|
||||
zh: 25a37c147a675643
|
||||
en: af93d8849bb8831a
|
||||
zh: 53e15e801ab844da
|
||||
/agent-note-pr-artifact-builds-and-aggregate-typescript-checks/alternatives-considered:
|
||||
en: f61da066f9ddcd62
|
||||
zh: 816fd6e0063017cd
|
||||
|
||||
@@ -12,13 +12,13 @@ PR jobs need the same compiled packages for benchmarks, compatibility, packaging
|
||||
|
||||
Required Linux consumer and native Windows build jobs retain normal builds, including both complete aggregate programs. Public `build` and `typecheck` keep the same checks. [Node version ownership](2026-07-06-node-engine-floor.md) and [native Windows ownership](2026-08-08-native-windows-pull-request-ci.md) remain in force.
|
||||
|
||||
Checked PR artifact builds compile every existing project reference from each compiler face with one serial `tsc -b` process, then run the ordinary Typert, bundling, Desktop, and Web stages. The helper reads the existing Host and Client aggregates rather than maintaining another package list. It omits only each root's additional test and script program; package diagnostics, emitted outputs, and Host-before-Client ordering remain required. Non-PR release and real-API workflow builds retain their normal checks.
|
||||
Checked PR artifact builds compile every existing project reference from each compiler face with one serial `tsc -b` process, then run the ordinary Typert, bundling, Desktop, and Web stages. The helper reads the existing Host and Client aggregates rather than maintaining another package list. It omits only each root's additional test and script program; package diagnostics, emitted outputs, and Host-before-Client ordering remain required. Non-PR release and real-API workflow builds retain their normal checks. That artifact build is `pnpm run build:artifacts`; [the build split](2026-10-11-build-artifacts-check-split.md) owns its steps and scheduling.
|
||||
|
||||
Only the CI benchmark helper selects `tsc -b --noCheck` for package emission. Benchmarks consume JavaScript and Typert runtime metadata; their declaration and build-information files are disposable and never published. Typert analyzes source types independently, while required complete builds own semantic diagnostics. Syntax and configuration failures still reject benchmark preparation. TypeScript records pending checks so a later ordinary incremental build diagnoses the same source without deleting caches or forcing a rebuild. Public benchmark scripts and every release, Python, preview, compatibility, and normal artifact builder keep their checked defaults.
|
||||
|
||||
The Python executable builder selects artifact compilation with `--artifacts-only` after verifying its runtime closure. Invalid closure inputs fail before compilation, deployment, or packing; the default complete build and prebuilt `--skip-build` mode keep their existing stages.
|
||||
The Python executable builder selects `build:artifacts` artifact compilation with `--build-artifacts` after verifying its runtime closure. Invalid closure inputs fail before compilation, deployment, or packing; the default complete build and prebuilt `--skip-build` mode keep their existing stages.
|
||||
|
||||
CI invokes the internal build helpers directly and keeps the root package manifest unchanged. Root manifest edits activate the separate native-addon platform matrix even when only build aliases change; avoiding that trigger preserves the existing workflow set and concurrent-job ceiling without weakening native validation.
|
||||
PR artifact builds in CI invoke the root manifest's `build:artifacts`; a PR that edits these root manifest scripts activates the separate native-addon platform matrix, a trade-off [the build split](2026-10-11-build-artifacts-check-split.md) records. The vendor rehearsal and benchmark lanes invoke the internal build helpers directly.
|
||||
|
||||
Job inventory, runner selectors, and benchmark sample counts stay fixed; gate and worker ceilings do not increase. The three compatibility cases run one job at a time; the two-target Python runtime matrix starts after the keyless Python SDK job succeeds. Four Linux lanes, the Python SDK, three Windows lanes, and one compatibility job permit at most nine worker jobs initially. Once the SDK job finishes, the remaining seven independent lanes, one compatibility job, and two runtime targets permit at most ten. The runtime plan and wheel stages precede those targets and each use one job. This permits runtime validation to overlap the Node 22 build within the original ten-job ceiling. Coverage partitions reuse compact file-cost metadata within their platform and runner pool. Parallel release packing uses pnpm's existing bounded scheduler; serial publication keeps family order.
|
||||
|
||||
|
||||
@@ -12,13 +12,13 @@ PR job 需要相同的编译包来运行 benchmark、兼容性、打包和运行
|
||||
|
||||
必需的 Linux consumer 与原生 Windows build job 保留普通构建,包括两个完整 aggregate program。公开的 `build` 和 `typecheck` 保留相同检查。[Node 版本职责](2026-07-06-node-engine-floor.zh.md)与[原生 Windows 职责](2026-08-08-native-windows-pull-request-ci.zh.md)仍然有效。
|
||||
|
||||
带检查的 PR 产物构建通过一个串行 `tsc -b` 进程编译各 compiler face 的全部现有 project reference,然后运行普通 Typert、bundling、Desktop 和 Web 阶段。helper 读取现有 Host 与 Client aggregate,而不维护另一份包清单。它只省略各 root 附加的测试和脚本 program;包诊断、emit 输出及 Host 先于 Client 的顺序仍然必需。非 PR 的 release 和真实 API 工作流构建保留普通检查。
|
||||
带检查的 PR 产物构建通过一个串行 `tsc -b` 进程编译各 compiler face 的全部现有 project reference,然后运行普通 Typert、bundling、Desktop 和 Web 阶段。helper 读取现有 Host 与 Client aggregate,而不维护另一份包清单。它只省略各 root 附加的测试和脚本 program;包诊断、emit 输出及 Host 先于 Client 的顺序仍然必需。非 PR 的 release 和真实 API 工作流构建保留普通检查。该产物构建即 `pnpm run build:artifacts`,其步骤与调度见[构建拆分](2026-10-11-build-artifacts-check-split.zh.md)。
|
||||
|
||||
只有 CI benchmark helper 选择 `tsc -b --noCheck` 来 emit 包。benchmark 消费 JavaScript 和 Typert 运行时元数据;其声明与 build-information 文件是临时产物,不会发布。Typert 独立分析源码类型,语义诊断由必需的完整构建负责。语法与配置错误仍会拒绝 benchmark 准备。TypeScript 记录待检查状态,使后续普通增量构建无需删除缓存或强制重建,就能诊断相同源码。公开 benchmark script,以及所有 release、Python、preview、compatibility 和普通产物 builder,均保留带检查的默认行为。
|
||||
|
||||
Python executable builder 在验证 runtime closure 后,通过 `--artifacts-only` 选择产物编译。无效 closure 在编译、部署或打包前失败;默认完整构建和复用预构建产物的 `--skip-build` 模式保留原有阶段。
|
||||
Python executable builder 在验证 runtime closure 后,通过 `--build-artifacts` 选择 `build:artifacts` 产物编译。无效 closure 在编译、部署或打包前失败;默认完整构建和复用预构建产物的 `--skip-build` 模式保留原有阶段。
|
||||
|
||||
CI 直接调用内部 build helper,并保持 root package manifest 不变。即使只修改 build alias,root manifest 改动也会激活独立的 native-addon 平台矩阵;避免这一触发条件,可以在不削弱 native 验证的情况下保留现有 workflow 集合与并发 job 上限。
|
||||
CI 的 PR 产物构建调用 root manifest 中的 `build:artifacts`;修改 root manifest 中这些 script 的 PR 会激活独立的 native-addon 平台矩阵,[构建拆分](2026-10-11-build-artifacts-check-split.zh.md)记录这一取舍。vendor 演练与 benchmark lane 直接调用内部 build helper。
|
||||
|
||||
job 清单、runner 选择和 benchmark sample 数量保持固定;gate 与 worker 上限不提高。三个 compatibility case 每次运行一个 job;双目标 Python runtime 矩阵在 keyless Python SDK job 成功后启动。四个 Linux lane、Python SDK、三个 Windows lane 和一个 compatibility job,初始最多占用九个 worker job。SDK job 完成后,剩余七个独立 lane、一个 compatibility job 和两个 runtime target,最多占用十个 job。runtime plan 与 wheel 阶段先于这些 target,各占一个 job。这允许 runtime 验证与 Node 22 构建重叠,同时保持原有十个 job 的上限。覆盖率分区在各自的平台和 runner pool 内复用精简的文件成本元数据。并行 release 打包使用 pnpm 现有的有界调度器;串行发布保持 family 顺序。
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# Bilingual-pair consistency record for 2026-10-11-build-artifacts-check-split.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-10-11-build-artifacts-check-split.md
|
||||
/agent-note-split-build-artifacts-and-build-check-with-build-running-both-concurrently:
|
||||
en: b1a98fdd8008dce1
|
||||
zh: 15bdc2f2c2584226
|
||||
/agent-note-split-build-artifacts-and-build-check-with-build-running-both-concurrently/problem:
|
||||
en: bc382ad1f1e71352
|
||||
zh: 44189bf0f69a0bf7
|
||||
/agent-note-split-build-artifacts-and-build-check-with-build-running-both-concurrently/decision:
|
||||
en: a15c7c13f167e5ac
|
||||
zh: 63bcc2ef60c2f344
|
||||
/agent-note-split-build-artifacts-and-build-check-with-build-running-both-concurrently/decision/commands:
|
||||
en: 174241330463841d
|
||||
zh: 7fe361c07e634053
|
||||
/agent-note-split-build-artifacts-and-build-check-with-build-running-both-concurrently/decision/steps-and-dependencies:
|
||||
en: 40f21a0941b9a23a
|
||||
zh: c9f1d702b804ecf2
|
||||
/agent-note-split-build-artifacts-and-build-check-with-build-running-both-concurrently/decision/scheduling-failure-and-output:
|
||||
en: 4b324f92307d3691
|
||||
zh: e0cf4411a747f7e5
|
||||
/agent-note-split-build-artifacts-and-build-check-with-build-running-both-concurrently/decision/callers:
|
||||
en: af86d6dbe8d3b17c
|
||||
zh: 8e5fa596d6709b35
|
||||
/agent-note-split-build-artifacts-and-build-check-with-build-running-both-concurrently/decision/measurements:
|
||||
en: a5d74335200d1476
|
||||
zh: e817deb711f98504
|
||||
/agent-note-split-build-artifacts-and-build-check-with-build-running-both-concurrently/decision/completeness-and-lossless-evidence:
|
||||
en: 7308ab88d2b0926f
|
||||
zh: 773ce413da90bcbc
|
||||
/agent-note-split-build-artifacts-and-build-check-with-build-running-both-concurrently/decision/intentional-trade-offs-not-lossless:
|
||||
en: f3a5621dbfcd602d
|
||||
zh: f5beabfa81272ffb
|
||||
/agent-note-split-build-artifacts-and-build-check-with-build-running-both-concurrently/alternatives-considered:
|
||||
en: 54905b912d17c74e
|
||||
zh: 86a298004c8024e3
|
||||
/agent-note-split-build-artifacts-and-build-check-with-build-running-both-concurrently/consequences:
|
||||
en: ce9135e424811303
|
||||
zh: 1737f084156211c3
|
||||
@@ -0,0 +1,157 @@
|
||||
# Agent Note: Split build:artifacts and build:check, with build running both concurrently
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-10-11-build-artifacts-check-split.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
`pnpm run build` runs the native addon, the package-library build (Host `tsc -b tsconfig.host.json` → Host tsdown → Desktop bundle → Client `tsc -b tsconfig.client.json` → Client tsdown), and the Web build serially. Besides referencing package projects, the Host and Client aggregates each carry a `noEmit` root program covering tests, scripts, benchmarks, and website. These two root programs emit no files and feed no later artifact step, yet sit on the serial critical path: timed alone, the Host root program takes 68.5 s and the Client root program 36.0 s, while the serial package-library build takes about 249 s (median of 2).
|
||||
|
||||
Development iterations need only artifacts; test and script type checks are needed before work is declared complete. The only way to skip the two root programs is the CI-oriented `pnpm run build --artifacts-only`, which has no local development command name and no counterpart that runs only those two checks. Meanwhile, `pnpm run build` must stay complete and detect every type error.
|
||||
|
||||
## Decision
|
||||
|
||||
### Commands
|
||||
|
||||
Each command does only its own job.
|
||||
|
||||
| Command | Responsibility | Steps | Scheduling | Client build record |
|
||||
|---|---|---|---|---|
|
||||
| `pnpm run build:artifacts` | Produces every artifact of `build`; package projects are fully type-checked as usual, the two aggregate root programs are not checked | native → host-src → host-bundle → desktop-bundle → client-src → client-bundle → web | One linear chain | Deleted at start, written after every step passes |
|
||||
| `pnpm run build:check` | Type-checks only the two aggregate root programs | host-check, client-check | Both steps concurrent, no prerequisite steps | Neither read nor written |
|
||||
| `pnpm run build` | Every step of `build:artifacts` and `build:check` | The dependency graph below | Concurrent through the run-gates scheduler | Deleted at start, written after every step passes |
|
||||
|
||||
`build:check` neither detects, rebuilds, nor validates `build:artifacts` outputs; when prerequisite outputs are missing or stale, the compiler's own diagnostics (for example TS6305 and TS2307) report it, and no command adds prerequisite checks, guards, or fallbacks. `build:artifacts` produces the same artifacts and record as `build` and replaces `--artifacts-only`; that flag and the `libraries` mode of `compile-referenced-projects.ts` are removed, and the Python executable builder uses `--build-artifacts`. `build:official` remains `tsx scripts/build.ts --profile official` and runs the complete `build`.
|
||||
|
||||
### Steps and dependencies
|
||||
|
||||
| Step | Command | `needs` in `build` |
|
||||
|---|---|---|
|
||||
| native | `pnpm run build:native-system` | — |
|
||||
| host-src | `node --max-old-space-size=4096 <tsc> -b <references of tsconfig.host.json>` | native |
|
||||
| host-bundle | `tsdown --config-loader native --env.DSH_BUILD_FACE host` (runs Typert) | host-src |
|
||||
| desktop-bundle | `pnpm --filter @deepseek-ai/dsh-desktop run bundle` | host-bundle |
|
||||
| client-src | `<tsc> -b <references of tsconfig.client.json>` | desktop-bundle |
|
||||
| client-bundle | `tsdown --config-loader native --env.DSH_BUILD_FACE client` | client-src |
|
||||
| web | `pnpm run build:web` | client-bundle |
|
||||
| host-check | `node --max-old-space-size=4096 <tsc> -p tsconfig.host.json` | host-src |
|
||||
| client-check | `<tsc> -p tsconfig.client.json` | client-src |
|
||||
|
||||
```text
|
||||
native → host-src ─┬─ host-check ───────────────────────────────────────────────────────┐
|
||||
└─ host-bundle → desktop-bundle → client-src ─┬─ client-check ───────┤ → record
|
||||
└─ client-bundle → web ┘
|
||||
```
|
||||
|
||||
- client-src must follow host-bundle: Client packages import `@deepseek-ai/dsh-*/remote`, whose types `lib/typert.remote-client.d.ts` only Typert inside Host tsdown writes. host-check reads no Typert output; clean serial builds have always finished the Host aggregate check before Host tsdown.
|
||||
- The artifact chain keeps the two non-data edges native → host-src and desktop-bundle → client-src; only the two check steps attach to the chain in parallel.
|
||||
- [`compile-referenced-projects.ts`](../../../../scripts/compile-referenced-projects.ts) owns the package-library step list `libraryBuildSteps`, the references argument construction `referencedProjectsCompilerArgs` (relative paths keep the command below Windows' command-line limit), and the aggregate-check arguments `aggregateCheckArgs`. The vendor release rehearsal (`host-libraries`) and CI benchmarks (`--noCheck`) run the same step list serially.
|
||||
- The check steps use project mode (`tsc -p`): it reads the declarations that references already emitted and compiles no reference, writing incremental state to the aggregates' existing `tsconfig.{host,client}.tsbuildinfo`. `build` and `build:check` run the same command, so their diagnostics are identical.
|
||||
|
||||
### Scheduling, failure, and output
|
||||
|
||||
- [`scripts/build.ts`](../../../../scripts/build.ts) owns the step table and the three scopes and executes them through `runAggregate` from [`scripts/run-gates.ts`](../../../../scripts/run-gates.ts): each step is a gate with `needs`, and shell-free spawning, per-step buffered output, fail-fast process-tree termination, Windows `taskkill`, and host signal forwarding come from the run-gates implementation. `build:artifacts` is `tsx scripts/build.ts --scope artifacts`, `build:check` is `--scope check`, and `build` omits `--scope` and runs both as one graph; `--profile` applies only to artifact scopes, and combining it with `--scope check` fails argument parsing.
|
||||
- The concurrency limit is `DSH_GATE_CONCURRENCY`, defaulting to the available CPU count; the dependency graph is at most 3 wide (client-check and client-bundle can start while host-check is still running). Master serial reference lanes set `DSH_GATE_CONCURRENCY=1`, so `build` runs serially in topological order there, consistent with the [serial reference decision](2026-07-21-serial-cross-platform-ci-reference.md) and without workflow edits.
|
||||
- By default (locally), every dependent of a failed step is recorded as skipped, while running steps and steps unrelated to the failure run to completion; the executed set depends only on step outcomes, so one run reports every independent type error. With `DSH_GATE_FAIL_FAST=1` (set in every building PR lane), the first failure terminates the process trees of the other running steps and records every unfinished step as skipped.
|
||||
- A failed step prints `== FAILED <step> ==`, its command, the exit reason, and the step's complete buffered output, and the final summary lists failed and skipped steps; a passing step prints only its PASS line, and `DSH_GATE_VERBOSE=1` prints all output. The exit code is 1 when any step failed or was skipped, and the client build record is not written. Every step inherits the public Client environment selected by `clientBuildProcessEnvironment`.
|
||||
- In CI, `build` itself is one gate of an outer run-gates aggregate; outer fail-fast termination propagates to the inner scheduler. `check:node-compat` and `check:ci:lint:contracts-ready` already nest the same way.
|
||||
|
||||
### Callers
|
||||
|
||||
| Caller | Command | Aggregate root-program checks |
|
||||
|---|---|---|
|
||||
| `build`, `build:official`, `Makefile` `build`, `scripts/publish-npm-baseline.ts`, `apps/desktop/scripts/package-target.ts`, `scripts/dev-bundle-registry.ts`, `release-publish.yml`, `sandbox.yml`, and the non-PR branches of `e2e.yml` and `release.yml` | `build` / `build:official` | Yes |
|
||||
| `test:web`, `test:web:refresh`, `test:web:perf`, `test:web:stress`, `apps/desktop` `test:updates:local`, `test:bench` / `build:bench` | `build`; `build:bench` runs `build:native-system`, `build:lib`, and the benchmark tsdown | Yes |
|
||||
| `typecheck` (run by the pre-push hook) | `build:lib:host` (Host `tsc -b tsconfig.host.json`, Host bundle, Desktop bundle) and Client `tsc -b tsconfig.client.json` | Yes |
|
||||
| `dev:web` ([`scripts/dev-web.ts`](../../../../scripts/dev-web.ts)), `dev:desktop` (`apps/desktop/scripts/dev.ts`) | `build:artifacts` | None before launch; after `dev:web` launches, its `tsc -b tsconfig.client.json --watch` keeps checking the Client aggregate root program |
|
||||
| Prerequisite step of `lint`, `lint:fix`, `doc-typecheck`, and the CI Typert contracts gate | Host package compilation and the Host bundle (`compile-referenced-projects.ts host-libraries`), without the Desktop bundle | None |
|
||||
| CI node 24 / snapshots and artifacts (`check:ci:consumers`), windows node 24 / build & observational (`check:ci:windows-blocking`) | `build` | Yes, the two required lanes |
|
||||
| CI node-compat 22.19 | `build:artifacts` (through run-gates) | None |
|
||||
| CI python runtime | `build-exe-for-python-sdk.ts --build-artifacts` | None |
|
||||
| PR rehearsals in `e2e.yml` and `release.yml` | `pnpm run build:artifacts --profile official` | None |
|
||||
| `build-preview-cloudflare.yml` | `pnpm run build:artifacts` | None |
|
||||
| CI node 24 / benchmarks | `run-ci-bench.ts` (`tsc -b --noCheck`) | None |
|
||||
| PR rehearsal in `release-vendor.yml` | `compile-referenced-projects.ts host-libraries` | None |
|
||||
| Non-PR branch of `release-vendor.yml`, `release-vendor-publish.yml` | `build:lib:host` | Host aggregate root program only |
|
||||
|
||||
`lint`, `lint:fix`, `doc-typecheck`, and the Typert contracts gate read only Typert outputs and package declarations: both aggregates are `noEmit`, oxlint builds its own programs, `doc-typecheck` references only package projects, and no consumer of `host-libraries` reads `apps/desktop/lib`. Their prerequisite step therefore skips the Host aggregate check and the Desktop bundle and falls from 159.8 s to 90.9 s (see the measurements below).
|
||||
|
||||
The consumers lane's HMR Web test starts `dev:web` and therefore uses `build:artifacts` inside that lane; the same lane's `build` gate still runs both aggregate checks. CI does not call `build:check` separately: the two required lanes already get the same checks through `build`. The [PR artifact build decision](2026-10-05-pr-artifact-typechecks.md) keeps its required complete builds, benchmark `--noCheck`, job and worker ceilings, serial node-compat Vitest, coverage weights, and pnpm packing checks; its PR artifact build is `build:artifacts`.
|
||||
|
||||
### Measurements
|
||||
|
||||
Prototype conditions: a shared 256 vCPU Linux host (load average 23–70), Node v24.18.0, TypeScript 6.0.3, React type packages hoisted to the root, without the Typert changes of [the lossless speed-ups note](2026-10-11-lossless-build-speedups.md); scope is the package-library build (excluding native and web). Every round starts from `pnpm run clean`, running once each in the order serial, concurrent, `build:lib`, `build:lib`, concurrent, serial; `/usr/bin/time -v` times each step, and summed process-tree RSS is sampled every 250 ms. The prototype runs the aggregate checks as `tsc -b tsconfig.{host,client}.json` once references are up to date, when build mode builds only the aggregate root program.
|
||||
|
||||
| Mode (medians of 2) | Wall (s) | CPU user+sys (s) | Process-tree RSS peak (GiB) |
|
||||
|---|---|---|---|
|
||||
| Serial `pnpm run build:lib` | 249.4 | 378.0 | 5.95 |
|
||||
| Each step run separately in sequence | 255.1 | 373.8 | 5.82 |
|
||||
| Concurrent by dependency graph | 172.1 | 392.1 | 8.00 |
|
||||
|
||||
Median wall falls from 249.4 s to 172.1 s (−77.3 s, −31%). The two runs of each step separately differ by 28 s, so the overhead of running the steps as separate processes is not resolvable. CPU grows by about 14 s (+3.7%), mostly Host aggregate check user time under contention; the RSS peak grows by about 2.1 GiB where the Host aggregate check (about 4 GiB) overlaps Host tsdown (about 5.7 GiB). The concurrent critical path is host-src → host-bundle → desktop-bundle → client-src → client-check; host-check finishes about 33 s before the end and client-bundle about 21 s before.
|
||||
|
||||
Final measurements: master (the merge base) and this branch run interleaved, each from clean, in a 64-core cgroup; values are medians of 2 samples unless marked n=1.
|
||||
|
||||
| Scenario | master | This branch | CPU user+sys (master → branch) | Process-tree RSS peak (master → branch) |
|
||||
|---|---|---|---|---|
|
||||
| Complete build `pnpm run build` | 317.1 s | 169.1 s (−46.7%) | 423.1 → 374.4 s (−11.5%) | 6.11 → 7.53 GiB (+1.42) |
|
||||
| Serial branch build `DSH_GATE_CONCURRENCY=1 pnpm run build` (n=1) | — | 266.7 s | 389.5 s | 6.27 GiB |
|
||||
| Artifact build: master `build --artifacts-only`, branch `build:artifacts` | 185.8 s | 153.6 s (−17.3%) | 256.2 → 231.2 s | 6.58 → 6.18 GiB |
|
||||
| Development iteration: master `build`, branch `build:artifacts` | 317.1 s | 153.6 s (−51.6%) | 423.1 → 231.2 s (−45.4%) | 6.11 → 6.18 GiB |
|
||||
| `pnpm run build:check` after `build:artifacts` | — | 67.7 s | 144.6 s | 5.33 GiB |
|
||||
| `pnpm run build:check` no-op rerun | — | 19.4 s | 33.1 s | Not measured |
|
||||
| Prerequisite of `lint` and `doc-typecheck`: master `build:lib:host`, branch `host-libraries` | 159.8 s | 90.9 s (−43.2%) | 234.7 → 136.0 s | Not measured |
|
||||
|
||||
The serial branch sample overlapped other work on the host (742 s of foreign CPU), so its wall likely overstates the serial cost. In the serial build segments (master → branch, one sample each), Host tsdown falls from 22.5 to 11.7 s, Client package compilation from 55.6 to 48.7 s, the Client aggregate check from 68.2 to 35.9 s, and Client tsdown from 13.9 to 10.5 s; the segments sum to 308.5 s and 254.9 s. Those reductions come from [the lossless speed-ups note](2026-10-11-lossless-build-speedups.md); the further reduction from 254.9 s of serial segments to the 169.1 s concurrent build comes from this note's scheduling, which takes the Host aggregate check (about 66 s) off the critical path. In the concurrent build, the critical path is native → host-src → host-bundle → desktop-bundle → client-src → client-check, and host-check ends about 30 s before the last step (one earlier sample). `build:check` runs the Host and Client aggregate checks concurrently, so its wall is the Host check's (about 65 s).
|
||||
|
||||
### Completeness and lossless evidence
|
||||
|
||||
`build` runs the same set of compiler programs as the serial build: host-src and client-src compile every package project of both faces, and host-check and client-check check both aggregate root programs with the same commands as `build:check`. Across the prototype's six rounds, the 11,580 files of `{packages,apps,vendor,native}/**/lib/**` (excluding `*.tsbuildinfo`) are byte-identical between serial and concurrent runs, and after every round `tsc -b --dry tsconfig.host.json tsconfig.client.json` reports no out-of-date project. tsbuildinfo writers are host-src (292), client-src (76, exactly the client-only projects), and one per aggregate check; client-src rewrites no tsbuildinfo or declaration that host-check reads, so the graph has no write race. The fixture tests `checks only the aggregate program in project mode` and `reports missing package declarations without compiling references` show that `tsc -b --dry` treats the tsbuildinfo written by `tsc -p` as up to date and that project mode compiles no reference. In this implementation, the concurrent `build` and `DSH_GATE_CONCURRENCY=1 pnpm run build` produce byte-identical `lib/` (11,584 files), `apps/web/dist` (156 files), and client build record; afterwards `tsc -b --dry` reports all 372 projects of both aggregates up to date, with none to build.
|
||||
|
||||
| Error | `build:artifacts` | `build:check` | `build` | `typecheck` | Required CI lanes |
|
||||
|---|---|---|---|---|---|
|
||||
| Package source type error | host-src / client-src | Not reported (reads existing declarations) | host-src / client-src | Reported | Reported |
|
||||
| Type error only in an aggregate root program's tests, scripts, benchmarks, or website | Not reported | host-check / client-check | host-check / client-check | Reported | consumers, windows-build |
|
||||
| Package tsconfig configuration error | host-src / client-src | Not reported (`tsc -p` does not validate referenced projects' tsconfig options or syntax) | host-src / client-src | Reported | Reported |
|
||||
| Typert analysis error | host-bundle | Not reported | host-bundle | Reported (through the Host bundle) | Reported |
|
||||
| Missing prerequisite output | — | Compiler diagnostic (TS6305, TS2307) | — | — | — |
|
||||
|
||||
Negative controls: after a type error (TS2322) is added to `scripts/pnpm-invocation.spec.ts`, which only the Host aggregate root program contains, `build:artifacts` exits 0; `build:check` exits 1 with the Host aggregate check failing and the Client aggregate check passing; `build` exits 1 with 8 of 9 steps passing and 1 failing, and writes no client build record. In a scratch fixture whose referenced project's tsconfig has an invalid `target`, a JSON syntax error, or an unknown compiler option, `tsc -p` exits 0, while `tsc -b` reports TS6046, TS5092 with TS1012, and TS5023 respectively and exits 1; when the referenced project lacks `composite`, both report TS6306.
|
||||
|
||||
CI evidence: on PR #6005 both required lanes that run the concurrent `pnpm run build` passed, `node 24 / snapshots and artifacts` (`check:ci:consumers` on `blacksmith-16vcpu-ubuntu-2404`) and `windows node 24 / build & observational` (`check:ci:windows-blocking` on `blacksmith-16vcpu-windows-2025`); the GitHub-hosted 4 vCPU benchmarks lane does not run `build`. The inner concurrency follows `DSH_GATE_CONCURRENCY`: master serial lanes set 1, the consumers lane sets 10 under self-hosted failover, and an unset value selects the available CPU count, capped at the 9 steps.
|
||||
|
||||
### Intentional trade-offs (not lossless)
|
||||
|
||||
- **Some commands skip the aggregate checks:** `build:artifacts`, the launch builds of `dev:web` and `dev:desktop`, and the prerequisite steps of `lint`, `lint:fix`, `doc-typecheck`, and the Typert contracts gate do not type-check the two aggregate root programs, and these prerequisite steps do not produce `apps/desktop/lib`. `build`, `build:check`, `typecheck` (pre-push hook), and the two required CI lanes own completeness; `dev:web` and `dev:desktop` do not refuse to launch because of test type errors.
|
||||
- **`build:check` does not check freshness:** When sources change after `build:artifacts`, `build:check` checks against stale declarations, including Typert Remote declarations; rerun `build:artifacts` first. The complete `build` is unaffected.
|
||||
- **Concurrent `build` raises the memory peak:** This branch's complete `build` peaks at 7.53 GiB of process-tree RSS against 6.11 GiB for the master serial build (+1.42 GiB, medians of 2) and 6.27 GiB for the branch's serial build (n=1); CPU meanwhile falls from 423.1 s to 374.4 s because [the lossless speed-ups note](2026-10-11-lossless-build-speedups.md) removes more work than concurrent contention adds. In the prototype (without the Typert changes), concurrent scheduling alone raises the RSS peak by about 2.1 GiB (5.9 → 8.0 GiB) and CPU by about 3.7%. `DSH_GATE_CONCURRENCY` bounds it; under memory pressure use `DSH_GATE_CONCURRENCY=1 pnpm run build`, or run `build:artifacts` and `build:check` separately. In the windows-build lane, `build` runs concurrently with `docs:build`.
|
||||
- **Passing steps' output is hidden by default:** Only failed steps print their complete output.
|
||||
- **`tsc -p` does not skip an up-to-date root program:** An immediate rerun of `build:check` without changes still takes 19.4 s and 33.1 s of CPU because both programs are parsed again; `tsc -b` skips an up-to-date root.
|
||||
- **On many-core cold builds `build:artifacts` is only 15.5 s faster than `build`:** This branch's cold `build` takes 169.1 s and `build:artifacts` 153.6 s (medians of 2), because host-check is off the critical path and client-check is only about 16 s longer than client-bundle plus web. The rest of `build:artifacts`'s saving is CPU and memory: 374.4 → 231.2 s of CPU and 7.53 → 6.18 GiB of peak RSS; on few-core machines and in incremental rebuilds this also turns into wall time.
|
||||
- **CI invokes public commands in the root manifest:** PR artifact builds invoke `build:artifacts` rather than an internal helper flag; a PR that edits these root manifest scripts activates the separate Node Addon System platform matrix.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- **Keep the aggregate checks only in `typecheck`:** `pnpm run build` must stay complete and detect every error.
|
||||
- **Keep `--artifacts-only` and add a `build:artifacts` alias:** One operation would have two names.
|
||||
- **Name the commands `build:src` and `build:test`:** `build:artifacts` produces every artifact, including the tsdown bundles, the Desktop bundle, and Web, not only compiled sources; `build:check` checks scripts, benchmarks, and website code as well as tests.
|
||||
- **Name the commands after compiler terms (`build:b`/`build:p`, `build:project`, `build:emit`) or `build:dist`:** tsc runs in only two artifact steps, and `-p` is an implementation choice for the check steps; TypeScript calls the `tsc -p` side the project; `emit` reads as the emit-only `--noCheck` mode although package projects stay fully checked; package outputs live under `lib/`, and `dist` suggests release packaging.
|
||||
- **`build:check` runs `tsc -b <aggregate>`:** Build mode compiles out-of-date references, doing `build:artifacts`'s work.
|
||||
- **`build:check` first completes or validates prerequisite outputs:** It violates each command doing only its own job and adds prerequisite logic; compiler diagnostics already name missing outputs.
|
||||
- **A new scheduler (shell `&`/`wait`, `concurrently`, turbo/nx):** It duplicates run-gates' failure summary, process-tree termination, and Windows handling.
|
||||
- **Expand `build` into several gates in CI's run-gates:** `pnpm run build` must be a complete command usable directly locally and by release workflows, and it writes the record.
|
||||
- **Relax the non-data edges in the artifact chain (native ∥ host-src, desktop-bundle ∥ client-src):** Not measured; the Desktop bundle itself takes only 4.0–4.5 s.
|
||||
- **Move tests into a new tsconfig:** The existing aggregates already separate package references from the root program.
|
||||
- **Split the aggregate root programs into sub-programs:** Each sub-program must inject the 242 files carrying module augmentations, CPU grows by 9–57%, and only wall falls (see [the lossless speed-ups note](2026-10-11-lossless-build-speedups.md)).
|
||||
- **Run `build:check` separately in CI:** The required lanes already get the same checks through `build`; a separate run repeats the work.
|
||||
|
||||
## Consequences
|
||||
|
||||
Development iterations can skip the two aggregate root programs with `build:artifacts` (51.6% faster cold than master's `build`) and complete the checks with `build:check` or `build` before declaring work complete. `build` stays complete: the Host aggregate check is off the critical path, the Client aggregate check runs alongside the Client bundle and web, and a cold build is 46.7% faster than master (including the lossless speed-ups note's changes; medians of 2). The costs are a higher memory peak, a little extra CPU from concurrent contention, less passing output by default, and several development and prerequisite commands that do not check test types (see the trade-offs above).
|
||||
|
||||
Removing or changing `build` in the two required lanes, consumers and windows-build, requires first transferring both aggregate checks to another blocking owner. If Typert later gains its own generation step, the step table adds it and client-src depends on it; command semantics and scheduling stay the same.
|
||||
|
||||
client-src's dependency on host-bundle guarantees the "Host and Remote artifacts complete before Client compilation" ordering that [Typert Remote method calls](../architecture/2026-08-02-typert-remote-method-calls.md) describes.
|
||||
|
||||
Verification: `scripts/build.spec.ts` covers the type-stripping check, rejection of the removed flag and invalid scopes, the conflict between `--profile` and `--scope check`, scope selection, step dependencies, and the environment; `compile-referenced-projects.spec.ts` covers project mode checking only the aggregate program, the diagnostic for missing declarations, and `tsc -b --dry` accepting the tsbuildinfo that `tsc -p` writes; failure propagation, output buffering, and process-tree termination come from the run-gates scheduler and its tests; `run-gates.spec.ts`, `ci-workflow.spec.ts`, `preview-workflow.spec.ts`, `build-exe-for-python-sdk.spec.ts`, `dev-web.spec.ts`, and `tests/ci-release-selfhosted.spec.ts` pin each caller's command.
|
||||
@@ -0,0 +1,157 @@
|
||||
# Agent Note: 拆分 build:artifacts 与 build:check,build 并发执行两者
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-10-11-build-artifacts-check-split.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
`pnpm run build` 串行执行 native addon、包库构建(Host `tsc -b tsconfig.host.json` → Host tsdown → Desktop bundle → Client `tsc -b tsconfig.client.json` → Client tsdown)和 Web 构建。Host 与 Client 聚合配置除了引用各包 project,还各自带有一个 `noEmit` 根程序,覆盖 tests、scripts、benchmarks 与 website。这两个根程序不产出任何文件,也不是后续产物步骤的输入,却位于串行关键路径上:单独计时时 Host 根程序为 68.5 s、Client 根程序为 36.0 s,串行包库构建约为 249 s(2 个样本的中位数)。
|
||||
|
||||
开发迭代只需要产物,测试与脚本的类型检查在声明完成前才需要。能跳过两个根程序的只有面向 CI 的 `pnpm run build --artifacts-only`;它没有本地开发命令名,也没有只做这两项检查的对应命令。同时,`pnpm run build` 必须保持完整,能检测出所有类型错误。
|
||||
|
||||
## 决策
|
||||
|
||||
### 命令
|
||||
|
||||
每个命令只做自己的工作。
|
||||
|
||||
| 命令 | 职责 | 步骤 | 调度 | client build record |
|
||||
|---|---|---|---|---|
|
||||
| `pnpm run build:artifacts` | 产出 `build` 的全部产物;包 project 照常完整类型检查,不检查两个聚合根程序 | native → host-src → host-bundle → desktop-bundle → client-src → client-bundle → web | 一条线性链 | 开始时删除,全部通过后写入 |
|
||||
| `pnpm run build:check` | 只类型检查两个聚合根程序 | host-check、client-check | 两步并发,无前置步骤 | 不读写 |
|
||||
| `pnpm run build` | `build:artifacts` 与 `build:check` 的全部步骤 | 下文依赖图 | run-gates 调度器并发执行 | 开始时删除,全部通过后写入 |
|
||||
|
||||
`build:check` 不检测、不重建、也不校验 `build:artifacts` 的产物;前置产物缺失或过期时,由编译器自身的诊断(例如 TS6305、TS2307)报告,任何命令都不增加前置检查、守卫或回退逻辑。`build:artifacts` 产出的产物与 record 和 `build` 相同,并取代 `--artifacts-only`;该参数与 `compile-referenced-projects.ts` 的 `libraries` 模式已删除,Python executable builder 使用 `--build-artifacts`。`build:official` 仍是 `tsx scripts/build.ts --profile official`,执行完整的 `build`。
|
||||
|
||||
### 步骤与依赖
|
||||
|
||||
| 步骤 | 命令 | `build` 中的 `needs` |
|
||||
|---|---|---|
|
||||
| native | `pnpm run build:native-system` | — |
|
||||
| host-src | `node --max-old-space-size=4096 <tsc> -b <tsconfig.host.json 的 references>` | native |
|
||||
| host-bundle | `tsdown --config-loader native --env.DSH_BUILD_FACE host`(运行 Typert) | host-src |
|
||||
| desktop-bundle | `pnpm --filter @deepseek-ai/dsh-desktop run bundle` | host-bundle |
|
||||
| client-src | `<tsc> -b <tsconfig.client.json 的 references>` | desktop-bundle |
|
||||
| client-bundle | `tsdown --config-loader native --env.DSH_BUILD_FACE client` | client-src |
|
||||
| web | `pnpm run build:web` | client-bundle |
|
||||
| host-check | `node --max-old-space-size=4096 <tsc> -p tsconfig.host.json` | host-src |
|
||||
| client-check | `<tsc> -p tsconfig.client.json` | client-src |
|
||||
|
||||
```text
|
||||
native → host-src ─┬─ host-check ───────────────────────────────────────────────────────┐
|
||||
└─ host-bundle → desktop-bundle → client-src ─┬─ client-check ───────┤ → record
|
||||
└─ client-bundle → web ┘
|
||||
```
|
||||
|
||||
- client-src 必须在 host-bundle 之后:Client 包导入 `@deepseek-ai/dsh-*/remote`,其类型 `lib/typert.remote-client.d.ts` 只由 Host tsdown 中的 Typert 写出。host-check 不读取 Typert 输出,干净的串行构建一直在 Host tsdown 之前完成 Host 聚合检查。
|
||||
- 产物链保留 native → host-src 与 desktop-bundle → client-src 这两条非数据依赖;只有两个 check 步骤并行挂接到链上。
|
||||
- [`compile-referenced-projects.ts`](../../../../scripts/compile-referenced-projects.ts) 拥有包库步骤列表 `libraryBuildSteps`、references 参数构造 `referencedProjectsCompilerArgs`(相对路径传参,避开 Windows 命令行长度上限)与聚合检查参数 `aggregateCheckArgs`。vendor 发布演练(`host-libraries`)与 CI benchmark(`--noCheck`)串行执行同一步骤列表。
|
||||
- check 步骤使用 project mode(`tsc -p`):它读取 references 已有的声明而不编译任何 reference,incremental 状态写入聚合配置已有的 `tsconfig.{host,client}.tsbuildinfo`。`build` 与 `build:check` 使用同一条命令,因此诊断相同。
|
||||
|
||||
### 调度、失败与输出
|
||||
|
||||
- [`scripts/build.ts`](../../../../scripts/build.ts) 拥有步骤表与三个范围,执行复用 [`scripts/run-gates.ts`](../../../../scripts/run-gates.ts) 的 `runAggregate`:每个步骤是一个带 `needs` 的 gate,shell-free 启动、按步骤缓冲输出、fail-fast 进程树终止、Windows `taskkill` 与宿主信号转发都沿用 run-gates 的实现。`build:artifacts` 为 `tsx scripts/build.ts --scope artifacts`,`build:check` 为 `--scope check`,`build` 不带 `--scope`,把两者作为一张图执行;`--profile` 只适用于产生产物的范围,与 `--scope check` 同用时参数解析失败。
|
||||
- 并发上限取 `DSH_GATE_CONCURRENCY`,未设置时为可用 CPU 数;依赖图宽度最多为 3(host-check 尚未结束时,client-check 与 client-bundle 可能已开始)。master 串行参考 lane 设置 `DSH_GATE_CONCURRENCY=1`,`build` 在那里按拓扑顺序串行执行,符合[串行参考决策](2026-07-21-serial-cross-platform-ci-reference.zh.md),无需修改 workflow。
|
||||
- 缺省(本地)时,失败步骤的所有依赖方记为 skipped,正在运行的步骤和与失败无关的步骤继续运行到结束;执行集合只取决于各步骤的结果,因此一次运行报告所有相互独立的类型错误。`DSH_GATE_FAIL_FAST=1`(所有构建型 PR lane 已设置)时,第一个失败终止其余正在运行步骤的进程树,所有未完成步骤记为 skipped。
|
||||
- 失败步骤打印 `== FAILED <步骤> ==`、命令、退出原因与该步骤完整的缓冲输出,结尾汇总列出 failed 与 skipped 步骤;成功步骤只打印 PASS 行,`DSH_GATE_VERBOSE=1` 打印全部输出。任一步骤 failed 或 skipped 时退出码为 1,且不写 client build record。所有步骤继承 `clientBuildProcessEnvironment` 选定的公开 Client 环境。
|
||||
- CI 中 `build` 本身是外层 run-gates 的一个 gate;外层 fail-fast 终止会转发到内层调度。`check:node-compat` 与 `check:ci:lint:contracts-ready` 已使用同样的嵌套。
|
||||
|
||||
### 调用方
|
||||
|
||||
| 调用方 | 命令 | 聚合根程序检查 |
|
||||
|---|---|---|
|
||||
| `build`、`build:official`、`Makefile` `build`、`scripts/publish-npm-baseline.ts`、`apps/desktop/scripts/package-target.ts`、`scripts/dev-bundle-registry.ts`、`release-publish.yml`、`sandbox.yml`、`e2e.yml` 与 `release.yml` 的非 PR 分支 | `build` / `build:official` | 有 |
|
||||
| `test:web`、`test:web:refresh`、`test:web:perf`、`test:web:stress`、`apps/desktop` `test:updates:local`、`test:bench` / `build:bench` | `build`;`build:bench` 运行 `build:native-system`、`build:lib` 与 benchmark tsdown | 有 |
|
||||
| `typecheck`(pre-push hook 运行它) | `build:lib:host`(Host `tsc -b tsconfig.host.json`、Host bundle、Desktop bundle)与 Client `tsc -b tsconfig.client.json` | 有 |
|
||||
| `dev:web`([`scripts/dev-web.ts`](../../../../scripts/dev-web.ts))、`dev:desktop`(`apps/desktop/scripts/dev.ts`) | `build:artifacts` | 启动前无;`dev:web` 启动后的 `tsc -b tsconfig.client.json --watch` 持续检查 Client 聚合根程序 |
|
||||
| `lint`、`lint:fix`、`doc-typecheck` 与 CI 的 Typert contracts gate 的前置步骤 | Host 包编译与 Host bundle(`compile-referenced-projects.ts host-libraries`),不含 Desktop bundle | 无 |
|
||||
| CI node 24 / snapshots and artifacts(`check:ci:consumers`)、windows node 24 / build & observational(`check:ci:windows-blocking`) | `build` | 有,两个必需 lane |
|
||||
| CI node-compat 22.19 | `build:artifacts`(经 run-gates) | 无 |
|
||||
| CI python runtime | `build-exe-for-python-sdk.ts --build-artifacts` | 无 |
|
||||
| `e2e.yml`、`release.yml` 的 PR 演练 | `pnpm run build:artifacts --profile official` | 无 |
|
||||
| `build-preview-cloudflare.yml` | `pnpm run build:artifacts` | 无 |
|
||||
| CI node 24 / benchmarks | `run-ci-bench.ts`(`tsc -b --noCheck`) | 无 |
|
||||
| `release-vendor.yml` 的 PR 演练 | `compile-referenced-projects.ts host-libraries` | 无 |
|
||||
| `release-vendor.yml` 的非 PR 分支、`release-vendor-publish.yml` | `build:lib:host` | 仅 Host 聚合根程序 |
|
||||
|
||||
`lint`、`lint:fix`、`doc-typecheck` 与 Typert contracts gate 只读取 Typert 输出与包声明:两个聚合配置都是 `noEmit`,oxlint 自行构建程序,`doc-typecheck` 只引用包 project,`host-libraries` 的消费方都不读取 `apps/desktop/lib`。它们的前置步骤因此跳过 Host 聚合检查与 Desktop bundle,耗时从 159.8 s 降到 90.9 s(见下文测量)。
|
||||
|
||||
consumers lane 的 HMR Web 测试启动 `dev:web`,因此在该 lane 内使用 `build:artifacts`;同一 lane 的 `build` gate 仍运行两个聚合检查。CI 不单独调用 `build:check`:两个必需 lane 已通过 `build` 获得同一组检查。[PR 产物构建决策](2026-10-05-pr-artifact-typechecks.zh.md)的必需完整构建、benchmark `--noCheck`、job 与 worker 上限、node-compat 串行 Vitest、覆盖率权重与 pnpm 打包检查仍然有效;其中的 PR 产物构建由 `build:artifacts` 执行。
|
||||
|
||||
### 测量
|
||||
|
||||
原型测量的条件:256 vCPU 共享 Linux 主机(load average 23–70),Node v24.18.0、TypeScript 6.0.3,React 类型包已提升到根,不含[无损提速 Note](2026-10-11-lossless-build-speedups.zh.md) 的 Typert 改动;范围是包库构建(不含 native 与 web)。每轮从 `pnpm run clean` 开始,按 串行、并发、`build:lib`、`build:lib`、并发、串行 的顺序各运行一次;每个步骤由 `/usr/bin/time -v` 计时,进程树 RSS 每 250 ms 采样求和。原型中的聚合检查以 `tsc -b tsconfig.{host,client}.json` 在 references 已是最新时运行,此时 build mode 只构建聚合根程序。
|
||||
|
||||
| 方式(2 个样本的中位数) | wall(s) | CPU user+sys(s) | 进程树 RSS 峰值(GiB) |
|
||||
|---|---|---|---|
|
||||
| 串行 `pnpm run build:lib` | 249.4 | 378.0 | 5.95 |
|
||||
| 各步骤依次单独运行 | 255.1 | 373.8 | 5.82 |
|
||||
| 按依赖图并发 | 172.1 | 392.1 | 8.00 |
|
||||
|
||||
wall 中位数从 249.4 s 降到 172.1 s(−77.3 s,−31%)。各步骤单独运行的两次样本相差 28 s,因此把步骤拆成独立进程的开销无法分辨。CPU 增加约 14 s(+3.7%),主要是争用下 Host 聚合检查增加的 user 时间;RSS 峰值增加约 2.1 GiB,出现在 Host 聚合检查(约 4 GiB)与 Host tsdown(约 5.7 GiB)重叠时。并发时的关键路径是 host-src → host-bundle → desktop-bundle → client-src → client-check;host-check 在结束前约 33 s 完成,client-bundle 在结束前约 21 s 完成。
|
||||
|
||||
最终测量:master(合并基点)与本分支交替运行,每次均从 clean 开始,在 64 核 cgroup 中运行;除标注 n=1 者外,数值均为 2 个样本的中位数。
|
||||
|
||||
| 场景 | master | 本分支 | CPU user+sys(master → 本分支) | 进程树 RSS 峰值(master → 本分支) |
|
||||
|---|---|---|---|---|
|
||||
| 完整构建 `pnpm run build` | 317.1 s | 169.1 s(−46.7%) | 423.1 → 374.4 s(−11.5%) | 6.11 → 7.53 GiB(+1.42) |
|
||||
| 本分支串行构建 `DSH_GATE_CONCURRENCY=1 pnpm run build`(n=1) | — | 266.7 s | 389.5 s | 6.27 GiB |
|
||||
| 产物构建:master `build --artifacts-only`,本分支 `build:artifacts` | 185.8 s | 153.6 s(−17.3%) | 256.2 → 231.2 s | 6.58 → 6.18 GiB |
|
||||
| 开发迭代:master `build`,本分支 `build:artifacts` | 317.1 s | 153.6 s(−51.6%) | 423.1 → 231.2 s(−45.4%) | 6.11 → 6.18 GiB |
|
||||
| `pnpm run build:check`(`build:artifacts` 之后) | — | 67.7 s | 144.6 s | 5.33 GiB |
|
||||
| `pnpm run build:check` 无改动重跑 | — | 19.4 s | 33.1 s | 未测量 |
|
||||
| `lint` 与 `doc-typecheck` 的前置步骤:master `build:lib:host`,本分支 `host-libraries` | 159.8 s | 90.9 s(−43.2%) | 234.7 → 136.0 s | 未测量 |
|
||||
|
||||
本分支串行样本运行时主机上有其他工作(外来 CPU 742 s),因此它的 wall 可能高估串行成本。串行构建的各段中(master → 本分支,各一个样本),Host tsdown 从 22.5 s 降到 11.7 s,Client 包编译从 55.6 s 降到 48.7 s,Client 聚合检查从 68.2 s 降到 35.9 s,Client tsdown 从 13.9 s 降到 10.5 s;各段合计分别为 308.5 s 与 254.9 s。这些缩短来自[无损提速 Note](2026-10-11-lossless-build-speedups.zh.md);从 254.9 s 的串行各段合计到 169.1 s 的并发构建,其余缩短来自本 Note 的调度,它把 Host 聚合检查(约 66 s)移出关键路径。并发构建的关键路径是 native → host-src → host-bundle → desktop-bundle → client-src → client-check,host-check 在最后一步结束前约 30 s 完成(取自更早的一个样本)。`build:check` 并发运行 Host 与 Client 聚合检查,因此其 wall 等于 Host 聚合检查的 wall(约 65 s)。
|
||||
|
||||
### 完整性与无损依据
|
||||
|
||||
`build` 运行的编译器程序集合与串行构建相同:host-src 与 client-src 编译两个 face 的全部包 project,host-check 与 client-check 检查两个聚合根程序,且使用与 `build:check` 相同的命令。原型的 6 轮运行中,`{packages,apps,vendor,native}/**/lib/**`(排除 `*.tsbuildinfo`)的 11,580 个文件在串行与并发之间逐字节一致,每轮之后 `tsc -b --dry tsconfig.host.json tsconfig.client.json` 报告没有过期 project。tsbuildinfo 写入方归属为:host-src 292 个,client-src 76 个(恰好是 client-only project),两个聚合检查各 1 个;client-src 不改写 host-check 读取的任何 tsbuildinfo 或声明,因此该依赖图中不存在写入竞争。fixture 测试 `checks only the aggregate program in project mode` 表明 `tsc -p` 写出的 tsbuildinfo 被 `tsc -b --dry` 视为最新,`reports missing package declarations without compiling references` 表明 project mode 不编译 references。本实现中,并发 `build` 与 `DSH_GATE_CONCURRENCY=1 pnpm run build` 产出逐字节一致的 `lib/`(11,584 个文件)、`apps/web/dist`(156 个文件)与 client build record;之后 `tsc -b --dry` 对两个聚合配置报告 372 个 project 全部为最新,没有需要构建的 project。
|
||||
|
||||
| 错误 | `build:artifacts` | `build:check` | `build` | `typecheck` | 必需 CI lane |
|
||||
|---|---|---|---|---|---|
|
||||
| 包源码类型错误 | host-src / client-src | 不报告(读取已有声明) | host-src / client-src | 报告 | 报告 |
|
||||
| 只属于聚合根程序的测试、脚本、benchmark、website 类型错误 | 不报告 | host-check / client-check | host-check / client-check | 报告 | consumers、windows-build |
|
||||
| 包 tsconfig 配置错误 | host-src / client-src | 不报告(`tsc -p` 不校验被引用 project 的 tsconfig 选项与语法) | host-src / client-src | 报告 | 报告 |
|
||||
| Typert 分析错误 | host-bundle | 不报告 | host-bundle | 报告(经 Host bundle) | 报告 |
|
||||
| 前置产物缺失 | — | 编译器诊断(TS6305、TS2307) | — | — | — |
|
||||
|
||||
负对照:在只属于 Host 聚合根程序的 `scripts/pnpm-invocation.spec.ts` 中加入类型错误(TS2322)后,`build:artifacts` 以 0 退出;`build:check` 以 1 退出,其中 Host 聚合检查失败、Client 聚合检查通过;`build` 以 1 退出,9 个步骤中 8 个通过、1 个失败,且不写 client build record。在临时 fixture 中,被引用 project 的 tsconfig 含无效 `target`、JSON 语法错误或未知编译选项时,`tsc -p` 以 0 退出,`tsc -b` 分别报告 TS6046、TS5092 与 TS1012、TS5023 并以 1 退出;被引用 project 缺少 `composite` 时,两者都报告 TS6306。
|
||||
|
||||
CI 证据:PR #6005 上,两个运行并发 `pnpm run build` 的必需 lane 均通过:`node 24 / snapshots and artifacts`(`check:ci:consumers`,运行于 `blacksmith-16vcpu-ubuntu-2404`)与 `windows node 24 / build & observational`(`check:ci:windows-blocking`,运行于 `blacksmith-16vcpu-windows-2025`);GitHub 托管的 4 vCPU benchmarks lane 不运行 `build`。内层并发取 `DSH_GATE_CONCURRENCY`:master 串行 lane 设为 1,consumers lane 在 self-hosted failover 时设为 10,未设置时取可用 CPU 数,且不超过 9 个步骤。
|
||||
|
||||
### 有意的取舍(非无损)
|
||||
|
||||
- **部分命令跳过聚合检查:** `build:artifacts`、`dev:web` 与 `dev:desktop` 的启动构建,以及 `lint`、`lint:fix`、`doc-typecheck` 与 Typert contracts gate 的前置步骤,都不类型检查两个聚合根程序,这些前置步骤也不产出 `apps/desktop/lib`。完整性由 `build`、`build:check`、`typecheck`(pre-push hook)与两个必需 CI lane 负责;`dev:web` 与 `dev:desktop` 不会因测试类型错误而拒绝启动。
|
||||
- **`build:check` 不检查新鲜度:** 在 `build:artifacts` 之后修改源码时,`build:check` 基于旧声明(包括 Typert Remote 声明)检查;需要先重跑 `build:artifacts`。完整 `build` 不受影响。
|
||||
- **并发 `build` 提高内存峰值:** 本分支完整 `build` 的进程树 RSS 峰值为 7.53 GiB,master 串行构建为 6.11 GiB(+1.42 GiB,2 个样本的中位数),本分支串行构建为 6.27 GiB(n=1);同时 CPU 从 423.1 s 降到 374.4 s,因为[无损提速 Note](2026-10-11-lossless-build-speedups.zh.md) 省去的工作多于并发争用增加的工作。原型(不含 Typert 改动)中,单独的并发调度使 RSS 峰值增加约 2.1 GiB(5.9 → 8.0 GiB)、CPU 增加约 3.7%。上限由 `DSH_GATE_CONCURRENCY` 控制;内存紧张时使用 `DSH_GATE_CONCURRENCY=1 pnpm run build`,或分别运行 `build:artifacts` 与 `build:check`。windows-build lane 中 `build` 与 `docs:build` 同时运行。
|
||||
- **成功步骤的输出默认不显示:** 只有失败步骤打印完整输出。
|
||||
- **`tsc -p` 不跳过已是最新的根程序:** 无改动时立即重跑 `build:check` 仍需 19.4 s 与 33.1 s CPU,因为两个程序都要重新解析;`tsc -b` 会跳过已是最新的根。
|
||||
- **多核冷启动时 `build:artifacts` 只比 `build` 快 15.5 s:** 本分支冷启动 `build` 为 169.1 s,`build:artifacts` 为 153.6 s(2 个样本的中位数),因为 host-check 不在关键路径上,client-check 只比 client-bundle 与 web 之和长约 16 s。`build:artifacts` 的其余收益是 CPU 与内存:CPU 从 374.4 s 降到 231.2 s,RSS 峰值从 7.53 GiB 降到 6.18 GiB;在少核机器与增量重建中,这部分也转化为 wall。
|
||||
- **CI 调用根 manifest 中的公开命令:** PR 产物构建调用 `build:artifacts`,而不是内部 helper 参数;修改根 manifest 中这些 script 的 PR 会激活独立的 Node Addon System 平台矩阵。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
- **只在 `typecheck` 中保留聚合检查:** `pnpm run build` 必须完整,能检测出所有错误。
|
||||
- **保留 `--artifacts-only` 并新增 `build:artifacts` 别名:** 同一件事有两个名字。
|
||||
- **命名为 `build:src` 与 `build:test`:** `build:artifacts` 产出全部产物,包括 tsdown bundle、Desktop bundle 与 Web,而不只是编译后的源码;`build:check` 除测试外还检查脚本、benchmarks 与 website 代码。
|
||||
- **按编译器术语命名(`build:b`/`build:p`、`build:project`、`build:emit`)或命名为 `build:dist`:** tsc 只出现在两个产物步骤中,`-p` 只是 check 步骤的实现选择;TypeScript 把 `tsc -p` 一侧称为 project;`emit` 容易被理解为只 emit 的 `--noCheck` 模式,而包 project 仍完整检查;包产物位于 `lib/`,`dist` 则让人联想到发布打包。
|
||||
- **`build:check` 使用 `tsc -b <aggregate>`:** build mode 会编译过期的 references,即执行 `build:artifacts` 的工作。
|
||||
- **`build:check` 先补齐或校验前置产物:** 违背每个命令只做自己的工作,并增加前置逻辑;编译器诊断已经指明缺失的输出。
|
||||
- **新的调度实现(shell `&`/`wait`、`concurrently`、turbo/nx):** 重复 run-gates 已有的失败汇总、进程树终止与 Windows 处理。
|
||||
- **在 CI 的 run-gates 中把 `build` 展开为多个 gate:** `pnpm run build` 必须是本地与 release workflow 可直接使用的完整命令,record 也由它写入。
|
||||
- **放宽产物链内的非数据依赖(native ∥ host-src、desktop-bundle ∥ client-src):** 未测量;Desktop bundle 本身只占 4.0–4.5 s。
|
||||
- **把测试移入新的 tsconfig:** 现有聚合配置已经把包 references 与根程序分开。
|
||||
- **把聚合根程序拆成多个子程序:** 每个子程序都要注入 242 个含模块增强的文件,CPU 增加 9–57%,只降低 wall(见[无损提速 Note](2026-10-11-lossless-build-speedups.zh.md))。
|
||||
- **CI 单独运行 `build:check`:** 必需 lane 已通过 `build` 获得同一组检查,再单独运行是重复工作。
|
||||
|
||||
## 后果
|
||||
|
||||
开发迭代可以用 `build:artifacts` 跳过两个聚合根程序(冷启动比 master 的 `build` 快 51.6%),在声明完成前用 `build:check` 或 `build` 补齐检查。`build` 保持完整:Host 聚合检查不在关键路径上,Client 聚合检查与 Client bundle、web 并行,冷启动比 master 快 46.7%(含无损提速 Note 的改动,2 个样本的中位数)。代价是更高的内存峰值、并发争用带来的少量额外 CPU、默认更少的成功输出,以及多个开发与前置命令不检查测试类型(见上文取舍)。
|
||||
|
||||
删除或改变 consumers 与 windows-build 两个必需 lane 的 `build`,必须先把两个聚合检查转交另一处阻塞型 owner。若 Typert 以后获得独立的生成步骤,步骤表新增该步骤,client-src 改为依赖它;命令语义与调度不变。
|
||||
|
||||
[Typert Remote 方法调用](../architecture/2026-08-02-typert-remote-method-calls.zh.md)描述的“Client 编译前先完成 Host 与 Remote 产物”由 client-src 对 host-bundle 的依赖保证。
|
||||
|
||||
验证:`scripts/build.spec.ts` 覆盖类型剥离检查、已删除参数与非法范围的拒绝、`--profile` 与 `--scope check` 的冲突、范围选择、步骤依赖与环境;`compile-referenced-projects.spec.ts` 覆盖 project mode 只检查聚合程序、缺失声明的诊断,以及 `tsc -b --dry` 接受 `tsc -p` 写出的 tsbuildinfo;失败传播、输出缓冲与进程树终止沿用 run-gates 的调度实现及其测试;`run-gates.spec.ts`、`ci-workflow.spec.ts`、`preview-workflow.spec.ts`、`build-exe-for-python-sdk.spec.ts`、`dev-web.spec.ts` 与 `tests/ci-release-selfhosted.spec.ts` 固定各调用方的命令。
|
||||
@@ -0,0 +1,46 @@
|
||||
# Bilingual-pair consistency record for 2026-10-11-lossless-build-speedups.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-10-11-lossless-build-speedups.md
|
||||
/agent-note-build-speed-ups-that-keep-outputs-unchanged:
|
||||
en: 48682b646b8f993a
|
||||
zh: 4c26cb4aab5cd08c
|
||||
/agent-note-build-speed-ups-that-keep-outputs-unchanged/problem:
|
||||
en: 46551eb7e33c40ca
|
||||
zh: f8c1fc063fa77062
|
||||
/agent-note-build-speed-ups-that-keep-outputs-unchanged/decision:
|
||||
en: 3948d6fc3fabf32e
|
||||
zh: c3b8dcdfe8168275
|
||||
/agent-note-build-speed-ups-that-keep-outputs-unchanged/decision/measurement-conditions:
|
||||
en: 2ab844073ec7c336
|
||||
zh: b25e39f2f80b0d7e
|
||||
/agent-note-build-speed-ups-that-keep-outputs-unchanged/decision/1-only-the-root-manifest-declares-react-type-packages:
|
||||
en: 1101c94d04f9c7e9
|
||||
zh: 413b7b15f98a5f62
|
||||
/agent-note-build-speed-ups-that-keep-outputs-unchanged/decision/2-typert-selects-packages-by-manifest-first:
|
||||
en: 1ae4b65792db0975
|
||||
zh: a11c0d90842a3b55
|
||||
/agent-note-build-speed-ups-that-keep-outputs-unchanged/decision/3-typert-does-not-expand-aggregate-include:
|
||||
en: 0c8ab2d412113914
|
||||
zh: 01ba4f5042422d77
|
||||
/agent-note-build-speed-ups-that-keep-outputs-unchanged/decision/4-typert-parses-package-tsconfigs-lazily:
|
||||
en: cd6e05c61a7cc495
|
||||
zh: 480d2d85d78d2d7f
|
||||
/agent-note-build-speed-ups-that-keep-outputs-unchanged/decision/5-the-typert-workspace-root-also-requires-packages:
|
||||
en: 8b89436df201141c
|
||||
zh: 2fab82419c8ba704
|
||||
/agent-note-build-speed-ups-that-keep-outputs-unchanged/decision/6-typert-generates-in-a-worker-thread:
|
||||
en: 0362483a79c07881
|
||||
zh: d84a2390b49c4012
|
||||
/agent-note-build-speed-ups-that-keep-outputs-unchanged/decision/7-the-typert-transform-hook-filters-by-module-id:
|
||||
en: b5ad6b5506c150d5
|
||||
zh: 25f09e4ab74696ad
|
||||
/agent-note-build-speed-ups-that-keep-outputs-unchanged/decision/8-client-tsdown-presets-read-the-manifest-from-the-package-directory:
|
||||
en: 80c2ddc5fdff0b5f
|
||||
zh: f5ce309a2e88ff4f
|
||||
/agent-note-build-speed-ups-that-keep-outputs-unchanged/alternatives-considered:
|
||||
en: 186e42f2f5481c8d
|
||||
zh: 4a1c79a391a268b0
|
||||
/agent-note-build-speed-ups-that-keep-outputs-unchanged/consequences:
|
||||
en: 93a96d3ed398f348
|
||||
zh: 93b7c7405be981a6
|
||||
@@ -0,0 +1,136 @@
|
||||
# Agent Note: Build speed-ups that keep outputs unchanged
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-10-11-lossless-build-speedups.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
A cold build carries three fixed costs that do not affect output contents:
|
||||
|
||||
- **JSX signatures in the Client test aggregate program.** The Check phase of `tsc -p tsconfig.client.json` takes 59.1 s, more than half of it computing the `@types/react` module specifier for every JSX file that imports React. TypeScript's `computeModuleSpecifiers` visits every symlink location of that declaration file and scans all 491 tsconfig `paths` entries at each location. pnpm creates one `node_modules/@types/react` link for each workspace package that declares `@types/react` itself; 66 manifests declare it, so the Client test aggregate program sees 65 locations. Counters measured 88 JSX files × 2 modules (`index.d.ts`, `jsx-runtime.d.ts`) × 65 locations: 11,457 `tryGetModuleNameFromPaths` calls and 5,623,782 entry scans, 32.6 s in total. A dose-response run at five truncation levels of the visible location count N (N = 1 to 65; Check 26.92 s to 58.40 s) fits Check ≈ 26.9 + 0.494·N s.
|
||||
- **Typert inside the Host tsdown pass.** The Host tsdown pass takes about 21.6–22.6 s. Typert discovers and generates artifacts synchronously inside the first `writeBundle`; the CPU profile shows it blocking the tsdown main thread for about 12.4 s while every package build waits. Discovery takes 8.06 s: it parses all 362 package tsconfigs referenced by the two face aggregates (about 3.8 s), expands the Host aggregate's 2,064 literal `include` entries on every parse (1.6 s; the analyzer never reads those file names), and walks the public-export surface of 322 registrations (2.7–3.2 s). Only the 28 packages that declare a Typert export produce Host artifacts.
|
||||
- **Manifest lookup in the Client tsdown pass.** The Client tsdown pass takes about 11.7 s with a saturated main thread (7.1% idle); 34.5% of it (3.95 s) is `workspaceManifest`: every package config runs `globSync('packages/*/*/package.json')` for its package name and parses manifests until the name matches. The module-level cache has no effect because tsdown's native config loader imports every package config through `import-without-cache`, creating a fresh module instance per config. Each glob takes 40–125 ms and runs inside the `neverBundle`/`alwaysBundle` callbacks of `tsdown:deps`, blocking every build.
|
||||
|
||||
#5849 merged Host fixture type narrowing and Typert memoization; this note's baseline is the master that follows it.
|
||||
|
||||
## Decision
|
||||
|
||||
The following eight speed-ups change neither the bytes of `lib/` and `apps/web/dist` outputs nor the set of type diagnostics that repository builds report. The only change in diagnostic ownership comes from item 4: Typert does not validate tsconfigs of packages it does not analyze, and only `tsc -b` reports those errors. [The build split note](2026-10-11-build-artifacts-check-split.md) records the command split and concurrent scheduling.
|
||||
|
||||
| # | Decision | Affected stage | Measured median | Outputs and diagnostics | Intended behavior difference |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | Only the root manifest declares React type packages | Client type checking | Test aggregate Check 59.1 → 26.3 s | Program file count and diagnostics unchanged | Package manifests do not list React type packages |
|
||||
| 2 | Typert selects packages by manifest first | Host tsdown | 22.62 → 19.94 s | Byte-identical | None |
|
||||
| 3 | Typert does not expand aggregate `include` | Host tsdown | 19.94 → 16.99 s | Byte-identical; registration inventory identical | None |
|
||||
| 4 | Typert parses package tsconfigs lazily | Host tsdown | 16.99 → 16.25 s (within noise); Typert discovery 5.9–7.1 → 0.36–0.40 s | Byte-identical; registration inventory identical | Only `tsc -b` reports tsconfig errors in unanalyzed packages |
|
||||
| 5 | The Typert workspace root also requires `packages/` | Host tsdown | Within noise | Byte-identical | Dual-face packages get artifacts in `package` mode |
|
||||
| 6 | Typert generates in a Worker Thread | Host tsdown | 15.39 → 10.25 s | Byte-identical; failures still fail tsdown | Host tsdown max RSS about +0.6 GiB |
|
||||
| 7 | The Typert `transform` hook filters by module id | Host tsdown | Within noise; hook calls 1,566 → 195 | Byte-identical | None |
|
||||
| 8 | Client tsdown presets read the manifest from the package directory | Client tsdown | 12.78 → 8.73 s | Byte-identical; external decisions identical | The presets' first argument is the package directory |
|
||||
|
||||
The published `./tsdown` export `typertPlugin()` changes with items 5–7 (`transform` as `{ filter, handler }`, an asynchronous `writeBundle`, and a workspace root that must contain `packages/`); rolldown and tsdown consumers accept both hook forms, and the analyzer already required `<root>/packages`, so no external workspace that worked before stops working.
|
||||
|
||||
### Measurement conditions
|
||||
|
||||
All numbers come from one shared Linux host (256 vCPU, 4 TB memory, load average 23–111 during runs) with Node v24.18.0, TypeScript 6.0.3, and tsdown 0.22.2 (rolldown 1.1.1).
|
||||
|
||||
- **Host tsdown:** `node node_modules/tsdown/dist/run.mjs --config-loader native --env.DSH_BUILD_FACE host`, deleting every `lib/typert.*` before each run; variants run interleaved and sequentially, 3–4 samples per group. Items 2–4 come from the first round (starting from the baseline) and items 5–7 from the second round (starting from the code with items 2–4). Output comparison covers SHA-256 of the 743 top-level files of `packages/*/*/lib`, `apps/*/lib`, and `vendor/*/lib` (140 of them Typert artifacts). Medians of two groups running identical code differ by 0–0.11 s; samples within a group spread by up to 2.5 s.
|
||||
- **Client test aggregate:** `tsc -p tsconfig.client.json --extendedDiagnostics` with prebuilt references and a fresh tsBuildInfo file; six samples of identical code spread by 3.8 s in Check (about 6%).
|
||||
- **Client tsdown:** `node node_modules/tsdown/dist/run.mjs --config-loader native --env.DSH_BUILD_FACE client` on a built tree. The item 8 prototype ran three rounds in rotated order with a baseline spread of 1.56 s; the final implementation and the original ran interleaved, three samples each. Output comparison covers every file of `{packages,apps,vendor,native}/**/lib/**`, excluding `*.tsbuildinfo`.
|
||||
|
||||
### 1. Only the root manifest declares React type packages
|
||||
|
||||
- **Purpose:** Reduce the number N of `@types/react` locations that the Client test aggregate program sees from 65 to 1, removing module-specifier work that grows linearly with N.
|
||||
- **Change:** The root `package.json` declares `@types/react` `~18.3.1` and `@types/react-dom` `~18.3.0` in `devDependencies`, the ranges every package used before; manifests under `apps/*`, `packages/*/*`, and `vendor/*` declare neither in any dependency section. TypeScript resolves the root `node_modules/@types/react` from every package. Packages still declare runtime `react` and `react-dom` under the [Client dependency declaration rules](../../../../packages/client/AGENTS.md#dependency-declaration); `packages/client/ui-slots`, which references React only through `import type`, declares `react` in `devDependencies`. pnpm `dedupeDirectDeps` stays off. Lockfile changes are confined to the importers section, plus one reordered `papaparse` entry with the same integrity.
|
||||
- **Regression guard:** [`verify-package-dependencies`](../../../../scripts/verify-package-dependencies.ts) runs in `hygiene` and CI. It requires the root manifest's `devDependencies` to declare both packages and rejects either one in the `dependencies`, `devDependencies`, `optionalDependencies`, or `peerDependencies` of any manifest under `apps/*`, `packages/*/*`, or `vendor/*`; the diagnostic names the entry to delete and links Client dependency declaration rule 8. The rule matches only these two exact names; other `@types/*` packages remain DefinitelyTyped type providers. Dependency-section inference does not treat these two packages as type providers for `react`/`react-dom`: when a package lacks a `react` declaration, the verifier reports `react` as missing and `--fix` refuses to repair it until a version range is declared; `--fix` never writes back or deletes the two packages. [`verify-package-dependencies.spec.ts`](../../../../scripts/verify-package-dependencies.spec.ts) covers rejection in each dependency section, a root manifest missing the declarations, the accepted layout, and the type-provider exclusion.
|
||||
- **Performance:**
|
||||
|
||||
| Measurement | Per-package declarations (N = 65) | Root manifest declaration (N = 1) |
|
||||
|---|---|---|
|
||||
| Client test aggregate Check | 59.08 s (median of 3) | 26.28 s (median of 5), −56% |
|
||||
| Client test aggregate wall | 68–71 s | 33.6–34.9 s |
|
||||
| Client test aggregate user CPU | 79–83 s | 44.5–45.3 s |
|
||||
| Check sum of the 76 client-only package projects | 34.6 s (median of 2) | 26.5 s (median of 2), −23% |
|
||||
| Wall of the 76 client-only package projects | 55.7 s (median of 2) | 44.5 s (median of 2) |
|
||||
|
||||
- **Why it is lossless:** The 66 package-level links and the root link share one realpath (`.pnpm/@[email protected]`), so the program reads the same declarations; the program keeps 2,613 files, and `tsc -b --force tsconfig.client.json` exits 0 without diagnostics. `devDependencies` are not installed with published packages, so the [published dependency faces](2026-08-26-published-dependency-faces.md) conclusion that independent TypeScript consumers install referenced type packages themselves is unchanged; runtime resolution never reads `@types/*`.
|
||||
- **Trade-off:** Package manifests do not list their own React type dependency, and every workspace package shares one React types version; a package that needs a different version requires a new Agent Note overturning this item. The check covers only `apps/*`, `packages/*/*`, and `vendor/*`; other pnpm members such as `website`, `benchmarks`, `native/system`, and `python/sdk-runtime` are out of scope, and none of them currently declares React type packages.
|
||||
|
||||
### 2. Typert selects packages by manifest first
|
||||
|
||||
- **Purpose:** Workspace mode generates artifacts only for packages that declare a Typert export, so it skips the surface walk for every other candidate.
|
||||
- **Change:** `WorkspaceAnalyzer.discoverPackages(includeManifest?)` and `WorkspaceTypertGenerator.discover(faces?, includeManifest?)` accept an optional `package.json` predicate applied before the surface walk; a rejected package has neither its sources read nor its project config parsed. `WorkspaceTypertGenerator.generateContributors(faces?)` passes `hasTypertExport` (`exports` contains `./typert`, `./client/typert`, or `./remote`). Callers that pass no predicate (the Cordis catalog, `generate()` without packages) behave as before.
|
||||
- **Performance:** Host tsdown 22.62 s → 19.94 s (medians of 3).
|
||||
- **Why it is lossless:** The predicate is the filter previously applied after discovery, and a package it rejects never produced artifacts; the 743 output files are byte-identical except the Typert generator's own `lib/index.js`, whose source is this change.
|
||||
|
||||
### 3. Typert does not expand aggregate `include`
|
||||
|
||||
- **Purpose:** The analyzer reads only an aggregate's compiler options and project references; expanding `include` only fills `fileNames`, which it never uses.
|
||||
- **Change:** `WorkspaceCaches.aggregate(path)` returns and caches `AggregateConfig { path, options, projectReferences }`. An aggregate that declares its own `references` is parsed with a `ParseConfigHost` whose `readDirectory` returns an empty list; an aggregate without `references` is expanded as usual.
|
||||
- **Performance:** Host tsdown 19.94 s → 16.99 s (medians of 3).
|
||||
- **Why it is lossless:** TypeScript reports TS18003 (no inputs) only for a config with neither `files` nor `references`, so an aggregate that declares `references` yields the same diagnostics, options, and references without expansion. The registration inventory dumped after forcing every lazy config (408 registrations, including `fileNames` and options, plus the aggregates' options and references) is byte-identical to the baseline, and the outputs are byte-identical. The tests `reads aggregate options and references without expanding include patterns` and `reports an aggregate without references or inputs` pin both kinds of config.
|
||||
|
||||
### 4. Typert parses package tsconfigs lazily
|
||||
|
||||
- **Purpose:** Parse only the tsconfigs of packages selected for analysis, not every package that an aggregate references.
|
||||
- **Change:** `PackageRegistration.config` is a getter over `caches.config(configPath)` that parses on first read. The registration inventory keeps its order, faces, names, roots, and export subpaths.
|
||||
- **Performance:** Host tsdown 16.99 s → 16.25 s (medians of 3), within the 2.5 s within-group spread. Together, items 2–4 reduce Typert discovery in an isolated benchmark from 5.9–7.1 s to 0.36–0.40 s, tsconfig parses from 362 to 28, surface walks from 322 to 28, and total Typert time from 9.7–11.8 s to 4.4–4.5 s.
|
||||
- **Why it is lossless:** The inventory comparison is the one in item 3 (byte-identical), and the outputs are byte-identical. The test `reads only the project configs of manifests selected for discovery` pins that only selected configs are parsed.
|
||||
- **Trade-off (not lossless):** Typert does not parse tsconfigs of packages it does not analyze, so a syntax or configuration error in such a tsconfig does not fail Typert. In the same build, the Host package compilation `tsc -b` that runs before the Host bundle and the Client package compilation `tsc -b` parse every referenced tsconfig and reject those errors; the `PackageRegistration.config` JSDoc and the [Typert generator README](../../../../packages/typert/generator/README.md) state this ownership. A consumer that runs Typert without `tsc -b` does not receive those diagnostics.
|
||||
|
||||
### 5. The Typert workspace root also requires `packages/`
|
||||
|
||||
- **Purpose:** Correct workspace-root resolution. 24 dual-face packages carry their own `tsconfig.host.json` leaf; searching upward for `tsconfig.host.json` alone took each of them as a root, so workspace mode ran one empty discovery per such package per build, and `package` mode silently produced no artifacts for dual-face packages.
|
||||
- **Change:** The workspace root is the nearest ancestor of the bundle output that contains both `tsconfig.host.json` and `packages/`.
|
||||
- **Performance:** Host tsdown 14.82 s → 15.39 s (medians of 3), within noise.
|
||||
- **Why it is lossless:** The repository root [`tsdown.config.ts`](../../../../tsdown.config.ts) uses only workspace mode, and the 743 output files are byte-identical. The test `resolves the workspace root above a dual-face package leaf` pins the rule.
|
||||
- **Behavior difference:** A `package`-mode consumer building a dual-face package receives that package's Typert artifacts; repository builds do not use `package` mode.
|
||||
|
||||
### 6. Typert generates in a Worker Thread
|
||||
|
||||
- **Purpose:** Overlap Typert analysis with bundling on the tsdown main thread instead of blocking every package build inside the first `writeBundle`.
|
||||
- **Change:** [`src/worker.ts`](../../../../packages/typert/generator/src/worker.ts) is the Worker Thread entry; it runs `generateContributors(faces)` and posts one `artifacts` or `failure` message. In workspace mode, the build's first `buildStart` locates the workspace root from `options.cwd` and starts generation; `writeBundle` is asynchronous, the first finishing bundle claims emission and awaits the result, and later bundles return immediately. An analysis error, a worker crash or early exit, a malformed or undeserializable response, or a worker that cannot be terminated fails the claiming bundle with a `TypertWorkerError` naming the workspace and cause, which fails the whole tsdown pass; the worker is terminated before the outcome settles, and an unclaimed outcome never becomes an unhandled rejection. Built output starts the worker from `./worker.js`; source runs (tests) load `worker.ts` through `tsx/esm/api`. `package` mode still generates on the main thread.
|
||||
- **Performance:** Host tsdown 15.39 s → 10.25 s (medians of 3). Profile of the final configuration (items 2–7): main thread 10.1 s, first `buildStart` at 4.18 s (tsdown config resolution), `writeBundle` on the main thread only 0.37 s; worker 5.0 s (module load about 0.4 s, discovery 0.28 s, generation 3.88 s including `createProgram` 2.21 s), ending at about 9.3 s. Host tsdown wall is about max(bundling, 4.2 s + Typert).
|
||||
- **Why it is lossless:** All 54 second-round runs exit 0, and each run's 743 output files match the baseline except the generator's own `lib/index.js`. The test `writes the artifacts in-process generation produces` compares worker and in-process artifacts; in a scratch workspace with one package's `files` broken, tsdown exits 1 within 2.4 s, reporting `TypertWorkerError` with the worker's `TypertAnalysisError`, and writes no artifacts. The `typertPlugin workspace mode` test group covers each failure path.
|
||||
- **Trade-off:** The worker heap is about 300–420 MiB; the final configuration's median Host tsdown max RSS is 6,296–6,408 MiB against 5,676 MiB at baseline. Under heavy shared-host load the worker can land on the critical path: one sample took 21.91 s with the artifact-owning package build waiting about 14 s for the worker, not reproduced in 10 later samples; at load average about 100, two runs took 17.86 s and 14.3 s. The worker also completes under a 1,024 MiB heap limit, so GC pressure is not the cause; attribution to host contention is unproven.
|
||||
|
||||
### 7. The Typert `transform` hook filters by module id
|
||||
|
||||
- **Purpose:** Stop every module from crossing from rolldown into the JavaScript `transform` hook only to be rejected by an extension check.
|
||||
- **Change:** `transform` declares `filter: { id: /^[^?]*\.[cm]?tsx?(?:\?|$)/ }`, and its handler still checks the id with the same regular expression.
|
||||
- **Performance:** Hook calls per Host build fall from 1,566 to 195 (195 TypeScript modules, 4 of them with decorators). Wall medians are 11.07 s (3 samples) and 9.98 s (4 samples) against 10.25 s for item 6, while the identical-code control groups give 11.07 s and 9.87 s; the difference is within noise.
|
||||
- **Why it is lossless:** The regular expression uses only ASCII constructs and matches the same id set in rolldown's Rust regex engine and in JavaScript; the handler applies the same check, so hosts that ignore filters behave the same. Outputs are byte-identical; the test `filters transform calls to TypeScript module ids` pins the filter.
|
||||
|
||||
### 8. Client tsdown presets read the manifest from the package directory
|
||||
|
||||
- **Purpose:** Each package config reads only its own `package.json`, removing the glob over every manifest by package name.
|
||||
- **Change:** The first argument of `clientBundle`, `staticLinked`, and `clientLibrary` in [`packages/client/tsdown.client.ts`](../../../../packages/client/tsdown.client.ts) is the package directory, normally the config file's `import.meta.dirname`; the preset reads `<packageDir>/package.json` once and uses its `name` as the plugin id and diagnostic name; when that manifest is missing, unreadable, or has no `name`, config loading fails with an error naming the path and cause. 82 package configs call these presets. The presets keep no module-level manifest cache; the Node half's production-dependency external list is computed from that manifest on first resolve and cached in that config's closure.
|
||||
- **Performance:** Client tsdown falls from 12.78 s to 8.73 s (medians of 3), −4.05 s (−31.7%), with disjoint sample ranges; user CPU falls from 26.1 s to 25.2 s and sys from 5.9 s to 4.9 s, so the saving comes mainly from unblocking the main thread rather than from less total CPU. The prototype (one glob building a name index) measured 11.72 s → 7.46 s (−36%). In Host tsdown only 5 `hostPhase` packages reach the lookup; one sample pair measured 11.92 s → 10.71 s, with low confidence.
|
||||
- **Why it is lossless:** The 11,566 lib files are byte-identical across 11 runs. After the 82 call sites switched to package directories, no resolved package name differs from the name previously written at the call site. An in-process comparison of the external decisions for 82 configs × 3 faces × 1,600 specifiers matches the original implementation exactly; a negative control with one extra external changes 75 decisions. `client-bundle-purity.spec.ts` covers these manifest errors.
|
||||
- **Behavior difference:** The presets' calling convention changes from a package name to a package directory.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- **Enable pnpm `dedupeDirectDeps` (item 1):** It applies to every direct dependency and changes the link layout of every workspace package, a global side effect.
|
||||
- **Patch TypeScript (item 1):** An upstream-style patch (precomputed candidate endings and cached normalized `paths` patterns) lowers Check by 31.9 s, comparable to the hoist; on the hoisted tree it adds nothing (references Check 25.76 s against 26.47 s) and must be maintained across every TypeScript upgrade.
|
||||
- **Alias `react` in tsconfig `paths` (item 1):** tsx, vitest, and vite also read `paths`, so the alias changes their resolution of runtime `react`.
|
||||
- **Narrow `paths` to each face's reference closure (item 1):** The 328-entry version that keeps wildcard keys checks in 55.73 s against 59.08 s (one sample), within noise; the version without wildcard keys produces 18 diagnostics.
|
||||
- **Split the Client test aggregate program (item 1):** Every sub-program must inject the 242 files carrying module augmentations to pass, and the visible location count stays at 33–49; splitting into 2, 4, or 8 groups leaves the Check sum nearly flat while user CPU rises by 9%, 30%, and 57%, lowering only wall, and the 4- and 8-group splits each produce 1–2 diagnostics.
|
||||
- **Cache resolution results and make the discovery queue O(1) (Typert):** After items 2–4 only 28 surface walks remain, about 0.18 s, below noise.
|
||||
- **Move Typert into a separate process or build step (Typert):** The generator would have to be compiled first, and `@deepseek-ai/dsh-client-file-upload/types` would need a `paths` entry (otherwise Typert reads `lib/types/types.d.ts` while `tsc` writes it); after item 6 the worker already overlaps bundling, leaving at most about 1 s.
|
||||
- **Filter `transform` by code content (item 7):** rolldown interprets `^` and `\s` with Rust regex semantics, which differ from JavaScript on lone CR, U+2028, and BOM, so a decorated module could be missed.
|
||||
- **Build a process-wide manifest index on `globalThis` (item 8):** The prototype saves about as much time as item 8, but it is process-global state without an owner, violating the shared-state ownership rule in the [package rules](../../../../packages/AGENTS.md).
|
||||
- **Disable tsdown's gzip report (item 8):** `--no-report` lowers Client tsdown by 0.95 s, within the 1.56 s baseline spread.
|
||||
- **Add rolldown filters to DSH `resolveId`/`load` hooks (item 8):** Lowers it by 1.16 s, within noise.
|
||||
- **Memoize the input-isolation check per module id (item 8):** Lowers it by 0.93 s, within noise; on top of the manifest index it saves only another 0.18 s.
|
||||
|
||||
## Consequences
|
||||
|
||||
Client type checking spends substantially less CPU and wall time, and the Host and Client tsdown passes take substantially less wall time, without changing runtime resolution, published installation semantics, or any output byte. In the serial build segments (master → branch, one sample each), Host tsdown falls from 22.5 to 11.7 s, Client package compilation from 55.6 to 48.7 s, the Client aggregate check from 68.2 to 35.9 s, and Client tsdown from 13.9 to 10.5 s; the serial segments sum to 308.5 s on master and 254.9 s on the branch (see [the build split note](2026-10-11-build-artifacts-check-split.md)). Host tsdown lies on the critical path of both `build` and `build:artifacts`, Client tsdown on that of `build:artifacts`, and the Client aggregate check is the last step of the `build` critical path.
|
||||
|
||||
Costs: every workspace package shares one React types version; Typert does not validate tsconfigs of packages it does not analyze, and `tsc -b` owns those errors; Host tsdown max RSS grows by about 0.6 GiB, and under heavy shared-host load the worker can become Host tsdown's critical path.
|
||||
|
||||
Reevaluation conditions: remeasure item 1 when migrating to TypeScript 7, and reconsider per-package declarations if the location count no longer affects Check time. tsdown's own config loading still takes 1.8–2.3 s per Client build (one uncached module instance per config) and needs an upstream change. The first `buildStart` arrives about 4.2 s into the Host build; starting the worker at plugin construction with an explicit root would start Typert earlier.
|
||||
|
||||
Verification: the Typert generator's `tsdown-plugin.spec.ts`, `tsdown-plugin-worker.spec.ts`, and `type-model.spec.ts` pin the behavior of items 2–7; the generator tests report 209 passed and 28 skipped, and `tsc -b tsconfig.host.json` exits 0. The repository coverage gate excludes `packages/typert/*/src/**`; under a scratch coverage config, `tsdown-plugin.ts` reaches 100% statements, branches, functions, and lines. Five negative controls (the old root rule, eager package configs, an expanded aggregate, no emission claim, a skipped worker termination) each fail the test that guards them; the real tsdown failure path exits 1 within 2.4 s and reports `TypertWorkerError`. Item 1: `verify-package-dependencies` exits 0 (74 packages), its spec passes 47/47, `verify-client-packages` exits 0, `tsc -b tsconfig.client.json` reports no diagnostics, and the Client aggregate Check takes 26.48 s. Item 8: all 72 focused specs pass.
|
||||
@@ -0,0 +1,136 @@
|
||||
# Agent Note: 不改变产物的构建提速
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-10-11-lossless-build-speedups.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
冷启动构建中有三处与产物内容无关的固定开销:
|
||||
|
||||
- **Client 测试聚合程序的 JSX 签名。** `tsc -p tsconfig.client.json` 的 Check 阶段为 59.1 s,其中一半以上用于为每个导入 React 的 JSX 文件计算 `@types/react` 的 module specifier。TypeScript 的 `computeModuleSpecifiers` 遍历该声明文件的每个符号链接位置,并在每个位置扫描全部 491 条 tsconfig `paths`。pnpm 为每个自行声明 `@types/react` 的 workspace 包创建一个 `node_modules/@types/react` 链接;66 个 manifest 声明了它,Client 测试聚合程序因此看到 65 个位置。计数器测得 88 个 JSX 文件 × 2 个模块(`index.d.ts`、`jsx-runtime.d.ts`)× 65 个位置,共 11,457 次 `tryGetModuleNameFromPaths` 调用、5,623,782 次条目扫描,合计 32.6 s。按可见位置数 N 截断的剂量实验在五个截断级别(N 从 1 到 65,Check 从 26.92 s 到 58.40 s)上拟合出 Check ≈ 26.9 + 0.494·N s。
|
||||
- **Host tsdown 阶段中的 Typert。** Host tsdown 阶段约 21.6–22.6 s。Typert 在第一个 `writeBundle` 中同步发现并生成产物,CPU profile 显示它阻塞 tsdown 主线程约 12.4 s,期间所有包构建都在等待。发现阶段占 8.06 s:它解析两个 face 聚合配置所引用的 362 个包 tsconfig(约 3.8 s),每次解析 Host 聚合配置都展开 2,064 条字面 `include`(1.6 s,分析器从不读取这些文件名),并对 322 个注册项做公开导出表面遍历(2.7–3.2 s);最终只有 28 个声明 Typert 导出的包产生 Host 产物。
|
||||
- **Client tsdown 阶段的 manifest 查找。** Client tsdown 阶段约 11.7 s,主线程饱和(空闲仅 7.1%),其中 34.5%(3.95 s)在 `workspaceManifest`:每个包配置按包名 `globSync('packages/*/*/package.json')` 并逐个解析 manifest,直到名字匹配。模块级缓存无效,因为 tsdown 的 native 配置加载器通过 `import-without-cache` 为每个包配置创建新的模块实例;每次 glob 耗时 40–125 ms,且发生在 `tsdown:deps` 的 `neverBundle`/`alwaysBundle` 回调中,阻塞全部构建。
|
||||
|
||||
#5849 已合并 Host fixture 类型收窄与 Typert memoization;本 Note 的基线是其后的 master。
|
||||
|
||||
## 决策
|
||||
|
||||
以下八项提速不改变 `lib/`、`apps/web/dist` 的产物字节,也不改变仓库构建报告的类型诊断集合。唯一的诊断归属变化来自第 4 项:Typert 不校验它不分析的包 tsconfig,这些错误只由 `tsc -b` 报告。命令拆分与并发调度由[构建拆分 Note](2026-10-11-build-artifacts-check-split.zh.md)记录。
|
||||
|
||||
| # | 决策 | 受影响阶段 | 实测中位数 | 产物与诊断 | 有意的行为差异 |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | React 类型包只由根 manifest 声明 | Client 类型检查 | 测试聚合 Check 59.1 → 26.3 s | 程序文件数与诊断不变 | 包 manifest 不列出 React 类型包 |
|
||||
| 2 | Typert 先按 manifest 选择包 | Host tsdown | 22.62 → 19.94 s | 逐字节一致 | 无 |
|
||||
| 3 | Typert 不展开聚合配置的 `include` | Host tsdown | 19.94 → 16.99 s | 逐字节一致;注册清单一致 | 无 |
|
||||
| 4 | Typert 惰性解析包 tsconfig | Host tsdown | 16.99 → 16.25 s(噪声内);Typert 发现 5.9–7.1 → 0.36–0.40 s | 逐字节一致;注册清单一致 | 未分析包的 tsconfig 错误只由 `tsc -b` 报告 |
|
||||
| 5 | Typert 工作区根目录同时要求 `packages/` | Host tsdown | 噪声内 | 逐字节一致 | `package` 模式下双 face 包获得产物 |
|
||||
| 6 | Typert 在 Worker Thread 中生成 | Host tsdown | 15.39 → 10.25 s | 逐字节一致;失败仍使 tsdown 失败 | Host tsdown 最大 RSS 约 +0.6 GiB |
|
||||
| 7 | Typert `transform` 钩子按模块 id 过滤 | Host tsdown | 噪声内;钩子调用 1,566 → 195 | 逐字节一致 | 无 |
|
||||
| 8 | Client tsdown 预设按包目录读取 manifest | Client tsdown | 12.78 → 8.73 s | 逐字节一致;external 判定一致 | 预设第一个参数是包目录 |
|
||||
|
||||
第 5–7 项改变了发布的 `./tsdown` 导出 `typertPlugin()`(`transform` 为 `{ filter, handler }`、`writeBundle` 为异步、工作区根目录必须包含 `packages/`);rolldown 与 tsdown 的使用方接受两种钩子形式,分析器本来就要求 `<root>/packages`,因此此前可用的外部工作区都不会失效。
|
||||
|
||||
### 测量条件
|
||||
|
||||
所有数字取自同一台共享 Linux 主机(256 vCPU、4 TB 内存,运行期间 load average 23–111),Node v24.18.0、TypeScript 6.0.3、tsdown 0.22.2(rolldown 1.1.1)。
|
||||
|
||||
- **Host tsdown:** `node node_modules/tsdown/dist/run.mjs --config-loader native --env.DSH_BUILD_FACE host`,每次运行前删除全部 `lib/typert.*`;各变体交替顺序运行,每组 3–4 个样本。第 2–4 项取自第一轮测量(起点为基线),第 5–7 项取自第二轮测量(起点为第 2–4 项合入后的代码)。产物比较覆盖 `packages/*/*/lib`、`apps/*/lib`、`vendor/*/lib` 顶层的 743 个文件(含 140 个 Typert 产物)的 SHA-256。相同代码的两组中位数相差 0–0.11 s,组内样本跨度最大 2.5 s。
|
||||
- **Client 测试聚合:** references 预先构建后运行 `tsc -p tsconfig.client.json --extendedDiagnostics`,使用全新的 tsBuildInfo 文件;相同代码 6 个样本的 Check 跨度为 3.8 s(约 6%)。
|
||||
- **Client tsdown:** 已构建的树上运行 `node node_modules/tsdown/dist/run.mjs --config-loader native --env.DSH_BUILD_FACE client`。第 8 项的原型 3 轮轮换顺序,基线跨度 1.56 s;最终实现与原实现交替运行,各 3 个样本。产物比较覆盖 `{packages,apps,vendor,native}/**/lib/**`(排除 `*.tsbuildinfo`)的全部文件。
|
||||
|
||||
### 1. React 类型包只由根 manifest 声明
|
||||
|
||||
- **目的:** 把 Client 测试聚合程序看到的 `@types/react` 位置数 N 从 65 降到 1,消除随 N 线性增长的 module specifier 计算。
|
||||
- **改动:** 根 `package.json` 的 `devDependencies` 声明 `@types/react` `~18.3.1` 与 `@types/react-dom` `~18.3.0`,沿用各包原先统一的版本范围;`apps/*`、`packages/*/*` 与 `vendor/*` 的 manifest 在任何依赖区段都不声明它们。TypeScript 从任一包向上查找时都解析到根 `node_modules/@types/react`。runtime `react` 与 `react-dom` 仍按 [Client 依赖声明规则](../../../../packages/client/AGENTS.md#dependency-declaration)由各包声明;只通过 `import type` 引用 React 的 `packages/client/ui-slots` 在 `devDependencies` 中声明 `react`。pnpm `dedupeDirectDeps` 保持关闭。lockfile 的变化集中在 importers 区段(另有一条 integrity 相同的 `papaparse` 条目重排)。
|
||||
- **防回退:** [`verify-package-dependencies`](../../../../scripts/verify-package-dependencies.ts) 在 `hygiene` 与 CI 中要求根 manifest 的 `devDependencies` 声明这两个包,并拒绝 `apps/*`、`packages/*/*`、`vendor/*` 中任一 manifest 在 `dependencies`、`devDependencies`、`optionalDependencies` 或 `peerDependencies` 中声明它们;诊断指明要删除的条目,并链接 Client 依赖声明规则第 8 条。规则只匹配这两个精确包名,其他 `@types/*` 包仍按 DefinitelyTyped 类型提供方处理。依赖区段推导不把这两个包视为 `react`/`react-dom` 的类型提供方:包缺少 `react` 声明时,验证器报告 `react` 缺失,`--fix` 拒绝修复并要求先声明版本范围;`--fix` 也不写回或删除这两个包。[`verify-package-dependencies.spec.ts`](../../../../scripts/verify-package-dependencies.spec.ts) 覆盖各依赖区段的拒绝、根 manifest 缺失声明、被接受的布局,以及类型提供方排除。
|
||||
- **性能:**
|
||||
|
||||
| 测量项 | 各包自声明(N = 65) | 根 manifest 声明(N = 1) |
|
||||
|---|---|---|
|
||||
| Client 测试聚合 Check | 59.08 s(3 个样本的中位数) | 26.28 s(5 个样本的中位数),−56% |
|
||||
| Client 测试聚合 wall | 68–71 s | 33.6–34.9 s |
|
||||
| Client 测试聚合 user CPU | 79–83 s | 44.5–45.3 s |
|
||||
| 76 个 client-only 包项目的 Check 总和 | 34.6 s(2 个样本的中位数) | 26.5 s(2 个样本的中位数),−23% |
|
||||
| 76 个 client-only 包项目的 wall | 55.7 s(2 个样本的中位数) | 44.5 s(2 个样本的中位数) |
|
||||
|
||||
- **无损依据:** 66 个包级链接与根链接指向同一个 realpath(`.pnpm/@[email protected]`),因此程序读取的声明内容相同;程序文件数保持 2,613,`tsc -b --force tsconfig.client.json` 以 0 退出且无诊断。`devDependencies` 不随已发布的包安装,[发布依赖面](2026-08-26-published-dependency-faces.zh.md)中独立 TypeScript 消费者自行安装所引用类型包的结论不变;runtime 解析不读取 `@types/*`。
|
||||
- **取舍:** 包 manifest 不列出自己的 React 类型依赖,所有 workspace 包共用同一个 React 类型版本;某个包需要不同版本时,需要新的 Agent Note 推翻本项。检查只覆盖 `apps/*`、`packages/*/*` 与 `vendor/*`;`website`、`benchmarks`、`native/system` 与 `python/sdk-runtime` 等其他 pnpm 成员不在范围内,目前它们都不声明 React 类型包。
|
||||
|
||||
### 2. Typert 先按 manifest 选择包
|
||||
|
||||
- **目的:** workspace 模式只为声明 Typert 导出的包生成产物,避免对其余候选包做表面遍历。
|
||||
- **改动:** `WorkspaceAnalyzer.discoverPackages(includeManifest?)` 与 `WorkspaceTypertGenerator.discover(faces?, includeManifest?)` 接受可选的 `package.json` 谓词,在表面遍历之前应用;被拒绝的包既不读取源码,也不解析项目配置。`WorkspaceTypertGenerator.generateContributors(faces?)` 以 `hasTypertExport`(`exports` 含 `./typert`、`./client/typert` 或 `./remote`)为谓词。不传谓词的调用方(Cordis 目录、不指定包的 `generate()`)行为不变。
|
||||
- **性能:** Host tsdown 22.62 s → 19.94 s(各 3 个样本的中位数)。
|
||||
- **无损依据:** 谓词就是原先在发现之后应用的同一过滤条件,被它拒绝的包本来就不产生产物;743 个产物文件逐字节一致(Typert generator 自身的 `lib/index.js` 除外,其源码即本项改动)。
|
||||
|
||||
### 3. Typert 不展开聚合配置的 `include`
|
||||
|
||||
- **目的:** 分析器只读取聚合配置的编译选项与 project references,展开 `include` 只为填充从不使用的 `fileNames`。
|
||||
- **改动:** `WorkspaceCaches.aggregate(path)` 返回并缓存 `AggregateConfig { path, options, projectReferences }`。解析自身声明 `references` 的聚合配置时,使用 `readDirectory` 返回空列表的 `ParseConfigHost`;不声明 `references` 的聚合配置照常展开。
|
||||
- **性能:** Host tsdown 19.94 s → 16.99 s(各 3 个样本的中位数)。
|
||||
- **无损依据:** TypeScript 只对既无 `files` 也无 `references` 的配置报告 TS18003(无输入),因此声明 `references` 的聚合配置在不展开时得到相同的诊断、选项与 references。强制读取每个惰性配置后导出的注册清单(408 个注册项,含 `fileNames` 与选项,以及聚合配置的选项和 references)与基线逐字节一致;产物逐字节一致。测试 `reads aggregate options and references without expanding include patterns` 与 `reports an aggregate without references or inputs` 固定这两种配置的行为。
|
||||
|
||||
### 4. Typert 惰性解析包 tsconfig
|
||||
|
||||
- **目的:** 只解析被选中分析的包的 tsconfig,而不是聚合配置引用的全部包。
|
||||
- **改动:** `PackageRegistration.config` 是 `caches.config(configPath)` 的 getter,首次读取时解析。注册清单的顺序、face、包名、根目录与导出子路径不变。
|
||||
- **性能:** Host tsdown 16.99 s → 16.25 s(各 3 个样本的中位数),在 2.5 s 的组内跨度以内。第 2–4 项合计在隔离基准中把 Typert 发现从 5.9–7.1 s 降到 0.36–0.40 s,tsconfig 解析从 362 次降到 28 次,表面遍历从 322 次降到 28 次,Typert 总耗时从 9.7–11.8 s 降到 4.4–4.5 s。
|
||||
- **无损依据:** 注册清单比较与第 3 项相同(逐字节一致),产物逐字节一致。测试 `reads only the project configs of manifests selected for discovery` 固定只解析所选包的配置。
|
||||
- **取舍(非无损):** Typert 不解析它不分析的包的 tsconfig,因此这类 tsconfig 的语法或配置错误不会使 Typert 失败。同一构建中,先于 Host bundle 运行的 Host 包编译 `tsc -b` 与 Client 包编译 `tsc -b` 解析每个被引用的 tsconfig,并拒绝其中的错误;`PackageRegistration.config` 的 JSDoc 与 [Typert generator README](../../../../packages/typert/generator/README.zh.md) 写明这一归属。单独调用 Typert 而不运行 `tsc -b` 的使用方不会得到这些诊断。
|
||||
|
||||
### 5. Typert 工作区根目录同时要求 `packages/`
|
||||
|
||||
- **目的:** 修正工作区根目录的判定。24 个双 face 包带有自己的 `tsconfig.host.json` 叶子配置;只按 `tsconfig.host.json` 向上查找时,每个这样的包都被当作根目录,workspace 模式每次构建为它们各做一次空发现,`package` 模式下双 face 包得不到产物且没有报错。
|
||||
- **改动:** 工作区根目录是打包输出最近的、同时包含 `tsconfig.host.json` 与 `packages/` 的祖先目录。
|
||||
- **性能:** Host tsdown 14.82 s → 15.39 s(各 3 个样本的中位数),在噪声内。
|
||||
- **无损依据:** 仓库根 [`tsdown.config.ts`](../../../../tsdown.config.ts) 只使用 workspace 模式,743 个产物文件逐字节一致。测试 `resolves the workspace root above a dual-face package leaf` 固定该判定。
|
||||
- **行为差异:** `package` 模式的使用方构建双 face 包时得到该包的 Typert 产物;仓库构建不使用 `package` 模式。
|
||||
|
||||
### 6. Typert 在 Worker Thread 中生成
|
||||
|
||||
- **目的:** 让 Typert 分析与 tsdown 主线程上的打包重叠,而不是在第一个 `writeBundle` 中阻塞全部包构建。
|
||||
- **改动:** [`src/worker.ts`](../../../../packages/typert/generator/src/worker.ts) 是 Worker Thread 入口,运行 `generateContributors(faces)` 并回传一条 `artifacts` 或 `failure` 消息。workspace 模式下,本次构建的第一个 `buildStart` 以 `options.cwd` 定位工作区根目录并启动生成;`writeBundle` 是异步的,第一个完成的打包认领写入并等待结果,之后的打包立即返回。分析错误、worker 崩溃或提前退出、格式错误或无法反序列化的响应、worker 无法终止,都以指明工作区与原因的 `TypertWorkerError` 使认领写入的打包失败,从而使整个 tsdown 阶段失败;结果敲定前 worker 已终止,未被认领的结果不会成为未处理的 rejection。构建产物从 `./worker.js` 启动 worker;源码运行(测试)通过 `tsx/esm/api` 加载 `worker.ts`。`package` 模式仍在主线程中生成。
|
||||
- **性能:** Host tsdown 15.39 s → 10.25 s(各 3 个样本的中位数)。最终配置(第 2–7 项)的 profile:主线程 10.1 s,第一个 `buildStart` 出现在 4.18 s(tsdown 配置解析),主线程上的 `writeBundle` 只占 0.37 s;worker 5.0 s(模块加载约 0.4 s、发现 0.28 s、生成 3.88 s,其中 `createProgram` 2.21 s),约在 9.3 s 结束。Host tsdown 的 wall 约为 max(打包, 4.2 s + Typert)。
|
||||
- **无损依据:** 第二轮的 54 次运行全部以 0 退出,每次的 743 个产物文件均与基线一致(generator 自身的 `lib/index.js` 除外)。测试 `writes the artifacts in-process generation produces` 比较 worker 与进程内生成的产物;在临时工作区中把一个包的 `files` 改坏后,tsdown 在 2.4 s 内以 1 退出,报告 `TypertWorkerError` 及 worker 内的 `TypertAnalysisError`,不写入任何产物。各失败路径由 `typertPlugin workspace mode` 测试组覆盖。
|
||||
- **取舍:** worker 堆约 300–420 MiB;最终配置的 Host tsdown 最大 RSS 中位数为 6,296–6,408 MiB,基线为 5,676 MiB。共享主机高负载时 worker 可能落在关键路径上:一次样本为 21.91 s,拥有产物的包构建等待 worker 约 14 s,其后 10 个样本未复现;load average 约 100 时两次运行为 17.86 s 与 14.3 s。worker 在 1,024 MiB 堆上限下也能完成,因此不是 GC 压力;归因于主机争用尚未证实。
|
||||
|
||||
### 7. Typert `transform` 钩子按模块 id 过滤
|
||||
|
||||
- **目的:** 避免每个模块都从 rolldown 进入 JavaScript `transform` 钩子,只为被扩展名检查拒绝。
|
||||
- **改动:** `transform` 声明 `filter: { id: /^[^?]*\.[cm]?tsx?(?:\?|$)/ }`,处理函数仍以同一正则检查 id。
|
||||
- **性能:** 每次 Host 构建的钩子调用从 1,566 次降到 195 次(195 个 TypeScript 模块,其中 4 个含装饰器)。wall 中位数为 11.07 s(3 个样本)与 9.98 s(4 个样本),第 6 项为 10.25 s,相同代码的对照组为 11.07 s 与 9.87 s,差异在噪声内。
|
||||
- **无损依据:** 该正则只含 ASCII 构造,在 rolldown 的 Rust 正则与 JavaScript 中匹配相同的 id 集合;处理函数应用同一检查,因此不读取过滤器的宿主行为相同。产物逐字节一致;测试 `filters transform calls to TypeScript module ids` 固定过滤器。
|
||||
|
||||
### 8. Client tsdown 预设按包目录读取 manifest
|
||||
|
||||
- **目的:** 每个包配置只读取自己的 `package.json`,消除按包名遍历全部 manifest 的 glob。
|
||||
- **改动:** [`packages/client/tsdown.client.ts`](../../../../packages/client/tsdown.client.ts) 的 `clientBundle`、`staticLinked` 与 `clientLibrary` 的第一个参数是包目录(通常为配置文件的 `import.meta.dirname`),预设读取一次 `<packageDir>/package.json`,以其中的 `name` 作为插件 id 与诊断名称;该 manifest 缺失、不可读或缺少 `name` 时,配置加载以指明路径与原因的错误失败。82 个包配置调用这些预设。预设不保留模块级 manifest 缓存;Node 半边的生产依赖 external 列表在首次 resolve 时由该 manifest 计算,并缓存在该配置的闭包中。
|
||||
- **性能:** Client tsdown 从 12.78 s 降到 8.73 s(各 3 个样本的中位数),−4.05 s(−31.7%),两组样本区间不重叠;user CPU 从 26.1 s 降到 25.2 s,sys 从 5.9 s 降到 4.9 s,因此收益主要来自解除主线程阻塞,而不是减少 CPU 总量。原型(一次 glob 建立按包名索引)为 11.72 s → 7.46 s(−36%)。Host tsdown 中只有 5 个 `hostPhase` 包经过该查找,一对样本为 11.92 s → 10.71 s,置信度低。
|
||||
- **无损依据:** 11 次运行的 11,566 个 lib 文件逐字节一致。82 个调用处改为传入包目录后,没有一个解析出的包名与原先手写的包名不同。进程内比较 82 个配置 × 3 个 face × 1,600 个 specifier 的 external 判定,结果与原实现完全一致;负对照(多加一个 external)使 75 个判定不同。`client-bundle-purity.spec.ts` 覆盖这些 manifest 错误。
|
||||
- **行为差异:** 预设的调用约定从包名改为包目录。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
- **开启 pnpm `dedupeDirectDeps`(第 1 项):** 作用于所有直接依赖,改变每个 workspace 包的链接布局,副作用是全局的。
|
||||
- **给 TypeScript 打补丁(第 1 项):** 上游式补丁(预先计算候选扩展名并缓存规范化的 `paths` 模式)使 Check 降低 31.9 s,与提升相当;在已提升的树上不再带来收益(refs Check 25.76 s 对 26.47 s),且需随每次 TypeScript 升级维护。
|
||||
- **在 tsconfig `paths` 中为 `react` 加别名(第 1 项):** tsx、vitest 与 vite 也读取 `paths`,别名会改变它们对 runtime `react` 的解析。
|
||||
- **把 `paths` 收窄为各 face 的 reference closure(第 1 项):** 保留通配键的 328 条版本 Check 为 55.73 s 对 59.08 s(1 个样本),在噪声内;去掉通配键的版本产生 18 个诊断。
|
||||
- **拆分 Client 测试聚合程序(第 1 项):** 每个子程序都必须注入 242 个含模块增强的文件才能通过检查,可见位置数仍为 33–49;拆成 2、4、8 组时 Check 总和基本不变,user CPU 分别增加 9%、30%、57%,只降低 wall,且 4 组与 8 组各出现 1–2 个诊断。
|
||||
- **缓存解析结果并把发现队列改为 O(1)(Typert):** 第 2–4 项之后只剩 28 次表面遍历,约 0.18 s,低于噪声。
|
||||
- **把 Typert 移到独立进程或独立构建步骤(Typert):** 需要先编译 generator 自身,并为 `@deepseek-ai/dsh-client-file-upload/types` 补充 `paths` 条目(否则它读取 `tsc` 正在写入的 `lib/types/types.d.ts`);第 6 项之后 worker 已与打包重叠,剩余收益不超过约 1 s。
|
||||
- **按代码内容过滤 `transform`(第 7 项):** rolldown 以 Rust 正则语义解释 `^` 与 `\s`,在孤立 CR、U+2028 与 BOM 上与 JavaScript 不同,可能漏掉含装饰器的模块。
|
||||
- **在 `globalThis` 上建立进程级 manifest 索引(第 8 项):** 原型收益与第 8 项相当,但它是没有 owner 的进程全局状态,违反[包规则](../../../../packages/AGENTS.md)中的共享状态归属要求。
|
||||
- **关闭 tsdown 的 gzip 报告(第 8 项):** `--no-report` 使 Client tsdown 降低 0.95 s,在 1.56 s 的基线跨度内。
|
||||
- **为 DSH 的 `resolveId`/`load` 钩子加 rolldown 过滤器(第 8 项):** 降低 1.16 s,在噪声内。
|
||||
- **按模块 id 缓存输入隔离检查(第 8 项):** 降低 0.93 s,在噪声内;叠加在 manifest 索引之上只再降低 0.18 s。
|
||||
|
||||
## 后果
|
||||
|
||||
Client 类型检查的 CPU 与 wall 时间、Host tsdown 与 Client tsdown 的 wall 时间都明显下降,且不改变 runtime 解析、发布安装语义或任何产物字节。串行构建的各段中(master → 本分支,各一个样本),Host tsdown 从 22.5 s 降到 11.7 s,Client 包编译从 55.6 s 降到 48.7 s,Client 聚合检查从 68.2 s 降到 35.9 s,Client tsdown 从 13.9 s 降到 10.5 s;串行各段合计在 master 上为 308.5 s,在本分支上为 254.9 s(见[构建拆分 Note](2026-10-11-build-artifacts-check-split.zh.md))。Host tsdown 位于 `build` 与 `build:artifacts` 的关键路径上,Client tsdown 位于 `build:artifacts` 的关键路径上,Client 聚合检查是 `build` 关键路径的最后一步。
|
||||
|
||||
代价:所有 workspace 包共用一个 React 类型版本;Typert 不校验它不分析的包 tsconfig,这些错误由 `tsc -b` 负责;Host tsdown 的最大 RSS 约增加 0.6 GiB,共享主机高负载时 worker 可能成为 Host tsdown 的关键路径。
|
||||
|
||||
重新评估条件:迁移到 TypeScript 7 时重新测量第 1 项,若位置数不再影响 Check 时间,可以重新评估包级声明。tsdown 的配置加载本身每次 Client 构建仍占 1.8–2.3 s(每个配置一个无缓存的模块实例),需要上游改动。第一个 `buildStart` 在 Host 构建开始约 4.2 s 后才出现;在插件构造时以显式根目录启动 worker 可以进一步提前 Typert。
|
||||
|
||||
验证:Typert generator 的 `tsdown-plugin.spec.ts`、`tsdown-plugin-worker.spec.ts` 与 `type-model.spec.ts` 固定第 2–7 项的行为;generator 测试 209 个通过、28 个跳过,`tsc -b tsconfig.host.json` 以 0 退出。仓库覆盖率 gate 不包含 `packages/typert/*/src/**`,在临时覆盖率配置下 `tsdown-plugin.ts` 的 statements、branches、functions 与 lines 均为 100%。五个负对照(旧根目录规则、立即解析包配置、展开聚合配置、不认领写入、跳过 worker 终止)各自使守护它的测试失败;真实 tsdown 失败路径在 2.4 s 内以 1 退出并报告 `TypertWorkerError`。第 1 项:`verify-package-dependencies` 以 0 退出(74 个包),其 spec 47/47 通过,`verify-client-packages` 以 0 退出,`tsc -b tsconfig.client.json` 无诊断,Client 聚合 Check 为 26.48 s。第 8 项:72 个相关 spec 全部通过。
|
||||
@@ -37,6 +37,7 @@ When the outgoing change adds or changes a resource-owning or asynchronous test,
|
||||
- **Expected-output placement:** a test whose selected recorded Session generation is replay input and expected persisted output belongs under top-level `snapshots/`, with `snapshot.yml` naming its shipped `dsh` profile and composition/header pin. Canonical parent files are `session[.vN].jsonl`, children are `session.<ordinal>[.vN].jsonl`, and the harness selects the highest generation per role. ARIA, geometry, generator, CLI, and unit expectations without that Session round trip stay beside their owning test under `tests/expected/`; do not place them in `snapshots/` or give them a `*.snapshot.ts` owner. Use the owning `test:expected`, `test:web`, or `test` lane.
|
||||
- **Profile and configuration placement:** cross-package behavior of a shipped `dsh` profile belongs under `apps/cli/tests/profiles/`; a package-specific Loader composition belongs under that package's `tests/fixtures/`. User-facing optional overlays live under `apps/cli/config/examples/` and pair with a guide under `docs/user/`.
|
||||
- **Package manifests, public exports, build configuration, worker/bin entries, or built runtime paths:** run `pnpm run build`, the relevant hygiene checks, and the owning built-artifact smoke.
|
||||
- **Repository test and script types:** `pnpm run build:artifacts` checks package projects only. Before claiming completion of TypeScript changes, run `pnpm run build:check` after the latest `build:artifacts`, or run `pnpm run build`; `build:check` reads the existing `build:artifacts` output and never rebuilds it.
|
||||
- **Real provider or agent behavior:** run the relevant `pnpm run test:e2e` target when credentials are available; never print secrets.
|
||||
|
||||
Do not manually repeat a passing check merely because commit or push follows. In particular, do not run typecheck immediately before pushing solely to duplicate the pre-push hook.
|
||||
|
||||
@@ -266,7 +266,7 @@ jobs:
|
||||
DSH_BUILD_CLIENT_PROFILE: official
|
||||
run: >-
|
||||
pnpm exec tsx scripts/build-exe-for-python-sdk.ts --targets=${{ matrix.target }}
|
||||
${{ inputs.ci && github.event_name == 'pull_request' && '--artifacts-only' || '' }}
|
||||
${{ inputs.ci && github.event_name == 'pull_request' && '--build-artifacts' || '' }}
|
||||
|
||||
- name: Resolve platform outputs (POSIX)
|
||||
id: runtime-posix
|
||||
|
||||
@@ -71,7 +71,7 @@ jobs:
|
||||
# build:preview packs the image through the packer's installed bin
|
||||
# (lib/bin.js), so neither half exists before the artifact build runs.
|
||||
- name: Build workspace
|
||||
run: pnpm run build --artifacts-only
|
||||
run: pnpm run build:artifacts
|
||||
|
||||
- name: Build the preview page and pack the VFS image
|
||||
env:
|
||||
|
||||
@@ -107,7 +107,7 @@ jobs:
|
||||
# the built artifact under plain Node, resolving plugins through real package
|
||||
# exports — the shape a real consumer runs. That requires a prior build.
|
||||
- name: Build (lib for the e2e example bins)
|
||||
run: ${{ github.event_name == 'pull_request' && 'pnpm run build --artifacts-only --profile official' || 'pnpm run build:official' }}
|
||||
run: ${{ github.event_name == 'pull_request' && 'pnpm run build:artifacts --profile official' || 'pnpm run build:official' }}
|
||||
|
||||
- name: Prepare Office runtime
|
||||
run: pnpm run prepare:primary-runtime --target linux-x64 --output .cache/e2e-office
|
||||
|
||||
@@ -146,7 +146,7 @@ jobs:
|
||||
run: pnpm run release:verify --family dsh
|
||||
|
||||
- name: Build
|
||||
run: ${{ github.event_name == 'pull_request' && 'pnpm run build --artifacts-only --profile official' || 'pnpm run build:official' }}
|
||||
run: ${{ github.event_name == 'pull_request' && 'pnpm run build:artifacts --profile official' || 'pnpm run build:official' }}
|
||||
|
||||
# Concurrency here is rehearsal-only: the credentialed publish workflows
|
||||
# invoke release:pack without the flag and keep the strictly serial path.
|
||||
|
||||
@@ -92,7 +92,9 @@ pnpm run test:snapshot:record # re-record expected outputs (needs key)
|
||||
pnpm run typecheck
|
||||
pnpm run lint
|
||||
pnpm run duplication # cross-file TypeScript clone detection
|
||||
pnpm run build # tsc emits lib/types, tsdown bundles runtime
|
||||
pnpm run build # build:artifacts + build:check, concurrently
|
||||
pnpm run build:artifacts # artifacts: tsc emits lib/types, tsdown bundles runtime, Web
|
||||
pnpm run build:check # typecheck aggregate tests/scripts against current build:artifacts output
|
||||
pnpm run hygiene # publint + workspace/package/dependency checks + NodeNext consumer check
|
||||
pnpm run check:windows-wine # ONLY when diagnosing a known Windows failure (needs wine); CI owns this signal
|
||||
pnpm run doc-sync # documentation gates (scripts/run-gates.ts)
|
||||
@@ -100,7 +102,7 @@ pnpm run test:docs # quick documentation checks (no build; doc-quick aggreg
|
||||
pnpm run website:build # VitePress build (doubles as dead-link check)
|
||||
pnpm dsh --profile headless "task" # run one task from source (needs DEEPSEEK_API_KEY)
|
||||
pnpm run demo:ptc -- "task" # headless PTC mode run (needs key)
|
||||
pnpm run dev:web | dev:desktop # build, then launch; Web also rebuilds client bundles on edits. start:web | start:desktop skip the build
|
||||
pnpm run dev:web | dev:desktop # build:artifacts, then launch; Web also rebuilds client bundles on edits. start:web | start:desktop skip the build
|
||||
make web|dev-web|desktop|dev-desktop|build # the same commands; ARGS='--no-open' forwards options
|
||||
```
|
||||
|
||||
@@ -115,6 +117,7 @@ Before pushing, follow [dsh-pre-push-checks](.agents/skills/dsh-pre-push-checks/
|
||||
- Match evidence to the surface: focused behavior tests, model/user-output snapshots, `doc-sync` for docs, built smokes for published paths, and real-API e2e for providers.
|
||||
- Never default to the full suite or repeat a passing check for commit or push. CI owns exhaustive coverage and the platform matrix; rehearse all locally only by explicit request, for CI diagnosis, or for an irreducibly repository-wide change.
|
||||
- `test:coverage`, not `test`, is the CI coverage gate ([why](docs/testing.md)).
|
||||
- Develop with `build:artifacts`; before claiming completion, run `build:check` after the latest `build:artifacts`, or `build` ([layout](docs/development.md#typescript-project-layout)).
|
||||
- **Web browser automation and GIF recording:** launch with `pnpm dsh web --patch apps/web/tests/pin-browse-picker.overlay.yml` to use the [in-page directory picker](apps/web/tests/pin-browse-picker.overlay.yml); omit this override only when testing native picker behavior explicitly.
|
||||
|
||||
## Secrets / .env
|
||||
|
||||
@@ -75,8 +75,6 @@
|
||||
"@deepseek-ai/dsh-client-ui-primitives": "workspace:*",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"@types/react": "~18.3.1",
|
||||
"@types/react-dom": "~18.3.0",
|
||||
"vite": "^6.4.3",
|
||||
"@deepseek-ai/dsh-client-ui-theme": "workspace:*",
|
||||
"@deepseek-ai/dsh-client-shortcuts": "workspace:*",
|
||||
|
||||
@@ -93,7 +93,7 @@ async function launchElectron(): Promise<void> {
|
||||
async function main(): Promise<void> {
|
||||
const { values } = parseArgs({ options: { 'skip-build': { type: 'boolean', default: false } } })
|
||||
if (!values['skip-build']) {
|
||||
await runPackageScript('build', REPOSITORY_ROOT)
|
||||
await runPackageScript('build:artifacts', REPOSITORY_ROOT)
|
||||
await runPackageScript('build', APP_ROOT)
|
||||
}
|
||||
for (const path of [
|
||||
|
||||
@@ -49,8 +49,6 @@
|
||||
"@deepseek-ai/dsh-session-projection-cache": "workspace:*",
|
||||
"@deepseek-ai/dsh-util-values": "workspace:*",
|
||||
"@types/node": "^22.20.1",
|
||||
"@types/react": "~18.3.1",
|
||||
"@types/react-dom": "~18.3.0",
|
||||
"@types/ws": "8.18.1",
|
||||
"@vitejs/plugin-react": "^4.7.0",
|
||||
"http-server": "^14.1.1",
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
en: e29f1d7026291075
|
||||
zh: 15e361e797d44fce
|
||||
/api-gateway/strict-generation-pipeline:
|
||||
en: cb8da81a92acfa52
|
||||
zh: be5f3c268f8571df
|
||||
en: 9c4a98eb0539ec7f
|
||||
zh: ad2a145abd805fdd
|
||||
/api-gateway/runtime-invocation:
|
||||
en: 2a2fed340aaa926c
|
||||
zh: 46bb1f500c870a76
|
||||
|
||||
+1
-1
@@ -96,7 +96,7 @@ The API Gateway package owns the Host dispatcher and Client Remote endpoint as p
|
||||
|
||||
## Strict generation pipeline
|
||||
|
||||
The root build runs `build:lib:host`, `build:lib:client`, and `build:web` in order. The Host lib phase first runs `tsc -b tsconfig.host.json`, then `tsdown --env.DSH_BUILD_FACE host`; the normal Host Project Reference graph compiles the Typert generator, which runs during this tsdown pass with the Host aggregate as its only `ts.Program` seed. The Client lib phase then runs `tsc -b tsconfig.client.json` and `tsdown --env.DSH_BUILD_FACE client`, consuming the newly generated Remote Client declarations and runtime contributions without starting Typert again.
|
||||
The root build compiles and bundles the Host face, then the Client face, then builds the Web shell ([step order](development.md#typescript-project-layout)). The Host phase first compiles the Host package references, then runs `tsdown --env.DSH_BUILD_FACE host`; the normal Host Project Reference graph compiles the Typert generator, which runs during this tsdown pass with the Host aggregate as its only `ts.Program` seed. The Client phase then compiles the Client package references and runs `tsdown --env.DSH_BUILD_FACE client`, consuming the newly generated Remote Client declarations and runtime contributions without starting Typert again.
|
||||
|
||||
Both tsdown passes match `vendor/*`, `packages/*/*`, and `apps/cli`, the Host pass also `apps/desktop-host` ([build order](development.md#typescript-project-layout)), and both bundle only JavaScript emitted to `lib/types` by the corresponding tsc phase. The root config does not scan Client artifacts, classify package names, or pass a maintained filter to tsdown; package-local configs return entries for the current phase based on `DSH_BUILD_FACE`. An ordinary Client plugin produces both its Node loader entry and browser bundle during the Client phase.
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ API Gateway 包同时拥有 Host dispatcher 与 Client Remote endpoint 两个对
|
||||
|
||||
## 严格生成流水线
|
||||
|
||||
根构建依次执行 `build:lib:host`、`build:lib:client` 与 `build:web`。Host lib 阶段先运行 `tsc -b tsconfig.host.json`,再运行 `tsdown --env.DSH_BUILD_FACE host`;Typert generator 由正常 Host Project Reference 图编译,并在这次 tsdown 中以 Host aggregate 为唯一 `ts.Program` 种子运行。Client lib 阶段随后运行 `tsc -b tsconfig.client.json` 与 `tsdown --env.DSH_BUILD_FACE client`,使用刚生成的 Remote Client 声明和运行时贡献,但不再次启动 Typert。
|
||||
根构建依次编译并打包 Host face、Client face,再构建 Web 壳层([步骤顺序](development.zh.md#typescript-project-layout))。Host 阶段先编译 Host 包 reference,再运行 `tsdown --env.DSH_BUILD_FACE host`;Typert generator 由正常 Host Project Reference 图编译,并在这次 tsdown 中以 Host aggregate 为唯一 `ts.Program` 种子运行。Client 阶段随后编译 Client 包 reference 并运行 `tsdown --env.DSH_BUILD_FACE client`,使用刚生成的 Remote Client 声明和运行时贡献,但不再次启动 Typert。
|
||||
|
||||
两次 tsdown 都匹配 `vendor/*`、`packages/*/*` 与 `apps/cli`,Host 阶段另外匹配 `apps/desktop-host`([构建顺序](development.zh.md#typescript-project-layout)),且都只打包 `lib/types` 中由对应 tsc 阶段发射的 JavaScript。根配置不扫描 Client 产物、不按包名分类,也不向 tsdown 传维护式 filter;各包的本地配置根据 `DSH_BUILD_FACE` 返回当前阶段的入口。普通 Client 插件在 Client 阶段一起生成 Node loader 入口与 browser bundle。
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
en: 3221d1076554001f
|
||||
zh: 29f166be6a5c04d9
|
||||
/development-guide/contributor-reference/typescript-project-layout:
|
||||
en: e3327c5492a593cb
|
||||
zh: d43a5edcb11523ae
|
||||
en: b137fdddcc8ce2ba
|
||||
zh: d9c078ba11734a21
|
||||
/development-guide/contributor-reference/environment-variables:
|
||||
en: dba2c72ee6ffa930
|
||||
zh: b65c27fbb9129ec8
|
||||
@@ -39,8 +39,8 @@
|
||||
en: 7ce6264a6a06dc4a
|
||||
zh: e12b684f2bef942b
|
||||
/development-guide/contributor-reference/application-commands:
|
||||
en: 28a029c8768f0880
|
||||
zh: 67c17256e046624e
|
||||
en: 98828fc2b972bf39
|
||||
zh: 1dc889a3d766bf35
|
||||
/development-guide/contributor-reference/todo-markers:
|
||||
en: 85b38f78d87fc88f
|
||||
zh: 00d5eb625e7dfb61
|
||||
|
||||
+21
-7
@@ -74,24 +74,38 @@ Host and Client stay two aggregate programs because both sides declaration-merge
|
||||
|
||||
Six packages split Host and Client tsconfigs: `api/remotes`, `api/gateway`, `api/session-controller`, `api/workspace-controller`, `client/connection`, and `session-query/session-log-export`. `api/remotes`' Host entry participates in the Host Typert graph while its Client entry imports generated `/remote` declarations; `session-log-export` keeps Node archive production out of its browser controller. Each split package-root `tsconfig.json` is therefore only a solution, and the two aggregates and direct consumers reference `tsconfig.host.json` or `tsconfig.client.json` respectively. The workspace `constraints` gate walks the reachable Project Reference graph and checks each referencing project's own compiler face: a single-config target remains valid from either face, while a split target must name the matching leaf rather than its solution root or opposite leaf; it discovers split packages from the presence of both leaf configs, so a new split joins the gate automatically. The [`api-remotes` README](../packages/api/remotes/README.md) and [`session-log-export` README](../packages/session-query/session-log-export/README.md) explain their splits.
|
||||
|
||||
The root build follows the generated dependency order:
|
||||
`pnpm run build:artifacts` emits every artifact in the generated dependency order, checking and compiling only the package references of each compiler face:
|
||||
|
||||
```sh
|
||||
tsc -b tsconfig.host.json
|
||||
pnpm run build:native-system
|
||||
tsc -b <tsconfig.host.json references>
|
||||
tsdown --env.DSH_BUILD_FACE host
|
||||
pnpm --filter @deepseek-ai/dsh-desktop run bundle
|
||||
tsc -b tsconfig.client.json
|
||||
tsc -b <tsconfig.client.json references>
|
||||
tsdown --env.DSH_BUILD_FACE client
|
||||
pnpm run build:web
|
||||
```
|
||||
|
||||
`pnpm run build --artifacts-only` emits the same project and application artifacts while checking and compiling only the existing package references of each compiler face. Required Linux and Windows normal builds and public `typecheck` also check the repository-wide test and script programs. CI benchmark preparation additionally uses `--noCheck` for its disposable library outputs, relying on those required builds for diagnostics; other artifact builders keep package checking ([rationale](../.agents/notes/implemented/process/2026-10-05-pr-artifact-typechecks.md)).
|
||||
`pnpm run build:check` runs only `tsc -p tsconfig.host.json` and `tsc -p tsconfig.client.json` concurrently. Project mode checks each aggregate's repository-wide test and script program against the package declarations already on disk and never compiles a reference, so `build:check` neither detects nor rebuilds missing or stale `build:artifacts` output; missing output surfaces as compiler errors. `pnpm run build` runs both step sets as one [scheduled](../scripts/run-gates.ts) dependency graph: each aggregate check starts once its own face's package compilation finishes and overlaps the remaining artifact steps. In all three commands `DSH_GATE_CONCURRENCY` bounds concurrent steps (`1` runs them in dependency order) and `DSH_GATE_FAIL_FAST=1` stops the remaining steps at the first failure; otherwise a failure skips only its dependents, so independent errors are all reported. A failed step prints its own output, and any failure exits non-zero without writing the client build record.
|
||||
|
||||
Choose the command by what the change needs:
|
||||
|
||||
| Situation | Command |
|
||||
|---|---|
|
||||
| Iterating on package sources and needing runnable artifacts | `pnpm run build:artifacts`; `dev:web` and `dev:desktop` run it before launching |
|
||||
| Type-checking tests, scripts, benchmarks, or website code | `pnpm run build:check` after the latest `build:artifacts`; rerun `build:artifacts` first when package sources changed |
|
||||
| Before declaring TypeScript work complete | `pnpm run build:check` after the latest `build:artifacts`, or `pnpm run build` |
|
||||
| Complete verification, as the required CI build lanes run it | `pnpm run build` |
|
||||
| Type checks without the Web and Desktop artifacts | `pnpm run typecheck` |
|
||||
| Lint or documentation code-block checks | `pnpm run lint` or `pnpm run doc-typecheck`; each prepares `host-libraries` itself |
|
||||
|
||||
PR artifact builders run `build:artifacts`; required Linux and Windows normal builds and public `typecheck` also check the repository-wide test and script programs. CI benchmark preparation additionally uses `--noCheck` for its disposable library outputs, relying on those required builds for diagnostics; other artifact builders keep package checking ([rationale](../.agents/notes/implemented/process/2026-10-05-pr-artifact-typechecks.md)).
|
||||
|
||||
Both tsdown passes match `vendor/*`, `packages/*/*`, and `apps/cli`; the Host pass also matches `apps/desktop-host`. They neither scan build artifacts to discover Client packages nor maintain a Host/Client package filter list. Package-local tsdown configs select entries for the current phase through `DSH_BUILD_FACE`: an ordinary Client plugin produces both its Node loader and browser bundle during the Client phase; `api-remotes` uses `hostPhase: true` to produce its Host entry early and only its browser bundle during the Client phase. Tsdown consumes only the JavaScript emitted to `lib/types` by the preceding tsc phase. Tsdown builds the matched workspace members concurrently, so `apps/desktop`, whose main bundle inlines workspace devDependencies from their `lib/` output, bundles in its own step after the Host pass ([Desktop README](../apps/desktop/README.md#bundled-workspace-dependencies)).
|
||||
|
||||
Typert runs only during Host tsdown, seeded by `tsconfig.host.json`. It analyzes Host types and generates both Host reflection artifacts and the Host-for-Client Remote projection; Client tsdown does not start Typert. Consequently, `pnpm run typecheck` runs the complete Host lib phase before Client tsc, while `pnpm run build` continues through Client tsdown and the Web build.
|
||||
Typert runs only during Host tsdown, seeded by `tsconfig.host.json`. It analyzes Host types and generates both Host reflection artifacts and the Host-for-Client Remote projection; Client tsdown does not start Typert. Consequently, Client package compilation always follows the Host bundle, and `pnpm run typecheck` runs the complete Host lib phase, including the Host aggregate check, before Client tsc. `lint`, `lint:fix`, and `doc-typecheck` read the generated declarations but not the Host aggregate check, so they prepare only Host package compilation and the Host bundle (`compile-referenced-projects.ts host-libraries`).
|
||||
|
||||
`pnpm run build` embeds the root package version, the seven-character source commit, and a dirty marker when Git reports local changes; it also inherits other caller-supplied `DSH_CLIENT_*` values. `pnpm run build:official` is the cross-platform local equivalent of the CI and release artifact build and omits the local dirty marker. Each successful complete build writes a gitignored record that binds the exact public values to the Vite output and dynamic client bundles; release packing and built Web tests reject a missing record or artifacts changed by a later partial build. `pnpm run dev:web` runs that complete build first (`--skip-build` reuses an existing artifact tree instead), then samples the current version and Git state once and shares that environment across every watcher stage for the session; it does not validate the complete-build record because the watcher stages rewrite its recorded artifacts.
|
||||
`pnpm run build` and `build:artifacts` embed the root package version, the seven-character source commit, and a dirty marker when Git reports local changes; they also inherit other caller-supplied `DSH_CLIENT_*` values. `pnpm run build:official` is the cross-platform local equivalent of the CI and release artifact build and omits the local dirty marker. Each successful `build` or `build:artifacts` writes a gitignored record that binds the exact public values to the Vite output and dynamic client bundles; release packing and built Web tests reject a missing record or artifacts changed by a later partial build. `pnpm run dev:web` runs `build:artifacts` first (`--skip-build` reuses an existing artifact tree instead), then samples the current version and Git state once and shares that environment across every watcher stage for the session; it does not validate the complete-build record because the watcher stages rewrite its recorded artifacts.
|
||||
|
||||
Static analysis and tests resolve workspace imports through the base `paths` map to `src` and must pass on a clean tree; gates that consume built `lib/` output declare that dependency explicitly. Generated Host-for-Client Remote declarations are the deliberate exception: the public `typecheck`, `lint`, and `doc-typecheck` commands generate them first, while internal `*:contracts-ready` scripts assume that an invoking public command or scheduler gate already depends on the Typert contract-generation pass or the complete build. See the [Typert Remote note](../.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md) for the gate-preparation contract.
|
||||
|
||||
@@ -164,7 +178,7 @@ pnpm run demo:ptc -- "summarize this workspace"
|
||||
|
||||
### Application commands
|
||||
|
||||
Web and Desktop share one command pair. `start:*` launches the artifacts of a prior `pnpm run build`; `dev:*` runs that build first and then launches. Web additionally keeps client bundles rebuilt on source edits, because its Host runs from source while the browser loads built bundles:
|
||||
Web and Desktop share one command pair. `start:*` launches the artifacts of a prior `pnpm run build` or `build:artifacts`; `dev:*` runs `build:artifacts` first and then launches. Web additionally keeps client bundles rebuilt on source edits, because its Host runs from source while the browser loads built bundles:
|
||||
|
||||
```sh
|
||||
pnpm run start:web # serve built Web artifacts through the source launcher (the same launch as pnpm dsh web)
|
||||
|
||||
+21
-7
@@ -78,24 +78,38 @@ Host 与 Client 保持两个 aggregate program,是因为两侧在相同键下
|
||||
|
||||
拆分 Host/Client tsconfig 的包有六个:`api/remotes`、`api/gateway`、`api/session-controller`、`api/workspace-controller`、`client/connection` 与 `session-query/session-log-export`。`api/remotes` 的 Host 入口进入 Host Typert 图,而 Client 入口导入生成的 `/remote` 声明;`session-log-export` 则让 Node archive 生产代码不进入浏览器 controller。每个拆分包根 `tsconfig.json` 因此只作为 solution,两个 aggregate 和直接消费方分别引用 `tsconfig.host.json` 或 `tsconfig.client.json`。workspace `constraints` 门禁遍历可达的 Project Reference 图,并按各引用 project 自身的 compiler face 检查:只有单一配置的目标可由任一 face 引用,拆分配置的目标则必须引用匹配的 leaf,不得引用 solution 根或另一侧 leaf;该门禁按「两个 leaf 配置同时存在」自动发现拆分包,所以新拆分的包会自动纳入管辖。[`api-remotes` README](../packages/api/remotes/README.zh.md) 与 [`session-log-export` README](../packages/session-query/session-log-export/README.zh.md)分别说明其拆分。
|
||||
|
||||
根构建按生成依赖排序:
|
||||
`pnpm run build:artifacts` 按生成依赖顺序产出全部产物,只检查并编译各 compiler face 的包 reference:
|
||||
|
||||
```sh
|
||||
tsc -b tsconfig.host.json
|
||||
pnpm run build:native-system
|
||||
tsc -b <tsconfig.host.json references>
|
||||
tsdown --env.DSH_BUILD_FACE host
|
||||
pnpm --filter @deepseek-ai/dsh-desktop run bundle
|
||||
tsc -b tsconfig.client.json
|
||||
tsc -b <tsconfig.client.json references>
|
||||
tsdown --env.DSH_BUILD_FACE client
|
||||
pnpm run build:web
|
||||
```
|
||||
|
||||
`pnpm run build --artifacts-only` 产出相同的 project 和应用产物,但只检查并编译各 compiler face 的现有包 reference。必需的 Linux 与 Windows 普通构建以及公开 `typecheck` 还检查仓库级测试和脚本 program。CI benchmark 准备阶段还对其临时 library 输出使用 `--noCheck`,由这些必需构建负责诊断;其他产物 builder 保留包检查([理由](../.agents/notes/implemented/process/2026-10-05-pr-artifact-typechecks.zh.md))。
|
||||
`pnpm run build:check` 只并发运行 `tsc -p tsconfig.host.json` 与 `tsc -p tsconfig.client.json`。project mode 依据磁盘上已有的包声明检查各 aggregate 的仓库级测试和脚本 program,从不编译 reference,因此 `build:check` 既不检测也不重建缺失或过期的 `build:artifacts` 产物;产物缺失时表现为编译器错误。`pnpm run build` 把两组步骤作为一张[调度](../scripts/run-gates.ts)依赖图运行:每个 aggregate 检查在本 face 的包编译完成后立即开始,与其余产物步骤重叠执行。三个命令中,`DSH_GATE_CONCURRENCY` 限制并发步骤数(`1` 按依赖顺序执行),`DSH_GATE_FAIL_FAST=1` 在第一个失败时停止其余步骤;否则失败只跳过其依赖方,因此会报告所有相互独立的错误。失败步骤打印自身输出,任一失败都以非零状态退出且不写 client build record。
|
||||
|
||||
按改动需要选择命令:
|
||||
|
||||
| 场景 | 命令 |
|
||||
|---|---|
|
||||
| 迭代包源码并需要可运行的产物 | `pnpm run build:artifacts`;`dev:web` 与 `dev:desktop` 启动前会先执行它 |
|
||||
| 检查测试、脚本、benchmarks 或 website 代码的类型 | 在最近一次 `build:artifacts` 之后运行 `pnpm run build:check`;包源码有改动时先重新运行 `build:artifacts` |
|
||||
| 声明 TypeScript 工作完成之前 | 在最近一次 `build:artifacts` 之后运行 `pnpm run build:check`,或运行 `pnpm run build` |
|
||||
| 完整验证(与 CI 必需的构建 lane 相同) | `pnpm run build` |
|
||||
| 只做类型检查、不需要 Web 与 Desktop 产物 | `pnpm run typecheck` |
|
||||
| lint 或文档代码块检查 | `pnpm run lint` 或 `pnpm run doc-typecheck`;它们各自先准备 `host-libraries` |
|
||||
|
||||
PR 产物 builder 运行 `build:artifacts`;必需的 Linux 与 Windows 普通构建以及公开 `typecheck` 还检查仓库级测试和脚本 program。CI benchmark 准备阶段还对其临时 library 输出使用 `--noCheck`,由这些必需构建负责诊断;其他产物 builder 保留包检查([理由](../.agents/notes/implemented/process/2026-10-05-pr-artifact-typechecks.zh.md))。
|
||||
|
||||
两次 tsdown 都匹配 `vendor/*`、`packages/*/*` 与 `apps/cli`,Host 阶段另外匹配 `apps/desktop-host`;两者都不扫描构建产物来发现 Client 包,也不维护 Host/Client 包过滤表。包内 tsdown 配置根据 `DSH_BUILD_FACE` 决定当前阶段的入口:普通 Client 插件在 Client 阶段同时生成 Node loader 与 browser bundle;`api-remotes` 通过 `hostPhase: true` 提前生成 Host 入口,再在 Client 阶段只生成 browser bundle。tsdown 只消费 `lib/types` 中由前置 tsc 发射的 JavaScript。tsdown 并发构建匹配到的 workspace 成员;`apps/desktop` 的主进程 bundle 需要从工作区 devDependencies 的 `lib/` 产物内联这些包,因此在 Host 阶段之后单独一步打 bundle([Desktop README](../apps/desktop/README.zh.md#bundled-workspace-dependencies))。
|
||||
|
||||
Typert 只在 Host tsdown 中以 `tsconfig.host.json` 为种子运行。它分析 Host 类型并生成 Host 反射产物及 Host-for-Client Remote 投影;Client tsdown 不启动 Typert。`pnpm run typecheck` 因此先执行完整 Host lib 阶段,再运行 Client tsc;`pnpm run build` 继续执行 Client tsdown 和 Web 构建。
|
||||
Typert 只在 Host tsdown 中以 `tsconfig.host.json` 为种子运行。它分析 Host 类型并生成 Host 反射产物及 Host-for-Client Remote 投影;Client tsdown 不启动 Typert。因此 Client 包编译总在 Host bundle 之后;`pnpm run typecheck` 先执行包含 Host aggregate 检查的完整 Host lib 阶段,再运行 Client tsc。`lint`、`lint:fix` 与 `doc-typecheck` 读取生成的声明但不依赖 Host aggregate 检查,因此只准备 Host 包编译与 Host bundle(`compile-referenced-projects.ts host-libraries`)。
|
||||
|
||||
`pnpm run build` 会内联根包版本、七位源码 commit,并在 Git 报告本地变化时内联 dirty 标记;调用方提供的其他 `DSH_CLIENT_*` 值也会被继承。`pnpm run build:official` 是与 CI 和 release 产物构建等价的跨平台本地命令,并省略本地 dirty 标记。每次完整构建成功后都会写入一份被 gitignore 的记录,把精确公开值与 Vite 输出及动态 client bundle 绑定;release 打包和 built Web 测试会拒绝缺少记录或被后续局部构建改动的产物。`pnpm run dev:web` 会先执行这次完整构建(`--skip-build` 则复用现有产物树),再读取一次当前版本和 Git 状态,并在本次会话的所有 watcher stage 之间共享该环境;它不会校验完整构建记录,因为 watcher stage 会重写记录覆盖的产物。
|
||||
`pnpm run build` 与 `build:artifacts` 会内联根包版本、七位源码 commit,并在 Git 报告本地变化时内联 dirty 标记;调用方提供的其他 `DSH_CLIENT_*` 值也会被继承。`pnpm run build:official` 是与 CI 和 release 产物构建等价的跨平台本地命令,并省略本地 dirty 标记。每次 `build` 或 `build:artifacts` 成功后都会写入一份被 gitignore 的记录,把精确公开值与 Vite 输出及动态 client bundle 绑定;release 打包和 built Web 测试会拒绝缺少记录或被后续局部构建改动的产物。`pnpm run dev:web` 会先执行 `build:artifacts`(`--skip-build` 则复用现有产物树),再读取一次当前版本和 Git 状态,并在本次会话的所有 watcher stage 之间共享该环境;它不会校验完整构建记录,因为 watcher stage 会重写记录覆盖的产物。
|
||||
|
||||
静态分析和测试通过 base 的 `paths` 映射把工作区 import 解析到 `src`,且必须在干净树上通过;消费构建产物 `lib/` 的门禁显式声明该依赖。生成的 Host-for-Client Remote 声明是有意设置的例外:公共 `typecheck`、`lint` 和 `doc-typecheck` 命令会先生成这些声明,而内部 `*:contracts-ready` 脚本假定调用它的公共命令或调度器门禁已经依赖 Typert 约定生成阶段或完整构建。门禁准备约定见 [Typert Remote Agent Note](../.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.zh.md)。
|
||||
|
||||
@@ -169,7 +183,7 @@ pnpm run demo:ptc -- "summarize this workspace"
|
||||
|
||||
### 应用命令
|
||||
|
||||
Web 与 Desktop 共用同一对命令。`start:*` 启动上一次 `pnpm run build` 的产物;`dev:*` 先执行该构建再启动。Web 还会在源码修改时持续重建 client bundle,因为它的 Host 从源码运行,而浏览器加载的是构建产物:
|
||||
Web 与 Desktop 共用同一对命令。`start:*` 启动上一次 `pnpm run build` 或 `build:artifacts` 的产物;`dev:*` 先执行 `build:artifacts` 再启动。Web 还会在源码修改时持续重建 client bundle,因为它的 Host 从源码运行,而浏览器加载的是构建产物:
|
||||
|
||||
```sh
|
||||
pnpm run start:web # serve built Web artifacts through the source launcher (the same launch as pnpm dsh web)
|
||||
|
||||
+7
-3
@@ -19,6 +19,8 @@
|
||||
"scripts": {
|
||||
"prepare:primary-runtime": "tsx scripts/primary-runtime/prepare.ts",
|
||||
"build": "tsx scripts/build.ts",
|
||||
"build:artifacts": "tsx scripts/build.ts --scope artifacts",
|
||||
"build:check": "tsx scripts/build.ts --scope check",
|
||||
"build:bench": "npm run build:native-system && npm run build:lib && tsdown --config-loader native --config benchmarks/tsdown.config.ts",
|
||||
"build:official": "tsx scripts/build.ts --profile official",
|
||||
"build:lib": "pnpm run build:lib:host && pnpm run build:lib:client",
|
||||
@@ -50,9 +52,9 @@
|
||||
"change-scope": "tsx scripts/change-scope.ts",
|
||||
"typecheck": "npm run build:lib:host && npm run typecheck:contracts-ready",
|
||||
"typecheck:contracts-ready": "tsc -b tsconfig.client.json",
|
||||
"lint": "npm run build:lib:host && npm run lint:contracts-ready",
|
||||
"lint": "tsx scripts/compile-referenced-projects.ts host-libraries && npm run lint:contracts-ready",
|
||||
"lint:contracts-ready": "tsx scripts/run-oxlint.ts .",
|
||||
"lint:fix": "npm run build:lib:host && npm run lint:fix:contracts-ready",
|
||||
"lint:fix": "tsx scripts/compile-referenced-projects.ts host-libraries && npm run lint:fix:contracts-ready",
|
||||
"lint:fix:contracts-ready": "tsx scripts/run-oxlint.ts --config .oxlintrc.staged.json packages/typert/generator/tests/fixtures/type-model --fix && tsx scripts/run-oxlint.ts . --fix",
|
||||
"duplication": "jscpd --config .jscpd.json packages scripts",
|
||||
"test": "pnpm run build:native-system && vitest run",
|
||||
@@ -99,7 +101,7 @@
|
||||
"check:ci:windows-observational-ready": "tsx scripts/run-gates.ts ci-windows-observational-ready",
|
||||
"check:node-compat": "tsx scripts/run-gates.ts node-compat",
|
||||
"publint": "tsx scripts/publint-all.ts",
|
||||
"doc-typecheck": "npm run build:lib:host && npm run doc-typecheck:contracts-ready",
|
||||
"doc-typecheck": "tsx scripts/compile-referenced-projects.ts host-libraries && npm run doc-typecheck:contracts-ready",
|
||||
"doc-typecheck:contracts-ready": "tsx scripts/doc-typecheck.ts",
|
||||
"verify-md-wrap": "tsx scripts/verify-md-wrap.ts",
|
||||
"verify-md-links": "tsx scripts/verify-md-links.ts",
|
||||
@@ -240,6 +242,8 @@
|
||||
"@types/mdast": "^4.0.4",
|
||||
"@types/papaparse": "^5.5.2",
|
||||
"@types/node": "^22.20.1",
|
||||
"@types/react": "~18.3.1",
|
||||
"@types/react-dom": "~18.3.0",
|
||||
"@types/semver": "^7.8.0",
|
||||
"@types/spdx-expression-parse": "^4.0.0",
|
||||
"@vitest/coverage-v8": "^4.1.11",
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { clientBundle } from '../../client/tsdown.client.ts'
|
||||
|
||||
export default clientBundle('@deepseek-ai/dsh-api-gateway', ['lib/types/index.js'])
|
||||
export default clientBundle(import.meta.dirname, ['lib/types/index.js'])
|
||||
@@ -1,7 +1,7 @@
|
||||
import { clientBundle } from '../../client/tsdown.client.ts'
|
||||
|
||||
export default clientBundle(
|
||||
'@deepseek-ai/dsh-api-job-controller',
|
||||
import.meta.dirname,
|
||||
['lib/types/index.js'],
|
||||
{ hostPhase: true },
|
||||
)
|
||||
@@ -1,7 +1,7 @@
|
||||
import { clientBundle } from '../../client/tsdown.client.ts'
|
||||
|
||||
export default clientBundle(
|
||||
'@deepseek-ai/dsh-api-remotes',
|
||||
import.meta.dirname,
|
||||
['lib/types/index.js'],
|
||||
{ hostPhase: true },
|
||||
)
|
||||
@@ -1,7 +1,7 @@
|
||||
import { clientBundle } from '../../client/tsdown.client.ts'
|
||||
|
||||
export default clientBundle(
|
||||
'@deepseek-ai/dsh-api-session-controller',
|
||||
import.meta.dirname,
|
||||
['lib/types/index.js'],
|
||||
{ hostPhase: true },
|
||||
)
|
||||
@@ -1,7 +1,7 @@
|
||||
import { clientBundle } from '../../client/tsdown.client.ts'
|
||||
|
||||
export default clientBundle(
|
||||
'@deepseek-ai/dsh-api-terminal-controller',
|
||||
import.meta.dirname,
|
||||
['lib/types/index.js'],
|
||||
{ hostPhase: true },
|
||||
)
|
||||
@@ -1,7 +1,7 @@
|
||||
import { clientBundle } from '../../client/tsdown.client.ts'
|
||||
|
||||
export default clientBundle(
|
||||
'@deepseek-ai/dsh-api-workspace-controller',
|
||||
import.meta.dirname,
|
||||
['lib/types/index.js'],
|
||||
{ hostPhase: true },
|
||||
)
|
||||
@@ -1,7 +1,7 @@
|
||||
import { clientBundle } from '../../client/tsdown.client.ts'
|
||||
|
||||
export default clientBundle(
|
||||
'@deepseek-ai/dsh-api-workspace-files',
|
||||
import.meta.dirname,
|
||||
['lib/types/index.js'],
|
||||
{ hostPhase: true },
|
||||
)
|
||||
@@ -69,6 +69,7 @@ Npm sections describe installation and development relationships; each build fac
|
||||
5. **Only Host runtime libraries require installation dependencies.** Browser-only third-party implementations belong in `devDependencies`, whether inlined into `lib/client.js` or retained as bare imports in a static browser library. Static libraries are Web-shell build inputs; the final Vite build merges their imports and styles. Distributed browser code remains subject to runtime license disclosure ([reference](../../.agents/notes/implemented/process/2026-08-26-published-dependency-faces.md)).
|
||||
6. **Browser and Node build faces declare externality independently.** A dynamic browser half uses the baseline plus `dsh.client.external`; a statically linked face externalizes every bare specifier; a Node face externalizes its production dependencies ([`tsdown.client.ts`](tsdown.client.ts)). Moving a name between npm sections must not silently change bundle contents.
|
||||
7. **Keep the published payload closed.** Every relative runtime import and emitted asset must be covered by `files`; the repository publint pass checks the exact publication view.
|
||||
8. **Only the root manifest declares React type packages.** `@types/react` and `@types/react-dom` belong only in the root `package.json` `devDependencies`, and `verify-package-dependencies` rejects them in every manifest under `apps/*`, `packages/*/*`, and `vendor/*`; packages still declare the `react` and `react-dom` they import. Each package-local pnpm link is one more location TypeScript scans when it computes module specifiers for every JSX file ([rationale](../../.agents/notes/implemented/process/2026-10-11-lossless-build-speedups.md#1-only-the-root-manifest-declares-react-type-packages)).
|
||||
|
||||
## Build-time browser environment
|
||||
|
||||
@@ -142,7 +143,7 @@ If `test:gui` is red on code you did not touch, neither silently fix nor ignore
|
||||
|
||||
Bringing up a new `packages/client/<name>` plugin package (ui-workspace is a complete example; ui-sidebar/ui-user-questions are minimal skeletons):
|
||||
|
||||
1. **Package skeleton**: `package.json` (`@deepseek-ai/dsh-client-<name>`, exports `.`/`./client`/`./src/*`/`./package.json`, `dsh.client` manifest, `files` list), `tsconfig.json` (extends `tsconfig.base.client.json`, one `references` entry per workspace dependency), `tsdown.config.ts` (`clientBundle(id, ['lib/types/index.js'])`), `src/index.ts` (empty node-half apply), `src/css-modules.d.ts` when using CSS Modules, and `README.md` with the Model Experience section.
|
||||
1. **Package skeleton**: `package.json` (`@deepseek-ai/dsh-client-<name>`, exports `.`/`./client`/`./src/*`/`./package.json`, `dsh.client` manifest, `files` list), `tsconfig.json` (extends `tsconfig.base.client.json`, one `references` entry per workspace dependency), `tsdown.config.ts` (`clientBundle(import.meta.dirname, ['lib/types/index.js'])`), `src/index.ts` (empty node-half apply), `src/css-modules.d.ts` when using CSS Modules, and `README.md` with the Model Experience section.
|
||||
2. **Three registration surfaces, all required** (missing any one fails at a different, later point): the `tsconfig.client.json` aggregate `references` entry; a `dsh.client` row in `packages/bundle/web-app/cordis.patch.yml`; a `packages/bundle/web-app/package.json` dependency (profile boots resolve bare row names through the computed runtime resolution, which is installed as an interception on Node's resolvers — a row whose package no manifest declares fails to import). `pnpm-workspace.yaml` already globs `packages/*/*`.
|
||||
3. **dsh.client manifest semantics**: `platform: 'web'` always, and the declaration requires a `./client` export (the scan throws without one); `immediately: true` only for stage-one-prefetch infrastructure rows. `inject` lists package-name dependency edges — they are **informational only** (preflight display, HMR diffing); they do not sequence entry activation or apply order. Activation order is Cordis fiber inject waiting on *services*, nothing else. A non-baseline `external` request sequences its dynamic supplier ahead of the consumer — see [shared modules](#shared-modules-and-the-module-graph).
|
||||
4. **Registering into another package's slot**: apply order is unconstrained, and a business service is not a declaration barrier. Use `ctx.slots.inject(name, () => ctx.slots.register(...))`; it waits on the actual declaration, removes the contribution when that declaration collapses, reruns after redeclaration, and leaves with the caller's plugin fiber. Return a generator yielding each registration when several contributions must install and roll back atomically. A bare `slots.register` into an undeclared slot remains an error; keep service edges only for services the contribution actually reads.
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { clientBundle } from '../tsdown.client.ts'
|
||||
|
||||
export default clientBundle('@deepseek-ai/dsh-client-connection', ['lib/types/index.js'])
|
||||
export default clientBundle(import.meta.dirname, ['lib/types/index.js'])
|
||||
@@ -1,3 +1,3 @@
|
||||
import { clientBundle } from '../tsdown.client.ts'
|
||||
|
||||
export default clientBundle('@deepseek-ai/dsh-client-file-upload', ['lib/types/index.js'])
|
||||
export default clientBundle(import.meta.dirname, ['lib/types/index.js'])
|
||||
@@ -1,3 +1,3 @@
|
||||
import { clientBundle } from '../tsdown.client.ts'
|
||||
|
||||
export default clientBundle('@deepseek-ai/dsh-client-hmr', ['lib/types/index.js'])
|
||||
export default clientBundle(import.meta.dirname, ['lib/types/index.js'])
|
||||
@@ -52,7 +52,6 @@
|
||||
"@deepseek-ai/dsh-client-ui-settings": "workspace:*",
|
||||
"@deepseek-ai/dsh-client-ui-slots": "workspace:*",
|
||||
"@deepseek-ai/dsh-package-manifest": "workspace:*",
|
||||
"@types/react": "~18.3.1",
|
||||
"react": "^18.2.0",
|
||||
"@deepseek-ai/dsh-client-connection": "workspace:*",
|
||||
"@deepseek-ai/dsh-settings": "workspace:*"
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { clientBundle } from '../tsdown.client.ts'
|
||||
|
||||
export default clientBundle('@deepseek-ai/dsh-client-locale', ['lib/types/index.js'])
|
||||
export default clientBundle(import.meta.dirname, ['lib/types/index.js'])
|
||||
@@ -1,6 +1,6 @@
|
||||
import { clientBundle } from '../tsdown.client.ts'
|
||||
|
||||
export default clientBundle(
|
||||
'@deepseek-ai/dsh-client-modules',
|
||||
import.meta.dirname,
|
||||
['lib/types/index.js'],
|
||||
)
|
||||
@@ -1,3 +1,3 @@
|
||||
import { clientBundle } from '../tsdown.client.ts'
|
||||
|
||||
export default clientBundle('@deepseek-ai/dsh-client-product-analytics', ['lib/types/index.js'], { hostPhase: true })
|
||||
export default clientBundle(import.meta.dirname, ['lib/types/index.js'], { hostPhase: true })
|
||||
@@ -1,3 +1,3 @@
|
||||
import { clientBundle } from '../tsdown.client.ts'
|
||||
|
||||
export default clientBundle('@deepseek-ai/dsh-client-resources', ['lib/types/index.js'])
|
||||
export default clientBundle(import.meta.dirname, ['lib/types/index.js'])
|
||||
@@ -1,7 +1,7 @@
|
||||
import { clientBundle } from '../tsdown.client.ts'
|
||||
|
||||
export default clientBundle(
|
||||
'@deepseek-ai/dsh-client-shortcuts',
|
||||
import.meta.dirname,
|
||||
['lib/types/index.js', 'lib/types/protocol.js'],
|
||||
{ hostPhase: true },
|
||||
)
|
||||
@@ -1,6 +1,6 @@
|
||||
import { staticLinked } from '../tsdown.client.ts'
|
||||
|
||||
export default staticLinked(
|
||||
'@deepseek-ai/dsh-client-store',
|
||||
import.meta.dirname,
|
||||
['lib/types/index.js'],
|
||||
)
|
||||
@@ -11,7 +11,7 @@
|
||||
* inputs, including origins recorded by chained source maps.
|
||||
*/
|
||||
import { readFile, stat, utimes } from 'node:fs/promises'
|
||||
import { existsSync, globSync, readFileSync } from 'node:fs'
|
||||
import { existsSync, readFileSync } from 'node:fs'
|
||||
import { createRequire, isBuiltin } from 'node:module'
|
||||
import { basename, dirname, isAbsolute, relative, resolve as resolvePath, sep } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
@@ -100,22 +100,25 @@ function browserSourcePath(source: string, sourcemapPath: string): string {
|
||||
* the package without lib/index.js and the host Loader cannot import its node
|
||||
* half. The Client build consumes `lib/types` and chains those tsc maps, with
|
||||
* original source content, into the standalone plugin map.
|
||||
* @param id - plugin id (package name), stamped into the __ModuleLoader__.load
|
||||
* handoff and onto the injected style tags.
|
||||
* @param packageDir - the package's own directory, normally the config's
|
||||
* `import.meta.dirname`. Its `package.json` name is the plugin id stamped into
|
||||
* the __ModuleLoader__.load handoff and onto the injected style tags.
|
||||
* @param libEntry - node-half entries.
|
||||
* @param options - phase placement, lib overrides, companion Node configs, and optional per-file Client banner.
|
||||
* @returns ENV-selected tsdown config for the current build face.
|
||||
* @throws {Error} when `<packageDir>/package.json` is unreadable or declares no name.
|
||||
*/
|
||||
export function clientBundle(
|
||||
id: string,
|
||||
packageDir: string,
|
||||
libEntry: readonly string[],
|
||||
options: ClientBundleOptions = {},
|
||||
): BuildFaceConfig {
|
||||
const lib = clientLibraryConfig(id, libEntry, options.lib)
|
||||
const manifest = readPackageManifest(packageDir)
|
||||
const lib = clientLibraryConfig(manifest, libEntry, options.lib)
|
||||
return ({ env }) => {
|
||||
const face = buildFace(env?.DSH_BUILD_FACE)
|
||||
const clientEntry = face === undefined ? 'src/client/index.ts' : 'lib/types/client/index.js'
|
||||
const client = clientConfig(id, clientEntry, options.clientBanner)
|
||||
const client = clientConfig(manifest, clientEntry, options.clientBanner)
|
||||
const node = [lib, ...(options.companions ?? [])]
|
||||
if (face === 'host') return options.hostPhase === true ? node : [SKIP_WORKSPACE_BUILD]
|
||||
if (face === 'client') {
|
||||
@@ -147,13 +150,16 @@ export function clientBundle(
|
||||
* 4. stylesheets ship with the package: a relative `.css` import survives as a
|
||||
* relative external and the sheet is emitted under `lib/` at its
|
||||
* `src`-relative path, so vite stays the only owner of class hashing.
|
||||
* @param id - package name, used in tsdown diagnostics.
|
||||
* @param packageDir - the package's own directory, normally the config's
|
||||
* `import.meta.dirname`; its `package.json` name is used in tsdown diagnostics.
|
||||
* @param libEntry - emitted JavaScript entries consumed from `lib/types`, one
|
||||
* bundle each: a multi-entry build would emit a hash-named shared chunk that
|
||||
* the exact `files` list cannot publish.
|
||||
* @returns ENV-selected tsdown config for the Client build face.
|
||||
* @throws {Error} when `<packageDir>/package.json` is unreadable or declares no name.
|
||||
*/
|
||||
export function staticLinked(id: string, libEntry: readonly string[]): BuildFaceConfig {
|
||||
export function staticLinked(packageDir: string, libEntry: readonly string[]): BuildFaceConfig {
|
||||
const id = readPackageManifest(packageDir).name
|
||||
// Each entry names its own output file, so two entries with the same basename
|
||||
// would overwrite one artifact instead of emitting two.
|
||||
const names = new Set(libEntry.map(entry => basename(entry, '.js')))
|
||||
@@ -177,12 +183,14 @@ export function isStaticLinkedConfig(configs: readonly UserConfig[]): boolean {
|
||||
|
||||
/**
|
||||
* Build a Client-only Node library during the Client pass.
|
||||
* @param id - Package name used in tsdown diagnostics.
|
||||
* @param packageDir - The package's own directory, normally the config's
|
||||
* `import.meta.dirname`; its `package.json` name is used in tsdown diagnostics.
|
||||
* @param libEntry - Emitted JavaScript entries consumed from `lib/types`.
|
||||
* @returns ENV-selected tsdown config for the Client build face.
|
||||
* @throws {Error} when `<packageDir>/package.json` is unreadable or declares no name.
|
||||
*/
|
||||
export function clientLibrary(id: string, libEntry: readonly string[]): BuildFaceConfig {
|
||||
const lib = clientLibraryConfig(id, libEntry)
|
||||
export function clientLibrary(packageDir: string, libEntry: readonly string[]): BuildFaceConfig {
|
||||
const lib = clientLibraryConfig(readPackageManifest(packageDir), libEntry)
|
||||
return clientOnly([lib])
|
||||
}
|
||||
|
||||
@@ -218,14 +226,15 @@ function buildFace(value: unknown): BuildFace {
|
||||
}
|
||||
|
||||
function clientLibraryConfig(
|
||||
id: string,
|
||||
manifest: PackageManifest,
|
||||
libEntry: readonly string[],
|
||||
overrides: UserConfig = {},
|
||||
): UserConfig {
|
||||
let externals: readonly RegExp[] | undefined
|
||||
const isProductionDependency = (specifier: string): boolean =>
|
||||
matchesSpecifier(productionExternals(id), specifier)
|
||||
matchesSpecifier(externals ??= productionExternals(manifest), specifier)
|
||||
return {
|
||||
name: id,
|
||||
name: manifest.name,
|
||||
entry: [...libEntry],
|
||||
outDir: 'lib',
|
||||
format: ['esm'],
|
||||
@@ -330,9 +339,10 @@ function stylesheetAsset(source: string, importer: string): { readonly file: str
|
||||
return { file, fileName: file.slice(boundary + SOURCE_MARKER.length).split(sep).join('/') }
|
||||
}
|
||||
|
||||
/** The manifest fields the build faces read to state their own module edges. */
|
||||
interface WorkspaceManifest {
|
||||
readonly name?: string
|
||||
/** The manifest fields the build faces read to name the package and state its own module edges. */
|
||||
interface PackageManifest {
|
||||
/** Package name: the tsdown config name, the Client loader id, and the diagnostic subject. */
|
||||
readonly name: string
|
||||
/** Sections a real install materializes on disk next to the built package. */
|
||||
readonly dependencies?: Record<string, string>
|
||||
readonly peerDependencies?: Record<string, string>
|
||||
@@ -340,52 +350,44 @@ interface WorkspaceManifest {
|
||||
readonly dsh?: { readonly client?: { readonly external?: unknown } }
|
||||
}
|
||||
|
||||
const manifestCache = new Map<string, WorkspaceManifest>()
|
||||
const productionExternalCache = new Map<string, readonly RegExp[]>()
|
||||
const clientExternalCache = new Map<string, ReadonlySet<string>>()
|
||||
|
||||
/**
|
||||
* Read one workspace package's manifest. Located by package name rather than by
|
||||
* cwd, because tsdown evaluates every package config with the repository root as
|
||||
* `process.cwd()` during a workspace build. Callers read it on the first
|
||||
* resolveId of a build, not while a config is built, so selecting a build face
|
||||
* never touches a manifest.
|
||||
* @param id - package name, as spelled at the preset call site.
|
||||
* Read the manifest of the package whose config calls a preset. The caller
|
||||
* passes its directory because tsdown evaluates every package config with the
|
||||
* repository root as `process.cwd()` during a workspace build.
|
||||
* @param packageDir - the package's own directory.
|
||||
* @returns the parsed manifest.
|
||||
* @throws {Error} when no workspace package declares that name.
|
||||
* @throws {Error} when `package.json` is unreadable, is not JSON, or declares no string name.
|
||||
*/
|
||||
function workspaceManifest(id: string): WorkspaceManifest {
|
||||
const cached = manifestCache.get(id)
|
||||
if (cached !== undefined) return cached
|
||||
for (const manifestPath of globSync('packages/*/*/package.json', { cwd: REPOSITORY_ROOT })) {
|
||||
const manifest = JSON.parse(
|
||||
readFileSync(resolvePath(REPOSITORY_ROOT, manifestPath), 'utf8'),
|
||||
) as WorkspaceManifest
|
||||
if (manifest.name !== id) continue
|
||||
manifestCache.set(id, manifest)
|
||||
return manifest
|
||||
function readPackageManifest(packageDir: string): PackageManifest {
|
||||
const manifestPath = resolvePath(packageDir, 'package.json')
|
||||
let parsed: unknown
|
||||
try {
|
||||
parsed = JSON.parse(readFileSync(manifestPath, 'utf8'))
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`tsdown: cannot read ${manifestPath}; pass the package directory (the import.meta.dirname of its tsdown.config.ts)`,
|
||||
{ cause: error },
|
||||
)
|
||||
}
|
||||
throw new Error(`tsdown: no packages/*/*/package.json declares the name ${id}`)
|
||||
if (typeof parsed !== 'object' || parsed === null || !('name' in parsed) || typeof parsed.name !== 'string') {
|
||||
throw new Error(`tsdown: ${manifestPath} declares no string package name`)
|
||||
}
|
||||
return parsed as PackageManifest
|
||||
}
|
||||
|
||||
/**
|
||||
* External patterns for one package's Node half: its own production sections,
|
||||
* subpaths included.
|
||||
* @param id - package name, as spelled at the preset call site.
|
||||
* @param manifest - the package's own manifest.
|
||||
* @returns one `^name(/|$)` pattern per production dependency, name-sorted.
|
||||
*/
|
||||
function productionExternals(id: string): readonly RegExp[] {
|
||||
const cached = productionExternalCache.get(id)
|
||||
if (cached !== undefined) return cached
|
||||
const manifest = workspaceManifest(id)
|
||||
function productionExternals(manifest: PackageManifest): readonly RegExp[] {
|
||||
const names = new Set([
|
||||
...Object.keys(manifest.dependencies ?? {}),
|
||||
...Object.keys(manifest.peerDependencies ?? {}),
|
||||
...Object.keys(manifest.optionalDependencies ?? {}),
|
||||
])
|
||||
const patterns = [...names].sort().map(name => new RegExp(`^${escapeSpecifier(name)}(/|$)`))
|
||||
productionExternalCache.set(id, patterns)
|
||||
return patterns
|
||||
return [...names].sort().map(name => new RegExp(`^${escapeSpecifier(name)}(/|$)`))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -408,19 +410,15 @@ export function requestedExternals(
|
||||
* Module-table specifiers one package requests. The shell baseline is implicit
|
||||
* for every dynamic bundle; `dsh.client.external` only adds package-specific
|
||||
* dynamic rows or subpaths.
|
||||
* @param id - package name, as spelled at the preset call site.
|
||||
* @param manifest - the package's own manifest, whose `dsh.client` declaration adds requests.
|
||||
* @returns the baseline plus the package's explicit requests.
|
||||
*/
|
||||
function clientExternals(id: string): ReadonlySet<string> {
|
||||
const cached = clientExternalCache.get(id)
|
||||
if (cached !== undefined) return cached
|
||||
const externals = new Set([
|
||||
function clientExternals(manifest: PackageManifest): ReadonlySet<string> {
|
||||
return new Set([
|
||||
...PLATFORM_MODULES,
|
||||
...PRELOADED_CLIENT_EXTERNALS,
|
||||
...requestedExternals(id, workspaceManifest(id).dsh?.client ?? {}),
|
||||
...requestedExternals(manifest.name, manifest.dsh?.client ?? {}),
|
||||
])
|
||||
clientExternalCache.set(id, externals)
|
||||
return externals
|
||||
}
|
||||
|
||||
/** Escape a package name for literal use inside a RegExp source. */
|
||||
@@ -469,8 +467,10 @@ function asyncChunkRequirePlugin(): TsdownPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
function clientConfig(id: string, entry: string, clientBanner?: (fileName: string) => string | undefined): UserConfig {
|
||||
const isRequested = (specifier: string): boolean => clientExternals(id).has(specifier)
|
||||
function clientConfig(manifest: PackageManifest, entry: string, clientBanner?: (fileName: string) => string | undefined): UserConfig {
|
||||
const id = manifest.name
|
||||
let requested: ReadonlySet<string> | undefined
|
||||
const isRequested = (specifier: string): boolean => (requested ??= clientExternals(manifest)).has(specifier)
|
||||
const isolation = clientInputIsolation(id)
|
||||
return {
|
||||
name: `${id}/client`,
|
||||
|
||||
@@ -68,7 +68,6 @@
|
||||
"@deepseek-ai/dsh-client-ui-workspace": "workspace:*",
|
||||
"@deepseek-ai/dsh-session": "workspace:*",
|
||||
"@deepseek-ai/dsh-util-values": "workspace:*",
|
||||
"@types/react": "~18.3.1",
|
||||
"react": "^18.2.0"
|
||||
},
|
||||
"files": [
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { clientBundle } from '../tsdown.client.ts'
|
||||
|
||||
export default clientBundle('@deepseek-ai/dsh-client-ui-agent-preset', ['lib/types/index.js'])
|
||||
export default clientBundle(import.meta.dirname, ['lib/types/index.js'])
|
||||
@@ -60,7 +60,6 @@
|
||||
"@deepseek-ai/dsh-llm": "workspace:*",
|
||||
"@deepseek-ai/dsh-session": "workspace:*",
|
||||
"@deepseek-ai/dsh-typert-protocol": "workspace:*",
|
||||
"@types/react": "~18.3.1",
|
||||
"react": "^18.2.0",
|
||||
"@deepseek-ai/dsh-client-shortcuts": "workspace:*"
|
||||
},
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { clientBundle } from '../tsdown.client.ts'
|
||||
|
||||
export default clientBundle('@deepseek-ai/dsh-client-ui-approval', ['lib/types/index.js'])
|
||||
export default clientBundle(import.meta.dirname, ['lib/types/index.js'])
|
||||
@@ -46,8 +46,6 @@
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@deepseek-ai/cordis": "workspace:~",
|
||||
"@types/react": "~18.3.1",
|
||||
"@types/react-dom": "~18.3.0",
|
||||
"@deepseek-ai/dsh-client-locale": "workspace:*",
|
||||
"@deepseek-ai/dsh-client-ui-chat": "workspace:*",
|
||||
"@deepseek-ai/dsh-client-ui-conversation": "workspace:*",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { clientBundle } from '../tsdown.client.ts'
|
||||
|
||||
export default clientBundle(
|
||||
'@deepseek-ai/dsh-client-ui-attachment',
|
||||
import.meta.dirname,
|
||||
['lib/types/index.js'],
|
||||
)
|
||||
@@ -49,7 +49,6 @@
|
||||
"@deepseek-ai/dsh-client-ui-sidebar": "workspace:*",
|
||||
"@deepseek-ai/cordis": "workspace:~",
|
||||
"@testing-library/react": "^16.3.3",
|
||||
"@types/react": "~18.3.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { clientBundle } from '../tsdown.client.ts'
|
||||
|
||||
export default clientBundle('@deepseek-ai/dsh-client-ui-brand-official', ['lib/types/index.js'])
|
||||
export default clientBundle(import.meta.dirname, ['lib/types/index.js'])
|
||||
@@ -89,8 +89,6 @@
|
||||
"@deepseek-ai/dsh-util-workspace-path": "workspace:*",
|
||||
"@deepseek-ai/dsh-util-values": "workspace:*",
|
||||
"@tanstack/react-virtual": "3.14.10",
|
||||
"@types/react": "~18.3.1",
|
||||
"@types/react-dom": "~18.3.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"@deepseek-ai/dsh-settings": "workspace:*",
|
||||
|
||||
@@ -2,7 +2,7 @@ import { clientBundle } from '../tsdown.client.ts'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const bundle = clientBundle('@deepseek-ai/dsh-client-ui-chat', ['lib/types/index.js'])
|
||||
const bundle = clientBundle(import.meta.dirname, ['lib/types/index.js'])
|
||||
const stylesheet = fileURLToPath(new URL('./src/client/chat/ChatView.module.css', import.meta.url)).replaceAll('\\', '/')
|
||||
const whaleImage = fileURLToPath(new URL('./src/client/chat/[email protected]', import.meta.url))
|
||||
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
"@deepseek-ai/dsh-client-ui-input-trigger": "workspace:*",
|
||||
"@deepseek-ai/dsh-client-ui-slots": "workspace:*",
|
||||
"@deepseek-ai/dsh-commands": "workspace:*",
|
||||
"@types/react": "~18.3.1",
|
||||
"@deepseek-ai/cordis": "workspace:~",
|
||||
"react": "^18.2.0",
|
||||
"@deepseek-ai/dsh-api-session-controller": "workspace:*",
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { clientBundle } from '../tsdown.client.ts'
|
||||
|
||||
export default clientBundle('@deepseek-ai/dsh-client-ui-commands', ['lib/types/index.js'])
|
||||
export default clientBundle(import.meta.dirname, ['lib/types/index.js'])
|
||||
@@ -83,11 +83,9 @@
|
||||
"@deepseek-ai/dsh-workspace": "workspace:*",
|
||||
"@deepseek-ai/dsh-file-reference": "workspace:*",
|
||||
"@deepseek-ai/dsh-util-workspace-path": "workspace:*",
|
||||
"@types/react": "~18.3.1",
|
||||
"react": "^18.2.0",
|
||||
"@lexical/headless": "^0.49.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"@types/react-dom": "~18.3.0",
|
||||
"zod": "4.4.3",
|
||||
"lexical": "^0.49.0",
|
||||
"clsx": "^2.0.0",
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { clientBundle } from '../tsdown.client.ts'
|
||||
|
||||
export default clientBundle('@deepseek-ai/dsh-client-ui-conversation', ['lib/types/index.js'])
|
||||
export default clientBundle(import.meta.dirname, ['lib/types/index.js'])
|
||||
@@ -62,7 +62,6 @@
|
||||
"@deepseek-ai/dsh-client-ui-renderer": "workspace:*",
|
||||
"@deepseek-ai/dsh-client-ui-sidebar-right": "workspace:*",
|
||||
"@deepseek-ai/dsh-client-ui-slots": "workspace:*",
|
||||
"@types/react": "~18.3.1",
|
||||
"@deepseek-ai/cordis": "workspace:~",
|
||||
"react": "^18.2.0",
|
||||
"@deepseek-ai/dsh-client-ui-primitives": "workspace:*",
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { clientBundle } from '../tsdown.client.ts'
|
||||
|
||||
export default clientBundle('@deepseek-ai/dsh-client-ui-deliverables', ['lib/types/index.js'])
|
||||
export default clientBundle(import.meta.dirname, ['lib/types/index.js'])
|
||||
@@ -54,7 +54,6 @@
|
||||
"@deepseek-ai/dsh-client-ui-slots": "workspace:*",
|
||||
"@deepseek-ai/dsh-client-ui-workspace": "workspace:*",
|
||||
"@testing-library/react": "^16.3.3",
|
||||
"@types/react": "~18.3.1",
|
||||
"@deepseek-ai/cordis": "workspace:~",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { clientBundle } from '../tsdown.client.ts'
|
||||
|
||||
export default clientBundle('@deepseek-ai/dsh-client-ui-directory-picker-browse', ['lib/types/index.js'])
|
||||
export default clientBundle(import.meta.dirname, ['lib/types/index.js'])
|
||||
@@ -48,7 +48,6 @@
|
||||
"@deepseek-ai/dsh-client-ui-renderer": "workspace:*",
|
||||
"@deepseek-ai/dsh-client-ui-workspace": "workspace:*",
|
||||
"@testing-library/react": "^16.3.3",
|
||||
"@types/react": "~18.3.1",
|
||||
"@deepseek-ai/cordis": "workspace:~",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { clientBundle } from '../tsdown.client.ts'
|
||||
|
||||
export default clientBundle('@deepseek-ai/dsh-client-ui-directory-picker-native', ['lib/types/index.js'])
|
||||
export default clientBundle(import.meta.dirname, ['lib/types/index.js'])
|
||||
@@ -27,8 +27,6 @@
|
||||
"@deepseek-ai/dsh-brand": "workspace:*",
|
||||
"@deepseek-ai/dsh-client-ui-primitives": "workspace:*",
|
||||
"@testing-library/react": "^16.3.3",
|
||||
"@types/react": "~18.3.1",
|
||||
"@types/react-dom": "~18.3.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react": "^18.2.0",
|
||||
"clsx": "^2.0.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { staticLinked } from '../tsdown.client.ts'
|
||||
|
||||
export default staticLinked(
|
||||
'@deepseek-ai/dsh-client-ui-dockkit',
|
||||
import.meta.dirname,
|
||||
['lib/types/index.js'],
|
||||
)
|
||||
@@ -62,7 +62,6 @@
|
||||
"@deepseek-ai/dsh-commands": "workspace:*",
|
||||
"@deepseek-ai/dsh-goal": "workspace:*",
|
||||
"@testing-library/react": "^16.3.3",
|
||||
"@types/react": "~18.3.1",
|
||||
"@deepseek-ai/cordis": "workspace:~",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { clientBundle } from '../tsdown.client.ts'
|
||||
|
||||
export default clientBundle('@deepseek-ai/dsh-client-ui-goal', ['lib/types/index.js'])
|
||||
export default clientBundle(import.meta.dirname, ['lib/types/index.js'])
|
||||
@@ -52,7 +52,6 @@
|
||||
"@deepseek-ai/dsh-client-test-runtime": "workspace:*",
|
||||
"@deepseek-ai/dsh-client-ui-primitives": "workspace:*",
|
||||
"@deepseek-ai/dsh-client-ui-slots": "workspace:*",
|
||||
"@types/react": "~18.3.1",
|
||||
"@deepseek-ai/cordis": "workspace:~",
|
||||
"react": "^18.2.0",
|
||||
"@deepseek-ai/dsh-file-reference": "workspace:*",
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { clientBundle } from '../tsdown.client.ts'
|
||||
|
||||
export default clientBundle('@deepseek-ai/dsh-client-ui-input-trigger', ['lib/types/index.js'])
|
||||
export default clientBundle(import.meta.dirname, ['lib/types/index.js'])
|
||||
@@ -46,8 +46,6 @@
|
||||
"@deepseek-ai/dsh-client-ui-slots": "workspace:*",
|
||||
"@deepseek-ai/dsh-jobs": "workspace:*",
|
||||
"@deepseek-ai/dsh-session": "workspace:*",
|
||||
"@types/react": "~18.3.1",
|
||||
"@types/react-dom": "~18.3.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { clientBundle } from '../tsdown.client.ts'
|
||||
|
||||
export default clientBundle('@deepseek-ai/dsh-client-ui-jobs', ['lib/types/index.js'])
|
||||
export default clientBundle(import.meta.dirname, ['lib/types/index.js'])
|
||||
@@ -54,7 +54,6 @@
|
||||
"@deepseek-ai/dsh-client-ui-session": "workspace:*",
|
||||
"@deepseek-ai/dsh-client-ui-slots": "workspace:*",
|
||||
"@deepseek-ai/dsh-client-ui-theme": "workspace:*",
|
||||
"@types/react": "~18.3.1",
|
||||
"@deepseek-ai/cordis": "workspace:~",
|
||||
"react": "^18.2.0",
|
||||
"@deepseek-ai/dsh-client-shortcuts": "workspace:*"
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { clientBundle } from '../tsdown.client.ts'
|
||||
|
||||
export default clientBundle('@deepseek-ai/dsh-client-ui-layout', ['lib/types/index.js'])
|
||||
export default clientBundle(import.meta.dirname, ['lib/types/index.js'])
|
||||
@@ -61,8 +61,6 @@
|
||||
"@deepseek-ai/dsh-typert-protocol": "workspace:*",
|
||||
"@deepseek-ai/cordis": "workspace:~",
|
||||
"@testing-library/react": "^16.3.3",
|
||||
"@types/react": "~18.3.1",
|
||||
"@types/react-dom": "~18.3.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"@deepseek-ai/dsh-client-ui-chat": "workspace:*",
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { clientBundle } from '../tsdown.client.ts'
|
||||
|
||||
export default clientBundle('@deepseek-ai/dsh-client-ui-message-feedback', ['lib/types/index.js'])
|
||||
export default clientBundle(import.meta.dirname, ['lib/types/index.js'])
|
||||
@@ -56,11 +56,9 @@
|
||||
"@deepseek-ai/dsh-client-ui-primitives": "workspace:*",
|
||||
"@deepseek-ai/dsh-client-ui-slots": "workspace:*",
|
||||
"@deepseek-ai/dsh-typert-protocol": "workspace:*",
|
||||
"@types/react": "~18.3.1",
|
||||
"@deepseek-ai/cordis": "workspace:~",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"@types/react-dom": "~18.3.0",
|
||||
"@deepseek-ai/dsh-client-store": "workspace:*",
|
||||
"@deepseek-ai/dsh-session": "workspace:*",
|
||||
"@deepseek-ai/dsh-util-values": "workspace:*",
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { clientBundle } from '../tsdown.client.ts'
|
||||
|
||||
export default clientBundle('@deepseek-ai/dsh-client-ui-model-selection', ['lib/types/index.js'])
|
||||
export default clientBundle(import.meta.dirname, ['lib/types/index.js'])
|
||||
@@ -75,7 +75,6 @@
|
||||
"@deepseek-ai/dsh-client-ui-slots": "workspace:*",
|
||||
"@deepseek-ai/dsh-host-open-in-app": "workspace:*",
|
||||
"@deepseek-ai/dsh-session": "workspace:*",
|
||||
"@types/react": "~18.3.1",
|
||||
"react": "^18.2.0",
|
||||
"@deepseek-ai/dsh-client-ui-deliverables": "workspace:*",
|
||||
"@deepseek-ai/dsh-native-command": "workspace:*",
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { clientBundle } from '../tsdown.client.ts'
|
||||
|
||||
export default clientBundle('@deepseek-ai/dsh-client-ui-open-in-app', ['lib/types/index.js'])
|
||||
export default clientBundle(import.meta.dirname, ['lib/types/index.js'])
|
||||
@@ -64,7 +64,6 @@
|
||||
"@deepseek-ai/dsh-client-ui-input-trigger": "workspace:*",
|
||||
"@deepseek-ai/dsh-client-ui-slots": "workspace:*",
|
||||
"@deepseek-ai/dsh-permission-presets": "workspace:*",
|
||||
"@types/react": "~18.3.1",
|
||||
"clsx": "^2.0.0",
|
||||
"react": "^18.2.0",
|
||||
"@deepseek-ai/dsh-client-ui-renderer": "workspace:*",
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { clientBundle } from '../tsdown.client.ts'
|
||||
|
||||
export default clientBundle('@deepseek-ai/dsh-client-ui-permission-presets', ['lib/types/index.js'])
|
||||
export default clientBundle(import.meta.dirname, ['lib/types/index.js'])
|
||||
@@ -58,7 +58,6 @@
|
||||
"@deepseek-ai/dsh-client-ui-primitives": "workspace:*",
|
||||
"@deepseek-ai/dsh-client-ui-slots": "workspace:*",
|
||||
"@deepseek-ai/dsh-plan-mode": "workspace:*",
|
||||
"@types/react": "~18.3.1",
|
||||
"@deepseek-ai/cordis": "workspace:~",
|
||||
"react": "^18.2.0",
|
||||
"@deepseek-ai/dsh-session": "workspace:*",
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { clientBundle } from '../tsdown.client.ts'
|
||||
|
||||
export default clientBundle('@deepseek-ai/dsh-client-ui-plan', ['lib/types/index.js'])
|
||||
export default clientBundle(import.meta.dirname, ['lib/types/index.js'])
|
||||
@@ -66,8 +66,6 @@
|
||||
"@deepseek-ai/dsh-client-ui-slots": "workspace:*",
|
||||
"@deepseek-ai/dsh-package-manifest": "workspace:*",
|
||||
"@testing-library/react": "^16.3.3",
|
||||
"@types/react": "~18.3.1",
|
||||
"@types/react-dom": "~18.3.0",
|
||||
"clsx": "^2.1.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { clientBundle } from '../tsdown.client.ts'
|
||||
|
||||
export default clientBundle('@deepseek-ai/dsh-client-ui-plugin-manager', ['lib/types/index.js'], { hostPhase: true })
|
||||
export default clientBundle(import.meta.dirname, ['lib/types/index.js'], { hostPhase: true })
|
||||
@@ -24,8 +24,6 @@
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@deepseek-ai/dsh-client-store": "workspace:*",
|
||||
"@types/react": "~18.3.1",
|
||||
"@types/react-dom": "~18.3.0",
|
||||
"@deepseek-ai/cordis": "workspace:~",
|
||||
"@deepseek-ai/dsh-util-code-language": "workspace:*",
|
||||
"react": "^18.2.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { staticLinked } from '../tsdown.client.ts'
|
||||
|
||||
export default staticLinked(
|
||||
'@deepseek-ai/dsh-client-ui-primitives',
|
||||
import.meta.dirname,
|
||||
['lib/types/index.js'],
|
||||
)
|
||||
@@ -56,7 +56,6 @@
|
||||
"@deepseek-ai/dsh-client-locale": "workspace:*",
|
||||
"@deepseek-ai/dsh-client-test-runtime": "workspace:*",
|
||||
"@deepseek-ai/dsh-client-ui-conversation": "workspace:*",
|
||||
"@types/react": "~18.3.1",
|
||||
"react": "^18.2.0",
|
||||
"@deepseek-ai/dsh-client-ui-input-trigger": "workspace:*",
|
||||
"@deepseek-ai/dsh-client-ui-primitives": "workspace:*",
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { clientBundle } from '../tsdown.client.ts'
|
||||
|
||||
export default clientBundle('@deepseek-ai/dsh-client-ui-reference', ['lib/types/index.js'])
|
||||
export default clientBundle(import.meta.dirname, ['lib/types/index.js'])
|
||||
@@ -44,8 +44,6 @@
|
||||
"@deepseek-ai/cordis": "workspace:~",
|
||||
"@deepseek-ai/dsh-client-test-runtime": "workspace:*",
|
||||
"@deepseek-ai/dsh-client-ui-slots": "workspace:*",
|
||||
"@types/react": "~18.3.1",
|
||||
"@types/react-dom": "~18.3.0",
|
||||
"@types/use-sync-external-store": "^1.5.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { clientBundle } from '../tsdown.client.ts'
|
||||
|
||||
export default clientBundle('@deepseek-ai/dsh-client-ui-renderer', ['lib/types/index.js'])
|
||||
export default clientBundle(import.meta.dirname, ['lib/types/index.js'])
|
||||
@@ -74,8 +74,6 @@
|
||||
"@deepseek-ai/dsh-session": "workspace:*",
|
||||
"@deepseek-ai/dsh-util-values": "workspace:*",
|
||||
"@testing-library/react": "^16.3.3",
|
||||
"@types/react": "~18.3.1",
|
||||
"@types/react-dom": "~18.3.0",
|
||||
"clsx": "^2.1.1",
|
||||
"@deepseek-ai/cordis": "workspace:~",
|
||||
"react": "^18.2.0",
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { clientBundle } from '../tsdown.client.ts'
|
||||
|
||||
export default clientBundle('@deepseek-ai/dsh-client-ui-schedule', ['lib/types/index.js'])
|
||||
export default clientBundle(import.meta.dirname, ['lib/types/index.js'])
|
||||
@@ -55,7 +55,6 @@
|
||||
"@deepseek-ai/dsh-remote-mock": "workspace:*",
|
||||
"@deepseek-ai/dsh-session": "workspace:*",
|
||||
"@deepseek-ai/dsh-util-values": "workspace:*",
|
||||
"@types/react": "~18.3.1",
|
||||
"react": "^18.2.0"
|
||||
},
|
||||
"files": [
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { clientBundle } from '../tsdown.client.ts'
|
||||
|
||||
export default clientBundle('@deepseek-ai/dsh-client-ui-session', ['lib/types/index.js'])
|
||||
export default clientBundle(import.meta.dirname, ['lib/types/index.js'])
|
||||
@@ -64,11 +64,9 @@
|
||||
"@deepseek-ai/dsh-settings": "workspace:*",
|
||||
"@testing-library/react": "^16.3.3",
|
||||
"@types/big.js": "^6.2.2",
|
||||
"@types/react": "~18.3.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"@deepseek-ai/dsh-client-ui-settings-models": "workspace:*",
|
||||
"@types/react-dom": "~18.3.0",
|
||||
"@deepseek-ai/dsh-host-webserver": "workspace:*",
|
||||
"big.js": "^7.0.1",
|
||||
"@deepseek-ai/dsh-client-product-analytics": "workspace:*"
|
||||
|
||||
Loaded 100 of 206 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user