@hominis/fireforge 0.51.0 → 0.53.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 (254) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/build-info.json +4 -4
  3. package/dist/src/commands/bootstrap.js +2 -3
  4. package/dist/src/commands/build-prelock.d.ts +3 -3
  5. package/dist/src/commands/build-prelock.js +3 -3
  6. package/dist/src/commands/build.js +4 -0
  7. package/dist/src/commands/declarations-report.d.ts +9 -3
  8. package/dist/src/commands/declarations-report.js +18 -11
  9. package/dist/src/commands/discard.js +3 -3
  10. package/dist/src/commands/doctor-furnace-config-sync.js +4 -4
  11. package/dist/src/commands/doctor-furnace.js +5 -5
  12. package/dist/src/commands/export-adopt-check.d.ts +5 -6
  13. package/dist/src/commands/export-adopt-check.js +5 -6
  14. package/dist/src/commands/export-flow.js +6 -9
  15. package/dist/src/commands/export-staged-registration.d.ts +4 -4
  16. package/dist/src/commands/export-staged-registration.js +4 -4
  17. package/dist/src/commands/forward-import-remedy.d.ts +2 -3
  18. package/dist/src/commands/forward-import-remedy.js +2 -3
  19. package/dist/src/commands/furnace/create-dry-run.d.ts +2 -2
  20. package/dist/src/commands/furnace/deploy-fragment-includers.d.ts +2 -2
  21. package/dist/src/commands/furnace/deploy-fragment-includers.js +2 -2
  22. package/dist/src/commands/furnace/index.js +2 -2
  23. package/dist/src/commands/lint.js +4 -4
  24. package/dist/src/commands/patch/compact.js +1 -1
  25. package/dist/src/commands/patch/lint-ignore-from.d.ts +3 -4
  26. package/dist/src/commands/patch/lint-ignore-from.js +3 -4
  27. package/dist/src/commands/patch/move-files-adopt.d.ts +33 -0
  28. package/dist/src/commands/patch/move-files-adopt.js +65 -0
  29. package/dist/src/commands/patch/move-files-create.js +41 -20
  30. package/dist/src/commands/patch/move-files-into.d.ts +18 -0
  31. package/dist/src/commands/patch/move-files-into.js +125 -60
  32. package/dist/src/commands/patch/move-files-retire.d.ts +39 -0
  33. package/dist/src/commands/patch/move-files-retire.js +91 -0
  34. package/dist/src/commands/patch/move-files.d.ts +3 -0
  35. package/dist/src/commands/patch/move-files.js +42 -12
  36. package/dist/src/commands/patch/move-size-tier.d.ts +27 -0
  37. package/dist/src/commands/patch/move-size-tier.js +66 -0
  38. package/dist/src/commands/patch/rename.js +1 -1
  39. package/dist/src/commands/patch/reorder-projection.d.ts +17 -0
  40. package/dist/src/commands/patch/reorder-projection.js +49 -0
  41. package/dist/src/commands/patch/reorder-shift.d.ts +30 -0
  42. package/dist/src/commands/patch/reorder-shift.js +307 -0
  43. package/dist/src/commands/patch/reorder.d.ts +9 -15
  44. package/dist/src/commands/patch/reorder.js +52 -50
  45. package/dist/src/commands/patch/split-plan.d.ts +31 -3
  46. package/dist/src/commands/patch/split-plan.js +36 -12
  47. package/dist/src/commands/patch/split-staged.d.ts +2 -2
  48. package/dist/src/commands/patch/split-staged.js +2 -2
  49. package/dist/src/commands/patch/split.js +18 -5
  50. package/dist/src/commands/patch/staged-dependency-entries.d.ts +9 -1
  51. package/dist/src/commands/patch/staged-dependency-entries.js +32 -4
  52. package/dist/src/commands/patch/staged-dependency-from.js +15 -9
  53. package/dist/src/commands/patch/staged-dependency.js +13 -9
  54. package/dist/src/commands/patch/tier.js +2 -2
  55. package/dist/src/commands/patch-rename-map.d.ts +14 -3
  56. package/dist/src/commands/patch-rename-map.js +21 -3
  57. package/dist/src/commands/re-export-adjacent.d.ts +4 -5
  58. package/dist/src/commands/re-export-lint.d.ts +4 -3
  59. package/dist/src/commands/re-export-lint.js +4 -3
  60. package/dist/src/commands/re-export-preflight.js +1 -2
  61. package/dist/src/commands/re-export-purity-scope.d.ts +1 -1
  62. package/dist/src/commands/re-export-purity-scope.js +13 -7
  63. package/dist/src/commands/re-export-retired-declarations.d.ts +22 -0
  64. package/dist/src/commands/re-export-retired-declarations.js +113 -0
  65. package/dist/src/commands/re-export-single.js +2 -2
  66. package/dist/src/commands/re-export-stale-registrations.d.ts +27 -6
  67. package/dist/src/commands/re-export-stale-registrations.js +65 -21
  68. package/dist/src/commands/re-export.js +4 -0
  69. package/dist/src/commands/rebase/continue.js +3 -3
  70. package/dist/src/commands/register.js +5 -4
  71. package/dist/src/commands/status-output.js +5 -1
  72. package/dist/src/commands/status.js +1 -1
  73. package/dist/src/commands/test-build-furnace.d.ts +1 -2
  74. package/dist/src/commands/test-build-furnace.js +1 -2
  75. package/dist/src/commands/test-build-phase.d.ts +10 -0
  76. package/dist/src/commands/test-build-phase.js +51 -16
  77. package/dist/src/commands/test-control-restore.d.ts +40 -0
  78. package/dist/src/commands/test-control-restore.js +86 -0
  79. package/dist/src/commands/test-dispatch.js +83 -13
  80. package/dist/src/commands/test-multi-verdict.js +1 -0
  81. package/dist/src/commands/test-orchestrate.d.ts +11 -0
  82. package/dist/src/commands/test-orchestrate.js +33 -2
  83. package/dist/src/commands/test-perf-scope.d.ts +1 -1
  84. package/dist/src/commands/test-perf-scope.js +1 -1
  85. package/dist/src/commands/test-prelock.d.ts +7 -8
  86. package/dist/src/commands/test-prelock.js +15 -12
  87. package/dist/src/commands/test-register.js +2 -2
  88. package/dist/src/commands/test-results.d.ts +10 -1
  89. package/dist/src/commands/test-results.js +20 -3
  90. package/dist/src/commands/test-run.d.ts +6 -0
  91. package/dist/src/commands/test-run.js +23 -4
  92. package/dist/src/commands/test-shards.d.ts +7 -3
  93. package/dist/src/commands/test-shards.js +24 -7
  94. package/dist/src/commands/test-stale-gate.d.ts +6 -1
  95. package/dist/src/commands/test-stale-gate.js +47 -2
  96. package/dist/src/commands/test-verdict.d.ts +5 -3
  97. package/dist/src/commands/test.js +14 -1
  98. package/dist/src/commands/token.js +2 -2
  99. package/dist/src/commands/verify.js +1 -1
  100. package/dist/src/commands/watch.js +1 -1
  101. package/dist/src/commands/wire.js +1 -1
  102. package/dist/src/core/build-audit-resolve.d.ts +4 -5
  103. package/dist/src/core/build-audit-resolve.js +4 -5
  104. package/dist/src/core/build-audit-skip.d.ts +2 -2
  105. package/dist/src/core/build-audit.d.ts +3 -4
  106. package/dist/src/core/build-baseline-types.d.ts +37 -3
  107. package/dist/src/core/build-baseline-validate.d.ts +30 -1
  108. package/dist/src/core/build-baseline-validate.js +134 -33
  109. package/dist/src/core/build-baseline.d.ts +8 -2
  110. package/dist/src/core/build-baseline.js +32 -16
  111. package/dist/src/core/build-jar-escalation.d.ts +4 -4
  112. package/dist/src/core/build-jar-escalation.js +4 -4
  113. package/dist/src/core/build-prepare.js +4 -4
  114. package/dist/src/core/config-validate.js +4 -4
  115. package/dist/src/core/coverage-extend.d.ts +5 -3
  116. package/dist/src/core/coverage-extend.js +8 -3
  117. package/dist/src/core/coverage-records.d.ts +4 -5
  118. package/dist/src/core/coverage-records.js +4 -5
  119. package/dist/src/core/destructive.d.ts +2 -2
  120. package/dist/src/core/destructive.js +2 -2
  121. package/dist/src/core/discard-baseline.d.ts +2 -2
  122. package/dist/src/core/discard-baseline.js +3 -3
  123. package/dist/src/core/engine-session-lock.d.ts +1 -1
  124. package/dist/src/core/engine-session-lock.js +1 -1
  125. package/dist/src/core/file-lock-owner.d.ts +5 -5
  126. package/dist/src/core/file-lock-owner.js +5 -5
  127. package/dist/src/core/file-lock-queue.js +6 -8
  128. package/dist/src/core/file-lock-shared.d.ts +4 -4
  129. package/dist/src/core/file-lock-shared.js +4 -4
  130. package/dist/src/core/file-lock.js +7 -10
  131. package/dist/src/core/firefox.js +2 -2
  132. package/dist/src/core/furnace-apply-overwrite-warn.d.ts +1 -1
  133. package/dist/src/core/furnace-apply-overwrite-warn.js +1 -1
  134. package/dist/src/core/furnace-apply.js +3 -3
  135. package/dist/src/core/furnace-config-validate-exceptions.d.ts +4 -3
  136. package/dist/src/core/furnace-config-validate-exceptions.js +26 -6
  137. package/dist/src/core/furnace-operation.js +5 -5
  138. package/dist/src/core/furnace-registration-jar-inplace.js +5 -6
  139. package/dist/src/core/furnace-registration-rename.d.ts +5 -5
  140. package/dist/src/core/furnace-registration-rename.js +5 -5
  141. package/dist/src/core/furnace-registration-validate.d.ts +2 -1
  142. package/dist/src/core/furnace-registration-validate.js +2 -1
  143. package/dist/src/core/furnace-registration.js +2 -2
  144. package/dist/src/core/furnace-validate-accessibility.d.ts +1 -1
  145. package/dist/src/core/furnace-validate-accessibility.js +22 -11
  146. package/dist/src/core/furnace-validate-exceptions.d.ts +2 -1
  147. package/dist/src/core/furnace-validate-exceptions.js +31 -13
  148. package/dist/src/core/git-base.js +4 -3
  149. package/dist/src/core/git-diff.js +1 -1
  150. package/dist/src/core/git.js +1 -1
  151. package/dist/src/core/harness-orphans.d.ts +6 -6
  152. package/dist/src/core/harness-orphans.js +6 -6
  153. package/dist/src/core/harness-port-reservation.d.ts +60 -0
  154. package/dist/src/core/harness-port-reservation.js +122 -0
  155. package/dist/src/core/history-log-file.d.ts +9 -9
  156. package/dist/src/core/history-log-file.js +15 -15
  157. package/dist/src/core/history-log.d.ts +3 -3
  158. package/dist/src/core/history-log.js +3 -3
  159. package/dist/src/core/launchable-build.d.ts +39 -0
  160. package/dist/src/core/launchable-build.js +85 -0
  161. package/dist/src/core/marionette-port.js +1 -3
  162. package/dist/src/core/moz-manifest-rules.js +3 -2
  163. package/dist/src/core/negative-control-compiled.d.ts +22 -3
  164. package/dist/src/core/negative-control-compiled.js +41 -5
  165. package/dist/src/core/parser-fallback.d.ts +5 -5
  166. package/dist/src/core/parser-fallback.js +5 -5
  167. package/dist/src/core/patch-identifier-suggest.d.ts +6 -0
  168. package/dist/src/core/patch-identifier-suggest.js +11 -4
  169. package/dist/src/core/patch-lint-checkjs.d.ts +1 -1
  170. package/dist/src/core/patch-lint-checkjs.js +1 -1
  171. package/dist/src/core/patch-lint-css.js +2 -2
  172. package/dist/src/core/patch-lint-file-size.d.ts +5 -7
  173. package/dist/src/core/patch-lint-file-size.js +5 -7
  174. package/dist/src/core/patch-lint-ignore.d.ts +3 -3
  175. package/dist/src/core/patch-lint-ignore.js +3 -3
  176. package/dist/src/core/patch-lint-prettier.d.ts +4 -6
  177. package/dist/src/core/patch-lint-prettier.js +4 -6
  178. package/dist/src/core/patch-lint-projection.d.ts +1 -1
  179. package/dist/src/core/patch-lint-projection.js +1 -1
  180. package/dist/src/core/patch-lint-queue-types.d.ts +2 -2
  181. package/dist/src/core/patch-lint-queue-types.js +2 -2
  182. package/dist/src/core/patch-manifest-consistency.d.ts +2 -2
  183. package/dist/src/core/patch-manifest-consistency.js +2 -2
  184. package/dist/src/core/patch-manifest-io.d.ts +5 -1
  185. package/dist/src/core/patch-manifest-io.js +12 -10
  186. package/dist/src/core/patch-manifest-key-order.d.ts +3 -4
  187. package/dist/src/core/patch-manifest-key-order.js +3 -4
  188. package/dist/src/core/patch-parse.d.ts +3 -3
  189. package/dist/src/core/patch-parse.js +3 -3
  190. package/dist/src/core/patch-policy.d.ts +1 -1
  191. package/dist/src/core/patch-policy.js +1 -1
  192. package/dist/src/core/patch-source-metadata.d.ts +1 -1
  193. package/dist/src/core/patch-source-metadata.js +1 -1
  194. package/dist/src/core/patch-subset.d.ts +5 -3
  195. package/dist/src/core/patch-subset.js +8 -7
  196. package/dist/src/core/patch-transform.d.ts +2 -2
  197. package/dist/src/core/patch-transform.js +2 -2
  198. package/dist/src/core/rebase-session.js +4 -6
  199. package/dist/src/core/register-xpcshell-test.d.ts +28 -7
  200. package/dist/src/core/register-xpcshell-test.js +76 -27
  201. package/dist/src/core/run-log.d.ts +8 -8
  202. package/dist/src/core/run-log.js +8 -8
  203. package/dist/src/core/status-classify.d.ts +1 -2
  204. package/dist/src/core/status-classify.js +1 -2
  205. package/dist/src/core/test-harness-crash.d.ts +5 -3
  206. package/dist/src/core/test-harness-crash.js +13 -14
  207. package/dist/src/core/test-harness-infra.d.ts +32 -0
  208. package/dist/src/core/test-harness-infra.js +69 -0
  209. package/dist/src/core/test-harness-signature.d.ts +6 -0
  210. package/dist/src/core/test-run-completeness.d.ts +12 -5
  211. package/dist/src/core/test-run-completeness.js +15 -8
  212. package/dist/src/core/test-stale-check.d.ts +48 -1
  213. package/dist/src/core/test-stale-check.js +97 -2
  214. package/dist/src/core/test-stall-triage.js +5 -7
  215. package/dist/src/core/token-category.js +2 -2
  216. package/dist/src/core/token-inventory.js +3 -3
  217. package/dist/src/core/token-manager.d.ts +2 -2
  218. package/dist/src/core/token-manager.js +5 -7
  219. package/dist/src/core/token-variant.d.ts +6 -6
  220. package/dist/src/core/token-variant.js +7 -7
  221. package/dist/src/core/typecheck-shim.d.ts +2 -1
  222. package/dist/src/core/typecheck-shim.js +29 -5
  223. package/dist/src/core/wire-destroy.d.ts +1 -1
  224. package/dist/src/core/wire-destroy.js +1 -1
  225. package/dist/src/core/wire-dom-fragment.js +1 -1
  226. package/dist/src/core/wire-init.d.ts +1 -1
  227. package/dist/src/core/wire-init.js +1 -1
  228. package/dist/src/core/wire-subscript.d.ts +1 -1
  229. package/dist/src/core/wire-subscript.js +1 -1
  230. package/dist/src/core/wire-utils.d.ts +2 -2
  231. package/dist/src/core/wire-utils.js +3 -3
  232. package/dist/src/errors/base.d.ts +1 -1
  233. package/dist/src/errors/base.js +1 -1
  234. package/dist/src/errors/rebase.d.ts +1 -1
  235. package/dist/src/errors/rebase.js +1 -1
  236. package/dist/src/types/commands/options.d.ts +26 -3
  237. package/dist/src/types/config.d.ts +5 -7
  238. package/dist/src/types/furnace.d.ts +9 -0
  239. package/dist/src/utils/options.js +2 -2
  240. package/dist/src/utils/process.js +2 -2
  241. package/dist/src/utils/regex.d.ts +2 -2
  242. package/dist/src/utils/regex.js +3 -3
  243. package/dist/src/utils/sleep.d.ts +5 -6
  244. package/dist/src/utils/sleep.js +5 -6
  245. package/dist/src/utils/validation.js +2 -2
  246. package/docs/configuration.md +40 -26
  247. package/docs/exit-codes.md +8 -11
  248. package/docs/furnace.md +21 -3
  249. package/docs/lifecycle-invariants.md +36 -45
  250. package/docs/machine-output.md +43 -22
  251. package/docs/patch-workflow.md +129 -24
  252. package/docs/run-logs.md +8 -9
  253. package/docs/testing.md +118 -44
  254. package/package.json +1 -1
