fix(typert): prepare remote contracts for source gates

This commit is contained in:
Tianyi Cui
2026-08-08 02:25:26 +08:00
parent 7ab20890e6
commit f91fd2269d
13 changed files with 122 additions and 37 deletions

View File

@@ -60,6 +60,7 @@ describe('gate graph validation', () => {
'ci-linux-primary',
'ci-static',
'ci-lint',
'ci-lint-contracts-ready',
'ci-coverage',
'ci-snapshot',
'ci-artifacts',
@@ -141,6 +142,57 @@ describe('Oxlint gate', () => {
})
})
describe('TypeRT contract preparation', () => {
it('prepares primary source consumers once before they run', () => {
const subject = withPnpmEntrypoint(() => gatesForMode('ci-primary'))
expect(subject.find(item => item.id === 'typert-contracts')).toMatchObject({
displayCommand: 'pnpm run build:lib:host',
args: ['/private/pnpm.cjs', 'run', 'build:lib:host'],
})
for (const [id, script] of [
['typecheck', 'typecheck:contracts-ready'],
['lint', 'lint:contracts-ready'],
['doc-typecheck', 'doc-typecheck:contracts-ready'],
] as const) {
expect(subject.find(item => item.id === id)).toMatchObject({
displayCommand: `pnpm run ${script}`,
args: ['/private/pnpm.cjs', 'run', script],
needs: ['typert-contracts'],
})
}
expect(subject.find(item => item.id === 'build')?.needs).toEqual([
'typecheck',
'lint',
'doc-typecheck',
])
})
it('reuses contracts from the validated consumer build', () => {
const subject = withPnpmEntrypoint(() => gatesForMode('ci-consumers'))
expect(subject.find(item => item.id === 'lint-and-duplication')).toMatchObject({
displayCommand: 'pnpm run check:ci:lint:contracts-ready',
args: ['/private/pnpm.cjs', 'run', 'check:ci:lint:contracts-ready'],
})
expect(subject.find(item => item.id === 'doc-typecheck')).toMatchObject({
displayCommand: 'pnpm run doc-typecheck:contracts-ready',
args: ['/private/pnpm.cjs', 'run', 'doc-typecheck:contracts-ready'],
})
})
it('keeps standalone aggregates responsible for preparation', () => {
const lint = withPnpmEntrypoint(() => gatesForMode('ci-lint')[0])
const preparedLint = withPnpmEntrypoint(() => gatesForMode('ci-lint-contracts-ready')[0])
const docTypecheck = withPnpmEntrypoint(() =>
gatesForMode('doc-sync').find(item => item.id === 'doc-typecheck'))
expect(lint?.displayCommand).toBe('pnpm run lint')
expect(preparedLint?.displayCommand).toBe('pnpm run lint:contracts-ready')
expect(docTypecheck?.displayCommand).toBe('pnpm run doc-typecheck')
})
})
describe('Node compatibility graph', () => {
it('runs the jsdom environment smoke on every advertised Node line', () => {
const subject = withPnpmEntrypoint(() => gatesForMode('node-compat'))

View File

@@ -17,6 +17,7 @@ export type Mode =
| 'ci-linux-primary'
| 'ci-static'
| 'ci-lint'
| 'ci-lint-contracts-ready'
| 'ci-coverage'
| 'ci-snapshot'
| 'ci-artifacts'
@@ -102,6 +103,7 @@ function parseMode(raw: string | undefined): Mode {
case 'ci-linux-primary':
case 'ci-static':
case 'ci-lint':
case 'ci-lint-contracts-ready':
case 'ci-coverage':
case 'ci-snapshot':
case 'ci-artifacts':
@@ -115,7 +117,7 @@ function parseMode(raw: string | undefined): Mode {
return raw
default:
throw new Error(
`run-gates: expected mode ci-primary | ci-linux-primary | ci-static | ci-lint | ci-coverage | ci-snapshot | ci-artifacts | ci-consumers | ci-windows-blocking | ci-windows-complete | ci-windows-observational | node-compat | check-all | doc-sync, got ${JSON.stringify(raw)}.`,
`run-gates: expected mode ci-primary | ci-linux-primary | ci-static | ci-lint | ci-lint-contracts-ready | ci-coverage | ci-snapshot | ci-artifacts | ci-consumers | ci-windows-blocking | ci-windows-complete | ci-windows-observational | node-compat | check-all | doc-sync, got ${JSON.stringify(raw)}.`,
)
}
}
@@ -202,6 +204,11 @@ export function gatesForMode(selected: Mode): Gate[] {
lintGate(),
pnpmScript('duplication', 'duplication'),
]
case 'ci-lint-contracts-ready':
return [
lintGate({ contractsReady: true }),
pnpmScript('duplication', 'duplication'),
]
case 'ci-coverage':
return coverageGates()
case 'ci-snapshot':
@@ -233,6 +240,7 @@ export function gatesForMode(selected: Mode): Gate[] {
...docSyncLeafGates({
docTypecheckNeeds: ['build'],
docTypecheckEnv: { DSH_DOC_TYPECHECK_USE_BUILD_OUTPUT: '1' },
docTypecheckScript: 'doc-typecheck:contracts-ready',
}),
pnpmScript('module-graph', 'verify-module-graph', { label: 'module graph' }),
]
@@ -254,19 +262,23 @@ function ciSharedStaticGates(): Gate[] {
function ciPrimaryGates(): Gate[] {
return [
...ciSharedStaticGates(),
pnpmScript('typecheck', 'typecheck'),
lintGate(),
typertContractsGate(),
pnpmScript('typecheck', 'typecheck:contracts-ready', { needs: ['typert-contracts'] }),
lintGate({ contractsReady: true, needs: ['typert-contracts'] }),
pnpmScript('duplication', 'duplication'),
...coverageGates(),
...nodeCompatSmokeGates(),
snapshotGate(),
...docSyncLeafGates(),
...docSyncLeafGates({
docTypecheckNeeds: ['typert-contracts'],
docTypecheckScript: 'doc-typecheck:contracts-ready',
}),
pnpmScript('module-graph', 'verify-module-graph', { label: 'module graph' }),
pnpmScript('knip', 'knip'),
// typecheck and build both drive the Host and Client tsc graphs; without
// the dependency concurrent runs race the same tsbuildinfo files.
// The tsc step is an incremental no-op after typecheck.
pnpmScript('build', 'build', { needs: ['typecheck'] }),
// The prepared typecheck and build both drive Client tsc, while build also
// repeats the Host contract pass. Wait for all three consumers so build
// neither races tsbuildinfo nor replaces declarations while they are read.
pnpmScript('build', 'build', { needs: ['typecheck', 'lint', 'doc-typecheck'] }),
pnpmScript('publint', 'publint', { needs: ['build'] }),
pnpmScript('node-next-types', 'verify-node-next-types', {
label: 'node-next types',
@@ -355,6 +367,7 @@ function ciStaticGates(options: { ownsBuild: boolean }): Gate[] {
? {
docTypecheckNeeds: ['build'],
docTypecheckEnv: { DSH_DOC_TYPECHECK_USE_BUILD_OUTPUT: '1' },
docTypecheckScript: 'doc-typecheck:contracts-ready',
}
: {},
docsBuildScript: 'docs:build:mpa',
@@ -385,13 +398,13 @@ function ciConsumerGates(): Gate[] {
pnpmScript('node-compat', 'check:node-compat', { label: 'Node compatibility' }),
pnpmScript('publint', 'publint', { needs: builtTree }),
builtPackageInvariantsGate(['publint']),
pnpmScript('lint-and-duplication', 'check:ci:lint', {
pnpmScript('lint-and-duplication', 'check:ci:lint:contracts-ready', {
label: 'lint and duplication',
needs: validatedBuild,
}),
snapshotGate(validatedBuild),
webSnapshotGate(validatedBuild),
pnpmScript('doc-typecheck', 'doc-typecheck', {
pnpmScript('doc-typecheck', 'doc-typecheck:contracts-ready', {
needs: validatedBuild,
env: { DSH_DOC_TYPECHECK_USE_BUILD_OUTPUT: '1' },
}),
@@ -447,11 +460,19 @@ function ciWindowsObservationalGates(): Gate[] {
]
}
function lintGate(): Gate {
function typertContractsGate(): Gate {
return pnpmScript('typert-contracts', 'build:lib:host', { label: 'TypeRT contracts' })
}
function lintGate(options: { contractsReady?: boolean; needs?: string[] } = {}): Gate {
const raw = process.env.DSH_OXLINT_THREADS
return pnpmScript('lint', 'lint', raw === undefined || raw === ''
? {}
: { displayCommand: `DSH_OXLINT_THREADS=${raw} pnpm run lint` })
const script = options.contractsReady === true ? 'lint:contracts-ready' : 'lint'
return pnpmScript('lint', script, {
...raw === undefined || raw === ''
? {}
: { displayCommand: `DSH_OXLINT_THREADS=${raw} pnpm run ${script}` },
...options.needs === undefined ? {} : { needs: options.needs },
})
}
// The heavy suites run uninstrumented beside the thresholded gate: their
@@ -554,6 +575,7 @@ function docSyncLeafGates(options: {
includeDocTypecheck?: boolean
docTypecheckNeeds?: string[]
docTypecheckEnv?: Record<string, string | undefined>
docTypecheckScript?: 'doc-typecheck' | 'doc-typecheck:contracts-ready'
docsBuildScript?: 'docs:build' | 'docs:build:mpa'
} = {}): Gate[] {
const docTypecheckOptions: Partial<Gate> = {}
@@ -562,7 +584,7 @@ function docSyncLeafGates(options: {
return [
...options.includeDocTypecheck === false
? []
: [pnpmScript('doc-typecheck', 'doc-typecheck', docTypecheckOptions)],
: [pnpmScript('doc-typecheck', options.docTypecheckScript ?? 'doc-typecheck', docTypecheckOptions)],
pnpmScript('cordis-catalog', 'verify-cordis-catalog', { label: 'cordis catalog' }),
pnpmScript('export-jsdoc', 'verify-export-jsdoc', { label: 'export jsdoc' }),
pnpmScript('tool-catalog', 'verify-tool-catalog', { label: 'tool catalog' }),