import assert from 'node:assert/strict'; import { readFile } from 'node:fs/promises'; export const DEFAULT_VERSION_TAG = 'latest'; export const NO_PACKAGE_REASON = 'Set KTX_PUBLISHED_KTX_PACKAGE or release-policy.json publishedPackageSmoke.packageName to the published npm package name after the release decision.'; function optionalTrimmedString(value) { return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null; } function assertSafePackageName(packageName, label) { if (!/^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/.test(packageName)) { throw new Error(`Invalid ${label}: ${packageName}`); } } function assertSafeVersionTag(version, label) { if (!/^[a-zA-Z0-9][a-zA-Z0-9._+-]*$/.test(version)) { throw new Error(`Invalid ${label}: ${version}`); } } function assertHttpRegistry(registry, label) { const parsed = new URL(registry); if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') { throw new Error(`${label} must be an http(s) URL`); } } function normalizePolicyConfig(policyConfig = {}) { if (policyConfig === null || policyConfig === undefined) { return { packageName: null, version: DEFAULT_VERSION_TAG, registry: null }; } if (typeof policyConfig !== 'object' || Array.isArray(policyConfig)) { throw new Error('release-policy.json publishedPackageSmoke must be a JSON object'); } const normalized = { packageName: optionalTrimmedString(policyConfig.packageName), version: optionalTrimmedString(policyConfig.version) ?? DEFAULT_VERSION_TAG, registry: optionalTrimmedString(policyConfig.registry), }; assertSafeVersionTag(normalized.version, 'release-policy.json publishedPackageSmoke.version'); if (normalized.registry) { assertHttpRegistry(normalized.registry, 'release-policy.json publishedPackageSmoke.registry'); } return normalized; } export function readPublishedPackageSmokeConfig(env = process.env, args = process.argv.slice(2), policyConfig = {}) { const requireConfig = args.includes('--require-config'); const policy = normalizePolicyConfig(policyConfig); const envPackageName = optionalTrimmedString(env.KTX_PUBLISHED_KTX_PACKAGE); const packageName = envPackageName ?? policy.packageName; if (!packageName) { return { enabled: false, requireConfig, reason: NO_PACKAGE_REASON, }; } const configSource = envPackageName ? 'environment' : 'release-policy'; assertSafePackageName( packageName, configSource === 'environment' ? 'KTX_PUBLISHED_KTX_PACKAGE' : 'release-policy.json publishedPackageSmoke.packageName', ); const packageVersion = optionalTrimmedString(env.KTX_PUBLISHED_KTX_VERSION) ?? policy.version; assertSafeVersionTag( packageVersion, optionalTrimmedString(env.KTX_PUBLISHED_KTX_VERSION) ? 'KTX_PUBLISHED_KTX_VERSION' : 'release-policy.json publishedPackageSmoke.version', ); const registry = optionalTrimmedString(env.KTX_PUBLISHED_KTX_REGISTRY) ?? policy.registry; if (registry) { assertHttpRegistry( registry, optionalTrimmedString(env.KTX_PUBLISHED_KTX_REGISTRY) ? 'KTX_PUBLISHED_KTX_REGISTRY' : 'release-policy.json publishedPackageSmoke.registry', ); } return { enabled: true, requireConfig, configSource, packageName, packageVersion, registry, }; } export async function readPublishedPackageSmokeConfigFromPolicyFile( policyPath, env = process.env, args = process.argv.slice(2), ) { const policy = JSON.parse(await readFile(policyPath, 'utf8')); return readPublishedPackageSmokeConfig(env, args, policy.publishedPackageSmoke ?? {}); } export function publishedPackageSpec(config) { assert.equal(config.enabled, true, 'publishedPackageSpec requires an enabled smoke config'); return `${config.packageName}@${config.packageVersion}`; } export function buildPublishedPackageNpxCommand(config, args, label = 'published package command') { const env = config.registry ? { npm_config_registry: config.registry } : {}; return { label, command: 'npx', args: ['--yes', publishedPackageSpec(config), ...args], env, }; } export function buildPublishedPackageSmokeCommands(config, projectDir, emptyProjectDir) { return [ buildPublishedPackageNpxCommand(config, ['--version'], 'published package version'), buildPublishedPackageNpxCommand( config, ['demo', '--project-dir', projectDir, '--no-input', '--plain'], 'published package demo', ), buildPublishedPackageNpxCommand( config, ['agent', 'wiki', 'search', 'ARR contract', '--json', '--limit', '5', '--project-dir', projectDir], 'published package wiki hybrid search', ), buildPublishedPackageNpxCommand( config, ['agent', 'sl', 'list', '--json', '--query', 'ARR', '--project-dir', projectDir], 'published package semantic-layer hybrid search', ), buildPublishedPackageNpxCommand( config, ['agent', 'sl', 'list', '--json', '--query', 'revenue', '--project-dir', emptyProjectDir], 'published package missing-project readiness', ), ]; }