Skip to content

feat(workbench): application extension API#1143

Draft
gu-stav wants to merge 18 commits into
feat/workbenchfrom
feat/workbench-define-app-name
Draft

feat(workbench): application extension API#1143
gu-stav wants to merge 18 commits into
feat/workbenchfrom
feat/workbench-define-app-name

Conversation

@gu-stav
Copy link
Copy Markdown
Member

@gu-stav gu-stav commented May 29, 2026

Depends on

  • sanity-io/workbench#226 — unstable_defineApp in @sanity/federation, consumed here via the released 0.1.0-alpha.9

Required by: sanity-io/sanity#12911

Why

Workbench application extension API on the CLI side:

  1. unstable_defineApp is the sole workbench opt-in. The federation: { enabled } config flag is removed entirely — gating (workbench dev server, federation registration, the federation Vite build plugin) keys off the isWorkbenchApp identity (imported from @sanity/federation, not re-declared). The branded app bypasses the legacy app object schema so the brand survives config parsing.
  2. applicationType resolved at config load — explicit wins, else inferred from a sanity.config.* (⇒ studio) else coreApp. determineIsApp reads it, so a studio that opts in via unstable_defineApp isn't misclassified as an SDK app.
  3. @sanity/cli re-exports unstable_defineApp (+ DefineAppInput) so sanity/cli can surface it to app authors.
  4. sanity init no longer prompts for / scaffolds federation — workbench is opt-in.

@sanity/federation is the released 0.1.0-alpha.9 (no preview override).

All CLI tests pass (cli-core config + dev/build suites updated to opt in via a branded app).

@gu-stav gu-stav requested a review from a team as a code owner May 29, 2026 09:47
@claude
Copy link
Copy Markdown

claude Bot commented May 29, 2026

Claude finished @gu-stav's task in 4m 43s —— View job


Review

  • Read changes vs origin/feat/workbench
  • Verify the Cursor Bugbot finding about missing .js extension
  • Review changed files for bugs/logic errors
  • Check changeset for correctness

Reviewed the 18 commits on top of feat/workbench. The branded-app config branch, the workbench/federation gating off isWorkbenchApp, and the test rewrites look correct. A few notes — only one is potentially worth acting on:


Already fixed — Cursor Bugbot's .js extension finding

packages/@sanity/cli-core/src/config/cli/workbenchApp.ts:4 now reads from './schemas.js' (fixed in f6ef212). The bugbot comment is on the stale 0ecd70b commit; nothing to do.

Question — @sanity/federation as a runtime dep of @sanity/cli-core

packages/@sanity/cli-core/package.json:69 adds @sanity/federation@0.1.0-alpha.9 so getCliConfig.ts:4 / getCliConfigSync.ts:5 can call isWorkbenchApp. That helper is just a Symbol.for('sanity.workbench.defineApp') in value check (your own test helpers and workbenchApp.test.ts:8 re-implement it inline).

Cost: cli-core Bundled raw +112.5 KB / gzip +19.9 KB, Import time +19 ms. cli-core is the hot path for every CLI invocation, including ones that never touch workbench (e.g. sanity documents query).

