refactor(cmdline): make command providers ordinary

This commit is contained in:
Turtle
2026-08-10 21:49:11 +08:00
parent 668bdb3d8e
commit 09e2d2ddc1
46 changed files with 400 additions and 742 deletions

View File

@@ -87,8 +87,8 @@ describe('parseDshArgs', () => {
expect(exitCode(['web', '--dump-config', '--dump-default-config'])).toBe(1)
expect(exitCode(['web', '--dump-default-config', '--patch', 'w.yml'])).toBe(1)
expect(exitCode(['web', '--patch='])).toBe(1)
// A dump never runs the app's startup row, so it cannot show what that
// app's own flags would decide; printing a tree that differs from the same
// A dump never runs app command-line providers, so it cannot show what
// those flags would decide; printing a tree that differs from the same
// invocation's boot would mislead.
expect(exitCode(['web', '--dump-config', '--port', '8080'])).toBe(1)
expect(exitCode(['--profile', 'web', '--dump-config', '-h'])).toBe(1)

View File

@@ -128,8 +128,8 @@ function createProfileLifecycleFixture(): ProfileLifecycleFixture {
return { home, ready, settled, disposed, interrupt }
}
function startProfileLifecycle(fixture: ProfileLifecycleFixture) {
return execa(process.execPath, [dshBin, '--profile', 'lifecycle'], {
function startProfileLifecycle(fixture: ProfileLifecycleFixture, args: readonly string[] = []) {
return execa(process.execPath, [dshBin, '--profile', 'lifecycle', ...args], {
cwd: fixture.home,
input: '',
reject: false,
@@ -203,9 +203,9 @@ interface StartupFixture {
}
/**
* A custom profile whose bundle owns a command line: a startup row whose
* `cmdlineArgs` injection identifies it to the launcher, and a row that reads
* what it resolved through a `!!js` config expression. Both plugin modules resolve
* A custom profile whose ordinary provider plugin injects `cmdlineArgs`, plus
* a row that reads its app-owned service through a `!!js` config expression.
* Both plugin modules resolve
* `@deepseek-ai/dsh-cmdline` and `commander` through the profile module
* fallback, exactly as an installed out-of-tree bundle does.
*/
@@ -219,12 +219,13 @@ function createStartupFixture(): StartupFixture {
mkdirSync(bundleDir, { recursive: true })
writeFileSync(join(bundleDir, 'startup.mjs'), [
"import { Command } from 'commander'",
"import { runStartup } from '@deepseek-ai/dsh-cmdline'",
"import { parseCmdline } from '@deepseek-ai/dsh-cmdline'",
"export const name = 'fixture-startup'",
"export const inject = ['cmdlineArgs']",
'export function apply(ctx) {',
" const program = new Command().name('fixture').option('--generation <value>', 'echoed generation')",
" return runStartup(ctx, 'fixtureStartup', program, parsed => ({ generation: parsed.opts().generation }))",
' const values = parseCmdline(ctx, program, parsed => ({ generation: parsed.opts().generation }))',
' if (values !== undefined) ctx.provide(\'fixtureStartup\', values)',
'}',
'',
].join('\n'))
@@ -260,11 +261,10 @@ function createStartupFixture(): StartupFixture {
` name: ${pathToFileURL(join(bundleDir, 'waiting.mjs')).href}`,
' inject: [fixtureStartup]',
' config:',
// The flag the startup row resolved wins over the value written beside it.
" generation: !!js ctx.get('fixtureStartup')?.generation ?? 'bundle-default'",
// Lazy interpolation runs only after the provider's service is injected.
" generation: !!js ctx.fixtureStartup.generation ?? 'bundle-default'",
' - id: fixture-startup',
` name: ${pathToFileURL(join(bundleDir, 'startup.mjs')).href}`,
' inject: [cmdlineArgs]',
' - id: reload-witness',
` name: ${pathToFileURL(join(bundleDir, 'witness.mjs')).href}`,
'',
@@ -466,21 +466,9 @@ describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)',
}
}, 30_000)
it('rejects arguments when no active row injects the profile command line', async () => {
it('lets a profile without a parser ignore app arguments and dispose on a startup-time signal', async () => {
const fixture = createProfileLifecycleFixture()
try {
const result = await runBuiltBin(['--profile', 'lifecycle', '--help'], { DSH_HOME: fixture.home })
expect(result.code).toBe(1)
expect(result.stderr).toContain('takes no app arguments because no active row injects cmdlineArgs')
expect(existsSync(fixture.ready)).toBe(false)
} finally {
rmSync(fixture.home, { recursive: true, force: true })
}
}, 30_000)
it('applies a custom profile bundle and disposes it on a startup-time signal', async () => {
const fixture = createProfileLifecycleFixture()
const child = startProfileLifecycle(fixture)
const child = startProfileLifecycle(fixture, ['--unclaimed'])
try {
await waitForFile(fixture.ready)
requestProfileShutdown(child, fixture)
@@ -551,8 +539,8 @@ describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)',
const child = startStartupProfile(fixture, ['--generation', 'flagged'])
try {
await waitForFile(fixture.ready)
// The waiting row started once, already carrying the flag value: the
// launcher never saw --generation, and the app resolved it first.
// The consumer started once, already carrying the flag value: the
// launcher never saw --generation, and the app provider resolved it first.
expect(readFileSync(fixture.echo, 'utf8')).toBe('flagged')
requestProfileShutdown(child, fixture)
expect((await child).exitCode).toBe(0)
@@ -562,7 +550,7 @@ describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)',
}
}, 30_000)
it('starts a waiting row on its composed value when the invocation carries no app arguments', async () => {
it('starts a consumer on its composed value when the invocation carries no app arguments', async () => {
const fixture = createStartupFixture()
const child = startStartupProfile(fixture, [])
try {
@@ -577,7 +565,7 @@ describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)',
}, 30_000)
it('keeps the app arguments across a user patch reload', async () => {
// A live edit recomposes every row while the startup service remains
// A live edit recomposes every row while the provider service remains
// active, so each config expression reads the same invocation value (a
// served port does not move back to its composed fallback).
const fixture = createStartupFixture()