@mysten-incubation/devstack 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/README.md +1 -1
  2. package/dashboard-ui/assets/{grpc-Clz0oOtc.js → grpc-CpkDu4SA.js} +1 -1
  3. package/dashboard-ui/assets/{index-Cmqv9kiU.js → index-jLPRmjst.js} +2 -2
  4. package/dashboard-ui/index.html +1 -1
  5. package/dist/api/inference-network.d.mts +5 -5
  6. package/dist/api/inference-network.mjs +5 -5
  7. package/dist/api/inference-network.mjs.map +1 -1
  8. package/dist/api/run-stack-internal.mjs +1 -3
  9. package/dist/api/run-stack-internal.mjs.map +1 -1
  10. package/dist/build-integrations/dapp-kit/index.d.mts +12 -0
  11. package/dist/build-integrations/dapp-kit/index.mjs +14 -1
  12. package/dist/build-integrations/dapp-kit/index.mjs.map +1 -1
  13. package/dist/build-integrations/playwright/errors.d.mts +1 -1
  14. package/dist/build-integrations/playwright/errors.mjs.map +1 -1
  15. package/dist/build-integrations/playwright/index.d.mts +2 -2
  16. package/dist/build-integrations/playwright/index.mjs +2 -2
  17. package/dist/build-integrations/playwright/wallet-context.d.mts +2 -1
  18. package/dist/build-integrations/playwright/wallet-context.mjs +141 -41
  19. package/dist/build-integrations/playwright/wallet-context.mjs.map +1 -1
  20. package/dist/build-integrations/runtime/dapp-kit-slot.mjs +1 -1
  21. package/dist/build-integrations/runtime/dapp-kit-slot.mjs.map +1 -1
  22. package/dist/build-integrations/vite/index.d.mts +54 -42
  23. package/dist/build-integrations/vite/index.mjs +239 -106
  24. package/dist/build-integrations/vite/index.mjs.map +1 -1
  25. package/dist/build-integrations/vitest/global-setup.mjs +8 -8
  26. package/dist/build-integrations/vitest/global-setup.mjs.map +1 -1
  27. package/dist/cli/main.mjs +3 -2
  28. package/dist/cli/main.mjs.map +1 -1
  29. package/dist/cli/wirings/apply.mjs +0 -1
  30. package/dist/cli/wirings/apply.mjs.map +1 -1
  31. package/dist/cli/wirings/codegen.mjs +2 -3
  32. package/dist/cli/wirings/codegen.mjs.map +1 -1
  33. package/dist/cli/wirings/dump-deployment.mjs +141 -0
  34. package/dist/cli/wirings/dump-deployment.mjs.map +1 -0
  35. package/dist/cli/wirings/snapshot.mjs +0 -1
  36. package/dist/cli/wirings/snapshot.mjs.map +1 -1
  37. package/dist/contracts/codegenable.d.mts +22 -25
  38. package/dist/contracts/codegenable.mjs +35 -4
  39. package/dist/contracts/codegenable.mjs.map +1 -1
  40. package/dist/contracts/config-bindings.mjs +43 -18
  41. package/dist/contracts/config-bindings.mjs.map +1 -1
  42. package/dist/index.mjs +1 -1
  43. package/dist/orchestrators/boot.d.mts +0 -10
  44. package/dist/orchestrators/boot.mjs +19 -59
  45. package/dist/orchestrators/boot.mjs.map +1 -1
  46. package/dist/orchestrators/codegen/bindings.mjs.map +1 -1
  47. package/dist/orchestrators/codegen/config-runtime.mjs +137 -112
  48. package/dist/orchestrators/codegen/config-runtime.mjs.map +1 -1
  49. package/dist/orchestrators/codegen/deployment-network-file.mjs +130 -0
  50. package/dist/orchestrators/codegen/deployment-network-file.mjs.map +1 -0
  51. package/dist/orchestrators/codegen/deployment-strict.mjs +77 -0
  52. package/dist/orchestrators/codegen/deployment-strict.mjs.map +1 -0
  53. package/dist/orchestrators/codegen/deployment.d.mts +63 -0
  54. package/dist/orchestrators/codegen/deployment.mjs +153 -0
  55. package/dist/orchestrators/codegen/deployment.mjs.map +1 -0
  56. package/dist/orchestrators/codegen/errors.mjs +7 -9
  57. package/dist/orchestrators/codegen/errors.mjs.map +1 -1
  58. package/dist/orchestrators/codegen/format.mjs +20 -1
  59. package/dist/orchestrators/codegen/format.mjs.map +1 -1
  60. package/dist/orchestrators/codegen/paths.mjs +1 -8
  61. package/dist/orchestrators/codegen/paths.mjs.map +1 -1
  62. package/dist/orchestrators/codegen/service.mjs +354 -141
  63. package/dist/orchestrators/codegen/service.mjs.map +1 -1
  64. package/dist/orchestrators/layers.mjs +0 -1
  65. package/dist/orchestrators/layers.mjs.map +1 -1
  66. package/dist/orchestrators/network-options.d.mts +37 -35
  67. package/dist/orchestrators/network-options.mjs +13 -10
  68. package/dist/orchestrators/network-options.mjs.map +1 -1
  69. package/dist/plugins/account/codegen.d.mts +3 -4
  70. package/dist/plugins/account/codegen.mjs +5 -7
  71. package/dist/plugins/account/codegen.mjs.map +1 -1
  72. package/dist/plugins/account/index.mjs +1 -3
  73. package/dist/plugins/account/index.mjs.map +1 -1
  74. package/dist/plugins/coin/codegen.mjs +5 -5
  75. package/dist/plugins/coin/codegen.mjs.map +1 -1
  76. package/dist/plugins/coin/index.mjs.map +1 -1
  77. package/dist/plugins/dashboard/schema/root.mjs +3 -1
  78. package/dist/plugins/dashboard/schema/root.mjs.map +1 -1
  79. package/dist/plugins/deepbook/codegen.mjs +4 -4
  80. package/dist/plugins/deepbook/codegen.mjs.map +1 -1
  81. package/dist/plugins/deepbook/index.mjs.map +1 -1
  82. package/dist/plugins/internal/codegen-helpers.mjs +0 -1
  83. package/dist/plugins/internal/codegen-helpers.mjs.map +1 -1
  84. package/dist/plugins/package/codegen.d.mts +1 -12
  85. package/dist/plugins/package/codegen.mjs +43 -25
  86. package/dist/plugins/package/codegen.mjs.map +1 -1
  87. package/dist/plugins/package/dep-resolution.mjs +1 -1
  88. package/dist/plugins/package/dep-resolution.mjs.map +1 -1
  89. package/dist/plugins/package/index.d.mts +14 -11
  90. package/dist/plugins/package/index.mjs +6 -7
  91. package/dist/plugins/package/index.mjs.map +1 -1
  92. package/dist/plugins/seal/codegen.mjs +3 -3
  93. package/dist/plugins/seal/codegen.mjs.map +1 -1
  94. package/dist/plugins/seal/index.mjs.map +1 -1
  95. package/dist/plugins/sui/codegen.mjs +5 -4
  96. package/dist/plugins/sui/codegen.mjs.map +1 -1
  97. package/dist/plugins/sui/index.mjs +1 -2
  98. package/dist/plugins/sui/index.mjs.map +1 -1
  99. package/dist/plugins/wallet/codegen.d.mts +8 -12
  100. package/dist/plugins/wallet/codegen.mjs +22 -15
  101. package/dist/plugins/wallet/codegen.mjs.map +1 -1
  102. package/dist/plugins/wallet/index.d.mts +4 -3
  103. package/dist/plugins/wallet/index.mjs +1 -1
  104. package/dist/plugins/wallet/index.mjs.map +1 -1
  105. package/dist/plugins/wallet/service.d.mts +2 -2
  106. package/dist/plugins/wallet/service.mjs +2 -3
  107. package/dist/plugins/wallet/service.mjs.map +1 -1
  108. package/dist/plugins/walrus/codegen.mjs +4 -4
  109. package/dist/plugins/walrus/codegen.mjs.map +1 -1
  110. package/dist/plugins/walrus/index.mjs +1 -1
  111. package/dist/plugins/walrus/index.mjs.map +1 -1
  112. package/dist/substrate/manifest.d.mts +17 -22
  113. package/dist/substrate/manifest.mjs +4 -6
  114. package/dist/substrate/manifest.mjs.map +1 -1
  115. package/dist/substrate/plugin-ctx.d.mts +6 -6
  116. package/dist/substrate/plugin-ctx.mjs.map +1 -1
  117. package/dist/substrate/runtime/cross-process/liveness.mjs.map +1 -1
  118. package/dist/substrate/runtime/manifest/manifest.mjs.map +1 -1
  119. package/dist/substrate/runtime/paths.mjs +1 -1
  120. package/dist/surfaces/cli/command-tree.mjs +6 -6
  121. package/dist/surfaces/cli/command-tree.mjs.map +1 -1
  122. package/dist/surfaces/cli/index.mjs +6 -4
  123. package/dist/surfaces/cli/index.mjs.map +1 -1
  124. package/dist/surfaces/tui/plain-renderer.mjs.map +1 -1
  125. package/package.json +1 -1
  126. package/dist/cli/wirings/dump-ids.mjs +0 -86
  127. package/dist/cli/wirings/dump-ids.mjs.map +0 -1
  128. package/dist/orchestrators/codegen/id-config.d.mts +0 -1
  129. package/dist/orchestrators/codegen/id-config.mjs +0 -107
  130. package/dist/orchestrators/codegen/id-config.mjs.map +0 -1
  131. package/dist/orchestrators/codegen/output-location.mjs +0 -32
  132. package/dist/orchestrators/codegen/output-location.mjs.map +0 -1
