@hominis/fireforge 0.45.0 → 0.46.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 (359) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/dist/bin/fireforge.js +6 -0
  3. package/dist/build-info.json +4 -4
  4. package/dist/src/cli-usage-error.d.ts +46 -0
  5. package/dist/src/cli-usage-error.js +81 -0
  6. package/dist/src/cli.d.ts +5 -1
  7. package/dist/src/cli.js +20 -21
  8. package/dist/src/commands/bootstrap.js +1 -2
  9. package/dist/src/commands/build.js +15 -6
  10. package/dist/src/commands/destructive-decision.d.ts +19 -0
  11. package/dist/src/commands/destructive-decision.js +31 -0
  12. package/dist/src/commands/doctor-check-core.d.ts +0 -6
  13. package/dist/src/commands/doctor-check-core.js +0 -8
  14. package/dist/src/commands/doctor-orphaned-harness.d.ts +0 -6
  15. package/dist/src/commands/doctor-orphaned-harness.js +4 -12
  16. package/dist/src/commands/doctor-post-rebase-audit.js +1 -1
  17. package/dist/src/commands/doctor.d.ts +2 -0
  18. package/dist/src/commands/doctor.js +7 -8
  19. package/dist/src/commands/download.js +35 -24
  20. package/dist/src/commands/export-all.js +21 -4
  21. package/dist/src/commands/export-flow.js +15 -45
  22. package/dist/src/commands/export-placement-conflicts.js +3 -3
  23. package/dist/src/commands/export-placement-gate.js +2 -8
  24. package/dist/src/commands/export-placement-policy.js +3 -5
  25. package/dist/src/commands/export-shared.d.ts +42 -23
  26. package/dist/src/commands/export-shared.js +4 -19
  27. package/dist/src/commands/export.js +10 -1
  28. package/dist/src/commands/furnace/authoring-config.d.ts +13 -0
  29. package/dist/src/commands/furnace/authoring-config.js +19 -0
  30. package/dist/src/commands/furnace/chrome-doc-remove.js +1 -1
  31. package/dist/src/commands/furnace/create-browser-test.js +3 -8
  32. package/dist/src/commands/furnace/create-dry-run.js +2 -5
  33. package/dist/src/commands/furnace/create-templates.d.ts +17 -1
  34. package/dist/src/commands/furnace/create-templates.js +2 -1
  35. package/dist/src/commands/furnace/create.js +25 -19
  36. package/dist/src/commands/furnace/deploy.js +11 -2
  37. package/dist/src/commands/furnace/init.js +2 -1
  38. package/dist/src/commands/furnace/override.js +28 -19
  39. package/dist/src/commands/furnace/refresh.js +9 -1
  40. package/dist/src/commands/furnace/remove-mutations.d.ts +6 -7
  41. package/dist/src/commands/furnace/remove-mutations.js +0 -1
  42. package/dist/src/commands/furnace/remove.js +2 -6
  43. package/dist/src/commands/furnace/rename-browser-test.js +1 -7
  44. package/dist/src/commands/furnace/rename.js +29 -29
  45. package/dist/src/commands/furnace/status.js +12 -9
  46. package/dist/src/commands/furnace/test-file-name.d.ts +28 -0
  47. package/dist/src/commands/furnace/test-file-name.js +39 -0
  48. package/dist/src/commands/furnace/validation-output.d.ts +21 -9
  49. package/dist/src/commands/furnace/validation-output.js +3 -8
  50. package/dist/src/commands/import.js +2 -9
  51. package/dist/src/commands/lint-per-patch.d.ts +2 -2
  52. package/dist/src/commands/lint-per-patch.js +19 -4
  53. package/dist/src/commands/lint-per-run-checkjs.d.ts +2 -2
  54. package/dist/src/commands/lint.d.ts +2 -1
  55. package/dist/src/commands/lint.js +1 -1
  56. package/dist/src/commands/manifest.js +1 -1
  57. package/dist/src/commands/package.js +2 -4
  58. package/dist/src/commands/patch/compact.js +14 -15
  59. package/dist/src/commands/patch/delete.js +15 -25
  60. package/dist/src/commands/patch/entry-projection.d.ts +13 -0
  61. package/dist/src/commands/patch/entry-projection.js +24 -0
  62. package/dist/src/commands/patch/lint-ignore.d.ts +0 -8
  63. package/dist/src/commands/patch/lint-ignore.js +19 -29
  64. package/dist/src/commands/patch/move-files-create.js +24 -10
  65. package/dist/src/commands/patch/move-files-into.js +38 -34
  66. package/dist/src/commands/patch/move-files.js +4 -13
  67. package/dist/src/commands/patch/rename.js +27 -41
  68. package/dist/src/commands/patch/reorder.js +25 -38
  69. package/dist/src/commands/patch/split-plan.d.ts +4 -5
  70. package/dist/src/commands/patch/split-plan.js +4 -8
  71. package/dist/src/commands/patch/split.js +45 -43
  72. package/dist/src/commands/patch/staged-dependency.js +16 -22
  73. package/dist/src/commands/patch/tier.js +15 -25
  74. package/dist/src/commands/patch-rename-map.d.ts +19 -0
  75. package/dist/src/commands/patch-rename-map.js +36 -0
  76. package/dist/src/commands/pending-resolution.d.ts +15 -0
  77. package/dist/src/commands/pending-resolution.js +25 -0
  78. package/dist/src/commands/re-export-adjacent.d.ts +24 -2
  79. package/dist/src/commands/re-export-adjacent.js +32 -16
  80. package/dist/src/commands/re-export-bulk-scan.js +6 -10
  81. package/dist/src/commands/re-export-drift.js +4 -4
  82. package/dist/src/commands/re-export-files.d.ts +2 -3
  83. package/dist/src/commands/re-export-files.js +38 -29
  84. package/dist/src/commands/re-export-lint.d.ts +3 -4
  85. package/dist/src/commands/re-export-lint.js +9 -2
  86. package/dist/src/commands/re-export-scan.js +5 -2
  87. package/dist/src/commands/re-export-single.d.ts +22 -3
  88. package/dist/src/commands/re-export-single.js +19 -11
  89. package/dist/src/commands/re-export.js +28 -11
  90. package/dist/src/commands/rebase/abort.js +3 -8
  91. package/dist/src/commands/rebase/continue.js +12 -12
  92. package/dist/src/commands/rebase/index.js +2 -2
  93. package/dist/src/commands/rebase/patch-loop.d.ts +2 -2
  94. package/dist/src/commands/rebase/patch-loop.js +2 -7
  95. package/dist/src/commands/resolve.js +18 -10
  96. package/dist/src/commands/run.d.ts +0 -13
  97. package/dist/src/commands/run.js +12 -25
  98. package/dist/src/commands/setup-support.d.ts +16 -4
  99. package/dist/src/commands/setup-support.js +16 -16
  100. package/dist/src/commands/setup.js +3 -9
  101. package/dist/src/commands/source.js +8 -1
  102. package/dist/src/commands/status-check.js +2 -2
  103. package/dist/src/commands/status-output.js +2 -2
  104. package/dist/src/commands/status.js +3 -4
  105. package/dist/src/commands/test-build-phase.d.ts +2 -2
  106. package/dist/src/commands/test-build-phase.js +31 -7
  107. package/dist/src/commands/test-modes.d.ts +2 -2
  108. package/dist/src/commands/test-modes.js +0 -1
  109. package/dist/src/commands/test-register.js +5 -3
  110. package/dist/src/commands/test-run.js +25 -18
  111. package/dist/src/commands/test-stale-gate.js +4 -4
  112. package/dist/src/commands/test-verdict.d.ts +16 -2
  113. package/dist/src/commands/test-verdict.js +18 -3
  114. package/dist/src/commands/test.js +42 -5
  115. package/dist/src/commands/token-list.d.ts +26 -0
  116. package/dist/src/commands/token-list.js +153 -0
  117. package/dist/src/commands/token.js +17 -1
  118. package/dist/src/commands/tree.js +7 -2
  119. package/dist/src/commands/typecheck.js +9 -9
  120. package/dist/src/commands/wire.js +4 -3
  121. package/dist/src/core/branding.d.ts +0 -7
  122. package/dist/src/core/branding.js +3 -2
  123. package/dist/src/core/browser-wire.js +6 -3
  124. package/dist/src/core/build-audit-platform.d.ts +29 -0
  125. package/dist/src/core/build-audit-platform.js +140 -0
  126. package/dist/src/core/build-audit-registration.js +3 -2
  127. package/dist/src/core/build-audit-skip.d.ts +57 -0
  128. package/dist/src/core/build-audit-skip.js +112 -0
  129. package/dist/src/core/build-audit.d.ts +15 -2
  130. package/dist/src/core/build-audit.js +110 -10
  131. package/dist/src/core/build-baseline.d.ts +45 -28
  132. package/dist/src/core/build-baseline.js +6 -29
  133. package/dist/src/core/build-jar-escalation.js +3 -2
  134. package/dist/src/core/build-overwrite-guard.js +1 -1
  135. package/dist/src/core/build-prepare.d.ts +0 -8
  136. package/dist/src/core/build-prepare.js +7 -12
  137. package/dist/src/core/config-paths.d.ts +2 -6
  138. package/dist/src/core/config-paths.js +4 -2
  139. package/dist/src/core/config-state.d.ts +0 -6
  140. package/dist/src/core/config-state.js +1 -19
  141. package/dist/src/core/config-validate-build-audit.d.ts +8 -0
  142. package/dist/src/core/config-validate-build-audit.js +80 -0
  143. package/dist/src/core/config-validate-file-size.js +1 -1
  144. package/dist/src/core/config-validate-patch-policy.d.ts +2 -2
  145. package/dist/src/core/config-validate-patch-policy.js +3 -3
  146. package/dist/src/core/config-validate-test-toolchains.d.ts +2 -2
  147. package/dist/src/core/config-validate-test-toolchains.js +4 -5
  148. package/dist/src/core/config-validate.js +14 -3
  149. package/dist/src/core/config.d.ts +2 -2
  150. package/dist/src/core/config.js +11 -15
  151. package/dist/src/core/coverage-extend.js +4 -4
  152. package/dist/src/core/discard-baseline.js +7 -7
  153. package/dist/src/core/engine-precondition.d.ts +1 -9
  154. package/dist/src/core/engine-precondition.js +3 -8
  155. package/dist/src/core/engine-session-lock.js +13 -17
  156. package/dist/src/core/export-moved-code-shape.js +2 -2
  157. package/dist/src/core/file-lock-owner.d.ts +103 -0
  158. package/dist/src/core/file-lock-owner.js +259 -0
  159. package/dist/src/core/file-lock.d.ts +2 -7
  160. package/dist/src/core/file-lock.js +40 -91
  161. package/dist/src/core/firefox-cache.d.ts +13 -1
  162. package/dist/src/core/firefox-cache.js +59 -34
  163. package/dist/src/core/firefox-download.d.ts +15 -0
  164. package/dist/src/core/firefox-download.js +37 -21
  165. package/dist/src/core/firefox-extract.d.ts +6 -5
  166. package/dist/src/core/firefox-extract.js +36 -18
  167. package/dist/src/core/firefox.d.ts +30 -32
  168. package/dist/src/core/firefox.js +5 -36
  169. package/dist/src/core/furnace-apply-dry-run.d.ts +1 -6
  170. package/dist/src/core/furnace-apply-dry-run.js +1 -7
  171. package/dist/src/core/furnace-apply-helpers.d.ts +0 -11
  172. package/dist/src/core/furnace-apply-helpers.js +8 -19
  173. package/dist/src/core/furnace-apply-overwrite-warn.js +4 -2
  174. package/dist/src/core/furnace-apply.d.ts +1 -1
  175. package/dist/src/core/furnace-apply.js +30 -10
  176. package/dist/src/core/furnace-config.d.ts +1 -1
  177. package/dist/src/core/furnace-config.js +23 -36
  178. package/dist/src/core/furnace-constants.js +3 -2
  179. package/dist/src/core/furnace-dir-entry.d.ts +20 -0
  180. package/dist/src/core/furnace-dir-entry.js +21 -0
  181. package/dist/src/core/furnace-operation.d.ts +2 -0
  182. package/dist/src/core/furnace-operation.js +3 -1
  183. package/dist/src/core/furnace-refresh.d.ts +18 -6
  184. package/dist/src/core/furnace-refresh.js +3 -6
  185. package/dist/src/core/furnace-registration-ast.d.ts +0 -1
  186. package/dist/src/core/furnace-registration-ast.js +0 -3
  187. package/dist/src/core/furnace-registration.d.ts +2 -1
  188. package/dist/src/core/furnace-registration.js +22 -29
  189. package/dist/src/core/furnace-scanner.js +3 -6
  190. package/dist/src/core/furnace-state-persist.d.ts +2 -2
  191. package/dist/src/core/furnace-validate-registration.js +5 -7
  192. package/dist/src/core/git-base.d.ts +3 -1
  193. package/dist/src/core/git-base.js +25 -3
  194. package/dist/src/core/git-diff-new-file.d.ts +40 -0
  195. package/dist/src/core/git-diff-new-file.js +119 -0
  196. package/dist/src/core/git-diff.d.ts +9 -9
  197. package/dist/src/core/git-diff.js +88 -157
  198. package/dist/src/core/git-file-ops.d.ts +0 -7
  199. package/dist/src/core/git-file-ops.js +4 -28
  200. package/dist/src/core/git-readonly-index.d.ts +0 -2
  201. package/dist/src/core/git-readonly-index.js +0 -4
  202. package/dist/src/core/git-status.d.ts +7 -0
  203. package/dist/src/core/git-status.js +21 -9
  204. package/dist/src/core/git.d.ts +0 -6
  205. package/dist/src/core/git.js +14 -43
  206. package/dist/src/core/harness-orphans.js +8 -7
  207. package/dist/src/core/history-log.d.ts +19 -0
  208. package/dist/src/core/history-log.js +29 -0
  209. package/dist/src/core/mach-error-hints.d.ts +0 -5
  210. package/dist/src/core/mach-error-hints.js +1 -1
  211. package/dist/src/core/mach-known-noise-filter.d.ts +4 -3
  212. package/dist/src/core/mach-known-noise-filter.js +7 -9
  213. package/dist/src/core/mach-mozconfig.js +2 -1
  214. package/dist/src/core/mach-python.d.ts +2 -0
  215. package/dist/src/core/mach-python.js +12 -7
  216. package/dist/src/core/mach-resource-shim.d.ts +13 -1
  217. package/dist/src/core/mach-resource-shim.js +112 -9
  218. package/dist/src/core/mach.d.ts +39 -53
  219. package/dist/src/core/mach.js +13 -81
  220. package/dist/src/core/marionette-port.d.ts +0 -17
  221. package/dist/src/core/marionette-port.js +12 -58
  222. package/dist/src/core/marionette-preflight.js +9 -27
  223. package/dist/src/core/mochitest-server-port.js +3 -3
  224. package/dist/src/core/moz-manifest-rules.js +37 -33
  225. package/dist/src/core/patch-apply-fuzz.js +4 -4
  226. package/dist/src/core/patch-apply.d.ts +4 -5
  227. package/dist/src/core/patch-apply.js +6 -12
  228. package/dist/src/core/patch-export-coverage.d.ts +0 -9
  229. package/dist/src/core/patch-export-coverage.js +1 -30
  230. package/dist/src/core/patch-export-update.d.ts +18 -1
  231. package/dist/src/core/patch-export-update.js +4 -3
  232. package/dist/src/core/patch-export.d.ts +11 -21
  233. package/dist/src/core/patch-export.js +44 -66
  234. package/dist/src/core/patch-files.d.ts +0 -4
  235. package/dist/src/core/patch-files.js +0 -11
  236. package/dist/src/core/patch-lint-binary.d.ts +6 -12
  237. package/dist/src/core/patch-lint-cache.d.ts +17 -1
  238. package/dist/src/core/patch-lint-cache.js +3 -6
  239. package/dist/src/core/patch-lint-checkjs.d.ts +33 -16
  240. package/dist/src/core/patch-lint-checkjs.js +37 -24
  241. package/dist/src/core/patch-lint-creators.d.ts +2 -6
  242. package/dist/src/core/patch-lint-cross.d.ts +16 -8
  243. package/dist/src/core/patch-lint-cross.js +49 -92
  244. package/dist/src/core/patch-lint-css.js +10 -6
  245. package/dist/src/core/patch-lint-file-size.js +3 -3
  246. package/dist/src/core/patch-lint-forward-registration.d.ts +58 -0
  247. package/dist/src/core/patch-lint-forward-registration.js +210 -0
  248. package/dist/src/core/patch-lint-jsdoc.js +36 -36
  249. package/dist/src/core/patch-lint-module-registration.d.ts +2 -8
  250. package/dist/src/core/patch-lint-prettier.js +1 -1
  251. package/dist/src/core/patch-lint-queue-types.d.ts +60 -0
  252. package/dist/src/core/patch-lint-queue-types.js +12 -0
  253. package/dist/src/core/patch-lint-staged-registration.d.ts +61 -0
  254. package/dist/src/core/patch-lint-staged-registration.js +90 -0
  255. package/dist/src/core/patch-lint.d.ts +14 -9
  256. package/dist/src/core/patch-lint.js +4 -3
  257. package/dist/src/core/patch-lock.js +3 -3
  258. package/dist/src/core/patch-manifest-io.js +8 -7
  259. package/dist/src/core/patch-parse.d.ts +19 -8
  260. package/dist/src/core/patch-parse.js +25 -0
  261. package/dist/src/core/patch-policy.js +15 -27
  262. package/dist/src/core/patch-source-metadata.d.ts +1 -3
  263. package/dist/src/core/patch-source-metadata.js +0 -4
  264. package/dist/src/core/patch-transform.d.ts +0 -8
  265. package/dist/src/core/patch-transform.js +0 -10
  266. package/dist/src/core/process-cpu-time.js +1 -1
  267. package/dist/src/core/rebase-session.d.ts +2 -8
  268. package/dist/src/core/rebase-session.js +5 -17
  269. package/dist/src/core/register-browser-content.js +3 -2
  270. package/dist/src/core/register-browser-test.js +2 -1
  271. package/dist/src/core/register-module.js +2 -1
  272. package/dist/src/core/register-scaffold.js +5 -4
  273. package/dist/src/core/register-shared-css.js +2 -1
  274. package/dist/src/core/run-log-redact.d.ts +38 -0
  275. package/dist/src/core/run-log-redact.js +69 -0
  276. package/dist/src/core/run-log.d.ts +13 -2
  277. package/dist/src/core/run-log.js +67 -18
  278. package/dist/src/core/signal-critical.js +2 -4
  279. package/dist/src/core/smoke-patterns.d.ts +0 -1
  280. package/dist/src/core/smoke-patterns.js +6 -11
  281. package/dist/src/core/state-file.d.ts +12 -1
  282. package/dist/src/core/state-file.js +14 -1
  283. package/dist/src/core/status-classify.js +2 -1
  284. package/dist/src/core/test-changed-prefs.js +26 -11
  285. package/dist/src/core/test-harness-crash.js +2 -2
  286. package/dist/src/core/test-stale-check.d.ts +0 -2
  287. package/dist/src/core/test-stale-check.js +2 -2
  288. package/dist/src/core/test-xpcshell-retry.d.ts +26 -7
  289. package/dist/src/core/test-xpcshell-retry.js +14 -11
  290. package/dist/src/core/token-category.d.ts +33 -0
  291. package/dist/src/core/token-category.js +56 -48
  292. package/dist/src/core/token-dark-mode.js +2 -14
  293. package/dist/src/core/token-inventory.d.ts +56 -0
  294. package/dist/src/core/token-inventory.js +109 -0
  295. package/dist/src/core/token-manager.d.ts +1 -1
  296. package/dist/src/core/token-manager.js +1 -1
  297. package/dist/src/core/token-variant.js +21 -22
  298. package/dist/src/core/toolchain-preflight.js +1 -1
  299. package/dist/src/core/tree-cow.d.ts +0 -2
  300. package/dist/src/core/tree-cow.js +2 -2
  301. package/dist/src/core/tree-store.js +4 -5
  302. package/dist/src/core/typecheck.js +2 -2
  303. package/dist/src/core/wire-dom-fragment.js +6 -17
  304. package/dist/src/core/wire-utils.js +5 -17
  305. package/dist/src/core/xpcshell-appdir.js +2 -4
  306. package/dist/src/errors/base.d.ts +53 -1
  307. package/dist/src/errors/base.js +79 -11
  308. package/dist/src/errors/build.js +20 -28
  309. package/dist/src/errors/config.d.ts +0 -14
  310. package/dist/src/errors/config.js +13 -34
  311. package/dist/src/errors/download.d.ts +13 -0
  312. package/dist/src/errors/download.js +53 -24
  313. package/dist/src/errors/furnace.js +8 -9
  314. package/dist/src/errors/git.d.ts +8 -8
  315. package/dist/src/errors/git.js +20 -30
  316. package/dist/src/errors/patch.js +8 -10
  317. package/dist/src/errors/rebase.js +7 -7
  318. package/dist/src/types/commands/options.d.ts +77 -123
  319. package/dist/src/types/config.d.ts +34 -2
  320. package/dist/src/types/index.d.ts +0 -1
  321. package/dist/src/utils/assert.d.ts +3 -15
  322. package/dist/src/utils/assert.js +1 -18
  323. package/dist/src/utils/build-info.d.ts +1 -1
  324. package/dist/src/utils/child-env.d.ts +2 -1
  325. package/dist/src/utils/elapsed.js +10 -1
  326. package/dist/src/utils/fs.js +8 -13
  327. package/dist/src/utils/hash.d.ts +12 -0
  328. package/dist/src/utils/hash.js +16 -0
  329. package/dist/src/utils/line-dispatch.d.ts +15 -0
  330. package/dist/src/utils/line-dispatch.js +48 -0
  331. package/dist/src/utils/options.d.ts +1 -7
  332. package/dist/src/utils/options.js +6 -7
  333. package/dist/src/utils/package-root.js +3 -12
  334. package/dist/src/utils/platform.d.ts +0 -4
  335. package/dist/src/utils/process-group.d.ts +2 -1
  336. package/dist/src/utils/process-group.js +20 -19
  337. package/dist/src/utils/process.d.ts +40 -26
  338. package/dist/src/utils/process.js +115 -166
  339. package/dist/src/utils/ps-duration.js +3 -3
  340. package/dist/src/utils/sleep.d.ts +26 -0
  341. package/dist/src/utils/sleep.js +31 -0
  342. package/dist/src/utils/validation.d.ts +2 -38
  343. package/dist/src/utils/validation.js +7 -16
  344. package/docs/configuration.md +89 -1
  345. package/docs/exit-codes.md +31 -19
  346. package/docs/lifecycle-invariants.md +18 -12
  347. package/docs/machine-output.md +31 -8
  348. package/docs/patch-workflow.md +2 -1
  349. package/docs/run-logs.md +44 -4
  350. package/docs/testing.md +8 -1
  351. package/package.json +8 -7
  352. package/dist/src/commands/rebase.d.ts +0 -4
  353. package/dist/src/commands/rebase.js +0 -5
  354. package/dist/src/core/patch-artifact-normalize.d.ts +0 -21
  355. package/dist/src/core/patch-artifact-normalize.js +0 -24
  356. package/dist/src/core/patch-lint-reexports.d.ts +0 -7
  357. package/dist/src/core/patch-lint-reexports.js +0 -8
  358. package/dist/src/core/wire-targets.d.ts +0 -7
  359. package/dist/src/core/wire-targets.js +0 -8
