diff --git a/examples/app-showcase/e2e/bulk-capability-gate.spec.ts b/examples/app-showcase/e2e/bulk-capability-gate.spec.ts index f44c213b31..b3e3fd77a0 100644 --- a/examples/app-showcase/e2e/bulk-capability-gate.spec.ts +++ b/examples/app-showcase/e2e/bulk-capability-gate.spec.ts @@ -27,7 +27,7 @@ import { test, expect } from '@playwright/test'; */ // Ambient `process` for the env read below — the showcase tsconfig doesn't pull -// in `@types/node`, and the package-global shim in test/node-shim.d.ts declares +// in `@types/node`, and the package-global shim in types/node-shim.d.ts declares // only `cwd()`. Same idiom (and same reason) as the declarations in // objectstack.config.ts and src/system/self-url.ts: keeps `pnpm typecheck` green // without widening the type surface. Playwright provides the real `process`. diff --git a/examples/app-showcase/e2e/detail-shapes.spec.ts b/examples/app-showcase/e2e/detail-shapes.spec.ts index 9c5570f9fc..641fc8300c 100644 --- a/examples/app-showcase/e2e/detail-shapes.spec.ts +++ b/examples/app-showcase/e2e/detail-shapes.spec.ts @@ -27,7 +27,7 @@ import { test, expect } from '@playwright/test'; */ // Ambient `process` for the env reads below — the showcase tsconfig doesn't pull -// in `@types/node`, and the package-global shim in test/node-shim.d.ts declares +// in `@types/node`, and the package-global shim in types/node-shim.d.ts declares // only `cwd()`. Same idiom (and same reason) as the declarations in // objectstack.config.ts and src/system/self-url.ts: keeps `pnpm typecheck` green // without widening the type surface. Playwright provides the real `process`. diff --git a/examples/app-showcase/e2e/global-setup.ts b/examples/app-showcase/e2e/global-setup.ts index 6209c9ddd1..f889a4b186 100644 --- a/examples/app-showcase/e2e/global-setup.ts +++ b/examples/app-showcase/e2e/global-setup.ts @@ -2,6 +2,13 @@ import { request } from '@playwright/test'; import { mkdirSync, writeFileSync } from 'node:fs'; import { dirname } from 'node:path'; +// Ambient `process` with `env` — types/node-shim.d.ts declares the global as +// `{ cwd(): string }` only, so each module that reads env widens it locally. +// Same one-liner as the specs beside this file and objectstack.config.ts. +// Declared above the block comment below so that comment stays attached to the +// first emitted statement. +declare const process: { env: Record }; + /** * Auth for the showcase smoke: sign in against the backend (better-auth) and * persist a Playwright storageState. The console (served at :3000/_console) diff --git a/examples/app-showcase/e2e/showcase-smoke.spec.ts b/examples/app-showcase/e2e/showcase-smoke.spec.ts index 185078757f..bc28dac5e1 100644 --- a/examples/app-showcase/e2e/showcase-smoke.spec.ts +++ b/examples/app-showcase/e2e/showcase-smoke.spec.ts @@ -8,7 +8,7 @@ import { test, expect } from '@playwright/test'; */ // Ambient `process` for the env read below — the showcase tsconfig doesn't pull -// in `@types/node`, and the package-global shim in test/node-shim.d.ts declares +// in `@types/node`, and the package-global shim in types/node-shim.d.ts declares // only `cwd()`. Same idiom (and same reason) as the declarations in // objectstack.config.ts and src/system/self-url.ts: keeps `pnpm typecheck` green // without widening the type surface. Playwright provides the real `process`. diff --git a/examples/app-showcase/playwright.config.ts b/examples/app-showcase/playwright.config.ts index d15ebcfec8..566a8bd383 100644 --- a/examples/app-showcase/playwright.config.ts +++ b/examples/app-showcase/playwright.config.ts @@ -1,5 +1,11 @@ import { defineConfig, devices } from '@playwright/test'; +// Ambient `process` with `env` — types/node-shim.d.ts declares the global as +// `{ cwd(): string }` only, so each module that reads env widens it locally. +// Declared above the block comment below so that comment stays attached to the +// first emitted statement. +declare const process: { env: Record }; + /** * Showcase smoke — drives the console (served by the backend at /_console) * across every nav surface. `webServer` boots the real backend so CI only needs diff --git a/examples/app-showcase/test/connector-self-url.test.ts b/examples/app-showcase/test/connector-self-url.test.ts index d4bdd24431..65da8669be 100644 --- a/examples/app-showcase/test/connector-self-url.test.ts +++ b/examples/app-showcase/test/connector-self-url.test.ts @@ -31,7 +31,7 @@ import { * detect the bug, so both halves are required. */ -// Ambient `process` with `env` — test/node-shim.d.ts declares the global as +// Ambient `process` with `env` — types/node-shim.d.ts declares the global as // `{ cwd(): string }` only, and this module-scoped declaration shadows it // rather than widening the shared shim (the same idiom objectstack.config.ts // uses for its own env reads). diff --git a/examples/app-showcase/test/node-shim.d.ts b/examples/app-showcase/test/node-shim.d.ts deleted file mode 100644 index a6ca0ddfa7..0000000000 --- a/examples/app-showcase/test/node-shim.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. - -// Minimal ambient surface for the node builtins the tests touch. The -// showcase tsconfig deliberately omits `@types/node` (see the ambient -// `process` note in objectstack.config.ts); vitest provides the real -// implementations at runtime. - -declare module 'node:fs' { - export function existsSync(path: string): boolean; - export function readFileSync(path: string, encoding: 'utf8'): string; - export function readdirSync( - path: string, - options: { withFileTypes: true }, - ): Array<{ name: string; isDirectory(): boolean }>; -} - -declare const process: { cwd(): string }; diff --git a/examples/app-showcase/tsconfig.json b/examples/app-showcase/tsconfig.json index f183228be9..2d3cf30938 100644 --- a/examples/app-showcase/tsconfig.json +++ b/examples/app-showcase/tsconfig.json @@ -10,19 +10,30 @@ "outDir": "./dist", "rootDir": "." }, - // `e2e/**/*.spec.ts`, NOT `e2e/**/*` (#7923). This package took the widened- - // `include` route rather than a sibling `tsconfig.test.json` because its - // `rootDir` is already the package root, so nothing here needs neutralising — - // the same repair #7312 applied to app-crm / app-todo. + // This package took the widened-`include` route rather than a sibling + // `tsconfig.test.json` because its `rootDir` is already the package root, so + // nothing here needs neutralising — the same repair #7312 applied to + // app-crm / app-todo. // - // The glob is spec-scoped on purpose, and it is the trap the TEST_DEBT entry - // that this change deletes was written to warn about: `e2e/**/*` would also - // pull in `e2e/global-setup.ts`, a Playwright fixture rather than a test, - // which carries 6 errors of its own (3 `process.env` reads plus 3 gaps in - // test/node-shim.d.ts — no `mkdirSync`/`writeFileSync` on the `node:fs` shim - // and no `node:path` module at all). Billing the test layer for a non-test - // file is what the ledger note measured around, so the repair holds the same - // line. `global-setup.ts` therefore stays outside this program; it is filed - // separately rather than silently folded in here. - "include": ["src/**/*", "objectstack.config.ts", "test/**/*", "e2e/**/*.spec.ts"] + // `e2e/**/*` is deliberately wholesale, not `e2e/**/*.spec.ts` (#8062): the + // spec-scoped form left `e2e/global-setup.ts` — the fixture that authenticates + // the whole smoke run — read by no tsc program at all, and the coverage gate + // counts test files, so nothing else had an opinion on it either. The 6 errors + // the narrow glob was avoiding are fixed at their source rather than excluded + // (file-local `declare const process`, plus the `node:fs`/`node:path` members + // in types/node-shim.d.ts), so the wholesale form now costs nothing. + // + // `playwright.config.ts` is named for the same reason: it configures that same + // smoke lane and was dark in exactly the same way. `vitest.config.ts` is NOT + // named — its `vitest/config` import transitively drags in the whole of + // `@types/node`, which is precisely the type surface this package is + // deliberately without (see types/node-shim.d.ts). + "include": [ + "src/**/*", + "objectstack.config.ts", + "playwright.config.ts", + "types/**/*", + "test/**/*", + "e2e/**/*" + ] } diff --git a/examples/app-showcase/types/node-shim.d.ts b/examples/app-showcase/types/node-shim.d.ts new file mode 100644 index 0000000000..04fac89b2d --- /dev/null +++ b/examples/app-showcase/types/node-shim.d.ts @@ -0,0 +1,34 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +// Minimal ambient surface for the node builtins this package touches. The +// showcase tsconfig deliberately omits `@types/node` (see the ambient +// `process` note in objectstack.config.ts); the real implementations come from +// the runtime — vitest for `test/`, the Playwright runner for `e2e/`. +// +// It lives in `types/`, not `test/`, because both trees depend on it: `test/` +// for `existsSync`/`readFileSync`/`readdirSync` and the `process.cwd()` global, +// `e2e/` for `mkdirSync`/`writeFileSync`/`dirname` in global-setup.ts. Under the +// old `test/` name, narrowing it for the test layer would have broken the e2e +// program with nothing in the path to warn the author. +// +// Declare only the members actually imported. Staying narrower than +// `@types/node` is the point: it IS installed at the workspace root and would +// resolve if this package named it in `compilerOptions.types`, at the cost of +// the whole node global surface the package is deliberately without. + +declare module 'node:fs' { + export function existsSync(path: string): boolean; + export function readFileSync(path: string, encoding: 'utf8'): string; + export function readdirSync( + path: string, + options: { withFileTypes: true }, + ): Array<{ name: string; isDirectory(): boolean }>; + export function mkdirSync(path: string, options: { recursive: true }): string | undefined; + export function writeFileSync(path: string, data: string): void; +} + +declare module 'node:path' { + export function dirname(path: string): string; +} + +declare const process: { cwd(): string };