mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-10-26 16:55:08 +00:00
Generate source metadata before package compilation
This commit is contained in:
1 parent
dfa1482c75
commit
9b310b2b4b
31 files changed
+314
-199
No files matched your search
+12
-12
@@ -12,23 +12,23 @@
|
||||
en: a15c7c13f167e5ac
|
||||
zh: 63bcc2ef60c2f344
|
||||
/agent-note-split-build-artifacts-and-build-check-with-build-running-both-concurrently/decision/commands:
|
||||
en: 174241330463841d
|
||||
zh: 7fe361c07e634053
|
||||
en: 3058f44cbcdbb1ea
|
||||
zh: 59cb9a5db6fd1825
|
||||
/agent-note-split-build-artifacts-and-build-check-with-build-running-both-concurrently/decision/steps-and-dependencies:
|
||||
en: 43e78e255306cd43
|
||||
zh: a741d5ef1c7379f3
|
||||
en: c6c77bb1c7acf5e3
|
||||
zh: d53c2358bef42502
|
||||
/agent-note-split-build-artifacts-and-build-check-with-build-running-both-concurrently/decision/scheduling-failure-and-output:
|
||||
en: 4b324f92307d3691
|
||||
zh: e0cf4411a747f7e5
|
||||
en: 6a1b4ed0d9910133
|
||||
zh: f32c6882208ded71
|
||||
/agent-note-split-build-artifacts-and-build-check-with-build-running-both-concurrently/decision/callers:
|
||||
en: 7e22307c84a043d2
|
||||
zh: 73edd48af9ee10ee
|
||||
en: 895968956b86a9d2
|
||||
zh: 5218d9ab8c60a53a
|
||||
/agent-note-split-build-artifacts-and-build-check-with-build-running-both-concurrently/decision/measurements:
|
||||
en: b3a753ac2aab6073
|
||||
zh: 19cbd0e32751896f
|
||||
/agent-note-split-build-artifacts-and-build-check-with-build-running-both-concurrently/decision/completeness-and-lossless-evidence:
|
||||
en: 3cd40e0db9a2c89a
|
||||
zh: 3f708be239779d0a
|
||||
en: bed9a43f84716794
|
||||
zh: 300ba712a7812a99
|
||||
/agent-note-split-build-artifacts-and-build-check-with-build-running-both-concurrently/decision/intentional-trade-offs-not-lossless:
|
||||
en: 14370bc5b54e1278
|
||||
zh: 7ac933092d3e9954
|
||||
@@ -36,5 +36,5 @@
|
||||
en: 54905b912d17c74e
|
||||
zh: 86a298004c8024e3
|
||||
/agent-note-split-build-artifacts-and-build-check-with-build-running-both-concurrently/consequences:
|
||||
en: 083467f68d0f03ed
|
||||
zh: 93400e9994b8c645
|
||||
en: 2e5cf828ac5e2211
|
||||
zh: e3b12f671eb8378d
|
||||
@@ -18,41 +18,25 @@ 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:artifacts` | Produces every artifact of `build`; package projects are fully type-checked as usual, the configured aggregate root programs are not checked | native + metadata → package/library steps → Web | Dependency order | Deleted at start, written after every step passes |
|
||||
| `pnpm run build:check` | Type-checks only the configured aggregate root programs | host-check, client-check | Independent checks 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 |
|
||||
[Source-first ordering](2026-10-18-source-first-metadata-builds.md) prepares metadata before package compilation. [Development](../../../../docs/development.md#typescript-project-layout) and `buildGates()` own the current step graph; each aggregate check starts after its own package declarations exist.
|
||||
|
||||
```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` Typert writes, inside Host tsdown in these scopes and through `scripts/generate-typert.ts` in the typecheck scope. 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.
|
||||
- Client compilation reads generated Remote declarations from the source-only metadata pass. Desktop bundling remains before Client compilation; changing that non-data edge requires separate evidence.
|
||||
- The artifact chain keeps the two non-data edges native → host-src and desktop-bundle → client-src; aggregate checks attach after their package compilation; native and metadata generation may run together.
|
||||
- [`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.
|
||||
- The concurrency limit is `DSH_GATE_CONCURRENCY`, using the repository worker budget by default; aggregate checks may overlap later artifact steps. 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.
|
||||
@@ -63,9 +47,9 @@ native → host-src ─┬─ host-check ─────────────
|
||||
|---|---|---|
|
||||
| `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.ts --scope typecheck`: host-src beside host-typert (the bundle-free, source-only Typert pass `generate-typert.ts`) → client-src, with `tsc -b tsconfig.{host,client}.json` after each face's package compilation; no bundle | Yes |
|
||||
| `typecheck` (run by the pre-push hook) | `build.ts --scope typecheck`: host-typert (the source-only Typert pass `generate-typert.ts`) → package compilation, with `tsc -b tsconfig.{host,client}.json` after each face's package compilation; no bundle | 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 beside the bundle-free Typert pass (`compile-referenced-projects.ts host-contracts`), without any bundle | None |
|
||||
| Prerequisite step of `lint`, `lint:fix`, `doc-typecheck`, and the CI Typert contracts gate | Source-only Typert generation before Host package compilation (`compile-referenced-projects.ts host-contracts`), without any 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 24.9 | `build:artifacts` (through run-gates) | None |
|
||||
| CI python runtime | `build-exe-for-python-sdk.ts --build-artifacts` | None |
|
||||
@@ -114,7 +98,7 @@ The serial branch sample overlapped other work on the host (742 s of foreign CPU
|
||||
| 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 host-typert) | Reported |
|
||||
| Typert analysis error | host-typert | Not reported | host-typert | Reported (through host-typert) | 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.
|
||||
@@ -150,7 +134,7 @@ CI evidence: on PR #6005 both required lanes that run the concurrent `pnpm run b
|
||||
|
||||
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. The typecheck scope and the lint prerequisites generate Typert contracts without bundling ([local gate speed-ups](2026-10-12-local-gate-speedups.md)); the artifact scopes keep generating them inside Host tsdown.
|
||||
Removing or changing `build` in the two required lanes, consumers and windows-build, requires first transferring both aggregate checks to another blocking owner. The typecheck scope and the lint prerequisites generate Typert contracts without bundling ([local gate speed-ups](2026-10-12-local-gate-speedups.md)); artifact scopes use [source-first metadata ordering](2026-10-18-source-first-metadata-builds.md).
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -18,41 +18,25 @@ Status: implemented
|
||||
|
||||
| 命令 | 职责 | 步骤 | 调度 | 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:artifacts` | 产出 `build` 的全部产物;包 project 照常完整类型检查,不检查已配置的聚合根程序 | native + metadata → package/library steps → 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 |
|
||||
[先生成顺序](2026-10-18-source-first-metadata-builds.zh.md)在包编译前准备元数据。[开发指南](../../../../docs/development.zh.md#typescript-project-layout)与 `buildGates()` 负责当前步骤图;每个 aggregate 检查在自己的包声明就绪后开始。
|
||||
|
||||
```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` 由 Typert 写出:这些 scope 在 Host tsdown 中生成,typecheck scope 经 `scripts/generate-typert.ts` 生成。host-check 不读取 Typert 输出,干净的串行构建一直在 Host tsdown 之前完成 Host 聚合检查。
|
||||
- 产物链保留 native → host-src 与 desktop-bundle → client-src 这两条非数据依赖;只有两个 check 步骤并行挂接到链上。
|
||||
- Client 编译读取仅源码元数据阶段生成的 Remote 声明。Desktop 打包仍在 Client 编译之前;改变该非数据依赖需要单独的证据。
|
||||
- 产物链保留 native → host-src 与 desktop-bundle → client-src 这两条非数据依赖;aggregate 检查在包编译后挂接;native 与元数据生成可以并行。
|
||||
- [`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。
|
||||
- 并发上限取 `DSH_GATE_CONCURRENCY`,未设置时采用仓库 worker 预算;aggregate 检查可与后续产物步骤重叠。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` 已使用同样的嵌套。
|
||||
@@ -63,9 +47,9 @@ native → host-src ─┬─ host-check ─────────────
|
||||
|---|---|---|
|
||||
| `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.ts --scope typecheck`:host-src 与 host-typert(不打包、只读源码的 Typert 生成 `generate-typert.ts`)并行 → client-src,各 face 包编译后运行 `tsc -b tsconfig.{host,client}.json`;不含任何 bundle | 有 |
|
||||
| `typecheck`(pre-push hook 运行它) | `build.ts --scope typecheck`:host-typert(只读源码的 Typert 生成 `generate-typert.ts`)→ 包编译,各 face 包编译后运行 `tsc -b tsconfig.{host,client}.json`;不含任何 bundle | 有 |
|
||||
| `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 包编译与不打包的 Typert 生成并行(`compile-referenced-projects.ts host-contracts`),不含任何 bundle | 无 |
|
||||
| `lint`、`lint:fix`、`doc-typecheck` 与 CI 的 Typert contracts gate 的前置步骤 | 仅源码 Typert 生成后进行 Host 包编译(`compile-referenced-projects.ts host-contracts`),不含任何 bundle | 无 |
|
||||
| CI node 24 / snapshots and artifacts(`check:ci:consumers`)、windows node 24 / build & observational(`check:ci:windows-blocking`) | `build` | 有,两个必需 lane |
|
||||
| CI node-compat 24.9 | `build:artifacts`(经 run-gates) | 无 |
|
||||
| CI python runtime | `build-exe-for-python-sdk.ts --build-artifacts` | 无 |
|
||||
@@ -114,7 +98,7 @@ wall 中位数从 249.4 s 降到 172.1 s(−77.3 s,−31%)。各步骤单
|
||||
| 包源码类型错误 | 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-typert) | 报告 |
|
||||
| Typert 分析错误 | host-typert | 不报告 | host-typert | 报告(经 host-typert) | 报告 |
|
||||
| 前置产物缺失 | — | 编译器诊断(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。
|
||||
@@ -150,7 +134,7 @@ CI 证据:PR #6005 上,两个运行并发 `pnpm run build` 的必需 lane
|
||||
|
||||
开发迭代可以用 `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。typecheck scope 与 lint 类前置步骤不打包即生成 Typert 约定([本地门禁提速](2026-10-12-local-gate-speedups.zh.md));产物 scope 仍在 Host tsdown 中生成。
|
||||
删除或改变 consumers 与 windows-build 两个必需 lane 的 `build`,必须先把两个聚合检查转交另一处阻塞型 owner。typecheck scope 与 lint 类前置步骤不打包即生成 Typert 约定([本地门禁提速](2026-10-12-local-gate-speedups.zh.md));产物 scope 采用[先生成元数据的顺序](2026-10-18-source-first-metadata-builds.zh.md)。
|
||||
|
||||
[Typert Remote 方法调用](../architecture/2026-08-02-typert-remote-method-calls.zh.md)描述的“Client 编译前先完成 Host 与 Remote 产物”由 client-src 对 host-bundle 的依赖保证。
|
||||
|
||||
|
||||
@@ -27,8 +27,8 @@
|
||||
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
|
||||
en: b2bea05b59abe4b3
|
||||
zh: 1e3304dec8a77146
|
||||
/agent-note-build-speed-ups-that-keep-outputs-unchanged/decision/6-typert-generates-in-a-worker-thread:
|
||||
en: 0362483a79c07881
|
||||
zh: d84a2390b49c4012
|
||||
@@ -39,8 +39,8 @@
|
||||
en: 80c2ddc5fdff0b5f
|
||||
zh: f5ce309a2e88ff4f
|
||||
/agent-note-build-speed-ups-that-keep-outputs-unchanged/alternatives-considered:
|
||||
en: 4372c82ef0c944dd
|
||||
zh: 5fb70d499baadea7
|
||||
en: 8def70ac09c5250c
|
||||
zh: 5385aa58fc4b0b3b
|
||||
/agent-note-build-speed-ups-that-keep-outputs-unchanged/consequences:
|
||||
en: 93a96d3ed398f348
|
||||
zh: 93b7c7405be981a6
|
||||
@@ -84,7 +84,7 @@ All numbers come from one shared Linux host (256 vCPU, 4 TB memory, load average
|
||||
- **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.
|
||||
- **Why it is lossless:** The workspace-mode comparison found the 743 output files byte-identical; [source-first ordering](2026-10-18-source-first-metadata-builds.md) owns current root-build generation. 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
|
||||
@@ -118,7 +118,7 @@ All numbers come from one shared Linux host (256 vCPU, 4 TB memory, load average
|
||||
- **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 build step in artifact builds (Typert):** After item 6 the worker already overlaps bundling, leaving at most about 1 s. Type checks, which need no bundle, run a source-only generation step ([local gate speed-ups](2026-10-12-local-gate-speedups.md)).
|
||||
- **Move Typert into a separate build step in artifact builds (Typert):** After item 6 the worker already overlaps bundling, leaving at most about 1 s. Type checks, which need no bundle, run a source-only generation step ([local gate speed-ups](2026-10-12-local-gate-speedups.md)). The [source-first ordering decision](2026-10-18-source-first-metadata-builds.md) selects a separate step for compiler dependency ordering and records its no-change build cost; the public worker plugin remains available.
|
||||
- **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.
|
||||
|
||||
@@ -84,7 +84,7 @@ Status: implemented
|
||||
- **目的:** 修正工作区根目录的判定。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` 固定该判定。
|
||||
- **无损依据:** workspace 模式的比较中,743 个产物文件逐字节一致;[先生成顺序](2026-10-18-source-first-metadata-builds.zh.md)负责当前根构建的生成。测试 `resolves the workspace root above a dual-face package leaf` 固定该判定。
|
||||
- **行为差异:** `package` 模式的使用方构建双 face 包时得到该包的 Typert 产物;仓库构建不使用 `package` 模式。
|
||||
|
||||
### 6. Typert 在 Worker Thread 中生成
|
||||
@@ -118,7 +118,7 @@ Status: implemented
|
||||
- **把 `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):** 第 6 项之后 worker 已与打包重叠,剩余收益不超过约 1 s。不需要 bundle 的类型检查运行只读源码的生成步骤([本地门禁提速](2026-10-12-local-gate-speedups.zh.md))。
|
||||
- **在产物构建中把 Typert 移到独立构建步骤(Typert):** 第 6 项之后 worker 已与打包重叠,剩余收益不超过约 1 s。不需要 bundle 的类型检查运行只读源码的生成步骤([本地门禁提速](2026-10-12-local-gate-speedups.zh.md))。 [先生成顺序决策](2026-10-18-source-first-metadata-builds.zh.md)为了编译依赖顺序而选择独立步骤,并记录无改动构建的代价;公开 worker 插件仍可使用。
|
||||
- **按代码内容过滤 `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 的基线跨度内。
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
en: 7430353afba96352
|
||||
zh: 6d2a09619a67ecba
|
||||
/agent-note-local-gate-hook-and-test-speed-ups/decision/1-type-checks-generate-typert-declarations-without-bundling:
|
||||
en: 4dc48e24a3323b7d
|
||||
zh: 7c88325160a41d12
|
||||
en: e9c08ebd00049b44
|
||||
zh: 36e90dc485b4acb7
|
||||
/agent-note-local-gate-hook-and-test-speed-ups/decision/2-the-pre-commit-hook-does-not-regenerate-third-party-notices:
|
||||
en: e2e344f184621172
|
||||
zh: 97d8785aaf9105bf
|
||||
|
||||
@@ -39,23 +39,15 @@ All numbers are wall times from one shared Linux container with a 64-CPU quota o
|
||||
### 1. Type checks generate Typert declarations without bundling
|
||||
|
||||
- **Purpose:** Give `typecheck` and the prerequisite of `lint`, `lint:fix`, `doc-typecheck`, and the CI `typert-contracts` gate the declarations they read, package declarations from `tsc` and Typert declarations, without the Host tsdown pass or the Desktop bundle.
|
||||
- **Change:** `typecheck` is `tsx scripts/build.ts --scope typecheck`, which runs the steps below through the run-gates scheduler ([`scripts/build.ts`](../../../../scripts/build.ts)). The scope builds no native addon, tsdown bundle, Desktop bundle, or Web output, writes no client build record, and leaves runnable `lib/` output as the last `build:artifacts` left it; run `build:artifacts` before launching or testing built output. The prerequisite of `lint`, `lint:fix`, `doc-typecheck`, and `typert-contracts` is `compile-referenced-projects.ts host-contracts`, which runs host-src beside host-typert ([`compile-referenced-projects.ts`](../../../../scripts/compile-referenced-projects.ts)). `build`, `build:artifacts`, benchmark builds, and the PR rehearsal in `release-vendor.yml` run Typert inside the Host tsdown pass.
|
||||
- **Change:** `typecheck` is `tsx scripts/build.ts --scope typecheck`, which runs the steps below through the run-gates scheduler ([`scripts/build.ts`](../../../../scripts/build.ts)). The scope builds no native addon, tsdown bundle, Desktop bundle, or Web output, writes no client build record, and leaves runnable `lib/` output as the last `build:artifacts` left it; run `build:artifacts` before launching or testing built output. The prerequisite of `lint`, `lint:fix`, `doc-typecheck`, and `typert-contracts` is `compile-referenced-projects.ts host-contracts`, which runs host-typert before host-src ([`compile-referenced-projects.ts`](../../../../scripts/compile-referenced-projects.ts)). `build`, `build:artifacts`, benchmark builds, and the vendor rehearsal use the same source-only generation before compilation ([ordering decision](2026-10-18-source-first-metadata-builds.md)).
|
||||
|
||||
| Step | Command | `needs` |
|
||||
|---|---|---|
|
||||
| host-src | `node --max-old-space-size=4096 <tsc> -b <references of tsconfig.host.json>` | — |
|
||||
| host-typert | `node --import tsx/esm scripts/generate-typert.ts` | — |
|
||||
| client-src | `<tsc> -b <references of tsconfig.client.json>` | host-src, host-typert |
|
||||
| host-check | `node --max-old-space-size=4096 <tsc> -b tsconfig.host.json` | host-src |
|
||||
| client-check | `<tsc> -b tsconfig.client.json` | client-src |
|
||||
|
||||
- **Shared generation:** [`scripts/generate-typert.ts`](../../../../scripts/generate-typert.ts) calls `WorkspaceTypertGenerator.generateContributors()` for `WORKSPACE_TYPERT_FACES` ([`scripts/typert-faces.ts`](../../../../scripts/typert-faces.ts)), the faces the Host tsdown plugin also generates, and writes the result with `writeWorkspaceArtifacts()` from [`artifact-files.ts`](../../../../packages/typert/generator/src/artifact-files.ts), the writer the plugin uses. Like the plugin, it skips TypeScript diagnostics, which Host package compilation reports; a Typert analysis error fails host-typert as it fails the Host bundle in `build`. host-typert runs under Node with tsx's ESM hook rather than through `pnpm exec`, whose startup would lengthen the path to Client compilation.
|
||||
- **Shared generation:** [`scripts/generate-typert.ts`](../../../../scripts/generate-typert.ts) calls `WorkspaceTypertGenerator.generateContributors()` for `WORKSPACE_TYPERT_FACES` ([`scripts/typert-faces.ts`](../../../../scripts/typert-faces.ts)), the repository generation faces, and writes the result with `writeWorkspaceArtifacts()` from [`artifact-files.ts`](../../../../packages/typert/generator/src/artifact-files.ts), the writer the plugin uses. Like the plugin, it skips TypeScript diagnostics, which Host package compilation reports; a Typert analysis error fails host-typert before dependent compilation. host-typert runs under Node with tsx's ESM hook rather than through `pnpm exec`, whose startup would lengthen the path to Client compilation.
|
||||
- **Aggregate checks in build mode:** host-check and client-check start after their own face's package compilation, so build mode finds every reference up to date, checks only the aggregate root program, and skips a root program whose tsbuildinfo is current.
|
||||
- **Source-only analysis:** host-typert can run beside host-src because its Host program reads no workspace build output. `tsconfig.base.json` maps `@deepseek-ai/dsh-client-file-upload/types` to source; without that alias the specifier resolves through package `exports` to `lib/types/types.d.ts`. With it, the Host program loads 1,289 files, none of them under a workspace package's `lib/`. `generate-typert.ts` enables `rejectBuildOutputs`: [`build-output-guard.ts`](../../../../packages/typert/generator/src/build-output-guard.ts) fails analysis with a `TypertAnalysisError` when a face program loads a file from a workspace package's `lib/` directory, or cannot resolve an import of a workspace package, as on a tree whose packages are not yet compiled, naming each such import specifier and the `paths` entry that maps it to source. Either way a missing alias fails generation whether or not host-src has run. The tsdown plugin leaves the option off because in `build` Typert always runs after Host package compilation. The [Typert generator README](../../../../packages/typert/generator/README.md) documents the writers and the option.
|
||||
- **Source-only analysis:** host-typert can run before host-src because its Host program reads no workspace build output. `tsconfig.base.json` maps `@deepseek-ai/dsh-client-file-upload/types` to source; without that alias the specifier resolves through package `exports` to `lib/types/types.d.ts`. With it, the Host program loads 1,289 files, none of them under a workspace package's `lib/`. `generate-typert.ts` enables `rejectBuildOutputs`: [`build-output-guard.ts`](../../../../packages/typert/generator/src/build-output-guard.ts) fails analysis with a `TypertAnalysisError` when a face program loads a file from a workspace package's `lib/` directory, or cannot resolve an import of a workspace package, as on a tree whose packages are not yet compiled, naming each such import specifier and the `paths` entry that maps it to source. Either way a missing alias fails generation whether or not host-src has run. The public tsdown plugin retains its independent generation policy; root artifact builds use the source-only pass. The [Typert generator README](../../../../packages/typert/generator/README.md) documents the writers and the option.
|
||||
- **Why it is lossless:** No workspace tsconfig sets `allowJs`, every `exports` `types` condition points into `lib/types/` or at `lib/typert.*`, and every tsdown config sets `dts: false`, so no type check reads tsdown output. The 140 Typert files (28 packages × 5 outputs) are byte-identical across the baseline tsdown pass, the current tsdown pass, `generate-typert.ts` after every Typert output is deleted, and `generate-typert.ts` with all 344 `lib/types` directories hidden; with one Remote method temporarily added to `packages/goal/goal`, the tsdown pass and `generate-typert.ts` also write identical files. `typecheck` checks every package project of both faces and both aggregate root programs, the programs that `build` checks.
|
||||
- **Performance:** `typecheck` with nothing to recheck takes 34.1/35.0 → 21.9/20.0 s. When both aggregate root programs must be rechecked, it takes 83.5 → 76.0/72.1 s, and its critical path is the Host aggregate check at about 66–70 s. The prerequisite of `lint` and `doc-typecheck` takes 52.2/29.6 → 12.5/11.3 s.
|
||||
- **Trade-off:** `typecheck`, `lint`, `lint:fix`, `doc-typecheck`, and `typert-contracts` write Typert declarations but no bundle, so runnable `lib/` JavaScript can lag behind those declarations until the next `build:artifacts`.
|
||||
- **Relation to the build:** [The lossless build speed-ups note](2026-10-11-lossless-build-speedups.md) rejects moving Typert out of the Host tsdown pass for builds, where its Worker Thread already overlaps bundling and a separate step saves at most about 1 s. Type-check commands run no bundler, so generating without one removes the whole Host tsdown pass and the Desktop bundle from their critical path. [The build split note](2026-10-11-build-artifacts-check-split.md) lists every caller and its command.
|
||||
- **Relation to the build:** [Source-first metadata ordering](2026-10-18-source-first-metadata-builds.md) owns generation placement and its measured cost. Declaration-only commands still omit bundling. [The build split note](2026-10-11-build-artifacts-check-split.md) owns command responsibilities.
|
||||
- **Known gap:** Incremental `tsc -b` does not record `lib/typert.remote-client.d.ts`, which Client sources reach through package `exports`, as an input of the consuming project; when a Remote signature changes and no Client source does, an incremental check does not recheck the consumers. The gap is independent of which pass writes the declarations; Issue #6022 tracks it.
|
||||
|
||||
### 2. The pre-commit hook does not regenerate third-party notices
|
||||
|
||||
@@ -39,23 +39,15 @@ Status: implemented
|
||||
### 1. 类型检查不打包地生成 Typert 声明
|
||||
|
||||
- **目的:** 让 `typecheck` 以及 `lint`、`lint:fix`、`doc-typecheck` 与 CI `typert-contracts` 门禁的前置步骤得到它们读取的声明,即 `tsc` 产出的包声明与 Typert 声明,而不运行 Host tsdown 阶段或 Desktop bundle。
|
||||
- **改动:** `typecheck` 是 `tsx scripts/build.ts --scope typecheck`,它通过 run-gates 调度器运行下表中的步骤([`scripts/build.ts`](../../../../scripts/build.ts))。该 scope 不构建 native addon、tsdown bundle、Desktop bundle 或 Web 产物,不写 client build record,可运行的 `lib/` 产物保持上一次 `build:artifacts` 的状态;启动或测试构建产物之前先运行 `build:artifacts`。`lint`、`lint:fix`、`doc-typecheck` 与 `typert-contracts` 的前置步骤是 `compile-referenced-projects.ts host-contracts`,它让 host-src 与 host-typert 并行([`compile-referenced-projects.ts`](../../../../scripts/compile-referenced-projects.ts))。`build`、`build:artifacts`、benchmark 构建与 `release-vendor.yml` 的 PR(Pull Request)演练在 Host tsdown 阶段内运行 Typert。
|
||||
- **改动:** `typecheck` 是 `tsx scripts/build.ts --scope typecheck`,它通过 run-gates 调度器运行下表中的步骤([`scripts/build.ts`](../../../../scripts/build.ts))。该 scope 不构建 native addon、tsdown bundle、Desktop bundle 或 Web 产物,不写 client build record,可运行的 `lib/` 产物保持上一次 `build:artifacts` 的状态;启动或测试构建产物之前先运行 `build:artifacts`。`lint`、`lint:fix`、`doc-typecheck` 与 `typert-contracts` 的前置步骤是 `compile-referenced-projects.ts host-contracts`,它在 host-src 之前运行 host-typert([`compile-referenced-projects.ts`](../../../../scripts/compile-referenced-projects.ts))。`build`、`build:artifacts`、benchmark 构建与 vendor 演练都在编译前采用同一仅源码生成([顺序决策](2026-10-18-source-first-metadata-builds.zh.md))。
|
||||
|
||||
| 步骤 | 命令 | `needs` |
|
||||
|---|---|---|
|
||||
| host-src | `node --max-old-space-size=4096 <tsc> -b <references of tsconfig.host.json>` | — |
|
||||
| host-typert | `node --import tsx/esm scripts/generate-typert.ts` | — |
|
||||
| client-src | `<tsc> -b <references of tsconfig.client.json>` | host-src、host-typert |
|
||||
| host-check | `node --max-old-space-size=4096 <tsc> -b tsconfig.host.json` | host-src |
|
||||
| client-check | `<tsc> -b tsconfig.client.json` | client-src |
|
||||
|
||||
- **共用生成:** [`scripts/generate-typert.ts`](../../../../scripts/generate-typert.ts) 对 `WORKSPACE_TYPERT_FACES`([`scripts/typert-faces.ts`](../../../../scripts/typert-faces.ts),即 Host tsdown 插件同样生成的 face)调用 `WorkspaceTypertGenerator.generateContributors()`,并用 [`artifact-files.ts`](../../../../packages/typert/generator/src/artifact-files.ts) 中插件同样使用的 `writeWorkspaceArtifacts()` 写出结果。与插件一样,它跳过 TypeScript 诊断,这些诊断由 Host 包编译报告;Typert 分析错误使 host-typert 失败,正如它在 `build` 中使 Host bundle 失败。host-typert 由 Node 加 tsx 的 ESM 钩子运行,而不经 `pnpm exec`,因为后者的启动开销会拉长通往 Client 编译的路径。
|
||||
- **共用生成:** [`scripts/generate-typert.ts`](../../../../scripts/generate-typert.ts) 对 `WORKSPACE_TYPERT_FACES`([`scripts/typert-faces.ts`](../../../../scripts/typert-faces.ts),即仓库生成的 face)调用 `WorkspaceTypertGenerator.generateContributors()`,并用 [`artifact-files.ts`](../../../../packages/typert/generator/src/artifact-files.ts) 中插件同样使用的 `writeWorkspaceArtifacts()` 写出结果。与插件一样,它跳过 TypeScript 诊断,这些诊断由 Host 包编译报告;Typert 分析错误使 host-typert 失败,依赖它的编译不会启动。host-typert 由 Node 加 tsx 的 ESM 钩子运行,而不经 `pnpm exec`,因为后者的启动开销会拉长通往 Client 编译的路径。
|
||||
- **以 build mode 运行聚合检查:** host-check 与 client-check 在本侧 face 的包编译之后才开始,因此 build mode 发现所有 references 都是最新的,只检查聚合根程序,并在根程序的 tsbuildinfo 为最新时跳过它。
|
||||
- **只读源码的分析:** host-typert 能与 host-src 并行,是因为它的 Host program 不读取任何工作区构建产物。`tsconfig.base.json` 把 `@deepseek-ai/dsh-client-file-upload/types` 映射到源码;没有这条别名时,该说明符经包 `exports` 解析到 `lib/types/types.d.ts`。有了它,Host program 加载 1,289 个文件,其中没有一个位于工作区包的 `lib/` 下。`generate-typert.ts` 开启 `rejectBuildOutputs`:face program 加载工作区包 `lib/` 目录下的文件,或无法解析对工作区包的 import(例如包尚未编译的工作区)时,[`build-output-guard.ts`](../../../../packages/typert/generator/src/build-output-guard.ts) 以 `TypertAnalysisError` 使分析失败,并指出每个这样的 import 说明符,以及把它映射到源码的 `paths` 条目。因此无论 host-src 是否已运行,缺失的别名都会使生成失败。tsdown 插件不开启该选项,因为在 `build` 中 Typert 总在 Host 包编译之后运行。[Typert generator README](../../../../packages/typert/generator/README.zh.md) 记录了这些写入函数与该选项。
|
||||
- **只读源码的分析:** host-typert 能在 host-src 之前运行,是因为它的 Host program 不读取任何工作区构建产物。`tsconfig.base.json` 把 `@deepseek-ai/dsh-client-file-upload/types` 映射到源码;没有这条别名时,该说明符经包 `exports` 解析到 `lib/types/types.d.ts`。有了它,Host program 加载 1,289 个文件,其中没有一个位于工作区包的 `lib/` 下。`generate-typert.ts` 开启 `rejectBuildOutputs`:face program 加载工作区包 `lib/` 目录下的文件,或无法解析对工作区包的 import(例如包尚未编译的工作区)时,[`build-output-guard.ts`](../../../../packages/typert/generator/src/build-output-guard.ts) 以 `TypertAnalysisError` 使分析失败,并指出每个这样的 import 说明符,以及把它映射到源码的 `paths` 条目。因此无论 host-src 是否已运行,缺失的别名都会使生成失败。公开 tsdown 插件保留独立的生成策略;根产物构建使用仅源码阶段。[Typert generator README](../../../../packages/typert/generator/README.zh.md) 记录了这些写入函数与该选项。
|
||||
- **无损依据:** 没有任何工作区 tsconfig 设置 `allowJs`,每个 `exports` 的 `types` 条件都指向 `lib/types/` 内或 `lib/typert.*`,每个 tsdown 配置都设置 `dts: false`,因此没有类型检查读取 tsdown 产物。140 个 Typert 文件(28 个包 × 5 个输出)在以下场景中逐字节一致:基线 tsdown 阶段、当前 tsdown 阶段、删除全部 Typert 输出后运行 `generate-typert.ts`、隐藏全部 344 个 `lib/types` 目录后运行 `generate-typert.ts`;临时给 `packages/goal/goal` 加一个 Remote 方法时,tsdown 阶段与 `generate-typert.ts` 写出的文件也相同。`typecheck` 检查两个 face 的全部包 project 与两个聚合根程序,即 `build` 检查的程序。
|
||||
- **性能:** 无需重查时 `typecheck` 为 34.1/35.0 → 21.9/20.0 s。两个聚合根程序都需要重查时为 83.5 → 76.0/72.1 s,其关键路径是约 66–70 s 的 Host 聚合检查。`lint` 与 `doc-typecheck` 的前置步骤为 52.2/29.6 → 12.5/11.3 s。
|
||||
- **取舍:** `typecheck`、`lint`、`lint:fix`、`doc-typecheck` 与 `typert-contracts` 写出 Typert 声明但不写 bundle,因此在下一次 `build:artifacts` 之前,可运行的 `lib/` JavaScript 可能落后于这些声明。
|
||||
- **与构建的关系:** [无损构建提速 Note](2026-10-11-lossless-build-speedups.zh.md) 否决了在构建中把 Typert 移出 Host tsdown 阶段:构建中它的 Worker Thread 已与打包重叠,独立步骤最多只能省约 1 s。类型检查命令不运行打包器,因此不打包地生成会把整个 Host tsdown 阶段与 Desktop bundle 移出它们的关键路径。[构建拆分 Note](2026-10-11-build-artifacts-check-split.zh.md) 列出每个调用方及其命令。
|
||||
- **与构建的关系:** [先生成元数据的顺序](2026-10-18-source-first-metadata-builds.zh.md)负责生成位置及其测得的代价。仅生成声明的命令仍省略打包。[构建拆分 Note](2026-10-11-build-artifacts-check-split.zh.md)负责命令职责。
|
||||
- **已知缺口:** 增量 `tsc -b` 不把 Client 源码经包 `exports` 访问到的 `lib/typert.remote-client.d.ts` 记为消费方 project 的输入;Remote 签名改变而 Client 源码未变时,增量检查不会重查消费方。这一缺口与由哪一种生成写出声明无关,由 Issue #6022 跟踪。
|
||||
|
||||
### 2. pre-commit 钩子不重新生成第三方声明
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Bilingual-pair consistency record for 2026-10-18-source-first-metadata-builds.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-18-source-first-metadata-builds.md
|
||||
/agent-note-generate-metadata-before-package-compilation:
|
||||
en: 734f339ae4f8dc93
|
||||
zh: 5adf12518d4be31d
|
||||
/agent-note-generate-metadata-before-package-compilation/problem:
|
||||
en: 3c40c9bd375290a6
|
||||
zh: dc8d892bdd08c6b4
|
||||
/agent-note-generate-metadata-before-package-compilation/decision:
|
||||
en: 7121ef78b28675af
|
||||
zh: ef7538377c6d367e
|
||||
/agent-note-generate-metadata-before-package-compilation/measurements:
|
||||
en: 80048ef5e9bc06ea
|
||||
zh: f74ec5f0fc617080
|
||||
/agent-note-generate-metadata-before-package-compilation/alternatives-considered:
|
||||
en: 81fde4514a8b6fc0
|
||||
zh: 4739f5c7978e7db2
|
||||
/agent-note-generate-metadata-before-package-compilation/consequences:
|
||||
en: 5f7d7bbeae4c86a7
|
||||
zh: 32fe8710a1c3015e
|
||||
@@ -0,0 +1,44 @@
|
||||
# Agent Note: Generate metadata before package compilation
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-10-18-source-first-metadata-builds.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
A Host package can consume generated Remote declarations without becoming a Client package. Generating metadata during Host bundling forces the build to identify those consumers, delay their compilation, and propagate that delay to their callers. TypeScript project references already describe the package dependencies; maintaining another source-import graph duplicates ordering work.
|
||||
|
||||
## Decision
|
||||
|
||||
Artifact and declaration-only builds run source-only Typert generation before package compilation. The existing generator rejects workspace build outputs and unresolved workspace imports, so a metadata producer cannot depend on its own generated declarations through another source package. TypeScript retains compiler-face ownership and rejects project-reference cycles. Generation failure stops dependent compilation and bundling.
|
||||
|
||||
The root bundler uses `decoratorPlugin()` to lower standard TypeScript decorators without generating metadata again. The public `typertPlugin()` retains package/workspace generation and its Worker Thread behavior. Public Node Client providers, when present, must bundle before their consumers; source-first metadata does not remove that runtime dependency. [Development](../../../../docs/development.md#typescript-project-layout) owns the current command order.
|
||||
|
||||
This ordering replaces only the generation placement in [local gate speed-ups](2026-10-12-local-gate-speedups.md), [lossless build speed-ups](2026-10-11-lossless-build-speedups.md), and [the build/check split](2026-10-11-build-artifacts-check-split.md). Their other optimizations, failure guarantees, public plugin behavior, and historical measurements remain useful and stay active.
|
||||
|
||||
## Measurements
|
||||
|
||||
The local experiment uses macOS ARM64, Node 24.19.0 and TypeScript 6.0.3. `DSH_GATE_CONCURRENCY=1 pnpm run build:artifacts --profile official` runs baseline/candidate/candidate/baseline. Each run starts after `pnpm run clean`, then repeats without source changes. Installation and cleanup are excluded; native preparation, package compilation, bundles, Desktop, Web and build-record publication are included. Other owned heavy jobs pause; unrelated host load remains possible. Filesystem caches are not flushed.
|
||||
|
||||
| Inputs | Clean baseline / candidate (s) | No-change baseline / candidate (s) | Clean peak RSS baseline / candidate (MiB) |
|
||||
|---|---|---|---|
|
||||
| Without Node Session consumers | 56.67, 53.55 / 56.29, 56.37 | 13.64, 13.74 / 15.32, 15.26 | 3855, 3858 / 3255, 3268 |
|
||||
| With Node Session consumers and delayed-stage removal | 61.18, 61.41 / 56.27, 56.44 | 20.08, 20.09 / 15.98, 16.09 | 4013, 4012 / 3278, 3291 |
|
||||
|
||||
RSS is summed over descendant processes approximately every 100ms; shared pages count more than once, so this is not unique physical memory. Two samples per case are local evidence, not a CI budget. All 150 generated Typert/Remote files are byte-identical in both comparisons. Complete inventories contain 12,246 and 12,352 files respectively; only the changed decorator-plugin module's four compiler outputs differ.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Keep generation inside Host bundling.** It preserves overlap and the faster standalone no-change build, but consumers of generated declarations require a separate delayed compilation schedule.
|
||||
|
||||
**Hand-maintain delayed packages or change their compiler faces.** Both make package ownership depend on build placement and add an inventory beside project references.
|
||||
|
||||
**Use source-first generation only for type checks.** Artifact builds would still need the consumer scanner, leaving two schedules to maintain.
|
||||
|
||||
**Remove the public generation plugin.** Its package/workspace API and Worker Thread failure ownership remain supported; repository scheduling does not justify deleting that API.
|
||||
|
||||
## Consequences
|
||||
|
||||
The standalone prerequisite costs 1.60s (+11.7%) in median no-change artifact time because generation no longer overlaps bundling. Its clean difference is +1.22s, smaller than the baseline spread. This is ordering support, not an independent speedup. With delayed stages removed, the measured consumer build improves by 4.94s clean and 4.05s without changes. Peak summed RSS decreases in both cases.
|
||||
|
||||
Source-generation tests reject metadata dependency cycles with absent and preexisting outputs. Compiler fixtures reject project-reference cycles before bundling. Decorator tests exercise both plugin entry points; complete builds and built Node-provider smokes retain runtime exports and identities. Wine's separate command sequence prepares metadata explicitly; its source guard rejects missing preparation, while native Windows execution remains CI-owned.
|
||||
@@ -0,0 +1,44 @@
|
||||
# Agent Note: 在包编译前生成元数据
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-10-18-source-first-metadata-builds.md) | 中文
|
||||
|
||||
## Problem
|
||||
|
||||
Host 包可以消费生成的 Remote 声明,而不必成为 Client 包。如果在 Host 打包期间生成元数据,构建就必须识别这些消费方、延迟其编译,并把延迟传播给调用方。TypeScript project reference 已经描述包依赖;再维护一张源码导入图会重复处理顺序。
|
||||
|
||||
## Decision
|
||||
|
||||
产物构建与仅生成声明的构建都在包编译之前运行仅源码的 Typert 生成。现有生成器拒绝工作区构建产物与无法解析的工作区导入,因此元数据生产者不能经由另一个源码包依赖自身生成的声明。TypeScript 保留 compiler face 的归属,并拒绝 project reference 环。生成失败后不启动依赖它的编译与打包。
|
||||
|
||||
根打包器使用 `decoratorPlugin()` 转换标准 TypeScript 装饰器,不重复生成元数据。公开的 `typertPlugin()` 保留 package/workspace 生成与 Worker Thread 行为。存在公开 Node Client provider 时,仍须在消费方之前打包;仅源码生成不消除该运行时依赖。[开发指南](../../../../docs/development.zh.md#typescript-project-layout)负责当前命令顺序。
|
||||
|
||||
该顺序只替代[本地门禁提速](2026-10-12-local-gate-speedups.zh.md)、[无损构建提速](2026-10-11-lossless-build-speedups.zh.md)与[构建/检查拆分](2026-10-11-build-artifacts-check-split.zh.md)中的生成位置。它们的其他优化、失败保证、公开插件行为与历史测量仍有价值,因此保留为活跃记录。
|
||||
|
||||
## Measurements
|
||||
|
||||
本地实验使用 macOS ARM64、Node 24.19.0 与 TypeScript 6.0.3。`DSH_GATE_CONCURRENCY=1 pnpm run build:artifacts --profile official` 按基线/候选/候选/基线顺序运行。每轮先执行 `pnpm run clean`,再在源码不变的情况下重跑。安装与清理不计时;native 准备、包编译、bundle、Desktop、Web 与构建记录发布计入时间。暂停本任务的其他重负载工作;主机上仍可能有无关负载。不清空文件系统缓存。
|
||||
|
||||
| 输入 | 干净构建基线/候选 (s) | 无改动构建基线/候选 (s) | 干净构建 RSS 峰值基线/候选 (MiB) |
|
||||
|---|---|---|---|
|
||||
| 不含 Node Session 消费方 | 56.67, 53.55 / 56.29, 56.37 | 13.64, 13.74 / 15.32, 15.26 | 3855, 3858 / 3255, 3268 |
|
||||
| 包含 Node Session 消费方并删除后置阶段 | 61.18, 61.41 / 56.27, 56.44 | 20.08, 20.09 / 15.98, 16.09 | 4013, 4012 / 3278, 3291 |
|
||||
|
||||
RSS 约每 100ms 汇总所有子孙进程;共享页面会重复计数,因此不代表唯一物理内存。每种情况的两个样本是本地证据,不是 CI 预算。两组比较中的全部 150 个 Typert/Remote 生成文件逐字节相同。完整清单分别包含 12,246 与 12,352 个文件;只有装饰器插件模块的四个编译产物不同。
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**保留 Host 打包内的生成。** 它保留重叠执行与更快的独立无改动构建,但生成声明的消费方需要单独的后置编译调度。
|
||||
|
||||
**手工维护后置包,或改变它们的 compiler face。** 两者都让包归属受构建位置影响,并在 project reference 之外增加一份清单。
|
||||
|
||||
**仅在类型检查中采用先生成顺序。** 产物构建仍需要消费方扫描器,留下两套需要维护的调度。
|
||||
|
||||
**删除公开生成插件。** 它的 package/workspace API 与 Worker Thread 失败归属仍受支持;仓库调度改变不能成为删除该 API 的理由。
|
||||
|
||||
## Consequences
|
||||
|
||||
独立前置改动使无改动产物构建的中位数增加 1.60s(+11.7%),因为生成不再与打包重叠。干净构建增加 1.22s,小于基线的样本波动。该改动提供顺序支持,并非独立提速。删除后置阶段后,测得的消费方构建在干净状态下快 4.94s,无改动时快 4.05s。两组的进程树 RSS 峰值都下降。
|
||||
|
||||
源码生成测试在产物缺失和已存在时均拒绝元数据依赖环。编译器 fixture 在打包前拒绝 project reference 环。装饰器测试覆盖两个插件入口;完整构建与构建产物上的 Node provider 冒烟保留运行时导出和身份。Wine 的独立命令序列显式准备元数据;源码守卫拒绝缺失的准备步骤,原生 Windows 执行仍由 CI 负责。
|
||||
@@ -21,8 +21,8 @@
|
||||
en: 3221d1076554001f
|
||||
zh: 29f166be6a5c04d9
|
||||
/development-guide/contributor-reference/typescript-project-layout:
|
||||
en: f0582a3a145a4d93
|
||||
zh: 9cab4abec6935b3c
|
||||
en: d96696154740ee48
|
||||
zh: 3c236513c6729cb6
|
||||
/development-guide/contributor-reference/environment-variables:
|
||||
en: dba2c72ee6ffa930
|
||||
zh: b65c27fbb9129ec8
|
||||
|
||||
+2
-1
@@ -78,6 +78,7 @@ Six packages split Host and Client tsconfigs: `api/remotes`, `api/gateway`, `api
|
||||
|
||||
```sh
|
||||
pnpm run build:native-system
|
||||
pnpm exec tsx scripts/generate-typert.ts
|
||||
tsc -b <tsconfig.host.json references>
|
||||
tsdown --env.DSH_BUILD_FACE host
|
||||
pnpm --filter @deepseek-ai/dsh-desktop run bundle
|
||||
@@ -103,7 +104,7 @@ PR artifact builders run `build:artifacts`; required Linux and Windows normal bu
|
||||
|
||||
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 analyzes Host types, seeded by `tsconfig.host.json`, and generates both Host reflection artifacts and the Host-for-Client Remote projection; Client tsdown does not start Typert. Host tsdown runs it as a plugin, and [`generate-typert.ts`](../scripts/generate-typert.ts) runs the same generation without bundling and writes byte-identical files. The bundle-free pass reads workspace packages only from source and fails when the Host program would load a package's `lib/` output, naming the import and the `paths` alias to add, so it can run beside Host package compilation. Client package compilation always follows one of the two passes because Client sources import the generated Remote declarations. `pnpm run typecheck` runs the `build.ts --scope typecheck` graph: Host package compilation beside the bundle-free Typert pass, then Client package compilation, with each aggregate checked in build mode (`tsc -b`) once its own face's packages compile; it skips the native addon, every bundle, Web, and the client build record. `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 bundle-free Typert pass (`compile-referenced-projects.ts host-contracts`). None of these commands writes bundles: the runnable `lib/` output stays as the last `build:artifacts` left it, so run `build:artifacts` before launching or testing built output.
|
||||
Typert analyzes Host types through `tsconfig.host.json` and generates Host reflection and Host-for-Client Remote declarations before package compilation. The source-only [`generate-typert.ts`](../scripts/generate-typert.ts) pass rejects workspace imports that require built output, including metadata dependencies that return to generated declarations. Host tsdown lowers source decorators without generating metadata again; Client tsdown also consumes the existing metadata. `pnpm run typecheck` generates metadata, compiles Host and Client packages in reference order, and checks each aggregate with `tsc -b` after its package compilation. It skips the native addon, bundles, Web, and the client build record. `lint`, `lint:fix`, and `doc-typecheck` prepare metadata and Host package declarations through `compile-referenced-projects.ts host-contracts`; generation failure stops dependent compilation. These declaration-only commands do not update runtime bundles: run `build:artifacts` before launching or testing built output.
|
||||
|
||||
`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.
|
||||
|
||||
|
||||
@@ -82,6 +82,7 @@ Host 与 Client 保持两个 aggregate program,是因为两侧在相同键下
|
||||
|
||||
```sh
|
||||
pnpm run build:native-system
|
||||
pnpm exec tsx scripts/generate-typert.ts
|
||||
tsc -b <tsconfig.host.json references>
|
||||
tsdown --env.DSH_BUILD_FACE host
|
||||
pnpm --filter @deepseek-ai/dsh-desktop run bundle
|
||||
@@ -107,7 +108,7 @@ PR 产物 builder 运行 `build:artifacts`;必需的 Linux 与 Windows 普通
|
||||
|
||||
两次 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 以 `tsconfig.host.json` 为种子分析 Host 类型,生成 Host 反射产物及 Host-for-Client Remote 投影;Client tsdown 不启动 Typert。Host tsdown 以插件形式运行它,[`generate-typert.ts`](../scripts/generate-typert.ts) 则不打包地执行同一生成,写出逐字节相同的文件。不打包的这一轮只从源码读取工作区包;若 Host program 会载入某个包的 `lib/` 产物,它就失败并指出该 import 及应补的 `paths` 别名,因此可以与 Host 包编译并行。Client 源码 import 生成的 Remote 声明,所以 Client 包编译总在这两种生成之一之后。`pnpm run typecheck` 运行 `build.ts --scope typecheck` 图:Host 包编译与不打包的 Typert 生成并行,之后是 Client 包编译,每个 aggregate 在本 face 的包编译完成后以 build mode(`tsc -b`)检查;它跳过 native addon、所有 bundle、Web 以及 client build record。`lint`、`lint:fix` 与 `doc-typecheck` 读取生成的声明但不依赖 Host aggregate 检查,因此只准备 Host 包编译与不打包的 Typert 生成(`compile-referenced-projects.ts host-contracts`)。这些命令都不写 bundle:可运行的 `lib/` 产物保持上一次 `build:artifacts` 的状态,因此启动或测试构建产物之前先运行 `build:artifacts`。
|
||||
Typert 通过 `tsconfig.host.json` 分析 Host 类型,在包编译之前生成 Host 反射和 Host-for-Client Remote 声明。仅源码的 [`generate-typert.ts`](../scripts/generate-typert.ts) 阶段拒绝依赖构建产物的工作区导入,包括经由依赖回到生成声明的元数据依赖。Host tsdown 转换源码装饰器,但不重复生成元数据;Client tsdown 同样使用已有元数据。`pnpm run typecheck` 生成元数据,按 reference 顺序编译 Host 与 Client 包,并在每个 aggregate 的包编译完成后以 `tsc -b` 检查它。该命令跳过 native addon、bundle、Web 和 client build record。`lint`、`lint:fix` 与 `doc-typecheck` 通过 `compile-referenced-projects.ts host-contracts` 准备元数据与 Host 包声明;生成失败时不启动依赖它的编译。这些仅生成声明的命令不更新运行时 bundle;启动或测试构建产物之前应运行 `build:artifacts`。
|
||||
|
||||
`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 会重写记录覆盖的产物。
|
||||
|
||||
|
||||
@@ -27,8 +27,8 @@
|
||||
en: 8227097859169cdf
|
||||
zh: 249790ed62e8558a
|
||||
/deepseek-ai-dsh-typert-generator/use-this-package/running-generation-inside-a-tsdown-build:
|
||||
en: ebcb4850a5620d93
|
||||
zh: 55f6f54265830f28
|
||||
en: 6b4df2ee7a16f46c
|
||||
zh: dda8992e29630761
|
||||
/deepseek-ai-dsh-typert-generator/use-this-package/generating-without-a-bundler:
|
||||
en: e1afb26aea87fe46
|
||||
zh: ed3cdfde78d87825
|
||||
|
||||
@@ -66,7 +66,9 @@ JSON, YAML, SVG, PNG, JPEG, and WebP resource exports contribute no TypeScript d
|
||||
|
||||
### Running generation inside a tsdown build
|
||||
|
||||
The package's `./tsdown` subpath provides `typertPlugin()` for the root tsdown config: it lowers standard decorators in TypeScript dependencies before bundling and emits the model-driven face artifacts at the package output root. A module-id filter on its `transform` hook keeps non-TypeScript modules out of JavaScript. The workspace root is the nearest ancestor of the bundle that contains both `tsconfig.host.json` and `packages/`, so a package's own face leaf config never qualifies. In `package` mode it generates synchronously in the bundled package's `writeBundle` and emits only that package.
|
||||
`decoratorPlugin()` lowers standard TypeScript decorators without reading or writing metadata. The repository build uses it after source-only generation; consumers that generate metadata during bundling use `typertPlugin()`.
|
||||
|
||||
The package's `./tsdown` subpath provides `typertPlugin()` for builds that generate metadata during bundling: it lowers standard decorators in TypeScript dependencies before bundling and emits the model-driven face artifacts at the package output root. A module-id filter on its `transform` hook keeps non-TypeScript modules out of JavaScript. The workspace root is the nearest ancestor of the bundle that contains both `tsconfig.host.json` and `packages/`, so a package's own face leaf config never qualifies. In `package` mode it generates synchronously in the bundled package's `writeBundle` and emits only that package.
|
||||
|
||||
In `workspace` mode it emits every explicit contributor once without blocking bundling: the first package build's `buildStart` starts generation in a Worker Thread, and the first `writeBundle` awaits the result and writes every artifact while later bundles return immediately. An analysis error, a worker crash or early exit, or a malformed worker response fails that bundle, and therefore the tsdown pass, with a `TypertWorkerError` naming the workspace and cause; the worker is terminated before the failure is reported.
|
||||
|
||||
|
||||
@@ -66,7 +66,9 @@ JSON、YAML、SVG、PNG、JPEG 和 WebP 资源导出不提供 TypeScript 声明
|
||||
|
||||
### 在 tsdown 构建中运行生成
|
||||
|
||||
包的 `./tsdown` 子路径为根 tsdown 配置提供 `typertPlugin()`:它在打包前转换 TypeScript 依赖中的标准装饰器,并在包输出根目录生成模型驱动的 face 产物。其 `transform` 钩子带有模块 id 过滤器,非 TypeScript 模块不会进入 JavaScript。工作区根目录是打包输出最近的、同时包含 `tsconfig.host.json` 与 `packages/` 的祖先目录,因此包自身的 face 叶子配置永远不会被当作根目录。`package` 模式在当前包的 `writeBundle` 中同步生成,并只生成该包。
|
||||
`decoratorPlugin()` 转换标准 TypeScript 装饰器,不读取或写入元数据。仓库构建在仅源码生成完成后使用它;需要在打包时生成元数据的消费方使用 `typertPlugin()`。
|
||||
|
||||
包的 `./tsdown` 子路径为需要在打包时生成元数据的构建提供 `typertPlugin()`:它在打包前转换 TypeScript 依赖中的标准装饰器,并在包输出根目录生成模型驱动的 face 产物。其 `transform` 钩子带有模块 id 过滤器,非 TypeScript 模块不会进入 JavaScript。工作区根目录是打包输出最近的、同时包含 `tsconfig.host.json` 与 `packages/` 的祖先目录,因此包自身的 face 叶子配置永远不会被当作根目录。`package` 模式在当前包的 `writeBundle` 中同步生成,并只生成该包。
|
||||
|
||||
`workspace` 模式对每个显式贡献方各生成一次,且不阻塞打包:第一个包构建的 `buildStart` 在 Worker Thread 中启动生成,第一个 `writeBundle` 等待结果并写入全部产物,之后的打包立即返回。分析错误、worker 崩溃或提前退出、worker 响应格式错误,都会以指明工作区与原因的 `TypertWorkerError` 使该打包失败,从而使整个 tsdown 阶段失败;报告失败之前 worker 已被终止。
|
||||
|
||||
|
||||
@@ -58,33 +58,12 @@ type GenerationOutcome =
|
||||
| { readonly ok: false; readonly error: TypertWorkerError }
|
||||
|
||||
/**
|
||||
* Create the decorator-lowering and typert-generation plugin for the root tsdown config.
|
||||
* @param pluginOptions - package/workspace emission mode and independent program faces.
|
||||
* @returns a rolldown-compatible plugin that lowers source decorators and emits local and Host-for-Client artifacts.
|
||||
* Lower standard TypeScript decorators when metadata was generated before bundling.
|
||||
* @returns a rolldown-compatible transform without metadata generation or artifact writes.
|
||||
*/
|
||||
export function typertPlugin(pluginOptions: TypertPluginOptions = {}): TypertPlugin {
|
||||
const artifactsByRoot = new Map<string, readonly WorkspaceEmitResult[]>()
|
||||
const generations = new Map<string, Promise<GenerationOutcome>>()
|
||||
const emittedWorkspaces = new Set<string>()
|
||||
const workspaceGeneration = (root: string): Promise<GenerationOutcome> => {
|
||||
let generation = generations.get(root)
|
||||
if (generation === undefined) {
|
||||
generation = generateInWorker({
|
||||
root,
|
||||
...(pluginOptions.faces === undefined ? {} : { faces: pluginOptions.faces }),
|
||||
options: TSC_OWNED_DIAGNOSTICS,
|
||||
})
|
||||
generations.set(root, generation)
|
||||
}
|
||||
return generation
|
||||
}
|
||||
export function decoratorPlugin(): Pick<TypertPlugin, 'name' | 'transform'> {
|
||||
return {
|
||||
name: 'dsh-typert-generator',
|
||||
buildStart(inputOptions) {
|
||||
// tsdown builds every workspace package concurrently through this one
|
||||
// plugin instance; the first package build to start launches generation.
|
||||
if (pluginOptions.mode === 'workspace') void workspaceGeneration(findWorkspaceRoot(inputOptions.cwd))
|
||||
},
|
||||
name: 'dsh-decorators',
|
||||
transform: {
|
||||
// Rolldown evaluates the id filter natively, so non-TypeScript modules
|
||||
// never cross into this JavaScript hook.
|
||||
@@ -107,6 +86,38 @@ export function typertPlugin(pluginOptions: TypertPluginOptions = {}): TypertPlu
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the decorator-lowering and Typert-generation plugin for generation during bundling.
|
||||
* @param pluginOptions - package/workspace emission mode and independent program faces.
|
||||
* @returns a rolldown-compatible plugin that lowers source decorators and emits local and Host-for-Client artifacts.
|
||||
*/
|
||||
export function typertPlugin(pluginOptions: TypertPluginOptions = {}): TypertPlugin {
|
||||
const artifactsByRoot = new Map<string, readonly WorkspaceEmitResult[]>()
|
||||
const generations = new Map<string, Promise<GenerationOutcome>>()
|
||||
const emittedWorkspaces = new Set<string>()
|
||||
const workspaceGeneration = (root: string): Promise<GenerationOutcome> => {
|
||||
let generation = generations.get(root)
|
||||
if (generation === undefined) {
|
||||
generation = generateInWorker({
|
||||
root,
|
||||
...(pluginOptions.faces === undefined ? {} : { faces: pluginOptions.faces }),
|
||||
options: TSC_OWNED_DIAGNOSTICS,
|
||||
})
|
||||
generations.set(root, generation)
|
||||
}
|
||||
return generation
|
||||
}
|
||||
return {
|
||||
...decoratorPlugin(),
|
||||
name: 'dsh-typert-generator',
|
||||
buildStart(inputOptions) {
|
||||
// tsdown builds every workspace package concurrently through this one
|
||||
// plugin instance; the first package build to start launches generation.
|
||||
if (pluginOptions.mode === 'workspace') void workspaceGeneration(findWorkspaceRoot(inputOptions.cwd))
|
||||
},
|
||||
async writeBundle(bundleOptions) {
|
||||
// options.dir is the package's absolute outDir (<package>/lib); its
|
||||
// nearest package.json owns the bundle even when a custom config writes
|
||||
|
||||
@@ -65,7 +65,7 @@ vi.mock('../src/workspace.ts', async importOriginal => ({
|
||||
},
|
||||
}))
|
||||
|
||||
const { TypertWorkerError, typertPlugin } = await import('../src/tsdown-plugin.ts')
|
||||
const { TypertWorkerError, typertPlugin, decoratorPlugin } = await import('../src/tsdown-plugin.ts')
|
||||
const roots: string[] = []
|
||||
|
||||
afterEach(() => {
|
||||
@@ -76,8 +76,8 @@ afterEach(() => {
|
||||
})
|
||||
|
||||
describe('typertPlugin', () => {
|
||||
it('lowers standard decorators in TypeScript source dependencies', () => {
|
||||
const { transform } = typertPlugin()
|
||||
it.each([typertPlugin, decoratorPlugin])('lowers standard decorators in TypeScript source dependencies through %s', (factory) => {
|
||||
const { transform } = factory()
|
||||
expect(transform.handler('export const value = 1\n', '/workspace/src/plain.ts')).toBeUndefined()
|
||||
expect(transform.handler('@sealed\nexport class Example {}\n', '/workspace/src/example.ts?raw')?.code)
|
||||
.not.toContain('@sealed')
|
||||
|
||||
@@ -7,7 +7,8 @@ const root = resolve(import.meta.dirname, '..')
|
||||
|
||||
const artifactGraph = {
|
||||
'native': [],
|
||||
'host-src': ['native'],
|
||||
'host-typert': [],
|
||||
'host-src': ['native', 'host-typert'],
|
||||
'host-bundle': ['host-src'],
|
||||
'desktop-bundle': ['host-bundle'],
|
||||
'client-src': ['desktop-bundle'],
|
||||
@@ -80,12 +81,12 @@ describe('repository build steps', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('type-checks every program with the source-only Typert pass beside Host compilation', () => {
|
||||
it('type-checks every program after source-only Typert generation', () => {
|
||||
const gates = buildGates(root, 'typecheck')
|
||||
expect(graph(gates)).toEqual({
|
||||
'host-src': [],
|
||||
'host-typert': [],
|
||||
'client-src': ['host-src', 'host-typert'],
|
||||
'host-src': ['host-typert'],
|
||||
'client-src': ['host-src'],
|
||||
'host-check': ['host-src'],
|
||||
'client-check': ['client-src'],
|
||||
})
|
||||
|
||||
+4
-7
@@ -68,10 +68,9 @@ export function parseBuildArguments(args: readonly string[]): BuildArguments {
|
||||
|
||||
/**
|
||||
* Construct the build steps of one scope as a gate dependency graph.
|
||||
* Artifact steps keep the serial order native, Host compilation, Host bundle (Typert),
|
||||
* Desktop bundle, Client compilation, Client bundle, then Web. The typecheck scope runs Host
|
||||
* compilation beside the bundle-free Typert pass, which reads only source, and then Client
|
||||
* compilation, which needs both outputs; it writes no bundle. In the complete and typecheck
|
||||
* Artifact steps generate Typert from source before Host compilation, then bundle Host,
|
||||
* Desktop, and Client artifacts before Web. Native output also precedes Host compilation.
|
||||
* The typecheck scope generates Typert before compiling Host and Client packages; it writes no bundle. In the complete and typecheck
|
||||
* scopes each aggregate check waits only for its own face's package compilation; alone, the
|
||||
* checks have no predecessors and read whatever declarations already exist. The typecheck scope
|
||||
* checks aggregates in build mode, which skips a program whose tsbuildinfo is current; the other
|
||||
@@ -92,7 +91,7 @@ export function buildGates(
|
||||
if (scope === 'complete' || scope === 'artifacts') {
|
||||
gates.push(pnpmGate('native', 'native addon', ['run', 'build:native-system'], [], env))
|
||||
for (const step of libraryBuildSteps(root, 'all')) {
|
||||
gates.push(libraryGate(step, step.id === 'host-src' ? ['native'] : step.needs, env))
|
||||
gates.push(libraryGate(step, step.id === 'host-src' ? ['native', ...step.needs] : step.needs, env))
|
||||
}
|
||||
gates.push(pnpmGate('web', 'Web build', ['run', 'build:web'], ['client-bundle'], env))
|
||||
}
|
||||
@@ -107,8 +106,6 @@ export function buildGates(
|
||||
displayCommand: `tsc ${scope === 'typecheck' ? '-b' : '-p'} tsconfig.${face}.json`,
|
||||
command: process.execPath,
|
||||
args: scope === 'typecheck' ? aggregateBuildArgs(face) : aggregateCheckArgs(face),
|
||||
// No Host aggregate file imports a generated `/typert` or `/remote` subpath, so
|
||||
// host-check never reads what a concurrent host-typert step writes.
|
||||
needs: scope === 'check' ? [] : [`${face}-src`],
|
||||
env,
|
||||
})
|
||||
|
||||
@@ -668,12 +668,15 @@ describe('CI workflow', () => {
|
||||
expect(uploadIndex).toBeGreaterThan(benchmarkIndex)
|
||||
})
|
||||
|
||||
it('gives the Wine Host TypeScript compile the repository heap budget', () => {
|
||||
it('generates metadata before the Wine Host TypeScript compile and retains its heap budget', () => {
|
||||
const wineGates = readFileSync(resolve(root, 'scripts/wine-windows-gates.sh'), 'utf8')
|
||||
|
||||
expect(wineGates).toContain(
|
||||
'wine_node "$scratch/logs/host-tsc.log" --max-old-space-size=4096 "$tsc_js" -b tsconfig.host.json --pretty false',
|
||||
)
|
||||
const metadata = wineGates.indexOf('wine_node "$scratch/logs/host-typert.log" --import tsx/esm scripts/generate-typert.ts')
|
||||
expect(metadata).toBeGreaterThan(-1)
|
||||
expect(metadata).toBeLessThan(wineGates.indexOf('wine_node "$scratch/logs/host-tsc.log"'))
|
||||
})
|
||||
|
||||
it('cancels superseded master runs without changing the post-merge job inventory', () => {
|
||||
|
||||
@@ -14,6 +14,7 @@ const compiler = fileURLToPath(import.meta.resolve('typescript/bin/tsc'))
|
||||
function fixture(): { root: string; leaf: string } {
|
||||
const root = mkdtempSync(join(tmpdir(), 'dsh-referenced-projects-'))
|
||||
onTestFinished(() => { removeFixtureSafely(root) })
|
||||
mkdirSync(join(root, 'packages'))
|
||||
const leaf = join(root, 'leaf package')
|
||||
mkdirSync(join(leaf, 'src'), { recursive: true })
|
||||
writeFileSync(join(root, 'package.json'), JSON.stringify({ type: 'module' }))
|
||||
@@ -110,6 +111,31 @@ describe('internal artifact build order', () => {
|
||||
expect(commands()).toEqual(expected.map(args => ({ args, title: 'Inherited Build Title' })))
|
||||
})
|
||||
|
||||
it('compiles a Host metadata consumer from a clean tree', () => {
|
||||
const { root } = fixture()
|
||||
const producer = writeTypertWorkspace(root)
|
||||
const commands = recordedCommands(root)
|
||||
const consumer = join(root, 'consumer')
|
||||
mkdirSync(join(consumer, 'src'), { recursive: true })
|
||||
writeFileSync(join(consumer, 'src/index.ts'), 'import { TYPERT } from "../../packages/fixture/probe/lib/typert.host.js"\nexport const value: unknown = TYPERT\n')
|
||||
writeFileSync(join(consumer, 'tsconfig.json'), JSON.stringify({
|
||||
extends: '../tsconfig.base.json', compilerOptions: { rootDir: 'src', outDir: 'lib/types' },
|
||||
include: ['src'], references: [{ path: '../packages/fixture/probe' }],
|
||||
}))
|
||||
writeFileSync(join(root, 'tsconfig.host.json'), JSON.stringify({
|
||||
extends: './tsconfig.base.json', compilerOptions: { noEmit: true }, files: [],
|
||||
references: [{ path: producer }, { path: consumer }],
|
||||
}))
|
||||
|
||||
expect(existsSync(join(producer, 'lib'))).toBe(false)
|
||||
buildLibraryArtifacts(root, true)
|
||||
|
||||
expect(readFileSync(join(consumer, 'lib/types/index.js'), 'utf8')).toContain('TYPERT')
|
||||
expect(commands().map(command => command.args)).toEqual([
|
||||
['exec', 'tsdown', '--config-loader', 'native', '--env.DSH_BUILD_FACE', 'host'],
|
||||
])
|
||||
})
|
||||
|
||||
it('stops after a failed Host bundler before Desktop and Client builds', () => {
|
||||
const { root } = fixture()
|
||||
const commands = recordedCommands(root, true)
|
||||
@@ -174,25 +200,21 @@ describe('bundle-free Host contracts', { timeout: 60_000 }, () => {
|
||||
expect(outputs()).toEqual(expect.arrayContaining(['typert.host.d.ts', 'typert.host.js']))
|
||||
})
|
||||
|
||||
it('fails when the Typert pass rejects a contributor while Host compilation completes', async () => {
|
||||
const { pkg, run, outputs } = typertWorkspace()
|
||||
it('does not compile Host packages after Typert rejects a contributor', async () => {
|
||||
const { pkg, run } = typertWorkspace()
|
||||
setPublishedFiles(pkg, [])
|
||||
|
||||
await expect(run()).rejects.toThrow('Host Typert exited with 1')
|
||||
expect(outputs()).toEqual(['types/index.d.ts', 'types/index.js'])
|
||||
expect(existsSync(join(pkg, 'lib'))).toBe(false)
|
||||
})
|
||||
|
||||
it('reports both steps when both fail', async () => {
|
||||
it('reports generation before an unrelated compiler error', async () => {
|
||||
const { pkg, run } = typertWorkspace('/** @typert schema */\nexport interface Payload { value: string }\nexport const value: number = "invalid"\n')
|
||||
setPublishedFiles(pkg, [])
|
||||
|
||||
const failure: unknown = await run().then(() => undefined, (error: unknown) => error)
|
||||
|
||||
expect(failure).toBeInstanceOf(AggregateError)
|
||||
expect((failure as AggregateError).errors.map(error => String(error))).toEqual([
|
||||
expect.stringContaining('host compiler exited with'),
|
||||
expect.stringContaining('Host Typert exited with 1'),
|
||||
])
|
||||
expect(String(failure)).toContain('Host Typert exited with 1')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/**
|
||||
* Compile and bundle package projects without repeating aggregate test and script typechecks.
|
||||
* Contract steps replace the Host bundle with the bundle-free Typert pass for consumers that read
|
||||
* only declarations.
|
||||
* Source-only Typert generation precedes compilation; contract steps omit runtime bundles.
|
||||
*/
|
||||
|
||||
import { spawn, spawnSync } from 'node:child_process'
|
||||
@@ -166,16 +165,17 @@ function pnpmStep(id: LibraryStepId, label: string, args: readonly string[], nee
|
||||
|
||||
/**
|
||||
* List the package-library build steps in Host, Desktop, then Client dependency order.
|
||||
* Host bundling runs Typert, whose Remote declarations Client compilation imports.
|
||||
* Source-only Typert generation supplies declarations before any package compilation.
|
||||
* @param root - Repository root containing the compiler and bundle configurations.
|
||||
* @param faces - `host` lists Host compilation and bundling, which produce the Typert outputs; `all` adds Desktop and Client.
|
||||
* @param faces - `host` lists Typert generation, Host compilation, and bundling; `all` adds Desktop and Client.
|
||||
* @param mode - Check package types by default; benchmark emission defers diagnostics to required full builds.
|
||||
* @returns The ordered steps; each step needs its predecessor.
|
||||
* @throws If an aggregate configuration is invalid.
|
||||
*/
|
||||
export function libraryBuildSteps(root: string, faces: 'host' | 'all', mode: CompilationMode = 'checked'): LibraryStep[] {
|
||||
const host: LibraryStep[] = [
|
||||
compilerStep(root, 'host', mode, []),
|
||||
typertStep(),
|
||||
compilerStep(root, 'host', mode, ['host-typert']),
|
||||
pnpmStep('host-bundle', 'Host bundle', ['exec', 'tsdown', '--config-loader', 'native', '--env.DSH_BUILD_FACE', 'host'], ['host-src']),
|
||||
]
|
||||
if (faces === 'host') return host
|
||||
@@ -188,19 +188,18 @@ export function libraryBuildSteps(root: string, faces: 'host' | 'all', mode: Com
|
||||
}
|
||||
|
||||
/**
|
||||
* List the bundle-free package steps that produce every declaration the type checkers, oxlint, and
|
||||
* documentation checks read: Host compilation and the Typert pass, which writes the generated
|
||||
* Typert declarations from source without bundling, run independently; Client compilation, which
|
||||
* imports both, needs the two. Unlike {@link libraryBuildSteps}, no step writes runnable bundles.
|
||||
* List bundle-free metadata generation and package compilation steps.
|
||||
* Source-only Typert generation supplies declarations before Host compilation; Client
|
||||
* compilation follows the Host declarations. No step writes runtime bundles.
|
||||
* @param root - Repository root containing the compiler configurations.
|
||||
* @param faces - `host` lists Host compilation and the Typert pass; `all` adds Client compilation.
|
||||
* @param faces - `host` lists the Typert pass and Host compilation; `all` adds Client compilation.
|
||||
* @returns The steps in report order.
|
||||
* @throws If an aggregate configuration is invalid.
|
||||
*/
|
||||
export function contractSteps(root: string, faces: 'host' | 'all'): LibraryStep[] {
|
||||
const host = [compilerStep(root, 'host', 'checked', []), typertStep()]
|
||||
const host = [typertStep(), compilerStep(root, 'host', 'checked', ['host-typert'])]
|
||||
if (faces === 'host') return host
|
||||
return [...host, compilerStep(root, 'client', 'checked', ['host-src', 'host-typert'])]
|
||||
return [...host, compilerStep(root, 'client', 'checked', ['host-src'])]
|
||||
}
|
||||
|
||||
function runStep(root: string, step: LibraryStep): void {
|
||||
@@ -227,7 +226,7 @@ function stepFailure(step: LibraryStep, exit: number | NodeJS.Signals | null): E
|
||||
/**
|
||||
* Emit package libraries serially in Host, Desktop, then Client dependency order.
|
||||
* @param root - Repository root containing the compiler and bundle configurations.
|
||||
* @param hostOnly - Stop after Host compilation and bundling, which produce the Typert outputs.
|
||||
* @param hostOnly - Stop after Typert generation, Host compilation, and bundling.
|
||||
* @param mode - Check package types by default; benchmark emission defers diagnostics to required full builds.
|
||||
* @throws If any compiler or bundler fails.
|
||||
*/
|
||||
@@ -236,18 +235,13 @@ export function buildLibraryArtifacts(root: string, hostOnly: boolean, mode: Com
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile Host package projects and, concurrently, write the generated Typert declarations
|
||||
* without bundling. Both steps run to completion even when the other fails.
|
||||
* @param root - Repository root containing the compiler configuration; the Typert pass runs there.
|
||||
* @returns A promise that settles after both steps exit.
|
||||
* @throws The failed step's error, or an `AggregateError` naming both steps when both fail.
|
||||
* Generate Typert declarations from source, then compile Host package projects.
|
||||
* @param root - Repository root containing the compiler configuration and source contributors.
|
||||
* @returns a promise that settles after generation and compilation exit.
|
||||
* @throws the first failed step's error; dependent compilation does not start after generation fails.
|
||||
*/
|
||||
export async function buildHostContracts(root: string): Promise<void> {
|
||||
const results = await Promise.allSettled(contractSteps(root, 'host').map(step => runStepConcurrently(root, step)))
|
||||
const failures: unknown[] = []
|
||||
for (const result of results) if (result.status === 'rejected') failures.push(result.reason)
|
||||
if (failures.length === 1) throw failures[0]
|
||||
if (failures.length > 1) throw new AggregateError(failures, 'compile-referenced-projects: host compiler and Host Typert both failed')
|
||||
for (const step of contractSteps(root, 'host')) await runStepConcurrently(root, step)
|
||||
}
|
||||
|
||||
if (import.meta.main) {
|
||||
|
||||
@@ -138,7 +138,7 @@ function formatDiagnostics(diagnostics: readonly ts.Diagnostic[], blocks: Block[
|
||||
/**
|
||||
* Reuse the Host aggregate references from a temp project one directory below
|
||||
* root. Generated Client API examples opt out because their declarations do
|
||||
* not exist until Host tsdown has run.
|
||||
* not exist until source-only Typert generation has run.
|
||||
*/
|
||||
function workspaceReferences(): { path: string }[] {
|
||||
const file = join(root, 'tsconfig.host.json')
|
||||
|
||||
@@ -136,6 +136,30 @@ describe('bundle-free Typert pass', { timeout: 60_000 }, () => {
|
||||
expect(existsSync(join(pkg, 'lib'))).toBe(false)
|
||||
})
|
||||
|
||||
it.each([false, true])('rejects a producer that reaches its own generated metadata through a source dependency (existing output: %s)', (compiled) => {
|
||||
const { root, pkg } = workspace(DEPENDENT_TYPERT_SOURCE)
|
||||
const dependency = writeCompiledDependency(root, false)
|
||||
writeFileSync(join(dependency, 'src/types.ts'), "export type Shape = import('@fixture/probe/typert').Payload\n")
|
||||
symlinkSync(pkg, join(root, 'node_modules/@fixture/probe'), 'junction')
|
||||
const basePath = join(root, 'tsconfig.base.json')
|
||||
const base = JSON.parse(readFileSync(basePath, 'utf8')) as { compilerOptions: Record<string, unknown> }
|
||||
writeFileSync(basePath, JSON.stringify({
|
||||
...base,
|
||||
compilerOptions: { ...base.compilerOptions, paths: { '@fixture/dep/types': ['./packages/fixture/dep/src/types.ts'] } },
|
||||
}))
|
||||
if (compiled) {
|
||||
mkdirSync(join(pkg, 'lib'))
|
||||
writeFileSync(join(pkg, 'lib/typert.host.d.ts'), 'export interface Payload { value: string }\n')
|
||||
}
|
||||
const output = captureConsole()
|
||||
|
||||
expect(main([], root)).toBe(1)
|
||||
|
||||
expect(output.error()).toContain(compiled ? 'loads workspace build outputs instead of source' : 'cannot resolve workspace imports')
|
||||
expect(output.error()).toContain('@fixture/probe/typert')
|
||||
expect(existsSync(join(pkg, 'lib/typert.host.js'))).toBe(false)
|
||||
})
|
||||
|
||||
it('reports a failed write as a possibly mixed generation', () => {
|
||||
const { root, pkg } = workspace()
|
||||
writeFileSync(join(pkg, 'lib'), 'not a directory\n')
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
/**
|
||||
* Generate the repository's Typert artifacts without bundling.
|
||||
*
|
||||
* The pass writes the same files, through the same generator calls, as the workspace-mode Typert
|
||||
* plugin in the Host tsdown pass (`tsdown.config.ts`): every explicit contributor's
|
||||
* `lib/typert.host.*` and `lib/typert.remote-client.*`. Gates that read only generated
|
||||
* declarations run it instead of bundling. It reads workspace packages only from source and fails
|
||||
* when the Host program would load a package's `lib/` output, so it can run beside Host package
|
||||
* compilation. Like the plugin it skips TypeScript diagnostics, which that compilation owns;
|
||||
* Typert's own checks still fail the pass.
|
||||
* Writes every explicit contributor's `lib/typert.host.*` and `lib/typert.remote-client.*`
|
||||
* with the same generator and writer as the workspace-mode Typert bundler plugin. It reads
|
||||
* workspace packages only from source and rejects imports requiring compiled output, so metadata
|
||||
* generation precedes package compilation. The compiler owns TypeScript diagnostics;
|
||||
* Typert analysis and emission failures still fail this pass.
|
||||
*
|
||||
* Usage: `tsx scripts/generate-typert.ts`, from the repository root or any directory below it.
|
||||
* Exit status 0 means every artifact was written, 1 means generation failed, and 2 means the
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
/**
|
||||
* Program faces of the repository's Typert pass. The Host face alone emits
|
||||
* Host reflection and the Host-for-Client Remote projection; no package
|
||||
* publishes Client reflection. The Host tsdown plugin (`tsdown.config.ts`)
|
||||
* and the bundle-free pass (`generate-typert.ts`) both generate exactly these
|
||||
* faces. Node loads this module through type stripping from
|
||||
* `tsdown.config.ts`, so it uses only erasable TypeScript syntax.
|
||||
* Program faces of source-only Typert generation. The Host face emits Host reflection
|
||||
* and the Host-for-Client Remote projection; no package publishes Client reflection.
|
||||
*/
|
||||
export const WORKSPACE_TYPERT_FACES = ['host'] as const
|
||||
@@ -243,10 +243,11 @@ grep -q '^smoke: win32 x64' "$scratch/logs/smoke.log" || { echo 'wine-windows-ga
|
||||
|
||||
# ---- the two blocking surfaces, concurrently ------------------------------
|
||||
# The build mirrors the root build:lib:host and build:lib:client scripts:
|
||||
# compile and bundle the Host face, bundle apps/desktop from the Host lib/
|
||||
# outputs, then compile and bundle the Client face.
|
||||
# generate metadata, compile and bundle the Host face, bundle apps/desktop from
|
||||
# the Host lib/ outputs, then compile and bundle the Client face.
|
||||
# Both statuses are captured so one failure cannot hide the other's result.
|
||||
build_gate() {
|
||||
wine_node "$scratch/logs/host-typert.log" --import tsx/esm scripts/generate-typert.ts || return $?
|
||||
wine_node "$scratch/logs/host-tsc.log" --max-old-space-size=4096 "$tsc_js" -b tsconfig.host.json --pretty false || return $?
|
||||
wine_node "$scratch/logs/host-tsdown.log" "$tsdown_js" --env.DSH_BUILD_FACE host || return $?
|
||||
(cd apps/desktop && wine_node "$scratch/logs/desktop-tsdown.log" "../../$tsdown_js") || return $?
|
||||
@@ -277,7 +278,8 @@ report() {
|
||||
for log in "$@"; do tail -n 200 "$log" >&2 || true; done
|
||||
fi
|
||||
}
|
||||
report 'build (Host tsc/tsdown, desktop tsdown, Client tsc/tsdown)' "$build_status" \
|
||||
report 'build (Typert, Host tsc/tsdown, desktop tsdown, Client tsc/tsdown)' "$build_status" \
|
||||
"$scratch/logs/host-typert.log" \
|
||||
"$scratch/logs/host-tsc.log" \
|
||||
"$scratch/logs/host-tsdown.log" \
|
||||
"$scratch/logs/desktop-tsdown.log" \
|
||||
|
||||
+3
-4
@@ -1,7 +1,6 @@
|
||||
import { globSync } from 'node:fs'
|
||||
import { defineConfig } from 'tsdown'
|
||||
import { typertPlugin } from './packages/typert/generator/lib/types/tsdown-plugin.js'
|
||||
import { WORKSPACE_TYPERT_FACES } from './scripts/typert-faces.ts'
|
||||
import { decoratorPlugin } from './packages/typert/generator/lib/types/tsdown-plugin.js'
|
||||
import { workspacePackageGlobs } from './scripts/workspace-package-globs.ts'
|
||||
|
||||
function isBuildFaceClient(value: unknown): boolean {
|
||||
@@ -12,7 +11,7 @@ function isBuildFaceClient(value: unknown): boolean {
|
||||
|
||||
/**
|
||||
* The ordinary workspace build consumes JavaScript emitted by the Host
|
||||
* TypeScript project and runs Typert. The Client pass selects packages that
|
||||
* TypeScript project after source-only Typert generation. The Client pass selects packages that
|
||||
* declare a browser bundle and lets their package-local configs emit both
|
||||
* their Node loader entry and browser artifact. `apps/desktop` bundles after
|
||||
* this pass (root package.json `build:lib:host`): its main bundle inlines
|
||||
@@ -32,6 +31,6 @@ export default defineConfig(({ env }) => {
|
||||
fixedExtension: false,
|
||||
dts: false,
|
||||
clean: false,
|
||||
plugins: client ? [] : [typertPlugin({ mode: 'workspace', faces: WORKSPACE_TYPERT_FACES })],
|
||||
plugins: client ? [] : [decoratorPlugin()],
|
||||
}
|
||||
})
|
||||
Reference in new issue
Block a user