@sanity/workbench-cli 1.3.0 → 1.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.
Files changed (40) hide show
  1. package/dist/_exports/deploy.d.ts +13 -3
  2. package/dist/_exports/deploy.js +2 -1
  3. package/dist/_exports/deploy.js.map +1 -1
  4. package/dist/_exports/dev.d.ts +3 -2
  5. package/dist/_exports/index.d.ts +3 -2
  6. package/dist/_exports/undeploy.d.ts +248 -0
  7. package/dist/_exports/undeploy.js +3 -0
  8. package/dist/_exports/undeploy.js.map +1 -0
  9. package/dist/actions/build/vite/plugin.js +5 -0
  10. package/dist/actions/build/vite/plugin.js.map +1 -1
  11. package/dist/actions/build/vite/plugins/plugin-sanity-app-id.js +5 -3
  12. package/dist/actions/build/vite/plugins/plugin-sanity-app-id.js.map +1 -1
  13. package/dist/actions/build/vite/plugins/plugin-sanity-environment.js +26 -2
  14. package/dist/actions/build/vite/plugins/plugin-sanity-environment.js.map +1 -1
  15. package/dist/actions/deploy/buildExposes.js +5 -2
  16. package/dist/actions/deploy/buildExposes.js.map +1 -1
  17. package/dist/actions/deploy/checkBuiltOutput.js +4 -3
  18. package/dist/actions/deploy/checkBuiltOutput.js.map +1 -1
  19. package/dist/actions/deploy/deployConfig.js +11 -38
  20. package/dist/actions/deploy/deployConfig.js.map +1 -1
  21. package/dist/actions/deploy/deployWorkbenchApp.js +2 -79
  22. package/dist/actions/deploy/deployWorkbenchApp.js.map +1 -1
  23. package/dist/actions/deploy/getWorkbench.js.map +1 -1
  24. package/dist/actions/dev/deriveInterfaces.js +17 -20
  25. package/dist/actions/dev/deriveInterfaces.js.map +1 -1
  26. package/dist/actions/dev/exposesSetId.js +2 -2
  27. package/dist/actions/dev/exposesSetId.js.map +1 -1
  28. package/dist/actions/dev/registry.js +8 -7
  29. package/dist/actions/dev/registry.js.map +1 -1
  30. package/dist/actions/undeploy/workbenchUndeployAdapter.js +150 -0
  31. package/dist/actions/undeploy/workbenchUndeployAdapter.js.map +1 -0
  32. package/dist/defineView.js.map +1 -1
  33. package/dist/services/apiVersion.js.map +1 -0
  34. package/dist/services/applications.js +100 -0
  35. package/dist/services/applications.js.map +1 -0
  36. package/dist/services/installations.js +63 -0
  37. package/dist/services/installations.js.map +1 -0
  38. package/package.json +6 -2
  39. package/dist/actions/deploy/apiVersion.js.map +0 -1
  40. /package/dist/{actions/deploy → services}/apiVersion.js +0 -0
@@ -1,11 +1,11 @@
1
1
  import { basename, dirname } from 'node:path';
2
- import { PassThrough } from 'node:stream';
3
2
  import { styleText } from 'node:util';
4
3
  import { createGzip } from 'node:zlib';
5
- import { getGlobalCliClient, subdebug } from '@sanity/cli-core';
6
- import FormData from 'form-data';
4
+ import { subdebug } from '@sanity/cli-core';
7
5
  import { pack } from 'tar-fs';
8
- import { APP_WORKBENCH_API_VERSION } from './apiVersion.js';
6
+ import { getWorkbenchUrl } from '../../services/applications.js';
7
+ import { createConfig, resolveSingletonInstallationId } from '../../services/installations.js';
8
+ import { summarizeExposeGroup } from './buildExposes.js';
9
9
  const debug = subdebug('deploy');
