@sanity/workbench-cli 2.0.2 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/_exports/_internal_render.d.ts +12 -0
- package/dist/_exports/_internal_render.js +3 -0
- package/dist/_exports/_internal_render.js.map +1 -0
- package/dist/_exports/build.d.ts +4 -4
- package/dist/_exports/build.js +2 -1
- package/dist/_exports/build.js.map +1 -1
- package/dist/_exports/defineApp-DldjQwq6.d.ts +375 -0
- package/dist/_exports/deploy.d.ts +10 -9
- package/dist/_exports/deploy.js +1 -0
- package/dist/_exports/deploy.js.map +1 -1
- package/dist/_exports/dev.d.ts +1 -1
- package/dist/_exports/index.d.ts +23 -16
- package/dist/_exports/index.js +2 -1
- package/dist/_exports/index.js.map +1 -1
- package/dist/_exports/preview.d.ts +1 -1
- package/dist/_exports/{registry-DI7hnTof.d.ts → registry-HE_dEHFa.d.ts} +38 -15
- package/dist/_exports/resolveWorkbenchConfig-6iznHbED.d.ts +60 -0
- package/dist/_exports/{summarizeInterfaces-DBGiAwNT.d.ts → summarizeInterfaces-CjkeWwkY.d.ts} +20 -17
- package/dist/_exports/undeploy.d.ts +12 -9
- package/dist/actions/build/artifact.js +1 -1
- package/dist/actions/build/artifact.js.map +1 -1
- package/dist/actions/build/views/artifact.js +1 -1
- package/dist/actions/build/views/artifact.js.map +1 -1
- package/dist/actions/build/vite/optimize-deps.js +3 -3
- package/dist/actions/build/vite/optimize-deps.js.map +1 -1
- package/dist/actions/build/vite/plugins/plugin-module-federation.js +8 -17
- package/dist/actions/build/vite/plugins/plugin-module-federation.js.map +1 -1
- package/dist/actions/build/vite/plugins/plugin-sanity-federation-runtime.js +1 -1
- package/dist/actions/build/vite/plugins/plugin-sanity-federation-runtime.js.map +1 -1
- package/dist/actions/build/vite/workbench-vite-plugins.js +1 -1
- package/dist/actions/build/vite/workbench-vite-plugins.js.map +1 -1
- package/dist/actions/deploy/deployPayload.js +0 -3
- package/dist/actions/deploy/deployPayload.js.map +1 -1
- package/dist/actions/deploy/deployWorkbenchApp.js +46 -4
- package/dist/actions/deploy/deployWorkbenchApp.js.map +1 -1
- package/dist/actions/deploy/getWorkbench.js +4 -5
- package/dist/actions/deploy/getWorkbench.js.map +1 -1
- package/dist/actions/deploy/summarizeInterfaces.js +14 -10
- package/dist/actions/deploy/summarizeInterfaces.js.map +1 -1
- package/dist/actions/deploy/viewDeployment.js +17 -5
- package/dist/actions/deploy/viewDeployment.js.map +1 -1
- package/dist/actions/dev/deriveConfigs.js +9 -6
- package/dist/actions/dev/deriveConfigs.js.map +1 -1
- package/dist/actions/dev/exposesSetId.js +2 -2
- package/dist/actions/dev/exposesSetId.js.map +1 -1
- package/dist/actions/dev/registry.js +31 -25
- package/dist/actions/dev/registry.js.map +1 -1
- package/dist/actions/dev/startDevManifestWatcher.js.map +1 -1
- package/dist/actions/dev/startDevServerRegistration.js +35 -60
- package/dist/actions/dev/startDevServerRegistration.js.map +1 -1
- package/dist/actions/dev/startWorkbenchDevServer.js +15 -33
- package/dist/actions/dev/startWorkbenchDevServer.js.map +1 -1
- package/dist/actions/dev/writeWorkbenchRuntime.js +2 -2
- package/dist/actions/dev/writeWorkbenchRuntime.js.map +1 -1
- package/dist/actions/preview/serveBuiltApplication.js +1 -1
- package/dist/actions/preview/serveBuiltApplication.js.map +1 -1
- package/dist/actions/preview/startWorkbenchPreview.js +1 -1
- package/dist/actions/preview/startWorkbenchPreview.js.map +1 -1
- package/dist/actions/undeploy/workbenchUndeployAdapter.js +24 -33
- package/dist/actions/undeploy/workbenchUndeployAdapter.js.map +1 -1
- package/dist/appId.js +13 -9
- package/dist/appId.js.map +1 -1
- package/dist/applicationReference.js +12 -0
- package/dist/applicationReference.js.map +1 -0
- package/dist/contract.js +69 -31
- package/dist/contract.js.map +1 -1
- package/dist/defineApp.js +53 -63
- package/dist/defineApp.js.map +1 -1
- package/dist/defineView.js +3 -3
- package/dist/defineView.js.map +1 -1
- package/dist/deriveInterfaces.js +45 -19
- package/dist/deriveInterfaces.js.map +1 -1
- package/dist/renderDashboard.js +63 -0
- package/dist/renderDashboard.js.map +1 -0
- package/dist/resolveWorkbenchApp.js +13 -9
- package/dist/resolveWorkbenchApp.js.map +1 -1
- package/dist/resolveWorkbenchConfig.js +30 -0
- package/dist/resolveWorkbenchConfig.js.map +1 -0
- package/dist/services/applications.js +7 -1
- package/dist/services/applications.js.map +1 -1
- package/package.json +12 -6
- package/dist/_exports/contract-DyG11fQ7.d.ts +0 -45
- package/dist/_exports/defineApp-r_CmoZfS.d.ts +0 -169
- package/dist/_exports/resolveWorkbenchApp-DqFqET9E.d.ts +0 -41
|
@@ -4,6 +4,51 @@ import { spinner } from '@sanity/cli-core/ux';
|
|
|
4
4
|
import { pack } from 'tar-fs';
|
|
5
5
|
import { deriveInterfaces } from '../../deriveInterfaces.js';
|
|
6
6
|
import { createApplication, createDeployment, deleteApplication, updateApplication } from '../../services/applications.js';
|
|
7
|
+
function toBrettInterface(iface, version) {
|
|
8
|
+
const { id: _id, src: _src, ...declaration } = iface;
|
|
9
|
+
if ('type' in declaration) return {
|
|
10
|
+
...declaration,
|
|
11
|
+
version
|
|
12
|
+
};
|
|
13
|
+
switch(declaration.surface){
|
|
14
|
+
case 'app':
|
|
15
|
+
{
|
|
16
|
+
const { surface, ...view } = declaration;
|
|
17
|
+
return {
|
|
18
|
+
...view,
|
|
19
|
+
type: surface,
|
|
20
|
+
version
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
case 'asset_source':
|
|
24
|
+
{
|
|
25
|
+
const { surface, ...view } = declaration;
|
|
26
|
+
return {
|
|
27
|
+
...view,
|
|
28
|
+
type: surface,
|
|
29
|
+
version
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
case 'panel':
|
|
33
|
+
{
|
|
34
|
+
const { surface, ...view } = declaration;
|
|
35
|
+
return {
|
|
36
|
+
...view,
|
|
37
|
+
type: surface,
|
|
38
|
+
version
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
case 'tile':
|
|
42
|
+
{
|
|
43
|
+
const { surface, ...view } = declaration;
|
|
44
|
+
return {
|
|
45
|
+
...view,
|
|
46
|
+
type: surface,
|
|
47
|
+
version
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
7
52
|
/**
|
|
8
53
|
* Create a coreApp record (no deployment), so the CLI can build with its id
|
|
9
54
|
* before shipping the first deployment. First deploy only.
|
|
@@ -71,10 +116,7 @@ import { createApplication, createDeployment, deleteApplication, updateApplicati
|
|
|
71
116
|
interfaces: deriveInterfaces(app, {
|
|
72
117
|
appTitle: title,
|
|
73
118
|
isApp
|
|
74
|
-
}).map((
|
|
75
|
-
...iface,
|
|
76
|
-
version
|
|
77
|
-
})),
|
|
119
|
+
}).map((iface)=>toBrettInterface(iface, version)),
|
|
78
120
|
isAutoUpdating,
|
|
79
121
|
tarball,
|
|
80
122
|
version,
|
|
@@ -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 {pack} from 'tar-fs'\n\nimport {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\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
|
|
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 {pack} from 'tar-fs'\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 'app': {\n const {surface, ...view} = declaration\n return {...view, type: surface, version}\n }\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 }\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 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 = pack(dirname(sourceDir), {entries: [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","pack","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","entries","pipe","interfaces","appTitle","map","clear"],"mappings":"AAAA,SAAQA,QAAQ,EAAEC,OAAO,QAAO,YAAW;AAC3C,SAAQC,UAAU,QAAO,YAAW;AAGpC,SAAQC,OAAO,QAAO,sBAAqB;AAC3C,SAAQC,IAAI,QAAO,SAAQ;AAE3B,SAA+BC,gBAAgB,QAAO,4BAA2B;AACjF,SAKEC,iBAAiB,EACjBC,gBAAgB,EAChBC,iBAAiB,EACjBC,iBAAiB,QACZ,iCAAgC;AAWvC,SAASC,iBAAiBC,KAAuB,EAAEC,OAAe;IAChE,MAAM,EAACC,IAAIC,GAAG,EAAEC,KAAKC,IAAI,EAAE,GAAGC,aAAY,GAAGN;IAC7C,IAAI,UAAUM,aAAa,OAAO;QAAC,GAAGA,WAAW;QAAEL;IAAO;IAE1D,OAAQK,YAAYC,OAAO;QACzB,KAAK;YAAO;gBACV,MAAM,EAACA,OAAO,EAAE,GAAGC,MAAK,GAAGF;gBAC3B,OAAO;oBAAC,GAAGE,IAAI;oBAAEC,MAAMF;oBAASN;gBAAO;YACzC;QACA,KAAK;YAAgB;gBACnB,MAAM,EAACM,OAAO,EAAE,GAAGC,MAAK,GAAGF;gBAC3B,OAAO;oBAAC,GAAGE,IAAI;oBAAEC,MAAMF;oBAASN;gBAAO;YACzC;QACA,KAAK;YAAS;gBACZ,MAAM,EAACM,OAAO,EAAE,GAAGC,MAAK,GAAGF;gBAC3B,OAAO;oBAAC,GAAGE,IAAI;oBAAEC,MAAMF;oBAASN;gBAAO;YACzC;QACA,KAAK;YAAQ;gBACX,MAAM,EAACM,OAAO,EAAE,GAAGC,MAAK,GAAGF;gBAC3B,OAAO;oBAAC,GAAGE,IAAI;oBAAEC,MAAMF;oBAASN;gBAAO;YACzC;IACF;AACF;AAEA;;;;CAIC,GACD,OAAO,eAAeS,cAAcC,OAMnC;IACC,MAAMC,OAAOpB,QAAQ,2BAA2BqB,KAAK;IACrD,IAAI;QACF,MAAMC,cAAc,MAAMnB,kBAAkB;YAAC,GAAGgB,OAAO;YAAEF,MAAM;QAAS;QACxEG,KAAKG,OAAO;QACZ,OAAO;YAACD;YAAaE,UAAU,IAAMnB,kBAAkBiB,YAAYZ,EAAE;QAAC;IACxE,EAAE,OAAOe,OAAO;QACdL,KAAKM,IAAI;QACT,MAAMD;IACR;AACF;AAEA;;;CAGC,GACD,OAAO,eAAeE,aAAaR,OAOlC;IACC,MAAMC,OAAOpB,QAAQ,sBAAsBqB,KAAK;IAChD,IAAI;QACF,MAAMC,cAAc,MAAMnB,kBAAkB;YAAC,GAAGgB,OAAO;YAAEF,MAAM;QAAQ;QACvEG,KAAKG,OAAO;QACZ,OAAO;YAACD;YAAaE,UAAU,IAAMnB,kBAAkBiB,YAAYZ,EAAE;QAAC;IACxE,EAAE,OAAOe,OAAO;QACdL,KAAKM,IAAI;QACT,MAAMD;IACR;AACF;AAEA;;;;;;;;;;CAUC,GACD,OAAO,eAAeG,mBAAmBT,OAcxC;IACC,MAAM,EACJU,MAAM,EACNC,GAAG,EACHC,aAAa,EACbC,IAAI,EACJC,KAAK,EACLC,cAAc,EACdC,QAAQ,cAAc,EACtBC,UAAU,EACVC,SAAS,EACTC,KAAK,EACL7B,OAAO,EACP8B,UAAU,EACVC,UAAU,EACX,GAAGrB;IACJ,MAAMsB,UAAUxC,KAAKH,QAAQuC,YAAY;QAACK,SAAS;YAAC7C,SAASwC;SAAW;IAAA,GAAGM,IAAI,CAAC5C;IAEhF,MAAMqB,OAAOpB,QAAQmC,OAAOd,KAAK;IACjC,IAAI;QACF,MAAMjB,iBAAiB;YACrByB;YACAE;YACA,+EAA+E;YAC/Ea,YAAY1C,iBAAiB4B,KAAK;gBAACe,UAAUP;gBAAOL;YAAK,GAAGa,GAAG,CAAC,CAACtC,QAC/DD,iBAAiBC,OAAOC;YAE1ByB;YACAO;YACAhC;YACA+B;QACF;QACAJ;QACA,MAAM9B,kBAAkByB,eAAe;YACrCO;YACA,GAAIN,OAAO;gBAACA;YAAI,IAAI,CAAC,CAAC;YACtB,GAAIO,aAAa;gBAACA;YAAU,IAAI,CAAC,CAAC;QACpC;QACAnB,KAAKG,OAAO;IACd,EAAE,OAAOE,OAAO;QACdL,KAAK2B,KAAK;QACV,MAAMtB;IACR;AACF"}
|
|
@@ -7,14 +7,13 @@ import { buildViewDeploymentPayload } from './viewDeployment.js';
|
|
|
7
7
|
export function getWorkbench(cliConfig) {
|
|
8
8
|
const app = resolveWorkbenchApp(cliConfig);
|
|
9
9
|
if (!app) return null;
|
|
10
|
-
const {
|
|
10
|
+
const { entry, views, webWorkers } = app;
|
|
11
11
|
return {
|
|
12
12
|
...app,
|
|
13
|
-
|
|
14
|
-
hasInterfaces: !!entry || views.length > 0 || services.length > 0,
|
|
13
|
+
hasInterfaces: !!entry || views.length > 0 || webWorkers.length > 0,
|
|
15
14
|
assertDeployable () {
|
|
16
|
-
if (!entry && views.length === 0 &&
|
|
17
|
-
throw new Error('Nothing to deploy: the app declares no entry, views
|
|
15
|
+
if (!entry && views.length === 0 && webWorkers.length === 0) {
|
|
16
|
+
throw new Error('Nothing to deploy: the app declares no entry, views or web workers. ' + 'Add at least one to the app config.');
|
|
18
17
|
}
|
|
19
18
|
},
|
|
20
19
|
buildViewDeploymentPayload (applicationId) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/deploy/getWorkbench.ts"],"sourcesContent":["// The deploy command's view of a workbench app: the resolved interfaces plus\n// the deploy-time guards that need the app's declarations. `sanity deploy` calls\n// `getWorkbench(config)` once and either gets `null` (plain project — normal\n// deploy) or an object it asks to validate the app before shipping.\n\nimport {type CliConfig} from '@sanity/cli-core'\n\nimport {type ResolvedWorkbenchApp, resolveWorkbenchApp} from '../../resolveWorkbenchApp.js'\nimport {buildViewDeploymentPayload, type ViewDeploymentPayload} from './viewDeployment.js'\n\nexport interface DeployableWorkbenchApp extends ResolvedWorkbenchApp {\n /**\n * Throws when the app exposes nothing (no entry, view,
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/deploy/getWorkbench.ts"],"sourcesContent":["// The deploy command's view of a workbench app: the resolved interfaces plus\n// the deploy-time guards that need the app's declarations. `sanity deploy` calls\n// `getWorkbench(config)` once and either gets `null` (plain project — normal\n// deploy) or an object it asks to validate the app before shipping.\n\nimport {type CliConfig} from '@sanity/cli-core'\n\nimport {type ResolvedWorkbenchApp, resolveWorkbenchApp} from '../../resolveWorkbenchApp.js'\nimport {buildViewDeploymentPayload, type ViewDeploymentPayload} from './viewDeployment.js'\n\nexport interface DeployableWorkbenchApp extends ResolvedWorkbenchApp {\n /**\n * Throws when the app exposes nothing (no entry, view, or service) — the\n * remote would have nothing to load. Gated before any prompt or API call.\n */\n assertDeployable(): void\n /**\n * Validates the app's declared views into the application-service payload.\n * Throws when a view declaration is malformed.\n */\n buildViewDeploymentPayload(applicationId: string): ViewDeploymentPayload\n /** Declares something to host as an application — an entry, view, or service. */\n hasInterfaces: boolean\n}\n\nexport function getWorkbench(\n cliConfig: CliConfig | null | undefined,\n): DeployableWorkbenchApp | null {\n const app = resolveWorkbenchApp(cliConfig)\n if (!app) return null\n\n const {entry, views, webWorkers} = app\n\n return {\n ...app,\n\n hasInterfaces: !!entry || views.length > 0 || webWorkers.length > 0,\n\n assertDeployable() {\n if (!entry && views.length === 0 && webWorkers.length === 0) {\n throw new Error(\n 'Nothing to deploy: the app declares no entry, views or web workers. ' +\n 'Add at least one to the app config.',\n )\n }\n },\n\n buildViewDeploymentPayload(applicationId) {\n return buildViewDeploymentPayload({applicationId, views})\n },\n }\n}\n"],"names":["resolveWorkbenchApp","buildViewDeploymentPayload","getWorkbench","cliConfig","app","entry","views","webWorkers","hasInterfaces","length","assertDeployable","Error","applicationId"],"mappings":"AAAA,6EAA6E;AAC7E,iFAAiF;AACjF,6EAA6E;AAC7E,oEAAoE;AAIpE,SAAmCA,mBAAmB,QAAO,+BAA8B;AAC3F,SAAQC,0BAA0B,QAAmC,sBAAqB;AAiB1F,OAAO,SAASC,aACdC,SAAuC;IAEvC,MAAMC,MAAMJ,oBAAoBG;IAChC,IAAI,CAACC,KAAK,OAAO;IAEjB,MAAM,EAACC,KAAK,EAAEC,KAAK,EAAEC,UAAU,EAAC,GAAGH;IAEnC,OAAO;QACL,GAAGA,GAAG;QAENI,eAAe,CAAC,CAACH,SAASC,MAAMG,MAAM,GAAG,KAAKF,WAAWE,MAAM,GAAG;QAElEC;YACE,IAAI,CAACL,SAASC,MAAMG,MAAM,KAAK,KAAKF,WAAWE,MAAM,KAAK,GAAG;gBAC3D,MAAM,IAAIE,MACR,yEACE;YAEN;QACF;QAEAV,4BAA2BW,aAAa;YACtC,OAAOX,2BAA2B;gBAACW;gBAAeN;YAAK;QACzD;IACF;AACF"}
|
|
@@ -8,18 +8,22 @@ const label = (item)=>item.title === item.name ? item.name : `${item.title} (${i
|
|
|
8
8
|
/**
|
|
9
9
|
* One report line per non-empty group, alongside the records `--json` reports.
|
|
10
10
|
* @internal
|
|
11
|
-
*/ export function summarizeInterfaces({
|
|
12
|
-
const
|
|
13
|
-
name:
|
|
14
|
-
src:
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
});
|
|
18
|
-
const
|
|
19
|
-
|
|
11
|
+
*/ export function summarizeInterfaces({ views, webWorkers }) {
|
|
12
|
+
const deployedViews = (views ?? []).map((view)=>({
|
|
13
|
+
name: view.name,
|
|
14
|
+
src: view.src,
|
|
15
|
+
surface: view.surface,
|
|
16
|
+
title: view.title
|
|
17
|
+
}));
|
|
18
|
+
const deployedServices = (webWorkers ?? []).map((webWorker)=>({
|
|
19
|
+
name: webWorker.name,
|
|
20
|
+
src: webWorker.src,
|
|
21
|
+
title: webWorker.title,
|
|
22
|
+
type: webWorker.type
|
|
23
|
+
}));
|
|
20
24
|
const lines = [];
|
|
21
25
|
if (deployedViews.length > 0) lines.push(summarizeGroup('Views', deployedViews));
|
|
22
|
-
if (deployedServices.length > 0) lines.push(summarizeGroup('
|
|
26
|
+
if (deployedServices.length > 0) lines.push(summarizeGroup('Web workers', deployedServices));
|
|
23
27
|
return {
|
|
24
28
|
lines,
|
|
25
29
|
services: deployedServices,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/deploy/summarizeInterfaces.ts"],"sourcesContent":["import {type WorkbenchExposes} from '../../resolveWorkbenchApp.js'\n\n/** A view
|
|
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(\n (view): DeployedView => ({\n name: view.name,\n src: view.src,\n surface: view.surface,\n title: view.title,\n }),\n )\n const deployedServices = (webWorkers ?? []).map(\n (webWorker): DeployedWebWorker => ({\n name: webWorker.name,\n src: webWorker.src,\n title: webWorker.title,\n type: webWorker.type,\n }),\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,CACrC,CAACO,OAAwB,CAAA;YACvBX,MAAMW,KAAKX,IAAI;YACfK,KAAKM,KAAKN,GAAG;YACbO,SAASD,KAAKC,OAAO;YACrBb,OAAOY,KAAKZ,KAAK;QACnB,CAAA;IAEF,MAAMc,mBAAmB,AAACJ,CAAAA,cAAc,EAAE,AAAD,EAAGL,GAAG,CAC7C,CAACU,YAAkC,CAAA;YACjCd,MAAMc,UAAUd,IAAI;YACpBK,KAAKS,UAAUT,GAAG;YAClBN,OAAOe,UAAUf,KAAK;YACtBgB,MAAMD,UAAUC,IAAI;QACtB,CAAA;IAGF,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"}
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import { z } from 'zod/mini';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
const viewDeclarationSchema = z.looseObject({
|
|
3
|
+
name: z.string().check(z.regex(/^[a-zA-Z0-9_-]+$/, 'View `name` must match /^[a-zA-Z0-9_-]+$/')),
|
|
4
|
+
src: z.string(),
|
|
5
|
+
surface: z.enum([
|
|
6
|
+
'app',
|
|
7
|
+
'panel',
|
|
8
|
+
'asset_source',
|
|
9
|
+
'tile'
|
|
10
|
+
])
|
|
11
|
+
});
|
|
12
|
+
/** A view record as persisted to the application service. */ const viewRecordSchema = z.looseObject({
|
|
6
13
|
name: z.string().check(z.regex(/^[a-zA-Z0-9_-]+$/, 'View `name` must match /^[a-zA-Z0-9_-]+$/')),
|
|
7
14
|
src: z.string(),
|
|
8
15
|
type: z.enum([
|
|
16
|
+
'app',
|
|
9
17
|
'panel',
|
|
10
18
|
'asset_source',
|
|
11
19
|
'tile'
|
|
@@ -25,9 +33,13 @@ import { z } from 'zod/mini';
|
|
|
25
33
|
* Validates an app's declared views into the application-service payload.
|
|
26
34
|
* Throws (via Zod) when a view declaration is malformed.
|
|
27
35
|
*/ export function buildViewDeploymentPayload(input) {
|
|
36
|
+
const views = z.array(viewDeclarationSchema).parse(input.views ?? []);
|
|
28
37
|
return viewDeploymentPayloadSchema.parse({
|
|
29
38
|
applicationId: input.applicationId,
|
|
30
|
-
views:
|
|
39
|
+
views: views.map(({ surface, ...view })=>({
|
|
40
|
+
...view,
|
|
41
|
+
type: surface
|
|
42
|
+
}))
|
|
31
43
|
});
|
|
32
44
|
}
|
|
33
45
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/deploy/viewDeployment.ts"],"sourcesContent":["import {z} from 'zod/mini'\n\
|
|
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(['app', '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(['app', '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;QAAO;QAAS;QAAgB;KAAO;AAC1D;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;QAAO;QAAS;QAAgB;KAAO;AACvD;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,5 +1,5 @@
|
|
|
1
1
|
import { MEDIA_LIBRARY_CONFIG_CONTRACT_VERSION } from '../../contract.js';
|
|
2
|
-
import {
|
|
2
|
+
import { resolveWorkbenchConfig } from '../../resolveWorkbenchConfig.js';
|
|
3
3
|
/**
|
|
4
4
|
* The named source files a config's generated module is built from, dispatched
|
|
5
5
|
* per app type — the projection the exposes-set id keys on, so the generic HMR
|
|
@@ -24,13 +24,15 @@ import { isWorkbenchApp, readConfig } from '../../defineApp.js';
|
|
|
24
24
|
* The fields' schema *values* can't serialize — the workbench loads them from
|
|
25
25
|
* the federation module. `src` stays on so the exposes-set id keys on it and a
|
|
26
26
|
* repoint rebuilds. `appType` routes the config to the singleton (no app id to
|
|
27
|
-
* key on)
|
|
27
|
+
* key on) and names the federation `moduleName`, so the alias no longer borrows
|
|
28
|
+
* the app slug. `id` is a content hash of the entry — it fills the
|
|
28
29
|
* installation-config id slot deployed apps get from the applications API,
|
|
29
30
|
* and the workbench keys change detection on it. `version` is a string, like
|
|
30
31
|
* the one Brett returns on a deployed `activeConfig`.
|
|
31
|
-
*/ export async function deriveConfigs(
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
*/ export async function deriveConfigs(cliConfig) {
|
|
33
|
+
// Keyed on the config brand (its target `appType`), not on having fields — an
|
|
34
|
+
// empty config is still a config; dev just has nothing local to load for it.
|
|
35
|
+
const config = resolveWorkbenchConfig(cliConfig);
|
|
34
36
|
if (!config) return [];
|
|
35
37
|
const entry = {
|
|
36
38
|
appType: config.appType,
|
|
@@ -40,7 +42,8 @@ import { isWorkbenchApp, readConfig } from '../../defineApp.js';
|
|
|
40
42
|
src: field.src,
|
|
41
43
|
title: field.title
|
|
42
44
|
})),
|
|
43
|
-
|
|
45
|
+
// A config keys on its target `appType`, not a fabricated slug.
|
|
46
|
+
moduleName: config.appType,
|
|
44
47
|
version: String(MEDIA_LIBRARY_CONFIG_CONTRACT_VERSION)
|
|
45
48
|
};
|
|
46
49
|
return [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/dev/deriveConfigs.ts"],"sourcesContent":["import {type CliConfig} from '@sanity/cli-core'\n\nimport {MEDIA_LIBRARY_CONFIG_CONTRACT_VERSION} from '../../contract.js'\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/dev/deriveConfigs.ts"],"sourcesContent":["import {type CliConfig} from '@sanity/cli-core'\n\nimport {MEDIA_LIBRARY_CONFIG_CONTRACT_VERSION} from '../../contract.js'\nimport {resolveWorkbenchConfig} from '../../resolveWorkbenchConfig.js'\nimport {type DevServerManifest} from './registry.js'\n\n/** One forwarded interface record on the dev-server registry entry. */\nexport type DevServerInterface = NonNullable<DevServerManifest['interfaces']>[number]\n\n/** One forwarded config on the dev-server registry entry. */\nexport type DevServerConfig = NonNullable<DevServerManifest['configs']>[number]\n\n/**\n * The named source files a config's generated module is built from, dispatched\n * per app type — the projection the exposes-set id keys on, so the generic HMR\n * tracker owns none of the per-type shape. Throws on an app type it can't\n * handle, so a new config family has to register its shape here.\n */\nexport function deriveConfigEntries(config: DevServerConfig): {name: string; src: string}[] {\n switch (config.appType) {\n case 'media-library': {\n return config.fields.map((field) => ({name: field.name, src: field.src}))\n }\n default: {\n throw new Error(`Cannot derive entries for unknown config appType: ${config.appType}`)\n }\n }\n}\n\n/**\n * The fields' schema *values* can't serialize — the workbench loads them from\n * the federation module. `src` stays on so the exposes-set id keys on it and a\n * repoint rebuilds. `appType` routes the config to the singleton (no app id to\n * key on) and names the federation `moduleName`, so the alias no longer borrows\n * the app slug. `id` is a content hash of the entry — it fills the\n * installation-config id slot deployed apps get from the applications API,\n * and the workbench keys change detection on it. `version` is a string, like\n * the one Brett returns on a deployed `activeConfig`.\n */\nexport async function deriveConfigs(\n cliConfig: CliConfig | null | undefined,\n): Promise<DevServerConfig[]> {\n // Keyed on the config brand (its target `appType`), not on having fields — an\n // empty config is still a config; dev just has nothing local to load for it.\n const config = resolveWorkbenchConfig(cliConfig)\n if (!config) return []\n const entry = {\n appType: config.appType,\n fields: config.fields.map((field) => ({\n name: field.name,\n public: field.public,\n src: field.src,\n title: field.title,\n })),\n // A config keys on its target `appType`, not a fabricated slug.\n moduleName: config.appType,\n version: String(MEDIA_LIBRARY_CONFIG_CONTRACT_VERSION),\n }\n return [{...entry, id: await contentHash(JSON.stringify(entry))}]\n}\n\n/**\n * SHA-256 of a string, as hex, via the Web Crypto API — available in both Node\n * and the browser. `node:crypto` can't be used: the Vite dev server's dep scan\n * pulls this module into the browser graph.\n */\nasync function contentHash(input: string): Promise<string> {\n // eslint-disable-next-line n/no-unsupported-features/node-builtins -- the Web Crypto global is available on our Node target and in the browser\n const digest = await globalThis.crypto.subtle.digest('SHA-256', new TextEncoder().encode(input))\n return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, '0')).join('')\n}\n"],"names":["MEDIA_LIBRARY_CONFIG_CONTRACT_VERSION","resolveWorkbenchConfig","deriveConfigEntries","config","appType","fields","map","field","name","src","Error","deriveConfigs","cliConfig","entry","public","title","moduleName","version","String","id","contentHash","JSON","stringify","input","digest","globalThis","crypto","subtle","TextEncoder","encode","Array","from","Uint8Array","byte","toString","padStart","join"],"mappings":"AAEA,SAAQA,qCAAqC,QAAO,oBAAmB;AACvE,SAAQC,sBAAsB,QAAO,kCAAiC;AAStE;;;;;CAKC,GACD,OAAO,SAASC,oBAAoBC,MAAuB;IACzD,OAAQA,OAAOC,OAAO;QACpB,KAAK;YAAiB;gBACpB,OAAOD,OAAOE,MAAM,CAACC,GAAG,CAAC,CAACC,QAAW,CAAA;wBAACC,MAAMD,MAAMC,IAAI;wBAAEC,KAAKF,MAAME,GAAG;oBAAA,CAAA;YACxE;QACA;YAAS;gBACP,MAAM,IAAIC,MAAM,CAAC,kDAAkD,EAAEP,OAAOC,OAAO,EAAE;YACvF;IACF;AACF;AAEA;;;;;;;;;CASC,GACD,OAAO,eAAeO,cACpBC,SAAuC;IAEvC,8EAA8E;IAC9E,6EAA6E;IAC7E,MAAMT,SAASF,uBAAuBW;IACtC,IAAI,CAACT,QAAQ,OAAO,EAAE;IACtB,MAAMU,QAAQ;QACZT,SAASD,OAAOC,OAAO;QACvBC,QAAQF,OAAOE,MAAM,CAACC,GAAG,CAAC,CAACC,QAAW,CAAA;gBACpCC,MAAMD,MAAMC,IAAI;gBAChBM,QAAQP,MAAMO,MAAM;gBACpBL,KAAKF,MAAME,GAAG;gBACdM,OAAOR,MAAMQ,KAAK;YACpB,CAAA;QACA,gEAAgE;QAChEC,YAAYb,OAAOC,OAAO;QAC1Ba,SAASC,OAAOlB;IAClB;IACA,OAAO;QAAC;YAAC,GAAGa,KAAK;YAAEM,IAAI,MAAMC,YAAYC,KAAKC,SAAS,CAACT;QAAO;KAAE;AACnE;AAEA;;;;CAIC,GACD,eAAeO,YAAYG,KAAa;IACtC,+IAA+I;IAC/I,MAAMC,SAAS,MAAMC,WAAWC,MAAM,CAACC,MAAM,CAACH,MAAM,CAAC,WAAW,IAAII,cAAcC,MAAM,CAACN;IACzF,OAAOO,MAAMC,IAAI,CAAC,IAAIC,WAAWR,SAAS,CAACS,OAASA,KAAKC,QAAQ,CAAC,IAAIC,QAAQ,CAAC,GAAG,MAAMC,IAAI,CAAC;AAC/F"}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { deriveConfigEntries } from './deriveConfigs.js';
|
|
2
2
|
/**
|
|
3
3
|
* Order-independent id of an app's exposed-module set. Keys each interface by
|
|
4
|
-
* its type, name, and source file, and each config by its module
|
|
4
|
+
* its surface or service type, name, and source file, and each config by its module
|
|
5
5
|
* identity plus one key per entry (a named source file), so the id changes on
|
|
6
6
|
* any rebuild-worthy edit — add, remove, rename, repoint, or gaining/losing the
|
|
7
7
|
* config module — while reordering and HMR content edits keep it stable.
|
|
8
8
|
*/ export function exposesSetId({ configs, interfaces }) {
|
|
9
9
|
const keys = [
|
|
10
10
|
...(interfaces ?? []).map((iface)=>[
|
|
11
|
-
iface.type,
|
|
11
|
+
'surface' in iface ? iface.surface : iface.type,
|
|
12
12
|
iface.name,
|
|
13
13
|
iface.src
|
|
14
14
|
].join('::')),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/dev/exposesSetId.ts"],"sourcesContent":["import {\n deriveConfigEntries,\n type DevServerConfig,\n type DevServerInterface,\n} from './deriveConfigs.js'\nimport {type DevServerManifest} from './registry.js'\n\ninterface ExposeSet {\n configs?: readonly DevServerConfig[] | undefined\n interfaces?: readonly DevServerInterface[] | undefined\n}\n\n/**\n * Order-independent id of an app's exposed-module set. Keys each interface by\n * its type, name, and source file, and each config by its module\n * identity plus one key per entry (a named source file), so the id changes on\n * any rebuild-worthy edit — add, remove, rename, repoint, or gaining/losing the\n * config module — while reordering and HMR content edits keep it stable.\n */\nexport function exposesSetId({configs, interfaces}: ExposeSet): string {\n const keys = [\n ...(interfaces ?? []).map((iface)
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/dev/exposesSetId.ts"],"sourcesContent":["import {\n deriveConfigEntries,\n type DevServerConfig,\n type DevServerInterface,\n} from './deriveConfigs.js'\nimport {type DevServerManifest} from './registry.js'\n\ninterface ExposeSet {\n configs?: readonly DevServerConfig[] | undefined\n interfaces?: readonly DevServerInterface[] | undefined\n}\n\n/**\n * Order-independent id of an app's exposed-module set. Keys each interface by\n * its surface or service type, name, and source file, and each config by its module\n * identity plus one key per entry (a named source file), so the id changes on\n * any rebuild-worthy edit — add, remove, rename, repoint, or gaining/losing the\n * config module — while reordering and HMR content edits keep it stable.\n */\nexport function exposesSetId({configs, interfaces}: ExposeSet): string {\n const keys = [\n ...(interfaces ?? []).map((iface) =>\n ['surface' in iface ? iface.surface : iface.type, iface.name, iface.src].join('::'),\n ),\n ...(configs ?? []).flatMap((config) => [\n ['config', config.appType].join('::'),\n ...deriveConfigEntries(config).map((entry) =>\n ['config', config.appType, entry.name, entry.src].join('::'),\n ),\n ]),\n ]\n if (keys.length === 0) return ''\n return keys.toSorted().join('|')\n}\n\n/**\n * `changed`/`commit` are split so the caller commits only after the rebuild that\n * depends on the new set succeeds — a thrown rebuild retries on the next save.\n */\nexport function trackExposesSet(initial: ExposeSet): {\n changed: (next: ExposeSet) => boolean\n commit: (next: ExposeSet) => void\n} {\n let lastId = exposesSetId(initial)\n return {\n changed: (next) => exposesSetId(next) !== lastId,\n commit: (next) => {\n lastId = exposesSetId(next)\n },\n }\n}\n\nconst serverKey = (server: DevServerManifest): string =>\n `${server.id ?? ''}@${server.host ?? ''}:${server.port}`\n\nconst serverExposesId = (server: DevServerManifest): string =>\n exposesSetId({configs: server.configs, interfaces: server.interfaces})\n\n/**\n * Multi-app counterpart to {@link trackExposesSet}: true when a *known* app's\n * set changed — its remote was rebuilt, so the workbench must full-reload to\n * drop the stale remote-entry. A new/removed app isn't a rebuild.\n */\nexport function createExposesTracker(): {\n hasChanged: (servers: readonly DevServerManifest[]) => boolean\n} {\n let known = new Map<string, string>()\n return {\n hasChanged(servers) {\n const rebuilt = servers.some((server) => {\n const key = serverKey(server)\n return known.has(key) && known.get(key) !== serverExposesId(server)\n })\n known = new Map(servers.map((server) => [serverKey(server), serverExposesId(server)]))\n return rebuilt\n },\n }\n}\n"],"names":["deriveConfigEntries","exposesSetId","configs","interfaces","keys","map","iface","surface","type","name","src","join","flatMap","config","appType","entry","length","toSorted","trackExposesSet","initial","lastId","changed","next","commit","serverKey","server","id","host","port","serverExposesId","createExposesTracker","known","Map","hasChanged","servers","rebuilt","some","key","has","get"],"mappings":"AAAA,SACEA,mBAAmB,QAGd,qBAAoB;AAQ3B;;;;;;CAMC,GACD,OAAO,SAASC,aAAa,EAACC,OAAO,EAAEC,UAAU,EAAY;IAC3D,MAAMC,OAAO;WACR,AAACD,CAAAA,cAAc,EAAE,AAAD,EAAGE,GAAG,CAAC,CAACC,QACzB;gBAAC,aAAaA,QAAQA,MAAMC,OAAO,GAAGD,MAAME,IAAI;gBAAEF,MAAMG,IAAI;gBAAEH,MAAMI,GAAG;aAAC,CAACC,IAAI,CAAC;WAE7E,AAACT,CAAAA,WAAW,EAAE,AAAD,EAAGU,OAAO,CAAC,CAACC,SAAW;gBACrC;oBAAC;oBAAUA,OAAOC,OAAO;iBAAC,CAACH,IAAI,CAAC;mBAC7BX,oBAAoBa,QAAQR,GAAG,CAAC,CAACU,QAClC;wBAAC;wBAAUF,OAAOC,OAAO;wBAAEC,MAAMN,IAAI;wBAAEM,MAAML,GAAG;qBAAC,CAACC,IAAI,CAAC;aAE1D;KACF;IACD,IAAIP,KAAKY,MAAM,KAAK,GAAG,OAAO;IAC9B,OAAOZ,KAAKa,QAAQ,GAAGN,IAAI,CAAC;AAC9B;AAEA;;;CAGC,GACD,OAAO,SAASO,gBAAgBC,OAAkB;IAIhD,IAAIC,SAASnB,aAAakB;IAC1B,OAAO;QACLE,SAAS,CAACC,OAASrB,aAAaqB,UAAUF;QAC1CG,QAAQ,CAACD;YACPF,SAASnB,aAAaqB;QACxB;IACF;AACF;AAEA,MAAME,YAAY,CAACC,SACjB,GAAGA,OAAOC,EAAE,IAAI,GAAG,CAAC,EAAED,OAAOE,IAAI,IAAI,GAAG,CAAC,EAAEF,OAAOG,IAAI,EAAE;AAE1D,MAAMC,kBAAkB,CAACJ,SACvBxB,aAAa;QAACC,SAASuB,OAAOvB,OAAO;QAAEC,YAAYsB,OAAOtB,UAAU;IAAA;AAEtE;;;;CAIC,GACD,OAAO,SAAS2B;IAGd,IAAIC,QAAQ,IAAIC;IAChB,OAAO;QACLC,YAAWC,OAAO;YAChB,MAAMC,UAAUD,QAAQE,IAAI,CAAC,CAACX;gBAC5B,MAAMY,MAAMb,UAAUC;gBACtB,OAAOM,MAAMO,GAAG,CAACD,QAAQN,MAAMQ,GAAG,CAACF,SAASR,gBAAgBJ;YAC9D;YACAM,QAAQ,IAAIC,IAAIE,QAAQ7B,GAAG,CAAC,CAACoB,SAAW;oBAACD,UAAUC;oBAASI,gBAAgBJ;iBAAQ;YACpF,OAAOU;QACT;IACF;AACF"}
|
|
@@ -2,7 +2,7 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, watch, wr
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { coreAppManifestSchema, getSanityDataDir, studioManifestSchema, subdebug } from '@sanity/cli-core';
|
|
4
4
|
import { z } from 'zod/mini';
|
|
5
|
-
import {
|
|
5
|
+
import { TileInterfaceMetadataSchema, ViewPlacementMetadataSchema } from '../../contract.js';
|
|
6
6
|
import { canonicalizeWatchDir } from './canonicalizeWatchDir.js';
|
|
7
7
|
import { getProcessStartTime, isOurProcess } from './processLiveness.js';
|
|
8
8
|
/**
|
|
@@ -24,7 +24,7 @@ import { getProcessStartTime, isOurProcess } from './processLiveness.js';
|
|
|
24
24
|
* `exit` backstop for abrupt exits (`unlinkOnProcessExit`), and a dead-pid prune
|
|
25
25
|
* on read (`isOurProcess`) that clears whatever a crashed process left behind.
|
|
26
26
|
*/ const devDebug = subdebug('dev');
|
|
27
|
-
/** Bump when the manifest/lock shape changes in a breaking way. */ const REGISTRY_VERSION =
|
|
27
|
+
/** Bump when the manifest/lock shape changes in a breaking way. */ const REGISTRY_VERSION = 2;
|
|
28
28
|
/**
|
|
29
29
|
* The current process's start time as reported by the OS, for the `startedAt`
|
|
30
30
|
* that `isOurProcess` checks on re-read. Falls back to now when the OS time is
|
|
@@ -42,29 +42,31 @@ const interfaceBaseFields = {
|
|
|
42
42
|
version: z.optional(z.string())
|
|
43
43
|
};
|
|
44
44
|
/**
|
|
45
|
-
* A forwarded interface
|
|
45
|
+
* A forwarded interface. Kept outside the manifest so
|
|
46
46
|
* the workbench renders local panels and runs workers without a deploy.
|
|
47
|
-
*/ const devServerInterfaceSchema = z.
|
|
48
|
-
z.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
47
|
+
*/ const devServerInterfaceSchema = z.union([
|
|
48
|
+
z.discriminatedUnion('surface', [
|
|
49
|
+
z.object({
|
|
50
|
+
...interfaceBaseFields,
|
|
51
|
+
metadata: z.nullable(ViewPlacementMetadataSchema),
|
|
52
|
+
surface: z.literal('app')
|
|
53
|
+
}),
|
|
54
|
+
z.object({
|
|
55
|
+
...interfaceBaseFields,
|
|
56
|
+
metadata: z.nullable(ViewPlacementMetadataSchema),
|
|
57
|
+
surface: z.literal('panel')
|
|
58
|
+
}),
|
|
59
|
+
z.object({
|
|
60
|
+
...interfaceBaseFields,
|
|
61
|
+
metadata: z.null(),
|
|
62
|
+
surface: z.literal('asset_source')
|
|
63
|
+
}),
|
|
64
|
+
z.object({
|
|
65
|
+
...interfaceBaseFields,
|
|
66
|
+
metadata: TileInterfaceMetadataSchema,
|
|
67
|
+
surface: z.literal('tile')
|
|
68
|
+
})
|
|
69
|
+
]),
|
|
68
70
|
z.object({
|
|
69
71
|
...interfaceBaseFields,
|
|
70
72
|
metadata: z.null(),
|
|
@@ -88,7 +90,7 @@ const devServerManifestSchema = z.object({
|
|
|
88
90
|
// Content hash of the config — the workbench's change-detection key
|
|
89
91
|
// (see deriveConfigs).
|
|
90
92
|
id: z.string(),
|
|
91
|
-
// The app's `
|
|
93
|
+
// The app's `defineApplication` slug — the module-federation alias the
|
|
92
94
|
// workbench loads this config's live values from.
|
|
93
95
|
moduleName: z.optional(z.string()),
|
|
94
96
|
// The version the workbench federates this config's module under —
|
|
@@ -111,9 +113,13 @@ const devServerManifestSchema = z.object({
|
|
|
111
113
|
* on every regeneration so re-writing this registry entry triggers the
|
|
112
114
|
* workbench `watchRegistry` watcher and forces a rebroadcast to clients.
|
|
113
115
|
*/ manifestUpdatedAt: z.optional(z.string()),
|
|
116
|
+
// Stable identity + qualified reference, composed by the CLI (the authority for
|
|
117
|
+
// local apps, which never reach brett) and read straight by the workbench.
|
|
118
|
+
name: z.optional(z.string()),
|
|
114
119
|
pid: z.number(),
|
|
115
120
|
port: z.number(),
|
|
116
121
|
projectId: z.optional(z.string()),
|
|
122
|
+
reference: z.optional(z.string()),
|
|
117
123
|
startedAt: z.string(),
|
|
118
124
|
type: z.enum([
|
|
119
125
|
'coreApp',
|
|
@@ -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 {AppInterfaceMetadataSchema, TileInterfaceMetadataSchema} 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 = 1\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, discriminated on `type`. Kept outside the manifest so\n * the workbench renders local panels and runs workers without a deploy.\n */\nconst devServerInterfaceSchema = z.discriminatedUnion('type', [\n z.object({\n ...interfaceBaseFields,\n metadata: z.nullable(AppInterfaceMetadataSchema),\n type: z.literal('app'),\n }),\n z.object({...interfaceBaseFields, metadata: z.null(), type: z.literal('panel')}),\n z.object({...interfaceBaseFields, metadata: z.null(), type: z.literal('asset_source')}),\n z.object({\n ...interfaceBaseFields,\n metadata: TileInterfaceMetadataSchema,\n type: z.literal('tile'),\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 `unstable_defineApp` 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 pid: z.number(),\n port: z.number(),\n projectId: 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","AppInterfaceMetadataSchema","TileInterfaceMetadataSchema","canonicalizeWatchDir","getProcessStartTime","isOurProcess","devDebug","REGISTRY_VERSION","ownStartedAt","process","pid","Date","toISOString","interfaceBaseFields","id","string","moduleId","name","src","title","version","optional","devServerInterfaceSchema","discriminatedUnion","object","metadata","nullable","type","literal","null","devServerManifestSchema","configs","array","appType","fields","public","boolean","moduleName","host","interfaces","manifest","union","manifestUpdatedAt","number","port","projectId","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('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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"}
|