@@ -2,12 +2,12 @@
2
2
  /**
3
3
  * Where the patch history log lives, and who wrote each record.
4
4
  *
5
- * The log used to be `patches/.fireforge-history.jsonl`, inside the tracked
6
- * patch queue, so it was committed with every export until consumers
7
- * gitignored it, and it grew to tens of megabytes. It now defaults to
8
- * `.fireforge/history.jsonl` (FireForge's own untracked data directory), or
9
- * `fireforge.json#history.path` when a project sets one. A log left at the
10
- * old path is moved on the first append.
5
+ * The log defaults to `.fireforge/history.jsonl` (FireForge's own untracked
6
+ * data directory), or `fireforge.json#history.path` when a project sets one.
7
+ * It stays out of the tracked patch queue because it grows to tens of
8
+ * megabytes and would otherwise be committed with every export. A log found
9
+ * at `patches/.fireforge-history.jsonl`, inside the queue, is moved on the
10
+ * first append.
11
11
  *
12
12
  * Several sessions sharing one checkout all append to the same log, so each
13
13
  * record carries the invocation that wrote it, plus a session hint, letting
@@ -21,7 +21,7 @@ import { ensureDir, pathExists } from '../utils/fs.js';
21
21
  import { info, verbose } from '../utils/logger.js';
22
22
  import { FIREFORGE_DIR } from './config-paths.js';
23
23
  import { isValidHistoryPath } from './config-validate-history.js';
24
- /** Filename of the history log at its pre-0.50 location, inside `patches/`. */
24
+ /** Filename of a history log inside `patches/`, which is moved out of the queue. */
25
25
  export const LEGACY_HISTORY_LOG_FILENAME = '.fireforge-history.jsonl';
