envio 3.15.0 → 3.16.0-subgraph

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 (186) hide show
  1. package/README.md +40 -34
  2. package/evm.schema.json +11 -2
  3. package/fuel.schema.json +10 -1
  4. package/index.d.ts +6 -0
  5. package/licenses/README.md +1 -1
  6. package/package.json +10 -9
  7. package/src/Batch.res.mjs +5 -5
  8. package/src/BatchProcessing.res +1 -0
  9. package/src/BatchProcessing.res.mjs +1 -0
  10. package/src/Bin.res +7 -0
  11. package/src/Bin.res.mjs +8 -0
  12. package/src/ChainFetching.res +450 -16
  13. package/src/ChainFetching.res.mjs +403 -32
  14. package/src/ChainState.res +124 -7
  15. package/src/ChainState.res.mjs +128 -67
  16. package/src/ChainState.resi +26 -0
  17. package/src/Clock.res +57 -0
  18. package/src/Clock.res.mjs +72 -0
  19. package/src/Config.res +31 -3
  20. package/src/Config.res.mjs +15 -4
  21. package/src/ContractRegisterContext.res +3 -6
  22. package/src/ContractRegisterContext.res.mjs +4 -2
  23. package/src/Core.res +95 -36
  24. package/src/Core.res.mjs +90 -1
  25. package/src/CrossChainState.res +12 -2
  26. package/src/CrossChainState.res.mjs +3 -3
  27. package/src/Ecosystem.res +7 -4
  28. package/src/Ecosystem.res.mjs +8 -8
  29. package/src/EffectCacheLookup.res +98 -0
  30. package/src/EffectCacheLookup.res.mjs +116 -0
  31. package/src/EffectState.res +10 -2
  32. package/src/EffectState.res.mjs +8 -4
  33. package/src/EffectState.resi +3 -1
  34. package/src/EntityId.res +2 -7
  35. package/src/EntityId.res.mjs +1 -8
  36. package/src/EntityTables.res +4 -1
  37. package/src/EntityTables.res.mjs +1 -1
  38. package/src/Env.res +20 -0
  39. package/src/Env.res.mjs +16 -0
  40. package/src/EventProcessing.res +38 -1
  41. package/src/EventProcessing.res.mjs +28 -1
  42. package/src/FetchState.res +408 -73
  43. package/src/FetchState.res.mjs +361 -91
  44. package/src/FinalizeBackfill.res +2 -4
  45. package/src/FinalizeBackfill.res.mjs +3 -4
  46. package/src/HandlerLoader.res +39 -15
  47. package/src/HandlerLoader.res.mjs +27 -8
  48. package/src/HandlerRegister.res +4 -0
  49. package/src/HandlerRegister.res.mjs +5 -2
  50. package/src/HandlerRegister.resi +2 -0
  51. package/src/InMemoryTable.res +17 -16
  52. package/src/InMemoryTable.res.mjs +14 -15
  53. package/src/IndexerState.res +28 -4
  54. package/src/IndexerState.res.mjs +19 -6
  55. package/src/IndexerState.resi +4 -0
  56. package/src/Internal.res +61 -49
  57. package/src/Internal.res.mjs +36 -80
  58. package/src/LoadLayer.res +54 -15
  59. package/src/LoadLayer.res.mjs +75 -45
  60. package/src/LoadLayer.resi +26 -0
  61. package/src/Main.res +14 -1
  62. package/src/Main.res.mjs +4 -3
  63. package/src/Persistence.res +3 -23
  64. package/src/Persistence.res.mjs +3 -3
  65. package/src/PreloadLookahead.res +202 -0
  66. package/src/PreloadLookahead.res.mjs +218 -0
  67. package/src/Rollback.res +0 -1
  68. package/src/Rollback.res.mjs +1 -1
  69. package/src/RunScope.res +3 -0
  70. package/src/SafeCheckpointTracking.res +4 -4
  71. package/src/SafeCheckpointTracking.res.mjs +1 -1
  72. package/src/SimulateItems.res +42 -26
  73. package/src/SimulateItems.res.mjs +40 -16
  74. package/src/StagedRows.res +106 -0
  75. package/src/StagedRows.res.mjs +102 -0
  76. package/src/Staging.res +32 -14
  77. package/src/Staging.res.mjs +17 -20
  78. package/src/Storage.res +303 -948
  79. package/src/Storage.res.mjs +229 -708
  80. package/src/SubgraphLog.res +19 -0
  81. package/src/SubgraphLog.res.mjs +26 -0
  82. package/src/Supervisor.res +75 -56
  83. package/src/Supervisor.res.mjs +55 -26
  84. package/src/SyncWrites.res +107 -0
  85. package/src/SyncWrites.res.mjs +98 -0
  86. package/src/TestIndexer.res +22 -27
  87. package/src/TestIndexer.res.mjs +20 -17
  88. package/src/Throttler.res +15 -8
  89. package/src/Throttler.res.mjs +9 -6
  90. package/src/Throttler.resi +1 -1
  91. package/src/UserContext.res +419 -46
  92. package/src/UserContext.res.mjs +318 -30
  93. package/src/Utils.res +17 -6
  94. package/src/Utils.res.mjs +12 -6
  95. package/src/Writing.res +0 -1
  96. package/src/Writing.res.mjs +1 -1
  97. package/src/bindings/ClickHouse.res +0 -322
  98. package/src/bindings/ClickHouse.res.mjs +0 -338
  99. package/src/bindings/StorageClient.res +108 -174
  100. package/src/bindings/StorageClient.res.mjs +19 -8
  101. package/src/bindings/Vitest.res +0 -26
  102. package/src/bindings/Vitest.res.mjs +0 -3
  103. package/src/db/Table.res +24 -150
  104. package/src/db/Table.res.mjs +38 -272
  105. package/src/sources/AddressSet.res +2 -0
  106. package/src/sources/AddressStore.res +3 -0
  107. package/src/sources/CallSource.res +274 -0
  108. package/src/sources/CallSource.res.mjs +196 -0
  109. package/src/sources/ChainSources.res +2 -0
  110. package/src/sources/ChainSources.res.mjs +2 -2
  111. package/src/sources/EvmChain.res +10 -1
  112. package/src/sources/EvmChain.res.mjs +6 -3
  113. package/src/sources/EvmHyperSyncSource.res +2 -2
  114. package/src/sources/EvmHyperSyncSource.res.mjs +1 -1
  115. package/src/sources/EvmRpcClient.res +13 -3
  116. package/src/sources/EvmRpcClient.res.mjs +4 -3
  117. package/src/sources/EvmRpcWs.res +4 -3
  118. package/src/sources/EvmRpcWs.res.mjs +2 -2
  119. package/src/sources/HeightFeed.res +31 -10
  120. package/src/sources/HeightFeed.res.mjs +10 -6
  121. package/src/sources/HeightStream.res +32 -17
  122. package/src/sources/HeightStream.res.mjs +10 -10
  123. package/src/sources/HyperSync.res +2 -1
  124. package/src/sources/HyperSync.res.mjs +2 -1
  125. package/src/sources/HyperSyncSSE.res +4 -6
  126. package/src/sources/HyperSyncSSE.res.mjs +2 -2
  127. package/src/sources/RpcSource.res +10 -1
  128. package/src/sources/RpcSource.res.mjs +4 -3
  129. package/src/sources/Source.res +3 -0
  130. package/src/sources/SourceManager.res +84 -77
  131. package/src/sources/SourceManager.res.mjs +17 -20
  132. package/src/sources/SourceManager.resi +3 -4
  133. package/src/sources/StartBlockResolver.res +1 -0
  134. package/src/sources/StartBlockResolver.res.mjs +3 -2
  135. package/src/sources/SvmHyperSyncSource.res +2 -2
  136. package/src/sources/SvmHyperSyncSource.res.mjs +1 -1
  137. package/src/subgraph/abi-types.ts +84 -0
  138. package/src/subgraph/aggregations.ts +745 -0
  139. package/src/subgraph/as-package.ts +143 -0
  140. package/src/subgraph/assemblyscript-regex.ts +67 -0
  141. package/src/subgraph/assemblyscript.ts +731 -0
  142. package/src/subgraph/blocks.ts +398 -0
  143. package/src/subgraph/calls-source.ts +702 -0
  144. package/src/subgraph/calls.ts +258 -0
  145. package/src/subgraph/conformance.ts +106 -0
  146. package/src/subgraph/errors.ts +85 -0
  147. package/src/subgraph/graft.ts +73 -0
  148. package/src/subgraph/graph-cli-worker.mjs +35 -0
  149. package/src/subgraph/graph-cli.ts +900 -0
  150. package/src/subgraph/graph-node-decimal.ts +199 -0
  151. package/src/subgraph/graph-ts-enums.ts +28 -0
  152. package/src/subgraph/graph-ts-types/VERSION +2 -0
  153. package/src/subgraph/graph-ts-types/chain/arweave.d.ts +70 -0
  154. package/src/subgraph/graph-ts-types/chain/cosmos.d.ts +327 -0
  155. package/src/subgraph/graph-ts-types/chain/ethereum.d.ts +233 -0
  156. package/src/subgraph/graph-ts-types/chain/near.d.ts +253 -0
  157. package/src/subgraph/graph-ts-types/chain/starknet.d.ts +32 -0
  158. package/src/subgraph/graph-ts-types/common/collections.d.ts +136 -0
  159. package/src/subgraph/graph-ts-types/common/conversion.d.ts +11 -0
  160. package/src/subgraph/graph-ts-types/common/datasource.d.ts +30 -0
  161. package/src/subgraph/graph-ts-types/common/eager-offset.d.ts +0 -0
  162. package/src/subgraph/graph-ts-types/common/json.d.ts +17 -0
  163. package/src/subgraph/graph-ts-types/common/numbers.d.ts +120 -0
  164. package/src/subgraph/graph-ts-types/common/value.d.ts +120 -0
  165. package/src/subgraph/graph-ts-types/common/yaml.d.ts +90 -0
  166. package/src/subgraph/graph-ts-types/global/global.d.ts +194 -0
  167. package/src/subgraph/graph-ts-types/helper-functions.d.ts +22 -0
  168. package/src/subgraph/graph-ts-types/index.d.ts +102 -0
  169. package/src/subgraph/graph-ts.ts +4339 -0
  170. package/src/subgraph/hosts.ts +554 -0
  171. package/src/subgraph/log.ts +28 -0
  172. package/src/subgraph/receipts.ts +184 -0
  173. package/src/subgraph/register-slots.ts +93 -0
  174. package/src/subgraph/rollup.ts +109 -0
  175. package/src/subgraph/rpc.ts +337 -0
  176. package/src/subgraph/runtime.ts +2059 -0
  177. package/src/subgraph/scope.ts +176 -0
  178. package/src/subgraph/stats.ts +42 -0
  179. package/src/subgraph/template-instances.ts +66 -0
  180. package/src/subgraph/toolchain.ts +327 -0
  181. package/src/subgraph/transient.ts +176 -0
  182. package/svm.schema.json +11 -2
  183. package/src/PgWriting.res +0 -36
  184. package/src/PgWriting.res.mjs +0 -34
  185. package/src/db/IndexDefinition.res +0 -47
  186. package/src/db/IndexDefinition.res.mjs +0 -67
