@mysten-incubation/devstack 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -11
- package/dashboard-ui/assets/{grpc-Clz0oOtc.js → grpc-CpkDu4SA.js} +1 -1
- package/dashboard-ui/assets/{index-Cmqv9kiU.js → index-jLPRmjst.js} +2 -2
- package/dashboard-ui/index.html +1 -1
- package/dist/api/inference-network.d.mts +5 -5
- package/dist/api/inference-network.mjs +5 -5
- package/dist/api/inference-network.mjs.map +1 -1
- package/dist/api/run-stack-internal.mjs +1 -3
- package/dist/api/run-stack-internal.mjs.map +1 -1
- package/dist/build-integrations/dapp-kit/index.d.mts +12 -0
- package/dist/build-integrations/dapp-kit/index.mjs +14 -1
- package/dist/build-integrations/dapp-kit/index.mjs.map +1 -1
- package/dist/build-integrations/playwright/errors.d.mts +1 -1
- package/dist/build-integrations/playwright/errors.mjs.map +1 -1
- package/dist/build-integrations/playwright/index.d.mts +2 -2
- package/dist/build-integrations/playwright/index.mjs +2 -2
- package/dist/build-integrations/playwright/wallet-context.d.mts +2 -1
- package/dist/build-integrations/playwright/wallet-context.mjs +141 -41
- package/dist/build-integrations/playwright/wallet-context.mjs.map +1 -1
- package/dist/build-integrations/runtime/dapp-kit-slot.mjs +1 -1
- package/dist/build-integrations/runtime/dapp-kit-slot.mjs.map +1 -1
- package/dist/build-integrations/vite/index.d.mts +54 -42
- package/dist/build-integrations/vite/index.mjs +239 -106
- package/dist/build-integrations/vite/index.mjs.map +1 -1
- package/dist/build-integrations/vitest/global-setup.mjs +8 -8
- package/dist/build-integrations/vitest/global-setup.mjs.map +1 -1
- package/dist/cli/main.mjs +3 -2
- package/dist/cli/main.mjs.map +1 -1
- package/dist/cli/wirings/apply.mjs +0 -1
- package/dist/cli/wirings/apply.mjs.map +1 -1
- package/dist/cli/wirings/codegen.mjs +2 -3
- package/dist/cli/wirings/codegen.mjs.map +1 -1
- package/dist/cli/wirings/dump-deployment.mjs +141 -0
- package/dist/cli/wirings/dump-deployment.mjs.map +1 -0
- package/dist/cli/wirings/snapshot.mjs +0 -1
- package/dist/cli/wirings/snapshot.mjs.map +1 -1
- package/dist/contracts/codegenable.d.mts +22 -25
- package/dist/contracts/codegenable.mjs +35 -4
- package/dist/contracts/codegenable.mjs.map +1 -1
- package/dist/contracts/config-bindings.mjs +43 -18
- package/dist/contracts/config-bindings.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/orchestrators/boot.d.mts +0 -10
- package/dist/orchestrators/boot.mjs +19 -59
- package/dist/orchestrators/boot.mjs.map +1 -1
- package/dist/orchestrators/codegen/bindings.mjs.map +1 -1
- package/dist/orchestrators/codegen/config-runtime.mjs +137 -112
- package/dist/orchestrators/codegen/config-runtime.mjs.map +1 -1
- package/dist/orchestrators/codegen/deployment-network-file.mjs +130 -0
- package/dist/orchestrators/codegen/deployment-network-file.mjs.map +1 -0
- package/dist/orchestrators/codegen/deployment-strict.mjs +77 -0
- package/dist/orchestrators/codegen/deployment-strict.mjs.map +1 -0
- package/dist/orchestrators/codegen/deployment.d.mts +63 -0
- package/dist/orchestrators/codegen/deployment.mjs +153 -0
- package/dist/orchestrators/codegen/deployment.mjs.map +1 -0
- package/dist/orchestrators/codegen/errors.mjs +7 -9
- package/dist/orchestrators/codegen/errors.mjs.map +1 -1
- package/dist/orchestrators/codegen/format.mjs +20 -1
- package/dist/orchestrators/codegen/format.mjs.map +1 -1
- package/dist/orchestrators/codegen/paths.mjs +1 -8
- package/dist/orchestrators/codegen/paths.mjs.map +1 -1
- package/dist/orchestrators/codegen/service.mjs +354 -141
- package/dist/orchestrators/codegen/service.mjs.map +1 -1
- package/dist/orchestrators/layers.mjs +0 -1
- package/dist/orchestrators/layers.mjs.map +1 -1
- package/dist/orchestrators/network-options.d.mts +37 -35
- package/dist/orchestrators/network-options.mjs +13 -10
- package/dist/orchestrators/network-options.mjs.map +1 -1
- package/dist/plugins/account/codegen.d.mts +3 -4
- package/dist/plugins/account/codegen.mjs +5 -7
- package/dist/plugins/account/codegen.mjs.map +1 -1
- package/dist/plugins/account/index.mjs +1 -3
- package/dist/plugins/account/index.mjs.map +1 -1
- package/dist/plugins/coin/codegen.mjs +5 -5
- package/dist/plugins/coin/codegen.mjs.map +1 -1
- package/dist/plugins/coin/index.mjs.map +1 -1
- package/dist/plugins/dashboard/schema/root.mjs +3 -1
- package/dist/plugins/dashboard/schema/root.mjs.map +1 -1
- package/dist/plugins/deepbook/codegen.mjs +4 -4
- package/dist/plugins/deepbook/codegen.mjs.map +1 -1
- package/dist/plugins/deepbook/index.d.mts +36 -6
- package/dist/plugins/deepbook/index.mjs +52 -16
- package/dist/plugins/deepbook/index.mjs.map +1 -1
- package/dist/plugins/internal/codegen-helpers.mjs +0 -1
- package/dist/plugins/internal/codegen-helpers.mjs.map +1 -1
- package/dist/plugins/package/codegen.d.mts +1 -12
- package/dist/plugins/package/codegen.mjs +43 -25
- package/dist/plugins/package/codegen.mjs.map +1 -1
- package/dist/plugins/package/dep-resolution.mjs +1 -1
- package/dist/plugins/package/dep-resolution.mjs.map +1 -1
- package/dist/plugins/package/index.d.mts +14 -11
- package/dist/plugins/package/index.mjs +6 -7
- package/dist/plugins/package/index.mjs.map +1 -1
- package/dist/plugins/seal/codegen.d.mts +5 -0
- package/dist/plugins/seal/codegen.mjs +55 -12
- package/dist/plugins/seal/codegen.mjs.map +1 -1
- package/dist/plugins/seal/index.d.mts +64 -19
- package/dist/plugins/seal/index.mjs +26 -36
- package/dist/plugins/seal/index.mjs.map +1 -1
- package/dist/plugins/seal/mode/fork-known.d.mts +0 -2
- package/dist/plugins/seal/mode/fork-known.mjs +11 -22
- package/dist/plugins/seal/mode/fork-known.mjs.map +1 -1
- package/dist/plugins/seal/mode/live.d.mts +36 -13
- package/dist/plugins/seal/mode/live.mjs +104 -43
- package/dist/plugins/seal/mode/live.mjs.map +1 -1
- package/dist/plugins/seal/mode/local-keygen.mjs +2 -1
- package/dist/plugins/seal/mode/local-keygen.mjs.map +1 -1
- package/dist/plugins/seal/registry-publish.d.mts +15 -3
- package/dist/plugins/seal/registry-publish.mjs.map +1 -1
- package/dist/plugins/sui/codegen.mjs +5 -4
- package/dist/plugins/sui/codegen.mjs.map +1 -1
- package/dist/plugins/sui/index.mjs +1 -2
- package/dist/plugins/sui/index.mjs.map +1 -1
- package/dist/plugins/wallet/codegen.d.mts +8 -12
- package/dist/plugins/wallet/codegen.mjs +22 -15
- package/dist/plugins/wallet/codegen.mjs.map +1 -1
- package/dist/plugins/wallet/index.d.mts +4 -3
- package/dist/plugins/wallet/index.mjs +1 -1
- package/dist/plugins/wallet/index.mjs.map +1 -1
- package/dist/plugins/wallet/service.d.mts +2 -2
- package/dist/plugins/wallet/service.mjs +2 -3
- package/dist/plugins/wallet/service.mjs.map +1 -1
- package/dist/plugins/walrus/codegen.mjs +4 -4
- package/dist/plugins/walrus/codegen.mjs.map +1 -1
- package/dist/plugins/walrus/index.d.mts +137 -8
- package/dist/plugins/walrus/index.mjs +46 -9
- package/dist/plugins/walrus/index.mjs.map +1 -1
- package/dist/plugins/walrus/mode/known-deploy.d.mts +5 -1
- package/dist/plugins/walrus/mode/known-deploy.mjs +13 -10
- package/dist/plugins/walrus/mode/known-deploy.mjs.map +1 -1
- package/dist/substrate/manifest.d.mts +17 -22
- package/dist/substrate/manifest.mjs +4 -6
- package/dist/substrate/manifest.mjs.map +1 -1
- package/dist/substrate/plugin-ctx.d.mts +6 -6
- package/dist/substrate/plugin-ctx.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/liveness.mjs.map +1 -1
- package/dist/substrate/runtime/manifest/manifest.mjs.map +1 -1
- package/dist/substrate/runtime/paths.mjs +1 -1
- package/dist/surfaces/cli/command-tree.mjs +6 -6
- package/dist/surfaces/cli/command-tree.mjs.map +1 -1
- package/dist/surfaces/cli/index.mjs +6 -4
- package/dist/surfaces/cli/index.mjs.map +1 -1
- package/dist/surfaces/tui/plain-renderer.mjs.map +1 -1
- package/package.json +2 -2
- package/dist/cli/wirings/dump-ids.mjs +0 -86
- package/dist/cli/wirings/dump-ids.mjs.map +0 -1
- package/dist/orchestrators/codegen/id-config.d.mts +0 -1
- package/dist/orchestrators/codegen/id-config.mjs +0 -107
- package/dist/orchestrators/codegen/id-config.mjs.map +0 -1
- package/dist/orchestrators/codegen/output-location.mjs +0 -32
- package/dist/orchestrators/codegen/output-location.mjs.map +0 -1
- package/dist/plugins/seal/service.mjs +0 -17
- package/dist/plugins/seal/service.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["runStricli"],"sources":["../../../src/surfaces/cli/index.ts"],"sourcesContent":["// CLI surface — top-level entry point.\n//\n// The public CLI is intentionally small: `up` is the attached operator\n// surface, `apply` is live-aware reconcile (publish to `up` when it is\n// running, one-shot otherwise), and every other command is offline/direct.\n\nimport {\n\tbuildApplication,\n\tbuildChoiceParser,\n\tbuildCommand,\n\tbuildRouteMap,\n\trun as runStricli,\n\ttext_en,\n\ttype CommandContext as StricliCommandContext,\n\ttype StricliProcess,\n} from '@stricli/core';\nimport { Effect, type Scope } from 'effect';\n\nimport { commandSchema } from './command-tree.ts';\nimport { type CliError, CliInternalError, CliUsageError, exitCodeFor } from './errors.ts';\nimport {\n\ttype CliRendererMode,\n\tENV_VARS,\n\ttype GlobalFlags,\n\ttype OutputMode,\n\ttype SnapshotStalePolicy,\n} from './flags.ts';\nimport { ExitCode } from './sysexits.ts';\nimport { parseDevstackNetworkName } from '../../api/inference-network.ts';\nimport { type CliIO, emitFailure, emitSuccess, nodeProcessIO } from './output.ts';\nimport {\n\ttype CommandResult,\n\ttype ConfigDeps,\n\ttype DoctorDeps,\n\ttype PruneDeps,\n\ttype PruneResourceScope,\n\ttype SnapshotDeps,\n\ttype StatusDeps,\n\ttype WipeDeps,\n\trunConfig,\n\trunDoctor,\n\trunPrune,\n\trunSnapshot,\n\trunStatus,\n\trunWipe,\n} from './commands/index.ts';\nimport { readDevstackVersion } from '../../cli/wirings/read-devstack-version.ts';\n\n// -----------------------------------------------------------------------------\n// Deps bundle\n// -----------------------------------------------------------------------------\n\nexport interface LifecycleCommandDeps {\n\treadonly run: (flags: GlobalFlags) => Effect.Effect<CommandResult, CliError>;\n}\n\n/** The stack-free `codegen` verb takes only a config path — it boots no\n * stack, so it needs no renderer/snapshot/identity flags. */\nexport interface CodegenCommandDeps {\n\treadonly run: (flags: {\n\t\treadonly configPath: string | undefined;\n\t}) => Effect.Effect<CommandResult, CliError>;\n}\n\n/** `dump-ids` emits the stack's `devstack-ids.json` id-config. It owns\n * its own output (file via `--out`, else stdout), so it receives `io`\n * and the resolved `outputMode` alongside the config path + destination. */\nexport interface DumpIdsCommandDeps {\n\treadonly run: (flags: {\n\t\treadonly configPath: string | undefined;\n\t\treadonly out: string | undefined;\n\t\treadonly io: CliIO;\n\t\treadonly outputMode: OutputMode;\n\t}) => Effect.Effect<CommandResult, CliError>;\n}\n\nexport interface CliDeps {\n\treadonly up: LifecycleCommandDeps;\n\treadonly apply: LifecycleCommandDeps;\n\treadonly codegen: CodegenCommandDeps;\n\treadonly dumpIds: DumpIdsCommandDeps;\n\treadonly status: StatusDeps;\n\treadonly snapshot: SnapshotDeps;\n\treadonly prune: PruneDeps;\n\treadonly doctor: DoctorDeps;\n\treadonly config: ConfigDeps;\n\treadonly wipe: WipeDeps;\n}\n\nexport interface DispatchEnv {\n\treadonly argv: ReadonlyArray<string>;\n\treadonly env: Readonly<Record<string, string | undefined>>;\n\treadonly stdinIsTty: boolean;\n\treadonly io?: CliIO;\n}\n\n// -----------------------------------------------------------------------------\n// Stricli context + buffered process\n// -----------------------------------------------------------------------------\n\ninterface BufferedProcess extends StricliProcess {\n\treadonly stdoutBuffer: Array<string>;\n\treadonly stderrBuffer: Array<string>;\n\texitCode?: number | string | null;\n}\n\ninterface TrackedIO extends CliIO {\n\treadonly touched: () => boolean;\n\treadonly lastExitCode: () => number | null;\n}\n\ninterface DevstackCliContext extends StricliCommandContext {\n\treadonly deps: CliDeps;\n\treadonly env: Readonly<Record<string, string | undefined>>;\n\treadonly stdinIsTty: boolean;\n\treadonly io: TrackedIO;\n\treadonly process: BufferedProcess;\n}\n\nconst makeBufferedProcess = (\n\tenv: Readonly<Record<string, string | undefined>>,\n): BufferedProcess => {\n\tconst stdoutBuffer: Array<string> = [];\n\tconst stderrBuffer: Array<string> = [];\n\treturn {\n\t\tstdoutBuffer,\n\t\tstderrBuffer,\n\t\tenv,\n\t\tstdout: {\n\t\t\twrite: (str) => {\n\t\t\t\tstdoutBuffer.push(str);\n\t\t\t},\n\t\t},\n\t\tstderr: {\n\t\t\twrite: (str) => {\n\t\t\t\tstderrBuffer.push(str);\n\t\t\t},\n\t\t},\n\t};\n};\n\nconst trackIO = (io: CliIO): TrackedIO => {\n\tlet touched = false;\n\tlet lastExitCode: number | null = null;\n\treturn {\n\t\ttouched: () => touched,\n\t\tlastExitCode: () => lastExitCode,\n\t\twriteStdout: (line) =>\n\t\t\tio.writeStdout(line).pipe(\n\t\t\t\tEffect.tap(() =>\n\t\t\t\t\tEffect.sync(() => {\n\t\t\t\t\t\ttouched = true;\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t),\n\t\twriteStderr: (line) =>\n\t\t\tio.writeStderr(line).pipe(\n\t\t\t\tEffect.tap(() =>\n\t\t\t\t\tEffect.sync(() => {\n\t\t\t\t\t\ttouched = true;\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t),\n\t\tsetExitCode: (code) =>\n\t\t\tio.setExitCode(code).pipe(\n\t\t\t\tEffect.tap(() =>\n\t\t\t\t\tEffect.sync(() => {\n\t\t\t\t\t\ttouched = true;\n\t\t\t\t\t\tlastExitCode = code;\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t),\n\t};\n};\n\n// -----------------------------------------------------------------------------\n// Flag models\n// -----------------------------------------------------------------------------\n\ninterface IdentityFlags {\n\treadonly json?: boolean;\n\treadonly app?: string;\n\treadonly stack?: string;\n\treadonly stateDir?: string;\n\treadonly verbose?: boolean;\n}\n\ninterface ConfigFlags extends IdentityFlags {\n\treadonly config?: string;\n\treadonly network?: string;\n}\n\ninterface UpFlags extends ConfigFlags {\n\treadonly renderer?: CliRendererMode;\n\treadonly fromSnapshot?: string;\n\treadonly snapshotCache?: string;\n\treadonly snapshotStale?: SnapshotStalePolicy;\n}\n\ninterface DumpIdsFlags extends ConfigFlags {\n\treadonly out?: string;\n}\n\ninterface DestructiveFlags extends IdentityFlags {\n\treadonly dryRun?: boolean;\n\treadonly yes?: boolean;\n\treadonly noInput?: boolean;\n}\n\ninterface ConfirmFlags extends IdentityFlags {\n\treadonly yes?: boolean;\n\treadonly noInput?: boolean;\n}\n\ninterface PruneFlags {\n\treadonly json?: boolean;\n\treadonly stateDir?: string;\n\treadonly verbose?: boolean;\n\treadonly dryRun?: boolean;\n\treadonly yes?: boolean;\n\treadonly noInput?: boolean;\n\treadonly list?: boolean;\n\treadonly all?: boolean;\n\treadonly noContainers?: boolean;\n\treadonly noNetworks?: boolean;\n\treadonly noVolumes?: boolean;\n\treadonly includeImages?: boolean;\n}\n\ninterface SnapshotSaveFlags extends ConfigFlags {\n\treadonly name?: string;\n}\n\nconst textParser = (input: string): string => input;\n\nconst stringFlag = (brief: string, placeholder: string) =>\n\t({\n\t\tkind: 'parsed',\n\t\tparse: textParser,\n\t\toptional: true,\n\t\tplaceholder,\n\t\tbrief,\n\t}) as const;\n\nconst boolFlag = (brief: string) =>\n\t({\n\t\tkind: 'boolean',\n\t\toptional: true,\n\t\tbrief,\n\t}) as const;\n\nconst identityFlagParams = {\n\tjson: boolFlag('Emit JSON envelope output'),\n\tapp: stringFlag('Override app name', 'name'),\n\tstack: stringFlag('Override stack name', 'name'),\n\tstateDir: stringFlag('Override state directory', 'path'),\n\tverbose: boolFlag('Enable more verbose logging'),\n} as const;\n\nconst configFlagParams = {\n\t...identityFlagParams,\n\tconfig: stringFlag('Override devstack.config.ts path', 'path'),\n\tnetwork: stringFlag('Override network before config import', 'name'),\n} as const;\n\nconst destructiveFlagParams = {\n\t...identityFlagParams,\n\tdryRun: boolFlag('Skip mutating effects'),\n\tyes: boolFlag('Assume yes on prompts'),\n\tnoInput: boolFlag('Forbid prompts'),\n} as const;\n\nconst globalMaintenanceFlagParams = {\n\tjson: identityFlagParams.json,\n\tstateDir: identityFlagParams.stateDir,\n\tverbose: identityFlagParams.verbose,\n} as const;\n\nconst confirmFlagParams = {\n\t...identityFlagParams,\n\tyes: boolFlag('Assume yes on prompts'),\n\tnoInput: boolFlag('Forbid prompts'),\n} as const;\n\nconst pruneFlagParams = {\n\t...globalMaintenanceFlagParams,\n\tdryRun: destructiveFlagParams.dryRun,\n\tyes: destructiveFlagParams.yes,\n\tnoInput: destructiveFlagParams.noInput,\n\tlist: boolFlag('List devstack-labelled Docker resources without pruning'),\n\tall: boolFlag('Prune every idle non-shared resource group'),\n\tnoContainers: boolFlag('Do not remove containers'),\n\tnoNetworks: boolFlag('Do not remove networks'),\n\tnoVolumes: boolFlag('Do not remove volumes'),\n\tincludeImages: boolFlag('Also remove devstack-labelled images for selected groups'),\n} as const;\n\nconst rendererParser = buildChoiceParser(['tui', 'plain', 'silent'] as const);\nconst snapshotStaleParser = buildChoiceParser(['warn', 'block', 'clean-start'] as const);\n\nconst outputModeFrom = (\n\tflags: Pick<IdentityFlags, 'json'>,\n\tenv: Readonly<Record<string, string | undefined>>,\n): OutputMode => (flags.json === true || env[ENV_VARS.JSON] === '1' ? 'json' : 'human');\n\nconst pruneResourcesFromFlags = (flags: PruneFlags): PruneResourceScope => ({\n\tcontainers: flags.noContainers !== true,\n\tnetworks: flags.noNetworks !== true,\n\tvolumes: flags.noVolumes !== true,\n\timages: flags.includeImages === true,\n});\n\nconst optionalEnv = (\n\tvalue: string | undefined,\n\tenv: Readonly<Record<string, string | undefined>>,\n\tkey: string,\n): string | undefined => value ?? env[key];\n\nconst makeGlobalFlags = (\n\tctx: DevstackCliContext,\n\tflags: IdentityFlags & Partial<ConfigFlags & UpFlags & DestructiveFlags>,\n\trest: ReadonlyArray<string>,\n): GlobalFlags => {\n\tconst networkRaw = optionalEnv(flags.network, ctx.env, ENV_VARS.NETWORK);\n\tlet network: string | undefined;\n\tif (networkRaw !== undefined) {\n\t\ttry {\n\t\t\tnetwork = parseDevstackNetworkName(\n\t\t\t\tnetworkRaw,\n\t\t\t\tflags.network === undefined ? ENV_VARS.NETWORK : '--network',\n\t\t\t);\n\t\t} catch (cause) {\n\t\t\tthrow cause instanceof Error\n\t\t\t\t? new CliUsageError({ message: cause.message })\n\t\t\t\t: new CliInternalError({ message: 'failed to parse network flag', cause });\n\t\t}\n\t}\n\treturn {\n\t\toutputMode: outputModeFrom(flags, ctx.env),\n\t\tapp: optionalEnv(flags.app, ctx.env, ENV_VARS.APP),\n\t\tstack: optionalEnv(flags.stack, ctx.env, ENV_VARS.STACK),\n\t\tstateDir: optionalEnv(flags.stateDir, ctx.env, ENV_VARS.STATE_DIR),\n\t\tconfigPath: optionalEnv(flags.config, ctx.env, ENV_VARS.CONFIG_PATH),\n\t\tnetwork,\n\t\trenderer: flags.renderer,\n\t\tfromSnapshot: flags.fromSnapshot,\n\t\tsnapshotCache: flags.snapshotCache,\n\t\tsnapshotStalePolicy: flags.snapshotStale,\n\t\tdryRun: flags.dryRun === true,\n\t\tconfirm: {\n\t\t\tassumeYes: flags.yes === true,\n\t\t\tforbidPrompt: flags.noInput === true || ctx.env[ENV_VARS.NO_INPUT] === '1',\n\t\t\tstdinIsTty: ctx.stdinIsTty,\n\t\t},\n\t\tverbose: flags.verbose === true,\n\t\trest,\n\t};\n};\n\n/**\n * Bridge the CLI `--network` flag through `process.env` so config-load-time\n * factory reads pick it up. This indirection is deliberate, not a leak.\n *\n * Why the mutation exists:\n * The `deepbook()` factory (`plugins/deepbook/index.ts`) defaults its mode\n * by reading `process.env.DEVSTACK_NETWORK` at config import time — before\n * any flag value has reached the orchestrator. To make `--network` affect\n * the same default, we must mutate the env BEFORE the user's\n * `devstack.config.ts` is loaded. The chain is:\n * `--network=<net>` flag → setNetworkEnv → `process.env.DEVSTACK_NETWORK`\n * → `deepbook()` factory's env read at import.\n *\n * Why save/restore (and a scoped finalizer) matters:\n * The CLI is also invoked from tests and embedded harnesses inside a single\n * process. An unscoped mutation leaks across invocations: a test that runs\n * `dispatch(['up','--network=testnet'])` followed by `dispatch(['up'])`\n * would see the second call inherit `testnet` from the first. The\n * finalizer restores the prior value (or deletes the key if it was unset)\n * on success, failure, AND interrupt — `Effect.addFinalizer` guarantees\n * the cleanup runs regardless of how the scope closes.\n */\nconst setNetworkEnv = (flags: GlobalFlags): Effect.Effect<void, never, Scope.Scope> => {\n\tif (flags.network === undefined) return Effect.void;\n\tconst next = flags.network;\n\treturn Effect.gen(function* () {\n\t\tconst prior = process.env[ENV_VARS.NETWORK];\n\t\tprocess.env[ENV_VARS.NETWORK] = next;\n\t\tyield* Effect.addFinalizer(() =>\n\t\t\tEffect.sync(() => {\n\t\t\t\tif (prior === undefined) {\n\t\t\t\t\tdelete process.env[ENV_VARS.NETWORK];\n\t\t\t\t} else {\n\t\t\t\t\tprocess.env[ENV_VARS.NETWORK] = prior;\n\t\t\t\t}\n\t\t\t}),\n\t\t);\n\t});\n};\n\n// -----------------------------------------------------------------------------\n// Command execution helpers\n// -----------------------------------------------------------------------------\n\nconst runCommandEffect = async (\n\tctx: DevstackCliContext,\n\tcommand: string,\n\tflags: GlobalFlags,\n\teffect: Effect.Effect<CommandResult, CliError>,\n): Promise<void> => {\n\t// `setNetworkEnv` registers a `process.env[ENV_VARS.NETWORK]` restore as a\n\t// scope finalizer; the outer `Effect.scoped` closes that scope after the\n\t// command completes (success, failure, or interrupt), preventing env leaks\n\t// between concurrent CLI invocations in the same process.\n\tconst program = Effect.scoped(\n\t\tsetNetworkEnv(flags).pipe(\n\t\t\tEffect.andThen(effect),\n\t\t\tEffect.catch((error: CliError) =>\n\t\t\t\temitFailure(ctx.io, flags.outputMode, {\n\t\t\t\t\tcommand,\n\t\t\t\t\telapsedMs: 0,\n\t\t\t\t\terror,\n\t\t\t\t}).pipe(Effect.as({ exitCode: exitCodeFor(error) })),\n\t\t\t),\n\t\t\tEffect.catchCause((cause) =>\n\t\t\t\temitFailure(ctx.io, flags.outputMode, {\n\t\t\t\t\tcommand,\n\t\t\t\t\telapsedMs: 0,\n\t\t\t\t\terror: new CliInternalError({ message: 'unexpected internal failure' }),\n\t\t\t\t\tcause,\n\t\t\t\t}).pipe(Effect.as({ exitCode: ExitCode.SOFTWARE })),\n\t\t\t),\n\t\t),\n\t);\n\tconst result = await Effect.runPromise(program);\n\tif (ctx.io.lastExitCode() === null) {\n\t\tawait Effect.runPromise(ctx.io.setExitCode(result.exitCode));\n\t}\n};\n\nconst runWithFlags = async (\n\tctx: DevstackCliContext,\n\tcommand: string,\n\trawFlags: IdentityFlags & Partial<ConfigFlags & UpFlags & DestructiveFlags>,\n\trest: ReadonlyArray<string>,\n\teffect: (flags: GlobalFlags) => Effect.Effect<CommandResult, CliError>,\n): Promise<void> => {\n\tlet flags: GlobalFlags;\n\ttry {\n\t\tflags = makeGlobalFlags(ctx, rawFlags, rest);\n\t} catch (cause) {\n\t\tconst error =\n\t\t\tcause instanceof CliUsageError\n\t\t\t\t? cause\n\t\t\t\t: new CliInternalError({ message: 'failed to resolve CLI flags', cause });\n\t\tconst mode = outputModeFrom(rawFlags, ctx.env);\n\t\tawait Effect.runPromise(\n\t\t\temitFailure(ctx.io, mode, {\n\t\t\t\tcommand,\n\t\t\t\telapsedMs: 0,\n\t\t\t\terror,\n\t\t\t}),\n\t\t);\n\t\treturn;\n\t}\n\tawait runCommandEffect(ctx, command, flags, effect(flags));\n};\n\nconst requiredPositional = (placeholder: string, brief: string) =>\n\t({\n\t\tparse: textParser,\n\t\tplaceholder,\n\t\tbrief,\n\t}) as const;\n\nconst optionalPositional = (placeholder: string, brief: string) =>\n\t({\n\t\tparse: textParser,\n\t\tplaceholder,\n\t\tbrief,\n\t\toptional: true as const,\n\t}) as const;\n\n// -----------------------------------------------------------------------------\n// Commands\n// -----------------------------------------------------------------------------\n\nconst upCommand = buildCommand<UpFlags, [], DevstackCliContext>({\n\tparameters: {\n\t\tflags: {\n\t\t\t...configFlagParams,\n\t\t\trenderer: {\n\t\t\t\tkind: 'parsed',\n\t\t\t\tparse: rendererParser,\n\t\t\t\toptional: true,\n\t\t\t\tplaceholder: 'tui|plain|silent',\n\t\t\t\tbrief: 'Select the attached renderer',\n\t\t\t},\n\t\t\tfromSnapshot: stringFlag('Start by restoring a named snapshot before acquire', 'name-or-id'),\n\t\t\tsnapshotCache: stringFlag(\n\t\t\t\t'Use a named snapshot as a startup cache and refresh it when stale',\n\t\t\t\t'name',\n\t\t\t),\n\t\t\tsnapshotStale: {\n\t\t\t\tkind: 'parsed',\n\t\t\t\tparse: snapshotStaleParser,\n\t\t\t\toptional: true,\n\t\t\t\tplaceholder: 'warn|block|clean-start',\n\t\t\t\tbrief: 'Policy when --from-snapshot inputs differ from the current stack',\n\t\t\t},\n\t\t},\n\t},\n\tdocs: {\n\t\tbrief: 'Boot a stack and stay attached until interrupted',\n\t},\n\tfunc: function (flags) {\n\t\treturn runWithFlags(this, 'up', flags, [], (global) => this.deps.up.run(global));\n\t},\n});\n\nconst applyCommand = buildCommand<ConfigFlags, [], DevstackCliContext>({\n\tparameters: { flags: configFlagParams },\n\tdocs: {\n\t\tbrief: 'Reconcile a live stack or run one-shot setup',\n\t},\n\tfunc: function (flags) {\n\t\treturn runWithFlags(this, 'apply', flags, [], (global) => this.deps.apply.run(global));\n\t},\n});\n\nconst codegenCommand = buildCommand<ConfigFlags, [], DevstackCliContext>({\n\tparameters: { flags: configFlagParams },\n\tdocs: {\n\t\tbrief: 'Regenerate committed bindings from Move source (no stack boot)',\n\t},\n\tfunc: function (flags) {\n\t\treturn runWithFlags(this, 'codegen', flags, [], (global) =>\n\t\t\tthis.deps.codegen.run({ configPath: global.configPath }),\n\t\t);\n\t},\n});\n\nconst dumpIdsCommand = buildCommand<DumpIdsFlags, [], DevstackCliContext>({\n\tparameters: {\n\t\tflags: {\n\t\t\t...configFlagParams,\n\t\t\tout: stringFlag('Write the id-config JSON to this file instead of stdout', 'path'),\n\t\t},\n\t},\n\tdocs: {\n\t\tbrief: 'Emit the stack id-config (devstack-ids.json) for a real-network deploy',\n\t},\n\tfunc: function (flags) {\n\t\treturn runWithFlags(this, 'dump-ids', flags, [], (global) =>\n\t\t\tthis.deps.dumpIds.run({\n\t\t\t\tconfigPath: global.configPath,\n\t\t\t\tout: flags.out,\n\t\t\t\tio: this.io,\n\t\t\t\toutputMode: global.outputMode,\n\t\t\t}),\n\t\t);\n\t},\n});\n\nconst statusCommand = buildCommand<IdentityFlags, [], DevstackCliContext>({\n\tparameters: { flags: identityFlagParams },\n\tdocs: { brief: 'Show the current stack projection (offline: from the manifest)' },\n\tfunc: function (flags) {\n\t\treturn runWithFlags(this, 'status', flags, [], (global) =>\n\t\t\trunStatus(this.deps.status, { flags: global, io: this.io }),\n\t\t);\n\t},\n});\n\nconst doctorCommand = buildCommand<IdentityFlags, [], DevstackCliContext>({\n\tparameters: { flags: identityFlagParams },\n\tdocs: { brief: 'Run host and stack preflight checks' },\n\tfunc: function (flags) {\n\t\treturn runWithFlags(this, 'doctor', flags, [], (global) =>\n\t\t\trunDoctor(this.deps.doctor, { flags: global, io: this.io }),\n\t\t);\n\t},\n});\n\nconst configCommand = buildCommand<ConfigFlags, [], DevstackCliContext>({\n\tparameters: { flags: configFlagParams },\n\tdocs: { brief: 'Print resolved config inputs' },\n\tfunc: function (flags) {\n\t\treturn runWithFlags(this, 'config', flags, [], (global) =>\n\t\t\trunConfig(this.deps.config, { flags: global, io: this.io }),\n\t\t);\n\t},\n});\n\nconst schemaCommand = buildCommand<Pick<IdentityFlags, 'json'>, [], DevstackCliContext>({\n\tparameters: { flags: { json: identityFlagParams.json } },\n\tdocs: { brief: 'Emit the CLI schema' },\n\tfunc: function (flags) {\n\t\treturn runWithFlags(this, 'schema', flags, [], (global) => {\n\t\t\tconst data = { ...commandSchema(), outputMode: global.outputMode };\n\t\t\treturn emitSuccess(this.io, global.outputMode, {\n\t\t\t\tcommand: 'schema',\n\t\t\t\telapsedMs: 0,\n\t\t\t\tdata,\n\t\t\t\thumanLines: [JSON.stringify(data, null, 2)],\n\t\t\t}).pipe(Effect.as({ exitCode: 0 }));\n\t\t});\n\t},\n});\n\nconst snapshotSaveCommand = buildCommand<\n\tSnapshotSaveFlags,\n\t[string | undefined],\n\tDevstackCliContext\n>({\n\tparameters: {\n\t\tflags: {\n\t\t\t...configFlagParams,\n\t\t\tname: stringFlag('Human-readable snapshot name', 'name'),\n\t\t},\n\t\tpositional: { kind: 'tuple', parameters: [optionalPositional('name', 'snapshot name')] },\n\t},\n\tdocs: { brief: 'Capture a snapshot' },\n\tfunc: function (flags, snapshotName) {\n\t\tconst rest = [\n\t\t\t'save',\n\t\t\t...(snapshotName === undefined ? [] : [snapshotName]),\n\t\t\t...(flags.name === undefined ? [] : ['--name', flags.name]),\n\t\t];\n\t\treturn runWithFlags(this, 'snapshot save', flags, rest, (global) =>\n\t\t\trunSnapshot(this.deps.snapshot, { flags: global, io: this.io }),\n\t\t);\n\t},\n});\n\nconst snapshotRestoreCommand = buildCommand<ConfirmFlags, [string], DevstackCliContext>({\n\tparameters: {\n\t\tflags: confirmFlagParams,\n\t\tpositional: {\n\t\t\tkind: 'tuple',\n\t\t\tparameters: [requiredPositional('name-or-id', 'snapshot name or id')],\n\t\t},\n\t},\n\tdocs: { brief: 'Restore a snapshot' },\n\tfunc: function (flags, snapshotRef) {\n\t\treturn runWithFlags(this, 'snapshot restore', flags, ['restore', snapshotRef], (global) =>\n\t\t\trunSnapshot(this.deps.snapshot, { flags: global, io: this.io }),\n\t\t);\n\t},\n});\n\nconst snapshotListCommand = buildCommand<IdentityFlags, [], DevstackCliContext>({\n\tparameters: { flags: identityFlagParams },\n\tdocs: { brief: 'List snapshots' },\n\tfunc: function (flags) {\n\t\treturn runWithFlags(this, 'snapshot list', flags, ['list'], (global) =>\n\t\t\trunSnapshot(this.deps.snapshot, { flags: global, io: this.io }),\n\t\t);\n\t},\n});\n\nconst snapshotDeleteCommand = buildCommand<ConfirmFlags, [string], DevstackCliContext>({\n\tparameters: {\n\t\tflags: confirmFlagParams,\n\t\tpositional: {\n\t\t\tkind: 'tuple',\n\t\t\tparameters: [requiredPositional('name-or-id', 'snapshot name or id')],\n\t\t},\n\t},\n\tdocs: { brief: 'Delete a snapshot' },\n\tfunc: function (flags, snapshotRef) {\n\t\treturn runWithFlags(this, 'snapshot delete', flags, ['delete', snapshotRef], (global) =>\n\t\t\trunSnapshot(this.deps.snapshot, { flags: global, io: this.io }),\n\t\t);\n\t},\n});\n\nconst snapshotCommands = buildRouteMap({\n\troutes: {\n\t\tsave: snapshotSaveCommand,\n\t\trestore: snapshotRestoreCommand,\n\t\tlist: snapshotListCommand,\n\t\tdelete: snapshotDeleteCommand,\n\t},\n\tdocs: { brief: 'Capture, restore, list, or delete stack snapshots' },\n});\n\nconst pruneCommand = buildCommand<PruneFlags, [], DevstackCliContext>({\n\tparameters: { flags: pruneFlagParams },\n\tdocs: { brief: 'Inventory and prune devstack-labelled Docker resources' },\n\tfunc: function (flags) {\n\t\treturn runWithFlags(this, 'prune', flags, [], (global) =>\n\t\t\trunPrune(\n\t\t\t\tthis.deps.prune,\n\t\t\t\t{ flags: global, io: this.io },\n\t\t\t\t{\n\t\t\t\t\tmode: flags.list === true ? 'list' : flags.all === true ? 'all' : 'auto',\n\t\t\t\t\tresources: pruneResourcesFromFlags(flags),\n\t\t\t\t},\n\t\t\t),\n\t\t);\n\t},\n});\n\nconst wipeCommand = buildCommand<DestructiveFlags, [], DevstackCliContext>({\n\tparameters: { flags: destructiveFlagParams },\n\tdocs: { brief: 'Destroy all state for the selected stack' },\n\tfunc: function (flags) {\n\t\treturn runWithFlags(this, 'wipe', flags, [], (global) =>\n\t\t\trunWipe(this.deps.wipe, { flags: global, io: this.io }),\n\t\t);\n\t},\n});\n\nconst root = buildRouteMap({\n\troutes: {\n\t\tup: upCommand,\n\t\tapply: applyCommand,\n\t\tcodegen: codegenCommand,\n\t\tdumpIds: dumpIdsCommand,\n\t\tstatus: statusCommand,\n\t\tdoctor: doctorCommand,\n\t\tconfig: configCommand,\n\t\tschema: schemaCommand,\n\t\tsnapshot: snapshotCommands,\n\t\tprune: pruneCommand,\n\t\twipe: wipeCommand,\n\t},\n\tdocs: {\n\t\tbrief: 'Sui development stack CLI',\n\t},\n});\n\nconst app = buildApplication(root, {\n\tname: 'devstack',\n\tversionInfo: { currentVersion: readDevstackVersion() },\n\tscanner: { caseStyle: 'allow-kebab-for-camel' },\n\tdocumentation: {\n\t\tcaseStyle: 'convert-camel-to-kebab',\n\t\tdisableAnsiColor: true,\n\t\tonlyRequiredInUsageLine: true,\n\t},\n\tlocalization: {\n\t\tloadText: () => ({\n\t\t\t...text_en,\n\t\t\texceptionWhileParsingArguments: (error) =>\n\t\t\t\terror instanceof Error ? error.message : String(error),\n\t\t}),\n\t},\n});\n\nconst jsonRequested = (\n\targv: ReadonlyArray<string>,\n\tenv: Readonly<Record<string, string | undefined>>,\n): boolean => env[ENV_VARS.JSON] === '1' || argv.includes('--json');\n\n/**\n * Project a `BufferedProcess.exitCode` to a sysexit code AT THE\n * STRICLI-PARSE BOUNDARY ONLY.\n *\n * Contract: `BufferedProcess.exitCode` is mutated ONLY by Stricli's\n * argv-parser when an argv parse step fails (unknown subcommand,\n * malformed flag value, missing required positional). Verbs route\n * their own outcomes through `ctx.io.setExitCode`, which marks\n * `io.touched()` and short-circuits this projection in `dispatch`\n * before `flushBufferedProcess` is reached.\n *\n * A non-zero value here means Stricli rejected argv before any verb\n * ran, so mapping to `USAGE` holds by construction. Kept as a named\n * function so the invariant is documented at the call site.\n */\nconst normalizeStricliExitCode = (code: number | string | null | undefined): number => {\n\tif (typeof code === 'number' && code !== 0) return ExitCode.USAGE;\n\treturn ExitCode.OK;\n};\n\n/**\n * Bridge Stricli's synchronous `StricliProcess.{stdout,stderr}.write`\n * shape to our async (Effect-based) `CliIO` surface.\n *\n * The indirection is load-bearing for the JSON-envelope contract:\n * Stricli writes argv-parse errors to stderr the moment the parser\n * trips, but `--json` mode demands the failure be EMITTED as a\n * structured envelope on stdout (not raw text on stderr) with exit\n * code `EX_USAGE`. We can't wire Stricli's `stderr.write` directly to\n * `nodeProcessIO.writeStderr` because:\n *\n * 1. The stderr bytes are the raw parser error text; in `--json`\n * mode we need to transform them into a failure envelope.\n * 2. We don't know whether the verb handler \"touched\" the IO\n * (rendered its own envelope) until after Stricli returns.\n *\n * Buffering lets us delay the decision until both signals are\n * available. Tests substitute a `BufferedProcess` whose buffers are\n * later inspected, mirroring the prod flush behavior.\n */\nconst flushBufferedProcess = (\n\tprocess: BufferedProcess,\n\tio: CliIO,\n\targv: ReadonlyArray<string>,\n\tenv: Readonly<Record<string, string | undefined>>,\n): Effect.Effect<void> =>\n\tEffect.gen(function* () {\n\t\tconst exitCode = normalizeStricliExitCode(process.exitCode);\n\t\tconst stdout = process.stdoutBuffer.join('');\n\t\tconst stderr = process.stderrBuffer.join('').trim();\n\t\tif (exitCode !== 0 && jsonRequested(argv, env)) {\n\t\t\tyield* emitFailure(io, 'json', {\n\t\t\t\tcommand: '(parse)',\n\t\t\t\telapsedMs: 0,\n\t\t\t\terror: new CliUsageError({\n\t\t\t\t\tmessage: stderr.length > 0 ? stderr : 'invalid command line',\n\t\t\t\t}),\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tif (stdout.length > 0)\n\t\t\tyield* io.writeStdout(stdout.endsWith('\\n') ? stdout.slice(0, -1) : stdout);\n\t\tif (stderr.length > 0) yield* io.writeStderr(stderr);\n\t\tyield* io.setExitCode(exitCode);\n\t});\n\nexport const dispatch = (deps: CliDeps, dispatchEnv: DispatchEnv): Effect.Effect<void> =>\n\tEffect.gen(function* () {\n\t\tconst io = trackIO(dispatchEnv.io ?? nodeProcessIO);\n\t\tconst process = makeBufferedProcess(dispatchEnv.env);\n\t\tconst ctx: DevstackCliContext = {\n\t\t\tprocess,\n\t\t\tdeps,\n\t\t\tenv: dispatchEnv.env,\n\t\t\tstdinIsTty: dispatchEnv.stdinIsTty,\n\t\t\tio,\n\t\t};\n\t\tyield* Effect.tryPromise({\n\t\t\ttry: () => runStricli(app, dispatchEnv.argv, ctx),\n\t\t\tcatch: (cause) => new CliInternalError({ message: 'CLI dispatcher failed', cause }),\n\t\t}).pipe(\n\t\t\tEffect.catch((error: CliError) =>\n\t\t\t\temitFailure(io, jsonRequested(dispatchEnv.argv, dispatchEnv.env) ? 'json' : 'human', {\n\t\t\t\t\tcommand: '(dispatch)',\n\t\t\t\t\telapsedMs: 0,\n\t\t\t\t\terror,\n\t\t\t\t}),\n\t\t\t),\n\t\t);\n\t\tif (!io.touched()) {\n\t\t\tyield* flushBufferedProcess(process, io, dispatchEnv.argv, dispatchEnv.env);\n\t\t}\n\t});\n\n// -----------------------------------------------------------------------------\n// Re-exports\n// -----------------------------------------------------------------------------\n\nexport type { CliIO } from './output.ts';\nexport type { GlobalFlags } from './flags.ts';\nexport { COMMAND_TREE, commandSchema, VERBS, type Verb } from './command-tree.ts';\nexport type { Envelope, EnvelopeError } from './envelope.ts';\nexport {\n\tENVELOPE_SCHEMA_VERSION,\n\tfailureEnvelope,\n\tstreamingEvent,\n\tsuccessEnvelope,\n\ttype StreamingEvent,\n} from './envelope.ts';\nexport { ExitCode, exitCodeName } from './sysexits.ts';\nexport {\n\ttype CliError,\n\tCliAlreadyReportedError,\n\tCliConfigInvalidError,\n\tCliConfigNotFoundError,\n\tCliConfirmDeclinedError,\n\tCliConfirmRequiredError,\n\tCliInternalError,\n\tCliSnapshotNotFoundError,\n\tCliSupervisorLiveError,\n\tCliUnavailableError,\n\tCliUsageError,\n} from './errors.ts';\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["runStricli"],"sources":["../../../src/surfaces/cli/index.ts"],"sourcesContent":["// CLI surface — top-level entry point.\n//\n// The public CLI is intentionally small: `up` is the attached operator\n// surface, `apply` is live-aware reconcile (publish to `up` when it is\n// running, one-shot otherwise), and every other command is offline/direct.\n\nimport {\n\tbuildApplication,\n\tbuildChoiceParser,\n\tbuildCommand,\n\tbuildRouteMap,\n\trun as runStricli,\n\ttext_en,\n\ttype CommandContext as StricliCommandContext,\n\ttype StricliProcess,\n} from '@stricli/core';\nimport { Effect, type Scope } from 'effect';\n\nimport { commandSchema } from './command-tree.ts';\nimport { type CliError, CliInternalError, CliUsageError, exitCodeFor } from './errors.ts';\nimport {\n\ttype CliRendererMode,\n\tENV_VARS,\n\ttype GlobalFlags,\n\ttype OutputMode,\n\ttype SnapshotStalePolicy,\n} from './flags.ts';\nimport { ExitCode } from './sysexits.ts';\nimport { parseDevstackNetworkName } from '../../api/inference-network.ts';\nimport { type CliIO, emitFailure, emitSuccess, nodeProcessIO } from './output.ts';\nimport {\n\ttype CommandResult,\n\ttype ConfigDeps,\n\ttype DoctorDeps,\n\ttype PruneDeps,\n\ttype PruneResourceScope,\n\ttype SnapshotDeps,\n\ttype StatusDeps,\n\ttype WipeDeps,\n\trunConfig,\n\trunDoctor,\n\trunPrune,\n\trunSnapshot,\n\trunStatus,\n\trunWipe,\n} from './commands/index.ts';\nimport { readDevstackVersion } from '../../cli/wirings/read-devstack-version.ts';\n\n// -----------------------------------------------------------------------------\n// Deps bundle\n// -----------------------------------------------------------------------------\n\nexport interface LifecycleCommandDeps {\n\treadonly run: (flags: GlobalFlags) => Effect.Effect<CommandResult, CliError>;\n}\n\n/** The stack-free `codegen` verb takes only a config path — it boots no\n * stack, so it needs no renderer/snapshot/identity flags. */\nexport interface CodegenCommandDeps {\n\treadonly run: (flags: {\n\t\treadonly configPath: string | undefined;\n\t}) => Effect.Effect<CommandResult, CliError>;\n}\n\n/** `dump-deployment` emits the stack's `deployment.json` deployment. It owns\n * its own output (file via `--out`, else stdout), so it receives `io`\n * and the resolved `outputMode` alongside the config path + destination. */\nexport interface DumpDeploymentCommandDeps {\n\treadonly run: (flags: {\n\t\treadonly configPath: string | undefined;\n\t\treadonly out: string | undefined;\n\t\treadonly network: string | undefined;\n\t\treadonly io: CliIO;\n\t\treadonly outputMode: OutputMode;\n\t}) => Effect.Effect<CommandResult, CliError>;\n}\n\nexport interface CliDeps {\n\treadonly up: LifecycleCommandDeps;\n\treadonly apply: LifecycleCommandDeps;\n\treadonly codegen: CodegenCommandDeps;\n\treadonly dumpDeployment: DumpDeploymentCommandDeps;\n\treadonly status: StatusDeps;\n\treadonly snapshot: SnapshotDeps;\n\treadonly prune: PruneDeps;\n\treadonly doctor: DoctorDeps;\n\treadonly config: ConfigDeps;\n\treadonly wipe: WipeDeps;\n}\n\nexport interface DispatchEnv {\n\treadonly argv: ReadonlyArray<string>;\n\treadonly env: Readonly<Record<string, string | undefined>>;\n\treadonly stdinIsTty: boolean;\n\treadonly io?: CliIO;\n}\n\n// -----------------------------------------------------------------------------\n// Stricli context + buffered process\n// -----------------------------------------------------------------------------\n\ninterface BufferedProcess extends StricliProcess {\n\treadonly stdoutBuffer: Array<string>;\n\treadonly stderrBuffer: Array<string>;\n\texitCode?: number | string | null;\n}\n\ninterface TrackedIO extends CliIO {\n\treadonly touched: () => boolean;\n\treadonly lastExitCode: () => number | null;\n}\n\ninterface DevstackCliContext extends StricliCommandContext {\n\treadonly deps: CliDeps;\n\treadonly env: Readonly<Record<string, string | undefined>>;\n\treadonly stdinIsTty: boolean;\n\treadonly io: TrackedIO;\n\treadonly process: BufferedProcess;\n}\n\nconst makeBufferedProcess = (\n\tenv: Readonly<Record<string, string | undefined>>,\n): BufferedProcess => {\n\tconst stdoutBuffer: Array<string> = [];\n\tconst stderrBuffer: Array<string> = [];\n\treturn {\n\t\tstdoutBuffer,\n\t\tstderrBuffer,\n\t\tenv,\n\t\tstdout: {\n\t\t\twrite: (str) => {\n\t\t\t\tstdoutBuffer.push(str);\n\t\t\t},\n\t\t},\n\t\tstderr: {\n\t\t\twrite: (str) => {\n\t\t\t\tstderrBuffer.push(str);\n\t\t\t},\n\t\t},\n\t};\n};\n\nconst trackIO = (io: CliIO): TrackedIO => {\n\tlet touched = false;\n\tlet lastExitCode: number | null = null;\n\treturn {\n\t\ttouched: () => touched,\n\t\tlastExitCode: () => lastExitCode,\n\t\twriteStdout: (line) =>\n\t\t\tio.writeStdout(line).pipe(\n\t\t\t\tEffect.tap(() =>\n\t\t\t\t\tEffect.sync(() => {\n\t\t\t\t\t\ttouched = true;\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t),\n\t\twriteStderr: (line) =>\n\t\t\tio.writeStderr(line).pipe(\n\t\t\t\tEffect.tap(() =>\n\t\t\t\t\tEffect.sync(() => {\n\t\t\t\t\t\ttouched = true;\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t),\n\t\tsetExitCode: (code) =>\n\t\t\tio.setExitCode(code).pipe(\n\t\t\t\tEffect.tap(() =>\n\t\t\t\t\tEffect.sync(() => {\n\t\t\t\t\t\ttouched = true;\n\t\t\t\t\t\tlastExitCode = code;\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t),\n\t};\n};\n\n// -----------------------------------------------------------------------------\n// Flag models\n// -----------------------------------------------------------------------------\n\ninterface IdentityFlags {\n\treadonly json?: boolean;\n\treadonly app?: string;\n\treadonly stack?: string;\n\treadonly stateDir?: string;\n\treadonly verbose?: boolean;\n}\n\ninterface ConfigFlags extends IdentityFlags {\n\treadonly config?: string;\n\treadonly network?: string;\n}\n\ninterface UpFlags extends ConfigFlags {\n\treadonly renderer?: CliRendererMode;\n\treadonly fromSnapshot?: string;\n\treadonly snapshotCache?: string;\n\treadonly snapshotStale?: SnapshotStalePolicy;\n}\n\ninterface DumpDeploymentFlags extends ConfigFlags {\n\treadonly out?: string;\n}\n\ninterface DestructiveFlags extends IdentityFlags {\n\treadonly dryRun?: boolean;\n\treadonly yes?: boolean;\n\treadonly noInput?: boolean;\n}\n\ninterface ConfirmFlags extends IdentityFlags {\n\treadonly yes?: boolean;\n\treadonly noInput?: boolean;\n}\n\ninterface PruneFlags {\n\treadonly json?: boolean;\n\treadonly stateDir?: string;\n\treadonly verbose?: boolean;\n\treadonly dryRun?: boolean;\n\treadonly yes?: boolean;\n\treadonly noInput?: boolean;\n\treadonly list?: boolean;\n\treadonly all?: boolean;\n\treadonly noContainers?: boolean;\n\treadonly noNetworks?: boolean;\n\treadonly noVolumes?: boolean;\n\treadonly includeImages?: boolean;\n}\n\ninterface SnapshotSaveFlags extends ConfigFlags {\n\treadonly name?: string;\n}\n\nconst textParser = (input: string): string => input;\n\nconst stringFlag = (brief: string, placeholder: string) =>\n\t({\n\t\tkind: 'parsed',\n\t\tparse: textParser,\n\t\toptional: true,\n\t\tplaceholder,\n\t\tbrief,\n\t}) as const;\n\nconst boolFlag = (brief: string) =>\n\t({\n\t\tkind: 'boolean',\n\t\toptional: true,\n\t\tbrief,\n\t}) as const;\n\nconst identityFlagParams = {\n\tjson: boolFlag('Emit JSON envelope output'),\n\tapp: stringFlag('Override app name', 'name'),\n\tstack: stringFlag('Override stack name', 'name'),\n\tstateDir: stringFlag('Override state directory', 'path'),\n\tverbose: boolFlag('Enable more verbose logging'),\n} as const;\n\nconst configFlagParams = {\n\t...identityFlagParams,\n\tconfig: stringFlag('Override devstack.config.ts path', 'path'),\n\tnetwork: stringFlag('Override network before config import', 'name'),\n} as const;\n\nconst destructiveFlagParams = {\n\t...identityFlagParams,\n\tdryRun: boolFlag('Skip mutating effects'),\n\tyes: boolFlag('Assume yes on prompts'),\n\tnoInput: boolFlag('Forbid prompts'),\n} as const;\n\nconst globalMaintenanceFlagParams = {\n\tjson: identityFlagParams.json,\n\tstateDir: identityFlagParams.stateDir,\n\tverbose: identityFlagParams.verbose,\n} as const;\n\nconst confirmFlagParams = {\n\t...identityFlagParams,\n\tyes: boolFlag('Assume yes on prompts'),\n\tnoInput: boolFlag('Forbid prompts'),\n} as const;\n\nconst pruneFlagParams = {\n\t...globalMaintenanceFlagParams,\n\tdryRun: destructiveFlagParams.dryRun,\n\tyes: destructiveFlagParams.yes,\n\tnoInput: destructiveFlagParams.noInput,\n\tlist: boolFlag('List devstack-labelled Docker resources without pruning'),\n\tall: boolFlag('Prune every idle non-shared resource group'),\n\tnoContainers: boolFlag('Do not remove containers'),\n\tnoNetworks: boolFlag('Do not remove networks'),\n\tnoVolumes: boolFlag('Do not remove volumes'),\n\tincludeImages: boolFlag('Also remove devstack-labelled images for selected groups'),\n} as const;\n\nconst rendererParser = buildChoiceParser(['tui', 'plain', 'silent'] as const);\nconst snapshotStaleParser = buildChoiceParser(['warn', 'block', 'clean-start'] as const);\n\nconst outputModeFrom = (\n\tflags: Pick<IdentityFlags, 'json'>,\n\tenv: Readonly<Record<string, string | undefined>>,\n): OutputMode => (flags.json === true || env[ENV_VARS.JSON] === '1' ? 'json' : 'human');\n\nconst pruneResourcesFromFlags = (flags: PruneFlags): PruneResourceScope => ({\n\tcontainers: flags.noContainers !== true,\n\tnetworks: flags.noNetworks !== true,\n\tvolumes: flags.noVolumes !== true,\n\timages: flags.includeImages === true,\n});\n\nconst optionalEnv = (\n\tvalue: string | undefined,\n\tenv: Readonly<Record<string, string | undefined>>,\n\tkey: string,\n): string | undefined => value ?? env[key];\n\nconst makeGlobalFlags = (\n\tctx: DevstackCliContext,\n\tflags: IdentityFlags & Partial<ConfigFlags & UpFlags & DestructiveFlags>,\n\trest: ReadonlyArray<string>,\n): GlobalFlags => {\n\tconst networkRaw = optionalEnv(flags.network, ctx.env, ENV_VARS.NETWORK);\n\tlet network: string | undefined;\n\tif (networkRaw !== undefined) {\n\t\ttry {\n\t\t\tnetwork = parseDevstackNetworkName(\n\t\t\t\tnetworkRaw,\n\t\t\t\tflags.network === undefined ? ENV_VARS.NETWORK : '--network',\n\t\t\t);\n\t\t} catch (cause) {\n\t\t\tthrow cause instanceof Error\n\t\t\t\t? new CliUsageError({ message: cause.message })\n\t\t\t\t: new CliInternalError({ message: 'failed to parse network flag', cause });\n\t\t}\n\t}\n\treturn {\n\t\toutputMode: outputModeFrom(flags, ctx.env),\n\t\tapp: optionalEnv(flags.app, ctx.env, ENV_VARS.APP),\n\t\tstack: optionalEnv(flags.stack, ctx.env, ENV_VARS.STACK),\n\t\tstateDir: optionalEnv(flags.stateDir, ctx.env, ENV_VARS.STATE_DIR),\n\t\tconfigPath: optionalEnv(flags.config, ctx.env, ENV_VARS.CONFIG_PATH),\n\t\tnetwork,\n\t\trenderer: flags.renderer,\n\t\tfromSnapshot: flags.fromSnapshot,\n\t\tsnapshotCache: flags.snapshotCache,\n\t\tsnapshotStalePolicy: flags.snapshotStale,\n\t\tdryRun: flags.dryRun === true,\n\t\tconfirm: {\n\t\t\tassumeYes: flags.yes === true,\n\t\t\tforbidPrompt: flags.noInput === true || ctx.env[ENV_VARS.NO_INPUT] === '1',\n\t\t\tstdinIsTty: ctx.stdinIsTty,\n\t\t},\n\t\tverbose: flags.verbose === true,\n\t\trest,\n\t};\n};\n\n/**\n * Bridge the CLI `--network` flag through `process.env` so config-load-time\n * factory reads pick it up. This indirection is deliberate, not a leak.\n *\n * Why the mutation exists:\n * The `deepbook()` factory (`plugins/deepbook/index.ts`) defaults its mode\n * by reading `process.env.DEVSTACK_NETWORK` at config import time — before\n * any flag value has reached the orchestrator. To make `--network` affect\n * the same default, we must mutate the env BEFORE the user's\n * `devstack.config.ts` is loaded. The chain is:\n * `--network=<net>` flag → setNetworkEnv → `process.env.DEVSTACK_NETWORK`\n * → `deepbook()` factory's env read at import.\n *\n * Why save/restore (and a scoped finalizer) matters:\n * The CLI is also invoked from tests and embedded harnesses inside a single\n * process. An unscoped mutation leaks across invocations: a test that runs\n * `dispatch(['up','--network=testnet'])` followed by `dispatch(['up'])`\n * would see the second call inherit `testnet` from the first. The\n * finalizer restores the prior value (or deletes the key if it was unset)\n * on success, failure, AND interrupt — `Effect.addFinalizer` guarantees\n * the cleanup runs regardless of how the scope closes.\n */\nconst setNetworkEnv = (flags: GlobalFlags): Effect.Effect<void, never, Scope.Scope> => {\n\tif (flags.network === undefined) return Effect.void;\n\tconst next = flags.network;\n\treturn Effect.gen(function* () {\n\t\tconst prior = process.env[ENV_VARS.NETWORK];\n\t\tprocess.env[ENV_VARS.NETWORK] = next;\n\t\tyield* Effect.addFinalizer(() =>\n\t\t\tEffect.sync(() => {\n\t\t\t\tif (prior === undefined) {\n\t\t\t\t\tdelete process.env[ENV_VARS.NETWORK];\n\t\t\t\t} else {\n\t\t\t\t\tprocess.env[ENV_VARS.NETWORK] = prior;\n\t\t\t\t}\n\t\t\t}),\n\t\t);\n\t});\n};\n\n// -----------------------------------------------------------------------------\n// Command execution helpers\n// -----------------------------------------------------------------------------\n\nconst runCommandEffect = async (\n\tctx: DevstackCliContext,\n\tcommand: string,\n\tflags: GlobalFlags,\n\teffect: Effect.Effect<CommandResult, CliError>,\n): Promise<void> => {\n\t// `setNetworkEnv` registers a `process.env[ENV_VARS.NETWORK]` restore as a\n\t// scope finalizer; the outer `Effect.scoped` closes that scope after the\n\t// command completes (success, failure, or interrupt), preventing env leaks\n\t// between concurrent CLI invocations in the same process.\n\tconst program = Effect.scoped(\n\t\tsetNetworkEnv(flags).pipe(\n\t\t\tEffect.andThen(effect),\n\t\t\tEffect.catch((error: CliError) =>\n\t\t\t\temitFailure(ctx.io, flags.outputMode, {\n\t\t\t\t\tcommand,\n\t\t\t\t\telapsedMs: 0,\n\t\t\t\t\terror,\n\t\t\t\t}).pipe(Effect.as({ exitCode: exitCodeFor(error) })),\n\t\t\t),\n\t\t\tEffect.catchCause((cause) =>\n\t\t\t\temitFailure(ctx.io, flags.outputMode, {\n\t\t\t\t\tcommand,\n\t\t\t\t\telapsedMs: 0,\n\t\t\t\t\terror: new CliInternalError({ message: 'unexpected internal failure' }),\n\t\t\t\t\tcause,\n\t\t\t\t}).pipe(Effect.as({ exitCode: ExitCode.SOFTWARE })),\n\t\t\t),\n\t\t),\n\t);\n\tconst result = await Effect.runPromise(program);\n\tif (ctx.io.lastExitCode() === null) {\n\t\tawait Effect.runPromise(ctx.io.setExitCode(result.exitCode));\n\t}\n};\n\nconst runWithFlags = async (\n\tctx: DevstackCliContext,\n\tcommand: string,\n\trawFlags: IdentityFlags & Partial<ConfigFlags & UpFlags & DestructiveFlags>,\n\trest: ReadonlyArray<string>,\n\teffect: (flags: GlobalFlags) => Effect.Effect<CommandResult, CliError>,\n): Promise<void> => {\n\tlet flags: GlobalFlags;\n\ttry {\n\t\tflags = makeGlobalFlags(ctx, rawFlags, rest);\n\t} catch (cause) {\n\t\tconst error =\n\t\t\tcause instanceof CliUsageError\n\t\t\t\t? cause\n\t\t\t\t: new CliInternalError({ message: 'failed to resolve CLI flags', cause });\n\t\tconst mode = outputModeFrom(rawFlags, ctx.env);\n\t\tawait Effect.runPromise(\n\t\t\temitFailure(ctx.io, mode, {\n\t\t\t\tcommand,\n\t\t\t\telapsedMs: 0,\n\t\t\t\terror,\n\t\t\t}),\n\t\t);\n\t\treturn;\n\t}\n\tawait runCommandEffect(ctx, command, flags, effect(flags));\n};\n\nconst requiredPositional = (placeholder: string, brief: string) =>\n\t({\n\t\tparse: textParser,\n\t\tplaceholder,\n\t\tbrief,\n\t}) as const;\n\nconst optionalPositional = (placeholder: string, brief: string) =>\n\t({\n\t\tparse: textParser,\n\t\tplaceholder,\n\t\tbrief,\n\t\toptional: true as const,\n\t}) as const;\n\n// -----------------------------------------------------------------------------\n// Commands\n// -----------------------------------------------------------------------------\n\nconst upCommand = buildCommand<UpFlags, [], DevstackCliContext>({\n\tparameters: {\n\t\tflags: {\n\t\t\t...configFlagParams,\n\t\t\trenderer: {\n\t\t\t\tkind: 'parsed',\n\t\t\t\tparse: rendererParser,\n\t\t\t\toptional: true,\n\t\t\t\tplaceholder: 'tui|plain|silent',\n\t\t\t\tbrief: 'Select the attached renderer',\n\t\t\t},\n\t\t\tfromSnapshot: stringFlag('Start by restoring a named snapshot before acquire', 'name-or-id'),\n\t\t\tsnapshotCache: stringFlag(\n\t\t\t\t'Use a named snapshot as a startup cache and refresh it when stale',\n\t\t\t\t'name',\n\t\t\t),\n\t\t\tsnapshotStale: {\n\t\t\t\tkind: 'parsed',\n\t\t\t\tparse: snapshotStaleParser,\n\t\t\t\toptional: true,\n\t\t\t\tplaceholder: 'warn|block|clean-start',\n\t\t\t\tbrief: 'Policy when --from-snapshot inputs differ from the current stack',\n\t\t\t},\n\t\t},\n\t},\n\tdocs: {\n\t\tbrief: 'Boot a stack and stay attached until interrupted',\n\t},\n\tfunc: function (flags) {\n\t\treturn runWithFlags(this, 'up', flags, [], (global) => this.deps.up.run(global));\n\t},\n});\n\nconst applyCommand = buildCommand<ConfigFlags, [], DevstackCliContext>({\n\tparameters: { flags: configFlagParams },\n\tdocs: {\n\t\tbrief: 'Reconcile a live stack or run one-shot setup',\n\t},\n\tfunc: function (flags) {\n\t\treturn runWithFlags(this, 'apply', flags, [], (global) => this.deps.apply.run(global));\n\t},\n});\n\nconst codegenCommand = buildCommand<ConfigFlags, [], DevstackCliContext>({\n\tparameters: { flags: configFlagParams },\n\tdocs: {\n\t\tbrief: 'Regenerate committed bindings from Move source (no stack boot)',\n\t},\n\tfunc: function (flags) {\n\t\treturn runWithFlags(this, 'codegen', flags, [], (global) =>\n\t\t\tthis.deps.codegen.run({ configPath: global.configPath }),\n\t\t);\n\t},\n});\n\nconst dumpDeploymentCommand = buildCommand<DumpDeploymentFlags, [], DevstackCliContext>({\n\tparameters: {\n\t\tflags: {\n\t\t\t...configFlagParams,\n\t\t\tout: stringFlag('Write the deployment JSON to this file instead of stdout', 'path'),\n\t\t\tnetwork: stringFlag(\n\t\t\t\t'Emit a typed deployments/<network>.ts (satisfies AppNetworkDeployment) instead of the raw envelope',\n\t\t\t\t'name',\n\t\t\t),\n\t\t},\n\t},\n\tdocs: {\n\t\tbrief: 'Emit the stack deployment (deployment.json) for a real-network deploy',\n\t},\n\tfunc: function (flags) {\n\t\treturn runWithFlags(this, 'dump-deployment', flags, [], (global) =>\n\t\t\tthis.deps.dumpDeployment.run({\n\t\t\t\tconfigPath: global.configPath,\n\t\t\t\tout: flags.out,\n\t\t\t\tnetwork: flags.network,\n\t\t\t\tio: this.io,\n\t\t\t\toutputMode: global.outputMode,\n\t\t\t}),\n\t\t);\n\t},\n});\n\nconst statusCommand = buildCommand<IdentityFlags, [], DevstackCliContext>({\n\tparameters: { flags: identityFlagParams },\n\tdocs: { brief: 'Show the current stack projection (offline: from the manifest)' },\n\tfunc: function (flags) {\n\t\treturn runWithFlags(this, 'status', flags, [], (global) =>\n\t\t\trunStatus(this.deps.status, { flags: global, io: this.io }),\n\t\t);\n\t},\n});\n\nconst doctorCommand = buildCommand<IdentityFlags, [], DevstackCliContext>({\n\tparameters: { flags: identityFlagParams },\n\tdocs: { brief: 'Run host and stack preflight checks' },\n\tfunc: function (flags) {\n\t\treturn runWithFlags(this, 'doctor', flags, [], (global) =>\n\t\t\trunDoctor(this.deps.doctor, { flags: global, io: this.io }),\n\t\t);\n\t},\n});\n\nconst configCommand = buildCommand<ConfigFlags, [], DevstackCliContext>({\n\tparameters: { flags: configFlagParams },\n\tdocs: { brief: 'Print resolved config inputs' },\n\tfunc: function (flags) {\n\t\treturn runWithFlags(this, 'config', flags, [], (global) =>\n\t\t\trunConfig(this.deps.config, { flags: global, io: this.io }),\n\t\t);\n\t},\n});\n\nconst schemaCommand = buildCommand<Pick<IdentityFlags, 'json'>, [], DevstackCliContext>({\n\tparameters: { flags: { json: identityFlagParams.json } },\n\tdocs: { brief: 'Emit the CLI schema' },\n\tfunc: function (flags) {\n\t\treturn runWithFlags(this, 'schema', flags, [], (global) => {\n\t\t\tconst data = { ...commandSchema(), outputMode: global.outputMode };\n\t\t\treturn emitSuccess(this.io, global.outputMode, {\n\t\t\t\tcommand: 'schema',\n\t\t\t\telapsedMs: 0,\n\t\t\t\tdata,\n\t\t\t\thumanLines: [JSON.stringify(data, null, 2)],\n\t\t\t}).pipe(Effect.as({ exitCode: 0 }));\n\t\t});\n\t},\n});\n\nconst snapshotSaveCommand = buildCommand<\n\tSnapshotSaveFlags,\n\t[string | undefined],\n\tDevstackCliContext\n>({\n\tparameters: {\n\t\tflags: {\n\t\t\t...configFlagParams,\n\t\t\tname: stringFlag('Human-readable snapshot name', 'name'),\n\t\t},\n\t\tpositional: { kind: 'tuple', parameters: [optionalPositional('name', 'snapshot name')] },\n\t},\n\tdocs: { brief: 'Capture a snapshot' },\n\tfunc: function (flags, snapshotName) {\n\t\tconst rest = [\n\t\t\t'save',\n\t\t\t...(snapshotName === undefined ? [] : [snapshotName]),\n\t\t\t...(flags.name === undefined ? [] : ['--name', flags.name]),\n\t\t];\n\t\treturn runWithFlags(this, 'snapshot save', flags, rest, (global) =>\n\t\t\trunSnapshot(this.deps.snapshot, { flags: global, io: this.io }),\n\t\t);\n\t},\n});\n\nconst snapshotRestoreCommand = buildCommand<ConfirmFlags, [string], DevstackCliContext>({\n\tparameters: {\n\t\tflags: confirmFlagParams,\n\t\tpositional: {\n\t\t\tkind: 'tuple',\n\t\t\tparameters: [requiredPositional('name-or-id', 'snapshot name or id')],\n\t\t},\n\t},\n\tdocs: { brief: 'Restore a snapshot' },\n\tfunc: function (flags, snapshotRef) {\n\t\treturn runWithFlags(this, 'snapshot restore', flags, ['restore', snapshotRef], (global) =>\n\t\t\trunSnapshot(this.deps.snapshot, { flags: global, io: this.io }),\n\t\t);\n\t},\n});\n\nconst snapshotListCommand = buildCommand<IdentityFlags, [], DevstackCliContext>({\n\tparameters: { flags: identityFlagParams },\n\tdocs: { brief: 'List snapshots' },\n\tfunc: function (flags) {\n\t\treturn runWithFlags(this, 'snapshot list', flags, ['list'], (global) =>\n\t\t\trunSnapshot(this.deps.snapshot, { flags: global, io: this.io }),\n\t\t);\n\t},\n});\n\nconst snapshotDeleteCommand = buildCommand<ConfirmFlags, [string], DevstackCliContext>({\n\tparameters: {\n\t\tflags: confirmFlagParams,\n\t\tpositional: {\n\t\t\tkind: 'tuple',\n\t\t\tparameters: [requiredPositional('name-or-id', 'snapshot name or id')],\n\t\t},\n\t},\n\tdocs: { brief: 'Delete a snapshot' },\n\tfunc: function (flags, snapshotRef) {\n\t\treturn runWithFlags(this, 'snapshot delete', flags, ['delete', snapshotRef], (global) =>\n\t\t\trunSnapshot(this.deps.snapshot, { flags: global, io: this.io }),\n\t\t);\n\t},\n});\n\nconst snapshotCommands = buildRouteMap({\n\troutes: {\n\t\tsave: snapshotSaveCommand,\n\t\trestore: snapshotRestoreCommand,\n\t\tlist: snapshotListCommand,\n\t\tdelete: snapshotDeleteCommand,\n\t},\n\tdocs: { brief: 'Capture, restore, list, or delete stack snapshots' },\n});\n\nconst pruneCommand = buildCommand<PruneFlags, [], DevstackCliContext>({\n\tparameters: { flags: pruneFlagParams },\n\tdocs: { brief: 'Inventory and prune devstack-labelled Docker resources' },\n\tfunc: function (flags) {\n\t\treturn runWithFlags(this, 'prune', flags, [], (global) =>\n\t\t\trunPrune(\n\t\t\t\tthis.deps.prune,\n\t\t\t\t{ flags: global, io: this.io },\n\t\t\t\t{\n\t\t\t\t\tmode: flags.list === true ? 'list' : flags.all === true ? 'all' : 'auto',\n\t\t\t\t\tresources: pruneResourcesFromFlags(flags),\n\t\t\t\t},\n\t\t\t),\n\t\t);\n\t},\n});\n\nconst wipeCommand = buildCommand<DestructiveFlags, [], DevstackCliContext>({\n\tparameters: { flags: destructiveFlagParams },\n\tdocs: { brief: 'Destroy all state for the selected stack' },\n\tfunc: function (flags) {\n\t\treturn runWithFlags(this, 'wipe', flags, [], (global) =>\n\t\t\trunWipe(this.deps.wipe, { flags: global, io: this.io }),\n\t\t);\n\t},\n});\n\nconst root = buildRouteMap({\n\troutes: {\n\t\tup: upCommand,\n\t\tapply: applyCommand,\n\t\tcodegen: codegenCommand,\n\t\tdumpDeployment: dumpDeploymentCommand,\n\t\tstatus: statusCommand,\n\t\tdoctor: doctorCommand,\n\t\tconfig: configCommand,\n\t\tschema: schemaCommand,\n\t\tsnapshot: snapshotCommands,\n\t\tprune: pruneCommand,\n\t\twipe: wipeCommand,\n\t},\n\tdocs: {\n\t\tbrief: 'Sui development stack CLI',\n\t},\n});\n\nconst app = buildApplication(root, {\n\tname: 'devstack',\n\tversionInfo: { currentVersion: readDevstackVersion() },\n\tscanner: { caseStyle: 'allow-kebab-for-camel' },\n\tdocumentation: {\n\t\tcaseStyle: 'convert-camel-to-kebab',\n\t\tdisableAnsiColor: true,\n\t\tonlyRequiredInUsageLine: true,\n\t},\n\tlocalization: {\n\t\tloadText: () => ({\n\t\t\t...text_en,\n\t\t\texceptionWhileParsingArguments: (error) =>\n\t\t\t\terror instanceof Error ? error.message : String(error),\n\t\t}),\n\t},\n});\n\nconst jsonRequested = (\n\targv: ReadonlyArray<string>,\n\tenv: Readonly<Record<string, string | undefined>>,\n): boolean => env[ENV_VARS.JSON] === '1' || argv.includes('--json');\n\n/**\n * Project a `BufferedProcess.exitCode` to a sysexit code AT THE\n * STRICLI-PARSE BOUNDARY ONLY.\n *\n * Contract: `BufferedProcess.exitCode` is mutated ONLY by Stricli's\n * argv-parser when an argv parse step fails (unknown subcommand,\n * malformed flag value, missing required positional). Verbs route\n * their own outcomes through `ctx.io.setExitCode`, which marks\n * `io.touched()` and short-circuits this projection in `dispatch`\n * before `flushBufferedProcess` is reached.\n *\n * A non-zero value here means Stricli rejected argv before any verb\n * ran, so mapping to `USAGE` holds by construction. Kept as a named\n * function so the invariant is documented at the call site.\n */\nconst normalizeStricliExitCode = (code: number | string | null | undefined): number => {\n\tif (typeof code === 'number' && code !== 0) return ExitCode.USAGE;\n\treturn ExitCode.OK;\n};\n\n/**\n * Bridge Stricli's synchronous `StricliProcess.{stdout,stderr}.write`\n * shape to our async (Effect-based) `CliIO` surface.\n *\n * The indirection is load-bearing for the JSON-envelope contract:\n * Stricli writes argv-parse errors to stderr the moment the parser\n * trips, but `--json` mode demands the failure be EMITTED as a\n * structured envelope on stdout (not raw text on stderr) with exit\n * code `EX_USAGE`. We can't wire Stricli's `stderr.write` directly to\n * `nodeProcessIO.writeStderr` because:\n *\n * 1. The stderr bytes are the raw parser error text; in `--json`\n * mode we need to transform them into a failure envelope.\n * 2. We don't know whether the verb handler \"touched\" the IO\n * (rendered its own envelope) until after Stricli returns.\n *\n * Buffering lets us delay the decision until both signals are\n * available. Tests substitute a `BufferedProcess` whose buffers are\n * later inspected, mirroring the prod flush behavior.\n */\nconst flushBufferedProcess = (\n\tprocess: BufferedProcess,\n\tio: CliIO,\n\targv: ReadonlyArray<string>,\n\tenv: Readonly<Record<string, string | undefined>>,\n): Effect.Effect<void> =>\n\tEffect.gen(function* () {\n\t\tconst exitCode = normalizeStricliExitCode(process.exitCode);\n\t\tconst stdout = process.stdoutBuffer.join('');\n\t\tconst stderr = process.stderrBuffer.join('').trim();\n\t\tif (exitCode !== 0 && jsonRequested(argv, env)) {\n\t\t\tyield* emitFailure(io, 'json', {\n\t\t\t\tcommand: '(parse)',\n\t\t\t\telapsedMs: 0,\n\t\t\t\terror: new CliUsageError({\n\t\t\t\t\tmessage: stderr.length > 0 ? stderr : 'invalid command line',\n\t\t\t\t}),\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tif (stdout.length > 0)\n\t\t\tyield* io.writeStdout(stdout.endsWith('\\n') ? stdout.slice(0, -1) : stdout);\n\t\tif (stderr.length > 0) yield* io.writeStderr(stderr);\n\t\tyield* io.setExitCode(exitCode);\n\t});\n\nexport const dispatch = (deps: CliDeps, dispatchEnv: DispatchEnv): Effect.Effect<void> =>\n\tEffect.gen(function* () {\n\t\tconst io = trackIO(dispatchEnv.io ?? nodeProcessIO);\n\t\tconst process = makeBufferedProcess(dispatchEnv.env);\n\t\tconst ctx: DevstackCliContext = {\n\t\t\tprocess,\n\t\t\tdeps,\n\t\t\tenv: dispatchEnv.env,\n\t\t\tstdinIsTty: dispatchEnv.stdinIsTty,\n\t\t\tio,\n\t\t};\n\t\tyield* Effect.tryPromise({\n\t\t\ttry: () => runStricli(app, dispatchEnv.argv, ctx),\n\t\t\tcatch: (cause) => new CliInternalError({ message: 'CLI dispatcher failed', cause }),\n\t\t}).pipe(\n\t\t\tEffect.catch((error: CliError) =>\n\t\t\t\temitFailure(io, jsonRequested(dispatchEnv.argv, dispatchEnv.env) ? 'json' : 'human', {\n\t\t\t\t\tcommand: '(dispatch)',\n\t\t\t\t\telapsedMs: 0,\n\t\t\t\t\terror,\n\t\t\t\t}),\n\t\t\t),\n\t\t);\n\t\tif (!io.touched()) {\n\t\t\tyield* flushBufferedProcess(process, io, dispatchEnv.argv, dispatchEnv.env);\n\t\t}\n\t});\n\n// -----------------------------------------------------------------------------\n// Re-exports\n// -----------------------------------------------------------------------------\n\nexport type { CliIO } from './output.ts';\nexport type { GlobalFlags } from './flags.ts';\nexport { COMMAND_TREE, commandSchema, VERBS, type Verb } from './command-tree.ts';\nexport type { Envelope, EnvelopeError } from './envelope.ts';\nexport {\n\tENVELOPE_SCHEMA_VERSION,\n\tfailureEnvelope,\n\tstreamingEvent,\n\tsuccessEnvelope,\n\ttype StreamingEvent,\n} from './envelope.ts';\nexport { ExitCode, exitCodeName } from './sysexits.ts';\nexport {\n\ttype CliError,\n\tCliAlreadyReportedError,\n\tCliConfigInvalidError,\n\tCliConfigNotFoundError,\n\tCliConfirmDeclinedError,\n\tCliConfirmRequiredError,\n\tCliInternalError,\n\tCliSnapshotNotFoundError,\n\tCliSupervisorLiveError,\n\tCliUnavailableError,\n\tCliUsageError,\n} from './errors.ts';\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plain-renderer.mjs","names":[],"sources":["../../../src/surfaces/tui/plain-renderer.ts"],"sourcesContent":["// Plain (non-TTY) renderer.\n//\n// Architecture §11 + distilled/21-tui §Renderers: when stdout is not\n// a TTY (CI, pipe, log file), the live ink dashboard is replaced by\n// a structured line stream. One line per event; periodic heartbeat\n// per internally-acquiring row, surfaced to operators as \"starting\".\n//\n// Discipline:\n// - One line per EngineEvent — no batching, no aggregation, no\n// diffing. Each event maps to exactly one structured line.\n// - Writes to stderr (architecture invariant: plain renderer goes\n// to stderr so stdout stays clean for whatever consumes the\n// pipe).\n// - EPIPE swallowed (pipe-safety invariant).\n// - Pure formatting helpers exposed for tests; the Effect-shaped\n// side is the `mount` function.\n// - Heartbeat formatting is pure here; scheduling belongs to the\n// renderer mount path. The architecture says it\n// \"anchors on first sighting in the internal acquiring state; phase\n// changes do NOT reset the clock; a late tick emits exactly one\n// heartbeat (no backlog catch-up)\". The seam is exposed via\n// `formatHeartbeat`.\n\nimport { Effect, Schema, Stream, SubscriptionRef } from 'effect';\n\nimport type { Renderer } from '../../contracts/renderer.ts';\nimport type { EngineEvent } from '../../substrate/events.ts';\nimport type { SubscribableState } from '../../substrate/projection.ts';\nimport {\n\tAccountProjectionSchema,\n\tPackageProjectionSchema,\n} from '../../substrate/runtime/projection/update.ts';\nimport {\n\taccountLine,\n\tendpointLine,\n\tlabelForRow,\n\tnarrationFor,\n\tpackageLine,\n\troleLabel,\n\tstatusLabel,\n} from './display-derivation.ts';\nimport { mountFailed } from './errors.ts';\nimport { eventAt } from '../../substrate/event-time.ts';\n\n// -----------------------------------------------------------------------------\n// Line shape — pure formatters\n// -----------------------------------------------------------------------------\n\n/**\n * Plain-renderer line shape (each call returns exactly one line, no\n * trailing newline).\n *\n * <iso-timestamp> <level> <event-tag> <key>=<value> ...\n *\n * Example:\n * 2026-05-19T20:11:32.001Z INFO lifecycle.statusChanged key=sui from=starting to=ready\n * 2026-05-19T20:11:32.500Z WARN log.appended key=walrus line=\"failed to bind port 9000\"\n * 2026-05-19T20:11:33.000Z INFO endpoint.registered name=aggregator url=http://localhost:9000\n */\nexport const formatEventLine = (event: EngineEvent): string => {\n\tconst ts = isoTimestamp(eventAt(event));\n\tconst level = levelForEvent(event);\n\tconst payload = payloadFor(event);\n\treturn `${ts} ${level} ${event.tag} ${payload}`;\n};\n\n/**\n * Heartbeat line for an in-flight internal acquiring row. Emitted by the\n * scheduler at architecture-blessed intervals; this function is the\n * pure formatter only.\n */\nexport const formatHeartbeat = (\n\tnow: number,\n\tkey: string,\n\tphase: string | null,\n\troleToken: string,\n): string => {\n\tconst ts = isoTimestamp(now);\n\tconst narration = narrationFor(phase, 'acquiring');\n\treturn `${ts} INFO heartbeat key=${key} role=${roleToken} narration=${quote(narration)}`;\n};\n\n// -----------------------------------------------------------------------------\n// Renderer implementation\n// -----------------------------------------------------------------------------\n\nexport interface PlainRendererOptions {\n\t/**\n\t * Quiet mode: emit only the milestones an operator tailing the stream\n\t * cares about — per-plugin readiness, endpoint registrations, codegen,\n\t * hard-kill escalations, and ALL warnings/errors — and drop the routine\n\t * intermediate transitions (starting/acquiring), info `log.appended`\n\t * lines, projection churn, build progress, and teardown chatter.\n\t *\n\t * OFF by default: the normal `--format plain` / CI stream keeps the\n\t * architecture invariant of one line per event (no aggregation). This\n\t * is opted into by embedded consumers that pipe the supervisor's output\n\t * through another tool — notably the Playwright `webServer`, where the\n\t * full firehose surfaces as confusing `[WebServer]`-prefixed noise.\n\t */\n\treadonly quiet?: boolean;\n}\n\n/**\n * Build a plain renderer satisfying the `Renderer` contract.\n *\n * Mount subscribes to the live event stream, writes one line per\n * event to stderr. The state-ref is sampled for the initial sweep\n * (one line per declared row) so a renderer attached after boot\n * still sees a coherent baseline. In quiet mode the sweep collapses\n * to the single `stack.identity` line — at boot the per-row/account\n * lines are all `<pending>` and the real signal arrives as readiness\n * + endpoint events.\n */\nexport const makePlainRenderer = (options: PlainRendererOptions = {}): Renderer => ({\n\tmount: (stateRef, events) =>\n\t\tEffect.gen(function* () {\n\t\t\tconst initial = yield* SubscriptionRef.get(stateRef);\n\t\t\tyield* emitInitialSweep(initial, { quiet: options.quiet });\n\t\t\tconst stream = options.quiet ? events.pipe(Stream.filter(keepInQuiet)) : events;\n\t\t\tyield* stream.pipe(Stream.runForEach((event) => writeStderrLine(formatEventLine(event))));\n\t\t}).pipe(\n\t\t\tEffect.catch((cause: unknown) =>\n\t\t\t\tEffect.fail(mountFailed(cause instanceof Error ? cause.message : String(cause))),\n\t\t\t),\n\t\t),\n\tflush: Effect.void.pipe(Effect.catch(() => Effect.fail(mountFailed('flush')))),\n});\n\n/**\n * Quiet-mode event predicate (pure; exported for tests). Keeps milestone\n * and non-INFO events; drops routine INFO churn. Anything that formats as\n * WARN/ERROR is always kept so failures are never silently filtered out.\n */\nexport const keepInQuiet = (event: EngineEvent): boolean => {\n\tif (levelForEvent(event) !== 'INFO') return true;\n\tswitch (event.tag) {\n\t\tcase 'lifecycle.statusChanged':\n\t\t\t// Only the readiness milestone, not every starting/acquiring tick.\n\t\t\treturn event.to === 'ready';\n\t\tcase 'endpoint.registered':\n\t\tcase 'codegen.emitted':\n\t\tcase 'shutdown.escalated':\n\t\t\treturn true;\n\t\tdefault:\n\t\t\treturn false;\n\t}\n};\n\n// -----------------------------------------------------------------------------\n// Internals — formatting\n// -----------------------------------------------------------------------------\n\nconst isoTimestamp = (at: number): string => new Date(at || Date.now()).toISOString();\n\nconst levelForEvent = (event: EngineEvent): 'INFO' | 'WARN' | 'ERROR' => {\n\tswitch (event.tag) {\n\t\tcase 'log.appended':\n\t\t\treturn event.level === 'error' ? 'ERROR' : event.level === 'warn' ? 'WARN' : 'INFO';\n\t\tcase 'error.reported':\n\t\t\treturn event.error.severity === 'fatal' || event.error.severity === 'error'\n\t\t\t\t? 'ERROR'\n\t\t\t\t: 'WARN';\n\t\t// A failed buffered contribution replayed through the\n\t\t// `ContributionDispatcher` leaves the plugin `ready` (non-fatal by\n\t\t// design — see `dispatchBufferedContributions` in acquire-node.ts),\n\t\t// so this is the operator's ONLY signal that e.g. RPC/wallet routing\n\t\t// is dead. It must not render as a routine INFO line.\n\t\tcase 'engine.orchestrator.dispatchFailed':\n\t\t\treturn 'WARN';\n\t\tdefault:\n\t\t\treturn 'INFO';\n\t}\n};\n\nconst payloadFor = (event: EngineEvent): string => {\n\tswitch (event.tag) {\n\t\tcase 'lifecycle.statusChanged':\n\t\t\treturn kv({\n\t\t\t\tkey: event.pluginKey,\n\t\t\t\tfrom: statusLabel(event.from),\n\t\t\t\tto: statusLabel(event.to),\n\t\t\t});\n\t\tcase 'lifecycle.phaseSet':\n\t\t\treturn kv({\n\t\t\t\tkey: event.pluginKey,\n\t\t\t\tphase: event.phase ?? '',\n\t\t\t});\n\t\tcase 'log.appended':\n\t\t\treturn kv({\n\t\t\t\tkey: event.pluginKey,\n\t\t\t\tline: event.line,\n\t\t\t});\n\t\tcase 'endpoint.registered':\n\t\t\treturn kv({\n\t\t\t\tkey: event.endpoint.endpointKey,\n\t\t\t\tname: event.endpoint.name,\n\t\t\t\tdisplayUrl: event.endpoint.displayUrl ?? event.endpoint.url,\n\t\t\t\turl: event.endpoint.url,\n\t\t\t\twire: event.endpoint.wireProtocol,\n\t\t\t});\n\t\tcase 'projection.updated':\n\t\t\tif (event.kind === 'account') {\n\t\t\t\t// STYLE_GUIDE §19 — substrate-side `decodeUnknownSync` so a\n\t\t\t\t// malformed payload skips the line rather than rendering\n\t\t\t\t// garbage (`Schema.decodeUnknownSync(...) as A` bare-cast\n\t\t\t\t// is banned). The reducer at\n\t\t\t\t// `substrate/runtime/projection/update.ts` performs the\n\t\t\t\t// same decode; we repeat it here because the renderer\n\t\t\t\t// reads the raw event stream, not the reduced state slice.\n\t\t\t\tconst account = tryDecodeProjection(AccountProjectionSchema, event.payload);\n\t\t\t\tif (account === null) return kv({ kind: event.kind, key: event.key });\n\t\t\t\treturn kv({\n\t\t\t\t\tkey: account.key,\n\t\t\t\t\trow: account.rowKey ?? '',\n\t\t\t\t\tname: account.name,\n\t\t\t\t\taddress: account.address ?? '',\n\t\t\t\t\tscheme: account.scheme ?? '',\n\t\t\t\t\tsource: account.source ?? '',\n\t\t\t\t\tfunding: account.funding.status,\n\t\t\t\t\tfundingEntries: (account.funding.entries ?? [])\n\t\t\t\t\t\t.map((entry) => `${entry.coin}:${entry.amount}:${entry.status}`)\n\t\t\t\t\t\t.join(','),\n\t\t\t\t\trequestedMist: account.funding.requestedMist ?? '',\n\t\t\t\t\tbalanceMist: account.funding.balanceMist ?? '',\n\t\t\t\t});\n\t\t\t}\n\t\t\tif (event.kind === 'package') {\n\t\t\t\tconst pkg = tryDecodeProjection(PackageProjectionSchema, event.payload);\n\t\t\t\tif (pkg === null) return kv({ kind: event.kind, key: event.key });\n\t\t\t\treturn kv({\n\t\t\t\t\tkey: pkg.key,\n\t\t\t\t\trow: pkg.rowKey ?? '',\n\t\t\t\t\tname: pkg.name,\n\t\t\t\t\tkind: pkg.kind,\n\t\t\t\t\tpackageId: pkg.packageId,\n\t\t\t\t\tupgradeCapId: pkg.upgradeCapId ?? '',\n\t\t\t\t\tmvr: pkg.mvrPlaceholder,\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn kv({ kind: event.kind, key: event.key });\n\t\tcase 'endpoint.released':\n\t\t\treturn kv({ key: event.endpointKey });\n\t\tcase 'strategy.registered':\n\t\t\treturn kv({\n\t\t\t\tcapability: event.capabilityKey,\n\t\t\t\tautoMounted: event.autoMounted,\n\t\t\t});\n\t\tcase 'strategy.unregistered':\n\t\t\treturn kv({ capability: event.capabilityKey });\n\t\tcase 'manifest.flushed':\n\t\t\treturn kv({ version: event.manifestVersion });\n\t\tcase 'codegen.emitted':\n\t\t\treturn kv({ files: event.files.length });\n\t\tcase 'engine.orchestrator.dispatchFailed':\n\t\t\treturn kv({\n\t\t\t\tkey: event.pluginKey,\n\t\t\t\tkind: event.kind,\n\t\t\t\t// `causeType` (the failing sink error's `_tag`, e.g.\n\t\t\t\t// `RouterBootFailed`) is the discriminator an operator scans\n\t\t\t\t// for to tell dead RPC routing from a codegen collision. `kv`\n\t\t\t\t// drops it when undefined, so untagged causes stay clean.\n\t\t\t\tcauseType: event.causeType ?? '',\n\t\t\t\tsummary: event.message,\n\t\t\t});\n\t\tcase 'error.reported':\n\t\t\treturn kv({\n\t\t\t\tkey: event.error.pluginKey ?? '',\n\t\t\t\ttag: event.error.tag,\n\t\t\t\tsummary: event.error.summary,\n\t\t\t\tcause: event.error.chain.join(' | '),\n\t\t\t\tseverity: event.error.severity,\n\t\t\t});\n\t\tcase 'build.statusChanged':\n\t\t\treturn kv({\n\t\t\t\tkey: event.entry.pluginKey ?? '',\n\t\t\t\tphase: event.entry.phase,\n\t\t\t\tprogress: event.entry.progress,\n\t\t\t});\n\t\tcase 'restart.requested':\n\t\tcase 'restart.completed':\n\t\t\treturn kv({\n\t\t\t\ttarget: event.target === 'stack' ? 'stack' : event.target.pluginKey,\n\t\t\t});\n\t\tcase 'shutdown.escalated':\n\t\t\treturn kv({\n\t\t\t\tmode: 'hard-kill',\n\t\t\t\tsignal: event.signal,\n\t\t\t\texitCode: event.exitCode,\n\t\t\t});\n\t\tcase 'snapshot.captureSkipped':\n\t\t\treturn kv({ reason: event.reason });\n\t\tcase 'snapshot.captureFailed':\n\t\t\treturn kv({ snapshotId: event.snapshotId, name: event.name, summary: event.summary });\n\t\tcase 'snapshot.captured':\n\t\tcase 'snapshot.restored':\n\t\t\treturn kv({ snapshotId: event.snapshotId });\n\t\tdefault: {\n\t\t\tconst _exhaustive: never = event;\n\t\t\tvoid _exhaustive;\n\t\t\treturn '';\n\t\t}\n\t}\n};\n\nconst kv = (record: Readonly<Record<string, unknown>>): string =>\n\tObject.entries(record)\n\t\t.filter(([, v]) => v !== '' && v !== null && v !== undefined)\n\t\t.map(([k, v]) => `${k}=${quoteIfNeeded(String(v))}`)\n\t\t.join(' ');\n\n// Quote anything that contains whitespace, a `\"`, or an ASCII control\n// byte (`\\x00-\\x1f`). Control bytes are the load-bearing case: a\n// projection payload field that contains a stray `\\x1b` (ESC) would\n// otherwise inject an ANSI escape sequence into the plain-renderer's\n// stdout, corrupting subsequent terminal state for callers tailing\n// `--format plain` output.\nconst quoteIfNeeded = (s: string): string => (/[\\s\"\\x00-\\x1f]/.test(s) ? quote(s) : s);\n\nconst quote = (s: string): string => `\"${s.replace(/\"/g, '\\\\\"')}\"`;\n\n/**\n * Structural validate-then-narrow for `projection.updated` payloads\n * before they are formatted as text. Mirrors\n * `tryDecodeProjectionPayload` in the substrate reducer — returns\n * `null` on schema-decode failure so the renderer drops the slice\n * rather than printing fields off a malformed object. The substrate\n * reducer emits the `Effect.logWarning` for the same payload (the\n * decode is deterministic), so we stay silent here to avoid double\n * logging.\n */\nconst tryDecodeProjection = <S extends Schema.Decoder<unknown>>(\n\tschema: S,\n\tpayload: unknown,\n): S['Type'] | null => {\n\ttry {\n\t\treturn Schema.decodeUnknownSync(schema)(payload);\n\t} catch {\n\t\treturn null;\n\t}\n};\n\n// -----------------------------------------------------------------------------\n// Internals — initial sweep\n// -----------------------------------------------------------------------------\n\nconst emitInitialSweep = (\n\tstate: SubscribableState,\n\toptions: { readonly quiet?: boolean } = {},\n): Effect.Effect<void> =>\n\tEffect.gen(function* () {\n\t\tyield* writeStderrLine(\n\t\t\t`${isoTimestamp(state.cycle.startedAt || Date.now())} INFO stack.identity app=${state.identity.app} stack=${state.identity.stack} network=${state.identity.network} cycle=${state.cycle.id}`,\n\t\t);\n\t\t// Quiet mode: stop after the identity line. The per-row/account/\n\t\t// endpoint/package sweep is all `<pending>` at boot; the real signal\n\t\t// arrives as readiness + endpoint events on the (filtered) stream.\n\t\tif (options.quiet) return;\n\t\tfor (const row of state.rows) {\n\t\t\tyield* writeStderrLine(\n\t\t\t\t`${isoTimestamp(Date.now())} INFO row.declared ${kv({\n\t\t\t\t\tkey: row.key,\n\t\t\t\t\tlabel: labelForRow(row.key),\n\t\t\t\t\trole: roleLabel(row.role),\n\t\t\t\t\tstatus: statusLabel(row.status),\n\t\t\t\t})}`,\n\t\t\t);\n\t\t}\n\t\tfor (const endpoint of state.endpoints) {\n\t\t\tyield* writeStderrLine(\n\t\t\t\t`${isoTimestamp(endpoint.registeredAt)} INFO endpoint.registered ${endpointLine(endpoint)}`,\n\t\t\t);\n\t\t}\n\t\tfor (const account of state.accounts) {\n\t\t\tyield* writeStderrLine(\n\t\t\t\t`${isoTimestamp(account.updatedAt)} INFO projection.updated[account] ${accountLine(account)}`,\n\t\t\t);\n\t\t}\n\t\tfor (const pkg of state.packages) {\n\t\t\tyield* writeStderrLine(\n\t\t\t\t`${isoTimestamp(pkg.updatedAt)} INFO projection.updated[package] ${packageLine(pkg)}`,\n\t\t\t);\n\t\t}\n\t});\n\n// -----------------------------------------------------------------------------\n// Internals — IO (pipe-safe)\n// -----------------------------------------------------------------------------\n\nconst writeStderrLine = (line: string): Effect.Effect<void> =>\n\tEffect.sync(() => {\n\t\ttry {\n\t\t\tprocess.stderr.write(`${line}\\n`);\n\t\t} catch (err) {\n\t\t\t// EPIPE swallowed (pipe-safety invariant); other errors\n\t\t\t// likewise swallowed because \"rendering never crashes the\n\t\t\t// stack\" (distilled/21-tui § Invariants).\n\t\t\tvoid err;\n\t\t}\n\t});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"plain-renderer.mjs","names":[],"sources":["../../../src/surfaces/tui/plain-renderer.ts"],"sourcesContent":["// Plain (non-TTY) renderer.\n//\n// Architecture §11 + distilled/21-tui §Renderers: when stdout is not\n// a TTY (CI, pipe, log file), the live ink dashboard is replaced by\n// a structured line stream. One line per event; periodic heartbeat\n// per internally-acquiring row, surfaced to operators as \"starting\".\n//\n// Discipline:\n// - One line per EngineEvent — no batching, no aggregation, no\n// diffing. Each event maps to exactly one structured line.\n// - Writes to stderr (architecture invariant: plain renderer goes\n// to stderr so stdout stays clean for whatever consumes the\n// pipe).\n// - EPIPE swallowed (pipe-safety invariant).\n// - Pure formatting helpers exposed for tests; the Effect-shaped\n// side is the `mount` function.\n// - Heartbeat formatting is pure here; scheduling belongs to the\n// renderer mount path. The architecture says it\n// \"anchors on first sighting in the internal acquiring state; phase\n// changes do NOT reset the clock; a late tick emits exactly one\n// heartbeat (no backlog catch-up)\". The seam is exposed via\n// `formatHeartbeat`.\n\nimport { Effect, Schema, Stream, SubscriptionRef } from 'effect';\n\nimport type { Renderer } from '../../contracts/renderer.ts';\nimport type { EngineEvent } from '../../substrate/events.ts';\nimport type { SubscribableState } from '../../substrate/projection.ts';\nimport {\n\tAccountProjectionSchema,\n\tPackageProjectionSchema,\n} from '../../substrate/runtime/projection/update.ts';\nimport {\n\taccountLine,\n\tendpointLine,\n\tlabelForRow,\n\tnarrationFor,\n\tpackageLine,\n\troleLabel,\n\tstatusLabel,\n} from './display-derivation.ts';\nimport { mountFailed } from './errors.ts';\nimport { eventAt } from '../../substrate/event-time.ts';\n\n// -----------------------------------------------------------------------------\n// Line shape — pure formatters\n// -----------------------------------------------------------------------------\n\n/**\n * Plain-renderer line shape (each call returns exactly one line, no\n * trailing newline).\n *\n * <iso-timestamp> <level> <event-tag> <key>=<value> ...\n *\n * Example:\n * 2026-05-19T20:11:32.001Z INFO lifecycle.statusChanged key=sui from=starting to=ready\n * 2026-05-19T20:11:32.500Z WARN log.appended key=walrus line=\"failed to bind port 9000\"\n * 2026-05-19T20:11:33.000Z INFO endpoint.registered name=aggregator url=http://localhost:9000\n */\nexport const formatEventLine = (event: EngineEvent): string => {\n\tconst ts = isoTimestamp(eventAt(event));\n\tconst level = levelForEvent(event);\n\tconst payload = payloadFor(event);\n\treturn `${ts} ${level} ${event.tag} ${payload}`;\n};\n\n/**\n * Heartbeat line for an in-flight internal acquiring row. Emitted by the\n * scheduler at architecture-blessed intervals; this function is the\n * pure formatter only.\n */\nexport const formatHeartbeat = (\n\tnow: number,\n\tkey: string,\n\tphase: string | null,\n\troleToken: string,\n): string => {\n\tconst ts = isoTimestamp(now);\n\tconst narration = narrationFor(phase, 'acquiring');\n\treturn `${ts} INFO heartbeat key=${key} role=${roleToken} narration=${quote(narration)}`;\n};\n\n// -----------------------------------------------------------------------------\n// Renderer implementation\n// -----------------------------------------------------------------------------\n\nexport interface PlainRendererOptions {\n\t/**\n\t * Quiet mode: emit only the milestones an operator tailing the stream\n\t * cares about — per-plugin readiness, endpoint registrations, codegen,\n\t * hard-kill escalations, and ALL warnings/errors — and drop the routine\n\t * intermediate transitions (starting/acquiring), info `log.appended`\n\t * lines, projection churn, build progress, and teardown chatter.\n\t *\n\t * OFF by default: the normal `--format plain` / CI stream keeps the\n\t * architecture invariant of one line per event (no aggregation). This\n\t * is opted into by embedded consumers that pipe the supervisor's output\n\t * through another tool — notably the Playwright `webServer`, where the\n\t * full firehose surfaces as confusing `[WebServer]`-prefixed noise.\n\t */\n\treadonly quiet?: boolean;\n}\n\n/**\n * Build a plain renderer satisfying the `Renderer` contract.\n *\n * Mount subscribes to the live event stream, writes one line per\n * event to stderr. The state-ref is sampled for the initial sweep\n * (one line per declared row) so a renderer attached after boot\n * still sees a coherent baseline. In quiet mode the sweep collapses\n * to the single `stack.identity` line — at boot the per-row/account\n * lines are all `<pending>` and the real signal arrives as readiness\n * + endpoint events.\n */\nexport const makePlainRenderer = (options: PlainRendererOptions = {}): Renderer => ({\n\tmount: (stateRef, events) =>\n\t\tEffect.gen(function* () {\n\t\t\tconst initial = yield* SubscriptionRef.get(stateRef);\n\t\t\tyield* emitInitialSweep(initial, { quiet: options.quiet });\n\t\t\tconst stream = options.quiet ? events.pipe(Stream.filter(keepInQuiet)) : events;\n\t\t\tyield* stream.pipe(Stream.runForEach((event) => writeStderrLine(formatEventLine(event))));\n\t\t}).pipe(\n\t\t\tEffect.catch((cause: unknown) =>\n\t\t\t\tEffect.fail(mountFailed(cause instanceof Error ? cause.message : String(cause))),\n\t\t\t),\n\t\t),\n\tflush: Effect.void.pipe(Effect.catch(() => Effect.fail(mountFailed('flush')))),\n});\n\n/**\n * Quiet-mode event predicate (pure; exported for tests). Keeps milestone\n * and non-INFO events; drops routine INFO churn. Anything that formats as\n * WARN/ERROR is always kept so failures are never silently filtered out.\n */\nexport const keepInQuiet = (event: EngineEvent): boolean => {\n\tif (levelForEvent(event) !== 'INFO') return true;\n\tswitch (event.tag) {\n\t\tcase 'lifecycle.statusChanged':\n\t\t\t// Only the readiness milestone, not every starting/acquiring tick.\n\t\t\treturn event.to === 'ready';\n\t\tcase 'endpoint.registered':\n\t\tcase 'codegen.emitted':\n\t\tcase 'shutdown.escalated':\n\t\t\treturn true;\n\t\tdefault:\n\t\t\treturn false;\n\t}\n};\n\n// -----------------------------------------------------------------------------\n// Internals — formatting\n// -----------------------------------------------------------------------------\n\nconst isoTimestamp = (at: number): string => new Date(at || Date.now()).toISOString();\n\nconst levelForEvent = (event: EngineEvent): 'INFO' | 'WARN' | 'ERROR' => {\n\tswitch (event.tag) {\n\t\tcase 'log.appended':\n\t\t\treturn event.level === 'error' ? 'ERROR' : event.level === 'warn' ? 'WARN' : 'INFO';\n\t\tcase 'error.reported':\n\t\t\treturn event.error.severity === 'fatal' || event.error.severity === 'error'\n\t\t\t\t? 'ERROR'\n\t\t\t\t: 'WARN';\n\t\t// A failed buffered contribution replayed through the\n\t\t// `ContributionDispatcher` leaves the plugin `ready` (non-fatal by\n\t\t// design — see `dispatchBufferedContributions` in acquire-node.ts),\n\t\t// so this is the operator's ONLY signal that e.g. RPC/wallet routing\n\t\t// is dead. It must not render as a routine INFO line.\n\t\tcase 'engine.orchestrator.dispatchFailed':\n\t\t\treturn 'WARN';\n\t\tdefault:\n\t\t\treturn 'INFO';\n\t}\n};\n\nconst payloadFor = (event: EngineEvent): string => {\n\tswitch (event.tag) {\n\t\tcase 'lifecycle.statusChanged':\n\t\t\treturn kv({\n\t\t\t\tkey: event.pluginKey,\n\t\t\t\tfrom: statusLabel(event.from),\n\t\t\t\tto: statusLabel(event.to),\n\t\t\t});\n\t\tcase 'lifecycle.phaseSet':\n\t\t\treturn kv({\n\t\t\t\tkey: event.pluginKey,\n\t\t\t\tphase: event.phase ?? '',\n\t\t\t});\n\t\tcase 'log.appended':\n\t\t\treturn kv({\n\t\t\t\tkey: event.pluginKey,\n\t\t\t\tline: event.line,\n\t\t\t});\n\t\tcase 'endpoint.registered':\n\t\t\treturn kv({\n\t\t\t\tkey: event.endpoint.endpointKey,\n\t\t\t\tname: event.endpoint.name,\n\t\t\t\tdisplayUrl: event.endpoint.displayUrl ?? event.endpoint.url,\n\t\t\t\turl: event.endpoint.url,\n\t\t\t\twire: event.endpoint.wireProtocol,\n\t\t\t});\n\t\tcase 'projection.updated':\n\t\t\tif (event.kind === 'account') {\n\t\t\t\t// STYLE_GUIDE §19 — substrate-side `decodeUnknownSync` so a\n\t\t\t\t// malformed payload skips the line rather than rendering\n\t\t\t\t// garbage (`Schema.decodeUnknownSync(...) as A` bare-cast\n\t\t\t\t// is banned). The reducer at\n\t\t\t\t// `substrate/runtime/projection/update.ts` performs the\n\t\t\t\t// same decode; we repeat it here because the renderer\n\t\t\t\t// reads the raw event stream, not the reduced state slice.\n\t\t\t\tconst account = tryDecodeProjection(AccountProjectionSchema, event.payload);\n\t\t\t\tif (account === null) return kv({ kind: event.kind, key: event.key });\n\t\t\t\treturn kv({\n\t\t\t\t\tkey: account.key,\n\t\t\t\t\trow: account.rowKey ?? '',\n\t\t\t\t\tname: account.name,\n\t\t\t\t\taddress: account.address ?? '',\n\t\t\t\t\tscheme: account.scheme ?? '',\n\t\t\t\t\tsource: account.source ?? '',\n\t\t\t\t\tfunding: account.funding.status,\n\t\t\t\t\tfundingEntries: (account.funding.entries ?? [])\n\t\t\t\t\t\t.map((entry) => `${entry.coin}:${entry.amount}:${entry.status}`)\n\t\t\t\t\t\t.join(','),\n\t\t\t\t\trequestedMist: account.funding.requestedMist ?? '',\n\t\t\t\t\tbalanceMist: account.funding.balanceMist ?? '',\n\t\t\t\t});\n\t\t\t}\n\t\t\tif (event.kind === 'package') {\n\t\t\t\tconst pkg = tryDecodeProjection(PackageProjectionSchema, event.payload);\n\t\t\t\tif (pkg === null) return kv({ kind: event.kind, key: event.key });\n\t\t\t\treturn kv({\n\t\t\t\t\tkey: pkg.key,\n\t\t\t\t\trow: pkg.rowKey ?? '',\n\t\t\t\t\tname: pkg.name,\n\t\t\t\t\tkind: pkg.kind,\n\t\t\t\t\tpackageId: pkg.packageId,\n\t\t\t\t\tupgradeCapId: pkg.upgradeCapId ?? '',\n\t\t\t\t\tmvr: pkg.mvrPlaceholder,\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn kv({ kind: event.kind, key: event.key });\n\t\tcase 'endpoint.released':\n\t\t\treturn kv({ key: event.endpointKey });\n\t\tcase 'strategy.registered':\n\t\t\treturn kv({\n\t\t\t\tcapability: event.capabilityKey,\n\t\t\t\tautoMounted: event.autoMounted,\n\t\t\t});\n\t\tcase 'strategy.unregistered':\n\t\t\treturn kv({ capability: event.capabilityKey });\n\t\tcase 'manifest.flushed':\n\t\t\treturn kv({ version: event.manifestVersion });\n\t\tcase 'codegen.emitted':\n\t\t\treturn kv({ files: event.files.length });\n\t\tcase 'engine.orchestrator.dispatchFailed':\n\t\t\treturn kv({\n\t\t\t\tkey: event.pluginKey,\n\t\t\t\tkind: event.kind,\n\t\t\t\t// `causeType` (the failing sink error's `_tag`, e.g.\n\t\t\t\t// `RouterBootFailed`) is the discriminator an operator scans\n\t\t\t\t// for to tell dead RPC routing from a codegen collision. `kv`\n\t\t\t\t// drops it when undefined, so untagged causes stay clean.\n\t\t\t\tcauseType: event.causeType ?? '',\n\t\t\t\tsummary: event.message,\n\t\t\t});\n\t\tcase 'error.reported':\n\t\t\treturn kv({\n\t\t\t\tkey: event.error.pluginKey ?? '',\n\t\t\t\ttag: event.error.tag,\n\t\t\t\tsummary: event.error.summary,\n\t\t\t\tcause: event.error.chain.join(' | '),\n\t\t\t\tseverity: event.error.severity,\n\t\t\t});\n\t\tcase 'build.statusChanged':\n\t\t\treturn kv({\n\t\t\t\tkey: event.entry.pluginKey ?? '',\n\t\t\t\tphase: event.entry.phase,\n\t\t\t\tprogress: event.entry.progress,\n\t\t\t});\n\t\tcase 'restart.requested':\n\t\tcase 'restart.completed':\n\t\t\treturn kv({\n\t\t\t\ttarget: event.target === 'stack' ? 'stack' : event.target.pluginKey,\n\t\t\t});\n\t\tcase 'shutdown.escalated':\n\t\t\treturn kv({\n\t\t\t\tmode: 'hard-kill',\n\t\t\t\tsignal: event.signal,\n\t\t\t\texitCode: event.exitCode,\n\t\t\t});\n\t\tcase 'snapshot.captureSkipped':\n\t\t\treturn kv({ reason: event.reason });\n\t\tcase 'snapshot.captureFailed':\n\t\t\treturn kv({ snapshotId: event.snapshotId, name: event.name, summary: event.summary });\n\t\tcase 'snapshot.captured':\n\t\tcase 'snapshot.restored':\n\t\t\treturn kv({ snapshotId: event.snapshotId });\n\t\tdefault: {\n\t\t\tconst _exhaustive: never = event;\n\t\t\tvoid _exhaustive;\n\t\t\treturn '';\n\t\t}\n\t}\n};\n\nconst kv = (record: Readonly<Record<string, unknown>>): string =>\n\tObject.entries(record)\n\t\t.filter(([, v]) => v !== '' && v !== null && v !== undefined)\n\t\t.map(([k, v]) => `${k}=${quoteIfNeeded(String(v))}`)\n\t\t.join(' ');\n\n// Quote anything that contains whitespace, a `\"`, or an ASCII control\n// byte (`\\x00-\\x1f`). Control bytes are the load-bearing case: a\n// projection payload field that contains a stray `\\x1b` (ESC) would\n// otherwise inject an ANSI escape sequence into the plain-renderer's\n// stdout, corrupting subsequent terminal state for callers tailing\n// `--format plain` output.\n// eslint-disable-next-line no-control-regex -- intentionally detects control chars that require quoting\nconst quoteIfNeeded = (s: string): string => (/[\\s\"\\x00-\\x1f]/.test(s) ? quote(s) : s);\n\nconst quote = (s: string): string => `\"${s.replace(/\"/g, '\\\\\"')}\"`;\n\n/**\n * Structural validate-then-narrow for `projection.updated` payloads\n * before they are formatted as text. Mirrors\n * `tryDecodeProjectionPayload` in the substrate reducer — returns\n * `null` on schema-decode failure so the renderer drops the slice\n * rather than printing fields off a malformed object. The substrate\n * reducer emits the `Effect.logWarning` for the same payload (the\n * decode is deterministic), so we stay silent here to avoid double\n * logging.\n */\nconst tryDecodeProjection = <S extends Schema.Decoder<unknown>>(\n\tschema: S,\n\tpayload: unknown,\n): S['Type'] | null => {\n\ttry {\n\t\treturn Schema.decodeUnknownSync(schema)(payload);\n\t} catch {\n\t\treturn null;\n\t}\n};\n\n// -----------------------------------------------------------------------------\n// Internals — initial sweep\n// -----------------------------------------------------------------------------\n\nconst emitInitialSweep = (\n\tstate: SubscribableState,\n\toptions: { readonly quiet?: boolean } = {},\n): Effect.Effect<void> =>\n\tEffect.gen(function* () {\n\t\tyield* writeStderrLine(\n\t\t\t`${isoTimestamp(state.cycle.startedAt || Date.now())} INFO stack.identity app=${state.identity.app} stack=${state.identity.stack} network=${state.identity.network} cycle=${state.cycle.id}`,\n\t\t);\n\t\t// Quiet mode: stop after the identity line. The per-row/account/\n\t\t// endpoint/package sweep is all `<pending>` at boot; the real signal\n\t\t// arrives as readiness + endpoint events on the (filtered) stream.\n\t\tif (options.quiet) return;\n\t\tfor (const row of state.rows) {\n\t\t\tyield* writeStderrLine(\n\t\t\t\t`${isoTimestamp(Date.now())} INFO row.declared ${kv({\n\t\t\t\t\tkey: row.key,\n\t\t\t\t\tlabel: labelForRow(row.key),\n\t\t\t\t\trole: roleLabel(row.role),\n\t\t\t\t\tstatus: statusLabel(row.status),\n\t\t\t\t})}`,\n\t\t\t);\n\t\t}\n\t\tfor (const endpoint of state.endpoints) {\n\t\t\tyield* writeStderrLine(\n\t\t\t\t`${isoTimestamp(endpoint.registeredAt)} INFO endpoint.registered ${endpointLine(endpoint)}`,\n\t\t\t);\n\t\t}\n\t\tfor (const account of state.accounts) {\n\t\t\tyield* writeStderrLine(\n\t\t\t\t`${isoTimestamp(account.updatedAt)} INFO projection.updated[account] ${accountLine(account)}`,\n\t\t\t);\n\t\t}\n\t\tfor (const pkg of state.packages) {\n\t\t\tyield* writeStderrLine(\n\t\t\t\t`${isoTimestamp(pkg.updatedAt)} INFO projection.updated[package] ${packageLine(pkg)}`,\n\t\t\t);\n\t\t}\n\t});\n\n// -----------------------------------------------------------------------------\n// Internals — IO (pipe-safe)\n// -----------------------------------------------------------------------------\n\nconst writeStderrLine = (line: string): Effect.Effect<void> =>\n\tEffect.sync(() => {\n\t\ttry {\n\t\t\tprocess.stderr.write(`${line}\\n`);\n\t\t} catch (err) {\n\t\t\t// EPIPE swallowed (pipe-safety invariant); other errors\n\t\t\t// likewise swallowed because \"rendering never crashes the\n\t\t\t// stack\" (distilled/21-tui § Invariants).\n\t\t\tvoid err;\n\t\t}\n\t});\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mysten-incubation/devstack",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Next-generation Sui devstack package.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sui",
|
|
@@ -80,6 +80,7 @@
|
|
|
80
80
|
"@mysten/codegen": "^0.11.0",
|
|
81
81
|
"@mysten/deepbook-v3": "^1.5.0",
|
|
82
82
|
"@mysten/sui": "^2.18.0",
|
|
83
|
+
"@mysten/walrus": "^1.2.0",
|
|
83
84
|
"@pothos/core": "^4.3.0",
|
|
84
85
|
"@pothos/plugin-relay": "^4.4.0",
|
|
85
86
|
"@pothos/plugin-simple-objects": "^4.1.0",
|
|
@@ -94,7 +95,6 @@
|
|
|
94
95
|
"devDependencies": {
|
|
95
96
|
"@effect/vitest": "4.0.0-beta.65",
|
|
96
97
|
"@mysten/seal": "^1.2.0",
|
|
97
|
-
"@mysten/walrus": "^1.2.0",
|
|
98
98
|
"@types/node": "^25.9.3",
|
|
99
99
|
"@types/react": "^19.2.17",
|
|
100
100
|
"ink-testing-library": "^4.0.0",
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { ID_CONFIG_FILENAME, decodeIdConfig } from "../../orchestrators/codegen/id-config.mjs";
|
|
2
|
-
import { ExitCode } from "../../surfaces/cli/sysexits.mjs";
|
|
3
|
-
import { CliInternalError } from "../../surfaces/cli/errors.mjs";
|
|
4
|
-
import { emitSuccess } from "../../surfaces/cli/output.mjs";
|
|
5
|
-
import { probeSupervisorPresence } from "../../surfaces/cli/commands/supervisor-presence.mjs";
|
|
6
|
-
import "../../surfaces/cli/commands/index.mjs";
|
|
7
|
-
import { cliErrorFromConfigExit } from "../bail.mjs";
|
|
8
|
-
import { makeConfigLoader } from "./config-loader.mjs";
|
|
9
|
-
import { resolvedIdentityForStack } from "./identity.mjs";
|
|
10
|
-
import { runApplyLive } from "./apply.mjs";
|
|
11
|
-
import { Effect, Exit } from "effect";
|
|
12
|
-
import { readFileSync, writeFileSync } from "node:fs";
|
|
13
|
-
import { resolve } from "node:path";
|
|
14
|
-
//#region src/cli/wirings/dump-ids.ts
|
|
15
|
-
/** Read the on-disk id-config as raw text. The file is written by boot's
|
|
16
|
-
* post-acquire hook (`writeIdConfig`) at `<stackRoot>/devstack-ids.json`;
|
|
17
|
-
* a miss here means the boot never produced it (or a hand-removal), which
|
|
18
|
-
* is an internal inconsistency the operator can't act on directly. */
|
|
19
|
-
const readIdConfigText = (idsFile) => Effect.try({
|
|
20
|
-
try: () => readFileSync(idsFile, "utf8"),
|
|
21
|
-
catch: (cause) => new CliInternalError({
|
|
22
|
-
message: `failed to read id-config at ${idsFile}`,
|
|
23
|
-
cause
|
|
24
|
-
})
|
|
25
|
-
});
|
|
26
|
-
const writeOutFile = (out, text) => Effect.try({
|
|
27
|
-
try: () => writeFileSync(out, text, "utf8"),
|
|
28
|
-
catch: (cause) => new CliInternalError({
|
|
29
|
-
message: `failed to write id-config to ${out}`,
|
|
30
|
-
cause
|
|
31
|
-
})
|
|
32
|
-
});
|
|
33
|
-
/** Decode the id-config text for the JSON envelope `data`, routed through
|
|
34
|
-
* the shared {@link decodeIdConfig} so the parse-and-validate decision is
|
|
35
|
-
* centralized (same seam the Vite plugin and codegen verb use). A failure
|
|
36
|
-
* here means the on-disk file is corrupt — bad JSON OR a shape that
|
|
37
|
-
* violates `IdConfigSchema` — so surface it rather than emit a bad
|
|
38
|
-
* envelope. (The file is written by `writeIdConfig` from the same schema,
|
|
39
|
-
* so a conforming boot-written file always decodes; only a corrupt or
|
|
40
|
-
* hand-edited file trips the stricter validation.) */
|
|
41
|
-
const parseIdConfig = (text, idsFile) => Effect.try({
|
|
42
|
-
try: () => decodeIdConfig(text),
|
|
43
|
-
catch: (cause) => new CliInternalError({
|
|
44
|
-
message: `id-config at ${idsFile} is not valid JSON`,
|
|
45
|
-
cause
|
|
46
|
-
})
|
|
47
|
-
});
|
|
48
|
-
const runDumpIds = (identity, opts) => {
|
|
49
|
-
const loader = makeConfigLoader();
|
|
50
|
-
return Effect.gen(function* () {
|
|
51
|
-
const started = Date.now();
|
|
52
|
-
const loadExit = yield* Effect.exit(loader.load(opts.configPath));
|
|
53
|
-
if (Exit.isFailure(loadExit)) return yield* Effect.fail(cliErrorFromConfigExit(loadExit));
|
|
54
|
-
const stack = loadExit.value.engine;
|
|
55
|
-
const effectiveIdentity = resolvedIdentityForStack(identity, stack);
|
|
56
|
-
const idsFile = resolve(effectiveIdentity.stackRoot, ID_CONFIG_FILENAME);
|
|
57
|
-
if (!(yield* probeSupervisorPresence(effectiveIdentity.rosterFile).pipe(Effect.catch(() => Effect.succeed({
|
|
58
|
-
live: false,
|
|
59
|
-
pid: null,
|
|
60
|
-
hostname: null
|
|
61
|
-
})))).live) yield* runApplyLive(opts.configPath, identity);
|
|
62
|
-
const text = yield* readIdConfigText(idsFile);
|
|
63
|
-
const data = yield* parseIdConfig(text, idsFile);
|
|
64
|
-
if (opts.out !== void 0) {
|
|
65
|
-
yield* writeOutFile(opts.out, text);
|
|
66
|
-
yield* emitSuccess(opts.io, opts.outputMode, {
|
|
67
|
-
command: "dump-ids",
|
|
68
|
-
elapsedMs: Date.now() - started,
|
|
69
|
-
data,
|
|
70
|
-
humanLines: [`wrote id-config to ${opts.out}`]
|
|
71
|
-
});
|
|
72
|
-
return { exitCode: ExitCode.OK };
|
|
73
|
-
}
|
|
74
|
-
yield* emitSuccess(opts.io, opts.outputMode, {
|
|
75
|
-
command: "dump-ids",
|
|
76
|
-
elapsedMs: Date.now() - started,
|
|
77
|
-
data,
|
|
78
|
-
humanLines: [text.endsWith("\n") ? text.slice(0, -1) : text]
|
|
79
|
-
});
|
|
80
|
-
return { exitCode: ExitCode.OK };
|
|
81
|
-
});
|
|
82
|
-
};
|
|
83
|
-
//#endregion
|
|
84
|
-
export { runDumpIds };
|
|
85
|
-
|
|
86
|
-
//# sourceMappingURL=dump-ids.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"dump-ids.mjs","names":["resolvePath"],"sources":["../../../src/cli/wirings/dump-ids.ts"],"sourcesContent":["// `devstack dump-ids` verb wiring.\n//\n// Emits the stack's `devstack-ids.json` id-config — the single source of\n// on-chain ids (see `orchestrators/codegen/id-config.ts`). This is the\n// supported way to obtain a committed id-config file for a real-network\n// deploy (docs' \"Deploy to a real network / Option A\"), replacing the\n// manual `cp .devstack/stacks/<stack>/devstack-ids.json …`.\n//\n// Live-aware, mirroring `apply.ts` / `snapshot.ts`:\n// - If a supervisor owns the selected stack (roster probe), the\n// id-config already exists on disk — boot's post-acquire hook wrote\n// it (`boot.ts buildProductionPostAcquireHook`). Read + emit it; NO\n// re-boot.\n// - Otherwise, run the SAME one-shot boot `apply` runs when no\n// supervisor is live (`runApplyLive` → `superviseStackWithProductionBoot`\n// with `lifetime: 'one-shot'`). That boots, writes the id-config to\n// `<stackRoot>/devstack-ids.json`, and tears down. We then read the\n// freshly-written file and emit it.\n//\n// The on-disk file is already pretty-printed by `writeIdConfig`\n// (`JSON.stringify(config, null, 2)\\n`), so `--out` copies its bytes\n// verbatim and stdout prints them verbatim — the format matches the\n// boot-written file exactly.\n\nimport { readFileSync, writeFileSync } from 'node:fs';\nimport { resolve as resolvePath } from 'node:path';\n\nimport { Effect, Exit } from 'effect';\n\nimport type { SupervisedStack } from '../../substrate/runtime/index.ts';\nimport { decodeIdConfig, ID_CONFIG_FILENAME } from '../../orchestrators/codegen/id-config.ts';\nimport { type CliError, CliInternalError } from '../../surfaces/cli/errors.ts';\nimport { type CommandResult } from '../../surfaces/cli/commands/index.ts';\nimport { probeSupervisorPresence } from '../../surfaces/cli/commands/index.ts';\nimport { ExitCode } from '../../surfaces/cli/sysexits.ts';\nimport { type CliIO, emitSuccess } from '../../surfaces/cli/output.ts';\nimport type { OutputMode } from '../../surfaces/cli/flags.ts';\nimport type { LoadedConfig } from '../../surfaces/cli/commands/config-loader.ts';\n\nimport { cliErrorFromConfigExit } from '../bail.ts';\nimport { makeConfigLoader } from './config-loader.ts';\nimport { resolvedIdentityForStack, type ResolvedIdentity } from './identity.ts';\nimport { runApplyLive } from './apply.ts';\n\nexport interface DumpIdsOptions {\n\treadonly configPath: string | undefined;\n\t/** Destination for the pretty id-config JSON. When omitted, the JSON\n\t * is printed to stdout. */\n\treadonly out: string | undefined;\n\treadonly io: CliIO;\n\treadonly outputMode: OutputMode;\n}\n\n/** Read the on-disk id-config as raw text. The file is written by boot's\n * post-acquire hook (`writeIdConfig`) at `<stackRoot>/devstack-ids.json`;\n * a miss here means the boot never produced it (or a hand-removal), which\n * is an internal inconsistency the operator can't act on directly. */\nconst readIdConfigText = (idsFile: string): Effect.Effect<string, CliError> =>\n\tEffect.try({\n\t\ttry: () => readFileSync(idsFile, 'utf8'),\n\t\tcatch: (cause) =>\n\t\t\tnew CliInternalError({\n\t\t\t\tmessage: `failed to read id-config at ${idsFile}`,\n\t\t\t\tcause,\n\t\t\t}),\n\t});\n\nconst writeOutFile = (out: string, text: string): Effect.Effect<void, CliError> =>\n\tEffect.try({\n\t\ttry: () => writeFileSync(out, text, 'utf8'),\n\t\tcatch: (cause) =>\n\t\t\tnew CliInternalError({ message: `failed to write id-config to ${out}`, cause }),\n\t});\n\n/** Decode the id-config text for the JSON envelope `data`, routed through\n * the shared {@link decodeIdConfig} so the parse-and-validate decision is\n * centralized (same seam the Vite plugin and codegen verb use). A failure\n * here means the on-disk file is corrupt — bad JSON OR a shape that\n * violates `IdConfigSchema` — so surface it rather than emit a bad\n * envelope. (The file is written by `writeIdConfig` from the same schema,\n * so a conforming boot-written file always decodes; only a corrupt or\n * hand-edited file trips the stricter validation.) */\nconst parseIdConfig = (text: string, idsFile: string): Effect.Effect<unknown, CliError> =>\n\tEffect.try({\n\t\ttry: () => decodeIdConfig(text),\n\t\tcatch: (cause) =>\n\t\t\tnew CliInternalError({ message: `id-config at ${idsFile} is not valid JSON`, cause }),\n\t});\n\nexport const runDumpIds = (\n\tidentity: ResolvedIdentity,\n\topts: DumpIdsOptions,\n): Effect.Effect<CommandResult, CliError> => {\n\tconst loader = makeConfigLoader();\n\treturn Effect.gen(function* () {\n\t\tconst started = Date.now();\n\n\t\t// Resolve identity against the EFFECTIVE stack (explicit\n\t\t// `--stack`/env > `config.stackName` > inferred), matching `apply` /\n\t\t// `snapshot`, so the roster probe + id-config path target the same\n\t\t// stack the operator selected.\n\t\tconst loadExit = yield* Effect.exit(loader.load(opts.configPath));\n\t\tif (Exit.isFailure(loadExit)) {\n\t\t\treturn yield* Effect.fail(cliErrorFromConfigExit(loadExit));\n\t\t}\n\t\tconst stack = (loadExit.value as LoadedConfig & { readonly engine: SupervisedStack }).engine;\n\t\tconst effectiveIdentity = resolvedIdentityForStack(identity, stack);\n\t\tconst idsFile = resolvePath(effectiveIdentity.stackRoot, ID_CONFIG_FILENAME);\n\n\t\t// Live supervisor? The id-config already exists on disk (boot wrote\n\t\t// it). Otherwise run the one-shot boot `apply` runs when nothing is\n\t\t// live — it writes the id-config to `idsFile` and tears down — then\n\t\t// read the file it produced.\n\t\tconst presence = yield* probeSupervisorPresence(effectiveIdentity.rosterFile).pipe(\n\t\t\tEffect.catch(() => Effect.succeed({ live: false, pid: null, hostname: null })),\n\t\t);\n\t\tif (!presence.live) {\n\t\t\t// `runApplyLive` re-probes (sees no live supervisor) and runs the\n\t\t\t// one-shot boot; its config-load / boot failures already surface as\n\t\t\t// typed `CliError`s. We pass the ORIGINAL identity — `runApplyLive`\n\t\t\t// re-derives the effective stack from the same config internally.\n\t\t\tyield* runApplyLive(opts.configPath, identity);\n\t\t}\n\n\t\tconst text = yield* readIdConfigText(idsFile);\n\t\tconst data = yield* parseIdConfig(text, idsFile);\n\n\t\tif (opts.out !== undefined) {\n\t\t\tyield* writeOutFile(opts.out, text);\n\t\t\tyield* emitSuccess(opts.io, opts.outputMode, {\n\t\t\t\tcommand: 'dump-ids',\n\t\t\t\telapsedMs: Date.now() - started,\n\t\t\t\tdata,\n\t\t\t\thumanLines: [`wrote id-config to ${opts.out}`],\n\t\t\t});\n\t\t\treturn { exitCode: ExitCode.OK };\n\t\t}\n\n\t\t// No `--out`: print the id-config JSON to stdout. In human mode the\n\t\t// raw pretty JSON IS the output (trailing newline trimmed by the IO\n\t\t// layer); in JSON mode the same object rides the success envelope's\n\t\t// `data`.\n\t\tyield* emitSuccess(opts.io, opts.outputMode, {\n\t\t\tcommand: 'dump-ids',\n\t\t\telapsedMs: Date.now() - started,\n\t\t\tdata,\n\t\t\thumanLines: [text.endsWith('\\n') ? text.slice(0, -1) : text],\n\t\t});\n\t\treturn { exitCode: ExitCode.OK };\n\t});\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAyDA,MAAM,oBAAoB,YACzB,OAAO,IAAI;CACV,WAAW,aAAa,SAAS,MAAM;CACvC,QAAQ,UACP,IAAI,iBAAiB;EACpB,SAAS,+BAA+B;EACxC;CACD,CAAC;AACH,CAAC;AAEF,MAAM,gBAAgB,KAAa,SAClC,OAAO,IAAI;CACV,WAAW,cAAc,KAAK,MAAM,MAAM;CAC1C,QAAQ,UACP,IAAI,iBAAiB;EAAE,SAAS,gCAAgC;EAAO;CAAM,CAAC;AAChF,CAAC;;;;;;;;;AAUF,MAAM,iBAAiB,MAAc,YACpC,OAAO,IAAI;CACV,WAAW,eAAe,IAAI;CAC9B,QAAQ,UACP,IAAI,iBAAiB;EAAE,SAAS,gBAAgB,QAAQ;EAAqB;CAAM,CAAC;AACtF,CAAC;AAEF,MAAa,cACZ,UACA,SAC4C;CAC5C,MAAM,SAAS,iBAAiB;CAChC,OAAO,OAAO,IAAI,aAAa;EAC9B,MAAM,UAAU,KAAK,IAAI;EAMzB,MAAM,WAAW,OAAO,OAAO,KAAK,OAAO,KAAK,KAAK,UAAU,CAAC;EAChE,IAAI,KAAK,UAAU,QAAQ,GAC1B,OAAO,OAAO,OAAO,KAAK,uBAAuB,QAAQ,CAAC;EAE3D,MAAM,QAAS,SAAS,MAA8D;EACtF,MAAM,oBAAoB,yBAAyB,UAAU,KAAK;EAClE,MAAM,UAAUA,QAAY,kBAAkB,WAAW,kBAAkB;EAS3E,IAAI,EAAC,OAHmB,wBAAwB,kBAAkB,UAAU,CAAC,CAAC,KAC7E,OAAO,YAAY,OAAO,QAAQ;GAAE,MAAM;GAAO,KAAK;GAAM,UAAU;EAAK,CAAC,CAAC,CAC9E,EAAA,CACc,MAKb,OAAO,aAAa,KAAK,YAAY,QAAQ;EAG9C,MAAM,OAAO,OAAO,iBAAiB,OAAO;EAC5C,MAAM,OAAO,OAAO,cAAc,MAAM,OAAO;EAE/C,IAAI,KAAK,QAAQ,KAAA,GAAW;GAC3B,OAAO,aAAa,KAAK,KAAK,IAAI;GAClC,OAAO,YAAY,KAAK,IAAI,KAAK,YAAY;IAC5C,SAAS;IACT,WAAW,KAAK,IAAI,IAAI;IACxB;IACA,YAAY,CAAC,sBAAsB,KAAK,KAAK;GAC9C,CAAC;GACD,OAAO,EAAE,UAAU,SAAS,GAAG;EAChC;EAMA,OAAO,YAAY,KAAK,IAAI,KAAK,YAAY;GAC5C,SAAS;GACT,WAAW,KAAK,IAAI,IAAI;GACxB;GACA,YAAY,CAAC,KAAK,SAAS,IAAI,IAAI,KAAK,MAAM,GAAG,EAAE,IAAI,IAAI;EAC5D,CAAC;EACD,OAAO,EAAE,UAAU,SAAS,GAAG;CAChC,CAAC;AACF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import { Effect, FileSystem, Schema } from "effect";
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import { decodeJsonTextSync } from "../../substrate/runtime/runtime-decode.mjs";
|
|
2
|
-
import { atomicWriteFile } from "../../substrate/runtime/atomic-write.mjs";
|
|
3
|
-
import { Effect, Schema } from "effect";
|
|
4
|
-
//#region src/orchestrators/codegen/id-config.ts
|
|
5
|
-
/** The all-zero Sui object id used as the placeholder for an unresolved
|
|
6
|
-
* (stack-free) id. A committed `config.ts` resolver treats this value —
|
|
7
|
-
* and a missing id — identically: it THROWS `DevstackConfigMissingError`
|
|
8
|
-
* at access time (see `config-runtime.ts`). Apps never transact with it. */
|
|
9
|
-
const UNRESOLVED_ID = "0x0000000000000000000000000000000000000000000000000000000000000000";
|
|
10
|
-
/** One `networks.<name>` entry — the connection coordinates for a
|
|
11
|
-
* network. Mirrors `SuiNetworkConfigEntry` (plugins/sui/codegen.ts);
|
|
12
|
-
* `rpc` is the load-bearing field the app reads synchronously at module
|
|
13
|
-
* load. */
|
|
14
|
-
const IdConfigNetworkSchema = Schema.Struct({
|
|
15
|
-
rpc: Schema.String,
|
|
16
|
-
chainId: Schema.optional(Schema.String),
|
|
17
|
-
faucet: Schema.optional(Schema.NullOr(Schema.String)),
|
|
18
|
-
graphql: Schema.optional(Schema.NullOr(Schema.String))
|
|
19
|
-
});
|
|
20
|
-
/** One `packages.<name>` entry — the resolved package id plus any
|
|
21
|
-
* resolved object captures for the active network. */
|
|
22
|
-
const IdConfigPackageSchema = Schema.Struct({
|
|
23
|
-
id: Schema.String,
|
|
24
|
-
objects: Schema.optional(Schema.Record(Schema.String, Schema.String))
|
|
25
|
-
});
|
|
26
|
-
/** A JSON-serialisable value carried in the generic `values` channel.
|
|
27
|
-
* The whole id-config round-trips through `JSON.stringify` into the
|
|
28
|
-
* vite `define` global, so the value space is exactly JSON. */
|
|
29
|
-
const IdConfigJsonSchema = Schema.suspend(() => Schema.Union([
|
|
30
|
-
Schema.Null,
|
|
31
|
-
Schema.String,
|
|
32
|
-
Schema.Number,
|
|
33
|
-
Schema.Boolean,
|
|
34
|
-
Schema.Array(IdConfigJsonSchema),
|
|
35
|
-
Schema.Record(Schema.String, IdConfigJsonSchema)
|
|
36
|
-
]));
|
|
37
|
-
/** The generic resolver channel: a two-level namespaced map of arbitrary
|
|
38
|
-
* JSON values a plugin contributes at boot and the committed `config.ts`
|
|
39
|
-
* reads back via `resolveValue(namespace, key)`. This is the open-ended
|
|
40
|
-
* sibling to the typed `network`/`networks`/`packages`/`accounts`/
|
|
41
|
-
* `mvrOverrides` fields — it carries the plugin live values the fixed
|
|
42
|
-
* channel can't (deepbook pool ids, coin types, walrus/seal endpoints). */
|
|
43
|
-
const IdConfigValuesSchema = Schema.Record(Schema.String, Schema.Record(Schema.String, IdConfigJsonSchema));
|
|
44
|
-
/** The id-config interchange shape. The whole document is data — no
|
|
45
|
-
* functions, no devstack imports — so it round-trips through JSON and a
|
|
46
|
-
* `JSON.stringify` into a vite `define` global. */
|
|
47
|
-
const IdConfigSchema = Schema.Struct({
|
|
48
|
-
/** Active network name (the `networks.<name>` key the app reads).
|
|
49
|
-
* Load-bearing — an id-config MUST name its network + connection. */
|
|
50
|
-
network: Schema.String,
|
|
51
|
-
networks: Schema.Record(Schema.String, IdConfigNetworkSchema),
|
|
52
|
-
/** Resolved package ids (+ object captures). Optional, default `{}`: an
|
|
53
|
-
* id-config may have zero packages (a network-only stack, or a
|
|
54
|
-
* hand-authored deploy file), and the boot writer always emits the key
|
|
55
|
-
* anyway — defaulting keeps the injected blob well-formed so the app's
|
|
56
|
-
* resolvers throw `DevstackConfigMissingError` rather than a raw
|
|
57
|
-
* TypeError on a missing section. */
|
|
58
|
-
packages: Schema.Record(Schema.String, IdConfigPackageSchema).pipe(Schema.withDecodingDefaultKey(Effect.succeed({}))),
|
|
59
|
-
accounts: Schema.Record(Schema.String, Schema.String).pipe(Schema.withDecodingDefaultKey(Effect.succeed({}))),
|
|
60
|
-
/** MVR placeholder (`@local/<slug>`) → resolved id, for the active
|
|
61
|
-
* network. An app feeds this straight into dapp-kit's
|
|
62
|
-
* `mvr.overrides.packages`. Optional, default `{}` (see `packages`). */
|
|
63
|
-
mvrOverrides: Schema.Record(Schema.String, Schema.String).pipe(Schema.withDecodingDefaultKey(Effect.succeed({}))),
|
|
64
|
-
/** Generic resolver channel — `values[namespace][key]` carries
|
|
65
|
-
* arbitrary live plugin JSON the typed fields above can't. Optional
|
|
66
|
-
* so older id-config files (no `values`) still decode. */
|
|
67
|
-
values: Schema.optional(IdConfigValuesSchema)
|
|
68
|
-
});
|
|
69
|
-
/** Failure reading or decoding an id-config file. */
|
|
70
|
-
var IdConfigError = class extends Schema.TaggedErrorClass()("IdConfigError", {
|
|
71
|
-
source: Schema.String,
|
|
72
|
-
message: Schema.String,
|
|
73
|
-
cause: Schema.optional(Schema.Defect)
|
|
74
|
-
}) {};
|
|
75
|
-
/** Canonical filename of the per-stack id-config under
|
|
76
|
-
* `.devstack/stacks/<name>/`. */
|
|
77
|
-
const ID_CONFIG_FILENAME = "devstack-ids.json";
|
|
78
|
-
/** Write the id-config as pretty JSON. Idempotent at the byte level for
|
|
79
|
-
* identical input (sorted by the caller's assembly order). */
|
|
80
|
-
const writeIdConfig = (path, config) => Effect.gen(function* () {
|
|
81
|
-
const text = `${JSON.stringify(config, null, 2)}\n`;
|
|
82
|
-
yield* atomicWriteFile(path, new TextEncoder().encode(text), { mode: 420 }).pipe(Effect.mapError((cause) => new IdConfigError({
|
|
83
|
-
source: path,
|
|
84
|
-
message: "failed to write id-config",
|
|
85
|
-
cause
|
|
86
|
-
})));
|
|
87
|
-
});
|
|
88
|
-
/** Project a runtime-decode issue into the typed `IdConfigError`. */
|
|
89
|
-
const mkIdConfigError = (issue) => new IdConfigError({
|
|
90
|
-
source: issue.source,
|
|
91
|
-
message: issue.message,
|
|
92
|
-
...issue.cause === void 0 ? {} : { cause: issue.cause }
|
|
93
|
-
});
|
|
94
|
-
/** Decode + validate an id-config from already-read JSON text. The single
|
|
95
|
-
* schema-decode seam every reader shares (the Vite plugin, the `dump-ids`
|
|
96
|
-
* verb, the codegen verb) so the parse-and-validate decision lives in ONE
|
|
97
|
-
* place rather than each caller hand-rolling `JSON.parse`. Throws
|
|
98
|
-
* `IdConfigError` on malformed JSON or a shape that violates
|
|
99
|
-
* `IdConfigSchema`. */
|
|
100
|
-
const decodeIdConfig = (text) => decodeJsonTextSync(IdConfigSchema, text, {
|
|
101
|
-
source: ID_CONFIG_FILENAME,
|
|
102
|
-
mkError: mkIdConfigError
|
|
103
|
-
});
|
|
104
|
-
//#endregion
|
|
105
|
-
export { ID_CONFIG_FILENAME, UNRESOLVED_ID, decodeIdConfig, writeIdConfig };
|
|
106
|
-
|
|
107
|
-
//# sourceMappingURL=id-config.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"id-config.mjs","names":[],"sources":["../../../src/orchestrators/codegen/id-config.ts"],"sourcesContent":["// The id-config interchange file — the single source of on-chain ids.\n//\n// Stop treating on-chain ids as codegen OUTPUT. Treat them as loaded\n// CONFIG DATA. Codegen is a deterministic, stack-free function\n// `(Move source, id-config?) → generated`. Booting a stack's only new\n// job is to PRODUCE this file; the codegen verb READS it (or runs with\n// none); the vite plugin INJECTS it into the app build/dev server.\n//\n// Four authors/consumers, one shared schema:\n// - WRITER: `orchestrators/boot.ts` post-acquire assembles this from\n// acquired plugin state and writes it to the gitignored\n// `.devstack/stacks/<name>/devstack-ids.json`.\n// - READER: the stack-free `devstack codegen` verb reads it via the\n// optional `--config <file>` flag (absent ⇒ ids unresolved).\n// - CONSUMER: the emitted `config-runtime.ts` resolver reads the same\n// shape off the injected `__DEVSTACK_IDS__` global.\n// - INJECTOR: the vite plugin reads the live file (dev) or a committed\n// id-config file (prod, via `ids`/`DEVSTACK_IDS_FILE`) and\n// `define`s the global.\n//\n// The shape is the id-half of today's `config.ts`: networks (rpc + the\n// rest), per-package ids (+ captured object ids), account addresses, and\n// the MVR placeholder → id override map an app feeds dapp-kit.\n\nimport { Effect, FileSystem, Schema } from 'effect';\n\nimport { atomicWriteFile } from '../../substrate/runtime/atomic-write.ts';\nimport {\n\tdecodeJsonTextSync,\n\ttype RuntimeDecodeIssue,\n} from '../../substrate/runtime/runtime-decode.ts';\n\n// -----------------------------------------------------------------------------\n// Sentinel — the all-zero id that marks an UNRESOLVED on-chain id.\n// -----------------------------------------------------------------------------\n\n/** The all-zero Sui object id used as the placeholder for an unresolved\n * (stack-free) id. A committed `config.ts` resolver treats this value —\n * and a missing id — identically: it THROWS `DevstackConfigMissingError`\n * at access time (see `config-runtime.ts`). Apps never transact with it. */\nexport const UNRESOLVED_ID = '0x0000000000000000000000000000000000000000000000000000000000000000';\n\n/** True when `id` is absent or the all-zero sentinel — i.e. not a real\n * resolved on-chain id. */\nexport const isUnresolvedId = (id: string | undefined | null): boolean =>\n\tid === undefined || id === null || id === UNRESOLVED_ID;\n\n// -----------------------------------------------------------------------------\n// JSON value space — the generic `values` channel carries exactly JSON.\n// -----------------------------------------------------------------------------\n\n/** Any JSON-serialisable value. The id-config round-trips through\n * `JSON.stringify` into the injected `__DEVSTACK_IDS__` global, so a\n * plugin live value carried in the generic `values` channel must be JSON. */\nexport type JsonValue =\n\t| null\n\t| string\n\t| number\n\t| boolean\n\t| ReadonlyArray<JsonValue>\n\t| { readonly [key: string]: JsonValue };\n\n// -----------------------------------------------------------------------------\n// Schema\n// -----------------------------------------------------------------------------\n\n/** One `networks.<name>` entry — the connection coordinates for a\n * network. Mirrors `SuiNetworkConfigEntry` (plugins/sui/codegen.ts);\n * `rpc` is the load-bearing field the app reads synchronously at module\n * load. */\nexport const IdConfigNetworkSchema = Schema.Struct({\n\trpc: Schema.String,\n\tchainId: Schema.optional(Schema.String),\n\tfaucet: Schema.optional(Schema.NullOr(Schema.String)),\n\tgraphql: Schema.optional(Schema.NullOr(Schema.String)),\n});\n\n/** One `packages.<name>` entry — the resolved package id plus any\n * resolved object captures for the active network. */\nexport const IdConfigPackageSchema = Schema.Struct({\n\tid: Schema.String,\n\tobjects: Schema.optional(Schema.Record(Schema.String, Schema.String)),\n});\n\n/** A JSON-serialisable value carried in the generic `values` channel.\n * The whole id-config round-trips through `JSON.stringify` into the\n * vite `define` global, so the value space is exactly JSON. */\n// Typed as `Schema.Codec<JsonValue>` (NOT `Schema.Schema<JsonValue>`): the\n// type-only `Schema<T>` widens `DecodingServices` to `unknown`, which would\n// poison every decoder of `IdConfigSchema`; `Codec<T>` defaults the decode/\n// encode services to `never`, matching the real (service-free) recursion.\nexport const IdConfigJsonSchema: Schema.Codec<JsonValue> = Schema.suspend(() =>\n\tSchema.Union([\n\t\tSchema.Null,\n\t\tSchema.String,\n\t\tSchema.Number,\n\t\tSchema.Boolean,\n\t\tSchema.Array(IdConfigJsonSchema),\n\t\tSchema.Record(Schema.String, IdConfigJsonSchema),\n\t]),\n) as Schema.Codec<JsonValue>;\n\n/** The generic resolver channel: a two-level namespaced map of arbitrary\n * JSON values a plugin contributes at boot and the committed `config.ts`\n * reads back via `resolveValue(namespace, key)`. This is the open-ended\n * sibling to the typed `network`/`networks`/`packages`/`accounts`/\n * `mvrOverrides` fields — it carries the plugin live values the fixed\n * channel can't (deepbook pool ids, coin types, walrus/seal endpoints). */\nexport const IdConfigValuesSchema = Schema.Record(\n\tSchema.String,\n\tSchema.Record(Schema.String, IdConfigJsonSchema),\n);\n\n/** The id-config interchange shape. The whole document is data — no\n * functions, no devstack imports — so it round-trips through JSON and a\n * `JSON.stringify` into a vite `define` global. */\nexport const IdConfigSchema = Schema.Struct({\n\t/** Active network name (the `networks.<name>` key the app reads).\n\t * Load-bearing — an id-config MUST name its network + connection. */\n\tnetwork: Schema.String,\n\tnetworks: Schema.Record(Schema.String, IdConfigNetworkSchema),\n\t/** Resolved package ids (+ object captures). Optional, default `{}`: an\n\t * id-config may have zero packages (a network-only stack, or a\n\t * hand-authored deploy file), and the boot writer always emits the key\n\t * anyway — defaulting keeps the injected blob well-formed so the app's\n\t * resolvers throw `DevstackConfigMissingError` rather than a raw\n\t * TypeError on a missing section. */\n\tpackages: Schema.Record(Schema.String, IdConfigPackageSchema).pipe(\n\t\tSchema.withDecodingDefaultKey(Effect.succeed({})),\n\t),\n\taccounts: Schema.Record(Schema.String, Schema.String).pipe(\n\t\tSchema.withDecodingDefaultKey(Effect.succeed({})),\n\t),\n\t/** MVR placeholder (`@local/<slug>`) → resolved id, for the active\n\t * network. An app feeds this straight into dapp-kit's\n\t * `mvr.overrides.packages`. Optional, default `{}` (see `packages`). */\n\tmvrOverrides: Schema.Record(Schema.String, Schema.String).pipe(\n\t\tSchema.withDecodingDefaultKey(Effect.succeed({})),\n\t),\n\t/** Generic resolver channel — `values[namespace][key]` carries\n\t * arbitrary live plugin JSON the typed fields above can't. Optional\n\t * so older id-config files (no `values`) still decode. */\n\tvalues: Schema.optional(IdConfigValuesSchema),\n});\n\nexport type IdConfig = typeof IdConfigSchema.Type;\nexport type IdConfigNetwork = typeof IdConfigNetworkSchema.Type;\nexport type IdConfigPackage = typeof IdConfigPackageSchema.Type;\nexport type IdConfigValues = typeof IdConfigValuesSchema.Type;\n\n// -----------------------------------------------------------------------------\n// Error\n// -----------------------------------------------------------------------------\n\n/** Failure reading or decoding an id-config file. */\nexport class IdConfigError extends Schema.TaggedErrorClass<IdConfigError>()('IdConfigError', {\n\tsource: Schema.String,\n\tmessage: Schema.String,\n\tcause: Schema.optional(Schema.Defect),\n}) {}\n\n// -----------------------------------------------------------------------------\n// Read / write helpers\n// -----------------------------------------------------------------------------\n\n/** Canonical filename of the per-stack id-config under\n * `.devstack/stacks/<name>/`. */\nexport const ID_CONFIG_FILENAME = 'devstack-ids.json';\n\n/** Write the id-config as pretty JSON. Idempotent at the byte level for\n * identical input (sorted by the caller's assembly order). */\nexport const writeIdConfig = (\n\tpath: string,\n\tconfig: IdConfig,\n): Effect.Effect<void, IdConfigError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst text = `${JSON.stringify(config, null, 2)}\\n`;\n\t\tconst bytes = new TextEncoder().encode(text);\n\t\t// Atomic temp+rename — the Vite plugin reads this file with a plain\n\t\t// `readFileSync` + `JSON.parse`, so a torn / partial write would surface\n\t\t// as a parse error. `atomicWriteFile` ensures the parent dir (the\n\t\t// id-config write may run before the manifest write) and fsyncs before\n\t\t// the rename, so readers only ever see a complete file.\n\t\tyield* atomicWriteFile(path, bytes, { mode: 0o644 }).pipe(\n\t\t\tEffect.mapError(\n\t\t\t\t(cause) => new IdConfigError({ source: path, message: 'failed to write id-config', cause }),\n\t\t\t),\n\t\t);\n\t});\n\n/** Project a runtime-decode issue into the typed `IdConfigError`. */\nconst mkIdConfigError = (issue: RuntimeDecodeIssue): IdConfigError =>\n\tnew IdConfigError({\n\t\tsource: issue.source,\n\t\tmessage: issue.message,\n\t\t...(issue.cause === undefined ? {} : { cause: issue.cause }),\n\t});\n\n/** Decode + validate an id-config from already-read JSON text. The single\n * schema-decode seam every reader shares (the Vite plugin, the `dump-ids`\n * verb, the codegen verb) so the parse-and-validate decision lives in ONE\n * place rather than each caller hand-rolling `JSON.parse`. Throws\n * `IdConfigError` on malformed JSON or a shape that violates\n * `IdConfigSchema`. */\nexport const decodeIdConfig = (text: string): IdConfig =>\n\tdecodeJsonTextSync(IdConfigSchema, text, {\n\t\tsource: ID_CONFIG_FILENAME,\n\t\tmkError: mkIdConfigError,\n\t});\n"],"mappings":";;;;;;;;AAwCA,MAAa,gBAAgB;;;;;AA8B7B,MAAa,wBAAwB,OAAO,OAAO;CAClD,KAAK,OAAO;CACZ,SAAS,OAAO,SAAS,OAAO,MAAM;CACtC,QAAQ,OAAO,SAAS,OAAO,OAAO,OAAO,MAAM,CAAC;CACpD,SAAS,OAAO,SAAS,OAAO,OAAO,OAAO,MAAM,CAAC;AACtD,CAAC;;;AAID,MAAa,wBAAwB,OAAO,OAAO;CAClD,IAAI,OAAO;CACX,SAAS,OAAO,SAAS,OAAO,OAAO,OAAO,QAAQ,OAAO,MAAM,CAAC;AACrE,CAAC;;;;AASD,MAAa,qBAA8C,OAAO,cACjE,OAAO,MAAM;CACZ,OAAO;CACP,OAAO;CACP,OAAO;CACP,OAAO;CACP,OAAO,MAAM,kBAAkB;CAC/B,OAAO,OAAO,OAAO,QAAQ,kBAAkB;AAChD,CAAC,CACF;;;;;;;AAQA,MAAa,uBAAuB,OAAO,OAC1C,OAAO,QACP,OAAO,OAAO,OAAO,QAAQ,kBAAkB,CAChD;;;;AAKA,MAAa,iBAAiB,OAAO,OAAO;;;CAG3C,SAAS,OAAO;CAChB,UAAU,OAAO,OAAO,OAAO,QAAQ,qBAAqB;;;;;;;CAO5D,UAAU,OAAO,OAAO,OAAO,QAAQ,qBAAqB,CAAC,CAAC,KAC7D,OAAO,uBAAuB,OAAO,QAAQ,CAAC,CAAC,CAAC,CACjD;CACA,UAAU,OAAO,OAAO,OAAO,QAAQ,OAAO,MAAM,CAAC,CAAC,KACrD,OAAO,uBAAuB,OAAO,QAAQ,CAAC,CAAC,CAAC,CACjD;;;;CAIA,cAAc,OAAO,OAAO,OAAO,QAAQ,OAAO,MAAM,CAAC,CAAC,KACzD,OAAO,uBAAuB,OAAO,QAAQ,CAAC,CAAC,CAAC,CACjD;;;;CAIA,QAAQ,OAAO,SAAS,oBAAoB;AAC7C,CAAC;;AAYD,IAAa,gBAAb,cAAmC,OAAO,iBAAgC,CAAC,CAAC,iBAAiB;CAC5F,QAAQ,OAAO;CACf,SAAS,OAAO;CAChB,OAAO,OAAO,SAAS,OAAO,MAAM;AACrC,CAAC,CAAC,CAAC,CAAC;;;AAQJ,MAAa,qBAAqB;;;AAIlC,MAAa,iBACZ,MACA,WAEA,OAAO,IAAI,aAAa;CACvB,MAAM,OAAO,GAAG,KAAK,UAAU,QAAQ,MAAM,CAAC,EAAE;CAOhD,OAAO,gBAAgB,MANT,IAAI,YAAY,CAAC,CAAC,OAAO,IAMN,GAAG,EAAE,MAAM,IAAM,CAAC,CAAC,CAAC,KACpD,OAAO,UACL,UAAU,IAAI,cAAc;EAAE,QAAQ;EAAM,SAAS;EAA6B;CAAM,CAAC,CAC3F,CACD;AACD,CAAC;;AAGF,MAAM,mBAAmB,UACxB,IAAI,cAAc;CACjB,QAAQ,MAAM;CACd,SAAS,MAAM;CACf,GAAI,MAAM,UAAU,KAAA,IAAY,CAAC,IAAI,EAAE,OAAO,MAAM,MAAM;AAC3D,CAAC;;;;;;;AAQF,MAAa,kBAAkB,SAC9B,mBAAmB,gBAAgB,MAAM;CACxC,QAAQ;CACR,SAAS;AACV,CAAC"}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { stackSubpath } from "../../substrate/runtime/paths.mjs";
|
|
2
|
-
import { join, resolve } from "node:path";
|
|
3
|
-
//#region src/orchestrators/codegen/output-location.ts
|
|
4
|
-
/** The app-rooted base under which codegen authors its per-stack subtrees:
|
|
5
|
-
* `<appRoot>/.devstack`. Mirrors the substrate's `RuntimeRoot` base, but
|
|
6
|
-
* rooted at the app source tree (codegen output lives with the user's
|
|
7
|
-
* source, not in engine-private state). The `stacks/<stack>/...` shape is
|
|
8
|
-
* composed by the shared `stackSubpath` so the two authors (substrate at
|
|
9
|
-
* `RuntimeRoot`, codegen here) never drift. */
|
|
10
|
-
const codegenStacksBase = (appRoot) => resolve(appRoot, ".devstack");
|
|
11
|
-
/** Resolve `<appRoot>/.devstack/stacks/<stack>/generated-extras` — the
|
|
12
|
-
* dev-only + secret tree. Always under `.devstack`, so dev-extras never
|
|
13
|
-
* land in the committed `src/generated/` tree. */
|
|
14
|
-
const extrasDirFor = (appRoot, stack) => stackSubpath(join, codegenStacksBase(appRoot), stack, "generated-extras");
|
|
15
|
-
/**
|
|
16
|
-
* Resolve the LIVE codegen output location for a stack. See the module
|
|
17
|
-
* header for the rule. Pure — same inputs always yield the same absolute
|
|
18
|
-
* path. A single fixed per-stack rule (no override): every live stack's
|
|
19
|
-
* dev-only `generated-extras` tree lands under
|
|
20
|
-
* `<appRoot>/.devstack/stacks/<effectiveStack>/generated-extras`. Boot
|
|
21
|
-
* writes ONLY this tree (`emitExtras`); it never emits the committed
|
|
22
|
-
* `src/generated` tree — that is owned solely by the stack-free `codegen`
|
|
23
|
-
* verb (which resolves its own output path directly).
|
|
24
|
-
*/
|
|
25
|
-
const resolveCodegenOutput = (input) => {
|
|
26
|
-
const { appRoot, effectiveStack } = input;
|
|
27
|
-
return { extrasDir: extrasDirFor(appRoot, effectiveStack) };
|
|
28
|
-
};
|
|
29
|
-
//#endregion
|
|
30
|
-
export { resolveCodegenOutput };
|
|
31
|
-
|
|
32
|
-
//# sourceMappingURL=output-location.mjs.map
|