From bb1d7692ff7c742da62f8b0b7b1951aa98e3d6b0 Mon Sep 17 00:00:00 2001 From: Tom Riglar Date: Fri, 12 Jun 2026 20:55:21 +0100 Subject: [PATCH] fix: enable TypeScript strict mode and type-check tests in CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit strict: false had the entire strict family off for a published binary parsing loosely-typed API responses. The burn-down turned out to be small — five errors: - upgrade.ts: DOM/Node web-stream typing clash on Readable.fromWeb (same cast pattern already used in api-gateway.ts and expo.ts) - results-polling: TestResult indexed an optional `results` array; duration_seconds needed an explicit null for absent values - version.service: resolveMaestroVersion could carry undefined through to its return — now fails fast with a clear message when compatibility data provides no default version Also closes the "tests get zero static checking" gap: tsconfig.test.json type-checks src + test strictly via `pnpm typecheck` (new CI step), and `pnpm lint` now covers test/ (with chai's property-style assertions exempted from no-unused-expressions). forceConsistentCasingInFileNames enabled alongside. Co-Authored-By: Claude Fable 5 --- .github/workflows/cli-ci.yml | 4 ++++ CLAUDE.md | 3 ++- eslint.config.js | 8 ++++++++ package.json | 5 +++-- src/commands/upgrade.ts | 5 ++++- src/services/results-polling.service.ts | 7 ++++--- src/services/version.service.ts | 6 ++++++ tsconfig.json | 5 +++-- tsconfig.test.json | 10 ++++++++++ 9 files changed, 44 insertions(+), 9 deletions(-) create mode 100644 tsconfig.test.json diff --git a/.github/workflows/cli-ci.yml b/.github/workflows/cli-ci.yml index d524c23..ba0d517 100644 --- a/.github/workflows/cli-ci.yml +++ b/.github/workflows/cli-ci.yml @@ -58,6 +58,10 @@ jobs: working-directory: ./cli run: pnpm lint + - name: Type check (strict, src + tests) + working-directory: ./cli + run: pnpm typecheck + - name: Run CLI tests working-directory: ./cli env: diff --git a/CLAUDE.md b/CLAUDE.md index 66fd340..e4d124b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,7 +6,8 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co - `pnpm dcd ` — run the CLI from source via `tsx`. - `pnpm build` — clean, compile TypeScript to `dist/`, and `chmod +x dist/index.js` so the published binary is directly executable. -- `pnpm lint` — ESLint over `src/`. +- `pnpm lint` — ESLint over `src/` and `test/`. +- `pnpm typecheck` — `tsc --noEmit` over `src/` and `test/` (strict mode; `pnpm build` only compiles `src/`). - `pnpm test` — runs `scripts/test-runner.mjs`: builds the CLI, boots a mock API, then runs all `test/**/*.test.ts` via mocha + ts-node. The mock API lives in the **sibling `dcd/` repo** (`../dcd/mock-api`). Override its location with `MOCK_API_DIR=/path/to/mock-api`. - Run a single test: `pnpm mocha test/integration/cloud.integration.test.ts --timeout 60000` (picks up `.mocharc.json` which wires tsx; requires the mock API already running on its expected port). diff --git a/eslint.config.js b/eslint.config.js index 6be1e43..7bbdba9 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -74,4 +74,12 @@ module.exports = tseslint.config( '@typescript-eslint/no-require-imports': 'off', }, }, + { + // Chai's property-style assertions (`expect(x).to.be.true`) are + // expression statements by design. + files: ['test/**/*.ts'], + rules: { + '@typescript-eslint/no-unused-expressions': 'off', + }, + }, ); diff --git a/package.json b/package.json index 785e4f6..630735c 100644 --- a/package.json +++ b/package.json @@ -60,9 +60,10 @@ "dcd": "tsx src/index.ts", "build": "shx rm -rf dist && tsc -b && shx chmod +x dist/index.js", "build:binaries": "node scripts/build-binaries.mjs", - "lint": "eslint src --ext .ts", + "lint": "eslint src test --ext .ts", "prepare": "pnpm build", - "test": "node scripts/test-runner.mjs" + "test": "node scripts/test-runner.mjs", + "typecheck": "tsc --noEmit -p tsconfig.test.json" }, "version": "5.0.0", "bugs": { diff --git a/src/commands/upgrade.ts b/src/commands/upgrade.ts index 57e5644..a499710 100644 --- a/src/commands/upgrade.ts +++ b/src/commands/upgrade.ts @@ -131,7 +131,10 @@ async function downloadToFile(url: string, dest: string): Promise { throw new Error(`HTTP ${res.status} fetching ${url}`); } // Node 22 exposes Readable.fromWeb for piping a WHATWG ReadableStream. - await pipeline(Readable.fromWeb(res.body), createWriteStream(dest)); + await pipeline( + Readable.fromWeb(res.body as Parameters[0]), + createWriteStream(dest), + ); } async function fetchExpectedChecksum( diff --git a/src/services/results-polling.service.ts b/src/services/results-polling.service.ts index a6f83e6..15c2c2f 100644 --- a/src/services/results-polling.service.ts +++ b/src/services/results-polling.service.ts @@ -8,8 +8,9 @@ import { checkInternetConnectivity } from '../utils/connectivity'; import { ux } from '../utils/progress'; import { colors, formatTestSummary, table } from '../utils/styling'; -type TestResult = - paths['/results/{uploadId}']['get']['responses']['200']['content']['application/json']['results'][number]; +type TestResult = NonNullable< + paths['/results/{uploadId}']['get']['responses']['200']['content']['application/json']['results'] +>[number]; /** * Custom error for run failures that includes the polling result @@ -177,7 +178,7 @@ export class ResultsPollingService { ? 'PASSED' : 'FAILED', tests: resultsWithoutEarlierTries.map((r) => ({ - durationSeconds: r.duration_seconds, + durationSeconds: r.duration_seconds ?? null, failReason: r.status === 'FAILED' ? r.fail_reason || 'No reason provided' : undefined, fileName: r.test_file_name, diff --git a/src/services/version.service.ts b/src/services/version.service.ts index bc0e1be..7ea255d 100644 --- a/src/services/version.service.ts +++ b/src/services/version.service.ts @@ -92,6 +92,12 @@ export class VersionService { } } + if (!resolvedVersion) { + throw new Error( + 'Unable to resolve a Maestro version: compatibility data did not provide a default.', + ); + } + // Validate Maestro version if (!supportedVersions.includes(resolvedVersion)) { throw new Error( diff --git a/tsconfig.json b/tsconfig.json index 4b0768c..7fa0664 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,10 +4,11 @@ "module": "commonjs", "outDir": "dist", "rootDir": "src", - "strict": false, + "strict": true, "target": "es2022", "skipLibCheck": true, - "resolveJsonModule": true + "resolveJsonModule": true, + "forceConsistentCasingInFileNames": true }, "include": ["src/**/*"] } diff --git a/tsconfig.test.json b/tsconfig.test.json new file mode 100644 index 0000000..9e36406 --- /dev/null +++ b/tsconfig.test.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true, + "declaration": false, + "rootDir": ".", + "types": ["node", "mocha", "chai"] + }, + "include": ["src/**/*", "test/**/*"] +}