@@ -1,8 +1,8 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
- import { createHash } from 'node:crypto';
3
2
  import { readFile } from 'node:fs/promises';
4
3
  import { join, resolve } from 'node:path';
5
4
  import { pathExists, readJson, writeJson } from '../utils/fs.js';
5
+ import { sha256Hex } from '../utils/hash.js';
6
6
  import { getPackageVersion } from '../utils/package-root.js';
7
7
  import { getFurnacePaths } from './furnace-config.js';
8
8
  import { git } from './git-base.js';
@@ -42,10 +42,6 @@ function stableJson(value) {
42
42
  .map(([key, item]) => `${JSON.stringify(key)}:${stableJson(item)}`)
43
43
  .join(',')}}`;
44
44
  }
45
- /** Computes a SHA-256 hex digest for text or binary content. */
46
- function sha256Hex(content) {
47
- return createHash('sha256').update(content).digest('hex');
48
- }
49
45
  /** Computes a stable SHA-256 digest for JSON-compatible data. */
50
46
  function stableHash(value) {
51
47
  return sha256Hex(stableJson(value));
@@ -223,7 +219,8 @@ export function getCachedPerPatchLintIssues(cache, patchFilename, key, lintIgnor
223
219
  };
224
220
  }
225
221
  /** Stores the per-patch lint payload after a successful lint calculation. */
226
- export function setCachedPerPatchLintIssues(cache, patchFilename, key, issues, suppressed, lineCount, lintIgnore) {
222
+ export function setCachedPerPatchLintIssues(input) {
223
+ const { cache, patchFilename, key, issues, suppressed, lineCount, lintIgnore } = input;
227
224
  cache.entries[patchFilename] = {
228
225
  key,
229
226
  patchFilename,
@@ -46,6 +46,37 @@ export interface GroupedCheckJsResult {
46
46
  byFile: Map<string, PatchLintIssue[]>;
47
47
  global: PatchLintIssue[];
48
48
  }
49
+ /** Inputs for {@link runCheckJsGrouped}. */
50
+ export interface RunCheckJsGroupedInput {
51
+ /** Absolute engine (repository) directory. */
52
+ repoDir: string;
53
+ /**
54
+ * Patch-owned `.sys.mjs` paths (relative to `repoDir`) the program should
55
+ * see and resolve against.
56
+ */
57
+ resolutionOwned: Set<string>;
58
+ /**
59
+ * Optional project-relative extra `.d.ts` appended to the built-in
60
+ * Firefox-globals shim (from `patchLint.checkJsExtraShim`).
61
+ */
62
+ extraShimPath?: string;
63
+ /** Absolute project root for resolving `extraShimPath`. */
64
+ projectRoot?: string;
65
+ /** Strictness preset plus allowlisted compiler-option overrides. */
66
+ mode?: CheckJsMode;
67
+ /** Optional shim text appended AFTER the consumer shim. */
68
+ builtinShimSuffix?: string;
69
+ /**
70
+ * When set, only these repo-relative files become program ROOTS;
71
+ * resolution (and the host allowlist) still spans all of
72
+ * `resolutionOwned`, so a subset root's cross-patch imports type-check
73
+ * against the real owning sources while unrelated owned files are never
74
+ * parsed. `.mjs` files are module-scoped, so a root's diagnostics are
75
+ * identical whether other owned files are roots or mere resolution
76
+ * targets.
77
+ */
78
+ rootScope?: ReadonlySet<string>;
79
+ }
49
80
  /**
50
81
  * Builds the checkJs program **once** over `resolutionOwned` and returns its
51
82
  * diagnostics grouped by originating file. Callers slice the result by their
@@ -54,24 +85,10 @@ export interface GroupedCheckJsResult {
54
85
  * every file in `resolutionOwned`, so cross-patch `resource:///`/`chrome://`
