@sanity/cli 7.4.0 → 7.4.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.
Files changed (59) hide show
  1. package/README.md +25 -1
  2. package/dist/actions/build/buildApp.js +20 -17
  3. package/dist/actions/build/buildApp.js.map +1 -1
  4. package/dist/actions/build/buildStudio.js +23 -21
  5. package/dist/actions/build/buildStudio.js.map +1 -1
  6. package/dist/actions/dev/devAction.js +46 -197
  7. package/dist/actions/dev/devAction.js.map +1 -1
  8. package/dist/actions/dev/servers/getDevServerConfig.js +1 -1
  9. package/dist/actions/dev/servers/getDevServerConfig.js.map +1 -1
  10. package/dist/actions/dev/servers/startAppDevServer.js +18 -9
  11. package/dist/actions/dev/servers/startAppDevServer.js.map +1 -1
  12. package/dist/actions/dev/servers/startStudioDevServer.js +2 -2
  13. package/dist/actions/dev/servers/startStudioDevServer.js.map +1 -1
  14. package/dist/actions/dev/types.js.map +1 -1
  15. package/dist/actions/{dev/registration/extractDevServerManifest.js → manifest/extractStudioManifest.js} +3 -3
  16. package/dist/actions/manifest/extractStudioManifest.js.map +1 -0
  17. package/dist/actions/mcp/editorConfigs.js +8 -4
  18. package/dist/actions/mcp/editorConfigs.js.map +1 -1
  19. package/dist/commands/datasets/copy.js +17 -19
  20. package/dist/commands/datasets/copy.js.map +1 -1
  21. package/dist/commands/datasets/import.js +11 -15
  22. package/dist/commands/datasets/import.js.map +1 -1
  23. package/dist/commands/doctor.js +9 -9
  24. package/dist/commands/doctor.js.map +1 -1
  25. package/dist/commands/telemetry/disable.js +3 -3
  26. package/dist/commands/telemetry/disable.js.map +1 -1
  27. package/dist/commands/telemetry/enable.js +3 -3
  28. package/dist/commands/telemetry/enable.js.map +1 -1
  29. package/dist/commands/telemetry/status.js +2 -2
  30. package/dist/commands/telemetry/status.js.map +1 -1
  31. package/dist/exports/_internal.d.ts +4 -47
  32. package/dist/exports/_internal.js +1 -1
  33. package/dist/exports/_internal.js.map +1 -1
  34. package/dist/server/devServer.js +1 -1
  35. package/dist/server/devServer.js.map +1 -1
  36. package/dist/util/compareDependencyVersions.js.map +1 -1
  37. package/oclif.manifest.json +1 -1
  38. package/package.json +9 -8
  39. package/dist/actions/build/buildStaticFiles.js +0 -144
  40. package/dist/actions/build/buildStaticFiles.js.map +0 -1
  41. package/dist/actions/build/getEnvironmentVariables.js +0 -54
  42. package/dist/actions/build/getEnvironmentVariables.js.map +0 -1
  43. package/dist/actions/dev/registration/deriveInterfaces.js +0 -46
  44. package/dist/actions/dev/registration/deriveInterfaces.js.map +0 -1
  45. package/dist/actions/dev/registration/extractDevServerManifest.js.map +0 -1
  46. package/dist/actions/dev/registration/interfaceSetId.js +0 -41
  47. package/dist/actions/dev/registration/interfaceSetId.js.map +0 -1
  48. package/dist/actions/dev/registration/startDevManifestWatcher.js +0 -104
  49. package/dist/actions/dev/registration/startDevManifestWatcher.js.map +0 -1
  50. package/dist/actions/dev/registration/startDevServerRegistration.js +0 -121
  51. package/dist/actions/dev/registration/startDevServerRegistration.js.map +0 -1
  52. package/dist/actions/dev/workbench/startWorkbenchDevServer.js +0 -276
  53. package/dist/actions/dev/workbench/startWorkbenchDevServer.js.map +0 -1
  54. package/dist/actions/dev/workbench/writeWorkbenchRuntime.js +0 -66
  55. package/dist/actions/dev/workbench/writeWorkbenchRuntime.js.map +0 -1
  56. package/dist/util/formatSize.js +0 -10
  57. package/dist/util/formatSize.js.map +0 -1
  58. package/dist/util/moduleFormatUtils.js +0 -18
  59. package/dist/util/moduleFormatUtils.js.map +0 -1
