From 6780fab0e3ea306d37d23bf3aa4d7b72054098d6 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 21 Aug 2026 21:53:21 +0000 Subject: [PATCH] =?UTF-8?q?docs(cli):=20correct=20capability-loop=20commen?= =?UTF-8?q?ts=20=E2=80=94=20bare=20import,=20CLI=20copy=20wins?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two `serve.ts` comment sites claimed the capability loop resolves `EmailServicePlugin`/`SmsServicePlugin` "host copy first". Measured at head, the loop does a bare `await import(spec.pkg)` / `await import(ex.pkg)` — no `importFromHost` — which Node ESM resolves against this CLI's own realpath, so the CLI's bundled copy always wins and the host's copy is never consulted. Corrected both comments to state that, and named the contrast with `Serve.importConfigPlugin` (the served app's own `plugins: [...]`, which IS host-anchored) so the file's now-real split stays legible. Comment-only: no runtime path, resolution order, or accepted specifier changes. Fixes #10909 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_019bmVFqoQPq63zhKrxdYG1r --- .changeset/capability-loop-host-copy-first.md | 25 +++++++++++++++++++ packages/cli/src/commands/serve.ts | 10 +++++--- 2 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 .changeset/capability-loop-host-copy-first.md diff --git a/.changeset/capability-loop-host-copy-first.md b/.changeset/capability-loop-host-copy-first.md new file mode 100644 index 0000000000..5619243047 --- /dev/null +++ b/.changeset/capability-loop-host-copy-first.md @@ -0,0 +1,25 @@ +--- +"@objectstack/cli": patch +--- + +Stop claiming the `os serve` capability loop loads a "host copy first" (#10909). +Two module-header comments in `packages/cli/src/commands/serve.ts` — above the +`@objectstack/plugin-email` and `@objectstack/service-sms` imports — described +the capability loop (`Serve.CAPABILITY_PROVIDERS`, the `for (const cap of +requires)` block) as resolving `EmailServicePlugin`/`SmsServicePlugin` "host +copy first". Measured at head, the loop does a bare `await import(spec.pkg)` / +`await import(ex.pkg)` — no `importFromHost` in either path — which Node ESM +resolves against **this CLI's own** realpath, so the CLI's bundled copy always +wins; the host app's copy is never consulted. The comments described a +behaviour the code does not have. + +The corrected comments also name the contrast the file now actually contains: +`Serve.importConfigPlugin` (the served app's own `plugins: [...]` entries) IS +host-anchored — an app-declared package wins there — while the capability +loop is not. Making that split legible is the point of the fix, so the next +reader does not assume one resolution rule governs the whole file. + +Comment-only: no runtime path, resolution order, or accepted specifier changes. +All 21 `CAPABILITY_PROVIDERS` packages remain CLI-declared, so bare resolution +still finds every one of them today — this only corrects what the comment +claims about *how* that resolution happens. diff --git a/packages/cli/src/commands/serve.ts b/packages/cli/src/commands/serve.ts index 424a822d1f..15add70083 100644 --- a/packages/cli/src/commands/serve.ts +++ b/packages/cli/src/commands/serve.ts @@ -33,15 +33,19 @@ import { missingProviderMessage } from '../utils/capability-preflight.js'; // transports rather than restated here (#5132) — `resolveEmailCapabilityArg` // has to refuse exactly the configurations `makeTransport` cannot build, and // two literal lists for one vocabulary is the drift #5094 was filed for. Values -// only (no plugin class): `os serve` loads `EmailServicePlugin` itself through -// the capability loop's dynamic import, host copy first. +// only (no plugin class): the capability loop loads `EmailServicePlugin` itself +// with a bare `import()`, resolved against THIS CLI's own realpath — its +// bundled copy always wins, never the host app's. Contrast `importConfigPlugin` +// below, which IS host-anchored: an app-declared package wins there (#10909). import { isEmailTransportProvider, emailProviderRequiresApiKey, unsupportedProviderFix } from '@objectstack/plugin-email'; // The SMS provider vocabulary, read from the package that materialises the // transports, for the same reason and by the same rule as the mail one above // (#5713). `resolveSmsCapabilityArg` has to refuse exactly the tags // `makeSmsTransport` cannot build — restating `log`/`aliyun`/`twilio` here would // be the second literal #5094 was filed for. Values only (no plugin class): the -// capability loop dynamic-imports `SmsServicePlugin` itself, host copy first. +// capability loop dynamic-imports `SmsServicePlugin` itself the same way — a +// bare `import()` resolved against this CLI's own realpath, so its bundled +// copy wins, never the host's (#10909). import { isSmsTransportProvider, SMS_TRANSPORT_PROVIDERS } from '@objectstack/service-sms'; import { resolveObjectStackHome } from '@objectstack/runtime'; import { LOG_LEVELS, resolveLogLevel, readLogLevelEnv } from '../utils/log-level.js';