10
10
  /**
11
11
  * The org's active installation for an app type, or `undefined` when none is
@@ -31,8 +31,7 @@ const debug = subdebug('deploy');
31
31
  switch(config.appType){
32
32
  case 'media-library':
33
33
  {
34
- const items = config.fields.map((field)=>` ${field.title} (${field.name})`).join('\n');
35
- return `Media library fields:\n${items}`;
34
+ return summarizeExposeGroup('Media library fields', config.fields);
36
35
  }
37
36
  default:
38
37
  {
@@ -46,45 +45,19 @@ const debug = subdebug('deploy');
46
45
  * reaches this mutating step.
47
46
  * @internal
48
47
  */ export async function deployConfig(options) {
49
- const { appType, installationId, output, sourceDir, version } = options;
48
+ const { appType, installationId, organizationId, output, sourceDir, version } = options;
50
49
  const tarball = pack(dirname(sourceDir), {
51
50
  entries: [
52
51
  basename(sourceDir)
53
52
  ]
54
53
  }).pipe(createGzip());
55
- const formData = new FormData();
56
- formData.append('version', version);
57
- formData.append('tarball', tarball, {
58
- contentType: 'application/gzip',
59
- filename: 'installation-config.tar.gz'
60
- });
61
- const client = await getGlobalCliClient({
62
- apiVersion: APP_WORKBENCH_API_VERSION,
63
- requireUser: true
64
- });
65
- await client.request({
66
- body: formData.pipe(new PassThrough()),
67
- headers: formData.getHeaders(),
68
- method: 'POST',
69
- uri: `/installations/${installationId}/configs`
54
+ await createConfig(installationId, {
55
+ tarball,
56
+ version
70
57
  });
71
58
  debug('Deployed config for app type: %s', appType);
72
- output.log(`\nšŸš€ ${styleText('bold', 'Success!')} Config deployed`);
73
- }
74
- /** The org's active singleton installation, matched on its slug. */ async function resolveSingletonInstallationId(organizationId, slug) {
75
- const client = await getGlobalCliClient({
76
- apiVersion: APP_WORKBENCH_API_VERSION,
77
- requireUser: true
78
- });
79
- // `limit=none` returns every installation in one response, no pagination.
80
- const { data } = await client.request({
81
- query: {
82
- limit: 'none',
83
- organizationId
84
- },
85
- uri: '/installations'
86
- });
87
- return data.find((item)=>item.application?.slug === slug)?.id;
59
+ const url = getWorkbenchUrl(organizationId);
60
+ output.log(`\nšŸš€ ${styleText('bold', 'Success!')} Config deployed to ${styleText('cyan', url)}`);
88
61
  }
89
62
 
90
63
  //# sourceMappingURL=deployConfig.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/deploy/deployConfig.ts"],"sourcesContent":["import {basename, dirname} from 'node:path'\nimport {PassThrough} from 'node:stream'\nimport {styleText} from 'node:util'\nimport {createGzip} from 'node:zlib'\n\nimport {getGlobalCliClient, type Output, subdebug} from '@sanity/cli-core'\nimport FormData from 'form-data'\nimport {pack} from 'tar-fs'\n\nimport {APP_WORKBENCH_API_VERSION} from './apiVersion.js'\n\nconst debug = subdebug('deploy')\n\ninterface InstallationListItem {\n id: string\n\n application?: {slug?: string}\n}\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; title: string}[]\n}): string {\n switch (config.appType) {\n case 'media-library': {\n const items = config.fields.map((field) => ` ${field.title} (${field.name})`).join('\\n')\n return `Media library fields:\\n${items}`\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 output: Output\n sourceDir: string\n version: string\n}): Promise<void> {\n const {appType, installationId, output, sourceDir, version} = options\n const tarball = pack(dirname(sourceDir), {entries: [basename(sourceDir)]}).pipe(createGzip())\n const formData = new FormData()\n formData.append('version', version)\n formData.append('tarball', tarball, {\n contentType: 'application/gzip',\n filename: 'installation-config.tar.gz',\n })\n\n const client = await getGlobalCliClient({\n apiVersion: APP_WORKBENCH_API_VERSION,\n requireUser: true,\n })\n await client.request({\n body: formData.pipe(new PassThrough()),\n headers: formData.getHeaders(),\n method: 'POST',\n uri: `/installations/${installationId}/configs`,\n })\n\n debug('Deployed config for app type: %s', appType)\n output.log(`\\nšŸš€ ${styleText('bold', 'Success!')} Config deployed`)\n}\n\n/** The org's active singleton installation, matched on its slug. */\nasync function resolveSingletonInstallationId(\n organizationId: string,\n slug: string,\n): Promise<string | undefined> {\n const client = await getGlobalCliClient({\n apiVersion: APP_WORKBENCH_API_VERSION,\n requireUser: true,\n })\n // `limit=none` returns every installation in one response, no pagination.\n const {data}: {data: InstallationListItem[]} = await client.request({\n query: {limit: 'none', organizationId},\n uri: '/installations',\n })\n return data.find((item) => item.application?.slug === slug)?.id\n}\n"],"names":["basename","dirname","PassThrough","styleText","createGzip","getGlobalCliClient","subdebug","FormData","pack","APP_WORKBENCH_API_VERSION","debug","resolveInstallationId","options","appType","resolveSingletonInstallationId","organizationId","Error","summarizeConfig","config","items","fields","map","field","title","name","join","deployConfig","installationId","output","sourceDir","version","tarball","entries","pipe","formData","append","contentType","filename","client","apiVersion","requireUser","request","body","headers","getHeaders","method","uri","log","slug","data","query","limit","find","item","application","id"],"mappings":"AAAA,SAAQA,QAAQ,EAAEC,OAAO,QAAO,YAAW;AAC3C,SAAQC,WAAW,QAAO,cAAa;AACvC,SAAQC,SAAS,QAAO,YAAW;AACnC,SAAQC,UAAU,QAAO,YAAW;AAEpC,SAAQC,kBAAkB,EAAeC,QAAQ,QAAO,mBAAkB;AAC1E,OAAOC,cAAc,YAAW;AAChC,SAAQC,IAAI,QAAO,SAAQ;AAE3B,SAAQC,yBAAyB,QAAO,kBAAiB;AAEzD,MAAMC,QAAQJ,SAAS;AAQvB;;;;CAIC,GACD,OAAO,eAAeK,sBAAsBC,OAG3C;IACC,OAAQA,QAAQC,OAAO;QACrB,KAAK;YAAiB;gBACpB,OAAOC,+BAA+BF,QAAQG,cAAc,EAAE;YAChE;QACA;YAAS;gBACP,MAAM,IAAIC,MAAM,CAAC,2CAA2C,EAAEJ,QAAQC,OAAO,EAAE;YACjF;IACF;AACF;AAEA;;;;CAIC,GACD,OAAO,SAASI,gBAAgBC,MAG/B;IACC,OAAQA,OAAOL,OAAO;QACpB,KAAK;YAAiB;gBACpB,MAAMM,QAAQD,OAAOE,MAAM,CAACC,GAAG,CAAC,CAACC,QAAU,CAAC,EAAE,EAAEA,MAAMC,KAAK,CAAC,EAAE,EAAED,MAAME,IAAI,CAAC,CAAC,CAAC,EAAEC,IAAI,CAAC;gBACpF,OAAO,CAAC,uBAAuB,EAAEN,OAAO;YAC1C;QACA;YAAS;gBACP,MAAM,IAAIH,MAAM,CAAC,2CAA2C,EAAEE,OAAOL,OAAO,EAAE;YAChF;IACF;AACF;AAEA;;;;;CAKC,GACD,OAAO,eAAea,aAAad,OAMlC;IACC,MAAM,EAACC,OAAO,EAAEc,cAAc,EAAEC,MAAM,EAAEC,SAAS,EAAEC,OAAO,EAAC,GAAGlB;IAC9D,MAAMmB,UAAUvB,KAAKP,QAAQ4B,YAAY;QAACG,SAAS;YAAChC,SAAS6B;SAAW;IAAA,GAAGI,IAAI,CAAC7B;IAChF,MAAM8B,WAAW,IAAI3B;IACrB2B,SAASC,MAAM,CAAC,WAAWL;IAC3BI,SAASC,MAAM,CAAC,WAAWJ,SAAS;QAClCK,aAAa;QACbC,UAAU;IACZ;IAEA,MAAMC,SAAS,MAAMjC,mBAAmB;QACtCkC,YAAY9B;QACZ+B,aAAa;IACf;IACA,MAAMF,OAAOG,OAAO,CAAC;QACnBC,MAAMR,SAASD,IAAI,CAAC,IAAI/B;QACxByC,SAAST,SAASU,UAAU;QAC5BC,QAAQ;QACRC,KAAK,CAAC,eAAe,EAAEnB,eAAe,QAAQ,CAAC;IACjD;IAEAjB,MAAM,oCAAoCG;IAC1Ce,OAAOmB,GAAG,CAAC,CAAC,KAAK,EAAE5C,UAAU,QAAQ,YAAY,gBAAgB,CAAC;AACpE;AAEA,kEAAkE,GAClE,eAAeW,+BACbC,cAAsB,EACtBiC,IAAY;IAEZ,MAAMV,SAAS,MAAMjC,mBAAmB;QACtCkC,YAAY9B;QACZ+B,aAAa;IACf;IACA,0EAA0E;IAC1E,MAAM,EAACS,IAAI,EAAC,GAAmC,MAAMX,OAAOG,OAAO,CAAC;QAClES,OAAO;YAACC,OAAO;YAAQpC;QAAc;QACrC+B,KAAK;IACP;IACA,OAAOG,KAAKG,IAAI,CAAC,CAACC,OAASA,KAAKC,WAAW,EAAEN,SAASA,OAAOO;AAC/D"}
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 {pack} from 'tar-fs'\n\nimport {getWorkbenchUrl} from '../../services/applications.js'\nimport {createConfig, resolveSingletonInstallationId} from '../../services/installations.js'\nimport {summarizeExposeGroup} from './buildExposes.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 summarizeExposeGroup('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 = pack(dirname(sourceDir), {entries: [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","pack","getWorkbenchUrl","createConfig","resolveSingletonInstallationId","summarizeExposeGroup","debug","resolveInstallationId","options","appType","organizationId","Error","summarizeConfig","config","fields","deployConfig","installationId","output","sourceDir","version","tarball","entries","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,IAAI,QAAO,SAAQ;AAE3B,SAAQC,eAAe,QAAO,iCAAgC;AAC9D,SAAQC,YAAY,EAAEC,8BAA8B,QAAO,kCAAiC;AAC5F,SAAQC,oBAAoB,QAAO,oBAAmB;AAEtD,MAAMC,QAAQN,SAAS;AAEvB;;;;CAIC,GACD,OAAO,eAAeO,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,qBAAqB,wBAAwBQ,OAAOC,MAAM;YACnE;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,KAAKJ,QAAQqB,YAAY;QAACG,SAAS;YAACzB,SAASsB;SAAW;IAAA,GAAGI,IAAI,CAACvB;IAChF,MAAMI,aAAaa,gBAAgB;QAACI;QAASD;IAAO;IAEpDb,MAAM,oCAAoCG;IAC1C,MAAMc,MAAMrB,gBAAgBQ;IAC5BO,OAAOO,GAAG,CAAC,CAAC,KAAK,EAAE1B,UAAU,QAAQ,YAAY,oBAAoB,EAAEA,UAAU,QAAQyB,MAAM;AACjG"}
@@ -1,86 +1,9 @@
1
1
  import { basename, dirname } from 'node:path';
2
- import { PassThrough } from 'node:stream';
3
2
  import { createGzip } from 'node:zlib';
4
- import { exitCodes, getGlobalCliClient } from '@sanity/cli-core';
3
+ import { exitCodes } from '@sanity/cli-core';
5
4
  import { spinner } from '@sanity/cli-core/ux';
6
- import FormData from 'form-data';
7
5
  import { pack } from 'tar-fs';
8
- import { APP_WORKBENCH_API_VERSION } from './apiVersion.js';
9
- export async function getApplication(applicationId) {
10
- const client = await getGlobalCliClient({
11
- apiVersion: APP_WORKBENCH_API_VERSION,
12
- requireUser: true
13
- });
14
- try {
15
- return await client.request({
16
- uri: `/applications/${applicationId}`
17
- });
18
- } catch (err) {
19
- if (err?.statusCode === 404) return null;
20
- throw err;
21
- }
22
- }
23
- /** Create an application and its first deployment in one call. */ export async function createApplication(options) {
24
- const { interfaces, isSingleton, organizationId, projectId, slug, tarball, title, type, version, workspaces } = options;
25
- const formData = new FormData();
26
- formData.append('type', type);
27
- formData.append('title', title);
28
- formData.append('organizationId', organizationId);
29
- formData.append('slug', slug);
30
- if (isSingleton !== undefined) formData.append('isSingleton', String(isSingleton));
31
- // Studio config is set once, at create — it's immutable on redeploy.
32
- if (projectId) appendJson(formData, 'config', {
33
- studio: {
34
- projectId
35
- }
36
- });
37
- appendDeploymentParts(formData, {
38
- interfaces,
39
- tarball,
40
- version,
41
- workspaces
42
- });
43
- return request(`/applications`, formData);
44
- }
45
- /** Deploy a new active version to an existing application. */ export async function createDeployment(options) {
46
- const { applicationId, interfaces, isAutoUpdating, tarball, version, workspaces } = options;
47
- const formData = new FormData();
48
- formData.append('isAutoUpdating', isAutoUpdating.toString());
49
- appendDeploymentParts(formData, {
50
- interfaces,
51
- tarball,
52
- version,
53
- workspaces
54
- });
55
- return request(`/applications/${applicationId}/deployments`, formData);
56
- }
57
- function appendDeploymentParts(formData, { interfaces, tarball, version, workspaces }) {
58
- formData.append('version', version);
59
- appendJson(formData, 'interfaces', interfaces);
60
- // Studio-only — the server rejects a workspaces part on non-studio types.
61
- if (workspaces?.length) appendJson(formData, 'workspaces', workspaces);
62
- formData.append('tarball', tarball, {
63
- contentType: 'application/gzip',
64
- filename: 'app.tar.gz'
65
- });
66
- }
67
- /** Structured parts must arrive as JSON so the server parses them. */ function appendJson(formData, name, value) {
68
- formData.append(name, JSON.stringify(value), {
69
- contentType: 'application/json'
70
- });
71
- }
72
- async function request(uri, formData) {
73
- const client = await getGlobalCliClient({
74
- apiVersion: APP_WORKBENCH_API_VERSION,
75
- requireUser: true
76
- });
77
- return client.request({
78
- body: formData.pipe(new PassThrough()),
79
- headers: formData.getHeaders(),
80
- method: 'POST',
81
- uri
82
- });
83
- }
6
+ import { createApplication, createDeployment } from '../../services/applications.js';
84
7
  /**
85
8
  * Deploy a workbench coreApp through Brett: redeploy when `appId` is set,
86
9
  * otherwise create the application at `slug`. Returns the application id for the
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/deploy/deployWorkbenchApp.ts"],"sourcesContent":["import {basename, dirname} from 'node:path'\nimport {PassThrough} from 'node:stream'\nimport {createGzip, type Gzip} from 'node:zlib'\n\nimport {exitCodes, getGlobalCliClient, type Output} from '@sanity/cli-core'\nimport {spinner} from '@sanity/cli-core/ux'\nimport FormData from 'form-data'\nimport {pack} from 'tar-fs'\n\nimport {APP_WORKBENCH_API_VERSION} from './apiVersion.js'\nimport {type BrettInterface} from './buildExposes.js'\n\nexport type ApplicationType = 'coreApp' | 'studio'\n\nexport interface Application {\n id: string\n organizationId: string\n slug: string | null\n title: string\n type: ApplicationType\n}\n\n/** A studio workspace as Brett stores it. */\nexport interface BrettWorkspace {\n dataset: string\n projectId: string\n\n basePath?: string\n icon?: string\n name?: string\n subtitle?: string\n title?: string\n}\n\nexport async function getApplication(applicationId: string): Promise<Application | null> {\n const client = await getGlobalCliClient({\n apiVersion: APP_WORKBENCH_API_VERSION,\n requireUser: true,\n })\n try {\n return await client.request({uri: `/applications/${applicationId}`})\n } catch (err) {\n if ((err as {statusCode?: number})?.statusCode === 404) return null\n throw err\n }\n}\n\n/** Create an application and its first deployment in one call. */\nexport async function createApplication(options: {\n interfaces: readonly BrettInterface[]\n isSingleton?: boolean\n organizationId: string\n projectId?: string\n slug: string\n tarball: Gzip\n title: string\n type: ApplicationType\n version: string\n workspaces?: readonly BrettWorkspace[]\n}): Promise<Application> {\n const {\n interfaces,\n isSingleton,\n organizationId,\n projectId,\n slug,\n tarball,\n title,\n type,\n version,\n workspaces,\n } = options\n const formData = new FormData()\n formData.append('type', type)\n formData.append('title', title)\n formData.append('organizationId', organizationId)\n formData.append('slug', slug)\n if (isSingleton !== undefined) formData.append('isSingleton', String(isSingleton))\n // Studio config is set once, at create — it's immutable on redeploy.\n if (projectId) appendJson(formData, 'config', {studio: {projectId}})\n appendDeploymentParts(formData, {interfaces, tarball, version, workspaces})\n return request(`/applications`, formData)\n}\n\n/** Deploy a new active version to an existing application. */\nexport async function createDeployment(options: {\n applicationId: string\n interfaces: readonly BrettInterface[]\n isAutoUpdating: boolean\n tarball: Gzip\n version: string\n workspaces?: readonly BrettWorkspace[]\n}): Promise<{id: string}> {\n const {applicationId, interfaces, isAutoUpdating, tarball, version, workspaces} = options\n const formData = new FormData()\n formData.append('isAutoUpdating', isAutoUpdating.toString())\n appendDeploymentParts(formData, {interfaces, tarball, version, workspaces})\n return request(`/applications/${applicationId}/deployments`, formData)\n}\n\nfunction appendDeploymentParts(\n formData: FormData,\n {\n interfaces,\n tarball,\n version,\n workspaces,\n }: {\n interfaces: readonly BrettInterface[]\n tarball: Gzip\n version: string\n workspaces?: readonly BrettWorkspace[]\n },\n): void {\n formData.append('version', version)\n appendJson(formData, 'interfaces', interfaces)\n // Studio-only — the server rejects a workspaces part on non-studio types.\n if (workspaces?.length) appendJson(formData, 'workspaces', workspaces)\n formData.append('tarball', tarball, {contentType: 'application/gzip', filename: 'app.tar.gz'})\n}\n\n/** Structured parts must arrive as JSON so the server parses them. */\nfunction appendJson(formData: FormData, name: string, value: unknown): void {\n formData.append(name, JSON.stringify(value), {contentType: 'application/json'})\n}\n\nasync function request<T>(uri: string, formData: FormData): Promise<T> {\n const client = await getGlobalCliClient({\n apiVersion: APP_WORKBENCH_API_VERSION,\n requireUser: true,\n })\n return client.request({\n body: formData.pipe(new PassThrough()),\n headers: formData.getHeaders(),\n method: 'POST',\n uri,\n })\n}\n\n/**\n * Deploy a workbench coreApp through Brett: redeploy when `appId` is set,\n * otherwise create the application at `slug`. Returns the application id for the\n * shell to report.\n * @internal\n */\nexport async function deployCoreApp(options: {\n appId: string | undefined\n interfaces: readonly BrettInterface[]\n isAutoUpdating: boolean\n isSingleton?: boolean\n organizationId: string\n slug: string\n sourceDir: string\n title: string\n version: string\n}): Promise<{applicationId: string}> {\n const {\n appId,\n interfaces,\n isAutoUpdating,\n isSingleton,\n organizationId,\n slug,\n sourceDir,\n title,\n version,\n } = options\n const tarball = pack(dirname(sourceDir), {entries: [basename(sourceDir)]}).pipe(createGzip())\n\n const spin = spinner('Deploying...').start()\n try {\n if (appId) {\n await createDeployment({applicationId: appId, interfaces, isAutoUpdating, tarball, version})\n spin.succeed()\n return {applicationId: appId}\n }\n\n const {id} = await createApplication({\n interfaces,\n isSingleton,\n organizationId,\n slug,\n tarball,\n title,\n type: 'coreApp',\n version,\n })\n spin.succeed()\n return {applicationId: id}\n } catch (error) {\n spin.clear()\n throw error\n }\n}\n\n/**\n * Deploy a workbench studio through Brett: redeploy when `appId` is set,\n * otherwise create the studio at `studioHost`. Returns the application id for\n * the shell to report; a missing `studioHost` on create is a usage error.\n * @internal\n */\nexport async function deployStudio(options: {\n appId: string | undefined\n interfaces: readonly BrettInterface[]\n isAutoUpdating: boolean\n organizationId: string\n output: Output\n projectId: string | undefined\n sourceDir: string\n studioHost: string | undefined\n title: string\n version: string\n workspaces: readonly BrettWorkspace[]\n}): Promise<{applicationId: string}> {\n const {\n appId,\n interfaces,\n isAutoUpdating,\n organizationId,\n output,\n projectId,\n sourceDir,\n studioHost,\n title,\n version,\n workspaces,\n } = options\n const tarball = pack(dirname(sourceDir), {entries: [basename(sourceDir)]}).pipe(createGzip())\n\n const spin = spinner('Deploying to sanity.studio').start()\n try {\n if (appId) {\n await createDeployment({\n applicationId: appId,\n interfaces,\n isAutoUpdating,\n tarball,\n version,\n workspaces,\n })\n spin.succeed()\n return {applicationId: appId}\n }\n\n if (!studioHost) {\n spin.fail()\n return output.error(\n 'No studio hostname configured. Set `studioHost` in sanity.cli.ts to create a studio.',\n {exit: exitCodes.USAGE_ERROR},\n )\n }\n\n const application = await createApplication({\n interfaces,\n organizationId,\n projectId,\n slug: studioHost,\n tarball,\n title,\n type: 'studio',\n version,\n workspaces,\n })\n spin.succeed()\n return {applicationId: application.id}\n } catch (error) {\n spin.fail()\n throw error\n }\n}\n"],"names":["basename","dirname","PassThrough","createGzip","exitCodes","getGlobalCliClient","spinner","FormData","pack","APP_WORKBENCH_API_VERSION","getApplication","applicationId","client","apiVersion","requireUser","request","uri","err","statusCode","createApplication","options","interfaces","isSingleton","organizationId","projectId","slug","tarball","title","type","version","workspaces","formData","append","undefined","String","appendJson","studio","appendDeploymentParts","createDeployment","isAutoUpdating","toString","length","contentType","filename","name","value","JSON","stringify","body","pipe","headers","getHeaders","method","deployCoreApp","appId","sourceDir","entries","spin","start","succeed","id","error","clear","deployStudio","output","studioHost","fail","exit","USAGE_ERROR","application"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/actions/deploy/deployWorkbenchApp.ts"],"sourcesContent":["import {basename, dirname} from 'node:path'\nimport {createGzip} from 'node:zlib'\n\nimport {exitCodes, type Output} from '@sanity/cli-core'\nimport {spinner} from '@sanity/cli-core/ux'\nimport {pack} from 'tar-fs'\n\nimport {\n type BrettInterface,\n type BrettWorkspace,\n createApplication,\n createDeployment,\n} from '../../services/applications.js'\n\n/**\n * Deploy a workbench coreApp through Brett: redeploy when `appId` is set,\n * otherwise create the application at `slug`. Returns the application id for the\n * shell to report.\n * @internal\n */\nexport async function deployCoreApp(options: {\n appId: string | undefined\n interfaces: readonly BrettInterface[]\n isAutoUpdating: boolean\n isSingleton?: boolean\n organizationId: string\n slug: string\n sourceDir: string\n title: string\n version: string\n}): Promise<{applicationId: string}> {\n const {\n appId,\n interfaces,\n isAutoUpdating,\n isSingleton,\n organizationId,\n slug,\n sourceDir,\n title,\n version,\n } = options\n const tarball = pack(dirname(sourceDir), {entries: [basename(sourceDir)]}).pipe(createGzip())\n\n const spin = spinner('Deploying...').start()\n try {\n if (appId) {\n await createDeployment({applicationId: appId, interfaces, isAutoUpdating, tarball, version})\n spin.succeed()\n return {applicationId: appId}\n }\n\n const {id} = await createApplication({\n interfaces,\n isSingleton,\n organizationId,\n slug,\n tarball,\n title,\n type: 'coreApp',\n version,\n })\n spin.succeed()\n return {applicationId: id}\n } catch (error) {\n spin.clear()\n throw error\n }\n}\n\n/**\n * Deploy a workbench studio through Brett: redeploy when `appId` is set,\n * otherwise create the studio at `studioHost`. Returns the application id for\n * the shell to report; a missing `studioHost` on create is a usage error.\n * @internal\n */\nexport async function deployStudio(options: {\n appId: string | undefined\n interfaces: readonly BrettInterface[]\n isAutoUpdating: boolean\n organizationId: string\n output: Output\n projectId: string | undefined\n sourceDir: string\n studioHost: string | undefined\n title: string\n version: string\n workspaces: readonly BrettWorkspace[]\n}): Promise<{applicationId: string}> {\n const {\n appId,\n interfaces,\n isAutoUpdating,\n organizationId,\n output,\n projectId,\n sourceDir,\n studioHost,\n title,\n version,\n workspaces,\n } = options\n const tarball = pack(dirname(sourceDir), {entries: [basename(sourceDir)]}).pipe(createGzip())\n\n const spin = spinner('Deploying to sanity.studio').start()\n try {\n if (appId) {\n await createDeployment({\n applicationId: appId,\n interfaces,\n isAutoUpdating,\n tarball,\n version,\n workspaces,\n })\n spin.succeed()\n return {applicationId: appId}\n }\n\n if (!studioHost) {\n spin.fail()\n return output.error(\n 'No studio hostname configured. Set `studioHost` in sanity.cli.ts to create a studio.',\n {exit: exitCodes.USAGE_ERROR},\n )\n }\n\n const application = await createApplication({\n interfaces,\n organizationId,\n projectId,\n slug: studioHost,\n tarball,\n title,\n type: 'studio',\n version,\n workspaces,\n })\n spin.succeed()\n return {applicationId: application.id}\n } catch (error) {\n spin.fail()\n throw error\n }\n}\n"],"names":["basename","dirname","createGzip","exitCodes","spinner","pack","createApplication","createDeployment","deployCoreApp","options","appId","interfaces","isAutoUpdating","isSingleton","organizationId","slug","sourceDir","title","version","tarball","entries","pipe","spin","start","applicationId","succeed","id","type","error","clear","deployStudio","output","projectId","studioHost","workspaces","fail","exit","USAGE_ERROR","application"],"mappings":"AAAA,SAAQA,QAAQ,EAAEC,OAAO,QAAO,YAAW;AAC3C,SAAQC,UAAU,QAAO,YAAW;AAEpC,SAAQC,SAAS,QAAoB,mBAAkB;AACvD,SAAQC,OAAO,QAAO,sBAAqB;AAC3C,SAAQC,IAAI,QAAO,SAAQ;AAE3B,SAGEC,iBAAiB,EACjBC,gBAAgB,QACX,iCAAgC;AAEvC;;;;;CAKC,GACD,OAAO,eAAeC,cAAcC,OAUnC;IACC,MAAM,EACJC,KAAK,EACLC,UAAU,EACVC,cAAc,EACdC,WAAW,EACXC,cAAc,EACdC,IAAI,EACJC,SAAS,EACTC,KAAK,EACLC,OAAO,EACR,GAAGT;IACJ,MAAMU,UAAUd,KAAKJ,QAAQe,YAAY;QAACI,SAAS;YAACpB,SAASgB;SAAW;IAAA,GAAGK,IAAI,CAACnB;IAEhF,MAAMoB,OAAOlB,QAAQ,gBAAgBmB,KAAK;IAC1C,IAAI;QACF,IAAIb,OAAO;YACT,MAAMH,iBAAiB;gBAACiB,eAAed;gBAAOC;gBAAYC;gBAAgBO;gBAASD;YAAO;YAC1FI,KAAKG,OAAO;YACZ,OAAO;gBAACD,eAAed;YAAK;QAC9B;QAEA,MAAM,EAACgB,EAAE,EAAC,GAAG,MAAMpB,kBAAkB;YACnCK;YACAE;YACAC;YACAC;YACAI;YACAF;YACAU,MAAM;YACNT;QACF;QACAI,KAAKG,OAAO;QACZ,OAAO;YAACD,eAAeE;QAAE;IAC3B,EAAE,OAAOE,OAAO;QACdN,KAAKO,KAAK;QACV,MAAMD;IACR;AACF;AAEA;;;;;CAKC,GACD,OAAO,eAAeE,aAAarB,OAYlC;IACC,MAAM,EACJC,KAAK,EACLC,UAAU,EACVC,cAAc,EACdE,cAAc,EACdiB,MAAM,EACNC,SAAS,EACThB,SAAS,EACTiB,UAAU,EACVhB,KAAK,EACLC,OAAO,EACPgB,UAAU,EACX,GAAGzB;IACJ,MAAMU,UAAUd,KAAKJ,QAAQe,YAAY;QAACI,SAAS;YAACpB,SAASgB;SAAW;IAAA,GAAGK,IAAI,CAACnB;IAEhF,MAAMoB,OAAOlB,QAAQ,8BAA8BmB,KAAK;IACxD,IAAI;QACF,IAAIb,OAAO;YACT,MAAMH,iBAAiB;gBACrBiB,eAAed;gBACfC;gBACAC;gBACAO;gBACAD;gBACAgB;YACF;YACAZ,KAAKG,OAAO;YACZ,OAAO;gBAACD,eAAed;YAAK;QAC9B;QAEA,IAAI,CAACuB,YAAY;YACfX,KAAKa,IAAI;YACT,OAAOJ,OAAOH,KAAK,CACjB,wFACA;gBAACQ,MAAMjC,UAAUkC,WAAW;YAAA;QAEhC;QAEA,MAAMC,cAAc,MAAMhC,kBAAkB;YAC1CK;YACAG;YACAkB;YACAjB,MAAMkB;YACNd;YACAF;YACAU,MAAM;YACNT;YACAgB;QACF;QACAZ,KAAKG,OAAO;QACZ,OAAO;YAACD,eAAec,YAAYZ,EAAE;QAAA;IACvC,EAAE,OAAOE,OAAO;QACdN,KAAKa,IAAI;QACT,MAAMP;IACR;AACF"}
@@ -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\ninterface DeployableWorkbenchApp extends ResolvedWorkbenchApp {\n /**\n * Throws when the app exposes nothing (no entry, view, service, or config) —\n * the 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 /**\n * A singleton (the Media Library) that carries an config — deploy\n * persists the config to the org's installation. Independent of the interfaces,\n * which register regardless; non-singletons never carry a config.\n */\n deploySingletonConfig: boolean\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 {config, entry, isSingleton, services, views} = app\n\n return {\n ...app,\n\n deploySingletonConfig: !!isSingleton && !!config,\n hasInterfaces: !!entry || views.length > 0 || services.length > 0,\n\n assertDeployable() {\n if (!entry && views.length === 0 && services.length === 0 && !config) {\n throw new Error(\n 'Nothing to deploy: the app declares no entry, views, services or config. ' +\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","config","entry","isSingleton","services","views","deploySingletonConfig","hasInterfaces","length","assertDeployable","Error","applicationId"],"mappings":"AAAA,6EAA6E;AAC7E,iFAAiF;AACjF,6EAA6E;AAC7E,oEAAoE;AAIpE,SAAmCA,mBAAmB,QAAO,+BAA8B;AAC3F,SAAQC,0BAA0B,QAAmC,sBAAqB;AAuB1F,OAAO,SAASC,aACdC,SAAuC;IAEvC,MAAMC,MAAMJ,oBAAoBG;IAChC,IAAI,CAACC,KAAK,OAAO;IAEjB,MAAM,EAACC,MAAM,EAAEC,KAAK,EAAEC,WAAW,EAAEC,QAAQ,EAAEC,KAAK,EAAC,GAAGL;IAEtD,OAAO;QACL,GAAGA,GAAG;QAENM,uBAAuB,CAAC,CAACH,eAAe,CAAC,CAACF;QAC1CM,eAAe,CAAC,CAACL,SAASG,MAAMG,MAAM,GAAG,KAAKJ,SAASI,MAAM,GAAG;QAEhEC;YACE,IAAI,CAACP,SAASG,MAAMG,MAAM,KAAK,KAAKJ,SAASI,MAAM,KAAK,KAAK,CAACP,QAAQ;gBACpE,MAAM,IAAIS,MACR,8EACE;YAEN;QACF;QAEAb,4BAA2Bc,aAAa;YACtC,OAAOd,2BAA2B;gBAACc;gBAAeN;YAAK;QACzD;IACF;AACF"}
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, service, or config) —\n * the 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 /**\n * A singleton (the Media Library) that carries an config — deploy\n * persists the config to the org's installation. Independent of the interfaces,\n * which register regardless; non-singletons never carry a config.\n */\n deploySingletonConfig: boolean\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 {config, entry, isSingleton, services, views} = app\n\n return {\n ...app,\n\n deploySingletonConfig: !!isSingleton && !!config,\n hasInterfaces: !!entry || views.length > 0 || services.length > 0,\n\n assertDeployable() {\n if (!entry && views.length === 0 && services.length === 0 && !config) {\n throw new Error(\n 'Nothing to deploy: the app declares no entry, views, services or config. ' +\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","config","entry","isSingleton","services","views","deploySingletonConfig","hasInterfaces","length","assertDeployable","Error","applicationId"],"mappings":"AAAA,6EAA6E;AAC7E,iFAAiF;AACjF,6EAA6E;AAC7E,oEAAoE;AAIpE,SAAmCA,mBAAmB,QAAO,+BAA8B;AAC3F,SAAQC,0BAA0B,QAAmC,sBAAqB;AAuB1F,OAAO,SAASC,aACdC,SAAuC;IAEvC,MAAMC,MAAMJ,oBAAoBG;IAChC,IAAI,CAACC,KAAK,OAAO;IAEjB,MAAM,EAACC,MAAM,EAAEC,KAAK,EAAEC,WAAW,EAAEC,QAAQ,EAAEC,KAAK,EAAC,GAAGL;IAEtD,OAAO;QACL,GAAGA,GAAG;QAENM,uBAAuB,CAAC,CAACH,eAAe,CAAC,CAACF;QAC1CM,eAAe,CAAC,CAACL,SAASG,MAAMG,MAAM,GAAG,KAAKJ,SAASI,MAAM,GAAG;QAEhEC;YACE,IAAI,CAACP,SAASG,MAAMG,MAAM,KAAK,KAAKJ,SAASI,MAAM,KAAK,KAAK,CAACP,QAAQ;gBACpE,MAAM,IAAIS,MACR,8EACE;YAEN;QACF;QAEAb,4BAA2Bc,aAAa;YACtC,OAAOd,2BAA2B;gBAACc;gBAAeN;YAAK;QACzD;IACF;AACF"}
@@ -4,35 +4,32 @@ import { isWorkbenchApp, readConfig } from '../../defineApp.js';
4
4
  /**
5
5
  * Map a workbench app's declarations to the interface records forwarded on its
6
6
  * registry entry: `views` → panels, `services` → workers, `entry` → the
7
- * navigable `app` view (`entry_point` is the raw `src`, not a resolved URL).
8
- * `undefined` for a non-branded app; a studio that declares `entry` is rejected
9
- * (studio app views are not implemented yet). The config is not an
10
- * interface — see {@link deriveConfigs}. `version` is the contract version the
11
- * interface's generated module exports, known before the module runs; the app
7
+ * navigable `app` view. `src` is the raw source, not a resolved URL. `undefined`
8
+ * for a non-branded app; a studio that declares `entry` is rejected (studio app
9
+ * views are not implemented yet). The config is not an interface — see
10
+ * {@link deriveConfigs}. `version` is the module's contract version; the app
12
11
  * view has no versioned contract, so it carries none.
13
12
  */ export function deriveInterfaces(app, options) {
14
13
  if (!isWorkbenchApp(app)) return undefined;
15
14
  if (!options.isApp && app.entry !== undefined) {
16
15
  throw new Error('App views for studios are not implemented yet');
17
16
  }
17
+ const toInterface = ({ name, src, title, type }, version)=>({
18
+ name,
19
+ src,
20
+ title: title ?? name,
21
+ type,
22
+ version
23
+ });
18
24
  return [
19
- ...app.views?.map((view)=>({
20
- entry_point: view.src,
21
- interface_type: view.type,
22
- name: view.name,
23
- version: VIEW_CONTRACT_VERSION
24
- })) ?? [],
25
- ...app.services?.map((service)=>({
26
- entry_point: service.src,
27
- interface_type: service.type,
28
- name: service.name,
29
- version: SERVICE_CONTRACT_VERSION
30
- })) ?? [],
25
+ ...(app.views ?? []).map((view)=>toInterface(view, VIEW_CONTRACT_VERSION)),
26
+ ...(app.services ?? []).map((service)=>toInterface(service, SERVICE_CONTRACT_VERSION)),
31
27
  ...app.entry === undefined ? [] : [
32
28
  {
33
- entry_point: app.entry,
34
- interface_type: 'app',
35
- name: app.name
29
+ name: app.name,
30
+ src: app.entry,
31
+ title: app.title,
32
+ type: 'app'
36
33
  }
37
34
  ]
38
35
  ];
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/dev/deriveInterfaces.ts"],"sourcesContent":["import {hash} from 'node:crypto'\n\nimport {type CliConfig} from '@sanity/cli-core'\n\nimport {\n MEDIA_LIBRARY_CONFIG_CONTRACT_VERSION,\n SERVICE_CONTRACT_VERSION,\n VIEW_CONTRACT_VERSION,\n} from '../../contract.js'\nimport {isWorkbenchApp, readConfig} from '../../defineApp.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 * Map a workbench app's declarations to the interface records forwarded on its\n * registry entry: `views` → panels, `services` → workers, `entry` → the\n * navigable `app` view (`entry_point` is the raw `src`, not a resolved URL).\n * `undefined` for a non-branded app; a studio that declares `entry` is rejected\n * (studio app views are not implemented yet). The config is not an\n * interface — see {@link deriveConfigs}. `version` is the contract version the\n * interface's generated module exports, known before the module runs; the app\n * view has no versioned contract, so it carries none.\n */\nexport function deriveInterfaces(\n app: CliConfig['app'],\n options: {isApp: boolean},\n): DevServerInterface[] | undefined {\n if (!isWorkbenchApp(app)) return undefined\n\n if (!options.isApp && app.entry !== undefined) {\n throw new Error('App views for studios are not implemented yet')\n }\n\n return [\n ...(app.views?.map((view) => ({\n entry_point: view.src,\n interface_type: view.type,\n name: view.name,\n version: VIEW_CONTRACT_VERSION,\n })) ?? []),\n ...(app.services?.map((service) => ({\n entry_point: service.src,\n interface_type: service.type,\n name: service.name,\n version: SERVICE_CONTRACT_VERSION,\n })) ?? []),\n ...(app.entry === undefined\n ? []\n : [{entry_point: app.entry, interface_type: 'app' as const, name: app.name}]),\n ]\n}\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). `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 the config\n * contract version the generated module exports, known before the module runs.\n */\nexport function deriveConfigs(app: CliConfig['app']): DevServerConfig[] {\n if (!isWorkbenchApp(app)) return []\n const config = readConfig(app)\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 moduleName: app.name,\n version: MEDIA_LIBRARY_CONFIG_CONTRACT_VERSION,\n }\n return [{...entry, id: hash('sha1', JSON.stringify(entry))}]\n}\n"],"names":["hash","MEDIA_LIBRARY_CONFIG_CONTRACT_VERSION","SERVICE_CONTRACT_VERSION","VIEW_CONTRACT_VERSION","isWorkbenchApp","readConfig","deriveInterfaces","app","options","undefined","isApp","entry","Error","views","map","view","entry_point","src","interface_type","type","name","version","services","service","deriveConfigEntries","config","appType","fields","field","deriveConfigs","public","title","moduleName","id","JSON","stringify"],"mappings":"AAAA,SAAQA,IAAI,QAAO,cAAa;AAIhC,SACEC,qCAAqC,EACrCC,wBAAwB,EACxBC,qBAAqB,QAChB,oBAAmB;AAC1B,SAAQC,cAAc,EAAEC,UAAU,QAAO,qBAAoB;AAS7D;;;;;;;;;CASC,GACD,OAAO,SAASC,iBACdC,GAAqB,EACrBC,OAAyB;IAEzB,IAAI,CAACJ,eAAeG,MAAM,OAAOE;IAEjC,IAAI,CAACD,QAAQE,KAAK,IAAIH,IAAII,KAAK,KAAKF,WAAW;QAC7C,MAAM,IAAIG,MAAM;IAClB;IAEA,OAAO;WACDL,IAAIM,KAAK,EAAEC,IAAI,CAACC,OAAU,CAAA;gBAC5BC,aAAaD,KAAKE,GAAG;gBACrBC,gBAAgBH,KAAKI,IAAI;gBACzBC,MAAML,KAAKK,IAAI;gBACfC,SAASlB;YACX,CAAA,MAAO,EAAE;WACLI,IAAIe,QAAQ,EAAER,IAAI,CAACS,UAAa,CAAA;gBAClCP,aAAaO,QAAQN,GAAG;gBACxBC,gBAAgBK,QAAQJ,IAAI;gBAC5BC,MAAMG,QAAQH,IAAI;gBAClBC,SAASnB;YACX,CAAA,MAAO,EAAE;WACLK,IAAII,KAAK,KAAKF,YACd,EAAE,GACF;YAAC;gBAACO,aAAaT,IAAII,KAAK;gBAAEO,gBAAgB;gBAAgBE,MAAMb,IAAIa,IAAI;YAAA;SAAE;KAC/E;AACH;AAEA;;;;;CAKC,GACD,OAAO,SAASI,oBAAoBC,MAAuB;IACzD,OAAQA,OAAOC,OAAO;QACpB,KAAK;YAAiB;gBACpB,OAAOD,OAAOE,MAAM,CAACb,GAAG,CAAC,CAACc,QAAW,CAAA;wBAACR,MAAMQ,MAAMR,IAAI;wBAAEH,KAAKW,MAAMX,GAAG;oBAAA,CAAA;YACxE;QACA;YAAS;gBACP,MAAM,IAAIL,MAAM,CAAC,kDAAkD,EAAEa,OAAOC,OAAO,EAAE;YACvF;IACF;AACF;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASG,cAActB,GAAqB;IACjD,IAAI,CAACH,eAAeG,MAAM,OAAO,EAAE;IACnC,MAAMkB,SAASpB,WAAWE;IAC1B,IAAI,CAACkB,QAAQ,OAAO,EAAE;IACtB,MAAMd,QAAQ;QACZe,SAASD,OAAOC,OAAO;QACvBC,QAAQF,OAAOE,MAAM,CAACb,GAAG,CAAC,CAACc,QAAW,CAAA;gBACpCR,MAAMQ,MAAMR,IAAI;gBAChBU,QAAQF,MAAME,MAAM;gBACpBb,KAAKW,MAAMX,GAAG;gBACdc,OAAOH,MAAMG,KAAK;YACpB,CAAA;QACAC,YAAYzB,IAAIa,IAAI;QACpBC,SAASpB;IACX;IACA,OAAO;QAAC;YAAC,GAAGU,KAAK;YAAEsB,IAAIjC,KAAK,QAAQkC,KAAKC,SAAS,CAACxB;QAAO;KAAE;AAC9D"}
1
+ {"version":3,"sources":["../../../src/actions/dev/deriveInterfaces.ts"],"sourcesContent":["import {hash} from 'node:crypto'\n\nimport {type CliConfig} from '@sanity/cli-core'\n\nimport {\n MEDIA_LIBRARY_CONFIG_CONTRACT_VERSION,\n SERVICE_CONTRACT_VERSION,\n VIEW_CONTRACT_VERSION,\n} from '../../contract.js'\nimport {isWorkbenchApp, readConfig} from '../../defineApp.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 * Map a workbench app's declarations to the interface records forwarded on its\n * registry entry: `views` → panels, `services` → workers, `entry` → the\n * navigable `app` view. `src` is the raw source, not a resolved URL. `undefined`\n * for a non-branded app; a studio that declares `entry` is rejected (studio app\n * views are not implemented yet). The config is not an interface — see\n * {@link deriveConfigs}. `version` is the module's contract version; the app\n * view has no versioned contract, so it carries none.\n */\nexport function deriveInterfaces(\n app: CliConfig['app'],\n options: {isApp: boolean},\n): DevServerInterface[] | undefined {\n if (!isWorkbenchApp(app)) return undefined\n\n if (!options.isApp && app.entry !== undefined) {\n throw new Error('App views for studios are not implemented yet')\n }\n\n const toInterface = (\n {name, src, title, type}: {name: string; src: string; title?: string; type: string},\n version: number,\n ): DevServerInterface => ({name, src, title: title ?? name, type, version})\n\n return [\n ...(app.views ?? []).map((view) => toInterface(view, VIEW_CONTRACT_VERSION)),\n ...(app.services ?? []).map((service) => toInterface(service, SERVICE_CONTRACT_VERSION)),\n ...(app.entry === undefined\n ? []\n : [{name: app.name, src: app.entry, title: app.title, type: 'app' as const}]),\n ]\n}\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). `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 the config\n * contract version the generated module exports, known before the module runs.\n */\nexport function deriveConfigs(app: CliConfig['app']): DevServerConfig[] {\n if (!isWorkbenchApp(app)) return []\n const config = readConfig(app)\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 moduleName: app.name,\n version: MEDIA_LIBRARY_CONFIG_CONTRACT_VERSION,\n }\n return [{...entry, id: hash('sha1', JSON.stringify(entry))}]\n}\n"],"names":["hash","MEDIA_LIBRARY_CONFIG_CONTRACT_VERSION","SERVICE_CONTRACT_VERSION","VIEW_CONTRACT_VERSION","isWorkbenchApp","readConfig","deriveInterfaces","app","options","undefined","isApp","entry","Error","toInterface","name","src","title","type","version","views","map","view","services","service","deriveConfigEntries","config","appType","fields","field","deriveConfigs","public","moduleName","id","JSON","stringify"],"mappings":"AAAA,SAAQA,IAAI,QAAO,cAAa;AAIhC,SACEC,qCAAqC,EACrCC,wBAAwB,EACxBC,qBAAqB,QAChB,oBAAmB;AAC1B,SAAQC,cAAc,EAAEC,UAAU,QAAO,qBAAoB;AAS7D;;;;;;;;CAQC,GACD,OAAO,SAASC,iBACdC,GAAqB,EACrBC,OAAyB;IAEzB,IAAI,CAACJ,eAAeG,MAAM,OAAOE;IAEjC,IAAI,CAACD,QAAQE,KAAK,IAAIH,IAAII,KAAK,KAAKF,WAAW;QAC7C,MAAM,IAAIG,MAAM;IAClB;IAEA,MAAMC,cAAc,CAClB,EAACC,IAAI,EAAEC,GAAG,EAAEC,KAAK,EAAEC,IAAI,EAA4D,EACnFC,UACwB,CAAA;YAACJ;YAAMC;YAAKC,OAAOA,SAASF;YAAMG;YAAMC;QAAO,CAAA;IAEzE,OAAO;WACF,AAACX,CAAAA,IAAIY,KAAK,IAAI,EAAE,AAAD,EAAGC,GAAG,CAAC,CAACC,OAASR,YAAYQ,MAAMlB;WAClD,AAACI,CAAAA,IAAIe,QAAQ,IAAI,EAAE,AAAD,EAAGF,GAAG,CAAC,CAACG,UAAYV,YAAYU,SAASrB;WAC1DK,IAAII,KAAK,KAAKF,YACd,EAAE,GACF;YAAC;gBAACK,MAAMP,IAAIO,IAAI;gBAAEC,KAAKR,IAAII,KAAK;gBAAEK,OAAOT,IAAIS,KAAK;gBAAEC,MAAM;YAAc;SAAE;KAC/E;AACH;AAEA;;;;;CAKC,GACD,OAAO,SAASO,oBAAoBC,MAAuB;IACzD,OAAQA,OAAOC,OAAO;QACpB,KAAK;YAAiB;gBACpB,OAAOD,OAAOE,MAAM,CAACP,GAAG,CAAC,CAACQ,QAAW,CAAA;wBAACd,MAAMc,MAAMd,IAAI;wBAAEC,KAAKa,MAAMb,GAAG;oBAAA,CAAA;YACxE;QACA;YAAS;gBACP,MAAM,IAAIH,MAAM,CAAC,kDAAkD,EAAEa,OAAOC,OAAO,EAAE;YACvF;IACF;AACF;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASG,cAActB,GAAqB;IACjD,IAAI,CAACH,eAAeG,MAAM,OAAO,EAAE;IACnC,MAAMkB,SAASpB,WAAWE;IAC1B,IAAI,CAACkB,QAAQ,OAAO,EAAE;IACtB,MAAMd,QAAQ;QACZe,SAASD,OAAOC,OAAO;QACvBC,QAAQF,OAAOE,MAAM,CAACP,GAAG,CAAC,CAACQ,QAAW,CAAA;gBACpCd,MAAMc,MAAMd,IAAI;gBAChBgB,QAAQF,MAAME,MAAM;gBACpBf,KAAKa,MAAMb,GAAG;gBACdC,OAAOY,MAAMZ,KAAK;YACpB,CAAA;QACAe,YAAYxB,IAAIO,IAAI;QACpBI,SAASjB;IACX;IACA,OAAO;QAAC;YAAC,GAAGU,KAAK;YAAEqB,IAAIhC,KAAK,QAAQiC,KAAKC,SAAS,CAACvB;QAAO;KAAE;AAC9D"}
@@ -8,9 +8,9 @@ import { deriveConfigEntries } from './deriveInterfaces.js';
8
8
  */ export function exposesSetId({ configs, interfaces }) {
9
9
  const keys = [
10
10
  ...(interfaces ?? []).map((iface)=>[
11
- iface.interface_type,
11
+ iface.type,
12
12
  iface.name,
13
- iface.entry_point
13
+ iface.src
14
14
  ].join('::')),
15
15
  ...(configs ?? []).flatMap((config)=>[
16
16
  [
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/dev/exposesSetId.ts"],"sourcesContent":["import {\n deriveConfigEntries,\n type DevServerConfig,\n type DevServerInterface,\n} from './deriveInterfaces.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) =>\n [iface.interface_type, iface.name, iface.entry_point].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","interface_type","name","entry_point","join","flatMap","config","appType","entry","src","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,wBAAuB;AAQ9B;;;;;;CAMC,GACD,OAAO,SAASC,aAAa,EAACC,OAAO,EAAEC,UAAU,EAAY;IAC3D,MAAMC,OAAO;WACR,AAACD,CAAAA,cAAc,EAAE,AAAD,EAAGE,GAAG,CAAC,CAACC,QACzB;gBAACA,MAAMC,cAAc;gBAAED,MAAME,IAAI;gBAAEF,MAAMG,WAAW;aAAC,CAACC,IAAI,CAAC;WAE1D,AAACR,CAAAA,WAAW,EAAE,AAAD,EAAGS,OAAO,CAAC,CAACC,SAAW;gBACrC;oBAAC;oBAAUA,OAAOC,OAAO;iBAAC,CAACH,IAAI,CAAC;mBAC7BV,oBAAoBY,QAAQP,GAAG,CAAC,CAACS,QAClC;wBAAC;wBAAUF,OAAOC,OAAO;wBAAEC,MAAMN,IAAI;wBAAEM,MAAMC,GAAG;qBAAC,CAACL,IAAI,CAAC;aAE1D;KACF;IACD,IAAIN,KAAKY,MAAM,KAAK,GAAG,OAAO;IAC9B,OAAOZ,KAAKa,QAAQ,GAAGP,IAAI,CAAC;AAC9B;AAEA;;;CAGC,GACD,OAAO,SAASQ,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"}
1
+ {"version":3,"sources":["../../../src/actions/dev/exposesSetId.ts"],"sourcesContent":["import {\n deriveConfigEntries,\n type DevServerConfig,\n type DevServerInterface,\n} from './deriveInterfaces.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) => [iface.type, iface.name, iface.src].join('::')),\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","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,wBAAuB;AAQ9B;;;;;;CAMC,GACD,OAAO,SAASC,aAAa,EAACC,OAAO,EAAEC,UAAU,EAAY;IAC3D,MAAMC,OAAO;WACR,AAACD,CAAAA,cAAc,EAAE,AAAD,EAAGE,GAAG,CAAC,CAACC,QAAU;gBAACA,MAAMC,IAAI;gBAAED,MAAME,IAAI;gBAAEF,MAAMG,GAAG;aAAC,CAACC,IAAI,CAAC;WAC3E,AAACR,CAAAA,WAAW,EAAE,AAAD,EAAGS,OAAO,CAAC,CAACC,SAAW;gBACrC;oBAAC;oBAAUA,OAAOC,OAAO;iBAAC,CAACH,IAAI,CAAC;mBAC7BV,oBAAoBY,QAAQP,GAAG,CAAC,CAACS,QAClC;wBAAC;wBAAUF,OAAOC,OAAO;wBAAEC,MAAMN,IAAI;wBAAEM,MAAML,GAAG;qBAAC,CAACC,IAAI,CAAC;aAE1D;KACF;IACD,IAAIN,KAAKW,MAAM,KAAK,GAAG,OAAO;IAC9B,OAAOX,KAAKY,QAAQ,GAAGN,IAAI,CAAC;AAC9B;AAEA;;;CAGC,GACD,OAAO,SAASO,gBAAgBC,OAAkB;IAIhD,IAAIC,SAASlB,aAAaiB;IAC1B,OAAO;QACLE,SAAS,CAACC,OAASpB,aAAaoB,UAAUF;QAC1CG,QAAQ,CAACD;YACPF,SAASlB,aAAaoB;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,SACvBvB,aAAa;QAACC,SAASsB,OAAOtB,OAAO;QAAEC,YAAYqB,OAAOrB,UAAU;IAAA;AAEtE;;;;CAIC,GACD,OAAO,SAAS0B;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,QAAQ5B,GAAG,CAAC,CAACmB,SAAW;oBAACD,UAAUC;oBAASI,gBAAgBJ;iBAAQ;YACpF,OAAOU;QACT;IACF;AACF"}
@@ -42,17 +42,18 @@ const devServerManifestSchema = z.object({
42
42
  id: z.optional(z.string()),
43
43
  /**
44
44
  * Interfaces the app exposes, mapped from the declared `views` (dock panels,
45
- * `interface_type: "panel"`) and `services` (background workers,
46
- * `interface_type: "worker"`). A service is just an interface, so both live
45
+ * `type: "panel"`) and `services` (background workers,
46
+ * `type: "worker"`). A service is just an interface, so both live
47
47
  * in this one list. Carried separately from the manifest — interfaces live in
48
48
  * the application service, not the manifest — so the workbench can render
49
- * local panels and run local workers without a deploy. `entry_point` is the
50
- * declared `src`. Lenient by design; the workbench is the authority on the
51
- * interface shape.
49
+ * local panels and run local workers without a deploy. `src` is the
50
+ * declared source file; `title` defaults to `name`. Lenient by design; the
51
+ * workbench is the authority on the interface shape.
52
52
  */ interfaces: z.optional(z.array(z.object({
53
- entry_point: z.string(),
54
- interface_type: z.string(),
55
53
  name: z.string(),
54
+ src: z.string(),
55
+ title: z.string(),
56
+ type: z.string(),
56
57
  // Contract version the interface's generated module exports; the app
57
58
  // view has no versioned contract and carries none.
58
59
  version: z.optional(z.number())
@@ -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 {canonicalizeWatchDir} from './canonicalizeWatchDir.js'\nimport {getProcessStartTime, isOurProcess} from './processLiveness.js'\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 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` name — the module-federation alias the\n // workbench loads this config's live values from.\n moduleName: z.optional(z.string()),\n // Config contract version the generated module exports, so the\n // workbench knows what it can resolve before loading the module.\n version: z.number(),\n }),\n ),\n ),\n host: z.string(),\n id: z.optional(z.string()),\n /**\n * Interfaces the app exposes, mapped from the declared `views` (dock panels,\n * `interface_type: \"panel\"`) and `services` (background workers,\n * `interface_type: \"worker\"`). A service is just an interface, so both live\n * in this one list. Carried separately from the manifest — interfaces live in\n * the application service, not the manifest — so the workbench can render\n * local panels and run local workers without a deploy. `entry_point` is the\n * declared `src`. Lenient by design; the workbench is the authority on the\n * interface shape.\n */\n interfaces: z.optional(\n z.array(\n z.object({\n entry_point: z.string(),\n interface_type: z.string(),\n name: z.string(),\n // Contract version the interface's generated module exports; the app\n // view has no versioned contract and carries none.\n version: z.optional(z.number()),\n }),\n ),\n ),\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 * 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\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 return {\n release() {\n released = true\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 return {\n release() {\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","canonicalizeWatchDir","getProcessStartTime","isOurProcess","devDebug","REGISTRY_VERSION","ownStartedAt","process","pid","Date","toISOString","devServerManifestSchema","object","configs","optional","array","appType","string","fields","name","public","boolean","src","title","id","moduleName","version","number","host","interfaces","entry_point","interface_type","manifest","union","manifestUpdatedAt","port","projectId","startedAt","type","enum","literal","workDir","getRegistryDir","registerDevServer","registryDir","recursive","current","filePath","JSON","stringify","released","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","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 {canonicalizeWatchDir} from './canonicalizeWatchDir.js'\nimport {getProcessStartTime, isOurProcess} from './processLiveness.js'\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 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` name — the module-federation alias the\n // workbench loads this config's live values from.\n moduleName: z.optional(z.string()),\n // Config contract version the generated module exports, so the\n // workbench knows what it can resolve before loading the module.\n version: z.number(),\n }),\n ),\n ),\n host: z.string(),\n id: z.optional(z.string()),\n /**\n * Interfaces the app exposes, mapped from the declared `views` (dock panels,\n * `type: \"panel\"`) and `services` (background workers,\n * `type: \"worker\"`). A service is just an interface, so both live\n * in this one list. Carried separately from the manifest — interfaces live in\n * the application service, not the manifest — so the workbench can render\n * local panels and run local workers without a deploy. `src` is the\n * declared source file; `title` defaults to `name`. Lenient by design; the\n * workbench is the authority on the interface shape.\n */\n interfaces: z.optional(\n z.array(\n z.object({\n name: z.string(),\n src: z.string(),\n title: z.string(),\n type: z.string(),\n // Contract version the interface's generated module exports; the app\n // view has no versioned contract and carries none.\n version: z.optional(z.number()),\n }),\n ),\n ),\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 * 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\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 return {\n release() {\n released = true\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 return {\n release() {\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","canonicalizeWatchDir","getProcessStartTime","isOurProcess","devDebug","REGISTRY_VERSION","ownStartedAt","process","pid","Date","toISOString","devServerManifestSchema","object","configs","optional","array","appType","string","fields","name","public","boolean","src","title","id","moduleName","version","number","host","interfaces","type","manifest","union","manifestUpdatedAt","port","projectId","startedAt","enum","literal","workDir","getRegistryDir","registerDevServer","registryDir","recursive","current","filePath","JSON","stringify","released","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","updatePort","err","Error","message","String","isNodeError","code","existing"],"mappings":"AAAA,SACEA,UAAU,EACVC,SAAS,EACTC,WAAW,EACXC,YAAY,EACZC,UAAU,EACVC,KAAK,EACLC,aAAa,QACR,UAAS;AAChB,SAAQC,IAAI,QAAO,YAAW;AAE9B,SACEC,qBAAqB,EACrBC,gBAAgB,EAChBC,oBAAoB,EACpBC,QAAQ,QACH,mBAAkB;AACzB,SAAQC,CAAC,QAAO,WAAU;AAE1B,SAAQC,oBAAoB,QAAO,4BAA2B;AAC9D,SAAQC,mBAAmB,EAAEC,YAAY,QAAO,uBAAsB;AAEtE,MAAMC,WAAWL,SAAS;AAE1B,iEAAiE,GACjE,MAAMM,mBAAmB;AAEzB;;;;;CAKC,GACD,SAASC;IACP,OAAO,AAACJ,CAAAA,oBAAoBK,QAAQC,GAAG,KAAK,IAAIC,MAAK,EAAGC,WAAW;AACrE;AAEA,MAAMC,0BAA0BX,EAAEY,MAAM,CAAC;IACvC;;;;GAIC,GACDC,SAASb,EAAEc,QAAQ,CACjBd,EAAEe,KAAK,CACLf,EAAEY,MAAM,CAAC;QACP,gEAAgE;QAChEI,SAAShB,EAAEc,QAAQ,CAACd,EAAEiB,MAAM;QAC5BC,QAAQlB,EAAEe,KAAK,CACbf,EAAEY,MAAM,CAAC;YACPO,MAAMnB,EAAEiB,MAAM;YACdG,QAAQpB,EAAEc,QAAQ,CAACd,EAAEqB,OAAO;YAC5BC,KAAKtB,EAAEiB,MAAM;YACbM,OAAOvB,EAAEiB,MAAM;QACjB;QAEF,oEAAoE;QACpE,uBAAuB;QACvBO,IAAIxB,EAAEiB,MAAM;QACZ,wEAAwE;QACxE,kDAAkD;QAClDQ,YAAYzB,EAAEc,QAAQ,CAACd,EAAEiB,MAAM;QAC/B,+DAA+D;QAC/D,iEAAiE;QACjES,SAAS1B,EAAE2B,MAAM;IACnB;IAGJC,MAAM5B,EAAEiB,MAAM;IACdO,IAAIxB,EAAEc,QAAQ,CAACd,EAAEiB,MAAM;IACvB;;;;;;;;;GASC,GACDY,YAAY7B,EAAEc,QAAQ,CACpBd,EAAEe,KAAK,CACLf,EAAEY,MAAM,CAAC;QACPO,MAAMnB,EAAEiB,MAAM;QACdK,KAAKtB,EAAEiB,MAAM;QACbM,OAAOvB,EAAEiB,MAAM;QACfa,MAAM9B,EAAEiB,MAAM;QACd,qEAAqE;QACrE,mDAAmD;QACnDS,SAAS1B,EAAEc,QAAQ,CAACd,EAAE2B,MAAM;IAC9B;IAGJ;;;;GAIC,GACDI,UAAU/B,EAAEc,QAAQ,CAACd,EAAEgC,KAAK,CAAC;QAAClC;QAAsBF;KAAsB;IAC1E;;;;GAIC,GACDqC,mBAAmBjC,EAAEc,QAAQ,CAACd,EAAEiB,MAAM;IACtCT,KAAKR,EAAE2B,MAAM;IACbO,MAAMlC,EAAE2B,MAAM;IACdQ,WAAWnC,EAAEc,QAAQ,CAACd,EAAEiB,MAAM;IAC9BmB,WAAWpC,EAAEiB,MAAM;IACnBa,MAAM9B,EAAEqC,IAAI,CAAC;QAAC;QAAW;KAAS;IAClCX,SAAS1B,EAAEsC,OAAO,CAACjC;IACnBkC,SAASvC,EAAEiB,MAAM;AACnB;AAUA;;;CAGC,GACD,SAASuB;IACP,OAAO7C,KAAKE,oBAAoB;AAClC;AAaA;;;;;CAKC,GACD,OAAO,SAAS4C,kBACdV,QAAkE;IAElE,MAAMW,cAAcF;IACpBnD,UAAUqD,aAAa;QAACC,WAAW;IAAI;IAEvC,IAAIC,UAA6B;QAC/B,GAAGb,QAAQ;QACXvB,KAAKD,QAAQC,GAAG;QAChB4B,WAAW9B;QACXoB,SAASrB;IACX;IAEA,MAAMwC,WAAWlD,KAAK+C,aAAa,GAAGnC,QAAQC,GAAG,CAAC,KAAK,CAAC;IACxDd,cAAcmD,UAAUC,KAAKC,SAAS,CAACH,SAAS,MAAM;IAEtD,qEAAqE;IACrE,wEAAwE;IACxE,wEAAwE;IACxE,IAAII,WAAW;IAEf,OAAO;QACLC;YACED,WAAW;YACX,IAAI;gBACFxD,WAAWqD;YACb,EAAE,OAAM;YACN,sCAAsC;YACxC;QACF;QACAK,QAAOC,KAAK;YACV,IAAIH,UAAU;YACdJ,UAAU;gBAAC,GAAGA,OAAO;gBAAE,GAAGO,KAAK;YAAA;YAC/BzD,cAAcmD,UAAUC,KAAKC,SAAS,CAACH,SAAS,MAAM;QACxD;IACF;AACF;AAEA;;;CAGC,GACD,OAAO,SAASQ;IACd,MAAMV,cAAcF;IAEpB,IAAI,CAACpD,WAAWsD,cAAc;QAC5B,OAAO,EAAE;IACX;IAEA,MAAMW,QAAQ/D,YAAYoD,aAAaY,MAAM,CAAC,CAACC,IAAMA,EAAEC,QAAQ,CAAC;IAChE,MAAMC,UAA+B,EAAE;IAEvC,KAAK,MAAMC,QAAQL,MAAO;QACxB,MAAMR,WAAWlD,KAAK+C,aAAagB;QACnC,IAAIC;QACJ,IAAI;YACFA,MAAMb,KAAKc,KAAK,CAACrE,aAAasD,UAAU;QAC1C,EAAE,OAAM;YACN;QACF;QAEA,MAAM,EAACgB,IAAI,EAAEC,OAAO,EAAC,GAAGnD,wBAAwBoD,SAAS,CAACJ;QAC1D,IAAI,CAACG,SAAS;QAEd,IAAI3D,aAAa0D,KAAKrD,GAAG,EAAEqD,KAAKzB,SAAS,GAAG;YAC1CqB,QAAQO,IAAI,CAACH;QACf,OAAO;YACL,IAAI;gBACFrE,WAAWqD;YACb,EAAE,OAAM;YACN,0DAA0D;YAC5D;QACF;IACF;IAEA,OAAOY;AACT;AAMA;;;;;;CAMC,GACD,OAAO,SAASQ,cAAcC,QAAgD;IAC5E,MAAMxB,cAAcF;IACpBnD,UAAUqD,aAAa;QAACC,WAAW;IAAI;IAEvC,4EAA4E;IAC5E,+CAA+C;IAC/C,MAAMwB,WAAWlE,qBAAqByC;IAEtC,IAAI0B;IAEJ,MAAMC,SAAS;QACbC,aAAaF;QACbA,gBAAgBG,WAAW;YACzBL,SAASd;QACX,GAAG;IACL;IAEA,MAAMoB,UAAU/E,MAAM0E,UAAUE;IAEhC,OAAO;QACLI;YACEH,aAAaF;YACbI,QAAQC,KAAK;QACf;IACF;AACF;AAEA,gFAAgF;AAChF,2EAA2E;AAC3E,+EAA+E;AAE/E,MAAMC,sBAAsB1E,EAAEY,MAAM,CAAC;IACnCgB,MAAM5B,EAAEiB,MAAM;IACdT,KAAKR,EAAE2B,MAAM;IACbO,MAAMlC,EAAE2B,MAAM;IACdS,WAAWpC,EAAEiB,MAAM;IACnBS,SAAS1B,EAAEsC,OAAO,CAACjC;AACrB;AAEA;;;CAGC,GACD,OAAO,SAASsE;IACd,MAAMC,WAAWjF,KAAK6C,kBAAkB;IAExC,IAAIqC;IACJ,IAAI;QACFA,WAAWtF,aAAaqF,UAAU;IACpC,EAAE,OAAM;QACN,2DAA2D;QAC3D,OAAOE;IACT;IAEA,0EAA0E;IAC1E,2EAA2E;IAC3E,wEAAwE;IACxE,wEAAwE;IACxE,MAAMjB,OAAOkB,kBAAkBF;IAC/BzE,SAAS,2BAA2ByD;IACpC,IAAIA,QAAQ1D,aAAa0D,KAAKrD,GAAG,EAAEqD,KAAKzB,SAAS,GAAG;QAClDhC,SAAS,mDAAmDyD,KAAKrD,GAAG,EAAEqD,KAAK3B,IAAI;QAC/E,OAAO2B;IACT;IAEAmB,mBAAmBJ;IACnB,OAAOE;AACT;AAEA,SAASC,kBAAkBF,QAAgB;IACzC,IAAI;QACF,MAAM,EAAChB,IAAI,EAAEC,OAAO,EAAC,GAAGY,oBAAoBX,SAAS,CAACjB,KAAKc,KAAK,CAACiB;QACjE,OAAOf,UAAUD,OAAOiB;IAC1B,EAAE,OAAM;QACN,OAAOA;IACT;AACF;AAEA,SAASE,mBAAmBJ,QAAgB;IAC1C,IAAI;QACFxE,SAAS;QACTZ,WAAWoF;QACXxE,SAAS;IACX,EAAE,OAAM;IACN,iDAAiD;IACnD;AACF;AASA;;;;;;;;;;;CAWC,GACD,OAAO,SAAS6E,qBACdC,IAAkC,EAClCC,UAAU,CAAC;IAEX,MAAMzC,cAAcF;IACpBnD,UAAUqD,aAAa;QAACC,WAAW;IAAI;IAEvC,MAAMiC,WAAWjF,KAAK+C,aAAa;IACnC,MAAMN,YAAY9B;IAClB,MAAM8E,WAAW;QACfxD,MAAMsD,KAAKtD,IAAI;QACfpB,KAAKD,QAAQC,GAAG;QAChB0B,MAAMgD,KAAKhD,IAAI;QACfE;QACAV,SAASrB;IACX;IAEAD,SAAS,kCAAkCwE;IAE3C,IAAI;QACFlF,cAAckF,UAAU9B,KAAKC,SAAS,CAACqC,WAAW;YAACC,MAAM;QAAI;QAC7DjF,SAAS;QACT,OAAO;YACL6C;gBACE,IAAI;oBACFzD,WAAWoF;gBACb,EAAE,OAAM;gBACN,qBAAqB;gBACvB;YACF;YACAU,YAAWpD,IAAY;gBACrBxC,cAAckF,UAAU9B,KAAKC,SAAS,CAAC;oBAAC,GAAGqC,QAAQ;oBAAElD;gBAAI;YAC3D;QACF;IACF,EAAE,OAAOqD,KAAc;QACrBnF,SACE,wCACAmF,eAAeC,QAAQD,IAAIE,OAAO,GAAGC,OAAOH;QAE9C,IAAI,CAACI,YAAYJ,QAAQA,IAAIK,IAAI,KAAK,UAAU,OAAOd;QAEvD,mDAAmD;QACnD,MAAMe,WAAWlB;QACjB,IAAIkB,UAAU,OAAOf;QAErB,6FAA6F;QAC7F,IAAIK,WAAW,GAAG,OAAOL;QACzB,OAAOG,qBAAqBC,MAAMC,UAAU;IAC9C;AACF;AAEA,SAASQ,YAAYJ,GAAY;IAC/B,OAAOA,eAAeC,SAAS,UAAUD;AAC3C"}