Skip to content

Stop SDK chunks from importing private @executor-js/api#755

Merged
RhysSullivan merged 2 commits into
mainfrom
fix/sdk-no-private-api-leak
May 11, 2026
Merged

Stop SDK chunks from importing private @executor-js/api#755
RhysSullivan merged 2 commits into
mainfrom
fix/sdk-no-private-api-leak

Conversation

@RhysSullivan

Copy link
Copy Markdown
Collaborator

Summary

The published plugin bundles (@executor-js/plugin-mcp, -graphql, -openapi, etc.) carry import { InternalError } from "@executor-js/api" (and addGroup / capture) inside their SDK chunks. Because @executor-js/api is private: true and isn't on npm, plain Node ESM consumers fail with Cannot find package '@executor-js/api' on import("@executor-js/plugin-mcp/core") (same for graphql / openapi).

Reproduced in a fresh /tmp dir against the live npm package; the dist files now have zero @executor-js/api strings after this change.

Fix

  • Move InternalError (a 10-line Schema.TaggedErrorClass for the wire-level 500) into @executor-js/sdk/core. @executor-js/api/observability.ts re-exports it for back-compat — server code is unaffected.
  • Each plugin's src/api/group.ts imports InternalError from @executor-js/sdk/core instead of the private package.
  • Each plugin's src/sdk/plugin.ts no longer reaches sideways into ../api/* and no longer sets the optional routes / handlers / extensionService fields. Those are layered on by a new HTTP-augmented variant on the /api subpath.
  • Hosts opt into HTTP via import { mcpHttpPlugin } from "@executor-js/plugin-mcp/api" (and graphqlHttpPlugin, openApiHttpPlugin, onepasswordHttpPlugin, googleDiscoveryHttpPlugin). Apps cloud / local / marketing updated.
  • @executor-js/api demoted from dependencies to devDep + optional peerDep on plugin-onepassword and plugin-google-discovery, matching the other plugins.

Net result:

// SDK-only consumers (unchanged path; no longer pulls @executor-js/api)
import { mcpPlugin } from "@executor-js/plugin-mcp";

// Hosts wanting HTTP
import { mcpHttpPlugin } from "@executor-js/plugin-mcp/api";

Breaking change

Any host code that read mcpPlugin(opts).routes / .handlers / .extensionService directly off the SDK factory's return value must switch to the *HttpPlugin factory from @executor-js/plugin-*/api. Every host in this repo was updated; out-of-tree callers that compose plugins by hand need the same import switch.

Changeset bumps the fixed group as minor (pre-1.0).

