@sanity/workbench-cli 2.3.0 → 2.4.1
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/build.d.ts +54 -2
- package/dist/_exports/build.js +1 -0
- package/dist/_exports/build.js.map +1 -1
- package/dist/_exports/{defineApp-BRFgHrPc.d.ts → defineApp-KCHd3Vab.d.ts} +25 -25
- package/dist/_exports/deploy.d.ts +11 -6
- package/dist/_exports/dev.d.ts +1 -1
- package/dist/_exports/index.d.ts +1 -1
- package/dist/_exports/init.d.ts +2 -2
- package/dist/_exports/preview.d.ts +1 -1
- package/dist/_exports/{registry-CexFxsKR.d.ts → registry-BCLMbrla.d.ts} +10 -10
- package/dist/_exports/{resolveWorkbenchConfig-CDUdRIjD.d.ts → resolveWorkbenchConfig-1CV651GT.d.ts} +2 -2
- package/dist/_exports/{summarizeInterfaces-BQ1v1O89.d.ts → summarizeInterfaces-DoG6OunM.d.ts} +3 -3
- package/dist/_exports/undeploy.d.ts +2 -2
- package/dist/actions/build/resource-bindings.js +56 -0
- package/dist/actions/build/resource-bindings.js.map +1 -0
- package/dist/actions/build/vite/plugin.js +6 -3
- package/dist/actions/build/vite/plugin.js.map +1 -1
- package/dist/actions/build/vite/plugins/plugin-sanity-environment.js +22 -3
- package/dist/actions/build/vite/plugins/plugin-sanity-environment.js.map +1 -1
- package/dist/actions/build/vite/plugins/plugin-sanity-federation-runtime.js +16 -4
- package/dist/actions/build/vite/plugins/plugin-sanity-federation-runtime.js.map +1 -1
- package/dist/actions/build/vite/workbench-vite-plugins.js +2 -1
- package/dist/actions/build/vite/workbench-vite-plugins.js.map +1 -1
- package/dist/actions/deploy/deployConfig.js +6 -6
- package/dist/actions/deploy/deployConfig.js.map +1 -1
- package/dist/actions/deploy/deployWorkbenchApp.js +39 -19
- package/dist/actions/deploy/deployWorkbenchApp.js.map +1 -1
- package/dist/actions/dev/startWorkbenchDevServer.js +3 -1
- package/dist/actions/dev/startWorkbenchDevServer.js.map +1 -1
- package/dist/actions/dev/toWireInterface.js +1 -1
- package/dist/actions/dev/toWireInterface.js.map +1 -1
- package/dist/actions/init/cliConfig.js +6 -6
- package/dist/actions/init/cliConfig.js.map +1 -1
- package/dist/contract.js +3 -3
- package/dist/contract.js.map +1 -1
- package/dist/services/applications.js.map +1 -1
- package/package.json +9 -10
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { renderRemote } from '../../render-remote.js';
|
|
4
|
+
import { RESOURCE_BINDINGS_ENTRY_IMPORT, RESOURCE_BINDINGS_FILENAME, RESOURCE_BINDINGS_MODULE_SOURCE } from '../../resource-bindings.js';
|
|
4
5
|
import { FEDERATION_FILE_NAME, RUNTIME_DIR } from '../constants.js';
|
|
5
6
|
const REMOTE_ENTRY_FILE = `${FEDERATION_FILE_NAME}.jsx`;
|
|
6
7
|
// The studio wraps `Studio` with the user's config; HMR re-renders through the
|
|
7
|
-
// new module so a config edit takes effect.
|
|
8
|
+
// new module so a config edit takes effect. The `%RESOURCE_BINDINGS_IMPORT%`
|
|
9
|
+
// placeholder is filled in per-build (Blueprints only — see below).
|
|
8
10
|
const STUDIO_ENTRY = renderRemote({
|
|
9
11
|
app: `(props) => createElement(Studio, { config, ...props })`,
|
|
10
12
|
hmr: true,
|
|
11
|
-
preamble:
|
|
13
|
+
preamble: `%RESOURCE_BINDINGS_IMPORT%import { Studio } from 'sanity'
|
|
12
14
|
import config from %STUDIO_CONFIG%`
|
|
13
15
|
});
|
|
14
16
|
// An SDK app's default export is the component; it Fast-Refreshes through its
|
|
15
17
|
// own dev server, so the wrapper needs no HMR boundary.
|
|
16
18
|
const APP_ENTRY = renderRemote({
|
|
17
|
-
preamble:
|
|
19
|
+
preamble: `%RESOURCE_BINDINGS_IMPORT%import App from %APP_ENTRY%`
|
|
18
20
|
});
|
|
19
21
|
// A branded app that declares no `entry` (e.g. a dock-only panel/worker app)
|
|
20
22
|
// has no navigable full-page view, so there's no `App` to import. The runtime
|
|
@@ -25,17 +27,23 @@ const HEADLESS_APP_ENTRY = `\
|
|
|
25
27
|
// Modifications to this file are automatically discarded
|
|
26
28
|
// This application declares no app view (no \`entry\`): it isn't navigable as a
|
|
27
29
|
// full-page app, only its panels/web workers are exposed.
|
|
28
|
-
export function render() {
|
|
30
|
+
%RESOURCE_BINDINGS_IMPORT%export function render() {
|
|
29
31
|
throw new Error('This application has no app view: it declares no \`entry\`.')
|
|
30
32
|
}
|
|
31
33
|
`;
|
|
32
34
|
export function sanityFederationRuntime(options) {
|
|
35
|
+
const { isBlueprints } = options;
|
|
33
36
|
let content;
|
|
34
37
|
if (options.isApp) {
|
|
35
38
|
content = options.appEntry ? APP_ENTRY.replace(/%APP_ENTRY%/, JSON.stringify(options.appEntry)) : HEADLESS_APP_ENTRY;
|
|
36
39
|
} else {
|
|
37
40
|
content = STUDIO_ENTRY.replace(/%STUDIO_CONFIG%/, JSON.stringify(options.studioConfigPath));
|
|
38
41
|
}
|
|
42
|
+
// Blueprints only: the remote entry statically imports the resource-bindings
|
|
43
|
+
// module first, so bindings evaluate before app code. Off Blueprints the
|
|
44
|
+
// placeholder resolves to nothing and the module is neither imported nor
|
|
45
|
+
// written below.
|
|
46
|
+
content = content.replace(/%RESOURCE_BINDINGS_IMPORT%/, isBlueprints ? `${RESOURCE_BINDINGS_ENTRY_IMPORT}\n` : '');
|
|
39
47
|
let entryFileAbsPath = '';
|
|
40
48
|
return {
|
|
41
49
|
configResolved (config) {
|
|
@@ -45,6 +53,10 @@ export function sanityFederationRuntime(options) {
|
|
|
45
53
|
recursive: true
|
|
46
54
|
});
|
|
47
55
|
fs.writeFileSync(entryFileAbsPath, content);
|
|
56
|
+
if (isBlueprints) {
|
|
57
|
+
// Brett bakes the resolved values into this module at deploy.
|
|
58
|
+
fs.writeFileSync(path.join(dir, RESOURCE_BINDINGS_FILENAME), RESOURCE_BINDINGS_MODULE_SOURCE);
|
|
59
|
+
}
|
|
48
60
|
},
|
|
49
61
|
hotUpdate ({ file, modules, timestamp }) {
|
|
50
62
|
if (options.isApp) return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../src/actions/build/vite/plugins/plugin-sanity-federation-runtime.ts"],"sourcesContent":["import fs from 'node:fs'\nimport path from 'node:path'\n\nimport {type EnvironmentModuleNode, type Plugin} from 'vite'\n\nimport {renderRemote} from '../../render-remote.js'\nimport {FEDERATION_FILE_NAME, RUNTIME_DIR} from '../constants.js'\n\nconst REMOTE_ENTRY_FILE = `${FEDERATION_FILE_NAME}.jsx`\n\n// The studio wraps `Studio` with the user's config; HMR re-renders through the\n// new module so a config edit takes effect.\nconst STUDIO_ENTRY = renderRemote({\n app: `(props) => createElement(Studio, { config, ...props })`,\n hmr: true,\n preamble:
|
|
1
|
+
{"version":3,"sources":["../../../../../src/actions/build/vite/plugins/plugin-sanity-federation-runtime.ts"],"sourcesContent":["import fs from 'node:fs'\nimport path from 'node:path'\n\nimport {type EnvironmentModuleNode, type Plugin} from 'vite'\n\nimport {renderRemote} from '../../render-remote.js'\nimport {\n RESOURCE_BINDINGS_ENTRY_IMPORT,\n RESOURCE_BINDINGS_FILENAME,\n RESOURCE_BINDINGS_MODULE_SOURCE,\n} from '../../resource-bindings.js'\nimport {FEDERATION_FILE_NAME, RUNTIME_DIR} from '../constants.js'\n\nconst REMOTE_ENTRY_FILE = `${FEDERATION_FILE_NAME}.jsx`\n\n// The studio wraps `Studio` with the user's config; HMR re-renders through the\n// new module so a config edit takes effect. The `%RESOURCE_BINDINGS_IMPORT%`\n// placeholder is filled in per-build (Blueprints only — see below).\nconst STUDIO_ENTRY = renderRemote({\n app: `(props) => createElement(Studio, { config, ...props })`,\n hmr: true,\n preamble: `%RESOURCE_BINDINGS_IMPORT%import { Studio } from 'sanity'\nimport config from %STUDIO_CONFIG%`,\n})\n\n// An SDK app's default export is the component; it Fast-Refreshes through its\n// own dev server, so the wrapper needs no HMR boundary.\nconst APP_ENTRY = renderRemote({\n preamble: `%RESOURCE_BINDINGS_IMPORT%import App from %APP_ENTRY%`,\n})\n\n// A branded app that declares no `entry` (e.g. a dock-only panel/worker app)\n// has no navigable full-page view, so there's no `App` to import. The runtime\n// still needs a valid module for the federation build input, but it exposes no\n// `./App` (see `plugin.ts`) — its `render` is unreachable and throws if reached.\nconst HEADLESS_APP_ENTRY = `\\\n// This file is auto-generated on 'sanity dev'\n// Modifications to this file are automatically discarded\n// This application declares no app view (no \\`entry\\`): it isn't navigable as a\n// full-page app, only its panels/web workers are exposed.\n%RESOURCE_BINDINGS_IMPORT%export function render() {\n throw new Error('This application has no app view: it declares no \\`entry\\`.')\n}\n`\n\nexport type FederationRuntimeOptions =\n | {appEntry?: string; isApp: true; isBlueprints?: boolean}\n | {isApp: false; isBlueprints?: boolean; studioConfigPath: string}\n\nexport function sanityFederationRuntime(options: FederationRuntimeOptions): Plugin {\n const {isBlueprints} = options\n\n let content: string\n if (options.isApp) {\n content = options.appEntry\n ? APP_ENTRY.replace(/%APP_ENTRY%/, JSON.stringify(options.appEntry))\n : HEADLESS_APP_ENTRY\n } else {\n content = STUDIO_ENTRY.replace(/%STUDIO_CONFIG%/, JSON.stringify(options.studioConfigPath))\n }\n\n // Blueprints only: the remote entry statically imports the resource-bindings\n // module first, so bindings evaluate before app code. Off Blueprints the\n // placeholder resolves to nothing and the module is neither imported nor\n // written below.\n content = content.replace(\n /%RESOURCE_BINDINGS_IMPORT%/,\n isBlueprints ? `${RESOURCE_BINDINGS_ENTRY_IMPORT}\\n` : '',\n )\n\n let entryFileAbsPath = ''\n\n return {\n configResolved(config) {\n const dir = path.resolve(config.root, RUNTIME_DIR)\n entryFileAbsPath = path.join(dir, REMOTE_ENTRY_FILE)\n\n fs.mkdirSync(dir, {recursive: true})\n fs.writeFileSync(entryFileAbsPath, content)\n\n if (isBlueprints) {\n // Brett bakes the resolved values into this module at deploy.\n fs.writeFileSync(\n path.join(dir, RESOURCE_BINDINGS_FILENAME),\n RESOURCE_BINDINGS_MODULE_SOURCE,\n )\n }\n },\n hotUpdate({file, modules, timestamp}) {\n if (options.isApp) return\n if (this.environment.name !== 'client') return\n\n const {moduleGraph} = this.environment\n const studioMods = moduleGraph.getModulesByFile(entryFileAbsPath)\n if (!studioMods?.size) return\n\n // Is the changed file reachable from the studio entry?\n const visited = new Set<EnvironmentModuleNode>()\n const queue: EnvironmentModuleNode[] = [...studioMods]\n while (queue.length > 0) {\n const mod = queue.pop()!\n if (visited.has(mod)) continue\n visited.add(mod)\n if (mod.file === file) {\n // The walk from `file` up through importers dead-ends at federation\n // gaps, so invalidate changed modules ourselves and route HMR to the\n // self-accepting studio entry.\n const seen = new Set<EnvironmentModuleNode>()\n for (const m of modules) {\n moduleGraph.invalidateModule(m, seen, timestamp, true)\n }\n return [...studioMods]\n }\n for (const dep of mod.importedModules) queue.push(dep)\n }\n },\n name: 'sanity/federation-runtime',\n }\n}\n"],"names":["fs","path","renderRemote","RESOURCE_BINDINGS_ENTRY_IMPORT","RESOURCE_BINDINGS_FILENAME","RESOURCE_BINDINGS_MODULE_SOURCE","FEDERATION_FILE_NAME","RUNTIME_DIR","REMOTE_ENTRY_FILE","STUDIO_ENTRY","app","hmr","preamble","APP_ENTRY","HEADLESS_APP_ENTRY","sanityFederationRuntime","options","isBlueprints","content","isApp","appEntry","replace","JSON","stringify","studioConfigPath","entryFileAbsPath","configResolved","config","dir","resolve","root","join","mkdirSync","recursive","writeFileSync","hotUpdate","file","modules","timestamp","environment","name","moduleGraph","studioMods","getModulesByFile","size","visited","Set","queue","length","mod","pop","has","add","seen","m","invalidateModule","dep","importedModules","push"],"mappings":"AAAA,OAAOA,QAAQ,UAAS;AACxB,OAAOC,UAAU,YAAW;AAI5B,SAAQC,YAAY,QAAO,yBAAwB;AACnD,SACEC,8BAA8B,EAC9BC,0BAA0B,EAC1BC,+BAA+B,QAC1B,6BAA4B;AACnC,SAAQC,oBAAoB,EAAEC,WAAW,QAAO,kBAAiB;AAEjE,MAAMC,oBAAoB,GAAGF,qBAAqB,IAAI,CAAC;AAEvD,+EAA+E;AAC/E,6EAA6E;AAC7E,oEAAoE;AACpE,MAAMG,eAAeP,aAAa;IAChCQ,KAAK,CAAC,sDAAsD,CAAC;IAC7DC,KAAK;IACLC,UAAU,CAAC;kCACqB,CAAC;AACnC;AAEA,8EAA8E;AAC9E,wDAAwD;AACxD,MAAMC,YAAYX,aAAa;IAC7BU,UAAU,CAAC,qDAAqD,CAAC;AACnE;AAEA,6EAA6E;AAC7E,8EAA8E;AAC9E,+EAA+E;AAC/E,iFAAiF;AACjF,MAAME,qBAAqB,CAAC;;;;;;;;AAQ5B,CAAC;AAMD,OAAO,SAASC,wBAAwBC,OAAiC;IACvE,MAAM,EAACC,YAAY,EAAC,GAAGD;IAEvB,IAAIE;IACJ,IAAIF,QAAQG,KAAK,EAAE;QACjBD,UAAUF,QAAQI,QAAQ,GACtBP,UAAUQ,OAAO,CAAC,eAAeC,KAAKC,SAAS,CAACP,QAAQI,QAAQ,KAChEN;IACN,OAAO;QACLI,UAAUT,aAAaY,OAAO,CAAC,mBAAmBC,KAAKC,SAAS,CAACP,QAAQQ,gBAAgB;IAC3F;IAEA,6EAA6E;IAC7E,yEAAyE;IACzE,yEAAyE;IACzE,iBAAiB;IACjBN,UAAUA,QAAQG,OAAO,CACvB,8BACAJ,eAAe,GAAGd,+BAA+B,EAAE,CAAC,GAAG;IAGzD,IAAIsB,mBAAmB;IAEvB,OAAO;QACLC,gBAAeC,MAAM;YACnB,MAAMC,MAAM3B,KAAK4B,OAAO,CAACF,OAAOG,IAAI,EAAEvB;YACtCkB,mBAAmBxB,KAAK8B,IAAI,CAACH,KAAKpB;YAElCR,GAAGgC,SAAS,CAACJ,KAAK;gBAACK,WAAW;YAAI;YAClCjC,GAAGkC,aAAa,CAACT,kBAAkBP;YAEnC,IAAID,cAAc;gBAChB,8DAA8D;gBAC9DjB,GAAGkC,aAAa,CACdjC,KAAK8B,IAAI,CAACH,KAAKxB,6BACfC;YAEJ;QACF;QACA8B,WAAU,EAACC,IAAI,EAAEC,OAAO,EAAEC,SAAS,EAAC;YAClC,IAAItB,QAAQG,KAAK,EAAE;YACnB,IAAI,IAAI,CAACoB,WAAW,CAACC,IAAI,KAAK,UAAU;YAExC,MAAM,EAACC,WAAW,EAAC,GAAG,IAAI,CAACF,WAAW;YACtC,MAAMG,aAAaD,YAAYE,gBAAgB,CAAClB;YAChD,IAAI,CAACiB,YAAYE,MAAM;YAEvB,uDAAuD;YACvD,MAAMC,UAAU,IAAIC;YACpB,MAAMC,QAAiC;mBAAIL;aAAW;YACtD,MAAOK,MAAMC,MAAM,GAAG,EAAG;gBACvB,MAAMC,MAAMF,MAAMG,GAAG;gBACrB,IAAIL,QAAQM,GAAG,CAACF,MAAM;gBACtBJ,QAAQO,GAAG,CAACH;gBACZ,IAAIA,IAAIb,IAAI,KAAKA,MAAM;oBACrB,oEAAoE;oBACpE,qEAAqE;oBACrE,+BAA+B;oBAC/B,MAAMiB,OAAO,IAAIP;oBACjB,KAAK,MAAMQ,KAAKjB,QAAS;wBACvBI,YAAYc,gBAAgB,CAACD,GAAGD,MAAMf,WAAW;oBACnD;oBACA,OAAO;2BAAII;qBAAW;gBACxB;gBACA,KAAK,MAAMc,OAAOP,IAAIQ,eAAe,CAAEV,MAAMW,IAAI,CAACF;YACpD;QACF;QACAhB,MAAM;IACR;AACF"}
|
|
@@ -20,7 +20,7 @@ import { sanityAppId } from './plugins/plugin-sanity-app-id.js';
|
|
|
20
20
|
return relativeConfigLocation;
|
|
21
21
|
}
|
|
22
22
|
/** Build the Vite plugins for a workbench app's module-federation remote. */ export async function workbenchVitePlugins(options) {
|
|
23
|
-
const { appId, cwd, entries, exposes, isApp } = options;
|
|
23
|
+
const { appId, cwd, entries, exposes, isApp, isBlueprints } = options;
|
|
24
24
|
const pkgJson = await readPackageJson(path.join(cwd, 'package.json'));
|
|
25
25
|
const federationPlugin = federation({
|
|
26
26
|
...isApp ? {
|
|
@@ -35,6 +35,7 @@ import { sanityAppId } from './plugins/plugin-sanity-app-id.js';
|
|
|
35
35
|
studioConfigPath: requireStudioConfigPath(entries.relativeConfigLocation)
|
|
36
36
|
},
|
|
37
37
|
exposes,
|
|
38
|
+
isBlueprints,
|
|
38
39
|
pkgJson,
|
|
39
40
|
workDir: cwd
|
|
40
41
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/actions/build/vite/workbench-vite-plugins.ts"],"sourcesContent":["// The build-facing entry of the workbench federation stack: turn a workbench\n// app's build inputs into the Vite plugins that produce its module-federation\n// remote. `@sanity/cli-build`'s `getViteConfig` calls this instead of\n// assembling `federation`'s options itself, so the discriminated app-vs-studio\n// option shape, the no-app-view rule, and the studio-config requirement all\n// live here next to `federation` — the build package just hands over its inputs.\n\nimport path from 'node:path'\n\nimport {readPackageJson} from '@sanity/cli-core'\nimport {type PluginOption} from 'vite'\n\nimport {type WorkbenchExposes} from '../../../resolveWorkbenchApp.js'\nimport {federation} from './plugin.js'\nimport {sanityAppId} from './plugins/plugin-sanity-app-id.js'\n\ninterface WorkbenchViteOptions {\n /** Project root — read for the federation remote name, and the plugin workDir. */\n cwd: string\n /**\n * Build entry paths relative to the federation runtime dir. `relativeEntry` is\n * the app's `entry` (null for a dock-only app with no app view);\n * `relativeConfigLocation` is the studio's `sanity.config.*` (null when absent).\n */\n entries: {relativeConfigLocation: string | null; relativeEntry: string | null}\n\n /** The app's bus identity, stamped into its modules for `@sanity/runtime`. */\n appId?: string\n\n exposes?: WorkbenchExposes\n /** App (vs studio) build — selects the discriminated federation option shape. */\n isApp?: boolean\n}\n\n/**\n * A workbench studio renders from its `sanity.config.*`, so the build needs one.\n * An explicit `applicationType: 'studio'` wins over detection, so a studio can\n * reach here with no config file — fail with the fix rather than a cryptic build\n * error downstream.\n */\nfunction requireStudioConfigPath(relativeConfigLocation: string | null): string {\n if (relativeConfigLocation === null) {\n throw new Error(\n 'Workbench studios need a sanity.config.js or sanity.config.ts file. ' +\n \"Add one, or remove `applicationType: 'studio'` from `defineApplication` \" +\n 'to let the CLI infer the application type.',\n )\n }\n return relativeConfigLocation\n}\n\n/** Build the Vite plugins for a workbench app's module-federation remote. */\nexport async function workbenchVitePlugins(options: WorkbenchViteOptions): Promise<PluginOption> {\n const {appId, cwd, entries, exposes, isApp} = options\n const pkgJson = await readPackageJson(path.join(cwd, 'package.json'))\n\n const federationPlugin = federation({\n ...(isApp\n ? {\n // `null` relativeEntry (a branded app with no `entry`) → omit `appEntry`,\n // so the remote exposes no `./App`, only its views.\n ...(entries.relativeEntry ? {appEntry: entries.relativeEntry} : {}),\n isApp: true as const,\n }\n : {\n isApp: false as const,\n studioConfigPath: requireStudioConfigPath(entries.relativeConfigLocation),\n }),\n exposes,\n pkgJson,\n workDir: cwd,\n })\n\n return appId === undefined ? federationPlugin : [federationPlugin, sanityAppId(appId)]\n}\n"],"names":["path","readPackageJson","federation","sanityAppId","requireStudioConfigPath","relativeConfigLocation","Error","workbenchVitePlugins","options","appId","cwd","entries","exposes","isApp","pkgJson","join","federationPlugin","relativeEntry","appEntry","studioConfigPath","workDir","undefined"],"mappings":"AAAA,6EAA6E;AAC7E,8EAA8E;AAC9E,sEAAsE;AACtE,+EAA+E;AAC/E,4EAA4E;AAC5E,iFAAiF;AAEjF,OAAOA,UAAU,YAAW;AAE5B,SAAQC,eAAe,QAAO,mBAAkB;AAIhD,SAAQC,UAAU,QAAO,cAAa;AACtC,SAAQC,WAAW,QAAO,oCAAmC;
|
|
1
|
+
{"version":3,"sources":["../../../../src/actions/build/vite/workbench-vite-plugins.ts"],"sourcesContent":["// The build-facing entry of the workbench federation stack: turn a workbench\n// app's build inputs into the Vite plugins that produce its module-federation\n// remote. `@sanity/cli-build`'s `getViteConfig` calls this instead of\n// assembling `federation`'s options itself, so the discriminated app-vs-studio\n// option shape, the no-app-view rule, and the studio-config requirement all\n// live here next to `federation` — the build package just hands over its inputs.\n\nimport path from 'node:path'\n\nimport {readPackageJson} from '@sanity/cli-core'\nimport {type PluginOption} from 'vite'\n\nimport {type WorkbenchExposes} from '../../../resolveWorkbenchApp.js'\nimport {federation} from './plugin.js'\nimport {sanityAppId} from './plugins/plugin-sanity-app-id.js'\n\ninterface WorkbenchViteOptions {\n /** Project root — read for the federation remote name, and the plugin workDir. */\n cwd: string\n /**\n * Build entry paths relative to the federation runtime dir. `relativeEntry` is\n * the app's `entry` (null for a dock-only app with no app view);\n * `relativeConfigLocation` is the studio's `sanity.config.*` (null when absent).\n */\n entries: {relativeConfigLocation: string | null; relativeEntry: string | null}\n\n /** The app's bus identity, stamped into its modules for `@sanity/runtime`. */\n appId?: string\n\n exposes?: WorkbenchExposes\n /** App (vs studio) build — selects the discriminated federation option shape. */\n isApp?: boolean\n /** Blueprints build (via `@sanity/runtime-cli`) — emit the resource-bindings module. */\n isBlueprints?: boolean\n}\n\n/**\n * A workbench studio renders from its `sanity.config.*`, so the build needs one.\n * An explicit `applicationType: 'studio'` wins over detection, so a studio can\n * reach here with no config file — fail with the fix rather than a cryptic build\n * error downstream.\n */\nfunction requireStudioConfigPath(relativeConfigLocation: string | null): string {\n if (relativeConfigLocation === null) {\n throw new Error(\n 'Workbench studios need a sanity.config.js or sanity.config.ts file. ' +\n \"Add one, or remove `applicationType: 'studio'` from `defineApplication` \" +\n 'to let the CLI infer the application type.',\n )\n }\n return relativeConfigLocation\n}\n\n/** Build the Vite plugins for a workbench app's module-federation remote. */\nexport async function workbenchVitePlugins(options: WorkbenchViteOptions): Promise<PluginOption> {\n const {appId, cwd, entries, exposes, isApp, isBlueprints} = options\n const pkgJson = await readPackageJson(path.join(cwd, 'package.json'))\n\n const federationPlugin = federation({\n ...(isApp\n ? {\n // `null` relativeEntry (a branded app with no `entry`) → omit `appEntry`,\n // so the remote exposes no `./App`, only its views.\n ...(entries.relativeEntry ? {appEntry: entries.relativeEntry} : {}),\n isApp: true as const,\n }\n : {\n isApp: false as const,\n studioConfigPath: requireStudioConfigPath(entries.relativeConfigLocation),\n }),\n exposes,\n isBlueprints,\n pkgJson,\n workDir: cwd,\n })\n\n return appId === undefined ? federationPlugin : [federationPlugin, sanityAppId(appId)]\n}\n"],"names":["path","readPackageJson","federation","sanityAppId","requireStudioConfigPath","relativeConfigLocation","Error","workbenchVitePlugins","options","appId","cwd","entries","exposes","isApp","isBlueprints","pkgJson","join","federationPlugin","relativeEntry","appEntry","studioConfigPath","workDir","undefined"],"mappings":"AAAA,6EAA6E;AAC7E,8EAA8E;AAC9E,sEAAsE;AACtE,+EAA+E;AAC/E,4EAA4E;AAC5E,iFAAiF;AAEjF,OAAOA,UAAU,YAAW;AAE5B,SAAQC,eAAe,QAAO,mBAAkB;AAIhD,SAAQC,UAAU,QAAO,cAAa;AACtC,SAAQC,WAAW,QAAO,oCAAmC;AAsB7D;;;;;CAKC,GACD,SAASC,wBAAwBC,sBAAqC;IACpE,IAAIA,2BAA2B,MAAM;QACnC,MAAM,IAAIC,MACR,yEACE,6EACA;IAEN;IACA,OAAOD;AACT;AAEA,2EAA2E,GAC3E,OAAO,eAAeE,qBAAqBC,OAA6B;IACtE,MAAM,EAACC,KAAK,EAAEC,GAAG,EAAEC,OAAO,EAAEC,OAAO,EAAEC,KAAK,EAAEC,YAAY,EAAC,GAAGN;IAC5D,MAAMO,UAAU,MAAMd,gBAAgBD,KAAKgB,IAAI,CAACN,KAAK;IAErD,MAAMO,mBAAmBf,WAAW;QAClC,GAAIW,QACA;YACE,0EAA0E;YAC1E,oDAAoD;YACpD,GAAIF,QAAQO,aAAa,GAAG;gBAACC,UAAUR,QAAQO,aAAa;YAAA,IAAI,CAAC,CAAC;YAClEL,OAAO;QACT,IACA;YACEA,OAAO;YACPO,kBAAkBhB,wBAAwBO,QAAQN,sBAAsB;QAC1E,CAAC;QACLO;QACAE;QACAC;QACAM,SAASX;IACX;IAEA,OAAOD,UAAUa,YAAYL,mBAAmB;QAACA;QAAkBd,YAAYM;KAAO;AACxF"}
|
|
@@ -2,7 +2,7 @@ import { basename, dirname } from 'node:path';
|
|
|
2
2
|
import { styleText } from 'node:util';
|
|
3
3
|
import { createGzip } from 'node:zlib';
|
|
4
4
|
import { subdebug } from '@sanity/cli-core';
|
|
5
|
-
import {
|
|
5
|
+
import { c as createTar } from 'tar';
|
|
6
6
|
import { getWorkbenchUrl } from '../../services/applications.js';
|
|
7
7
|
import { createConfig, resolveSingletonInstallationId } from '../../services/installations.js';
|
|
8
8
|
import { summarizeGroup } from './summarizeInterfaces.js';
|
|
@@ -46,11 +46,11 @@ const debug = subdebug('deploy');
|
|
|
46
46
|
* @internal
|
|
47
47
|
*/ export async function deployConfig(options) {
|
|
48
48
|
const { appType, installationId, organizationId, output, sourceDir, version } = options;
|
|
49
|
-
const tarball =
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
const tarball = createTar({
|
|
50
|
+
cwd: dirname(sourceDir)
|
|
51
|
+
}, [
|
|
52
|
+
basename(sourceDir)
|
|
53
|
+
]).pipe(createGzip());
|
|
54
54
|
await createConfig(installationId, {
|
|
55
55
|
tarball,
|
|
56
56
|
version
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/deploy/deployConfig.ts"],"sourcesContent":["import {basename, dirname} from 'node:path'\nimport {styleText} from 'node:util'\nimport {createGzip} from 'node:zlib'\n\nimport {type Output, subdebug} from '@sanity/cli-core'\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/deploy/deployConfig.ts"],"sourcesContent":["import {basename, dirname} from 'node:path'\nimport {styleText} from 'node:util'\nimport {createGzip} from 'node:zlib'\n\nimport {type Output, subdebug} from '@sanity/cli-core'\nimport {c as createTar} from 'tar'\n\nimport {getWorkbenchUrl} from '../../services/applications.js'\nimport {createConfig, resolveSingletonInstallationId} from '../../services/installations.js'\nimport {summarizeGroup} from './summarizeInterfaces.js'\n\nconst debug = subdebug('deploy')\n\n/**\n * The org's active installation for an app type, or `undefined` when none is\n * installed. Read-only, so `--dry-run` can report deployability.\n * @internal\n */\nexport async function resolveInstallationId(options: {\n appType: string\n organizationId: string\n}): Promise<string | undefined> {\n switch (options.appType) {\n case 'media-library': {\n return resolveSingletonInstallationId(options.organizationId, 'media-library')\n }\n default: {\n throw new Error(`Cannot create config for unknown app type: ${options.appType}`)\n }\n }\n}\n\n/**\n * A report heading and item list for a config; a media library's `fields` are\n * one of potentially many shapes.\n * @internal\n */\nexport function summarizeConfig(config: {\n appType: string\n fields: {name: string; src: string; title: string}[]\n}): string {\n switch (config.appType) {\n case 'media-library': {\n return summarizeGroup('Media library fields', config.fields)\n }\n default: {\n throw new Error(`Cannot create config for unknown app type: ${config.appType}`)\n }\n }\n}\n\n/**\n * Upload the built module-federation remote to the installation as its config\n * snapshot. `installationId` is resolved by the caller so `--dry-run` never\n * reaches this mutating step.\n * @internal\n */\nexport async function deployConfig(options: {\n appType: string\n installationId: string\n organizationId: string\n output: Output\n sourceDir: string\n version: string\n}): Promise<void> {\n const {appType, installationId, organizationId, output, sourceDir, version} = options\n const tarball = createTar({cwd: dirname(sourceDir)}, [basename(sourceDir)]).pipe(createGzip())\n await createConfig(installationId, {tarball, version})\n\n debug('Deployed config for app type: %s', appType)\n const url = getWorkbenchUrl(organizationId)\n output.log(`\\n🚀 ${styleText('bold', 'Success!')} Config deployed to ${styleText('cyan', url)}`)\n}\n"],"names":["basename","dirname","styleText","createGzip","subdebug","c","createTar","getWorkbenchUrl","createConfig","resolveSingletonInstallationId","summarizeGroup","debug","resolveInstallationId","options","appType","organizationId","Error","summarizeConfig","config","fields","deployConfig","installationId","output","sourceDir","version","tarball","cwd","pipe","url","log"],"mappings":"AAAA,SAAQA,QAAQ,EAAEC,OAAO,QAAO,YAAW;AAC3C,SAAQC,SAAS,QAAO,YAAW;AACnC,SAAQC,UAAU,QAAO,YAAW;AAEpC,SAAqBC,QAAQ,QAAO,mBAAkB;AACtD,SAAQC,KAAKC,SAAS,QAAO,MAAK;AAElC,SAAQC,eAAe,QAAO,iCAAgC;AAC9D,SAAQC,YAAY,EAAEC,8BAA8B,QAAO,kCAAiC;AAC5F,SAAQC,cAAc,QAAO,2BAA0B;AAEvD,MAAMC,QAAQP,SAAS;AAEvB;;;;CAIC,GACD,OAAO,eAAeQ,sBAAsBC,OAG3C;IACC,OAAQA,QAAQC,OAAO;QACrB,KAAK;YAAiB;gBACpB,OAAOL,+BAA+BI,QAAQE,cAAc,EAAE;YAChE;QACA;YAAS;gBACP,MAAM,IAAIC,MAAM,CAAC,2CAA2C,EAAEH,QAAQC,OAAO,EAAE;YACjF;IACF;AACF;AAEA;;;;CAIC,GACD,OAAO,SAASG,gBAAgBC,MAG/B;IACC,OAAQA,OAAOJ,OAAO;QACpB,KAAK;YAAiB;gBACpB,OAAOJ,eAAe,wBAAwBQ,OAAOC,MAAM;YAC7D;QACA;YAAS;gBACP,MAAM,IAAIH,MAAM,CAAC,2CAA2C,EAAEE,OAAOJ,OAAO,EAAE;YAChF;IACF;AACF;AAEA;;;;;CAKC,GACD,OAAO,eAAeM,aAAaP,OAOlC;IACC,MAAM,EAACC,OAAO,EAAEO,cAAc,EAAEN,cAAc,EAAEO,MAAM,EAAEC,SAAS,EAAEC,OAAO,EAAC,GAAGX;IAC9E,MAAMY,UAAUnB,UAAU;QAACoB,KAAKzB,QAAQsB;IAAU,GAAG;QAACvB,SAASuB;KAAW,EAAEI,IAAI,CAACxB;IACjF,MAAMK,aAAaa,gBAAgB;QAACI;QAASD;IAAO;IAEpDb,MAAM,oCAAoCG;IAC1C,MAAMc,MAAMrB,gBAAgBQ;IAC5BO,OAAOO,GAAG,CAAC,CAAC,KAAK,EAAE3B,UAAU,QAAQ,YAAY,oBAAoB,EAAEA,UAAU,QAAQ0B,MAAM;AACjG"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { basename, dirname } from 'node:path';
|
|
2
2
|
import { createGzip } from 'node:zlib';
|
|
3
|
+
import { getErrorMessage } from '@sanity/cli-core/errors';
|
|
3
4
|
import { spinner } from '@sanity/cli-core/ux';
|
|
4
|
-
import {
|
|
5
|
+
import { c as createTar } from 'tar';
|
|
5
6
|
import { deriveInterfaces } from '../../deriveInterfaces.js';
|
|
6
7
|
import { createApplication, createDeployment, deleteApplication, updateApplication } from '../../services/applications.js';
|
|
7
8
|
function toBrettInterface(iface, version) {
|
|
@@ -92,21 +93,21 @@ function toBrettInterface(iface, version) {
|
|
|
92
93
|
}
|
|
93
94
|
/**
|
|
94
95
|
* Ship a deployment to an already-created (or `deployment.appId`) application,
|
|
95
|
-
* then sync its mutable metadata (`title`, and `icon`/`visibility` when
|
|
96
|
-
* from config. The deploy endpoint ignores these, so a redeploy patches
|
|
97
|
-
* here alongside the new deployment.
|
|
96
|
+
* then sync its mutable metadata (`title`, and `icon`/`slug`/`visibility` when
|
|
97
|
+
* set) from config. The deploy endpoint ignores these, so a redeploy patches
|
|
98
|
+
* them here alongside the new deployment.
|
|
98
99
|
*
|
|
99
100
|
* `onDeployed` fires the instant the deployment is live, before the metadata
|
|
100
101
|
* sync — so a caller can disarm a create-time rollback that must not delete an
|
|
101
102
|
* application once it has an active deployment.
|
|
102
103
|
* @internal
|
|
103
104
|
*/ export async function deployWorkbenchApp(options) {
|
|
104
|
-
const { access, app, applicationId, icon, isApp, isAutoUpdating, label = 'Deploying...', onDeployed, sourceDir, title, version, visibility, workspaces } = options;
|
|
105
|
-
const tarball =
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
105
|
+
const { access, app, applicationId, icon, isApp, isAutoUpdating, label = 'Deploying...', onDeployed, slug, sourceDir, title, version, visibility, workspaces } = options;
|
|
106
|
+
const tarball = createTar({
|
|
107
|
+
cwd: dirname(sourceDir)
|
|
108
|
+
}, [
|
|
109
|
+
basename(sourceDir)
|
|
110
|
+
]).pipe(createGzip());
|
|
110
111
|
const spin = spinner(label).start();
|
|
111
112
|
try {
|
|
112
113
|
await createDeployment({
|
|
@@ -123,20 +124,39 @@ function toBrettInterface(iface, version) {
|
|
|
123
124
|
workspaces
|
|
124
125
|
});
|
|
125
126
|
onDeployed?.();
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
icon
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
127
|
+
try {
|
|
128
|
+
await updateApplication(applicationId, {
|
|
129
|
+
title,
|
|
130
|
+
...icon ? {
|
|
131
|
+
icon
|
|
132
|
+
} : {},
|
|
133
|
+
...slug ? {
|
|
134
|
+
slug
|
|
135
|
+
} : {},
|
|
136
|
+
...visibility ? {
|
|
137
|
+
visibility
|
|
138
|
+
} : {}
|
|
139
|
+
});
|
|
140
|
+
} catch (error) {
|
|
141
|
+
throw toSlugRejection(error, slug);
|
|
142
|
+
}
|
|
135
143
|
spin.succeed();
|
|
136
144
|
} catch (error) {
|
|
137
145
|
spin.clear();
|
|
138
146
|
throw error;
|
|
139
147
|
}
|
|
140
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* The slug is the one synced field the server validates — it must be free
|
|
151
|
+
* within the organization, and a singleton app only accepts its reserved
|
|
152
|
+
* identifiers — so a 4xx on the metadata patch is a rejected rename. Name the
|
|
153
|
+
* slug and the server's reason; anything else propagates untouched.
|
|
154
|
+
*/ function toSlugRejection(error, slug) {
|
|
155
|
+
const statusCode = error?.statusCode;
|
|
156
|
+
if (!slug || statusCode === undefined || statusCode < 400 || statusCode >= 500) return error;
|
|
157
|
+
return new Error(`Slug "${slug}" was rejected: ${getErrorMessage(error)}. The deployment is live at the application's previous slug — change \`app.slug\` in sanity.cli.ts and deploy again.`, {
|
|
158
|
+
cause: error
|
|
159
|
+
});
|
|
160
|
+
}
|
|
141
161
|
|
|
142
162
|
//# sourceMappingURL=deployWorkbenchApp.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/deploy/deployWorkbenchApp.ts"],"sourcesContent":["import {basename, dirname} from 'node:path'\nimport {createGzip} from 'node:zlib'\n\nimport {type AppVisibility, type CliConfig} from '@sanity/cli-core'\nimport {spinner} from '@sanity/cli-core/ux'\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/deploy/deployWorkbenchApp.ts"],"sourcesContent":["import {basename, dirname} from 'node:path'\nimport {createGzip} from 'node:zlib'\n\nimport {type AppVisibility, type CliConfig} from '@sanity/cli-core'\nimport {getErrorMessage} from '@sanity/cli-core/errors'\nimport {spinner} from '@sanity/cli-core/ux'\nimport {c as createTar} from 'tar'\n\nimport {type DerivedInterface, deriveInterfaces} from '../../deriveInterfaces.js'\nimport {\n type Application,\n type BrettAccess,\n type BrettInterface,\n type BrettWorkspace,\n createApplication,\n createDeployment,\n deleteApplication,\n updateApplication,\n} from '../../services/applications.js'\n\n/**\n * `rollback` undoes the creation, so a later failure leaves no record stranded at the slug.\n * @internal\n */\nexport interface CreatedApplication {\n application: Application\n rollback: () => Promise<void>\n}\n\nfunction toBrettInterface(iface: DerivedInterface, version: string): BrettInterface {\n const {id: _id, src: _src, ...declaration} = iface\n if ('type' in declaration) return {...declaration, version}\n\n switch (declaration.surface) {\n case 'asset_source': {\n const {surface, ...view} = declaration\n return {...view, type: surface, version}\n }\n case 'panel': {\n const {surface, ...view} = declaration\n return {...view, type: surface, version}\n }\n case 'tile': {\n const {surface, ...view} = declaration\n return {...view, type: surface, version}\n }\n case 'window': {\n const {surface, ...view} = declaration\n return {...view, type: 'app', version}\n }\n }\n}\n\n/**\n * Create a coreApp record (no deployment), so the CLI can build with its id\n * before shipping the first deployment. First deploy only.\n * @internal\n */\nexport async function createCoreApp(options: {\n isSingleton?: boolean\n name?: string\n organizationId: string\n slug: string\n title: string\n visibility?: AppVisibility\n}): Promise<CreatedApplication> {\n const spin = spinner('Creating application...').start()\n try {\n const application = await createApplication({...options, type: 'coreApp'})\n spin.succeed()\n return {application, rollback: () => deleteApplication(application.id)}\n } catch (error) {\n spin.fail()\n throw error\n }\n}\n\n/**\n * Create a studio record (no deployment).\n * @internal\n */\nexport async function createStudio(options: {\n name?: string\n organizationId: string\n projectId: string | undefined\n slug: string\n title: string\n visibility?: AppVisibility\n}): Promise<CreatedApplication> {\n const spin = spinner('Creating studio...').start()\n try {\n const application = await createApplication({...options, type: 'studio'})\n spin.succeed()\n return {application, rollback: () => deleteApplication(application.id)}\n } catch (error) {\n spin.fail()\n throw error\n }\n}\n\n/**\n * Ship a deployment to an already-created (or `deployment.appId`) application,\n * then sync its mutable metadata (`title`, and `icon`/`slug`/`visibility` when\n * set) from config. The deploy endpoint ignores these, so a redeploy patches\n * them here alongside the new deployment.\n *\n * `onDeployed` fires the instant the deployment is live, before the metadata\n * sync — so a caller can disarm a create-time rollback that must not delete an\n * application once it has an active deployment.\n * @internal\n */\nexport async function deployWorkbenchApp(options: {\n access?: readonly BrettAccess[]\n app: CliConfig['app']\n applicationId: string\n icon?: string\n isApp: boolean\n isAutoUpdating: boolean\n label?: string\n onDeployed?: () => void\n /**\n * The address from config. Only `createApplication` used to send it, so an\n * app with a `deployment.appId` kept whatever slug it was created at.\n */\n slug?: string\n sourceDir: string\n title: string\n version: string\n visibility?: AppVisibility\n workspaces?: readonly BrettWorkspace[]\n}): Promise<void> {\n const {\n access,\n app,\n applicationId,\n icon,\n isApp,\n isAutoUpdating,\n label = 'Deploying...',\n onDeployed,\n slug,\n sourceDir,\n title,\n version,\n visibility,\n workspaces,\n } = options\n const tarball = createTar({cwd: dirname(sourceDir)}, [basename(sourceDir)]).pipe(createGzip())\n\n const spin = spinner(label).start()\n try {\n await createDeployment({\n access,\n applicationId,\n // Brett assigns the id and resolves modules by `moduleId`, so neither travels.\n interfaces: deriveInterfaces(app, {appTitle: title, isApp}).map((iface) =>\n toBrettInterface(iface, version),\n ),\n isAutoUpdating,\n tarball,\n version,\n workspaces,\n })\n onDeployed?.()\n try {\n await updateApplication(applicationId, {\n title,\n ...(icon ? {icon} : {}),\n ...(slug ? {slug} : {}),\n ...(visibility ? {visibility} : {}),\n })\n } catch (error) {\n throw toSlugRejection(error, slug)\n }\n spin.succeed()\n } catch (error) {\n spin.clear()\n throw error\n }\n}\n\n/**\n * The slug is the one synced field the server validates — it must be free\n * within the organization, and a singleton app only accepts its reserved\n * identifiers — so a 4xx on the metadata patch is a rejected rename. Name the\n * slug and the server's reason; anything else propagates untouched.\n */\nfunction toSlugRejection(error: unknown, slug: string | undefined): unknown {\n const statusCode = (error as {statusCode?: number})?.statusCode\n if (!slug || statusCode === undefined || statusCode < 400 || statusCode >= 500) return error\n return new Error(\n `Slug \"${slug}\" was rejected: ${getErrorMessage(error)}. The deployment is live at the application's previous slug — change \\`app.slug\\` in sanity.cli.ts and deploy again.`,\n {cause: error},\n )\n}\n"],"names":["basename","dirname","createGzip","getErrorMessage","spinner","c","createTar","deriveInterfaces","createApplication","createDeployment","deleteApplication","updateApplication","toBrettInterface","iface","version","id","_id","src","_src","declaration","surface","view","type","createCoreApp","options","spin","start","application","succeed","rollback","error","fail","createStudio","deployWorkbenchApp","access","app","applicationId","icon","isApp","isAutoUpdating","label","onDeployed","slug","sourceDir","title","visibility","workspaces","tarball","cwd","pipe","interfaces","appTitle","map","toSlugRejection","clear","statusCode","undefined","Error","cause"],"mappings":"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"}
|
|
@@ -17,14 +17,16 @@ const noop = async ()=>{};
|
|
|
17
17
|
// with no interfaces (the media library). A server with both lands in both channels.
|
|
18
18
|
const isLocalApp = (server)=>!isConfigOnlyServer(server);
|
|
19
19
|
const toApplicationsPayload = (servers)=>({
|
|
20
|
-
applications: servers.filter((server)=>isLocalApp(server)).map(({ host, id, interfaces, manifest, port, projectId, type })=>({
|
|
20
|
+
applications: servers.filter((server)=>isLocalApp(server)).map(({ host, id, interfaces, manifest, name, port, projectId, reference, type })=>({
|
|
21
21
|
host,
|
|
22
22
|
id,
|
|
23
23
|
// Views cross to the remote keyed on `type`, never the internal `surface`.
|
|
24
24
|
interfaces: interfaces?.map((iface)=>toWireInterface(iface)),
|
|
25
25
|
manifest,
|
|
26
|
+
name,
|
|
26
27
|
port,
|
|
27
28
|
projectId,
|
|
29
|
+
reference,
|
|
28
30
|
type
|
|
29
31
|
})),
|
|
30
32
|
configs: servers.flatMap(({ configs, host, port })=>// The registry stores the config flat; the workbench wire shape nests the
|
|
@@ -1 +1 @@
|
|
|
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, isWorkbenchConfig} 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 {toWireInterface} from './toWireInterface.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 // Views cross to the remote keyed on `type`, never the internal `surface`.\n interfaces: interfaces?.map((iface) => toWireInterface(iface)),\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 by a `defineApplication` app or a config-only\n // `unstable_defineMediaLibrary` config — the latter still needs the shell to\n // render it.\n if (!isWorkbenchApp(cliConfig?.app) && !isWorkbenchConfig(cliConfig?.app)) {\n devDebug('Not a workbench app or config, 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 `defineApplication`, 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 defineApplication() 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","isWorkbenchConfig","createExposesTracker","acquireWorkbenchLock","getRegisteredServers","isConfigOnlyServer","readWorkbenchLock","watchRegistry","toWireInterface","writeWorkbenchRuntime","devDebug","renderDashboardEntry","renderDashboardPath","URL","url","noop","isLocalApp","server","toApplicationsPayload","servers","applications","filter","map","host","id","interfaces","manifest","port","projectId","type","iface","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":"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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, isWorkbenchConfig} 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 {toWireInterface} from './toWireInterface.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, name, port, projectId, reference, type}) => ({\n host,\n id,\n // Views cross to the remote keyed on `type`, never the internal `surface`.\n interfaces: interfaces?.map((iface) => toWireInterface(iface)),\n manifest,\n name,\n port,\n projectId,\n reference,\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 by a `defineApplication` app or a config-only\n // `unstable_defineMediaLibrary` config — the latter still needs the shell to\n // render it.\n if (!isWorkbenchApp(cliConfig?.app) && !isWorkbenchConfig(cliConfig?.app)) {\n devDebug('Not a workbench app or config, 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 `defineApplication`, 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 defineApplication() 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","isWorkbenchConfig","createExposesTracker","acquireWorkbenchLock","getRegisteredServers","isConfigOnlyServer","readWorkbenchLock","watchRegistry","toWireInterface","writeWorkbenchRuntime","devDebug","renderDashboardEntry","renderDashboardPath","URL","url","noop","isLocalApp","server","toApplicationsPayload","servers","applications","filter","map","host","id","interfaces","manifest","name","port","projectId","reference","type","iface","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"],"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,EAAEC,iBAAiB,QAAO,qBAAoB;AACpE,SAAQC,oBAAoB,QAAO,oBAAmB;AACtD,SACEC,oBAAoB,EAEpBC,oBAAoB,EACpBC,kBAAkB,EAClBC,iBAAiB,EACjBC,aAAa,QACR,gBAAe;AACtB,SAAQC,eAAe,QAAO,uBAAsB;AACpD,SAAQC,qBAAqB,QAAO,6BAA4B;AAEhE,MAAMC,WAAWf,SAAS;AAC1B,MAAMgB,uBAAuB;AAC7B,MAAMC,sBAAsBlB,cAC1B,IAAImB,IAAI,sCAAsC,YAAYC,GAAG;AAG/D,MAAMC,OAAO,WAAa;AAE1B,kEAAkE;AAClE,qFAAqF;AACrF,MAAMC,aAAa,CAACC,SAAuC,CAACZ,mBAAmBY;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,IAAI,EAAEC,SAAS,EAAEC,SAAS,EAAEC,IAAI,EAAC,GAAM,CAAA;gBAClFR;gBACAC;gBACA,2EAA2E;gBAC3EC,YAAYA,YAAYH,IAAI,CAACU,QAAUxB,gBAAgBwB;gBACvDN;gBACAC;gBACAC;gBACAC;gBACAC;gBACAC;YACF,CAAA;QACFE,SAASd,QAAQe,OAAO,CAAC,CAAC,EAACD,OAAO,EAAEV,IAAI,EAAEK,IAAI,EAAC,GAI7C,AAHA,0EAA0E;YAC1E,6EAA6E;YAC7E,kCAAkC;YACjCK,CAAAA,WAAW,EAAE,AAAD,EAAGX,GAAG,CAAC,CAAC,EAACa,OAAO,EAAEX,EAAE,EAAEY,UAAU,EAAEC,OAAO,EAAE,GAAGC,QAAO,GAAM,CAAA;oBACtEH;oBACAG;oBACAd;oBACAY;oBACAG,WAAW,CAAC,OAAO,EAAEhB,KAAK,CAAC,EAAEK,MAAM;oBACnCS;gBACF,CAAA;IAEJ,CAAA;AAEA;;;;;CAKC,GACD,SAASG,0BAA0BvB,MAAqB;IACtDA,OAAOwB,EAAE,CAACC,EAAE,CAAC,2CAA2C,CAACC,GAAGC;QAC1DA,OAAOC,IAAI,CACT,uCACA3B,sBAAsBd;IAE1B;IAEA,MAAM0C,aAAa5C;IACnB,MAAM6C,kBAAkBxC,cAAc,CAACY;QACrC,IAAI2B,WAAWE,UAAU,CAAC7B,UAAU;YAClCF,OAAOwB,EAAE,CAACI,IAAI,CAAC;gBAACd,MAAM;YAAa;YACnC;QACF;QACAd,OAAOwB,EAAE,CAACI,IAAI,CAAC,uCAAuC3B,sBAAsBC;IAC9E;IAEA,OAAO,IAAM4B,gBAAgBE,KAAK;AACpC;AAEA;;;;;CAKC,GACD,OAAO,SAASC,gCAAgCC,OAI/C;IACC,MAAM,EAACC,QAAQ,EAAExB,IAAI,EAAEX,MAAM,EAAC,GAAGkC;IAEjC,MAAME,OAAOlD,qBAAqB;QAACoB,MAAM6B,YAAY;QAAaxB;IAAI;IACtE,IAAI,CAACyB,MAAM;QACT,MAAMC,WAAWhD;QACjBI,SACE,+FACA4C,UAAUC,KACVD,UAAU1B;IAEd;IAEA,MAAM4B,eAAehB,0BAA0BvB;IAE/C,OAAO;QACLgC,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,wEAAwE;IACxE,6EAA6E;IAC7E,aAAa;IACb,IAAI,CAACnD,eAAe4D,WAAWO,QAAQ,CAAClE,kBAAkB2D,WAAWO,MAAM;QACzEzD,SAAS;QACT,OAAO;YAACuC,OAAOlC;YAAMqC;YAAUgB,oBAAoB;YAAON;QAAa;IACzE;IAEA,8DAA8D;IAC9D,qEAAqE;IACrE,yEAAyE;IACzE,MAAMO,gBAAgBlE,qBAAqB;QAACoB,MAAM6B,YAAY;QAAaxB,MAAMkC;IAAa;IAC9F,IAAI,CAACO,eAAe;QAClB,MAAMf,WAAWhD;QACjBI,SACE,4DACA4C,UAAUC,KACVD,UAAU1B;QAEZ,OAAO;YACLqB,OAAOlC;YACPqC,UAAUE,UAAU/B,QAAQ6B;YAC5BgB,oBAAoB;YACpBN,eAAeR,UAAU1B,QAAQkC;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,OAAOlC;YAAMqC;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;IAE7ClD,SAAS;IACT,MAAMyE,OAAO,MAAM1E,sBAAsB;QACvC2E,KAAKlB;QACLe;QACAhB;QACAU;IACF;IAEA,MAAMU,aAA2B;QAC/B,0FAA0F;QAC1F1B;QACA2B,YAAY;QACZC,QAAQ;YACNC,oBAAoB5F;YACpB,wDAAwD6F,KAAKC,SAAS,CAACf;QACzE;QACAgB,UAAU;QACV5B,MAAM;QACN6B,cAAc;YACZ,uEAAuE;YACvEC,SAAS;gBAAClF;aAAqB;QACjC;QACA,8EAA8E;QAC9E,+EAA+E;QAC/E,iFAAiF;QACjF,4EAA4E;QAC5E,2EAA2E;QAC3E,2EAA2E;QAC3E,2EAA2E;QAC3EmF,SAAS;YAACjG;eAAiB8E,YAAY;gBAACoB,kCAAkCpB;aAAW,GAAG,EAAE;SAAE;QAC5FqB,SAAS;YACP,gEAAgE;YAChEC,OAAO;gBAAC,CAACtF,qBAAqB,EAAEC;YAAmB;YACnDsF,QAAQ;gBAAC;gBAAS;aAAY;QAChC;QACAf;QACAlE,QAAQ;YACNM,MAAM6B;YACNxB,MAAMkC;YACNqC,YAAY;YACZC,QAAQ;gBACNC,aAAa;oBAAC;iBAAiB;YACjC;QACF;IACF;IAEA3F,SAAS;IACT,MAAMO,SAAS,MAAMnB,aAAauF;IAClC,IAAI;QACF,MAAMpE,OAAOqF,MAAM;IACrB,EAAE,OAAO9B,KAAK;QACZ,MAAMvD,OAAOgC,KAAK;QAClBe,OAAOuC,IAAI,CACT,CAAC,sCAAsC,EAAE/B,eAAegC,QAAQhC,IAAIiC,OAAO,GAAGC,OAAOlC,MAAM;QAE7F,OAAOI;IACT;IAEA,wEAAwE;IACxE,MAAM+B,OAAO1F,OAAO2F,UAAU,EAAEC;IAChC,MAAMpC,aAAa,OAAOkC,SAAS,YAAYA,OAAOA,KAAK/E,IAAI,GAAGkC;IAElE,0EAA0E;IAC1E,+DAA+D;IAC/D,IAAIa,WAAW;QACbmC,MAAMnC,WACHoC,IAAI,CAAC,CAACC,IAAMA,EAAEC,IAAI,EAAEC,UACpBC,KAAK,CAAC,KAAO;QAChBzG,SAAS,kCAAkCiE;IAC7C;IAEA,MAAMnB,eAAehB,0BAA0BvB;IAE/C,OAAO;QACLwD;QACAxB,OAAO;YACLO;YACA,MAAMvC,OAAOgC,KAAK;QACpB;IACF;AACF;AAEA,qEAAqE;AACrE,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,kBAAkBrH,EAAEe,GAAG,CAAC;IAACuG,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,iBAAgB3G,MAAM;YACpBA,OAAO4G,WAAW,CAACC,GAAG,CAAC,CAACC,KAAKC,KAAKC;gBAChC,MAAMC,WAAW,AAACH,CAAAA,IAAIjH,GAAG,IAAI,GAAE,EAAGqH,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;QACAtG,MAAM;IACR;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/dev/toWireInterface.ts"],"sourcesContent":["import {type DevServerInterface} from './deriveConfigs.js'\n\n// The registry stores view interfaces discriminated on `surface` (the author\n// API's word), but the workbench remote — like the deployed application record\n// (see viewDeployment.ts) — keys every interface on `type`. This module is the\n// single, strictly-typed boundary that converts the internal `surface` shape to\n// the wire `type` shape for local dev, so the two can never drift again: the\n// return type carries no `surface`, so a raw registry interface cannot be sent\n// to the remote without passing through here.\n\n/** A registry view interface — the members discriminated on `surface`. */\ntype ViewInterface = Extract<DevServerInterface, {surface: unknown}>\n/** A registry worker interface — already keyed on `type`. */\ntype WorkerInterface = Extract<DevServerInterface, {type: unknown}>\n\n/**\n * An interface as the workbench remote consumes it: every member keyed on\n * `type`. Views have their `surface` renamed to `type`; workers pass through.\n * @internal\n */\nexport type WorkbenchWireInterface =\n | (Omit<ViewInterface, 'surface'> & {type: ViewInterface['surface']})\n | WorkerInterface\n\n/**\n * Convert a registry interface to the remote's wire shape. Views rename\n * `surface` → `type`; workers (already `type`) pass through untouched. The only\n * surface→type conversion for local dev — mirrors the deploy boundary.\n * @internal\n */\nexport function toWireInterface(iface: DevServerInterface): WorkbenchWireInterface {\n if ('surface' in iface) {\n const {surface, ...rest} = iface\n return {...rest, type: surface}\n }\n return iface\n}\n"],"names":["toWireInterface","iface","surface","rest","type"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/dev/toWireInterface.ts"],"sourcesContent":["import {type DevServerInterface} from './deriveConfigs.js'\n\n// The registry stores view interfaces discriminated on `surface` (the author\n// API's word), but the workbench remote — like the deployed application record\n// (see viewDeployment.ts) — keys every interface on `type`. This module is the\n// single, strictly-typed boundary that converts the internal `surface` shape to\n// the wire `type` shape for local dev, so the two can never drift again: the\n// return type carries no `surface`, so a raw registry interface cannot be sent\n// to the remote without passing through here.\n\n/** A registry view interface — the members discriminated on `surface`. */\ntype ViewInterface = Extract<DevServerInterface, {surface: unknown}>\n/** A registry worker interface — already keyed on `type`. */\ntype WorkerInterface = Extract<DevServerInterface, {type: unknown}>\n\n/**\n * An interface as the workbench remote consumes it: every member keyed on\n * `type`. Views have their `surface` renamed to `type`; workers pass through.\n * @internal\n */\nexport type WorkbenchWireInterface =\n | (Omit<ViewInterface, 'surface'> & {\n type: 'app' | Exclude<ViewInterface['surface'], 'window'>\n })\n | WorkerInterface\n\n/**\n * Convert a registry interface to the remote's wire shape. Views rename\n * `surface` → `type`; workers (already `type`) pass through untouched. The only\n * surface→type conversion for local dev — mirrors the deploy boundary.\n * @internal\n */\nexport function toWireInterface(iface: DevServerInterface): WorkbenchWireInterface {\n if ('surface' in iface) {\n const {surface, ...rest} = iface\n return {...rest, type: surface === 'window' ? 'app' : surface}\n }\n return iface\n}\n"],"names":["toWireInterface","iface","surface","rest","type"],"mappings":"AA0BA;;;;;CAKC,GACD,OAAO,SAASA,gBAAgBC,KAAyB;IACvD,IAAI,aAAaA,OAAO;QACtB,MAAM,EAACC,OAAO,EAAE,GAAGC,MAAK,GAAGF;QAC3B,OAAO;YAAC,GAAGE,IAAI;YAAEC,MAAMF,YAAY,WAAW,QAAQA;QAAO;IAC/D;IACA,OAAOD;AACT"}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
// `sanity.cli.ts` templates for workbench (`
|
|
2
|
-
// consumed by the CLI's `init` scaffolding. The branded `
|
|
1
|
+
// `sanity.cli.ts` templates for workbench (`defineApplication`) projects,
|
|
2
|
+
// consumed by the CLI's `init` scaffolding. The branded `defineApplication`
|
|
3
3
|
// result is the sole workbench (module-federation) opt-in, so its config shape
|
|
4
4
|
// is workbench's to own; the CLI keeps the non-workbench templates and the
|
|
5
5
|
// `%placeholder%` substitution. `%slug%`/`%title%`/etc. are filled in by the
|
|
6
6
|
// CLI's template processor.
|
|
7
7
|
/** App scaffold — `entry` auto-declares the navigable app view. */ export const workbenchAppConfigTemplate = `
|
|
8
|
-
import {
|
|
8
|
+
import {defineApplication, defineCliConfig} from 'sanity/cli'
|
|
9
9
|
|
|
10
10
|
export default defineCliConfig({
|
|
11
|
-
app:
|
|
11
|
+
app: defineApplication({
|
|
12
12
|
title: '%title%',
|
|
13
13
|
slug: '%slug%',
|
|
14
14
|
organizationId: '%organizationId%',
|
|
@@ -20,14 +20,14 @@ export default defineCliConfig({
|
|
|
20
20
|
* Studio scaffold — brands with slug/title only, no `entry` (studio app views
|
|
21
21
|
* aren't implemented yet).
|
|
22
22
|
*/ export const workbenchStudioConfigTemplate = `
|
|
23
|
-
import {
|
|
23
|
+
import {defineApplication, defineCliConfig} from 'sanity/cli'
|
|
24
24
|
|
|
25
25
|
export default defineCliConfig({
|
|
26
26
|
api: {
|
|
27
27
|
projectId: '%projectId%',
|
|
28
28
|
dataset: '%dataset%'
|
|
29
29
|
},
|
|
30
|
-
app:
|
|
30
|
+
app: defineApplication({
|
|
31
31
|
title: '%title%',
|
|
32
32
|
slug: '%slug%',
|
|
33
33
|
organizationId: '%organizationId%',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/init/cliConfig.ts"],"sourcesContent":["// `sanity.cli.ts` templates for workbench (`
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/init/cliConfig.ts"],"sourcesContent":["// `sanity.cli.ts` templates for workbench (`defineApplication`) projects,\n// consumed by the CLI's `init` scaffolding. The branded `defineApplication`\n// result is the sole workbench (module-federation) opt-in, so its config shape\n// is workbench's to own; the CLI keeps the non-workbench templates and the\n// `%placeholder%` substitution. `%slug%`/`%title%`/etc. are filled in by the\n// CLI's template processor.\n\n/** App scaffold — `entry` auto-declares the navigable app view. */\nexport const workbenchAppConfigTemplate = `\nimport {defineApplication, defineCliConfig} from 'sanity/cli'\n\nexport default defineCliConfig({\n app: defineApplication({\n title: '%title%',\n slug: '%slug%',\n organizationId: '%organizationId%',\n entry: '%entry%',\n }),\n})\n`\n\n/**\n * Studio scaffold — brands with slug/title only, no `entry` (studio app views\n * aren't implemented yet).\n */\nexport const workbenchStudioConfigTemplate = `\nimport {defineApplication, defineCliConfig} from 'sanity/cli'\n\nexport default defineCliConfig({\n api: {\n projectId: '%projectId%',\n dataset: '%dataset%'\n },\n app: defineApplication({\n title: '%title%',\n slug: '%slug%',\n organizationId: '%organizationId%',\n }),\n deployment: {\n /**\n * Enable auto-updates for studios.\n * Learn more at https://www.sanity.io/docs/studio/latest-version-of-sanity#k47faf43faf56\n */\n autoUpdates: __BOOL__autoUpdates__,\n },\n})\n`\n"],"names":["workbenchAppConfigTemplate","workbenchStudioConfigTemplate"],"mappings":"AAAA,0EAA0E;AAC1E,4EAA4E;AAC5E,+EAA+E;AAC/E,2EAA2E;AAC3E,6EAA6E;AAC7E,4BAA4B;AAE5B,iEAAiE,GACjE,OAAO,MAAMA,6BAA6B,CAAC;;;;;;;;;;;AAW3C,CAAC,CAAA;AAED;;;CAGC,GACD,OAAO,MAAMC,gCAAgC,CAAC;;;;;;;;;;;;;;;;;;;;;AAqB9C,CAAC,CAAA"}
|
package/dist/contract.js
CHANGED
|
@@ -30,9 +30,9 @@ import { z } from 'zod/mini';
|
|
|
30
30
|
'banner'
|
|
31
31
|
]);
|
|
32
32
|
const DockGroupSchema = z.enum([
|
|
33
|
-
'
|
|
34
|
-
'
|
|
35
|
-
'
|
|
33
|
+
'system',
|
|
34
|
+
'applications',
|
|
35
|
+
'user'
|
|
36
36
|
]);
|
|
37
37
|
/** @internal */ export const DockSchema = z.object({
|
|
38
38
|
group: z.optional(DockGroupSchema),
|