26
26
  /** Default history log location, relative to the project root. */
27
27
  export const DEFAULT_HISTORY_LOG_PATH = join(FIREFORGE_DIR, 'history.jsonl');
@@ -87,24 +87,24 @@ export async function resolveHistoryLogPath(projectRoot) {
87
87
  return resolve(projectRoot, configured ?? DEFAULT_HISTORY_LOG_PATH);
88
88
  }
89
89
  /**
90
- * Moves a history log left at the legacy `patches/` path to `target`.
90
+ * Moves a history log found at the `patches/` path to `target`.
91
91
  *
92
92
  * Records are never dropped, whatever else is happening:
93
- * - The legacy file is first claimed by renaming it aside, so of two
93
+ * - The in-queue file is first claimed by renaming it aside, so of two
94
94
  * commands migrating at once exactly one does the work and neither
95
95
  * duplicates it.
96
96
  * - When `target` does not exist yet (the first run), the claimed file is
97
97
  * hard-linked into place, which cannot overwrite a log another command
98
98
  * created in the meantime, and keeps a large log a constant-time move.
99
- * - When both exist (an older FireForge appended to the legacy path after
100
- * a migration, or `history.path` changed), the legacy records are
99
+ * - When both exist (a log reappeared at the `patches/` path after a
100
+ * migration, or `history.path` changed), the in-queue records are
101
101
  * appended to the target in one write. They then follow newer records;
102
102
  * every record carries its own `ts`.
103
103
  *
104
104
  * A crash between the claim and the move leaves the records in a
105
- * `.migrating-*` file next to the legacy path, never deleted.
105
+ * `.migrating-*` file next to the in-queue path, never deleted.
106
106
  *
107
- * @param patchesDir - The patches directory that may hold a legacy log
107
+ * @param patchesDir - The patches directory that may hold an in-queue log
108
108
  * @param target - Absolute path of the history log
109
109
  */
