@sanity/workbench-cli 1.2.0 → 1.3.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 (42) hide show
  1. package/dist/_exports/build.d.ts +22 -17
  2. package/dist/_exports/deploy.d.ts +156 -20
  3. package/dist/_exports/deploy.js +3 -1
  4. package/dist/_exports/deploy.js.map +1 -1
  5. package/dist/_exports/dev.d.ts +6 -3
  6. package/dist/_exports/index.d.ts +16 -13
  7. package/dist/actions/build/artifact.js +2 -2
  8. package/dist/actions/build/artifact.js.map +1 -1
  9. package/dist/actions/build/configs/artifact.js +5 -5
  10. package/dist/actions/build/configs/artifact.js.map +1 -1
  11. package/dist/actions/deploy/apiVersion.js +5 -0
  12. package/dist/actions/deploy/apiVersion.js.map +1 -0
  13. package/dist/actions/deploy/buildExposes.js +56 -0
  14. package/dist/actions/deploy/buildExposes.js.map +1 -0
  15. package/dist/actions/deploy/{deployInstallationConfig.js → deployConfig.js} +10 -11
  16. package/dist/actions/deploy/deployConfig.js.map +1 -0
  17. package/dist/actions/deploy/deployWorkbenchApp.js +185 -0
  18. package/dist/actions/deploy/deployWorkbenchApp.js.map +1 -0
  19. package/dist/actions/deploy/getWorkbench.js +4 -4
  20. package/dist/actions/deploy/getWorkbench.js.map +1 -1
  21. package/dist/actions/dev/deriveInterfaces.js +33 -19
  22. package/dist/actions/dev/deriveInterfaces.js.map +1 -1
  23. package/dist/actions/dev/exposesSetId.js +6 -6
  24. package/dist/actions/dev/exposesSetId.js.map +1 -1
  25. package/dist/actions/dev/registry.js +14 -5
  26. package/dist/actions/dev/registry.js.map +1 -1
  27. package/dist/actions/dev/startDevManifestWatcher.js +2 -2
  28. package/dist/actions/dev/startDevManifestWatcher.js.map +1 -1
  29. package/dist/actions/dev/startDevServerRegistration.js +7 -7
  30. package/dist/actions/dev/startDevServerRegistration.js.map +1 -1
  31. package/dist/actions/dev/startWorkbenchDevServer.js +9 -6
  32. package/dist/actions/dev/startWorkbenchDevServer.js.map +1 -1
  33. package/dist/actions/dev/writeWorkbenchRuntime.js +4 -1
  34. package/dist/actions/dev/writeWorkbenchRuntime.js.map +1 -1
  35. package/dist/contract.js +16 -8
  36. package/dist/contract.js.map +1 -1
  37. package/dist/defineApp.js +25 -20
  38. package/dist/defineApp.js.map +1 -1
  39. package/dist/resolveWorkbenchApp.js +4 -3
  40. package/dist/resolveWorkbenchApp.js.map +1 -1
  41. package/package.json +3 -3
  42. package/dist/actions/deploy/deployInstallationConfig.js.map +0 -1
