@mysten-incubation/devstack 0.7.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/build-integrations/vite/index.mjs +1 -0
  2. package/dist/build-integrations/vite/index.mjs.map +1 -1
  3. package/dist/cli/wirings/apply.mjs +24 -6
  4. package/dist/cli/wirings/apply.mjs.map +1 -1
  5. package/dist/cli/wirings/dump-deployment.mjs +2 -1
  6. package/dist/cli/wirings/dump-deployment.mjs.map +1 -1
  7. package/dist/cli/wirings/identity.mjs +2 -1
  8. package/dist/cli/wirings/identity.mjs.map +1 -1
  9. package/dist/cli/wirings/snapshot.mjs +2 -1
  10. package/dist/cli/wirings/snapshot.mjs.map +1 -1
  11. package/dist/cli/wirings/up.mjs +25 -29
  12. package/dist/cli/wirings/up.mjs.map +1 -1
  13. package/dist/plugins/package/codegen.mjs +4 -4
  14. package/dist/plugins/package/codegen.mjs.map +1 -1
  15. package/dist/plugins/package/dep-resolution.mjs +19 -9
  16. package/dist/plugins/package/dep-resolution.mjs.map +1 -1
  17. package/dist/plugins/package/index.d.mts +3 -3
  18. package/dist/plugins/package/index.mjs +9 -5
  19. package/dist/plugins/package/index.mjs.map +1 -1
  20. package/dist/plugins/package/mode-known.mjs +2 -2
  21. package/dist/plugins/package/mode-known.mjs.map +1 -1
  22. package/dist/plugins/package/mode-local.mjs +4 -4
  23. package/dist/plugins/package/mode-local.mjs.map +1 -1
  24. package/dist/plugins/sui/chain-build-container.mjs.map +1 -1
  25. package/dist/plugins/sui/index.d.mts +10 -10
  26. package/dist/plugins/sui/index.mjs.map +1 -1
  27. package/dist/plugins/sui/move/index.mjs +10 -6
  28. package/dist/plugins/sui/move/index.mjs.map +1 -1
  29. package/dist/plugins/wallet/codegen.d.mts +14 -10
  30. package/dist/plugins/wallet/codegen.mjs.map +1 -1
  31. package/dist/plugins/wallet/origin-policy.mjs +43 -3
  32. package/dist/plugins/wallet/origin-policy.mjs.map +1 -1
  33. package/dist/plugins/wallet/service.mjs +9 -7
  34. package/dist/plugins/wallet/service.mjs.map +1 -1
  35. package/dist/substrate/cross-process.mjs +2 -1
  36. package/dist/substrate/cross-process.mjs.map +1 -1
  37. package/dist/substrate/runtime/cross-process/roster.mjs +5 -2
  38. package/dist/substrate/runtime/cross-process/roster.mjs.map +1 -1
  39. package/dist/surfaces/cli/commands/supervisor-presence.mjs +4 -2
  40. package/dist/surfaces/cli/commands/supervisor-presence.mjs.map +1 -1
  41. package/dist/surfaces/cli/errors.mjs +12 -2
  42. package/dist/surfaces/cli/errors.mjs.map +1 -1
  43. package/dist/surfaces/cli/index.mjs.map +1 -1
  44. package/package.json +1 -1
