@sanity/workbench-cli 1.5.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/_exports/build.d.ts +47 -3
  2. package/dist/_exports/build.js +2 -0
  3. package/dist/_exports/build.js.map +1 -1
  4. package/dist/_exports/deploy.d.ts +60 -37
  5. package/dist/_exports/deploy.js +1 -1
  6. package/dist/_exports/deploy.js.map +1 -1
  7. package/dist/_exports/dev.d.ts +0 -2
  8. package/dist/_exports/index.d.ts +39 -1
  9. package/dist/_exports/index.js +1 -0
  10. package/dist/_exports/index.js.map +1 -1
  11. package/dist/_exports/init.d.ts +2 -2
  12. package/dist/_exports/preview.d.ts +169 -0
  13. package/dist/_exports/preview.js +3 -0
  14. package/dist/_exports/preview.js.map +1 -0
  15. package/dist/_exports/undeploy.d.ts +7 -3
  16. package/dist/actions/build/vite/optimize-deps.js +84 -0
  17. package/dist/actions/build/vite/optimize-deps.js.map +1 -0
  18. package/dist/actions/build/vite/plugins/plugin-sanity-app-id.js +15 -1
  19. package/dist/actions/build/vite/plugins/plugin-sanity-app-id.js.map +1 -1
  20. package/dist/actions/deploy/checkBuiltOutput.js +16 -2
  21. package/dist/actions/deploy/checkBuiltOutput.js.map +1 -1
  22. package/dist/actions/deploy/deployWorkbenchApp.js +55 -73
  23. package/dist/actions/deploy/deployWorkbenchApp.js.map +1 -1
  24. package/dist/actions/dev/registry.js +69 -1
  25. package/dist/actions/dev/registry.js.map +1 -1
  26. package/dist/actions/dev/startDevServerRegistration.js +9 -2
  27. package/dist/actions/dev/startDevServerRegistration.js.map +1 -1
  28. package/dist/actions/dev/startWorkbenchDev.js +7 -43
  29. package/dist/actions/dev/startWorkbenchDev.js.map +1 -1
  30. package/dist/actions/dev/startWorkbenchDevServer.js +7 -3
  31. package/dist/actions/dev/startWorkbenchDevServer.js.map +1 -1
  32. package/dist/actions/init/cliConfig.js +2 -0
  33. package/dist/actions/init/cliConfig.js.map +1 -1
  34. package/dist/actions/preview/serveBuiltApplication.js +53 -0
  35. package/dist/actions/preview/serveBuiltApplication.js.map +1 -0
  36. package/dist/actions/preview/startWorkbenchPreview.js +107 -0
  37. package/dist/actions/preview/startWorkbenchPreview.js.map +1 -0
  38. package/dist/appId.js +52 -0
  39. package/dist/appId.js.map +1 -0
  40. package/dist/defineApp.js +2 -5
  41. package/dist/defineApp.js.map +1 -1
  42. package/dist/resolveWorkbenchApp.js +2 -0
  43. package/dist/resolveWorkbenchApp.js.map +1 -1
  44. package/dist/services/applications.js +37 -20
  45. package/dist/services/applications.js.map +1 -1
  46. package/dist/util/serverOrchestration.js +75 -0
  47. package/dist/util/serverOrchestration.js.map +1 -0
  48. package/package.json +7 -3
@@ -1,4 +1,5 @@
1
1
  import { getCliConfigUncached } from '@sanity/cli-core';
2
+ import { resolveAppId } from '../../appId.js';
2
3
  import { deriveConfigs, deriveInterfaces } from './deriveInterfaces.js';
3
4
  import { trackExposesSet } from './exposesSetId.js';
4
5
  import { registerDevServer } from './registry.js';
@@ -16,7 +17,7 @@ import { startDevManifestWatcher } from './startDevManifestWatcher.js';
16
17
  * interface changes. The workbench reads the entry to locate and render the
17
18
  * server; the watcher keeps it current as `sanity.cli.ts` is edited.