55
86
  * imports resolve to real sources.
56
87
  *
57
- * @param repoDir - Absolute engine (repository) directory
58
- * @param resolutionOwned - Patch-owned `.sys.mjs` paths (relative to repoDir)
59
- * the program should see and resolve against
60
- * @param extraShimPath - Optional project-relative extra `.d.ts` appended to
61
- * the built-in Firefox-globals shim (from `patchLint.checkJsExtraShim`)
62
- * @param projectRoot - Absolute project root for resolving `extraShimPath`
63
- * @param mode - Strictness preset plus allowlisted compiler-option overrides
64
- * @param builtinShimSuffix - Optional shim text appended AFTER the consumer
65
- * shim
66
- * @param rootScope - When set, only these repo-relative files become program
67
- * ROOTS; resolution (and the host allowlist) still spans all of
68
- * `resolutionOwned`, so a subset root's cross-patch imports type-check
69
- * against the real owning sources while unrelated owned files are never
70
- * parsed. `.mjs` files are module-scoped, so a root's diagnostics are
71
- * identical whether other owned files are roots or mere resolution targets.
88
+ * @param input - Program inputs; see {@link RunCheckJsGroupedInput}
72
89
  * @returns Diagnostics grouped per owning file plus run-level errors
73
90
  */
74
- export declare function runCheckJsGrouped(repoDir: string, resolutionOwned: Set<string>, extraShimPath?: string, projectRoot?: string, mode?: CheckJsMode, builtinShimSuffix?: string, rootScope?: ReadonlySet<string>): Promise<GroupedCheckJsResult>;
91
+ export declare function runCheckJsGrouped(input: RunCheckJsGroupedInput): Promise<GroupedCheckJsResult>;
75
92
  /**
76
93
  * Flattens a {@link runCheckJsGrouped} run into a single issue list. When
77
94
  * `reportScope` is supplied, only diagnostics from files in that set are
@@ -162,24 +162,11 @@ function createPathsResolver(ts, paths, baseDir, fileExists, onResolved) {
162
162
  * every file in `resolutionOwned`, so cross-patch `resource:///`/`chrome://`
163
163
  * imports resolve to real sources.
164
164
  *
165
- * @param repoDir - Absolute engine (repository) directory
166
- * @param resolutionOwned - Patch-owned `.sys.mjs` paths (relative to repoDir)
167
- * the program should see and resolve against
168
- * @param extraShimPath - Optional project-relative extra `.d.ts` appended to
169
- * the built-in Firefox-globals shim (from `patchLint.checkJsExtraShim`)
170
- * @param projectRoot - Absolute project root for resolving `extraShimPath`
171
- * @param mode - Strictness preset plus allowlisted compiler-option overrides
172
- * @param builtinShimSuffix - Optional shim text appended AFTER the consumer
173
- * shim
174
- * @param rootScope - When set, only these repo-relative files become program
175
- * ROOTS; resolution (and the host allowlist) still spans all of
176
- * `resolutionOwned`, so a subset root's cross-patch imports type-check
177
- * against the real owning sources while unrelated owned files are never
178
- * parsed. `.mjs` files are module-scoped, so a root's diagnostics are
179
- * identical whether other owned files are roots or mere resolution targets.
165
+ * @param input - Program inputs; see {@link RunCheckJsGroupedInput}
180
166
  * @returns Diagnostics grouped per owning file plus run-level errors
181
167
  */