110
110
  export async function migrateLegacyHistoryLog(patchesDir, target) {
@@ -149,8 +149,8 @@ export async function migrateLegacyHistoryLog(patchesDir, target) {
149
149
  'Nothing reads it back; it is an audit trail only.');
150
150
  }
151
151
  /**
152
- * Appends one serialized record to the project's history log, migrating a
153
- * legacy log first.
152
+ * Appends one serialized record to the project's history log, first moving
153
+ * a log found inside `patches/`.
154
154
  *
155
155
  * @param patchesDir - The project's `patches/` directory; the project root is
156
156
  * its parent, as every caller derives it from `getProjectPaths`
@@ -3,13 +3,13 @@ import type { HistoryEntry } from './destructive.js';
3
3
  * Appends a history entry for a mutation that has already committed, degrading
4
4
  * to a warning when the log cannot be written.
5
5
  *
6
- * Eleven call sites across `export-flow` and the `patch` subcommands had each
7
- * grown the same `try { await appendHistory(...) } catch { warn(...) }` block.
6
+ * This is the one `try { await appendHistory(...) } catch { warn(...) }`
7
+ * block for every call site across `export-flow` and the `patch` subcommands.
8
8
  * The swallow is intentional: the patch files and the manifest
9
9
  * are already on disk by the time this runs, so letting a failed log write
10
10
  * reject would report a completed, irreversible mutation as a failure and send
11
11
  * the operator looking for damage that is not there. Because that reasoning is
12
- * easy to lose in a copy, it now lives in one place.
12
+ * easy to lose in a copy, it lives in one place.
13
13
  * @param patchesDir - The project's `patches/` directory (the log itself lives
14
14
  * under `.fireforge/` or `history.path`; see `history-log-file.ts`).
15
15
  * @param entry - The entry to append.
@@ -6,13 +6,13 @@ import { appendHistory } from './destructive.js';
6
6
  * Appends a history entry for a mutation that has already committed, degrading
7
7
  * to a warning when the log cannot be written.
8
8
  *
9
- * Eleven call sites across `export-flow` and the `patch` subcommands had each
10
- * grown the same `try { await appendHistory(...) } catch { warn(...) }` block.
9
+ * This is the one `try { await appendHistory(...) } catch { warn(...) }`
10
+ * block for every call site across `export-flow` and the `patch` subcommands.
11
11
  * The swallow is intentional: the patch files and the manifest
12
12
  * are already on disk by the time this runs, so letting a failed log write
13
13
  * reject would report a completed, irreversible mutation as a failure and send
14
14
  * the operator looking for damage that is not there. Because that reasoning is
15
- * easy to lose in a copy, it now lives in one place.
15
+ * easy to lose in a copy, it lives in one place.
16
16
  * @param patchesDir - The project's `patches/` directory (the log itself lives
17
17
  * under `.fireforge/` or `history.path`; see `history-log-file.ts`).
18
18
  * @param entry - The entry to append.
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Facts about the launchable build a test run will exercise: its build id
3
+ * and when its compiled code was last linked.
4
+ *
5
+ * A green or red against a binary older than the C++ under test reads as a
6
+ * verdict on that C++ unless the run says which binary it was. The
7
+ * build id goes on the verdict line (`build-id=`), and the link time anchors
8
+ * the stale-compiled preflight (`findCompiledSourcesNewerThanBinary` in
9
+ * `test-stale-check.ts`).
10
+ */
11
+ /**
12
+ * The `application.ini` beside a launchable binary: `Contents/Resources`
13
+ * of the `.app` bundle on macOS, the binary's own directory (`dist/bin`)
14
+ * elsewhere.
15
+ *
16
+ * @param binaryPath - Absolute launchable binary path
17
+ */
18
+ export declare function applicationIniPath(binaryPath: string): string;
19
+ /**
20
+ * Reads the `BuildID=` of the launchable build (the `moz:buildID` the
21
+ * browser reports). Undefined when the file or the key is missing, so a
22
+ * caller can simply leave the attribute off.
23
+ *
24
+ * @param engineDir - Absolute engine directory
25
+ * @param launchablePath - Engine-relative launchable binary path
26
+ */
27
+ export declare function readBuildId(engineDir: string, launchablePath: string): Promise<string | undefined>;
28
+ /**
29
+ * When the launchable build's compiled code was last linked: the newest
30
+ * mtime of the binary and the `libxul` beside it. Undefined when neither
31
+ * can be read.
32
+ *
33
+ * @param engineDir - Absolute engine directory
34
+ * @param launchablePath - Engine-relative launchable binary path
35
+ */
36
+ export declare function readLinkedAt(engineDir: string, launchablePath: string): Promise<{
37
+ mtimeMs: number;
38
+ path: string;
39
+ } | undefined>;
@@ -0,0 +1,85 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Facts about the launchable build a test run will exercise: its build id
4
+ * and when its compiled code was last linked.
5
+ *
6
+ * A green or red against a binary older than the C++ under test reads as a
7
+ * verdict on that C++ unless the run says which binary it was. The
8
+ * build id goes on the verdict line (`build-id=`), and the link time anchors
9
+ * the stale-compiled preflight (`findCompiledSourcesNewerThanBinary` in
10
+ * `test-stale-check.ts`).
11
+ */
12
+ import { stat } from 'node:fs/promises';
13
+ import { basename, dirname, join } from 'node:path';
14
+ import { getNodeErrorCode, toError } from '../utils/errors.js';
15
+ import { readText } from '../utils/fs.js';
16
+ import { verbose } from '../utils/logger.js';
17
+ /**
18
+ * The compiled libraries a build links next to the launchable binary.
19
+ * `libxul` carries nearly all compiled engine code (C++, Rust, generated
20
+ * bindings, prefs), so it, not the small launcher executable, is what a
21
+ * compiled-source edit relinks.
22
+ */
23
+ const LINKED_LIBRARY_NAMES = ['XUL', 'libxul.so', 'xul.dll'];
24
+ /**
25
+ * The `application.ini` beside a launchable binary: `Contents/Resources`
26
+ * of the `.app` bundle on macOS, the binary's own directory (`dist/bin`)
27
+ * elsewhere.
28
+ *
29
+ * @param binaryPath - Absolute launchable binary path
30
+ */
31
+ export function applicationIniPath(binaryPath) {
32
+ const binaryDir = dirname(binaryPath);
33
+ if (basename(binaryDir) === 'MacOS' && basename(dirname(binaryDir)) === 'Contents') {
34
+ return join(dirname(binaryDir), 'Resources', 'application.ini');
35
+ }
36
+ return join(binaryDir, 'application.ini');
37
+ }
38
+ /**
39
+ * Reads the `BuildID=` of the launchable build (the `moz:buildID` the
40
+ * browser reports). Undefined when the file or the key is missing, so a
41
+ * caller can simply leave the attribute off.
42
+ *
43
+ * @param engineDir - Absolute engine directory
44
+ * @param launchablePath - Engine-relative launchable binary path
45
+ */
46
+ export async function readBuildId(engineDir, launchablePath) {
47
+ const iniPath = applicationIniPath(join(engineDir, launchablePath));
48
+ let content;
49
+ try {
50
+ content = await readText(iniPath);
51
+ }
52
+ catch (error) {
53
+ if (getNodeErrorCode(error) !== 'ENOENT') {
54
+ verbose(`Build id: could not read ${iniPath} — ${toError(error).message}`);
55
+ }
56
+ return undefined;
57
+ }
58
+ const match = /^BuildID=(\S+)\s*$/m.exec(content);
59
+ return match?.[1];
60
+ }
61
+ /**
62
+ * When the launchable build's compiled code was last linked: the newest
63
+ * mtime of the binary and the `libxul` beside it. Undefined when neither
64
+ * can be read.
65
+ *
66
+ * @param engineDir - Absolute engine directory
67
+ * @param launchablePath - Engine-relative launchable binary path
68
+ */
69
+ export async function readLinkedAt(engineDir, launchablePath) {
70
+ const binary = join(engineDir, launchablePath);
71
+ const candidates = [binary, ...LINKED_LIBRARY_NAMES.map((name) => join(dirname(binary), name))];
72
+ let newest;
73
+ for (const path of candidates) {
74
+ try {
75
+ // stat follows the dist/bin symlinks to the linked objdir file.
76
+ const { mtimeMs } = await stat(path);
77
+ if (newest === undefined || mtimeMs > newest.mtimeMs)
78
+ newest = { mtimeMs, path };
79
+ }
80
+ catch {
81
+ // A platform's other library names are simply absent.
82
+ }
83
+ }
84
+ return newest;
85
+ }
@@ -247,9 +247,7 @@ async function probeWithLsof(port) {
247
247
  catch {
248
248
  // `lsof` missing, or its stdout did not parse. Either way the port state
249
249
  // is unknown, and an unknown port is reported as free so the probe never
250
- // blocks a run on its own uncertainty. (The errno test that used to sit
251
- // here returned the same value as this fallthrough, so it decided
252
- // nothing.)
250
+ // blocks a run on its own uncertainty.
253
251
  return { inUse: false };
254
252
  }
255
253
  }
