@sanity/workbench-cli 2.2.4 → 2.4.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/build.d.ts +54 -2
- package/dist/_exports/build.js +1 -0
- package/dist/_exports/build.js.map +1 -1
- package/dist/_exports/{defineApp-Dmrf4GjS.d.ts → defineApp-KCHd3Vab.d.ts} +29 -29
- package/dist/_exports/deploy.d.ts +3 -3
- package/dist/_exports/dev.d.ts +1 -1
- package/dist/_exports/index.d.ts +3 -3
- package/dist/_exports/init.d.ts +2 -2
- package/dist/_exports/preview.d.ts +1 -1
- package/dist/_exports/{registry-HE_dEHFa.d.ts → registry-BCLMbrla.d.ts} +11 -11
- package/dist/_exports/{resolveWorkbenchConfig-0Crh5P9n.d.ts → resolveWorkbenchConfig-1CV651GT.d.ts} +2 -2
- package/dist/_exports/{summarizeInterfaces-CaYUOK3F.d.ts → summarizeInterfaces-DoG6OunM.d.ts} +4 -4
- 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 +10 -7
- 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 +11 -11
- package/dist/actions/deploy/deployWorkbenchApp.js.map +1 -1
- package/dist/actions/deploy/summarizeInterfaces.js.map +1 -1
- package/dist/actions/deploy/viewDeployment.js +2 -2
- package/dist/actions/deploy/viewDeployment.js.map +1 -1
- package/dist/actions/dev/appServerSupervisor.js.map +1 -1
- package/dist/actions/dev/registry.js +1 -1
- package/dist/actions/dev/registry.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 +12 -12
- package/dist/contract.js.map +1 -1
- package/dist/defineView.js.map +1 -1
- package/dist/deriveInterfaces.js +5 -5
- package/dist/deriveInterfaces.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,7 @@
|
|
|
1
1
|
import { basename, dirname } from 'node:path';
|
|
2
2
|
import { createGzip } from 'node:zlib';
|
|
3
3
|
import { spinner } from '@sanity/cli-core/ux';
|
|
4
|
-
import {
|
|
4
|
+
import { c as createTar } from 'tar';
|
|
5
5
|
import { deriveInterfaces } from '../../deriveInterfaces.js';
|
|
6
6
|
import { createApplication, createDeployment, deleteApplication, updateApplication } from '../../services/applications.js';
|
|
7
7
|
function toBrettInterface(iface, version) {
|
|
@@ -11,7 +11,7 @@ function toBrettInterface(iface, version) {
|
|
|
11
11
|
version
|
|
12
12
|
};
|
|
13
13
|
switch(declaration.surface){
|
|
14
|
-
case '
|
|
14
|
+
case 'asset_source':
|
|
15
15
|
{
|
|
16
16
|
const { surface, ...view } = declaration;
|
|
17
17
|
return {
|
|
@@ -20,7 +20,7 @@ function toBrettInterface(iface, version) {
|
|
|
20
20
|
version
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
|
-
case '
|
|
23
|
+
case 'panel':
|
|
24
24
|
{
|
|
25
25
|
const { surface, ...view } = declaration;
|
|
26
26
|
return {
|
|
@@ -29,7 +29,7 @@ function toBrettInterface(iface, version) {
|
|
|
29
29
|
version
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
|
-
case '
|
|
32
|
+
case 'tile':
|
|
33
33
|
{
|
|
34
34
|
const { surface, ...view } = declaration;
|
|
35
35
|
return {
|
|
@@ -38,12 +38,12 @@ function toBrettInterface(iface, version) {
|
|
|
38
38
|
version
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
|
-
case '
|
|
41
|
+
case 'window':
|
|
42
42
|
{
|
|
43
43
|
const { surface, ...view } = declaration;
|
|
44
44
|
return {
|
|
45
45
|
...view,
|
|
46
|
-
type:
|
|
46
|
+
type: 'app',
|
|
47
47
|
version
|
|
48
48
|
};
|
|
49
49
|
}
|
|
@@ -102,11 +102,11 @@ function toBrettInterface(iface, version) {
|
|
|
102
102
|
* @internal
|
|
103
103
|
*/ export async function deployWorkbenchApp(options) {
|
|
104
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 tarball = createTar({
|
|
106
|
+
cwd: dirname(sourceDir)
|
|
107
|
+
}, [
|
|
108
|
+
basename(sourceDir)
|
|
109
|
+
]).pipe(createGzip());
|
|
110
110
|
const spin = spinner(label).start();
|
|
111
111
|
try {
|
|
112
112
|
await createDeployment({
|
|
@@ -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 {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`/`visibility` when set)\n * from config. The deploy endpoint ignores these, so a redeploy patches them\n * 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 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 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 await updateApplication(applicationId, {\n title,\n ...(icon ? {icon} : {}),\n ...(visibility ? {visibility} : {}),\n })\n spin.succeed()\n } catch (error) {\n spin.clear()\n throw error\n }\n}\n"],"names":["basename","dirname","createGzip","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","sourceDir","title","visibility","workspaces","tarball","cwd","pipe","interfaces","appTitle","map","clear"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/deploy/summarizeInterfaces.ts"],"sourcesContent":["import {type ServiceType, type ViewSurface} from '../../contract.js'\nimport {type WorkbenchExposes} from '../../resolveWorkbenchApp.js'\n\ninterface DeployedInterfaceBase {\n name: string\n src: string\n title: string\n}\n\n/** A view as the deploy report and `--json` output surface it. */\nexport interface DeployedView extends DeployedInterfaceBase {\n surface: ViewSurface\n}\n\n/** A web worker as the deploy report and `--json` output surface it. */\nexport interface DeployedWebWorker extends DeployedInterfaceBase {\n type: ServiceType\n}\n\nexport type DeployedInterface = DeployedView | DeployedWebWorker\n\nconst label = (item: {name: string; title: string}) =>\n item.title === item.name ? item.name : `${item.title} (${item.name})`\n\n/**\n * One `Title (name): src` report line per declared entry point.\n * @internal\n */\nexport function summarizeGroup(\n heading: string,\n items: readonly {name: string; src: string; title: string}[],\n): string {\n return `${heading}:\\n${items.map((item) => ` ${label(item)}: ${item.src}`).join('\\n')}`\n}\n\n/**\n * One report line per non-empty group, alongside the records `--json` reports.\n * @internal\n */\nexport function summarizeInterfaces({views, webWorkers}: WorkbenchExposes): {\n lines: string[]\n services: DeployedWebWorker[]\n views: DeployedView[]\n} {\n const deployedViews = (views ?? []).map(
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/deploy/summarizeInterfaces.ts"],"sourcesContent":["import {type ServiceType, type ViewSurface} from '../../contract.js'\nimport {type WorkbenchExposes} from '../../resolveWorkbenchApp.js'\n\ninterface DeployedInterfaceBase {\n name: string\n src: string\n title: string\n}\n\n/** A view as the deploy report and `--json` output surface it. */\nexport interface DeployedView extends DeployedInterfaceBase {\n surface: ViewSurface\n}\n\n/** A web worker as the deploy report and `--json` output surface it. */\nexport interface DeployedWebWorker extends DeployedInterfaceBase {\n type: ServiceType\n}\n\nexport type DeployedInterface = DeployedView | DeployedWebWorker\n\nconst label = (item: {name: string; title: string}) =>\n item.title === item.name ? item.name : `${item.title} (${item.name})`\n\n/**\n * One `Title (name): src` report line per declared entry point.\n * @internal\n */\nexport function summarizeGroup(\n heading: string,\n items: readonly {name: string; src: string; title: string}[],\n): string {\n return `${heading}:\\n${items.map((item) => ` ${label(item)}: ${item.src}`).join('\\n')}`\n}\n\n/**\n * One report line per non-empty group, alongside the records `--json` reports.\n * @internal\n */\nexport function summarizeInterfaces({views, webWorkers}: WorkbenchExposes): {\n lines: string[]\n services: DeployedWebWorker[]\n views: DeployedView[]\n} {\n const deployedViews = (views ?? []).map((view): DeployedView => ({\n name: view.name,\n src: view.src,\n surface: view.surface,\n title: view.title,\n }))\n const deployedServices = (webWorkers ?? []).map((webWorker): DeployedWebWorker => ({\n name: webWorker.name,\n src: webWorker.src,\n title: webWorker.title,\n type: webWorker.type,\n }))\n\n const lines: string[] = []\n if (deployedViews.length > 0) lines.push(summarizeGroup('Views', deployedViews))\n if (deployedServices.length > 0) lines.push(summarizeGroup('Web workers', deployedServices))\n return {lines, services: deployedServices, views: deployedViews}\n}\n"],"names":["label","item","title","name","summarizeGroup","heading","items","map","src","join","summarizeInterfaces","views","webWorkers","deployedViews","view","surface","deployedServices","webWorker","type","lines","length","push","services"],"mappings":"AAqBA,MAAMA,QAAQ,CAACC,OACbA,KAAKC,KAAK,KAAKD,KAAKE,IAAI,GAAGF,KAAKE,IAAI,GAAG,GAAGF,KAAKC,KAAK,CAAC,EAAE,EAAED,KAAKE,IAAI,CAAC,CAAC,CAAC;AAEvE;;;CAGC,GACD,OAAO,SAASC,eACdC,OAAe,EACfC,KAA4D;IAE5D,OAAO,GAAGD,QAAQ,GAAG,EAAEC,MAAMC,GAAG,CAAC,CAACN,OAAS,CAAC,EAAE,EAAED,MAAMC,MAAM,EAAE,EAAEA,KAAKO,GAAG,EAAE,EAAEC,IAAI,CAAC,OAAO;AAC1F;AAEA;;;CAGC,GACD,OAAO,SAASC,oBAAoB,EAACC,KAAK,EAAEC,UAAU,EAAmB;IAKvE,MAAMC,gBAAgB,AAACF,CAAAA,SAAS,EAAE,AAAD,EAAGJ,GAAG,CAAC,CAACO,OAAwB,CAAA;YAC/DX,MAAMW,KAAKX,IAAI;YACfK,KAAKM,KAAKN,GAAG;YACbO,SAASD,KAAKC,OAAO;YACrBb,OAAOY,KAAKZ,KAAK;QACnB,CAAA;IACA,MAAMc,mBAAmB,AAACJ,CAAAA,cAAc,EAAE,AAAD,EAAGL,GAAG,CAAC,CAACU,YAAkC,CAAA;YACjFd,MAAMc,UAAUd,IAAI;YACpBK,KAAKS,UAAUT,GAAG;YAClBN,OAAOe,UAAUf,KAAK;YACtBgB,MAAMD,UAAUC,IAAI;QACtB,CAAA;IAEA,MAAMC,QAAkB,EAAE;IAC1B,IAAIN,cAAcO,MAAM,GAAG,GAAGD,MAAME,IAAI,CAACjB,eAAe,SAASS;IACjE,IAAIG,iBAAiBI,MAAM,GAAG,GAAGD,MAAME,IAAI,CAACjB,eAAe,eAAeY;IAC1E,OAAO;QAACG;QAAOG,UAAUN;QAAkBL,OAAOE;IAAa;AACjE"}
|
|
@@ -3,7 +3,7 @@ const viewDeclarationSchema = z.looseObject({
|
|
|
3
3
|
name: z.string().check(z.regex(/^[a-zA-Z0-9_-]+$/, 'View `name` must match /^[a-zA-Z0-9_-]+$/')),
|
|
4
4
|
src: z.string(),
|
|
5
5
|
surface: z.enum([
|
|
6
|
-
'
|
|
6
|
+
'window',
|
|
7
7
|
'panel',
|
|
8
8
|
'asset_source',
|
|
9
9
|
'tile'
|
|
@@ -13,7 +13,7 @@ const viewDeclarationSchema = z.looseObject({
|
|
|
13
13
|
name: z.string().check(z.regex(/^[a-zA-Z0-9_-]+$/, 'View `name` must match /^[a-zA-Z0-9_-]+$/')),
|
|
14
14
|
src: z.string(),
|
|
15
15
|
type: z.enum([
|
|
16
|
-
'
|
|
16
|
+
'window',
|
|
17
17
|
'panel',
|
|
18
18
|
'asset_source',
|
|
19
19
|
'tile'
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/deploy/viewDeployment.ts"],"sourcesContent":["import {z} from 'zod/mini'\n\nconst viewDeclarationSchema = z.looseObject({\n name: z.string().check(z.regex(/^[a-zA-Z0-9_-]+$/, 'View `name` must match /^[a-zA-Z0-9_-]+$/')),\n src: z.string(),\n surface: z.enum(['
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/deploy/viewDeployment.ts"],"sourcesContent":["import {z} from 'zod/mini'\n\nconst viewDeclarationSchema = z.looseObject({\n name: z.string().check(z.regex(/^[a-zA-Z0-9_-]+$/, 'View `name` must match /^[a-zA-Z0-9_-]+$/')),\n src: z.string(),\n surface: z.enum(['window', 'panel', 'asset_source', 'tile']),\n})\n\n/** A view record as persisted to the application service. */\nconst viewRecordSchema = z.looseObject({\n name: z.string().check(z.regex(/^[a-zA-Z0-9_-]+$/, 'View `name` must match /^[a-zA-Z0-9_-]+$/')),\n src: z.string(),\n type: z.enum(['window', 'panel', 'asset_source', 'tile']),\n})\n\n/**\n * Payload registering an app's views with the application service on deploy.\n *\n * Phase 1 stub: the service that stores views does not exist yet, so the\n * payload is validated and logged only — never sent. Builds the contract the\n * application-service endpoint will accept.\n */\nconst viewDeploymentPayloadSchema = z.object({\n applicationId: z.string(),\n views: z.array(viewRecordSchema),\n})\n\nexport type ViewDeploymentPayload = z.infer<typeof viewDeploymentPayloadSchema>\n\n/**\n * Validates an app's declared views into the application-service payload.\n * Throws (via Zod) when a view declaration is malformed.\n */\nexport function buildViewDeploymentPayload(input: {\n applicationId: string\n views?: ReadonlyArray<Record<string, unknown>>\n}): ViewDeploymentPayload {\n const views = z.array(viewDeclarationSchema).parse(input.views ?? [])\n return viewDeploymentPayloadSchema.parse({\n applicationId: input.applicationId,\n views: views.map(({surface, ...view}) => ({...view, type: surface})),\n })\n}\n"],"names":["z","viewDeclarationSchema","looseObject","name","string","check","regex","src","surface","enum","viewRecordSchema","type","viewDeploymentPayloadSchema","object","applicationId","views","array","buildViewDeploymentPayload","input","parse","map","view"],"mappings":"AAAA,SAAQA,CAAC,QAAO,WAAU;AAE1B,MAAMC,wBAAwBD,EAAEE,WAAW,CAAC;IAC1CC,MAAMH,EAAEI,MAAM,GAAGC,KAAK,CAACL,EAAEM,KAAK,CAAC,oBAAoB;IACnDC,KAAKP,EAAEI,MAAM;IACbI,SAASR,EAAES,IAAI,CAAC;QAAC;QAAU;QAAS;QAAgB;KAAO;AAC7D;AAEA,2DAA2D,GAC3D,MAAMC,mBAAmBV,EAAEE,WAAW,CAAC;IACrCC,MAAMH,EAAEI,MAAM,GAAGC,KAAK,CAACL,EAAEM,KAAK,CAAC,oBAAoB;IACnDC,KAAKP,EAAEI,MAAM;IACbO,MAAMX,EAAES,IAAI,CAAC;QAAC;QAAU;QAAS;QAAgB;KAAO;AAC1D;AAEA;;;;;;CAMC,GACD,MAAMG,8BAA8BZ,EAAEa,MAAM,CAAC;IAC3CC,eAAed,EAAEI,MAAM;IACvBW,OAAOf,EAAEgB,KAAK,CAACN;AACjB;AAIA;;;CAGC,GACD,OAAO,SAASO,2BAA2BC,KAG1C;IACC,MAAMH,QAAQf,EAAEgB,KAAK,CAACf,uBAAuBkB,KAAK,CAACD,MAAMH,KAAK,IAAI,EAAE;IACpE,OAAOH,4BAA4BO,KAAK,CAAC;QACvCL,eAAeI,MAAMJ,aAAa;QAClCC,OAAOA,MAAMK,GAAG,CAAC,CAAC,EAACZ,OAAO,EAAE,GAAGa,MAAK,GAAM,CAAA;gBAAC,GAAGA,IAAI;gBAAEV,MAAMH;YAAO,CAAA;IACnE;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/dev/appServerSupervisor.ts"],"sourcesContent":["import {type CliConfig, getCliConfigUncached} from '@sanity/cli-core'\nimport {type ViteDevServer} from 'vite'\n\n/**\n * Minimal shape the supervisor needs back from a started app/studio dev server.\n * The not-started arm is reserved for expected early exits the server already\n * reported (e.g. a missing organization id) — a failure to *boot* still throws.\n */\nexport type AppServerResult =\n | {close: () => Promise<void>; server: ViteDevServer; started: true}\n | {reason: string; started: false}\n\n/** Start the app/studio dev server for a given config (port + URL intent baked in by the caller). */\nexport type StartAppServer = (cliConfig: CliConfig) => Promise<AppServerResult>\n\nconst noop = async () => {}\n\nexport interface AppServerSupervisor {\n /** Stop the server once, waiting out any rebuild already in flight. */\n close: () => Promise<void>\n /** Tear down the running server and bring it back up with a freshly-loaded config. */\n rebuild: () => Promise<ViteDevServer>\n /** The currently-live dev server; re-points after a rebuild. */\n readonly server: ViteDevServer\n}\n\n/**\n * Own the app/studio dev server's lifecycle behind the dev-server registry seam.\n *\n * Adding or removing a view/service rebuilds the federation remote: its\n * module-federation `exposes` map and codegen artifacts are computed once at\n * server start, so a newly-declared interface has no expose until the server is\n * recreated — `server.restart()` can't do it (it reuses the inline config).\n * `rebuild` therefore tears the server down and starts a fresh one with a\n * reloaded config; the registry watcher calls it when the interface set changes.\n *\n * Returns the not-started result verbatim when the initial boot is an expected\n * early exit, so the caller can skip the rest of the orchestration.\n */\nexport async function startAppServerSupervisor(options: {\n cliConfig: CliConfig\n start: StartAppServer\n workDir: string\n}): Promise<{reason: string; started: false} | {started: true; supervisor: AppServerSupervisor}> {\n const {cliConfig, start, workDir} = options\n\n const initial = await start(cliConfig)\n if (!initial.started) return {reason: initial.reason, started: false}\n\n // `closeCurrent` repoints at the replacement only once a rebuild succeeds, so a\n // failed rebuild (old server already closed) leaves nothing for close() to re-close.\n let server = initial.server\n let closeCurrent = initial.close\n let closed = false\n // close() waits on this so a rebuild racing teardown can't orphan the replacement.\n // Rejections are the watcher's (warn + retry); the tracked copy is swallowed.\n let rebuildInFlight: Promise<unknown> = Promise.resolve()\n\n const runRebuild = async (): Promise<ViteDevServer> => {\n // Refuse once shutting down — a config save in the teardown window must not\n // boot a replacement nobody owns.\n if (closed) throw new Error('Dev server is shutting down')\n const freshConfig = await getCliConfigUncached(workDir)\n await closeCurrent()\n closeCurrent = noop\n const result = await start(freshConfig)\n if (!result.started) {\n // The server already reported why (e.g. organizationId was removed).\n throw new Error('Dev server did not restart after the view/service change')\n }\n server = result.server\n closeCurrent = result.close\n return server\n }\n\n return {\n started: true,\n supervisor: {\n async close() {\n closed = true\n await rebuildInFlight\n await closeCurrent()\n },\n rebuild() {\n const rebuild = runRebuild()\n rebuildInFlight = rebuild.catch(() => {})\n return rebuild\n },\n get server() {\n return server\n },\n },\n }\n}\n"],"names":["getCliConfigUncached","noop","startAppServerSupervisor","options","cliConfig","start","workDir","initial","started","reason","server","closeCurrent","close","closed","rebuildInFlight","Promise","resolve","runRebuild","Error","freshConfig","result","supervisor","rebuild","catch"],"mappings":"AAAA,SAAwBA,oBAAoB,QAAO,mBAAkB;AAerE,MAAMC,OAAO,WAAa;AAW1B;;;;;;;;;;;;CAYC,GACD,OAAO,eAAeC,yBAAyBC,OAI9C;IACC,MAAM,EAACC,SAAS,EAAEC,KAAK,EAAEC,OAAO,EAAC,GAAGH;IAEpC,MAAMI,UAAU,MAAMF,MAAMD;IAC5B,IAAI,CAACG,QAAQC,OAAO,EAAE,OAAO;QAACC,QAAQF,QAAQE,MAAM;QAAED,SAAS;IAAK;IAEpE,gFAAgF;IAChF,qFAAqF;IACrF,IAAIE,SAASH,QAAQG,MAAM;IAC3B,IAAIC,eAAeJ,QAAQK,KAAK;IAChC,IAAIC,SAAS;IACb,mFAAmF;IACnF,8EAA8E;IAC9E,IAAIC,kBAAoCC,QAAQC,OAAO;IAEvD,MAAMC,aAAa;QACjB,4EAA4E;QAC5E,kCAAkC;QAClC,IAAIJ,QAAQ,MAAM,IAAIK,MAAM;QAC5B,MAAMC,cAAc,MAAMnB,qBAAqBM;QAC/C,MAAMK;QACNA,eAAeV;QACf,MAAMmB,SAAS,MAAMf,MAAMc;QAC3B,IAAI,CAACC,OAAOZ,OAAO,EAAE;YACnB,qEAAqE;YACrE,MAAM,IAAIU,MAAM;QAClB;QACAR,SAASU,OAAOV,MAAM;QACtBC,eAAeS,OAAOR,KAAK;QAC3B,OAAOF;IACT;IAEA,OAAO;QACLF,SAAS;QACTa,YAAY;YACV,MAAMT;gBACJC,SAAS;gBACT,MAAMC;gBACN,MAAMH;YACR;YACAW;gBACE,MAAMA,UAAUL;gBAChBH,kBAAkBQ,QAAQC,KAAK,CAAC,KAAO;gBACvC,OAAOD;YACT;YACA,IAAIZ
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/dev/appServerSupervisor.ts"],"sourcesContent":["import {type CliConfig, getCliConfigUncached} from '@sanity/cli-core'\nimport {type ViteDevServer} from 'vite'\n\n/**\n * Minimal shape the supervisor needs back from a started app/studio dev server.\n * The not-started arm is reserved for expected early exits the server already\n * reported (e.g. a missing organization id) — a failure to *boot* still throws.\n */\nexport type AppServerResult =\n | {close: () => Promise<void>; server: ViteDevServer; started: true}\n | {reason: string; started: false}\n\n/** Start the app/studio dev server for a given config (port + URL intent baked in by the caller). */\nexport type StartAppServer = (cliConfig: CliConfig) => Promise<AppServerResult>\n\nconst noop = async () => {}\n\nexport interface AppServerSupervisor {\n /** Stop the server once, waiting out any rebuild already in flight. */\n close: () => Promise<void>\n /** Tear down the running server and bring it back up with a freshly-loaded config. */\n rebuild: () => Promise<ViteDevServer>\n /** The currently-live dev server; re-points after a rebuild. */\n readonly server: ViteDevServer\n}\n\n/**\n * Own the app/studio dev server's lifecycle behind the dev-server registry seam.\n *\n * Adding or removing a view/service rebuilds the federation remote: its\n * module-federation `exposes` map and codegen artifacts are computed once at\n * server start, so a newly-declared interface has no expose until the server is\n * recreated — `server.restart()` can't do it (it reuses the inline config).\n * `rebuild` therefore tears the server down and starts a fresh one with a\n * reloaded config; the registry watcher calls it when the interface set changes.\n *\n * Returns the not-started result verbatim when the initial boot is an expected\n * early exit, so the caller can skip the rest of the orchestration.\n */\nexport async function startAppServerSupervisor(options: {\n cliConfig: CliConfig\n start: StartAppServer\n workDir: string\n}): Promise<{reason: string; started: false} | {started: true; supervisor: AppServerSupervisor}> {\n const {cliConfig, start, workDir} = options\n\n const initial = await start(cliConfig)\n if (!initial.started) return {reason: initial.reason, started: false}\n\n // `closeCurrent` repoints at the replacement only once a rebuild succeeds, so a\n // failed rebuild (old server already closed) leaves nothing for close() to re-close.\n let server = initial.server\n let closeCurrent = initial.close\n let closed = false\n // close() waits on this so a rebuild racing teardown can't orphan the replacement.\n // Rejections are the watcher's (warn + retry); the tracked copy is swallowed.\n let rebuildInFlight: Promise<unknown> = Promise.resolve()\n\n const runRebuild = async (): Promise<ViteDevServer> => {\n // Refuse once shutting down — a config save in the teardown window must not\n // boot a replacement nobody owns.\n if (closed) throw new Error('Dev server is shutting down')\n const freshConfig = await getCliConfigUncached(workDir)\n await closeCurrent()\n closeCurrent = noop\n const result = await start(freshConfig)\n if (!result.started) {\n // The server already reported why (e.g. organizationId was removed).\n throw new Error('Dev server did not restart after the view/service change')\n }\n server = result.server\n closeCurrent = result.close\n return server\n }\n\n return {\n started: true,\n supervisor: {\n async close() {\n closed = true\n await rebuildInFlight\n await closeCurrent()\n },\n rebuild() {\n const rebuild = runRebuild()\n rebuildInFlight = rebuild.catch(() => {})\n return rebuild\n },\n get server() {\n return server\n },\n },\n }\n}\n"],"names":["getCliConfigUncached","noop","startAppServerSupervisor","options","cliConfig","start","workDir","initial","started","reason","server","closeCurrent","close","closed","rebuildInFlight","Promise","resolve","runRebuild","Error","freshConfig","result","supervisor","rebuild","catch"],"mappings":"AAAA,SAAwBA,oBAAoB,QAAO,mBAAkB;AAerE,MAAMC,OAAO,WAAa;AAW1B;;;;;;;;;;;;CAYC,GACD,OAAO,eAAeC,yBAAyBC,OAI9C;IACC,MAAM,EAACC,SAAS,EAAEC,KAAK,EAAEC,OAAO,EAAC,GAAGH;IAEpC,MAAMI,UAAU,MAAMF,MAAMD;IAC5B,IAAI,CAACG,QAAQC,OAAO,EAAE,OAAO;QAACC,QAAQF,QAAQE,MAAM;QAAED,SAAS;IAAK;IAEpE,gFAAgF;IAChF,qFAAqF;IACrF,IAAIE,SAASH,QAAQG,MAAM;IAC3B,IAAIC,eAAeJ,QAAQK,KAAK;IAChC,IAAIC,SAAS;IACb,mFAAmF;IACnF,8EAA8E;IAC9E,IAAIC,kBAAoCC,QAAQC,OAAO;IAEvD,MAAMC,aAAa;QACjB,4EAA4E;QAC5E,kCAAkC;QAClC,IAAIJ,QAAQ,MAAM,IAAIK,MAAM;QAC5B,MAAMC,cAAc,MAAMnB,qBAAqBM;QAC/C,MAAMK;QACNA,eAAeV;QACf,MAAMmB,SAAS,MAAMf,MAAMc;QAC3B,IAAI,CAACC,OAAOZ,OAAO,EAAE;YACnB,qEAAqE;YACrE,MAAM,IAAIU,MAAM;QAClB;QACAR,SAASU,OAAOV,MAAM;QACtBC,eAAeS,OAAOR,KAAK;QAC3B,OAAOF;IACT;IAEA,OAAO;QACLF,SAAS;QACTa,YAAY;YACV,MAAMT;gBACJC,SAAS;gBACT,MAAMC;gBACN,MAAMH;YACR;YACAW;gBACE,MAAMA,UAAUL;gBAChBH,kBAAkBQ,QAAQC,KAAK,CAAC,KAAO;gBACvC,OAAOD;YACT;YACA,IAAIZ;gBACF,OAAOA;YACT;QACF;IACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/dev/registry.ts"],"sourcesContent":["import {\n existsSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n unlinkSync,\n watch,\n writeFileSync,\n} from 'node:fs'\nimport {join} from 'node:path'\n\nimport {\n coreAppManifestSchema,\n getSanityDataDir,\n studioManifestSchema,\n subdebug,\n} from '@sanity/cli-core'\nimport {z} from 'zod/mini'\n\nimport {TileInterfaceMetadataSchema, ViewPlacementMetadataSchema} from '../../contract.js'\nimport {canonicalizeWatchDir} from './canonicalizeWatchDir.js'\nimport {getProcessStartTime, isOurProcess} from './processLiveness.js'\n\n/**\n * The dev-server registry: how a running `sanity dev` / `sanity start` process\n * advertises itself so the workbench on this machine can find and load it.\n *\n * Two kinds of file under `~/.sanity/dev-servers/` do the coordinating:\n *\n * - `<pid>.json` — one per running app/studio server, holding where it's served\n * plus its inlined manifest and interfaces. The workbench reads these to\n * discover and render local apps. Written by `registerDevServer`, watched by\n * `watchRegistry`.\n * - `workbench.lock` — a single machine-wide lock, so only one workbench shell\n * runs at a time and later `dev`s register into it instead of starting their\n * own. Managed by `acquireWorkbenchLock` / `readWorkbenchLock`.\n *\n * Both files belong to the process that created them and must not outlive it.\n * Three things keep that true: an explicit `release()` on clean shutdown, an\n * `exit` backstop for abrupt exits (`unlinkOnProcessExit`), and a dead-pid prune\n * on read (`isOurProcess`) that clears whatever a crashed process left behind.\n */\n\nconst devDebug = subdebug('dev')\n\n/** Bump when the manifest/lock shape changes in a breaking way. */\nconst REGISTRY_VERSION = 2\n\n/**\n * The current process's start time as reported by the OS, for the `startedAt`\n * that `isOurProcess` checks on re-read. Falls back to now when the OS time is\n * unavailable — `new Date()` alone records the write time, which drifts from\n * process start by enough to look stale and get pruned right after writing.\n */\nfunction ownStartedAt(): string {\n return (getProcessStartTime(process.pid) ?? new Date()).toISOString()\n}\n\nconst interfaceBaseFields = {\n /** CLI-minted for a local interface; a deployed one gets its id from Brett. */\n id: z.string(),\n moduleId: z.string(),\n name: z.string(),\n /** Raw source vite serves; a deployed interface carries only the `moduleId`. */\n src: z.string(),\n title: z.string(),\n version: z.optional(z.string()),\n}\n\n/**\n * A forwarded interface. Kept outside the manifest so\n * the workbench renders local panels and runs workers without a deploy.\n */\nconst devServerInterfaceSchema = z.union([\n z.discriminatedUnion('surface', [\n z.object({\n ...interfaceBaseFields,\n metadata: z.nullable(ViewPlacementMetadataSchema),\n surface: z.literal('app'),\n }),\n z.object({\n ...interfaceBaseFields,\n metadata: z.nullable(ViewPlacementMetadataSchema),\n surface: z.literal('panel'),\n }),\n z.object({...interfaceBaseFields, metadata: z.null(), surface: z.literal('asset_source')}),\n z.object({\n ...interfaceBaseFields,\n metadata: TileInterfaceMetadataSchema,\n surface: z.literal('tile'),\n }),\n ]),\n z.object({...interfaceBaseFields, metadata: z.null(), type: z.literal('worker')}),\n])\n\nconst devServerManifestSchema = z.object({\n /**\n * Field schema *values* load from the federation module; each field's `src`\n * rides along so a repoint bumps the exposes-set id and forces a rebuild.\n * Lenient — the workbench is the authority.\n */\n configs: z.optional(\n z.array(\n z.object({\n // Identifies the owning app when it has no app id (singletons).\n appType: z.optional(z.string()),\n fields: z.array(\n z.object({\n name: z.string(),\n public: z.optional(z.boolean()),\n src: z.string(),\n title: z.string(),\n }),\n ),\n // Content hash of the config — the workbench's change-detection key\n // (see deriveConfigs).\n id: z.string(),\n // The app's `defineApplication` slug — the module-federation alias the\n // workbench loads this config's live values from.\n moduleName: z.optional(z.string()),\n // The version the workbench federates this config's module under —\n // a string, like the one Brett returns on a deployed `activeConfig`.\n version: z.string(),\n }),\n ),\n ),\n host: z.string(),\n id: z.optional(z.string()),\n interfaces: z.optional(z.array(devServerInterfaceSchema)),\n /**\n * Inlined manifest — either a {@link StudioManifest} or {@link CoreAppManifest},\n * validated against the shared cli-core schemas. The registry stores and\n * rebroadcasts it; the CLI is what extracts and writes it.\n */\n manifest: z.optional(z.union([studioManifestSchema, coreAppManifestSchema])),\n /**\n * ISO timestamp of the most recent successful manifest extraction. Bumped\n * on every regeneration so re-writing this registry entry triggers the\n * workbench `watchRegistry` watcher and forces a rebroadcast to clients.\n */\n manifestUpdatedAt: z.optional(z.string()),\n // Stable identity + qualified reference, composed by the CLI (the authority for\n // local apps, which never reach brett) and read straight by the workbench.\n name: z.optional(z.string()),\n pid: z.number(),\n port: z.number(),\n projectId: z.optional(z.string()),\n reference: z.optional(z.string()),\n startedAt: z.string(),\n type: z.enum(['coreApp', 'studio']),\n version: z.literal(REGISTRY_VERSION),\n workDir: z.string(),\n})\n/**\n * A manifest describing a running dev server process (studio or app).\n * Stored as `~/.sanity/dev-servers/<pid>.json`.\n *\n * The workbench singleton is tracked separately via the lock file — see\n * `acquireWorkbenchLock` and `readWorkbenchLock` below.\n */\nexport type DevServerManifest = z.infer<typeof devServerManifestSchema>\n\n/**\n * A config-only server carries configs but no interfaces — e.g. a\n * media-library config app under development. The workbench never routes it\n * as an app; only its configs are published. It therefore plays a different\n * role than an app server, and the two may share a slug (a config app\n * developed alongside the locally served singleton it configures).\n */\nexport function isConfigOnlyServer(\n server: Pick<DevServerManifest, 'configs' | 'interfaces'>,\n): boolean {\n return Boolean(server.configs?.length) && !server.interfaces?.length\n}\n\n/**\n * Path to the dev server registry directory. Lives under the shared Sanity\n * config directory to stay consistent with other CLI paths.\n */\nfunction getRegistryDir(): string {\n return join(getSanityDataDir(), 'dev-servers')\n}\n\n// One shared `exit` listener drives every registered cleanup, so N locks/entries\n// don't each add a listener and trip Node's MaxListeners warning.\nconst exitCleanups = new Set<() => void>()\nlet exitListenerInstalled = false\n\nfunction runExitCleanups(): void {\n for (const cleanup of exitCleanups) cleanup()\n}\n\n/** Exercise the exit backstop in tests without terminating the process; not part\n * of the package's public surface. */\nexport const runRegistryExitCleanupForTesting = runExitCleanups\n\n/**\n * Delete a registry file synchronously on process exit, as a backstop for abrupt\n * termination. Vite installs its own SIGTERM handler that calls `process.exit()`,\n * which can outrun the async server teardown and leave the lock or registry entry\n * behind — a stray dev-server that lingers until the dead-pid prune clears it. The\n * `exit` event only runs synchronous work, hence `unlinkSync`. `ownedByUs` guards\n * the shared lock so a successor that reacquired it isn't wiped. Returns a\n * detacher to call after a clean release.\n */\nfunction unlinkOnProcessExit(filePath: string, ownedByUs: () => boolean): () => void {\n const cleanup = () => {\n if (!ownedByUs()) return\n try {\n unlinkSync(filePath)\n } catch {\n // The file may already have been removed during shutdown.\n }\n }\n exitCleanups.add(cleanup)\n\n if (!exitListenerInstalled) {\n exitListenerInstalled = true\n process.once('exit', runExitCleanups)\n }\n\n return () => exitCleanups.delete(cleanup)\n}\n\ninterface DevServerRegistration {\n /** Remove the registry entry. */\n release: () => void\n /**\n * Rewrite the registry entry with partial updates merged in. Also bumps the\n * file's mtime, which fires `watchRegistry` in any workbench process and\n * triggers a rebroadcast to connected clients.\n */\n update: (patch: Partial<Omit<DevServerManifest, 'pid' | 'startedAt' | 'version'>>) => void\n}\n\n/**\n * Write a manifest file for the current process and return a handle with a\n * `release` function that removes it plus an `update` function for patching\n * fields post-registration. Uses synchronous I/O so the file exists before\n * any signal handler could fire.\n */\nexport function registerDevServer(\n manifest: Omit<DevServerManifest, 'pid' | 'startedAt' | 'version'>,\n): DevServerRegistration {\n const registryDir = getRegistryDir()\n mkdirSync(registryDir, {recursive: true})\n\n let current: DevServerManifest = {\n ...manifest,\n pid: process.pid,\n startedAt: ownStartedAt(),\n version: REGISTRY_VERSION,\n }\n\n const filePath = join(registryDir, `${process.pid}.json`)\n writeFileSync(filePath, JSON.stringify(current, null, 2))\n\n // Guard against late updates from background tasks (e.g. the initial\n // manifest extraction) landing after `release()` has deleted the file —\n // without this, the update would re-create the registry entry and leak.\n let released = false\n\n // The file is pid-named, so it's always ours to remove on exit.\n const detachExitCleanup = unlinkOnProcessExit(filePath, () => !released)\n\n return {\n release() {\n released = true\n detachExitCleanup()\n try {\n unlinkSync(filePath)\n } catch {\n // ENOENT is fine — already cleaned up\n }\n },\n update(patch) {\n if (released) return\n current = {...current, ...patch}\n writeFileSync(filePath, JSON.stringify(current, null, 2))\n },\n }\n}\n\n/**\n * Read all manifest files from the registry, prune stale entries (dead PIDs),\n * and return the live ones.\n */\nexport function getRegisteredServers(): DevServerManifest[] {\n const registryDir = getRegistryDir()\n\n if (!existsSync(registryDir)) {\n return []\n }\n\n const files = readdirSync(registryDir).filter((f) => f.endsWith('.json'))\n const servers: DevServerManifest[] = []\n\n for (const file of files) {\n const filePath = join(registryDir, file)\n let raw: unknown\n try {\n raw = JSON.parse(readFileSync(filePath, 'utf8'))\n } catch {\n continue\n }\n\n const {data, success} = devServerManifestSchema.safeParse(raw)\n if (!success) continue\n\n if (isOurProcess(data.pid, data.startedAt)) {\n servers.push(data)\n } else {\n try {\n unlinkSync(filePath)\n } catch {\n // Ignore — another process may have already cleaned it up\n }\n }\n }\n\n return servers\n}\n\ninterface RegistryWatcher {\n close(): void\n}\n\n/**\n * Watch the registry directory for changes and invoke the callback with the\n * current list of live servers whenever a change is detected.\n *\n * Uses `fs.watch` with a debounce to coalesce rapid file changes (e.g. a\n * server starting and writing its manifest triggers multiple FS events).\n */\nexport function watchRegistry(callback: (servers: DevServerManifest[]) => void): RegistryWatcher {\n const registryDir = getRegistryDir()\n mkdirSync(registryDir, {recursive: true})\n\n // Canonicalize to the real long path so `fs.watch` doesn't abort on Windows\n // short-path dirs. See `canonicalizeWatchDir`.\n const watchDir = canonicalizeWatchDir(registryDir)\n\n let debounceTimer: ReturnType<typeof setTimeout> | undefined\n\n const notify = () => {\n clearTimeout(debounceTimer)\n debounceTimer = setTimeout(() => {\n callback(getRegisteredServers())\n }, 50)\n }\n\n const watcher = watch(watchDir, notify)\n\n return {\n close() {\n clearTimeout(debounceTimer)\n watcher.close()\n },\n }\n}\n\n// The workbench singleton lock — \"one workbench per machine\". Lives in the same\n// registry dir and shares the liveness/prune model: a stale lock left by a\n// crashed process is pruned on read so the next acquire isn't blocked forever.\n\nconst workbenchLockSchema = z.object({\n host: z.string(),\n pid: z.number(),\n port: z.number(),\n startedAt: z.string(),\n version: z.literal(REGISTRY_VERSION),\n})\n\n/**\n * Read the workbench lock file and return its contents if the holding\n * process is still alive. Prunes stale locks from crashed processes.\n */\nexport function readWorkbenchLock(): z.infer<typeof workbenchLockSchema> | undefined {\n const lockPath = join(getRegistryDir(), 'workbench.lock')\n\n let contents: string\n try {\n contents = readFileSync(lockPath, 'utf8')\n } catch {\n // File doesn't exist — nothing to prune, nothing to return\n return undefined\n }\n\n // Past this point the file exists. Anything that isn't a live, valid lock\n // (unparsable JSON, schema mismatch, dead/reused PID) is stale and must be\n // pruned — otherwise the next `acquireWorkbenchLock` call is blocked by\n // EEXIST forever and `sanity dev` silently no-ops the workbench server.\n const data = parseLockContents(contents)\n devDebug('Read workbench lock: %o', data)\n if (data && isOurProcess(data.pid, data.startedAt)) {\n devDebug('Workbench process is alive at pid %d on port %d', data.pid, data.port)\n return data\n }\n\n pruneWorkbenchLock(lockPath)\n return undefined\n}\n\nfunction parseLockContents(contents: string): z.infer<typeof workbenchLockSchema> | undefined {\n try {\n const {data, success} = workbenchLockSchema.safeParse(JSON.parse(contents))\n return success ? data : undefined\n } catch {\n return undefined\n }\n}\n\nfunction pruneWorkbenchLock(lockPath: string): void {\n try {\n devDebug('Removing stale workbench lock')\n unlinkSync(lockPath)\n devDebug('Stale workbench lock removed')\n } catch {\n // Another process may have already cleaned it up\n }\n}\n\ninterface WorkbenchLock {\n /** Release the lock file. */\n release: () => void\n /** Update the lock with the actual port after the server starts listening. */\n updatePort: (port: number) => void\n}\n\n/**\n * Attempt to acquire an exclusive lock for the workbench process.\n * Uses `O_EXCL` (the `wx` flag) which is atomic at the OS level — only one\n * process can create the file.\n *\n * The lock stores `{pid, host, port}` so other processes can find the\n * running workbench. Call `updatePort` after the Vite server starts to\n * write the actual port (Vite may pick a different one).\n *\n * @returns A {@link WorkbenchLock} if acquired, or `undefined` if another\n * live process already holds it.\n */\nexport function acquireWorkbenchLock(\n info: {host: string; port: number},\n retries = 1,\n): WorkbenchLock | undefined {\n const registryDir = getRegistryDir()\n mkdirSync(registryDir, {recursive: true})\n\n const lockPath = join(registryDir, 'workbench.lock')\n const startedAt = ownStartedAt()\n const lockData = {\n host: info.host,\n pid: process.pid,\n port: info.port,\n startedAt,\n version: REGISTRY_VERSION,\n }\n\n devDebug('Acquiring workbench lock at %s', lockPath)\n\n try {\n writeFileSync(lockPath, JSON.stringify(lockData), {flag: 'wx'})\n devDebug('Workbench lock acquired')\n\n let released = false\n // Only wipe the lock on exit if it's still ours — a successor that reacquired\n // it after our own release must not be clobbered.\n const detachExitCleanup = unlinkOnProcessExit(lockPath, () => {\n if (released) return false\n try {\n const disk = parseLockContents(readFileSync(lockPath, 'utf8'))\n return disk?.pid === process.pid && disk.startedAt === startedAt\n } catch {\n return false\n }\n })\n\n return {\n release() {\n released = true\n detachExitCleanup()\n try {\n unlinkSync(lockPath)\n } catch {\n // Already cleaned up\n }\n },\n updatePort(port: number) {\n writeFileSync(lockPath, JSON.stringify({...lockData, port}))\n },\n }\n } catch (err: unknown) {\n devDebug(\n 'Failed to acquire workbench lock: %s',\n err instanceof Error ? err.message : String(err),\n )\n if (!isNodeError(err) || err.code !== 'EEXIST') return undefined\n\n // Lock exists — check if the holder is still alive\n const existing = readWorkbenchLock()\n if (existing) return undefined\n\n // Stale lock was pruned by readWorkbenchLock — retry (with guard against infinite recursion)\n if (retries <= 0) return undefined\n return acquireWorkbenchLock(info, retries - 1)\n }\n}\n\nfunction isNodeError(err: unknown): err is NodeJS.ErrnoException {\n return err instanceof Error && 'code' in err\n}\n"],"names":["existsSync","mkdirSync","readdirSync","readFileSync","unlinkSync","watch","writeFileSync","join","coreAppManifestSchema","getSanityDataDir","studioManifestSchema","subdebug","z","TileInterfaceMetadataSchema","ViewPlacementMetadataSchema","canonicalizeWatchDir","getProcessStartTime","isOurProcess","devDebug","REGISTRY_VERSION","ownStartedAt","process","pid","Date","toISOString","interfaceBaseFields","id","string","moduleId","name","src","title","version","optional","devServerInterfaceSchema","union","discriminatedUnion","object","metadata","nullable","surface","literal","null","type","devServerManifestSchema","configs","array","appType","fields","public","boolean","moduleName","host","interfaces","manifest","manifestUpdatedAt","number","port","projectId","reference","startedAt","enum","workDir","isConfigOnlyServer","server","Boolean","length","getRegistryDir","exitCleanups","Set","exitListenerInstalled","runExitCleanups","cleanup","runRegistryExitCleanupForTesting","unlinkOnProcessExit","filePath","ownedByUs","add","once","delete","registerDevServer","registryDir","recursive","current","JSON","stringify","released","detachExitCleanup","release","update","patch","getRegisteredServers","files","filter","f","endsWith","servers","file","raw","parse","data","success","safeParse","push","watchRegistry","callback","watchDir","debounceTimer","notify","clearTimeout","setTimeout","watcher","close","workbenchLockSchema","readWorkbenchLock","lockPath","contents","undefined","parseLockContents","pruneWorkbenchLock","acquireWorkbenchLock","info","retries","lockData","flag","disk","updatePort","err","Error","message","String","isNodeError","code","existing"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/dev/registry.ts"],"sourcesContent":["import {\n existsSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n unlinkSync,\n watch,\n writeFileSync,\n} from 'node:fs'\nimport {join} from 'node:path'\n\nimport {\n coreAppManifestSchema,\n getSanityDataDir,\n studioManifestSchema,\n subdebug,\n} from '@sanity/cli-core'\nimport {z} from 'zod/mini'\n\nimport {TileInterfaceMetadataSchema, ViewPlacementMetadataSchema} from '../../contract.js'\nimport {canonicalizeWatchDir} from './canonicalizeWatchDir.js'\nimport {getProcessStartTime, isOurProcess} from './processLiveness.js'\n\n/**\n * The dev-server registry: how a running `sanity dev` / `sanity start` process\n * advertises itself so the workbench on this machine can find and load it.\n *\n * Two kinds of file under `~/.sanity/dev-servers/` do the coordinating:\n *\n * - `<pid>.json` — one per running app/studio server, holding where it's served\n * plus its inlined manifest and interfaces. The workbench reads these to\n * discover and render local apps. Written by `registerDevServer`, watched by\n * `watchRegistry`.\n * - `workbench.lock` — a single machine-wide lock, so only one workbench shell\n * runs at a time and later `dev`s register into it instead of starting their\n * own. Managed by `acquireWorkbenchLock` / `readWorkbenchLock`.\n *\n * Both files belong to the process that created them and must not outlive it.\n * Three things keep that true: an explicit `release()` on clean shutdown, an\n * `exit` backstop for abrupt exits (`unlinkOnProcessExit`), and a dead-pid prune\n * on read (`isOurProcess`) that clears whatever a crashed process left behind.\n */\n\nconst devDebug = subdebug('dev')\n\n/** Bump when the manifest/lock shape changes in a breaking way. */\nconst REGISTRY_VERSION = 2\n\n/**\n * The current process's start time as reported by the OS, for the `startedAt`\n * that `isOurProcess` checks on re-read. Falls back to now when the OS time is\n * unavailable — `new Date()` alone records the write time, which drifts from\n * process start by enough to look stale and get pruned right after writing.\n */\nfunction ownStartedAt(): string {\n return (getProcessStartTime(process.pid) ?? new Date()).toISOString()\n}\n\nconst interfaceBaseFields = {\n /** CLI-minted for a local interface; a deployed one gets its id from Brett. */\n id: z.string(),\n moduleId: z.string(),\n name: z.string(),\n /** Raw source vite serves; a deployed interface carries only the `moduleId`. */\n src: z.string(),\n title: z.string(),\n version: z.optional(z.string()),\n}\n\n/**\n * A forwarded interface. Kept outside the manifest so\n * the workbench renders local panels and runs workers without a deploy.\n */\nconst devServerInterfaceSchema = z.union([\n z.discriminatedUnion('surface', [\n z.object({\n ...interfaceBaseFields,\n metadata: z.nullable(ViewPlacementMetadataSchema),\n surface: z.literal('window'),\n }),\n z.object({\n ...interfaceBaseFields,\n metadata: z.nullable(ViewPlacementMetadataSchema),\n surface: z.literal('panel'),\n }),\n z.object({...interfaceBaseFields, metadata: z.null(), surface: z.literal('asset_source')}),\n z.object({\n ...interfaceBaseFields,\n metadata: TileInterfaceMetadataSchema,\n surface: z.literal('tile'),\n }),\n ]),\n z.object({...interfaceBaseFields, metadata: z.null(), type: z.literal('worker')}),\n])\n\nconst devServerManifestSchema = z.object({\n /**\n * Field schema *values* load from the federation module; each field's `src`\n * rides along so a repoint bumps the exposes-set id and forces a rebuild.\n * Lenient — the workbench is the authority.\n */\n configs: z.optional(\n z.array(\n z.object({\n // Identifies the owning app when it has no app id (singletons).\n appType: z.optional(z.string()),\n fields: z.array(\n z.object({\n name: z.string(),\n public: z.optional(z.boolean()),\n src: z.string(),\n title: z.string(),\n }),\n ),\n // Content hash of the config — the workbench's change-detection key\n // (see deriveConfigs).\n id: z.string(),\n // The app's `defineApplication` slug — the module-federation alias the\n // workbench loads this config's live values from.\n moduleName: z.optional(z.string()),\n // The version the workbench federates this config's module under —\n // a string, like the one Brett returns on a deployed `activeConfig`.\n version: z.string(),\n }),\n ),\n ),\n host: z.string(),\n id: z.optional(z.string()),\n interfaces: z.optional(z.array(devServerInterfaceSchema)),\n /**\n * Inlined manifest — either a {@link StudioManifest} or {@link CoreAppManifest},\n * validated against the shared cli-core schemas. The registry stores and\n * rebroadcasts it; the CLI is what extracts and writes it.\n */\n manifest: z.optional(z.union([studioManifestSchema, coreAppManifestSchema])),\n /**\n * ISO timestamp of the most recent successful manifest extraction. Bumped\n * on every regeneration so re-writing this registry entry triggers the\n * workbench `watchRegistry` watcher and forces a rebroadcast to clients.\n */\n manifestUpdatedAt: z.optional(z.string()),\n // Stable identity + qualified reference, composed by the CLI (the authority for\n // local apps, which never reach brett) and read straight by the workbench.\n name: z.optional(z.string()),\n pid: z.number(),\n port: z.number(),\n projectId: z.optional(z.string()),\n reference: z.optional(z.string()),\n startedAt: z.string(),\n type: z.enum(['coreApp', 'studio']),\n version: z.literal(REGISTRY_VERSION),\n workDir: z.string(),\n})\n/**\n * A manifest describing a running dev server process (studio or app).\n * Stored as `~/.sanity/dev-servers/<pid>.json`.\n *\n * The workbench singleton is tracked separately via the lock file — see\n * `acquireWorkbenchLock` and `readWorkbenchLock` below.\n */\nexport type DevServerManifest = z.infer<typeof devServerManifestSchema>\n\n/**\n * A config-only server carries configs but no interfaces — e.g. a\n * media-library config app under development. The workbench never routes it\n * as an app; only its configs are published. It therefore plays a different\n * role than an app server, and the two may share a slug (a config app\n * developed alongside the locally served singleton it configures).\n */\nexport function isConfigOnlyServer(\n server: Pick<DevServerManifest, 'configs' | 'interfaces'>,\n): boolean {\n return Boolean(server.configs?.length) && !server.interfaces?.length\n}\n\n/**\n * Path to the dev server registry directory. Lives under the shared Sanity\n * config directory to stay consistent with other CLI paths.\n */\nfunction getRegistryDir(): string {\n return join(getSanityDataDir(), 'dev-servers')\n}\n\n// One shared `exit` listener drives every registered cleanup, so N locks/entries\n// don't each add a listener and trip Node's MaxListeners warning.\nconst exitCleanups = new Set<() => void>()\nlet exitListenerInstalled = false\n\nfunction runExitCleanups(): void {\n for (const cleanup of exitCleanups) cleanup()\n}\n\n/** Exercise the exit backstop in tests without terminating the process; not part\n * of the package's public surface. */\nexport const runRegistryExitCleanupForTesting = runExitCleanups\n\n/**\n * Delete a registry file synchronously on process exit, as a backstop for abrupt\n * termination. Vite installs its own SIGTERM handler that calls `process.exit()`,\n * which can outrun the async server teardown and leave the lock or registry entry\n * behind — a stray dev-server that lingers until the dead-pid prune clears it. The\n * `exit` event only runs synchronous work, hence `unlinkSync`. `ownedByUs` guards\n * the shared lock so a successor that reacquired it isn't wiped. Returns a\n * detacher to call after a clean release.\n */\nfunction unlinkOnProcessExit(filePath: string, ownedByUs: () => boolean): () => void {\n const cleanup = () => {\n if (!ownedByUs()) return\n try {\n unlinkSync(filePath)\n } catch {\n // The file may already have been removed during shutdown.\n }\n }\n exitCleanups.add(cleanup)\n\n if (!exitListenerInstalled) {\n exitListenerInstalled = true\n process.once('exit', runExitCleanups)\n }\n\n return () => exitCleanups.delete(cleanup)\n}\n\ninterface DevServerRegistration {\n /** Remove the registry entry. */\n release: () => void\n /**\n * Rewrite the registry entry with partial updates merged in. Also bumps the\n * file's mtime, which fires `watchRegistry` in any workbench process and\n * triggers a rebroadcast to connected clients.\n */\n update: (patch: Partial<Omit<DevServerManifest, 'pid' | 'startedAt' | 'version'>>) => void\n}\n\n/**\n * Write a manifest file for the current process and return a handle with a\n * `release` function that removes it plus an `update` function for patching\n * fields post-registration. Uses synchronous I/O so the file exists before\n * any signal handler could fire.\n */\nexport function registerDevServer(\n manifest: Omit<DevServerManifest, 'pid' | 'startedAt' | 'version'>,\n): DevServerRegistration {\n const registryDir = getRegistryDir()\n mkdirSync(registryDir, {recursive: true})\n\n let current: DevServerManifest = {\n ...manifest,\n pid: process.pid,\n startedAt: ownStartedAt(),\n version: REGISTRY_VERSION,\n }\n\n const filePath = join(registryDir, `${process.pid}.json`)\n writeFileSync(filePath, JSON.stringify(current, null, 2))\n\n // Guard against late updates from background tasks (e.g. the initial\n // manifest extraction) landing after `release()` has deleted the file —\n // without this, the update would re-create the registry entry and leak.\n let released = false\n\n // The file is pid-named, so it's always ours to remove on exit.\n const detachExitCleanup = unlinkOnProcessExit(filePath, () => !released)\n\n return {\n release() {\n released = true\n detachExitCleanup()\n try {\n unlinkSync(filePath)\n } catch {\n // ENOENT is fine — already cleaned up\n }\n },\n update(patch) {\n if (released) return\n current = {...current, ...patch}\n writeFileSync(filePath, JSON.stringify(current, null, 2))\n },\n }\n}\n\n/**\n * Read all manifest files from the registry, prune stale entries (dead PIDs),\n * and return the live ones.\n */\nexport function getRegisteredServers(): DevServerManifest[] {\n const registryDir = getRegistryDir()\n\n if (!existsSync(registryDir)) {\n return []\n }\n\n const files = readdirSync(registryDir).filter((f) => f.endsWith('.json'))\n const servers: DevServerManifest[] = []\n\n for (const file of files) {\n const filePath = join(registryDir, file)\n let raw: unknown\n try {\n raw = JSON.parse(readFileSync(filePath, 'utf8'))\n } catch {\n continue\n }\n\n const {data, success} = devServerManifestSchema.safeParse(raw)\n if (!success) continue\n\n if (isOurProcess(data.pid, data.startedAt)) {\n servers.push(data)\n } else {\n try {\n unlinkSync(filePath)\n } catch {\n // Ignore — another process may have already cleaned it up\n }\n }\n }\n\n return servers\n}\n\ninterface RegistryWatcher {\n close(): void\n}\n\n/**\n * Watch the registry directory for changes and invoke the callback with the\n * current list of live servers whenever a change is detected.\n *\n * Uses `fs.watch` with a debounce to coalesce rapid file changes (e.g. a\n * server starting and writing its manifest triggers multiple FS events).\n */\nexport function watchRegistry(callback: (servers: DevServerManifest[]) => void): RegistryWatcher {\n const registryDir = getRegistryDir()\n mkdirSync(registryDir, {recursive: true})\n\n // Canonicalize to the real long path so `fs.watch` doesn't abort on Windows\n // short-path dirs. See `canonicalizeWatchDir`.\n const watchDir = canonicalizeWatchDir(registryDir)\n\n let debounceTimer: ReturnType<typeof setTimeout> | undefined\n\n const notify = () => {\n clearTimeout(debounceTimer)\n debounceTimer = setTimeout(() => {\n callback(getRegisteredServers())\n }, 50)\n }\n\n const watcher = watch(watchDir, notify)\n\n return {\n close() {\n clearTimeout(debounceTimer)\n watcher.close()\n },\n }\n}\n\n// The workbench singleton lock — \"one workbench per machine\". Lives in the same\n// registry dir and shares the liveness/prune model: a stale lock left by a\n// crashed process is pruned on read so the next acquire isn't blocked forever.\n\nconst workbenchLockSchema = z.object({\n host: z.string(),\n pid: z.number(),\n port: z.number(),\n startedAt: z.string(),\n version: z.literal(REGISTRY_VERSION),\n})\n\n/**\n * Read the workbench lock file and return its contents if the holding\n * process is still alive. Prunes stale locks from crashed processes.\n */\nexport function readWorkbenchLock(): z.infer<typeof workbenchLockSchema> | undefined {\n const lockPath = join(getRegistryDir(), 'workbench.lock')\n\n let contents: string\n try {\n contents = readFileSync(lockPath, 'utf8')\n } catch {\n // File doesn't exist — nothing to prune, nothing to return\n return undefined\n }\n\n // Past this point the file exists. Anything that isn't a live, valid lock\n // (unparsable JSON, schema mismatch, dead/reused PID) is stale and must be\n // pruned — otherwise the next `acquireWorkbenchLock` call is blocked by\n // EEXIST forever and `sanity dev` silently no-ops the workbench server.\n const data = parseLockContents(contents)\n devDebug('Read workbench lock: %o', data)\n if (data && isOurProcess(data.pid, data.startedAt)) {\n devDebug('Workbench process is alive at pid %d on port %d', data.pid, data.port)\n return data\n }\n\n pruneWorkbenchLock(lockPath)\n return undefined\n}\n\nfunction parseLockContents(contents: string): z.infer<typeof workbenchLockSchema> | undefined {\n try {\n const {data, success} = workbenchLockSchema.safeParse(JSON.parse(contents))\n return success ? data : undefined\n } catch {\n return undefined\n }\n}\n\nfunction pruneWorkbenchLock(lockPath: string): void {\n try {\n devDebug('Removing stale workbench lock')\n unlinkSync(lockPath)\n devDebug('Stale workbench lock removed')\n } catch {\n // Another process may have already cleaned it up\n }\n}\n\ninterface WorkbenchLock {\n /** Release the lock file. */\n release: () => void\n /** Update the lock with the actual port after the server starts listening. */\n updatePort: (port: number) => void\n}\n\n/**\n * Attempt to acquire an exclusive lock for the workbench process.\n * Uses `O_EXCL` (the `wx` flag) which is atomic at the OS level — only one\n * process can create the file.\n *\n * The lock stores `{pid, host, port}` so other processes can find the\n * running workbench. Call `updatePort` after the Vite server starts to\n * write the actual port (Vite may pick a different one).\n *\n * @returns A {@link WorkbenchLock} if acquired, or `undefined` if another\n * live process already holds it.\n */\nexport function acquireWorkbenchLock(\n info: {host: string; port: number},\n retries = 1,\n): WorkbenchLock | undefined {\n const registryDir = getRegistryDir()\n mkdirSync(registryDir, {recursive: true})\n\n const lockPath = join(registryDir, 'workbench.lock')\n const startedAt = ownStartedAt()\n const lockData = {\n host: info.host,\n pid: process.pid,\n port: info.port,\n startedAt,\n version: REGISTRY_VERSION,\n }\n\n devDebug('Acquiring workbench lock at %s', lockPath)\n\n try {\n writeFileSync(lockPath, JSON.stringify(lockData), {flag: 'wx'})\n devDebug('Workbench lock acquired')\n\n let released = false\n // Only wipe the lock on exit if it's still ours — a successor that reacquired\n // it after our own release must not be clobbered.\n const detachExitCleanup = unlinkOnProcessExit(lockPath, () => {\n if (released) return false\n try {\n const disk = parseLockContents(readFileSync(lockPath, 'utf8'))\n return disk?.pid === process.pid && disk.startedAt === startedAt\n } catch {\n return false\n }\n })\n\n return {\n release() {\n released = true\n detachExitCleanup()\n try {\n unlinkSync(lockPath)\n } catch {\n // Already cleaned up\n }\n },\n updatePort(port: number) {\n writeFileSync(lockPath, JSON.stringify({...lockData, port}))\n },\n }\n } catch (err: unknown) {\n devDebug(\n 'Failed to acquire workbench lock: %s',\n err instanceof Error ? err.message : String(err),\n )\n if (!isNodeError(err) || err.code !== 'EEXIST') return undefined\n\n // Lock exists — check if the holder is still alive\n const existing = readWorkbenchLock()\n if (existing) return undefined\n\n // Stale lock was pruned by readWorkbenchLock — retry (with guard against infinite recursion)\n if (retries <= 0) return undefined\n return acquireWorkbenchLock(info, retries - 1)\n }\n}\n\nfunction isNodeError(err: unknown): err is NodeJS.ErrnoException {\n return err instanceof Error && 'code' in err\n}\n"],"names":["existsSync","mkdirSync","readdirSync","readFileSync","unlinkSync","watch","writeFileSync","join","coreAppManifestSchema","getSanityDataDir","studioManifestSchema","subdebug","z","TileInterfaceMetadataSchema","ViewPlacementMetadataSchema","canonicalizeWatchDir","getProcessStartTime","isOurProcess","devDebug","REGISTRY_VERSION","ownStartedAt","process","pid","Date","toISOString","interfaceBaseFields","id","string","moduleId","name","src","title","version","optional","devServerInterfaceSchema","union","discriminatedUnion","object","metadata","nullable","surface","literal","null","type","devServerManifestSchema","configs","array","appType","fields","public","boolean","moduleName","host","interfaces","manifest","manifestUpdatedAt","number","port","projectId","reference","startedAt","enum","workDir","isConfigOnlyServer","server","Boolean","length","getRegistryDir","exitCleanups","Set","exitListenerInstalled","runExitCleanups","cleanup","runRegistryExitCleanupForTesting","unlinkOnProcessExit","filePath","ownedByUs","add","once","delete","registerDevServer","registryDir","recursive","current","JSON","stringify","released","detachExitCleanup","release","update","patch","getRegisteredServers","files","filter","f","endsWith","servers","file","raw","parse","data","success","safeParse","push","watchRegistry","callback","watchDir","debounceTimer","notify","clearTimeout","setTimeout","watcher","close","workbenchLockSchema","readWorkbenchLock","lockPath","contents","undefined","parseLockContents","pruneWorkbenchLock","acquireWorkbenchLock","info","retries","lockData","flag","disk","updatePort","err","Error","message","String","isNodeError","code","existing"],"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
|