182
- export async function runCheckJsGrouped(repoDir, resolutionOwned, extraShimPath, projectRoot, mode, builtinShimSuffix, rootScope) {
168
+ export async function runCheckJsGrouped(input) {
169
+ const { repoDir, resolutionOwned, extraShimPath, rootScope } = input;
183
170
  const empty = { byFile: new Map(), global: [] };
184
171
  if (resolutionOwned.size === 0)
185
172
  return empty;
@@ -227,12 +214,14 @@ export async function runCheckJsGrouped(repoDir, resolutionOwned, extraShimPath,
227
214
  // also undefined, so the resolution target is irrelevant.
228
215
  let shimSource;
229
216
  try {
230
- const composed = await composeShimSource(projectRoot ?? repoDir, extraShimPath);
217
+ const composed = await composeShimSource(input.projectRoot ?? repoDir, extraShimPath);
231
218
  // The suffix (e.g. the loose test-harness baseline) goes
232
219
  // AFTER the consumer's extra shim: TypeScript resolves conflicting
233
220
  // `declare var` redeclarations to the FIRST declaration, so a typed
234
221
  // consumer declaration must precede the loose fallback to win.
235
- shimSource = builtinShimSuffix ? `${composed.source}\n${builtinShimSuffix}` : composed.source;
222
+ shimSource = input.builtinShimSuffix
223
+ ? `${composed.source}\n${input.builtinShimSuffix}`
224
+ : composed.source;
236
225
  if (composed.extraShimAppended) {
237
226
  verbose(`checkJs: extra shim ${extraShimPath ?? ''} appended to Firefox globals shim`);
238
227
  }
@@ -252,7 +241,7 @@ export async function runCheckJsGrouped(repoDir, resolutionOwned, extraShimPath,
252
241
  }
253
242
  const shimPath = toTsPath(resolve(repoDir, SHIM_FILENAME));
254
243
  rootFiles.push(shimPath);
255
- const strict = mode?.strict === true;
244
+ const strict = input.mode?.strict === true;
256
245
  const strictness = strict
257
246
  ? { strict: true, noImplicitAny: true }
258
247
  : {
@@ -266,7 +255,7 @@ export async function runCheckJsGrouped(repoDir, resolutionOwned, extraShimPath,
266
255
  // sources without a hand-generated ambient stub shim.
267
256
  const overrides = {};
268
257
  let pathsMapping;
269
- const co = mode?.compilerOptions;
258
+ const co = input.mode?.compilerOptions;
270
259
  if (co) {
271
260
  for (const key of Object.keys(co)) {
272
261
  const v = co[key];
@@ -351,7 +340,7 @@ export async function runCheckJsGrouped(repoDir, resolutionOwned, extraShimPath,
351
340
  },
352
341
  };
353
342
  const program = ts.createProgram(rootFiles, options, host);
354
- const byFile = groupOwnedDiagnostics(ts, [...program.getSemanticDiagnostics(), ...program.getSyntacticDiagnostics()], relByAbsolute, mode?.undefinedIdentifiers ?? 'warning');
343
+ const byFile = groupOwnedDiagnostics(ts, [...program.getSemanticDiagnostics(), ...program.getSyntacticDiagnostics()], relByAbsolute, input.mode?.undefinedIdentifiers ?? 'warning');
355
344
  verbose(`checkJs: analyzed ${rootFiles.length - 1} file(s) across ${byFile.size} owning file(s)`);
356
345
  return { byFile, global: [] };
357
346
  }
@@ -417,7 +406,13 @@ function groupOwnedDiagnostics(ts, diagnostics, relByAbsolute, undefinedIdentifi
417
406
  * @returns Array of lint issues from TS diagnostics
418
407
  */
419
408
  export async function runCheckJs(repoDir, patchOwnedFiles, extraShimPath, projectRoot, mode, reportScope) {
420
- const { byFile, global } = await runCheckJsGrouped(repoDir, patchOwnedFiles, extraShimPath, projectRoot, mode);
409
+ const { byFile, global } = await runCheckJsGrouped({
410
+ repoDir,
411
+ resolutionOwned: patchOwnedFiles,
412
+ ...(extraShimPath !== undefined ? { extraShimPath } : {}),
413
+ ...(projectRoot !== undefined ? { projectRoot } : {}),
414
+ ...(mode !== undefined ? { mode } : {}),
415
+ });
421
416
  const issues = [...global];
422
417
  for (const [rel, list] of byFile) {
423
418
  if (reportScope && !reportScope.has(rel))
@@ -482,7 +477,16 @@ export async function runCheckJsTestFilesGrouped(repoDir, testFiles, patchLint,
482
477
  roots.add(candidate);
483
478
  }
484
479
  }
485
- const result = await runCheckJsGrouped(repoDir, roots, patchLint.checkJsTestShim, projectRoot, mode, TEST_HARNESS_SHIM);
480
+ const result = await runCheckJsGrouped({
481
+ repoDir,
482
+ resolutionOwned: roots,
483
+ ...(patchLint.checkJsTestShim !== undefined
484
+ ? { extraShimPath: patchLint.checkJsTestShim }
485
+ : {}),
486
+ projectRoot,
487
+ mode,
488
+ builtinShimSuffix: TEST_HARNESS_SHIM,
489
+ });
486
490
  const own = result.byFile.get(file);
487
491
  if (own && own.length > 0) {
488
492
  merged.byFile.set(file, [
@@ -527,7 +531,16 @@ function modeFromPatchLintConfig(patchLint) {
527
531
  * (`--patches`); resolution still spans the whole queue
528
532
  */
529
533
  export async function invokePatchLintCheckJsGrouped(repoDir, patchOwnedFiles, patchLint, projectRoot, rootScope) {
530
- return runCheckJsGrouped(repoDir, patchOwnedFiles, patchLint.checkJsExtraShim, projectRoot, modeFromPatchLintConfig(patchLint), undefined, rootScope);
534
+ return runCheckJsGrouped({
535
+ repoDir,
536
+ resolutionOwned: patchOwnedFiles,
537
+ ...(patchLint.checkJsExtraShim !== undefined
538
+ ? { extraShimPath: patchLint.checkJsExtraShim }
539
+ : {}),
540
+ projectRoot,
541
+ mode: modeFromPatchLintConfig(patchLint),
542
+ ...(rootScope !== undefined ? { rootScope } : {}),
543
+ });
531
544
  }
532
545
  /**
533
546
  * Cheap probe reproducing the only run-level ("global") checkJs findings the
@@ -10,13 +10,9 @@
10
10
  * so a discarded context frees its map; a caller that mutates a context
11
11
  * entry in place must call {@link invalidateNewFileCreatorsCache}.
12
12
  */
13
+ import type { PatchQueueBodyEntry, PatchQueueView } from './patch-lint-queue-types.js';
13
14
  /** The slice of a queue context the creators map is derived from. */
14
- interface CreatorsContext {
15
- entries: {
16
- filename: string;
17
- diff: string;
18
- }[];
19
- }
15
+ type CreatorsContext = PatchQueueView<PatchQueueBodyEntry>;
20
16
  /** Drops the memoised creators map after an in-place context entry update. */
21
17
  export declare function invalidateNewFileCreatorsCache(ctx: CreatorsContext): void;
22
18
  /**
@@ -34,6 +34,17 @@ export interface PatchQueueEntry {
34
34
  * are not indexed here.
35
35
  */
36
36
  newFiles: Map<string, string>;
37
+ /**
38
+ * Every path this patch CREATES, whatever the body kind — text, a
39
+ * reconstructable `GIT binary patch`, or a payload-less
40
+ * `Binary files … differ` stub. `newFiles` is text-only by design (a
41
+ * binary authors no imports, so the import rules gain nothing from it and
42
+ * `extractNewFileContent` correctly refuses to decode one), but "does a
43
+ * later patch create this path" is a question about existence after apply,
44
+ * not about content — and answering it from `newFiles` made every binary
45
+ * creation invisible to the staged-registration rules.
46
+ */
47
+ createdFiles: Set<string>;
37
48
  /**
38
49
  * Map from existing-file path → concatenated added lines from the patch's
39
50
  * hunks against that file (joined with `\n`). Populated for files the
@@ -109,13 +120,10 @@ export declare function isForwardImportableFile(path: string): boolean;
109
120
  * Returns the raw specifier strings — callers should take the leaf basename
110
121
  * to match against the newFileIndex, because we do not resolve `resource://`
111
122
  * URLs to engine file paths.
112
- */
113
- export declare function extractImportSpecifiers(source: string): string[];
114
- /**
115
- * Internal form of {@link extractImportSpecifiers} that also returns the
116
- * (0-indexed) line number where each specifier was found. Used by the
117
- * forward-import rule so it can correlate specifiers against the
118
- * ignore-marker line set and skip suppressed matches.
123
+ *
124
+ * Also returns the (0-indexed) line number where each specifier was found, so
125
+ * the forward-import rule can correlate specifiers against the ignore-marker
126
+ * line set and skip suppressed matches.
119
127
  */
120
128
  export interface ExtractedSpecifier {
121
129
  specifier: string;
@@ -123,7 +131,7 @@ export interface ExtractedSpecifier {
123
131
  }
124
132
  /**
125
133
  * Returns import specifiers plus 0-indexed line numbers, preserving the
126
- * same matching behavior as {@link extractImportSpecifiers}.
134
+ * same matching behavior as {@link extractImportSpecifiersWithLines}.
127
135
  */
128
136
  export declare function extractImportSpecifiersWithLines(source: string): ExtractedSpecifier[];
129
137
  /**
@@ -19,7 +19,9 @@ import { discoverPatches } from './patch-files.js';
19
19
  import { lintPatchQueueBinaryBodies } from './patch-lint-binary.js';
20
20
  import { collectNewFileCreatorsByPath } from './patch-lint-creators.js';
21
21
  import { detectNewFilesInDiff, extractAddedLinesPerFile } from './patch-lint-diff.js';
22
+ import { lintPatchQueueForwardRegistrations } from './patch-lint-forward-registration.js';
22
23
  import { lintPatchQueueModuleRegistrations } from './patch-lint-module-registration.js';
24
+ import { describeRegistrationFailure, findStagedRegistrationFailure, isLaterOwner, quoteRegistrationLine, } from './patch-lint-staged-registration.js';
23
25
  import { loadPatchesManifest } from './patch-manifest-io.js';
24
26
  import { categoryRangeForOrder, categoryRangeLabel } from './patch-policy.js';
25
27
  import { extractNewFileContent } from './patch-transform.js';
@@ -77,6 +79,9 @@ export async function buildPatchQueueContext(patchesDir, config) {
77
79
  metadata: metadataByFilename.get(patch.filename) ?? null,
78
80
  diff,
79
81
  newFiles,
82
+ // Straight from the detector, before the text-extraction filter above
83
+ // drops the bodies it cannot decode.
84
+ createdFiles: newFilePaths,
80
85
  modifiedFileAdditions,
81
86
  });
82
87
  }
@@ -125,24 +130,6 @@ const FORWARD_IMPORTABLE_EXTENSIONS = ['.mjs', '.sys.mjs', '.js', '.jsm'];
125
130
  export function isForwardImportableFile(path) {
126
131
  return FORWARD_IMPORTABLE_EXTENSIONS.some((ext) => path.endsWith(ext));
127
132
  }
128
- /**
129
- * Regex-level extractor for module specifiers in ES module / subscript files.
130
- *
131
- * Intentionally conservative. Catches:
132
- * - `import ... from "specifier"` (ES module static imports)
133
- * - `import "specifier"` (side-effect imports — the `from`
134
- * clause is optional in the regex)
135
- * - `import("specifier")` (dynamic imports)
136
- * - ChromeUtils.importESModule("specifier")
137
- * - ChromeUtils.defineESModuleGetters(obj, { Name: "specifier", ... })
138
- *
139
- * Returns the raw specifier strings — callers should take the leaf basename
140
- * to match against the newFileIndex, because we do not resolve `resource://`
141
- * URLs to engine file paths.
142
- */
143
- export function extractImportSpecifiers(source) {
144
- return extractImportSpecifiersWithLines(source).map((item) => item.specifier);
145
- }
146
133
  function buildLineOffsets(source) {
147
134
  const offsets = [0];
148
135
  for (let i = 0; i < source.length; i++) {
@@ -236,7 +223,7 @@ function collectGetterSpecifiers(stripped, results, offsetToLine) {
236
223
  }
237
224
  /**
238
225
  * Returns import specifiers plus 0-indexed line numbers, preserving the
239
- * same matching behavior as {@link extractImportSpecifiers}.
226
+ * same matching behavior as {@link extractImportSpecifiersWithLines}.
240
227
  */
241
228
  export function extractImportSpecifiersWithLines(source) {
242
229
  // stripJsComments replaces comment bodies with space runs of equal
@@ -290,8 +277,8 @@ function extractBareGetterSpecifiers(source) {
290
277
  const stripped = stripJsComments(source);
291
278
  const results = [];
292
279
  const lines = stripped.split('\n');
293
- for (let i = 0; i < lines.length; i++) {
294
- const match = BARE_GETTER_PROPERTY_LINE.exec(lines[i] ?? '');
280
+ for (const [i, line] of lines.entries()) {
281
+ const match = BARE_GETTER_PROPERTY_LINE.exec(line);
295
282
  if (match?.[1])
296
283
  results.push({ specifier: match[1], line: i });
297
284
  }
@@ -323,9 +310,8 @@ export const FORWARD_IMPORT_IGNORE_MARKER = 'fireforge-ignore: forward-import';
323
310
  export function findForwardImportIgnoreLines(source) {
324
311
  const lines = source.split('\n');
325
312
  const ignored = new Set();
326
- for (let i = 0; i < lines.length; i++) {
327
- const line = lines[i];
328
- if (line && line.includes(FORWARD_IMPORT_IGNORE_MARKER)) {
313
+ for (const [i, line] of lines.entries()) {
314
+ if (line.includes(FORWARD_IMPORT_IGNORE_MARKER)) {
329
315
  ignored.add(i);
330
316
  ignored.add(i + 1);
331
317
  }
@@ -348,29 +334,44 @@ function findMatchingStagedDependency(entry, sitePath, specifier, laterOwners) {
348
334
  laterOwners.some((owner) => owner.fullPath === dependency.creates &&
349
335
  (dependency.owner === undefined || dependency.owner === owner.filename)));
350
336
  }
351
- /**
352
- * Later-in-apply-order predicate shared by the forward-import scan and the
353
- * registration validation: strictly higher ordinal, or same ordinal with a
354
- * lexicographically later filename (the apply loop's tiebreak).
355
- */
356
- function isLaterOwner(owner, entry) {
357
- return (owner.order > entry.order || (owner.order === entry.order && owner.filename > entry.filename));
358
- }
359
- /** Builds the basename → later-creators index used by the forward-import scan. */
360
- function buildNewFileIndex(ctx) {
361
- const newFileIndex = new Map();
337
+ /** Builds a basename → creators index from one path set per entry. */
338
+ function buildCreatorIndex(ctx, pathsOf) {
339
+ const index = new Map();
362
340
  for (const entry of ctx.entries) {
363
- for (const fullPath of entry.newFiles.keys()) {
341
+ for (const fullPath of pathsOf(entry)) {
364
342
  const leaf = basename(fullPath);
365
- let owners = newFileIndex.get(leaf);
343
+ let owners = index.get(leaf);
366
344
  if (!owners) {
367
345
  owners = [];
368
- newFileIndex.set(leaf, owners);
346
+ index.set(leaf, owners);
369
347
  }
370
348
  owners.push({ filename: entry.filename, order: entry.order, fullPath });
371
349
  }
372
350
  }
373
- return newFileIndex;
351
+ return index;
352
+ }
353
+ /**
354
+ * Basename → later-creators index for the forward-IMPORT scan.
355
+ *
356
+ * Keyed on `newFiles` deliberately: this index answers "which patch creates
357
+ * the module this specifier resolves to", and a binary is not a module.
358
+ */
359
+ function buildNewFileIndex(ctx) {
360
+ return buildCreatorIndex(ctx, (entry) => entry.newFiles.keys());
361
+ }
362
+ /**
363
+ * Basename → creators index for the staged-REGISTRATION rules.
364
+ *
365
+ * Keyed on `createdFiles`, which includes binary creations. A registration
366
+ * (`support-files`, a jar.mn line, an actor entry) points at a file that
367
+ * must EXIST after apply; whether its body was text or a `GIT binary patch`
368
+ * has nothing to do with it. Resolving this from `newFiles` made a declared
369
+ * dependency on a binary fixture report `creates-unresolved`, so the
370
+ * declaration read as stale on every lint run and removing it changed
371
+ * nothing either — the failure was symmetric and therefore invisible.
372
+ */
373
+ function buildCreatedFileIndex(ctx) {
374
+ return buildCreatorIndex(ctx, (entry) => entry.createdFiles);
374
375
  }
375
376
  /**
376
377
  * Visits every forward-import site (an import whose leaf is created by a
@@ -389,9 +390,9 @@ function eachForwardImportSite(ctx, newFileIndex, visit) {
389
390
  // Dedupe by (specifier, line): a patch that adds the whole getter
390
391
  // call carries both the opener (found by the balanced walk) and the
391
392
  // property line (found here) — report each site once.
392
- const seen = new Set(sites.map((site) => `${site.specifier}|${String(site.line)}`));
393
+ const seen = new Set(sites.map((site) => `${site.specifier}|${site.line}`));
393
394
  for (const extra of extractBareGetterSpecifiers(content)) {
394
- if (!seen.has(`${extra.specifier}|${String(extra.line)}`))
395
+ if (!seen.has(`${extra.specifier}|${extra.line}`))
395
396
  sites.push(extra);
396
397
  }
397
398
  }
@@ -453,6 +454,7 @@ export function collectForwardImportEdges(ctx) {
453
454
  */
454
455
  export function lintPatchQueueForwardImports(ctx) {
455
456
  const newFileIndex = buildNewFileIndex(ctx);
457
+ const createdFileIndex = buildCreatedFileIndex(ctx);
456
458
  const issues = [];
457
459
  const usedStagedDeclarations = new Set();
458
460
  eachForwardImportSite(ctx, newFileIndex, (entry, sitePath, specifier, cleaned, laterOwners) => {
@@ -531,7 +533,7 @@ export function lintPatchQueueForwardImports(ctx) {
531
533
  // (with `owner`, when set, naming that patch) — mirroring
532
534
  // findMatchingStagedDependency for the import kind.
533
535
  for (const registration of entry.metadata?.stagedDependencies?.registrations ?? []) {
534
- const failure = findStagedRegistrationFailure(entry, registration, newFileIndex);
536
+ const failure = findStagedRegistrationFailure(entry, registration, createdFileIndex);
535
537
  if (failure === undefined)
536
538
  continue;
537
539
  issues.push({
@@ -541,62 +543,16 @@ export function lintPatchQueueForwardImports(ctx) {
541
543
  fingerprint: `staged-dependency-unused|${entry.filename}|${registration.file}|reg:${registration.line}|${registration.creates}|${registration.owner ?? ''}`,
542
544
  message: `${entry.filename} declares a staged registration in ${registration.file} ` +
543
545
  `("${registration.line}") for ${registration.creates}, but ${describeRegistrationFailure(failure)} ` +
544
- 'Remove the stale declaration with "fireforge patch staged-dependency --remove --kind registration" or update it to match the patch and queue.',
546
+ 'Remove the stale declaration with: ' +
547
+ `fireforge patch staged-dependency ${entry.filename} --remove --kind registration ` +
548
+ `--file ${registration.file} --line "${quoteRegistrationLine(registration.line)}" ` +
549
+ `--creates ${registration.creates}; or update it to match the patch and queue.`,
545
550
  severity: 'warning',
546
551
  });
547
552
  }
548
553
  }
549
554
  return issues;
550
555
  }
551
- /**
552
- * Validates one registration-kind declaration against the patch content
553
- * and the queue, mirroring {@link findMatchingStagedDependency}: the
554
- * declared line must be added by this patch, `creates` must exactly match
555
- * a file a LATER-ordered patch creates (an earlier-only creator means the
556
- * dependency is already satisfied and the declaration is stale), and
557
- * `owner`, when set, must name one of those creating patches. Returns
558
- * undefined when valid.
559
- */
560
- function findStagedRegistrationFailure(entry, registration, newFileIndex) {
561
- if (!isRegistrationLinePresent(entry, registration))
562
- return { kind: 'line-absent' };
563
- const creators = (newFileIndex.get(basename(registration.creates)) ?? []).filter((owner) => owner.fullPath === registration.creates && isLaterOwner(owner, entry));
564
- if (creators.length === 0)
565
- return { kind: 'creates-unresolved' };
566
- if (registration.owner !== undefined &&
567
- !creators.some((owner) => owner.filename === registration.owner)) {
568
- return { kind: 'owner-mismatch', creators };
569
- }
570
- return undefined;
571
- }
572
- /** Message tail for each {@link StagedRegistrationFailure} shape. */
573
- function describeRegistrationFailure(failure) {
574
- switch (failure.kind) {
575
- case 'line-absent':
576
- return 'the patch does not add that line.';
577
- case 'creates-unresolved':
578
- return ('no later-ordered patch creates that file — the declaration is stale, ' +
579
- 'or the queue order no longer stages it.');
580
- case 'owner-mismatch':
581
- return `that file is created by ${failure.creators.map((o) => o.filename).join(', ')}, not the declared owner.`;
582
- }
583
- }
584
- /**
585
- * True when the declared registration/packaging line appears (trimmed)
586
- * among the lines the patch introduces in the declaring file — either as a
587
- * newly-created file's content or as added lines to an existing file
588
- * (where jar.mn / customElements.js / actor-registration edits land).
589
- */
590
- function isRegistrationLinePresent(entry, registration) {
591
- const declared = registration.line.trim();
592
- if (declared.length === 0)
593
- return false;
594
- const introduced = [
595
- entry.newFiles.get(registration.file),
596
- entry.modifiedFileAdditions.get(registration.file),
597
- ];
598
- return introduced.some((content) => content !== undefined && content.split('\n').some((line) => line.trim() === declared));
599
- }
600
556
  /**
601
557
  * Cross-patch lint orchestrator. Runs every cross-patch rule against the
602
558
  * provided context and returns combined issues.
@@ -614,6 +570,7 @@ export function lintPatchQueue(ctx) {
614
570
  ...lintPatchQueueDuplicateCreations(ctx),
615
571
  ...lintPatchQueueForwardImports(ctx),
616
572
  ...lintPatchQueueModuleRegistrations(ctx),
573
+ ...lintPatchQueueForwardRegistrations(ctx),
617
574
  ...lintPatchQueueBinaryBodies(ctx),
618
575
  ];
619
576
  }
@@ -73,11 +73,8 @@ function buildAddedLinesScanSource(rawCss, addedNumbers) {
73
73
  .map((n) => maskedLines[n - 1] ?? '')
74
74
  .join('\n');
75
75
  }
76
- /**
77
- * Raw-color check for one patched CSS file, scoped to introduced lines
78
- * when diff context is available. Pushes onto `issues`.
79
- */
80
- function checkRawColorValues(file, rawCss, addedLinesByFile, addedLineNumbersByFile, config, issues) {
76
+ function checkRawColorValues(input) {
77
+ const { file, rawCss, addedLinesByFile, addedLineNumbersByFile, config, issues } = input;
81
78
  // Check only introduced raw color values when diff context is available.
82
79
  // Skip files on the raw-color allowlist (exact path or basename match) and
83
80
  // auto-exempt files under `browser/branding/` — those are the fork's
@@ -207,7 +204,14 @@ export async function lintPatchedCss(repoDir, affectedFiles, diffContent, config
207
204
  const rawCss = await readText(filePath);
208
205
  // Strip block comments before scanning
209
206
  const cssContent = rawCss.replace(/\/\*[\s\S]*?\*\//g, '');
210
- checkRawColorValues(file, rawCss, addedLinesByFile, addedLineNumbersByFile, config, issues);
207
+ checkRawColorValues({
208
+ file,
209
+ rawCss,
210
+ addedLinesByFile,
211
+ addedLineNumbersByFile,
212
+ config,
213
+ issues,
214
+ });
211
215
  checkTokenPrefixViolations(file, cssContent, addedLinesByFile, tokenContext, issues);
212
216
  }
213
217
  return issues;
@@ -44,10 +44,10 @@ export function resolveFileSizeThresholds(overrides, isTest) {
44
44
  export function formatFileTooLargeMessage(args) {
45
45
  const { label, lineCount, thresholds, band, verb } = args;
46
46
  if (band === 'error') {
47
- return `${label} has ${String(lineCount)} lines (error threshold: ${String(thresholds.error)}). Consider ${verb}.`;
47
+ return `${label} has ${lineCount} lines (error threshold: ${thresholds.error}). Consider ${verb}.`;
48
48
  }
49
- return (`${label} has ${String(lineCount)} lines (warning threshold: ${String(thresholds.warning)}, ` +
50
- `error threshold: ${String(thresholds.error)}). Consider ${verb}. ` +
49
+ return (`${label} has ${lineCount} lines (warning threshold: ${thresholds.warning}, ` +
50
+ `error threshold: ${thresholds.error}). Consider ${verb}. ` +
51
51
  `Reported as a ${band}, which a gate running --max-warnings 0 treats as a failure; ` +
52
52
  `tune "patchLint.fileSizeThresholds" if these limits are wrong for this project.`);
53
53
  }
@@ -0,0 +1,58 @@
1
+ import type { PatchLintIssue } from '../types/commands/index.js';
2
+ import type { PatchQueueForwardRegistrationEntry, PatchQueueView } from './patch-lint-queue-types.js';
3
+ /**
4
+ * One `support-files` entry, with the line the patch actually adds it on.
5
+ *
6
+ * The entry alone is not enough for the discharge command the rule prints:
7
+ * `--line` is compared, whitespace-trimmed, against the lines the patch
8
+ * ADDS, and a synthesised `support-files = ["<entry>"]` only ever matches a
9
+ * manifest whose array is single-line and single-entry. Carrying the source
10
+ * line keeps the remedy and the validation talking about the same text.
11
+ */
12
+ export interface SupportFileEntry {
13
+ /** The entry as written inside the manifest (e.g. `fixtures/*.sqlite`). */
14
+ entry: string;
15
+ /** The first added line, whitespace-trimmed, that spells the entry out. */
16
+ line: string;
17
+ /**
18
+ * How many distinct added lines spell it out. Above one, the printed
19
+ * command quotes the first and says so: one declaration covers the file
20
+ * either way, because the declared check keys on file + creates.
21
+ */
22
+ occurrences: number;
23
+ }
24
+ /**
25
+ * Extracts the `support-files` entries declared in `content`, each paired
26
+ * with the manifest line it is written on.
27
+ *
28
+ * Handles the TOML array on one line and spread over several, and the
29
+ * whitespace-separated `.ini` spelling. Anything else — a computed value, a
30
+ * shape this does not recognise — yields nothing, because a rule that
31
+ * guesses at a manifest it cannot parse refuses correct queues.
32
+ *
33
+ * Quoted tokens are matched per physical line rather than over the whole
34
+ * accumulated array body, so a multi-line array attributes each element to
35
+ * its own line. Entries are deduplicated: a manifest that lists the same
36
+ * file under two per-test sections needs one declaration, not two.
37
+ */
38
+ export declare function extractSupportFileEntries(content: string): SupportFileEntry[];
39
+ /**
40
+ * Resolves one `support-files` entry, relative to its manifest's directory,
41
+ * into a matcher over engine-relative paths.
42
+ *
43
+ * Only a `*` inside a single path segment is expanded — the shape the
44
+ * downstream report uses (`fixtures/*.sqlite`). `**`, `!` exclusions and
45
+ * absolute `/`-rooted entries return undefined: they either cross directory
46
+ * boundaries or subtract, and a matcher that guessed at them would attribute
47
+ * a creation to the wrong manifest.
48
+ */
49
+ export declare function buildSupportFileMatcher(manifestPath: string, entry: string): ((candidate: string) => boolean) | undefined;
50
+ /**
51
+ * Flags every `support-files` registration a patch introduces that names a
52
+ * file only a later-ordered patch creates, unless the staged dependency is
53
+ * declared.
54
+ *
55
+ * @param ctx - Projected queue in application order
56
+ * @returns One issue per undeclared forward registration
57
+ */
58
+ export declare function lintPatchQueueForwardRegistrations(ctx: PatchQueueView<PatchQueueForwardRegistrationEntry>): PatchLintIssue[];