@mysten-incubation/devstack 0.2.0 → 0.3.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 (47) hide show
  1. package/dashboard-ui/assets/{grpc-a4usE3Nk.js → grpc-Clz0oOtc.js} +1 -1
  2. package/dashboard-ui/assets/{index-CRYJ4pru.js → index-Cmqv9kiU.js} +39 -33
  3. package/dashboard-ui/index.html +1 -1
  4. package/dist/api/run-stack-internal.mjs +1 -0
  5. package/dist/api/run-stack-internal.mjs.map +1 -1
  6. package/dist/build-integrations/playwright/config.d.mts +2 -1
  7. package/dist/build-integrations/playwright/config.mjs +1 -1
  8. package/dist/build-integrations/playwright/config.mjs.map +1 -1
  9. package/dist/build-integrations/vite/index.d.mts +39 -9
  10. package/dist/build-integrations/vite/index.mjs +47 -11
  11. package/dist/build-integrations/vite/index.mjs.map +1 -1
  12. package/dist/build-integrations/vitest/config.mjs +3 -4
  13. package/dist/build-integrations/vitest/config.mjs.map +1 -1
  14. package/dist/cli/doctor-probes.mjs +7 -2
  15. package/dist/cli/doctor-probes.mjs.map +1 -1
  16. package/dist/cli/main.mjs +2 -2
  17. package/dist/cli/main.mjs.map +1 -1
  18. package/dist/cli/wirings/codegen.mjs +35 -35
  19. package/dist/cli/wirings/codegen.mjs.map +1 -1
  20. package/dist/cli/wirings/up.mjs +7 -0
  21. package/dist/cli/wirings/up.mjs.map +1 -1
  22. package/dist/orchestrators/boot.mjs +12 -2
  23. package/dist/orchestrators/boot.mjs.map +1 -1
  24. package/dist/orchestrators/codegen/service.mjs +23 -8
  25. package/dist/orchestrators/codegen/service.mjs.map +1 -1
  26. package/dist/plugins/dashboard/domain.mjs +18 -1
  27. package/dist/plugins/dashboard/domain.mjs.map +1 -1
  28. package/dist/plugins/dashboard/schema/root.mjs +2 -2
  29. package/dist/plugins/dashboard/schema/root.mjs.map +1 -1
  30. package/dist/plugins/dashboard/schema/types.mjs +15 -2
  31. package/dist/plugins/dashboard/schema/types.mjs.map +1 -1
  32. package/dist/plugins/deepbook/index.d.mts +7 -3
  33. package/dist/plugins/deepbook/index.mjs +3 -3
  34. package/dist/plugins/deepbook/index.mjs.map +1 -1
  35. package/dist/plugins/sui/index.d.mts +10 -10
  36. package/dist/plugins/sui/move-summary-runner.mjs +29 -2
  37. package/dist/plugins/sui/move-summary-runner.mjs.map +1 -1
  38. package/dist/substrate/runtime/index.mjs +1 -0
  39. package/dist/substrate/runtime/lifecycle/file-watcher.d.mts +1 -0
  40. package/dist/substrate/runtime/lifecycle/file-watcher.mjs +54 -0
  41. package/dist/substrate/runtime/lifecycle/file-watcher.mjs.map +1 -0
  42. package/dist/substrate/runtime/lifecycle/index.mjs +1 -0
  43. package/dist/substrate/runtime/lifecycle/watch-attribution.mjs +76 -6
  44. package/dist/substrate/runtime/lifecycle/watch-attribution.mjs.map +1 -1
  45. package/dist/substrate/runtime/supervisor/start-supervisor.mjs +2 -6
  46. package/dist/substrate/runtime/supervisor/start-supervisor.mjs.map +1 -1
  47. package/package.json +1 -1
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>devstack dashboard</title>
7
- <script type="module" crossorigin src="/assets/index-CRYJ4pru.js"></script>
7
+ <script type="module" crossorigin src="/assets/index-Cmqv9kiU.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="/assets/index-B82Bh84P.css">
9
9
  </head>
10
10
  <body>
