Merge origin/master into codex/dsh-badge-plugin

This commit is contained in:
Tianyi Cui
2026-08-08 15:42:47 +08:00
143 changed files with 1211 additions and 2360 deletions

View File

@@ -21,12 +21,16 @@ function exitCode(argv: string[]): number {
afterEach(() => { vi.restoreAllMocks() })
describe('parseDshArgs', () => {
it('routes profile boots, one-shot tasks, and the web alias', () => {
it('routes profile boots, one-shot runs, and the web alias', () => {
expect(parse(['--profile', 'tui'])).toEqual({ mode: 'profile', profile: 'tui', patches: [] })
expect(parse(['--profile', 'headless', 'run', 'the', 'tests']))
.toEqual({ mode: 'profile', profile: 'headless', patches: [], task: 'run the tests' })
expect(parse(['--profile', 'tui', '--patch', 'a.yml', '--patch', 'b.yml']))
.toEqual({ mode: 'profile', profile: 'tui', patches: ['a.yml', 'b.yml'] })
expect(parse(['run', 'run', 'the', 'tests']))
.toEqual({ mode: 'run', profile: 'headless', patches: [], task: 'run the tests' })
expect(parse(['run', '--profile', 'custom', '--patch', 'a.yml', '--patch', 'b.yml', 'run', 'the', 'tests']))
.toEqual({ mode: 'run', profile: 'custom', patches: ['a.yml', 'b.yml'], task: 'run the tests' })
expect(parse(['run', '--', '--profile', 'is', 'task', 'text']))
.toEqual({ mode: 'run', profile: 'headless', patches: [], task: '--profile is task text' })
expect(parse(['web'])).toEqual({ mode: 'web', dev: false, patches: [] })
expect(parse(['web', '--patch', 'web.yml'])).toEqual({ mode: 'web', dev: false, patches: ['web.yml'] })
expect(parse(['web', '--host', '0.0.0.0', '--port', '8080', '--dev', '--workspace-root', '/w']))
@@ -65,6 +69,13 @@ describe('parseDshArgs', () => {
expect(exitCode(['tui'])).toBe(1) // a bare word is a task without --profile
expect(exitCode(['--config', 'c.yml'])).toBe(1) // removed
expect(exitCode(['-p', 'task'])).toBe(1) // removed
expect(exitCode(['--profile', 'headless', 'task'])).toBe(1) // tasks belong to `run`
expect(exitCode(['run'])).toBe(1)
expect(exitCode(['run', ''])).toBe(1)
expect(exitCode(['run', '--profile', '', 'task'])).toBe(1)
expect(exitCode(['run', '--patch=', 'task'])).toBe(1)
expect(exitCode(['--profile', 'headless', 'run', 'task'])).toBe(1)
expect(exitCode(['--patch', 'parent.yml', 'run', 'task'])).toBe(1)
expect(exitCode(['--profile', ''])).toBe(1)
expect(exitCode(['--profile', 'x', '--patch='])).toBe(1)
expect(exitCode(['--dump-config'])).toBe(1)
@@ -90,6 +101,7 @@ describe('parseDshArgs', () => {
it('exits 0 for help and version', () => {
expect(exitCode(['--help'])).toBe(0)
expect(exitCode(['run', '--help'])).toBe(0)
expect(exitCode(['--version'])).toBe(0)
})
})

View File

@@ -182,14 +182,56 @@ describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)',
const help = await runBuiltBin(['--help'])
expect(help.code).toBe(0)
expect(help.stdout).toContain('dsh --profile web')
expect(help.stdout).toContain('dsh run "run the tests"')
expect(help.stdout).toContain('dsh plugin --profile')
expect(help.stdout).not.toMatch(/^\s+(?:tui|meta|upgrade)\b/mu)
for (const removed of [['tui'], ['--config', 'x.yml'], ['-p', 'task']]) {
for (const removed of [['tui'], ['--config', 'x.yml'], ['-p', 'task'], ['--profile', 'headless', 'task']]) {
const result = await runBuiltBin(removed)
expect(result.code).toBe(1)
}
}, 30_000)
it('prints run help without initializing the selected profile', async () => {
const parent = mkdtempSync(join(tmpdir(), 'dsh-run-help-'))
const home = join(parent, 'not-created')
try {
const result = await runBuiltBin(['run', '--help'], { DSH_HOME: home })
expect(result.code).toBe(0)
expect(result.stderr).toBe('')
expect(result.stdout).toContain('Usage: dsh run [options] <task...>')
expect(existsSync(home)).toBe(false)
} finally {
rmSync(parent, { recursive: true, force: true })
}
})
it('runs the default headless profile through the published run command', async () => {
const apiKey = 'built-dsh-run-key'
const server = await startMockLlmServer({
sequence: ['success'],
apiKey,
successText: 'published dsh run reached the mock',
})
const home = mkdtempSync(join(tmpdir(), 'dsh-built-run-'))
try {
const result = await runBuiltBin(['run', 'answer', 'from', 'the', 'published', 'entry'], {
DSH_HOME: home,
DSH_TELEMETRY_DISABLED: '1',
DEEPSEEK_API_KEY: apiKey,
DEEPSEEK_BASE_URL: server.baseURL,
})
expect(result.code, result.stderr).toBe(0)
expect(result.stdout).toBe('published dsh run reached the mock')
expect(result.stderr).toMatch(/^dsh: observing at http:\/\/127\.0\.0\.1:\d+$/u)
expect(server.requests.length).toBeGreaterThan(0)
expect(server.requests.every(request => request.path === '/chat/completions')).toBe(true)
expect(JSON.stringify(server.requests.map(request => request.body))).toContain('answer from the published entry')
} finally {
await server.close()
rmSync(home, { recursive: true, force: true })
}
}, 30_000)
it('does not load a project environment for --version', async () => {
const project = mkdtempSync(join(tmpdir(), 'dsh-version-project-'))
writeFileSync(join(project, '.env'), 'PATH=/project-only-path\n')

View File

@@ -66,7 +66,7 @@ async function runHeadlessPtySmoke(): Promise<string> {
try {
const home = join(cwd, '.dsh')
// Pre-initialize the headless profile with the never-dispose row in its
// user patch layer (the same file `dsh --profile headless` hot-reloads).
// user patch layer (the same file a long-lived profile boot hot-reloads).
const profileDir = join(home, 'profiles', 'headless')
await mkdir(profileDir, { recursive: true })
await writeFile(join(profileDir, 'package.json'), JSON.stringify({
@@ -83,7 +83,7 @@ async function runHeadlessPtySmoke(): Promise<string> {
].join('\n'))
const launch = resolveExampleLaunch({
srcBin: dshBinScript,
configArgs: ['--profile', 'headless', 'never complete'],
configArgs: ['run', 'never complete'],
tsconfigPath,
env: {
DSH_HOME: home,