Loading files
402b08f79This file is pinned to the selected release. Download the original to open it in your design tool.
// `harness dsh …` as a person at a terminal uses it: what each verb prints, and the exit code it returns.
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { execFileSync } from 'node:child_process'
import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { env } from '../config/env.js'
import { dshInstallDir, installedDsh, invalidateInstalledDsh, upsertInstalledRecord } from './installed.js'
import type { DshInstallOptions } from './install.js'
import { HARNESS_MONOREPO, resetBundledDshRegistry } from './registry.js'
// When `captured` is a list, installDsh records what it was asked and fails as if offline; otherwise it
// is the real one. The registry-id cases would otherwise clone from GitHub.
const seams = vi.hoisted(() => ({ captured: null as null | unknown[] }))
vi.mock('./install.js', async (importOriginal) => {
const actual = await importOriginal<typeof import('./install.js')>()
return {
...actual,
installDsh: (opts: DshInstallOptions) => {
if (!seams.captured) return actual.installDsh(opts)
seams.captured.push({ source: opts.source, ref: opts.ref, path: opts.path, link: opts.link })
return Promise.resolve({ ok: false, error: 'OFFLINE', detail: 'no network in a test' })
},
}
})
const { dshCommand, dshUsage } = await import('./command.js')
function gitRepo(dir: string, files: Record<string, string>): string {
mkdirSync(dir, { recursive: true })
for (const [name, body] of Object.entries(files)) writeFileSync(join(dir, name), body, { mode: name.endsWith('.sh') ? 0o755 : 0o644 })
const git = (...args: string[]) => execFileSync('git', ['-C', dir, ...args], { stdio: 'ignore', env: { ...process.env, GIT_AUTHOR_NAME: 't', GIT_AUTHOR_EMAIL: 't@example.com', GIT_COMMITTER_NAME: 't', GIT_COMMITTER_EMAIL: 't@example.com' } })
git('init', '-q', '-b', 'main'); git('add', '-A'); git('commit', '-q', '-m', 'fixture')
return dir
}
describe('harness dsh', () => {
let root: string
let savedDshDir: string
let savedRef: string | undefined
let out: string[]
let err: string[]
beforeEach(() => {
root = realpathSync(mkdtempSync(join(tmpdir(), 'dsh-command-')))
savedDshDir = env.DSH_DIR
savedRef = env.HARNESS_STORE_REF
env.DSH_DIR = join(root, 'installed')
env.HARNESS_STORE_REF = undefined
invalidateInstalledDsh()
// These command-contract fixtures exercise the offline bundled fallback.
// catalog.spec.ts covers live discovery and installation separately.
vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('offline fixture')))
out = []
err = []
vi.spyOn(console, 'log').mockImplementation((...args: unknown[]) => { out.push(args.join(' ')) })
vi.spyOn(console, 'error').mockImplementation((...args: unknown[]) => { err.push(args.join(' ')) })
// A registry of our own instead of the real shelf: what `list` offers and what an id resolves to.
vi.stubGlobal('__DSH_REGISTRY__', JSON.stringify([
{ id: 'acme/thing', name: 'Thing', repo: 'https://example.com/thing.git', engine: 'claude', tier: 1 },
{ id: 'autonomous/typst', name: 'Typst', repo: HARNESS_MONOREPO, ref: 'main', path: 'store/agents/typst', engine: 'claude', tier: 2 },
{ id: 'autonomous/pane', kind: 'viewer', name: 'Pane', repo: HARNESS_MONOREPO, ref: 'main', path: 'store/viewers/pane' },
]))
resetBundledDshRegistry()
})
afterEach(() => {
seams.captured = null
vi.restoreAllMocks()
vi.unstubAllGlobals()
resetBundledDshRegistry()
env.DSH_DIR = savedDshDir
env.HARNESS_STORE_REF = savedRef
invalidateInstalledDsh()
rmSync(root, { recursive: true, force: true })
})
const thing = (manifest: Record<string, unknown> = {}, files: Record<string, string> = {}): string => gitRepo(join(root, 'src', String(manifest.id ?? 'acme/thing').replace('/', '-')), {
'harness.json': JSON.stringify({ spec: 1, id: 'acme/thing', name: 'Thing', engine: 'claude', ...manifest }),
...files,
})
it('prints its usage for no verb, help, or a verb it does not know; only the last is a failure', async () => {
expect(await dshCommand(undefined, [])).toBe(0)
expect(await dshCommand('help', [])).toBe(0)
expect(await dshCommand('frobnicate', [])).toBe(1)
expect(err).toEqual([dshUsage(), dshUsage(), dshUsage()])
expect(dshUsage()).toContain('harness dsh install <id|url|path> [--ref <ref>] [--path <folder>] [--link]')
})
describe('list', () => {
it('says so when nothing is installed and the registry is empty', async () => {
vi.stubGlobal('__DSH_REGISTRY__', '[]')
resetBundledDshRegistry()
expect(await dshCommand('list', [])).toBe(0)
expect(out).toEqual([' (nothing installed, registry empty)'])
})
it('lists what is installed, what is broken, then what the registry offers that is not here', async () => {
await dshCommand('install', [thing({ viewer: { command: 'v.sh', url: 'http://127.0.0.1:${port}/' } })])
await dshCommand('install', [thing({ id: 'acme/pane', kind: 'viewer', engine: undefined, viewer: { command: 'v.sh', url: 'http://127.0.0.1:${port}/' } }), '--link'])
upsertInstalledRecord({ id: 'acme/broken', dir: join(root, 'gone'), source: 'x', ref: null, commit: null, linked: false, installedAt: 0 })
out = []
expect(await dshCommand('list', [])).toBe(0)
expect(out).toHaveLength(1)
expect(out[0].split('\n')).toEqual([
` ${'acme/pane'.padEnd(28)} ${'Thing'.padEnd(12)} ${'viewer'.padEnd(11)} tier 2 installed (linked) @ ${installedDsh('acme/pane')!.commit!.slice(0, 8)} · ${dshInstallDir('acme/pane')}`,
` ${'acme/thing'.padEnd(28)} ${'Thing'.padEnd(12)} ${'on claude'.padEnd(11)} tier 2 installed @ ${installedDsh('acme/thing')!.commit!.slice(0, 8)} · ${dshInstallDir('acme/thing')}`,
` ${'acme/broken'.padEnd(28)} ${'?'.padEnd(12)} ${''.padEnd(11)} ${''.padEnd(6)} BROKEN · ${join(root, 'gone')} is missing`,
` ${'autonomous/pane'.padEnd(28)} ${'Pane'.padEnd(12)} ${'viewer'.padEnd(11)} tier ? available · ${HARNESS_MONOREPO}/tree/main/store/viewers/pane`,
` ${'autonomous/typst'.padEnd(28)} ${'Typst'.padEnd(12)} ${'on claude'.padEnd(11)} tier 2 available · ${HARNESS_MONOREPO}/tree/main/store/agents/typst`,
])
})
})
describe('install', () => {
it('installs a local repo, narrating phases and lines, and says what it installed', async () => {
const repo = thing({ toolchain: { doctor: './doctor.sh' } }, { 'doctor.sh': '#!/bin/sh\necho "ok all here"\n' })
expect(await dshCommand('install', [repo])).toBe(0)
expect(out[0]).toBe(`[dsh] ${repo} · clone · cloning ${repo}`)
expect(out).toContain('[dsh] acme/thing · doctor')
expect(out).toContain(' ok all here')
expect(out.at(-2)).toBe('[dsh] acme/thing · done')
expect(out.at(-1)).toBe(`Installed acme/thing (Thing, runs on claude) at ${dshInstallDir('acme/thing')}`)
})
it('says a viewer package is one', async () => {
const repo = thing({ id: 'acme/pane', kind: 'viewer', engine: undefined, viewer: { command: 'v.sh', url: 'http://127.0.0.1:${port}/' } })
expect(await dshCommand('install', [repo, '--link'])).toBe(0)
expect(out.at(-1)).toBe(`Installed acme/pane (Thing, a viewer package) at ${dshInstallDir('acme/pane')}`)
})
it('fails with usage for no target, and plainly for a target that is no id, URL or path', async () => {
expect(await dshCommand('install', [])).toBe(1)
expect(await dshCommand('install', ['--link'])).toBe(1)
expect(err).toEqual([dshUsage(), dshUsage()])
expect(await dshCommand('install', ['bad\nsource'])).toBe(1)
expect(err.at(-1)).toBe('harness dsh install: bad\nsource is not an id, URL or path')
})
it('reports a failed install with its error and detail', async () => {
expect(await dshCommand('install', [join(root, 'nothing-here')])).toBe(1)
expect(err.at(-1)).toMatch(/^harness dsh install failed · CLONE_FAILED · git clone exited 128: /)
})
it('an id installs from its registry entry; --ref and --path override it, and never count as the target', async () => {
seams.captured = []
expect(await dshCommand('install', ['autonomous/typst'])).toBe(1)
expect(await dshCommand('install', ['--ref', 'v2', 'autonomous/typst', '--path', 'store/agents/typst-next'])).toBe(1)
expect(await dshCommand('install', ['https://example.com/other.git', '--ref'])).toBe(1)
expect(seams.captured).toEqual([
{ source: HARNESS_MONOREPO, ref: 'main', path: 'store/agents/typst', link: false },
{ source: HARNESS_MONOREPO, ref: 'v2', path: 'store/agents/typst-next', link: false },
{ source: 'https://example.com/other.git', ref: undefined, path: undefined, link: false },
])
expect(err.at(-1)).toBe('harness dsh install failed · OFFLINE · no network in a test')
})
it('HARNESS_STORE_REF moves an id from the shelf to the branch under test; --link takes no ref or path', async () => {
env.HARNESS_STORE_REF = 'store-e2e'
resetBundledDshRegistry()
seams.captured = []
await dshCommand('install', ['autonomous/typst'])
await dshCommand('install', ['/Users/example/code/typst', '--link', '--ref', 'v2', '--path', 'x'])
expect(seams.captured).toEqual([
{ source: HARNESS_MONOREPO, ref: 'store-e2e', path: 'store/agents/typst', link: false },
{ source: '/Users/example/code/typst', ref: undefined, path: undefined, link: true },
])
})
})
it('updates from the recorded source, reports the version, and rejects missing or invalid targets', async () => {
const repo = thing()
expect(await dshCommand('install', [repo])).toBe(0)
writeFileSync(join(repo, 'README.md'), 'new package version')
execFileSync('git', ['-C', repo, 'add', '-A'])
execFileSync('git', ['-C', repo, '-c', 'user.name=test', '-c', 'user.email=test@example.test', 'commit', '-qm', 'updated'])
expect(await dshCommand('update', ['acme/thing'])).toBe(0)
expect(out.at(-1)).toMatch(/acme\/thing is up to date @ [a-f0-9]{8}. Workspaces preserved./)
expect(await dshCommand('update', ['acme/missing'])).toBe(1)
expect(err.at(-1)).toContain('NOT_INSTALLED')
for (const args of [[], ['../../etc'], ['acme/thing', 'acme/other']]) {
expect(await dshCommand('update', args)).toBe(1)
expect(err.at(-1)).toContain('harness dsh update <id>')
}
})
describe('doctor', () => {
it('runs the installed harness\'s doctor, by its id or an id it went by before', async () => {
await dshCommand('install', [thing({ formerly: ['acme/old-thing'], toolchain: { doctor: './doctor.sh' } }, { 'doctor.sh': '#!/bin/sh\necho "ok fine"\n' })])
out = []
expect(await dshCommand('doctor', ['acme/thing'])).toBe(0)
expect(out).toEqual([' ok fine', 'acme/thing is ready'])
out = []
expect(await dshCommand('doctor', ['acme/old-thing'])).toBe(0)
expect(out.at(-1)).toBe('acme/thing is ready')
})
it('a doctor that fails is not ready; no id, or one not installed, is said', async () => {
await dshCommand('install', [thing({ toolchain: { doctor: 'echo "miss everything"; exit 1' } })])
out = []
expect(await dshCommand('doctor', ['acme/thing'])).toBe(1)
expect(out.at(-1)).toBe('acme/thing is not ready')
expect(await dshCommand('doctor', [])).toBe(1)
expect(await dshCommand('doctor', ['acme/nobody'])).toBe(1)
expect(err.slice(-2)).toEqual(['harness dsh doctor: <id> is not installed', 'harness dsh doctor: acme/nobody is not installed'])
})
})
describe('check', () => {
it('prints each line and whether the checkout conforms, from a path or the current folder', async () => {
const repo = thing({ author: 'Acme', description: 'A thing' })
expect(await dshCommand('check', [repo])).toBe(0)
expect(out.at(-1)).toBe('acme/thing conforms to spec 1')
expect(out[0]).toContain('ok harness.json parses · acme/thing "Thing" runs on claude')
const cwd = process.cwd()
process.chdir(repo)
try {
expect(await dshCommand('check', [])).toBe(0)
} finally {
process.chdir(cwd)
}
expect(out.at(-1)).toBe('acme/thing conforms to spec 1')
})
it('a checkout that fails names its id; one with no manifest names the path', async () => {
const repo = thing({ toolchain: { doctor: 'missing.sh' } })
expect(await dshCommand('check', [repo])).toBe(1)
expect(out.at(-1)).toBe('acme/thing does not conform')
const empty = join(root, 'empty')
mkdirSync(empty)
expect(await dshCommand('check', [empty])).toBe(1)
expect(out.at(-1)).toBe(`${empty} does not conform`)
})
})
describe('remove', () => {
it('removes an installed harness; no id is usage; one not installed is a failure', async () => {
await dshCommand('install', [thing()])
out = []
expect(await dshCommand('remove', ['acme/thing'])).toBe(0)
expect(out).toEqual(['Removed acme/thing'])
expect(await dshCommand('remove', [])).toBe(1)
expect(err.at(-1)).toBe(dshUsage())
expect(await dshCommand('remove', ['acme/thing'])).toBe(1)
expect(err.at(-1)).toBe('harness dsh remove failed · NOT_INSTALLED · acme/thing is not installed')
})
})
})