@@ -77,11 +77,12 @@ export function getRules(binaryName, createManifest = false) {
77
77
  // scaffolded and wired via XPCSHELL_TESTS_MANIFESTS.
78
78
  pattern: /^(?!browser\/base\/content\/test\/)(.+)\/(test_[^/]+\.js)$/,
79
79
  isRegistered: (engineDir, dirRel, fileName) => isXpcshellTestRegistered(engineDir, dirRel, fileName),
80
- register: (engineDir, _after, dryRun, dirRel, fileName) => registerXpcshellTest(engineDir, dirRel, fileName, dryRun, createManifest),
80
+ register: (engineDir, after, dryRun, dirRel, fileName) => registerXpcshellTest(engineDir, dirRel, fileName, dryRun, createManifest, after),
81
81
  // Both groups are guaranteed by the pattern, so slice avoids dead
82
82
  // fallback branches.
83
83
  extractArgs: (m) => m.slice(1, 3),
84
- supportsAfter: false,
84
+ // `--after <entry>` places the section after that entry's block.
85
+ supportsAfter: true,
85
86
  },
86
87
  ];
87
88
  }
@@ -6,18 +6,37 @@
6
6
  * objdir links to. A C++ (or Rust, IDL, …) source edited at that point is
7
7
  * never compiled: the run tests the unmutated binary and reports
8
8
  * `control=missed`, which reads as a test that cannot fail. Such a target
9
- * is now refused without `--build`, and with `--build` the control is
10
- * applied before the build so the binary under test carries it.
9
+ * is refused without `--build`, and with `--build` the control is
10
+ * applied before the build so the binary under test carries it. That build
11
+ * is a full `mach build`: the pre-test build is otherwise `mach build
12
+ * faster`, which compiles nothing, so the control would still miss.
11
13
  */
