Skip to content

Commit 4a532ac

Browse files
committed
docs(cli): say what is true now that the CLI ships no plugin manager
The README, the plugins docs callout, the bin entry's auto-transpile note, the doctor comment, the platform checklist and the plugin-surface test all described `oclif.plugins` or an `os plugins` command group. Restate each to the new fact: no `oclif.plugins`, no plugin manager, `os --help` is the help entry, and a CLI extension loads through an `os` distribution that lists it in both `oclif.plugins` and `dependencies`. The test now pins that state instead of the dead array. Adds the patch changeset. Claude-Session: https://claude.ai/code/session_018gA1pE6eJtwHhqx72G8U9X Co-authored-by: Claude <noreply@anthropic.com>
1 parent cc13e25 commit 4a532ac

8 files changed

Lines changed: 101 additions & 53 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@objectstack/cli': patch
3+
---
4+
5+
The published `package.json` no longer declares `oclif.plugins`, and the package no longer lists `@oclif/plugin-help` or `@oclif/plugin-plugins` as devDependencies. The array named both plugins, but they were only devDependencies, and oclif loads an `oclif.plugins` entry only when the same name is in `dependencies`. Neither plugin ever loaded.
6+
7+
Clause-②: no
8+
9+
**What changes for an operator.** Nothing. `os --help`, every command and topic, and the output of `os help` and `os plugins` read byte-identical before and after the change. `os help` and `os plugins …` were never commands, and each still exits 2 with `command … not found`. Use `os --help` or `os <command> --help` for help.
10+
11+
**What the README now says.** It said `os plugins install`, `uninstall` and `update` came from `@oclif/plugin-plugins` and installed CLI extensions. That was never true. This CLI ships no plugin manager. To add commands to it, build an `os` distribution: a package whose own `package.json` lists the extension in both `oclif.plugins` and `dependencies`.