@@ -7,28 +7,29 @@ const CONFIG_RUNTIME_SOURCE = `// THIS FILE IS AUTO-GENERATED BY @mysten/devstac
7
7
  // \`devstack codegen\`. Apps consume codegen output; codegen output never
8
8
  // imports from devstack.
9
9
  //
10
- // Loud-failing on-chain id resolver. The committed \`config.ts\` carries no
11
- // ids; it resolves each id through \`resolveId(...)\` here, which reads the
12
- // build-time-injected \`__DEVSTACK_IDS__\` global. Run \`devstack up\` for
13
- // local dev (the Vite plugin injects the live ids), or point the build at a
14
- // committed id-config file for a real deployment.
10
+ // On-chain deployment resolver. The committed \`config.ts\`
11
+ // carries no ids; it resolves each id through the deployment API here
12
+ // (\`loadDeployment()\` / \`requireId(dep, ...)\`), which reads the build-time-
13
+ // injected \`__DEVSTACK_DEPLOYMENT__\` envelope. Run \`devstack up\` for local
14
+ // dev (the Vite plugin injects the live deployment), or commit a per-network
15
+ // \`deployments/<net>.ts\` for a real deployment.
15
16
 
16
17
  const UNRESOLVED_ID =
17
18
  '0x0000000000000000000000000000000000000000000000000000000000000000';
18
19
 
19
- // Node-only runtime fallback for the on-chain ids. The browser/dev/prod
20
- // path injects \`__DEVSTACK_IDS__\` via the Vite \`define\` (replaced at
20
+ // Node-only runtime fallback for the injected deployment. The browser/dev/prod
21
+ // path injects \`__DEVSTACK_DEPLOYMENT__\` via the Vite \`define\` (replaced at
21
22
  // transform time); this fallback ONLY engages when that define is absent —
22
23
  // i.e. under Node/vitest, where the \`define\` bakes to \`null\` because the
23
24
  // test stack is booted by the vitest \`globalSetup\` AFTER the Vite config
24
25
  // (and so this module) is evaluated. The devstack vitest \`globalSetup\`
25
- // publishes the booted stack's id-config FILE path as
26
- // \`process.env.DEVSTACK_IDS_FILE\`; we read+parse it lazily here. Guarded so
27
- // browser bundles (no \`process\`, no \`node:fs\`) never touch it. Returns
28
- // \`null\` on any miss, so \`ids()\` throws its actionable error as before.
29
- // Minimal LOCAL shapes (no \`@types/node\` dependency — this file is also
30
- // type-checked in browser tsconfigs that don't load node types). We reach
31
- // \`process\` through \`globalThis\` and feature-test every member.
26
+ // publishes the booted stack's deployment FILE path as
27
+ // \`process.env.DEVSTACK_DEPLOYMENT_FILE\`; we read+parse the ENVELOPE lazily
28
+ // here. Guarded so browser bundles (no \`process\`, no \`node:fs\`) never touch
29
+ // it. Returns \`null\` on any miss, so the resolvers throw their actionable
30
+ // error as before. Minimal LOCAL shapes (no \`@types/node\` dependency — this
31
+ // file is also type-checked in browser tsconfigs that don't load node types).
32
+ // We reach \`process\` through \`globalThis\` and feature-test every member.
32
33
  interface NodeProcessLike {
33
34
  readonly env?: { readonly [k: string]: string | undefined };
34
35
  readonly getBuiltinModule?: (id: string) => unknown;
@@ -37,15 +38,15 @@ interface NodeFsLike {
37
38
  readonly existsSync: (p: string) => boolean;
38
39
  readonly readFileSync: (p: string, enc: 'utf8') => string;
39
40
  }
40
- let nodeFallbackCache: DevstackIds | null | undefined;
41
- const readNodeFallbackIds = (): DevstackIds | null => {
41
+ let nodeFallbackCache: DevstackDeployment | null | undefined;
42
+ const readNodeFallbackDeployment = (): DevstackDeployment | null => {
42
43
  if (nodeFallbackCache !== undefined) return nodeFallbackCache;
43
44
  nodeFallbackCache = null;
44
45
  try {
45
46
  const proc = (globalThis as { process?: NodeProcessLike }).process;
46
47
  if (proc === undefined) return null;
47
- const idsFile = proc.env?.DEVSTACK_IDS_FILE;
48
- if (idsFile === undefined || idsFile.length === 0) return null;
48
+ const file = proc.env?.DEVSTACK_DEPLOYMENT_FILE;
49
+ if (file === undefined || file.length === 0) return null;
49
50
  // \`process.getBuiltinModule\` (Node >=22.3) loads \`node:fs\`
50
51
  // synchronously without an \`import\`/\`require\` literal a browser
51
52
  // bundler would try to resolve — this whole branch is unreachable in
@@ -53,153 +54,177 @@ const readNodeFallbackIds = (): DevstackIds | null => {
53
54
  const getBuiltin = proc.getBuiltinModule;
54
55
  if (typeof getBuiltin !== 'function') return null;
55
56
  const fs = getBuiltin('node:fs') as NodeFsLike;
56
- if (!fs.existsSync(idsFile)) return null;
57
- nodeFallbackCache = JSON.parse(fs.readFileSync(idsFile, 'utf8')) as DevstackIds;
57
+ if (!fs.existsSync(file)) return null;
58
+ nodeFallbackCache = JSON.parse(fs.readFileSync(file, 'utf8')) as DevstackDeployment;
58
59
  return nodeFallbackCache;
59
60
  } catch {
60
61
  return null;
61
62
  }
62
63
  };
63
64
 
64
- /** The injected ids, or — under Node with no injected define — the
65
- * \`DEVSTACK_IDS_FILE\` fallback. \`null\` when neither is available. */
66
- const injectedIds = (): DevstackIds | null => {
67
- const injected = typeof __DEVSTACK_IDS__ === 'undefined' ? null : __DEVSTACK_IDS__;
65
+ /** The injected deployment envelope, or — under Node with no injected define —
66
+ * the \`DEVSTACK_DEPLOYMENT_FILE\` fallback. \`null\` when neither is
67
+ * available. */
68
+ const injectedDeployment = (): DevstackDeployment | null => {
69
+ const injected =
70
+ typeof __DEVSTACK_DEPLOYMENT__ === 'undefined' ? null : __DEVSTACK_DEPLOYMENT__;
68
71
  if (injected !== null && injected !== undefined) return injected;
69
- return readNodeFallbackIds();
72
+ return readNodeFallbackDeployment();
70
73
  };
71
74
 
72
- /** One network connection entry — \`rpc\` is the load-bearing field the app
73
- * reads synchronously; \`chainId\`/\`faucet\`/\`graphql\` are optional
74
- * diagnostics (mirrors \`IdConfigNetworkSchema\`). */
75
- export interface DevstackNetworkEntry {
75
+ /** One network's full resolved deployment connection fields (\`rpc\` is the
76
+ * load-bearing one the app reads synchronously; \`chainId\`/\`faucet\`/
77
+ * \`graphql\` are optional diagnostics) plus the on-chain ids/values for that
78
+ * network. The per-network UNIT of the multi-network envelope; hand-writable
79
+ * for production. */
80
+ export interface NetworkDeployment {
81
+ readonly network?: string;
76
82
  readonly rpc: string;
77
83
  readonly chainId?: string;
78
84
  readonly faucet?: string | null;
79
85
  readonly graphql?: string | null;
80
- }
81
-
82
- /** Shape of the injected ids. \`null\` when no stack is running and no
83
- * id-config file was supplied at build time. */
84
- export interface DevstackIds {
85
- readonly network: string;
86
- readonly networks: { readonly [name: string]: DevstackNetworkEntry };
86
+ /** Marks a live LOCAL network (a \`devstack up\` stack). Production builds
87
+ * ship only non-local networks. */
88
+ readonly local?: boolean;
87
89
  readonly packages: {
88
90
  readonly [name: string]: { readonly id: string; readonly objects?: { readonly [k: string]: string } };
89
91
  };
90
- readonly accounts: { readonly [name: string]: string };
91
- readonly mvrOverrides: { readonly [mvrPlaceholder: string]: string };
92
- /** Generic resolver channel: \`values[namespace][key]\` carries
93
- * arbitrary plugin JSON the typed fields above can't (deepbook pool
94
- * ids, coin types, walrus/seal endpoints). Optional — an older
95
- * injected blob (no \`values\`) still satisfies the typed resolvers. */
92
+ readonly mvrOverrides: {
93
+ readonly packages: { readonly [mvrPlaceholder: string]: string };
94
+ readonly types: { readonly [namedType: string]: string };
95
+ };
96
+ /** Generic resolver channel: \`values[namespace][key]\` carries arbitrary
97
+ * plugin JSON the typed fields above can't (deepbook pool ids, coin
98
+ * types, walrus/seal endpoints). Optional — an older injected unit (no
99
+ * \`values\`) still satisfies the typed resolvers. */
96
100
  readonly values?: { readonly [namespace: string]: { readonly [key: string]: unknown } };
97
101
  }
98
102
 
103
+ /** The injected multi-network ENVELOPE: every network this build supports,
104
+ * keyed by name, plus the default the app opens on. \`null\` when no stack is
105
+ * running and no committed deployment was supplied at build time. */
106
+ export interface DevstackDeployment {
107
+ readonly defaultNetwork: string;
108
+ readonly networks: { readonly [name: string]: NetworkDeployment };
109
+ /** DEV-only account name → address map. Network-AGNOSTIC (keypair gen
110
+ * needs no network), so it lives on the envelope, NOT per-network. Only
111
+ * present when running THROUGH devstack (dev serve); a real prod
112
+ * deployment carries none. Optional — \`resolveAccounts()\` defaults to
113
+ * \`{}\` when absent. */
114
+ readonly accounts?: { readonly [name: string]: string };
115
+ }
116
+
117
+ /** Accessor returned by \`loadDeployment()\` — the default network, the set of
118
+ * available network names (for dapp-kit's network list), and a per-network
119
+ * lookup that throws when the network is unknown. */
120
+ export interface LoadedDeployment {
121
+ readonly defaultNetwork: string;
122
+ readonly networkNames: readonly string[];
123
+ readonly forNetwork: (network: string) => NetworkDeployment;
124
+ }
125
+
99
126
  declare global {
100
127
  // Injected by the devstack Vite plugin via \`define\`. \`null\` when no
101
- // ids are available (no stack up + no id-config file).
102
- const __DEVSTACK_IDS__: DevstackIds | null | undefined;
128
+ // deployment is available (no stack up + no committed deployment).
129
+ const __DEVSTACK_DEPLOYMENT__: DevstackDeployment | null | undefined;
103
130
  }
104
131
 
105
132
  /** Thrown when a generated config value needs an on-chain id but none was
106
- * injected — actionable, not a silent zero. */
133
+ * injected. */
107
134
  export class DevstackConfigMissingError extends Error {
108
135
  constructor(detail: string) {
109
136
  super(
110
137
  \`[devstack] \${detail}\\n\` +
111
- \`No on-chain ids are available. For local dev, run \\\`devstack up\\\` \` +
112
- \`(the Vite dev server injects the live ids). For a production build, \` +
113
- \`point the build at the known deployment's committed id-config file \` +
114
- \`(the Vite plugin \\\`ids\\\` option, or \\\`DEVSTACK_IDS_FILE\\\`) before \` +
115
- \`\\\`pnpm build\\\`. See your example README's "Deploy to a real network" \` +
116
- \`section.\`,
138
+ \`No on-chain deployment is available. For local dev, run \\\`devstack up\\\` \` +
139
+ \`(the Vite dev server injects the live deployment). For a production build, \` +
140
+ \`commit the known deployment's per-network \\\`deployments/<net>.ts\\\` \` +
141
+ \`(wired into the Vite plugin's \\\`deployments\\\` option, or pointed at via \` +
142
+ \`\\\`DEVSTACK_DEPLOYMENT_FILE\\\`) before \\\`pnpm build\\\`. See your example \` +
143
+ \`README's "Deploy to a real network" section.\`,
117
144
  );
118
145
  this.name = 'DevstackConfigMissingError';
119
146
  }
120
147
  }
121
148
 
122
- const ids = (): DevstackIds => {
123
- const injected = injectedIds();
149
+ /** Wrap a resolved \`DevstackDeployment\` envelope in the \`LoadedDeployment\`
150
+ * accessor the app consumes. */
151
+ const loadedFrom = (deployment: DevstackDeployment): LoadedDeployment => ({
152
+ defaultNetwork: deployment.defaultNetwork,
153
+ networkNames: Object.keys(deployment.networks),
154
+ forNetwork: (network) => {
155
+ const entry = deployment.networks[network];
156
+ if (entry === undefined) {
157
+ throw new DevstackConfigMissingError(\`network "\${network}" has no deployment\`);
158
+ }
159
+ return entry;
160
+ },
161
+ });
162
+
163
+ /** Load the injected deployment envelope. Throws once here when nothing
164
+ * was injected; the app then reads typed fields off \`forNetwork(net)\`. */
165
+ export const loadDeployment = (): LoadedDeployment => {
166
+ const injected = injectedDeployment();
124
167
  if (injected === null || injected === undefined) {
125
- throw new DevstackConfigMissingError('on-chain ids were never injected');
168
+ throw new DevstackConfigMissingError('no deployment was ever injected');
126
169
  }
127
- return injected;
170
+ return loadedFrom(injected);
128
171
  };
129
172
 
130
- /** Resolve the active-network id for an MVR placeholder (\`@local/<slug>\`).
131
- * Throws \`DevstackConfigMissingError\` when the id is absent or the
132
- * all-zero sentinel. */
133
- export const resolveId = (mvrPlaceholder: string): string => {
134
- const id = ids().mvrOverrides[mvrPlaceholder];
173
+ /** Non-throwing sibling of \`loadDeployment\` returns \`null\` instead of
174
+ * throwing when nothing was injected. For dev-only consumers (e.g. the dev
175
+ * wallet) that must no-op gracefully when no stack is running. */
176
+ export const loadDeploymentOptional = (): LoadedDeployment | null => {
177
+ const injected = injectedDeployment();
178
+ if (injected === null || injected === undefined) return null;
179
+ return loadedFrom(injected);
180
+ };
181
+
182
+ /** The DEV-only account name → address map off the injected envelope.
183
+ * Accounts are network-AGNOSTIC dev identities (envelope-level, never
184
+ * per-network), injected only when running THROUGH devstack. Returns \`{}\`
185
+ * — without throwing — when no deployment was injected or none carries
186
+ * accounts (a pure prod build), so a dev-only consumer (e.g. the dev
187
+ * wallet) no-ops gracefully. */
188
+ export const resolveAccounts = (): { readonly [name: string]: string } => {
189
+ const injected = injectedDeployment();
190
+ return injected?.accounts ?? {};
191
+ };
192
+
193
+ /** Resolve a package id for an MVR placeholder off a loaded deployment.
194
+ * Throws when the id is missing or unresolved. */
195
+ export const requireId = (deployment: NetworkDeployment, mvrPlaceholder: string): string => {
196
+ const id = deployment.mvrOverrides.packages[mvrPlaceholder];
135
197
  if (id === undefined || id === UNRESOLVED_ID) {
136
198
  throw new DevstackConfigMissingError(\`id for "\${mvrPlaceholder}" is unresolved\`);
137
199
  }
138
200
  return id;
139
201
  };
140
202
 
141
- /** Resolve the active network name (the \`networks.<name>\` key the app
142
- * reads). Environment/live data the local stack picks a dynamic value
143
- * and a real deployment names a different network — so it is injected,
144
- * never baked into the committed tree. Throws when no ids were injected. */
145
- export const resolveNetwork = (): string => ids().network;
146
-
147
- /** Resolve the network connection map (\`{ [name]: DevstackNetworkEntry }\`).
148
- * The local rpc carries a dynamic port; a real deployment supplies its own —
149
- * so this is injected, never baked. Throws when no ids were injected. */
150
- export const resolveNetworks = (): { readonly [name: string]: DevstackNetworkEntry } =>
151
- ids().networks;
152
-
153
- /** Resolve the ACTIVE network's connection entry —
154
- * \`resolveNetworks()[resolveNetwork()]\` with a non-undefined return type and
155
- * a loud throw. Removes the \`config.networks[config.network]\` index-signature
156
- * footgun: the app reads \`.rpc\` etc. off a guaranteed entry instead of
157
- * threading an \`undefined\` past the index lookup. Throws
158
- * \`DevstackConfigMissingError\` when no ids were injected or the active
159
- * network has no entry in the injected map. */
160
- export const resolveActiveNetwork = (): DevstackNetworkEntry => {
161
- const network = resolveNetwork();
162
- const entry = resolveNetworks()[network];
163
- if (entry === undefined) {
164
- throw new DevstackConfigMissingError(\`active network "\${network}" has no entry\`);
165
- }
166
- return entry;
167
- };
168
-
169
- /** Resolve a generic plugin value (\`values[namespace][key]\`). The committed
170
- * \`config.ts\` calls this for any live plugin value that isn't a package id
171
- * or the network — deepbook pool ids, coin types, walrus/seal endpoints.
172
- * Throws \`DevstackConfigMissingError\` when no ids were injected, the value
173
- * is missing, or it is the all-zero sentinel. */
174
- export const resolveValue = <T = unknown>(namespace: string, key: string): T => {
175
- const values = ids().values ?? {};
176
- const namespaceValues = values[namespace];
203
+ /** Resolve a generic plugin value (\`values[namespace][key]\`) off a loaded
204
+ * deployment. Throws when the value is missing or unresolved. */
205
+ export const requireValue = <T = unknown>(
206
+ deployment: NetworkDeployment,
207
+ namespace: string,
208
+ key: string,
209
+ ): T => {
210
+ const namespaceValues = deployment.values?.[namespace];
177
211
  const value = namespaceValues === undefined ? undefined : namespaceValues[key];
178
212
  if (value === undefined || value === UNRESOLVED_ID) {
179
- throw new DevstackConfigMissingError(
180
- \`value "\${namespace}.\${key}" is unresolved\`,
181
- );
213
+ throw new DevstackConfigMissingError(\`value "\${namespace}.\${key}" is unresolved\`);
182
214
  }
183
215
  return value as T;
184
216
  };
185
217
 
186
- /** Non-throwing variant of \`resolveValue\`: returns \`undefined\` instead of
187
- * throwing \`DevstackConfigMissingError\` when no ids were injected, the value
188
- * is missing, or it is the all-zero sentinel. For discovery-only ids the app
189
- * GATES on (e.g. a coin's \`treasuryCapId\`/\`metadataId\`, only known after a
190
- * live publish) rather than the load-bearing fields that should fail loud. */
191
- export const resolveValueOptional = <T = unknown>(
218
+ /** Non-throwing sibling of \`requireValue\` for discovery-only fields the app
219
+ * gates on (returns \`undefined\` when missing, unresolved, or absent). */
220
+ export const optionalValue = <T = unknown>(
221
+ deployment: NetworkDeployment,
192
222
  namespace: string,
193
223
  key: string,
194
224
  ): T | undefined => {
195
- const injected = injectedIds();
196
- const values = injected?.values ?? {};
197
- const namespaceValues = values[namespace];
225
+ const namespaceValues = deployment.values?.[namespace];
198
226
  const value = namespaceValues === undefined ? undefined : namespaceValues[key];
199
- if (value === undefined || value === UNRESOLVED_ID) {
200
- return undefined;
201
- }
202
- return value as T;
227
+ return value === undefined || value === UNRESOLVED_ID ? undefined : (value as T);
203
228
  };
204
229
  `;