14
+ /**
15
+ * Whether an engine-relative path has a compiled-source extension: a file
16
+ * the build compiles or code-generates into the binary. This is the
17
+ * extension test only. {@link findCompiledControlTargets} additionally
18
+ * drops non-header files an install manifest ships as-is.
19
+ *
20
+ * @param path - Engine-relative path
21
+ */
22
+ export declare function isCompiledSourcePath(path: string): boolean;
12
23
  /**
13
24
  * The engine-relative targets that reach the runtime only through a
14
25
  * compile: a compiled-source extension and no install-manifest entry that
15
- * ships the file itself.
26
+ * ships the file itself. Headers are always compiled, even when an
27
+ * `EXPORTS` manifest installs them into `dist/include`.
16
28
  *
17
29
  * @param engineDir - Absolute engine directory
18
30
  * @param enginePaths - The control's engine-relative targets
19
31
  */
20
32
  export declare function findCompiledControlTargets(engineDir: string, enginePaths: readonly string[]): Promise<string[]>;
33
+ /**
34
+ * The notice that explains why a compiled control's pre-test build is a
35
+ * full `mach build` instead of `mach build faster`.
36
+ *
37
+ * @param compiled - Engine-relative compiled targets
38
+ */
39
+ export declare function formatCompiledControlBuildNotice(compiled: readonly string[]): string;
21
40
  /**
22
41
  * The refusal for a compiled control without `--build`, naming the manual
23
42
  * procedure for a run that wants to keep its build out of FireForge.
@@ -7,8 +7,10 @@
7
7
  * objdir links to. A C++ (or Rust, IDL, …) source edited at that point is
8
8
  * never compiled: the run tests the unmutated binary and reports
9
9
  * `control=missed`, which reads as a test that cannot fail. Such a target
10
- * is now refused without `--build`, and with `--build` the control is
11
- * applied before the build so the binary under test carries it.
10
+ * is refused without `--build`, and with `--build` the control is
11
+ * applied before the build so the binary under test carries it. That build
12
+ * is a full `mach build`: the pre-test build is otherwise `mach build
13
+ * faster`, which compiles nothing, so the control would still miss.
12
14
  */
13
15
  import { extname } from 'node:path';
14
16
  import { classifyRuntimeDelivery } from './objdir-install-manifests.js';
@@ -32,29 +34,63 @@ const COMPILED_EXTENSIONS = new Set([
32
34
  '.yaml',
33
35
  '.yml',
34
36
  ]);
37
+ /**
38
+ * Header extensions. An `EXPORTS` install manifest names many headers (it
39
+ * copies them into `dist/include`), but that copy only feeds other
40
+ * compiles: the runtime never reads a header, so a header edit reaches the
41
+ * binary through a compile whatever the manifests say.
42
+ */
43
+ const HEADER_EXTENSIONS = new Set(['.h', '.hh', '.hpp', '.ipdlh']);
44
+ /**
45
+ * Whether an engine-relative path has a compiled-source extension: a file
46
+ * the build compiles or code-generates into the binary. This is the
47
+ * extension test only. {@link findCompiledControlTargets} additionally
48
+ * drops non-header files an install manifest ships as-is.
49
+ *
50
+ * @param path - Engine-relative path
51
+ */
52
+ export function isCompiledSourcePath(path) {
53
+ return COMPILED_EXTENSIONS.has(extname(path));
54
+ }
35
55
  /**
36
56
  * The engine-relative targets that reach the runtime only through a
37
57
  * compile: a compiled-source extension and no install-manifest entry that
38
- * ships the file itself.
58
+ * ships the file itself. Headers are always compiled, even when an
59
+ * `EXPORTS` manifest installs them into `dist/include`.
39
60
  *
40
61
  * @param engineDir - Absolute engine directory
41
62
  * @param enginePaths - The control's engine-relative targets
42
63
  */
