@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.
- package/README.md +25 -1
- package/dist/actions/build/buildApp.js +20 -17
- package/dist/actions/build/buildApp.js.map +1 -1
- package/dist/actions/build/buildStudio.js +23 -21
- package/dist/actions/build/buildStudio.js.map +1 -1
- package/dist/actions/dev/devAction.js +46 -197
- package/dist/actions/dev/devAction.js.map +1 -1
- package/dist/actions/dev/servers/getDevServerConfig.js +1 -1
- package/dist/actions/dev/servers/getDevServerConfig.js.map +1 -1
- package/dist/actions/dev/servers/startAppDevServer.js +18 -9
- package/dist/actions/dev/servers/startAppDevServer.js.map +1 -1
- package/dist/actions/dev/servers/startStudioDevServer.js +2 -2
- package/dist/actions/dev/servers/startStudioDevServer.js.map +1 -1
- package/dist/actions/dev/types.js.map +1 -1
- package/dist/actions/{dev/registration/extractDevServerManifest.js → manifest/extractStudioManifest.js} +3 -3
- package/dist/actions/manifest/extractStudioManifest.js.map +1 -0
- package/dist/actions/mcp/editorConfigs.js +8 -4
- package/dist/actions/mcp/editorConfigs.js.map +1 -1
- package/dist/commands/datasets/copy.js +17 -19
- package/dist/commands/datasets/copy.js.map +1 -1
- package/dist/commands/datasets/import.js +11 -15
- package/dist/commands/datasets/import.js.map +1 -1
- package/dist/commands/doctor.js +9 -9
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/telemetry/disable.js +3 -3
- package/dist/commands/telemetry/disable.js.map +1 -1
- package/dist/commands/telemetry/enable.js +3 -3
- package/dist/commands/telemetry/enable.js.map +1 -1
- package/dist/commands/telemetry/status.js +2 -2
- package/dist/commands/telemetry/status.js.map +1 -1
- package/dist/exports/_internal.d.ts +4 -47
- package/dist/exports/_internal.js +1 -1
- package/dist/exports/_internal.js.map +1 -1
- package/dist/server/devServer.js +1 -1
- package/dist/server/devServer.js.map +1 -1
- package/dist/util/compareDependencyVersions.js.map +1 -1
- package/oclif.manifest.json +1 -1
- package/package.json +9 -8
- package/dist/actions/build/buildStaticFiles.js +0 -144
- package/dist/actions/build/buildStaticFiles.js.map +0 -1
- package/dist/actions/build/getEnvironmentVariables.js +0 -54
- package/dist/actions/build/getEnvironmentVariables.js.map +0 -1
- package/dist/actions/dev/registration/deriveInterfaces.js +0 -46
- package/dist/actions/dev/registration/deriveInterfaces.js.map +0 -1
- package/dist/actions/dev/registration/extractDevServerManifest.js.map +0 -1
- package/dist/actions/dev/registration/interfaceSetId.js +0 -41
- package/dist/actions/dev/registration/interfaceSetId.js.map +0 -1
- package/dist/actions/dev/registration/startDevManifestWatcher.js +0 -104
- package/dist/actions/dev/registration/startDevManifestWatcher.js.map +0 -1
- package/dist/actions/dev/registration/startDevServerRegistration.js +0 -121
- package/dist/actions/dev/registration/startDevServerRegistration.js.map +0 -1
- package/dist/actions/dev/workbench/startWorkbenchDevServer.js +0 -276
- package/dist/actions/dev/workbench/startWorkbenchDevServer.js.map +0 -1
- package/dist/actions/dev/workbench/writeWorkbenchRuntime.js +0 -66
- package/dist/actions/dev/workbench/writeWorkbenchRuntime.js.map +0 -1
- package/dist/util/formatSize.js +0 -10
- package/dist/util/formatSize.js.map +0 -1
- package/dist/util/moduleFormatUtils.js +0 -18
- 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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"}
|