diff --git a/AGENTS.md b/AGENTS.md index 782c6b46e3..8c54b1b145 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -101,7 +101,7 @@ pnpm run website:build # VitePress build (doubles as dead-link check) pnpm dsh --profile headless "task" # run one task from source (needs DEEPSEEK_API_KEY) pnpm run demo:ptc -- "task" # headless PTC mode run (needs key) pnpm run dev:web | dev:desktop # build, then launch; Web also rebuilds client bundles on edits. start:web | start:desktop skip the build -make web|dev-web|desktop|dev-desktop|build|clean # the same commands; ARGS='--no-open' forwards options +make web|dev-web|desktop|dev-desktop|build # the same commands; ARGS='--no-open' forwards options ``` ### Host sandbox failures diff --git a/Makefile b/Makefile index 11f3de95f2..0836489ef8 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ -# Short names for repository builds, cleanup, and Web/Desktop commands. -# package.json owns the scripts; docs/development.md documents these commands. +# Short names for the Web and Desktop application commands. The package.json +# scripts they call stay the source of truth; docs/development.md documents both. .DEFAULT_GOAL := help .PHONY: help build clean web desktop dev-web dev-desktop diff --git a/docs/development.i18n.yaml b/docs/development.i18n.yaml index 114e7b8eaa..2a08605a85 100644 --- a/docs/development.i18n.yaml +++ b/docs/development.i18n.yaml @@ -21,8 +21,8 @@ en: 3221d1076554001f zh: 29f166be6a5c04d9 /development-guide/contributor-reference/typescript-project-layout: - en: 637e7c21d468df9a - zh: c1b7081eba036234 + en: e3327c5492a593cb + zh: d43a5edcb11523ae /development-guide/contributor-reference/environment-variables: en: dba2c72ee6ffa930 zh: b65c27fbb9129ec8 @@ -39,8 +39,8 @@ en: 7ce6264a6a06dc4a zh: e12b684f2bef942b /development-guide/contributor-reference/application-commands: - en: dc29a9d5f3f10cc0 - zh: 5f1420641be960ae + en: 28a029c8768f0880 + zh: 67c17256e046624e /development-guide/contributor-reference/todo-markers: en: 85b38f78d87fc88f zh: 00d5eb625e7dfb61 diff --git a/docs/development.md b/docs/development.md index cc3449029e..668beec326 100644 --- a/docs/development.md +++ b/docs/development.md @@ -87,7 +87,7 @@ pnpm run build:web `pnpm run build --artifacts-only` emits the same project and application artifacts while checking and compiling only the existing package references of each compiler face. Required Linux and Windows normal builds and public `typecheck` also check the repository-wide test and script programs. CI benchmark preparation additionally uses `--noCheck` for its disposable library outputs, relying on those required builds for diagnostics; other artifact builders keep package checking ([rationale](../.agents/notes/implemented/process/2026-10-05-pr-artifact-typechecks.md)). -Both tsdown passes match `vendor/*`, `packages/*/*`, and `apps/cli`; the Host pass also matches `apps/desktop-host`. Only directories containing `package.json` are included, so generated residue from moved or removed packages is not bundled. 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)). +Both tsdown passes match `vendor/*`, `packages/*/*`, and `apps/cli`; the Host pass also matches `apps/desktop-host`. They neither scan build artifacts to discover Client packages nor maintain a Host/Client package filter list. Package-local tsdown configs select entries for the current phase through `DSH_BUILD_FACE`: an ordinary Client plugin produces both its Node loader and browser bundle during the Client phase; `api-remotes` uses `hostPhase: true` to produce its Host entry early and only its browser bundle during the Client phase. Tsdown consumes only the JavaScript emitted to `lib/types` by the preceding tsc phase. Tsdown builds the matched workspace members concurrently, so `apps/desktop`, whose main bundle inlines workspace devDependencies from their `lib/` output, bundles in its own step after the Host pass ([Desktop README](../apps/desktop/README.md#bundled-workspace-dependencies)). Typert runs only during Host tsdown, seeded by `tsconfig.host.json`. It analyzes Host types and generates both Host reflection artifacts and the Host-for-Client Remote projection; Client tsdown does not start Typert. Consequently, `pnpm run typecheck` runs the complete Host lib phase before Client tsc, while `pnpm run build` continues through Client tsdown and the Web build. @@ -173,7 +173,7 @@ pnpm run start:desktop # launch built Desktop artifacts pnpm run dev:desktop # build, then launch Desktop ``` -Arguments after a Web command reach `dsh web`, for example `pnpm run dev:web --no-open --port 3081`; `dev:web` also accepts `--skip-build` to reuse the existing artifact tree and `--no-serve` to run only the rebuild watchers beside a server started elsewhere. Both Web commands use the normal Harness home, while the Desktop commands use the isolated development home described in the [Desktop README](../apps/desktop/README.md). The root `Makefile` names the same commands as `make web`, `make dev-web`, `make desktop`, `make dev-desktop`, and `make build`; `ARGS='--no-open'` forwards options. `make clean` runs `pnpm run clean` to remove build outputs and safe residue from deleted packages. +Arguments after a Web command reach `dsh web`, for example `pnpm run dev:web --no-open --port 3081`; `dev:web` also accepts `--skip-build` to reuse the existing artifact tree and `--no-serve` to run only the rebuild watchers beside a server started elsewhere. Both Web commands use the normal Harness home, while the Desktop commands use the isolated development home described in the [Desktop README](../apps/desktop/README.md). The root `Makefile` names the same commands as `make web`, `make dev-web`, `make desktop`, `make dev-desktop`, and `make build`; `ARGS='--no-open'` forwards options. ### TODO markers diff --git a/docs/development.zh.md b/docs/development.zh.md index 67fc7a7e3f..3154c4d988 100644 --- a/docs/development.zh.md +++ b/docs/development.zh.md @@ -91,7 +91,7 @@ pnpm run build:web `pnpm run build --artifacts-only` 产出相同的 project 和应用产物,但只检查并编译各 compiler face 的现有包 reference。必需的 Linux 与 Windows 普通构建以及公开 `typecheck` 还检查仓库级测试和脚本 program。CI benchmark 准备阶段还对其临时 library 输出使用 `--noCheck`,由这些必需构建负责诊断;其他产物 builder 保留包检查([理由](../.agents/notes/implemented/process/2026-10-05-pr-artifact-typechecks.zh.md))。 -两次 tsdown 都匹配 `vendor/*`、`packages/*/*` 与 `apps/cli`,Host 阶段另外匹配 `apps/desktop-host`。只有含 `package.json` 的目录才会被纳入,因此已迁移或删除包残留的构建产物不会参与打包。两者都不扫描构建产物来发现 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))。 +两次 tsdown 都匹配 `vendor/*`、`packages/*/*` 与 `apps/cli`,Host 阶段另外匹配 `apps/desktop-host`;两者都不扫描构建产物来发现 Client 包,也不维护 Host/Client 包过滤表。包内 tsdown 配置根据 `DSH_BUILD_FACE` 决定当前阶段的入口:普通 Client 插件在 Client 阶段同时生成 Node loader 与 browser bundle;`api-remotes` 通过 `hostPhase: true` 提前生成 Host 入口,再在 Client 阶段只生成 browser bundle。tsdown 只消费 `lib/types` 中由前置 tsc 发射的 JavaScript。tsdown 并发构建匹配到的 workspace 成员;`apps/desktop` 的主进程 bundle 需要从工作区 devDependencies 的 `lib/` 产物内联这些包,因此在 Host 阶段之后单独一步打 bundle([Desktop README](../apps/desktop/README.zh.md#bundled-workspace-dependencies))。 Typert 只在 Host tsdown 中以 `tsconfig.host.json` 为种子运行。它分析 Host 类型并生成 Host 反射产物及 Host-for-Client Remote 投影;Client tsdown 不启动 Typert。`pnpm run typecheck` 因此先执行完整 Host lib 阶段,再运行 Client tsc;`pnpm run build` 继续执行 Client tsdown 和 Web 构建。 @@ -177,7 +177,7 @@ pnpm run start:desktop # launch built Desktop artifacts pnpm run dev:desktop # build, then launch Desktop ``` -Web 命令后面的参数会传给 `dsh web`,例如 `pnpm run dev:web --no-open --port 3081`;`dev:web` 还接受 `--skip-build` 复用现有产物树,以及 `--no-serve` 只运行重建 watcher、配合别处启动的服务器。两个 Web 命令使用正常的 Harness home,而 Desktop 命令使用 [Desktop README](../apps/desktop/README.zh.md) 描述的隔离开发 home。根目录 `Makefile` 以 `make web`、`make dev-web`、`make desktop`、`make dev-desktop` 和 `make build` 命名同一套命令;`ARGS='--no-open'` 用于转发参数。`make clean` 调用 `pnpm run clean`,删除构建产物和已删除包中可安全清理的残留。 +Web 命令后面的参数会传给 `dsh web`,例如 `pnpm run dev:web --no-open --port 3081`;`dev:web` 还接受 `--skip-build` 复用现有产物树,以及 `--no-serve` 只运行重建 watcher、配合别处启动的服务器。两个 Web 命令使用正常的 Harness home,而 Desktop 命令使用 [Desktop README](../apps/desktop/README.zh.md) 描述的隔离开发 home。根目录 `Makefile` 以 `make web`、`make dev-web`、`make desktop`、`make dev-desktop` 和 `make build` 命名同一套命令;`ARGS='--no-open'` 用于转发参数。 ### TODO 标记 diff --git a/scripts/build-workspace.spec.ts b/scripts/build-workspace.spec.ts deleted file mode 100644 index 2e4c2130a2..0000000000 --- a/scripts/build-workspace.spec.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** Workspace bundle discovery after a package moves or is removed. */ - -import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs' -import { tmpdir } from 'node:os' -import { dirname, join } from 'node:path' -import { build, type TsdownBundle } from 'tsdown' -import { describe, expect, it, onTestFinished } from 'vitest' -import { buildWorkspaceDirectories } from './build-workspace.ts' -import { removeFixtureSafely } from './test-fixture-cleanup.ts' - -function write(root: string, path: string, content: string): void { - const target = join(root, path) - mkdirSync(dirname(target), { recursive: true }) - writeFileSync(target, content) -} - -describe('repository bundle workspaces', () => { - it.each(['host', 'client'] as const)('builds live %s packages without bundling a moved package’s residue', async (face) => { - const root = mkdtempSync(join(tmpdir(), 'dsh-build-workspace-')) - onTestFinished(() => { removeFixtureSafely(root) }) - write(root, 'package.json', JSON.stringify({ private: true, type: 'module' })) - const shared = [ - 'apps/cli', - 'packages/experimental/session-title-all-prompts-llm', - 'packages/session/session-title-llm', - 'vendor/cordis', - ] - for (const directory of [...shared, 'apps/desktop-host', 'apps/desktop', 'apps/web', 'native/system']) { - write(root, `${directory}/package.json`, JSON.stringify({ name: directory.replaceAll('/', '-'), type: 'module' })) - write(root, `${directory}/lib/types/index.js`, 'export const executeSessionTitleLlm = "live"\n') - } - const staleEntry = 'packages/session/session-title-all-prompts-llm/lib/types/index.js' - const staleCode = 'export { registerSessionTitleLlmProvider } from "../../../session-title-llm/lib/types/index.js"\n' - write(root, staleEntry, staleCode) - write(root, 'packages/session/session-title-all-prompts-llm/node_modules/.cache/residue', '') - - const workspace = buildWorkspaceDirectories(root, face) - const expected = [...shared, ...face === 'host' ? ['apps/desktop-host'] : []].sort() - expect(workspace).toEqual(expected) - - let bundles: TsdownBundle[] = [] - try { - bundles = await build({ - cwd: root, workspace, config: false, tsconfig: false, - entry: ['lib/types/index.js'], outDir: 'lib', format: 'esm', platform: 'node', - dts: false, clean: false, report: false, logLevel: 'silent', - }) - for (const directory of expected) { - expect(readFileSync(join(root, directory, 'lib/index.mjs'), 'utf8')).toContain('executeSessionTitleLlm') - } - expect(readFileSync(join(root, staleEntry), 'utf8')).toBe(staleCode) - } finally { - for (const bundle of bundles) await bundle[Symbol.asyncDispose]() - } - }) -}) diff --git a/scripts/build-workspace.ts b/scripts/build-workspace.ts deleted file mode 100644 index 263b6f5732..0000000000 --- a/scripts/build-workspace.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** Discover live workspace packages for the repository's tsdown build phases. */ - -import { globSync } from 'node:fs' -import { dirname, sep } from 'node:path' - -/** - * Select packages with manifests, excluding generated residue from removed packages. - * @param root - Repository root containing the workspace directories. - * @param face - Compiler phase selecting the Host-only desktop host package. - * @returns Sorted repository-relative package directories with forward slashes. - */ -export function buildWorkspaceDirectories(root: string, face: 'host' | 'client'): string[] { - const patterns = ['vendor/*/package.json', 'packages/*/*/package.json', 'apps/cli/package.json'] - if (face === 'host') patterns.push('apps/desktop-host/package.json') - return globSync(patterns, { cwd: root }).map(path => dirname(path).split(sep).join('/')).sort() -} diff --git a/tsdown.config.ts b/tsdown.config.ts index dbe9a3c81b..c378de376a 100644 --- a/tsdown.config.ts +++ b/tsdown.config.ts @@ -1,6 +1,5 @@ import { defineConfig } from 'tsdown' import { typertPlugin } from './packages/typert/generator/lib/types/tsdown-plugin.js' -import { buildWorkspaceDirectories } from './scripts/build-workspace.ts' function isBuildFaceClient(value: unknown): boolean { if (value === undefined || value === 'host') return false @@ -20,7 +19,9 @@ function isBuildFaceClient(value: unknown): boolean { export default defineConfig(({ env }) => { const client = isBuildFaceClient(env?.DSH_BUILD_FACE) return { - workspace: buildWorkspaceDirectories(import.meta.dirname, client ? 'client' : 'host'), + workspace: client + ? ['vendor/*', 'packages/*/*', 'apps/cli'] + : ['vendor/*', 'packages/*/*', 'apps/cli', 'apps/desktop-host'], entry: client ? '' : ['lib/types/{index,startup}.js'], outDir: 'lib', format: ['esm'],