@sanity/workbench-cli 2.0.1 → 2.1.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.
- package/dist/_exports/_internal_render.d.ts +12 -0
- package/dist/_exports/_internal_render.js +3 -0
- package/dist/_exports/_internal_render.js.map +1 -0
- package/dist/_exports/build.d.ts +13 -253
- package/dist/_exports/contract-DyG11fQ7.d.ts +45 -0
- package/dist/_exports/defineApp-DSvQx8rf.d.ts +151 -0
- package/dist/_exports/deploy.d.ts +94 -427
- package/dist/_exports/dev.d.ts +26 -196
- package/dist/_exports/index.d.ts +74 -407
- package/dist/_exports/init.d.ts +5 -9
- package/dist/_exports/preview.d.ts +20 -187
- package/dist/_exports/registry-DI7hnTof.d.ts +102 -0
- package/dist/_exports/resolveWorkbenchApp-Be9eU8mu.d.ts +41 -0
- package/dist/_exports/summarizeInterfaces-DLsq6P43.d.ts +62 -0
- package/dist/_exports/undeploy.d.ts +15 -296
- package/dist/actions/build/vite/plugins/plugin-module-federation.js +7 -15
- package/dist/actions/build/vite/plugins/plugin-module-federation.js.map +1 -1
- package/dist/actions/dev/registry.js +9 -0
- package/dist/actions/dev/registry.js.map +1 -1
- package/dist/actions/dev/startDevServerRegistration.js +50 -3
- package/dist/actions/dev/startDevServerRegistration.js.map +1 -1
- package/dist/actions/dev/startWorkbenchDevServer.js +15 -36
- package/dist/actions/dev/startWorkbenchDevServer.js.map +1 -1
- package/dist/actions/dev/writeWorkbenchRuntime.js +2 -2
- package/dist/actions/dev/writeWorkbenchRuntime.js.map +1 -1
- package/dist/defineApp.js +1 -4
- package/dist/defineApp.js.map +1 -1
- package/dist/renderDashboard.js +63 -0
- package/dist/renderDashboard.js.map +1 -0
- package/dist/services/applications.js +7 -7
- package/dist/services/applications.js.map +1 -1
- package/dist/services/installations.js +4 -4
- package/dist/services/installations.js.map +1 -1
- package/package.json +13 -7
|
@@ -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 {isWorkbenchApp} from '../../defineApp.js'\nimport {deriveInterfaces} from '../../deriveInterfaces.js'\nimport {formatWorkbenchAppErrors, validateWorkbenchApp} from '../../validateWorkbenchApp.js'\nimport {deriveConfigs} from './deriveConfigs.js'\nimport {trackExposesSet} from './exposesSetId.js'\nimport {type DevServerManifest, getRegisteredServers, 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/**\n * Log any config validation errors without aborting. Unlike build and deploy,\n * dev stays up on an invalid config so the author sees the errors and fixes them\n * live on the next save.\n */\nfunction reportConfigErrors(app: CliConfig['app'], output: Output): void {\n const errors = validateWorkbenchApp(app)\n if (errors.length === 0) return\n output.warn(formatWorkbenchAppErrors(errors))\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 reportConfigErrors(cliConfig.app, output)\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 = await deriveConfigs(cliConfig.app)\n\n const id = isWorkbenchApp(cliConfig.app) ? cliConfig.app.slug : undefined\n\n const devServer = id ? getRegisteredServers().find((server) => server.id === id) : undefined\n\n if (id && devServer) {\n output.error(\n `The app \"${id}\" is already served by another dev server running on port ${devServer.port}, ` +\n \"so the workbench can't tell them apart and this one stays out of it. \" +\n 'Stop that server, or give this app its own `slug` in sanity.cli.ts.',\n {exit: false},\n )\n return {close: async () => {}}\n }\n\n const registration = registerDevServer({\n configs,\n host: appHost,\n id,\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 reportConfigErrors(app, output)\n return {\n configs: await 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","isWorkbenchApp","deriveInterfaces","formatWorkbenchAppErrors","validateWorkbenchApp","deriveConfigs","trackExposesSet","getRegisteredServers","registerDevServer","startDevManifestWatcher","reportConfigErrors","app","output","errors","length","warn","serverAddress","server","resolvedHost","config","host","addr","httpServer","address","port","startDevServerRegistration","options","cliConfig","extractManifest","isApp","onInterfaceSetChange","workDir","appHost","appPort","interfaces","configs","id","slug","undefined","devServer","find","error","exit","close","registration","projectId","api","type","exposesSet","watcher","extract","params","manifest","extraWatchFilenames","update","patch","changed","rebuiltServer","commit","release"],"mappings":"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"}
|
|
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 {isWorkbenchApp} from '../../defineApp.js'\nimport {deriveInterfaces} from '../../deriveInterfaces.js'\nimport {formatWorkbenchAppErrors, validateWorkbenchApp} from '../../validateWorkbenchApp.js'\nimport {deriveConfigs} from './deriveConfigs.js'\nimport {trackExposesSet} from './exposesSetId.js'\nimport {\n type DevServerManifest,\n getRegisteredServers,\n isConfigOnlyServer,\n registerDevServer,\n} 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/**\n * Log any config validation errors without aborting. Unlike build and deploy,\n * dev stays up on an invalid config so the author sees the errors and fixes them\n * live on the next save.\n */\nfunction reportConfigErrors(app: CliConfig['app'], output: Output): void {\n const errors = validateWorkbenchApp(app)\n if (errors.length === 0) return\n output.warn(formatWorkbenchAppErrors(errors))\n}\n\n/**\n * A live server — other than this process — already playing the given role for\n * the slug. Only a *same-role* duplicate is a conflict: a config-only server\n * (configs, no interfaces — e.g. a media-library config app) is never routed\n * as an app, so it may share a slug with the app server it configures. The\n * workbench renders the app and publishes both servers' configs, and can\n * always tell them apart.\n */\nfunction findSameRoleConflict(id: string, configOnly: boolean): DevServerManifest | undefined {\n return getRegisteredServers().find(\n (server) =>\n server.pid !== process.pid && server.id === id && isConfigOnlyServer(server) === configOnly,\n )\n}\n\n/**\n * Remedy line for a same-role slug conflict, phrased for the role. Changing the\n * slug is only real advice for an app — a config app's slug is fixed by the\n * app it configures (e.g. `unstable_defineMediaLibrary` hard-codes it).\n */\nfunction conflictRemedy(configOnly: boolean): string {\n return configOnly\n ? 'Stop that server first.'\n : 'Stop that server, or give this app its own `slug` in sanity.cli.ts.'\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 reportConfigErrors(cliConfig.app, output)\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 = await deriveConfigs(cliConfig.app)\n\n const id = isWorkbenchApp(cliConfig.app) ? cliConfig.app.slug : undefined\n\n const configOnly = isConfigOnlyServer({configs, interfaces})\n const devServer = id ? findSameRoleConflict(id, configOnly) : undefined\n\n if (id && devServer) {\n const subject = configOnly ? `A config for \"${id}\"` : `The app \"${id}\"`\n output.error(\n `${subject} is already served by another dev server running on port ${devServer.port}, ` +\n \"so the workbench can't tell them apart and this one stays out of it. \" +\n conflictRemedy(configOnly),\n {exit: false},\n )\n return {close: async () => {}}\n }\n\n // The role the registry currently advertises for this server; a config edit\n // can flip it (see the re-check in `update`). Committed only after a\n // successful registry patch, so a failed pass re-checks on the next save.\n let registeredConfigOnly = configOnly\n\n const registration = registerDevServer({\n configs,\n host: appHost,\n id,\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 reportConfigErrors(app, output)\n return {\n configs: await 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 // A save can flip the server's role — e.g. a config-only app gaining an\n // `entry` becomes app-role — so re-run the same-role collision check the\n // registration gate applied, or the flip would quietly reintroduce the\n // ambiguity (two app-role servers on one slug). The patch is skipped, not\n // fatal: the registry keeps the previous shape and the next save retries.\n const nextConfigOnly = isConfigOnlyServer({\n configs: patch.configs,\n interfaces: patch.interfaces,\n })\n if (id && nextConfigOnly !== registeredConfigOnly) {\n const conflict = findSameRoleConflict(id, nextConfigOnly)\n if (conflict) {\n const subject = nextConfigOnly ? `a config for \"${id}\"` : `the app \"${id}\"`\n output.error(\n `This change makes this dev server serve ${subject} like the dev server running on ` +\n `port ${conflict.port} already does, so the workbench couldn't tell them apart — ` +\n `keeping the previous registration. ${conflictRemedy(nextConfigOnly)}`,\n {exit: false},\n )\n return\n }\n }\n\n if (\n !exposesSet.changed({\n configs: patch.configs,\n interfaces: patch.interfaces,\n })\n ) {\n registration.update(patch)\n registeredConfigOnly = nextConfigOnly\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 registeredConfigOnly = nextConfigOnly\n },\n workDir,\n })\n\n return {\n close: async () => {\n registration.release()\n await watcher.close()\n },\n }\n}\n"],"names":["getCliConfigUncached","isWorkbenchApp","deriveInterfaces","formatWorkbenchAppErrors","validateWorkbenchApp","deriveConfigs","trackExposesSet","getRegisteredServers","isConfigOnlyServer","registerDevServer","startDevManifestWatcher","reportConfigErrors","app","output","errors","length","warn","findSameRoleConflict","id","configOnly","find","server","pid","process","conflictRemedy","serverAddress","resolvedHost","config","host","addr","httpServer","address","port","startDevServerRegistration","options","cliConfig","extractManifest","isApp","onInterfaceSetChange","workDir","appHost","appPort","interfaces","configs","slug","undefined","devServer","subject","error","exit","close","registeredConfigOnly","registration","projectId","api","type","exposesSet","watcher","extract","params","manifest","extraWatchFilenames","update","patch","nextConfigOnly","conflict","changed","rebuiltServer","commit","release"],"mappings":"AAAA,SAAwBA,oBAAoB,QAAoB,mBAAkB;AAGlF,SAAQC,cAAc,QAAO,qBAAoB;AACjD,SAAQC,gBAAgB,QAAO,4BAA2B;AAC1D,SAAQC,wBAAwB,EAAEC,oBAAoB,QAAO,gCAA+B;AAC5F,SAAQC,aAAa,QAAO,qBAAoB;AAChD,SAAQC,eAAe,QAAO,oBAAmB;AACjD,SAEEC,oBAAoB,EACpBC,kBAAkB,EAClBC,iBAAiB,QACZ,gBAAe;AACtB,SAAQC,uBAAuB,QAAO,+BAA8B;AAiCpE;;;;CAIC,GACD,SAASC,mBAAmBC,GAAqB,EAAEC,MAAc;IAC/D,MAAMC,SAASV,qBAAqBQ;IACpC,IAAIE,OAAOC,MAAM,KAAK,GAAG;IACzBF,OAAOG,IAAI,CAACb,yBAAyBW;AACvC;AAEA;;;;;;;CAOC,GACD,SAASG,qBAAqBC,EAAU,EAAEC,UAAmB;IAC3D,OAAOZ,uBAAuBa,IAAI,CAChC,CAACC,SACCA,OAAOC,GAAG,KAAKC,QAAQD,GAAG,IAAID,OAAOH,EAAE,KAAKA,MAAMV,mBAAmBa,YAAYF;AAEvF;AAEA;;;;CAIC,GACD,SAASK,eAAeL,UAAmB;IACzC,OAAOA,aACH,4BACA;AACN;AAEA,+HAA+H,GAC/H,SAASM,cAAcJ,MAAqB;IAC1C,MAAMK,eAAeL,OAAOM,MAAM,CAACN,MAAM,CAACO,IAAI;IAC9C,MAAMC,OAAOR,OAAOS,UAAU,EAAEC;IAChC,OAAO;QACLH,MAAM,OAAOF,iBAAiB,WAAWA,eAAe;QACxDM,MAAM,OAAOH,SAAS,YAAYA,OAAOA,KAAKG,IAAI,GAAGX,OAAOM,MAAM,CAACN,MAAM,CAACW,IAAI;IAChF;AACF;AAEA;;;;CAIC,GACD,OAAO,eAAeC,2BACpBC,OAAqC;IAErC,MAAM,EAACC,SAAS,EAAEC,eAAe,EAAEC,KAAK,EAAEC,oBAAoB,EAAEzB,MAAM,EAAEQ,MAAM,EAAEkB,OAAO,EAAC,GAAGL;IAE3F,MAAM,EAACN,MAAMY,OAAO,EAAER,MAAMS,OAAO,EAAC,GAAGhB,cAAcJ;IAErDV,mBAAmBwB,UAAUvB,GAAG,EAAEC;IAElC,+EAA+E;IAC/E,yDAAyD;IACzD,MAAM6B,aAAaxC,iBAAiBiC,UAAUvB,GAAG,EAAE;QAACyB;IAAK;IACzD,MAAMM,UAAU,MAAMtC,cAAc8B,UAAUvB,GAAG;IAEjD,MAAMM,KAAKjB,eAAekC,UAAUvB,GAAG,IAAIuB,UAAUvB,GAAG,CAACgC,IAAI,GAAGC;IAEhE,MAAM1B,aAAaX,mBAAmB;QAACmC;QAASD;IAAU;IAC1D,MAAMI,YAAY5B,KAAKD,qBAAqBC,IAAIC,cAAc0B;IAE9D,IAAI3B,MAAM4B,WAAW;QACnB,MAAMC,UAAU5B,aAAa,CAAC,cAAc,EAAED,GAAG,CAAC,CAAC,GAAG,CAAC,SAAS,EAAEA,GAAG,CAAC,CAAC;QACvEL,OAAOmC,KAAK,CACV,GAAGD,QAAQ,yDAAyD,EAAED,UAAUd,IAAI,CAAC,EAAE,CAAC,GACtF,0EACAR,eAAeL,aACjB;YAAC8B,MAAM;QAAK;QAEd,OAAO;YAACC,OAAO,WAAa;QAAC;IAC/B;IAEA,4EAA4E;IAC5E,qEAAqE;IACrE,0EAA0E;IAC1E,IAAIC,uBAAuBhC;IAE3B,MAAMiC,eAAe3C,kBAAkB;QACrCkC;QACAf,MAAMY;QACNtB;QACAwB;QACAV,MAAMS;QACNY,WAAWlB,WAAWmB,KAAKD;QAC3BE,MAAMlB,QAAQ,YAAY;QAC1BE;IACF;IAEA,MAAMiB,aAAalD,gBAAgB;QAACqC;QAASD;IAAU;IAEvD,MAAMe,UAAU,MAAM/C,wBAAwB;QAC5C,4EAA4E;QAC5E,6CAA6C;QAC7CgD,SAAS,OAAOC;YACd,MAAM/C,MAAM,AAAC,CAAA,MAAMZ,qBAAqB2D,OAAOpB,OAAO,CAAA,EAAG3B,GAAG;YAC5DD,mBAAmBC,KAAKC;YACxB,OAAO;gBACL8B,SAAS,MAAMtC,cAAcO;gBAC7B8B,YAAYxC,iBAAiBU,KAAK;oBAACyB;gBAAK;gBACxCuB,UAAU,MAAMxB,gBAAgBuB;YAClC;QACF;QACA,2EAA2E;QAC3E,wEAAwE;QACxEE,qBAAqBxB,QAAQQ,YAAY;YAAC;YAAiB;SAAgB;QAC3EhC;QACAiD,QAAQ,OAAOC;YACb,wEAAwE;YACxE,yEAAyE;YACzE,uEAAuE;YACvE,0EAA0E;YAC1E,0EAA0E;YAC1E,MAAMC,iBAAiBxD,mBAAmB;gBACxCmC,SAASoB,MAAMpB,OAAO;gBACtBD,YAAYqB,MAAMrB,UAAU;YAC9B;YACA,IAAIxB,MAAM8C,mBAAmBb,sBAAsB;gBACjD,MAAMc,WAAWhD,qBAAqBC,IAAI8C;gBAC1C,IAAIC,UAAU;oBACZ,MAAMlB,UAAUiB,iBAAiB,CAAC,cAAc,EAAE9C,GAAG,CAAC,CAAC,GAAG,CAAC,SAAS,EAAEA,GAAG,CAAC,CAAC;oBAC3EL,OAAOmC,KAAK,CACV,CAAC,wCAAwC,EAAED,QAAQ,gCAAgC,CAAC,GAClF,CAAC,KAAK,EAAEkB,SAASjC,IAAI,CAAC,2DAA2D,CAAC,GAClF,CAAC,mCAAmC,EAAER,eAAewC,iBAAiB,EACxE;wBAACf,MAAM;oBAAK;oBAEd;gBACF;YACF;YAEA,IACE,CAACO,WAAWU,OAAO,CAAC;gBAClBvB,SAASoB,MAAMpB,OAAO;gBACtBD,YAAYqB,MAAMrB,UAAU;YAC9B,IACA;gBACAU,aAAaU,MAAM,CAACC;gBACpBZ,uBAAuBa;gBACvB;YACF;YACA,4EAA4E;YAC5E,6EAA6E;YAC7E,MAAMG,gBAAgB,MAAM7B;YAC5B,6EAA6E;YAC7EkB,WAAWY,MAAM,CAAC;gBAChBzB,SAASoB,MAAMpB,OAAO;gBACtBD,YAAYqB,MAAMrB,UAAU;YAC9B;YACA,qEAAqE;YACrEU,aAAaU,MAAM,CAACK,gBAAgB;gBAAC,GAAGJ,KAAK;gBAAE,GAAGtC,cAAc0C,cAAc;YAAA,IAAIJ;YAClFZ,uBAAuBa;QACzB;QACAzB;IACF;IAEA,OAAO;QACLW,OAAO;YACLE,aAAaiB,OAAO;YACpB,MAAMZ,QAAQP,KAAK;QACrB;IACF;AACF"}
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { fileURLToPath } from 'node:url';
|
|
2
|
+
import { subdebug } from '@sanity/cli-core';
|
|
2
3
|
import { isStaging } from '@sanity/cli-core/util';
|
|
3
4
|
import viteReact from '@vitejs/plugin-react';
|
|
4
5
|
import { createServer } from 'vite';
|
|
5
6
|
import { z } from 'zod/mini';
|
|
6
7
|
import { isWorkbenchApp } from '../../defineApp.js';
|
|
7
8
|
import { createExposesTracker } from './exposesSetId.js';
|
|
8
|
-
import { acquireWorkbenchLock, getRegisteredServers, readWorkbenchLock, watchRegistry } from './registry.js';
|
|
9
|
+
import { acquireWorkbenchLock, getRegisteredServers, isConfigOnlyServer, readWorkbenchLock, watchRegistry } from './registry.js';
|
|
9
10
|
import { writeWorkbenchRuntime } from './writeWorkbenchRuntime.js';
|
|
10
11
|
const devDebug = subdebug('dev');
|
|
12
|
+
const renderDashboardEntry = '@sanity/workbench-cli/_internal_render';
|
|
13
|
+
const renderDashboardPath = fileURLToPath(new URL('../../_exports/_internal_render.js', import.meta.url));
|
|
11
14
|
const noop = async ()=>{};
|
|
12
|
-
// Every server is a local app except a config-only one —
|
|
15
|
+
// Every server is a local app except a config-only one — a config
|
|
13
16
|
// with no interfaces (the media library). A server with both lands in both channels.
|
|
14
|
-
const isLocalApp = (server)
|
|
15
|
-
const configOnly = Boolean(server.configs?.length) && !server.interfaces?.length;
|
|
16
|
-
return !configOnly;
|
|
17
|
-
};
|
|
17
|
+
const isLocalApp = (server)=>!isConfigOnlyServer(server);
|
|
18
18
|
const toApplicationsPayload = (servers)=>({
|
|
19
19
|
applications: servers.filter((server)=>isLocalApp(server)).map(({ host, id, interfaces, manifest, port, projectId, type })=>({
|
|
20
20
|
host,
|
|
@@ -81,12 +81,7 @@ const toApplicationsPayload = (servers)=>({
|
|
|
81
81
|
}
|
|
82
82
|
};
|
|
83
83
|
}
|
|
84
|
-
|
|
85
|
-
* Start the workbench dev server when federation is enabled and the workbench
|
|
86
|
-
* package is available. If the desired port is already taken — by another
|
|
87
|
-
* workbench instance or an unrelated process — fall back to running without a
|
|
88
|
-
* workbench and let the app/studio dev server claim the configured port.
|
|
89
|
-
*/ export async function startWorkbenchDevServer(options) {
|
|
84
|
+
export async function startWorkbenchDevServer(options) {
|
|
90
85
|
const { cacheDir, cliConfig, httpHost, httpPort: workbenchPort, mode, output, reactStrictMode, workDir } = options;
|
|
91
86
|
// Workbench is opted into solely by calling `unstable_defineApp`.
|
|
92
87
|
if (!isWorkbenchApp(cliConfig?.app)) {
|
|
@@ -98,21 +93,6 @@ const toApplicationsPayload = (servers)=>({
|
|
|
98
93
|
workbenchPort
|
|
99
94
|
};
|
|
100
95
|
}
|
|
101
|
-
let workbenchAvailable = false;
|
|
102
|
-
try {
|
|
103
|
-
await resolveLocalPackage('sanity/workbench', workDir);
|
|
104
|
-
workbenchAvailable = true;
|
|
105
|
-
} catch {
|
|
106
|
-
devDebug('Workbench not available, skipping workbench dev server');
|
|
107
|
-
}
|
|
108
|
-
if (!workbenchAvailable) {
|
|
109
|
-
return {
|
|
110
|
-
close: noop,
|
|
111
|
-
httpHost,
|
|
112
|
-
workbenchAvailable,
|
|
113
|
-
workbenchPort
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
96
|
// Acquire an exclusive lock — only one workbench per machine.
|
|
117
97
|
// Uses O_EXCL which is atomic at the OS level, preventing races when
|
|
118
98
|
// multiple `sanity dev` processes start simultaneously (e.g. via turbo).
|
|
@@ -166,7 +146,7 @@ const toApplicationsPayload = (servers)=>({
|
|
|
166
146
|
await close();
|
|
167
147
|
},
|
|
168
148
|
httpHost,
|
|
169
|
-
workbenchAvailable,
|
|
149
|
+
workbenchAvailable: true,
|
|
170
150
|
workbenchPort: actualPort
|
|
171
151
|
};
|
|
172
152
|
}
|
|
@@ -195,14 +175,9 @@ async function createWorkbenchViteServer(options) {
|
|
|
195
175
|
logLevel: 'warn',
|
|
196
176
|
mode: 'development',
|
|
197
177
|
optimizeDeps: {
|
|
198
|
-
//
|
|
199
|
-
// from dep pre-bundling so that `import.meta.hot` is available at
|
|
200
|
-
// runtime — pre-bundled modules do not receive Vite's HMR client
|
|
201
|
-
// injection, which causes the custom HMR events for local application
|
|
202
|
-
// discovery to silently not fire.
|
|
178
|
+
// Keep this entry out of pre-bundling so Vite injects its HMR context.
|
|
203
179
|
exclude: [
|
|
204
|
-
|
|
205
|
-
'@sanity/workbench'
|
|
180
|
+
renderDashboardEntry
|
|
206
181
|
]
|
|
207
182
|
},
|
|
208
183
|
// viteReact looks inert here — it transforms none of the host's own modules —
|
|
@@ -219,6 +194,10 @@ async function createWorkbenchViteServer(options) {
|
|
|
219
194
|
] : []
|
|
220
195
|
],
|
|
221
196
|
resolve: {
|
|
197
|
+
// The generated Vite root cannot reliably resolve this package.
|
|
198
|
+
alias: {
|
|
199
|
+
[renderDashboardEntry]: renderDashboardPath
|
|
200
|
+
},
|
|
222
201
|
dedupe: [
|
|
223
202
|
'react',
|
|
224
203
|
'react-dom'
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/dev/startWorkbenchDevServer.ts"],"sourcesContent":["import {type CliConfig, type Output, resolveLocalPackage, subdebug} from '@sanity/cli-core'\nimport {isStaging} from '@sanity/cli-core/util'\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__: isStaging(),\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","isStaging","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__","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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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/dev/startWorkbenchDevServer.ts"],"sourcesContent":["import {fileURLToPath} from 'node:url'\n\nimport {type CliConfig, type Output, subdebug} from '@sanity/cli-core'\nimport {isStaging} from '@sanity/cli-core/util'\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 isConfigOnlyServer,\n readWorkbenchLock,\n watchRegistry,\n} from './registry.js'\nimport {writeWorkbenchRuntime} from './writeWorkbenchRuntime.js'\n\nconst devDebug = subdebug('dev')\nconst renderDashboardEntry = '@sanity/workbench-cli/_internal_render'\nconst renderDashboardPath = fileURLToPath(\n new URL('../../_exports/_internal_render.js', import.meta.url),\n)\n\nconst noop = async () => {}\n\n// Every server is a local app except a config-only one — a config\n// with no interfaces (the media library). A server with both lands in both channels.\nconst isLocalApp = (server: DevServerManifest): boolean => !isConfigOnlyServer(server)\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\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 // 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: true,\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__: isStaging(),\n 'import.meta.env.SANITY_INTERNAL_WORKBENCH_REMOTE_URL': JSON.stringify(remoteUrl),\n },\n logLevel: 'warn',\n mode: 'development',\n optimizeDeps: {\n // Keep this entry out of pre-bundling so Vite injects its HMR context.\n exclude: [renderDashboardEntry],\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: {\n // The generated Vite root cannot reliably resolve this package.\n alias: {[renderDashboardEntry]: renderDashboardPath},\n dedupe: ['react', 'react-dom'],\n },\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":["fileURLToPath","subdebug","isStaging","viteReact","createServer","z","isWorkbenchApp","createExposesTracker","acquireWorkbenchLock","getRegisteredServers","isConfigOnlyServer","readWorkbenchLock","watchRegistry","writeWorkbenchRuntime","devDebug","renderDashboardEntry","renderDashboardPath","URL","url","noop","isLocalApp","server","toApplicationsPayload","servers","applications","filter","map","host","id","interfaces","manifest","port","projectId","type","configs","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__","JSON","stringify","logLevel","optimizeDeps","exclude","plugins","remoteManifestPreloadHeaderPlugin","resolve","alias","dedupe","strictPort","warmup","clientFiles","listen","warn","Error","message","String","addr","httpServer","address","fetch","then","r","body","cancel","catch","remoteUrlSchema","normalize","protocol","value","safeParse","success","data","apply","configureServer","middlewares","use","req","res","next","pathname","split","setHeader","name"],"mappings":"AAAA,SAAQA,aAAa,QAAO,WAAU;AAEtC,SAAqCC,QAAQ,QAAO,mBAAkB;AACtE,SAAQC,SAAS,QAAO,wBAAuB;AAC/C,OAAOC,eAAe,uBAAsB;AAC5C,SAAQC,YAAY,QAA2D,OAAM;AACrF,SAAQC,CAAC,QAAO,WAAU;AAE1B,SAAQC,cAAc,QAAO,qBAAoB;AACjD,SAAQC,oBAAoB,QAAO,oBAAmB;AACtD,SACEC,oBAAoB,EAEpBC,oBAAoB,EACpBC,kBAAkB,EAClBC,iBAAiB,EACjBC,aAAa,QACR,gBAAe;AACtB,SAAQC,qBAAqB,QAAO,6BAA4B;AAEhE,MAAMC,WAAWb,SAAS;AAC1B,MAAMc,uBAAuB;AAC7B,MAAMC,sBAAsBhB,cAC1B,IAAIiB,IAAI,sCAAsC,YAAYC,GAAG;AAG/D,MAAMC,OAAO,WAAa;AAE1B,kEAAkE;AAClE,qFAAqF;AACrF,MAAMC,aAAa,CAACC,SAAuC,CAACX,mBAAmBW;AAE/E,MAAMC,wBAAwB,CAACC,UAAkC,CAAA;QAC/DC,cAAcD,QACXE,MAAM,CAAC,CAACJ,SAAWD,WAAWC,SAC9BK,GAAG,CAAC,CAAC,EAACC,IAAI,EAAEC,EAAE,EAAEC,UAAU,EAAEC,QAAQ,EAAEC,IAAI,EAAEC,SAAS,EAAEC,IAAI,EAAC,GAAM,CAAA;gBACjEN;gBACAC;gBACAC;gBACAC;gBACAC;gBACAC;gBACAC;YACF,CAAA;QACFC,SAASX,QAAQY,OAAO,CAAC,CAAC,EAACD,OAAO,EAAEP,IAAI,EAAEI,IAAI,EAAC,GAI7C,AAHA,0EAA0E;YAC1E,6EAA6E;YAC7E,kCAAkC;YACjCG,CAAAA,WAAW,EAAE,AAAD,EAAGR,GAAG,CAAC,CAAC,EAACU,OAAO,EAAER,EAAE,EAAES,UAAU,EAAEC,OAAO,EAAE,GAAGC,QAAO,GAAM,CAAA;oBACtEH;oBACAG;oBACAX;oBACAS;oBACAG,WAAW,CAAC,OAAO,EAAEb,KAAK,CAAC,EAAEI,MAAM;oBACnCO;gBACF,CAAA;IAEJ,CAAA;AAEA;;;;;CAKC,GACD,SAASG,0BAA0BpB,MAAqB;IACtDA,OAAOqB,EAAE,CAACC,EAAE,CAAC,2CAA2C,CAACC,GAAGC;QAC1DA,OAAOC,IAAI,CACT,uCACAxB,sBAAsBb;IAE1B;IAEA,MAAMsC,aAAaxC;IACnB,MAAMyC,kBAAkBpC,cAAc,CAACW;QACrC,IAAIwB,WAAWE,UAAU,CAAC1B,UAAU;YAClCF,OAAOqB,EAAE,CAACI,IAAI,CAAC;gBAACb,MAAM;YAAa;YACnC;QACF;QACAZ,OAAOqB,EAAE,CAACI,IAAI,CAAC,uCAAuCxB,sBAAsBC;IAC9E;IAEA,OAAO,IAAMyB,gBAAgBE,KAAK;AACpC;AAEA;;;;;CAKC,GACD,OAAO,SAASC,gCAAgCC,OAI/C;IACC,MAAM,EAACC,QAAQ,EAAEtB,IAAI,EAAEV,MAAM,EAAC,GAAG+B;IAEjC,MAAME,OAAO9C,qBAAqB;QAACmB,MAAM0B,YAAY;QAAatB;IAAI;IACtE,IAAI,CAACuB,MAAM;QACT,MAAMC,WAAW5C;QACjBG,SACE,+FACAyC,UAAUC,KACVD,UAAUxB;IAEd;IAEA,MAAM0B,eAAehB,0BAA0BpB;IAE/C,OAAO;QACL6B,OAAO;YACLO;YACAH,MAAMI;QACR;IACF;AACF;AAwBA,OAAO,eAAeC,wBACpBP,OAA8B;IAE9B,MAAM,EACJQ,QAAQ,EACRC,SAAS,EACTR,QAAQ,EACRS,UAAUC,aAAa,EACvBC,IAAI,EACJC,MAAM,EACNC,eAAe,EACfC,OAAO,EACR,GAAGf;IAEJ,kEAAkE;IAClE,IAAI,CAAC9C,eAAeuD,WAAWO,MAAM;QACnCtD,SAAS;QACT,OAAO;YAACoC,OAAO/B;YAAMkC;YAAUgB,oBAAoB;YAAON;QAAa;IACzE;IAEA,8DAA8D;IAC9D,qEAAqE;IACrE,yEAAyE;IACzE,MAAMO,gBAAgB9D,qBAAqB;QAACmB,MAAM0B,YAAY;QAAatB,MAAMgC;IAAa;IAC9F,IAAI,CAACO,eAAe;QAClB,MAAMf,WAAW5C;QACjBG,SACE,4DACAyC,UAAUC,KACVD,UAAUxB;QAEZ,OAAO;YACLmB,OAAO/B;YACPkC,UAAUE,UAAU5B,QAAQ0B;YAC5BgB,oBAAoB;YACpBN,eAAeR,UAAUxB,QAAQgC;QACnC;IACF;IAEA,2EAA2E;IAC3E,4EAA4E;IAC5E,iBAAiB;IACjB,IAAIQ;IACJ,IAAI;QACFA,SAAS,MAAMC,0BAA0B;YACvCZ;YACAC;YACAR;YACAW;YACAC;YACAC;YACAH;YACAI;QACF;IACF,EAAE,OAAOM,KAAK;QACZH,cAAcZ,OAAO;QACrB,MAAMe;IACR;IAEA,IAAI,CAACF,QAAQ;QACXD,cAAcZ,OAAO;QACrB,OAAO;YAACR,OAAO/B;YAAMkC;YAAUgB,oBAAoB;YAAON;QAAa;IACzE;IAEA,MAAM,EAACW,UAAU,EAAExB,KAAK,EAAC,GAAGqB;IAC5BD,cAAcK,UAAU,CAACD;IAEzB,OAAO;QACLxB,OAAO;YACLoB,cAAcZ,OAAO;YACrB,MAAMR;QACR;QACAG;QACAgB,oBAAoB;QACpBN,eAAeW;IACjB;AACF;AAkBA,eAAeF,0BACbpB,OAAyC;IAEzC,MAAM,EAACQ,QAAQ,EAAEC,SAAS,EAAER,QAAQ,EAAEW,IAAI,EAAEC,MAAM,EAAEC,eAAe,EAAEH,aAAa,EAAEI,OAAO,EAAC,GAC1Ff;IAEF,gFAAgF;IAChF,2EAA2E;IAC3E,4EAA4E;IAC5E,MAAMwB,YACJZ,SAAS,YACLa,YACAC,eAAeC,QAAQC,GAAG,CAACC,oCAAoC;IAErE,MAAMC,iBAAiBC,sBAAsBtB;IAE7C/C,SAAS;IACT,MAAMsE,OAAO,MAAMvE,sBAAsB;QACvCwE,KAAKlB;QACLe;QACAhB;QACAU;IACF;IAEA,MAAMU,aAA2B;QAC/B,0FAA0F;QAC1F1B;QACA2B,YAAY;QACZC,QAAQ;YACNC,oBAAoBvF;YACpB,wDAAwDwF,KAAKC,SAAS,CAACf;QACzE;QACAgB,UAAU;QACV5B,MAAM;QACN6B,cAAc;YACZ,uEAAuE;YACvEC,SAAS;gBAAC/E;aAAqB;QACjC;QACA,8EAA8E;QAC9E,+EAA+E;QAC/E,iFAAiF;QACjF,4EAA4E;QAC5E,2EAA2E;QAC3E,2EAA2E;QAC3E,2EAA2E;QAC3EgF,SAAS;YAAC5F;eAAiByE,YAAY;gBAACoB,kCAAkCpB;aAAW,GAAG,EAAE;SAAE;QAC5FqB,SAAS;YACP,gEAAgE;YAChEC,OAAO;gBAAC,CAACnF,qBAAqB,EAAEC;YAAmB;YACnDmF,QAAQ;gBAAC;gBAAS;aAAY;QAChC;QACAf;QACA/D,QAAQ;YACNM,MAAM0B;YACNtB,MAAMgC;YACNqC,YAAY;YACZC,QAAQ;gBACNC,aAAa;oBAAC;iBAAiB;YACjC;QACF;IACF;IAEAxF,SAAS;IACT,MAAMO,SAAS,MAAMjB,aAAakF;IAClC,IAAI;QACF,MAAMjE,OAAOkF,MAAM;IACrB,EAAE,OAAO9B,KAAK;QACZ,MAAMpD,OAAO6B,KAAK;QAClBe,OAAOuC,IAAI,CACT,CAAC,sCAAsC,EAAE/B,eAAegC,QAAQhC,IAAIiC,OAAO,GAAGC,OAAOlC,MAAM;QAE7F,OAAOI;IACT;IAEA,wEAAwE;IACxE,MAAM+B,OAAOvF,OAAOwF,UAAU,EAAEC;IAChC,MAAMpC,aAAa,OAAOkC,SAAS,YAAYA,OAAOA,KAAK7E,IAAI,GAAGgC;IAElE,0EAA0E;IAC1E,+DAA+D;IAC/D,IAAIa,WAAW;QACbmC,MAAMnC,WACHoC,IAAI,CAAC,CAACC,IAAMA,EAAEC,IAAI,EAAEC,UACpBC,KAAK,CAAC,KAAO;QAChBtG,SAAS,kCAAkC8D;IAC7C;IAEA,MAAMnB,eAAehB,0BAA0BpB;IAE/C,OAAO;QACLqD;QACAxB,OAAO;YACLO;YACA,MAAMpC,OAAO6B,KAAK;QACpB;IACF;AACF;AAEA,sEAAsE;AACtE,wEAAwE;AACxE,0EAA0E;AAC1E,6EAA6E;AAC7E,MAAMiC,wBAAwB,CAACtB;IAC7B,IAAIA,UAAUO,GAAG,EAAEc,gBAAgB;QACjC,OAAOrB,UAAUO,GAAG,CAACc,cAAc;IACrC;IAEA,MAAM,IAAIuB,MACR;AAEJ;AAEA,4EAA4E;AAC5E,0CAA0C;AAC1C,MAAMY,kBAAkBhH,EAAEa,GAAG,CAAC;IAACoG,WAAW;IAAMC,UAAU;AAAU;AAEpE,SAASzC,eAAe0C,KAAyB;IAC/C,IAAI,CAACA,OAAO,OAAO3C;IAEnB,MAAMN,SAAS8C,gBAAgBI,SAAS,CAACD;IAEzC,IAAI,CAACjD,OAAOmD,OAAO,EAAE;QACnB,MAAM,IAAIjB,MACR,CAAC,8CAA8C,EAAEe,MAAM,yBAAyB,CAAC;IAErF;IAEA,OAAOjD,OAAOoD,IAAI;AACpB;AAEA;;;;;;CAMC,GACD,SAAS3B,kCAAkCpB,SAAiB;IAC1D,OAAO;QACLgD,OAAO;QACPC,iBAAgBxG,MAAM;YACpBA,OAAOyG,WAAW,CAACC,GAAG,CAAC,CAACC,KAAKC,KAAKC;gBAChC,MAAMC,WAAW,AAACH,CAAAA,IAAI9G,GAAG,IAAI,GAAE,EAAGkH,KAAK,CAAC,IAAI,CAAC,EAAE;gBAC/C,IAAID,aAAa,OAAOA,aAAa,eAAe;oBAClDF,IAAII,SAAS,CAAC,QAAQ,CAAC,CAAC,EAAEzD,UAAU,qCAAqC,CAAC;gBAC5E;gBACAsD;YACF;QACF;QACAI,MAAM;IACR;AACF"}
|
|
@@ -6,9 +6,9 @@ const devDebug = subdebug('dev');
|
|
|
6
6
|
const workbenchJsTemplate = `\
|
|
7
7
|
// This file is auto-generated on 'sanity dev'
|
|
8
8
|
// Modifications to this file are automatically discarded
|
|
9
|
-
import {
|
|
9
|
+
import {renderDashboard} from "@sanity/workbench-cli/_internal_render"
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
renderDashboard(
|
|
12
12
|
document.getElementById("workbench"),
|
|
13
13
|
{organizationId: %SANITY_WORKBENCH_ORGANIZATION_ID%},
|
|
14
14
|
{reactStrictMode: %SANITY_WORKBENCH_REACT_STRICT_MODE%}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/dev/writeWorkbenchRuntime.ts"],"sourcesContent":["import fs from 'node:fs/promises'\nimport path from 'node:path'\n\nimport {subdebug} from '@sanity/cli-core'\nimport {isStaging} from '@sanity/cli-core/util'\n\nconst devDebug = subdebug('dev')\n\nconst workbenchJsTemplate = `\\\n// This file is auto-generated on 'sanity dev'\n// Modifications to this file are automatically discarded\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/dev/writeWorkbenchRuntime.ts"],"sourcesContent":["import fs from 'node:fs/promises'\nimport path from 'node:path'\n\nimport {subdebug} from '@sanity/cli-core'\nimport {isStaging} from '@sanity/cli-core/util'\n\nconst devDebug = subdebug('dev')\n\nconst workbenchJsTemplate = `\\\n// This file is auto-generated on 'sanity dev'\n// Modifications to this file are automatically discarded\nimport {renderDashboard} from \"@sanity/workbench-cli/_internal_render\"\n\nrenderDashboard(\n document.getElementById(\"workbench\"),\n {organizationId: %SANITY_WORKBENCH_ORGANIZATION_ID%},\n {reactStrictMode: %SANITY_WORKBENCH_REACT_STRICT_MODE%}\n)\n`\n\nconst indexHtmlTemplate = `\\\n<!DOCTYPE html>\n<!-- This file is auto-generated on 'sanity dev' -->\n<!-- Modifications to this file are automatically discarded -->\n<html>\n <head>\n <meta charset=\"UTF-8\" />\n%SANITY_WORKBENCH_PREFETCH_HINTS%\n </head>\n <body>\n <div id=\"workbench\"></div>\n <script>globalThis.__SANITY_STAGING__ = %SANITY_WORKBENCH_STAGING%</script>\n <script type=\"module\" src=\"./workbench.js\"></script>\n </body>\n</html>\n`\n\n/**\n * Generates the `.sanity/workbench` directory with static entry files for\n * the workbench Vite dev server.\n *\n * @param cwd - Current working directory (Sanity root dir)\n * @returns The absolute path to the written workbench runtime directory\n * @internal\n */\nexport async function writeWorkbenchRuntime(options: {\n cwd: string\n organizationId?: string\n reactStrictMode: boolean\n remoteUrl?: string\n}): Promise<string> {\n const {cwd, organizationId, reactStrictMode, remoteUrl} = options\n const workbenchDir = path.join(cwd, '.sanity', 'workbench')\n\n const workbenchJs = workbenchJsTemplate\n .replace(\n /%SANITY_WORKBENCH_ORGANIZATION_ID%/,\n organizationId === undefined ? 'undefined' : JSON.stringify(organizationId),\n )\n .replace(/%SANITY_WORKBENCH_REACT_STRICT_MODE%/, JSON.stringify(reactStrictMode))\n\n const prefetchHints = buildPrefetchHints(remoteUrl)\n\n // The runtime flag builds get via decorateIndexWithStagingScript — a vite\n // `define` never reaches pre-bundled dependencies like the SDK.\n const indexHtml = indexHtmlTemplate\n .replace(/%SANITY_WORKBENCH_PREFETCH_HINTS%/, prefetchHints)\n .replace(/%SANITY_WORKBENCH_STAGING%/, JSON.stringify(isStaging()))\n\n devDebug('Making workbench runtime directory')\n await fs.mkdir(workbenchDir, {recursive: true})\n\n devDebug('Writing workbench.js to workbench runtime directory')\n await fs.writeFile(path.join(workbenchDir, 'workbench.js'), workbenchJs)\n\n devDebug('Writing index.html to workbench runtime directory')\n await fs.writeFile(path.join(workbenchDir, 'index.html'), indexHtml)\n\n return workbenchDir\n}\n\nfunction buildPrefetchHints(remoteUrl: string | undefined): string {\n if (!remoteUrl) return ''\n\n try {\n const url = new URL(remoteUrl)\n return [\n ` <link rel=\"preconnect\" href=\"${url.origin}\" />`,\n ` <link rel=\"preload\" as=\"fetch\" href=\"${url.toString()}\" crossorigin />`,\n ].join('\\n')\n } catch {\n return ''\n }\n}\n"],"names":["fs","path","subdebug","isStaging","devDebug","workbenchJsTemplate","indexHtmlTemplate","writeWorkbenchRuntime","options","cwd","organizationId","reactStrictMode","remoteUrl","workbenchDir","join","workbenchJs","replace","undefined","JSON","stringify","prefetchHints","buildPrefetchHints","indexHtml","mkdir","recursive","writeFile","url","URL","origin","toString"],"mappings":"AAAA,OAAOA,QAAQ,mBAAkB;AACjC,OAAOC,UAAU,YAAW;AAE5B,SAAQC,QAAQ,QAAO,mBAAkB;AACzC,SAAQC,SAAS,QAAO,wBAAuB;AAE/C,MAAMC,WAAWF,SAAS;AAE1B,MAAMG,sBAAsB,CAAC;;;;;;;;;;AAU7B,CAAC;AAED,MAAMC,oBAAoB,CAAC;;;;;;;;;;;;;;;AAe3B,CAAC;AAED;;;;;;;CAOC,GACD,OAAO,eAAeC,sBAAsBC,OAK3C;IACC,MAAM,EAACC,GAAG,EAAEC,cAAc,EAAEC,eAAe,EAAEC,SAAS,EAAC,GAAGJ;IAC1D,MAAMK,eAAeZ,KAAKa,IAAI,CAACL,KAAK,WAAW;IAE/C,MAAMM,cAAcV,oBACjBW,OAAO,CACN,sCACAN,mBAAmBO,YAAY,cAAcC,KAAKC,SAAS,CAACT,iBAE7DM,OAAO,CAAC,wCAAwCE,KAAKC,SAAS,CAACR;IAElE,MAAMS,gBAAgBC,mBAAmBT;IAEzC,0EAA0E;IAC1E,gEAAgE;IAChE,MAAMU,YAAYhB,kBACfU,OAAO,CAAC,qCAAqCI,eAC7CJ,OAAO,CAAC,8BAA8BE,KAAKC,SAAS,CAAChB;IAExDC,SAAS;IACT,MAAMJ,GAAGuB,KAAK,CAACV,cAAc;QAACW,WAAW;IAAI;IAE7CpB,SAAS;IACT,MAAMJ,GAAGyB,SAAS,CAACxB,KAAKa,IAAI,CAACD,cAAc,iBAAiBE;IAE5DX,SAAS;IACT,MAAMJ,GAAGyB,SAAS,CAACxB,KAAKa,IAAI,CAACD,cAAc,eAAeS;IAE1D,OAAOT;AACT;AAEA,SAASQ,mBAAmBT,SAA6B;IACvD,IAAI,CAACA,WAAW,OAAO;IAEvB,IAAI;QACF,MAAMc,MAAM,IAAIC,IAAIf;QACpB,OAAO;YACL,CAAC,iCAAiC,EAAEc,IAAIE,MAAM,CAAC,IAAI,CAAC;YACpD,CAAC,yCAAyC,EAAEF,IAAIG,QAAQ,GAAG,gBAAgB,CAAC;SAC7E,CAACf,IAAI,CAAC;IACT,EAAE,OAAM;QACN,OAAO;IACT;AACF"}
|
package/dist/defineApp.js
CHANGED
|
@@ -74,10 +74,7 @@ z.refine((input)=>!(input.config && !input.isSingleton), {
|
|
|
74
74
|
path: [
|
|
75
75
|
'config'
|
|
76
76
|
]
|
|
77
|
-
}))
|
|
78
|
-
// navigable kinds. An `asset_source` view is a separate kind — a picker
|
|
79
|
-
// brokered to other apps — so it may sit alongside either.
|
|
80
|
-
z.refine((input)=>!(input.entry !== undefined && (input.views?.some((view)=>view.type === 'panel') ?? false)), 'An app cannot expose both an app view (`entry`) and panel views. Declare one or the other.')).check(z.refine((input)=>(input.views?.filter((view)=>view.type === 'panel').length ?? 0) <= 1, 'An app can expose at most one panel view.'));
|
|
77
|
+
}));
|
|
81
78
|
/**
|
|
82
79
|
* Nominal brand the CLI discriminates on to enable the workbench build/deploy
|
|
83
80
|
* codepath. Registered via `Symbol.for` so the marker survives module-realm
|
package/dist/defineApp.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/defineApp.ts"],"sourcesContent":["import {z} from 'zod/mini'\n\nimport {APP_SLUG_PATTERN} from './appSlug.js'\nimport {ConfigSchema, InterfaceDeclarationSchema, ServiceDeclarationSchema} from './contract.js'\n\n/**\n * Dashboard visibility values. Mirrors `APP_VISIBILITIES` in `@sanity/cli-core`\n * (which can't be imported here — pulling the barrel into this lean module bloats\n * the config-load path). Kept in sync by a type test in `defineApp.test.ts`.\n */\nconst APP_VISIBILITIES = ['default', 'unlisted', 'disabled'] as const\n\n/**\n * Internal application discriminator. Sanity-owned singleton apps only;\n * validated by the schema but excluded from the public `DefineAppInput` type.\n */\nconst ApplicationType = z.enum(['coreApp', 'studio', 'canvas', 'dashboard', 'media-library'])\n\n/** Dock groups an app can place itself into. */\nconst DockGroupSchema = z.enum(['dock.system', 'dock.applications', 'dock.user'])\n\n/**\n * Dock group identifier. The API does not block a user app from declaring a\n * reserved group (e.g. `dock.system`); priority conventions keep Sanity-owned\n * apps ahead.\n * @public\n */\nexport type DockGroup = z.output<typeof DockGroupSchema>\n\n/**\n * Runtime-validation schema for `unstable_defineApp`.\n * @internal\n */\nexport const DefineAppInputSchema = z\n .object({\n /**\n * Internal — Sanity-owned singleton apps only. Validated here but excluded\n * from the public `DefineAppInput` type.\n * @internal\n */\n applicationType: z.optional(ApplicationType),\n /**\n * Deployed as a versioned snapshot on the app's org installation, not the\n * application service. Singletons only. Internal, so excluded from the public\n * `DefineAppInput` and set via `@ts-expect-error` like `applicationType`.\n * @internal\n */\n config: z.optional(ConfigSchema),\n /**\n * App entrypoint module. Defaults to `./src/App.tsx` when omitted. The build\n * derives the app's navigable `app` view from it. SDK apps only — setting it\n * on a studio is rejected (studio app views are not yet implemented).\n */\n entry: z.optional(z.string(\"must be a path to the app's entry file\")),\n /** Dock group to render in. Defaults to `dock.applications` when omitted. */\n group: z.optional(DockGroupSchema),\n /** Optional icon override (path to an SVG). Wins over manifest/studio icon. */\n icon: z.optional(z.string()),\n /**\n * Sanity-owned app deployed once, installed per org; excluded from the public `DefineAppInput`.\n * @internal\n */\n isSingleton: z.optional(z.boolean()),\n /** Organization that owns the app — the workbench runs and deploys against it. */\n organizationId: z.string(\n \"App `organizationId` is required — pass the owning organization's ID to `unstable_defineApp`\",\n ),\n /** Sort position within the group, ascending. Defaults to `100` when omitted. */\n priority: z.optional(z.number()),\n /** Background services the app runs (e.g. a `worker` emitting dock badges). */\n services: z.optional(\n z\n .array(ServiceDeclarationSchema, 'must be an array of services')\n .check(\n z.refine(\n (services) => new Set(services.map((service) => service.name)).size === services.length,\n 'Service `name` must be unique within an app',\n ),\n ),\n ),\n slug: z\n .string('App `slug` is required — the hostname the application is created at on deploy')\n .check(\n z.regex(\n APP_SLUG_PATTERN,\n 'App `slug` must be lowercase alphanumerics and hyphens, starting with a letter and ending with an alphanumeric',\n ),\n ),\n /** User-facing app title. Wins over studio.config.ts title on merge. */\n title: z.string(),\n /** Views the app exposes (e.g. dock panels). */\n views: z.optional(\n z\n .array(InterfaceDeclarationSchema, 'must be an array of panels')\n .check(\n z.refine(\n (views) => new Set(views.map((view) => view.name)).size === views.length,\n 'View `name` must be unique within an app',\n ),\n ),\n ),\n /** Dashboard visibility of the app. Defaults to `default` when omitted. */\n visibility: z.optional(z.enum(APP_VISIBILITIES)),\n })\n .check(\n // Studio app views are not implemented yet. A studio that declares `entry`\n // (the SDK app-view entrypoint) is rejected here rather than silently\n // generating one; studios keep navigating via their existing render path.\n z.refine((input) => !(input.applicationType === 'studio' && input.entry !== undefined), {\n error: 'App views for studios are not implemented yet',\n path: ['entry'],\n }),\n )\n .check(\n // An config belongs to a Sanity-owned singleton (the Media\n // Library). A non-singleton declaring one is rejected — see\n // {@link readConfig} for the runtime guard.\n z.refine((input) => !(input.config && !input.isSingleton), {\n error: '`config` is only supported for singleton apps',\n path: ['config'],\n }),\n )\n .check(\n // A navigable app view (`entry`) and dock panels are the mutually-exclusive\n // navigable kinds. An `asset_source` view is a separate kind — a picker\n // brokered to other apps — so it may sit alongside either.\n z.refine(\n (input) =>\n !(\n input.entry !== undefined &&\n (input.views?.some((view) => view.type === 'panel') ?? false)\n ),\n 'An app cannot expose both an app view (`entry`) and panel views. Declare one or the other.',\n ),\n )\n .check(\n z.refine(\n (input) => (input.views?.filter((view) => view.type === 'panel').length ?? 0) <= 1,\n 'An app can expose at most one panel view.',\n ),\n )\n\n/** The `asset_source` variant of an app's `views`. @public */\nexport type AssetSourceView = Extract<\n NonNullable<z.output<typeof DefineAppInputSchema>['views']>[number],\n {type: 'asset_source'}\n>\n\n/** The `tile` variant of an app's `views`. @public */\nexport type TileView = Extract<\n NonNullable<z.output<typeof DefineAppInputSchema>['views']>[number],\n {type: 'tile'}\n>\n\n/**\n * User-facing input for `unstable_defineApp`. Excludes the internal\n * `applicationType`, `isSingleton`, and `config` — validated by the schema but\n * not part of the public surface (Sanity-owned apps set them via\n * `@ts-expect-error`). A union: an app declares an app `entry` (navigable) or\n * panel `views`, never both — but `asset_source` and `tile` views are separate\n * kinds and may accompany either.\n * @public\n */\nexport type DefineAppInput = Omit<\n z.output<typeof DefineAppInputSchema>,\n 'applicationType' | 'config' | 'entry' | 'isSingleton' | 'views'\n> &\n (\n | {entry?: never; views?: NonNullable<z.output<typeof DefineAppInputSchema>['views']>}\n | {entry?: string; views?: (AssetSourceView | TileView)[]}\n )\n\n/**\n * Nominal brand the CLI discriminates on to enable the workbench build/deploy\n * codepath. Registered via `Symbol.for` so the marker survives module-realm\n * boundaries — `@sanity/cli-core` re-derives the same global symbol with\n * `Symbol.for` rather than importing it, so it stays internal to this module.\n */\nconst WORKBENCH_APP: unique symbol = Symbol.for('sanity.workbench.defineApp')\n\n/**\n * The branded result of `unstable_defineApp`. Carries the same fields as the\n * input plus the internal brand — users only ever see `DefineAppInput`.\n * @public\n */\nexport type DefineAppResult = DefineAppInput & {readonly [WORKBENCH_APP]: true}\n\n/**\n * A branded app as the CLI reads it — the full schema shape, including the\n * internal fields `DefineAppInput` omits. Schema-derived so the narrowing\n * can't drift from what the schema validates.\n * @public\n */\nexport type WorkbenchApp = DefineAppResult & z.output<typeof DefineAppInputSchema>\n\n/**\n * Whether `app` is a branded `unstable_defineApp(...)` result — the sole\n * workbench opt-in.\n * @public\n */\nexport function isWorkbenchApp(app: unknown): app is WorkbenchApp {\n return typeof app === 'object' && app !== null && WORKBENCH_APP in app\n}\n\n/**\n * The app's config, or `undefined` when it declares none. Throws\n * when a non-singleton declares one — configs belong to Sanity-owned singletons,\n * so build/dev/deploy all read it through here to reject the combination\n * consistently.\n * @internal\n */\nexport function readConfig(app: WorkbenchApp): WorkbenchApp['config'] | undefined {\n if (app.config && !app.isSingleton) {\n throw new Error('`config` is only supported for singleton apps')\n }\n return app.config\n}\n\n/**\n * Declare a Sanity Workbench application. Identity at runtime — returns the same\n * object reference, tagged with the workbench brand. Field validation (the\n * `slug` pattern etc.) runs at build time in the CLI via `DefineAppInputSchema`;\n * this helper stays a thin, pure identity wrapper.\n * @public\n */\nexport function unstable_defineApp(input: DefineAppInput): DefineAppResult {\n return Object.defineProperty(input, WORKBENCH_APP, {\n configurable: false,\n enumerable: false,\n value: true,\n writable: false,\n }) as DefineAppResult\n}\n\n/**\n * One custom field a media library exposes. `src` default-exports a `defineField(...)` schema type.\n * @public\n */\nexport interface MediaLibraryField {\n /** Unique within the media library. */\n name: string\n src: string\n title: string\n\n /** Readable outside the owning organization. */\n public?: boolean\n}\n\n/**\n * Sanity-owned singleton, so authors don't name or title the app — only `organizationId` is required.\n * @public\n */\nexport interface DefineMediaLibraryInput {\n /** Organization that owns the media library — the CLI runs and deploys against it. */\n organizationId: string\n\n fields?: MediaLibraryField[]\n}\n\n/**\n * Declare the Sanity Media Library as a workbench app — a singleton whose `fields` become its config.\n * @public\n */\nexport function unstable_defineMediaLibrary(input: DefineMediaLibraryInput): DefineAppResult {\n return unstable_defineApp({\n // @ts-expect-error -- `applicationType`/`isSingleton`/`config` are internal, excluded from `DefineAppInput`; Sanity-owned apps set them\n applicationType: 'media-library',\n config: input.fields?.length ? {appType: 'media-library', fields: input.fields} : undefined,\n isSingleton: true,\n organizationId: input.organizationId,\n slug: 'media-library',\n title: 'Media Library',\n })\n}\n"],"names":["z","APP_SLUG_PATTERN","ConfigSchema","InterfaceDeclarationSchema","ServiceDeclarationSchema","APP_VISIBILITIES","ApplicationType","enum","DockGroupSchema","DefineAppInputSchema","object","applicationType","optional","config","entry","string","group","icon","isSingleton","boolean","organizationId","priority","number","services","array","check","refine","Set","map","service","name","size","length","slug","regex","title","views","view","visibility","input","undefined","error","path","some","type","filter","WORKBENCH_APP","Symbol","for","isWorkbenchApp","app","readConfig","Error","unstable_defineApp","Object","defineProperty","configurable","enumerable","value","writable","unstable_defineMediaLibrary","fields","appType"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../src/defineApp.ts"],"sourcesContent":["import {z} from 'zod/mini'\n\nimport {APP_SLUG_PATTERN} from './appSlug.js'\nimport {ConfigSchema, InterfaceDeclarationSchema, ServiceDeclarationSchema} from './contract.js'\n\n/**\n * Dashboard visibility values. Mirrors `APP_VISIBILITIES` in `@sanity/cli-core`\n * (which can't be imported here — pulling the barrel into this lean module bloats\n * the config-load path). Kept in sync by a type test in `defineApp.test.ts`.\n */\nconst APP_VISIBILITIES = ['default', 'unlisted', 'disabled'] as const\n\n/**\n * Internal application discriminator. Sanity-owned singleton apps only;\n * validated by the schema but excluded from the public `DefineAppInput` type.\n */\nconst ApplicationType = z.enum(['coreApp', 'studio', 'canvas', 'dashboard', 'media-library'])\n\n/** Dock groups an app can place itself into. */\nconst DockGroupSchema = z.enum(['dock.system', 'dock.applications', 'dock.user'])\n\n/**\n * Dock group identifier. The API does not block a user app from declaring a\n * reserved group (e.g. `dock.system`); priority conventions keep Sanity-owned\n * apps ahead.\n * @public\n */\nexport type DockGroup = z.output<typeof DockGroupSchema>\n\n/**\n * Runtime-validation schema for `unstable_defineApp`.\n * @internal\n */\nexport const DefineAppInputSchema = z\n .object({\n /**\n * Internal — Sanity-owned singleton apps only. Validated here but excluded\n * from the public `DefineAppInput` type.\n * @internal\n */\n applicationType: z.optional(ApplicationType),\n /**\n * Deployed as a versioned snapshot on the app's org installation, not the\n * application service. Singletons only. Internal, so excluded from the public\n * `DefineAppInput` and set via `@ts-expect-error` like `applicationType`.\n * @internal\n */\n config: z.optional(ConfigSchema),\n /**\n * App entrypoint module. Defaults to `./src/App.tsx` when omitted. The build\n * derives the app's navigable `app` view from it. SDK apps only — setting it\n * on a studio is rejected (studio app views are not yet implemented).\n */\n entry: z.optional(z.string(\"must be a path to the app's entry file\")),\n /** Dock group to render in. Defaults to `dock.applications` when omitted. */\n group: z.optional(DockGroupSchema),\n /** Optional icon override (path to an SVG). Wins over manifest/studio icon. */\n icon: z.optional(z.string()),\n /**\n * Sanity-owned app deployed once, installed per org; excluded from the public `DefineAppInput`.\n * @internal\n */\n isSingleton: z.optional(z.boolean()),\n /** Organization that owns the app — the workbench runs and deploys against it. */\n organizationId: z.string(\n \"App `organizationId` is required — pass the owning organization's ID to `unstable_defineApp`\",\n ),\n /** Sort position within the group, ascending. Defaults to `100` when omitted. */\n priority: z.optional(z.number()),\n /** Background services the app runs (e.g. a `worker` emitting dock badges). */\n services: z.optional(\n z\n .array(ServiceDeclarationSchema, 'must be an array of services')\n .check(\n z.refine(\n (services) => new Set(services.map((service) => service.name)).size === services.length,\n 'Service `name` must be unique within an app',\n ),\n ),\n ),\n slug: z\n .string('App `slug` is required — the hostname the application is created at on deploy')\n .check(\n z.regex(\n APP_SLUG_PATTERN,\n 'App `slug` must be lowercase alphanumerics and hyphens, starting with a letter and ending with an alphanumeric',\n ),\n ),\n /** User-facing app title. Wins over studio.config.ts title on merge. */\n title: z.string(),\n /** Views the app exposes (e.g. dock panels). */\n views: z.optional(\n z\n .array(InterfaceDeclarationSchema, 'must be an array of panels')\n .check(\n z.refine(\n (views) => new Set(views.map((view) => view.name)).size === views.length,\n 'View `name` must be unique within an app',\n ),\n ),\n ),\n /** Dashboard visibility of the app. Defaults to `default` when omitted. */\n visibility: z.optional(z.enum(APP_VISIBILITIES)),\n })\n .check(\n // Studio app views are not implemented yet. A studio that declares `entry`\n // (the SDK app-view entrypoint) is rejected here rather than silently\n // generating one; studios keep navigating via their existing render path.\n z.refine((input) => !(input.applicationType === 'studio' && input.entry !== undefined), {\n error: 'App views for studios are not implemented yet',\n path: ['entry'],\n }),\n )\n .check(\n // An config belongs to a Sanity-owned singleton (the Media\n // Library). A non-singleton declaring one is rejected — see\n // {@link readConfig} for the runtime guard.\n z.refine((input) => !(input.config && !input.isSingleton), {\n error: '`config` is only supported for singleton apps',\n path: ['config'],\n }),\n )\n\n/**\n * User-facing input for `unstable_defineApp`. Excludes the internal\n * `applicationType`, `isSingleton`, and `config`.\n * @public\n */\nexport type DefineAppInput = Omit<\n z.output<typeof DefineAppInputSchema>,\n 'applicationType' | 'config' | 'isSingleton'\n>\n\n/**\n * Nominal brand the CLI discriminates on to enable the workbench build/deploy\n * codepath. Registered via `Symbol.for` so the marker survives module-realm\n * boundaries — `@sanity/cli-core` re-derives the same global symbol with\n * `Symbol.for` rather than importing it, so it stays internal to this module.\n */\nconst WORKBENCH_APP: unique symbol = Symbol.for('sanity.workbench.defineApp')\n\n/**\n * The branded result of `unstable_defineApp`. Carries the same fields as the\n * input plus the internal brand — users only ever see `DefineAppInput`.\n * @public\n */\nexport type DefineAppResult = DefineAppInput & {readonly [WORKBENCH_APP]: true}\n\n/**\n * A branded app as the CLI reads it — the full schema shape, including the\n * internal fields `DefineAppInput` omits. Schema-derived so the narrowing\n * can't drift from what the schema validates.\n * @public\n */\nexport type WorkbenchApp = DefineAppResult & z.output<typeof DefineAppInputSchema>\n\n/**\n * Whether `app` is a branded `unstable_defineApp(...)` result — the sole\n * workbench opt-in.\n * @public\n */\nexport function isWorkbenchApp(app: unknown): app is WorkbenchApp {\n return typeof app === 'object' && app !== null && WORKBENCH_APP in app\n}\n\n/**\n * The app's config, or `undefined` when it declares none. Throws\n * when a non-singleton declares one — configs belong to Sanity-owned singletons,\n * so build/dev/deploy all read it through here to reject the combination\n * consistently.\n * @internal\n */\nexport function readConfig(app: WorkbenchApp): WorkbenchApp['config'] | undefined {\n if (app.config && !app.isSingleton) {\n throw new Error('`config` is only supported for singleton apps')\n }\n return app.config\n}\n\n/**\n * Declare a Sanity Workbench application. Identity at runtime — returns the same\n * object reference, tagged with the workbench brand. Field validation (the\n * `slug` pattern etc.) runs at build time in the CLI via `DefineAppInputSchema`;\n * this helper stays a thin, pure identity wrapper.\n * @public\n */\nexport function unstable_defineApp(input: DefineAppInput): DefineAppResult {\n return Object.defineProperty(input, WORKBENCH_APP, {\n configurable: false,\n enumerable: false,\n value: true,\n writable: false,\n }) as DefineAppResult\n}\n\n/**\n * One custom field a media library exposes. `src` default-exports a `defineField(...)` schema type.\n * @public\n */\nexport interface MediaLibraryField {\n /** Unique within the media library. */\n name: string\n src: string\n title: string\n\n /** Readable outside the owning organization. */\n public?: boolean\n}\n\n/**\n * Sanity-owned singleton, so authors don't name or title the app — only `organizationId` is required.\n * @public\n */\nexport interface DefineMediaLibraryInput {\n /** Organization that owns the media library — the CLI runs and deploys against it. */\n organizationId: string\n\n fields?: MediaLibraryField[]\n}\n\n/**\n * Declare the Sanity Media Library as a workbench app — a singleton whose `fields` become its config.\n * @public\n */\nexport function unstable_defineMediaLibrary(input: DefineMediaLibraryInput): DefineAppResult {\n return unstable_defineApp({\n // @ts-expect-error -- `applicationType`/`isSingleton`/`config` are internal, excluded from `DefineAppInput`; Sanity-owned apps set them\n applicationType: 'media-library',\n config: input.fields?.length ? {appType: 'media-library', fields: input.fields} : undefined,\n isSingleton: true,\n organizationId: input.organizationId,\n slug: 'media-library',\n title: 'Media Library',\n })\n}\n"],"names":["z","APP_SLUG_PATTERN","ConfigSchema","InterfaceDeclarationSchema","ServiceDeclarationSchema","APP_VISIBILITIES","ApplicationType","enum","DockGroupSchema","DefineAppInputSchema","object","applicationType","optional","config","entry","string","group","icon","isSingleton","boolean","organizationId","priority","number","services","array","check","refine","Set","map","service","name","size","length","slug","regex","title","views","view","visibility","input","undefined","error","path","WORKBENCH_APP","Symbol","for","isWorkbenchApp","app","readConfig","Error","unstable_defineApp","Object","defineProperty","configurable","enumerable","value","writable","unstable_defineMediaLibrary","fields","appType"],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
2
|
+
// Keep this module browser-safe: Vite loads it directly in the generated dashboard runtime.
|
|
3
|
+
import { createInstance } from '@module-federation/runtime';
|
|
4
|
+
import { BehaviorSubject } from 'rxjs';
|
|
5
|
+
const remoteName = 'workbench-remote';
|
|
6
|
+
const remoteModuleId = `${remoteName}/App`;
|
|
7
|
+
const localApplicationsEvent = 'sanity:workbench:local-applications';
|
|
8
|
+
const noop = ()=>{};
|
|
9
|
+
export async function renderDashboard(rootElement, config, options) {
|
|
10
|
+
if (!rootElement) {
|
|
11
|
+
throw new Error('Missing root element to mount application into');
|
|
12
|
+
}
|
|
13
|
+
const deployedDashboardHost = globalThis.__SANITY_STAGING__ === true ? 'workbench-apps-of5p8qpku.run.sanity.work' : 'workbench-apps-osyh1iet5.sanity.run';
|
|
14
|
+
const remoteUrl = import.meta.env.SANITY_INTERNAL_WORKBENCH_REMOTE_URL ?? `https://${deployedDashboardHost}/mf-manifest.json`;
|
|
15
|
+
const federation = createInstance({
|
|
16
|
+
name: 'sanity-workbench',
|
|
17
|
+
remotes: [
|
|
18
|
+
{
|
|
19
|
+
entry: remoteUrl,
|
|
20
|
+
name: remoteName
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
});
|
|
24
|
+
let remoteModule;
|
|
25
|
+
try {
|
|
26
|
+
remoteModule = await federation.loadRemote(remoteModuleId);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
throw new Error(`Failed to load remote module "${remoteModuleId}"`, {
|
|
29
|
+
cause: error
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
if (!remoteModule || typeof remoteModule.render !== 'function') {
|
|
33
|
+
throw new Error(`Remote module "${remoteModuleId}" did not expose a render function`);
|
|
34
|
+
}
|
|
35
|
+
let appConfigs;
|
|
36
|
+
let cleanupHmr = noop;
|
|
37
|
+
let localApplications;
|
|
38
|
+
if (import.meta.hot) {
|
|
39
|
+
const hot = import.meta.hot;
|
|
40
|
+
const hmrLocalApplications = new BehaviorSubject([]);
|
|
41
|
+
const hmrAppConfigs = new BehaviorSubject([]);
|
|
42
|
+
const handler = (payload)=>{
|
|
43
|
+
hmrLocalApplications.next(payload.applications);
|
|
44
|
+
hmrAppConfigs.next(payload.configs ?? []);
|
|
45
|
+
};
|
|
46
|
+
localApplications = hmrLocalApplications;
|
|
47
|
+
appConfigs = hmrAppConfigs;
|
|
48
|
+
hot.on(localApplicationsEvent, handler);
|
|
49
|
+
hot.send('sanity:workbench:get-local-applications');
|
|
50
|
+
cleanupHmr = ()=>hot.off(localApplicationsEvent, handler);
|
|
51
|
+
}
|
|
52
|
+
const unmount = remoteModule.render(rootElement, {
|
|
53
|
+
appConfigs,
|
|
54
|
+
config,
|
|
55
|
+
localApplications
|
|
56
|
+
}, options);
|
|
57
|
+
return ()=>{
|
|
58
|
+
cleanupHmr();
|
|
59
|
+
unmount();
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
//# sourceMappingURL=renderDashboard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/renderDashboard.ts"],"sourcesContent":["/// <reference types=\"vite/client\" />\n\n// Keep this module browser-safe: Vite loads it directly in the generated dashboard runtime.\nimport {createInstance} from '@module-federation/runtime'\nimport {BehaviorSubject, type Observable} from 'rxjs'\n\ndeclare global {\n var __SANITY_STAGING__: boolean | undefined\n}\n\ninterface DashboardConfig {\n organizationId: string\n}\n\ninterface RenderDashboardOptions {\n reactStrictMode?: boolean\n}\n\ninterface DashboardRemoteModule {\n render: (\n rootElement: HTMLElement,\n props: {\n appConfigs?: Observable<unknown[]>\n config: DashboardConfig\n localApplications?: Observable<unknown[]>\n },\n options?: RenderDashboardOptions,\n ) => () => void\n}\n\nconst remoteName = 'workbench-remote'\nconst remoteModuleId = `${remoteName}/App`\nconst localApplicationsEvent = 'sanity:workbench:local-applications'\nconst noop = () => {}\n\nexport async function renderDashboard(\n rootElement: HTMLElement | null,\n config: DashboardConfig,\n options?: RenderDashboardOptions,\n): Promise<() => void> {\n if (!rootElement) {\n throw new Error('Missing root element to mount application into')\n }\n\n const deployedDashboardHost =\n globalThis.__SANITY_STAGING__ === true\n ? 'workbench-apps-of5p8qpku.run.sanity.work'\n : 'workbench-apps-osyh1iet5.sanity.run'\n const remoteUrl =\n import.meta.env.SANITY_INTERNAL_WORKBENCH_REMOTE_URL ??\n `https://${deployedDashboardHost}/mf-manifest.json`\n\n const federation = createInstance({\n name: 'sanity-workbench',\n remotes: [{entry: remoteUrl, name: remoteName}],\n })\n\n let remoteModule: DashboardRemoteModule | null\n try {\n remoteModule = await federation.loadRemote<DashboardRemoteModule>(remoteModuleId)\n } catch (error) {\n throw new Error(`Failed to load remote module \"${remoteModuleId}\"`, {cause: error})\n }\n\n if (!remoteModule || typeof remoteModule.render !== 'function') {\n throw new Error(`Remote module \"${remoteModuleId}\" did not expose a render function`)\n }\n\n let appConfigs: BehaviorSubject<unknown[]> | undefined\n let cleanupHmr = noop\n let localApplications: BehaviorSubject<unknown[]> | undefined\n\n if (import.meta.hot) {\n const hot = import.meta.hot\n const hmrLocalApplications = new BehaviorSubject<unknown[]>([])\n const hmrAppConfigs = new BehaviorSubject<unknown[]>([])\n const handler = (payload: {applications: unknown[]; configs?: unknown[]}) => {\n hmrLocalApplications.next(payload.applications)\n hmrAppConfigs.next(payload.configs ?? [])\n }\n\n localApplications = hmrLocalApplications\n appConfigs = hmrAppConfigs\n hot.on(localApplicationsEvent, handler)\n hot.send('sanity:workbench:get-local-applications')\n cleanupHmr = () => hot.off(localApplicationsEvent, handler)\n }\n\n const unmount = remoteModule.render(rootElement, {appConfigs, config, localApplications}, options)\n\n return () => {\n cleanupHmr()\n unmount()\n }\n}\n"],"names":["createInstance","BehaviorSubject","remoteName","remoteModuleId","localApplicationsEvent","noop","renderDashboard","rootElement","config","options","Error","deployedDashboardHost","globalThis","__SANITY_STAGING__","remoteUrl","env","SANITY_INTERNAL_WORKBENCH_REMOTE_URL","federation","name","remotes","entry","remoteModule","loadRemote","error","cause","render","appConfigs","cleanupHmr","localApplications","hot","hmrLocalApplications","hmrAppConfigs","handler","payload","next","applications","configs","on","send","off","unmount"],"mappings":"AAAA,qCAAqC;AAErC,4FAA4F;AAC5F,SAAQA,cAAc,QAAO,6BAA4B;AACzD,SAAQC,eAAe,QAAwB,OAAM;AA0BrD,MAAMC,aAAa;AACnB,MAAMC,iBAAiB,GAAGD,WAAW,IAAI,CAAC;AAC1C,MAAME,yBAAyB;AAC/B,MAAMC,OAAO,KAAO;AAEpB,OAAO,eAAeC,gBACpBC,WAA+B,EAC/BC,MAAuB,EACvBC,OAAgC;IAEhC,IAAI,CAACF,aAAa;QAChB,MAAM,IAAIG,MAAM;IAClB;IAEA,MAAMC,wBACJC,WAAWC,kBAAkB,KAAK,OAC9B,6CACA;IACN,MAAMC,YACJ,YAAYC,GAAG,CAACC,oCAAoC,IACpD,CAAC,QAAQ,EAAEL,sBAAsB,iBAAiB,CAAC;IAErD,MAAMM,aAAajB,eAAe;QAChCkB,MAAM;QACNC,SAAS;YAAC;gBAACC,OAAON;gBAAWI,MAAMhB;YAAU;SAAE;IACjD;IAEA,IAAImB;IACJ,IAAI;QACFA,eAAe,MAAMJ,WAAWK,UAAU,CAAwBnB;IACpE,EAAE,OAAOoB,OAAO;QACd,MAAM,IAAIb,MAAM,CAAC,8BAA8B,EAAEP,eAAe,CAAC,CAAC,EAAE;YAACqB,OAAOD;QAAK;IACnF;IAEA,IAAI,CAACF,gBAAgB,OAAOA,aAAaI,MAAM,KAAK,YAAY;QAC9D,MAAM,IAAIf,MAAM,CAAC,eAAe,EAAEP,eAAe,kCAAkC,CAAC;IACtF;IAEA,IAAIuB;IACJ,IAAIC,aAAatB;IACjB,IAAIuB;IAEJ,IAAI,YAAYC,GAAG,EAAE;QACnB,MAAMA,MAAM,YAAYA,GAAG;QAC3B,MAAMC,uBAAuB,IAAI7B,gBAA2B,EAAE;QAC9D,MAAM8B,gBAAgB,IAAI9B,gBAA2B,EAAE;QACvD,MAAM+B,UAAU,CAACC;YACfH,qBAAqBI,IAAI,CAACD,QAAQE,YAAY;YAC9CJ,cAAcG,IAAI,CAACD,QAAQG,OAAO,IAAI,EAAE;QAC1C;QAEAR,oBAAoBE;QACpBJ,aAAaK;QACbF,IAAIQ,EAAE,CAACjC,wBAAwB4B;QAC/BH,IAAIS,IAAI,CAAC;QACTX,aAAa,IAAME,IAAIU,GAAG,CAACnC,wBAAwB4B;IACrD;IAEA,MAAMQ,UAAUnB,aAAaI,MAAM,CAAClB,aAAa;QAACmB;QAAYlB;QAAQoB;IAAiB,GAAGnB;IAE1F,OAAO;QACLkB;QACAa;IACF;AACF"}
|
|
@@ -20,7 +20,7 @@ export async function getApplication(applicationId) {
|
|
|
20
20
|
const client = await getClient();
|
|
21
21
|
try {
|
|
22
22
|
return await client.request({
|
|
23
|
-
|
|
23
|
+
url: `/applications/${applicationId}`
|
|
24
24
|
});
|
|
25
25
|
} catch (err) {
|
|
26
26
|
if (err?.statusCode === 404) return null;
|
|
@@ -34,7 +34,7 @@ export async function getApplication(applicationId) {
|
|
|
34
34
|
limit: 'none',
|
|
35
35
|
organizationId
|
|
36
36
|
},
|
|
37
|
-
|
|
37
|
+
url: '/applications'
|
|
38
38
|
});
|
|
39
39
|
return data;
|
|
40
40
|
}
|
|
@@ -66,7 +66,7 @@ export async function getApplication(applicationId) {
|
|
|
66
66
|
} : {}
|
|
67
67
|
},
|
|
68
68
|
method: 'POST',
|
|
69
|
-
|
|
69
|
+
url: `/applications`
|
|
70
70
|
});
|
|
71
71
|
}
|
|
72
72
|
// Patch an application's mutable fields.
|
|
@@ -75,7 +75,7 @@ export async function updateApplication(applicationId, update) {
|
|
|
75
75
|
await client.request({
|
|
76
76
|
body: update,
|
|
77
77
|
method: 'PATCH',
|
|
78
|
-
|
|
78
|
+
url: `/applications/${applicationId}`
|
|
79
79
|
});
|
|
80
80
|
}
|
|
81
81
|
/** Deploy a new active version to an existing application. */ export async function createDeployment(options) {
|
|
@@ -96,7 +96,7 @@ export async function updateApplication(applicationId, update) {
|
|
|
96
96
|
try {
|
|
97
97
|
await client.request({
|
|
98
98
|
method: 'DELETE',
|
|
99
|
-
|
|
99
|
+
url: `/applications/${applicationId}`
|
|
100
100
|
});
|
|
101
101
|
} catch (err) {
|
|
102
102
|
if (err?.statusCode !== 404) throw err;
|
|
@@ -119,13 +119,13 @@ function appendDeploymentParts(formData, { access, interfaces, tarball, version,
|
|
|
119
119
|
contentType: 'application/json'
|
|
120
120
|
});
|
|
121
121
|
}
|
|
122
|
-
async function request(
|
|
122
|
+
async function request(url, formData) {
|
|
123
123
|
const client = await getClient();
|
|
124
124
|
return client.request({
|
|
125
125
|
body: formData.pipe(new PassThrough()),
|
|
126
126
|
headers: formData.getHeaders(),
|
|
127
127
|
method: 'POST',
|
|
128
|
-
|
|
128
|
+
url
|
|
129
129
|
});
|
|
130
130
|
}
|
|
131
131
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/services/applications.ts"],"sourcesContent":["import {PassThrough} from 'node:stream'\nimport {type Gzip} from 'node:zlib'\n\nimport {type AppVisibility, getGlobalCliClient} from '@sanity/cli-core'\nimport {isStaging} from '@sanity/cli-core/util'\nimport FormData from 'form-data'\n\nimport {type AppInterfaceMetadata, type TileInterfaceMetadata} from '../contract.js'\nimport {APP_WORKBENCH_API_VERSION} from './apiVersion.js'\n\nexport type ApplicationType = 'coreApp' | 'studio'\n\nexport interface Application {\n id: string\n organizationId: string\n slug: string | null\n title: string\n type: ApplicationType\n}\n\ninterface BrettInterfaceBase {\n moduleId: string\n name: string\n title: string\n version: string\n}\n\n/**\n * An interface as Brett stores it, discriminated on `type`. `moduleId` is\n * remote-relative — the host prepends the app's id. Brett assigns the id.\n * @internal\n */\nexport type BrettInterface =\n | (BrettInterfaceBase & {metadata: AppInterfaceMetadata | null; type: 'app'})\n | (BrettInterfaceBase & {metadata: null; type: 'asset_source'})\n | (BrettInterfaceBase & {metadata: null; type: 'panel'})\n | (BrettInterfaceBase & {metadata: null; type: 'worker'})\n | (BrettInterfaceBase & {metadata: TileInterfaceMetadata; type: 'tile'})\n\n/**\n * A resource a deployment may interact with, as Brett stores it. Per-deployment\n * and forbidden for singletons (the server 400s).\n */\nexport interface BrettAccess {\n resourceId: string\n resourceType: 'canvas' | 'dashboard' | 'dataset' | 'media-library'\n}\n\n/** A studio workspace as Brett stores it. */\nexport interface BrettWorkspace {\n dataset: string\n projectId: string\n /** Lexicon schema descriptor id; Brett requires one per workspace. */\n schemaDescriptorId: string\n\n basePath?: string\n icon?: string\n name?: string\n subtitle?: string\n title?: string\n}\n\nexport function getWorkbenchUrl(organizationId: string): string {\n return `https://${organizationId}.${isStaging() ? 'run.sanity.work' : 'sanity.run'}`\n}\n\n/** Where a deployed application is served on its organization's workbench. */\nexport function getApplicationUrl(\n application: Pick<Application, 'id' | 'organizationId' | 'type'>,\n): string {\n const segment = application.type === 'studio' ? 'studio' : 'application'\n return `${getWorkbenchUrl(application.organizationId)}/${segment}/${application.id}`\n}\n\nasync function getClient() {\n return getGlobalCliClient({apiVersion: APP_WORKBENCH_API_VERSION, requireUser: true})\n}\n\nexport async function getApplication(applicationId: string): Promise<Application | null> {\n const client = await getClient()\n try {\n return await client.request({uri: `/applications/${applicationId}`})\n } catch (err) {\n if ((err as {statusCode?: number})?.statusCode === 404) return null\n throw err\n }\n}\n\n/** Every application in an organization, in one page (`limit=none`). */\nexport async function listApplications(organizationId: string): Promise<Application[]> {\n const client = await getClient()\n const {data}: {data: Application[]} = await client.request({\n query: {limit: 'none', organizationId},\n uri: '/applications',\n })\n return data\n}\n\n/**\n * Create an application record (no deployment), so the CLI can build with the\n * returned id, then ship it via {@link createDeployment}.\n */\nexport async function createApplication(options: {\n isSingleton?: boolean\n organizationId: string\n projectId?: string\n slug: string\n title: string\n type: ApplicationType\n visibility?: AppVisibility\n}): Promise<Application> {\n const {isSingleton, organizationId, projectId, slug, title, type, visibility} = options\n const client = await getClient()\n return client.request({\n body: {\n organizationId,\n slug,\n title,\n type,\n ...(isSingleton === undefined ? {} : {isSingleton}),\n ...(visibility ? {visibility} : {}),\n // Studio config is set once, at create — it's immutable on redeploy.\n ...(projectId ? {config: {studio: {projectId}}} : {}),\n },\n method: 'POST',\n uri: `/applications`,\n })\n}\n\n/** Mutable application fields the deploy flow patches after create. */\nexport interface ApplicationUpdate {\n icon?: string | null\n title?: string\n visibility?: AppVisibility\n}\n\n// Patch an application's mutable fields.\nexport async function updateApplication(\n applicationId: string,\n update: ApplicationUpdate,\n): Promise<void> {\n const client = await getClient()\n await client.request({body: update, method: 'PATCH', uri: `/applications/${applicationId}`})\n}\n\n/** Deploy a new active version to an existing application. */\nexport async function createDeployment(options: {\n access?: readonly BrettAccess[]\n applicationId: string\n interfaces: readonly BrettInterface[]\n isAutoUpdating: boolean\n tarball: Gzip\n version: string\n workspaces?: readonly BrettWorkspace[]\n}): Promise<{id: string}> {\n const {access, applicationId, interfaces, isAutoUpdating, tarball, version, workspaces} = options\n const formData = new FormData()\n formData.append('isAutoUpdating', isAutoUpdating.toString())\n appendDeploymentParts(formData, {access, interfaces, tarball, version, workspaces})\n return request(`/applications/${applicationId}/deployments`, formData)\n}\n\n/** Soft-deletes the application and all its deployments; already deleted counts as done. */\nexport async function deleteApplication(applicationId: string): Promise<void> {\n const client = await getClient()\n try {\n await client.request({method: 'DELETE', uri: `/applications/${applicationId}`})\n } catch (err) {\n if ((err as {statusCode?: number})?.statusCode !== 404) throw err\n }\n}\n\nfunction appendDeploymentParts(\n formData: FormData,\n {\n access,\n interfaces,\n tarball,\n version,\n workspaces,\n }: {\n access?: readonly BrettAccess[]\n interfaces: readonly BrettInterface[]\n tarball: Gzip\n version: string\n workspaces?: readonly BrettWorkspace[]\n },\n): void {\n formData.append('version', version)\n appendJson(formData, 'interfaces', interfaces)\n // Studio-only — the server rejects a workspaces part on non-studio types.\n if (workspaces?.length) appendJson(formData, 'workspaces', workspaces)\n // Per-deployment; forbidden for singletons, so callers omit it there.\n if (access?.length) appendJson(formData, 'access', access)\n formData.append('tarball', tarball, {contentType: 'application/gzip', filename: 'app.tar.gz'})\n}\n\n/** Structured parts must arrive as JSON so the server parses them. */\nfunction appendJson(formData: FormData, name: string, value: unknown): void {\n formData.append(name, JSON.stringify(value), {contentType: 'application/json'})\n}\n\nasync function request<T>(uri: string, formData: FormData): Promise<T> {\n const client = await getClient()\n return client.request({\n body: formData.pipe(new PassThrough()),\n headers: formData.getHeaders(),\n method: 'POST',\n uri,\n })\n}\n"],"names":["PassThrough","getGlobalCliClient","isStaging","FormData","APP_WORKBENCH_API_VERSION","getWorkbenchUrl","organizationId","getApplicationUrl","application","segment","type","id","getClient","apiVersion","requireUser","getApplication","applicationId","client","request","uri","err","statusCode","listApplications","data","query","limit","createApplication","options","isSingleton","projectId","slug","title","visibility","body","undefined","config","studio","method","updateApplication","update","createDeployment","access","interfaces","isAutoUpdating","tarball","version","workspaces","formData","append","toString","appendDeploymentParts","deleteApplication","appendJson","length","contentType","filename","name","value","JSON","stringify","pipe","headers","getHeaders"],"mappings":"AAAA,SAAQA,WAAW,QAAO,cAAa;AAGvC,SAA4BC,kBAAkB,QAAO,mBAAkB;AACvE,SAAQC,SAAS,QAAO,wBAAuB;AAC/C,OAAOC,cAAc,YAAW;AAGhC,SAAQC,yBAAyB,QAAO,kBAAiB;AAsDzD,OAAO,SAASC,gBAAgBC,cAAsB;IACpD,OAAO,CAAC,QAAQ,EAAEA,eAAe,CAAC,EAAEJ,cAAc,oBAAoB,cAAc;AACtF;AAEA,4EAA4E,GAC5E,OAAO,SAASK,kBACdC,WAAgE;IAEhE,MAAMC,UAAUD,YAAYE,IAAI,KAAK,WAAW,WAAW;IAC3D,OAAO,GAAGL,gBAAgBG,YAAYF,cAAc,EAAE,CAAC,EAAEG,QAAQ,CAAC,EAAED,YAAYG,EAAE,EAAE;AACtF;AAEA,eAAeC;IACb,OAAOX,mBAAmB;QAACY,YAAYT;QAA2BU,aAAa;IAAI;AACrF;AAEA,OAAO,eAAeC,eAAeC,aAAqB;IACxD,MAAMC,SAAS,MAAML;IACrB,IAAI;QACF,OAAO,MAAMK,OAAOC,OAAO,CAAC;YAACC,KAAK,CAAC,cAAc,EAAEH,eAAe;QAAA;IACpE,EAAE,OAAOI,KAAK;QACZ,IAAI,AAACA,KAA+BC,eAAe,KAAK,OAAO;QAC/D,MAAMD;IACR;AACF;AAEA,sEAAsE,GACtE,OAAO,eAAeE,iBAAiBhB,cAAsB;IAC3D,MAAMW,SAAS,MAAML;IACrB,MAAM,EAACW,IAAI,EAAC,GAA0B,MAAMN,OAAOC,OAAO,CAAC;QACzDM,OAAO;YAACC,OAAO;YAAQnB;QAAc;QACrCa,KAAK;IACP;IACA,OAAOI;AACT;AAEA;;;CAGC,GACD,OAAO,eAAeG,kBAAkBC,OAQvC;IACC,MAAM,EAACC,WAAW,EAAEtB,cAAc,EAAEuB,SAAS,EAAEC,IAAI,EAAEC,KAAK,EAAErB,IAAI,EAAEsB,UAAU,EAAC,GAAGL;IAChF,MAAMV,SAAS,MAAML;IACrB,OAAOK,OAAOC,OAAO,CAAC;QACpBe,MAAM;YACJ3B;YACAwB;YACAC;YACArB;YACA,GAAIkB,gBAAgBM,YAAY,CAAC,IAAI;gBAACN;YAAW,CAAC;YAClD,GAAII,aAAa;gBAACA;YAAU,IAAI,CAAC,CAAC;YAClC,qEAAqE;YACrE,GAAIH,YAAY;gBAACM,QAAQ;oBAACC,QAAQ;wBAACP;oBAAS;gBAAC;YAAC,IAAI,CAAC,CAAC;QACtD;QACAQ,QAAQ;QACRlB,KAAK,CAAC,aAAa,CAAC;IACtB;AACF;AASA,yCAAyC;AACzC,OAAO,eAAemB,kBACpBtB,aAAqB,EACrBuB,MAAyB;IAEzB,MAAMtB,SAAS,MAAML;IACrB,MAAMK,OAAOC,OAAO,CAAC;QAACe,MAAMM;QAAQF,QAAQ;QAASlB,KAAK,CAAC,cAAc,EAAEH,eAAe;IAAA;AAC5F;AAEA,4DAA4D,GAC5D,OAAO,eAAewB,iBAAiBb,OAQtC;IACC,MAAM,EAACc,MAAM,EAAEzB,aAAa,EAAE0B,UAAU,EAAEC,cAAc,EAAEC,OAAO,EAAEC,OAAO,EAAEC,UAAU,EAAC,GAAGnB;IAC1F,MAAMoB,WAAW,IAAI5C;IACrB4C,SAASC,MAAM,CAAC,kBAAkBL,eAAeM,QAAQ;IACzDC,sBAAsBH,UAAU;QAACN;QAAQC;QAAYE;QAASC;QAASC;IAAU;IACjF,OAAO5B,QAAQ,CAAC,cAAc,EAAEF,cAAc,YAAY,CAAC,EAAE+B;AAC/D;AAEA,0FAA0F,GAC1F,OAAO,eAAeI,kBAAkBnC,aAAqB;IAC3D,MAAMC,SAAS,MAAML;IACrB,IAAI;QACF,MAAMK,OAAOC,OAAO,CAAC;YAACmB,QAAQ;YAAUlB,KAAK,CAAC,cAAc,EAAEH,eAAe;QAAA;IAC/E,EAAE,OAAOI,KAAK;QACZ,IAAI,AAACA,KAA+BC,eAAe,KAAK,MAAMD;IAChE;AACF;AAEA,SAAS8B,sBACPH,QAAkB,EAClB,EACEN,MAAM,EACNC,UAAU,EACVE,OAAO,EACPC,OAAO,EACPC,UAAU,EAOX;IAEDC,SAASC,MAAM,CAAC,WAAWH;IAC3BO,WAAWL,UAAU,cAAcL;IACnC,0EAA0E;IAC1E,IAAII,YAAYO,QAAQD,WAAWL,UAAU,cAAcD;IAC3D,sEAAsE;IACtE,IAAIL,QAAQY,QAAQD,WAAWL,UAAU,UAAUN;IACnDM,SAASC,MAAM,CAAC,WAAWJ,SAAS;QAACU,aAAa;QAAoBC,UAAU;IAAY;AAC9F;AAEA,oEAAoE,GACpE,SAASH,WAAWL,QAAkB,EAAES,IAAY,EAAEC,KAAc;IAClEV,SAASC,MAAM,CAACQ,MAAME,KAAKC,SAAS,CAACF,QAAQ;QAACH,aAAa;IAAkB;AAC/E;AAEA,eAAepC,QAAWC,GAAW,EAAE4B,QAAkB;IACvD,MAAM9B,SAAS,MAAML;IACrB,OAAOK,OAAOC,OAAO,CAAC;QACpBe,MAAMc,SAASa,IAAI,CAAC,IAAI5D;QACxB6D,SAASd,SAASe,UAAU;QAC5BzB,QAAQ;QACRlB;IACF;AACF"}
|
|
1
|
+
{"version":3,"sources":["../../src/services/applications.ts"],"sourcesContent":["import {PassThrough} from 'node:stream'\nimport {type Gzip} from 'node:zlib'\n\nimport {type AppVisibility, getGlobalCliClient} from '@sanity/cli-core'\nimport {isStaging} from '@sanity/cli-core/util'\nimport FormData from 'form-data'\n\nimport {type AppInterfaceMetadata, type TileInterfaceMetadata} from '../contract.js'\nimport {APP_WORKBENCH_API_VERSION} from './apiVersion.js'\n\nexport type ApplicationType = 'coreApp' | 'studio'\n\nexport interface Application {\n id: string\n organizationId: string\n slug: string | null\n title: string\n type: ApplicationType\n}\n\ninterface BrettInterfaceBase {\n moduleId: string\n name: string\n title: string\n version: string\n}\n\n/**\n * An interface as Brett stores it, discriminated on `type`. `moduleId` is\n * remote-relative — the host prepends the app's id. Brett assigns the id.\n * @internal\n */\nexport type BrettInterface =\n | (BrettInterfaceBase & {metadata: AppInterfaceMetadata | null; type: 'app'})\n | (BrettInterfaceBase & {metadata: null; type: 'asset_source'})\n | (BrettInterfaceBase & {metadata: null; type: 'panel'})\n | (BrettInterfaceBase & {metadata: null; type: 'worker'})\n | (BrettInterfaceBase & {metadata: TileInterfaceMetadata; type: 'tile'})\n\n/**\n * A resource a deployment may interact with, as Brett stores it. Per-deployment\n * and forbidden for singletons (the server 400s).\n */\nexport interface BrettAccess {\n resourceId: string\n resourceType: 'canvas' | 'dashboard' | 'dataset' | 'media-library'\n}\n\n/** A studio workspace as Brett stores it. */\nexport interface BrettWorkspace {\n dataset: string\n projectId: string\n /** Lexicon schema descriptor id; Brett requires one per workspace. */\n schemaDescriptorId: string\n\n basePath?: string\n icon?: string\n name?: string\n subtitle?: string\n title?: string\n}\n\nexport function getWorkbenchUrl(organizationId: string): string {\n return `https://${organizationId}.${isStaging() ? 'run.sanity.work' : 'sanity.run'}`\n}\n\n/** Where a deployed application is served on its organization's workbench. */\nexport function getApplicationUrl(\n application: Pick<Application, 'id' | 'organizationId' | 'type'>,\n): string {\n const segment = application.type === 'studio' ? 'studio' : 'application'\n return `${getWorkbenchUrl(application.organizationId)}/${segment}/${application.id}`\n}\n\nasync function getClient() {\n return getGlobalCliClient({apiVersion: APP_WORKBENCH_API_VERSION, requireUser: true})\n}\n\nexport async function getApplication(applicationId: string): Promise<Application | null> {\n const client = await getClient()\n try {\n return await client.request({url: `/applications/${applicationId}`})\n } catch (err) {\n if ((err as {statusCode?: number})?.statusCode === 404) return null\n throw err\n }\n}\n\n/** Every application in an organization, in one page (`limit=none`). */\nexport async function listApplications(organizationId: string): Promise<Application[]> {\n const client = await getClient()\n const {data}: {data: Application[]} = await client.request({\n query: {limit: 'none', organizationId},\n url: '/applications',\n })\n return data\n}\n\n/**\n * Create an application record (no deployment), so the CLI can build with the\n * returned id, then ship it via {@link createDeployment}.\n */\nexport async function createApplication(options: {\n isSingleton?: boolean\n organizationId: string\n projectId?: string\n slug: string\n title: string\n type: ApplicationType\n visibility?: AppVisibility\n}): Promise<Application> {\n const {isSingleton, organizationId, projectId, slug, title, type, visibility} = options\n const client = await getClient()\n return client.request({\n body: {\n organizationId,\n slug,\n title,\n type,\n ...(isSingleton === undefined ? {} : {isSingleton}),\n ...(visibility ? {visibility} : {}),\n // Studio config is set once, at create — it's immutable on redeploy.\n ...(projectId ? {config: {studio: {projectId}}} : {}),\n },\n method: 'POST',\n url: `/applications`,\n })\n}\n\n/** Mutable application fields the deploy flow patches after create. */\nexport interface ApplicationUpdate {\n icon?: string | null\n title?: string\n visibility?: AppVisibility\n}\n\n// Patch an application's mutable fields.\nexport async function updateApplication(\n applicationId: string,\n update: ApplicationUpdate,\n): Promise<void> {\n const client = await getClient()\n await client.request({body: update, method: 'PATCH', url: `/applications/${applicationId}`})\n}\n\n/** Deploy a new active version to an existing application. */\nexport async function createDeployment(options: {\n access?: readonly BrettAccess[]\n applicationId: string\n interfaces: readonly BrettInterface[]\n isAutoUpdating: boolean\n tarball: Gzip\n version: string\n workspaces?: readonly BrettWorkspace[]\n}): Promise<{id: string}> {\n const {access, applicationId, interfaces, isAutoUpdating, tarball, version, workspaces} = options\n const formData = new FormData()\n formData.append('isAutoUpdating', isAutoUpdating.toString())\n appendDeploymentParts(formData, {access, interfaces, tarball, version, workspaces})\n return request(`/applications/${applicationId}/deployments`, formData)\n}\n\n/** Soft-deletes the application and all its deployments; already deleted counts as done. */\nexport async function deleteApplication(applicationId: string): Promise<void> {\n const client = await getClient()\n try {\n await client.request({method: 'DELETE', url: `/applications/${applicationId}`})\n } catch (err) {\n if ((err as {statusCode?: number})?.statusCode !== 404) throw err\n }\n}\n\nfunction appendDeploymentParts(\n formData: FormData,\n {\n access,\n interfaces,\n tarball,\n version,\n workspaces,\n }: {\n access?: readonly BrettAccess[]\n interfaces: readonly BrettInterface[]\n tarball: Gzip\n version: string\n workspaces?: readonly BrettWorkspace[]\n },\n): void {\n formData.append('version', version)\n appendJson(formData, 'interfaces', interfaces)\n // Studio-only — the server rejects a workspaces part on non-studio types.\n if (workspaces?.length) appendJson(formData, 'workspaces', workspaces)\n // Per-deployment; forbidden for singletons, so callers omit it there.\n if (access?.length) appendJson(formData, 'access', access)\n formData.append('tarball', tarball, {contentType: 'application/gzip', filename: 'app.tar.gz'})\n}\n\n/** Structured parts must arrive as JSON so the server parses them. */\nfunction appendJson(formData: FormData, name: string, value: unknown): void {\n formData.append(name, JSON.stringify(value), {contentType: 'application/json'})\n}\n\nasync function request<T>(url: string, formData: FormData): Promise<T> {\n const client = await getClient()\n return client.request({\n body: formData.pipe(new PassThrough()),\n headers: formData.getHeaders(),\n method: 'POST',\n url,\n })\n}\n"],"names":["PassThrough","getGlobalCliClient","isStaging","FormData","APP_WORKBENCH_API_VERSION","getWorkbenchUrl","organizationId","getApplicationUrl","application","segment","type","id","getClient","apiVersion","requireUser","getApplication","applicationId","client","request","url","err","statusCode","listApplications","data","query","limit","createApplication","options","isSingleton","projectId","slug","title","visibility","body","undefined","config","studio","method","updateApplication","update","createDeployment","access","interfaces","isAutoUpdating","tarball","version","workspaces","formData","append","toString","appendDeploymentParts","deleteApplication","appendJson","length","contentType","filename","name","value","JSON","stringify","pipe","headers","getHeaders"],"mappings":"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"}
|