@mysten-incubation/devstack 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/README.md +1 -1
  2. package/dashboard-ui/assets/{grpc-a4usE3Nk.js → grpc-CpkDu4SA.js} +1 -1
  3. package/dashboard-ui/assets/{index-CRYJ4pru.js → index-jLPRmjst.js} +39 -33
  4. package/dashboard-ui/index.html +1 -1
  5. package/dist/api/inference-network.d.mts +5 -5
  6. package/dist/api/inference-network.mjs +5 -5
  7. package/dist/api/inference-network.mjs.map +1 -1
  8. package/dist/api/run-stack-internal.mjs +2 -3
  9. package/dist/api/run-stack-internal.mjs.map +1 -1
  10. package/dist/build-integrations/dapp-kit/index.d.mts +12 -0
  11. package/dist/build-integrations/dapp-kit/index.mjs +14 -1
  12. package/dist/build-integrations/dapp-kit/index.mjs.map +1 -1
  13. package/dist/build-integrations/playwright/config.d.mts +2 -1
  14. package/dist/build-integrations/playwright/config.mjs +1 -1
  15. package/dist/build-integrations/playwright/config.mjs.map +1 -1
  16. package/dist/build-integrations/playwright/errors.d.mts +1 -1
  17. package/dist/build-integrations/playwright/errors.mjs.map +1 -1
  18. package/dist/build-integrations/playwright/index.d.mts +2 -2
  19. package/dist/build-integrations/playwright/index.mjs +2 -2
  20. package/dist/build-integrations/playwright/wallet-context.d.mts +2 -1
  21. package/dist/build-integrations/playwright/wallet-context.mjs +141 -41
  22. package/dist/build-integrations/playwright/wallet-context.mjs.map +1 -1
  23. package/dist/build-integrations/runtime/dapp-kit-slot.mjs +1 -1
  24. package/dist/build-integrations/runtime/dapp-kit-slot.mjs.map +1 -1
  25. package/dist/build-integrations/vite/index.d.mts +82 -40
  26. package/dist/build-integrations/vite/index.mjs +281 -112
  27. package/dist/build-integrations/vite/index.mjs.map +1 -1
  28. package/dist/build-integrations/vitest/config.mjs +3 -4
  29. package/dist/build-integrations/vitest/config.mjs.map +1 -1
  30. package/dist/build-integrations/vitest/global-setup.mjs +8 -8
  31. package/dist/build-integrations/vitest/global-setup.mjs.map +1 -1
  32. package/dist/cli/doctor-probes.mjs +7 -2
  33. package/dist/cli/doctor-probes.mjs.map +1 -1
  34. package/dist/cli/main.mjs +5 -4
  35. package/dist/cli/main.mjs.map +1 -1
  36. package/dist/cli/wirings/apply.mjs +0 -1
  37. package/dist/cli/wirings/apply.mjs.map +1 -1
  38. package/dist/cli/wirings/codegen.mjs +37 -38
  39. package/dist/cli/wirings/codegen.mjs.map +1 -1
  40. package/dist/cli/wirings/dump-deployment.mjs +141 -0
  41. package/dist/cli/wirings/dump-deployment.mjs.map +1 -0
  42. package/dist/cli/wirings/snapshot.mjs +0 -1
  43. package/dist/cli/wirings/snapshot.mjs.map +1 -1
  44. package/dist/cli/wirings/up.mjs +7 -0
  45. package/dist/cli/wirings/up.mjs.map +1 -1
  46. package/dist/contracts/codegenable.d.mts +22 -25
  47. package/dist/contracts/codegenable.mjs +35 -4
  48. package/dist/contracts/codegenable.mjs.map +1 -1
  49. package/dist/contracts/config-bindings.mjs +43 -18
  50. package/dist/contracts/config-bindings.mjs.map +1 -1
  51. package/dist/index.mjs +1 -1
  52. package/dist/orchestrators/boot.d.mts +0 -10
  53. package/dist/orchestrators/boot.mjs +24 -54
  54. package/dist/orchestrators/boot.mjs.map +1 -1
  55. package/dist/orchestrators/codegen/bindings.mjs.map +1 -1
  56. package/dist/orchestrators/codegen/config-runtime.mjs +137 -112
  57. package/dist/orchestrators/codegen/config-runtime.mjs.map +1 -1
  58. package/dist/orchestrators/codegen/deployment-network-file.mjs +130 -0
  59. package/dist/orchestrators/codegen/deployment-network-file.mjs.map +1 -0
  60. package/dist/orchestrators/codegen/deployment-strict.mjs +77 -0
  61. package/dist/orchestrators/codegen/deployment-strict.mjs.map +1 -0
  62. package/dist/orchestrators/codegen/deployment.d.mts +63 -0
  63. package/dist/orchestrators/codegen/deployment.mjs +153 -0
  64. package/dist/orchestrators/codegen/deployment.mjs.map +1 -0
  65. package/dist/orchestrators/codegen/errors.mjs +7 -9
  66. package/dist/orchestrators/codegen/errors.mjs.map +1 -1
  67. package/dist/orchestrators/codegen/format.mjs +20 -1
  68. package/dist/orchestrators/codegen/format.mjs.map +1 -1
  69. package/dist/orchestrators/codegen/paths.mjs +1 -8
  70. package/dist/orchestrators/codegen/paths.mjs.map +1 -1
  71. package/dist/orchestrators/codegen/service.mjs +369 -141
  72. package/dist/orchestrators/codegen/service.mjs.map +1 -1
  73. package/dist/orchestrators/layers.mjs +0 -1
  74. package/dist/orchestrators/layers.mjs.map +1 -1
  75. package/dist/orchestrators/network-options.d.mts +37 -35
  76. package/dist/orchestrators/network-options.mjs +13 -10
  77. package/dist/orchestrators/network-options.mjs.map +1 -1
  78. package/dist/plugins/account/codegen.d.mts +3 -4
  79. package/dist/plugins/account/codegen.mjs +5 -7
  80. package/dist/plugins/account/codegen.mjs.map +1 -1
  81. package/dist/plugins/account/index.mjs +1 -3
  82. package/dist/plugins/account/index.mjs.map +1 -1
  83. package/dist/plugins/coin/codegen.mjs +5 -5
  84. package/dist/plugins/coin/codegen.mjs.map +1 -1
  85. package/dist/plugins/coin/index.mjs.map +1 -1
  86. package/dist/plugins/dashboard/domain.mjs +18 -1
  87. package/dist/plugins/dashboard/domain.mjs.map +1 -1
  88. package/dist/plugins/dashboard/schema/root.mjs +5 -3
  89. package/dist/plugins/dashboard/schema/root.mjs.map +1 -1
  90. package/dist/plugins/dashboard/schema/types.mjs +15 -2
  91. package/dist/plugins/dashboard/schema/types.mjs.map +1 -1
  92. package/dist/plugins/deepbook/codegen.mjs +4 -4
  93. package/dist/plugins/deepbook/codegen.mjs.map +1 -1
  94. package/dist/plugins/deepbook/index.d.mts +7 -3
  95. package/dist/plugins/deepbook/index.mjs +3 -3
  96. package/dist/plugins/deepbook/index.mjs.map +1 -1
  97. package/dist/plugins/internal/codegen-helpers.mjs +0 -1
  98. package/dist/plugins/internal/codegen-helpers.mjs.map +1 -1
  99. package/dist/plugins/package/codegen.d.mts +1 -12
  100. package/dist/plugins/package/codegen.mjs +43 -25
  101. package/dist/plugins/package/codegen.mjs.map +1 -1
  102. package/dist/plugins/package/dep-resolution.mjs +1 -1
  103. package/dist/plugins/package/dep-resolution.mjs.map +1 -1
  104. package/dist/plugins/package/index.d.mts +14 -11
  105. package/dist/plugins/package/index.mjs +6 -7
  106. package/dist/plugins/package/index.mjs.map +1 -1
  107. package/dist/plugins/seal/codegen.mjs +3 -3
  108. package/dist/plugins/seal/codegen.mjs.map +1 -1
  109. package/dist/plugins/seal/index.mjs.map +1 -1
  110. package/dist/plugins/sui/codegen.mjs +5 -4
  111. package/dist/plugins/sui/codegen.mjs.map +1 -1
  112. package/dist/plugins/sui/index.d.mts +10 -10
  113. package/dist/plugins/sui/index.mjs +1 -2
  114. package/dist/plugins/sui/index.mjs.map +1 -1
  115. package/dist/plugins/sui/move-summary-runner.mjs +29 -2
  116. package/dist/plugins/sui/move-summary-runner.mjs.map +1 -1
  117. package/dist/plugins/wallet/codegen.d.mts +8 -12
  118. package/dist/plugins/wallet/codegen.mjs +22 -15
  119. package/dist/plugins/wallet/codegen.mjs.map +1 -1
  120. package/dist/plugins/wallet/index.d.mts +4 -3
  121. package/dist/plugins/wallet/index.mjs +1 -1
  122. package/dist/plugins/wallet/index.mjs.map +1 -1
  123. package/dist/plugins/wallet/service.d.mts +2 -2
  124. package/dist/plugins/wallet/service.mjs +2 -3
  125. package/dist/plugins/wallet/service.mjs.map +1 -1
  126. package/dist/plugins/walrus/codegen.mjs +4 -4
  127. package/dist/plugins/walrus/codegen.mjs.map +1 -1
  128. package/dist/plugins/walrus/index.mjs +1 -1
  129. package/dist/plugins/walrus/index.mjs.map +1 -1
  130. package/dist/substrate/manifest.d.mts +17 -22
  131. package/dist/substrate/manifest.mjs +4 -6
  132. package/dist/substrate/manifest.mjs.map +1 -1
  133. package/dist/substrate/plugin-ctx.d.mts +6 -6
  134. package/dist/substrate/plugin-ctx.mjs.map +1 -1
  135. package/dist/substrate/runtime/cross-process/liveness.mjs.map +1 -1
  136. package/dist/substrate/runtime/index.mjs +1 -0
  137. package/dist/substrate/runtime/lifecycle/file-watcher.d.mts +1 -0
  138. package/dist/substrate/runtime/lifecycle/file-watcher.mjs +54 -0
  139. package/dist/substrate/runtime/lifecycle/file-watcher.mjs.map +1 -0
  140. package/dist/substrate/runtime/lifecycle/index.mjs +1 -0
  141. package/dist/substrate/runtime/lifecycle/watch-attribution.mjs +76 -6
  142. package/dist/substrate/runtime/lifecycle/watch-attribution.mjs.map +1 -1
  143. package/dist/substrate/runtime/manifest/manifest.mjs.map +1 -1
  144. package/dist/substrate/runtime/paths.mjs +1 -1
  145. package/dist/substrate/runtime/supervisor/start-supervisor.mjs +2 -6
  146. package/dist/substrate/runtime/supervisor/start-supervisor.mjs.map +1 -1
  147. package/dist/surfaces/cli/command-tree.mjs +6 -6
  148. package/dist/surfaces/cli/command-tree.mjs.map +1 -1
  149. package/dist/surfaces/cli/index.mjs +6 -4
  150. package/dist/surfaces/cli/index.mjs.map +1 -1
  151. package/dist/surfaces/tui/plain-renderer.mjs.map +1 -1
  152. package/package.json +1 -1
  153. package/dist/cli/wirings/dump-ids.mjs +0 -86
  154. package/dist/cli/wirings/dump-ids.mjs.map +0 -1
  155. package/dist/orchestrators/codegen/id-config.d.mts +0 -1
  156. package/dist/orchestrators/codegen/id-config.mjs +0 -107
  157. package/dist/orchestrators/codegen/id-config.mjs.map +0 -1
  158. package/dist/orchestrators/codegen/output-location.mjs +0 -32
  159. package/dist/orchestrators/codegen/output-location.mjs.map +0 -1
package/dist/cli/main.mjs CHANGED
@@ -14,10 +14,10 @@ import { nodeConfirmPrompt } from "../surfaces/cli/commands/confirm-node.mjs";
14
14
  import { makeSnapshotReader } from "./snapshot-reader.mjs";
15
15
  import { makeDirectPruneDeps } from "./prune-direct.mjs";
16
16
  import { makeConfigLoader } from "./wirings/config-loader.mjs";
17
+ import { runCodegen } from "./wirings/codegen.mjs";
17
18
  import { runUpLive } from "./wirings/up.mjs";
18
19
  import { runApplyLive } from "./wirings/apply.mjs";
19
- import { runCodegen } from "./wirings/codegen.mjs";
20
- import { runDumpIds } from "./wirings/dump-ids.mjs";
20
+ import { runDumpDeployment } from "./wirings/dump-deployment.mjs";
21
21
  import { runSnapshotCaptureLiveAware, runSnapshotDeleteDirect, runSnapshotRestoreDirect } from "./wirings/snapshot.mjs";
22
22
  import { runWipeDirect, runWipePlanDirect } from "./wirings/wipe.mjs";
23
23
  import { Effect } from "effect";
@@ -135,10 +135,11 @@ const buildDirectDeps = (identity) => {
135
135
  snapshotStalePolicy: flags.snapshotStalePolicy
136
136
  }) },
137
137
  apply: { run: (flags) => runApplyLive(flags.configPath, identity) },