@@ -129,6 +129,7 @@ const runStackWithBoot = (stack, opts = {}) => {
129
129
  yield* superviseStackWithProductionBoot(supervisedStack, identity, state, {
130
130
  extras: stack.options.extras,
131
131
  networkOptions: stack.options.networkOptions,
132
+ ...opts.emitBindings === void 0 ? {} : { emitBindings: opts.emitBindings },
132
133
  ...opts.boot?.devstackVersion === void 0 ? {} : { devstackVersion: opts.boot.devstackVersion },
133
134
  ...commandHandler === void 0 ? {} : { commandHandler },
134
135
  ...opts.extendContext === void 0 ? {} : { extendContextAfterBuiltIn: opts.extendContext },
@@ -1 +1 @@
1
- {"version":3,"file":"run-stack-internal.mjs","names":[],"sources":["../../src/api/run-stack-internal.ts"],"sourcesContent":["// `runStackWithBoot(stack, optsWithBoot)` — the NON-PUBLIC seam under\n// `runStack`.\n//\n// `api/run-stack.ts` is the thin PUBLIC facade: `runStack` delegates here\n// with a zero-`boot` bag, so the public surface (`RunHandle`,\n// `RunStackOptions`, `BootError`) and the zero-`boot` path stay\n// byte-identical. This module owns the supervised-body construction and\n// the caller-injectable boot hook seam the CLI `up` verb routes through.\n//\n// Why a seam at all: the CLI `up` verb needs the SAME substrate Layer\n// composition + supervised body `runStack` builds, but wrapped with extra\n// `beforeInitialAcquire`/`withinScope` work (interrupted-restore recovery,\n// the roster lock, the command-channel IPC bridge, and the TUI surface).\n// `runStackWithBoot` threads those as CALLER-INJECTED hooks so\n// the CLI consumes ONE boot core instead of forking it. Because the bag is\n// non-public, its hooks receive an `InternalRunHandle` that additionally\n// carries the live `SupervisorHandle` (the seam the CLI hooks drain) —\n// substrate detail that NEVER reaches the public `RunHandle`.\n//\n// PR#21 boot-ordering invariant (do not reorder): the composed hooks run\n// the BUILT-IN work FIRST, then the caller's. For `beforeInitialAcquire`\n// this means the event-queue handoff + stop-bridge + command-pump are armed\n// before any caller hook runs (so a `stop()` during a caller hook always\n// has a bridge) AND before the first acquire (`superviseStackEffect` runs\n// the whole composed `beforeInitialAcquire` ahead of `runInitialAcquire`).\n// For `withinScope` the built-in readiness-gate resolution (the status\n// scan that resolves `bootDeferred`) runs first, so a caller `withinScope`\n// can never delay `handle.start`.\n\nimport {\n\tCause,\n\tDeferred,\n\tEffect,\n\tExit,\n\tFiber,\n\ttype FileSystem,\n\tLayer,\n\tLogger,\n\tQueue,\n\tRef,\n\ttype Scope,\n\tStream,\n\ttype SubscriptionRef,\n} from 'effect';\n\nimport { appName, stackName } from '../substrate/brand.ts';\nimport type { Identity } from '../substrate/identity.ts';\nimport type { EngineCommand, EngineEvent } from '../substrate/events.ts';\nimport { makeProjectionRefSync } from '../substrate/runtime/index.ts';\nimport type { StackPathsService } from '../substrate/runtime/paths.ts';\nimport type { SnapshotOrchestratorService } from '../orchestrators/snapshot/index.ts';\nimport type {\n\tSupervisorCommandHandler,\n\tSupervisorHandle,\n} from '../substrate/runtime/supervisor/index.ts';\nimport {\n\tbuildSubstrateLayers,\n\tlayerProductionOrchestrators,\n\tresolveProductionCodegenOptions,\n\tsuperviseStackWithProductionBoot,\n} from '../orchestrators/boot.ts';\nimport { readStackEngine, type Stack } from './define-devstack.ts';\nimport type { AnyPlugin } from '../substrate/plugin.ts';\nimport {\n\tresolveAppName,\n\tresolveNetworkSync,\n\tresolveStackName,\n\tresolveStateDir,\n} from './inference-network.ts';\nimport type {\n\tBootError,\n\tRunHandle,\n\tRunStackIdentityOptions,\n\tRunStackOptions,\n} from './run-stack.ts';\n\n// -----------------------------------------------------------------------------\n// Internal handle + boot bag\n// -----------------------------------------------------------------------------\n\n/** The handle the caller-injected `boot` hooks receive. It is the public\n * `RunHandle` PLUS the live `SupervisorHandle` — the substrate seam the\n * CLI `up` hooks drain (roster lock keys off it, the IPC bridge tails\n * `supervisor.events` / pumps `supervisor.commands`, the TUI mounts\n * `supervisor.state`). NON-PUBLIC: `index.ts` never re-exports it, so the\n * `SupervisorHandle` substrate type stays off the public surface. */\nexport interface InternalRunHandle extends RunHandle {\n\t/** The live supervisor handle for this boot. Available inside both\n\t * composed hooks; carries `events`/`commands`/`runCommand`/`state`/\n\t * `graph`/`registry` the CLI hooks consume directly. */\n\treadonly supervisor: SupervisorHandle;\n}\n\n/** The substrate services the seam already has in scope when it runs the\n * caller hooks (the composed `beforeInitialAcquire`/`withinScope` run\n * inside `superviseStackEffect`, which is `Effect.provide(substrate)`).\n * The bag hooks may yield these directly — the CLI `up` hooks drive the\n * ONE `SnapshotOrchestratorService` instance the supervisor's contribution\n * dispatcher registers participants on (so an operator `snapshot save`\n * captures the LIVE participant set, not an empty one off a sibling\n * orchestrator), plus `FileSystem`/`StackPathsService` for the\n * interrupted-restore + roster paths. `Scope.Scope` lets a hook fork\n * scoped fibers (the IPC pump, the TUI mount) onto the supervised scope.\n * NON-PUBLIC: like `InternalRunHandle`, this never reaches the public\n * `runStack` facade. */\nexport type BootHookServices =\n\t| Scope.Scope\n\t| SnapshotOrchestratorService\n\t| FileSystem.FileSystem\n\t| StackPathsService;\n\n/** Caller-injected boot hooks. Both run AFTER their built-in counterpart\n * (see the PR#21 ordering note at the top of this file). Their failures\n * fold into `BootError.cause` via the same `catchCause` tee the built-in\n * work uses, so `handle.start` stays `Effect<void, BootError, never>` —\n * the bag does NOT widen the public error channel. The R-channel is\n * `BootHookServices` (substrate services the seam already provides), NOT\n * `never`: the hooks run inside the supervised scope and may drive the\n * seam's live substrate (notably the supervisor's snapshot orchestrator). */\nexport interface RunStackBootBag {\n\treadonly devstackVersion?: string;\n\treadonly beforeInitialAcquire?: (\n\t\thandle: InternalRunHandle,\n\t) => Effect.Effect<void, unknown, BootHookServices>;\n\treadonly withinScope?: (\n\t\thandle: InternalRunHandle,\n\t) => Effect.Effect<void, unknown, BootHookServices>;\n}\n\n/** The substrate services a {@link CommandHandlerFactory} may yield — the\n * same singletons the contribution dispatcher + boot hooks see, minus\n * `Scope.Scope` (the factory runs before the supervised scope opens, so it\n * forks nothing). A handler closes over `SnapshotOrchestratorService` to\n * capture the LIVE participant set and `FileSystem` to thread it into the\n * snapshot I/O. */\ntype CommandHandlerServices =\n\t| SnapshotOrchestratorService\n\t| FileSystem.FileSystem\n\t| StackPathsService;\n\n/** A FACTORY for the supervisor command handler. The seam runs it ONCE,\n * inside the supervised body (so `CommandHandlerServices` — notably the live\n * `SnapshotOrchestratorService` + `FileSystem` — are in scope), BEFORE it\n * hands `startSupervisor` the resolved handler. So the resulting handler\n * (whose own R-channel is `never`, as the supervisor command loop requires)\n * closes over the seam's REAL substrate instances directly — no Deferred\n * hand-off. Resolving the snapshot orchestrator here yields the SAME\n * instance the contribution dispatcher registers live participants on, so an\n * operator `snapshot save` captures the LIVE chain/blob/db state, not an\n * empty set off a sibling orchestrator. */\nexport type CommandHandlerFactory = Effect.Effect<\n\tSupervisorCommandHandler,\n\tnever,\n\tCommandHandlerServices\n>;\n\n/** `RunStackOptions` plus the non-public injection points. `commandHandler`\n * is a FACTORY the seam resolves against its live substrate (see\n * {@link CommandHandlerFactory}); `boot` carries the caller-injected hooks. */\nexport interface RunStackOptionsWithBoot extends RunStackOptions {\n\treadonly commandHandler?: CommandHandlerFactory;\n\treadonly boot?: RunStackBootBag;\n}\n\n// -----------------------------------------------------------------------------\n// Identity resolution\n// -----------------------------------------------------------------------------\n\nconst resolveIdentity = (\n\tstack: Stack<ReadonlyArray<AnyPlugin>>,\n\topts: RunStackIdentityOptions | undefined,\n\tcwd: string,\n): Identity => {\n\tconst app = resolveAppName({\n\t\texplicit: opts?.app,\n\t\tcwd,\n\t});\n\tconst stackNameStr = resolveStackName({\n\t\texplicit: opts?.stack ?? stack.options.stackName,\n\t\tcwd,\n\t});\n\t// Parse + validate up-front so a malformed value fails here rather\n\t// than downstream when a plugin probes the network. The identity's\n\t// network is the canonical name (`localnet`, `testnet`, …) — never the\n\t// `sui:`-prefixed wallet-standard form, which devstack does not use\n\t// internally — so plugin checks and on-disk cache namespaces key on one\n\t// stable name regardless of how it was spelled on the CLI.\n\tconst resolved = resolveNetworkSync({\n\t\texplicit: opts?.network,\n\t\tenv: process.env.DEVSTACK_NETWORK,\n\t\texplicitSource: 'runStack({ identity.network })',\n\t});\n\treturn {\n\t\tapp: appName(app),\n\t\tstack: stackName(stackNameStr),\n\t\tnetwork: resolved.parsed.name,\n\t};\n};\n\nconst toBootError = (cause: Cause.Cause<unknown>): BootError => ({\n\t_tag: 'BootError',\n\tcause,\n});\n\n// -----------------------------------------------------------------------------\n// Boot-time handle slots\n// -----------------------------------------------------------------------------\n\n/** The mutable cells + ref the handle's lifecycle effects close over. Every\n * field is allocated SYNCHRONOUSLY in `makeRunHandleSlots` (see its\n * sync-safety contract) so the public handle can expose a live `state` /\n * `commands` queue and let callers subscribe to `state.changes` BEFORE\n * `start` ever forks the supervisor fiber. */\ninterface RunHandleSlots {\n\t/** The supervisor's live projection. Seeded sync; populated by the\n\t * supervised body once it boots. */\n\treadonly state: SubscriptionRef.SubscriptionRef<\n\t\timport('../substrate/projection.ts').SubscribableState\n\t>;\n\t/** Resolved (or failed with `BootError`) by the readiness gate; `start`\n\t * awaits it. */\n\treadonly bootDeferred: Deferred.Deferred<void, BootError>;\n\t/** Set by `stop`; the built-in stop-bridge awaits it and offers\n\t * `shutdown.requested` onto the supervisor's command channel. */\n\treadonly stopRequested: Deferred.Deferred<void>;\n\t/** Handed the supervisor's event dequeue by `beforeInitialAcquire`; the\n\t * public `events` stream unwraps it. */\n\treadonly eventQueueRef: Deferred.Deferred<Queue.Dequeue<EngineEvent>>;\n\t/** Public command channel, pumped onto the supervisor's own queue once it\n\t * boots — the TUI/IPC publish target. */\n\treadonly commandQueue: Queue.Queue<EngineCommand>;\n\t/** Handed the supervisor's submit-and-await dispatch by\n\t * `beforeInitialAcquire`; the public `runCommand` awaits it. */\n\treadonly runCommandRef: Deferred.Deferred<\n\t\t(command: EngineCommand) => Effect.Effect<void, unknown, never>\n\t>;\n\t/** Holds the forked supervisor fiber; `stop`/`awaitShutdown` await it. */\n\treadonly fiberRef: Deferred.Deferred<Fiber.Fiber<void, never>>;\n\t/** Single-fire guard so a second `start` is a no-op. */\n\treadonly startClaim: Ref.Ref<boolean>;\n\t/** Tee for a mid-run defect/failure raised AFTER boot completed, so\n\t * `awaitShutdown` can re-surface it (a post-boot `Deferred.fail` is a\n\t * no-op). */\n\treadonly midRunCauseRef: Ref.Ref<Cause.Cause<unknown> | null>;\n}\n\n/**\n * Allocate every boot-time handle slot in ONE place.\n *\n * SYNC-SAFETY CONTRACT (the reason this is a sync constructor, not an\n * `Effect`): the handle must expose a real `state` / `commands` queue and\n * be subscribable BEFORE `start` forks the supervisor. `Deferred.make`,\n * `Queue.unbounded`, and `Ref.make` are pure sync effects (no side effects,\n * no async), so `Effect.runSync` is safe for all of them. The projection ref\n * goes through the explicit `makeProjectionRefSync` so the sync contract is\n * pinned at the substrate constructor — if `makeProjectionRef` ever picks up\n * an async/Layer wrapper (`withSpan`, annotation), `makeProjectionRefSync`\n * must stay sync-only or this whole allocation must move behind a\n * Deferred-handoff seam.\n */\nconst makeRunHandleSlots = (): RunHandleSlots => ({\n\tstate: makeProjectionRefSync(),\n\tbootDeferred: Effect.runSync(Deferred.make<void, BootError>()),\n\tstopRequested: Effect.runSync(Deferred.make<void>()),\n\teventQueueRef: Effect.runSync(Deferred.make<Queue.Dequeue<EngineEvent>>()),\n\tcommandQueue: Effect.runSync(Queue.unbounded<EngineCommand>()),\n\trunCommandRef: Effect.runSync(\n\t\tDeferred.make<(command: EngineCommand) => Effect.Effect<void, unknown, never>>(),\n\t),\n\tfiberRef: Effect.runSync(Deferred.make<Fiber.Fiber<void, never>>()),\n\tstartClaim: Effect.runSync(Ref.make(false)),\n\tmidRunCauseRef: Effect.runSync(Ref.make<Cause.Cause<unknown> | null>(null)),\n});\n\n/** Compose the public handle + the live `SupervisorHandle` into the\n * `InternalRunHandle` the caller boot hooks receive. The two composed hooks\n * build it identically; factored here so the closure-soundness note (the\n * hooks only deref `publicHandle` at boot, after the body assigns it) lives\n * in one place. */\nconst buildInternalHandle = (\n\tpublicHandle: RunHandle,\n\tsupervisor: SupervisorHandle,\n): InternalRunHandle => ({ ...publicHandle, supervisor });\n\n// -----------------------------------------------------------------------------\n// runStackWithBoot\n// -----------------------------------------------------------------------------\n\n/**\n * The implementation behind `runStack`. Boots a `Stack` for programmatic\n * embedding and returns a `RunHandle` synchronously; the supervisor fiber\n * is forked on `start`.\n *\n * Lifecycle (identical to `runStack`'s documented contract):\n *\n * 1. `runStackWithBoot(stack, opts)` — synchronous; no fiber forked yet.\n * 2. `await Effect.runPromise(handle.start)` — forks the supervisor,\n * blocks until every plugin reaches `ready` (or fails with\n * `BootError`).\n * 3. Use `handle.events` / `handle.state` to observe.\n * 4. `await Effect.runPromise(handle.stop)` — graceful shutdown.\n * 5. `handle.awaitShutdown` resolves once finalizers complete.\n *\n * Internal architecture: the handle stores a `Deferred` for boot\n * completion. The supervised body resolves it from the SUPERVISOR-OWNED\n * readiness signal — `superviseStackEffect`'s `withinScope` hook fires\n * only after `runInitialAcquire` wins its `raceFirst` against\n * `awaitShutdown` (i.e. once `allReadyOrTerminal` — `ready || done` — is\n * true). This is the same `done`-tolerant gate the long-running CLI path\n * uses; it does NOT watch per-node ready-gates (a non-failed terminal\n * `done` node need never resolve its `awaitReady` gate). Initial-acquire\n * failures surface through the supervised body's `catchCause` tee, which\n * fails the deferred with a `BootError`. `start` awaits it.\n *\n * `opts.commandHandler` threads straight into `superviseStackEffect`'s\n * `commandHandler` slot. `opts.boot` injects caller hooks that run AFTER\n * their built-in counterpart (the PR#21 ordering); their failures fold\n * into `BootError` via the same `catchCause` tee, so `start` keeps its\n * `Effect<void, BootError, never>` type.\n */\nexport const runStackWithBoot = (\n\tstack: Stack<ReadonlyArray<AnyPlugin>>,\n\topts: RunStackOptionsWithBoot = {},\n): RunHandle => {\n\tconst engineStack = readStackEngine(stack);\n\tconst runtimeRoot = resolveStateDir({\n\t\truntimeRoot: opts.runtimeRoot,\n\t\tstateDir: opts.stateDir ?? engineStack.options.stateDir,\n\t\tenv: process.env.DEVSTACK_STATE_DIR,\n\t\tcwd: process.cwd(),\n\t});\n\tconst appRoot = opts.appRoot ?? process.cwd();\n\tconst identity = resolveIdentity(stack, opts.identity, appRoot);\n\tconst codegen = opts.codegen ?? engineStack.options.codegen;\n\n\tconst supervisedStack = {\n\t\t_tag: 'Stack' as const,\n\t\tmembers: engineStack.members,\n\t\toptions: engineStack.options,\n\t};\n\n\t// Every boot-time handle slot is allocated in ONE place (the sync-safety\n\t// contract lives on `makeRunHandleSlots`): the handle must expose a live\n\t// `state` / `commands` queue and be subscribable BEFORE `start` forks the\n\t// supervisor fiber.\n\tconst {\n\t\tstate,\n\t\tbootDeferred,\n\t\tstopRequested,\n\t\teventQueueRef,\n\t\tcommandQueue,\n\t\trunCommandRef,\n\t\tfiberRef,\n\t\tstartClaim,\n\t\tmidRunCauseRef,\n\t} = makeRunHandleSlots();\n\n\t// Resolve the per-stack LIVE codegen output location through the ONE\n\t// shared boot seam: EVERY live run emits into\n\t// `.devstack/stacks/<stack>/generated/` (gitignored), so the id-bearing\n\t// tree never lands in committed source. The committed `src/generated`\n\t// tree is owned solely by the stack-free `codegen` verb. An explicit\n\t// `opts.codegen` (or the stack's own `codegen`) is honored verbatim by\n\t// the resolver. The effective stack (the resolved `identity.stack`) is\n\t// in scope here, exactly as in the CLI's `buildVerbLayers` seam — both\n\t// route through `resolveProductionCodegenOptions`.\n\tconst substrate = layerProductionOrchestrators({\n\t\tcodegen: resolveProductionCodegenOptions({\n\t\t\tappRoot,\n\t\t\teffectiveStack: String(identity.stack),\n\t\t\tcodegen,\n\t\t}),\n\t}).pipe(Layer.provideMerge(buildSubstrateLayers(identity, runtimeRoot)));\n\n\t// The submit-and-await dispatch surfaced on the public handle. Awaits\n\t// the supervisor's `runCommand` (wired in the built-in\n\t// `beforeInitialAcquire`), then delegates.\n\tconst runCommand = (command: EngineCommand): Effect.Effect<void, unknown, never> =>\n\t\tEffect.flatMap(Deferred.await(runCommandRef), (dispatch) => dispatch(command));\n\n\tconst events: Stream.Stream<EngineEvent, never, never> = Stream.unwrap(\n\t\tEffect.gen(function* () {\n\t\t\tconst queue = yield* Deferred.await(eventQueueRef);\n\t\t\treturn Stream.fromQueue(queue);\n\t\t}),\n\t);\n\n\t// The PUBLIC handle is assigned exactly once at the end of this body\n\t// (after the lifecycle effects exist) and returned. The composed boot\n\t// hooks below close over `publicHandle` to build an `InternalRunHandle`\n\t// (the public handle + the live `SupervisorHandle`), but they only\n\t// deref it at BOOT time — long after this synchronous body finishes and\n\t// assigns it — so the closure is sound. Declared with a forward `let`\n\t// rather than mutated readonly fields so `RunHandle` stays immutable.\n\tlet publicHandle: RunHandle;\n\n\tconst supervised = Effect.gen(function* () {\n\t\t// Resolve the command-handler FACTORY against the seam's live\n\t\t// substrate (in scope here via `Effect.provide(substrate)` below)\n\t\t// BEFORE `startSupervisor` consumes the handler. The factory closes\n\t\t// over the same `SnapshotOrchestratorService` the contribution\n\t\t// dispatcher registers participants on, so the resulting handler\n\t\t// (R = `never`, as the command loop requires) drives the LIVE state.\n\t\tconst commandHandler =\n\t\t\topts.commandHandler === undefined ? undefined : yield* opts.commandHandler;\n\t\t// The shared production-boot assembly (contribution dispatcher +\n\t\t// post-acquire hook + built-in plugin-context extension, then\n\t\t// `layerBuiltInPluginRuntime`) lives in ONE place\n\t\t// (`orchestrators/boot.ts superviseStackWithProductionBoot`). This\n\t\t// seam supplies only the LONG-RUNNING-specific wrapping: the\n\t\t// resolved command handler, the public `runStack({ extendContext })`\n\t\t// chained after the built-in, and the composed boot hooks below.\n\t\tyield* superviseStackWithProductionBoot(supervisedStack, identity, state, {\n\t\t\textras: stack.options.extras,\n\t\t\tnetworkOptions: stack.options.networkOptions,\n\t\t\t...(opts.boot?.devstackVersion === undefined\n\t\t\t\t? {}\n\t\t\t\t: { devstackVersion: opts.boot.devstackVersion }),\n\t\t\t...(commandHandler === undefined ? {} : { commandHandler }),\n\t\t\t...(opts.extendContext === undefined\n\t\t\t\t? {}\n\t\t\t\t: { extendContextAfterBuiltIn: opts.extendContext }),\n\t\t\t// ── COMPOSED `beforeInitialAcquire` (ONE ordered gen) ──────────\n\t\t\t// ORDER (PR#21-load-bearing): BUILT-IN work first, THEN caller.\n\t\t\t// 1. built-in: event-queue handoff + runCommand surface +\n\t\t\t// command-pump fork + stop-bridge fork.\n\t\t\t// 2. caller: `opts.boot?.beforeInitialAcquire(...)`.\n\t\t\t// `superviseStackEffect` runs this whole composed effect BEFORE\n\t\t\t// `runInitialAcquire`, so the caller hook (the CLI's recover/\n\t\t\t// roster/IPC/TUI bundle) runs before first acquire too; and because\n\t\t\t// the built-in stop-bridge is armed first, a `stop()` raised during\n\t\t\t// a caller hook always has a command bridge.\n\t\t\tbeforeInitialAcquire: (handle) =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tyield* Deferred.succeed(eventQueueRef, handle.events).pipe(\n\t\t\t\t\t\tEffect.catch(() => Effect.void),\n\t\t\t\t\t);\n\t\t\t\t\t// Surface the supervisor's submit-and-await dispatch onto the\n\t\t\t\t\t// public handle BEFORE acquire so a boot failure can't leave\n\t\t\t\t\t// the handle without a command bridge.\n\t\t\t\t\tyield* Deferred.succeed(runCommandRef, handle.runCommand).pipe(\n\t\t\t\t\t\tEffect.catch(() => Effect.void),\n\t\t\t\t\t);\n\t\t\t\t\t// Pump the eager public command queue onto the supervisor's\n\t\t\t\t\t// own command queue (`handle.commands` — the seam the signal\n\t\t\t\t\t// handler + command loop drain). A publish on the public\n\t\t\t\t\t// queue therefore always reaches the supervisor's drain.\n\t\t\t\t\tyield* Effect.forkScoped(\n\t\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\t\twhile (true) {\n\t\t\t\t\t\t\t\tconst command = yield* Queue.take(commandQueue);\n\t\t\t\t\t\t\t\tyield* Queue.offer(handle.commands, command);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\n\t\t\t\t\t// Bridge `stop` requests onto the supervisor's command\n\t\t\t\t\t// channel before acquire starts so a boot failure cannot\n\t\t\t\t\t// leave the public handle without an event/command bridge.\n\t\t\t\t\tyield* Effect.forkScoped(\n\t\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\t\tyield* Deferred.await(stopRequested);\n\t\t\t\t\t\t\tyield* Queue.offer(handle.commands, {\n\t\t\t\t\t\t\t\ttag: 'shutdown.requested',\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\n\t\t\t\t\t// Caller-injected work runs AFTER the built-in bridges are\n\t\t\t\t\t// armed. Its failures fold into the supervised body's\n\t\t\t\t\t// `catchCause` below (and thence into `BootError`).\n\t\t\t\t\tif (opts.boot?.beforeInitialAcquire !== undefined) {\n\t\t\t\t\t\tyield* opts.boot.beforeInitialAcquire(buildInternalHandle(publicHandle, handle));\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t\t// ── COMPOSED `withinScope` (ONE ordered gen) ───────────────────\n\t\t\t// ORDER: BUILT-IN readiness-gate resolution first, THEN caller.\n\t\t\t// 1. built-in: the status scan that resolves `bootDeferred`.\n\t\t\t// 2. caller: `opts.boot?.withinScope(...)`.\n\t\t\t// Resolving the gate first means a caller `withinScope` can never\n\t\t\t// delay `handle.start`.\n\t\t\twithinScope: (handle) =>\n\t\t\t\t// Resolve the boot gate from the SUPERVISOR-OWNED readiness\n\t\t\t\t// signal. `superviseStackEffect` only calls `withinScope`\n\t\t\t\t// after `runInitialAcquire` wins its `raceFirst` against\n\t\t\t\t// `awaitShutdown` and yields `'booted'` (boot.ts) — i.e. once\n\t\t\t\t// `allReadyOrTerminal` is true (`ready || done`). This is the\n\t\t\t\t// SAME `done`-tolerant gate the long-running CLI path uses,\n\t\t\t\t// not a per-node `awaitReady` watcher (whose ready-gate a\n\t\t\t\t// non-failed terminal `done` node need never resolve, so it\n\t\t\t\t// would hang here).\n\t\t\t\t//\n\t\t\t\t// `runInitialAcquire` does NOT itself fail on a per-PLUGIN\n\t\t\t\t// acquire failure: `acquireFullGraph` swallows node failures\n\t\t\t\t// into the registry's `failed` status (the CLI surfaces them as\n\t\t\t\t// red TUI rows and keeps the stack up). runStack's contract is\n\t\t\t\t// stricter — `start` FAILS with `BootError` if any node failed\n\t\t\t\t// initial acquire. So gate on the STATUS contract here: succeed\n\t\t\t\t// iff every node is `ready`/`done`, else fail with the first\n\t\t\t\t// `failed` node's `PluginAcquireFailed` cause. A failed node's\n\t\t\t\t// ready-gate IS resolved (markFailed failed it), so reading it\n\t\t\t\t// is hang-free — the `done`-tolerant equivalent of the old\n\t\t\t\t// watcher's failure detection. (Post-acquire HOOK failures —\n\t\t\t\t// codegen / manifest — fail `runInitialAcquire` directly and\n\t\t\t\t// tee through the supervised body's `catchCause` below; we\n\t\t\t\t// never reach here on that path.)\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tlet firstFailure: BootError | null = null;\n\t\t\t\t\tfor (const [key] of handle.graph.nodes) {\n\t\t\t\t\t\tconst status = yield* handle.registry\n\t\t\t\t\t\t\t.getStatus(key)\n\t\t\t\t\t\t\t.pipe(Effect.catch(() => Effect.succeed('failed' as const)));\n\t\t\t\t\t\tif (status === 'failed') {\n\t\t\t\t\t\t\tconst failureExit = yield* Effect.exit(handle.registry.awaitReady(key));\n\t\t\t\t\t\t\tfirstFailure = toBootError(\n\t\t\t\t\t\t\t\tExit.isFailure(failureExit) ? failureExit.cause : Cause.empty,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (firstFailure === null) {\n\t\t\t\t\t\tyield* Deferred.succeed(bootDeferred, undefined).pipe(Effect.asVoid);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tyield* Deferred.fail(bootDeferred, firstFailure).pipe(Effect.asVoid);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Caller-injected work runs AFTER the boot gate is resolved,\n\t\t\t\t\t// so it can never delay `handle.start`. Its failures fold\n\t\t\t\t\t// into the supervised body's `catchCause` below; since the\n\t\t\t\t\t// boot gate already succeeded by this point, such a failure\n\t\t\t\t\t// surfaces on `awaitShutdown` (mid-run), not `start` — the\n\t\t\t\t\t// same place a built-in `withinScope` failure would land.\n\t\t\t\t\tif (opts.boot?.withinScope !== undefined) {\n\t\t\t\t\t\tyield* opts.boot.withinScope(buildInternalHandle(publicHandle, handle));\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t});\n\t});\n\n\tconst loggerLayer = Logger.layer([]);\n\tconst layered = supervised.pipe(Effect.provide(substrate), Effect.provide(loggerLayer));\n\n\t// Convert any uncaught cause into a boot failure if the deferred\n\t// hasn't completed. If `bootDeferred` HAS already succeeded then\n\t// `Deferred.fail` is a no-op — the cause is a mid-run failure\n\t// (e.g. a plugin scope finalizer defect) and would otherwise be\n\t// silently dropped: the fiber exits `Success(void)` and\n\t// `awaitShutdown` resolves clean. Tee the cause into\n\t// `midRunCauseRef` so `awaitShutdown` can re-surface it, AND emit\n\t// `Effect.logError(Cause.pretty(cause))` so observability stays\n\t// loud regardless of whether anyone awaits. Boot failures (still in\n\t// the bootDeferred-pending window) only surface via `start`, not\n\t// here — we'd otherwise re-raise them on `awaitShutdown` after\n\t// `start` already rejected. NOTE: a CALLER `boot` hook failure folds\n\t// through this SAME tee, so its failure surfaces as `BootError` on\n\t// `start` (when raised before the gate resolves) or re-raises on\n\t// `awaitShutdown` (after) — never widening `start`'s error channel.\n\tconst supervisedProgram: Effect.Effect<void, never, never> = layered.pipe(\n\t\tEffect.catchCause((cause) =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst bootAlreadyCompleted = yield* Deferred.isDone(bootDeferred);\n\t\t\t\tyield* Deferred.fail(bootDeferred, toBootError(cause)).pipe(\n\t\t\t\t\tEffect.asVoid,\n\t\t\t\t\tEffect.catch(() => Effect.void),\n\t\t\t\t);\n\t\t\t\tif (bootAlreadyCompleted) {\n\t\t\t\t\tyield* Ref.set(midRunCauseRef, cause);\n\t\t\t\t}\n\t\t\t\tyield* Effect.logError(`devstack runStack: supervisor died\\n${Cause.pretty(cause)}`);\n\t\t\t}),\n\t\t),\n\t);\n\n\tconst start: Effect.Effect<void, BootError, never> = Effect.uninterruptibleMask((restore) =>\n\t\tEffect.gen(function* () {\n\t\t\tconst shouldStart = yield* Ref.modify(startClaim, (started) =>\n\t\t\t\tstarted ? [false, true] : [true, true],\n\t\t\t);\n\t\t\tif (shouldStart) {\n\t\t\t\t// `forkDetach` decouples the supervisor fiber from the `start`\n\t\t\t\t// fiber's scope. `forkChild`\n\t\t\t\t// would tie the supervisor to whatever fiber runs `start`, and\n\t\t\t\t// once `start` resolves (after `bootDeferred` succeeds) the\n\t\t\t\t// runtime would interrupt the supervisor — transitioning every\n\t\t\t\t// plugin `ready → stopping → stopped` before the caller can\n\t\t\t\t// read post-ready state or call `handle.stop`. The handle's\n\t\t\t\t// explicit `stop` + `awaitShutdown` paths are the only\n\t\t\t\t// shutdown signals; the captured `Fiber` reference is how the\n\t\t\t\t// daemon stays releasable.\n\t\t\t\tconst fiber = yield* Effect.forkDetach(supervisedProgram);\n\t\t\t\tyield* Deferred.succeed(fiberRef, fiber);\n\t\t\t}\n\t\t\tyield* restore(Deferred.await(bootDeferred));\n\t\t}),\n\t);\n\n\tconst stop: Effect.Effect<void, never, never> = Effect.gen(function* () {\n\t\tyield* Deferred.succeed(stopRequested, undefined).pipe(Effect.catch(() => Effect.void));\n\t\tconst alreadyStarted = yield* Deferred.isDone(fiberRef);\n\t\tif (!alreadyStarted) {\n\t\t\treturn;\n\t\t}\n\t\tconst fiber = yield* Deferred.await(fiberRef);\n\t\t// `Fiber.await` returns an `Exit` without raising — handles both\n\t\t// success and interrupt-cause cases (the supervisor's\n\t\t// graceful-shutdown path closes the scope, which surfaces as an\n\t\t// interrupt cause if the fiber was mid-await on the latch poll).\n\t\tyield* Fiber.await(fiber);\n\t});\n\n\tconst awaitShutdown: Effect.Effect<void, unknown, never> = Effect.gen(function* () {\n\t\tconst alreadyStarted = yield* Deferred.isDone(fiberRef);\n\t\tif (!alreadyStarted) {\n\t\t\treturn;\n\t\t}\n\t\tconst fiber = yield* Deferred.await(fiberRef);\n\t\tyield* Fiber.await(fiber);\n\t\t// Re-surface any mid-run defect/failure captured by the\n\t\t// supervised body's `catchCause` (boot failures are already\n\t\t// surfaced via `start`). Without this re-raise a plugin scope\n\t\t// finalizer defect would silently drop and operators get no\n\t\t// signal — see the comment on `midRunCauseRef` above.\n\t\tconst midRunCause = yield* Ref.get(midRunCauseRef);\n\t\tif (midRunCause !== null) {\n\t\t\treturn yield* Effect.failCause(midRunCause);\n\t\t}\n\t});\n\n\t// Assign the handle the composed boot hooks close over (they only deref\n\t// it at boot, after this assignment) and return it.\n\tpublicHandle = {\n\t\tstart,\n\t\tstop,\n\t\tawaitShutdown,\n\t\tevents,\n\t\tstate,\n\t\tcommands: commandQueue,\n\t\trunCommand,\n\t\tidentity,\n\t};\n\n\treturn publicHandle;\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"run-stack-internal.mjs","names":[],"sources":["../../src/api/run-stack-internal.ts"],"sourcesContent":["// `runStackWithBoot(stack, optsWithBoot)` — the NON-PUBLIC seam under\n// `runStack`.\n//\n// `api/run-stack.ts` is the thin PUBLIC facade: `runStack` delegates here\n// with a zero-`boot` bag, so the public surface (`RunHandle`,\n// `RunStackOptions`, `BootError`) and the zero-`boot` path stay\n// byte-identical. This module owns the supervised-body construction and\n// the caller-injectable boot hook seam the CLI `up` verb routes through.\n//\n// Why a seam at all: the CLI `up` verb needs the SAME substrate Layer\n// composition + supervised body `runStack` builds, but wrapped with extra\n// `beforeInitialAcquire`/`withinScope` work (interrupted-restore recovery,\n// the roster lock, the command-channel IPC bridge, and the TUI surface).\n// `runStackWithBoot` threads those as CALLER-INJECTED hooks so\n// the CLI consumes ONE boot core instead of forking it. Because the bag is\n// non-public, its hooks receive an `InternalRunHandle` that additionally\n// carries the live `SupervisorHandle` (the seam the CLI hooks drain) —\n// substrate detail that NEVER reaches the public `RunHandle`.\n//\n// PR#21 boot-ordering invariant (do not reorder): the composed hooks run\n// the BUILT-IN work FIRST, then the caller's. For `beforeInitialAcquire`\n// this means the event-queue handoff + stop-bridge + command-pump are armed\n// before any caller hook runs (so a `stop()` during a caller hook always\n// has a bridge) AND before the first acquire (`superviseStackEffect` runs\n// the whole composed `beforeInitialAcquire` ahead of `runInitialAcquire`).\n// For `withinScope` the built-in readiness-gate resolution (the status\n// scan that resolves `bootDeferred`) runs first, so a caller `withinScope`\n// can never delay `handle.start`.\n\nimport {\n\tCause,\n\tDeferred,\n\tEffect,\n\tExit,\n\tFiber,\n\ttype FileSystem,\n\tLayer,\n\tLogger,\n\tQueue,\n\tRef,\n\ttype Scope,\n\tStream,\n\ttype SubscriptionRef,\n} from 'effect';\n\nimport { appName, stackName } from '../substrate/brand.ts';\nimport type { Identity } from '../substrate/identity.ts';\nimport type { EngineCommand, EngineEvent } from '../substrate/events.ts';\nimport { makeProjectionRefSync } from '../substrate/runtime/index.ts';\nimport type { StackPathsService } from '../substrate/runtime/paths.ts';\nimport type { SnapshotOrchestratorService } from '../orchestrators/snapshot/index.ts';\nimport type {\n\tSupervisorCommandHandler,\n\tSupervisorHandle,\n} from '../substrate/runtime/supervisor/index.ts';\nimport {\n\tbuildSubstrateLayers,\n\tlayerProductionOrchestrators,\n\tresolveProductionCodegenOptions,\n\tsuperviseStackWithProductionBoot,\n} from '../orchestrators/boot.ts';\nimport type { Codegenable } from '../orchestrators/codegen/service.ts';\nimport { readStackEngine, type Stack } from './define-devstack.ts';\nimport type { AnyPlugin } from '../substrate/plugin.ts';\nimport {\n\tresolveAppName,\n\tresolveNetworkSync,\n\tresolveStackName,\n\tresolveStateDir,\n} from './inference-network.ts';\nimport type {\n\tBootError,\n\tRunHandle,\n\tRunStackIdentityOptions,\n\tRunStackOptions,\n} from './run-stack.ts';\n\n// -----------------------------------------------------------------------------\n// Internal handle + boot bag\n// -----------------------------------------------------------------------------\n\n/** The handle the caller-injected `boot` hooks receive. It is the public\n * `RunHandle` PLUS the live `SupervisorHandle` — the substrate seam the\n * CLI `up` hooks drain (roster lock keys off it, the IPC bridge tails\n * `supervisor.events` / pumps `supervisor.commands`, the TUI mounts\n * `supervisor.state`). NON-PUBLIC: `index.ts` never re-exports it, so the\n * `SupervisorHandle` substrate type stays off the public surface. */\nexport interface InternalRunHandle extends RunHandle {\n\t/** The live supervisor handle for this boot. Available inside both\n\t * composed hooks; carries `events`/`commands`/`runCommand`/`state`/\n\t * `graph`/`registry` the CLI hooks consume directly. */\n\treadonly supervisor: SupervisorHandle;\n}\n\n/** The substrate services the seam already has in scope when it runs the\n * caller hooks (the composed `beforeInitialAcquire`/`withinScope` run\n * inside `superviseStackEffect`, which is `Effect.provide(substrate)`).\n * The bag hooks may yield these directly — the CLI `up` hooks drive the\n * ONE `SnapshotOrchestratorService` instance the supervisor's contribution\n * dispatcher registers participants on (so an operator `snapshot save`\n * captures the LIVE participant set, not an empty one off a sibling\n * orchestrator), plus `FileSystem`/`StackPathsService` for the\n * interrupted-restore + roster paths. `Scope.Scope` lets a hook fork\n * scoped fibers (the IPC pump, the TUI mount) onto the supervised scope.\n * NON-PUBLIC: like `InternalRunHandle`, this never reaches the public\n * `runStack` facade. */\nexport type BootHookServices =\n\t| Scope.Scope\n\t| SnapshotOrchestratorService\n\t| FileSystem.FileSystem\n\t| StackPathsService;\n\n/** Caller-injected boot hooks. Both run AFTER their built-in counterpart\n * (see the PR#21 ordering note at the top of this file). Their failures\n * fold into `BootError.cause` via the same `catchCause` tee the built-in\n * work uses, so `handle.start` stays `Effect<void, BootError, never>` —\n * the bag does NOT widen the public error channel. The R-channel is\n * `BootHookServices` (substrate services the seam already provides), NOT\n * `never`: the hooks run inside the supervised scope and may drive the\n * seam's live substrate (notably the supervisor's snapshot orchestrator). */\nexport interface RunStackBootBag {\n\treadonly devstackVersion?: string;\n\treadonly beforeInitialAcquire?: (\n\t\thandle: InternalRunHandle,\n\t) => Effect.Effect<void, unknown, BootHookServices>;\n\treadonly withinScope?: (\n\t\thandle: InternalRunHandle,\n\t) => Effect.Effect<void, unknown, BootHookServices>;\n}\n\n/** The substrate services a {@link CommandHandlerFactory} may yield — the\n * same singletons the contribution dispatcher + boot hooks see, minus\n * `Scope.Scope` (the factory runs before the supervised scope opens, so it\n * forks nothing). A handler closes over `SnapshotOrchestratorService` to\n * capture the LIVE participant set and `FileSystem` to thread it into the\n * snapshot I/O. */\ntype CommandHandlerServices =\n\t| SnapshotOrchestratorService\n\t| FileSystem.FileSystem\n\t| StackPathsService;\n\n/** A FACTORY for the supervisor command handler. The seam runs it ONCE,\n * inside the supervised body (so `CommandHandlerServices` — notably the live\n * `SnapshotOrchestratorService` + `FileSystem` — are in scope), BEFORE it\n * hands `startSupervisor` the resolved handler. So the resulting handler\n * (whose own R-channel is `never`, as the supervisor command loop requires)\n * closes over the seam's REAL substrate instances directly — no Deferred\n * hand-off. Resolving the snapshot orchestrator here yields the SAME\n * instance the contribution dispatcher registers live participants on, so an\n * operator `snapshot save` captures the LIVE chain/blob/db state, not an\n * empty set off a sibling orchestrator. */\nexport type CommandHandlerFactory = Effect.Effect<\n\tSupervisorCommandHandler,\n\tnever,\n\tCommandHandlerServices\n>;\n\n/** `RunStackOptions` plus the non-public injection points. `commandHandler`\n * is a FACTORY the seam resolves against its live substrate (see\n * {@link CommandHandlerFactory}); `boot` carries the caller-injected hooks. */\nexport interface RunStackOptionsWithBoot extends RunStackOptions {\n\treadonly commandHandler?: CommandHandlerFactory;\n\treadonly boot?: RunStackBootBag;\n\t/** Dev-`up` only: the STATIC (id-free) codegen contributions used to\n\t * regenerate the committed `src/generated` tree on every (re)acquire so a\n\t * Move-source edit refreshes the bindings WITHOUT baking live on-chain ids\n\t * into `config.ts`. The `up` verb sets it (`deriveContributions(...)`); the\n\t * public `runStack` facade never does (committed bindings are an input\n\t * there, not an output). */\n\treadonly emitBindings?: ReadonlyArray<Codegenable>;\n}\n\n// -----------------------------------------------------------------------------\n// Identity resolution\n// -----------------------------------------------------------------------------\n\nconst resolveIdentity = (\n\tstack: Stack<ReadonlyArray<AnyPlugin>>,\n\topts: RunStackIdentityOptions | undefined,\n\tcwd: string,\n): Identity => {\n\tconst app = resolveAppName({\n\t\texplicit: opts?.app,\n\t\tcwd,\n\t});\n\tconst stackNameStr = resolveStackName({\n\t\texplicit: opts?.stack ?? stack.options.stackName,\n\t\tcwd,\n\t});\n\t// Parse + validate up-front so a malformed value fails here rather\n\t// than downstream when a plugin probes the network. The identity's\n\t// network is the canonical name (`localnet`, `testnet`, …) — never the\n\t// `sui:`-prefixed wallet-standard form, which devstack does not use\n\t// internally — so plugin checks and on-disk cache namespaces key on one\n\t// stable name regardless of how it was spelled on the CLI.\n\tconst resolved = resolveNetworkSync({\n\t\texplicit: opts?.network,\n\t\tenv: process.env.DEVSTACK_NETWORK,\n\t\texplicitSource: 'runStack({ identity.network })',\n\t});\n\treturn {\n\t\tapp: appName(app),\n\t\tstack: stackName(stackNameStr),\n\t\tnetwork: resolved.parsed.name,\n\t};\n};\n\nconst toBootError = (cause: Cause.Cause<unknown>): BootError => ({\n\t_tag: 'BootError',\n\tcause,\n});\n\n// -----------------------------------------------------------------------------\n// Boot-time handle slots\n// -----------------------------------------------------------------------------\n\n/** The mutable cells + ref the handle's lifecycle effects close over. Every\n * field is allocated SYNCHRONOUSLY in `makeRunHandleSlots` (see its\n * sync-safety contract) so the public handle can expose a live `state` /\n * `commands` queue and let callers subscribe to `state.changes` BEFORE\n * `start` ever forks the supervisor fiber. */\ninterface RunHandleSlots {\n\t/** The supervisor's live projection. Seeded sync; populated by the\n\t * supervised body once it boots. */\n\treadonly state: SubscriptionRef.SubscriptionRef<\n\t\timport('../substrate/projection.ts').SubscribableState\n\t>;\n\t/** Resolved (or failed with `BootError`) by the readiness gate; `start`\n\t * awaits it. */\n\treadonly bootDeferred: Deferred.Deferred<void, BootError>;\n\t/** Set by `stop`; the built-in stop-bridge awaits it and offers\n\t * `shutdown.requested` onto the supervisor's command channel. */\n\treadonly stopRequested: Deferred.Deferred<void>;\n\t/** Handed the supervisor's event dequeue by `beforeInitialAcquire`; the\n\t * public `events` stream unwraps it. */\n\treadonly eventQueueRef: Deferred.Deferred<Queue.Dequeue<EngineEvent>>;\n\t/** Public command channel, pumped onto the supervisor's own queue once it\n\t * boots — the TUI/IPC publish target. */\n\treadonly commandQueue: Queue.Queue<EngineCommand>;\n\t/** Handed the supervisor's submit-and-await dispatch by\n\t * `beforeInitialAcquire`; the public `runCommand` awaits it. */\n\treadonly runCommandRef: Deferred.Deferred<\n\t\t(command: EngineCommand) => Effect.Effect<void, unknown, never>\n\t>;\n\t/** Holds the forked supervisor fiber; `stop`/`awaitShutdown` await it. */\n\treadonly fiberRef: Deferred.Deferred<Fiber.Fiber<void, never>>;\n\t/** Single-fire guard so a second `start` is a no-op. */\n\treadonly startClaim: Ref.Ref<boolean>;\n\t/** Tee for a mid-run defect/failure raised AFTER boot completed, so\n\t * `awaitShutdown` can re-surface it (a post-boot `Deferred.fail` is a\n\t * no-op). */\n\treadonly midRunCauseRef: Ref.Ref<Cause.Cause<unknown> | null>;\n}\n\n/**\n * Allocate every boot-time handle slot in ONE place.\n *\n * SYNC-SAFETY CONTRACT (the reason this is a sync constructor, not an\n * `Effect`): the handle must expose a real `state` / `commands` queue and\n * be subscribable BEFORE `start` forks the supervisor. `Deferred.make`,\n * `Queue.unbounded`, and `Ref.make` are pure sync effects (no side effects,\n * no async), so `Effect.runSync` is safe for all of them. The projection ref\n * goes through the explicit `makeProjectionRefSync` so the sync contract is\n * pinned at the substrate constructor — if `makeProjectionRef` ever picks up\n * an async/Layer wrapper (`withSpan`, annotation), `makeProjectionRefSync`\n * must stay sync-only or this whole allocation must move behind a\n * Deferred-handoff seam.\n */\nconst makeRunHandleSlots = (): RunHandleSlots => ({\n\tstate: makeProjectionRefSync(),\n\tbootDeferred: Effect.runSync(Deferred.make<void, BootError>()),\n\tstopRequested: Effect.runSync(Deferred.make<void>()),\n\teventQueueRef: Effect.runSync(Deferred.make<Queue.Dequeue<EngineEvent>>()),\n\tcommandQueue: Effect.runSync(Queue.unbounded<EngineCommand>()),\n\trunCommandRef: Effect.runSync(\n\t\tDeferred.make<(command: EngineCommand) => Effect.Effect<void, unknown, never>>(),\n\t),\n\tfiberRef: Effect.runSync(Deferred.make<Fiber.Fiber<void, never>>()),\n\tstartClaim: Effect.runSync(Ref.make(false)),\n\tmidRunCauseRef: Effect.runSync(Ref.make<Cause.Cause<unknown> | null>(null)),\n});\n\n/** Compose the public handle + the live `SupervisorHandle` into the\n * `InternalRunHandle` the caller boot hooks receive. The two composed hooks\n * build it identically; factored here so the closure-soundness note (the\n * hooks only deref `publicHandle` at boot, after the body assigns it) lives\n * in one place. */\nconst buildInternalHandle = (\n\tpublicHandle: RunHandle,\n\tsupervisor: SupervisorHandle,\n): InternalRunHandle => ({ ...publicHandle, supervisor });\n\n// -----------------------------------------------------------------------------\n// runStackWithBoot\n// -----------------------------------------------------------------------------\n\n/**\n * The implementation behind `runStack`. Boots a `Stack` for programmatic\n * embedding and returns a `RunHandle` synchronously; the supervisor fiber\n * is forked on `start`.\n *\n * Lifecycle (identical to `runStack`'s documented contract):\n *\n * 1. `runStackWithBoot(stack, opts)` — synchronous; no fiber forked yet.\n * 2. `await Effect.runPromise(handle.start)` — forks the supervisor,\n * blocks until every plugin reaches `ready` (or fails with\n * `BootError`).\n * 3. Use `handle.events` / `handle.state` to observe.\n * 4. `await Effect.runPromise(handle.stop)` — graceful shutdown.\n * 5. `handle.awaitShutdown` resolves once finalizers complete.\n *\n * Internal architecture: the handle stores a `Deferred` for boot\n * completion. The supervised body resolves it from the SUPERVISOR-OWNED\n * readiness signal — `superviseStackEffect`'s `withinScope` hook fires\n * only after `runInitialAcquire` wins its `raceFirst` against\n * `awaitShutdown` (i.e. once `allReadyOrTerminal` — `ready || done` — is\n * true). This is the same `done`-tolerant gate the long-running CLI path\n * uses; it does NOT watch per-node ready-gates (a non-failed terminal\n * `done` node need never resolve its `awaitReady` gate). Initial-acquire\n * failures surface through the supervised body's `catchCause` tee, which\n * fails the deferred with a `BootError`. `start` awaits it.\n *\n * `opts.commandHandler` threads straight into `superviseStackEffect`'s\n * `commandHandler` slot. `opts.boot` injects caller hooks that run AFTER\n * their built-in counterpart (the PR#21 ordering); their failures fold\n * into `BootError` via the same `catchCause` tee, so `start` keeps its\n * `Effect<void, BootError, never>` type.\n */\nexport const runStackWithBoot = (\n\tstack: Stack<ReadonlyArray<AnyPlugin>>,\n\topts: RunStackOptionsWithBoot = {},\n): RunHandle => {\n\tconst engineStack = readStackEngine(stack);\n\tconst runtimeRoot = resolveStateDir({\n\t\truntimeRoot: opts.runtimeRoot,\n\t\tstateDir: opts.stateDir ?? engineStack.options.stateDir,\n\t\tenv: process.env.DEVSTACK_STATE_DIR,\n\t\tcwd: process.cwd(),\n\t});\n\tconst appRoot = opts.appRoot ?? process.cwd();\n\tconst identity = resolveIdentity(stack, opts.identity, appRoot);\n\tconst codegen = opts.codegen ?? engineStack.options.codegen;\n\n\tconst supervisedStack = {\n\t\t_tag: 'Stack' as const,\n\t\tmembers: engineStack.members,\n\t\toptions: engineStack.options,\n\t};\n\n\t// Every boot-time handle slot is allocated in ONE place (the sync-safety\n\t// contract lives on `makeRunHandleSlots`): the handle must expose a live\n\t// `state` / `commands` queue and be subscribable BEFORE `start` forks the\n\t// supervisor fiber.\n\tconst {\n\t\tstate,\n\t\tbootDeferred,\n\t\tstopRequested,\n\t\teventQueueRef,\n\t\tcommandQueue,\n\t\trunCommandRef,\n\t\tfiberRef,\n\t\tstartClaim,\n\t\tmidRunCauseRef,\n\t} = makeRunHandleSlots();\n\n\t// Resolve the per-stack LIVE codegen output location through the ONE\n\t// shared boot seam: EVERY live run emits into\n\t// `.devstack/stacks/<stack>/generated/` (gitignored), so the id-bearing\n\t// tree never lands in committed source. The committed `src/generated`\n\t// tree is owned solely by the stack-free `codegen` verb. An explicit\n\t// `opts.codegen` (or the stack's own `codegen`) is honored verbatim by\n\t// the resolver. The effective stack (the resolved `identity.stack`) is\n\t// in scope here, exactly as in the CLI's `buildVerbLayers` seam — both\n\t// route through `resolveProductionCodegenOptions`.\n\tconst substrate = layerProductionOrchestrators({\n\t\tcodegen: resolveProductionCodegenOptions({\n\t\t\tappRoot,\n\t\t\teffectiveStack: String(identity.stack),\n\t\t\tcodegen,\n\t\t}),\n\t}).pipe(Layer.provideMerge(buildSubstrateLayers(identity, runtimeRoot)));\n\n\t// The submit-and-await dispatch surfaced on the public handle. Awaits\n\t// the supervisor's `runCommand` (wired in the built-in\n\t// `beforeInitialAcquire`), then delegates.\n\tconst runCommand = (command: EngineCommand): Effect.Effect<void, unknown, never> =>\n\t\tEffect.flatMap(Deferred.await(runCommandRef), (dispatch) => dispatch(command));\n\n\tconst events: Stream.Stream<EngineEvent, never, never> = Stream.unwrap(\n\t\tEffect.gen(function* () {\n\t\t\tconst queue = yield* Deferred.await(eventQueueRef);\n\t\t\treturn Stream.fromQueue(queue);\n\t\t}),\n\t);\n\n\t// The PUBLIC handle is assigned exactly once at the end of this body\n\t// (after the lifecycle effects exist) and returned. The composed boot\n\t// hooks below close over `publicHandle` to build an `InternalRunHandle`\n\t// (the public handle + the live `SupervisorHandle`), but they only\n\t// deref it at BOOT time — long after this synchronous body finishes and\n\t// assigns it — so the closure is sound. Declared with a forward `let`\n\t// rather than mutated readonly fields so `RunHandle` stays immutable.\n\tlet publicHandle: RunHandle;\n\n\tconst supervised = Effect.gen(function* () {\n\t\t// Resolve the command-handler FACTORY against the seam's live\n\t\t// substrate (in scope here via `Effect.provide(substrate)` below)\n\t\t// BEFORE `startSupervisor` consumes the handler. The factory closes\n\t\t// over the same `SnapshotOrchestratorService` the contribution\n\t\t// dispatcher registers participants on, so the resulting handler\n\t\t// (R = `never`, as the command loop requires) drives the LIVE state.\n\t\tconst commandHandler =\n\t\t\topts.commandHandler === undefined ? undefined : yield* opts.commandHandler;\n\t\t// The shared production-boot assembly (contribution dispatcher +\n\t\t// post-acquire hook + built-in plugin-context extension, then\n\t\t// `layerBuiltInPluginRuntime`) lives in ONE place\n\t\t// (`orchestrators/boot.ts superviseStackWithProductionBoot`). This\n\t\t// seam supplies only the LONG-RUNNING-specific wrapping: the\n\t\t// resolved command handler, the public `runStack({ extendContext })`\n\t\t// chained after the built-in, and the composed boot hooks below.\n\t\tyield* superviseStackWithProductionBoot(supervisedStack, identity, state, {\n\t\t\textras: stack.options.extras,\n\t\t\tnetworkOptions: stack.options.networkOptions,\n\t\t\t...(opts.emitBindings === undefined ? {} : { emitBindings: opts.emitBindings }),\n\t\t\t...(opts.boot?.devstackVersion === undefined\n\t\t\t\t? {}\n\t\t\t\t: { devstackVersion: opts.boot.devstackVersion }),\n\t\t\t...(commandHandler === undefined ? {} : { commandHandler }),\n\t\t\t...(opts.extendContext === undefined\n\t\t\t\t? {}\n\t\t\t\t: { extendContextAfterBuiltIn: opts.extendContext }),\n\t\t\t// ── COMPOSED `beforeInitialAcquire` (ONE ordered gen) ──────────\n\t\t\t// ORDER (PR#21-load-bearing): BUILT-IN work first, THEN caller.\n\t\t\t// 1. built-in: event-queue handoff + runCommand surface +\n\t\t\t// command-pump fork + stop-bridge fork.\n\t\t\t// 2. caller: `opts.boot?.beforeInitialAcquire(...)`.\n\t\t\t// `superviseStackEffect` runs this whole composed effect BEFORE\n\t\t\t// `runInitialAcquire`, so the caller hook (the CLI's recover/\n\t\t\t// roster/IPC/TUI bundle) runs before first acquire too; and because\n\t\t\t// the built-in stop-bridge is armed first, a `stop()` raised during\n\t\t\t// a caller hook always has a command bridge.\n\t\t\tbeforeInitialAcquire: (handle) =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tyield* Deferred.succeed(eventQueueRef, handle.events).pipe(\n\t\t\t\t\t\tEffect.catch(() => Effect.void),\n\t\t\t\t\t);\n\t\t\t\t\t// Surface the supervisor's submit-and-await dispatch onto the\n\t\t\t\t\t// public handle BEFORE acquire so a boot failure can't leave\n\t\t\t\t\t// the handle without a command bridge.\n\t\t\t\t\tyield* Deferred.succeed(runCommandRef, handle.runCommand).pipe(\n\t\t\t\t\t\tEffect.catch(() => Effect.void),\n\t\t\t\t\t);\n\t\t\t\t\t// Pump the eager public command queue onto the supervisor's\n\t\t\t\t\t// own command queue (`handle.commands` — the seam the signal\n\t\t\t\t\t// handler + command loop drain). A publish on the public\n\t\t\t\t\t// queue therefore always reaches the supervisor's drain.\n\t\t\t\t\tyield* Effect.forkScoped(\n\t\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\t\twhile (true) {\n\t\t\t\t\t\t\t\tconst command = yield* Queue.take(commandQueue);\n\t\t\t\t\t\t\t\tyield* Queue.offer(handle.commands, command);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\n\t\t\t\t\t// Bridge `stop` requests onto the supervisor's command\n\t\t\t\t\t// channel before acquire starts so a boot failure cannot\n\t\t\t\t\t// leave the public handle without an event/command bridge.\n\t\t\t\t\tyield* Effect.forkScoped(\n\t\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\t\tyield* Deferred.await(stopRequested);\n\t\t\t\t\t\t\tyield* Queue.offer(handle.commands, {\n\t\t\t\t\t\t\t\ttag: 'shutdown.requested',\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\n\t\t\t\t\t// Caller-injected work runs AFTER the built-in bridges are\n\t\t\t\t\t// armed. Its failures fold into the supervised body's\n\t\t\t\t\t// `catchCause` below (and thence into `BootError`).\n\t\t\t\t\tif (opts.boot?.beforeInitialAcquire !== undefined) {\n\t\t\t\t\t\tyield* opts.boot.beforeInitialAcquire(buildInternalHandle(publicHandle, handle));\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t\t// ── COMPOSED `withinScope` (ONE ordered gen) ───────────────────\n\t\t\t// ORDER: BUILT-IN readiness-gate resolution first, THEN caller.\n\t\t\t// 1. built-in: the status scan that resolves `bootDeferred`.\n\t\t\t// 2. caller: `opts.boot?.withinScope(...)`.\n\t\t\t// Resolving the gate first means a caller `withinScope` can never\n\t\t\t// delay `handle.start`.\n\t\t\twithinScope: (handle) =>\n\t\t\t\t// Resolve the boot gate from the SUPERVISOR-OWNED readiness\n\t\t\t\t// signal. `superviseStackEffect` only calls `withinScope`\n\t\t\t\t// after `runInitialAcquire` wins its `raceFirst` against\n\t\t\t\t// `awaitShutdown` and yields `'booted'` (boot.ts) — i.e. once\n\t\t\t\t// `allReadyOrTerminal` is true (`ready || done`). This is the\n\t\t\t\t// SAME `done`-tolerant gate the long-running CLI path uses,\n\t\t\t\t// not a per-node `awaitReady` watcher (whose ready-gate a\n\t\t\t\t// non-failed terminal `done` node need never resolve, so it\n\t\t\t\t// would hang here).\n\t\t\t\t//\n\t\t\t\t// `runInitialAcquire` does NOT itself fail on a per-PLUGIN\n\t\t\t\t// acquire failure: `acquireFullGraph` swallows node failures\n\t\t\t\t// into the registry's `failed` status (the CLI surfaces them as\n\t\t\t\t// red TUI rows and keeps the stack up). runStack's contract is\n\t\t\t\t// stricter — `start` FAILS with `BootError` if any node failed\n\t\t\t\t// initial acquire. So gate on the STATUS contract here: succeed\n\t\t\t\t// iff every node is `ready`/`done`, else fail with the first\n\t\t\t\t// `failed` node's `PluginAcquireFailed` cause. A failed node's\n\t\t\t\t// ready-gate IS resolved (markFailed failed it), so reading it\n\t\t\t\t// is hang-free — the `done`-tolerant equivalent of the old\n\t\t\t\t// watcher's failure detection. (Post-acquire HOOK failures —\n\t\t\t\t// codegen / manifest — fail `runInitialAcquire` directly and\n\t\t\t\t// tee through the supervised body's `catchCause` below; we\n\t\t\t\t// never reach here on that path.)\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tlet firstFailure: BootError | null = null;\n\t\t\t\t\tfor (const [key] of handle.graph.nodes) {\n\t\t\t\t\t\tconst status = yield* handle.registry\n\t\t\t\t\t\t\t.getStatus(key)\n\t\t\t\t\t\t\t.pipe(Effect.catch(() => Effect.succeed('failed' as const)));\n\t\t\t\t\t\tif (status === 'failed') {\n\t\t\t\t\t\t\tconst failureExit = yield* Effect.exit(handle.registry.awaitReady(key));\n\t\t\t\t\t\t\tfirstFailure = toBootError(\n\t\t\t\t\t\t\t\tExit.isFailure(failureExit) ? failureExit.cause : Cause.empty,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (firstFailure === null) {\n\t\t\t\t\t\tyield* Deferred.succeed(bootDeferred, undefined).pipe(Effect.asVoid);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tyield* Deferred.fail(bootDeferred, firstFailure).pipe(Effect.asVoid);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Caller-injected work runs AFTER the boot gate is resolved,\n\t\t\t\t\t// so it can never delay `handle.start`. Its failures fold\n\t\t\t\t\t// into the supervised body's `catchCause` below; since the\n\t\t\t\t\t// boot gate already succeeded by this point, such a failure\n\t\t\t\t\t// surfaces on `awaitShutdown` (mid-run), not `start` — the\n\t\t\t\t\t// same place a built-in `withinScope` failure would land.\n\t\t\t\t\tif (opts.boot?.withinScope !== undefined) {\n\t\t\t\t\t\tyield* opts.boot.withinScope(buildInternalHandle(publicHandle, handle));\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t});\n\t});\n\n\tconst loggerLayer = Logger.layer([]);\n\tconst layered = supervised.pipe(Effect.provide(substrate), Effect.provide(loggerLayer));\n\n\t// Convert any uncaught cause into a boot failure if the deferred\n\t// hasn't completed. If `bootDeferred` HAS already succeeded then\n\t// `Deferred.fail` is a no-op — the cause is a mid-run failure\n\t// (e.g. a plugin scope finalizer defect) and would otherwise be\n\t// silently dropped: the fiber exits `Success(void)` and\n\t// `awaitShutdown` resolves clean. Tee the cause into\n\t// `midRunCauseRef` so `awaitShutdown` can re-surface it, AND emit\n\t// `Effect.logError(Cause.pretty(cause))` so observability stays\n\t// loud regardless of whether anyone awaits. Boot failures (still in\n\t// the bootDeferred-pending window) only surface via `start`, not\n\t// here — we'd otherwise re-raise them on `awaitShutdown` after\n\t// `start` already rejected. NOTE: a CALLER `boot` hook failure folds\n\t// through this SAME tee, so its failure surfaces as `BootError` on\n\t// `start` (when raised before the gate resolves) or re-raises on\n\t// `awaitShutdown` (after) — never widening `start`'s error channel.\n\tconst supervisedProgram: Effect.Effect<void, never, never> = layered.pipe(\n\t\tEffect.catchCause((cause) =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst bootAlreadyCompleted = yield* Deferred.isDone(bootDeferred);\n\t\t\t\tyield* Deferred.fail(bootDeferred, toBootError(cause)).pipe(\n\t\t\t\t\tEffect.asVoid,\n\t\t\t\t\tEffect.catch(() => Effect.void),\n\t\t\t\t);\n\t\t\t\tif (bootAlreadyCompleted) {\n\t\t\t\t\tyield* Ref.set(midRunCauseRef, cause);\n\t\t\t\t}\n\t\t\t\tyield* Effect.logError(`devstack runStack: supervisor died\\n${Cause.pretty(cause)}`);\n\t\t\t}),\n\t\t),\n\t);\n\n\tconst start: Effect.Effect<void, BootError, never> = Effect.uninterruptibleMask((restore) =>\n\t\tEffect.gen(function* () {\n\t\t\tconst shouldStart = yield* Ref.modify(startClaim, (started) =>\n\t\t\t\tstarted ? [false, true] : [true, true],\n\t\t\t);\n\t\t\tif (shouldStart) {\n\t\t\t\t// `forkDetach` decouples the supervisor fiber from the `start`\n\t\t\t\t// fiber's scope. `forkChild`\n\t\t\t\t// would tie the supervisor to whatever fiber runs `start`, and\n\t\t\t\t// once `start` resolves (after `bootDeferred` succeeds) the\n\t\t\t\t// runtime would interrupt the supervisor — transitioning every\n\t\t\t\t// plugin `ready → stopping → stopped` before the caller can\n\t\t\t\t// read post-ready state or call `handle.stop`. The handle's\n\t\t\t\t// explicit `stop` + `awaitShutdown` paths are the only\n\t\t\t\t// shutdown signals; the captured `Fiber` reference is how the\n\t\t\t\t// daemon stays releasable.\n\t\t\t\tconst fiber = yield* Effect.forkDetach(supervisedProgram);\n\t\t\t\tyield* Deferred.succeed(fiberRef, fiber);\n\t\t\t}\n\t\t\tyield* restore(Deferred.await(bootDeferred));\n\t\t}),\n\t);\n\n\tconst stop: Effect.Effect<void, never, never> = Effect.gen(function* () {\n\t\tyield* Deferred.succeed(stopRequested, undefined).pipe(Effect.catch(() => Effect.void));\n\t\tconst alreadyStarted = yield* Deferred.isDone(fiberRef);\n\t\tif (!alreadyStarted) {\n\t\t\treturn;\n\t\t}\n\t\tconst fiber = yield* Deferred.await(fiberRef);\n\t\t// `Fiber.await` returns an `Exit` without raising — handles both\n\t\t// success and interrupt-cause cases (the supervisor's\n\t\t// graceful-shutdown path closes the scope, which surfaces as an\n\t\t// interrupt cause if the fiber was mid-await on the latch poll).\n\t\tyield* Fiber.await(fiber);\n\t});\n\n\tconst awaitShutdown: Effect.Effect<void, unknown, never> = Effect.gen(function* () {\n\t\tconst alreadyStarted = yield* Deferred.isDone(fiberRef);\n\t\tif (!alreadyStarted) {\n\t\t\treturn;\n\t\t}\n\t\tconst fiber = yield* Deferred.await(fiberRef);\n\t\tyield* Fiber.await(fiber);\n\t\t// Re-surface any mid-run defect/failure captured by the\n\t\t// supervised body's `catchCause` (boot failures are already\n\t\t// surfaced via `start`). Without this re-raise a plugin scope\n\t\t// finalizer defect would silently drop and operators get no\n\t\t// signal — see the comment on `midRunCauseRef` above.\n\t\tconst midRunCause = yield* Ref.get(midRunCauseRef);\n\t\tif (midRunCause !== null) {\n\t\t\treturn yield* Effect.failCause(midRunCause);\n\t\t}\n\t});\n\n\t// Assign the handle the composed boot hooks close over (they only deref\n\t// it at boot, after this assignment) and return it.\n\tpublicHandle = {\n\t\tstart,\n\t\tstop,\n\t\tawaitShutdown,\n\t\tevents,\n\t\tstate,\n\t\tcommands: commandQueue,\n\t\trunCommand,\n\t\tidentity,\n\t};\n\n\treturn publicHandle;\n};\n"],"mappings":"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"}
@@ -26,7 +26,8 @@ interface PlaywrightProjectShape {
26
26
  use: Record<string, unknown>;
27
27
  }
28
28
  interface DevstackPlaywrightBaseConfigOptions {
29
- /** Test directory. Default: `'./e2e'` (architecture invariant). */
29
+ /** Test directory. Default: `'./tests/browser'` (browser specs live
30
+ * under `tests/browser/`, beside `tests/unit/` and `tests/e2e/`). */
30
31
  readonly testDir?: string;
31
32
  /** Path to a global-setup module. Default: the devstack setup that
32
33
  * waits for post-acquire codegen before specs load the app. Pass
@@ -2,7 +2,7 @@ import { BUILT_IN_ENDPOINT_ALIASES } from "../runtime/conventional-routes.mjs";
2
2
  import { resolveEndpointUrl } from "./stack-context.mjs";
3
3
  //#region src/build-integrations/playwright/config.ts
4
4
  const DEFAULT_GLOBAL_SETUP = "@mysten-incubation/devstack/playwright/global-setup";
5
- const DEFAULT_TEST_DIR = "./e2e";
5
+ const DEFAULT_TEST_DIR = "./tests/browser";
6
6
  const DEFAULT_ENDPOINT_NAME = BUILT_IN_ENDPOINT_ALIASES.app;
7
7
  /**
8
8
  * Resolve the browser app URL Playwright should target. This is the
@@ -1 +1 @@
1
- {"version":3,"file":"config.mjs","names":[],"sources":["../../../src/build-integrations/playwright/config.ts"],"sourcesContent":["// Playwright config helpers.\n//\n// Architecture (distilled/23-build-integrations.md § Playwright /\n// \"What it produces\"):\n//\n// Composable pieces for a canonical `PlaywrightTestConfig`\n// (workers 1, fullyParallel false, `testDir: './e2e'`,\n// CI-aware reporter/retries/forbidOnly), plus a `baseURL` resolver.\n//\n// The stack is booted by the `globalSetup` (programmatic `runStack`,\n// see `global-setup.ts`), NOT by Playwright's `webServer`. Playwright's\n// `webServer` shells out (`pnpm dev`) and force-kills the supervisor on\n// teardown before its container drain finishes, orphaning containers;\n// the in-process `runStack` boot + `handle.stop` teardown drains cleanly.\n//\n// Load-bearing invariants this module enforces:\n// - `workers: 1` and `fullyParallel: false` — single supervisor per\n// stack; parallel tests would contend on shared faucet / wallet\n// / RPC (distilled § Invariants).\n// - `use.baseURL` settable at config-load time even with no manifest —\n// via cold-start fallback to a conventional URL (the stack the\n// globalSetup boots later answers on that conventional router host).\n//\n// This module returns `PlaywrightTestConfig`-shaped fragments. We do\n// NOT import `@playwright/test` at module init: the types are\n// structural and we accept the optional-peer cost rather than\n// importing transitively from a build-integration that must be\n// loadable without `@playwright/test` (matching the Vitest helpers'\n// optional-peer pattern).\n\nimport { BUILT_IN_ENDPOINT_ALIASES } from '../runtime/conventional-routes.ts';\nimport { type ResolveStackContextOptions, resolveEndpointUrl } from './stack-context.ts';\n\n// -----------------------------------------------------------------------------\n// PlaywrightTestConfig shape (structural; we do NOT import the type\n// directly to keep `@playwright/test` an optional peer)\n// -----------------------------------------------------------------------------\n\ntype PlaywrightReporterShape = [string] | [string, Record<string, unknown>];\ntype PlaywrightGlobalSetupShape = string | string[];\n\nconst DEFAULT_GLOBAL_SETUP = '@mysten-incubation/devstack/playwright/global-setup';\n\n/** Base subset of `PlaywrightTestConfig` this surface produces. The\n * full type lives in `@playwright/test`; we keep this structural so\n * the helpers compile without the peer. */\nexport interface PlaywrightBaseConfigShape {\n\treadonly testDir: string;\n\treadonly fullyParallel: boolean;\n\treadonly forbidOnly: boolean;\n\treadonly retries: number;\n\treadonly workers: number;\n\treadonly reporter: PlaywrightReporterShape[] | string;\n\treadonly globalSetup?: PlaywrightGlobalSetupShape;\n\treadonly globalTeardown?: string;\n}\n\nexport type PlaywrightUseConfigShape = {\n\treadonly baseURL: string;\n\treadonly trace: 'on-first-retry' | 'off' | 'retain-on-failure';\n\treadonly screenshot: 'only-on-failure' | 'off' | 'on';\n} & Record<string, unknown>;\n\nexport interface PlaywrightProjectShape {\n\tname: string;\n\tuse: Record<string, unknown>;\n}\n\n// -----------------------------------------------------------------------------\n// Public option shape\n// -----------------------------------------------------------------------------\n\nexport interface DevstackPlaywrightBaseConfigOptions {\n\t/** Test directory. Default: `'./e2e'` (architecture invariant). */\n\treadonly testDir?: string;\n\n\t/** Path to a global-setup module. Default: the devstack setup that\n\t * waits for post-acquire codegen before specs load the app. Pass\n\t * `null` to keep the property omitted when composing conditionally. */\n\treadonly globalSetup?: PlaywrightGlobalSetupShape | null;\n}\n\nexport interface DevstackPlaywrightEndpointOptions extends ResolveStackContextOptions {\n\t/** Endpoint name whose URL becomes `webServer.url` + `use.baseURL`.\n\t * Default: `'dev'` (the host-service dev server endpoint). */\n\treadonly endpointName?: string;\n\n\t/** Explicit baseURL override. When set, manifest discovery is\n\t * bypassed entirely. */\n\treadonly baseURL?: string;\n}\n\nexport interface DevstackPlaywrightProjectsOptions {\n\t/** Extra Playwright projects to append to the default Chromium\n\t * project. */\n\treadonly projects?: ReadonlyArray<PlaywrightProjectShape>;\n}\n\nexport interface DevstackPlaywrightUseOptions extends DevstackPlaywrightEndpointOptions {\n\treadonly trace?: PlaywrightUseConfigShape['trace'];\n\treadonly screenshot?: PlaywrightUseConfigShape['screenshot'];\n\treadonly use?: Omit<Partial<PlaywrightUseConfigShape>, 'baseURL'>;\n}\n\n// -----------------------------------------------------------------------------\n// Defaults\n// -----------------------------------------------------------------------------\n\nconst DEFAULT_TEST_DIR = './e2e';\nconst DEFAULT_ENDPOINT_NAME = BUILT_IN_ENDPOINT_ALIASES.app;\n\n// -----------------------------------------------------------------------------\n// Builders\n// -----------------------------------------------------------------------------\n\n/**\n * Resolve the browser app URL Playwright should target. This is the\n * only helper here that may synchronously read the manifest; callers\n * can pass `baseURL` to bypass discovery entirely.\n */\nexport const resolveDevstackPlaywrightBaseURL = (\n\toptions: DevstackPlaywrightEndpointOptions = {},\n): string => {\n\tconst endpointName = options.endpointName ?? DEFAULT_ENDPOINT_NAME;\n\treturn (\n\t\toptions.baseURL ??\n\t\tresolveEndpointUrl(endpointName, {\n\t\t\t...(options.cwd !== undefined ? { cwd: options.cwd } : {}),\n\t\t\t...(options.manifestPath !== undefined ? { manifestPath: options.manifestPath } : {}),\n\t\t\t...(options.stack !== undefined ? { stack: options.stack } : {}),\n\t\t\t...(options.stateDir !== undefined ? { stateDir: options.stateDir } : {}),\n\t\t\t...(options.env !== undefined ? { env: options.env } : {}),\n\t\t}).url\n\t);\n};\n\nexport const devstackPlaywrightBaseConfig = (\n\toptions: DevstackPlaywrightBaseConfigOptions = {},\n): PlaywrightBaseConfigShape => {\n\tconst ci = Boolean(process.env.CI);\n\treturn {\n\t\ttestDir: options.testDir ?? DEFAULT_TEST_DIR,\n\t\tfullyParallel: false,\n\t\tforbidOnly: ci,\n\t\tretries: ci ? 2 : 0,\n\t\tworkers: 1,\n\t\treporter: ci ? [['github'], ['list']] : 'list',\n\t\tglobalSetup:\n\t\t\toptions.globalSetup === null ? undefined : (options.globalSetup ?? DEFAULT_GLOBAL_SETUP),\n\t};\n};\n\nexport const devstackPlaywrightUse = (\n\toptions: DevstackPlaywrightUseOptions = {},\n): PlaywrightUseConfigShape => ({\n\tbaseURL: resolveDevstackPlaywrightBaseURL(options),\n\ttrace: options.trace ?? 'on-first-retry',\n\tscreenshot: options.screenshot ?? 'only-on-failure',\n\t...options.use,\n});\n\nexport const devstackPlaywrightProjects = (\n\toptions: DevstackPlaywrightProjectsOptions = {},\n): PlaywrightProjectShape[] => [\n\t{\n\t\tname: 'chromium',\n\t\tuse: { browserName: 'chromium' },\n\t},\n\t...(options.projects ?? []),\n];\n"],"mappings":";;;AAyCA,MAAM,uBAAuB;AAmE7B,MAAM,mBAAmB;AACzB,MAAM,wBAAwB,0BAA0B;;;;;;AAWxD,MAAa,oCACZ,UAA6C,CAAC,MAClC;CACZ,MAAM,eAAe,QAAQ,gBAAgB;CAC7C,OACC,QAAQ,WACR,mBAAmB,cAAc;EAChC,GAAI,QAAQ,QAAQ,KAAA,IAAY,EAAE,KAAK,QAAQ,IAAI,IAAI,CAAC;EACxD,GAAI,QAAQ,iBAAiB,KAAA,IAAY,EAAE,cAAc,QAAQ,aAAa,IAAI,CAAC;EACnF,GAAI,QAAQ,UAAU,KAAA,IAAY,EAAE,OAAO,QAAQ,MAAM,IAAI,CAAC;EAC9D,GAAI,QAAQ,aAAa,KAAA,IAAY,EAAE,UAAU,QAAQ,SAAS,IAAI,CAAC;EACvE,GAAI,QAAQ,QAAQ,KAAA,IAAY,EAAE,KAAK,QAAQ,IAAI,IAAI,CAAC;CACzD,CAAC,CAAC,CAAC;AAEL;AAEA,MAAa,gCACZ,UAA+C,CAAC,MACjB;CAC/B,MAAM,KAAK,QAAQ,QAAQ,IAAI,EAAE;CACjC,OAAO;EACN,SAAS,QAAQ,WAAW;EAC5B,eAAe;EACf,YAAY;EACZ,SAAS,KAAK,IAAI;EAClB,SAAS;EACT,UAAU,KAAK,CAAC,CAAC,QAAQ,GAAG,CAAC,MAAM,CAAC,IAAI;EACxC,aACC,QAAQ,gBAAgB,OAAO,KAAA,IAAa,QAAQ,eAAe;CACrE;AACD;AAEA,MAAa,yBACZ,UAAwC,CAAC,OACV;CAC/B,SAAS,iCAAiC,OAAO;CACjD,OAAO,QAAQ,SAAS;CACxB,YAAY,QAAQ,cAAc;CAClC,GAAG,QAAQ;AACZ;AAEA,MAAa,8BACZ,UAA6C,CAAC,MAChB,CAC9B;CACC,MAAM;CACN,KAAK,EAAE,aAAa,WAAW;AAChC,GACA,GAAI,QAAQ,YAAY,CAAC,CAC1B"}
1
+ {"version":3,"file":"config.mjs","names":[],"sources":["../../../src/build-integrations/playwright/config.ts"],"sourcesContent":["// Playwright config helpers.\n//\n// Architecture (distilled/23-build-integrations.md § Playwright /\n// \"What it produces\"):\n//\n// Composable pieces for a canonical `PlaywrightTestConfig`\n// (workers 1, fullyParallel false, `testDir: './e2e'`,\n// CI-aware reporter/retries/forbidOnly), plus a `baseURL` resolver.\n//\n// The stack is booted by the `globalSetup` (programmatic `runStack`,\n// see `global-setup.ts`), NOT by Playwright's `webServer`. Playwright's\n// `webServer` shells out (`pnpm dev`) and force-kills the supervisor on\n// teardown before its container drain finishes, orphaning containers;\n// the in-process `runStack` boot + `handle.stop` teardown drains cleanly.\n//\n// Load-bearing invariants this module enforces:\n// - `workers: 1` and `fullyParallel: false` — single supervisor per\n// stack; parallel tests would contend on shared faucet / wallet\n// / RPC (distilled § Invariants).\n// - `use.baseURL` settable at config-load time even with no manifest —\n// via cold-start fallback to a conventional URL (the stack the\n// globalSetup boots later answers on that conventional router host).\n//\n// This module returns `PlaywrightTestConfig`-shaped fragments. We do\n// NOT import `@playwright/test` at module init: the types are\n// structural and we accept the optional-peer cost rather than\n// importing transitively from a build-integration that must be\n// loadable without `@playwright/test` (matching the Vitest helpers'\n// optional-peer pattern).\n\nimport { BUILT_IN_ENDPOINT_ALIASES } from '../runtime/conventional-routes.ts';\nimport { type ResolveStackContextOptions, resolveEndpointUrl } from './stack-context.ts';\n\n// -----------------------------------------------------------------------------\n// PlaywrightTestConfig shape (structural; we do NOT import the type\n// directly to keep `@playwright/test` an optional peer)\n// -----------------------------------------------------------------------------\n\ntype PlaywrightReporterShape = [string] | [string, Record<string, unknown>];\ntype PlaywrightGlobalSetupShape = string | string[];\n\nconst DEFAULT_GLOBAL_SETUP = '@mysten-incubation/devstack/playwright/global-setup';\n\n/** Base subset of `PlaywrightTestConfig` this surface produces. The\n * full type lives in `@playwright/test`; we keep this structural so\n * the helpers compile without the peer. */\nexport interface PlaywrightBaseConfigShape {\n\treadonly testDir: string;\n\treadonly fullyParallel: boolean;\n\treadonly forbidOnly: boolean;\n\treadonly retries: number;\n\treadonly workers: number;\n\treadonly reporter: PlaywrightReporterShape[] | string;\n\treadonly globalSetup?: PlaywrightGlobalSetupShape;\n\treadonly globalTeardown?: string;\n}\n\nexport type PlaywrightUseConfigShape = {\n\treadonly baseURL: string;\n\treadonly trace: 'on-first-retry' | 'off' | 'retain-on-failure';\n\treadonly screenshot: 'only-on-failure' | 'off' | 'on';\n} & Record<string, unknown>;\n\nexport interface PlaywrightProjectShape {\n\tname: string;\n\tuse: Record<string, unknown>;\n}\n\n// -----------------------------------------------------------------------------\n// Public option shape\n// -----------------------------------------------------------------------------\n\nexport interface DevstackPlaywrightBaseConfigOptions {\n\t/** Test directory. Default: `'./tests/browser'` (browser specs live\n\t * under `tests/browser/`, beside `tests/unit/` and `tests/e2e/`). */\n\treadonly testDir?: string;\n\n\t/** Path to a global-setup module. Default: the devstack setup that\n\t * waits for post-acquire codegen before specs load the app. Pass\n\t * `null` to keep the property omitted when composing conditionally. */\n\treadonly globalSetup?: PlaywrightGlobalSetupShape | null;\n}\n\nexport interface DevstackPlaywrightEndpointOptions extends ResolveStackContextOptions {\n\t/** Endpoint name whose URL becomes `webServer.url` + `use.baseURL`.\n\t * Default: `'dev'` (the host-service dev server endpoint). */\n\treadonly endpointName?: string;\n\n\t/** Explicit baseURL override. When set, manifest discovery is\n\t * bypassed entirely. */\n\treadonly baseURL?: string;\n}\n\nexport interface DevstackPlaywrightProjectsOptions {\n\t/** Extra Playwright projects to append to the default Chromium\n\t * project. */\n\treadonly projects?: ReadonlyArray<PlaywrightProjectShape>;\n}\n\nexport interface DevstackPlaywrightUseOptions extends DevstackPlaywrightEndpointOptions {\n\treadonly trace?: PlaywrightUseConfigShape['trace'];\n\treadonly screenshot?: PlaywrightUseConfigShape['screenshot'];\n\treadonly use?: Omit<Partial<PlaywrightUseConfigShape>, 'baseURL'>;\n}\n\n// -----------------------------------------------------------------------------\n// Defaults\n// -----------------------------------------------------------------------------\n\nconst DEFAULT_TEST_DIR = './tests/browser';\nconst DEFAULT_ENDPOINT_NAME = BUILT_IN_ENDPOINT_ALIASES.app;\n\n// -----------------------------------------------------------------------------\n// Builders\n// -----------------------------------------------------------------------------\n\n/**\n * Resolve the browser app URL Playwright should target. This is the\n * only helper here that may synchronously read the manifest; callers\n * can pass `baseURL` to bypass discovery entirely.\n */\nexport const resolveDevstackPlaywrightBaseURL = (\n\toptions: DevstackPlaywrightEndpointOptions = {},\n): string => {\n\tconst endpointName = options.endpointName ?? DEFAULT_ENDPOINT_NAME;\n\treturn (\n\t\toptions.baseURL ??\n\t\tresolveEndpointUrl(endpointName, {\n\t\t\t...(options.cwd !== undefined ? { cwd: options.cwd } : {}),\n\t\t\t...(options.manifestPath !== undefined ? { manifestPath: options.manifestPath } : {}),\n\t\t\t...(options.stack !== undefined ? { stack: options.stack } : {}),\n\t\t\t...(options.stateDir !== undefined ? { stateDir: options.stateDir } : {}),\n\t\t\t...(options.env !== undefined ? { env: options.env } : {}),\n\t\t}).url\n\t);\n};\n\nexport const devstackPlaywrightBaseConfig = (\n\toptions: DevstackPlaywrightBaseConfigOptions = {},\n): PlaywrightBaseConfigShape => {\n\tconst ci = Boolean(process.env.CI);\n\treturn {\n\t\ttestDir: options.testDir ?? DEFAULT_TEST_DIR,\n\t\tfullyParallel: false,\n\t\tforbidOnly: ci,\n\t\tretries: ci ? 2 : 0,\n\t\tworkers: 1,\n\t\treporter: ci ? [['github'], ['list']] : 'list',\n\t\tglobalSetup:\n\t\t\toptions.globalSetup === null ? undefined : (options.globalSetup ?? DEFAULT_GLOBAL_SETUP),\n\t};\n};\n\nexport const devstackPlaywrightUse = (\n\toptions: DevstackPlaywrightUseOptions = {},\n): PlaywrightUseConfigShape => ({\n\tbaseURL: resolveDevstackPlaywrightBaseURL(options),\n\ttrace: options.trace ?? 'on-first-retry',\n\tscreenshot: options.screenshot ?? 'only-on-failure',\n\t...options.use,\n});\n\nexport const devstackPlaywrightProjects = (\n\toptions: DevstackPlaywrightProjectsOptions = {},\n): PlaywrightProjectShape[] => [\n\t{\n\t\tname: 'chromium',\n\t\tuse: { browserName: 'chromium' },\n\t},\n\t...(options.projects ?? []),\n];\n"],"mappings":";;;AAyCA,MAAM,uBAAuB;AAoE7B,MAAM,mBAAmB;AACzB,MAAM,wBAAwB,0BAA0B;;;;;;AAWxD,MAAa,oCACZ,UAA6C,CAAC,MAClC;CACZ,MAAM,eAAe,QAAQ,gBAAgB;CAC7C,OACC,QAAQ,WACR,mBAAmB,cAAc;EAChC,GAAI,QAAQ,QAAQ,KAAA,IAAY,EAAE,KAAK,QAAQ,IAAI,IAAI,CAAC;EACxD,GAAI,QAAQ,iBAAiB,KAAA,IAAY,EAAE,cAAc,QAAQ,aAAa,IAAI,CAAC;EACnF,GAAI,QAAQ,UAAU,KAAA,IAAY,EAAE,OAAO,QAAQ,MAAM,IAAI,CAAC;EAC9D,GAAI,QAAQ,aAAa,KAAA,IAAY,EAAE,UAAU,QAAQ,SAAS,IAAI,CAAC;EACvE,GAAI,QAAQ,QAAQ,KAAA,IAAY,EAAE,KAAK,QAAQ,IAAI,IAAI,CAAC;CACzD,CAAC,CAAC,CAAC;AAEL;AAEA,MAAa,gCACZ,UAA+C,CAAC,MACjB;CAC/B,MAAM,KAAK,QAAQ,QAAQ,IAAI,EAAE;CACjC,OAAO;EACN,SAAS,QAAQ,WAAW;EAC5B,eAAe;EACf,YAAY;EACZ,SAAS,KAAK,IAAI;EAClB,SAAS;EACT,UAAU,KAAK,CAAC,CAAC,QAAQ,GAAG,CAAC,MAAM,CAAC,IAAI;EACxC,aACC,QAAQ,gBAAgB,OAAO,KAAA,IAAa,QAAQ,eAAe;CACrE;AACD;AAEA,MAAa,yBACZ,UAAwC,CAAC,OACV;CAC/B,SAAS,iCAAiC,OAAO;CACjD,OAAO,QAAQ,SAAS;CACxB,YAAY,QAAQ,cAAc;CAClC,GAAG,QAAQ;AACZ;AAEA,MAAa,8BACZ,UAA6C,CAAC,MAChB,CAC9B;CACC,MAAM;CACN,KAAK,EAAE,aAAa,WAAW;AAChC,GACA,GAAI,QAAQ,YAAY,CAAC,CAC1B"}
@@ -76,6 +76,26 @@ interface ViteUserConfigLike {
76
76
  interface ViteConfigEnvLike {
77
77
  readonly command?: 'build' | 'serve';
78
78
  }
79
+ /** The structural subset of Vite's `ViteDevServer` the `configureServer`
80
+ * hook uses: the chokidar `watcher` (to track the ids file) and the HMR
81
+ * channel (to push a full reload). `ws` is Vite's long-stable channel;
82
+ * `hot` is the newer alias — we use whichever the running Vite exposes. */
83
+ interface ViteDevServerLike {
84
+ readonly watcher: {
85
+ add: (paths: string | ReadonlyArray<string>) => void;
86
+ on: (event: 'change' | 'add', listener: (path: string) => void) => void;
87
+ };
88
+ readonly ws?: {
89
+ send: (payload: {
90
+ type: 'full-reload';
91
+ }) => void;
92
+ };
93
+ readonly hot?: {
94
+ send: (payload: {
95
+ type: 'full-reload';
96
+ }) => void;
97
+ };
98
+ }
79
99
  interface DevstackVitePlugin {
80
100
  readonly name: string;
81
101
  readonly config: (config: ViteUserConfigLike, env?: ViteConfigEnvLike) => {
@@ -86,27 +106,37 @@ interface DevstackVitePlugin {
86
106
  readonly optimizeDeps?: {
87
107
  readonly include: string[];
88
108
  };
89
- /** Build-time `define` injecting the on-chain ids as the
90
- * `__DEVSTACK_IDS__` global (the generated resolver reads it). */
109
+ /** `define` injecting the on-chain ids as `__DEVSTACK_IDS__`. In a
110
+ * prod `build` this is the static id literal; in dev `serve` it is a
111
+ * reference to the `globalThis.__DEVSTACK_IDS_LIVE__` runtime global
112
+ * the `transformIndexHtml` hook sets fresh per page load, so a
113
+ * republished id reaches the app on reload (`define` is fixed for the
114
+ * dev server's lifetime and could never hot-update). */
91
115
  readonly define: Record<string, string>;
92
116
  };
93
117
  /** Capture the resolved command so injection is DEV-only. */
94
118
  readonly configResolved: (config: ViteUserConfigLike) => void;
119
+ /** Dev-only: watch the live `devstack-ids.json` and full-reload the page
120
+ * when it changes, so a republished package id (rewritten by the
121
+ * supervisor's post-acquire hook) reaches the running app. */
122
+ readonly configureServer: (server: ViteDevServerLike) => void;
95
123
  /** Resolve the dev-wallet virtual module. */
96
124
  readonly resolveId: (id: string) => string | undefined;
97
125
  /** Emit the dev-wallet register module (or a no-op when not applicable). */
98
126
  readonly load: (id: string) => string | undefined;
99
- /** Inject a `<script type="module">` importing the virtual module into
100
- * the dev page's HTML. DEV-only. The return shape mirrors a structural
101
- * subset of Vite's `IndexHtmlTransformResult` (mutable `tags`,
102
- * `injectTo` as the literal `'head'`) so the plugin stays assignable to
103
- * Vite's `Plugin` without devstack importing `vite`. */
127
+ /** Inject the dev-only HTML tags: the `__DEVSTACK_IDS_LIVE__` global
128
+ * (read fresh from the ids file per request) and, when the app carries
129
+ * the dev wallet, the `<script type="module">` importing the virtual
130
+ * module. The return shape mirrors a structural subset of Vite's
131
+ * `IndexHtmlTransformResult` so the plugin stays assignable to Vite's
132
+ * `Plugin` without devstack importing `vite`. */
104
133
  readonly transformIndexHtml: (html: string) => {
105
134
  html: string;
106
135
  tags: Array<{
107
136
  tag: string;
108
- attrs: Record<string, string | boolean>;
109
- injectTo: 'head';
137
+ attrs?: Record<string, string | boolean>;
138
+ children?: string;
139
+ injectTo: 'head' | 'head-prepend';
110
140
  }>;
111
141
  } | undefined;
112
142
  }
@@ -146,6 +146,19 @@ const LIT_DEDUPE = [
146
146
  "lit-element",
147
147
  "@lit/reactive-element"
148
148
  ];
149
+ /** Filter {@link LIT_DEDUPE} to the packages actually installed at the app
150
+ * root (`<root>/node_modules/<pkg>`). `resolve.dedupe` forces Vite to
151
+ * resolve each listed package from the ROOT copy — but under pnpm's strict
152
+ * layout a package is only surfaced at the root when the app declares it as
153
+ * a direct dependency. Listing a package that is merely a phantom
154
+ * (transitive-only, reachable solely under `@mysten/dapp-kit-core`'s nested
155
+ * store dir) makes Vite's resolver look for a root copy that does not exist
156
+ * and FAIL the production build with `Rollup failed to resolve import
157
+ * "lit"`. So we dedupe only what the app truly hoists: the `app` template
158
+ * declares `lit` (all dapp-kit-core Lit usage routes through the `lit`
159
+ * meta-package, so one `lit` ⇒ one nested `@lit/reactive-element`); the
160
+ * sub-packages stay phantom and are correctly dropped here. */
161
+ const resolvableLitDedupe = (root) => LIT_DEDUPE.filter((pkg) => existsSync(resolve(root, "node_modules", ...pkg.split("/"))));
149
162
  /** The dev-wallet entry points the injected virtual module imports. They are
150
163
  * reached only through the `<script>` this plugin adds in `transformIndexHtml`,
151
164
  * so Vite's initial dep scan never sees them and re-optimizes mid-session the
@@ -182,15 +195,17 @@ const devstackVitePlugin = (options = {}) => {
182
195
  resolvedExtrasDir = extrasDir;
183
196
  const includeDevWallet = injectDevWallet && devWalletInstalled(root);
184
197
  const injectedIds = resolveInjectedIds(env, root, command, options.ids);
198
+ const isVitest = env["VITEST"] !== void 0;
199
+ const idsDefine = command === "serve" && !isVitest ? "globalThis.__DEVSTACK_IDS_LIVE__" : JSON.stringify(injectedIds ?? null);
185
200
  return {
186
201
  resolve: {
187
202
  alias: {
188
203
  [alias]: generatedDir,
189
204
  [devExtrasAlias]: extrasDir
190
205
  },
191
- dedupe: [...LIT_DEDUPE]
206
+ dedupe: resolvableLitDedupe(root)
192
207
  },
193
- define: { __DEVSTACK_IDS__: JSON.stringify(injectedIds ?? null) },
208
+ define: { __DEVSTACK_IDS__: idsDefine },
194
209
  ...includeDevWallet ? { optimizeDeps: { include: [...DEV_WALLET_OPTIMIZE_ENTRIES] } } : {}
195
210
  };
196
211
  },
@@ -226,18 +241,39 @@ const devstackVitePlugin = (options = {}) => {
226
241
  `}).catch((err) => console.error('[devstack] dev-wallet injection failed:', err));`
227
242
  ].join("\n");
228
243
  },
244
+ configureServer: (server) => {
245
+ if (!isServe) return;
246
+ const env = process.env;
247
+ const idsFile = readIdsFileFromManifest(env, resolvedRoot);
248
+ if (idsFile === null) return;
249
+ server.watcher.add(idsFile);
250
+ const reloadOnIdsChange = (changed) => {
251
+ if (resolve(resolvedRoot, changed) !== resolve(idsFile)) return;
252
+ (server.hot ?? server.ws)?.send({ type: "full-reload" });
253
+ };
254
+ server.watcher.on("change", reloadOnIdsChange);
255
+ server.watcher.on("add", reloadOnIdsChange);
256
+ },
229
257
  transformIndexHtml: (html) => {
230
- if (!injectDevWallet || !isServe) return void 0;
258
+ if (!isServe) return void 0;
259
+ const env = process.env;
260
+ const liveIds = resolveInjectedIds(env, resolvedRoot, "serve", options.ids);
261
+ const tags = [{
262
+ tag: "script",
263
+ children: `globalThis.__DEVSTACK_IDS_LIVE__ = ${JSON.stringify(liveIds ?? null)};`,
264
+ injectTo: "head-prepend"
265
+ }];
266
+ if (injectDevWallet && devWalletInstalled(resolvedRoot)) tags.push({
267
+ tag: "script",
268
+ attrs: {
269
+ type: "module",
270
+ src: VIRTUAL_DEV_WALLET_SCRIPT_SRC
271
+ },
272
+ injectTo: "head"
273
+ });
231
274
  return {
232
275
  html,
233
- tags: [{
234
- tag: "script",
235
- attrs: {
236
- type: "module",
237
- src: VIRTUAL_DEV_WALLET_SCRIPT_SRC
238
- },
239
- injectTo: "head"
240
- }]
276
+ tags
241
277
  };
242
278
  }
243
279
  };
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/build-integrations/vite/index.ts"],"sourcesContent":["// Vite build-integration — `@generated` alias plugin.\n//\n// App source imports Move codegen through a configurable alias prefix\n// (default `@generated`) instead of `./generated`. This plugin points\n// that alias at the committed `<root>/src/generated` tree — the ONE\n// source of bindings, written only by the stack-free `devstack codegen`\n// verb. On-chain ids are NOT baked into that tree; they resolve at\n// runtime via the `__DEVSTACK_IDS__` global (see `resolveInjectedIds`),\n// so the same generated source serves every stack. Resolution:\n// 1. `options.generatedDir` — explicit escape hatch (relative → root).\n// 2. `<root>/src/generated` — the committed tree, always.\n//\n// Because Playwright's `webServer` runs the app's OWN Vite as a child,\n// the same plugin serves both `pnpm dev` and the e2e dev server\n// automatically. Vitest has its own Vite pipeline, so apps add this\n// plugin to `vitest.config.ts` too — and resolve `@generated` to the\n// same committed tree (the per-stack live tree no longer exists).\n//\n// SYNC + dependency-light, mirroring the playwright/vitest helpers: NO\n// heavy imports at module top-level, and `vite` is NOT imported (it is\n// an app-side dev dependency, not a devstack runtime dep). The return\n// value is a STRUCTURAL Vite `Plugin` — a `{ name, config }` object —\n// typed loosely so this module loads without `vite` installed. The\n// manifest read goes through the same `build-integrations/runtime`\n// machinery the playwright/vitest integrations use.\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { isAbsolute, resolve } from 'node:path';\n\nimport { discoverManifestPath, resolveDiscoveryEnv } from '../runtime/index.ts';\nimport { resolveNetworkOptions } from '../../orchestrators/network-options.ts';\nimport { decodeIdConfig } from '../../orchestrators/codegen/id-config.ts';\n\n/** Default import-alias prefix. Customizable via `options.alias` (some\n * apps prefer `@gen`, `~generated`, …). The app MUST use the SAME\n * prefix in three derivable places: this plugin option, the\n * `tsconfig` `paths` entry, and its import specifiers. */\nexport const DEFAULT_GENERATED_ALIAS = '@generated';\n\n/** Default dev-extras import-alias prefix. Resolves the PRIMARY stack's\n * `.devstack/stacks/<stack>/generated-extras` tree (dev-only / secret\n * artifacts — `accounts.ts`, `dev-wallet.ts`). Mirror the `@generated`\n * three-place discipline: this option, the `tsconfig` `paths` entry,\n * and the import specifiers. */\nexport const DEFAULT_DEV_EXTRAS_ALIAS = '@devstack-dev';\n\n/** The committed generated-bindings subpath, relative to the Vite root.\n * The single source of bindings — written by `devstack codegen` — that\n * `@generated` always resolves to (absent the `options.generatedDir`\n * escape hatch). */\nconst GENERATED_SUBPATH = 'src/generated';\n\nexport interface DevstackVitePluginOptions {\n\t/** Import-alias prefix. Default `'@generated'`. */\n\treadonly alias?: string;\n\t/** Dev-extras import-alias prefix. Default `'@devstack-dev'`. */\n\treadonly devExtrasAlias?: string;\n\t/** Explicit generated dir — bypasses manifest discovery entirely\n\t * (escape hatch for unusual layouts / tests). Relative paths\n\t * resolve against the Vite root. */\n\treadonly generatedDir?: string;\n\t/** Explicit dev-extras dir — bypasses manifest discovery. Relative\n\t * paths resolve against the Vite root. */\n\treadonly extrasDir?: string;\n\t/** Inject + register the devstack dev wallet on the page in DEV\n\t * (wallet-standard, so dApp Kit auto-discovers it). Defaults to\n\t * `true`. Production builds (`command === 'build'`) inject nothing\n\t * regardless. */\n\treadonly injectDevWallet?: boolean;\n\t/** Auto-approve all dev-wallet signing requests (headless Playwright /\n\t * in-app \"Open/Join as\" buttons). Defaults to the `DEVSTACK_AUTO_APPROVE`\n\t * env (`'1'`/`'true'`), then to the active network's `autoApproveSigning`\n\t * per-network policy (ON for every network except live `mainnet`). A\n\t * single switch, replacing per-app `VITE_*_AUTO_APPROVE`.\n\t *\n\t * HARD-CLAMP: on live `mainnet` the per-network policy forces\n\t * auto-approve OFF — a real-funds signature is NEVER granted without a\n\t * human in the loop. An explicit `autoApprove: true` here, or\n\t * `DEVSTACK_AUTO_APPROVE`, still take precedence (the author opted in\n\t * deliberately), but the policy default never silently auto-approves on\n\t * mainnet. */\n\treadonly autoApprove?: boolean;\n\t/** Per-network overrides for the dev-convenience policy (the same\n\t * `networkOptions` shape `defineDevstack` takes, forwarded verbatim).\n\t * Only the `autoApproveSigning` field is read here, to resolve the\n\t * auto-approve default for the active network. Omitted ⇒ the built-in\n\t * policy (ON except live `mainnet`) applies. The override RECORD is not\n\t * otherwise on disk, so an app that customizes per-network signing must\n\t * thread it here explicitly; the mainnet hard-clamp holds regardless. */\n\treadonly networkOptions?: Readonly<Record<string, unknown>>;\n\t/** Production id-config FILE — the known deployment's `devstack-ids.json`\n\t * (same schema the dev stack writes), committed at e.g.\n\t * `config/<network>.ids.json`. Used only for `command === 'build'` to\n\t * inject `__DEVSTACK_IDS__`. Relative paths resolve against the Vite\n\t * root. If omitted, the `DEVSTACK_IDS_FILE` env (a path pointer) is used.\n\t * Neither ⇒ no ids baked, and the generated resolver throws loudly at\n\t * id-access time. We deliberately take a FILE, not a JSON env blob: a\n\t * real deployment's ids are many + nested. */\n\treadonly ids?: string;\n}\n\n/** A `vite` `Plugin`'s `config` hook receives the partial user config.\n * We only ever read `config.root` and return a `resolve.alias` patch,\n * so a one-field structural subset is enough — this keeps the module\n * loadable without `vite` types (mirroring how the playwright/vitest\n * presets avoid importing their optional peer at module init). */\ninterface ViteUserConfigLike {\n\treadonly root?: string;\n\treadonly command?: 'build' | 'serve';\n}\n\n/** Virtual module id the dev-only HTML injection imports. Vite convention:\n * a `virtual:` specifier resolved to a `\\0`-prefixed id in `resolveId`. */\nconst VIRTUAL_DEV_WALLET_ID = 'virtual:devstack-dev-wallet';\nconst RESOLVED_VIRTUAL_DEV_WALLET_ID = '\\0virtual:devstack-dev-wallet';\n/** The `/@id/` URL form Vite's middleware understands for a `\\0`-prefixed\n * virtual id: the NUL byte is encoded as the literal `__x00__` token (a\n * raw `\\0` in an HTML attribute is mangled to a space, which Vite then\n * misroutes to the SPA fallback). Used for the injected `<script src>`. */\nconst VIRTUAL_DEV_WALLET_SCRIPT_SRC = `/@id/__x00__${VIRTUAL_DEV_WALLET_ID}`;\n\n/** The structural `Plugin` shape we return — `name`, a `config` hook, and\n * the DEV-only dev-wallet-injection hooks. Typed as the loose object Vite\n * accepts (Vite's `Plugin` is structurally compatible) so callers spread\n * it into `plugins: []` without devstack depending on `vite`. */\n/** Vite's `config` hook second arg — `{ command, mode }`. We read only\n * `command` to pick the dev vs prod id-injection source. */\ninterface ViteConfigEnvLike {\n\treadonly command?: 'build' | 'serve';\n}\n\nexport interface DevstackVitePlugin {\n\treadonly name: string;\n\treadonly config: (\n\t\tconfig: ViteUserConfigLike,\n\t\tenv?: ViteConfigEnvLike,\n\t) => {\n\t\treadonly resolve: {\n\t\t\treadonly alias: Record<string, string>;\n\t\t\t// Mutable `string[]` (NOT `readonly`): Vite's `ResolveOptions.dedupe`\n\t\t\t// and `DepOptimizationConfig.include` are both `string[] | undefined`,\n\t\t\t// and a `readonly string[]` return makes the whole `config` hook\n\t\t\t// unassignable to Vite's `Plugin` type in a consuming app's\n\t\t\t// `vite.config.ts`/`vitest.config.ts`. The values we return\n\t\t\t// (`[...LIT_DEDUPE]`, a freshly built array) are already mutable.\n\t\t\treadonly dedupe?: string[];\n\t\t};\n\t\treadonly optimizeDeps?: { readonly include: string[] };\n\t\t/** Build-time `define` injecting the on-chain ids as the\n\t\t * `__DEVSTACK_IDS__` global (the generated resolver reads it). */\n\t\treadonly define: Record<string, string>;\n\t};\n\t/** Capture the resolved command so injection is DEV-only. */\n\treadonly configResolved: (config: ViteUserConfigLike) => void;\n\t/** Resolve the dev-wallet virtual module. */\n\treadonly resolveId: (id: string) => string | undefined;\n\t/** Emit the dev-wallet register module (or a no-op when not applicable). */\n\treadonly load: (id: string) => string | undefined;\n\t/** Inject a `<script type=\"module\">` importing the virtual module into\n\t * the dev page's HTML. DEV-only. The return shape mirrors a structural\n\t * subset of Vite's `IndexHtmlTransformResult` (mutable `tags`,\n\t * `injectTo` as the literal `'head'`) so the plugin stays assignable to\n\t * Vite's `Plugin` without devstack importing `vite`. */\n\treadonly transformIndexHtml: (html: string) =>\n\t\t| {\n\t\t\t\thtml: string;\n\t\t\t\ttags: Array<{\n\t\t\t\t\ttag: string;\n\t\t\t\t\tattrs: Record<string, string | boolean>;\n\t\t\t\t\tinjectTo: 'head';\n\t\t\t\t}>;\n\t\t }\n\t\t| undefined;\n}\n\n/** Best-effort, SYNC read of a string-valued field at `dottedPath` (e.g.\n * `'codegen.idsFile'`, `'identity.network'`) from the active stack's\n * manifest. Walks each path segment as a nested object, returning the leaf\n * value, or `null` on ANY miss (absent / partially-written / version-\n * mismatched manifest, a non-object hop, a missing or non-string/empty\n * leaf). We read + `JSON.parse` directly (rather than the schema-decoding\n * `readStackContext`, which drops the `codegen` field in its projection\n * and throws on a version mismatch) so an out-of-date or partially-written\n * manifest degrades gracefully instead of crashing the dev server. Never\n * throws — the single discover→parse→guard→degrade-to-null reader every\n * manifest field above (`codegen.idsFile`, `codegen.extrasDir`,\n * `identity.network`) routes through. */\nconst readManifestField = (\n\tenv: Readonly<Record<string, string | undefined>>,\n\tcwd: string,\n\tdottedPath: string,\n): string | null => {\n\ttry {\n\t\tconst { stack, stateDir } = resolveDiscoveryEnv(env, { cwd });\n\t\tconst manifestPath = discoverManifestPath({ env, stack, stateDir, cwd });\n\t\tif (manifestPath === undefined || !existsSync(manifestPath)) return null;\n\t\tlet node: unknown = JSON.parse(readFileSync(manifestPath, 'utf8'));\n\t\tfor (const segment of dottedPath.split('.')) {\n\t\t\tif (typeof node !== 'object' || node === null) return null;\n\t\t\tnode = (node as Record<string, unknown>)[segment];\n\t\t}\n\t\treturn typeof node === 'string' && node.length > 0 ? node : null;\n\t} catch {\n\t\treturn null;\n\t}\n};\n\n/** The gitignored `devstack-ids.json` path the boot wrote for the active\n * stack (`codegen.idsFile`), or `null` on any miss. */\nconst readIdsFileFromManifest = (\n\tenv: Readonly<Record<string, string | undefined>>,\n\tcwd: string,\n): string | null => readManifestField(env, cwd, 'codegen.idsFile');\n\n/** SYNC read + schema-decode of an id-config FILE. The MISSING-file case\n * (absent path, no file on disk) collapses to `null` so the Vite config\n * load degrades gracefully when no stack has booted / no committed file is\n * wired. A PRESENT-but-malformed file is NOT swallowed: it flows through\n * the shared {@link decodeIdConfig}, which THROWS on bad JSON or a shape\n * that violates `IdConfigSchema` — surfacing a genuinely broken committed\n * id-config at config-load instead of silently injecting `null`. */\nconst readIdConfigFile = (idsFile: string | null): unknown => {\n\tif (idsFile === null || !existsSync(idsFile)) return null;\n\treturn decodeIdConfig(readFileSync(idsFile, 'utf8'));\n};\n\n/** Resolve the on-chain ids to inject as the `__DEVSTACK_IDS__` global —\n * ALWAYS from an id-config FILE (same schema in dev and prod), never a\n * JSON env blob. Dev (`serve`): the live `devstack-ids.json` (via the\n * manifest `codegen.idsFile`). Prod (`build`): the committed known-\n * deployment file — the plugin `ids` option, else the `DEVSTACK_IDS_FILE`\n * env (a PATH pointer, not data). Neither ⇒ `null`, so the generated\n * resolver throws loudly at id-access time. */\nconst resolveInjectedIds = (\n\tenv: Readonly<Record<string, string | undefined>>,\n\troot: string,\n\tcommand: 'build' | 'serve' | undefined,\n\tidsOption: string | undefined,\n): unknown => {\n\t// Prod build: the known deployment's committed id-config file. Option\n\t// wins; else a `DEVSTACK_IDS_FILE` path pointer. Relative → Vite root.\n\tif (command === 'build') {\n\t\tconst pointer = idsOption ?? env['DEVSTACK_IDS_FILE'];\n\t\tif (pointer === undefined || pointer.length === 0) return null;\n\t\treturn readIdConfigFile(isAbsolute(pointer) ? pointer : resolve(root, pointer));\n\t}\n\t// Dev serve (and config-load default): the live id-config file.\n\treturn readIdConfigFile(readIdsFileFromManifest(env, root));\n};\n\n/** The manifest-recorded dev-extras tree (`codegen.extrasDir`) the\n * `@devstack-dev` alias points at for the active stack, or `null` on any\n * miss. */\nconst readExtrasDirFromManifest = (\n\tenv: Readonly<Record<string, string | undefined>>,\n\tcwd: string,\n): string | null => readManifestField(env, cwd, 'codegen.extrasDir');\n\n/**\n * Build the devstack Vite plugin that aliases `options.alias`\n * (default `@generated`) at the committed `<root>/src/generated` tree.\n *\n * // vite.config.ts\n * import { devstackVitePlugin } from '@mysten-incubation/devstack/vite';\n * export default defineConfig({ plugins: [devstackVitePlugin()] });\n * // or devstackVitePlugin({ alias: '@gen' })\n *\n * The plugin's `config` hook merges `resolve.alias[<prefix>] = <dir>`\n * into the user config (Vite deep-merges the returned partial). Sync;\n * reads `process.env` + the manifest (for `__DEVSTACK_IDS__` / the\n * `@devstack-dev` extras dir) once at config-load.\n */\n/** Derive the cold-start fallback dev-extras dir\n * (`<root>/.devstack/stacks/<stack>/generated-extras`) for the active\n * stack — used when no manifest / `codegen.extrasDir` is on disk yet.\n * Mirrors `output-location.ts`'s `extrasDirFor`. Best-effort: a\n * discovery failure collapses to the `default` stack name. */\nconst fallbackExtrasDir = (\n\tenv: Readonly<Record<string, string | undefined>>,\n\troot: string,\n): string => {\n\tlet stack = 'default';\n\ttry {\n\t\tstack = resolveDiscoveryEnv(env, { cwd: root }).stack;\n\t} catch {\n\t\t// keep the `default` fallback.\n\t}\n\treturn resolve(root, '.devstack', 'stacks', stack, 'generated-extras');\n};\n\n/** Resolve the dev-extras dir for the active stack (shared by the alias\n * config and the dev-wallet virtual module). Explicit option wins, else\n * manifest `codegen.extrasDir`, else the derived cold-start path. */\nconst resolveExtrasDir = (\n\toptions: DevstackVitePluginOptions,\n\troot: string,\n\tenv: Readonly<Record<string, string | undefined>>,\n): string => {\n\tconst explicitExtras = options.extrasDir;\n\treturn explicitExtras !== undefined\n\t\t? resolve(root, explicitExtras)\n\t\t: (readExtrasDirFromManifest(env, root) ?? fallbackExtrasDir(env, root));\n};\n\n/** Read the `DEVSTACK_AUTO_APPROVE` env (`'1'`/`'true'`, case-insensitive). */\nconst autoApproveFromEnv = (env: Readonly<Record<string, string | undefined>>): boolean => {\n\tconst raw = env['DEVSTACK_AUTO_APPROVE'];\n\treturn raw === '1' || raw?.toLowerCase() === 'true';\n};\n\n/** Lit packages deduped to a single instance. The injected dev-wallet UI and\n * the app's dapp-kit UI are both Lit-based; if Vite loads two Lit copies they\n * register custom elements in separate realms, and the second realm's element\n * classes are unknown to the global `customElements` registry — so re-rendering\n * the dev-wallet UI (e.g. on disconnect/reconnect) throws `Illegal constructor`\n * and leaves the app in an unusable connection state. */\nconst LIT_DEDUPE = ['lit', 'lit-html', 'lit-element', '@lit/reactive-element'] as const;\n\n/** The dev-wallet entry points the injected virtual module imports. They are\n * reached only through the `<script>` this plugin adds in `transformIndexHtml`,\n * so Vite's initial dep scan never sees them and re-optimizes mid-session the\n * first time the page loads them — and that late, separate optimize pass pulls\n * a SECOND Lit instance (see {@link LIT_DEDUPE}). Pre-bundling them up front via\n * `optimizeDeps.include` keeps the whole dev-wallet UI graph in the initial pass,\n * sharing one Lit. */\nconst DEV_WALLET_OPTIMIZE_ENTRIES = [\n\t'@mysten-incubation/dev-wallet/inject',\n\t'@mysten-incubation/dev-wallet/adapters',\n] as const;\n\n/** True when `@mysten-incubation/dev-wallet` is installed at the app root —\n * i.e. the app actually depends on the dev wallet (the `app` template does;\n * the headless `ts` template does not). Checked by package presence rather\n * than `require.resolve('.../inject')`, whose `exports` entry declares only an\n * `import` condition (the CJS resolver throws `ERR_PACKAGE_PATH_NOT_EXPORTED`).\n * Best-effort: if it's absent we never hand Vite an `optimizeDeps.include` it\n * can't resolve (which would fail the dep scan). */\nconst devWalletInstalled = (root: string): boolean =>\n\texistsSync(resolve(root, 'node_modules', '@mysten-incubation', 'dev-wallet', 'package.json'));\n\nexport const devstackVitePlugin = (options: DevstackVitePluginOptions = {}): DevstackVitePlugin => {\n\tconst alias = options.alias ?? DEFAULT_GENERATED_ALIAS;\n\tconst devExtrasAlias = options.devExtrasAlias ?? DEFAULT_DEV_EXTRAS_ALIAS;\n\tconst injectDevWallet = options.injectDevWallet ?? true;\n\n\t// Captured across hooks. `config` runs first (alias resolution),\n\t// `configResolved` records the command (DEV-gate), and `load` re-reads\n\t// the dev-wallet config off `extrasDir`.\n\tlet resolvedRoot = process.cwd();\n\tlet resolvedExtrasDir: string | null = null;\n\tlet isServe = false;\n\n\treturn {\n\t\tname: 'devstack:generated-alias',\n\t\tconfig: (config: ViteUserConfigLike, configEnv?: ViteConfigEnvLike) => {\n\t\t\tconst root = config.root ?? process.cwd();\n\t\t\tresolvedRoot = root;\n\t\t\tconst env = process.env as Readonly<Record<string, string | undefined>>;\n\t\t\t// `command` comes from the second hook arg (`{ command, mode }`);\n\t\t\t// fall back to `config.command` (some callers pass it on the\n\t\t\t// config). Default the UNKNOWN case to `build` (build-safe): only an\n\t\t\t// EXPLICIT `serve` takes the live local-stack id-injection path, so a\n\t\t\t// programmatic `vite.build()` that omits the env arg never bakes\n\t\t\t// dev-stack ids into a production bundle.\n\t\t\tconst command = configEnv?.command ?? config.command ?? 'build';\n\t\t\t// Explicit `generatedDir` wins (relative → resolved against the\n\t\t\t// Vite root). Otherwise always the committed `<root>/src/generated`\n\t\t\t// tree — the single source of bindings written by `devstack\n\t\t\t// codegen`; ids resolve at runtime via `__DEVSTACK_IDS__`, so the\n\t\t\t// same tree serves every stack.\n\t\t\tconst explicit = options.generatedDir;\n\t\t\tconst generatedDir =\n\t\t\t\texplicit !== undefined ? resolve(root, explicit) : resolve(root, GENERATED_SUBPATH);\n\t\t\t// `@devstack-dev` mirrors `@generated` exactly.\n\t\t\tconst extrasDir = resolveExtrasDir(options, root, env);\n\t\t\tresolvedExtrasDir = extrasDir;\n\t\t\t// Return a partial config; Vite deep-merges it. `resolve.dedupe`\n\t\t\t// pins a single Lit copy, and — when this app carries the dev\n\t\t\t// wallet — we pre-bundle the injected dev-wallet entries so Vite\n\t\t\t// never re-optimizes them mid-session into a second Lit realm\n\t\t\t// (see the constants above).\n\t\t\tconst includeDevWallet = injectDevWallet && devWalletInstalled(root);\n\t\t\t// Inject the on-chain ids as a build-time global. The generated\n\t\t\t// `config-runtime.ts` resolver reads `__DEVSTACK_IDS__`\n\t\t\t// synchronously and throws `DevstackConfigMissingError` when an id\n\t\t\t// is unresolved. `define` substitutes it identically in the dev\n\t\t\t// server and the prod build.\n\t\t\tconst injectedIds = resolveInjectedIds(env, root, command, options.ids);\n\t\t\treturn {\n\t\t\t\tresolve: {\n\t\t\t\t\t// A bare-prefix alias (no trailing `/`) matches both\n\t\t\t\t\t// `@generated` and `@generated/foo.js` under Vite's default\n\t\t\t\t\t// string-alias resolution.\n\t\t\t\t\talias: {\n\t\t\t\t\t\t[alias]: generatedDir,\n\t\t\t\t\t\t[devExtrasAlias]: extrasDir,\n\t\t\t\t\t},\n\t\t\t\t\tdedupe: [...LIT_DEDUPE],\n\t\t\t\t},\n\t\t\t\tdefine: {\n\t\t\t\t\t__DEVSTACK_IDS__: JSON.stringify(injectedIds ?? null),\n\t\t\t\t},\n\t\t\t\t...(includeDevWallet\n\t\t\t\t\t? { optimizeDeps: { include: [...DEV_WALLET_OPTIMIZE_ENTRIES] } }\n\t\t\t\t\t: {}),\n\t\t\t};\n\t\t},\n\n\t\tconfigResolved: (config: ViteUserConfigLike) => {\n\t\t\t// DEV-gate: only a `serve` command injects the dev wallet. A\n\t\t\t// production `vite build` injects nothing.\n\t\t\tisServe = config.command !== 'build';\n\t\t},\n\n\t\tresolveId: (id: string) => {\n\t\t\tif (id === VIRTUAL_DEV_WALLET_ID) return RESOLVED_VIRTUAL_DEV_WALLET_ID;\n\t\t\treturn undefined;\n\t\t},\n\n\t\tload: (id: string): string | undefined => {\n\t\t\tif (id !== RESOLVED_VIRTUAL_DEV_WALLET_ID) return undefined;\n\t\t\t// Graceful no-op when injection is off, not a dev serve, or the\n\t\t\t// dev-extras config is absent (no `devstack apply` yet).\n\t\t\tconst extrasDir = resolvedExtrasDir ?? resolveExtrasDir(options, resolvedRoot, process.env);\n\t\t\tconst devWalletFile = resolve(extrasDir, 'dev-wallet.ts');\n\t\t\tconst accountsFile = resolve(extrasDir, 'accounts.ts');\n\t\t\tif (!injectDevWallet || !isServe || !existsSync(devWalletFile) || !existsSync(accountsFile)) {\n\t\t\t\treturn 'export {};';\n\t\t\t}\n\t\t\tconst env = process.env as Readonly<Record<string, string | undefined>>;\n\t\t\t// Auto-approve resolution (highest precedence first): explicit\n\t\t\t// `options.autoApprove`, then the `DEVSTACK_AUTO_APPROVE` env, then\n\t\t\t// the active network's `autoApproveSigning` per-network policy. The\n\t\t\t// policy is ON for every network EXCEPT live `mainnet`, so a stack\n\t\t\t// booted against real mainnet never silently auto-approves a\n\t\t\t// real-funds signature. The active network comes from the manifest\n\t\t\t// boot wrote (`identity.network`); absent that we conservatively\n\t\t\t// resolve as `mainnet` (auto-approve OFF) rather than assuming a\n\t\t\t// dev network. The override RECORD isn't on disk — `networkOptions`\n\t\t\t// is read from the plugin option when the app threads it.\n\t\t\tconst network = readManifestField(env, resolvedRoot, 'identity.network') ?? 'mainnet';\n\t\t\tconst netOpts = resolveNetworkOptions(network, options.networkOptions);\n\t\t\tconst autoApprove =\n\t\t\t\toptions.autoApprove ?? (autoApproveFromEnv(env) || netOpts.autoApproveSigning);\n\t\t\t// Re-export the generated config through the `@devstack-dev`\n\t\t\t// alias (already wired in `config`), parse the token, and\n\t\t\t// register the page wallet on load. Kept as source the dev\n\t\t\t// server transpiles — it imports `@mysten-incubation/dev-wallet`\n\t\t\t// + the generated extras, both resolvable in the app graph.\n\t\t\treturn [\n\t\t\t\t`import { registerDevstackDevWallet } from '@mysten-incubation/dev-wallet/inject';`,\n\t\t\t\t`import { parseDevstackToken } from '@mysten-incubation/dev-wallet/adapters';`,\n\t\t\t\t`import { devWallet } from '${devExtrasAlias}/dev-wallet.js';`,\n\t\t\t\t`import { accounts } from '${devExtrasAlias}/accounts.js';`,\n\t\t\t\t// The wallet EXECUTES (and simulates) against the same routed\n\t\t\t\t// RPC the app's dApp Kit client uses — a raw 127.0.0.1 RPC is\n\t\t\t\t// CORS-blocked from the routed page origin. Sourced from the\n\t\t\t\t// generated runtime config (active network's `rpc`).\n\t\t\t\t`import { config as __devstackConfig } from '${alias}/config.js';`,\n\t\t\t\t`registerDevstackDevWallet({`,\n\t\t\t\t` serverOrigin: devWallet.walletUrl,`,\n\t\t\t\t` token: parseDevstackToken(devWallet.pairUrl),`,\n\t\t\t\t` accounts,`,\n\t\t\t\t` rpcUrl: __devstackConfig.networks[__devstackConfig.network].rpc,`,\n\t\t\t\t` network: devWallet.network,`,\n\t\t\t\t` autoApprove: ${autoApprove ? 'true' : 'false'},`,\n\t\t\t\t` mountUI: true,`,\n\t\t\t\t`}).catch((err) => console.error('[devstack] dev-wallet injection failed:', err));`,\n\t\t\t].join('\\n');\n\t\t},\n\n\t\ttransformIndexHtml: (html: string) => {\n\t\t\tif (!injectDevWallet || !isServe) return undefined;\n\t\t\treturn {\n\t\t\t\thtml,\n\t\t\t\ttags: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttag: 'script',\n\t\t\t\t\t\tattrs: { type: 'module', src: VIRTUAL_DEV_WALLET_SCRIPT_SRC },\n\t\t\t\t\t\tinjectTo: 'head',\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t};\n\t\t},\n\t};\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/build-integrations/vite/index.ts"],"sourcesContent":["// Vite build-integration — `@generated` alias plugin.\n//\n// App source imports Move codegen through a configurable alias prefix\n// (default `@generated`) instead of `./generated`. This plugin points\n// that alias at the committed `<root>/src/generated` tree — the ONE\n// source of bindings, written only by the stack-free `devstack codegen`\n// verb. On-chain ids are NOT baked into that tree; they resolve at\n// runtime via the `__DEVSTACK_IDS__` global (see `resolveInjectedIds`),\n// so the same generated source serves every stack. Resolution:\n// 1. `options.generatedDir` — explicit escape hatch (relative → root).\n// 2. `<root>/src/generated` — the committed tree, always.\n//\n// Because Playwright's `webServer` runs the app's OWN Vite as a child,\n// the same plugin serves both `pnpm dev` and the e2e dev server\n// automatically. Vitest has its own Vite pipeline, so apps add this\n// plugin to `vitest.config.ts` too — and resolve `@generated` to the\n// same committed tree (the per-stack live tree no longer exists).\n//\n// SYNC + dependency-light, mirroring the playwright/vitest helpers: NO\n// heavy imports at module top-level, and `vite` is NOT imported (it is\n// an app-side dev dependency, not a devstack runtime dep). The return\n// value is a STRUCTURAL Vite `Plugin` — a `{ name, config }` object —\n// typed loosely so this module loads without `vite` installed. The\n// manifest read goes through the same `build-integrations/runtime`\n// machinery the playwright/vitest integrations use.\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { isAbsolute, resolve } from 'node:path';\n\nimport { discoverManifestPath, resolveDiscoveryEnv } from '../runtime/index.ts';\nimport { resolveNetworkOptions } from '../../orchestrators/network-options.ts';\nimport { decodeIdConfig } from '../../orchestrators/codegen/id-config.ts';\n\n/** Default import-alias prefix. Customizable via `options.alias` (some\n * apps prefer `@gen`, `~generated`, …). The app MUST use the SAME\n * prefix in three derivable places: this plugin option, the\n * `tsconfig` `paths` entry, and its import specifiers. */\nexport const DEFAULT_GENERATED_ALIAS = '@generated';\n\n/** Default dev-extras import-alias prefix. Resolves the PRIMARY stack's\n * `.devstack/stacks/<stack>/generated-extras` tree (dev-only / secret\n * artifacts — `accounts.ts`, `dev-wallet.ts`). Mirror the `@generated`\n * three-place discipline: this option, the `tsconfig` `paths` entry,\n * and the import specifiers. */\nexport const DEFAULT_DEV_EXTRAS_ALIAS = '@devstack-dev';\n\n/** The committed generated-bindings subpath, relative to the Vite root.\n * The single source of bindings — written by `devstack codegen` — that\n * `@generated` always resolves to (absent the `options.generatedDir`\n * escape hatch). */\nconst GENERATED_SUBPATH = 'src/generated';\n\nexport interface DevstackVitePluginOptions {\n\t/** Import-alias prefix. Default `'@generated'`. */\n\treadonly alias?: string;\n\t/** Dev-extras import-alias prefix. Default `'@devstack-dev'`. */\n\treadonly devExtrasAlias?: string;\n\t/** Explicit generated dir — bypasses manifest discovery entirely\n\t * (escape hatch for unusual layouts / tests). Relative paths\n\t * resolve against the Vite root. */\n\treadonly generatedDir?: string;\n\t/** Explicit dev-extras dir — bypasses manifest discovery. Relative\n\t * paths resolve against the Vite root. */\n\treadonly extrasDir?: string;\n\t/** Inject + register the devstack dev wallet on the page in DEV\n\t * (wallet-standard, so dApp Kit auto-discovers it). Defaults to\n\t * `true`. Production builds (`command === 'build'`) inject nothing\n\t * regardless. */\n\treadonly injectDevWallet?: boolean;\n\t/** Auto-approve all dev-wallet signing requests (headless Playwright /\n\t * in-app \"Open/Join as\" buttons). Defaults to the `DEVSTACK_AUTO_APPROVE`\n\t * env (`'1'`/`'true'`), then to the active network's `autoApproveSigning`\n\t * per-network policy (ON for every network except live `mainnet`). A\n\t * single switch, replacing per-app `VITE_*_AUTO_APPROVE`.\n\t *\n\t * HARD-CLAMP: on live `mainnet` the per-network policy forces\n\t * auto-approve OFF — a real-funds signature is NEVER granted without a\n\t * human in the loop. An explicit `autoApprove: true` here, or\n\t * `DEVSTACK_AUTO_APPROVE`, still take precedence (the author opted in\n\t * deliberately), but the policy default never silently auto-approves on\n\t * mainnet. */\n\treadonly autoApprove?: boolean;\n\t/** Per-network overrides for the dev-convenience policy (the same\n\t * `networkOptions` shape `defineDevstack` takes, forwarded verbatim).\n\t * Only the `autoApproveSigning` field is read here, to resolve the\n\t * auto-approve default for the active network. Omitted ⇒ the built-in\n\t * policy (ON except live `mainnet`) applies. The override RECORD is not\n\t * otherwise on disk, so an app that customizes per-network signing must\n\t * thread it here explicitly; the mainnet hard-clamp holds regardless. */\n\treadonly networkOptions?: Readonly<Record<string, unknown>>;\n\t/** Production id-config FILE — the known deployment's `devstack-ids.json`\n\t * (same schema the dev stack writes), committed at e.g.\n\t * `config/<network>.ids.json`. Used only for `command === 'build'` to\n\t * inject `__DEVSTACK_IDS__`. Relative paths resolve against the Vite\n\t * root. If omitted, the `DEVSTACK_IDS_FILE` env (a path pointer) is used.\n\t * Neither ⇒ no ids baked, and the generated resolver throws loudly at\n\t * id-access time. We deliberately take a FILE, not a JSON env blob: a\n\t * real deployment's ids are many + nested. */\n\treadonly ids?: string;\n}\n\n/** A `vite` `Plugin`'s `config` hook receives the partial user config.\n * We only ever read `config.root` and return a `resolve.alias` patch,\n * so a one-field structural subset is enough — this keeps the module\n * loadable without `vite` types (mirroring how the playwright/vitest\n * presets avoid importing their optional peer at module init). */\ninterface ViteUserConfigLike {\n\treadonly root?: string;\n\treadonly command?: 'build' | 'serve';\n}\n\n/** Virtual module id the dev-only HTML injection imports. Vite convention:\n * a `virtual:` specifier resolved to a `\\0`-prefixed id in `resolveId`. */\nconst VIRTUAL_DEV_WALLET_ID = 'virtual:devstack-dev-wallet';\nconst RESOLVED_VIRTUAL_DEV_WALLET_ID = '\\0virtual:devstack-dev-wallet';\n/** The `/@id/` URL form Vite's middleware understands for a `\\0`-prefixed\n * virtual id: the NUL byte is encoded as the literal `__x00__` token (a\n * raw `\\0` in an HTML attribute is mangled to a space, which Vite then\n * misroutes to the SPA fallback). Used for the injected `<script src>`. */\nconst VIRTUAL_DEV_WALLET_SCRIPT_SRC = `/@id/__x00__${VIRTUAL_DEV_WALLET_ID}`;\n\n/** The structural `Plugin` shape we return — `name`, a `config` hook, and\n * the DEV-only dev-wallet-injection hooks. Typed as the loose object Vite\n * accepts (Vite's `Plugin` is structurally compatible) so callers spread\n * it into `plugins: []` without devstack depending on `vite`. */\n/** Vite's `config` hook second arg — `{ command, mode }`. We read only\n * `command` to pick the dev vs prod id-injection source. */\ninterface ViteConfigEnvLike {\n\treadonly command?: 'build' | 'serve';\n}\n\n/** The structural subset of Vite's `ViteDevServer` the `configureServer`\n * hook uses: the chokidar `watcher` (to track the ids file) and the HMR\n * channel (to push a full reload). `ws` is Vite's long-stable channel;\n * `hot` is the newer alias — we use whichever the running Vite exposes. */\ninterface ViteDevServerLike {\n\treadonly watcher: {\n\t\tadd: (paths: string | ReadonlyArray<string>) => void;\n\t\ton: (event: 'change' | 'add', listener: (path: string) => void) => void;\n\t};\n\treadonly ws?: { send: (payload: { type: 'full-reload' }) => void };\n\treadonly hot?: { send: (payload: { type: 'full-reload' }) => void };\n}\n\nexport interface DevstackVitePlugin {\n\treadonly name: string;\n\treadonly config: (\n\t\tconfig: ViteUserConfigLike,\n\t\tenv?: ViteConfigEnvLike,\n\t) => {\n\t\treadonly resolve: {\n\t\t\treadonly alias: Record<string, string>;\n\t\t\t// Mutable `string[]` (NOT `readonly`): Vite's `ResolveOptions.dedupe`\n\t\t\t// and `DepOptimizationConfig.include` are both `string[] | undefined`,\n\t\t\t// and a `readonly string[]` return makes the whole `config` hook\n\t\t\t// unassignable to Vite's `Plugin` type in a consuming app's\n\t\t\t// `vite.config.ts`/`vitest.config.ts`. The value we return\n\t\t\t// (`resolvableLitDedupe(root)`, a freshly built array) is already\n\t\t\t// mutable.\n\t\t\treadonly dedupe?: string[];\n\t\t};\n\t\treadonly optimizeDeps?: { readonly include: string[] };\n\t\t/** `define` injecting the on-chain ids as `__DEVSTACK_IDS__`. In a\n\t\t * prod `build` this is the static id literal; in dev `serve` it is a\n\t\t * reference to the `globalThis.__DEVSTACK_IDS_LIVE__` runtime global\n\t\t * the `transformIndexHtml` hook sets fresh per page load, so a\n\t\t * republished id reaches the app on reload (`define` is fixed for the\n\t\t * dev server's lifetime and could never hot-update). */\n\t\treadonly define: Record<string, string>;\n\t};\n\t/** Capture the resolved command so injection is DEV-only. */\n\treadonly configResolved: (config: ViteUserConfigLike) => void;\n\t/** Dev-only: watch the live `devstack-ids.json` and full-reload the page\n\t * when it changes, so a republished package id (rewritten by the\n\t * supervisor's post-acquire hook) reaches the running app. */\n\treadonly configureServer: (server: ViteDevServerLike) => void;\n\t/** Resolve the dev-wallet virtual module. */\n\treadonly resolveId: (id: string) => string | undefined;\n\t/** Emit the dev-wallet register module (or a no-op when not applicable). */\n\treadonly load: (id: string) => string | undefined;\n\t/** Inject the dev-only HTML tags: the `__DEVSTACK_IDS_LIVE__` global\n\t * (read fresh from the ids file per request) and, when the app carries\n\t * the dev wallet, the `<script type=\"module\">` importing the virtual\n\t * module. The return shape mirrors a structural subset of Vite's\n\t * `IndexHtmlTransformResult` so the plugin stays assignable to Vite's\n\t * `Plugin` without devstack importing `vite`. */\n\treadonly transformIndexHtml: (html: string) =>\n\t\t| {\n\t\t\t\thtml: string;\n\t\t\t\ttags: Array<{\n\t\t\t\t\ttag: string;\n\t\t\t\t\tattrs?: Record<string, string | boolean>;\n\t\t\t\t\tchildren?: string;\n\t\t\t\t\tinjectTo: 'head' | 'head-prepend';\n\t\t\t\t}>;\n\t\t }\n\t\t| undefined;\n}\n\n/** Best-effort, SYNC read of a string-valued field at `dottedPath` (e.g.\n * `'codegen.idsFile'`, `'identity.network'`) from the active stack's\n * manifest. Walks each path segment as a nested object, returning the leaf\n * value, or `null` on ANY miss (absent / partially-written / version-\n * mismatched manifest, a non-object hop, a missing or non-string/empty\n * leaf). We read + `JSON.parse` directly (rather than the schema-decoding\n * `readStackContext`, which drops the `codegen` field in its projection\n * and throws on a version mismatch) so an out-of-date or partially-written\n * manifest degrades gracefully instead of crashing the dev server. Never\n * throws — the single discover→parse→guard→degrade-to-null reader every\n * manifest field above (`codegen.idsFile`, `codegen.extrasDir`,\n * `identity.network`) routes through. */\nconst readManifestField = (\n\tenv: Readonly<Record<string, string | undefined>>,\n\tcwd: string,\n\tdottedPath: string,\n): string | null => {\n\ttry {\n\t\tconst { stack, stateDir } = resolveDiscoveryEnv(env, { cwd });\n\t\tconst manifestPath = discoverManifestPath({ env, stack, stateDir, cwd });\n\t\tif (manifestPath === undefined || !existsSync(manifestPath)) return null;\n\t\tlet node: unknown = JSON.parse(readFileSync(manifestPath, 'utf8'));\n\t\tfor (const segment of dottedPath.split('.')) {\n\t\t\tif (typeof node !== 'object' || node === null) return null;\n\t\t\tnode = (node as Record<string, unknown>)[segment];\n\t\t}\n\t\treturn typeof node === 'string' && node.length > 0 ? node : null;\n\t} catch {\n\t\treturn null;\n\t}\n};\n\n/** The gitignored `devstack-ids.json` path the boot wrote for the active\n * stack (`codegen.idsFile`), or `null` on any miss. */\nconst readIdsFileFromManifest = (\n\tenv: Readonly<Record<string, string | undefined>>,\n\tcwd: string,\n): string | null => readManifestField(env, cwd, 'codegen.idsFile');\n\n/** SYNC read + schema-decode of an id-config FILE. The MISSING-file case\n * (absent path, no file on disk) collapses to `null` so the Vite config\n * load degrades gracefully when no stack has booted / no committed file is\n * wired. A PRESENT-but-malformed file is NOT swallowed: it flows through\n * the shared {@link decodeIdConfig}, which THROWS on bad JSON or a shape\n * that violates `IdConfigSchema` — surfacing a genuinely broken committed\n * id-config at config-load instead of silently injecting `null`. */\nconst readIdConfigFile = (idsFile: string | null): unknown => {\n\tif (idsFile === null || !existsSync(idsFile)) return null;\n\treturn decodeIdConfig(readFileSync(idsFile, 'utf8'));\n};\n\n/** Resolve the on-chain ids to inject as the `__DEVSTACK_IDS__` global —\n * ALWAYS from an id-config FILE (same schema in dev and prod), never a\n * JSON env blob. Dev (`serve`): the live `devstack-ids.json` (via the\n * manifest `codegen.idsFile`). Prod (`build`): the committed known-\n * deployment file — the plugin `ids` option, else the `DEVSTACK_IDS_FILE`\n * env (a PATH pointer, not data). Neither ⇒ `null`, so the generated\n * resolver throws loudly at id-access time. */\nconst resolveInjectedIds = (\n\tenv: Readonly<Record<string, string | undefined>>,\n\troot: string,\n\tcommand: 'build' | 'serve' | undefined,\n\tidsOption: string | undefined,\n): unknown => {\n\t// Prod build: the known deployment's committed id-config file. Option\n\t// wins; else a `DEVSTACK_IDS_FILE` path pointer. Relative → Vite root.\n\tif (command === 'build') {\n\t\tconst pointer = idsOption ?? env['DEVSTACK_IDS_FILE'];\n\t\tif (pointer === undefined || pointer.length === 0) return null;\n\t\treturn readIdConfigFile(isAbsolute(pointer) ? pointer : resolve(root, pointer));\n\t}\n\t// Dev serve (and config-load default): the live id-config file.\n\treturn readIdConfigFile(readIdsFileFromManifest(env, root));\n};\n\n/** The manifest-recorded dev-extras tree (`codegen.extrasDir`) the\n * `@devstack-dev` alias points at for the active stack, or `null` on any\n * miss. */\nconst readExtrasDirFromManifest = (\n\tenv: Readonly<Record<string, string | undefined>>,\n\tcwd: string,\n): string | null => readManifestField(env, cwd, 'codegen.extrasDir');\n\n/**\n * Build the devstack Vite plugin that aliases `options.alias`\n * (default `@generated`) at the committed `<root>/src/generated` tree.\n *\n * // vite.config.ts\n * import { devstackVitePlugin } from '@mysten-incubation/devstack/vite';\n * export default defineConfig({ plugins: [devstackVitePlugin()] });\n * // or devstackVitePlugin({ alias: '@gen' })\n *\n * The plugin's `config` hook merges `resolve.alias[<prefix>] = <dir>`\n * into the user config (Vite deep-merges the returned partial). Sync;\n * reads `process.env` + the manifest (for `__DEVSTACK_IDS__` / the\n * `@devstack-dev` extras dir) once at config-load.\n */\n/** Derive the cold-start fallback dev-extras dir\n * (`<root>/.devstack/stacks/<stack>/generated-extras`) for the active\n * stack — used when no manifest / `codegen.extrasDir` is on disk yet.\n * Mirrors `output-location.ts`'s `extrasDirFor`. Best-effort: a\n * discovery failure collapses to the `default` stack name. */\nconst fallbackExtrasDir = (\n\tenv: Readonly<Record<string, string | undefined>>,\n\troot: string,\n): string => {\n\tlet stack = 'default';\n\ttry {\n\t\tstack = resolveDiscoveryEnv(env, { cwd: root }).stack;\n\t} catch {\n\t\t// keep the `default` fallback.\n\t}\n\treturn resolve(root, '.devstack', 'stacks', stack, 'generated-extras');\n};\n\n/** Resolve the dev-extras dir for the active stack (shared by the alias\n * config and the dev-wallet virtual module). Explicit option wins, else\n * manifest `codegen.extrasDir`, else the derived cold-start path. */\nconst resolveExtrasDir = (\n\toptions: DevstackVitePluginOptions,\n\troot: string,\n\tenv: Readonly<Record<string, string | undefined>>,\n): string => {\n\tconst explicitExtras = options.extrasDir;\n\treturn explicitExtras !== undefined\n\t\t? resolve(root, explicitExtras)\n\t\t: (readExtrasDirFromManifest(env, root) ?? fallbackExtrasDir(env, root));\n};\n\n/** Read the `DEVSTACK_AUTO_APPROVE` env (`'1'`/`'true'`, case-insensitive). */\nconst autoApproveFromEnv = (env: Readonly<Record<string, string | undefined>>): boolean => {\n\tconst raw = env['DEVSTACK_AUTO_APPROVE'];\n\treturn raw === '1' || raw?.toLowerCase() === 'true';\n};\n\n/** Lit packages deduped to a single instance. The injected dev-wallet UI and\n * the app's dapp-kit UI are both Lit-based; if Vite loads two Lit copies they\n * register custom elements in separate realms, and the second realm's element\n * classes are unknown to the global `customElements` registry — so re-rendering\n * the dev-wallet UI (e.g. on disconnect/reconnect) throws `Illegal constructor`\n * and leaves the app in an unusable connection state. */\nconst LIT_DEDUPE = ['lit', 'lit-html', 'lit-element', '@lit/reactive-element'] as const;\n\n/** Filter {@link LIT_DEDUPE} to the packages actually installed at the app\n * root (`<root>/node_modules/<pkg>`). `resolve.dedupe` forces Vite to\n * resolve each listed package from the ROOT copy — but under pnpm's strict\n * layout a package is only surfaced at the root when the app declares it as\n * a direct dependency. Listing a package that is merely a phantom\n * (transitive-only, reachable solely under `@mysten/dapp-kit-core`'s nested\n * store dir) makes Vite's resolver look for a root copy that does not exist\n * and FAIL the production build with `Rollup failed to resolve import\n * \"lit\"`. So we dedupe only what the app truly hoists: the `app` template\n * declares `lit` (all dapp-kit-core Lit usage routes through the `lit`\n * meta-package, so one `lit` ⇒ one nested `@lit/reactive-element`); the\n * sub-packages stay phantom and are correctly dropped here. */\nconst resolvableLitDedupe = (root: string): string[] =>\n\tLIT_DEDUPE.filter((pkg) => existsSync(resolve(root, 'node_modules', ...pkg.split('/'))));\n\n/** The dev-wallet entry points the injected virtual module imports. They are\n * reached only through the `<script>` this plugin adds in `transformIndexHtml`,\n * so Vite's initial dep scan never sees them and re-optimizes mid-session the\n * first time the page loads them — and that late, separate optimize pass pulls\n * a SECOND Lit instance (see {@link LIT_DEDUPE}). Pre-bundling them up front via\n * `optimizeDeps.include` keeps the whole dev-wallet UI graph in the initial pass,\n * sharing one Lit. */\nconst DEV_WALLET_OPTIMIZE_ENTRIES = [\n\t'@mysten-incubation/dev-wallet/inject',\n\t'@mysten-incubation/dev-wallet/adapters',\n] as const;\n\n/** True when `@mysten-incubation/dev-wallet` is installed at the app root —\n * i.e. the app actually depends on the dev wallet (the `app` template does;\n * the headless `ts` template does not). Checked by package presence rather\n * than `require.resolve('.../inject')`, whose `exports` entry declares only an\n * `import` condition (the CJS resolver throws `ERR_PACKAGE_PATH_NOT_EXPORTED`).\n * Best-effort: if it's absent we never hand Vite an `optimizeDeps.include` it\n * can't resolve (which would fail the dep scan). */\nconst devWalletInstalled = (root: string): boolean =>\n\texistsSync(resolve(root, 'node_modules', '@mysten-incubation', 'dev-wallet', 'package.json'));\n\nexport const devstackVitePlugin = (options: DevstackVitePluginOptions = {}): DevstackVitePlugin => {\n\tconst alias = options.alias ?? DEFAULT_GENERATED_ALIAS;\n\tconst devExtrasAlias = options.devExtrasAlias ?? DEFAULT_DEV_EXTRAS_ALIAS;\n\tconst injectDevWallet = options.injectDevWallet ?? true;\n\n\t// Captured across hooks. `config` runs first (alias resolution),\n\t// `configResolved` records the command (DEV-gate), and `load` re-reads\n\t// the dev-wallet config off `extrasDir`.\n\tlet resolvedRoot = process.cwd();\n\tlet resolvedExtrasDir: string | null = null;\n\tlet isServe = false;\n\n\treturn {\n\t\tname: 'devstack:generated-alias',\n\t\tconfig: (config: ViteUserConfigLike, configEnv?: ViteConfigEnvLike) => {\n\t\t\tconst root = config.root ?? process.cwd();\n\t\t\tresolvedRoot = root;\n\t\t\tconst env = process.env as Readonly<Record<string, string | undefined>>;\n\t\t\t// `command` comes from the second hook arg (`{ command, mode }`);\n\t\t\t// fall back to `config.command` (some callers pass it on the\n\t\t\t// config). Default the UNKNOWN case to `build` (build-safe): only an\n\t\t\t// EXPLICIT `serve` takes the live local-stack id-injection path, so a\n\t\t\t// programmatic `vite.build()` that omits the env arg never bakes\n\t\t\t// dev-stack ids into a production bundle.\n\t\t\tconst command = configEnv?.command ?? config.command ?? 'build';\n\t\t\t// Explicit `generatedDir` wins (relative → resolved against the\n\t\t\t// Vite root). Otherwise always the committed `<root>/src/generated`\n\t\t\t// tree — the single source of bindings written by `devstack\n\t\t\t// codegen`; ids resolve at runtime via `__DEVSTACK_IDS__`, so the\n\t\t\t// same tree serves every stack.\n\t\t\tconst explicit = options.generatedDir;\n\t\t\tconst generatedDir =\n\t\t\t\texplicit !== undefined ? resolve(root, explicit) : resolve(root, GENERATED_SUBPATH);\n\t\t\t// `@devstack-dev` mirrors `@generated` exactly.\n\t\t\tconst extrasDir = resolveExtrasDir(options, root, env);\n\t\t\tresolvedExtrasDir = extrasDir;\n\t\t\t// Return a partial config; Vite deep-merges it. `resolve.dedupe`\n\t\t\t// pins a single Lit copy, and — when this app carries the dev\n\t\t\t// wallet — we pre-bundle the injected dev-wallet entries so Vite\n\t\t\t// never re-optimizes them mid-session into a second Lit realm\n\t\t\t// (see the constants above).\n\t\t\tconst includeDevWallet = injectDevWallet && devWalletInstalled(root);\n\t\t\t// Inject the on-chain ids the generated `config-runtime.ts` resolver\n\t\t\t// reads as `__DEVSTACK_IDS__`.\n\t\t\t// - PROD build: bake the static literal.\n\t\t\t// - DEV server (`vite dev`): point the identifier at the\n\t\t\t// `__DEVSTACK_IDS_LIVE__` runtime global the `transformIndexHtml`\n\t\t\t// hook sets fresh per page load — `define` is fixed for the dev\n\t\t\t// server's lifetime, so baking the value would pin the FIRST id\n\t\t\t// forever and a republish would never reach the app. A distinct\n\t\t\t// global name avoids a single-pass `define` token collision with\n\t\t\t// `__DEVSTACK_IDS__`.\n\t\t\t// - VITEST: vitest ALSO reports `command === 'serve'`, but it runs\n\t\t\t// no `transformIndexHtml` (no HTML), so the live global is never\n\t\t\t// set — and esbuild's stricter `define` rejects an operator\n\t\t\t// expression anyway. Bake a literal instead: the Vite config loads\n\t\t\t// BEFORE the test stack boots, so `injectedIds` is null and the\n\t\t\t// resolver falls back to the `DEVSTACK_IDS_FILE` env the vitest\n\t\t\t// globalSetup points at the freshly-booted stack (see\n\t\t\t// vitest/global-setup.ts).\n\t\t\t// The define VALUE must be esbuild-valid (a JSON literal or a\n\t\t\t// member-access chain) — note the bare `globalThis.…`, NOT a\n\t\t\t// parenthesised `(… ?? null)`; `config-runtime.ts` already guards the\n\t\t\t// `typeof … === 'undefined'` case.\n\t\t\tconst injectedIds = resolveInjectedIds(env, root, command, options.ids);\n\t\t\tconst isVitest = env['VITEST'] !== undefined;\n\t\t\tconst idsDefine =\n\t\t\t\tcommand === 'serve' && !isVitest\n\t\t\t\t\t? 'globalThis.__DEVSTACK_IDS_LIVE__'\n\t\t\t\t\t: JSON.stringify(injectedIds ?? null);\n\t\t\treturn {\n\t\t\t\tresolve: {\n\t\t\t\t\t// A bare-prefix alias (no trailing `/`) matches both\n\t\t\t\t\t// `@generated` and `@generated/foo.js` under Vite's default\n\t\t\t\t\t// string-alias resolution.\n\t\t\t\t\talias: {\n\t\t\t\t\t\t[alias]: generatedDir,\n\t\t\t\t\t\t[devExtrasAlias]: extrasDir,\n\t\t\t\t\t},\n\t\t\t\t\tdedupe: resolvableLitDedupe(root),\n\t\t\t\t},\n\t\t\t\tdefine: {\n\t\t\t\t\t__DEVSTACK_IDS__: idsDefine,\n\t\t\t\t},\n\t\t\t\t...(includeDevWallet\n\t\t\t\t\t? { optimizeDeps: { include: [...DEV_WALLET_OPTIMIZE_ENTRIES] } }\n\t\t\t\t\t: {}),\n\t\t\t};\n\t\t},\n\n\t\tconfigResolved: (config: ViteUserConfigLike) => {\n\t\t\t// DEV-gate: only a `serve` command injects the dev wallet. A\n\t\t\t// production `vite build` injects nothing.\n\t\t\tisServe = config.command !== 'build';\n\t\t},\n\n\t\tresolveId: (id: string) => {\n\t\t\tif (id === VIRTUAL_DEV_WALLET_ID) return RESOLVED_VIRTUAL_DEV_WALLET_ID;\n\t\t\treturn undefined;\n\t\t},\n\n\t\tload: (id: string): string | undefined => {\n\t\t\tif (id !== RESOLVED_VIRTUAL_DEV_WALLET_ID) return undefined;\n\t\t\t// Graceful no-op when injection is off, not a dev serve, or the\n\t\t\t// dev-extras config is absent (no `devstack apply` yet).\n\t\t\tconst extrasDir = resolvedExtrasDir ?? resolveExtrasDir(options, resolvedRoot, process.env);\n\t\t\tconst devWalletFile = resolve(extrasDir, 'dev-wallet.ts');\n\t\t\tconst accountsFile = resolve(extrasDir, 'accounts.ts');\n\t\t\tif (!injectDevWallet || !isServe || !existsSync(devWalletFile) || !existsSync(accountsFile)) {\n\t\t\t\treturn 'export {};';\n\t\t\t}\n\t\t\tconst env = process.env as Readonly<Record<string, string | undefined>>;\n\t\t\t// Auto-approve resolution (highest precedence first): explicit\n\t\t\t// `options.autoApprove`, then the `DEVSTACK_AUTO_APPROVE` env, then\n\t\t\t// the active network's `autoApproveSigning` per-network policy. The\n\t\t\t// policy is ON for every network EXCEPT live `mainnet`, so a stack\n\t\t\t// booted against real mainnet never silently auto-approves a\n\t\t\t// real-funds signature. The active network comes from the manifest\n\t\t\t// boot wrote (`identity.network`); absent that we conservatively\n\t\t\t// resolve as `mainnet` (auto-approve OFF) rather than assuming a\n\t\t\t// dev network. The override RECORD isn't on disk — `networkOptions`\n\t\t\t// is read from the plugin option when the app threads it.\n\t\t\tconst network = readManifestField(env, resolvedRoot, 'identity.network') ?? 'mainnet';\n\t\t\tconst netOpts = resolveNetworkOptions(network, options.networkOptions);\n\t\t\tconst autoApprove =\n\t\t\t\toptions.autoApprove ?? (autoApproveFromEnv(env) || netOpts.autoApproveSigning);\n\t\t\t// Re-export the generated config through the `@devstack-dev`\n\t\t\t// alias (already wired in `config`), parse the token, and\n\t\t\t// register the page wallet on load. Kept as source the dev\n\t\t\t// server transpiles — it imports `@mysten-incubation/dev-wallet`\n\t\t\t// + the generated extras, both resolvable in the app graph.\n\t\t\treturn [\n\t\t\t\t`import { registerDevstackDevWallet } from '@mysten-incubation/dev-wallet/inject';`,\n\t\t\t\t`import { parseDevstackToken } from '@mysten-incubation/dev-wallet/adapters';`,\n\t\t\t\t`import { devWallet } from '${devExtrasAlias}/dev-wallet.js';`,\n\t\t\t\t`import { accounts } from '${devExtrasAlias}/accounts.js';`,\n\t\t\t\t// The wallet EXECUTES (and simulates) against the same routed\n\t\t\t\t// RPC the app's dApp Kit client uses — a raw 127.0.0.1 RPC is\n\t\t\t\t// CORS-blocked from the routed page origin. Sourced from the\n\t\t\t\t// generated runtime config (active network's `rpc`).\n\t\t\t\t`import { config as __devstackConfig } from '${alias}/config.js';`,\n\t\t\t\t`registerDevstackDevWallet({`,\n\t\t\t\t` serverOrigin: devWallet.walletUrl,`,\n\t\t\t\t` token: parseDevstackToken(devWallet.pairUrl),`,\n\t\t\t\t` accounts,`,\n\t\t\t\t` rpcUrl: __devstackConfig.networks[__devstackConfig.network].rpc,`,\n\t\t\t\t` network: devWallet.network,`,\n\t\t\t\t` autoApprove: ${autoApprove ? 'true' : 'false'},`,\n\t\t\t\t` mountUI: true,`,\n\t\t\t\t`}).catch((err) => console.error('[devstack] dev-wallet injection failed:', err));`,\n\t\t\t].join('\\n');\n\t\t},\n\n\t\tconfigureServer: (server: ViteDevServerLike) => {\n\t\t\tif (!isServe) return;\n\t\t\tconst env = process.env as Readonly<Record<string, string | undefined>>;\n\t\t\tconst idsFile = readIdsFileFromManifest(env, resolvedRoot);\n\t\t\tif (idsFile === null) return;\n\t\t\t// Watch the live id-config and full-reload on change. The supervisor's\n\t\t\t// post-acquire hook rewrites this file whenever a package (re)publishes\n\t\t\t// (e.g. after a Move-source edit fires the file watcher → selective\n\t\t\t// restart); a reload re-runs `transformIndexHtml`, which re-reads the\n\t\t\t// file into `__DEVSTACK_IDS_LIVE__`, so the app picks up the new id.\n\t\t\tserver.watcher.add(idsFile);\n\t\t\tconst reloadOnIdsChange = (changed: string): void => {\n\t\t\t\t// Chokidar fires absolute paths for an absolute `watcher.add`, so\n\t\t\t\t// `resolve(changed)` is normally a no-op; resolve against the Vite\n\t\t\t\t// root anyway so a relative event path can't silently miss.\n\t\t\t\tif (resolve(resolvedRoot, changed) !== resolve(idsFile)) return;\n\t\t\t\tconst channel = server.hot ?? server.ws;\n\t\t\t\tchannel?.send({ type: 'full-reload' });\n\t\t\t};\n\t\t\tserver.watcher.on('change', reloadOnIdsChange);\n\t\t\tserver.watcher.on('add', reloadOnIdsChange);\n\t\t},\n\n\t\ttransformIndexHtml: (html: string) => {\n\t\t\tif (!isServe) return undefined;\n\t\t\tconst env = process.env as Readonly<Record<string, string | undefined>>;\n\t\t\t// Read the live ids FRESH per request so a full-reload after a\n\t\t\t// republish injects the new id. Set on a distinct global\n\t\t\t// (`__DEVSTACK_IDS_LIVE__`) the `config` hook's `define` points\n\t\t\t// `__DEVSTACK_IDS__` at; `head-prepend` runs it before any app module.\n\t\t\tconst liveIds = resolveInjectedIds(env, resolvedRoot, 'serve', options.ids);\n\t\t\tconst tags: Array<{\n\t\t\t\ttag: string;\n\t\t\t\tattrs?: Record<string, string | boolean>;\n\t\t\t\tchildren?: string;\n\t\t\t\tinjectTo: 'head' | 'head-prepend';\n\t\t\t}> = [\n\t\t\t\t{\n\t\t\t\t\ttag: 'script',\n\t\t\t\t\tchildren: `globalThis.__DEVSTACK_IDS_LIVE__ = ${JSON.stringify(liveIds ?? null)};`,\n\t\t\t\t\tinjectTo: 'head-prepend',\n\t\t\t\t},\n\t\t\t];\n\t\t\t// Gate the script tag on the app actually carrying the wallet (same\n\t\t\t// condition as the `config` hook's `optimizeDeps` include), so a\n\t\t\t// headless app emits zero dev-wallet plumbing rather than a tag that\n\t\t\t// resolves to the no-op virtual module.\n\t\t\tif (injectDevWallet && devWalletInstalled(resolvedRoot)) {\n\t\t\t\ttags.push({\n\t\t\t\t\ttag: 'script',\n\t\t\t\t\tattrs: { type: 'module', src: VIRTUAL_DEV_WALLET_SCRIPT_SRC },\n\t\t\t\t\tinjectTo: 'head',\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn { html, tags };\n\t\t},\n\t};\n};\n"],"mappings":"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"}