From cc13e2532357edb38b6fe1b86c9737c70be7d2f6 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 2 Oct 2026 02:03:14 +0000 Subject: [PATCH 1/2] fix(cli): drop the never-loaded oclif.plugins entries and their devDependencies `oclif.plugins` named @oclif/plugin-help and @oclif/plugin-plugins, but both sat in devDependencies and oclif's core-plugin loader matches those names only against `dependencies`, so neither ever loaded. Remove the array and the two devDependencies; the lockfile regeneration drops exactly their two subtrees (14 entries, 0 added, no surviving resolution changed). Claude-Session: https://claude.ai/code/session_018gA1pE6eJtwHhqx72G8U9X Co-authored-by: Claude --- packages/cli/package.json | 6 -- pnpm-lock.yaml | 185 -------------------------------------- 2 files changed, 191 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 9d84cdcf337..4245e225fde 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -53,10 +53,6 @@ "hooks": { "preparse": "./dist/hooks/preparse/strip-arg-separator.js" }, - "plugins": [ - "@oclif/plugin-help", - "@oclif/plugin-plugins" - ], "topicSeparator": " " }, "dependencies": { @@ -137,8 +133,6 @@ "@objectstack/connector-rest": "workspace:*", "@objectstack/driver-turso": "workspace:*", "@objectstack/plugin-dev": "workspace:*", - "@oclif/plugin-help": "^7.0.2", - "@oclif/plugin-plugins": "^7.0.3", "@types/better-sqlite3": "^7.6.13", "@types/node": "^26.6.3", "typescript": "^6.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2957d82b79c..28fbb331f82 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -638,12 +638,6 @@ importers: '@objectstack/plugin-dev': specifier: workspace:* version: link:../plugins/plugin-dev - '@oclif/plugin-help': - specifier: ^7.0.2 - version: 7.0.2 - '@oclif/plugin-plugins': - specifier: ^7.0.3 - version: 7.0.3 '@types/better-sqlite3': specifier: ^7.6.13 version: 7.6.13 @@ -4333,14 +4327,6 @@ packages: resolution: {integrity: sha512-vgbWWFKjjsf7oAbJONFPnDkVNeqmy70DTBUjsprW/HRiTrXe8sMlFc0ih8GjpU+dn+m6V2ksaZjBV9HgnsmWVg==} engines: {node: '>=22.0.0'} - '@oclif/plugin-help@7.0.2': - resolution: {integrity: sha512-xqYsSECa7HrO+yb20ukcRzlz86Tq6f7Ft48kp4YLka0/4X5WbehOCSLdKlV8ejdcfK5gQzC4erMNeIXxyF5aXQ==} - engines: {node: '>=22.0.0'} - - '@oclif/plugin-plugins@7.0.3': - resolution: {integrity: sha512-ufjwLdKbw5AC8BjguvRGRBFoSc8RGXV1TVjxMDr8smJuN8QM4yhZIvKCoSVQC24BX71pF75LrvQEWpwQJbwpQA==} - engines: {node: '>=22.0.0'} - '@open-draft/deferred-promise@2.2.0': resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} @@ -6990,10 +6976,6 @@ packages: hookified@2.2.0: resolution: {integrity: sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==} - hosted-git-info@7.0.2: - resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} - engines: {node: ^16.14.0 || >=18.0.0} - html-encoding-sniffer@7.0.0: resolution: {integrity: sha512-UikN5yr7xsCDAq87Or5or0PAlD3HJJOKVzM05az588WnpDJ4Ux7a2A53Qi6gofGg2/EtvF/H4hCi/TXfCW4Y6w==} engines: {node: ^22.13.0 || >=24.0.0} @@ -7166,10 +7148,6 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - isexe@3.1.5: - resolution: {integrity: sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==} - engines: {node: '>=18'} - istanbul-lib-coverage@3.2.2: resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} engines: {node: '>=8'} @@ -7565,9 +7543,6 @@ packages: longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} - lru-cache@10.4.3: - resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@11.5.3: resolution: {integrity: sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==} engines: {node: 20 || >=22} @@ -8031,89 +8006,10 @@ packages: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} - npm-package-arg@11.0.3: - resolution: {integrity: sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==} - engines: {node: ^16.14.0 || >=18.0.0} - - npm-run-path@5.3.0: - resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - npm-to-yarn@3.2.0: resolution: {integrity: sha512-K1HmQeZT2HrjpsR6KgqbN2FAXL2NrJJmNUSD9ck7HGTVu1JKXox8n9SB+tjbU8m8JGLF4OscrroPepew/L7/Xw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - npm@11.21.0: - resolution: {integrity: sha512-Zov8KhamNneiLdELtj5YALtNmJW4L4fCLTzjfpzXG2w6MSHcf0UxgdlK5uloCuksWT+7mGUU7wi79cO6RqivPg==} - engines: {node: ^20.17.0 || >=22.9.0} - hasBin: true - bundledDependencies: - - '@isaacs/string-locale-compare' - - '@npmcli/arborist' - - '@npmcli/config' - - '@npmcli/fs' - - '@npmcli/map-workspaces' - - '@npmcli/metavuln-calculator' - - '@npmcli/package-json' - - '@npmcli/promise-spawn' - - '@npmcli/redact' - - '@npmcli/run-script' - - '@sigstore/tuf' - - abbrev - - archy - - cacache - - chalk - - ci-info - - fastest-levenshtein - - fs-minipass - - glob - - graceful-fs - - hosted-git-info - - ini - - init-package-json - - is-cidr - - json-parse-even-better-errors - - libnpmaccess - - libnpmdiff - - libnpmexec - - libnpmfund - - libnpmorg - - libnpmpack - - libnpmpublish - - libnpmsearch - - libnpmteam - - libnpmversion - - make-fetch-happen - - minimatch - - minipass - - minipass-pipeline - - ms - - node-gyp - - nopt - - npm-audit-report - - npm-install-checks - - npm-package-arg - - npm-pick-manifest - - npm-profile - - npm-registry-fetch - - npm-user-validate - - p-map - - pacote - - parse-conflict-json - - proc-log - - qrcode-terminal - - read - - semver - - spdx-expression-parse - - ssri - - supports-color - - tar - - text-table - - tiny-relative-date - - treeverse - - validate-npm-package-name - - which - object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -8122,10 +8018,6 @@ packages: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} - object-treeify@4.0.1: - resolution: {integrity: sha512-Y6tg5rHfsefSkfKujv2SwHulInROy/rCL5F4w0QOWxut8AnxYxf0YmNhTh95Zfyxpsudo66uqkux0ACFnyMSgQ==} - engines: {node: '>= 16'} - obug@2.1.4: resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} engines: {node: '>=12.20.0'} @@ -8215,10 +8107,6 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} - path-key@4.0.0: - resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} - engines: {node: '>=12'} - path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} @@ -8385,10 +8273,6 @@ packages: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - proc-log@4.2.0: - resolution: {integrity: sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} @@ -9150,10 +9034,6 @@ packages: resolution: {integrity: sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==} hasBin: true - validate-npm-package-name@5.0.1: - resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} @@ -9291,11 +9171,6 @@ packages: engines: {node: '>= 8'} hasBin: true - which@4.0.0: - resolution: {integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==} - engines: {node: ^16.13.0 || >=18.0.0} - hasBin: true - why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} @@ -9398,11 +9273,6 @@ packages: resolution: {integrity: sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==} engines: {node: '>=12'} - yarn@1.22.22: - resolution: {integrity: sha512-prL3kGtyG7o9Z9Sv8IPfBNrWTDmXB4Qbes8A9rEzt6wkJV8mUvoirjU0Mp3GGAU06Y0XQyA3/2/RQFVuK7MTfg==} - engines: {node: '>=4.0.0'} - hasBin: true - yauzl@3.4.0: resolution: {integrity: sha512-jIH9yLR9wqr0wOS0TpBvo/g/2UgZH5qePVbjgRliiF0BYvOZyaBknKsF+x9Iht0O6sqgnB93rCICdOZFecJuDw==} engines: {node: '>=12'} @@ -10773,26 +10643,6 @@ snapshots: wrap-ansi: 7.0.0 wsl-utils: 0.4.0 - '@oclif/plugin-help@7.0.2': - dependencies: - '@oclif/core': 5.1.2 - - '@oclif/plugin-plugins@7.0.3': - dependencies: - '@oclif/core': 5.1.2 - ansis: 3.17.0 - debug: 4.4.3(supports-color@8.1.1) - npm: 11.21.0 - npm-package-arg: 11.0.3 - npm-run-path: 5.3.0 - object-treeify: 4.0.1 - semver: 7.8.5 - validate-npm-package-name: 5.0.1 - which: 4.0.0 - yarn: 1.22.22 - transitivePeerDependencies: - - supports-color - '@open-draft/deferred-promise@2.2.0': optional: true @@ -13476,10 +13326,6 @@ snapshots: hookified@2.2.0: {} - hosted-git-info@7.0.2: - dependencies: - lru-cache: 10.4.3 - html-encoding-sniffer@7.0.0(@noble/hashes@2.4.0): dependencies: '@exodus/bytes': 1.16.0(@noble/hashes@2.4.0) @@ -13634,8 +13480,6 @@ snapshots: isexe@2.0.0: {} - isexe@3.1.5: {} - istanbul-lib-coverage@3.2.2: {} istanbul-lib-report@3.0.1: @@ -13970,8 +13814,6 @@ snapshots: longest-streak@3.1.0: {} - lru-cache@10.4.3: {} - lru-cache@11.5.3: {} lru.min@1.1.4: {} @@ -14753,27 +14595,12 @@ snapshots: normalize-path@3.0.0: {} - npm-package-arg@11.0.3: - dependencies: - hosted-git-info: 7.0.2 - proc-log: 4.2.0 - semver: 7.8.5 - validate-npm-package-name: 5.0.1 - - npm-run-path@5.3.0: - dependencies: - path-key: 4.0.0 - npm-to-yarn@3.2.0: {} - npm@11.21.0: {} - object-assign@4.1.1: {} object-inspect@1.13.4: {} - object-treeify@4.0.1: {} - obug@2.1.4: {} on-finished@2.4.1: @@ -14865,8 +14692,6 @@ snapshots: path-key@3.1.1: {} - path-key@4.0.0: {} - path-parse@1.0.7: {} path-scurry@2.0.2: @@ -15020,8 +14845,6 @@ snapshots: ansi-styles: 5.2.0 react-is: 17.0.2 - proc-log@4.2.0: {} - process-nextick-args@2.0.1: {} process@0.11.10: {} @@ -15985,8 +15808,6 @@ snapshots: uuid@11.1.1: {} - validate-npm-package-name@5.0.1: {} - vary@1.1.2: {} vfile-location@5.0.3: @@ -16136,10 +15957,6 @@ snapshots: dependencies: isexe: 2.0.0 - which@4.0.0: - dependencies: - isexe: 3.1.5 - why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 @@ -16217,8 +16034,6 @@ snapshots: yargs-parser: 21.1.1 optional: true - yarn@1.22.22: {} - yauzl@3.4.0: dependencies: pend: 1.2.0 From 4a532ac754497aa86eb219a2b2942049190b8541 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 2 Oct 2026 02:08:56 +0000 Subject: [PATCH 2/2] 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 --- .changeset/21285-drop-dead-oclif-plugins.md | 11 +++ content/docs/plugins/index.mdx | 16 ++-- docs/qa/platform-checklist/areas/cli.json | 7 +- packages/cli/README.md | 15 ++-- packages/cli/bin/run.js | 19 ++--- .../doctor-deprecation-hint-commands.test.ts | 4 +- packages/cli/src/commands/doctor.ts | 4 +- packages/cli/test/plugin-commands.test.ts | 78 +++++++++++++------ 8 files changed, 101 insertions(+), 53 deletions(-) create mode 100644 .changeset/21285-drop-dead-oclif-plugins.md diff --git a/.changeset/21285-drop-dead-oclif-plugins.md b/.changeset/21285-drop-dead-oclif-plugins.md new file mode 100644 index 00000000000..f3f7b395668 --- /dev/null +++ b/.changeset/21285-drop-dead-oclif-plugins.md @@ -0,0 +1,11 @@ +--- +'@objectstack/cli': patch +--- + +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. + +Clause-②: no + +**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 --help` for help. + +**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`. diff --git a/content/docs/plugins/index.mdx b/content/docs/plugins/index.mdx index 338ee73848a..39a14901cfc 100644 --- a/content/docs/plugins/index.mdx +++ b/content/docs/plugins/index.mdx @@ -399,13 +399,15 @@ export default class MarketplaceSearch extends Command { #### Step 3: Load the Plugin into the CLI -**`os plugins install` is not available today.** `@objectstack/cli`'s `package.json` -lists `@oclif/plugin-plugins` under `oclif.plugins`, but the package sits in -`devDependencies` — and oclif's core-plugin loader only matches names that appear in -`dependencies`. The result is that neither `os plugins …` nor `os help` is a -registered command; `os --help` shows no `plugins` topic. Until that is fixed, load a -CLI extension by building an `os` distribution that lists the package in **both** -`oclif.plugins` and `dependencies`. +**`@objectstack/cli` ships no plugin manager.** Its `package.json` declares no +`oclif.plugins`, and it does not depend on `@oclif/plugin-plugins` or +`@oclif/plugin-help`. So `os plugins …` (`install`, `link`, `update`, …) and `os help` +are not commands: each exits 2 with `command … not found`. `os --help` is the help +entry, and it shows no `plugins` topic. To load a CLI extension, build an `os` +distribution: a package whose own `package.json` lists the extension in **both** +`oclif.plugins` and `dependencies`. oclif's core-plugin loader matches `oclif.plugins` +names only against `dependencies`, so a name listed under `devDependencies` alone never +loads. #### Using the Extended CLI diff --git a/docs/qa/platform-checklist/areas/cli.json b/docs/qa/platform-checklist/areas/cli.json index 4017f4874d5..0592d17421a 100644 --- a/docs/qa/platform-checklist/areas/cli.json +++ b/docs/qa/platform-checklist/areas/cli.json @@ -574,7 +574,7 @@ "title": "Wrong flags and unknown commands error with usage and a nonzero exit — never silently ignored, never executed anyway", "since": "v16", "status": "active", - "revision": 1, + "revision": 2, "priority": "P2", "surface": "cli", "personas": ["operator (local shell)"], @@ -655,11 +655,12 @@ ], "source": [ "packages/cli/src/commands/ (the 20 top-level commands + 10 topics enumerated as variants — oclif pattern discovery per package.json oclif.commands)", - "packages/cli/package.json (oclif.plugins = help + plugins only — no not-found plugin, so unknown commands hard-error)", + "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)", "@oclif/core parse contract (Nonexistent flag / enum FailedFlagValidation / command-not-found, exit 2)" ], "history": [ - { "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" } + { "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" }, + { "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" } ] }, { diff --git a/packages/cli/README.md b/packages/cli/README.md index c6eb92396c1..493fb8907e1 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -203,9 +203,9 @@ Common variables: `OS_DATABASE_URL`, `OS_DATABASE_DRIVER`, - `-d, --dir ` — Override target directory -### `os plugins` (oclif) +### `os plugins` and `os help` (not commands) -`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. +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 --help` for help. To add third-party CLI commands, see [oclif Plugin System](#oclif-plugin-system). ### `os info` @@ -218,13 +218,13 @@ Common variables: `OS_DATABASE_URL`, `OS_DATABASE_DRIVER`, ## oclif Plugin System -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. +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`. ### How Plugin Extension Works 1. **Create an oclif plugin package** with its own `oclif` config in `package.json` 2. **Export oclif Command classes** from the plugin's `src/commands/` directory -3. **Install the plugin** via `os plugins install ` or declare it in the main CLI's `oclif.plugins` +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. ### Creating a CLI Plugin @@ -263,10 +263,11 @@ export default class MarketplaceSearch extends Command { } ``` -**3. Install and use:** +**3. Load it through your `os` distribution, then use it:** + +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`: ```bash -os plugins install @acme/plugin-marketplace os marketplace search "crm" ``` @@ -274,7 +275,7 @@ os marketplace search "crm" | Before (Commander.js) | After (oclif) | |---|---| -| Plugins declared in `objectstack.config.ts` | Plugins installed via `os plugins install` or `oclif.plugins` | +| Plugins declared in `objectstack.config.ts` | Plugins listed in an `os` distribution's `oclif.plugins` and `dependencies` | | Custom `loadPluginCommands` mechanism | oclif's built-in plugin discovery | | `contributes.commands` in manifest | `oclif.commands` in `package.json` | | Commander.js `new Command(...)` exports | oclif `class extends Command` exports | diff --git a/packages/cli/bin/run.js b/packages/cli/bin/run.js index c400f736379..569e6b6fb08 100755 --- a/packages/cli/bin/run.js +++ b/packages/cli/bin/run.js @@ -86,15 +86,16 @@ import { flush, handle, run, settings } from '@oclif/core'; * (`plugin?.type !== 'link'` guards the `isProduction` early return), and this * setting is checked ahead of that — so a `plugins link`ed TypeScript plugin * would no longer be auto-transpiled through this entry. ⭐ That path is not - * reachable today: `@oclif/plugin-plugins` sits in `devDependencies`, and - * oclif's core-plugin loader only matches names under `dependencies`, so - * `os plugins` is not a registered command at all (measured on this entry — - * `os --help` lists 34 topics and none of them is `plugins`; the count is the - * control, so the zero is a reading). `content/docs/plugins/index.mdx` says the - * same in its own words and tells an extension author to build an `os` - * distribution listing the package in both places. ⛔ If that is ever fixed, - * this line is what has to be revisited — the remedy is `bin/run-dev.js`, or - * building the plugin. + * reachable today: this package ships no plugin manager — `package.json` + * declares no `oclif.plugins` and does not depend on `@oclif/plugin-plugins` — + * so `os plugins` (and with it `os plugins link`) is not a registered command + * at all (measured on this entry — `os --help` lists 34 entries, 12 topics and + * 22 commands, and none of them is `plugins`; the count is the control, so the + * zero is a reading). `content/docs/plugins/index.mdx` says the same in its own + * words and tells an extension author to build an `os` distribution listing + * the package in both `oclif.plugins` and `dependencies`. ⛔ If a plugin + * manager is ever shipped, this line is what has to be revisited — the remedy + * is `bin/run-dev.js`, or building the plugin. * * The other change in behaviour is a convergence, not a loss: on an UNBUILT * tree this file now answers oclif's "command not found" under diff --git a/packages/cli/src/commands/doctor-deprecation-hint-commands.test.ts b/packages/cli/src/commands/doctor-deprecation-hint-commands.test.ts index 5222890032f..7579b9e1a72 100644 --- a/packages/cli/src/commands/doctor-deprecation-hint-commands.test.ts +++ b/packages/cli/src/commands/doctor-deprecation-hint-commands.test.ts @@ -11,8 +11,8 @@ * * There is no `codemod` command. oclif resolves this CLI's commands by * globbing `dist/commands/**` (`package.json` → `oclif.commands`, pattern - * strategy); nothing under `src/commands/` compiles to `codemod`, and neither - * bundled plugin (`@oclif/plugin-help`, `@oclif/plugin-plugins`) supplies one. + * strategy); nothing under `src/commands/` compiles to `codemod`, and no plugin + * supplies one (`package.json` declares no `oclif.plugins`). * An operator who followed the prescription got oclif's exit 2, * `command codemod:v2-to-v3 not found` — after spending their time on it. * `content/docs/protocol/backward-compatibility.mdx` already recorded the diff --git a/packages/cli/src/commands/doctor.ts b/packages/cli/src/commands/doctor.ts index f2c37b87bb4..77fad88c99d 100644 --- a/packages/cli/src/commands/doctor.ts +++ b/packages/cli/src/commands/doctor.ts @@ -2375,8 +2375,8 @@ export default class Doctor extends Command { // #10680 — this line used to prescribe `objectstack codemod v2-to-v3`, // a command `os` has never registered. oclif resolves commands by // globbing `dist/commands/**/*.js` (package.json `oclif.commands`); - // there is no `src/commands/codemod*`, and neither bundled plugin - // (`@oclif/plugin-help`, `@oclif/plugin-plugins`) supplies one — so the + // there is no `src/commands/codemod*`, and no plugin supplies one (the + // package declares no `oclif.plugins`) — so the // prescription exited 2, `command codemod:v2-to-v3 not found`, for every // operator who followed it. `content/docs/protocol/backward-compatibility.mdx` // already records the automated codemod as "not yet available". diff --git a/packages/cli/test/plugin-commands.test.ts b/packages/cli/test/plugin-commands.test.ts index ddb9b4daaad..0e9f1a6e741 100644 --- a/packages/cli/test/plugin-commands.test.ts +++ b/packages/cli/test/plugin-commands.test.ts @@ -1,43 +1,75 @@ import { describe, it, expect } from 'vitest'; +import { createRequire } from 'node:module'; /** - * The custom loadPluginCommands mechanism has been removed. - * Plugin command extension is now handled by oclif's built-in plugin system. + * The published `os` binary's oclif plugin surface. * - * Plugins extend the CLI by: - * 1. Including `oclif` config in their package.json - * 2. Exporting oclif Command classes from `src/commands/` - * 3. Being installed via `os plugins install ` + * Command extension is handled by oclif's plugin system: a plugin package + * carries its own `oclif` config and exports oclif Command classes from + * `src/commands/`. The host project's `objectstack.config.ts` does not decide + * which CLI commands exist. * - * The objectstack.config.ts no longer determines CLI command availability. + * This package ships NO plugin manager and loads no plugin of its own. It used + * to list `@oclif/plugin-help` and `@oclif/plugin-plugins` under + * `oclif.plugins` while both sat in `devDependencies`. `@oclif/core`'s + * core-plugin loader (`lib/config/plugin-loader.js`, `loadCorePlugins`) + * matches `oclif.plugins` names only against `dependencies`, so neither ever + * loaded: `os plugins …` and `os help` were never commands, and `os --help` + * read the same with or without the array. The array was dead configuration, + * and every text that read it as "`os plugins install` works" was false. + * + * What these pins hold is the state the published text now describes + * (`README.md` → "`os plugins` and `os help` (not commands)" and "oclif Plugin + * System"; `content/docs/plugins/index.mdx` → the Step 3 callout; + * `bin/run.js` → the `enableAutoTranspile` note on linked plugins). Shipping a + * plugin manager is a product change, not a manifest tweak: it lists the + * plugin in BOTH `oclif.plugins` and `dependencies`, and corrects those texts + * in the same change. */ -describe('oclif Plugin System', () => { - it('should have oclif plugins configured in package.json', async () => { - const { createRequire } = await import('module'); - const require = createRequire(import.meta.url); - const pkg = require('../package.json'); +const require = createRequire(import.meta.url); +const pkg = require('../package.json'); + +const DEPENDENCY_FIELDS = [ + 'dependencies', + 'optionalDependencies', + 'peerDependencies', + 'devDependencies', +] as const; +describe('oclif plugin surface — the published `os` ships no plugin manager', () => { + it('declares no oclif.plugins (oclif would load an entry only from `dependencies`)', () => { expect(pkg.oclif).toBeDefined(); - expect(pkg.oclif.plugins).toContain('@oclif/plugin-help'); - expect(pkg.oclif.plugins).toContain('@oclif/plugin-plugins'); + expect( + pkg.oclif.plugins, + 'package.json `oclif.plugins` is back. An entry here loads only when the same name is in `dependencies` ' + + '(oclif loadCorePlugins); otherwise it is dead configuration. Either way README.md, ' + + 'content/docs/plugins/index.mdx and bin/run.js state that this CLI ships no plugin manager — change them with it.', + ).toBeUndefined(); }); - it('should have oclif command discovery configured', async () => { - const { createRequire } = await import('module'); - const require = createRequire(import.meta.url); - const pkg = require('../package.json'); + it('depends on no @oclif/plugin-* package in any dependency field', () => { + const found = DEPENDENCY_FIELDS.flatMap((field) => + Object.keys(pkg[field] ?? {}) + .filter((name) => name.startsWith('@oclif/plugin-')) + .map((name) => `${field}: ${name}`), + ); + expect( + found, + 'an oclif plugin package is listed. Not named in `oclif.plugins` + `dependencies`, it never loads and is dead ' + + 'weight; loaded, it makes `os plugins` / `os help` real and the published text false.', + ).toEqual([]); + }); +}); +describe('oclif command discovery and entry points', () => { + it('discovers commands by pattern under dist/commands', () => { expect(pkg.oclif.commands).toBeDefined(); expect(pkg.oclif.commands.strategy).toBe('pattern'); expect(pkg.oclif.commands.target).toBe('./dist/commands'); }); - it('should have bin entries pointing to oclif runner', async () => { - const { createRequire } = await import('module'); - const require = createRequire(import.meta.url); - const pkg = require('../package.json'); - + it('points both bin entries at the oclif runner', () => { expect(pkg.bin.os).toBe('./bin/run.js'); expect(pkg.bin.objectstack).toBe('./bin/run.js'); });