138
- codegen: { run: (flags) => runCodegen(flags.configPath, identity) },
139
- dumpIds: { run: (flags) => runDumpIds(identity, {
138
+ codegen: { run: (flags) => runCodegen(flags.configPath) },
139
+ dumpDeployment: { run: (flags) => runDumpDeployment(identity, {
140
140
  configPath: flags.configPath,
141
141
  out: flags.out,
142
+ network: flags.network,
142
143
  io: flags.io,
143
144
  outputMode: flags.outputMode
144
145
  }) },
@@ -1 +1 @@
1
- {"version":3,"file":"main.mjs","names":["resolvePath"],"sources":["../../src/cli/main.ts"],"sourcesContent":["// devstack CLI bin entry — argv → identity → deps → dispatch → exit.\n//\n// This file is the executable entry the `bin: { devstack: ... }`\n// package.json field points at. Build output: `dist/cli/main.mjs`.\n//\n// Architecture invariant (surfaces/cli/index.ts header):\n// \"`up` must hand its long-running effect to the outer Node runtime\n// directly, not nest a runtime — otherwise SIGINT cannot reach\n// scope finalizers and container teardown leaks.\"\n//\n// Shape:\n// 1. Pre-parse identity inputs from argv + env (see\n// `identityInputsFromArgv`).\n// 2. Resolve identity (app / stack / network / runtime root) via the\n// shared `api/inference-network` helpers.\n// 3. Build the dispatcher's `CliDeps` bundle from each verb's wiring\n// module in `cli/wirings/`.\n// 4. Hand control to the Stricli-backed dispatcher, which routes to\n// the verb-scoped wiring function.\n\nimport { realpathSync } from 'node:fs';\nimport { dirname, join, resolve as resolvePath } from 'node:path';\nimport { fileURLToPath, pathToFileURL } from 'node:url';\n\nimport { Effect } from 'effect';\n\nimport { endpointKey, pluginKey } from '../substrate/brand.ts';\nimport { emptyProjection } from '../substrate/runtime/projection/state-ref.ts';\nimport type { SubscribableState } from '../substrate/projection.ts';\nimport { readStackContext } from '../build-integrations/runtime/read-stack-context.ts';\nimport type { StackContext } from '../build-integrations/runtime/stack-context.ts';\nimport { dispatch, type CliDeps, CliUsageError } from '../surfaces/cli/index.ts';\nimport { emitFailure, nodeProcessIO } from '../surfaces/cli/output.ts';\nimport { ENV_VARS } from '../surfaces/cli/flags.ts';\nimport { defaultProbes } from './doctor-probes.ts';\nimport { nodeConfirmPrompt } from '../surfaces/cli/commands/confirm-node.ts';\nimport type { StatusReader } from '../surfaces/cli/commands/status.ts';\nimport { ExitCode } from '../surfaces/cli/sysexits.ts';\nimport { makeSnapshotReader } from './snapshot-reader.ts';\nimport {\n\tDevstackNetworkParseError,\n\tresolveAppName,\n\tresolveNetworkSync,\n\tresolveStackName,\n\tresolveStateDir,\n} from '../api/inference-network.ts';\nimport { makeDirectPruneDeps } from './prune-direct.ts';\nimport { runUpLive } from './wirings/up.ts';\nimport { runApplyLive } from './wirings/apply.ts';\nimport { runCodegen } from './wirings/codegen.ts';\nimport { runDumpIds } from './wirings/dump-ids.ts';\nimport {\n\trunSnapshotCaptureLiveAware,\n\trunSnapshotDeleteDirect,\n\trunSnapshotRestoreDirect,\n} from './wirings/snapshot.ts';\nimport { runWipeDirect, runWipePlanDirect } from './wirings/wipe.ts';\nimport { makeConfigLoader, resolveConfigPath } from './wirings/config-loader.ts';\nimport type { ResolvedIdentity } from './wirings/identity.ts';\n\n// -----------------------------------------------------------------------------\n// Identity resolution\n// -----------------------------------------------------------------------------\n\n/** Resolve identity from flags + env. App and stack fall through the\n * shared cwd/package metadata resolver before their defaults.\n *\n * State-dir precedence ladder:\n * `--state-dir` flag (`stateDir`) > `config.options.stateDir`\n * (`configStateDir`) > `$DEVSTACK_STATE_DIR` > `<cwd>/.devstack`.\n * The flag maps to `resolveStateDir`'s top `runtimeRoot` rung and the\n * config value to its `stateDir` rung so the flag always wins over a\n * config-declared `defineDevstack({ stateDir })`.\n *\n * Stack-name precedence ladder (the SAME flag-beats-config shape as\n * state-dir, so the two are consistent and reviewable side-by-side):\n * `--stack` flag / `$DEVSTACK_STACK` (`params.stack`)\n * > config's `defineDevstack({ stackName })`\n * > cwd/package inference (default `'main'`).\n * Config-vs-explicit resolution can't happen here (the config isn't\n * loaded yet for the no-config verbs), so we record the explicit value\n * on `ResolvedIdentity.explicitStack` and let the verb wirings apply\n * `config.stackName` ONLY when no explicit stack was given — see\n * `effectiveStackName` in `cli/wirings/identity.ts`. `params.stack` is\n * exactly the explicit `--stack`-or-`$DEVSTACK_STACK` value (the argv\n * pre-parser seeds it from env, then lets the flag overwrite it), and\n * is `undefined` when neither was provided. */\nconst resolveIdentity = (params: {\n\treadonly app: string | undefined;\n\treadonly stack: string | undefined;\n\treadonly network: string | undefined;\n\treadonly stateDir: string | undefined;\n\treadonly configStateDir?: string | undefined;\n\treadonly cwd?: string;\n}): ResolvedIdentity => {\n\tconst cwd = params.cwd ?? process.cwd();\n\tconst app = resolveAppName({\n\t\texplicit: params.app,\n\t\tcwd,\n\t});\n\tconst runtimeRoot = resolveStateDir({\n\t\truntimeRoot: params.stateDir,\n\t\tstateDir: params.configStateDir,\n\t\tenv: process.env.DEVSTACK_STATE_DIR,\n\t\tcwd,\n\t});\n\tconst stacksRoot = resolvePath(runtimeRoot, 'stacks');\n\t// `resolveStackName` here folds explicit (flag/env) > inferred >\n\t// default. Crucially it does NOT see `config.stackName`: a non-empty\n\t// `params.stack` means the operator was explicit, so `stack` already\n\t// equals the explicit value and `config.stackName` must not override\n\t// it downstream. An empty `params.stack` falls through to the\n\t// inferred/default name, which a verb's `config.stackName` may then\n\t// supersede via `effectiveStackName`.\n\tconst explicitStack =\n\t\tparams.stack !== undefined && params.stack.length > 0 ? params.stack : undefined;\n\tconst stack = resolveStackName({\n\t\texplicit: params.stack,\n\t\tcwd,\n\t});\n\t// Centralized explicit > env > default ladder. Throws\n\t// `DevstackNetworkParseError` on a malformed value so the CLI fails\n\t// fast with a structured error instead of a downstream cryptic\n\t// network-probe failure. The identity's network is the canonical name\n\t// (`localnet`, `testnet`, …), independent of how it was spelled.\n\tconst network = resolveNetworkSync({\n\t\texplicit: params.network,\n\t\tenv: process.env.DEVSTACK_NETWORK,\n\t\texplicitSource: '--network',\n\t}).parsed.name;\n\tconst stackRoot = resolvePath(stacksRoot, stack);\n\treturn {\n\t\tapp,\n\t\tstack,\n\t\tnetwork,\n\t\truntimeRoot,\n\t\tstacksRoot,\n\t\tstackRoot,\n\t\trosterFile: resolvePath(stackRoot, 'roster.json'),\n\t\texplicitStack,\n\t};\n};\n\n// -----------------------------------------------------------------------------\n// Verb deps composition\n// -----------------------------------------------------------------------------\n\n/**\n * Degraded offline-status projection — built from the on-disk manifest,\n * NOT from a persisted projection twin (the projection snapshot file no\n * longer exists). When the stack is DOWN, the manifest is the only\n * durable record of the boot; `status` surfaces identity + endpoints\n * from it and leaves the live-only slices (`rows` / `accounts` /\n * `packages` / `errors`) empty. A freshly-booted stack also has empty\n * `rows`, so the status renderer already tolerates this shape.\n *\n * Pure projector — exported for the focused unit test. Starts from\n * `emptyProjection()` (the closed-vocabulary baseline) and fills only\n * the two manifest-backed slices, so no display vocabulary can leak in.\n * The manifest identity tuple is `{ app, stack, network }`, matching\n * the projection's identity shape directly. Endpoints get re-branded\n * (`endpointKey` / `pluginKey`)\n * to the projection's branded shape; `registeredAt` is unknown offline,\n * so it defaults to `0`.\n */\nexport const degradedStatusFromContext = (ctx: StackContext): SubscribableState => ({\n\t...emptyProjection(),\n\tidentity: {\n\t\tapp: ctx.identity.app,\n\t\tstack: ctx.identity.stack,\n\t\tnetwork: ctx.identity.network,\n\t},\n\tendpoints: ctx.endpoints.all().map((endpoint) => ({\n\t\tendpointKey: endpointKey(endpoint.endpointKey),\n\t\tpluginKey: pluginKey(endpoint.pluginKey),\n\t\tname: endpoint.name,\n\t\turl: endpoint.url,\n\t\tdisplayUrl: endpoint.displayUrl,\n\t\twireProtocol: endpoint.wireProtocol,\n\t\tregisteredAt: 0,\n\t})),\n});\n\nconst projectionStatusReader = (identity: ResolvedIdentity): StatusReader => ({\n\treadState: () =>\n\t\tEffect.sync(() => {\n\t\t\t// Offline read: project the on-disk manifest for THIS stack root.\n\t\t\t// A missing / malformed manifest (no `up` yet, or a hand-edit)\n\t\t\t// is a tolerated \"no state present\" — `readStackContext` throws\n\t\t\t// in that case, so swallow the throw and report `null` (the\n\t\t\t// status command's contract for an absent stack).\n\t\t\ttry {\n\t\t\t\tconst ctx = readStackContext({ manifestPath: join(identity.stackRoot, 'manifest.json') });\n\t\t\t\treturn degradedStatusFromContext(ctx);\n\t\t\t} catch {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t}),\n});\n\nconst buildDirectDeps = (identity: ResolvedIdentity): CliDeps => {\n\treturn {\n\t\tup: {\n\t\t\trun: (flags) =>\n\t\t\t\trunUpLive(flags.configPath, identity, {\n\t\t\t\t\trenderer: flags.renderer,\n\t\t\t\t\tstdoutIsTty: Boolean((process.stdout as { isTTY?: boolean }).isTTY),\n\t\t\t\t\tfromSnapshot: flags.fromSnapshot,\n\t\t\t\t\tsnapshotCache: flags.snapshotCache,\n\t\t\t\t\tsnapshotStalePolicy: flags.snapshotStalePolicy,\n\t\t\t\t}),\n\t\t},\n\t\tapply: {\n\t\t\trun: (flags) => runApplyLive(flags.configPath, identity),\n\t\t},\n\t\tcodegen: {\n\t\t\trun: (flags) => runCodegen(flags.configPath, identity),\n\t\t},\n\t\tdumpIds: {\n\t\t\trun: (flags) =>\n\t\t\t\trunDumpIds(identity, {\n\t\t\t\t\tconfigPath: flags.configPath,\n\t\t\t\t\tout: flags.out,\n\t\t\t\t\tio: flags.io,\n\t\t\t\t\toutputMode: flags.outputMode,\n\t\t\t\t}),\n\t\t},\n\t\tstatus: { reader: projectionStatusReader(identity) },\n\t\tsnapshot: {\n\t\t\treader: makeSnapshotReader(identity),\n\t\t\tcapture: (args) => runSnapshotCaptureLiveAware(identity, args),\n\t\t\trestore: (snapshotId) => runSnapshotRestoreDirect(identity, snapshotId),\n\t\t\tdelete: (snapshotId) => runSnapshotDeleteDirect(identity, snapshotId),\n\t\t\tconfirm: nodeConfirmPrompt,\n\t\t},\n\t\tprune: makeDirectPruneDeps({ runtimeRoot: identity.runtimeRoot }),\n\t\tdoctor: {\n\t\t\tprobes: defaultProbes({\n\t\t\t\tstateDir: identity.runtimeRoot,\n\t\t\t\tappRoot: identity.stacksRoot,\n\t\t\t}),\n\t\t},\n\t\tconfig: { loader: makeConfigLoader() },\n\t\twipe: {\n\t\t\twipe: () => runWipeDirect(identity),\n\t\t\tplan: () => runWipePlanDirect(identity),\n\t\t\tconfirm: nodeConfirmPrompt,\n\t\t},\n\t};\n};\n\n// -----------------------------------------------------------------------------\n// Argv pre-parser\n// -----------------------------------------------------------------------------\n\n/** @internal Exported for tests. Resolves identity flag inputs from a\n * `--app <x>` / `--stack <x>` / `--network <x>` / `--state-dir <x>` /\n * `--config <x>` argv, falling back to `DEVSTACK_*` env vars. Throws\n * on a missing or flag-shaped value so a typo doesn't silently demote\n * a downstream flag, and on a duplicate flag (`--app a --app b`) so\n * the pre-parser does not silently last-write-wins a value that\n * Stricli will later reject outright. */\nexport const identityInputsFromArgv = (\n\targv: ReadonlyArray<string>,\n\tenv: Readonly<Record<string, string | undefined>>,\n) => {\n\tlet app = env.DEVSTACK_APP;\n\tlet stack = env.DEVSTACK_STACK;\n\tlet network = env.DEVSTACK_NETWORK;\n\t// `stateDir` intentionally captures the `--state-dir` flag ONLY. The\n\t// full ladder — `--state-dir` flag > `config.options.stateDir`\n\t// (`defineDevstack({ stateDir })`) > `$DEVSTACK_STATE_DIR` >\n\t// `<cwd>/.devstack` — is assembled at the `resolveStateDir(...)`\n\t// call-site in `resolveIdentity`: the flag wins (top `runtimeRoot`\n\t// rung), the best-effort config value sits below it (`stateDir`\n\t// rung), then env, then the cwd default. Keeping the pre-parser a\n\t// thin flag-extractor means config + env precedence live in one place.\n\tlet stateDir: string | undefined;\n\tlet configPath = env.DEVSTACK_CONFIG;\n\t// Tracks which flags have been seen on the argv side so a second\n\t// occurrence trips a usage error before the value silently overwrites\n\t// the first. Env-sourced defaults are NOT counted (they are not user\n\t// argv).\n\tconst seenArgvFlags = new Set<string>();\n\tfor (let i = 0; i < argv.length; i += 1) {\n\t\tconst token = argv[i]!;\n\t\tconst readValue = (name: string): string | undefined => {\n\t\t\tlet value: string | undefined;\n\t\t\t// `--name=value` form: trust the literal between `=` and end.\n\t\t\tif (token.startsWith(`--${name}=`)) {\n\t\t\t\tvalue = token.slice(name.length + 3);\n\t\t\t}\n\t\t\t// `--name value` form: peek the next token. Reject another\n\t\t\t// flag token (`--foo`) as the value — it almost certainly\n\t\t\t// means the user meant `--name <empty>` (typo / forgotten\n\t\t\t// argument) and quietly absorbing `--foo` as the value\n\t\t\t// silently demotes a downstream flag.\n\t\t\telse if (token === `--${name}`) {\n\t\t\t\tconst next = argv[i + 1];\n\t\t\t\tif (next === undefined) {\n\t\t\t\t\tthrow new CliUsageError({ message: `flag --${name} requires a value` });\n\t\t\t\t}\n\t\t\t\tif (next.startsWith('--')) {\n\t\t\t\t\tthrow new CliUsageError({\n\t\t\t\t\t\tmessage: `flag --${name} requires a value; got \"${next}\" which looks like a flag`,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tvalue = next;\n\t\t\t}\n\t\t\tif (value === undefined) return undefined;\n\t\t\tif (seenArgvFlags.has(name)) {\n\t\t\t\tthrow new CliUsageError({ message: `flag --${name} given more than once` });\n\t\t\t}\n\t\t\tseenArgvFlags.add(name);\n\t\t\treturn value;\n\t\t};\n\t\tapp = readValue('app') ?? app;\n\t\tstack = readValue('stack') ?? stack;\n\t\tnetwork = readValue('network') ?? network;\n\t\tstateDir = readValue('state-dir') ?? stateDir;\n\t\tconfigPath = readValue('config') ?? configPath;\n\t}\n\treturn { app, stack, network, stateDir, configPath };\n};\n\nconst identityCwdFromConfig = (configPath: string | undefined): string => {\n\tconst resolved = resolveConfigPath(configPath);\n\treturn resolved === null ? process.cwd() : dirname(resolved);\n};\n\n/** Whether the resolved argv is a purely-informational invocation that\n * never consumes `identity.runtimeRoot` (the only value the config\n * pre-load feeds). For these we SKIP `configStateDirBestEffort` so we\n * don't dynamic-import (and run the top-level side effects of) the\n * user's `devstack.config.ts` — plus walk parent dirs — for commands\n * the file header itself says must stay cheap:\n *\n * - empty argv → Stricli prints root help\n * - `--help`/`-h` or `--version`/`-v` anywhere → Stricli short-circuits\n * - first token `schema` → emits the static CLI schema (no state dir)\n *\n * An unknown verb also never reaches a state-dir consumer (Stricli\n * fails the parse), but we keep pre-loading on the general path: the\n * pre-parser already accepted the identity flags, and gating on a\n * closed verb set here would duplicate the dispatcher's route table.\n * The two flagged-as-wasteful informational paths (help/version/schema)\n * are the ones with a registered short-circuit, so skipping exactly\n * those removes the eager-import surprise without re-deriving routes. */\nconst argvSkipsConfigPreload = (argv: ReadonlyArray<string>): boolean => {\n\tif (argv.length === 0) return true;\n\tfor (const token of argv) {\n\t\tif (token === '--help' || token === '-h' || token === '--version' || token === '-v') {\n\t\t\treturn true;\n\t\t}\n\t}\n\tconst firstVerb = argv.find((token) => !token.startsWith('-'));\n\treturn firstVerb === 'schema';\n};\n\n/** Best-effort read of `config.options.stateDir` (the value a program\n * sets via `defineDevstack({ stateDir })`) for the state-dir precedence\n * ladder in `resolveIdentity`. Swallows EVERY config-loader failure\n * (not-found AND evaluation errors) and returns `undefined`:\n *\n * - No-config verbs (`prune`, `wipe`) MUST keep resolving identity\n * without a config, so a missing config silently falls through to\n * the flag > env > cwd ladder.\n * - A genuinely malformed config is NOT surfaced here — the verbs that\n * actually consume config (`up` / `apply` via `makeConfigLoader`)\n * re-load it and surface the typed `CliConfig*` error through the\n * normal envelope path, so behavior for those verbs is unchanged.\n *\n * This is one redundant config evaluation for `up` / `apply` (the verb\n * wiring re-loads via its own loader); threading the loaded value into\n * the verb dispatch would require touching the off-limits `wirings/up.ts`\n * signature, so the duplicate import is accepted deliberately. */\nconst configStateDirBestEffort = async (\n\tconfigPath: string | undefined,\n): Promise<string | undefined> => {\n\tconst loaded = await Effect.runPromise(makeConfigLoader().load(configPath).pipe(Effect.option));\n\tif (loaded._tag === 'None') return undefined;\n\tconst options = (loaded.value.stack as { readonly options?: { readonly stateDir?: string } })\n\t\t.options;\n\tconst stateDir = options?.stateDir;\n\treturn stateDir !== undefined && stateDir.length > 0 ? stateDir : undefined;\n};\n\n// -----------------------------------------------------------------------------\n// Bin entry\n// -----------------------------------------------------------------------------\n\nexport const runCli = async (\n\targv: ReadonlyArray<string> = process.argv.slice(2),\n): Promise<void> => {\n\tconst stdinIsTty = Boolean((process.stdin as { isTTY?: boolean }).isTTY);\n\tconst env: Record<string, string | undefined> = { ...process.env };\n\tlet identityInputs: ReturnType<typeof identityInputsFromArgv>;\n\ttry {\n\t\tidentityInputs = identityInputsFromArgv(argv, env);\n\t} catch (cause) {\n\t\tconst error =\n\t\t\tcause instanceof CliUsageError\n\t\t\t\t? cause\n\t\t\t\t: new CliUsageError({\n\t\t\t\t\t\tmessage: cause instanceof Error ? cause.message : String(cause),\n\t\t\t\t\t});\n\t\tconst jsonMode = env[ENV_VARS.JSON] === '1' || argv.includes('--json');\n\t\tawait Effect.runPromise(\n\t\t\temitFailure(nodeProcessIO, jsonMode ? 'json' : 'human', {\n\t\t\t\tcommand: '(parse-argv)',\n\t\t\t\telapsedMs: 0,\n\t\t\t\terror,\n\t\t\t}),\n\t\t);\n\t\treturn;\n\t}\n\t// Best-effort config pre-load for the state-dir ladder. Swallows\n\t// not-found / malformed configs so no-config verbs (prune/wipe) keep\n\t// working; the `--state-dir` flag still wins over the config value.\n\t// Skipped for informational invocations (help/version/schema/empty)\n\t// that never read `identity.runtimeRoot`, so those commands don't\n\t// dynamic-import the user's config (and run its top-level side\n\t// effects) just to print help.\n\tconst configStateDir = argvSkipsConfigPreload(argv)\n\t\t? undefined\n\t\t: await configStateDirBestEffort(identityInputs.configPath);\n\tlet identity: ResolvedIdentity;\n\ttry {\n\t\tidentity = resolveIdentity({\n\t\t\tapp: identityInputs.app,\n\t\t\tstack: identityInputs.stack,\n\t\t\tnetwork: identityInputs.network,\n\t\t\tstateDir: identityInputs.stateDir,\n\t\t\tconfigStateDir,\n\t\t\tcwd: identityCwdFromConfig(identityInputs.configPath),\n\t\t});\n\t} catch (cause) {\n\t\t// `resolveIdentity` -> `resolveNetworkSync` throws\n\t\t// `DevstackNetworkParseError` (a plain Error, NOT a CliError) on a\n\t\t// malformed `--network`/`$DEVSTACK_NETWORK` value. This runs OUTSIDE\n\t\t// the argv pre-parse try/catch above and BEFORE dispatch, so without\n\t\t// this guard the throw escapes to the bin entry's generic `.catch`\n\t\t// (exit 1, no envelope). Convert to `CliUsageError` and route through\n\t\t// the same envelope path the parse-argv block uses so a bad value\n\t\t// exits USAGE (64) with a JSON envelope in `--json` mode — never the\n\t\t// disallowed generic exit 1.\n\t\tconst error =\n\t\t\tcause instanceof DevstackNetworkParseError\n\t\t\t\t? new CliUsageError({ message: cause.message })\n\t\t\t\t: cause instanceof CliUsageError\n\t\t\t\t\t? cause\n\t\t\t\t\t: new CliUsageError({\n\t\t\t\t\t\t\tmessage: cause instanceof Error ? cause.message : String(cause),\n\t\t\t\t\t\t});\n\t\tconst jsonMode = env[ENV_VARS.JSON] === '1' || argv.includes('--json');\n\t\tawait Effect.runPromise(\n\t\t\temitFailure(nodeProcessIO, jsonMode ? 'json' : 'human', {\n\t\t\t\tcommand: '(resolve-identity)',\n\t\t\t\telapsedMs: 0,\n\t\t\t\terror,\n\t\t\t}),\n\t\t);\n\t\treturn;\n\t}\n\tconst deps = buildDirectDeps(identity);\n\tawait Effect.runPromise(\n\t\tdispatch(deps, {\n\t\t\targv,\n\t\t\tenv: {\n\t\t\t\t...env,\n\t\t\t\tDEVSTACK_APP: identity.app,\n\t\t\t\tDEVSTACK_STACK: identity.stack,\n\t\t\t\tDEVSTACK_STATE_DIR: identity.runtimeRoot,\n\t\t\t},\n\t\t\tstdinIsTty,\n\t\t}),\n\t);\n};\n\nconst isMainEntrypoint = (): boolean => {\n\tconst argvPath = process.argv[1];\n\tif (argvPath === undefined) return false;\n\ttry {\n\t\treturn realpathSync(argvPath) === realpathSync(fileURLToPath(import.meta.url));\n\t} catch {\n\t\treturn import.meta.url === pathToFileURL(argvPath).href;\n\t}\n};\n\nif (isMainEntrypoint()) {\n\t// Intentionally do NOT call `process.exit(...)` after `runCli`:\n\t// `process.exit` synchronously terminates the event loop before any\n\t// pending `setImmediate` work flushes. The `up` lifecycle's hard-kill\n\t// path schedules its escalation via `setImmediate(process.exit)` in\n\t// `up-lifecycle.ts:scheduleProcessExit` (the file-header invariant for\n\t// SIGINT → finalizers requires the outer Node fiber to drain\n\t// naturally). Letting Node's natural exit handle the shutdown\n\t// preserves that invariant. We only set `process.exitCode` so the\n\t// final OS exit code reflects the verb's outcome.\n\trunCli().catch((err) => {\n\t\tprocess.stderr.write(`error: ${err instanceof Error ? err.message : String(err)}\\n`);\n\t\tprocess.exitCode = ExitCode.GENERIC;\n\t});\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"main.mjs","names":["resolvePath"],"sources":["../../src/cli/main.ts"],"sourcesContent":["// devstack CLI bin entry — argv → identity → deps → dispatch → exit.\n//\n// This file is the executable entry the `bin: { devstack: ... }`\n// package.json field points at. Build output: `dist/cli/main.mjs`.\n//\n// Architecture invariant (surfaces/cli/index.ts header):\n// \"`up` must hand its long-running effect to the outer Node runtime\n// directly, not nest a runtime — otherwise SIGINT cannot reach\n// scope finalizers and container teardown leaks.\"\n//\n// Shape:\n// 1. Pre-parse identity inputs from argv + env (see\n// `identityInputsFromArgv`).\n// 2. Resolve identity (app / stack / network / runtime root) via the\n// shared `api/inference-network` helpers.\n// 3. Build the dispatcher's `CliDeps` bundle from each verb's wiring\n// module in `cli/wirings/`.\n// 4. Hand control to the Stricli-backed dispatcher, which routes to\n// the verb-scoped wiring function.\n\nimport { realpathSync } from 'node:fs';\nimport { dirname, join, resolve as resolvePath } from 'node:path';\nimport { fileURLToPath, pathToFileURL } from 'node:url';\n\nimport { Effect } from 'effect';\n\nimport { endpointKey, pluginKey } from '../substrate/brand.ts';\nimport { emptyProjection } from '../substrate/runtime/projection/state-ref.ts';\nimport type { SubscribableState } from '../substrate/projection.ts';\nimport { readStackContext } from '../build-integrations/runtime/read-stack-context.ts';\nimport type { StackContext } from '../build-integrations/runtime/stack-context.ts';\nimport { dispatch, type CliDeps, CliUsageError } from '../surfaces/cli/index.ts';\nimport { emitFailure, nodeProcessIO } from '../surfaces/cli/output.ts';\nimport { ENV_VARS } from '../surfaces/cli/flags.ts';\nimport { defaultProbes } from './doctor-probes.ts';\nimport { nodeConfirmPrompt } from '../surfaces/cli/commands/confirm-node.ts';\nimport type { StatusReader } from '../surfaces/cli/commands/status.ts';\nimport { ExitCode } from '../surfaces/cli/sysexits.ts';\nimport { makeSnapshotReader } from './snapshot-reader.ts';\nimport {\n\tDevstackNetworkParseError,\n\tresolveAppName,\n\tresolveNetworkSync,\n\tresolveStackName,\n\tresolveStateDir,\n} from '../api/inference-network.ts';\nimport { makeDirectPruneDeps } from './prune-direct.ts';\nimport { runUpLive } from './wirings/up.ts';\nimport { runApplyLive } from './wirings/apply.ts';\nimport { runCodegen } from './wirings/codegen.ts';\nimport { runDumpDeployment } from './wirings/dump-deployment.ts';\nimport {\n\trunSnapshotCaptureLiveAware,\n\trunSnapshotDeleteDirect,\n\trunSnapshotRestoreDirect,\n} from './wirings/snapshot.ts';\nimport { runWipeDirect, runWipePlanDirect } from './wirings/wipe.ts';\nimport { makeConfigLoader, resolveConfigPath } from './wirings/config-loader.ts';\nimport type { ResolvedIdentity } from './wirings/identity.ts';\n\n// -----------------------------------------------------------------------------\n// Identity resolution\n// -----------------------------------------------------------------------------\n\n/** Resolve identity from flags + env. App and stack fall through the\n * shared cwd/package metadata resolver before their defaults.\n *\n * State-dir precedence ladder:\n * `--state-dir` flag (`stateDir`) > `config.options.stateDir`\n * (`configStateDir`) > `$DEVSTACK_STATE_DIR` > `<cwd>/.devstack`.\n * The flag maps to `resolveStateDir`'s top `runtimeRoot` rung and the\n * config value to its `stateDir` rung so the flag always wins over a\n * config-declared `defineDevstack({ stateDir })`.\n *\n * Stack-name precedence ladder (the SAME flag-beats-config shape as\n * state-dir, so the two are consistent and reviewable side-by-side):\n * `--stack` flag / `$DEVSTACK_STACK` (`params.stack`)\n * > config's `defineDevstack({ stackName })`\n * > cwd/package inference (default `'main'`).\n * Config-vs-explicit resolution can't happen here (the config isn't\n * loaded yet for the no-config verbs), so we record the explicit value\n * on `ResolvedIdentity.explicitStack` and let the verb wirings apply\n * `config.stackName` ONLY when no explicit stack was given — see\n * `effectiveStackName` in `cli/wirings/identity.ts`. `params.stack` is\n * exactly the explicit `--stack`-or-`$DEVSTACK_STACK` value (the argv\n * pre-parser seeds it from env, then lets the flag overwrite it), and\n * is `undefined` when neither was provided. */\nconst resolveIdentity = (params: {\n\treadonly app: string | undefined;\n\treadonly stack: string | undefined;\n\treadonly network: string | undefined;\n\treadonly stateDir: string | undefined;\n\treadonly configStateDir?: string | undefined;\n\treadonly cwd?: string;\n}): ResolvedIdentity => {\n\tconst cwd = params.cwd ?? process.cwd();\n\tconst app = resolveAppName({\n\t\texplicit: params.app,\n\t\tcwd,\n\t});\n\tconst runtimeRoot = resolveStateDir({\n\t\truntimeRoot: params.stateDir,\n\t\tstateDir: params.configStateDir,\n\t\tenv: process.env.DEVSTACK_STATE_DIR,\n\t\tcwd,\n\t});\n\tconst stacksRoot = resolvePath(runtimeRoot, 'stacks');\n\t// `resolveStackName` here folds explicit (flag/env) > inferred >\n\t// default. Crucially it does NOT see `config.stackName`: a non-empty\n\t// `params.stack` means the operator was explicit, so `stack` already\n\t// equals the explicit value and `config.stackName` must not override\n\t// it downstream. An empty `params.stack` falls through to the\n\t// inferred/default name, which a verb's `config.stackName` may then\n\t// supersede via `effectiveStackName`.\n\tconst explicitStack =\n\t\tparams.stack !== undefined && params.stack.length > 0 ? params.stack : undefined;\n\tconst stack = resolveStackName({\n\t\texplicit: params.stack,\n\t\tcwd,\n\t});\n\t// Centralized explicit > env > default ladder. Throws\n\t// `DevstackNetworkParseError` on a malformed value so the CLI fails\n\t// fast with a structured error instead of a downstream cryptic\n\t// network-probe failure. The identity's network is the canonical name\n\t// (`localnet`, `testnet`, …), independent of how it was spelled.\n\tconst network = resolveNetworkSync({\n\t\texplicit: params.network,\n\t\tenv: process.env.DEVSTACK_NETWORK,\n\t\texplicitSource: '--network',\n\t}).parsed.name;\n\tconst stackRoot = resolvePath(stacksRoot, stack);\n\treturn {\n\t\tapp,\n\t\tstack,\n\t\tnetwork,\n\t\truntimeRoot,\n\t\tstacksRoot,\n\t\tstackRoot,\n\t\trosterFile: resolvePath(stackRoot, 'roster.json'),\n\t\texplicitStack,\n\t};\n};\n\n// -----------------------------------------------------------------------------\n// Verb deps composition\n// -----------------------------------------------------------------------------\n\n/**\n * Degraded offline-status projection — built from the on-disk manifest,\n * NOT from a persisted projection twin (the projection snapshot file no\n * longer exists). When the stack is DOWN, the manifest is the only\n * durable record of the boot; `status` surfaces identity + endpoints\n * from it and leaves the live-only slices (`rows` / `accounts` /\n * `packages` / `errors`) empty. A freshly-booted stack also has empty\n * `rows`, so the status renderer already tolerates this shape.\n *\n * Pure projector — exported for the focused unit test. Starts from\n * `emptyProjection()` (the closed-vocabulary baseline) and fills only\n * the two manifest-backed slices, so no display vocabulary can leak in.\n * The manifest identity tuple is `{ app, stack, network }`, matching\n * the projection's identity shape directly. Endpoints get re-branded\n * (`endpointKey` / `pluginKey`)\n * to the projection's branded shape; `registeredAt` is unknown offline,\n * so it defaults to `0`.\n */\nexport const degradedStatusFromContext = (ctx: StackContext): SubscribableState => ({\n\t...emptyProjection(),\n\tidentity: {\n\t\tapp: ctx.identity.app,\n\t\tstack: ctx.identity.stack,\n\t\tnetwork: ctx.identity.network,\n\t},\n\tendpoints: ctx.endpoints.all().map((endpoint) => ({\n\t\tendpointKey: endpointKey(endpoint.endpointKey),\n\t\tpluginKey: pluginKey(endpoint.pluginKey),\n\t\tname: endpoint.name,\n\t\turl: endpoint.url,\n\t\tdisplayUrl: endpoint.displayUrl,\n\t\twireProtocol: endpoint.wireProtocol,\n\t\tregisteredAt: 0,\n\t})),\n});\n\nconst projectionStatusReader = (identity: ResolvedIdentity): StatusReader => ({\n\treadState: () =>\n\t\tEffect.sync(() => {\n\t\t\t// Offline read: project the on-disk manifest for THIS stack root.\n\t\t\t// A missing / malformed manifest (no `up` yet, or a hand-edit)\n\t\t\t// is a tolerated \"no state present\" — `readStackContext` throws\n\t\t\t// in that case, so swallow the throw and report `null` (the\n\t\t\t// status command's contract for an absent stack).\n\t\t\ttry {\n\t\t\t\tconst ctx = readStackContext({ manifestPath: join(identity.stackRoot, 'manifest.json') });\n\t\t\t\treturn degradedStatusFromContext(ctx);\n\t\t\t} catch {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t}),\n});\n\nconst buildDirectDeps = (identity: ResolvedIdentity): CliDeps => {\n\treturn {\n\t\tup: {\n\t\t\trun: (flags) =>\n\t\t\t\trunUpLive(flags.configPath, identity, {\n\t\t\t\t\trenderer: flags.renderer,\n\t\t\t\t\tstdoutIsTty: Boolean((process.stdout as { isTTY?: boolean }).isTTY),\n\t\t\t\t\tfromSnapshot: flags.fromSnapshot,\n\t\t\t\t\tsnapshotCache: flags.snapshotCache,\n\t\t\t\t\tsnapshotStalePolicy: flags.snapshotStalePolicy,\n\t\t\t\t}),\n\t\t},\n\t\tapply: {\n\t\t\trun: (flags) => runApplyLive(flags.configPath, identity),\n\t\t},\n\t\tcodegen: {\n\t\t\trun: (flags) => runCodegen(flags.configPath),\n\t\t},\n\t\tdumpDeployment: {\n\t\t\trun: (flags) =>\n\t\t\t\trunDumpDeployment(identity, {\n\t\t\t\t\tconfigPath: flags.configPath,\n\t\t\t\t\tout: flags.out,\n\t\t\t\t\tnetwork: flags.network,\n\t\t\t\t\tio: flags.io,\n\t\t\t\t\toutputMode: flags.outputMode,\n\t\t\t\t}),\n\t\t},\n\t\tstatus: { reader: projectionStatusReader(identity) },\n\t\tsnapshot: {\n\t\t\treader: makeSnapshotReader(identity),\n\t\t\tcapture: (args) => runSnapshotCaptureLiveAware(identity, args),\n\t\t\trestore: (snapshotId) => runSnapshotRestoreDirect(identity, snapshotId),\n\t\t\tdelete: (snapshotId) => runSnapshotDeleteDirect(identity, snapshotId),\n\t\t\tconfirm: nodeConfirmPrompt,\n\t\t},\n\t\tprune: makeDirectPruneDeps({ runtimeRoot: identity.runtimeRoot }),\n\t\tdoctor: {\n\t\t\tprobes: defaultProbes({\n\t\t\t\tstateDir: identity.runtimeRoot,\n\t\t\t\tappRoot: identity.stacksRoot,\n\t\t\t}),\n\t\t},\n\t\tconfig: { loader: makeConfigLoader() },\n\t\twipe: {\n\t\t\twipe: () => runWipeDirect(identity),\n\t\t\tplan: () => runWipePlanDirect(identity),\n\t\t\tconfirm: nodeConfirmPrompt,\n\t\t},\n\t};\n};\n\n// -----------------------------------------------------------------------------\n// Argv pre-parser\n// -----------------------------------------------------------------------------\n\n/** @internal Exported for tests. Resolves identity flag inputs from a\n * `--app <x>` / `--stack <x>` / `--network <x>` / `--state-dir <x>` /\n * `--config <x>` argv, falling back to `DEVSTACK_*` env vars. Throws\n * on a missing or flag-shaped value so a typo doesn't silently demote\n * a downstream flag, and on a duplicate flag (`--app a --app b`) so\n * the pre-parser does not silently last-write-wins a value that\n * Stricli will later reject outright. */\nexport const identityInputsFromArgv = (\n\targv: ReadonlyArray<string>,\n\tenv: Readonly<Record<string, string | undefined>>,\n) => {\n\tlet app = env.DEVSTACK_APP;\n\tlet stack = env.DEVSTACK_STACK;\n\tlet network = env.DEVSTACK_NETWORK;\n\t// `stateDir` intentionally captures the `--state-dir` flag ONLY. The\n\t// full ladder — `--state-dir` flag > `config.options.stateDir`\n\t// (`defineDevstack({ stateDir })`) > `$DEVSTACK_STATE_DIR` >\n\t// `<cwd>/.devstack` — is assembled at the `resolveStateDir(...)`\n\t// call-site in `resolveIdentity`: the flag wins (top `runtimeRoot`\n\t// rung), the best-effort config value sits below it (`stateDir`\n\t// rung), then env, then the cwd default. Keeping the pre-parser a\n\t// thin flag-extractor means config + env precedence live in one place.\n\tlet stateDir: string | undefined;\n\tlet configPath = env.DEVSTACK_CONFIG;\n\t// Tracks which flags have been seen on the argv side so a second\n\t// occurrence trips a usage error before the value silently overwrites\n\t// the first. Env-sourced defaults are NOT counted (they are not user\n\t// argv).\n\tconst seenArgvFlags = new Set<string>();\n\tfor (let i = 0; i < argv.length; i += 1) {\n\t\tconst token = argv[i]!;\n\t\tconst readValue = (name: string): string | undefined => {\n\t\t\tlet value: string | undefined;\n\t\t\t// `--name=value` form: trust the literal between `=` and end.\n\t\t\tif (token.startsWith(`--${name}=`)) {\n\t\t\t\tvalue = token.slice(name.length + 3);\n\t\t\t}\n\t\t\t// `--name value` form: peek the next token. Reject another\n\t\t\t// flag token (`--foo`) as the value — it almost certainly\n\t\t\t// means the user meant `--name <empty>` (typo / forgotten\n\t\t\t// argument) and quietly absorbing `--foo` as the value\n\t\t\t// silently demotes a downstream flag.\n\t\t\telse if (token === `--${name}`) {\n\t\t\t\tconst next = argv[i + 1];\n\t\t\t\tif (next === undefined) {\n\t\t\t\t\tthrow new CliUsageError({ message: `flag --${name} requires a value` });\n\t\t\t\t}\n\t\t\t\tif (next.startsWith('--')) {\n\t\t\t\t\tthrow new CliUsageError({\n\t\t\t\t\t\tmessage: `flag --${name} requires a value; got \"${next}\" which looks like a flag`,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tvalue = next;\n\t\t\t}\n\t\t\tif (value === undefined) return undefined;\n\t\t\tif (seenArgvFlags.has(name)) {\n\t\t\t\tthrow new CliUsageError({ message: `flag --${name} given more than once` });\n\t\t\t}\n\t\t\tseenArgvFlags.add(name);\n\t\t\treturn value;\n\t\t};\n\t\tapp = readValue('app') ?? app;\n\t\tstack = readValue('stack') ?? stack;\n\t\tnetwork = readValue('network') ?? network;\n\t\tstateDir = readValue('state-dir') ?? stateDir;\n\t\tconfigPath = readValue('config') ?? configPath;\n\t}\n\treturn { app, stack, network, stateDir, configPath };\n};\n\nconst identityCwdFromConfig = (configPath: string | undefined): string => {\n\tconst resolved = resolveConfigPath(configPath);\n\treturn resolved === null ? process.cwd() : dirname(resolved);\n};\n\n/** Whether the resolved argv is a purely-informational invocation that\n * never consumes `identity.runtimeRoot` (the only value the config\n * pre-load feeds). For these we SKIP `configStateDirBestEffort` so we\n * don't dynamic-import (and run the top-level side effects of) the\n * user's `devstack.config.ts` — plus walk parent dirs — for commands\n * the file header itself says must stay cheap:\n *\n * - empty argv → Stricli prints root help\n * - `--help`/`-h` or `--version`/`-v` anywhere → Stricli short-circuits\n * - first token `schema` → emits the static CLI schema (no state dir)\n *\n * An unknown verb also never reaches a state-dir consumer (Stricli\n * fails the parse), but we keep pre-loading on the general path: the\n * pre-parser already accepted the identity flags, and gating on a\n * closed verb set here would duplicate the dispatcher's route table.\n * The two flagged-as-wasteful informational paths (help/version/schema)\n * are the ones with a registered short-circuit, so skipping exactly\n * those removes the eager-import surprise without re-deriving routes. */\nconst argvSkipsConfigPreload = (argv: ReadonlyArray<string>): boolean => {\n\tif (argv.length === 0) return true;\n\tfor (const token of argv) {\n\t\tif (token === '--help' || token === '-h' || token === '--version' || token === '-v') {\n\t\t\treturn true;\n\t\t}\n\t}\n\tconst firstVerb = argv.find((token) => !token.startsWith('-'));\n\treturn firstVerb === 'schema';\n};\n\n/** Best-effort read of `config.options.stateDir` (the value a program\n * sets via `defineDevstack({ stateDir })`) for the state-dir precedence\n * ladder in `resolveIdentity`. Swallows EVERY config-loader failure\n * (not-found AND evaluation errors) and returns `undefined`:\n *\n * - No-config verbs (`prune`, `wipe`) MUST keep resolving identity\n * without a config, so a missing config silently falls through to\n * the flag > env > cwd ladder.\n * - A genuinely malformed config is NOT surfaced here — the verbs that\n * actually consume config (`up` / `apply` via `makeConfigLoader`)\n * re-load it and surface the typed `CliConfig*` error through the\n * normal envelope path, so behavior for those verbs is unchanged.\n *\n * This is one redundant config evaluation for `up` / `apply` (the verb\n * wiring re-loads via its own loader); threading the loaded value into\n * the verb dispatch would require touching the off-limits `wirings/up.ts`\n * signature, so the duplicate import is accepted deliberately. */\nconst configStateDirBestEffort = async (\n\tconfigPath: string | undefined,\n): Promise<string | undefined> => {\n\tconst loaded = await Effect.runPromise(makeConfigLoader().load(configPath).pipe(Effect.option));\n\tif (loaded._tag === 'None') return undefined;\n\tconst options = (loaded.value.stack as { readonly options?: { readonly stateDir?: string } })\n\t\t.options;\n\tconst stateDir = options?.stateDir;\n\treturn stateDir !== undefined && stateDir.length > 0 ? stateDir : undefined;\n};\n\n// -----------------------------------------------------------------------------\n// Bin entry\n// -----------------------------------------------------------------------------\n\nexport const runCli = async (\n\targv: ReadonlyArray<string> = process.argv.slice(2),\n): Promise<void> => {\n\tconst stdinIsTty = Boolean((process.stdin as { isTTY?: boolean }).isTTY);\n\tconst env: Record<string, string | undefined> = { ...process.env };\n\tlet identityInputs: ReturnType<typeof identityInputsFromArgv>;\n\ttry {\n\t\tidentityInputs = identityInputsFromArgv(argv, env);\n\t} catch (cause) {\n\t\tconst error =\n\t\t\tcause instanceof CliUsageError\n\t\t\t\t? cause\n\t\t\t\t: new CliUsageError({\n\t\t\t\t\t\tmessage: cause instanceof Error ? cause.message : String(cause),\n\t\t\t\t\t});\n\t\tconst jsonMode = env[ENV_VARS.JSON] === '1' || argv.includes('--json');\n\t\tawait Effect.runPromise(\n\t\t\temitFailure(nodeProcessIO, jsonMode ? 'json' : 'human', {\n\t\t\t\tcommand: '(parse-argv)',\n\t\t\t\telapsedMs: 0,\n\t\t\t\terror,\n\t\t\t}),\n\t\t);\n\t\treturn;\n\t}\n\t// Best-effort config pre-load for the state-dir ladder. Swallows\n\t// not-found / malformed configs so no-config verbs (prune/wipe) keep\n\t// working; the `--state-dir` flag still wins over the config value.\n\t// Skipped for informational invocations (help/version/schema/empty)\n\t// that never read `identity.runtimeRoot`, so those commands don't\n\t// dynamic-import the user's config (and run its top-level side\n\t// effects) just to print help.\n\tconst configStateDir = argvSkipsConfigPreload(argv)\n\t\t? undefined\n\t\t: await configStateDirBestEffort(identityInputs.configPath);\n\tlet identity: ResolvedIdentity;\n\ttry {\n\t\tidentity = resolveIdentity({\n\t\t\tapp: identityInputs.app,\n\t\t\tstack: identityInputs.stack,\n\t\t\tnetwork: identityInputs.network,\n\t\t\tstateDir: identityInputs.stateDir,\n\t\t\tconfigStateDir,\n\t\t\tcwd: identityCwdFromConfig(identityInputs.configPath),\n\t\t});\n\t} catch (cause) {\n\t\t// `resolveIdentity` -> `resolveNetworkSync` throws\n\t\t// `DevstackNetworkParseError` (a plain Error, NOT a CliError) on a\n\t\t// malformed `--network`/`$DEVSTACK_NETWORK` value. This runs OUTSIDE\n\t\t// the argv pre-parse try/catch above and BEFORE dispatch, so without\n\t\t// this guard the throw escapes to the bin entry's generic `.catch`\n\t\t// (exit 1, no envelope). Convert to `CliUsageError` and route through\n\t\t// the same envelope path the parse-argv block uses so a bad value\n\t\t// exits USAGE (64) with a JSON envelope in `--json` mode — never the\n\t\t// disallowed generic exit 1.\n\t\tconst error =\n\t\t\tcause instanceof DevstackNetworkParseError\n\t\t\t\t? new CliUsageError({ message: cause.message })\n\t\t\t\t: cause instanceof CliUsageError\n\t\t\t\t\t? cause\n\t\t\t\t\t: new CliUsageError({\n\t\t\t\t\t\t\tmessage: cause instanceof Error ? cause.message : String(cause),\n\t\t\t\t\t\t});\n\t\tconst jsonMode = env[ENV_VARS.JSON] === '1' || argv.includes('--json');\n\t\tawait Effect.runPromise(\n\t\t\temitFailure(nodeProcessIO, jsonMode ? 'json' : 'human', {\n\t\t\t\tcommand: '(resolve-identity)',\n\t\t\t\telapsedMs: 0,\n\t\t\t\terror,\n\t\t\t}),\n\t\t);\n\t\treturn;\n\t}\n\tconst deps = buildDirectDeps(identity);\n\tawait Effect.runPromise(\n\t\tdispatch(deps, {\n\t\t\targv,\n\t\t\tenv: {\n\t\t\t\t...env,\n\t\t\t\tDEVSTACK_APP: identity.app,\n\t\t\t\tDEVSTACK_STACK: identity.stack,\n\t\t\t\tDEVSTACK_STATE_DIR: identity.runtimeRoot,\n\t\t\t},\n\t\t\tstdinIsTty,\n\t\t}),\n\t);\n};\n\nconst isMainEntrypoint = (): boolean => {\n\tconst argvPath = process.argv[1];\n\tif (argvPath === undefined) return false;\n\ttry {\n\t\treturn realpathSync(argvPath) === realpathSync(fileURLToPath(import.meta.url));\n\t} catch {\n\t\treturn import.meta.url === pathToFileURL(argvPath).href;\n\t}\n};\n\nif (isMainEntrypoint()) {\n\t// Intentionally do NOT call `process.exit(...)` after `runCli`:\n\t// `process.exit` synchronously terminates the event loop before any\n\t// pending `setImmediate` work flushes. The `up` lifecycle's hard-kill\n\t// path schedules its escalation via `setImmediate(process.exit)` in\n\t// `up-lifecycle.ts:scheduleProcessExit` (the file-header invariant for\n\t// SIGINT → finalizers requires the outer Node fiber to drain\n\t// naturally). Letting Node's natural exit handle the shutdown\n\t// preserves that invariant. We only set `process.exitCode` so the\n\t// final OS exit code reflects the verb's outcome.\n\trunCli().catch((err) => {\n\t\tprocess.stderr.write(`error: ${err instanceof Error ? err.message : String(err)}\\n`);\n\t\tprocess.exitCode = ExitCode.GENERIC;\n\t});\n}\n"],"mappings":"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"}
@@ -84,7 +84,6 @@ const runApplyLive = (configPath, identity) => {
84
84
  options: stack.options
85
85
  }, identityValue, state, {
86
86
  extras: stack.options.extras,
87
- networkOptions: stack.options.networkOptions,
88
87
  lifetime: "one-shot"
89
88
  });
90
89
  }).pipe(Effect.provide(substrateLayers), Effect.provide(Logger.layer([Logger.consolePretty()])), Effect.matchCauseEffect({
@@ -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\tnetworkOptions: stack.options.networkOptions,\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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"}
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":"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"}
@@ -1,14 +1,12 @@
1
1
  import { layerMystenMoveCodegen } from "../../orchestrators/codegen/bindings.mjs";
2
- import { layerSuiMoveSummaryRunnerDocker, layerSuiMoveSummaryRunnerHost } from "../../plugins/sui/move-summary-runner.mjs";
2
+ import { layerSuiMoveSummaryRunnerHost } from "../../plugins/sui/move-summary-runner.mjs";
3
3
  import { layerCodegenPaths, layerCodegenRoot } from "../../orchestrators/codegen/paths.mjs";
4
4
  import { runEmitCycle } from "../../orchestrators/codegen/service.mjs";
5
- import { buildSubstrateLayers } from "../../orchestrators/boot.mjs";
6
5
  import { ExitCode } from "../../surfaces/cli/sysexits.mjs";
7
- import { CliInternalError } from "../../surfaces/cli/errors.mjs";
6
+ import { CliInternalError, CliUnavailableError } from "../../surfaces/cli/errors.mjs";
8
7
  import "../../surfaces/cli/commands/index.mjs";
9
8
  import { cliErrorFromConfigExit } from "../bail.mjs";
10
9
  import { makeConfigLoader } from "./config-loader.mjs";
11
- import { identityValueFor } from "./identity.mjs";
12
10
  import { Cause, Effect, Exit, Layer, Logger } from "effect";
13
11
  import { dirname, resolve } from "node:path";
14
12
  import { spawnSync } from "node:child_process";
@@ -20,7 +18,7 @@ import * as NodeChildProcessSpawner from "@effect/platform-node/NodeChildProcess
20
18
  * hook (skipping plugins that lack one) to derive the committed-tree
21
19
  * contributions from config alone. KNOWN package ids come from the
22
20
  * declared `networks` literals; LOCAL ids stay the all-zero sentinel
23
- * (resolved at app build/dev time through `__DEVSTACK_IDS__`). */
21
+ * (resolved at app build/dev time through `__DEVSTACK_DEPLOYMENT__`). */
24
22
  const deriveContributions = (members) => {
25
23
  const decls = [];
26
24
  for (const member of members) {
@@ -29,26 +27,24 @@ const deriveContributions = (members) => {
29
27
  }
30
28
  return decls;
31
29
  };
32
- /** True if a `sui` binary is resolvable on PATH (cheap `--version` probe). */
33
- const hostSuiAvailable = () => {
30
+ /** Probe the host `sui` CLI with a cheap `--version` call. Returns the
31
+ * trimmed version line on success (e.g. `sui 1.39.0-abcdef0`), or `null`
32
+ * when no `sui` is resolvable on PATH (missing binary, non-zero exit, or
33
+ * spawn failure). The version is surfaced in codegen output so the build
34
+ * that produced the committed tree is visible. */
35
+ const probeHostSui = () => {
34
36
  try {
35
- return spawnSync("sui", ["--version"], {
37
+ const probe = spawnSync("sui", ["--version"], {
36
38
  timeout: 5e3,
37
- stdio: [
38
- "ignore",
39
- "ignore",
40
- "ignore"
41
- ]
42
- }).status === 0;
39
+ encoding: "utf8"
40
+ });
41
+ if (probe.status !== 0) return null;
42
+ const line = (probe.stdout ?? "").trim() || (probe.stderr ?? "").trim();
43
+ return line.length > 0 ? line : "sui (version unknown)";
43
44
  } catch {
44
- return false;
45
+ return null;
45
46
  }
46
47
  };
47
- const selectCodegenRunner = () => {
48
- const requested = process.env["DEVSTACK_CODEGEN_RUNNER"]?.trim().toLowerCase();
49
- if (requested === "docker" || requested === "host") return requested;
50
- return hostSuiAvailable() ? "host" : "docker";
51
- };
52
48
  /**
53
49
  * Build the codegen service layer: `CodegenPathsService` +
54
50
  * `MoveSummaryRunnerService` + `MoveCodegenService`, with the Node
@@ -57,23 +53,18 @@ const selectCodegenRunner = () => {
57
53
  * the services, with `Logger` supplied as a separate outer layer over the
58
54
  * program (`consolePretty` needs no FileSystem).
59
55
  *
60
- * The Move-summary runner is selected per `selectCodegenRunner`. The
61
- * `host` runner only needs a child-process spawner; the `docker` runner
62
- * needs the full Docker `ContainerRuntimeService`, so it provides the
63
- * substrate layer stack (`buildSubstrateLayers`) UNDER it. The Docker
64
- * runner produces byte-identical bindings to the host runner — it runs the
65
- * SAME `sui move summary` argv, just inside the pinned CLI image — so the
66
- * committed `src/generated` tree is independent of which runner emitted it.
56
+ * The Move-summary runner is the HOST runner — it invokes the `sui` CLI on
57
+ * PATH and only needs a child-process spawner (no Docker, no substrate
58
+ * layer stack). The caller guarantees `sui` is present (see `runCodegen`).
67
59
  */
68
- const buildCodegenLayer = (appRoot, runner, identity) => {
60
+ const buildCodegenLayer = (appRoot) => {
69
61
  const generatedDir = resolve(appRoot, "src", "generated");
70
62
  const platform = Layer.mergeAll(NodeFileSystem.layer, NodePath.layer);
71
63
  const codegenPaths = layerCodegenPaths.pipe(Layer.provideMerge(layerCodegenRoot({
72
64
  outputDir: generatedDir,
73
- stackSubdir: null,
74
- extrasDir: resolve(appRoot, "src", "generated-extras")
65
+ stackSubdir: null
75
66
  })));
76
- const moveRunner = runner === "docker" ? layerSuiMoveSummaryRunnerDocker.pipe(Layer.provideMerge(buildSubstrateLayers(identityValueFor(identity), identity.runtimeRoot))) : layerSuiMoveSummaryRunnerHost.pipe(Layer.provideMerge(NodeChildProcessSpawner.layer));
67
+ const moveRunner = layerSuiMoveSummaryRunnerHost.pipe(Layer.provideMerge(NodeChildProcessSpawner.layer));
77
68
  return Layer.mergeAll(codegenPaths, moveRunner, layerMystenMoveCodegen()).pipe(Layer.provideMerge(platform));
78
69
  };
79
70
  /**
@@ -81,20 +72,28 @@ const buildCodegenLayer = (appRoot, runner, identity) => {
81
72
  * path. No stack boots; the committed `src/generated` tree is rewritten
82
73
  * deterministically from Move source.
83
74
  */
84
- const runCodegen = (configPath, identity) => {
75
+ const runCodegen = (configPath) => {
85
76
  const loader = makeConfigLoader();
86
77
  return Effect.gen(function* () {
78
+ const suiVersion = probeHostSui();
79
+ if (suiVersion === null) return yield* Effect.fail(new CliUnavailableError({
80
+ service: "sui",
81
+ message: "the Sui CLI (`sui`) is required for `devstack codegen` but was not found on PATH",
82
+ hint: "Install it — https://docs.sui.io/guides/developer/getting-started/sui-install — then re-run `devstack codegen`."
83
+ }));
87
84
  const loadExit = yield* Effect.exit(loader.load(configPath));
88
85
  if (Exit.isFailure(loadExit)) return yield* Effect.fail(cliErrorFromConfigExit(loadExit));
89
86
  const loaded = loadExit.value;
90
87
  const stack = loaded.engine;
91
88
  const appRoot = dirname(loaded.resolvedConfigPath);
92
89
  const contributions = deriveContributions(stack.members);
93
- const runner = selectCodegenRunner();
94
- const exit = yield* Effect.exit(runEmitCycle({
95
- contributions,
96
- trackTree: true
97
- }).pipe(Effect.provide(buildCodegenLayer(appRoot, runner, identity)), Effect.provide(Logger.layer([Logger.consolePretty()]))));
90
+ const exit = yield* Effect.exit(Effect.gen(function* () {
91
+ yield* Effect.logInfo(`codegen: using host ${suiVersion}`);
92
+ return yield* runEmitCycle({
93
+ contributions,
94
+ trackTree: true
95
+ });
96
+ }).pipe(Effect.provide(buildCodegenLayer(appRoot)), Effect.provide(Logger.layer([Logger.consolePretty()]))));
98
97
  if (Exit.isFailure(exit)) return yield* Effect.fail(new CliInternalError({
99
98
  message: "codegen failed",
100
99
  cause: Cause.pretty(exit.cause)
@@ -103,6 +102,6 @@ const runCodegen = (configPath, identity) => {
103
102
  });
104
103
  };
105
104
  //#endregion
106
- export { runCodegen };
105
+ export { deriveContributions, runCodegen };
107
106
 
108
107
  //# sourceMappingURL=codegen.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"codegen.mjs","names":["resolvePath"],"sources":["../../../src/cli/wirings/codegen.ts"],"sourcesContent":["// `devstack codegen` verb wiring.\n//\n// Deterministic, STACK-FREE codegen: `(Move source) → generated`. It\n// boots NO stack — no supervisor, no acquire, no Docker. It loads the\n// config, derives each plugin's codegen contributions from config ALONE\n// (via the plugin spec's `staticCodegen` hook), and runs ONE\n// `runEmitCycle` that writes the committed `src/generated` tree.\n//\n// On-chain ids are LOADED CONFIG DATA, not generated output. The\n// committed tree carries the all-zero sentinel for every LOCAL id (and\n// declared literals for KNOWN packages); the committed `config.ts`\n// resolver throws loudly at runtime if a sentinel id is used. The Vite\n// plugin injects the real ids in dev (live `devstack-ids.json`) and prod\n// (a committed id-config file via the `ids` option / `DEVSTACK_IDS_FILE`).\n// So `git status` shows NO churn under\n// `src/generated` after a `devstack up` — ids land only in the\n// gitignored `.devstack/`.\n//\n// Logger layer: `Logger.consolePretty()` — mirrors `apply.ts` exactly.\n// `consolePretty` needs no FileSystem, so it is provided as a separate\n// OUTER layer over the codegen service layer (which provides the Node\n// FileSystem / Path / child-process spawner UNDER the codegen services).\n// This is the same provision shape `apply` uses and compiles cleanly\n// under the strict tsconfig.\n\nimport { spawnSync } from 'node:child_process';\nimport { dirname, resolve as resolvePath } from 'node:path';\n\nimport { Cause, Effect, Exit, Layer, Logger } from 'effect';\nimport * as NodeChildProcessSpawner from '@effect/platform-node/NodeChildProcessSpawner';\nimport * as NodeFileSystem from '@effect/platform-node/NodeFileSystem';\nimport * as NodePath from '@effect/platform-node/NodePath';\n\nimport type { CodegenableDecl } from '../../contracts/codegenable.ts';\nimport type { AnyPlugin } from '../../substrate/plugin.ts';\nimport type { SupervisedStack } from '../../substrate/runtime/index.ts';\nimport { layerMystenMoveCodegen } from '../../orchestrators/codegen/bindings.ts';\nimport {\n\tlayerSuiMoveSummaryRunnerDocker,\n\tlayerSuiMoveSummaryRunnerHost,\n} from '../../plugins/sui/move-summary-runner.ts';\nimport { buildSubstrateLayers } from '../../orchestrators/boot.ts';\nimport { layerCodegenPaths, layerCodegenRoot } from '../../orchestrators/codegen/paths.ts';\nimport { runEmitCycle } from '../../orchestrators/codegen/service.ts';\nimport { type CliError, CliInternalError } from '../../surfaces/cli/errors.ts';\nimport { type CommandResult } 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 { identityValueFor, type ResolvedIdentity } from './identity.ts';\n\n/** Walk the stack members, calling each plugin spec's `staticCodegen`\n * hook (skipping plugins that lack one) to derive the committed-tree\n * contributions from config alone. KNOWN package ids come from the\n * declared `networks` literals; LOCAL ids stay the all-zero sentinel\n * (resolved at app build/dev time through `__DEVSTACK_IDS__`). */\nconst deriveContributions = (members: ReadonlyArray<AnyPlugin>): ReadonlyArray<CodegenableDecl> => {\n\tconst decls: Array<CodegenableDecl> = [];\n\tfor (const member of members) {\n\t\tif (member.staticCodegen === undefined) continue;\n\t\tdecls.push(...member.staticCodegen());\n\t}\n\treturn decls;\n};\n\n/** Which Move-summary runner the stack-free codegen verb uses.\n *\n * - `host` — invoke a local `sui` CLI on PATH directly (fast, no Docker).\n * - `docker` — run `sui move summary` inside the pinned Sui CLI image\n * (the same runner `boot.ts` wires for `up`/`apply`). Needs Docker but\n * NO host `sui`, which is what CI has.\n *\n * Selection follows `DEVSTACK_CODEGEN_RUNNER` (`docker`/`host`); when unset,\n * auto-detect: use `host` if a `sui` binary is on PATH, else fall back to\n * `docker`. CI never installs `sui`, so it lands on `docker` automatically;\n * a workflow can still force it with `DEVSTACK_CODEGEN_RUNNER=docker`. */\ntype CodegenRunner = 'host' | 'docker';\n\n/** True if a `sui` binary is resolvable on PATH (cheap `--version` probe). */\nconst hostSuiAvailable = (): boolean => {\n\ttry {\n\t\tconst probe = spawnSync('sui', ['--version'], {\n\t\t\ttimeout: 5_000,\n\t\t\tstdio: ['ignore', 'ignore', 'ignore'],\n\t\t});\n\t\treturn probe.status === 0;\n\t} catch {\n\t\treturn false;\n\t}\n};\n\nconst selectCodegenRunner = (): CodegenRunner => {\n\tconst requested = process.env['DEVSTACK_CODEGEN_RUNNER']?.trim().toLowerCase();\n\tif (requested === 'docker' || requested === 'host') return requested;\n\treturn hostSuiAvailable() ? 'host' : 'docker';\n};\n\n/**\n * Build the codegen service layer: `CodegenPathsService` +\n * `MoveSummaryRunnerService` + `MoveCodegenService`, with the Node\n * FileSystem / Path / child-process spawner provided UNDER them. Mirrors\n * `apply`'s substrate-layer provision shape — one merged layer providing\n * the services, with `Logger` supplied as a separate outer layer over the\n * program (`consolePretty` needs no FileSystem).\n *\n * The Move-summary runner is selected per `selectCodegenRunner`. The\n * `host` runner only needs a child-process spawner; the `docker` runner\n * needs the full Docker `ContainerRuntimeService`, so it provides the\n * substrate layer stack (`buildSubstrateLayers`) UNDER it. The Docker\n * runner produces byte-identical bindings to the host runner — it runs the\n * SAME `sui move summary` argv, just inside the pinned CLI image — so the\n * committed `src/generated` tree is independent of which runner emitted it.\n */\nconst buildCodegenLayer = (appRoot: string, runner: CodegenRunner, identity: ResolvedIdentity) => {\n\tconst generatedDir = resolvePath(appRoot, 'src', 'generated');\n\tconst platform = Layer.mergeAll(NodeFileSystem.layer, NodePath.layer);\n\tconst codegenPaths = layerCodegenPaths.pipe(\n\t\tLayer.provideMerge(\n\t\t\tlayerCodegenRoot({\n\t\t\t\toutputDir: generatedDir,\n\t\t\t\tstackSubdir: null,\n\t\t\t\t// Static contributions never target `generated-extras` (those\n\t\t\t\t// decls are dev-only and carry no `staticCodegen`), so this\n\t\t\t\t// path is declared but never written.\n\t\t\t\textrasDir: resolvePath(appRoot, 'src', 'generated-extras'),\n\t\t\t}),\n\t\t),\n\t);\n\tconst moveRunner =\n\t\trunner === 'docker'\n\t\t\t? layerSuiMoveSummaryRunnerDocker.pipe(\n\t\t\t\t\t// The Docker runner only needs `ContainerRuntimeService`; the\n\t\t\t\t\t// substrate layer stack provides it (along with the rest of L0).\n\t\t\t\t\t// No stack containers are created — only one-shot `sui move\n\t\t\t\t\t// summary` runs against the pinned CLI image.\n\t\t\t\t\tLayer.provideMerge(\n\t\t\t\t\t\tbuildSubstrateLayers(identityValueFor(identity), identity.runtimeRoot),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t: layerSuiMoveSummaryRunnerHost.pipe(Layer.provideMerge(NodeChildProcessSpawner.layer));\n\treturn Layer.mergeAll(codegenPaths, moveRunner, layerMystenMoveCodegen()).pipe(\n\t\tLayer.provideMerge(platform),\n\t);\n};\n\n/**\n * Run the stack-free codegen verb. `configPath` is the devstack config\n * path. No stack boots; the committed `src/generated` tree is rewritten\n * deterministically from Move source.\n */\nexport const runCodegen = (\n\tconfigPath: string | undefined,\n\tidentity: ResolvedIdentity,\n): Effect.Effect<CommandResult, CliError> => {\n\tconst loader = makeConfigLoader();\n\treturn Effect.gen(function* () {\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\tconst appRoot = dirname(loaded.resolvedConfigPath);\n\n\t\tconst contributions = deriveContributions(stack.members);\n\t\tconst runner = selectCodegenRunner();\n\n\t\tconst exit = yield* Effect.exit(\n\t\t\trunEmitCycle({ contributions, trackTree: true }).pipe(\n\t\t\t\tEffect.provide(buildCodegenLayer(appRoot, runner, identity)),\n\t\t\t\tEffect.provide(Logger.layer([Logger.consolePretty()])),\n\t\t\t),\n\t\t);\n\t\tif (Exit.isFailure(exit)) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew CliInternalError({\n\t\t\t\t\tmessage: 'codegen 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\t\treturn { exitCode: ExitCode.OK };\n\t});\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA0DA,MAAM,uBAAuB,YAAsE;CAClG,MAAM,QAAgC,CAAC;CACvC,KAAK,MAAM,UAAU,SAAS;EAC7B,IAAI,OAAO,kBAAkB,KAAA,GAAW;EACxC,MAAM,KAAK,GAAG,OAAO,cAAc,CAAC;CACrC;CACA,OAAO;AACR;;AAgBA,MAAM,yBAAkC;CACvC,IAAI;EAKH,OAJc,UAAU,OAAO,CAAC,WAAW,GAAG;GAC7C,SAAS;GACT,OAAO;IAAC;IAAU;IAAU;GAAQ;EACrC,CACW,CAAC,CAAC,WAAW;CACzB,QAAQ;EACP,OAAO;CACR;AACD;AAEA,MAAM,4BAA2C;CAChD,MAAM,YAAY,QAAQ,IAAI,0BAA0B,EAAE,KAAK,CAAC,CAAC,YAAY;CAC7E,IAAI,cAAc,YAAY,cAAc,QAAQ,OAAO;CAC3D,OAAO,iBAAiB,IAAI,SAAS;AACtC;;;;;;;;;;;;;;;;;AAkBA,MAAM,qBAAqB,SAAiB,QAAuB,aAA+B;CACjG,MAAM,eAAeA,QAAY,SAAS,OAAO,WAAW;CAC5D,MAAM,WAAW,MAAM,SAAS,eAAe,OAAO,SAAS,KAAK;CACpE,MAAM,eAAe,kBAAkB,KACtC,MAAM,aACL,iBAAiB;EAChB,WAAW;EACX,aAAa;EAIb,WAAWA,QAAY,SAAS,OAAO,kBAAkB;CAC1D,CAAC,CACF,CACD;CACA,MAAM,aACL,WAAW,WACR,gCAAgC,KAKhC,MAAM,aACL,qBAAqB,iBAAiB,QAAQ,GAAG,SAAS,WAAW,CACtE,CACD,IACC,8BAA8B,KAAK,MAAM,aAAa,wBAAwB,KAAK,CAAC;CACxF,OAAO,MAAM,SAAS,cAAc,YAAY,uBAAuB,CAAC,CAAC,CAAC,KACzE,MAAM,aAAa,QAAQ,CAC5B;AACD;;;;;;AAOA,MAAa,cACZ,YACA,aAC4C;CAC5C,MAAM,SAAS,iBAAiB;CAChC,OAAO,OAAO,IAAI,aAAa;EAC9B,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;EAC9E,MAAM,UAAU,QAAQ,OAAO,kBAAkB;EAEjD,MAAM,gBAAgB,oBAAoB,MAAM,OAAO;EACvD,MAAM,SAAS,oBAAoB;EAEnC,MAAM,OAAO,OAAO,OAAO,KAC1B,aAAa;GAAE;GAAe,WAAW;EAAK,CAAC,CAAC,CAAC,KAChD,OAAO,QAAQ,kBAAkB,SAAS,QAAQ,QAAQ,CAAC,GAC3D,OAAO,QAAQ,OAAO,MAAM,CAAC,OAAO,cAAc,CAAC,CAAC,CAAC,CACtD,CACD;EACA,IAAI,KAAK,UAAU,IAAI,GACtB,OAAO,OAAO,OAAO,KACpB,IAAI,iBAAiB;GACpB,SAAS;GACT,OAAO,MAAM,OAAO,KAAK,KAA6B;EACvD,CAAC,CACF;EAED,OAAO,EAAE,UAAU,SAAS,GAAG;CAChC,CAAC;AACF"}
1
+ {"version":3,"file":"codegen.mjs","names":["resolvePath"],"sources":["../../../src/cli/wirings/codegen.ts"],"sourcesContent":["// `devstack codegen` verb wiring.\n//\n// Deterministic, STACK-FREE codegen: `(Move source) → generated`. It\n// boots NO stack and uses NO Docker — the `sui move summary` step runs\n// against a host `sui` CLI on PATH, which is REQUIRED: codegen fails fast\n// with an actionable error when `sui` is absent (rather than silently\n// pulling a Docker image). The detected `sui --version` is logged so the\n// build that produced the committed tree is visible; CI installs a pinned\n// `sui` and the drift-guard catches any version-skew in the output. It\n// loads the config, derives each plugin's codegen contributions from config\n// ALONE (via the plugin spec's `staticCodegen` hook), and runs ONE\n// `runEmitCycle` that writes the committed `src/generated` tree.\n//\n// On-chain ids are LOADED CONFIG DATA, not generated output. The\n// committed tree carries the all-zero sentinel for every LOCAL id (and\n// declared literals for KNOWN packages); the committed `config.ts`\n// resolver throws loudly at runtime if a sentinel id is used. The Vite\n// plugin injects the real ids in dev (live `deployment.json`) and prod\n// (the committed per-network `deployments` thunks).\n// So `git status` shows NO churn under\n// `src/generated` after a `devstack up` — ids land only in the\n// gitignored `.devstack/`.\n//\n// Logger layer: `Logger.consolePretty()` — mirrors `apply.ts` exactly.\n// `consolePretty` needs no FileSystem, so it is provided as a separate\n// OUTER layer over the codegen service layer (which provides the Node\n// FileSystem / Path / child-process spawner UNDER the codegen services).\n// This is the same provision shape `apply` uses and compiles cleanly\n// under the strict tsconfig.\n\nimport { spawnSync } from 'node:child_process';\nimport { dirname, resolve as resolvePath } from 'node:path';\n\nimport { Cause, Effect, Exit, Layer, Logger } from 'effect';\nimport * as NodeChildProcessSpawner from '@effect/platform-node/NodeChildProcessSpawner';\nimport * as NodeFileSystem from '@effect/platform-node/NodeFileSystem';\nimport * as NodePath from '@effect/platform-node/NodePath';\n\nimport type { CodegenableDecl } from '../../contracts/codegenable.ts';\nimport type { AnyPlugin } from '../../substrate/plugin.ts';\nimport type { SupervisedStack } from '../../substrate/runtime/index.ts';\nimport { layerMystenMoveCodegen } from '../../orchestrators/codegen/bindings.ts';\nimport { layerSuiMoveSummaryRunnerHost } from '../../plugins/sui/move-summary-runner.ts';\nimport { layerCodegenPaths, layerCodegenRoot } from '../../orchestrators/codegen/paths.ts';\nimport { runEmitCycle } from '../../orchestrators/codegen/service.ts';\nimport { type CliError, CliInternalError, CliUnavailableError } from '../../surfaces/cli/errors.ts';\nimport { type CommandResult } 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';\n\n/** Walk the stack members, calling each plugin spec's `staticCodegen`\n * hook (skipping plugins that lack one) to derive the committed-tree\n * contributions from config alone. KNOWN package ids come from the\n * declared `networks` literals; LOCAL ids stay the all-zero sentinel\n * (resolved at app build/dev time through `__DEVSTACK_DEPLOYMENT__`). */\nexport const deriveContributions = (\n\tmembers: ReadonlyArray<AnyPlugin>,\n): ReadonlyArray<CodegenableDecl> => {\n\tconst decls: Array<CodegenableDecl> = [];\n\tfor (const member of members) {\n\t\tif (member.staticCodegen === undefined) continue;\n\t\tdecls.push(...member.staticCodegen());\n\t}\n\treturn decls;\n};\n\n/** Probe the host `sui` CLI with a cheap `--version` call. Returns the\n * trimmed version line on success (e.g. `sui 1.39.0-abcdef0`), or `null`\n * when no `sui` is resolvable on PATH (missing binary, non-zero exit, or\n * spawn failure). The version is surfaced in codegen output so the build\n * that produced the committed tree is visible. */\nconst probeHostSui = (): string | null => {\n\ttry {\n\t\tconst probe = spawnSync('sui', ['--version'], { timeout: 5_000, encoding: 'utf8' });\n\t\tif (probe.status !== 0) return null;\n\t\tconst line = (probe.stdout ?? '').trim() || (probe.stderr ?? '').trim();\n\t\treturn line.length > 0 ? line : 'sui (version unknown)';\n\t} catch {\n\t\treturn null;\n\t}\n};\n\n/**\n * Build the codegen service layer: `CodegenPathsService` +\n * `MoveSummaryRunnerService` + `MoveCodegenService`, with the Node\n * FileSystem / Path / child-process spawner provided UNDER them. Mirrors\n * `apply`'s substrate-layer provision shape — one merged layer providing\n * the services, with `Logger` supplied as a separate outer layer over the\n * program (`consolePretty` needs no FileSystem).\n *\n * The Move-summary runner is the HOST runner — it invokes the `sui` CLI on\n * PATH and only needs a child-process spawner (no Docker, no substrate\n * layer stack). The caller guarantees `sui` is present (see `runCodegen`).\n */\nconst buildCodegenLayer = (appRoot: string) => {\n\tconst generatedDir = resolvePath(appRoot, 'src', 'generated');\n\tconst platform = Layer.mergeAll(NodeFileSystem.layer, NodePath.layer);\n\tconst codegenPaths = layerCodegenPaths.pipe(\n\t\tLayer.provideMerge(\n\t\t\tlayerCodegenRoot({\n\t\t\t\toutputDir: generatedDir,\n\t\t\t\tstackSubdir: null,\n\t\t\t}),\n\t\t),\n\t);\n\tconst moveRunner = layerSuiMoveSummaryRunnerHost.pipe(\n\t\tLayer.provideMerge(NodeChildProcessSpawner.layer),\n\t);\n\treturn Layer.mergeAll(codegenPaths, moveRunner, layerMystenMoveCodegen()).pipe(\n\t\tLayer.provideMerge(platform),\n\t);\n};\n\n/**\n * Run the stack-free codegen verb. `configPath` is the devstack config\n * path. No stack boots; the committed `src/generated` tree is rewritten\n * deterministically from Move source.\n */\nexport const runCodegen = (\n\tconfigPath: string | undefined,\n): Effect.Effect<CommandResult, CliError> => {\n\tconst loader = makeConfigLoader();\n\treturn Effect.gen(function* () {\n\t\t// Host `sui` is REQUIRED — the `sui move summary` step has no Docker\n\t\t// fallback. Fail fast with an actionable hint when it is missing,\n\t\t// rather than emitting an empty/partial tree.\n\t\tconst suiVersion = probeHostSui();\n\t\tif (suiVersion === null) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew CliUnavailableError({\n\t\t\t\t\tservice: 'sui',\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t'the Sui CLI (`sui`) is required for `devstack codegen` but was not found on PATH',\n\t\t\t\t\thint: 'Install it — https://docs.sui.io/guides/developer/getting-started/sui-install — then re-run `devstack codegen`.',\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\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\tconst appRoot = dirname(loaded.resolvedConfigPath);\n\n\t\tconst contributions = deriveContributions(stack.members);\n\n\t\tconst exit = yield* Effect.exit(\n\t\t\tEffect.gen(function* () {\n\t\t\t\tyield* Effect.logInfo(`codegen: using host ${suiVersion}`);\n\t\t\t\treturn yield* runEmitCycle({ contributions, trackTree: true });\n\t\t\t}).pipe(\n\t\t\t\tEffect.provide(buildCodegenLayer(appRoot)),\n\t\t\t\tEffect.provide(Logger.layer([Logger.consolePretty()])),\n\t\t\t),\n\t\t);\n\t\tif (Exit.isFailure(exit)) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew CliInternalError({\n\t\t\t\t\tmessage: 'codegen 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\t\treturn { exitCode: ExitCode.OK };\n\t});\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA0DA,MAAa,uBACZ,YACoC;CACpC,MAAM,QAAgC,CAAC;CACvC,KAAK,MAAM,UAAU,SAAS;EAC7B,IAAI,OAAO,kBAAkB,KAAA,GAAW;EACxC,MAAM,KAAK,GAAG,OAAO,cAAc,CAAC;CACrC;CACA,OAAO;AACR;;;;;;AAOA,MAAM,qBAAoC;CACzC,IAAI;EACH,MAAM,QAAQ,UAAU,OAAO,CAAC,WAAW,GAAG;GAAE,SAAS;GAAO,UAAU;EAAO,CAAC;EAClF,IAAI,MAAM,WAAW,GAAG,OAAO;EAC/B,MAAM,QAAQ,MAAM,UAAU,GAAA,CAAI,KAAK,MAAM,MAAM,UAAU,GAAA,CAAI,KAAK;EACtE,OAAO,KAAK,SAAS,IAAI,OAAO;CACjC,QAAQ;EACP,OAAO;CACR;AACD;;;;;;;;;;;;;AAcA,MAAM,qBAAqB,YAAoB;CAC9C,MAAM,eAAeA,QAAY,SAAS,OAAO,WAAW;CAC5D,MAAM,WAAW,MAAM,SAAS,eAAe,OAAO,SAAS,KAAK;CACpE,MAAM,eAAe,kBAAkB,KACtC,MAAM,aACL,iBAAiB;EAChB,WAAW;EACX,aAAa;CACd,CAAC,CACF,CACD;CACA,MAAM,aAAa,8BAA8B,KAChD,MAAM,aAAa,wBAAwB,KAAK,CACjD;CACA,OAAO,MAAM,SAAS,cAAc,YAAY,uBAAuB,CAAC,CAAC,CAAC,KACzE,MAAM,aAAa,QAAQ,CAC5B;AACD;;;;;;AAOA,MAAa,cACZ,eAC4C;CAC5C,MAAM,SAAS,iBAAiB;CAChC,OAAO,OAAO,IAAI,aAAa;EAI9B,MAAM,aAAa,aAAa;EAChC,IAAI,eAAe,MAClB,OAAO,OAAO,OAAO,KACpB,IAAI,oBAAoB;GACvB,SAAS;GACT,SACC;GACD,MAAM;EACP,CAAC,CACF;EAGD,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;EAC9E,MAAM,UAAU,QAAQ,OAAO,kBAAkB;EAEjD,MAAM,gBAAgB,oBAAoB,MAAM,OAAO;EAEvD,MAAM,OAAO,OAAO,OAAO,KAC1B,OAAO,IAAI,aAAa;GACvB,OAAO,OAAO,QAAQ,uBAAuB,YAAY;GACzD,OAAO,OAAO,aAAa;IAAE;IAAe,WAAW;GAAK,CAAC;EAC9D,CAAC,CAAC,CAAC,KACF,OAAO,QAAQ,kBAAkB,OAAO,CAAC,GACzC,OAAO,QAAQ,OAAO,MAAM,CAAC,OAAO,cAAc,CAAC,CAAC,CAAC,CACtD,CACD;EACA,IAAI,KAAK,UAAU,IAAI,GACtB,OAAO,OAAO,OAAO,KACpB,IAAI,iBAAiB;GACpB,SAAS;GACT,OAAO,MAAM,OAAO,KAAK,KAA6B;EACvD,CAAC,CACF;EAED,OAAO,EAAE,UAAU,SAAS,GAAG;CAChC,CAAC;AACF"}
@@ -0,0 +1,141 @@
1
+ import "../../api/inference-network.mjs";
2
+ import { DEPLOYMENT_FILENAME, decodeDeployment } from "../../orchestrators/codegen/deployment.mjs";
3
+ import { ExitCode } from "../../surfaces/cli/sysexits.mjs";
4
+ import { CliInternalError, CliUsageError } from "../../surfaces/cli/errors.mjs";
5
+ import { emitSuccess } from "../../surfaces/cli/output.mjs";
6
+ import { probeSupervisorPresence } from "../../surfaces/cli/commands/supervisor-presence.mjs";
7
+ import "../../surfaces/cli/commands/index.mjs";
8
+ import { cliErrorFromConfigExit } from "../bail.mjs";
9
+ import { makeConfigLoader } from "./config-loader.mjs";
10
+ import { resolvedIdentityForStack } from "./identity.mjs";
11
+ import { runApplyLive } from "./apply.mjs";
12
+ import { DEPLOYMENTS_DIRNAME, deploymentBody, renderNetworkDeploymentFile } from "../../orchestrators/codegen/deployment-network-file.mjs";
13
+ import { Effect, Exit } from "effect";
14
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
15
+ import { dirname, resolve } from "node:path";
16
+ //#region src/cli/wirings/dump-deployment.ts
17
+ /** Read the on-disk deployment as raw text. The file is written by boot's
18
+ * post-acquire hook (`writeDeployment`) at `<stackRoot>/deployment.json`;
19
+ * a miss here means the boot never produced it (or a hand-removal), which
20
+ * is an internal inconsistency the operator can't act on directly. */
21
+ const readDeploymentText = (deploymentFile) => Effect.try({
22
+ try: () => readFileSync(deploymentFile, "utf8"),
23
+ catch: (cause) => new CliInternalError({
24
+ message: `failed to read deployment at ${deploymentFile}`,
25
+ cause
26
+ })
27
+ });
28
+ const writeOutFile = (out, text) => Effect.try({
29
+ try: () => writeFileSync(out, text, "utf8"),
30
+ catch: (cause) => new CliInternalError({
31
+ message: `failed to write deployment to ${out}`,
32
+ cause
33
+ })
34
+ });
35
+ /** Decode the deployment text for the JSON envelope `data`, routed through
36
+ * the shared {@link decodeDeployment} so the parse-and-validate decision is
37
+ * centralized (same seam the Vite plugin and codegen verb use). A failure
38
+ * here means the on-disk file is corrupt — bad JSON OR a shape that
39
+ * violates `DeploymentSchema` — so surface it rather than emit a bad
40
+ * envelope. (The file is written by `writeDeployment` from the same schema,
41
+ * so a conforming boot-written file always decodes; only a corrupt or
42
+ * hand-edited file trips the stricter validation.) */
43
+ const parseDeployment = (text, deploymentFile) => Effect.try({
44
+ try: () => decodeDeployment(text),
45
+ catch: (cause) => new CliInternalError({
46
+ message: `could not decode deployment at ${deploymentFile} (malformed JSON or invalid shape)`,
47
+ cause
48
+ })
49
+ });
50
+ /** The project root the `deployments/<net>.ts` file is written relative to:
51
+ * the directory of the resolved `devstack.config.ts` (so a `--config` in a
52
+ * subdir still lands `deployments/` next to that app's `src/`), else the
53
+ * process cwd. */
54
+ const projectRootOf = (configPath) => configPath === void 0 ? process.cwd() : dirname(resolve(configPath));
55
+ /** Render + write the typed `deployments/<network>.ts` for `network` from the
56
+ * resolved envelope, creating the `deployments/` dir if absent. Loud-fails
57
+ * (usage error) when the envelope carries no such network — listing the ones
58
+ * it does, so the operator can pick a valid name. */
59
+ const writeNetworkDeploymentFile = (envelope, network, projectRoot) => Effect.gen(function* () {
60
+ const unit = envelope.networks[network];
61
+ if (unit === void 0) {
62
+ const available = Object.keys(envelope.networks).sort().join(", ");
63
+ return yield* Effect.fail(new CliUsageError({
64
+ message: `the resolved deployment has no network "${network}"`,
65
+ hint: available.length > 0 ? `available networks: ${available}` : "the deployment carries no networks"
66
+ }));
67
+ }
68
+ if (network === "localnet" || unit.local === true) return yield* Effect.fail(new CliUsageError({
69
+ message: `cannot dump the live local network "${network}" as a committed deployment`,
70
+ hint: "committed deployments are for real networks; pick a non-local --network (e.g. testnet, mainnet)"
71
+ }));
72
+ const rendered = renderNetworkDeploymentFile(network, unit);
73
+ if (!rendered.ok) return yield* Effect.fail(new CliInternalError({
74
+ message: `failed to render deployments/${network}.ts`,
75
+ cause: rendered.error
76
+ }));
77
+ const dir = resolve(projectRoot, DEPLOYMENTS_DIRNAME);
78
+ const outFile = resolve(dir, `${network}.ts`);
79
+ yield* Effect.try({
80
+ try: () => {
81
+ mkdirSync(dir, { recursive: true });
82
+ writeFileSync(outFile, rendered.text, "utf8");
83
+ },
84
+ catch: (cause) => new CliInternalError({
85
+ message: `failed to write ${outFile}`,
86
+ cause
87
+ })
88
+ });
89
+ return outFile;
90
+ });
91
+ const runDumpDeployment = (identity, opts) => {
92
+ const loader = makeConfigLoader();
93
+ return Effect.gen(function* () {
94
+ const started = Date.now();
95
+ const loadExit = yield* Effect.exit(loader.load(opts.configPath));
96
+ if (Exit.isFailure(loadExit)) return yield* Effect.fail(cliErrorFromConfigExit(loadExit));
97
+ const stack = loadExit.value.engine;
98
+ const effectiveIdentity = resolvedIdentityForStack(identity, stack);
99
+ const deploymentFile = resolve(effectiveIdentity.stackRoot, DEPLOYMENT_FILENAME);
100
+ if (!(yield* probeSupervisorPresence(effectiveIdentity.rosterFile).pipe(Effect.catch(() => Effect.succeed({
101
+ live: false,
102
+ pid: null,
103
+ hostname: null
104
+ })))).live) yield* runApplyLive(opts.configPath, identity);
105
+ const text = yield* readDeploymentText(deploymentFile);
106
+ const data = yield* parseDeployment(text, deploymentFile);
107
+ if (opts.network !== void 0) {
108
+ const projectRoot = projectRootOf(opts.configPath);
109
+ const outFile = yield* writeNetworkDeploymentFile(data, opts.network, projectRoot);
110
+ const writtenUnit = data.networks[opts.network];
111
+ yield* emitSuccess(opts.io, opts.outputMode, {
112
+ command: "dump-deployment",
113
+ elapsedMs: Date.now() - started,
114
+ data: deploymentBody(opts.network, writtenUnit),
115
+ humanLines: [`wrote ${outFile}`]
116
+ });
117
+ return { exitCode: ExitCode.OK };
118
+ }
119
+ if (opts.out !== void 0) {
120
+ yield* writeOutFile(opts.out, text);
121
+ yield* emitSuccess(opts.io, opts.outputMode, {
122
+ command: "dump-deployment",
123
+ elapsedMs: Date.now() - started,
124
+ data,
125
+ humanLines: [`wrote deployment to ${opts.out}`]
126
+ });
127
+ return { exitCode: ExitCode.OK };
128
+ }
129
+ yield* emitSuccess(opts.io, opts.outputMode, {
130
+ command: "dump-deployment",
131
+ elapsedMs: Date.now() - started,
132
+ data,
133
+ humanLines: [text.endsWith("\n") ? text.slice(0, -1) : text]
134
+ });
135
+ return { exitCode: ExitCode.OK };
136
+ });
137
+ };
138
+ //#endregion
139
+ export { runDumpDeployment };
140
+
141
+ //# sourceMappingURL=dump-deployment.mjs.map
@@ -0,0 +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":";;;;;;;;;;;;;;;;;;;;AAyEA,MAAM,sBAAsB,mBAC3B,OAAO,IAAI;CACV,WAAW,aAAa,gBAAgB,MAAM;CAC9C,QAAQ,UACP,IAAI,iBAAiB;EACpB,SAAS,gCAAgC;EACzC;CACD,CAAC;AACH,CAAC;AAEF,MAAM,gBAAgB,KAAa,SAClC,OAAO,IAAI;CACV,WAAW,cAAc,KAAK,MAAM,MAAM;CAC1C,QAAQ,UACP,IAAI,iBAAiB;EAAE,SAAS,iCAAiC;EAAO;CAAM,CAAC;AACjF,CAAC;;;;;;;;;AAUF,MAAM,mBACL,MACA,mBAEA,OAAO,IAAI;CACV,WAAW,iBAAiB,IAAI;CAChC,QAAQ,UACP,IAAI,iBAAiB;EAGpB,SAAS,kCAAkC,eAAe;EAC1D;CACD,CAAC;AACH,CAAC;;;;;AAMF,MAAM,iBAAiB,eACtB,eAAe,KAAA,IAAY,QAAQ,IAAI,IAAI,QAAQA,QAAY,UAAU,CAAC;;;;;AAM3E,MAAa,8BACZ,UACA,SACA,gBAEA,OAAO,IAAI,aAAa;CACvB,MAAM,OAAO,SAAS,SAAS;CAC/B,IAAI,SAAS,KAAA,GAAW;EACvB,MAAM,YAAY,OAAO,KAAK,SAAS,QAAQ,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI;EACjE,OAAO,OAAO,OAAO,KACpB,IAAI,cAAc;GACjB,SAAS,2CAA2C,QAAQ;GAC5D,MACC,UAAU,SAAS,IAChB,uBAAuB,cACvB;EACL,CAAC,CACF;CACD;CAOA,IAAI,YAAA,cAAkC,KAAK,UAAU,MACpD,OAAO,OAAO,OAAO,KACpB,IAAI,cAAc;EACjB,SAAS,uCAAuC,QAAQ;EACxD,MAAM;CACP,CAAC,CACF;CAED,MAAM,WAAW,4BAA4B,SAAS,IAAI;CAC1D,IAAI,CAAC,SAAS,IACb,OAAO,OAAO,OAAO,KACpB,IAAI,iBAAiB;EACpB,SAAS,gCAAgC,QAAQ;EACjD,OAAO,SAAS;CACjB,CAAC,CACF;CAED,MAAM,MAAMA,QAAY,aAAa,mBAAmB;CACxD,MAAM,UAAUA,QAAY,KAAK,GAAG,QAAQ,IAAI;CAChD,OAAO,OAAO,IAAI;EACjB,WAAW;GACV,UAAU,KAAK,EAAE,WAAW,KAAK,CAAC;GAClC,cAAc,SAAS,SAAS,MAAM,MAAM;EAC7C;EACA,QAAQ,UAAU,IAAI,iBAAiB;GAAE,SAAS,mBAAmB;GAAW;EAAM,CAAC;CACxF,CAAC;CACD,OAAO;AACR,CAAC;AAEF,MAAa,qBACZ,UACA,SAC4C;CAC5C,MAAM,SAAS,iBAAiB;CAChC,OAAO,OAAO,IAAI,aAAa;EAC9B,MAAM,UAAU,KAAK,IAAI;EAMzB,MAAM,WAAW,OAAO,OAAO,KAAK,OAAO,KAAK,KAAK,UAAU,CAAC;EAChE,IAAI,KAAK,UAAU,QAAQ,GAC1B,OAAO,OAAO,OAAO,KAAK,uBAAuB,QAAQ,CAAC;EAE3D,MAAM,QAAS,SAAS,MAA8D;EACtF,MAAM,oBAAoB,yBAAyB,UAAU,KAAK;EAClE,MAAM,iBAAiBA,QAAY,kBAAkB,WAAW,mBAAmB;EASnF,IAAI,EAAC,OAHmB,wBAAwB,kBAAkB,UAAU,CAAC,CAAC,KAC7E,OAAO,YAAY,OAAO,QAAQ;GAAE,MAAM;GAAO,KAAK;GAAM,UAAU;EAAK,CAAC,CAAC,CAC9E,EAAA,CACc,MAKb,OAAO,aAAa,KAAK,YAAY,QAAQ;EAG9C,MAAM,OAAO,OAAO,mBAAmB,cAAc;EACrD,MAAM,OAAO,OAAO,gBAAgB,MAAM,cAAc;EAMxD,IAAI,KAAK,YAAY,KAAA,GAAW;GAC/B,MAAM,cAAc,cAAc,KAAK,UAAU;GACjD,MAAM,UAAU,OAAO,2BAA2B,MAAM,KAAK,SAAS,WAAW;GAMjF,MAAM,cAAc,KAAK,SAAS,KAAK;GACvC,OAAO,YAAY,KAAK,IAAI,KAAK,YAAY;IAC5C,SAAS;IACT,WAAW,KAAK,IAAI,IAAI;IACxB,MAAM,eAAe,KAAK,SAAS,WAAW;IAC9C,YAAY,CAAC,SAAS,SAAS;GAChC,CAAC;GACD,OAAO,EAAE,UAAU,SAAS,GAAG;EAChC;EAEA,IAAI,KAAK,QAAQ,KAAA,GAAW;GAC3B,OAAO,aAAa,KAAK,KAAK,IAAI;GAClC,OAAO,YAAY,KAAK,IAAI,KAAK,YAAY;IAC5C,SAAS;IACT,WAAW,KAAK,IAAI,IAAI;IACxB;IACA,YAAY,CAAC,uBAAuB,KAAK,KAAK;GAC/C,CAAC;GACD,OAAO,EAAE,UAAU,SAAS,GAAG;EAChC;EAMA,OAAO,YAAY,KAAK,IAAI,KAAK,YAAY;GAC5C,SAAS;GACT,WAAW,KAAK,IAAI,IAAI;GACxB;GACA,YAAY,CAAC,KAAK,SAAS,IAAI,IAAI,KAAK,MAAM,GAAG,EAAE,IAAI,IAAI;EAC5D,CAAC;EACD,OAAO,EAAE,UAAU,SAAS,GAAG;CAChC,CAAC;AACF"}
@@ -121,7 +121,6 @@ const runSnapshotCaptureDirectLoaded = (identity, loaded, args) => Effect.gen(fu
121
121
  options: stack.options
122
122
  }, identityValue, state, {
123
123
  extras: stack.options.extras,
124
- networkOptions: stack.options.networkOptions,
125
124
  lifetime: "one-shot",
126
125
  withinScope: () => Effect.gen(function* () {
127
126
  const snapshot = yield* SnapshotOrchestratorService;