Worth considering: inlining the brand check as a one-liner in cli-core and keeping @sanity/federation as a dependency only of @sanity/cli. Tradeoff: the brand symbol contract is duplicated across the two packages. Given the comments in workbenchApp.ts:16-19 (Mirrors @sanity/federation's ApplicationType enum) already accept some duplication of the contract, the brand-check is arguably no worse.

Not a blocker — flagging for explicit consideration.

Nit — fixture doesn't dogfood the new user-facing import

fixtures/federated-studio/sanity.cli.ts:1 imports unstable_defineApp from @sanity/federation, but the PR's stated goal (and the @sanity/cli re-export in exports/index.ts:12) is that app authors get this from sanity/cli. The fixture is the closest thing to an end-user example in the repo and should use import {unstable_defineApp} from 'sanity/cli' to match what real users will write.

Nit — changeset summary length

.changeset/pr-1143.md is two sentences; existing changesets (e.g. sdk-1277-workbench-resolve-org-id.md) are one sentence. The second sentence ("the previous experimental federation: { enabled } config flag is removed") is useful breaking-change context for the unreleased workbench branch, so I'd keep it — but it does run against the one-sentence guideline.


Summary: ship-able. The brand-check / cli-core dependency question is the only thing worth a real decision; everything else is fine.
· Branch

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 29, 2026

📦 Bundle Stats — @sanity/cli

Compared against feat/workbench (c492c9f7)

@sanity/cli

Metric Value vs feat/workbench (c492c9f)
Internal (raw) 2.4 KB +269 B, +12.3%
Internal (gzip) 922 B +123 B, +15.4%
Bundled (raw) 11.08 MB +115.7 KB, +1.0%
Bundled (gzip) 2.08 MB +21.0 KB, +1.0%
Import time 837ms +15ms, +1.8%

bin:sanity

Metric Value vs feat/workbench (c492c9f)
Internal (raw) 1023 B -
Internal (gzip) 486 B -
Bundled (raw) 9.84 MB -
Bundled (gzip) 1.77 MB -
Import time 1.97s +26ms, +1.3%

🗺️ View treemap · Artifacts

Details
  • Import time regressions over 10% are flagged with ⚠️
  • Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.

📦 Bundle Stats — @sanity/cli-core

Compared against feat/workbench (c492c9f7)

Metric Value vs feat/workbench (c492c9f)
Internal (raw) 102.3 KB +3.6 KB, +3.6%
Internal (gzip) 24.7 KB +1.4 KB, +5.8%
Bundled (raw) 21.75 MB +114.4 KB, +0.5%
Bundled (gzip) 3.45 MB +20.3 KB, +0.6%
Import time 801ms +3ms, +0.4%

🗺️ View treemap · Artifacts

Details
  • Import time regressions over 10% are flagged with ⚠️
  • Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.

📦 Bundle Stats — create-sanity

Compared against feat/workbench (c492c9f7)

Metric Value vs feat/workbench (c492c9f)
Internal (raw) 908 B -
Internal (gzip) 483 B -
Bundled (raw) 931 B -
Bundled (gzip) 491 B -
Import time ❌ ChildProcess denied: node -
Details
  • Import time regressions over 10% are flagged with ⚠️
  • Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.

Calling `unstable_defineApp` opts a project into workbench behavior. Detect
its brand right after the config module loads and route the branded `app`
around the legacy `app` object schema, which would otherwise strip the
helper's identity fields. Configs that don't call it are untouched.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 29, 2026

Coverage Delta

File Statements
packages/@sanity/cli-core/src/config/cli/getCliConfig.ts 100.0% (±0%)
packages/@sanity/cli-core/src/config/cli/getCliConfigSync.ts 81.0% (+ 38.9%)
packages/@sanity/cli-core/src/config/cli/workbenchApp.ts 100.0% (new)
packages/@sanity/cli-core/src/services/cliUserConfig.ts 100.0% (±0%)
packages/@sanity/cli-core/src/util/getSanityConfigDir.ts 66.7% (new)
packages/@sanity/cli/src/actions/build/buildApp.ts 95.3% (±0%)
packages/@sanity/cli/src/actions/build/buildStaticFiles.ts 97.6% (+ 0.9%)
packages/@sanity/cli/src/actions/build/buildStudio.ts 96.7% (±0%)
packages/@sanity/cli/src/actions/build/getViteConfig.ts 100.0% (new)
packages/@sanity/cli/src/actions/build/writeSanityRuntime.ts 96.2% (new)
packages/@sanity/cli/src/actions/deploy/deployApp.ts 80.5% (±0%)
packages/@sanity/cli/src/actions/deploy/deployStudioSchemasAndManifests.ts 100.0% (±0%)
packages/@sanity/cli/src/actions/dev/devAction.ts 96.7% (- 3.3%)
packages/@sanity/cli/src/actions/dev/devServerRegistry.ts 93.8% (new)
packages/@sanity/cli/src/actions/dev/extractDevServerManifest.ts 20.0% (new)
packages/@sanity/cli/src/actions/dev/getDevServerConfig.ts 100.0% (±0%)
packages/@sanity/cli/src/actions/dev/startAppDevServer.ts 100.0% (+ 13.0%)
packages/@sanity/cli/src/actions/dev/startDevManifestWatcher.ts 90.9% (new)
packages/@sanity/cli/src/actions/dev/startFederationRegistration.ts 100.0% (new)
packages/@sanity/cli/src/actions/dev/startStudioDevServer.ts 100.0% (+ 5.0%)
packages/@sanity/cli/src/actions/dev/startWorkbenchDevServer.ts 98.7% (new)
packages/@sanity/cli/src/actions/dev/writeWorkbenchRuntime.ts 100.0% (new)
packages/@sanity/cli/src/actions/init/createAppCliConfig.ts 100.0% (±0%)
packages/@sanity/cli/src/actions/init/createCliConfig.ts 50.0% (±0%)
packages/@sanity/cli/src/actions/manifest/extractCoreAppManifest.ts 93.1% (new)
packages/@sanity/cli/src/actions/manifest/extractManifest.ts 100.0% (±0%)
packages/@sanity/cli/src/actions/manifest/types.ts 100.0% (±0%)
packages/@sanity/cli/src/actions/manifest/writeManifestFile.ts 100.0% (±0%)
packages/@sanity/cli/src/commands/dev.ts 100.0% (±0%)
packages/@sanity/cli/src/commands/manifest/extract.ts 100.0% (±0%)
packages/@sanity/cli/src/commands/schemas/deploy.ts 95.0% (±0%)
packages/@sanity/cli/src/server/devServer.ts 94.1% (- 0.3%)
packages/@sanity/cli/src/util/determineIsApp.ts 100.0% (±0%)
packages/@sanity/cli/src/util/getSharedServerConfig.ts 100.0% (±0%)
packages/@sanity/cli/src/util/resolveReactStrictMode.ts 100.0% (new)

Comparing 35 changed files against main @ 7d2118d4509f4d0a7406b1e262d17f4b531d1d07

Overall Coverage

Metric Coverage
Statements 84.7% (+ 0.4%)
Branches 74.7% (+ 0.4%)
Functions 84.4% (+ 0.2%)
Lines 85.2% (+ 0.4%)

@gu-stav gu-stav force-pushed the feat/workbench-define-app-name branch from 9ac8f28 to f60f27d Compare May 29, 2026 10:00
@gu-stav gu-stav changed the title feat(config): accept unstable_defineApp name in app config feat(config): branch on unstable_defineApp brand at config load May 29, 2026
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 0ecd70b. Configure here.

@@ -0,0 +1,34 @@
import {cliConfigSchema} from './schemas'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing .js extension in ESM import path

High Severity

The value import from './schemas' lacks the .js extension required for Node.js ESM resolution. Every other file importing this module (e.g., getCliConfig.ts, getCliConfigSync.ts) uses './schemas.js'. The package is "type": "module" and SWC does not rewrite import extensions (no rewriteRelativeImportExtensions in .swcrc), so the compiled output will retain './schemas' and fail at runtime with ERR_MODULE_NOT_FOUND when the workbench app path is taken.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0ecd70b. Configure here.

@gu-stav gu-stav marked this pull request as draft May 29, 2026 10:10
@gu-stav gu-stav force-pushed the feat/workbench-define-app-name branch from 08b2890 to 0ecd70b Compare May 29, 2026 10:51
gu-stav and others added 2 commits May 29, 2026 12:56
Surfaces the workbench app extension API from the CLI's public entry so
`sanity/cli` can pass it through to app authors. Pins @sanity/federation
to 0.1.0-alpha.9 (the release adding the root export); resolves once that
is published.
gu-stav added 2 commits May 29, 2026 14:43
Point the federation dependency at the pkg.pr.new preview built from
workbench#226 so the `unstable_defineApp` re-export resolves now, instead
of pinning an unpublished version. Drops the eslint-disable that masked
the previously-unresolvable import.
cli-core is NodeNext ESM — the compiled workbenchApp.js needs explicit
.js specifiers or Node can't resolve ./schemas and ./types/cliConfig at
runtime (MODULE_NOT_FOUND during build). Was masked while install failed.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 29, 2026

Preview this PR with pkg.pr.new

Run the Sanity CLI

npx https://pkg.pr.new/sanity-io/cli/@sanity/cli@c496368 <command>

...Or upgrade project dependencies

📦 @sanity/cli
pnpm install https://pkg.pr.new/@sanity/cli@c496368
📦 @sanity/cli-build
pnpm install https://pkg.pr.new/@sanity/cli-build@c496368
📦 @sanity/cli-core
pnpm install https://pkg.pr.new/@sanity/cli-core@c496368
📦 @sanity/cli-test
pnpm install https://pkg.pr.new/@sanity/cli-test@c496368
📦 @sanity/eslint-config-cli
pnpm install https://pkg.pr.new/@sanity/eslint-config-cli@c496368

View Commit (c496368)

gu-stav added 5 commits May 29, 2026 15:05
Point @sanity/federation at the rebuilt pkg.pr.new preview whose root
export no longer leaks src under the development condition (fixes the
Node worker type-stripping crash). Allow `unstable_defineApp` in the
v5↔v6 exports parity test as an intentional new value export.
A branded config implies federation — force `federation: {enabled: true}`
when `unstable_defineApp` is used, so the workbench dev server and
federation registration start without a separate flag. Adds unit tests
for the brand-branch (detection, auto-enable, branded-app preservation).
…ineApp

Calling `unstable_defineApp` is now the sole workbench opt-in — the
`federation: {enabled}` config flag is removed entirely. Dev/build/config
gating keys off the `isWorkbenchApp` identity (imported from
`@sanity/federation`, not re-declared). `applicationType` is resolved at
config load (explicit, else inferred from a `sanity.config.*` → studio)
so studio-vs-app classification is settled once. Tests updated to opt in
via a branded app.
`sanity init` no longer prompts for or writes any `federation` config; new
projects are plain studios/apps. Authors opt into workbench by adding
`unstable_defineApp` to `sanity.cli.ts`. Removes the federation prompt,
the `federation` flag/variable threaded through the init flow, and the
`federation: { enabled }` block from the generated config templates.
Drop the pkg.pr.new preview override now that the federation changes
(unstable_defineApp, isWorkbenchApp, root export without the src-leaking
development condition) are published.
@gu-stav gu-stav changed the title feat(config): branch on unstable_defineApp brand at config load feat(workbench): application extension API May 29, 2026
squiggler-app Bot and others added 3 commits May 29, 2026 15:19
…xture

The federation config flag is gone; `unstable_defineApp` is the sole
workbench opt-in. Remove the leftover `--federation` CLI flag and the
init tests that still passed/asserted it. Convert the federated-studio
fixture to opt in via `unstable_defineApp` (its `sanity.config.ts` resolves
it to a studio workbench build) and add `@sanity/federation` to the fixture
deps so the import resolves. Reword the changeset to be user-facing.
`@sanity/cli-test` copies fixtures from the repo root via `copy:fixtures`,
but the generic `build` task only keys on files inside the package. Editing
a repo-root fixture left the cache key unchanged, so turbo replayed a stale
bundled fixture — the federated-studio test then read the old config and
failed. Key cli-test's build on `$TURBO_ROOT$/fixtures` and the workspace
catalog so fixture edits invalidate it.
gu-stav added 3 commits May 29, 2026 18:28
The federation flag is gone, so the e2e init flows that still passed
`--no-federation` errored out with "Nonexistent flag" before reaching
their prompts.
`parseWorkbenchCliConfig` no longer mutates the caller's `app` object — it
resolves `applicationType` onto a clone (copying descriptors so the
non-enumerable brand survives), so re-parsing the same app for a different
directory can't inherit a stale inference. An explicit `applicationType` is
now validated against the known set and fails fast, since `unstable_defineApp`
is a pure identity wrapper that doesn't validate. Cover the workbench branch
in both the async and sync loaders.
Forcing `@module-federation/vite`'s plugins to run inside a vitest worker
(via MFE_VITE_NO_TEST_ENV_CHECK) crashes the worker on Windows — esbuild's
service pipe dies with "Unexpected end of JSON input [plugin onEnd]". Real
`sanity build` runs as its own process and the federation-artifact shape is
platform-independent, so Linux coverage is enough.
@gu-stav gu-stav force-pushed the feat/workbench-define-app-name branch from cb95e6a to c496368 Compare June 1, 2026 09:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant