feat: add --spec-version option to tier-check (#176)
* feat: add --spec-version option to tier-check * refactor: share resolveSpecVersion between CLI and tier-check Move resolveSpecVersion to src/scenarios/index.ts so tier-check can reuse it instead of duplicating the validation logic. --------- Co-authored-by: Felix Weinberger <fweinberger@anthropic.com>
This commit is contained in:
parent
fe16e381c7
commit
1c48a30ff6
4 changed files with 72 additions and 39 deletions
11
src/index.ts
11
src/index.ts
|
|
@ -24,7 +24,7 @@ import {
|
||||||
listScenariosForSpec,
|
listScenariosForSpec,
|
||||||
listClientScenariosForSpec,
|
listClientScenariosForSpec,
|
||||||
getScenarioSpecVersions,
|
getScenarioSpecVersions,
|
||||||
ALL_SPEC_VERSIONS
|
resolveSpecVersion
|
||||||
} from './scenarios';
|
} from './scenarios';
|
||||||
import type { SpecVersion } from './scenarios';
|
import type { SpecVersion } from './scenarios';
|
||||||
import { ConformanceCheck } from './types';
|
import { ConformanceCheck } from './types';
|
||||||
|
|
@ -37,15 +37,6 @@ import {
|
||||||
import { createTierCheckCommand } from './tier-check';
|
import { createTierCheckCommand } from './tier-check';
|
||||||
import packageJson from '../package.json';
|
import packageJson from '../package.json';
|
||||||
|
|
||||||
function resolveSpecVersion(value: string): SpecVersion {
|
|
||||||
if (ALL_SPEC_VERSIONS.includes(value as SpecVersion)) {
|
|
||||||
return value as SpecVersion;
|
|
||||||
}
|
|
||||||
console.error(`Unknown spec version: ${value}`);
|
|
||||||
console.error(`Valid versions: ${ALL_SPEC_VERSIONS.join(', ')}`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note on naming: `command` refers to which CLI command is calling this.
|
// Note on naming: `command` refers to which CLI command is calling this.
|
||||||
// The `client` command tests Scenario objects (which test clients),
|
// The `client` command tests Scenario objects (which test clients),
|
||||||
// and the `server` command tests ClientScenario objects (which test servers).
|
// and the `server` command tests ClientScenario objects (which test servers).
|
||||||
|
|
|
||||||
|
|
@ -227,6 +227,15 @@ export const ALL_SPEC_VERSIONS: SpecVersion[] = [
|
||||||
'extension'
|
'extension'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export function resolveSpecVersion(value: string): SpecVersion {
|
||||||
|
if (ALL_SPEC_VERSIONS.includes(value as SpecVersion)) {
|
||||||
|
return value as SpecVersion;
|
||||||
|
}
|
||||||
|
console.error(`Unknown spec version: ${value}`);
|
||||||
|
console.error(`Valid versions: ${ALL_SPEC_VERSIONS.join(', ')}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
export function listScenariosForSpec(version: SpecVersion): string[] {
|
export function listScenariosForSpec(version: SpecVersion): string[] {
|
||||||
return scenariosList
|
return scenariosList
|
||||||
.filter((s) => s.specVersions.includes(version))
|
.filter((s) => s.specVersions.includes(version))
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ import { ConformanceResult } from '../types';
|
||||||
import {
|
import {
|
||||||
listScenarios,
|
listScenarios,
|
||||||
listActiveClientScenarios,
|
listActiveClientScenarios,
|
||||||
|
listScenariosForSpec,
|
||||||
|
listClientScenariosForSpec,
|
||||||
getScenarioSpecVersions
|
getScenarioSpecVersions
|
||||||
} from '../../scenarios';
|
} from '../../scenarios';
|
||||||
import { ConformanceCheck, SpecVersion } from '../../types';
|
import { ConformanceCheck, SpecVersion } from '../../types';
|
||||||
|
|
@ -166,6 +168,7 @@ function reconcileWithExpected(
|
||||||
export async function checkConformance(options: {
|
export async function checkConformance(options: {
|
||||||
serverUrl?: string;
|
serverUrl?: string;
|
||||||
skip?: boolean;
|
skip?: boolean;
|
||||||
|
specVersion?: SpecVersion;
|
||||||
}): Promise<ConformanceResult> {
|
}): Promise<ConformanceResult> {
|
||||||
if (options.skip || !options.serverUrl) {
|
if (options.skip || !options.serverUrl) {
|
||||||
return {
|
return {
|
||||||
|
|
@ -179,23 +182,37 @@ export async function checkConformance(options: {
|
||||||
}
|
}
|
||||||
|
|
||||||
const outputDir = mkdtempSync(join(tmpdir(), 'tier-check-server-'));
|
const outputDir = mkdtempSync(join(tmpdir(), 'tier-check-server-'));
|
||||||
|
const args = [
|
||||||
|
process.argv[1],
|
||||||
|
'server',
|
||||||
|
'--url',
|
||||||
|
options.serverUrl,
|
||||||
|
'-o',
|
||||||
|
outputDir
|
||||||
|
];
|
||||||
|
if (options.specVersion) {
|
||||||
|
args.push('--spec-version', options.specVersion);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
execFileSync(
|
execFileSync(process.execPath, args, {
|
||||||
process.execPath,
|
stdio: ['pipe', 'pipe', 'pipe'],
|
||||||
[process.argv[1], 'server', '--url', options.serverUrl, '-o', outputDir],
|
timeout: 120_000
|
||||||
{
|
});
|
||||||
stdio: ['pipe', 'pipe', 'pipe'],
|
|
||||||
timeout: 120_000
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} catch {
|
} catch {
|
||||||
// Non-zero exit is expected when tests fail — results are still in outputDir
|
// Non-zero exit is expected when tests fail — results are still in outputDir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const activeScenarios = new Set(listActiveClientScenarios());
|
||||||
|
const expectedScenarios = options.specVersion
|
||||||
|
? listClientScenariosForSpec(options.specVersion).filter((s) =>
|
||||||
|
activeScenarios.has(s)
|
||||||
|
)
|
||||||
|
: [...activeScenarios];
|
||||||
|
|
||||||
return reconcileWithExpected(
|
return reconcileWithExpected(
|
||||||
parseOutputDir(outputDir),
|
parseOutputDir(outputDir),
|
||||||
listActiveClientScenarios(),
|
expectedScenarios,
|
||||||
'server'
|
'server'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -206,6 +223,7 @@ export async function checkConformance(options: {
|
||||||
export async function checkClientConformance(options: {
|
export async function checkClientConformance(options: {
|
||||||
clientCmd?: string;
|
clientCmd?: string;
|
||||||
skip?: boolean;
|
skip?: boolean;
|
||||||
|
specVersion?: SpecVersion;
|
||||||
}): Promise<ConformanceResult> {
|
}): Promise<ConformanceResult> {
|
||||||
if (options.skip || !options.clientCmd) {
|
if (options.skip || !options.clientCmd) {
|
||||||
return {
|
return {
|
||||||
|
|
@ -219,28 +237,32 @@ export async function checkClientConformance(options: {
|
||||||
}
|
}
|
||||||
|
|
||||||
const outputDir = mkdtempSync(join(tmpdir(), 'tier-check-client-'));
|
const outputDir = mkdtempSync(join(tmpdir(), 'tier-check-client-'));
|
||||||
|
const args = [
|
||||||
|
process.argv[1],
|
||||||
|
'client',
|
||||||
|
'--command',
|
||||||
|
options.clientCmd,
|
||||||
|
'--suite',
|
||||||
|
'all',
|
||||||
|
'-o',
|
||||||
|
outputDir
|
||||||
|
];
|
||||||
|
if (options.specVersion) {
|
||||||
|
args.push('--spec-version', options.specVersion);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
execFileSync(
|
execFileSync(process.execPath, args, {
|
||||||
process.execPath,
|
stdio: ['pipe', 'pipe', 'pipe'],
|
||||||
[
|
timeout: 120_000
|
||||||
process.argv[1],
|
});
|
||||||
'client',
|
|
||||||
'--command',
|
|
||||||
options.clientCmd,
|
|
||||||
'--suite',
|
|
||||||
'all',
|
|
||||||
'-o',
|
|
||||||
outputDir
|
|
||||||
],
|
|
||||||
{
|
|
||||||
stdio: ['pipe', 'pipe', 'pipe'],
|
|
||||||
timeout: 120_000
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} catch {
|
} catch {
|
||||||
// Non-zero exit is expected when tests fail — results are still in outputDir
|
// Non-zero exit is expected when tests fail — results are still in outputDir
|
||||||
}
|
}
|
||||||
|
|
||||||
return reconcileWithExpected(parseOutputDir(outputDir), listScenarios());
|
const expectedScenarios = options.specVersion
|
||||||
|
? listScenariosForSpec(options.specVersion)
|
||||||
|
: listScenarios();
|
||||||
|
|
||||||
|
return reconcileWithExpected(parseOutputDir(outputDir), expectedScenarios);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import { checkSpecTracking } from './checks/spec-tracking';
|
||||||
import { computeTier } from './tier-logic';
|
import { computeTier } from './tier-logic';
|
||||||
import { formatJson, formatMarkdown, formatTerminal } from './output';
|
import { formatJson, formatMarkdown, formatTerminal } from './output';
|
||||||
import { TierScorecard } from './types';
|
import { TierScorecard } from './types';
|
||||||
|
import { resolveSpecVersion } from '../scenarios';
|
||||||
|
|
||||||
function parseRepo(repo: string): { owner: string; repo: string } {
|
function parseRepo(repo: string): { owner: string; repo: string } {
|
||||||
const parts = repo.split('/');
|
const parts = repo.split('/');
|
||||||
|
|
@ -48,10 +49,18 @@ export function createTierCheckCommand(): Command {
|
||||||
'--token <token>',
|
'--token <token>',
|
||||||
'GitHub token (defaults to GITHUB_TOKEN env var)'
|
'GitHub token (defaults to GITHUB_TOKEN env var)'
|
||||||
)
|
)
|
||||||
|
.option(
|
||||||
|
'--spec-version <version>',
|
||||||
|
'Only run conformance scenarios for this spec version'
|
||||||
|
)
|
||||||
.action(async (options) => {
|
.action(async (options) => {
|
||||||
const { owner, repo } = parseRepo(options.repo);
|
const { owner, repo } = parseRepo(options.repo);
|
||||||
let token = options.token || process.env.GITHUB_TOKEN;
|
let token = options.token || process.env.GITHUB_TOKEN;
|
||||||
|
|
||||||
|
const specVersion = options.specVersion
|
||||||
|
? resolveSpecVersion(options.specVersion)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
// Try to get token from GitHub CLI
|
// Try to get token from GitHub CLI
|
||||||
try {
|
try {
|
||||||
|
|
@ -90,14 +99,16 @@ export function createTierCheckCommand(): Command {
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
checkConformance({
|
checkConformance({
|
||||||
serverUrl: options.conformanceServerUrl,
|
serverUrl: options.conformanceServerUrl,
|
||||||
skip: options.skipConformance
|
skip: options.skipConformance,
|
||||||
|
specVersion
|
||||||
}).then((r) => {
|
}).then((r) => {
|
||||||
console.error(' ✓ Server Conformance');
|
console.error(' ✓ Server Conformance');
|
||||||
return r;
|
return r;
|
||||||
}),
|
}),
|
||||||
checkClientConformance({
|
checkClientConformance({
|
||||||
clientCmd: options.clientCmd,
|
clientCmd: options.clientCmd,
|
||||||
skip: options.skipConformance || !options.clientCmd
|
skip: options.skipConformance || !options.clientCmd,
|
||||||
|
specVersion
|
||||||
}).then((r) => {
|
}).then((r) => {
|
||||||
console.error(' ✓ Client Conformance');
|
console.error(' ✓ Client Conformance');
|
||||||
return r;
|
return r;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue