@0xsequence/catapult 1.1.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 (393) hide show
  1. package/.eslintrc.json +29 -0
  2. package/.github/workflows/ci.yml +181 -0
  3. package/CONCEPT.md +24 -0
  4. package/README.md +772 -0
  5. package/contracts/checked-call.huff +65 -0
  6. package/dist/cli.d.ts +3 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +16 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/commands/common.d.ts +11 -0
  11. package/dist/commands/common.d.ts.map +1 -0
  12. package/dist/commands/common.js +73 -0
  13. package/dist/commands/common.js.map +1 -0
  14. package/dist/commands/dry.d.ts +3 -0
  15. package/dist/commands/dry.d.ts.map +1 -0
  16. package/dist/commands/dry.js +171 -0
  17. package/dist/commands/dry.js.map +1 -0
  18. package/dist/commands/etherscan.d.ts +3 -0
  19. package/dist/commands/etherscan.d.ts.map +1 -0
  20. package/dist/commands/etherscan.js +323 -0
  21. package/dist/commands/etherscan.js.map +1 -0
  22. package/dist/commands/index.d.ts +6 -0
  23. package/dist/commands/index.d.ts.map +1 -0
  24. package/dist/commands/index.js +22 -0
  25. package/dist/commands/index.js.map +1 -0
  26. package/dist/commands/list.d.ts +3 -0
  27. package/dist/commands/list.d.ts.map +1 -0
  28. package/dist/commands/list.js +259 -0
  29. package/dist/commands/list.js.map +1 -0
  30. package/dist/commands/run.d.ts +3 -0
  31. package/dist/commands/run.d.ts.map +1 -0
  32. package/dist/commands/run.js +96 -0
  33. package/dist/commands/run.js.map +1 -0
  34. package/dist/commands/utils.d.ts +3 -0
  35. package/dist/commands/utils.d.ts.map +1 -0
  36. package/dist/commands/utils.js +46 -0
  37. package/dist/commands/utils.js.map +1 -0
  38. package/dist/index.d.ts +4 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +58 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/lib/__tests__/deployer-events.spec.d.ts +2 -0
  43. package/dist/lib/__tests__/deployer-events.spec.d.ts.map +1 -0
  44. package/dist/lib/__tests__/deployer-events.spec.js +260 -0
  45. package/dist/lib/__tests__/deployer-events.spec.js.map +1 -0
  46. package/dist/lib/__tests__/deployer.spec.d.ts +2 -0
  47. package/dist/lib/__tests__/deployer.spec.d.ts.map +1 -0
  48. package/dist/lib/__tests__/deployer.spec.js +884 -0
  49. package/dist/lib/__tests__/deployer.spec.js.map +1 -0
  50. package/dist/lib/__tests__/network-utils.spec.d.ts +2 -0
  51. package/dist/lib/__tests__/network-utils.spec.d.ts.map +1 -0
  52. package/dist/lib/__tests__/network-utils.spec.js +140 -0
  53. package/dist/lib/__tests__/network-utils.spec.js.map +1 -0
  54. package/dist/lib/contracts/__tests__/repository.spec.d.ts +2 -0
  55. package/dist/lib/contracts/__tests__/repository.spec.d.ts.map +1 -0
  56. package/dist/lib/contracts/__tests__/repository.spec.js +321 -0
  57. package/dist/lib/contracts/__tests__/repository.spec.js.map +1 -0
  58. package/dist/lib/contracts/repository.d.ts +27 -0
  59. package/dist/lib/contracts/repository.d.ts.map +1 -0
  60. package/dist/lib/contracts/repository.js +241 -0
  61. package/dist/lib/contracts/repository.js.map +1 -0
  62. package/dist/lib/core/__tests__/engine.spec.d.ts +2 -0
  63. package/dist/lib/core/__tests__/engine.spec.d.ts.map +1 -0
  64. package/dist/lib/core/__tests__/engine.spec.js +1212 -0
  65. package/dist/lib/core/__tests__/engine.spec.js.map +1 -0
  66. package/dist/lib/core/__tests__/graph.spec.d.ts +2 -0
  67. package/dist/lib/core/__tests__/graph.spec.d.ts.map +1 -0
  68. package/dist/lib/core/__tests__/graph.spec.js +116 -0
  69. package/dist/lib/core/__tests__/graph.spec.js.map +1 -0
  70. package/dist/lib/core/__tests__/json-integration.spec.d.ts +2 -0
  71. package/dist/lib/core/__tests__/json-integration.spec.d.ts.map +1 -0
  72. package/dist/lib/core/__tests__/json-integration.spec.js +300 -0
  73. package/dist/lib/core/__tests__/json-integration.spec.js.map +1 -0
  74. package/dist/lib/core/__tests__/loader.spec.d.ts +2 -0
  75. package/dist/lib/core/__tests__/loader.spec.d.ts.map +1 -0
  76. package/dist/lib/core/__tests__/loader.spec.js +288 -0
  77. package/dist/lib/core/__tests__/loader.spec.js.map +1 -0
  78. package/dist/lib/core/__tests__/multi-platform-verification.spec.d.ts +2 -0
  79. package/dist/lib/core/__tests__/multi-platform-verification.spec.d.ts.map +1 -0
  80. package/dist/lib/core/__tests__/multi-platform-verification.spec.js +342 -0
  81. package/dist/lib/core/__tests__/multi-platform-verification.spec.js.map +1 -0
  82. package/dist/lib/core/__tests__/resolver.spec.d.ts +2 -0
  83. package/dist/lib/core/__tests__/resolver.spec.d.ts.map +1 -0
  84. package/dist/lib/core/__tests__/resolver.spec.js +1367 -0
  85. package/dist/lib/core/__tests__/resolver.spec.js.map +1 -0
  86. package/dist/lib/core/__tests__/static-action.spec.d.ts +2 -0
  87. package/dist/lib/core/__tests__/static-action.spec.d.ts.map +1 -0
  88. package/dist/lib/core/__tests__/static-action.spec.js +136 -0
  89. package/dist/lib/core/__tests__/static-action.spec.js.map +1 -0
  90. package/dist/lib/core/context.d.ts +29 -0
  91. package/dist/lib/core/context.d.ts.map +1 -0
  92. package/dist/lib/core/context.js +88 -0
  93. package/dist/lib/core/context.js.map +1 -0
  94. package/dist/lib/core/engine.d.ts +25 -0
  95. package/dist/lib/core/engine.d.ts.map +1 -0
  96. package/dist/lib/core/engine.js +1191 -0
  97. package/dist/lib/core/engine.js.map +1 -0
  98. package/dist/lib/core/graph.d.ts +18 -0
  99. package/dist/lib/core/graph.d.ts.map +1 -0
  100. package/dist/lib/core/graph.js +158 -0
  101. package/dist/lib/core/graph.js.map +1 -0
  102. package/dist/lib/core/loader.d.ts +25 -0
  103. package/dist/lib/core/loader.d.ts.map +1 -0
  104. package/dist/lib/core/loader.js +248 -0
  105. package/dist/lib/core/loader.js.map +1 -0
  106. package/dist/lib/core/resolver.d.ts +20 -0
  107. package/dist/lib/core/resolver.d.ts.map +1 -0
  108. package/dist/lib/core/resolver.js +307 -0
  109. package/dist/lib/core/resolver.js.map +1 -0
  110. package/dist/lib/deployer.d.ts +39 -0
  111. package/dist/lib/deployer.d.ts.map +1 -0
  112. package/dist/lib/deployer.js +533 -0
  113. package/dist/lib/deployer.js.map +1 -0
  114. package/dist/lib/events/__tests__/event-system.spec.d.ts +2 -0
  115. package/dist/lib/events/__tests__/event-system.spec.d.ts.map +1 -0
  116. package/dist/lib/events/__tests__/event-system.spec.js +256 -0
  117. package/dist/lib/events/__tests__/event-system.spec.js.map +1 -0
  118. package/dist/lib/events/cli-adapter.d.ts +13 -0
  119. package/dist/lib/events/cli-adapter.d.ts.map +1 -0
  120. package/dist/lib/events/cli-adapter.js +244 -0
  121. package/dist/lib/events/cli-adapter.js.map +1 -0
  122. package/dist/lib/events/emitter.d.ts +11 -0
  123. package/dist/lib/events/emitter.d.ts.map +1 -0
  124. package/dist/lib/events/emitter.js +29 -0
  125. package/dist/lib/events/emitter.js.map +1 -0
  126. package/dist/lib/events/index.d.ts +4 -0
  127. package/dist/lib/events/index.d.ts.map +1 -0
  128. package/dist/lib/events/index.js +20 -0
  129. package/dist/lib/events/index.js.map +1 -0
  130. package/dist/lib/events/types.d.ts +368 -0
  131. package/dist/lib/events/types.d.ts.map +1 -0
  132. package/dist/lib/events/types.js +3 -0
  133. package/dist/lib/events/types.js.map +1 -0
  134. package/dist/lib/index.d.ts +5 -0
  135. package/dist/lib/index.d.ts.map +1 -0
  136. package/dist/lib/index.js +44 -0
  137. package/dist/lib/index.js.map +1 -0
  138. package/dist/lib/network-loader.d.ts +3 -0
  139. package/dist/lib/network-loader.d.ts.map +1 -0
  140. package/dist/lib/network-loader.js +80 -0
  141. package/dist/lib/network-loader.js.map +1 -0
  142. package/dist/lib/network-match.d.ts +3 -0
  143. package/dist/lib/network-match.d.ts.map +1 -0
  144. package/dist/lib/network-match.js +62 -0
  145. package/dist/lib/network-match.js.map +1 -0
  146. package/dist/lib/network-utils.d.ts +4 -0
  147. package/dist/lib/network-utils.d.ts.map +1 -0
  148. package/dist/lib/network-utils.js +39 -0
  149. package/dist/lib/network-utils.js.map +1 -0
  150. package/dist/lib/parsers/__tests__/buildinfo.spec.d.ts +2 -0
  151. package/dist/lib/parsers/__tests__/buildinfo.spec.d.ts.map +1 -0
  152. package/dist/lib/parsers/__tests__/buildinfo.spec.js +132 -0
  153. package/dist/lib/parsers/__tests__/buildinfo.spec.js.map +1 -0
  154. package/dist/lib/parsers/__tests__/job.spec.d.ts +2 -0
  155. package/dist/lib/parsers/__tests__/job.spec.d.ts.map +1 -0
  156. package/dist/lib/parsers/__tests__/job.spec.js +318 -0
  157. package/dist/lib/parsers/__tests__/job.spec.js.map +1 -0
  158. package/dist/lib/parsers/__tests__/template.spec.d.ts +2 -0
  159. package/dist/lib/parsers/__tests__/template.spec.d.ts.map +1 -0
  160. package/dist/lib/parsers/__tests__/template.spec.js +126 -0
  161. package/dist/lib/parsers/__tests__/template.spec.js.map +1 -0
  162. package/dist/lib/parsers/artifact/__tests__/artifact.spec.d.ts +2 -0
  163. package/dist/lib/parsers/artifact/__tests__/artifact.spec.d.ts.map +1 -0
  164. package/dist/lib/parsers/artifact/__tests__/artifact.spec.js +128 -0
  165. package/dist/lib/parsers/artifact/__tests__/artifact.spec.js.map +1 -0
  166. package/dist/lib/parsers/artifact/foundry-1.2.d.ts +3 -0
  167. package/dist/lib/parsers/artifact/foundry-1.2.d.ts.map +1 -0
  168. package/dist/lib/parsers/artifact/foundry-1.2.js +82 -0
  169. package/dist/lib/parsers/artifact/foundry-1.2.js.map +1 -0
  170. package/dist/lib/parsers/artifact/index.d.ts +3 -0
  171. package/dist/lib/parsers/artifact/index.d.ts.map +1 -0
  172. package/dist/lib/parsers/artifact/index.js +17 -0
  173. package/dist/lib/parsers/artifact/index.js.map +1 -0
  174. package/dist/lib/parsers/artifact/types.d.ts +3 -0
  175. package/dist/lib/parsers/artifact/types.d.ts.map +1 -0
  176. package/dist/lib/parsers/artifact/types.js +3 -0
  177. package/dist/lib/parsers/artifact/types.js.map +1 -0
  178. package/dist/lib/parsers/buildinfo.d.ts +5 -0
  179. package/dist/lib/parsers/buildinfo.d.ts.map +1 -0
  180. package/dist/lib/parsers/buildinfo.js +85 -0
  181. package/dist/lib/parsers/buildinfo.js.map +1 -0
  182. package/dist/lib/parsers/constants.d.ts +4 -0
  183. package/dist/lib/parsers/constants.d.ts.map +1 -0
  184. package/dist/lib/parsers/constants.js +45 -0
  185. package/dist/lib/parsers/constants.js.map +1 -0
  186. package/dist/lib/parsers/index.d.ts +5 -0
  187. package/dist/lib/parsers/index.d.ts.map +1 -0
  188. package/dist/lib/parsers/index.js +21 -0
  189. package/dist/lib/parsers/index.js.map +1 -0
  190. package/dist/lib/parsers/job.d.ts +3 -0
  191. package/dist/lib/parsers/job.d.ts.map +1 -0
  192. package/dist/lib/parsers/job.js +74 -0
  193. package/dist/lib/parsers/job.js.map +1 -0
  194. package/dist/lib/parsers/template.d.ts +3 -0
  195. package/dist/lib/parsers/template.d.ts.map +1 -0
  196. package/dist/lib/parsers/template.js +91 -0
  197. package/dist/lib/parsers/template.js.map +1 -0
  198. package/dist/lib/std/templates/assured-deployment.yaml +45 -0
  199. package/dist/lib/std/templates/erc-2470.yaml +67 -0
  200. package/dist/lib/std/templates/min-balance.yaml +32 -0
  201. package/dist/lib/std/templates/nano-universal-deployer.yaml +59 -0
  202. package/dist/lib/std/templates/raw-erc-2470.yaml +59 -0
  203. package/dist/lib/std/templates/raw-nano-universal-deployer.yaml +51 -0
  204. package/dist/lib/std/templates/raw-sequence-universal-deployer-2.yaml +48 -0
  205. package/dist/lib/std/templates/sequence-universal-deployer-2.yaml +57 -0
  206. package/dist/lib/types/__tests__/json-request-action.spec.d.ts +2 -0
  207. package/dist/lib/types/__tests__/json-request-action.spec.d.ts.map +1 -0
  208. package/dist/lib/types/__tests__/json-request-action.spec.js +219 -0
  209. package/dist/lib/types/__tests__/json-request-action.spec.js.map +1 -0
  210. package/dist/lib/types/__tests__/read-json-value.spec.d.ts +2 -0
  211. package/dist/lib/types/__tests__/read-json-value.spec.d.ts.map +1 -0
  212. package/dist/lib/types/__tests__/read-json-value.spec.js +233 -0
  213. package/dist/lib/types/__tests__/read-json-value.spec.js.map +1 -0
  214. package/dist/lib/types/actions.d.ts +74 -0
  215. package/dist/lib/types/actions.d.ts.map +1 -0
  216. package/dist/lib/types/actions.js +18 -0
  217. package/dist/lib/types/actions.js.map +1 -0
  218. package/dist/lib/types/artifacts.d.ts +15 -0
  219. package/dist/lib/types/artifacts.d.ts.map +1 -0
  220. package/dist/lib/types/artifacts.js +3 -0
  221. package/dist/lib/types/artifacts.js.map +1 -0
  222. package/dist/lib/types/buildinfo.d.ts +112 -0
  223. package/dist/lib/types/buildinfo.d.ts.map +1 -0
  224. package/dist/lib/types/buildinfo.js +3 -0
  225. package/dist/lib/types/buildinfo.js.map +1 -0
  226. package/dist/lib/types/conditions.d.ts +17 -0
  227. package/dist/lib/types/conditions.d.ts.map +1 -0
  228. package/dist/lib/types/conditions.js +21 -0
  229. package/dist/lib/types/conditions.js.map +1 -0
  230. package/dist/lib/types/contracts.d.ts +14 -0
  231. package/dist/lib/types/contracts.d.ts.map +1 -0
  232. package/dist/lib/types/contracts.js +3 -0
  233. package/dist/lib/types/contracts.js.map +1 -0
  234. package/dist/lib/types/definitions.d.ts +51 -0
  235. package/dist/lib/types/definitions.d.ts.map +1 -0
  236. package/dist/lib/types/definitions.js +3 -0
  237. package/dist/lib/types/definitions.js.map +1 -0
  238. package/dist/lib/types/index.d.ts +9 -0
  239. package/dist/lib/types/index.d.ts.map +1 -0
  240. package/dist/lib/types/index.js +25 -0
  241. package/dist/lib/types/index.js.map +1 -0
  242. package/dist/lib/types/network.d.ts +9 -0
  243. package/dist/lib/types/network.d.ts.map +1 -0
  244. package/dist/lib/types/network.js +3 -0
  245. package/dist/lib/types/network.js.map +1 -0
  246. package/dist/lib/types/project.d.ts +5 -0
  247. package/dist/lib/types/project.d.ts.map +1 -0
  248. package/dist/lib/types/project.js +3 -0
  249. package/dist/lib/types/project.js.map +1 -0
  250. package/dist/lib/types/task.d.ts +9 -0
  251. package/dist/lib/types/task.d.ts.map +1 -0
  252. package/dist/lib/types/task.js +3 -0
  253. package/dist/lib/types/task.js.map +1 -0
  254. package/dist/lib/types/values.d.ts +78 -0
  255. package/dist/lib/types/values.d.ts.map +1 -0
  256. package/dist/lib/types/values.js +3 -0
  257. package/dist/lib/types/values.js.map +1 -0
  258. package/dist/lib/utils/validation.d.ts +5 -0
  259. package/dist/lib/utils/validation.d.ts.map +1 -0
  260. package/dist/lib/utils/validation.js +77 -0
  261. package/dist/lib/utils/validation.js.map +1 -0
  262. package/dist/lib/validation/contract-references.d.ts +12 -0
  263. package/dist/lib/validation/contract-references.d.ts.map +1 -0
  264. package/dist/lib/validation/contract-references.js +112 -0
  265. package/dist/lib/validation/contract-references.js.map +1 -0
  266. package/dist/lib/validation/index.d.ts +1 -0
  267. package/dist/lib/validation/index.d.ts.map +1 -0
  268. package/dist/lib/validation/index.js +2 -0
  269. package/dist/lib/validation/index.js.map +1 -0
  270. package/dist/lib/verification/__tests__/etherscan.spec.d.ts +2 -0
  271. package/dist/lib/verification/__tests__/etherscan.spec.d.ts.map +1 -0
  272. package/dist/lib/verification/__tests__/etherscan.spec.js +565 -0
  273. package/dist/lib/verification/__tests__/etherscan.spec.js.map +1 -0
  274. package/dist/lib/verification/__tests__/sourcify.spec.d.ts +2 -0
  275. package/dist/lib/verification/__tests__/sourcify.spec.d.ts.map +1 -0
  276. package/dist/lib/verification/__tests__/sourcify.spec.js +212 -0
  277. package/dist/lib/verification/__tests__/sourcify.spec.js.map +1 -0
  278. package/dist/lib/verification/etherscan.d.ts +56 -0
  279. package/dist/lib/verification/etherscan.d.ts.map +1 -0
  280. package/dist/lib/verification/etherscan.js +340 -0
  281. package/dist/lib/verification/etherscan.js.map +1 -0
  282. package/dist/lib/verification/sourcify.d.ts +12 -0
  283. package/dist/lib/verification/sourcify.d.ts.map +1 -0
  284. package/dist/lib/verification/sourcify.js +227 -0
  285. package/dist/lib/verification/sourcify.js.map +1 -0
  286. package/eslint.config.js +48 -0
  287. package/examples/jobs/guards-v1.yaml +17 -0
  288. package/examples/jobs/sequence-seq-0001-patch.yaml +59 -0
  289. package/examples/jobs/sequence-v1.yaml +59 -0
  290. package/examples/templates/sequence-factory-v1.yaml +56 -0
  291. package/jest.config.js +25 -0
  292. package/package.json +68 -0
  293. package/src/cli.ts +17 -0
  294. package/src/commands/common.ts +61 -0
  295. package/src/commands/dry.ts +208 -0
  296. package/src/commands/etherscan.ts +360 -0
  297. package/src/commands/index.ts +5 -0
  298. package/src/commands/list.ts +249 -0
  299. package/src/commands/run.ts +136 -0
  300. package/src/commands/utils.ts +52 -0
  301. package/src/index.ts +67 -0
  302. package/src/lib/__tests__/deployer-events.spec.ts +338 -0
  303. package/src/lib/__tests__/deployer.spec.ts +1204 -0
  304. package/src/lib/__tests__/network-utils.spec.ts +181 -0
  305. package/src/lib/artifacts/__tests__/fixtures/contract1.json +19 -0
  306. package/src/lib/artifacts/__tests__/fixtures/contract2.json +19 -0
  307. package/src/lib/artifacts/__tests__/fixtures/duplicate-name.json +19 -0
  308. package/src/lib/artifacts/__tests__/fixtures/nested/nested-contract.json +18 -0
  309. package/src/lib/artifacts/__tests__/fixtures/not-an-artifact.json +8 -0
  310. package/src/lib/artifacts/__tests__/fixtures/readme.txt +2 -0
  311. package/src/lib/contracts/__tests__/repository.spec.ts +344 -0
  312. package/src/lib/contracts/repository.ts +313 -0
  313. package/src/lib/core/__tests__/engine.spec.ts +1514 -0
  314. package/src/lib/core/__tests__/graph.spec.ts +125 -0
  315. package/src/lib/core/__tests__/json-integration.spec.ts +360 -0
  316. package/src/lib/core/__tests__/loader.spec.ts +334 -0
  317. package/src/lib/core/__tests__/multi-platform-verification.spec.ts +406 -0
  318. package/src/lib/core/__tests__/resolver.spec.ts +1693 -0
  319. package/src/lib/core/__tests__/static-action.spec.ts +172 -0
  320. package/src/lib/core/context.ts +127 -0
  321. package/src/lib/core/engine.ts +1531 -0
  322. package/src/lib/core/graph.ts +252 -0
  323. package/src/lib/core/loader.ts +263 -0
  324. package/src/lib/core/resolver.ts +498 -0
  325. package/src/lib/deployer.ts +768 -0
  326. package/src/lib/events/__tests__/event-system.spec.ts +343 -0
  327. package/src/lib/events/cli-adapter.ts +325 -0
  328. package/src/lib/events/emitter.ts +62 -0
  329. package/src/lib/events/index.ts +3 -0
  330. package/src/lib/events/types.ts +469 -0
  331. package/src/lib/index.ts +14 -0
  332. package/src/lib/network-loader.ts +59 -0
  333. package/src/lib/network-utils.ts +64 -0
  334. package/src/lib/parsers/__tests__/buildinfo.spec.ts +122 -0
  335. package/src/lib/parsers/__tests__/fixtures/buildinfo/invalid-bytecode-buildinfo.json +62 -0
  336. package/src/lib/parsers/__tests__/fixtures/buildinfo/invalid-json.txt +2 -0
  337. package/src/lib/parsers/__tests__/fixtures/buildinfo/multi-contract-buildinfo.json +89 -0
  338. package/src/lib/parsers/__tests__/fixtures/buildinfo/no-contracts-buildinfo.json +17 -0
  339. package/src/lib/parsers/__tests__/fixtures/buildinfo/simple-buildinfo.json +63 -0
  340. package/src/lib/parsers/__tests__/fixtures/buildinfo/wrong-format.json +4 -0
  341. package/src/lib/parsers/__tests__/job.spec.ts +335 -0
  342. package/src/lib/parsers/__tests__/template.spec.ts +111 -0
  343. package/src/lib/parsers/artifact/__tests__/artifact.spec.ts +117 -0
  344. package/src/lib/parsers/artifact/__tests__/fixtures/empty-bytecode.json +5 -0
  345. package/src/lib/parsers/artifact/__tests__/fixtures/hardhat-artifact.json +67 -0
  346. package/src/lib/parsers/artifact/__tests__/fixtures/invalid-bytecode.json +5 -0
  347. package/src/lib/parsers/artifact/__tests__/fixtures/invalid-json.txt +11 -0
  348. package/src/lib/parsers/artifact/__tests__/fixtures/minimal-artifact.json +5 -0
  349. package/src/lib/parsers/artifact/__tests__/fixtures/missing-abi.json +4 -0
  350. package/src/lib/parsers/artifact/__tests__/fixtures/missing-bytecode.json +11 -0
  351. package/src/lib/parsers/artifact/__tests__/fixtures/missing-contract-name.json +11 -0
  352. package/src/lib/parsers/artifact/__tests__/fixtures/simple-artifact.json +40 -0
  353. package/src/lib/parsers/artifact/__tests__/fixtures/wrong-types.json +7 -0
  354. package/src/lib/parsers/artifact/foundry-1.2.ts +72 -0
  355. package/src/lib/parsers/artifact/index.ts +27 -0
  356. package/src/lib/parsers/artifact/types.ts +9 -0
  357. package/src/lib/parsers/buildinfo.ts +127 -0
  358. package/src/lib/parsers/constants.ts +56 -0
  359. package/src/lib/parsers/index.ts +5 -0
  360. package/src/lib/parsers/job.ts +101 -0
  361. package/src/lib/parsers/template.ts +131 -0
  362. package/src/lib/std/templates/assured-deployment.yaml +45 -0
  363. package/src/lib/std/templates/erc-2470.yaml +67 -0
  364. package/src/lib/std/templates/min-balance.yaml +32 -0
  365. package/src/lib/std/templates/nano-universal-deployer.yaml +59 -0
  366. package/src/lib/std/templates/raw-erc-2470.yaml +59 -0
  367. package/src/lib/std/templates/raw-nano-universal-deployer.yaml +51 -0
  368. package/src/lib/std/templates/raw-sequence-universal-deployer-2.yaml +48 -0
  369. package/src/lib/std/templates/sequence-universal-deployer-2.yaml +57 -0
  370. package/src/lib/types/__tests__/json-request-action.spec.ts +243 -0
  371. package/src/lib/types/__tests__/read-json-value.spec.ts +264 -0
  372. package/src/lib/types/actions.ts +127 -0
  373. package/src/lib/types/artifacts.ts +21 -0
  374. package/src/lib/types/buildinfo.ts +116 -0
  375. package/src/lib/types/conditions.ts +50 -0
  376. package/src/lib/types/contracts.ts +23 -0
  377. package/src/lib/types/definitions.ts +68 -0
  378. package/src/lib/types/index.ts +8 -0
  379. package/src/lib/types/network.ts +22 -0
  380. package/src/lib/types/project.ts +9 -0
  381. package/src/lib/types/task.ts +9 -0
  382. package/src/lib/types/values.ts +116 -0
  383. package/src/lib/utils/validation.ts +116 -0
  384. package/src/lib/validation/contract-references.ts +210 -0
  385. package/src/lib/validation/index.ts +1 -0
  386. package/src/lib/verification/__tests__/etherscan.spec.ts +710 -0
  387. package/src/lib/verification/__tests__/sourcify.spec.ts +288 -0
  388. package/src/lib/verification/etherscan.ts +546 -0
  389. package/src/lib/verification/sourcify.ts +248 -0
  390. package/test_validation/artifacts/TestContract.json +9 -0
  391. package/test_validation/jobs/test-missing.yaml +16 -0
  392. package/test_validation/networks.yaml +3 -0
  393. package/tsconfig.json +36 -0
