mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
dsh --profile <name> replaces the fixed entry modes: --config and -p are removed, --patch adds overlays over the composed profile, a positional task selects one-shot mode (requires the headless-runner row), and dsh web stays as the alias for --profile web carrying the Web flag family as patches. dsh plugin --profile <name> forwards verbatim to pnpm in the profile directory, initializes on first use, and reconciles the dsh.plugins layer list after add/remove (patch-less packages warn and stay plain dependencies). Config dumps and the keyless web e2e scaffold compose the same bundle layers over the same empty root as the boot.
113 lines
4.1 KiB
TypeScript
113 lines
4.1 KiB
TypeScript
/**
|
|
* Node 22 startup-output smoke for the shipped Web CLI composition.
|
|
*
|
|
* Only the dedicated Node compatibility gate opts this test in after building
|
|
* both artifacts; ordinary Vitest inventory deterministically skips it.
|
|
* The child runs built artifacts under plain Node with the real shipped
|
|
* web profile (dsh-base + dsh-web-app bundle patches, auto-initialized).
|
|
* Its URL line follows the settled profile boot; SIGTERM then exercises the
|
|
* shipped quiescent disposer.
|
|
*/
|
|
|
|
import { spawn } from 'node:child_process'
|
|
import { existsSync } from 'node:fs'
|
|
import { mkdtemp, readFile, rm } from 'node:fs/promises'
|
|
import { tmpdir } from 'node:os'
|
|
import { join, resolve } from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
import yaml from 'js-yaml'
|
|
import { describe, expect, it } from 'vitest'
|
|
|
|
const repoRoot = fileURLToPath(new URL('../../../', import.meta.url))
|
|
const builtBin = join(repoRoot, 'apps/cli/lib/bin.js')
|
|
const webDist = join(repoRoot, 'apps/web/dist/index.html')
|
|
// The web bundle's patch owns the session-query-sqlite lazy-open row.
|
|
const configPath = join(repoRoot, 'packages/bundle/web-app/cordis.patch.yml')
|
|
const requireBuiltArtifacts = process.env.DSH_REQUIRE_BUILT_CLI_SMOKE === '1'
|
|
|
|
interface ConfigRow {
|
|
id?: string
|
|
config?: { openAt?: unknown }
|
|
}
|
|
|
|
const jsExprType = new yaml.Type('tag:yaml.org,2002:js', {
|
|
kind: 'scalar',
|
|
construct: value => String(value),
|
|
})
|
|
const configSchema = yaml.JSON_SCHEMA.extend(jsExprType)
|
|
|
|
/** Boot the built Web CLI, wait for its settled URL, then dispose through SIGTERM. */
|
|
function runBuiltWeb(cwd: string): Promise<{ stdout: string; stderr: string; code: number }> {
|
|
return new Promise((resolveRun, rejectRun) => {
|
|
const env: NodeJS.ProcessEnv = {
|
|
...process.env,
|
|
DEEPSEEK_API_KEY: 'dsh-cli-smoke-dummy-key',
|
|
DSH_HOME: join(cwd, '.dsh'),
|
|
}
|
|
delete env.DEEPSEEK_BASE_URL
|
|
delete env.NODE_OPTIONS
|
|
delete env.NODE_NO_WARNINGS
|
|
const child = spawn(process.execPath, [
|
|
builtBin,
|
|
'web',
|
|
'--host',
|
|
'127.0.0.1',
|
|
'--port',
|
|
'0',
|
|
], {
|
|
cwd,
|
|
env,
|
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
})
|
|
let stdout = ''
|
|
let stderr = ''
|
|
let settled = false
|
|
child.stdout.setEncoding('utf8')
|
|
child.stderr.setEncoding('utf8')
|
|
child.stdout.on('data', (chunk: string) => {
|
|
stdout += chunk
|
|
if (!settled && /dsh web: http:\/\/127\.0\.0\.1:\d+/u.test(stdout)) {
|
|
settled = true
|
|
child.kill('SIGTERM')
|
|
}
|
|
})
|
|
child.stderr.on('data', (chunk: string) => { stderr += chunk })
|
|
const timer = setTimeout(() => {
|
|
child.kill('SIGKILL')
|
|
rejectRun(new Error(`built Web CLI did not settle and dispose within 60s\nstdout:\n${stdout}\nstderr:\n${stderr}`))
|
|
}, 60_000)
|
|
child.on('error', (error) => {
|
|
clearTimeout(timer)
|
|
rejectRun(error)
|
|
})
|
|
child.on('close', (code) => {
|
|
clearTimeout(timer)
|
|
if (!settled) {
|
|
rejectRun(new Error(`built Web CLI exited before settled startup (code ${String(code)})\nstdout:\n${stdout}\nstderr:\n${stderr}`))
|
|
return
|
|
}
|
|
resolveRun({ stdout, stderr, code: code ?? -1 })
|
|
})
|
|
})
|
|
}
|
|
|
|
describe.skipIf(!requireBuiltArtifacts)('built CLI lazy-search startup', () => {
|
|
it('boots and disposes the shipped composition without a SQLite startup warning', async () => {
|
|
expect(existsSync(builtBin), `missing built CLI ${resolve(builtBin)}; run pnpm build`).toBe(true)
|
|
expect(existsSync(webDist), `missing Web dist ${resolve(webDist)}; run pnpm run build:web`).toBe(true)
|
|
const rows = yaml.load(await readFile(configPath, 'utf8'), { schema: configSchema }) as ConfigRow[]
|
|
const searchRow = rows.find(row => row.id === 'session-query-sqlite')
|
|
expect(searchRow?.config?.openAt).toBe('first-search')
|
|
|
|
const cwd = await mkdtemp(join(tmpdir(), 'dsh-cli-lazy-search-'))
|
|
try {
|
|
const result = await runBuiltWeb(cwd)
|
|
expect(result.stdout).toMatch(/dsh web: http:\/\/127\.0\.0\.1:\d+/u)
|
|
expect(result.code).toBe(0)
|
|
expect(result.stderr).not.toMatch(/ExperimentalWarning: SQLite/u)
|
|
} finally {
|
|
await rm(cwd, { recursive: true, force: true })
|
|
}
|
|
}, 70_000)
|
|
})
|