@sanity/workbench-cli 1.7.0 → 1.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_exports/build.d.ts +23 -12
- package/dist/_exports/deploy.d.ts +22 -8
- package/dist/_exports/deploy.js +1 -1
- package/dist/_exports/deploy.js.map +1 -1
- package/dist/_exports/dev.d.ts +1 -1
- package/dist/_exports/index.d.ts +25 -43
- package/dist/_exports/preview.d.ts +1 -1
- package/dist/_exports/undeploy.d.ts +17 -8
- package/dist/actions/build/vite/plugins/plugin-sanity-environment.js +1 -0
- package/dist/actions/build/vite/plugins/plugin-sanity-environment.js.map +1 -1
- package/dist/actions/dev/deriveInterfaces.js +14 -6
- package/dist/actions/dev/deriveInterfaces.js.map +1 -1
- package/dist/actions/dev/registry.js +3 -3
- package/dist/actions/dev/registry.js.map +1 -1
- package/dist/actions/dev/startDevServerRegistration.js +15 -2
- package/dist/actions/dev/startDevServerRegistration.js.map +1 -1
- package/dist/actions/preview/startWorkbenchPreview.js +6 -4
- package/dist/actions/preview/startWorkbenchPreview.js.map +1 -1
- package/dist/appId.js +25 -38
- package/dist/appId.js.map +1 -1
- package/dist/defineApp.js +6 -16
- package/dist/defineApp.js.map +1 -1
- package/dist/resolveWorkbenchApp.js +3 -0
- package/dist/resolveWorkbenchApp.js.map +1 -1
- package/dist/services/applications.js +11 -0
- package/dist/services/applications.js.map +1 -1
- package/dist/validateWorkbenchApp.js +23 -0
- package/dist/validateWorkbenchApp.js.map +1 -0
- package/package.json +5 -5
package/dist/_exports/build.d.ts
CHANGED
|
@@ -4,28 +4,39 @@ import { PluginOption } from "vite";
|
|
|
4
4
|
import { z } from "zod/mini";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* The `build`/`start` id
|
|
8
|
-
*
|
|
9
|
-
* by `sanity start` resolve to the same id.
|
|
7
|
+
* The `build`/`start` id — a hash of the app's declared shape (its identity, not
|
|
8
|
+
* its code), so the bundle inlined by `sanity build` and the registry entry
|
|
9
|
+
* advertised by `sanity start` resolve to the same id. Hashed with the Web Crypto
|
|
10
|
+
* API rather than `node:crypto` for parity with `resolveAppId`'s browser-safe
|
|
11
|
+
* home. `sanity deploy` resolves its own id from the applications API.
|
|
10
12
|
*/
|
|
11
|
-
export declare function buildAppId(app: ResolvedWorkbenchApp): string
|
|
13
|
+
export declare function buildAppId(app: ResolvedWorkbenchApp): Promise<string>;
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
16
|
* User-facing input for `unstable_defineApp`. Excludes the internal
|
|
15
17
|
* `applicationType`, `isSingleton`, and `config` — validated by the
|
|
16
18
|
* schema but not part of the public surface (Sanity-owned apps set them via
|
|
17
|
-
* `@ts-expect-error`).
|
|
19
|
+
* `@ts-expect-error`). A union so an app declares an app `entry` or `views`,
|
|
20
|
+
* never both.
|
|
18
21
|
* @public
|
|
19
22
|
*/
|
|
20
23
|
declare type DefineAppInput = Omit<
|
|
21
24
|
z.output<typeof DefineAppInputSchema>,
|
|
22
|
-
"applicationType" | "config" | "isSingleton"
|
|
23
|
-
|
|
25
|
+
"applicationType" | "config" | "entry" | "isSingleton" | "views"
|
|
26
|
+
> &
|
|
27
|
+
(
|
|
28
|
+
| {
|
|
29
|
+
entry?: never;
|
|
30
|
+
views?: NonNullable<z.output<typeof DefineAppInputSchema>["views"]>;
|
|
31
|
+
}
|
|
32
|
+
| {
|
|
33
|
+
entry?: string;
|
|
34
|
+
views?: never;
|
|
35
|
+
}
|
|
36
|
+
);
|
|
24
37
|
|
|
25
38
|
/**
|
|
26
|
-
* Runtime-validation schema for `unstable_defineApp`.
|
|
27
|
-
* including the internal `applicationType`; the user-facing `DefineAppInput`
|
|
28
|
-
* type below omits that field.
|
|
39
|
+
* Runtime-validation schema for `unstable_defineApp`.
|
|
29
40
|
* @internal
|
|
30
41
|
*/
|
|
31
42
|
declare const DefineAppInputSchema: z.ZodMiniObject<
|
|
@@ -130,9 +141,9 @@ declare const DefineAppInputSchema: z.ZodMiniObject<
|
|
|
130
141
|
* input plus the internal brand — users only ever see `DefineAppInput`.
|
|
131
142
|
* @public
|
|
132
143
|
*/
|
|
133
|
-
declare
|
|
144
|
+
declare type DefineAppResult = DefineAppInput & {
|
|
134
145
|
readonly [WORKBENCH_APP]: true;
|
|
135
|
-
}
|
|
146
|
+
};
|
|
136
147
|
|
|
137
148
|
/** @public */
|
|
138
149
|
declare interface ResolvedWorkbenchApp {
|
|
@@ -134,18 +134,27 @@ export declare function createStudio(options: {
|
|
|
134
134
|
* User-facing input for `unstable_defineApp`. Excludes the internal
|
|
135
135
|
* `applicationType`, `isSingleton`, and `config` — validated by the
|
|
136
136
|
* schema but not part of the public surface (Sanity-owned apps set them via
|
|
137
|
-
* `@ts-expect-error`).
|
|
137
|
+
* `@ts-expect-error`). A union so an app declares an app `entry` or `views`,
|
|
138
|
+
* never both.
|
|
138
139
|
* @public
|
|
139
140
|
*/
|
|
140
141
|
declare type DefineAppInput = Omit<
|
|
141
142
|
z.output<typeof DefineAppInputSchema>,
|
|
142
|
-
"applicationType" | "config" | "isSingleton"
|
|
143
|
-
|
|
143
|
+
"applicationType" | "config" | "entry" | "isSingleton" | "views"
|
|
144
|
+
> &
|
|
145
|
+
(
|
|
146
|
+
| {
|
|
147
|
+
entry?: never;
|
|
148
|
+
views?: NonNullable<z.output<typeof DefineAppInputSchema>["views"]>;
|
|
149
|
+
}
|
|
150
|
+
| {
|
|
151
|
+
entry?: string;
|
|
152
|
+
views?: never;
|
|
153
|
+
}
|
|
154
|
+
);
|
|
144
155
|
|
|
145
156
|
/**
|
|
146
|
-
* Runtime-validation schema for `unstable_defineApp`.
|
|
147
|
-
* including the internal `applicationType`; the user-facing `DefineAppInput`
|
|
148
|
-
* type below omits that field.
|
|
157
|
+
* Runtime-validation schema for `unstable_defineApp`.
|
|
149
158
|
* @internal
|
|
150
159
|
*/
|
|
151
160
|
declare const DefineAppInputSchema: z.ZodMiniObject<
|
|
@@ -250,9 +259,9 @@ declare const DefineAppInputSchema: z.ZodMiniObject<
|
|
|
250
259
|
* input plus the internal brand — users only ever see `DefineAppInput`.
|
|
251
260
|
* @public
|
|
252
261
|
*/
|
|
253
|
-
declare
|
|
262
|
+
declare type DefineAppResult = DefineAppInput & {
|
|
254
263
|
readonly [WORKBENCH_APP]: true;
|
|
255
|
-
}
|
|
264
|
+
};
|
|
256
265
|
|
|
257
266
|
declare interface DeployableWorkbenchApp extends ResolvedWorkbenchApp {
|
|
258
267
|
/**
|
|
@@ -338,6 +347,11 @@ export declare function getWorkbench(
|
|
|
338
347
|
|
|
339
348
|
export declare function getWorkbenchUrl(organizationId: string): string;
|
|
340
349
|
|
|
350
|
+
/** Every application in an organization, in one page (`limit=none`). */
|
|
351
|
+
export declare function listApplications(
|
|
352
|
+
organizationId: string,
|
|
353
|
+
): Promise<Application[]>;
|
|
354
|
+
|
|
341
355
|
/** @public */
|
|
342
356
|
declare interface ResolvedWorkbenchApp {
|
|
343
357
|
/** The app's unique `name` from `unstable_defineApp`. */
|
package/dist/_exports/deploy.js
CHANGED
|
@@ -3,6 +3,6 @@ export { checkBuiltOutput } from '../actions/deploy/checkBuiltOutput.js';
|
|
|
3
3
|
export { deployConfig, resolveInstallationId, summarizeConfig } from '../actions/deploy/deployConfig.js';
|
|
4
4
|
export { createCoreApp, createStudio, deployWorkbenchApp } from '../actions/deploy/deployWorkbenchApp.js';
|
|
5
5
|
export { getWorkbench } from '../actions/deploy/getWorkbench.js';
|
|
6
|
-
export { getApplication, getApplicationUrl, getWorkbenchUrl } from '../services/applications.js';
|
|
6
|
+
export { getApplication, getApplicationUrl, getWorkbenchUrl, listApplications } from '../services/applications.js';
|
|
7
7
|
|
|
8
8
|
//# sourceMappingURL=deploy.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/_exports/deploy.ts"],"sourcesContent":["export {\n buildExposes,\n type DeployedExpose,\n summarizeExposes,\n} from '../actions/deploy/buildExposes.js'\nexport {checkBuiltOutput} from '../actions/deploy/checkBuiltOutput.js'\nexport {\n deployConfig,\n resolveInstallationId,\n summarizeConfig,\n} from '../actions/deploy/deployConfig.js'\nexport {\n createCoreApp,\n type CreatedApplication,\n createStudio,\n deployWorkbenchApp,\n} from '../actions/deploy/deployWorkbenchApp.js'\nexport {getWorkbench} from '../actions/deploy/getWorkbench.js'\nexport {\n type Application,\n type BrettInterface,\n type BrettWorkspace,\n getApplication,\n getApplicationUrl,\n getWorkbenchUrl,\n} from '../services/applications.js'\n"],"names":["buildExposes","summarizeExposes","checkBuiltOutput","deployConfig","resolveInstallationId","summarizeConfig","createCoreApp","createStudio","deployWorkbenchApp","getWorkbench","getApplication","getApplicationUrl","getWorkbenchUrl"],"mappings":"AAAA,SACEA,YAAY,EAEZC,gBAAgB,QACX,oCAAmC;AAC1C,SAAQC,gBAAgB,QAAO,wCAAuC;AACtE,SACEC,YAAY,EACZC,qBAAqB,EACrBC,eAAe,QACV,oCAAmC;AAC1C,SACEC,aAAa,EAEbC,YAAY,EACZC,kBAAkB,QACb,0CAAyC;AAChD,SAAQC,YAAY,QAAO,oCAAmC;AAC9D,SAIEC,cAAc,EACdC,iBAAiB,EACjBC,eAAe,
|
|
1
|
+
{"version":3,"sources":["../../src/_exports/deploy.ts"],"sourcesContent":["export {\n buildExposes,\n type DeployedExpose,\n summarizeExposes,\n} from '../actions/deploy/buildExposes.js'\nexport {checkBuiltOutput} from '../actions/deploy/checkBuiltOutput.js'\nexport {\n deployConfig,\n resolveInstallationId,\n summarizeConfig,\n} from '../actions/deploy/deployConfig.js'\nexport {\n createCoreApp,\n type CreatedApplication,\n createStudio,\n deployWorkbenchApp,\n} from '../actions/deploy/deployWorkbenchApp.js'\nexport {getWorkbench} from '../actions/deploy/getWorkbench.js'\nexport {\n type Application,\n type BrettInterface,\n type BrettWorkspace,\n getApplication,\n getApplicationUrl,\n getWorkbenchUrl,\n listApplications,\n} from '../services/applications.js'\n"],"names":["buildExposes","summarizeExposes","checkBuiltOutput","deployConfig","resolveInstallationId","summarizeConfig","createCoreApp","createStudio","deployWorkbenchApp","getWorkbench","getApplication","getApplicationUrl","getWorkbenchUrl","listApplications"],"mappings":"AAAA,SACEA,YAAY,EAEZC,gBAAgB,QACX,oCAAmC;AAC1C,SAAQC,gBAAgB,QAAO,wCAAuC;AACtE,SACEC,YAAY,EACZC,qBAAqB,EACrBC,eAAe,QACV,oCAAmC;AAC1C,SACEC,aAAa,EAEbC,YAAY,EACZC,kBAAkB,QACb,0CAAyC;AAChD,SAAQC,YAAY,QAAO,oCAAmC;AAC9D,SAIEC,cAAc,EACdC,iBAAiB,EACjBC,eAAe,EACfC,gBAAgB,QACX,8BAA6B"}
|
package/dist/_exports/dev.d.ts
CHANGED
package/dist/_exports/index.d.ts
CHANGED
|
@@ -1,29 +1,30 @@
|
|
|
1
1
|
import { z } from "zod/mini";
|
|
2
2
|
|
|
3
|
-
/** The declared shape hashed into a build id — the app's identity, not its code. */
|
|
4
|
-
declare interface BuildAppIdentity {
|
|
5
|
-
name: string;
|
|
6
|
-
organizationId: string;
|
|
7
|
-
entry?: string;
|
|
8
|
-
exposes?: WorkbenchExposes;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
3
|
/**
|
|
12
4
|
* User-facing input for `unstable_defineApp`. Excludes the internal
|
|
13
5
|
* `applicationType`, `isSingleton`, and `config` — validated by the
|
|
14
6
|
* schema but not part of the public surface (Sanity-owned apps set them via
|
|
15
|
-
* `@ts-expect-error`).
|
|
7
|
+
* `@ts-expect-error`). A union so an app declares an app `entry` or `views`,
|
|
8
|
+
* never both.
|
|
16
9
|
* @public
|
|
17
10
|
*/
|
|
18
11
|
export declare type DefineAppInput = Omit<
|
|
19
12
|
z.output<typeof DefineAppInputSchema>,
|
|
20
|
-
"applicationType" | "config" | "isSingleton"
|
|
21
|
-
|
|
13
|
+
"applicationType" | "config" | "entry" | "isSingleton" | "views"
|
|
14
|
+
> &
|
|
15
|
+
(
|
|
16
|
+
| {
|
|
17
|
+
entry?: never;
|
|
18
|
+
views?: NonNullable<z.output<typeof DefineAppInputSchema>["views"]>;
|
|
19
|
+
}
|
|
20
|
+
| {
|
|
21
|
+
entry?: string;
|
|
22
|
+
views?: never;
|
|
23
|
+
}
|
|
24
|
+
);
|
|
22
25
|
|
|
23
26
|
/**
|
|
24
|
-
* Runtime-validation schema for `unstable_defineApp`.
|
|
25
|
-
* including the internal `applicationType`; the user-facing `DefineAppInput`
|
|
26
|
-
* type below omits that field.
|
|
27
|
+
* Runtime-validation schema for `unstable_defineApp`.
|
|
27
28
|
* @internal
|
|
28
29
|
*/
|
|
29
30
|
declare const DefineAppInputSchema: z.ZodMiniObject<
|
|
@@ -128,9 +129,9 @@ declare const DefineAppInputSchema: z.ZodMiniObject<
|
|
|
128
129
|
* input plus the internal brand — users only ever see `DefineAppInput`.
|
|
129
130
|
* @public
|
|
130
131
|
*/
|
|
131
|
-
export declare
|
|
132
|
+
export declare type DefineAppResult = DefineAppInput & {
|
|
132
133
|
readonly [WORKBENCH_APP]: true;
|
|
133
|
-
}
|
|
134
|
+
};
|
|
134
135
|
|
|
135
136
|
/**
|
|
136
137
|
* The result of `unstable_defineService`: the author's callback, the service
|
|
@@ -239,23 +240,15 @@ export declare type PanelViewProps = ViewComponentBaseProps<{
|
|
|
239
240
|
}>;
|
|
240
241
|
|
|
241
242
|
/**
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
* of the declared shape. `sanity deploy` resolves its own id from the
|
|
247
|
-
* applications API, so it isn't handled here.
|
|
243
|
+
* The dev id for a workbench app — the address the server bound. `sanity dev`
|
|
244
|
+
* keys on where the app is served so a running app can't collide with its
|
|
245
|
+
* deployed twin. Sync and dependency-free: it's re-exported from the package's
|
|
246
|
+
* browser-facing entry, so it must not pull in `node:crypto`.
|
|
248
247
|
*/
|
|
249
|
-
export declare function resolveAppId(
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
}
|
|
254
|
-
| {
|
|
255
|
-
host: string;
|
|
256
|
-
port: number;
|
|
257
|
-
},
|
|
258
|
-
): string;
|
|
248
|
+
export declare function resolveAppId(source: {
|
|
249
|
+
host: string;
|
|
250
|
+
port: number;
|
|
251
|
+
}): string;
|
|
259
252
|
|
|
260
253
|
/** @internal */
|
|
261
254
|
declare const SERVICE_CONTRACT_VERSION = 1;
|
|
@@ -393,15 +386,4 @@ declare const WORKBENCH_APP: unique symbol;
|
|
|
393
386
|
export declare type WorkbenchApp = DefineAppResult &
|
|
394
387
|
z.output<typeof DefineAppInputSchema>;
|
|
395
388
|
|
|
396
|
-
/**
|
|
397
|
-
* Bundled so adding a declaration family touches this type and the artifact
|
|
398
|
-
* expanders, not every hop of build/dev plumbing in between.
|
|
399
|
-
* @internal
|
|
400
|
-
*/
|
|
401
|
-
declare interface WorkbenchExposes {
|
|
402
|
-
config?: WorkbenchApp["config"];
|
|
403
|
-
services?: DefineAppInput["services"];
|
|
404
|
-
views?: DefineAppInput["views"];
|
|
405
|
-
}
|
|
406
|
-
|
|
407
389
|
export {};
|
|
@@ -22,18 +22,27 @@ export declare function createWorkbenchUndeployAdapter(options: {
|
|
|
22
22
|
* User-facing input for `unstable_defineApp`. Excludes the internal
|
|
23
23
|
* `applicationType`, `isSingleton`, and `config` — validated by the
|
|
24
24
|
* schema but not part of the public surface (Sanity-owned apps set them via
|
|
25
|
-
* `@ts-expect-error`).
|
|
25
|
+
* `@ts-expect-error`). A union so an app declares an app `entry` or `views`,
|
|
26
|
+
* never both.
|
|
26
27
|
* @public
|
|
27
28
|
*/
|
|
28
29
|
declare type DefineAppInput = Omit<
|
|
29
30
|
z.output<typeof DefineAppInputSchema>,
|
|
30
|
-
"applicationType" | "config" | "isSingleton"
|
|
31
|
-
|
|
31
|
+
"applicationType" | "config" | "entry" | "isSingleton" | "views"
|
|
32
|
+
> &
|
|
33
|
+
(
|
|
34
|
+
| {
|
|
35
|
+
entry?: never;
|
|
36
|
+
views?: NonNullable<z.output<typeof DefineAppInputSchema>["views"]>;
|
|
37
|
+
}
|
|
38
|
+
| {
|
|
39
|
+
entry?: string;
|
|
40
|
+
views?: never;
|
|
41
|
+
}
|
|
42
|
+
);
|
|
32
43
|
|
|
33
44
|
/**
|
|
34
|
-
* Runtime-validation schema for `unstable_defineApp`.
|
|
35
|
-
* including the internal `applicationType`; the user-facing `DefineAppInput`
|
|
36
|
-
* type below omits that field.
|
|
45
|
+
* Runtime-validation schema for `unstable_defineApp`.
|
|
37
46
|
* @internal
|
|
38
47
|
*/
|
|
39
48
|
declare const DefineAppInputSchema: z.ZodMiniObject<
|
|
@@ -138,9 +147,9 @@ declare const DefineAppInputSchema: z.ZodMiniObject<
|
|
|
138
147
|
* input plus the internal brand — users only ever see `DefineAppInput`.
|
|
139
148
|
* @public
|
|
140
149
|
*/
|
|
141
|
-
declare
|
|
150
|
+
declare type DefineAppResult = DefineAppInput & {
|
|
142
151
|
readonly [WORKBENCH_APP]: true;
|
|
143
|
-
}
|
|
152
|
+
};
|
|
144
153
|
|
|
145
154
|
declare interface DeployableWorkbenchApp extends ResolvedWorkbenchApp {
|
|
146
155
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../src/actions/build/vite/plugins/plugin-sanity-environment.ts"],"sourcesContent":["import {type Plugin} from 'vite'\n\nimport {FEDERATION_DIR_NAME} from '../constants.js'\n\ninterface EnvironmentOptions {\n input: string\n\n /**\n * When set, also build a standalone `client` SPA environment (its own\n * `index.html` + bootstrap) alongside the federation remote, from this entry.\n * Omitted for a dock-only app or when the workbench-remote SPA is disabled.\n */\n clientInput?: string\n}\n\nexport function sanityEnvironmentPlugin(options: EnvironmentOptions): Plugin {\n const {clientInput, input} = options\n\n return {\n config() {\n return {\n builder: {\n async buildApp(builder) {\n // `emptyOutDir` is false on both environments and the CLI clears\n // `dist` once up-front, so the SPA and federation outputs coexist\n // without either build wiping the other's files.\n if (clientInput) {\n await builder.build(builder.environments.client)\n }\n await builder.build(builder.environments[FEDERATION_DIR_NAME])\n },\n },\n environments: {\n ...(clientInput\n ? {\n client: {\n build: {\n assetsDir: 'static',\n copyPublicDir: false,\n emptyOutDir: false,\n outDir: `dist`,\n rolldownOptions: {input: {sanity: clientInput}},\n },\n consumer: 'client',\n },\n }\n : {}),\n [FEDERATION_DIR_NAME]: {\n build: {\n copyPublicDir: false,\n emptyOutDir: false,\n outDir: `dist`,\n rolldownOptions: {input},\n },\n consumer: 'client',\n },\n },\n }\n },\n name: 'sanity/environment',\n }\n}\n"],"names":["FEDERATION_DIR_NAME","sanityEnvironmentPlugin","options","clientInput","input","config","builder","buildApp","build","environments","client","assetsDir","copyPublicDir","emptyOutDir","outDir","rolldownOptions","sanity","consumer","name"],"mappings":"AAEA,SAAQA,mBAAmB,QAAO,kBAAiB;AAanD,OAAO,SAASC,wBAAwBC,OAA2B;IACjE,MAAM,EAACC,WAAW,EAAEC,KAAK,EAAC,GAAGF;IAE7B,OAAO;QACLG;YACE,OAAO;gBACLC,SAAS;oBACP,MAAMC,UAASD,OAAO;wBACpB,iEAAiE;wBACjE,kEAAkE;wBAClE,iDAAiD;wBACjD,IAAIH,aAAa;4BACf,MAAMG,QAAQE,KAAK,CAACF,QAAQG,YAAY,CAACC,MAAM;wBACjD;wBACA,MAAMJ,QAAQE,KAAK,CAACF,QAAQG,YAAY,CAACT,oBAAoB;oBAC/D;gBACF;gBACAS,cAAc;oBACZ,GAAIN,cACA;wBACEO,QAAQ;4BACNF,OAAO;gCACLG,WAAW;gCACXC,eAAe;gCACfC,aAAa;gCACbC,QAAQ,CAAC,IAAI,CAAC;gCACdC,iBAAiB;oCAACX,OAAO;wCAACY,QAAQb;oCAAW;gCAAC;4BAChD;4BACAc,UAAU;wBACZ;oBACF,IACA,CAAC,CAAC;oBACN,CAACjB,oBAAoB,EAAE;wBACrBQ,OAAO;
|
|
1
|
+
{"version":3,"sources":["../../../../../src/actions/build/vite/plugins/plugin-sanity-environment.ts"],"sourcesContent":["import {type Plugin} from 'vite'\n\nimport {FEDERATION_DIR_NAME} from '../constants.js'\n\ninterface EnvironmentOptions {\n input: string\n\n /**\n * When set, also build a standalone `client` SPA environment (its own\n * `index.html` + bootstrap) alongside the federation remote, from this entry.\n * Omitted for a dock-only app or when the workbench-remote SPA is disabled.\n */\n clientInput?: string\n}\n\nexport function sanityEnvironmentPlugin(options: EnvironmentOptions): Plugin {\n const {clientInput, input} = options\n\n return {\n config() {\n return {\n builder: {\n async buildApp(builder) {\n // `emptyOutDir` is false on both environments and the CLI clears\n // `dist` once up-front, so the SPA and federation outputs coexist\n // without either build wiping the other's files.\n if (clientInput) {\n await builder.build(builder.environments.client)\n }\n await builder.build(builder.environments[FEDERATION_DIR_NAME])\n },\n },\n environments: {\n ...(clientInput\n ? {\n client: {\n build: {\n assetsDir: 'static',\n copyPublicDir: false,\n emptyOutDir: false,\n outDir: `dist`,\n rolldownOptions: {input: {sanity: clientInput}},\n },\n consumer: 'client',\n },\n }\n : {}),\n [FEDERATION_DIR_NAME]: {\n build: {\n assetsDir: 'static',\n copyPublicDir: false,\n emptyOutDir: false,\n outDir: `dist`,\n rolldownOptions: {input},\n },\n consumer: 'client',\n },\n },\n }\n },\n name: 'sanity/environment',\n }\n}\n"],"names":["FEDERATION_DIR_NAME","sanityEnvironmentPlugin","options","clientInput","input","config","builder","buildApp","build","environments","client","assetsDir","copyPublicDir","emptyOutDir","outDir","rolldownOptions","sanity","consumer","name"],"mappings":"AAEA,SAAQA,mBAAmB,QAAO,kBAAiB;AAanD,OAAO,SAASC,wBAAwBC,OAA2B;IACjE,MAAM,EAACC,WAAW,EAAEC,KAAK,EAAC,GAAGF;IAE7B,OAAO;QACLG;YACE,OAAO;gBACLC,SAAS;oBACP,MAAMC,UAASD,OAAO;wBACpB,iEAAiE;wBACjE,kEAAkE;wBAClE,iDAAiD;wBACjD,IAAIH,aAAa;4BACf,MAAMG,QAAQE,KAAK,CAACF,QAAQG,YAAY,CAACC,MAAM;wBACjD;wBACA,MAAMJ,QAAQE,KAAK,CAACF,QAAQG,YAAY,CAACT,oBAAoB;oBAC/D;gBACF;gBACAS,cAAc;oBACZ,GAAIN,cACA;wBACEO,QAAQ;4BACNF,OAAO;gCACLG,WAAW;gCACXC,eAAe;gCACfC,aAAa;gCACbC,QAAQ,CAAC,IAAI,CAAC;gCACdC,iBAAiB;oCAACX,OAAO;wCAACY,QAAQb;oCAAW;gCAAC;4BAChD;4BACAc,UAAU;wBACZ;oBACF,IACA,CAAC,CAAC;oBACN,CAACjB,oBAAoB,EAAE;wBACrBQ,OAAO;4BACLG,WAAW;4BACXC,eAAe;4BACfC,aAAa;4BACbC,QAAQ,CAAC,IAAI,CAAC;4BACdC,iBAAiB;gCAACX;4BAAK;wBACzB;wBACAa,UAAU;oBACZ;gBACF;YACF;QACF;QACAC,MAAM;IACR;AACF"}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { hash } from 'node:crypto';
|
|
2
1
|
import { interfaceModuleId, MEDIA_LIBRARY_CONFIG_CONTRACT_VERSION, SERVICE_CONTRACT_VERSION, VIEW_CONTRACT_VERSION } from '../../contract.js';
|
|
3
2
|
import { isWorkbenchApp, readConfig } from '../../defineApp.js';
|
|
4
3
|
/**
|
|
@@ -76,9 +75,9 @@ import { isWorkbenchApp, readConfig } from '../../defineApp.js';
|
|
|
76
75
|
* repoint rebuilds. `appType` routes the config to the singleton (no app id to
|
|
77
76
|
* key on). `id` is a content hash of the entry — it fills the
|
|
78
77
|
* installation-config id slot deployed apps get from the applications API,
|
|
79
|
-
* and the workbench keys change detection on it. `version` is
|
|
80
|
-
*
|
|
81
|
-
*/ export function deriveConfigs(app) {
|
|
78
|
+
* and the workbench keys change detection on it. `version` is a string, like
|
|
79
|
+
* the one Brett returns on a deployed `activeConfig`.
|
|
80
|
+
*/ export async function deriveConfigs(app) {
|
|
82
81
|
if (!isWorkbenchApp(app)) return [];
|
|
83
82
|
const config = readConfig(app);
|
|
84
83
|
if (!config) return [];
|
|
@@ -91,14 +90,23 @@ import { isWorkbenchApp, readConfig } from '../../defineApp.js';
|
|
|
91
90
|
title: field.title
|
|
92
91
|
})),
|
|
93
92
|
moduleName: app.name,
|
|
94
|
-
version: MEDIA_LIBRARY_CONFIG_CONTRACT_VERSION
|
|
93
|
+
version: String(MEDIA_LIBRARY_CONFIG_CONTRACT_VERSION)
|
|
95
94
|
};
|
|
96
95
|
return [
|
|
97
96
|
{
|
|
98
97
|
...entry,
|
|
99
|
-
id:
|
|
98
|
+
id: await contentHash(JSON.stringify(entry))
|
|
100
99
|
}
|
|
101
100
|
];
|
|
102
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* SHA-256 of a string, as hex, via the Web Crypto API — available in both Node
|
|
104
|
+
* and the browser. `node:crypto` can't be used: the Vite dev server's dep scan
|
|
105
|
+
* pulls this module into the browser graph.
|
|
106
|
+
*/ async function contentHash(input) {
|
|
107
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins -- the Web Crypto global is available on our Node target and in the browser
|
|
108
|
+
const digest = await globalThis.crypto.subtle.digest('SHA-256', new TextEncoder().encode(input));
|
|
109
|
+
return Array.from(new Uint8Array(digest), (byte)=>byte.toString(16).padStart(2, '0')).join('');
|
|
110
|
+
}
|
|
103
111
|
|
|
104
112
|
//# sourceMappingURL=deriveInterfaces.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/dev/deriveInterfaces.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/dev/deriveInterfaces.ts"],"sourcesContent":["import {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 a string, like\n * the one Brett returns on a deployed `activeConfig`.\n */\nexport async function deriveConfigs(app: CliConfig['app']): Promise<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: String(MEDIA_LIBRARY_CONFIG_CONTRACT_VERSION),\n }\n return [{...entry, id: await contentHash(JSON.stringify(entry))}]\n}\n\n/**\n * SHA-256 of a string, as hex, via the Web Crypto API — available in both Node\n * and the browser. `node:crypto` can't be used: the Vite dev server's dep scan\n * pulls this module into the browser graph.\n */\nasync function contentHash(input: string): Promise<string> {\n // eslint-disable-next-line n/no-unsupported-features/node-builtins -- the Web Crypto global is available on our Node target and in the browser\n const digest = await globalThis.crypto.subtle.digest('SHA-256', new TextEncoder().encode(input))\n return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, '0')).join('')\n}\n"],"names":["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","contentHash","JSON","stringify","input","digest","globalThis","crypto","subtle","TextEncoder","encode","Array","from","Uint8Array","byte","toString","padStart","join"],"mappings":"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"}
|
|
@@ -81,9 +81,9 @@ const devServerManifestSchema = z.object({
|
|
|
81
81
|
// The app's `unstable_defineApp` name — the module-federation alias the
|
|
82
82
|
// workbench loads this config's live values from.
|
|
83
83
|
moduleName: z.optional(z.string()),
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
version: z.
|
|
84
|
+
// The version the workbench federates this config's module under —
|
|
85
|
+
// a string, like the one Brett returns on a deployed `activeConfig`.
|
|
86
|
+
version: z.string()
|
|
87
87
|
}))),
|
|
88
88
|
host: z.string(),
|
|
89
89
|
id: z.optional(z.string()),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/dev/registry.ts"],"sourcesContent":["import {\n existsSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n unlinkSync,\n watch,\n writeFileSync,\n} from 'node:fs'\nimport {join} from 'node:path'\n\nimport {\n coreAppManifestSchema,\n getSanityDataDir,\n studioManifestSchema,\n subdebug,\n} from '@sanity/cli-core'\nimport {z} from 'zod/mini'\n\nimport {AppInterfaceMetadataSchema} from '../../contract.js'\nimport {canonicalizeWatchDir} from './canonicalizeWatchDir.js'\nimport {getProcessStartTime, isOurProcess} from './processLiveness.js'\n\n/**\n * The dev-server registry: how a running `sanity dev` / `sanity start` process\n * advertises itself so the workbench on this machine can find and load it.\n *\n * Two kinds of file under `~/.sanity/dev-servers/` do the coordinating:\n *\n * - `<pid>.json` — one per running app/studio server, holding where it's served\n * plus its inlined manifest and interfaces. The workbench reads these to\n * discover and render local apps. Written by `registerDevServer`, watched by\n * `watchRegistry`.\n * - `workbench.lock` — a single machine-wide lock, so only one workbench shell\n * runs at a time and later `dev`s register into it instead of starting their\n * own. Managed by `acquireWorkbenchLock` / `readWorkbenchLock`.\n *\n * Both files belong to the process that created them and must not outlive it.\n * Three things keep that true: an explicit `release()` on clean shutdown, an\n * `exit` backstop for abrupt exits (`unlinkOnProcessExit`), and a dead-pid prune\n * on read (`isOurProcess`) that clears whatever a crashed process left behind.\n */\n\nconst devDebug = subdebug('dev')\n\n/** Bump when the manifest/lock shape changes in a breaking way. */\nconst REGISTRY_VERSION = 1\n\n/**\n * The current process's start time as reported by the OS, for the `startedAt`\n * that `isOurProcess` checks on re-read. Falls back to now when the OS time is\n * unavailable — `new Date()` alone records the write time, which drifts from\n * process start by enough to look stale and get pruned right after writing.\n */\nfunction ownStartedAt(): string {\n return (getProcessStartTime(process.pid) ?? new Date()).toISOString()\n}\n\nconst interfaceBaseFields = {\n /** CLI-minted for a local interface; a deployed one gets its id from Brett. */\n id: z.string(),\n moduleId: z.string(),\n name: z.string(),\n /** Raw source vite serves; a deployed interface carries only the `moduleId`. */\n src: z.string(),\n title: z.string(),\n version: z.optional(z.string()),\n}\n\n/**\n * A forwarded interface, discriminated on `type`. Kept outside the manifest so\n * the workbench renders local panels and runs workers without a deploy.\n */\nconst devServerInterfaceSchema = z.discriminatedUnion('type', [\n z.object({\n ...interfaceBaseFields,\n metadata: z.nullable(AppInterfaceMetadataSchema),\n type: z.literal('app'),\n }),\n z.object({...interfaceBaseFields, metadata: z.null(), type: z.literal('panel')}),\n z.object({...interfaceBaseFields, metadata: z.null(), type: z.literal('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\n// One shared `exit` listener drives every registered cleanup, so N locks/entries\n// don't each add a listener and trip Node's MaxListeners warning.\nconst exitCleanups = new Set<() => void>()\nlet exitListenerInstalled = false\n\nfunction runExitCleanups(): void {\n for (const cleanup of exitCleanups) cleanup()\n}\n\n/** Exercise the exit backstop in tests without terminating the process; not part\n * of the package's public surface. */\nexport const runRegistryExitCleanupForTesting = runExitCleanups\n\n/**\n * Delete a registry file synchronously on process exit, as a backstop for abrupt\n * termination. Vite installs its own SIGTERM handler that calls `process.exit()`,\n * which can outrun the async server teardown and leave the lock or registry entry\n * behind — a stray dev-server that lingers until the dead-pid prune clears it. The\n * `exit` event only runs synchronous work, hence `unlinkSync`. `ownedByUs` guards\n * the shared lock so a successor that reacquired it isn't wiped. Returns a\n * detacher to call after a clean release.\n */\nfunction unlinkOnProcessExit(filePath: string, ownedByUs: () => boolean): () => void {\n const cleanup = () => {\n if (!ownedByUs()) return\n try {\n unlinkSync(filePath)\n } catch {\n // The file may already have been removed during shutdown.\n }\n }\n exitCleanups.add(cleanup)\n\n if (!exitListenerInstalled) {\n exitListenerInstalled = true\n process.once('exit', runExitCleanups)\n }\n\n return () => exitCleanups.delete(cleanup)\n}\n\ninterface DevServerRegistration {\n /** Remove the registry entry. */\n release: () => void\n /**\n * Rewrite the registry entry with partial updates merged in. Also bumps the\n * file's mtime, which fires `watchRegistry` in any workbench process and\n * triggers a rebroadcast to connected clients.\n */\n update: (patch: Partial<Omit<DevServerManifest, 'pid' | 'startedAt' | 'version'>>) => void\n}\n\n/**\n * Write a manifest file for the current process and return a handle with a\n * `release` function that removes it plus an `update` function for patching\n * fields post-registration. Uses synchronous I/O so the file exists before\n * any signal handler could fire.\n */\nexport function registerDevServer(\n manifest: Omit<DevServerManifest, 'pid' | 'startedAt' | 'version'>,\n): DevServerRegistration {\n const registryDir = getRegistryDir()\n mkdirSync(registryDir, {recursive: true})\n\n let current: DevServerManifest = {\n ...manifest,\n pid: process.pid,\n startedAt: ownStartedAt(),\n version: REGISTRY_VERSION,\n }\n\n const filePath = join(registryDir, `${process.pid}.json`)\n writeFileSync(filePath, JSON.stringify(current, null, 2))\n\n // Guard against late updates from background tasks (e.g. the initial\n // manifest extraction) landing after `release()` has deleted the file —\n // without this, the update would re-create the registry entry and leak.\n let released = false\n\n // The file is pid-named, so it's always ours to remove on exit.\n const detachExitCleanup = unlinkOnProcessExit(filePath, () => !released)\n\n return {\n release() {\n released = true\n detachExitCleanup()\n try {\n unlinkSync(filePath)\n } catch {\n // ENOENT is fine — already cleaned up\n }\n },\n update(patch) {\n if (released) return\n current = {...current, ...patch}\n writeFileSync(filePath, JSON.stringify(current, null, 2))\n },\n }\n}\n\n/**\n * Read all manifest files from the registry, prune stale entries (dead PIDs),\n * and return the live ones.\n */\nexport function getRegisteredServers(): DevServerManifest[] {\n const registryDir = getRegistryDir()\n\n if (!existsSync(registryDir)) {\n return []\n }\n\n const files = readdirSync(registryDir).filter((f) => f.endsWith('.json'))\n const servers: DevServerManifest[] = []\n\n for (const file of files) {\n const filePath = join(registryDir, file)\n let raw: unknown\n try {\n raw = JSON.parse(readFileSync(filePath, 'utf8'))\n } catch {\n continue\n }\n\n const {data, success} = devServerManifestSchema.safeParse(raw)\n if (!success) continue\n\n if (isOurProcess(data.pid, data.startedAt)) {\n servers.push(data)\n } else {\n try {\n unlinkSync(filePath)\n } catch {\n // Ignore — another process may have already cleaned it up\n }\n }\n }\n\n return servers\n}\n\ninterface RegistryWatcher {\n close(): void\n}\n\n/**\n * Watch the registry directory for changes and invoke the callback with the\n * current list of live servers whenever a change is detected.\n *\n * Uses `fs.watch` with a debounce to coalesce rapid file changes (e.g. a\n * server starting and writing its manifest triggers multiple FS events).\n */\nexport function watchRegistry(callback: (servers: DevServerManifest[]) => void): RegistryWatcher {\n const registryDir = getRegistryDir()\n mkdirSync(registryDir, {recursive: true})\n\n // Canonicalize to the real long path so `fs.watch` doesn't abort on Windows\n // short-path dirs. See `canonicalizeWatchDir`.\n const watchDir = canonicalizeWatchDir(registryDir)\n\n let debounceTimer: ReturnType<typeof setTimeout> | undefined\n\n const notify = () => {\n clearTimeout(debounceTimer)\n debounceTimer = setTimeout(() => {\n callback(getRegisteredServers())\n }, 50)\n }\n\n const watcher = watch(watchDir, notify)\n\n return {\n close() {\n clearTimeout(debounceTimer)\n watcher.close()\n },\n }\n}\n\n// The workbench singleton lock — \"one workbench per machine\". Lives in the same\n// registry dir and shares the liveness/prune model: a stale lock left by a\n// crashed process is pruned on read so the next acquire isn't blocked forever.\n\nconst workbenchLockSchema = z.object({\n host: z.string(),\n pid: z.number(),\n port: z.number(),\n startedAt: z.string(),\n version: z.literal(REGISTRY_VERSION),\n})\n\n/**\n * Read the workbench lock file and return its contents if the holding\n * process is still alive. Prunes stale locks from crashed processes.\n */\nexport function readWorkbenchLock(): z.infer<typeof workbenchLockSchema> | undefined {\n const lockPath = join(getRegistryDir(), 'workbench.lock')\n\n let contents: string\n try {\n contents = readFileSync(lockPath, 'utf8')\n } catch {\n // File doesn't exist — nothing to prune, nothing to return\n return undefined\n }\n\n // Past this point the file exists. Anything that isn't a live, valid lock\n // (unparsable JSON, schema mismatch, dead/reused PID) is stale and must be\n // pruned — otherwise the next `acquireWorkbenchLock` call is blocked by\n // EEXIST forever and `sanity dev` silently no-ops the workbench server.\n const data = parseLockContents(contents)\n devDebug('Read workbench lock: %o', data)\n if (data && isOurProcess(data.pid, data.startedAt)) {\n devDebug('Workbench process is alive at pid %d on port %d', data.pid, data.port)\n return data\n }\n\n pruneWorkbenchLock(lockPath)\n return undefined\n}\n\nfunction parseLockContents(contents: string): z.infer<typeof workbenchLockSchema> | undefined {\n try {\n const {data, success} = workbenchLockSchema.safeParse(JSON.parse(contents))\n return success ? data : undefined\n } catch {\n return undefined\n }\n}\n\nfunction pruneWorkbenchLock(lockPath: string): void {\n try {\n devDebug('Removing stale workbench lock')\n unlinkSync(lockPath)\n devDebug('Stale workbench lock removed')\n } catch {\n // Another process may have already cleaned it up\n }\n}\n\ninterface WorkbenchLock {\n /** Release the lock file. */\n release: () => void\n /** Update the lock with the actual port after the server starts listening. */\n updatePort: (port: number) => void\n}\n\n/**\n * Attempt to acquire an exclusive lock for the workbench process.\n * Uses `O_EXCL` (the `wx` flag) which is atomic at the OS level — only one\n * process can create the file.\n *\n * The lock stores `{pid, host, port}` so other processes can find the\n * running workbench. Call `updatePort` after the Vite server starts to\n * write the actual port (Vite may pick a different one).\n *\n * @returns A {@link WorkbenchLock} if acquired, or `undefined` if another\n * live process already holds it.\n */\nexport function acquireWorkbenchLock(\n info: {host: string; port: number},\n retries = 1,\n): WorkbenchLock | undefined {\n const registryDir = getRegistryDir()\n mkdirSync(registryDir, {recursive: true})\n\n const lockPath = join(registryDir, 'workbench.lock')\n const startedAt = ownStartedAt()\n const lockData = {\n host: info.host,\n pid: process.pid,\n port: info.port,\n startedAt,\n version: REGISTRY_VERSION,\n }\n\n devDebug('Acquiring workbench lock at %s', lockPath)\n\n try {\n writeFileSync(lockPath, JSON.stringify(lockData), {flag: 'wx'})\n devDebug('Workbench lock acquired')\n\n let released = false\n // Only wipe the lock on exit if it's still ours — a successor that reacquired\n // it after our own release must not be clobbered.\n const detachExitCleanup = unlinkOnProcessExit(lockPath, () => {\n if (released) return false\n try {\n const disk = parseLockContents(readFileSync(lockPath, 'utf8'))\n return disk?.pid === process.pid && disk.startedAt === startedAt\n } catch {\n return false\n }\n })\n\n return {\n release() {\n released = true\n detachExitCleanup()\n try {\n unlinkSync(lockPath)\n } catch {\n // Already cleaned up\n }\n },\n updatePort(port: number) {\n writeFileSync(lockPath, JSON.stringify({...lockData, port}))\n },\n }\n } catch (err: unknown) {\n devDebug(\n 'Failed to acquire workbench lock: %s',\n err instanceof Error ? err.message : String(err),\n )\n if (!isNodeError(err) || err.code !== 'EEXIST') return undefined\n\n // Lock exists — check if the holder is still alive\n const existing = readWorkbenchLock()\n if (existing) return undefined\n\n // Stale lock was pruned by readWorkbenchLock — retry (with guard against infinite recursion)\n if (retries <= 0) return undefined\n return acquireWorkbenchLock(info, retries - 1)\n }\n}\n\nfunction isNodeError(err: unknown): err is NodeJS.ErrnoException {\n return err instanceof Error && 'code' in err\n}\n"],"names":["existsSync","mkdirSync","readdirSync","readFileSync","unlinkSync","watch","writeFileSync","join","coreAppManifestSchema","getSanityDataDir","studioManifestSchema","subdebug","z","AppInterfaceMetadataSchema","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","exitCleanups","Set","exitListenerInstalled","runExitCleanups","cleanup","runRegistryExitCleanupForTesting","unlinkOnProcessExit","filePath","ownedByUs","add","once","delete","registerDevServer","registryDir","recursive","current","JSON","stringify","released","detachExitCleanup","release","update","patch","getRegisteredServers","files","filter","f","endsWith","servers","file","raw","parse","data","success","safeParse","push","watchRegistry","callback","watchDir","debounceTimer","notify","clearTimeout","setTimeout","watcher","close","workbenchLockSchema","readWorkbenchLock","lockPath","contents","undefined","parseLockContents","pruneWorkbenchLock","acquireWorkbenchLock","info","retries","lockData","flag","disk","updatePort","err","Error","message","String","isNodeError","code","existing"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/dev/registry.ts"],"sourcesContent":["import {\n existsSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n unlinkSync,\n watch,\n writeFileSync,\n} from 'node:fs'\nimport {join} from 'node:path'\n\nimport {\n coreAppManifestSchema,\n getSanityDataDir,\n studioManifestSchema,\n subdebug,\n} from '@sanity/cli-core'\nimport {z} from 'zod/mini'\n\nimport {AppInterfaceMetadataSchema} from '../../contract.js'\nimport {canonicalizeWatchDir} from './canonicalizeWatchDir.js'\nimport {getProcessStartTime, isOurProcess} from './processLiveness.js'\n\n/**\n * The dev-server registry: how a running `sanity dev` / `sanity start` process\n * advertises itself so the workbench on this machine can find and load it.\n *\n * Two kinds of file under `~/.sanity/dev-servers/` do the coordinating:\n *\n * - `<pid>.json` — one per running app/studio server, holding where it's served\n * plus its inlined manifest and interfaces. The workbench reads these to\n * discover and render local apps. Written by `registerDevServer`, watched by\n * `watchRegistry`.\n * - `workbench.lock` — a single machine-wide lock, so only one workbench shell\n * runs at a time and later `dev`s register into it instead of starting their\n * own. Managed by `acquireWorkbenchLock` / `readWorkbenchLock`.\n *\n * Both files belong to the process that created them and must not outlive it.\n * Three things keep that true: an explicit `release()` on clean shutdown, an\n * `exit` backstop for abrupt exits (`unlinkOnProcessExit`), and a dead-pid prune\n * on read (`isOurProcess`) that clears whatever a crashed process left behind.\n */\n\nconst devDebug = subdebug('dev')\n\n/** Bump when the manifest/lock shape changes in a breaking way. */\nconst REGISTRY_VERSION = 1\n\n/**\n * The current process's start time as reported by the OS, for the `startedAt`\n * that `isOurProcess` checks on re-read. Falls back to now when the OS time is\n * unavailable — `new Date()` alone records the write time, which drifts from\n * process start by enough to look stale and get pruned right after writing.\n */\nfunction ownStartedAt(): string {\n return (getProcessStartTime(process.pid) ?? new Date()).toISOString()\n}\n\nconst interfaceBaseFields = {\n /** CLI-minted for a local interface; a deployed one gets its id from Brett. */\n id: z.string(),\n moduleId: z.string(),\n name: z.string(),\n /** Raw source vite serves; a deployed interface carries only the `moduleId`. */\n src: z.string(),\n title: z.string(),\n version: z.optional(z.string()),\n}\n\n/**\n * A forwarded interface, discriminated on `type`. Kept outside the manifest so\n * the workbench renders local panels and runs workers without a deploy.\n */\nconst devServerInterfaceSchema = z.discriminatedUnion('type', [\n z.object({\n ...interfaceBaseFields,\n metadata: z.nullable(AppInterfaceMetadataSchema),\n type: z.literal('app'),\n }),\n z.object({...interfaceBaseFields, metadata: z.null(), type: z.literal('panel')}),\n z.object({...interfaceBaseFields, metadata: z.null(), type: z.literal('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 // The version the workbench federates this config's module under —\n // a string, like the one Brett returns on a deployed `activeConfig`.\n version: z.string(),\n }),\n ),\n ),\n host: z.string(),\n id: z.optional(z.string()),\n interfaces: z.optional(z.array(devServerInterfaceSchema)),\n /**\n * Inlined manifest — either a {@link StudioManifest} or {@link CoreAppManifest},\n * validated against the shared cli-core schemas. The registry stores and\n * rebroadcasts it; the CLI is what extracts and writes it.\n */\n manifest: z.optional(z.union([studioManifestSchema, coreAppManifestSchema])),\n /**\n * ISO timestamp of the most recent successful manifest extraction. Bumped\n * on every regeneration so re-writing this registry entry triggers the\n * workbench `watchRegistry` watcher and forces a rebroadcast to clients.\n */\n manifestUpdatedAt: z.optional(z.string()),\n pid: z.number(),\n port: z.number(),\n projectId: z.optional(z.string()),\n startedAt: z.string(),\n type: z.enum(['coreApp', 'studio']),\n version: z.literal(REGISTRY_VERSION),\n workDir: z.string(),\n})\n/**\n * A manifest describing a running dev server process (studio or app).\n * Stored as `~/.sanity/dev-servers/<pid>.json`.\n *\n * The workbench singleton is tracked separately via the lock file — see\n * `acquireWorkbenchLock` and `readWorkbenchLock` below.\n */\nexport type DevServerManifest = z.infer<typeof devServerManifestSchema>\n\n/**\n * Path to the dev server registry directory. Lives under the shared Sanity\n * config directory to stay consistent with other CLI paths.\n */\nfunction getRegistryDir(): string {\n return join(getSanityDataDir(), 'dev-servers')\n}\n\n// One shared `exit` listener drives every registered cleanup, so N locks/entries\n// don't each add a listener and trip Node's MaxListeners warning.\nconst exitCleanups = new Set<() => void>()\nlet exitListenerInstalled = false\n\nfunction runExitCleanups(): void {\n for (const cleanup of exitCleanups) cleanup()\n}\n\n/** Exercise the exit backstop in tests without terminating the process; not part\n * of the package's public surface. */\nexport const runRegistryExitCleanupForTesting = runExitCleanups\n\n/**\n * Delete a registry file synchronously on process exit, as a backstop for abrupt\n * termination. Vite installs its own SIGTERM handler that calls `process.exit()`,\n * which can outrun the async server teardown and leave the lock or registry entry\n * behind — a stray dev-server that lingers until the dead-pid prune clears it. The\n * `exit` event only runs synchronous work, hence `unlinkSync`. `ownedByUs` guards\n * the shared lock so a successor that reacquired it isn't wiped. Returns a\n * detacher to call after a clean release.\n */\nfunction unlinkOnProcessExit(filePath: string, ownedByUs: () => boolean): () => void {\n const cleanup = () => {\n if (!ownedByUs()) return\n try {\n unlinkSync(filePath)\n } catch {\n // The file may already have been removed during shutdown.\n }\n }\n exitCleanups.add(cleanup)\n\n if (!exitListenerInstalled) {\n exitListenerInstalled = true\n process.once('exit', runExitCleanups)\n }\n\n return () => exitCleanups.delete(cleanup)\n}\n\ninterface DevServerRegistration {\n /** Remove the registry entry. */\n release: () => void\n /**\n * Rewrite the registry entry with partial updates merged in. Also bumps the\n * file's mtime, which fires `watchRegistry` in any workbench process and\n * triggers a rebroadcast to connected clients.\n */\n update: (patch: Partial<Omit<DevServerManifest, 'pid' | 'startedAt' | 'version'>>) => void\n}\n\n/**\n * Write a manifest file for the current process and return a handle with a\n * `release` function that removes it plus an `update` function for patching\n * fields post-registration. Uses synchronous I/O so the file exists before\n * any signal handler could fire.\n */\nexport function registerDevServer(\n manifest: Omit<DevServerManifest, 'pid' | 'startedAt' | 'version'>,\n): DevServerRegistration {\n const registryDir = getRegistryDir()\n mkdirSync(registryDir, {recursive: true})\n\n let current: DevServerManifest = {\n ...manifest,\n pid: process.pid,\n startedAt: ownStartedAt(),\n version: REGISTRY_VERSION,\n }\n\n const filePath = join(registryDir, `${process.pid}.json`)\n writeFileSync(filePath, JSON.stringify(current, null, 2))\n\n // Guard against late updates from background tasks (e.g. the initial\n // manifest extraction) landing after `release()` has deleted the file —\n // without this, the update would re-create the registry entry and leak.\n let released = false\n\n // The file is pid-named, so it's always ours to remove on exit.\n const detachExitCleanup = unlinkOnProcessExit(filePath, () => !released)\n\n return {\n release() {\n released = true\n detachExitCleanup()\n try {\n unlinkSync(filePath)\n } catch {\n // ENOENT is fine — already cleaned up\n }\n },\n update(patch) {\n if (released) return\n current = {...current, ...patch}\n writeFileSync(filePath, JSON.stringify(current, null, 2))\n },\n }\n}\n\n/**\n * Read all manifest files from the registry, prune stale entries (dead PIDs),\n * and return the live ones.\n */\nexport function getRegisteredServers(): DevServerManifest[] {\n const registryDir = getRegistryDir()\n\n if (!existsSync(registryDir)) {\n return []\n }\n\n const files = readdirSync(registryDir).filter((f) => f.endsWith('.json'))\n const servers: DevServerManifest[] = []\n\n for (const file of files) {\n const filePath = join(registryDir, file)\n let raw: unknown\n try {\n raw = JSON.parse(readFileSync(filePath, 'utf8'))\n } catch {\n continue\n }\n\n const {data, success} = devServerManifestSchema.safeParse(raw)\n if (!success) continue\n\n if (isOurProcess(data.pid, data.startedAt)) {\n servers.push(data)\n } else {\n try {\n unlinkSync(filePath)\n } catch {\n // Ignore — another process may have already cleaned it up\n }\n }\n }\n\n return servers\n}\n\ninterface RegistryWatcher {\n close(): void\n}\n\n/**\n * Watch the registry directory for changes and invoke the callback with the\n * current list of live servers whenever a change is detected.\n *\n * Uses `fs.watch` with a debounce to coalesce rapid file changes (e.g. a\n * server starting and writing its manifest triggers multiple FS events).\n */\nexport function watchRegistry(callback: (servers: DevServerManifest[]) => void): RegistryWatcher {\n const registryDir = getRegistryDir()\n mkdirSync(registryDir, {recursive: true})\n\n // Canonicalize to the real long path so `fs.watch` doesn't abort on Windows\n // short-path dirs. See `canonicalizeWatchDir`.\n const watchDir = canonicalizeWatchDir(registryDir)\n\n let debounceTimer: ReturnType<typeof setTimeout> | undefined\n\n const notify = () => {\n clearTimeout(debounceTimer)\n debounceTimer = setTimeout(() => {\n callback(getRegisteredServers())\n }, 50)\n }\n\n const watcher = watch(watchDir, notify)\n\n return {\n close() {\n clearTimeout(debounceTimer)\n watcher.close()\n },\n }\n}\n\n// The workbench singleton lock — \"one workbench per machine\". Lives in the same\n// registry dir and shares the liveness/prune model: a stale lock left by a\n// crashed process is pruned on read so the next acquire isn't blocked forever.\n\nconst workbenchLockSchema = z.object({\n host: z.string(),\n pid: z.number(),\n port: z.number(),\n startedAt: z.string(),\n version: z.literal(REGISTRY_VERSION),\n})\n\n/**\n * Read the workbench lock file and return its contents if the holding\n * process is still alive. Prunes stale locks from crashed processes.\n */\nexport function readWorkbenchLock(): z.infer<typeof workbenchLockSchema> | undefined {\n const lockPath = join(getRegistryDir(), 'workbench.lock')\n\n let contents: string\n try {\n contents = readFileSync(lockPath, 'utf8')\n } catch {\n // File doesn't exist — nothing to prune, nothing to return\n return undefined\n }\n\n // Past this point the file exists. Anything that isn't a live, valid lock\n // (unparsable JSON, schema mismatch, dead/reused PID) is stale and must be\n // pruned — otherwise the next `acquireWorkbenchLock` call is blocked by\n // EEXIST forever and `sanity dev` silently no-ops the workbench server.\n const data = parseLockContents(contents)\n devDebug('Read workbench lock: %o', data)\n if (data && isOurProcess(data.pid, data.startedAt)) {\n devDebug('Workbench process is alive at pid %d on port %d', data.pid, data.port)\n return data\n }\n\n pruneWorkbenchLock(lockPath)\n return undefined\n}\n\nfunction parseLockContents(contents: string): z.infer<typeof workbenchLockSchema> | undefined {\n try {\n const {data, success} = workbenchLockSchema.safeParse(JSON.parse(contents))\n return success ? data : undefined\n } catch {\n return undefined\n }\n}\n\nfunction pruneWorkbenchLock(lockPath: string): void {\n try {\n devDebug('Removing stale workbench lock')\n unlinkSync(lockPath)\n devDebug('Stale workbench lock removed')\n } catch {\n // Another process may have already cleaned it up\n }\n}\n\ninterface WorkbenchLock {\n /** Release the lock file. */\n release: () => void\n /** Update the lock with the actual port after the server starts listening. */\n updatePort: (port: number) => void\n}\n\n/**\n * Attempt to acquire an exclusive lock for the workbench process.\n * Uses `O_EXCL` (the `wx` flag) which is atomic at the OS level — only one\n * process can create the file.\n *\n * The lock stores `{pid, host, port}` so other processes can find the\n * running workbench. Call `updatePort` after the Vite server starts to\n * write the actual port (Vite may pick a different one).\n *\n * @returns A {@link WorkbenchLock} if acquired, or `undefined` if another\n * live process already holds it.\n */\nexport function acquireWorkbenchLock(\n info: {host: string; port: number},\n retries = 1,\n): WorkbenchLock | undefined {\n const registryDir = getRegistryDir()\n mkdirSync(registryDir, {recursive: true})\n\n const lockPath = join(registryDir, 'workbench.lock')\n const startedAt = ownStartedAt()\n const lockData = {\n host: info.host,\n pid: process.pid,\n port: info.port,\n startedAt,\n version: REGISTRY_VERSION,\n }\n\n devDebug('Acquiring workbench lock at %s', lockPath)\n\n try {\n writeFileSync(lockPath, JSON.stringify(lockData), {flag: 'wx'})\n devDebug('Workbench lock acquired')\n\n let released = false\n // Only wipe the lock on exit if it's still ours — a successor that reacquired\n // it after our own release must not be clobbered.\n const detachExitCleanup = unlinkOnProcessExit(lockPath, () => {\n if (released) return false\n try {\n const disk = parseLockContents(readFileSync(lockPath, 'utf8'))\n return disk?.pid === process.pid && disk.startedAt === startedAt\n } catch {\n return false\n }\n })\n\n return {\n release() {\n released = true\n detachExitCleanup()\n try {\n unlinkSync(lockPath)\n } catch {\n // Already cleaned up\n }\n },\n updatePort(port: number) {\n writeFileSync(lockPath, JSON.stringify({...lockData, port}))\n },\n }\n } catch (err: unknown) {\n devDebug(\n 'Failed to acquire workbench lock: %s',\n err instanceof Error ? err.message : String(err),\n )\n if (!isNodeError(err) || err.code !== 'EEXIST') return undefined\n\n // Lock exists — check if the holder is still alive\n const existing = readWorkbenchLock()\n if (existing) return undefined\n\n // Stale lock was pruned by readWorkbenchLock — retry (with guard against infinite recursion)\n if (retries <= 0) return undefined\n return acquireWorkbenchLock(info, retries - 1)\n }\n}\n\nfunction isNodeError(err: unknown): err is NodeJS.ErrnoException {\n return err instanceof Error && 'code' in err\n}\n"],"names":["existsSync","mkdirSync","readdirSync","readFileSync","unlinkSync","watch","writeFileSync","join","coreAppManifestSchema","getSanityDataDir","studioManifestSchema","subdebug","z","AppInterfaceMetadataSchema","canonicalizeWatchDir","getProcessStartTime","isOurProcess","devDebug","REGISTRY_VERSION","ownStartedAt","process","pid","Date","toISOString","interfaceBaseFields","id","string","moduleId","name","src","title","version","optional","devServerInterfaceSchema","discriminatedUnion","object","metadata","nullable","type","literal","null","devServerManifestSchema","configs","array","appType","fields","public","boolean","moduleName","host","interfaces","manifest","union","manifestUpdatedAt","number","port","projectId","startedAt","enum","workDir","getRegistryDir","exitCleanups","Set","exitListenerInstalled","runExitCleanups","cleanup","runRegistryExitCleanupForTesting","unlinkOnProcessExit","filePath","ownedByUs","add","once","delete","registerDevServer","registryDir","recursive","current","JSON","stringify","released","detachExitCleanup","release","update","patch","getRegisteredServers","files","filter","f","endsWith","servers","file","raw","parse","data","success","safeParse","push","watchRegistry","callback","watchDir","debounceTimer","notify","clearTimeout","setTimeout","watcher","close","workbenchLockSchema","readWorkbenchLock","lockPath","contents","undefined","parseLockContents","pruneWorkbenchLock","acquireWorkbenchLock","info","retries","lockData","flag","disk","updatePort","err","Error","message","String","isNodeError","code","existing"],"mappings":"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"}
|
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
import { getCliConfigUncached } from '@sanity/cli-core';
|
|
2
2
|
import { resolveAppId } from '../../appId.js';
|
|
3
|
+
import { formatWorkbenchAppErrors, validateWorkbenchApp } from '../../validateWorkbenchApp.js';
|
|
3
4
|
import { deriveConfigs, deriveInterfaces } from './deriveInterfaces.js';
|
|
4
5
|
import { trackExposesSet } from './exposesSetId.js';
|
|
5
6
|
import { registerDevServer } from './registry.js';
|
|
6
7
|
import { startDevManifestWatcher } from './startDevManifestWatcher.js';
|
|
8
|
+
/**
|
|
9
|
+
* Log any config validation errors without aborting. Unlike build and deploy,
|
|
10
|
+
* dev stays up on an invalid config so the author sees the errors and fixes them
|
|
11
|
+
* live on the next save.
|
|
12
|
+
*/ function reportConfigErrors(app, output) {
|
|
13
|
+
const errors = validateWorkbenchApp(app);
|
|
14
|
+
if (errors.length === 0) return;
|
|
15
|
+
// `output.error` exits the process; `warn` keeps the dev server alive.
|
|
16
|
+
output.warn(formatWorkbenchAppErrors(errors));
|
|
17
|
+
}
|
|
7
18
|
/** The address the server actually bound — the live socket, which can differ from the configured port under non-strict ports. */ function serverAddress(server) {
|
|
8
19
|
const resolvedHost = server.config.server.host;
|
|
9
20
|
const addr = server.httpServer?.address();
|
|
@@ -19,12 +30,13 @@ import { startDevManifestWatcher } from './startDevManifestWatcher.js';
|
|
|
19
30
|
*/ export async function startDevServerRegistration(options) {
|
|
20
31
|
const { cliConfig, extractManifest, isApp, onInterfaceSetChange, output, server, workDir } = options;
|
|
21
32
|
const { host: appHost, port: appPort } = serverAddress(server);
|
|
33
|
+
reportConfigErrors(cliConfig.app, output);
|
|
22
34
|
// Forwarded alongside (not inside) the manifest so the workbench renders local
|
|
23
35
|
// panels/workers and reads the configs without a deploy.
|
|
24
36
|
const interfaces = deriveInterfaces(cliConfig.app, {
|
|
25
37
|
isApp
|
|
26
38
|
});
|
|
27
|
-
const configs = deriveConfigs(cliConfig.app);
|
|
39
|
+
const configs = await deriveConfigs(cliConfig.app);
|
|
28
40
|
const registration = registerDevServer({
|
|
29
41
|
configs,
|
|
30
42
|
host: appHost,
|
|
@@ -50,8 +62,9 @@ import { startDevManifestWatcher } from './startDevManifestWatcher.js';
|
|
|
50
62
|
// so omitting would wipe the registered set.
|
|
51
63
|
extract: async (params)=>{
|
|
52
64
|
const app = (await getCliConfigUncached(params.workDir)).app;
|
|
65
|
+
reportConfigErrors(app, output);
|
|
53
66
|
return {
|
|
54
|
-
configs: deriveConfigs(app),
|
|
67
|
+
configs: await deriveConfigs(app),
|
|
55
68
|
interfaces: deriveInterfaces(app, {
|
|
56
69
|
isApp
|
|
57
70
|
}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/dev/startDevServerRegistration.ts"],"sourcesContent":["import {type CliConfig, getCliConfigUncached, type Output} from '@sanity/cli-core'\nimport {type ViteDevServer} from 'vite'\n\nimport {resolveAppId} from '../../appId.js'\nimport {deriveConfigs, deriveInterfaces} from './deriveInterfaces.js'\nimport {trackExposesSet} from './exposesSetId.js'\nimport {type DevServerManifest, registerDevServer} from './registry.js'\nimport {startDevManifestWatcher} from './startDevManifestWatcher.js'\n\ninterface DevServerRegistrationOptions {\n cliConfig: CliConfig\n /**\n * Extract the project manifest to inline into the registry. The caller owns the\n * studio-vs-app split (manifest formats are CLI-domain); registration re-derives\n * the interface set alongside it.\n */\n extractManifest: (params: {\n configPath: string\n workDir: string\n }) => Promise<DevServerManifest['manifest']>\n isApp: boolean\n output: Output\n server: ViteDevServer\n workDir: string\n\n /**\n * Rebuild the app's federation remote when its interface set changes, awaited\n * *before* the registry patch — the patch reloads the workbench page, which must\n * re-fetch a remote that already exposes the new interface. Resolves with the\n * recreated server so the entry gets its actual address (non-strict ports may\n * shift it); must reject if the restart produces no server, so the set stays\n * uncommitted and the next save retries instead of advertising a dead port.\n */\n onInterfaceSetChange?: () => Promise<ViteDevServer>\n}\n\ninterface DevServerRegistrationHandle {\n close: () => Promise<void>\n}\n\n/** The address the server actually bound — the live socket, which can differ from the configured port under non-strict ports. */\nfunction serverAddress(server: ViteDevServer) {\n const resolvedHost = server.config.server.host\n const addr = server.httpServer?.address()\n return {\n host: typeof resolvedHost === 'string' ? resolvedHost : 'localhost',\n port: typeof addr === 'object' && addr ? addr.port : server.config.server.port,\n }\n}\n\n/**\n * Register the dev server in the registry and watch its config for manifest +\n * interface changes. The workbench reads the entry to locate and render the\n * server; the watcher keeps it current as `sanity.cli.ts` is edited.\n */\nexport async function startDevServerRegistration(\n options: DevServerRegistrationOptions,\n): Promise<DevServerRegistrationHandle> {\n const {cliConfig, extractManifest, isApp, onInterfaceSetChange, output, server, workDir} = options\n\n const {host: appHost, port: appPort} = serverAddress(server)\n\n // Forwarded alongside (not inside) the manifest so the workbench renders local\n // panels/workers and reads the configs without a deploy.\n const interfaces = deriveInterfaces(cliConfig.app, {isApp})\n const configs = deriveConfigs(cliConfig.app)\n\n const registration = registerDevServer({\n configs,\n host: appHost,\n // Keyed by where it's served (not the deployment id), so a running app can't\n // collide with its deployed twin — on the configured port, not the bound one,\n // to match `__SANITY_APP_ID__`, compiled before any non-strict shift.\n id: resolveAppId({host: appHost, port: server.config.server.port ?? appPort}),\n interfaces,\n port: appPort,\n projectId: cliConfig?.api?.projectId,\n type: isApp ? 'coreApp' : 'studio',\n workDir,\n })\n\n const exposesSet = trackExposesSet({configs, interfaces})\n\n const watcher = await startDevManifestWatcher({\n // Re-derive every pass (don't omit): the registry patch is a shallow merge,\n // so omitting would wipe the registered set.\n extract: async (params) => {\n const app = (await getCliConfigUncached(params.workDir)).app\n return {\n configs: deriveConfigs(app),\n interfaces: deriveInterfaces(app, {isApp}),\n manifest: await extractManifest(params),\n }\n },\n // A studio's root resolves to `sanity.config.*` but its interfaces live in\n // `sanity.cli.*` — watch that too. Apps already root at `sanity.cli.*`.\n extraWatchFilenames: isApp ? undefined : ['sanity.cli.js', 'sanity.cli.ts'],\n output,\n update: async (patch) => {\n if (\n !exposesSet.changed({\n configs: patch.configs,\n interfaces: patch.interfaces,\n })\n ) {\n registration.update(patch)\n return\n }\n // Rebuild the remote *before* patching the registry — the patch reloads the\n // page, which must re-fetch a remote that already exposes the new interface.\n const rebuiltServer = await onInterfaceSetChange?.()\n // Commit only after a successful rebuild, so a thrown one retries next pass.\n exposesSet.commit({\n configs: patch.configs,\n interfaces: patch.interfaces,\n })\n // The recreated server can bind a different port (non-strict ports).\n registration.update(rebuiltServer ? {...patch, ...serverAddress(rebuiltServer)} : patch)\n },\n workDir,\n })\n\n return {\n close: async () => {\n registration.release()\n await watcher.close()\n },\n }\n}\n"],"names":["getCliConfigUncached","resolveAppId","deriveConfigs","deriveInterfaces","trackExposesSet","registerDevServer","startDevManifestWatcher","serverAddress","server","resolvedHost","config","host","addr","httpServer","address","port","startDevServerRegistration","options","cliConfig","extractManifest","isApp","onInterfaceSetChange","
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/dev/startDevServerRegistration.ts"],"sourcesContent":["import {type CliConfig, getCliConfigUncached, type Output} from '@sanity/cli-core'\nimport {type ViteDevServer} from 'vite'\n\nimport {resolveAppId} from '../../appId.js'\nimport {formatWorkbenchAppErrors, validateWorkbenchApp} from '../../validateWorkbenchApp.js'\nimport {deriveConfigs, deriveInterfaces} from './deriveInterfaces.js'\nimport {trackExposesSet} from './exposesSetId.js'\nimport {type DevServerManifest, registerDevServer} from './registry.js'\nimport {startDevManifestWatcher} from './startDevManifestWatcher.js'\n\ninterface DevServerRegistrationOptions {\n cliConfig: CliConfig\n /**\n * Extract the project manifest to inline into the registry. The caller owns the\n * studio-vs-app split (manifest formats are CLI-domain); registration re-derives\n * the interface set alongside it.\n */\n extractManifest: (params: {\n configPath: string\n workDir: string\n }) => Promise<DevServerManifest['manifest']>\n isApp: boolean\n output: Output\n server: ViteDevServer\n workDir: string\n\n /**\n * Rebuild the app's federation remote when its interface set changes, awaited\n * *before* the registry patch — the patch reloads the workbench page, which must\n * re-fetch a remote that already exposes the new interface. Resolves with the\n * recreated server so the entry gets its actual address (non-strict ports may\n * shift it); must reject if the restart produces no server, so the set stays\n * uncommitted and the next save retries instead of advertising a dead port.\n */\n onInterfaceSetChange?: () => Promise<ViteDevServer>\n}\n\ninterface DevServerRegistrationHandle {\n close: () => Promise<void>\n}\n\n/**\n * Log any config validation errors without aborting. Unlike build and deploy,\n * dev stays up on an invalid config so the author sees the errors and fixes them\n * live on the next save.\n */\nfunction reportConfigErrors(app: CliConfig['app'], output: Output): void {\n const errors = validateWorkbenchApp(app)\n if (errors.length === 0) return\n // `output.error` exits the process; `warn` keeps the dev server alive.\n output.warn(formatWorkbenchAppErrors(errors))\n}\n\n/** The address the server actually bound — the live socket, which can differ from the configured port under non-strict ports. */\nfunction serverAddress(server: ViteDevServer) {\n const resolvedHost = server.config.server.host\n const addr = server.httpServer?.address()\n return {\n host: typeof resolvedHost === 'string' ? resolvedHost : 'localhost',\n port: typeof addr === 'object' && addr ? addr.port : server.config.server.port,\n }\n}\n\n/**\n * Register the dev server in the registry and watch its config for manifest +\n * interface changes. The workbench reads the entry to locate and render the\n * server; the watcher keeps it current as `sanity.cli.ts` is edited.\n */\nexport async function startDevServerRegistration(\n options: DevServerRegistrationOptions,\n): Promise<DevServerRegistrationHandle> {\n const {cliConfig, extractManifest, isApp, onInterfaceSetChange, output, server, workDir} = options\n\n const {host: appHost, port: appPort} = serverAddress(server)\n\n reportConfigErrors(cliConfig.app, output)\n\n // Forwarded alongside (not inside) the manifest so the workbench renders local\n // panels/workers and reads the configs without a deploy.\n const interfaces = deriveInterfaces(cliConfig.app, {isApp})\n const configs = await deriveConfigs(cliConfig.app)\n\n const registration = registerDevServer({\n configs,\n host: appHost,\n // Keyed by where it's served (not the deployment id), so a running app can't\n // collide with its deployed twin — on the configured port, not the bound one,\n // to match `__SANITY_APP_ID__`, compiled before any non-strict shift.\n id: resolveAppId({host: appHost, port: server.config.server.port ?? appPort}),\n interfaces,\n port: appPort,\n projectId: cliConfig?.api?.projectId,\n type: isApp ? 'coreApp' : 'studio',\n workDir,\n })\n\n const exposesSet = trackExposesSet({configs, interfaces})\n\n const watcher = await startDevManifestWatcher({\n // Re-derive every pass (don't omit): the registry patch is a shallow merge,\n // so omitting would wipe the registered set.\n extract: async (params) => {\n const app = (await getCliConfigUncached(params.workDir)).app\n reportConfigErrors(app, output)\n return {\n configs: await deriveConfigs(app),\n interfaces: deriveInterfaces(app, {isApp}),\n manifest: await extractManifest(params),\n }\n },\n // A studio's root resolves to `sanity.config.*` but its interfaces live in\n // `sanity.cli.*` — watch that too. Apps already root at `sanity.cli.*`.\n extraWatchFilenames: isApp ? undefined : ['sanity.cli.js', 'sanity.cli.ts'],\n output,\n update: async (patch) => {\n if (\n !exposesSet.changed({\n configs: patch.configs,\n interfaces: patch.interfaces,\n })\n ) {\n registration.update(patch)\n return\n }\n // Rebuild the remote *before* patching the registry — the patch reloads the\n // page, which must re-fetch a remote that already exposes the new interface.\n const rebuiltServer = await onInterfaceSetChange?.()\n // Commit only after a successful rebuild, so a thrown one retries next pass.\n exposesSet.commit({\n configs: patch.configs,\n interfaces: patch.interfaces,\n })\n // The recreated server can bind a different port (non-strict ports).\n registration.update(rebuiltServer ? {...patch, ...serverAddress(rebuiltServer)} : patch)\n },\n workDir,\n })\n\n return {\n close: async () => {\n registration.release()\n await watcher.close()\n },\n }\n}\n"],"names":["getCliConfigUncached","resolveAppId","formatWorkbenchAppErrors","validateWorkbenchApp","deriveConfigs","deriveInterfaces","trackExposesSet","registerDevServer","startDevManifestWatcher","reportConfigErrors","app","output","errors","length","warn","serverAddress","server","resolvedHost","config","host","addr","httpServer","address","port","startDevServerRegistration","options","cliConfig","extractManifest","isApp","onInterfaceSetChange","workDir","appHost","appPort","interfaces","configs","registration","id","projectId","api","type","exposesSet","watcher","extract","params","manifest","extraWatchFilenames","undefined","update","patch","changed","rebuiltServer","commit","close","release"],"mappings":"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"}
|
|
@@ -54,12 +54,14 @@ import { serveBuiltApplication } from './serveBuiltApplication.js';
|
|
|
54
54
|
// Read the id the build inlined so start matches it even for a deploy build
|
|
55
55
|
// (which carries the API id, not the shape hash); fall back for older builds.
|
|
56
56
|
const inlinedId = await readInlinedAppId(outDir);
|
|
57
|
+
const configs = await deriveConfigs(cliConfig.app);
|
|
58
|
+
// `start` serves a build, so it advertises the build's inlined id (matching
|
|
59
|
+
// the bundle's `__SANITY_APP_ID__`), not the dev host-port.
|
|
60
|
+
const id = workbench ? inlinedId ?? await buildAppId(workbench) : `${remote.host}-${remote.port}`;
|
|
57
61
|
const registration = registerDevServer({
|
|
58
|
-
configs
|
|
62
|
+
configs,
|
|
59
63
|
host: remote.host,
|
|
60
|
-
|
|
61
|
-
// the bundle's `__SANITY_APP_ID__`), not the dev host-port.
|
|
62
|
-
id: workbench ? inlinedId ?? buildAppId(workbench) : `${remote.host}-${remote.port}`,
|
|
64
|
+
id,
|
|
63
65
|
interfaces: deriveInterfaces(cliConfig.app, {
|
|
64
66
|
isApp
|
|
65
67
|
}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/preview/startWorkbenchPreview.ts"],"sourcesContent":["import {readFile} from 'node:fs/promises'\nimport path from 'node:path'\nimport {styleText} from 'node:util'\n\nimport {type CliConfig, findProjectRoot, type Output} from '@sanity/cli-core'\n\nimport {buildAppId, SANITY_APP_ID_FILE} from '../../appId.js'\nimport {resolveWorkbenchApp} from '../../resolveWorkbenchApp.js'\nimport {createServerLifecycle, toDisplayHost} from '../../util/serverOrchestration.js'\nimport {deriveConfigs, deriveInterfaces} from '../dev/deriveInterfaces.js'\nimport {type DevServerManifest, registerDevServer} from '../dev/registry.js'\nimport {startWorkbenchDevServer} from '../dev/startWorkbenchDevServer.js'\nimport {serveBuiltApplication} from './serveBuiltApplication.js'\n\nexport interface StartWorkbenchPreviewOptions {\n /** Directory for the workbench Vite server's dependency cache. */\n cacheDir: string\n /** CLI-domain `app.id`/`deployment.appId` deprecation check, run before registering. */\n checkForDeprecatedAppId: () => void\n cliConfig: CliConfig\n /** Extract the project manifest to inline into the registry (studio-vs-app handled by the CLI). */\n extractManifest: (params: {\n configPath: string\n workDir: string\n }) => Promise<DevServerManifest['manifest']>\n httpHost: string\n httpPort: number\n isApp: boolean\n /** The built `dist` directory to serve as the federation remote. */\n outDir: string\n output: Output\n reactStrictMode: boolean\n workDir: string\n}\n\n/**\n * `sanity start` for a workbench app: serve a production build the way dev serves\n * a live one. The same singleton workbench shell renders it and the same registry\n * advertises it — only the remote differs, static files from the build output\n * instead of a live Vite dev server. There's no config watcher or rebuild: a\n * build is fixed, so nothing re-syncs.\n *\n * A running workbench claims the configured port, so the built remote binds the\n * next one. Without one the remote takes the configured port and announces its\n * own URL.\n */\nexport async function startWorkbenchPreview(\n options: StartWorkbenchPreviewOptions,\n): Promise<{close: () => Promise<void>}> {\n const {\n cacheDir,\n checkForDeprecatedAppId,\n cliConfig,\n extractManifest,\n httpHost,\n httpPort,\n isApp,\n outDir,\n output,\n reactStrictMode,\n workDir,\n } = options\n\n const {close, closers, installSignalHandlers} = createServerLifecycle()\n\n const workbench = await startWorkbenchDevServer({\n cacheDir,\n cliConfig,\n httpHost,\n httpPort,\n mode: 'preview',\n output,\n reactStrictMode,\n workDir,\n })\n closers.push(workbench.close)\n\n const remotePort = workbench.workbenchAvailable ? workbench.workbenchPort + 1 : httpPort\n\n const remote = await serveBuiltApplication({\n cacheDir,\n httpHost,\n httpPort: remotePort,\n outDir,\n workDir,\n }).catch(async (err) => {\n await close()\n throw err\n })\n closers.push(remote.close)\n\n try {\n // Callers provide CLI-only validation and manifest extraction to keep them\n // out of workbench-cli.\n checkForDeprecatedAppId()\n const configPath = (await findProjectRoot(workDir)).path\n const workbench = resolveWorkbenchApp(cliConfig)\n // Read the id the build inlined so start matches it even for a deploy build\n // (which carries the API id, not the shape hash); fall back for older builds.\n const inlinedId = await readInlinedAppId(outDir)\n const
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/preview/startWorkbenchPreview.ts"],"sourcesContent":["import {readFile} from 'node:fs/promises'\nimport path from 'node:path'\nimport {styleText} from 'node:util'\n\nimport {type CliConfig, findProjectRoot, type Output} from '@sanity/cli-core'\n\nimport {buildAppId, SANITY_APP_ID_FILE} from '../../appId.js'\nimport {resolveWorkbenchApp} from '../../resolveWorkbenchApp.js'\nimport {createServerLifecycle, toDisplayHost} from '../../util/serverOrchestration.js'\nimport {deriveConfigs, deriveInterfaces} from '../dev/deriveInterfaces.js'\nimport {type DevServerManifest, registerDevServer} from '../dev/registry.js'\nimport {startWorkbenchDevServer} from '../dev/startWorkbenchDevServer.js'\nimport {serveBuiltApplication} from './serveBuiltApplication.js'\n\nexport interface StartWorkbenchPreviewOptions {\n /** Directory for the workbench Vite server's dependency cache. */\n cacheDir: string\n /** CLI-domain `app.id`/`deployment.appId` deprecation check, run before registering. */\n checkForDeprecatedAppId: () => void\n cliConfig: CliConfig\n /** Extract the project manifest to inline into the registry (studio-vs-app handled by the CLI). */\n extractManifest: (params: {\n configPath: string\n workDir: string\n }) => Promise<DevServerManifest['manifest']>\n httpHost: string\n httpPort: number\n isApp: boolean\n /** The built `dist` directory to serve as the federation remote. */\n outDir: string\n output: Output\n reactStrictMode: boolean\n workDir: string\n}\n\n/**\n * `sanity start` for a workbench app: serve a production build the way dev serves\n * a live one. The same singleton workbench shell renders it and the same registry\n * advertises it — only the remote differs, static files from the build output\n * instead of a live Vite dev server. There's no config watcher or rebuild: a\n * build is fixed, so nothing re-syncs.\n *\n * A running workbench claims the configured port, so the built remote binds the\n * next one. Without one the remote takes the configured port and announces its\n * own URL.\n */\nexport async function startWorkbenchPreview(\n options: StartWorkbenchPreviewOptions,\n): Promise<{close: () => Promise<void>}> {\n const {\n cacheDir,\n checkForDeprecatedAppId,\n cliConfig,\n extractManifest,\n httpHost,\n httpPort,\n isApp,\n outDir,\n output,\n reactStrictMode,\n workDir,\n } = options\n\n const {close, closers, installSignalHandlers} = createServerLifecycle()\n\n const workbench = await startWorkbenchDevServer({\n cacheDir,\n cliConfig,\n httpHost,\n httpPort,\n mode: 'preview',\n output,\n reactStrictMode,\n workDir,\n })\n closers.push(workbench.close)\n\n const remotePort = workbench.workbenchAvailable ? workbench.workbenchPort + 1 : httpPort\n\n const remote = await serveBuiltApplication({\n cacheDir,\n httpHost,\n httpPort: remotePort,\n outDir,\n workDir,\n }).catch(async (err) => {\n await close()\n throw err\n })\n closers.push(remote.close)\n\n try {\n // Callers provide CLI-only validation and manifest extraction to keep them\n // out of workbench-cli.\n checkForDeprecatedAppId()\n const configPath = (await findProjectRoot(workDir)).path\n const workbench = resolveWorkbenchApp(cliConfig)\n // Read the id the build inlined so start matches it even for a deploy build\n // (which carries the API id, not the shape hash); fall back for older builds.\n const inlinedId = await readInlinedAppId(outDir)\n const configs = await deriveConfigs(cliConfig.app)\n // `start` serves a build, so it advertises the build's inlined id (matching\n // the bundle's `__SANITY_APP_ID__`), not the dev host-port.\n const id = workbench\n ? (inlinedId ?? (await buildAppId(workbench)))\n : `${remote.host}-${remote.port}`\n const registration = registerDevServer({\n configs,\n host: remote.host,\n id,\n interfaces: deriveInterfaces(cliConfig.app, {isApp}),\n manifest: await extractManifest({configPath, workDir}),\n manifestUpdatedAt: new Date().toISOString(),\n port: remote.port,\n projectId: cliConfig?.api?.projectId,\n type: isApp ? 'coreApp' : 'studio',\n workDir,\n })\n closers.push(async () => registration.release())\n } catch (err) {\n await close()\n throw err\n }\n\n if (workbench.workbenchAvailable) {\n const workbenchUrl = `http://${toDisplayHost(workbench.httpHost)}:${workbench.workbenchPort}`\n output.log(\n `Workbench preview server started at ${styleText(['blue', 'underline'], workbenchUrl)} (serving build on port ${remote.port})`,\n )\n } else {\n const remoteUrl = `http://${toDisplayHost(remote.host)}:${remote.port}`\n output.log(`Serving build at ${styleText(['blue', 'underline'], remoteUrl)}`)\n }\n\n installSignalHandlers()\n\n return {close}\n}\n\n/** The id the build inlined into its bundle, or undefined when absent. */\nasync function readInlinedAppId(outDir: string): Promise<string | undefined> {\n try {\n return (await readFile(path.join(outDir, SANITY_APP_ID_FILE), 'utf8')).trim() || undefined\n } catch {\n return undefined\n }\n}\n"],"names":["readFile","path","styleText","findProjectRoot","buildAppId","SANITY_APP_ID_FILE","resolveWorkbenchApp","createServerLifecycle","toDisplayHost","deriveConfigs","deriveInterfaces","registerDevServer","startWorkbenchDevServer","serveBuiltApplication","startWorkbenchPreview","options","cacheDir","checkForDeprecatedAppId","cliConfig","extractManifest","httpHost","httpPort","isApp","outDir","output","reactStrictMode","workDir","close","closers","installSignalHandlers","workbench","mode","push","remotePort","workbenchAvailable","workbenchPort","remote","catch","err","configPath","inlinedId","readInlinedAppId","configs","app","id","host","port","registration","interfaces","manifest","manifestUpdatedAt","Date","toISOString","projectId","api","type","release","workbenchUrl","log","remoteUrl","join","trim","undefined"],"mappings":"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"}
|
package/dist/appId.js
CHANGED
|
@@ -1,52 +1,39 @@
|
|
|
1
|
-
import { hash } from 'node:crypto';
|
|
2
1
|
/**
|
|
3
2
|
* File the build writes into its output, carrying the id compiled into the
|
|
4
3
|
* bundle. `sanity start` serves a build without recompiling, so it reads this
|
|
5
4
|
* instead of recomputing — a deploy inlines the API id, not the shape hash.
|
|
6
5
|
*/ export const SANITY_APP_ID_FILE = 'sanity-app-id.txt';
|
|
7
6
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* of the declared shape. `sanity deploy` resolves its own id from the
|
|
13
|
-
* applications API, so it isn't handled here.
|
|
7
|
+
* The dev id for a workbench app — the address the server bound. `sanity dev`
|
|
8
|
+
* keys on where the app is served so a running app can't collide with its
|
|
9
|
+
* deployed twin. Sync and dependency-free: it's re-exported from the package's
|
|
10
|
+
* browser-facing entry, so it must not pull in `node:crypto`.
|
|
14
11
|
*/ export function resolveAppId(source) {
|
|
15
|
-
if ('app' in source) {
|
|
16
|
-
const { app } = source;
|
|
17
|
-
const canonical = (interfaces)=>(interfaces ?? []).map((i)=>[
|
|
18
|
-
i.type,
|
|
19
|
-
i.name,
|
|
20
|
-
i.src
|
|
21
|
-
]).toSorted();
|
|
22
|
-
return hash('sha1', JSON.stringify({
|
|
23
|
-
config: app.exposes?.config ?? null,
|
|
24
|
-
entry: app.entry ?? null,
|
|
25
|
-
name: app.name,
|
|
26
|
-
organizationId: app.organizationId,
|
|
27
|
-
services: canonical(app.exposes?.services),
|
|
28
|
-
views: canonical(app.exposes?.views)
|
|
29
|
-
}), 'hex');
|
|
30
|
-
}
|
|
31
12
|
return `${source.host}-${source.port}`;
|
|
32
13
|
}
|
|
33
14
|
/**
|
|
34
|
-
* The `build`/`start` id
|
|
35
|
-
*
|
|
36
|
-
* by `sanity start` resolve to the same id.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
15
|
+
* The `build`/`start` id — a hash of the app's declared shape (its identity, not
|
|
16
|
+
* its code), so the bundle inlined by `sanity build` and the registry entry
|
|
17
|
+
* advertised by `sanity start` resolve to the same id. Hashed with the Web Crypto
|
|
18
|
+
* API rather than `node:crypto` for parity with `resolveAppId`'s browser-safe
|
|
19
|
+
* home. `sanity deploy` resolves its own id from the applications API.
|
|
20
|
+
*/ export async function buildAppId(app) {
|
|
21
|
+
const canonical = (interfaces)=>(interfaces ?? []).map((i)=>[
|
|
22
|
+
i.type,
|
|
23
|
+
i.name,
|
|
24
|
+
i.src
|
|
25
|
+
]).toSorted();
|
|
26
|
+
const shape = JSON.stringify({
|
|
27
|
+
config: app.config ?? null,
|
|
28
|
+
entry: app.entry ?? null,
|
|
29
|
+
name: app.name,
|
|
30
|
+
organizationId: app.organizationId,
|
|
31
|
+
services: canonical(app.services),
|
|
32
|
+
views: canonical(app.views)
|
|
49
33
|
});
|
|
34
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins -- the Web Crypto global is available on our Node target and in the browser
|
|
35
|
+
const digest = await globalThis.crypto.subtle.digest('SHA-256', new TextEncoder().encode(shape));
|
|
36
|
+
return Array.from(new Uint8Array(digest), (byte)=>byte.toString(16).padStart(2, '0')).join('');
|
|
50
37
|
}
|
|
51
38
|
|
|
52
39
|
//# sourceMappingURL=appId.js.map
|
package/dist/appId.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/appId.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"sources":["../src/appId.ts"],"sourcesContent":["import {type ResolvedWorkbenchApp} from './resolveWorkbenchApp.js'\n\n/**\n * File the build writes into its output, carrying the id compiled into the\n * bundle. `sanity start` serves a build without recompiling, so it reads this\n * instead of recomputing — a deploy inlines the API id, not the shape hash.\n */\nexport const SANITY_APP_ID_FILE = 'sanity-app-id.txt'\n\n/**\n * The dev id for a workbench app — the address the server bound. `sanity dev`\n * keys on where the app is served so a running app can't collide with its\n * deployed twin. Sync and dependency-free: it's re-exported from the package's\n * browser-facing entry, so it must not pull in `node:crypto`.\n */\nexport function resolveAppId(source: {host: string; port: number}): string {\n return `${source.host}-${source.port}`\n}\n\n/**\n * The `build`/`start` id — a hash of the app's declared shape (its identity, not\n * its code), so the bundle inlined by `sanity build` and the registry entry\n * advertised by `sanity start` resolve to the same id. Hashed with the Web Crypto\n * API rather than `node:crypto` for parity with `resolveAppId`'s browser-safe\n * home. `sanity deploy` resolves its own id from the applications API.\n */\nexport async function buildAppId(app: ResolvedWorkbenchApp): Promise<string> {\n const canonical = (\n interfaces: ReadonlyArray<{name: string; src: string; type: string}> | undefined,\n ): Array<[string, string, string]> =>\n (interfaces ?? []).map((i): [string, string, string] => [i.type, i.name, i.src]).toSorted()\n const shape = JSON.stringify({\n config: app.config ?? null,\n entry: app.entry ?? null,\n name: app.name,\n organizationId: app.organizationId,\n services: canonical(app.services),\n views: canonical(app.views),\n })\n // eslint-disable-next-line n/no-unsupported-features/node-builtins -- the Web Crypto global is available on our Node target and in the browser\n const digest = await globalThis.crypto.subtle.digest('SHA-256', new TextEncoder().encode(shape))\n return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, '0')).join('')\n}\n"],"names":["SANITY_APP_ID_FILE","resolveAppId","source","host","port","buildAppId","app","canonical","interfaces","map","i","type","name","src","toSorted","shape","JSON","stringify","config","entry","organizationId","services","views","digest","globalThis","crypto","subtle","TextEncoder","encode","Array","from","Uint8Array","byte","toString","padStart","join"],"mappings":"AAEA;;;;CAIC,GACD,OAAO,MAAMA,qBAAqB,oBAAmB;AAErD;;;;;CAKC,GACD,OAAO,SAASC,aAAaC,MAAoC;IAC/D,OAAO,GAAGA,OAAOC,IAAI,CAAC,CAAC,EAAED,OAAOE,IAAI,EAAE;AACxC;AAEA;;;;;;CAMC,GACD,OAAO,eAAeC,WAAWC,GAAyB;IACxD,MAAMC,YAAY,CAChBC,aAEA,AAACA,CAAAA,cAAc,EAAE,AAAD,EAAGC,GAAG,CAAC,CAACC,IAAgC;gBAACA,EAAEC,IAAI;gBAAED,EAAEE,IAAI;gBAAEF,EAAEG,GAAG;aAAC,EAAEC,QAAQ;IAC3F,MAAMC,QAAQC,KAAKC,SAAS,CAAC;QAC3BC,QAAQZ,IAAIY,MAAM,IAAI;QACtBC,OAAOb,IAAIa,KAAK,IAAI;QACpBP,MAAMN,IAAIM,IAAI;QACdQ,gBAAgBd,IAAIc,cAAc;QAClCC,UAAUd,UAAUD,IAAIe,QAAQ;QAChCC,OAAOf,UAAUD,IAAIgB,KAAK;IAC5B;IACA,+IAA+I;IAC/I,MAAMC,SAAS,MAAMC,WAAWC,MAAM,CAACC,MAAM,CAACH,MAAM,CAAC,WAAW,IAAII,cAAcC,MAAM,CAACb;IACzF,OAAOc,MAAMC,IAAI,CAAC,IAAIC,WAAWR,SAAS,CAACS,OAASA,KAAKC,QAAQ,CAAC,IAAIC,QAAQ,CAAC,GAAG,MAAMC,IAAI,CAAC;AAC/F"}
|
package/dist/defineApp.js
CHANGED
|
@@ -26,9 +26,7 @@ import { ConfigSchema, InterfaceDeclarationSchema, ServiceDeclarationSchema } fr
|
|
|
26
26
|
'dock.user'
|
|
27
27
|
]);
|
|
28
28
|
/**
|
|
29
|
-
* Runtime-validation schema for `unstable_defineApp`.
|
|
30
|
-
* including the internal `applicationType`; the user-facing `DefineAppInput`
|
|
31
|
-
* type below omits that field.
|
|
29
|
+
* Runtime-validation schema for `unstable_defineApp`.
|
|
32
30
|
* @internal
|
|
33
31
|
*/ export const DefineAppInputSchema = z.object({
|
|
34
32
|
/**
|
|
@@ -46,7 +44,7 @@ import { ConfigSchema, InterfaceDeclarationSchema, ServiceDeclarationSchema } fr
|
|
|
46
44
|
* App entrypoint module. Defaults to `./src/App.tsx` when omitted. The build
|
|
47
45
|
* derives the app's navigable `app` view from it. SDK apps only — setting it
|
|
48
46
|
* on a studio is rejected (studio app views are not yet implemented).
|
|
49
|
-
*/ entry: z.optional(z.string()),
|
|
47
|
+
*/ entry: z.optional(z.string("must be a path to the app's entry file")),
|
|
50
48
|
/** Dock group to render in. Defaults to `dock.applications` when omitted. */ group: z.optional(DockGroupSchema),
|
|
51
49
|
/** Optional icon override (path to an SVG). Wins over manifest/studio icon. */ icon: z.optional(z.string()),
|
|
52
50
|
/**
|
|
@@ -56,19 +54,10 @@ import { ConfigSchema, InterfaceDeclarationSchema, ServiceDeclarationSchema } fr
|
|
|
56
54
|
/** Unique app identifier — must match `APP_NAME_PATTERN`. */ name: z.string().check(z.regex(APP_NAME_PATTERN, 'App `name` must match /^[a-zA-Z0-9_-]+$/')),
|
|
57
55
|
/** Organization that owns the app — the workbench runs and deploys against it. */ organizationId: z.string("App `organizationId` is required — pass the owning organization's ID to `unstable_defineApp`"),
|
|
58
56
|
/** Sort position within the group, ascending. Defaults to `100` when omitted. */ priority: z.optional(z.number()),
|
|
59
|
-
/**
|
|
60
|
-
* Background services the app runs (e.g. a `worker` emitting dock badges).
|
|
61
|
-
* Metadata only — built into worker artifacts and persisted to the
|
|
62
|
-
* application service on deploy, not into the app manifest. Service `name`s
|
|
63
|
-
* must be unique within the app.
|
|
64
|
-
*/ services: z.optional(z.array(ServiceDeclarationSchema).check(z.refine((services)=>new Set(services.map((service)=>service.name)).size === services.length, 'Service `name` must be unique within an app'))),
|
|
57
|
+
/** Background services the app runs (e.g. a `worker` emitting dock badges). */ services: z.optional(z.array(ServiceDeclarationSchema, 'must be an array of services').check(z.refine((services)=>new Set(services.map((service)=>service.name)).size === services.length, 'Service `name` must be unique within an app'))),
|
|
65
58
|
slug: z.string('App `slug` is required — the hostname the application is created at on deploy'),
|
|
66
59
|
/** User-facing app title. Wins over studio.config.ts title on merge. */ title: z.string(),
|
|
67
|
-
/**
|
|
68
|
-
* Views the app exposes (e.g. dock panels). Metadata only — built into
|
|
69
|
-
* render artifacts and persisted to the application service on deploy, not
|
|
70
|
-
* into the app manifest. View `name`s must be unique within the app.
|
|
71
|
-
*/ 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'))),
|
|
60
|
+
/** Views the app exposes (e.g. dock panels). */ views: z.optional(z.array(InterfaceDeclarationSchema, 'must be an array of panels').check(z.refine((views)=>new Set(views.map((view)=>view.name)).size === views.length, 'View `name` must be unique within an app'))),
|
|
72
61
|
/** Dashboard visibility of the app. Defaults to `default` when omitted. */ visibility: z.optional(z.enum(APP_VISIBILITIES))
|
|
73
62
|
}).check(// Studio app views are not implemented yet. A studio that declares `entry`
|
|
74
63
|
// (the SDK app-view entrypoint) is rejected here rather than silently
|
|
@@ -86,7 +75,8 @@ z.refine((input)=>!(input.config && !input.isSingleton), {
|
|
|
86
75
|
path: [
|
|
87
76
|
'config'
|
|
88
77
|
]
|
|
89
|
-
}))
|
|
78
|
+
})).check(// An app exposes one interface kind: an app view (`entry`) or panels.
|
|
79
|
+
z.refine((input)=>!(input.entry !== undefined && (input.views?.length ?? 0) > 0), 'An app cannot expose both an app view (`entry`) and panel views. Declare one or the other.')).check(z.refine((input)=>(input.views?.length ?? 0) <= 1, 'An app can expose at most one panel view.'));
|
|
90
80
|
/**
|
|
91
81
|
* Nominal brand the CLI discriminates on to enable the workbench build/deploy
|
|
92
82
|
* codepath. Registered via `Symbol.for` so the marker survives module-realm
|
package/dist/defineApp.js.map
CHANGED
|
@@ -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 * 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 slug: z.string('App `slug` is required — the hostname the application is created at on deploy'),\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 slug: 'media-library',\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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"}
|
|
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`.\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(\"must be a path to the app's entry file\")),\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 /** Background services the app runs (e.g. a `worker` emitting dock badges). */\n services: z.optional(\n z\n .array(ServiceDeclarationSchema, 'must be an array of services')\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 slug: z.string('App `slug` is required — the hostname the application is created at on deploy'),\n /** User-facing app title. Wins over studio.config.ts title on merge. */\n title: z.string(),\n /** Views the app exposes (e.g. dock panels). */\n views: z.optional(\n z\n .array(InterfaceDeclarationSchema, 'must be an array of panels')\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 .check(\n // An app exposes one interface kind: an app view (`entry`) or panels.\n z.refine(\n (input) => !(input.entry !== undefined && (input.views?.length ?? 0) > 0),\n 'An app cannot expose both an app view (`entry`) and panel views. Declare one or the other.',\n ),\n )\n .check(\n z.refine(\n (input) => (input.views?.length ?? 0) <= 1,\n 'An app can expose at most one panel view.',\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`). A union so an app declares an app `entry` or `views`,\n * never both.\n * @public\n */\nexport type DefineAppInput = Omit<\n z.output<typeof DefineAppInputSchema>,\n 'applicationType' | 'config' | 'entry' | 'isSingleton' | 'views'\n> &\n (\n | {entry?: never; views?: NonNullable<z.output<typeof DefineAppInputSchema>['views']>}\n | {entry?: string; views?: never}\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 type DefineAppResult = DefineAppInput & {readonly [WORKBENCH_APP]: true}\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 slug: 'media-library',\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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"}
|
|
@@ -4,12 +4,15 @@
|
|
|
4
4
|
// build their command-specific view on top of this one brand-check +
|
|
5
5
|
// extraction, so the discrimination lives in exactly one place.
|
|
6
6
|
import { isWorkbenchApp, readConfig } from './defineApp.js';
|
|
7
|
+
import { formatWorkbenchAppErrors, validateWorkbenchApp } from './validateWorkbenchApp.js';
|
|
7
8
|
/**
|
|
8
9
|
* Resolve the workbench app for a CLI config, or `null` for a plain project.
|
|
9
10
|
* @public
|
|
10
11
|
*/ export function resolveWorkbenchApp(cliConfig) {
|
|
11
12
|
const app = cliConfig?.app;
|
|
12
13
|
if (!isWorkbenchApp(app)) return null;
|
|
14
|
+
const errors = validateWorkbenchApp(app);
|
|
15
|
+
if (errors.length > 0) throw new Error(formatWorkbenchAppErrors(errors));
|
|
13
16
|
return {
|
|
14
17
|
applicationType: app.applicationType,
|
|
15
18
|
config: readConfig(app),
|
|
@@ -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 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 /** Organization that owns the app — part of its build-id identity. */\n readonly organizationId: string\n /** Background worker services the app declares. */\n readonly services: NonNullable<DefineAppInput['services']>\n\n /** Hostname the application is created at on first deploy. */\n readonly slug: string\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 /** Path to the app's icon SVG, resolved and shipped to Brett on deploy. */\n readonly icon?: string\n /** Explicit singleton flag (a Sanity-owned app); `undefined` when the app doesn't set it. */\n readonly isSingleton?: boolean\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 icon: app.icon,\n isSingleton: app.isSingleton,\n name: app.name,\n organizationId: app.organizationId,\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","icon","isSingleton","name","organizationId","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;
|
|
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'\nimport {formatWorkbenchAppErrors, validateWorkbenchApp} from './validateWorkbenchApp.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 /** Organization that owns the app — part of its build-id identity. */\n readonly organizationId: string\n /** Background worker services the app declares. */\n readonly services: NonNullable<DefineAppInput['services']>\n\n /** Hostname the application is created at on first deploy. */\n readonly slug: string\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 /** Path to the app's icon SVG, resolved and shipped to Brett on deploy. */\n readonly icon?: string\n /** Explicit singleton flag (a Sanity-owned app); `undefined` when the app doesn't set it. */\n readonly isSingleton?: boolean\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 const errors = validateWorkbenchApp(app)\n if (errors.length > 0) throw new Error(formatWorkbenchAppErrors(errors))\n\n return {\n applicationType: app.applicationType,\n config: readConfig(app),\n entry: app.entry,\n icon: app.icon,\n isSingleton: app.isSingleton,\n name: app.name,\n organizationId: app.organizationId,\n services: app.services ?? [],\n slug: app.slug,\n views: app.views ?? [],\n visibility: app.visibility,\n }\n}\n"],"names":["isWorkbenchApp","readConfig","formatWorkbenchAppErrors","validateWorkbenchApp","resolveWorkbenchApp","cliConfig","app","errors","length","Error","applicationType","config","entry","icon","isSingleton","name","organizationId","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;AACjG,SAAQC,wBAAwB,EAAEC,oBAAoB,QAAO,4BAA2B;AA0CxF;;;CAGC,GACD,OAAO,SAASC,oBACdC,SAAuC;IAEvC,MAAMC,MAAMD,WAAWC;IACvB,IAAI,CAACN,eAAeM,MAAM,OAAO;IAEjC,MAAMC,SAASJ,qBAAqBG;IACpC,IAAIC,OAAOC,MAAM,GAAG,GAAG,MAAM,IAAIC,MAAMP,yBAAyBK;IAEhE,OAAO;QACLG,iBAAiBJ,IAAII,eAAe;QACpCC,QAAQV,WAAWK;QACnBM,OAAON,IAAIM,KAAK;QAChBC,MAAMP,IAAIO,IAAI;QACdC,aAAaR,IAAIQ,WAAW;QAC5BC,MAAMT,IAAIS,IAAI;QACdC,gBAAgBV,IAAIU,cAAc;QAClCC,UAAUX,IAAIW,QAAQ,IAAI,EAAE;QAC5BC,MAAMZ,IAAIY,IAAI;QACdC,OAAOb,IAAIa,KAAK,IAAI,EAAE;QACtBC,YAAYd,IAAIc,UAAU;IAC5B;AACF"}
|
|
@@ -27,6 +27,17 @@ export async function getApplication(applicationId) {
|
|
|
27
27
|
throw err;
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
|
+
/** Every application in an organization, in one page (`limit=none`). */ export async function listApplications(organizationId) {
|
|
31
|
+
const client = await getClient();
|
|
32
|
+
const { data } = await client.request({
|
|
33
|
+
query: {
|
|
34
|
+
limit: 'none',
|
|
35
|
+
organizationId
|
|
36
|
+
},
|
|
37
|
+
uri: '/applications'
|
|
38
|
+
});
|
|
39
|
+
return data;
|
|
40
|
+
}
|
|
30
41
|
/**
|
|
31
42
|
* Create an application record (no deployment), so the CLI can build with the
|
|
32
43
|
* returned id, then ship it via {@link createDeployment}.
|
|
@@ -1 +1 @@
|
|
|
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 /** Lexicon schema descriptor id; Brett requires one per workspace. */\n schemaDescriptorId: 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/**\n * Create an application record (no deployment), so the CLI can build with the\n * returned id, then ship it via {@link createDeployment}.\n */\nexport async function createApplication(options: {\n isSingleton?: boolean\n organizationId: string\n projectId?: string\n slug: string\n title: string\n type: ApplicationType\n visibility?: AppVisibility\n}): Promise<Application> {\n const {isSingleton, organizationId, projectId, slug, title, type, visibility} = options\n const client = await getClient()\n return client.request({\n body: {\n organizationId,\n slug,\n title,\n type,\n ...(isSingleton === undefined ? {} : {isSingleton}),\n ...(visibility ? {visibility} : {}),\n // Studio config is set once, at create — it's immutable on redeploy.\n ...(projectId ? {config: {studio: {projectId}}} : {}),\n },\n method: 'POST',\n uri: `/applications`,\n })\n}\n\n/** Mutable application fields the deploy flow patches after create. */\nexport interface ApplicationUpdate {\n icon?: string | null\n title?: string\n visibility?: AppVisibility\n}\n\n// Patch an application's mutable fields.\nexport async function updateApplication(\n applicationId: string,\n update: ApplicationUpdate,\n): Promise<void> {\n const client = await getClient()\n await client.request({body: update, method: 'PATCH', uri: `/applications/${applicationId}`})\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","isSingleton","projectId","slug","title","visibility","body","undefined","config","studio","method","updateApplication","update","createDeployment","interfaces","isAutoUpdating","tarball","version","workspaces","formData","append","toString","appendDeploymentParts","deleteApplication","appendJson","length","contentType","filename","name","value","JSON","stringify","pipe","headers","getHeaders"],"mappings":"AAAA,SAAQA,WAAW,QAAO,cAAa;AAGvC,SAA4BC,kBAAkB,QAAO,mBAAkB;AACvE,SAAQC,SAAS,QAAO,wBAAuB;AAC/C,OAAOC,cAAc,YAAW;AAGhC,SAAQC,yBAAyB,QAAO,kBAAiB;AA2CzD,OAAO,SAASC,gBAAgBC,cAAsB;IACpD,OAAO,CAAC,QAAQ,EAAEA,eAAe,CAAC,EAAEJ,cAAc,oBAAoB,cAAc;AACtF;AAEA,4EAA4E,GAC5E,OAAO,SAASK,kBACdC,WAAgE;IAEhE,MAAMC,UAAUD,YAAYE,IAAI,KAAK,WAAW,WAAW;IAC3D,OAAO,GAAGL,gBAAgBG,YAAYF,cAAc,EAAE,CAAC,EAAEG,QAAQ,CAAC,EAAED,YAAYG,EAAE,EAAE;AACtF;AAEA,eAAeC;IACb,OAAOX,mBAAmB;QAACY,YAAYT;QAA2BU,aAAa;IAAI;AACrF;AAEA,OAAO,eAAeC,eAAeC,aAAqB;IACxD,MAAMC,SAAS,MAAML;IACrB,IAAI;QACF,OAAO,MAAMK,OAAOC,OAAO,CAAC;YAACC,KAAK,CAAC,cAAc,EAAEH,eAAe;QAAA;IACpE,EAAE,OAAOI,KAAK;QACZ,IAAI,AAACA,KAA+BC,eAAe,KAAK,OAAO;QAC/D,MAAMD;IACR;AACF;AAEA;;;CAGC,GACD,OAAO,
|
|
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 /** Lexicon schema descriptor id; Brett requires one per workspace. */\n schemaDescriptorId: 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/** Every application in an organization, in one page (`limit=none`). */\nexport async function listApplications(organizationId: string): Promise<Application[]> {\n const client = await getClient()\n const {data}: {data: Application[]} = await client.request({\n query: {limit: 'none', organizationId},\n uri: '/applications',\n })\n return data\n}\n\n/**\n * Create an application record (no deployment), so the CLI can build with the\n * returned id, then ship it via {@link createDeployment}.\n */\nexport async function createApplication(options: {\n isSingleton?: boolean\n organizationId: string\n projectId?: string\n slug: string\n title: string\n type: ApplicationType\n visibility?: AppVisibility\n}): Promise<Application> {\n const {isSingleton, organizationId, projectId, slug, title, type, visibility} = options\n const client = await getClient()\n return client.request({\n body: {\n organizationId,\n slug,\n title,\n type,\n ...(isSingleton === undefined ? {} : {isSingleton}),\n ...(visibility ? {visibility} : {}),\n // Studio config is set once, at create — it's immutable on redeploy.\n ...(projectId ? {config: {studio: {projectId}}} : {}),\n },\n method: 'POST',\n uri: `/applications`,\n })\n}\n\n/** Mutable application fields the deploy flow patches after create. */\nexport interface ApplicationUpdate {\n icon?: string | null\n title?: string\n visibility?: AppVisibility\n}\n\n// Patch an application's mutable fields.\nexport async function updateApplication(\n applicationId: string,\n update: ApplicationUpdate,\n): Promise<void> {\n const client = await getClient()\n await client.request({body: update, method: 'PATCH', uri: `/applications/${applicationId}`})\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","listApplications","data","query","limit","createApplication","options","isSingleton","projectId","slug","title","visibility","body","undefined","config","studio","method","updateApplication","update","createDeployment","interfaces","isAutoUpdating","tarball","version","workspaces","formData","append","toString","appendDeploymentParts","deleteApplication","appendJson","length","contentType","filename","name","value","JSON","stringify","pipe","headers","getHeaders"],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { DefineAppInputSchema } from './defineApp.js';
|
|
2
|
+
/**
|
|
3
|
+
* Collect validation errors for a workbench app.
|
|
4
|
+
* @internal
|
|
5
|
+
*/ export function validateWorkbenchApp(app) {
|
|
6
|
+
const result = DefineAppInputSchema.safeParse(app);
|
|
7
|
+
if (result.success) return [];
|
|
8
|
+
return result.error.issues.map((issue)=>{
|
|
9
|
+
const location = issue.path.length > 0 ? `${issue.path.join('.')}: ` : '';
|
|
10
|
+
return `${location}${issue.message}`;
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Render the errors as one message, so callers report them together.
|
|
15
|
+
* @internal
|
|
16
|
+
*/ export function formatWorkbenchAppErrors(errors) {
|
|
17
|
+
return [
|
|
18
|
+
'Invalid workbench app config:',
|
|
19
|
+
...errors.map((error)=>` - ${error}`)
|
|
20
|
+
].join('\n');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
//# sourceMappingURL=validateWorkbenchApp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/validateWorkbenchApp.ts"],"sourcesContent":["import {DefineAppInputSchema} from './defineApp.js'\n\n/**\n * Collect validation errors for a workbench app.\n * @internal\n */\nexport function validateWorkbenchApp(app: unknown): string[] {\n const result = DefineAppInputSchema.safeParse(app)\n if (result.success) return []\n\n return result.error.issues.map((issue) => {\n const location = issue.path.length > 0 ? `${issue.path.join('.')}: ` : ''\n return `${location}${issue.message}`\n })\n}\n\n/**\n * Render the errors as one message, so callers report them together.\n * @internal\n */\nexport function formatWorkbenchAppErrors(errors: string[]): string {\n return ['Invalid workbench app config:', ...errors.map((error) => ` - ${error}`)].join('\\n')\n}\n"],"names":["DefineAppInputSchema","validateWorkbenchApp","app","result","safeParse","success","error","issues","map","issue","location","path","length","join","message","formatWorkbenchAppErrors","errors"],"mappings":"AAAA,SAAQA,oBAAoB,QAAO,iBAAgB;AAEnD;;;CAGC,GACD,OAAO,SAASC,qBAAqBC,GAAY;IAC/C,MAAMC,SAASH,qBAAqBI,SAAS,CAACF;IAC9C,IAAIC,OAAOE,OAAO,EAAE,OAAO,EAAE;IAE7B,OAAOF,OAAOG,KAAK,CAACC,MAAM,CAACC,GAAG,CAAC,CAACC;QAC9B,MAAMC,WAAWD,MAAME,IAAI,CAACC,MAAM,GAAG,IAAI,GAAGH,MAAME,IAAI,CAACE,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG;QACvE,OAAO,GAAGH,WAAWD,MAAMK,OAAO,EAAE;IACtC;AACF;AAEA;;;CAGC,GACD,OAAO,SAASC,yBAAyBC,MAAgB;IACvD,OAAO;QAAC;WAAoCA,OAAOR,GAAG,CAAC,CAACF,QAAU,CAAC,IAAI,EAAEA,OAAO;KAAE,CAACO,IAAI,CAAC;AAC1F"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/workbench-cli",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.2",
|
|
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",
|
|
@@ -53,13 +53,13 @@
|
|
|
53
53
|
"access": "public"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@module-federation/vite": "1.
|
|
56
|
+
"@module-federation/vite": "1.19.1",
|
|
57
57
|
"@vitejs/plugin-react": "^6.0.3",
|
|
58
58
|
"form-data": "^4.0.5",
|
|
59
59
|
"tar-fs": "^3.1.2",
|
|
60
60
|
"vite": "^8.1.5",
|
|
61
61
|
"zod": "^4.4.3",
|
|
62
|
-
"@sanity/cli-core": "^2.
|
|
62
|
+
"@sanity/cli-core": "^2.6.0"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"@eslint/compat": "^2.1.0",
|
|
@@ -74,8 +74,8 @@
|
|
|
74
74
|
"typescript": "^6.0.3",
|
|
75
75
|
"vitest": "^4.1.10",
|
|
76
76
|
"@repo/package.config": "0.0.1",
|
|
77
|
-
"@
|
|
78
|
-
"@
|
|
77
|
+
"@sanity/eslint-config-cli": "^1.1.3",
|
|
78
|
+
"@repo/tsconfig": "3.70.0"
|
|
79
79
|
},
|
|
80
80
|
"engines": {
|
|
81
81
|
"node": ">=22.12"
|