@sanity/workbench-cli 1.4.0 → 1.5.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.
@@ -1,3 +1,4 @@
1
+ import { AppVisibility } from "@sanity/cli-core";
1
2
  import { CliConfig } from "@sanity/cli-core";
2
3
  import { PluginOption } from "vite";
3
4
  import { z } from "zod/mini";
@@ -106,6 +107,13 @@ declare const DefineAppInputSchema: z.ZodMiniObject<
106
107
  >
107
108
  >
108
109
  >;
110
+ visibility: z.ZodMiniOptional<
111
+ z.ZodMiniEnum<{
112
+ default: "default";
113
+ unlisted: "unlisted";
114
+ disabled: "disabled";
115
+ }>
116
+ >;
109
117
  },
110
118
  z.core.$strip
111
119
  >;
@@ -137,6 +145,8 @@ declare interface ResolvedWorkbenchApp {
137
145
  readonly isSingleton?: boolean;
138
146
  /** Hostname the application is created at on first deploy. */
139
147
  readonly slug?: string;
148
+ /** Dashboard visibility declared by the app; `undefined` when unset. */
149
+ readonly visibility?: AppVisibility;
140
150
  }
141
151
 
142
152
  /**
@@ -1,7 +1,24 @@
1
+ import { AppVisibility } from "@sanity/cli-core";
1
2
  import { CliConfig } from "@sanity/cli-core";
2
3
  import { Output } from "@sanity/cli-core";
3
4
  import { z } from "zod/mini";
4
5
 
6
+ /** @internal */
7
+ declare type AppInterfaceMetadata = z.infer<typeof AppInterfaceMetadataSchema>;
8
+
9
+ /**
10
+ * The `app` interface's dock-placement metadata. Interface metadata is
11
+ * discriminated on `type`; `app` is the only type with a shape so far.
12
+ * @internal
13
+ */
14
+ declare const AppInterfaceMetadataSchema: z.ZodMiniObject<
15
+ {
16
+ group: z.ZodMiniOptional<z.ZodMiniString<string>>;
17
+ priority: z.ZodMiniOptional<z.ZodMiniNumber<number>>;
18
+ },
19
+ z.core.$strip
20
+ >;
21
+
5
22
  export declare interface Application {
6
23
  id: string;
7
24
  organizationId: string;
@@ -13,16 +30,28 @@ export declare interface Application {
13
30
  declare type ApplicationType = "coreApp" | "studio";
14
31
 
15
32
  /**
16
- * An interface as Brett stores it: the declared `type` (validated server-side,
17
- * not here) and the remote-relative `moduleId` the workbench loads it by the
18
- * host prepends the app's own id.
33
+ * An interface as Brett stores it, discriminated on `type`. `moduleId` is
34
+ * remote-relative the host prepends the app's id. Brett assigns the id.
19
35
  * @internal
20
36
  */
21
- export declare interface BrettInterface {
37
+ export declare type BrettInterface =
38
+ | (BrettInterfaceBase & {
39
+ metadata: AppInterfaceMetadata | null;
40
+ type: "app";
41
+ })
42
+ | (BrettInterfaceBase & {
43
+ metadata: null;
44
+ type: "panel";
45
+ })
46
+ | (BrettInterfaceBase & {
47
+ metadata: null;
48
+ type: "worker";
49
+ });
50
+
51
+ declare interface BrettInterfaceBase {
22
52
  moduleId: string;
23
53
  name: string;
24
54
  title: string;
25
- type: string;
26
55
  version: string;
27
56
  }
28
57
 
@@ -168,6 +197,13 @@ declare const DefineAppInputSchema: z.ZodMiniObject<
168
197
  >
169
198
  >
170
199
  >;
200
+ visibility: z.ZodMiniOptional<
201
+ z.ZodMiniEnum<{
202
+ default: "default";
203
+ unlisted: "unlisted";
204
+ disabled: "disabled";
205
+ }>
206
+ >;
171
207
  },
172
208
  z.core.$strip
173
209
  >;
@@ -233,6 +269,7 @@ export declare function deployCoreApp(options: {
233
269
  sourceDir: string;
234
270
  title: string;
235
271
  version: string;
272
+ visibility?: AppVisibility;
236
273
  }): Promise<{
237
274
  applicationId: string;
238
275
  }>;
@@ -300,6 +337,8 @@ declare interface ResolvedWorkbenchApp {
300
337
  readonly isSingleton?: boolean;
301
338
  /** Hostname the application is created at on first deploy. */
302
339
  readonly slug?: string;
340
+ /** Dashboard visibility declared by the app; `undefined` when unset. */
341
+ readonly visibility?: AppVisibility;
303
342
  }
304
343
 
305
344
  /**
@@ -58,15 +58,57 @@ declare const devServerManifestSchema: z.ZodMiniObject<
58
58
  id: z.ZodMiniOptional<z.ZodMiniString<string>>;
59
59
  interfaces: z.ZodMiniOptional<
60
60
  z.ZodMiniArray<
61
- z.ZodMiniObject<
62
- {
63
- name: z.ZodMiniString<string>;
64
- src: z.ZodMiniString<string>;
65
- title: z.ZodMiniString<string>;
66
- type: z.ZodMiniString<string>;
67
- version: z.ZodMiniOptional<z.ZodMiniNumber<number>>;
68
- },
69
- z.core.$strip
61
+ z.ZodMiniDiscriminatedUnion<
62
+ [
63
+ z.ZodMiniObject<
64
+ {
65
+ metadata: z.ZodMiniNullable<
66
+ z.ZodMiniObject<
67
+ {
68
+ group: z.ZodMiniOptional<z.ZodMiniString<string>>;
69
+ priority: z.ZodMiniOptional<z.ZodMiniNumber<number>>;
70
+ },
71
+ z.core.$strip
72
+ >
73
+ >;
74
+ type: z.ZodMiniLiteral<"app">;
75
+ id: z.ZodMiniString<string>;
76
+ moduleId: z.ZodMiniString<string>;
77
+ name: z.ZodMiniString<string>;
78
+ src: z.ZodMiniString<string>;
79
+ title: z.ZodMiniString<string>;
80
+ version: z.ZodMiniOptional<z.ZodMiniString<string>>;
81
+ },
82
+ z.core.$strip
83
+ >,
84
+ z.ZodMiniObject<
85
+ {
86
+ metadata: z.ZodMiniNull;
87
+ type: z.ZodMiniLiteral<"panel">;
88
+ id: z.ZodMiniString<string>;
89
+ moduleId: z.ZodMiniString<string>;
90
+ name: z.ZodMiniString<string>;
91
+ src: z.ZodMiniString<string>;
92
+ title: z.ZodMiniString<string>;
93
+ version: z.ZodMiniOptional<z.ZodMiniString<string>>;
94
+ },
95
+ z.core.$strip
96
+ >,
97
+ z.ZodMiniObject<
98
+ {
99
+ metadata: z.ZodMiniNull;
100
+ type: z.ZodMiniLiteral<"worker">;
101
+ id: z.ZodMiniString<string>;
102
+ moduleId: z.ZodMiniString<string>;
103
+ name: z.ZodMiniString<string>;
104
+ src: z.ZodMiniString<string>;
105
+ title: z.ZodMiniString<string>;
106
+ version: z.ZodMiniOptional<z.ZodMiniString<string>>;
107
+ },
108
+ z.core.$strip
109
+ >,
110
+ ],
111
+ "type"
70
112
  >
71
113
  >
72
114
  >;
@@ -79,6 +121,7 @@ declare const devServerManifestSchema: z.ZodMiniObject<
79
121
  group: z.ZodMiniOptional<z.ZodMiniString<string>>;
80
122
  icon: z.ZodMiniOptional<z.ZodMiniString<string>>;
81
123
  priority: z.ZodMiniOptional<z.ZodMiniNumber<number>>;
124
+ slug: z.ZodMiniOptional<z.ZodMiniString<string>>;
82
125
  title: z.ZodMiniOptional<z.ZodMiniString<string>>;
83
126
  version: z.ZodMiniString<string>;
84
127
  },
@@ -104,6 +104,13 @@ declare const DefineAppInputSchema: z.ZodMiniObject<
104
104
  >
105
105
  >
106
106
  >;
107
+ visibility: z.ZodMiniOptional<
108
+ z.ZodMiniEnum<{
109
+ default: "default";
110
+ unlisted: "unlisted";
111
+ disabled: "disabled";
112
+ }>
113
+ >;
107
114
  },
108
115
  z.core.$strip
109
116
  >;
@@ -1,3 +1,4 @@
1
+ import { AppVisibility } from "@sanity/cli-core";
1
2
  import { UndeployAdapter } from "@sanity/cli-core/undeploy";
2
3
  import { UndeployApplicationTarget } from "@sanity/cli-core/undeploy";
3
4
  import { UndeployConfigTarget } from "@sanity/cli-core/undeploy";
@@ -121,6 +122,13 @@ declare const DefineAppInputSchema: z.ZodMiniObject<
121
122
  >
122
123
  >
123
124
  >;
125
+ visibility: z.ZodMiniOptional<
126
+ z.ZodMiniEnum<{
127
+ default: "default";
128
+ unlisted: "unlisted";
129
+ disabled: "disabled";
130
+ }>
131
+ >;
124
132
  },
125
133
  z.core.$strip
126
134
  >;
@@ -181,6 +189,8 @@ declare interface ResolvedWorkbenchApp {
181
189
  readonly isSingleton?: boolean;
182
190
  /** Hostname the application is created at on first deploy. */
183
191
  readonly slug?: string;
192
+ /** Dashboard visibility declared by the app; `undefined` when unset. */
193
+ readonly visibility?: AppVisibility;
184
194
  }
185
195
 
186
196
  declare type ViewDeploymentPayload = z.infer<
@@ -1,27 +1,40 @@
1
+ import { interfaceModuleId } from '../../contract.js';
1
2
  /**
2
3
  * The interface records deploy sends: the app view (only when `exposesAppView`),
3
4
  * every view, and every service.
4
5
  * @internal
5
6
  */ 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
7
  const records = [];
14
8
  if (exposesAppView) {
15
9
  records.push({
16
- moduleId: 'App',
10
+ metadata: null,
11
+ moduleId: interfaceModuleId('app', appName),
17
12
  name: appName,
18
13
  title: appTitle,
19
14
  type: 'app',
20
15
  version
21
16
  });
22
17
  }
23
- for (const view of exposes.views ?? [])records.push(toRecord('views', view));
24
- for (const service of exposes.services ?? [])records.push(toRecord('services', service));
18
+ for (const view of exposes.views ?? []){
19
+ records.push({
20
+ metadata: null,
21
+ moduleId: interfaceModuleId('panel', view.name),
22
+ name: view.name,
23
+ title: view.title ?? view.name,
24
+ type: 'panel',
25
+ version
26
+ });
27
+ }
28
+ for (const service of exposes.services ?? []){
29
+ records.push({
30
+ metadata: null,
31
+ moduleId: interfaceModuleId('worker', service.name),
32
+ name: service.name,
33
+ title: service.title ?? service.name,
34
+ type: 'worker',
35
+ version
36
+ });
37
+ }
25
38
  return records;
26
39
  }