43
64
  export async function findCompiledControlTargets(engineDir, enginePaths) {
44
- const candidates = enginePaths.filter((path) => COMPILED_EXTENSIONS.has(extname(path)));
65
+ const candidates = enginePaths.filter(isCompiledSourcePath);
45
66
  if (candidates.length === 0)
46
67
  return [];
68
+ if (candidates.every((path) => HEADER_EXTENSIONS.has(extname(path))))
69
+ return candidates;
47
70
  const index = await loadEngineInstallIndex(engineDir);
48
71
  if (index === undefined)
49
72
  return candidates;
50
73
  const compiled = [];
51
74
  for (const path of candidates) {
52
- if ((await classifyRuntimeDelivery(index, engineDir, path)) === 'unlisted') {
75
+ if (HEADER_EXTENSIONS.has(extname(path)) ||
76
+ (await classifyRuntimeDelivery(index, engineDir, path)) === 'unlisted') {
53
77
  compiled.push(path);
54
78
  }
55
79
  }
56
80
  return compiled;
57
81
  }
82
+ /**
83
+ * The notice that explains why a compiled control's pre-test build is a
84
+ * full `mach build` instead of `mach build faster`.
85
+ *
86
+ * @param compiled - Engine-relative compiled targets
87
+ */
88
+ export function formatCompiledControlBuildNotice(compiled) {
89
+ const files = compiled.map((path) => `engine/${path}`).join(', ');
90
+ return (`The negative control edits compiled source (${files}); escalating this pre-test build to ` +
91
+ 'a full mach build, since mach build faster compiles nothing and the binary under test ' +
92
+ 'would not carry the control.');
93
+ }
58
94
  /**
59
95
  * The refusal for a compiled control without `--build`, naming the manual
60
96
  * procedure for a run that wants to keep its build out of FireForge.
@@ -13,12 +13,12 @@ export declare function consumeParserFallbackEvents(): ParserFallbackEvent[];
13
13
  * The `rethrowIf` predicate every AST-primary caller wants.
14
14
  *
15
15
  * Rethrows internal-invariant `GeneralError`s ("Unexpected empty …array"):
16
- * those are programming bugs, and retrying the legacy scanner cannot fix a
16
+ * those are programming bugs, and retrying the fallback scanner cannot fix a
17
17
  * broken invariant, it just buries the stack. Everything else still falls
18
18
  * back, which matters: the AST path raises a raw acorn SyntaxError
19
19
  * on chrome sources acorn cannot parse (preprocessor directives), and
20
20
  * BuildError when the file's shape is unexpected. Both are exactly what the
21
- * legacy scanner is here to handle.
21
+ * fallback scanner is here to handle.
22
22
  *
23
23
  * @param error - The error the primary parser threw
24
24
  * @returns True when the error must propagate instead of falling back
@@ -33,15 +33,15 @@ export interface ParserResult<T> {
33
33
  fallbackReason?: string;
34
34
  }
35
35
  /**
36
- * Wraps a primary (AST/tokenizer) parser with a legacy (regex/line-based)
36
+ * Wraps a primary (AST/tokenizer) parser with a regex/line-based
37
37
  * fallback. If the primary throws, a warning is logged and the fallback is
38
38
  * used instead.
39
39
  *
40
40
  * Returns a {@link ParserResult} so callers (and tests) can detect which
41
41
  * code path was taken and surface it in logs or assertions.
42
42
  *
43
- * @param primary - The modern parser implementation
44
- * @param fallback - The legacy fallback implementation
43
+ * @param primary - The primary parser implementation
44
+ * @param fallback - The regex/line-based fallback implementation
45
45
  * @param context - File/context name used in the warning message
46
46
  * @param rethrowIf - Optional predicate. If it returns true for the caught
47
47
  * error the error is re-thrown instead of falling back (useful for
@@ -18,12 +18,12 @@ export function consumeParserFallbackEvents() {
18
18
  * The `rethrowIf` predicate every AST-primary caller wants.
19
19
  *
20
20
  * Rethrows internal-invariant `GeneralError`s ("Unexpected empty …array"):
21
- * those are programming bugs, and retrying the legacy scanner cannot fix a
21
+ * those are programming bugs, and retrying the fallback scanner cannot fix a
22
22
  * broken invariant, it just buries the stack. Everything else still falls
23
23
  * back, which matters: the AST path raises a raw acorn SyntaxError
24
24
  * on chrome sources acorn cannot parse (preprocessor directives), and
25
25
  * BuildError when the file's shape is unexpected. Both are exactly what the
26
- * legacy scanner is here to handle.
26
+ * fallback scanner is here to handle.
27
27
  *
28
28
  * @param error - The error the primary parser threw
29
29
  * @returns True when the error must propagate instead of falling back
@@ -32,15 +32,15 @@ export function isInternalInvariantFailure(error) {
32
32
  return error instanceof GeneralError;
33
33
  }
34
34
  /**
35
- * Wraps a primary (AST/tokenizer) parser with a legacy (regex/line-based)
35
+ * Wraps a primary (AST/tokenizer) parser with a regex/line-based
36
36
  * fallback. If the primary throws, a warning is logged and the fallback is
37
37
  * used instead.
38
38
  *
39
39
  * Returns a {@link ParserResult} so callers (and tests) can detect which
40
40
  * code path was taken and surface it in logs or assertions.
41
41
  *
42
- * @param primary - The modern parser implementation
43
- * @param fallback - The legacy fallback implementation
42
+ * @param primary - The primary parser implementation
43
+ * @param fallback - The regex/line-based fallback implementation
44
44
  * @param context - File/context name used in the warning message
45
45
  * @param rethrowIf - Optional predicate. If it returns true for the caught
46
46
  * error the error is re-thrown instead of falling back (useful for
@@ -18,3 +18,9 @@ import type { PatchMetadata } from '../types/commands/index.js';
18
18
  * `InvalidArgumentError` body, never the full queue enumeration.
19
19
  */
20
20
  export declare function formatPatchNotFoundError(identifier: string, patches: readonly PatchMetadata[]): string;
21
+ /**
22
+ * The nearest-match half of {@link formatPatchNotFoundError}: the closest
23
+ * accepted identifiers and the queue size, never the full queue. For
24
+ * callers that word their own lead, such as the `--patches` subset.
25
+ */
26
+ export declare function formatPatchSuggestions(identifier: string, patches: readonly PatchMetadata[]): string;
@@ -118,11 +118,18 @@ function rankSuggestions(identifier, candidates) {
118
118
  * `InvalidArgumentError` body, never the full queue enumeration.
119
119
  */
120
120
  export function formatPatchNotFoundError(identifier, patches) {
121
- const accepted = collectAcceptedIdentifiers(patches);
122
- const suggestions = rankSuggestions(identifier, accepted);
123
121
  const lead = `Patch "${identifier}" not found. Accepted identifiers: ordinal (e.g. 2), filename (e.g. 002-ui-foo.patch), or manifest name (e.g. ui-foo).`;
122
+ return `${lead} ${formatPatchSuggestions(identifier, patches)}`;
123
+ }
124
+ /**
125
+ * The nearest-match half of {@link formatPatchNotFoundError}: the closest
126
+ * accepted identifiers and the queue size, never the full queue. For
127
+ * callers that word their own lead, such as the `--patches` subset.
128
+ */
129
+ export function formatPatchSuggestions(identifier, patches) {
130
+ const suggestions = rankSuggestions(identifier, collectAcceptedIdentifiers(patches));
124
131
  if (suggestions.length > 0) {
125
- return `${lead} Did you mean: ${suggestions.join(', ')}? (${patches.length} patches in queue — run "fireforge patch list" for the full list.)`;
132
+ return `Did you mean: ${suggestions.join(', ')}? (${patches.length} patches in queue — run "fireforge patch list" for the full list.)`;
126
133
  }
127
- return `${lead} No close match found among ${patches.length} patches in the queue. Run "fireforge patch list" to see them.`;
134
+ return `No close match found among ${patches.length} patches in the queue. Run "fireforge patch list" to see them.`;
128
135
  }
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * Separated from `patch-lint.ts` to keep both files within the
11
11
  * project's per-file line budget. The shim itself and the suppressed
12
- * diagnostic code list now live in `typecheck-shim.ts` and are shared
12
+ * diagnostic code list live in `typecheck-shim.ts` and are shared
13
13
  * with the whole-project `fireforge typecheck` command, keeping a
14
14
  * single source of truth for the Firefox-globals coverage.
15
15
  * `patchLint.checkJsStrict` only tightens `strict` / `noImplicitAny`
@@ -10,7 +10,7 @@
10
10
  *
11
11
  * Separated from `patch-lint.ts` to keep both files within the
12
12
  * project's per-file line budget. The shim itself and the suppressed
13
- * diagnostic code list now live in `typecheck-shim.ts` and are shared
13
+ * diagnostic code list live in `typecheck-shim.ts` and are shared
14
14
  * with the whole-project `fireforge typecheck` command, keeping a
15
15
  * single source of truth for the Firefox-globals coverage.
16
16
  * `patchLint.checkJsStrict` only tightens `strict` / `noImplicitAny`
@@ -65,7 +65,7 @@ const RAW_COLOR_IGNORE_MARKER = 'fireforge-ignore: raw-color-value';
65
65
  * comment spanning the context/added boundary suppresses its hex, then
66
66
  * only the patch's added line numbers are scanned. Returns undefined
67
67
  * when any added line number falls outside the on-disk file (patch not
68
- * applied / drifted). The caller then falls back to the legacy
68
+ * applied / drifted). The caller then falls back to the
69
69
  * joined-added-lines scan rather than misattributing lines.
70
70
  */
71
71
  function buildAddedLinesScanSource(rawCss, addedNumbers) {
@@ -100,7 +100,7 @@ function checkRawColorValues(input) {
100
100
  suppressedContent = buildAddedLinesScanSource(rawCss, addedNumbers);
101
101
  }
102
102
  if (suppressedContent === undefined) {
103
- // Legacy scan: full file (no diff), or the joined added lines when
103
+ // Fallback scan: full file (no diff), or the joined added lines when
104
104
  // the diff and on-disk file disagree. Waiver lines are filtered
105
105
  // against the pre-strip text so the CSS comment marker is present.
106
106
  const sourceForSuppression = addedLinesByFile
@@ -6,13 +6,11 @@
6
6
  * `patch-lint.ts` from `config-validate.ts` for a constant would draw an
7
7
  * import edge (and a cycle) between the rule bodies and config parsing.
8
8
  *
9
- * On the vocabulary: the bands were `notice` / `warning` / `error` with the
10
- * middle one described as a "soft limit". That description is false under
11
- * the posture FireForge itself recommends for a release gate,
12
- * `--max-warnings 0`, where a warning is a hard failure, so a file at 751
13
- * lines failed a gate with a message calling 750 soft. The message below
14
- * says what the band is (a warning) and what a zero-warning gate does with
15
- * it, and the thresholds are now tunable, so a project whose file-size
9
+ * On the vocabulary: the bands are `notice` / `warning` / `error`, and the
10
+ * middle one is not a "soft limit". Under the posture FireForge itself
11
+ * recommends for a release gate, `--max-warnings 0`, a warning is a hard
12
+ * failure. The message below therefore says what the band is (a warning)
13
+ * and what a zero-warning gate does with it, and the thresholds are tunable, so a project whose file-size
16
14
  * policy differs has a dial instead of an argument. The notice band is the
17
15
  * design target: advisory, and its message says so rather than borrowing
18
16
  * the warning band's gate sentence.
@@ -7,13 +7,11 @@
7
7
  * `patch-lint.ts` from `config-validate.ts` for a constant would draw an
8
8
  * import edge (and a cycle) between the rule bodies and config parsing.
9
9
  *
10
- * On the vocabulary: the bands were `notice` / `warning` / `error` with the
11
- * middle one described as a "soft limit". That description is false under
12
- * the posture FireForge itself recommends for a release gate,
13
- * `--max-warnings 0`, where a warning is a hard failure, so a file at 751
14
- * lines failed a gate with a message calling 750 soft. The message below
15
- * says what the band is (a warning) and what a zero-warning gate does with
16
- * it, and the thresholds are now tunable, so a project whose file-size
10
+ * On the vocabulary: the bands are `notice` / `warning` / `error`, and the
11
+ * middle one is not a "soft limit". Under the posture FireForge itself
12
+ * recommends for a release gate, `--max-warnings 0`, a warning is a hard
13
+ * failure. The message below therefore says what the band is (a warning)
14
+ * and what a zero-warning gate does with it, and the thresholds are tunable, so a project whose file-size
17
15
  * policy differs has a dial instead of an argument. The notice band is the
18
16
  * design target: advisory, and its message says so rather than borrowing
19
17
  * the warning band's gate sentence.
@@ -3,9 +3,9 @@
3
3
  *
4
4
  * An entry is either a bare check ID, which suppresses that rule at any
5
5
  * size, or a ceiling `{ rule, max, reason }`, which suppresses a size rule
6
- * only while its measurement is at or below `max`. The bare form let a
7
- * waived file grow without bound: nothing re-examined the waiver once it
8
- * was written, so a 950-line file waived at review time was still waived at
6
+ * only while its measurement is at or below `max`. The bare form lets a
7
+ * waived file grow without bound: nothing re-examines the waiver once it
8
+ * is written, so a 950-line file waived at review time is still waived at
9
9
  * 2,000 lines. The ceiling makes growth past the reviewed size fire again,
10
10
  * and lets a gate say when the waiver is no longer needed at all.
11
11
  *
@@ -4,9 +4,9 @@
4
4
  *
5
5
  * An entry is either a bare check ID, which suppresses that rule at any
6
6
  * size, or a ceiling `{ rule, max, reason }`, which suppresses a size rule
7
- * only while its measurement is at or below `max`. The bare form let a
8
- * waived file grow without bound: nothing re-examined the waiver once it
9
- * was written, so a 950-line file waived at review time was still waived at
7
+ * only while its measurement is at or below `max`. The bare form lets a
8
+ * waived file grow without bound: nothing re-examines the waiver once it
9
+ * is written, so a 950-line file waived at review time is still waived at
10
10
  * 2,000 lines. The ceiling makes growth past the reviewed size fire again,
11
11
  * and lets a gate say when the waiver is no longer needed at all.
12
12
  *
@@ -1,14 +1,12 @@
1
1
  /**
2
2
  * Optional Prettier pass over patch-owned `.sys.mjs` modules.
3
3
  *
4
- * Until 0.45.0 no tier of the chain ran Prettier on `.sys.mjs` at all, so
5
- * module formatting drift was caught by nothing and survived to a human
6
- * reviewer. Worse, the obvious manual check was misleading in the same
7
- * direction: a fork's root `.prettierignore` typically excludes `engine/`,
4
+ * Without this pass no tier of the chain runs Prettier on `.sys.mjs`, so
5
+ * module formatting drift survives to a human reviewer. The obvious manual
6
+ * check is misleading in the same direction: a fork's root `.prettierignore` typically excludes `engine/`,
8
7
  * so `prettier --check --config engine/.prettierrc.js <engine files>` run
9
8
  * from the repo root reports "all files use Prettier code style" for files
10
- * that fail the identical check run from inside `engine/`. A root-level
11
- * spot-check falsely refuted a correct reviewer finding on exactly this.
9
+ * that fail the identical check run from inside `engine/`.
12
10
  *
13
11
  * This pass therefore runs prettier with `cwd` set to the engine directory,
14
12
  * so the engine's own `.prettierrc*` and `.prettierignore` resolve the way