From ae12413ee29ccff857a7eef8ffdd13098fcda27d Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 11 Jul 2026 07:28:20 -0700 Subject: [PATCH] chore(tooling): enforce no-floating-promises and drop DOM globals from Node-side typechecking (#104422) * chore(lint): enforce no-floating-promises repo-wide * feat(tooling): split DOM globals out of the Node-side typecheck program tsconfig.core.json now compiles src/ + packages/ with lib ES2023; new tsconfig.ui.json owns ui/ with DOM. Web-global names used by Node code alias to canonical undici/stream-web types; highlight.js loads through a validated narrow API because its d.ts force-includes lib.dom. Boundary, sparse-guard, changed-lane routing, and profile lanes cover the new ui graph. * chore(deps): declare undici-types and teach knip about the highlight.js runtime require * ci: retrigger dropped pull_request synchronize event * refactor(types): derive web-global aliases from @types/node fetch globals, drop undici-types dep * ci: retrigger after dependency guard cleared * fix(scripts): add ui lane to changed-lanes declaration union --- .github/instructions/copilot.instructions.md | 2 +- .oxlintrc.json | 4 +-- config/knip.config.ts | 3 ++ package.json | 3 +- scripts/changed-lanes.d.mts | 1 + scripts/changed-lanes.mjs | 18 +++++++++-- scripts/check-changed.mjs | 5 ++- scripts/check-tsgo-core-boundary.mjs | 1 + scripts/lib/tsgo-sparse-guard.mjs | 33 +++++++++++++++----- scripts/profile-tsgo.mjs | 4 +++ src/agents/code-mode.ts | 7 ++++- src/agents/utils/syntax-highlight.ts | 33 +++++++++++++++++++- src/secrets/config-io.ts | 4 ++- src/types/node-runtime-globals.d.ts | 20 ++++++++++++ test/scripts/changed-lanes.test.ts | 26 +++++++++++++++ test/scripts/oxlint-config.test.ts | 3 +- test/scripts/run-tsgo.test.ts | 25 ++++++++++++--- tsconfig.core.json | 4 ++- tsconfig.core.projects.json | 1 + tsconfig.ui.json | 10 ++++++ 20 files changed, 182 insertions(+), 25 deletions(-) create mode 100644 src/types/node-runtime-globals.d.ts create mode 100644 tsconfig.ui.json diff --git a/.github/instructions/copilot.instructions.md b/.github/instructions/copilot.instructions.md index 62bf9f8343bd..03e56ab51277 100644 --- a/.github/instructions/copilot.instructions.md +++ b/.github/instructions/copilot.instructions.md @@ -56,7 +56,7 @@ - **Package manager**: pnpm (`pnpm install`) - **Dev**: `pnpm openclaw ...` or `pnpm dev` -- **Type-check**: `pnpm tsgo` (core production), `pnpm tsgo:prod` (core + extension production), `pnpm check:test-types` (tests) +- **Type-check**: `pnpm tsgo` (core production), `pnpm tsgo:prod` (core + UI + extension production), `pnpm check:test-types` (tests) - **Lint/format**: `pnpm check` - **Tests**: `pnpm test` - **Build**: `pnpm build` diff --git a/.oxlintrc.json b/.oxlintrc.json index 400d25a53d12..4cfa98d036ff 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -78,6 +78,7 @@ "typescript/no-empty-object-type": ["error", { "allowInterfaces": "with-single-extends" }], "typescript/no-explicit-any": "error", "typescript/no-extraneous-class": "error", + "typescript/no-floating-promises": "error", "typescript/no-import-type-side-effects": "error", "typescript/no-meaningless-void-operator": "error", "typescript/no-misused-promises": "error", @@ -209,15 +210,14 @@ "dist/", "dist-runtime/", "docs/_layouts/", + "**/a2ui.bundle.js", "extensions/diffs/assets/viewer-runtime.js", "extensions/diffs-language-pack/assets/viewer-runtime.js", - "extensions/canvas/src/host/a2ui/a2ui.bundle.js", "node_modules/", "patches/", "pnpm-lock.yaml", "skills/**", "src/auto-reply/reply/export-html/template.js", - "src/canvas-host/a2ui/a2ui.bundle.js", "vendor/", "**/.cache/**", "**/.openclaw-runtime-deps-copy-*/**", diff --git a/config/knip.config.ts b/config/knip.config.ts index 5793c875c343..480f66282b59 100644 --- a/config/knip.config.ts +++ b/config/knip.config.ts @@ -145,6 +145,9 @@ const config = { "@mistralai/mistralai", "cross-spawn", "file-type", + // Loaded via createRequire in src/agents/utils/syntax-highlight.ts because its + // d.ts force-includes lib.dom; knip cannot see the dynamic require. + "highlight.js", "playwright-core", "partial-json", "sqlite-vec", diff --git a/package.json b/package.json index 6cf27dc76eb3..3b49b2bca33e 100644 --- a/package.json +++ b/package.json @@ -1960,7 +1960,7 @@ "tsgo:extensions": "node scripts/run-tsgo.mjs -p tsconfig.extensions.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/extensions.tsbuildinfo", "tsgo:extensions:all": "node scripts/run-tsgo.mjs -b tsconfig.extensions.projects.json", "tsgo:extensions:test": "node scripts/run-tsgo.mjs -p test/tsconfig/tsconfig.extensions.test.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/extensions-test.tsbuildinfo", - "tsgo:prod": "pnpm tsgo:core && pnpm tsgo:extensions", + "tsgo:prod": "pnpm tsgo:core && pnpm tsgo:ui && pnpm tsgo:extensions", "tsgo:profile": "node scripts/profile-tsgo.mjs", "tsgo:scripts": "node scripts/run-tsgo.mjs -p tsconfig.scripts.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/scripts.tsbuildinfo", "tsgo:test": "pnpm tsgo:core:test && pnpm tsgo:extensions:test && pnpm tsgo:test:root", @@ -1969,6 +1969,7 @@ "tsgo:test:root": "node scripts/run-tsgo.mjs -p test/tsconfig/tsconfig.test.root.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/test-root.tsbuildinfo", "tsgo:test:src": "node scripts/run-tsgo.mjs -p test/tsconfig/tsconfig.test.src.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/test-src.tsbuildinfo", "tsgo:test:ui": "node scripts/run-tsgo.mjs -p test/tsconfig/tsconfig.test.ui.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/test-ui.tsbuildinfo", + "tsgo:ui": "node scripts/run-tsgo.mjs -p tsconfig.ui.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/ui.tsbuildinfo", "tui": "node scripts/run-node.mjs tui", "tui:dev": "node scripts/run-with-env.mjs OPENCLAW_PROFILE=dev -- node scripts/run-node.mjs --dev tui", "tui:pty:test:watch": "node --import tsx scripts/dev/tui-pty-test-watch.ts", diff --git a/scripts/changed-lanes.d.mts b/scripts/changed-lanes.d.mts index 7a5beeca546b..e50a00e0bbe4 100644 --- a/scripts/changed-lanes.d.mts +++ b/scripts/changed-lanes.d.mts @@ -1,6 +1,7 @@ export type ChangedLane = | "core" | "coreTests" + | "ui" | "extensions" | "extensionTests" | "scripts" diff --git a/scripts/changed-lanes.mjs b/scripts/changed-lanes.mjs index e0741c363246..4386abf0dff9 100644 --- a/scripts/changed-lanes.mjs +++ b/scripts/changed-lanes.mjs @@ -12,7 +12,8 @@ const RAW_SYNC_CHANGED_LANES_ENV = "OPENCLAW_CHANGED_LANES_RAW_SYNC"; const DOCS_PATH_RE = /^(?:docs\/|README\.md$|AGENTS\.md$|.*\.mdx?$)/u; const APP_PATH_RE = /^(?:apps\/|Swabble\/|appcast\.xml$)/u; const EXTENSION_PATH_RE = /^extensions\/[^/]+(?:\/|$)/u; -const CORE_PATH_RE = /^(?:src\/|ui\/|packages\/)/u; +const CORE_PATH_RE = /^(?:src\/|packages\/)/u; +const UI_PATH_RE = /^(?:ui\/|tsconfig\.ui\.json$)/u; const SCRIPTS_TYPECHECK_PATH_RE = /^(?:scripts\/.*\.(?:[cm]?ts|[cm]?tsx)|tsconfig\.scripts\.json)$/u; const TEST_ROOT_TYPECHECK_PATH_RE = @@ -58,7 +59,7 @@ export const RELEASE_METADATA_PATHS = new Set([ "package.json", ]); -/** @typedef {"core" | "coreTests" | "extensions" | "extensionTests" | "scripts" | "testRoot" | "apps" | "docs" | "tooling" | "liveDockerTooling" | "releaseMetadata" | "all"} ChangedLane */ +/** @typedef {"core" | "coreTests" | "ui" | "extensions" | "extensionTests" | "scripts" | "testRoot" | "apps" | "docs" | "tooling" | "liveDockerTooling" | "releaseMetadata" | "all"} ChangedLane */ /** * @typedef {{ @@ -87,6 +88,7 @@ export function createEmptyChangedLanes() { return { core: false, coreTests: false, + ui: false, extensions: false, extensionTests: false, scripts: false, @@ -218,6 +220,18 @@ export function detectChangedLanes(changedPaths, options = {}) { continue; } + if (UI_PATH_RE.test(changedPath)) { + if (isChangedLaneTestPath(changedPath)) { + lanes.coreTests = true; + reasons.push(`${changedPath}: UI test`); + } else { + lanes.ui = true; + lanes.coreTests = true; + reasons.push(`${changedPath}: UI production`); + } + continue; + } + if (APP_PATH_RE.test(changedPath)) { lanes.apps = true; reasons.push(`${changedPath}: app surface`); diff --git a/scripts/check-changed.mjs b/scripts/check-changed.mjs index c3e3b6c94d2c..5e5474b05332 100644 --- a/scripts/check-changed.mjs +++ b/scripts/check-changed.mjs @@ -385,6 +385,9 @@ export function createChangedCheckPlan(result, options = {}) { if (lanes.coreTests) { addTypecheck("typecheck core tests", ["tsgo:core:test"]); } + if (lanes.ui) { + addTypecheck("typecheck UI", ["tsgo:ui"]); + } if (lanes.extensions) { addTypecheck("typecheck extensions", ["tsgo:extensions"]); } @@ -398,7 +401,7 @@ export function createChangedCheckPlan(result, options = {}) { addTypecheck("typecheck test root", ["tsgo:test:root"]); } - if (lanes.core || lanes.coreTests) { + if (lanes.core || lanes.coreTests || lanes.ui) { const coreLintCommand = createTargetedCoreLintCommand(result.paths, baseEnv); if (coreLintCommand) { addCommand( diff --git a/scripts/check-tsgo-core-boundary.mjs b/scripts/check-tsgo-core-boundary.mjs index c7771827a54a..50aa0a4f76aa 100644 --- a/scripts/check-tsgo-core-boundary.mjs +++ b/scripts/check-tsgo-core-boundary.mjs @@ -10,6 +10,7 @@ const tsgoPath = path.join(repoRoot, "node_modules", ".bin", "tsgo"); const coreGraphs = [ { name: "core", config: "tsconfig.core.json" }, + { name: "ui", config: "tsconfig.ui.json" }, { name: "core-test", config: "test/tsconfig/tsconfig.core.test.json" }, { name: "core-test-agents", config: "test/tsconfig/tsconfig.core.test.agents.json" }, { name: "core-test-non-agents", config: "test/tsconfig/tsconfig.core.test.non-agents.json" }, diff --git a/scripts/lib/tsgo-sparse-guard.mjs b/scripts/lib/tsgo-sparse-guard.mjs index c040a6837cc3..5e6f65a6d736 100644 --- a/scripts/lib/tsgo-sparse-guard.mjs +++ b/scripts/lib/tsgo-sparse-guard.mjs @@ -12,14 +12,13 @@ const CORE_TEST_CONFIGS = new Set([ ]); const CORE_PROD_CONFIGS = new Set(["tsconfig.core.json"]); +const UI_PROD_CONFIGS = new Set(["tsconfig.ui.json"]); const TSGO_SPARSE_SKIP_ENV_KEY = "OPENCLAW_TSGO_SPARSE_SKIP"; -const CORE_SPARSE_ROOTS = ["packages", "ui/config", "ui/src"]; +const CORE_PROD_SPARSE_ROOTS = ["packages"]; +const UI_PROD_SPARSE_ROOTS = ["packages", "src", "ui/config", "ui/src"]; +const CORE_TEST_SPARSE_ROOTS = ["packages", "ui/config", "ui/src"]; const CORE_PROD_REQUIRED_PATHS = [ - { - path: "apps/shared/OpenClawKit/Sources/OpenClawKit/Resources/tool-display.json", - whenPresent: "ui/src/ui/tool-display.ts", - }, { path: "scripts/lib/bundled-runtime-sidecar-paths.json", whenPresent: "src/plugins/runtime-sidecar-paths.ts", @@ -42,6 +41,13 @@ const CORE_PROD_REQUIRED_PATHS = [ }, ]; +const UI_PROD_REQUIRED_PATHS = [ + { + path: "apps/shared/OpenClawKit/Sources/OpenClawKit/Resources/tool-display.json", + whenPresent: "ui/src/lib/chat/tool-display.ts", + }, +]; + const CORE_TEST_REQUIRED_PATHS = [ "packages/plugin-package-contract/src/index.ts", "ui/config/control-ui-chunking.ts", @@ -85,7 +91,9 @@ export function getSparseTsgoGuardError( const projectName = projectPath ? path.basename(projectPath) : null; if ( !projectName || - (!CORE_PROD_CONFIGS.has(projectName) && !CORE_TEST_CONFIGS.has(projectName)) || + (!CORE_PROD_CONFIGS.has(projectName) && + !UI_PROD_CONFIGS.has(projectName) && + !CORE_TEST_CONFIGS.has(projectName)) || isMetadataOnlyCommand(args) ) { return null; @@ -118,8 +126,14 @@ export function getSparseTsgoGuardError( } function getRequiredSparseRootsForProject(projectName) { - if (CORE_PROD_CONFIGS.has(projectName) || CORE_TEST_CONFIGS.has(projectName)) { - return CORE_SPARSE_ROOTS; + if (CORE_PROD_CONFIGS.has(projectName)) { + return CORE_PROD_SPARSE_ROOTS; + } + if (UI_PROD_CONFIGS.has(projectName)) { + return UI_PROD_SPARSE_ROOTS; + } + if (CORE_TEST_CONFIGS.has(projectName)) { + return CORE_TEST_SPARSE_ROOTS; } return []; } @@ -129,6 +143,9 @@ function getRequiredPathsForProject(projectName, cwd, fileExists) { if (CORE_PROD_CONFIGS.has(projectName)) { requiredPaths.push(...conditionalRequiredPaths(CORE_PROD_REQUIRED_PATHS, cwd, fileExists)); } + if (UI_PROD_CONFIGS.has(projectName)) { + requiredPaths.push(...conditionalRequiredPaths(UI_PROD_REQUIRED_PATHS, cwd, fileExists)); + } if (CORE_TEST_CONFIGS.has(projectName)) { requiredPaths.push(...CORE_TEST_REQUIRED_PATHS); } diff --git a/scripts/profile-tsgo.mjs b/scripts/profile-tsgo.mjs index 1dab0771812b..f27503caa7ce 100644 --- a/scripts/profile-tsgo.mjs +++ b/scripts/profile-tsgo.mjs @@ -21,6 +21,10 @@ const GRAPH_DEFINITIONS = { config: "tsconfig.core.json", description: "core production graph", }, + ui: { + config: "tsconfig.ui.json", + description: "UI production graph", + }, "core-test": { config: "test/tsconfig/tsconfig.core.test.json", description: "core colocated test graph", diff --git a/src/agents/code-mode.ts b/src/agents/code-mode.ts index 3fe293e8b941..bf7831300ddb 100644 --- a/src/agents/code-mode.ts +++ b/src/agents/code-mode.ts @@ -807,7 +807,12 @@ export class CodeModeHeadlessTimeoutError extends Error { } } -function createHeadlessAbortScope(signal: AbortSignal | undefined, wallClockMs: number) { +// Explicit return type: declaration emit cannot name the inferred AbortSignal +// in the DOM-free core lane (@types/node keeps it in a non-exported module). +function createHeadlessAbortScope( + signal: AbortSignal | undefined, + wallClockMs: number, +): { signal: AbortSignal; cleanup: () => void } { const controller = new AbortController(); const onAbort = () => controller.abort(signal?.reason); signal?.addEventListener("abort", onAbort, { once: true }); diff --git a/src/agents/utils/syntax-highlight.ts b/src/agents/utils/syntax-highlight.ts index 7175ad3dd6c9..2c8a3bfda016 100644 --- a/src/agents/utils/syntax-highlight.ts +++ b/src/agents/utils/syntax-highlight.ts @@ -4,9 +4,40 @@ * Highlight.js emits HTML spans; this module walks that small HTML subset and * maps active scopes to caller-provided text formatters. */ -import hljs from "highlight.js"; +import { createRequire } from "node:module"; import { decodeHtmlEntityAt } from "./html.js"; +type HighlightJs = { + getLanguage(name: string): unknown; + highlight( + code: string, + options: { language: string; ignoreIllegals?: boolean }, + ): { value: string }; + highlightAuto(code: string, languageSubset?: string[]): { value: string }; +}; + +function isHighlightJs(value: unknown): value is HighlightJs { + return ( + typeof value === "object" && + value !== null && + "getLanguage" in value && + typeof value.getLanguage === "function" && + "highlight" in value && + typeof value.highlight === "function" && + "highlightAuto" in value && + typeof value.highlightAuto === "function" + ); +} + +// highlight.js ships `/// ` in its d.ts, which would +// silently re-inject DOM globals into the DOM-free core program. Load it +// untyped and validate the narrow API we use instead of importing its types. +const highlightJsModule: unknown = createRequire(import.meta.url)("highlight.js"); +if (!isHighlightJs(highlightJsModule)) { + throw new TypeError("highlight.js did not expose the expected Node API"); +} +const hljs = highlightJsModule; + /** Formatter applied to highlighted text segments. */ export type HighlightFormatter = (text: string) => string; /** Mapping from highlight.js scope names to text formatters. */ diff --git a/src/secrets/config-io.ts b/src/secrets/config-io.ts index ae9c062c6154..b1aeb40536ca 100644 --- a/src/secrets/config-io.ts +++ b/src/secrets/config-io.ts @@ -9,7 +9,9 @@ const silentConfigIoLogger = { /** * Creates config I/O for secrets commands with config-loader logging suppressed. */ -export function createSecretsConfigIO(params: { env: NodeJS.ProcessEnv }) { +export function createSecretsConfigIO(params: { + env: NodeJS.ProcessEnv; +}): ReturnType { // Secrets command output is owned by the CLI command so --json stays machine-parseable. return createConfigIO({ env: params.env, diff --git a/src/types/node-runtime-globals.d.ts b/src/types/node-runtime-globals.d.ts new file mode 100644 index 000000000000..6704154b3f0d --- /dev/null +++ b/src/types/node-runtime-globals.d.ts @@ -0,0 +1,20 @@ +// The core lane compiles with lib ES2023 (no DOM), so these web-global names +// no longer resolve. Derive them from the fetch globals @types/node already +// declares (backed by undici) instead of depending on undici-types directly; +// indexed-access derivation tracks @types/node across upgrades. +type BodyInit = Exclude; +type FormDataEntryValue = string | import("node:buffer").File; +type HeadersInit = Exclude; +type ReadableStreamReadResult = + | import("node:stream/web").ReadableStreamReadDoneResult + | import("node:stream/web").ReadableStreamReadValueResult; +type RequestCredentials = Exclude; +type RequestInfo = Parameters[0]; + +// Minimal surface for the one compile() caller; WebAssembly types otherwise +// live in lib.dom, which the core lane intentionally excludes. +declare namespace WebAssembly { + type Module = object; + + function compile(bytes: ArrayBuffer | ArrayBufferView): Promise; +} diff --git a/test/scripts/changed-lanes.test.ts b/test/scripts/changed-lanes.test.ts index fb506a2fdd8b..237284a01b38 100644 --- a/test/scripts/changed-lanes.test.ts +++ b/test/scripts/changed-lanes.test.ts @@ -598,6 +598,31 @@ describe("scripts/changed-lanes", () => { }); }); + it("routes UI production changes to UI prod and core test lanes", () => { + const result = detectChangedLanes(["ui/src/app.ts"]); + const plan = createChangedCheckPlan(result, { env: { PATH: "/usr/bin" } }); + + expectLanes(result.lanes, { + coreTests: true, + ui: true, + }); + expect(plan.commands.map((command) => command.args[0])).toContain("tsgo:ui"); + expect(plan.commands.map((command) => command.args[0])).toContain("tsgo:core:test"); + expect(plan.commands.map((command) => command.args[0])).not.toContain("tsgo:core"); + }); + + it("routes the UI production config to UI prod and core test lanes", () => { + const result = detectChangedLanes(["tsconfig.ui.json"]); + const plan = createChangedCheckPlan(result, { env: { PATH: "/usr/bin" } }); + + expectLanes(result.lanes, { + coreTests: true, + ui: true, + }); + expect(plan.commands.map((command) => command.args[0])).toContain("tsgo:ui"); + expect(plan.commands.map((command) => command.args[0])).toContain("tsgo:core:test"); + }); + it.each([ "scripts/control-ui-i18n.ts", "scripts/lib/example.ts", @@ -1851,6 +1876,7 @@ describe("scripts/changed-lanes", () => { expect(result.lanes).toEqual({ core: false, coreTests: false, + ui: false, extensions: false, extensionTests: false, scripts: false, diff --git a/test/scripts/oxlint-config.test.ts b/test/scripts/oxlint-config.test.ts index f877133b3b5e..ec9c7c6d1a92 100644 --- a/test/scripts/oxlint-config.test.ts +++ b/test/scripts/oxlint-config.test.ts @@ -140,15 +140,14 @@ describe("oxlint config", () => { "dist/", "dist-runtime/", "docs/_layouts/", + "**/a2ui.bundle.js", "extensions/diffs/assets/viewer-runtime.js", "extensions/diffs-language-pack/assets/viewer-runtime.js", - "extensions/canvas/src/host/a2ui/a2ui.bundle.js", "node_modules/", "patches/", "pnpm-lock.yaml", "skills/**", "src/auto-reply/reply/export-html/template.js", - "src/canvas-host/a2ui/a2ui.bundle.js", "vendor/", "**/.cache/**", "**/.openclaw-runtime-deps-copy-*/**", diff --git a/test/scripts/run-tsgo.test.ts b/test/scripts/run-tsgo.test.ts index 703e5165e852..fe3e48bb28cc 100644 --- a/test/scripts/run-tsgo.test.ts +++ b/test/scripts/run-tsgo.test.ts @@ -111,24 +111,41 @@ describe("run-tsgo sparse guard", () => { `); }); - it("returns a helpful message for sparse core worktrees missing transitive project files", () => { + it("returns a helpful message for sparse UI worktrees missing transitive project files", () => { const cwd = createTempDir("openclaw-run-tsgo-"); - const uiToolDisplay = path.join(cwd, "ui/src/ui/tool-display.ts"); + const uiToolDisplay = path.join(cwd, "ui/src/lib/chat/tool-display.ts"); fs.mkdirSync(path.dirname(uiToolDisplay), { recursive: true }); fs.writeFileSync(uiToolDisplay, "", "utf8"); expect( - getSparseTsgoGuardError(["-p", "tsconfig.core.json"], { + getSparseTsgoGuardError(["-p", "tsconfig.ui.json"], { cwd, isSparseCheckoutEnabled: () => true, }), ).toMatchInlineSnapshot(` - "tsconfig.core.json cannot be typechecked from this sparse checkout because tracked project inputs are missing or only partially included: + "tsconfig.ui.json cannot be typechecked from this sparse checkout because tracked project inputs are missing or only partially included: - apps/shared/OpenClawKit/Sources/OpenClawKit/Resources/tool-display.json Expand this worktree's sparse checkout to include those paths, or rerun in a full worktree." `); }); + it("rejects sparse UI worktrees missing the transitive src root", () => { + const cwd = createTempDir("openclaw-run-tsgo-"); + + expect( + getSparseTsgoGuardError(["-p", "tsconfig.ui.json"], { + cwd, + fileExists: () => true, + isSparseCheckoutEnabled: () => true, + sparseCheckoutPatterns: ["/packages/", "/ui/config/", "/ui/src/"], + }), + ).toMatchInlineSnapshot(` + "tsconfig.ui.json cannot be typechecked from this sparse checkout because tracked project inputs are missing or only partially included: + - src + Expand this worktree's sparse checkout to include those paths, or rerun in a full worktree." + `); + }); + it("returns a helpful message for sparse core-test worktrees missing ui and packages files", () => { const cwd = createTempDir("openclaw-run-tsgo-"); diff --git a/tsconfig.core.json b/tsconfig.core.json index 8f02d04071d3..57283740a1f0 100644 --- a/tsconfig.core.json +++ b/tsconfig.core.json @@ -1,10 +1,12 @@ { "extends": "./tsconfig.json", "compilerOptions": { + // Node-side production code must not see browser globals; ui/ owns DOM via tsconfig.ui.json. + "lib": ["ES2023"], "noUnusedLocals": true, "noUnusedParameters": true, "tsBuildInfoFile": ".artifacts/tsgo-cache/core.tsbuildinfo" }, - "include": ["src/**/*", "ui/**/*", "packages/**/*"], + "include": ["src/**/*", "packages/**/*"], "exclude": ["node_modules", "dist", "**/dist/**", "**/*.test.ts", "**/*.test.tsx", "test/**"] } diff --git a/tsconfig.core.projects.json b/tsconfig.core.projects.json index 5c7ff06bae9f..d66561f85d03 100644 --- a/tsconfig.core.projects.json +++ b/tsconfig.core.projects.json @@ -2,6 +2,7 @@ "files": [], "references": [ { "path": "./tsconfig.core.json" }, + { "path": "./tsconfig.ui.json" }, { "path": "./test/tsconfig/tsconfig.core.test.json" } ] } diff --git a/tsconfig.ui.json b/tsconfig.ui.json new file mode 100644 index 000000000000..b767d44eb571 --- /dev/null +++ b/tsconfig.ui.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noUnusedLocals": true, + "noUnusedParameters": true, + "tsBuildInfoFile": ".artifacts/tsgo-cache/ui.tsbuildinfo" + }, + "include": ["ui/**/*", "src/**/*.d.ts", "packages/**/*.d.ts"], + "exclude": ["node_modules", "dist", "**/dist/**", "**/*.test.ts", "**/*.test.tsx", "test/**"] +}