@mysten-incubation/devstack 0.7.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build-integrations/vite/index.mjs +1 -0
- package/dist/build-integrations/vite/index.mjs.map +1 -1
- package/dist/cli/wirings/apply.mjs +24 -6
- package/dist/cli/wirings/apply.mjs.map +1 -1
- package/dist/cli/wirings/dump-deployment.mjs +2 -1
- package/dist/cli/wirings/dump-deployment.mjs.map +1 -1
- package/dist/cli/wirings/identity.mjs +2 -1
- package/dist/cli/wirings/identity.mjs.map +1 -1
- package/dist/cli/wirings/snapshot.mjs +2 -1
- package/dist/cli/wirings/snapshot.mjs.map +1 -1
- package/dist/cli/wirings/up.mjs +25 -29
- package/dist/cli/wirings/up.mjs.map +1 -1
- package/dist/plugins/package/codegen.mjs +4 -4
- package/dist/plugins/package/codegen.mjs.map +1 -1
- package/dist/plugins/package/dep-resolution.mjs +19 -9
- package/dist/plugins/package/dep-resolution.mjs.map +1 -1
- package/dist/plugins/package/index.d.mts +3 -3
- package/dist/plugins/package/index.mjs +9 -5
- package/dist/plugins/package/index.mjs.map +1 -1
- package/dist/plugins/package/mode-known.mjs +2 -2
- package/dist/plugins/package/mode-known.mjs.map +1 -1
- package/dist/plugins/package/mode-local.mjs +4 -4
- package/dist/plugins/package/mode-local.mjs.map +1 -1
- package/dist/plugins/sui/chain-build-container.mjs.map +1 -1
- package/dist/plugins/sui/index.d.mts +10 -10
- package/dist/plugins/sui/index.mjs.map +1 -1
- package/dist/plugins/sui/move/index.mjs +10 -6
- package/dist/plugins/sui/move/index.mjs.map +1 -1
- package/dist/plugins/wallet/codegen.d.mts +14 -10
- package/dist/plugins/wallet/codegen.mjs.map +1 -1
- package/dist/plugins/wallet/origin-policy.mjs +43 -3
- package/dist/plugins/wallet/origin-policy.mjs.map +1 -1
- package/dist/plugins/wallet/service.mjs +9 -7
- package/dist/plugins/wallet/service.mjs.map +1 -1
- package/dist/substrate/cross-process.mjs +2 -1
- package/dist/substrate/cross-process.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/roster.mjs +5 -2
- package/dist/substrate/runtime/cross-process/roster.mjs.map +1 -1
- package/dist/surfaces/cli/commands/supervisor-presence.mjs +4 -2
- package/dist/surfaces/cli/commands/supervisor-presence.mjs.map +1 -1
- package/dist/surfaces/cli/errors.mjs +12 -2
- package/dist/surfaces/cli/errors.mjs.map +1 -1
- package/dist/surfaces/cli/index.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -20,7 +20,7 @@ import { Effect, Scope } from "effect";
|
|
|
20
20
|
declare const sui: <const O extends SuiOptions = {
|
|
21
21
|
mode: "local";
|
|
22
22
|
}>(opts?: O) => Plugin<"sui", {
|
|
23
|
-
mode: "local" | "
|
|
23
|
+
mode: "local" | "local-rpc" | "live" | "fork";
|
|
24
24
|
fundingFaucetStrategy: FaucetStrategy | null;
|
|
25
25
|
sdk: SuiSdkShim;
|
|
26
26
|
rpcUrl: string;
|
|
@@ -51,7 +51,7 @@ declare const sui: <const O extends SuiOptions = {
|
|
|
51
51
|
declare const suiFor: ModeNamespace<{
|
|
52
52
|
local: {
|
|
53
53
|
local: <const O extends Omit<SuiLocalOptions, "mode">>(opts?: O) => Plugin<"sui", {
|
|
54
|
-
mode: "local" | "
|
|
54
|
+
mode: "local" | "local-rpc" | "live" | "fork";
|
|
55
55
|
fundingFaucetStrategy: FaucetStrategy | null;
|
|
56
56
|
sdk: SuiSdkShim;
|
|
57
57
|
rpcUrl: string;
|
|
@@ -70,7 +70,7 @@ declare const suiFor: ModeNamespace<{
|
|
|
70
70
|
buildImage: ImageRef | null;
|
|
71
71
|
}, readonly []>;
|
|
72
72
|
localRpc: (opts: Omit<SuiLocalRpcOptions, "mode">) => Plugin<"sui", {
|
|
73
|
-
mode: "local" | "
|
|
73
|
+
mode: "local" | "local-rpc" | "live" | "fork";
|
|
74
74
|
fundingFaucetStrategy: FaucetStrategy | null;
|
|
75
75
|
sdk: SuiSdkShim;
|
|
76
76
|
rpcUrl: string;
|
|
@@ -91,7 +91,7 @@ declare const suiFor: ModeNamespace<{
|
|
|
91
91
|
};
|
|
92
92
|
live: {
|
|
93
93
|
testnet: (opts?: Omit<SuiLiveOptions, "mode" | "network">) => Plugin<"sui", {
|
|
94
|
-
mode: "local" | "
|
|
94
|
+
mode: "local" | "local-rpc" | "live" | "fork";
|
|
95
95
|
fundingFaucetStrategy: FaucetStrategy | null;
|
|
96
96
|
sdk: SuiSdkShim;
|
|
97
97
|
rpcUrl: string;
|
|
@@ -110,7 +110,7 @@ declare const suiFor: ModeNamespace<{
|
|
|
110
110
|
buildImage: ImageRef | null;
|
|
111
111
|
}, readonly []>;
|
|
112
112
|
mainnet: (opts?: Omit<SuiLiveOptions, "mode" | "network">) => Plugin<"sui", {
|
|
113
|
-
mode: "local" | "
|
|
113
|
+
mode: "local" | "local-rpc" | "live" | "fork";
|
|
114
114
|
fundingFaucetStrategy: FaucetStrategy | null;
|
|
115
115
|
sdk: SuiSdkShim;
|
|
116
116
|
rpcUrl: string;
|
|
@@ -129,7 +129,7 @@ declare const suiFor: ModeNamespace<{
|
|
|
129
129
|
buildImage: ImageRef | null;
|
|
130
130
|
}, readonly []>;
|
|
131
131
|
devnet: (opts?: Omit<SuiLiveOptions, "mode" | "network">) => Plugin<"sui", {
|
|
132
|
-
mode: "local" | "
|
|
132
|
+
mode: "local" | "local-rpc" | "live" | "fork";
|
|
133
133
|
fundingFaucetStrategy: FaucetStrategy | null;
|
|
134
134
|
sdk: SuiSdkShim;
|
|
135
135
|
rpcUrl: string;
|
|
@@ -148,7 +148,7 @@ declare const suiFor: ModeNamespace<{
|
|
|
148
148
|
buildImage: ImageRef | null;
|
|
149
149
|
}, readonly []>;
|
|
150
150
|
custom: (opts: Omit<SuiLiveOptions, "mode" | "network">) => Plugin<"sui", {
|
|
151
|
-
mode: "local" | "
|
|
151
|
+
mode: "local" | "local-rpc" | "live" | "fork";
|
|
152
152
|
fundingFaucetStrategy: FaucetStrategy | null;
|
|
153
153
|
sdk: SuiSdkShim;
|
|
154
154
|
rpcUrl: string;
|
|
@@ -169,7 +169,7 @@ declare const suiFor: ModeNamespace<{
|
|
|
169
169
|
};
|
|
170
170
|
fork: {
|
|
171
171
|
mainnet: (opts?: Omit<SuiForkOptions, "mode" | "upstream">) => Plugin<"sui", {
|
|
172
|
-
mode: "local" | "
|
|
172
|
+
mode: "local" | "local-rpc" | "live" | "fork";
|
|
173
173
|
fundingFaucetStrategy: FaucetStrategy | null;
|
|
174
174
|
sdk: SuiSdkShim;
|
|
175
175
|
rpcUrl: string;
|
|
@@ -188,7 +188,7 @@ declare const suiFor: ModeNamespace<{
|
|
|
188
188
|
buildImage: ImageRef | null;
|
|
189
189
|
}, readonly []>;
|
|
190
190
|
testnet: (opts?: Omit<SuiForkOptions, "mode" | "upstream">) => Plugin<"sui", {
|
|
191
|
-
mode: "local" | "
|
|
191
|
+
mode: "local" | "local-rpc" | "live" | "fork";
|
|
192
192
|
fundingFaucetStrategy: FaucetStrategy | null;
|
|
193
193
|
sdk: SuiSdkShim;
|
|
194
194
|
rpcUrl: string;
|
|
@@ -207,7 +207,7 @@ declare const suiFor: ModeNamespace<{
|
|
|
207
207
|
buildImage: ImageRef | null;
|
|
208
208
|
}, readonly []>;
|
|
209
209
|
devnet: (opts?: Omit<SuiForkOptions, "mode" | "upstream">) => Plugin<"sui", {
|
|
210
|
-
mode: "local" | "
|
|
210
|
+
mode: "local" | "local-rpc" | "live" | "fork";
|
|
211
211
|
fundingFaucetStrategy: FaucetStrategy | null;
|
|
212
212
|
sdk: SuiSdkShim;
|
|
213
213
|
rpcUrl: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../../src/plugins/sui/index.ts"],"sourcesContent":["// Sui plugin — barrel + factories.\n//\n// Architecture: Sui is the most-depended-on plugin in the stack.\n// Every consumer (Account/Coin/Wallet/Faucet/Package; Walrus/Seal/\n// Deepbook fork variants) reads its resolved `SuiClient` via the\n// `suiResource`. The factory at this file folds the four modes behind:\n//\n// - `sui(opts?)` — local shorthand. Defaults to an in-stack\n// local validator; pass a typed `opts`\n// record to select a different mode.\n// - `suiFor(network)` — mode-narrowed factory namespace (per\n// architecture Tension 11). Returns\n// `{ local: …, live: …, fork: … }`\n// narrowed to the network's mode.\n//\n// During `start`, the plugin emits (via the typed `ctx.*` verbs):\n//\n// 1. `ctx.provides` — `chain-probe:<chainId>` strategy contributor,\n// the schema-validated read surface (`makeSuiChainProbe`).\n// 2. `ctx.snapshotExtra` — mode-aware container + bind-mount capture.\n// 3. `ctx.codegen` — `sui-network` bindings (chain id, rpc, etc.).\n// 4. `ctx.provides` — faucet strategy contributor, local-coin\n// dispensing for the mode's chain id.\n//\n// `ctx.endpoint` contributions are MODE-DEPENDENT (local + fork yes;\n// local-rpc + live no — the caller fronts their own RPC). They land\n// in the per-mode builder under `mode/*.ts`; this barrel emits them\n// alongside the rest during `start`.\n\nimport { Effect, type Scope } from 'effect';\n\nimport { defineModeNamespace } from '../../api/mode-narrowed-factory.ts';\nimport { definePlugin, resource, staticInputIdentity } from '../../api/define-plugin.ts';\nimport {\n\tbootPostgresSidecar,\n\tcredentialedUrl,\n\twithDatabase,\n} from '../internal/postgres-sidecar/index.ts';\nimport type { ChainProbe } from '../../contracts/chain-probe.ts';\nimport type { StrategyContributorDecl } from '../../contracts/strategy-contributor.ts';\nimport { emitContributions, PluginContext } from '../../substrate/plugin-ctx.ts';\n\nimport { chainProbeCapabilityKey } from '../../contracts/chain-probe.ts';\nimport { ContainerRuntimeService } from '../../runtime/docker/service.ts';\nimport type {\n\tContainerRuntime,\n\tContainerRuntimeError,\n\tImageRef,\n} from '../../contracts/container-runtime.ts';\nimport type { Identity } from '../../substrate/identity.ts';\nimport { IdentityContext, StackPathsService } from '../../substrate/runtime/paths.ts';\nimport { sanitizeAlias } from '../../substrate/runtime/managed-container.ts';\nimport {\n\tLeaseBrokerService,\n\ttype LeaseBroker,\n} from '../../substrate/runtime/lease-broker/index.ts';\nimport { PortBrokerService } from '../../substrate/runtime/port-broker/index.ts';\nimport { makeCodegenable, makeStaticCodegen } from './codegen.ts';\nimport type { SuiProbeKey } from './chain-probe.ts';\nimport { makeSnapshotable } from './snapshot.ts';\nimport { bootSuiService } from './service.ts';\nimport { suiPluginError, type SuiPluginError } from './errors.ts';\nimport { makeSuiForkRoutables, makeSuiLocalRoutables } from './routable.ts';\nimport { faucetCapabilityKey, type FaucetStrategy } from '../faucet/index.ts';\nimport { suiLocalStrategy } from './local-faucet-strategy.ts';\nimport { suiForkFaucetStrategy } from './fork-faucet-strategy.ts';\nimport { selectSufficientForkCoin } from './fork-transaction.ts';\nimport { FORK_FAUCET_WHALE_MIN_COIN_MIST, resolveForkWhale } from './mode/fork.ts';\nimport { resolveImage, SUI_INDEXER_DB_ROLE, type LocalIndexer } from './mode/local.ts';\nimport type { SuiClient } from './mode/shared.ts';\nimport type {\n\tSuiForkOptions,\n\tSuiLiveOptions,\n\tSuiLocalRpcOptions,\n\tSuiLocalOptions,\n\tSuiOptions,\n} from './mode/spec.ts';\n\n// ---------------------------------------------------------------------------\n// Resource identity\n// ---------------------------------------------------------------------------\n\ntype SuiResolved = SuiClient & {\n\treadonly mode: SuiOptions['mode'];\n};\n\n/** Internal extension of `SuiResolved` carrying the pre-built funding\n * faucet strategy. `start` owns the `LeaseBrokerService` instance for\n * serialization; building the strategy at start-time (where the broker\n * is reachable) lets the capabilities factory consume a flat value\n * without threading the broker through a side channel. Local/live wrap\n * the HTTP faucet; fork impersonates a whale; `null` on networks with\n * no faucet (live-mainnet, external-rpc-without-faucet, faucet-disabled\n * fork). */\ntype SuiResolvedRuntime = SuiResolved & {\n\treadonly fundingFaucetStrategy: FaucetStrategy | null;\n};\n\n/** The Sui plugin's resource identity. The id is `'sui'` (singular). */\nexport const suiResource = resource<'sui', SuiResolved>('sui');\n\n// ---------------------------------------------------------------------------\n// Plugin construction (internal — used by sui() + suiFor())\n// ---------------------------------------------------------------------------\n\n/** Build the funds-ready faucet strategy for the resolved network, or\n * `null` when the network has none. Local/live wrap the HTTP faucet;\n * fork mode impersonates a whale (resolved + validated here). */\nconst resolveFundingFaucetStrategy = (\n\topts: SuiOptions,\n\tclient: SuiClient,\n\tbroker: LeaseBroker,\n): Effect.Effect<FaucetStrategy | null, SuiPluginError> => {\n\tif (opts.mode === 'fork') {\n\t\treturn resolveForkFaucetStrategy(opts, client, broker);\n\t}\n\tif (client.fundingFaucetUrl === null) {\n\t\treturn Effect.succeed(null);\n\t}\n\treturn Effect.succeed(\n\t\tsuiLocalStrategy({\n\t\t\tfaucetUrl: client.fundingFaucetUrl,\n\t\t\tserialization: {\n\t\t\t\tbroker,\n\t\t\t\tkey: `sui-faucet:${client.chainId}`,\n\t\t\t\towner: `sui-faucet:${client.chainId}`,\n\t\t\t},\n\t\t}),\n\t);\n};\n\n/** Fork-mode faucet: resolve the whale, validate it holds a large enough\n * SUI coin, then build the impersonation strategy. A whale the user set\n * explicitly that fails validation hard-fails the boot; a per-upstream\n * default whale only warns and disables the faucet so the fork still\n * comes up (account funding then surfaces the \"no faucet strategy\"\n * error only if something actually needs SUI). */\nconst resolveForkFaucetStrategy = (\n\topts: SuiForkOptions,\n\tclient: SuiClient,\n\tbroker: LeaseBroker,\n): Effect.Effect<FaucetStrategy | null, SuiPluginError> =>\n\tEffect.gen(function* () {\n\t\tconst resolved = resolveForkWhale(opts);\n\t\tconst fork = client.fork;\n\t\tif (resolved === null || fork === null) {\n\t\t\treturn null;\n\t\t}\n\t\tconst strategy = suiForkFaucetStrategy({\n\t\t\twhale: resolved.whale,\n\t\t\tfork,\n\t\t\tsdk: client.sdk,\n\t\t\tperRequestCapMist: resolved.perRequestCapMist,\n\t\t\tserialization: {\n\t\t\t\tbroker,\n\t\t\t\tkey: `sui-fork-faucet:${client.chainId}`,\n\t\t\t\towner: `sui-fork-faucet:${client.chainId}`,\n\t\t\t},\n\t\t});\n\t\treturn yield* selectSufficientForkCoin(\n\t\t\tclient.sdk.core,\n\t\t\tresolved.whale,\n\t\t\tFORK_FAUCET_WHALE_MIN_COIN_MIST,\n\t\t).pipe(\n\t\t\tEffect.as(strategy),\n\t\t\tEffect.catchTag('SuiPluginError', (cause) =>\n\t\t\t\tresolved.explicit\n\t\t\t\t\t? Effect.fail(cause)\n\t\t\t\t\t: Effect.logWarning(\n\t\t\t\t\t\t\t`sui fork mode: default faucet whale ${resolved.whale} is unusable ` +\n\t\t\t\t\t\t\t\t`(${cause.message}); disabling the fork faucet. Set faucet.whale to override.`,\n\t\t\t\t\t\t).pipe(Effect.as(null)),\n\t\t\t),\n\t\t);\n\t});\n\n/** Default name for sui's GraphQL-indexer database. */\nconst DEFAULT_INDEXER_DATABASE = 'sui_indexer';\n\n/** In-network DNS alias siblings dial the indexer-db sidecar by. */\nconst SUI_INDEXER_DB_ALIAS = 'sui-indexer-db';\n\nconst suiInputIdentity = (opts: SuiOptions): unknown => {\n\tconst { readyTimeout: _readyTimeout, ...authored } = opts;\n\tif (authored.mode !== 'local') return { plugin: 'sui', ...authored };\n\treturn {\n\t\tplugin: 'sui',\n\t\t...authored,\n\t\tindexer: authored.indexer !== false,\n\t\tindexerDb:\n\t\t\tauthored.indexerDb === undefined\n\t\t\t\t? undefined\n\t\t\t\t: {\n\t\t\t\t\t\t...authored.indexerDb,\n\t\t\t\t\t\tdatabase: authored.indexerDb.database ?? DEFAULT_INDEXER_DATABASE,\n\t\t\t\t\t},\n\t};\n};\n\n/** Compose the indexer-db sidecar's stable `configHash` from validator\n * inputs known before local mode starts. A validator image change recreates\n * the validator, so the sidecar's mount-less PGDATA must reset with it.\n *\n * Validator data-layer resets that are not config values (absent validator\n * or last exit `137`) are handled by `validatorNeedsIndexerReset`, which\n * removes the sidecar before this hash is applied. */\nexport const composeIndexerConfigHash = (imageRef: string): string =>\n\t['indexer-db', `validator-img=${imageRef}`].join('|');\n\n/** Append a database segment to a BYO DSN only when it has no path (so a\n * caller-supplied `.../mydb` is respected). Rebuilds via the URL object\n * so the db slots into `pathname` BEFORE `?search` — a naive string\n * append on a `host:5432?sslmode=require` DSN (path-less but with a\n * query) would wrongly yield `...?sslmode=require/sui_indexer`. The\n * catch fallback (non-URL-parseable DSN) splits the query off first. */\nconst appendDatabaseIfMissing = (url: string, database: string): string => {\n\ttry {\n\t\tconst parsed = new URL(url);\n\t\tconst hasPath = parsed.pathname !== '' && parsed.pathname !== '/';\n\t\tif (hasPath) return url;\n\t\tparsed.pathname = `/${database}`;\n\t\treturn parsed.toString();\n\t} catch {\n\t\tconst queryAt = url.indexOf('?');\n\t\tconst base = queryAt === -1 ? url : url.slice(0, queryAt);\n\t\tconst query = queryAt === -1 ? '' : url.slice(queryAt);\n\t\tconst afterScheme = base.replace(/^postgres(ql)?:\\/\\//, '');\n\t\tif (/\\/[^/]+$/.test(afterScheme)) return url;\n\t\treturn `${base.replace(/\\/$/, '')}/${encodeURIComponent(database)}${query}`;\n\t}\n};\n\n/** Whether the Sui-owned indexer DB must be deleted before boot.\n *\n * The validator's chain identity lives in its writable layer. If the\n * validator container is absent, the next boot creates a new layer and a\n * new chain. If it last exited `137`, the runtime's `on-failure` policy\n * also recreates it. Both cases make any existing indexer DB stale. */\nexport const validatorNeedsIndexerReset = (\n\truntime: ContainerRuntime,\n\tidentity: Identity,\n): Effect.Effect<boolean, ContainerRuntimeError> =>\n\truntime\n\t\t.inspectByLabels({\n\t\t\tapp: identity.app,\n\t\t\tstack: identity.stack,\n\t\t\tplugin: 'sui',\n\t\t\trole: 'validator',\n\t\t})\n\t\t.pipe(\n\t\t\tEffect.map((handles) => {\n\t\t\t\tconst validator = handles[0];\n\t\t\t\treturn validator === undefined || validator.lastExitCode === 137;\n\t\t\t}),\n\t\t);\n\n/** Provision the GraphQL-indexer DB wiring for local mode.\n *\n * Default: sui OWNS a postgres sidecar (labelled under sui) — boot it on\n * a per-stack network and compose the DSN from its in-network alias (NOT\n * the per-stack container DNS host, which isn't parallel-stack-portable).\n * The sidecar resets on the same validator inputs that can make its rows\n * stale: absent/exited-`137` validator state removes the sidecar before\n * boot, and validator image identity is folded into its `configHash`.\n *\n * `validatorImage` is the SAME `ImageRef` the barrel hands `bootLocalMode`\n * for the validator container, so the image the sidecar hashed and the image\n * the validator runs cannot drift.\n *\n * BYO: when `indexerDb` is set, no sidecar — pass the caller's DSN +\n * network straight through (appending the default db iff the DSN has no\n * path). The caller owns that DB's lifecycle. */\nexport const provisionLocalIndexer = (\n\truntime: ContainerRuntime,\n\tidentity: Identity,\n\topts: SuiLocalOptions,\n\tvalidatorImage: ImageRef,\n): Effect.Effect<LocalIndexer, SuiPluginError, Scope.Scope> => {\n\tconst database = opts.indexerDb?.database ?? DEFAULT_INDEXER_DATABASE;\n\tif (opts.indexerDb !== undefined) {\n\t\treturn Effect.succeed({\n\t\t\turl: appendDatabaseIfMissing(opts.indexerDb.url, database),\n\t\t\tnetwork: opts.indexerDb.network,\n\t\t});\n\t}\n\tconst network = sanitizeAlias(`devstack-${identity.app}-${identity.stack}-sui-indexer`);\n\treturn Effect.gen(function* () {\n\t\tconst resetExistingSidecar = yield* validatorNeedsIndexerReset(runtime, identity);\n\t\tif (resetExistingSidecar) {\n\t\t\tyield* runtime.removeManagedContainers({\n\t\t\t\tapp: identity.app,\n\t\t\t\tstack: identity.stack,\n\t\t\t\tplugin: 'sui',\n\t\t\t\trole: SUI_INDEXER_DB_ROLE,\n\t\t\t});\n\t\t}\n\t\tconst imageRef = validatorImage.tag ?? validatorImage.digest;\n\t\tconst { handle } = yield* bootPostgresSidecar(runtime, identity, {\n\t\t\tnetwork,\n\t\t\talias: SUI_INDEXER_DB_ALIAS,\n\t\t\trole: SUI_INDEXER_DB_ROLE,\n\t\t\tdatabase,\n\t\t\tconfigHash: composeIndexerConfigHash(imageRef),\n\t\t});\n\t\treturn {\n\t\t\turl: withDatabase(\n\t\t\t\tcredentialedUrl({\n\t\t\t\t\tuser: handle.user,\n\t\t\t\t\tpassword: handle.password,\n\t\t\t\t\thost: handle.networkAlias,\n\t\t\t\t\tport: handle.port,\n\t\t\t\t}),\n\t\t\t\tdatabase,\n\t\t\t),\n\t\t\tnetwork: handle.containerNetwork,\n\t\t} satisfies LocalIndexer;\n\t}).pipe(\n\t\t// Postgres / runtime-domain failures wrap into the sui error channel\n\t\t// — the sidecar is a sui implementation detail. Nothing in the gen\n\t\t// body produces a `SuiPluginError` directly, so we wrap every cause.\n\t\tEffect.mapError((cause) =>\n\t\t\tsuiPluginError(\n\t\t\t\t'container-start',\n\t\t\t\t`sui local mode: failed to provision the GraphQL indexer postgres sidecar (${cause._tag})`,\n\t\t\t\tcause,\n\t\t\t),\n\t\t),\n\t);\n};\n\n/** Shared boot + inline contribution emission, parameterised by the\n * resolved external-indexer wiring (`undefined` = no GraphQL, the\n * zero-config / non-local case) and, for local mode, the validator image\n * the barrel pre-resolved (so the sidecar's `configHash` and the validator\n * container share one `ImageRef`; `undefined` on the no-sidecar paths,\n * where `bootLocalMode` resolves it inline). */\nconst bootAndEmit = (\n\topts: SuiOptions,\n\tindexer: LocalIndexer | undefined,\n\tprebuiltImage?: ImageRef,\n) =>\n\tEffect.gen(function* () {\n\t\tconst ctx = yield* PluginContext;\n\t\t// The substrate threads `ContainerRuntime` + `IdentityContext`\n\t\t// via the plugin runtime context; the supervisor provides\n\t\t// these before this body runs.\n\t\tconst runtime = yield* ContainerRuntimeService;\n\t\tconst identity = yield* IdentityContext;\n\t\tconst paths = yield* StackPathsService;\n\t\tconst portBroker = yield* PortBrokerService;\n\t\tconst fundingFaucetLeaseBroker = yield* LeaseBrokerService;\n\t\tconst { client } = yield* bootSuiService(\n\t\t\truntime,\n\t\t\tidentity,\n\t\t\tportBroker,\n\t\t\tpaths,\n\t\t\topts,\n\t\t\tindexer,\n\t\t\tprebuiltImage,\n\t\t);\n\n\t\tconst fundingFaucetStrategy = yield* resolveFundingFaucetStrategy(\n\t\t\topts,\n\t\t\tclient,\n\t\t\tfundingFaucetLeaseBroker,\n\t\t);\n\t\tconst value = {\n\t\t\t...client,\n\t\t\tmode: opts.mode,\n\t\t\tfundingFaucetStrategy,\n\t\t} satisfies SuiResolvedRuntime;\n\t\t// Emit the resolved contributions inline, top-to-bottom: the\n\t\t// decls stamp REAL chain ids / rpc URLs / container names\n\t\t// (the genesis-digest chain id, not the network name).\n\t\t// (`value` is the just-resolved runtime; `identity` from\n\t\t// `IdentityContext`, NOT re-fetched). The shared\n\t\t// `emitContributions` routes each by `kind`. Faucet (conditional\n\t\t// on a resolved strategy) and routables (mode-dependent) are the\n\t\t// only optional members; order is load-bearing.\n\t\tconst realChainId = value.chainId;\n\t\t// Funding-faucet gate: a FIXED policy — ON for every network EXCEPT\n\t\t// live `mainnet`, where the funding faucet must NEVER run.\n\t\t// `identity.network` is the resolved network name. There is NO\n\t\t// per-network override surface for this gate: the override RECORD\n\t\t// (`networkOptions`) is an orchestrator-level concern that the\n\t\t// name-blind substrate does not forward into plugins (this plugin\n\t\t// only receives the closed `IdentityContext` tuple), so a per-network\n\t\t// `{ <network>: { faucet: false } }` toggle could never reach here —\n\t\t// `NetworkScopedOptions` therefore deliberately omits a `faucet`\n\t\t// field rather than advertise a silent no-op (see\n\t\t// `orchestrators/network-options.ts`). The mainnet exclusion is\n\t\t// load-bearing: a resolved strategy on `mainnet` (none of the live\n\t\t// modes build one today, but a future faucet-bearing mainnet config\n\t\t// could) is suppressed and the strategy registry never exposes\n\t\t// `faucet:request:<mainnet-chain-id>`; account funding then surfaces\n\t\t// the actionable \"no faucet strategy\" error rather than silently\n\t\t// faucet-funding against a production network.\n\t\tconst faucetEnabled = identity.network !== 'mainnet';\n\t\tconst faucetContribution: ReadonlyArray<StrategyContributorDecl> =\n\t\t\t!faucetEnabled || value.fundingFaucetStrategy === null\n\t\t\t\t? []\n\t\t\t\t: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tkind: 'strategy-contributor',\n\t\t\t\t\t\t\tcapabilityKey: faucetCapabilityKey(realChainId),\n\t\t\t\t\t\t\tstrategy: value.fundingFaucetStrategy,\n\t\t\t\t\t\t\tautoMounted: true,\n\t\t\t\t\t\t} satisfies StrategyContributorDecl<\n\t\t\t\t\t\t\t`faucet:request:${string}`,\n\t\t\t\t\t\t\tReturnType<typeof suiLocalStrategy>\n\t\t\t\t\t\t>,\n\t\t\t\t\t];\n\t\tconst localRoutables =\n\t\t\topts.mode === 'local'\n\t\t\t\t? makeSuiLocalRoutables({\n\t\t\t\t\t\tcontainerName: `devstack-${identity.app}-${identity.stack}-sui-validator`,\n\t\t\t\t\t\t// GraphQL routes only when the external indexer is wired.\n\t\t\t\t\t\tincludeGraphql: indexer !== undefined,\n\t\t\t\t\t})\n\t\t\t\t: [];\n\t\tconst forkRoutables =\n\t\t\topts.mode === 'fork'\n\t\t\t\t? makeSuiForkRoutables({\n\t\t\t\t\t\tcontainerName: `devstack-${identity.app}-${identity.stack}-sui-fork`,\n\t\t\t\t\t})\n\t\t\t\t: [];\n\t\temitContributions(ctx, [\n\t\t\t// `hasIndexer` (local only) folds the sui-owned indexer-db\n\t\t\t// sidecar into the captured containers; `indexer !== undefined`\n\t\t\t// is the same gate `bootAndEmit`'s caller resolved GraphQL on.\n\t\t\tmakeSnapshotable(opts.mode, identity.app, identity.stack, realChainId, indexer !== undefined),\n\t\t\tmakeCodegenable({\n\t\t\t\tmode: opts.mode,\n\t\t\t\tchainId: realChainId,\n\t\t\t\trpc: value.rpcUrl,\n\t\t\t\tsource: 'default',\n\t\t\t\t...(value.faucetUrl !== null ? { faucet: value.faucetUrl } : {}),\n\t\t\t\t...(value.graphqlUrl !== null ? { graphql: value.graphqlUrl } : {}),\n\t\t\t}),\n\t\t\t{\n\t\t\t\tkind: 'strategy-contributor',\n\t\t\t\tcapabilityKey: chainProbeCapabilityKey(realChainId),\n\t\t\t\tstrategy: value.chainProbe,\n\t\t\t\tautoMounted: true,\n\t\t\t} satisfies StrategyContributorDecl<`chain-probe:${string}`, ChainProbe<SuiProbeKey>>,\n\t\t\t...faucetContribution,\n\t\t\t...localRoutables,\n\t\t\t...forkRoutables,\n\t\t]);\n\t\treturn value;\n\t});\n\n/** The single sui plugin builder. No sibling `dependsOn`: in local mode\n * with the indexer on (the default), `start` provisions the GraphQL\n * indexer DB itself — a sui-owned postgres sidecar — inside the boot\n * scope BEFORE the boot-time validator (so the validator can join its\n * network), then boots + emits. Resolution order:\n * - `indexerDb` present → BYO DB (no sidecar)\n * - `indexer === false` → opt out (RPC + faucet only, GraphQL off)\n * - else (local default) → sui-owned sidecar\n * Non-local modes never touch the indexer (GraphQL off). */\nconst buildSuiPlugin = (opts: SuiOptions) =>\n\tdefinePlugin({\n\t\tid: suiResource.id,\n\t\trole: 'service',\n\t\tsection: 'service',\n\t\tinputIdentity: staticInputIdentity(suiInputIdentity(opts)),\n\t\t// Stack-free codegen: the `codegen` verb derives the committed\n\t\t// `config.ts`'s `network`/`networks` from this hook. Both are\n\t\t// environment/live data (dynamic local rpc port; a real deployment\n\t\t// names a different network), so the committed tree carries\n\t\t// `dep.network`/`Object.fromEntries(networkNames.map(forNetwork))` raw\n\t\t// expressions off the loaded deployment that resolve\n\t\t// at app build/dev time via the injected `__DEVSTACK_DEPLOYMENT__` global —\n\t\t// never literal values. No id-resolver input needed.\n\t\tstaticCodegen: makeStaticCodegen(),\n\t\t// Zero-arg `start` (no `dependsOn`); the substrate supplies the\n\t\t// container runtime + identity via the plugin runtime context.\n\t\tstart: () =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\t// Resolution order: BYO `indexerDb` wins; then an explicit\n\t\t\t\t// `indexer: false` opt-out; then the sui-owned sidecar default.\n\t\t\t\t// Non-local modes never wire the indexer.\n\t\t\t\tif (opts.mode !== 'local' || (opts.indexerDb === undefined && opts.indexer === false)) {\n\t\t\t\t\treturn yield* bootAndEmit(opts, undefined);\n\t\t\t\t}\n\t\t\t\tconst runtime = yield* ContainerRuntimeService;\n\t\t\t\tconst identity = yield* IdentityContext;\n\t\t\t\t// Resolve the validator image ONCE, before the sidecar — single\n\t\t\t\t// source for both the sidecar's configHash (it folds the resolved\n\t\t\t\t// image ref so an image bump resets the indexer DB) and the\n\t\t\t\t// validator container `bootAndEmit` boots below. `resolveImage`\n\t\t\t\t// is deterministic (content-addressed build/pull) so this is the\n\t\t\t\t// SAME ref the validator runs; resolving it here, not twice,\n\t\t\t\t// avoids a redundant build AND any drift from `decideRunAction`.\n\t\t\t\tconst validatorImage = yield* resolveImage(runtime, identity, opts);\n\t\t\t\t// The sidecar boots first; if the validator boot below fails, the\n\t\t\t\t// sidecar's finalizer lingers on the plugin acquire scope until\n\t\t\t\t// teardown/retry. Benign and intentional — the stable name +\n\t\t\t\t// labels mean a retry ADOPTS the existing sidecar container\n\t\t\t\t// (no scope-threading machinery needed).\n\t\t\t\tconst indexer = yield* provisionLocalIndexer(runtime, identity, opts, validatorImage);\n\t\t\t\treturn yield* bootAndEmit(opts, indexer, validatorImage);\n\t\t\t}),\n\t});\n\nconst buildPlugin = <O extends SuiOptions>(opts: O) => buildSuiPlugin(opts);\n\n// ---------------------------------------------------------------------------\n// User-facing factories\n// ---------------------------------------------------------------------------\n\n/** Local Sui shorthand. Network/env selection belongs to the CLI or\n * `defineDevstackWith(...)`; plain `sui()` always means an in-stack\n * local validator (GraphQL/indexer/Postgres on by default via a\n * sui-owned sidecar). */\nexport const sui = <const O extends SuiOptions = { mode: 'local' }>(\n\topts: O = { mode: 'local' } as O,\n) => buildPlugin(opts);\n\n/** Mode-narrowed factory namespace.\n *\n * Usage:\n * const network = { mode: 'local' } as const;\n * suiFor(network).local({...}) // OK\n * suiFor(network).fork({...}) // type error: 'fork' not in 'local' branch\n *\n * The namespace MIRRORS the four mode option records: `local`,\n * `localRpc` (mapped onto the substrate `'local'` branch),\n * `live`, `fork`. */\nexport const suiFor = defineModeNamespace({\n\tlocal: {\n\t\tlocal: <const O extends Omit<SuiLocalOptions, 'mode'>>(opts: O = {} as O) =>\n\t\t\tbuildPlugin({ mode: 'local', ...opts }),\n\t\tlocalRpc: (opts: Omit<SuiLocalRpcOptions, 'mode'>) =>\n\t\t\tbuildPlugin({ mode: 'local-rpc', ...opts }),\n\t},\n\tlive: {\n\t\ttestnet: (opts: Omit<SuiLiveOptions, 'mode' | 'network'> = {}) =>\n\t\t\tbuildPlugin({ mode: 'live', network: 'testnet', ...opts }),\n\t\tmainnet: (opts: Omit<SuiLiveOptions, 'mode' | 'network'> = {}) =>\n\t\t\tbuildPlugin({ mode: 'live', network: 'mainnet', ...opts }),\n\t\tdevnet: (opts: Omit<SuiLiveOptions, 'mode' | 'network'> = {}) =>\n\t\t\tbuildPlugin({ mode: 'live', network: 'devnet', ...opts }),\n\t\tcustom: (opts: Omit<SuiLiveOptions, 'mode' | 'network'>) =>\n\t\t\tbuildPlugin({ mode: 'live', network: 'custom', ...opts }),\n\t},\n\tfork: {\n\t\tmainnet: (opts: Omit<SuiForkOptions, 'mode' | 'upstream'> = {}) =>\n\t\t\tbuildPlugin({ mode: 'fork', upstream: 'mainnet', ...opts }),\n\t\ttestnet: (opts: Omit<SuiForkOptions, 'mode' | 'upstream'> = {}) =>\n\t\t\tbuildPlugin({ mode: 'fork', upstream: 'testnet', ...opts }),\n\t\tdevnet: (opts: Omit<SuiForkOptions, 'mode' | 'upstream'> = {}) =>\n\t\t\tbuildPlugin({ mode: 'fork', upstream: 'devnet', ...opts }),\n\t},\n});\n\n// ---------------------------------------------------------------------------\n// Re-exports for advanced callers (Account/Coin/Wallet/etc.) and for\n// the sibling plugins (Walrus/Seal/Deepbook fork variants).\n// ---------------------------------------------------------------------------\n\nexport type { SuiClient, ForkAdminSurface, WaitForTransactionsReady } from './mode/shared.ts';\nexport type { ResolvedSuiNetwork } from './network-resolver.ts';\nexport type {\n\tNetworkConfig,\n\tNetworkMode,\n\tDevstackNetworkModeRegistry,\n\tDefaultNetwork,\n} from './network-config.ts';\nexport type {\n\tSuiOptions,\n\tSuiLocalOptions,\n\tSuiLocalRpcOptions,\n\tSuiLiveOptions,\n\tSuiForkOptions,\n\tSuiPluginMode,\n} from './mode/spec.ts';\nexport type { SuiNetworkConfigEntry } from './codegen.ts';\nexport type {\n\tSuiError,\n\tSuiPluginError,\n\tSuiCliError,\n\tSuiConfigError,\n\tForkUnsupportedError,\n} from './errors.ts';\n\n// Cross-plugin seams (consumed by Walrus/Seal/Deepbook fork variants\n// and by Account/Coin/Wallet/Package).\nexport {\n\tchainProbeCapabilityKey,\n\ttype ChainProbe,\n\ttype ChainProbeError,\n\ttype ChainProbeMode,\n} from '../../contracts/chain-probe.ts';\nexport {\n\tFORK_UNSUPPORTED_SURFACES,\n\twrapWithForkGuard,\n\ttype ForkLockHolder,\n} from './fork-orchestration.ts';\nexport type { SuiProbeKey, SuiSdkShim } from './chain-probe.ts';\nexport { SuiLogAttr } from './log-attrs.ts';\n\n/** The shape `Transaction.build({ client })` and every `sdk.core.*` call\n * accepts. Re-exported from `@mysten/sui/client` so callers cast\n * `sui.sdk.client as ClientWithCoreApi` without each having to know the\n * SDK subpath. */\nexport type { ClientWithCoreApi } from '@mysten/sui/client';\n// Cross-plugin seams: fork impersonation + chain-build container.\n// Consumed by `action` (Move-call execution against fork) and\n// `package` (publish-to-fork + Move-build orchestration). Consumer\n// plugins import these barrel entries rather than internal modules.\nexport {\n\tbuildForkImpersonationTransactionBytes,\n\tprepareForkImpersonationTransaction,\n\tverifyForkImpersonationSender,\n\tselectSufficientForkCoin,\n\tFORK_IMPERSONATION_GAS_BUDGET,\n\tFORK_IMPERSONATION_GAS_PRICE,\n\ttype ForkGasCoin,\n\ttype ForkImpersonationGasClient,\n} from './fork-transaction.ts';\nexport {\n\tsuiForkFaucetStrategy,\n\ttype SuiForkFaucetStrategyOptions,\n\ttype SuiForkFaucetSerialization,\n} from './fork-faucet-strategy.ts';\nexport {\n\tacquireChainBuildContainer,\n\tcontainerNameForApp,\n\tmoveBuildLockPathFor,\n\tMOVE_BUILD_LOCK_TIMEOUT_MS,\n\ttype ChainBuildContainer,\n\ttype ChainBuildContainerSpec,\n} from './chain-build-container.ts';\n\n// Cross-plugin seams: hoisted exec / move / ledger helpers.\n// Consumed by Account/Action/Coin/Deepbook/Package/Seal/Walrus. These\n// barrel entries route those consumers through the sibling plugin's\n// barrel per the `plugin-boundary` invariant, rather than reaching into\n// the deep `sui/exec`, `sui/move`, and `sui/ledger` internal modules.\nexport {\n\textractExecuteDigest,\n\tformatExecutedFailure,\n\texecuteSuiTx,\n\tisSuiStaleObjectVersionError,\n\ttype ResolvedSigner,\n\ttype ExecutedFailure,\n\ttype ExecutedReceipt,\n\ttype TransactionSignerScope,\n} from './exec/index.ts';\nexport { signAndDispatch } from './exec/sign-and-dispatch.ts';\nexport {\n\thashMoveSources,\n\trunMoveBuild,\n\tscrubLocksHost,\n\twithMoveBuildLock,\n\ttype BuildOutput,\n\ttype MoveBuildContainer,\n\ttype MoveBuildError,\n} from './move/index.ts';\nexport { currentLedgerObjectRef } from './ledger/object-ref.ts';\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../src/plugins/sui/index.ts"],"sourcesContent":["// Sui plugin — barrel + factories.\n//\n// Architecture: Sui is the most-depended-on plugin in the stack.\n// Every consumer (Account/Coin/Wallet/Faucet/Package; Walrus/Seal/\n// Deepbook fork variants) reads its resolved `SuiClient` via the\n// `suiResource`. The factory at this file folds the four modes behind:\n//\n// - `sui(opts?)` — local shorthand. Defaults to an in-stack\n// local validator; pass a typed `opts`\n// record to select a different mode.\n// - `suiFor(network)` — mode-narrowed factory namespace (per\n// architecture Tension 11). Returns\n// `{ local: …, live: …, fork: … }`\n// narrowed to the network's mode.\n//\n// During `start`, the plugin emits (via the typed `ctx.*` verbs):\n//\n// 1. `ctx.provides` — `chain-probe:<chainId>` strategy contributor,\n// the schema-validated read surface (`makeSuiChainProbe`).\n// 2. `ctx.snapshotExtra` — mode-aware container + bind-mount capture.\n// 3. `ctx.codegen` — `sui-network` bindings (chain id, rpc, etc.).\n// 4. `ctx.provides` — faucet strategy contributor, local-coin\n// dispensing for the mode's chain id.\n//\n// `ctx.endpoint` contributions are MODE-DEPENDENT (local + fork yes;\n// local-rpc + live no — the caller fronts their own RPC). They land\n// in the per-mode builder under `mode/*.ts`; this barrel emits them\n// alongside the rest during `start`.\n\nimport { Effect, type Scope } from 'effect';\n\nimport { defineModeNamespace } from '../../api/mode-narrowed-factory.ts';\nimport { definePlugin, resource, staticInputIdentity } from '../../api/define-plugin.ts';\nimport {\n\tbootPostgresSidecar,\n\tcredentialedUrl,\n\twithDatabase,\n} from '../internal/postgres-sidecar/index.ts';\nimport type { ChainProbe } from '../../contracts/chain-probe.ts';\nimport type { StrategyContributorDecl } from '../../contracts/strategy-contributor.ts';\nimport { emitContributions, PluginContext } from '../../substrate/plugin-ctx.ts';\n\nimport { chainProbeCapabilityKey } from '../../contracts/chain-probe.ts';\nimport { ContainerRuntimeService } from '../../runtime/docker/service.ts';\nimport type {\n\tContainerRuntime,\n\tContainerRuntimeError,\n\tImageRef,\n} from '../../contracts/container-runtime.ts';\nimport type { Identity } from '../../substrate/identity.ts';\nimport { IdentityContext, StackPathsService } from '../../substrate/runtime/paths.ts';\nimport { sanitizeAlias } from '../../substrate/runtime/managed-container.ts';\nimport {\n\tLeaseBrokerService,\n\ttype LeaseBroker,\n} from '../../substrate/runtime/lease-broker/index.ts';\nimport { PortBrokerService } from '../../substrate/runtime/port-broker/index.ts';\nimport { makeCodegenable, makeStaticCodegen } from './codegen.ts';\nimport type { SuiProbeKey } from './chain-probe.ts';\nimport { makeSnapshotable } from './snapshot.ts';\nimport { bootSuiService } from './service.ts';\nimport { suiPluginError, type SuiPluginError } from './errors.ts';\nimport { makeSuiForkRoutables, makeSuiLocalRoutables } from './routable.ts';\nimport { faucetCapabilityKey, type FaucetStrategy } from '../faucet/index.ts';\nimport { suiLocalStrategy } from './local-faucet-strategy.ts';\nimport { suiForkFaucetStrategy } from './fork-faucet-strategy.ts';\nimport { selectSufficientForkCoin } from './fork-transaction.ts';\nimport { FORK_FAUCET_WHALE_MIN_COIN_MIST, resolveForkWhale } from './mode/fork.ts';\nimport { resolveImage, SUI_INDEXER_DB_ROLE, type LocalIndexer } from './mode/local.ts';\nimport type { SuiClient } from './mode/shared.ts';\nimport type {\n\tSuiForkOptions,\n\tSuiLiveOptions,\n\tSuiLocalRpcOptions,\n\tSuiLocalOptions,\n\tSuiOptions,\n} from './mode/spec.ts';\n\n// ---------------------------------------------------------------------------\n// Resource identity\n// ---------------------------------------------------------------------------\n\ntype SuiResolved = SuiClient & {\n\treadonly mode: SuiOptions['mode'];\n};\n\n/** Internal extension of `SuiResolved` carrying the pre-built funding\n * faucet strategy. `start` owns the `LeaseBrokerService` instance for\n * serialization; building the strategy at start-time (where the broker\n * is reachable) lets the capabilities factory consume a flat value\n * without threading the broker through a side channel. Local/live wrap\n * the HTTP faucet; fork impersonates a whale; `null` on networks with\n * no faucet (live-mainnet, external-rpc-without-faucet, faucet-disabled\n * fork). */\ntype SuiResolvedRuntime = SuiResolved & {\n\treadonly fundingFaucetStrategy: FaucetStrategy | null;\n};\n\n/** The Sui plugin's resource identity. The id is `'sui'` (singular). */\nexport const suiResource = resource<'sui', SuiResolved>('sui');\n\n// ---------------------------------------------------------------------------\n// Plugin construction (internal — used by sui() + suiFor())\n// ---------------------------------------------------------------------------\n\n/** Build the funds-ready faucet strategy for the resolved network, or\n * `null` when the network has none. Local/live wrap the HTTP faucet;\n * fork mode impersonates a whale (resolved + validated here). */\nconst resolveFundingFaucetStrategy = (\n\topts: SuiOptions,\n\tclient: SuiClient,\n\tbroker: LeaseBroker,\n): Effect.Effect<FaucetStrategy | null, SuiPluginError> => {\n\tif (opts.mode === 'fork') {\n\t\treturn resolveForkFaucetStrategy(opts, client, broker);\n\t}\n\tif (client.fundingFaucetUrl === null) {\n\t\treturn Effect.succeed(null);\n\t}\n\treturn Effect.succeed(\n\t\tsuiLocalStrategy({\n\t\t\tfaucetUrl: client.fundingFaucetUrl,\n\t\t\tserialization: {\n\t\t\t\tbroker,\n\t\t\t\tkey: `sui-faucet:${client.chainId}`,\n\t\t\t\towner: `sui-faucet:${client.chainId}`,\n\t\t\t},\n\t\t}),\n\t);\n};\n\n/** Fork-mode faucet: resolve the whale, validate it holds a large enough\n * SUI coin, then build the impersonation strategy. A whale the user set\n * explicitly that fails validation hard-fails the boot; a per-upstream\n * default whale only warns and disables the faucet so the fork still\n * comes up (account funding then surfaces the \"no faucet strategy\"\n * error only if something actually needs SUI). */\nconst resolveForkFaucetStrategy = (\n\topts: SuiForkOptions,\n\tclient: SuiClient,\n\tbroker: LeaseBroker,\n): Effect.Effect<FaucetStrategy | null, SuiPluginError> =>\n\tEffect.gen(function* () {\n\t\tconst resolved = resolveForkWhale(opts);\n\t\tconst fork = client.fork;\n\t\tif (resolved === null || fork === null) {\n\t\t\treturn null;\n\t\t}\n\t\tconst strategy = suiForkFaucetStrategy({\n\t\t\twhale: resolved.whale,\n\t\t\tfork,\n\t\t\tsdk: client.sdk,\n\t\t\tperRequestCapMist: resolved.perRequestCapMist,\n\t\t\tserialization: {\n\t\t\t\tbroker,\n\t\t\t\tkey: `sui-fork-faucet:${client.chainId}`,\n\t\t\t\towner: `sui-fork-faucet:${client.chainId}`,\n\t\t\t},\n\t\t});\n\t\treturn yield* selectSufficientForkCoin(\n\t\t\tclient.sdk.core,\n\t\t\tresolved.whale,\n\t\t\tFORK_FAUCET_WHALE_MIN_COIN_MIST,\n\t\t).pipe(\n\t\t\tEffect.as(strategy),\n\t\t\tEffect.catchTag('SuiPluginError', (cause) =>\n\t\t\t\tresolved.explicit\n\t\t\t\t\t? Effect.fail(cause)\n\t\t\t\t\t: Effect.logWarning(\n\t\t\t\t\t\t\t`sui fork mode: default faucet whale ${resolved.whale} is unusable ` +\n\t\t\t\t\t\t\t\t`(${cause.message}); disabling the fork faucet. Set faucet.whale to override.`,\n\t\t\t\t\t\t).pipe(Effect.as(null)),\n\t\t\t),\n\t\t);\n\t});\n\n/** Default name for sui's GraphQL-indexer database. */\nconst DEFAULT_INDEXER_DATABASE = 'sui_indexer';\n\n/** In-network DNS alias siblings dial the indexer-db sidecar by. */\nconst SUI_INDEXER_DB_ALIAS = 'sui-indexer-db';\n\nconst suiInputIdentity = (opts: SuiOptions): unknown => {\n\tconst { readyTimeout: _readyTimeout, ...authored } = opts;\n\tif (authored.mode !== 'local') return { plugin: 'sui', ...authored };\n\treturn {\n\t\tplugin: 'sui',\n\t\t...authored,\n\t\tindexer: authored.indexer !== false,\n\t\tindexerDb:\n\t\t\tauthored.indexerDb === undefined\n\t\t\t\t? undefined\n\t\t\t\t: {\n\t\t\t\t\t\t...authored.indexerDb,\n\t\t\t\t\t\tdatabase: authored.indexerDb.database ?? DEFAULT_INDEXER_DATABASE,\n\t\t\t\t\t},\n\t};\n};\n\n/** Compose the indexer-db sidecar's stable `configHash` from validator\n * inputs known before local mode starts. A validator image change recreates\n * the validator, so the sidecar's mount-less PGDATA must reset with it.\n *\n * Validator data-layer resets that are not config values (absent validator\n * or last exit `137`) are handled by `validatorNeedsIndexerReset`, which\n * removes the sidecar before this hash is applied. */\nexport const composeIndexerConfigHash = (imageRef: string): string =>\n\t['indexer-db', `validator-img=${imageRef}`].join('|');\n\n/** Append a database segment to a BYO DSN only when it has no path (so a\n * caller-supplied `.../mydb` is respected). Rebuilds via the URL object\n * so the db slots into `pathname` BEFORE `?search` — a naive string\n * append on a `host:5432?sslmode=require` DSN (path-less but with a\n * query) would wrongly yield `...?sslmode=require/sui_indexer`. The\n * catch fallback (non-URL-parseable DSN) splits the query off first. */\nconst appendDatabaseIfMissing = (url: string, database: string): string => {\n\ttry {\n\t\tconst parsed = new URL(url);\n\t\tconst hasPath = parsed.pathname !== '' && parsed.pathname !== '/';\n\t\tif (hasPath) return url;\n\t\tparsed.pathname = `/${database}`;\n\t\treturn parsed.toString();\n\t} catch {\n\t\tconst queryAt = url.indexOf('?');\n\t\tconst base = queryAt === -1 ? url : url.slice(0, queryAt);\n\t\tconst query = queryAt === -1 ? '' : url.slice(queryAt);\n\t\tconst afterScheme = base.replace(/^postgres(ql)?:\\/\\//, '');\n\t\tif (/\\/[^/]+$/.test(afterScheme)) return url;\n\t\treturn `${base.replace(/\\/$/, '')}/${encodeURIComponent(database)}${query}`;\n\t}\n};\n\n/** Whether the Sui-owned indexer DB must be deleted before boot.\n *\n * The validator's chain identity lives in its writable layer. If the\n * validator container is absent, the next boot creates a new layer and a\n * new chain. If it last exited `137`, the runtime's `on-failure` policy\n * also recreates it. Both cases make any existing indexer DB stale. */\nexport const validatorNeedsIndexerReset = (\n\truntime: ContainerRuntime,\n\tidentity: Identity,\n): Effect.Effect<boolean, ContainerRuntimeError> =>\n\truntime\n\t\t.inspectByLabels({\n\t\t\tapp: identity.app,\n\t\t\tstack: identity.stack,\n\t\t\tplugin: 'sui',\n\t\t\trole: 'validator',\n\t\t})\n\t\t.pipe(\n\t\t\tEffect.map((handles) => {\n\t\t\t\tconst validator = handles[0];\n\t\t\t\treturn validator === undefined || validator.lastExitCode === 137;\n\t\t\t}),\n\t\t);\n\n/** Provision the GraphQL-indexer DB wiring for local mode.\n *\n * Default: sui OWNS a postgres sidecar (labelled under sui) — boot it on\n * a per-stack network and compose the DSN from its in-network alias (NOT\n * the per-stack container DNS host, which isn't parallel-stack-portable).\n * The sidecar resets on the same validator inputs that can make its rows\n * stale: absent/exited-`137` validator state removes the sidecar before\n * boot, and validator image identity is folded into its `configHash`.\n *\n * `validatorImage` is the SAME `ImageRef` the barrel hands `bootLocalMode`\n * for the validator container, so the image the sidecar hashed and the image\n * the validator runs cannot drift.\n *\n * BYO: when `indexerDb` is set, no sidecar — pass the caller's DSN +\n * network straight through (appending the default db iff the DSN has no\n * path). The caller owns that DB's lifecycle. */\nexport const provisionLocalIndexer = (\n\truntime: ContainerRuntime,\n\tidentity: Identity,\n\topts: SuiLocalOptions,\n\tvalidatorImage: ImageRef,\n): Effect.Effect<LocalIndexer, SuiPluginError, Scope.Scope> => {\n\tconst database = opts.indexerDb?.database ?? DEFAULT_INDEXER_DATABASE;\n\tif (opts.indexerDb !== undefined) {\n\t\treturn Effect.succeed({\n\t\t\turl: appendDatabaseIfMissing(opts.indexerDb.url, database),\n\t\t\tnetwork: opts.indexerDb.network,\n\t\t});\n\t}\n\tconst network = sanitizeAlias(`devstack-${identity.app}-${identity.stack}-sui-indexer`);\n\treturn Effect.gen(function* () {\n\t\tconst resetExistingSidecar = yield* validatorNeedsIndexerReset(runtime, identity);\n\t\tif (resetExistingSidecar) {\n\t\t\tyield* runtime.removeManagedContainers({\n\t\t\t\tapp: identity.app,\n\t\t\t\tstack: identity.stack,\n\t\t\t\tplugin: 'sui',\n\t\t\t\trole: SUI_INDEXER_DB_ROLE,\n\t\t\t});\n\t\t}\n\t\tconst imageRef = validatorImage.tag ?? validatorImage.digest;\n\t\tconst { handle } = yield* bootPostgresSidecar(runtime, identity, {\n\t\t\tnetwork,\n\t\t\talias: SUI_INDEXER_DB_ALIAS,\n\t\t\trole: SUI_INDEXER_DB_ROLE,\n\t\t\tdatabase,\n\t\t\tconfigHash: composeIndexerConfigHash(imageRef),\n\t\t});\n\t\treturn {\n\t\t\turl: withDatabase(\n\t\t\t\tcredentialedUrl({\n\t\t\t\t\tuser: handle.user,\n\t\t\t\t\tpassword: handle.password,\n\t\t\t\t\thost: handle.networkAlias,\n\t\t\t\t\tport: handle.port,\n\t\t\t\t}),\n\t\t\t\tdatabase,\n\t\t\t),\n\t\t\tnetwork: handle.containerNetwork,\n\t\t} satisfies LocalIndexer;\n\t}).pipe(\n\t\t// Postgres / runtime-domain failures wrap into the sui error channel\n\t\t// — the sidecar is a sui implementation detail. Nothing in the gen\n\t\t// body produces a `SuiPluginError` directly, so we wrap every cause.\n\t\tEffect.mapError((cause) =>\n\t\t\tsuiPluginError(\n\t\t\t\t'container-start',\n\t\t\t\t`sui local mode: failed to provision the GraphQL indexer postgres sidecar (${cause._tag})`,\n\t\t\t\tcause,\n\t\t\t),\n\t\t),\n\t);\n};\n\n/** Shared boot + inline contribution emission, parameterised by the\n * resolved external-indexer wiring (`undefined` = no GraphQL, the\n * zero-config / non-local case) and, for local mode, the validator image\n * the barrel pre-resolved (so the sidecar's `configHash` and the validator\n * container share one `ImageRef`; `undefined` on the no-sidecar paths,\n * where `bootLocalMode` resolves it inline). */\nconst bootAndEmit = (\n\topts: SuiOptions,\n\tindexer: LocalIndexer | undefined,\n\tprebuiltImage?: ImageRef,\n) =>\n\tEffect.gen(function* () {\n\t\tconst ctx = yield* PluginContext;\n\t\t// The substrate threads `ContainerRuntime` + `IdentityContext`\n\t\t// via the plugin runtime context; the supervisor provides\n\t\t// these before this body runs.\n\t\tconst runtime = yield* ContainerRuntimeService;\n\t\tconst identity = yield* IdentityContext;\n\t\tconst paths = yield* StackPathsService;\n\t\tconst portBroker = yield* PortBrokerService;\n\t\tconst fundingFaucetLeaseBroker = yield* LeaseBrokerService;\n\t\tconst { client } = yield* bootSuiService(\n\t\t\truntime,\n\t\t\tidentity,\n\t\t\tportBroker,\n\t\t\tpaths,\n\t\t\topts,\n\t\t\tindexer,\n\t\t\tprebuiltImage,\n\t\t);\n\n\t\tconst fundingFaucetStrategy = yield* resolveFundingFaucetStrategy(\n\t\t\topts,\n\t\t\tclient,\n\t\t\tfundingFaucetLeaseBroker,\n\t\t);\n\t\tconst value = {\n\t\t\t...client,\n\t\t\tmode: opts.mode,\n\t\t\tfundingFaucetStrategy,\n\t\t} satisfies SuiResolvedRuntime;\n\t\t// Emit the resolved contributions inline, top-to-bottom: the\n\t\t// decls stamp REAL chain ids / rpc URLs / container names\n\t\t// (the genesis-digest chain id, not the network name).\n\t\t// (`value` is the just-resolved runtime; `identity` from\n\t\t// `IdentityContext`, NOT re-fetched). The shared\n\t\t// `emitContributions` routes each by `kind`. Faucet (conditional\n\t\t// on a resolved strategy) and routables (mode-dependent) are the\n\t\t// only optional members; order is load-bearing.\n\t\tconst realChainId = value.chainId;\n\t\t// Funding-faucet gate: a FIXED policy — ON for every network EXCEPT\n\t\t// live `mainnet`, where the funding faucet must NEVER run.\n\t\t// `identity.network` is the resolved network name. There is NO\n\t\t// per-network override surface for this gate: the override RECORD\n\t\t// (`networkOptions`) is an orchestrator-level concern that the\n\t\t// name-blind substrate does not forward into plugins (this plugin\n\t\t// only receives the closed `IdentityContext` tuple), so a per-network\n\t\t// `{ <network>: { faucet: false } }` toggle could never reach here —\n\t\t// `NetworkScopedOptions` therefore deliberately omits a `faucet`\n\t\t// field rather than advertise a silent no-op (see\n\t\t// `orchestrators/network-options.ts`). The mainnet exclusion is\n\t\t// load-bearing: a resolved strategy on `mainnet` (none of the live\n\t\t// modes build one today, but a future faucet-bearing mainnet config\n\t\t// could) is suppressed and the strategy registry never exposes\n\t\t// `faucet:request:<mainnet-chain-id>`; account funding then surfaces\n\t\t// the actionable \"no faucet strategy\" error rather than silently\n\t\t// faucet-funding against a production network.\n\t\tconst faucetEnabled = identity.network !== 'mainnet';\n\t\tconst faucetContribution: ReadonlyArray<StrategyContributorDecl> =\n\t\t\t!faucetEnabled || value.fundingFaucetStrategy === null\n\t\t\t\t? []\n\t\t\t\t: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tkind: 'strategy-contributor',\n\t\t\t\t\t\t\tcapabilityKey: faucetCapabilityKey(realChainId),\n\t\t\t\t\t\t\tstrategy: value.fundingFaucetStrategy,\n\t\t\t\t\t\t\tautoMounted: true,\n\t\t\t\t\t\t} satisfies StrategyContributorDecl<\n\t\t\t\t\t\t\t`faucet:request:${string}`,\n\t\t\t\t\t\t\tReturnType<typeof suiLocalStrategy>\n\t\t\t\t\t\t>,\n\t\t\t\t\t];\n\t\tconst localRoutables =\n\t\t\topts.mode === 'local'\n\t\t\t\t? makeSuiLocalRoutables({\n\t\t\t\t\t\tcontainerName: `devstack-${identity.app}-${identity.stack}-sui-validator`,\n\t\t\t\t\t\t// GraphQL routes only when the external indexer is wired.\n\t\t\t\t\t\tincludeGraphql: indexer !== undefined,\n\t\t\t\t\t})\n\t\t\t\t: [];\n\t\tconst forkRoutables =\n\t\t\topts.mode === 'fork'\n\t\t\t\t? makeSuiForkRoutables({\n\t\t\t\t\t\tcontainerName: `devstack-${identity.app}-${identity.stack}-sui-fork`,\n\t\t\t\t\t})\n\t\t\t\t: [];\n\t\temitContributions(ctx, [\n\t\t\t// `hasIndexer` (local only) folds the sui-owned indexer-db\n\t\t\t// sidecar into the captured containers; `indexer !== undefined`\n\t\t\t// is the same gate `bootAndEmit`'s caller resolved GraphQL on.\n\t\t\tmakeSnapshotable(opts.mode, identity.app, identity.stack, realChainId, indexer !== undefined),\n\t\t\tmakeCodegenable({\n\t\t\t\tmode: opts.mode,\n\t\t\t\tchainId: realChainId,\n\t\t\t\trpc: value.rpcUrl,\n\t\t\t\tsource: 'default',\n\t\t\t\t...(value.faucetUrl !== null ? { faucet: value.faucetUrl } : {}),\n\t\t\t\t...(value.graphqlUrl !== null ? { graphql: value.graphqlUrl } : {}),\n\t\t\t}),\n\t\t\t{\n\t\t\t\tkind: 'strategy-contributor',\n\t\t\t\tcapabilityKey: chainProbeCapabilityKey(realChainId),\n\t\t\t\tstrategy: value.chainProbe,\n\t\t\t\tautoMounted: true,\n\t\t\t} satisfies StrategyContributorDecl<`chain-probe:${string}`, ChainProbe<SuiProbeKey>>,\n\t\t\t...faucetContribution,\n\t\t\t...localRoutables,\n\t\t\t...forkRoutables,\n\t\t]);\n\t\treturn value;\n\t});\n\n/** The single sui plugin builder. No sibling `dependsOn`: in local mode\n * with the indexer on (the default), `start` provisions the GraphQL\n * indexer DB itself — a sui-owned postgres sidecar — inside the boot\n * scope BEFORE the boot-time validator (so the validator can join its\n * network), then boots + emits. Resolution order:\n * - `indexerDb` present → BYO DB (no sidecar)\n * - `indexer === false` → opt out (RPC + faucet only, GraphQL off)\n * - else (local default) → sui-owned sidecar\n * Non-local modes never touch the indexer (GraphQL off). */\nconst buildSuiPlugin = (opts: SuiOptions) =>\n\tdefinePlugin({\n\t\tid: suiResource.id,\n\t\trole: 'service',\n\t\tsection: 'service',\n\t\tinputIdentity: staticInputIdentity(suiInputIdentity(opts)),\n\t\t// Stack-free codegen: the `codegen` verb derives the committed\n\t\t// `config.ts`'s `network`/`networks` from this hook. Both are\n\t\t// environment/live data (dynamic local rpc port; a real deployment\n\t\t// names a different network), so the committed tree carries\n\t\t// `dep.network`/`Object.fromEntries(networkNames.map(forNetwork))` raw\n\t\t// expressions off the loaded deployment that resolve\n\t\t// at app build/dev time via the injected `__DEVSTACK_DEPLOYMENT__` global —\n\t\t// never literal values. No id-resolver input needed.\n\t\tstaticCodegen: makeStaticCodegen(),\n\t\t// Zero-arg `start` (no `dependsOn`); the substrate supplies the\n\t\t// container runtime + identity via the plugin runtime context.\n\t\tstart: () =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\t// Resolution order: BYO `indexerDb` wins; then an explicit\n\t\t\t\t// `indexer: false` opt-out; then the sui-owned sidecar default.\n\t\t\t\t// Non-local modes never wire the indexer.\n\t\t\t\tif (opts.mode !== 'local' || (opts.indexerDb === undefined && opts.indexer === false)) {\n\t\t\t\t\treturn yield* bootAndEmit(opts, undefined);\n\t\t\t\t}\n\t\t\t\tconst runtime = yield* ContainerRuntimeService;\n\t\t\t\tconst identity = yield* IdentityContext;\n\t\t\t\t// Resolve the validator image ONCE, before the sidecar — single\n\t\t\t\t// source for both the sidecar's configHash (it folds the resolved\n\t\t\t\t// image ref so an image bump resets the indexer DB) and the\n\t\t\t\t// validator container `bootAndEmit` boots below. `resolveImage`\n\t\t\t\t// is deterministic (content-addressed build/pull) so this is the\n\t\t\t\t// SAME ref the validator runs; resolving it here, not twice,\n\t\t\t\t// avoids a redundant build AND any drift from `decideRunAction`.\n\t\t\t\tconst validatorImage = yield* resolveImage(runtime, identity, opts);\n\t\t\t\t// The sidecar boots first; if the validator boot below fails, the\n\t\t\t\t// sidecar's finalizer lingers on the plugin acquire scope until\n\t\t\t\t// teardown/retry. Benign and intentional — the stable name +\n\t\t\t\t// labels mean a retry ADOPTS the existing sidecar container\n\t\t\t\t// (no scope-threading machinery needed).\n\t\t\t\tconst indexer = yield* provisionLocalIndexer(runtime, identity, opts, validatorImage);\n\t\t\t\treturn yield* bootAndEmit(opts, indexer, validatorImage);\n\t\t\t}),\n\t});\n\nconst buildPlugin = <O extends SuiOptions>(opts: O) => buildSuiPlugin(opts);\n\n// ---------------------------------------------------------------------------\n// User-facing factories\n// ---------------------------------------------------------------------------\n\n/** Local Sui shorthand. Network/env selection belongs to the CLI or\n * `defineDevstackWith(...)`; plain `sui()` always means an in-stack\n * local validator (GraphQL/indexer/Postgres on by default via a\n * sui-owned sidecar). */\nexport const sui = <const O extends SuiOptions = { mode: 'local' }>(\n\topts: O = { mode: 'local' } as O,\n) => buildPlugin(opts);\n\n/** Mode-narrowed factory namespace.\n *\n * Usage:\n * const network = { mode: 'local' } as const;\n * suiFor(network).local({...}) // OK\n * suiFor(network).fork({...}) // type error: 'fork' not in 'local' branch\n *\n * The namespace MIRRORS the four mode option records: `local`,\n * `localRpc` (mapped onto the substrate `'local'` branch),\n * `live`, `fork`. */\nexport const suiFor = defineModeNamespace({\n\tlocal: {\n\t\tlocal: <const O extends Omit<SuiLocalOptions, 'mode'>>(opts: O = {} as O) =>\n\t\t\tbuildPlugin({ mode: 'local', ...opts }),\n\t\tlocalRpc: (opts: Omit<SuiLocalRpcOptions, 'mode'>) =>\n\t\t\tbuildPlugin({ mode: 'local-rpc', ...opts }),\n\t},\n\tlive: {\n\t\ttestnet: (opts: Omit<SuiLiveOptions, 'mode' | 'network'> = {}) =>\n\t\t\tbuildPlugin({ mode: 'live', network: 'testnet', ...opts }),\n\t\tmainnet: (opts: Omit<SuiLiveOptions, 'mode' | 'network'> = {}) =>\n\t\t\tbuildPlugin({ mode: 'live', network: 'mainnet', ...opts }),\n\t\tdevnet: (opts: Omit<SuiLiveOptions, 'mode' | 'network'> = {}) =>\n\t\t\tbuildPlugin({ mode: 'live', network: 'devnet', ...opts }),\n\t\tcustom: (opts: Omit<SuiLiveOptions, 'mode' | 'network'>) =>\n\t\t\tbuildPlugin({ mode: 'live', network: 'custom', ...opts }),\n\t},\n\tfork: {\n\t\tmainnet: (opts: Omit<SuiForkOptions, 'mode' | 'upstream'> = {}) =>\n\t\t\tbuildPlugin({ mode: 'fork', upstream: 'mainnet', ...opts }),\n\t\ttestnet: (opts: Omit<SuiForkOptions, 'mode' | 'upstream'> = {}) =>\n\t\t\tbuildPlugin({ mode: 'fork', upstream: 'testnet', ...opts }),\n\t\tdevnet: (opts: Omit<SuiForkOptions, 'mode' | 'upstream'> = {}) =>\n\t\t\tbuildPlugin({ mode: 'fork', upstream: 'devnet', ...opts }),\n\t},\n});\n\n// ---------------------------------------------------------------------------\n// Re-exports for advanced callers (Account/Coin/Wallet/etc.) and for\n// the sibling plugins (Walrus/Seal/Deepbook fork variants).\n// ---------------------------------------------------------------------------\n\nexport type { SuiClient, ForkAdminSurface, WaitForTransactionsReady } from './mode/shared.ts';\nexport type { ResolvedSuiNetwork } from './network-resolver.ts';\nexport type {\n\tNetworkConfig,\n\tNetworkMode,\n\tDevstackNetworkModeRegistry,\n\tDefaultNetwork,\n} from './network-config.ts';\nexport type {\n\tSuiOptions,\n\tSuiLocalOptions,\n\tSuiLocalRpcOptions,\n\tSuiLiveOptions,\n\tSuiForkOptions,\n\tSuiPluginMode,\n} from './mode/spec.ts';\nexport type { SuiNetworkConfigEntry } from './codegen.ts';\nexport type {\n\tSuiError,\n\tSuiPluginError,\n\tSuiCliError,\n\tSuiConfigError,\n\tForkUnsupportedError,\n} from './errors.ts';\n\n// Cross-plugin seams (consumed by Walrus/Seal/Deepbook fork variants\n// and by Account/Coin/Wallet/Package).\nexport {\n\tchainProbeCapabilityKey,\n\ttype ChainProbe,\n\ttype ChainProbeError,\n\ttype ChainProbeMode,\n} from '../../contracts/chain-probe.ts';\nexport {\n\tFORK_UNSUPPORTED_SURFACES,\n\twrapWithForkGuard,\n\ttype ForkLockHolder,\n} from './fork-orchestration.ts';\nexport type { SuiProbeKey, SuiSdkShim } from './chain-probe.ts';\nexport { SuiLogAttr } from './log-attrs.ts';\n\n/** The shape `Transaction.build({ client })` and every `sdk.core.*` call\n * accepts. Re-exported from `@mysten/sui/client` so callers cast\n * `sui.sdk.client as ClientWithCoreApi` without each having to know the\n * SDK subpath. */\nexport type { ClientWithCoreApi } from '@mysten/sui/client';\n// Cross-plugin seams: fork impersonation + chain-build container.\n// Consumed by `action` (Move-call execution against fork) and\n// `package` (publish-to-fork + Move-build orchestration). Consumer\n// plugins import these barrel entries rather than internal modules.\nexport {\n\tbuildForkImpersonationTransactionBytes,\n\tprepareForkImpersonationTransaction,\n\tverifyForkImpersonationSender,\n\tselectSufficientForkCoin,\n\tFORK_IMPERSONATION_GAS_BUDGET,\n\tFORK_IMPERSONATION_GAS_PRICE,\n\ttype ForkGasCoin,\n\ttype ForkImpersonationGasClient,\n} from './fork-transaction.ts';\nexport {\n\tsuiForkFaucetStrategy,\n\ttype SuiForkFaucetStrategyOptions,\n\ttype SuiForkFaucetSerialization,\n} from './fork-faucet-strategy.ts';\nexport {\n\tacquireChainBuildContainer,\n\tcontainerNameForApp,\n\tmoveBuildLockPathFor,\n\tMOVE_BUILD_LOCK_TIMEOUT_MS,\n\ttype ChainBuildContainer,\n\ttype ChainBuildContainerSpec,\n} from './chain-build-container.ts';\n\n// Cross-plugin seams: hoisted exec / move / ledger helpers.\n// Consumed by Account/Action/Coin/Deepbook/Package/Seal/Walrus. These\n// barrel entries route those consumers through the sibling plugin's\n// barrel per the `plugin-boundary` invariant, rather than reaching into\n// the deep `sui/exec`, `sui/move`, and `sui/ledger` internal modules.\nexport {\n\textractExecuteDigest,\n\tformatExecutedFailure,\n\texecuteSuiTx,\n\tisSuiStaleObjectVersionError,\n\ttype ResolvedSigner,\n\ttype ExecutedFailure,\n\ttype ExecutedReceipt,\n\ttype TransactionSignerScope,\n} from './exec/index.ts';\nexport { signAndDispatch } from './exec/sign-and-dispatch.ts';\nexport {\n\thashMoveSources,\n\trunMoveBuild,\n\tscrubLocksHost,\n\twithMoveBuildLock,\n\tDEFAULT_MOVE_BUILD_ENV,\n\ttype BuildOutput,\n\ttype MoveBuildEnv,\n\ttype MoveBuildContainer,\n\ttype MoveBuildError,\n\ttype MoveBuildOptions,\n} from './move/index.ts';\nexport { currentLedgerObjectRef } from './ledger/object-ref.ts';\n"],"mappings":"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"}
|
|
@@ -37,6 +37,7 @@ const moveBuildError = (phase, parts) => new MoveBuildError({
|
|
|
37
37
|
message: parts.message,
|
|
38
38
|
...parts.cause !== void 0 ? { cause: parts.cause } : {}
|
|
39
39
|
});
|
|
40
|
+
const DEFAULT_MOVE_BUILD_ENV = "testnet";
|
|
40
41
|
const stripPinnedSections = (source) => {
|
|
41
42
|
const lines = source.split("\n");
|
|
42
43
|
const out = [];
|
|
@@ -65,7 +66,9 @@ const containerScrubShellScript = (workspaceRoot, moveHomeRoot) => {
|
|
|
65
66
|
return [
|
|
66
67
|
`printf '%s\\n%s\\n%s\\n' ${CONTAINER_SCRUB_AWK_SCRIPT.split("\n").map((l) => `'${l}'`).join(" ")} > /tmp/scrub-move-lock.awk`,
|
|
67
68
|
`find ${workspaceRoot} -type f -name Move.lock -not -path '*/node_modules/*' -not -path '*/.git/*' -exec gawk -i inplace -f /tmp/scrub-move-lock.awk {} ';'`,
|
|
68
|
-
`
|
|
69
|
+
`find ${workspaceRoot} -type f -name Published.toml -not -path '*/node_modules/*' -not -path '*/.git/*' -exec rm -f {} ';'`,
|
|
70
|
+
`[ -d ${moveHomeRoot}/git ] && find ${moveHomeRoot}/git -type f -name Move.lock -not -path '*/.git/*' -exec gawk -i inplace -f /tmp/scrub-move-lock.awk {} ';' || true`,
|
|
71
|
+
`[ -d ${moveHomeRoot}/git ] && find ${moveHomeRoot}/git -type f -name Published.toml -not -path '*/.git/*' -exec rm -f {} ';' || true`
|
|
69
72
|
].join("; ");
|
|
70
73
|
};
|
|
71
74
|
const shellQuote = (s) => `'${s.replaceAll("'", "'\\''")}'`;
|
|
@@ -76,8 +79,9 @@ const extractTrailingJson = (text) => {
|
|
|
76
79
|
if (idx === -1) return trimmed;
|
|
77
80
|
return trimmed.slice(idx);
|
|
78
81
|
};
|
|
79
|
-
const containerInnerScript = (pkgName) => {
|
|
82
|
+
const containerInnerScript = (pkgName, options) => {
|
|
80
83
|
const quotedPkg = shellQuote(pkgName);
|
|
84
|
+
const buildEnv = shellQuote(options?.buildEnv ?? "testnet");
|
|
81
85
|
const scratchRoot = "/tmp/move-build-$$";
|
|
82
86
|
const scratchPkg = `${scratchRoot}/${quotedPkg}`;
|
|
83
87
|
const stage = `rm -rf ${scratchRoot} && mkdir -p ${scratchRoot} && cp -a /workspace/. ${scratchRoot}/`;
|
|
@@ -87,7 +91,7 @@ const containerInnerScript = (pkgName) => {
|
|
|
87
91
|
stage,
|
|
88
92
|
scrub,
|
|
89
93
|
"set +e",
|
|
90
|
-
`sui move build --path ${scratchPkg} -
|
|
94
|
+
`sui move build --path ${scratchPkg} --build-env ${buildEnv} --no-tree-shaking --dump-bytecode-as-base64 --with-unpublished-dependencies`,
|
|
91
95
|
"status=$?",
|
|
92
96
|
"set -e",
|
|
93
97
|
scrub,
|
|
@@ -240,7 +244,7 @@ const ensureMoveHomeMountSource = (moveHome, sourcePath, packageName) => Effect.
|
|
|
240
244
|
})
|
|
241
245
|
}).pipe(Effect.asVoid);
|
|
242
246
|
const buildViaContainerExec = (inputs, bc) => Effect.gen(function* () {
|
|
243
|
-
const result = yield* bc.runBuild(inputs.sourcePath).pipe(Effect.mapError((err) => moveBuildError("build", {
|
|
247
|
+
const result = yield* (inputs.buildEnv === void 0 ? bc.runBuild(inputs.sourcePath) : bc.runBuild(inputs.sourcePath, { buildEnv: inputs.buildEnv })).pipe(Effect.mapError((err) => moveBuildError("build", {
|
|
244
248
|
sourcePath: inputs.sourcePath,
|
|
245
249
|
packageName: inputs.packageName,
|
|
246
250
|
message: `buildContainer.runBuild failed: ${String(err?.cause?.message ?? err)}`,
|
|
@@ -293,7 +297,7 @@ const stageLocalMoveDeps = (packageSrc, stagedPackage, stagingRoot, inputs) => E
|
|
|
293
297
|
});
|
|
294
298
|
const buildViaOneShot = (inputs, runtime, image) => Effect.gen(function* () {
|
|
295
299
|
const pkgName = basename(inputs.sourcePath);
|
|
296
|
-
const inner = containerInnerScript(pkgName);
|
|
300
|
+
const inner = inputs.buildEnv === void 0 ? containerInnerScript(pkgName) : containerInnerScript(pkgName, { buildEnv: inputs.buildEnv });
|
|
297
301
|
const moveHome = join(homedir(), ".move");
|
|
298
302
|
yield* ensureMoveHomeMountSource(moveHome, inputs.sourcePath, inputs.packageName);
|
|
299
303
|
const stagingRoot = yield* Effect.acquireRelease(Effect.tryPromise({
|
|
@@ -350,6 +354,6 @@ const runMoveBuild = (inputs) => {
|
|
|
350
354
|
}));
|
|
351
355
|
};
|
|
352
356
|
//#endregion
|
|
353
|
-
export { DEFAULT_SUI_CLI_VERSION, copyLocalMoveDeps, hashMoveSources, runMoveBuild, scrubLocksHost, shellQuote, suiCliImageBuildContext, withMoveBuildLock };
|
|
357
|
+
export { DEFAULT_MOVE_BUILD_ENV, DEFAULT_SUI_CLI_VERSION, copyLocalMoveDeps, hashMoveSources, runMoveBuild, scrubLocksHost, shellQuote, suiCliImageBuildContext, withMoveBuildLock };
|
|
354
358
|
|
|
355
359
|
//# sourceMappingURL=index.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/plugins/sui/move/index.ts"],"sourcesContent":["// sui-move-build — shared Sui Move build helpers.\n//\n// Lives in `plugins/sui`. Owns the mechanical \"scrub Move.lock → run\n// sui move build → parse bytecode\" path so Move-publishing plugins do\n// not import each other's internals.\n\nimport { createHash } from 'node:crypto';\nimport { cp, lstat, mkdir, mkdtemp, readFile, readdir, rm, writeFile } from 'node:fs/promises';\nimport { homedir, tmpdir } from 'node:os';\nimport { basename, join, relative, resolve, sep } from 'node:path';\n\nimport { Effect, Schema, type Scope, Semaphore } from 'effect';\n\nimport type {\n\tContainerBuildContext,\n\tContainerRuntime,\n\tExecResult,\n\tImageRef,\n} from '../../../contracts/container-runtime.ts';\nimport { contentHash, type ContentHash } from '../../../substrate/brand.ts';\nimport { mintRandomSuffix } from '../../../substrate/runtime/random-suffix.ts';\nimport { decodeJsonTextSync } from '../../../substrate/runtime/runtime-decode.ts';\n\nexport type MoveBuildPhase = 'hash' | 'scrub' | 'build' | 'parse';\n\n// Sui-fork image still builds the CLI from a release tarball at this\n// version (see mode/fork.ts); local mode now bases on sui-tools.\nexport const DEFAULT_SUI_CLI_VERSION = 'devnet-v1.71.0';\n\n// Per-arch sui-tools pin. The tag is NOT a multi-arch manifest list, so\n// the `-arm64` suffix is chosen host-side from `process.arch`.\nconst DEFAULT_SUI_TOOLS_REF = 'eced02468444d429a4e9a2b9622b7bd30a1710d4';\n\nexport const suiCliImageBuildContext = (): ContainerBuildContext => {\n\tconst suffix = process.arch === 'arm64' ? '-arm64' : '';\n\treturn {\n\t\tcontextPath: new URL('../../../../images/', import.meta.url).pathname,\n\t\tdockerfile: 'sui/Dockerfile',\n\t\tfingerprintPaths: ['sui/Dockerfile', 'sui/entrypoint.sh'],\n\t\tbuildArgs: { SUI_TOOLS_IMAGE: `mysten/sui-tools:${DEFAULT_SUI_TOOLS_REF}${suffix}` },\n\t};\n};\n\nexport class MoveBuildError extends Schema.TaggedErrorClass<MoveBuildError>()('MoveBuildError', {\n\tphase: Schema.Literals(['hash', 'scrub', 'build', 'parse']),\n\tsourcePath: Schema.String,\n\tpackageName: Schema.String,\n\tmessage: Schema.String,\n\tcause: Schema.optional(Schema.Defect),\n}) {}\n\nconst moveBuildError = (\n\tphase: MoveBuildPhase,\n\tparts: {\n\t\treadonly sourcePath: string;\n\t\treadonly packageName: string;\n\t\treadonly message: string;\n\t\treadonly cause?: unknown;\n\t},\n): MoveBuildError =>\n\tnew MoveBuildError({\n\t\tphase,\n\t\tsourcePath: parts.sourcePath,\n\t\tpackageName: parts.packageName,\n\t\tmessage: parts.message,\n\t\t...(parts.cause !== undefined ? { cause: parts.cause } : {}),\n\t});\n\nexport interface MoveBuildContainer {\n\treadonly runBuild: (hostPackagePath: string) => Effect.Effect<ExecResult, unknown, Scope.Scope>;\n}\n\nexport interface BuildInputs {\n\treadonly sourcePath: string;\n\treadonly packageName: string;\n\treadonly buildContainer?: MoveBuildContainer;\n\treadonly runtime?: ContainerRuntime;\n\treadonly buildImage?: ImageRef;\n}\n\nexport interface BuildOutput {\n\treadonly modules: ReadonlyArray<Uint8Array>;\n\treadonly dependencies: ReadonlyArray<string>;\n}\n\nexport const stripPinnedSections = (source: string): string => {\n\tconst lines = source.split('\\n');\n\tconst out: Array<string> = [];\n\tlet skipping = false;\n\tfor (const line of lines) {\n\t\tconst trimmed = line.trimStart();\n\t\tif (trimmed.startsWith('[')) {\n\t\t\tconst header = trimmed.replace(/\\s+/g, '');\n\t\t\tif (header.startsWith('[pinned.') || header === '[env]' || header.startsWith('[env.')) {\n\t\t\t\tskipping = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tskipping = false;\n\t\t}\n\t\tif (!skipping) out.push(line);\n\t}\n\tif (skipping && source.endsWith('\\n') && out.at(-1) === '') out.push('');\n\treturn out.join('\\n');\n};\n\nexport const CONTAINER_SCRUB_AWK_SCRIPT = [\n\t'/^\\\\[pinned\\\\./ || /^\\\\[env(\\\\.|\\\\])/ { skip=1; next }',\n\t'/^\\\\[/ && !/^\\\\[pinned\\\\./ && !/^\\\\[env(\\\\.|\\\\])/ { skip=0 }',\n\t'!skip { print }',\n].join('\\n');\n\nexport const containerScrubShellScript = (workspaceRoot: string, moveHomeRoot: string): string => {\n\tconst lines = CONTAINER_SCRUB_AWK_SCRIPT.split('\\n')\n\t\t.map((l) => `'${l}'`)\n\t\t.join(' ');\n\tconst stage = `printf '%s\\\\n%s\\\\n%s\\\\n' ${lines} > /tmp/scrub-move-lock.awk`;\n\tconst findPkg =\n\t\t`find ${workspaceRoot} -type f -name Move.lock ` +\n\t\t`-not -path '*/node_modules/*' -not -path '*/.git/*' ` +\n\t\t`-exec gawk -i inplace -f /tmp/scrub-move-lock.awk {} ';'`;\n\tconst findCache =\n\t\t`[ -d ${moveHomeRoot}/git ] && find ${moveHomeRoot}/git -type f -name Move.lock ` +\n\t\t`-not -path '*/.git/*' ` +\n\t\t`-exec gawk -i inplace -f /tmp/scrub-move-lock.awk {} ';' || true`;\n\treturn [stage, findPkg, findCache].join('; ');\n};\n\nexport const shellQuote = (s: string): string => `'${s.replaceAll(\"'\", \"'\\\\''\")}'`;\n\nexport const extractTrailingJson = (text: string): string => {\n\tconst trimmed = text.trim();\n\tif (trimmed.startsWith('{')) return trimmed;\n\tconst idx = trimmed.lastIndexOf('{');\n\tif (idx === -1) return trimmed;\n\treturn trimmed.slice(idx);\n};\n\nexport const containerInnerScript = (pkgName: string): string => {\n\t// Copy the WHOLE mounted tree into an in-container scratch dir and scrub +\n\t// build the package THERE, never `/workspace/<pkg>` directly. `/workspace`\n\t// is a bind mount of the developer's real source tree (the per-app build\n\t// container in `chain-build-container.ts` mounts the app dir as-is), and the\n\t// scrub's `gawk -i inplace` rewrite of Move.lock would corrupt their\n\t// checked-in pinned deps if run against the mount.\n\t//\n\t// Two reasons we copy `/workspace/.` (the whole tree) rather than only\n\t// `/workspace/<pkgName>`:\n\t// 1. NESTED PACKAGES. `pkgName` is the package path RELATIVE to the\n\t// bind-mounted app dir (e.g. `packages/demo`), so it can contain a\n\t// slash. Copying only `/workspace/<pkgName>` into\n\t// `/tmp/move-build-$$/<pkgName>` would need the intermediate\n\t// `packages/` dir to exist first — it doesn't, and `set -e` would\n\t// abort the build before `sui move build` ran.\n\t// 2. SIBLING LOCAL DEPS. A package can reference siblings via\n\t// `{ local = \"../token\" }`. A scoped copy of only the package subtree\n\t// drops those, failing the build with \"Invalid directory at ../…\".\n\t// Copying the whole tree both materialises the nested path AND carries the\n\t// sibling `../` deps. The mount itself is never rewritten — the in-container\n\t// copy is always the disposable one. Both consumers share this script: the\n\t// exec path (`chain-build-container.ts`) bind-mounts the developer's real app\n\t// dir, and the one-shot path (`buildViaOneShot`) mounts a disposable host-side\n\t// staging copy whose transitive local deps were pre-staged (see\n\t// `stageLocalMoveDeps`); in both cases `/workspace` is copied, not rewritten.\n\t//\n\t// `$$` (the shell PID) scopes the scratch dir per exec so concurrent\n\t// builds of the same package don't share a tree.\n\tconst quotedPkg = shellQuote(pkgName);\n\tconst scratchRoot = '/tmp/move-build-$$';\n\tconst scratchPkg = `${scratchRoot}/${quotedPkg}`;\n\tconst stage = `rm -rf ${scratchRoot} && mkdir -p ${scratchRoot} && cp -a /workspace/. ${scratchRoot}/`;\n\t// Scrub the whole scratch tree AND the mounted Move git cache. The cache\n\t// (`/root/.move/git`) is process-shared mutable state we legitimately want\n\t// scrubbed so a stale pin can't leak into the build; the scratch tree is the\n\t// disposable copy, so scrubbing all of it (package + staged siblings) is safe.\n\tconst scrub = containerScrubShellScript(scratchRoot, '/root/.move');\n\tconst build =\n\t\t`sui move build --path ${scratchPkg} ` +\n\t\t`-e testnet --no-tree-shaking --dump-bytecode-as-base64 ` +\n\t\t`--with-unpublished-dependencies`;\n\tconst cleanup = `rm -rf ${scratchRoot}`;\n\treturn [\n\t\t'set -e',\n\t\tstage,\n\t\tscrub,\n\t\t'set +e',\n\t\tbuild,\n\t\t'status=$?',\n\t\t'set -e',\n\t\tscrub,\n\t\tcleanup,\n\t\t'exit \"$status\"',\n\t].join('; ');\n};\n\nconst isHashedFile = (name: string): boolean =>\n\tname === 'Move.toml' || name === 'Move.lock' || name.endsWith('.move');\n\nconst isSkippedDir = (name: string): boolean =>\n\tname === 'build' || name === 'node_modules' || name === '.git' || name.startsWith('.');\n\nconst collectHashedSources = async (root: string): Promise<ReadonlyArray<string>> => {\n\tconst out: Array<string> = [];\n\tconst walk = async (dir: string): Promise<void> => {\n\t\tlet entries: ReadonlyArray<{\n\t\t\treadonly name: string;\n\t\t\treadonly isDirectory: () => boolean;\n\t\t\treadonly isFile: () => boolean;\n\t\t}> = [];\n\t\ttry {\n\t\t\tentries = await readdir(dir, { withFileTypes: true });\n\t\t} catch {\n\t\t\treturn;\n\t\t}\n\t\tfor (const entry of entries) {\n\t\t\tif (entry.isDirectory()) {\n\t\t\t\tif (isSkippedDir(entry.name)) continue;\n\t\t\t\tawait walk(join(dir, entry.name));\n\t\t\t} else if (entry.isFile() && isHashedFile(entry.name)) {\n\t\t\t\tconst abs = join(dir, entry.name);\n\t\t\t\ttry {\n\t\t\t\t\tconst stat = await lstat(abs);\n\t\t\t\t\tif (stat.isFile() && !stat.isSymbolicLink()) out.push(abs);\n\t\t\t\t} catch {\n\t\t\t\t\t// vanished / unreadable — skip\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n\tawait walk(root);\n\treturn out;\n};\n\nexport const hashMoveSources = (sourcePath: string): Effect.Effect<ContentHash, MoveBuildError> =>\n\tEffect.tryPromise({\n\t\ttry: async (): Promise<ContentHash> => {\n\t\t\tconst files = await collectHashedSources(sourcePath);\n\t\t\tconst sorted = [...files].sort((a, b) =>\n\t\t\t\trelative(sourcePath, a).localeCompare(relative(sourcePath, b)),\n\t\t\t);\n\t\t\tconst hasher = createHash('sha256');\n\t\t\tfor (const abs of sorted) {\n\t\t\t\tconst rel = relative(sourcePath, abs);\n\t\t\t\tconst raw = await readFile(abs, 'utf8');\n\t\t\t\tconst normalised = basename(abs) === 'Move.lock' ? stripPinnedSections(raw) : raw;\n\t\t\t\thasher.update(rel);\n\t\t\t\thasher.update('\\0');\n\t\t\t\thasher.update(normalised);\n\t\t\t\thasher.update('\\0');\n\t\t\t}\n\t\t\treturn contentHash(hasher.digest('hex'));\n\t\t},\n\t\tcatch: (cause): MoveBuildError =>\n\t\t\tmoveBuildError('hash', {\n\t\t\t\tsourcePath,\n\t\t\t\tpackageName: sourcePath,\n\t\t\t\tmessage: `source-tree hash failed: ${String((cause as Error)?.message ?? cause)}`,\n\t\t\t\tcause,\n\t\t\t}),\n\t});\n\nconst expandHome = (p: string): string =>\n\tp.startsWith('~/') ? join(homedir(), p.slice(2)) : p === '~' ? homedir() : p;\n\nconst findMoveLockFiles = async (root: string): Promise<ReadonlyArray<string>> => {\n\tconst out: Array<string> = [];\n\tconst walk = async (dir: string): Promise<void> => {\n\t\tlet entries: ReadonlyArray<{\n\t\t\treadonly name: string;\n\t\t\treadonly isDirectory: () => boolean;\n\t\t\treadonly isFile: () => boolean;\n\t\t}> = [];\n\t\ttry {\n\t\t\tentries = await readdir(dir, { withFileTypes: true });\n\t\t} catch {\n\t\t\treturn;\n\t\t}\n\t\tfor (const entry of entries) {\n\t\t\tif (entry.name === 'node_modules' || entry.name === '.git' || entry.name === 'build') {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst child = join(dir, entry.name);\n\t\t\tif (entry.isDirectory()) {\n\t\t\t\tawait walk(child);\n\t\t\t} else if (entry.isFile() && entry.name === 'Move.lock') {\n\t\t\t\ttry {\n\t\t\t\t\tconst stat = await lstat(child);\n\t\t\t\t\tif (stat.isFile() && !stat.isSymbolicLink()) out.push(child);\n\t\t\t\t} catch {\n\t\t\t\t\t// unreadable / vanished — skip\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n\tawait walk(root);\n\treturn out;\n};\n\nconst scrubOneLockFile = async (path: string): Promise<void> => {\n\tconst original = await readFile(path, 'utf8');\n\tconst scrubbed = stripPinnedSections(original);\n\tif (scrubbed === original) return;\n\tawait writeFile(path, scrubbed, 'utf8');\n};\n\nconst scrubCachedLockFiles = async (root: string): Promise<void> => {\n\tconst cachedLocks = await findMoveLockFiles(root);\n\tfor (const f of cachedLocks) {\n\t\ttry {\n\t\t\tawait scrubOneLockFile(f);\n\t\t} catch {\n\t\t\t// The Docker build scrubs the mounted Move cache again from inside the container.\n\t\t}\n\t}\n};\n\n// Scrub ONLY the shared Move git cache (`~/.move/git`), never the caller's\n// source tree. The package's own Move.lock is scrubbed inside the container on\n// a disposable copy (see `containerInnerScript`), so rewriting the developer's\n// checked-in Move.lock here would corrupt their pinned deps for no benefit. The\n// cache is process-shared mutable state, so scrubbing it host-side keeps a stale\n// pin from leaking into the build.\nexport const scrubLocksHost = (\n\tsourcePath: string,\n\tmoveHomeRoot: string,\n): Effect.Effect<void, MoveBuildError, Scope.Scope> =>\n\tEffect.tryPromise({\n\t\ttry: async () => {\n\t\t\tconst moveHome = expandHome(moveHomeRoot);\n\t\t\tconst gitCache = join(moveHome, 'git');\n\t\t\ttry {\n\t\t\t\tawait lstat(gitCache);\n\t\t\t} catch {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait scrubCachedLockFiles(gitCache);\n\t\t},\n\t\tcatch: (cause): MoveBuildError =>\n\t\t\tmoveBuildError('scrub', {\n\t\t\t\tsourcePath,\n\t\t\t\tpackageName: sourcePath,\n\t\t\t\tmessage: `scrub host locks failed (root=${moveHomeRoot})`,\n\t\t\t\tcause,\n\t\t\t}),\n\t});\n\n// Serialize the host-scrub → build critical section across ALL Move builds in\n// this process. The host `~/.move/git` cache is shared mutable state: every\n// build host-scrubs it (`scrubLocksHost`), the build container bind-mounts it\n// (see `buildViaOneShot`), and `sui move build` writes fresh `[pinned.*]`\n// sections that the in-container post-build scrub strips (Invariant 14). Under\n// concurrent builds those host-side `gawk -i inplace` scrubs collide with a\n// sibling container's live `git clone` into the same cache dir, corrupting the\n// in-flight fetch — only git-resolving builds are exposed, so a package that\n// pulls a Move dep (or the framework) from git is the usual casualty. A\n// single-permit semaphore makes the cache-touching section mutually exclusive\n// while leaving the on-chain publish-tx parallel. In-process only: a global\n// `~/.move` shared across concurrent devstack PROCESSES is a pre-existing\n// condition this does not (and the short-lived `stack.lock` cannot) cover.\nconst moveBuildLock = Semaphore.makeUnsafe(1);\n\n/** Run `effect` while holding the process-wide Move-build permit. Wrap ONLY\n * the host-scrub → build critical section (never the on-chain publish, which\n * must stay parallel) so just one build mutates the shared `~/.move` git\n * cache at a time. Generic over the effect's channels — it neither adds\n * requirements nor changes the error type. */\nexport const withMoveBuildLock = <A, E, R>(\n\teffect: Effect.Effect<A, E, R>,\n): Effect.Effect<A, E, R> => moveBuildLock.withPermit(effect);\n\nconst SuiBuildJsonSchema = Schema.Struct({\n\tmodules: Schema.Array(Schema.String),\n\tdependencies: Schema.Array(Schema.String),\n});\n\nconst decodeBase64Module = (s: string): Uint8Array =>\n\tUint8Array.from(globalThis.Buffer.from(s, 'base64'));\n\nexport const parseBuildOutput = (\n\tstdout: string,\n\tsourcePath: string,\n\tpackageName: string,\n): Effect.Effect<BuildOutput, MoveBuildError> =>\n\tEffect.try({\n\t\ttry: (): BuildOutput => {\n\t\t\tconst trimmed = extractTrailingJson(stdout);\n\t\t\tconst parsed = decodeJsonTextSync(SuiBuildJsonSchema, trimmed, {\n\t\t\t\tsource: 'sui move build stdout',\n\t\t\t\tmessage: 'unexpected sui move build JSON shape',\n\t\t\t\tmkError: (issue) => new Error(issue.message, { cause: issue.cause }),\n\t\t\t});\n\t\t\tconst modules = parsed.modules.map((m) => decodeBase64Module(m));\n\t\t\tconst dependencies = parsed.dependencies;\n\t\t\treturn { modules, dependencies };\n\t\t},\n\t\tcatch: (cause): MoveBuildError =>\n\t\t\tmoveBuildError('parse', {\n\t\t\t\tsourcePath,\n\t\t\t\tpackageName,\n\t\t\t\tmessage: `failed to parse sui move build output: ${String((cause as Error)?.message ?? cause)}`,\n\t\t\t\tcause,\n\t\t\t}),\n\t});\n\nconst promoteNonZero = (\n\top: 'docker exec' | 'docker run --rm',\n\tresult: ExecResult,\n\tsourcePath: string,\n\tpackageName: string,\n): MoveBuildError =>\n\tmoveBuildError('build', {\n\t\tsourcePath,\n\t\tpackageName,\n\t\tmessage:\n\t\t\t`${op} sui move build exited ${result.exitCode} for package \"${packageName}\".\\n` +\n\t\t\t`stderr: ${result.stderr || '(empty)'}\\n` +\n\t\t\t`stdout (tail): ${result.stdout.slice(-400) || '(empty)'}`,\n\t});\n\nconst ensureMoveHomeMountSource = (\n\tmoveHome: string,\n\tsourcePath: string,\n\tpackageName: string,\n): Effect.Effect<void, MoveBuildError> =>\n\tEffect.tryPromise({\n\t\ttry: () => mkdir(moveHome, { recursive: true }),\n\t\tcatch: (cause): MoveBuildError =>\n\t\t\tmoveBuildError('build', {\n\t\t\t\tsourcePath,\n\t\t\t\tpackageName,\n\t\t\t\tmessage: `failed to create Move cache mount source \"${moveHome}\"`,\n\t\t\t\tcause,\n\t\t\t}),\n\t}).pipe(Effect.asVoid);\n\nconst buildViaContainerExec = (\n\tinputs: BuildInputs,\n\tbc: MoveBuildContainer,\n): Effect.Effect<BuildOutput, MoveBuildError, Scope.Scope> =>\n\tEffect.gen(function* () {\n\t\tconst result = yield* bc.runBuild(inputs.sourcePath).pipe(\n\t\t\tEffect.mapError(\n\t\t\t\t(err): MoveBuildError =>\n\t\t\t\t\tmoveBuildError('build', {\n\t\t\t\t\t\tsourcePath: inputs.sourcePath,\n\t\t\t\t\t\tpackageName: inputs.packageName,\n\t\t\t\t\t\tmessage: `buildContainer.runBuild failed: ${String(\n\t\t\t\t\t\t\t(err as { cause?: { message?: string } })?.cause?.message ?? err,\n\t\t\t\t\t\t)}`,\n\t\t\t\t\t\tcause: err,\n\t\t\t\t\t}),\n\t\t\t),\n\t\t);\n\t\tif (result.exitCode !== 0) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tpromoteNonZero('docker exec', result, inputs.sourcePath, inputs.packageName),\n\t\t\t);\n\t\t}\n\t\treturn yield* parseBuildOutput(result.stdout, inputs.sourcePath, inputs.packageName);\n\t});\n\n/** `{ local = \"../path\" }` dependency paths, harvested from a Move.toml. */\nconst LOCAL_MOVE_DEP_RE = /\\blocal\\s*=\\s*\"([^\"]+)\"/g;\n\n/** Copy a package's transitive local-path Move dependencies into a staging\n * tree, preserving each dep's path RELATIVE to the staged package so\n * references like `{ local = \"../token\" }` resolve. Without this, a scoped\n * copy of only the package fails `sui move build` / `sui move summary` with\n * \"Invalid directory at ../…\". Throws if a dep resolves outside `stagingRoot`\n * (the single-mount layout can't represent it). Promise-based so the build\n * (`MoveBuildError`) and summary (`CodegenBindingsFailed`) call sites can\n * wrap it in their own error type. */\nexport const copyLocalMoveDeps = async (\n\tpackageSrc: string,\n\tstagedPackage: string,\n\tstagingRoot: string,\n): Promise<void> => {\n\tconst staged = new Set<string>([packageSrc]);\n\tconst walk = async (srcDir: string, stagedDir: string): Promise<void> => {\n\t\tlet toml: string;\n\t\ttry {\n\t\t\ttoml = await readFile(join(srcDir, 'Move.toml'), 'utf8');\n\t\t} catch {\n\t\t\treturn;\n\t\t}\n\t\tfor (const match of toml.matchAll(LOCAL_MOVE_DEP_RE)) {\n\t\t\tconst rel = match[1]!;\n\t\t\tconst depSrc = resolve(srcDir, rel);\n\t\t\tconst depStaged = resolve(stagedDir, rel);\n\t\t\tif (depStaged !== stagingRoot && !depStaged.startsWith(stagingRoot + sep)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`local Move dependency \"${rel}\" of \"${basename(srcDir)}\" resolves outside the ` +\n\t\t\t\t\t\t`staging root; vendor it under the package tree so it can be staged.`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (staged.has(depSrc)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tstaged.add(depSrc);\n\t\t\tawait cp(depSrc, depStaged, { recursive: true });\n\t\t\tawait walk(depSrc, depStaged);\n\t\t}\n\t};\n\tawait walk(packageSrc, stagedPackage);\n};\n\n/** Build-path wrapper around {@link copyLocalMoveDeps}. */\nconst stageLocalMoveDeps = (\n\tpackageSrc: string,\n\tstagedPackage: string,\n\tstagingRoot: string,\n\tinputs: BuildInputs,\n): Effect.Effect<void, MoveBuildError> =>\n\tEffect.tryPromise({\n\t\ttry: () => copyLocalMoveDeps(packageSrc, stagedPackage, stagingRoot),\n\t\tcatch: (cause): MoveBuildError =>\n\t\t\tmoveBuildError('scrub', {\n\t\t\t\tsourcePath: inputs.sourcePath,\n\t\t\t\tpackageName: inputs.packageName,\n\t\t\t\tmessage: 'failed to stage local Move dependencies for the one-shot build',\n\t\t\t\tcause,\n\t\t\t}),\n\t});\n\nconst buildViaOneShot = (\n\tinputs: BuildInputs,\n\truntime: ContainerRuntime,\n\timage: ImageRef,\n): Effect.Effect<BuildOutput, MoveBuildError, Scope.Scope> =>\n\tEffect.gen(function* () {\n\t\tconst pkgName = basename(inputs.sourcePath);\n\t\tconst inner = containerInnerScript(pkgName);\n\t\tconst moveHome = join(homedir(), '.move');\n\t\tyield* ensureMoveHomeMountSource(moveHome, inputs.sourcePath, inputs.packageName);\n\n\t\t// Stage a scoped copy of the package and mount THAT, never the user's\n\t\t// source tree. The container scrub (`gawk -i inplace`) rewrites Move.lock\n\t\t// in place; pointing it at the developer's checked-in tree corrupts their\n\t\t// pinned deps. The staging dir is scope-bound (acquireRelease removes it on\n\t\t// scope close) and carries a random suffix so two concurrent builds of the\n\t\t// same package don't share a tree. We keep the `/workspace/<pkgName>` layout\n\t\t// so `containerInnerScript(pkgName)` and the mounted Move cache stay unchanged.\n\t\tconst stagingRoot = yield* Effect.acquireRelease(\n\t\t\tEffect.tryPromise({\n\t\t\t\ttry: () => mkdtemp(join(tmpdir(), `move-build-${mintRandomSuffix(12)}-`)),\n\t\t\t\tcatch: (cause): MoveBuildError =>\n\t\t\t\t\tmoveBuildError('scrub', {\n\t\t\t\t\t\tsourcePath: inputs.sourcePath,\n\t\t\t\t\t\tpackageName: inputs.packageName,\n\t\t\t\t\t\tmessage: 'failed to create staging dir for Move build copy',\n\t\t\t\t\t\tcause,\n\t\t\t\t\t}),\n\t\t\t}),\n\t\t\t(dir) => Effect.promise(() => rm(dir, { recursive: true, force: true })),\n\t\t);\n\t\tconst stagedPackage = join(stagingRoot, pkgName);\n\t\tyield* Effect.tryPromise({\n\t\t\ttry: () => cp(inputs.sourcePath, stagedPackage, { recursive: true }),\n\t\t\tcatch: (cause): MoveBuildError =>\n\t\t\t\tmoveBuildError('scrub', {\n\t\t\t\t\tsourcePath: inputs.sourcePath,\n\t\t\t\t\tpackageName: inputs.packageName,\n\t\t\t\t\tmessage: `failed to stage package copy for Move build (staging=${stagedPackage})`,\n\t\t\t\t\tcause,\n\t\t\t\t}),\n\t\t});\n\t\t// Bring the package's transitive local `../` deps into the staging tree\n\t\t// so the in-place one-shot build resolves them (the scoped copy alone\n\t\t// would omit siblings like `{ local = \"../token\" }`).\n\t\tyield* stageLocalMoveDeps(inputs.sourcePath, stagedPackage, stagingRoot, inputs);\n\n\t\tconst result = yield* runtime\n\t\t\t.runOneShot({\n\t\t\t\timage,\n\t\t\t\tentrypoint: 'sh',\n\t\t\t\targv: ['-c', inner],\n\t\t\t\tmounts: [\n\t\t\t\t\t{ source: stagingRoot, target: '/workspace' },\n\t\t\t\t\t{ source: moveHome, target: '/root/.move' },\n\t\t\t\t],\n\t\t\t\ttimeoutMillis: 5 * 60_000,\n\t\t\t})\n\t\t\t.pipe(\n\t\t\t\tEffect.mapError(\n\t\t\t\t\t(err): MoveBuildError =>\n\t\t\t\t\t\tmoveBuildError('build', {\n\t\t\t\t\t\t\tsourcePath: inputs.sourcePath,\n\t\t\t\t\t\t\tpackageName: inputs.packageName,\n\t\t\t\t\t\t\tmessage: `runtime.runOneShot failed: ${err.reason}: ${err.detail}`,\n\t\t\t\t\t\t\tcause: err,\n\t\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t);\n\t\tif (result.exitCode !== 0) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tpromoteNonZero('docker run --rm', result, inputs.sourcePath, inputs.packageName),\n\t\t\t);\n\t\t}\n\t\treturn yield* parseBuildOutput(result.stdout, inputs.sourcePath, inputs.packageName);\n\t});\n\nexport const runMoveBuild = (\n\tinputs: BuildInputs,\n): Effect.Effect<BuildOutput, MoveBuildError, Scope.Scope> => {\n\tif (inputs.buildContainer !== undefined) {\n\t\treturn buildViaContainerExec(inputs, inputs.buildContainer);\n\t}\n\tif (inputs.runtime !== undefined && inputs.buildImage !== undefined) {\n\t\treturn buildViaOneShot(inputs, inputs.runtime, inputs.buildImage);\n\t}\n\treturn Effect.fail(\n\t\tmoveBuildError('build', {\n\t\t\tsourcePath: inputs.sourcePath,\n\t\t\tpackageName: inputs.packageName,\n\t\t\tmessage:\n\t\t\t\t'runMoveBuild: no build container and no fresh-container runtime/image — ' +\n\t\t\t\t'host `sui` CLI path is not available without a routed ChildProcessSpawner',\n\t\t}),\n\t);\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/plugins/sui/move/index.ts"],"sourcesContent":["// sui-move-build — shared Sui Move build helpers.\n//\n// Lives in `plugins/sui`. Owns the mechanical \"scrub Move.lock → run\n// sui move build → parse bytecode\" path so Move-publishing plugins do\n// not import each other's internals.\n\nimport { createHash } from 'node:crypto';\nimport { cp, lstat, mkdir, mkdtemp, readFile, readdir, rm, writeFile } from 'node:fs/promises';\nimport { homedir, tmpdir } from 'node:os';\nimport { basename, join, relative, resolve, sep } from 'node:path';\n\nimport { Effect, Schema, type Scope, Semaphore } from 'effect';\n\nimport type {\n\tContainerBuildContext,\n\tContainerRuntime,\n\tExecResult,\n\tImageRef,\n} from '../../../contracts/container-runtime.ts';\nimport { contentHash, type ContentHash } from '../../../substrate/brand.ts';\nimport { mintRandomSuffix } from '../../../substrate/runtime/random-suffix.ts';\nimport { decodeJsonTextSync } from '../../../substrate/runtime/runtime-decode.ts';\n\nexport type MoveBuildPhase = 'hash' | 'scrub' | 'build' | 'parse';\n\n// Sui-fork image still builds the CLI from a release tarball at this\n// version (see mode/fork.ts); local mode now bases on sui-tools.\nexport const DEFAULT_SUI_CLI_VERSION = 'devnet-v1.71.0';\n\n// Per-arch sui-tools pin. The tag is NOT a multi-arch manifest list, so\n// the `-arm64` suffix is chosen host-side from `process.arch`.\nconst DEFAULT_SUI_TOOLS_REF = 'eced02468444d429a4e9a2b9622b7bd30a1710d4';\n\nexport const suiCliImageBuildContext = (): ContainerBuildContext => {\n\tconst suffix = process.arch === 'arm64' ? '-arm64' : '';\n\treturn {\n\t\tcontextPath: new URL('../../../../images/', import.meta.url).pathname,\n\t\tdockerfile: 'sui/Dockerfile',\n\t\tfingerprintPaths: ['sui/Dockerfile', 'sui/entrypoint.sh'],\n\t\tbuildArgs: { SUI_TOOLS_IMAGE: `mysten/sui-tools:${DEFAULT_SUI_TOOLS_REF}${suffix}` },\n\t};\n};\n\nexport class MoveBuildError extends Schema.TaggedErrorClass<MoveBuildError>()('MoveBuildError', {\n\tphase: Schema.Literals(['hash', 'scrub', 'build', 'parse']),\n\tsourcePath: Schema.String,\n\tpackageName: Schema.String,\n\tmessage: Schema.String,\n\tcause: Schema.optional(Schema.Defect),\n}) {}\n\nconst moveBuildError = (\n\tphase: MoveBuildPhase,\n\tparts: {\n\t\treadonly sourcePath: string;\n\t\treadonly packageName: string;\n\t\treadonly message: string;\n\t\treadonly cause?: unknown;\n\t},\n): MoveBuildError =>\n\tnew MoveBuildError({\n\t\tphase,\n\t\tsourcePath: parts.sourcePath,\n\t\tpackageName: parts.packageName,\n\t\tmessage: parts.message,\n\t\t...(parts.cause !== undefined ? { cause: parts.cause } : {}),\n\t});\n\nexport interface MoveBuildContainer {\n\treadonly runBuild: (\n\t\thostPackagePath: string,\n\t\toptions?: MoveBuildOptions,\n\t) => Effect.Effect<ExecResult, unknown, Scope.Scope>;\n}\n\nexport type MoveBuildEnv = string;\n\nexport interface MoveBuildOptions {\n\treadonly buildEnv?: MoveBuildEnv;\n}\n\nexport const DEFAULT_MOVE_BUILD_ENV: MoveBuildEnv = 'testnet';\n\nexport interface BuildInputs {\n\treadonly sourcePath: string;\n\treadonly packageName: string;\n\treadonly buildContainer?: MoveBuildContainer;\n\treadonly runtime?: ContainerRuntime;\n\treadonly buildImage?: ImageRef;\n\treadonly buildEnv?: MoveBuildEnv;\n}\n\nexport interface BuildOutput {\n\treadonly modules: ReadonlyArray<Uint8Array>;\n\treadonly dependencies: ReadonlyArray<string>;\n}\n\nexport const stripPinnedSections = (source: string): string => {\n\tconst lines = source.split('\\n');\n\tconst out: Array<string> = [];\n\tlet skipping = false;\n\tfor (const line of lines) {\n\t\tconst trimmed = line.trimStart();\n\t\tif (trimmed.startsWith('[')) {\n\t\t\tconst header = trimmed.replace(/\\s+/g, '');\n\t\t\tif (header.startsWith('[pinned.') || header === '[env]' || header.startsWith('[env.')) {\n\t\t\t\tskipping = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tskipping = false;\n\t\t}\n\t\tif (!skipping) out.push(line);\n\t}\n\tif (skipping && source.endsWith('\\n') && out.at(-1) === '') out.push('');\n\treturn out.join('\\n');\n};\n\nexport const CONTAINER_SCRUB_AWK_SCRIPT = [\n\t'/^\\\\[pinned\\\\./ || /^\\\\[env(\\\\.|\\\\])/ { skip=1; next }',\n\t'/^\\\\[/ && !/^\\\\[pinned\\\\./ && !/^\\\\[env(\\\\.|\\\\])/ { skip=0 }',\n\t'!skip { print }',\n].join('\\n');\n\nexport const containerScrubShellScript = (workspaceRoot: string, moveHomeRoot: string): string => {\n\tconst lines = CONTAINER_SCRUB_AWK_SCRIPT.split('\\n')\n\t\t.map((l) => `'${l}'`)\n\t\t.join(' ');\n\tconst stage = `printf '%s\\\\n%s\\\\n%s\\\\n' ${lines} > /tmp/scrub-move-lock.awk`;\n\tconst findPkg =\n\t\t`find ${workspaceRoot} -type f -name Move.lock ` +\n\t\t`-not -path '*/node_modules/*' -not -path '*/.git/*' ` +\n\t\t`-exec gawk -i inplace -f /tmp/scrub-move-lock.awk {} ';'`;\n\tconst findPkgPublished =\n\t\t`find ${workspaceRoot} -type f -name Published.toml ` +\n\t\t`-not -path '*/node_modules/*' -not -path '*/.git/*' ` +\n\t\t`-exec rm -f {} ';'`;\n\tconst findCache =\n\t\t`[ -d ${moveHomeRoot}/git ] && find ${moveHomeRoot}/git -type f -name Move.lock ` +\n\t\t`-not -path '*/.git/*' ` +\n\t\t`-exec gawk -i inplace -f /tmp/scrub-move-lock.awk {} ';' || true`;\n\tconst findCachePublished =\n\t\t`[ -d ${moveHomeRoot}/git ] && find ${moveHomeRoot}/git -type f -name Published.toml ` +\n\t\t`-not -path '*/.git/*' ` +\n\t\t`-exec rm -f {} ';' || true`;\n\treturn [stage, findPkg, findPkgPublished, findCache, findCachePublished].join('; ');\n};\n\nexport const shellQuote = (s: string): string => `'${s.replaceAll(\"'\", \"'\\\\''\")}'`;\n\nexport const extractTrailingJson = (text: string): string => {\n\tconst trimmed = text.trim();\n\tif (trimmed.startsWith('{')) return trimmed;\n\tconst idx = trimmed.lastIndexOf('{');\n\tif (idx === -1) return trimmed;\n\treturn trimmed.slice(idx);\n};\n\nexport const containerInnerScript = (pkgName: string, options?: MoveBuildOptions): string => {\n\t// Copy the WHOLE mounted tree into an in-container scratch dir and scrub +\n\t// build the package THERE, never `/workspace/<pkg>` directly. `/workspace`\n\t// is a bind mount of the developer's real source tree (the per-app build\n\t// container in `chain-build-container.ts` mounts the app dir as-is), and the\n\t// scrub's `gawk -i inplace` rewrite of Move.lock would corrupt their\n\t// checked-in pinned deps if run against the mount.\n\t//\n\t// Two reasons we copy `/workspace/.` (the whole tree) rather than only\n\t// `/workspace/<pkgName>`:\n\t// 1. NESTED PACKAGES. `pkgName` is the package path RELATIVE to the\n\t// bind-mounted app dir (e.g. `packages/demo`), so it can contain a\n\t// slash. Copying only `/workspace/<pkgName>` into\n\t// `/tmp/move-build-$$/<pkgName>` would need the intermediate\n\t// `packages/` dir to exist first — it doesn't, and `set -e` would\n\t// abort the build before `sui move build` ran.\n\t// 2. SIBLING LOCAL DEPS. A package can reference siblings via\n\t// `{ local = \"../token\" }`. A scoped copy of only the package subtree\n\t// drops those, failing the build with \"Invalid directory at ../…\".\n\t// Copying the whole tree both materialises the nested path AND carries the\n\t// sibling `../` deps. The mount itself is never rewritten — the in-container\n\t// copy is always the disposable one. Both consumers share this script: the\n\t// exec path (`chain-build-container.ts`) bind-mounts the developer's real app\n\t// dir, and the one-shot path (`buildViaOneShot`) mounts a disposable host-side\n\t// staging copy whose transitive local deps were pre-staged (see\n\t// `stageLocalMoveDeps`); in both cases `/workspace` is copied, not rewritten.\n\t//\n\t// `$$` (the shell PID) scopes the scratch dir per exec so concurrent\n\t// builds of the same package don't share a tree.\n\tconst quotedPkg = shellQuote(pkgName);\n\tconst buildEnv = shellQuote(options?.buildEnv ?? DEFAULT_MOVE_BUILD_ENV);\n\tconst scratchRoot = '/tmp/move-build-$$';\n\tconst scratchPkg = `${scratchRoot}/${quotedPkg}`;\n\tconst stage = `rm -rf ${scratchRoot} && mkdir -p ${scratchRoot} && cp -a /workspace/. ${scratchRoot}/`;\n\t// Scrub the whole scratch tree AND the mounted Move git cache. The cache\n\t// (`/root/.move/git`) is process-shared mutable state we legitimately want\n\t// scrubbed so a stale pin can't leak into the build; the scratch tree is the\n\t// disposable copy, so scrubbing all of it (package + staged siblings) is safe.\n\tconst scrub = containerScrubShellScript(scratchRoot, '/root/.move');\n\tconst build =\n\t\t`sui move build --path ${scratchPkg} ` +\n\t\t`--build-env ${buildEnv} --no-tree-shaking --dump-bytecode-as-base64 ` +\n\t\t`--with-unpublished-dependencies`;\n\tconst cleanup = `rm -rf ${scratchRoot}`;\n\treturn [\n\t\t'set -e',\n\t\tstage,\n\t\tscrub,\n\t\t'set +e',\n\t\tbuild,\n\t\t'status=$?',\n\t\t'set -e',\n\t\tscrub,\n\t\tcleanup,\n\t\t'exit \"$status\"',\n\t].join('; ');\n};\n\nconst isHashedFile = (name: string): boolean =>\n\tname === 'Move.toml' || name === 'Move.lock' || name.endsWith('.move');\n\nconst isSkippedDir = (name: string): boolean =>\n\tname === 'build' || name === 'node_modules' || name === '.git' || name.startsWith('.');\n\nconst collectHashedSources = async (root: string): Promise<ReadonlyArray<string>> => {\n\tconst out: Array<string> = [];\n\tconst walk = async (dir: string): Promise<void> => {\n\t\tlet entries: ReadonlyArray<{\n\t\t\treadonly name: string;\n\t\t\treadonly isDirectory: () => boolean;\n\t\t\treadonly isFile: () => boolean;\n\t\t}> = [];\n\t\ttry {\n\t\t\tentries = await readdir(dir, { withFileTypes: true });\n\t\t} catch {\n\t\t\treturn;\n\t\t}\n\t\tfor (const entry of entries) {\n\t\t\tif (entry.isDirectory()) {\n\t\t\t\tif (isSkippedDir(entry.name)) continue;\n\t\t\t\tawait walk(join(dir, entry.name));\n\t\t\t} else if (entry.isFile() && isHashedFile(entry.name)) {\n\t\t\t\tconst abs = join(dir, entry.name);\n\t\t\t\ttry {\n\t\t\t\t\tconst stat = await lstat(abs);\n\t\t\t\t\tif (stat.isFile() && !stat.isSymbolicLink()) out.push(abs);\n\t\t\t\t} catch {\n\t\t\t\t\t// vanished / unreadable — skip\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n\tawait walk(root);\n\treturn out;\n};\n\nexport const hashMoveSources = (sourcePath: string): Effect.Effect<ContentHash, MoveBuildError> =>\n\tEffect.tryPromise({\n\t\ttry: async (): Promise<ContentHash> => {\n\t\t\tconst files = await collectHashedSources(sourcePath);\n\t\t\tconst sorted = [...files].sort((a, b) =>\n\t\t\t\trelative(sourcePath, a).localeCompare(relative(sourcePath, b)),\n\t\t\t);\n\t\t\tconst hasher = createHash('sha256');\n\t\t\tfor (const abs of sorted) {\n\t\t\t\tconst rel = relative(sourcePath, abs);\n\t\t\t\tconst raw = await readFile(abs, 'utf8');\n\t\t\t\tconst normalised = basename(abs) === 'Move.lock' ? stripPinnedSections(raw) : raw;\n\t\t\t\thasher.update(rel);\n\t\t\t\thasher.update('\\0');\n\t\t\t\thasher.update(normalised);\n\t\t\t\thasher.update('\\0');\n\t\t\t}\n\t\t\treturn contentHash(hasher.digest('hex'));\n\t\t},\n\t\tcatch: (cause): MoveBuildError =>\n\t\t\tmoveBuildError('hash', {\n\t\t\t\tsourcePath,\n\t\t\t\tpackageName: sourcePath,\n\t\t\t\tmessage: `source-tree hash failed: ${String((cause as Error)?.message ?? cause)}`,\n\t\t\t\tcause,\n\t\t\t}),\n\t});\n\nconst expandHome = (p: string): string =>\n\tp.startsWith('~/') ? join(homedir(), p.slice(2)) : p === '~' ? homedir() : p;\n\nconst findMoveLockFiles = async (root: string): Promise<ReadonlyArray<string>> => {\n\tconst out: Array<string> = [];\n\tconst walk = async (dir: string): Promise<void> => {\n\t\tlet entries: ReadonlyArray<{\n\t\t\treadonly name: string;\n\t\t\treadonly isDirectory: () => boolean;\n\t\t\treadonly isFile: () => boolean;\n\t\t}> = [];\n\t\ttry {\n\t\t\tentries = await readdir(dir, { withFileTypes: true });\n\t\t} catch {\n\t\t\treturn;\n\t\t}\n\t\tfor (const entry of entries) {\n\t\t\tif (entry.name === 'node_modules' || entry.name === '.git' || entry.name === 'build') {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst child = join(dir, entry.name);\n\t\t\tif (entry.isDirectory()) {\n\t\t\t\tawait walk(child);\n\t\t\t} else if (entry.isFile() && entry.name === 'Move.lock') {\n\t\t\t\ttry {\n\t\t\t\t\tconst stat = await lstat(child);\n\t\t\t\t\tif (stat.isFile() && !stat.isSymbolicLink()) out.push(child);\n\t\t\t\t} catch {\n\t\t\t\t\t// unreadable / vanished — skip\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n\tawait walk(root);\n\treturn out;\n};\n\nconst scrubOneLockFile = async (path: string): Promise<void> => {\n\tconst original = await readFile(path, 'utf8');\n\tconst scrubbed = stripPinnedSections(original);\n\tif (scrubbed === original) return;\n\tawait writeFile(path, scrubbed, 'utf8');\n};\n\nconst scrubCachedLockFiles = async (root: string): Promise<void> => {\n\tconst cachedLocks = await findMoveLockFiles(root);\n\tfor (const f of cachedLocks) {\n\t\ttry {\n\t\t\tawait scrubOneLockFile(f);\n\t\t} catch {\n\t\t\t// The Docker build scrubs the mounted Move cache again from inside the container.\n\t\t}\n\t}\n};\n\n// Scrub ONLY the shared Move git cache (`~/.move/git`), never the caller's\n// source tree. The package's own Move.lock is scrubbed inside the container on\n// a disposable copy (see `containerInnerScript`), so rewriting the developer's\n// checked-in Move.lock here would corrupt their pinned deps for no benefit. The\n// cache is process-shared mutable state, so scrubbing it host-side keeps a stale\n// pin from leaking into the build.\nexport const scrubLocksHost = (\n\tsourcePath: string,\n\tmoveHomeRoot: string,\n): Effect.Effect<void, MoveBuildError, Scope.Scope> =>\n\tEffect.tryPromise({\n\t\ttry: async () => {\n\t\t\tconst moveHome = expandHome(moveHomeRoot);\n\t\t\tconst gitCache = join(moveHome, 'git');\n\t\t\ttry {\n\t\t\t\tawait lstat(gitCache);\n\t\t\t} catch {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait scrubCachedLockFiles(gitCache);\n\t\t},\n\t\tcatch: (cause): MoveBuildError =>\n\t\t\tmoveBuildError('scrub', {\n\t\t\t\tsourcePath,\n\t\t\t\tpackageName: sourcePath,\n\t\t\t\tmessage: `scrub host locks failed (root=${moveHomeRoot})`,\n\t\t\t\tcause,\n\t\t\t}),\n\t});\n\n// Serialize the host-scrub → build critical section across ALL Move builds in\n// this process. The host `~/.move/git` cache is shared mutable state: every\n// build host-scrubs it (`scrubLocksHost`), the build container bind-mounts it\n// (see `buildViaOneShot`), and `sui move build` writes fresh `[pinned.*]`\n// sections that the in-container post-build scrub strips (Invariant 14). Under\n// concurrent builds those host-side `gawk -i inplace` scrubs collide with a\n// sibling container's live `git clone` into the same cache dir, corrupting the\n// in-flight fetch — only git-resolving builds are exposed, so a package that\n// pulls a Move dep (or the framework) from git is the usual casualty. A\n// single-permit semaphore makes the cache-touching section mutually exclusive\n// while leaving the on-chain publish-tx parallel. In-process only: a global\n// `~/.move` shared across concurrent devstack PROCESSES is a pre-existing\n// condition this does not (and the short-lived `stack.lock` cannot) cover.\nconst moveBuildLock = Semaphore.makeUnsafe(1);\n\n/** Run `effect` while holding the process-wide Move-build permit. Wrap ONLY\n * the host-scrub → build critical section (never the on-chain publish, which\n * must stay parallel) so just one build mutates the shared `~/.move` git\n * cache at a time. Generic over the effect's channels — it neither adds\n * requirements nor changes the error type. */\nexport const withMoveBuildLock = <A, E, R>(\n\teffect: Effect.Effect<A, E, R>,\n): Effect.Effect<A, E, R> => moveBuildLock.withPermit(effect);\n\nconst SuiBuildJsonSchema = Schema.Struct({\n\tmodules: Schema.Array(Schema.String),\n\tdependencies: Schema.Array(Schema.String),\n});\n\nconst decodeBase64Module = (s: string): Uint8Array =>\n\tUint8Array.from(globalThis.Buffer.from(s, 'base64'));\n\nexport const parseBuildOutput = (\n\tstdout: string,\n\tsourcePath: string,\n\tpackageName: string,\n): Effect.Effect<BuildOutput, MoveBuildError> =>\n\tEffect.try({\n\t\ttry: (): BuildOutput => {\n\t\t\tconst trimmed = extractTrailingJson(stdout);\n\t\t\tconst parsed = decodeJsonTextSync(SuiBuildJsonSchema, trimmed, {\n\t\t\t\tsource: 'sui move build stdout',\n\t\t\t\tmessage: 'unexpected sui move build JSON shape',\n\t\t\t\tmkError: (issue) => new Error(issue.message, { cause: issue.cause }),\n\t\t\t});\n\t\t\tconst modules = parsed.modules.map((m) => decodeBase64Module(m));\n\t\t\tconst dependencies = parsed.dependencies;\n\t\t\treturn { modules, dependencies };\n\t\t},\n\t\tcatch: (cause): MoveBuildError =>\n\t\t\tmoveBuildError('parse', {\n\t\t\t\tsourcePath,\n\t\t\t\tpackageName,\n\t\t\t\tmessage: `failed to parse sui move build output: ${String((cause as Error)?.message ?? cause)}`,\n\t\t\t\tcause,\n\t\t\t}),\n\t});\n\nconst promoteNonZero = (\n\top: 'docker exec' | 'docker run --rm',\n\tresult: ExecResult,\n\tsourcePath: string,\n\tpackageName: string,\n): MoveBuildError =>\n\tmoveBuildError('build', {\n\t\tsourcePath,\n\t\tpackageName,\n\t\tmessage:\n\t\t\t`${op} sui move build exited ${result.exitCode} for package \"${packageName}\".\\n` +\n\t\t\t`stderr: ${result.stderr || '(empty)'}\\n` +\n\t\t\t`stdout (tail): ${result.stdout.slice(-400) || '(empty)'}`,\n\t});\n\nconst ensureMoveHomeMountSource = (\n\tmoveHome: string,\n\tsourcePath: string,\n\tpackageName: string,\n): Effect.Effect<void, MoveBuildError> =>\n\tEffect.tryPromise({\n\t\ttry: () => mkdir(moveHome, { recursive: true }),\n\t\tcatch: (cause): MoveBuildError =>\n\t\t\tmoveBuildError('build', {\n\t\t\t\tsourcePath,\n\t\t\t\tpackageName,\n\t\t\t\tmessage: `failed to create Move cache mount source \"${moveHome}\"`,\n\t\t\t\tcause,\n\t\t\t}),\n\t}).pipe(Effect.asVoid);\n\nconst buildViaContainerExec = (\n\tinputs: BuildInputs,\n\tbc: MoveBuildContainer,\n): Effect.Effect<BuildOutput, MoveBuildError, Scope.Scope> =>\n\tEffect.gen(function* () {\n\t\tconst result = yield* (\n\t\t\tinputs.buildEnv === undefined\n\t\t\t\t? bc.runBuild(inputs.sourcePath)\n\t\t\t\t: bc.runBuild(inputs.sourcePath, { buildEnv: inputs.buildEnv })\n\t\t).pipe(\n\t\t\tEffect.mapError(\n\t\t\t\t(err): MoveBuildError =>\n\t\t\t\t\tmoveBuildError('build', {\n\t\t\t\t\t\tsourcePath: inputs.sourcePath,\n\t\t\t\t\t\tpackageName: inputs.packageName,\n\t\t\t\t\t\tmessage: `buildContainer.runBuild failed: ${String(\n\t\t\t\t\t\t\t(err as { cause?: { message?: string } })?.cause?.message ?? err,\n\t\t\t\t\t\t)}`,\n\t\t\t\t\t\tcause: err,\n\t\t\t\t\t}),\n\t\t\t),\n\t\t);\n\t\tif (result.exitCode !== 0) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tpromoteNonZero('docker exec', result, inputs.sourcePath, inputs.packageName),\n\t\t\t);\n\t\t}\n\t\treturn yield* parseBuildOutput(result.stdout, inputs.sourcePath, inputs.packageName);\n\t});\n\n/** `{ local = \"../path\" }` dependency paths, harvested from a Move.toml. */\nconst LOCAL_MOVE_DEP_RE = /\\blocal\\s*=\\s*\"([^\"]+)\"/g;\n\n/** Copy a package's transitive local-path Move dependencies into a staging\n * tree, preserving each dep's path RELATIVE to the staged package so\n * references like `{ local = \"../token\" }` resolve. Without this, a scoped\n * copy of only the package fails `sui move build` / `sui move summary` with\n * \"Invalid directory at ../…\". Throws if a dep resolves outside `stagingRoot`\n * (the single-mount layout can't represent it). Promise-based so the build\n * (`MoveBuildError`) and summary (`CodegenBindingsFailed`) call sites can\n * wrap it in their own error type. */\nexport const copyLocalMoveDeps = async (\n\tpackageSrc: string,\n\tstagedPackage: string,\n\tstagingRoot: string,\n): Promise<void> => {\n\tconst staged = new Set<string>([packageSrc]);\n\tconst walk = async (srcDir: string, stagedDir: string): Promise<void> => {\n\t\tlet toml: string;\n\t\ttry {\n\t\t\ttoml = await readFile(join(srcDir, 'Move.toml'), 'utf8');\n\t\t} catch {\n\t\t\treturn;\n\t\t}\n\t\tfor (const match of toml.matchAll(LOCAL_MOVE_DEP_RE)) {\n\t\t\tconst rel = match[1]!;\n\t\t\tconst depSrc = resolve(srcDir, rel);\n\t\t\tconst depStaged = resolve(stagedDir, rel);\n\t\t\tif (depStaged !== stagingRoot && !depStaged.startsWith(stagingRoot + sep)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`local Move dependency \"${rel}\" of \"${basename(srcDir)}\" resolves outside the ` +\n\t\t\t\t\t\t`staging root; vendor it under the package tree so it can be staged.`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (staged.has(depSrc)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tstaged.add(depSrc);\n\t\t\tawait cp(depSrc, depStaged, { recursive: true });\n\t\t\tawait walk(depSrc, depStaged);\n\t\t}\n\t};\n\tawait walk(packageSrc, stagedPackage);\n};\n\n/** Build-path wrapper around {@link copyLocalMoveDeps}. */\nconst stageLocalMoveDeps = (\n\tpackageSrc: string,\n\tstagedPackage: string,\n\tstagingRoot: string,\n\tinputs: BuildInputs,\n): Effect.Effect<void, MoveBuildError> =>\n\tEffect.tryPromise({\n\t\ttry: () => copyLocalMoveDeps(packageSrc, stagedPackage, stagingRoot),\n\t\tcatch: (cause): MoveBuildError =>\n\t\t\tmoveBuildError('scrub', {\n\t\t\t\tsourcePath: inputs.sourcePath,\n\t\t\t\tpackageName: inputs.packageName,\n\t\t\t\tmessage: 'failed to stage local Move dependencies for the one-shot build',\n\t\t\t\tcause,\n\t\t\t}),\n\t});\n\nconst buildViaOneShot = (\n\tinputs: BuildInputs,\n\truntime: ContainerRuntime,\n\timage: ImageRef,\n): Effect.Effect<BuildOutput, MoveBuildError, Scope.Scope> =>\n\tEffect.gen(function* () {\n\t\tconst pkgName = basename(inputs.sourcePath);\n\t\tconst inner =\n\t\t\tinputs.buildEnv === undefined\n\t\t\t\t? containerInnerScript(pkgName)\n\t\t\t\t: containerInnerScript(pkgName, { buildEnv: inputs.buildEnv });\n\t\tconst moveHome = join(homedir(), '.move');\n\t\tyield* ensureMoveHomeMountSource(moveHome, inputs.sourcePath, inputs.packageName);\n\n\t\t// Stage a scoped copy of the package and mount THAT, never the user's\n\t\t// source tree. The container scrub (`gawk -i inplace`) rewrites Move.lock\n\t\t// in place; pointing it at the developer's checked-in tree corrupts their\n\t\t// pinned deps. The staging dir is scope-bound (acquireRelease removes it on\n\t\t// scope close) and carries a random suffix so two concurrent builds of the\n\t\t// same package don't share a tree. We keep the `/workspace/<pkgName>` layout\n\t\t// so `containerInnerScript(pkgName)` and the mounted Move cache stay unchanged.\n\t\tconst stagingRoot = yield* Effect.acquireRelease(\n\t\t\tEffect.tryPromise({\n\t\t\t\ttry: () => mkdtemp(join(tmpdir(), `move-build-${mintRandomSuffix(12)}-`)),\n\t\t\t\tcatch: (cause): MoveBuildError =>\n\t\t\t\t\tmoveBuildError('scrub', {\n\t\t\t\t\t\tsourcePath: inputs.sourcePath,\n\t\t\t\t\t\tpackageName: inputs.packageName,\n\t\t\t\t\t\tmessage: 'failed to create staging dir for Move build copy',\n\t\t\t\t\t\tcause,\n\t\t\t\t\t}),\n\t\t\t}),\n\t\t\t(dir) => Effect.promise(() => rm(dir, { recursive: true, force: true })),\n\t\t);\n\t\tconst stagedPackage = join(stagingRoot, pkgName);\n\t\tyield* Effect.tryPromise({\n\t\t\ttry: () => cp(inputs.sourcePath, stagedPackage, { recursive: true }),\n\t\t\tcatch: (cause): MoveBuildError =>\n\t\t\t\tmoveBuildError('scrub', {\n\t\t\t\t\tsourcePath: inputs.sourcePath,\n\t\t\t\t\tpackageName: inputs.packageName,\n\t\t\t\t\tmessage: `failed to stage package copy for Move build (staging=${stagedPackage})`,\n\t\t\t\t\tcause,\n\t\t\t\t}),\n\t\t});\n\t\t// Bring the package's transitive local `../` deps into the staging tree\n\t\t// so the in-place one-shot build resolves them (the scoped copy alone\n\t\t// would omit siblings like `{ local = \"../token\" }`).\n\t\tyield* stageLocalMoveDeps(inputs.sourcePath, stagedPackage, stagingRoot, inputs);\n\n\t\tconst result = yield* runtime\n\t\t\t.runOneShot({\n\t\t\t\timage,\n\t\t\t\tentrypoint: 'sh',\n\t\t\t\targv: ['-c', inner],\n\t\t\t\tmounts: [\n\t\t\t\t\t{ source: stagingRoot, target: '/workspace' },\n\t\t\t\t\t{ source: moveHome, target: '/root/.move' },\n\t\t\t\t],\n\t\t\t\ttimeoutMillis: 5 * 60_000,\n\t\t\t})\n\t\t\t.pipe(\n\t\t\t\tEffect.mapError(\n\t\t\t\t\t(err): MoveBuildError =>\n\t\t\t\t\t\tmoveBuildError('build', {\n\t\t\t\t\t\t\tsourcePath: inputs.sourcePath,\n\t\t\t\t\t\t\tpackageName: inputs.packageName,\n\t\t\t\t\t\t\tmessage: `runtime.runOneShot failed: ${err.reason}: ${err.detail}`,\n\t\t\t\t\t\t\tcause: err,\n\t\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t);\n\t\tif (result.exitCode !== 0) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tpromoteNonZero('docker run --rm', result, inputs.sourcePath, inputs.packageName),\n\t\t\t);\n\t\t}\n\t\treturn yield* parseBuildOutput(result.stdout, inputs.sourcePath, inputs.packageName);\n\t});\n\nexport const runMoveBuild = (\n\tinputs: BuildInputs,\n): Effect.Effect<BuildOutput, MoveBuildError, Scope.Scope> => {\n\tif (inputs.buildContainer !== undefined) {\n\t\treturn buildViaContainerExec(inputs, inputs.buildContainer);\n\t}\n\tif (inputs.runtime !== undefined && inputs.buildImage !== undefined) {\n\t\treturn buildViaOneShot(inputs, inputs.runtime, inputs.buildImage);\n\t}\n\treturn Effect.fail(\n\t\tmoveBuildError('build', {\n\t\t\tsourcePath: inputs.sourcePath,\n\t\t\tpackageName: inputs.packageName,\n\t\t\tmessage:\n\t\t\t\t'runMoveBuild: no build container and no fresh-container runtime/image — ' +\n\t\t\t\t'host `sui` CLI path is not available without a routed ChildProcessSpawner',\n\t\t}),\n\t);\n};\n"],"mappings":";;;;;;;;;AA2BA,MAAa,0BAA0B;AAIvC,MAAM,wBAAwB;AAE9B,MAAa,gCAAuD;CACnE,MAAM,SAAS,QAAQ,SAAS,UAAU,WAAW;CACrD,OAAO;EACN,aAAa,IAAI,IAAI,uBAAuB,OAAO,KAAK,GAAG,CAAC,CAAC;EAC7D,YAAY;EACZ,kBAAkB,CAAC,kBAAkB,mBAAmB;EACxD,WAAW,EAAE,iBAAiB,oBAAoB,wBAAwB,SAAS;CACpF;AACD;AAEA,IAAa,iBAAb,cAAoC,OAAO,iBAAiC,CAAC,CAAC,kBAAkB;CAC/F,OAAO,OAAO,SAAS;EAAC;EAAQ;EAAS;EAAS;CAAO,CAAC;CAC1D,YAAY,OAAO;CACnB,aAAa,OAAO;CACpB,SAAS,OAAO;CAChB,OAAO,OAAO,SAAS,OAAO,MAAM;AACrC,CAAC,CAAC,CAAC,CAAC;AAEJ,MAAM,kBACL,OACA,UAOA,IAAI,eAAe;CAClB;CACA,YAAY,MAAM;CAClB,aAAa,MAAM;CACnB,SAAS,MAAM;CACf,GAAI,MAAM,UAAU,KAAA,IAAY,EAAE,OAAO,MAAM,MAAM,IAAI,CAAC;AAC3D,CAAC;AAeF,MAAa,yBAAuC;AAgBpD,MAAa,uBAAuB,WAA2B;CAC9D,MAAM,QAAQ,OAAO,MAAM,IAAI;CAC/B,MAAM,MAAqB,CAAC;CAC5B,IAAI,WAAW;CACf,KAAK,MAAM,QAAQ,OAAO;EACzB,MAAM,UAAU,KAAK,UAAU;EAC/B,IAAI,QAAQ,WAAW,GAAG,GAAG;GAC5B,MAAM,SAAS,QAAQ,QAAQ,QAAQ,EAAE;GACzC,IAAI,OAAO,WAAW,UAAU,KAAK,WAAW,WAAW,OAAO,WAAW,OAAO,GAAG;IACtF,WAAW;IACX;GACD;GACA,WAAW;EACZ;EACA,IAAI,CAAC,UAAU,IAAI,KAAK,IAAI;CAC7B;CACA,IAAI,YAAY,OAAO,SAAS,IAAI,KAAK,IAAI,GAAG,EAAE,MAAM,IAAI,IAAI,KAAK,EAAE;CACvE,OAAO,IAAI,KAAK,IAAI;AACrB;AAEA,MAAa,6BAA6B;CACzC;CACA;CACA;AACD,CAAC,CAAC,KAAK,IAAI;AAEX,MAAa,6BAA6B,eAAuB,iBAAiC;CAqBjG,OAAO;EAAC,4BApBM,2BAA2B,MAAM,IAAI,CAAC,CAClD,KAAK,MAAM,IAAI,EAAE,EAAE,CAAC,CACpB,KAAK,GACuC,EAAE;EAiBjC,QAfN,cAAc;EAeC,QAXf,cAAc;EAWmB,QAPjC,aAAa,iBAAiB,aAAa;EAOC,QAH5C,aAAa,iBAAiB,aAAa;CAGmB,CAAC,CAAC,KAAK,IAAI;AACnF;AAEA,MAAa,cAAc,MAAsB,IAAI,EAAE,WAAW,KAAK,OAAO,EAAE;AAEhF,MAAa,uBAAuB,SAAyB;CAC5D,MAAM,UAAU,KAAK,KAAK;CAC1B,IAAI,QAAQ,WAAW,GAAG,GAAG,OAAO;CACpC,MAAM,MAAM,QAAQ,YAAY,GAAG;CACnC,IAAI,QAAQ,IAAI,OAAO;CACvB,OAAO,QAAQ,MAAM,GAAG;AACzB;AAEA,MAAa,wBAAwB,SAAiB,YAAuC;CA6B5F,MAAM,YAAY,WAAW,OAAO;CACpC,MAAM,WAAW,WAAW,SAAS,YAAA,SAAkC;CACvE,MAAM,cAAc;CACpB,MAAM,aAAa,GAAG,YAAY,GAAG;CACrC,MAAM,QAAQ,UAAU,YAAY,eAAe,YAAY,yBAAyB,YAAY;CAKpG,MAAM,QAAQ,0BAA0B,aAAa,aAAa;CAMlE,OAAO;EACN;EACA;EACA;EACA;EACA,yBATyB,WAAW,eACrB,SAAS;EASxB;EACA;EACA;EACA,UAVyB;EAWzB;CACD,CAAC,CAAC,KAAK,IAAI;AACZ;AAEA,MAAM,gBAAgB,SACrB,SAAS,eAAe,SAAS,eAAe,KAAK,SAAS,OAAO;AAEtE,MAAM,gBAAgB,SACrB,SAAS,WAAW,SAAS,kBAAkB,SAAS,UAAU,KAAK,WAAW,GAAG;AAEtF,MAAM,uBAAuB,OAAO,SAAiD;CACpF,MAAM,MAAqB,CAAC;CAC5B,MAAM,OAAO,OAAO,QAA+B;EAClD,IAAI,UAIC,CAAC;EACN,IAAI;GACH,UAAU,MAAM,QAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;EACrD,QAAQ;GACP;EACD;EACA,KAAK,MAAM,SAAS,SACnB,IAAI,MAAM,YAAY,GAAG;GACxB,IAAI,aAAa,MAAM,IAAI,GAAG;GAC9B,MAAM,KAAK,KAAK,KAAK,MAAM,IAAI,CAAC;EACjC,OAAO,IAAI,MAAM,OAAO,KAAK,aAAa,MAAM,IAAI,GAAG;GACtD,MAAM,MAAM,KAAK,KAAK,MAAM,IAAI;GAChC,IAAI;IACH,MAAM,OAAO,MAAM,MAAM,GAAG;IAC5B,IAAI,KAAK,OAAO,KAAK,CAAC,KAAK,eAAe,GAAG,IAAI,KAAK,GAAG;GAC1D,QAAQ,CAER;EACD;CAEF;CACA,MAAM,KAAK,IAAI;CACf,OAAO;AACR;AAEA,MAAa,mBAAmB,eAC/B,OAAO,WAAW;CACjB,KAAK,YAAkC;EAEtC,MAAM,SAAS,CAAC,GAAG,MADC,qBAAqB,UAAU,CAC3B,CAAC,CAAC,MAAM,GAAG,MAClC,SAAS,YAAY,CAAC,CAAC,CAAC,cAAc,SAAS,YAAY,CAAC,CAAC,CAC9D;EACA,MAAM,SAAS,WAAW,QAAQ;EAClC,KAAK,MAAM,OAAO,QAAQ;GACzB,MAAM,MAAM,SAAS,YAAY,GAAG;GACpC,MAAM,MAAM,MAAM,SAAS,KAAK,MAAM;GACtC,MAAM,aAAa,SAAS,GAAG,MAAM,cAAc,oBAAoB,GAAG,IAAI;GAC9E,OAAO,OAAO,GAAG;GACjB,OAAO,OAAO,IAAI;GAClB,OAAO,OAAO,UAAU;GACxB,OAAO,OAAO,IAAI;EACnB;EACA,OAAO,YAAY,OAAO,OAAO,KAAK,CAAC;CACxC;CACA,QAAQ,UACP,eAAe,QAAQ;EACtB;EACA,aAAa;EACb,SAAS,4BAA4B,OAAQ,OAAiB,WAAW,KAAK;EAC9E;CACD,CAAC;AACH,CAAC;AAEF,MAAM,cAAc,MACnB,EAAE,WAAW,IAAI,IAAI,KAAK,QAAQ,GAAG,EAAE,MAAM,CAAC,CAAC,IAAI,MAAM,MAAM,QAAQ,IAAI;AAE5E,MAAM,oBAAoB,OAAO,SAAiD;CACjF,MAAM,MAAqB,CAAC;CAC5B,MAAM,OAAO,OAAO,QAA+B;EAClD,IAAI,UAIC,CAAC;EACN,IAAI;GACH,UAAU,MAAM,QAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;EACrD,QAAQ;GACP;EACD;EACA,KAAK,MAAM,SAAS,SAAS;GAC5B,IAAI,MAAM,SAAS,kBAAkB,MAAM,SAAS,UAAU,MAAM,SAAS,SAC5E;GAED,MAAM,QAAQ,KAAK,KAAK,MAAM,IAAI;GAClC,IAAI,MAAM,YAAY,GACrB,MAAM,KAAK,KAAK;QACV,IAAI,MAAM,OAAO,KAAK,MAAM,SAAS,aAC3C,IAAI;IACH,MAAM,OAAO,MAAM,MAAM,KAAK;IAC9B,IAAI,KAAK,OAAO,KAAK,CAAC,KAAK,eAAe,GAAG,IAAI,KAAK,KAAK;GAC5D,QAAQ,CAER;EAEF;CACD;CACA,MAAM,KAAK,IAAI;CACf,OAAO;AACR;AAEA,MAAM,mBAAmB,OAAO,SAAgC;CAC/D,MAAM,WAAW,MAAM,SAAS,MAAM,MAAM;CAC5C,MAAM,WAAW,oBAAoB,QAAQ;CAC7C,IAAI,aAAa,UAAU;CAC3B,MAAM,UAAU,MAAM,UAAU,MAAM;AACvC;AAEA,MAAM,uBAAuB,OAAO,SAAgC;CACnE,MAAM,cAAc,MAAM,kBAAkB,IAAI;CAChD,KAAK,MAAM,KAAK,aACf,IAAI;EACH,MAAM,iBAAiB,CAAC;CACzB,QAAQ,CAER;AAEF;AAQA,MAAa,kBACZ,YACA,iBAEA,OAAO,WAAW;CACjB,KAAK,YAAY;EAEhB,MAAM,WAAW,KADA,WAAW,YACC,GAAG,KAAK;EACrC,IAAI;GACH,MAAM,MAAM,QAAQ;EACrB,QAAQ;GACP;EACD;EACA,MAAM,qBAAqB,QAAQ;CACpC;CACA,QAAQ,UACP,eAAe,SAAS;EACvB;EACA,aAAa;EACb,SAAS,iCAAiC,aAAa;EACvD;CACD,CAAC;AACH,CAAC;AAeF,MAAM,gBAAgB,UAAU,WAAW,CAAC;;;;;;AAO5C,MAAa,qBACZ,WAC4B,cAAc,WAAW,MAAM;AAE5D,MAAM,qBAAqB,OAAO,OAAO;CACxC,SAAS,OAAO,MAAM,OAAO,MAAM;CACnC,cAAc,OAAO,MAAM,OAAO,MAAM;AACzC,CAAC;AAED,MAAM,sBAAsB,MAC3B,WAAW,KAAK,WAAW,OAAO,KAAK,GAAG,QAAQ,CAAC;AAEpD,MAAa,oBACZ,QACA,YACA,gBAEA,OAAO,IAAI;CACV,WAAwB;EAEvB,MAAM,SAAS,mBAAmB,oBADlB,oBAAoB,MACwB,GAAG;GAC9D,QAAQ;GACR,SAAS;GACT,UAAU,UAAU,IAAI,MAAM,MAAM,SAAS,EAAE,OAAO,MAAM,MAAM,CAAC;EACpE,CAAC;EAGD,OAAO;GAAE,SAFO,OAAO,QAAQ,KAAK,MAAM,mBAAmB,CAAC,CAE/C;GAAG,cADG,OAAO;EACG;CAChC;CACA,QAAQ,UACP,eAAe,SAAS;EACvB;EACA;EACA,SAAS,0CAA0C,OAAQ,OAAiB,WAAW,KAAK;EAC5F;CACD,CAAC;AACH,CAAC;AAEF,MAAM,kBACL,IACA,QACA,YACA,gBAEA,eAAe,SAAS;CACvB;CACA;CACA,SACC,GAAG,GAAG,yBAAyB,OAAO,SAAS,gBAAgB,YAAY,cAChE,OAAO,UAAU,UAAU,mBACpB,OAAO,OAAO,MAAM,IAAI,KAAK;AACjD,CAAC;AAEF,MAAM,6BACL,UACA,YACA,gBAEA,OAAO,WAAW;CACjB,WAAW,MAAM,UAAU,EAAE,WAAW,KAAK,CAAC;CAC9C,QAAQ,UACP,eAAe,SAAS;EACvB;EACA;EACA,SAAS,6CAA6C,SAAS;EAC/D;CACD,CAAC;AACH,CAAC,CAAC,CAAC,KAAK,OAAO,MAAM;AAEtB,MAAM,yBACL,QACA,OAEA,OAAO,IAAI,aAAa;CACvB,MAAM,SAAS,QACd,OAAO,aAAa,KAAA,IACjB,GAAG,SAAS,OAAO,UAAU,IAC7B,GAAG,SAAS,OAAO,YAAY,EAAE,UAAU,OAAO,SAAS,CAAC,EAAA,CAC9D,KACD,OAAO,UACL,QACA,eAAe,SAAS;EACvB,YAAY,OAAO;EACnB,aAAa,OAAO;EACpB,SAAS,mCAAmC,OAC1C,KAA0C,OAAO,WAAW,GAC9D;EACA,OAAO;CACR,CAAC,CACH,CACD;CACA,IAAI,OAAO,aAAa,GACvB,OAAO,OAAO,OAAO,KACpB,eAAe,eAAe,QAAQ,OAAO,YAAY,OAAO,WAAW,CAC5E;CAED,OAAO,OAAO,iBAAiB,OAAO,QAAQ,OAAO,YAAY,OAAO,WAAW;AACpF,CAAC;;AAGF,MAAM,oBAAoB;;;;;;;;;AAU1B,MAAa,oBAAoB,OAChC,YACA,eACA,gBACmB;CACnB,MAAM,SAAS,IAAI,IAAY,CAAC,UAAU,CAAC;CAC3C,MAAM,OAAO,OAAO,QAAgB,cAAqC;EACxE,IAAI;EACJ,IAAI;GACH,OAAO,MAAM,SAAS,KAAK,QAAQ,WAAW,GAAG,MAAM;EACxD,QAAQ;GACP;EACD;EACA,KAAK,MAAM,SAAS,KAAK,SAAS,iBAAiB,GAAG;GACrD,MAAM,MAAM,MAAM;GAClB,MAAM,SAAS,QAAQ,QAAQ,GAAG;GAClC,MAAM,YAAY,QAAQ,WAAW,GAAG;GACxC,IAAI,cAAc,eAAe,CAAC,UAAU,WAAW,cAAc,GAAG,GACvE,MAAM,IAAI,MACT,0BAA0B,IAAI,QAAQ,SAAS,MAAM,EAAE,2FAExD;GAED,IAAI,OAAO,IAAI,MAAM,GACpB;GAED,OAAO,IAAI,MAAM;GACjB,MAAM,GAAG,QAAQ,WAAW,EAAE,WAAW,KAAK,CAAC;GAC/C,MAAM,KAAK,QAAQ,SAAS;EAC7B;CACD;CACA,MAAM,KAAK,YAAY,aAAa;AACrC;;AAGA,MAAM,sBACL,YACA,eACA,aACA,WAEA,OAAO,WAAW;CACjB,WAAW,kBAAkB,YAAY,eAAe,WAAW;CACnE,QAAQ,UACP,eAAe,SAAS;EACvB,YAAY,OAAO;EACnB,aAAa,OAAO;EACpB,SAAS;EACT;CACD,CAAC;AACH,CAAC;AAEF,MAAM,mBACL,QACA,SACA,UAEA,OAAO,IAAI,aAAa;CACvB,MAAM,UAAU,SAAS,OAAO,UAAU;CAC1C,MAAM,QACL,OAAO,aAAa,KAAA,IACjB,qBAAqB,OAAO,IAC5B,qBAAqB,SAAS,EAAE,UAAU,OAAO,SAAS,CAAC;CAC/D,MAAM,WAAW,KAAK,QAAQ,GAAG,OAAO;CACxC,OAAO,0BAA0B,UAAU,OAAO,YAAY,OAAO,WAAW;CAShF,MAAM,cAAc,OAAO,OAAO,eACjC,OAAO,WAAW;EACjB,WAAW,QAAQ,KAAK,OAAO,GAAG,cAAc,iBAAiB,EAAE,EAAE,EAAE,CAAC;EACxE,QAAQ,UACP,eAAe,SAAS;GACvB,YAAY,OAAO;GACnB,aAAa,OAAO;GACpB,SAAS;GACT;EACD,CAAC;CACH,CAAC,IACA,QAAQ,OAAO,cAAc,GAAG,KAAK;EAAE,WAAW;EAAM,OAAO;CAAK,CAAC,CAAC,CACxE;CACA,MAAM,gBAAgB,KAAK,aAAa,OAAO;CAC/C,OAAO,OAAO,WAAW;EACxB,WAAW,GAAG,OAAO,YAAY,eAAe,EAAE,WAAW,KAAK,CAAC;EACnE,QAAQ,UACP,eAAe,SAAS;GACvB,YAAY,OAAO;GACnB,aAAa,OAAO;GACpB,SAAS,wDAAwD,cAAc;GAC/E;EACD,CAAC;CACH,CAAC;CAID,OAAO,mBAAmB,OAAO,YAAY,eAAe,aAAa,MAAM;CAE/E,MAAM,SAAS,OAAO,QACpB,WAAW;EACX;EACA,YAAY;EACZ,MAAM,CAAC,MAAM,KAAK;EAClB,QAAQ,CACP;GAAE,QAAQ;GAAa,QAAQ;EAAa,GAC5C;GAAE,QAAQ;GAAU,QAAQ;EAAc,CAC3C;EACA,eAAe,IAAI;CACpB,CAAC,CAAC,CACD,KACA,OAAO,UACL,QACA,eAAe,SAAS;EACvB,YAAY,OAAO;EACnB,aAAa,OAAO;EACpB,SAAS,8BAA8B,IAAI,OAAO,IAAI,IAAI;EAC1D,OAAO;CACR,CAAC,CACH,CACD;CACD,IAAI,OAAO,aAAa,GACvB,OAAO,OAAO,OAAO,KACpB,eAAe,mBAAmB,QAAQ,OAAO,YAAY,OAAO,WAAW,CAChF;CAED,OAAO,OAAO,iBAAiB,OAAO,QAAQ,OAAO,YAAY,OAAO,WAAW;AACpF,CAAC;AAEF,MAAa,gBACZ,WAC6D;CAC7D,IAAI,OAAO,mBAAmB,KAAA,GAC7B,OAAO,sBAAsB,QAAQ,OAAO,cAAc;CAE3D,IAAI,OAAO,YAAY,KAAA,KAAa,OAAO,eAAe,KAAA,GACzD,OAAO,gBAAgB,QAAQ,OAAO,SAAS,OAAO,UAAU;CAEjE,OAAO,OAAO,KACb,eAAe,SAAS;EACvB,YAAY,OAAO;EACnB,aAAa,OAAO;EACpB,SACC;CAEF,CAAC,CACF;AACD"}
|
|
@@ -4,20 +4,24 @@
|
|
|
4
4
|
* envelope's `values['dev-wallet']` channel — NEVER a generated file, and
|
|
5
5
|
* NEVER the secret token (that stays in the `0o600` side-channel).
|
|
6
6
|
*
|
|
7
|
-
* - `walletUrl`
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* - `network`
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* - `
|
|
16
|
-
*
|
|
7
|
+
* - `walletUrl` : the wallet HTTP server's URL (router-fronted
|
|
8
|
+
* host form when available, direct-loopback
|
|
9
|
+
* fallback otherwise).
|
|
10
|
+
* - `network` : the network name the wallet's accounts are
|
|
11
|
+
* scoped to (e.g. `localnet`). The dev wallet
|
|
12
|
+
* derives the wallet-standard chain (`sui:<network>`)
|
|
13
|
+
* from it at the wallet-standard boundary; devstack
|
|
14
|
+
* itself never carries the `sui:`-prefixed form.
|
|
15
|
+
* - `allowedOrigins` : stack-scoped browser origins the wallet server
|
|
16
|
+
* accepts. Non-secret; used only to explain a
|
|
17
|
+
* forbidden raw-loopback page load in the browser.
|
|
18
|
+
* - `protocolPaths` : path constants the adapter reads. Mirrored here
|
|
19
|
+
* so the adapter doesn't depend on a separate import.
|
|
17
20
|
*/
|
|
18
21
|
interface DevWalletConnection {
|
|
19
22
|
readonly walletUrl: string;
|
|
20
23
|
readonly network: string;
|
|
24
|
+
readonly allowedOrigins: ReadonlyArray<string>;
|
|
21
25
|
readonly protocolPaths: {
|
|
22
26
|
readonly health: string;
|
|
23
27
|
readonly accounts: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codegen.mjs","names":[],"sources":["../../../src/plugins/wallet/codegen.ts"],"sourcesContent":["// Wallet plugin — Codegenable contribution.\n//\n// Architecture (15-wallet.md §\"Capabilities PRODUCED\"):\n//\n// - The browser-side dev-wallet adapter is the cross-boundary\n// consumer. It reads the dev-wallet CONNECTION metadata (server\n// URL, network, protocol paths) at startup, constructs a\n// `DevstackSignerAdapter`, and registers it with `@mysten/dapp-kit`'s\n// wallet-standard surface (in user-app bundle code — devstack itself\n// NEVER imports dapp-kit).\n//\n// - The connection metadata rides the deployment ENVELOPE's generic\n// `values['dev-wallet']` channel (NOT a generated file): boot's\n// `assembleDeployment` folds this decl's `idConfigValues` into\n// `deployment.networks[net].values['dev-wallet']`, which the Vite\n// plugin reads (`optionalValue(dep, 'dev-wallet', …)`) to assemble\n// the dev-wallet injection. The decl emits NO standalone file — it\n// is a values-only contribution.\n//\n// SECRET TOKEN (manifest-vs-token threat surface):\n//\n// - The pairing token is NEVER routed through `values` (which lands in\n// the world-readable `deployment.json`). It stays in its `0o600`\n// side-channel file (see `pairing.ts:tokenPath`); the Vite `load`\n// hook runs in Node and reads it by path. Only the NON-secret\n// connection fields (`walletUrl`, `network`, `protocolPaths`) ride
|
|
1
|
+
{"version":3,"file":"codegen.mjs","names":[],"sources":["../../../src/plugins/wallet/codegen.ts"],"sourcesContent":["// Wallet plugin — Codegenable contribution.\n//\n// Architecture (15-wallet.md §\"Capabilities PRODUCED\"):\n//\n// - The browser-side dev-wallet adapter is the cross-boundary\n// consumer. It reads the dev-wallet CONNECTION metadata (server\n// URL, network, protocol paths) at startup, constructs a\n// `DevstackSignerAdapter`, and registers it with `@mysten/dapp-kit`'s\n// wallet-standard surface (in user-app bundle code — devstack itself\n// NEVER imports dapp-kit).\n//\n// - The connection metadata rides the deployment ENVELOPE's generic\n// `values['dev-wallet']` channel (NOT a generated file): boot's\n// `assembleDeployment` folds this decl's `idConfigValues` into\n// `deployment.networks[net].values['dev-wallet']`, which the Vite\n// plugin reads (`optionalValue(dep, 'dev-wallet', …)`) to assemble\n// the dev-wallet injection. The decl emits NO standalone file — it\n// is a values-only contribution.\n//\n// SECRET TOKEN (manifest-vs-token threat surface):\n//\n// - The pairing token is NEVER routed through `values` (which lands in\n// the world-readable `deployment.json`). It stays in its `0o600`\n// side-channel file (see `pairing.ts:tokenPath`); the Vite `load`\n// hook runs in Node and reads it by path. Only the NON-secret\n// connection fields (`walletUrl`, `network`, `allowedOrigins`,\n// `protocolPaths`) ride `values`.\n\nimport type { CodegenableDecl } from '../../contracts/codegenable.ts';\n\nimport { defineSimpleConstExport } from '../internal/codegen-helpers.ts';\n\n// ----------------------------------------------------------------------\n// Connection metadata shape\n// ----------------------------------------------------------------------\n\n/** The NON-secret dev-wallet connection metadata the adapter needs to\n * reach the in-process wallet HTTP server. Routed through the deployment\n * envelope's `values['dev-wallet']` channel — NEVER a generated file, and\n * NEVER the secret token (that stays in the `0o600` side-channel).\n *\n * - `walletUrl` : the wallet HTTP server's URL (router-fronted\n * host form when available, direct-loopback\n * fallback otherwise).\n * - `network` : the network name the wallet's accounts are\n * scoped to (e.g. `localnet`). The dev wallet\n * derives the wallet-standard chain (`sui:<network>`)\n * from it at the wallet-standard boundary; devstack\n * itself never carries the `sui:`-prefixed form.\n * - `allowedOrigins` : stack-scoped browser origins the wallet server\n * accepts. Non-secret; used only to explain a\n * forbidden raw-loopback page load in the browser.\n * - `protocolPaths` : path constants the adapter reads. Mirrored here\n * so the adapter doesn't depend on a separate import.\n */\nexport interface DevWalletConnection {\n\treadonly walletUrl: string;\n\treadonly network: string;\n\treadonly allowedOrigins: ReadonlyArray<string>;\n\treadonly protocolPaths: {\n\t\treadonly health: string;\n\t\treadonly accounts: string;\n\t\treadonly signTransaction: string;\n\t\treadonly signPersonalMessage: string;\n\t};\n}\n\n/** The deployment-values namespace + key the dev-wallet connection rides.\n * Shared between the producer (this decl's `idConfigValues`) and the\n * Vite plugin's `optionalValue(dep, DEV_WALLET_VALUES_NAMESPACE,\n * DEV_WALLET_VALUES_KEY)` reader so the two can never drift. */\nexport const DEV_WALLET_VALUES_NAMESPACE = 'dev-wallet' as const;\nexport const DEV_WALLET_VALUES_KEY = 'connection' as const;\n\n// ----------------------------------------------------------------------\n// Decl construction\n// ----------------------------------------------------------------------\n\n/**\n * Construct the Codegenable contribution.\n *\n * Values-only: emits NO standalone file. It carries the non-secret\n * dev-wallet connection metadata through `aggregate.idConfigValues`, which\n * boot's `assembleDeployment` folds into the deployment envelope's\n * `values['dev-wallet'].connection`. The secret token is never routed here\n * — it stays in the `0o600` side-channel file (`pairing.ts:tokenPath`).\n */\nexport const makeWalletCodegen = (\n\tconnection: DevWalletConnection,\n): CodegenableDecl<'dev-wallet-connection'> =>\n\tdefineSimpleConstExport({\n\t\temitterName: 'dev-wallet-connection',\n\t\t// Never written: `aggregateOnly` skips the standalone file, and the\n\t\t// aggregate carries ONLY `idConfigValues` (no `bucket` file emit at\n\t\t// `assembleDeployment`, which special-cases `config.ts` / `accounts.ts`).\n\t\toutputPath: 'dev-wallet.ts',\n\t\texportName: 'devWallet',\n\t\tvalue: connection,\n\t\taggregateOnly: true,\n\t\taggregate: {\n\t\t\t// A neutral bucket name: `assembleDeployment` only PROJECTS the\n\t\t\t// `config.ts` / `accounts.ts` buckets into the typed deployment\n\t\t\t// fields; every other bucket contributes via `idConfigValues` only.\n\t\t\t// The committed-tree emit cycle never sees this decl (boot emits\n\t\t\t// only the STATIC id-free contributions + the deployment file).\n\t\t\tbucket: 'dev-wallet.ts',\n\t\t\tkind: 'dev-wallet',\n\t\t\t// Pass-through: the connection rides `idConfigValues`, so the\n\t\t\t// projection contributes nothing to a file bucket.\n\t\t\tproject: () => null,\n\t\t\t// The non-secret connection metadata folded into the deployment\n\t\t\t// envelope's `values['dev-wallet'].connection` by `assembleDeployment`.\n\t\t\tidConfigValues: {\n\t\t\t\t[DEV_WALLET_VALUES_NAMESPACE]: {\n\t\t\t\t\t[DEV_WALLET_VALUES_KEY]: connection,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t});\n"],"mappings":";;;;;;AAuEA,MAAa,8BAA8B;AAC3C,MAAa,wBAAwB;;;;;;;;;;AAerC,MAAa,qBACZ,eAEA,wBAAwB;CACvB,aAAa;CAIb,YAAY;CACZ,YAAY;CACZ,OAAO;CACP,eAAe;CACf,WAAW;EAMV,QAAQ;EACR,MAAM;EAGN,eAAe;EAGf,gBAAgB,GACd,8BAA8B,GAC7B,wBAAwB,WAC1B,EACD;CACD;AACD,CAAC"}
|