From 0991c04cd723f6162d10c228cf96189f4fc355f3 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 31 Jul 2026 05:09:36 +0000 Subject: [PATCH] fix(seed-posts): import dieWithLabel before calling it (Pass 4 P1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The P3-H CLI consolidation left seed-posts calling dieWithLabel with no import — a ReferenceError on the empty-fetch abort path, invisible to lint/typecheck/knip. Recurrence guard in the same delivery: scriptCliConventions.unit.spec.ts fails the build when a script calls dieWithLabel/dieAgent without importing (or defining) it, and bans re-spelled 'const DAY_MS =' outside src/lib/text.ts (P3-K's fold leaked one private copy in engagementLevel.ts). --- scripts/seed-posts.mjs | 2 +- tests/unit/scriptCliConventions.unit.spec.ts | 63 ++++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 tests/unit/scriptCliConventions.unit.spec.ts diff --git a/scripts/seed-posts.mjs b/scripts/seed-posts.mjs index 15c7040d..beb7d38e 100644 --- a/scripts/seed-posts.mjs +++ b/scripts/seed-posts.mjs @@ -27,7 +27,7 @@ import { convertHTMLToLexical, editorConfigFactory } from '@payloadcms/richtext- import { del as blobDel } from '@vercel/blob' import { JSDOM } from 'jsdom' import { getPayload } from 'payload' -import { loadCliEnv } from './lib/cli.mjs' +import { dieWithLabel, loadCliEnv } from './lib/cli.mjs' import { assertLocalDatabase } from './assert-local-database.mjs' diff --git a/tests/unit/scriptCliConventions.unit.spec.ts b/tests/unit/scriptCliConventions.unit.spec.ts new file mode 100644 index 00000000..58b5e8b8 --- /dev/null +++ b/tests/unit/scriptCliConventions.unit.spec.ts @@ -0,0 +1,63 @@ +import { readdirSync, readFileSync } from 'node:fs' +import { relative, resolve } from 'node:path' +import { describe, expect, it } from 'vitest' + +// Pass 4 (2026-07-31): `scripts/seed-posts.mjs` called `dieWithLabel(…)` +// without importing it — a ReferenceError on the empty-fetch abort path that +// lint, typecheck and knip all let through (P3-H half-adopted in the same pass +// that added the guard). The CLI skeleton is only a skeleton when the symbols +// it exports are actually imported. +// +// `DAY_MS` is single-sourced in `src/lib/text.ts` (P3-K); a private re-spell +// survived in `engagementLevel.ts` until this pass — ban the declaration form. + +const repoRoot = process.cwd() +const repoPath = (absolute: string) => relative(repoRoot, absolute) + +const walkFiles = (root: string, extensions: readonly string[]): string[] => + readdirSync(root, { recursive: true, withFileTypes: true }) + .filter((entry) => entry.isFile() && extensions.some((ext) => entry.name.endsWith(ext))) + .map((entry) => resolve(entry.parentPath, entry.name)) + +describe('script CLI skeleton (Pass 4)', () => { + const scriptFiles = walkFiles(resolve(repoRoot, 'scripts'), ['.mjs']) + + it('imports dieWithLabel / dieAgent wherever they are called', () => { + const offenders: string[] = [] + + for (const file of scriptFiles) { + const source = readFileSync(file, 'utf8') + for (const symbol of ['dieWithLabel', 'dieAgent'] as const) { + const callsIt = new RegExp(`\\b${symbol}\\s*\\(`).test(source) + if (!callsIt) continue + const definesIt = new RegExp(`export const ${symbol}\\b`).test(source) + const importsIt = new RegExp(`^import\\s*\\{[^}]*\\b${symbol}\\b[^}]*\\}`, 'm').test(source) + if (!definesIt && !importsIt) offenders.push(`${repoPath(file)} (${symbol})`) + } + } + + expect( + offenders, + 'import the CLI die helper from scripts/lib instead of calling it bare', + ).toEqual([]) + }) + + it('keeps DAY_MS single-sourced in src/lib/text.ts', () => { + const offenders: string[] = [] + const declaration = /const DAY_MS\s*=/ // this spec quotes the shape itself + + for (const root of ['src', 'tests', 'scripts'] as const) { + for (const file of walkFiles(resolve(repoRoot, root), ['.ts', '.tsx', '.mjs'])) { + const path = repoPath(file) + if (path === 'src/lib/text.ts') continue + if (path === repoPath(import.meta.filename)) continue + const lines = readFileSync(file, 'utf8').split('\n') + for (const [index, line] of lines.entries()) { + if (declaration.test(line)) offenders.push(`${path}:${index + 1}`) + } + } + } + + expect(offenders, 'import DAY_MS from @/lib/text instead of re-spelling it').toEqual([]) + }) +})