@sanity/workbench-cli 1.7.0 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_exports/build.d.ts +6 -4
- package/dist/_exports/index.d.ts +8 -35
- package/dist/actions/dev/deriveInterfaces.js +11 -3
- package/dist/actions/dev/deriveInterfaces.js.map +1 -1
- package/dist/actions/dev/startDevServerRegistration.js +2 -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/package.json +1 -1
package/dist/_exports/build.d.ts
CHANGED
|
@@ -4,11 +4,13 @@ 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
|
package/dist/_exports/index.d.ts
CHANGED
|
@@ -1,13 +1,5 @@
|
|
|
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
|
|
@@ -239,23 +231,15 @@ export declare type PanelViewProps = ViewComponentBaseProps<{
|
|
|
239
231
|
}>;
|
|
240
232
|
|
|
241
233
|
/**
|
|
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.
|
|
234
|
+
* The dev id for a workbench app — the address the server bound. `sanity dev`
|
|
235
|
+
* keys on where the app is served so a running app can't collide with its
|
|
236
|
+
* deployed twin. Sync and dependency-free: it's re-exported from the package's
|
|
237
|
+
* browser-facing entry, so it must not pull in `node:crypto`.
|
|
248
238
|
*/
|
|
249
|
-
export declare function resolveAppId(
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
}
|
|
254
|
-
| {
|
|
255
|
-
host: string;
|
|
256
|
-
port: number;
|
|
257
|
-
},
|
|
258
|
-
): string;
|
|
239
|
+
export declare function resolveAppId(source: {
|
|
240
|
+
host: string;
|
|
241
|
+
port: number;
|
|
242
|
+
}): string;
|
|
259
243
|
|
|
260
244
|
/** @internal */
|
|
261
245
|
declare const SERVICE_CONTRACT_VERSION = 1;
|
|
@@ -393,15 +377,4 @@ declare const WORKBENCH_APP: unique symbol;
|
|
|
393
377
|
export declare type WorkbenchApp = DefineAppResult &
|
|
394
378
|
z.output<typeof DefineAppInputSchema>;
|
|
395
379
|
|
|
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
380
|
export {};
|
|
@@ -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
|
/**
|
|
@@ -78,7 +77,7 @@ import { isWorkbenchApp, readConfig } from '../../defineApp.js';
|
|
|
78
77
|
* installation-config id slot deployed apps get from the applications API,
|
|
79
78
|
* and the workbench keys change detection on it. `version` is the config
|
|
80
79
|
* contract version the generated module exports, known before the module runs.
|
|
81
|
-
*/ export function deriveConfigs(app) {
|
|
80
|
+
*/ export async function deriveConfigs(app) {
|
|
82
81
|
if (!isWorkbenchApp(app)) return [];
|
|
83
82
|
const config = readConfig(app);
|
|
84
83
|
if (!config) return [];
|
|
@@ -96,9 +95,18 @@ import { isWorkbenchApp, readConfig } from '../../defineApp.js';
|
|
|
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 the config\n * contract version the generated module exports, known before the module runs.\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: 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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"}
|
|
@@ -24,7 +24,7 @@ import { startDevManifestWatcher } from './startDevManifestWatcher.js';
|
|
|
24
24
|
const interfaces = deriveInterfaces(cliConfig.app, {
|
|
25
25
|
isApp
|
|
26
26
|
});
|
|
27
|
-
const configs = deriveConfigs(cliConfig.app);
|
|
27
|
+
const configs = await deriveConfigs(cliConfig.app);
|
|
28
28
|
const registration = registerDevServer({
|
|
29
29
|
configs,
|
|
30
30
|
host: appHost,
|
|
@@ -51,7 +51,7 @@ import { startDevManifestWatcher } from './startDevManifestWatcher.js';
|
|
|
51
51
|
extract: async (params)=>{
|
|
52
52
|
const app = (await getCliConfigUncached(params.workDir)).app;
|
|
53
53
|
return {
|
|
54
|
-
configs: deriveConfigs(app),
|
|
54
|
+
configs: await deriveConfigs(app),
|
|
55
55
|
interfaces: deriveInterfaces(app, {
|
|
56
56
|
isApp
|
|
57
57
|
}),
|
|
@@ -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","output","workDir","appHost","appPort","interfaces","app","configs","registration","id","projectId","api","type","exposesSet","watcher","extract","params","manifest","extraWatchFilenames","undefined","update","patch","changed","rebuiltServer","commit","close","release"],"mappings":"AAAA,SAAwBA,oBAAoB,QAAoB,mBAAkB;AAGlF,SAAQC,YAAY,QAAO,iBAAgB;AAC3C,SAAQC,aAAa,EAAEC,gBAAgB,QAAO,wBAAuB;AACrE,SAAQC,eAAe,QAAO,oBAAmB;AACjD,SAAgCC,iBAAiB,QAAO,gBAAe;AACvE,SAAQC,uBAAuB,QAAO,+BAA8B;AAiCpE,+HAA+H,GAC/H,SAASC,cAAcC,MAAqB;IAC1C,MAAMC,eAAeD,OAAOE,MAAM,CAACF,MAAM,CAACG,IAAI;IAC9C,MAAMC,OAAOJ,OAAOK,UAAU,EAAEC;IAChC,OAAO;QACLH,MAAM,OAAOF,iBAAiB,WAAWA,eAAe;QACxDM,MAAM,OAAOH,SAAS,YAAYA,OAAOA,KAAKG,IAAI,GAAGP,OAAOE,MAAM,CAACF,MAAM,CAACO,IAAI;IAChF;AACF;AAEA;;;;CAIC,GACD,OAAO,eAAeC,2BACpBC,OAAqC;IAErC,MAAM,EAACC,SAAS,EAAEC,eAAe,EAAEC,KAAK,EAAEC,oBAAoB,EAAEC,MAAM,EAAEd,MAAM,EAAEe,OAAO,EAAC,GAAGN;IAE3F,MAAM,EAACN,MAAMa,OAAO,EAAET,MAAMU,OAAO,EAAC,GAAGlB,cAAcC;IAErD,+EAA+E;IAC/E,yDAAyD;IACzD,MAAMkB,aAAavB,iBAAiBe,UAAUS,GAAG,EAAE;QAACP;IAAK;IACzD,MAAMQ,
|
|
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 = 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 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","deriveConfigs","deriveInterfaces","trackExposesSet","registerDevServer","startDevManifestWatcher","serverAddress","server","resolvedHost","config","host","addr","httpServer","address","port","startDevServerRegistration","options","cliConfig","extractManifest","isApp","onInterfaceSetChange","output","workDir","appHost","appPort","interfaces","app","configs","registration","id","projectId","api","type","exposesSet","watcher","extract","params","manifest","extraWatchFilenames","undefined","update","patch","changed","rebuiltServer","commit","close","release"],"mappings":"AAAA,SAAwBA,oBAAoB,QAAoB,mBAAkB;AAGlF,SAAQC,YAAY,QAAO,iBAAgB;AAC3C,SAAQC,aAAa,EAAEC,gBAAgB,QAAO,wBAAuB;AACrE,SAAQC,eAAe,QAAO,oBAAmB;AACjD,SAAgCC,iBAAiB,QAAO,gBAAe;AACvE,SAAQC,uBAAuB,QAAO,+BAA8B;AAiCpE,+HAA+H,GAC/H,SAASC,cAAcC,MAAqB;IAC1C,MAAMC,eAAeD,OAAOE,MAAM,CAACF,MAAM,CAACG,IAAI;IAC9C,MAAMC,OAAOJ,OAAOK,UAAU,EAAEC;IAChC,OAAO;QACLH,MAAM,OAAOF,iBAAiB,WAAWA,eAAe;QACxDM,MAAM,OAAOH,SAAS,YAAYA,OAAOA,KAAKG,IAAI,GAAGP,OAAOE,MAAM,CAACF,MAAM,CAACO,IAAI;IAChF;AACF;AAEA;;;;CAIC,GACD,OAAO,eAAeC,2BACpBC,OAAqC;IAErC,MAAM,EAACC,SAAS,EAAEC,eAAe,EAAEC,KAAK,EAAEC,oBAAoB,EAAEC,MAAM,EAAEd,MAAM,EAAEe,OAAO,EAAC,GAAGN;IAE3F,MAAM,EAACN,MAAMa,OAAO,EAAET,MAAMU,OAAO,EAAC,GAAGlB,cAAcC;IAErD,+EAA+E;IAC/E,yDAAyD;IACzD,MAAMkB,aAAavB,iBAAiBe,UAAUS,GAAG,EAAE;QAACP;IAAK;IACzD,MAAMQ,UAAU,MAAM1B,cAAcgB,UAAUS,GAAG;IAEjD,MAAME,eAAexB,kBAAkB;QACrCuB;QACAjB,MAAMa;QACN,6EAA6E;QAC7E,8EAA8E;QAC9E,sEAAsE;QACtEM,IAAI7B,aAAa;YAACU,MAAMa;YAAST,MAAMP,OAAOE,MAAM,CAACF,MAAM,CAACO,IAAI,IAAIU;QAAO;QAC3EC;QACAX,MAAMU;QACNM,WAAWb,WAAWc,KAAKD;QAC3BE,MAAMb,QAAQ,YAAY;QAC1BG;IACF;IAEA,MAAMW,aAAa9B,gBAAgB;QAACwB;QAASF;IAAU;IAEvD,MAAMS,UAAU,MAAM7B,wBAAwB;QAC5C,4EAA4E;QAC5E,6CAA6C;QAC7C8B,SAAS,OAAOC;YACd,MAAMV,MAAM,AAAC,CAAA,MAAM3B,qBAAqBqC,OAAOd,OAAO,CAAA,EAAGI,GAAG;YAC5D,OAAO;gBACLC,SAAS,MAAM1B,cAAcyB;gBAC7BD,YAAYvB,iBAAiBwB,KAAK;oBAACP;gBAAK;gBACxCkB,UAAU,MAAMnB,gBAAgBkB;YAClC;QACF;QACA,2EAA2E;QAC3E,wEAAwE;QACxEE,qBAAqBnB,QAAQoB,YAAY;YAAC;YAAiB;SAAgB;QAC3ElB;QACAmB,QAAQ,OAAOC;YACb,IACE,CAACR,WAAWS,OAAO,CAAC;gBAClBf,SAASc,MAAMd,OAAO;gBACtBF,YAAYgB,MAAMhB,UAAU;YAC9B,IACA;gBACAG,aAAaY,MAAM,CAACC;gBACpB;YACF;YACA,4EAA4E;YAC5E,6EAA6E;YAC7E,MAAME,gBAAgB,MAAMvB;YAC5B,6EAA6E;YAC7Ea,WAAWW,MAAM,CAAC;gBAChBjB,SAASc,MAAMd,OAAO;gBACtBF,YAAYgB,MAAMhB,UAAU;YAC9B;YACA,qEAAqE;YACrEG,aAAaY,MAAM,CAACG,gBAAgB;gBAAC,GAAGF,KAAK;gBAAE,GAAGnC,cAAcqC,cAAc;YAAA,IAAIF;QACpF;QACAnB;IACF;IAEA,OAAO;QACLuB,OAAO;YACLjB,aAAakB,OAAO;YACpB,MAAMZ,QAAQW,KAAK;QACrB;IACF;AACF"}
|
|
@@ -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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/workbench-cli",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.1",
|
|
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",
|