Test plan

  • bun run typecheck — 33/33 pass
  • bun run lint — 0 warnings, 0 errors
  • bun run format:check — clean
  • bun run test — only pre-existing failure (returns 401 for malformed bearer tokens in cloud/mcp-miniflare.e2e.node.test.ts) reproduces on main without these changes
  • All 5 plugin dist/*.js files: zero @executor-js/api references after bun run build

Each plugin's `src/sdk/plugin.ts` was reaching into `../api/group` and
`../api/handlers` to fill the optional `routes`/`handlers`/
`extensionService` fields. tsup followed those imports into the
published SDK chunk, which carried `import { InternalError } from
"@executor-js/api"` and `import { addGroup, capture } from
"@executor-js/api"`. Because `@executor-js/api` is `private: true`,
plain ESM consumers hit `Cannot find package '@executor-js/api'` on
`import("@executor-js/plugin-mcp/core")` (and graphql / openapi).

Changes:

- Move `InternalError` (the wire-level 500 schema) to
  `@executor-js/sdk/core`. `@executor-js/api/observability.ts`
  re-exports it for back-compat — server code is unaffected.
- Each plugin's `src/api/group.ts` now imports `InternalError` from
  `@executor-js/sdk/core` instead of `@executor-js/api`.
- Each plugin's `src/sdk/plugin.ts` no longer imports `../api/*` and
  no longer sets `routes`/`handlers`/`extensionService` on the
  returned plugin.
- New `*HttpPlugin` factories on the `/api` subpath wrap the SDK
  factory and add the HTTP fields. Hosts that mount HTTP routes
  switch to the `/api` subpath and the new factory name
  (`mcpHttpPlugin`, `graphqlHttpPlugin`, `openApiHttpPlugin`,
  `onepasswordHttpPlugin`, `googleDiscoveryHttpPlugin`).
- Apps cloud / local / marketing updated to use the `*HttpPlugin`
  factories from `/api`.
- `@executor-js/api` demoted from `dependencies` to devDep + optional
  peerDep on `plugin-onepassword` and `plugin-google-discovery`,
  matching the other plugins.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented May 11, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
executor-marketing c3ce5a0 Commit Preview URL

Branch Preview URL
May 11 2026, 05:36 AM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented May 11, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
executor-cloud c3ce5a0 May 11 2026, 05:36 AM

@pkg-pr-new

pkg-pr-new Bot commented May 11, 2026

Copy link
Copy Markdown

Open in StackBlitz

@executor-js/cli

npm i https://pkg.pr.new/@executor-js/cli@755

@executor-js/config

npm i https://pkg.pr.new/@executor-js/config@755

@executor-js/execution

npm i https://pkg.pr.new/@executor-js/execution@755

@executor-js/sdk

npm i https://pkg.pr.new/@executor-js/sdk@755

@executor-js/storage-core

npm i https://pkg.pr.new/@executor-js/storage-core@755

@executor-js/codemode-core

npm i https://pkg.pr.new/@executor-js/codemode-core@755

@executor-js/runtime-quickjs

npm i https://pkg.pr.new/@executor-js/runtime-quickjs@755

@executor-js/plugin-file-secrets

npm i https://pkg.pr.new/@executor-js/plugin-file-secrets@755

@executor-js/plugin-google-discovery

npm i https://pkg.pr.new/@executor-js/plugin-google-discovery@755

@executor-js/plugin-graphql

npm i https://pkg.pr.new/@executor-js/plugin-graphql@755

@executor-js/plugin-keychain

npm i https://pkg.pr.new/@executor-js/plugin-keychain@755

@executor-js/plugin-mcp

npm i https://pkg.pr.new/@executor-js/plugin-mcp@755

@executor-js/plugin-onepassword

npm i https://pkg.pr.new/@executor-js/plugin-onepassword@755

@executor-js/plugin-openapi

npm i https://pkg.pr.new/@executor-js/plugin-openapi@755

executor

npm i https://pkg.pr.new/executor@755

commit: c3ce5a0

Catches the regression class where a published `@executor-js/*` bundle
transitively imports a private workspace package (e.g. the
`@executor-js/api` leak this branch fixes). Runs as a release-gate
step before `release:publish:packages`, so a broken bundle blocks
publish.

How it works:

- Reuses `publish-packages.ts --dry-run` to pack every public
  package with `publishConfig.exports` and `workspace:*` rewrites
  applied.
- For each tarball, installs into a fresh temp dir with npm
  `overrides` pointing every other `@executor-js/*` transitive dep at
  its local tarball — without that, npm pulls the currently-published
  registry version of `@executor-js/sdk` etc. and masks any
  unreleased-symbol drift.
- Reads the installed manifest (post-`publishConfig`) and dynamically
  `import()`s every subpath in its `exports` map.
- Hard-fails on any `Cannot find package '@executor-js/*'`. Other
  resolution failures (missing `react`, `effect`, `@effect/vitest`,
  etc.) are logged as `skip` since they reflect a missing peer in the
  smoke env, not a bug in the bundle.

`publish-packages.ts` reordered so pack always happens (was skipped
when the version was already on npm). The "already published" check
now only short-circuits the `npm publish` step, so smoke tests and
pkg-pr-new previews always have a fresh tarball to work with.

Wired up:

- `bun run release:smoke:packages` — invoke locally.
- `release.yml` runs it before `release:publish:packages`.

Reverse-verified by temporarily restoring the pre-fix mcp files —
script reports `FAIL @executor-js/plugin-mcp — references private
'@executor-js/api'` and exits 1.
@RhysSullivan
RhysSullivan merged commit d0cf70c into main May 11, 2026
10 checks passed
RhysSullivan added a commit that referenced this pull request May 31, 2026
* Stop SDK chunks from importing private @executor-js/api

Each plugin's `src/sdk/plugin.ts` was reaching into `../api/group` and
`../api/handlers` to fill the optional `routes`/`handlers`/
`extensionService` fields. tsup followed those imports into the
published SDK chunk, which carried `import { InternalError } from
"@executor-js/api"` and `import { addGroup, capture } from
"@executor-js/api"`. Because `@executor-js/api` is `private: true`,
plain ESM consumers hit `Cannot find package '@executor-js/api'` on
`import("@executor-js/plugin-mcp/core")` (and graphql / openapi).

Changes:

- Move `InternalError` (the wire-level 500 schema) to
  `@executor-js/sdk/core`. `@executor-js/api/observability.ts`
  re-exports it for back-compat — server code is unaffected.
- Each plugin's `src/api/group.ts` now imports `InternalError` from
  `@executor-js/sdk/core` instead of `@executor-js/api`.
- Each plugin's `src/sdk/plugin.ts` no longer imports `../api/*` and
  no longer sets `routes`/`handlers`/`extensionService` on the
  returned plugin.
- New `*HttpPlugin` factories on the `/api` subpath wrap the SDK
  factory and add the HTTP fields. Hosts that mount HTTP routes
  switch to the `/api` subpath and the new factory name
  (`mcpHttpPlugin`, `graphqlHttpPlugin`, `openApiHttpPlugin`,
  `onepasswordHttpPlugin`, `googleDiscoveryHttpPlugin`).
- Apps cloud / local / marketing updated to use the `*HttpPlugin`
  factories from `/api`.
- `@executor-js/api` demoted from `dependencies` to devDep + optional
  peerDep on `plugin-onepassword` and `plugin-google-discovery`,
  matching the other plugins.

* Add pack-and-import smoke test for public library packages

Catches the regression class where a published `@executor-js/*` bundle
transitively imports a private workspace package (e.g. the
`@executor-js/api` leak this branch fixes). Runs as a release-gate
step before `release:publish:packages`, so a broken bundle blocks
publish.

How it works:

- Reuses `publish-packages.ts --dry-run` to pack every public
  package with `publishConfig.exports` and `workspace:*` rewrites
  applied.
- For each tarball, installs into a fresh temp dir with npm
  `overrides` pointing every other `@executor-js/*` transitive dep at
  its local tarball — without that, npm pulls the currently-published
  registry version of `@executor-js/sdk` etc. and masks any
  unreleased-symbol drift.
- Reads the installed manifest (post-`publishConfig`) and dynamically
  `import()`s every subpath in its `exports` map.
- Hard-fails on any `Cannot find package '@executor-js/*'`. Other
  resolution failures (missing `react`, `effect`, `@effect/vitest`,
  etc.) are logged as `skip` since they reflect a missing peer in the
  smoke env, not a bug in the bundle.

`publish-packages.ts` reordered so pack always happens (was skipped
when the version was already on npm). The "already published" check
now only short-circuits the `npm publish` step, so smoke tests and
pkg-pr-new previews always have a fresh tarball to work with.

Wired up:

- `bun run release:smoke:packages` — invoke locally.
- `release.yml` runs it before `release:publish:packages`.

Reverse-verified by temporarily restoring the pre-fix mcp files —
script reports `FAIL @executor-js/plugin-mcp — references private
'@executor-js/api'` and exits 1.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant