feat(cli): typed subcommand stubs (Phase 0 + skeleton) - #46
Open
codewizdave wants to merge 11 commits into
Open
codewizdave wants to merge 11 commits into
codewizdave wants to merge 11 commits into
Conversation
Documents the design contract for the CLI before any code is written.
Read in order; this is the source of truth that the implementation
must satisfy.
- docs/cli/README.md: domain index and reading order.
- docs/cli/02-design.md: the six subcommands (ls, cat, grep, find,
path, symbols), output formats, the .docs.md convention, the
Source column in find, and naming consistency.
- docs/cli/03-architecture.md: module layout under apps/cli/, core
types (DocsFile, Corpus, SymbolMatch, TextMatch, ExitCode, UserError,
InternalError), data flow, argv parsing via commander, output
contract, error model, performance expectations.
- docs/cli/04-corpus.md: three options (A: direct read of MDX, B: read
.source/ build artefacts, C: shared indexer package) and the
decision for B with rationale and cost.
- docs/cli/05-testing.md: three layers (unit, integration, smoke),
fixture strategy, what is explicitly not tested, local loop.
- docs/cli/06-roadmap.md: six phases (scaffold, corpus loader, read
commands, indexer and search, polish, publish) each with an exit
criterion.
- docs/cli/commands/{ls,cat,grep,find,path,symbols,README}.md:
per-subcommand specification (signature, output format, exit codes,
edge cases, internal modules).
No code changes. No runtime behavior changes. No changeset required
(no release to cut). The CLI workspace itself does not yet exist;
this PR exists to align on design before scaffolding begins.
Phase 0 of the @deessejs/package-cli roadmap: a workspace that builds, lints, type-checks, tests, and exposes a no-op stub binary. Files: - apps/cli/package.json: name @deessejs/package-cli, bin package-cli.mjs, scripts matching the turbo contract (build, test:run, type-check, lint, clean), ESM-only with exports map, files allowlist (bin + dist). Commander as runtime dependency. - apps/cli/tsconfig.json: noEmit, ES2022 + NodeNext, strict, includes src/ and tests/, types: [node]. - apps/cli/tsconfig.build.json: emits dist/ from src/, excludes tests. - apps/cli/eslint.config.js: flat config copied from packages/example, scoped to src/. - apps/cli/vitest.config.ts: node environment, globals enabled, v8 coverage, globalSetup that rebuilds dist/ (tests spawn the bin which imports dist/index.js). - apps/cli/.gitignore: dist, coverage, node_modules, tsbuildinfo. - apps/cli/bin/package-cli.mjs: shebang, re-exports dist/index.js. - apps/cli/scripts/postbuild.mjs: chmod 755 on the bin file so npm install doesn't drop the executable bit. - apps/cli/src/index.ts: shebang entry, delegates to cli.run. - apps/cli/tests/global-setup.ts: tsc -p tsconfig.build.json before the test files are loaded. - pnpm-lock.yaml: new package + transitive deps. Verified locally: pnpm --filter @deessejs/package-cli build -> builds dist/ pnpm --filter @deessejs/package-cli lint -> clean pnpm --filter @deessejs/package-cli type-check -> clean
Wires every subcommand in apps/cli/src/commands/ as a typed stub that parses its own args, validates them, and exits 2 with a clear 'not implemented yet' InternalError. No corpus loading, no real behavior: this lands the architecture described in docs/cli/03-architecture.md and docs/cli/commands/<name>.md without claiming the Phase 1/2/3 implementations. Modules introduced: - src/errors.ts: ExitCode (0/1/2), UserError (exit 1), InternalError (exit 2), exitWithError() helper used by the top-level handler. - src/output.ts: writeRecords() (stdout, one row per \n), writeWarning() (stderr, '[warn] <message>'). - src/corpus.ts: DocsFile and Corpus types, plus loadCorpus() and resolveCorpusRoot() stubs that throw InternalError. - src/index/build.ts: buildIndex() stub, reportDuplicateSymbol() helper that emits the standard '[warn] duplicate symbol' format. - src/index/search.ts: SymbolMatch, TextMatch types, plus findSymbols(), grepCorpus(), listSymbols() stubs. - src/commands/shared.ts: SubcommandRegist type, CommandContext interface that threads the resolved corpus root into each subcommand. - src/commands/<ls,cat,grep,find,path,symbols>.ts: each parses its args with commander, validates them (UserError on missing), and throws InternalError 'is not implemented yet'. - src/cli.ts: buildProgram() and run(argv), the top-level handler. Tests: - tests/errors.test.ts: ExitCode values, UserError/InternalError exit code mapping, exitWithError() format and exit code for UserError, plain Error, and wrapped errors. - tests/output.test.ts: writeRecords() row formatting, iterable acceptance, empty iterable, writeWarning() format and stdout separation. - tests/commands.test.ts: integration via the published bin. Each subcommand is spawned once with valid args (exit 2 with 'not implemented yet' stderr) and once with invalid args (commander rejects with status > 0). Also covers --version, --help, docs --help, and the global --corpus flag. Verified locally: pnpm --filter @deessejs/package-cli type-check -> clean pnpm --filter @deessejs/package-cli lint -> clean pnpm --filter @deessejs/package-cli test:run -> 22 passed prettier --check apps/cli/** docs/cli/** -> clean vale --config .vale.ini docs/cli/ -> 0 errors This lands Phase 0's scaffold (commit 2 of 3) plus the typed skeleton for all six subcommands. Phase 1 (corpus loader), Phase 2 (read commands), and Phase 3 (search) will follow on separate branches; each one will land the corresponding implementation in apps/cli/src/ without needing further doc changes unless reality forces a contract correction.
Coverage Report for @deessejs/example coverage (packages/example)
File CoverageNo changed files found. |
commit: |
The Publish Dry Run job inspects the tarball that would ship to npm.
Until this commit it only ran against @deessejs/example, so a broken
`files` allowlist or `exports` map on @deessejs/package-cli would
slip past CI and only fail at the real release.
Add a second `npm pack --dry-run` invocation for the CLI. Both
packages now go through the same pre-release gate.
Verified locally:
pnpm --filter @deessejs/package-cli exec npm pack --dry-run
-> 58 files, 10.4 kB tarball, contents match the
bin/ + dist/ + package.json allowlist.
Until this commit pkg.pr.new published only @deessejs/example. Every PR now also produces a preview tarball for the CLI, so contributors can install the in-progress build with: npm i https://pkg.pr.new/<owner>/<repo>/@deessejs/package-cli@<sha> The build step is split per workspace to keep the dependency order explicit. pkg-pr-new publish is invoked once per package; that gives each one its own URL on pkg.pr.new and its own install command on the PR comment.
…shes
The previous attempt ran two `pkg-pr-new publish` invocations in the
same job. The first one succeeded; the second one failed with:
{"url":"/check","statusCode":404,"statusMessage":"Not Found",
"message":"There is no workflow defined for HxC6z2DNtL"}
pkg.pr.new ties each upload to a single workflow run, and a single
job run can only register one upload. Trying to register a second
one in the same job returns 404 on the /check endpoint.
Fix: split the workflow into two independent jobs, one per package.
Each job does its own install + build + publish. They run in
parallel and each one registers exactly one upload with pkg.pr.new.
Cost: ~30s of duplicate install + cache hit on both runners. Acceptable
for the gain of working preview releases for both packages.
…th publishes" This reverts commit cef8102.
The pkg.pr.new README is explicit:
> Should be run one time for all the desired packages
pnpm exec pkg-pr-new publish './packages/A' './packages/B'
Two separate `pkg-pr-new publish` invocations in the same workflow
run fail with:
{"url":"/check","statusCode":404,"statusMessage":"Not Found",
"message":"There is no workflow defined for ..."}
regardless of whether they're sequential steps in one job or two
parallel jobs. The service ties one upload token per workflow run
and rejects the second attempt.
Fix: collapse the two `publish` steps into one invocation that takes
both paths. pkg.pr.new generates two URLs (one per package) and posts
them as a single comment with both install commands.
This commit supersedes f8891f3 (sequential steps) and cef8102 (two
parallel jobs), both of which fail.
pkg.pr.new defaults to `pnpm add` in its install comment, which is correct for libraries but wrong for binaries. `@deessejs/package-cli` has a `bin` field in its package.json; the comment should advertise `pnpm dlx` (or `npx`) so users don't have to install a binary they only want to invoke. The pkg.pr.new README documents `--bin` for this exact case: > For CLI applications you might want to show npx instead of npm i > for the preview command. This can be accomplished with the --bin flag `--bin` is a global flag and cannot be scoped to a single path in a multi-path invocation, so the workflow splits back into two jobs. The previous multi-path commit (6a3443a) addressed a different 404 ("no workflow defined") that came from a single job doing two `publish` invocations. Two jobs in parallel is what pkg.pr.new expects for two packages: one upload per workflow run per job. If pkg.pr.new's /check endpoint rejects the second job with the same 404, fall back to the multi-path single invocation and accept `pnpm add` for the CLI. Tracked upstream if needed.
This reverts commit ec79cf5.
The pkg.pr.new README documents `--bin` for showing `npx` instead of `npm i`, but until PR #524 (merged Jul 24, 2026) the flag was global. With one CLI binary and one library in the same multi-path invocation, a global `--bin` would render `npx` for both, which is wrong for the library. PR #524 added per-package scoping: `--bin pkg-a,pkg-b` marks only the named packages as binaries. The server now picks `npx` or `pnpm add` per package instead of per request. Apply `--bin '@deessejs/package-cli'` so the package comment in the PR shows: pnpm add .../@deessejs/example pnpm dlx .../@deessejs/package-cli instead of two `pnpm add` lines. Source: stackblitz-labs/pkg.pr.new issue #396, PR #524.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three commits that land the CLI's design contract and its Phase 0 implementation, in order:
docs/cli/*: the architecture documents (02-design.md,03-architecture.md,04-corpus.md,05-testing.md,06-roadmap.md, plus per-subcommand specs undercommands/).apps/cli/*scaffold:@deessejs/package-cliworkspace that builds, lints, type-checks, and exposes the bin.not implemented yetInternalError.This PR is independent of any other open PR. It branches directly off
origin/stagingand contains everything needed for the CI to go green on its own.Why a single PR
The user instruction was that documentation and implementation belong in the same PR. The doc in
docs/cli/is the contract thatapps/cli/must satisfy; splitting them across PRs lets one drift from the other.What's added