feat(examples): add one-shot CLI demo
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# `@deepseek-ai/dsh-loader-smoke`
|
||||
|
||||
Shared subprocess harness for keyless example smokes that boot the real stdio-agent bin and a real `cordis.yml` through the Cordis Loader. A test supplies absolute bin/config/tsconfig paths, optional environment overrides, and stdin lines; `runLoaderSmoke` owns the isolated cwd, DSH homes, tsx path resolution, 30-second process deadline, captured diagnostics, forced kill, EOF, and cleanup.
|
||||
Shared subprocess harness for keyless example smokes that boot a real app bin and `cordis.yml` through the Cordis Loader. A test supplies absolute bin/config/tsconfig paths, optional complete bin arguments, environment overrides, stdin lines, pre-run world setup, and a pre-cleanup world assertion; `runLoaderSmoke` owns the isolated cwd, DSH homes, tsx path resolution, 30-second process deadline, captured diagnostics, forced kill, EOF, and cleanup.
|
||||
|
||||
Successful runs return stdout and stderr only after a zero exit. Non-zero exits and deadlines reject with both captured streams. `LOADER_SMOKE_TEST_TIMEOUT_MS` leaves Vitest enough room for the process-owned diagnostic timeout to fire first.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Shared subprocess harness for keyless example smokes that boot a real
|
||||
* `cordis.yml` through the stdio-agent bin and Cordis Loader.
|
||||
* `cordis.yml` through an app bin and Cordis Loader.
|
||||
*
|
||||
* @module @deepseek-ai/dsh-loader-smoke
|
||||
*/
|
||||
@@ -23,10 +23,12 @@ export interface LoaderSmokeOptions {
|
||||
readonly label: string
|
||||
/** Prefix for the isolated temporary process cwd. */
|
||||
readonly tempDirPrefix: string
|
||||
/** Absolute stdio-agent bin path. */
|
||||
/** Absolute app-bin path. */
|
||||
readonly binScript: string
|
||||
/** Absolute real Loader config path. */
|
||||
/** Absolute real Loader config path, passed as the sole bin argument by default. */
|
||||
readonly configPath: string
|
||||
/** Complete argv after the bin path; overrides the default `[configPath]`. */
|
||||
readonly binArgs?: readonly string[]
|
||||
/** Absolute repo tsconfig path used for unbuilt workspace-package resolution. */
|
||||
readonly tsconfigPath: string
|
||||
/** Environment overrides layered over the parent and isolated DSH homes. */
|
||||
@@ -35,6 +37,10 @@ export interface LoaderSmokeOptions {
|
||||
readonly stdinLines?: readonly string[]
|
||||
/** Process deadline override for harness tests. */
|
||||
readonly processTimeoutMs?: number
|
||||
/** Optional world-state setup run in the isolated cwd before process start. */
|
||||
readonly prepare?: (cwd: string) => Promise<void> | void
|
||||
/** Optional world-state assertion run in the isolated cwd before cleanup. */
|
||||
readonly inspect?: (cwd: string) => Promise<void> | void
|
||||
}
|
||||
|
||||
/** Captured output from a Loader smoke that exited successfully. */
|
||||
@@ -56,10 +62,11 @@ export async function runLoaderSmoke(options: LoaderSmokeOptions): Promise<Loade
|
||||
const cwd = await mkdtemp(join(tmpdir(), options.tempDirPrefix))
|
||||
const processTimeoutMs = options.processTimeoutMs ?? DEFAULT_PROCESS_TIMEOUT_MS
|
||||
try {
|
||||
return await new Promise((resolve, reject) => {
|
||||
await options.prepare?.(cwd)
|
||||
const result = await new Promise<LoaderSmokeResult>((resolve, reject) => {
|
||||
const child = spawn(
|
||||
process.execPath,
|
||||
['--expose-internals', '--import', TSX_LOADER, options.binScript, options.configPath],
|
||||
['--expose-internals', '--import', TSX_LOADER, options.binScript, ...(options.binArgs ?? [options.configPath])],
|
||||
{
|
||||
cwd,
|
||||
env: {
|
||||
@@ -111,6 +118,8 @@ export async function runLoaderSmoke(options: LoaderSmokeOptions): Promise<Loade
|
||||
|
||||
child.stdin.end((options.stdinLines ?? []).map(line => `${line}\n`).join(''))
|
||||
})
|
||||
await options.inspect?.(cwd)
|
||||
return result
|
||||
} finally {
|
||||
await rm(cwd, { recursive: true, force: true })
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ process.stdin.on('data', (chunk: string) => { input += chunk })
|
||||
process.stdin.on('end', () => {
|
||||
console.log(JSON.stringify({
|
||||
configPath: process.argv[2],
|
||||
args: process.argv.slice(2),
|
||||
cwd: process.cwd(),
|
||||
dshHome: process.env.DSH_HOME,
|
||||
agentsHome: process.env.DSH_AGENTS_HOME,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { existsSync } from 'node:fs'
|
||||
import { readFile, writeFile } from 'node:fs/promises'
|
||||
import { join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { LOADER_SMOKE_TEST_TIMEOUT_MS, runLoaderSmoke } from '@deepseek-ai/dsh-loader-smoke'
|
||||
@@ -21,6 +23,7 @@ describe('runLoaderSmoke', () => {
|
||||
})
|
||||
const output = JSON.parse(result.stdout) as {
|
||||
configPath: string
|
||||
args: string[]
|
||||
cwd: string
|
||||
dshHome: string
|
||||
agentsHome: string
|
||||
@@ -29,6 +32,7 @@ describe('runLoaderSmoke', () => {
|
||||
}
|
||||
expect(output).toMatchObject({
|
||||
configPath,
|
||||
args: [configPath],
|
||||
marker: 'present',
|
||||
input: 'one\ntwo\n',
|
||||
})
|
||||
@@ -38,6 +42,29 @@ describe('runLoaderSmoke', () => {
|
||||
expect(existsSync(output.cwd)).toBe(false)
|
||||
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
|
||||
|
||||
it('passes an arbitrary bin argv and inspects world state before cleanup', async () => {
|
||||
let inspected = ''
|
||||
let marker = ''
|
||||
const result = await runLoaderSmoke({
|
||||
label: 'argv fixture',
|
||||
tempDirPrefix: 'loader-smoke-argv-',
|
||||
binScript: fixture('success'),
|
||||
configPath,
|
||||
binArgs: ['--config', configPath, '--output-format', 'json', 'task with spaces'],
|
||||
tsconfigPath,
|
||||
prepare: cwd => writeFile(join(cwd, 'marker.txt'), 'prepared'),
|
||||
inspect: async (cwd) => {
|
||||
inspected = cwd
|
||||
marker = await readFile(join(cwd, 'marker.txt'), 'utf8')
|
||||
},
|
||||
})
|
||||
const output = JSON.parse(result.stdout) as { args: string[]; cwd: string }
|
||||
expect(output.args).toEqual(['--config', configPath, '--output-format', 'json', 'task with spaces'])
|
||||
expect(canonicalTempPath(inspected)).toBe(canonicalTempPath(output.cwd))
|
||||
expect(marker).toBe('prepared')
|
||||
expect(existsSync(inspected)).toBe(false)
|
||||
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
|
||||
|
||||
it('rejects a non-zero exit with captured diagnostics', async () => {
|
||||
await expect(runLoaderSmoke({
|
||||
label: 'failure fixture',
|
||||
|
||||
Reference in New Issue
Block a user