@@ -364,6 +364,7 @@ const devstackVitePlugin = (options = {}) => {
364
364
  ` serverOrigin: __devstackConnection.walletUrl,`,
365
365
  ` token: __devstackToken,`,
366
366
  ` accounts: __devstackAccounts,`,
367
+ ` allowedOrigins: __devstackConnection.allowedOrigins ?? [],`,
367
368
  ` networks: __devstackNetworks,`,
368
369
  ` defaultNetwork: __devstackConfig.defaultNetwork,`,
369
370
  ` autoApprove: ${autoApprove ? "true" : "false"},`,
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/build-integrations/vite/index.ts"],"sourcesContent":["// Vite build-integration — `@generated` alias plugin.\n//\n// App source imports Move codegen through a configurable alias prefix\n// (default `@generated`) instead of `./generated`. This plugin points\n// that alias at the committed `<root>/src/generated` tree — the ONE\n// source of bindings, written only by the stack-free `devstack codegen`\n// verb. On-chain ids are NOT baked into that tree; they resolve at\n// runtime via the `__DEVSTACK_DEPLOYMENT__` global (see\n// `mergeDeployment`), so the same generated source serves every\n// stack. Resolution:\n// 1. `options.generatedDir` — explicit escape hatch (relative → root).\n// 2. `<root>/src/generated` — the committed tree, always.\n//\n// Because Playwright's `webServer` runs the app's OWN Vite as a child,\n// the same plugin serves both `pnpm dev` and the e2e dev server\n// automatically. Vitest has its own Vite pipeline, so apps add this\n// plugin to `vitest.config.ts` too — and resolve `@generated` to the\n// same committed tree (the per-stack live tree no longer exists).\n//\n// SYNC + dependency-light, mirroring the playwright/vitest helpers: NO\n// heavy imports at module top-level, and `vite` is NOT imported (it is\n// an app-side dev dependency, not a devstack runtime dep). The return\n// value is a STRUCTURAL Vite `Plugin` — a `{ name, config }` object —\n// typed loosely so this module loads without `vite` installed. The\n// manifest read goes through the same `build-integrations/runtime`\n// machinery the playwright/vitest integrations use.\n\nimport { existsSync, readdirSync, readFileSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nimport { discoverManifestPath, resolveDiscoveryEnv } from '../runtime/index.ts';\nimport { resolveNetworkOptions } from '../../orchestrators/network-options.ts';\nimport {\n\tdecodeDeployment,\n\tdecodeNetworkDeployment,\n\ttype DevstackDeployment,\n\ttype NetworkDeployment,\n} from '../../orchestrators/codegen/deployment.ts';\n\n/** Default import-alias prefix. Customizable via `options.alias` (some\n * apps prefer `@gen`, `~generated`, …). The app MUST use the SAME\n * prefix in three derivable places: this plugin option, the\n * `tsconfig` `paths` entry, and its import specifiers. */\nexport const DEFAULT_GENERATED_ALIAS = '@generated';\n\n/** The committed generated-bindings subpath, relative to the Vite root.\n * The single source of bindings — written by `devstack codegen` — that\n * `@generated` always resolves to (absent the `options.generatedDir`\n * escape hatch). */\nconst GENERATED_SUBPATH = 'src/generated';\n\nexport interface DevstackVitePluginOptions {\n\t/** Import-alias prefix. Default `'@generated'`. */\n\treadonly alias?: string;\n\t/** Explicit generated dir — bypasses manifest discovery entirely\n\t * (escape hatch for unusual layouts / tests). Relative paths\n\t * resolve against the Vite root. */\n\treadonly generatedDir?: string;\n\t/** Inject + register the devstack dev wallet on the page in DEV\n\t * (wallet-standard, so dApp Kit auto-discovers it). Defaults to\n\t * `true`. Production builds (`command === 'build'`) inject nothing\n\t * regardless. */\n\treadonly injectDevWallet?: boolean;\n\t/** Auto-approve all dev-wallet signing requests (headless Playwright /\n\t * in-app \"Open/Join as\" buttons). Defaults to the `DEVSTACK_AUTO_APPROVE`\n\t * env (`'1'`/`'true'`), then to the active network's `autoApproveSigning`\n\t * per-network policy (OFF by default on every network — a normal `pnpm dev`\n\t * shows the real connect + approve UX; opt in per-network). A single\n\t * switch, replacing per-app `VITE_*_AUTO_APPROVE`.\n\t *\n\t * PRECEDENCE: an explicit value here ALWAYS wins — passing `autoApprove:\n\t * false` forces it off even when `DEVSTACK_AUTO_APPROVE` is set, and\n\t * `autoApprove: true` forces it on. The `DEVSTACK_AUTO_APPROVE` env and the\n\t * per-network policy default apply ONLY when this option is left undefined.\n\t *\n\t * HARD-CLAMP: on live `mainnet` the per-network policy default forces\n\t * auto-approve OFF — a real-funds signature is NEVER granted without a\n\t * human in the loop. The policy default never silently auto-approves on\n\t * mainnet; an explicit `autoApprove: true` (or, when this option is\n\t * undefined, `DEVSTACK_AUTO_APPROVE`) is still the author opting in\n\t * deliberately. */\n\treadonly autoApprove?: boolean;\n\t/** Per-network overrides for the dev-convenience policy (the same\n\t * `networkOptions` shape `defineDevstack` takes, forwarded verbatim).\n\t * Only the `autoApproveSigning` field is read here, to resolve the\n\t * auto-approve default for the active network. Omitted ⇒ the built-in\n\t * policy (OFF by default; opt in per-network) applies. The override RECORD is not\n\t * otherwise on disk, so an app that customizes per-network signing must\n\t * thread it here explicitly; the mainnet hard-clamp holds regardless. */\n\treadonly networkOptions?: Readonly<Record<string, unknown>>;\n\t/** Committed, per-network PROD deployments — `{ <net>: () => import('./deployments/<net>.ts') }`,\n\t * each thunk resolving to `{ deployment: NetworkDeployment }` (the typed\n\t * hand-written / `dump-deployment`-emitted file). These are the\n\t * non-local networks a production `build` ships, and the additional\n\t * networks a dev `serve` makes selectable alongside the live local one.\n\t * Each thunk's `deployment` is validated against `NetworkDeploymentSchema`\n\t * at config-load — a malformed committed file fails LOUDLY rather than\n\t * silently injecting a broken network.\n\t *\n\t * DEFAULT (when omitted): AUTO-DISCOVERY of `<root>/deployments/*.ts` (D7\n\t * — \"just drop a file\"). Each filename (sans `.ts`) becomes a live network\n\t * name keyed to a dynamic-`import()` thunk reading the file's `deployment`\n\t * export. Supplying this option OVERRIDES auto-discovery entirely (a\n\t * custom dir / explicit paths). No `deployments/` dir + no option ⇒ no\n\t * committed networks. */\n\treadonly deployments?: Readonly<Record<string, () => Promise<{ deployment: NetworkDeployment }>>>;\n\t/** The network the app opens on when no live local network is present\n\t * (a pure production build, or a dev serve with only committed\n\t * networks). Defaults to the first committed network key. The live\n\t * local network always wins as the default in dev. */\n\treadonly defaultNetwork?: string;\n}\n\n/** A `vite` `Plugin`'s `config` hook receives the partial user config.\n * We only ever read `config.root` and return a `resolve.alias` patch,\n * so a one-field structural subset is enough — this keeps the module\n * loadable without `vite` types (mirroring how the playwright/vitest\n * presets avoid importing their optional peer at module init). */\ninterface ViteUserConfigLike {\n\treadonly root?: string;\n\treadonly command?: 'build' | 'serve';\n}\n\n/** Virtual module id the dev-only HTML injection imports. Vite convention:\n * a `virtual:` specifier resolved to a `\\0`-prefixed id in `resolveId`. */\nconst VIRTUAL_DEV_WALLET_ID = 'virtual:devstack-dev-wallet';\nconst RESOLVED_VIRTUAL_DEV_WALLET_ID = '\\0virtual:devstack-dev-wallet';\n/** The `/@id/` URL form Vite's middleware understands for a `\\0`-prefixed\n * virtual id: the NUL byte is encoded as the literal `__x00__` token (a\n * raw `\\0` in an HTML attribute is mangled to a space, which Vite then\n * misroutes to the SPA fallback). Used for the injected `<script src>`. */\nconst VIRTUAL_DEV_WALLET_SCRIPT_SRC = `/@id/__x00__${VIRTUAL_DEV_WALLET_ID}`;\n\n/** The structural `Plugin` shape we return — `name`, a `config` hook, and\n * the DEV-only dev-wallet-injection hooks. Typed as the loose object Vite\n * accepts (Vite's `Plugin` is structurally compatible) so callers spread\n * it into `plugins: []` without devstack depending on `vite`. */\n/** Vite's `config` hook second arg — `{ command, mode }`. We read only\n * `command` to pick the dev vs prod id-injection source. */\ninterface ViteConfigEnvLike {\n\treadonly command?: 'build' | 'serve';\n}\n\n/** The structural subset of Vite's `ViteDevServer` the `configureServer`\n * hook uses: the chokidar `watcher` (to track the ids file) and the HMR\n * channel (to push a full reload). `ws` is Vite's long-stable channel;\n * `hot` is the newer alias — we use whichever the running Vite exposes. */\ninterface ViteDevServerLike {\n\treadonly watcher: {\n\t\tadd: (paths: string | ReadonlyArray<string>) => void;\n\t\ton: (event: 'change' | 'add', listener: (path: string) => void) => void;\n\t};\n\treadonly ws?: { send: (payload: { type: 'full-reload' }) => void };\n\treadonly hot?: { send: (payload: { type: 'full-reload' }) => void };\n}\n\nexport interface DevstackVitePlugin {\n\treadonly name: string;\n\treadonly config: (\n\t\tconfig: ViteUserConfigLike,\n\t\tenv?: ViteConfigEnvLike,\n\t) => Promise<{\n\t\treadonly resolve: {\n\t\t\treadonly alias: Record<string, string>;\n\t\t\t// Mutable `string[]` (NOT `readonly`): Vite's `ResolveOptions.dedupe`\n\t\t\t// and `DepOptimizationConfig.include` are both `string[] | undefined`,\n\t\t\t// and a `readonly string[]` return makes the whole `config` hook\n\t\t\t// unassignable to Vite's `Plugin` type in a consuming app's\n\t\t\t// `vite.config.ts`/`vitest.config.ts`. The value we return\n\t\t\t// (`resolvableLitDedupe(root)`, a freshly built array) is already\n\t\t\t// mutable.\n\t\t\treadonly dedupe?: string[];\n\t\t};\n\t\treadonly optimizeDeps?: { readonly include: string[] };\n\t\t/** `define` injecting the deployment envelope as\n\t\t * `__DEVSTACK_DEPLOYMENT__`. In a prod `build` this is the static\n\t\t * envelope literal; in dev `serve` it is a reference to the\n\t\t * `globalThis.__DEVSTACK_DEPLOYMENT_LIVE__` runtime global the\n\t\t * `transformIndexHtml` hook sets fresh per page load, so a republished\n\t\t * id reaches the app on reload (`define` is fixed for the dev server's\n\t\t * lifetime and could never hot-update). */\n\t\treadonly define: Record<string, string>;\n\t}>;\n\t/** Capture the resolved command so injection is DEV-only. */\n\treadonly configResolved: (config: ViteUserConfigLike) => void;\n\t/** Dev-only: watch the live `deployment.json` and full-reload the page\n\t * when it changes, so a republished package id (rewritten by the\n\t * supervisor's post-acquire hook) reaches the running app. */\n\treadonly configureServer: (server: ViteDevServerLike) => void;\n\t/** Resolve the dev-wallet virtual module. */\n\treadonly resolveId: (id: string) => string | undefined;\n\t/** Emit the dev-wallet register module (or a no-op when not applicable). */\n\treadonly load: (id: string) => string | undefined;\n\t/** Inject the dev-only HTML tags: the `__DEVSTACK_DEPLOYMENT_LIVE__`\n\t * global (read fresh from the deployment file per request) and, when the\n\t * app carries the dev wallet, the `<script type=\"module\">` importing the\n\t * virtual module. The return shape mirrors a structural subset of Vite's\n\t * `IndexHtmlTransformResult` so the plugin stays assignable to Vite's\n\t * `Plugin` without devstack importing `vite`. */\n\treadonly transformIndexHtml: (html: string) =>\n\t\t| {\n\t\t\t\thtml: string;\n\t\t\t\ttags: Array<{\n\t\t\t\t\ttag: string;\n\t\t\t\t\tattrs?: Record<string, string | boolean>;\n\t\t\t\t\tchildren?: string;\n\t\t\t\t\tinjectTo: 'head' | 'head-prepend';\n\t\t\t\t}>;\n\t\t }\n\t\t| undefined;\n}\n\n/** Best-effort, SYNC read of a string-valued field at `dottedPath` (e.g.\n * `'codegen.deploymentFile'`, `'identity.network'`) from the active stack's\n * manifest. Walks each path segment as a nested object, returning the leaf\n * value, or `null` on ANY miss (absent / partially-written / version-\n * mismatched manifest, a non-object hop, a missing or non-string/empty\n * leaf). We read + `JSON.parse` directly (rather than the schema-decoding\n * `readStackContext`, which drops the `codegen` field in its projection\n * and throws on a version mismatch) so an out-of-date or partially-written\n * manifest degrades gracefully instead of crashing the dev server. Never\n * throws — the single discover→parse→guard→degrade-to-null reader every\n * manifest field above (`codegen.deploymentFile`, `identity.network`)\n * routes through. */\nconst readManifestObject = (\n\tenv: Readonly<Record<string, string | undefined>>,\n\tcwd: string,\n): unknown => {\n\ttry {\n\t\tconst { stack, stateDir } = resolveDiscoveryEnv(env, { cwd });\n\t\tconst manifestPath = discoverManifestPath({ env, stack, stateDir, cwd });\n\t\tif (manifestPath === undefined || !existsSync(manifestPath)) return null;\n\t\treturn JSON.parse(readFileSync(manifestPath, 'utf8'));\n\t} catch {\n\t\treturn null;\n\t}\n};\n\n/** Walk `dottedPath` over an already-parsed manifest object, returning the\n * non-empty string leaf or `null` on ANY miss (non-object hop, missing /\n * non-string / empty leaf). Lets one parsed manifest serve several field\n * reads without re-parsing. */\nconst pickManifestString = (manifest: unknown, dottedPath: string): string | null => {\n\tlet node = manifest;\n\tfor (const segment of dottedPath.split('.')) {\n\t\tif (typeof node !== 'object' || node === null) return null;\n\t\tnode = (node as Record<string, unknown>)[segment];\n\t}\n\treturn typeof node === 'string' && node.length > 0 ? node : null;\n};\n\nconst readManifestField = (\n\tenv: Readonly<Record<string, string | undefined>>,\n\tcwd: string,\n\tdottedPath: string,\n): string | null => pickManifestString(readManifestObject(env, cwd), dottedPath);\n\n/** The gitignored `deployment.json` path the boot wrote for the active\n * stack (`codegen.deploymentFile`), or `null` on any miss. */\nconst readIdsFileFromManifest = (\n\tenv: Readonly<Record<string, string | undefined>>,\n\tcwd: string,\n): string | null => readManifestField(env, cwd, 'codegen.deploymentFile');\n\n/** SYNC read + schema-decode of a deployment ENVELOPE file. The MISSING-file\n * case (absent path, no file on disk) collapses to `null` so the Vite config\n * load degrades gracefully when no stack has booted / no committed file is\n * wired. A PRESENT-but-malformed file is NOT swallowed: it flows through the\n * shared {@link decodeDeployment}, which THROWS on bad JSON or a shape that\n * violates `DevstackDeploymentSchema` — surfacing a genuinely broken\n * deployment at config-load instead of silently injecting `null`. */\nconst readDeploymentFile = (file: string | null): DevstackDeployment | null => {\n\tif (file === null || !existsSync(file)) return null;\n\treturn decodeDeployment(readFileSync(file, 'utf8'));\n};\n\n/** The LIVE local-stack deployment envelope for the active stack — the\n * `deployment.json` boot wrote (via the manifest `codegen.deploymentFile`),\n * or `null` on any miss. Read fresh so a republish reaches the app. */\nconst readLiveEnvelope = (\n\tenv: Readonly<Record<string, string | undefined>>,\n\troot: string,\n): DevstackDeployment | null => readDeploymentFile(readIdsFileFromManifest(env, root));\n\n/** Resolve + validate the committed per-network deployments — the\n * `deployments` option's thunks, each `() => Promise<{ deployment }>`. Each\n * `deployment` is validated against `NetworkDeploymentSchema` (loud-fail on a\n * malformed committed file) and stamped `{ network: <key>, local: false }` so\n * the envelope key is authoritative and the deploy filter treats it as a real\n * (non-local) network. Empty when no `deployments` are supplied. */\nconst resolveCommittedNetworks = async (\n\tdeployments: Readonly<Record<string, () => Promise<{ deployment: NetworkDeployment }>>>,\n): Promise<Record<string, NetworkDeployment>> => {\n\tconst committed: Record<string, NetworkDeployment> = {};\n\tfor (const [net, thunk] of Object.entries(deployments)) {\n\t\tconst loaded = await thunk();\n\t\tconst validated = decodeNetworkDeployment(loaded.deployment, `deployments['${net}']`);\n\t\tcommitted[net] = { ...validated, network: net, local: false };\n\t}\n\treturn committed;\n};\n\n/** Auto-discover the committed per-network deployment thunks by globbing\n * `<root>/deployments/*.ts` (D7 — \"just drop a file\" UX). Each filename\n * (sans `.ts`) becomes a live network name keyed to a thunk that\n * dynamically `import()`s the absolute file path and reads its `deployment`\n * export (the `dump-deployment`-emitted / hand-written\n * `export const deployment = {…} satisfies AppNetworkDeployment`). Returns\n * `{}` when there is no `deployments/` dir (a clean clone / template — a\n * pure types-only `deployment.ts` with an empty `ProvidedDeployments`). The\n * explicit `deployments` option overrides this entirely (custom dir/paths).\n * File CONTENTS are not read here — only the import is wired; ids load when\n * Vite transforms the thunk at build/serve. */\nconst autoDiscoverDeployments = (\n\troot: string,\n): Record<string, () => Promise<{ deployment: NetworkDeployment }>> => {\n\tconst deploymentsDir = resolve(root, 'deployments');\n\tif (!existsSync(deploymentsDir)) return {};\n\tconst thunks: Record<string, () => Promise<{ deployment: NetworkDeployment }>> = {};\n\tlet files: ReadonlyArray<string>;\n\ttry {\n\t\tfiles = readdirSync(deploymentsDir);\n\t} catch {\n\t\treturn {};\n\t}\n\tfor (const file of files) {\n\t\tif (!file.endsWith('.ts') || file.endsWith('.d.ts')) continue;\n\t\tconst net = file.slice(0, -'.ts'.length);\n\t\tif (net.length === 0) continue;\n\t\tconst abs = resolve(deploymentsDir, file);\n\t\t// `/* @vite-ignore */` — the specifier is a runtime-computed absolute\n\t\t// path (not a static literal Vite/Rollup can pre-analyze). Both Vite's\n\t\t// dev server and `vite build` resolve it as a dynamic import.\n\t\tthunks[net] = () =>\n\t\t\timport(/* @vite-ignore */ pathToFileURL(abs).href) as Promise<{\n\t\t\t\tdeployment: NetworkDeployment;\n\t\t\t}>;\n\t}\n\treturn thunks;\n};\n\n/** The committed per-network deployments to merge — the explicit\n * `deployments` option when supplied, else auto-discovery of\n * `<root>/deployments/*.ts` (D7). */\nconst resolveDeploymentThunks = (\n\toptions: DevstackVitePluginOptions,\n\troot: string,\n): Record<string, () => Promise<{ deployment: NetworkDeployment }>> =>\n\toptions.deployments !== undefined ? { ...options.deployments } : autoDiscoverDeployments(root);\n\n/** The first key of a record, or `undefined` when empty. */\nconst firstKey = (record: Record<string, unknown>): string | undefined => Object.keys(record)[0];\n\n/** Merge the committed + live deployments into the envelope to inject as\n * `__DEVSTACK_DEPLOYMENT__`.\n * - `command === 'build'` (DEPLOY): the committed, NON-local networks only —\n * the live local network is dropped. Empty ⇒ `null` (the generated\n * resolver throws loudly at access time).\n * - dev `serve` (and config-load default): the committed networks OVERLAID\n * with the live local network(s), the live default winning. No live\n * envelope ⇒ committed only (default = `defaultNetwork` option / first\n * committed key).\n * The live envelope's entries are forced `local: true` (they ARE the running\n * local stack) so a later `build` would drop them. */\nconst mergeDeployment = (\n\tcommand: 'build' | 'serve' | undefined,\n\tcommitted: Record<string, NetworkDeployment>,\n\tlive: DevstackDeployment | null,\n\tdefaultNetworkOption: string | undefined,\n): DevstackDeployment | null => {\n\tif (command === 'build') {\n\t\tif (Object.keys(committed).length === 0) return null;\n\t\tconst fallback = firstKey(committed)!;\n\t\t// A pure prod build ships NO dev accounts (committed-only, network-\n\t\t// agnostic identities exist only when running through devstack).\n\t\treturn {\n\t\t\tdefaultNetwork:\n\t\t\t\tdefaultNetworkOption !== undefined && committed[defaultNetworkOption] !== undefined\n\t\t\t\t\t? defaultNetworkOption\n\t\t\t\t\t: fallback,\n\t\t\tnetworks: committed,\n\t\t\taccounts: {},\n\t\t};\n\t}\n\t// Dev serve / config-load default: overlay the live local network(s).\n\tconst networks: Record<string, NetworkDeployment> = { ...committed };\n\tif (live !== null) {\n\t\tfor (const [net, dep] of Object.entries(live.networks)) {\n\t\t\tnetworks[net] = { ...dep, network: net, local: true };\n\t\t}\n\t\t// Dev `accounts` ride the ENVELOPE (network-agnostic dev identities):\n\t\t// the live local stack supplies them; committed `deployments/*.ts`\n\t\t// networks carry none. Carry them through onto the merged envelope.\n\t\treturn {\n\t\t\tdefaultNetwork: live.defaultNetwork,\n\t\t\tnetworks,\n\t\t\taccounts: live.accounts ?? {},\n\t\t};\n\t}\n\tconst fallback = firstKey(networks);\n\tif (fallback === undefined) return null;\n\treturn {\n\t\tdefaultNetwork:\n\t\t\tdefaultNetworkOption !== undefined && networks[defaultNetworkOption] !== undefined\n\t\t\t\t? defaultNetworkOption\n\t\t\t\t: fallback,\n\t\tnetworks,\n\t\taccounts: {},\n\t};\n};\n\n/**\n * Build the devstack Vite plugin that aliases `options.alias`\n * (default `@generated`) at the committed `<root>/src/generated` tree.\n *\n * // vite.config.ts\n * import { devstackVitePlugin } from '@mysten-incubation/devstack/vite';\n * export default defineConfig({ plugins: [devstackVitePlugin()] });\n * // or devstackVitePlugin({ alias: '@gen' })\n *\n * The plugin's `config` hook merges `resolve.alias[<prefix>] = <dir>`\n * into the user config (Vite deep-merges the returned partial). Async\n * (it awaits the committed `deployments` thunks); reads `process.env` +\n * the manifest (for `__DEVSTACK_DEPLOYMENT__`) once at config-load.\n */\n\n/** The on-disk dev-wallet pairing-token for the active stack, given the\n * already-resolved `deploymentFile` path (the manifest's\n * `codegen.deploymentFile`). The token lives in a `0o600` side-channel file\n * (`<stackRoot>/wallet/token`, written by `pairing.ts`), NEVER in the\n * world-readable `deployment.json`. The boot writes `deployment.json` into the\n * SAME `<stackRoot>`, so the token sits at `<dirname(deploymentFile)>/wallet/\n * token`. Read in Node by the `load` hook (which runs server-side) so the\n * secret stays off the manifest/deployment surface. Returns `null` on any\n * miss (including a `null` deployment file). */\nconst readDevWalletToken = (deploymentFile: string | null): string | null => {\n\tif (deploymentFile === null) return null;\n\tconst tokenFile = resolve(dirname(deploymentFile), 'wallet', 'token');\n\ttry {\n\t\tif (!existsSync(tokenFile)) return null;\n\t\tconst raw = readFileSync(tokenFile, 'utf8').trim();\n\t\treturn /^[0-9a-f]{32}$/.test(raw) ? raw : null;\n\t} catch {\n\t\treturn null;\n\t}\n};\n\n/** Escape a `JSON.stringify` result for safe embedding in an inline\n * `<script>` element's text. Unlike an external file, inline-script text is\n * parsed by the HTML tokenizer first, so a raw `</script>` (or `<!--`) inside\n * any string value would terminate the tag early — and a lone `<` is enough to\n * matter. We escape `<` / `>` / `&` to their `\\uXXXX` JS-string forms (valid\n * inside JSON-as-JS, and inert to the HTML tokenizer) and the U+2028/U+2029\n * line separators (legal in JSON strings but illegal as raw JS line\n * terminators in older engines). Used ONLY on the inline-script injection path\n * — the `define` build path embeds into a JS module, not HTML, so it needs no\n * such escaping. */\nconst escapeForInlineScript = (json: string): string =>\n\tjson\n\t\t.replace(/</g, '\\\\u003c')\n\t\t.replace(/>/g, '\\\\u003e')\n\t\t.replace(/&/g, '\\\\u0026')\n\t\t// U+2028 / U+2029: legal in JSON strings but illegal as raw JS line\n\t\t// terminators in older engines — matched by code point so no literal\n\t\t// separator lives in this source.\n\t\t.replace(/[\\u2028\\u2029]/g, (ch) => '\\\\u' + ch.charCodeAt(0).toString(16).padStart(4, '0'));\n\n/** A boolean env flag is truthy when set to `'1'` or `'true'`\n * (case-insensitive). Shared by the auto-approve + e2e env readers. */\nconst isTruthyEnvFlag = (raw: string | undefined): boolean =>\n\traw === '1' || raw?.toLowerCase() === 'true';\n\n/** Read the `DEVSTACK_AUTO_APPROVE` env (`'1'`/`'true'`, case-insensitive). */\nconst autoApproveFromEnv = (env: Readonly<Record<string, string | undefined>>): boolean =>\n\tisTruthyEnvFlag(env['DEVSTACK_AUTO_APPROVE']);\n\n/** True under a Playwright e2e run (`DEVSTACK_E2E` set to `'1'`/`'true'`,\n * case-insensitive). DEDICATED signal — distinct from `DEVSTACK_AUTO_APPROVE`\n * — driving the injected `__DEVSTACK_E2E__` global. Gates dApp Kit's\n * `autoConnect` ON only under e2e, so a normal `pnpm dev` serve exercises the\n * real connect UX. A prod `vite build` never sets the env ⇒ injects `false`\n * (tree-shakeable). */\nconst e2eFromEnv = (env: Readonly<Record<string, string | undefined>>): boolean =>\n\tisTruthyEnvFlag(env['DEVSTACK_E2E']);\n\n/** Lit packages deduped to a single instance. The injected dev-wallet UI and\n * the app's dapp-kit UI are both Lit-based; if Vite loads two Lit copies they\n * register custom elements in separate realms, and the second realm's element\n * classes are unknown to the global `customElements` registry — so re-rendering\n * the dev-wallet UI (e.g. on disconnect/reconnect) throws `Illegal constructor`\n * and leaves the app in an unusable connection state. */\nconst LIT_DEDUPE = ['lit', 'lit-html', 'lit-element', '@lit/reactive-element'] as const;\n\n/** Filter {@link LIT_DEDUPE} to the packages actually installed at the app\n * root (`<root>/node_modules/<pkg>`). `resolve.dedupe` forces Vite to\n * resolve each listed package from the ROOT copy — but under pnpm's strict\n * layout a package is only surfaced at the root when the app declares it as\n * a direct dependency. Listing a package that is merely a phantom\n * (transitive-only, reachable solely under `@mysten/dapp-kit-core`'s nested\n * store dir) makes Vite's resolver look for a root copy that does not exist\n * and FAIL the production build with `Rollup failed to resolve import\n * \"lit\"`. So we dedupe only what the app truly hoists: the `app` template\n * declares `lit` (all dapp-kit-core Lit usage routes through the `lit`\n * meta-package, so one `lit` ⇒ one nested `@lit/reactive-element`); the\n * sub-packages stay phantom and are correctly dropped here. */\nconst resolvableLitDedupe = (root: string): string[] =>\n\tLIT_DEDUPE.filter((pkg) => existsSync(resolve(root, 'node_modules', ...pkg.split('/'))));\n\n/** The dev-wallet entry points the injected virtual module imports. They are\n * reached only through the `<script>` this plugin adds in `transformIndexHtml`,\n * so Vite's initial dep scan never sees them and re-optimizes mid-session the\n * first time the page loads them — and that late, separate optimize pass pulls\n * a SECOND Lit instance (see {@link LIT_DEDUPE}). Pre-bundling them up front via\n * `optimizeDeps.include` keeps the whole dev-wallet UI graph in the initial pass,\n * sharing one Lit. */\nconst DEV_WALLET_OPTIMIZE_ENTRIES = [\n\t'@mysten-incubation/dev-wallet/inject',\n\t'@mysten-incubation/dev-wallet/adapters',\n] as const;\n\n/** True when `@mysten-incubation/dev-wallet` is installed at the app root —\n * i.e. the app actually depends on the dev wallet (the `app` template does;\n * the headless `ts` template does not). Checked by package presence rather\n * than `require.resolve('.../inject')`, whose `exports` entry declares only an\n * `import` condition (the CJS resolver throws `ERR_PACKAGE_PATH_NOT_EXPORTED`).\n * Best-effort: if it's absent we never hand Vite an `optimizeDeps.include` it\n * can't resolve (which would fail the dep scan). */\nconst devWalletInstalled = (root: string): boolean =>\n\texistsSync(resolve(root, 'node_modules', '@mysten-incubation', 'dev-wallet', 'package.json'));\n\nexport const devstackVitePlugin = (options: DevstackVitePluginOptions = {}): DevstackVitePlugin => {\n\tconst alias = options.alias ?? DEFAULT_GENERATED_ALIAS;\n\tconst injectDevWallet = options.injectDevWallet ?? true;\n\n\t// Captured across hooks. `config` runs first (alias resolution + committed\n\t// deployment validation), `configResolved` records the command (DEV-gate),\n\t// and `load` reads the dev-wallet connection off the injected deployment +\n\t// the secret token off its side-channel file.\n\tlet resolvedRoot = process.cwd();\n\tlet isServe = false;\n\t// Whether the app carries the dev wallet (`devWalletInstalled(root)`),\n\t// computed ONCE in the `config` hook and reused by `transformIndexHtml` so\n\t// the per-request HTML injection doesn't re-`stat` the package on disk.\n\tlet devWalletPresent = false;\n\t// The committed per-network deployments, resolved + validated ONCE in the\n\t// async `config` hook (the thunks are async; the runtime-global path in\n\t// `transformIndexHtml` is sync). Stable for the dev server's lifetime, so\n\t// the per-request HTML injection overlays the fresh live network on top of\n\t// these without re-awaiting.\n\tlet resolvedCommittedNetworks: Record<string, NetworkDeployment> = {};\n\t// Per-republish cache of the live local envelope. `readLiveEnvelope` walks\n\t// the manifest + reads/decodes `deployment.json` on every call, but the file\n\t// only changes on a republish — which the `configureServer` watcher already\n\t// observes. So we read a PRESENT envelope once (lazily, on the first\n\t// `transformIndexHtml`) and reuse it until the watcher invalidates it, rather\n\t// than re-reading per page load. `liveEnvelopeFresh === false` is the\n\t// \"needs (re)read\" sentinel.\n\tlet liveEnvelopeFresh = false;\n\tlet liveEnvelopeCache: DevstackDeployment | null = null;\n\tconst readLiveEnvelopeCached = (\n\t\tenv: Readonly<Record<string, string | undefined>>,\n\t): DevstackDeployment | null => {\n\t\tif (liveEnvelopeFresh) return liveEnvelopeCache;\n\t\tconst live = readLiveEnvelope(env, resolvedRoot);\n\t\t// Only memoize a PRESENT envelope. A `null` result (no live stack yet)\n\t\t// must NOT stick: the `configureServer` watcher that invalidates this\n\t\t// cache only registers when a manifest already exists at server start\n\t\t// (`if (idsFile === null) return`), so a standalone `vite dev` launched\n\t\t// BEFORE `devstack up` has no invalidation hook — caching `null` here\n\t\t// would pin it forever, and the app would never pick up the stack even\n\t\t// after it boots and the page reloads. Re-reading while absent is cheap\n\t\t// (no `deployment.json` to decode) and lets the first post-boot reload\n\t\t// resolve the freshly-written envelope.\n\t\tif (live !== null) {\n\t\t\tliveEnvelopeCache = live;\n\t\t\tliveEnvelopeFresh = true;\n\t\t} else {\n\t\t\tliveEnvelopeCache = null;\n\t\t}\n\t\treturn live;\n\t};\n\n\treturn {\n\t\tname: 'devstack:generated-alias',\n\t\tconfig: async (config: ViteUserConfigLike, configEnv?: ViteConfigEnvLike) => {\n\t\t\tconst root = config.root ?? process.cwd();\n\t\t\tresolvedRoot = root;\n\t\t\tconst env = process.env as Readonly<Record<string, string | undefined>>;\n\t\t\t// `command` comes from the second hook arg (`{ command, mode }`);\n\t\t\t// fall back to `config.command` (some callers pass it on the\n\t\t\t// config). Default the UNKNOWN case to `build` (build-safe): only an\n\t\t\t// EXPLICIT `serve` takes the live local-stack id-injection path, so a\n\t\t\t// programmatic `vite.build()` that omits the env arg never bakes\n\t\t\t// dev-stack ids into a production bundle.\n\t\t\tconst command = configEnv?.command ?? config.command ?? 'build';\n\t\t\t// Explicit `generatedDir` wins (relative → resolved against the\n\t\t\t// Vite root). Otherwise always the committed `<root>/src/generated`\n\t\t\t// tree — the single source of bindings written by `devstack\n\t\t\t// codegen`; ids resolve at runtime via `__DEVSTACK_DEPLOYMENT__`, so\n\t\t\t// the same tree serves every stack.\n\t\t\tconst explicit = options.generatedDir;\n\t\t\tconst generatedDir =\n\t\t\t\texplicit !== undefined ? resolve(root, explicit) : resolve(root, GENERATED_SUBPATH);\n\t\t\t// Return a partial config; Vite deep-merges it. `resolve.dedupe`\n\t\t\t// pins a single Lit copy, and — when this app carries the dev\n\t\t\t// wallet — we pre-bundle the injected dev-wallet entries so Vite\n\t\t\t// never re-optimizes them mid-session into a second Lit realm\n\t\t\t// (see the constants above).\n\t\t\t// `devWalletInstalled` stats the package on disk; do it once here and\n\t\t\t// reuse the result in `transformIndexHtml` (the root is stable for the\n\t\t\t// server's lifetime).\n\t\t\tdevWalletPresent = devWalletInstalled(root);\n\t\t\tconst includeDevWallet = injectDevWallet && devWalletPresent;\n\t\t\t// Resolve + validate the committed per-network deployments ONCE (the\n\t\t\t// thunks are async; the per-request HTML path is sync). Captured so\n\t\t\t// `transformIndexHtml` overlays the fresh live network on top of\n\t\t\t// these without re-awaiting. A malformed committed file throws here,\n\t\t\t// at config-load.\n\t\t\tresolvedCommittedNetworks = await resolveCommittedNetworks(\n\t\t\t\tresolveDeploymentThunks(options, root),\n\t\t\t);\n\t\t\t// Inject the deployment envelope the generated `config-runtime.ts`\n\t\t\t// resolver reads as `__DEVSTACK_DEPLOYMENT__`.\n\t\t\t// - PROD build: bake the static merged envelope literal (committed,\n\t\t\t// non-local networks only).\n\t\t\t// - DEV server (`vite dev`): point the identifier at the\n\t\t\t// `__DEVSTACK_DEPLOYMENT_LIVE__` runtime global the\n\t\t\t// `transformIndexHtml` hook sets fresh per page load — `define`\n\t\t\t// is fixed for the dev server's lifetime, so baking the value\n\t\t\t// would pin the FIRST id forever and a republish would never\n\t\t\t// reach the app. A distinct global name avoids a single-pass\n\t\t\t// `define` token collision with `__DEVSTACK_DEPLOYMENT__`.\n\t\t\t// - VITEST: vitest ALSO reports `command === 'serve'`, but it runs\n\t\t\t// no `transformIndexHtml` (no HTML), so the live global is never\n\t\t\t// set — and esbuild's stricter `define` rejects an operator\n\t\t\t// expression anyway. Bake a literal instead: the Vite config loads\n\t\t\t// BEFORE the test stack boots, so the merged envelope is null and\n\t\t\t// the resolver falls back to the `DEVSTACK_DEPLOYMENT_FILE` env the\n\t\t\t// vitest globalSetup points at the freshly-booted stack (see\n\t\t\t// vitest/global-setup.ts).\n\t\t\t// The define VALUE must be esbuild-valid (a JSON literal or a\n\t\t\t// member-access chain) — note the bare `globalThis.…`, NOT a\n\t\t\t// parenthesised `(… ?? null)`; `config-runtime.ts` already guards the\n\t\t\t// `typeof … === 'undefined'` case.\n\t\t\t// Merge the deployment envelope to bake/point at — reusing the\n\t\t\t// committed networks just resolved above rather than re-resolving the\n\t\t\t// thunks. `build` drops the live local stack; `serve`/config-load\n\t\t\t// overlays it.\n\t\t\tconst injectedLive = command === 'build' ? null : readLiveEnvelope(env, root);\n\t\t\tconst injectedDeployment = mergeDeployment(\n\t\t\t\tcommand,\n\t\t\t\tresolvedCommittedNetworks,\n\t\t\t\tinjectedLive,\n\t\t\t\toptions.defaultNetwork,\n\t\t\t);\n\t\t\tconst isVitest = env['VITEST'] !== undefined;\n\t\t\tconst deploymentDefine =\n\t\t\t\tcommand === 'serve' && !isVitest\n\t\t\t\t\t? 'globalThis.__DEVSTACK_DEPLOYMENT_LIVE__'\n\t\t\t\t\t: JSON.stringify(injectedDeployment ?? null);\n\t\t\treturn {\n\t\t\t\tresolve: {\n\t\t\t\t\t// A bare-prefix alias (no trailing `/`) matches both\n\t\t\t\t\t// `@generated` and `@generated/foo.js` under Vite's default\n\t\t\t\t\t// string-alias resolution.\n\t\t\t\t\talias: {\n\t\t\t\t\t\t[alias]: generatedDir,\n\t\t\t\t\t},\n\t\t\t\t\tdedupe: resolvableLitDedupe(root),\n\t\t\t\t},\n\t\t\t\tdefine: {\n\t\t\t\t\t__DEVSTACK_DEPLOYMENT__: deploymentDefine,\n\t\t\t\t\t// `true` ONLY under a Playwright e2e run (`DEVSTACK_E2E`), so the\n\t\t\t\t\t// app's dApp Kit `autoConnect` is on for headless e2e and OFF for a\n\t\t\t\t\t// normal `pnpm dev` serve. A prod `vite build` never sets the env ⇒\n\t\t\t\t\t// `false` (tree-shakeable). Distinct from `DEVSTACK_AUTO_APPROVE`.\n\t\t\t\t\t__DEVSTACK_E2E__: JSON.stringify(e2eFromEnv(env)),\n\t\t\t\t},\n\t\t\t\t...(includeDevWallet\n\t\t\t\t\t? { optimizeDeps: { include: [...DEV_WALLET_OPTIMIZE_ENTRIES] } }\n\t\t\t\t\t: {}),\n\t\t\t};\n\t\t},\n\n\t\tconfigResolved: (config: ViteUserConfigLike) => {\n\t\t\t// DEV-gate: only a `serve` command injects the dev wallet. A\n\t\t\t// production `vite build` injects nothing.\n\t\t\tisServe = config.command !== 'build';\n\t\t},\n\n\t\tresolveId: (id: string) => {\n\t\t\tif (id === VIRTUAL_DEV_WALLET_ID) return RESOLVED_VIRTUAL_DEV_WALLET_ID;\n\t\t\treturn undefined;\n\t\t},\n\n\t\tload: (id: string): string | undefined => {\n\t\t\tif (id !== RESOLVED_VIRTUAL_DEV_WALLET_ID) return undefined;\n\t\t\tconst env = process.env as Readonly<Record<string, string | undefined>>;\n\t\t\t// The dev-wallet connection metadata rides the injected deployment's\n\t\t\t// `values['dev-wallet'].connection` channel (non-secret); the secret\n\t\t\t// pairing token stays in its `0o600` side-channel file, read HERE in\n\t\t\t// Node (the `load` hook runs server-side) so it never lands in the\n\t\t\t// world-readable `deployment.json`. Graceful no-op when injection is\n\t\t\t// off, not a dev serve, or no live stack has written the token yet\n\t\t\t// (no `devstack up`). The presence of BOTH the deployment file (for the\n\t\t\t// connection) and the token gate injection — the browser side resolves\n\t\t\t// the connection off the injected envelope.\n\t\t\t// Parse the manifest ONCE and read both fields it carries below — the\n\t\t\t// `codegen.deploymentFile` (locating the token side-channel) and the\n\t\t\t// `identity.network` (the active network for the auto-approve policy) —\n\t\t\t// off the same parsed object rather than discovering + parsing twice.\n\t\t\tconst manifest = readManifestObject(env, resolvedRoot);\n\t\t\tconst deploymentFile = pickManifestString(manifest, 'codegen.deploymentFile');\n\t\t\tconst token = readDevWalletToken(deploymentFile);\n\t\t\tif (!injectDevWallet || !isServe || token === null) {\n\t\t\t\treturn 'export {};';\n\t\t\t}\n\t\t\t// Auto-approve resolution (highest precedence first): explicit\n\t\t\t// `options.autoApprove`, then the `DEVSTACK_AUTO_APPROVE` env, then\n\t\t\t// the active network's `autoApproveSigning` per-network policy. The\n\t\t\t// policy defaults OFF on every network — a normal `pnpm dev` exercises\n\t\t\t// the real connect + approve UX, headless e2e sets the env, and an\n\t\t\t// author can opt a network back in via `networkOptions`. Live\n\t\t\t// `mainnet` is additionally hard-clamped OFF, so a stack booted\n\t\t\t// against real mainnet never silently auto-approves a real-funds\n\t\t\t// signature. The active network comes from the manifest\n\t\t\t// boot wrote (`identity.network`); absent that we conservatively\n\t\t\t// resolve as `mainnet` (auto-approve OFF) rather than assuming a\n\t\t\t// dev network. The override RECORD isn't on disk — `networkOptions`\n\t\t\t// is read from the plugin option when the app threads it.\n\t\t\tconst network = pickManifestString(manifest, 'identity.network') ?? 'mainnet';\n\t\t\tconst netOpts = resolveNetworkOptions(network, options.networkOptions);\n\t\t\tconst autoApprove =\n\t\t\t\toptions.autoApprove ?? (autoApproveFromEnv(env) || netOpts.autoApproveSigning);\n\t\t\t// Browser-side registration source the dev server transpiles. It reads\n\t\t\t// the dev-only account name→address map (`resolveAccounts()`) and the\n\t\t\t// non-secret dev-wallet connection (`optionalValue(dep, 'dev-wallet',\n\t\t\t// 'connection')`) off the injected deployment envelope (via the\n\t\t\t// generated `config-runtime.ts`), and the FULL network set the app\n\t\t\t// supports off the generated `config.networks` map (each with its\n\t\t\t// routed `rpc` + optional `faucet`). The wallet advertises each network\n\t\t\t// as a wallet-standard chain and operates on whichever dApp Kit has\n\t\t\t// selected, so it PERSISTS across a UI `switchNetwork` (registered once;\n\t\t\t// only the active chain changes). The token is baked in from the Node\n\t\t\t// read above — the only secret on this surface.\n\t\t\treturn [\n\t\t\t\t`import { registerDevstackDevWallet } from '@mysten-incubation/dev-wallet/inject';`,\n\t\t\t\t`import { config as __devstackConfig } from '${alias}/config.js';`,\n\t\t\t\t`import { resolveAccounts, optionalValue } from '${alias}/config-runtime.js';`,\n\t\t\t\t`const __devstackToken = ${JSON.stringify(token)};`,\n\t\t\t\t// name → address (from the injected envelope's dev `accounts`),\n\t\t\t\t// mapped to the inject API's `{ address }` shape.\n\t\t\t\t`const __devstackAccounts = Object.fromEntries(`,\n\t\t\t\t` Object.entries(resolveAccounts()).map(([__name, __address]) => [`,\n\t\t\t\t` __name,`,\n\t\t\t\t` { name: __name, address: __address },`,\n\t\t\t\t` ]),`,\n\t\t\t\t`);`,\n\t\t\t\t// The full routed network set (live local + any committed live).\n\t\t\t\t`const __devstackNetworks = Object.fromEntries(`,\n\t\t\t\t` Object.entries(__devstackConfig.networks).map(([__net, __dep]) => [`,\n\t\t\t\t` __net,`,\n\t\t\t\t` { rpc: __dep.rpc, faucet: __dep.faucet ?? null },`,\n\t\t\t\t` ]),`,\n\t\t\t\t`);`,\n\t\t\t\t// Non-secret connection (server URL + protocol paths), resolved off\n\t\t\t\t// the default network's `values['dev-wallet'].connection`. No\n\t\t\t\t// stack ⇒ undefined ⇒ no injection.\n\t\t\t\t`const __devstackDefaultDep = __devstackConfig.networks[__devstackConfig.defaultNetwork];`,\n\t\t\t\t`const __devstackConnection = __devstackDefaultDep`,\n\t\t\t\t` ? optionalValue(__devstackDefaultDep, 'dev-wallet', 'connection')`,\n\t\t\t\t` : undefined;`,\n\t\t\t\t`if (__devstackConnection) {`,\n\t\t\t\t` registerDevstackDevWallet({`,\n\t\t\t\t` serverOrigin: __devstackConnection.walletUrl,`,\n\t\t\t\t` token: __devstackToken,`,\n\t\t\t\t` accounts: __devstackAccounts,`,\n\t\t\t\t` networks: __devstackNetworks,`,\n\t\t\t\t` defaultNetwork: __devstackConfig.defaultNetwork,`,\n\t\t\t\t` autoApprove: ${autoApprove ? 'true' : 'false'},`,\n\t\t\t\t` mountUI: true,`,\n\t\t\t\t` }).catch((err) => console.error('[devstack] dev-wallet injection failed:', err));`,\n\t\t\t\t`}`,\n\t\t\t].join('\\n');\n\t\t},\n\n\t\tconfigureServer: (server: ViteDevServerLike) => {\n\t\t\tif (!isServe) return;\n\t\t\tconst env = process.env as Readonly<Record<string, string | undefined>>;\n\t\t\tconst idsFile = readIdsFileFromManifest(env, resolvedRoot);\n\t\t\tif (idsFile === null) return;\n\t\t\t// Watch the live deployment and full-reload on change. The supervisor's\n\t\t\t// post-acquire hook rewrites this file whenever a package (re)publishes\n\t\t\t// (e.g. after a Move-source edit fires the file watcher → selective\n\t\t\t// restart); a reload re-runs `transformIndexHtml`, which re-reads the\n\t\t\t// file into `__DEVSTACK_DEPLOYMENT_LIVE__`, so the app picks up the new id.\n\t\t\tserver.watcher.add(idsFile);\n\t\t\tconst reloadOnIdsChange = (changed: string): void => {\n\t\t\t\t// Chokidar fires absolute paths for an absolute `watcher.add`, so\n\t\t\t\t// `resolve(changed)` is normally a no-op; resolve against the Vite\n\t\t\t\t// root anyway so a relative event path can't silently miss.\n\t\t\t\tif (resolve(resolvedRoot, changed) !== resolve(idsFile)) return;\n\t\t\t\t// Invalidate the per-republish live-envelope cache so the next\n\t\t\t\t// `transformIndexHtml` re-reads the rewritten `deployment.json`.\n\t\t\t\tliveEnvelopeFresh = false;\n\t\t\t\tconst channel = server.hot ?? server.ws;\n\t\t\t\tchannel?.send({ type: 'full-reload' });\n\t\t\t};\n\t\t\tserver.watcher.on('change', reloadOnIdsChange);\n\t\t\tserver.watcher.on('add', reloadOnIdsChange);\n\t\t},\n\n\t\ttransformIndexHtml: (html: string) => {\n\t\t\tif (!isServe) return undefined;\n\t\t\tconst env = process.env as Readonly<Record<string, string | undefined>>;\n\t\t\t// Read the live local envelope (cached per republish, invalidated by\n\t\t\t// the `configureServer` watcher on a `deployment.json` change), then\n\t\t\t// overlay it on the committed networks captured at config-load (a sync\n\t\t\t// merge — the committed thunks were already awaited in `config`). Set on\n\t\t\t// a distinct global (`__DEVSTACK_DEPLOYMENT_LIVE__`) the `config` hook's\n\t\t\t// `define` points `__DEVSTACK_DEPLOYMENT__` at; `head-prepend` runs it\n\t\t\t// before any app module.\n\t\t\tconst live = readLiveEnvelopeCached(env);\n\t\t\tconst liveDeployment = mergeDeployment(\n\t\t\t\t'serve',\n\t\t\t\tresolvedCommittedNetworks,\n\t\t\t\tlive,\n\t\t\t\toptions.defaultNetwork,\n\t\t\t);\n\t\t\tconst tags: Array<{\n\t\t\t\ttag: string;\n\t\t\t\tattrs?: Record<string, string | boolean>;\n\t\t\t\tchildren?: string;\n\t\t\t\tinjectTo: 'head' | 'head-prepend';\n\t\t\t}> = [\n\t\t\t\t{\n\t\t\t\t\ttag: 'script',\n\t\t\t\t\tchildren: `globalThis.__DEVSTACK_DEPLOYMENT_LIVE__ = ${escapeForInlineScript(JSON.stringify(liveDeployment ?? null))};`,\n\t\t\t\t\tinjectTo: 'head-prepend',\n\t\t\t\t},\n\t\t\t];\n\t\t\t// Gate the script tag on the app actually carrying the wallet (same\n\t\t\t// condition as the `config` hook's `optimizeDeps` include — reusing the\n\t\t\t// boolean it computed), so a headless app emits zero dev-wallet plumbing\n\t\t\t// rather than a tag that resolves to the no-op virtual module.\n\t\t\tif (injectDevWallet && devWalletPresent) {\n\t\t\t\ttags.push({\n\t\t\t\t\ttag: 'script',\n\t\t\t\t\tattrs: { type: 'module', src: VIRTUAL_DEV_WALLET_SCRIPT_SRC },\n\t\t\t\t\tinjectTo: 'head',\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn { html, tags };\n\t\t},\n\t};\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/build-integrations/vite/index.ts"],"sourcesContent":["// Vite build-integration — `@generated` alias plugin.\n//\n// App source imports Move codegen through a configurable alias prefix\n// (default `@generated`) instead of `./generated`. This plugin points\n// that alias at the committed `<root>/src/generated` tree — the ONE\n// source of bindings, written only by the stack-free `devstack codegen`\n// verb. On-chain ids are NOT baked into that tree; they resolve at\n// runtime via the `__DEVSTACK_DEPLOYMENT__` global (see\n// `mergeDeployment`), so the same generated source serves every\n// stack. Resolution:\n// 1. `options.generatedDir` — explicit escape hatch (relative → root).\n// 2. `<root>/src/generated` — the committed tree, always.\n//\n// Because Playwright's `webServer` runs the app's OWN Vite as a child,\n// the same plugin serves both `pnpm dev` and the e2e dev server\n// automatically. Vitest has its own Vite pipeline, so apps add this\n// plugin to `vitest.config.ts` too — and resolve `@generated` to the\n// same committed tree (the per-stack live tree no longer exists).\n//\n// SYNC + dependency-light, mirroring the playwright/vitest helpers: NO\n// heavy imports at module top-level, and `vite` is NOT imported (it is\n// an app-side dev dependency, not a devstack runtime dep). The return\n// value is a STRUCTURAL Vite `Plugin` — a `{ name, config }` object —\n// typed loosely so this module loads without `vite` installed. The\n// manifest read goes through the same `build-integrations/runtime`\n// machinery the playwright/vitest integrations use.\n\nimport { existsSync, readdirSync, readFileSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nimport { discoverManifestPath, resolveDiscoveryEnv } from '../runtime/index.ts';\nimport { resolveNetworkOptions } from '../../orchestrators/network-options.ts';\nimport {\n\tdecodeDeployment,\n\tdecodeNetworkDeployment,\n\ttype DevstackDeployment,\n\ttype NetworkDeployment,\n} from '../../orchestrators/codegen/deployment.ts';\n\n/** Default import-alias prefix. Customizable via `options.alias` (some\n * apps prefer `@gen`, `~generated`, …). The app MUST use the SAME\n * prefix in three derivable places: this plugin option, the\n * `tsconfig` `paths` entry, and its import specifiers. */\nexport const DEFAULT_GENERATED_ALIAS = '@generated';\n\n/** The committed generated-bindings subpath, relative to the Vite root.\n * The single source of bindings — written by `devstack codegen` — that\n * `@generated` always resolves to (absent the `options.generatedDir`\n * escape hatch). */\nconst GENERATED_SUBPATH = 'src/generated';\n\nexport interface DevstackVitePluginOptions {\n\t/** Import-alias prefix. Default `'@generated'`. */\n\treadonly alias?: string;\n\t/** Explicit generated dir — bypasses manifest discovery entirely\n\t * (escape hatch for unusual layouts / tests). Relative paths\n\t * resolve against the Vite root. */\n\treadonly generatedDir?: string;\n\t/** Inject + register the devstack dev wallet on the page in DEV\n\t * (wallet-standard, so dApp Kit auto-discovers it). Defaults to\n\t * `true`. Production builds (`command === 'build'`) inject nothing\n\t * regardless. */\n\treadonly injectDevWallet?: boolean;\n\t/** Auto-approve all dev-wallet signing requests (headless Playwright /\n\t * in-app \"Open/Join as\" buttons). Defaults to the `DEVSTACK_AUTO_APPROVE`\n\t * env (`'1'`/`'true'`), then to the active network's `autoApproveSigning`\n\t * per-network policy (OFF by default on every network — a normal `pnpm dev`\n\t * shows the real connect + approve UX; opt in per-network). A single\n\t * switch, replacing per-app `VITE_*_AUTO_APPROVE`.\n\t *\n\t * PRECEDENCE: an explicit value here ALWAYS wins — passing `autoApprove:\n\t * false` forces it off even when `DEVSTACK_AUTO_APPROVE` is set, and\n\t * `autoApprove: true` forces it on. The `DEVSTACK_AUTO_APPROVE` env and the\n\t * per-network policy default apply ONLY when this option is left undefined.\n\t *\n\t * HARD-CLAMP: on live `mainnet` the per-network policy default forces\n\t * auto-approve OFF — a real-funds signature is NEVER granted without a\n\t * human in the loop. The policy default never silently auto-approves on\n\t * mainnet; an explicit `autoApprove: true` (or, when this option is\n\t * undefined, `DEVSTACK_AUTO_APPROVE`) is still the author opting in\n\t * deliberately. */\n\treadonly autoApprove?: boolean;\n\t/** Per-network overrides for the dev-convenience policy (the same\n\t * `networkOptions` shape `defineDevstack` takes, forwarded verbatim).\n\t * Only the `autoApproveSigning` field is read here, to resolve the\n\t * auto-approve default for the active network. Omitted ⇒ the built-in\n\t * policy (OFF by default; opt in per-network) applies. The override RECORD is not\n\t * otherwise on disk, so an app that customizes per-network signing must\n\t * thread it here explicitly; the mainnet hard-clamp holds regardless. */\n\treadonly networkOptions?: Readonly<Record<string, unknown>>;\n\t/** Committed, per-network PROD deployments — `{ <net>: () => import('./deployments/<net>.ts') }`,\n\t * each thunk resolving to `{ deployment: NetworkDeployment }` (the typed\n\t * hand-written / `dump-deployment`-emitted file). These are the\n\t * non-local networks a production `build` ships, and the additional\n\t * networks a dev `serve` makes selectable alongside the live local one.\n\t * Each thunk's `deployment` is validated against `NetworkDeploymentSchema`\n\t * at config-load — a malformed committed file fails LOUDLY rather than\n\t * silently injecting a broken network.\n\t *\n\t * DEFAULT (when omitted): AUTO-DISCOVERY of `<root>/deployments/*.ts` (D7\n\t * — \"just drop a file\"). Each filename (sans `.ts`) becomes a live network\n\t * name keyed to a dynamic-`import()` thunk reading the file's `deployment`\n\t * export. Supplying this option OVERRIDES auto-discovery entirely (a\n\t * custom dir / explicit paths). No `deployments/` dir + no option ⇒ no\n\t * committed networks. */\n\treadonly deployments?: Readonly<Record<string, () => Promise<{ deployment: NetworkDeployment }>>>;\n\t/** The network the app opens on when no live local network is present\n\t * (a pure production build, or a dev serve with only committed\n\t * networks). Defaults to the first committed network key. The live\n\t * local network always wins as the default in dev. */\n\treadonly defaultNetwork?: string;\n}\n\n/** A `vite` `Plugin`'s `config` hook receives the partial user config.\n * We only ever read `config.root` and return a `resolve.alias` patch,\n * so a one-field structural subset is enough — this keeps the module\n * loadable without `vite` types (mirroring how the playwright/vitest\n * presets avoid importing their optional peer at module init). */\ninterface ViteUserConfigLike {\n\treadonly root?: string;\n\treadonly command?: 'build' | 'serve';\n}\n\n/** Virtual module id the dev-only HTML injection imports. Vite convention:\n * a `virtual:` specifier resolved to a `\\0`-prefixed id in `resolveId`. */\nconst VIRTUAL_DEV_WALLET_ID = 'virtual:devstack-dev-wallet';\nconst RESOLVED_VIRTUAL_DEV_WALLET_ID = '\\0virtual:devstack-dev-wallet';\n/** The `/@id/` URL form Vite's middleware understands for a `\\0`-prefixed\n * virtual id: the NUL byte is encoded as the literal `__x00__` token (a\n * raw `\\0` in an HTML attribute is mangled to a space, which Vite then\n * misroutes to the SPA fallback). Used for the injected `<script src>`. */\nconst VIRTUAL_DEV_WALLET_SCRIPT_SRC = `/@id/__x00__${VIRTUAL_DEV_WALLET_ID}`;\n\n/** The structural `Plugin` shape we return — `name`, a `config` hook, and\n * the DEV-only dev-wallet-injection hooks. Typed as the loose object Vite\n * accepts (Vite's `Plugin` is structurally compatible) so callers spread\n * it into `plugins: []` without devstack depending on `vite`. */\n/** Vite's `config` hook second arg — `{ command, mode }`. We read only\n * `command` to pick the dev vs prod id-injection source. */\ninterface ViteConfigEnvLike {\n\treadonly command?: 'build' | 'serve';\n}\n\n/** The structural subset of Vite's `ViteDevServer` the `configureServer`\n * hook uses: the chokidar `watcher` (to track the ids file) and the HMR\n * channel (to push a full reload). `ws` is Vite's long-stable channel;\n * `hot` is the newer alias — we use whichever the running Vite exposes. */\ninterface ViteDevServerLike {\n\treadonly watcher: {\n\t\tadd: (paths: string | ReadonlyArray<string>) => void;\n\t\ton: (event: 'change' | 'add', listener: (path: string) => void) => void;\n\t};\n\treadonly ws?: { send: (payload: { type: 'full-reload' }) => void };\n\treadonly hot?: { send: (payload: { type: 'full-reload' }) => void };\n}\n\nexport interface DevstackVitePlugin {\n\treadonly name: string;\n\treadonly config: (\n\t\tconfig: ViteUserConfigLike,\n\t\tenv?: ViteConfigEnvLike,\n\t) => Promise<{\n\t\treadonly resolve: {\n\t\t\treadonly alias: Record<string, string>;\n\t\t\t// Mutable `string[]` (NOT `readonly`): Vite's `ResolveOptions.dedupe`\n\t\t\t// and `DepOptimizationConfig.include` are both `string[] | undefined`,\n\t\t\t// and a `readonly string[]` return makes the whole `config` hook\n\t\t\t// unassignable to Vite's `Plugin` type in a consuming app's\n\t\t\t// `vite.config.ts`/`vitest.config.ts`. The value we return\n\t\t\t// (`resolvableLitDedupe(root)`, a freshly built array) is already\n\t\t\t// mutable.\n\t\t\treadonly dedupe?: string[];\n\t\t};\n\t\treadonly optimizeDeps?: { readonly include: string[] };\n\t\t/** `define` injecting the deployment envelope as\n\t\t * `__DEVSTACK_DEPLOYMENT__`. In a prod `build` this is the static\n\t\t * envelope literal; in dev `serve` it is a reference to the\n\t\t * `globalThis.__DEVSTACK_DEPLOYMENT_LIVE__` runtime global the\n\t\t * `transformIndexHtml` hook sets fresh per page load, so a republished\n\t\t * id reaches the app on reload (`define` is fixed for the dev server's\n\t\t * lifetime and could never hot-update). */\n\t\treadonly define: Record<string, string>;\n\t}>;\n\t/** Capture the resolved command so injection is DEV-only. */\n\treadonly configResolved: (config: ViteUserConfigLike) => void;\n\t/** Dev-only: watch the live `deployment.json` and full-reload the page\n\t * when it changes, so a republished package id (rewritten by the\n\t * supervisor's post-acquire hook) reaches the running app. */\n\treadonly configureServer: (server: ViteDevServerLike) => void;\n\t/** Resolve the dev-wallet virtual module. */\n\treadonly resolveId: (id: string) => string | undefined;\n\t/** Emit the dev-wallet register module (or a no-op when not applicable). */\n\treadonly load: (id: string) => string | undefined;\n\t/** Inject the dev-only HTML tags: the `__DEVSTACK_DEPLOYMENT_LIVE__`\n\t * global (read fresh from the deployment file per request) and, when the\n\t * app carries the dev wallet, the `<script type=\"module\">` importing the\n\t * virtual module. The return shape mirrors a structural subset of Vite's\n\t * `IndexHtmlTransformResult` so the plugin stays assignable to Vite's\n\t * `Plugin` without devstack importing `vite`. */\n\treadonly transformIndexHtml: (html: string) =>\n\t\t| {\n\t\t\t\thtml: string;\n\t\t\t\ttags: Array<{\n\t\t\t\t\ttag: string;\n\t\t\t\t\tattrs?: Record<string, string | boolean>;\n\t\t\t\t\tchildren?: string;\n\t\t\t\t\tinjectTo: 'head' | 'head-prepend';\n\t\t\t\t}>;\n\t\t }\n\t\t| undefined;\n}\n\n/** Best-effort, SYNC read of a string-valued field at `dottedPath` (e.g.\n * `'codegen.deploymentFile'`, `'identity.network'`) from the active stack's\n * manifest. Walks each path segment as a nested object, returning the leaf\n * value, or `null` on ANY miss (absent / partially-written / version-\n * mismatched manifest, a non-object hop, a missing or non-string/empty\n * leaf). We read + `JSON.parse` directly (rather than the schema-decoding\n * `readStackContext`, which drops the `codegen` field in its projection\n * and throws on a version mismatch) so an out-of-date or partially-written\n * manifest degrades gracefully instead of crashing the dev server. Never\n * throws — the single discover→parse→guard→degrade-to-null reader every\n * manifest field above (`codegen.deploymentFile`, `identity.network`)\n * routes through. */\nconst readManifestObject = (\n\tenv: Readonly<Record<string, string | undefined>>,\n\tcwd: string,\n): unknown => {\n\ttry {\n\t\tconst { stack, stateDir } = resolveDiscoveryEnv(env, { cwd });\n\t\tconst manifestPath = discoverManifestPath({ env, stack, stateDir, cwd });\n\t\tif (manifestPath === undefined || !existsSync(manifestPath)) return null;\n\t\treturn JSON.parse(readFileSync(manifestPath, 'utf8'));\n\t} catch {\n\t\treturn null;\n\t}\n};\n\n/** Walk `dottedPath` over an already-parsed manifest object, returning the\n * non-empty string leaf or `null` on ANY miss (non-object hop, missing /\n * non-string / empty leaf). Lets one parsed manifest serve several field\n * reads without re-parsing. */\nconst pickManifestString = (manifest: unknown, dottedPath: string): string | null => {\n\tlet node = manifest;\n\tfor (const segment of dottedPath.split('.')) {\n\t\tif (typeof node !== 'object' || node === null) return null;\n\t\tnode = (node as Record<string, unknown>)[segment];\n\t}\n\treturn typeof node === 'string' && node.length > 0 ? node : null;\n};\n\nconst readManifestField = (\n\tenv: Readonly<Record<string, string | undefined>>,\n\tcwd: string,\n\tdottedPath: string,\n): string | null => pickManifestString(readManifestObject(env, cwd), dottedPath);\n\n/** The gitignored `deployment.json` path the boot wrote for the active\n * stack (`codegen.deploymentFile`), or `null` on any miss. */\nconst readIdsFileFromManifest = (\n\tenv: Readonly<Record<string, string | undefined>>,\n\tcwd: string,\n): string | null => readManifestField(env, cwd, 'codegen.deploymentFile');\n\n/** SYNC read + schema-decode of a deployment ENVELOPE file. The MISSING-file\n * case (absent path, no file on disk) collapses to `null` so the Vite config\n * load degrades gracefully when no stack has booted / no committed file is\n * wired. A PRESENT-but-malformed file is NOT swallowed: it flows through the\n * shared {@link decodeDeployment}, which THROWS on bad JSON or a shape that\n * violates `DevstackDeploymentSchema` — surfacing a genuinely broken\n * deployment at config-load instead of silently injecting `null`. */\nconst readDeploymentFile = (file: string | null): DevstackDeployment | null => {\n\tif (file === null || !existsSync(file)) return null;\n\treturn decodeDeployment(readFileSync(file, 'utf8'));\n};\n\n/** The LIVE local-stack deployment envelope for the active stack — the\n * `deployment.json` boot wrote (via the manifest `codegen.deploymentFile`),\n * or `null` on any miss. Read fresh so a republish reaches the app. */\nconst readLiveEnvelope = (\n\tenv: Readonly<Record<string, string | undefined>>,\n\troot: string,\n): DevstackDeployment | null => readDeploymentFile(readIdsFileFromManifest(env, root));\n\n/** Resolve + validate the committed per-network deployments — the\n * `deployments` option's thunks, each `() => Promise<{ deployment }>`. Each\n * `deployment` is validated against `NetworkDeploymentSchema` (loud-fail on a\n * malformed committed file) and stamped `{ network: <key>, local: false }` so\n * the envelope key is authoritative and the deploy filter treats it as a real\n * (non-local) network. Empty when no `deployments` are supplied. */\nconst resolveCommittedNetworks = async (\n\tdeployments: Readonly<Record<string, () => Promise<{ deployment: NetworkDeployment }>>>,\n): Promise<Record<string, NetworkDeployment>> => {\n\tconst committed: Record<string, NetworkDeployment> = {};\n\tfor (const [net, thunk] of Object.entries(deployments)) {\n\t\tconst loaded = await thunk();\n\t\tconst validated = decodeNetworkDeployment(loaded.deployment, `deployments['${net}']`);\n\t\tcommitted[net] = { ...validated, network: net, local: false };\n\t}\n\treturn committed;\n};\n\n/** Auto-discover the committed per-network deployment thunks by globbing\n * `<root>/deployments/*.ts` (D7 — \"just drop a file\" UX). Each filename\n * (sans `.ts`) becomes a live network name keyed to a thunk that\n * dynamically `import()`s the absolute file path and reads its `deployment`\n * export (the `dump-deployment`-emitted / hand-written\n * `export const deployment = {…} satisfies AppNetworkDeployment`). Returns\n * `{}` when there is no `deployments/` dir (a clean clone / template — a\n * pure types-only `deployment.ts` with an empty `ProvidedDeployments`). The\n * explicit `deployments` option overrides this entirely (custom dir/paths).\n * File CONTENTS are not read here — only the import is wired; ids load when\n * Vite transforms the thunk at build/serve. */\nconst autoDiscoverDeployments = (\n\troot: string,\n): Record<string, () => Promise<{ deployment: NetworkDeployment }>> => {\n\tconst deploymentsDir = resolve(root, 'deployments');\n\tif (!existsSync(deploymentsDir)) return {};\n\tconst thunks: Record<string, () => Promise<{ deployment: NetworkDeployment }>> = {};\n\tlet files: ReadonlyArray<string>;\n\ttry {\n\t\tfiles = readdirSync(deploymentsDir);\n\t} catch {\n\t\treturn {};\n\t}\n\tfor (const file of files) {\n\t\tif (!file.endsWith('.ts') || file.endsWith('.d.ts')) continue;\n\t\tconst net = file.slice(0, -'.ts'.length);\n\t\tif (net.length === 0) continue;\n\t\tconst abs = resolve(deploymentsDir, file);\n\t\t// `/* @vite-ignore */` — the specifier is a runtime-computed absolute\n\t\t// path (not a static literal Vite/Rollup can pre-analyze). Both Vite's\n\t\t// dev server and `vite build` resolve it as a dynamic import.\n\t\tthunks[net] = () =>\n\t\t\timport(/* @vite-ignore */ pathToFileURL(abs).href) as Promise<{\n\t\t\t\tdeployment: NetworkDeployment;\n\t\t\t}>;\n\t}\n\treturn thunks;\n};\n\n/** The committed per-network deployments to merge — the explicit\n * `deployments` option when supplied, else auto-discovery of\n * `<root>/deployments/*.ts` (D7). */\nconst resolveDeploymentThunks = (\n\toptions: DevstackVitePluginOptions,\n\troot: string,\n): Record<string, () => Promise<{ deployment: NetworkDeployment }>> =>\n\toptions.deployments !== undefined ? { ...options.deployments } : autoDiscoverDeployments(root);\n\n/** The first key of a record, or `undefined` when empty. */\nconst firstKey = (record: Record<string, unknown>): string | undefined => Object.keys(record)[0];\n\n/** Merge the committed + live deployments into the envelope to inject as\n * `__DEVSTACK_DEPLOYMENT__`.\n * - `command === 'build'` (DEPLOY): the committed, NON-local networks only —\n * the live local network is dropped. Empty ⇒ `null` (the generated\n * resolver throws loudly at access time).\n * - dev `serve` (and config-load default): the committed networks OVERLAID\n * with the live local network(s), the live default winning. No live\n * envelope ⇒ committed only (default = `defaultNetwork` option / first\n * committed key).\n * The live envelope's entries are forced `local: true` (they ARE the running\n * local stack) so a later `build` would drop them. */\nconst mergeDeployment = (\n\tcommand: 'build' | 'serve' | undefined,\n\tcommitted: Record<string, NetworkDeployment>,\n\tlive: DevstackDeployment | null,\n\tdefaultNetworkOption: string | undefined,\n): DevstackDeployment | null => {\n\tif (command === 'build') {\n\t\tif (Object.keys(committed).length === 0) return null;\n\t\tconst fallback = firstKey(committed)!;\n\t\t// A pure prod build ships NO dev accounts (committed-only, network-\n\t\t// agnostic identities exist only when running through devstack).\n\t\treturn {\n\t\t\tdefaultNetwork:\n\t\t\t\tdefaultNetworkOption !== undefined && committed[defaultNetworkOption] !== undefined\n\t\t\t\t\t? defaultNetworkOption\n\t\t\t\t\t: fallback,\n\t\t\tnetworks: committed,\n\t\t\taccounts: {},\n\t\t};\n\t}\n\t// Dev serve / config-load default: overlay the live local network(s).\n\tconst networks: Record<string, NetworkDeployment> = { ...committed };\n\tif (live !== null) {\n\t\tfor (const [net, dep] of Object.entries(live.networks)) {\n\t\t\tnetworks[net] = { ...dep, network: net, local: true };\n\t\t}\n\t\t// Dev `accounts` ride the ENVELOPE (network-agnostic dev identities):\n\t\t// the live local stack supplies them; committed `deployments/*.ts`\n\t\t// networks carry none. Carry them through onto the merged envelope.\n\t\treturn {\n\t\t\tdefaultNetwork: live.defaultNetwork,\n\t\t\tnetworks,\n\t\t\taccounts: live.accounts ?? {},\n\t\t};\n\t}\n\tconst fallback = firstKey(networks);\n\tif (fallback === undefined) return null;\n\treturn {\n\t\tdefaultNetwork:\n\t\t\tdefaultNetworkOption !== undefined && networks[defaultNetworkOption] !== undefined\n\t\t\t\t? defaultNetworkOption\n\t\t\t\t: fallback,\n\t\tnetworks,\n\t\taccounts: {},\n\t};\n};\n\n/**\n * Build the devstack Vite plugin that aliases `options.alias`\n * (default `@generated`) at the committed `<root>/src/generated` tree.\n *\n * // vite.config.ts\n * import { devstackVitePlugin } from '@mysten-incubation/devstack/vite';\n * export default defineConfig({ plugins: [devstackVitePlugin()] });\n * // or devstackVitePlugin({ alias: '@gen' })\n *\n * The plugin's `config` hook merges `resolve.alias[<prefix>] = <dir>`\n * into the user config (Vite deep-merges the returned partial). Async\n * (it awaits the committed `deployments` thunks); reads `process.env` +\n * the manifest (for `__DEVSTACK_DEPLOYMENT__`) once at config-load.\n */\n\n/** The on-disk dev-wallet pairing-token for the active stack, given the\n * already-resolved `deploymentFile` path (the manifest's\n * `codegen.deploymentFile`). The token lives in a `0o600` side-channel file\n * (`<stackRoot>/wallet/token`, written by `pairing.ts`), NEVER in the\n * world-readable `deployment.json`. The boot writes `deployment.json` into the\n * SAME `<stackRoot>`, so the token sits at `<dirname(deploymentFile)>/wallet/\n * token`. Read in Node by the `load` hook (which runs server-side) so the\n * secret stays off the manifest/deployment surface. Returns `null` on any\n * miss (including a `null` deployment file). */\nconst readDevWalletToken = (deploymentFile: string | null): string | null => {\n\tif (deploymentFile === null) return null;\n\tconst tokenFile = resolve(dirname(deploymentFile), 'wallet', 'token');\n\ttry {\n\t\tif (!existsSync(tokenFile)) return null;\n\t\tconst raw = readFileSync(tokenFile, 'utf8').trim();\n\t\treturn /^[0-9a-f]{32}$/.test(raw) ? raw : null;\n\t} catch {\n\t\treturn null;\n\t}\n};\n\n/** Escape a `JSON.stringify` result for safe embedding in an inline\n * `<script>` element's text. Unlike an external file, inline-script text is\n * parsed by the HTML tokenizer first, so a raw `</script>` (or `<!--`) inside\n * any string value would terminate the tag early — and a lone `<` is enough to\n * matter. We escape `<` / `>` / `&` to their `\\uXXXX` JS-string forms (valid\n * inside JSON-as-JS, and inert to the HTML tokenizer) and the U+2028/U+2029\n * line separators (legal in JSON strings but illegal as raw JS line\n * terminators in older engines). Used ONLY on the inline-script injection path\n * — the `define` build path embeds into a JS module, not HTML, so it needs no\n * such escaping. */\nconst escapeForInlineScript = (json: string): string =>\n\tjson\n\t\t.replace(/</g, '\\\\u003c')\n\t\t.replace(/>/g, '\\\\u003e')\n\t\t.replace(/&/g, '\\\\u0026')\n\t\t// U+2028 / U+2029: legal in JSON strings but illegal as raw JS line\n\t\t// terminators in older engines — matched by code point so no literal\n\t\t// separator lives in this source.\n\t\t.replace(/[\\u2028\\u2029]/g, (ch) => '\\\\u' + ch.charCodeAt(0).toString(16).padStart(4, '0'));\n\n/** A boolean env flag is truthy when set to `'1'` or `'true'`\n * (case-insensitive). Shared by the auto-approve + e2e env readers. */\nconst isTruthyEnvFlag = (raw: string | undefined): boolean =>\n\traw === '1' || raw?.toLowerCase() === 'true';\n\n/** Read the `DEVSTACK_AUTO_APPROVE` env (`'1'`/`'true'`, case-insensitive). */\nconst autoApproveFromEnv = (env: Readonly<Record<string, string | undefined>>): boolean =>\n\tisTruthyEnvFlag(env['DEVSTACK_AUTO_APPROVE']);\n\n/** True under a Playwright e2e run (`DEVSTACK_E2E` set to `'1'`/`'true'`,\n * case-insensitive). DEDICATED signal — distinct from `DEVSTACK_AUTO_APPROVE`\n * — driving the injected `__DEVSTACK_E2E__` global. Gates dApp Kit's\n * `autoConnect` ON only under e2e, so a normal `pnpm dev` serve exercises the\n * real connect UX. A prod `vite build` never sets the env ⇒ injects `false`\n * (tree-shakeable). */\nconst e2eFromEnv = (env: Readonly<Record<string, string | undefined>>): boolean =>\n\tisTruthyEnvFlag(env['DEVSTACK_E2E']);\n\n/** Lit packages deduped to a single instance. The injected dev-wallet UI and\n * the app's dapp-kit UI are both Lit-based; if Vite loads two Lit copies they\n * register custom elements in separate realms, and the second realm's element\n * classes are unknown to the global `customElements` registry — so re-rendering\n * the dev-wallet UI (e.g. on disconnect/reconnect) throws `Illegal constructor`\n * and leaves the app in an unusable connection state. */\nconst LIT_DEDUPE = ['lit', 'lit-html', 'lit-element', '@lit/reactive-element'] as const;\n\n/** Filter {@link LIT_DEDUPE} to the packages actually installed at the app\n * root (`<root>/node_modules/<pkg>`). `resolve.dedupe` forces Vite to\n * resolve each listed package from the ROOT copy — but under pnpm's strict\n * layout a package is only surfaced at the root when the app declares it as\n * a direct dependency. Listing a package that is merely a phantom\n * (transitive-only, reachable solely under `@mysten/dapp-kit-core`'s nested\n * store dir) makes Vite's resolver look for a root copy that does not exist\n * and FAIL the production build with `Rollup failed to resolve import\n * \"lit\"`. So we dedupe only what the app truly hoists: the `app` template\n * declares `lit` (all dapp-kit-core Lit usage routes through the `lit`\n * meta-package, so one `lit` ⇒ one nested `@lit/reactive-element`); the\n * sub-packages stay phantom and are correctly dropped here. */\nconst resolvableLitDedupe = (root: string): string[] =>\n\tLIT_DEDUPE.filter((pkg) => existsSync(resolve(root, 'node_modules', ...pkg.split('/'))));\n\n/** The dev-wallet entry points the injected virtual module imports. They are\n * reached only through the `<script>` this plugin adds in `transformIndexHtml`,\n * so Vite's initial dep scan never sees them and re-optimizes mid-session the\n * first time the page loads them — and that late, separate optimize pass pulls\n * a SECOND Lit instance (see {@link LIT_DEDUPE}). Pre-bundling them up front via\n * `optimizeDeps.include` keeps the whole dev-wallet UI graph in the initial pass,\n * sharing one Lit. */\nconst DEV_WALLET_OPTIMIZE_ENTRIES = [\n\t'@mysten-incubation/dev-wallet/inject',\n\t'@mysten-incubation/dev-wallet/adapters',\n] as const;\n\n/** True when `@mysten-incubation/dev-wallet` is installed at the app root —\n * i.e. the app actually depends on the dev wallet (the `app` template does;\n * the headless `ts` template does not). Checked by package presence rather\n * than `require.resolve('.../inject')`, whose `exports` entry declares only an\n * `import` condition (the CJS resolver throws `ERR_PACKAGE_PATH_NOT_EXPORTED`).\n * Best-effort: if it's absent we never hand Vite an `optimizeDeps.include` it\n * can't resolve (which would fail the dep scan). */\nconst devWalletInstalled = (root: string): boolean =>\n\texistsSync(resolve(root, 'node_modules', '@mysten-incubation', 'dev-wallet', 'package.json'));\n\nexport const devstackVitePlugin = (options: DevstackVitePluginOptions = {}): DevstackVitePlugin => {\n\tconst alias = options.alias ?? DEFAULT_GENERATED_ALIAS;\n\tconst injectDevWallet = options.injectDevWallet ?? true;\n\n\t// Captured across hooks. `config` runs first (alias resolution + committed\n\t// deployment validation), `configResolved` records the command (DEV-gate),\n\t// and `load` reads the dev-wallet connection off the injected deployment +\n\t// the secret token off its side-channel file.\n\tlet resolvedRoot = process.cwd();\n\tlet isServe = false;\n\t// Whether the app carries the dev wallet (`devWalletInstalled(root)`),\n\t// computed ONCE in the `config` hook and reused by `transformIndexHtml` so\n\t// the per-request HTML injection doesn't re-`stat` the package on disk.\n\tlet devWalletPresent = false;\n\t// The committed per-network deployments, resolved + validated ONCE in the\n\t// async `config` hook (the thunks are async; the runtime-global path in\n\t// `transformIndexHtml` is sync). Stable for the dev server's lifetime, so\n\t// the per-request HTML injection overlays the fresh live network on top of\n\t// these without re-awaiting.\n\tlet resolvedCommittedNetworks: Record<string, NetworkDeployment> = {};\n\t// Per-republish cache of the live local envelope. `readLiveEnvelope` walks\n\t// the manifest + reads/decodes `deployment.json` on every call, but the file\n\t// only changes on a republish — which the `configureServer` watcher already\n\t// observes. So we read a PRESENT envelope once (lazily, on the first\n\t// `transformIndexHtml`) and reuse it until the watcher invalidates it, rather\n\t// than re-reading per page load. `liveEnvelopeFresh === false` is the\n\t// \"needs (re)read\" sentinel.\n\tlet liveEnvelopeFresh = false;\n\tlet liveEnvelopeCache: DevstackDeployment | null = null;\n\tconst readLiveEnvelopeCached = (\n\t\tenv: Readonly<Record<string, string | undefined>>,\n\t): DevstackDeployment | null => {\n\t\tif (liveEnvelopeFresh) return liveEnvelopeCache;\n\t\tconst live = readLiveEnvelope(env, resolvedRoot);\n\t\t// Only memoize a PRESENT envelope. A `null` result (no live stack yet)\n\t\t// must NOT stick: the `configureServer` watcher that invalidates this\n\t\t// cache only registers when a manifest already exists at server start\n\t\t// (`if (idsFile === null) return`), so a standalone `vite dev` launched\n\t\t// BEFORE `devstack up` has no invalidation hook — caching `null` here\n\t\t// would pin it forever, and the app would never pick up the stack even\n\t\t// after it boots and the page reloads. Re-reading while absent is cheap\n\t\t// (no `deployment.json` to decode) and lets the first post-boot reload\n\t\t// resolve the freshly-written envelope.\n\t\tif (live !== null) {\n\t\t\tliveEnvelopeCache = live;\n\t\t\tliveEnvelopeFresh = true;\n\t\t} else {\n\t\t\tliveEnvelopeCache = null;\n\t\t}\n\t\treturn live;\n\t};\n\n\treturn {\n\t\tname: 'devstack:generated-alias',\n\t\tconfig: async (config: ViteUserConfigLike, configEnv?: ViteConfigEnvLike) => {\n\t\t\tconst root = config.root ?? process.cwd();\n\t\t\tresolvedRoot = root;\n\t\t\tconst env = process.env as Readonly<Record<string, string | undefined>>;\n\t\t\t// `command` comes from the second hook arg (`{ command, mode }`);\n\t\t\t// fall back to `config.command` (some callers pass it on the\n\t\t\t// config). Default the UNKNOWN case to `build` (build-safe): only an\n\t\t\t// EXPLICIT `serve` takes the live local-stack id-injection path, so a\n\t\t\t// programmatic `vite.build()` that omits the env arg never bakes\n\t\t\t// dev-stack ids into a production bundle.\n\t\t\tconst command = configEnv?.command ?? config.command ?? 'build';\n\t\t\t// Explicit `generatedDir` wins (relative → resolved against the\n\t\t\t// Vite root). Otherwise always the committed `<root>/src/generated`\n\t\t\t// tree — the single source of bindings written by `devstack\n\t\t\t// codegen`; ids resolve at runtime via `__DEVSTACK_DEPLOYMENT__`, so\n\t\t\t// the same tree serves every stack.\n\t\t\tconst explicit = options.generatedDir;\n\t\t\tconst generatedDir =\n\t\t\t\texplicit !== undefined ? resolve(root, explicit) : resolve(root, GENERATED_SUBPATH);\n\t\t\t// Return a partial config; Vite deep-merges it. `resolve.dedupe`\n\t\t\t// pins a single Lit copy, and — when this app carries the dev\n\t\t\t// wallet — we pre-bundle the injected dev-wallet entries so Vite\n\t\t\t// never re-optimizes them mid-session into a second Lit realm\n\t\t\t// (see the constants above).\n\t\t\t// `devWalletInstalled` stats the package on disk; do it once here and\n\t\t\t// reuse the result in `transformIndexHtml` (the root is stable for the\n\t\t\t// server's lifetime).\n\t\t\tdevWalletPresent = devWalletInstalled(root);\n\t\t\tconst includeDevWallet = injectDevWallet && devWalletPresent;\n\t\t\t// Resolve + validate the committed per-network deployments ONCE (the\n\t\t\t// thunks are async; the per-request HTML path is sync). Captured so\n\t\t\t// `transformIndexHtml` overlays the fresh live network on top of\n\t\t\t// these without re-awaiting. A malformed committed file throws here,\n\t\t\t// at config-load.\n\t\t\tresolvedCommittedNetworks = await resolveCommittedNetworks(\n\t\t\t\tresolveDeploymentThunks(options, root),\n\t\t\t);\n\t\t\t// Inject the deployment envelope the generated `config-runtime.ts`\n\t\t\t// resolver reads as `__DEVSTACK_DEPLOYMENT__`.\n\t\t\t// - PROD build: bake the static merged envelope literal (committed,\n\t\t\t// non-local networks only).\n\t\t\t// - DEV server (`vite dev`): point the identifier at the\n\t\t\t// `__DEVSTACK_DEPLOYMENT_LIVE__` runtime global the\n\t\t\t// `transformIndexHtml` hook sets fresh per page load — `define`\n\t\t\t// is fixed for the dev server's lifetime, so baking the value\n\t\t\t// would pin the FIRST id forever and a republish would never\n\t\t\t// reach the app. A distinct global name avoids a single-pass\n\t\t\t// `define` token collision with `__DEVSTACK_DEPLOYMENT__`.\n\t\t\t// - VITEST: vitest ALSO reports `command === 'serve'`, but it runs\n\t\t\t// no `transformIndexHtml` (no HTML), so the live global is never\n\t\t\t// set — and esbuild's stricter `define` rejects an operator\n\t\t\t// expression anyway. Bake a literal instead: the Vite config loads\n\t\t\t// BEFORE the test stack boots, so the merged envelope is null and\n\t\t\t// the resolver falls back to the `DEVSTACK_DEPLOYMENT_FILE` env the\n\t\t\t// vitest globalSetup points at the freshly-booted stack (see\n\t\t\t// vitest/global-setup.ts).\n\t\t\t// The define VALUE must be esbuild-valid (a JSON literal or a\n\t\t\t// member-access chain) — note the bare `globalThis.…`, NOT a\n\t\t\t// parenthesised `(… ?? null)`; `config-runtime.ts` already guards the\n\t\t\t// `typeof … === 'undefined'` case.\n\t\t\t// Merge the deployment envelope to bake/point at — reusing the\n\t\t\t// committed networks just resolved above rather than re-resolving the\n\t\t\t// thunks. `build` drops the live local stack; `serve`/config-load\n\t\t\t// overlays it.\n\t\t\tconst injectedLive = command === 'build' ? null : readLiveEnvelope(env, root);\n\t\t\tconst injectedDeployment = mergeDeployment(\n\t\t\t\tcommand,\n\t\t\t\tresolvedCommittedNetworks,\n\t\t\t\tinjectedLive,\n\t\t\t\toptions.defaultNetwork,\n\t\t\t);\n\t\t\tconst isVitest = env['VITEST'] !== undefined;\n\t\t\tconst deploymentDefine =\n\t\t\t\tcommand === 'serve' && !isVitest\n\t\t\t\t\t? 'globalThis.__DEVSTACK_DEPLOYMENT_LIVE__'\n\t\t\t\t\t: JSON.stringify(injectedDeployment ?? null);\n\t\t\treturn {\n\t\t\t\tresolve: {\n\t\t\t\t\t// A bare-prefix alias (no trailing `/`) matches both\n\t\t\t\t\t// `@generated` and `@generated/foo.js` under Vite's default\n\t\t\t\t\t// string-alias resolution.\n\t\t\t\t\talias: {\n\t\t\t\t\t\t[alias]: generatedDir,\n\t\t\t\t\t},\n\t\t\t\t\tdedupe: resolvableLitDedupe(root),\n\t\t\t\t},\n\t\t\t\tdefine: {\n\t\t\t\t\t__DEVSTACK_DEPLOYMENT__: deploymentDefine,\n\t\t\t\t\t// `true` ONLY under a Playwright e2e run (`DEVSTACK_E2E`), so the\n\t\t\t\t\t// app's dApp Kit `autoConnect` is on for headless e2e and OFF for a\n\t\t\t\t\t// normal `pnpm dev` serve. A prod `vite build` never sets the env ⇒\n\t\t\t\t\t// `false` (tree-shakeable). Distinct from `DEVSTACK_AUTO_APPROVE`.\n\t\t\t\t\t__DEVSTACK_E2E__: JSON.stringify(e2eFromEnv(env)),\n\t\t\t\t},\n\t\t\t\t...(includeDevWallet\n\t\t\t\t\t? { optimizeDeps: { include: [...DEV_WALLET_OPTIMIZE_ENTRIES] } }\n\t\t\t\t\t: {}),\n\t\t\t};\n\t\t},\n\n\t\tconfigResolved: (config: ViteUserConfigLike) => {\n\t\t\t// DEV-gate: only a `serve` command injects the dev wallet. A\n\t\t\t// production `vite build` injects nothing.\n\t\t\tisServe = config.command !== 'build';\n\t\t},\n\n\t\tresolveId: (id: string) => {\n\t\t\tif (id === VIRTUAL_DEV_WALLET_ID) return RESOLVED_VIRTUAL_DEV_WALLET_ID;\n\t\t\treturn undefined;\n\t\t},\n\n\t\tload: (id: string): string | undefined => {\n\t\t\tif (id !== RESOLVED_VIRTUAL_DEV_WALLET_ID) return undefined;\n\t\t\tconst env = process.env as Readonly<Record<string, string | undefined>>;\n\t\t\t// The dev-wallet connection metadata rides the injected deployment's\n\t\t\t// `values['dev-wallet'].connection` channel (non-secret); the secret\n\t\t\t// pairing token stays in its `0o600` side-channel file, read HERE in\n\t\t\t// Node (the `load` hook runs server-side) so it never lands in the\n\t\t\t// world-readable `deployment.json`. Graceful no-op when injection is\n\t\t\t// off, not a dev serve, or no live stack has written the token yet\n\t\t\t// (no `devstack up`). The presence of BOTH the deployment file (for the\n\t\t\t// connection) and the token gate injection — the browser side resolves\n\t\t\t// the connection off the injected envelope.\n\t\t\t// Parse the manifest ONCE and read both fields it carries below — the\n\t\t\t// `codegen.deploymentFile` (locating the token side-channel) and the\n\t\t\t// `identity.network` (the active network for the auto-approve policy) —\n\t\t\t// off the same parsed object rather than discovering + parsing twice.\n\t\t\tconst manifest = readManifestObject(env, resolvedRoot);\n\t\t\tconst deploymentFile = pickManifestString(manifest, 'codegen.deploymentFile');\n\t\t\tconst token = readDevWalletToken(deploymentFile);\n\t\t\tif (!injectDevWallet || !isServe || token === null) {\n\t\t\t\treturn 'export {};';\n\t\t\t}\n\t\t\t// Auto-approve resolution (highest precedence first): explicit\n\t\t\t// `options.autoApprove`, then the `DEVSTACK_AUTO_APPROVE` env, then\n\t\t\t// the active network's `autoApproveSigning` per-network policy. The\n\t\t\t// policy defaults OFF on every network — a normal `pnpm dev` exercises\n\t\t\t// the real connect + approve UX, headless e2e sets the env, and an\n\t\t\t// author can opt a network back in via `networkOptions`. Live\n\t\t\t// `mainnet` is additionally hard-clamped OFF, so a stack booted\n\t\t\t// against real mainnet never silently auto-approves a real-funds\n\t\t\t// signature. The active network comes from the manifest\n\t\t\t// boot wrote (`identity.network`); absent that we conservatively\n\t\t\t// resolve as `mainnet` (auto-approve OFF) rather than assuming a\n\t\t\t// dev network. The override RECORD isn't on disk — `networkOptions`\n\t\t\t// is read from the plugin option when the app threads it.\n\t\t\tconst network = pickManifestString(manifest, 'identity.network') ?? 'mainnet';\n\t\t\tconst netOpts = resolveNetworkOptions(network, options.networkOptions);\n\t\t\tconst autoApprove =\n\t\t\t\toptions.autoApprove ?? (autoApproveFromEnv(env) || netOpts.autoApproveSigning);\n\t\t\t// Browser-side registration source the dev server transpiles. It reads\n\t\t\t// the dev-only account name→address map (`resolveAccounts()`) and the\n\t\t\t// non-secret dev-wallet connection (`optionalValue(dep, 'dev-wallet',\n\t\t\t// 'connection')`) off the injected deployment envelope (via the\n\t\t\t// generated `config-runtime.ts`), and the FULL network set the app\n\t\t\t// supports off the generated `config.networks` map (each with its\n\t\t\t// routed `rpc` + optional `faucet`). The wallet advertises each network\n\t\t\t// as a wallet-standard chain and operates on whichever dApp Kit has\n\t\t\t// selected, so it PERSISTS across a UI `switchNetwork` (registered once;\n\t\t\t// only the active chain changes). The token is baked in from the Node\n\t\t\t// read above — the only secret on this surface.\n\t\t\treturn [\n\t\t\t\t`import { registerDevstackDevWallet } from '@mysten-incubation/dev-wallet/inject';`,\n\t\t\t\t`import { config as __devstackConfig } from '${alias}/config.js';`,\n\t\t\t\t`import { resolveAccounts, optionalValue } from '${alias}/config-runtime.js';`,\n\t\t\t\t`const __devstackToken = ${JSON.stringify(token)};`,\n\t\t\t\t// name → address (from the injected envelope's dev `accounts`),\n\t\t\t\t// mapped to the inject API's `{ address }` shape.\n\t\t\t\t`const __devstackAccounts = Object.fromEntries(`,\n\t\t\t\t` Object.entries(resolveAccounts()).map(([__name, __address]) => [`,\n\t\t\t\t` __name,`,\n\t\t\t\t` { name: __name, address: __address },`,\n\t\t\t\t` ]),`,\n\t\t\t\t`);`,\n\t\t\t\t// The full routed network set (live local + any committed live).\n\t\t\t\t`const __devstackNetworks = Object.fromEntries(`,\n\t\t\t\t` Object.entries(__devstackConfig.networks).map(([__net, __dep]) => [`,\n\t\t\t\t` __net,`,\n\t\t\t\t` { rpc: __dep.rpc, faucet: __dep.faucet ?? null },`,\n\t\t\t\t` ]),`,\n\t\t\t\t`);`,\n\t\t\t\t// Non-secret connection (server URL + protocol paths), resolved off\n\t\t\t\t// the default network's `values['dev-wallet'].connection`. No\n\t\t\t\t// stack ⇒ undefined ⇒ no injection.\n\t\t\t\t`const __devstackDefaultDep = __devstackConfig.networks[__devstackConfig.defaultNetwork];`,\n\t\t\t\t`const __devstackConnection = __devstackDefaultDep`,\n\t\t\t\t` ? optionalValue(__devstackDefaultDep, 'dev-wallet', 'connection')`,\n\t\t\t\t` : undefined;`,\n\t\t\t\t`if (__devstackConnection) {`,\n\t\t\t\t` registerDevstackDevWallet({`,\n\t\t\t\t` serverOrigin: __devstackConnection.walletUrl,`,\n\t\t\t\t` token: __devstackToken,`,\n\t\t\t\t` accounts: __devstackAccounts,`,\n\t\t\t\t` allowedOrigins: __devstackConnection.allowedOrigins ?? [],`,\n\t\t\t\t` networks: __devstackNetworks,`,\n\t\t\t\t` defaultNetwork: __devstackConfig.defaultNetwork,`,\n\t\t\t\t` autoApprove: ${autoApprove ? 'true' : 'false'},`,\n\t\t\t\t` mountUI: true,`,\n\t\t\t\t` }).catch((err) => console.error('[devstack] dev-wallet injection failed:', err));`,\n\t\t\t\t`}`,\n\t\t\t].join('\\n');\n\t\t},\n\n\t\tconfigureServer: (server: ViteDevServerLike) => {\n\t\t\tif (!isServe) return;\n\t\t\tconst env = process.env as Readonly<Record<string, string | undefined>>;\n\t\t\tconst idsFile = readIdsFileFromManifest(env, resolvedRoot);\n\t\t\tif (idsFile === null) return;\n\t\t\t// Watch the live deployment and full-reload on change. The supervisor's\n\t\t\t// post-acquire hook rewrites this file whenever a package (re)publishes\n\t\t\t// (e.g. after a Move-source edit fires the file watcher → selective\n\t\t\t// restart); a reload re-runs `transformIndexHtml`, which re-reads the\n\t\t\t// file into `__DEVSTACK_DEPLOYMENT_LIVE__`, so the app picks up the new id.\n\t\t\tserver.watcher.add(idsFile);\n\t\t\tconst reloadOnIdsChange = (changed: string): void => {\n\t\t\t\t// Chokidar fires absolute paths for an absolute `watcher.add`, so\n\t\t\t\t// `resolve(changed)` is normally a no-op; resolve against the Vite\n\t\t\t\t// root anyway so a relative event path can't silently miss.\n\t\t\t\tif (resolve(resolvedRoot, changed) !== resolve(idsFile)) return;\n\t\t\t\t// Invalidate the per-republish live-envelope cache so the next\n\t\t\t\t// `transformIndexHtml` re-reads the rewritten `deployment.json`.\n\t\t\t\tliveEnvelopeFresh = false;\n\t\t\t\tconst channel = server.hot ?? server.ws;\n\t\t\t\tchannel?.send({ type: 'full-reload' });\n\t\t\t};\n\t\t\tserver.watcher.on('change', reloadOnIdsChange);\n\t\t\tserver.watcher.on('add', reloadOnIdsChange);\n\t\t},\n\n\t\ttransformIndexHtml: (html: string) => {\n\t\t\tif (!isServe) return undefined;\n\t\t\tconst env = process.env as Readonly<Record<string, string | undefined>>;\n\t\t\t// Read the live local envelope (cached per republish, invalidated by\n\t\t\t// the `configureServer` watcher on a `deployment.json` change), then\n\t\t\t// overlay it on the committed networks captured at config-load (a sync\n\t\t\t// merge — the committed thunks were already awaited in `config`). Set on\n\t\t\t// a distinct global (`__DEVSTACK_DEPLOYMENT_LIVE__`) the `config` hook's\n\t\t\t// `define` points `__DEVSTACK_DEPLOYMENT__` at; `head-prepend` runs it\n\t\t\t// before any app module.\n\t\t\tconst live = readLiveEnvelopeCached(env);\n\t\t\tconst liveDeployment = mergeDeployment(\n\t\t\t\t'serve',\n\t\t\t\tresolvedCommittedNetworks,\n\t\t\t\tlive,\n\t\t\t\toptions.defaultNetwork,\n\t\t\t);\n\t\t\tconst tags: Array<{\n\t\t\t\ttag: string;\n\t\t\t\tattrs?: Record<string, string | boolean>;\n\t\t\t\tchildren?: string;\n\t\t\t\tinjectTo: 'head' | 'head-prepend';\n\t\t\t}> = [\n\t\t\t\t{\n\t\t\t\t\ttag: 'script',\n\t\t\t\t\tchildren: `globalThis.__DEVSTACK_DEPLOYMENT_LIVE__ = ${escapeForInlineScript(JSON.stringify(liveDeployment ?? null))};`,\n\t\t\t\t\tinjectTo: 'head-prepend',\n\t\t\t\t},\n\t\t\t];\n\t\t\t// Gate the script tag on the app actually carrying the wallet (same\n\t\t\t// condition as the `config` hook's `optimizeDeps` include — reusing the\n\t\t\t// boolean it computed), so a headless app emits zero dev-wallet plumbing\n\t\t\t// rather than a tag that resolves to the no-op virtual module.\n\t\t\tif (injectDevWallet && devWalletPresent) {\n\t\t\t\ttags.push({\n\t\t\t\t\ttag: 'script',\n\t\t\t\t\tattrs: { type: 'module', src: VIRTUAL_DEV_WALLET_SCRIPT_SRC },\n\t\t\t\t\tinjectTo: 'head',\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn { html, tags };\n\t\t},\n\t};\n};\n"],"mappings":"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"}
@@ -8,7 +8,7 @@ import "../../orchestrators/snapshot/index.mjs";
8
8
  import "../../substrate/runtime/index.mjs";
9
9
  import { superviseStackWithProductionBoot } from "../../orchestrators/boot.mjs";
10
10
  import { ExitCode } from "../../surfaces/cli/sysexits.mjs";
11
- import { CliInternalError } from "../../surfaces/cli/errors.mjs";
11
+ import { CliInternalError, CliLiveGraphMismatchError } from "../../surfaces/cli/errors.mjs";
12
12
  import { probeSupervisorPresence } from "../../surfaces/cli/commands/supervisor-presence.mjs";
13
13
  import "../../surfaces/cli/commands/index.mjs";
14
14
  import { readDevstackVersion } from "./read-devstack-version.mjs";
@@ -22,13 +22,25 @@ import { dirname, resolve } from "node:path";
22
22
  const LIVE_APPLY_ACK_TIMEOUT_MILLIS = 600 * 1e3;
23
23
  /** Probe the roster file; if a supervisor is live, publish
24
24
  * `apply.requested` and await its ack. */
25
- const runApplyAgainstLiveSupervisor = (identity, identityValue) => Effect.gen(function* () {
25
+ const runApplyAgainstLiveSupervisor = (identity, identityValue, currentGraphInputId) => Effect.gen(function* () {
26
26
  const stackRoot = stackRootFor(identity.runtimeRoot, String(identityValue.stack));
27
- if (!(yield* probeSupervisorPresence(resolve(stackRoot, "roster.json")).pipe(Effect.catch(() => Effect.succeed({
27
+ const presence = yield* probeSupervisorPresence(resolve(stackRoot, "roster.json")).pipe(Effect.catch(() => Effect.succeed({
28
28
  live: false,
29
29
  pid: null,
30
- hostname: null
31
- })))).live) return { kind: "direct" };
30
+ hostname: null,
31
+ graphInputId: null
32
+ })));
33
+ if (!presence.live) return { kind: "direct" };
34
+ if (presence.graphInputId !== null && presence.graphInputId !== currentGraphInputId) return {
35
+ kind: "live-failed",
36
+ error: new CliLiveGraphMismatchError({
37
+ app: String(identityValue.app),
38
+ stack: String(identityValue.stack),
39
+ liveGraphInputId: presence.graphInputId,
40
+ currentGraphInputId,
41
+ hint: "restart the running `devstack up` session so it loads the edited stack config, then re-run `devstack apply`."
42
+ })
43
+ };
32
44
  const exit = yield* Effect.exit(Effect.gen(function* () {
33
45
  const publisher = yield* makeCommandChannelPublisher(commandChannelPaths(stackRoot));
34
46
  const published = yield* publisher.publish({ tag: "apply.requested" });
@@ -53,7 +65,13 @@ const runApplyLive = (configPath, identity) => {
53
65
  const stack = loaded.engine;
54
66
  const effectiveIdentity = resolvedIdentityForStack(identity, stack);
55
67
  const identityValue = identityValueFor(effectiveIdentity);
56
- const dispatch = yield* runApplyAgainstLiveSupervisor(effectiveIdentity, identityValue);
68
+ const dispatch = yield* runApplyAgainstLiveSupervisor(effectiveIdentity, identityValue, (yield* computeSnapshotGraphInputFromStack({
69
+ stack,
70
+ devstackVersion: readDevstackVersion({ fallback: "0.0.0" })
71
+ }).pipe(Effect.mapError((cause) => new CliInternalError({
72
+ message: "failed to compute stack graph identity",
73
+ cause
74
+ })))).graphInputId);
57
75
  if (dispatch.kind === "live-failed") return yield* Effect.fail(dispatch.error);
58
76
  if (dispatch.kind === "live-ok") return { exitCode: ExitCode.OK };
59
77
  const substrateLayers = buildVerbLayers({
@@ -1 +1 @@
1
- {"version":3,"file":"apply.mjs","names":["resolvePath"],"sources":["../../../src/cli/wirings/apply.ts"],"sourcesContent":["// `devstack apply` verb wiring.\n//\n// Live-aware: if a supervisor owns the selected stack, publish an\n// `apply.requested` command and await its ack. Otherwise run a\n// one-shot supervise that exits when all plugins are ready.\n//\n// Logger layer: `Logger.consolePretty()`. `apply` is the documented\n// one-shot mode (no TUI) — its consumers are CI logs and ad-hoc\n// operator runs that read raw stderr. Structured `Effect.log*` records\n// are the only operator-visible diagnostic for non-TUI surfaces, so we\n// route them through the pretty console layer. The sibling `up` verb\n// silences this layer (see `wirings/up.ts` header) because it owns the\n// TUI and would render duplicate / scrambled output otherwise.\n\nimport { dirname, resolve as resolvePath } from 'node:path';\n\nimport { Cause, Effect, Exit, Logger } from 'effect';\n\nimport type { Identity } from '../../substrate/identity.ts';\nimport { StackPathsService } from '../../substrate/runtime/paths.ts';\nimport {\n\tcommandChannelPaths,\n\tmakeCommandChannelPublisher,\n\tmakeProjectionRef,\n\ttype SupervisedStack,\n} from '../../substrate/runtime/index.ts';\nimport { superviseStackWithProductionBoot } from '../../orchestrators/boot.ts';\nimport {\n\tcomputeSnapshotGraphInputFromStack,\n\trecoverInterruptedRestore,\n\tSnapshotOrchestratorService,\n} from '../../orchestrators/snapshot/index.ts';\nimport { type CliError, CliInternalError } from '../../surfaces/cli/errors.ts';\nimport { type CommandResult, probeSupervisorPresence } from '../../surfaces/cli/commands/index.ts';\nimport { ExitCode } from '../../surfaces/cli/sysexits.ts';\nimport type { LoadedConfig } from '../../surfaces/cli/commands/config-loader.ts';\n\nimport { cliErrorFromConfigExit } from '../bail.ts';\nimport { makeConfigLoader } from './config-loader.ts';\nimport {\n\tidentityValueFor,\n\tresolvedIdentityForStack,\n\tstackRootFor,\n\ttype ResolvedIdentity,\n} from './identity.ts';\nimport { buildVerbLayers } from '../../orchestrators/layers.ts';\nimport { readDevstackVersion } from './read-devstack-version.ts';\n\nconst LIVE_APPLY_ACK_TIMEOUT_MILLIS = 10 * 60 * 1000;\n\n/** Outcome of the live-supervisor probe + dispatch:\n * - `kind: 'direct'` — no supervisor; caller runs the direct path.\n * - `kind: 'live-ok'` — live supervisor acked; verb is done.\n * - `kind: 'live-failed'` — live supervisor refused/timed-out; caller\n * surfaces the typed `CliError` via the dispatcher envelope. */\ntype ApplyDispatch =\n\t| { readonly kind: 'direct' }\n\t| { readonly kind: 'live-ok' }\n\t| { readonly kind: 'live-failed'; readonly error: CliError };\n\n/** Probe the roster file; if a supervisor is live, publish\n * `apply.requested` and await its ack. */\nconst runApplyAgainstLiveSupervisor = (\n\tidentity: ResolvedIdentity,\n\tidentityValue: Identity,\n): Effect.Effect<ApplyDispatch> =>\n\tEffect.gen(function* () {\n\t\tconst stackRoot = stackRootFor(identity.runtimeRoot, String(identityValue.stack));\n\t\tconst presence = yield* probeSupervisorPresence(resolvePath(stackRoot, 'roster.json')).pipe(\n\t\t\tEffect.catch(() => Effect.succeed({ live: false, pid: null, hostname: null })),\n\t\t);\n\t\tif (!presence.live) return { kind: 'direct' as const };\n\n\t\tconst exit = yield* Effect.exit(\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst publisher = yield* makeCommandChannelPublisher(commandChannelPaths(stackRoot));\n\t\t\t\tconst published = yield* publisher.publish({ tag: 'apply.requested' });\n\t\t\t\tconst reply = yield* publisher.awaitCompletion(published, {\n\t\t\t\t\ttimeoutMillis: LIVE_APPLY_ACK_TIMEOUT_MILLIS,\n\t\t\t\t});\n\t\t\t\tif (!reply.ok) {\n\t\t\t\t\treturn yield* Effect.fail(reply.message);\n\t\t\t\t}\n\t\t\t}),\n\t\t);\n\n\t\tif (Exit.isFailure(exit)) {\n\t\t\treturn {\n\t\t\t\tkind: 'live-failed' as const,\n\t\t\t\terror: new CliInternalError({\n\t\t\t\t\tmessage: 'live stack apply failed',\n\t\t\t\t\tcause: Cause.pretty(exit.cause as Cause.Cause<unknown>),\n\t\t\t\t}),\n\t\t\t};\n\t\t}\n\n\t\treturn { kind: 'live-ok' as const };\n\t});\n\nexport const runApplyLive = (\n\tconfigPath: string | undefined,\n\tidentity: ResolvedIdentity,\n): Effect.Effect<CommandResult, CliError> => {\n\tconst loader = makeConfigLoader();\n\treturn Effect.gen(function* () {\n\t\t// Config-load failures surface as typed `CliError`s; the\n\t\t// dispatcher's outer `emitFailure` renders the envelope.\n\t\tconst loadExit = yield* Effect.exit(loader.load(configPath));\n\t\tif (Exit.isFailure(loadExit)) {\n\t\t\treturn yield* Effect.fail(cliErrorFromConfigExit(loadExit));\n\t\t}\n\t\tconst loaded = loadExit.value;\n\t\tconst stack = (loaded as LoadedConfig & { readonly engine: SupervisedStack }).engine;\n\t\t// Re-derive the identity against the EFFECTIVE stack (explicit\n\t\t// `--stack`/`$DEVSTACK_STACK` > `config.stackName` > inferred) so\n\t\t// the live-supervisor probe + roster paths target the same stack\n\t\t// the operator selected — matching `snapshot.ts`. An explicit\n\t\t// `--stack` must NOT be overridden by `config.stackName`, otherwise\n\t\t// `apply` would probe/boot the wrong stack's supervisor.\n\t\tconst effectiveIdentity = resolvedIdentityForStack(identity, stack);\n\t\tconst identityValue: Identity = identityValueFor(effectiveIdentity);\n\t\tconst dispatch = yield* runApplyAgainstLiveSupervisor(effectiveIdentity, identityValue);\n\t\tif (dispatch.kind === 'live-failed') {\n\t\t\treturn yield* Effect.fail(dispatch.error);\n\t\t}\n\t\tif (dispatch.kind === 'live-ok') {\n\t\t\treturn { exitCode: ExitCode.OK };\n\t\t}\n\t\tconst appRoot = dirname(loaded.resolvedConfigPath);\n\t\tconst substrateLayers = buildVerbLayers({\n\t\t\tidentity: identityValue,\n\t\t\tstack,\n\t\t\tappRoot,\n\t\t\truntimeRoot: effectiveIdentity.runtimeRoot,\n\t\t});\n\n\t\tconst program = Effect.gen(function* () {\n\t\t\tconst state = yield* makeProjectionRef();\n\t\t\tconst stackPaths = yield* StackPathsService;\n\t\t\tconst computeGraphInput = computeSnapshotGraphInputFromStack({\n\t\t\t\tstack,\n\t\t\t\tdevstackVersion: readDevstackVersion({ fallback: '0.0.0' }),\n\t\t\t});\n\t\t\t// Mirror the up-path recovery: resume any restore interrupted by a\n\t\t\t// hard kill between the atomic swap and the image-promotion handoff\n\t\t\t// completing (the interrupted-restore sentinel rode the swap into the\n\t\t\t// live root and was never cleared) BEFORE the one-shot apply starts\n\t\t\t// the stack. Idempotent + no-op when no sentinel is present.\n\t\t\tconst snapshot = yield* SnapshotOrchestratorService;\n\t\t\tyield* recoverInterruptedRestore({\n\t\t\t\tliveRoot: stackPaths.stackRoot,\n\t\t\t\trestoreSnapshot: (id) =>\n\t\t\t\t\tcomputeGraphInput.pipe(\n\t\t\t\t\t\tEffect.flatMap((currentGraphInput) =>\n\t\t\t\t\t\t\tsnapshot.restore({\n\t\t\t\t\t\t\t\tid,\n\t\t\t\t\t\t\t\tcurrentGraphInput,\n\t\t\t\t\t\t\t\tgraphInputMismatchPolicy: 'warn',\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t});\n\t\t\t// The contribution dispatcher + post-acquire hook + built-in\n\t\t\t// plugin-context extension are assembled in ONE place\n\t\t\t// (`orchestrators/boot.ts superviseStackWithProductionBoot`); this\n\t\t\t// one-shot verb supplies only the per-caller inputs (`extras`,\n\t\t\t// `lifetime: 'one-shot'`). Run-to-completion + result semantics are\n\t\t\t// unchanged: the surrounding `matchCauseEffect` below still maps the\n\t\t\t// supervised cause/success onto the `CommandResult`.\n\t\t\tyield* superviseStackWithProductionBoot(\n\t\t\t\t{ _tag: 'Stack', members: stack.members, options: stack.options },\n\t\t\t\tidentityValue,\n\t\t\t\tstate,\n\t\t\t\t{\n\t\t\t\t\textras: stack.options.extras,\n\t\t\t\t\tlifetime: 'one-shot',\n\t\t\t\t},\n\t\t\t);\n\t\t});\n\n\t\treturn yield* program.pipe(\n\t\t\tEffect.provide(substrateLayers),\n\t\t\tEffect.provide(Logger.layer([Logger.consolePretty()])),\n\t\t\tEffect.matchCauseEffect({\n\t\t\t\tonFailure: (cause): Effect.Effect<CommandResult, CliError> =>\n\t\t\t\t\tEffect.fail(\n\t\t\t\t\t\tnew CliInternalError({\n\t\t\t\t\t\t\tmessage: 'stack apply failed',\n\t\t\t\t\t\t\tcause: Cause.pretty(cause as Cause.Cause<unknown>),\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\tonSuccess: (): Effect.Effect<CommandResult, CliError> =>\n\t\t\t\t\tEffect.succeed({ exitCode: ExitCode.OK }),\n\t\t\t}),\n\t\t);\n\t});\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAgDA,MAAM,gCAAgC,MAAU;;;AAchD,MAAM,iCACL,UACA,kBAEA,OAAO,IAAI,aAAa;CACvB,MAAM,YAAY,aAAa,SAAS,aAAa,OAAO,cAAc,KAAK,CAAC;CAIhF,IAAI,EAAC,OAHmB,wBAAwBA,QAAY,WAAW,aAAa,CAAC,CAAC,CAAC,KACtF,OAAO,YAAY,OAAO,QAAQ;EAAE,MAAM;EAAO,KAAK;EAAM,UAAU;CAAK,CAAC,CAAC,CAC9E,EAAA,CACc,MAAM,OAAO,EAAE,MAAM,SAAkB;CAErD,MAAM,OAAO,OAAO,OAAO,KAC1B,OAAO,IAAI,aAAa;EACvB,MAAM,YAAY,OAAO,4BAA4B,oBAAoB,SAAS,CAAC;EACnF,MAAM,YAAY,OAAO,UAAU,QAAQ,EAAE,KAAK,kBAAkB,CAAC;EACrE,MAAM,QAAQ,OAAO,UAAU,gBAAgB,WAAW,EACzD,eAAe,8BAChB,CAAC;EACD,IAAI,CAAC,MAAM,IACV,OAAO,OAAO,OAAO,KAAK,MAAM,OAAO;CAEzC,CAAC,CACF;CAEA,IAAI,KAAK,UAAU,IAAI,GACtB,OAAO;EACN,MAAM;EACN,OAAO,IAAI,iBAAiB;GAC3B,SAAS;GACT,OAAO,MAAM,OAAO,KAAK,KAA6B;EACvD,CAAC;CACF;CAGD,OAAO,EAAE,MAAM,UAAmB;AACnC,CAAC;AAEF,MAAa,gBACZ,YACA,aAC4C;CAC5C,MAAM,SAAS,iBAAiB;CAChC,OAAO,OAAO,IAAI,aAAa;EAG9B,MAAM,WAAW,OAAO,OAAO,KAAK,OAAO,KAAK,UAAU,CAAC;EAC3D,IAAI,KAAK,UAAU,QAAQ,GAC1B,OAAO,OAAO,OAAO,KAAK,uBAAuB,QAAQ,CAAC;EAE3D,MAAM,SAAS,SAAS;EACxB,MAAM,QAAS,OAA+D;EAO9E,MAAM,oBAAoB,yBAAyB,UAAU,KAAK;EAClE,MAAM,gBAA0B,iBAAiB,iBAAiB;EAClE,MAAM,WAAW,OAAO,8BAA8B,mBAAmB,aAAa;EACtF,IAAI,SAAS,SAAS,eACrB,OAAO,OAAO,OAAO,KAAK,SAAS,KAAK;EAEzC,IAAI,SAAS,SAAS,WACrB,OAAO,EAAE,UAAU,SAAS,GAAG;EAGhC,MAAM,kBAAkB,gBAAgB;GACvC,UAAU;GACV;GACA,SAJe,QAAQ,OAAO,kBAIxB;GACN,aAAa,kBAAkB;EAChC,CAAC;EA8CD,OAAO,OA5CS,OAAO,IAAI,aAAa;GACvC,MAAM,QAAQ,OAAO,kBAAkB;GACvC,MAAM,aAAa,OAAO;GAC1B,MAAM,oBAAoB,mCAAmC;IAC5D;IACA,iBAAiB,oBAAoB,EAAE,UAAU,QAAQ,CAAC;GAC3D,CAAC;GAMD,MAAM,WAAW,OAAO;GACxB,OAAO,0BAA0B;IAChC,UAAU,WAAW;IACrB,kBAAkB,OACjB,kBAAkB,KACjB,OAAO,SAAS,sBACf,SAAS,QAAQ;KAChB;KACA;KACA,0BAA0B;IAC3B,CAAC,CACF,CACD;GACF,CAAC;GAQD,OAAO,iCACN;IAAE,MAAM;IAAS,SAAS,MAAM;IAAS,SAAS,MAAM;GAAQ,GAChE,eACA,OACA;IACC,QAAQ,MAAM,QAAQ;IACtB,UAAU;GACX,CACD;EACD,CAEoB,CAAC,CAAC,KACrB,OAAO,QAAQ,eAAe,GAC9B,OAAO,QAAQ,OAAO,MAAM,CAAC,OAAO,cAAc,CAAC,CAAC,CAAC,GACrD,OAAO,iBAAiB;GACvB,YAAY,UACX,OAAO,KACN,IAAI,iBAAiB;IACpB,SAAS;IACT,OAAO,MAAM,OAAO,KAA6B;GAClD,CAAC,CACF;GACD,iBACC,OAAO,QAAQ,EAAE,UAAU,SAAS,GAAG,CAAC;EAC1C,CAAC,CACF;CACD,CAAC;AACF"}
1
+ {"version":3,"file":"apply.mjs","names":["resolvePath"],"sources":["../../../src/cli/wirings/apply.ts"],"sourcesContent":["// `devstack apply` verb wiring.\n//\n// Live-aware: if a supervisor owns the selected stack, publish an\n// `apply.requested` command and await its ack. Otherwise run a\n// one-shot supervise that exits when all plugins are ready.\n//\n// Logger layer: `Logger.consolePretty()`. `apply` is the documented\n// one-shot mode (no TUI) — its consumers are CI logs and ad-hoc\n// operator runs that read raw stderr. Structured `Effect.log*` records\n// are the only operator-visible diagnostic for non-TUI surfaces, so we\n// route them through the pretty console layer. The sibling `up` verb\n// silences this layer (see `wirings/up.ts` header) because it owns the\n// TUI and would render duplicate / scrambled output otherwise.\n\nimport { dirname, resolve as resolvePath } from 'node:path';\n\nimport { Cause, Effect, Exit, Logger } from 'effect';\n\nimport type { Identity } from '../../substrate/identity.ts';\nimport { StackPathsService } from '../../substrate/runtime/paths.ts';\nimport {\n\tcommandChannelPaths,\n\tmakeCommandChannelPublisher,\n\tmakeProjectionRef,\n\ttype SupervisedStack,\n} from '../../substrate/runtime/index.ts';\nimport { superviseStackWithProductionBoot } from '../../orchestrators/boot.ts';\nimport {\n\tcomputeSnapshotGraphInputFromStack,\n\trecoverInterruptedRestore,\n\tSnapshotOrchestratorService,\n} from '../../orchestrators/snapshot/index.ts';\nimport {\n\ttype CliError,\n\tCliInternalError,\n\tCliLiveGraphMismatchError,\n} from '../../surfaces/cli/errors.ts';\nimport { type CommandResult, probeSupervisorPresence } from '../../surfaces/cli/commands/index.ts';\nimport { ExitCode } from '../../surfaces/cli/sysexits.ts';\nimport type { LoadedConfig } from '../../surfaces/cli/commands/config-loader.ts';\n\nimport { cliErrorFromConfigExit } from '../bail.ts';\nimport { makeConfigLoader } from './config-loader.ts';\nimport {\n\tidentityValueFor,\n\tresolvedIdentityForStack,\n\tstackRootFor,\n\ttype ResolvedIdentity,\n} from './identity.ts';\nimport { buildVerbLayers } from '../../orchestrators/layers.ts';\nimport { readDevstackVersion } from './read-devstack-version.ts';\n\nconst LIVE_APPLY_ACK_TIMEOUT_MILLIS = 10 * 60 * 1000;\n\n/** Outcome of the live-supervisor probe + dispatch:\n * - `kind: 'direct'` — no supervisor; caller runs the direct path.\n * - `kind: 'live-ok'` — live supervisor acked; verb is done.\n * - `kind: 'live-failed'` — live supervisor refused/timed-out; caller\n * surfaces the typed `CliError` via the dispatcher envelope. */\ntype ApplyDispatch =\n\t| { readonly kind: 'direct' }\n\t| { readonly kind: 'live-ok' }\n\t| { readonly kind: 'live-failed'; readonly error: CliError };\n\n/** Probe the roster file; if a supervisor is live, publish\n * `apply.requested` and await its ack. */\nconst runApplyAgainstLiveSupervisor = (\n\tidentity: ResolvedIdentity,\n\tidentityValue: Identity,\n\tcurrentGraphInputId: string,\n): Effect.Effect<ApplyDispatch> =>\n\tEffect.gen(function* () {\n\t\tconst stackRoot = stackRootFor(identity.runtimeRoot, String(identityValue.stack));\n\t\tconst presence = yield* probeSupervisorPresence(resolvePath(stackRoot, 'roster.json')).pipe(\n\t\t\tEffect.catch(() =>\n\t\t\t\tEffect.succeed({ live: false, pid: null, hostname: null, graphInputId: null }),\n\t\t\t),\n\t\t);\n\t\tif (!presence.live) return { kind: 'direct' as const };\n\t\tif (presence.graphInputId !== null && presence.graphInputId !== currentGraphInputId) {\n\t\t\treturn {\n\t\t\t\tkind: 'live-failed' as const,\n\t\t\t\terror: new CliLiveGraphMismatchError({\n\t\t\t\t\tapp: String(identityValue.app),\n\t\t\t\t\tstack: String(identityValue.stack),\n\t\t\t\t\tliveGraphInputId: presence.graphInputId,\n\t\t\t\t\tcurrentGraphInputId,\n\t\t\t\t\thint: 'restart the running `devstack up` session so it loads the edited stack config, then re-run `devstack apply`.',\n\t\t\t\t}),\n\t\t\t};\n\t\t}\n\n\t\tconst exit = yield* Effect.exit(\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst publisher = yield* makeCommandChannelPublisher(commandChannelPaths(stackRoot));\n\t\t\t\tconst published = yield* publisher.publish({ tag: 'apply.requested' });\n\t\t\t\tconst reply = yield* publisher.awaitCompletion(published, {\n\t\t\t\t\ttimeoutMillis: LIVE_APPLY_ACK_TIMEOUT_MILLIS,\n\t\t\t\t});\n\t\t\t\tif (!reply.ok) {\n\t\t\t\t\treturn yield* Effect.fail(reply.message);\n\t\t\t\t}\n\t\t\t}),\n\t\t);\n\n\t\tif (Exit.isFailure(exit)) {\n\t\t\treturn {\n\t\t\t\tkind: 'live-failed' as const,\n\t\t\t\terror: new CliInternalError({\n\t\t\t\t\tmessage: 'live stack apply failed',\n\t\t\t\t\tcause: Cause.pretty(exit.cause as Cause.Cause<unknown>),\n\t\t\t\t}),\n\t\t\t};\n\t\t}\n\n\t\treturn { kind: 'live-ok' as const };\n\t});\n\nexport const runApplyLive = (\n\tconfigPath: string | undefined,\n\tidentity: ResolvedIdentity,\n): Effect.Effect<CommandResult, CliError> => {\n\tconst loader = makeConfigLoader();\n\treturn Effect.gen(function* () {\n\t\t// Config-load failures surface as typed `CliError`s; the\n\t\t// dispatcher's outer `emitFailure` renders the envelope.\n\t\tconst loadExit = yield* Effect.exit(loader.load(configPath));\n\t\tif (Exit.isFailure(loadExit)) {\n\t\t\treturn yield* Effect.fail(cliErrorFromConfigExit(loadExit));\n\t\t}\n\t\tconst loaded = loadExit.value;\n\t\tconst stack = (loaded as LoadedConfig & { readonly engine: SupervisedStack }).engine;\n\t\t// Re-derive the identity against the EFFECTIVE stack (explicit\n\t\t// `--stack`/`$DEVSTACK_STACK` > `config.stackName` > inferred) so\n\t\t// the live-supervisor probe + roster paths target the same stack\n\t\t// the operator selected — matching `snapshot.ts`. An explicit\n\t\t// `--stack` must NOT be overridden by `config.stackName`, otherwise\n\t\t// `apply` would probe/boot the wrong stack's supervisor.\n\t\tconst effectiveIdentity = resolvedIdentityForStack(identity, stack);\n\t\tconst identityValue: Identity = identityValueFor(effectiveIdentity);\n\t\tconst currentGraphInput = yield* computeSnapshotGraphInputFromStack({\n\t\t\tstack,\n\t\t\tdevstackVersion: readDevstackVersion({ fallback: '0.0.0' }),\n\t\t}).pipe(\n\t\t\tEffect.mapError(\n\t\t\t\t(cause) =>\n\t\t\t\t\tnew CliInternalError({\n\t\t\t\t\t\tmessage: 'failed to compute stack graph identity',\n\t\t\t\t\t\tcause,\n\t\t\t\t\t}),\n\t\t\t),\n\t\t);\n\t\tconst dispatch = yield* runApplyAgainstLiveSupervisor(\n\t\t\teffectiveIdentity,\n\t\t\tidentityValue,\n\t\t\tcurrentGraphInput.graphInputId,\n\t\t);\n\t\tif (dispatch.kind === 'live-failed') {\n\t\t\treturn yield* Effect.fail(dispatch.error);\n\t\t}\n\t\tif (dispatch.kind === 'live-ok') {\n\t\t\treturn { exitCode: ExitCode.OK };\n\t\t}\n\t\tconst appRoot = dirname(loaded.resolvedConfigPath);\n\t\tconst substrateLayers = buildVerbLayers({\n\t\t\tidentity: identityValue,\n\t\t\tstack,\n\t\t\tappRoot,\n\t\t\truntimeRoot: effectiveIdentity.runtimeRoot,\n\t\t});\n\n\t\tconst program = Effect.gen(function* () {\n\t\t\tconst state = yield* makeProjectionRef();\n\t\t\tconst stackPaths = yield* StackPathsService;\n\t\t\tconst computeGraphInput = computeSnapshotGraphInputFromStack({\n\t\t\t\tstack,\n\t\t\t\tdevstackVersion: readDevstackVersion({ fallback: '0.0.0' }),\n\t\t\t});\n\t\t\t// Mirror the up-path recovery: resume any restore interrupted by a\n\t\t\t// hard kill between the atomic swap and the image-promotion handoff\n\t\t\t// completing (the interrupted-restore sentinel rode the swap into the\n\t\t\t// live root and was never cleared) BEFORE the one-shot apply starts\n\t\t\t// the stack. Idempotent + no-op when no sentinel is present.\n\t\t\tconst snapshot = yield* SnapshotOrchestratorService;\n\t\t\tyield* recoverInterruptedRestore({\n\t\t\t\tliveRoot: stackPaths.stackRoot,\n\t\t\t\trestoreSnapshot: (id) =>\n\t\t\t\t\tcomputeGraphInput.pipe(\n\t\t\t\t\t\tEffect.flatMap((currentGraphInput) =>\n\t\t\t\t\t\t\tsnapshot.restore({\n\t\t\t\t\t\t\t\tid,\n\t\t\t\t\t\t\t\tcurrentGraphInput,\n\t\t\t\t\t\t\t\tgraphInputMismatchPolicy: 'warn',\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t});\n\t\t\t// The contribution dispatcher + post-acquire hook + built-in\n\t\t\t// plugin-context extension are assembled in ONE place\n\t\t\t// (`orchestrators/boot.ts superviseStackWithProductionBoot`); this\n\t\t\t// one-shot verb supplies only the per-caller inputs (`extras`,\n\t\t\t// `lifetime: 'one-shot'`). Run-to-completion + result semantics are\n\t\t\t// unchanged: the surrounding `matchCauseEffect` below still maps the\n\t\t\t// supervised cause/success onto the `CommandResult`.\n\t\t\tyield* superviseStackWithProductionBoot(\n\t\t\t\t{ _tag: 'Stack', members: stack.members, options: stack.options },\n\t\t\t\tidentityValue,\n\t\t\t\tstate,\n\t\t\t\t{\n\t\t\t\t\textras: stack.options.extras,\n\t\t\t\t\tlifetime: 'one-shot',\n\t\t\t\t},\n\t\t\t);\n\t\t});\n\n\t\treturn yield* program.pipe(\n\t\t\tEffect.provide(substrateLayers),\n\t\t\tEffect.provide(Logger.layer([Logger.consolePretty()])),\n\t\t\tEffect.matchCauseEffect({\n\t\t\t\tonFailure: (cause): Effect.Effect<CommandResult, CliError> =>\n\t\t\t\t\tEffect.fail(\n\t\t\t\t\t\tnew CliInternalError({\n\t\t\t\t\t\t\tmessage: 'stack apply failed',\n\t\t\t\t\t\t\tcause: Cause.pretty(cause as Cause.Cause<unknown>),\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\tonSuccess: (): Effect.Effect<CommandResult, CliError> =>\n\t\t\t\t\tEffect.succeed({ exitCode: ExitCode.OK }),\n\t\t\t}),\n\t\t);\n\t});\n};\n"],"mappings":"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"}
@@ -100,7 +100,8 @@ const runDumpDeployment = (identity, opts) => {
100
100
  if (!(yield* probeSupervisorPresence(effectiveIdentity.rosterFile).pipe(Effect.catch(() => Effect.succeed({
101
101
  live: false,
102
102
  pid: null,
103
- hostname: null
103
+ hostname: null,
104
+ graphInputId: null
104
105
  })))).live) yield* runApplyLive(opts.configPath, identity);
105
106
  const text = yield* readDeploymentText(deploymentFile);
106
107
  const data = yield* parseDeployment(text, deploymentFile);
@@ -1 +1 @@
1
- {"version":3,"file":"dump-deployment.mjs","names":["resolvePath"],"sources":["../../../src/cli/wirings/dump-deployment.ts"],"sourcesContent":["// `devstack dump-deployment` verb wiring.\n//\n// Emits the stack's `deployment.json` deployment — the single source of\n// on-chain ids (see `orchestrators/codegen/deployment.ts`). This is the\n// supported way to obtain a committed deployment file for a real-network\n// deploy (docs' \"Deploy to a real network / Option A\"), replacing the\n// manual `cp .devstack/stacks/<stack>/deployment.json …`.\n//\n// Live-aware, mirroring `apply.ts` / `snapshot.ts`:\n// - If a supervisor owns the selected stack (roster probe), the\n// deployment already exists on disk — boot's post-acquire hook wrote\n// it (`boot.ts buildProductionPostAcquireHook`). Read + emit it; NO\n// re-boot.\n// - Otherwise, run the SAME one-shot boot `apply` runs when no\n// supervisor is live (`runApplyLive` → `superviseStackWithProductionBoot`\n// with `lifetime: 'one-shot'`). That boots, writes the deployment to\n// `<stackRoot>/deployment.json`, and tears down. We then read the\n// freshly-written file and emit it.\n//\n// The on-disk file is already pretty-printed by `writeDeployment`\n// (`JSON.stringify(config, null, 2)\\n`), so `--out` copies its bytes\n// verbatim and stdout prints them verbatim — the format matches the\n// boot-written file exactly.\n\nimport { mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, resolve as resolvePath } from 'node:path';\n\nimport { Effect, Exit } from 'effect';\n\nimport type { SupervisedStack } from '../../substrate/runtime/index.ts';\nimport {\n\tdecodeDeployment,\n\tDEPLOYMENT_FILENAME,\n\ttype DevstackDeployment,\n} from '../../orchestrators/codegen/deployment.ts';\nimport {\n\tdeploymentBody,\n\tDEPLOYMENTS_DIRNAME,\n\trenderNetworkDeploymentFile,\n} from '../../orchestrators/codegen/deployment-network-file.ts';\nimport { LOCAL_NETWORK_NAME } from '../../api/inference-network.ts';\nimport { type CliError, CliInternalError, CliUsageError } from '../../surfaces/cli/errors.ts';\nimport { type CommandResult } from '../../surfaces/cli/commands/index.ts';\nimport { probeSupervisorPresence } from '../../surfaces/cli/commands/index.ts';\nimport { ExitCode } from '../../surfaces/cli/sysexits.ts';\nimport { type CliIO, emitSuccess } from '../../surfaces/cli/output.ts';\nimport type { OutputMode } from '../../surfaces/cli/flags.ts';\nimport type { LoadedConfig } from '../../surfaces/cli/commands/config-loader.ts';\n\nimport { cliErrorFromConfigExit } from '../bail.ts';\nimport { makeConfigLoader } from './config-loader.ts';\nimport { resolvedIdentityForStack, type ResolvedIdentity } from './identity.ts';\nimport { runApplyLive } from './apply.ts';\n\nexport interface DumpDeploymentOptions {\n\treadonly configPath: string | undefined;\n\t/** Destination for the pretty deployment JSON. When omitted (and no\n\t * `--network`), the JSON is printed to stdout. */\n\treadonly out: string | undefined;\n\t/** When set, write a TYPED single-network `deployments/<network>.ts`\n\t * (`export const deployment = {…} satisfies AppNetworkDeployment`) sourced\n\t * from the resolved envelope's `networks.<network>` entry, instead of\n\t * dumping the raw envelope JSON. The committed, `tsc`-checked authoring\n\t * surface for a real-network deploy. */\n\treadonly network: string | undefined;\n\treadonly io: CliIO;\n\treadonly outputMode: OutputMode;\n}\n\n/** Read the on-disk deployment as raw text. The file is written by boot's\n * post-acquire hook (`writeDeployment`) at `<stackRoot>/deployment.json`;\n * a miss here means the boot never produced it (or a hand-removal), which\n * is an internal inconsistency the operator can't act on directly. */\nconst readDeploymentText = (deploymentFile: string): Effect.Effect<string, CliError> =>\n\tEffect.try({\n\t\ttry: () => readFileSync(deploymentFile, 'utf8'),\n\t\tcatch: (cause) =>\n\t\t\tnew CliInternalError({\n\t\t\t\tmessage: `failed to read deployment at ${deploymentFile}`,\n\t\t\t\tcause,\n\t\t\t}),\n\t});\n\nconst writeOutFile = (out: string, text: string): Effect.Effect<void, CliError> =>\n\tEffect.try({\n\t\ttry: () => writeFileSync(out, text, 'utf8'),\n\t\tcatch: (cause) =>\n\t\t\tnew CliInternalError({ message: `failed to write deployment to ${out}`, cause }),\n\t});\n\n/** Decode the deployment text for the JSON envelope `data`, routed through\n * the shared {@link decodeDeployment} so the parse-and-validate decision is\n * centralized (same seam the Vite plugin and codegen verb use). A failure\n * here means the on-disk file is corrupt — bad JSON OR a shape that\n * violates `DeploymentSchema` — so surface it rather than emit a bad\n * envelope. (The file is written by `writeDeployment` from the same schema,\n * so a conforming boot-written file always decodes; only a corrupt or\n * hand-edited file trips the stricter validation.) */\nconst parseDeployment = (\n\ttext: string,\n\tdeploymentFile: string,\n): Effect.Effect<DevstackDeployment, CliError> =>\n\tEffect.try({\n\t\ttry: () => decodeDeployment(text),\n\t\tcatch: (cause) =>\n\t\t\tnew CliInternalError({\n\t\t\t\t// `decodeDeployment` throws on BOTH malformed JSON AND a shape that\n\t\t\t\t// violates `DeploymentSchema`, so the message must cover both causes.\n\t\t\t\tmessage: `could not decode deployment at ${deploymentFile} (malformed JSON or invalid shape)`,\n\t\t\t\tcause,\n\t\t\t}),\n\t});\n\n/** The project root the `deployments/<net>.ts` file is written relative to:\n * the directory of the resolved `devstack.config.ts` (so a `--config` in a\n * subdir still lands `deployments/` next to that app's `src/`), else the\n * process cwd. */\nconst projectRootOf = (configPath: string | undefined): string =>\n\tconfigPath === undefined ? process.cwd() : dirname(resolvePath(configPath));\n\n/** Render + write the typed `deployments/<network>.ts` for `network` from the\n * resolved envelope, creating the `deployments/` dir if absent. Loud-fails\n * (usage error) when the envelope carries no such network — listing the ones\n * it does, so the operator can pick a valid name. */\nexport const writeNetworkDeploymentFile = (\n\tenvelope: DevstackDeployment,\n\tnetwork: string,\n\tprojectRoot: string,\n): Effect.Effect<string, CliError> =>\n\tEffect.gen(function* () {\n\t\tconst unit = envelope.networks[network];\n\t\tif (unit === undefined) {\n\t\t\tconst available = Object.keys(envelope.networks).sort().join(', ');\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew CliUsageError({\n\t\t\t\t\tmessage: `the resolved deployment has no network \"${network}\"`,\n\t\t\t\t\thint:\n\t\t\t\t\t\tavailable.length > 0\n\t\t\t\t\t\t\t? `available networks: ${available}`\n\t\t\t\t\t\t\t: 'the deployment carries no networks',\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\t// A committed `deployments/<net>.ts` is non-local by definition\n\t\t// (deployment-network-file.ts header) — it is the authoring surface for a\n\t\t// REAL-network deploy. The live local stack's envelope is keyed under\n\t\t// `localnet` with `local: true`, so `--network localnet` (or any unit\n\t\t// flagged `local`) must be rejected rather than write a committed file\n\t\t// that captures the throwaway dev stack.\n\t\tif (network === LOCAL_NETWORK_NAME || unit.local === true) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew CliUsageError({\n\t\t\t\t\tmessage: `cannot dump the live local network \"${network}\" as a committed deployment`,\n\t\t\t\t\thint: 'committed deployments are for real networks; pick a non-local --network (e.g. testnet, mainnet)',\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\tconst rendered = renderNetworkDeploymentFile(network, unit);\n\t\tif (!rendered.ok) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew CliInternalError({\n\t\t\t\t\tmessage: `failed to render deployments/${network}.ts`,\n\t\t\t\t\tcause: rendered.error,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\tconst dir = resolvePath(projectRoot, DEPLOYMENTS_DIRNAME);\n\t\tconst outFile = resolvePath(dir, `${network}.ts`);\n\t\tyield* Effect.try({\n\t\t\ttry: () => {\n\t\t\t\tmkdirSync(dir, { recursive: true });\n\t\t\t\twriteFileSync(outFile, rendered.text, 'utf8');\n\t\t\t},\n\t\t\tcatch: (cause) => new CliInternalError({ message: `failed to write ${outFile}`, cause }),\n\t\t});\n\t\treturn outFile;\n\t});\n\nexport const runDumpDeployment = (\n\tidentity: ResolvedIdentity,\n\topts: DumpDeploymentOptions,\n): Effect.Effect<CommandResult, CliError> => {\n\tconst loader = makeConfigLoader();\n\treturn Effect.gen(function* () {\n\t\tconst started = Date.now();\n\n\t\t// Resolve identity against the EFFECTIVE stack (explicit\n\t\t// `--stack`/env > `config.stackName` > inferred), matching `apply` /\n\t\t// `snapshot`, so the roster probe + deployment path target the same\n\t\t// stack the operator selected.\n\t\tconst loadExit = yield* Effect.exit(loader.load(opts.configPath));\n\t\tif (Exit.isFailure(loadExit)) {\n\t\t\treturn yield* Effect.fail(cliErrorFromConfigExit(loadExit));\n\t\t}\n\t\tconst stack = (loadExit.value as LoadedConfig & { readonly engine: SupervisedStack }).engine;\n\t\tconst effectiveIdentity = resolvedIdentityForStack(identity, stack);\n\t\tconst deploymentFile = resolvePath(effectiveIdentity.stackRoot, DEPLOYMENT_FILENAME);\n\n\t\t// Live supervisor? The deployment already exists on disk (boot wrote\n\t\t// it). Otherwise run the one-shot boot `apply` runs when nothing is\n\t\t// live — it writes the deployment to `deploymentFile` and tears down — then\n\t\t// read the file it produced.\n\t\tconst presence = yield* probeSupervisorPresence(effectiveIdentity.rosterFile).pipe(\n\t\t\tEffect.catch(() => Effect.succeed({ live: false, pid: null, hostname: null })),\n\t\t);\n\t\tif (!presence.live) {\n\t\t\t// `runApplyLive` re-probes (sees no live supervisor) and runs the\n\t\t\t// one-shot boot; its config-load / boot failures already surface as\n\t\t\t// typed `CliError`s. We pass the ORIGINAL identity — `runApplyLive`\n\t\t\t// re-derives the effective stack from the same config internally.\n\t\t\tyield* runApplyLive(opts.configPath, identity);\n\t\t}\n\n\t\tconst text = yield* readDeploymentText(deploymentFile);\n\t\tconst data = yield* parseDeployment(text, deploymentFile);\n\n\t\t// `--network <net>`: emit a TYPED single-network `deployments/<net>.ts`\n\t\t// (`satisfies AppNetworkDeployment`) instead of the raw envelope. Sourced\n\t\t// from the resolved envelope's `networks.<net>` entry — the same data\n\t\t// `assembleDeployment` produced — written next to the app's `src/`.\n\t\tif (opts.network !== undefined) {\n\t\t\tconst projectRoot = projectRootOf(opts.configPath);\n\t\t\tconst outFile = yield* writeNetworkDeploymentFile(data, opts.network, projectRoot);\n\t\t\t// The JSON `data` must mirror the file actually written — the NORMALIZED\n\t\t\t// body (network re-derived from the arg, dev-only `local` dropped), NOT\n\t\t\t// the raw envelope unit (which still carries `local` + the boot-written\n\t\t\t// `network`). `writeNetworkDeploymentFile` already guaranteed the unit\n\t\t\t// exists, so the lookup here is non-undefined.\n\t\t\tconst writtenUnit = data.networks[opts.network]!;\n\t\t\tyield* emitSuccess(opts.io, opts.outputMode, {\n\t\t\t\tcommand: 'dump-deployment',\n\t\t\t\telapsedMs: Date.now() - started,\n\t\t\t\tdata: deploymentBody(opts.network, writtenUnit),\n\t\t\t\thumanLines: [`wrote ${outFile}`],\n\t\t\t});\n\t\t\treturn { exitCode: ExitCode.OK };\n\t\t}\n\n\t\tif (opts.out !== undefined) {\n\t\t\tyield* writeOutFile(opts.out, text);\n\t\t\tyield* emitSuccess(opts.io, opts.outputMode, {\n\t\t\t\tcommand: 'dump-deployment',\n\t\t\t\telapsedMs: Date.now() - started,\n\t\t\t\tdata,\n\t\t\t\thumanLines: [`wrote deployment to ${opts.out}`],\n\t\t\t});\n\t\t\treturn { exitCode: ExitCode.OK };\n\t\t}\n\n\t\t// No `--out`: print the deployment JSON to stdout. In human mode the\n\t\t// raw pretty JSON IS the output (trailing newline trimmed by the IO\n\t\t// layer); in JSON mode the same object rides the success envelope's\n\t\t// `data`.\n\t\tyield* emitSuccess(opts.io, opts.outputMode, {\n\t\t\tcommand: 'dump-deployment',\n\t\t\telapsedMs: Date.now() - started,\n\t\t\tdata,\n\t\t\thumanLines: [text.endsWith('\\n') ? text.slice(0, -1) : text],\n\t\t});\n\t\treturn { exitCode: ExitCode.OK };\n\t});\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"dump-deployment.mjs","names":["resolvePath"],"sources":["../../../src/cli/wirings/dump-deployment.ts"],"sourcesContent":["// `devstack dump-deployment` verb wiring.\n//\n// Emits the stack's `deployment.json` deployment — the single source of\n// on-chain ids (see `orchestrators/codegen/deployment.ts`). This is the\n// supported way to obtain a committed deployment file for a real-network\n// deploy (docs' \"Deploy to a real network / Option A\"), replacing the\n// manual `cp .devstack/stacks/<stack>/deployment.json …`.\n//\n// Live-aware, mirroring `apply.ts` / `snapshot.ts`:\n// - If a supervisor owns the selected stack (roster probe), the\n// deployment already exists on disk — boot's post-acquire hook wrote\n// it (`boot.ts buildProductionPostAcquireHook`). Read + emit it; NO\n// re-boot.\n// - Otherwise, run the SAME one-shot boot `apply` runs when no\n// supervisor is live (`runApplyLive` → `superviseStackWithProductionBoot`\n// with `lifetime: 'one-shot'`). That boots, writes the deployment to\n// `<stackRoot>/deployment.json`, and tears down. We then read the\n// freshly-written file and emit it.\n//\n// The on-disk file is already pretty-printed by `writeDeployment`\n// (`JSON.stringify(config, null, 2)\\n`), so `--out` copies its bytes\n// verbatim and stdout prints them verbatim — the format matches the\n// boot-written file exactly.\n\nimport { mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, resolve as resolvePath } from 'node:path';\n\nimport { Effect, Exit } from 'effect';\n\nimport type { SupervisedStack } from '../../substrate/runtime/index.ts';\nimport {\n\tdecodeDeployment,\n\tDEPLOYMENT_FILENAME,\n\ttype DevstackDeployment,\n} from '../../orchestrators/codegen/deployment.ts';\nimport {\n\tdeploymentBody,\n\tDEPLOYMENTS_DIRNAME,\n\trenderNetworkDeploymentFile,\n} from '../../orchestrators/codegen/deployment-network-file.ts';\nimport { LOCAL_NETWORK_NAME } from '../../api/inference-network.ts';\nimport { type CliError, CliInternalError, CliUsageError } from '../../surfaces/cli/errors.ts';\nimport { type CommandResult } from '../../surfaces/cli/commands/index.ts';\nimport { probeSupervisorPresence } from '../../surfaces/cli/commands/index.ts';\nimport { ExitCode } from '../../surfaces/cli/sysexits.ts';\nimport { type CliIO, emitSuccess } from '../../surfaces/cli/output.ts';\nimport type { OutputMode } from '../../surfaces/cli/flags.ts';\nimport type { LoadedConfig } from '../../surfaces/cli/commands/config-loader.ts';\n\nimport { cliErrorFromConfigExit } from '../bail.ts';\nimport { makeConfigLoader } from './config-loader.ts';\nimport { resolvedIdentityForStack, type ResolvedIdentity } from './identity.ts';\nimport { runApplyLive } from './apply.ts';\n\nexport interface DumpDeploymentOptions {\n\treadonly configPath: string | undefined;\n\t/** Destination for the pretty deployment JSON. When omitted (and no\n\t * `--network`), the JSON is printed to stdout. */\n\treadonly out: string | undefined;\n\t/** When set, write a TYPED single-network `deployments/<network>.ts`\n\t * (`export const deployment = {…} satisfies AppNetworkDeployment`) sourced\n\t * from the resolved envelope's `networks.<network>` entry, instead of\n\t * dumping the raw envelope JSON. The committed, `tsc`-checked authoring\n\t * surface for a real-network deploy. */\n\treadonly network: string | undefined;\n\treadonly io: CliIO;\n\treadonly outputMode: OutputMode;\n}\n\n/** Read the on-disk deployment as raw text. The file is written by boot's\n * post-acquire hook (`writeDeployment`) at `<stackRoot>/deployment.json`;\n * a miss here means the boot never produced it (or a hand-removal), which\n * is an internal inconsistency the operator can't act on directly. */\nconst readDeploymentText = (deploymentFile: string): Effect.Effect<string, CliError> =>\n\tEffect.try({\n\t\ttry: () => readFileSync(deploymentFile, 'utf8'),\n\t\tcatch: (cause) =>\n\t\t\tnew CliInternalError({\n\t\t\t\tmessage: `failed to read deployment at ${deploymentFile}`,\n\t\t\t\tcause,\n\t\t\t}),\n\t});\n\nconst writeOutFile = (out: string, text: string): Effect.Effect<void, CliError> =>\n\tEffect.try({\n\t\ttry: () => writeFileSync(out, text, 'utf8'),\n\t\tcatch: (cause) =>\n\t\t\tnew CliInternalError({ message: `failed to write deployment to ${out}`, cause }),\n\t});\n\n/** Decode the deployment text for the JSON envelope `data`, routed through\n * the shared {@link decodeDeployment} so the parse-and-validate decision is\n * centralized (same seam the Vite plugin and codegen verb use). A failure\n * here means the on-disk file is corrupt — bad JSON OR a shape that\n * violates `DeploymentSchema` — so surface it rather than emit a bad\n * envelope. (The file is written by `writeDeployment` from the same schema,\n * so a conforming boot-written file always decodes; only a corrupt or\n * hand-edited file trips the stricter validation.) */\nconst parseDeployment = (\n\ttext: string,\n\tdeploymentFile: string,\n): Effect.Effect<DevstackDeployment, CliError> =>\n\tEffect.try({\n\t\ttry: () => decodeDeployment(text),\n\t\tcatch: (cause) =>\n\t\t\tnew CliInternalError({\n\t\t\t\t// `decodeDeployment` throws on BOTH malformed JSON AND a shape that\n\t\t\t\t// violates `DeploymentSchema`, so the message must cover both causes.\n\t\t\t\tmessage: `could not decode deployment at ${deploymentFile} (malformed JSON or invalid shape)`,\n\t\t\t\tcause,\n\t\t\t}),\n\t});\n\n/** The project root the `deployments/<net>.ts` file is written relative to:\n * the directory of the resolved `devstack.config.ts` (so a `--config` in a\n * subdir still lands `deployments/` next to that app's `src/`), else the\n * process cwd. */\nconst projectRootOf = (configPath: string | undefined): string =>\n\tconfigPath === undefined ? process.cwd() : dirname(resolvePath(configPath));\n\n/** Render + write the typed `deployments/<network>.ts` for `network` from the\n * resolved envelope, creating the `deployments/` dir if absent. Loud-fails\n * (usage error) when the envelope carries no such network — listing the ones\n * it does, so the operator can pick a valid name. */\nexport const writeNetworkDeploymentFile = (\n\tenvelope: DevstackDeployment,\n\tnetwork: string,\n\tprojectRoot: string,\n): Effect.Effect<string, CliError> =>\n\tEffect.gen(function* () {\n\t\tconst unit = envelope.networks[network];\n\t\tif (unit === undefined) {\n\t\t\tconst available = Object.keys(envelope.networks).sort().join(', ');\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew CliUsageError({\n\t\t\t\t\tmessage: `the resolved deployment has no network \"${network}\"`,\n\t\t\t\t\thint:\n\t\t\t\t\t\tavailable.length > 0\n\t\t\t\t\t\t\t? `available networks: ${available}`\n\t\t\t\t\t\t\t: 'the deployment carries no networks',\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\t// A committed `deployments/<net>.ts` is non-local by definition\n\t\t// (deployment-network-file.ts header) — it is the authoring surface for a\n\t\t// REAL-network deploy. The live local stack's envelope is keyed under\n\t\t// `localnet` with `local: true`, so `--network localnet` (or any unit\n\t\t// flagged `local`) must be rejected rather than write a committed file\n\t\t// that captures the throwaway dev stack.\n\t\tif (network === LOCAL_NETWORK_NAME || unit.local === true) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew CliUsageError({\n\t\t\t\t\tmessage: `cannot dump the live local network \"${network}\" as a committed deployment`,\n\t\t\t\t\thint: 'committed deployments are for real networks; pick a non-local --network (e.g. testnet, mainnet)',\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\tconst rendered = renderNetworkDeploymentFile(network, unit);\n\t\tif (!rendered.ok) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew CliInternalError({\n\t\t\t\t\tmessage: `failed to render deployments/${network}.ts`,\n\t\t\t\t\tcause: rendered.error,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\tconst dir = resolvePath(projectRoot, DEPLOYMENTS_DIRNAME);\n\t\tconst outFile = resolvePath(dir, `${network}.ts`);\n\t\tyield* Effect.try({\n\t\t\ttry: () => {\n\t\t\t\tmkdirSync(dir, { recursive: true });\n\t\t\t\twriteFileSync(outFile, rendered.text, 'utf8');\n\t\t\t},\n\t\t\tcatch: (cause) => new CliInternalError({ message: `failed to write ${outFile}`, cause }),\n\t\t});\n\t\treturn outFile;\n\t});\n\nexport const runDumpDeployment = (\n\tidentity: ResolvedIdentity,\n\topts: DumpDeploymentOptions,\n): Effect.Effect<CommandResult, CliError> => {\n\tconst loader = makeConfigLoader();\n\treturn Effect.gen(function* () {\n\t\tconst started = Date.now();\n\n\t\t// Resolve identity against the EFFECTIVE stack (explicit\n\t\t// `--stack`/env > `config.stackName` > inferred), matching `apply` /\n\t\t// `snapshot`, so the roster probe + deployment path target the same\n\t\t// stack the operator selected.\n\t\tconst loadExit = yield* Effect.exit(loader.load(opts.configPath));\n\t\tif (Exit.isFailure(loadExit)) {\n\t\t\treturn yield* Effect.fail(cliErrorFromConfigExit(loadExit));\n\t\t}\n\t\tconst stack = (loadExit.value as LoadedConfig & { readonly engine: SupervisedStack }).engine;\n\t\tconst effectiveIdentity = resolvedIdentityForStack(identity, stack);\n\t\tconst deploymentFile = resolvePath(effectiveIdentity.stackRoot, DEPLOYMENT_FILENAME);\n\n\t\t// Live supervisor? The deployment already exists on disk (boot wrote\n\t\t// it). Otherwise run the one-shot boot `apply` runs when nothing is\n\t\t// live — it writes the deployment to `deploymentFile` and tears down — then\n\t\t// read the file it produced.\n\t\tconst presence = yield* probeSupervisorPresence(effectiveIdentity.rosterFile).pipe(\n\t\t\tEffect.catch(() =>\n\t\t\t\tEffect.succeed({ live: false, pid: null, hostname: null, graphInputId: null }),\n\t\t\t),\n\t\t);\n\t\tif (!presence.live) {\n\t\t\t// `runApplyLive` re-probes (sees no live supervisor) and runs the\n\t\t\t// one-shot boot; its config-load / boot failures already surface as\n\t\t\t// typed `CliError`s. We pass the ORIGINAL identity — `runApplyLive`\n\t\t\t// re-derives the effective stack from the same config internally.\n\t\t\tyield* runApplyLive(opts.configPath, identity);\n\t\t}\n\n\t\tconst text = yield* readDeploymentText(deploymentFile);\n\t\tconst data = yield* parseDeployment(text, deploymentFile);\n\n\t\t// `--network <net>`: emit a TYPED single-network `deployments/<net>.ts`\n\t\t// (`satisfies AppNetworkDeployment`) instead of the raw envelope. Sourced\n\t\t// from the resolved envelope's `networks.<net>` entry — the same data\n\t\t// `assembleDeployment` produced — written next to the app's `src/`.\n\t\tif (opts.network !== undefined) {\n\t\t\tconst projectRoot = projectRootOf(opts.configPath);\n\t\t\tconst outFile = yield* writeNetworkDeploymentFile(data, opts.network, projectRoot);\n\t\t\t// The JSON `data` must mirror the file actually written — the NORMALIZED\n\t\t\t// body (network re-derived from the arg, dev-only `local` dropped), NOT\n\t\t\t// the raw envelope unit (which still carries `local` + the boot-written\n\t\t\t// `network`). `writeNetworkDeploymentFile` already guaranteed the unit\n\t\t\t// exists, so the lookup here is non-undefined.\n\t\t\tconst writtenUnit = data.networks[opts.network]!;\n\t\t\tyield* emitSuccess(opts.io, opts.outputMode, {\n\t\t\t\tcommand: 'dump-deployment',\n\t\t\t\telapsedMs: Date.now() - started,\n\t\t\t\tdata: deploymentBody(opts.network, writtenUnit),\n\t\t\t\thumanLines: [`wrote ${outFile}`],\n\t\t\t});\n\t\t\treturn { exitCode: ExitCode.OK };\n\t\t}\n\n\t\tif (opts.out !== undefined) {\n\t\t\tyield* writeOutFile(opts.out, text);\n\t\t\tyield* emitSuccess(opts.io, opts.outputMode, {\n\t\t\t\tcommand: 'dump-deployment',\n\t\t\t\telapsedMs: Date.now() - started,\n\t\t\t\tdata,\n\t\t\t\thumanLines: [`wrote deployment to ${opts.out}`],\n\t\t\t});\n\t\t\treturn { exitCode: ExitCode.OK };\n\t\t}\n\n\t\t// No `--out`: print the deployment JSON to stdout. In human mode the\n\t\t// raw pretty JSON IS the output (trailing newline trimmed by the IO\n\t\t// layer); in JSON mode the same object rides the success envelope's\n\t\t// `data`.\n\t\tyield* emitSuccess(opts.io, opts.outputMode, {\n\t\t\tcommand: 'dump-deployment',\n\t\t\telapsedMs: Date.now() - started,\n\t\t\tdata,\n\t\t\thumanLines: [text.endsWith('\\n') ? text.slice(0, -1) : text],\n\t\t});\n\t\treturn { exitCode: ExitCode.OK };\n\t});\n};\n"],"mappings":"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"}
@@ -51,7 +51,8 @@ const ensureNoLiveSupervisor = (identity, hint) => Effect.gen(function* () {
51
51
  if ((yield* probeSupervisorPresence(identity.rosterFile).pipe(Effect.catch(() => Effect.succeed({
52
52
  live: false,
53
53
  pid: null,
54
- hostname: null
54
+ hostname: null,
55
+ graphInputId: null
55
56
  })))).live) return yield* Effect.fail(new CliSupervisorLiveError({
56
57
  app: identity.app,
57
58
  stack: identity.stack,
@@ -1 +1 @@
1
- {"version":3,"file":"identity.mjs","names":["resolvePath"],"sources":["../../../src/cli/wirings/identity.ts"],"sourcesContent":["// Shared identity types + helpers consumed by every verb wiring.\n//\n// `ResolvedIdentity` is the flat CLI-internal identity bundle (app /\n// stack / network / runtimeRoot / stackRoot) that the `main.ts` argv\n// pre-parser produces. Wirings consume it directly; they do NOT\n// re-derive identity from process.env.\n\nimport { resolve as resolvePath } from 'node:path';\n\nimport { Cause, Effect } from 'effect';\n\nimport { appName, stackName } from '../../substrate/brand.ts';\nimport type { Identity } from '../../substrate/identity.ts';\nimport type { SupervisedStack } from '../../substrate/runtime/index.ts';\nimport { CliSupervisorLiveError } from '../../surfaces/cli/index.ts';\nimport { probeSupervisorPresence } from '../../surfaces/cli/commands/index.ts';\n\nexport interface ResolvedIdentity {\n\treadonly app: string;\n\treadonly stack: string;\n\treadonly network: string;\n\treadonly runtimeRoot: string;\n\treadonly stacksRoot: string;\n\treadonly stackRoot: string;\n\treadonly rosterFile: string;\n\t/** The stack name the operator supplied EXPLICITLY via the `--stack`\n\t * flag or `$DEVSTACK_STACK` env var — `undefined` when neither was\n\t * given (i.e. `stack` above was cwd/package-inferred). Threaded from\n\t * `main.ts`'s argv pre-parser so the stack-precedence ladder below\n\t * can let an explicit flag/env beat a config-declared `stackName`. */\n\treadonly explicitStack?: string | undefined;\n}\n\nexport const stackRootFor = (runtimeRoot: string, stack: string): string =>\n\tresolvePath(runtimeRoot, 'stacks', stack);\n\n/** Stack-name precedence ladder, mirrored from the state-dir ladder in\n * `main.ts` (`--state-dir` flag > `config.stateDir` > env > default):\n *\n * explicit `--stack` / `$DEVSTACK_STACK` (`identity.explicitStack`)\n * > config's `defineDevstack({ stackName })` (`stack.options.stackName`)\n * > cwd/package inference (already folded into `identity.stack`,\n * default `'main'`).\n *\n * The explicit rung MUST win over a config `stackName` so an operator\n * can run `pnpm dev` (default stack) and `pnpm test:e2e` (a `test`/`e2e`\n * stack via `--stack`/env) against the SAME config concurrently — the\n * two supervisors then claim distinct stack roots instead of colliding\n * on `error: supervisor live for <app>/<stack>` (exit 40). `runStack`\n * (`api/run-stack.ts`) already prefers its explicit option over\n * `stack.options.stackName`; this keeps the CLI consistent with it. */\nconst effectiveStackName = (identity: ResolvedIdentity, stack?: SupervisedStack): string =>\n\tidentity.explicitStack ?? stack?.options.stackName ?? identity.stack;\n\nexport const identityValueFor = (\n\tidentity: ResolvedIdentity,\n\tstack?: SupervisedStack,\n): Identity => ({\n\tapp: appName(identity.app),\n\tstack: stackName(effectiveStackName(identity, stack)),\n\tnetwork: identity.network,\n});\n\n/** When a verb loads a config whose `stackName` may differ from the\n * effective stack, re-derive the `ResolvedIdentity` against the\n * effective stack so the stack-root, roster file, and command channel\n * paths all target the same stack the supervisor uses. The effective\n * stack follows the precedence ladder documented on `effectiveStackName`\n * (explicit flag/env > config `stackName` > inferred), so an explicit\n * `--stack`/`$DEVSTACK_STACK` is NOT overridden by `config.stackName`. */\nexport const resolvedIdentityForStack = (\n\tidentity: ResolvedIdentity,\n\tstack: SupervisedStack,\n): ResolvedIdentity => {\n\tconst stackValue = effectiveStackName(identity, stack);\n\tconst stackRoot = stackRootFor(identity.runtimeRoot, stackValue);\n\treturn {\n\t\t...identity,\n\t\tstack: stackValue,\n\t\tstackRoot,\n\t\trosterFile: resolvePath(stackRoot, 'roster.json'),\n\t};\n};\n\n/** Probe the roster file; if a supervisor is live, refuse the verb\n * with a `CliSupervisorLiveError` carrying the operator hint. */\nexport const ensureNoLiveSupervisor = (\n\tidentity: ResolvedIdentity,\n\thint: string,\n): Effect.Effect<void, CliSupervisorLiveError> =>\n\tEffect.gen(function* () {\n\t\tconst presence = yield* probeSupervisorPresence(identity.rosterFile).pipe(\n\t\t\tEffect.catch(() => Effect.succeed({ live: false, pid: null, hostname: null })),\n\t\t);\n\t\tif (presence.live) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew CliSupervisorLiveError({\n\t\t\t\t\tapp: identity.app,\n\t\t\t\t\tstack: identity.stack,\n\t\t\t\t\thint,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t});\n\nexport const findCliSupervisorLiveError = (\n\tcause: Cause.Cause<unknown>,\n): CliSupervisorLiveError | null => {\n\tfor (const reason of cause.reasons) {\n\t\tif (!Cause.isFailReason(reason)) continue;\n\t\tconst error = reason.error;\n\t\tif (\n\t\t\ttypeof error === 'object' &&\n\t\t\terror !== null &&\n\t\t\t(error as { readonly _tag?: unknown })._tag === 'CliSupervisorLiveError'\n\t\t) {\n\t\t\treturn error as CliSupervisorLiveError;\n\t\t}\n\t}\n\treturn null;\n};\n"],"mappings":";;;;;;;;AAiCA,MAAa,gBAAgB,aAAqB,UACjDA,QAAY,aAAa,UAAU,KAAK;;;;;;;;;;;;;;;;AAiBzC,MAAM,sBAAsB,UAA4B,UACvD,SAAS,iBAAiB,OAAO,QAAQ,aAAa,SAAS;AAEhE,MAAa,oBACZ,UACA,WACe;CACf,KAAK,QAAQ,SAAS,GAAG;CACzB,OAAO,UAAU,mBAAmB,UAAU,KAAK,CAAC;CACpD,SAAS,SAAS;AACnB;;;;;;;;AASA,MAAa,4BACZ,UACA,UACsB;CACtB,MAAM,aAAa,mBAAmB,UAAU,KAAK;CACrD,MAAM,YAAY,aAAa,SAAS,aAAa,UAAU;CAC/D,OAAO;EACN,GAAG;EACH,OAAO;EACP;EACA,YAAYA,QAAY,WAAW,aAAa;CACjD;AACD;;;AAIA,MAAa,0BACZ,UACA,SAEA,OAAO,IAAI,aAAa;CAIvB,KAAI,OAHoB,wBAAwB,SAAS,UAAU,CAAC,CAAC,KACpE,OAAO,YAAY,OAAO,QAAQ;EAAE,MAAM;EAAO,KAAK;EAAM,UAAU;CAAK,CAAC,CAAC,CAC9E,EAAA,CACa,MACZ,OAAO,OAAO,OAAO,KACpB,IAAI,uBAAuB;EAC1B,KAAK,SAAS;EACd,OAAO,SAAS;EAChB;CACD,CAAC,CACF;AAEF,CAAC;AAEF,MAAa,8BACZ,UACmC;CACnC,KAAK,MAAM,UAAU,MAAM,SAAS;EACnC,IAAI,CAAC,MAAM,aAAa,MAAM,GAAG;EACjC,MAAM,QAAQ,OAAO;EACrB,IACC,OAAO,UAAU,YACjB,UAAU,QACT,MAAsC,SAAS,0BAEhD,OAAO;CAET;CACA,OAAO;AACR"}
1
+ {"version":3,"file":"identity.mjs","names":["resolvePath"],"sources":["../../../src/cli/wirings/identity.ts"],"sourcesContent":["// Shared identity types + helpers consumed by every verb wiring.\n//\n// `ResolvedIdentity` is the flat CLI-internal identity bundle (app /\n// stack / network / runtimeRoot / stackRoot) that the `main.ts` argv\n// pre-parser produces. Wirings consume it directly; they do NOT\n// re-derive identity from process.env.\n\nimport { resolve as resolvePath } from 'node:path';\n\nimport { Cause, Effect } from 'effect';\n\nimport { appName, stackName } from '../../substrate/brand.ts';\nimport type { Identity } from '../../substrate/identity.ts';\nimport type { SupervisedStack } from '../../substrate/runtime/index.ts';\nimport { CliSupervisorLiveError } from '../../surfaces/cli/index.ts';\nimport { probeSupervisorPresence } from '../../surfaces/cli/commands/index.ts';\n\nexport interface ResolvedIdentity {\n\treadonly app: string;\n\treadonly stack: string;\n\treadonly network: string;\n\treadonly runtimeRoot: string;\n\treadonly stacksRoot: string;\n\treadonly stackRoot: string;\n\treadonly rosterFile: string;\n\t/** The stack name the operator supplied EXPLICITLY via the `--stack`\n\t * flag or `$DEVSTACK_STACK` env var — `undefined` when neither was\n\t * given (i.e. `stack` above was cwd/package-inferred). Threaded from\n\t * `main.ts`'s argv pre-parser so the stack-precedence ladder below\n\t * can let an explicit flag/env beat a config-declared `stackName`. */\n\treadonly explicitStack?: string | undefined;\n}\n\nexport const stackRootFor = (runtimeRoot: string, stack: string): string =>\n\tresolvePath(runtimeRoot, 'stacks', stack);\n\n/** Stack-name precedence ladder, mirrored from the state-dir ladder in\n * `main.ts` (`--state-dir` flag > `config.stateDir` > env > default):\n *\n * explicit `--stack` / `$DEVSTACK_STACK` (`identity.explicitStack`)\n * > config's `defineDevstack({ stackName })` (`stack.options.stackName`)\n * > cwd/package inference (already folded into `identity.stack`,\n * default `'main'`).\n *\n * The explicit rung MUST win over a config `stackName` so an operator\n * can run `pnpm dev` (default stack) and `pnpm test:e2e` (a `test`/`e2e`\n * stack via `--stack`/env) against the SAME config concurrently — the\n * two supervisors then claim distinct stack roots instead of colliding\n * on `error: supervisor live for <app>/<stack>` (exit 40). `runStack`\n * (`api/run-stack.ts`) already prefers its explicit option over\n * `stack.options.stackName`; this keeps the CLI consistent with it. */\nconst effectiveStackName = (identity: ResolvedIdentity, stack?: SupervisedStack): string =>\n\tidentity.explicitStack ?? stack?.options.stackName ?? identity.stack;\n\nexport const identityValueFor = (\n\tidentity: ResolvedIdentity,\n\tstack?: SupervisedStack,\n): Identity => ({\n\tapp: appName(identity.app),\n\tstack: stackName(effectiveStackName(identity, stack)),\n\tnetwork: identity.network,\n});\n\n/** When a verb loads a config whose `stackName` may differ from the\n * effective stack, re-derive the `ResolvedIdentity` against the\n * effective stack so the stack-root, roster file, and command channel\n * paths all target the same stack the supervisor uses. The effective\n * stack follows the precedence ladder documented on `effectiveStackName`\n * (explicit flag/env > config `stackName` > inferred), so an explicit\n * `--stack`/`$DEVSTACK_STACK` is NOT overridden by `config.stackName`. */\nexport const resolvedIdentityForStack = (\n\tidentity: ResolvedIdentity,\n\tstack: SupervisedStack,\n): ResolvedIdentity => {\n\tconst stackValue = effectiveStackName(identity, stack);\n\tconst stackRoot = stackRootFor(identity.runtimeRoot, stackValue);\n\treturn {\n\t\t...identity,\n\t\tstack: stackValue,\n\t\tstackRoot,\n\t\trosterFile: resolvePath(stackRoot, 'roster.json'),\n\t};\n};\n\n/** Probe the roster file; if a supervisor is live, refuse the verb\n * with a `CliSupervisorLiveError` carrying the operator hint. */\nexport const ensureNoLiveSupervisor = (\n\tidentity: ResolvedIdentity,\n\thint: string,\n): Effect.Effect<void, CliSupervisorLiveError> =>\n\tEffect.gen(function* () {\n\t\tconst presence = yield* probeSupervisorPresence(identity.rosterFile).pipe(\n\t\t\tEffect.catch(() =>\n\t\t\t\tEffect.succeed({ live: false, pid: null, hostname: null, graphInputId: null }),\n\t\t\t),\n\t\t);\n\t\tif (presence.live) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew CliSupervisorLiveError({\n\t\t\t\t\tapp: identity.app,\n\t\t\t\t\tstack: identity.stack,\n\t\t\t\t\thint,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t});\n\nexport const findCliSupervisorLiveError = (\n\tcause: Cause.Cause<unknown>,\n): CliSupervisorLiveError | null => {\n\tfor (const reason of cause.reasons) {\n\t\tif (!Cause.isFailReason(reason)) continue;\n\t\tconst error = reason.error;\n\t\tif (\n\t\t\ttypeof error === 'object' &&\n\t\t\terror !== null &&\n\t\t\t(error as { readonly _tag?: unknown })._tag === 'CliSupervisorLiveError'\n\t\t) {\n\t\t\treturn error as CliSupervisorLiveError;\n\t\t}\n\t}\n\treturn null;\n};\n"],"mappings":";;;;;;;;AAiCA,MAAa,gBAAgB,aAAqB,UACjDA,QAAY,aAAa,UAAU,KAAK;;;;;;;;;;;;;;;;AAiBzC,MAAM,sBAAsB,UAA4B,UACvD,SAAS,iBAAiB,OAAO,QAAQ,aAAa,SAAS;AAEhE,MAAa,oBACZ,UACA,WACe;CACf,KAAK,QAAQ,SAAS,GAAG;CACzB,OAAO,UAAU,mBAAmB,UAAU,KAAK,CAAC;CACpD,SAAS,SAAS;AACnB;;;;;;;;AASA,MAAa,4BACZ,UACA,UACsB;CACtB,MAAM,aAAa,mBAAmB,UAAU,KAAK;CACrD,MAAM,YAAY,aAAa,SAAS,aAAa,UAAU;CAC/D,OAAO;EACN,GAAG;EACH,OAAO;EACP;EACA,YAAYA,QAAY,WAAW,aAAa;CACjD;AACD;;;AAIA,MAAa,0BACZ,UACA,SAEA,OAAO,IAAI,aAAa;CAMvB,KAAI,OALoB,wBAAwB,SAAS,UAAU,CAAC,CAAC,KACpE,OAAO,YACN,OAAO,QAAQ;EAAE,MAAM;EAAO,KAAK;EAAM,UAAU;EAAM,cAAc;CAAK,CAAC,CAC9E,CACD,EAAA,CACa,MACZ,OAAO,OAAO,OAAO,KACpB,IAAI,uBAAuB;EAC1B,KAAK,SAAS;EACd,OAAO,SAAS;EAChB;CACD,CAAC,CACF;AAEF,CAAC;AAEF,MAAa,8BACZ,UACmC;CACnC,KAAK,MAAM,UAAU,MAAM,SAAS;EACnC,IAAI,CAAC,MAAM,aAAa,MAAM,GAAG;EACjC,MAAM,QAAQ,OAAO;EACrB,IACC,OAAO,UAAU,YACjB,UAAU,QACT,MAAsC,SAAS,0BAEhD,OAAO;CAET;CACA,OAAO;AACR"}
@@ -35,7 +35,8 @@ const runSnapshotCaptureAgainstLiveSupervisor = (identity, args) => Effect.gen(f
35
35
  if (!(yield* probeSupervisorPresence(identity.rosterFile).pipe(Effect.catch(() => Effect.succeed({
36
36
  live: false,
37
37
  pid: null,
38
- hostname: null
38
+ hostname: null,
39
+ graphInputId: null
39
40
  })))).live) return null;
40
41
  const snapshotId = args.snapshotId ?? mintCliSnapshotId();
41
42
  return yield* Effect.scoped(Effect.gen(function* () {