@@ -1,54 +0,0 @@
1
- import { loadEnv } from '../../util/loadEnv.js';
2
- const appEnvPrefix = 'SANITY_APP_';
3
- const studioEnvPrefix = 'SANITY_STUDIO_';
4
- /**
5
- * Get environment variables prefixed with SANITY_STUDIO_, as an object.
6
- *
7
- * @param options - Options for the environment variable loading
8
- * {@link StudioEnvVariablesOptions}
9
- * @returns Object of studio environment variables
10
- *
11
- * @example
12
- * ```tsx
13
- * getStudioEnvironmentVariables({prefix: 'process.env.', jsonEncode: true})
14
- * ```
15
- *
16
- * @public
17
- */ export function getStudioEnvironmentVariables(options = {}) {
18
- return getEnvironmentVariables({
19
- ...options,
20
- varTypePrefix: studioEnvPrefix
21
- });
22
- }
23
- /**
24
- * Get environment variables prefixed with SANITY_APP_, as an object.
25
- *
26
- * @param options - Options for the environment variable loading
27
- * {@link StudioEnvVariablesOptions}
28
- * @returns Object of app environment variables
29
- *
30
- * @internal
31
- */ export function getAppEnvironmentVariables(options = {}) {
32
- return getEnvironmentVariables({
33
- ...options,
34
- varTypePrefix: appEnvPrefix
35
- });
36
- }
37
- function getEnvironmentVariables(options) {
38
- const { envFile = false, jsonEncode = false, prefix = '', varTypePrefix } = options;
39
- const fullEnv = envFile ? {
40
- ...process.env,
41
- ...loadEnv(envFile.mode, envFile.envDir || process.cwd(), [
42
- varTypePrefix
43
- ])
44
- } : process.env;
45
- const appEnv = {};
46
- for(const key in fullEnv){
47
- if (key.startsWith(varTypePrefix)) {
48
- appEnv[`${prefix}${key}`] = jsonEncode ? JSON.stringify(fullEnv[key] || '') : fullEnv[key] || '';
49
- }
50
- }
51
- return appEnv;
52
- }
53
-
54
- //# sourceMappingURL=getEnvironmentVariables.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../src/actions/build/getEnvironmentVariables.ts"],"sourcesContent":["import {loadEnv} from '../../util/loadEnv.js'\n\nconst appEnvPrefix = 'SANITY_APP_'\nconst studioEnvPrefix = 'SANITY_STUDIO_'\n\n/**\n * The params for the `getStudioEnvironmentVariables` and `getAppEnvironmentVariables` function\n * that gets Studio/App-focused environment variables.\n *\n * @public\n */\nexport interface StudioEnvVariablesOptions {\n /**\n * When specified includes environment variables from dotenv files (`.env`), in the same way the studio does.\n * A `mode` must be specified, usually `development`\n * or `production`, which will load the corresponding `.env.development` or `.env.production`.\n * To specify where to look for the dotenv files, specify `options.envFile.envDir`.\n */\n envFile?: false | {envDir?: string; mode: string}\n /**\n * When specified, JSON-encodes the values, which is handy if you want to pass\n * this to a bundlers hardcoded defines, such as Vite's `define` or Webpack's `DefinePlugin`.\n */\n jsonEncode?: boolean\n /**\n * When specified adds a prefix to the environment variable keys,\n * eg: `getStudioEnvironmentVariables({prefix: 'process.env.'})`\n */\n prefix?: string\n}\n\n/**\n * Get environment variables prefixed with SANITY_STUDIO_, as an object.\n *\n * @param options - Options for the environment variable loading\n * {@link StudioEnvVariablesOptions}\n * @returns Object of studio environment variables\n *\n * @example\n * ```tsx\n * getStudioEnvironmentVariables({prefix: 'process.env.', jsonEncode: true})\n * ```\n *\n * @public\n */\nexport function getStudioEnvironmentVariables(\n options: StudioEnvVariablesOptions = {},\n): Record<string, string> {\n return getEnvironmentVariables({...options, varTypePrefix: studioEnvPrefix})\n}\n\n/**\n * Get environment variables prefixed with SANITY_APP_, as an object.\n *\n * @param options - Options for the environment variable loading\n * {@link StudioEnvVariablesOptions}\n * @returns Object of app environment variables\n *\n * @internal\n */\nexport function getAppEnvironmentVariables(\n options: StudioEnvVariablesOptions = {},\n): Record<string, string> {\n return getEnvironmentVariables({...options, varTypePrefix: appEnvPrefix})\n}\n\nfunction getEnvironmentVariables(\n options: StudioEnvVariablesOptions & {varTypePrefix: string},\n): Record<string, string> {\n const {envFile = false, jsonEncode = false, prefix = '', varTypePrefix} = options\n const fullEnv = envFile\n ? {...process.env, ...loadEnv(envFile.mode, envFile.envDir || process.cwd(), [varTypePrefix])}\n : process.env\n\n const appEnv: Record<string, string> = {}\n for (const key in fullEnv) {\n if (key.startsWith(varTypePrefix)) {\n appEnv[`${prefix}${key}`] = jsonEncode\n ? JSON.stringify(fullEnv[key] || '')\n : fullEnv[key] || ''\n }\n }\n return appEnv\n}\n"],"names":["loadEnv","appEnvPrefix","studioEnvPrefix","getStudioEnvironmentVariables","options","getEnvironmentVariables","varTypePrefix","getAppEnvironmentVariables","envFile","jsonEncode","prefix","fullEnv","process","env","mode","envDir","cwd","appEnv","key","startsWith","JSON","stringify"],"mappings":"AAAA,SAAQA,OAAO,QAAO,wBAAuB;AAE7C,MAAMC,eAAe;AACrB,MAAMC,kBAAkB;AA4BxB;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASC,8BACdC,UAAqC,CAAC,CAAC;IAEvC,OAAOC,wBAAwB;QAAC,GAAGD,OAAO;QAAEE,eAAeJ;IAAe;AAC5E;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASK,2BACdH,UAAqC,CAAC,CAAC;IAEvC,OAAOC,wBAAwB;QAAC,GAAGD,OAAO;QAAEE,eAAeL;IAAY;AACzE;AAEA,SAASI,wBACPD,OAA4D;IAE5D,MAAM,EAACI,UAAU,KAAK,EAAEC,aAAa,KAAK,EAAEC,SAAS,EAAE,EAAEJ,aAAa,EAAC,GAAGF;IAC1E,MAAMO,UAAUH,UACZ;QAAC,GAAGI,QAAQC,GAAG;QAAE,GAAGb,QAAQQ,QAAQM,IAAI,EAAEN,QAAQO,MAAM,IAAIH,QAAQI,GAAG,IAAI;YAACV;SAAc,CAAC;IAAA,IAC3FM,QAAQC,GAAG;IAEf,MAAMI,SAAiC,CAAC;IACxC,IAAK,MAAMC,OAAOP,QAAS;QACzB,IAAIO,IAAIC,UAAU,CAACb,gBAAgB;YACjCW,MAAM,CAAC,GAAGP,SAASQ,KAAK,CAAC,GAAGT,aACxBW,KAAKC,SAAS,CAACV,OAAO,CAACO,IAAI,IAAI,MAC/BP,OAAO,CAACO,IAAI,IAAI;QACtB;IACF;IACA,OAAOD;AACT"}
@@ -1,46 +0,0 @@
1
- import { isWorkbenchApp } from '@sanity/cli-core';
2
- /**
3
- * Derive the workbench `interfaces[]` an app forwards to the dev-server
4
- * registry from its `unstable_defineApp` config: `views` → `panel`s,
5
- * `services` → `worker`s, and (SDK apps) `entry` → the navigable `app` view.
6
- * `entry_point` is the declared `src` — the raw value, not a resolved URL.
7
- *
8
- * Returns `undefined` for a non-branded app (no `unstable_defineApp`). A studio
9
- * that declares `entry` reaches the not-yet-implemented studio app-view path and
10
- * is rejected (FR-026).
11
- *
12
- * Shared by the initial registration and the dev config watcher so editing
13
- * `views`/`services`/`entry` in `sanity.cli.ts` re-pushes the same shape live,
14
- * the way `title`/`icon` already re-sync (FR-024).
15
- */ export function deriveInterfaces(app, options) {
16
- if (!isWorkbenchApp(app)) return undefined;
17
- // sanity-io/workbench spec 002-workbench-extension-api, US5 — studio app views are not implemented yet. A studio (not an SDK app)
18
- // that declares `entry` reaches the app-view path; reject with a clear error
19
- // rather than deriving an `app` interface for it.
20
- if (!options.isApp && app.entry !== undefined) {
21
- throw new Error('App views for studios are not implemented yet');
22
- }
23
- return [
24
- ...app.views?.map((view)=>({
25
- entry_point: view.src,
26
- interface_type: view.type,
27
- name: view.name
28
- })) ?? [],
29
- ...app.services?.map((service)=>({
30
- entry_point: service.src,
31
- interface_type: service.type,
32
- name: service.name
33
- })) ?? [],
34
- // sanity-io/workbench spec 002-workbench-extension-api, US5 — with no `entry` the app has no `app` view and isn't reachable as a
35
- // full-page app; with one, forward it so the workbench gates navigability.
36
- ...app.entry === undefined ? [] : [
37
- {
38
- entry_point: app.entry,
39
- interface_type: 'app',
40
- name: app.name
41
- }
42
- ]
43
- ];
44
- }
45
-
46
- //# sourceMappingURL=deriveInterfaces.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../src/actions/dev/registration/deriveInterfaces.ts"],"sourcesContent":["import {type CliConfig, isWorkbenchApp} from '@sanity/cli-core'\nimport {type DevServerManifest} from '@sanity/workbench-cli/dev'\n\n/** One forwarded interface record on the dev-server registry entry. */\nexport type DevServerInterface = NonNullable<DevServerManifest['interfaces']>[number]\n\n/**\n * Derive the workbench `interfaces[]` an app forwards to the dev-server\n * registry from its `unstable_defineApp` config: `views` → `panel`s,\n * `services` → `worker`s, and (SDK apps) `entry` → the navigable `app` view.\n * `entry_point` is the declared `src` — the raw value, not a resolved URL.\n *\n * Returns `undefined` for a non-branded app (no `unstable_defineApp`). A studio\n * that declares `entry` reaches the not-yet-implemented studio app-view path and\n * is rejected (FR-026).\n *\n * Shared by the initial registration and the dev config watcher so editing\n * `views`/`services`/`entry` in `sanity.cli.ts` re-pushes the same shape live,\n * the way `title`/`icon` already re-sync (FR-024).\n */\nexport function deriveInterfaces(\n app: CliConfig['app'],\n options: {isApp: boolean},\n): DevServerInterface[] | undefined {\n if (!isWorkbenchApp(app)) return undefined\n\n // sanity-io/workbench spec 002-workbench-extension-api, US5 — studio app views are not implemented yet. A studio (not an SDK app)\n // that declares `entry` reaches the app-view path; reject with a clear error\n // rather than deriving an `app` interface for it.\n if (!options.isApp && app.entry !== undefined) {\n throw new Error('App views for studios are not implemented yet')\n }\n\n return [\n ...(app.views?.map((view) => ({\n entry_point: view.src,\n interface_type: view.type,\n name: view.name,\n })) ?? []),\n ...(app.services?.map((service) => ({\n entry_point: service.src,\n interface_type: service.type,\n name: service.name,\n })) ?? []),\n // sanity-io/workbench spec 002-workbench-extension-api, US5 — with no `entry` the app has no `app` view and isn't reachable as a\n // full-page app; with one, forward it so the workbench gates navigability.\n ...(app.entry === undefined\n ? []\n : [{entry_point: app.entry, interface_type: 'app' as const, name: app.name}]),\n ]\n}\n"],"names":["isWorkbenchApp","deriveInterfaces","app","options","undefined","isApp","entry","Error","views","map","view","entry_point","src","interface_type","type","name","services","service"],"mappings":"AAAA,SAAwBA,cAAc,QAAO,mBAAkB;AAM/D;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASC,iBACdC,GAAqB,EACrBC,OAAyB;IAEzB,IAAI,CAACH,eAAeE,MAAM,OAAOE;IAEjC,kIAAkI;IAClI,6EAA6E;IAC7E,kDAAkD;IAClD,IAAI,CAACD,QAAQE,KAAK,IAAIH,IAAII,KAAK,KAAKF,WAAW;QAC7C,MAAM,IAAIG,MAAM;IAClB;IAEA,OAAO;WACDL,IAAIM,KAAK,EAAEC,IAAI,CAACC,OAAU,CAAA;gBAC5BC,aAAaD,KAAKE,GAAG;gBACrBC,gBAAgBH,KAAKI,IAAI;gBACzBC,MAAML,KAAKK,IAAI;YACjB,CAAA,MAAO,EAAE;WACLb,IAAIc,QAAQ,EAAEP,IAAI,CAACQ,UAAa,CAAA;gBAClCN,aAAaM,QAAQL,GAAG;gBACxBC,gBAAgBI,QAAQH,IAAI;gBAC5BC,MAAME,QAAQF,IAAI;YACpB,CAAA,MAAO,EAAE;QACT,iIAAiI;QACjI,2EAA2E;WACvEb,IAAII,KAAK,KAAKF,YACd,EAAE,GACF;YAAC;gBAACO,aAAaT,IAAII,KAAK;gBAAEO,gBAAgB;gBAAgBE,MAAMb,IAAIa,IAAI;YAAA;SAAE;KAC/E;AACH"}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../src/actions/dev/registration/extractDevServerManifest.ts"],"sourcesContent":["import {readFile} from 'node:fs/promises'\nimport {join, resolve} from 'node:path'\n\nimport {SANITY_CACHE_DIR} from '@sanity/cli-build/_internal/build'\n\nimport {extractManifest} from '../../manifest/extractManifest.js'\nimport {type StudioManifest} from '../../manifest/types.js'\nimport {MANIFEST_FILENAME} from '../../manifest/writeManifestFile.js'\n\n/**\n * Dev-time manifest output directory, relative to the studio working\n * directory. Sibling of Vite's `cacheDir` so it stays out of `dist` and is\n * ignored by default in typical `.gitignore` files.\n */\nconst MANIFEST_DIR = `${SANITY_CACHE_DIR}/manifest`\n\n/**\n * Run the worker-based studio schema extraction, write the resulting manifest\n * to `MANIFEST_DIR`, then read it back so the caller can inline it into the\n * registry.\n *\n * `configPath` must be the resolved `sanity.config.(ts|js)` path — passing it\n * in (e.g. from `findProjectRoot`) avoids re-traversing the filesystem on\n * every call.\n */\nexport async function extractStudioManifest(options: {\n configPath: string\n workDir: string\n}): Promise<StudioManifest | undefined> {\n const outPath = resolve(options.workDir, MANIFEST_DIR)\n await extractManifest({outPath, path: options.configPath, workDir: options.workDir})\n const raw = await readFile(join(outPath, MANIFEST_FILENAME), 'utf8')\n return JSON.parse(raw)\n}\n"],"names":["readFile","join","resolve","SANITY_CACHE_DIR","extractManifest","MANIFEST_FILENAME","MANIFEST_DIR","extractStudioManifest","options","outPath","workDir","path","configPath","raw","JSON","parse"],"mappings":"AAAA,SAAQA,QAAQ,QAAO,mBAAkB;AACzC,SAAQC,IAAI,EAAEC,OAAO,QAAO,YAAW;AAEvC,SAAQC,gBAAgB,QAAO,oCAAmC;AAElE,SAAQC,eAAe,QAAO,oCAAmC;AAEjE,SAAQC,iBAAiB,QAAO,sCAAqC;AAErE;;;;CAIC,GACD,MAAMC,eAAe,GAAGH,iBAAiB,SAAS,CAAC;AAEnD;;;;;;;;CAQC,GACD,OAAO,eAAeI,sBAAsBC,OAG3C;IACC,MAAMC,UAAUP,QAAQM,QAAQE,OAAO,EAAEJ;IACzC,MAAMF,gBAAgB;QAACK;QAASE,MAAMH,QAAQI,UAAU;QAAEF,SAASF,QAAQE,OAAO;IAAA;IAClF,MAAMG,MAAM,MAAMb,SAASC,KAAKQ,SAASJ,oBAAoB;IAC7D,OAAOS,KAAKC,KAAK,CAACF;AACpB"}
@@ -1,41 +0,0 @@
1
- /**
2
- * The identity of an app's declared interface set — an order-independent key
3
- * over its forwarded Interface records (interface_type, name, entry_point).
4
- * Two sets that differ only in declaration order share an id, so reordering
5
- * `views`/`services` in `sanity.cli.ts` is not a change; adding, removing,
6
- * renaming, or repointing a view/service is. An `undefined` set (project types
7
- * that declare no interfaces, e.g. studios) gets the same id as the empty set.
8
- *
9
- * Both detection sites compare this id against their own last-seen value across
10
- * the dev-server registry seam: the app dev server rebuilds the federation
11
- * remote when its set changes, and the workbench dev server reloads the page.
12
- * Editing a view's/service's *source file* doesn't change the id, so it stays
13
- * on the HMR path.
14
- */ export function interfaceSetId(interfaces) {
15
- if (!interfaces || interfaces.length === 0) return '';
16
- return interfaces.map((iface)=>[
17
- iface.interface_type,
18
- iface.name,
19
- iface.entry_point
20
- ].join('::')).toSorted().join('|');
21
- }
22
- /**
23
- * Track the declared interface *set* across config reloads — an added, removed,
24
- * renamed, or repointed view/service. `changed` reports whether a set differs
25
- * from the last *committed* one (a reorder or manifest-only/source-file edit
26
- * leaves it unchanged, so HMR handles it) without advancing the committed set;
27
- * `commit` advances it. Splitting the two lets the caller commit only after the
28
- * rebuild that depends on the new set has succeeded — a thrown rebuild leaves
29
- * the set uncommitted, so the next config save retries it instead of skipping.
30
- * Seed it with the initially registered set.
31
- */ export function trackInterfaceSet(initial) {
32
- let lastId = interfaceSetId(initial);
33
- return {
34
- changed: (interfaces)=>interfaceSetId(interfaces) !== lastId,
35
- commit: (interfaces)=>{
36
- lastId = interfaceSetId(interfaces);
37
- }
38
- };
39
- }
40
-
41
- //# sourceMappingURL=interfaceSetId.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../src/actions/dev/registration/interfaceSetId.ts"],"sourcesContent":["import {type DevServerInterface} from './deriveInterfaces.js'\n\n/**\n * The identity of an app's declared interface set — an order-independent key\n * over its forwarded Interface records (interface_type, name, entry_point).\n * Two sets that differ only in declaration order share an id, so reordering\n * `views`/`services` in `sanity.cli.ts` is not a change; adding, removing,\n * renaming, or repointing a view/service is. An `undefined` set (project types\n * that declare no interfaces, e.g. studios) gets the same id as the empty set.\n *\n * Both detection sites compare this id against their own last-seen value across\n * the dev-server registry seam: the app dev server rebuilds the federation\n * remote when its set changes, and the workbench dev server reloads the page.\n * Editing a view's/service's *source file* doesn't change the id, so it stays\n * on the HMR path.\n */\nexport function interfaceSetId(interfaces: readonly DevServerInterface[] | undefined): string {\n if (!interfaces || interfaces.length === 0) return ''\n return interfaces\n .map((iface) => [iface.interface_type, iface.name, iface.entry_point].join('::'))\n .toSorted()\n .join('|')\n}\n\n/**\n * Track the declared interface *set* across config reloads — an added, removed,\n * renamed, or repointed view/service. `changed` reports whether a set differs\n * from the last *committed* one (a reorder or manifest-only/source-file edit\n * leaves it unchanged, so HMR handles it) without advancing the committed set;\n * `commit` advances it. Splitting the two lets the caller commit only after the\n * rebuild that depends on the new set has succeeded — a thrown rebuild leaves\n * the set uncommitted, so the next config save retries it instead of skipping.\n * Seed it with the initially registered set.\n */\nexport function trackInterfaceSet(initial: readonly DevServerInterface[] | undefined): {\n changed: (interfaces: readonly DevServerInterface[] | undefined) => boolean\n commit: (interfaces: readonly DevServerInterface[] | undefined) => void\n} {\n let lastId = interfaceSetId(initial)\n return {\n changed: (interfaces) => interfaceSetId(interfaces) !== lastId,\n commit: (interfaces) => {\n lastId = interfaceSetId(interfaces)\n },\n }\n}\n"],"names":["interfaceSetId","interfaces","length","map","iface","interface_type","name","entry_point","join","toSorted","trackInterfaceSet","initial","lastId","changed","commit"],"mappings":"AAEA;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASA,eAAeC,UAAqD;IAClF,IAAI,CAACA,cAAcA,WAAWC,MAAM,KAAK,GAAG,OAAO;IACnD,OAAOD,WACJE,GAAG,CAAC,CAACC,QAAU;YAACA,MAAMC,cAAc;YAAED,MAAME,IAAI;YAAEF,MAAMG,WAAW;SAAC,CAACC,IAAI,CAAC,OAC1EC,QAAQ,GACRD,IAAI,CAAC;AACV;AAEA;;;;;;;;;CASC,GACD,OAAO,SAASE,kBAAkBC,OAAkD;IAIlF,IAAIC,SAASZ,eAAeW;IAC5B,OAAO;QACLE,SAAS,CAACZ,aAAeD,eAAeC,gBAAgBW;QACxDE,QAAQ,CAACb;YACPW,SAASZ,eAAeC;QAC1B;IACF;AACF"}
@@ -1,104 +0,0 @@
1
- import { watch } from 'node:fs';
2
- import { basename, dirname } from 'node:path';
3
- import { findProjectRoot } from '@sanity/cli-core';
4
- import { canonicalizeWatchDir } from '@sanity/workbench-cli/dev';
5
- import { devDebug } from '../devDebug.js';
6
- /**
7
- * Debounce window between config file events and the next manifest
8
- * regeneration. Coalesces rapid saves (e.g. editor auto-save) and
9
- * atomic-rename bursts emitted by tools like VS Code.
10
- */ const DEBOUNCE_MS = 250;
11
- /**
12
- * Generate the project manifest once and then keep it in sync with the
13
- * project's config file (`sanity.config.(ts|js)` for studios,
14
- * `sanity.cli.(ts|js)` for core-apps) on disk. The initial generation runs
15
- * fire-and-forget so it doesn't block dev-server startup; subsequent
16
- * file-system events are coalesced behind it, so the extractor never has
17
- * overlapping writes to its shared output directory. Each successful
18
- * regeneration inlines the new manifest into the registry via the `update`
19
- * callback, so any running workbench rebroadcasts to its clients.
20
- *
21
- * Errors during extraction are logged as warnings and do not crash the dev
22
- * server — the previously extracted manifest (if any) stays in the
23
- * registry.
24
- */ export async function startDevManifestWatcher({ extract, extraWatchFilenames, output, update, workDir }) {
25
- const projectRoot = await findProjectRoot(workDir);
26
- const configPath = projectRoot.path;
27
- let running = false;
28
- let pending = false;
29
- let closed = false;
30
- const regenerate = async ()=>{
31
- if (closed) return;
32
- if (running) {
33
- pending = true;
34
- return;
35
- }
36
- running = true;
37
- try {
38
- const { interfaces, manifest } = await extract({
39
- configPath,
40
- workDir
41
- });
42
- if (closed) return;
43
- await update({
44
- interfaces,
45
- manifest,
46
- manifestUpdatedAt: new Date().toISOString()
47
- });
48
- } catch (err) {
49
- // Extractors print their own spinner failure; log the reason here so
50
- // the user sees what went wrong alongside the spinner indicator.
51
- devDebug('Manifest regeneration failed: %O', err);
52
- output.warn(`Could not extract manifest for workbench: ${err instanceof Error ? err.message : String(err)}`);
53
- } finally{
54
- running = false;
55
- if (pending && !closed) {
56
- pending = false;
57
- void regenerate();
58
- }
59
- }
60
- };
61
- // Route the initial extraction through `regenerate` too, so file-system
62
- // events arriving before it finishes get coalesced rather than racing it
63
- // for the shared output directory.
64
- void regenerate();
65
- // Watch the config file's parent directory and filter by filename.
66
- // Watching the file itself is unreliable across editors that perform
67
- // atomic-save (delete + rename) — the watcher loses its target once the
68
- // inode changes. Directory watches survive those transitions.
69
- // Canonicalize to the real long path so `fs.watch` doesn't abort on Windows
70
- // short-path dirs. See `canonicalizeWatchDir`.
71
- const configDir = canonicalizeWatchDir(dirname(configPath));
72
- const watchFilenames = new Set([
73
- basename(configPath),
74
- ...extraWatchFilenames ?? []
75
- ]);
76
- let debounceTimer;
77
- const onEvent = (_event, filename)=>{
78
- if (!filename) return;
79
- const name = typeof filename === 'string' ? filename : filename.toString('utf8');
80
- if (!watchFilenames.has(name)) return;
81
- clearTimeout(debounceTimer);
82
- debounceTimer = setTimeout(()=>{
83
- void regenerate();
84
- }, DEBOUNCE_MS);
85
- };
86
- const watcher = watch(configDir, onEvent);
87
- watcher.on('error', (err)=>{
88
- devDebug('Config watcher error: %O', err);
89
- output.warn(`Manifest watcher error: ${err instanceof Error ? err.message : String(err)}`);
90
- });
91
- return {
92
- // Idempotent — a repeat close (e.g. a signal handler racing an explicit
93
- // close) is a no-op, so we never clear an already-cleared timer or
94
- // double-close the underlying watcher.
95
- close: async ()=>{
96
- if (closed) return;
97
- closed = true;
98
- clearTimeout(debounceTimer);
99
- watcher.close();
100
- }
101
- };
102
- }
103
-
104
- //# sourceMappingURL=startDevManifestWatcher.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../src/actions/dev/registration/startDevManifestWatcher.ts"],"sourcesContent":["import {watch} from 'node:fs'\nimport {basename, dirname} from 'node:path'\n\nimport {findProjectRoot, type Output} from '@sanity/cli-core'\nimport {canonicalizeWatchDir} from '@sanity/workbench-cli/dev'\n\nimport {devDebug} from '../devDebug.js'\nimport {type DevServerInterface} from './deriveInterfaces.js'\n\n/**\n * Debounce window between config file events and the next manifest\n * regeneration. Coalesces rapid saves (e.g. editor auto-save) and\n * atomic-rename bursts emitted by tools like VS Code.\n */\nconst DEBOUNCE_MS = 250\n\ninterface DevManifestWatcher {\n close: () => Promise<void>\n}\n\n/** Subset of registry fields the watcher is allowed to update. */\ninterface ManifestPatch<T> {\n manifest: T | undefined\n manifestUpdatedAt: string\n\n /**\n * Workbench interfaces (views/services/app view) re-derived from the config\n * on each change, so editing `views`/`services`/`entry` in `sanity.cli.ts`\n * re-syncs live like `title`/`icon` (FR-024). `undefined` only for\n * non-branded configs — the registry patch is a shallow merge, so extractors\n * must re-derive rather than omit, or the registered set gets wiped.\n */\n interfaces?: DevServerInterface[] | undefined\n}\n\ninterface StartDevManifestWatcherOptions<T> {\n /**\n * Run the project-specific extraction and resolve to the inlined manifest\n * plus the workbench `interfaces[]` (when the project declares them).\n * Receives the resolved config path (e.g. `sanity.config.ts` for studios,\n * `sanity.cli.ts` for core-apps) and the working directory.\n */\n extract: (params: {\n configPath: string\n workDir: string\n }) => Promise<{interfaces?: DevServerInterface[] | undefined; manifest: T | undefined}>\n output: Output\n /**\n * Called after every successful extraction with the inlined manifest +\n * interfaces. Awaited, so an interface-set change can rebuild the federation\n * remote before the registry is patched (which is what reloads the workbench).\n */\n update: (patch: ManifestPatch<T>) => Promise<void> | void\n workDir: string\n\n /**\n * Extra config filenames (basenames in the project root directory) that also\n * trigger a regeneration. Studios resolve their project root via\n * `sanity.config.*` but declare workbench interfaces in `sanity.cli.*`, so\n * their watcher needs to react to both files.\n */\n extraWatchFilenames?: readonly string[]\n}\n\n/**\n * Generate the project manifest once and then keep it in sync with the\n * project's config file (`sanity.config.(ts|js)` for studios,\n * `sanity.cli.(ts|js)` for core-apps) on disk. The initial generation runs\n * fire-and-forget so it doesn't block dev-server startup; subsequent\n * file-system events are coalesced behind it, so the extractor never has\n * overlapping writes to its shared output directory. Each successful\n * regeneration inlines the new manifest into the registry via the `update`\n * callback, so any running workbench rebroadcasts to its clients.\n *\n * Errors during extraction are logged as warnings and do not crash the dev\n * server — the previously extracted manifest (if any) stays in the\n * registry.\n */\nexport async function startDevManifestWatcher<T>({\n extract,\n extraWatchFilenames,\n output,\n update,\n workDir,\n}: StartDevManifestWatcherOptions<T>): Promise<DevManifestWatcher> {\n const projectRoot = await findProjectRoot(workDir)\n const configPath = projectRoot.path\n\n let running = false\n let pending = false\n let closed = false\n\n const regenerate = async () => {\n if (closed) return\n if (running) {\n pending = true\n return\n }\n running = true\n try {\n const {interfaces, manifest} = await extract({configPath, workDir})\n if (closed) return\n await update({interfaces, manifest, manifestUpdatedAt: new Date().toISOString()})\n } catch (err) {\n // Extractors print their own spinner failure; log the reason here so\n // the user sees what went wrong alongside the spinner indicator.\n devDebug('Manifest regeneration failed: %O', err)\n output.warn(\n `Could not extract manifest for workbench: ${err instanceof Error ? err.message : String(err)}`,\n )\n } finally {\n running = false\n if (pending && !closed) {\n pending = false\n void regenerate()\n }\n }\n }\n\n // Route the initial extraction through `regenerate` too, so file-system\n // events arriving before it finishes get coalesced rather than racing it\n // for the shared output directory.\n void regenerate()\n\n // Watch the config file's parent directory and filter by filename.\n // Watching the file itself is unreliable across editors that perform\n // atomic-save (delete + rename) — the watcher loses its target once the\n // inode changes. Directory watches survive those transitions.\n // Canonicalize to the real long path so `fs.watch` doesn't abort on Windows\n // short-path dirs. See `canonicalizeWatchDir`.\n const configDir = canonicalizeWatchDir(dirname(configPath))\n const watchFilenames = new Set([basename(configPath), ...(extraWatchFilenames ?? [])])\n\n let debounceTimer: ReturnType<typeof setTimeout> | undefined\n\n const onEvent = (_event: string, filename: Buffer | string | null) => {\n if (!filename) return\n const name = typeof filename === 'string' ? filename : filename.toString('utf8')\n if (!watchFilenames.has(name)) return\n clearTimeout(debounceTimer)\n debounceTimer = setTimeout(() => {\n void regenerate()\n }, DEBOUNCE_MS)\n }\n\n const watcher = watch(configDir, onEvent)\n\n watcher.on('error', (err) => {\n devDebug('Config watcher error: %O', err)\n output.warn(`Manifest watcher error: ${err instanceof Error ? err.message : String(err)}`)\n })\n\n return {\n // Idempotent — a repeat close (e.g. a signal handler racing an explicit\n // close) is a no-op, so we never clear an already-cleared timer or\n // double-close the underlying watcher.\n close: async () => {\n if (closed) return\n closed = true\n clearTimeout(debounceTimer)\n watcher.close()\n },\n }\n}\n"],"names":["watch","basename","dirname","findProjectRoot","canonicalizeWatchDir","devDebug","DEBOUNCE_MS","startDevManifestWatcher","extract","extraWatchFilenames","output","update","workDir","projectRoot","configPath","path","running","pending","closed","regenerate","interfaces","manifest","manifestUpdatedAt","Date","toISOString","err","warn","Error","message","String","configDir","watchFilenames","Set","debounceTimer","onEvent","_event","filename","name","toString","has","clearTimeout","setTimeout","watcher","on","close"],"mappings":"AAAA,SAAQA,KAAK,QAAO,UAAS;AAC7B,SAAQC,QAAQ,EAAEC,OAAO,QAAO,YAAW;AAE3C,SAAQC,eAAe,QAAoB,mBAAkB;AAC7D,SAAQC,oBAAoB,QAAO,4BAA2B;AAE9D,SAAQC,QAAQ,QAAO,iBAAgB;AAGvC;;;;CAIC,GACD,MAAMC,cAAc;AAkDpB;;;;;;;;;;;;;CAaC,GACD,OAAO,eAAeC,wBAA2B,EAC/CC,OAAO,EACPC,mBAAmB,EACnBC,MAAM,EACNC,MAAM,EACNC,OAAO,EAC2B;IAClC,MAAMC,cAAc,MAAMV,gBAAgBS;IAC1C,MAAME,aAAaD,YAAYE,IAAI;IAEnC,IAAIC,UAAU;IACd,IAAIC,UAAU;IACd,IAAIC,SAAS;IAEb,MAAMC,aAAa;QACjB,IAAID,QAAQ;QACZ,IAAIF,SAAS;YACXC,UAAU;YACV;QACF;QACAD,UAAU;QACV,IAAI;YACF,MAAM,EAACI,UAAU,EAAEC,QAAQ,EAAC,GAAG,MAAMb,QAAQ;gBAACM;gBAAYF;YAAO;YACjE,IAAIM,QAAQ;YACZ,MAAMP,OAAO;gBAACS;gBAAYC;gBAAUC,mBAAmB,IAAIC,OAAOC,WAAW;YAAE;QACjF,EAAE,OAAOC,KAAK;YACZ,qEAAqE;YACrE,iEAAiE;YACjEpB,SAAS,oCAAoCoB;YAC7Cf,OAAOgB,IAAI,CACT,CAAC,0CAA0C,EAAED,eAAeE,QAAQF,IAAIG,OAAO,GAAGC,OAAOJ,MAAM;QAEnG,SAAU;YACRT,UAAU;YACV,IAAIC,WAAW,CAACC,QAAQ;gBACtBD,UAAU;gBACV,KAAKE;YACP;QACF;IACF;IAEA,wEAAwE;IACxE,yEAAyE;IACzE,mCAAmC;IACnC,KAAKA;IAEL,mEAAmE;IACnE,qEAAqE;IACrE,wEAAwE;IACxE,8DAA8D;IAC9D,4EAA4E;IAC5E,+CAA+C;IAC/C,MAAMW,YAAY1B,qBAAqBF,QAAQY;IAC/C,MAAMiB,iBAAiB,IAAIC,IAAI;QAAC/B,SAASa;WAAiBL,uBAAuB,EAAE;KAAE;IAErF,IAAIwB;IAEJ,MAAMC,UAAU,CAACC,QAAgBC;QAC/B,IAAI,CAACA,UAAU;QACf,MAAMC,OAAO,OAAOD,aAAa,WAAWA,WAAWA,SAASE,QAAQ,CAAC;QACzE,IAAI,CAACP,eAAeQ,GAAG,CAACF,OAAO;QAC/BG,aAAaP;QACbA,gBAAgBQ,WAAW;YACzB,KAAKtB;QACP,GAAGb;IACL;IAEA,MAAMoC,UAAU1C,MAAM8B,WAAWI;IAEjCQ,QAAQC,EAAE,CAAC,SAAS,CAAClB;QACnBpB,SAAS,4BAA4BoB;QACrCf,OAAOgB,IAAI,CAAC,CAAC,wBAAwB,EAAED,eAAeE,QAAQF,IAAIG,OAAO,GAAGC,OAAOJ,MAAM;IAC3F;IAEA,OAAO;QACL,wEAAwE;QACxE,mEAAmE;QACnE,uCAAuC;QACvCmB,OAAO;YACL,IAAI1B,QAAQ;YACZA,SAAS;YACTsB,aAAaP;YACbS,QAAQE,KAAK;QACf;IACF;AACF"}
@@ -1,121 +0,0 @@
1
- import { getCliConfigUncached } from '@sanity/cli-core';
2
- import { registerDevServer } from '@sanity/workbench-cli/dev';
3
- import { checkForDeprecatedAppId, getAppId } from '../../../util/appId.js';
4
- import { extractCoreAppManifest } from '../../manifest/extractCoreAppManifest.js';
5
- import { deriveInterfaces } from './deriveInterfaces.js';
6
- import { extractStudioManifest } from './extractDevServerManifest.js';
7
- import { trackInterfaceSet } from './interfaceSetId.js';
8
- import { startDevManifestWatcher } from './startDevManifestWatcher.js';
9
- /**
10
- * The address a dev server actually bound, preferring the live socket over the
11
- * configured port — with non-strict ports the two can disagree. Used for the
12
- * initial registration and again after an interface-set rebuild recreates the
13
- * server, so the registry never advertises a port nothing listens on.
14
- */ function serverAddress(server) {
15
- const resolvedHost = server.config.server.host;
16
- const addr = server.httpServer?.address();
17
- return {
18
- host: typeof resolvedHost === 'string' ? resolvedHost : 'localhost',
19
- port: typeof addr === 'object' && addr ? addr.port : server.config.server.port
20
- };
21
- }
22
- /**
23
- * Register the dev server in the dev server registry and start a watcher for
24
- * the manifest file. The workbench reads the registration to locate the dev
25
- * server and display it in the UI; the manifest watcher keeps that registration
26
- * pointed at the latest manifest, which the workbench renders as project metadata.
27
- */ export async function startDevServerRegistration(options) {
28
- const { cliConfig, isApp, onInterfaceSetChange, output, server, workDir } = options;
29
- checkForDeprecatedAppId({
30
- cliConfig,
31
- output
32
- });
33
- const { host: appHost, port: appPort } = serverAddress(server);
34
- // Interfaces (panels/workers/app view) are derived from the branded
35
- // `unstable_defineApp` config and forwarded on the registry entry (alongside,
36
- // not inside, the manifest) so the workbench renders local panels, runs local
37
- // workers, and resolves the app view without a deploy. The watcher below
38
- // re-derives them on every `sanity.cli.ts` edit so adding/removing a view or
39
- // service re-syncs live (FR-024) — the way `title`/`icon` already do.
40
- const interfaces = deriveInterfaces(cliConfig.app, {
41
- isApp
42
- });
43
- const registration = registerDevServer({
44
- host: appHost,
45
- id: getAppId(cliConfig),
46
- interfaces,
47
- port: appPort,
48
- projectId: cliConfig?.api?.projectId,
49
- type: isApp ? 'coreApp' : 'studio',
50
- workDir
51
- });
52
- // Tracks whether a watcher pass changed the *set* of interfaces (rebuild
53
- // needed) vs. only the manifest (title/icon) or a view/service source file
54
- // (HMR handles it — the set is unchanged). Committed separately from the
55
- // detection so a failed rebuild stays eligible for retry (see below).
56
- const interfaceSet = trackInterfaceSet(interfaces);
57
- const watcher = await startDevManifestWatcher({
58
- extract: isApp ? async ({ workDir: wd })=>({
59
- // Interfaces are NOT part of the manifest — they're re-derived from the
60
- // same config edit and forwarded as a separate registry field, so a
61
- // `views`/`services`/`entry` change re-syncs live (FR-024).
62
- interfaces: deriveInterfaces((await getCliConfigUncached(wd)).app, {
63
- isApp
64
- }),
65
- manifest: await extractCoreAppManifest({
66
- workDir: wd
67
- })
68
- }) : async (params)=>({
69
- // Studios declare views/services in `sanity.cli.ts` too — re-derive
70
- // them like the app extract does. The registry patch is a shallow
71
- // merge, so a hardcoded `interfaces: undefined` here would wipe the
72
- // panels/workers forwarded by the initial registration on the very
73
- // first regeneration.
74
- interfaces: deriveInterfaces((await getCliConfigUncached(params.workDir)).app, {
75
- isApp
76
- }),
77
- manifest: await extractStudioManifest(params)
78
- }),
79
- // A studio's project root resolves to `sanity.config.*`, but its workbench
80
- // interfaces live in `sanity.cli.*` — watch that too so adding/removing a
81
- // view or service regenerates without a manual restart. Apps already
82
- // resolve their root to `sanity.cli.*`.
83
- extraWatchFilenames: isApp ? undefined : [
84
- 'sanity.cli.js',
85
- 'sanity.cli.ts'
86
- ],
87
- output,
88
- update: async (patch)=>{
89
- if (!interfaceSet.changed(patch.interfaces)) {
90
- // Set unchanged (reorder, or a manifest-only/source-file edit) — patch
91
- // the registry and let HMR handle the rest; no rebuild needed.
92
- registration.update(patch);
93
- return;
94
- }
95
- // Rebuild the app remote first (so the new view/service has an expose +
96
- // artifact), THEN patch the registry — the registry patch is what reloads
97
- // the workbench page, and it must re-fetch a remote that already exposes
98
- // the new interface.
99
- const rebuiltServer = await onInterfaceSetChange?.();
100
- // Commit only after the rebuild resolves: a thrown rebuild surfaces
101
- // through the watcher's failure path with the set uncommitted, so the
102
- // next pass over the same declarations retries instead of skipping.
103
- interfaceSet.commit(patch.interfaces);
104
- // The recreated server can bind a different port (non-strict ports), so
105
- // the patch carries its actual address alongside the manifest fields.
106
- registration.update(rebuiltServer ? {
107
- ...patch,
108
- ...serverAddress(rebuiltServer)
109
- } : patch);
110
- },
111
- workDir
112
- });
113
- return {
114
- close: async ()=>{
115
- registration.release();
116
- await watcher.close();
117
- }
118
- };
119
- }
120
-
121
- //# sourceMappingURL=startDevServerRegistration.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../src/actions/dev/registration/startDevServerRegistration.ts"],"sourcesContent":["import {type CliConfig, getCliConfigUncached, type Output} from '@sanity/cli-core'\nimport {registerDevServer} from '@sanity/workbench-cli/dev'\nimport {type ViteDevServer} from 'vite'\n\nimport {checkForDeprecatedAppId, getAppId} from '../../../util/appId.js'\nimport {extractCoreAppManifest} from '../../manifest/extractCoreAppManifest.js'\nimport {deriveInterfaces} from './deriveInterfaces.js'\nimport {extractStudioManifest} from './extractDevServerManifest.js'\nimport {trackInterfaceSet} from './interfaceSetId.js'\nimport {startDevManifestWatcher} from './startDevManifestWatcher.js'\n\ninterface DevServerRegistrationOptions {\n cliConfig: CliConfig\n isApp: boolean\n output: Output\n server: ViteDevServer\n workDir: string\n\n /**\n * Called when the declared interface *set* changes (a view/service added,\n * removed, renamed, or repointed in `sanity.cli.ts`), awaited *before* the\n * registry is patched — the registry patch is what reloads the workbench\n * page, and it must re-fetch a remote that already exposes the new\n * interface, so the caller's rebuild has to complete first. A view/service\n * *source* edit doesn't change the set and never fires this. Studios\n * declare views/services the same way apps do, so both pass a rebuild.\n *\n * Resolves with the recreated dev server so the registry entry can be\n * patched with its actual address — workbench projects run with non-strict\n * ports, so the replacement isn't guaranteed to bind the port registered at\n * startup. Must reject when the restart doesn't produce a listening server:\n * the set id stays uncommitted so the next config save retries the rebuild\n * instead of advertising the new interface set on a dead port.\n */\n onInterfaceSetChange?: () => Promise<ViteDevServer>\n}\n\ninterface DevServerRegistrationHandle {\n close: () => Promise<void>\n}\n\n/**\n * The address a dev server actually bound, preferring the live socket over the\n * configured port — with non-strict ports the two can disagree. Used for the\n * initial registration and again after an interface-set rebuild recreates the\n * server, so the registry never advertises a port nothing listens on.\n */\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 dev server registry and start a watcher for\n * the manifest file. The workbench reads the registration to locate the dev\n * server and display it in the UI; the manifest watcher keeps that registration\n * pointed at the latest manifest, which the workbench renders as project metadata.\n */\nexport async function startDevServerRegistration(\n options: DevServerRegistrationOptions,\n): Promise<DevServerRegistrationHandle> {\n const {cliConfig, isApp, onInterfaceSetChange, output, server, workDir} = options\n\n checkForDeprecatedAppId({cliConfig, output})\n\n const {host: appHost, port: appPort} = serverAddress(server)\n\n // Interfaces (panels/workers/app view) are derived from the branded\n // `unstable_defineApp` config and forwarded on the registry entry (alongside,\n // not inside, the manifest) so the workbench renders local panels, runs local\n // workers, and resolves the app view without a deploy. The watcher below\n // re-derives them on every `sanity.cli.ts` edit so adding/removing a view or\n // service re-syncs live (FR-024) — the way `title`/`icon` already do.\n const interfaces = deriveInterfaces(cliConfig.app, {isApp})\n\n const registration = registerDevServer({\n host: appHost,\n id: getAppId(cliConfig),\n interfaces,\n port: appPort,\n projectId: cliConfig?.api?.projectId,\n type: isApp ? 'coreApp' : 'studio',\n workDir,\n })\n\n // Tracks whether a watcher pass changed the *set* of interfaces (rebuild\n // needed) vs. only the manifest (title/icon) or a view/service source file\n // (HMR handles it — the set is unchanged). Committed separately from the\n // detection so a failed rebuild stays eligible for retry (see below).\n const interfaceSet = trackInterfaceSet(interfaces)\n\n const watcher = await startDevManifestWatcher({\n extract: isApp\n ? async ({workDir: wd}) => ({\n // Interfaces are NOT part of the manifest — they're re-derived from the\n // same config edit and forwarded as a separate registry field, so a\n // `views`/`services`/`entry` change re-syncs live (FR-024).\n interfaces: deriveInterfaces((await getCliConfigUncached(wd)).app, {isApp}),\n manifest: await extractCoreAppManifest({workDir: wd}),\n })\n : async (params) => ({\n // Studios declare views/services in `sanity.cli.ts` too — re-derive\n // them like the app extract does. The registry patch is a shallow\n // merge, so a hardcoded `interfaces: undefined` here would wipe the\n // panels/workers forwarded by the initial registration on the very\n // first regeneration.\n interfaces: deriveInterfaces((await getCliConfigUncached(params.workDir)).app, {isApp}),\n manifest: await extractStudioManifest(params),\n }),\n // A studio's project root resolves to `sanity.config.*`, but its workbench\n // interfaces live in `sanity.cli.*` — watch that too so adding/removing a\n // view or service regenerates without a manual restart. Apps already\n // resolve their root to `sanity.cli.*`.\n extraWatchFilenames: isApp ? undefined : ['sanity.cli.js', 'sanity.cli.ts'],\n output,\n update: async (patch) => {\n if (!interfaceSet.changed(patch.interfaces)) {\n // Set unchanged (reorder, or a manifest-only/source-file edit) — patch\n // the registry and let HMR handle the rest; no rebuild needed.\n registration.update(patch)\n return\n }\n // Rebuild the app remote first (so the new view/service has an expose +\n // artifact), THEN patch the registry — the registry patch is what reloads\n // the workbench page, and it must re-fetch a remote that already exposes\n // the new interface.\n const rebuiltServer = await onInterfaceSetChange?.()\n // Commit only after the rebuild resolves: a thrown rebuild surfaces\n // through the watcher's failure path with the set uncommitted, so the\n // next pass over the same declarations retries instead of skipping.\n interfaceSet.commit(patch.interfaces)\n // The recreated server can bind a different port (non-strict ports), so\n // the patch carries its actual address alongside the manifest fields.\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","registerDevServer","checkForDeprecatedAppId","getAppId","extractCoreAppManifest","deriveInterfaces","extractStudioManifest","trackInterfaceSet","startDevManifestWatcher","serverAddress","server","resolvedHost","config","host","addr","httpServer","address","port","startDevServerRegistration","options","cliConfig","isApp","onInterfaceSetChange","output","workDir","appHost","appPort","interfaces","app","registration","id","projectId","api","type","interfaceSet","watcher","extract","wd","manifest","params","extraWatchFilenames","undefined","update","patch","changed","rebuiltServer","commit","close","release"],"mappings":"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"}