diff --git a/.github/scripts/protocol/desktop-support.ts b/.github/scripts/protocol/desktop-support.ts deleted file mode 100644 index 3df112132c..0000000000 --- a/.github/scripts/protocol/desktop-support.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { existsSync, readFileSync, writeFileSync } from 'node:fs' -import { resolve } from 'node:path' -import { desktopSupportMatrix } from '../../../storage/framework/core/desktop/src/support' - -const root = resolve(import.meta.dir, '../../..') -const outputPath = resolve(root, '.github/protocol/evidence/desktop-support.json') -const document = { - schemaVersion: '1.0.0', - generatedFrom: 'storage/framework/core/desktop/src/support.ts', - stableTargets: desktopSupportMatrix.filter(row => row.status === 'stable').length, - externalGates: [ - 'https://github.com/stacksjs/stacks/issues/2059', - 'https://github.com/stacksjs/stacks/issues/2062', - 'https://github.com/stacksjs/stacks/issues/2063', - ], - targets: desktopSupportMatrix, -} -const expected = `${JSON.stringify(document, null, 2)}\n` - -if (process.argv.includes('--write')) { - writeFileSync(outputPath, expected) - console.log(`Wrote desktop support matrix (${document.stableTargets} stable targets)`) -} -else if (process.argv.includes('--check')) { - if (!existsSync(outputPath) || readFileSync(outputPath, 'utf8') !== expected) - throw new Error('desktop support evidence is stale; run bun run protocol:desktop') - if (document.targets.some(row => row.status === 'stable' && (!row.installLaunchEvidence || !row.updateRollbackEvidence || row.signing !== 'enforced'))) - throw new Error('stable desktop target lacks required evidence') - console.log(`Desktop support matrix is current (${document.stableTargets} stable targets)`) -} -else { - console.error('usage: bun .github/scripts/protocol/desktop-support.ts --write | --check') - process.exit(2) -} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ff42da12d2..2d33a668b9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -174,8 +174,8 @@ jobs: env: PROTOCOL_REPORT_DIR: .github/protocol/reports run: | - bun test ./.github/scripts/protocol/run-conformance.test.ts - bun test ./.github/scripts/protocol/craft-evidence.test.ts ./.github/scripts/protocol/desktop-lifecycle-report.test.ts + bun test ./app/Commands/protocol/run-conformance.test.ts + bun test ./app/Commands/protocol/craft-evidence.test.ts ./app/Commands/protocol/desktop-lifecycle-report.test.ts bun test ./.github/scripts/deploy/resolve-target.test.ts bun run protocol:drivers:test bun run protocol:conformance diff --git a/.github/workflows/desktop-lifecycle.yml b/.github/workflows/desktop-lifecycle.yml index 7cd0091aca..639fc735d3 100644 --- a/.github/workflows/desktop-lifecycle.yml +++ b/.github/workflows/desktop-lifecycle.yml @@ -5,16 +5,16 @@ on: branches: [main] paths: - storage/framework/core/desktop/** - - .github/scripts/protocol/craft-evidence.ts - - .github/scripts/protocol/desktop-lifecycle-report.ts + - app/Commands/protocol/craft-evidence.ts + - app/Commands/protocol/desktop-lifecycle-report.ts - .github/protocol/evidence/craft.json - .github/workflows/desktop-lifecycle.yml pull_request: branches: [main] paths: - storage/framework/core/desktop/** - - .github/scripts/protocol/craft-evidence.ts - - .github/scripts/protocol/desktop-lifecycle-report.ts + - app/Commands/protocol/craft-evidence.ts + - app/Commands/protocol/desktop-lifecycle-report.ts - .github/protocol/evidence/craft.json - .github/workflows/desktop-lifecycle.yml workflow_dispatch: @@ -123,7 +123,7 @@ jobs: - name: Validate and attest lifecycle report run: >- - bun .github/scripts/protocol/desktop-lifecycle-report.ts + bun app/Commands/protocol/desktop-lifecycle-report.ts --report .craft-contract/artifacts/native-lifecycle/${{ matrix.name }}/report.json --output .github/protocol/reports/desktop/${{ matrix.name }}/attestation.json --revision "$CRAFT_SOURCE_REVISION" diff --git a/app/Commands.ts b/app/Commands.ts index 1d76c105a1..2739b8e5e9 100644 --- a/app/Commands.ts +++ b/app/Commands.ts @@ -28,4 +28,6 @@ export type CommandRegistry = Record */ export default { 'inspire': 'Inspire', + 'protocol': 'Protocol', + 'docs': 'Docs', } satisfies CommandRegistry diff --git a/app/Commands/Docs.ts b/app/Commands/Docs.ts new file mode 100644 index 0000000000..5ce304d3c2 --- /dev/null +++ b/app/Commands/Docs.ts @@ -0,0 +1,48 @@ +import type { CLI } from '@stacksjs/types' +import { run as runArtifacts } from './docs/generated-artifacts' +import { run as runBuddyDocs } from './docs/buddy-commands' +import { run as runLinks } from './docs/links' +import { runTool } from './run-tool' + +/** + * Repo-scoped `buddy docs:*` commands wrapping the documentation-freshness + * tooling under `app/Commands/docs/`: the generated buddy command reference, + * generated API artifacts (OpenAPI + types), and internal link checking. + */ +export default function (cli: CLI): void { + cli + .command('docs:buddy', 'Regenerate the buddy command reference doc') + .action(async () => { + await runTool(runBuddyDocs, '--write') + }) + + cli + .command('docs:buddy:check', 'Verify the buddy command reference doc is current') + .action(async () => { + await runTool(runBuddyDocs, '--check') + }) + + cli + .command('docs:artifacts', 'Regenerate the generated API artifacts (OpenAPI + types)') + .action(async () => { + await runTool(runArtifacts, '--write') + }) + + cli + .command('docs:artifacts:check', 'Verify the generated API artifacts are current') + .action(async () => { + await runTool(runArtifacts, '--check') + }) + + cli + .command('docs:links', 'Report internal documentation links') + .action(async () => { + await runTool(runLinks) + }) + + cli + .command('docs:links:check', 'Verify internal documentation links resolve') + .action(async () => { + await runTool(runLinks, '--check') + }) +} diff --git a/app/Commands/Protocol.ts b/app/Commands/Protocol.ts new file mode 100644 index 0000000000..caaccf19db --- /dev/null +++ b/app/Commands/Protocol.ts @@ -0,0 +1,119 @@ +import type { CLI } from '@stacksjs/types' +import { run as runConformance } from './protocol/run-conformance' +import { run as runCraft } from './protocol/craft-evidence' +import { run as runDesktop } from './protocol/desktop-support' +import { run as runDrivers } from './protocol/driver-registry' +import { run as runDriverContracts } from './protocol/run-driver-contracts' +import { run as runPantry } from './protocol/pantry-evidence' +import { run as runRelease } from './protocol/release-manifest' +import { run as runManifest } from './protocol/source-manifest' +import { run as runSync } from './protocol/sync-suite' +import { runTool } from './run-tool' + +/** + * Repo-scoped `buddy protocol:*` commands. The governance tooling itself lives + * beside this file under `app/Commands/protocol/`; the protocol suite + evidence + * DATA stays under `.github/protocol/`. These wrap the tooling so it is + * discoverable via the CLI (`buddy protocol:conformance`) instead of a bare + * `bun app/Commands/protocol/...`. + */ +export default function (cli: CLI): void { + cli + .command('protocol:conformance', 'Generate the Stacks protocol conformance report') + .action(async () => { + await runConformance() + }) + + cli + .command('protocol:sync', 'Sync the vendored protocol suite from stacksjs/rfcs') + .option('--source ', 'Path to a local rfcs checkout', { default: undefined }) + .action(async (options: { source?: string }) => { + await runTool(runSync, '--write', ...(options.source ? ['--source', options.source] : [])) + }) + + cli + .command('protocol:check', 'Verify the vendored protocol suite is pinned + internally consistent') + .action(async () => { + await runTool(runSync, '--check') + }) + + cli + .command('protocol:manifest', 'Write the protocol source manifest') + .option('--revision ', 'Source revision to pin (default HEAD)', { default: undefined }) + .action(async (options: { revision?: string }) => { + await runTool(runManifest, '--write', ...(options.revision ? ['--revision', options.revision] : [])) + }) + + cli + .command('protocol:manifest:check', 'Verify the protocol source manifest is current') + .action(async () => { + await runTool(runManifest, '--check') + }) + + cli + .command('protocol:release', 'Write the protocol release manifest') + .option('--tag ', 'Release tag', { default: undefined }) + .action(async (options: { tag?: string }) => { + await runTool(runRelease, '--write', ...(options.tag ? ['--tag', options.tag] : [])) + }) + + cli + .command('protocol:release:check', 'Verify the protocol release manifest is current') + .action(async () => { + await runTool(runRelease, '--check') + }) + + cli + .command('protocol:drivers', 'Write the driver capability registry evidence') + .action(async () => { + await runTool(runDrivers, '--write') + }) + + cli + .command('protocol:drivers:check', 'Verify the driver capability registry evidence') + .action(async () => { + await runTool(runDrivers, '--check') + }) + + cli + .command('protocol:drivers:test', 'Run the driver contract suite') + .action(async () => { + await runTool(runDriverContracts) + }) + + cli + .command('protocol:desktop', 'Write the desktop support matrix evidence') + .action(async () => { + await runTool(runDesktop, '--write') + }) + + cli + .command('protocol:desktop:check', 'Verify the desktop support matrix evidence') + .action(async () => { + await runTool(runDesktop, '--check') + }) + + cli + .command('protocol:pantry', 'Write the pantry evidence') + .action(async () => { + await runTool(runPantry, '--write') + }) + + cli + .command('protocol:pantry:check', 'Verify the pantry evidence') + .action(async () => { + await runTool(runPantry, '--check') + }) + + cli + .command('protocol:craft', 'Write the Craft evidence') + .action(async () => { + await runTool(runCraft, '--write') + }) + + cli + .command('protocol:craft:check', 'Verify the Craft evidence') + .action(async () => { + await runTool(runCraft, '--check') + }) +} diff --git a/.github/scripts/docs/buddy-commands.test.ts b/app/Commands/docs/buddy-commands.test.ts similarity index 100% rename from .github/scripts/docs/buddy-commands.test.ts rename to app/Commands/docs/buddy-commands.test.ts diff --git a/.github/scripts/docs/buddy-commands.ts b/app/Commands/docs/buddy-commands.ts similarity index 95% rename from .github/scripts/docs/buddy-commands.ts rename to app/Commands/docs/buddy-commands.ts index 101e0b24d2..a82acb28d3 100644 --- a/.github/scripts/docs/buddy-commands.ts +++ b/app/Commands/docs/buddy-commands.ts @@ -89,7 +89,7 @@ export function renderBuddyCommandReference(inventory: BuddyInventory): string { 'title: Buddy Command Reference', 'description: Generated reference for every Buddy command, argument, option, alias, default, and example.', '---', - '', + '', '', '# Buddy Command Reference', '', @@ -125,10 +125,10 @@ export function loadBuddyInventory(): BuddyInventory { return inventory } -if (import.meta.main) { +export async function run(): Promise { const mode = process.argv.includes('--write') ? 'write' : process.argv.includes('--check') ? 'check' : null if (!mode) { - console.error('usage: bun .github/scripts/docs/buddy-commands.ts --write | --check') + console.error('usage: bun app/Commands/docs/buddy-commands.ts --write | --check') process.exit(2) } @@ -145,3 +145,6 @@ if (import.meta.main) { console.log('Buddy command reference matches the runtime registry') } } + +if (import.meta.main) + await run() diff --git a/.github/scripts/docs/generated-artifacts.test.ts b/app/Commands/docs/generated-artifacts.test.ts similarity index 100% rename from .github/scripts/docs/generated-artifacts.test.ts rename to app/Commands/docs/generated-artifacts.test.ts diff --git a/.github/scripts/docs/generated-artifacts.ts b/app/Commands/docs/generated-artifacts.ts similarity index 94% rename from .github/scripts/docs/generated-artifacts.ts rename to app/Commands/docs/generated-artifacts.ts index 2341e30879..cc799ff2a5 100644 --- a/.github/scripts/docs/generated-artifacts.ts +++ b/app/Commands/docs/generated-artifacts.ts @@ -51,12 +51,12 @@ async function check(): Promise { console.log(`Generated API artifacts are current (${Object.keys(JSON.parse(expected.openApi).paths).length} paths)`) } -if (import.meta.main) { +export async function run(): Promise { try { if (process.argv.includes('--write')) await write() else if (process.argv.includes('--check')) await check() else { - console.error('usage: bun .github/scripts/docs/generated-artifacts.ts --write | --check') + console.error('usage: bun app/Commands/docs/generated-artifacts.ts --write | --check') process.exit(2) } } @@ -65,3 +65,6 @@ if (import.meta.main) { process.exit(1) } } + +if (import.meta.main) + await run() diff --git a/.github/scripts/docs/links.test.ts b/app/Commands/docs/links.test.ts similarity index 100% rename from .github/scripts/docs/links.test.ts rename to app/Commands/docs/links.test.ts diff --git a/.github/scripts/docs/links.ts b/app/Commands/docs/links.ts similarity index 97% rename from .github/scripts/docs/links.ts rename to app/Commands/docs/links.ts index 90be2b6307..c3d6836ece 100644 --- a/.github/scripts/docs/links.ts +++ b/app/Commands/docs/links.ts @@ -7,7 +7,7 @@ * can reject a broken cross-reference. External links, mail/tel, and same-page * anchors are intentionally left alone. * - * Usage: `bun .github/scripts/docs/links.ts [--check]` + * Usage: `bun app/Commands/docs/links.ts [--check]` */ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs' @@ -127,7 +127,7 @@ export function checkDocsLinks(docsRoot = docsDir): BrokenDocLink[] { return broken } -if (import.meta.main) { +export async function run(): Promise { const broken = checkDocsLinks() if (broken.length === 0) { console.log('✓ All internal documentation links resolve.') @@ -140,3 +140,6 @@ if (import.meta.main) { process.exit(1) } } + +if (import.meta.main) + await run() diff --git a/.github/scripts/protocol/craft-evidence.test.ts b/app/Commands/protocol/craft-evidence.test.ts similarity index 100% rename from .github/scripts/protocol/craft-evidence.test.ts rename to app/Commands/protocol/craft-evidence.test.ts diff --git a/.github/scripts/protocol/craft-evidence.ts b/app/Commands/protocol/craft-evidence.ts similarity index 96% rename from .github/scripts/protocol/craft-evidence.ts rename to app/Commands/protocol/craft-evidence.ts index 09f057b044..e7519da16f 100644 --- a/.github/scripts/protocol/craft-evidence.ts +++ b/app/Commands/protocol/craft-evidence.ts @@ -73,7 +73,7 @@ function serializedEvidence(): string { return `${JSON.stringify(craftEvidence, null, 2)}\n` } -if (import.meta.main) { +export async function run(): Promise { const errors = validateCraftEvidence(craftEvidence) if (process.argv.includes('--write')) { if (errors.length) throw new Error(errors.join('\n')) @@ -87,7 +87,10 @@ if (import.meta.main) { console.log(`Craft evidence pins ${craftEvidence.source.tag} at ${craftEvidence.source.revision}`) } else { - console.error('usage: bun .github/scripts/protocol/craft-evidence.ts --write | --check') + console.error('usage: bun app/Commands/protocol/craft-evidence.ts --write | --check') process.exit(2) } } + +if (import.meta.main) + await run() diff --git a/.github/scripts/protocol/desktop-lifecycle-report.test.ts b/app/Commands/protocol/desktop-lifecycle-report.test.ts similarity index 100% rename from .github/scripts/protocol/desktop-lifecycle-report.test.ts rename to app/Commands/protocol/desktop-lifecycle-report.test.ts diff --git a/.github/scripts/protocol/desktop-lifecycle-report.ts b/app/Commands/protocol/desktop-lifecycle-report.ts similarity index 97% rename from .github/scripts/protocol/desktop-lifecycle-report.ts rename to app/Commands/protocol/desktop-lifecycle-report.ts index 7c7b97c09a..a40d01d58d 100644 --- a/.github/scripts/protocol/desktop-lifecycle-report.ts +++ b/app/Commands/protocol/desktop-lifecycle-report.ts @@ -37,7 +37,7 @@ export function validateLifecycleReport(report: LifecycleReport, expected: { return errors } -if (import.meta.main) { +export async function run(): Promise { const value = (name: string): string => { const index = process.argv.indexOf(name) const result = index === -1 ? undefined : process.argv[index + 1] @@ -68,3 +68,6 @@ if (import.meta.main) { writeFileSync(outputPath, `${JSON.stringify(attestation, null, 2)}\n`) console.log(`Validated Craft lifecycle for ${expected.platform}/${expected.architecture}`) } + +if (import.meta.main) + await run() diff --git a/app/Commands/protocol/desktop-support.ts b/app/Commands/protocol/desktop-support.ts new file mode 100644 index 0000000000..476ed4a11d --- /dev/null +++ b/app/Commands/protocol/desktop-support.ts @@ -0,0 +1,39 @@ +import { existsSync, readFileSync, writeFileSync } from 'node:fs' +import { resolve } from 'node:path' +import { desktopSupportMatrix } from '../../../storage/framework/core/desktop/src/support' + +const root = resolve(import.meta.dir, '../../..') +const outputPath = resolve(root, '.github/protocol/evidence/desktop-support.json') +const document = { + schemaVersion: '1.0.0', + generatedFrom: 'storage/framework/core/desktop/src/support.ts', + stableTargets: desktopSupportMatrix.filter(row => row.status === 'stable').length, + externalGates: [ + 'https://github.com/stacksjs/stacks/issues/2059', + 'https://github.com/stacksjs/stacks/issues/2062', + 'https://github.com/stacksjs/stacks/issues/2063', + ], + targets: desktopSupportMatrix, +} +const expected = `${JSON.stringify(document, null, 2)}\n` + +export function run(): void { + if (process.argv.includes('--write')) { + writeFileSync(outputPath, expected) + console.log(`Wrote desktop support matrix (${document.stableTargets} stable targets)`) + } + else if (process.argv.includes('--check')) { + if (!existsSync(outputPath) || readFileSync(outputPath, 'utf8') !== expected) + throw new Error('desktop support evidence is stale; run buddy protocol:desktop') + if (document.targets.some(row => row.status === 'stable' && (!row.installLaunchEvidence || !row.updateRollbackEvidence || row.signing !== 'enforced'))) + throw new Error('stable desktop target lacks required evidence') + console.log(`Desktop support matrix is current (${document.stableTargets} stable targets)`) + } + else { + console.error('usage: buddy protocol:desktop --write | --check') + process.exit(2) + } +} + +if (import.meta.main) + run() diff --git a/.github/scripts/protocol/desktop-workflow.test.ts b/app/Commands/protocol/desktop-workflow.test.ts similarity index 100% rename from .github/scripts/protocol/desktop-workflow.test.ts rename to app/Commands/protocol/desktop-workflow.test.ts diff --git a/.github/scripts/protocol/driver-registry.ts b/app/Commands/protocol/driver-registry.ts similarity index 95% rename from .github/scripts/protocol/driver-registry.ts rename to app/Commands/protocol/driver-registry.ts index defba00ea0..6e15ff0e1a 100644 --- a/.github/scripts/protocol/driver-registry.ts +++ b/app/Commands/protocol/driver-registry.ts @@ -47,7 +47,7 @@ function render(): string { return `${JSON.stringify(document, null, 2)}\n` } -if (import.meta.main) { +export async function run(): Promise { const errors = validate() if (errors.length > 0) { for (const error of errors) console.error(`error: ${error}`) @@ -64,7 +64,10 @@ if (import.meta.main) { console.log(`Driver registry is current (${capabilityRegistry.length} records)`) } else { - console.error('usage: bun .github/scripts/protocol/driver-registry.ts --write | --check') + console.error('usage: bun app/Commands/protocol/driver-registry.ts --write | --check') process.exit(2) } } + +if (import.meta.main) + await run() diff --git a/.github/scripts/protocol/pantry-evidence.test.ts b/app/Commands/protocol/pantry-evidence.test.ts similarity index 100% rename from .github/scripts/protocol/pantry-evidence.test.ts rename to app/Commands/protocol/pantry-evidence.test.ts diff --git a/.github/scripts/protocol/pantry-evidence.ts b/app/Commands/protocol/pantry-evidence.ts similarity index 97% rename from .github/scripts/protocol/pantry-evidence.ts rename to app/Commands/protocol/pantry-evidence.ts index 17bf8a4074..56d2b2f381 100644 --- a/.github/scripts/protocol/pantry-evidence.ts +++ b/app/Commands/protocol/pantry-evidence.ts @@ -117,7 +117,7 @@ function check(): void { console.log(`Pantry evidence pins ${pantryEvidence.source.tag} at ${pantryEvidence.source.revision}`) } -if (import.meta.main) { +export async function run(): Promise { if (process.argv.includes('--write')) { const errors = validatePantryEvidence(pantryEvidence) if (errors.length) throw new Error(errors.join('\n')) @@ -126,7 +126,10 @@ if (import.meta.main) { } else if (process.argv.includes('--check')) check() else { - console.error('usage: bun .github/scripts/protocol/pantry-evidence.ts --write | --check') + console.error('usage: bun app/Commands/protocol/pantry-evidence.ts --write | --check') process.exit(2) } } + +if (import.meta.main) + await run() diff --git a/.github/scripts/protocol/release-manifest.test.ts b/app/Commands/protocol/release-manifest.test.ts similarity index 100% rename from .github/scripts/protocol/release-manifest.test.ts rename to app/Commands/protocol/release-manifest.test.ts diff --git a/.github/scripts/protocol/release-manifest.ts b/app/Commands/protocol/release-manifest.ts similarity index 97% rename from .github/scripts/protocol/release-manifest.ts rename to app/Commands/protocol/release-manifest.ts index 298c2ead09..96532f93d9 100644 --- a/.github/scripts/protocol/release-manifest.ts +++ b/app/Commands/protocol/release-manifest.ts @@ -129,10 +129,10 @@ export function renderReleaseManifest(manifest: ReleaseManifest): string { return `${JSON.stringify(manifest, null, 2)}\n` } -if (import.meta.main) { +export async function run(): Promise { const mode = process.argv.includes('--write') ? 'write' : process.argv.includes('--check') ? 'check' : null if (!mode) { - console.error('usage: bun .github/scripts/protocol/release-manifest.ts --write --tag | --check') + console.error('usage: bun app/Commands/protocol/release-manifest.ts --write --tag | --check') process.exit(2) } @@ -154,3 +154,6 @@ if (import.meta.main) { console.log(`Release manifest matches ${current.tag} at ${current.sourceRevision}`) } } + +if (import.meta.main) + await run() diff --git a/.github/scripts/protocol/run-conformance.test.ts b/app/Commands/protocol/run-conformance.test.ts similarity index 100% rename from .github/scripts/protocol/run-conformance.test.ts rename to app/Commands/protocol/run-conformance.test.ts diff --git a/.github/scripts/protocol/run-conformance.ts b/app/Commands/protocol/run-conformance.ts similarity index 99% rename from .github/scripts/protocol/run-conformance.ts rename to app/Commands/protocol/run-conformance.ts index 4ae1ca3e55..f09f538037 100644 --- a/.github/scripts/protocol/run-conformance.ts +++ b/app/Commands/protocol/run-conformance.ts @@ -477,7 +477,7 @@ export async function buildReport(): Promise> { } } -if (import.meta.main) { +export async function run(): Promise { const report = await buildReport() const schema = JSON.parse(readFileSync(resolve(suiteRoot, 'schemas/conformance-report.schema.json'), 'utf8')) const catalog = JSON.parse(readFileSync(resolve(suiteRoot, 'catalog.json'), 'utf8')) as Catalog @@ -499,3 +499,6 @@ if (import.meta.main) { writeFileSync(resolve(outputDirectory, 'stacks-conformance.md'), renderSummary(report)) console.log(`Protocol report generated in ${outputDirectory}`) } + +if (import.meta.main) + await run() diff --git a/.github/scripts/protocol/run-driver-contracts.test.ts b/app/Commands/protocol/run-driver-contracts.test.ts similarity index 100% rename from .github/scripts/protocol/run-driver-contracts.test.ts rename to app/Commands/protocol/run-driver-contracts.test.ts diff --git a/.github/scripts/protocol/run-driver-contracts.ts b/app/Commands/protocol/run-driver-contracts.ts similarity index 98% rename from .github/scripts/protocol/run-driver-contracts.ts rename to app/Commands/protocol/run-driver-contracts.ts index 9f5a672724..283b646dee 100644 --- a/.github/scripts/protocol/run-driver-contracts.ts +++ b/app/Commands/protocol/run-driver-contracts.ts @@ -92,7 +92,7 @@ export function runDriverContracts(): void { console.log(`Driver contract matrix passed ${plan.tests.length} retained test files`) } -if (import.meta.main) { +export async function run(): Promise { try { runDriverContracts() } @@ -101,3 +101,6 @@ if (import.meta.main) { process.exit(1) } } + +if (import.meta.main) + await run() diff --git a/.github/scripts/protocol/source-manifest.ts b/app/Commands/protocol/source-manifest.ts similarity index 95% rename from .github/scripts/protocol/source-manifest.ts rename to app/Commands/protocol/source-manifest.ts index 4c74b2db50..20b6ae3cb4 100644 --- a/.github/scripts/protocol/source-manifest.ts +++ b/app/Commands/protocol/source-manifest.ts @@ -120,10 +120,10 @@ function render(manifest: SourceManifest): string { return `${JSON.stringify(manifest, null, 2)}\n` } -if (import.meta.main) { +export async function run(): Promise { const mode = process.argv.includes('--write') ? 'write' : process.argv.includes('--check') ? 'check' : null if (!mode) { - console.error('usage: bun .github/scripts/protocol/source-manifest.ts --write [--revision ] | --check') + console.error('usage: bun app/Commands/protocol/source-manifest.ts --write [--revision ] | --check') process.exit(2) } @@ -141,7 +141,10 @@ if (import.meta.main) { const current = JSON.parse(readFileSync(outputPath, 'utf8')) as SourceManifest const expected = render(buildSourceManifest(current.sourceRevision)) if (readFileSync(outputPath, 'utf8') !== expected) - throw new Error(`source manifest is stale or modified; run bun .github/scripts/protocol/source-manifest.ts --write --revision ${current.sourceRevision}`) + throw new Error(`source manifest is stale or modified; run bun app/Commands/protocol/source-manifest.ts --write --revision ${current.sourceRevision}`) console.log(`Source manifest matches ${current.sourceRevision}`) } } + +if (import.meta.main) + await run() diff --git a/.github/scripts/protocol/sync-suite.test.ts b/app/Commands/protocol/sync-suite.test.ts similarity index 95% rename from .github/scripts/protocol/sync-suite.test.ts rename to app/Commands/protocol/sync-suite.test.ts index 5e4500192f..63c4ecf6e6 100644 --- a/.github/scripts/protocol/sync-suite.test.ts +++ b/app/Commands/protocol/sync-suite.test.ts @@ -21,7 +21,7 @@ describe('protocol requirement-id uniqueness (stacksjs/stacks#2050)', () => { }) it('the vendored catalog.json has unique requirement ids', () => { - const catalog = JSON.parse(readFileSync(resolve(import.meta.dir, '../../protocol/suite/1.0-draft/catalog.json'), 'utf8')) as { + const catalog = JSON.parse(readFileSync(resolve(import.meta.dir, '../../../.github/protocol/suite/1.0-draft/catalog.json'), 'utf8')) as { requirements: Array<{ id: string }> } const ids = catalog.requirements.map(requirement => requirement.id) @@ -56,7 +56,7 @@ describe('protocol fixture-corpus integrity (stacksjs/stacks#2051)', () => { }) it('the vendored fixture corpus has unique ids and no dangling requirement refs', () => { - const base = resolve(import.meta.dir, '../../protocol/suite/1.0-draft') + const base = resolve(import.meta.dir, '../../../.github/protocol/suite/1.0-draft') const corpus = JSON.parse(readFileSync(resolve(base, 'fixtures/conformance.json'), 'utf8')) as { fixtures: Array<{ id: string, requirements?: string[] }> } diff --git a/.github/scripts/protocol/sync-suite.ts b/app/Commands/protocol/sync-suite.ts similarity index 97% rename from .github/scripts/protocol/sync-suite.ts rename to app/Commands/protocol/sync-suite.ts index 645bd26049..42f19f8fb1 100644 --- a/.github/scripts/protocol/sync-suite.ts +++ b/app/Commands/protocol/sync-suite.ts @@ -189,7 +189,7 @@ function checkFixtures(): void { console.log(`Protocol fixtures: ${ids.length} fixtures, ids unique, all requirement refs resolve`) } -if (import.meta.main) { +export async function run(): Promise { if (process.argv.includes('--write')) { const source = resolve(argument('--source') || process.env.STACKS_RFC_SOURCE || resolve(root, '../rfcs')) writeSnapshot(source) @@ -200,7 +200,10 @@ if (import.meta.main) { checkFixtures() } else { - console.error('usage: bun .github/scripts/protocol/sync-suite.ts --write [--source ../rfcs] | --check') + console.error('usage: bun app/Commands/protocol/sync-suite.ts --write [--source ../rfcs] | --check') process.exit(2) } } + +if (import.meta.main) + await run() diff --git a/app/Commands/run-tool.ts b/app/Commands/run-tool.ts new file mode 100644 index 0000000000..872328cfc3 --- /dev/null +++ b/app/Commands/run-tool.ts @@ -0,0 +1,16 @@ +/** + * Invoke a migrated tooling module's `run()` with a synthetic argv so its + * existing `process.argv` flag parsing (`--check` / `--write` / `--revision` …) + * sees the flags the buddy command wants, then restore the real argv. Buddy runs + * one command per process and the action is terminal, so this swap is safe. + */ +export async function runTool(run: () => void | Promise, ...flags: string[]): Promise { + const saved = process.argv + process.argv = [saved[0] ?? 'bun', saved[1] ?? 'buddy', ...flags] + try { + await run() + } + finally { + process.argv = saved + } +} diff --git a/docs/guide/buddy/commands.md b/docs/guide/buddy/commands.md index f73d5c5a46..338fa7f722 100644 --- a/docs/guide/buddy/commands.md +++ b/docs/guide/buddy/commands.md @@ -2,11 +2,11 @@ title: Buddy Command Reference description: Generated reference for every Buddy command, argument, option, alias, default, and example. --- - + # Buddy Command Reference -This reference is generated from Buddy's runtime command registry and currently contains **244 commands**. Run `bun run docs:buddy` after changing the registry; CI rejects stale output. +This reference is generated from Buddy's runtime command registry and currently contains **266 commands**. Run `bun run docs:buddy` after changing the registry; CI rejects stale output. ## Command groups @@ -24,6 +24,7 @@ This reference is generated from Buddy's runtime command registry and currently | `dashboard` | 2 | | `dev` | 8 | | `dns` | 3 | +| `docs` | 6 | | `domains` | 3 | | `email` | 7 | | `env` | 7 | @@ -43,6 +44,7 @@ This reference is generated from Buddy's runtime command registry and currently | `phone` | 4 | | `ports` | 2 | | `projects` | 1 | +| `protocol` | 16 | | `publish` | 5 | | `queue` | 21 | | `realtime` | 2 | @@ -966,6 +968,60 @@ Additively sync config/dns.ts to the registrar (creates missing records; never d | `--dry-run` | Show the plan without writing any records | boolean, optional | `false` | | `--verbose` | Enable verbose output | boolean, optional | `false` | +### `docs:artifacts` + +Regenerate the generated API artifacts (OpenAPI + types) + +- Usage: `$ buddy docs:artifacts` +- Namespace: `docs` +- Aliases: none +- Arguments: none + +### `docs:artifacts:check` + +Verify the generated API artifacts are current + +- Usage: `$ buddy docs:artifacts:check` +- Namespace: `docs` +- Aliases: none +- Arguments: none + +### `docs:buddy` + +Regenerate the buddy command reference doc + +- Usage: `$ buddy docs:buddy` +- Namespace: `docs` +- Aliases: none +- Arguments: none + +### `docs:buddy:check` + +Verify the buddy command reference doc is current + +- Usage: `$ buddy docs:buddy:check` +- Namespace: `docs` +- Aliases: none +- Arguments: none + +### `docs:links` + +Report internal documentation links + +- Usage: `$ buddy docs:links` +- Namespace: `docs` +- Aliases: none +- Arguments: none + +### `docs:links:check` + +Verify internal documentation links resolve + +- Usage: `$ buddy docs:links:check` +- Namespace: `docs` +- Aliases: none +- Arguments: none + ### `doctor` Run health checks on your Stacks installation @@ -1524,7 +1580,7 @@ Synchronize metadata and submit an existing Safari version to App Review | `--version` | Marketing version to submit (defaults to package.json) | value, required | — | | `--api-key-id` | App Store Connect API key ID | value, required | — | | `--api-issuer-id` | App Store Connect API issuer ID | value, required | — | -| `--api-key-path` | Path to the App Store Connect AuthKey_*.p8 file | value, required | — | +| `--api-key-path` | Path to the App Store Connect AuthKey__.p8 file | value, required | — | | `--platform` | Submit macos, ios, or all (defaults to config safariPlatforms) | value, required | — | | `--prepare-only` | Synchronize the listing without submitting it for review | boolean, optional | — | @@ -2715,6 +2771,162 @@ Find all Stacks projects on your system | `-l`, `--list` | List all local Stacks projects | boolean, optional | `true` | | `--verbose` | Enable verbose output | boolean, optional | `false` | +### `protocol:check` + +Verify the vendored protocol suite is pinned + internally consistent + +- Usage: `$ buddy protocol:check` +- Namespace: `protocol` +- Aliases: none +- Arguments: none + +### `protocol:conformance` + +Generate the Stacks protocol conformance report + +- Usage: `$ buddy protocol:conformance` +- Namespace: `protocol` +- Aliases: none +- Arguments: none + +### `protocol:craft` + +Write the Craft evidence + +- Usage: `$ buddy protocol:craft` +- Namespace: `protocol` +- Aliases: none +- Arguments: none + +### `protocol:craft:check` + +Verify the Craft evidence + +- Usage: `$ buddy protocol:craft:check` +- Namespace: `protocol` +- Aliases: none +- Arguments: none + +### `protocol:desktop` + +Write the desktop support matrix evidence + +- Usage: `$ buddy protocol:desktop` +- Namespace: `protocol` +- Aliases: none +- Arguments: none + +### `protocol:desktop:check` + +Verify the desktop support matrix evidence + +- Usage: `$ buddy protocol:desktop:check` +- Namespace: `protocol` +- Aliases: none +- Arguments: none + +### `protocol:drivers` + +Write the driver capability registry evidence + +- Usage: `$ buddy protocol:drivers` +- Namespace: `protocol` +- Aliases: none +- Arguments: none + +### `protocol:drivers:check` + +Verify the driver capability registry evidence + +- Usage: `$ buddy protocol:drivers:check` +- Namespace: `protocol` +- Aliases: none +- Arguments: none + +### `protocol:drivers:test` + +Run the driver contract suite + +- Usage: `$ buddy protocol:drivers:test` +- Namespace: `protocol` +- Aliases: none +- Arguments: none + +### `protocol:manifest` + +Write the protocol source manifest + +- Usage: `$ buddy protocol:manifest` +- Namespace: `protocol` +- Aliases: none +- Arguments: none + +| Option | Description | Contract | Default | +| --- | --- | --- | --- | +| `--revision` | Source revision to pin (default HEAD) | value, required | — | + +### `protocol:manifest:check` + +Verify the protocol source manifest is current + +- Usage: `$ buddy protocol:manifest:check` +- Namespace: `protocol` +- Aliases: none +- Arguments: none + +### `protocol:pantry` + +Write the pantry evidence + +- Usage: `$ buddy protocol:pantry` +- Namespace: `protocol` +- Aliases: none +- Arguments: none + +### `protocol:pantry:check` + +Verify the pantry evidence + +- Usage: `$ buddy protocol:pantry:check` +- Namespace: `protocol` +- Aliases: none +- Arguments: none + +### `protocol:release` + +Write the protocol release manifest + +- Usage: `$ buddy protocol:release` +- Namespace: `protocol` +- Aliases: none +- Arguments: none + +| Option | Description | Contract | Default | +| --- | --- | --- | --- | +| `--tag` | Release tag | value, required | — | + +### `protocol:release:check` + +Verify the protocol release manifest is current + +- Usage: `$ buddy protocol:release:check` +- Namespace: `protocol` +- Aliases: none +- Arguments: none + +### `protocol:sync` + +Sync the vendored protocol suite from stacksjs/rfcs + +- Usage: `$ buddy protocol:sync` +- Namespace: `protocol` +- Aliases: none +- Arguments: none + +| Option | Description | Contract | Default | +| --- | --- | --- | --- | +| `--source` | Path to a local rfcs checkout | value, required | — | + ### `publish` Publish a Stacks default into your userland (app/) directory so you can customize it diff --git a/package.json b/package.json index 5a6a74990f..67c4f5e607 100644 --- a/package.json +++ b/package.json @@ -62,29 +62,29 @@ "test:ui": "./buddy test:ui", "test:coverage": "./buddy test:coverage", "test:types": "./buddy test:types", - "docs:buddy": "bun .github/scripts/docs/buddy-commands.ts --write", - "docs:buddy:check": "bun .github/scripts/docs/buddy-commands.ts --check", - "docs:artifacts": "bun .github/scripts/docs/generated-artifacts.ts --write", - "docs:artifacts:check": "bun .github/scripts/docs/generated-artifacts.ts --check", - "docs:links": "bun .github/scripts/docs/links.ts", - "docs:links:check": "bun .github/scripts/docs/links.ts --check", + "docs:buddy": "./buddy docs:buddy", + "docs:buddy:check": "./buddy docs:buddy:check", + "docs:artifacts": "./buddy docs:artifacts", + "docs:artifacts:check": "./buddy docs:artifacts:check", + "docs:links": "./buddy docs:links", + "docs:links:check": "./buddy docs:links:check", "deps:lockfile:check": "bun .github/scripts/check-lockfile-version.ts", - "protocol:sync": "bun .github/scripts/protocol/sync-suite.ts --write", - "protocol:check": "bun .github/scripts/protocol/sync-suite.ts --check", - "protocol:conformance": "bun .github/scripts/protocol/run-conformance.ts", - "protocol:manifest": "bun .github/scripts/protocol/source-manifest.ts --write", - "protocol:manifest:check": "bun .github/scripts/protocol/source-manifest.ts --check", - "protocol:release": "bun .github/scripts/protocol/release-manifest.ts --write", - "protocol:release:check": "bun .github/scripts/protocol/release-manifest.ts --check", - "protocol:drivers": "bun .github/scripts/protocol/driver-registry.ts --write", - "protocol:drivers:check": "bun .github/scripts/protocol/driver-registry.ts --check", - "protocol:drivers:test": "bun .github/scripts/protocol/run-driver-contracts.ts", - "protocol:desktop": "bun .github/scripts/protocol/desktop-support.ts --write", - "protocol:desktop:check": "bun .github/scripts/protocol/desktop-support.ts --check", - "protocol:pantry": "bun .github/scripts/protocol/pantry-evidence.ts --write", - "protocol:pantry:check": "bun .github/scripts/protocol/pantry-evidence.ts --check", - "protocol:craft": "bun .github/scripts/protocol/craft-evidence.ts --write", - "protocol:craft:check": "bun .github/scripts/protocol/craft-evidence.ts --check", + "protocol:sync": "./buddy protocol:sync", + "protocol:check": "./buddy protocol:check", + "protocol:conformance": "./buddy protocol:conformance", + "protocol:manifest": "./buddy protocol:manifest", + "protocol:manifest:check": "./buddy protocol:manifest:check", + "protocol:release": "./buddy protocol:release", + "protocol:release:check": "./buddy protocol:release:check", + "protocol:drivers": "./buddy protocol:drivers", + "protocol:drivers:check": "./buddy protocol:drivers:check", + "protocol:drivers:test": "./buddy protocol:drivers:test", + "protocol:desktop": "./buddy protocol:desktop", + "protocol:desktop:check": "./buddy protocol:desktop:check", + "protocol:pantry": "./buddy protocol:pantry", + "protocol:pantry:check": "./buddy protocol:pantry:check", + "protocol:craft": "./buddy protocol:craft", + "protocol:craft:check": "./buddy protocol:craft:check", "bud": "./buddy", "stx": "./buddy" },