// pgh CLI help/usage surface (forge issue #63 — "Fix pgh ... --help /
// missing-args usage"). Locks in: every command exports a usage string,
// --help is detected before any daemon connection, git-passthrough commands
// forward a trailing --help to git, and `pgh issue <verb>` (a habit-mistake
// where an action is typed in the <repo> slot) surfaces usage rather than
// silently listing a repo by that name.
import test from 'brittle'
import { wantsHelp } from '../lib/cli.js'
import { run as runIssue } from '../lib/commands/issue.js'
// Every command module the dispatcher can load. Kept in sync with the
// COMMANDS map in cli.js (names.js is reached via claim/release/names).
const COMMAND_MODULES = [
'init', 'store', 'publish', 'releases', 'app', 'seed', 'names', 'user',
'overlay', 'identity', 'device', 'peer', 'status', 'dump-keys', 'search',
'profile', 'deploy', 'label', 'admin', 'repo', 'issue', 'org', 'config',
'commit', 'clone', 'pull', 'push', 'remote', 'server'
]
test('every command module exports a non-empty usage string', async (t) => {
for (const name of COMMAND_MODULES) {
const mod = await import(`../lib/commands/${name}.js`)
t.ok(typeof mod.usage === 'string' && mod.usage.trim().length > 0,
`${name}.js exports usage`)
}
})
test('wantsHelp: --help / -h / help anywhere triggers for normal commands', (t) => {
t.ok(wantsHelp('issue', ['--help']))
t.ok(wantsHelp('issue', ['-h']))
t.ok(wantsHelp('issue', ['help']))
t.ok(wantsHelp('issue', ['myrepo', '--help']), 'non-leading --help still triggers')
t.absent(wantsHelp('issue', ['myrepo']), 'no help flag → run normally')
t.absent(wantsHelp('issue', []), 'no args → run normally (lets the command print its own usage)')
})
test('wantsHelp: passthrough commands forward a trailing --help to git', (t) => {
// Leading help is ours (explain pgh's wrapper)...
t.ok(wantsHelp('push', ['--help']))
t.ok(wantsHelp('push', ['help']))
// ...but a --help after an arg belongs to git.
t.absent(wantsHelp('push', ['main', '--help']))
t.absent(wantsHelp('commit', ['-m', 'x', '--help']))
t.absent(wantsHelp('pull', ['origin', 'main', '-h']))
})
test('pgh issue <action-verb> shows usage instead of listing a repo by that name', async (t) => {
const client = {
async call () { t.fail('should not reach the daemon for a mistaken verb'); return {} }
}
for (const verb of ['create', 'close', 'comment', 'reopen', 'delete', 'edit']) {
await t.exception(() => runIssue(client, [verb]),
new RegExp(`'${verb}' is an action, not a repo`))
}
})
test('pgh issue <repo> --create dispatches create-issue (regression)', async (t) => {
const calls = []
const client = { async call (m, p) { calls.push({ m, p }); return { ok: true } } }
await runIssue(client, ['myrepo', '--create', 'a title', '--body', 'hello'])
t.alike(calls, [{ m: 'create-issue', p: { repo: 'myrepo', title: 'a title', body: 'hello' } }])
})