27
40
  const label = (item)=>item.title === item.name ? item.name : `${item.title} (${item.name})`;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/deploy/buildExposes.ts"],"sourcesContent":["import {type WorkbenchExposes} from '../../resolveWorkbenchApp.js'\nimport {type BrettInterface} from '../../services/applications.js'\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\nconst label = (item: {name: string; title: string}) =>\n item.title === item.name ? item.name : `${item.title} (${item.name})`\n\n/**\n * One `Title (name): src` report line per declared entry point.\n * @internal\n */\nexport function summarizeExposeGroup(\n heading: string,\n items: readonly {name: string; src: string; title: string}[],\n): string {\n return `${heading}:\\n${items.map((item) => ` ${label(item)}: ${item.src}`).join('\\n')}`\n}\n\n/**\n * 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","label","item","summarizeExposeGroup","heading","items","map","src","join","summarizeExposes","toExpose","viewExposes","serviceExposes","lines","length"],"mappings":"AAWA;;;;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,MAAMM,QAAQ,CAACC,OACbA,KAAKT,KAAK,KAAKS,KAAKV,IAAI,GAAGU,KAAKV,IAAI,GAAG,GAAGU,KAAKT,KAAK,CAAC,EAAE,EAAES,KAAKV,IAAI,CAAC,CAAC,CAAC;AAEvE;;;CAGC,GACD,OAAO,SAASW,qBACdC,OAAe,EACfC,KAA4D;IAE5D,OAAO,GAAGD,QAAQ,GAAG,EAAEC,MAAMC,GAAG,CAAC,CAACJ,OAAS,CAAC,EAAE,EAAED,MAAMC,MAAM,EAAE,EAAEA,KAAKK,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,CAACO,qBAAqB,SAASQ;IACrE,IAAIC,eAAeE,MAAM,GAAG,GAAGD,MAAMjB,IAAI,CAACO,qBAAqB,YAAYS;IAC3E,OAAO;QAAC7B,SAAS;eAAI4B;eAAgBC;SAAe;QAAEC;IAAK;AAC7D"}
1
+ {"version":3,"sources":["../../../src/actions/deploy/buildExposes.ts"],"sourcesContent":["import {interfaceModuleId} from '../../contract.js'\nimport {type WorkbenchExposes} from '../../resolveWorkbenchApp.js'\nimport {type BrettInterface} from '../../services/applications.js'\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 records: BrettInterface[] = []\n if (exposesAppView) {\n records.push({\n metadata: null,\n moduleId: interfaceModuleId('app', appName),\n name: appName,\n title: appTitle,\n type: 'app',\n version,\n })\n }\n for (const view of exposes.views ?? []) {\n records.push({\n metadata: null,\n moduleId: interfaceModuleId('panel', view.name),\n name: view.name,\n title: view.title ?? view.name,\n type: 'panel',\n version,\n })\n }\n for (const service of exposes.services ?? []) {\n records.push({\n metadata: null,\n moduleId: interfaceModuleId('worker', service.name),\n name: service.name,\n title: service.title ?? service.name,\n type: 'worker',\n version,\n })\n }\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\nconst label = (item: {name: string; title: string}) =>\n item.title === item.name ? item.name : `${item.title} (${item.name})`\n\n/**\n * One `Title (name): src` report line per declared entry point.\n * @internal\n */\nexport function summarizeExposeGroup(\n heading: string,\n items: readonly {name: string; src: string; title: string}[],\n): string {\n return `${heading}:\\n${items.map((item) => ` ${label(item)}: ${item.src}`).join('\\n')}`\n}\n\n/**\n * 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":["interfaceModuleId","buildExposes","exposes","appName","appTitle","exposesAppView","version","records","push","metadata","moduleId","name","title","type","view","views","service","services","label","item","summarizeExposeGroup","heading","items","map","src","join","summarizeExposes","toExpose","decl","viewExposes","serviceExposes","lines","length"],"mappings":"AAAA,SAAQA,iBAAiB,QAAO,oBAAmB;AAYnD;;;;CAIC,GACD,OAAO,SAASC,aACdC,OAAyB,EACzB,EAACC,OAAO,EAAEC,QAAQ,EAAEC,cAAc,EAAEC,OAAO,EAAsB;IAEjE,MAAMC,UAA4B,EAAE;IACpC,IAAIF,gBAAgB;QAClBE,QAAQC,IAAI,CAAC;YACXC,UAAU;YACVC,UAAUV,kBAAkB,OAAOG;YACnCQ,MAAMR;YACNS,OAAOR;YACPS,MAAM;YACNP;QACF;IACF;IACA,KAAK,MAAMQ,QAAQZ,QAAQa,KAAK,IAAI,EAAE,CAAE;QACtCR,QAAQC,IAAI,CAAC;YACXC,UAAU;YACVC,UAAUV,kBAAkB,SAASc,KAAKH,IAAI;YAC9CA,MAAMG,KAAKH,IAAI;YACfC,OAAOE,KAAKF,KAAK,IAAIE,KAAKH,IAAI;YAC9BE,MAAM;YACNP;QACF;IACF;IACA,KAAK,MAAMU,WAAWd,QAAQe,QAAQ,IAAI,EAAE,CAAE;QAC5CV,QAAQC,IAAI,CAAC;YACXC,UAAU;YACVC,UAAUV,kBAAkB,UAAUgB,QAAQL,IAAI;YAClDA,MAAMK,QAAQL,IAAI;YAClBC,OAAOI,QAAQJ,KAAK,IAAII,QAAQL,IAAI;YACpCE,MAAM;YACNP;QACF;IACF;IACA,OAAOC;AACT;AAUA,MAAMW,QAAQ,CAACC,OACbA,KAAKP,KAAK,KAAKO,KAAKR,IAAI,GAAGQ,KAAKR,IAAI,GAAG,GAAGQ,KAAKP,KAAK,CAAC,EAAE,EAAEO,KAAKR,IAAI,CAAC,CAAC,CAAC;AAEvE;;;CAGC,GACD,OAAO,SAASS,qBACdC,OAAe,EACfC,KAA4D;IAE5D,OAAO,GAAGD,QAAQ,GAAG,EAAEC,MAAMC,GAAG,CAAC,CAACJ,OAAS,CAAC,EAAE,EAAED,MAAMC,MAAM,EAAE,EAAEA,KAAKK,GAAG,EAAE,EAAEC,IAAI,CAAC,OAAO;AAC1F;AAEA;;;;CAIC,GACD,OAAO,SAASC,iBAAiB,EAACT,QAAQ,EAAEF,KAAK,EAAmB;IAIlE,MAAMY,WAAW,CAACC,OAKK,CAAA;YACrBjB,MAAMiB,KAAKjB,IAAI;YACfa,KAAKI,KAAKJ,GAAG;YACbZ,OAAOgB,KAAKhB,KAAK,IAAIgB,KAAKjB,IAAI;YAC9BE,MAAMe,KAAKf,IAAI;QACjB,CAAA;IACA,MAAMgB,cAAc,AAACd,CAAAA,SAAS,EAAE,AAAD,EAAGQ,GAAG,CAAC,CAACT,OAASa,SAASb;IACzD,MAAMgB,iBAAiB,AAACb,CAAAA,YAAY,EAAE,AAAD,EAAGM,GAAG,CAAC,CAACP,UAAYW,SAASX;IAElE,MAAMe,QAAkB,EAAE;IAC1B,IAAIF,YAAYG,MAAM,GAAG,GAAGD,MAAMvB,IAAI,CAACY,qBAAqB,SAASS;IACrE,IAAIC,eAAeE,MAAM,GAAG,GAAGD,MAAMvB,IAAI,CAACY,qBAAqB,YAAYU;IAC3E,OAAO;QAAC5B,SAAS;eAAI2B;eAAgBC;SAAe;QAAEC;IAAK;AAC7D"}
@@ -10,7 +10,7 @@ import { createApplication, createDeployment } from '../../services/applications
10
10
  * shell to report.
11
11
  * @internal
12
12
  */ export async function deployCoreApp(options) {
13
- const { appId, interfaces, isAutoUpdating, isSingleton, organizationId, slug, sourceDir, title, version } = options;
13
+ const { appId, interfaces, isAutoUpdating, isSingleton, organizationId, slug, sourceDir, title, version, visibility } = options;
14
14
  const tarball = pack(dirname(sourceDir), {
15
15
  entries: [
16
16
  basename(sourceDir)
@@ -39,7 +39,8 @@ import { createApplication, createDeployment } from '../../services/applications
39
39
  tarball,
40
40
  title,
41
41
  type: 'coreApp',
42
- version
42
+ version,
43
+ visibility
43
44
  });
44
45
  spin.succeed();
45
46
  return {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/deploy/deployWorkbenchApp.ts"],"sourcesContent":["import {basename, dirname} from 'node:path'\nimport {createGzip} from 'node:zlib'\n\nimport {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
+ {"version":3,"sources":["../../../src/actions/deploy/deployWorkbenchApp.ts"],"sourcesContent":["import {basename, dirname} from 'node:path'\nimport {createGzip} from 'node:zlib'\n\nimport {type AppVisibility, 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 visibility?: AppVisibility\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 visibility,\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 visibility,\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","visibility","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,SAA4BC,SAAS,QAAoB,mBAAkB;AAC3E,SAAQC,OAAO,QAAO,sBAAqB;AAC3C,SAAQC,IAAI,QAAO,SAAQ;AAE3B,SAGEC,iBAAiB,EACjBC,gBAAgB,QACX,iCAAgC;AAEvC;;;;;CAKC,GACD,OAAO,eAAeC,cAAcC,OAWnC;IACC,MAAM,EACJC,KAAK,EACLC,UAAU,EACVC,cAAc,EACdC,WAAW,EACXC,cAAc,EACdC,IAAI,EACJC,SAAS,EACTC,KAAK,EACLC,OAAO,EACPC,UAAU,EACX,GAAGV;IACJ,MAAMW,UAAUf,KAAKJ,QAAQe,YAAY;QAACK,SAAS;YAACrB,SAASgB;SAAW;IAAA,GAAGM,IAAI,CAACpB;IAEhF,MAAMqB,OAAOnB,QAAQ,gBAAgBoB,KAAK;IAC1C,IAAI;QACF,IAAId,OAAO;YACT,MAAMH,iBAAiB;gBAACkB,eAAef;gBAAOC;gBAAYC;gBAAgBQ;gBAASF;YAAO;YAC1FK,KAAKG,OAAO;YACZ,OAAO;gBAACD,eAAef;YAAK;QAC9B;QAEA,MAAM,EAACiB,EAAE,EAAC,GAAG,MAAMrB,kBAAkB;YACnCK;YACAE;YACAC;YACAC;YACAK;YACAH;YACAW,MAAM;YACNV;YACAC;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,aAAatB,OAYlC;IACC,MAAM,EACJC,KAAK,EACLC,UAAU,EACVC,cAAc,EACdE,cAAc,EACdkB,MAAM,EACNC,SAAS,EACTjB,SAAS,EACTkB,UAAU,EACVjB,KAAK,EACLC,OAAO,EACPiB,UAAU,EACX,GAAG1B;IACJ,MAAMW,UAAUf,KAAKJ,QAAQe,YAAY;QAACK,SAAS;YAACrB,SAASgB;SAAW;IAAA,GAAGM,IAAI,CAACpB;IAEhF,MAAMqB,OAAOnB,QAAQ,8BAA8BoB,KAAK;IACxD,IAAI;QACF,IAAId,OAAO;YACT,MAAMH,iBAAiB;gBACrBkB,eAAef;gBACfC;gBACAC;gBACAQ;gBACAF;gBACAiB;YACF;YACAZ,KAAKG,OAAO;YACZ,OAAO;gBAACD,eAAef;YAAK;QAC9B;QAEA,IAAI,CAACwB,YAAY;YACfX,KAAKa,IAAI;YACT,OAAOJ,OAAOH,KAAK,CACjB,wFACA;gBAACQ,MAAMlC,UAAUmC,WAAW;YAAA;QAEhC;QAEA,MAAMC,cAAc,MAAMjC,kBAAkB;YAC1CK;YACAG;YACAmB;YACAlB,MAAMmB;YACNd;YACAH;YACAW,MAAM;YACNV;YACAiB;QACF;QACAZ,KAAKG,OAAO;QACZ,OAAO;YAACD,eAAec,YAAYZ,EAAE;QAAA;IACvC,EAAE,OAAOE,OAAO;QACdN,KAAKa,IAAI;QACT,MAAMP;IACR;AACF"}
@@ -1,37 +1,53 @@
1
1
  import { hash } from 'node:crypto';
2
- import { MEDIA_LIBRARY_CONFIG_CONTRACT_VERSION, SERVICE_CONTRACT_VERSION, VIEW_CONTRACT_VERSION } from '../../contract.js';
2
+ import { interfaceModuleId, MEDIA_LIBRARY_CONFIG_CONTRACT_VERSION, SERVICE_CONTRACT_VERSION, VIEW_CONTRACT_VERSION } from '../../contract.js';
3
3
  import { isWorkbenchApp, readConfig } from '../../defineApp.js';
4
4
  /**
5
- * Map a workbench app's declarations to the interface records forwarded on its
6
- * registry entry: `views` → panels, `services` → workers, `entry` → the
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
11
- * view has no versioned contract, so it carries none.
5
+ * Map a workbench app's declarations to its registry interface records:
6
+ * `views` → panels, `services` → workers, `entry` → the `app` view. Each mirrors
7
+ * a deployed record so the workbench loads a local interface like a deployed one.
8
+ * `undefined` for a non-branded app; a studio that declares `entry` is rejected
9
+ * (studio app views aren't implemented yet).
12
10
  */ export function deriveInterfaces(app, options) {
13
11
  if (!isWorkbenchApp(app)) return undefined;
14
12
  if (!options.isApp && app.entry !== undefined) {
15
13
  throw new Error('App views for studios are not implemented yet');
16
14
  }
17
- const toInterface = ({ name, src, title, type }, version)=>({
18
- name,
19
- src,
20
- title: title ?? name,
21
- type,
22
- version
23
- });
15
+ const interfaceId = (type, name)=>`${app.name}-${type}-${name}`;
16
+ const views = (app.views ?? []).map((view)=>({
17
+ id: interfaceId('panel', view.name),
18
+ metadata: null,
19
+ moduleId: interfaceModuleId('panel', view.name),
20
+ name: view.name,
21
+ src: view.src,
22
+ title: view.title ?? view.name,
23
+ type: 'panel',
24
+ version: String(VIEW_CONTRACT_VERSION)
25
+ }));
26
+ const services = (app.services ?? []).map((service)=>({
27
+ id: interfaceId('worker', service.name),
28
+ metadata: null,
29
+ moduleId: interfaceModuleId('worker', service.name),
30
+ name: service.name,
31
+ src: service.src,
32
+ title: service.title ?? service.name,
33
+ type: 'worker',
34
+ version: String(SERVICE_CONTRACT_VERSION)
35
+ }));
36
+ const appView = app.entry === undefined ? [] : [
37
+ {
38
+ id: interfaceId('app', app.name),
39
+ metadata: null,
40
+ moduleId: interfaceModuleId('app', app.name),
41
+ name: app.name,
42
+ src: app.entry,
43
+ title: app.title,
44
+ type: 'app'
45
+ }
46
+ ];
24
47
  return [
25
- ...(app.views ?? []).map((view)=>toInterface(view, VIEW_CONTRACT_VERSION)),
26
- ...(app.services ?? []).map((service)=>toInterface(service, SERVICE_CONTRACT_VERSION)),
27
- ...app.entry === undefined ? [] : [
28
- {
29
- name: app.name,
30
- src: app.entry,
31
- title: app.title,
32
- type: 'app'
33
- }
34
- ]
48
+ ...views,
49
+ ...services,
50
+ ...appView
35
51
  ];
36
52
  }
37
53
  /**
@@ -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. `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"}
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 interfaceModuleId,\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 its registry interface records:\n * `views` → panels, `services` → workers, `entry` → the `app` view. Each mirrors\n * a deployed record so the workbench loads a local interface like a deployed one.\n * `undefined` for a non-branded app; a studio that declares `entry` is rejected\n * (studio app views aren't implemented yet).\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 interfaceId = (type: string, name: string): string => `${app.name}-${type}-${name}`\n\n const views = (app.views ?? []).map(\n (view): DevServerInterface => ({\n id: interfaceId('panel', view.name),\n metadata: null,\n moduleId: interfaceModuleId('panel', view.name),\n name: view.name,\n src: view.src,\n title: view.title ?? view.name,\n type: 'panel',\n version: String(VIEW_CONTRACT_VERSION),\n }),\n )\n\n const services = (app.services ?? []).map(\n (service): DevServerInterface => ({\n id: interfaceId('worker', service.name),\n metadata: null,\n moduleId: interfaceModuleId('worker', service.name),\n name: service.name,\n src: service.src,\n title: service.title ?? service.name,\n type: 'worker',\n version: String(SERVICE_CONTRACT_VERSION),\n }),\n )\n\n const appView: DevServerInterface[] =\n app.entry === undefined\n ? []\n : [\n {\n id: interfaceId('app', app.name),\n metadata: null,\n moduleId: interfaceModuleId('app', app.name),\n name: app.name,\n src: app.entry,\n title: app.title,\n type: 'app',\n },\n ]\n\n return [...views, ...services, ...appView]\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","interfaceModuleId","MEDIA_LIBRARY_CONFIG_CONTRACT_VERSION","SERVICE_CONTRACT_VERSION","VIEW_CONTRACT_VERSION","isWorkbenchApp","readConfig","deriveInterfaces","app","options","undefined","isApp","entry","Error","interfaceId","type","name","views","map","view","id","metadata","moduleId","src","title","version","String","services","service","appView","deriveConfigEntries","config","appType","fields","field","deriveConfigs","public","moduleName","JSON","stringify"],"mappings":"AAAA,SAAQA,IAAI,QAAO,cAAa;AAIhC,SACEC,iBAAiB,EACjBC,qCAAqC,EACrCC,wBAAwB,EACxBC,qBAAqB,QAChB,oBAAmB;AAC1B,SAAQC,cAAc,EAAEC,UAAU,QAAO,qBAAoB;AAS7D;;;;;;CAMC,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,CAACC,MAAcC,OAAyB,GAAGR,IAAIQ,IAAI,CAAC,CAAC,EAAED,KAAK,CAAC,EAAEC,MAAM;IAEzF,MAAMC,QAAQ,AAACT,CAAAA,IAAIS,KAAK,IAAI,EAAE,AAAD,EAAGC,GAAG,CACjC,CAACC,OAA8B,CAAA;YAC7BC,IAAIN,YAAY,SAASK,KAAKH,IAAI;YAClCK,UAAU;YACVC,UAAUrB,kBAAkB,SAASkB,KAAKH,IAAI;YAC9CA,MAAMG,KAAKH,IAAI;YACfO,KAAKJ,KAAKI,GAAG;YACbC,OAAOL,KAAKK,KAAK,IAAIL,KAAKH,IAAI;YAC9BD,MAAM;YACNU,SAASC,OAAOtB;QAClB,CAAA;IAGF,MAAMuB,WAAW,AAACnB,CAAAA,IAAImB,QAAQ,IAAI,EAAE,AAAD,EAAGT,GAAG,CACvC,CAACU,UAAiC,CAAA;YAChCR,IAAIN,YAAY,UAAUc,QAAQZ,IAAI;YACtCK,UAAU;YACVC,UAAUrB,kBAAkB,UAAU2B,QAAQZ,IAAI;YAClDA,MAAMY,QAAQZ,IAAI;YAClBO,KAAKK,QAAQL,GAAG;YAChBC,OAAOI,QAAQJ,KAAK,IAAII,QAAQZ,IAAI;YACpCD,MAAM;YACNU,SAASC,OAAOvB;QAClB,CAAA;IAGF,MAAM0B,UACJrB,IAAII,KAAK,KAAKF,YACV,EAAE,GACF;QACE;YACEU,IAAIN,YAAY,OAAON,IAAIQ,IAAI;YAC/BK,UAAU;YACVC,UAAUrB,kBAAkB,OAAOO,IAAIQ,IAAI;YAC3CA,MAAMR,IAAIQ,IAAI;YACdO,KAAKf,IAAII,KAAK;YACdY,OAAOhB,IAAIgB,KAAK;YAChBT,MAAM;QACR;KACD;IAEP,OAAO;WAAIE;WAAUU;WAAaE;KAAQ;AAC5C;AAEA;;;;;CAKC,GACD,OAAO,SAASC,oBAAoBC,MAAuB;IACzD,OAAQA,OAAOC,OAAO;QACpB,KAAK;YAAiB;gBACpB,OAAOD,OAAOE,MAAM,CAACf,GAAG,CAAC,CAACgB,QAAW,CAAA;wBAAClB,MAAMkB,MAAMlB,IAAI;wBAAEO,KAAKW,MAAMX,GAAG;oBAAA,CAAA;YACxE;QACA;YAAS;gBACP,MAAM,IAAIV,MAAM,CAAC,kDAAkD,EAAEkB,OAAOC,OAAO,EAAE;YACvF;IACF;AACF;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASG,cAAc3B,GAAqB;IACjD,IAAI,CAACH,eAAeG,MAAM,OAAO,EAAE;IACnC,MAAMuB,SAASzB,WAAWE;IAC1B,IAAI,CAACuB,QAAQ,OAAO,EAAE;IACtB,MAAMnB,QAAQ;QACZoB,SAASD,OAAOC,OAAO;QACvBC,QAAQF,OAAOE,MAAM,CAACf,GAAG,CAAC,CAACgB,QAAW,CAAA;gBACpClB,MAAMkB,MAAMlB,IAAI;gBAChBoB,QAAQF,MAAME,MAAM;gBACpBb,KAAKW,MAAMX,GAAG;gBACdC,OAAOU,MAAMV,KAAK;YACpB,CAAA;QACAa,YAAY7B,IAAIQ,IAAI;QACpBS,SAASvB;IACX;IACA,OAAO;QAAC;YAAC,GAAGU,KAAK;YAAEQ,IAAIpB,KAAK,QAAQsC,KAAKC,SAAS,CAAC3B;QAAO;KAAE;AAC9D"}
@@ -2,6 +2,7 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, watch, wr
2
2
  import { join } from 'node:path';
3
3
  import { coreAppManifestSchema, getSanityDataDir, studioManifestSchema, subdebug } from '@sanity/cli-core';
4
4
  import { z } from 'zod/mini';
5
+ import { AppInterfaceMetadataSchema } from '../../contract.js';
5
6
  import { canonicalizeWatchDir } from './canonicalizeWatchDir.js';
6
7
  import { getProcessStartTime, isOurProcess } from './processLiveness.js';
7
8
  const devDebug = subdebug('dev');
@@ -14,6 +15,34 @@ const devDebug = subdebug('dev');
14
15
  */ function ownStartedAt() {
15
16
  return (getProcessStartTime(process.pid) ?? new Date()).toISOString();
16
17
  }
18
+ const interfaceBaseFields = {
19
+ /** CLI-minted for a local interface; a deployed one gets its id from Brett. */ id: z.string(),
20
+ moduleId: z.string(),
21
+ name: z.string(),
22
+ /** Raw source vite serves; a deployed interface carries only the `moduleId`. */ src: z.string(),
23
+ title: z.string(),
24
+ version: z.optional(z.string())
25
+ };
26
+ /**
27
+ * A forwarded interface, discriminated on `type`. Kept outside the manifest so
28
+ * the workbench renders local panels and runs workers without a deploy.
29
+ */ const devServerInterfaceSchema = z.discriminatedUnion('type', [
30
+ z.object({
31
+ ...interfaceBaseFields,
32
+ metadata: z.nullable(AppInterfaceMetadataSchema),
33
+ type: z.literal('app')
34
+ }),
35
+ z.object({
36
+ ...interfaceBaseFields,
37
+ metadata: z.null(),
38
+ type: z.literal('panel')
39
+ }),
40
+ z.object({
41
+ ...interfaceBaseFields,
42
+ metadata: z.null(),
43
+ type: z.literal('worker')
44
+ })
45
+ ]);
17
46
  const devServerManifestSchema = z.object({
18
47
  /**
19
48
  * Field schema *values* load from the federation module; each field's `src`
@@ -40,24 +69,7 @@ const devServerManifestSchema = z.object({
40
69
  }))),
41
70
  host: z.string(),
42
71
  id: z.optional(z.string()),
43
- /**
44
- * Interfaces the app exposes, mapped from the declared `views` (dock panels,
45
- * `type: "panel"`) and `services` (background workers,
46
- * `type: "worker"`). A service is just an interface, so both live
47
- * in this one list. Carried separately from the manifest — interfaces live in
48
- * the application service, not the manifest — so the workbench can render
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
- */ interfaces: z.optional(z.array(z.object({
53
- name: z.string(),
54
- src: z.string(),
55
- title: z.string(),
56
- type: z.string(),
57
- // Contract version the interface's generated module exports; the app
58
- // view has no versioned contract and carries none.
59
- version: z.optional(z.number())
60
- }))),
72
+ interfaces: z.optional(z.array(devServerInterfaceSchema)),
61
73
  /**
62
74
  * Inlined manifest — either a {@link StudioManifest} or {@link CoreAppManifest},
63
75
  * validated against the shared cli-core schemas. The registry stores and
@@ -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 * `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":"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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 {AppInterfaceMetadataSchema} from '../../contract.js'\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 interfaceBaseFields = {\n /** CLI-minted for a local interface; a deployed one gets its id from Brett. */\n id: z.string(),\n moduleId: z.string(),\n name: z.string(),\n /** Raw source vite serves; a deployed interface carries only the `moduleId`. */\n src: z.string(),\n title: z.string(),\n version: z.optional(z.string()),\n}\n\n/**\n * A forwarded interface, discriminated on `type`. Kept outside the manifest so\n * the workbench renders local panels and runs workers without a deploy.\n */\nconst devServerInterfaceSchema = z.discriminatedUnion('type', [\n z.object({\n ...interfaceBaseFields,\n metadata: z.nullable(AppInterfaceMetadataSchema),\n type: z.literal('app'),\n }),\n z.object({...interfaceBaseFields, metadata: z.null(), type: z.literal('panel')}),\n z.object({...interfaceBaseFields, metadata: z.null(), type: z.literal('worker')}),\n])\n\nconst devServerManifestSchema = z.object({\n /**\n * Field schema *values* load from the federation module; each field's `src`\n * rides along so a repoint bumps the exposes-set id and forces a rebuild.\n * Lenient — the workbench is the authority.\n */\n configs: z.optional(\n z.array(\n z.object({\n // Identifies the owning app when it has no app id (singletons).\n appType: z.optional(z.string()),\n fields: z.array(\n z.object({\n name: z.string(),\n public: z.optional(z.boolean()),\n src: z.string(),\n title: z.string(),\n }),\n ),\n // Content hash of the config — the workbench's change-detection key\n // (see deriveConfigs).\n id: z.string(),\n // The app's `unstable_defineApp` 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 interfaces: z.optional(z.array(devServerInterfaceSchema)),\n /**\n * Inlined manifest — either a {@link StudioManifest} or {@link CoreAppManifest},\n * validated against the shared cli-core schemas. The registry stores and\n * rebroadcasts it; the CLI is what extracts and writes it.\n */\n manifest: z.optional(z.union([studioManifestSchema, coreAppManifestSchema])),\n /**\n * ISO timestamp of the most recent successful manifest extraction. Bumped\n * on every regeneration so re-writing this registry entry triggers the\n * workbench `watchRegistry` watcher and forces a rebroadcast to clients.\n */\n manifestUpdatedAt: z.optional(z.string()),\n pid: z.number(),\n port: z.number(),\n projectId: z.optional(z.string()),\n startedAt: z.string(),\n type: z.enum(['coreApp', 'studio']),\n version: z.literal(REGISTRY_VERSION),\n workDir: z.string(),\n})\n/**\n * A manifest describing a running dev server process (studio or app).\n * Stored as `~/.sanity/dev-servers/<pid>.json`.\n *\n * The workbench singleton is tracked separately via the lock file — see\n * `acquireWorkbenchLock` and `readWorkbenchLock` below.\n */\nexport type DevServerManifest = z.infer<typeof devServerManifestSchema>\n\n/**\n * 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","AppInterfaceMetadataSchema","canonicalizeWatchDir","getProcessStartTime","isOurProcess","devDebug","REGISTRY_VERSION","ownStartedAt","process","pid","Date","toISOString","interfaceBaseFields","id","string","moduleId","name","src","title","version","optional","devServerInterfaceSchema","discriminatedUnion","object","metadata","nullable","type","literal","null","devServerManifestSchema","configs","array","appType","fields","public","boolean","moduleName","number","host","interfaces","manifest","union","manifestUpdatedAt","port","projectId","startedAt","enum","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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"}
package/dist/contract.js CHANGED
@@ -15,6 +15,38 @@ import { z } from 'zod/mini';
15
15
  'panel'
16
16
  ]
17
17
  };
18
+ /**
19
+ * The `app` interface's dock-placement metadata. Interface metadata is
20
+ * discriminated on `type`; `app` is the only type with a shape so far.
21
+ * @internal
22
+ */ export const AppInterfaceMetadataSchema = z.object({
23
+ group: z.optional(z.string()),
24
+ priority: z.optional(z.number())
25
+ });
26
+ /**
27
+ * The module-federation id a build exposes an interface at. Dev stamps the same
28
+ * id a deploy would, so the workbench loads a local interface like a deployed one.
29
+ * @internal
30
+ */ export function interfaceModuleId(type, name) {
31
+ switch(type){
32
+ case 'app':
33
+ {
34
+ return 'App';
35
+ }
36
+ case 'panel':
37
+ {
38
+ return `views/${name}`;
39
+ }
40
+ case 'worker':
41
+ {
42
+ return `services/${name}`;
43
+ }
44
+ default:
45
+ {
46
+ throw new Error(`Cannot derive a moduleId for unknown interface type: ${type}`);
47
+ }
48
+ }
49
+ }
18
50
  // Shared `name` + `src`; `kind` only tailors the validation message.
19
51
  function extensionDeclarationFields(kind) {
20
52
  const pattern = /^[a-zA-Z0-9_-]+$/;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/contract.ts"],"sourcesContent":["import {z} from 'zod/mini'\n\n// Shared module-federation extension contract: interface (view/service) and\n// config declaration schemas, plus the versions the build stamps.\n// `zod/mini` keeps the bundle small.\n\n/** @internal */\nexport const VIEW_CONTRACT_VERSION = 1\n\n/** @internal */\nexport const SERVICE_CONTRACT_VERSION = 1\n\n/** @internal */\nexport const MEDIA_LIBRARY_CONFIG_CONTRACT_VERSION = 1\n\n/**\n * A view component. The return is opaque so the runtime helpers carry no React\n * dependency — the generated artifact renders it with the app's own React.\n * @public\n */\nexport type ViewComponent<TProps> = (props: TProps) => unknown\n\n/** @public */\nexport interface ViewComponentBaseProps<TView> {\n view: TView\n}\n\n/**\n * Component slots each interface type exposes, in render order. Source of truth\n * for {@link InterfaceType} and the build; add a type by registering it here.\n * @internal\n */\nexport const VIEW_COMPONENTS = {\n panel: ['title', 'panel'],\n} as const satisfies Record<string, readonly string[]>\n\n/** @public */\nexport type InterfaceType = keyof typeof VIEW_COMPONENTS\n\n/** @public */\nexport type ServiceType = 'worker'\n\n// Shared `name` + `src`; `kind` only tailors the validation message.\nfunction extensionDeclarationFields(kind: 'Field' | 'Service' | 'View') {\n const pattern = /^[a-zA-Z0-9_-]+$/\n return {\n name: z.string().check(z.regex(pattern, `${kind} \\`name\\` must match ${pattern}`)),\n src: z.string(),\n }\n}\n\n// Every interface (view, service) shares `name` + `src` + an optional display\n// `title` that defaults to `name` on deploy.\nfunction interfaceDeclarationFields(kind: 'Service' | 'View') {\n return {...extensionDeclarationFields(kind), title: z.optional(z.string())}\n}\n\nconst PanelViewSchema = z.object({\n type: z.literal('panel'),\n ...interfaceDeclarationFields('View'),\n})\n\n/** @internal */\nexport const InterfaceDeclarationSchema = z.discriminatedUnion('type', [PanelViewSchema])\n\nconst WorkerServiceSchema = z.object({\n type: z.literal('worker'),\n ...interfaceDeclarationFields('Service'),\n})\n\n/** @internal */\nexport const ServiceDeclarationSchema = z.discriminatedUnion('type', [WorkerServiceSchema])\n\nconst MediaLibraryFieldSchema = z.object({\n ...extensionDeclarationFields('Field'),\n public: z.optional(z.boolean()),\n title: z.string(),\n})\n\n/**\n * Stamped where the config crosses a boundary so the authoring model doesn't carry a constant discriminator.\n * @internal\n */\nexport const INSTALLATION_CONFIG_TYPE = 'installation_config'\n\n// `appType` is stamped by `unstable_defineMediaLibrary`, never authored.\nconst MediaLibraryConfigSchema = z.object({\n appType: z.literal('media-library'),\n fields: z\n .array(MediaLibraryFieldSchema)\n .check(\n z.refine(\n (fields) => new Set(fields.map((field) => field.name)).size === fields.length,\n 'Field `name` must be unique within a media library',\n ),\n ),\n})\n\n/**\n * An app's optional config, keyed by `appType`; deploys as a versioned snapshot, not an interface.\n * @internal\n */\nexport const ConfigSchema = z.discriminatedUnion('appType', [MediaLibraryConfigSchema])\n"],"names":["z","VIEW_CONTRACT_VERSION","SERVICE_CONTRACT_VERSION","MEDIA_LIBRARY_CONFIG_CONTRACT_VERSION","VIEW_COMPONENTS","panel","extensionDeclarationFields","kind","pattern","name","string","check","regex","src","interfaceDeclarationFields","title","optional","PanelViewSchema","object","type","literal","InterfaceDeclarationSchema","discriminatedUnion","WorkerServiceSchema","ServiceDeclarationSchema","MediaLibraryFieldSchema","public","boolean","INSTALLATION_CONFIG_TYPE","MediaLibraryConfigSchema","appType","fields","array","refine","Set","map","field","size","length","ConfigSchema"],"mappings":"AAAA,SAAQA,CAAC,QAAO,WAAU;AAE1B,4EAA4E;AAC5E,kEAAkE;AAClE,qCAAqC;AAErC,cAAc,GACd,OAAO,MAAMC,wBAAwB,EAAC;AAEtC,cAAc,GACd,OAAO,MAAMC,2BAA2B,EAAC;AAEzC,cAAc,GACd,OAAO,MAAMC,wCAAwC,EAAC;AActD;;;;CAIC,GACD,OAAO,MAAMC,kBAAkB;IAC7BC,OAAO;QAAC;QAAS;KAAQ;AAC3B,EAAsD;AAQtD,qEAAqE;AACrE,SAASC,2BAA2BC,IAAkC;IACpE,MAAMC,UAAU;IAChB,OAAO;QACLC,MAAMT,EAAEU,MAAM,GAAGC,KAAK,CAACX,EAAEY,KAAK,CAACJ,SAAS,GAAGD,KAAK,qBAAqB,EAAEC,SAAS;QAChFK,KAAKb,EAAEU,MAAM;IACf;AACF;AAEA,8EAA8E;AAC9E,6CAA6C;AAC7C,SAASI,2BAA2BP,IAAwB;IAC1D,OAAO;QAAC,GAAGD,2BAA2BC,KAAK;QAAEQ,OAAOf,EAAEgB,QAAQ,CAAChB,EAAEU,MAAM;IAAG;AAC5E;AAEA,MAAMO,kBAAkBjB,EAAEkB,MAAM,CAAC;IAC/BC,MAAMnB,EAAEoB,OAAO,CAAC;IAChB,GAAGN,2BAA2B,OAAO;AACvC;AAEA,cAAc,GACd,OAAO,MAAMO,6BAA6BrB,EAAEsB,kBAAkB,CAAC,QAAQ;IAACL;CAAgB,EAAC;AAEzF,MAAMM,sBAAsBvB,EAAEkB,MAAM,CAAC;IACnCC,MAAMnB,EAAEoB,OAAO,CAAC;IAChB,GAAGN,2BAA2B,UAAU;AAC1C;AAEA,cAAc,GACd,OAAO,MAAMU,2BAA2BxB,EAAEsB,kBAAkB,CAAC,QAAQ;IAACC;CAAoB,EAAC;AAE3F,MAAME,0BAA0BzB,EAAEkB,MAAM,CAAC;IACvC,GAAGZ,2BAA2B,QAAQ;IACtCoB,QAAQ1B,EAAEgB,QAAQ,CAAChB,EAAE2B,OAAO;IAC5BZ,OAAOf,EAAEU,MAAM;AACjB;AAEA;;;CAGC,GACD,OAAO,MAAMkB,2BAA2B,sBAAqB;AAE7D,yEAAyE;AACzE,MAAMC,2BAA2B7B,EAAEkB,MAAM,CAAC;IACxCY,SAAS9B,EAAEoB,OAAO,CAAC;IACnBW,QAAQ/B,EACLgC,KAAK,CAACP,yBACNd,KAAK,CACJX,EAAEiC,MAAM,CACN,CAACF,SAAW,IAAIG,IAAIH,OAAOI,GAAG,CAAC,CAACC,QAAUA,MAAM3B,IAAI,GAAG4B,IAAI,KAAKN,OAAOO,MAAM,EAC7E;AAGR;AAEA;;;CAGC,GACD,OAAO,MAAMC,eAAevC,EAAEsB,kBAAkB,CAAC,WAAW;IAACO;CAAyB,EAAC"}
1
+ {"version":3,"sources":["../src/contract.ts"],"sourcesContent":["import {z} from 'zod/mini'\n\n// Shared module-federation extension contract: interface (view/service) and\n// config declaration schemas, plus the versions the build stamps.\n// `zod/mini` keeps the bundle small.\n\n/** @internal */\nexport const VIEW_CONTRACT_VERSION = 1\n\n/** @internal */\nexport const SERVICE_CONTRACT_VERSION = 1\n\n/** @internal */\nexport const MEDIA_LIBRARY_CONFIG_CONTRACT_VERSION = 1\n\n/**\n * A view component. The return is opaque so the runtime helpers carry no React\n * dependency — the generated artifact renders it with the app's own React.\n * @public\n */\nexport type ViewComponent<TProps> = (props: TProps) => unknown\n\n/** @public */\nexport interface ViewComponentBaseProps<TView> {\n view: TView\n}\n\n/**\n * Component slots each interface type exposes, in render order. Source of truth\n * for {@link InterfaceType} and the build; add a type by registering it here.\n * @internal\n */\nexport const VIEW_COMPONENTS = {\n panel: ['title', 'panel'],\n} as const satisfies Record<string, readonly string[]>\n\n/** @public */\nexport type InterfaceType = keyof typeof VIEW_COMPONENTS\n\n/** @public */\nexport type ServiceType = 'worker'\n\n/**\n * The `app` interface's dock-placement metadata. Interface metadata is\n * discriminated on `type`; `app` is the only type with a shape so far.\n * @internal\n */\nexport const AppInterfaceMetadataSchema = z.object({\n group: z.optional(z.string()),\n priority: z.optional(z.number()),\n})\n\n/** @internal */\nexport type AppInterfaceMetadata = z.infer<typeof AppInterfaceMetadataSchema>\n\n/**\n * The module-federation id a build exposes an interface at. Dev stamps the same\n * id a deploy would, so the workbench loads a local interface like a deployed one.\n * @internal\n */\nexport function interfaceModuleId(type: string, name: string): string {\n switch (type) {\n case 'app': {\n return 'App'\n }\n case 'panel': {\n return `views/${name}`\n }\n case 'worker': {\n return `services/${name}`\n }\n default: {\n throw new Error(`Cannot derive a moduleId for unknown interface type: ${type}`)\n }\n }\n}\n\n// Shared `name` + `src`; `kind` only tailors the validation message.\nfunction extensionDeclarationFields(kind: 'Field' | 'Service' | 'View') {\n const pattern = /^[a-zA-Z0-9_-]+$/\n return {\n name: z.string().check(z.regex(pattern, `${kind} \\`name\\` must match ${pattern}`)),\n src: z.string(),\n }\n}\n\n// Every interface (view, service) shares `name` + `src` + an optional display\n// `title` that defaults to `name` on deploy.\nfunction interfaceDeclarationFields(kind: 'Service' | 'View') {\n return {...extensionDeclarationFields(kind), title: z.optional(z.string())}\n}\n\nconst PanelViewSchema = z.object({\n type: z.literal('panel'),\n ...interfaceDeclarationFields('View'),\n})\n\n/** @internal */\nexport const InterfaceDeclarationSchema = z.discriminatedUnion('type', [PanelViewSchema])\n\nconst WorkerServiceSchema = z.object({\n type: z.literal('worker'),\n ...interfaceDeclarationFields('Service'),\n})\n\n/** @internal */\nexport const ServiceDeclarationSchema = z.discriminatedUnion('type', [WorkerServiceSchema])\n\nconst MediaLibraryFieldSchema = z.object({\n ...extensionDeclarationFields('Field'),\n public: z.optional(z.boolean()),\n title: z.string(),\n})\n\n/**\n * Stamped where the config crosses a boundary so the authoring model doesn't carry a constant discriminator.\n * @internal\n */\nexport const INSTALLATION_CONFIG_TYPE = 'installation_config'\n\n// `appType` is stamped by `unstable_defineMediaLibrary`, never authored.\nconst MediaLibraryConfigSchema = z.object({\n appType: z.literal('media-library'),\n fields: z\n .array(MediaLibraryFieldSchema)\n .check(\n z.refine(\n (fields) => new Set(fields.map((field) => field.name)).size === fields.length,\n 'Field `name` must be unique within a media library',\n ),\n ),\n})\n\n/**\n * An app's optional config, keyed by `appType`; deploys as a versioned snapshot, not an interface.\n * @internal\n */\nexport const ConfigSchema = z.discriminatedUnion('appType', [MediaLibraryConfigSchema])\n"],"names":["z","VIEW_CONTRACT_VERSION","SERVICE_CONTRACT_VERSION","MEDIA_LIBRARY_CONFIG_CONTRACT_VERSION","VIEW_COMPONENTS","panel","AppInterfaceMetadataSchema","object","group","optional","string","priority","number","interfaceModuleId","type","name","Error","extensionDeclarationFields","kind","pattern","check","regex","src","interfaceDeclarationFields","title","PanelViewSchema","literal","InterfaceDeclarationSchema","discriminatedUnion","WorkerServiceSchema","ServiceDeclarationSchema","MediaLibraryFieldSchema","public","boolean","INSTALLATION_CONFIG_TYPE","MediaLibraryConfigSchema","appType","fields","array","refine","Set","map","field","size","length","ConfigSchema"],"mappings":"AAAA,SAAQA,CAAC,QAAO,WAAU;AAE1B,4EAA4E;AAC5E,kEAAkE;AAClE,qCAAqC;AAErC,cAAc,GACd,OAAO,MAAMC,wBAAwB,EAAC;AAEtC,cAAc,GACd,OAAO,MAAMC,2BAA2B,EAAC;AAEzC,cAAc,GACd,OAAO,MAAMC,wCAAwC,EAAC;AActD;;;;CAIC,GACD,OAAO,MAAMC,kBAAkB;IAC7BC,OAAO;QAAC;QAAS;KAAQ;AAC3B,EAAsD;AAQtD;;;;CAIC,GACD,OAAO,MAAMC,6BAA6BN,EAAEO,MAAM,CAAC;IACjDC,OAAOR,EAAES,QAAQ,CAACT,EAAEU,MAAM;IAC1BC,UAAUX,EAAES,QAAQ,CAACT,EAAEY,MAAM;AAC/B,GAAE;AAKF;;;;CAIC,GACD,OAAO,SAASC,kBAAkBC,IAAY,EAAEC,IAAY;IAC1D,OAAQD;QACN,KAAK;YAAO;gBACV,OAAO;YACT;QACA,KAAK;YAAS;gBACZ,OAAO,CAAC,MAAM,EAAEC,MAAM;YACxB;QACA,KAAK;YAAU;gBACb,OAAO,CAAC,SAAS,EAAEA,MAAM;YAC3B;QACA;YAAS;gBACP,MAAM,IAAIC,MAAM,CAAC,qDAAqD,EAAEF,MAAM;YAChF;IACF;AACF;AAEA,qEAAqE;AACrE,SAASG,2BAA2BC,IAAkC;IACpE,MAAMC,UAAU;IAChB,OAAO;QACLJ,MAAMf,EAAEU,MAAM,GAAGU,KAAK,CAACpB,EAAEqB,KAAK,CAACF,SAAS,GAAGD,KAAK,qBAAqB,EAAEC,SAAS;QAChFG,KAAKtB,EAAEU,MAAM;IACf;AACF;AAEA,8EAA8E;AAC9E,6CAA6C;AAC7C,SAASa,2BAA2BL,IAAwB;IAC1D,OAAO;QAAC,GAAGD,2BAA2BC,KAAK;QAAEM,OAAOxB,EAAES,QAAQ,CAACT,EAAEU,MAAM;IAAG;AAC5E;AAEA,MAAMe,kBAAkBzB,EAAEO,MAAM,CAAC;IAC/BO,MAAMd,EAAE0B,OAAO,CAAC;IAChB,GAAGH,2BAA2B,OAAO;AACvC;AAEA,cAAc,GACd,OAAO,MAAMI,6BAA6B3B,EAAE4B,kBAAkB,CAAC,QAAQ;IAACH;CAAgB,EAAC;AAEzF,MAAMI,sBAAsB7B,EAAEO,MAAM,CAAC;IACnCO,MAAMd,EAAE0B,OAAO,CAAC;IAChB,GAAGH,2BAA2B,UAAU;AAC1C;AAEA,cAAc,GACd,OAAO,MAAMO,2BAA2B9B,EAAE4B,kBAAkB,CAAC,QAAQ;IAACC;CAAoB,EAAC;AAE3F,MAAME,0BAA0B/B,EAAEO,MAAM,CAAC;IACvC,GAAGU,2BAA2B,QAAQ;IACtCe,QAAQhC,EAAES,QAAQ,CAACT,EAAEiC,OAAO;IAC5BT,OAAOxB,EAAEU,MAAM;AACjB;AAEA;;;CAGC,GACD,OAAO,MAAMwB,2BAA2B,sBAAqB;AAE7D,yEAAyE;AACzE,MAAMC,2BAA2BnC,EAAEO,MAAM,CAAC;IACxC6B,SAASpC,EAAE0B,OAAO,CAAC;IACnBW,QAAQrC,EACLsC,KAAK,CAACP,yBACNX,KAAK,CACJpB,EAAEuC,MAAM,CACN,CAACF,SAAW,IAAIG,IAAIH,OAAOI,GAAG,CAAC,CAACC,QAAUA,MAAM3B,IAAI,GAAG4B,IAAI,KAAKN,OAAOO,MAAM,EAC7E;AAGR;AAEA;;;CAGC,GACD,OAAO,MAAMC,eAAe7C,EAAE4B,kBAAkB,CAAC,WAAW;IAACO;CAAyB,EAAC"}
package/dist/defineApp.js CHANGED
@@ -1,6 +1,15 @@
1
1
  import { z } from 'zod/mini';
2
2
  import { ConfigSchema, InterfaceDeclarationSchema, ServiceDeclarationSchema } from './contract.js';
3
3
  /** Allowed characters for an app `name`. */ const APP_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
4
+ /**
5
+ * Dashboard visibility values. Mirrors `APP_VISIBILITIES` in `@sanity/cli-core`
6
+ * (which can't be imported here — pulling the barrel into this lean module bloats
7
+ * the config-load path). Kept in sync by a type test in `defineApp.test.ts`.
8
+ */ const APP_VISIBILITIES = [
9
+ 'default',
10
+ 'unlisted',
11
+ 'disabled'
12
+ ];
4
13
  /**
5
14
  * Internal application discriminator. Sanity-owned singleton apps only;
6
15
  * validated by the schema but excluded from the public `DefineAppInput` type.
@@ -63,7 +72,8 @@ import { ConfigSchema, InterfaceDeclarationSchema, ServiceDeclarationSchema } fr
63
72
  * Views the app exposes (e.g. dock panels). Metadata only — built into
64
73
  * render artifacts and persisted to the application service on deploy, not
65
74
  * into the app manifest. View `name`s must be unique within the app.
66
- */ views: z.optional(z.array(InterfaceDeclarationSchema).check(z.refine((views)=>new Set(views.map((view)=>view.name)).size === views.length, 'View `name` must be unique within an app')))
75
+ */ views: z.optional(z.array(InterfaceDeclarationSchema).check(z.refine((views)=>new Set(views.map((view)=>view.name)).size === views.length, 'View `name` must be unique within an app'))),
76
+ /** Dashboard visibility of the app. Defaults to `default` when omitted. */ visibility: z.optional(z.enum(APP_VISIBILITIES))
67
77
  }).check(// Studio app views are not implemented yet. A studio that declares `entry`
68
78
  // (the SDK app-view entrypoint) is rejected here rather than silently
69
79
  // generating one; studios keep navigating via their existing render path.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/defineApp.ts"],"sourcesContent":["import {z} from 'zod/mini'\n\nimport {ConfigSchema, InterfaceDeclarationSchema, ServiceDeclarationSchema} from './contract.js'\n\n/** Allowed characters for an app `name`. */\nconst APP_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/\n\n/**\n * Internal application discriminator. Sanity-owned singleton apps only;\n * validated by the schema but excluded from the public `DefineAppInput` type.\n */\nconst ApplicationType = z.enum(['coreApp', 'studio', 'canvas', 'dashboard', 'media-library'])\n\n/** Dock groups an app can place itself into. */\nconst DockGroupSchema = z.enum(['dock.system', 'dock.applications', 'dock.user'])\n\n/**\n * Dock group identifier. The API does not block a user app from declaring a\n * reserved group (e.g. `dock.system`); priority conventions keep Sanity-owned\n * apps ahead.\n * @public\n */\nexport type DockGroup = z.output<typeof DockGroupSchema>\n\n/**\n * Runtime-validation schema for `unstable_defineApp`. Validates the full shape\n * including the internal `applicationType`; the user-facing `DefineAppInput`\n * type below omits that field.\n * @internal\n */\nexport const DefineAppInputSchema = z\n .object({\n /**\n * Internal — Sanity-owned singleton apps only. Validated here but excluded\n * from the public `DefineAppInput` type.\n * @internal\n */\n applicationType: z.optional(ApplicationType),\n /**\n * Deployed as a versioned snapshot on the app's org installation, not the\n * application service. Singletons only. Internal, so excluded from the public\n * `DefineAppInput` and set via `@ts-expect-error` like `applicationType`.\n * @internal\n */\n config: z.optional(ConfigSchema),\n /**\n * App entrypoint module. Defaults to `./src/App.tsx` when omitted. The build\n * derives the app's navigable `app` view from it. SDK apps only — setting it\n * on a studio is rejected (studio app views are not yet implemented).\n */\n entry: z.optional(z.string()),\n /** Dock group to render in. Defaults to `dock.applications` when omitted. */\n group: z.optional(DockGroupSchema),\n /** Optional icon override (path to an SVG). Wins over manifest/studio icon. */\n icon: z.optional(z.string()),\n /**\n * Sanity-owned app deployed once, installed per org; excluded from the public `DefineAppInput`.\n * @internal\n */\n isSingleton: z.optional(z.boolean()),\n /** Unique app identifier — must match `APP_NAME_PATTERN`. */\n name: z.string().check(z.regex(APP_NAME_PATTERN, 'App `name` must match /^[a-zA-Z0-9_-]+$/')),\n /** Organization that owns the app — the workbench runs and deploys against it. */\n organizationId: z.string(\n \"App `organizationId` is required — pass the owning organization's ID to `unstable_defineApp`\",\n ),\n /** Sort position within the group, ascending. Defaults to `100` when omitted. */\n priority: z.optional(z.number()),\n /**\n * Background services the app runs (e.g. a `worker` emitting dock badges).\n * Metadata only — built into worker artifacts and persisted to the\n * application service on deploy, not into the app manifest. Service `name`s\n * must be unique within the app.\n */\n services: z.optional(\n z\n .array(ServiceDeclarationSchema)\n .check(\n z.refine(\n (services) => new Set(services.map((service) => service.name)).size === services.length,\n 'Service `name` must be unique within an app',\n ),\n ),\n ),\n /**\n * Hostname the application is created at on first deploy. Generated when\n * omitted; redeploys target `deployment.appId` and ignore it. SDK apps\n * only — studios use `studioHost` in sanity.cli.ts.\n */\n slug: z.optional(z.string()),\n /** User-facing app title. Wins over studio.config.ts title on merge. */\n title: z.string(),\n /**\n * Views the app exposes (e.g. dock panels). Metadata only — built into\n * render artifacts and persisted to the application service on deploy, not\n * into the app manifest. View `name`s must be unique within the app.\n */\n views: z.optional(\n z\n .array(InterfaceDeclarationSchema)\n .check(\n z.refine(\n (views) => new Set(views.map((view) => view.name)).size === views.length,\n 'View `name` must be unique within an app',\n ),\n ),\n ),\n })\n .check(\n // Studio app views are not implemented yet. A studio that declares `entry`\n // (the SDK app-view entrypoint) is rejected here rather than silently\n // generating one; studios keep navigating via their existing render path.\n z.refine((input) => !(input.applicationType === 'studio' && input.entry !== undefined), {\n error: 'App views for studios are not implemented yet',\n path: ['entry'],\n }),\n )\n .check(\n // An config belongs to a Sanity-owned singleton (the Media\n // Library). A non-singleton declaring one is rejected — see\n // {@link readConfig} for the runtime guard.\n z.refine((input) => !(input.config && !input.isSingleton), {\n error: '`config` is only supported for singleton apps',\n path: ['config'],\n }),\n )\n\n/**\n * User-facing input for `unstable_defineApp`. Excludes the internal\n * `applicationType`, `isSingleton`, and `config` — validated by the\n * schema but not part of the public surface (Sanity-owned apps set them via\n * `@ts-expect-error`).\n * @public\n */\nexport type DefineAppInput = Omit<\n z.output<typeof DefineAppInputSchema>,\n 'applicationType' | 'config' | 'isSingleton'\n>\n\n/**\n * Nominal brand the CLI discriminates on to enable the workbench build/deploy\n * codepath. Registered via `Symbol.for` so the marker survives module-realm\n * boundaries — `@sanity/cli-core` re-derives the same global symbol with\n * `Symbol.for` rather than importing it, so it stays internal to this module.\n */\nconst WORKBENCH_APP: unique symbol = Symbol.for('sanity.workbench.defineApp')\n\n/**\n * The branded result of `unstable_defineApp`. Carries the same fields as the\n * input plus the internal brand — users only ever see `DefineAppInput`.\n * @public\n */\nexport interface DefineAppResult extends DefineAppInput {\n readonly [WORKBENCH_APP]: true\n}\n\n/**\n * A branded app as the CLI reads it — the full schema shape, including the\n * internal fields `DefineAppInput` omits. Schema-derived so the narrowing\n * can't drift from what the schema validates.\n * @public\n */\nexport type WorkbenchApp = DefineAppResult & z.output<typeof DefineAppInputSchema>\n\n/**\n * Whether `app` is a branded `unstable_defineApp(...)` result — the sole\n * workbench opt-in.\n * @public\n */\nexport function isWorkbenchApp(app: unknown): app is WorkbenchApp {\n return typeof app === 'object' && app !== null && WORKBENCH_APP in app\n}\n\n/**\n * The app's config, or `undefined` when it declares none. Throws\n * when a non-singleton declares one — configs belong to Sanity-owned singletons,\n * so build/dev/deploy all read it through here to reject the combination\n * consistently.\n * @internal\n */\nexport function readConfig(app: WorkbenchApp): WorkbenchApp['config'] | undefined {\n if (app.config && !app.isSingleton) {\n throw new Error('`config` is only supported for singleton apps')\n }\n return app.config\n}\n\n/**\n * Declare a Sanity Workbench application. Identity at runtime — returns the same\n * object reference, tagged with the workbench brand. Field validation (the\n * `name` pattern etc.) runs at build time in the CLI via `DefineAppInputSchema`;\n * this helper stays a thin, pure identity wrapper.\n * @public\n */\nexport function unstable_defineApp(input: DefineAppInput): DefineAppResult {\n return Object.defineProperty(input, WORKBENCH_APP, {\n configurable: false,\n enumerable: false,\n value: true,\n writable: false,\n }) as DefineAppResult\n}\n\n/**\n * One custom field a media library exposes. `src` default-exports a `defineField(...)` schema type.\n * @public\n */\nexport interface MediaLibraryField {\n /** Unique within the media library. */\n name: string\n src: string\n title: string\n\n /** Readable outside the owning organization. */\n public?: boolean\n}\n\n/**\n * Sanity-owned singleton, so authors don't name or title the app — only `organizationId` is required.\n * @public\n */\nexport interface DefineMediaLibraryInput {\n /** Organization that owns the media library — the CLI runs and deploys against it. */\n organizationId: string\n\n fields?: MediaLibraryField[]\n}\n\n/**\n * Declare the Sanity Media Library as a workbench app — a singleton whose `fields` become its config.\n * @public\n */\nexport function unstable_defineMediaLibrary(input: DefineMediaLibraryInput): DefineAppResult {\n return unstable_defineApp({\n // @ts-expect-error -- `applicationType`/`isSingleton`/`config` are internal, excluded from `DefineAppInput`; Sanity-owned apps set them\n applicationType: 'media-library',\n config: input.fields?.length ? {appType: 'media-library', fields: input.fields} : undefined,\n isSingleton: true,\n name: 'media-library',\n organizationId: input.organizationId,\n title: 'Media Library',\n })\n}\n"],"names":["z","ConfigSchema","InterfaceDeclarationSchema","ServiceDeclarationSchema","APP_NAME_PATTERN","ApplicationType","enum","DockGroupSchema","DefineAppInputSchema","object","applicationType","optional","config","entry","string","group","icon","isSingleton","boolean","name","check","regex","organizationId","priority","number","services","array","refine","Set","map","service","size","length","slug","title","views","view","input","undefined","error","path","WORKBENCH_APP","Symbol","for","isWorkbenchApp","app","readConfig","Error","unstable_defineApp","Object","defineProperty","configurable","enumerable","value","writable","unstable_defineMediaLibrary","fields","appType"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/defineApp.ts"],"sourcesContent":["import {z} from 'zod/mini'\n\nimport {ConfigSchema, InterfaceDeclarationSchema, ServiceDeclarationSchema} from './contract.js'\n\n/** Allowed characters for an app `name`. */\nconst APP_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/\n\n/**\n * Dashboard visibility values. Mirrors `APP_VISIBILITIES` in `@sanity/cli-core`\n * (which can't be imported here — pulling the barrel into this lean module bloats\n * the config-load path). Kept in sync by a type test in `defineApp.test.ts`.\n */\nconst APP_VISIBILITIES = ['default', 'unlisted', 'disabled'] as const\n\n/**\n * Internal application discriminator. Sanity-owned singleton apps only;\n * validated by the schema but excluded from the public `DefineAppInput` type.\n */\nconst ApplicationType = z.enum(['coreApp', 'studio', 'canvas', 'dashboard', 'media-library'])\n\n/** Dock groups an app can place itself into. */\nconst DockGroupSchema = z.enum(['dock.system', 'dock.applications', 'dock.user'])\n\n/**\n * Dock group identifier. The API does not block a user app from declaring a\n * reserved group (e.g. `dock.system`); priority conventions keep Sanity-owned\n * apps ahead.\n * @public\n */\nexport type DockGroup = z.output<typeof DockGroupSchema>\n\n/**\n * Runtime-validation schema for `unstable_defineApp`. Validates the full shape\n * including the internal `applicationType`; the user-facing `DefineAppInput`\n * type below omits that field.\n * @internal\n */\nexport const DefineAppInputSchema = z\n .object({\n /**\n * Internal — Sanity-owned singleton apps only. Validated here but excluded\n * from the public `DefineAppInput` type.\n * @internal\n */\n applicationType: z.optional(ApplicationType),\n /**\n * Deployed as a versioned snapshot on the app's org installation, not the\n * application service. Singletons only. Internal, so excluded from the public\n * `DefineAppInput` and set via `@ts-expect-error` like `applicationType`.\n * @internal\n */\n config: z.optional(ConfigSchema),\n /**\n * App entrypoint module. Defaults to `./src/App.tsx` when omitted. The build\n * derives the app's navigable `app` view from it. SDK apps only — setting it\n * on a studio is rejected (studio app views are not yet implemented).\n */\n entry: z.optional(z.string()),\n /** Dock group to render in. Defaults to `dock.applications` when omitted. */\n group: z.optional(DockGroupSchema),\n /** Optional icon override (path to an SVG). Wins over manifest/studio icon. */\n icon: z.optional(z.string()),\n /**\n * Sanity-owned app deployed once, installed per org; excluded from the public `DefineAppInput`.\n * @internal\n */\n isSingleton: z.optional(z.boolean()),\n /** Unique app identifier — must match `APP_NAME_PATTERN`. */\n name: z.string().check(z.regex(APP_NAME_PATTERN, 'App `name` must match /^[a-zA-Z0-9_-]+$/')),\n /** Organization that owns the app — the workbench runs and deploys against it. */\n organizationId: z.string(\n \"App `organizationId` is required — pass the owning organization's ID to `unstable_defineApp`\",\n ),\n /** Sort position within the group, ascending. Defaults to `100` when omitted. */\n priority: z.optional(z.number()),\n /**\n * Background services the app runs (e.g. a `worker` emitting dock badges).\n * Metadata only — built into worker artifacts and persisted to the\n * application service on deploy, not into the app manifest. Service `name`s\n * must be unique within the app.\n */\n services: z.optional(\n z\n .array(ServiceDeclarationSchema)\n .check(\n z.refine(\n (services) => new Set(services.map((service) => service.name)).size === services.length,\n 'Service `name` must be unique within an app',\n ),\n ),\n ),\n /**\n * Hostname the application is created at on first deploy. Generated when\n * omitted; redeploys target `deployment.appId` and ignore it. SDK apps\n * only — studios use `studioHost` in sanity.cli.ts.\n */\n slug: z.optional(z.string()),\n /** User-facing app title. Wins over studio.config.ts title on merge. */\n title: z.string(),\n /**\n * Views the app exposes (e.g. dock panels). Metadata only — built into\n * render artifacts and persisted to the application service on deploy, not\n * into the app manifest. View `name`s must be unique within the app.\n */\n views: z.optional(\n z\n .array(InterfaceDeclarationSchema)\n .check(\n z.refine(\n (views) => new Set(views.map((view) => view.name)).size === views.length,\n 'View `name` must be unique within an app',\n ),\n ),\n ),\n /** Dashboard visibility of the app. Defaults to `default` when omitted. */\n visibility: z.optional(z.enum(APP_VISIBILITIES)),\n })\n .check(\n // Studio app views are not implemented yet. A studio that declares `entry`\n // (the SDK app-view entrypoint) is rejected here rather than silently\n // generating one; studios keep navigating via their existing render path.\n z.refine((input) => !(input.applicationType === 'studio' && input.entry !== undefined), {\n error: 'App views for studios are not implemented yet',\n path: ['entry'],\n }),\n )\n .check(\n // An config belongs to a Sanity-owned singleton (the Media\n // Library). A non-singleton declaring one is rejected — see\n // {@link readConfig} for the runtime guard.\n z.refine((input) => !(input.config && !input.isSingleton), {\n error: '`config` is only supported for singleton apps',\n path: ['config'],\n }),\n )\n\n/**\n * User-facing input for `unstable_defineApp`. Excludes the internal\n * `applicationType`, `isSingleton`, and `config` — validated by the\n * schema but not part of the public surface (Sanity-owned apps set them via\n * `@ts-expect-error`).\n * @public\n */\nexport type DefineAppInput = Omit<\n z.output<typeof DefineAppInputSchema>,\n 'applicationType' | 'config' | 'isSingleton'\n>\n\n/**\n * Nominal brand the CLI discriminates on to enable the workbench build/deploy\n * codepath. Registered via `Symbol.for` so the marker survives module-realm\n * boundaries — `@sanity/cli-core` re-derives the same global symbol with\n * `Symbol.for` rather than importing it, so it stays internal to this module.\n */\nconst WORKBENCH_APP: unique symbol = Symbol.for('sanity.workbench.defineApp')\n\n/**\n * The branded result of `unstable_defineApp`. Carries the same fields as the\n * input plus the internal brand — users only ever see `DefineAppInput`.\n * @public\n */\nexport interface DefineAppResult extends DefineAppInput {\n readonly [WORKBENCH_APP]: true\n}\n\n/**\n * A branded app as the CLI reads it — the full schema shape, including the\n * internal fields `DefineAppInput` omits. Schema-derived so the narrowing\n * can't drift from what the schema validates.\n * @public\n */\nexport type WorkbenchApp = DefineAppResult & z.output<typeof DefineAppInputSchema>\n\n/**\n * Whether `app` is a branded `unstable_defineApp(...)` result — the sole\n * workbench opt-in.\n * @public\n */\nexport function isWorkbenchApp(app: unknown): app is WorkbenchApp {\n return typeof app === 'object' && app !== null && WORKBENCH_APP in app\n}\n\n/**\n * The app's config, or `undefined` when it declares none. Throws\n * when a non-singleton declares one — configs belong to Sanity-owned singletons,\n * so build/dev/deploy all read it through here to reject the combination\n * consistently.\n * @internal\n */\nexport function readConfig(app: WorkbenchApp): WorkbenchApp['config'] | undefined {\n if (app.config && !app.isSingleton) {\n throw new Error('`config` is only supported for singleton apps')\n }\n return app.config\n}\n\n/**\n * Declare a Sanity Workbench application. Identity at runtime — returns the same\n * object reference, tagged with the workbench brand. Field validation (the\n * `name` pattern etc.) runs at build time in the CLI via `DefineAppInputSchema`;\n * this helper stays a thin, pure identity wrapper.\n * @public\n */\nexport function unstable_defineApp(input: DefineAppInput): DefineAppResult {\n return Object.defineProperty(input, WORKBENCH_APP, {\n configurable: false,\n enumerable: false,\n value: true,\n writable: false,\n }) as DefineAppResult\n}\n\n/**\n * One custom field a media library exposes. `src` default-exports a `defineField(...)` schema type.\n * @public\n */\nexport interface MediaLibraryField {\n /** Unique within the media library. */\n name: string\n src: string\n title: string\n\n /** Readable outside the owning organization. */\n public?: boolean\n}\n\n/**\n * Sanity-owned singleton, so authors don't name or title the app — only `organizationId` is required.\n * @public\n */\nexport interface DefineMediaLibraryInput {\n /** Organization that owns the media library — the CLI runs and deploys against it. */\n organizationId: string\n\n fields?: MediaLibraryField[]\n}\n\n/**\n * Declare the Sanity Media Library as a workbench app — a singleton whose `fields` become its config.\n * @public\n */\nexport function unstable_defineMediaLibrary(input: DefineMediaLibraryInput): DefineAppResult {\n return unstable_defineApp({\n // @ts-expect-error -- `applicationType`/`isSingleton`/`config` are internal, excluded from `DefineAppInput`; Sanity-owned apps set them\n applicationType: 'media-library',\n config: input.fields?.length ? {appType: 'media-library', fields: input.fields} : undefined,\n isSingleton: true,\n name: 'media-library',\n organizationId: input.organizationId,\n title: 'Media Library',\n })\n}\n"],"names":["z","ConfigSchema","InterfaceDeclarationSchema","ServiceDeclarationSchema","APP_NAME_PATTERN","APP_VISIBILITIES","ApplicationType","enum","DockGroupSchema","DefineAppInputSchema","object","applicationType","optional","config","entry","string","group","icon","isSingleton","boolean","name","check","regex","organizationId","priority","number","services","array","refine","Set","map","service","size","length","slug","title","views","view","visibility","input","undefined","error","path","WORKBENCH_APP","Symbol","for","isWorkbenchApp","app","readConfig","Error","unstable_defineApp","Object","defineProperty","configurable","enumerable","value","writable","unstable_defineMediaLibrary","fields","appType"],"mappings":"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"}
@@ -18,7 +18,8 @@ import { isWorkbenchApp, readConfig } from './defineApp.js';
18
18
  name: app.name,
19
19
  services: app.services ?? [],
20
20
  slug: app.slug,
21
- views: app.views ?? []
21
+ views: app.views ?? [],
22
+ visibility: app.visibility
22
23
  };
23
24
  }
24
25
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/resolveWorkbenchApp.ts"],"sourcesContent":["// Package-internal shared resolver: turn a CLI config's branded\n// `unstable_defineApp` app into its declared interfaces, or `null` for a plain\n// project. The build and deploy accessors (actions/build, actions/deploy) each\n// build their command-specific view on top of this one brand-check +\n// extraction, so the discrimination lives in exactly one place.\n\nimport {type CliConfig} from '@sanity/cli-core'\n\nimport {type DefineAppInput, isWorkbenchApp, readConfig, type WorkbenchApp} from './defineApp.js'\n\n/**\n * Bundled so adding a declaration family touches this type and the artifact\n * expanders, not every hop of build/dev plumbing in between.\n * @internal\n */\nexport interface WorkbenchExposes {\n config?: WorkbenchApp['config']\n services?: DefineAppInput['services']\n views?: DefineAppInput['views']\n}\n\n/** @public */\nexport interface ResolvedWorkbenchApp {\n /** The app's unique `name` from `unstable_defineApp`. */\n readonly name: string\n /** Background worker services the app declares. */\n readonly services: NonNullable<DefineAppInput['services']>\n\n /** Dock panel views the app declares. */\n readonly views: NonNullable<DefineAppInput['views']>\n\n /** Resolved app kind — `studio` or one of the SDK app types. */\n readonly applicationType?: string\n /** Deploys on its own path, separate from the interfaces. */\n readonly config?: WorkbenchApp['config']\n /** SDK app-view entrypoint, when declared. */\n readonly entry?: string\n /** Explicit singleton flag (a Sanity-owned app); `undefined` when the app doesn't set it. */\n readonly isSingleton?: boolean\n /** Hostname the application is created at on first deploy. */\n readonly slug?: string\n}\n\n/**\n * Resolve the workbench app for a CLI config, or `null` for a plain project.\n * @public\n */\nexport function resolveWorkbenchApp(\n cliConfig: CliConfig | null | undefined,\n): ResolvedWorkbenchApp | null {\n const app = cliConfig?.app\n if (!isWorkbenchApp(app)) return null\n\n return {\n applicationType: app.applicationType,\n config: readConfig(app),\n entry: app.entry,\n isSingleton: app.isSingleton,\n name: app.name,\n services: app.services ?? [],\n slug: app.slug,\n views: app.views ?? [],\n }\n}\n"],"names":["isWorkbenchApp","readConfig","resolveWorkbenchApp","cliConfig","app","applicationType","config","entry","isSingleton","name","services","slug","views"],"mappings":"AAAA,gEAAgE;AAChE,+EAA+E;AAC/E,+EAA+E;AAC/E,qEAAqE;AACrE,gEAAgE;AAIhE,SAA6BA,cAAc,EAAEC,UAAU,QAA0B,iBAAgB;AAmCjG;;;CAGC,GACD,OAAO,SAASC,oBACdC,SAAuC;IAEvC,MAAMC,MAAMD,WAAWC;IACvB,IAAI,CAACJ,eAAeI,MAAM,OAAO;IAEjC,OAAO;QACLC,iBAAiBD,IAAIC,eAAe;QACpCC,QAAQL,WAAWG;QACnBG,OAAOH,IAAIG,KAAK;QAChBC,aAAaJ,IAAII,WAAW;QAC5BC,MAAML,IAAIK,IAAI;QACdC,UAAUN,IAAIM,QAAQ,IAAI,EAAE;QAC5BC,MAAMP,IAAIO,IAAI;QACdC,OAAOR,IAAIQ,KAAK,IAAI,EAAE;IACxB;AACF"}
1
+ {"version":3,"sources":["../src/resolveWorkbenchApp.ts"],"sourcesContent":["// Package-internal shared resolver: turn a CLI config's branded\n// `unstable_defineApp` app into its declared interfaces, or `null` for a plain\n// project. The build and deploy accessors (actions/build, actions/deploy) each\n// build their command-specific view on top of this one brand-check +\n// extraction, so the discrimination lives in exactly one place.\n\nimport {type AppVisibility, type CliConfig} from '@sanity/cli-core'\n\nimport {type DefineAppInput, isWorkbenchApp, readConfig, type WorkbenchApp} from './defineApp.js'\n\n/**\n * Bundled so adding a declaration family touches this type and the artifact\n * expanders, not every hop of build/dev plumbing in between.\n * @internal\n */\nexport interface WorkbenchExposes {\n config?: WorkbenchApp['config']\n services?: DefineAppInput['services']\n views?: DefineAppInput['views']\n}\n\n/** @public */\nexport interface ResolvedWorkbenchApp {\n /** The app's unique `name` from `unstable_defineApp`. */\n readonly name: string\n /** Background worker services the app declares. */\n readonly services: NonNullable<DefineAppInput['services']>\n\n /** Dock panel views the app declares. */\n readonly views: NonNullable<DefineAppInput['views']>\n\n /** Resolved app kind — `studio` or one of the SDK app types. */\n readonly applicationType?: string\n /** Deploys on its own path, separate from the interfaces. */\n readonly config?: WorkbenchApp['config']\n /** SDK app-view entrypoint, when declared. */\n readonly entry?: string\n /** Explicit singleton flag (a Sanity-owned app); `undefined` when the app doesn't set it. */\n readonly isSingleton?: boolean\n /** Hostname the application is created at on first deploy. */\n readonly slug?: string\n /** Dashboard visibility declared by the app; `undefined` when unset. */\n readonly visibility?: AppVisibility\n}\n\n/**\n * Resolve the workbench app for a CLI config, or `null` for a plain project.\n * @public\n */\nexport function resolveWorkbenchApp(\n cliConfig: CliConfig | null | undefined,\n): ResolvedWorkbenchApp | null {\n const app = cliConfig?.app\n if (!isWorkbenchApp(app)) return null\n\n return {\n applicationType: app.applicationType,\n config: readConfig(app),\n entry: app.entry,\n isSingleton: app.isSingleton,\n name: app.name,\n services: app.services ?? [],\n slug: app.slug,\n views: app.views ?? [],\n visibility: app.visibility,\n }\n}\n"],"names":["isWorkbenchApp","readConfig","resolveWorkbenchApp","cliConfig","app","applicationType","config","entry","isSingleton","name","services","slug","views","visibility"],"mappings":"AAAA,gEAAgE;AAChE,+EAA+E;AAC/E,+EAA+E;AAC/E,qEAAqE;AACrE,gEAAgE;AAIhE,SAA6BA,cAAc,EAAEC,UAAU,QAA0B,iBAAgB;AAqCjG;;;CAGC,GACD,OAAO,SAASC,oBACdC,SAAuC;IAEvC,MAAMC,MAAMD,WAAWC;IACvB,IAAI,CAACJ,eAAeI,MAAM,OAAO;IAEjC,OAAO;QACLC,iBAAiBD,IAAIC,eAAe;QACpCC,QAAQL,WAAWG;QACnBG,OAAOH,IAAIG,KAAK;QAChBC,aAAaJ,IAAII,WAAW;QAC5BC,MAAML,IAAIK,IAAI;QACdC,UAAUN,IAAIM,QAAQ,IAAI,EAAE;QAC5BC,MAAMP,IAAIO,IAAI;QACdC,OAAOR,IAAIQ,KAAK,IAAI,EAAE;QACtBC,YAAYT,IAAIS,UAAU;IAC5B;AACF"}
@@ -28,13 +28,14 @@ export async function getApplication(applicationId) {
28
28
  }
29
29
  }
30
30
  /** Create an application and its first deployment in one call. */ export async function createApplication(options) {
31
- const { interfaces, isSingleton, organizationId, projectId, slug, tarball, title, type, version, workspaces } = options;
31
+ const { interfaces, isSingleton, organizationId, projectId, slug, tarball, title, type, version, visibility, workspaces } = options;
32
32
  const formData = new FormData();
33
33
  formData.append('type', type);
34
34
  formData.append('title', title);
35
35
  formData.append('organizationId', organizationId);
36
36
  formData.append('slug', slug);
37
37
  if (isSingleton !== undefined) formData.append('isSingleton', String(isSingleton));
38
+ if (visibility) formData.append('visibility', visibility);
38
39
  // Studio config is set once, at create — it's immutable on redeploy.
39
40
  if (projectId) appendJson(formData, 'config', {
40
41
  studio: {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/services/applications.ts"],"sourcesContent":["import {PassThrough} from 'node:stream'\nimport {type Gzip} from 'node:zlib'\n\nimport {getGlobalCliClient} from '@sanity/cli-core'\nimport {isStaging} from '@sanity/cli-core/util'\nimport FormData from 'form-data'\n\nimport {APP_WORKBENCH_API_VERSION} from './apiVersion.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/**\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\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 function getWorkbenchUrl(organizationId: string): string {\n return `https://${organizationId}.${isStaging() ? 'run.sanity.work' : 'sanity.run'}`\n}\n\n/** Where a deployed application is served on its organization's workbench. */\nexport function getApplicationUrl(\n application: Pick<Application, 'id' | 'organizationId' | 'type'>,\n): string {\n const segment = application.type === 'studio' ? 'studio' : 'application'\n return `${getWorkbenchUrl(application.organizationId)}/${segment}/${application.id}`\n}\n\nasync function getClient() {\n return getGlobalCliClient({apiVersion: APP_WORKBENCH_API_VERSION, requireUser: true})\n}\n\nexport async function getApplication(applicationId: string): Promise<Application | null> {\n const client = await getClient()\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\n/** Soft-deletes the application and all its deployments; already deleted counts as done. */\nexport async function deleteApplication(applicationId: string): Promise<void> {\n const client = await getClient()\n try {\n await client.request({method: 'DELETE', uri: `/applications/${applicationId}`})\n } catch (err) {\n if ((err as {statusCode?: number})?.statusCode !== 404) throw err\n }\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 getClient()\n return client.request({\n body: formData.pipe(new PassThrough()),\n headers: formData.getHeaders(),\n method: 'POST',\n uri,\n })\n}\n"],"names":["PassThrough","getGlobalCliClient","isStaging","FormData","APP_WORKBENCH_API_VERSION","getWorkbenchUrl","organizationId","getApplicationUrl","application","segment","type","id","getClient","apiVersion","requireUser","getApplication","applicationId","client","request","uri","err","statusCode","createApplication","options","interfaces","isSingleton","projectId","slug","tarball","title","version","workspaces","formData","append","undefined","String","appendJson","studio","appendDeploymentParts","createDeployment","isAutoUpdating","toString","deleteApplication","method","length","contentType","filename","name","value","JSON","stringify","body","pipe","headers","getHeaders"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/services/applications.ts"],"sourcesContent":["import {PassThrough} from 'node:stream'\nimport {type Gzip} from 'node:zlib'\n\nimport {type AppVisibility, getGlobalCliClient} from '@sanity/cli-core'\nimport {isStaging} from '@sanity/cli-core/util'\nimport FormData from 'form-data'\n\nimport {type AppInterfaceMetadata} from '../contract.js'\nimport {APP_WORKBENCH_API_VERSION} from './apiVersion.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\ninterface BrettInterfaceBase {\n moduleId: string\n name: string\n title: string\n version: string\n}\n\n/**\n * An interface as Brett stores it, discriminated on `type`. `moduleId` is\n * remote-relative — the host prepends the app's id. Brett assigns the id.\n * @internal\n */\nexport type BrettInterface =\n | (BrettInterfaceBase & {metadata: AppInterfaceMetadata | null; type: 'app'})\n | (BrettInterfaceBase & {metadata: null; type: 'panel'})\n | (BrettInterfaceBase & {metadata: null; type: 'worker'})\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 function getWorkbenchUrl(organizationId: string): string {\n return `https://${organizationId}.${isStaging() ? 'run.sanity.work' : 'sanity.run'}`\n}\n\n/** Where a deployed application is served on its organization's workbench. */\nexport function getApplicationUrl(\n application: Pick<Application, 'id' | 'organizationId' | 'type'>,\n): string {\n const segment = application.type === 'studio' ? 'studio' : 'application'\n return `${getWorkbenchUrl(application.organizationId)}/${segment}/${application.id}`\n}\n\nasync function getClient() {\n return getGlobalCliClient({apiVersion: APP_WORKBENCH_API_VERSION, requireUser: true})\n}\n\nexport async function getApplication(applicationId: string): Promise<Application | null> {\n const client = await getClient()\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 visibility?: AppVisibility\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 visibility,\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 if (visibility) formData.append('visibility', visibility)\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\n/** Soft-deletes the application and all its deployments; already deleted counts as done. */\nexport async function deleteApplication(applicationId: string): Promise<void> {\n const client = await getClient()\n try {\n await client.request({method: 'DELETE', uri: `/applications/${applicationId}`})\n } catch (err) {\n if ((err as {statusCode?: number})?.statusCode !== 404) throw err\n }\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 getClient()\n return client.request({\n body: formData.pipe(new PassThrough()),\n headers: formData.getHeaders(),\n method: 'POST',\n uri,\n })\n}\n"],"names":["PassThrough","getGlobalCliClient","isStaging","FormData","APP_WORKBENCH_API_VERSION","getWorkbenchUrl","organizationId","getApplicationUrl","application","segment","type","id","getClient","apiVersion","requireUser","getApplication","applicationId","client","request","uri","err","statusCode","createApplication","options","interfaces","isSingleton","projectId","slug","tarball","title","version","visibility","workspaces","formData","append","undefined","String","appendJson","studio","appendDeploymentParts","createDeployment","isAutoUpdating","toString","deleteApplication","method","length","contentType","filename","name","value","JSON","stringify","body","pipe","headers","getHeaders"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/workbench-cli",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Internal implementation detail of the Sanity CLI's unstable workbench support. Not intended for direct use.",
5
5
  "homepage": "https://github.com/sanity-io/cli",
6
6
  "bugs": "https://github.com/sanity-io/cli/issues",
@@ -49,35 +49,35 @@
49
49
  "access": "public"
50
50
  },
51
51
  "dependencies": {
52
- "@module-federation/vite": "1.16.14",
52
+ "@module-federation/vite": "1.17.1",
53
53
  "@vitejs/plugin-react": "^6.0.3",
54
54
  "form-data": "^4.0.5",
55
55
  "tar-fs": "^3.1.2",
56
- "vite": "^8.1.3",
56
+ "vite": "^8.1.5",
57
57
  "zod": "^4.4.3",
58
- "@sanity/cli-core": "^2.4.0"
58
+ "@sanity/cli-core": "^2.5.0"
59
59
  },
60
60
  "devDependencies": {
61
61
  "@eslint/compat": "^2.1.0",
62
- "@sanity/pkg-utils": "^10.8.1",
62
+ "@sanity/pkg-utils": "^11.0.9",
63
63
  "@swc/cli": "^0.8.1",
64
- "@swc/core": "^1.15.41",
64
+ "@swc/core": "^1.15.43",
65
65
  "@types/node": "^22.20.0",
66
66
  "@types/tar-fs": "^2.0.4",
67
- "@vitest/coverage-istanbul": "^4.1.9",
68
- "eslint": "^10.4.1",
67
+ "@vitest/coverage-istanbul": "^4.1.10",
68
+ "eslint": "^10.7.0",
69
69
  "publint": "^0.3.21",
70
- "typescript": "^5.9.3",
71
- "vitest": "^4.1.9",
70
+ "typescript": "^6.0.3",
71
+ "vitest": "^4.1.10",
72
72
  "@repo/package.config": "0.0.1",
73
73
  "@repo/tsconfig": "3.70.0",
74
- "@sanity/eslint-config-cli": "^1.1.2"
74
+ "@sanity/eslint-config-cli": "^1.1.3"
75
75
  },
76
76
  "engines": {
77
77
  "node": ">=22.12"
78
78
  },
79
79
  "scripts": {
80
- "build": "swc --delete-dir-on-start --strip-leading-paths --out-dir dist/ src --ignore '**/*.test.ts' --ignore '**/__tests__/**'",
80
+ "build": "swc --delete-dir-on-start --strip-leading-paths --out-dir dist/ src",
81
81
  "build:types": "pkg-utils build --emitDeclarationOnly",
82
82
  "check:types": "tsc --noEmit",
83
83
  "lint": "eslint .",