‎content/docs/plugins/index.mdx‎

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -399,13 +399,15 @@ export default class MarketplaceSearch extends Command {
399399
#### Step 3: Load the Plugin into the CLI
400400

401401
<Callout type="warn">
402-
**`os plugins install` is not available today.** `@objectstack/cli`'s `package.json`
403-
lists `@oclif/plugin-plugins` under `oclif.plugins`, but the package sits in
404-
`devDependencies` — and oclif's core-plugin loader only matches names that appear in
405-
`dependencies`. The result is that neither `os plugins …` nor `os help` is a
406-
registered command; `os --help` shows no `plugins` topic. Until that is fixed, load a
407-
CLI extension by building an `os` distribution that lists the package in **both**
408-
`oclif.plugins` and `dependencies`.
402+
**`@objectstack/cli` ships no plugin manager.** Its `package.json` declares no
403+
`oclif.plugins`, and it does not depend on `@oclif/plugin-plugins` or
404+
`@oclif/plugin-help`. So `os plugins …` (`install`, `link`, `update`, …) and `os help`
405+
are not commands: each exits 2 with `command … not found`. `os --help` is the help
406+
entry, and it shows no `plugins` topic. To load a CLI extension, build an `os`
407+
distribution: a package whose own `package.json` lists the extension in **both**
408+
`oclif.plugins` and `dependencies`. oclif's core-plugin loader matches `oclif.plugins`
409+
names only against `dependencies`, so a name listed under `devDependencies` alone never
410+
loads.
409411
</Callout>
410412

411413
#### Using the Extended CLI

‎docs/qa/platform-checklist/areas/cli.json‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -574,7 +574,7 @@
574574
"title": "Wrong flags and unknown commands error with usage and a nonzero exit — never silently ignored, never executed anyway",
575575
"since": "v16",
576576
"status": "active",
577-
"revision": 1,
577+
"revision": 2,
578578
"priority": "P2",
579579
"surface": "cli",
580580
"personas": ["operator (local shell)"],
@@ -655,11 +655,12 @@
655655
],
656656
"source": [
657657
"packages/cli/src/commands/ (the 20 top-level commands + 10 topics enumerated as variants — oclif pattern discovery per package.json oclif.commands)",
658-
"packages/cli/package.json (oclif.plugins = help + plugins only — no not-found plugin, so unknown commands hard-error)",
658+
"packages/cli/package.json (no oclif.plugins at all — no plugin manager, no help command and no not-found plugin, so unknown commands hard-error)",
659659
"@oclif/core parse contract (Nonexistent flag / enum FailedFlagValidation / command-not-found, exit 2)"
660660
],
661661
"history": [
662-
{ "revision": 1, "date": "2026-08-07", "change": "new item: flag/command error UX with the full registered command surface enumerated from src/commands/ as variants, and never-executed-anyway as the load-bearing negative", "ref": "claude/platform-test-checklist-ocwugl" }
662+
{ "revision": 1, "date": "2026-08-07", "change": "new item: flag/command error UX with the full registered command surface enumerated from src/commands/ as variants, and never-executed-anyway as the load-bearing negative", "ref": "claude/platform-test-checklist-ocwugl" },
663+
{ "revision": 2, "date": "2026-10-02", "change": "source: package.json no longer declares oclif.plugins — the two listed plugins sat in devDependencies and never loaded, so the array was dropped; the registered surface and the unknown-command hard-error are unchanged (os --help and the full command table read byte-identical before and after)", "ref": "claude/issue-21285-drop-dead-oclif-plugins" }
663664
]
664665
},
665666
{

‎packages/cli/README.md‎

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -203,9 +203,9 @@ Common variables: `OS_DATABASE_URL`, `OS_DATABASE_DRIVER`,
203203

204204
- `-d, --dir <directory>` — Override target directory
205205

206-
### `os plugins` (oclif)
206+
### `os plugins` and `os help` (not commands)
207207

208-
`os plugins install`, `os plugins uninstall`, `os plugins update`, and friends come from `@oclif/plugin-plugins`. They install third-party CLI extensions (oclif plugins), not runtime plugins for an ObjectStack project. See [oclif's plugin docs](https://oclif.io/docs/plugins) for the full surface.
208+
This package ships no oclif plugin manager: its `package.json` declares no `oclif.plugins` and does not depend on `@oclif/plugin-plugins` or `@oclif/plugin-help`. So `os plugins` (`install`, `uninstall`, `update`, `link`, and the rest) and `os help` are not registered commands; each exits 2 with `command … not found`. Use `os --help` or `os <command> --help` for help. To add third-party CLI commands, see [oclif Plugin System](#oclif-plugin-system).
209209

210210
### `os info`
211211

@@ -218,13 +218,13 @@ Common variables: `OS_DATABASE_URL`, `OS_DATABASE_DRIVER`,
218218

219219
## oclif Plugin System
220220

221-
The CLI uses oclif's built-in plugin system for extensibility. Third-party plugins (e.g., cloud commands, marketplace tools) can extend the CLI without modifying the main package.
221+
The CLI is built on oclif, and oclif's plugin system is its only command-extension mechanism: a third-party package (e.g., cloud commands, marketplace tools) ships oclif Command classes and is loaded as an oclif plugin, without modifying this package. The `os` binary this package publishes declares no plugins and ships no plugin manager, so there is no `os plugins install`.
222222

223223
### How Plugin Extension Works
224224

225225
1. **Create an oclif plugin package** with its own `oclif` config in `package.json`
226226
2. **Export oclif Command classes** from the plugin's `src/commands/` directory
227-
3. **Install the plugin** via `os plugins install <package>` or declare it in the main CLI's `oclif.plugins`
227+
3. **Load the plugin through an `os` distribution you build**: a package whose own `package.json` lists the plugin in **both** `oclif.plugins` and `dependencies`. oclif's core-plugin loader matches `oclif.plugins` names only against `dependencies`; a name listed under `devDependencies` alone never loads.
228228

229229
### Creating a CLI Plugin
230230

@@ -263,18 +263,19 @@ export default class MarketplaceSearch extends Command {
263263
}
264264
```
265265

266-
**3. Install and use:**
266+
**3. Load it through your `os` distribution, then use it:**
267+
268+
List `@acme/plugin-marketplace` in both `oclif.plugins` and `dependencies` of the distribution's `package.json` (see [How Plugin Extension Works](#how-plugin-extension-works)). Its commands then appear in that distribution's `os --help`:
267269

268270
```bash
269-
os plugins install @acme/plugin-marketplace
270271
os marketplace search "crm"
271272
```
272273

273274
### Key Differences from Previous Plugin Model
274275

275276
| Before (Commander.js) | After (oclif) |
276277
|---|---|
277-
| Plugins declared in `objectstack.config.ts` | Plugins installed via `os plugins install` or `oclif.plugins` |
278+
| Plugins declared in `objectstack.config.ts` | Plugins listed in an `os` distribution's `oclif.plugins` and `dependencies` |
278279
| Custom `loadPluginCommands` mechanism | oclif's built-in plugin discovery |
279280
| `contributes.commands` in manifest | `oclif.commands` in `package.json` |
280281
| Commander.js `new Command(...)` exports | oclif `class extends Command` exports |

‎packages/cli/bin/run.js‎

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -86,15 +86,16 @@ import { flush, handle, run, settings } from '@oclif/core';
8686
* (`plugin?.type !== 'link'` guards the `isProduction` early return), and this
8787
* setting is checked ahead of that — so a `plugins link`ed TypeScript plugin
8888
* would no longer be auto-transpiled through this entry. ⭐ That path is not
89-
* reachable today: `@oclif/plugin-plugins` sits in `devDependencies`, and
90-
* oclif's core-plugin loader only matches names under `dependencies`, so
91-
* `os plugins` is not a registered command at all (measured on this entry —
92-
* `os --help` lists 34 topics and none of them is `plugins`; the count is the
93-
* control, so the zero is a reading). `content/docs/plugins/index.mdx` says the
94-
* same in its own words and tells an extension author to build an `os`
95-
* distribution listing the package in both places. ⛔ If that is ever fixed,
96-
* this line is what has to be revisited — the remedy is `bin/run-dev.js`, or
97-
* building the plugin.
89+
* reachable today: this package ships no plugin manager — `package.json`
90+
* declares no `oclif.plugins` and does not depend on `@oclif/plugin-plugins` —
91+
* so `os plugins` (and with it `os plugins link`) is not a registered command
92+
* at all (measured on this entry — `os --help` lists 34 entries, 12 topics and
93+
* 22 commands, and none of them is `plugins`; the count is the control, so the
94+
* zero is a reading). `content/docs/plugins/index.mdx` says the same in its own
95+
* words and tells an extension author to build an `os` distribution listing
96+
* the package in both `oclif.plugins` and `dependencies`. ⛔ If a plugin
97+
* manager is ever shipped, this line is what has to be revisited — the remedy
98+
* is `bin/run-dev.js`, or building the plugin.
9899
*
99100
* The other change in behaviour is a convergence, not a loss: on an UNBUILT
100101
* tree this file now answers oclif's "command not found" under

‎packages/cli/src/commands/doctor-deprecation-hint-commands.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
*
1212
* There is no `codemod` command. oclif resolves this CLI's commands by
1313
* globbing `dist/commands/**` (`package.json` → `oclif.commands`, pattern
14-
* strategy); nothing under `src/commands/` compiles to `codemod`, and neither
15-
* bundled plugin (`@oclif/plugin-help`, `@oclif/plugin-plugins`) supplies one.
14+
* strategy); nothing under `src/commands/` compiles to `codemod`, and no plugin
15+
* supplies one (`package.json` declares no `oclif.plugins`).
1616
* An operator who followed the prescription got oclif's exit 2,
1717
* `command codemod:v2-to-v3 not found` — after spending their time on it.
1818
* `content/docs/protocol/backward-compatibility.mdx` already recorded the

‎packages/cli/src/commands/doctor.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2375,8 +2375,8 @@ export default class Doctor extends Command {
23752375
// #10680 — this line used to prescribe `objectstack codemod v2-to-v3`,
23762376
// a command `os` has never registered. oclif resolves commands by
23772377
// globbing `dist/commands/**/*.js` (package.json `oclif.commands`);
2378-
// there is no `src/commands/codemod*`, and neither bundled plugin
2379-
// (`@oclif/plugin-help`, `@oclif/plugin-plugins`) supplies one — so the
2378+
// there is no `src/commands/codemod*`, and no plugin supplies one (the
2379+
// package declares no `oclif.plugins`) — so the
23802380
// prescription exited 2, `command codemod:v2-to-v3 not found`, for every
23812381
// operator who followed it. `content/docs/protocol/backward-compatibility.mdx`
23822382
// already records the automated codemod as "not yet available".

‎packages/cli/test/plugin-commands.test.ts‎

Lines changed: 55 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,75 @@
11
import { describe, it, expect } from 'vitest';
2+
import { createRequire } from 'node:module';
23

34
/**
4-
* The custom loadPluginCommands mechanism has been removed.
5-
* Plugin command extension is now handled by oclif's built-in plugin system.
5+
* The published `os` binary's oclif plugin surface.
66
*
7-
* Plugins extend the CLI by:
8-
* 1. Including `oclif` config in their package.json
9-
* 2. Exporting oclif Command classes from `src/commands/`
10-
* 3. Being installed via `os plugins install <package>`
7+
* Command extension is handled by oclif's plugin system: a plugin package
8+
* carries its own `oclif` config and exports oclif Command classes from
9+
* `src/commands/`. The host project's `objectstack.config.ts` does not decide
10+
* which CLI commands exist.
1111
*
12-
* The objectstack.config.ts no longer determines CLI command availability.
12+
* This package ships NO plugin manager and loads no plugin of its own. It used
13+
* to list `@oclif/plugin-help` and `@oclif/plugin-plugins` under
14+
* `oclif.plugins` while both sat in `devDependencies`. `@oclif/core`'s
15+
* core-plugin loader (`lib/config/plugin-loader.js`, `loadCorePlugins`)
16+
* matches `oclif.plugins` names only against `dependencies`, so neither ever
17+
* loaded: `os plugins …` and `os help` were never commands, and `os --help`
18+
* read the same with or without the array. The array was dead configuration,
19+
* and every text that read it as "`os plugins install` works" was false.
20+
*
21+
* What these pins hold is the state the published text now describes
22+
* (`README.md` → "`os plugins` and `os help` (not commands)" and "oclif Plugin
23+
* System"; `content/docs/plugins/index.mdx` → the Step 3 callout;
24+
* `bin/run.js` → the `enableAutoTranspile` note on linked plugins). Shipping a
25+
* plugin manager is a product change, not a manifest tweak: it lists the
26+
* plugin in BOTH `oclif.plugins` and `dependencies`, and corrects those texts
27+
* in the same change.
1328
*/
1429

15-
describe('oclif Plugin System', () => {
16-
it('should have oclif plugins configured in package.json', async () => {
17-
const { createRequire } = await import('module');
18-
const require = createRequire(import.meta.url);
19-
const pkg = require('../package.json');
30+
const require = createRequire(import.meta.url);
31+
const pkg = require('../package.json');
32+
33+
const DEPENDENCY_FIELDS = [
34+
'dependencies',
35+
'optionalDependencies',
36+
'peerDependencies',
37+
'devDependencies',
38+
] as const;
2039

40+
describe('oclif plugin surface — the published `os` ships no plugin manager', () => {
41+
it('declares no oclif.plugins (oclif would load an entry only from `dependencies`)', () => {
2142
expect(pkg.oclif).toBeDefined();
22-
expect(pkg.oclif.plugins).toContain('@oclif/plugin-help');
23-
expect(pkg.oclif.plugins).toContain('@oclif/plugin-plugins');
43+
expect(
44+
pkg.oclif.plugins,
45+
'package.json `oclif.plugins` is back. An entry here loads only when the same name is in `dependencies` ' +
46+
'(oclif loadCorePlugins); otherwise it is dead configuration. Either way README.md, ' +
47+
'content/docs/plugins/index.mdx and bin/run.js state that this CLI ships no plugin manager — change them with it.',
48+
).toBeUndefined();
2449
});
2550

26-
it('should have oclif command discovery configured', async () => {
27-
const { createRequire } = await import('module');
28-
const require = createRequire(import.meta.url);
29-
const pkg = require('../package.json');
51+
it('depends on no @oclif/plugin-* package in any dependency field', () => {
52+
const found = DEPENDENCY_FIELDS.flatMap((field) =>
53+
Object.keys(pkg[field] ?? {})
54+
.filter((name) => name.startsWith('@oclif/plugin-'))
55+
.map((name) => `${field}: ${name}`),
56+
);
57+
expect(
58+
found,
59+
'an oclif plugin package is listed. Not named in `oclif.plugins` + `dependencies`, it never loads and is dead ' +
60+
'weight; loaded, it makes `os plugins` / `os help` real and the published text false.',
61+
).toEqual([]);
62+
});
63+
});
3064

65+
describe('oclif command discovery and entry points', () => {
66+
it('discovers commands by pattern under dist/commands', () => {
3167
expect(pkg.oclif.commands).toBeDefined();
3268
expect(pkg.oclif.commands.strategy).toBe('pattern');
3369
expect(pkg.oclif.commands.target).toBe('./dist/commands');
3470
});
3571

36-
it('should have bin entries pointing to oclif runner', async () => {
37-
const { createRequire } = await import('module');
38-
const require = createRequire(import.meta.url);
39-
const pkg = require('../package.json');
40-
72+
it('points both bin entries at the oclif runner', () => {
4173
expect(pkg.bin.os).toBe('./bin/run.js');
4274
expect(pkg.bin.objectstack).toBe('./bin/run.js');
4375
});

0 commit comments

Comments
 (0)