chore(deps): cover .mts/.cts in the shared-source scan, fix the README validate row
Both from Copilot's review of #1962. The candidate-set enumeration matched only `.ts`/`.tsx`, while `verify:format-coverage` and `verify:typecheck-coverage` both gate all four TypeScript extensions. No `.mts`/`.cts` exists under `core/` or `test-servers/src` today, which is exactly why the omission would have gone unnoticed until a shared dependency arrived through one and skewed — the failure this guard exists to prevent. Extract the rule as the exported `isSharedSourceFile` predicate, cover all four extensions, and pin it with regression tests (including path-boundary anchoring, so a `core-internal/` sibling isn't swept in). The README's `validate` row also omitted `test:scripts` and claimed typecheck was cli/tui only; launcher runs one too, and web typechecks via `tsc -b` inside its `build`. Restate the row to match the actual script chain. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HDdo1rNRVQnVRRdsqSrvG3
This commit is contained in:
parent
6c33da38c9
commit
e480fdc49d
3 changed files with 51 additions and 3 deletions
|
|
@ -263,7 +263,7 @@ Each client self-validates from its own folder; the root scripts chain them. The
|
||||||
|
|
||||||
| Script | What it does |
|
| Script | What it does |
|
||||||
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `npm run validate` | Runs `verify:format-coverage` (asserts every tracked source file is format-gated) first, then `verify:typecheck-coverage` and `verify:dep-lockstep`, then `validate:core` (the shared `core/` `format:check` + `lint` gate), then per client: `format:check` + `lint` + **`typecheck`** (cli/tui only) + `build` + fast unit tests. The quick inner-loop check. |
|
| `npm run validate` | Runs the three durable guards first — `verify:format-coverage` (every tracked source file is format-gated), `verify:typecheck-coverage` (every one lands in a tsconfig project), `verify:dep-lockstep` (no install-crossing dependency skews) — then `test:scripts` (the guards' own parser unit tests), then `validate:core` (the shared `core/` `format:check` + `lint` gate), then per client: `format:check` + `lint` + **`typecheck`** (cli/tui/launcher; web typechecks via `tsc -b` inside its `build`) + `build` + fast unit tests. The quick inner-loop check. |
|
||||||
| `npm run coverage` | The **per-file ≥90% gate** (lines/statements/functions/branches) under v8 instrumentation, per client. CI-enforced. For web this also runs the integration project and covers the shared `core/` runtime (including `core/json` and `core/client`). |
|
| `npm run coverage` | The **per-file ≥90% gate** (lines/statements/functions/branches) under v8 instrumentation, per client. CI-enforced. For web this also runs the integration project and covers the shared `core/` runtime (including `core/json` and `core/client`). |
|
||||||
| `npm run smoke` | End-to-end smokes through the built launcher (`--help` dispatch + prod cli/tui/web), plus two headless-Chromium smokes: a boot smoke that runs the prod web bundle and asserts a clean first render (no uncaught error — sync exception or unhandled rejection, how a Node built-in reaching the browser bundle manifests), and an **MCP Apps** smoke (`smoke:web:app`) that drives connect → open app → `data-app-status="ready"` against a composable App server, covering the sandbox proxy and UI-protocol bridge. |
|
| `npm run smoke` | End-to-end smokes through the built launcher (`--help` dispatch + prod cli/tui/web), plus two headless-Chromium smokes: a boot smoke that runs the prod web bundle and asserts a clean first render (no uncaught error — sync exception or unhandled rejection, how a Node built-in reaching the browser bundle manifests), and an **MCP Apps** smoke (`smoke:web:app`) that drives connect → open app → `data-app-status="ready"` against a composable App server, covering the sandbox proxy and UI-protocol bridge. |
|
||||||
| `npm run verify:build-gate` | Runs a real `vite build` with a Node built-in forced into the browser graph and asserts the build **fails** via the #1769 gate (which turns Vite's browser-externalization warning into a hard error). Guards against the warning phrasing drifting in a Vite bump and silently disabling the gate. Part of `npm run ci`. |
|
| `npm run verify:build-gate` | Runs a real `vite build` with a Node built-in forced into the browser graph and asserts the build **fails** via the #1769 gate (which turns Vite's browser-externalization warning into a hard error). Guards against the warning phrasing drifting in a Vite bump and silently disabling the gate. Part of `npm run ci`. |
|
||||||
|
|
|
||||||
|
|
@ -170,14 +170,30 @@ export function partitionSkew(skewed, tolerated = TOLERATED_SKEW) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Tracked `.ts`/`.tsx` files under the shared first-party source trees. */
|
// The TypeScript extensions the shared trees can hold. Deliberately the same
|
||||||
|
// four `verify:format-coverage` and `verify:typecheck-coverage` gate on: a
|
||||||
|
// `.mts`/`.cts` under `core/` or `test-servers/src` is typechecked like any
|
||||||
|
// other source, so its imports must reach the candidate set too. None exist
|
||||||
|
// under those trees today, which is exactly why omitting them would go
|
||||||
|
// unnoticed until a new shared dependency arrived through one and skewed.
|
||||||
|
const SOURCE_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts"];
|
||||||
|
|
||||||
|
/** Whether a repo-relative path is a TypeScript source of a shared tree. */
|
||||||
|
export function isSharedSourceFile(file) {
|
||||||
|
if (!SOURCE_EXTENSIONS.some((ext) => file.endsWith(ext))) return false;
|
||||||
|
// Anchored on a path boundary so a sibling whose name merely starts with a
|
||||||
|
// shared dir (`core-internal/`, `test-servers/src-legacy/`) isn't swept in.
|
||||||
|
return SHARED_SOURCE_DIRS.some((dir) => file.startsWith(`${dir}/`));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tracked TypeScript files under the shared first-party source trees. */
|
||||||
function sharedSourceFiles() {
|
function sharedSourceFiles() {
|
||||||
const out = execFileSync(
|
const out = execFileSync(
|
||||||
"git",
|
"git",
|
||||||
["ls-files", "--", ...SHARED_SOURCE_DIRS.map((d) => `${d}/**`)],
|
["ls-files", "--", ...SHARED_SOURCE_DIRS.map((d) => `${d}/**`)],
|
||||||
{ cwd: repoRoot, encoding: "utf8" },
|
{ cwd: repoRoot, encoding: "utf8" },
|
||||||
);
|
);
|
||||||
return out.split("\n").filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
|
return out.split("\n").filter(isSharedSourceFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,43 @@ import assert from "node:assert/strict";
|
||||||
import {
|
import {
|
||||||
findSkew,
|
findSkew,
|
||||||
importedPackageNames,
|
importedPackageNames,
|
||||||
|
isSharedSourceFile,
|
||||||
packageNameOf,
|
packageNameOf,
|
||||||
partitionSkew,
|
partitionSkew,
|
||||||
topLevelLockVersions,
|
topLevelLockVersions,
|
||||||
} from "./verify-dep-lockstep.mjs";
|
} from "./verify-dep-lockstep.mjs";
|
||||||
|
|
||||||
|
test("isSharedSourceFile: all four TS extensions, not just .ts/.tsx", () => {
|
||||||
|
// `.mts`/`.cts` are gated by `verify:format-coverage` and
|
||||||
|
// `verify:typecheck-coverage` too. None exist under the shared trees today,
|
||||||
|
// so dropping them here would go unnoticed until a new shared dependency
|
||||||
|
// arrived through one — the skew this guard exists to catch (Copilot, #1962).
|
||||||
|
for (const ext of [".ts", ".tsx", ".mts", ".cts"]) {
|
||||||
|
assert.equal(isSharedSourceFile(`core/mcp/thing${ext}`), true, ext);
|
||||||
|
assert.equal(
|
||||||
|
isSharedSourceFile(`test-servers/src/thing${ext}`),
|
||||||
|
true,
|
||||||
|
`test-servers ${ext}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("isSharedSourceFile: non-TS files and other trees are excluded", () => {
|
||||||
|
const rejected = [
|
||||||
|
"core/README.md", // not TypeScript
|
||||||
|
"core/mcp/data.json",
|
||||||
|
"clients/web/src/App.tsx", // a client's own sources resolve from the client
|
||||||
|
"scripts/verify-dep-lockstep.mjs",
|
||||||
|
"test-servers/configs/modern-http.json",
|
||||||
|
// Path-boundary anchoring: a sibling dir whose name merely starts with a
|
||||||
|
// shared dir's name must not be swept in.
|
||||||
|
"core-internal/thing.ts",
|
||||||
|
"test-servers/src-legacy/thing.ts",
|
||||||
|
];
|
||||||
|
for (const file of rejected)
|
||||||
|
assert.equal(isSharedSourceFile(file), false, file);
|
||||||
|
});
|
||||||
|
|
||||||
test("packageNameOf: bare names, scopes, and subpaths", () => {
|
test("packageNameOf: bare names, scopes, and subpaths", () => {
|
||||||
const cases = [
|
const cases = [
|
||||||
["zod", "zod"],
|
["zod", "zod"],
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue