Enable canonical versioned Go module consumption
Why: Allow consumers to download an immutable module revision without nested source checkouts or local path replacements. Evidence: SELF package tests and the prior canonical-path rejection are recorded in the Dev dependency-workspace task card; remote consumption is replayed against the committed candidate. Impact: Change the module identity and its Go imports while preserving API behavior. Existing historical submodule revisions remain buildable. Refs: Versioned dependencies U1/U3; feature/dependency-workspace
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
// Explicit generation only: authenticated Git acquisition must never run package scripts.
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import { mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const root = dirname(dirname(fileURLToPath(import.meta.url)))
|
||||
const check = process.argv.includes('--check')
|
||||
const output = check ? mkdtempSync(join(tmpdir(), 'agent-contracts-build-')) : join(root, 'package')
|
||||
try {
|
||||
mkdirSync(output, { recursive: true })
|
||||
execFileSync(process.execPath, [join(root, 'node_modules/typescript/bin/tsc'), '-p', join(root, 'tsconfig.package.json'), '--outDir', output], { cwd: root, stdio: 'inherit' })
|
||||
for (const extension of ['js', 'd.ts']) renameSync(join(output, `types.${extension}`), join(output, `index.${extension}`))
|
||||
if (check) {
|
||||
for (const file of ['index.js', 'index.d.ts']) {
|
||||
if (!readFileSync(join(root, 'package', file)).equals(readFileSync(join(output, file)))) {
|
||||
throw new Error(`package/${file} is stale; run npm run build before committing the provider`)
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (check) rmSync(output, { recursive: true, force: true })
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { execFileSync, spawnSync } from 'node:child_process'
|
||||
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { test } from 'node:test'
|
||||
|
||||
const root = dirname(dirname(fileURLToPath(import.meta.url)))
|
||||
|
||||
test('packed contracts compile in a consumer without the Core checkout', () => {
|
||||
const consumer = mkdtempSync(join(tmpdir(), 'agent-contracts-consumer-'))
|
||||
try {
|
||||
const pack = spawnSync('npm', ['pack', '--json', '--ignore-scripts', '--pack-destination', consumer], {
|
||||
cwd: root, encoding: 'utf8',
|
||||
})
|
||||
assert.equal(pack.status, 0, `Core must expose an independently packable contracts package:\n${pack.stderr}`)
|
||||
const [artifact] = JSON.parse(pack.stdout)
|
||||
assert.equal(artifact.name, '@agent-system/contracts')
|
||||
assert.ok(artifact.files.some(file => file.path === 'package/index.d.ts'), 'declarations must be packed')
|
||||
assert.ok(artifact.files.some(file => file.path === 'package/index.js'), 'ESM entry must be packed')
|
||||
assert.ok(!artifact.files.some(file => file.path.endsWith('.go')), 'Go implementation is outside the npm package')
|
||||
|
||||
writeFileSync(join(consumer, 'package.json'), JSON.stringify({ private: true, type: 'module' }))
|
||||
writeFileSync(join(consumer, 'consumer.ts'), `
|
||||
import type { ActivityCard, Message, SurfaceSnapshot } from '@agent-system/contracts'
|
||||
const card: ActivityCard = { id: 'card', projectId: 'project', conversationId: 'conversation', kind: 'progress', status: 'active', title: 'Working', createdAt: '2026-10-13T00:00:00Z', updatedAt: '2026-10-13T00:00:00Z' }
|
||||
const message: Message = { id: 'message', conversationId: 'conversation', role: 'assistant', parts: [{ type: 'text', text: card.title }], createdAt: card.createdAt }
|
||||
type Surface = SurfaceSnapshot
|
||||
// @ts-expect-error unknown message roles are not part of the wire contract
|
||||
const invalidRole: Message['role'] = 'operator'
|
||||
export { message, invalidRole }; export type { Surface }
|
||||
`)
|
||||
const tsc = [join(root, 'node_modules/typescript/bin/tsc'), '--noEmit', '--strict', '--skipLibCheck', '--module', 'NodeNext', '--moduleResolution', 'NodeNext', 'consumer.ts']
|
||||
const missing = spawnSync(process.execPath, tsc, { cwd: consumer, encoding: 'utf8' })
|
||||
assert.notEqual(missing.status, 0, 'the consumer must not resolve Core types before package installation')
|
||||
assert.match(missing.stdout, /TS2307.*@agent-system\/contracts/)
|
||||
execFileSync('npm', ['install', '--ignore-scripts', '--no-audit', '--no-fund', '--offline', '--package-lock=false', join(consumer, artifact.filename)], { cwd: consumer, stdio: 'pipe' })
|
||||
execFileSync(process.execPath, tsc, { cwd: consumer, stdio: 'inherit' })
|
||||
execFileSync(process.execPath, ['--input-type=module', '-e', "import * as contracts from '@agent-system/contracts'; if (Object.keys(contracts).length) throw Error('contracts must remain type-only')"], { cwd: consumer, stdio: 'pipe' })
|
||||
const declaration = readFileSync(join(consumer, 'node_modules/@agent-system/contracts/package/index.d.ts'), 'utf8')
|
||||
assert.match(declaration, /export interface SurfaceSnapshot/)
|
||||
} finally {
|
||||
rmSync(consumer, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user