From f14d21f04794de5ed075d64f8c920f5519a9b251 Mon Sep 17 00:00:00 2001 From: Yan Lucas Date: Mon, 15 Jun 2026 23:10:33 -0300 Subject: [PATCH] fix(e2e): migrate the demo project and full-flow suite to v2 (TV4.3) main CI regressed to red after v2 landed: the v2 config loader rejects examples/demo-project's schemaVersion 1.0 config, so the E2E suite failed with "Unexpected end of JSON input" (the CLI emitted no report). Migrate the demo project and the E2E harness to the v2 model using path-linked checks: - examples/demo-project/sentiness.config.json is now schemaVersion 2.0 with an engine pin and a path-linked biome check. - The harness writes v2 configs (buildV2Config) that path-link each check to its built package under packages/checks/* via a project-relative path, replacing the node_modules/@sentiness symlink setup. - The non-interactive `init` E2E assertion now expects the v2 output ({ biome: { version: '*', tier: 'fast' } }). Path-linked checks need no cache, no `sentiness install`, and no project node_modules: the engine resolves them directly from the repo, and each check's external tool (biome) resolves from the inherited PATH (cliEnv prepends the repo node_modules/.bin). All 14 E2E cases pass, alongside 209 unit tests, typecheck, lint, check:release-packages (15 packages), and the dogfood check --tier=fast. --- docs/progress.md | 25 +++++-- examples/demo-project/sentiness.config.json | 8 +-- packages/core/test/e2e/full-flow.test.ts | 78 ++++++++++----------- 3 files changed, 60 insertions(+), 51 deletions(-) diff --git a/docs/progress.md b/docs/progress.md index d01bf1d..6482062 100644 --- a/docs/progress.md +++ b/docs/progress.md @@ -72,13 +72,24 @@ root zone). Per-zone `checks` are the enabled catalog ids the zone owns (`clippy (the only non-JS check in v2); go zones are recorded with no checks yet. The generated polyglot config validates against the v2 schema (asserted in tests). -### Still deferred: `pnpm test:e2e` (out of this scope, spec task TV4.3) - -`pnpm test:e2e` (`packages/core/test/e2e/full-flow.test.ts`) runs the built CLI -against `examples/demo-project`, whose `sentiness.config.json` is still v1 and -whose checks resolve from `node_modules`. Migrating the demo config + the harness -to the v2 cache/`install` model is a larger rewrite that belongs to the V4 E2E -plan; left as-is here to keep this change focused on `init`. +### E2E migrated to v2 (TV4.3) — done (2026-06-16) + +`pnpm test:e2e` (`packages/core/test/e2e/full-flow.test.ts`) and +`examples/demo-project` were migrated to the v2 model. The demo config is now +`schemaVersion: '2.0'` with a **path-linked** biome check; the harness writes v2 +configs (`buildV2Config`) that path-link each check to its built package under +`packages/checks/*` via a project-relative path, instead of symlinking into +`node_modules/@sentiness`. This needs **no cache, no `sentiness install`, and no +project node_modules**: the engine resolves path-linked checks directly from the +repo, and each check's external tool (e.g. biome) still resolves from the +inherited PATH (`cliEnv` prepends the repo `node_modules/.bin`). The non- +interactive `init` E2E assertion was updated to the v2 output +(`{ biome: { version: '*', tier: 'fast' } }`). + +This unblocked `main` CI, which regressed to red after v2 landed because the v1 +demo config was rejected by the v2 loader. All 14 E2E cases pass, alongside 209 +unit tests, typecheck, lint, `check:release-packages` (15 packages), and the +dogfood `check --tier=fast`. ## Implementation approach diff --git a/examples/demo-project/sentiness.config.json b/examples/demo-project/sentiness.config.json index 7f34dbd..ee00545 100644 --- a/examples/demo-project/sentiness.config.json +++ b/examples/demo-project/sentiness.config.json @@ -1,9 +1,7 @@ { - "schemaVersion": "1.0", + "schemaVersion": "2.0", + "engine": "0.1.4", "checks": { - "biome": { - "enabled": true, - "tier": "fast" - } + "biome": { "path": "../../packages/checks/biome", "tier": "fast" } } } diff --git a/packages/core/test/e2e/full-flow.test.ts b/packages/core/test/e2e/full-flow.test.ts index 9351bf5..2673f00 100644 --- a/packages/core/test/e2e/full-flow.test.ts +++ b/packages/core/test/e2e/full-flow.test.ts @@ -1,8 +1,8 @@ import { execFile, spawn } from 'node:child_process'; import { existsSync } from 'node:fs'; -import { cp, mkdir, mkdtemp, readFile, rm, symlink, writeFile } from 'node:fs/promises'; +import { cp, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; -import { dirname, join, resolve } from 'node:path'; +import { dirname, join, relative, resolve } from 'node:path'; import process from 'node:process'; import { fileURLToPath } from 'node:url'; import { promisify } from 'node:util'; @@ -141,21 +141,45 @@ function parseJson(stdout: string): unknown { return JSON.parse(stdout.trim()); } +// v2 config: each check is path-linked directly to the repo's built check +// package (relative to the project dir), so the engine resolves it without a +// cache or any project node_modules. The check's external tool (e.g. biome) +// still resolves from the inherited PATH via `cliEnv` (rootBinPath). +function buildV2Config( + projectDir: string, + checks: readonly CheckPackageId[], + perCheck: Partial>> = {}, +): Record { + return { + schemaVersion: '2.0', + engine: '0.1.4', + checks: Object.fromEntries( + checks.map((id) => [ + id, + { path: relative(projectDir, checkPackages[id]), ...(perCheck[id] ?? {}) }, + ]), + ), + }; +} + +async function writeConfig(projectDir: string, config: Record): Promise { + await writeFile( + join(projectDir, 'sentiness.config.json'), + `${JSON.stringify(config, null, 2)}\n`, + ); +} + async function createDemoCopy( source: string, checks: readonly CheckPackageId[] = ['biome'], + perCheck: Partial>> = {}, ): Promise { const tempRoot = await mkdtemp(join(tmpdir(), 'sentiness-e2e-')); cleanupPaths.push(tempRoot); const projectDir = join(tempRoot, 'demo-project'); await cp(demoProject, projectDir, { recursive: true }); await writeFile(join(projectDir, 'src/index.ts'), source); - - const sentinessScope = join(projectDir, 'node_modules/@sentiness'); - await mkdir(sentinessScope, { recursive: true }); - for (const check of checks) { - await symlink(checkPackages[check], join(sentinessScope, `check-${check}`), 'dir'); - } + await writeConfig(projectDir, buildV2Config(projectDir, checks, perCheck)); return projectDir; } @@ -251,17 +275,6 @@ describe('Sentiness CLI E2E full flow', () => { it('flags a missing tool config in doctor and writes it through init-config', async () => { const projectDir = await createDemoCopy('export const value = 1;\n', ['dependency-cruiser']); - await writeFile( - join(projectDir, 'sentiness.config.json'), - `${JSON.stringify( - { - schemaVersion: '1.0', - checks: { 'dependency-cruiser': { enabled: true } }, - }, - null, - 2, - )}\n`, - ); const before = await runCli(projectDir, ['doctor']); const beforeDoctor = DoctorResultSchema.parse(parseJson(before.stdout)); @@ -458,25 +471,10 @@ describe('Sentiness CLI E2E full flow', () => { }); it('ratchets metric baselines through baseline update', async () => { - const projectDir = await createDemoCopy('export const value = 1;\nexport const other = 2;\n', [ - 'coverage', - ]); - await writeFile( - join(projectDir, 'sentiness.config.json'), - `${JSON.stringify( - { - schemaVersion: '1.0', - checks: { - coverage: { - enabled: true, - tier: 'slow', - thresholds: { lineCoverage: 0 }, - }, - }, - }, - null, - 2, - )}\n`, + const projectDir = await createDemoCopy( + 'export const value = 1;\nexport const other = 2;\n', + ['coverage'], + { coverage: { tier: 'slow', thresholds: { lineCoverage: 0 } } }, ); await writeCoverageReport(projectDir, [1, 0]); await initGitRepo(projectDir); @@ -565,7 +563,9 @@ describe('Sentiness CLI E2E full flow', () => { const gitignore = await readFile(join(projectDir, '.gitignore'), 'utf8'); expect(result.exitCode).toBe(0); - expect(config.checks).toEqual({ biome: { enabled: true, tier: 'fast' } }); + expect(config.schemaVersion).toBe('2.0'); + expect(typeof config.engine).toBe('string'); + expect(config.checks).toEqual({ biome: { version: '*', tier: 'fast' } }); expect(config.reporting.omitOk).toBe(true); expect(gitignore).toContain('.sentiness/jobs/'); expect(gitignore).toContain('.sentiness/pending-feedback.json');