skillhub/cli/test/helpers/run-cli.ts
dongmucat e249db35a3 test(cli): add comprehensive integration tests and fix update command bugs
- Add integration tests for doctor, install, list, publish, remove, whoami commands
- Expand fake-registry with resolve/delete state capture for real assertions
- Fix update command: use correct package name @astron-team/skillhub from constants
- Refactor runUpdateCommand to accept string[] instead of fragile string splitting
- Add dependency injection to updateCommand for testable unit tests without global mocks
- Replace package.json import with codegen (scripts/generate-pkg-info.ts) to avoid
  leaking devDependencies into the build artifact
- Fix startNetworkFailureServer TOCTOU race by keeping listener alive
- Add TODO markers for known help command bugs (--json not forwarded, unknown topic crash)
- Extend update integration test timeout for real npm registry checks
2026-05-07 14:46:40 +08:00

54 lines
1.7 KiB
TypeScript

import { existsSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
/**
* Spawn the CLI with a clean environment so host-shell exports like
* SKILLHUB_REGISTRY or SKILLHUB_TOKEN don't leak into the test process and
* silently override stored credentials/config. Tests can still inject any
* SKILLHUB_* variable explicitly via the `env` argument.
*/
function sanitizeProcessEnv(): Record<string, string> {
const cleaned: Record<string, string> = {}
for (const [key, value] of Object.entries(process.env)) {
if (typeof value !== 'string') continue
if (key.startsWith('SKILLHUB_')) continue
cleaned[key] = value
}
return cleaned
}
export interface RunCliOptions {
/**
* Working directory for the child process. Defaults to the CLI package root
* so `bun src/index.ts` resolves. Pass a temp dir when the command scans
* cwd (e.g. `doctor`) so tests don't leak fixtures into the repo tree.
*/
cwd?: string
}
export async function runCli(
args: string[],
env: Record<string, string> = {},
options: RunCliOptions = {}
) {
// Use Bun.which() to find bun in PATH, but verify it exists
const whichBun = await Bun.which('bun')
const bunPath = (whichBun && existsSync(whichBun)) ? whichBun : process.execPath
const cliRoot = fileURLToPath(new URL('../../', import.meta.url))
const entry = `${cliRoot}src/index.ts`
const proc = Bun.spawn({
cmd: [bunPath, entry, ...args],
cwd: options.cwd ?? cliRoot,
env: { ...sanitizeProcessEnv(), ...env },
stdout: 'pipe',
stderr: 'pipe'
})
const [stdout, stderr, exitCode] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
proc.exited
])
return { stdout: stdout.trim(), stderr: stderr.trim(), exitCode }
}