@mysten-incubation/devstack 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,7 +11,7 @@ The current docs live at <https://ts-sdks-incubation.vercel.app/devstack>.
11
11
  Scaffold a new app from the canonical template:
12
12
 
13
13
  ```bash
14
- pnpm create @mysten-incubation/devstack-app my-app
14
+ pnpm create @mysten-incubation/devstack-app@latest my-app
15
15
  cd my-app
16
16
  pnpm dev
17
17
  ```
@@ -79,7 +79,7 @@ const runStack = (stack, opts = {}) => {
79
79
  const codegenOutput = resolveCodegenOutput({
80
80
  appRoot,
81
81
  effectiveStack: String(identity.stack),
82
- homeStack: engineStack.options.stackName,
82
+ primaryStack: engineStack.options.stackName,
83
83
  explicitOutputDir: codegen?.outputDir,
84
84
  explicitStackSubdir: codegen?.stackSubdir ?? null
85
85
  });
@@ -1 +1 @@
1
- {"version":3,"file":"run-stack.mjs","names":[],"sources":["../../src/api/run-stack.ts"],"sourcesContent":["// `runStack(stack, opts?)` — top-level programmatic embedding.\n//\n// `defineDevstack(...)` returns a static `Stack<Members>` manifest with\n// no runnable surface. Library consumers (vitest setup, custom hosts,\n// Effect-native apps, embedded fixtures) would otherwise have to\n// re-implement `cli/wirings/up.ts:runUpLive`'s substrate Layer composition.\n// `runStack` is the single embedder seam — it consumes the same\n// `orchestrators/runtime-composition.ts` helper the CLI consumes. See\n// ARCHITECTURE.md §\"Layer composition lives at L3, not L0\".\n//\n// Shape:\n//\n// ```ts\n// const stack = defineDevstack(...);\n// const handle = runStack(stack, { runtimeRoot: '/tmp/devstack' });\n// await Effect.runPromise(handle.start);\n// // ... interact ...\n// await Effect.runPromise(handle.stop);\n// await Effect.runPromise(handle.awaitShutdown);\n// ```\n//\n// `start` resolves when every plugin has reached `ready` (or when one\n// fails to acquire — `start` then fails with `BootError`). `stop`\n// triggers graceful shutdown; `awaitShutdown` resolves when the fiber\n// finishes its scope finalizers.\n\nimport {\n\tCause,\n\tContext,\n\tDeferred,\n\tEffect,\n\tExit,\n\tFiber,\n\tLayer,\n\tLogger,\n\tQueue,\n\tRef,\n\tScope,\n\tStream,\n\tSubscriptionRef,\n} from 'effect';\n\nimport { appName, chainId, stackName } from '../substrate/brand.ts';\nimport type { Identity } from '../substrate/identity.ts';\nimport type { EngineEvent } from '../substrate/events.ts';\nimport type { SubscribableState } from '../substrate/projection.ts';\nimport { CapabilitySinksService } from '../substrate/runtime/capability-sinks/index.ts';\nimport { makeProjectionRefSync } from '../substrate/runtime/index.ts';\nimport { buildSubstrateLayers, superviseStackEffect } from '../orchestrators/run.ts';\nimport {\n\tbuildProductionOrchestratorSinks,\n\tbuildProductionPostAcquireHook,\n\tlayerProductionOrchestrators,\n\ttype ProductionCodegenOptions,\n} from '../orchestrators/runtime-composition.ts';\nimport { resolveCodegenOutput } from '../orchestrators/codegen/output-location.ts';\nimport {\n\textendBuiltInPluginContext,\n\tlayerBuiltInPluginRuntime,\n} from '../orchestrators/built-in-plugin-layers.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';\n\n// -----------------------------------------------------------------------------\n// Public types\n// -----------------------------------------------------------------------------\n\n/** Identity overrides for `runStack`. App and stack fall back through\n * shared package metadata inference after their env overrides. */\nexport interface RunStackIdentityOptions {\n\treadonly app?: string;\n\treadonly stack?: string;\n\treadonly network?: string;\n}\n\nexport interface RunStackOptions {\n\t/** Identity overrides — falls back to env + library defaults. */\n\treadonly identity?: RunStackIdentityOptions;\n\t/** User application root. Codegen defaults to `<appRoot>/src/generated`.\n\t * Defaults to `process.cwd()`. */\n\treadonly appRoot?: string;\n\t/** Codegen output overrides for embedded tests or custom app layout. */\n\treadonly codegen?: Omit<ProductionCodegenOptions, 'appRoot'>;\n\t/** Filesystem root under which the substrate stores per-stack\n\t * artifacts (cache, snapshots, manifest, projection, etc.).\n\t * Precedence: `runtimeRoot` > `stateDir` (this option or\n\t * `DevstackOptions.stateDir` on the stack) > `$DEVSTACK_STATE_DIR`\n\t * > `<cwd>/.devstack`. */\n\treadonly runtimeRoot?: string;\n\t/** Sibling of `runtimeRoot` — the `DevstackOptions.stateDir` field\n\t * threaded through `runStack` so a stack-level default can be\n\t * overridden per-embedding without forcing every call site to\n\t * flip between `runtimeRoot` and `stateDir`. Same semantics as\n\t * `runtimeRoot`; lower precedence. */\n\treadonly stateDir?: string;\n\t/** Extend the plugin execution context after built-in plugin\n\t * services are installed. Use this for custom plugin-author\n\t * services, capability sinks, or logger overrides. */\n\treadonly extendContext?: (\n\t\tctx: Context.Context<never>,\n\t) => Effect.Effect<Context.Context<never>, never, Scope.Scope | CapabilitySinksService>;\n}\n\n/** Boot error surfaced by `RunHandle.start`. Wraps the supervisor's\n * startup failure tree for cascade-formatter rendering. */\nexport interface BootError {\n\treadonly _tag: 'BootError';\n\treadonly cause: Cause.Cause<unknown>;\n}\n\n/** Programmatic handle. Symmetric with the attached CLI surface:\n * `events`, `state`, and `awaitShutdown` are the same primitives the\n * TUI consumes in-process. */\nexport interface RunHandle {\n\t/** Boot the supervisor and resolve when every plugin reaches\n\t * `ready`. Fails with `BootError` if any plugin's acquire path\n\t * fails. */\n\treadonly start: Effect.Effect<void, BootError, never>;\n\t/** Trigger graceful shutdown: enqueues `shutdown.requested` onto\n\t * the supervisor's command channel, then awaits the fiber. */\n\treadonly stop: Effect.Effect<void, never, never>;\n\t/** Resolve when the supervisor fiber exits. Succeeds on a clean\n\t * shutdown; fails with the captured `Cause` if the supervisor died\n\t * mid-run after boot completed (e.g. a plugin scope finalizer\n\t * defect). Boot-time failures still surface via `start`. */\n\treadonly awaitShutdown: Effect.Effect<void, unknown, never>;\n\t/** Tail of typed engine events from the supervisor's hub. The\n\t * upstream is a `Queue.Dequeue`; consume via `Stream.run...`. */\n\treadonly events: Stream.Stream<EngineEvent, never, never>;\n\t/** The supervisor's live projection. Renderers + tests read this\n\t * directly; changes flow through `SubscriptionRef.changes`. */\n\treadonly state: SubscriptionRef.SubscriptionRef<SubscribableState>;\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 chain id. We keep the\n\t// raw input string (`'sui:local'`, `'sui:testnet'`, …) for the\n\t// chain-id brand so existing on-disk cache namespaces and plugin\n\t// equality checks (`chain === 'sui:testnet'`) remain stable.\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\tchain: chainId(resolved.raw),\n\t};\n};\n\nconst toBootError = (cause: Cause.Cause<unknown>): BootError => ({\n\t_tag: 'BootError',\n\tcause,\n});\n\n// -----------------------------------------------------------------------------\n// runStack\n// -----------------------------------------------------------------------------\n\n/**\n * Boot a `Stack` for programmatic embedding. Returns a `RunHandle`\n * synchronously; the supervisor fiber is forked on `start`.\n *\n * Lifecycle:\n *\n * 1. `runStack(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 forks a watcher fiber over the\n * registry's `awaitReady` per node; once every node is `ready` (or one\n * fails) the deferred completes. `start` awaits it.\n */\nexport const runStack = (\n\tstack: Stack<ReadonlyArray<AnyPlugin>>,\n\topts: RunStackOptions = {},\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// State + handle slots are created at `runStack(...)` time so the\n\t// caller can subscribe to `state.changes` BEFORE `start` runs.\n\t// `Deferred.make` is sync-effect (no side-effects, no async);\n\t// `Effect.runSync` is safe for it. The projection ref is allocated\n\t// via the explicit `makeProjectionRefSync` so the sync contract is\n\t// pinned at the substrate constructor — if `makeProjectionRef`\n\t// ever picks up an async/Layer wrapper (`withSpan`, annotation),\n\t// `makeProjectionRefSync` must remain sync-only or be replaced by\n\t// a Deferred-handoff seam at this boot-time call site.\n\tconst state = makeProjectionRefSync();\n\tconst bootDeferred = Effect.runSync(Deferred.make<void, BootError>());\n\tconst stopRequested = Effect.runSync(Deferred.make<void>());\n\tconst eventQueueRef = Effect.runSync(Deferred.make<Queue.Dequeue<EngineEvent>>());\n\tconst fiberRef = Effect.runSync(Deferred.make<Fiber.Fiber<void, never>>());\n\tconst startClaim = Effect.runSync(Ref.make(false));\n\t// Tee for mid-run defects/failures. `Deferred.fail(bootDeferred, …)`\n\t// below is a no-op once `bootDeferred` has succeeded (post-boot), so\n\t// without this sibling ref a late scope-finalizer defect would\n\t// otherwise leave the supervised fiber exiting `Success(void)` and\n\t// `awaitShutdown` resolving clean — the operator would have no\n\t// signal. `awaitShutdown` re-raises whatever this ref captured.\n\tconst midRunCauseRef = Effect.runSync(Ref.make<Cause.Cause<unknown> | null>(null));\n\n\t// Resolve the per-stack codegen output location: home run (effective\n\t// stack === config `stackName`) → `src/generated/`; a non-home\n\t// embedding → `.devstack/stacks/<stack>/generated/`. An explicit\n\t// `opts.codegen.outputDir` (or the stack's own\n\t// `codegen.outputDir`) is honored verbatim by the resolver. Both the\n\t// home stack (`engineStack.options.stackName`) and the effective\n\t// stack (the resolved `identity.stack`) are in scope here, mirroring\n\t// the CLI's `buildVerbLayers` seam.\n\tconst codegenOutput = resolveCodegenOutput({\n\t\tappRoot,\n\t\teffectiveStack: String(identity.stack),\n\t\thomeStack: engineStack.options.stackName,\n\t\texplicitOutputDir: codegen?.outputDir,\n\t\texplicitStackSubdir: codegen?.stackSubdir ?? null,\n\t});\n\tconst substrate = layerProductionOrchestrators({\n\t\tcodegen: {\n\t\t\tappRoot,\n\t\t\toutputDir: codegenOutput.outputDir,\n\t\t\tstackSubdir: codegenOutput.stackSubdir,\n\t\t},\n\t}).pipe(Layer.provideMerge(buildSubstrateLayers(identity, runtimeRoot)));\n\n\tconst supervised = Effect.gen(function* () {\n\t\tconst orchestratorSinks = yield* buildProductionOrchestratorSinks();\n\t\tconst postAcquireHook = yield* buildProductionPostAcquireHook({ extras: stack.options.extras });\n\t\tyield* superviseStackEffect(supervisedStack, identity, state, {\n\t\t\torchestratorSinks,\n\t\t\tpostAcquireHook,\n\t\t\textendContext: (ctx) =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tconst builtInContext = yield* extendBuiltInPluginContext(ctx);\n\t\t\t\t\treturn opts.extendContext === undefined\n\t\t\t\t\t\t? builtInContext\n\t\t\t\t\t\t: yield* opts.extendContext(builtInContext);\n\t\t\t\t}),\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\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\t\t\t\t}),\n\t\t\twithinScope: (handle) =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t// Watch every plugin for `ready`; resolve `bootDeferred` once\n\t\t\t\t\t// every node has reached ready (or one fails). The\n\t\t\t\t\t// `awaitReady` callbacks resolve from the per-plugin\n\t\t\t\t\t// ready-gates the registry holds — see supervisor §\n\t\t\t\t\t// \"ready-gate awaits its acquire effect\".\n\t\t\t\t\t// Both forks tie to the surrounding scope (the supervised\n\t\t\t\t\t// scope inside superviseStackEffect). When the supervisor\n\t\t\t\t\t// scope closes — either via graceful shutdown or interrupt\n\t\t\t\t\t// — these fibers are torn down with it.\n\t\t\t\t\tyield* Effect.forkScoped(\n\t\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\t\tconst exits = yield* Effect.forEach(\n\t\t\t\t\t\t\t\thandle.graph.nodes,\n\t\t\t\t\t\t\t\t([key]) => handle.registry.awaitReady(key).pipe(Effect.exit),\n\t\t\t\t\t\t\t\t{ concurrency: 'unbounded' },\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tconst firstFailure = exits.find(Exit.isFailure);\n\t\t\t\t\t\t\tif (firstFailure === undefined) {\n\t\t\t\t\t\t\t\tyield* Deferred.succeed(bootDeferred, undefined);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tyield* Deferred.fail(bootDeferred, toBootError(firstFailure.cause));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}),\n\t\t}).pipe(Effect.provide(layerBuiltInPluginRuntime(orchestratorSinks)));\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.\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` (v4 spelling of v3's `forkDaemon`) decouples the\n\t\t\t\t// supervisor fiber from the `start` 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\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\treturn {\n\t\tstart,\n\t\tstop,\n\t\tawaitShutdown,\n\t\tevents,\n\t\tstate,\n\t};\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"run-stack.mjs","names":[],"sources":["../../src/api/run-stack.ts"],"sourcesContent":["// `runStack(stack, opts?)` — top-level programmatic embedding.\n//\n// `defineDevstack(...)` returns a static `Stack<Members>` manifest with\n// no runnable surface. Library consumers (vitest setup, custom hosts,\n// Effect-native apps, embedded fixtures) would otherwise have to\n// re-implement `cli/wirings/up.ts:runUpLive`'s substrate Layer composition.\n// `runStack` is the single embedder seam — it consumes the same\n// `orchestrators/runtime-composition.ts` helper the CLI consumes. See\n// ARCHITECTURE.md §\"Layer composition lives at L3, not L0\".\n//\n// Shape:\n//\n// ```ts\n// const stack = defineDevstack(...);\n// const handle = runStack(stack, { runtimeRoot: '/tmp/devstack' });\n// await Effect.runPromise(handle.start);\n// // ... interact ...\n// await Effect.runPromise(handle.stop);\n// await Effect.runPromise(handle.awaitShutdown);\n// ```\n//\n// `start` resolves when every plugin has reached `ready` (or when one\n// fails to acquire — `start` then fails with `BootError`). `stop`\n// triggers graceful shutdown; `awaitShutdown` resolves when the fiber\n// finishes its scope finalizers.\n\nimport {\n\tCause,\n\tContext,\n\tDeferred,\n\tEffect,\n\tExit,\n\tFiber,\n\tLayer,\n\tLogger,\n\tQueue,\n\tRef,\n\tScope,\n\tStream,\n\tSubscriptionRef,\n} from 'effect';\n\nimport { appName, chainId, stackName } from '../substrate/brand.ts';\nimport type { Identity } from '../substrate/identity.ts';\nimport type { EngineEvent } from '../substrate/events.ts';\nimport type { SubscribableState } from '../substrate/projection.ts';\nimport { CapabilitySinksService } from '../substrate/runtime/capability-sinks/index.ts';\nimport { makeProjectionRefSync } from '../substrate/runtime/index.ts';\nimport { buildSubstrateLayers, superviseStackEffect } from '../orchestrators/run.ts';\nimport {\n\tbuildProductionOrchestratorSinks,\n\tbuildProductionPostAcquireHook,\n\tlayerProductionOrchestrators,\n\ttype ProductionCodegenOptions,\n} from '../orchestrators/runtime-composition.ts';\nimport { resolveCodegenOutput } from '../orchestrators/codegen/output-location.ts';\nimport {\n\textendBuiltInPluginContext,\n\tlayerBuiltInPluginRuntime,\n} from '../orchestrators/built-in-plugin-layers.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';\n\n// -----------------------------------------------------------------------------\n// Public types\n// -----------------------------------------------------------------------------\n\n/** Identity overrides for `runStack`. App and stack fall back through\n * shared package metadata inference after their env overrides. */\nexport interface RunStackIdentityOptions {\n\treadonly app?: string;\n\treadonly stack?: string;\n\treadonly network?: string;\n}\n\nexport interface RunStackOptions {\n\t/** Identity overrides — falls back to env + library defaults. */\n\treadonly identity?: RunStackIdentityOptions;\n\t/** User application root. Codegen defaults to `<appRoot>/src/generated`.\n\t * Defaults to `process.cwd()`. */\n\treadonly appRoot?: string;\n\t/** Codegen output overrides for embedded tests or custom app layout. */\n\treadonly codegen?: Omit<ProductionCodegenOptions, 'appRoot'>;\n\t/** Filesystem root under which the substrate stores per-stack\n\t * artifacts (cache, snapshots, manifest, projection, etc.).\n\t * Precedence: `runtimeRoot` > `stateDir` (this option or\n\t * `DevstackOptions.stateDir` on the stack) > `$DEVSTACK_STATE_DIR`\n\t * > `<cwd>/.devstack`. */\n\treadonly runtimeRoot?: string;\n\t/** Sibling of `runtimeRoot` — the `DevstackOptions.stateDir` field\n\t * threaded through `runStack` so a stack-level default can be\n\t * overridden per-embedding without forcing every call site to\n\t * flip between `runtimeRoot` and `stateDir`. Same semantics as\n\t * `runtimeRoot`; lower precedence. */\n\treadonly stateDir?: string;\n\t/** Extend the plugin execution context after built-in plugin\n\t * services are installed. Use this for custom plugin-author\n\t * services, capability sinks, or logger overrides. */\n\treadonly extendContext?: (\n\t\tctx: Context.Context<never>,\n\t) => Effect.Effect<Context.Context<never>, never, Scope.Scope | CapabilitySinksService>;\n}\n\n/** Boot error surfaced by `RunHandle.start`. Wraps the supervisor's\n * startup failure tree for cascade-formatter rendering. */\nexport interface BootError {\n\treadonly _tag: 'BootError';\n\treadonly cause: Cause.Cause<unknown>;\n}\n\n/** Programmatic handle. Symmetric with the attached CLI surface:\n * `events`, `state`, and `awaitShutdown` are the same primitives the\n * TUI consumes in-process. */\nexport interface RunHandle {\n\t/** Boot the supervisor and resolve when every plugin reaches\n\t * `ready`. Fails with `BootError` if any plugin's acquire path\n\t * fails. */\n\treadonly start: Effect.Effect<void, BootError, never>;\n\t/** Trigger graceful shutdown: enqueues `shutdown.requested` onto\n\t * the supervisor's command channel, then awaits the fiber. */\n\treadonly stop: Effect.Effect<void, never, never>;\n\t/** Resolve when the supervisor fiber exits. Succeeds on a clean\n\t * shutdown; fails with the captured `Cause` if the supervisor died\n\t * mid-run after boot completed (e.g. a plugin scope finalizer\n\t * defect). Boot-time failures still surface via `start`. */\n\treadonly awaitShutdown: Effect.Effect<void, unknown, never>;\n\t/** Tail of typed engine events from the supervisor's hub. The\n\t * upstream is a `Queue.Dequeue`; consume via `Stream.run...`. */\n\treadonly events: Stream.Stream<EngineEvent, never, never>;\n\t/** The supervisor's live projection. Renderers + tests read this\n\t * directly; changes flow through `SubscriptionRef.changes`. */\n\treadonly state: SubscriptionRef.SubscriptionRef<SubscribableState>;\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 chain id. We keep the\n\t// raw input string (`'sui:local'`, `'sui:testnet'`, …) for the\n\t// chain-id brand so existing on-disk cache namespaces and plugin\n\t// equality checks (`chain === 'sui:testnet'`) remain stable.\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\tchain: chainId(resolved.raw),\n\t};\n};\n\nconst toBootError = (cause: Cause.Cause<unknown>): BootError => ({\n\t_tag: 'BootError',\n\tcause,\n});\n\n// -----------------------------------------------------------------------------\n// runStack\n// -----------------------------------------------------------------------------\n\n/**\n * Boot a `Stack` for programmatic embedding. Returns a `RunHandle`\n * synchronously; the supervisor fiber is forked on `start`.\n *\n * Lifecycle:\n *\n * 1. `runStack(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 forks a watcher fiber over the\n * registry's `awaitReady` per node; once every node is `ready` (or one\n * fails) the deferred completes. `start` awaits it.\n */\nexport const runStack = (\n\tstack: Stack<ReadonlyArray<AnyPlugin>>,\n\topts: RunStackOptions = {},\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// State + handle slots are created at `runStack(...)` time so the\n\t// caller can subscribe to `state.changes` BEFORE `start` runs.\n\t// `Deferred.make` is sync-effect (no side-effects, no async);\n\t// `Effect.runSync` is safe for it. The projection ref is allocated\n\t// via the explicit `makeProjectionRefSync` so the sync contract is\n\t// pinned at the substrate constructor — if `makeProjectionRef`\n\t// ever picks up an async/Layer wrapper (`withSpan`, annotation),\n\t// `makeProjectionRefSync` must remain sync-only or be replaced by\n\t// a Deferred-handoff seam at this boot-time call site.\n\tconst state = makeProjectionRefSync();\n\tconst bootDeferred = Effect.runSync(Deferred.make<void, BootError>());\n\tconst stopRequested = Effect.runSync(Deferred.make<void>());\n\tconst eventQueueRef = Effect.runSync(Deferred.make<Queue.Dequeue<EngineEvent>>());\n\tconst fiberRef = Effect.runSync(Deferred.make<Fiber.Fiber<void, never>>());\n\tconst startClaim = Effect.runSync(Ref.make(false));\n\t// Tee for mid-run defects/failures. `Deferred.fail(bootDeferred, …)`\n\t// below is a no-op once `bootDeferred` has succeeded (post-boot), so\n\t// without this sibling ref a late scope-finalizer defect would\n\t// otherwise leave the supervised fiber exiting `Success(void)` and\n\t// `awaitShutdown` resolving clean — the operator would have no\n\t// signal. `awaitShutdown` re-raises whatever this ref captured.\n\tconst midRunCauseRef = Effect.runSync(Ref.make<Cause.Cause<unknown> | null>(null));\n\n\t// Resolve the per-stack codegen output location: primary run (effective\n\t// stack === config `stackName`) → `src/generated/`; a secondary\n\t// embedding → `.devstack/stacks/<stack>/generated/`. An explicit\n\t// `opts.codegen.outputDir` (or the stack's own\n\t// `codegen.outputDir`) is honored verbatim by the resolver. Both the\n\t// primary stack (`engineStack.options.stackName`) and the effective\n\t// stack (the resolved `identity.stack`) are in scope here, mirroring\n\t// the CLI's `buildVerbLayers` seam.\n\tconst codegenOutput = resolveCodegenOutput({\n\t\tappRoot,\n\t\teffectiveStack: String(identity.stack),\n\t\tprimaryStack: engineStack.options.stackName,\n\t\texplicitOutputDir: codegen?.outputDir,\n\t\texplicitStackSubdir: codegen?.stackSubdir ?? null,\n\t});\n\tconst substrate = layerProductionOrchestrators({\n\t\tcodegen: {\n\t\t\tappRoot,\n\t\t\toutputDir: codegenOutput.outputDir,\n\t\t\tstackSubdir: codegenOutput.stackSubdir,\n\t\t},\n\t}).pipe(Layer.provideMerge(buildSubstrateLayers(identity, runtimeRoot)));\n\n\tconst supervised = Effect.gen(function* () {\n\t\tconst orchestratorSinks = yield* buildProductionOrchestratorSinks();\n\t\tconst postAcquireHook = yield* buildProductionPostAcquireHook({ extras: stack.options.extras });\n\t\tyield* superviseStackEffect(supervisedStack, identity, state, {\n\t\t\torchestratorSinks,\n\t\t\tpostAcquireHook,\n\t\t\textendContext: (ctx) =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tconst builtInContext = yield* extendBuiltInPluginContext(ctx);\n\t\t\t\t\treturn opts.extendContext === undefined\n\t\t\t\t\t\t? builtInContext\n\t\t\t\t\t\t: yield* opts.extendContext(builtInContext);\n\t\t\t\t}),\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\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\t\t\t\t}),\n\t\t\twithinScope: (handle) =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t// Watch every plugin for `ready`; resolve `bootDeferred` once\n\t\t\t\t\t// every node has reached ready (or one fails). The\n\t\t\t\t\t// `awaitReady` callbacks resolve from the per-plugin\n\t\t\t\t\t// ready-gates the registry holds — see supervisor §\n\t\t\t\t\t// \"ready-gate awaits its acquire effect\".\n\t\t\t\t\t// Both forks tie to the surrounding scope (the supervised\n\t\t\t\t\t// scope inside superviseStackEffect). When the supervisor\n\t\t\t\t\t// scope closes — either via graceful shutdown or interrupt\n\t\t\t\t\t// — these fibers are torn down with it.\n\t\t\t\t\tyield* Effect.forkScoped(\n\t\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\t\tconst exits = yield* Effect.forEach(\n\t\t\t\t\t\t\t\thandle.graph.nodes,\n\t\t\t\t\t\t\t\t([key]) => handle.registry.awaitReady(key).pipe(Effect.exit),\n\t\t\t\t\t\t\t\t{ concurrency: 'unbounded' },\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tconst firstFailure = exits.find(Exit.isFailure);\n\t\t\t\t\t\t\tif (firstFailure === undefined) {\n\t\t\t\t\t\t\t\tyield* Deferred.succeed(bootDeferred, undefined);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tyield* Deferred.fail(bootDeferred, toBootError(firstFailure.cause));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}),\n\t\t}).pipe(Effect.provide(layerBuiltInPluginRuntime(orchestratorSinks)));\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.\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` (v4 spelling of v3's `forkDaemon`) decouples the\n\t\t\t\t// supervisor fiber from the `start` 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\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\treturn {\n\t\tstart,\n\t\tstop,\n\t\tawaitShutdown,\n\t\tevents,\n\t\tstate,\n\t};\n};\n"],"mappings":"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"}
@@ -9,9 +9,9 @@ import { resolve } from "node:path";
9
9
  * prefix in three derivable places: this plugin option, the
10
10
  * `tsconfig` `paths` entry, and its import specifiers. */
11
11
  const DEFAULT_GENERATED_ALIAS = "@generated";
12
- /** Default home-stack output subpath, relative to the Vite root. The
12
+ /** Default primary-stack output subpath, relative to the Vite root. The
13
13
  * cold-start fallback when no manifest / `codegen.generatedDir` is on
14
- * disk yet. Mirrors `output-location.ts`'s home rule. */
14
+ * disk yet. Mirrors `output-location.ts`'s primary rule. */
15
15
  const FALLBACK_GENERATED_SUBPATH = "src/generated";
16
16
  /** Best-effort, SYNC read of the manifest-recorded `codegen.generatedDir`
17
17
  * for the active stack. Returns the absolute dir, or `null` on any
@@ -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// notes/per-stack-codegen-design.md §\"Import mapping via a customizable\n// alias\": app source imports its Move codegen through a configurable\n// alias prefix (default `@generated`) instead of `./generated`. This\n// plugin points that alias at the ACTIVE stack's output dir — the EXACT\n// dir codegen wrote to for the current stack — so two stacks of the\n// same app (`pnpm dev` on the home stack + `pnpm test:e2e` on a\n// `--stack e2e` override) resolve `@generated/*` to different\n// directories and never read each other's clobbered package-id /\n// wallet-pair-token literals.\n//\n// The target is the manifest-recorded `codegen.generatedDir` (the\n// single source of truth — the supervisor wrote it at flush time; see\n// `orchestrators/codegen/output-location.ts` +\n// `runtime-composition.ts`). Resolution:\n// 1. `options.generatedDir` — explicit escape hatch, used verbatim.\n// 2. `resolveDiscoveryEnv(process.env)` → `{ stack, stateDir }`\n// (single source of truth; honors `DEVSTACK_STACK` +\n// `DEVSTACK_RUNTIME_ROOT`/`DEVSTACK_STATE_DIR`), then\n// `discoverManifestPath(...)` locates `<stateDir>/stacks/<stack>/manifest.json`,\n// and we read its `codegen.generatedDir`.\n// 3. Cold-start fallback — no manifest / no field yet → `<root>/src/generated`.\n// (In the supervised flow Vite starts AFTER post-acquire codegen,\n// so the field is present; the fallback is the pre-`up` window.)\n//\n// Because Playwright's `webServer` runs the app's OWN Vite as a child\n// inheriting `DEVSTACK_STACK`, the same plugin serves both `pnpm dev`\n// and the e2e dev server automatically. Vitest has its own Vite\n// pipeline, so apps add this plugin to `vitest.config.ts` too.\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 { resolve } from 'node:path';\n\nimport { discoverManifestPath, resolveDiscoveryEnv } from '../runtime/index.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 home-stack output subpath, relative to the Vite root. The\n * cold-start fallback when no manifest / `codegen.generatedDir` is on\n * disk yet. Mirrors `output-location.ts`'s home rule. */\nconst FALLBACK_GENERATED_SUBPATH = 'src/generated';\n\nexport interface DevstackVitePluginOptions {\n\t/** Import-alias prefix. Default `'@generated'`. */\n\treadonly alias?: string;\n\t/** Explicit generated dir — bypasses manifest discovery entirely\n\t * (escape hatch for unusual layouts / tests). Relative paths\n\t * resolve against the Vite root. */\n\treadonly generatedDir?: string;\n}\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}\n\n/** The structural `Plugin` shape we return — `name` + a `config` hook.\n * Typed as the loose object Vite accepts (Vite's `Plugin` is\n * structurally compatible) so callers spread it into `plugins: []`\n * without devstack depending on `vite`. */\nexport interface DevstackVitePlugin {\n\treadonly name: string;\n\treadonly config: (config: ViteUserConfigLike) => {\n\t\treadonly resolve: { readonly alias: Record<string, string> };\n\t};\n}\n\n/** Best-effort, SYNC read of the manifest-recorded `codegen.generatedDir`\n * for the active stack. Returns the absolute dir, or `null` on any\n * miss (no manifest yet, no `codegen` field, unreadable/corrupt file).\n * Never throws — the caller falls back to `src/generated/`. We read +\n * `JSON.parse` directly (rather than the schema-decoding\n * `readStackContext`, which (a) drops the `codegen` field in its\n * projection and (b) throws on a version mismatch) so an out-of-date\n * or partially-written manifest degrades to the cold-start fallback\n * instead of crashing the dev server. */\nconst readGeneratedDirFromManifest = (\n\tenv: Readonly<Record<string, string | undefined>>,\n\tcwd: string,\n): string | null => {\n\ttry {\n\t\tconst { stack, stateDir } = resolveDiscoveryEnv(env);\n\t\tconst manifestPath = discoverManifestPath({ env, stack, stateDir, cwd });\n\t\tif (manifestPath === undefined || !existsSync(manifestPath)) return null;\n\t\tconst parsed: unknown = JSON.parse(readFileSync(manifestPath, 'utf8'));\n\t\tif (typeof parsed !== 'object' || parsed === null) return null;\n\t\tconst codegen = (parsed as { readonly codegen?: unknown }).codegen;\n\t\tif (typeof codegen !== 'object' || codegen === null) return null;\n\t\tconst generatedDir = (codegen as { readonly generatedDir?: unknown }).generatedDir;\n\t\treturn typeof generatedDir === 'string' && generatedDir.length > 0 ? generatedDir : null;\n\t} catch {\n\t\t// Discovery / read / parse failure → cold-start fallback. A\n\t\t// best-effort alias resolver must never fail the Vite config load.\n\t\treturn null;\n\t}\n};\n\n/**\n * Build the devstack Vite plugin that aliases `options.alias`\n * (default `@generated`) at the active stack's codegen output dir.\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 once at config-load.\n */\nexport const devstackVitePlugin = (options: DevstackVitePluginOptions = {}): DevstackVitePlugin => {\n\tconst alias = options.alias ?? DEFAULT_GENERATED_ALIAS;\n\treturn {\n\t\tname: 'devstack:generated-alias',\n\t\tconfig: (config: ViteUserConfigLike) => {\n\t\t\tconst root = config.root ?? process.cwd();\n\t\t\t// Explicit `generatedDir` wins (relative → resolved against the\n\t\t\t// Vite root). Otherwise consult the manifest-recorded dir, then\n\t\t\t// fall back to the home-stack `src/generated/` for the\n\t\t\t// pre-supervisor cold-start window.\n\t\t\tconst explicit = options.generatedDir;\n\t\t\tconst generatedDir =\n\t\t\t\texplicit !== undefined\n\t\t\t\t\t? resolve(root, explicit)\n\t\t\t\t\t: (readGeneratedDirFromManifest(\n\t\t\t\t\t\t\tprocess.env as Readonly<Record<string, string | undefined>>,\n\t\t\t\t\t\t\troot,\n\t\t\t\t\t\t) ?? resolve(root, FALLBACK_GENERATED_SUBPATH));\n\t\t\t// Return a partial config; Vite merges `resolve.alias` into the\n\t\t\t// existing alias map. A bare-prefix alias (no trailing `/`)\n\t\t\t// matches both `@generated` and `@generated/foo.js` under Vite's\n\t\t\t// default string-alias resolution.\n\t\t\treturn { resolve: { alias: { [alias]: generatedDir } } };\n\t\t},\n\t};\n};\n"],"mappings":";;;;;;;;;;AAgDA,MAAa,0BAA0B;;;;AAKvC,MAAM,6BAA6B;;;;;;;;;;AAwCnC,MAAM,gCACL,KACA,QACmB;AACnB,KAAI;EACH,MAAM,EAAE,OAAO,aAAa,oBAAoB,IAAI;EACpD,MAAM,eAAe,qBAAqB;GAAE;GAAK;GAAO;GAAU;GAAK,CAAC;AACxE,MAAI,iBAAiB,KAAA,KAAa,CAAC,WAAW,aAAa,CAAE,QAAO;EACpE,MAAM,SAAkB,KAAK,MAAM,aAAa,cAAc,OAAO,CAAC;AACtE,MAAI,OAAO,WAAW,YAAY,WAAW,KAAM,QAAO;EAC1D,MAAM,UAAW,OAA0C;AAC3D,MAAI,OAAO,YAAY,YAAY,YAAY,KAAM,QAAO;EAC5D,MAAM,eAAgB,QAAgD;AACtE,SAAO,OAAO,iBAAiB,YAAY,aAAa,SAAS,IAAI,eAAe;SAC7E;AAGP,SAAO;;;;;;;;;;;;;;;;AAiBT,MAAa,sBAAsB,UAAqC,EAAE,KAAyB;CAClG,MAAM,QAAQ,QAAQ,SAAA;AACtB,QAAO;EACN,MAAM;EACN,SAAS,WAA+B;GACvC,MAAM,OAAO,OAAO,QAAQ,QAAQ,KAAK;GAKzC,MAAM,WAAW,QAAQ;GACzB,MAAM,eACL,aAAa,KAAA,IACV,QAAQ,MAAM,SAAS,GACtB,6BACD,QAAQ,KACR,KACA,IAAI,QAAQ,MAAM,2BAA2B;AAKjD,UAAO,EAAE,SAAS,EAAE,OAAO,GAAG,QAAQ,cAAc,EAAE,EAAE;;EAEzD"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/build-integrations/vite/index.ts"],"sourcesContent":["// Vite build-integration — `@generated` alias plugin.\n//\n// notes/per-stack-codegen-design.md §\"Import mapping via a customizable\n// alias\": app source imports its Move codegen through a configurable\n// alias prefix (default `@generated`) instead of `./generated`. This\n// plugin points that alias at the ACTIVE stack's output dir — the EXACT\n// dir codegen wrote to for the current stack — so two stacks of the\n// same app (`pnpm dev` on the primary stack + `pnpm test:e2e` on a\n// `--stack e2e` override) resolve `@generated/*` to different\n// directories and never read each other's clobbered package-id /\n// wallet-pair-token literals.\n//\n// The target is the manifest-recorded `codegen.generatedDir` (the\n// single source of truth — the supervisor wrote it at flush time; see\n// `orchestrators/codegen/output-location.ts` +\n// `runtime-composition.ts`). Resolution:\n// 1. `options.generatedDir` — explicit escape hatch, used verbatim.\n// 2. `resolveDiscoveryEnv(process.env)` → `{ stack, stateDir }`\n// (single source of truth; honors `DEVSTACK_STACK` +\n// `DEVSTACK_RUNTIME_ROOT`/`DEVSTACK_STATE_DIR`), then\n// `discoverManifestPath(...)` locates `<stateDir>/stacks/<stack>/manifest.json`,\n// and we read its `codegen.generatedDir`.\n// 3. Cold-start fallback — no manifest / no field yet → `<root>/src/generated`.\n// (In the supervised flow Vite starts AFTER post-acquire codegen,\n// so the field is present; the fallback is the pre-`up` window.)\n//\n// Because Playwright's `webServer` runs the app's OWN Vite as a child\n// inheriting `DEVSTACK_STACK`, the same plugin serves both `pnpm dev`\n// and the e2e dev server automatically. Vitest has its own Vite\n// pipeline, so apps add this plugin to `vitest.config.ts` too.\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 { resolve } from 'node:path';\n\nimport { discoverManifestPath, resolveDiscoveryEnv } from '../runtime/index.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 primary-stack output subpath, relative to the Vite root. The\n * cold-start fallback when no manifest / `codegen.generatedDir` is on\n * disk yet. Mirrors `output-location.ts`'s primary rule. */\nconst FALLBACK_GENERATED_SUBPATH = 'src/generated';\n\nexport interface DevstackVitePluginOptions {\n\t/** Import-alias prefix. Default `'@generated'`. */\n\treadonly alias?: string;\n\t/** Explicit generated dir — bypasses manifest discovery entirely\n\t * (escape hatch for unusual layouts / tests). Relative paths\n\t * resolve against the Vite root. */\n\treadonly generatedDir?: string;\n}\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}\n\n/** The structural `Plugin` shape we return — `name` + a `config` hook.\n * Typed as the loose object Vite accepts (Vite's `Plugin` is\n * structurally compatible) so callers spread it into `plugins: []`\n * without devstack depending on `vite`. */\nexport interface DevstackVitePlugin {\n\treadonly name: string;\n\treadonly config: (config: ViteUserConfigLike) => {\n\t\treadonly resolve: { readonly alias: Record<string, string> };\n\t};\n}\n\n/** Best-effort, SYNC read of the manifest-recorded `codegen.generatedDir`\n * for the active stack. Returns the absolute dir, or `null` on any\n * miss (no manifest yet, no `codegen` field, unreadable/corrupt file).\n * Never throws — the caller falls back to `src/generated/`. We read +\n * `JSON.parse` directly (rather than the schema-decoding\n * `readStackContext`, which (a) drops the `codegen` field in its\n * projection and (b) throws on a version mismatch) so an out-of-date\n * or partially-written manifest degrades to the cold-start fallback\n * instead of crashing the dev server. */\nconst readGeneratedDirFromManifest = (\n\tenv: Readonly<Record<string, string | undefined>>,\n\tcwd: string,\n): string | null => {\n\ttry {\n\t\tconst { stack, stateDir } = resolveDiscoveryEnv(env);\n\t\tconst manifestPath = discoverManifestPath({ env, stack, stateDir, cwd });\n\t\tif (manifestPath === undefined || !existsSync(manifestPath)) return null;\n\t\tconst parsed: unknown = JSON.parse(readFileSync(manifestPath, 'utf8'));\n\t\tif (typeof parsed !== 'object' || parsed === null) return null;\n\t\tconst codegen = (parsed as { readonly codegen?: unknown }).codegen;\n\t\tif (typeof codegen !== 'object' || codegen === null) return null;\n\t\tconst generatedDir = (codegen as { readonly generatedDir?: unknown }).generatedDir;\n\t\treturn typeof generatedDir === 'string' && generatedDir.length > 0 ? generatedDir : null;\n\t} catch {\n\t\t// Discovery / read / parse failure → cold-start fallback. A\n\t\t// best-effort alias resolver must never fail the Vite config load.\n\t\treturn null;\n\t}\n};\n\n/**\n * Build the devstack Vite plugin that aliases `options.alias`\n * (default `@generated`) at the active stack's codegen output dir.\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 once at config-load.\n */\nexport const devstackVitePlugin = (options: DevstackVitePluginOptions = {}): DevstackVitePlugin => {\n\tconst alias = options.alias ?? DEFAULT_GENERATED_ALIAS;\n\treturn {\n\t\tname: 'devstack:generated-alias',\n\t\tconfig: (config: ViteUserConfigLike) => {\n\t\t\tconst root = config.root ?? process.cwd();\n\t\t\t// Explicit `generatedDir` wins (relative → resolved against the\n\t\t\t// Vite root). Otherwise consult the manifest-recorded dir, then\n\t\t\t// fall back to the primary-stack `src/generated/` for the\n\t\t\t// pre-supervisor cold-start window.\n\t\t\tconst explicit = options.generatedDir;\n\t\t\tconst generatedDir =\n\t\t\t\texplicit !== undefined\n\t\t\t\t\t? resolve(root, explicit)\n\t\t\t\t\t: (readGeneratedDirFromManifest(\n\t\t\t\t\t\t\tprocess.env as Readonly<Record<string, string | undefined>>,\n\t\t\t\t\t\t\troot,\n\t\t\t\t\t\t) ?? resolve(root, FALLBACK_GENERATED_SUBPATH));\n\t\t\t// Return a partial config; Vite merges `resolve.alias` into the\n\t\t\t// existing alias map. A bare-prefix alias (no trailing `/`)\n\t\t\t// matches both `@generated` and `@generated/foo.js` under Vite's\n\t\t\t// default string-alias resolution.\n\t\t\treturn { resolve: { alias: { [alias]: generatedDir } } };\n\t\t},\n\t};\n};\n"],"mappings":";;;;;;;;;;AAgDA,MAAa,0BAA0B;;;;AAKvC,MAAM,6BAA6B;;;;;;;;;;AAwCnC,MAAM,gCACL,KACA,QACmB;AACnB,KAAI;EACH,MAAM,EAAE,OAAO,aAAa,oBAAoB,IAAI;EACpD,MAAM,eAAe,qBAAqB;GAAE;GAAK;GAAO;GAAU;GAAK,CAAC;AACxE,MAAI,iBAAiB,KAAA,KAAa,CAAC,WAAW,aAAa,CAAE,QAAO;EACpE,MAAM,SAAkB,KAAK,MAAM,aAAa,cAAc,OAAO,CAAC;AACtE,MAAI,OAAO,WAAW,YAAY,WAAW,KAAM,QAAO;EAC1D,MAAM,UAAW,OAA0C;AAC3D,MAAI,OAAO,YAAY,YAAY,YAAY,KAAM,QAAO;EAC5D,MAAM,eAAgB,QAAgD;AACtE,SAAO,OAAO,iBAAiB,YAAY,aAAa,SAAS,IAAI,eAAe;SAC7E;AAGP,SAAO;;;;;;;;;;;;;;;;AAiBT,MAAa,sBAAsB,UAAqC,EAAE,KAAyB;CAClG,MAAM,QAAQ,QAAQ,SAAA;AACtB,QAAO;EACN,MAAM;EACN,SAAS,WAA+B;GACvC,MAAM,OAAO,OAAO,QAAQ,QAAQ,KAAK;GAKzC,MAAM,WAAW,QAAQ;GACzB,MAAM,eACL,aAAa,KAAA,IACV,QAAQ,MAAM,SAAS,GACtB,6BACD,QAAQ,KACR,KACA,IAAI,QAAQ,MAAM,2BAA2B;AAKjD,UAAO,EAAE,SAAS,EAAE,OAAO,GAAG,QAAQ,cAAc,EAAE,EAAE;;EAEzD"}
@@ -9,11 +9,11 @@ import { Layer } from "effect";
9
9
  * load a config (e.g. restore/delete/wipe).
10
10
  *
11
11
  * Resolves the per-stack codegen output location HERE — the one boot
12
- * seam where both the home stack (`stack.options.stackName`) and the
12
+ * seam where both the primary stack (`stack.options.stackName`) and the
13
13
  * EFFECTIVE stack (`String(identity.stack)`, already run through the
14
14
  * explicit-`--stack` > `config.stackName` > inferred precedence ladder
15
15
  * by `resolvedIdentityForStack`/`identityValueFor` upstream) are in
16
- * scope. The home run emits into `src/generated/`; a non-home run
16
+ * scope. The primary run emits into `src/generated/`; a secondary run
17
17
  * (concurrent `--stack e2e`/etc.) emits into
18
18
  * `.devstack/stacks/<stack>/generated/` so the two never clobber. The
19
19
  * resolved literal `outputDir`/`stackSubdir` flow into
@@ -23,7 +23,7 @@ const buildVerbLayers = (params) => {
23
23
  const codegenOutput = resolveCodegenOutput({
24
24
  appRoot: params.appRoot,
25
25
  effectiveStack: String(params.identity.stack),
26
- homeStack: params.stack.options.stackName,
26
+ primaryStack: params.stack.options.stackName,
27
27
  explicitOutputDir: params.stack.options.codegen?.outputDir,
28
28
  explicitStackSubdir: params.stack.options.codegen?.stackSubdir ?? null
29
29
  });
@@ -1 +1 @@
1
- {"version":3,"file":"build-verb-layers.mjs","names":[],"sources":["../../../src/cli/wirings/build-verb-layers.ts"],"sourcesContent":["// Shared substrate + orchestrator Layer composition for verb wirings.\n//\n// The CLI verbs that boot a substrate (`up`, `apply --direct`, snapshot\n// capture --direct) all build the same composition:\n// - `layerProductionOrchestrators({ codegen })` on top of\n// - `buildSubstrateLayers(identity, runtimeRoot)`.\n//\n// Extracted here so each wiring file references one helper instead of\n// rebuilding the composition inline (formerly ~5 sites in `main.ts`).\n\nimport { Layer } from 'effect';\n\nimport type { Identity } from '../../substrate/identity.ts';\nimport { buildSubstrateLayers } from '../../orchestrators/run.ts';\nimport { layerProductionOrchestrators } from '../../orchestrators/runtime-composition.ts';\nimport { resolveCodegenOutput } from '../../orchestrators/codegen/output-location.ts';\nimport type { SupervisedStack } from '../../substrate/runtime/index.ts';\n\n/** Compose substrate + orchestrator Layers for a verb that knows its\n * stack-config (i.e. has a `loaded` config and can pass codegen\n * options). Use `buildDirectSnapshotLayers` when the verb does NOT\n * load a config (e.g. restore/delete/wipe).\n *\n * Resolves the per-stack codegen output location HERE — the one boot\n * seam where both the home stack (`stack.options.stackName`) and the\n * EFFECTIVE stack (`String(identity.stack)`, already run through the\n * explicit-`--stack` > `config.stackName` > inferred precedence ladder\n * by `resolvedIdentityForStack`/`identityValueFor` upstream) are in\n * scope. The home run emits into `src/generated/`; a non-home run\n * (concurrent `--stack e2e`/etc.) emits into\n * `.devstack/stacks/<stack>/generated/` so the two never clobber. The\n * resolved literal `outputDir`/`stackSubdir` flow into\n * `layerProductionOrchestrators` unchanged — `paths.ts` keeps consuming\n * a literal, minimal blast radius. */\nexport const buildVerbLayers = (params: {\n\treadonly identity: Identity;\n\treadonly stack: SupervisedStack;\n\treadonly appRoot: string;\n\treadonly runtimeRoot: string;\n}) => {\n\tconst codegenOutput = resolveCodegenOutput({\n\t\tappRoot: params.appRoot,\n\t\teffectiveStack: String(params.identity.stack),\n\t\thomeStack: params.stack.options.stackName,\n\t\texplicitOutputDir: params.stack.options.codegen?.outputDir,\n\t\texplicitStackSubdir: params.stack.options.codegen?.stackSubdir ?? null,\n\t});\n\treturn layerProductionOrchestrators({\n\t\tcodegen: {\n\t\t\tappRoot: params.appRoot,\n\t\t\toutputDir: codegenOutput.outputDir,\n\t\t\tstackSubdir: codegenOutput.stackSubdir,\n\t\t},\n\t}).pipe(Layer.provideMerge(buildSubstrateLayers(params.identity, params.runtimeRoot)));\n};\n\n/** Substrate + (default-codegen) orchestrator Layers for verbs that\n * don't load a config. */\nexport const buildDirectSnapshotLayers = (params: {\n\treadonly identity: Identity;\n\treadonly runtimeRoot: string;\n}) =>\n\tlayerProductionOrchestrators().pipe(\n\t\tLayer.provideMerge(buildSubstrateLayers(params.identity, params.runtimeRoot)),\n\t);\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAkCA,MAAa,mBAAmB,WAK1B;CACL,MAAM,gBAAgB,qBAAqB;EAC1C,SAAS,OAAO;EAChB,gBAAgB,OAAO,OAAO,SAAS,MAAM;EAC7C,WAAW,OAAO,MAAM,QAAQ;EAChC,mBAAmB,OAAO,MAAM,QAAQ,SAAS;EACjD,qBAAqB,OAAO,MAAM,QAAQ,SAAS,eAAe;EAClE,CAAC;AACF,QAAO,6BAA6B,EACnC,SAAS;EACR,SAAS,OAAO;EAChB,WAAW,cAAc;EACzB,aAAa,cAAc;EAC3B,EACD,CAAC,CAAC,KAAK,MAAM,aAAa,qBAAqB,OAAO,UAAU,OAAO,YAAY,CAAC,CAAC;;;;AAKvF,MAAa,6BAA6B,WAIzC,8BAA8B,CAAC,KAC9B,MAAM,aAAa,qBAAqB,OAAO,UAAU,OAAO,YAAY,CAAC,CAC7E"}
1
+ {"version":3,"file":"build-verb-layers.mjs","names":[],"sources":["../../../src/cli/wirings/build-verb-layers.ts"],"sourcesContent":["// Shared substrate + orchestrator Layer composition for verb wirings.\n//\n// The CLI verbs that boot a substrate (`up`, `apply --direct`, snapshot\n// capture --direct) all build the same composition:\n// - `layerProductionOrchestrators({ codegen })` on top of\n// - `buildSubstrateLayers(identity, runtimeRoot)`.\n//\n// Extracted here so each wiring file references one helper instead of\n// rebuilding the composition inline (formerly ~5 sites in `main.ts`).\n\nimport { Layer } from 'effect';\n\nimport type { Identity } from '../../substrate/identity.ts';\nimport { buildSubstrateLayers } from '../../orchestrators/run.ts';\nimport { layerProductionOrchestrators } from '../../orchestrators/runtime-composition.ts';\nimport { resolveCodegenOutput } from '../../orchestrators/codegen/output-location.ts';\nimport type { SupervisedStack } from '../../substrate/runtime/index.ts';\n\n/** Compose substrate + orchestrator Layers for a verb that knows its\n * stack-config (i.e. has a `loaded` config and can pass codegen\n * options). Use `buildDirectSnapshotLayers` when the verb does NOT\n * load a config (e.g. restore/delete/wipe).\n *\n * Resolves the per-stack codegen output location HERE — the one boot\n * seam where both the primary stack (`stack.options.stackName`) and the\n * EFFECTIVE stack (`String(identity.stack)`, already run through the\n * explicit-`--stack` > `config.stackName` > inferred precedence ladder\n * by `resolvedIdentityForStack`/`identityValueFor` upstream) are in\n * scope. The primary run emits into `src/generated/`; a secondary run\n * (concurrent `--stack e2e`/etc.) emits into\n * `.devstack/stacks/<stack>/generated/` so the two never clobber. The\n * resolved literal `outputDir`/`stackSubdir` flow into\n * `layerProductionOrchestrators` unchanged — `paths.ts` keeps consuming\n * a literal, minimal blast radius. */\nexport const buildVerbLayers = (params: {\n\treadonly identity: Identity;\n\treadonly stack: SupervisedStack;\n\treadonly appRoot: string;\n\treadonly runtimeRoot: string;\n}) => {\n\tconst codegenOutput = resolveCodegenOutput({\n\t\tappRoot: params.appRoot,\n\t\teffectiveStack: String(params.identity.stack),\n\t\tprimaryStack: params.stack.options.stackName,\n\t\texplicitOutputDir: params.stack.options.codegen?.outputDir,\n\t\texplicitStackSubdir: params.stack.options.codegen?.stackSubdir ?? null,\n\t});\n\treturn layerProductionOrchestrators({\n\t\tcodegen: {\n\t\t\tappRoot: params.appRoot,\n\t\t\toutputDir: codegenOutput.outputDir,\n\t\t\tstackSubdir: codegenOutput.stackSubdir,\n\t\t},\n\t}).pipe(Layer.provideMerge(buildSubstrateLayers(params.identity, params.runtimeRoot)));\n};\n\n/** Substrate + (default-codegen) orchestrator Layers for verbs that\n * don't load a config. */\nexport const buildDirectSnapshotLayers = (params: {\n\treadonly identity: Identity;\n\treadonly runtimeRoot: string;\n}) =>\n\tlayerProductionOrchestrators().pipe(\n\t\tLayer.provideMerge(buildSubstrateLayers(params.identity, params.runtimeRoot)),\n\t);\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAkCA,MAAa,mBAAmB,WAK1B;CACL,MAAM,gBAAgB,qBAAqB;EAC1C,SAAS,OAAO;EAChB,gBAAgB,OAAO,OAAO,SAAS,MAAM;EAC7C,cAAc,OAAO,MAAM,QAAQ;EACnC,mBAAmB,OAAO,MAAM,QAAQ,SAAS;EACjD,qBAAqB,OAAO,MAAM,QAAQ,SAAS,eAAe;EAClE,CAAC;AACF,QAAO,6BAA6B,EACnC,SAAS;EACR,SAAS,OAAO;EAChB,WAAW,cAAc;EACzB,aAAa,cAAc;EAC3B,EACD,CAAC,CAAC,KAAK,MAAM,aAAa,qBAAqB,OAAO,UAAU,OAAO,YAAY,CAAC,CAAC;;;;AAKvF,MAAa,6BAA6B,WAIzC,8BAA8B,CAAC,KAC9B,MAAM,aAAa,qBAAqB,OAAO,UAAU,OAAO,YAAY,CAAC,CAC7E"}
@@ -1,10 +1,10 @@
1
1
  import { isAbsolute, resolve } from "node:path";
2
2
  //#region src/orchestrators/codegen/output-location.ts
3
- /** Default home-stack output dir (relative to `appRoot`). */
4
- const HOME_OUTPUT_SUBPATH = "src/generated";
3
+ /** Default primary-stack output dir (relative to `appRoot`). */
4
+ const PRIMARY_OUTPUT_SUBPATH = "src/generated";
5
5
  /** Resolve `<appRoot>/.devstack/stacks/<stack>/generated` for a
6
- * non-home stack — sibling of that stack's manifest. */
7
- const nonHomeOutputDir = (appRoot, stack) => resolve(appRoot, ".devstack", "stacks", stack, "generated");
6
+ * secondary stack — sibling of that stack's manifest. */
7
+ const secondaryOutputDir = (appRoot, stack) => resolve(appRoot, ".devstack", "stacks", stack, "generated");
8
8
  /**
9
9
  * Resolve the codegen output location for a stack. See the module
10
10
  * header for the rule. Pure — same inputs always yield the same
@@ -14,13 +14,14 @@ const nonHomeOutputDir = (appRoot, stack) => resolve(appRoot, ".devstack", "stac
14
14
  * 1. Explicit `defineDevstack({ codegen.outputDir })` → honored
15
15
  * verbatim (relative → resolved against `appRoot`). The explicit
16
16
  * `stackSubdir` rides along.
17
- * 2. Home stack (`effectiveStack === homeStack`, or no `homeStack`
18
- * declared) → `<appRoot>/src/generated`, `stackSubdir: null`.
19
- * 3. Non-home stack → `<appRoot>/.devstack/stacks/<effectiveStack>/generated`,
17
+ * 2. Primary stack (`effectiveStack === primaryStack`, or no
18
+ * `primaryStack` declared) → `<appRoot>/src/generated`,
19
+ * `stackSubdir: null`.
20
+ * 3. Secondary stack → `<appRoot>/.devstack/stacks/<effectiveStack>/generated`,
20
21
  * `stackSubdir: null`.
21
22
  */
22
23
  const resolveCodegenOutput = (input) => {
23
- const { appRoot, effectiveStack, homeStack } = input;
24
+ const { appRoot, effectiveStack, primaryStack } = input;
24
25
  const explicitStackSubdir = input.explicitStackSubdir ?? null;
25
26
  if (input.explicitOutputDir !== void 0) {
26
27
  const target = input.explicitOutputDir;
@@ -30,7 +31,7 @@ const resolveCodegenOutput = (input) => {
30
31
  };
31
32
  }
32
33
  return {
33
- outputDir: effectiveStack === (homeStack ?? effectiveStack) ? resolve(appRoot, HOME_OUTPUT_SUBPATH) : nonHomeOutputDir(appRoot, effectiveStack),
34
+ outputDir: effectiveStack === (primaryStack ?? effectiveStack) ? resolve(appRoot, PRIMARY_OUTPUT_SUBPATH) : secondaryOutputDir(appRoot, effectiveStack),
34
35
  stackSubdir: null
35
36
  };
36
37
  };
@@ -1 +1 @@
1
- {"version":3,"file":"output-location.mjs","names":[],"sources":["../../../src/orchestrators/codegen/output-location.ts"],"sourcesContent":["// Per-stack codegen output-location resolver.\n//\n// notes/per-stack-codegen-design.md §\"Output-location rule\": two stacks\n// of the same app must not emit Move codegen into the SAME\n// `src/generated/` dir — the second stack's `codegen.emitted` would\n// clobber the first's package-id / wallet-pair-token literals and break\n// the already-running app. This pure function is the single decision\n// point: it maps (appRoot, effective stack, home stack) → the output\n// dir codegen owns for THIS stack.\n//\n// - Home stack (`effectiveStack === homeStack`, no explicit\n// `--stack`/`$DEVSTACK_STACK` override) → `<appRoot>/src/generated`\n// (canonical, unchanged, committed-ignored).\n// - Non-home stack (`test`/`e2e`/`demo`/...) →\n// `<appRoot>/.devstack/stacks/<effectiveStack>/generated` — a\n// sibling of that stack's manifest at\n// `.devstack/stacks/<stack>/manifest.json`, already gitignored +\n// tsconfig-excluded.\n// - An app that sets `codegen.outputDir` / `codegen.stackSubdir`\n// explicitly (`defineDevstack({ codegen })`) keeps that behavior\n// verbatim (back-compat escape hatch); per-stack isolation is then\n// the app's responsibility.\n//\n// The decision is made ONCE here, at the boot seam where both the home\n// and effective stack names are in scope, and the resolved absolute\n// `outputDir` is then recorded in the per-stack manifest\n// (`codegen.generatedDir`) so the reader (the Vite plugin) consults the\n// SAME location the writer chose — read and write are gated by one\n// decision, not two. Pure + unit-testable; no `process.env`, no I/O.\n\nimport { isAbsolute, resolve } from 'node:path';\n\nexport interface ResolveCodegenOutputInput {\n\t/** The user application root — codegen output is resolved against\n\t * it. */\n\treadonly appRoot: string;\n\t/** The stack the supervisor is actually running (the resolved\n\t * identity's stack: explicit `--stack`/`$DEVSTACK_STACK` >\n\t * `config.stackName` > inferred). */\n\treadonly effectiveStack: string;\n\t/** The config's declared `stackName` (`stack.options.stackName`).\n\t * `undefined` when the config declares none then there is no\n\t * override to diverge from, so the run is treated as home. */\n\treadonly homeStack: string | undefined;\n\t/** Explicit `defineDevstack({ codegen: { outputDir } })` value, if\n\t * the app pinned one. Honored verbatim (back-compat) — relative\n\t * paths resolve against `appRoot`. */\n\treadonly explicitOutputDir?: string | undefined;\n\t/** Explicit `defineDevstack({ codegen: { stackSubdir } })` value, if\n\t * the app pinned one. Passed through unchanged. */\n\treadonly explicitStackSubdir?: string | null | undefined;\n}\n\nexport interface ResolvedCodegenOutput {\n\t/** Absolute path to the directory codegen owns and overwrites for\n\t * THIS stack. Recorded in the manifest as `codegen.generatedDir`. */\n\treadonly outputDir: string;\n\t/** Per-stack subdirectory under `outputDir`, threaded through to\n\t * `CodegenRoot.stackSubdir` unchanged. `null` for the\n\t * home/non-home default rules (the `.devstack/stacks/<stack>`\n\t * location already isolates per stack, so no extra subdir is\n\t * needed); only an explicit `defineDevstack({ codegen.stackSubdir })`\n\t * populates it. */\n\treadonly stackSubdir: string | null;\n}\n\n/** Default home-stack output dir (relative to `appRoot`). */\nconst HOME_OUTPUT_SUBPATH = 'src/generated';\n\n/** Resolve `<appRoot>/.devstack/stacks/<stack>/generated` for a\n * non-home stack — sibling of that stack's manifest. */\nconst nonHomeOutputDir = (appRoot: string, stack: string): string =>\n\tresolve(appRoot, '.devstack', 'stacks', stack, 'generated');\n\n/**\n * Resolve the codegen output location for a stack. See the module\n * header for the rule. Pure — same inputs always yield the same\n * absolute paths.\n *\n * Precedence:\n * 1. Explicit `defineDevstack({ codegen.outputDir })` → honored\n * verbatim (relative → resolved against `appRoot`). The explicit\n * `stackSubdir` rides along.\n * 2. Home stack (`effectiveStack === homeStack`, or no `homeStack`\n * declared) → `<appRoot>/src/generated`, `stackSubdir: null`.\n * 3. Non-home stack → `<appRoot>/.devstack/stacks/<effectiveStack>/generated`,\n * `stackSubdir: null`.\n */\nexport const resolveCodegenOutput = (input: ResolveCodegenOutputInput): ResolvedCodegenOutput => {\n\tconst { appRoot, effectiveStack, homeStack } = input;\n\tconst explicitStackSubdir = input.explicitStackSubdir ?? null;\n\n\t// (1) Explicit override wins — back-compat escape hatch. Per-stack\n\t// isolation is the app's responsibility once it pins `outputDir`.\n\tif (input.explicitOutputDir !== undefined) {\n\t\tconst target = input.explicitOutputDir;\n\t\treturn {\n\t\t\toutputDir: isAbsolute(target) ? target : resolve(appRoot, target),\n\t\t\tstackSubdir: explicitStackSubdir,\n\t\t};\n\t}\n\n\t// `homeStack === undefined` means the config declared no `stackName`,\n\t// so there is no config value for `effectiveStack` to diverge from —\n\t// the run is the home run by definition (it falls through to the\n\t// inferred/default stack with no explicit override). Collapse it to\n\t// `effectiveStack` so the equality below reads `true`.\n\tconst isHome = effectiveStack === (homeStack ?? effectiveStack);\n\n\t// (2) Home → canonical `src/generated`. (3) Non-home → per-stack\n\t// `.devstack/stacks/<stack>/generated`. Neither uses `stackSubdir`\n\t// (the `.devstack` path already isolates per stack); an explicit\n\t// `stackSubdir` only rides the explicit-outputDir branch above.\n\treturn {\n\t\toutputDir: isHome\n\t\t\t? resolve(appRoot, HOME_OUTPUT_SUBPATH)\n\t\t\t: nonHomeOutputDir(appRoot, effectiveStack),\n\t\tstackSubdir: null,\n\t};\n};\n"],"mappings":";;;AAmEA,MAAM,sBAAsB;;;AAI5B,MAAM,oBAAoB,SAAiB,UAC1C,QAAQ,SAAS,aAAa,UAAU,OAAO,YAAY;;;;;;;;;;;;;;;AAgB5D,MAAa,wBAAwB,UAA4D;CAChG,MAAM,EAAE,SAAS,gBAAgB,cAAc;CAC/C,MAAM,sBAAsB,MAAM,uBAAuB;AAIzD,KAAI,MAAM,sBAAsB,KAAA,GAAW;EAC1C,MAAM,SAAS,MAAM;AACrB,SAAO;GACN,WAAW,WAAW,OAAO,GAAG,SAAS,QAAQ,SAAS,OAAO;GACjE,aAAa;GACb;;AAcF,QAAO;EACN,WAPc,oBAAoB,aAAa,kBAQ5C,QAAQ,SAAS,oBAAoB,GACrC,iBAAiB,SAAS,eAAe;EAC5C,aAAa;EACb"}
1
+ {"version":3,"file":"output-location.mjs","names":[],"sources":["../../../src/orchestrators/codegen/output-location.ts"],"sourcesContent":["// Per-stack codegen output-location resolver.\n//\n// notes/per-stack-codegen-design.md §\"Output-location rule\": two stacks\n// of the same app must not emit Move codegen into the SAME\n// `src/generated/` dir — the second stack's `codegen.emitted` would\n// clobber the first's package-id / wallet-pair-token literals and break\n// the already-running app. This pure function is the single decision\n// point: it maps (appRoot, effective stack, primary stack) → the output\n// dir codegen owns for THIS stack.\n//\n// - Primary stack (`effectiveStack === primaryStack`, no explicit\n// `--stack`/`$DEVSTACK_STACK` override) → `<appRoot>/src/generated`\n// (canonical, unchanged, committed-ignored).\n// - Secondary stack (`test`/`e2e`/`demo`/...) →\n// `<appRoot>/.devstack/stacks/<effectiveStack>/generated` — a\n// sibling of that stack's manifest at\n// `.devstack/stacks/<stack>/manifest.json`, already gitignored +\n// tsconfig-excluded.\n// - An app that sets `codegen.outputDir` / `codegen.stackSubdir`\n// explicitly (`defineDevstack({ codegen })`) keeps that behavior\n// verbatim (back-compat escape hatch); per-stack isolation is then\n// the app's responsibility.\n//\n// The decision is made ONCE here, at the boot seam where both the\n// primary and effective stack names are in scope, and the resolved\n// absolute `outputDir` is then recorded in the per-stack manifest\n// (`codegen.generatedDir`) so the reader (the Vite plugin) consults the\n// SAME location the writer chose — read and write are gated by one\n// decision, not two. Pure + unit-testable; no `process.env`, no I/O.\n\nimport { isAbsolute, resolve } from 'node:path';\n\nexport interface ResolveCodegenOutputInput {\n\t/** The user application root — codegen output is resolved against\n\t * it. */\n\treadonly appRoot: string;\n\t/** The stack the supervisor is actually running (the resolved\n\t * identity's stack: explicit `--stack`/`$DEVSTACK_STACK` >\n\t * `config.stackName` > inferred). */\n\treadonly effectiveStack: string;\n\t/** The config's declared `stackName` (`stack.options.stackName`) —\n\t * the primary stack. `undefined` when the config declares none —\n\t * then there is no override to diverge from, so the run is treated\n\t * as primary. */\n\treadonly primaryStack: string | undefined;\n\t/** Explicit `defineDevstack({ codegen: { outputDir } })` value, if\n\t * the app pinned one. Honored verbatim (back-compat) — relative\n\t * paths resolve against `appRoot`. */\n\treadonly explicitOutputDir?: string | undefined;\n\t/** Explicit `defineDevstack({ codegen: { stackSubdir } })` value, if\n\t * the app pinned one. Passed through unchanged. */\n\treadonly explicitStackSubdir?: string | null | undefined;\n}\n\nexport interface ResolvedCodegenOutput {\n\t/** Absolute path to the directory codegen owns and overwrites for\n\t * THIS stack. Recorded in the manifest as `codegen.generatedDir`. */\n\treadonly outputDir: string;\n\t/** Per-stack subdirectory under `outputDir`, threaded through to\n\t * `CodegenRoot.stackSubdir` unchanged. `null` for the\n\t * primary/secondary default rules (the `.devstack/stacks/<stack>`\n\t * location already isolates per stack, so no extra subdir is\n\t * needed); only an explicit `defineDevstack({ codegen.stackSubdir })`\n\t * populates it. */\n\treadonly stackSubdir: string | null;\n}\n\n/** Default primary-stack output dir (relative to `appRoot`). */\nconst PRIMARY_OUTPUT_SUBPATH = 'src/generated';\n\n/** Resolve `<appRoot>/.devstack/stacks/<stack>/generated` for a\n * secondary stack — sibling of that stack's manifest. */\nconst secondaryOutputDir = (appRoot: string, stack: string): string =>\n\tresolve(appRoot, '.devstack', 'stacks', stack, 'generated');\n\n/**\n * Resolve the codegen output location for a stack. See the module\n * header for the rule. Pure — same inputs always yield the same\n * absolute paths.\n *\n * Precedence:\n * 1. Explicit `defineDevstack({ codegen.outputDir })` → honored\n * verbatim (relative → resolved against `appRoot`). The explicit\n * `stackSubdir` rides along.\n * 2. Primary stack (`effectiveStack === primaryStack`, or no\n * `primaryStack` declared) → `<appRoot>/src/generated`,\n * `stackSubdir: null`.\n * 3. Secondary stack → `<appRoot>/.devstack/stacks/<effectiveStack>/generated`,\n * `stackSubdir: null`.\n */\nexport const resolveCodegenOutput = (input: ResolveCodegenOutputInput): ResolvedCodegenOutput => {\n\tconst { appRoot, effectiveStack, primaryStack } = input;\n\tconst explicitStackSubdir = input.explicitStackSubdir ?? null;\n\n\t// (1) Explicit override wins — back-compat escape hatch. Per-stack\n\t// isolation is the app's responsibility once it pins `outputDir`.\n\tif (input.explicitOutputDir !== undefined) {\n\t\tconst target = input.explicitOutputDir;\n\t\treturn {\n\t\t\toutputDir: isAbsolute(target) ? target : resolve(appRoot, target),\n\t\t\tstackSubdir: explicitStackSubdir,\n\t\t};\n\t}\n\n\t// `primaryStack === undefined` means the config declared no `stackName`,\n\t// so there is no config value for `effectiveStack` to diverge from —\n\t// the run is the primary run by definition (it falls through to the\n\t// inferred/default stack with no explicit override). Collapse it to\n\t// `effectiveStack` so the equality below reads `true`.\n\tconst isPrimary = effectiveStack === (primaryStack ?? effectiveStack);\n\n\t// (2) Primary → canonical `src/generated`. (3) Secondary → per-stack\n\t// `.devstack/stacks/<stack>/generated`. Neither uses `stackSubdir`\n\t// (the `.devstack` path already isolates per stack); an explicit\n\t// `stackSubdir` only rides the explicit-outputDir branch above.\n\treturn {\n\t\toutputDir: isPrimary\n\t\t\t? resolve(appRoot, PRIMARY_OUTPUT_SUBPATH)\n\t\t\t: secondaryOutputDir(appRoot, effectiveStack),\n\t\tstackSubdir: null,\n\t};\n};\n"],"mappings":";;;AAoEA,MAAM,yBAAyB;;;AAI/B,MAAM,sBAAsB,SAAiB,UAC5C,QAAQ,SAAS,aAAa,UAAU,OAAO,YAAY;;;;;;;;;;;;;;;;AAiB5D,MAAa,wBAAwB,UAA4D;CAChG,MAAM,EAAE,SAAS,gBAAgB,iBAAiB;CAClD,MAAM,sBAAsB,MAAM,uBAAuB;AAIzD,KAAI,MAAM,sBAAsB,KAAA,GAAW;EAC1C,MAAM,SAAS,MAAM;AACrB,SAAO;GACN,WAAW,WAAW,OAAO,GAAG,SAAS,QAAQ,SAAS,OAAO;GACjE,aAAa;GACb;;AAcF,QAAO;EACN,WAPiB,oBAAoB,gBAAgB,kBAQlD,QAAQ,SAAS,uBAAuB,GACxC,mBAAmB,SAAS,eAAe;EAC9C,aAAa;EACb"}
@@ -35,7 +35,7 @@ declare class ManifestExtrasLookupError extends ManifestExtrasLookupError_base {
35
35
  * `src/generated/`. */
36
36
  interface ManifestCodegen {
37
37
  /** Absolute path to the directory codegen emitted into for this
38
- * stack (home → `<appRoot>/src/generated`; non-home
38
+ * stack (primary → `<appRoot>/src/generated`; secondary
39
39
  * `<appRoot>/.devstack/stacks/<stack>/generated`). */
40
40
  readonly generatedDir: string;
41
41
  }
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.mjs","names":[],"sources":["../../src/substrate/manifest.ts"],"sourcesContent":["// Manifest envelope schema.\n//\n// Architecture § Manifest data model: envelope shape is fixed at L0,\n// per-service projection is each plugin's responsibility (lives in\n// the Codegenable contribution). Build integrations read the\n// envelope; codegen output carries the typed per-service slice.\n//\n// L0 owns:\n// - the envelope (identity tuple, manifestVersion, services slot,\n// endpoints lookup, opaque extras),\n// - the endpoint-declaration shape (decl emitted by Routable; the\n// manifest writer at L3 walks them).\n\nimport { Effect, Schema } from 'effect';\n\n/** Tagged failure when a plugin's manifest-extras contribution does\n * not resolve to a plain record. STYLE_GUIDE §2.2 — substrate (L0)\n * errors use `Schema.TaggedErrorClass`; downstream classifiers\n * `catchTag('ManifestExtrasInvalid', ...)` instead of sniffing the\n * message. */\nexport class ManifestExtrasInvalid extends Schema.TaggedErrorClass<ManifestExtrasInvalid>()(\n\t'ManifestExtrasInvalid',\n\t{\n\t\tdetail: Schema.String,\n\t},\n) {}\n\n/** Tagged failure when a plugin's `extras` factory references a\n * resource the host cannot resolve — either the resource id is not\n * registered with the supervisor, or it has not produced a value\n * yet at the point the factory ran. Thrown synchronously from the\n * user-supplied `ctx.value(...)` closure; `resolveManifestExtras`\n * catches the throw and surfaces it as a typed failure so callers\n * classify via `catchTag('ManifestExtrasLookupError', ...)` rather\n * than die-cause inspection. */\nexport class ManifestExtrasLookupError extends Schema.TaggedErrorClass<ManifestExtrasLookupError>()(\n\t'ManifestExtrasLookupError',\n\t{\n\t\tkind: Schema.Literals(['unknown-resource', 'unresolved-resource']),\n\t\tresourceId: Schema.String,\n\t},\n) {}\n\n/** Codegen metadata recorded per stack. The supervisor writes the\n * resolved absolute output dir here at manifest-flush time so the\n * read-side build integrations (the Vite plugin) point an `@generated`\n * alias at the EXACT dir codegen emitted into for THIS stack — read\n * and write share one decision (see\n * `orchestrators/codegen/output-location.ts`). Optional + additive:\n * manifests written before this field existed (and stacks that somehow\n * flush without it) still decode; consumers fall back to\n * `src/generated/`. */\nexport interface ManifestCodegen {\n\t/** Absolute path to the directory codegen emitted into for this\n\t * stack (home → `<appRoot>/src/generated`; non-home →\n\t * `<appRoot>/.devstack/stacks/<stack>/generated`). */\n\treadonly generatedDir: string;\n}\n\n/** Manifest envelope. The `services` slot is open (`unknown`) at\n * the envelope level; each plugin's Codegenable contribution\n * emits a typed file the consumer imports for the typed shape.\n */\nexport interface ManifestEnvelope {\n\treadonly identity: {\n\t\treadonly app: string;\n\t\treadonly stack: string;\n\t\treadonly chain: string;\n\t};\n\treadonly manifestVersion: number;\n\treadonly services: Readonly<Record<string, unknown>>;\n\treadonly endpoints: Readonly<Record<string, EndpointEntry>>;\n\treadonly extras: Readonly<Record<string, unknown>>;\n\t/** Per-stack codegen metadata. Optional — absent in older\n\t * manifests; the reader falls back to `src/generated/`. */\n\treadonly codegen?: ManifestCodegen;\n}\n\nexport type ManifestExtras = Readonly<Record<string, unknown>>;\n\nexport interface ManifestExtrasContext {\n\treadonly value: (resource: { readonly id: string }) => unknown;\n}\n\nexport type ManifestExtrasInput =\n\t| ManifestExtras\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t| Effect.Effect<ManifestExtras, any, never>\n\t| ((\n\t\t\tctx: ManifestExtrasContext,\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t ) => ManifestExtras | Effect.Effect<ManifestExtras, any, never>);\n\nconst isRecord = (value: unknown): value is ManifestExtras =>\n\ttypeof value === 'object' && value !== null && !Array.isArray(value);\n\nexport const resolveManifestExtras = (\n\tinput: ManifestExtrasInput | undefined,\n\tctx: ManifestExtrasContext,\n): Effect.Effect<ManifestExtras, ManifestExtrasInvalid | ManifestExtrasLookupError, never> =>\n\tEffect.gen(function* () {\n\t\tif (input === undefined) return {};\n\t\t// The user-supplied `extras` factory invokes `ctx.value(...)`\n\t\t// synchronously; a missing/unresolved resource throws a\n\t\t// `ManifestExtrasLookupError`. `Effect.try` with a typed\n\t\t// `catch` mapper promotes that throw into the typed failure\n\t\t// channel so callers `catchTag` it instead of having to inspect\n\t\t// the die-cause. Any non-tagged throw is rethrown to preserve\n\t\t// the existing defect semantics for genuine programmer errors\n\t\t// inside the factory body.\n\t\tconst resolvedEffect = Effect.isEffect(input)\n\t\t\t? input\n\t\t\t: typeof input === 'function'\n\t\t\t\t? Effect.try({\n\t\t\t\t\t\ttry: () => input(ctx),\n\t\t\t\t\t\tcatch: (cause) => {\n\t\t\t\t\t\t\tif (cause instanceof ManifestExtrasLookupError) return cause;\n\t\t\t\t\t\t\tthrow cause;\n\t\t\t\t\t\t},\n\t\t\t\t\t}).pipe(\n\t\t\t\t\t\tEffect.flatMap((value) => (Effect.isEffect(value) ? value : Effect.succeed(value))),\n\t\t\t\t\t)\n\t\t\t\t: Effect.succeed(input);\n\t\tconst resolved = yield* resolvedEffect;\n\t\tif (!isRecord(resolved)) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew ManifestExtrasInvalid({\n\t\t\t\t\tdetail: 'manifest extras must resolve to a plain record',\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\treturn resolved;\n\t});\n\n/** Flat endpoint entry — the manifest's load-bearing surface for\n * build integrations. */\nexport interface EndpointEntry {\n\treadonly name: string;\n\treadonly url: string;\n\treadonly displayUrl: string | null;\n\treadonly wireProtocol: 'http' | 'h2c' | 'tcp';\n\treadonly pluginKey: string;\n\treadonly endpointKey: string;\n}\n\n/** Schema for runtime validation of an on-disk manifest. Build\n * integrations decode through this on read. */\nexport const ManifestEnvelopeSchema = Schema.Struct({\n\tidentity: Schema.Struct({\n\t\tapp: Schema.String,\n\t\tstack: Schema.String,\n\t\tchain: Schema.String,\n\t}),\n\tmanifestVersion: Schema.Number,\n\tservices: Schema.Record(Schema.String, Schema.Unknown),\n\tendpoints: Schema.Record(\n\t\tSchema.String,\n\t\tSchema.Struct({\n\t\t\tname: Schema.String,\n\t\t\turl: Schema.String,\n\t\t\tdisplayUrl: Schema.NullOr(Schema.String),\n\t\t\twireProtocol: Schema.Literals(['http', 'h2c', 'tcp']),\n\t\t\tpluginKey: Schema.String,\n\t\t\tendpointKey: Schema.String,\n\t\t}),\n\t),\n\textras: Schema.Record(Schema.String, Schema.Unknown),\n\t// Optional + additive — a manifest written before this field\n\t// existed still decodes (the key is simply absent). The Vite plugin\n\t// reads `codegen.generatedDir` to point its `@generated` alias; on a\n\t// miss it falls back to `src/generated/`.\n\tcodegen: Schema.optional(\n\t\tSchema.Struct({\n\t\t\tgeneratedDir: Schema.String,\n\t\t}),\n\t),\n});\n"],"mappings":";;;;;;;AAoBA,IAAa,wBAAb,cAA2C,OAAO,kBAAyC,CAC1F,yBACA,EACC,QAAQ,OAAO,QACf,CACD,CAAC;;;;;;;;;AAUF,IAAa,4BAAb,cAA+C,OAAO,kBAA6C,CAClG,6BACA;CACC,MAAM,OAAO,SAAS,CAAC,oBAAoB,sBAAsB,CAAC;CAClE,YAAY,OAAO;CACnB,CACD,CAAC;AAoDF,MAAM,YAAY,UACjB,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,MAAM;AAErE,MAAa,yBACZ,OACA,QAEA,OAAO,IAAI,aAAa;AACvB,KAAI,UAAU,KAAA,EAAW,QAAO,EAAE;CAsBlC,MAAM,WAAW,OAbM,OAAO,SAAS,MAAM,GAC1C,QACA,OAAO,UAAU,aAChB,OAAO,IAAI;EACX,WAAW,MAAM,IAAI;EACrB,QAAQ,UAAU;AACjB,OAAI,iBAAiB,0BAA2B,QAAO;AACvD,SAAM;;EAEP,CAAC,CAAC,KACF,OAAO,SAAS,UAAW,OAAO,SAAS,MAAM,GAAG,QAAQ,OAAO,QAAQ,MAAM,CAAE,CACnF,GACA,OAAO,QAAQ,MAAM;AAEzB,KAAI,CAAC,SAAS,SAAS,CACtB,QAAO,OAAO,OAAO,KACpB,IAAI,sBAAsB,EACzB,QAAQ,kDACR,CAAC,CACF;AAEF,QAAO;EACN;;;AAeH,MAAa,yBAAyB,OAAO,OAAO;CACnD,UAAU,OAAO,OAAO;EACvB,KAAK,OAAO;EACZ,OAAO,OAAO;EACd,OAAO,OAAO;EACd,CAAC;CACF,iBAAiB,OAAO;CACxB,UAAU,OAAO,OAAO,OAAO,QAAQ,OAAO,QAAQ;CACtD,WAAW,OAAO,OACjB,OAAO,QACP,OAAO,OAAO;EACb,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,YAAY,OAAO,OAAO,OAAO,OAAO;EACxC,cAAc,OAAO,SAAS;GAAC;GAAQ;GAAO;GAAM,CAAC;EACrD,WAAW,OAAO;EAClB,aAAa,OAAO;EACpB,CAAC,CACF;CACD,QAAQ,OAAO,OAAO,OAAO,QAAQ,OAAO,QAAQ;CAKpD,SAAS,OAAO,SACf,OAAO,OAAO,EACb,cAAc,OAAO,QACrB,CAAC,CACF;CACD,CAAC"}
1
+ {"version":3,"file":"manifest.mjs","names":[],"sources":["../../src/substrate/manifest.ts"],"sourcesContent":["// Manifest envelope schema.\n//\n// Architecture § Manifest data model: envelope shape is fixed at L0,\n// per-service projection is each plugin's responsibility (lives in\n// the Codegenable contribution). Build integrations read the\n// envelope; codegen output carries the typed per-service slice.\n//\n// L0 owns:\n// - the envelope (identity tuple, manifestVersion, services slot,\n// endpoints lookup, opaque extras),\n// - the endpoint-declaration shape (decl emitted by Routable; the\n// manifest writer at L3 walks them).\n\nimport { Effect, Schema } from 'effect';\n\n/** Tagged failure when a plugin's manifest-extras contribution does\n * not resolve to a plain record. STYLE_GUIDE §2.2 — substrate (L0)\n * errors use `Schema.TaggedErrorClass`; downstream classifiers\n * `catchTag('ManifestExtrasInvalid', ...)` instead of sniffing the\n * message. */\nexport class ManifestExtrasInvalid extends Schema.TaggedErrorClass<ManifestExtrasInvalid>()(\n\t'ManifestExtrasInvalid',\n\t{\n\t\tdetail: Schema.String,\n\t},\n) {}\n\n/** Tagged failure when a plugin's `extras` factory references a\n * resource the host cannot resolve — either the resource id is not\n * registered with the supervisor, or it has not produced a value\n * yet at the point the factory ran. Thrown synchronously from the\n * user-supplied `ctx.value(...)` closure; `resolveManifestExtras`\n * catches the throw and surfaces it as a typed failure so callers\n * classify via `catchTag('ManifestExtrasLookupError', ...)` rather\n * than die-cause inspection. */\nexport class ManifestExtrasLookupError extends Schema.TaggedErrorClass<ManifestExtrasLookupError>()(\n\t'ManifestExtrasLookupError',\n\t{\n\t\tkind: Schema.Literals(['unknown-resource', 'unresolved-resource']),\n\t\tresourceId: Schema.String,\n\t},\n) {}\n\n/** Codegen metadata recorded per stack. The supervisor writes the\n * resolved absolute output dir here at manifest-flush time so the\n * read-side build integrations (the Vite plugin) point an `@generated`\n * alias at the EXACT dir codegen emitted into for THIS stack — read\n * and write share one decision (see\n * `orchestrators/codegen/output-location.ts`). Optional + additive:\n * manifests written before this field existed (and stacks that somehow\n * flush without it) still decode; consumers fall back to\n * `src/generated/`. */\nexport interface ManifestCodegen {\n\t/** Absolute path to the directory codegen emitted into for this\n\t * stack (primary → `<appRoot>/src/generated`; secondary →\n\t * `<appRoot>/.devstack/stacks/<stack>/generated`). */\n\treadonly generatedDir: string;\n}\n\n/** Manifest envelope. The `services` slot is open (`unknown`) at\n * the envelope level; each plugin's Codegenable contribution\n * emits a typed file the consumer imports for the typed shape.\n */\nexport interface ManifestEnvelope {\n\treadonly identity: {\n\t\treadonly app: string;\n\t\treadonly stack: string;\n\t\treadonly chain: string;\n\t};\n\treadonly manifestVersion: number;\n\treadonly services: Readonly<Record<string, unknown>>;\n\treadonly endpoints: Readonly<Record<string, EndpointEntry>>;\n\treadonly extras: Readonly<Record<string, unknown>>;\n\t/** Per-stack codegen metadata. Optional — absent in older\n\t * manifests; the reader falls back to `src/generated/`. */\n\treadonly codegen?: ManifestCodegen;\n}\n\nexport type ManifestExtras = Readonly<Record<string, unknown>>;\n\nexport interface ManifestExtrasContext {\n\treadonly value: (resource: { readonly id: string }) => unknown;\n}\n\nexport type ManifestExtrasInput =\n\t| ManifestExtras\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t| Effect.Effect<ManifestExtras, any, never>\n\t| ((\n\t\t\tctx: ManifestExtrasContext,\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t ) => ManifestExtras | Effect.Effect<ManifestExtras, any, never>);\n\nconst isRecord = (value: unknown): value is ManifestExtras =>\n\ttypeof value === 'object' && value !== null && !Array.isArray(value);\n\nexport const resolveManifestExtras = (\n\tinput: ManifestExtrasInput | undefined,\n\tctx: ManifestExtrasContext,\n): Effect.Effect<ManifestExtras, ManifestExtrasInvalid | ManifestExtrasLookupError, never> =>\n\tEffect.gen(function* () {\n\t\tif (input === undefined) return {};\n\t\t// The user-supplied `extras` factory invokes `ctx.value(...)`\n\t\t// synchronously; a missing/unresolved resource throws a\n\t\t// `ManifestExtrasLookupError`. `Effect.try` with a typed\n\t\t// `catch` mapper promotes that throw into the typed failure\n\t\t// channel so callers `catchTag` it instead of having to inspect\n\t\t// the die-cause. Any non-tagged throw is rethrown to preserve\n\t\t// the existing defect semantics for genuine programmer errors\n\t\t// inside the factory body.\n\t\tconst resolvedEffect = Effect.isEffect(input)\n\t\t\t? input\n\t\t\t: typeof input === 'function'\n\t\t\t\t? Effect.try({\n\t\t\t\t\t\ttry: () => input(ctx),\n\t\t\t\t\t\tcatch: (cause) => {\n\t\t\t\t\t\t\tif (cause instanceof ManifestExtrasLookupError) return cause;\n\t\t\t\t\t\t\tthrow cause;\n\t\t\t\t\t\t},\n\t\t\t\t\t}).pipe(\n\t\t\t\t\t\tEffect.flatMap((value) => (Effect.isEffect(value) ? value : Effect.succeed(value))),\n\t\t\t\t\t)\n\t\t\t\t: Effect.succeed(input);\n\t\tconst resolved = yield* resolvedEffect;\n\t\tif (!isRecord(resolved)) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew ManifestExtrasInvalid({\n\t\t\t\t\tdetail: 'manifest extras must resolve to a plain record',\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\treturn resolved;\n\t});\n\n/** Flat endpoint entry — the manifest's load-bearing surface for\n * build integrations. */\nexport interface EndpointEntry {\n\treadonly name: string;\n\treadonly url: string;\n\treadonly displayUrl: string | null;\n\treadonly wireProtocol: 'http' | 'h2c' | 'tcp';\n\treadonly pluginKey: string;\n\treadonly endpointKey: string;\n}\n\n/** Schema for runtime validation of an on-disk manifest. Build\n * integrations decode through this on read. */\nexport const ManifestEnvelopeSchema = Schema.Struct({\n\tidentity: Schema.Struct({\n\t\tapp: Schema.String,\n\t\tstack: Schema.String,\n\t\tchain: Schema.String,\n\t}),\n\tmanifestVersion: Schema.Number,\n\tservices: Schema.Record(Schema.String, Schema.Unknown),\n\tendpoints: Schema.Record(\n\t\tSchema.String,\n\t\tSchema.Struct({\n\t\t\tname: Schema.String,\n\t\t\turl: Schema.String,\n\t\t\tdisplayUrl: Schema.NullOr(Schema.String),\n\t\t\twireProtocol: Schema.Literals(['http', 'h2c', 'tcp']),\n\t\t\tpluginKey: Schema.String,\n\t\t\tendpointKey: Schema.String,\n\t\t}),\n\t),\n\textras: Schema.Record(Schema.String, Schema.Unknown),\n\t// Optional + additive — a manifest written before this field\n\t// existed still decodes (the key is simply absent). The Vite plugin\n\t// reads `codegen.generatedDir` to point its `@generated` alias; on a\n\t// miss it falls back to `src/generated/`.\n\tcodegen: Schema.optional(\n\t\tSchema.Struct({\n\t\t\tgeneratedDir: Schema.String,\n\t\t}),\n\t),\n});\n"],"mappings":";;;;;;;AAoBA,IAAa,wBAAb,cAA2C,OAAO,kBAAyC,CAC1F,yBACA,EACC,QAAQ,OAAO,QACf,CACD,CAAC;;;;;;;;;AAUF,IAAa,4BAAb,cAA+C,OAAO,kBAA6C,CAClG,6BACA;CACC,MAAM,OAAO,SAAS,CAAC,oBAAoB,sBAAsB,CAAC;CAClE,YAAY,OAAO;CACnB,CACD,CAAC;AAoDF,MAAM,YAAY,UACjB,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,MAAM;AAErE,MAAa,yBACZ,OACA,QAEA,OAAO,IAAI,aAAa;AACvB,KAAI,UAAU,KAAA,EAAW,QAAO,EAAE;CAsBlC,MAAM,WAAW,OAbM,OAAO,SAAS,MAAM,GAC1C,QACA,OAAO,UAAU,aAChB,OAAO,IAAI;EACX,WAAW,MAAM,IAAI;EACrB,QAAQ,UAAU;AACjB,OAAI,iBAAiB,0BAA2B,QAAO;AACvD,SAAM;;EAEP,CAAC,CAAC,KACF,OAAO,SAAS,UAAW,OAAO,SAAS,MAAM,GAAG,QAAQ,OAAO,QAAQ,MAAM,CAAE,CACnF,GACA,OAAO,QAAQ,MAAM;AAEzB,KAAI,CAAC,SAAS,SAAS,CACtB,QAAO,OAAO,OAAO,KACpB,IAAI,sBAAsB,EACzB,QAAQ,kDACR,CAAC,CACF;AAEF,QAAO;EACN;;;AAeH,MAAa,yBAAyB,OAAO,OAAO;CACnD,UAAU,OAAO,OAAO;EACvB,KAAK,OAAO;EACZ,OAAO,OAAO;EACd,OAAO,OAAO;EACd,CAAC;CACF,iBAAiB,OAAO;CACxB,UAAU,OAAO,OAAO,OAAO,QAAQ,OAAO,QAAQ;CACtD,WAAW,OAAO,OACjB,OAAO,QACP,OAAO,OAAO;EACb,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,YAAY,OAAO,OAAO,OAAO,OAAO;EACxC,cAAc,OAAO,SAAS;GAAC;GAAQ;GAAO;GAAM,CAAC;EACrD,WAAW,OAAO;EAClB,aAAa,OAAO;EACpB,CAAC,CACF;CACD,QAAQ,OAAO,OAAO,OAAO,QAAQ,OAAO,QAAQ;CAKpD,SAAS,OAAO,SACf,OAAO,OAAO,EACb,cAAc,OAAO,QACrB,CAAC,CACF;CACD,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mysten-incubation/devstack",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Next-generation Sui devstack package.",
5
5
  "keywords": [
6
6
  "sui",