@sanity/cli 7.2.3 → 7.4.0
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/bin/run.js +3 -12
- package/dist/actions/auth/authServer.js +37 -20
- package/dist/actions/auth/authServer.js.map +1 -1
- package/dist/actions/backup/assertDatasetExist.js +3 -3
- package/dist/actions/backup/assertDatasetExist.js.map +1 -1
- package/dist/actions/build/buildApp.js +15 -5
- package/dist/actions/build/buildApp.js.map +1 -1
- package/dist/actions/build/buildStaticFiles.js +57 -5
- package/dist/actions/build/buildStaticFiles.js.map +1 -1
- package/dist/actions/build/buildStudio.js +13 -5
- package/dist/actions/build/buildStudio.js.map +1 -1
- package/dist/actions/build/handlePrereleaseVersions.js +2 -2
- package/dist/actions/build/handlePrereleaseVersions.js.map +1 -1
- package/dist/actions/dataset/resolveDataset.js +2 -2
- package/dist/actions/dataset/resolveDataset.js.map +1 -1
- package/dist/actions/deploy/deployApp.js +43 -5
- package/dist/actions/deploy/deployApp.js.map +1 -1
- package/dist/actions/deploy/deployStudio.js +14 -2
- package/dist/actions/deploy/deployStudio.js.map +1 -1
- package/dist/actions/deploy/viewDeployment.js +32 -0
- package/dist/actions/deploy/viewDeployment.js.map +1 -0
- package/dist/actions/dev/devAction.js +219 -4
- package/dist/actions/dev/devAction.js.map +1 -1
- package/dist/actions/dev/registration/deriveInterfaces.js +46 -0
- package/dist/actions/dev/registration/deriveInterfaces.js.map +1 -0
- package/dist/actions/dev/registration/extractDevServerManifest.js +30 -0
- package/dist/actions/dev/registration/extractDevServerManifest.js.map +1 -0
- package/dist/actions/dev/registration/interfaceSetId.js +41 -0
- package/dist/actions/dev/registration/interfaceSetId.js.map +1 -0
- package/dist/actions/dev/registration/startDevManifestWatcher.js +104 -0
- package/dist/actions/dev/registration/startDevManifestWatcher.js.map +1 -0
- package/dist/actions/dev/registration/startDevServerRegistration.js +121 -0
- package/dist/actions/dev/registration/startDevServerRegistration.js.map +1 -0
- package/dist/actions/dev/servers/getDashboardAppUrl.js.map +1 -0
- package/dist/actions/dev/servers/getDevServerConfig.js +45 -0
- package/dist/actions/dev/servers/getDevServerConfig.js.map +1 -0
- package/dist/actions/dev/servers/startAppDevServer.js +78 -0
- package/dist/actions/dev/servers/startAppDevServer.js.map +1 -0
- package/dist/actions/dev/{startStudioDevServer.js → servers/startStudioDevServer.js} +20 -16
- package/dist/actions/dev/servers/startStudioDevServer.js.map +1 -0
- package/dist/actions/dev/types.js +7 -1
- package/dist/actions/dev/types.js.map +1 -1
- package/dist/actions/dev/workbench/startWorkbenchDevServer.js +276 -0
- package/dist/actions/dev/workbench/startWorkbenchDevServer.js.map +1 -0
- package/dist/actions/dev/workbench/writeWorkbenchRuntime.js +66 -0
- package/dist/actions/dev/workbench/writeWorkbenchRuntime.js.map +1 -0
- package/dist/actions/init/bootstrapLocalTemplate.js +15 -3
- package/dist/actions/init/bootstrapLocalTemplate.js.map +1 -1
- package/dist/actions/init/bootstrapTemplate.js +3 -2
- package/dist/actions/init/bootstrapTemplate.js.map +1 -1
- package/dist/actions/init/createAppCliConfig.js +5 -2
- package/dist/actions/init/createAppCliConfig.js.map +1 -1
- package/dist/actions/init/createCliConfig.js +5 -3
- package/dist/actions/init/createCliConfig.js.map +1 -1
- package/dist/actions/init/createStudioConfig.js.map +1 -1
- package/dist/actions/init/initAction.js +11 -4
- package/dist/actions/init/initAction.js.map +1 -1
- package/dist/actions/init/initApp.js +2 -1
- package/dist/actions/init/initApp.js.map +1 -1
- package/dist/actions/init/initStudio.js +2 -1
- package/dist/actions/init/initStudio.js.map +1 -1
- package/dist/actions/init/scaffoldTemplate.js +6 -14
- package/dist/actions/init/scaffoldTemplate.js.map +1 -1
- package/dist/actions/init/templates/pageBuilder.js +1 -1
- package/dist/actions/init/templates/pageBuilder.js.map +1 -1
- package/dist/actions/init/types.js +1 -0
- package/dist/actions/init/types.js.map +1 -1
- package/dist/actions/manifest/{extractAppManifest.js → extractCoreAppManifest.js} +30 -11
- package/dist/actions/manifest/extractCoreAppManifest.js.map +1 -0
- package/dist/actions/manifest/extractManifest.js +7 -7
- package/dist/actions/manifest/extractManifest.js.map +1 -1
- package/dist/actions/manifest/iconResolver.js +2 -3
- package/dist/actions/manifest/iconResolver.js.map +1 -1
- package/dist/actions/manifest/sanitizeIcon.js +13 -0
- package/dist/actions/manifest/sanitizeIcon.js.map +1 -0
- package/dist/actions/manifest/types.js +4 -9
- package/dist/actions/manifest/types.js.map +1 -1
- package/dist/actions/manifest/writeManifestFile.js +1 -1
- package/dist/actions/manifest/writeManifestFile.js.map +1 -1
- package/dist/actions/mcp/editorConfigs.js +26 -2
- package/dist/actions/mcp/editorConfigs.js.map +1 -1
- package/dist/actions/mcp/setupMCP.js +24 -25
- package/dist/actions/mcp/setupMCP.js.map +1 -1
- package/dist/actions/mcp/types.js.map +1 -1
- package/dist/actions/skills/configureSkills.js +49 -0
- package/dist/actions/skills/configureSkills.js.map +1 -0
- package/dist/actions/skills/readSkillState.js +40 -5
- package/dist/actions/skills/readSkillState.js.map +1 -1
- package/dist/actions/skills/setupSkills.js +47 -6
- package/dist/actions/skills/setupSkills.js.map +1 -1
- package/dist/actions/skills/skillCandidates.js +40 -0
- package/dist/actions/skills/skillCandidates.js.map +1 -0
- package/dist/actions/telemetry/telemetryDisclosure.js +2 -3
- package/dist/actions/telemetry/telemetryDisclosure.js.map +1 -1
- package/dist/actions/tokens/validateRole.js +3 -3
- package/dist/actions/tokens/validateRole.js.map +1 -1
- package/dist/commands/backups/disable.js +1 -1
- package/dist/commands/backups/disable.js.map +1 -1
- package/dist/commands/backups/download.js +1 -1
- package/dist/commands/backups/download.js.map +1 -1
- package/dist/commands/backups/enable.js +1 -1
- package/dist/commands/backups/enable.js.map +1 -1
- package/dist/commands/backups/list.js +1 -1
- package/dist/commands/backups/list.js.map +1 -1
- package/dist/commands/datasets/embeddings/disable.js +1 -0
- package/dist/commands/datasets/embeddings/disable.js.map +1 -1
- package/dist/commands/datasets/embeddings/enable.js +1 -0
- package/dist/commands/datasets/embeddings/enable.js.map +1 -1
- package/dist/commands/datasets/embeddings/status.js +1 -0
- package/dist/commands/datasets/embeddings/status.js.map +1 -1
- package/dist/commands/dev.js +11 -1
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/init.js +7 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/manifest/extract.js +7 -2
- package/dist/commands/manifest/extract.js.map +1 -1
- package/dist/commands/mcp/configure.js +1 -0
- package/dist/commands/mcp/configure.js.map +1 -1
- package/dist/commands/skills/install.js +40 -0
- package/dist/commands/skills/install.js.map +1 -0
- package/dist/commands/tokens/add.js +1 -1
- package/dist/commands/tokens/add.js.map +1 -1
- package/dist/exports/index.d.ts +6 -0
- package/dist/exports/index.js +8 -0
- package/dist/exports/index.js.map +1 -1
- package/dist/exports/runtime.d.ts +41 -0
- package/dist/exports/runtime.js +13 -0
- package/dist/exports/runtime.js.map +1 -0
- package/dist/hooks/prerun/setupTelemetry.js +3 -1
- package/dist/hooks/prerun/setupTelemetry.js.map +1 -1
- package/dist/server/devServer.js +8 -3
- package/dist/server/devServer.js.map +1 -1
- package/dist/services/documents.js +0 -1
- package/dist/services/documents.js.map +1 -1
- package/dist/telemetry/skills.telemetry.js +8 -0
- package/dist/telemetry/skills.telemetry.js.map +1 -0
- package/dist/util/compareDependencyVersions.js +1 -1
- package/dist/util/compareDependencyVersions.js.map +1 -1
- package/dist/util/determineIsApp.js +12 -1
- package/dist/util/determineIsApp.js.map +1 -1
- package/dist/util/getSharedServerConfig.js +2 -1
- package/dist/util/getSharedServerConfig.js.map +1 -1
- package/dist/util/resolveLatestVersions.js +20 -2
- package/dist/util/resolveLatestVersions.js.map +1 -1
- package/dist/util/resolveReactStrictMode.js +11 -0
- package/dist/util/resolveReactStrictMode.js.map +1 -0
- package/oclif.config.js +2 -1
- package/oclif.manifest.json +146 -113
- package/package.json +29 -23
- package/templates/page-builder/schemaTypes/index.js +1 -1
- package/dist/actions/build/checkRequiredDependencies.js +0 -124
- package/dist/actions/build/checkRequiredDependencies.js.map +0 -1
- package/dist/actions/build/getAutoUpdatesImportMap.js +0 -66
- package/dist/actions/build/getAutoUpdatesImportMap.js.map +0 -1
- package/dist/actions/dev/getDashboardAppUrl.js.map +0 -1
- package/dist/actions/dev/getDevServerConfig.js +0 -32
- package/dist/actions/dev/getDevServerConfig.js.map +0 -1
- package/dist/actions/dev/startAppDevServer.js +0 -59
- package/dist/actions/dev/startAppDevServer.js.map +0 -1
- package/dist/actions/dev/startStudioDevServer.js.map +0 -1
- package/dist/actions/manifest/extractAppManifest.js.map +0 -1
- /package/dist/actions/dev/{getDashboardAppUrl.js → servers/getDashboardAppUrl.js} +0 -0
|
@@ -1,7 +1,222 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { styleText } from 'node:util';
|
|
2
|
+
import { getCliConfigUncached, isWorkbenchApp } from '@sanity/cli-core';
|
|
3
|
+
import { getSharedServerConfig } from '../../util/getSharedServerConfig.js';
|
|
4
|
+
import { startDevServerRegistration } from './registration/startDevServerRegistration.js';
|
|
5
|
+
import { startAppDevServer } from './servers/startAppDevServer.js';
|
|
6
|
+
import { startStudioDevServer } from './servers/startStudioDevServer.js';
|
|
7
|
+
import { startWorkbenchDevServer } from './workbench/startWorkbenchDevServer.js';
|
|
8
|
+
const noop = async ()=>{};
|
|
9
|
+
/**
|
|
10
|
+
* How long a signal-triggered teardown may run before the process force-exits
|
|
11
|
+
* by re-raising the signal — generous enough for Vite servers and watchers to
|
|
12
|
+
* close, short enough not to strand a backgrounded process holding the ports.
|
|
13
|
+
*/ const SHUTDOWN_GRACE_MS = 5000;
|
|
14
|
+
// Bind-only addresses ('0.0.0.0', '::') aren't routable URLs in every
|
|
15
|
+
// browser (notably on Windows), so the displayed URL falls back to
|
|
16
|
+
// localhost. The bind address itself is untouched — listening and the
|
|
17
|
+
// lock file keep whatever the user configured.
|
|
18
|
+
function toDisplayHost(host) {
|
|
19
|
+
if (!host || host === '0.0.0.0' || host === '::' || host === '[::]') {
|
|
20
|
+
return 'localhost';
|
|
21
|
+
}
|
|
22
|
+
return host;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Orchestrates the dev servers required by the process. It will attempt to run a workbench
|
|
26
|
+
* dev-server and, if successful, will run the app/studio dev server on the next available port.
|
|
27
|
+
* If the workbench dev-server fails to start for an expected reason, e.g. because there is already
|
|
28
|
+
* a workbench instance running or the workbench package is unavailable, it will run the app/studio
|
|
29
|
+
* dev server on the configured port.
|
|
30
|
+
*/ export async function devAction(options) {
|
|
31
|
+
const { cliConfig, flags, output, workDir } = options;
|
|
32
|
+
const { httpHost, httpPort } = getSharedServerConfig({
|
|
33
|
+
cliConfig,
|
|
34
|
+
flags: {
|
|
35
|
+
host: flags.host,
|
|
36
|
+
port: flags.port
|
|
37
|
+
},
|
|
38
|
+
workDir
|
|
39
|
+
});
|
|
40
|
+
/**
|
|
41
|
+
* The workbench remote is the app that renders "workbench ready" apps, thereby it can't render itself.
|
|
42
|
+
* But it still wants to be deployed as a singleton in the sanity ecosystem. Therefore, this internal flag
|
|
43
|
+
* allows us to develop the workbench remote correctly – as a standalone app.
|
|
44
|
+
*/ const isWorkbenchRemote = isWorkbenchApp(cliConfig?.app) && process.env.SANITY_INTERNAL_IS_WORKBENCH_REMOTE === 'true';
|
|
45
|
+
const { close: closeWorkbenchServer, httpHost: workbenchHost, workbenchAvailable, workbenchPort } = isWorkbenchRemote ? {
|
|
46
|
+
close: noop,
|
|
47
|
+
httpHost,
|
|
48
|
+
workbenchAvailable: false,
|
|
49
|
+
workbenchPort: httpPort
|
|
50
|
+
} : await startWorkbenchDevServer({
|
|
51
|
+
...options,
|
|
52
|
+
httpHost,
|
|
53
|
+
httpPort
|
|
54
|
+
});
|
|
55
|
+
// A running workbench claims the configured port, so the app server binds the
|
|
56
|
+
// next one — passed explicitly rather than by rewriting the shared flags.
|
|
57
|
+
// Without a workbench the app server resolves its port from flags/env/config
|
|
58
|
+
// downstream, exactly as it did before workbench existed.
|
|
59
|
+
const appOptions = workbenchAvailable ? {
|
|
60
|
+
...options,
|
|
61
|
+
httpPort: workbenchPort + 1,
|
|
62
|
+
workbenchAvailable
|
|
63
|
+
} : {
|
|
64
|
+
...options,
|
|
65
|
+
workbenchAvailable
|
|
66
|
+
};
|
|
67
|
+
let closeAppDevServer = noop;
|
|
68
|
+
// Takes the config as a param so a rebuild can feed a freshly-loaded one.
|
|
69
|
+
const startApp = async (config)=>{
|
|
70
|
+
const result = options.isApp ? await startAppDevServer({
|
|
71
|
+
...appOptions,
|
|
72
|
+
cliConfig: config
|
|
73
|
+
}) : await startStudioDevServer({
|
|
74
|
+
...appOptions,
|
|
75
|
+
cliConfig: config
|
|
76
|
+
});
|
|
77
|
+
closeAppDevServer = result.started ? result.close : noop;
|
|
78
|
+
return result;
|
|
79
|
+
};
|
|
80
|
+
let initial;
|
|
81
|
+
try {
|
|
82
|
+
initial = await startApp(cliConfig);
|
|
83
|
+
} catch (err) {
|
|
84
|
+
await closeWorkbenchServer();
|
|
85
|
+
throw err;
|
|
86
|
+
}
|
|
87
|
+
if (!initial.started) {
|
|
88
|
+
// The server has already reported why (e.g. missing organization ID).
|
|
89
|
+
return {
|
|
90
|
+
close: closeWorkbenchServer
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const { server } = initial;
|
|
94
|
+
// Adding/removing a view or service in `sanity.cli.ts` during dev requires
|
|
95
|
+
// rebuilding the federation remote: its module-federation `exposes` map +
|
|
96
|
+
// codegen artifacts are computed once at server start, so a newly-declared
|
|
97
|
+
// interface has no expose until the server is recreated. `server.restart()`
|
|
98
|
+
// can't do it — it re-uses the inline config — so we tear the app server
|
|
99
|
+
// down and bring it back up with a freshly-loaded config. The workbench page
|
|
100
|
+
// then reloads (driven by the registry watch in the workbench server) to
|
|
101
|
+
// re-fetch the rebuilt remote. A view/service *source* edit doesn't change
|
|
102
|
+
// the interface set, so it stays on the HMR path untouched. Studios declare
|
|
103
|
+
// views/services the same way (only `entry` is rejected, FR-026), so they
|
|
104
|
+
// get the same rebuild. The returned-server / thrown-failed-restart contract
|
|
105
|
+
// is documented on `onInterfaceSetChange` in startDevServerRegistration.
|
|
106
|
+
// Declared ahead of `runRebuild`, which the watcher's initial background
|
|
107
|
+
// extraction can invoke before the statements below it have run.
|
|
108
|
+
let closed = false;
|
|
109
|
+
const runRebuild = async ()=>{
|
|
110
|
+
// The watcher only learns about shutdown when its own close runs, late in
|
|
111
|
+
// the teardown sequence — refuse here so a config save during shutdown
|
|
112
|
+
// can't boot a replacement server nobody owns. Checked before the first
|
|
113
|
+
// await so a rebuild can't start after close() has read `rebuildInFlight`.
|
|
114
|
+
if (closed) {
|
|
115
|
+
throw new Error('Dev server is shutting down');
|
|
116
|
+
}
|
|
117
|
+
const freshConfig = await getCliConfigUncached(workDir);
|
|
118
|
+
await closeAppDevServer();
|
|
119
|
+
const result = await startApp(freshConfig);
|
|
120
|
+
if (!result.started) {
|
|
121
|
+
// The server already reported why (e.g. organizationId was removed).
|
|
122
|
+
throw new Error('Dev server did not restart after the view/service change');
|
|
123
|
+
}
|
|
124
|
+
return result.server;
|
|
125
|
+
};
|
|
126
|
+
// `closeAppDevServer` is repointed at the replacement server only after
|
|
127
|
+
// `startApp` resolves — a close() racing a rebuild would tear down the old
|
|
128
|
+
// (already-closed) server and leave the replacement running with no owner,
|
|
129
|
+
// so close() waits on the rebuild in flight. Rejections are owned by the
|
|
130
|
+
// watcher (warn + retry on next save); the tracked copy swallows them so
|
|
131
|
+
// close() can't reject.
|
|
132
|
+
let rebuildInFlight = Promise.resolve();
|
|
133
|
+
const onInterfaceSetChange = ()=>{
|
|
134
|
+
const rebuild = runRebuild();
|
|
135
|
+
rebuildInFlight = rebuild.catch(()=>{});
|
|
136
|
+
return rebuild;
|
|
137
|
+
};
|
|
138
|
+
// Workbench is opted into solely by calling `unstable_defineApp` — its
|
|
139
|
+
// branded identity is the only signal. The workbench remote is the exception:
|
|
140
|
+
// it's the shell itself, not a dock app, so it never registers into the
|
|
141
|
+
// shared workbench registry.
|
|
142
|
+
let registration;
|
|
143
|
+
try {
|
|
144
|
+
registration = isWorkbenchApp(cliConfig?.app) && !isWorkbenchRemote ? await startDevServerRegistration({
|
|
145
|
+
cliConfig,
|
|
146
|
+
isApp: options.isApp,
|
|
147
|
+
onInterfaceSetChange,
|
|
148
|
+
output,
|
|
149
|
+
server,
|
|
150
|
+
workDir
|
|
151
|
+
}) : undefined;
|
|
152
|
+
} catch (err) {
|
|
153
|
+
// Registration runs only after both servers are already up. If it throws
|
|
154
|
+
// (e.g. `deriveInterfaces` rejects), tear them down before rethrowing —
|
|
155
|
+
// otherwise the workbench lock and dev servers leak until the next run.
|
|
156
|
+
await Promise.allSettled([
|
|
157
|
+
closeWorkbenchServer(),
|
|
158
|
+
closeAppDevServer()
|
|
159
|
+
]);
|
|
160
|
+
throw err;
|
|
161
|
+
}
|
|
162
|
+
if (workbenchAvailable) {
|
|
163
|
+
const workbenchUrl = `http://${toDisplayHost(workbenchHost)}:${workbenchPort}`;
|
|
164
|
+
const addr = server.httpServer?.address();
|
|
165
|
+
const appPort = typeof addr === 'object' && addr ? addr.port : server.config.server.port;
|
|
166
|
+
output.log(`Workbench dev server started at ${styleText([
|
|
167
|
+
'blue',
|
|
168
|
+
'underline'
|
|
169
|
+
], workbenchUrl)} (app on port ${appPort})`);
|
|
170
|
+
}
|
|
171
|
+
// Single-flight: SIGINT followed by SIGTERM (each signal has its own `once`
|
|
172
|
+
// handler), or a signal racing the caller's own close(), must share one
|
|
173
|
+
// teardown instead of double-closing every server.
|
|
174
|
+
let closing;
|
|
175
|
+
const close = ()=>{
|
|
176
|
+
closing ??= (async ()=>{
|
|
177
|
+
closed = true;
|
|
178
|
+
process.off('SIGINT', onSignal);
|
|
179
|
+
process.off('SIGTERM', onSignal);
|
|
180
|
+
await rebuildInFlight;
|
|
181
|
+
await Promise.allSettled([
|
|
182
|
+
registration?.close(),
|
|
183
|
+
closeWorkbenchServer(),
|
|
184
|
+
closeAppDevServer()
|
|
185
|
+
]);
|
|
186
|
+
})();
|
|
187
|
+
return closing;
|
|
188
|
+
};
|
|
189
|
+
// Ensure the workbench lock file and registry entries are cleaned up on
|
|
190
|
+
// abrupt shutdown. The registry is self-healing (stale PIDs are pruned on
|
|
191
|
+
// next read), but eager cleanup avoids the detect-prune-retry cycle.
|
|
192
|
+
// Plain projects have no lock or registry entry, so they keep the default
|
|
193
|
+
// signal handling (and exit codes) they had before workbench existed.
|
|
194
|
+
//
|
|
195
|
+
// Trapping the signal disables Node's default exit, and a finished teardown
|
|
196
|
+
// doesn't guarantee an empty event loop (keep-alive sockets, an extraction
|
|
197
|
+
// worker mid-run) — without an explicit exit the process lingers, holding
|
|
198
|
+
// its ports, while the shell prompt returns. Re-raising after teardown
|
|
199
|
+
// restores the default termination (the `once` handler is gone by then),
|
|
200
|
+
// with conventional signal exit semantics.
|
|
201
|
+
const onSignal = (signal)=>{
|
|
202
|
+
// Backstop for a teardown that never settles (a wedged socket or watcher):
|
|
203
|
+
// force the exit once the grace period elapses. Cleared on the normal path
|
|
204
|
+
// so the re-raise fires exactly once; `unref` keeps the timer from holding
|
|
205
|
+
// the process open by itself.
|
|
206
|
+
const graceTimer = setTimeout(()=>process.kill(process.pid, signal), SHUTDOWN_GRACE_MS);
|
|
207
|
+
graceTimer.unref();
|
|
208
|
+
void close().finally(()=>{
|
|
209
|
+
clearTimeout(graceTimer);
|
|
210
|
+
process.kill(process.pid, signal);
|
|
211
|
+
});
|
|
212
|
+
};
|
|
213
|
+
if (workbenchAvailable || registration) {
|
|
214
|
+
process.once('SIGINT', onSignal);
|
|
215
|
+
process.once('SIGTERM', onSignal);
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
close
|
|
219
|
+
};
|
|
5
220
|
}
|
|
6
221
|
|
|
7
222
|
//# sourceMappingURL=devAction.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/dev/devAction.ts"],"sourcesContent":["import {startAppDevServer} from './startAppDevServer.js'\nimport {startStudioDevServer} from './startStudioDevServer.js'\nimport {type DevActionOptions} from './types.js'\n\nexport function devAction(options: DevActionOptions): Promise<{close?: () => Promise<void>}> {\n return options.isApp ? startAppDevServer(options) : startStudioDevServer(options)\n}\n"],"names":["startAppDevServer","startStudioDevServer","devAction","options","isApp"],"mappings":"AAAA,SAAQA,iBAAiB,QAAO,yBAAwB;AACxD,SAAQC,oBAAoB,QAAO,4BAA2B;AAG9D,OAAO,SAASC,UAAUC,OAAyB;IACjD,OAAOA,QAAQC,KAAK,GAAGJ,kBAAkBG,WAAWF,qBAAqBE;AAC3E"}
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/dev/devAction.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {getCliConfigUncached, isWorkbenchApp} from '@sanity/cli-core'\nimport {type ViteDevServer} from 'vite'\n\nimport {getSharedServerConfig} from '../../util/getSharedServerConfig.js'\nimport {startDevServerRegistration} from './registration/startDevServerRegistration.js'\nimport {startAppDevServer} from './servers/startAppDevServer.js'\nimport {startStudioDevServer} from './servers/startStudioDevServer.js'\nimport {type DevActionOptions, type StartDevServerResult} from './types.js'\nimport {startWorkbenchDevServer} from './workbench/startWorkbenchDevServer.js'\n\nconst noop = async () => {}\n\n/**\n * How long a signal-triggered teardown may run before the process force-exits\n * by re-raising the signal — generous enough for Vite servers and watchers to\n * close, short enough not to strand a backgrounded process holding the ports.\n */\nconst SHUTDOWN_GRACE_MS = 5000\n\n// Bind-only addresses ('0.0.0.0', '::') aren't routable URLs in every\n// browser (notably on Windows), so the displayed URL falls back to\n// localhost. The bind address itself is untouched — listening and the\n// lock file keep whatever the user configured.\nfunction toDisplayHost(host: string | undefined): string {\n if (!host || host === '0.0.0.0' || host === '::' || host === '[::]') {\n return 'localhost'\n }\n return host\n}\n\n/**\n * Orchestrates the dev servers required by the process. It will attempt to run a workbench\n * dev-server and, if successful, will run the app/studio dev server on the next available port.\n * If the workbench dev-server fails to start for an expected reason, e.g. because there is already\n * a workbench instance running or the workbench package is unavailable, it will run the app/studio\n * dev server on the configured port.\n */\nexport async function devAction(options: DevActionOptions): Promise<{close: () => Promise<void>}> {\n const {cliConfig, flags, output, workDir} = options\n\n const {httpHost, httpPort} = getSharedServerConfig({\n cliConfig,\n flags: {host: flags.host, port: flags.port},\n workDir,\n })\n\n /**\n * The workbench remote is the app that renders \"workbench ready\" apps, thereby it can't render itself.\n * But it still wants to be deployed as a singleton in the sanity ecosystem. Therefore, this internal flag\n * allows us to develop the workbench remote correctly – as a standalone app.\n */\n const isWorkbenchRemote =\n isWorkbenchApp(cliConfig?.app) && process.env.SANITY_INTERNAL_IS_WORKBENCH_REMOTE === 'true'\n\n const {\n close: closeWorkbenchServer,\n httpHost: workbenchHost,\n workbenchAvailable,\n workbenchPort,\n } = isWorkbenchRemote\n ? {close: noop, httpHost, workbenchAvailable: false, workbenchPort: httpPort}\n : await startWorkbenchDevServer({...options, httpHost, httpPort})\n\n // A running workbench claims the configured port, so the app server binds the\n // next one — passed explicitly rather than by rewriting the shared flags.\n // Without a workbench the app server resolves its port from flags/env/config\n // downstream, exactly as it did before workbench existed.\n const appOptions: DevActionOptions = workbenchAvailable\n ? {...options, httpPort: workbenchPort + 1, workbenchAvailable}\n : {...options, workbenchAvailable}\n\n let closeAppDevServer: () => Promise<void> = noop\n\n // Takes the config as a param so a rebuild can feed a freshly-loaded one.\n const startApp = async (config: DevActionOptions['cliConfig']): Promise<StartDevServerResult> => {\n const result = options.isApp\n ? await startAppDevServer({...appOptions, cliConfig: config})\n : await startStudioDevServer({...appOptions, cliConfig: config})\n closeAppDevServer = result.started ? result.close : noop\n return result\n }\n\n let initial: StartDevServerResult\n try {\n initial = await startApp(cliConfig)\n } catch (err) {\n await closeWorkbenchServer()\n throw err\n }\n\n if (!initial.started) {\n // The server has already reported why (e.g. missing organization ID).\n return {close: closeWorkbenchServer}\n }\n const {server} = initial\n\n // Adding/removing a view or service in `sanity.cli.ts` during dev requires\n // rebuilding the federation remote: its module-federation `exposes` map +\n // codegen artifacts are computed once at server start, so a newly-declared\n // interface has no expose until the server is recreated. `server.restart()`\n // can't do it — it re-uses the inline config — so we tear the app server\n // down and bring it back up with a freshly-loaded config. The workbench page\n // then reloads (driven by the registry watch in the workbench server) to\n // re-fetch the rebuilt remote. A view/service *source* edit doesn't change\n // the interface set, so it stays on the HMR path untouched. Studios declare\n // views/services the same way (only `entry` is rejected, FR-026), so they\n // get the same rebuild. The returned-server / thrown-failed-restart contract\n // is documented on `onInterfaceSetChange` in startDevServerRegistration.\n\n // Declared ahead of `runRebuild`, which the watcher's initial background\n // extraction can invoke before the statements below it have run.\n let closed = false\n\n const runRebuild = async (): Promise<ViteDevServer> => {\n // The watcher only learns about shutdown when its own close runs, late in\n // the teardown sequence — refuse here so a config save during shutdown\n // can't boot a replacement server nobody owns. Checked before the first\n // await so a rebuild can't start after close() has read `rebuildInFlight`.\n if (closed) {\n throw new Error('Dev server is shutting down')\n }\n const freshConfig = await getCliConfigUncached(workDir)\n await closeAppDevServer()\n const result = await startApp(freshConfig)\n if (!result.started) {\n // The server already reported why (e.g. organizationId was removed).\n throw new Error('Dev server did not restart after the view/service change')\n }\n return result.server\n }\n\n // `closeAppDevServer` is repointed at the replacement server only after\n // `startApp` resolves — a close() racing a rebuild would tear down the old\n // (already-closed) server and leave the replacement running with no owner,\n // so close() waits on the rebuild in flight. Rejections are owned by the\n // watcher (warn + retry on next save); the tracked copy swallows them so\n // close() can't reject.\n let rebuildInFlight: Promise<unknown> = Promise.resolve()\n const onInterfaceSetChange = (): Promise<ViteDevServer> => {\n const rebuild = runRebuild()\n rebuildInFlight = rebuild.catch(() => {})\n return rebuild\n }\n\n // Workbench is opted into solely by calling `unstable_defineApp` — its\n // branded identity is the only signal. The workbench remote is the exception:\n // it's the shell itself, not a dock app, so it never registers into the\n // shared workbench registry.\n let registration: Awaited<ReturnType<typeof startDevServerRegistration>> | undefined\n try {\n registration =\n isWorkbenchApp(cliConfig?.app) && !isWorkbenchRemote\n ? await startDevServerRegistration({\n cliConfig,\n isApp: options.isApp,\n onInterfaceSetChange,\n output,\n server,\n workDir,\n })\n : undefined\n } catch (err) {\n // Registration runs only after both servers are already up. If it throws\n // (e.g. `deriveInterfaces` rejects), tear them down before rethrowing —\n // otherwise the workbench lock and dev servers leak until the next run.\n await Promise.allSettled([closeWorkbenchServer(), closeAppDevServer()])\n throw err\n }\n\n if (workbenchAvailable) {\n const workbenchUrl = `http://${toDisplayHost(workbenchHost)}:${workbenchPort}`\n const addr = server.httpServer?.address()\n const appPort = typeof addr === 'object' && addr ? addr.port : server.config.server.port\n output.log(\n `Workbench dev server started at ${styleText(['blue', 'underline'], workbenchUrl)} (app on port ${appPort})`,\n )\n }\n\n // Single-flight: SIGINT followed by SIGTERM (each signal has its own `once`\n // handler), or a signal racing the caller's own close(), must share one\n // teardown instead of double-closing every server.\n let closing: Promise<void> | undefined\n const close = () => {\n closing ??= (async () => {\n closed = true\n process.off('SIGINT', onSignal)\n process.off('SIGTERM', onSignal)\n await rebuildInFlight\n await Promise.allSettled([registration?.close(), closeWorkbenchServer(), closeAppDevServer()])\n })()\n return closing\n }\n\n // Ensure the workbench lock file and registry entries are cleaned up on\n // abrupt shutdown. The registry is self-healing (stale PIDs are pruned on\n // next read), but eager cleanup avoids the detect-prune-retry cycle.\n // Plain projects have no lock or registry entry, so they keep the default\n // signal handling (and exit codes) they had before workbench existed.\n //\n // Trapping the signal disables Node's default exit, and a finished teardown\n // doesn't guarantee an empty event loop (keep-alive sockets, an extraction\n // worker mid-run) — without an explicit exit the process lingers, holding\n // its ports, while the shell prompt returns. Re-raising after teardown\n // restores the default termination (the `once` handler is gone by then),\n // with conventional signal exit semantics.\n const onSignal = (signal: NodeJS.Signals) => {\n // Backstop for a teardown that never settles (a wedged socket or watcher):\n // force the exit once the grace period elapses. Cleared on the normal path\n // so the re-raise fires exactly once; `unref` keeps the timer from holding\n // the process open by itself.\n const graceTimer = setTimeout(() => process.kill(process.pid, signal), SHUTDOWN_GRACE_MS)\n graceTimer.unref()\n void close().finally(() => {\n clearTimeout(graceTimer)\n process.kill(process.pid, signal)\n })\n }\n if (workbenchAvailable || registration) {\n process.once('SIGINT', onSignal)\n process.once('SIGTERM', onSignal)\n }\n\n return {close}\n}\n"],"names":["styleText","getCliConfigUncached","isWorkbenchApp","getSharedServerConfig","startDevServerRegistration","startAppDevServer","startStudioDevServer","startWorkbenchDevServer","noop","SHUTDOWN_GRACE_MS","toDisplayHost","host","devAction","options","cliConfig","flags","output","workDir","httpHost","httpPort","port","isWorkbenchRemote","app","process","env","SANITY_INTERNAL_IS_WORKBENCH_REMOTE","close","closeWorkbenchServer","workbenchHost","workbenchAvailable","workbenchPort","appOptions","closeAppDevServer","startApp","config","result","isApp","started","initial","err","server","closed","runRebuild","Error","freshConfig","rebuildInFlight","Promise","resolve","onInterfaceSetChange","rebuild","catch","registration","undefined","allSettled","workbenchUrl","addr","httpServer","address","appPort","log","closing","off","onSignal","signal","graceTimer","setTimeout","kill","pid","unref","finally","clearTimeout","once"],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { join, resolve } from 'node:path';
|
|
3
|
+
import { SANITY_CACHE_DIR } from '@sanity/cli-build/_internal/build';
|
|
4
|
+
import { extractManifest } from '../../manifest/extractManifest.js';
|
|
5
|
+
import { MANIFEST_FILENAME } from '../../manifest/writeManifestFile.js';
|
|
6
|
+
/**
|
|
7
|
+
* Dev-time manifest output directory, relative to the studio working
|
|
8
|
+
* directory. Sibling of Vite's `cacheDir` so it stays out of `dist` and is
|
|
9
|
+
* ignored by default in typical `.gitignore` files.
|
|
10
|
+
*/ const MANIFEST_DIR = `${SANITY_CACHE_DIR}/manifest`;
|
|
11
|
+
/**
|
|
12
|
+
* Run the worker-based studio schema extraction, write the resulting manifest
|
|
13
|
+
* to `MANIFEST_DIR`, then read it back so the caller can inline it into the
|
|
14
|
+
* registry.
|
|
15
|
+
*
|
|
16
|
+
* `configPath` must be the resolved `sanity.config.(ts|js)` path — passing it
|
|
17
|
+
* in (e.g. from `findProjectRoot`) avoids re-traversing the filesystem on
|
|
18
|
+
* every call.
|
|
19
|
+
*/ export async function extractStudioManifest(options) {
|
|
20
|
+
const outPath = resolve(options.workDir, MANIFEST_DIR);
|
|
21
|
+
await extractManifest({
|
|
22
|
+
outPath,
|
|
23
|
+
path: options.configPath,
|
|
24
|
+
workDir: options.workDir
|
|
25
|
+
});
|
|
26
|
+
const raw = await readFile(join(outPath, MANIFEST_FILENAME), 'utf8');
|
|
27
|
+
return JSON.parse(raw);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
//# sourceMappingURL=extractDevServerManifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,104 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|