@@ -0,0 +1,56 @@
1
+ /**
2
+ * The interface records deploy sends: the app view (only when `exposesAppView`),
3
+ * every view, and every service.
4
+ * @internal
5
+ */ export function buildExposes(exposes, { appName, appTitle, exposesAppView, version }) {
6
+ const toRecord = (prefix, decl)=>({
7
+ moduleId: `${prefix}/${decl.name}`,
8
+ name: decl.name,
9
+ title: decl.title ?? decl.name,
10
+ type: decl.type,
11
+ version
12
+ });
13
+ const records = [];
14
+ if (exposesAppView) {
15
+ records.push({
16
+ moduleId: 'App',
17
+ name: appName,
18
+ title: appTitle,
19
+ type: 'app',
20
+ version
21
+ });
22
+ }
23
+ for (const view of exposes.views ?? [])records.push(toRecord('views', view));
24
+ for (const service of exposes.services ?? [])records.push(toRecord('services', service));
25
+ return records;
26
+ }
27
+ function summarizeExposeGroup(heading, items) {
28
+ const label = (item)=>item.title === item.name ? item.name : `${item.title} (${item.name})`;
29
+ return `${heading}:\n${items.map((item)=>` ${label(item)}: ${item.src}`).join('\n')}`;
30
+ }
31
+ /**
32
+ * The deploy summary of an app's exposes: the structured records (for `--json`)
33
+ * and one report line per non-empty group (for the human report).
34
+ * @internal
35
+ */ export function summarizeExposes({ services, views }) {
36
+ const toExpose = (decl)=>({
37
+ name: decl.name,
38
+ src: decl.src,
39
+ title: decl.title ?? decl.name,
40
+ type: decl.type
41
+ });
42
+ const viewExposes = (views ?? []).map((view)=>toExpose(view));
43
+ const serviceExposes = (services ?? []).map((service)=>toExpose(service));
44
+ const lines = [];
45
+ if (viewExposes.length > 0) lines.push(summarizeExposeGroup('Views', viewExposes));
46
+ if (serviceExposes.length > 0) lines.push(summarizeExposeGroup('Services', serviceExposes));
47
+ return {
48
+ exposes: [
49
+ ...viewExposes,
50
+ ...serviceExposes
51
+ ],
52
+ lines
53
+ };
54
+ }
55
+
56
+ //# sourceMappingURL=buildExposes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/actions/deploy/buildExposes.ts"],"sourcesContent":["import {type WorkbenchExposes} from '../../resolveWorkbenchApp.js'\n\n/**\n * An interface as Brett stores it: the declared `type` (validated server-side,\n * not here) and the remote-relative `moduleId` the workbench loads it by — the\n * host prepends the app's own id.\n * @internal\n */\nexport interface BrettInterface {\n moduleId: string\n name: string\n title: string\n type: string\n version: string\n}\n\ninterface BuildExposesContext {\n appName: string\n appTitle: string\n /** Whether the build exposes the app view (`./App`) — apps with an `entry`, and every studio. */\n exposesAppView: boolean\n version: string\n}\n\n/**\n * The interface records deploy sends: the app view (only when `exposesAppView`),\n * every view, and every service.\n * @internal\n */\nexport function buildExposes(\n exposes: WorkbenchExposes,\n {appName, appTitle, exposesAppView, version}: BuildExposesContext,\n): BrettInterface[] {\n const toRecord = (\n prefix: string,\n decl: {name: string; title?: string; type: string},\n ): BrettInterface => ({\n moduleId: `${prefix}/${decl.name}`,\n name: decl.name,\n title: decl.title ?? decl.name,\n type: decl.type,\n version,\n })\n\n const records: BrettInterface[] = []\n if (exposesAppView) {\n records.push({moduleId: 'App', name: appName, title: appTitle, type: 'app', version})\n }\n for (const view of exposes.views ?? []) records.push(toRecord('views', view))\n for (const service of exposes.services ?? []) records.push(toRecord('services', service))\n return records\n}\n\n/** A view or service as the deploy report and `--json` output surface it. */\nexport interface DeployedExpose {\n name: string\n src: string\n title: string\n type: string\n}\n\nfunction summarizeExposeGroup(heading: string, items: readonly DeployedExpose[]): string {\n const label = (item: DeployedExpose) =>\n item.title === item.name ? item.name : `${item.title} (${item.name})`\n return `${heading}:\\n${items.map((item) => ` ${label(item)}: ${item.src}`).join('\\n')}`\n}\n\n/**\n * The deploy summary of an app's exposes: the structured records (for `--json`)\n * and one report line per non-empty group (for the human report).\n * @internal\n */\nexport function summarizeExposes({services, views}: WorkbenchExposes): {\n exposes: DeployedExpose[]\n lines: string[]\n} {\n const toExpose = (decl: {\n name: string\n src: string\n title?: string\n type: string\n }): DeployedExpose => ({\n name: decl.name,\n src: decl.src,\n title: decl.title ?? decl.name,\n type: decl.type,\n })\n const viewExposes = (views ?? []).map((view) => toExpose(view))\n const serviceExposes = (services ?? []).map((service) => toExpose(service))\n\n const lines: string[] = []\n if (viewExposes.length > 0) lines.push(summarizeExposeGroup('Views', viewExposes))\n if (serviceExposes.length > 0) lines.push(summarizeExposeGroup('Services', serviceExposes))\n return {exposes: [...viewExposes, ...serviceExposes], lines}\n}\n"],"names":["buildExposes","exposes","appName","appTitle","exposesAppView","version","toRecord","prefix","decl","moduleId","name","title","type","records","push","view","views","service","services","summarizeExposeGroup","heading","items","label","item","map","src","join","summarizeExposes","toExpose","viewExposes","serviceExposes","lines","length"],"mappings":"AAwBA;;;;CAIC,GACD,OAAO,SAASA,aACdC,OAAyB,EACzB,EAACC,OAAO,EAAEC,QAAQ,EAAEC,cAAc,EAAEC,OAAO,EAAsB;IAEjE,MAAMC,WAAW,CACfC,QACAC,OACoB,CAAA;YACpBC,UAAU,GAAGF,OAAO,CAAC,EAAEC,KAAKE,IAAI,EAAE;YAClCA,MAAMF,KAAKE,IAAI;YACfC,OAAOH,KAAKG,KAAK,IAAIH,KAAKE,IAAI;YAC9BE,MAAMJ,KAAKI,IAAI;YACfP;QACF,CAAA;IAEA,MAAMQ,UAA4B,EAAE;IACpC,IAAIT,gBAAgB;QAClBS,QAAQC,IAAI,CAAC;YAACL,UAAU;YAAOC,MAAMR;YAASS,OAAOR;YAAUS,MAAM;YAAOP;QAAO;IACrF;IACA,KAAK,MAAMU,QAAQd,QAAQe,KAAK,IAAI,EAAE,CAAEH,QAAQC,IAAI,CAACR,SAAS,SAASS;IACvE,KAAK,MAAME,WAAWhB,QAAQiB,QAAQ,IAAI,EAAE,CAAEL,QAAQC,IAAI,CAACR,SAAS,YAAYW;IAChF,OAAOJ;AACT;AAUA,SAASM,qBAAqBC,OAAe,EAAEC,KAAgC;IAC7E,MAAMC,QAAQ,CAACC,OACbA,KAAKZ,KAAK,KAAKY,KAAKb,IAAI,GAAGa,KAAKb,IAAI,GAAG,GAAGa,KAAKZ,KAAK,CAAC,EAAE,EAAEY,KAAKb,IAAI,CAAC,CAAC,CAAC;IACvE,OAAO,GAAGU,QAAQ,GAAG,EAAEC,MAAMG,GAAG,CAAC,CAACD,OAAS,CAAC,EAAE,EAAED,MAAMC,MAAM,EAAE,EAAEA,KAAKE,GAAG,EAAE,EAAEC,IAAI,CAAC,OAAO;AAC1F;AAEA;;;;CAIC,GACD,OAAO,SAASC,iBAAiB,EAACT,QAAQ,EAAEF,KAAK,EAAmB;IAIlE,MAAMY,WAAW,CAACpB,OAKK,CAAA;YACrBE,MAAMF,KAAKE,IAAI;YACfe,KAAKjB,KAAKiB,GAAG;YACbd,OAAOH,KAAKG,KAAK,IAAIH,KAAKE,IAAI;YAC9BE,MAAMJ,KAAKI,IAAI;QACjB,CAAA;IACA,MAAMiB,cAAc,AAACb,CAAAA,SAAS,EAAE,AAAD,EAAGQ,GAAG,CAAC,CAACT,OAASa,SAASb;IACzD,MAAMe,iBAAiB,AAACZ,CAAAA,YAAY,EAAE,AAAD,EAAGM,GAAG,CAAC,CAACP,UAAYW,SAASX;IAElE,MAAMc,QAAkB,EAAE;IAC1B,IAAIF,YAAYG,MAAM,GAAG,GAAGD,MAAMjB,IAAI,CAACK,qBAAqB,SAASU;IACrE,IAAIC,eAAeE,MAAM,GAAG,GAAGD,MAAMjB,IAAI,CAACK,qBAAqB,YAAYW;IAC3E,OAAO;QAAC7B,SAAS;eAAI4B;eAAgBC;SAAe;QAAEC;IAAK;AAC7D"}
@@ -5,8 +5,7 @@ import { createGzip } from 'node:zlib';
5
5
  import { getGlobalCliClient, subdebug } from '@sanity/cli-core';
