@sanity/cli 7.4.0 → 7.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -1
- package/dist/actions/build/buildApp.js +19 -14
- package/dist/actions/build/buildApp.js.map +1 -1
- package/dist/actions/build/buildStaticFiles.js +1 -1
- package/dist/actions/build/buildStaticFiles.js.map +1 -1
- package/dist/actions/build/buildStudio.js +22 -18
- 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 +6 -5
- 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
|
@@ -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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"}
|
|
@@ -1,276 +0,0 @@
|
|
|
1
|
-
import { SANITY_CACHE_DIR } from '@sanity/cli-build/_internal/build';
|
|
2
|
-
import { isWorkbenchApp, resolveLocalPackage } from '@sanity/cli-core';
|
|
3
|
-
import { acquireWorkbenchLock, getRegisteredServers, readWorkbenchLock, watchRegistry } from '@sanity/workbench-cli/dev';
|
|
4
|
-
import viteReact from '@vitejs/plugin-react';
|
|
5
|
-
import { createServer } from 'vite';
|
|
6
|
-
import { z } from 'zod/mini';
|
|
7
|
-
import { resolveReactStrictMode } from '../../../util/resolveReactStrictMode.js';
|
|
8
|
-
import { devDebug } from '../devDebug.js';
|
|
9
|
-
import { interfaceSetId } from '../registration/interfaceSetId.js';
|
|
10
|
-
import { writeWorkbenchRuntime } from './writeWorkbenchRuntime.js';
|
|
11
|
-
const noop = async ()=>{};
|
|
12
|
-
/** Stable per-app key for the registry-watch interface diff. */ const serverKey = (s)=>`${s.id ?? ''}@${s.host ?? ''}:${s.port}`;
|
|
13
|
-
const toApplicationsPayload = (servers)=>({
|
|
14
|
-
applications: servers.map(({ host, id, interfaces, manifest, port, projectId, type })=>({
|
|
15
|
-
host,
|
|
16
|
-
id,
|
|
17
|
-
interfaces,
|
|
18
|
-
manifest,
|
|
19
|
-
port,
|
|
20
|
-
projectId,
|
|
21
|
-
type
|
|
22
|
-
}))
|
|
23
|
-
});
|
|
24
|
-
/**
|
|
25
|
-
* Start the workbench dev server when federation is enabled and the workbench
|
|
26
|
-
* package is available. If the desired port is already taken — by another
|
|
27
|
-
* workbench instance or an unrelated process — fall back to running without a
|
|
28
|
-
* workbench and let the app/studio dev server claim the configured port.
|
|
29
|
-
*/ export async function startWorkbenchDevServer(options) {
|
|
30
|
-
const { cliConfig, httpHost, httpPort: workbenchPort, output, workDir } = options;
|
|
31
|
-
// Workbench is opted into solely by calling `unstable_defineApp`.
|
|
32
|
-
if (!isWorkbenchApp(cliConfig?.app)) {
|
|
33
|
-
devDebug('Not a workbench app, skipping workbench dev server');
|
|
34
|
-
return {
|
|
35
|
-
close: noop,
|
|
36
|
-
httpHost,
|
|
37
|
-
workbenchAvailable: false,
|
|
38
|
-
workbenchPort
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
let workbenchAvailable = false;
|
|
42
|
-
try {
|
|
43
|
-
await resolveLocalPackage('sanity/workbench', workDir);
|
|
44
|
-
workbenchAvailable = true;
|
|
45
|
-
} catch {
|
|
46
|
-
devDebug('Workbench not available, skipping workbench dev server');
|
|
47
|
-
}
|
|
48
|
-
if (!workbenchAvailable) {
|
|
49
|
-
return {
|
|
50
|
-
close: noop,
|
|
51
|
-
httpHost,
|
|
52
|
-
workbenchAvailable,
|
|
53
|
-
workbenchPort
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
// Acquire an exclusive lock — only one workbench per machine.
|
|
57
|
-
// Uses O_EXCL which is atomic at the OS level, preventing races when
|
|
58
|
-
// multiple `sanity dev` processes start simultaneously (e.g. via turbo).
|
|
59
|
-
const workbenchLock = acquireWorkbenchLock({
|
|
60
|
-
host: httpHost || 'localhost',
|
|
61
|
-
port: workbenchPort
|
|
62
|
-
});
|
|
63
|
-
if (!workbenchLock) {
|
|
64
|
-
const existing = readWorkbenchLock();
|
|
65
|
-
devDebug('Workbench already running at pid %d on port %d, skipping', existing?.pid, existing?.port);
|
|
66
|
-
return {
|
|
67
|
-
close: noop,
|
|
68
|
-
httpHost: existing?.host ?? httpHost,
|
|
69
|
-
workbenchAvailable: true,
|
|
70
|
-
workbenchPort: existing?.port ?? workbenchPort
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
// The lock is already held; an exception here (runtime-file write failure,
|
|
74
|
-
// invalid remote URL) would otherwise leak it until the next acquire prunes
|
|
75
|
-
// the stale PID.
|
|
76
|
-
let result;
|
|
77
|
-
try {
|
|
78
|
-
result = await createWorkbenchViteServer({
|
|
79
|
-
cliConfig,
|
|
80
|
-
httpHost,
|
|
81
|
-
output,
|
|
82
|
-
workbenchPort,
|
|
83
|
-
workDir
|
|
84
|
-
});
|
|
85
|
-
} catch (err) {
|
|
86
|
-
workbenchLock.release();
|
|
87
|
-
throw err;
|
|
88
|
-
}
|
|
89
|
-
if (!result) {
|
|
90
|
-
workbenchLock.release();
|
|
91
|
-
return {
|
|
92
|
-
close: noop,
|
|
93
|
-
httpHost,
|
|
94
|
-
workbenchAvailable: false,
|
|
95
|
-
workbenchPort
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
const { actualPort, close } = result;
|
|
99
|
-
workbenchLock.updatePort(actualPort);
|
|
100
|
-
return {
|
|
101
|
-
close: async ()=>{
|
|
102
|
-
workbenchLock.release();
|
|
103
|
-
await close();
|
|
104
|
-
},
|
|
105
|
-
httpHost,
|
|
106
|
-
workbenchAvailable,
|
|
107
|
-
workbenchPort: actualPort
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
async function createWorkbenchViteServer(options) {
|
|
111
|
-
const { cliConfig, httpHost, output, workbenchPort, workDir } = options;
|
|
112
|
-
const remoteUrl = parseRemoteUrl(process.env.SANITY_INTERNAL_WORKBENCH_REMOTE_URL);
|
|
113
|
-
// The workbench runtime template needs a concrete boolean (it has no studio
|
|
114
|
-
// default to defer to), so collapse an unset config to off.
|
|
115
|
-
const reactStrictMode = resolveReactStrictMode(cliConfig) ?? false;
|
|
116
|
-
const organizationId = resolveOrganizationId(cliConfig);
|
|
117
|
-
devDebug('Writing workbench runtime files');
|
|
118
|
-
const root = await writeWorkbenchRuntime({
|
|
119
|
-
cwd: workDir,
|
|
120
|
-
organizationId,
|
|
121
|
-
reactStrictMode,
|
|
122
|
-
remoteUrl
|
|
123
|
-
});
|
|
124
|
-
const viteConfig = {
|
|
125
|
-
// Custom cache directory so sanity's vite cache doesn't conflict with local vite projects
|
|
126
|
-
cacheDir: `${SANITY_CACHE_DIR}/vite`,
|
|
127
|
-
configFile: false,
|
|
128
|
-
define: {
|
|
129
|
-
__SANITY_STAGING__: process.env.SANITY_INTERNAL_ENV === 'staging',
|
|
130
|
-
'import.meta.env.SANITY_INTERNAL_WORKBENCH_REMOTE_URL': JSON.stringify(remoteUrl)
|
|
131
|
-
},
|
|
132
|
-
logLevel: 'warn',
|
|
133
|
-
mode: 'development',
|
|
134
|
-
optimizeDeps: {
|
|
135
|
-
// Exclude sanity/workbench (and its transitive dep @sanity/workbench)
|
|
136
|
-
// from dep pre-bundling so that `import.meta.hot` is available at
|
|
137
|
-
// runtime — pre-bundled modules do not receive Vite's HMR client
|
|
138
|
-
// injection, which causes the custom HMR events for local application
|
|
139
|
-
// discovery to silently not fire.
|
|
140
|
-
exclude: [
|
|
141
|
-
'sanity',
|
|
142
|
-
'@sanity/workbench'
|
|
143
|
-
]
|
|
144
|
-
},
|
|
145
|
-
// viteReact looks inert here — it transforms none of the host's own modules —
|
|
146
|
-
// but it's load-bearing for the remotes. It serves the Fast Refresh runtime at
|
|
147
|
-
// /@react-refresh and injects the preamble that defines window.$RefreshReg$. The
|
|
148
|
-
// federated remotes loaded into this page are react-refresh transformed, so
|
|
149
|
-
// without the preamble they throw "can't detect preamble", and without the
|
|
150
|
-
// runtime their /@react-refresh import (wired by @module-federation/vite's
|
|
151
|
-
// remoteHmr) fails. Dropping it as dead code broke every panel; see #1262.
|
|
152
|
-
plugins: [
|
|
153
|
-
viteReact(),
|
|
154
|
-
...remoteUrl ? [
|
|
155
|
-
remoteManifestPreloadHeaderPlugin(remoteUrl)
|
|
156
|
-
] : []
|
|
157
|
-
],
|
|
158
|
-
resolve: {
|
|
159
|
-
dedupe: [
|
|
160
|
-
'react',
|
|
161
|
-
'react-dom'
|
|
162
|
-
]
|
|
163
|
-
},
|
|
164
|
-
root,
|
|
165
|
-
server: {
|
|
166
|
-
host: httpHost,
|
|
167
|
-
port: workbenchPort,
|
|
168
|
-
strictPort: false,
|
|
169
|
-
warmup: {
|
|
170
|
-
clientFiles: [
|
|
171
|
-
'./workbench.js'
|
|
172
|
-
]
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
};
|
|
176
|
-
devDebug('Creating workbench vite server');
|
|
177
|
-
const server = await createServer(viteConfig);
|
|
178
|
-
try {
|
|
179
|
-
await server.listen();
|
|
180
|
-
} catch (err) {
|
|
181
|
-
await server.close();
|
|
182
|
-
output.warn(`Workbench dev server failed to start: ${err instanceof Error ? err.message : String(err)}`);
|
|
183
|
-
return undefined;
|
|
184
|
-
}
|
|
185
|
-
// Vite may have picked a different port if the desired one was occupied
|
|
186
|
-
const addr = server.httpServer?.address();
|
|
187
|
-
const actualPort = typeof addr === 'object' && addr ? addr.port : workbenchPort;
|
|
188
|
-
// Fire-and-forget: warm the workbench remote's Vite transform pipeline so
|
|
189
|
-
// the first browser request hits a pre-populated module graph.
|
|
190
|
-
if (remoteUrl) {
|
|
191
|
-
fetch(remoteUrl).then((r)=>r.body?.cancel()).catch(()=>{});
|
|
192
|
-
devDebug('Warming workbench remote at %s', remoteUrl);
|
|
193
|
-
}
|
|
194
|
-
server.ws.on('sanity:workbench:get-local-applications', (_, client)=>{
|
|
195
|
-
client.send('sanity:workbench:local-applications', toApplicationsPayload(getRegisteredServers()));
|
|
196
|
-
});
|
|
197
|
-
// A running app's declared interface set changing (a view/service added or
|
|
198
|
-
// removed in `sanity.cli.ts`) means its remote was rebuilt with new exposes.
|
|
199
|
-
// Module federation has the old remote-entry cached, so an in-place reconcile
|
|
200
|
-
// would load a stale remote (empty panel / no worker) — the page must reload
|
|
201
|
-
// to re-fetch it. A new/removed app, or a manifest-only edit (title/icon),
|
|
202
|
-
// reconciles softly as before. Source-file edits don't change the set, so they
|
|
203
|
-
// stay on the HMR path and never trip a reload here.
|
|
204
|
-
let knownInterfaces = new Map();
|
|
205
|
-
const registryWatcher = watchRegistry((servers)=>{
|
|
206
|
-
const rebuiltApp = servers.some((s)=>{
|
|
207
|
-
const key = serverKey(s);
|
|
208
|
-
return knownInterfaces.has(key) && knownInterfaces.get(key) !== interfaceSetId(s.interfaces);
|
|
209
|
-
});
|
|
210
|
-
knownInterfaces = new Map(servers.map((s)=>[
|
|
211
|
-
serverKey(s),
|
|
212
|
-
interfaceSetId(s.interfaces)
|
|
213
|
-
]));
|
|
214
|
-
if (rebuiltApp) {
|
|
215
|
-
server.ws.send({
|
|
216
|
-
type: 'full-reload'
|
|
217
|
-
});
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
server.ws.send('sanity:workbench:local-applications', toApplicationsPayload(servers));
|
|
221
|
-
});
|
|
222
|
-
return {
|
|
223
|
-
actualPort,
|
|
224
|
-
close: async ()=>{
|
|
225
|
-
registryWatcher.close();
|
|
226
|
-
await server.close();
|
|
227
|
-
}
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
// Workbench is opted into via `unstable_defineApp`, which carries the
|
|
231
|
-
// organization ID. Deliberately no fallback (e.g. resolving it from the
|
|
232
|
-
// configured project): the lookup would need an authenticated user and an
|
|
233
|
-
// API round-trip on every startup for something the opt-in already declares.
|
|
234
|
-
const resolveOrganizationId = (cliConfig)=>{
|
|
235
|
-
if (cliConfig.app?.organizationId) {
|
|
236
|
-
return cliConfig.app.organizationId;
|
|
237
|
-
}
|
|
238
|
-
throw new Error('Workbench requires an organization ID. Pass "organizationId" to unstable_defineApp() in sanity.cli.ts.');
|
|
239
|
-
};
|
|
240
|
-
// Restricts protocol to http(s) so the URL is safe to interpolate into HTML
|
|
241
|
-
// attributes and Link headers downstream.
|
|
242
|
-
const remoteUrlSchema = z.url({
|
|
243
|
-
normalize: true,
|
|
244
|
-
protocol: /^https?$/
|
|
245
|
-
});
|
|
246
|
-
function parseRemoteUrl(value) {
|
|
247
|
-
if (!value) return undefined;
|
|
248
|
-
const result = remoteUrlSchema.safeParse(value);
|
|
249
|
-
if (!result.success) {
|
|
250
|
-
throw new Error(`Invalid SANITY_INTERNAL_WORKBENCH_REMOTE_URL: ${value} (must be an http(s) URL)`);
|
|
251
|
-
}
|
|
252
|
-
return result.data;
|
|
253
|
-
}
|
|
254
|
-
/**
|
|
255
|
-
* Sets a `Link: <remoteUrl>; rel=preload; as=fetch; crossorigin` response header
|
|
256
|
-
* on the index document so the browser can start fetching the Module Federation
|
|
257
|
-
* manifest as soon as response headers arrive — before HTML parsing reaches the
|
|
258
|
-
* in-head preconnect hint. `as=fetch` matches how the federation runtime later
|
|
259
|
-
* retrieves the JSON manifest, allowing the preload entry to satisfy that fetch.
|
|
260
|
-
*/ function remoteManifestPreloadHeaderPlugin(remoteUrl) {
|
|
261
|
-
return {
|
|
262
|
-
apply: 'serve',
|
|
263
|
-
configureServer (server) {
|
|
264
|
-
server.middlewares.use((req, res, next)=>{
|
|
265
|
-
const pathname = (req.url || '/').split('?')[0];
|
|
266
|
-
if (pathname === '/' || pathname === '/index.html') {
|
|
267
|
-
res.setHeader('Link', `<${remoteUrl}>; rel=preload; as=fetch; crossorigin`);
|
|
268
|
-
}
|
|
269
|
-
next();
|
|
270
|
-
});
|
|
271
|
-
},
|
|
272
|
-
name: 'sanity:workbench-remote-preload-header'
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
//# sourceMappingURL=startWorkbenchDevServer.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/actions/dev/workbench/startWorkbenchDevServer.ts"],"sourcesContent":["import {SANITY_CACHE_DIR} from '@sanity/cli-build/_internal/build'\nimport {isWorkbenchApp, resolveLocalPackage} from '@sanity/cli-core'\nimport {\n acquireWorkbenchLock,\n type DevServerManifest,\n getRegisteredServers,\n readWorkbenchLock,\n watchRegistry,\n} from '@sanity/workbench-cli/dev'\nimport viteReact from '@vitejs/plugin-react'\nimport {createServer, type InlineConfig, type Plugin} from 'vite'\nimport {z} from 'zod/mini'\n\nimport {resolveReactStrictMode} from '../../../util/resolveReactStrictMode.js'\nimport {devDebug} from '../devDebug.js'\nimport {interfaceSetId} from '../registration/interfaceSetId.js'\nimport {type DevActionOptions} from '../types.js'\nimport {writeWorkbenchRuntime} from './writeWorkbenchRuntime.js'\n\nconst noop = async () => {}\n\n/** Stable per-app key for the registry-watch interface diff. */\nconst serverKey = (s: DevServerManifest) => `${s.id ?? ''}@${s.host ?? ''}:${s.port}`\n\nconst toApplicationsPayload = (servers: DevServerManifest[]) => ({\n applications: servers.map(({host, id, interfaces, manifest, port, projectId, type}) => ({\n host,\n id,\n interfaces,\n manifest,\n port,\n projectId,\n type,\n })),\n})\n\ninterface WorkbenchDevServerResult {\n close: () => Promise<void>\n httpHost: string | undefined\n workbenchAvailable: boolean\n workbenchPort: number\n}\n\nexport interface StartWorkbenchOptions extends DevActionOptions {\n httpHost: string | undefined\n httpPort: number\n}\n\n/**\n * Start the workbench dev server when federation is enabled and the workbench\n * package is available. If the desired port is already taken — by another\n * workbench instance or an unrelated process — fall back to running without a\n * workbench and let the app/studio dev server claim the configured port.\n */\nexport async function startWorkbenchDevServer(\n options: StartWorkbenchOptions,\n): Promise<WorkbenchDevServerResult> {\n const {cliConfig, httpHost, httpPort: workbenchPort, output, workDir} = options\n\n // Workbench is opted into solely by calling `unstable_defineApp`.\n if (!isWorkbenchApp(cliConfig?.app)) {\n devDebug('Not a workbench app, skipping workbench dev server')\n return {close: noop, httpHost, workbenchAvailable: false, workbenchPort}\n }\n\n let workbenchAvailable = false\n\n try {\n await resolveLocalPackage('sanity/workbench', workDir)\n workbenchAvailable = true\n } catch {\n devDebug('Workbench not available, skipping workbench dev server')\n }\n\n if (!workbenchAvailable) {\n return {close: noop, httpHost, workbenchAvailable, workbenchPort}\n }\n\n // Acquire an exclusive lock — only one workbench per machine.\n // Uses O_EXCL which is atomic at the OS level, preventing races when\n // multiple `sanity dev` processes start simultaneously (e.g. via turbo).\n const workbenchLock = acquireWorkbenchLock({host: httpHost || 'localhost', port: workbenchPort})\n if (!workbenchLock) {\n const existing = readWorkbenchLock()\n devDebug(\n 'Workbench already running at pid %d on port %d, skipping',\n existing?.pid,\n existing?.port,\n )\n return {\n close: noop,\n httpHost: existing?.host ?? httpHost,\n workbenchAvailable: true,\n workbenchPort: existing?.port ?? workbenchPort,\n }\n }\n\n // The lock is already held; an exception here (runtime-file write failure,\n // invalid remote URL) would otherwise leak it until the next acquire prunes\n // the stale PID.\n let result: Awaited<ReturnType<typeof createWorkbenchViteServer>>\n try {\n result = await createWorkbenchViteServer({\n cliConfig,\n httpHost,\n output,\n workbenchPort,\n workDir,\n })\n } catch (err) {\n workbenchLock.release()\n throw err\n }\n\n if (!result) {\n workbenchLock.release()\n return {close: noop, httpHost, workbenchAvailable: false, workbenchPort}\n }\n\n const {actualPort, close} = result\n workbenchLock.updatePort(actualPort)\n\n return {\n close: async () => {\n workbenchLock.release()\n await close()\n },\n httpHost,\n workbenchAvailable,\n workbenchPort: actualPort,\n }\n}\n\ninterface CreateWorkbenchViteServerOptions {\n cliConfig: DevActionOptions['cliConfig']\n httpHost: string | undefined\n output: DevActionOptions['output']\n workbenchPort: number\n workDir: string\n}\n\ninterface CreateWorkbenchViteServerResult {\n actualPort: number\n close: () => Promise<void>\n}\n\nasync function createWorkbenchViteServer(\n options: CreateWorkbenchViteServerOptions,\n): Promise<CreateWorkbenchViteServerResult | undefined> {\n const {cliConfig, httpHost, output, workbenchPort, workDir} = options\n\n const remoteUrl = parseRemoteUrl(process.env.SANITY_INTERNAL_WORKBENCH_REMOTE_URL)\n // The workbench runtime template needs a concrete boolean (it has no studio\n // default to defer to), so collapse an unset config to off.\n const reactStrictMode = resolveReactStrictMode(cliConfig) ?? false\n\n const organizationId = resolveOrganizationId(cliConfig)\n\n devDebug('Writing workbench runtime files')\n const root = await writeWorkbenchRuntime({\n cwd: workDir,\n organizationId,\n reactStrictMode,\n remoteUrl,\n })\n\n const viteConfig: InlineConfig = {\n // Custom cache directory so sanity's vite cache doesn't conflict with local vite projects\n cacheDir: `${SANITY_CACHE_DIR}/vite`,\n configFile: false,\n define: {\n __SANITY_STAGING__: process.env.SANITY_INTERNAL_ENV === 'staging',\n 'import.meta.env.SANITY_INTERNAL_WORKBENCH_REMOTE_URL': JSON.stringify(remoteUrl),\n },\n logLevel: 'warn',\n mode: 'development',\n optimizeDeps: {\n // Exclude sanity/workbench (and its transitive dep @sanity/workbench)\n // from dep pre-bundling so that `import.meta.hot` is available at\n // runtime — pre-bundled modules do not receive Vite's HMR client\n // injection, which causes the custom HMR events for local application\n // discovery to silently not fire.\n exclude: ['sanity', '@sanity/workbench'],\n },\n // viteReact looks inert here — it transforms none of the host's own modules —\n // but it's load-bearing for the remotes. It serves the Fast Refresh runtime at\n // /@react-refresh and injects the preamble that defines window.$RefreshReg$. The\n // federated remotes loaded into this page are react-refresh transformed, so\n // without the preamble they throw \"can't detect preamble\", and without the\n // runtime their /@react-refresh import (wired by @module-federation/vite's\n // remoteHmr) fails. Dropping it as dead code broke every panel; see #1262.\n plugins: [viteReact(), ...(remoteUrl ? [remoteManifestPreloadHeaderPlugin(remoteUrl)] : [])],\n resolve: {dedupe: ['react', 'react-dom']},\n root,\n server: {\n host: httpHost,\n port: workbenchPort,\n strictPort: false,\n warmup: {\n clientFiles: ['./workbench.js'],\n },\n },\n }\n\n devDebug('Creating workbench vite server')\n const server = await createServer(viteConfig)\n try {\n await server.listen()\n } catch (err) {\n await server.close()\n output.warn(\n `Workbench dev server failed to start: ${err instanceof Error ? err.message : String(err)}`,\n )\n return undefined\n }\n\n // Vite may have picked a different port if the desired one was occupied\n const addr = server.httpServer?.address()\n const actualPort = typeof addr === 'object' && addr ? addr.port : workbenchPort\n\n // Fire-and-forget: warm the workbench remote's Vite transform pipeline so\n // the first browser request hits a pre-populated module graph.\n if (remoteUrl) {\n fetch(remoteUrl)\n .then((r) => r.body?.cancel())\n .catch(() => {})\n devDebug('Warming workbench remote at %s', remoteUrl)\n }\n\n server.ws.on('sanity:workbench:get-local-applications', (_, client) => {\n client.send(\n 'sanity:workbench:local-applications',\n toApplicationsPayload(getRegisteredServers()),\n )\n })\n\n // A running app's declared interface set changing (a view/service added or\n // removed in `sanity.cli.ts`) means its remote was rebuilt with new exposes.\n // Module federation has the old remote-entry cached, so an in-place reconcile\n // would load a stale remote (empty panel / no worker) — the page must reload\n // to re-fetch it. A new/removed app, or a manifest-only edit (title/icon),\n // reconciles softly as before. Source-file edits don't change the set, so they\n // stay on the HMR path and never trip a reload here.\n let knownInterfaces = new Map<string, string>()\n const registryWatcher = watchRegistry((servers) => {\n const rebuiltApp = servers.some((s) => {\n const key = serverKey(s)\n return knownInterfaces.has(key) && knownInterfaces.get(key) !== interfaceSetId(s.interfaces)\n })\n knownInterfaces = new Map(servers.map((s) => [serverKey(s), interfaceSetId(s.interfaces)]))\n\n if (rebuiltApp) {\n server.ws.send({type: 'full-reload'})\n return\n }\n server.ws.send('sanity:workbench:local-applications', toApplicationsPayload(servers))\n })\n\n return {\n actualPort,\n close: async () => {\n registryWatcher.close()\n await server.close()\n },\n }\n}\n\n// Workbench is opted into via `unstable_defineApp`, which carries the\n// organization ID. Deliberately no fallback (e.g. resolving it from the\n// configured project): the lookup would need an authenticated user and an\n// API round-trip on every startup for something the opt-in already declares.\nconst resolveOrganizationId = (cliConfig: DevActionOptions['cliConfig']): string => {\n if (cliConfig.app?.organizationId) {\n return cliConfig.app.organizationId\n }\n\n throw new Error(\n 'Workbench requires an organization ID. Pass \"organizationId\" to unstable_defineApp() in sanity.cli.ts.',\n )\n}\n\n// Restricts protocol to http(s) so the URL is safe to interpolate into HTML\n// attributes and Link headers downstream.\nconst remoteUrlSchema = z.url({normalize: true, protocol: /^https?$/})\n\nfunction parseRemoteUrl(value: string | undefined): string | undefined {\n if (!value) return undefined\n\n const result = remoteUrlSchema.safeParse(value)\n\n if (!result.success) {\n throw new Error(\n `Invalid SANITY_INTERNAL_WORKBENCH_REMOTE_URL: ${value} (must be an http(s) URL)`,\n )\n }\n\n return result.data\n}\n\n/**\n * Sets a `Link: <remoteUrl>; rel=preload; as=fetch; crossorigin` response header\n * on the index document so the browser can start fetching the Module Federation\n * manifest as soon as response headers arrive — before HTML parsing reaches the\n * in-head preconnect hint. `as=fetch` matches how the federation runtime later\n * retrieves the JSON manifest, allowing the preload entry to satisfy that fetch.\n */\nfunction remoteManifestPreloadHeaderPlugin(remoteUrl: string): Plugin {\n return {\n apply: 'serve',\n configureServer(server) {\n server.middlewares.use((req, res, next) => {\n const pathname = (req.url || '/').split('?')[0]\n if (pathname === '/' || pathname === '/index.html') {\n res.setHeader('Link', `<${remoteUrl}>; rel=preload; as=fetch; crossorigin`)\n }\n next()\n })\n },\n name: 'sanity:workbench-remote-preload-header',\n }\n}\n"],"names":["SANITY_CACHE_DIR","isWorkbenchApp","resolveLocalPackage","acquireWorkbenchLock","getRegisteredServers","readWorkbenchLock","watchRegistry","viteReact","createServer","z","resolveReactStrictMode","devDebug","interfaceSetId","writeWorkbenchRuntime","noop","serverKey","s","id","host","port","toApplicationsPayload","servers","applications","map","interfaces","manifest","projectId","type","startWorkbenchDevServer","options","cliConfig","httpHost","httpPort","workbenchPort","output","workDir","app","close","workbenchAvailable","workbenchLock","existing","pid","result","createWorkbenchViteServer","err","release","actualPort","updatePort","remoteUrl","parseRemoteUrl","process","env","SANITY_INTERNAL_WORKBENCH_REMOTE_URL","reactStrictMode","organizationId","resolveOrganizationId","root","cwd","viteConfig","cacheDir","configFile","define","__SANITY_STAGING__","SANITY_INTERNAL_ENV","JSON","stringify","logLevel","mode","optimizeDeps","exclude","plugins","remoteManifestPreloadHeaderPlugin","resolve","dedupe","server","strictPort","warmup","clientFiles","listen","warn","Error","message","String","undefined","addr","httpServer","address","fetch","then","r","body","cancel","catch","ws","on","_","client","send","knownInterfaces","Map","registryWatcher","rebuiltApp","some","key","has","get","remoteUrlSchema","url","normalize","protocol","value","safeParse","success","data","apply","configureServer","middlewares","use","req","res","next","pathname","split","setHeader","name"],"mappings":"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"}
|