@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":"codegen.mjs","names":["resolvePath"],"sources":["../../../src/cli/wirings/codegen.ts"],"sourcesContent":["// `devstack codegen` verb wiring.\n//\n// Deterministic, STACK-FREE codegen: `(Move source) → generated`. It\n// boots NO stack and uses NO Docker — the `sui move summary` step runs\n// against a host `sui` CLI on PATH, which is REQUIRED: codegen fails fast\n// with an actionable error when `sui` is absent (rather than silently\n// pulling a Docker image). The detected `sui --version` is logged so the\n// build that produced the committed tree is visible; CI installs a pinned\n// `sui` and the drift-guard catches any version-skew in the output. It\n// loads the config, derives each plugin's codegen contributions from config\n// ALONE (via the plugin spec's `staticCodegen` hook), and runs ONE\n// `runEmitCycle` that writes the committed `src/generated` tree.\n//\n// On-chain ids are LOADED CONFIG DATA, not generated output. The\n// committed tree carries the all-zero sentinel for every LOCAL id (and\n// declared literals for KNOWN packages); the committed `config.ts`\n// resolver throws loudly at runtime if a sentinel id is used. The Vite\n// plugin injects the real ids in dev (live `
|
|
1
|
+
{"version":3,"file":"codegen.mjs","names":["resolvePath"],"sources":["../../../src/cli/wirings/codegen.ts"],"sourcesContent":["// `devstack codegen` verb wiring.\n//\n// Deterministic, STACK-FREE codegen: `(Move source) → generated`. It\n// boots NO stack and uses NO Docker — the `sui move summary` step runs\n// against a host `sui` CLI on PATH, which is REQUIRED: codegen fails fast\n// with an actionable error when `sui` is absent (rather than silently\n// pulling a Docker image). The detected `sui --version` is logged so the\n// build that produced the committed tree is visible; CI installs a pinned\n// `sui` and the drift-guard catches any version-skew in the output. It\n// loads the config, derives each plugin's codegen contributions from config\n// ALONE (via the plugin spec's `staticCodegen` hook), and runs ONE\n// `runEmitCycle` that writes the committed `src/generated` tree.\n//\n// On-chain ids are LOADED CONFIG DATA, not generated output. The\n// committed tree carries the all-zero sentinel for every LOCAL id (and\n// declared literals for KNOWN packages); the committed `config.ts`\n// resolver throws loudly at runtime if a sentinel id is used. The Vite\n// plugin injects the real ids in dev (live `deployment.json`) and prod\n// (the committed per-network `deployments` thunks).\n// So `git status` shows NO churn under\n// `src/generated` after a `devstack up` — ids land only in the\n// gitignored `.devstack/`.\n//\n// Logger layer: `Logger.consolePretty()` — mirrors `apply.ts` exactly.\n// `consolePretty` needs no FileSystem, so it is provided as a separate\n// OUTER layer over the codegen service layer (which provides the Node\n// FileSystem / Path / child-process spawner UNDER the codegen services).\n// This is the same provision shape `apply` uses and compiles cleanly\n// under the strict tsconfig.\n\nimport { spawnSync } from 'node:child_process';\nimport { dirname, resolve as resolvePath } from 'node:path';\n\nimport { Cause, Effect, Exit, Layer, Logger } from 'effect';\nimport * as NodeChildProcessSpawner from '@effect/platform-node/NodeChildProcessSpawner';\nimport * as NodeFileSystem from '@effect/platform-node/NodeFileSystem';\nimport * as NodePath from '@effect/platform-node/NodePath';\n\nimport type { CodegenableDecl } from '../../contracts/codegenable.ts';\nimport type { AnyPlugin } from '../../substrate/plugin.ts';\nimport type { SupervisedStack } from '../../substrate/runtime/index.ts';\nimport { layerMystenMoveCodegen } from '../../orchestrators/codegen/bindings.ts';\nimport { layerSuiMoveSummaryRunnerHost } from '../../plugins/sui/move-summary-runner.ts';\nimport { layerCodegenPaths, layerCodegenRoot } from '../../orchestrators/codegen/paths.ts';\nimport { runEmitCycle } from '../../orchestrators/codegen/service.ts';\nimport { type CliError, CliInternalError, CliUnavailableError } from '../../surfaces/cli/errors.ts';\nimport { type CommandResult } from '../../surfaces/cli/commands/index.ts';\nimport { ExitCode } from '../../surfaces/cli/sysexits.ts';\nimport type { LoadedConfig } from '../../surfaces/cli/commands/config-loader.ts';\n\nimport { cliErrorFromConfigExit } from '../bail.ts';\nimport { makeConfigLoader } from './config-loader.ts';\n\n/** Walk the stack members, calling each plugin spec's `staticCodegen`\n * hook (skipping plugins that lack one) to derive the committed-tree\n * contributions from config alone. KNOWN package ids come from the\n * declared `networks` literals; LOCAL ids stay the all-zero sentinel\n * (resolved at app build/dev time through `__DEVSTACK_DEPLOYMENT__`). */\nexport const deriveContributions = (\n\tmembers: ReadonlyArray<AnyPlugin>,\n): ReadonlyArray<CodegenableDecl> => {\n\tconst decls: Array<CodegenableDecl> = [];\n\tfor (const member of members) {\n\t\tif (member.staticCodegen === undefined) continue;\n\t\tdecls.push(...member.staticCodegen());\n\t}\n\treturn decls;\n};\n\n/** Probe the host `sui` CLI with a cheap `--version` call. Returns the\n * trimmed version line on success (e.g. `sui 1.39.0-abcdef0`), or `null`\n * when no `sui` is resolvable on PATH (missing binary, non-zero exit, or\n * spawn failure). The version is surfaced in codegen output so the build\n * that produced the committed tree is visible. */\nconst probeHostSui = (): string | null => {\n\ttry {\n\t\tconst probe = spawnSync('sui', ['--version'], { timeout: 5_000, encoding: 'utf8' });\n\t\tif (probe.status !== 0) return null;\n\t\tconst line = (probe.stdout ?? '').trim() || (probe.stderr ?? '').trim();\n\t\treturn line.length > 0 ? line : 'sui (version unknown)';\n\t} catch {\n\t\treturn null;\n\t}\n};\n\n/**\n * Build the codegen service layer: `CodegenPathsService` +\n * `MoveSummaryRunnerService` + `MoveCodegenService`, with the Node\n * FileSystem / Path / child-process spawner provided UNDER them. Mirrors\n * `apply`'s substrate-layer provision shape — one merged layer providing\n * the services, with `Logger` supplied as a separate outer layer over the\n * program (`consolePretty` needs no FileSystem).\n *\n * The Move-summary runner is the HOST runner — it invokes the `sui` CLI on\n * PATH and only needs a child-process spawner (no Docker, no substrate\n * layer stack). The caller guarantees `sui` is present (see `runCodegen`).\n */\nconst buildCodegenLayer = (appRoot: string) => {\n\tconst generatedDir = resolvePath(appRoot, 'src', 'generated');\n\tconst platform = Layer.mergeAll(NodeFileSystem.layer, NodePath.layer);\n\tconst codegenPaths = layerCodegenPaths.pipe(\n\t\tLayer.provideMerge(\n\t\t\tlayerCodegenRoot({\n\t\t\t\toutputDir: generatedDir,\n\t\t\t\tstackSubdir: null,\n\t\t\t}),\n\t\t),\n\t);\n\tconst moveRunner = layerSuiMoveSummaryRunnerHost.pipe(\n\t\tLayer.provideMerge(NodeChildProcessSpawner.layer),\n\t);\n\treturn Layer.mergeAll(codegenPaths, moveRunner, layerMystenMoveCodegen()).pipe(\n\t\tLayer.provideMerge(platform),\n\t);\n};\n\n/**\n * Run the stack-free codegen verb. `configPath` is the devstack config\n * path. No stack boots; the committed `src/generated` tree is rewritten\n * deterministically from Move source.\n */\nexport const runCodegen = (\n\tconfigPath: string | undefined,\n): Effect.Effect<CommandResult, CliError> => {\n\tconst loader = makeConfigLoader();\n\treturn Effect.gen(function* () {\n\t\t// Host `sui` is REQUIRED — the `sui move summary` step has no Docker\n\t\t// fallback. Fail fast with an actionable hint when it is missing,\n\t\t// rather than emitting an empty/partial tree.\n\t\tconst suiVersion = probeHostSui();\n\t\tif (suiVersion === null) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew CliUnavailableError({\n\t\t\t\t\tservice: 'sui',\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t'the Sui CLI (`sui`) is required for `devstack codegen` but was not found on PATH',\n\t\t\t\t\thint: 'Install it — https://docs.sui.io/guides/developer/getting-started/sui-install — then re-run `devstack codegen`.',\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\n\t\tconst loadExit = yield* Effect.exit(loader.load(configPath));\n\t\tif (Exit.isFailure(loadExit)) {\n\t\t\treturn yield* Effect.fail(cliErrorFromConfigExit(loadExit));\n\t\t}\n\t\tconst loaded = loadExit.value;\n\t\tconst stack = (loaded as LoadedConfig & { readonly engine: SupervisedStack }).engine;\n\t\tconst appRoot = dirname(loaded.resolvedConfigPath);\n\n\t\tconst contributions = deriveContributions(stack.members);\n\n\t\tconst exit = yield* Effect.exit(\n\t\t\tEffect.gen(function* () {\n\t\t\t\tyield* Effect.logInfo(`codegen: using host ${suiVersion}`);\n\t\t\t\treturn yield* runEmitCycle({ contributions, trackTree: true });\n\t\t\t}).pipe(\n\t\t\t\tEffect.provide(buildCodegenLayer(appRoot)),\n\t\t\t\tEffect.provide(Logger.layer([Logger.consolePretty()])),\n\t\t\t),\n\t\t);\n\t\tif (Exit.isFailure(exit)) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew CliInternalError({\n\t\t\t\t\tmessage: 'codegen failed',\n\t\t\t\t\tcause: Cause.pretty(exit.cause as Cause.Cause<unknown>),\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\treturn { exitCode: ExitCode.OK };\n\t});\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA0DA,MAAa,uBACZ,YACoC;CACpC,MAAM,QAAgC,CAAC;CACvC,KAAK,MAAM,UAAU,SAAS;EAC7B,IAAI,OAAO,kBAAkB,KAAA,GAAW;EACxC,MAAM,KAAK,GAAG,OAAO,cAAc,CAAC;CACrC;CACA,OAAO;AACR;;;;;;AAOA,MAAM,qBAAoC;CACzC,IAAI;EACH,MAAM,QAAQ,UAAU,OAAO,CAAC,WAAW,GAAG;GAAE,SAAS;GAAO,UAAU;EAAO,CAAC;EAClF,IAAI,MAAM,WAAW,GAAG,OAAO;EAC/B,MAAM,QAAQ,MAAM,UAAU,GAAA,CAAI,KAAK,MAAM,MAAM,UAAU,GAAA,CAAI,KAAK;EACtE,OAAO,KAAK,SAAS,IAAI,OAAO;CACjC,QAAQ;EACP,OAAO;CACR;AACD;;;;;;;;;;;;;AAcA,MAAM,qBAAqB,YAAoB;CAC9C,MAAM,eAAeA,QAAY,SAAS,OAAO,WAAW;CAC5D,MAAM,WAAW,MAAM,SAAS,eAAe,OAAO,SAAS,KAAK;CACpE,MAAM,eAAe,kBAAkB,KACtC,MAAM,aACL,iBAAiB;EAChB,WAAW;EACX,aAAa;CACd,CAAC,CACF,CACD;CACA,MAAM,aAAa,8BAA8B,KAChD,MAAM,aAAa,wBAAwB,KAAK,CACjD;CACA,OAAO,MAAM,SAAS,cAAc,YAAY,uBAAuB,CAAC,CAAC,CAAC,KACzE,MAAM,aAAa,QAAQ,CAC5B;AACD;;;;;;AAOA,MAAa,cACZ,eAC4C;CAC5C,MAAM,SAAS,iBAAiB;CAChC,OAAO,OAAO,IAAI,aAAa;EAI9B,MAAM,aAAa,aAAa;EAChC,IAAI,eAAe,MAClB,OAAO,OAAO,OAAO,KACpB,IAAI,oBAAoB;GACvB,SAAS;GACT,SACC;GACD,MAAM;EACP,CAAC,CACF;EAGD,MAAM,WAAW,OAAO,OAAO,KAAK,OAAO,KAAK,UAAU,CAAC;EAC3D,IAAI,KAAK,UAAU,QAAQ,GAC1B,OAAO,OAAO,OAAO,KAAK,uBAAuB,QAAQ,CAAC;EAE3D,MAAM,SAAS,SAAS;EACxB,MAAM,QAAS,OAA+D;EAC9E,MAAM,UAAU,QAAQ,OAAO,kBAAkB;EAEjD,MAAM,gBAAgB,oBAAoB,MAAM,OAAO;EAEvD,MAAM,OAAO,OAAO,OAAO,KAC1B,OAAO,IAAI,aAAa;GACvB,OAAO,OAAO,QAAQ,uBAAuB,YAAY;GACzD,OAAO,OAAO,aAAa;IAAE;IAAe,WAAW;GAAK,CAAC;EAC9D,CAAC,CAAC,CAAC,KACF,OAAO,QAAQ,kBAAkB,OAAO,CAAC,GACzC,OAAO,QAAQ,OAAO,MAAM,CAAC,OAAO,cAAc,CAAC,CAAC,CAAC,CACtD,CACD;EACA,IAAI,KAAK,UAAU,IAAI,GACtB,OAAO,OAAO,OAAO,KACpB,IAAI,iBAAiB;GACpB,SAAS;GACT,OAAO,MAAM,OAAO,KAAK,KAA6B;EACvD,CAAC,CACF;EAED,OAAO,EAAE,UAAU,SAAS,GAAG;CAChC,CAAC;AACF"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import "../../api/inference-network.mjs";
|
|
2
|
+
import { DEPLOYMENT_FILENAME, decodeDeployment } from "../../orchestrators/codegen/deployment.mjs";
|
|
3
|
+
import { ExitCode } from "../../surfaces/cli/sysexits.mjs";
|
|
4
|
+
import { CliInternalError, CliUsageError } from "../../surfaces/cli/errors.mjs";
|
|
5
|
+
import { emitSuccess } from "../../surfaces/cli/output.mjs";
|
|
6
|
+
import { probeSupervisorPresence } from "../../surfaces/cli/commands/supervisor-presence.mjs";
|
|
7
|
+
import "../../surfaces/cli/commands/index.mjs";
|
|
8
|
+
import { cliErrorFromConfigExit } from "../bail.mjs";
|
|
9
|
+
import { makeConfigLoader } from "./config-loader.mjs";
|
|
10
|
+
import { resolvedIdentityForStack } from "./identity.mjs";
|
|
11
|
+
import { runApplyLive } from "./apply.mjs";
|
|
12
|
+
import { DEPLOYMENTS_DIRNAME, deploymentBody, renderNetworkDeploymentFile } from "../../orchestrators/codegen/deployment-network-file.mjs";
|
|
13
|
+
import { Effect, Exit } from "effect";
|
|
14
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
15
|
+
import { dirname, resolve } from "node:path";
|
|
16
|
+
//#region src/cli/wirings/dump-deployment.ts
|
|
17
|
+
/** Read the on-disk deployment as raw text. The file is written by boot's
|
|
18
|
+
* post-acquire hook (`writeDeployment`) at `<stackRoot>/deployment.json`;
|
|
19
|
+
* a miss here means the boot never produced it (or a hand-removal), which
|
|
20
|
+
* is an internal inconsistency the operator can't act on directly. */
|
|
21
|
+
const readDeploymentText = (deploymentFile) => Effect.try({
|
|
22
|
+
try: () => readFileSync(deploymentFile, "utf8"),
|
|
23
|
+
catch: (cause) => new CliInternalError({
|
|
24
|
+
message: `failed to read deployment at ${deploymentFile}`,
|
|
25
|
+
cause
|
|
26
|
+
})
|
|
27
|
+
});
|
|
28
|
+
const writeOutFile = (out, text) => Effect.try({
|
|
29
|
+
try: () => writeFileSync(out, text, "utf8"),
|
|
30
|
+
catch: (cause) => new CliInternalError({
|
|
31
|
+
message: `failed to write deployment to ${out}`,
|
|
32
|
+
cause
|
|
33
|
+
})
|
|
34
|
+
});
|
|
35
|
+
/** Decode the deployment text for the JSON envelope `data`, routed through
|
|
36
|
+
* the shared {@link decodeDeployment} so the parse-and-validate decision is
|
|
37
|
+
* centralized (same seam the Vite plugin and codegen verb use). A failure
|
|
38
|
+
* here means the on-disk file is corrupt — bad JSON OR a shape that
|
|
39
|
+
* violates `DeploymentSchema` — so surface it rather than emit a bad
|
|
40
|
+
* envelope. (The file is written by `writeDeployment` from the same schema,
|
|
41
|
+
* so a conforming boot-written file always decodes; only a corrupt or
|
|
42
|
+
* hand-edited file trips the stricter validation.) */
|
|
43
|
+
const parseDeployment = (text, deploymentFile) => Effect.try({
|
|
44
|
+
try: () => decodeDeployment(text),
|
|
45
|
+
catch: (cause) => new CliInternalError({
|
|
46
|
+
message: `could not decode deployment at ${deploymentFile} (malformed JSON or invalid shape)`,
|
|
47
|
+
cause
|
|
48
|
+
})
|
|
49
|
+
});
|
|
50
|
+
/** The project root the `deployments/<net>.ts` file is written relative to:
|
|
51
|
+
* the directory of the resolved `devstack.config.ts` (so a `--config` in a
|
|
52
|
+
* subdir still lands `deployments/` next to that app's `src/`), else the
|
|
53
|
+
* process cwd. */
|
|
54
|
+
const projectRootOf = (configPath) => configPath === void 0 ? process.cwd() : dirname(resolve(configPath));
|
|
55
|
+
/** Render + write the typed `deployments/<network>.ts` for `network` from the
|
|
56
|
+
* resolved envelope, creating the `deployments/` dir if absent. Loud-fails
|
|
57
|
+
* (usage error) when the envelope carries no such network — listing the ones
|
|
58
|
+
* it does, so the operator can pick a valid name. */
|
|
59
|
+
const writeNetworkDeploymentFile = (envelope, network, projectRoot) => Effect.gen(function* () {
|
|
60
|
+
const unit = envelope.networks[network];
|
|
61
|
+
if (unit === void 0) {
|
|
62
|
+
const available = Object.keys(envelope.networks).sort().join(", ");
|
|
63
|
+
return yield* Effect.fail(new CliUsageError({
|
|
64
|
+
message: `the resolved deployment has no network "${network}"`,
|
|
65
|
+
hint: available.length > 0 ? `available networks: ${available}` : "the deployment carries no networks"
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
if (network === "localnet" || unit.local === true) return yield* Effect.fail(new CliUsageError({
|
|
69
|
+
message: `cannot dump the live local network "${network}" as a committed deployment`,
|
|
70
|
+
hint: "committed deployments are for real networks; pick a non-local --network (e.g. testnet, mainnet)"
|
|
71
|
+
}));
|
|
72
|
+
const rendered = renderNetworkDeploymentFile(network, unit);
|
|
73
|
+
if (!rendered.ok) return yield* Effect.fail(new CliInternalError({
|
|
74
|
+
message: `failed to render deployments/${network}.ts`,
|
|
75
|
+
cause: rendered.error
|
|
76
|
+
}));
|
|
77
|
+
const dir = resolve(projectRoot, DEPLOYMENTS_DIRNAME);
|
|
78
|
+
const outFile = resolve(dir, `${network}.ts`);
|
|
79
|
+
yield* Effect.try({
|
|
80
|
+
try: () => {
|
|
81
|
+
mkdirSync(dir, { recursive: true });
|
|
82
|
+
writeFileSync(outFile, rendered.text, "utf8");
|
|
83
|
+
},
|
|
84
|
+
catch: (cause) => new CliInternalError({
|
|
85
|
+
message: `failed to write ${outFile}`,
|
|
86
|
+
cause
|
|
87
|
+
})
|
|
88
|
+
});
|
|
89
|
+
return outFile;
|
|
90
|
+
});
|
|
91
|
+
const runDumpDeployment = (identity, opts) => {
|
|
92
|
+
const loader = makeConfigLoader();
|
|
93
|
+
return Effect.gen(function* () {
|
|
94
|
+
const started = Date.now();
|
|
95
|
+
const loadExit = yield* Effect.exit(loader.load(opts.configPath));
|
|
96
|
+
if (Exit.isFailure(loadExit)) return yield* Effect.fail(cliErrorFromConfigExit(loadExit));
|
|
97
|
+
const stack = loadExit.value.engine;
|
|
98
|
+
const effectiveIdentity = resolvedIdentityForStack(identity, stack);
|
|
99
|
+
const deploymentFile = resolve(effectiveIdentity.stackRoot, DEPLOYMENT_FILENAME);
|
|
100
|
+
if (!(yield* probeSupervisorPresence(effectiveIdentity.rosterFile).pipe(Effect.catch(() => Effect.succeed({
|
|
101
|
+
live: false,
|
|
102
|
+
pid: null,
|
|
103
|
+
hostname: null
|
|
104
|
+
})))).live) yield* runApplyLive(opts.configPath, identity);
|
|
105
|
+
const text = yield* readDeploymentText(deploymentFile);
|
|
106
|
+
const data = yield* parseDeployment(text, deploymentFile);
|
|
107
|
+
if (opts.network !== void 0) {
|
|
108
|
+
const projectRoot = projectRootOf(opts.configPath);
|
|
109
|
+
const outFile = yield* writeNetworkDeploymentFile(data, opts.network, projectRoot);
|
|
110
|
+
const writtenUnit = data.networks[opts.network];
|
|
111
|
+
yield* emitSuccess(opts.io, opts.outputMode, {
|
|
112
|
+
command: "dump-deployment",
|
|
113
|
+
elapsedMs: Date.now() - started,
|
|
114
|
+
data: deploymentBody(opts.network, writtenUnit),
|
|
115
|
+
humanLines: [`wrote ${outFile}`]
|
|
116
|
+
});
|
|
117
|
+
return { exitCode: ExitCode.OK };
|
|
118
|
+
}
|
|
119
|
+
if (opts.out !== void 0) {
|
|
120
|
+
yield* writeOutFile(opts.out, text);
|
|
121
|
+
yield* emitSuccess(opts.io, opts.outputMode, {
|
|
122
|
+
command: "dump-deployment",
|
|
123
|
+
elapsedMs: Date.now() - started,
|
|
124
|
+
data,
|
|
125
|
+
humanLines: [`wrote deployment to ${opts.out}`]
|
|
126
|
+
});
|
|
127
|
+
return { exitCode: ExitCode.OK };
|
|
128
|
+
}
|
|
129
|
+
yield* emitSuccess(opts.io, opts.outputMode, {
|
|
130
|
+
command: "dump-deployment",
|
|
131
|
+
elapsedMs: Date.now() - started,
|
|
132
|
+
data,
|
|
133
|
+
humanLines: [text.endsWith("\n") ? text.slice(0, -1) : text]
|
|
134
|
+
});
|
|
135
|
+
return { exitCode: ExitCode.OK };
|
|
136
|
+
});
|
|
137
|
+
};
|
|
138
|
+
//#endregion
|
|
139
|
+
export { runDumpDeployment };
|
|
140
|
+
|
|
141
|
+
//# sourceMappingURL=dump-deployment.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dump-deployment.mjs","names":["resolvePath"],"sources":["../../../src/cli/wirings/dump-deployment.ts"],"sourcesContent":["// `devstack dump-deployment` verb wiring.\n//\n// Emits the stack's `deployment.json` deployment — the single source of\n// on-chain ids (see `orchestrators/codegen/deployment.ts`). This is the\n// supported way to obtain a committed deployment file for a real-network\n// deploy (docs' \"Deploy to a real network / Option A\"), replacing the\n// manual `cp .devstack/stacks/<stack>/deployment.json …`.\n//\n// Live-aware, mirroring `apply.ts` / `snapshot.ts`:\n// - If a supervisor owns the selected stack (roster probe), the\n// deployment already exists on disk — boot's post-acquire hook wrote\n// it (`boot.ts buildProductionPostAcquireHook`). Read + emit it; NO\n// re-boot.\n// - Otherwise, run the SAME one-shot boot `apply` runs when no\n// supervisor is live (`runApplyLive` → `superviseStackWithProductionBoot`\n// with `lifetime: 'one-shot'`). That boots, writes the deployment to\n// `<stackRoot>/deployment.json`, and tears down. We then read the\n// freshly-written file and emit it.\n//\n// The on-disk file is already pretty-printed by `writeDeployment`\n// (`JSON.stringify(config, null, 2)\\n`), so `--out` copies its bytes\n// verbatim and stdout prints them verbatim — the format matches the\n// boot-written file exactly.\n\nimport { mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, resolve as resolvePath } from 'node:path';\n\nimport { Effect, Exit } from 'effect';\n\nimport type { SupervisedStack } from '../../substrate/runtime/index.ts';\nimport {\n\tdecodeDeployment,\n\tDEPLOYMENT_FILENAME,\n\ttype DevstackDeployment,\n} from '../../orchestrators/codegen/deployment.ts';\nimport {\n\tdeploymentBody,\n\tDEPLOYMENTS_DIRNAME,\n\trenderNetworkDeploymentFile,\n} from '../../orchestrators/codegen/deployment-network-file.ts';\nimport { LOCAL_NETWORK_NAME } from '../../api/inference-network.ts';\nimport { type CliError, CliInternalError, CliUsageError } from '../../surfaces/cli/errors.ts';\nimport { type CommandResult } from '../../surfaces/cli/commands/index.ts';\nimport { probeSupervisorPresence } from '../../surfaces/cli/commands/index.ts';\nimport { ExitCode } from '../../surfaces/cli/sysexits.ts';\nimport { type CliIO, emitSuccess } from '../../surfaces/cli/output.ts';\nimport type { OutputMode } from '../../surfaces/cli/flags.ts';\nimport type { LoadedConfig } from '../../surfaces/cli/commands/config-loader.ts';\n\nimport { cliErrorFromConfigExit } from '../bail.ts';\nimport { makeConfigLoader } from './config-loader.ts';\nimport { resolvedIdentityForStack, type ResolvedIdentity } from './identity.ts';\nimport { runApplyLive } from './apply.ts';\n\nexport interface DumpDeploymentOptions {\n\treadonly configPath: string | undefined;\n\t/** Destination for the pretty deployment JSON. When omitted (and no\n\t * `--network`), the JSON is printed to stdout. */\n\treadonly out: string | undefined;\n\t/** When set, write a TYPED single-network `deployments/<network>.ts`\n\t * (`export const deployment = {…} satisfies AppNetworkDeployment`) sourced\n\t * from the resolved envelope's `networks.<network>` entry, instead of\n\t * dumping the raw envelope JSON. The committed, `tsc`-checked authoring\n\t * surface for a real-network deploy. */\n\treadonly network: string | undefined;\n\treadonly io: CliIO;\n\treadonly outputMode: OutputMode;\n}\n\n/** Read the on-disk deployment as raw text. The file is written by boot's\n * post-acquire hook (`writeDeployment`) at `<stackRoot>/deployment.json`;\n * a miss here means the boot never produced it (or a hand-removal), which\n * is an internal inconsistency the operator can't act on directly. */\nconst readDeploymentText = (deploymentFile: string): Effect.Effect<string, CliError> =>\n\tEffect.try({\n\t\ttry: () => readFileSync(deploymentFile, 'utf8'),\n\t\tcatch: (cause) =>\n\t\t\tnew CliInternalError({\n\t\t\t\tmessage: `failed to read deployment at ${deploymentFile}`,\n\t\t\t\tcause,\n\t\t\t}),\n\t});\n\nconst writeOutFile = (out: string, text: string): Effect.Effect<void, CliError> =>\n\tEffect.try({\n\t\ttry: () => writeFileSync(out, text, 'utf8'),\n\t\tcatch: (cause) =>\n\t\t\tnew CliInternalError({ message: `failed to write deployment to ${out}`, cause }),\n\t});\n\n/** Decode the deployment text for the JSON envelope `data`, routed through\n * the shared {@link decodeDeployment} so the parse-and-validate decision is\n * centralized (same seam the Vite plugin and codegen verb use). A failure\n * here means the on-disk file is corrupt — bad JSON OR a shape that\n * violates `DeploymentSchema` — so surface it rather than emit a bad\n * envelope. (The file is written by `writeDeployment` from the same schema,\n * so a conforming boot-written file always decodes; only a corrupt or\n * hand-edited file trips the stricter validation.) */\nconst parseDeployment = (\n\ttext: string,\n\tdeploymentFile: string,\n): Effect.Effect<DevstackDeployment, CliError> =>\n\tEffect.try({\n\t\ttry: () => decodeDeployment(text),\n\t\tcatch: (cause) =>\n\t\t\tnew CliInternalError({\n\t\t\t\t// `decodeDeployment` throws on BOTH malformed JSON AND a shape that\n\t\t\t\t// violates `DeploymentSchema`, so the message must cover both causes.\n\t\t\t\tmessage: `could not decode deployment at ${deploymentFile} (malformed JSON or invalid shape)`,\n\t\t\t\tcause,\n\t\t\t}),\n\t});\n\n/** The project root the `deployments/<net>.ts` file is written relative to:\n * the directory of the resolved `devstack.config.ts` (so a `--config` in a\n * subdir still lands `deployments/` next to that app's `src/`), else the\n * process cwd. */\nconst projectRootOf = (configPath: string | undefined): string =>\n\tconfigPath === undefined ? process.cwd() : dirname(resolvePath(configPath));\n\n/** Render + write the typed `deployments/<network>.ts` for `network` from the\n * resolved envelope, creating the `deployments/` dir if absent. Loud-fails\n * (usage error) when the envelope carries no such network — listing the ones\n * it does, so the operator can pick a valid name. */\nexport const writeNetworkDeploymentFile = (\n\tenvelope: DevstackDeployment,\n\tnetwork: string,\n\tprojectRoot: string,\n): Effect.Effect<string, CliError> =>\n\tEffect.gen(function* () {\n\t\tconst unit = envelope.networks[network];\n\t\tif (unit === undefined) {\n\t\t\tconst available = Object.keys(envelope.networks).sort().join(', ');\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew CliUsageError({\n\t\t\t\t\tmessage: `the resolved deployment has no network \"${network}\"`,\n\t\t\t\t\thint:\n\t\t\t\t\t\tavailable.length > 0\n\t\t\t\t\t\t\t? `available networks: ${available}`\n\t\t\t\t\t\t\t: 'the deployment carries no networks',\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\t// A committed `deployments/<net>.ts` is non-local by definition\n\t\t// (deployment-network-file.ts header) — it is the authoring surface for a\n\t\t// REAL-network deploy. The live local stack's envelope is keyed under\n\t\t// `localnet` with `local: true`, so `--network localnet` (or any unit\n\t\t// flagged `local`) must be rejected rather than write a committed file\n\t\t// that captures the throwaway dev stack.\n\t\tif (network === LOCAL_NETWORK_NAME || unit.local === true) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew CliUsageError({\n\t\t\t\t\tmessage: `cannot dump the live local network \"${network}\" as a committed deployment`,\n\t\t\t\t\thint: 'committed deployments are for real networks; pick a non-local --network (e.g. testnet, mainnet)',\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\tconst rendered = renderNetworkDeploymentFile(network, unit);\n\t\tif (!rendered.ok) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew CliInternalError({\n\t\t\t\t\tmessage: `failed to render deployments/${network}.ts`,\n\t\t\t\t\tcause: rendered.error,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\tconst dir = resolvePath(projectRoot, DEPLOYMENTS_DIRNAME);\n\t\tconst outFile = resolvePath(dir, `${network}.ts`);\n\t\tyield* Effect.try({\n\t\t\ttry: () => {\n\t\t\t\tmkdirSync(dir, { recursive: true });\n\t\t\t\twriteFileSync(outFile, rendered.text, 'utf8');\n\t\t\t},\n\t\t\tcatch: (cause) => new CliInternalError({ message: `failed to write ${outFile}`, cause }),\n\t\t});\n\t\treturn outFile;\n\t});\n\nexport const runDumpDeployment = (\n\tidentity: ResolvedIdentity,\n\topts: DumpDeploymentOptions,\n): Effect.Effect<CommandResult, CliError> => {\n\tconst loader = makeConfigLoader();\n\treturn Effect.gen(function* () {\n\t\tconst started = Date.now();\n\n\t\t// Resolve identity against the EFFECTIVE stack (explicit\n\t\t// `--stack`/env > `config.stackName` > inferred), matching `apply` /\n\t\t// `snapshot`, so the roster probe + deployment path target the same\n\t\t// stack the operator selected.\n\t\tconst loadExit = yield* Effect.exit(loader.load(opts.configPath));\n\t\tif (Exit.isFailure(loadExit)) {\n\t\t\treturn yield* Effect.fail(cliErrorFromConfigExit(loadExit));\n\t\t}\n\t\tconst stack = (loadExit.value as LoadedConfig & { readonly engine: SupervisedStack }).engine;\n\t\tconst effectiveIdentity = resolvedIdentityForStack(identity, stack);\n\t\tconst deploymentFile = resolvePath(effectiveIdentity.stackRoot, DEPLOYMENT_FILENAME);\n\n\t\t// Live supervisor? The deployment already exists on disk (boot wrote\n\t\t// it). Otherwise run the one-shot boot `apply` runs when nothing is\n\t\t// live — it writes the deployment to `deploymentFile` and tears down — then\n\t\t// read the file it produced.\n\t\tconst presence = yield* probeSupervisorPresence(effectiveIdentity.rosterFile).pipe(\n\t\t\tEffect.catch(() => Effect.succeed({ live: false, pid: null, hostname: null })),\n\t\t);\n\t\tif (!presence.live) {\n\t\t\t// `runApplyLive` re-probes (sees no live supervisor) and runs the\n\t\t\t// one-shot boot; its config-load / boot failures already surface as\n\t\t\t// typed `CliError`s. We pass the ORIGINAL identity — `runApplyLive`\n\t\t\t// re-derives the effective stack from the same config internally.\n\t\t\tyield* runApplyLive(opts.configPath, identity);\n\t\t}\n\n\t\tconst text = yield* readDeploymentText(deploymentFile);\n\t\tconst data = yield* parseDeployment(text, deploymentFile);\n\n\t\t// `--network <net>`: emit a TYPED single-network `deployments/<net>.ts`\n\t\t// (`satisfies AppNetworkDeployment`) instead of the raw envelope. Sourced\n\t\t// from the resolved envelope's `networks.<net>` entry — the same data\n\t\t// `assembleDeployment` produced — written next to the app's `src/`.\n\t\tif (opts.network !== undefined) {\n\t\t\tconst projectRoot = projectRootOf(opts.configPath);\n\t\t\tconst outFile = yield* writeNetworkDeploymentFile(data, opts.network, projectRoot);\n\t\t\t// The JSON `data` must mirror the file actually written — the NORMALIZED\n\t\t\t// body (network re-derived from the arg, dev-only `local` dropped), NOT\n\t\t\t// the raw envelope unit (which still carries `local` + the boot-written\n\t\t\t// `network`). `writeNetworkDeploymentFile` already guaranteed the unit\n\t\t\t// exists, so the lookup here is non-undefined.\n\t\t\tconst writtenUnit = data.networks[opts.network]!;\n\t\t\tyield* emitSuccess(opts.io, opts.outputMode, {\n\t\t\t\tcommand: 'dump-deployment',\n\t\t\t\telapsedMs: Date.now() - started,\n\t\t\t\tdata: deploymentBody(opts.network, writtenUnit),\n\t\t\t\thumanLines: [`wrote ${outFile}`],\n\t\t\t});\n\t\t\treturn { exitCode: ExitCode.OK };\n\t\t}\n\n\t\tif (opts.out !== undefined) {\n\t\t\tyield* writeOutFile(opts.out, text);\n\t\t\tyield* emitSuccess(opts.io, opts.outputMode, {\n\t\t\t\tcommand: 'dump-deployment',\n\t\t\t\telapsedMs: Date.now() - started,\n\t\t\t\tdata,\n\t\t\t\thumanLines: [`wrote deployment to ${opts.out}`],\n\t\t\t});\n\t\t\treturn { exitCode: ExitCode.OK };\n\t\t}\n\n\t\t// No `--out`: print the deployment JSON to stdout. In human mode the\n\t\t// raw pretty JSON IS the output (trailing newline trimmed by the IO\n\t\t// layer); in JSON mode the same object rides the success envelope's\n\t\t// `data`.\n\t\tyield* emitSuccess(opts.io, opts.outputMode, {\n\t\t\tcommand: 'dump-deployment',\n\t\t\telapsedMs: Date.now() - started,\n\t\t\tdata,\n\t\t\thumanLines: [text.endsWith('\\n') ? text.slice(0, -1) : text],\n\t\t});\n\t\treturn { exitCode: ExitCode.OK };\n\t});\n};\n"],"mappings":"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"}
|
|
@@ -121,7 +121,6 @@ const runSnapshotCaptureDirectLoaded = (identity, loaded, args) => Effect.gen(fu
|
|
|
121
121
|
options: stack.options
|
|
122
122
|
}, identityValue, state, {
|
|
123
123
|
extras: stack.options.extras,
|
|
124
|
-
networkOptions: stack.options.networkOptions,
|
|
125
124
|
lifetime: "one-shot",
|
|
126
125
|
withinScope: () => Effect.gen(function* () {
|
|
127
126
|
const snapshot = yield* SnapshotOrchestratorService;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshot.mjs","names":[],"sources":["../../../src/cli/wirings/snapshot.ts"],"sourcesContent":["// `devstack snapshot save / restore / delete` verb wirings.\n//\n// `save` is live-aware: when a supervisor owns the stack, publish a\n// `snapshot.capture` command and read the structured result from the\n// command-channel reply payload (no tail-fiber). `restore` and\n// `delete` are direct/offline only; they refuse to run when a\n// supervisor is live.\n\nimport { dirname } from 'node:path';\n\nimport { Effect, Exit, FileSystem, Logger } from 'effect';\n\nimport type { Identity } from '../../substrate/identity.ts';\nimport { mintRandomSuffix } from '../../substrate/runtime/random-suffix.ts';\nimport {\n\tcommandChannelPaths,\n\tmakeCommandChannelPublisher,\n\tmakeProjectionRef,\n\ttype SupervisedStack,\n} from '../../substrate/runtime/index.ts';\nimport { superviseStackWithProductionBoot } from '../../orchestrators/boot.ts';\nimport {\n\tcomputeSnapshotGraphInputFromStack,\n\tSnapshotOrchestratorService,\n\ttype SnapshotMetadata,\n} from '../../orchestrators/snapshot/index.ts';\nimport { CliInternalError, CliUnavailableError } from '../../surfaces/cli/index.ts';\nimport { probeSupervisorPresence } from '../../surfaces/cli/commands/index.ts';\nimport type { LoadedConfig } from '../../surfaces/cli/commands/config-loader.ts';\n\nimport { makeConfigLoader, resolveConfigPath } from './config-loader.ts';\nimport {\n\tensureNoLiveSupervisor,\n\tidentityValueFor,\n\tresolvedIdentityForStack,\n\ttype ResolvedIdentity,\n} from './identity.ts';\nimport { buildDirectSnapshotLayers, buildVerbLayers } from '../../orchestrators/layers.ts';\nimport { provideFileSystem } from './provide-file-system.ts';\nimport { readDevstackVersion } from './read-devstack-version.ts';\n\nconst LIVE_SNAPSHOT_CAPTURE_TIMEOUT_MILLIS = 60 * 60 * 1000;\n\nconst mintCliSnapshotId = (): string => `snap-${Date.now()}-${mintRandomSuffix(8)}`;\n\n/** Structured payload that the supervisor's command-channel bridge\n * attaches to the ack/error reply for `snapshot.capture`. Mirrors the\n * shape produced by `cli/wirings/up.ts:snapshotCaptureAckFromEvent`. */\ninterface SnapshotCaptureAckPayload {\n\treadonly kind: 'captured' | 'failed' | 'skipped';\n\treadonly snapshotId?: string;\n\treadonly name?: string;\n\treadonly summary?: string;\n\treadonly reason?: string;\n}\n\nconst isCapturePayload = (value: unknown): value is SnapshotCaptureAckPayload => {\n\tif (typeof value !== 'object' || value === null) return false;\n\tconst kind = (value as { readonly kind?: unknown }).kind;\n\treturn kind === 'captured' || kind === 'failed' || kind === 'skipped';\n};\n\n/** Publish `snapshot.capture` to a live supervisor and surface its\n * structured ack/error payload. The reply payload carries the\n * captured metadata (success) or failure summary (failure) — no\n * side-channel event tail required. Returns `null` if no supervisor\n * is live so the caller can fall through to the direct path. */\nexport const runSnapshotCaptureAgainstLiveSupervisor = (\n\tidentity: ResolvedIdentity,\n\targs: { readonly snapshotId?: string; readonly name?: string },\n): Effect.Effect<{ readonly snapshotId: string; readonly name: string } | null, unknown> =>\n\tEffect.gen(function* () {\n\t\tconst presence = yield* probeSupervisorPresence(identity.rosterFile).pipe(\n\t\t\tEffect.catch(() => Effect.succeed({ live: false, pid: null, hostname: null })),\n\t\t);\n\t\tif (!presence.live) return null;\n\n\t\tconst snapshotId = args.snapshotId ?? mintCliSnapshotId();\n\t\treturn yield* Effect.scoped(\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst publisher = yield* makeCommandChannelPublisher(\n\t\t\t\t\tcommandChannelPaths(identity.stackRoot),\n\t\t\t\t);\n\t\t\t\tconst published = yield* publisher.publish({\n\t\t\t\t\ttag: 'snapshot.capture',\n\t\t\t\t\tsnapshotId,\n\t\t\t\t\t...(args.name === undefined ? {} : { name: args.name }),\n\t\t\t\t});\n\t\t\t\tconst reply = yield* publisher.awaitCompletion(published, {\n\t\t\t\t\ttimeoutMillis: LIVE_SNAPSHOT_CAPTURE_TIMEOUT_MILLIS,\n\t\t\t\t});\n\n\t\t\t\tif (reply.ok) {\n\t\t\t\t\tconst payload = isCapturePayload(reply.payload) ? reply.payload : null;\n\t\t\t\t\tif (payload?.kind === 'captured' && typeof payload.snapshotId === 'string') {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tsnapshotId: payload.snapshotId,\n\t\t\t\t\t\t\tname: payload.name ?? args.name ?? payload.snapshotId,\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t\t// Older supervisor without structured payload (or a peer mock\n\t\t\t\t\t// in tests): synthesize from the request — the supervisor\n\t\t\t\t\t// already accepted our snapshotId, so it's authoritative.\n\t\t\t\t\treturn {\n\t\t\t\t\t\tsnapshotId,\n\t\t\t\t\t\tname: args.name ?? snapshotId,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tconst payload = isCapturePayload(reply.payload) ? reply.payload : null;\n\t\t\t\tif (payload?.kind === 'skipped') {\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\tnew CliUnavailableError({\n\t\t\t\t\t\t\tservice: 'snapshot capture',\n\t\t\t\t\t\t\tmessage: 'another snapshot capture is already running',\n\t\t\t\t\t\t\thint: 'wait for the current snapshot to finish and try again',\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (payload?.kind === 'failed') {\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\tnew CliInternalError({\n\t\t\t\t\t\t\tmessage: `snapshot capture failed: ${payload.summary ?? reply.message}`,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\t// No structured payload — surface the raw reply.message.\n\t\t\t\tif (/already running/i.test(reply.message)) {\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\tnew CliUnavailableError({\n\t\t\t\t\t\t\tservice: 'snapshot capture',\n\t\t\t\t\t\t\tmessage: 'another snapshot capture is already running',\n\t\t\t\t\t\t\thint: 'wait for the current snapshot to finish and try again',\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\tnew CliUnavailableError({\n\t\t\t\t\t\tservice: 'devstack supervisor',\n\t\t\t\t\t\tmessage: reply.message,\n\t\t\t\t\t\thint: 'check the attached `devstack up` session and try again',\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}),\n\t\t);\n\t});\n\nexport const runSnapshotRestoreDirect = (\n\tidentity: ResolvedIdentity,\n\tsnapshotId: string,\n): Effect.Effect<void, unknown> => {\n\tconst program = Effect.gen(function* () {\n\t\tconst snapshot = yield* SnapshotOrchestratorService;\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\t// Offline restore: there is NO live supervisor (ensured below), so no\n\t\t// plugin contributes a live identity slice. Call `restore` with no\n\t\t// `participants` — `runRestore` reads this as \"no live stack\" and\n\t\t// skips ONLY the cross-plugin contribution guard (the snapshot's own\n\t\t// recorded identity has nothing live to disagree with). The runtime\n\t\t// guard (app/stack/network) and the snapshot-side emptiness refusal\n\t\t// still fire. This is the same empty-participants startup restore\n\t\t// contract interrupted-restore recovery uses.\n\t\tyield* provideFileSystem(fs, snapshot.restore({ id: snapshotId }));\n\t});\n\tconst restored = program.pipe(\n\t\tEffect.provide(\n\t\t\tbuildDirectSnapshotLayers({\n\t\t\t\tidentity: identityValueFor(identity),\n\t\t\t\truntimeRoot: identity.runtimeRoot,\n\t\t\t}),\n\t\t),\n\t\tEffect.provide(Logger.layer([Logger.consolePretty()])),\n\t);\n\treturn ensureNoLiveSupervisor(\n\t\tidentity,\n\t\t'shut down the attached `devstack up` session before restoring a snapshot',\n\t).pipe(Effect.andThen(restored));\n};\n\nexport const runSnapshotDeleteDirect = (\n\tidentity: ResolvedIdentity,\n\tsnapshotId: string,\n): Effect.Effect<void, unknown> => {\n\tconst program = Effect.gen(function* () {\n\t\tconst snapshot = yield* SnapshotOrchestratorService;\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tyield* provideFileSystem(fs, snapshot.delete(snapshotId));\n\t});\n\treturn program.pipe(\n\t\tEffect.provide(\n\t\t\tbuildDirectSnapshotLayers({\n\t\t\t\tidentity: identityValueFor(identity),\n\t\t\t\truntimeRoot: identity.runtimeRoot,\n\t\t\t}),\n\t\t),\n\t\tEffect.provide(Logger.layer([Logger.consolePretty()])),\n\t);\n};\n\nconst runSnapshotCaptureDirect = (\n\tidentity: ResolvedIdentity,\n\targs: { readonly snapshotId?: string; readonly name?: string; readonly configPath?: string },\n): Effect.Effect<{ readonly snapshotId: string; readonly name: string }, unknown> => {\n\tconst loader = makeConfigLoader();\n\treturn Effect.gen(function* () {\n\t\tconst loaded = yield* loader.load(args.configPath);\n\t\treturn yield* runSnapshotCaptureDirectLoaded(identity, loaded, args);\n\t});\n};\n\nexport const runSnapshotCaptureDirectLoaded = (\n\tidentity: ResolvedIdentity,\n\tloaded: LoadedConfig,\n\targs: { readonly snapshotId?: string; readonly name?: string },\n): Effect.Effect<{ readonly snapshotId: string; readonly name: string }, unknown> =>\n\tEffect.gen(function* () {\n\t\tconst stack = (loaded as LoadedConfig & { readonly engine: SupervisedStack }).engine;\n\t\tconst effectiveIdentity = resolvedIdentityForStack(identity, stack);\n\t\tconst identityValue: Identity = identityValueFor(effectiveIdentity);\n\t\tconst appRoot = dirname(loaded.resolvedConfigPath);\n\t\tconst substrateLayers = buildVerbLayers({\n\t\t\tidentity: identityValue,\n\t\t\tstack,\n\t\t\tappRoot,\n\t\t\truntimeRoot: effectiveIdentity.runtimeRoot,\n\t\t});\n\n\t\tconst program = Effect.gen(function* () {\n\t\t\tconst state = yield* makeProjectionRef();\n\t\t\tlet captureExit: Exit.Exit<void, unknown> = Exit.succeed(undefined);\n\t\t\tconst capturedMeta: { current: SnapshotMetadata | null } = { current: null };\n\t\t\t// The contribution dispatcher + post-acquire hook + built-in\n\t\t\t// plugin-context extension are assembled in ONE place\n\t\t\t// (`orchestrators/boot.ts superviseStackWithProductionBoot`); this\n\t\t\t// one-shot verb supplies only the per-caller inputs (`extras`,\n\t\t\t// `lifetime: 'one-shot'`, and the capture `withinScope` hook).\n\t\t\t// Result semantics are unchanged: the hook still tees the capture\n\t\t\t// `Exit` + metadata into the closure cells read below.\n\t\t\tyield* superviseStackWithProductionBoot(\n\t\t\t\t{ _tag: 'Stack', members: stack.members, options: stack.options },\n\t\t\t\tidentityValue,\n\t\t\t\tstate,\n\t\t\t\t{\n\t\t\t\t\textras: stack.options.extras,\n\t\t\t\t\tnetworkOptions: stack.options.networkOptions,\n\t\t\t\t\tlifetime: 'one-shot',\n\t\t\t\t\t// Offline one-shot capture: the bounce gather → stop → commit →\n\t\t\t\t\t// retag → hard-rm runs here; NO `resume` is injected because\n\t\t\t\t\t// this supervise scope is `one-shot` (it closes right after) —\n\t\t\t\t\t// the NEXT boot is the resume. Direct call to the orchestrator\n\t\t\t\t\t// (the former `captureSnapshot` command-primitive is gone).\n\t\t\t\t\twithinScope: () =>\n\t\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\t\tconst snapshot = yield* SnapshotOrchestratorService;\n\t\t\t\t\t\t\tconst fs = yield* FileSystem.FileSystem;\n\t\t\t\t\t\t\tconst graphInput = yield* computeSnapshotGraphInputFromStack({\n\t\t\t\t\t\t\t\tstack,\n\t\t\t\t\t\t\t\tdevstackVersion: readDevstackVersion({ fallback: '0.0.0' }),\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\treturn yield* snapshot\n\t\t\t\t\t\t\t\t.capture({\n\t\t\t\t\t\t\t\t\t...(args.snapshotId === undefined ? {} : { id: args.snapshotId }),\n\t\t\t\t\t\t\t\t\t...(args.name === undefined ? {} : { label: args.name }),\n\t\t\t\t\t\t\t\t\tgraphInput,\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t.pipe(Effect.provideService(FileSystem.FileSystem, fs));\n\t\t\t\t\t\t}).pipe(\n\t\t\t\t\t\t\tEffect.tap((meta) =>\n\t\t\t\t\t\t\t\tEffect.sync(() => {\n\t\t\t\t\t\t\t\t\tcapturedMeta.current = meta;\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\tEffect.asVoid,\n\t\t\t\t\t\t\tEffect.exit,\n\t\t\t\t\t\t\tEffect.tap((exit) =>\n\t\t\t\t\t\t\t\tEffect.sync(() => {\n\t\t\t\t\t\t\t\t\tcaptureExit = exit;\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\tEffect.asVoid,\n\t\t\t\t\t\t),\n\t\t\t\t},\n\t\t\t);\n\t\t\tif (Exit.isFailure(captureExit)) {\n\t\t\t\tyield* Effect.failCause(captureExit.cause);\n\t\t\t}\n\t\t\tif (capturedMeta.current === null) {\n\t\t\t\treturn yield* Effect.die('snapshot capture completed without metadata');\n\t\t\t}\n\t\t\tconst meta = capturedMeta.current;\n\t\t\treturn { snapshotId: meta.id, name: meta.label ?? meta.id };\n\t\t});\n\n\t\treturn yield* ensureNoLiveSupervisor(\n\t\t\teffectiveIdentity,\n\t\t\t'shut down the attached `devstack up` session before saving a snapshot',\n\t\t).pipe(\n\t\t\tEffect.andThen(\n\t\t\t\tprogram.pipe(\n\t\t\t\t\tEffect.provide(substrateLayers),\n\t\t\t\t\tEffect.provide(Logger.layer([Logger.consolePretty()])),\n\t\t\t\t),\n\t\t\t),\n\t\t);\n\t});\n\nexport const runSnapshotCaptureLiveAware = (\n\tidentity: ResolvedIdentity,\n\targs: { readonly snapshotId?: string; readonly name?: string; readonly configPath?: string },\n): Effect.Effect<{ readonly snapshotId: string; readonly name: string }, unknown> =>\n\tEffect.gen(function* () {\n\t\tif (args.configPath === undefined && resolveConfigPath(undefined) === null) {\n\t\t\tconst live = yield* runSnapshotCaptureAgainstLiveSupervisor(identity, args);\n\t\t\tif (live !== null) return live;\n\t\t\treturn yield* runSnapshotCaptureDirect(identity, args);\n\t\t}\n\t\tconst loaded = yield* makeConfigLoader().load(args.configPath);\n\t\tconst stack = (loaded as LoadedConfig & { readonly engine: SupervisedStack }).engine;\n\t\tconst effectiveIdentity = resolvedIdentityForStack(identity, stack);\n\t\tconst live = yield* runSnapshotCaptureAgainstLiveSupervisor(effectiveIdentity, args);\n\t\tif (live !== null) return live;\n\t\treturn yield* runSnapshotCaptureDirectLoaded(effectiveIdentity, loaded, args);\n\t});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"snapshot.mjs","names":[],"sources":["../../../src/cli/wirings/snapshot.ts"],"sourcesContent":["// `devstack snapshot save / restore / delete` verb wirings.\n//\n// `save` is live-aware: when a supervisor owns the stack, publish a\n// `snapshot.capture` command and read the structured result from the\n// command-channel reply payload (no tail-fiber). `restore` and\n// `delete` are direct/offline only; they refuse to run when a\n// supervisor is live.\n\nimport { dirname } from 'node:path';\n\nimport { Effect, Exit, FileSystem, Logger } from 'effect';\n\nimport type { Identity } from '../../substrate/identity.ts';\nimport { mintRandomSuffix } from '../../substrate/runtime/random-suffix.ts';\nimport {\n\tcommandChannelPaths,\n\tmakeCommandChannelPublisher,\n\tmakeProjectionRef,\n\ttype SupervisedStack,\n} from '../../substrate/runtime/index.ts';\nimport { superviseStackWithProductionBoot } from '../../orchestrators/boot.ts';\nimport {\n\tcomputeSnapshotGraphInputFromStack,\n\tSnapshotOrchestratorService,\n\ttype SnapshotMetadata,\n} from '../../orchestrators/snapshot/index.ts';\nimport { CliInternalError, CliUnavailableError } from '../../surfaces/cli/index.ts';\nimport { probeSupervisorPresence } from '../../surfaces/cli/commands/index.ts';\nimport type { LoadedConfig } from '../../surfaces/cli/commands/config-loader.ts';\n\nimport { makeConfigLoader, resolveConfigPath } from './config-loader.ts';\nimport {\n\tensureNoLiveSupervisor,\n\tidentityValueFor,\n\tresolvedIdentityForStack,\n\ttype ResolvedIdentity,\n} from './identity.ts';\nimport { buildDirectSnapshotLayers, buildVerbLayers } from '../../orchestrators/layers.ts';\nimport { provideFileSystem } from './provide-file-system.ts';\nimport { readDevstackVersion } from './read-devstack-version.ts';\n\nconst LIVE_SNAPSHOT_CAPTURE_TIMEOUT_MILLIS = 60 * 60 * 1000;\n\nconst mintCliSnapshotId = (): string => `snap-${Date.now()}-${mintRandomSuffix(8)}`;\n\n/** Structured payload that the supervisor's command-channel bridge\n * attaches to the ack/error reply for `snapshot.capture`. Mirrors the\n * shape produced by `cli/wirings/up.ts:snapshotCaptureAckFromEvent`. */\ninterface SnapshotCaptureAckPayload {\n\treadonly kind: 'captured' | 'failed' | 'skipped';\n\treadonly snapshotId?: string;\n\treadonly name?: string;\n\treadonly summary?: string;\n\treadonly reason?: string;\n}\n\nconst isCapturePayload = (value: unknown): value is SnapshotCaptureAckPayload => {\n\tif (typeof value !== 'object' || value === null) return false;\n\tconst kind = (value as { readonly kind?: unknown }).kind;\n\treturn kind === 'captured' || kind === 'failed' || kind === 'skipped';\n};\n\n/** Publish `snapshot.capture` to a live supervisor and surface its\n * structured ack/error payload. The reply payload carries the\n * captured metadata (success) or failure summary (failure) — no\n * side-channel event tail required. Returns `null` if no supervisor\n * is live so the caller can fall through to the direct path. */\nexport const runSnapshotCaptureAgainstLiveSupervisor = (\n\tidentity: ResolvedIdentity,\n\targs: { readonly snapshotId?: string; readonly name?: string },\n): Effect.Effect<{ readonly snapshotId: string; readonly name: string } | null, unknown> =>\n\tEffect.gen(function* () {\n\t\tconst presence = yield* probeSupervisorPresence(identity.rosterFile).pipe(\n\t\t\tEffect.catch(() => Effect.succeed({ live: false, pid: null, hostname: null })),\n\t\t);\n\t\tif (!presence.live) return null;\n\n\t\tconst snapshotId = args.snapshotId ?? mintCliSnapshotId();\n\t\treturn yield* Effect.scoped(\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst publisher = yield* makeCommandChannelPublisher(\n\t\t\t\t\tcommandChannelPaths(identity.stackRoot),\n\t\t\t\t);\n\t\t\t\tconst published = yield* publisher.publish({\n\t\t\t\t\ttag: 'snapshot.capture',\n\t\t\t\t\tsnapshotId,\n\t\t\t\t\t...(args.name === undefined ? {} : { name: args.name }),\n\t\t\t\t});\n\t\t\t\tconst reply = yield* publisher.awaitCompletion(published, {\n\t\t\t\t\ttimeoutMillis: LIVE_SNAPSHOT_CAPTURE_TIMEOUT_MILLIS,\n\t\t\t\t});\n\n\t\t\t\tif (reply.ok) {\n\t\t\t\t\tconst payload = isCapturePayload(reply.payload) ? reply.payload : null;\n\t\t\t\t\tif (payload?.kind === 'captured' && typeof payload.snapshotId === 'string') {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tsnapshotId: payload.snapshotId,\n\t\t\t\t\t\t\tname: payload.name ?? args.name ?? payload.snapshotId,\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t\t// Older supervisor without structured payload (or a peer mock\n\t\t\t\t\t// in tests): synthesize from the request — the supervisor\n\t\t\t\t\t// already accepted our snapshotId, so it's authoritative.\n\t\t\t\t\treturn {\n\t\t\t\t\t\tsnapshotId,\n\t\t\t\t\t\tname: args.name ?? snapshotId,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tconst payload = isCapturePayload(reply.payload) ? reply.payload : null;\n\t\t\t\tif (payload?.kind === 'skipped') {\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\tnew CliUnavailableError({\n\t\t\t\t\t\t\tservice: 'snapshot capture',\n\t\t\t\t\t\t\tmessage: 'another snapshot capture is already running',\n\t\t\t\t\t\t\thint: 'wait for the current snapshot to finish and try again',\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (payload?.kind === 'failed') {\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\tnew CliInternalError({\n\t\t\t\t\t\t\tmessage: `snapshot capture failed: ${payload.summary ?? reply.message}`,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\t// No structured payload — surface the raw reply.message.\n\t\t\t\tif (/already running/i.test(reply.message)) {\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\tnew CliUnavailableError({\n\t\t\t\t\t\t\tservice: 'snapshot capture',\n\t\t\t\t\t\t\tmessage: 'another snapshot capture is already running',\n\t\t\t\t\t\t\thint: 'wait for the current snapshot to finish and try again',\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\tnew CliUnavailableError({\n\t\t\t\t\t\tservice: 'devstack supervisor',\n\t\t\t\t\t\tmessage: reply.message,\n\t\t\t\t\t\thint: 'check the attached `devstack up` session and try again',\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}),\n\t\t);\n\t});\n\nexport const runSnapshotRestoreDirect = (\n\tidentity: ResolvedIdentity,\n\tsnapshotId: string,\n): Effect.Effect<void, unknown> => {\n\tconst program = Effect.gen(function* () {\n\t\tconst snapshot = yield* SnapshotOrchestratorService;\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\t// Offline restore: there is NO live supervisor (ensured below), so no\n\t\t// plugin contributes a live identity slice. Call `restore` with no\n\t\t// `participants` — `runRestore` reads this as \"no live stack\" and\n\t\t// skips ONLY the cross-plugin contribution guard (the snapshot's own\n\t\t// recorded identity has nothing live to disagree with). The runtime\n\t\t// guard (app/stack/network) and the snapshot-side emptiness refusal\n\t\t// still fire. This is the same empty-participants startup restore\n\t\t// contract interrupted-restore recovery uses.\n\t\tyield* provideFileSystem(fs, snapshot.restore({ id: snapshotId }));\n\t});\n\tconst restored = program.pipe(\n\t\tEffect.provide(\n\t\t\tbuildDirectSnapshotLayers({\n\t\t\t\tidentity: identityValueFor(identity),\n\t\t\t\truntimeRoot: identity.runtimeRoot,\n\t\t\t}),\n\t\t),\n\t\tEffect.provide(Logger.layer([Logger.consolePretty()])),\n\t);\n\treturn ensureNoLiveSupervisor(\n\t\tidentity,\n\t\t'shut down the attached `devstack up` session before restoring a snapshot',\n\t).pipe(Effect.andThen(restored));\n};\n\nexport const runSnapshotDeleteDirect = (\n\tidentity: ResolvedIdentity,\n\tsnapshotId: string,\n): Effect.Effect<void, unknown> => {\n\tconst program = Effect.gen(function* () {\n\t\tconst snapshot = yield* SnapshotOrchestratorService;\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tyield* provideFileSystem(fs, snapshot.delete(snapshotId));\n\t});\n\treturn program.pipe(\n\t\tEffect.provide(\n\t\t\tbuildDirectSnapshotLayers({\n\t\t\t\tidentity: identityValueFor(identity),\n\t\t\t\truntimeRoot: identity.runtimeRoot,\n\t\t\t}),\n\t\t),\n\t\tEffect.provide(Logger.layer([Logger.consolePretty()])),\n\t);\n};\n\nconst runSnapshotCaptureDirect = (\n\tidentity: ResolvedIdentity,\n\targs: { readonly snapshotId?: string; readonly name?: string; readonly configPath?: string },\n): Effect.Effect<{ readonly snapshotId: string; readonly name: string }, unknown> => {\n\tconst loader = makeConfigLoader();\n\treturn Effect.gen(function* () {\n\t\tconst loaded = yield* loader.load(args.configPath);\n\t\treturn yield* runSnapshotCaptureDirectLoaded(identity, loaded, args);\n\t});\n};\n\nexport const runSnapshotCaptureDirectLoaded = (\n\tidentity: ResolvedIdentity,\n\tloaded: LoadedConfig,\n\targs: { readonly snapshotId?: string; readonly name?: string },\n): Effect.Effect<{ readonly snapshotId: string; readonly name: string }, unknown> =>\n\tEffect.gen(function* () {\n\t\tconst stack = (loaded as LoadedConfig & { readonly engine: SupervisedStack }).engine;\n\t\tconst effectiveIdentity = resolvedIdentityForStack(identity, stack);\n\t\tconst identityValue: Identity = identityValueFor(effectiveIdentity);\n\t\tconst appRoot = dirname(loaded.resolvedConfigPath);\n\t\tconst substrateLayers = buildVerbLayers({\n\t\t\tidentity: identityValue,\n\t\t\tstack,\n\t\t\tappRoot,\n\t\t\truntimeRoot: effectiveIdentity.runtimeRoot,\n\t\t});\n\n\t\tconst program = Effect.gen(function* () {\n\t\t\tconst state = yield* makeProjectionRef();\n\t\t\tlet captureExit: Exit.Exit<void, unknown> = Exit.succeed(undefined);\n\t\t\tconst capturedMeta: { current: SnapshotMetadata | null } = { current: null };\n\t\t\t// The contribution dispatcher + post-acquire hook + built-in\n\t\t\t// plugin-context extension are assembled in ONE place\n\t\t\t// (`orchestrators/boot.ts superviseStackWithProductionBoot`); this\n\t\t\t// one-shot verb supplies only the per-caller inputs (`extras`,\n\t\t\t// `lifetime: 'one-shot'`, and the capture `withinScope` hook).\n\t\t\t// Result semantics are unchanged: the hook still tees the capture\n\t\t\t// `Exit` + metadata into the closure cells read below.\n\t\t\tyield* superviseStackWithProductionBoot(\n\t\t\t\t{ _tag: 'Stack', members: stack.members, options: stack.options },\n\t\t\t\tidentityValue,\n\t\t\t\tstate,\n\t\t\t\t{\n\t\t\t\t\textras: stack.options.extras,\n\t\t\t\t\tlifetime: 'one-shot',\n\t\t\t\t\t// Offline one-shot capture: the bounce gather → stop → commit →\n\t\t\t\t\t// retag → hard-rm runs here; NO `resume` is injected because\n\t\t\t\t\t// this supervise scope is `one-shot` (it closes right after) —\n\t\t\t\t\t// the NEXT boot is the resume. Direct call to the orchestrator\n\t\t\t\t\t// (the former `captureSnapshot` command-primitive is gone).\n\t\t\t\t\twithinScope: () =>\n\t\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\t\tconst snapshot = yield* SnapshotOrchestratorService;\n\t\t\t\t\t\t\tconst fs = yield* FileSystem.FileSystem;\n\t\t\t\t\t\t\tconst graphInput = yield* computeSnapshotGraphInputFromStack({\n\t\t\t\t\t\t\t\tstack,\n\t\t\t\t\t\t\t\tdevstackVersion: readDevstackVersion({ fallback: '0.0.0' }),\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\treturn yield* snapshot\n\t\t\t\t\t\t\t\t.capture({\n\t\t\t\t\t\t\t\t\t...(args.snapshotId === undefined ? {} : { id: args.snapshotId }),\n\t\t\t\t\t\t\t\t\t...(args.name === undefined ? {} : { label: args.name }),\n\t\t\t\t\t\t\t\t\tgraphInput,\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t.pipe(Effect.provideService(FileSystem.FileSystem, fs));\n\t\t\t\t\t\t}).pipe(\n\t\t\t\t\t\t\tEffect.tap((meta) =>\n\t\t\t\t\t\t\t\tEffect.sync(() => {\n\t\t\t\t\t\t\t\t\tcapturedMeta.current = meta;\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\tEffect.asVoid,\n\t\t\t\t\t\t\tEffect.exit,\n\t\t\t\t\t\t\tEffect.tap((exit) =>\n\t\t\t\t\t\t\t\tEffect.sync(() => {\n\t\t\t\t\t\t\t\t\tcaptureExit = exit;\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\tEffect.asVoid,\n\t\t\t\t\t\t),\n\t\t\t\t},\n\t\t\t);\n\t\t\tif (Exit.isFailure(captureExit)) {\n\t\t\t\tyield* Effect.failCause(captureExit.cause);\n\t\t\t}\n\t\t\tif (capturedMeta.current === null) {\n\t\t\t\treturn yield* Effect.die('snapshot capture completed without metadata');\n\t\t\t}\n\t\t\tconst meta = capturedMeta.current;\n\t\t\treturn { snapshotId: meta.id, name: meta.label ?? meta.id };\n\t\t});\n\n\t\treturn yield* ensureNoLiveSupervisor(\n\t\t\teffectiveIdentity,\n\t\t\t'shut down the attached `devstack up` session before saving a snapshot',\n\t\t).pipe(\n\t\t\tEffect.andThen(\n\t\t\t\tprogram.pipe(\n\t\t\t\t\tEffect.provide(substrateLayers),\n\t\t\t\t\tEffect.provide(Logger.layer([Logger.consolePretty()])),\n\t\t\t\t),\n\t\t\t),\n\t\t);\n\t});\n\nexport const runSnapshotCaptureLiveAware = (\n\tidentity: ResolvedIdentity,\n\targs: { readonly snapshotId?: string; readonly name?: string; readonly configPath?: string },\n): Effect.Effect<{ readonly snapshotId: string; readonly name: string }, unknown> =>\n\tEffect.gen(function* () {\n\t\tif (args.configPath === undefined && resolveConfigPath(undefined) === null) {\n\t\t\tconst live = yield* runSnapshotCaptureAgainstLiveSupervisor(identity, args);\n\t\t\tif (live !== null) return live;\n\t\t\treturn yield* runSnapshotCaptureDirect(identity, args);\n\t\t}\n\t\tconst loaded = yield* makeConfigLoader().load(args.configPath);\n\t\tconst stack = (loaded as LoadedConfig & { readonly engine: SupervisedStack }).engine;\n\t\tconst effectiveIdentity = resolvedIdentityForStack(identity, stack);\n\t\tconst live = yield* runSnapshotCaptureAgainstLiveSupervisor(effectiveIdentity, args);\n\t\tif (live !== null) return live;\n\t\treturn yield* runSnapshotCaptureDirectLoaded(effectiveIdentity, loaded, args);\n\t});\n"],"mappings":"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"}
|
|
@@ -54,23 +54,15 @@ interface AggregateContribution {
|
|
|
54
54
|
* orchestrator MUST NOT branch on this value — it is annotation-
|
|
55
55
|
* only. */
|
|
56
56
|
readonly kind?: string;
|
|
57
|
-
/** Where the synthesized aggregate file lands. `'generated'` is
|
|
58
|
-
* the canonical runtime tree (`src/generated/`); `'generated-extras'`
|
|
59
|
-
* routes the aggregate to the gitignored
|
|
60
|
-
* `.devstack/stacks/<stack>/generated-extras/` tree (dev-only +
|
|
61
|
-
* secret artifacts). All decls contributing to one bucket MUST
|
|
62
|
-
* agree on this; the orchestrator reads it from the first
|
|
63
|
-
* contributor it sees for the bucket. Defaults to `'generated'`. */
|
|
64
|
-
readonly outputLocation?: OutputLocation;
|
|
65
57
|
/** When `true`, the synthesized aggregate file is written with
|
|
66
58
|
* tightened `0o600` perms and injected into `.gitignore`. Mirrors
|
|
67
59
|
* `CodegenableDecl.sensitive` for aggregate files. Defaults to
|
|
68
60
|
* `false`. */
|
|
69
61
|
readonly sensitive?: boolean;
|
|
70
|
-
/** Generic-channel contributions for the loadable
|
|
62
|
+
/** Generic-channel contributions for the loadable deployment. Only the
|
|
71
63
|
* LIVE (boot) aggregate of a unified config-binding set sets this;
|
|
72
|
-
* boot's `
|
|
73
|
-
* Carries the live plugin JSON the typed
|
|
64
|
+
* boot's `assembleDeployment` folds it into `deployment.values[ns][key]`.
|
|
65
|
+
* Carries the live plugin JSON the typed deployment fields can't (pool
|
|
74
66
|
* ids, coin types, walrus/seal endpoints). The committed-tree (static)
|
|
75
67
|
* aggregate omits it — those values resolve at app build time. */
|
|
76
68
|
readonly idConfigValues?: {
|
|
@@ -78,13 +70,22 @@ interface AggregateContribution {
|
|
|
78
70
|
readonly [key: string]: unknown;
|
|
79
71
|
};
|
|
80
72
|
};
|
|
73
|
+
/** The STATIC TS type of each generic-channel (`values[ns][key]`)
|
|
74
|
+
* contribution, as a source-string map `valueTypes[namespace][key] =
|
|
75
|
+
* '<tsType>'`. Derived from the SAME config-binding set as `idConfigValues`
|
|
76
|
+
* (every `resolved` binding WITHOUT a `sugar` resolver), but carries the
|
|
77
|
+
* field's declared TS type rather than its live value — so it is present on
|
|
78
|
+
* BOTH the static AND live aggregates (the type is config-known, not
|
|
79
|
+
* resolution-dependent). The codegen orchestrator folds it into the strict
|
|
80
|
+
* `deployment.ts`'s required `values` shape, so a hand-written
|
|
81
|
+
* `deployments/<net>.ts` is compile-checked for every service value. A
|
|
82
|
+
* binding with no declared tsType contributes `'unknown'`. */
|
|
83
|
+
readonly valueTypes?: {
|
|
84
|
+
readonly [namespace: string]: {
|
|
85
|
+
readonly [key: string]: string;
|
|
86
|
+
};
|
|
87
|
+
};
|
|
81
88
|
}
|
|
82
|
-
/** Which codegen output tree a decl (or aggregate) emits into.
|
|
83
|
-
* `'generated'` is the runtime-imported `src/generated/` tree;
|
|
84
|
-
* `'generated-extras'` is the gitignored
|
|
85
|
-
* `.devstack/stacks/<stack>/generated-extras/` dev-only tree reached
|
|
86
|
-
* via the `@devstack-dev` alias. */
|
|
87
|
-
type OutputLocation = 'generated' | 'generated-extras';
|
|
88
89
|
/**
|
|
89
90
|
* Static, stack-free codegen-decl source. A plugin that can describe its
|
|
90
91
|
* codegen contributions from CONFIG ALONE (no live acquire) exposes this
|
|
@@ -93,9 +94,10 @@ type OutputLocation = 'generated' | 'generated-extras';
|
|
|
93
94
|
* the live `acquire` would emit, but stack-free — KNOWN package ids come
|
|
94
95
|
* from the declared `networks` literals, LOCAL ids stay the all-zero
|
|
95
96
|
* sentinel (`UNRESOLVED_ID`) resolved at app build/dev time through the
|
|
96
|
-
* injected `
|
|
97
|
-
* require live resolution (
|
|
98
|
-
*
|
|
97
|
+
* injected `__DEVSTACK_DEPLOYMENT__` global. Plugins whose contributions genuinely
|
|
98
|
+
* require live resolution (e.g. the values-only account / dev-wallet decls,
|
|
99
|
+
* which feed the deployment envelope rather than a committed file) omit this
|
|
100
|
+
* — the verb simply skips them.
|
|
99
101
|
*/
|
|
100
102
|
type StaticCodegenSource = () => ReadonlyArray<CodegenableDecl>;
|
|
101
103
|
/**
|
|
@@ -107,11 +109,6 @@ interface CodegenableDecl<Emitter extends string = string> {
|
|
|
107
109
|
readonly emitterName: Emitter;
|
|
108
110
|
/** Relative path under the codegen staging dir. */
|
|
109
111
|
readonly outputPath: string;
|
|
110
|
-
/** Which codegen tree this decl's standalone file emits into.
|
|
111
|
-
* `'generated'` (default) → `src/generated/`; `'generated-extras'`
|
|
112
|
-
* → the gitignored `.devstack/stacks/<stack>/generated-extras/`
|
|
113
|
-
* dev-only tree (reached via the `@devstack-dev` alias). */
|
|
114
|
-
readonly outputLocation?: OutputLocation;
|
|
115
112
|
/** When `true`, this decl contributes ONLY to its `aggregate`
|
|
116
113
|
* bucket — the orchestrator skips emitting the standalone
|
|
117
114
|
* per-decl file. Use when the per-decl singleton has no consumer
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* A value the codegen renderer emits VERBATIM (not as a quoted literal).
|
|
4
4
|
*
|
|
5
5
|
* The one authorized use is id resolution in the emitted `config.ts`:
|
|
6
|
-
* `
|
|
7
|
-
* `RawExpr('
|
|
6
|
+
* `mvrOverrides` / `packageId` entries hold a
|
|
7
|
+
* `RawExpr('requireId(dep, "@local/foo")')` so the COMMITTED config carries NO
|
|
8
8
|
* on-chain id. The id resolves at app build/dev time through the injected
|
|
9
|
-
* `
|
|
9
|
+
* `__DEVSTACK_DEPLOYMENT__` global (see the emitted `config-runtime.ts`), which
|
|
10
10
|
* throws loudly when an id is unresolved. Lives at the L0 contract layer
|
|
11
11
|
* so both the package plugin (producer) and the renderer (consumer) can
|
|
12
12
|
* reach it without a layering cycle.
|
|
@@ -22,7 +22,38 @@ var RawExpr = class {
|
|
|
22
22
|
const rawExpr = (expr) => new RawExpr(expr);
|
|
23
23
|
/** Type guard for `RawExpr` — the renderer's verbatim-emit branch. */
|
|
24
24
|
const isRawExpr = (v) => typeof v === "object" && v !== null && v.__rawExpr === true;
|
|
25
|
+
/**
|
|
26
|
+
* A PER-NETWORK service bucket value. The renderer wraps `inner` in a
|
|
27
|
+
* `{ forNetwork(network) { const dep = loadDeployment().forNetwork(network);
|
|
28
|
+
* return <inner> as const; } }` accessor so a service bucket (coins / deepbook
|
|
29
|
+
* / seal / walrus) resolves its ids for whichever network the app's dapp-kit
|
|
30
|
+
* has selected — a runtime `switchNetwork` flips service ids in lockstep with
|
|
31
|
+
* rpc / packages, instead of baking the default network at module load.
|
|
32
|
+
*
|
|
33
|
+
* `inner` is the SAME object the bucket projected before (literals + the
|
|
34
|
+
* resolved `requireValue(dep, …)` raw expressions); the only change is WHERE
|
|
35
|
+
* `dep` comes from — a per-call `loadDeployment().forNetwork(network)` local
|
|
36
|
+
* inside the accessor, not the module-level default-network `dep`. The
|
|
37
|
+
* orchestrator therefore suppresses the module-level `dep` preamble for these
|
|
38
|
+
* buckets (the accessor declares its own) while still importing
|
|
39
|
+
* `loadDeployment` / `requireValue`.
|
|
40
|
+
*
|
|
41
|
+
* `needsDep` records whether `inner` actually references `dep` (a pure-literal
|
|
42
|
+
* bucket — e.g. a known/live seal or a builtin-only coin — does not, so the
|
|
43
|
+
* accessor omits the `const dep = …` line and ignores its `network` param).
|
|
44
|
+
*/
|
|
45
|
+
var ForNetworkBucket = class {
|
|
46
|
+
inner;
|
|
47
|
+
needsDep;
|
|
48
|
+
__forNetworkBucket = true;
|
|
49
|
+
constructor(inner, needsDep) {
|
|
50
|
+
this.inner = inner;
|
|
51
|
+
this.needsDep = needsDep;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
/** Type guard for `ForNetworkBucket` — the renderer's per-network-accessor branch. */
|
|
55
|
+
const isForNetworkBucket = (v) => typeof v === "object" && v !== null && v.__forNetworkBucket === true;
|
|
25
56
|
//#endregion
|
|
26
|
-
export { isRawExpr, rawExpr };
|
|
57
|
+
export { ForNetworkBucket, isForNetworkBucket, isRawExpr, rawExpr };
|
|
27
58
|
|
|
28
59
|
//# sourceMappingURL=codegenable.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codegenable.mjs","names":[],"sources":["../../src/contracts/codegenable.ts"],"sourcesContent":["// Codegenable capability contract (architecture §6).\n//\n// Lets a plugin contribute files to the user's source tree without\n// the codegen surface (L4) knowing the plugin exists. The runtime\n// contract only needs the literal emitter name, output path,\n// sensitivity, and an emit operation that writes through an opaque\n// context. Generated files own their app-facing export types directly.\n\nimport type { Effect } from 'effect';\n\n// -----------------------------------------------------------------------------\n// Raw-expression escape hatch\n// -----------------------------------------------------------------------------\n\n/**\n * A value the codegen renderer emits VERBATIM (not as a quoted literal).\n *\n * The one authorized use is id resolution in the emitted `config.ts`:\n * `byNetwork` / `mvrOverrides` / `packageId` entries hold a\n * `RawExpr('resolveId(\"@local/foo\")')` so the COMMITTED config carries NO\n * on-chain id. The id resolves at app build/dev time through the injected\n * `__DEVSTACK_IDS__` global (see the emitted `config-runtime.ts`), which\n * throws loudly when an id is unresolved. Lives at the L0 contract layer\n * so both the package plugin (producer) and the renderer (consumer) can\n * reach it without a layering cycle.\n */\nexport class RawExpr {\n\treadonly __rawExpr = true as const;\n\tconstructor(readonly expr: string) {}\n}\n\n/** Build a `RawExpr`. */\nexport const rawExpr = (expr: string): RawExpr => new RawExpr(expr);\n\n/** Type guard for `RawExpr` — the renderer's verbatim-emit branch. */\nexport const isRawExpr = (v: unknown): v is RawExpr =>\n\ttypeof v === 'object' && v !== null && (v as { __rawExpr?: unknown }).__rawExpr === true;\n\n/**\n * Opaque per-file emission context.\n *\n * Plugin authors declare named generated exports by calling\n * `exportConst(...)`. The orchestrator owns how those declarations are\n * collected, rendered, grouped, permissioned, and written. Emitters do\n * not return a raw `{ [exportName]: value }` record, which keeps the\n * codegen contract focused on the public generated file shape instead\n * of an internal renderer payload.\n */\nexport interface CodegenEmitContext {\n\t/** Add one `export const <name> = <value> as const;` to the file. */\n\treadonly exportConst: (name: string, value: unknown) => void;\n\t/** Add a raw import statement before the generated exports. */\n\treadonly importStatement: (statement: string) => void;\n\t/** Finish emission after all exports/imports have been written. */\n\treadonly done: () => CodegenEmitDone;\n}\n\nexport interface CodegenEmitDone {\n\treadonly _tag: 'CodegenEmitDone';\n}\n\n/**\n * Optional cross-decl aggregation. When present, the codegen\n * orchestrator additionally folds this decl's exported map into a\n * shared aggregate file alongside its own per-decl output.\n *\n * Architectural note: the orchestrator treats `bucket` as opaque and\n * delegates the projection to the plugin via `project`. Aggregate\n * special-casing (e.g. \"the Sui `services.ts` row holds `{rpc,\n * faucet, graphql}`\") belongs in the plugin's contributor, NOT in\n * the codegen orchestrator (architecture: \"Orchestrator boundaries\n * — never names a service\").\n */\nexport interface AggregateContribution {\n\t/** Which aggregate file this decl contributes to (e.g.\n\t * `'accounts.ts'`, `'coins.ts'`, `'config.ts'`). The plugin\n\t * chooses; the orchestrator treats it as opaque. Distinct decls\n\t * that target the same `bucket` deep-merge into one aggregate\n\t * file (so e.g. sui's `networks.localnet` and every package's\n\t * `packages.<name>` coexist in one `config.ts`). */\n\treadonly bucket: string;\n\t/** Project this decl's `exported` map into the value to merge\n\t * into the aggregate bucket. Returning `null` opts out of\n\t * contributing for this cycle (e.g. when the emitter produced\n\t * no usable shape). The returned record is deep-merged onto\n\t * the bucket; for typed shapes the plugin owns the merge\n\t * semantics via the returned object's key set. */\n\treadonly project: (\n\t\texported: Readonly<Record<string, unknown>>,\n\t) => Readonly<Record<string, unknown>> | null;\n\t/** Optional plugin-supplied kind tag for diagnostics / span\n\t * attributes (e.g. `'sui-network'`, `'account'`). The\n\t * orchestrator MUST NOT branch on this value — it is annotation-\n\t * only. */\n\treadonly kind?: string;\n\t/** Where the synthesized aggregate file lands. `'generated'` is\n\t * the canonical runtime tree (`src/generated/`); `'generated-extras'`\n\t * routes the aggregate to the gitignored\n\t * `.devstack/stacks/<stack>/generated-extras/` tree (dev-only +\n\t * secret artifacts). All decls contributing to one bucket MUST\n\t * agree on this; the orchestrator reads it from the first\n\t * contributor it sees for the bucket. Defaults to `'generated'`. */\n\treadonly outputLocation?: OutputLocation;\n\t/** When `true`, the synthesized aggregate file is written with\n\t * tightened `0o600` perms and injected into `.gitignore`. Mirrors\n\t * `CodegenableDecl.sensitive` for aggregate files. Defaults to\n\t * `false`. */\n\treadonly sensitive?: boolean;\n\t/** Generic-channel contributions for the loadable id-config. Only the\n\t * LIVE (boot) aggregate of a unified config-binding set sets this;\n\t * boot's `assembleIdConfig` folds it into `idConfig.values[ns][key]`.\n\t * Carries the live plugin JSON the typed id-config fields can't (pool\n\t * ids, coin types, walrus/seal endpoints). The committed-tree (static)\n\t * aggregate omits it — those values resolve at app build time. */\n\treadonly idConfigValues?: {\n\t\treadonly [namespace: string]: { readonly [key: string]: unknown };\n\t};\n}\n\n/** Which codegen output tree a decl (or aggregate) emits into.\n * `'generated'` is the runtime-imported `src/generated/` tree;\n * `'generated-extras'` is the gitignored\n * `.devstack/stacks/<stack>/generated-extras/` dev-only tree reached\n * via the `@devstack-dev` alias. */\nexport type OutputLocation = 'generated' | 'generated-extras';\n\n/**\n * Static, stack-free codegen-decl source. A plugin that can describe its\n * codegen contributions from CONFIG ALONE (no live acquire) exposes this\n * on its spec so the `codegen` verb can emit a committed projection without\n * booting a stack. The hook is pure: it returns the same `CodegenableDecl`s\n * the live `acquire` would emit, but stack-free — KNOWN package ids come\n * from the declared `networks` literals, LOCAL ids stay the all-zero\n * sentinel (`UNRESOLVED_ID`) resolved at app build/dev time through the\n * injected `__DEVSTACK_IDS__` global. Plugins whose contributions genuinely\n * require live resolution (or which only land in the gitignored\n * `generated-extras` dev tree) omit this — the verb simply skips them.\n */\nexport type StaticCodegenSource = () => ReadonlyArray<CodegenableDecl>;\n\n/**\n * Codegen contribution. `Emitter` is a literal emitter name used\n * by the codegen orchestrator for attribution and grouping.\n */\nexport interface CodegenableDecl<Emitter extends string = string> {\n\treadonly kind: 'codegenable';\n\treadonly emitterName: Emitter;\n\t/** Relative path under the codegen staging dir. */\n\treadonly outputPath: string;\n\t/** Which codegen tree this decl's standalone file emits into.\n\t * `'generated'` (default) → `src/generated/`; `'generated-extras'`\n\t * → the gitignored `.devstack/stacks/<stack>/generated-extras/`\n\t * dev-only tree (reached via the `@devstack-dev` alias). */\n\treadonly outputLocation?: OutputLocation;\n\t/** When `true`, this decl contributes ONLY to its `aggregate`\n\t * bucket — the orchestrator skips emitting the standalone\n\t * per-decl file. Use when the per-decl singleton has no consumer\n\t * (the combined aggregate is the only app-facing surface). */\n\treadonly aggregateOnly?: boolean;\n\t/** Optional sensitivity flag — drives file permissions and\n\t * `.gitignore` inclusion. */\n\treadonly sensitive?: boolean;\n\t/** When `true`, multiple decls may share this `emitterName`\n\t * (the orchestrator skips the emitter-name uniqueness check for\n\t * this decl). Use sparingly — only when the plugin legitimately\n\t * emits one decl per item (e.g. one `Package` per published\n\t * package). Output-path uniqueness is still enforced. */\n\treadonly allowEmitterNameRepetition?: boolean;\n\t/** Optional aggregation seam — when present, the orchestrator\n\t * folds the per-decl exported map into a shared aggregate file\n\t * via `aggregate.project(exported)`. Plugins own the projection\n\t * shape; the orchestrator is name-blind. */\n\treadonly aggregate?: AggregateContribution;\n\t/** Emit operation; writes generated file declarations through the\n\t * supplied context. */\n\treadonly emit: (ctx: CodegenEmitContext) => Effect.Effect<CodegenEmitDone>;\n}\n"],"mappings":";;;;;;;;;;;;;AA0BA,IAAa,UAAb,MAAqB;CAEC;CADrB,YAAqB;CACrB,YAAY,MAAuB;EAAd,KAAA,OAAA;CAAe;AACrC;;AAGA,MAAa,WAAW,SAA0B,IAAI,QAAQ,IAAI;;AAGlE,MAAa,aAAa,MACzB,OAAO,MAAM,YAAY,MAAM,QAAS,EAA8B,cAAc"}
|
|
1
|
+
{"version":3,"file":"codegenable.mjs","names":[],"sources":["../../src/contracts/codegenable.ts"],"sourcesContent":["// Codegenable capability contract (architecture §6).\n//\n// Lets a plugin contribute files to the user's source tree without\n// the codegen surface (L4) knowing the plugin exists. The runtime\n// contract only needs the literal emitter name, output path,\n// sensitivity, and an emit operation that writes through an opaque\n// context. Generated files own their app-facing export types directly.\n\nimport type { Effect } from 'effect';\n\n// -----------------------------------------------------------------------------\n// Raw-expression escape hatch\n// -----------------------------------------------------------------------------\n\n/**\n * A value the codegen renderer emits VERBATIM (not as a quoted literal).\n *\n * The one authorized use is id resolution in the emitted `config.ts`:\n * `mvrOverrides` / `packageId` entries hold a\n * `RawExpr('requireId(dep, \"@local/foo\")')` so the COMMITTED config carries NO\n * on-chain id. The id resolves at app build/dev time through the injected\n * `__DEVSTACK_DEPLOYMENT__` global (see the emitted `config-runtime.ts`), which\n * throws loudly when an id is unresolved. Lives at the L0 contract layer\n * so both the package plugin (producer) and the renderer (consumer) can\n * reach it without a layering cycle.\n */\nexport class RawExpr {\n\treadonly __rawExpr = true as const;\n\tconstructor(readonly expr: string) {}\n}\n\n/** Build a `RawExpr`. */\nexport const rawExpr = (expr: string): RawExpr => new RawExpr(expr);\n\n/** Type guard for `RawExpr` — the renderer's verbatim-emit branch. */\nexport const isRawExpr = (v: unknown): v is RawExpr =>\n\ttypeof v === 'object' && v !== null && (v as { __rawExpr?: unknown }).__rawExpr === true;\n\n/**\n * A PER-NETWORK service bucket value. The renderer wraps `inner` in a\n * `{ forNetwork(network) { const dep = loadDeployment().forNetwork(network);\n * return <inner> as const; } }` accessor so a service bucket (coins / deepbook\n * / seal / walrus) resolves its ids for whichever network the app's dapp-kit\n * has selected — a runtime `switchNetwork` flips service ids in lockstep with\n * rpc / packages, instead of baking the default network at module load.\n *\n * `inner` is the SAME object the bucket projected before (literals + the\n * resolved `requireValue(dep, …)` raw expressions); the only change is WHERE\n * `dep` comes from — a per-call `loadDeployment().forNetwork(network)` local\n * inside the accessor, not the module-level default-network `dep`. The\n * orchestrator therefore suppresses the module-level `dep` preamble for these\n * buckets (the accessor declares its own) while still importing\n * `loadDeployment` / `requireValue`.\n *\n * `needsDep` records whether `inner` actually references `dep` (a pure-literal\n * bucket — e.g. a known/live seal or a builtin-only coin — does not, so the\n * accessor omits the `const dep = …` line and ignores its `network` param).\n */\nexport class ForNetworkBucket {\n\treadonly __forNetworkBucket = true as const;\n\tconstructor(\n\t\treadonly inner: Record<string, unknown>,\n\t\treadonly needsDep: boolean,\n\t) {}\n}\n\n/** Type guard for `ForNetworkBucket` — the renderer's per-network-accessor branch. */\nexport const isForNetworkBucket = (v: unknown): v is ForNetworkBucket =>\n\ttypeof v === 'object' &&\n\tv !== null &&\n\t(v as { __forNetworkBucket?: unknown }).__forNetworkBucket === true;\n\n/**\n * Opaque per-file emission context.\n *\n * Plugin authors declare named generated exports by calling\n * `exportConst(...)`. The orchestrator owns how those declarations are\n * collected, rendered, grouped, permissioned, and written. Emitters do\n * not return a raw `{ [exportName]: value }` record, which keeps the\n * codegen contract focused on the public generated file shape instead\n * of an internal renderer payload.\n */\nexport interface CodegenEmitContext {\n\t/** Add one `export const <name> = <value> as const;` to the file. */\n\treadonly exportConst: (name: string, value: unknown) => void;\n\t/** Add a raw import statement before the generated exports. */\n\treadonly importStatement: (statement: string) => void;\n\t/** Finish emission after all exports/imports have been written. */\n\treadonly done: () => CodegenEmitDone;\n}\n\nexport interface CodegenEmitDone {\n\treadonly _tag: 'CodegenEmitDone';\n}\n\n/**\n * Optional cross-decl aggregation. When present, the codegen\n * orchestrator additionally folds this decl's exported map into a\n * shared aggregate file alongside its own per-decl output.\n *\n * Architectural note: the orchestrator treats `bucket` as opaque and\n * delegates the projection to the plugin via `project`. Aggregate\n * special-casing (e.g. \"the Sui `services.ts` row holds `{rpc,\n * faucet, graphql}`\") belongs in the plugin's contributor, NOT in\n * the codegen orchestrator (architecture: \"Orchestrator boundaries\n * — never names a service\").\n */\nexport interface AggregateContribution {\n\t/** Which aggregate file this decl contributes to (e.g.\n\t * `'accounts.ts'`, `'coins.ts'`, `'config.ts'`). The plugin\n\t * chooses; the orchestrator treats it as opaque. Distinct decls\n\t * that target the same `bucket` deep-merge into one aggregate\n\t * file (so e.g. sui's `networks.localnet` and every package's\n\t * `packages.<name>` coexist in one `config.ts`). */\n\treadonly bucket: string;\n\t/** Project this decl's `exported` map into the value to merge\n\t * into the aggregate bucket. Returning `null` opts out of\n\t * contributing for this cycle (e.g. when the emitter produced\n\t * no usable shape). The returned record is deep-merged onto\n\t * the bucket; for typed shapes the plugin owns the merge\n\t * semantics via the returned object's key set. */\n\treadonly project: (\n\t\texported: Readonly<Record<string, unknown>>,\n\t) => Readonly<Record<string, unknown>> | null;\n\t/** Optional plugin-supplied kind tag for diagnostics / span\n\t * attributes (e.g. `'sui-network'`, `'account'`). The\n\t * orchestrator MUST NOT branch on this value — it is annotation-\n\t * only. */\n\treadonly kind?: string;\n\t/** When `true`, the synthesized aggregate file is written with\n\t * tightened `0o600` perms and injected into `.gitignore`. Mirrors\n\t * `CodegenableDecl.sensitive` for aggregate files. Defaults to\n\t * `false`. */\n\treadonly sensitive?: boolean;\n\t/** Generic-channel contributions for the loadable deployment. Only the\n\t * LIVE (boot) aggregate of a unified config-binding set sets this;\n\t * boot's `assembleDeployment` folds it into `deployment.values[ns][key]`.\n\t * Carries the live plugin JSON the typed deployment fields can't (pool\n\t * ids, coin types, walrus/seal endpoints). The committed-tree (static)\n\t * aggregate omits it — those values resolve at app build time. */\n\treadonly idConfigValues?: {\n\t\treadonly [namespace: string]: { readonly [key: string]: unknown };\n\t};\n\t/** The STATIC TS type of each generic-channel (`values[ns][key]`)\n\t * contribution, as a source-string map `valueTypes[namespace][key] =\n\t * '<tsType>'`. Derived from the SAME config-binding set as `idConfigValues`\n\t * (every `resolved` binding WITHOUT a `sugar` resolver), but carries the\n\t * field's declared TS type rather than its live value — so it is present on\n\t * BOTH the static AND live aggregates (the type is config-known, not\n\t * resolution-dependent). The codegen orchestrator folds it into the strict\n\t * `deployment.ts`'s required `values` shape, so a hand-written\n\t * `deployments/<net>.ts` is compile-checked for every service value. A\n\t * binding with no declared tsType contributes `'unknown'`. */\n\treadonly valueTypes?: {\n\t\treadonly [namespace: string]: { readonly [key: string]: string };\n\t};\n}\n\n/**\n * Static, stack-free codegen-decl source. A plugin that can describe its\n * codegen contributions from CONFIG ALONE (no live acquire) exposes this\n * on its spec so the `codegen` verb can emit a committed projection without\n * booting a stack. The hook is pure: it returns the same `CodegenableDecl`s\n * the live `acquire` would emit, but stack-free — KNOWN package ids come\n * from the declared `networks` literals, LOCAL ids stay the all-zero\n * sentinel (`UNRESOLVED_ID`) resolved at app build/dev time through the\n * injected `__DEVSTACK_DEPLOYMENT__` global. Plugins whose contributions genuinely\n * require live resolution (e.g. the values-only account / dev-wallet decls,\n * which feed the deployment envelope rather than a committed file) omit this\n * — the verb simply skips them.\n */\nexport type StaticCodegenSource = () => ReadonlyArray<CodegenableDecl>;\n\n/**\n * Codegen contribution. `Emitter` is a literal emitter name used\n * by the codegen orchestrator for attribution and grouping.\n */\nexport interface CodegenableDecl<Emitter extends string = string> {\n\treadonly kind: 'codegenable';\n\treadonly emitterName: Emitter;\n\t/** Relative path under the codegen staging dir. */\n\treadonly outputPath: string;\n\t/** When `true`, this decl contributes ONLY to its `aggregate`\n\t * bucket — the orchestrator skips emitting the standalone\n\t * per-decl file. Use when the per-decl singleton has no consumer\n\t * (the combined aggregate is the only app-facing surface). */\n\treadonly aggregateOnly?: boolean;\n\t/** Optional sensitivity flag — drives file permissions and\n\t * `.gitignore` inclusion. */\n\treadonly sensitive?: boolean;\n\t/** When `true`, multiple decls may share this `emitterName`\n\t * (the orchestrator skips the emitter-name uniqueness check for\n\t * this decl). Use sparingly — only when the plugin legitimately\n\t * emits one decl per item (e.g. one `Package` per published\n\t * package). Output-path uniqueness is still enforced. */\n\treadonly allowEmitterNameRepetition?: boolean;\n\t/** Optional aggregation seam — when present, the orchestrator\n\t * folds the per-decl exported map into a shared aggregate file\n\t * via `aggregate.project(exported)`. Plugins own the projection\n\t * shape; the orchestrator is name-blind. */\n\treadonly aggregate?: AggregateContribution;\n\t/** Emit operation; writes generated file declarations through the\n\t * supplied context. */\n\treadonly emit: (ctx: CodegenEmitContext) => Effect.Effect<CodegenEmitDone>;\n}\n"],"mappings":";;;;;;;;;;;;;AA0BA,IAAa,UAAb,MAAqB;CAEC;CADrB,YAAqB;CACrB,YAAY,MAAuB;EAAd,KAAA,OAAA;CAAe;AACrC;;AAGA,MAAa,WAAW,SAA0B,IAAI,QAAQ,IAAI;;AAGlE,MAAa,aAAa,MACzB,OAAO,MAAM,YAAY,MAAM,QAAS,EAA8B,cAAc;;;;;;;;;;;;;;;;;;;;;AAsBrF,IAAa,mBAAb,MAA8B;CAGnB;CACA;CAHV,qBAA8B;CAC9B,YACC,OACA,UACC;EAFQ,KAAA,QAAA;EACA,KAAA,WAAA;CACP;AACJ;;AAGA,MAAa,sBAAsB,MAClC,OAAO,MAAM,YACb,MAAM,QACL,EAAuC,uBAAuB"}
|