Skip to content

Commit dabd1c5

Browse files
fix(cli): drop the never-loaded oclif.plugins entries and correct the text that says os plugins works (#21306)
Fixes #21285 Clause-②: no ## What this does `packages/cli/package.json` listed `@oclif/plugin-help` and `@oclif/plugin-plugins` under `oclif.plugins`, but both were only `devDependencies`. oclif loads an `oclif.plugins` entry only when the same name is in `dependencies`, so neither ever loaded. This PR: - removes the `oclif.plugins` array (no other `oclif` key changes); - removes the two `devDependencies` (no consumer remains, see H1) and regenerates `pnpm-lock.yaml` with `pnpm install --lockfile-only`; - corrects every published text that described the array or said `os plugins` works (per-site table below); - rewrites `test/plugin-commands.test.ts` so it pins the new state instead of the dead array; - adds `.changeset/21285-drop-dead-oclif-plugins.md` (`patch`, `@objectstack/cli`). Maintainer ruling (verbatim): > 同意:`oclif.plugins`: 里面那两个插件只装在 devDependencies,所以从来没加载过,`os help` 和 `os plugins` 都不是可用命令。我建议删掉这两条配置。 `packages/spec/**` is untouched. That includes `cli-extension.zod.ts` and its generated page `content/docs/references/kernel/cli-extension.mdx`, which still say `os plugins install`. The spec-lane card #21286 carries them, and it remains open. ## Behaviour: nothing an operator sees changes Reading script: `node packages/cli/bin/run.js` with `NODE_ENV` unset and `OCLIF_COLUMNS=120`, run from an empty directory. Each run's stdout, stderr and exit code were captured. The runs: - `--help`, `help`, `plugins`, `plugins install @acme/plugin-marketplace` and `frobnicate`; - `TOPIC --help` for each of the 32 root-level topics; - an `@oclif/core` `Config.load` dump: loaded plugins, all 65 command ids and all 75 topics. That is 114 files per reading. | Reading | Tree | Result | |---|---|---| | before | base `748b24072`, unmodified | `os --help`: 12 topics + 22 commands. `help`, `plugins` and `plugins install` exit 2 with `command ... not found`. Plugins loaded: `@objectstack/cli` only. | | **positive control** | base, with the two plugins added to `dependencies` (mutation through `scripts/ablation-replace.mjs`, restore proven: blob == HEAD, `git diff HEAD` empty) | **differs** in 9 files, plus 6 new ones. `os help` exits 0. `os plugins` exits 0 ("No plugins installed."). The command table gains `help` and 10 `plugins:*` ids (65 to 76). The root help gains the `plugins` topic and the `help` and `plugins` commands. So the reading catches a real difference. | | after | `cc13e2532` (array and devDependencies removed, lockfile regenerated) and `a593c8c62` (CLI rebuilt) | `diff -r` against before: **empty**, all 114 files, same sha256 over the concatenation (`338e1f0f...5c6f38`) | | final heads | `3c8442fc0` and `a1e72918c` (after merging main) | identical, except the two version strings `17.5.0` to `17.6.0`. Those come from main's Version Packages merge, not from this diff. | ## Hypotheses - **H1 (no consumer): holds. Both devDependencies are removed.** `git grep` finds `plugin-help` and `plugin-plugins` only in these places: - the array and the `devDependencies` block; - comments in `bin/run.js`, `doctor.ts` and `doctor-deprecation-hint-commands.test.ts`; - docs text; - the one test assertion. No import, `require`, script, fixture or other importer names them. In the lockfile, only the `packages/cli` importer referenced them. - **Lockfile comparison**, measured against both merge bases (`748b24072` and `5a9292e6f`), with the same result: - 14 package entries and 14 snapshots are removed and 0 added. Every removed entry is in the transitive closure of the two plugins: `@oclif/plugin-help@7.0.2`, `@oclif/plugin-plugins@7.0.3`, `hosted-git-info@7.0.2`, `isexe@3.1.5`, `lru-cache@10.4.3`, `npm@11.21.0`, `npm-package-arg@11.0.3`, `npm-run-path@5.3.0`, `object-treeify@4.0.1`, `path-key@4.0.0`, `proc-log@4.2.0`, `validate-npm-package-name@5.0.1`, `which@4.0.0` and `yarn@1.22.22`. - All 1379 kept snapshots and packages are byte-identical. - **DOWN count: 0.** Four names lose only a second, plugin-only version: `isexe` 3.1.5, `lru-cache` 10.4.3, `path-key` 4.0.0 and `which` 4.0.0. The versions every other consumer resolves are unchanged. - **H2 (only `dependencies` count): holds.** `@oclif/core` 5.1.2 `lib/config/plugin-loader.js` `loadCorePlugins` calls `findMatchingDependencies(rootPlugin.pjson.dependencies ?? {}, corePlugins)`. Measured three ways: - the before/after identity above; - the positive control above; - a standalone fixture root on 5.1.2: with `@acme/plugin-marketplace` listed in `oclif.plugins` plus `dependencies`, `marketplace:search` loads and runs. Moved to `devDependencies`, nothing loads. - **H3 (the site list is complete): holds, with no new site.** The PM's grep was re-run, then widened to `plugins install/uninstall/update/link/...` in space and colon forms, every `@oclif/plugin-*`, and `os|objectstack plugins|help`. Every hit is accounted for in the table below. The widened spellings found only three things beyond the card's sites: `bin/run.js:87` (the `plugins link` sentence, covered with the run.js site), the phrase "ObjectStack plugins" (prose, not a command), and one `CHANGELOG.md` line. - **H4 (the build-your-own-distribution route stays true): holds.** Decided from the loader, not the old text. Fixture distribution roots on `@oclif/core` 5.1.2 `Config.load`: - a root listing `@acme/plugin-marketplace` in both `oclif.plugins` and `dependencies` loads `marketplace:search` and runs it; - a root listing both `@objectstack/cli` and the extension that way loads 66 commands (this CLI's 65 plus `marketplace:search`); - devDependencies-only loads nothing. ## Per-site conclusions | Site | Conclusion | |---|---| | `packages/cli/package.json` `oclif.plugins` + 2 `devDependencies` | **Changed**: removed. | | `pnpm-lock.yaml` | **Changed**: regenerated by the tooling, comparison above. | | `content/docs/plugins/index.mdx` Step 3 callout (lines 400-408) | **Changed**. Its reason ("`plugin-plugins` sits in devDependencies") became false. It now says: no plugin manager; `os plugins ...` and `os help` are not commands; `os --help` is the help entry; the distribution route, kept because H4 holds; and the loader's `dependencies`-only rule. | | same page, "Once loaded, the new commands appear in `os --help`" | **Already true**: true of the distribution's own `os --help` (H4 fixture). | | `packages/cli/README.md` `### os plugins (oclif)` | **Changed**. Now `### os plugins and os help (not commands)`: no plugin manager; each exits 2; use `os --help`; link to the plugin-system section. | | `packages/cli/README.md` `## oclif Plugin System` (intro, step 3, "Install and use", comparison row) | **Changed**. `os plugins install` is gone. Step 3 and the example load through an `os` distribution listing the plugin in `oclif.plugins` + `dependencies`. | | `packages/cli/bin/run.js:84-97` | **Changed**. The reasoning now names "no plugin manager, no `oclif.plugins`, no `@oclif/plugin-plugins` dependency" instead of the devDependencies placement. The 34-entry count is re-measured (12 topics + 22 commands). | | `packages/cli/src/commands/doctor.ts:2377-2380` | **Changed** (comment): "no plugin supplies one (the package declares no `oclif.plugins`)". | | `packages/cli/src/commands/doctor-deprecation-hint-commands.test.ts:15` | **Changed** (comment), same restatement. | | `packages/cli/test/plugin-commands.test.ts` | **Rewritten** to pin the new state. `oclif.plugins` is undefined, and no `@oclif/plugin-*` package appears in any dependency field. The command-discovery and bin pins are kept. The guard is not deleted (reverse verification below). | | `docs/qa/platform-checklist/areas/cli.json:658` (`cli.flag-command-error-ux`) | **Changed**. The source line now reads "no oclif.plugins at all, no plugin manager, no help command and no not-found plugin, so unknown commands hard-error". Revision 1 to 2, with a history entry. `pnpm check:platform-checklist` is green. | | `packages/spec/src/kernel/cli-extension.zod.ts:18` and `content/docs/references/kernel/cli-extension.mdx:21` | **Out of scope**. Spec-lane, carried by #21286, untouched as the card directs. | | `packages/cli/CHANGELOG.md:1362` (the "`plugins link`ed TypeScript plugin ... `@oclif/plugin-plugins` sits in `devDependencies`" entry) | **Out of scope**. Release-owned, and true of the version it shipped with. Not edited in a code PR. | | "ObjectStack plugins" hits (`content/docs/ai/skills.mdx`, `api/error-handling-server.mdx`, `plugins/development.mdx`, `packages/core/src/types.ts`, `packages/types/README.md`, `skills/objectstack-platform/references/plugin-hooks.md`), root `CHANGELOG.md:1367` | **Not a site**: a case-insensitive match on prose, not an `os plugins` command. | ## Tests and gates (final head `a1e72918c` unless noted) - Gates: `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` at `a1e72918c` derives **106** commands. All 106 were run with each exit code captured before any pipe, and **all 106 exit 0**. `--ran` reconciles them: `106 derived famil(ies) accounted for, 106 run, 0 NOT-MEASURED`. - The same 106 were also all green at `3c8442fc0`. - At `a593c8c62`, 4 needed a re-run. Three refused with exit 3 (`PREREQUISITE NOT MET`) before the packages they read were built: `check:skill-examples`, `check:dual-build-cjs-loads` and `check:i18n-coverage`. `check:slot-lookup` hit an ENOENT on a temp fixture that a concurrent CLI test deleted. All 4 were green on re-run. - `@objectstack/cli` unit tier (`vitest run --project unit --maxWorkers=2`): **243 files and 3440 tests passed** at `3c8442fc0`, and again at `a593c8c62`. The two edited test files were re-run at `a1e72918c`: 2 files and 16 tests passed. The last merge (`3c8442fc0` to `a1e72918c`) brought 4 main commits, none of which touch `packages/cli`. - Reverse verification on the rewritten test, with the fix committed. Each mutation went through `scripts/ablation-replace.mjs` (anchor hit, blob changed) with its restore proven (blob == HEAD, `git diff HEAD` empty): - re-adding the `oclif.plugins` array: `declares no oclif.plugins` goes **red** (`expected [ '@oclif/plugin-help', ... ] to be undefined`); - adding `@oclif/plugin-plugins` to `devDependencies`: `depends on no @oclif/plugin-* package` goes **red**. - `pnpm --filter @objectstack/cli typecheck` (at `a593c8c62`): exit 0. `plugin-commands.test.ts` is in `tsconfig.test.json`'s program and `doctor-deprecation-hint-commands.test.ts` in `tsconfig.json`'s (`--listFilesOnly`). - `--project integration` (at `a593c8c62`, run because the diff touches `bin/`): 69/70 files and 601/603 tests pass. The 1 failure is pre-existing: `test/published-entry-node-env-source-reroute.test.ts`, `CONTROL: neutralising the declaration in the child reproduces the card verbatim`. It fails identically on base `748b24072`, built (59/59 turbo cache), in a separate worktree. Cause: tsx 4.23.15's ESM API registers `./esm/index.mjs` relative to `dist/esm/api/index.cjs`. That resolves to the nonexistent `dist/esm/api/esm/index.mjs` (`oclif:config:ts-path` debug: "Could not find tsx. Skipping tsx registration"), so the control's trap never arms. It is unrelated to `oclif.plugins`. The integration tier was not re-run at the final head: the incoming main commits touch no `packages/cli/bin` or `src` file. It is declared to CI. - Lint, narrowed and measured: `eslint --no-inline-config --format json` over the 4 touched JS/TS files reports 4 files, 0 errors and 0 warnings. The other 6 touched files (`.md`, `.mdx`, `.json`, `.yaml`) are outside `eslint.config.mjs`'s `files` globs. The config enables no type-aware linting (no `parserOptions.project`), so this diff cannot move an untouched file's verdict. The full `pnpm lint` is CI's. ## Acceptance notes (not filed here; for the seat) - `packages/cli/README.md` `### Global` says `-v, --version` and `-h, --help`. Measured on the built entry: `os -h` and `os -v` exit 2 with `command -h not found` and `command -v not found`. Only `--help` and `--version` work. This is pre-existing and unrelated to this diff, so it is reported, not fixed. - `packages/cli/README.md` `### Plugin Management` says "There is no `os plugin` command group in v1". `os plugin build|sign|publish` is registered: the `plugin` topic is in `os --help`. This is pre-existing, and #21167 also holds this file, so it is left untouched. - The tsx 4.23.15 ESM-API defect above. It reds that integration control leg on base too, so it will red `packages/cli`'s integration tier wherever that file runs. --- _Generated by [Claude Code](https://claude.ai/code/session_018gA1pE6eJtwHhqx72G8U9X)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 135daaa commit dabd1c5

10 files changed

Lines changed: 101 additions & 244 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/package.json‎

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,6 @@
5353
"hooks": {
5454
"preparse": "./dist/hooks/preparse/strip-arg-separator.js"
5555
},
56-
"plugins": [
57-
"@oclif/plugin-help",
58-
"@oclif/plugin-plugins"
59-
],
6056
"topicSeparator": " "
6157
},
6258
"dependencies": {
@@ -137,8 +133,6 @@
137133
"@objectstack/connector-rest": "workspace:*",
138134
"@objectstack/driver-turso": "workspace:*",
139135
"@objectstack/plugin-dev": "workspace:*",
140-
"@oclif/plugin-help": "^7.0.2",
141-
"@oclif/plugin-plugins": "^7.0.3",
142136
"@types/better-sqlite3": "^7.6.13",
143137
"@types/node": "^26.6.3",
144138
"typescript": "^6.0.3",

‎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)