@@ -0,0 +1,768 @@
1
+ import * as fs from 'fs/promises'
2
+ import * as path from 'path'
3
+
4
+ import { ProjectLoader, ProjectLoaderOptions } from './core/loader'
5
+ import { DependencyGraph } from './core/graph'
6
+ import { ExecutionEngine } from './core/engine'
7
+ import { createDefaultVerificationRegistry } from './verification/etherscan'
8
+ import { ExecutionContext } from './core/context'
9
+ import { Network, Job } from './types'
10
+ import { DeploymentEventEmitter, deploymentEvents } from './events'
11
+ import type { RunSummaryEvent } from './events'
12
+
13
+ /**
14
+ * Options for configuring a Deployer instance.
15
+ */
16
+ export interface DeployerOptions {
17
+ /** The root directory of the deployment project. */
18
+ projectRoot: string
19
+
20
+ /** The private key of the EOA to be used as the signer/relayer. Optional if an implicit sender from RPC is desired. */
21
+ privateKey?: string
22
+
23
+ /** An array of network configurations to use for deployment. */
24
+ networks: Network[]
25
+
26
+ /** Optional: An array of job names to execute. If not provided, all jobs are considered. */
27
+ runJobs?: string[]
28
+
29
+ /** Optional: An array of chain IDs to run on. If not provided, all configured networks are used. */
30
+ runOnNetworks?: number[]
31
+
32
+ /** Optional: Custom event emitter instance. If not provided, uses the global singleton. */
33
+ eventEmitter?: DeploymentEventEmitter
34
+
35
+ /** Optional: Project loader options (e.g., whether to load standard templates). */
36
+ loaderOptions?: ProjectLoaderOptions
37
+
38
+ /** Optional Etherscan API key for contract verification. */
39
+ etherscanApiKey?: string
40
+
41
+ /** Optional: Stop execution as soon as any job fails. Defaults to false. */
42
+ failEarly?: boolean
43
+
44
+ /** Optional: Skip post-execution check of skip conditions. Defaults to false (post-check enabled). */
45
+ noPostCheckConditions?: boolean
46
+
47
+ /** Optional: When true, write outputs in a flat directory instead of mirroring the jobs dir structure. */
48
+ flatOutput?: boolean
49
+
50
+ /** Optional: Allow running jobs marked as deprecated when true. */
51
+ runDeprecated?: boolean
52
+
53
+ /** Optional: Show end-of-run summary (default: true). */
54
+ showSummary?: boolean
55
+ }
56
+
57
+ /**
58
+ * The Deployer is the top-level orchestrator for the entire deployment process.
59
+ * It loads a project, builds the dependency graph, and executes jobs across
60
+ * specified networks in the correct order.
61
+ */
62
+ export class Deployer {
63
+ private readonly options: DeployerOptions
64
+ public readonly events: DeploymentEventEmitter
65
+ private readonly loader: ProjectLoader
66
+ private readonly noPostCheckConditions: boolean
67
+ private readonly showSummary: boolean
68
+
69
+ // Store both successful and failed execution results
70
+ private readonly results = new Map<string, {
71
+ job: Job;
72
+ outputs: Map<number, { status: 'success' | 'error'; data: Map<string, unknown> | string }>
73
+ }>()
74
+ private graph?: DependencyGraph
75
+
76
+
77
+ constructor(options: DeployerOptions) {
78
+ this.options = options
79
+ this.events = options.eventEmitter || deploymentEvents
80
+ this.loader = new ProjectLoader(options.projectRoot, options.loaderOptions)
81
+ this.noPostCheckConditions = options.noPostCheckConditions ?? false
82
+ this.showSummary = options.showSummary !== false
83
+ }
84
+
85
+
86
+ /**
87
+ * Runs the entire deployment process from loading to execution and outputting results.
88
+ */
89
+ public async run(): Promise<void> {
90
+ this.events.emitEvent({
91
+ type: 'deployment_started',
92
+ level: 'info',
93
+ data: {
94
+ projectRoot: this.options.projectRoot
95
+ }
96
+ })
97
+
98
+ try {
99
+ // 1. Load all project artifacts, templates, and jobs.
100
+ this.events.emitEvent({
101
+ type: 'project_loading_started',
102
+ level: 'info',
103
+ data: {
104
+ projectRoot: this.options.projectRoot
105
+ }
106
+ })
107
+
108
+ await this.loader.load()
109
+
110
+ this.events.emitEvent({
111
+ type: 'project_loaded',
112
+ level: 'info',
113
+ data: {
114
+ jobCount: this.loader.jobs.size,
115
+ templateCount: this.loader.templates.size
116
+ }
117
+ })
118
+
119
+ // 2. Build the dependency graph and determine execution order.
120
+ const graph = new DependencyGraph(this.loader.jobs, this.loader.templates)
121
+ this.graph = graph
122
+ const jobOrder = graph.getExecutionOrder()
123
+
124
+ // 3. Filter jobs and networks based on user options.
125
+ const jobsToRun = this.getJobExecutionPlan(jobOrder)
126
+
127
+ // Inform about skipped deprecated jobs (when applicable)
128
+ if (!this.options.runDeprecated) {
129
+ const skippedDeprecated = jobOrder.filter(name => {
130
+ const j = this.loader.jobs.get(name) as { deprecated?: boolean } | undefined
131
+ return !jobsToRun.includes(name) && j?.deprecated === true
132
+ })
133
+ if (skippedDeprecated.length > 0) {
134
+ this.events.emitEvent({
135
+ type: 'deprecated_jobs_skipped',
136
+ level: 'warn',
137
+ data: { jobs: skippedDeprecated }
138
+ })
139
+ }
140
+ }
141
+ const targetNetworks = this.getTargetNetworks()
142
+
143
+ this.events.emitEvent({
144
+ type: 'execution_plan',
145
+ level: 'info',
146
+ data: {
147
+ targetNetworks: targetNetworks.map(n => ({
148
+ name: n.name,
149
+ chainId: n.chainId
150
+ })),
151
+ jobExecutionOrder: jobsToRun
152
+ }
153
+ })
154
+
155
+ // 4. Execute the plan.
156
+ const verificationRegistry = createDefaultVerificationRegistry(this.options.etherscanApiKey)
157
+ const engine = new ExecutionEngine(this.loader.templates, this.events, verificationRegistry, this.noPostCheckConditions)
158
+
159
+ // Track if any jobs have failed
160
+ let hasFailures = false
161
+
162
+ for (const network of targetNetworks) {
163
+ this.events.emitEvent({
164
+ type: 'network_started',
165
+ level: 'info',
166
+ data: {
167
+ networkName: network.name,
168
+ chainId: network.chainId
169
+ }
170
+ })
171
+
172
+ for (const jobName of jobsToRun) {
173
+ const job = this.loader.jobs.get(jobName)!
174
+
175
+ if (this.shouldSkipJobOnNetwork(job, network)) {
176
+ this.events.emitEvent({
177
+ type: 'job_skipped',
178
+ level: 'warn',
179
+ data: {
180
+ jobName,
181
+ networkName: network.name,
182
+ reason: 'configuration'
183
+ }
184
+ })
185
+ continue
186
+ }
187
+
188
+ // Initialize results storage for this job if not exists
189
+ if (!this.results.has(job.name)) {
190
+ this.results.set(job.name, { job, outputs: new Map() })
191
+ }
192
+
193
+ let context: ExecutionContext | undefined
194
+ try {
195
+ context = new ExecutionContext(
196
+ network,
197
+ this.options.privateKey,
198
+ this.loader.contractRepository,
199
+ this.options.etherscanApiKey,
200
+ this.loader.constants
201
+ )
202
+ // Set job-level constants if present (guard for mocked contexts in tests)
203
+ if (typeof (context as unknown as { setJobConstants?: (constants: unknown) => void }).setJobConstants === 'function') {
204
+ (context as unknown as { setJobConstants: (constants: unknown) => void }).setJobConstants(job.constants)
205
+ }
206
+
207
+ // Populate context with outputs from previously executed dependent jobs
208
+ this.populateContextWithDependentJobOutputs(job, context, network)
209
+
210
+ await engine.executeJob(job, context)
211
+
212
+ // Store successful results
213
+ this.results.get(job.name)!.outputs.set(network.chainId, {
214
+ status: 'success',
215
+ data: (context as { getOutputs(): Map<string, unknown> }).getOutputs()
216
+ })
217
+ } catch (error) {
218
+ // Store error results
219
+ const errorMessage = error instanceof Error ? error.message : String(error)
220
+ this.results.get(job.name)!.outputs.set(network.chainId, {
221
+ status: 'error',
222
+ data: errorMessage
223
+ })
224
+
225
+ this.events.emitEvent({
226
+ type: 'job_execution_failed',
227
+ level: 'error',
228
+ data: {
229
+ jobName: job.name,
230
+ networkName: network.name,
231
+ chainId: network.chainId,
232
+ error: errorMessage
233
+ }
234
+ })
235
+
236
+ // Mark that we have failures
237
+ hasFailures = true
238
+
239
+ // If fail-early is enabled, throw the error immediately
240
+ if (this.options.failEarly) {
241
+ throw error
242
+ }
243
+
244
+ // Otherwise, continue to next job/network
245
+ } finally {
246
+ // Clean up the context to prevent hanging connections
247
+ if (context) {
248
+ try {
249
+ await context.dispose()
250
+ } catch (disposeError) {
251
+ // Log disposal errors but don't let them interrupt the flow
252
+ this.events.emitEvent({
253
+ type: 'context_disposal_warning',
254
+ level: 'warn',
255
+ data: {
256
+ jobName: job.name,
257
+ networkName: network.name,
258
+ error: disposeError instanceof Error ? disposeError.message : String(disposeError)
259
+ }
260
+ })
261
+ }
262
+ }
263
+ }
264
+ }
265
+ }
266
+
267
+ // 5. Write results to output files.
268
+ await this.writeOutputFiles()
269
+
270
+ // Emit end-of-run summary before final status
271
+ if (this.showSummary) {
272
+ this.emitRunSummary(hasFailures)
273
+ }
274
+
275
+ // Check if any jobs failed and exit with error if so
276
+ if (hasFailures) {
277
+ const error = new Error('One or more jobs failed during execution')
278
+ this.events.emitEvent({
279
+ type: 'deployment_failed',
280
+ level: 'error',
281
+ data: {
282
+ error: error.message,
283
+ stack: error.stack
284
+ }
285
+ })
286
+ throw error
287
+ }
288
+
289
+ this.events.emitEvent({
290
+ type: 'deployment_completed',
291
+ level: 'info'
292
+ })
293
+ } catch (error) {
294
+ this.events.emitEvent({
295
+ type: 'deployment_failed',
296
+ level: 'error',
297
+ data: {
298
+ error: error instanceof Error ? error.message : String(error),
299
+ stack: error instanceof Error ? error.stack : undefined
300
+ }
301
+ })
302
+ // Re-throw to allow CLI to exit with a non-zero code
303
+ throw error
304
+ }
305
+ }
306
+
307
+ /**
308
+ * Emit a concise run summary event for the CLI to render at the end.
309
+ */
310
+ private emitRunSummary(hasFailures: boolean): void {
311
+ // Compute counts
312
+ const jobCount = this.results.size
313
+ let successCount = 0
314
+ let failedCount = 0
315
+ const skippedCount = 0
316
+
317
+ // Detect skipped by comparing planned jobs across networks vs executed entries
318
+ // Here we approximate: an entry exists per job and per network outcome. We count
319
+ // successes/errors; skips were emitted as events during execution and are not persisted
320
+ // in results. We cannot perfectly reconstruct skipped count without tracking, so we
321
+ // expose it as 0 for now; could be improved by tracking per-network skip events.
322
+
323
+ for (const [, result] of this.results) {
324
+ for (const [, netResult] of result.outputs) {
325
+ if (netResult.status === 'success') successCount++
326
+ else failedCount++
327
+ }
328
+ }
329
+
330
+ // Collect key contract addresses from outputs for quick visibility
331
+ const keyContracts: Array<{ job: string; action: string; address: string }> = []
332
+ for (const [, result] of this.results) {
333
+ for (const [, netResult] of result.outputs) {
334
+ if (netResult.status !== 'success') continue
335
+ const outputs = netResult.data as Map<string, unknown>
336
+ for (const [k, v] of outputs) {
337
+ if (k.endsWith('.address') && typeof v === 'string') {
338
+ const action = k.split('.')[0]
339
+ keyContracts.push({ job: result.job.name, action, address: v })
340
+ }
341
+ }
342
+ }
343
+ }
344
+
345
+ const summaryEvent = {
346
+ type: 'run_summary',
347
+ level: (hasFailures ? 'warn' : 'info') as 'info' | 'warn',
348
+ data: {
349
+ networkCount: this.options.networks.length,
350
+ jobCount,
351
+ successCount,
352
+ failedCount,
353
+ skippedCount,
354
+ keyContracts: keyContracts.slice(0, 10)
355
+ }
356
+ } satisfies Omit<RunSummaryEvent, 'timestamp'>
357
+
358
+ this.events.emitEvent(summaryEvent)
359
+ }
360
+
361
+ /**
362
+ * Determines the final, ordered list of jobs to execute based on user input.
363
+ * If a user requests specific jobs, this ensures all their dependencies are also included.
364
+ */
365
+ private getJobExecutionPlan(fullOrder: string[]): string[] {
366
+ // Expand provided runJobs to concrete job names by supporting simple glob patterns
367
+ const expandRunJobs = (patterns: string[]): string[] => {
368
+ const allJobNames = Array.from(this.loader.jobs.keys())
369
+
370
+ const isPattern = (s: string): boolean => /[*?]/.test(s)
371
+ const escapeRegex = (s: string): string => s.replace(/[-\\^$+?.()|[\]{}*?]/g, '\\$&')
372
+ const patternToRegex = (pattern: string): RegExp => {
373
+ // Escape regex metacharacters, then translate wildcard tokens
374
+ const escaped = escapeRegex(pattern)
375
+ .replace(/\\\*/g, '.*') // escaped '*' -> '.*'
376
+ .replace(/\\\?/g, '.') // escaped '?' -> '.'
377
+ return new RegExp(`^${escaped}$`)
378
+ }
379
+
380
+ const expanded: string[] = []
381
+ const seen = new Set<string>()
382
+
383
+ for (const p of patterns) {
384
+ if (!isPattern(p)) {
385
+ // Exact name; validate exists
386
+ if (!this.loader.jobs.has(p)) {
387
+ throw new Error(`Specified job "${p}" not found in project.`)
388
+ }
389
+ if (!seen.has(p)) {
390
+ seen.add(p)
391
+ expanded.push(p)
392
+ }
393
+ continue
394
+ }
395
+
396
+ const re = patternToRegex(p)
397
+ const matches = allJobNames.filter(name => re.test(name))
398
+ if (matches.length === 0) {
399
+ throw new Error(`Job pattern "${p}" did not match any jobs in project.`)
400
+ }
401
+ for (const m of matches) {
402
+ if (!seen.has(m)) {
403
+ seen.add(m)
404
+ expanded.push(m)
405
+ }
406
+ }
407
+ }
408
+
409
+ return expanded
410
+ }
411
+
412
+ // Helper to decide if a job is deprecated
413
+ const isDeprecated = (jobName: string): boolean => {
414
+ const j = this.loader.jobs.get(jobName)
415
+ return !!(j && (j as { deprecated?: boolean }).deprecated === true)
416
+ }
417
+
418
+ // If user didn't specify jobs explicitly, include all non-deprecated jobs.
419
+ // Additionally, ALWAYS include deprecated jobs when they are dependencies of any non-deprecated job.
420
+ if (!this.options.runJobs || this.options.runJobs.length === 0) {
421
+ if (this.options.runDeprecated) {
422
+ return fullOrder
423
+ }
424
+
425
+ const nonDeprecatedJobs = new Set(fullOrder.filter(name => !isDeprecated(name)))
426
+
427
+ // Collect deprecated jobs that are required by any non-deprecated job
428
+ const requiredDeprecated = new Set<string>()
429
+ for (const jobName of nonDeprecatedJobs) {
430
+ const deps = this.graph?.getDependencies(jobName) || new Set<string>()
431
+ for (const dep of deps) {
432
+ if (isDeprecated(dep)) {
433
+ requiredDeprecated.add(dep)
434
+ }
435
+ }
436
+ }
437
+
438
+ const allowed = new Set<string>([...nonDeprecatedJobs, ...requiredDeprecated])
439
+ return fullOrder.filter(name => allowed.has(name))
440
+ }
441
+
442
+ // Expand patterns to concrete names
443
+ const expandedRunJobs = expandRunJobs(this.options.runJobs)
444
+ const explicitlyRequested = new Set<string>(expandedRunJobs)
445
+
446
+ const jobsToRun = new Set<string>()
447
+ for (const jobName of expandedRunJobs) {
448
+ jobsToRun.add(jobName)
449
+ const dependencies = this.graph?.getDependencies(jobName) || new Set()
450
+ dependencies.forEach((dep: string) => jobsToRun.add(dep))
451
+ }
452
+
453
+ // Deprecated dependencies must be kept even when --run-deprecated is not set.
454
+ // Only drop deprecated jobs that are neither explicitly requested nor required as a dependency.
455
+ const depsOfRequested = new Set<string>()
456
+ for (const jobName of expandedRunJobs) {
457
+ const deps = this.graph?.getDependencies(jobName) || new Set<string>()
458
+ deps.forEach(d => depsOfRequested.add(d))
459
+ }
460
+
461
+ const filtered = Array.from(jobsToRun).filter(name => {
462
+ if (!isDeprecated(name)) return true
463
+ if (explicitlyRequested.has(name)) return true
464
+ if (depsOfRequested.has(name)) return true // keep deprecated dependency
465
+ return this.options.runDeprecated === true
466
+ })
467
+ const allowedSet = new Set(filtered)
468
+
469
+ // Filter the original execution order to only include the required jobs, preserving the correct sequence.
470
+ return fullOrder.filter(jobName => allowedSet.has(jobName))
471
+ }
472
+
473
+ /**
474
+ * Determines the final list of networks to run on based on user input.
475
+ */
476
+ private getTargetNetworks(): Network[] {
477
+ if (!this.options.runOnNetworks || this.options.runOnNetworks.length === 0) {
478
+ return this.options.networks // Run on all configured networks
479
+ }
480
+
481
+ const targetChainIds = new Set(this.options.runOnNetworks)
482
+ const filteredNetworks = this.options.networks.filter(n => targetChainIds.has(n.chainId))
483
+
484
+ if (filteredNetworks.length !== this.options.runOnNetworks.length) {
485
+ const foundIds = new Set(filteredNetworks.map(n => n.chainId))
486
+ const missingIds = this.options.runOnNetworks.filter(id => !foundIds.has(id))
487
+ this.events.emitEvent({
488
+ type: 'missing_network_config_warning',
489
+ level: 'warn',
490
+ data: {
491
+ missingChainIds: missingIds
492
+ }
493
+ })
494
+ }
495
+
496
+ return filteredNetworks
497
+ }
498
+
499
+ /**
500
+ * Checks a job's `only_networks` and `skip_networks` fields to see if it should run on the given network.
501
+ */
502
+ private shouldSkipJobOnNetwork(job: Job, network: Network): boolean {
503
+ // Note: This relies on `only_networks` and `skip_networks` being present on the Job type.
504
+ const jobWithNetworkFilters = job as Job & { only_networks?: number[]; skip_networks?: number[] }
505
+
506
+ // Check only_networks: if present, the job only runs on these networks.
507
+ if (jobWithNetworkFilters.only_networks && jobWithNetworkFilters.only_networks.length > 0) {
508
+ return !jobWithNetworkFilters.only_networks.includes(network.chainId)
509
+ }
510
+
511
+ // Check skip_networks: if present, the job skips these networks.
512
+ if (jobWithNetworkFilters.skip_networks && jobWithNetworkFilters.skip_networks.length > 0) {
513
+ return jobWithNetworkFilters.skip_networks.includes(network.chainId)
514
+ }
515
+
516
+ return false // Run by default
517
+ }
518
+
519
+ /**
520
+ * Populates the execution context with outputs from previously executed dependent jobs.
521
+ */
522
+ private populateContextWithDependentJobOutputs(job: Job, context: ExecutionContext, network: Network): void {
523
+ if (!job.depends_on) return
524
+
525
+ for (const dependentJobName of job.depends_on) {
526
+ const dependentJobResults = this.results.get(dependentJobName)
527
+ if (!dependentJobResults) continue
528
+
529
+ const networkResult = dependentJobResults.outputs.get(network.chainId)
530
+ if (!networkResult || networkResult.status !== 'success') continue
531
+
532
+ // Add outputs with job name prefixes for cross-job access
533
+ const outputs = networkResult.data as Map<string, unknown>
534
+ for (const [key, value] of outputs.entries()) {
535
+ const prefixedKey = `${dependentJobName}.${key}`
536
+ context.setOutput(prefixedKey, value)
537
+ }
538
+ }
539
+ }
540
+
541
+
542
+
543
+ /**
544
+ * Writes the collected deployment results to JSON files in the output directory.
545
+ * By default, mirrors the jobs directory structure under output/. When flatOutput
546
+ * is true, writes all job JSONs directly under output/.
547
+ */
548
+ private async writeOutputFiles(): Promise<void> {
549
+ if (this.results.size === 0) {
550
+ this.events.emitEvent({
551
+ type: 'no_outputs',
552
+ level: 'warn'
553
+ })
554
+ return
555
+ }
556
+
557
+ const outputRoot = path.join(this.options.projectRoot, 'output')
558
+ await fs.mkdir(outputRoot, { recursive: true })
559
+
560
+ this.events.emitEvent({
561
+ type: 'output_writing_started',
562
+ level: 'info'
563
+ })
564
+
565
+ for (const [jobName, resultData] of this.results.entries()) {
566
+ // Determine relative subpath for this job based on its source path under jobs/
567
+ let relativeJobSubpath = `${jobName}.json`
568
+ if (!this.options.flatOutput && resultData.job._path) {
569
+ // Find jobs directory within project
570
+ const jobsDir = path.join(this.options.projectRoot, 'jobs')
571
+ const normalizedJobPath = path.normalize(resultData.job._path)
572
+ const normalizedJobsDir = path.normalize(jobsDir)
573
+ if (normalizedJobPath.startsWith(normalizedJobsDir)) {
574
+ // Compute relative path from jobs dir to the yaml file, and replace extension with .json
575
+ const relFromJobs = path.relative(normalizedJobsDir, normalizedJobPath)
576
+ const dirPart = path.dirname(relFromJobs)
577
+ const fileBase = path.basename(relFromJobs, path.extname(relFromJobs))
578
+ relativeJobSubpath = dirPart === '.' ? `${fileBase}.json` : path.join(dirPart, `${fileBase}.json`)
579
+ } else {
580
+ // Fallback to job name if path isn't within jobs dir
581
+ relativeJobSubpath = `${jobName}.json`
582
+ }
583
+ }
584
+
585
+ const outputFilePath = path.join(outputRoot, relativeJobSubpath)
586
+ const outputFileDir = path.dirname(outputFilePath)
587
+ await fs.mkdir(outputFileDir, { recursive: true })
588
+
589
+ // Group networks by identical status and outputs
590
+ const groupedResults = this.groupNetworkResults(resultData.outputs, resultData.job)
591
+
592
+ const fileContent = {
593
+ jobName: resultData.job.name,
594
+ jobVersion: resultData.job.version,
595
+ lastRun: new Date().toISOString(),
596
+ networks: groupedResults
597
+ }
598
+
599
+ await fs.writeFile(outputFilePath, JSON.stringify(fileContent, null, 2))
600
+ this.events.emitEvent({
601
+ type: 'output_file_written',
602
+ level: 'info',
603
+ data: {
604
+ relativePath: path.relative(this.options.projectRoot, outputFilePath)
605
+ }
606
+ })
607
+ }
608
+ }
609
+
610
+ /**
611
+ * Filters outputs according to job actions' output selection:
612
+ * - output: true -> include all outputs for that action
613
+ * - output: false -> exclude outputs for that action
614
+ * - output: object -> include ONLY the specified keys, resolved from context if they are placeholders
615
+ *
616
+ * If no actions have output: true or object (i.e., only false/undefined), includes all outputs (backward compatibility),
617
+ * but excludes dependency outputs when there are explicit dependencies defined.
618
+ */
619
+ private filterOutputsByActionFlags(outputs: Map<string, unknown>, job: Job): Record<string, unknown> {
620
+ // Partition actions by output config
621
+ const actionsWithCustomMap = job.actions.filter(a => a.output && typeof a.output === 'object' && a.output !== null) as Array<Job['actions'][number] & { output: Record<string, unknown> }>
622
+ const actionsWithTrue = job.actions.filter(a => a.output === true)
623
+ const actionsWithFalse = new Set(job.actions.filter(a => a.output === false).map(a => a.name))
624
+
625
+ // If there are any custom maps, include only those mapped keys for those actions.
626
+ // Collect explicit inclusions here.
627
+ const result = new Map<string, unknown>()
628
+
629
+ // Helper to include by prefix
630
+ const includeAllForAction = (actionName: string) => {
631
+ for (const [key, value] of outputs) {
632
+ if (key.startsWith(`${actionName}.`)) {
633
+ result.set(key, value)
634
+ }
635
+ }
636
+ }
637
+
638
+ // 1) Handle custom output maps (highest precedence and explicit selection)
639
+ if (actionsWithCustomMap.length > 0) {
640
+ for (const action of actionsWithCustomMap) {
641
+ const prefix = `${action.name}.`
642
+ // For mapped keys, accept either fully qualified keys (e.g., "txHash") which we map to `${action.name}.txHash`
643
+ // or already-qualified keys (rare). We'll normalize to prefixed keys in the output.
644
+ for (const mappedKey of Object.keys(action.output)) {
645
+ // If user provided fully-qualified "action.key", strip if redundant
646
+ const normalizedKey = mappedKey.startsWith(prefix) ? mappedKey : `${prefix}${mappedKey}`
647
+ // Only include if present in outputs map
648
+ if (outputs.has(normalizedKey)) {
649
+ result.set(normalizedKey, outputs.get(normalizedKey)!)
650
+ }
651
+ }
652
+ }
653
+ // Note: when any custom maps exist, we DO NOT automatically include actionsWithTrue;
654
+ // the requirement states "if action specifies custom output, then the output is defined by them and not by the template".
655
+ // That means for those actions, only mapped keys are included. For other actions (without custom maps),
656
+ // they will be handled by output:true rules below.
657
+ }
658
+
659
+ // 2) Include all for actions marked output: true (that do not have a custom map)
660
+ const actionsWithTrueNames = new Set(actionsWithTrue.map(a => a.name))
661
+ for (const actionName of actionsWithTrueNames) {
662
+ // If this action also had a custom map, custom map already handled it and should be authoritative.
663
+ const hadCustom = actionsWithCustomMap.some(a => a.name === actionName)
664
+ if (!hadCustom) {
665
+ includeAllForAction(actionName)
666
+ }
667
+ }
668
+
669
+ // 3) Exclude any actions explicitly marked false (they won't be included by rules above anyway)
670
+
671
+ // If we have any inclusions (custom maps or trues), return them
672
+ if (result.size > 0) {
673
+ // Additionally, filter out any accidentally included outputs from actions marked false
674
+ for (const falseActionName of actionsWithFalse) {
675
+ for (const key of Array.from(result.keys())) {
676
+ if (key.startsWith(`${falseActionName}.`)) {
677
+ result.delete(key)
678
+ }
679
+ }
680
+ }
681
+ return Object.fromEntries(result)
682
+ }
683
+
684
+ // 4) Backward compatibility: include all outputs if no action opted-in via true/object.
685
+ // Exclude dependency outputs if the job has explicit dependencies.
686
+ if (job.depends_on && job.depends_on.length > 0) {
687
+ return this.filterOutDependencyOutputs(outputs, job)
688
+ }
689
+ return Object.fromEntries(outputs)
690
+ }
691
+
692
+ /**
693
+ * Filters out dependency outputs from the outputs map.
694
+ * Dependency outputs are identified by being prefixed with dependency job names.
695
+ */
696
+ private filterOutDependencyOutputs(outputs: Map<string, unknown>, job: Job): Record<string, unknown> {
697
+ const filtered = new Map<string, unknown>()
698
+
699
+ // Get list of dependency job names
700
+ const dependencyNames = job.depends_on || []
701
+
702
+ for (const [key, value] of outputs) {
703
+ // Check if this output key starts with any dependency job name prefix
704
+ const isDependencyOutput = dependencyNames.some(depName => key.startsWith(`${depName}.`))
705
+
706
+ // Only include outputs that are NOT from dependencies
707
+ if (!isDependencyOutput) {
708
+ filtered.set(key, value)
709
+ }
710
+ }
711
+
712
+ return Object.fromEntries(filtered)
713
+ }
714
+
715
+ /**
716
+ * Groups network results by status and outputs.
717
+ * - Success states with identical outputs are grouped together with chainIds array
718
+ * - Error states are kept separate (one entry per network)
719
+ */
720
+ private groupNetworkResults(outputs: Map<number, { status: 'success' | 'error'; data: Map<string, unknown> | string }>, job: Job): Array<{
721
+ status: 'success' | 'error';
722
+ chainIds?: string[];
723
+ chainId?: string;
724
+ outputs?: Record<string, unknown>;
725
+ error?: string;
726
+ }> {
727
+ const successGroups = new Map<string, { chainIds: string[], outputs: Record<string, unknown> }>()
728
+ const errorEntries: Array<{
729
+ status: 'error';
730
+ chainId: string;
731
+ error: string;
732
+ }> = []
733
+
734
+ for (const [chainId, result] of outputs.entries()) {
735
+ if (result.status === 'success') {
736
+ // Group successful results by identical outputs, filtered by action output flags
737
+ const outputsObj = result.data instanceof Map ? this.filterOutputsByActionFlags(result.data, job) : {}
738
+ const key = JSON.stringify(outputsObj)
739
+
740
+ if (!successGroups.has(key)) {
741
+ successGroups.set(key, {
742
+ chainIds: [],
743
+ outputs: outputsObj
744
+ })
745
+ }
746
+
747
+ successGroups.get(key)!.chainIds.push(chainId.toString())
748
+ } else {
749
+ // Keep error results separate - one entry per network
750
+ errorEntries.push({
751
+ status: 'error',
752
+ chainId: chainId.toString(),
753
+ error: result.data as string
754
+ })
755
+ }
756
+ }
757
+
758
+ // Convert success groups to array format
759
+ const successEntries = Array.from(successGroups.values()).map(group => ({
760
+ status: 'success' as const,
761
+ chainIds: group.chainIds.sort(), // Sort for consistent output
762
+ outputs: group.outputs
763
+ }))
764
+
765
+ // Return all entries: successes first, then errors
766
+ return [...successEntries, ...errorEntries]
767
+ }
768
+ }