18
19
  */ export async function startDevServerRegistration(options) {
19
- const { appId, cliConfig, extractManifest, isApp, onInterfaceSetChange, output, server, workDir } = options;
20
+ const { cliConfig, extractManifest, isApp, onInterfaceSetChange, output, server, workDir } = options;
20
21
  const { host: appHost, port: appPort } = serverAddress(server);
21
22
  // Forwarded alongside (not inside) the manifest so the workbench renders local
22
23
  // panels/workers and reads the configs without a deploy.
@@ -27,7 +28,13 @@ import { startDevManifestWatcher } from './startDevManifestWatcher.js';
27
28
  const registration = registerDevServer({
28
29
  configs,
29
30
  host: appHost,
30
- id: appId,
31
+ // Keyed by where it's served (not the deployment id), so a running app can't
32
+ // collide with its deployed twin — on the configured port, not the bound one,
33
+ // to match `__SANITY_APP_ID__`, compiled before any non-strict shift.
34
+ id: resolveAppId({
35
+ host: appHost,
36
+ port: server.config.server.port ?? appPort
37
+ }),
31
38
  interfaces,
32
39
  port: appPort,
33
40
  projectId: cliConfig?.api?.projectId,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/dev/startDevServerRegistration.ts"],"sourcesContent":["import {type CliConfig, getCliConfigUncached, type Output} from '@sanity/cli-core'\nimport {type ViteDevServer} from 'vite'\n\nimport {deriveConfigs, deriveInterfaces} from './deriveInterfaces.js'\nimport {trackExposesSet} from './exposesSetId.js'\nimport {type DevServerManifest, registerDevServer} from './registry.js'\nimport {startDevManifestWatcher} from './startDevManifestWatcher.js'\n\ninterface DevServerRegistrationOptions {\n /** Resolved app id for the registry entry; the caller owns id resolution and the deprecation check. */\n appId: string | undefined\n cliConfig: CliConfig\n /**\n * Extract the project manifest to inline into the registry. The caller owns the\n * studio-vs-app split (manifest formats are CLI-domain); registration re-derives\n * the interface set alongside it.\n */\n extractManifest: (params: {\n configPath: string\n workDir: string\n }) => Promise<DevServerManifest['manifest']>\n isApp: boolean\n output: Output\n server: ViteDevServer\n workDir: string\n\n /**\n * Rebuild the app's federation remote when its interface set changes, awaited\n * *before* the registry patch — the patch reloads the workbench page, which must\n * re-fetch a remote that already exposes the new interface. Resolves with the\n * recreated server so the entry gets its actual address (non-strict ports may\n * shift it); must reject if the restart produces no server, so the set stays\n * uncommitted and the next save retries instead of advertising a dead port.\n */\n onInterfaceSetChange?: () => Promise<ViteDevServer>\n}\n\ninterface DevServerRegistrationHandle {\n close: () => Promise<void>\n}\n\n/** The address the server actually bound — the live socket, which can differ from the configured port under non-strict ports. */\nfunction serverAddress(server: ViteDevServer) {\n const resolvedHost = server.config.server.host\n const addr = server.httpServer?.address()\n return {\n host: typeof resolvedHost === 'string' ? resolvedHost : 'localhost',\n port: typeof addr === 'object' && addr ? addr.port : server.config.server.port,\n }\n}\n\n/**\n * Register the dev server in the registry and watch its config for manifest +\n * interface changes. The workbench reads the entry to locate and render the\n * server; the watcher keeps it current as `sanity.cli.ts` is edited.\n */\nexport async function startDevServerRegistration(\n options: DevServerRegistrationOptions,\n): Promise<DevServerRegistrationHandle> {\n const {appId, cliConfig, extractManifest, isApp, onInterfaceSetChange, output, server, workDir} =\n options\n\n const {host: appHost, port: appPort} = serverAddress(server)\n\n // Forwarded alongside (not inside) the manifest so the workbench renders local\n // panels/workers and reads the configs without a deploy.\n const interfaces = deriveInterfaces(cliConfig.app, {isApp})\n const configs = deriveConfigs(cliConfig.app)\n\n const registration = registerDevServer({\n configs,\n host: appHost,\n id: appId,\n interfaces,\n port: appPort,\n projectId: cliConfig?.api?.projectId,\n type: isApp ? 'coreApp' : 'studio',\n workDir,\n })\n\n const exposesSet = trackExposesSet({configs, interfaces})\n\n const watcher = await startDevManifestWatcher({\n // Re-derive every pass (don't omit): the registry patch is a shallow merge,\n // so omitting would wipe the registered set.\n extract: async (params) => {\n const app = (await getCliConfigUncached(params.workDir)).app\n return {\n configs: deriveConfigs(app),\n interfaces: deriveInterfaces(app, {isApp}),\n manifest: await extractManifest(params),\n }\n },\n // A studio's root resolves to `sanity.config.*` but its interfaces live in\n // `sanity.cli.*` — watch that too. Apps already root at `sanity.cli.*`.\n extraWatchFilenames: isApp ? undefined : ['sanity.cli.js', 'sanity.cli.ts'],\n output,\n update: async (patch) => {\n if (\n !exposesSet.changed({\n configs: patch.configs,\n interfaces: patch.interfaces,\n })\n ) {\n registration.update(patch)\n return\n }\n // Rebuild the remote *before* patching the registry — the patch reloads the\n // page, which must re-fetch a remote that already exposes the new interface.\n const rebuiltServer = await onInterfaceSetChange?.()\n // Commit only after a successful rebuild, so a thrown one retries next pass.\n exposesSet.commit({\n configs: patch.configs,\n interfaces: patch.interfaces,\n })\n // The recreated server can bind a different port (non-strict ports).\n registration.update(rebuiltServer ? {...patch, ...serverAddress(rebuiltServer)} : patch)\n },\n workDir,\n })\n\n return {\n close: async () => {\n registration.release()\n await watcher.close()\n },\n }\n}\n"],"names":["getCliConfigUncached","deriveConfigs","deriveInterfaces","trackExposesSet","registerDevServer","startDevManifestWatcher","serverAddress","server","resolvedHost","config","host","addr","httpServer","address","port","startDevServerRegistration","options","appId","cliConfig","extractManifest","isApp","onInterfaceSetChange","output","workDir","appHost","appPort","interfaces","app","configs","registration","id","projectId","api","type","exposesSet","watcher","extract","params","manifest","extraWatchFilenames","undefined","update","patch","changed","rebuiltServer","commit","close","release"],"mappings":"AAAA,SAAwBA,oBAAoB,QAAoB,mBAAkB;AAGlF,SAAQC,aAAa,EAAEC,gBAAgB,QAAO,wBAAuB;AACrE,SAAQC,eAAe,QAAO,oBAAmB;AACjD,SAAgCC,iBAAiB,QAAO,gBAAe;AACvE,SAAQC,uBAAuB,QAAO,+BAA8B;AAmCpE,+HAA+H,GAC/H,SAASC,cAAcC,MAAqB;IAC1C,MAAMC,eAAeD,OAAOE,MAAM,CAACF,MAAM,CAACG,IAAI;IAC9C,MAAMC,OAAOJ,OAAOK,UAAU,EAAEC;IAChC,OAAO;QACLH,MAAM,OAAOF,iBAAiB,WAAWA,eAAe;QACxDM,MAAM,OAAOH,SAAS,YAAYA,OAAOA,KAAKG,IAAI,GAAGP,OAAOE,MAAM,CAACF,MAAM,CAACO,IAAI;IAChF;AACF;AAEA;;;;CAIC,GACD,OAAO,eAAeC,2BACpBC,OAAqC;IAErC,MAAM,EAACC,KAAK,EAAEC,SAAS,EAAEC,eAAe,EAAEC,KAAK,EAAEC,oBAAoB,EAAEC,MAAM,EAAEf,MAAM,EAAEgB,OAAO,EAAC,GAC7FP;IAEF,MAAM,EAACN,MAAMc,OAAO,EAAEV,MAAMW,OAAO,EAAC,GAAGnB,cAAcC;IAErD,+EAA+E;IAC/E,yDAAyD;IACzD,MAAMmB,aAAaxB,iBAAiBgB,UAAUS,GAAG,EAAE;QAACP;IAAK;IACzD,MAAMQ,UAAU3B,cAAciB,UAAUS,GAAG;IAE3C,MAAME,eAAezB,kBAAkB;QACrCwB;QACAlB,MAAMc;QACNM,IAAIb;QACJS;QACAZ,MAAMW;QACNM,WAAWb,WAAWc,KAAKD;QAC3BE,MAAMb,QAAQ,YAAY;QAC1BG;IACF;IAEA,MAAMW,aAAa/B,gBAAgB;QAACyB;QAASF;IAAU;IAEvD,MAAMS,UAAU,MAAM9B,wBAAwB;QAC5C,4EAA4E;QAC5E,6CAA6C;QAC7C+B,SAAS,OAAOC;YACd,MAAMV,MAAM,AAAC,CAAA,MAAM3B,qBAAqBqC,OAAOd,OAAO,CAAA,EAAGI,GAAG;YAC5D,OAAO;gBACLC,SAAS3B,cAAc0B;gBACvBD,YAAYxB,iBAAiByB,KAAK;oBAACP;gBAAK;gBACxCkB,UAAU,MAAMnB,gBAAgBkB;YAClC;QACF;QACA,2EAA2E;QAC3E,wEAAwE;QACxEE,qBAAqBnB,QAAQoB,YAAY;YAAC;YAAiB;SAAgB;QAC3ElB;QACAmB,QAAQ,OAAOC;YACb,IACE,CAACR,WAAWS,OAAO,CAAC;gBAClBf,SAASc,MAAMd,OAAO;gBACtBF,YAAYgB,MAAMhB,UAAU;YAC9B,IACA;gBACAG,aAAaY,MAAM,CAACC;gBACpB;YACF;YACA,4EAA4E;YAC5E,6EAA6E;YAC7E,MAAME,gBAAgB,MAAMvB;YAC5B,6EAA6E;YAC7Ea,WAAWW,MAAM,CAAC;gBAChBjB,SAASc,MAAMd,OAAO;gBACtBF,YAAYgB,MAAMhB,UAAU;YAC9B;YACA,qEAAqE;YACrEG,aAAaY,MAAM,CAACG,gBAAgB;gBAAC,GAAGF,KAAK;gBAAE,GAAGpC,cAAcsC,cAAc;YAAA,IAAIF;QACpF;QACAnB;IACF;IAEA,OAAO;QACLuB,OAAO;YACLjB,aAAakB,OAAO;YACpB,MAAMZ,QAAQW,KAAK;QACrB;IACF;AACF"}
1
+ {"version":3,"sources":["../../../src/actions/dev/startDevServerRegistration.ts"],"sourcesContent":["import {type CliConfig, getCliConfigUncached, type Output} from '@sanity/cli-core'\nimport {type ViteDevServer} from 'vite'\n\nimport {resolveAppId} from '../../appId.js'\nimport {deriveConfigs, deriveInterfaces} from './deriveInterfaces.js'\nimport {trackExposesSet} from './exposesSetId.js'\nimport {type DevServerManifest, registerDevServer} from './registry.js'\nimport {startDevManifestWatcher} from './startDevManifestWatcher.js'\n\ninterface DevServerRegistrationOptions {\n cliConfig: CliConfig\n /**\n * Extract the project manifest to inline into the registry. The caller owns the\n * studio-vs-app split (manifest formats are CLI-domain); registration re-derives\n * the interface set alongside it.\n */\n extractManifest: (params: {\n configPath: string\n workDir: string\n }) => Promise<DevServerManifest['manifest']>\n isApp: boolean\n output: Output\n server: ViteDevServer\n workDir: string\n\n /**\n * Rebuild the app's federation remote when its interface set changes, awaited\n * *before* the registry patch — the patch reloads the workbench page, which must\n * re-fetch a remote that already exposes the new interface. Resolves with the\n * recreated server so the entry gets its actual address (non-strict ports may\n * shift it); must reject if the restart produces no server, so the set stays\n * uncommitted and the next save retries instead of advertising a dead port.\n */\n onInterfaceSetChange?: () => Promise<ViteDevServer>\n}\n\ninterface DevServerRegistrationHandle {\n close: () => Promise<void>\n}\n\n/** The address the server actually bound — the live socket, which can differ from the configured port under non-strict ports. */\nfunction serverAddress(server: ViteDevServer) {\n const resolvedHost = server.config.server.host\n const addr = server.httpServer?.address()\n return {\n host: typeof resolvedHost === 'string' ? resolvedHost : 'localhost',\n port: typeof addr === 'object' && addr ? addr.port : server.config.server.port,\n }\n}\n\n/**\n * Register the dev server in the registry and watch its config for manifest +\n * interface changes. The workbench reads the entry to locate and render the\n * server; the watcher keeps it current as `sanity.cli.ts` is edited.\n */\nexport async function startDevServerRegistration(\n options: DevServerRegistrationOptions,\n): Promise<DevServerRegistrationHandle> {\n const {cliConfig, extractManifest, isApp, onInterfaceSetChange, output, server, workDir} = options\n\n const {host: appHost, port: appPort} = serverAddress(server)\n\n // Forwarded alongside (not inside) the manifest so the workbench renders local\n // panels/workers and reads the configs without a deploy.\n const interfaces = deriveInterfaces(cliConfig.app, {isApp})\n const configs = deriveConfigs(cliConfig.app)\n\n const registration = registerDevServer({\n configs,\n host: appHost,\n // Keyed by where it's served (not the deployment id), so a running app can't\n // collide with its deployed twin — on the configured port, not the bound one,\n // to match `__SANITY_APP_ID__`, compiled before any non-strict shift.\n id: resolveAppId({host: appHost, port: server.config.server.port ?? appPort}),\n interfaces,\n port: appPort,\n projectId: cliConfig?.api?.projectId,\n type: isApp ? 'coreApp' : 'studio',\n workDir,\n })\n\n const exposesSet = trackExposesSet({configs, interfaces})\n\n const watcher = await startDevManifestWatcher({\n // Re-derive every pass (don't omit): the registry patch is a shallow merge,\n // so omitting would wipe the registered set.\n extract: async (params) => {\n const app = (await getCliConfigUncached(params.workDir)).app\n return {\n configs: deriveConfigs(app),\n interfaces: deriveInterfaces(app, {isApp}),\n manifest: await extractManifest(params),\n }\n },\n // A studio's root resolves to `sanity.config.*` but its interfaces live in\n // `sanity.cli.*` — watch that too. Apps already root at `sanity.cli.*`.\n extraWatchFilenames: isApp ? undefined : ['sanity.cli.js', 'sanity.cli.ts'],\n output,\n update: async (patch) => {\n if (\n !exposesSet.changed({\n configs: patch.configs,\n interfaces: patch.interfaces,\n })\n ) {\n registration.update(patch)\n return\n }\n // Rebuild the remote *before* patching the registry — the patch reloads the\n // page, which must re-fetch a remote that already exposes the new interface.\n const rebuiltServer = await onInterfaceSetChange?.()\n // Commit only after a successful rebuild, so a thrown one retries next pass.\n exposesSet.commit({\n configs: patch.configs,\n interfaces: patch.interfaces,\n })\n // The recreated server can bind a different port (non-strict ports).\n registration.update(rebuiltServer ? {...patch, ...serverAddress(rebuiltServer)} : patch)\n },\n workDir,\n })\n\n return {\n close: async () => {\n registration.release()\n await watcher.close()\n },\n }\n}\n"],"names":["getCliConfigUncached","resolveAppId","deriveConfigs","deriveInterfaces","trackExposesSet","registerDevServer","startDevManifestWatcher","serverAddress","server","resolvedHost","config","host","addr","httpServer","address","port","startDevServerRegistration","options","cliConfig","extractManifest","isApp","onInterfaceSetChange","output","workDir","appHost","appPort","interfaces","app","configs","registration","id","projectId","api","type","exposesSet","watcher","extract","params","manifest","extraWatchFilenames","undefined","update","patch","changed","rebuiltServer","commit","close","release"],"mappings":"AAAA,SAAwBA,oBAAoB,QAAoB,mBAAkB;AAGlF,SAAQC,YAAY,QAAO,iBAAgB;AAC3C,SAAQC,aAAa,EAAEC,gBAAgB,QAAO,wBAAuB;AACrE,SAAQC,eAAe,QAAO,oBAAmB;AACjD,SAAgCC,iBAAiB,QAAO,gBAAe;AACvE,SAAQC,uBAAuB,QAAO,+BAA8B;AAiCpE,+HAA+H,GAC/H,SAASC,cAAcC,MAAqB;IAC1C,MAAMC,eAAeD,OAAOE,MAAM,CAACF,MAAM,CAACG,IAAI;IAC9C,MAAMC,OAAOJ,OAAOK,UAAU,EAAEC;IAChC,OAAO;QACLH,MAAM,OAAOF,iBAAiB,WAAWA,eAAe;QACxDM,MAAM,OAAOH,SAAS,YAAYA,OAAOA,KAAKG,IAAI,GAAGP,OAAOE,MAAM,CAACF,MAAM,CAACO,IAAI;IAChF;AACF;AAEA;;;;CAIC,GACD,OAAO,eAAeC,2BACpBC,OAAqC;IAErC,MAAM,EAACC,SAAS,EAAEC,eAAe,EAAEC,KAAK,EAAEC,oBAAoB,EAAEC,MAAM,EAAEd,MAAM,EAAEe,OAAO,EAAC,GAAGN;IAE3F,MAAM,EAACN,MAAMa,OAAO,EAAET,MAAMU,OAAO,EAAC,GAAGlB,cAAcC;IAErD,+EAA+E;IAC/E,yDAAyD;IACzD,MAAMkB,aAAavB,iBAAiBe,UAAUS,GAAG,EAAE;QAACP;IAAK;IACzD,MAAMQ,UAAU1B,cAAcgB,UAAUS,GAAG;IAE3C,MAAME,eAAexB,kBAAkB;QACrCuB;QACAjB,MAAMa;QACN,6EAA6E;QAC7E,8EAA8E;QAC9E,sEAAsE;QACtEM,IAAI7B,aAAa;YAACU,MAAMa;YAAST,MAAMP,OAAOE,MAAM,CAACF,MAAM,CAACO,IAAI,IAAIU;QAAO;QAC3EC;QACAX,MAAMU;QACNM,WAAWb,WAAWc,KAAKD;QAC3BE,MAAMb,QAAQ,YAAY;QAC1BG;IACF;IAEA,MAAMW,aAAa9B,gBAAgB;QAACwB;QAASF;IAAU;IAEvD,MAAMS,UAAU,MAAM7B,wBAAwB;QAC5C,4EAA4E;QAC5E,6CAA6C;QAC7C8B,SAAS,OAAOC;YACd,MAAMV,MAAM,AAAC,CAAA,MAAM3B,qBAAqBqC,OAAOd,OAAO,CAAA,EAAGI,GAAG;YAC5D,OAAO;gBACLC,SAAS1B,cAAcyB;gBACvBD,YAAYvB,iBAAiBwB,KAAK;oBAACP;gBAAK;gBACxCkB,UAAU,MAAMnB,gBAAgBkB;YAClC;QACF;QACA,2EAA2E;QAC3E,wEAAwE;QACxEE,qBAAqBnB,QAAQoB,YAAY;YAAC;YAAiB;SAAgB;QAC3ElB;QACAmB,QAAQ,OAAOC;YACb,IACE,CAACR,WAAWS,OAAO,CAAC;gBAClBf,SAASc,MAAMd,OAAO;gBACtBF,YAAYgB,MAAMhB,UAAU;YAC9B,IACA;gBACAG,aAAaY,MAAM,CAACC;gBACpB;YACF;YACA,4EAA4E;YAC5E,6EAA6E;YAC7E,MAAME,gBAAgB,MAAMvB;YAC5B,6EAA6E;YAC7Ea,WAAWW,MAAM,CAAC;gBAChBjB,SAASc,MAAMd,OAAO;gBACtBF,YAAYgB,MAAMhB,UAAU;YAC9B;YACA,qEAAqE;YACrEG,aAAaY,MAAM,CAACG,gBAAgB;gBAAC,GAAGF,KAAK;gBAAE,GAAGnC,cAAcqC,cAAc;YAAA,IAAIF;QACpF;QACAnB;IACF;IAEA,OAAO;QACLuB,OAAO;YACLjB,aAAakB,OAAO;YACpB,MAAMZ,QAAQW,KAAK;QACrB;IACF;AACF"}
@@ -1,17 +1,8 @@
1
1
  import { styleText } from 'node:util';
2
+ import { createServerLifecycle, toDisplayHost } from '../../util/serverOrchestration.js';
2
3
  import { startAppServerSupervisor } from './appServerSupervisor.js';
3
4
  import { startDevServerRegistration } from './startDevServerRegistration.js';
4
5
  import { startWorkbenchDevServer, startWorkbenchRemoteCoordinator } from './startWorkbenchDevServer.js';
5
- /** How long teardown runs before re-raising the signal to force-exit — enough for
6
- * Vite/watchers to close, short enough not to strand a backgrounded process. */ const SHUTDOWN_GRACE_MS = 5000;
7
- // Bind-only addresses ('0.0.0.0', '::') aren't routable in every browser (notably
8
- // Windows); the displayed URL falls back to localhost. The bind address is untouched.
9
- function toDisplayHost(host) {
10
- if (!host || host === '0.0.0.0' || host === '::' || host === '[::]') {
11
- return 'localhost';
12
- }
13
- return host;
14
- }
15
6
  /**
16
7
  * Orchestrate the dev servers a workbench project needs: a singleton workbench
17
8
  * Vite server plus the app/studio dev server it renders, wired to the dev-server
@@ -22,7 +13,7 @@ function toDisplayHost(host) {
22
13
  * server falls back to the configured port and announces its own URL, exactly as
23
14
  * a plain `sanity dev` would.
24
15
  */ export async function startWorkbenchDev(options) {
25
- const { appId, cacheDir, checkForDeprecatedAppId, cliConfig, extractManifest, httpHost, httpPort, isApp, output, reactStrictMode, startAppServer, workDir } = options;
16
+ const { cacheDir, checkForDeprecatedAppId, cliConfig, extractManifest, httpHost, httpPort, isApp, output, reactStrictMode, startAppServer, workDir } = options;
26
17
  // The remote can't render itself, so it runs as a plain app server (not the
27
18
  // shell) that still claims the lock and bridges the registry, so app
28
19
  // `sanity dev`s register into it.
@@ -51,17 +42,13 @@ function toDisplayHost(host) {
51
42
  }
52
43
  // Unwound in reverse on any failure or on close(): the watcher stops before
53
44
  // the app server, and the supervisor waits out an in-flight rebuild.
54
- const closers = [];
55
- const disposeAll = async ()=>{
56
- for (const close of closers.splice(0).toReversed()){
57
- await close().catch(()=>{});
58
- }
59
- };
45
+ const { close, closers, installSignalHandlers } = createServerLifecycle();
60
46
  const workbench = await startWorkbenchDevServer({
61
47
  cacheDir,
62
48
  cliConfig,
63
49
  httpHost,
64
50
  httpPort,
51
+ mode: 'development',
65
52
  output,
66
53
  reactStrictMode,
67
54
  workDir
@@ -71,15 +58,6 @@ function toDisplayHost(host) {
71
58
  // Without one it claims the configured port and announces its own URL.
72
59
  const appPort = workbench.workbenchAvailable ? workbench.workbenchPort + 1 : httpPort;
73
60
  const announceUrl = !workbench.workbenchAvailable;
74
- let closing;
75
- const close = ()=>{
76
- closing ??= (async ()=>{
77
- process.off('SIGINT', onSignal);
78
- process.off('SIGTERM', onSignal);
79
- await disposeAll();
80
- })();
81
- return closing;
82
- };
83
61
  const supervised = await startAppServerSupervisor({
84
62
  cliConfig,
85
63
  start: (config)=>startAppServer({
@@ -89,7 +67,7 @@ function toDisplayHost(host) {
89
67
  }),
90
68
  workDir
91
69
  }).catch(async (err)=>{
92
- await disposeAll();
70
+ await close();
93
71
  throw err;
94
72
  });
95
73
  if (!supervised.started) {
@@ -105,7 +83,6 @@ function toDisplayHost(host) {
105
83
  // The deprecated-id check and manifest extractor are CLI-domain, injected here.
106
84
  checkForDeprecatedAppId();
107
85
  const registration = await startDevServerRegistration({
108
- appId,
109
86
  cliConfig,
110
87
  extractManifest,
111
88
  isApp,
@@ -118,7 +95,7 @@ function toDisplayHost(host) {
118
95
  } catch (err) {
119
96
  // Registration runs after both servers are up; a failure here would leak the
120
97
  // workbench lock and dev servers without this teardown.
121
- await disposeAll();
98
+ await close();
122
99
  throw err;
123
100
  }
124
101
  if (workbench.workbenchAvailable) {
@@ -130,20 +107,7 @@ function toDisplayHost(host) {
130
107
  'underline'
131
108
  ], workbenchUrl)} (app on port ${port})`);
132
109
  }
133
- // Trapping the signal disables Node's default exit, and a finished teardown
134
- // doesn't guarantee an empty event loop (keep-alive sockets, an extraction
135
- // worker mid-run) — so re-raise after teardown to restore conventional signal
136
- // exit semantics. A backstop timer force-exits if teardown wedges.
137
- function onSignal(signal) {
138
- const graceTimer = setTimeout(()=>process.kill(process.pid, signal), SHUTDOWN_GRACE_MS);
139
- graceTimer.unref();
140
- void close().finally(()=>{
141
- clearTimeout(graceTimer);
142
- process.kill(process.pid, signal);
143
- });
144
- }
145
- process.once('SIGINT', onSignal);
146
- process.once('SIGTERM', onSignal);
110
+ installSignalHandlers();
147
111
  return {
148
112
  close
149
113
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/dev/startWorkbenchDev.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {type CliConfig, type Output} from '@sanity/cli-core'\n\nimport {type AppServerResult, startAppServerSupervisor} from './appServerSupervisor.js'\nimport {type DevServerManifest} from './registry.js'\nimport {startDevServerRegistration} from './startDevServerRegistration.js'\nimport {\n startWorkbenchDevServer,\n startWorkbenchRemoteCoordinator,\n} from './startWorkbenchDevServer.js'\n\n/** How long teardown runs before re-raising the signal to force-exit — enough for\n * Vite/watchers to close, short enough not to strand a backgrounded process. */\nconst SHUTDOWN_GRACE_MS = 5000\n\n// Bind-only addresses ('0.0.0.0', '::') aren't routable in every browser (notably\n// Windows); the displayed URL falls back to localhost. The bind address is untouched.\nfunction toDisplayHost(host: string | undefined): string {\n if (!host || host === '0.0.0.0' || host === '::' || host === '[::]') {\n return 'localhost'\n }\n return host\n}\n\nexport interface StartWorkbenchDevOptions {\n /** Resolved app id for the registry entry (the CLI owns id resolution). */\n appId: string | undefined\n /** Directory for the workbench Vite server's dependency cache. */\n cacheDir: string\n /** CLI-domain `app.id`/`deployment.appId` deprecation check, run before registering. */\n checkForDeprecatedAppId: () => void\n cliConfig: CliConfig\n /** Extract the project manifest to inline into the registry (studio-vs-app handled by the CLI). */\n extractManifest: (params: {\n configPath: string\n workDir: string\n }) => Promise<DevServerManifest['manifest']>\n httpHost: string | undefined\n httpPort: number\n isApp: boolean\n output: Output\n reactStrictMode: boolean\n /** Start the app/studio dev server — the CLI owns the server, this orchestrates it. */\n startAppServer: (params: {\n announceUrl: boolean\n cliConfig: CliConfig\n httpPort: number\n }) => Promise<AppServerResult>\n workDir: string\n}\n\n/**\n * Orchestrate the dev servers a workbench project needs: a singleton workbench\n * Vite server plus the app/studio dev server it renders, wired to the dev-server\n * registry so view/service edits re-sync live.\n *\n * A running workbench claims the configured port, so the app server binds the\n * next one. If the workbench can't start (package or port unavailable), the app\n * server falls back to the configured port and announces its own URL, exactly as\n * a plain `sanity dev` would.\n */\nexport async function startWorkbenchDev(\n options: StartWorkbenchDevOptions,\n): Promise<{close: () => Promise<void>}> {\n const {\n appId,\n cacheDir,\n checkForDeprecatedAppId,\n cliConfig,\n extractManifest,\n httpHost,\n httpPort,\n isApp,\n output,\n reactStrictMode,\n startAppServer,\n workDir,\n } = options\n\n // The remote can't render itself, so it runs as a plain app server (not the\n // shell) that still claims the lock and bridges the registry, so app\n // `sanity dev`s register into it.\n if (process.env.SANITY_INTERNAL_IS_WORKBENCH_REMOTE === 'true') {\n const remote = await startAppServer({announceUrl: true, cliConfig, httpPort})\n if (!remote.started) return {close: async () => {}}\n\n const addr = remote.server.httpServer?.address()\n const port =\n (typeof addr === 'object' && addr ? addr.port : remote.server.config.server.port) ?? httpPort\n const coordinator = startWorkbenchRemoteCoordinator({httpHost, port, server: remote.server})\n\n return {\n close: async () => {\n await coordinator.close()\n await remote.close()\n },\n }\n }\n\n // Unwound in reverse on any failure or on close(): the watcher stops before\n // the app server, and the supervisor waits out an in-flight rebuild.\n const closers: Array<() => Promise<void>> = []\n const disposeAll = async () => {\n for (const close of closers.splice(0).toReversed()) {\n await close().catch(() => {})\n }\n }\n\n const workbench = await startWorkbenchDevServer({\n cacheDir,\n cliConfig,\n httpHost,\n httpPort,\n output,\n reactStrictMode,\n workDir,\n })\n closers.push(workbench.close)\n\n // A running workbench owns the configured port; the app server takes the next.\n // Without one it claims the configured port and announces its own URL.\n const appPort = workbench.workbenchAvailable ? workbench.workbenchPort + 1 : httpPort\n const announceUrl = !workbench.workbenchAvailable\n\n let closing: Promise<void> | undefined\n const close = () => {\n closing ??= (async () => {\n process.off('SIGINT', onSignal)\n process.off('SIGTERM', onSignal)\n await disposeAll()\n })()\n return closing\n }\n\n const supervised = await startAppServerSupervisor({\n cliConfig,\n start: (config) => startAppServer({announceUrl, cliConfig: config, httpPort: appPort}),\n workDir,\n }).catch(async (err) => {\n await disposeAll()\n throw err\n })\n\n if (!supervised.started) {\n // The app server already reported why (e.g. missing organization id). Hand\n // back a close that releases the workbench lock; nothing else came up.\n return {close}\n }\n const {supervisor} = supervised\n closers.push(supervisor.close)\n\n try {\n // The deprecated-id check and manifest extractor are CLI-domain, injected here.\n checkForDeprecatedAppId()\n const registration = await startDevServerRegistration({\n appId,\n cliConfig,\n extractManifest,\n isApp,\n onInterfaceSetChange: () => supervisor.rebuild(),\n output,\n server: supervisor.server,\n workDir,\n })\n closers.push(registration.close)\n } catch (err) {\n // Registration runs after both servers are up; a failure here would leak the\n // workbench lock and dev servers without this teardown.\n await disposeAll()\n throw err\n }\n\n if (workbench.workbenchAvailable) {\n const workbenchUrl = `http://${toDisplayHost(workbench.httpHost)}:${workbench.workbenchPort}`\n const addr = supervisor.server.httpServer?.address()\n const port = typeof addr === 'object' && addr ? addr.port : supervisor.server.config.server.port\n output.log(\n `Workbench dev server started at ${styleText(['blue', 'underline'], workbenchUrl)} (app on port ${port})`,\n )\n }\n\n // Trapping the signal disables Node's default exit, and a finished teardown\n // doesn't guarantee an empty event loop (keep-alive sockets, an extraction\n // worker mid-run) — so re-raise after teardown to restore conventional signal\n // exit semantics. A backstop timer force-exits if teardown wedges.\n function onSignal(signal: NodeJS.Signals) {\n const graceTimer = setTimeout(() => process.kill(process.pid, signal), SHUTDOWN_GRACE_MS)\n graceTimer.unref()\n void close().finally(() => {\n clearTimeout(graceTimer)\n process.kill(process.pid, signal)\n })\n }\n process.once('SIGINT', onSignal)\n process.once('SIGTERM', onSignal)\n\n return {close}\n}\n"],"names":["styleText","startAppServerSupervisor","startDevServerRegistration","startWorkbenchDevServer","startWorkbenchRemoteCoordinator","SHUTDOWN_GRACE_MS","toDisplayHost","host","startWorkbenchDev","options","appId","cacheDir","checkForDeprecatedAppId","cliConfig","extractManifest","httpHost","httpPort","isApp","output","reactStrictMode","startAppServer","workDir","process","env","SANITY_INTERNAL_IS_WORKBENCH_REMOTE","remote","announceUrl","started","close","addr","server","httpServer","address","port","config","coordinator","closers","disposeAll","splice","toReversed","catch","workbench","push","appPort","workbenchAvailable","workbenchPort","closing","off","onSignal","supervised","start","err","supervisor","registration","onInterfaceSetChange","rebuild","workbenchUrl","log","signal","graceTimer","setTimeout","kill","pid","unref","finally","clearTimeout","once"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/actions/dev/startWorkbenchDev.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {type CliConfig, type Output} from '@sanity/cli-core'\n\nimport {createServerLifecycle, toDisplayHost} from '../../util/serverOrchestration.js'\nimport {type AppServerResult, startAppServerSupervisor} from './appServerSupervisor.js'\nimport {type DevServerManifest} from './registry.js'\nimport {startDevServerRegistration} from './startDevServerRegistration.js'\nimport {\n startWorkbenchDevServer,\n startWorkbenchRemoteCoordinator,\n} from './startWorkbenchDevServer.js'\n\nexport interface StartWorkbenchDevOptions {\n /** Directory for the workbench Vite server's dependency cache. */\n cacheDir: string\n /** CLI-domain `app.id`/`deployment.appId` deprecation check, run before registering. */\n checkForDeprecatedAppId: () => void\n cliConfig: CliConfig\n /** Extract the project manifest to inline into the registry (studio-vs-app handled by the CLI). */\n extractManifest: (params: {\n configPath: string\n workDir: string\n }) => Promise<DevServerManifest['manifest']>\n httpHost: string | undefined\n httpPort: number\n isApp: boolean\n output: Output\n reactStrictMode: boolean\n /** Start the app/studio dev server — the CLI owns the server, this orchestrates it. */\n startAppServer: (params: {\n announceUrl: boolean\n cliConfig: CliConfig\n httpPort: number\n }) => Promise<AppServerResult>\n workDir: string\n}\n\n/**\n * Orchestrate the dev servers a workbench project needs: a singleton workbench\n * Vite server plus the app/studio dev server it renders, wired to the dev-server\n * registry so view/service edits re-sync live.\n *\n * A running workbench claims the configured port, so the app server binds the\n * next one. If the workbench can't start (package or port unavailable), the app\n * server falls back to the configured port and announces its own URL, exactly as\n * a plain `sanity dev` would.\n */\nexport async function startWorkbenchDev(\n options: StartWorkbenchDevOptions,\n): Promise<{close: () => Promise<void>}> {\n const {\n cacheDir,\n checkForDeprecatedAppId,\n cliConfig,\n extractManifest,\n httpHost,\n httpPort,\n isApp,\n output,\n reactStrictMode,\n startAppServer,\n workDir,\n } = options\n\n // The remote can't render itself, so it runs as a plain app server (not the\n // shell) that still claims the lock and bridges the registry, so app\n // `sanity dev`s register into it.\n if (process.env.SANITY_INTERNAL_IS_WORKBENCH_REMOTE === 'true') {\n const remote = await startAppServer({announceUrl: true, cliConfig, httpPort})\n if (!remote.started) return {close: async () => {}}\n\n const addr = remote.server.httpServer?.address()\n const port =\n (typeof addr === 'object' && addr ? addr.port : remote.server.config.server.port) ?? httpPort\n const coordinator = startWorkbenchRemoteCoordinator({httpHost, port, server: remote.server})\n\n return {\n close: async () => {\n await coordinator.close()\n await remote.close()\n },\n }\n }\n\n // Unwound in reverse on any failure or on close(): the watcher stops before\n // the app server, and the supervisor waits out an in-flight rebuild.\n const {close, closers, installSignalHandlers} = createServerLifecycle()\n\n const workbench = await startWorkbenchDevServer({\n cacheDir,\n cliConfig,\n httpHost,\n httpPort,\n mode: 'development',\n output,\n reactStrictMode,\n workDir,\n })\n closers.push(workbench.close)\n\n // A running workbench owns the configured port; the app server takes the next.\n // Without one it claims the configured port and announces its own URL.\n const appPort = workbench.workbenchAvailable ? workbench.workbenchPort + 1 : httpPort\n const announceUrl = !workbench.workbenchAvailable\n\n const supervised = await startAppServerSupervisor({\n cliConfig,\n start: (config) => startAppServer({announceUrl, cliConfig: config, httpPort: appPort}),\n workDir,\n }).catch(async (err) => {\n await close()\n throw err\n })\n\n if (!supervised.started) {\n // The app server already reported why (e.g. missing organization id). Hand\n // back a close that releases the workbench lock; nothing else came up.\n return {close}\n }\n const {supervisor} = supervised\n closers.push(supervisor.close)\n\n try {\n // The deprecated-id check and manifest extractor are CLI-domain, injected here.\n checkForDeprecatedAppId()\n const registration = await startDevServerRegistration({\n cliConfig,\n extractManifest,\n isApp,\n onInterfaceSetChange: () => supervisor.rebuild(),\n output,\n server: supervisor.server,\n workDir,\n })\n closers.push(registration.close)\n } catch (err) {\n // Registration runs after both servers are up; a failure here would leak the\n // workbench lock and dev servers without this teardown.\n await close()\n throw err\n }\n\n if (workbench.workbenchAvailable) {\n const workbenchUrl = `http://${toDisplayHost(workbench.httpHost)}:${workbench.workbenchPort}`\n const addr = supervisor.server.httpServer?.address()\n const port = typeof addr === 'object' && addr ? addr.port : supervisor.server.config.server.port\n output.log(\n `Workbench dev server started at ${styleText(['blue', 'underline'], workbenchUrl)} (app on port ${port})`,\n )\n }\n\n installSignalHandlers()\n\n return {close}\n}\n"],"names":["styleText","createServerLifecycle","toDisplayHost","startAppServerSupervisor","startDevServerRegistration","startWorkbenchDevServer","startWorkbenchRemoteCoordinator","startWorkbenchDev","options","cacheDir","checkForDeprecatedAppId","cliConfig","extractManifest","httpHost","httpPort","isApp","output","reactStrictMode","startAppServer","workDir","process","env","SANITY_INTERNAL_IS_WORKBENCH_REMOTE","remote","announceUrl","started","close","addr","server","httpServer","address","port","config","coordinator","closers","installSignalHandlers","workbench","mode","push","appPort","workbenchAvailable","workbenchPort","supervised","start","catch","err","supervisor","registration","onInterfaceSetChange","rebuild","workbenchUrl","log"],"mappings":"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"}
@@ -86,7 +86,7 @@ const toApplicationsPayload = (servers)=>({
86
86
  * workbench instance or an unrelated process — fall back to running without a
87
87
  * workbench and let the app/studio dev server claim the configured port.
88
88
  */ export async function startWorkbenchDevServer(options) {
89
- const { cacheDir, cliConfig, httpHost, httpPort: workbenchPort, output, reactStrictMode, workDir } = options;
89
+ const { cacheDir, cliConfig, httpHost, httpPort: workbenchPort, mode, output, reactStrictMode, workDir } = options;
90
90
  // Workbench is opted into solely by calling `unstable_defineApp`.
91
91
  if (!isWorkbenchApp(cliConfig?.app)) {
92
92
  devDebug('Not a workbench app, skipping workbench dev server');
@@ -138,6 +138,7 @@ const toApplicationsPayload = (servers)=>({
138
138
  cacheDir,
139
139
  cliConfig,
140
140
  httpHost,
141
+ mode,
141
142
  output,
142
143
  reactStrictMode,
143
144
  workbenchPort,
@@ -169,8 +170,11 @@ const toApplicationsPayload = (servers)=>({
169
170
  };
170
171
  }
171
172
  async function createWorkbenchViteServer(options) {
172
- const { cacheDir, cliConfig, httpHost, output, reactStrictMode, workbenchPort, workDir } = options;
173
- const remoteUrl = parseRemoteUrl(process.env.SANITY_INTERNAL_WORKBENCH_REMOTE_URL);
173
+ const { cacheDir, cliConfig, httpHost, mode, output, reactStrictMode, workbenchPort, workDir } = options;
174
+ // `preview` loads `.env.development` (the env hook treats only `build`/`deploy`
175
+ // as production), which points the workbench UI at a local dev server that
176
+ // isn't running here. Ignore the override and load the deployed UI instead.
177
+ const remoteUrl = mode === 'preview' ? undefined : parseRemoteUrl(process.env.SANITY_INTERNAL_WORKBENCH_REMOTE_URL);
174
178
  const organizationId = resolveOrganizationId(cliConfig);
175
179
  devDebug('Writing workbench runtime files');
176
180
  const root = await writeWorkbenchRuntime({
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/dev/startWorkbenchDevServer.ts"],"sourcesContent":["import {type CliConfig, type Output, resolveLocalPackage, subdebug} from '@sanity/cli-core'\nimport viteReact from '@vitejs/plugin-react'\nimport {createServer, type InlineConfig, type Plugin, type ViteDevServer} from 'vite'\nimport {z} from 'zod/mini'\n\nimport {isWorkbenchApp} from '../../defineApp.js'\nimport {createExposesTracker} from './exposesSetId.js'\nimport {\n acquireWorkbenchLock,\n type DevServerManifest,\n getRegisteredServers,\n readWorkbenchLock,\n watchRegistry,\n} from './registry.js'\nimport {writeWorkbenchRuntime} from './writeWorkbenchRuntime.js'\n\nconst devDebug = subdebug('dev')\n\nconst noop = async () => {}\n\n// Every server is a local app except a config-only one — an config\n// with no interfaces (the media library). A server with both lands in both channels.\nconst isLocalApp = (server: DevServerManifest): boolean => {\n const configOnly = Boolean(server.configs?.length) && !server.interfaces?.length\n return !configOnly\n}\n\nconst toApplicationsPayload = (servers: DevServerManifest[]) => ({\n applications: servers\n .filter((server) => isLocalApp(server))\n .map(({host, id, interfaces, manifest, port, projectId, type}) => ({\n host,\n id,\n interfaces,\n manifest,\n port,\n projectId,\n type,\n })),\n configs: servers.flatMap(({configs, host, port}) =>\n // The registry stores the config flat; the workbench wire shape nests the\n // type-specific payload (`fields` for a media library) under `config`, keyed\n // by the `appType` discriminator.\n (configs ?? []).map(({appType, id, moduleName, version, ...config}) => ({\n appType,\n config,\n id,\n moduleName,\n remoteURL: `http://${host}:${port}`,\n version,\n })),\n ),\n})\n\n/**\n * Bridge the dev-server registry into a workbench Vite server's HMR channel so\n * the page tracks apps as they come and go. A changed interface set means a\n * rebuilt remote — full-reload to drop the stale remote-entry; otherwise\n * rebroadcast for a soft reconcile. Returns a detach fn.\n */\nfunction attachViteDevServerBridge(server: ViteDevServer): () => void {\n server.ws.on('sanity:workbench:get-local-applications', (_, client) => {\n client.send(\n 'sanity:workbench:local-applications',\n toApplicationsPayload(getRegisteredServers()),\n )\n })\n\n const setTracker = createExposesTracker()\n const registryWatcher = watchRegistry((servers) => {\n if (setTracker.hasChanged(servers)) {\n server.ws.send({type: 'full-reload'})\n return\n }\n server.ws.send('sanity:workbench:local-applications', toApplicationsPayload(servers))\n })\n\n return () => registryWatcher.close()\n}\n\n/**\n * Make the workbench remote act as the machine's workbench: claim the singleton\n * lock so app `sanity dev`s register into it instead of each starting their own,\n * and bridge the registry so the remote shows the local apps. No-op lock if one\n * is already held.\n */\nexport function startWorkbenchRemoteCoordinator(options: {\n httpHost: string | undefined\n port: number\n server: ViteDevServer\n}): {close: () => Promise<void>} {\n const {httpHost, port, server} = options\n\n const lock = acquireWorkbenchLock({host: httpHost || 'localhost', port})\n if (!lock) {\n const existing = readWorkbenchLock()\n devDebug(\n 'Workbench lock already held by pid %d on port %d; bridging the registry without claiming it',\n existing?.pid,\n existing?.port,\n )\n }\n\n const detachBridge = attachViteDevServerBridge(server)\n\n return {\n close: async () => {\n detachBridge()\n lock?.release()\n },\n }\n}\n\ninterface WorkbenchDevServerResult {\n close: () => Promise<void>\n httpHost: string | undefined\n workbenchAvailable: boolean\n workbenchPort: number\n}\n\nexport interface StartWorkbenchOptions {\n /** Dependency-cache dir for the workbench Vite server, kept apart from the user's own. */\n cacheDir: string\n cliConfig: CliConfig\n httpHost: string | undefined\n httpPort: number\n output: Output\n /** Wrap the workbench in React StrictMode; the CLI resolves it (unset collapses to `false`). */\n reactStrictMode: boolean\n workDir: string\n}\n\n/**\n * Start the workbench dev server when federation is enabled and the workbench\n * package is available. If the desired port is already taken — by another\n * workbench instance or an unrelated process — fall back to running without a\n * workbench and let the app/studio dev server claim the configured port.\n */\nexport async function startWorkbenchDevServer(\n options: StartWorkbenchOptions,\n): Promise<WorkbenchDevServerResult> {\n const {\n cacheDir,\n cliConfig,\n httpHost,\n httpPort: workbenchPort,\n output,\n reactStrictMode,\n workDir,\n } = options\n\n // Workbench is opted into solely by calling `unstable_defineApp`.\n if (!isWorkbenchApp(cliConfig?.app)) {\n devDebug('Not a workbench app, skipping workbench dev server')\n return {close: noop, httpHost, workbenchAvailable: false, workbenchPort}\n }\n\n let workbenchAvailable = false\n\n try {\n await resolveLocalPackage('sanity/workbench', workDir)\n workbenchAvailable = true\n } catch {\n devDebug('Workbench not available, skipping workbench dev server')\n }\n\n if (!workbenchAvailable) {\n return {close: noop, httpHost, workbenchAvailable, workbenchPort}\n }\n\n // Acquire an exclusive lock — only one workbench per machine.\n // Uses O_EXCL which is atomic at the OS level, preventing races when\n // multiple `sanity dev` processes start simultaneously (e.g. via turbo).\n const workbenchLock = acquireWorkbenchLock({host: httpHost || 'localhost', port: workbenchPort})\n if (!workbenchLock) {\n const existing = readWorkbenchLock()\n devDebug(\n 'Workbench already running at pid %d on port %d, skipping',\n existing?.pid,\n existing?.port,\n )\n return {\n close: noop,\n httpHost: existing?.host ?? httpHost,\n workbenchAvailable: true,\n workbenchPort: existing?.port ?? workbenchPort,\n }\n }\n\n // The lock is already held; an exception here (runtime-file write failure,\n // invalid remote URL) would otherwise leak it until the next acquire prunes\n // the stale PID.\n let result: Awaited<ReturnType<typeof createWorkbenchViteServer>>\n try {\n result = await createWorkbenchViteServer({\n cacheDir,\n cliConfig,\n httpHost,\n output,\n reactStrictMode,\n workbenchPort,\n workDir,\n })\n } catch (err) {\n workbenchLock.release()\n throw err\n }\n\n if (!result) {\n workbenchLock.release()\n return {close: noop, httpHost, workbenchAvailable: false, workbenchPort}\n }\n\n const {actualPort, close} = result\n workbenchLock.updatePort(actualPort)\n\n return {\n close: async () => {\n workbenchLock.release()\n await close()\n },\n httpHost,\n workbenchAvailable,\n workbenchPort: actualPort,\n }\n}\n\ninterface CreateWorkbenchViteServerOptions {\n cacheDir: string\n cliConfig: CliConfig\n httpHost: string | undefined\n output: Output\n reactStrictMode: boolean\n workbenchPort: number\n workDir: string\n}\n\ninterface CreateWorkbenchViteServerResult {\n actualPort: number\n close: () => Promise<void>\n}\n\nasync function createWorkbenchViteServer(\n options: CreateWorkbenchViteServerOptions,\n): Promise<CreateWorkbenchViteServerResult | undefined> {\n const {cacheDir, cliConfig, httpHost, output, reactStrictMode, workbenchPort, workDir} = options\n\n const remoteUrl = parseRemoteUrl(process.env.SANITY_INTERNAL_WORKBENCH_REMOTE_URL)\n\n const organizationId = resolveOrganizationId(cliConfig)\n\n devDebug('Writing workbench runtime files')\n const root = await writeWorkbenchRuntime({\n cwd: workDir,\n organizationId,\n reactStrictMode,\n remoteUrl,\n })\n\n const viteConfig: InlineConfig = {\n // Custom cache directory so sanity's vite cache doesn't conflict with local vite projects\n cacheDir,\n configFile: false,\n define: {\n __SANITY_STAGING__: process.env.SANITY_INTERNAL_ENV === 'staging',\n 'import.meta.env.SANITY_INTERNAL_WORKBENCH_REMOTE_URL': JSON.stringify(remoteUrl),\n },\n logLevel: 'warn',\n mode: 'development',\n optimizeDeps: {\n // Exclude sanity/workbench (and its transitive dep @sanity/workbench)\n // from dep pre-bundling so that `import.meta.hot` is available at\n // runtime — pre-bundled modules do not receive Vite's HMR client\n // injection, which causes the custom HMR events for local application\n // discovery to silently not fire.\n exclude: ['sanity', '@sanity/workbench'],\n },\n // viteReact looks inert here — it transforms none of the host's own modules —\n // but it's load-bearing for the remotes. It serves the Fast Refresh runtime at\n // /@react-refresh and injects the preamble that defines window.$RefreshReg$. The\n // federated remotes loaded into this page are react-refresh transformed, so\n // without the preamble they throw \"can't detect preamble\", and without the\n // runtime their /@react-refresh import (wired by @module-federation/vite's\n // remoteHmr) fails. Dropping it as dead code broke every panel; see #1262.\n plugins: [viteReact(), ...(remoteUrl ? [remoteManifestPreloadHeaderPlugin(remoteUrl)] : [])],\n resolve: {dedupe: ['react', 'react-dom']},\n root,\n server: {\n host: httpHost,\n port: workbenchPort,\n strictPort: false,\n warmup: {\n clientFiles: ['./workbench.js'],\n },\n },\n }\n\n devDebug('Creating workbench vite server')\n const server = await createServer(viteConfig)\n try {\n await server.listen()\n } catch (err) {\n await server.close()\n output.warn(\n `Workbench dev server failed to start: ${err instanceof Error ? err.message : String(err)}`,\n )\n return undefined\n }\n\n // Vite may have picked a different port if the desired one was occupied\n const addr = server.httpServer?.address()\n const actualPort = typeof addr === 'object' && addr ? addr.port : workbenchPort\n\n // Fire-and-forget: warm the workbench remote's Vite transform pipeline so\n // the first browser request hits a pre-populated module graph.\n if (remoteUrl) {\n fetch(remoteUrl)\n .then((r) => r.body?.cancel())\n .catch(() => {})\n devDebug('Warming workbench remote at %s', remoteUrl)\n }\n\n const detachBridge = attachViteDevServerBridge(server)\n\n return {\n actualPort,\n close: async () => {\n detachBridge()\n await server.close()\n },\n }\n}\n\n// Workbench is opted into via `unstable_defineApp`, which carries the\n// organization ID. Deliberately no fallback (e.g. resolving it from the\n// configured project): the lookup would need an authenticated user and an\n// API round-trip on every startup for something the opt-in already declares.\nconst resolveOrganizationId = (cliConfig: CliConfig): string => {\n if (cliConfig.app?.organizationId) {\n return cliConfig.app.organizationId\n }\n\n throw new Error(\n 'Workbench requires an organization ID. Pass \"organizationId\" to unstable_defineApp() in sanity.cli.ts.',\n )\n}\n\n// Restricts protocol to http(s) so the URL is safe to interpolate into HTML\n// attributes and Link headers downstream.\nconst remoteUrlSchema = z.url({normalize: true, protocol: /^https?$/})\n\nfunction parseRemoteUrl(value: string | undefined): string | undefined {\n if (!value) return undefined\n\n const result = remoteUrlSchema.safeParse(value)\n\n if (!result.success) {\n throw new Error(\n `Invalid SANITY_INTERNAL_WORKBENCH_REMOTE_URL: ${value} (must be an http(s) URL)`,\n )\n }\n\n return result.data\n}\n\n/**\n * Sets a `Link: <remoteUrl>; rel=preload; as=fetch; crossorigin` response header\n * on the index document so the browser can start fetching the Module Federation\n * manifest as soon as response headers arrive — before HTML parsing reaches the\n * in-head preconnect hint. `as=fetch` matches how the federation runtime later\n * retrieves the JSON manifest, allowing the preload entry to satisfy that fetch.\n */\nfunction remoteManifestPreloadHeaderPlugin(remoteUrl: string): Plugin {\n return {\n apply: 'serve',\n configureServer(server) {\n server.middlewares.use((req, res, next) => {\n const pathname = (req.url || '/').split('?')[0]\n if (pathname === '/' || pathname === '/index.html') {\n res.setHeader('Link', `<${remoteUrl}>; rel=preload; as=fetch; crossorigin`)\n }\n next()\n })\n },\n name: 'sanity:workbench-remote-preload-header',\n }\n}\n"],"names":["resolveLocalPackage","subdebug","viteReact","createServer","z","isWorkbenchApp","createExposesTracker","acquireWorkbenchLock","getRegisteredServers","readWorkbenchLock","watchRegistry","writeWorkbenchRuntime","devDebug","noop","isLocalApp","server","configOnly","Boolean","configs","length","interfaces","toApplicationsPayload","servers","applications","filter","map","host","id","manifest","port","projectId","type","flatMap","appType","moduleName","version","config","remoteURL","attachViteDevServerBridge","ws","on","_","client","send","setTracker","registryWatcher","hasChanged","close","startWorkbenchRemoteCoordinator","options","httpHost","lock","existing","pid","detachBridge","release","startWorkbenchDevServer","cacheDir","cliConfig","httpPort","workbenchPort","output","reactStrictMode","workDir","app","workbenchAvailable","workbenchLock","result","createWorkbenchViteServer","err","actualPort","updatePort","remoteUrl","parseRemoteUrl","process","env","SANITY_INTERNAL_WORKBENCH_REMOTE_URL","organizationId","resolveOrganizationId","root","cwd","viteConfig","configFile","define","__SANITY_STAGING__","SANITY_INTERNAL_ENV","JSON","stringify","logLevel","mode","optimizeDeps","exclude","plugins","remoteManifestPreloadHeaderPlugin","resolve","dedupe","strictPort","warmup","clientFiles","listen","warn","Error","message","String","undefined","addr","httpServer","address","fetch","then","r","body","cancel","catch","remoteUrlSchema","url","normalize","protocol","value","safeParse","success","data","apply","configureServer","middlewares","use","req","res","next","pathname","split","setHeader","name"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/actions/dev/startWorkbenchDevServer.ts"],"sourcesContent":["import {type CliConfig, type Output, resolveLocalPackage, subdebug} from '@sanity/cli-core'\nimport viteReact from '@vitejs/plugin-react'\nimport {createServer, type InlineConfig, type Plugin, type ViteDevServer} from 'vite'\nimport {z} from 'zod/mini'\n\nimport {isWorkbenchApp} from '../../defineApp.js'\nimport {createExposesTracker} from './exposesSetId.js'\nimport {\n acquireWorkbenchLock,\n type DevServerManifest,\n getRegisteredServers,\n readWorkbenchLock,\n watchRegistry,\n} from './registry.js'\nimport {writeWorkbenchRuntime} from './writeWorkbenchRuntime.js'\n\nconst devDebug = subdebug('dev')\n\nconst noop = async () => {}\n\n// Every server is a local app except a config-only one — an config\n// with no interfaces (the media library). A server with both lands in both channels.\nconst isLocalApp = (server: DevServerManifest): boolean => {\n const configOnly = Boolean(server.configs?.length) && !server.interfaces?.length\n return !configOnly\n}\n\nconst toApplicationsPayload = (servers: DevServerManifest[]) => ({\n applications: servers\n .filter((server) => isLocalApp(server))\n .map(({host, id, interfaces, manifest, port, projectId, type}) => ({\n host,\n id,\n interfaces,\n manifest,\n port,\n projectId,\n type,\n })),\n configs: servers.flatMap(({configs, host, port}) =>\n // The registry stores the config flat; the workbench wire shape nests the\n // type-specific payload (`fields` for a media library) under `config`, keyed\n // by the `appType` discriminator.\n (configs ?? []).map(({appType, id, moduleName, version, ...config}) => ({\n appType,\n config,\n id,\n moduleName,\n remoteURL: `http://${host}:${port}`,\n version,\n })),\n ),\n})\n\n/**\n * Bridge the dev-server registry into a workbench Vite server's HMR channel so\n * the page tracks apps as they come and go. A changed interface set means a\n * rebuilt remote — full-reload to drop the stale remote-entry; otherwise\n * rebroadcast for a soft reconcile. Returns a detach fn.\n */\nfunction attachViteDevServerBridge(server: ViteDevServer): () => void {\n server.ws.on('sanity:workbench:get-local-applications', (_, client) => {\n client.send(\n 'sanity:workbench:local-applications',\n toApplicationsPayload(getRegisteredServers()),\n )\n })\n\n const setTracker = createExposesTracker()\n const registryWatcher = watchRegistry((servers) => {\n if (setTracker.hasChanged(servers)) {\n server.ws.send({type: 'full-reload'})\n return\n }\n server.ws.send('sanity:workbench:local-applications', toApplicationsPayload(servers))\n })\n\n return () => registryWatcher.close()\n}\n\n/**\n * Make the workbench remote act as the machine's workbench: claim the singleton\n * lock so app `sanity dev`s register into it instead of each starting their own,\n * and bridge the registry so the remote shows the local apps. No-op lock if one\n * is already held.\n */\nexport function startWorkbenchRemoteCoordinator(options: {\n httpHost: string | undefined\n port: number\n server: ViteDevServer\n}): {close: () => Promise<void>} {\n const {httpHost, port, server} = options\n\n const lock = acquireWorkbenchLock({host: httpHost || 'localhost', port})\n if (!lock) {\n const existing = readWorkbenchLock()\n devDebug(\n 'Workbench lock already held by pid %d on port %d; bridging the registry without claiming it',\n existing?.pid,\n existing?.port,\n )\n }\n\n const detachBridge = attachViteDevServerBridge(server)\n\n return {\n close: async () => {\n detachBridge()\n lock?.release()\n },\n }\n}\n\ninterface WorkbenchDevServerResult {\n close: () => Promise<void>\n httpHost: string | undefined\n workbenchAvailable: boolean\n workbenchPort: number\n}\n\nexport interface StartWorkbenchOptions {\n /** Dependency-cache dir for the workbench Vite server, kept apart from the user's own. */\n cacheDir: string\n cliConfig: CliConfig\n httpHost: string | undefined\n httpPort: number\n /** `dev` renders a live app and honors a local workbench-UI override; `preview`\n * (`sanity start`) previews a build and loads the deployed workbench UI. */\n mode: 'development' | 'preview'\n output: Output\n /** Wrap the workbench in React StrictMode; the CLI resolves it (unset collapses to `false`). */\n reactStrictMode: boolean\n workDir: string\n}\n\n/**\n * Start the workbench dev server when federation is enabled and the workbench\n * package is available. If the desired port is already taken — by another\n * workbench instance or an unrelated process — fall back to running without a\n * workbench and let the app/studio dev server claim the configured port.\n */\nexport async function startWorkbenchDevServer(\n options: StartWorkbenchOptions,\n): Promise<WorkbenchDevServerResult> {\n const {\n cacheDir,\n cliConfig,\n httpHost,\n httpPort: workbenchPort,\n mode,\n output,\n reactStrictMode,\n workDir,\n } = options\n\n // Workbench is opted into solely by calling `unstable_defineApp`.\n if (!isWorkbenchApp(cliConfig?.app)) {\n devDebug('Not a workbench app, skipping workbench dev server')\n return {close: noop, httpHost, workbenchAvailable: false, workbenchPort}\n }\n\n let workbenchAvailable = false\n\n try {\n await resolveLocalPackage('sanity/workbench', workDir)\n workbenchAvailable = true\n } catch {\n devDebug('Workbench not available, skipping workbench dev server')\n }\n\n if (!workbenchAvailable) {\n return {close: noop, httpHost, workbenchAvailable, workbenchPort}\n }\n\n // Acquire an exclusive lock — only one workbench per machine.\n // Uses O_EXCL which is atomic at the OS level, preventing races when\n // multiple `sanity dev` processes start simultaneously (e.g. via turbo).\n const workbenchLock = acquireWorkbenchLock({host: httpHost || 'localhost', port: workbenchPort})\n if (!workbenchLock) {\n const existing = readWorkbenchLock()\n devDebug(\n 'Workbench already running at pid %d on port %d, skipping',\n existing?.pid,\n existing?.port,\n )\n return {\n close: noop,\n httpHost: existing?.host ?? httpHost,\n workbenchAvailable: true,\n workbenchPort: existing?.port ?? workbenchPort,\n }\n }\n\n // The lock is already held; an exception here (runtime-file write failure,\n // invalid remote URL) would otherwise leak it until the next acquire prunes\n // the stale PID.\n let result: Awaited<ReturnType<typeof createWorkbenchViteServer>>\n try {\n result = await createWorkbenchViteServer({\n cacheDir,\n cliConfig,\n httpHost,\n mode,\n output,\n reactStrictMode,\n workbenchPort,\n workDir,\n })\n } catch (err) {\n workbenchLock.release()\n throw err\n }\n\n if (!result) {\n workbenchLock.release()\n return {close: noop, httpHost, workbenchAvailable: false, workbenchPort}\n }\n\n const {actualPort, close} = result\n workbenchLock.updatePort(actualPort)\n\n return {\n close: async () => {\n workbenchLock.release()\n await close()\n },\n httpHost,\n workbenchAvailable,\n workbenchPort: actualPort,\n }\n}\n\ninterface CreateWorkbenchViteServerOptions {\n cacheDir: string\n cliConfig: CliConfig\n httpHost: string | undefined\n mode: 'development' | 'preview'\n output: Output\n reactStrictMode: boolean\n workbenchPort: number\n workDir: string\n}\n\ninterface CreateWorkbenchViteServerResult {\n actualPort: number\n close: () => Promise<void>\n}\n\nasync function createWorkbenchViteServer(\n options: CreateWorkbenchViteServerOptions,\n): Promise<CreateWorkbenchViteServerResult | undefined> {\n const {cacheDir, cliConfig, httpHost, mode, output, reactStrictMode, workbenchPort, workDir} =\n options\n\n // `preview` loads `.env.development` (the env hook treats only `build`/`deploy`\n // as production), which points the workbench UI at a local dev server that\n // isn't running here. Ignore the override and load the deployed UI instead.\n const remoteUrl =\n mode === 'preview'\n ? undefined\n : parseRemoteUrl(process.env.SANITY_INTERNAL_WORKBENCH_REMOTE_URL)\n\n const organizationId = resolveOrganizationId(cliConfig)\n\n devDebug('Writing workbench runtime files')\n const root = await writeWorkbenchRuntime({\n cwd: workDir,\n organizationId,\n reactStrictMode,\n remoteUrl,\n })\n\n const viteConfig: InlineConfig = {\n // Custom cache directory so sanity's vite cache doesn't conflict with local vite projects\n cacheDir,\n configFile: false,\n define: {\n __SANITY_STAGING__: process.env.SANITY_INTERNAL_ENV === 'staging',\n 'import.meta.env.SANITY_INTERNAL_WORKBENCH_REMOTE_URL': JSON.stringify(remoteUrl),\n },\n logLevel: 'warn',\n mode: 'development',\n optimizeDeps: {\n // Exclude sanity/workbench (and its transitive dep @sanity/workbench)\n // from dep pre-bundling so that `import.meta.hot` is available at\n // runtime — pre-bundled modules do not receive Vite's HMR client\n // injection, which causes the custom HMR events for local application\n // discovery to silently not fire.\n exclude: ['sanity', '@sanity/workbench'],\n },\n // viteReact looks inert here — it transforms none of the host's own modules —\n // but it's load-bearing for the remotes. It serves the Fast Refresh runtime at\n // /@react-refresh and injects the preamble that defines window.$RefreshReg$. The\n // federated remotes loaded into this page are react-refresh transformed, so\n // without the preamble they throw \"can't detect preamble\", and without the\n // runtime their /@react-refresh import (wired by @module-federation/vite's\n // remoteHmr) fails. Dropping it as dead code broke every panel; see #1262.\n plugins: [viteReact(), ...(remoteUrl ? [remoteManifestPreloadHeaderPlugin(remoteUrl)] : [])],\n resolve: {dedupe: ['react', 'react-dom']},\n root,\n server: {\n host: httpHost,\n port: workbenchPort,\n strictPort: false,\n warmup: {\n clientFiles: ['./workbench.js'],\n },\n },\n }\n\n devDebug('Creating workbench vite server')\n const server = await createServer(viteConfig)\n try {\n await server.listen()\n } catch (err) {\n await server.close()\n output.warn(\n `Workbench dev server failed to start: ${err instanceof Error ? err.message : String(err)}`,\n )\n return undefined\n }\n\n // Vite may have picked a different port if the desired one was occupied\n const addr = server.httpServer?.address()\n const actualPort = typeof addr === 'object' && addr ? addr.port : workbenchPort\n\n // Fire-and-forget: warm the workbench remote's Vite transform pipeline so\n // the first browser request hits a pre-populated module graph.\n if (remoteUrl) {\n fetch(remoteUrl)\n .then((r) => r.body?.cancel())\n .catch(() => {})\n devDebug('Warming workbench remote at %s', remoteUrl)\n }\n\n const detachBridge = attachViteDevServerBridge(server)\n\n return {\n actualPort,\n close: async () => {\n detachBridge()\n await server.close()\n },\n }\n}\n\n// Workbench is opted into via `unstable_defineApp`, which carries the\n// organization ID. Deliberately no fallback (e.g. resolving it from the\n// configured project): the lookup would need an authenticated user and an\n// API round-trip on every startup for something the opt-in already declares.\nconst resolveOrganizationId = (cliConfig: CliConfig): string => {\n if (cliConfig.app?.organizationId) {\n return cliConfig.app.organizationId\n }\n\n throw new Error(\n 'Workbench requires an organization ID. Pass \"organizationId\" to unstable_defineApp() in sanity.cli.ts.',\n )\n}\n\n// Restricts protocol to http(s) so the URL is safe to interpolate into HTML\n// attributes and Link headers downstream.\nconst remoteUrlSchema = z.url({normalize: true, protocol: /^https?$/})\n\nfunction parseRemoteUrl(value: string | undefined): string | undefined {\n if (!value) return undefined\n\n const result = remoteUrlSchema.safeParse(value)\n\n if (!result.success) {\n throw new Error(\n `Invalid SANITY_INTERNAL_WORKBENCH_REMOTE_URL: ${value} (must be an http(s) URL)`,\n )\n }\n\n return result.data\n}\n\n/**\n * Sets a `Link: <remoteUrl>; rel=preload; as=fetch; crossorigin` response header\n * on the index document so the browser can start fetching the Module Federation\n * manifest as soon as response headers arrive — before HTML parsing reaches the\n * in-head preconnect hint. `as=fetch` matches how the federation runtime later\n * retrieves the JSON manifest, allowing the preload entry to satisfy that fetch.\n */\nfunction remoteManifestPreloadHeaderPlugin(remoteUrl: string): Plugin {\n return {\n apply: 'serve',\n configureServer(server) {\n server.middlewares.use((req, res, next) => {\n const pathname = (req.url || '/').split('?')[0]\n if (pathname === '/' || pathname === '/index.html') {\n res.setHeader('Link', `<${remoteUrl}>; rel=preload; as=fetch; crossorigin`)\n }\n next()\n })\n },\n name: 'sanity:workbench-remote-preload-header',\n }\n}\n"],"names":["resolveLocalPackage","subdebug","viteReact","createServer","z","isWorkbenchApp","createExposesTracker","acquireWorkbenchLock","getRegisteredServers","readWorkbenchLock","watchRegistry","writeWorkbenchRuntime","devDebug","noop","isLocalApp","server","configOnly","Boolean","configs","length","interfaces","toApplicationsPayload","servers","applications","filter","map","host","id","manifest","port","projectId","type","flatMap","appType","moduleName","version","config","remoteURL","attachViteDevServerBridge","ws","on","_","client","send","setTracker","registryWatcher","hasChanged","close","startWorkbenchRemoteCoordinator","options","httpHost","lock","existing","pid","detachBridge","release","startWorkbenchDevServer","cacheDir","cliConfig","httpPort","workbenchPort","mode","output","reactStrictMode","workDir","app","workbenchAvailable","workbenchLock","result","createWorkbenchViteServer","err","actualPort","updatePort","remoteUrl","undefined","parseRemoteUrl","process","env","SANITY_INTERNAL_WORKBENCH_REMOTE_URL","organizationId","resolveOrganizationId","root","cwd","viteConfig","configFile","define","__SANITY_STAGING__","SANITY_INTERNAL_ENV","JSON","stringify","logLevel","optimizeDeps","exclude","plugins","remoteManifestPreloadHeaderPlugin","resolve","dedupe","strictPort","warmup","clientFiles","listen","warn","Error","message","String","addr","httpServer","address","fetch","then","r","body","cancel","catch","remoteUrlSchema","url","normalize","protocol","value","safeParse","success","data","apply","configureServer","middlewares","use","req","res","next","pathname","split","setHeader","name"],"mappings":"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"}
@@ -11,6 +11,7 @@ export default defineCliConfig({
11
11
  app: unstable_defineApp({
12
12
  name: '%name%',
13
13
  title: '%title%',
14
+ slug: '%slug%',
14
15
  organizationId: '%organizationId%',
15
16
  entry: '%entry%',
16
17
  }),
@@ -30,6 +31,7 @@ export default defineCliConfig({
30
31
  app: unstable_defineApp({
31
32
  name: '%name%',
32
33
  title: '%title%',
34
+ slug: '%slug%',
33
35
  organizationId: '%organizationId%',
34
36
  }),
35
37
  deployment: {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/init/cliConfig.ts"],"sourcesContent":["// `sanity.cli.ts` templates for workbench (`unstable_defineApp`) projects,\n// consumed by the CLI's `init` scaffolding. The branded `unstable_defineApp`\n// result is the sole workbench (module-federation) opt-in, so its config shape\n// is workbench's to own; the CLI keeps the non-workbench templates and the\n// `%placeholder%` substitution. `%name%`/`%title%`/etc. are filled in by the\n// CLI's template processor.\n\n/** App scaffold — `entry` auto-declares the navigable app view. */\nexport const workbenchAppConfigTemplate = `\nimport {defineCliConfig, unstable_defineApp} from 'sanity/cli'\n\nexport default defineCliConfig({\n app: unstable_defineApp({\n name: '%name%',\n title: '%title%',\n organizationId: '%organizationId%',\n entry: '%entry%',\n }),\n})\n`\n\n/**\n * Studio scaffold — brands with name/title only, no `entry` (studio app views\n * aren't implemented yet).\n */\nexport const workbenchStudioConfigTemplate = `\nimport {defineCliConfig, unstable_defineApp} from 'sanity/cli'\n\nexport default defineCliConfig({\n api: {\n projectId: '%projectId%',\n dataset: '%dataset%'\n },\n app: unstable_defineApp({\n name: '%name%',\n title: '%title%',\n organizationId: '%organizationId%',\n }),\n deployment: {\n /**\n * Enable auto-updates for studios.\n * Learn more at https://www.sanity.io/docs/studio/latest-version-of-sanity#k47faf43faf56\n */\n autoUpdates: __BOOL__autoUpdates__,\n },\n})\n`\n"],"names":["workbenchAppConfigTemplate","workbenchStudioConfigTemplate"],"mappings":"AAAA,2EAA2E;AAC3E,6EAA6E;AAC7E,+EAA+E;AAC/E,2EAA2E;AAC3E,6EAA6E;AAC7E,4BAA4B;AAE5B,iEAAiE,GACjE,OAAO,MAAMA,6BAA6B,CAAC;;;;;;;;;;;AAW3C,CAAC,CAAA;AAED;;;CAGC,GACD,OAAO,MAAMC,gCAAgC,CAAC;;;;;;;;;;;;;;;;;;;;;AAqB9C,CAAC,CAAA"}
1
+ {"version":3,"sources":["../../../src/actions/init/cliConfig.ts"],"sourcesContent":["// `sanity.cli.ts` templates for workbench (`unstable_defineApp`) projects,\n// consumed by the CLI's `init` scaffolding. The branded `unstable_defineApp`\n// result is the sole workbench (module-federation) opt-in, so its config shape\n// is workbench's to own; the CLI keeps the non-workbench templates and the\n// `%placeholder%` substitution. `%name%`/`%title%`/etc. are filled in by the\n// CLI's template processor.\n\n/** App scaffold — `entry` auto-declares the navigable app view. */\nexport const workbenchAppConfigTemplate = `\nimport {defineCliConfig, unstable_defineApp} from 'sanity/cli'\n\nexport default defineCliConfig({\n app: unstable_defineApp({\n name: '%name%',\n title: '%title%',\n slug: '%slug%',\n organizationId: '%organizationId%',\n entry: '%entry%',\n }),\n})\n`\n\n/**\n * Studio scaffold — brands with name/title only, no `entry` (studio app views\n * aren't implemented yet).\n */\nexport const workbenchStudioConfigTemplate = `\nimport {defineCliConfig, unstable_defineApp} from 'sanity/cli'\n\nexport default defineCliConfig({\n api: {\n projectId: '%projectId%',\n dataset: '%dataset%'\n },\n app: unstable_defineApp({\n name: '%name%',\n title: '%title%',\n slug: '%slug%',\n organizationId: '%organizationId%',\n }),\n deployment: {\n /**\n * Enable auto-updates for studios.\n * Learn more at https://www.sanity.io/docs/studio/latest-version-of-sanity#k47faf43faf56\n */\n autoUpdates: __BOOL__autoUpdates__,\n },\n})\n`\n"],"names":["workbenchAppConfigTemplate","workbenchStudioConfigTemplate"],"mappings":"AAAA,2EAA2E;AAC3E,6EAA6E;AAC7E,+EAA+E;AAC/E,2EAA2E;AAC3E,6EAA6E;AAC7E,4BAA4B;AAE5B,iEAAiE,GACjE,OAAO,MAAMA,6BAA6B,CAAC;;;;;;;;;;;;AAY3C,CAAC,CAAA;AAED;;;CAGC,GACD,OAAO,MAAMC,gCAAgC,CAAC;;;;;;;;;;;;;;;;;;;;;;AAsB9C,CAAC,CAAA"}
@@ -0,0 +1,53 @@
1
+ import { subdebug } from '@sanity/cli-core';
2
+ import { preview } from 'vite';
3
+ import { checkBuiltOutput } from '../deploy/checkBuiltOutput.js';
4
+ const previewDebug = subdebug('preview');
5
+ /**
6
+ * Serve a built workbench app's `dist` as static files over a Vite `preview`
7
+ * server, so its `mf-manifest.json` + `remote-entry.js` are reachable exactly as
8
+ * a live `sanity dev` remote would be — the workbench then federates it in.
9
+ *
10
+ * A missing federation build surfaces as `BUILD_NOT_FOUND` (from
11
+ * `checkBuiltOutput`) so the command shows the same "run sanity build" hint the
12
+ * studio preview path does; real I/O errors surface as themselves.
13
+ */ export async function serveBuiltApplication(options) {
14
+ const { cacheDir, httpHost, httpPort, outDir, workDir } = options;
15
+ await checkBuiltOutput(outDir);
16
+ const config = {
17
+ // Serve at the root so `mf-manifest.json` and its `publicPath: 'auto'` chunks
18
+ // resolve under the registered remote URL.
19
+ base: '/',
20
+ // Needed for vite to serve `outDir` itself rather than `outDir/dist`.
21
+ build: {
22
+ outDir
23
+ },
24
+ cacheDir,
25
+ configFile: false,
26
+ logLevel: 'warn',
27
+ mode: 'production',
28
+ preview: {
29
+ host: httpHost,
30
+ port: httpPort,
31
+ strictPort: false
32
+ },
33
+ root: workDir
34
+ };
35
+ previewDebug('Serving built remote from %s', outDir);
36
+ const server = await preview(config);
37
+ const addr = server.httpServer.address();
38
+ const port = typeof addr === 'object' && addr ? addr.port : httpPort;
39
+ return {
40
+ // Drop idle keep-alive sockets so shutdown doesn't stall on a browser that
41
+ // kept the connection open — otherwise `close` waits out the teardown grace.
42
+ // `closeAllConnections` isn't on Vite's `HttpServer` type, but is on the
43
+ // underlying node server (18.2+); the optional call tolerates its absence.
44
+ close: ()=>new Promise((resolve, reject)=>{
45
+ server.httpServer.close((err)=>err ? reject(err) : resolve());
46
+ server.httpServer.closeAllConnections?.();
47
+ }),
48
+ host: httpHost,
49
+ port
50
+ };
51
+ }
52
+
53
+ //# sourceMappingURL=serveBuiltApplication.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/actions/preview/serveBuiltApplication.ts"],"sourcesContent":["import {subdebug} from '@sanity/cli-core'\nimport {type InlineConfig, preview} from 'vite'\n\nimport {checkBuiltOutput} from '../deploy/checkBuiltOutput.js'\n\nconst previewDebug = subdebug('preview')\n\nexport interface BuiltApplicationServer {\n close: () => Promise<void>\n /** Routable host the remote is reachable at, for the registry's remote URL. */\n host: string\n /** Port Vite bound — may differ from the requested one under non-strict ports. */\n port: number\n}\n\n/**\n * Serve a built workbench app's `dist` as static files over a Vite `preview`\n * server, so its `mf-manifest.json` + `remote-entry.js` are reachable exactly as\n * a live `sanity dev` remote would be — the workbench then federates it in.\n *\n * A missing federation build surfaces as `BUILD_NOT_FOUND` (from\n * `checkBuiltOutput`) so the command shows the same \"run sanity build\" hint the\n * studio preview path does; real I/O errors surface as themselves.\n */\nexport async function serveBuiltApplication(options: {\n cacheDir: string\n httpHost: string\n httpPort: number\n outDir: string\n workDir: string\n}): Promise<BuiltApplicationServer> {\n const {cacheDir, httpHost, httpPort, outDir, workDir} = options\n\n await checkBuiltOutput(outDir)\n\n const config: InlineConfig = {\n // Serve at the root so `mf-manifest.json` and its `publicPath: 'auto'` chunks\n // resolve under the registered remote URL.\n base: '/',\n // Needed for vite to serve `outDir` itself rather than `outDir/dist`.\n build: {outDir},\n cacheDir,\n configFile: false,\n logLevel: 'warn',\n mode: 'production',\n preview: {host: httpHost, port: httpPort, strictPort: false},\n root: workDir,\n }\n\n previewDebug('Serving built remote from %s', outDir)\n const server = await preview(config)\n\n const addr = server.httpServer.address()\n const port = typeof addr === 'object' && addr ? addr.port : httpPort\n\n return {\n // Drop idle keep-alive sockets so shutdown doesn't stall on a browser that\n // kept the connection open — otherwise `close` waits out the teardown grace.\n // `closeAllConnections` isn't on Vite's `HttpServer` type, but is on the\n // underlying node server (18.2+); the optional call tolerates its absence.\n close: () =>\n new Promise((resolve, reject) => {\n server.httpServer.close((err) => (err ? reject(err) : resolve()))\n ;(server.httpServer as {closeAllConnections?: () => void}).closeAllConnections?.()\n }),\n host: httpHost,\n port,\n }\n}\n"],"names":["subdebug","preview","checkBuiltOutput","previewDebug","serveBuiltApplication","options","cacheDir","httpHost","httpPort","outDir","workDir","config","base","build","configFile","logLevel","mode","host","port","strictPort","root","server","addr","httpServer","address","close","Promise","resolve","reject","err","closeAllConnections"],"mappings":"AAAA,SAAQA,QAAQ,QAAO,mBAAkB;AACzC,SAA2BC,OAAO,QAAO,OAAM;AAE/C,SAAQC,gBAAgB,QAAO,gCAA+B;AAE9D,MAAMC,eAAeH,SAAS;AAU9B;;;;;;;;CAQC,GACD,OAAO,eAAeI,sBAAsBC,OAM3C;IACC,MAAM,EAACC,QAAQ,EAAEC,QAAQ,EAAEC,QAAQ,EAAEC,MAAM,EAAEC,OAAO,EAAC,GAAGL;IAExD,MAAMH,iBAAiBO;IAEvB,MAAME,SAAuB;QAC3B,8EAA8E;QAC9E,2CAA2C;QAC3CC,MAAM;QACN,sEAAsE;QACtEC,OAAO;YAACJ;QAAM;QACdH;QACAQ,YAAY;QACZC,UAAU;QACVC,MAAM;QACNf,SAAS;YAACgB,MAAMV;YAAUW,MAAMV;YAAUW,YAAY;QAAK;QAC3DC,MAAMV;IACR;IAEAP,aAAa,gCAAgCM;IAC7C,MAAMY,SAAS,MAAMpB,QAAQU;IAE7B,MAAMW,OAAOD,OAAOE,UAAU,CAACC,OAAO;IACtC,MAAMN,OAAO,OAAOI,SAAS,YAAYA,OAAOA,KAAKJ,IAAI,GAAGV;IAE5D,OAAO;QACL,2EAA2E;QAC3E,6EAA6E;QAC7E,yEAAyE;QACzE,2EAA2E;QAC3EiB,OAAO,IACL,IAAIC,QAAQ,CAACC,SAASC;gBACpBP,OAAOE,UAAU,CAACE,KAAK,CAAC,CAACI,MAASA,MAAMD,OAAOC,OAAOF;gBACpDN,OAAOE,UAAU,CAAwCO,mBAAmB;YAChF;QACFb,MAAMV;QACNW;IACF;AACF"}
@@ -0,0 +1,107 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { styleText } from 'node:util';
4
+ import { findProjectRoot } from '@sanity/cli-core';
5
+ import { buildAppId, SANITY_APP_ID_FILE } from '../../appId.js';
6
+ import { resolveWorkbenchApp } from '../../resolveWorkbenchApp.js';
7
+ import { createServerLifecycle, toDisplayHost } from '../../util/serverOrchestration.js';
8
+ import { deriveConfigs, deriveInterfaces } from '../dev/deriveInterfaces.js';
9
+ import { registerDevServer } from '../dev/registry.js';
10
+ import { startWorkbenchDevServer } from '../dev/startWorkbenchDevServer.js';
11
+ import { serveBuiltApplication } from './serveBuiltApplication.js';
12
+ /**
13
+ * `sanity start` for a workbench app: serve a production build the way dev serves
14
+ * a live one. The same singleton workbench shell renders it and the same registry
15
+ * advertises it — only the remote differs, static files from the build output
16
+ * instead of a live Vite dev server. There's no config watcher or rebuild: a
17
+ * build is fixed, so nothing re-syncs.
18
+ *
19
+ * A running workbench claims the configured port, so the built remote binds the
20
+ * next one. Without one the remote takes the configured port and announces its
21
+ * own URL.
22
+ */ export async function startWorkbenchPreview(options) {
23
+ const { cacheDir, checkForDeprecatedAppId, cliConfig, extractManifest, httpHost, httpPort, isApp, outDir, output, reactStrictMode, workDir } = options;
24
+ const { close, closers, installSignalHandlers } = createServerLifecycle();
25
+ const workbench = await startWorkbenchDevServer({
26
+ cacheDir,
27
+ cliConfig,
28
+ httpHost,
29
+ httpPort,
30
+ mode: 'preview',
31
+ output,
32
+ reactStrictMode,
33
+ workDir
34
+ });
35
+ closers.push(workbench.close);
36
+ const remotePort = workbench.workbenchAvailable ? workbench.workbenchPort + 1 : httpPort;
37
+ const remote = await serveBuiltApplication({
38
+ cacheDir,
39
+ httpHost,
40
+ httpPort: remotePort,
41
+ outDir,
42
+ workDir
43
+ }).catch(async (err)=>{
44
+ await close();
45
+ throw err;
46
+ });
47
+ closers.push(remote.close);
48
+ try {
49
+ // Callers provide CLI-only validation and manifest extraction to keep them
50
+ // out of workbench-cli.
51
+ checkForDeprecatedAppId();
52
+ const configPath = (await findProjectRoot(workDir)).path;
53
+ const workbench = resolveWorkbenchApp(cliConfig);
54
+ // Read the id the build inlined so start matches it even for a deploy build
55
+ // (which carries the API id, not the shape hash); fall back for older builds.
56
+ const inlinedId = await readInlinedAppId(outDir);
57
+ const registration = registerDevServer({
58
+ configs: deriveConfigs(cliConfig.app),
59
+ host: remote.host,
60
+ // `start` serves a build, so it advertises the build's inlined id (matching
61
+ // the bundle's `__SANITY_APP_ID__`), not the dev host-port.
62
+ id: workbench ? inlinedId ?? buildAppId(workbench) : `${remote.host}-${remote.port}`,
63
+ interfaces: deriveInterfaces(cliConfig.app, {
64
+ isApp
65
+ }),
66
+ manifest: await extractManifest({
67
+ configPath,
68
+ workDir
69
+ }),
70
+ manifestUpdatedAt: new Date().toISOString(),
71
+ port: remote.port,
72
+ projectId: cliConfig?.api?.projectId,
73
+ type: isApp ? 'coreApp' : 'studio',
74
+ workDir
75
+ });
76
+ closers.push(async ()=>registration.release());
77
+ } catch (err) {
78
+ await close();
79
+ throw err;
80
+ }
81
+ if (workbench.workbenchAvailable) {
82
+ const workbenchUrl = `http://${toDisplayHost(workbench.httpHost)}:${workbench.workbenchPort}`;
83
+ output.log(`Workbench preview server started at ${styleText([
84
+ 'blue',
85
+ 'underline'
86
+ ], workbenchUrl)} (serving build on port ${remote.port})`);
87
+ } else {
88
+ const remoteUrl = `http://${toDisplayHost(remote.host)}:${remote.port}`;
89
+ output.log(`Serving build at ${styleText([
90
+ 'blue',
91
+ 'underline'
92
+ ], remoteUrl)}`);
93
+ }
94
+ installSignalHandlers();
95
+ return {
96
+ close
97
+ };
98
+ }
99
+ /** The id the build inlined into its bundle, or undefined when absent. */ async function readInlinedAppId(outDir) {
100
+ try {
101
+ return (await readFile(path.join(outDir, SANITY_APP_ID_FILE), 'utf8')).trim() || undefined;
102
+ } catch {
103
+ return undefined;
104
+ }
105
+ }
106
+
107
+ //# sourceMappingURL=startWorkbenchPreview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/actions/preview/startWorkbenchPreview.ts"],"sourcesContent":["import {readFile} from 'node:fs/promises'\nimport path from 'node:path'\nimport {styleText} from 'node:util'\n\nimport {type CliConfig, findProjectRoot, type Output} from '@sanity/cli-core'\n\nimport {buildAppId, SANITY_APP_ID_FILE} from '../../appId.js'\nimport {resolveWorkbenchApp} from '../../resolveWorkbenchApp.js'\nimport {createServerLifecycle, toDisplayHost} from '../../util/serverOrchestration.js'\nimport {deriveConfigs, deriveInterfaces} from '../dev/deriveInterfaces.js'\nimport {type DevServerManifest, registerDevServer} from '../dev/registry.js'\nimport {startWorkbenchDevServer} from '../dev/startWorkbenchDevServer.js'\nimport {serveBuiltApplication} from './serveBuiltApplication.js'\n\nexport interface StartWorkbenchPreviewOptions {\n /** Directory for the workbench Vite server's dependency cache. */\n cacheDir: string\n /** CLI-domain `app.id`/`deployment.appId` deprecation check, run before registering. */\n checkForDeprecatedAppId: () => void\n cliConfig: CliConfig\n /** Extract the project manifest to inline into the registry (studio-vs-app handled by the CLI). */\n extractManifest: (params: {\n configPath: string\n workDir: string\n }) => Promise<DevServerManifest['manifest']>\n httpHost: string\n httpPort: number\n isApp: boolean\n /** The built `dist` directory to serve as the federation remote. */\n outDir: string\n output: Output\n reactStrictMode: boolean\n workDir: string\n}\n\n/**\n * `sanity start` for a workbench app: serve a production build the way dev serves\n * a live one. The same singleton workbench shell renders it and the same registry\n * advertises it — only the remote differs, static files from the build output\n * instead of a live Vite dev server. There's no config watcher or rebuild: a\n * build is fixed, so nothing re-syncs.\n *\n * A running workbench claims the configured port, so the built remote binds the\n * next one. Without one the remote takes the configured port and announces its\n * own URL.\n */\nexport async function startWorkbenchPreview(\n options: StartWorkbenchPreviewOptions,\n): Promise<{close: () => Promise<void>}> {\n const {\n cacheDir,\n checkForDeprecatedAppId,\n cliConfig,\n extractManifest,\n httpHost,\n httpPort,\n isApp,\n outDir,\n output,\n reactStrictMode,\n workDir,\n } = options\n\n const {close, closers, installSignalHandlers} = createServerLifecycle()\n\n const workbench = await startWorkbenchDevServer({\n cacheDir,\n cliConfig,\n httpHost,\n httpPort,\n mode: 'preview',\n output,\n reactStrictMode,\n workDir,\n })\n closers.push(workbench.close)\n\n const remotePort = workbench.workbenchAvailable ? workbench.workbenchPort + 1 : httpPort\n\n const remote = await serveBuiltApplication({\n cacheDir,\n httpHost,\n httpPort: remotePort,\n outDir,\n workDir,\n }).catch(async (err) => {\n await close()\n throw err\n })\n closers.push(remote.close)\n\n try {\n // Callers provide CLI-only validation and manifest extraction to keep them\n // out of workbench-cli.\n checkForDeprecatedAppId()\n const configPath = (await findProjectRoot(workDir)).path\n const workbench = resolveWorkbenchApp(cliConfig)\n // Read the id the build inlined so start matches it even for a deploy build\n // (which carries the API id, not the shape hash); fall back for older builds.\n const inlinedId = await readInlinedAppId(outDir)\n const registration = registerDevServer({\n configs: deriveConfigs(cliConfig.app),\n host: remote.host,\n // `start` serves a build, so it advertises the build's inlined id (matching\n // the bundle's `__SANITY_APP_ID__`), not the dev host-port.\n id: workbench ? (inlinedId ?? buildAppId(workbench)) : `${remote.host}-${remote.port}`,\n interfaces: deriveInterfaces(cliConfig.app, {isApp}),\n manifest: await extractManifest({configPath, workDir}),\n manifestUpdatedAt: new Date().toISOString(),\n port: remote.port,\n projectId: cliConfig?.api?.projectId,\n type: isApp ? 'coreApp' : 'studio',\n workDir,\n })\n closers.push(async () => registration.release())\n } catch (err) {\n await close()\n throw err\n }\n\n if (workbench.workbenchAvailable) {\n const workbenchUrl = `http://${toDisplayHost(workbench.httpHost)}:${workbench.workbenchPort}`\n output.log(\n `Workbench preview server started at ${styleText(['blue', 'underline'], workbenchUrl)} (serving build on port ${remote.port})`,\n )\n } else {\n const remoteUrl = `http://${toDisplayHost(remote.host)}:${remote.port}`\n output.log(`Serving build at ${styleText(['blue', 'underline'], remoteUrl)}`)\n }\n\n installSignalHandlers()\n\n return {close}\n}\n\n/** The id the build inlined into its bundle, or undefined when absent. */\nasync function readInlinedAppId(outDir: string): Promise<string | undefined> {\n try {\n return (await readFile(path.join(outDir, SANITY_APP_ID_FILE), 'utf8')).trim() || undefined\n } catch {\n return undefined\n }\n}\n"],"names":["readFile","path","styleText","findProjectRoot","buildAppId","SANITY_APP_ID_FILE","resolveWorkbenchApp","createServerLifecycle","toDisplayHost","deriveConfigs","deriveInterfaces","registerDevServer","startWorkbenchDevServer","serveBuiltApplication","startWorkbenchPreview","options","cacheDir","checkForDeprecatedAppId","cliConfig","extractManifest","httpHost","httpPort","isApp","outDir","output","reactStrictMode","workDir","close","closers","installSignalHandlers","workbench","mode","push","remotePort","workbenchAvailable","workbenchPort","remote","catch","err","configPath","inlinedId","readInlinedAppId","registration","configs","app","host","id","port","interfaces","manifest","manifestUpdatedAt","Date","toISOString","projectId","api","type","release","workbenchUrl","log","remoteUrl","join","trim","undefined"],"mappings":"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"}