@sanity/workbench-cli 2.4.1 → 2.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_exports/dev.d.ts +1 -1
- package/dist/_exports/preview.d.ts +1 -1
- package/dist/_exports/{registry-BCLMbrla.d.ts → registry-CIoKFIDw.d.ts} +8 -1
- package/dist/actions/dev/registry.js +7 -0
- package/dist/actions/dev/registry.js.map +1 -1
- package/dist/actions/dev/startDevServerRegistration.js +3 -0
- package/dist/actions/dev/startDevServerRegistration.js.map +1 -1
- package/dist/actions/dev/startWorkbenchDevServer.js +7 -3
- package/dist/actions/dev/startWorkbenchDevServer.js.map +1 -1
- package/dist/actions/preview/startWorkbenchPreview.js +3 -0
- package/dist/actions/preview/startWorkbenchPreview.js.map +1 -1
- package/dist/services/installations.js +2 -2
- package/dist/services/installations.js.map +1 -1
- package/package.json +2 -2
package/dist/_exports/dev.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as DevServerManifest } from "./registry-
|
|
1
|
+
import { t as DevServerManifest } from "./registry-CIoKFIDw.js";
|
|
2
2
|
import { CliConfig, Output } from "@sanity/cli-core";
|
|
3
3
|
interface StartWorkbenchPreviewOptions {
|
|
4
4
|
/** Directory for the workbench Vite server's dependency cache. */
|
|
@@ -101,16 +101,23 @@ declare const devServerManifestSchema: z.ZodMiniObject<{
|
|
|
101
101
|
}, z.core.$strip>]>>;
|
|
102
102
|
manifestUpdatedAt: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
103
103
|
name: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
104
|
+
organizationId: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
104
105
|
pid: z.ZodMiniNumber<number>;
|
|
105
106
|
port: z.ZodMiniNumber<number>;
|
|
106
107
|
projectId: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
107
108
|
reference: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
109
|
+
slug: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
108
110
|
startedAt: z.ZodMiniString<string>;
|
|
109
111
|
type: z.ZodMiniEnum<{
|
|
110
112
|
coreApp: "coreApp";
|
|
111
113
|
studio: "studio";
|
|
112
114
|
}>;
|
|
113
115
|
version: z.ZodMiniLiteral<2>;
|
|
116
|
+
visibility: z.ZodMiniOptional<z.ZodMiniEnum<{
|
|
117
|
+
default: "default";
|
|
118
|
+
unlisted: "unlisted";
|
|
119
|
+
disabled: "disabled";
|
|
120
|
+
}>>;
|
|
114
121
|
workDir: z.ZodMiniString<string>;
|
|
115
122
|
}, z.core.$strip>;
|
|
116
123
|
/**
|
|
@@ -122,4 +129,4 @@ declare const devServerManifestSchema: z.ZodMiniObject<{
|
|
|
122
129
|
*/
|
|
123
130
|
type DevServerManifest = z.infer<typeof devServerManifestSchema>;
|
|
124
131
|
export { DevServerManifest as t };
|
|
125
|
-
//# sourceMappingURL=registry-
|
|
132
|
+
//# sourceMappingURL=registry-CIoKFIDw.d.ts.map
|
|
@@ -116,16 +116,23 @@ const devServerManifestSchema = z.object({
|
|
|
116
116
|
// Stable identity + qualified reference, composed by the CLI (the authority for
|
|
117
117
|
// local apps, which never reach brett) and read straight by the workbench.
|
|
118
118
|
name: z.optional(z.string()),
|
|
119
|
+
organizationId: z.optional(z.string()),
|
|
119
120
|
pid: z.number(),
|
|
120
121
|
port: z.number(),
|
|
121
122
|
projectId: z.optional(z.string()),
|
|
122
123
|
reference: z.optional(z.string()),
|
|
124
|
+
slug: z.optional(z.string()),
|
|
123
125
|
startedAt: z.string(),
|
|
124
126
|
type: z.enum([
|
|
125
127
|
'coreApp',
|
|
126
128
|
'studio'
|
|
127
129
|
]),
|
|
128
130
|
version: z.literal(REGISTRY_VERSION),
|
|
131
|
+
visibility: z.optional(z.enum([
|
|
132
|
+
'default',
|
|
133
|
+
'unlisted',
|
|
134
|
+
'disabled'
|
|
135
|
+
])),
|
|
129
136
|
workDir: z.string()
|
|
130
137
|
});
|
|
131
138
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/dev/registry.ts"],"sourcesContent":["import {\n existsSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n unlinkSync,\n watch,\n writeFileSync,\n} from 'node:fs'\nimport {join} from 'node:path'\n\nimport {\n coreAppManifestSchema,\n getSanityDataDir,\n studioManifestSchema,\n subdebug,\n} from '@sanity/cli-core'\nimport {z} from 'zod/mini'\n\nimport {TileInterfaceMetadataSchema, ViewPlacementMetadataSchema} from '../../contract.js'\nimport {canonicalizeWatchDir} from './canonicalizeWatchDir.js'\nimport {getProcessStartTime, isOurProcess} from './processLiveness.js'\n\n/**\n * The dev-server registry: how a running `sanity dev` / `sanity start` process\n * advertises itself so the workbench on this machine can find and load it.\n *\n * Two kinds of file under `~/.sanity/dev-servers/` do the coordinating:\n *\n * - `<pid>.json` — one per running app/studio server, holding where it's served\n * plus its inlined manifest and interfaces. The workbench reads these to\n * discover and render local apps. Written by `registerDevServer`, watched by\n * `watchRegistry`.\n * - `workbench.lock` — a single machine-wide lock, so only one workbench shell\n * runs at a time and later `dev`s register into it instead of starting their\n * own. Managed by `acquireWorkbenchLock` / `readWorkbenchLock`.\n *\n * Both files belong to the process that created them and must not outlive it.\n * Three things keep that true: an explicit `release()` on clean shutdown, an\n * `exit` backstop for abrupt exits (`unlinkOnProcessExit`), and a dead-pid prune\n * on read (`isOurProcess`) that clears whatever a crashed process left behind.\n */\n\nconst devDebug = subdebug('dev')\n\n/** Bump when the manifest/lock shape changes in a breaking way. */\nconst REGISTRY_VERSION = 2\n\n/**\n * The current process's start time as reported by the OS, for the `startedAt`\n * that `isOurProcess` checks on re-read. Falls back to now when the OS time is\n * unavailable — `new Date()` alone records the write time, which drifts from\n * process start by enough to look stale and get pruned right after writing.\n */\nfunction ownStartedAt(): string {\n return (getProcessStartTime(process.pid) ?? new Date()).toISOString()\n}\n\nconst interfaceBaseFields = {\n /** CLI-minted for a local interface; a deployed one gets its id from Brett. */\n id: z.string(),\n moduleId: z.string(),\n name: z.string(),\n /** Raw source vite serves; a deployed interface carries only the `moduleId`. */\n src: z.string(),\n title: z.string(),\n version: z.optional(z.string()),\n}\n\n/**\n * A forwarded interface. Kept outside the manifest so\n * the workbench renders local panels and runs workers without a deploy.\n */\nconst devServerInterfaceSchema = z.union([\n z.discriminatedUnion('surface', [\n z.object({\n ...interfaceBaseFields,\n metadata: z.nullable(ViewPlacementMetadataSchema),\n surface: z.literal('window'),\n }),\n z.object({\n ...interfaceBaseFields,\n metadata: z.nullable(ViewPlacementMetadataSchema),\n surface: z.literal('panel'),\n }),\n z.object({...interfaceBaseFields, metadata: z.null(), surface: z.literal('asset_source')}),\n z.object({\n ...interfaceBaseFields,\n metadata: TileInterfaceMetadataSchema,\n surface: z.literal('tile'),\n }),\n ]),\n z.object({...interfaceBaseFields, metadata: z.null(), type: z.literal('worker')}),\n])\n\nconst devServerManifestSchema = z.object({\n /**\n * Field schema *values* load from the federation module; each field's `src`\n * rides along so a repoint bumps the exposes-set id and forces a rebuild.\n * Lenient — the workbench is the authority.\n */\n configs: z.optional(\n z.array(\n z.object({\n // Identifies the owning app when it has no app id (singletons).\n appType: z.optional(z.string()),\n fields: z.array(\n z.object({\n name: z.string(),\n public: z.optional(z.boolean()),\n src: z.string(),\n title: z.string(),\n }),\n ),\n // Content hash of the config — the workbench's change-detection key\n // (see deriveConfigs).\n id: z.string(),\n // The app's `defineApplication` slug — the module-federation alias the\n // workbench loads this config's live values from.\n moduleName: z.optional(z.string()),\n // The version the workbench federates this config's module under —\n // a string, like the one Brett returns on a deployed `activeConfig`.\n version: z.string(),\n }),\n ),\n ),\n host: z.string(),\n id: z.optional(z.string()),\n interfaces: z.optional(z.array(devServerInterfaceSchema)),\n /**\n * Inlined manifest — either a {@link StudioManifest} or {@link CoreAppManifest},\n * validated against the shared cli-core schemas. The registry stores and\n * rebroadcasts it; the CLI is what extracts and writes it.\n */\n manifest: z.optional(z.union([studioManifestSchema, coreAppManifestSchema])),\n /**\n * ISO timestamp of the most recent successful manifest extraction. Bumped\n * on every regeneration so re-writing this registry entry triggers the\n * workbench `watchRegistry` watcher and forces a rebroadcast to clients.\n */\n manifestUpdatedAt: z.optional(z.string()),\n // Stable identity + qualified reference, composed by the CLI (the authority for\n // local apps, which never reach brett) and read straight by the workbench.\n name: z.optional(z.string()),\n pid: z.number(),\n port: z.number(),\n projectId: z.optional(z.string()),\n reference: z.optional(z.string()),\n startedAt: z.string(),\n type: z.enum(['coreApp', 'studio']),\n version: z.literal(REGISTRY_VERSION),\n workDir: z.string(),\n})\n/**\n * A manifest describing a running dev server process (studio or app).\n * Stored as `~/.sanity/dev-servers/<pid>.json`.\n *\n * The workbench singleton is tracked separately via the lock file — see\n * `acquireWorkbenchLock` and `readWorkbenchLock` below.\n */\nexport type DevServerManifest = z.infer<typeof devServerManifestSchema>\n\n/**\n * A config-only server carries configs but no interfaces — e.g. a\n * media-library config app under development. The workbench never routes it\n * as an app; only its configs are published. It therefore plays a different\n * role than an app server, and the two may share a slug (a config app\n * developed alongside the locally served singleton it configures).\n */\nexport function isConfigOnlyServer(\n server: Pick<DevServerManifest, 'configs' | 'interfaces'>,\n): boolean {\n return Boolean(server.configs?.length) && !server.interfaces?.length\n}\n\n/**\n * Path to the dev server registry directory. Lives under the shared Sanity\n * config directory to stay consistent with other CLI paths.\n */\nfunction getRegistryDir(): string {\n return join(getSanityDataDir(), 'dev-servers')\n}\n\n// One shared `exit` listener drives every registered cleanup, so N locks/entries\n// don't each add a listener and trip Node's MaxListeners warning.\nconst exitCleanups = new Set<() => void>()\nlet exitListenerInstalled = false\n\nfunction runExitCleanups(): void {\n for (const cleanup of exitCleanups) cleanup()\n}\n\n/** Exercise the exit backstop in tests without terminating the process; not part\n * of the package's public surface. */\nexport const runRegistryExitCleanupForTesting = runExitCleanups\n\n/**\n * Delete a registry file synchronously on process exit, as a backstop for abrupt\n * termination. Vite installs its own SIGTERM handler that calls `process.exit()`,\n * which can outrun the async server teardown and leave the lock or registry entry\n * behind — a stray dev-server that lingers until the dead-pid prune clears it. The\n * `exit` event only runs synchronous work, hence `unlinkSync`. `ownedByUs` guards\n * the shared lock so a successor that reacquired it isn't wiped. Returns a\n * detacher to call after a clean release.\n */\nfunction unlinkOnProcessExit(filePath: string, ownedByUs: () => boolean): () => void {\n const cleanup = () => {\n if (!ownedByUs()) return\n try {\n unlinkSync(filePath)\n } catch {\n // The file may already have been removed during shutdown.\n }\n }\n exitCleanups.add(cleanup)\n\n if (!exitListenerInstalled) {\n exitListenerInstalled = true\n process.once('exit', runExitCleanups)\n }\n\n return () => exitCleanups.delete(cleanup)\n}\n\ninterface DevServerRegistration {\n /** Remove the registry entry. */\n release: () => void\n /**\n * Rewrite the registry entry with partial updates merged in. Also bumps the\n * file's mtime, which fires `watchRegistry` in any workbench process and\n * triggers a rebroadcast to connected clients.\n */\n update: (patch: Partial<Omit<DevServerManifest, 'pid' | 'startedAt' | 'version'>>) => void\n}\n\n/**\n * Write a manifest file for the current process and return a handle with a\n * `release` function that removes it plus an `update` function for patching\n * fields post-registration. Uses synchronous I/O so the file exists before\n * any signal handler could fire.\n */\nexport function registerDevServer(\n manifest: Omit<DevServerManifest, 'pid' | 'startedAt' | 'version'>,\n): DevServerRegistration {\n const registryDir = getRegistryDir()\n mkdirSync(registryDir, {recursive: true})\n\n let current: DevServerManifest = {\n ...manifest,\n pid: process.pid,\n startedAt: ownStartedAt(),\n version: REGISTRY_VERSION,\n }\n\n const filePath = join(registryDir, `${process.pid}.json`)\n writeFileSync(filePath, JSON.stringify(current, null, 2))\n\n // Guard against late updates from background tasks (e.g. the initial\n // manifest extraction) landing after `release()` has deleted the file —\n // without this, the update would re-create the registry entry and leak.\n let released = false\n\n // The file is pid-named, so it's always ours to remove on exit.\n const detachExitCleanup = unlinkOnProcessExit(filePath, () => !released)\n\n return {\n release() {\n released = true\n detachExitCleanup()\n try {\n unlinkSync(filePath)\n } catch {\n // ENOENT is fine — already cleaned up\n }\n },\n update(patch) {\n if (released) return\n current = {...current, ...patch}\n writeFileSync(filePath, JSON.stringify(current, null, 2))\n },\n }\n}\n\n/**\n * Read all manifest files from the registry, prune stale entries (dead PIDs),\n * and return the live ones.\n */\nexport function getRegisteredServers(): DevServerManifest[] {\n const registryDir = getRegistryDir()\n\n if (!existsSync(registryDir)) {\n return []\n }\n\n const files = readdirSync(registryDir).filter((f) => f.endsWith('.json'))\n const servers: DevServerManifest[] = []\n\n for (const file of files) {\n const filePath = join(registryDir, file)\n let raw: unknown\n try {\n raw = JSON.parse(readFileSync(filePath, 'utf8'))\n } catch {\n continue\n }\n\n const {data, success} = devServerManifestSchema.safeParse(raw)\n if (!success) continue\n\n if (isOurProcess(data.pid, data.startedAt)) {\n servers.push(data)\n } else {\n try {\n unlinkSync(filePath)\n } catch {\n // Ignore — another process may have already cleaned it up\n }\n }\n }\n\n return servers\n}\n\ninterface RegistryWatcher {\n close(): void\n}\n\n/**\n * Watch the registry directory for changes and invoke the callback with the\n * current list of live servers whenever a change is detected.\n *\n * Uses `fs.watch` with a debounce to coalesce rapid file changes (e.g. a\n * server starting and writing its manifest triggers multiple FS events).\n */\nexport function watchRegistry(callback: (servers: DevServerManifest[]) => void): RegistryWatcher {\n const registryDir = getRegistryDir()\n mkdirSync(registryDir, {recursive: true})\n\n // Canonicalize to the real long path so `fs.watch` doesn't abort on Windows\n // short-path dirs. See `canonicalizeWatchDir`.\n const watchDir = canonicalizeWatchDir(registryDir)\n\n let debounceTimer: ReturnType<typeof setTimeout> | undefined\n\n const notify = () => {\n clearTimeout(debounceTimer)\n debounceTimer = setTimeout(() => {\n callback(getRegisteredServers())\n }, 50)\n }\n\n const watcher = watch(watchDir, notify)\n\n return {\n close() {\n clearTimeout(debounceTimer)\n watcher.close()\n },\n }\n}\n\n// The workbench singleton lock — \"one workbench per machine\". Lives in the same\n// registry dir and shares the liveness/prune model: a stale lock left by a\n// crashed process is pruned on read so the next acquire isn't blocked forever.\n\nconst workbenchLockSchema = z.object({\n host: z.string(),\n pid: z.number(),\n port: z.number(),\n startedAt: z.string(),\n version: z.literal(REGISTRY_VERSION),\n})\n\n/**\n * Read the workbench lock file and return its contents if the holding\n * process is still alive. Prunes stale locks from crashed processes.\n */\nexport function readWorkbenchLock(): z.infer<typeof workbenchLockSchema> | undefined {\n const lockPath = join(getRegistryDir(), 'workbench.lock')\n\n let contents: string\n try {\n contents = readFileSync(lockPath, 'utf8')\n } catch {\n // File doesn't exist — nothing to prune, nothing to return\n return undefined\n }\n\n // Past this point the file exists. Anything that isn't a live, valid lock\n // (unparsable JSON, schema mismatch, dead/reused PID) is stale and must be\n // pruned — otherwise the next `acquireWorkbenchLock` call is blocked by\n // EEXIST forever and `sanity dev` silently no-ops the workbench server.\n const data = parseLockContents(contents)\n devDebug('Read workbench lock: %o', data)\n if (data && isOurProcess(data.pid, data.startedAt)) {\n devDebug('Workbench process is alive at pid %d on port %d', data.pid, data.port)\n return data\n }\n\n pruneWorkbenchLock(lockPath)\n return undefined\n}\n\nfunction parseLockContents(contents: string): z.infer<typeof workbenchLockSchema> | undefined {\n try {\n const {data, success} = workbenchLockSchema.safeParse(JSON.parse(contents))\n return success ? data : undefined\n } catch {\n return undefined\n }\n}\n\nfunction pruneWorkbenchLock(lockPath: string): void {\n try {\n devDebug('Removing stale workbench lock')\n unlinkSync(lockPath)\n devDebug('Stale workbench lock removed')\n } catch {\n // Another process may have already cleaned it up\n }\n}\n\ninterface WorkbenchLock {\n /** Release the lock file. */\n release: () => void\n /** Update the lock with the actual port after the server starts listening. */\n updatePort: (port: number) => void\n}\n\n/**\n * Attempt to acquire an exclusive lock for the workbench process.\n * Uses `O_EXCL` (the `wx` flag) which is atomic at the OS level — only one\n * process can create the file.\n *\n * The lock stores `{pid, host, port}` so other processes can find the\n * running workbench. Call `updatePort` after the Vite server starts to\n * write the actual port (Vite may pick a different one).\n *\n * @returns A {@link WorkbenchLock} if acquired, or `undefined` if another\n * live process already holds it.\n */\nexport function acquireWorkbenchLock(\n info: {host: string; port: number},\n retries = 1,\n): WorkbenchLock | undefined {\n const registryDir = getRegistryDir()\n mkdirSync(registryDir, {recursive: true})\n\n const lockPath = join(registryDir, 'workbench.lock')\n const startedAt = ownStartedAt()\n const lockData = {\n host: info.host,\n pid: process.pid,\n port: info.port,\n startedAt,\n version: REGISTRY_VERSION,\n }\n\n devDebug('Acquiring workbench lock at %s', lockPath)\n\n try {\n writeFileSync(lockPath, JSON.stringify(lockData), {flag: 'wx'})\n devDebug('Workbench lock acquired')\n\n let released = false\n // Only wipe the lock on exit if it's still ours — a successor that reacquired\n // it after our own release must not be clobbered.\n const detachExitCleanup = unlinkOnProcessExit(lockPath, () => {\n if (released) return false\n try {\n const disk = parseLockContents(readFileSync(lockPath, 'utf8'))\n return disk?.pid === process.pid && disk.startedAt === startedAt\n } catch {\n return false\n }\n })\n\n return {\n release() {\n released = true\n detachExitCleanup()\n try {\n unlinkSync(lockPath)\n } catch {\n // Already cleaned up\n }\n },\n updatePort(port: number) {\n writeFileSync(lockPath, JSON.stringify({...lockData, port}))\n },\n }\n } catch (err: unknown) {\n devDebug(\n 'Failed to acquire workbench lock: %s',\n err instanceof Error ? err.message : String(err),\n )\n if (!isNodeError(err) || err.code !== 'EEXIST') return undefined\n\n // Lock exists — check if the holder is still alive\n const existing = readWorkbenchLock()\n if (existing) return undefined\n\n // Stale lock was pruned by readWorkbenchLock — retry (with guard against infinite recursion)\n if (retries <= 0) return undefined\n return acquireWorkbenchLock(info, retries - 1)\n }\n}\n\nfunction isNodeError(err: unknown): err is NodeJS.ErrnoException {\n return err instanceof Error && 'code' in err\n}\n"],"names":["existsSync","mkdirSync","readdirSync","readFileSync","unlinkSync","watch","writeFileSync","join","coreAppManifestSchema","getSanityDataDir","studioManifestSchema","subdebug","z","TileInterfaceMetadataSchema","ViewPlacementMetadataSchema","canonicalizeWatchDir","getProcessStartTime","isOurProcess","devDebug","REGISTRY_VERSION","ownStartedAt","process","pid","Date","toISOString","interfaceBaseFields","id","string","moduleId","name","src","title","version","optional","devServerInterfaceSchema","union","discriminatedUnion","object","metadata","nullable","surface","literal","null","type","devServerManifestSchema","configs","array","appType","fields","public","boolean","moduleName","host","interfaces","manifest","manifestUpdatedAt","number","port","projectId","reference","startedAt","enum","workDir","isConfigOnlyServer","server","Boolean","length","getRegistryDir","exitCleanups","Set","exitListenerInstalled","runExitCleanups","cleanup","runRegistryExitCleanupForTesting","unlinkOnProcessExit","filePath","ownedByUs","add","once","delete","registerDevServer","registryDir","recursive","current","JSON","stringify","released","detachExitCleanup","release","update","patch","getRegisteredServers","files","filter","f","endsWith","servers","file","raw","parse","data","success","safeParse","push","watchRegistry","callback","watchDir","debounceTimer","notify","clearTimeout","setTimeout","watcher","close","workbenchLockSchema","readWorkbenchLock","lockPath","contents","undefined","parseLockContents","pruneWorkbenchLock","acquireWorkbenchLock","info","retries","lockData","flag","disk","updatePort","err","Error","message","String","isNodeError","code","existing"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/dev/registry.ts"],"sourcesContent":["import {\n existsSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n unlinkSync,\n watch,\n writeFileSync,\n} from 'node:fs'\nimport {join} from 'node:path'\n\nimport {\n coreAppManifestSchema,\n getSanityDataDir,\n studioManifestSchema,\n subdebug,\n} from '@sanity/cli-core'\nimport {z} from 'zod/mini'\n\nimport {TileInterfaceMetadataSchema, ViewPlacementMetadataSchema} from '../../contract.js'\nimport {canonicalizeWatchDir} from './canonicalizeWatchDir.js'\nimport {getProcessStartTime, isOurProcess} from './processLiveness.js'\n\n/**\n * The dev-server registry: how a running `sanity dev` / `sanity start` process\n * advertises itself so the workbench on this machine can find and load it.\n *\n * Two kinds of file under `~/.sanity/dev-servers/` do the coordinating:\n *\n * - `<pid>.json` — one per running app/studio server, holding where it's served\n * plus its inlined manifest and interfaces. The workbench reads these to\n * discover and render local apps. Written by `registerDevServer`, watched by\n * `watchRegistry`.\n * - `workbench.lock` — a single machine-wide lock, so only one workbench shell\n * runs at a time and later `dev`s register into it instead of starting their\n * own. Managed by `acquireWorkbenchLock` / `readWorkbenchLock`.\n *\n * Both files belong to the process that created them and must not outlive it.\n * Three things keep that true: an explicit `release()` on clean shutdown, an\n * `exit` backstop for abrupt exits (`unlinkOnProcessExit`), and a dead-pid prune\n * on read (`isOurProcess`) that clears whatever a crashed process left behind.\n */\n\nconst devDebug = subdebug('dev')\n\n/** Bump when the manifest/lock shape changes in a breaking way. */\nconst REGISTRY_VERSION = 2\n\n/**\n * The current process's start time as reported by the OS, for the `startedAt`\n * that `isOurProcess` checks on re-read. Falls back to now when the OS time is\n * unavailable — `new Date()` alone records the write time, which drifts from\n * process start by enough to look stale and get pruned right after writing.\n */\nfunction ownStartedAt(): string {\n return (getProcessStartTime(process.pid) ?? new Date()).toISOString()\n}\n\nconst interfaceBaseFields = {\n /** CLI-minted for a local interface; a deployed one gets its id from Brett. */\n id: z.string(),\n moduleId: z.string(),\n name: z.string(),\n /** Raw source vite serves; a deployed interface carries only the `moduleId`. */\n src: z.string(),\n title: z.string(),\n version: z.optional(z.string()),\n}\n\n/**\n * A forwarded interface. Kept outside the manifest so\n * the workbench renders local panels and runs workers without a deploy.\n */\nconst devServerInterfaceSchema = z.union([\n z.discriminatedUnion('surface', [\n z.object({\n ...interfaceBaseFields,\n metadata: z.nullable(ViewPlacementMetadataSchema),\n surface: z.literal('window'),\n }),\n z.object({\n ...interfaceBaseFields,\n metadata: z.nullable(ViewPlacementMetadataSchema),\n surface: z.literal('panel'),\n }),\n z.object({...interfaceBaseFields, metadata: z.null(), surface: z.literal('asset_source')}),\n z.object({\n ...interfaceBaseFields,\n metadata: TileInterfaceMetadataSchema,\n surface: z.literal('tile'),\n }),\n ]),\n z.object({...interfaceBaseFields, metadata: z.null(), type: z.literal('worker')}),\n])\n\nconst devServerManifestSchema = z.object({\n /**\n * Field schema *values* load from the federation module; each field's `src`\n * rides along so a repoint bumps the exposes-set id and forces a rebuild.\n * Lenient — the workbench is the authority.\n */\n configs: z.optional(\n z.array(\n z.object({\n // Identifies the owning app when it has no app id (singletons).\n appType: z.optional(z.string()),\n fields: z.array(\n z.object({\n name: z.string(),\n public: z.optional(z.boolean()),\n src: z.string(),\n title: z.string(),\n }),\n ),\n // Content hash of the config — the workbench's change-detection key\n // (see deriveConfigs).\n id: z.string(),\n // The app's `defineApplication` slug — the module-federation alias the\n // workbench loads this config's live values from.\n moduleName: z.optional(z.string()),\n // The version the workbench federates this config's module under —\n // a string, like the one Brett returns on a deployed `activeConfig`.\n version: z.string(),\n }),\n ),\n ),\n host: z.string(),\n id: z.optional(z.string()),\n interfaces: z.optional(z.array(devServerInterfaceSchema)),\n /**\n * Inlined manifest — either a {@link StudioManifest} or {@link CoreAppManifest},\n * validated against the shared cli-core schemas. The registry stores and\n * rebroadcasts it; the CLI is what extracts and writes it.\n */\n manifest: z.optional(z.union([studioManifestSchema, coreAppManifestSchema])),\n /**\n * ISO timestamp of the most recent successful manifest extraction. Bumped\n * on every regeneration so re-writing this registry entry triggers the\n * workbench `watchRegistry` watcher and forces a rebroadcast to clients.\n */\n manifestUpdatedAt: z.optional(z.string()),\n // Stable identity + qualified reference, composed by the CLI (the authority for\n // local apps, which never reach brett) and read straight by the workbench.\n name: z.optional(z.string()),\n organizationId: z.optional(z.string()),\n pid: z.number(),\n port: z.number(),\n projectId: z.optional(z.string()),\n reference: z.optional(z.string()),\n slug: z.optional(z.string()),\n startedAt: z.string(),\n type: z.enum(['coreApp', 'studio']),\n version: z.literal(REGISTRY_VERSION),\n visibility: z.optional(z.enum(['default', 'unlisted', 'disabled'])),\n workDir: z.string(),\n})\n/**\n * A manifest describing a running dev server process (studio or app).\n * Stored as `~/.sanity/dev-servers/<pid>.json`.\n *\n * The workbench singleton is tracked separately via the lock file — see\n * `acquireWorkbenchLock` and `readWorkbenchLock` below.\n */\nexport type DevServerManifest = z.infer<typeof devServerManifestSchema>\n\n/**\n * A config-only server carries configs but no interfaces — e.g. a\n * media-library config app under development. The workbench never routes it\n * as an app; only its configs are published. It therefore plays a different\n * role than an app server, and the two may share a slug (a config app\n * developed alongside the locally served singleton it configures).\n */\nexport function isConfigOnlyServer(\n server: Pick<DevServerManifest, 'configs' | 'interfaces'>,\n): boolean {\n return Boolean(server.configs?.length) && !server.interfaces?.length\n}\n\n/**\n * Path to the dev server registry directory. Lives under the shared Sanity\n * config directory to stay consistent with other CLI paths.\n */\nfunction getRegistryDir(): string {\n return join(getSanityDataDir(), 'dev-servers')\n}\n\n// One shared `exit` listener drives every registered cleanup, so N locks/entries\n// don't each add a listener and trip Node's MaxListeners warning.\nconst exitCleanups = new Set<() => void>()\nlet exitListenerInstalled = false\n\nfunction runExitCleanups(): void {\n for (const cleanup of exitCleanups) cleanup()\n}\n\n/** Exercise the exit backstop in tests without terminating the process; not part\n * of the package's public surface. */\nexport const runRegistryExitCleanupForTesting = runExitCleanups\n\n/**\n * Delete a registry file synchronously on process exit, as a backstop for abrupt\n * termination. Vite installs its own SIGTERM handler that calls `process.exit()`,\n * which can outrun the async server teardown and leave the lock or registry entry\n * behind — a stray dev-server that lingers until the dead-pid prune clears it. The\n * `exit` event only runs synchronous work, hence `unlinkSync`. `ownedByUs` guards\n * the shared lock so a successor that reacquired it isn't wiped. Returns a\n * detacher to call after a clean release.\n */\nfunction unlinkOnProcessExit(filePath: string, ownedByUs: () => boolean): () => void {\n const cleanup = () => {\n if (!ownedByUs()) return\n try {\n unlinkSync(filePath)\n } catch {\n // The file may already have been removed during shutdown.\n }\n }\n exitCleanups.add(cleanup)\n\n if (!exitListenerInstalled) {\n exitListenerInstalled = true\n process.once('exit', runExitCleanups)\n }\n\n return () => exitCleanups.delete(cleanup)\n}\n\ninterface DevServerRegistration {\n /** Remove the registry entry. */\n release: () => void\n /**\n * Rewrite the registry entry with partial updates merged in. Also bumps the\n * file's mtime, which fires `watchRegistry` in any workbench process and\n * triggers a rebroadcast to connected clients.\n */\n update: (patch: Partial<Omit<DevServerManifest, 'pid' | 'startedAt' | 'version'>>) => void\n}\n\n/**\n * Write a manifest file for the current process and return a handle with a\n * `release` function that removes it plus an `update` function for patching\n * fields post-registration. Uses synchronous I/O so the file exists before\n * any signal handler could fire.\n */\nexport function registerDevServer(\n manifest: Omit<DevServerManifest, 'pid' | 'startedAt' | 'version'>,\n): DevServerRegistration {\n const registryDir = getRegistryDir()\n mkdirSync(registryDir, {recursive: true})\n\n let current: DevServerManifest = {\n ...manifest,\n pid: process.pid,\n startedAt: ownStartedAt(),\n version: REGISTRY_VERSION,\n }\n\n const filePath = join(registryDir, `${process.pid}.json`)\n writeFileSync(filePath, JSON.stringify(current, null, 2))\n\n // Guard against late updates from background tasks (e.g. the initial\n // manifest extraction) landing after `release()` has deleted the file —\n // without this, the update would re-create the registry entry and leak.\n let released = false\n\n // The file is pid-named, so it's always ours to remove on exit.\n const detachExitCleanup = unlinkOnProcessExit(filePath, () => !released)\n\n return {\n release() {\n released = true\n detachExitCleanup()\n try {\n unlinkSync(filePath)\n } catch {\n // ENOENT is fine — already cleaned up\n }\n },\n update(patch) {\n if (released) return\n current = {...current, ...patch}\n writeFileSync(filePath, JSON.stringify(current, null, 2))\n },\n }\n}\n\n/**\n * Read all manifest files from the registry, prune stale entries (dead PIDs),\n * and return the live ones.\n */\nexport function getRegisteredServers(): DevServerManifest[] {\n const registryDir = getRegistryDir()\n\n if (!existsSync(registryDir)) {\n return []\n }\n\n const files = readdirSync(registryDir).filter((f) => f.endsWith('.json'))\n const servers: DevServerManifest[] = []\n\n for (const file of files) {\n const filePath = join(registryDir, file)\n let raw: unknown\n try {\n raw = JSON.parse(readFileSync(filePath, 'utf8'))\n } catch {\n continue\n }\n\n const {data, success} = devServerManifestSchema.safeParse(raw)\n if (!success) continue\n\n if (isOurProcess(data.pid, data.startedAt)) {\n servers.push(data)\n } else {\n try {\n unlinkSync(filePath)\n } catch {\n // Ignore — another process may have already cleaned it up\n }\n }\n }\n\n return servers\n}\n\ninterface RegistryWatcher {\n close(): void\n}\n\n/**\n * Watch the registry directory for changes and invoke the callback with the\n * current list of live servers whenever a change is detected.\n *\n * Uses `fs.watch` with a debounce to coalesce rapid file changes (e.g. a\n * server starting and writing its manifest triggers multiple FS events).\n */\nexport function watchRegistry(callback: (servers: DevServerManifest[]) => void): RegistryWatcher {\n const registryDir = getRegistryDir()\n mkdirSync(registryDir, {recursive: true})\n\n // Canonicalize to the real long path so `fs.watch` doesn't abort on Windows\n // short-path dirs. See `canonicalizeWatchDir`.\n const watchDir = canonicalizeWatchDir(registryDir)\n\n let debounceTimer: ReturnType<typeof setTimeout> | undefined\n\n const notify = () => {\n clearTimeout(debounceTimer)\n debounceTimer = setTimeout(() => {\n callback(getRegisteredServers())\n }, 50)\n }\n\n const watcher = watch(watchDir, notify)\n\n return {\n close() {\n clearTimeout(debounceTimer)\n watcher.close()\n },\n }\n}\n\n// The workbench singleton lock — \"one workbench per machine\". Lives in the same\n// registry dir and shares the liveness/prune model: a stale lock left by a\n// crashed process is pruned on read so the next acquire isn't blocked forever.\n\nconst workbenchLockSchema = z.object({\n host: z.string(),\n pid: z.number(),\n port: z.number(),\n startedAt: z.string(),\n version: z.literal(REGISTRY_VERSION),\n})\n\n/**\n * Read the workbench lock file and return its contents if the holding\n * process is still alive. Prunes stale locks from crashed processes.\n */\nexport function readWorkbenchLock(): z.infer<typeof workbenchLockSchema> | undefined {\n const lockPath = join(getRegistryDir(), 'workbench.lock')\n\n let contents: string\n try {\n contents = readFileSync(lockPath, 'utf8')\n } catch {\n // File doesn't exist — nothing to prune, nothing to return\n return undefined\n }\n\n // Past this point the file exists. Anything that isn't a live, valid lock\n // (unparsable JSON, schema mismatch, dead/reused PID) is stale and must be\n // pruned — otherwise the next `acquireWorkbenchLock` call is blocked by\n // EEXIST forever and `sanity dev` silently no-ops the workbench server.\n const data = parseLockContents(contents)\n devDebug('Read workbench lock: %o', data)\n if (data && isOurProcess(data.pid, data.startedAt)) {\n devDebug('Workbench process is alive at pid %d on port %d', data.pid, data.port)\n return data\n }\n\n pruneWorkbenchLock(lockPath)\n return undefined\n}\n\nfunction parseLockContents(contents: string): z.infer<typeof workbenchLockSchema> | undefined {\n try {\n const {data, success} = workbenchLockSchema.safeParse(JSON.parse(contents))\n return success ? data : undefined\n } catch {\n return undefined\n }\n}\n\nfunction pruneWorkbenchLock(lockPath: string): void {\n try {\n devDebug('Removing stale workbench lock')\n unlinkSync(lockPath)\n devDebug('Stale workbench lock removed')\n } catch {\n // Another process may have already cleaned it up\n }\n}\n\ninterface WorkbenchLock {\n /** Release the lock file. */\n release: () => void\n /** Update the lock with the actual port after the server starts listening. */\n updatePort: (port: number) => void\n}\n\n/**\n * Attempt to acquire an exclusive lock for the workbench process.\n * Uses `O_EXCL` (the `wx` flag) which is atomic at the OS level — only one\n * process can create the file.\n *\n * The lock stores `{pid, host, port}` so other processes can find the\n * running workbench. Call `updatePort` after the Vite server starts to\n * write the actual port (Vite may pick a different one).\n *\n * @returns A {@link WorkbenchLock} if acquired, or `undefined` if another\n * live process already holds it.\n */\nexport function acquireWorkbenchLock(\n info: {host: string; port: number},\n retries = 1,\n): WorkbenchLock | undefined {\n const registryDir = getRegistryDir()\n mkdirSync(registryDir, {recursive: true})\n\n const lockPath = join(registryDir, 'workbench.lock')\n const startedAt = ownStartedAt()\n const lockData = {\n host: info.host,\n pid: process.pid,\n port: info.port,\n startedAt,\n version: REGISTRY_VERSION,\n }\n\n devDebug('Acquiring workbench lock at %s', lockPath)\n\n try {\n writeFileSync(lockPath, JSON.stringify(lockData), {flag: 'wx'})\n devDebug('Workbench lock acquired')\n\n let released = false\n // Only wipe the lock on exit if it's still ours — a successor that reacquired\n // it after our own release must not be clobbered.\n const detachExitCleanup = unlinkOnProcessExit(lockPath, () => {\n if (released) return false\n try {\n const disk = parseLockContents(readFileSync(lockPath, 'utf8'))\n return disk?.pid === process.pid && disk.startedAt === startedAt\n } catch {\n return false\n }\n })\n\n return {\n release() {\n released = true\n detachExitCleanup()\n try {\n unlinkSync(lockPath)\n } catch {\n // Already cleaned up\n }\n },\n updatePort(port: number) {\n writeFileSync(lockPath, JSON.stringify({...lockData, port}))\n },\n }\n } catch (err: unknown) {\n devDebug(\n 'Failed to acquire workbench lock: %s',\n err instanceof Error ? err.message : String(err),\n )\n if (!isNodeError(err) || err.code !== 'EEXIST') return undefined\n\n // Lock exists — check if the holder is still alive\n const existing = readWorkbenchLock()\n if (existing) return undefined\n\n // Stale lock was pruned by readWorkbenchLock — retry (with guard against infinite recursion)\n if (retries <= 0) return undefined\n return acquireWorkbenchLock(info, retries - 1)\n }\n}\n\nfunction isNodeError(err: unknown): err is NodeJS.ErrnoException {\n return err instanceof Error && 'code' in err\n}\n"],"names":["existsSync","mkdirSync","readdirSync","readFileSync","unlinkSync","watch","writeFileSync","join","coreAppManifestSchema","getSanityDataDir","studioManifestSchema","subdebug","z","TileInterfaceMetadataSchema","ViewPlacementMetadataSchema","canonicalizeWatchDir","getProcessStartTime","isOurProcess","devDebug","REGISTRY_VERSION","ownStartedAt","process","pid","Date","toISOString","interfaceBaseFields","id","string","moduleId","name","src","title","version","optional","devServerInterfaceSchema","union","discriminatedUnion","object","metadata","nullable","surface","literal","null","type","devServerManifestSchema","configs","array","appType","fields","public","boolean","moduleName","host","interfaces","manifest","manifestUpdatedAt","organizationId","number","port","projectId","reference","slug","startedAt","enum","visibility","workDir","isConfigOnlyServer","server","Boolean","length","getRegistryDir","exitCleanups","Set","exitListenerInstalled","runExitCleanups","cleanup","runRegistryExitCleanupForTesting","unlinkOnProcessExit","filePath","ownedByUs","add","once","delete","registerDevServer","registryDir","recursive","current","JSON","stringify","released","detachExitCleanup","release","update","patch","getRegisteredServers","files","filter","f","endsWith","servers","file","raw","parse","data","success","safeParse","push","watchRegistry","callback","watchDir","debounceTimer","notify","clearTimeout","setTimeout","watcher","close","workbenchLockSchema","readWorkbenchLock","lockPath","contents","undefined","parseLockContents","pruneWorkbenchLock","acquireWorkbenchLock","info","retries","lockData","flag","disk","updatePort","err","Error","message","String","isNodeError","code","existing"],"mappings":"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"}
|
|
@@ -73,10 +73,13 @@ import { startDevManifestWatcher } from './startDevManifestWatcher.js';
|
|
|
73
73
|
id,
|
|
74
74
|
interfaces,
|
|
75
75
|
name,
|
|
76
|
+
organizationId: workbenchApp?.organizationId,
|
|
76
77
|
port: appPort,
|
|
77
78
|
projectId: cliConfig?.api?.projectId,
|
|
78
79
|
reference,
|
|
80
|
+
slug: workbenchApp?.slug,
|
|
79
81
|
type: isApp ? 'coreApp' : 'studio',
|
|
82
|
+
visibility: workbenchApp ? workbenchApp.visibility ?? 'default' : undefined,
|
|
80
83
|
workDir
|
|
81
84
|
});
|
|
82
85
|
const exposesSet = trackExposesSet({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/dev/startDevServerRegistration.ts"],"sourcesContent":["import {type CliConfig, getCliConfigUncached, type Output} from '@sanity/cli-core'\nimport {type ViteDevServer} from 'vite'\n\nimport {applicationReference} from '../../applicationReference.js'\nimport {isWorkbenchApp, isWorkbenchConfig} from '../../defineApp.js'\nimport {deriveInterfaces} from '../../deriveInterfaces.js'\nimport {resolveWorkbenchConfig} from '../../resolveWorkbenchConfig.js'\nimport {formatWorkbenchAppErrors, validateWorkbenchApp} from '../../validateWorkbenchApp.js'\nimport {deriveConfigs} from './deriveConfigs.js'\nimport {trackExposesSet} from './exposesSetId.js'\nimport {type DevServerManifest, getRegisteredServers, registerDevServer} from './registry.js'\nimport {startDevManifestWatcher} from './startDevManifestWatcher.js'\n\ninterface DevServerRegistrationOptions {\n cliConfig: CliConfig\n /**\n * Extract the project manifest to inline into the registry. The caller owns the\n * studio-vs-app split (manifest formats are CLI-domain); registration re-derives\n * the interface set alongside it.\n */\n extractManifest: (params: {\n applicationId?: string\n configPath: string\n workDir: string\n }) => Promise<DevServerManifest['manifest']>\n isApp: boolean\n output: Output\n server: ViteDevServer\n workDir: string\n\n /**\n * Rebuild the app's federation remote when its interface set changes, awaited\n * *before* the registry patch — the patch reloads the workbench page, which must\n * re-fetch a remote that already exposes the new interface. Resolves with the\n * recreated server so the entry gets its actual address (non-strict ports may\n * shift it); must reject if the restart produces no server, so the set stays\n * uncommitted and the next save retries instead of advertising a dead port.\n */\n onInterfaceSetChange?: () => Promise<ViteDevServer>\n}\n\ninterface DevServerRegistrationHandle {\n close: () => Promise<void>\n}\n\n/**\n * Log any app validation errors without aborting. Unlike build and deploy, dev\n * stays up on an invalid app so the author sees the errors and fixes them live on\n * the next save. A config is not an app — it isn't validated by the app schema\n * (which would spuriously demand a slug/title), so skip it here.\n */\nfunction reportConfigErrors(app: CliConfig['app'], output: Output): void {\n if (isWorkbenchConfig(app)) return\n const errors = validateWorkbenchApp(app)\n if (errors.length === 0) return\n output.warn(formatWorkbenchAppErrors(errors))\n}\n\n/** The address the server actually bound — the live socket, which can differ from the configured port under non-strict ports. */\nfunction serverAddress(server: ViteDevServer) {\n const resolvedHost = server.config.server.host\n const addr = server.httpServer?.address()\n return {\n host: typeof resolvedHost === 'string' ? resolvedHost : 'localhost',\n port: typeof addr === 'object' && addr ? addr.port : server.config.server.port,\n }\n}\n\n/**\n * Register the dev server in the registry and watch its config for manifest +\n * interface changes. The workbench reads the entry to locate and render the\n * server; the watcher keeps it current as `sanity.cli.ts` is edited.\n */\nexport async function startDevServerRegistration(\n options: DevServerRegistrationOptions,\n): Promise<DevServerRegistrationHandle> {\n const {cliConfig, extractManifest, isApp, onInterfaceSetChange, output, server, workDir} = options\n\n const {host: appHost, port: appPort} = serverAddress(server)\n\n reportConfigErrors(cliConfig.app, output)\n\n // Forwarded alongside (not inside) the manifest so the workbench renders local\n // panels/workers and reads the configs without a deploy.\n const interfaces = deriveInterfaces(cliConfig.app, {isApp})\n const configs = await deriveConfigs(cliConfig)\n\n const workbenchApp = isWorkbenchApp(cliConfig.app) ? cliConfig.app : undefined\n const config = resolveWorkbenchConfig(cliConfig)\n // A config runs alongside the app it configures, so give it its own id\n // namespace — sharing the app's slug would make the two indistinguishable here.\n const id = config ? `config:${config.appType}` : workbenchApp?.slug\n // Identity is resolved and the reference composed here, so the workbench reads\n // both off the registry entry instead of recomposing them. Name defaults to the\n // slug, mirroring brett.\n const name = workbenchApp?.name ?? workbenchApp?.slug\n const reference =\n workbenchApp && name\n ? // No workbench app is a singleton now that the only one (the Media Library)\n // is a config, not an app — so its reference is always `<org>/<name>`.\n applicationReference({\n isSingleton: false,\n name,\n organizationId: workbenchApp.organizationId,\n })\n : undefined\n\n // Separate namespaces mean a shared id is a genuine duplicate, so the gate is\n // a plain id match — no config-vs-app role left to reconcile.\n const devServer = id ? getRegisteredServers().find((server) => server.id === id) : undefined\n\n if (id && devServer) {\n output.error(\n `\"${id}\" is already served by another dev server running on port ${devServer.port}, ` +\n \"so the workbench can't tell them apart and this one stays out of it. Stop that server first.\",\n {exit: false},\n )\n return {close: async () => {}}\n }\n\n const registration = registerDevServer({\n configs,\n host: appHost,\n id,\n interfaces,\n name,\n port: appPort,\n projectId: cliConfig?.api?.projectId,\n reference,\n type: isApp ? 'coreApp' : 'studio',\n workDir,\n })\n\n const exposesSet = trackExposesSet({configs, interfaces})\n\n const watcher = await startDevManifestWatcher({\n // Re-derive every pass (don't omit): the registry patch is a shallow merge,\n // so omitting would wipe the registered set.\n extract: async (params) => {\n const nextConfig = await getCliConfigUncached(params.workDir)\n reportConfigErrors(nextConfig.app, output)\n return {\n configs: await deriveConfigs(nextConfig),\n interfaces: deriveInterfaces(nextConfig.app, {isApp}),\n manifest: await extractManifest({...params, applicationId: id}),\n }\n },\n // A studio's root resolves to `sanity.config.*` but its interfaces live in\n // `sanity.cli.*` — watch that too. Apps already root at `sanity.cli.*`.\n extraWatchFilenames: isApp ? undefined : ['sanity.cli.js', 'sanity.cli.ts'],\n output,\n update: async (patch) => {\n if (\n !exposesSet.changed({\n configs: patch.configs,\n interfaces: patch.interfaces,\n })\n ) {\n registration.update(patch)\n return\n }\n // Rebuild the remote *before* patching the registry — the patch reloads the\n // page, which must re-fetch a remote that already exposes the new interface.\n const rebuiltServer = await onInterfaceSetChange?.()\n // Commit only after a successful rebuild, so a thrown one retries next pass.\n exposesSet.commit({\n configs: patch.configs,\n interfaces: patch.interfaces,\n })\n // The recreated server can bind a different port (non-strict ports).\n registration.update(rebuiltServer ? {...patch, ...serverAddress(rebuiltServer)} : patch)\n },\n workDir,\n })\n\n return {\n close: async () => {\n registration.release()\n await watcher.close()\n },\n }\n}\n"],"names":["getCliConfigUncached","applicationReference","isWorkbenchApp","isWorkbenchConfig","deriveInterfaces","resolveWorkbenchConfig","formatWorkbenchAppErrors","validateWorkbenchApp","deriveConfigs","trackExposesSet","getRegisteredServers","registerDevServer","startDevManifestWatcher","reportConfigErrors","app","output","errors","length","warn","serverAddress","server","resolvedHost","config","host","addr","httpServer","address","port","startDevServerRegistration","options","cliConfig","extractManifest","isApp","onInterfaceSetChange","workDir","appHost","appPort","interfaces","configs","workbenchApp","undefined","id","appType","slug","name","reference","isSingleton","organizationId","devServer","find","error","exit","close","registration","projectId","api","type","exposesSet","watcher","extract","params","nextConfig","manifest","applicationId","extraWatchFilenames","update","patch","changed","rebuiltServer","commit","release"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/dev/startDevServerRegistration.ts"],"sourcesContent":["import {type CliConfig, getCliConfigUncached, type Output} from '@sanity/cli-core'\nimport {type ViteDevServer} from 'vite'\n\nimport {applicationReference} from '../../applicationReference.js'\nimport {isWorkbenchApp, isWorkbenchConfig} from '../../defineApp.js'\nimport {deriveInterfaces} from '../../deriveInterfaces.js'\nimport {resolveWorkbenchConfig} from '../../resolveWorkbenchConfig.js'\nimport {formatWorkbenchAppErrors, validateWorkbenchApp} from '../../validateWorkbenchApp.js'\nimport {deriveConfigs} from './deriveConfigs.js'\nimport {trackExposesSet} from './exposesSetId.js'\nimport {type DevServerManifest, getRegisteredServers, registerDevServer} from './registry.js'\nimport {startDevManifestWatcher} from './startDevManifestWatcher.js'\n\ninterface DevServerRegistrationOptions {\n cliConfig: CliConfig\n /**\n * Extract the project manifest to inline into the registry. The caller owns the\n * studio-vs-app split (manifest formats are CLI-domain); registration re-derives\n * the interface set alongside it.\n */\n extractManifest: (params: {\n applicationId?: string\n configPath: string\n workDir: string\n }) => Promise<DevServerManifest['manifest']>\n isApp: boolean\n output: Output\n server: ViteDevServer\n workDir: string\n\n /**\n * Rebuild the app's federation remote when its interface set changes, awaited\n * *before* the registry patch — the patch reloads the workbench page, which must\n * re-fetch a remote that already exposes the new interface. Resolves with the\n * recreated server so the entry gets its actual address (non-strict ports may\n * shift it); must reject if the restart produces no server, so the set stays\n * uncommitted and the next save retries instead of advertising a dead port.\n */\n onInterfaceSetChange?: () => Promise<ViteDevServer>\n}\n\ninterface DevServerRegistrationHandle {\n close: () => Promise<void>\n}\n\n/**\n * Log any app validation errors without aborting. Unlike build and deploy, dev\n * stays up on an invalid app so the author sees the errors and fixes them live on\n * the next save. A config is not an app — it isn't validated by the app schema\n * (which would spuriously demand a slug/title), so skip it here.\n */\nfunction reportConfigErrors(app: CliConfig['app'], output: Output): void {\n if (isWorkbenchConfig(app)) return\n const errors = validateWorkbenchApp(app)\n if (errors.length === 0) return\n output.warn(formatWorkbenchAppErrors(errors))\n}\n\n/** The address the server actually bound — the live socket, which can differ from the configured port under non-strict ports. */\nfunction serverAddress(server: ViteDevServer) {\n const resolvedHost = server.config.server.host\n const addr = server.httpServer?.address()\n return {\n host: typeof resolvedHost === 'string' ? resolvedHost : 'localhost',\n port: typeof addr === 'object' && addr ? addr.port : server.config.server.port,\n }\n}\n\n/**\n * Register the dev server in the registry and watch its config for manifest +\n * interface changes. The workbench reads the entry to locate and render the\n * server; the watcher keeps it current as `sanity.cli.ts` is edited.\n */\nexport async function startDevServerRegistration(\n options: DevServerRegistrationOptions,\n): Promise<DevServerRegistrationHandle> {\n const {cliConfig, extractManifest, isApp, onInterfaceSetChange, output, server, workDir} = options\n\n const {host: appHost, port: appPort} = serverAddress(server)\n\n reportConfigErrors(cliConfig.app, output)\n\n // Forwarded alongside (not inside) the manifest so the workbench renders local\n // panels/workers and reads the configs without a deploy.\n const interfaces = deriveInterfaces(cliConfig.app, {isApp})\n const configs = await deriveConfigs(cliConfig)\n\n const workbenchApp = isWorkbenchApp(cliConfig.app) ? cliConfig.app : undefined\n const config = resolveWorkbenchConfig(cliConfig)\n // A config runs alongside the app it configures, so give it its own id\n // namespace — sharing the app's slug would make the two indistinguishable here.\n const id = config ? `config:${config.appType}` : workbenchApp?.slug\n // Identity is resolved and the reference composed here, so the workbench reads\n // both off the registry entry instead of recomposing them. Name defaults to the\n // slug, mirroring brett.\n const name = workbenchApp?.name ?? workbenchApp?.slug\n const reference =\n workbenchApp && name\n ? // No workbench app is a singleton now that the only one (the Media Library)\n // is a config, not an app — so its reference is always `<org>/<name>`.\n applicationReference({\n isSingleton: false,\n name,\n organizationId: workbenchApp.organizationId,\n })\n : undefined\n\n // Separate namespaces mean a shared id is a genuine duplicate, so the gate is\n // a plain id match — no config-vs-app role left to reconcile.\n const devServer = id ? getRegisteredServers().find((server) => server.id === id) : undefined\n\n if (id && devServer) {\n output.error(\n `\"${id}\" is already served by another dev server running on port ${devServer.port}, ` +\n \"so the workbench can't tell them apart and this one stays out of it. Stop that server first.\",\n {exit: false},\n )\n return {close: async () => {}}\n }\n\n const registration = registerDevServer({\n configs,\n host: appHost,\n id,\n interfaces,\n name,\n organizationId: workbenchApp?.organizationId,\n port: appPort,\n projectId: cliConfig?.api?.projectId,\n reference,\n slug: workbenchApp?.slug,\n type: isApp ? 'coreApp' : 'studio',\n visibility: workbenchApp ? (workbenchApp.visibility ?? 'default') : undefined,\n workDir,\n })\n\n const exposesSet = trackExposesSet({configs, interfaces})\n\n const watcher = await startDevManifestWatcher({\n // Re-derive every pass (don't omit): the registry patch is a shallow merge,\n // so omitting would wipe the registered set.\n extract: async (params) => {\n const nextConfig = await getCliConfigUncached(params.workDir)\n reportConfigErrors(nextConfig.app, output)\n return {\n configs: await deriveConfigs(nextConfig),\n interfaces: deriveInterfaces(nextConfig.app, {isApp}),\n manifest: await extractManifest({...params, applicationId: id}),\n }\n },\n // A studio's root resolves to `sanity.config.*` but its interfaces live in\n // `sanity.cli.*` — watch that too. Apps already root at `sanity.cli.*`.\n extraWatchFilenames: isApp ? undefined : ['sanity.cli.js', 'sanity.cli.ts'],\n output,\n update: async (patch) => {\n if (\n !exposesSet.changed({\n configs: patch.configs,\n interfaces: patch.interfaces,\n })\n ) {\n registration.update(patch)\n return\n }\n // Rebuild the remote *before* patching the registry — the patch reloads the\n // page, which must re-fetch a remote that already exposes the new interface.\n const rebuiltServer = await onInterfaceSetChange?.()\n // Commit only after a successful rebuild, so a thrown one retries next pass.\n exposesSet.commit({\n configs: patch.configs,\n interfaces: patch.interfaces,\n })\n // The recreated server can bind a different port (non-strict ports).\n registration.update(rebuiltServer ? {...patch, ...serverAddress(rebuiltServer)} : patch)\n },\n workDir,\n })\n\n return {\n close: async () => {\n registration.release()\n await watcher.close()\n },\n }\n}\n"],"names":["getCliConfigUncached","applicationReference","isWorkbenchApp","isWorkbenchConfig","deriveInterfaces","resolveWorkbenchConfig","formatWorkbenchAppErrors","validateWorkbenchApp","deriveConfigs","trackExposesSet","getRegisteredServers","registerDevServer","startDevManifestWatcher","reportConfigErrors","app","output","errors","length","warn","serverAddress","server","resolvedHost","config","host","addr","httpServer","address","port","startDevServerRegistration","options","cliConfig","extractManifest","isApp","onInterfaceSetChange","workDir","appHost","appPort","interfaces","configs","workbenchApp","undefined","id","appType","slug","name","reference","isSingleton","organizationId","devServer","find","error","exit","close","registration","projectId","api","type","visibility","exposesSet","watcher","extract","params","nextConfig","manifest","applicationId","extraWatchFilenames","update","patch","changed","rebuiltServer","commit","release"],"mappings":"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"}
|
|
@@ -15,9 +15,8 @@ const renderDashboardPath = fileURLToPath(new URL('../../_exports/_internal_rend
|
|
|
15
15
|
const noop = async ()=>{};
|
|
16
16
|
// Every server is a local app except a config-only one — a config
|
|
17
17
|
// with no interfaces (the media library). A server with both lands in both channels.
|
|
18
|
-
const isLocalApp = (server)=>!isConfigOnlyServer(server);
|
|
19
18
|
const toApplicationsPayload = (servers)=>({
|
|
20
|
-
applications: servers.filter((server)
|
|
19
|
+
applications: servers.filter((server)=>!isConfigOnlyServer(server)).map(({ host, id, interfaces, manifest, name, organizationId, port, projectId, reference, slug, type, visibility })=>({
|
|
21
20
|
host,
|
|
22
21
|
id,
|
|
23
22
|
// Views cross to the remote keyed on `type`, never the internal `surface`.
|
|
@@ -27,7 +26,12 @@ const toApplicationsPayload = (servers)=>({
|
|
|
27
26
|
port,
|
|
28
27
|
projectId,
|
|
29
28
|
reference,
|
|
30
|
-
type
|
|
29
|
+
type,
|
|
30
|
+
...organizationId && slug && visibility ? {
|
|
31
|
+
organizationId,
|
|
32
|
+
slug,
|
|
33
|
+
visibility
|
|
34
|
+
} : {}
|
|
31
35
|
})),
|
|
32
36
|
configs: servers.flatMap(({ configs, host, port })=>// The registry stores the config flat; the workbench wire shape nests the
|
|
33
37
|
// type-specific payload (`fields` for a media library) under `config`, keyed
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/dev/startWorkbenchDevServer.ts"],"sourcesContent":["import {fileURLToPath} from 'node:url'\n\nimport {type CliConfig, type Output, subdebug} from '@sanity/cli-core'\nimport {isStaging} from '@sanity/cli-core/util'\nimport viteReact from '@vitejs/plugin-react'\nimport {createServer, type InlineConfig, type Plugin, type ViteDevServer} from 'vite'\nimport {z} from 'zod/mini'\n\nimport {isWorkbenchApp, isWorkbenchConfig} from '../../defineApp.js'\nimport {createExposesTracker} from './exposesSetId.js'\nimport {\n acquireWorkbenchLock,\n type DevServerManifest,\n getRegisteredServers,\n isConfigOnlyServer,\n readWorkbenchLock,\n watchRegistry,\n} from './registry.js'\nimport {toWireInterface} from './toWireInterface.js'\nimport {writeWorkbenchRuntime} from './writeWorkbenchRuntime.js'\n\nconst devDebug = subdebug('dev')\nconst renderDashboardEntry = '@sanity/workbench-cli/_internal_render'\nconst renderDashboardPath = fileURLToPath(\n new URL('../../_exports/_internal_render.js', import.meta.url),\n)\n\nconst noop = async () => {}\n\n// Every server is a local app except a config-only one — a config\n// with no interfaces (the media library). A server with both lands in both channels.\nconst isLocalApp = (server: DevServerManifest): boolean => !isConfigOnlyServer(server)\n\nconst toApplicationsPayload = (servers: DevServerManifest[]) => ({\n applications: servers\n .filter((server) => isLocalApp(server))\n .map(({host, id, interfaces, manifest, name, port, projectId, reference, type}) => ({\n host,\n id,\n // Views cross to the remote keyed on `type`, never the internal `surface`.\n interfaces: interfaces?.map((iface) => toWireInterface(iface)),\n manifest,\n name,\n port,\n projectId,\n reference,\n type,\n })),\n configs: servers.flatMap(({configs, host, port}) =>\n // The registry stores the config flat; the workbench wire shape nests the\n // type-specific payload (`fields` for a media library) under `config`, keyed\n // by the `appType` discriminator.\n (configs ?? []).map(({appType, id, moduleName, version, ...config}) => ({\n appType,\n config,\n id,\n moduleName,\n remoteURL: `http://${host}:${port}`,\n version,\n })),\n ),\n})\n\n/**\n * Bridge the dev-server registry into a workbench Vite server's HMR channel so\n * the page tracks apps as they come and go. A changed interface set means a\n * rebuilt remote — full-reload to drop the stale remote-entry; otherwise\n * rebroadcast for a soft reconcile. Returns a detach fn.\n */\nfunction attachViteDevServerBridge(server: ViteDevServer): () => void {\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 const setTracker = createExposesTracker()\n const registryWatcher = watchRegistry((servers) => {\n if (setTracker.hasChanged(servers)) {\n server.ws.send({type: 'full-reload'})\n return\n }\n server.ws.send('sanity:workbench:local-applications', toApplicationsPayload(servers))\n })\n\n return () => registryWatcher.close()\n}\n\n/**\n * Make the workbench remote act as the machine's workbench: claim the singleton\n * lock so app `sanity dev`s register into it instead of each starting their own,\n * and bridge the registry so the remote shows the local apps. No-op lock if one\n * is already held.\n */\nexport function startWorkbenchRemoteCoordinator(options: {\n httpHost: string | undefined\n port: number\n server: ViteDevServer\n}): {close: () => Promise<void>} {\n const {httpHost, port, server} = options\n\n const lock = acquireWorkbenchLock({host: httpHost || 'localhost', port})\n if (!lock) {\n const existing = readWorkbenchLock()\n devDebug(\n 'Workbench lock already held by pid %d on port %d; bridging the registry without claiming it',\n existing?.pid,\n existing?.port,\n )\n }\n\n const detachBridge = attachViteDevServerBridge(server)\n\n return {\n close: async () => {\n detachBridge()\n lock?.release()\n },\n }\n}\n\ninterface WorkbenchDevServerResult {\n close: () => Promise<void>\n httpHost: string | undefined\n workbenchAvailable: boolean\n workbenchPort: number\n}\n\nexport interface StartWorkbenchOptions {\n /** Dependency-cache dir for the workbench Vite server, kept apart from the user's own. */\n cacheDir: string\n cliConfig: CliConfig\n httpHost: string | undefined\n httpPort: number\n /** `dev` renders a live app and honors a local workbench-UI override; `preview`\n * (`sanity start`) previews a build and loads the deployed workbench UI. */\n mode: 'development' | 'preview'\n output: Output\n /** Wrap the workbench in React StrictMode; the CLI resolves it (unset collapses to `false`). */\n reactStrictMode: boolean\n workDir: string\n}\n\nexport async function startWorkbenchDevServer(\n options: StartWorkbenchOptions,\n): Promise<WorkbenchDevServerResult> {\n const {\n cacheDir,\n cliConfig,\n httpHost,\n httpPort: workbenchPort,\n mode,\n output,\n reactStrictMode,\n workDir,\n } = options\n\n // Workbench is opted into by a `defineApplication` app or a config-only\n // `unstable_defineMediaLibrary` config — the latter still needs the shell to\n // render it.\n if (!isWorkbenchApp(cliConfig?.app) && !isWorkbenchConfig(cliConfig?.app)) {\n devDebug('Not a workbench app or config, skipping workbench dev server')\n return {close: noop, httpHost, workbenchAvailable: false, 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 cacheDir,\n cliConfig,\n httpHost,\n mode,\n output,\n reactStrictMode,\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: true,\n workbenchPort: actualPort,\n }\n}\n\ninterface CreateWorkbenchViteServerOptions {\n cacheDir: string\n cliConfig: CliConfig\n httpHost: string | undefined\n mode: 'development' | 'preview'\n output: Output\n reactStrictMode: boolean\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 {cacheDir, cliConfig, httpHost, mode, output, reactStrictMode, workbenchPort, workDir} =\n options\n\n // `preview` loads `.env.development` (the env hook treats only `build`/`deploy`\n // as production), which points the workbench UI at a local dev server that\n // isn't running here. Ignore the override and load the deployed UI instead.\n const remoteUrl =\n mode === 'preview'\n ? undefined\n : parseRemoteUrl(process.env.SANITY_INTERNAL_WORKBENCH_REMOTE_URL)\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,\n configFile: false,\n define: {\n __SANITY_STAGING__: isStaging(),\n 'import.meta.env.SANITY_INTERNAL_WORKBENCH_REMOTE_URL': JSON.stringify(remoteUrl),\n },\n logLevel: 'warn',\n mode: 'development',\n optimizeDeps: {\n // Keep this entry out of pre-bundling so Vite injects its HMR context.\n exclude: [renderDashboardEntry],\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: {\n // The generated Vite root cannot reliably resolve this package.\n alias: {[renderDashboardEntry]: renderDashboardPath},\n dedupe: ['react', 'react-dom'],\n },\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 const detachBridge = attachViteDevServerBridge(server)\n\n return {\n actualPort,\n close: async () => {\n detachBridge()\n await server.close()\n },\n }\n}\n\n// Workbench is opted into via `defineApplication`, 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: 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 defineApplication() 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":["fileURLToPath","subdebug","isStaging","viteReact","createServer","z","isWorkbenchApp","isWorkbenchConfig","createExposesTracker","acquireWorkbenchLock","getRegisteredServers","isConfigOnlyServer","readWorkbenchLock","watchRegistry","toWireInterface","writeWorkbenchRuntime","devDebug","renderDashboardEntry","renderDashboardPath","URL","url","noop","isLocalApp","server","toApplicationsPayload","servers","applications","filter","map","host","id","interfaces","manifest","name","port","projectId","reference","type","iface","configs","flatMap","appType","moduleName","version","config","remoteURL","attachViteDevServerBridge","ws","on","_","client","send","setTracker","registryWatcher","hasChanged","close","startWorkbenchRemoteCoordinator","options","httpHost","lock","existing","pid","detachBridge","release","startWorkbenchDevServer","cacheDir","cliConfig","httpPort","workbenchPort","mode","output","reactStrictMode","workDir","app","workbenchAvailable","workbenchLock","result","createWorkbenchViteServer","err","actualPort","updatePort","remoteUrl","undefined","parseRemoteUrl","process","env","SANITY_INTERNAL_WORKBENCH_REMOTE_URL","organizationId","resolveOrganizationId","root","cwd","viteConfig","configFile","define","__SANITY_STAGING__","JSON","stringify","logLevel","optimizeDeps","exclude","plugins","remoteManifestPreloadHeaderPlugin","resolve","alias","dedupe","strictPort","warmup","clientFiles","listen","warn","Error","message","String","addr","httpServer","address","fetch","then","r","body","cancel","catch","remoteUrlSchema","normalize","protocol","value","safeParse","success","data","apply","configureServer","middlewares","use","req","res","next","pathname","split","setHeader"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/dev/startWorkbenchDevServer.ts"],"sourcesContent":["import {fileURLToPath} from 'node:url'\n\nimport {type CliConfig, type Output, subdebug} from '@sanity/cli-core'\nimport {isStaging} from '@sanity/cli-core/util'\nimport viteReact from '@vitejs/plugin-react'\nimport {createServer, type InlineConfig, type Plugin, type ViteDevServer} from 'vite'\nimport {z} from 'zod/mini'\n\nimport {isWorkbenchApp, isWorkbenchConfig} from '../../defineApp.js'\nimport {createExposesTracker} from './exposesSetId.js'\nimport {\n acquireWorkbenchLock,\n type DevServerManifest,\n getRegisteredServers,\n isConfigOnlyServer,\n readWorkbenchLock,\n watchRegistry,\n} from './registry.js'\nimport {toWireInterface} from './toWireInterface.js'\nimport {writeWorkbenchRuntime} from './writeWorkbenchRuntime.js'\n\nconst devDebug = subdebug('dev')\nconst renderDashboardEntry = '@sanity/workbench-cli/_internal_render'\nconst renderDashboardPath = fileURLToPath(\n new URL('../../_exports/_internal_render.js', import.meta.url),\n)\n\nconst noop = async () => {}\n\n// Every server is a local app except a config-only one — a config\n// with no interfaces (the media library). A server with both lands in both channels.\nconst toApplicationsPayload = (servers: DevServerManifest[]) => ({\n applications: servers\n .filter((server) => !isConfigOnlyServer(server))\n .map(\n ({\n host,\n id,\n interfaces,\n manifest,\n name,\n organizationId,\n port,\n projectId,\n reference,\n slug,\n type,\n visibility,\n }) => ({\n host,\n id,\n // Views cross to the remote keyed on `type`, never the internal `surface`.\n interfaces: interfaces?.map((iface) => toWireInterface(iface)),\n manifest,\n name,\n port,\n projectId,\n reference,\n type,\n ...(organizationId && slug && visibility ? {organizationId, slug, visibility} : {}),\n }),\n ),\n configs: servers.flatMap(({configs, host, port}) =>\n // The registry stores the config flat; the workbench wire shape nests the\n // type-specific payload (`fields` for a media library) under `config`, keyed\n // by the `appType` discriminator.\n (configs ?? []).map(({appType, id, moduleName, version, ...config}) => ({\n appType,\n config,\n id,\n moduleName,\n remoteURL: `http://${host}:${port}`,\n version,\n })),\n ),\n})\n\n/**\n * Bridge the dev-server registry into a workbench Vite server's HMR channel so\n * the page tracks apps as they come and go. A changed interface set means a\n * rebuilt remote — full-reload to drop the stale remote-entry; otherwise\n * rebroadcast for a soft reconcile. Returns a detach fn.\n */\nfunction attachViteDevServerBridge(server: ViteDevServer): () => void {\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 const setTracker = createExposesTracker()\n const registryWatcher = watchRegistry((servers) => {\n if (setTracker.hasChanged(servers)) {\n server.ws.send({type: 'full-reload'})\n return\n }\n server.ws.send('sanity:workbench:local-applications', toApplicationsPayload(servers))\n })\n\n return () => registryWatcher.close()\n}\n\n/**\n * Make the workbench remote act as the machine's workbench: claim the singleton\n * lock so app `sanity dev`s register into it instead of each starting their own,\n * and bridge the registry so the remote shows the local apps. No-op lock if one\n * is already held.\n */\nexport function startWorkbenchRemoteCoordinator(options: {\n httpHost: string | undefined\n port: number\n server: ViteDevServer\n}): {close: () => Promise<void>} {\n const {httpHost, port, server} = options\n\n const lock = acquireWorkbenchLock({host: httpHost || 'localhost', port})\n if (!lock) {\n const existing = readWorkbenchLock()\n devDebug(\n 'Workbench lock already held by pid %d on port %d; bridging the registry without claiming it',\n existing?.pid,\n existing?.port,\n )\n }\n\n const detachBridge = attachViteDevServerBridge(server)\n\n return {\n close: async () => {\n detachBridge()\n lock?.release()\n },\n }\n}\n\ninterface WorkbenchDevServerResult {\n close: () => Promise<void>\n httpHost: string | undefined\n workbenchAvailable: boolean\n workbenchPort: number\n}\n\nexport interface StartWorkbenchOptions {\n /** Dependency-cache dir for the workbench Vite server, kept apart from the user's own. */\n cacheDir: string\n cliConfig: CliConfig\n httpHost: string | undefined\n httpPort: number\n /** `dev` renders a live app and honors a local workbench-UI override; `preview`\n * (`sanity start`) previews a build and loads the deployed workbench UI. */\n mode: 'development' | 'preview'\n output: Output\n /** Wrap the workbench in React StrictMode; the CLI resolves it (unset collapses to `false`). */\n reactStrictMode: boolean\n workDir: string\n}\n\nexport async function startWorkbenchDevServer(\n options: StartWorkbenchOptions,\n): Promise<WorkbenchDevServerResult> {\n const {\n cacheDir,\n cliConfig,\n httpHost,\n httpPort: workbenchPort,\n mode,\n output,\n reactStrictMode,\n workDir,\n } = options\n\n // Workbench is opted into by a `defineApplication` app or a config-only\n // `unstable_defineMediaLibrary` config — the latter still needs the shell to\n // render it.\n if (!isWorkbenchApp(cliConfig?.app) && !isWorkbenchConfig(cliConfig?.app)) {\n devDebug('Not a workbench app or config, skipping workbench dev server')\n return {close: noop, httpHost, workbenchAvailable: false, 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 cacheDir,\n cliConfig,\n httpHost,\n mode,\n output,\n reactStrictMode,\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: true,\n workbenchPort: actualPort,\n }\n}\n\ninterface CreateWorkbenchViteServerOptions {\n cacheDir: string\n cliConfig: CliConfig\n httpHost: string | undefined\n mode: 'development' | 'preview'\n output: Output\n reactStrictMode: boolean\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 {cacheDir, cliConfig, httpHost, mode, output, reactStrictMode, workbenchPort, workDir} =\n options\n\n // `preview` loads `.env.development` (the env hook treats only `build`/`deploy`\n // as production), which points the workbench UI at a local dev server that\n // isn't running here. Ignore the override and load the deployed UI instead.\n const remoteUrl =\n mode === 'preview'\n ? undefined\n : parseRemoteUrl(process.env.SANITY_INTERNAL_WORKBENCH_REMOTE_URL)\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,\n configFile: false,\n define: {\n __SANITY_STAGING__: isStaging(),\n 'import.meta.env.SANITY_INTERNAL_WORKBENCH_REMOTE_URL': JSON.stringify(remoteUrl),\n },\n logLevel: 'warn',\n mode: 'development',\n optimizeDeps: {\n // Keep this entry out of pre-bundling so Vite injects its HMR context.\n exclude: [renderDashboardEntry],\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: {\n // The generated Vite root cannot reliably resolve this package.\n alias: {[renderDashboardEntry]: renderDashboardPath},\n dedupe: ['react', 'react-dom'],\n },\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 const detachBridge = attachViteDevServerBridge(server)\n\n return {\n actualPort,\n close: async () => {\n detachBridge()\n await server.close()\n },\n }\n}\n\n// Workbench is opted into via `defineApplication`, 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: 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 defineApplication() 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":["fileURLToPath","subdebug","isStaging","viteReact","createServer","z","isWorkbenchApp","isWorkbenchConfig","createExposesTracker","acquireWorkbenchLock","getRegisteredServers","isConfigOnlyServer","readWorkbenchLock","watchRegistry","toWireInterface","writeWorkbenchRuntime","devDebug","renderDashboardEntry","renderDashboardPath","URL","url","noop","toApplicationsPayload","servers","applications","filter","server","map","host","id","interfaces","manifest","name","organizationId","port","projectId","reference","slug","type","visibility","iface","configs","flatMap","appType","moduleName","version","config","remoteURL","attachViteDevServerBridge","ws","on","_","client","send","setTracker","registryWatcher","hasChanged","close","startWorkbenchRemoteCoordinator","options","httpHost","lock","existing","pid","detachBridge","release","startWorkbenchDevServer","cacheDir","cliConfig","httpPort","workbenchPort","mode","output","reactStrictMode","workDir","app","workbenchAvailable","workbenchLock","result","createWorkbenchViteServer","err","actualPort","updatePort","remoteUrl","undefined","parseRemoteUrl","process","env","SANITY_INTERNAL_WORKBENCH_REMOTE_URL","resolveOrganizationId","root","cwd","viteConfig","configFile","define","__SANITY_STAGING__","JSON","stringify","logLevel","optimizeDeps","exclude","plugins","remoteManifestPreloadHeaderPlugin","resolve","alias","dedupe","strictPort","warmup","clientFiles","listen","warn","Error","message","String","addr","httpServer","address","fetch","then","r","body","cancel","catch","remoteUrlSchema","normalize","protocol","value","safeParse","success","data","apply","configureServer","middlewares","use","req","res","next","pathname","split","setHeader"],"mappings":"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"}
|
|
@@ -69,9 +69,12 @@ import { serveBuiltApplication } from './serveBuiltApplication.js';
|
|
|
69
69
|
workDir
|
|
70
70
|
}),
|
|
71
71
|
manifestUpdatedAt: new Date().toISOString(),
|
|
72
|
+
organizationId: workbench.organizationId,
|
|
72
73
|
port: remote.port,
|
|
73
74
|
projectId: cliConfig?.api?.projectId,
|
|
75
|
+
slug: workbench.slug,
|
|
74
76
|
type: isApp ? 'coreApp' : 'studio',
|
|
77
|
+
visibility: workbench.visibility ?? 'default',
|
|
75
78
|
workDir
|
|
76
79
|
});
|
|
77
80
|
closers.push(async ()=>registration.release());
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/preview/startWorkbenchPreview.ts"],"sourcesContent":["import {readFile} from 'node:fs/promises'\nimport path from 'node:path'\nimport {styleText} from 'node:util'\n\nimport {type CliConfig, findProjectRoot, type Output} from '@sanity/cli-core'\n\nimport {buildAppId, SANITY_APP_ID_FILE} from '../../appId.js'\nimport {deriveInterfaces} from '../../deriveInterfaces.js'\nimport {resolveWorkbenchApp} from '../../resolveWorkbenchApp.js'\nimport {createServerLifecycle, toDisplayHost} from '../../util/serverOrchestration.js'\nimport {deriveConfigs} from '../dev/deriveConfigs.js'\nimport {type DevServerManifest, registerDevServer} from '../dev/registry.js'\nimport {startWorkbenchDevServer} from '../dev/startWorkbenchDevServer.js'\nimport {serveBuiltApplication} from './serveBuiltApplication.js'\n\nexport interface StartWorkbenchPreviewOptions {\n /** Directory for the workbench Vite server's dependency cache. */\n cacheDir: string\n /** CLI-domain `app.id`/`deployment.appId` deprecation check, run before registering. */\n checkForDeprecatedAppId: () => void\n cliConfig: CliConfig\n /** Extract the project manifest to inline into the registry (studio-vs-app handled by the CLI). */\n extractManifest: (params: {\n applicationId?: string\n configPath: string\n workDir: string\n }) => Promise<DevServerManifest['manifest']>\n httpHost: string\n httpPort: number\n isApp: boolean\n /** The built `dist` directory to serve as the federation remote. */\n outDir: string\n output: Output\n reactStrictMode: boolean\n workDir: string\n}\n\n/**\n * `sanity start` for a workbench app: serve a production build the way dev serves\n * a live one. The same singleton workbench shell renders it and the same registry\n * advertises it — only the remote differs, static files from the build output\n * instead of a live Vite dev server. There's no config watcher or rebuild: a\n * build is fixed, so nothing re-syncs.\n *\n * A running workbench claims the configured port, so the built remote binds the\n * next one. Without one the remote takes the configured port and announces its\n * own URL.\n */\nexport async function startWorkbenchPreview(\n options: StartWorkbenchPreviewOptions,\n): Promise<{close: () => Promise<void>}> {\n const {\n cacheDir,\n checkForDeprecatedAppId,\n cliConfig,\n extractManifest,\n httpHost,\n httpPort,\n isApp,\n outDir,\n output,\n reactStrictMode,\n workDir,\n } = options\n\n const {close, closers, installSignalHandlers} = createServerLifecycle()\n\n const workbench = await startWorkbenchDevServer({\n cacheDir,\n cliConfig,\n httpHost,\n httpPort,\n mode: 'preview',\n output,\n reactStrictMode,\n workDir,\n })\n closers.push(workbench.close)\n\n const remotePort = workbench.workbenchAvailable ? workbench.workbenchPort + 1 : httpPort\n\n const remote = await serveBuiltApplication({\n cacheDir,\n httpHost,\n httpPort: remotePort,\n outDir,\n workDir,\n }).catch(async (err) => {\n await close()\n throw err\n })\n closers.push(remote.close)\n\n try {\n // Callers provide CLI-only validation and manifest extraction to keep them\n // out of workbench-cli.\n checkForDeprecatedAppId()\n const configPath = (await findProjectRoot(workDir)).path\n const workbench = resolveWorkbenchApp(cliConfig)\n\n if (!workbench) throw new Error('`sanity start` was invoked in a non-workbench application')\n const inlinedId = await readInlinedAppId(outDir)\n const configs = await deriveConfigs(cliConfig)\n const id = inlinedId ?? (await buildAppId(workbench))\n const registration = registerDevServer({\n configs,\n host: remote.host,\n id,\n interfaces: deriveInterfaces(cliConfig.app, {isApp}),\n manifest: await extractManifest({applicationId: id, configPath, workDir}),\n manifestUpdatedAt: new Date().toISOString(),\n port: remote.port,\n projectId: cliConfig?.api?.projectId,\n type: isApp ? 'coreApp' : 'studio',\n workDir,\n })\n closers.push(async () => registration.release())\n } catch (err) {\n await close()\n throw err\n }\n\n if (workbench.workbenchAvailable) {\n const workbenchUrl = `http://${toDisplayHost(workbench.httpHost)}:${workbench.workbenchPort}`\n output.log(\n `Workbench preview server started at ${styleText(['blue', 'underline'], workbenchUrl)} (serving build on port ${remote.port})`,\n )\n } else {\n const remoteUrl = `http://${toDisplayHost(remote.host)}:${remote.port}`\n output.log(`Serving build at ${styleText(['blue', 'underline'], remoteUrl)}`)\n }\n\n installSignalHandlers()\n\n return {close}\n}\n\n/** The id the build inlined into its bundle, or undefined when absent. */\nasync function readInlinedAppId(outDir: string): Promise<string | undefined> {\n try {\n return (await readFile(path.join(outDir, SANITY_APP_ID_FILE), 'utf8')).trim() || undefined\n } catch {\n return undefined\n }\n}\n"],"names":["readFile","path","styleText","findProjectRoot","buildAppId","SANITY_APP_ID_FILE","deriveInterfaces","resolveWorkbenchApp","createServerLifecycle","toDisplayHost","deriveConfigs","registerDevServer","startWorkbenchDevServer","serveBuiltApplication","startWorkbenchPreview","options","cacheDir","checkForDeprecatedAppId","cliConfig","extractManifest","httpHost","httpPort","isApp","outDir","output","reactStrictMode","workDir","close","closers","installSignalHandlers","workbench","mode","push","remotePort","workbenchAvailable","workbenchPort","remote","catch","err","configPath","Error","inlinedId","readInlinedAppId","configs","id","registration","host","interfaces","app","manifest","applicationId","manifestUpdatedAt","Date","toISOString","port","projectId","api","type","release","workbenchUrl","log","remoteUrl","join","trim","undefined"],"mappings":"AAAA,SAAQA,QAAQ,QAAO,mBAAkB;AACzC,OAAOC,UAAU,YAAW;AAC5B,SAAQC,SAAS,QAAO,YAAW;AAEnC,SAAwBC,eAAe,QAAoB,mBAAkB;AAE7E,SAAQC,UAAU,EAAEC,kBAAkB,QAAO,iBAAgB;AAC7D,SAAQC,gBAAgB,QAAO,4BAA2B;AAC1D,SAAQC,mBAAmB,QAAO,+BAA8B;AAChE,SAAQC,qBAAqB,EAAEC,aAAa,QAAO,oCAAmC;AACtF,SAAQC,aAAa,QAAO,0BAAyB;AACrD,SAAgCC,iBAAiB,QAAO,qBAAoB;AAC5E,SAAQC,uBAAuB,QAAO,oCAAmC;AACzE,SAAQC,qBAAqB,QAAO,6BAA4B;AAwBhE;;;;;;;;;;CAUC,GACD,OAAO,eAAeC,sBACpBC,OAAqC;IAErC,MAAM,EACJC,QAAQ,EACRC,uBAAuB,EACvBC,SAAS,EACTC,eAAe,EACfC,QAAQ,EACRC,QAAQ,EACRC,KAAK,EACLC,MAAM,EACNC,MAAM,EACNC,eAAe,EACfC,OAAO,EACR,GAAGX;IAEJ,MAAM,EAACY,KAAK,EAAEC,OAAO,EAAEC,qBAAqB,EAAC,GAAGrB;IAEhD,MAAMsB,YAAY,MAAMlB,wBAAwB;QAC9CI;QACAE;QACAE;QACAC;QACAU,MAAM;QACNP;QACAC;QACAC;IACF;IACAE,QAAQI,IAAI,CAACF,UAAUH,KAAK;IAE5B,MAAMM,aAAaH,UAAUI,kBAAkB,GAAGJ,UAAUK,aAAa,GAAG,IAAId;IAEhF,MAAMe,SAAS,MAAMvB,sBAAsB;QACzCG;QACAI;QACAC,UAAUY;QACVV;QACAG;IACF,GAAGW,KAAK,CAAC,OAAOC;QACd,MAAMX;QACN,MAAMW;IACR;IACAV,QAAQI,IAAI,CAACI,OAAOT,KAAK;IAEzB,IAAI;QACF,2EAA2E;QAC3E,wBAAwB;QACxBV;QACA,MAAMsB,aAAa,AAAC,CAAA,MAAMpC,gBAAgBuB,QAAO,EAAGzB,IAAI;QACxD,MAAM6B,YAAYvB,oBAAoBW;QAEtC,IAAI,CAACY,WAAW,MAAM,IAAIU,MAAM;QAChC,MAAMC,YAAY,MAAMC,iBAAiBnB;QACzC,MAAMoB,UAAU,MAAMjC,cAAcQ;QACpC,MAAM0B,KAAKH,aAAc,MAAMrC,WAAW0B;QAC1C,MAAMe,eAAelC,kBAAkB;YACrCgC;YACAG,MAAMV,OAAOU,IAAI;YACjBF;YACAG,YAAYzC,iBAAiBY,UAAU8B,GAAG,EAAE;gBAAC1B;YAAK;YAClD2B,UAAU,MAAM9B,gBAAgB;gBAAC+B,eAAeN;gBAAIL;gBAAYb;YAAO;YACvEyB,mBAAmB,IAAIC,OAAOC,WAAW;YACzCC,
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/preview/startWorkbenchPreview.ts"],"sourcesContent":["import {readFile} from 'node:fs/promises'\nimport path from 'node:path'\nimport {styleText} from 'node:util'\n\nimport {type CliConfig, findProjectRoot, type Output} from '@sanity/cli-core'\n\nimport {buildAppId, SANITY_APP_ID_FILE} from '../../appId.js'\nimport {deriveInterfaces} from '../../deriveInterfaces.js'\nimport {resolveWorkbenchApp} from '../../resolveWorkbenchApp.js'\nimport {createServerLifecycle, toDisplayHost} from '../../util/serverOrchestration.js'\nimport {deriveConfigs} from '../dev/deriveConfigs.js'\nimport {type DevServerManifest, registerDevServer} from '../dev/registry.js'\nimport {startWorkbenchDevServer} from '../dev/startWorkbenchDevServer.js'\nimport {serveBuiltApplication} from './serveBuiltApplication.js'\n\nexport interface StartWorkbenchPreviewOptions {\n /** Directory for the workbench Vite server's dependency cache. */\n cacheDir: string\n /** CLI-domain `app.id`/`deployment.appId` deprecation check, run before registering. */\n checkForDeprecatedAppId: () => void\n cliConfig: CliConfig\n /** Extract the project manifest to inline into the registry (studio-vs-app handled by the CLI). */\n extractManifest: (params: {\n applicationId?: string\n configPath: string\n workDir: string\n }) => Promise<DevServerManifest['manifest']>\n httpHost: string\n httpPort: number\n isApp: boolean\n /** The built `dist` directory to serve as the federation remote. */\n outDir: string\n output: Output\n reactStrictMode: boolean\n workDir: string\n}\n\n/**\n * `sanity start` for a workbench app: serve a production build the way dev serves\n * a live one. The same singleton workbench shell renders it and the same registry\n * advertises it — only the remote differs, static files from the build output\n * instead of a live Vite dev server. There's no config watcher or rebuild: a\n * build is fixed, so nothing re-syncs.\n *\n * A running workbench claims the configured port, so the built remote binds the\n * next one. Without one the remote takes the configured port and announces its\n * own URL.\n */\nexport async function startWorkbenchPreview(\n options: StartWorkbenchPreviewOptions,\n): Promise<{close: () => Promise<void>}> {\n const {\n cacheDir,\n checkForDeprecatedAppId,\n cliConfig,\n extractManifest,\n httpHost,\n httpPort,\n isApp,\n outDir,\n output,\n reactStrictMode,\n workDir,\n } = options\n\n const {close, closers, installSignalHandlers} = createServerLifecycle()\n\n const workbench = await startWorkbenchDevServer({\n cacheDir,\n cliConfig,\n httpHost,\n httpPort,\n mode: 'preview',\n output,\n reactStrictMode,\n workDir,\n })\n closers.push(workbench.close)\n\n const remotePort = workbench.workbenchAvailable ? workbench.workbenchPort + 1 : httpPort\n\n const remote = await serveBuiltApplication({\n cacheDir,\n httpHost,\n httpPort: remotePort,\n outDir,\n workDir,\n }).catch(async (err) => {\n await close()\n throw err\n })\n closers.push(remote.close)\n\n try {\n // Callers provide CLI-only validation and manifest extraction to keep them\n // out of workbench-cli.\n checkForDeprecatedAppId()\n const configPath = (await findProjectRoot(workDir)).path\n const workbench = resolveWorkbenchApp(cliConfig)\n\n if (!workbench) throw new Error('`sanity start` was invoked in a non-workbench application')\n const inlinedId = await readInlinedAppId(outDir)\n const configs = await deriveConfigs(cliConfig)\n const id = inlinedId ?? (await buildAppId(workbench))\n const registration = registerDevServer({\n configs,\n host: remote.host,\n id,\n interfaces: deriveInterfaces(cliConfig.app, {isApp}),\n manifest: await extractManifest({applicationId: id, configPath, workDir}),\n manifestUpdatedAt: new Date().toISOString(),\n organizationId: workbench.organizationId,\n port: remote.port,\n projectId: cliConfig?.api?.projectId,\n slug: workbench.slug,\n type: isApp ? 'coreApp' : 'studio',\n visibility: workbench.visibility ?? 'default',\n workDir,\n })\n closers.push(async () => registration.release())\n } catch (err) {\n await close()\n throw err\n }\n\n if (workbench.workbenchAvailable) {\n const workbenchUrl = `http://${toDisplayHost(workbench.httpHost)}:${workbench.workbenchPort}`\n output.log(\n `Workbench preview server started at ${styleText(['blue', 'underline'], workbenchUrl)} (serving build on port ${remote.port})`,\n )\n } else {\n const remoteUrl = `http://${toDisplayHost(remote.host)}:${remote.port}`\n output.log(`Serving build at ${styleText(['blue', 'underline'], remoteUrl)}`)\n }\n\n installSignalHandlers()\n\n return {close}\n}\n\n/** The id the build inlined into its bundle, or undefined when absent. */\nasync function readInlinedAppId(outDir: string): Promise<string | undefined> {\n try {\n return (await readFile(path.join(outDir, SANITY_APP_ID_FILE), 'utf8')).trim() || undefined\n } catch {\n return undefined\n }\n}\n"],"names":["readFile","path","styleText","findProjectRoot","buildAppId","SANITY_APP_ID_FILE","deriveInterfaces","resolveWorkbenchApp","createServerLifecycle","toDisplayHost","deriveConfigs","registerDevServer","startWorkbenchDevServer","serveBuiltApplication","startWorkbenchPreview","options","cacheDir","checkForDeprecatedAppId","cliConfig","extractManifest","httpHost","httpPort","isApp","outDir","output","reactStrictMode","workDir","close","closers","installSignalHandlers","workbench","mode","push","remotePort","workbenchAvailable","workbenchPort","remote","catch","err","configPath","Error","inlinedId","readInlinedAppId","configs","id","registration","host","interfaces","app","manifest","applicationId","manifestUpdatedAt","Date","toISOString","organizationId","port","projectId","api","slug","type","visibility","release","workbenchUrl","log","remoteUrl","join","trim","undefined"],"mappings":"AAAA,SAAQA,QAAQ,QAAO,mBAAkB;AACzC,OAAOC,UAAU,YAAW;AAC5B,SAAQC,SAAS,QAAO,YAAW;AAEnC,SAAwBC,eAAe,QAAoB,mBAAkB;AAE7E,SAAQC,UAAU,EAAEC,kBAAkB,QAAO,iBAAgB;AAC7D,SAAQC,gBAAgB,QAAO,4BAA2B;AAC1D,SAAQC,mBAAmB,QAAO,+BAA8B;AAChE,SAAQC,qBAAqB,EAAEC,aAAa,QAAO,oCAAmC;AACtF,SAAQC,aAAa,QAAO,0BAAyB;AACrD,SAAgCC,iBAAiB,QAAO,qBAAoB;AAC5E,SAAQC,uBAAuB,QAAO,oCAAmC;AACzE,SAAQC,qBAAqB,QAAO,6BAA4B;AAwBhE;;;;;;;;;;CAUC,GACD,OAAO,eAAeC,sBACpBC,OAAqC;IAErC,MAAM,EACJC,QAAQ,EACRC,uBAAuB,EACvBC,SAAS,EACTC,eAAe,EACfC,QAAQ,EACRC,QAAQ,EACRC,KAAK,EACLC,MAAM,EACNC,MAAM,EACNC,eAAe,EACfC,OAAO,EACR,GAAGX;IAEJ,MAAM,EAACY,KAAK,EAAEC,OAAO,EAAEC,qBAAqB,EAAC,GAAGrB;IAEhD,MAAMsB,YAAY,MAAMlB,wBAAwB;QAC9CI;QACAE;QACAE;QACAC;QACAU,MAAM;QACNP;QACAC;QACAC;IACF;IACAE,QAAQI,IAAI,CAACF,UAAUH,KAAK;IAE5B,MAAMM,aAAaH,UAAUI,kBAAkB,GAAGJ,UAAUK,aAAa,GAAG,IAAId;IAEhF,MAAMe,SAAS,MAAMvB,sBAAsB;QACzCG;QACAI;QACAC,UAAUY;QACVV;QACAG;IACF,GAAGW,KAAK,CAAC,OAAOC;QACd,MAAMX;QACN,MAAMW;IACR;IACAV,QAAQI,IAAI,CAACI,OAAOT,KAAK;IAEzB,IAAI;QACF,2EAA2E;QAC3E,wBAAwB;QACxBV;QACA,MAAMsB,aAAa,AAAC,CAAA,MAAMpC,gBAAgBuB,QAAO,EAAGzB,IAAI;QACxD,MAAM6B,YAAYvB,oBAAoBW;QAEtC,IAAI,CAACY,WAAW,MAAM,IAAIU,MAAM;QAChC,MAAMC,YAAY,MAAMC,iBAAiBnB;QACzC,MAAMoB,UAAU,MAAMjC,cAAcQ;QACpC,MAAM0B,KAAKH,aAAc,MAAMrC,WAAW0B;QAC1C,MAAMe,eAAelC,kBAAkB;YACrCgC;YACAG,MAAMV,OAAOU,IAAI;YACjBF;YACAG,YAAYzC,iBAAiBY,UAAU8B,GAAG,EAAE;gBAAC1B;YAAK;YAClD2B,UAAU,MAAM9B,gBAAgB;gBAAC+B,eAAeN;gBAAIL;gBAAYb;YAAO;YACvEyB,mBAAmB,IAAIC,OAAOC,WAAW;YACzCC,gBAAgBxB,UAAUwB,cAAc;YACxCC,MAAMnB,OAAOmB,IAAI;YACjBC,WAAWtC,WAAWuC,KAAKD;YAC3BE,MAAM5B,UAAU4B,IAAI;YACpBC,MAAMrC,QAAQ,YAAY;YAC1BsC,YAAY9B,UAAU8B,UAAU,IAAI;YACpClC;QACF;QACAE,QAAQI,IAAI,CAAC,UAAYa,aAAagB,OAAO;IAC/C,EAAE,OAAOvB,KAAK;QACZ,MAAMX;QACN,MAAMW;IACR;IAEA,IAAIR,UAAUI,kBAAkB,EAAE;QAChC,MAAM4B,eAAe,CAAC,OAAO,EAAErD,cAAcqB,UAAUV,QAAQ,EAAE,CAAC,EAAEU,UAAUK,aAAa,EAAE;QAC7FX,OAAOuC,GAAG,CACR,CAAC,oCAAoC,EAAE7D,UAAU;YAAC;YAAQ;SAAY,EAAE4D,cAAc,wBAAwB,EAAE1B,OAAOmB,IAAI,CAAC,CAAC,CAAC;IAElI,OAAO;QACL,MAAMS,YAAY,CAAC,OAAO,EAAEvD,cAAc2B,OAAOU,IAAI,EAAE,CAAC,EAAEV,OAAOmB,IAAI,EAAE;QACvE/B,OAAOuC,GAAG,CAAC,CAAC,iBAAiB,EAAE7D,UAAU;YAAC;YAAQ;SAAY,EAAE8D,YAAY;IAC9E;IAEAnC;IAEA,OAAO;QAACF;IAAK;AACf;AAEA,wEAAwE,GACxE,eAAee,iBAAiBnB,MAAc;IAC5C,IAAI;QACF,OAAO,AAAC,CAAA,MAAMvB,SAASC,KAAKgE,IAAI,CAAC1C,QAAQlB,qBAAqB,OAAM,EAAG6D,IAAI,MAAMC;IACnF,EAAE,OAAM;QACN,OAAOA;IACT;AACF"}
|
|
@@ -8,7 +8,7 @@ async function getClient() {
|
|
|
8
8
|
requireUser: true
|
|
9
9
|
});
|
|
10
10
|
}
|
|
11
|
-
/** The org's active singleton installation, matched on its
|
|
11
|
+
/** The org's active singleton installation, matched on its application name. */ export async function resolveSingletonInstallationId(organizationId, appType) {
|
|
12
12
|
const client = await getClient();
|
|
13
13
|
// `limit=none` returns every installation in one response, no pagination.
|
|
14
14
|
const { data } = await client.request({
|
|
@@ -18,7 +18,7 @@ async function getClient() {
|
|
|
18
18
|
},
|
|
19
19
|
url: '/installations'
|
|
20
20
|
});
|
|
21
|
-
return data.find((item)=>item.application?.
|
|
21
|
+
return data.find((item)=>item.application?.name === appType)?.id;
|
|
22
22
|
}
|
|
23
23
|
/** Upload a config snapshot to the installation as a multipart tarball. */ export async function createConfig(installationId, { tarball, version }) {
|
|
24
24
|
const formData = new FormData();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/services/installations.ts"],"sourcesContent":["import {PassThrough} from 'node:stream'\nimport {type Gzip} from 'node:zlib'\n\nimport {getGlobalCliClient} from '@sanity/cli-core'\nimport FormData from 'form-data'\n\nimport {APP_WORKBENCH_API_VERSION} from './apiVersion.js'\n\nexport interface ConfigSnapshot {\n id: string\n\n createdAt?: string\n deployedBy?: string\n /** Whether this snapshot is the one being served; at most one per installation. */\n isActive?: boolean\n}\n\ninterface InstallationListItem {\n id: string\n\n application?: {
|
|
1
|
+
{"version":3,"sources":["../../src/services/installations.ts"],"sourcesContent":["import {PassThrough} from 'node:stream'\nimport {type Gzip} from 'node:zlib'\n\nimport {getGlobalCliClient} from '@sanity/cli-core'\nimport FormData from 'form-data'\n\nimport {APP_WORKBENCH_API_VERSION} from './apiVersion.js'\n\nexport interface ConfigSnapshot {\n id: string\n\n createdAt?: string\n deployedBy?: string\n /** Whether this snapshot is the one being served; at most one per installation. */\n isActive?: boolean\n}\n\ninterface InstallationListItem {\n id: string\n\n application?: {name?: string}\n}\n\nasync function getClient() {\n return getGlobalCliClient({apiVersion: APP_WORKBENCH_API_VERSION, requireUser: true})\n}\n\n/** The org's active singleton installation, matched on its application name. */\nexport async function resolveSingletonInstallationId(\n organizationId: string,\n appType: string,\n): Promise<string | undefined> {\n const client = await getClient()\n // `limit=none` returns every installation in one response, no pagination.\n const {data}: {data: InstallationListItem[]} = await client.request({\n query: {limit: 'none', organizationId},\n url: '/installations',\n })\n return data.find((item) => item.application?.name === appType)?.id\n}\n\n/** Upload a config snapshot to the installation as a multipart tarball. */\nexport async function createConfig(\n installationId: string,\n {tarball, version}: {tarball: Gzip; version: string},\n): Promise<void> {\n const formData = new FormData()\n formData.append('version', version)\n formData.append('tarball', tarball, {\n contentType: 'application/gzip',\n filename: 'installation-config.tar.gz',\n })\n\n const client = await getClient()\n await client.request({\n body: formData.pipe(new PassThrough()),\n headers: formData.getHeaders(),\n method: 'POST',\n url: `/installations/${installationId}/configs`,\n })\n}\n\n/** The installation's deployed config snapshots, newest first. */\nexport async function listConfigs(installationId: string): Promise<ConfigSnapshot[]> {\n const client = await getClient()\n const {data}: {data: ConfigSnapshot[]} = await client.request({\n query: {limit: 'none'},\n url: `/installations/${installationId}/configs`,\n })\n return data\n}\n\n/**\n * Soft-deletes one config snapshot; its content is purged once superseded.\n * Already deleted counts as done, so a partially-failed undeploy can re-run.\n */\nexport async function deleteConfig(installationId: string, configId: string): Promise<void> {\n const client = await getClient()\n try {\n await client.request({\n method: 'DELETE',\n url: `/installations/${installationId}/configs/${configId}`,\n })\n } catch (err) {\n if ((err as {statusCode?: number})?.statusCode !== 404) throw err\n }\n}\n"],"names":["PassThrough","getGlobalCliClient","FormData","APP_WORKBENCH_API_VERSION","getClient","apiVersion","requireUser","resolveSingletonInstallationId","organizationId","appType","client","data","request","query","limit","url","find","item","application","name","id","createConfig","installationId","tarball","version","formData","append","contentType","filename","body","pipe","headers","getHeaders","method","listConfigs","deleteConfig","configId","err","statusCode"],"mappings":"AAAA,SAAQA,WAAW,QAAO,cAAa;AAGvC,SAAQC,kBAAkB,QAAO,mBAAkB;AACnD,OAAOC,cAAc,YAAW;AAEhC,SAAQC,yBAAyB,QAAO,kBAAiB;AAiBzD,eAAeC;IACb,OAAOH,mBAAmB;QAACI,YAAYF;QAA2BG,aAAa;IAAI;AACrF;AAEA,8EAA8E,GAC9E,OAAO,eAAeC,+BACpBC,cAAsB,EACtBC,OAAe;IAEf,MAAMC,SAAS,MAAMN;IACrB,0EAA0E;IAC1E,MAAM,EAACO,IAAI,EAAC,GAAmC,MAAMD,OAAOE,OAAO,CAAC;QAClEC,OAAO;YAACC,OAAO;YAAQN;QAAc;QACrCO,KAAK;IACP;IACA,OAAOJ,KAAKK,IAAI,CAAC,CAACC,OAASA,KAAKC,WAAW,EAAEC,SAASV,UAAUW;AAClE;AAEA,yEAAyE,GACzE,OAAO,eAAeC,aACpBC,cAAsB,EACtB,EAACC,OAAO,EAAEC,OAAO,EAAmC;IAEpD,MAAMC,WAAW,IAAIvB;IACrBuB,SAASC,MAAM,CAAC,WAAWF;IAC3BC,SAASC,MAAM,CAAC,WAAWH,SAAS;QAClCI,aAAa;QACbC,UAAU;IACZ;IAEA,MAAMlB,SAAS,MAAMN;IACrB,MAAMM,OAAOE,OAAO,CAAC;QACnBiB,MAAMJ,SAASK,IAAI,CAAC,IAAI9B;QACxB+B,SAASN,SAASO,UAAU;QAC5BC,QAAQ;QACRlB,KAAK,CAAC,eAAe,EAAEO,eAAe,QAAQ,CAAC;IACjD;AACF;AAEA,gEAAgE,GAChE,OAAO,eAAeY,YAAYZ,cAAsB;IACtD,MAAMZ,SAAS,MAAMN;IACrB,MAAM,EAACO,IAAI,EAAC,GAA6B,MAAMD,OAAOE,OAAO,CAAC;QAC5DC,OAAO;YAACC,OAAO;QAAM;QACrBC,KAAK,CAAC,eAAe,EAAEO,eAAe,QAAQ,CAAC;IACjD;IACA,OAAOX;AACT;AAEA;;;CAGC,GACD,OAAO,eAAewB,aAAab,cAAsB,EAAEc,QAAgB;IACzE,MAAM1B,SAAS,MAAMN;IACrB,IAAI;QACF,MAAMM,OAAOE,OAAO,CAAC;YACnBqB,QAAQ;YACRlB,KAAK,CAAC,eAAe,EAAEO,eAAe,SAAS,EAAEc,UAAU;QAC7D;IACF,EAAE,OAAOC,KAAK;QACZ,IAAI,AAACA,KAA+BC,eAAe,KAAK,MAAMD;IAChE;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/workbench-cli",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.3",
|
|
4
4
|
"description": "Internal implementation detail of the Sanity CLI's unstable workbench support. Not intended for direct use.",
|
|
5
5
|
"homepage": "https://github.com/sanity-io/cli",
|
|
6
6
|
"bugs": "https://github.com/sanity-io/cli/issues",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"dependencies": {
|
|
60
60
|
"@module-federation/runtime": "2.9.0",
|
|
61
61
|
"@module-federation/vite": "1.21.0",
|
|
62
|
-
"@sanity/cli-core": "^3.
|
|
62
|
+
"@sanity/cli-core": "^3.7.0",
|
|
63
63
|
"@sanity/types": "^6.12.0",
|
|
64
64
|
"@vitejs/plugin-react": "^6.1.1",
|
|
65
65
|
"form-data": "^4.0.5",
|