Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .changeset/cli-auth-command-examples-resolve.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
---
"@objectstack/cli": patch
---

fix(cli): `register`/`whoami`/`logout` examples no longer spell `os auth <cmd>` in live `--help` output (#11221)

`static override examples` is printed verbatim as part of oclif's `--help`. `register.ts`,
`whoami.ts` and `logout.ts` live at the **root** of `packages/cli/src/commands/`, so oclif's
pattern-strategy loader registers them as `register` / `whoami` / `logout` — but their
`examples` spelled an `os auth <cmd>` shape that has never resolved. A user copy-pasting
straight out of `--help` hit `Error: Command auth:whoami not found.` (exit 2), the same dead
command #10927 fixed in `packages/cli/README.md` and #10967 fixed for the `environments`
topic, this time on the root auth-family commands.

Measured against the built CLI (`packages/cli/bin/run.js`) before the fix: `os auth whoami`,
`os auth register` and `os auth logout` each exited 2 with `Error: Command auth:<cmd> not
found.`, while the bare `os whoami` / `os register` / `os logout` each exited 0 and printed
help — so the examples named the one spelling that could not work. All seven `examples`
entries now say the bare, registered spelling.

The exported default class on each file is renamed to match its real, file-path-derived
command id (`AuthRegister` → `Register`, `AuthWhoami` → `Whoami`, `AuthLogout` → `Logout`).
oclif derives a command's id purely from its file path, never from the class name, so this
changes no runtime resolution — confirmed by rebuilding the CLI and re-running `--help` on
all three. The rename also brings them onto this package's measured convention: every other
root-level command class is exactly the PascalCase of its filename. `login.ts` keeps
`AuthLogin` — its `examples` were already correct (`$ os login`), so it is outside this
card's file surface; that lone remaining class-name holdout is reported, not swept.

`environments.test.ts`'s `#10967` pin carried a deliberately self-retiring `EXCLUDED` entry
for each of these three files, asserting the defect was *still present* so the exemption
could not outlive its cause. This fix removed the last unresolved entry, that assertion went
red exactly as designed, and the three entries are retired — the map is now empty and all
three files are scanned by the main assertion like every other command source.
20 changes: 20 additions & 0 deletions .changeset/cli-readme-drop-os-studio.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
---
"@objectstack/cli": patch
---

docs(cli): drop the `os studio` row from the README command table — the CLI ships no such command (#11180)

`packages/cli/README.md`'s **Development** command table listed
`` | `os studio [config]` | Launch Studio UI with development server | ``. The CLI has no
`studio` command and has not had one: the oclif command set is pattern-derived from
`packages/cli/src/commands/**`, and loading the built CLI's own `Config` enumerates 60
registered ids with **zero** matching `studio` (control: `dev`, `serve`, `login`, `logout`,
`register`, `whoami` are all present in the same enumeration, so the check is not vacuous).
Running it confirms the same from the outside — `os studio --help` exits 2 with
`Error: Command studio not found.`

The row is deleted rather than rewritten. Studio is not reached by a CLI command at all —
it is served by the console at `/_console/studio` after `os dev` or `os serve`, both of
which the same table already lists — so a replacement row would reintroduce the category
error that made this one wrong: a Commands table is a list of commands, and a browser route
is not one.
1 change: 0 additions & 1 deletion packages/cli/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,7 +42,6 @@ os compile
| `os init [name]` | Initialize a new ObjectStack project in the current directory |
| `os dev [package]` | Start development mode with hot reload |
| `os serve [config]` | Start the ObjectStack server with plugin auto-detection |
| `os studio [config]` | Launch Studio UI with development server |

### Build & Validate

Expand Down
46 changes: 25 additions & 21 deletions packages/cli/src/commands/environments/environments.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -151,23 +151,27 @@ describe('os environments commands', () => {
* back" (AGENTS.md, Route & surface ownership §3): a shape this pin cannot
* parse is exactly the shape that could hide a stale command undetected.
*
* ## The one exclusion, and why it must self-retire
* ## The one exclusion, and why it self-retired
*
* `register.ts` / `whoami.ts` / `logout.ts` are root-level commands whose
* `examples` say `os auth register` / `os auth whoami` / `os auth logout`,
* though no `auth` topic has ever existed for them (confirmed via
* `examples` USED TO say `os auth register` / `os auth whoami` / `os auth
* logout`, though no `auth` topic has ever existed for them (confirmed via
* `--help`: `Error: Command auth:whoami not found.`) — the same defect
* class as #10967, found by scanning the whole tree, but not #10967's to
* fix (outside its dispatched file surface). Filed as #11221. `EXCLUDED`
* carves exactly those three files out of the main assertion below, but a
* silent, permanent exemption is its own defect — a file excluded here
* stops being checked by this pin forever, even after #11221 lands and the
* excluded condition no longer holds. So a second `it.each` re-runs the
* SAME predicate over the excluded files and asserts it still finds an
* unresolved entry: when #11221's fix removes the last one, that assertion
* goes red on purpose, and the failure message says to delete the entry.
* The pattern (map-of-reason + filtered main assertion + a "still needs
* its exclusion" retiring assertion) matches
* fix (outside its dispatched file surface). Filed as #11221 and fixed
* there, so `EXCLUDED` is now empty and all three are scanned by the main
* assertion like every other command source.
*
* The mechanism stays, because it is what made that handoff safe: a silent,
* permanent exemption is its own defect — a file excluded here stops being
* checked by this pin forever, even after the excluded condition no longer
* holds. So a second `it.each` re-runs the SAME predicate over the excluded
* files and asserts it still finds an unresolved entry. That is not
* hypothetical here: when #11221's fix removed the last unresolved entry,
* this assertion went red on purpose for all three files, and its message
* ("remove it from EXCLUDED above") is what retired them. The pattern
* (map-of-reason + filtered main assertion + a "still needs its exclusion"
* retiring assertion) matches
* `packages/create-objectstack/src/starter-comments-self-contained.test.ts`'s
* `EXCLUDED`, which has retired this same way before (#11022).
*/
Expand DownExpand Up@@ -287,15 +291,15 @@ describe('#10967 pin: examples resolve to a real command id', () => {
}

/**
* Files with a KNOWN, currently-live instance of this defect class this
* card does not own the fix for (#11221). The retiring assertion below
* proves each entry is still load-bearing, not decorative.
* Nothing is excluded — every command source is scanned. `register.ts` /
* `whoami.ts` / `logout.ts` each carried a self-retiring entry here while
* their `os auth …` examples were #11221's to fix; that fix landed, the
* retiring assertion below went red exactly as designed, and the map goes
* back to empty rather than staying around as a silent exemption over three
* root-level commands. The assertion stays, so the next entry added here is
* held to the same self-retirement.
*/
const EXCLUDED = new Map<string, string>([
['register.ts', '#11221: examples say `os auth register`, no `auth` topic exists'],
['whoami.ts', '#11221: examples say `os auth whoami`, no `auth` topic exists'],
['logout.ts', '#11221: examples say `os auth logout`, no `auth` topic exists'],
]);
const EXCLUDED = new Map<string, string>();

const sourceFiles = commandSourceFiles();
const registeredIds = registeredCommandIds();
Expand Down
6 changes: 3 additions & 3 deletions packages/cli/src/commands/logout.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,11 +5,11 @@ import { printHeader, printSuccess, printError, emitJson } from '../utils/format
import { deleteAuthConfig, readAuthConfig } from '../utils/auth-config.js';
import { ObjectStackClient } from '@objectstack/client';

export default class AuthLogout extends Command {
export default class Logout extends Command {
static override description = 'Clear stored authentication credentials';

static override examples = [
'$ os auth logout',
'$ os logout',
];

static override flags = {
Expand All@@ -19,7 +19,7 @@ export default class AuthLogout extends Command {
};

async run(): Promise<void> {
const { flags } = await this.parse(AuthLogout);
const { flags } = await this.parse(Logout);

try {
if (!flags.json) {
Expand Down
10 changes: 5 additions & 5 deletions packages/cli/src/commands/register.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,13 +59,13 @@ async function promptPassword(promptText: string): Promise<string> {
});
}

export default class AuthRegister extends Command {
export default class Register extends Command {
static override description = 'Create a new account and store credentials';

static override examples = [
'$ os auth register',
'$ os auth register --email user@example.com --name "Jane Doe" --password mypassword',
'$ os auth register --url https://api.example.com',
'$ os register',
'$ os register --email user@example.com --name "Jane Doe" --password mypassword',
'$ os register --url https://api.example.com',
];

static override flags = {
Expand DownExpand Up@@ -93,7 +93,7 @@ export default class AuthRegister extends Command {
};

async run(): Promise<void> {
const { flags } = await this.parse(AuthRegister);
const { flags } = await this.parse(Register);

try {
if (!flags.json) {
Expand Down
10 changes: 5 additions & 5 deletions packages/cli/src/commands/whoami.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,13 +5,13 @@ import { printHeader, printError, printKV, emitJson } from '../utils/format.js';
import { createApiClient, requireAuth } from '../utils/api-client.js';
import { formatOutput } from '../utils/output-formatter.js';

export default class AuthWhoami extends Command {
export default class Whoami extends Command {
static override description = 'Show current session information';

static override examples = [
'$ os auth whoami',
'$ os auth whoami --format json',
'$ os auth whoami --url https://api.example.com --token <token>',
'$ os whoami',
'$ os whoami --format json',
'$ os whoami --url https://api.example.com --token <token>',
];

static override flags = {
Expand All@@ -34,7 +34,7 @@ export default class AuthWhoami extends Command {
};

async run(): Promise<void> {
const { flags } = await this.parse(AuthWhoami);
const { flags } = await this.parse(Whoami);

try {
const { client, token } = await createApiClient({
Expand Down
Loading