-
Notifications
You must be signed in to change notification settings - Fork 0
test: add bin-entries regression guard #40
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,49 @@ | ||||||||||||||||||||||||||
| import { existsSync, readFileSync } from 'node:fs'; | ||||||||||||||||||||||||||
| import { join } from 'node:path'; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const repoRoot = process.cwd(); | ||||||||||||||||||||||||||
| const pkg = JSON.parse(readFileSync(join(repoRoot, 'package.json'), 'utf-8')) as { | ||||||||||||||||||||||||||
| name: string; | ||||||||||||||||||||||||||
| bin?: string | Record<string, string>; | ||||||||||||||||||||||||||
| files?: string[]; | ||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| function normalizeBin(bin: typeof pkg.bin, name: string): Array<[string, string]> { | ||||||||||||||||||||||||||
| if (typeof bin === 'string') return [[name, bin]]; | ||||||||||||||||||||||||||
| if (bin && typeof bin === 'object') return Object.entries(bin); | ||||||||||||||||||||||||||
| return []; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| function pkgFilesCoversPath(files: string[], relPath: string): boolean { | ||||||||||||||||||||||||||
| const clean = relPath.replace(/^\.\//, ''); | ||||||||||||||||||||||||||
| const top = clean.split('/')[0]; | ||||||||||||||||||||||||||
| return files.some((f) => { | ||||||||||||||||||||||||||
| const c = f.replace(/\/$/, '').replace(/^\.\//, ''); | ||||||||||||||||||||||||||
| return c === top; | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| describe('package.json bin entries', () => { | ||||||||||||||||||||||||||
| const entries = normalizeBin(pkg.bin, pkg.name); | ||||||||||||||||||||||||||
| const files: string[] = pkg.files ?? []; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| it('declares at least one bin entry', () => { | ||||||||||||||||||||||||||
| expect(entries.length).toBeGreaterThan(0); | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| it.each(entries)('bin "%s" -> "%s" exists on disk', (_name, rel) => { | ||||||||||||||||||||||||||
| const abs = join(repoRoot, rel.replace(/^\.\//, '')); | ||||||||||||||||||||||||||
| expect(existsSync(abs)).toBe(true); | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
|
Comment on lines
+34
to
+37
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Build-order dependency is currently breaking CI. This assertion depends on prebuilt 🧰 Tools🪛 GitHub Actions: CI[error] 34-36: Jest failed: package.json bin entries expects "forge-ai-init" -> "./dist/index.js" to exist on disk. expect(existsSync(abs)).toBe(true) received false. 🪛 GitHub Actions: SonarCloud Analysis[error] 34-36: Jest test failed: package.json bin entries expected the target file to exist on disk. Failed assertion 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| it.each(entries)('bin "%s" -> "%s" starts with a node shebang', (_name, rel) => { | ||||||||||||||||||||||||||
| const abs = join(repoRoot, rel.replace(/^\.\//, '')); | ||||||||||||||||||||||||||
| if (!existsSync(abs)) return; | ||||||||||||||||||||||||||
| const firstLine = readFileSync(abs, 'utf-8').split('\n', 1)[0] ?? ''; | ||||||||||||||||||||||||||
| expect(firstLine).toMatch(/^#!.*\bnode\b/); | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
|
Comment on lines
+39
to
+44
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shebang test can pass even when the bin target is missing. Early-return on missing file masks the failure when this test is run in isolation. Assert file existence inside this test too, then check the shebang. Suggested fix it.each(entries)('bin "%s" -> "%s" starts with a node shebang', (_name, rel) => {
const abs = join(repoRoot, rel.replace(/^\.\//, ''));
- if (!existsSync(abs)) return;
+ expect(existsSync(abs)).toBe(true);
const firstLine = readFileSync(abs, 'utf-8').split('\n', 1)[0] ?? '';
expect(firstLine).toMatch(/^#!.*\bnode\b/);
});📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| it.each(entries)('bin "%s" -> "%s" lives under a directory in pkg.files', (_name, rel) => { | ||||||||||||||||||||||||||
| expect(pkgFilesCoversPath(files, rel)).toBe(true); | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pkg.filescoverage check is too narrow and can reject valid package configs.Matching only the top-level directory misses valid
filesentries like explicit file paths (e.g.,dist/index.js) and common pattern forms. That can create false failures when this guard is rolled out across packages.🤖 Prompt for AI Agents