# Root Platform CLI (`rp`)

TypeScript CLI for building and deploying Root product/collection modules. Commands live in `src/actions/`, shared logic in `src/helpers/`, error types in `src/errors/`, and every command is registered in `src/index.ts`.

The rules below apply to **any new command and any change to an existing command**. They exist so `rp` output stays consistent and CI-safe; each rule reflects how the current commands (`push`, `pull`, `publish`, `invoke`, …) already behave.

## Command registration

- Register commands in `src/index.ts` with `.action(actionRunner(fn))`. `actionRunner` provides version checks, usage logging, and routes thrown errors to `actionErrorHandler` — never bypass it or add your own top-level try/catch that swallows errors.
- Option flags follow `-x, --long <arg>`; destructive commands take `-f, --force`. Internal commands use `{ hidden: true }`.

## Spinners (`src/helpers/spinner.ts`)

- Wrap every async/network step in `runWithSpinner('Doing X...', fn)`. It auto-succeeds on resolve and, on reject, fails the spinner with the error's first line and rethrows.
- Use manual `createSpinner()` + `.start()` / `.succeed(msg)` / `.fail()` only when you need custom success text or to wrap a `Promise.all` (see `src/actions/publish.ts`).
- Spinner text is present-progressive and ends with `...` (`'Fetching draft version...'`). Explicit success text is past tense (`'Validation complete'`).

## Symbols (`src/helpers/symbols.ts`)

- Prefix status lines with `symbols.*` (`info`, `check`, `success`, `warning`, `error`, `disabled`, `pointer`, …). Each includes its trailing space.
- **Never hardcode emoji or unicode glyphs** in output — `symbols.*` falls back to ASCII (`[ok]`, `[!]`) on legacy Windows terminals and piped/CI output.

## Chalk color semantics

- `chalk.green` — success/completion lines.
- `chalk.blue` — identifiers, module keys, versions, links.
- `chalk.yellow` — warnings before destructive actions; filenames and `rp <cmd>` strings echoed inside messages.
- `chalk.red` — reserved for the central error handler in `src/index.ts`; action code should rarely use it directly.
- `chalk.gray` — secondary/de-emphasised detail.

## Errors (`src/errors/platform-error.ts`)

- Local/user errors: `throw new CLIError(message, ExitCodes.GENERAL_ERROR)` — rendered under a red **CLI Error** header.
- API responses: let `PlatformError` surface (helpers use `throwResponseErrors: true`). If a specific API error has an obvious user action, add a mapping in `PlatformError.getSuggestion` so the output ends with a `Tip:` line.
- Connection failures: `NetworkError` (already thrown by the API helper).
- Never `console.log` an error and continue, and never print raw error objects — throw, and let `actionErrorHandler` format and exit.

## Exit codes

- **Failure paths must exit non-zero.** Never `process.exit(0)` after printing an error — a missing-terms.pdf path once did this and let CI publish stale drafts. Throw a `CLIError` instead.
- `ExitCodes`: `SUCCESS = 0`, `GENERAL_ERROR = 1`, `VERSION_MISMATCH = 2`. If a command computes its own status (e.g. test runners), set `process.exitCode` rather than calling `process.exit`.

## Confirmation prompts and `--force`

- Read force as `const forceX = !!options.force;` and guard the prompt with `if (!forceX) { ... }` — `--force` skips prompts only, never validation.
- Print a `chalk.yellow` warning describing the consequences **before** the prompt. Prompts use `ask.yesNo` (`src/helpers/readline-helper.ts`) and end with `(y/n)? `.
- On "no": print `\nAborting <verb>. When you are ready, run the "rp <cmd>" command again.` and `return` (do not throw — declining is not an error).

## Pre-checks

- Detect "nothing to do / user error" states early — before the confirm prompt and before any mutation — and throw a `CLIError` naming the next command in backticks. Example: `rp publish` catches a missing draft during its version fetch and says `` Run `rp push` … then retry `rp publish` `` instead of surfacing a raw 404 (`src/actions/publish.ts`).

## Message format

- Final success line: green, past tense, single-quoted identifier, version detail, prefixed with `\n` to separate from spinner output: `Product module 'x' published successfully. Live version is now 3.0.0, new draft version is 3.1.0.`
- Multi-line local errors/warnings build an array joined with `'\n'`: title, `File: <yellow path>` context, blank line, then a `Tip:` line (see `src/helpers/read-product-module-definition.ts`).
- `Tip:` lines are user-actionable and reference commands as `` `rp <cmd>` ``; continuation lines are indented five spaces to align under the text.

## Tests

- Every new/changed command gets `src/actions/__tests__/<cmd>.test.ts`: mocha + sinon + chai, `describe('rp <cmd>')`, `sinon.createSandbox()` with `afterEach(() => sandbox.verifyAndRestore())`.
- Stub `readAuthAndConfig` (fixture config from `test-utils/test-root-funeral-pm`) and the helper modules the action calls; `expect` comes from `test-utils`, not chai directly.
- Assert the messaging contract, not just behavior: error is `instanceOf CLIError` with the right `exitCode`, message contains the identifier and the `Tip`/next-command, and side-effect stubs are `notCalled` on abort/pre-check paths (see `src/actions/__tests__/publish.test.ts`).
- Run with `npm test` (compiles then runs mocha over `dist`).

## Version bumps (required on every PR)

- Every PR to `main` must increase `package.json`'s `version` — the "Version bump check" CI job fails otherwise, with no opt-out. Bump with `npm version <patch|minor|major> --no-git-tag-version` (keeps `package-lock.json` in sync; never hand-edit), committed on its own as `⚙️ Bumped version to X.Y.Z`.
- patch = bug fixes / internal changes · minor = backwards-compatible features · major = breaking changes. State the chosen level and why in the PR's "Version bump & rationale" section — a bump without rationale is incomplete.
- If `main`'s version catches up while a PR is open, merge/rebase `main` and bump again — the check requires strictly greater, not just changed.
