fix(cli): preserve numeric-looking version strings

Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
This commit is contained in:
XiaoSeS 2026-09-10 16:04:55 +08:00
parent 96c9662be1
commit fa7410a56c
2 changed files with 60 additions and 5 deletions

View file

@ -27,6 +27,26 @@ function toArray(val: string | string[] | undefined): string[] | undefined {
return Array.isArray(val) ? val : [val]
}
/** Read a string option before cac/mri coerces numeric-looking values to numbers. */
function rawStringOption(argv: string[], name: string): string | undefined {
const optionWithEquals = `${name}=`
const end = argv.indexOf('--')
const args = end === -1 ? argv : argv.slice(0, end)
let value: string | undefined
for (let index = 0; index < args.length; index += 1) {
const argument = args[index]!
if (argument === name) {
value = args[index + 1]
index += 1
} else if (argument.startsWith(optionWithEquals)) {
value = argument.slice(optionWithEquals.length)
}
}
return value
}
async function runCommand(action: () => Promise<string>, json = false): Promise<void> {
try {
const output = await action()
@ -246,7 +266,11 @@ cli
.option('--token <token>', 'API token')
.option('--json', 'Output JSON')
.action((slug: string, options: InstallCommandOptions & { agent?: string | string[] }) => {
return runCommand(() => installCommand(slug, { ...options, agent: toArray(options.agent) }), Boolean(options.json))
return runCommand(() => installCommand(slug, {
...options,
version: rawStringOption(process.argv.slice(2), '--version'),
agent: toArray(options.agent)
}), Boolean(options.json))
})
cli
@ -262,7 +286,11 @@ cli
.option('--json', 'Output JSON')
.action((action: string, coordinate: string, options: SuiteCommandOptions & { agent?: string | string[] }) => {
return runCommand(
() => suiteCommand(action, coordinate, { ...options, agent: toArray(options.agent) }),
() => suiteCommand(action, coordinate, {
...options,
version: rawStringOption(process.argv.slice(2), '--version'),
agent: toArray(options.agent)
}),
Boolean(options.json)
)
})

View file

@ -43,9 +43,36 @@ describe('--version parsing', () => {
expect(registry.received.resolve?.version).toBe(TIMESTAMP_VERSION)
})
test('install preserves trailing zeros with the --version=value form', async () => {
const env = await createTempHome()
const registry = await startFakeRegistry({
token: 'sk_ok',
skills: [{
namespace: 'global',
slug: 'timestamped-equals',
version: TIMESTAMP_VERSION,
zipBytes: zipSync({ 'SKILL.md': strToU8('# timestamped equals') })
}]
})
stopServer = registry.stop
const installDir = join(env.cwd, 'skills-equals')
await mkdir(installDir, { recursive: true })
const result = await runCli([
'install', '@global/timestamped-equals',
`--version=${TIMESTAMP_VERSION}`,
'--dir', installDir,
'--registry', registry.url,
'--token', 'sk_ok'
], { HOME: env.home, USERPROFILE: env.home })
expect(result.exitCode).toBe(0)
expect(registry.received.resolve?.version).toBe(TIMESTAMP_VERSION)
})
test('suite install preserves trailing zeros in a numeric-looking version', async () => {
const env = await createTempHome()
let requestedVersion: string | null = null
const received = { version: null as string | null }
const server = Bun.serve({
port: 0,
fetch(request) {
@ -54,7 +81,7 @@ describe('--version parsing', () => {
return Response.json({ apiBase: '/api/v1', capabilities: ['skill-suite-v1'] })
}
if (url.pathname === '/api/v1/suites/global/starter-pack/install-plan') {
requestedVersion = url.searchParams.get('version')
received.version = url.searchParams.get('version')
return Response.json({ code: 404, message: 'stop after capturing version' }, { status: 404 })
}
return Response.json({ code: 404, message: 'not found' }, { status: 404 })
@ -73,6 +100,6 @@ describe('--version parsing', () => {
], { HOME: env.home, USERPROFILE: env.home })
expect(result.exitCode).not.toBe(0)
expect(requestedVersion).toBe(TIMESTAMP_VERSION)
expect(received.version).toBe(TIMESTAMP_VERSION)
})
})