feat(invariants): implement package runtime checks
This commit is contained in:
@@ -4,8 +4,6 @@ import { join } from 'node:path'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import {
|
||||
collectPackageInvariantViolations,
|
||||
packageInvariantOwners,
|
||||
renderBaselineInvariant,
|
||||
} from './package-invariants.ts'
|
||||
|
||||
const roots: string[] = []
|
||||
@@ -14,6 +12,18 @@ afterEach(() => {
|
||||
for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
function handwrittenInvariant(packageName: string): string {
|
||||
return `
|
||||
export const name = 'probe-invariant'
|
||||
export const inject = ['invariants']
|
||||
const install = (_ctx: unknown, fail: (message: string) => never) => {
|
||||
if (typeof ${JSON.stringify(packageName)} !== 'string') fail('package name must remain a string')
|
||||
}
|
||||
export const apply = (ctx: { invariants: { register(name: string, install: typeof install): () => void } }) =>
|
||||
Promise.resolve(ctx.invariants.register(${JSON.stringify(packageName)}, install))
|
||||
`
|
||||
}
|
||||
|
||||
function fixture(options: {
|
||||
packageName?: string
|
||||
source?: string
|
||||
@@ -47,8 +57,7 @@ function fixture(options: {
|
||||
writeFileSync(join(dir, 'tsconfig.json'), `${JSON.stringify({
|
||||
references: options.invariantReference === false ? [] : [{ path: '../../support/invariants' }],
|
||||
}, null, 2)}\n`)
|
||||
const owner = packageInvariantOwners(root)[0]!
|
||||
writeFileSync(join(dir, 'src/invariant.ts'), options.source ?? renderBaselineInvariant(owner))
|
||||
writeFileSync(join(dir, 'src/invariant.ts'), options.source ?? handwrittenInvariant(packageName))
|
||||
writeFileSync(
|
||||
join(dir, 'tsdown.config.ts'),
|
||||
options.buildEntry === false ? "export default { entry: ['lib/types/index.js'] }\n" : "export default { entry: ['lib/types/index.js', 'lib/types/invariant.js'] }\n",
|
||||
@@ -56,8 +65,43 @@ function fixture(options: {
|
||||
return root
|
||||
}
|
||||
|
||||
function addConformingPackage(root: string, slug: string, packageName: string, source: string): void {
|
||||
const dir = join(root, `packages/core/${slug}`)
|
||||
mkdirSync(join(dir, 'src'), { recursive: true })
|
||||
writeFileSync(join(dir, 'package.json'), `${JSON.stringify({
|
||||
name: packageName,
|
||||
exports: {
|
||||
'./invariant': {
|
||||
types: './lib/types/invariant.d.ts',
|
||||
default: './lib/invariant.js',
|
||||
},
|
||||
},
|
||||
files: ['lib/invariant.js'],
|
||||
peerDependencies: { '@deepseek-ai/dsh-invariants': '^0.0.1' },
|
||||
devDependencies: { '@deepseek-ai/dsh-invariants': 'workspace:^' },
|
||||
}, null, 2)}\n`)
|
||||
writeFileSync(join(dir, 'tsconfig.json'), `${JSON.stringify({
|
||||
references: [{ path: '../../support/invariants' }],
|
||||
}, null, 2)}\n`)
|
||||
writeFileSync(join(dir, 'src/invariant.ts'), source)
|
||||
writeFileSync(join(dir, 'tsdown.config.ts'), "export default { entry: ['lib/types/invariant.js'] }\n")
|
||||
}
|
||||
|
||||
function nameObservedInvariant(packageName: string, pluginName: string): string {
|
||||
return `
|
||||
import { observePluginInvariant } from '@deepseek-ai/dsh-invariants'
|
||||
export const name = 'probe-invariant'
|
||||
export const inject = ['invariants']
|
||||
const install = (ctx: never, fail: (message: string) => never) => {
|
||||
observePluginInvariant(ctx, fail, { name: ${JSON.stringify(pluginName)} })
|
||||
}
|
||||
export const apply = (ctx: { invariants: { register(name: string, install: typeof install): () => void } }) =>
|
||||
Promise.resolve(ctx.invariants.register(${JSON.stringify(packageName)}, install))
|
||||
`
|
||||
}
|
||||
|
||||
describe('package invariant gate', () => {
|
||||
it('accepts a generated owner companion with publication metadata', () => {
|
||||
it('accepts a hand-owned checking companion with publication metadata', () => {
|
||||
expect(collectPackageInvariantViolations(fixture())).toEqual([])
|
||||
})
|
||||
|
||||
@@ -82,9 +126,10 @@ describe('package invariant gate', () => {
|
||||
export const name = 'probe-invariant'
|
||||
export const inject = ['invariants']
|
||||
const selected = process.env.PACKAGE_NAME
|
||||
export const apply = (ctx: { invariants: { register(name: string, install: () => void): () => void } }) => {
|
||||
ctx.invariants.register('@deepseek-ai/dsh-foreign', () => {})
|
||||
return ctx.invariants.register(selected!, () => {})
|
||||
const install = (_ctx: unknown, fail: (message: string) => never) => { fail('probe') }
|
||||
export const apply = (ctx: { invariants: { register(name: string, install: typeof install): () => void } }) => {
|
||||
ctx.invariants.register('@deepseek-ai/dsh-foreign', install)
|
||||
return ctx.invariants.register(selected!, install)
|
||||
}
|
||||
`
|
||||
const violations = collectPackageInvariantViolations(fixture({ source }))
|
||||
@@ -94,11 +139,52 @@ export const apply = (ctx: { invariants: { register(name: string, install: () =>
|
||||
]))
|
||||
})
|
||||
|
||||
it('rejects edits to a generated baseline', () => {
|
||||
const root = fixture()
|
||||
const path = join(root, 'packages/core/probe/src/invariant.ts')
|
||||
writeFileSync(path, `${renderBaselineInvariant(packageInvariantOwners(root)[0]!)}// stale\n`)
|
||||
it('rejects generated markers and empty or reporter-free installers', () => {
|
||||
const generated = fixture({
|
||||
source: `/** @generated scripts/gen-package-invariants.ts */\n${handwrittenInvariant('@deepseek-ai/dsh-probe')}`,
|
||||
})
|
||||
expect(collectPackageInvariantViolations(generated).map(violation => violation.message))
|
||||
.toContain('invariant companions must be hand-owned and may not carry @generated markers')
|
||||
|
||||
const empty = fixture({
|
||||
source: `
|
||||
export const name = 'probe-invariant'
|
||||
export const inject = ['invariants']
|
||||
const install = () => {}
|
||||
export const apply = (ctx: { invariants: { register(name: string, install: typeof install): () => void } }) =>
|
||||
Promise.resolve(ctx.invariants.register('@deepseek-ai/dsh-probe', install))
|
||||
`,
|
||||
})
|
||||
expect(collectPackageInvariantViolations(empty).map(violation => violation.message))
|
||||
.toEqual(expect.arrayContaining([
|
||||
'install function must contain a package-owned invariant check',
|
||||
'install function must accept the bound failure reporter as its second parameter',
|
||||
]))
|
||||
|
||||
const unused = fixture({
|
||||
source: `
|
||||
export const name = 'probe-invariant'
|
||||
export const inject = ['invariants']
|
||||
const install = (_ctx: unknown, _fail: (message: string) => never) => { void 0 }
|
||||
export const apply = (ctx: { invariants: { register(name: string, install: typeof install): () => void } }) =>
|
||||
Promise.resolve(ctx.invariants.register('@deepseek-ai/dsh-probe', install))
|
||||
`,
|
||||
})
|
||||
expect(collectPackageInvariantViolations(unused).map(violation => violation.message))
|
||||
.toContain('install function must use its bound failure reporter')
|
||||
})
|
||||
|
||||
it('rejects duplicate name-based plugin observers across packages', () => {
|
||||
const root = fixture({
|
||||
source: nameObservedInvariant('@deepseek-ai/dsh-probe', 'shared-runtime-name'),
|
||||
})
|
||||
addConformingPackage(
|
||||
root,
|
||||
'probe-two',
|
||||
'@deepseek-ai/dsh-probe-two',
|
||||
nameObservedInvariant('@deepseek-ai/dsh-probe-two', 'shared-runtime-name'),
|
||||
)
|
||||
expect(collectPackageInvariantViolations(root).map(violation => violation.message))
|
||||
.toContain('generated baseline is stale; run pnpm run gen-package-invariants')
|
||||
.toContain('name-based plugin invariant "shared-runtime-name" is already owned by "@deepseek-ai/dsh-probe-two"')
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user