205
230
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"config-runtime.mjs","names":[],"sources":["../../../src/orchestrators/codegen/config-runtime.ts"],"sourcesContent":["// The FIXED `config-runtime.ts` source the codegen verb emits into the\n// committed `src/generated` tree.\n//\n// This is NOT routed through the literal renderer (`format.ts`) — it is a\n// constant string, emitted verbatim. It is the loud-failing id resolver\n// the committed `config.ts` calls: `config.ts` carries NO on-chain id\n// (those are LOADED CONFIG DATA, not codegen output), so each id is a\n// `resolveId('@local/<slug>')` call into this module, which reads the\n// build-time-injected `__DEVSTACK_IDS__` global and THROWS a clear,\n// actionable error when the id is absent or the all-zero sentinel.\n//\n// `__DEVSTACK_IDS__` is injected synchronously by the devstack Vite plugin\n// (`build-integrations/vite/index.ts`) via `define`:\n// - dev (stack up): the live `.devstack/stacks/<stack>/devstack-ids.json`.\n// - prod build: a committed id-config FILE (same schema) — the Vite\n// plugin `ids` option or a `DEVSTACK_IDS_FILE` path.\n// - neither: `null` → `resolveId` throws at access time.\n\n/** Output path (relative to the generated tree) of the emitted resolver. */\nexport const CONFIG_RUNTIME_OUTPUT_PATH = 'config-runtime.ts';\n\n/** The verbatim source emitted as `src/generated/config-runtime.ts`. */\nexport const CONFIG_RUNTIME_SOURCE = `// THIS FILE IS AUTO-GENERATED BY @mysten/devstack.\n// Do not edit by hand — your changes will be overwritten by the next\n// \\`devstack codegen\\`. Apps consume codegen output; codegen output never\n// imports from devstack.\n//\n// Loud-failing on-chain id resolver. The committed \\`config.ts\\` carries no\n// ids; it resolves each id through \\`resolveId(...)\\` here, which reads the\n// build-time-injected \\`__DEVSTACK_IDS__\\` global. Run \\`devstack up\\` for\n// local dev (the Vite plugin injects the live ids), or point the build at a\n// committed id-config file for a real deployment.\n\nconst UNRESOLVED_ID =\n\t'0x0000000000000000000000000000000000000000000000000000000000000000';\n\n// Node-only runtime fallback for the on-chain ids. The browser/dev/prod\n// path injects \\`__DEVSTACK_IDS__\\` via the Vite \\`define\\` (replaced at\n// transform time); this fallback ONLY engages when that define is absent —\n// i.e. under Node/vitest, where the \\`define\\` bakes to \\`null\\` because the\n// test stack is booted by the vitest \\`globalSetup\\` AFTER the Vite config\n// (and so this module) is evaluated. The devstack vitest \\`globalSetup\\`\n// publishes the booted stack's id-config FILE path as\n// \\`process.env.DEVSTACK_IDS_FILE\\`; we read+parse it lazily here. Guarded so\n// browser bundles (no \\`process\\`, no \\`node:fs\\`) never touch it. Returns\n// \\`null\\` on any miss, so \\`ids()\\` throws its actionable error as before.\n// Minimal LOCAL shapes (no \\`@types/node\\` dependency — this file is also\n// type-checked in browser tsconfigs that don't load node types). We reach\n// \\`process\\` through \\`globalThis\\` and feature-test every member.\ninterface NodeProcessLike {\n\treadonly env?: { readonly [k: string]: string | undefined };\n\treadonly getBuiltinModule?: (id: string) => unknown;\n}\ninterface NodeFsLike {\n\treadonly existsSync: (p: string) => boolean;\n\treadonly readFileSync: (p: string, enc: 'utf8') => string;\n}\nlet nodeFallbackCache: DevstackIds | null | undefined;\nconst readNodeFallbackIds = (): DevstackIds | null => {\n\tif (nodeFallbackCache !== undefined) return nodeFallbackCache;\n\tnodeFallbackCache = null;\n\ttry {\n\t\tconst proc = (globalThis as { process?: NodeProcessLike }).process;\n\t\tif (proc === undefined) return null;\n\t\tconst idsFile = proc.env?.DEVSTACK_IDS_FILE;\n\t\tif (idsFile === undefined || idsFile.length === 0) return null;\n\t\t// \\`process.getBuiltinModule\\` (Node >=22.3) loads \\`node:fs\\`\n\t\t// synchronously without an \\`import\\`/\\`require\\` literal a browser\n\t\t// bundler would try to resolve — this whole branch is unreachable in\n\t\t// the browser (no \\`process\\`), so node:fs never enters the bundle.\n\t\tconst getBuiltin = proc.getBuiltinModule;\n\t\tif (typeof getBuiltin !== 'function') return null;\n\t\tconst fs = getBuiltin('node:fs') as NodeFsLike;\n\t\tif (!fs.existsSync(idsFile)) return null;\n\t\tnodeFallbackCache = JSON.parse(fs.readFileSync(idsFile, 'utf8')) as DevstackIds;\n\t\treturn nodeFallbackCache;\n\t} catch {\n\t\treturn null;\n\t}\n};\n\n/** The injected ids, or — under Node with no injected define — the\n * \\`DEVSTACK_IDS_FILE\\` fallback. \\`null\\` when neither is available. */\nconst injectedIds = (): DevstackIds | null => {\n\tconst injected = typeof __DEVSTACK_IDS__ === 'undefined' ? null : __DEVSTACK_IDS__;\n\tif (injected !== null && injected !== undefined) return injected;\n\treturn readNodeFallbackIds();\n};\n\n/** One network connection entry — \\`rpc\\` is the load-bearing field the app\n * reads synchronously; \\`chainId\\`/\\`faucet\\`/\\`graphql\\` are optional\n * diagnostics (mirrors \\`IdConfigNetworkSchema\\`). */\nexport interface DevstackNetworkEntry {\n\treadonly rpc: string;\n\treadonly chainId?: string;\n\treadonly faucet?: string | null;\n\treadonly graphql?: string | null;\n}\n\n/** Shape of the injected ids. \\`null\\` when no stack is running and no\n * id-config file was supplied at build time. */\nexport interface DevstackIds {\n\treadonly network: string;\n\treadonly networks: { readonly [name: string]: DevstackNetworkEntry };\n\treadonly packages: {\n\t\treadonly [name: string]: { readonly id: string; readonly objects?: { readonly [k: string]: string } };\n\t};\n\treadonly accounts: { readonly [name: string]: string };\n\treadonly mvrOverrides: { readonly [mvrPlaceholder: string]: string };\n\t/** Generic resolver channel: \\`values[namespace][key]\\` carries\n\t * arbitrary plugin JSON the typed fields above can't (deepbook pool\n\t * ids, coin types, walrus/seal endpoints). Optional — an older\n\t * injected blob (no \\`values\\`) still satisfies the typed resolvers. */\n\treadonly values?: { readonly [namespace: string]: { readonly [key: string]: unknown } };\n}\n\ndeclare global {\n\t// Injected by the devstack Vite plugin via \\`define\\`. \\`null\\` when no\n\t// ids are available (no stack up + no id-config file).\n\tconst __DEVSTACK_IDS__: DevstackIds | null | undefined;\n}\n\n/** Thrown when a generated config value needs an on-chain id but none was\n * injected — actionable, not a silent zero. */\nexport class DevstackConfigMissingError extends Error {\n\tconstructor(detail: string) {\n\t\tsuper(\n\t\t\t\\`[devstack] \\${detail}\\\\n\\` +\n\t\t\t\t\\`No on-chain ids are available. For local dev, run \\\\\\`devstack up\\\\\\` \\` +\n\t\t\t\t\\`(the Vite dev server injects the live ids). For a production build, \\` +\n\t\t\t\t\\`point the build at the known deployment's committed id-config file \\` +\n\t\t\t\t\\`(the Vite plugin \\\\\\`ids\\\\\\` option, or \\\\\\`DEVSTACK_IDS_FILE\\\\\\`) before \\` +\n\t\t\t\t\\`\\\\\\`pnpm build\\\\\\`. See your example README's \"Deploy to a real network\" \\` +\n\t\t\t\t\\`section.\\`,\n\t\t);\n\t\tthis.name = 'DevstackConfigMissingError';\n\t}\n}\n\nconst ids = (): DevstackIds => {\n\tconst injected = injectedIds();\n\tif (injected === null || injected === undefined) {\n\t\tthrow new DevstackConfigMissingError('on-chain ids were never injected');\n\t}\n\treturn injected;\n};\n\n/** Resolve the active-network id for an MVR placeholder (\\`@local/<slug>\\`).\n * Throws \\`DevstackConfigMissingError\\` when the id is absent or the\n * all-zero sentinel. */\nexport const resolveId = (mvrPlaceholder: string): string => {\n\tconst id = ids().mvrOverrides[mvrPlaceholder];\n\tif (id === undefined || id === UNRESOLVED_ID) {\n\t\tthrow new DevstackConfigMissingError(\\`id for \"\\${mvrPlaceholder}\" is unresolved\\`);\n\t}\n\treturn id;\n};\n\n/** Resolve the active network name (the \\`networks.<name>\\` key the app\n * reads). Environment/live data — the local stack picks a dynamic value\n * and a real deployment names a different network — so it is injected,\n * never baked into the committed tree. Throws when no ids were injected. */\nexport const resolveNetwork = (): string => ids().network;\n\n/** Resolve the network connection map (\\`{ [name]: DevstackNetworkEntry }\\`).\n * The local rpc carries a dynamic port; a real deployment supplies its own —\n * so this is injected, never baked. Throws when no ids were injected. */\nexport const resolveNetworks = (): { readonly [name: string]: DevstackNetworkEntry } =>\n\tids().networks;\n\n/** Resolve the ACTIVE network's connection entry —\n * \\`resolveNetworks()[resolveNetwork()]\\` with a non-undefined return type and\n * a loud throw. Removes the \\`config.networks[config.network]\\` index-signature\n * footgun: the app reads \\`.rpc\\` etc. off a guaranteed entry instead of\n * threading an \\`undefined\\` past the index lookup. Throws\n * \\`DevstackConfigMissingError\\` when no ids were injected or the active\n * network has no entry in the injected map. */\nexport const resolveActiveNetwork = (): DevstackNetworkEntry => {\n\tconst network = resolveNetwork();\n\tconst entry = resolveNetworks()[network];\n\tif (entry === undefined) {\n\t\tthrow new DevstackConfigMissingError(\\`active network \"\\${network}\" has no entry\\`);\n\t}\n\treturn entry;\n};\n\n/** Resolve a generic plugin value (\\`values[namespace][key]\\`). The committed\n * \\`config.ts\\` calls this for any live plugin value that isn't a package id\n * or the network — deepbook pool ids, coin types, walrus/seal endpoints.\n * Throws \\`DevstackConfigMissingError\\` when no ids were injected, the value\n * is missing, or it is the all-zero sentinel. */\nexport const resolveValue = <T = unknown>(namespace: string, key: string): T => {\n\tconst values = ids().values ?? {};\n\tconst namespaceValues = values[namespace];\n\tconst value = namespaceValues === undefined ? undefined : namespaceValues[key];\n\tif (value === undefined || value === UNRESOLVED_ID) {\n\t\tthrow new DevstackConfigMissingError(\n\t\t\t\\`value \"\\${namespace}.\\${key}\" is unresolved\\`,\n\t\t);\n\t}\n\treturn value as T;\n};\n\n/** Non-throwing variant of \\`resolveValue\\`: returns \\`undefined\\` instead of\n * throwing \\`DevstackConfigMissingError\\` when no ids were injected, the value\n * is missing, or it is the all-zero sentinel. For discovery-only ids the app\n * GATES on (e.g. a coin's \\`treasuryCapId\\`/\\`metadataId\\`, only known after a\n * live publish) rather than the load-bearing fields that should fail loud. */\nexport const resolveValueOptional = <T = unknown>(\n\tnamespace: string,\n\tkey: string,\n): T | undefined => {\n\tconst injected = injectedIds();\n\tconst values = injected?.values ?? {};\n\tconst namespaceValues = values[namespace];\n\tconst value = namespaceValues === undefined ? undefined : namespaceValues[key];\n\tif (value === undefined || value === UNRESOLVED_ID) {\n\t\treturn undefined;\n\t}\n\treturn value as T;\n};\n`;\n"],"mappings":";;AAmBA,MAAa,6BAA6B;;AAG1C,MAAa,wBAAwB"}
1
+ {"version":3,"file":"config-runtime.mjs","names":[],"sources":["../../../src/orchestrators/codegen/config-runtime.ts"],"sourcesContent":["// The FIXED `config-runtime.ts` source the codegen verb emits into the\n// committed `src/generated` tree.\n//\n// This is NOT routed through the literal renderer (`format.ts`) — it is a\n// constant string, emitted verbatim. It is the deployment\n// resolver the committed `config.ts` calls: `config.ts` carries NO on-chain\n// id (those are LOADED CONFIG DATA, not codegen output), so each id resolves\n// through `requireId(dep, '@local/<slug>')` / `loadDeployment()` here, which\n// reads the build-time-injected `__DEVSTACK_DEPLOYMENT__` global and throws a\n// clear, actionable error when the id is absent or unresolved.\n//\n// `__DEVSTACK_DEPLOYMENT__` is the multi-network ENVELOPE\n// `{ defaultNetwork, networks: { <net>: NetworkDeployment } }`, injected\n// synchronously by the devstack Vite plugin (`build-integrations/vite/index.ts`)\n// via `define`:\n// - dev (stack up): the live local network OVERLAID on any committed\n// networks (the live network is the default).\n// - prod build: the committed, non-local networks only (the per-network\n// `deployments/<net>.ts` thunks the plugin merges).\n// - neither: `null` → the resolvers throw at access time.\n\n/** Output path (relative to the generated tree) of the emitted resolver. */\nexport const CONFIG_RUNTIME_OUTPUT_PATH = 'config-runtime.ts';\n\n/** The verbatim source emitted as `src/generated/config-runtime.ts`. */\nexport const CONFIG_RUNTIME_SOURCE = `// THIS FILE IS AUTO-GENERATED BY @mysten/devstack.\n// Do not edit by hand — your changes will be overwritten by the next\n// \\`devstack codegen\\`. Apps consume codegen output; codegen output never\n// imports from devstack.\n//\n// On-chain deployment resolver. The committed \\`config.ts\\`\n// carries no ids; it resolves each id through the deployment API here\n// (\\`loadDeployment()\\` / \\`requireId(dep, ...)\\`), which reads the build-time-\n// injected \\`__DEVSTACK_DEPLOYMENT__\\` envelope. Run \\`devstack up\\` for local\n// dev (the Vite plugin injects the live deployment), or commit a per-network\n// \\`deployments/<net>.ts\\` for a real deployment.\n\nconst UNRESOLVED_ID =\n\t'0x0000000000000000000000000000000000000000000000000000000000000000';\n\n// Node-only runtime fallback for the injected deployment. The browser/dev/prod\n// path injects \\`__DEVSTACK_DEPLOYMENT__\\` via the Vite \\`define\\` (replaced at\n// transform time); this fallback ONLY engages when that define is absent —\n// i.e. under Node/vitest, where the \\`define\\` bakes to \\`null\\` because the\n// test stack is booted by the vitest \\`globalSetup\\` AFTER the Vite config\n// (and so this module) is evaluated. The devstack vitest \\`globalSetup\\`\n// publishes the booted stack's deployment FILE path as\n// \\`process.env.DEVSTACK_DEPLOYMENT_FILE\\`; we read+parse the ENVELOPE lazily\n// here. Guarded so browser bundles (no \\`process\\`, no \\`node:fs\\`) never touch\n// it. Returns \\`null\\` on any miss, so the resolvers throw their actionable\n// error as before. Minimal LOCAL shapes (no \\`@types/node\\` dependency — this\n// file is also type-checked in browser tsconfigs that don't load node types).\n// We reach \\`process\\` through \\`globalThis\\` and feature-test every member.\ninterface NodeProcessLike {\n\treadonly env?: { readonly [k: string]: string | undefined };\n\treadonly getBuiltinModule?: (id: string) => unknown;\n}\ninterface NodeFsLike {\n\treadonly existsSync: (p: string) => boolean;\n\treadonly readFileSync: (p: string, enc: 'utf8') => string;\n}\nlet nodeFallbackCache: DevstackDeployment | null | undefined;\nconst readNodeFallbackDeployment = (): DevstackDeployment | null => {\n\tif (nodeFallbackCache !== undefined) return nodeFallbackCache;\n\tnodeFallbackCache = null;\n\ttry {\n\t\tconst proc = (globalThis as { process?: NodeProcessLike }).process;\n\t\tif (proc === undefined) return null;\n\t\tconst file = proc.env?.DEVSTACK_DEPLOYMENT_FILE;\n\t\tif (file === undefined || file.length === 0) return null;\n\t\t// \\`process.getBuiltinModule\\` (Node >=22.3) loads \\`node:fs\\`\n\t\t// synchronously without an \\`import\\`/\\`require\\` literal a browser\n\t\t// bundler would try to resolve — this whole branch is unreachable in\n\t\t// the browser (no \\`process\\`), so node:fs never enters the bundle.\n\t\tconst getBuiltin = proc.getBuiltinModule;\n\t\tif (typeof getBuiltin !== 'function') return null;\n\t\tconst fs = getBuiltin('node:fs') as NodeFsLike;\n\t\tif (!fs.existsSync(file)) return null;\n\t\tnodeFallbackCache = JSON.parse(fs.readFileSync(file, 'utf8')) as DevstackDeployment;\n\t\treturn nodeFallbackCache;\n\t} catch {\n\t\treturn null;\n\t}\n};\n\n/** The injected deployment envelope, or — under Node with no injected define —\n * the \\`DEVSTACK_DEPLOYMENT_FILE\\` fallback. \\`null\\` when neither is\n * available. */\nconst injectedDeployment = (): DevstackDeployment | null => {\n\tconst injected =\n\t\ttypeof __DEVSTACK_DEPLOYMENT__ === 'undefined' ? null : __DEVSTACK_DEPLOYMENT__;\n\tif (injected !== null && injected !== undefined) return injected;\n\treturn readNodeFallbackDeployment();\n};\n\n/** One network's full resolved deployment — connection fields (\\`rpc\\` is the\n * load-bearing one the app reads synchronously; \\`chainId\\`/\\`faucet\\`/\n * \\`graphql\\` are optional diagnostics) plus the on-chain ids/values for that\n * network. The per-network UNIT of the multi-network envelope; hand-writable\n * for production. */\nexport interface NetworkDeployment {\n\treadonly network?: string;\n\treadonly rpc: string;\n\treadonly chainId?: string;\n\treadonly faucet?: string | null;\n\treadonly graphql?: string | null;\n\t/** Marks a live LOCAL network (a \\`devstack up\\` stack). Production builds\n\t * ship only non-local networks. */\n\treadonly local?: boolean;\n\treadonly packages: {\n\t\treadonly [name: string]: { readonly id: string; readonly objects?: { readonly [k: string]: string } };\n\t};\n\treadonly mvrOverrides: {\n\t\treadonly packages: { readonly [mvrPlaceholder: string]: string };\n\t\treadonly types: { readonly [namedType: string]: string };\n\t};\n\t/** Generic resolver channel: \\`values[namespace][key]\\` carries arbitrary\n\t * plugin JSON the typed fields above can't (deepbook pool ids, coin\n\t * types, walrus/seal endpoints). Optional — an older injected unit (no\n\t * \\`values\\`) still satisfies the typed resolvers. */\n\treadonly values?: { readonly [namespace: string]: { readonly [key: string]: unknown } };\n}\n\n/** The injected multi-network ENVELOPE: every network this build supports,\n * keyed by name, plus the default the app opens on. \\`null\\` when no stack is\n * running and no committed deployment was supplied at build time. */\nexport interface DevstackDeployment {\n\treadonly defaultNetwork: string;\n\treadonly networks: { readonly [name: string]: NetworkDeployment };\n\t/** DEV-only account name → address map. Network-AGNOSTIC (keypair gen\n\t * needs no network), so it lives on the envelope, NOT per-network. Only\n\t * present when running THROUGH devstack (dev serve); a real prod\n\t * deployment carries none. Optional — \\`resolveAccounts()\\` defaults to\n\t * \\`{}\\` when absent. */\n\treadonly accounts?: { readonly [name: string]: string };\n}\n\n/** Accessor returned by \\`loadDeployment()\\` — the default network, the set of\n * available network names (for dapp-kit's network list), and a per-network\n * lookup that throws when the network is unknown. */\nexport interface LoadedDeployment {\n\treadonly defaultNetwork: string;\n\treadonly networkNames: readonly string[];\n\treadonly forNetwork: (network: string) => NetworkDeployment;\n}\n\ndeclare global {\n\t// Injected by the devstack Vite plugin via \\`define\\`. \\`null\\` when no\n\t// deployment is available (no stack up + no committed deployment).\n\tconst __DEVSTACK_DEPLOYMENT__: DevstackDeployment | null | undefined;\n}\n\n/** Thrown when a generated config value needs an on-chain id but none was\n * injected. */\nexport class DevstackConfigMissingError extends Error {\n\tconstructor(detail: string) {\n\t\tsuper(\n\t\t\t\\`[devstack] \\${detail}\\\\n\\` +\n\t\t\t\t\\`No on-chain deployment is available. For local dev, run \\\\\\`devstack up\\\\\\` \\` +\n\t\t\t\t\\`(the Vite dev server injects the live deployment). For a production build, \\` +\n\t\t\t\t\\`commit the known deployment's per-network \\\\\\`deployments/<net>.ts\\\\\\` \\` +\n\t\t\t\t\\`(wired into the Vite plugin's \\\\\\`deployments\\\\\\` option, or pointed at via \\` +\n\t\t\t\t\\`\\\\\\`DEVSTACK_DEPLOYMENT_FILE\\\\\\`) before \\\\\\`pnpm build\\\\\\`. See your example \\` +\n\t\t\t\t\\`README's \"Deploy to a real network\" section.\\`,\n\t\t);\n\t\tthis.name = 'DevstackConfigMissingError';\n\t}\n}\n\n/** Wrap a resolved \\`DevstackDeployment\\` envelope in the \\`LoadedDeployment\\`\n * accessor the app consumes. */\nconst loadedFrom = (deployment: DevstackDeployment): LoadedDeployment => ({\n\tdefaultNetwork: deployment.defaultNetwork,\n\tnetworkNames: Object.keys(deployment.networks),\n\tforNetwork: (network) => {\n\t\tconst entry = deployment.networks[network];\n\t\tif (entry === undefined) {\n\t\t\tthrow new DevstackConfigMissingError(\\`network \"\\${network}\" has no deployment\\`);\n\t\t}\n\t\treturn entry;\n\t},\n});\n\n/** Load the injected deployment envelope. Throws once here when nothing\n * was injected; the app then reads typed fields off \\`forNetwork(net)\\`. */\nexport const loadDeployment = (): LoadedDeployment => {\n\tconst injected = injectedDeployment();\n\tif (injected === null || injected === undefined) {\n\t\tthrow new DevstackConfigMissingError('no deployment was ever injected');\n\t}\n\treturn loadedFrom(injected);\n};\n\n/** Non-throwing sibling of \\`loadDeployment\\` — returns \\`null\\` instead of\n * throwing when nothing was injected. For dev-only consumers (e.g. the dev\n * wallet) that must no-op gracefully when no stack is running. */\nexport const loadDeploymentOptional = (): LoadedDeployment | null => {\n\tconst injected = injectedDeployment();\n\tif (injected === null || injected === undefined) return null;\n\treturn loadedFrom(injected);\n};\n\n/** The DEV-only account name → address map off the injected envelope.\n * Accounts are network-AGNOSTIC dev identities (envelope-level, never\n * per-network), injected only when running THROUGH devstack. Returns \\`{}\\`\n * — without throwing — when no deployment was injected or none carries\n * accounts (a pure prod build), so a dev-only consumer (e.g. the dev\n * wallet) no-ops gracefully. */\nexport const resolveAccounts = (): { readonly [name: string]: string } => {\n\tconst injected = injectedDeployment();\n\treturn injected?.accounts ?? {};\n};\n\n/** Resolve a package id for an MVR placeholder off a loaded deployment.\n * Throws when the id is missing or unresolved. */\nexport const requireId = (deployment: NetworkDeployment, mvrPlaceholder: string): string => {\n\tconst id = deployment.mvrOverrides.packages[mvrPlaceholder];\n\tif (id === undefined || id === UNRESOLVED_ID) {\n\t\tthrow new DevstackConfigMissingError(\\`id for \"\\${mvrPlaceholder}\" is unresolved\\`);\n\t}\n\treturn id;\n};\n\n/** Resolve a generic plugin value (\\`values[namespace][key]\\`) off a loaded\n * deployment. Throws when the value is missing or unresolved. */\nexport const requireValue = <T = unknown>(\n\tdeployment: NetworkDeployment,\n\tnamespace: string,\n\tkey: string,\n): T => {\n\tconst namespaceValues = deployment.values?.[namespace];\n\tconst value = namespaceValues === undefined ? undefined : namespaceValues[key];\n\tif (value === undefined || value === UNRESOLVED_ID) {\n\t\tthrow new DevstackConfigMissingError(\\`value \"\\${namespace}.\\${key}\" is unresolved\\`);\n\t}\n\treturn value as T;\n};\n\n/** Non-throwing sibling of \\`requireValue\\` for discovery-only fields the app\n * gates on (returns \\`undefined\\` when missing, unresolved, or absent). */\nexport const optionalValue = <T = unknown>(\n\tdeployment: NetworkDeployment,\n\tnamespace: string,\n\tkey: string,\n): T | undefined => {\n\tconst namespaceValues = deployment.values?.[namespace];\n\tconst value = namespaceValues === undefined ? undefined : namespaceValues[key];\n\treturn value === undefined || value === UNRESOLVED_ID ? undefined : (value as T);\n};\n`;\n"],"mappings":";;AAsBA,MAAa,6BAA6B;;AAG1C,MAAa,wBAAwB"}
@@ -0,0 +1,130 @@
1
+ import { CodegenRenderError } from "./errors.mjs";
2
+ //#region src/orchestrators/codegen/deployment-network-file.ts
3
+ /** Canonical directory (relative to the project root) committed per-network
4
+ * deployments live under — the convention the Vite plugin auto-discovers. */
5
+ const DEPLOYMENTS_DIRNAME = "deployments";
6
+ /** The header every emitted `deployments/<net>.ts` carries. Hand-editable —
7
+ * this is an authoring surface — so it is NOT marked auto-generated. */
8
+ const HEADER = [
9
+ "// Committed per-network deployment for a real-network deploy.",
10
+ "//",
11
+ "// Emitted by `devstack dump-deployment --network <net>` from a resolved",
12
+ "// stack, then committed. Hand-editable: the `satisfies AppNetworkDeployment`",
13
+ "// type-checks it against this app declared packages + MVR placeholders, so a",
14
+ "// missing or mistyped id fails `tsc`. No dev `accounts` (those ride the",
15
+ "// runtime envelope, never the committed authoring surface)."
16
+ ].join("\n");
17
+ const INDENT = " ";
18
+ const IDENT_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
19
+ /** A single-quoted TS string literal (prettier `singleQuote: true`). Falls
20
+ * back to a double-quoted literal only when the value itself contains a
21
+ * single quote but no double quote (prettier's own preference), else escapes
22
+ * within single quotes.
23
+ *
24
+ * Escaping is derived from `JSON.stringify`, which already produces a valid
25
+ * double-quoted JS string literal for ANY input — escaping backslash, the
26
+ * double quote, and all control characters (newline, CR, tab, etc.) — so a
27
+ * `values`-channel blob with literal newlines (PEM/cert text, multi-line
28
+ * descriptions) can never emit an unterminated literal. The result is then
29
+ * rephrased into prettier's quote preference. `U+2028`/`U+2029` are valid in
30
+ * JS string literals (and `JSON.stringify` leaves them unescaped), but are
31
+ * escaped here defensively so the emitted file is robust under tools that
32
+ * treat them as line terminators. */
33
+ const LINE_SEP = /* @__PURE__ */ new RegExp("\\u2028", "g");
34
+ const PARA_SEP = /* @__PURE__ */ new RegExp("\\u2029", "g");
35
+ const quote = (s) => {
36
+ const json = JSON.stringify(s).replace(LINE_SEP, "\\u2028").replace(PARA_SEP, "\\u2029");
37
+ if (s.includes("'") && !s.includes("\"")) return json;
38
+ return `'${json.slice(1, -1).replace(/\\"/g, "\"").replace(/'/g, "\\'")}'`;
39
+ };
40
+ const key = (k) => IDENT_RE.test(k) ? k : quote(k);
41
+ /** Render a JSON value to a prettier-compatible, SINGLE-quoted TS literal at
42
+ * the given indent `depth`. The deployment value space is exactly JSON
43
+ * (strings/numbers/booleans/null/arrays/objects), so no function/bigint/
44
+ * symbol handling is needed. */
45
+ const renderValue = (value, depth) => {
46
+ if (value === null) return "null";
47
+ const t = typeof value;
48
+ if (t === "string") return quote(value);
49
+ if (t === "number" || t === "boolean") return String(value);
50
+ if (Array.isArray(value)) {
51
+ if (value.length === 0) return "[]";
52
+ return `[\n${value.map((v) => INDENT.repeat(depth) + renderValue(v, depth + 1)).join(",\n")},\n${INDENT.repeat(depth - 1)}]`;
53
+ }
54
+ if (t === "object") {
55
+ const obj = value;
56
+ const keys = Object.keys(obj).sort();
57
+ if (keys.length === 0) return "{}";
58
+ return `{\n${keys.map((k) => `${INDENT.repeat(depth)}${key(k)}: ${renderValue(obj[k], depth + 1)}`).join(",\n")},\n${INDENT.repeat(depth - 1)}}`;
59
+ }
60
+ throw new CodegenRenderError({
61
+ emitterName: "dump-deployment",
62
+ outputPath: `${DEPLOYMENTS_DIRNAME}/<net>.ts`,
63
+ detail: `value of type ${t} is not JSON-serialisable`
64
+ });
65
+ };
66
+ /** The ordered, JSON-ish object the file body renders. Built explicitly (NOT a
67
+ * spread of the decoded unit) so the field order is stable and the dev-only
68
+ * `local` marker is dropped. Optional connection diagnostics are omitted when
69
+ * absent. `accounts` is structurally absent from `NetworkDeployment`, so it
70
+ * can never leak in.
71
+ *
72
+ * Exported as the SHARED normalize seam: `dump-deployment --network`'s JSON
73
+ * `data` reflects this same shape (network re-derived from the arg, `local`
74
+ * dropped) so a `--json` consumer never sees a shape that diverges from the
75
+ * committed file actually written. */
76
+ const deploymentBody = (network, unit) => {
77
+ const body = {
78
+ network,
79
+ rpc: unit.rpc
80
+ };
81
+ if (unit.chainId !== void 0) body.chainId = unit.chainId;
82
+ if (unit.faucet !== void 0) body.faucet = unit.faucet;
83
+ if (unit.graphql !== void 0) body.graphql = unit.graphql;
84
+ body.packages = unit.packages;
85
+ body.mvrOverrides = unit.mvrOverrides;
86
+ if (unit.values !== void 0) body.values = unit.values;
87
+ return body;
88
+ };
89
+ /**
90
+ * Render a typed `deployments/<net>.ts` for `network` from its resolved
91
+ * `NetworkDeployment` unit.
92
+ *
93
+ * `importPath` is the module specifier the emitted `import type` points at —
94
+ * the app's generated `deployment.js` relative to the `deployments/` dir
95
+ * (default `../src/generated/deployment.js`). Output is prettier-compatible
96
+ * (tab indent, single quotes, trailing commas) so a prettier-on-write hook is
97
+ * a no-op.
98
+ */
99
+ const renderNetworkDeploymentFile = (network, unit, importPath = "../src/generated/deployment.js") => {
100
+ try {
101
+ const body = renderValue(deploymentBody(network, unit), 1);
102
+ return {
103
+ ok: true,
104
+ text: `${HEADER}
105
+
106
+ import type { AppNetworkDeployment } from ${quote(importPath)};
107
+
108
+ export const deployment = ${body} satisfies AppNetworkDeployment;
109
+ `
110
+ };
111
+ } catch (cause) {
112
+ if (cause instanceof CodegenRenderError) return {
113
+ ok: false,
114
+ error: cause
115
+ };
116
+ return {
117
+ ok: false,
118
+ error: new CodegenRenderError({
119
+ emitterName: "dump-deployment",
120
+ outputPath: `${DEPLOYMENTS_DIRNAME}/${network}.ts`,
121
+ detail: "failed to render network deployment file",
122
+ cause
123
+ })
124
+ };
125
+ }
126
+ };
127
+ //#endregion
128
+ export { DEPLOYMENTS_DIRNAME, deploymentBody, renderNetworkDeploymentFile };
129
+
130
+ //# sourceMappingURL=deployment-network-file.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deployment-network-file.mjs","names":[],"sources":["../../../src/orchestrators/codegen/deployment-network-file.ts"],"sourcesContent":["// The typed per-network deployment file `dump-deployment --network <net>`\n// emits — a committed `deployments/<net>.ts` a prod build / dev serve loads.\n//\n// Unlike the boot-written `deployment.json` (the untyped multi-network\n// ENVELOPE), this is the SINGLE-network, hand-editable, type-checked authoring\n// surface: `export const deployment = {…} satisfies AppNetworkDeployment`. The\n// `satisfies` ties it to the app's strict generated `deployment.ts`, so a\n// missing/typo'd package id or MVR placeholder fails `tsc` against the app's\n// own `AppNetworkDeployment`.\n//\n// Sourced from one `networks.<net>` entry of the resolved envelope (the same\n// data `assembleDeployment` produces). The dev-only `accounts` are NOT part of\n// a `NetworkDeployment` (they live on the envelope), so they never appear here;\n// the `local` marker (a dev-stack flag) is also dropped — a committed\n// deployment is non-local by definition.\n//\n// This file is COMMITTED (not under `generated/`), so unlike the codegen\n// renderer (`format.ts`, double-quoted JSON literals in a prettier-IGNORED\n// tree) the output must be prettier-clean: SINGLE-quoted strings, tab indent,\n// trailing commas, sorted keys. A prettier-on-write hook is then a no-op.\n\nimport type { NetworkDeployment } from './deployment.ts';\nimport { CodegenRenderError } from './errors.ts';\n\n/** Canonical directory (relative to the project root) committed per-network\n * deployments live under — the convention the Vite plugin auto-discovers. */\nexport const DEPLOYMENTS_DIRNAME = 'deployments';\n\n/** The header every emitted `deployments/<net>.ts` carries. Hand-editable —\n * this is an authoring surface — so it is NOT marked auto-generated. */\nconst HEADER = [\n\t'// Committed per-network deployment for a real-network deploy.',\n\t'//',\n\t'// Emitted by `devstack dump-deployment --network <net>` from a resolved',\n\t'// stack, then committed. Hand-editable: the `satisfies AppNetworkDeployment`',\n\t'// type-checks it against this app declared packages + MVR placeholders, so a',\n\t'// missing or mistyped id fails `tsc`. No dev `accounts` (those ride the',\n\t'// runtime envelope, never the committed authoring surface).',\n].join('\\n');\n\nconst INDENT = '\\t';\nconst IDENT_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;\n\n/** A single-quoted TS string literal (prettier `singleQuote: true`). Falls\n * back to a double-quoted literal only when the value itself contains a\n * single quote but no double quote (prettier's own preference), else escapes\n * within single quotes.\n *\n * Escaping is derived from `JSON.stringify`, which already produces a valid\n * double-quoted JS string literal for ANY input — escaping backslash, the\n * double quote, and all control characters (newline, CR, tab, etc.) — so a\n * `values`-channel blob with literal newlines (PEM/cert text, multi-line\n * descriptions) can never emit an unterminated literal. The result is then\n * rephrased into prettier's quote preference. `U+2028`/`U+2029` are valid in\n * JS string literals (and `JSON.stringify` leaves them unescaped), but are\n * escaped here defensively so the emitted file is robust under tools that\n * treat them as line terminators. */\n// JS line/paragraph separators: valid in string literals but treated as line\n// terminators by some tooling. Built from escape sequences so this SOURCE file\n// stays pure-ASCII (no raw separator bytes).\nconst LINE_SEP = new RegExp('\\\\u2028', 'g');\nconst PARA_SEP = new RegExp('\\\\u2029', 'g');\nconst quote = (s: string): string => {\n\t// `JSON.stringify` yields `\"…\"` with `\\\\`, `\\\"`, and all control chars\n\t// escaped; the body (sans surrounding quotes) is then a safe double-quoted\n\t// payload we can re-quote. Escape the JS line/paragraph separators too.\n\tconst json = JSON.stringify(s).replace(LINE_SEP, '\\\\u2028').replace(PARA_SEP, '\\\\u2029');\n\t// Prefer single quotes (prettier `singleQuote: true`). The JSON body escapes\n\t// `\"` as `\\\"`; to single-quote we unescape `\\\"` → `\"` and escape any literal\n\t// `'` instead. Keep the double-quoted form when the value has a single quote\n\t// but no double quote (prettier's own preference: fewer escapes).\n\tif (s.includes(\"'\") && !s.includes('\"')) return json;\n\tconst body = json.slice(1, -1).replace(/\\\\\"/g, '\"').replace(/'/g, \"\\\\'\");\n\treturn `'${body}'`;\n};\n\nconst key = (k: string): string => (IDENT_RE.test(k) ? k : quote(k));\n\n/** Render a JSON value to a prettier-compatible, SINGLE-quoted TS literal at\n * the given indent `depth`. The deployment value space is exactly JSON\n * (strings/numbers/booleans/null/arrays/objects), so no function/bigint/\n * symbol handling is needed. */\nconst renderValue = (value: unknown, depth: number): string => {\n\tif (value === null) return 'null';\n\tconst t = typeof value;\n\tif (t === 'string') return quote(value as string);\n\tif (t === 'number' || t === 'boolean') return String(value);\n\tif (Array.isArray(value)) {\n\t\tif (value.length === 0) return '[]';\n\t\tconst inner = value.map((v) => INDENT.repeat(depth) + renderValue(v, depth + 1)).join(',\\n');\n\t\treturn `[\\n${inner},\\n${INDENT.repeat(depth - 1)}]`;\n\t}\n\tif (t === 'object') {\n\t\tconst obj = value as Record<string, unknown>;\n\t\tconst keys = Object.keys(obj).sort();\n\t\tif (keys.length === 0) return '{}';\n\t\tconst lines = keys\n\t\t\t.map((k) => `${INDENT.repeat(depth)}${key(k)}: ${renderValue(obj[k], depth + 1)}`)\n\t\t\t.join(',\\n');\n\t\treturn `{\\n${lines},\\n${INDENT.repeat(depth - 1)}}`;\n\t}\n\t// `undefined` / symbol / function — not JSON. The deployment schema admits\n\t// only JSON, so this is unreachable for a schema-decoded unit; throw rather\n\t// than emit a broken file.\n\tthrow new CodegenRenderError({\n\t\temitterName: 'dump-deployment',\n\t\toutputPath: `${DEPLOYMENTS_DIRNAME}/<net>.ts`,\n\t\tdetail: `value of type ${t} is not JSON-serialisable`,\n\t});\n};\n\n/** The ordered, JSON-ish object the file body renders. Built explicitly (NOT a\n * spread of the decoded unit) so the field order is stable and the dev-only\n * `local` marker is dropped. Optional connection diagnostics are omitted when\n * absent. `accounts` is structurally absent from `NetworkDeployment`, so it\n * can never leak in.\n *\n * Exported as the SHARED normalize seam: `dump-deployment --network`'s JSON\n * `data` reflects this same shape (network re-derived from the arg, `local`\n * dropped) so a `--json` consumer never sees a shape that diverges from the\n * committed file actually written. */\nexport const deploymentBody = (\n\tnetwork: string,\n\tunit: NetworkDeployment,\n): Record<string, unknown> => {\n\tconst body: Record<string, unknown> = { network, rpc: unit.rpc };\n\tif (unit.chainId !== undefined) body.chainId = unit.chainId;\n\tif (unit.faucet !== undefined) body.faucet = unit.faucet;\n\tif (unit.graphql !== undefined) body.graphql = unit.graphql;\n\tbody.packages = unit.packages;\n\tbody.mvrOverrides = unit.mvrOverrides;\n\tif (unit.values !== undefined) body.values = unit.values;\n\treturn body;\n};\n\n/** Result of rendering — discriminated so the caller dispatches on `ok`\n * without `instanceof` (STYLE_GUIDE §2). */\nexport type NetworkFileRenderResult =\n\t| { readonly ok: true; readonly text: string }\n\t| { readonly ok: false; readonly error: CodegenRenderError };\n\n/**\n * Render a typed `deployments/<net>.ts` for `network` from its resolved\n * `NetworkDeployment` unit.\n *\n * `importPath` is the module specifier the emitted `import type` points at —\n * the app's generated `deployment.js` relative to the `deployments/` dir\n * (default `../src/generated/deployment.js`). Output is prettier-compatible\n * (tab indent, single quotes, trailing commas) so a prettier-on-write hook is\n * a no-op.\n */\nexport const renderNetworkDeploymentFile = (\n\tnetwork: string,\n\tunit: NetworkDeployment,\n\timportPath = '../src/generated/deployment.js',\n): NetworkFileRenderResult => {\n\ttry {\n\t\tconst body = renderValue(deploymentBody(network, unit), 1);\n\t\tconst text = `${HEADER}\n\nimport type { AppNetworkDeployment } from ${quote(importPath)};\n\nexport const deployment = ${body} satisfies AppNetworkDeployment;\n`;\n\t\treturn { ok: true, text };\n\t} catch (cause) {\n\t\tif (cause instanceof CodegenRenderError) return { ok: false, error: cause };\n\t\treturn {\n\t\t\tok: false,\n\t\t\terror: new CodegenRenderError({\n\t\t\t\temitterName: 'dump-deployment',\n\t\t\t\toutputPath: `${DEPLOYMENTS_DIRNAME}/${network}.ts`,\n\t\t\t\tdetail: 'failed to render network deployment file',\n\t\t\t\tcause,\n\t\t\t}),\n\t\t};\n\t}\n};\n"],"mappings":";;;;AA0BA,MAAa,sBAAsB;;;AAInC,MAAM,SAAS;CACd;CACA;CACA;CACA;CACA;CACA;CACA;AACD,CAAC,CAAC,KAAK,IAAI;AAEX,MAAM,SAAS;AACf,MAAM,WAAW;;;;;;;;;;;;;;;AAmBjB,MAAM,2BAAW,IAAI,OAAO,WAAW,GAAG;AAC1C,MAAM,2BAAW,IAAI,OAAO,WAAW,GAAG;AAC1C,MAAM,SAAS,MAAsB;CAIpC,MAAM,OAAO,KAAK,UAAU,CAAC,CAAC,CAAC,QAAQ,UAAU,SAAS,CAAC,CAAC,QAAQ,UAAU,SAAS;CAKvF,IAAI,EAAE,SAAS,GAAG,KAAK,CAAC,EAAE,SAAS,IAAG,GAAG,OAAO;CAEhD,OAAO,IADM,KAAK,MAAM,GAAG,EAAE,CAAC,CAAC,QAAQ,QAAQ,IAAG,CAAC,CAAC,QAAQ,MAAM,KACpD,EAAE;AACjB;AAEA,MAAM,OAAO,MAAuB,SAAS,KAAK,CAAC,IAAI,IAAI,MAAM,CAAC;;;;;AAMlE,MAAM,eAAe,OAAgB,UAA0B;CAC9D,IAAI,UAAU,MAAM,OAAO;CAC3B,MAAM,IAAI,OAAO;CACjB,IAAI,MAAM,UAAU,OAAO,MAAM,KAAe;CAChD,IAAI,MAAM,YAAY,MAAM,WAAW,OAAO,OAAO,KAAK;CAC1D,IAAI,MAAM,QAAQ,KAAK,GAAG;EACzB,IAAI,MAAM,WAAW,GAAG,OAAO;EAE/B,OAAO,MADO,MAAM,KAAK,MAAM,OAAO,OAAO,KAAK,IAAI,YAAY,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,KACrE,EAAE,KAAK,OAAO,OAAO,QAAQ,CAAC,EAAE;CAClD;CACA,IAAI,MAAM,UAAU;EACnB,MAAM,MAAM;EACZ,MAAM,OAAO,OAAO,KAAK,GAAG,CAAC,CAAC,KAAK;EACnC,IAAI,KAAK,WAAW,GAAG,OAAO;EAI9B,OAAO,MAHO,KACZ,KAAK,MAAM,GAAG,OAAO,OAAO,KAAK,IAAI,IAAI,CAAC,EAAE,IAAI,YAAY,IAAI,IAAI,QAAQ,CAAC,GAAG,CAAC,CACjF,KAAK,KACU,EAAE,KAAK,OAAO,OAAO,QAAQ,CAAC,EAAE;CAClD;CAIA,MAAM,IAAI,mBAAmB;EAC5B,aAAa;EACb,YAAY,GAAG,oBAAoB;EACnC,QAAQ,iBAAiB,EAAE;CAC5B,CAAC;AACF;;;;;;;;;;;AAYA,MAAa,kBACZ,SACA,SAC6B;CAC7B,MAAM,OAAgC;EAAE;EAAS,KAAK,KAAK;CAAI;CAC/D,IAAI,KAAK,YAAY,KAAA,GAAW,KAAK,UAAU,KAAK;CACpD,IAAI,KAAK,WAAW,KAAA,GAAW,KAAK,SAAS,KAAK;CAClD,IAAI,KAAK,YAAY,KAAA,GAAW,KAAK,UAAU,KAAK;CACpD,KAAK,WAAW,KAAK;CACrB,KAAK,eAAe,KAAK;CACzB,IAAI,KAAK,WAAW,KAAA,GAAW,KAAK,SAAS,KAAK;CAClD,OAAO;AACR;;;;;;;;;;;AAkBA,MAAa,+BACZ,SACA,MACA,aAAa,qCACgB;CAC7B,IAAI;EACH,MAAM,OAAO,YAAY,eAAe,SAAS,IAAI,GAAG,CAAC;EAOzD,OAAO;GAAE,IAAI;GAAM,MAAA,GANH,OAAO;;4CAEmB,MAAM,UAAU,EAAE;;4BAElC,KAAK;;EAEP;CACzB,SAAS,OAAO;EACf,IAAI,iBAAiB,oBAAoB,OAAO;GAAE,IAAI;GAAO,OAAO;EAAM;EAC1E,OAAO;GACN,IAAI;GACJ,OAAO,IAAI,mBAAmB;IAC7B,aAAa;IACb,YAAY,GAAG,oBAAoB,GAAG,QAAQ;IAC9C,QAAQ;IACR;GACD,CAAC;EACF;CACD;AACD"}
@@ -0,0 +1,77 @@
1
+ //#region src/orchestrators/codegen/deployment-strict.ts
2
+ /** Output path (relative to the generated tree) of the emitted strict type. */
3
+ const DEPLOYMENT_STRICT_OUTPUT_PATH = "deployment.ts";
4
+ /** Render the app-specific strict deployment type from data. Pure +
5
+ * deterministic (stable key ordering), types-only output. */
6
+ const renderDeploymentStrict = (input) => {
7
+ const { localNetworkName, packageNames, mvrPlaceholders, mvrTypeTags } = input;
8
+ const providedNetworks = [...new Set(input.providedNetworks)].filter((n) => n !== localNetworkName);
9
+ const packagesBody = packageNames.length > 0 ? packageNames.map((name) => `\treadonly ${JSON.stringify(name)}: { readonly id: string; readonly objects?: Record<string, string> };`).join("\n") : " readonly [packageName: string]: never;";
10
+ const mvrPackagesBody = mvrPlaceholders.length > 0 ? mvrPlaceholders.map((mvr) => `\t\t\t${JSON.stringify(mvr)}: string;`).join("\n") : " // (this app declares no MVR placeholders)";
11
+ const hasMvrTypes = mvrTypeTags.length > 0;
12
+ const mvrTypesBody = hasMvrTypes ? mvrTypeTags.map((tag) => `\t\t\t${JSON.stringify(tag)}: string;`).join("\n") : "";
13
+ const mvrTypesField = hasMvrTypes ? `\t\treadonly types: {\n${mvrTypesBody}\n\t\t} & { readonly [namedType: string]: string };` : `\t\treadonly types?: { readonly [namedType: string]: string };`;
14
+ const valueNamespaces = Object.keys(input.serviceValues).sort();
15
+ const hasServiceValues = valueNamespaces.length > 0;
16
+ const valuesBody = hasServiceValues ? valueNamespaces.map((ns) => {
17
+ const fields = Object.keys(input.serviceValues[ns]).sort().map((k) => `\t\t\t${JSON.stringify(k)}: ${input.serviceValues[ns][k]};`).join("\n");
18
+ return `\t\t${JSON.stringify(ns)}: {\n${fields}\n\t\t};`;
19
+ }).join("\n") : "";
20
+ return `// THIS FILE IS AUTO-GENERATED BY @mysten/devstack.
21
+ // Do not edit by hand — your changes will be overwritten by the next
22
+ // \`devstack codegen\`. Apps consume codegen output; codegen output never
23
+ // imports from devstack.
24
+ //
25
+ // The APP-SPECIFIC strict deployment type. A prod author writes a
26
+ // \`deployments/<net>.ts\` that \`satisfies AppNetworkDeployment\` — the type
27
+ // is exhaustive over this app's declared packages + MVR placeholders, so a
28
+ // missing/typo'd id fails \`tsc\`. The live network set
29
+ // (\`ProvidedNetwork\` / \`NETWORK_NAMES\`) is derived from the
30
+ // \`deployments/*.ts\` filenames. Types-only — zero runtime — except the
31
+ // \`NETWORK_NAMES\` literal tuple (for dapp-kit's typed \`switchNetwork\`).
32
+
33
+ import type { NetworkDeployment } from './config-runtime.js';
34
+
35
+ /** Exhaustive over THIS app's declared package names. */
36
+ export interface AppPackages {
37
+ ${packagesBody}
38
+ }
39
+
40
+ /** The per-network shape a prod author hand-writes in
41
+ * \`deployments/<net>.ts\`: a \`NetworkDeployment\` narrowed so \`packages\` is
42
+ * exhaustive over this app's packages, \`mvrOverrides\` is the @mysten MVR
43
+ * override surface (\`{ packages, types }\`) requiring every declared
44
+ * \`@local/<slug>\` placeholder + named-type tag, and \`values\` requires every
45
+ * service value namespace/key (when the app declares any). NO \`accounts\` —
46
+ * dev accounts ride the runtime envelope, never the per-network authoring
47
+ * surface. */
48
+ export interface AppNetworkDeployment
49
+ extends Omit<NetworkDeployment, 'packages' | 'mvrOverrides' | 'values'> {
50
+ readonly packages: AppPackages;
51
+ readonly mvrOverrides: {
52
+ readonly packages: {
53
+ ${mvrPackagesBody}
54
+ } & { readonly [mvrPlaceholder: string]: string };
55
+ ${mvrTypesField}
56
+ };
57
+ ${hasServiceValues ? `\treadonly values: {\n${valuesBody}\n\t} & {\n\t\treadonly [namespace: string]: { readonly [key: string]: unknown };\n\t};` : `\treadonly values?: NetworkDeployment['values'];`}
58
+ }
59
+
60
+ /** The LIVE network names this app ships — the \`deployments/*.ts\` filenames
61
+ * (local excluded). \`never\` when the app ships no committed deployments. */
62
+ export type ProvidedNetwork = ${providedNetworks.length > 0 ? providedNetworks.map((n) => JSON.stringify(n)).join(" | ") : "never"};
63
+
64
+ /** The committed per-network deployments map a prod build / dev serve loads.
65
+ * Partial — an app need not ship every live network at once. */
66
+ export type ProvidedDeployments = Partial<Record<ProvidedNetwork, AppNetworkDeployment>>;
67
+
68
+ /** The full network-name set, local-first: \`[<local>, ...<provided>]\`.
69
+ * dapp-kit's \`networks\` / \`switchNetwork\` type-check against this tuple
70
+ * (D2). */
71
+ export const NETWORK_NAMES = [${[localNetworkName, ...providedNetworks].map((n) => JSON.stringify(n)).join(", ")}] as const;
72
+ `;
73
+ };
74
+ //#endregion
75
+ export { DEPLOYMENT_STRICT_OUTPUT_PATH, renderDeploymentStrict };
76
+
77
+ //# sourceMappingURL=deployment-strict.mjs.map