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');