@@ -0,0 +1,900 @@
1
+ /**
2
+ * The project's own graph-cli, run inside this process on a worker thread.
3
+ *
4
+ * Its own version, so the generated code is byte-for-byte what the developer
5
+ * gets from `graph codegen`; a worker, so its heap can be sized for it alone —
6
+ * codegen over a large ABI set outgrows Node's default heap long before envio
7
+ * does.
8
+ */
9
+
10
+ import {
11
+ existsSync,
12
+ mkdirSync,
13
+ readFileSync,
14
+ readdirSync,
15
+ realpathSync,
16
+ rmSync,
17
+ statSync,
18
+ writeFileSync,
19
+ } from "node:fs";
20
+ import { spawn } from "node:child_process";
21
+ import { totalmem } from "node:os";
22
+ import path from "node:path";
23
+ import { Worker } from "node:worker_threads";
24
+ import {
25
+ subgraphBundledCodegen,
26
+ subgraphBundledCodegenCurrent,
27
+ subgraphFillGenerated,
28
+ subgraphMissingGraphCliMessage,
29
+ } from "../Core.res.mjs";
30
+ import { info, warn } from "./log.ts";
31
+ import { fetchGraphCli, graphCliVersions } from "./toolchain.ts";
32
+
33
+ const WORKER = new URL("./graph-cli-worker.mjs", import.meta.url);
34
+
35
+ /**
36
+ * Three quarters of the machine — the worker is the only thing running — or of
37
+ * the container, whose limit `totalmem` doesn't see.
38
+ */
39
+ const HEAP_MB = Math.floor(
40
+ (Math.min(totalmem(), process.constrainedMemory?.() || Infinity) / 2 ** 20) * 0.75,
41
+ );
42
+
43
+ /**
44
+ * Where Node would resolve graph-cli from the project: its own node_modules,
45
+ * then each parent's — a yarn or npm workspace hoists it to the root. A
46
+ * package directory left half-installed (an install that died on a dead git
47
+ * dependency) doesn't count: it can't run.
48
+ */
49
+ function graphCliPackage(root: string): string | null {
50
+ for (let dir = path.resolve(root); ; dir = path.dirname(dir)) {
51
+ const candidate = path.join(dir, "node_modules", "@graphprotocol", "graph-cli");
52
+ // pnpm links the package in; its own plugins resolve only from where it lives.
53
+ if (existsSync(path.join(candidate, "package.json"))) {
54
+ const packageDir = realpathSync(candidate);
55
+ if (runnable(packageDir)) return packageDir;
56
+ }
57
+ if (path.dirname(dir) === dir) return null;
58
+ }
59
+ }
60
+
61
+ function runnable(packageDir: string): boolean {
62
+ const bin = legacyBin(packageDir);
63
+ return existsSync(path.join(packageDir, "dist", "commands", "codegen.js")) || (bin !== null && existsSync(bin));
64
+ }
65
+
66
+ /**
67
+ * One place `graph codegen` can come from. The project's own graph-cli is one;
68
+ * a pinned one envio fetches into its own cache is another; a codegen bundled
69
+ * in envio, with no graph-cli at all, slots in here as a third.
70
+ */
71
+ export interface CodegenProvider {
72
+ readonly name: string;
73
+ /** Whether using it downloads something: only done when the mappings import generated code. */
74
+ readonly fetches?: boolean;
75
+ /** graph-cli package directories to try, best first; empty when this provider has none. */
76
+ locate(root: string): Promise<string[]>;
77
+ }
78
+
79
+ const projectGraphCli: CodegenProvider = {
80
+ name: "the project's graph-cli",
81
+ locate: async (root) => {
82
+ const found = graphCliPackage(root);
83
+ return found ? [found] : [];
84
+ },
85
+ };
86
+
87
+ const fetchedGraphCli: CodegenProvider = {
88
+ name: "a graph-cli fetched by envio",
89
+ fetches: true,
90
+ locate: async (root) => {
91
+ const found: string[] = [];
92
+ for (const version of graphCliVersions(root)) {
93
+ const packageDir = await fetchGraphCli(version);
94
+ if (packageDir) found.push(packageDir);
95
+ }
96
+ return found;
97
+ },
98
+ };
99
+
100
+ export const codegenProviders: CodegenProvider[] = [projectGraphCli, fetchedGraphCli];
101
+
102
+ type Outcome = "ok" | "failed" | "outOfMemory";
103
+
104
+ /**
105
+ * graph-cli before oclif (0.2x) has no `dist/commands/` to run in-process, only
106
+ * its gluegun bin.
107
+ */
108
+ function legacyBin(packageDir: string): string | null {
109
+ if (existsSync(path.join(packageDir, "dist", "commands"))) return null;
110
+ try {
111
+ const { bin } = JSON.parse(readFileSync(path.join(packageDir, "package.json"), "utf8"));
112
+ const graph = typeof bin === "string" ? bin : bin?.graph;
113
+ return typeof graph === "string" ? path.join(packageDir, graph) : null;
114
+ } catch {
115
+ return null;
116
+ }
117
+ }
118
+
119
+ function runLegacyGraphCli(bin: string, root: string, command: string, argv: string[]): Promise<Outcome> {
120
+ return new Promise((resolve) => {
121
+ const child = spawn(process.execPath, [`--max-old-space-size=${HEAP_MB}`, bin, command, ...argv], {
122
+ cwd: root,
123
+ stdio: "inherit",
124
+ });
125
+ child.on("error", (error) => {
126
+ console.error(error);
127
+ resolve("failed");
128
+ });
129
+ child.on("exit", (code) => resolve(code === 0 ? "ok" : "failed"));
130
+ });
131
+ }
132
+
133
+ function runGraphCli(packageDir: string, root: string, command: string, argv: string[]): Promise<Outcome> {
134
+ const bin = legacyBin(packageDir);
135
+ if (bin) return runLegacyGraphCli(bin, root, command, argv);
136
+ return new Promise((resolve) => {
137
+ let ok = false;
138
+ const worker = new Worker(WORKER, {
139
+ workerData: { packageDir, command, argv },
140
+ resourceLimits: { maxOldGenerationSizeMb: HEAP_MB },
141
+ });
142
+ worker.on("message", (message: boolean) => (ok = message));
143
+ worker.on("error", (error: NodeJS.ErrnoException) => {
144
+ if (error.code === "ERR_WORKER_OUT_OF_MEMORY") {
145
+ resolve("outOfMemory");
146
+ } else {
147
+ console.error(error);
148
+ resolve("failed");
149
+ }
150
+ });
151
+ worker.on("exit", () => resolve(ok ? "ok" : "failed"));
152
+ });
153
+ }
154
+
155
+ /**
156
+ * Migrations are skipped: they rewrite the developer's `subgraph.yaml` in
157
+ * place, and envio reads a manifest, it doesn't maintain one.
158
+ */
159
+ async function runOrThrow(
160
+ graphCli: string,
161
+ root: string,
162
+ manifestFile: string,
163
+ command: string,
164
+ outputDir: string,
165
+ failure: string,
166
+ ): Promise<void> {
167
+ const outcome = await runGraphCli(graphCli, root, command, [
168
+ path.resolve(root, manifestFile),
169
+ "--output-dir",
170
+ outputDir,
171
+ "--skip-migrations",
172
+ ]);
173
+ if (outcome === "outOfMemory") throw outOfMemory(command);
174
+ if (outcome === "failed") throw new Error(failure);
175
+ }
176
+
177
+ function outOfMemory(command: string): Error {
178
+ return new Error(
179
+ `Envio Subgraph ran \`graph ${command}\` with a ${HEAP_MB} MB heap, three\n` +
180
+ "quarters of this machine's memory, and it ran out. Run it on a machine\n" +
181
+ "with more memory, then start envio again.",
182
+ );
183
+ }
184
+
185
+ /**
186
+ * graph-cli skips migrations here (they rewrite the developer's manifest), and
187
+ * 0.5x then refuses a mapping below apiVersion 0.0.5, which is what an old
188
+ * subgraph says. Codegen reads only the ABIs and the schema, so it is run on a
189
+ * copy beside the manifest with those versions raised.
190
+ */
191
+ async function withCodegenManifest<T>(
192
+ manifest: string,
193
+ pathFixes: [string, string][],
194
+ use: (file: string) => Promise<T>,
195
+ { raiseApiVersion = true }: { raiseApiVersion?: boolean } = {},
196
+ ): Promise<T> {
197
+ let source: string;
198
+ try {
199
+ source = readFileSync(manifest, "utf8");
200
+ } catch {
201
+ return use(manifest);
202
+ }
203
+ let patched = raiseApiVersion ? source.replace(/(\bapiVersion:\s*["']?)0\.0\.[0-4]\b/g, "$10.0.5") : source;
204
+ // graph-cli on Linux can't read a file the manifest names in another case
205
+ // than the repo's; the copy names it the repo's way.
206
+ for (const [written, found] of pathFixes) {
207
+ const escaped = written.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
208
+ patched = patched.replace(new RegExp(`(\\bfile:\\s*["']?)${escaped}(["']?\\s*$)`, "gm"), `$1${found}$2`);
209
+ }
210
+ if (patched === source) return use(manifest);
211
+ const copy = path.join(path.dirname(manifest), `.envio-codegen-${path.basename(manifest)}`);
212
+ writeFileSync(copy, patched);
213
+ try {
214
+ return await use(copy);
215
+ } finally {
216
+ rmSync(copy, { force: true });
217
+ }
218
+ }
219
+
220
+ /**
221
+ * `graph codegen` from the first provider that has a graph-cli, trying each of
222
+ * its candidates in turn: a fetched graph-cli is a ladder (the project's
223
+ * declared version, then two pins), since no one version reads every manifest.
224
+ * Returns false when no provider has one.
225
+ */
226
+ async function runCodegen(
227
+ root: string,
228
+ manifestFile: string,
229
+ outputDir: string,
230
+ needed: boolean,
231
+ flags: string[] = [],
232
+ pathFixes: [string, string][] = [],
233
+ ): Promise<boolean> {
234
+ for (const provider of codegenProviders) {
235
+ if (provider.fetches && !needed) continue;
236
+ const candidates = await provider.locate(root);
237
+ if (candidates.length === 0) continue;
238
+ for (const [index, graphCli] of candidates.entries()) {
239
+ const outcome = await withCodegenManifest(path.resolve(root, manifestFile), pathFixes, (manifest) =>
240
+ runGraphCli(graphCli, root, "codegen", [manifest, "--output-dir", outputDir, "--skip-migrations", ...flags]),
241
+ );
242
+ if (outcome === "ok") return true;
243
+ if (outcome === "outOfMemory") throw outOfMemory("codegen");
244
+ if (index < candidates.length - 1) {
245
+ info(`Envio Subgraph: \`graph codegen\` failed with ${provider.name} at ${graphCli}; trying the next version.`);
246
+ }
247
+ }
248
+ throw new Error(
249
+ "Envio Subgraph ran `graph codegen` to build the generated code, but it\n" +
250
+ `failed with ${provider.name} (${candidates.length} version${candidates.length === 1 ? "" : "s"} tried) — the error above comes from The Graph's own codegen, so fix it\n` +
251
+ "there and rerun. If `graph codegen` succeeds on its own but fails through\n" +
252
+ "envio, please open an issue: https://github.com/enviodev/hyperindex/issues",
253
+ );
254
+ }
255
+ return false;
256
+ }
257
+
258
+ const RELATIVE_IMPORT = /\bfrom\s*["'](\.\.?\/[^"']+)["']/g;
259
+
260
+ /**
261
+ * Where the mappings import their generated code from: `graph codegen -o`
262
+ * moves it, and the mappings are the only record of where. Every generated
263
+ * tree has a `schema.ts`, and a template's a `templates.ts`.
264
+ */
265
+ /**
266
+ * The project files the manifest's mappings reach through relative imports,
267
+ * breadth first, each with the relative specifiers it imports: an entry file
268
+ * may only re-export handlers written elsewhere (Uniswap v4's do).
269
+ */
270
+ function* scanImports(root: string, mappingFiles: string[]): Generator<{ file: string; specifiers: string[] }> {
271
+ const queue = [...new Set(mappingFiles)].map((mappingFile) => path.resolve(root, mappingFile));
272
+ const seen = new Set(queue);
273
+ for (let index = 0; index < queue.length && index < MAX_SCANNED_FILES; index++) {
274
+ const file = queue[index];
275
+ let source: string;
276
+ try {
277
+ source = readFileSync(file, "utf8");
278
+ } catch {
279
+ continue;
280
+ }
281
+ const specifiers = [...source.matchAll(RELATIVE_IMPORT)].map(([, specifier]) => specifier);
282
+ yield { file, specifiers };
283
+ for (const specifier of specifiers) {
284
+ const target = path.resolve(path.dirname(file), specifier);
285
+ for (const candidate of [target, `${target}.ts`, path.join(target, "index.ts")]) {
286
+ if (!seen.has(candidate) && existsSync(candidate) && statSync(candidate).isFile()) {
287
+ seen.add(candidate);
288
+ queue.push(candidate);
289
+ break;
290
+ }
291
+ }
292
+ }
293
+ }
294
+ }
295
+
296
+ export function generatedDir(root: string, mappingFiles: string[]): string {
297
+ for (const { file, specifiers } of scanImports(root, mappingFiles)) {
298
+ // A mapping shipped in a dependency (@openzeppelin/subgraphs) imports the
299
+ // generated code shipped beside it, not the tree `graph codegen` writes.
300
+ if (inDependency(root, file)) continue;
301
+ for (const specifier of specifiers) {
302
+ const base = path.basename(specifier).replace(/\.ts$/, "");
303
+ if (base === "schema" || base === "templates") {
304
+ return path.resolve(path.dirname(file), path.dirname(specifier));
305
+ }
306
+ }
307
+ }
308
+ return path.resolve(root, "generated");
309
+ }
310
+
311
+ function inDependency(root: string, file: string): boolean {
312
+ return path.relative(root, file).split(path.sep).includes("node_modules");
313
+ }
314
+
315
+ /**
316
+ * The generated files the mappings import that aren't there. A directory that
317
+ * exists isn't enough: Envio Cloud recreates generated/ empty before the build.
318
+ */
319
+ export function missingGeneratedFiles(root: string, mappingFiles: string[], dir: string): string[] {
320
+ // Imports resolve to absolute paths; `envio start` passes the root as ".".
321
+ dir = path.resolve(dir);
322
+ const missing = new Set<string>();
323
+ for (const { file, specifiers } of scanImports(root, mappingFiles)) {
324
+ for (const specifier of specifiers) {
325
+ const target = path.resolve(path.dirname(file), specifier);
326
+ if (!target.startsWith(dir + path.sep)) continue;
327
+ const candidates = [target, `${target}.ts`, path.join(target, "index.ts")];
328
+ if (!candidates.some((candidate) => existsSync(candidate) && statSync(candidate).isFile())) {
329
+ missing.add(path.relative(root, target));
330
+ }
331
+ }
332
+ }
333
+ return [...missing];
334
+ }
335
+
336
+ const MAX_SCANNED_FILES = 2_000;
337
+
338
+ /** Whether any file the mappings reach imports something from `dir`. */
339
+ function importsFrom(root: string, mappingFiles: string[], dir: string): boolean {
340
+ dir = path.resolve(dir);
341
+ for (const { file, specifiers } of scanImports(root, mappingFiles)) {
342
+ for (const specifier of specifiers) {
343
+ if (path.resolve(path.dirname(file), specifier).startsWith(dir + path.sep)) return true;
344
+ }
345
+ }
346
+ return false;
347
+ }
348
+
349
+ /** The CLI's words for the same failure, which it reports before the runtime starts. */
350
+ export function missingGeneratedCode(root: string, dir: string): Error {
351
+ return new Error(subgraphMissingGraphCliMessage(path.relative(root, dir)));
352
+ }
353
+
354
+ /**
355
+ * A package install (`yarn`, `npm ci`, `pnpm install --frozen-lockfile`), which
356
+ * graphprotocol/block-oracle's `"codegen": "yarn && graph codegen"` begins with:
357
+ * the developer installs before envio starts, and envio runs no installer, whose
358
+ * lifecycle scripts would run with it.
359
+ */
360
+ const INSTALL_STEP =
361
+ /^(?:(?:npx|corepack)\s+)?(?:yarn(?:\s+install)?|npm\s+(?:i|install|ci)|pnpm\s+(?:i|install)|bun\s+(?:i|install))(?:\s+--?[\w-]+(?:=\S+)?)*$/;
362
+
363
+ function isInstall(step: string): boolean {
364
+ return INSTALL_STEP.test(step.trim());
365
+ }
366
+
367
+ const GRAPH_CODEGEN = /^(?:(?:npx|pnpm exec|pnpm|yarn|bunx)\s+)?graph\s+codegen\b/;
368
+
369
+ /**
370
+ * The flags of a `graph codegen` step that change what it writes, which the
371
+ * run envio makes would otherwise drop: `-u` / `--uncrashable` also writes
372
+ * `generated/UncrashableEntityHelpers.ts` (Wildcat's mappings import it), from
373
+ * `--uncrashable-config <file>` or `uncrashable-config.yaml`. Only graph-cli
374
+ * can write it; the bundled codegen has no such helper.
375
+ */
376
+ export function codegenFlags(root: string, step: string): string[] {
377
+ const words = step.split(/\s+/);
378
+ const flags: string[] = [];
379
+ for (let index = words.indexOf("codegen") + 1; index > 0 && index < words.length; index++) {
380
+ const word = words[index];
381
+ const [name, inline] = word.split("=", 2);
382
+ if (name === "-u" || name === "--uncrashable") {
383
+ flags.push("--uncrashable");
384
+ } else if (name === "--uncrashable-config" || name === "--uc") {
385
+ const file = inline ?? words[++index];
386
+ if (file) flags.push("--uncrashable-config", path.resolve(root, file.replace(/^["']|["']$/g, "")));
387
+ }
388
+ }
389
+ // graph-cli looks for the default config in its own working directory.
390
+ const fallback = path.join(root, "uncrashable-config.yaml");
391
+ if (flags.length > 0 && !flags.includes("--uncrashable-config") && existsSync(fallback)) {
392
+ flags.push("--uncrashable-config", fallback);
393
+ }
394
+ return flags;
395
+ }
396
+
397
+ const CODEGEN_VALUE_FLAGS = new Set(["-o", "--output-dir", "--uncrashable-config", "--uc", "-i", "--ipfs"]);
398
+
399
+ /**
400
+ * The manifest and output directory a `graph codegen` step names, if any:
401
+ * balancer/gauges-subgraph runs `graph codegen subgraph.yaml -o src/types/ &&
402
+ * graph codegen subgraph.arbitrum.yaml -o src/types/`, one tree from two
403
+ * manifests.
404
+ */
405
+ function codegenTarget(step: string): { manifest?: string; outputDir?: string } {
406
+ const words = step.split(/\s+/);
407
+ const target: { manifest?: string; outputDir?: string } = {};
408
+ for (let index = words.indexOf("codegen") + 1; index > 0 && index < words.length; index++) {
409
+ const word = words[index].replace(/^["']|["']$/g, "");
410
+ if (word === "--" || word === "") continue;
411
+ const [name, inline] = word.split("=", 2);
412
+ if (CODEGEN_VALUE_FLAGS.has(name)) {
413
+ const value = inline ?? words[++index]?.replace(/^["']|["']$/g, "");
414
+ if ((name === "-o" || name === "--output-dir") && value) target.outputDir = value;
415
+ } else if (!word.startsWith("-")) {
416
+ target.manifest ??= word;
417
+ }
418
+ }
419
+ return target;
420
+ }
421
+
422
+ /**
423
+ * What a `graph codegen` step generates: the manifest it names, into where it
424
+ * writes, when that is another manifest of the project; envio's own manifest
425
+ * into `dir` otherwise.
426
+ */
427
+ function stepTarget(root: string, manifestFile: string, dir: string, step: string) {
428
+ const { manifest, outputDir } = codegenTarget(step);
429
+ const named = manifest && path.resolve(root, manifest);
430
+ if (!named || named === path.resolve(root, manifestFile) || !existsSync(named)) {
431
+ return { manifest: manifestFile, outputDir: dir, own: true };
432
+ }
433
+ return { manifest, outputDir: outputDir ? path.resolve(root, outputDir) : dir, own: false };
434
+ }
435
+
436
+ /**
437
+ * Every manifest the codegen script generates into the tree, in its order;
438
+ * envio's own last when no step names it, so its files win.
439
+ */
440
+ function codegenPlan(root: string, manifestFile: string, dir: string) {
441
+ const plan = (codegenSteps(root) ?? [])
442
+ .filter((step) => GRAPH_CODEGEN.test(step))
443
+ .map((step) => stepTarget(root, manifestFile, dir, step));
444
+ return plan.some(({ own }) => own) ? plan : [...plan, { manifest: manifestFile, outputDir: dir, own: true }];
445
+ }
446
+
447
+ /**
448
+ * The project's `codegen` script, step by step, when it runs `graph codegen`
449
+ * itself. Some do more after it — Lido writes `generated/parserData.ts` with a
450
+ * script of its own — and the mappings import what those steps leave.
451
+ */
452
+ function codegenSteps(root: string, skip: SkippedSteps = {}): string[] | null {
453
+ let script: unknown;
454
+ try {
455
+ script = JSON.parse(readFileSync(path.join(root, "package.json"), "utf8")).scripts?.codegen;
456
+ } catch {
457
+ return null;
458
+ }
459
+ if (typeof script !== "string") return null;
460
+ const steps = script
461
+ .split("&&")
462
+ .map((step) => step.trim())
463
+ .filter(Boolean)
464
+ .filter((step) => !(skip.renderScripts ?? []).some((script) => runsScript(step, script)))
465
+ .filter((step) => !skip.ranSteps?.includes(step));
466
+ return steps.some((step) => GRAPH_CODEGEN.test(step)) ? steps : null;
467
+ }
468
+
469
+ /**
470
+ * Codegen steps envio already ran to write the manifest, for the network
471
+ * picked: run again, they would write the script's default network over it.
472
+ */
473
+ type SkippedSteps = {
474
+ /** The package.json script envio rendered the manifest with itself, and those it runs. */
475
+ renderScripts?: string[];
476
+ /** The codegen script's own steps envio ran before translating. */
477
+ ranSteps?: string[];
478
+ };
479
+
480
+ type CodegenOptions = { pathFixes?: [string, string][] } & SkippedSteps;
481
+
482
+ /** Whether a step runs package.json script `script`: `yarn x`, `npm run x`, `pnpm x`. */
483
+ function runsScript(step: string, script: string): boolean {
484
+ const words = step.split(/\s+/);
485
+ const named = words[0] === "npm" || words[1] === "run" ? words[2] : words[1];
486
+ return ["yarn", "npm", "pnpm", "bun"].includes(words[0]) && named === script;
487
+ }
488
+
489
+ /** The project's bins and its workspaces', nearest first, as a package manager puts them on PATH. */
490
+ function binDirs(root: string): string[] {
491
+ const dirs: string[] = [];
492
+ for (let dir = path.resolve(root); ; dir = path.dirname(dir)) {
493
+ dirs.push(path.join(dir, "node_modules", ".bin"));
494
+ if (path.dirname(dir) === dir) return dirs;
495
+ }
496
+ }
497
+
498
+ /**
499
+ * A step as the package manager would run it: a shell, with the project's bins
500
+ * on PATH. It is the repo's own code, so envio's settings (its HyperSync token,
501
+ * the database password) stay out of its environment.
502
+ */
503
+ function runScriptStep(root: string, step: string): Promise<void> {
504
+ const env = Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.startsWith("ENVIO_")));
505
+ return new Promise((resolve, reject) => {
506
+ const child = spawn(step, {
507
+ cwd: root,
508
+ shell: true,
509
+ stdio: "inherit",
510
+ env: {
511
+ ...env,
512
+ PATH: [...binDirs(root), process.env.PATH].join(path.delimiter),
513
+ },
514
+ });
515
+ child.on("error", reject);
516
+ child.on("exit", (code) =>
517
+ code === 0
518
+ ? resolve()
519
+ : reject(
520
+ new Error(
521
+ `Envio Subgraph ran \`${step}\` from the project's codegen script, and it\n` +
522
+ `exited with ${code} — the error above is the script's own.`,
523
+ ),
524
+ ),
525
+ );
526
+ });
527
+ }
528
+
529
+ /**
530
+ * `ENVIO_SUBGRAPH_CODEGEN`: `bundled` always writes the tree itself, `graph-cli`
531
+ * only ever asks the project's graph-cli, and the default (`auto`) asks
532
+ * graph-cli first and writes the tree itself when graph-cli is missing or fails.
533
+ */
534
+ function codegenMode(): "auto" | "bundled" | "graph-cli" {
535
+ const mode = process.env.ENVIO_SUBGRAPH_CODEGEN;
536
+ return mode === "bundled" || mode === "graph-cli" ? mode : "auto";
537
+ }
538
+
539
+ /** envio's own `graph codegen`: needs no install, only the manifest, schema and ABIs. */
540
+ export function bundledCodegen(root: string, dir: string, manifestFile: string): string[] {
541
+ let files: string[] = [];
542
+ for (const { manifest, outputDir, own } of codegenPlan(root, manifestFile, dir)) {
543
+ if (own) {
544
+ files = subgraphBundledCodegen(path.resolve(root, manifest), outputDir).files;
545
+ continue;
546
+ }
547
+ // Another manifest's half of the tree is the codegen script's business;
548
+ // one that can't be generated mustn't stop this manifest's.
549
+ try {
550
+ subgraphBundledCodegen(path.resolve(root, manifest), outputDir);
551
+ } catch (error) {
552
+ warn(
553
+ `Envio Subgraph couldn't write the generated code of ${manifest}, which the codegen script also generates: ` +
554
+ `${error instanceof Error ? error.message : error}`,
555
+ );
556
+ }
557
+ }
558
+ info(`Envio Subgraph wrote ${files.length} generated files to "${path.relative(root, dir)}/" without graph-cli.`);
559
+ return files;
560
+ }
561
+
562
+ /**
563
+ * The project's other codegen steps (Lido writes `generated/parserData.ts` with
564
+ * a script of its own), which bundled codegen can't replace. A failure is a
565
+ * warning: the mappings that don't import their output still run.
566
+ */
567
+ async function runOtherSteps(root: string, skip: SkippedSteps): Promise<void> {
568
+ for (const step of codegenSteps(root, skip) ?? []) {
569
+ if (GRAPH_CODEGEN.test(step) || isInstall(step)) continue;
570
+ try {
571
+ await runScriptStep(root, step);
572
+ } catch (error) {
573
+ warn(`${error instanceof Error ? error.message : error}`);
574
+ }
575
+ }
576
+ }
577
+
578
+ /**
579
+ * The generated code is usually gitignored, so it's built when missing. `graph
580
+ * codegen` itself runs in-process, sized and without migrations, wherever the
581
+ * project's codegen script puts it among its steps; when graph-cli is missing
582
+ * or fails, envio writes the tree itself.
583
+ */
584
+ export async function ensureGeneratedCode(
585
+ root: string,
586
+ dir: string,
587
+ manifestFile = "subgraph.yaml",
588
+ mappingFiles: string[] = [],
589
+ options: CodegenOptions = {},
590
+ ): Promise<void> {
591
+ await buildGeneratedCode(root, dir, manifestFile, mappingFiles, options);
592
+ if (codegenMode() !== "graph-cli") {
593
+ fillFromOtherManifests(root, dir, manifestFile, mappingFiles);
594
+ fillFromImportedNames(root, dir, mappingFiles);
595
+ }
596
+ }
597
+
598
+ /** The generated ABI modules under `dir`: `<data source>/<ABI>.ts` and `templates/<template>/<ABI>.ts`. */
599
+ function generatedModules(dir: string): string[] {
600
+ const subdirs = (parent: string) =>
601
+ existsSync(parent)
602
+ ? readdirSync(parent, { withFileTypes: true })
603
+ .filter((entry) => entry.isDirectory())
604
+ .map((entry) => entry.name)
605
+ : [];
606
+ const modulesIn = (relative: string) =>
607
+ readdirSync(path.join(dir, relative))
608
+ .filter((file) => file.endsWith(".ts") && file !== "index.ts")
609
+ .map((file) => path.join(relative, file));
610
+ return [
611
+ ...subdirs(dir)
612
+ .filter((name) => name !== "templates")
613
+ .flatMap(modulesIn),
614
+ ...subdirs(path.join(dir, "templates")).flatMap((name) => modulesIn(path.join("templates", name))),
615
+ ].sort();
616
+ }
617
+
618
+ /**
619
+ * A generated module the mappings import that graph codegen didn't write and no
620
+ * manifest or ABI accounts for, when every generated module exporting all the
621
+ * names they import from it is the same code: tornado-subgraph imports its event
622
+ * classes from the generated directory itself (`from '../generated'`; its node
623
+ * manifest script writes an index.ts re-exporting a data source's module), and
624
+ * balancer's cow-amm LOG_NEW_POOL from src/types/Factory/Factory, from when its
625
+ * factory data source was "Factory" with the ABI named "Factory" (now Factory3
626
+ * and Factory4 with BFactory). The file is written as a re-export of that
627
+ * module, with a warning; anything else keeps the load error.
628
+ */
629
+ function fillFromImportedNames(root: string, dir: string, mappingFiles: string[]): void {
630
+ dir = path.resolve(dir);
631
+ if (!existsSync(dir)) return;
632
+ const wanted = new Map<string, { names: Set<string>; importers: Set<string> }>();
633
+ for (const { file } of scanImports(root, mappingFiles)) {
634
+ const source = readFileSync(file, "utf8");
635
+ for (const [, clause, specifier] of source.matchAll(/\bimport\s*\{([^}]*)\}\s*from\s*["'](\.\.?\/[^"']*)["']/g)) {
636
+ const target = path.resolve(path.dirname(file), specifier.replace(/\/$/, "").replace(/\.ts$/, ""));
637
+ if (target !== dir && !target.startsWith(dir + path.sep)) continue;
638
+ if ([`${target}.ts`, path.join(target, "index.ts")].some((candidate) => existsSync(candidate))) continue;
639
+ const module = target === dir || existsSync(target) ? path.join(target, "index.ts") : `${target}.ts`;
640
+ // Only where graph codegen writes ABI modules, or the directory's own
641
+ // index: schema.ts and templates.ts hold entity and template classes.
642
+ const parts = path.relative(dir, module).split(path.sep);
643
+ const abiModule =
644
+ parts.length === 1
645
+ ? parts[0] === "index.ts"
646
+ : parts.length === 2
647
+ ? parts[0] !== "templates"
648
+ : parts.length === 3 && parts[0] === "templates";
649
+ if (!abiModule) continue;
650
+ const entry = wanted.get(module) ?? { names: new Set<string>(), importers: new Set<string>() };
651
+ for (const name of clause.split(",")) {
652
+ const imported = name.trim().split(/\s+as\s+/)[0];
653
+ if (imported) entry.names.add(imported);
654
+ }
655
+ entry.importers.add(path.relative(root, file));
656
+ wanted.set(module, entry);
657
+ }
658
+ }
659
+ if (wanted.size === 0) return;
660
+ const exportsOf = new Map(
661
+ generatedModules(dir).map((module) => {
662
+ const source = readFileSync(path.join(dir, module), "utf8");
663
+ return [module, { source, classes: new Set([...source.matchAll(/^export class (\w+)/gm)].map(([, name]) => name)) }];
664
+ }),
665
+ );
666
+ for (const [module, { names, importers }] of wanted) {
667
+ if (names.size === 0) continue;
668
+ const sibling = ([candidate]: [string, unknown]) => (path.join(dir, path.dirname(candidate)) === path.dirname(module) ? 0 : 1);
669
+ const found = [...exportsOf]
670
+ .filter(([, { classes }]) => [...names].every((name) => classes.has(name)))
671
+ .sort((a, b) => sibling(a) - sibling(b));
672
+ if (new Set(found.map(([, { source }]) => source)).size !== 1) continue;
673
+ const from = path.join(dir, found[0][0]);
674
+ let specifier = path.relative(path.dirname(module), from).replace(/\.ts$/, "").split(path.sep).join("/");
675
+ if (!specifier.startsWith(".")) specifier = `./${specifier}`;
676
+ mkdirSync(path.dirname(module), { recursive: true });
677
+ writeFileSync(module, `export * from "${specifier}";\n`);
678
+ const files = [...importers];
679
+ warn(
680
+ `Envio Subgraph wrote ${path.relative(root, module)}, re-exporting ${path.relative(root, from)}: ` +
681
+ `${files.join(", ")} import${files.length === 1 ? "s" : ""} ${[...names].join(", ")} from ` +
682
+ `${path.relative(root, module).replace(/(\/index)?\.ts$/, "")}, which graph codegen doesn't write for any ` +
683
+ "manifest of the project, so `graph build` fails on a clean checkout. " +
684
+ (found.length === 1
685
+ ? "That module is the only generated one with those names; "
686
+ : "Every generated module with those names is the same code; ") +
687
+ "the tree this subgraph was built with had the file from an earlier manifest or the project's own scripts.",
688
+ );
689
+ }
690
+ }
691
+
692
+ /**
693
+ * graph codegen never cleans its output directory, so the tree a project with
694
+ * one manifest per network was built with holds the data sources of every
695
+ * manifest it generated, and its mappings may import them: WOOFi's bsc mappings
696
+ * import generated/WooRouterV1_3/ (avax.yaml), DODO's generated/DVMFactory/
697
+ * (mainnet). `graph build` of the one manifest fails on a clean checkout, but the
698
+ * deployed subgraph was compiled against that tree. The missing files are
699
+ * written from the other manifest that declares them, with a warning.
700
+ */
701
+ function fillFromOtherManifests(root: string, dir: string, manifestFile: string, mappingFiles: string[]): void {
702
+ const missing = missingGeneratedFiles(root, mappingFiles, dir);
703
+ if (missing.length === 0) return;
704
+ const wanted = missing.map((file) => {
705
+ const relative = path.relative(dir, path.resolve(root, file));
706
+ return relative.endsWith(".ts") ? relative : `${relative}.ts`;
707
+ });
708
+ const filled = subgraphFillGenerated(root, path.resolve(root, manifestFile), dir, wanted);
709
+ const shown = (file: string) => path.join(path.relative(root, dir), file);
710
+ const bySource = new Map<string, string[]>();
711
+ for (const { file, source, abiName, renamedFrom, undeclared } of filled) {
712
+ if (undeclared) {
713
+ warn(
714
+ `Envio Subgraph wrote ${shown(file)} from ${source}, an ABI file no manifest of the project names any more: ` +
715
+ `the mappings import it, and no manifest has a data source or template named ` +
716
+ `"${path.basename(path.dirname(file))}" or an ABI named "${abiName}", so \`graph build\` fails on a clean ` +
717
+ "checkout. The tree this subgraph was built with had it from when one did.",
718
+ );
719
+ } else if (renamedFrom) {
720
+ const name = path.basename(path.dirname(file));
721
+ warn(
722
+ `Envio Subgraph wrote ${shown(file)} from data source ${name}'s ABI "${renamedFrom}" (${source}) under the ` +
723
+ `name ${name}: the mappings import it, and graph codegen writes that ABI to ${name}/${renamedFrom}.ts now, ` +
724
+ "so `graph build` fails on a clean checkout. The tree this subgraph was built with had it from when the " +
725
+ "ABI bore the data source's name.",
726
+ );
727
+ } else if (abiName) {
728
+ warn(
729
+ `Envio Subgraph wrote ${shown(file)} from the ABI "${abiName}" (${source}): the mappings import it, and no ` +
730
+ `manifest of the project has a data source or template named "${path.basename(path.dirname(file))}" that ` +
731
+ "graph codegen would write it for, so `graph build` fails on a clean checkout. The tree this subgraph was " +
732
+ "built with had it from an earlier manifest; graph codegen writes the same file for every data source with " +
733
+ "that ABI.",
734
+ );
735
+ } else {
736
+ bySource.set(source, [...(bySource.get(source) ?? []), shown(file)]);
737
+ }
738
+ }
739
+ for (const [manifest, files] of bySource) {
740
+ const it = files.length === 1 ? "it" : "them";
741
+ warn(
742
+ `Envio Subgraph wrote ${files.join(", ")} from ${manifest}: the mappings import ${it}, and ` +
743
+ `\`graph codegen ${manifestFile}\` doesn't write ${it}, so \`graph build\` of ${manifestFile} fails on a ` +
744
+ `clean checkout. The tree this subgraph was built with had ${it} from an earlier \`graph codegen ${manifest}\`.`,
745
+ );
746
+ }
747
+ }
748
+
749
+ async function buildGeneratedCode(
750
+ root: string,
751
+ dir: string,
752
+ manifestFile: string,
753
+ mappingFiles: string[],
754
+ { pathFixes = [], ...skip }: CodegenOptions,
755
+ ): Promise<void> {
756
+ const mode = codegenMode();
757
+ if (mode === "bundled") {
758
+ bundledCodegen(root, dir, manifestFile);
759
+ await runOtherSteps(root, skip);
760
+ return;
761
+ }
762
+ const graphCli = graphCliPackage(root);
763
+ const complete = existsSync(dir) && missingGeneratedFiles(root, mappingFiles, dir).length === 0;
764
+ if (complete) {
765
+ // `envio start` writes the bundled tree before the runtime loads.
766
+ if (!graphCli && !subgraphBundledCodegenCurrent(path.resolve(root, manifestFile), dir)) {
767
+ warn(
768
+ `Envio Subgraph is running the generated code in "${path.relative(root, dir)}/" as it is: ` +
769
+ "@graphprotocol/graph-cli isn't installed, so it can't be rebuilt. If subgraph.yaml, the " +
770
+ "schema or an ABI changed since it was generated, install the project's dependencies.",
771
+ );
772
+ }
773
+ return;
774
+ }
775
+ let bundledFailed = false;
776
+ const needsGraphCli = (codegenSteps(root, skip) ?? []).some((step) => codegenFlags(root, step).length > 0);
777
+ if (!graphCli && mode === "auto" && !needsGraphCli) {
778
+ // Nothing to build when no mapping imports a generated file.
779
+ if (missingGeneratedFiles(root, mappingFiles, dir).length === 0) return;
780
+ info("Envio Subgraph found no @graphprotocol/graph-cli in the project; writing the generated code itself.");
781
+ try {
782
+ bundledCodegen(root, dir, manifestFile);
783
+ await runOtherSteps(root, skip);
784
+ return;
785
+ } catch (error) {
786
+ bundledFailed = true;
787
+ warn(
788
+ `Envio Subgraph couldn't write the generated code itself: ${error instanceof Error ? error.message : error}. ` +
789
+ "Trying a graph-cli fetched with npm.",
790
+ );
791
+ }
792
+ }
793
+
794
+ try {
795
+ let ranOwn = false;
796
+ for (const step of codegenSteps(root, skip) ?? ["graph codegen"]) {
797
+ if (isInstall(step)) {
798
+ info(
799
+ `Envio Subgraph skipped the codegen script step \`${step}\`: installing the project's dependencies is ` +
800
+ "done before envio starts.",
801
+ );
802
+ continue;
803
+ }
804
+ if (!GRAPH_CODEGEN.test(step)) {
805
+ try {
806
+ await runScriptStep(root, step);
807
+ } catch (error) {
808
+ // A step that needs the project's own tools (truffle, a mustache
809
+ // render) can't run when its install never worked; `graph codegen`
810
+ // still can, so the failure is reported rather than fatal.
811
+ if (binDirs(root).some((dir) => existsSync(dir))) throw error;
812
+ warn(
813
+ `Envio Subgraph skipped the codegen script step \`${step}\`: the project's dependencies aren't installed, so ` +
814
+ "its tools are missing. Running graph codegen without it.",
815
+ );
816
+ }
817
+ continue;
818
+ }
819
+ const { manifest, outputDir, own } = stepTarget(root, manifestFile, dir, step);
820
+ ranOwn ||= own;
821
+ // With no graph-cli anywhere, the mapping that imports the missing code
822
+ // reports it, naming that mapping.
823
+ // The fixes are for paths as envio's manifest writes them; another
824
+ // manifest's same path may be another file.
825
+ const fixes = own ? pathFixes : [];
826
+ const needed = importsFrom(root, mappingFiles, dir);
827
+ if (!(await runCodegen(root, manifest, outputDir, needed, codegenFlags(root, step), fixes))) return;
828
+ }
829
+ const needed = importsFrom(root, mappingFiles, dir);
830
+ if (!ranOwn && !(await runCodegen(root, manifestFile, dir, needed, [], pathFixes))) return;
831
+ } catch (error) {
832
+ if (mode === "graph-cli" || bundledFailed) throw error;
833
+ warn(
834
+ "Envio Subgraph: the project's `graph codegen` failed (see above); writing the generated code " +
835
+ "itself. Set ENVIO_SUBGRAPH_CODEGEN=graph-cli to stop at the failure instead.",
836
+ );
837
+ bundledCodegen(root, dir, manifestFile);
838
+ await runOtherSteps(root, skip);
839
+ }
840
+ }
841
+
842
+ /**
843
+ * `graph build` compiles the mappings with `asc` against the real
844
+ * `@graphprotocol/graph-ts`. That is the type check a subgraph project already
845
+ * has, and running it in `envio dev` keeps the feedback loop the developer
846
+ * knows — a type error reads the same here as it does on Graph Node.
847
+ *
848
+ * Only in dev, and only when something it reads has changed: it is an
849
+ * AssemblyScript compile, not something to pay on every restart.
850
+ */
851
+ export async function typeCheckMappings(
852
+ root: string,
853
+ manifestFile = "subgraph.yaml",
854
+ pathFixes: [string, string][] = [],
855
+ ): Promise<void> {
856
+ const graphCli = graphCliPackage(root);
857
+ if (!graphCli) return;
858
+
859
+ const inputs = [manifestFile, "schema.graphql", "src", "abis"]
860
+ .map((entry) => path.join(root, entry))
861
+ .filter((entry) => existsSync(entry))
862
+ .map((entry) => fingerprint(entry))
863
+ .join("|");
864
+
865
+ const stamp = path.join(root, ".envio", "graph-build.stamp");
866
+ if (existsSync(stamp) && readFileSync(stamp, "utf8") === inputs) return;
867
+
868
+ // The manifest spelled the repo's way, as codegen got it; its apiVersion
869
+ // as written, which is what the compile checks against.
870
+ await withCodegenManifest(
871
+ path.resolve(root, manifestFile),
872
+ pathFixes,
873
+ (manifest) =>
874
+ runOrThrow(
875
+ graphCli,
876
+ root,
877
+ manifest,
878
+ "build",
879
+ path.resolve(root, "build"),
880
+ "Envio Subgraph ran `graph build` to type-check the mappings, and it\n" +
881
+ "failed — the error above comes from The Graph's own AssemblyScript\n" +
882
+ "compiler, so fix it there and rerun.",
883
+ ),
884
+ { raiseApiVersion: false },
885
+ );
886
+
887
+ mkdirSync(path.dirname(stamp), { recursive: true });
888
+ writeFileSync(stamp, inputs);
889
+ }
890
+
891
+ function fingerprint(entry: string): string {
892
+ const stats = statSync(entry);
893
+ if (!stats.isDirectory()) {
894
+ return `${entry}:${stats.mtimeMs}:${stats.size}`;
895
+ }
896
+ return readdirSync(entry)
897
+ .sort()
898
+ .map((child) => fingerprint(path.join(entry, child)))
899
+ .join(",");
900
+ }