6
6
  import FormData from 'form-data';
7
7
  import { pack } from 'tar-fs';
8
- // Brett's app-registry endpoints live behind the experimental `vX` version.
9
- const INSTALLATIONS_API_VERSION = 'vX';
8
+ import { APP_WORKBENCH_API_VERSION } from './apiVersion.js';
10
9
  const debug = subdebug('deploy');
11
10
  /**
12
11
  * The org's active installation for an app type, or `undefined` when none is
@@ -20,7 +19,7 @@ const debug = subdebug('deploy');
20
19
  }
21
20
  default:
22
21
  {
23
- throw new Error(`Cannot create installation config for unknown app type: ${options.appType}`);
22
+ throw new Error(`Cannot create config for unknown app type: ${options.appType}`);
24
23
  }
25
24
  }
26
25
  }
@@ -28,7 +27,7 @@ const debug = subdebug('deploy');
28
27
  * A report heading and item list for a config; a media library's `fields` are
29
28
  * one of potentially many shapes.
30
29
  * @internal
31
- */ export function summarizeInstallationConfig(config) {
30
+ */ export function summarizeConfig(config) {
32
31
  switch(config.appType){
33
32
  case 'media-library':
34
33
  {
@@ -37,7 +36,7 @@ const debug = subdebug('deploy');
37
36
  }
38
37
  default:
39
38
  {
40
- throw new Error(`Cannot create installation config for unknown app type: ${config.appType}`);
39
+ throw new Error(`Cannot create config for unknown app type: ${config.appType}`);
41
40
  }
42
41
  }
43
42
  }
@@ -46,7 +45,7 @@ const debug = subdebug('deploy');
46
45
  * snapshot. `installationId` is resolved by the caller so `--dry-run` never
47
46
  * reaches this mutating step.
48
47
  * @internal
49
- */ export async function deployInstallationConfig(options) {
48
+ */ export async function deployConfig(options) {
50
49
  const { appType, installationId, output, sourceDir, version } = options;
51
50
  const tarball = pack(dirname(sourceDir), {
52
51
  entries: [
@@ -60,7 +59,7 @@ const debug = subdebug('deploy');
60
59
  filename: 'installation-config.tar.gz'
61
60
  });
62
61
  const client = await getGlobalCliClient({
63
- apiVersion: INSTALLATIONS_API_VERSION,
62
+ apiVersion: APP_WORKBENCH_API_VERSION,
64
63
  requireUser: true
65
64
  });
66
65
  await client.request({
@@ -69,12 +68,12 @@ const debug = subdebug('deploy');
69
68
  method: 'POST',
70
69
  uri: `/installations/${installationId}/configs`
71
70
  });
72
- debug('Deployed installation config for app type: %s', appType);
73
- output.log(`\n🚀 ${styleText('bold', 'Success!')} Installation config deployed`);
71
+ debug('Deployed config for app type: %s', appType);
72
+ output.log(`\n🚀 ${styleText('bold', 'Success!')} Config deployed`);
74
73
  }
75
74
  /** The org's active singleton installation, matched on its slug. */ async function resolveSingletonInstallationId(organizationId, slug) {
76
75
  const client = await getGlobalCliClient({
77
- apiVersion: INSTALLATIONS_API_VERSION,
76
+ apiVersion: APP_WORKBENCH_API_VERSION,
78
77
  requireUser: true
79
78
  });
80
79
  // `limit=none` returns every installation in one response, no pagination.
@@ -88,4 +87,4 @@ const debug = subdebug('deploy');
88
87
  return data.find((item)=>item.application?.slug === slug)?.id;
89
88
  }
90
89
 
91
- //# sourceMappingURL=deployInstallationConfig.js.map
90
+ //# sourceMappingURL=deployConfig.js.map
@@ -0,0 +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"}
@@ -0,0 +1,185 @@
1
+ import { basename, dirname } from 'node:path';
2
+ import { PassThrough } from 'node:stream';
3
+ import { createGzip } from 'node:zlib';
4
+ import { exitCodes, getGlobalCliClient } from '@sanity/cli-core';
5
+ import { spinner } from '@sanity/cli-core/ux';
6
+ import FormData from 'form-data';
7
+ 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
+ }
84
+ /**
85
+ * Deploy a workbench coreApp through Brett: redeploy when `appId` is set,
86
+ * otherwise create the application at `slug`. Returns the application id for the
87
+ * shell to report.
88
+ * @internal
89
+ */ export async function deployCoreApp(options) {
90
+ const { appId, interfaces, isAutoUpdating, isSingleton, organizationId, slug, sourceDir, title, version } = options;
91
+ const tarball = pack(dirname(sourceDir), {
92
+ entries: [
93
+ basename(sourceDir)
94
+ ]
95
+ }).pipe(createGzip());
96
+ const spin = spinner('Deploying...').start();
97
+ try {
98
+ if (appId) {
99
+ await createDeployment({
100
+ applicationId: appId,
101
+ interfaces,
102
+ isAutoUpdating,
103
+ tarball,
104
+ version
105
+ });
106
+ spin.succeed();
107
+ return {
108
+ applicationId: appId
109
+ };
110
+ }
111
+ const { id } = await createApplication({
112
+ interfaces,
113
+ isSingleton,
114
+ organizationId,
115
+ slug,
116
+ tarball,
117
+ title,
118
+ type: 'coreApp',
119
+ version
120
+ });
121
+ spin.succeed();
122
+ return {
123
+ applicationId: id
124
+ };
125
+ } catch (error) {
126
+ spin.clear();
127
+ throw error;
128
+ }
129
+ }
130
+ /**
131
+ * Deploy a workbench studio through Brett: redeploy when `appId` is set,
132
+ * otherwise create the studio at `studioHost`. Returns the application id for
133
+ * the shell to report; a missing `studioHost` on create is a usage error.
134
+ * @internal
135
+ */ export async function deployStudio(options) {
136
+ const { appId, interfaces, isAutoUpdating, organizationId, output, projectId, sourceDir, studioHost, title, version, workspaces } = options;
137
+ const tarball = pack(dirname(sourceDir), {
138
+ entries: [
139
+ basename(sourceDir)
140
+ ]
141
+ }).pipe(createGzip());
142
+ const spin = spinner('Deploying to sanity.studio').start();
143
+ try {
144
+ if (appId) {
145
+ await createDeployment({
146
+ applicationId: appId,
147
+ interfaces,
148
+ isAutoUpdating,
149
+ tarball,
150
+ version,
151
+ workspaces
152
+ });
153
+ spin.succeed();
154
+ return {
155
+ applicationId: appId
156
+ };
157
+ }
158
+ if (!studioHost) {
159
+ spin.fail();
160
+ return output.error('No studio hostname configured. Set `studioHost` in sanity.cli.ts to create a studio.', {
161
+ exit: exitCodes.USAGE_ERROR
162
+ });
163
+ }
164
+ const application = await createApplication({
165
+ interfaces,
166
+ organizationId,
167
+ projectId,
168
+ slug: studioHost,
169
+ tarball,
170
+ title,
171
+ type: 'studio',
172
+ version,
173
+ workspaces
174
+ });
175
+ spin.succeed();
176
+ return {
177
+ applicationId: application.id
178
+ };
179
+ } catch (error) {
180
+ spin.fail();
181
+ throw error;
182
+ }
183
+ }
184
+
185
+ //# sourceMappingURL=deployWorkbenchApp.js.map
@@ -0,0 +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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"}
@@ -7,14 +7,14 @@ import { buildViewDeploymentPayload } from './viewDeployment.js';
7
7
  export function getWorkbench(cliConfig) {
8
8
  const app = resolveWorkbenchApp(cliConfig);
9
9
  if (!app) return null;
10
- const { entry, installationConfig, isSingleton, services, views } = app;
10
+ const { config, entry, isSingleton, services, views } = app;
11
11
  return {
12
12
  ...app,
13
- deploySingletonInstallationConfig: isSingleton && !!installationConfig,
13
+ deploySingletonConfig: !!isSingleton && !!config,
14
14
  hasInterfaces: !!entry || views.length > 0 || services.length > 0,
15
15
  assertDeployable () {
16
- if (!entry && views.length === 0 && services.length === 0 && !installationConfig) {
17
- throw new Error('Nothing to deploy: the app declares no entry, views, services or installation config. ' + 'Add at least one to the app config.');
16
+ if (!entry && views.length === 0 && services.length === 0 && !config) {
17
+ throw new Error('Nothing to deploy: the app declares no entry, views, services or config. ' + 'Add at least one to the app config.');
18
18
  }
19
19
  },
20
20
  buildViewDeploymentPayload (applicationId) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/deploy/getWorkbench.ts"],"sourcesContent":["// The deploy command's view of a workbench app: the resolved interfaces plus\n// the deploy-time guards that need the app's declarations. `sanity deploy` calls\n// `getWorkbench(config)` once and either gets `null` (plain project — normal\n// deploy) or an object it asks to validate the app before shipping.\n\nimport {type CliConfig} from '@sanity/cli-core'\n\nimport {type ResolvedWorkbenchApp, resolveWorkbenchApp} from '../../resolveWorkbenchApp.js'\nimport {buildViewDeploymentPayload, type ViewDeploymentPayload} from './viewDeployment.js'\n\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 installation 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 deploySingletonInstallationConfig: 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 {entry, installationConfig, isSingleton, services, views} = app\n\n return {\n ...app,\n\n deploySingletonInstallationConfig: isSingleton && !!installationConfig,\n hasInterfaces: !!entry || views.length > 0 || services.length > 0,\n\n assertDeployable() {\n if (!entry && views.length === 0 && services.length === 0 && !installationConfig) {\n throw new Error(\n 'Nothing to deploy: the app declares no entry, views, services or installation 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","entry","installationConfig","isSingleton","services","views","deploySingletonInstallationConfig","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,KAAK,EAAEC,kBAAkB,EAAEC,WAAW,EAAEC,QAAQ,EAAEC,KAAK,EAAC,GAAGL;IAElE,OAAO;QACL,GAAGA,GAAG;QAENM,mCAAmCH,eAAe,CAAC,CAACD;QACpDK,eAAe,CAAC,CAACN,SAASI,MAAMG,MAAM,GAAG,KAAKJ,SAASI,MAAM,GAAG;QAEhEC;YACE,IAAI,CAACR,SAASI,MAAMG,MAAM,KAAK,KAAKJ,SAASI,MAAM,KAAK,KAAK,CAACN,oBAAoB;gBAChF,MAAM,IAAIQ,MACR,2FACE;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\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,11 +1,15 @@
1
- import { isWorkbenchApp, readInstallationConfig } from '../../defineApp.js';
1
+ import { hash } from 'node:crypto';
2
+ import { MEDIA_LIBRARY_CONFIG_CONTRACT_VERSION, SERVICE_CONTRACT_VERSION, VIEW_CONTRACT_VERSION } from '../../contract.js';
3
+ import { isWorkbenchApp, readConfig } from '../../defineApp.js';
2
4
  /**
3
5
  * Map a workbench app's declarations to the interface records forwarded on its
4
6
  * registry entry: `views` → panels, `services` → workers, `entry` → the
5
7
  * navigable `app` view (`entry_point` is the raw `src`, not a resolved URL).
6
8
  * `undefined` for a non-branded app; a studio that declares `entry` is rejected
7
- * (studio app views are not implemented yet). The installation config is not an
8
- * interface — see {@link deriveInstallationConfigs}.
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
12
+ * view has no versioned contract, so it carries none.
9
13
  */ export function deriveInterfaces(app, options) {
10
14
  if (!isWorkbenchApp(app)) return undefined;
11
15
  if (!options.isApp && app.entry !== undefined) {
@@ -15,12 +19,14 @@ import { isWorkbenchApp, readInstallationConfig } from '../../defineApp.js';
15
19
  ...app.views?.map((view)=>({
16
20
  entry_point: view.src,
17
21
  interface_type: view.type,
18
- name: view.name
22
+ name: view.name,
23
+ version: VIEW_CONTRACT_VERSION
19
24
  })) ?? [],
20
25
  ...app.services?.map((service)=>({
21
26
  entry_point: service.src,
22
27
  interface_type: service.type,
23
- name: service.name
28
+ name: service.name,
29
+ version: SERVICE_CONTRACT_VERSION
24
30
  })) ?? [],
25
31
  ...app.entry === undefined ? [] : [
26
32
  {
@@ -36,7 +42,7 @@ import { isWorkbenchApp, readInstallationConfig } from '../../defineApp.js';
36
42
  * per app type — the projection the exposes-set id keys on, so the generic HMR
37
43
  * tracker owns none of the per-type shape. Throws on an app type it can't
38
44
  * handle, so a new config family has to register its shape here.
39
- */ export function deriveInstallationConfigEntries(config) {
45
+ */ export function deriveConfigEntries(config) {
40
46
  switch(config.appType){
41
47
  case 'media-library':
42
48
  {
@@ -47,7 +53,7 @@ import { isWorkbenchApp, readInstallationConfig } from '../../defineApp.js';
47
53
  }
48
54
  default:
49
55
  {
50
- throw new Error(`Cannot derive entries for unknown installation config appType: ${config.appType}`);
56
+ throw new Error(`Cannot derive entries for unknown config appType: ${config.appType}`);
51
57
  }
52
58
  }
53
59
  }
@@ -55,21 +61,29 @@ import { isWorkbenchApp, readInstallationConfig } from '../../defineApp.js';
55
61
  * The fields' schema *values* can't serialize — the workbench loads them from
56
62
  * the federation module. `src` stays on so the exposes-set id keys on it and a
57
63
  * repoint rebuilds. `appType` routes the config to the singleton (no app id to
58
- * key on).
59
- */ export function deriveInstallationConfigs(app) {
64
+ * key on). `id` is a content hash of the entry — it fills the
65
+ * installation-config id slot deployed apps get from the applications API,
66
+ * and the workbench keys change detection on it. `version` is the config
67
+ * contract version the generated module exports, known before the module runs.
68
+ */ export function deriveConfigs(app) {
60
69
  if (!isWorkbenchApp(app)) return [];
61
- const installationConfig = readInstallationConfig(app);
62
- if (!installationConfig) return [];
70
+ const config = readConfig(app);
71
+ if (!config) return [];
72
+ const entry = {
73
+ appType: config.appType,
74
+ fields: config.fields.map((field)=>({
75
+ name: field.name,
76
+ public: field.public,
77
+ src: field.src,
78
+ title: field.title
79
+ })),
80
+ moduleName: app.name,
81
+ version: MEDIA_LIBRARY_CONFIG_CONTRACT_VERSION
82
+ };
63
83
  return [
64
84
  {
65
- appType: installationConfig.appType,
66
- fields: installationConfig.fields.map((field)=>({
67
- name: field.name,
68
- public: field.public,
69
- src: field.src,
70
- title: field.title
71
- })),
72
- moduleName: app.name
85
+ ...entry,
86
+ id: hash('sha1', JSON.stringify(entry))
73
87
  }
74
88
  ];
75
89
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/dev/deriveInterfaces.ts"],"sourcesContent":["import {type CliConfig} from '@sanity/cli-core'\n\nimport {isWorkbenchApp, readInstallationConfig} 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 installation config on the dev-server registry entry. */\nexport type DevServerConfig = NonNullable<DevServerManifest['installationConfigs']>[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 installation config is not an\n * interface — see {@link deriveInstallationConfigs}.\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 })) ?? []),\n ...(app.services?.map((service) => ({\n entry_point: service.src,\n interface_type: service.type,\n name: service.name,\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 deriveInstallationConfigEntries(\n config: DevServerConfig,\n): {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(\n `Cannot derive entries for unknown installation config appType: ${config.appType}`,\n )\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).\n */\nexport function deriveInstallationConfigs(app: CliConfig['app']): DevServerConfig[] {\n if (!isWorkbenchApp(app)) return []\n const installationConfig = readInstallationConfig(app)\n if (!installationConfig) return []\n return [\n {\n appType: installationConfig.appType,\n fields: installationConfig.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 },\n ]\n}\n"],"names":["isWorkbenchApp","readInstallationConfig","deriveInterfaces","app","options","undefined","isApp","entry","Error","views","map","view","entry_point","src","interface_type","type","name","services","service","deriveInstallationConfigEntries","config","appType","fields","field","deriveInstallationConfigs","installationConfig","public","title","moduleName"],"mappings":"AAEA,SAAQA,cAAc,EAAEC,sBAAsB,QAAO,qBAAoB;AASzE;;;;;;;CAOC,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;YACjB,CAAA,MAAO,EAAE;WACLb,IAAIc,QAAQ,EAAEP,IAAI,CAACQ,UAAa,CAAA;gBAClCN,aAAaM,QAAQL,GAAG;gBACxBC,gBAAgBI,QAAQH,IAAI;gBAC5BC,MAAME,QAAQF,IAAI;YACpB,CAAA,MAAO,EAAE;WACLb,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,SAASG,gCACdC,MAAuB;IAEvB,OAAQA,OAAOC,OAAO;QACpB,KAAK;YAAiB;gBACpB,OAAOD,OAAOE,MAAM,CAACZ,GAAG,CAAC,CAACa,QAAW,CAAA;wBAACP,MAAMO,MAAMP,IAAI;wBAAEH,KAAKU,MAAMV,GAAG;oBAAA,CAAA;YACxE;QACA;YAAS;gBACP,MAAM,IAAIL,MACR,CAAC,+DAA+D,EAAEY,OAAOC,OAAO,EAAE;YAEtF;IACF;AACF;AAEA;;;;;CAKC,GACD,OAAO,SAASG,0BAA0BrB,GAAqB;IAC7D,IAAI,CAACH,eAAeG,MAAM,OAAO,EAAE;IACnC,MAAMsB,qBAAqBxB,uBAAuBE;IAClD,IAAI,CAACsB,oBAAoB,OAAO,EAAE;IAClC,OAAO;QACL;YACEJ,SAASI,mBAAmBJ,OAAO;YACnCC,QAAQG,mBAAmBH,MAAM,CAACZ,GAAG,CAAC,CAACa,QAAW,CAAA;oBAChDP,MAAMO,MAAMP,IAAI;oBAChBU,QAAQH,MAAMG,MAAM;oBACpBb,KAAKU,MAAMV,GAAG;oBACdc,OAAOJ,MAAMI,KAAK;gBACpB,CAAA;YACAC,YAAYzB,IAAIa,IAAI;QACtB;KACD;AACH"}
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,23 +1,23 @@
1
- import { deriveInstallationConfigEntries } from './deriveInterfaces.js';
1
+ import { deriveConfigEntries } from './deriveInterfaces.js';
2
2
  /**
3
3
  * Order-independent id of an app's exposed-module set. Keys each interface by
4
- * its type, name, and source file, and each installation config by its module
4
+ * its type, name, and source file, and each config by its module
5
5
  * identity plus one key per entry (a named source file), so the id changes on
6
6
  * any rebuild-worthy edit — add, remove, rename, repoint, or gaining/losing the
7
7
  * config module — while reordering and HMR content edits keep it stable.
8
- */ export function exposesSetId({ installationConfigs, interfaces }) {
8
+ */ export function exposesSetId({ configs, interfaces }) {
9
9
  const keys = [
10
10
  ...(interfaces ?? []).map((iface)=>[
11
11
  iface.interface_type,
12
12
  iface.name,
13
13
  iface.entry_point
14
14
  ].join('::')),
15
- ...(installationConfigs ?? []).flatMap((config)=>[
15
+ ...(configs ?? []).flatMap((config)=>[
16
16
  [
17
17
  'config',
18
18
  config.appType
19
19
  ].join('::'),
20
- ...deriveInstallationConfigEntries(config).map((entry)=>[
20
+ ...deriveConfigEntries(config).map((entry)=>[
21
21
  'config',
22
22
  config.appType,
23
23
  entry.name,
@@ -42,7 +42,7 @@ import { deriveInstallationConfigEntries } from './deriveInterfaces.js';
42
42
  }
43
43
  const serverKey = (server)=>`${server.id ?? ''}@${server.host ?? ''}:${server.port}`;
44
44
  const serverExposesId = (server)=>exposesSetId({
45
- installationConfigs: server.installationConfigs,
45
+ configs: server.configs,
46
46
  interfaces: server.interfaces
47
47
  });
48
48
  /**
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/dev/exposesSetId.ts"],"sourcesContent":["import {\n deriveInstallationConfigEntries,\n type DevServerConfig,\n type DevServerInterface,\n} from './deriveInterfaces.js'\nimport {type DevServerManifest} from './registry.js'\n\ninterface ExposeSet {\n installationConfigs?: 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 installation 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({installationConfigs, interfaces}: ExposeSet): string {\n const keys = [\n ...(interfaces ?? []).map((iface) =>\n [iface.interface_type, iface.name, iface.entry_point].join('::'),\n ),\n ...(installationConfigs ?? []).flatMap((config) => [\n ['config', config.appType].join('::'),\n ...deriveInstallationConfigEntries(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({installationConfigs: server.installationConfigs, 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":["deriveInstallationConfigEntries","exposesSetId","installationConfigs","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,+BAA+B,QAG1B,wBAAuB;AAQ9B;;;;;;CAMC,GACD,OAAO,SAASC,aAAa,EAACC,mBAAmB,EAAEC,UAAU,EAAY;IACvE,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,uBAAuB,EAAE,AAAD,EAAGS,OAAO,CAAC,CAACC,SAAW;gBACjD;oBAAC;oBAAUA,OAAOC,OAAO;iBAAC,CAACH,IAAI,CAAC;mBAC7BV,gCAAgCY,QAAQP,GAAG,CAAC,CAACS,QAC9C;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,qBAAqBuB,OAAOvB,mBAAmB;QAAEC,YAAYsB,OAAOtB,UAAU;IAAA;AAE9F;;;;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) =>\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"}