@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.
- package/CHANGELOG.md +80 -0
- package/dist/bin/fireforge.js +6 -0
- package/dist/build-info.json +4 -4
- package/dist/src/cli-usage-error.d.ts +46 -0
- package/dist/src/cli-usage-error.js +81 -0
- package/dist/src/cli.d.ts +5 -1
- package/dist/src/cli.js +20 -21
- package/dist/src/commands/bootstrap.js +1 -2
- package/dist/src/commands/build.js +15 -6
- package/dist/src/commands/destructive-decision.d.ts +19 -0
- package/dist/src/commands/destructive-decision.js +31 -0
- package/dist/src/commands/doctor-check-core.d.ts +0 -6
- package/dist/src/commands/doctor-check-core.js +0 -8
- package/dist/src/commands/doctor-orphaned-harness.d.ts +0 -6
- package/dist/src/commands/doctor-orphaned-harness.js +4 -12
- package/dist/src/commands/doctor-post-rebase-audit.js +1 -1
- package/dist/src/commands/doctor.d.ts +2 -0
- package/dist/src/commands/doctor.js +7 -8
- package/dist/src/commands/download.js +35 -24
- package/dist/src/commands/export-all.js +21 -4
- package/dist/src/commands/export-flow.js +15 -45
- package/dist/src/commands/export-placement-conflicts.js +3 -3
- package/dist/src/commands/export-placement-gate.js +2 -8
- package/dist/src/commands/export-placement-policy.js +3 -5
- package/dist/src/commands/export-shared.d.ts +42 -23
- package/dist/src/commands/export-shared.js +4 -19
- package/dist/src/commands/export.js +10 -1
- package/dist/src/commands/furnace/authoring-config.d.ts +13 -0
- package/dist/src/commands/furnace/authoring-config.js +19 -0
- package/dist/src/commands/furnace/chrome-doc-remove.js +1 -1
- package/dist/src/commands/furnace/create-browser-test.js +3 -8
- package/dist/src/commands/furnace/create-dry-run.js +2 -5
- package/dist/src/commands/furnace/create-templates.d.ts +17 -1
- package/dist/src/commands/furnace/create-templates.js +2 -1
- package/dist/src/commands/furnace/create.js +25 -19
- package/dist/src/commands/furnace/deploy.js +11 -2
- package/dist/src/commands/furnace/init.js +2 -1
- package/dist/src/commands/furnace/override.js +28 -19
- package/dist/src/commands/furnace/refresh.js +9 -1
- package/dist/src/commands/furnace/remove-mutations.d.ts +6 -7
- package/dist/src/commands/furnace/remove-mutations.js +0 -1
- package/dist/src/commands/furnace/remove.js +2 -6
- package/dist/src/commands/furnace/rename-browser-test.js +1 -7
- package/dist/src/commands/furnace/rename.js +29 -29
- package/dist/src/commands/furnace/status.js +12 -9
- package/dist/src/commands/furnace/test-file-name.d.ts +28 -0
- package/dist/src/commands/furnace/test-file-name.js +39 -0
- package/dist/src/commands/furnace/validation-output.d.ts +21 -9
- package/dist/src/commands/furnace/validation-output.js +3 -8
- package/dist/src/commands/import.js +2 -9
- package/dist/src/commands/lint-per-patch.d.ts +2 -2
- package/dist/src/commands/lint-per-patch.js +19 -4
- package/dist/src/commands/lint-per-run-checkjs.d.ts +2 -2
- package/dist/src/commands/lint.d.ts +2 -1
- package/dist/src/commands/lint.js +1 -1
- package/dist/src/commands/manifest.js +1 -1
- package/dist/src/commands/package.js +2 -4
- package/dist/src/commands/patch/compact.js +14 -15
- package/dist/src/commands/patch/delete.js +15 -25
- package/dist/src/commands/patch/entry-projection.d.ts +13 -0
- package/dist/src/commands/patch/entry-projection.js +24 -0
- package/dist/src/commands/patch/lint-ignore.d.ts +0 -8
- package/dist/src/commands/patch/lint-ignore.js +19 -29
- package/dist/src/commands/patch/move-files-create.js +24 -10
- package/dist/src/commands/patch/move-files-into.js +38 -34
- package/dist/src/commands/patch/move-files.js +4 -13
- package/dist/src/commands/patch/rename.js +27 -41
- package/dist/src/commands/patch/reorder.js +25 -38
- package/dist/src/commands/patch/split-plan.d.ts +4 -5
- package/dist/src/commands/patch/split-plan.js +4 -8
- package/dist/src/commands/patch/split.js +45 -43
- package/dist/src/commands/patch/staged-dependency.js +16 -22
- package/dist/src/commands/patch/tier.js +15 -25
- package/dist/src/commands/patch-rename-map.d.ts +19 -0
- package/dist/src/commands/patch-rename-map.js +36 -0
- package/dist/src/commands/pending-resolution.d.ts +15 -0
- package/dist/src/commands/pending-resolution.js +25 -0
- package/dist/src/commands/re-export-adjacent.d.ts +24 -2
- package/dist/src/commands/re-export-adjacent.js +32 -16
- package/dist/src/commands/re-export-bulk-scan.js +6 -10
- package/dist/src/commands/re-export-drift.js +4 -4
- package/dist/src/commands/re-export-files.d.ts +2 -3
- package/dist/src/commands/re-export-files.js +38 -29
- package/dist/src/commands/re-export-lint.d.ts +3 -4
- package/dist/src/commands/re-export-lint.js +9 -2
- package/dist/src/commands/re-export-scan.js +5 -2
- package/dist/src/commands/re-export-single.d.ts +22 -3
- package/dist/src/commands/re-export-single.js +19 -11
- package/dist/src/commands/re-export.js +28 -11
- package/dist/src/commands/rebase/abort.js +3 -8
- package/dist/src/commands/rebase/continue.js +12 -12
- package/dist/src/commands/rebase/index.js +2 -2
- package/dist/src/commands/rebase/patch-loop.d.ts +2 -2
- package/dist/src/commands/rebase/patch-loop.js +2 -7
- package/dist/src/commands/resolve.js +18 -10
- package/dist/src/commands/run.d.ts +0 -13
- package/dist/src/commands/run.js +12 -25
- package/dist/src/commands/setup-support.d.ts +16 -4
- package/dist/src/commands/setup-support.js +16 -16
- package/dist/src/commands/setup.js +3 -9
- package/dist/src/commands/source.js +8 -1
- package/dist/src/commands/status-check.js +2 -2
- package/dist/src/commands/status-output.js +2 -2
- package/dist/src/commands/status.js +3 -4
- package/dist/src/commands/test-build-phase.d.ts +2 -2
- package/dist/src/commands/test-build-phase.js +31 -7
- package/dist/src/commands/test-modes.d.ts +2 -2
- package/dist/src/commands/test-modes.js +0 -1
- package/dist/src/commands/test-register.js +5 -3
- package/dist/src/commands/test-run.js +25 -18
- package/dist/src/commands/test-stale-gate.js +4 -4
- package/dist/src/commands/test-verdict.d.ts +16 -2
- package/dist/src/commands/test-verdict.js +18 -3
- package/dist/src/commands/test.js +42 -5
- package/dist/src/commands/token-list.d.ts +26 -0
- package/dist/src/commands/token-list.js +153 -0
- package/dist/src/commands/token.js +17 -1
- package/dist/src/commands/tree.js +7 -2
- package/dist/src/commands/typecheck.js +9 -9
- package/dist/src/commands/wire.js +4 -3
- package/dist/src/core/branding.d.ts +0 -7
- package/dist/src/core/branding.js +3 -2
- package/dist/src/core/browser-wire.js +6 -3
- package/dist/src/core/build-audit-platform.d.ts +29 -0
- package/dist/src/core/build-audit-platform.js +140 -0
- package/dist/src/core/build-audit-registration.js +3 -2
- package/dist/src/core/build-audit-skip.d.ts +57 -0
- package/dist/src/core/build-audit-skip.js +112 -0
- package/dist/src/core/build-audit.d.ts +15 -2
- package/dist/src/core/build-audit.js +110 -10
- package/dist/src/core/build-baseline.d.ts +45 -28
- package/dist/src/core/build-baseline.js +6 -29
- package/dist/src/core/build-jar-escalation.js +3 -2
- package/dist/src/core/build-overwrite-guard.js +1 -1
- package/dist/src/core/build-prepare.d.ts +0 -8
- package/dist/src/core/build-prepare.js +7 -12
- package/dist/src/core/config-paths.d.ts +2 -6
- package/dist/src/core/config-paths.js +4 -2
- package/dist/src/core/config-state.d.ts +0 -6
- package/dist/src/core/config-state.js +1 -19
- package/dist/src/core/config-validate-build-audit.d.ts +8 -0
- package/dist/src/core/config-validate-build-audit.js +80 -0
- package/dist/src/core/config-validate-file-size.js +1 -1
- package/dist/src/core/config-validate-patch-policy.d.ts +2 -2
- package/dist/src/core/config-validate-patch-policy.js +3 -3
- package/dist/src/core/config-validate-test-toolchains.d.ts +2 -2
- package/dist/src/core/config-validate-test-toolchains.js +4 -5
- package/dist/src/core/config-validate.js +14 -3
- package/dist/src/core/config.d.ts +2 -2
- package/dist/src/core/config.js +11 -15
- package/dist/src/core/coverage-extend.js +4 -4
- package/dist/src/core/discard-baseline.js +7 -7
- package/dist/src/core/engine-precondition.d.ts +1 -9
- package/dist/src/core/engine-precondition.js +3 -8
- package/dist/src/core/engine-session-lock.js +13 -17
- package/dist/src/core/export-moved-code-shape.js +2 -2
- package/dist/src/core/file-lock-owner.d.ts +103 -0
- package/dist/src/core/file-lock-owner.js +259 -0
- package/dist/src/core/file-lock.d.ts +2 -7
- package/dist/src/core/file-lock.js +40 -91
- package/dist/src/core/firefox-cache.d.ts +13 -1
- package/dist/src/core/firefox-cache.js +59 -34
- package/dist/src/core/firefox-download.d.ts +15 -0
- package/dist/src/core/firefox-download.js +37 -21
- package/dist/src/core/firefox-extract.d.ts +6 -5
- package/dist/src/core/firefox-extract.js +36 -18
- package/dist/src/core/firefox.d.ts +30 -32
- package/dist/src/core/firefox.js +5 -36
- package/dist/src/core/furnace-apply-dry-run.d.ts +1 -6
- package/dist/src/core/furnace-apply-dry-run.js +1 -7
- package/dist/src/core/furnace-apply-helpers.d.ts +0 -11
- package/dist/src/core/furnace-apply-helpers.js +8 -19
- package/dist/src/core/furnace-apply-overwrite-warn.js +4 -2
- package/dist/src/core/furnace-apply.d.ts +1 -1
- package/dist/src/core/furnace-apply.js +30 -10
- package/dist/src/core/furnace-config.d.ts +1 -1
- package/dist/src/core/furnace-config.js +23 -36
- package/dist/src/core/furnace-constants.js +3 -2
- package/dist/src/core/furnace-dir-entry.d.ts +20 -0
- package/dist/src/core/furnace-dir-entry.js +21 -0
- package/dist/src/core/furnace-operation.d.ts +2 -0
- package/dist/src/core/furnace-operation.js +3 -1
- package/dist/src/core/furnace-refresh.d.ts +18 -6
- package/dist/src/core/furnace-refresh.js +3 -6
- package/dist/src/core/furnace-registration-ast.d.ts +0 -1
- package/dist/src/core/furnace-registration-ast.js +0 -3
- package/dist/src/core/furnace-registration.d.ts +2 -1
- package/dist/src/core/furnace-registration.js +22 -29
- package/dist/src/core/furnace-scanner.js +3 -6
- package/dist/src/core/furnace-state-persist.d.ts +2 -2
- package/dist/src/core/furnace-validate-registration.js +5 -7
- package/dist/src/core/git-base.d.ts +3 -1
- package/dist/src/core/git-base.js +25 -3
- package/dist/src/core/git-diff-new-file.d.ts +40 -0
- package/dist/src/core/git-diff-new-file.js +119 -0
- package/dist/src/core/git-diff.d.ts +9 -9
- package/dist/src/core/git-diff.js +88 -157
- package/dist/src/core/git-file-ops.d.ts +0 -7
- package/dist/src/core/git-file-ops.js +4 -28
- package/dist/src/core/git-readonly-index.d.ts +0 -2
- package/dist/src/core/git-readonly-index.js +0 -4
- package/dist/src/core/git-status.d.ts +7 -0
- package/dist/src/core/git-status.js +21 -9
- package/dist/src/core/git.d.ts +0 -6
- package/dist/src/core/git.js +14 -43
- package/dist/src/core/harness-orphans.js +8 -7
- package/dist/src/core/history-log.d.ts +19 -0
- package/dist/src/core/history-log.js +29 -0
- package/dist/src/core/mach-error-hints.d.ts +0 -5
- package/dist/src/core/mach-error-hints.js +1 -1
- package/dist/src/core/mach-known-noise-filter.d.ts +4 -3
- package/dist/src/core/mach-known-noise-filter.js +7 -9
- package/dist/src/core/mach-mozconfig.js +2 -1
- package/dist/src/core/mach-python.d.ts +2 -0
- package/dist/src/core/mach-python.js +12 -7
- package/dist/src/core/mach-resource-shim.d.ts +13 -1
- package/dist/src/core/mach-resource-shim.js +112 -9
- package/dist/src/core/mach.d.ts +39 -53
- package/dist/src/core/mach.js +13 -81
- package/dist/src/core/marionette-port.d.ts +0 -17
- package/dist/src/core/marionette-port.js +12 -58
- package/dist/src/core/marionette-preflight.js +9 -27
- package/dist/src/core/mochitest-server-port.js +3 -3
- package/dist/src/core/moz-manifest-rules.js +37 -33
- package/dist/src/core/patch-apply-fuzz.js +4 -4
- package/dist/src/core/patch-apply.d.ts +4 -5
- package/dist/src/core/patch-apply.js +6 -12
- package/dist/src/core/patch-export-coverage.d.ts +0 -9
- package/dist/src/core/patch-export-coverage.js +1 -30
- package/dist/src/core/patch-export-update.d.ts +18 -1
- package/dist/src/core/patch-export-update.js +4 -3
- package/dist/src/core/patch-export.d.ts +11 -21
- package/dist/src/core/patch-export.js +44 -66
- package/dist/src/core/patch-files.d.ts +0 -4
- package/dist/src/core/patch-files.js +0 -11
- package/dist/src/core/patch-lint-binary.d.ts +6 -12
- package/dist/src/core/patch-lint-cache.d.ts +17 -1
- package/dist/src/core/patch-lint-cache.js +3 -6
- package/dist/src/core/patch-lint-checkjs.d.ts +33 -16
- package/dist/src/core/patch-lint-checkjs.js +37 -24
- package/dist/src/core/patch-lint-creators.d.ts +2 -6
- package/dist/src/core/patch-lint-cross.d.ts +16 -8
- package/dist/src/core/patch-lint-cross.js +49 -92
- package/dist/src/core/patch-lint-css.js +10 -6
- package/dist/src/core/patch-lint-file-size.js +3 -3
- package/dist/src/core/patch-lint-forward-registration.d.ts +58 -0
- package/dist/src/core/patch-lint-forward-registration.js +210 -0
- package/dist/src/core/patch-lint-jsdoc.js +36 -36
- package/dist/src/core/patch-lint-module-registration.d.ts +2 -8
- package/dist/src/core/patch-lint-prettier.js +1 -1
- package/dist/src/core/patch-lint-queue-types.d.ts +60 -0
- package/dist/src/core/patch-lint-queue-types.js +12 -0
- package/dist/src/core/patch-lint-staged-registration.d.ts +61 -0
- package/dist/src/core/patch-lint-staged-registration.js +90 -0
- package/dist/src/core/patch-lint.d.ts +14 -9
- package/dist/src/core/patch-lint.js +4 -3
- package/dist/src/core/patch-lock.js +3 -3
- package/dist/src/core/patch-manifest-io.js +8 -7
- package/dist/src/core/patch-parse.d.ts +19 -8
- package/dist/src/core/patch-parse.js +25 -0
- package/dist/src/core/patch-policy.js +15 -27
- package/dist/src/core/patch-source-metadata.d.ts +1 -3
- package/dist/src/core/patch-source-metadata.js +0 -4
- package/dist/src/core/patch-transform.d.ts +0 -8
- package/dist/src/core/patch-transform.js +0 -10
- package/dist/src/core/process-cpu-time.js +1 -1
- package/dist/src/core/rebase-session.d.ts +2 -8
- package/dist/src/core/rebase-session.js +5 -17
- package/dist/src/core/register-browser-content.js +3 -2
- package/dist/src/core/register-browser-test.js +2 -1
- package/dist/src/core/register-module.js +2 -1
- package/dist/src/core/register-scaffold.js +5 -4
- package/dist/src/core/register-shared-css.js +2 -1
- package/dist/src/core/run-log-redact.d.ts +38 -0
- package/dist/src/core/run-log-redact.js +69 -0
- package/dist/src/core/run-log.d.ts +13 -2
- package/dist/src/core/run-log.js +67 -18
- package/dist/src/core/signal-critical.js +2 -4
- package/dist/src/core/smoke-patterns.d.ts +0 -1
- package/dist/src/core/smoke-patterns.js +6 -11
- package/dist/src/core/state-file.d.ts +12 -1
- package/dist/src/core/state-file.js +14 -1
- package/dist/src/core/status-classify.js +2 -1
- package/dist/src/core/test-changed-prefs.js +26 -11
- package/dist/src/core/test-harness-crash.js +2 -2
- package/dist/src/core/test-stale-check.d.ts +0 -2
- package/dist/src/core/test-stale-check.js +2 -2
- package/dist/src/core/test-xpcshell-retry.d.ts +26 -7
- package/dist/src/core/test-xpcshell-retry.js +14 -11
- package/dist/src/core/token-category.d.ts +33 -0
- package/dist/src/core/token-category.js +56 -48
- package/dist/src/core/token-dark-mode.js +2 -14
- package/dist/src/core/token-inventory.d.ts +56 -0
- package/dist/src/core/token-inventory.js +109 -0
- package/dist/src/core/token-manager.d.ts +1 -1
- package/dist/src/core/token-manager.js +1 -1
- package/dist/src/core/token-variant.js +21 -22
- package/dist/src/core/toolchain-preflight.js +1 -1
- package/dist/src/core/tree-cow.d.ts +0 -2
- package/dist/src/core/tree-cow.js +2 -2
- package/dist/src/core/tree-store.js +4 -5
- package/dist/src/core/typecheck.js +2 -2
- package/dist/src/core/wire-dom-fragment.js +6 -17
- package/dist/src/core/wire-utils.js +5 -17
- package/dist/src/core/xpcshell-appdir.js +2 -4
- package/dist/src/errors/base.d.ts +53 -1
- package/dist/src/errors/base.js +79 -11
- package/dist/src/errors/build.js +20 -28
- package/dist/src/errors/config.d.ts +0 -14
- package/dist/src/errors/config.js +13 -34
- package/dist/src/errors/download.d.ts +13 -0
- package/dist/src/errors/download.js +53 -24
- package/dist/src/errors/furnace.js +8 -9
- package/dist/src/errors/git.d.ts +8 -8
- package/dist/src/errors/git.js +20 -30
- package/dist/src/errors/patch.js +8 -10
- package/dist/src/errors/rebase.js +7 -7
- package/dist/src/types/commands/options.d.ts +77 -123
- package/dist/src/types/config.d.ts +34 -2
- package/dist/src/types/index.d.ts +0 -1
- package/dist/src/utils/assert.d.ts +3 -15
- package/dist/src/utils/assert.js +1 -18
- package/dist/src/utils/build-info.d.ts +1 -1
- package/dist/src/utils/child-env.d.ts +2 -1
- package/dist/src/utils/elapsed.js +10 -1
- package/dist/src/utils/fs.js +8 -13
- package/dist/src/utils/hash.d.ts +12 -0
- package/dist/src/utils/hash.js +16 -0
- package/dist/src/utils/line-dispatch.d.ts +15 -0
- package/dist/src/utils/line-dispatch.js +48 -0
- package/dist/src/utils/options.d.ts +1 -7
- package/dist/src/utils/options.js +6 -7
- package/dist/src/utils/package-root.js +3 -12
- package/dist/src/utils/platform.d.ts +0 -4
- package/dist/src/utils/process-group.d.ts +2 -1
- package/dist/src/utils/process-group.js +20 -19
- package/dist/src/utils/process.d.ts +40 -26
- package/dist/src/utils/process.js +115 -166
- package/dist/src/utils/ps-duration.js +3 -3
- package/dist/src/utils/sleep.d.ts +26 -0
- package/dist/src/utils/sleep.js +31 -0
- package/dist/src/utils/validation.d.ts +2 -38
- package/dist/src/utils/validation.js +7 -16
- package/docs/configuration.md +89 -1
- package/docs/exit-codes.md +31 -19
- package/docs/lifecycle-invariants.md +18 -12
- package/docs/machine-output.md +31 -8
- package/docs/patch-workflow.md +2 -1
- package/docs/run-logs.md +44 -4
- package/docs/testing.md +8 -1
- package/package.json +8 -7
- package/dist/src/commands/rebase.d.ts +0 -4
- package/dist/src/commands/rebase.js +0 -5
- package/dist/src/core/patch-artifact-normalize.d.ts +0 -21
- package/dist/src/core/patch-artifact-normalize.js +0 -24
- package/dist/src/core/patch-lint-reexports.d.ts +0 -7
- package/dist/src/core/patch-lint-reexports.js +0 -8
- package/dist/src/core/wire-targets.d.ts +0 -7
- package/dist/src/core/wire-targets.js +0 -8
|
@@ -16,12 +16,12 @@ const PATCH_DIRECTORY_LOCK = '.fireforge-patches.lock';
|
|
|
16
16
|
* session lock's progress line so operators read one shape for both locks.
|
|
17
17
|
*/
|
|
18
18
|
function formatWaitProgressLine(waitedMs, timeoutMs, holder) {
|
|
19
|
-
const progress = `${
|
|
19
|
+
const progress = `${Math.round(waitedMs / 1000)}s of up to ${Math.round(timeoutMs / 1000)}s`;
|
|
20
20
|
if (holder === undefined) {
|
|
21
21
|
return `Waiting for the FireForge patch directory lock — ${progress}.`;
|
|
22
22
|
}
|
|
23
23
|
const details = holder.metadata.length > 0 ? ` (${holder.metadata.join(', ')})` : '';
|
|
24
|
-
return `Waiting for the FireForge patch directory lock held by PID ${
|
|
24
|
+
return `Waiting for the FireForge patch directory lock held by PID ${holder.pid}${details} — ${progress}.`;
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
27
|
* Asserts that this process is inside {@link withPatchDirectoryLock} for the
|
|
@@ -47,7 +47,7 @@ export async function assertPatchDirectoryLockHeld(patchesDir, context) {
|
|
|
47
47
|
const status = await readLockStatus(join(patchesDir, PATCH_DIRECTORY_LOCK));
|
|
48
48
|
assert(status.held, () => `patch directory lock is held before ${context}`);
|
|
49
49
|
assert(status.holder === undefined || status.holder.pid === process.pid, () => `patch directory lock is owned by this process before ${context} ` +
|
|
50
|
-
`(held by PID ${
|
|
50
|
+
`(held by PID ${status.holder?.pid}, we are ${process.pid})`);
|
|
51
51
|
}
|
|
52
52
|
/**
|
|
53
53
|
* Runs a patch directory mutation while holding an exclusive filesystem lock.
|
|
@@ -98,17 +98,18 @@ export async function mutatePatchRowsInManifest(patchesDir, filenames, mutator)
|
|
|
98
98
|
if (!(await pathExistsStrict(manifestPath)))
|
|
99
99
|
return null;
|
|
100
100
|
const rawManifest = await readJson(manifestPath);
|
|
101
|
+
// `validatePatchesManifest` has already rejected a manifest that is not an
|
|
102
|
+
// object with an array of object rows (it throws the corruption error the
|
|
103
|
+
// caller expects), so the shape re-checks below are FireForge's own
|
|
104
|
+
// invariants, not input validation: failing them means the validator and
|
|
105
|
+
// this walker disagree about the manifest schema, which is a bug here.
|
|
101
106
|
const beforeManifest = validatePatchesManifest(rawManifest);
|
|
102
|
-
|
|
103
|
-
throw new Error('patches.json must be a JSON object with a patches array');
|
|
104
|
-
}
|
|
107
|
+
assert(isObject(rawManifest) && isArray(rawManifest['patches']), 'validated patches.json is a JSON object with a patches array');
|
|
105
108
|
const filenameSet = new Set(filenames);
|
|
106
109
|
if (filenameSet.size === 0)
|
|
107
110
|
return [];
|
|
108
|
-
const rawPatches = rawManifest['patches'].map((entry) => {
|
|
109
|
-
|
|
110
|
-
throw new Error('patches.json patches entries must be objects');
|
|
111
|
-
}
|
|
111
|
+
const rawPatches = rawManifest['patches'].map((entry, index) => {
|
|
112
|
+
assert(isObject(entry), () => `validated patches.json row ${index} is an object`);
|
|
112
113
|
return { ...entry };
|
|
113
114
|
});
|
|
114
115
|
const changedIndexes = [];
|
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Renders a patch order as the zero-padded prefix used in filenames and in
|
|
3
|
+
* every message that names an order.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* `
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* Eleven call sites across `patch-policy`, `patch-export`, `export-flow`,
|
|
6
|
+
* `export-placement-policy` and `patch move-files` spelled this as
|
|
7
|
+
* `String(order).padStart(3, '0')`, so the padding width lived in eleven
|
|
8
|
+
* places while the filenames it has to agree with are produced in one.
|
|
9
|
+
* @param order - Numeric patch order.
|
|
10
|
+
* @param width - Prefix width; defaults to the on-disk convention.
|
|
11
|
+
* @returns The zero-padded order, e.g. `007`.
|
|
11
12
|
*/
|
|
13
|
+
export declare function formatPatchOrder(order: number, width?: number): string;
|
|
14
|
+
/**
|
|
15
|
+
* Renders an inclusive order range the way `patchPolicy` diagnostics name it.
|
|
16
|
+
* @param range - Inclusive `from`/`to` order bounds.
|
|
17
|
+
* @returns The range label, e.g. `100-199`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function formatOrderRange(range: {
|
|
20
|
+
from: number;
|
|
21
|
+
to: number;
|
|
22
|
+
}): string;
|
|
12
23
|
/**
|
|
13
24
|
* Extracts the order number from a patch filename.
|
|
14
25
|
* Expects format like "001-description.patch"
|
|
@@ -11,6 +11,31 @@ import { assert } from '../utils/assert.js';
|
|
|
11
11
|
* captures that mis-split any path containing ` b/`. New diff-shaped parsing
|
|
12
12
|
* must build on {@link parseDiffSections} rather than re-walking lines.
|
|
13
13
|
*/
|
|
14
|
+
/** Width of the zero-padded numeric prefix on a patch filename ("001-…"). */
|
|
15
|
+
const PATCH_ORDER_WIDTH = 3;
|
|
16
|
+
/**
|
|
17
|
+
* Renders a patch order as the zero-padded prefix used in filenames and in
|
|
18
|
+
* every message that names an order.
|
|
19
|
+
*
|
|
20
|
+
* Eleven call sites across `patch-policy`, `patch-export`, `export-flow`,
|
|
21
|
+
* `export-placement-policy` and `patch move-files` spelled this as
|
|
22
|
+
* `String(order).padStart(3, '0')`, so the padding width lived in eleven
|
|
23
|
+
* places while the filenames it has to agree with are produced in one.
|
|
24
|
+
* @param order - Numeric patch order.
|
|
25
|
+
* @param width - Prefix width; defaults to the on-disk convention.
|
|
26
|
+
* @returns The zero-padded order, e.g. `007`.
|
|
27
|
+
*/
|
|
28
|
+
export function formatPatchOrder(order, width = PATCH_ORDER_WIDTH) {
|
|
29
|
+
return String(order).padStart(width, '0');
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Renders an inclusive order range the way `patchPolicy` diagnostics name it.
|
|
33
|
+
* @param range - Inclusive `from`/`to` order bounds.
|
|
34
|
+
* @returns The range label, e.g. `100-199`.
|
|
35
|
+
*/
|
|
36
|
+
export function formatOrderRange(range) {
|
|
37
|
+
return `${formatPatchOrder(range.from)}-${formatPatchOrder(range.to)}`;
|
|
38
|
+
}
|
|
14
39
|
/**
|
|
15
40
|
* Extracts the order number from a patch filename.
|
|
16
41
|
* Expects format like "001-description.patch"
|
|
@@ -11,6 +11,7 @@ import { InvalidArgumentError } from '../errors/base.js';
|
|
|
11
11
|
import { warn } from '../utils/logger.js';
|
|
12
12
|
import { PATCH_CATEGORIES } from '../utils/validation.js';
|
|
13
13
|
import { rewriteStagedDependencyOwners } from './patch-manifest-io.js';
|
|
14
|
+
import { formatOrderRange, formatPatchOrder } from './patch-parse.js';
|
|
14
15
|
/** Default patch filename contract used when a policy omits `filenamePattern`. */
|
|
15
16
|
const DEFAULT_PATCH_POLICY_FILENAME_PATTERN = '^(?<order>\\d{3})-(?<category>[a-z][a-z0-9-]*)-(?<slug>[a-z0-9-]+)\\.patch$';
|
|
16
17
|
function policy(config) {
|
|
@@ -19,13 +20,6 @@ function policy(config) {
|
|
|
19
20
|
function mutationMode(config) {
|
|
20
21
|
return policy(config)?.mutationMode ?? 'error';
|
|
21
22
|
}
|
|
22
|
-
function issueSeverity(config) {
|
|
23
|
-
return mutationMode(config) === 'warn' ? 'warning' : 'error';
|
|
24
|
-
}
|
|
25
|
-
/** Returns true when the loaded config includes an opt-in patch policy. */
|
|
26
|
-
function hasPatchPolicy(config) {
|
|
27
|
-
return policy(config) !== undefined;
|
|
28
|
-
}
|
|
29
23
|
/** Returns valid categories for prompts and CLI validation under the config. */
|
|
30
24
|
export function getPatchPolicyCategories(config) {
|
|
31
25
|
const cfg = policy(config);
|
|
@@ -42,9 +36,6 @@ export function isCategoryAllowedByConfig(config, category) {
|
|
|
42
36
|
return PATCH_CATEGORIES.includes(category);
|
|
43
37
|
return cfg.ranges.some((range) => range.category === category);
|
|
44
38
|
}
|
|
45
|
-
function rangeLabel(range) {
|
|
46
|
-
return `${String(range.from).padStart(3, '0')}-${String(range.to).padStart(3, '0')}`;
|
|
47
|
-
}
|
|
48
39
|
/**
|
|
49
40
|
* Human-readable label for a category's configured ranges, e.g. `300-399`.
|
|
50
41
|
* Exported for the forward-import hint, which must render the
|
|
@@ -54,7 +45,7 @@ export function categoryRangeLabel(ranges, category) {
|
|
|
54
45
|
const matches = ranges.filter((range) => range.category === category);
|
|
55
46
|
if (matches.length === 0)
|
|
56
47
|
return '(no configured range)';
|
|
57
|
-
return matches.map(
|
|
48
|
+
return matches.map(formatOrderRange).join(', ');
|
|
58
49
|
}
|
|
59
50
|
function reservedRangeForOrder(cfg, order) {
|
|
60
51
|
return cfg.reservedRanges?.find((range) => order >= range.from && order <= range.to) ?? null;
|
|
@@ -73,9 +64,6 @@ export function categoryRangeForOrder(cfg, category, order) {
|
|
|
73
64
|
function anyRangeForOrder(cfg, order) {
|
|
74
65
|
return cfg.ranges.find((range) => order >= range.from && order <= range.to) ?? null;
|
|
75
66
|
}
|
|
76
|
-
function compileFilenamePattern(cfg) {
|
|
77
|
-
return new RegExp(cfg.filenamePattern ?? DEFAULT_PATCH_POLICY_FILENAME_PATTERN);
|
|
78
|
-
}
|
|
79
67
|
function parseFilenameWithPolicy(cfg, patch, severity) {
|
|
80
68
|
if (cfg.filenamePattern === undefined) {
|
|
81
69
|
const defaultMatch = /^(?<order>\d{3})-(?<rest>[a-z0-9-]+)\.patch$/.exec(patch.filename);
|
|
@@ -117,7 +105,7 @@ function parseFilenameWithPolicy(cfg, patch, severity) {
|
|
|
117
105
|
}
|
|
118
106
|
return { order: Number.parseInt(orderRaw, 10), category };
|
|
119
107
|
}
|
|
120
|
-
const pattern =
|
|
108
|
+
const pattern = new RegExp(cfg.filenamePattern ?? DEFAULT_PATCH_POLICY_FILENAME_PATTERN);
|
|
121
109
|
const match = pattern.exec(patch.filename);
|
|
122
110
|
if (!match) {
|
|
123
111
|
return [
|
|
@@ -188,7 +176,7 @@ function evaluatePatchMetadata(cfg, patch, severity) {
|
|
|
188
176
|
code: 'reserved-range',
|
|
189
177
|
filename: patch.filename,
|
|
190
178
|
severity,
|
|
191
|
-
message: `${patch.filename} is in reserved range ${
|
|
179
|
+
message: `${patch.filename} is in reserved range ${formatOrderRange(reserved)}. ` +
|
|
192
180
|
'Reserved ranges require an exact patchPolicy.reservedRanges[].allowed filename exception.',
|
|
193
181
|
});
|
|
194
182
|
return issues;
|
|
@@ -198,7 +186,7 @@ function evaluatePatchMetadata(cfg, patch, severity) {
|
|
|
198
186
|
code: 'reserved-documentation',
|
|
199
187
|
filename: patch.filename,
|
|
200
188
|
severity,
|
|
201
|
-
message: `${patch.filename} is allowlisted for reserved range ${
|
|
189
|
+
message: `${patch.filename} is allowlisted for reserved range ${formatOrderRange(reserved)}, ` +
|
|
202
190
|
'but the allowlist entry must include either "adr" or "documentation".',
|
|
203
191
|
});
|
|
204
192
|
}
|
|
@@ -210,7 +198,7 @@ function evaluatePatchMetadata(cfg, patch, severity) {
|
|
|
210
198
|
code: 'reserved-files',
|
|
211
199
|
filename: patch.filename,
|
|
212
200
|
severity,
|
|
213
|
-
message: `${patch.filename} is allowlisted for reserved range ${
|
|
201
|
+
message: `${patch.filename} is allowlisted for reserved range ${formatOrderRange(reserved)}, ` +
|
|
214
202
|
`but touches file(s) outside its reserved allowlist: ${extraFiles.join(', ')}.`,
|
|
215
203
|
});
|
|
216
204
|
}
|
|
@@ -222,8 +210,8 @@ function evaluatePatchMetadata(cfg, patch, severity) {
|
|
|
222
210
|
const owner = anyRangeForOrder(cfg, patch.order);
|
|
223
211
|
const expected = categoryRangeLabel(cfg.ranges, patch.category);
|
|
224
212
|
const actual = owner !== null
|
|
225
|
-
? `${
|
|
226
|
-
: `${
|
|
213
|
+
? `${formatPatchOrder(patch.order)} is configured for ${owner.category} (${formatOrderRange(owner)})`
|
|
214
|
+
: `${formatPatchOrder(patch.order)} is outside all configured ranges`;
|
|
227
215
|
issues.push({
|
|
228
216
|
code: 'category-range',
|
|
229
217
|
filename: patch.filename,
|
|
@@ -262,10 +250,10 @@ function evaluateGaps(cfg, patches, severity) {
|
|
|
262
250
|
if (missing.length > 0) {
|
|
263
251
|
issues.push({
|
|
264
252
|
code: 'numeric-gap',
|
|
265
|
-
filename: `${range.category}:${
|
|
253
|
+
filename: `${range.category}:${formatOrderRange(range)}`,
|
|
266
254
|
severity,
|
|
267
|
-
message: `${range.category} range ${
|
|
268
|
-
missing.map((order) =>
|
|
255
|
+
message: `${range.category} range ${formatOrderRange(range)} has numeric gap(s): ` +
|
|
256
|
+
missing.map((order) => formatPatchOrder(order)).join(', ') +
|
|
269
257
|
'. patchPolicy.allowGaps is false.',
|
|
270
258
|
});
|
|
271
259
|
}
|
|
@@ -286,9 +274,9 @@ function evaluateOrderCollisions(patches, severity) {
|
|
|
286
274
|
const filenames = matches.map((patch) => patch.filename).sort((a, b) => a.localeCompare(b));
|
|
287
275
|
issues.push({
|
|
288
276
|
code: 'order-collision',
|
|
289
|
-
filename:
|
|
277
|
+
filename: formatPatchOrder(order),
|
|
290
278
|
severity,
|
|
291
|
-
message: `patchPolicy requires unique numeric orders; order ${
|
|
279
|
+
message: `patchPolicy requires unique numeric orders; order ${formatPatchOrder(order)} ` +
|
|
292
280
|
`is used by: ${filenames.join(', ')}.`,
|
|
293
281
|
});
|
|
294
282
|
}
|
|
@@ -299,7 +287,7 @@ export function evaluatePatchPolicy(config, manifest) {
|
|
|
299
287
|
const cfg = policy(config);
|
|
300
288
|
if (!cfg)
|
|
301
289
|
return [];
|
|
302
|
-
const severity =
|
|
290
|
+
const severity = mutationMode(config) === 'warn' ? 'warning' : 'error';
|
|
303
291
|
const issues = manifest.patches.flatMap((patch) => evaluatePatchMetadata(cfg, patch, severity));
|
|
304
292
|
issues.push(...evaluateOrderCollisions(manifest.patches, severity));
|
|
305
293
|
issues.push(...evaluateGaps(cfg, manifest.patches, severity));
|
|
@@ -335,7 +323,7 @@ export function applyRenameMapToManifest(manifest, renameMap) {
|
|
|
335
323
|
}
|
|
336
324
|
/** Enforces patch policy according to the configured mutation mode. */
|
|
337
325
|
export function enforcePatchPolicy(input) {
|
|
338
|
-
if (
|
|
326
|
+
if (policy(input.config) === undefined)
|
|
339
327
|
return;
|
|
340
328
|
const issues = evaluatePatchPolicy(input.config, input.manifest);
|
|
341
329
|
if (issues.length === 0)
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import type { PatchMetadata } from '../types/commands/index.js';
|
|
2
|
-
import type { FirefoxConfig
|
|
2
|
+
import type { FirefoxConfig } from '../types/config.js';
|
|
3
3
|
/** Metadata fields stamped on new or refreshed patch entries. */
|
|
4
4
|
export declare function buildPatchSourceMetadata(firefox: Pick<FirefoxConfig, 'product' | 'version'>): Pick<PatchMetadata, 'sourceEsrVersion' | 'sourceProduct' | 'sourceVersion'>;
|
|
5
5
|
/** Backward-compatible source version reader for legacy manifests. */
|
|
6
6
|
export declare function getPatchSourceVersion(patch: Pick<PatchMetadata, 'sourceEsrVersion' | 'sourceVersion'>): string;
|
|
7
|
-
/** Backward-compatible source product reader for legacy manifests. */
|
|
8
|
-
export declare function getPatchSourceProduct(patch: Pick<PatchMetadata, 'sourceProduct'>): FirefoxProduct | undefined;
|
|
@@ -10,7 +10,3 @@ export function buildPatchSourceMetadata(firefox) {
|
|
|
10
10
|
export function getPatchSourceVersion(patch) {
|
|
11
11
|
return patch.sourceVersion ?? patch.sourceEsrVersion;
|
|
12
12
|
}
|
|
13
|
-
/** Backward-compatible source product reader for legacy manifests. */
|
|
14
|
-
export function getPatchSourceProduct(patch) {
|
|
15
|
-
return patch.sourceProduct;
|
|
16
|
-
}
|
|
@@ -48,14 +48,6 @@ export declare function buildNewFileTextProjection(diff: string): Map<string, st
|
|
|
48
48
|
* @returns The file content that the patch would create
|
|
49
49
|
*/
|
|
50
50
|
export declare function extractNewFileContent(patchPath: string, targetFile?: string): Promise<string>;
|
|
51
|
-
/**
|
|
52
|
-
* Applies a patch's changes to content.
|
|
53
|
-
* @param content - Original content (null for new files)
|
|
54
|
-
* @param patchPath - Path to the patch file
|
|
55
|
-
* @param targetFile - The file path within the patch
|
|
56
|
-
* @returns Modified content
|
|
57
|
-
*/
|
|
58
|
-
export declare function applyPatchToContent(content: string | null, patchPath: string, targetFile: string): Promise<string>;
|
|
59
51
|
/**
|
|
60
52
|
* Applies an already-read patch body to content. Batched callers that hold
|
|
61
53
|
* many (patch, file) pairs use this to read each patch body once instead of
|
|
@@ -101,16 +101,6 @@ export async function extractNewFileContent(patchPath, targetFile) {
|
|
|
101
101
|
const content = await readText(patchPath);
|
|
102
102
|
return extractNewFileContentFromDiff(content, targetFile);
|
|
103
103
|
}
|
|
104
|
-
/**
|
|
105
|
-
* Applies a patch's changes to content.
|
|
106
|
-
* @param content - Original content (null for new files)
|
|
107
|
-
* @param patchPath - Path to the patch file
|
|
108
|
-
* @param targetFile - The file path within the patch
|
|
109
|
-
* @returns Modified content
|
|
110
|
-
*/
|
|
111
|
-
export async function applyPatchToContent(content, patchPath, targetFile) {
|
|
112
|
-
return applyPatchTextToContent(content, await readText(patchPath), targetFile);
|
|
113
|
-
}
|
|
114
104
|
/**
|
|
115
105
|
* Applies an already-read patch body to content. Batched callers that hold
|
|
116
106
|
* many (patch, file) pairs use this to read each patch body once instead of
|
|
@@ -34,7 +34,7 @@ export async function readProcessCpuSeconds(pid) {
|
|
|
34
34
|
'-NoProfile',
|
|
35
35
|
'-NonInteractive',
|
|
36
36
|
'-Command',
|
|
37
|
-
`(Get-Process -Id ${
|
|
37
|
+
`(Get-Process -Id ${pid} -ErrorAction SilentlyContinue).CPU`,
|
|
38
38
|
]);
|
|
39
39
|
const seconds = Number.parseFloat(result.stdout.trim());
|
|
40
40
|
return Number.isFinite(seconds) ? seconds : undefined;
|
|
@@ -52,6 +52,8 @@ export interface RebaseSession {
|
|
|
52
52
|
/** Index of the next patch to process (resume point). */
|
|
53
53
|
currentIndex: number;
|
|
54
54
|
}
|
|
55
|
+
/** Absolute path of the session file, for messages that must name it. */
|
|
56
|
+
export declare function getRebaseSessionPath(projectRoot: string): string;
|
|
55
57
|
/**
|
|
56
58
|
* Outcome of reading the session file, following the {@link LockOwner}
|
|
57
59
|
* discriminated-union form rather than collapsing three states into `null`.
|
|
@@ -74,8 +76,6 @@ export type RebaseSessionRead = {
|
|
|
74
76
|
valid: false;
|
|
75
77
|
reason: string;
|
|
76
78
|
};
|
|
77
|
-
/** Absolute path of the session file, for messages that must name it. */
|
|
78
|
-
export declare function getRebaseSessionPath(projectRoot: string): string;
|
|
79
79
|
/**
|
|
80
80
|
* Reads the session file, reporting absent, valid, and corrupt as three
|
|
81
81
|
* distinct outcomes. Never throws for a malformed file: `readJson` calls
|
|
@@ -90,12 +90,6 @@ export declare function getRebaseSessionPath(projectRoot: string): string;
|
|
|
90
90
|
* markers.
|
|
91
91
|
*/
|
|
92
92
|
export declare function readRebaseSession(projectRoot: string): Promise<RebaseSessionRead>;
|
|
93
|
-
/**
|
|
94
|
-
* Loads an existing rebase session, or returns `null` when none exists or the
|
|
95
|
-
* file on disk is unusable. Callers that must tell those two apart — every
|
|
96
|
-
* command that reports to an operator — should use {@link readRebaseSession}.
|
|
97
|
-
*/
|
|
98
|
-
export declare function tryReadRebaseSession(projectRoot: string): Promise<RebaseSession | null>;
|
|
99
93
|
/**
|
|
100
94
|
* Persists a rebase session atomically.
|
|
101
95
|
*/
|
|
@@ -13,7 +13,8 @@ import { getProjectPaths } from './config-paths.js';
|
|
|
13
13
|
import { createSiblingLockPath, withFileLock } from './file-lock.js';
|
|
14
14
|
// ── Helpers ──
|
|
15
15
|
const SESSION_FILENAME = 'rebase-session.json';
|
|
16
|
-
|
|
16
|
+
/** Absolute path of the session file, for messages that must name it. */
|
|
17
|
+
export function getRebaseSessionPath(projectRoot) {
|
|
17
18
|
return join(getProjectPaths(projectRoot).fireforgeDir, SESSION_FILENAME);
|
|
18
19
|
}
|
|
19
20
|
const REBASE_PATCH_STATUSES = [
|
|
@@ -116,10 +117,6 @@ function isValidSession(data) {
|
|
|
116
117
|
return false;
|
|
117
118
|
return currentIndex >= 0 && currentIndex <= patches.length;
|
|
118
119
|
}
|
|
119
|
-
/** Absolute path of the session file, for messages that must name it. */
|
|
120
|
-
export function getRebaseSessionPath(projectRoot) {
|
|
121
|
-
return sessionPath(projectRoot);
|
|
122
|
-
}
|
|
123
120
|
/**
|
|
124
121
|
* Reads the session file, reporting absent, valid, and corrupt as three
|
|
125
122
|
* distinct outcomes. Never throws for a malformed file: `readJson` calls
|
|
@@ -134,7 +131,7 @@ export function getRebaseSessionPath(projectRoot) {
|
|
|
134
131
|
* markers.
|
|
135
132
|
*/
|
|
136
133
|
export async function readRebaseSession(projectRoot) {
|
|
137
|
-
const path =
|
|
134
|
+
const path = getRebaseSessionPath(projectRoot);
|
|
138
135
|
let data;
|
|
139
136
|
try {
|
|
140
137
|
data = await readJson(path);
|
|
@@ -159,20 +156,11 @@ export async function readRebaseSession(projectRoot) {
|
|
|
159
156
|
session: { ...data, patches: data.patches.map(normalizeEntry) },
|
|
160
157
|
};
|
|
161
158
|
}
|
|
162
|
-
/**
|
|
163
|
-
* Loads an existing rebase session, or returns `null` when none exists or the
|
|
164
|
-
* file on disk is unusable. Callers that must tell those two apart — every
|
|
165
|
-
* command that reports to an operator — should use {@link readRebaseSession}.
|
|
166
|
-
*/
|
|
167
|
-
export async function tryReadRebaseSession(projectRoot) {
|
|
168
|
-
const result = await readRebaseSession(projectRoot);
|
|
169
|
-
return result.present && result.valid ? result.session : null;
|
|
170
|
-
}
|
|
171
159
|
/**
|
|
172
160
|
* Persists a rebase session atomically.
|
|
173
161
|
*/
|
|
174
162
|
export async function saveRebaseSession(projectRoot, session) {
|
|
175
|
-
const path =
|
|
163
|
+
const path = getRebaseSessionPath(projectRoot);
|
|
176
164
|
await withFileLock(createSiblingLockPath(path, '.rebase-session.lock'), async () => {
|
|
177
165
|
await writeJson(path, session);
|
|
178
166
|
});
|
|
@@ -181,7 +169,7 @@ export async function saveRebaseSession(projectRoot, session) {
|
|
|
181
169
|
* Removes the rebase session file.
|
|
182
170
|
*/
|
|
183
171
|
export async function clearRebaseSession(projectRoot) {
|
|
184
|
-
const path =
|
|
172
|
+
const path = getRebaseSessionPath(projectRoot);
|
|
185
173
|
if (await pathExists(path)) {
|
|
186
174
|
await removeFile(path);
|
|
187
175
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
import { GeneralError } from '../errors/base.js';
|
|
7
7
|
import { pathExists, readText, writeText } from '../utils/fs.js';
|
|
8
|
+
import { normalizePathSlashes } from '../utils/paths.js';
|
|
8
9
|
import { insertJarMnEntry } from './moz-manifest-helpers.js';
|
|
9
10
|
/**
|
|
10
11
|
* Tokenizer-based implementation for browser content registration.
|
|
@@ -30,8 +31,8 @@ export async function registerBrowserContent(engineDir, fileName, after, sourceP
|
|
|
30
31
|
if (!(await pathExists(manifestPath))) {
|
|
31
32
|
throw new GeneralError(`Manifest not found: ${manifest}`);
|
|
32
33
|
}
|
|
33
|
-
const source = (sourcePath ?? `content/${fileName}`)
|
|
34
|
-
const entry = ` content/browser/${fileName} (${source})
|
|
34
|
+
const source = normalizePathSlashes(sourcePath ?? `content/${fileName}`);
|
|
35
|
+
const entry = normalizePathSlashes(` content/browser/${fileName} (${source})`);
|
|
35
36
|
const content = await readText(manifestPath);
|
|
36
37
|
// Idempotency check
|
|
37
38
|
if (content.includes(`content/browser/${fileName}`)) {
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
import { GeneralError } from '../errors/base.js';
|
|
7
7
|
import { pathExists, readText, writeText } from '../utils/fs.js';
|
|
8
|
+
import { normalizePathSlashes } from '../utils/paths.js';
|
|
8
9
|
import { insertMozBuildListEntry } from './moz-manifest-helpers.js';
|
|
9
10
|
/**
|
|
10
11
|
* Tokenizer-based implementation for test manifest registration.
|
|
@@ -28,7 +29,7 @@ export async function registerTestManifest(engineDir, testDir, dryRun = false) {
|
|
|
28
29
|
if (!(await pathExists(manifestPath))) {
|
|
29
30
|
throw new GeneralError(`Manifest not found: ${manifest}`);
|
|
30
31
|
}
|
|
31
|
-
const entry = ` "content/test/${testDir}/browser.toml"
|
|
32
|
+
const entry = normalizePathSlashes(` "content/test/${testDir}/browser.toml",`);
|
|
32
33
|
const content = await readText(manifestPath);
|
|
33
34
|
// Idempotency check
|
|
34
35
|
if (content.includes(`content/test/${testDir}/browser.toml`)) {
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
import { GeneralError } from '../errors/base.js';
|
|
7
7
|
import { pathExists, readText, writeText } from '../utils/fs.js';
|
|
8
|
+
import { normalizePathSlashes } from '../utils/paths.js';
|
|
8
9
|
import { insertMozBuildListEntry } from './moz-manifest-helpers.js';
|
|
9
10
|
import { scaffoldModuleMozBuild } from './register-scaffold.js';
|
|
10
11
|
/**
|
|
@@ -41,7 +42,7 @@ export async function registerFireForgeModule(engineDir, fileName, moduleDir, dr
|
|
|
41
42
|
}
|
|
42
43
|
throw new GeneralError(`Manifest not found: ${manifest}. Pass --create-manifest to scaffold it and wire the parent DIRS entry.`);
|
|
43
44
|
}
|
|
44
|
-
const entry = ` "${fileName}"
|
|
45
|
+
const entry = normalizePathSlashes(` "${fileName}",`);
|
|
45
46
|
const content = await readText(manifestPath);
|
|
46
47
|
// Idempotency check
|
|
47
48
|
if (content.includes(`"${fileName}"`)) {
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
import { dirname, join } from 'node:path';
|
|
20
20
|
import { GeneralError } from '../errors/base.js';
|
|
21
21
|
import { pathExists, readText, writeText } from '../utils/fs.js';
|
|
22
|
+
import { normalizePathSlashes } from '../utils/paths.js';
|
|
22
23
|
import { getLicenseHeader } from './license-headers.js';
|
|
23
24
|
import { findAlphabeticalMozBuildPosition } from './moz-manifest-helpers.js';
|
|
24
25
|
import { tokenizeMozBuildList } from './moz-manifest-tokenizers.js';
|
|
@@ -64,7 +65,7 @@ export function upsertMozBuildListEntry(content, directive, value) {
|
|
|
64
65
|
export async function ensureParentDirsWiring(engineDir, childRelDir, dryRun) {
|
|
65
66
|
const actions = [];
|
|
66
67
|
let childName = childRelDir.split('/').at(-1) ?? childRelDir;
|
|
67
|
-
let parentRel = dirname(childRelDir)
|
|
68
|
+
let parentRel = normalizePathSlashes(dirname(childRelDir));
|
|
68
69
|
for (;;) {
|
|
69
70
|
if (parentRel === '.' || parentRel === '' || parentRel === '/') {
|
|
70
71
|
throw new GeneralError(`No moz.build found in any parent of ${childRelDir} up to the engine root; ` +
|
|
@@ -90,7 +91,7 @@ export async function ensureParentDirsWiring(engineDir, childRelDir, dryRun) {
|
|
|
90
91
|
await writeText(parentManifestPath, scaffold);
|
|
91
92
|
actions.push({ manifest: parentManifestRel, change: `created with DIRS += ["${childName}"]` });
|
|
92
93
|
childName = parentRel.split('/').at(-1) ?? parentRel;
|
|
93
|
-
parentRel = dirname(parentRel)
|
|
94
|
+
parentRel = normalizePathSlashes(dirname(parentRel));
|
|
94
95
|
}
|
|
95
96
|
}
|
|
96
97
|
/**
|
|
@@ -122,7 +123,7 @@ export async function scaffoldModuleMozBuild(engineDir, moduleDir, fileName, dry
|
|
|
122
123
|
* entry already exists).
|
|
123
124
|
*/
|
|
124
125
|
export async function ensureXpcshellManifestWiring(engineDir, manifestRelPath, dryRun) {
|
|
125
|
-
let dirRel = dirname(manifestRelPath)
|
|
126
|
+
let dirRel = normalizePathSlashes(dirname(manifestRelPath));
|
|
126
127
|
for (;;) {
|
|
127
128
|
if (dirRel === '.' || dirRel === '' || dirRel === '/') {
|
|
128
129
|
throw new GeneralError(`No moz.build found in any parent of ${manifestRelPath} up to the engine root; ` +
|
|
@@ -140,6 +141,6 @@ export async function ensureXpcshellManifestWiring(engineDir, manifestRelPath, d
|
|
|
140
141
|
await writeText(mozBuildPath, updated);
|
|
141
142
|
return [{ manifest: mozBuildRel, change: `XPCSHELL_TESTS_MANIFESTS += ["${relEntry}"]` }];
|
|
142
143
|
}
|
|
143
|
-
dirRel = dirname(dirRel)
|
|
144
|
+
dirRel = normalizePathSlashes(dirname(dirRel));
|
|
144
145
|
}
|
|
145
146
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { basename, join } from 'node:path';
|
|
6
6
|
import { GeneralError } from '../errors/base.js';
|
|
7
7
|
import { pathExists, readText, writeText } from '../utils/fs.js';
|
|
8
|
+
import { normalizePathSlashes } from '../utils/paths.js';
|
|
8
9
|
import { insertJarMnEntry } from './moz-manifest-helpers.js';
|
|
9
10
|
/**
|
|
10
11
|
* Tokenizer-based implementation for shared CSS registration.
|
|
@@ -65,7 +66,7 @@ export function buildEntry(name, sourceColumn) {
|
|
|
65
66
|
const minColumn = target.length + MIN_SOURCE_GAP;
|
|
66
67
|
const column = sourceColumn !== undefined && sourceColumn >= minColumn ? sourceColumn : minColumn;
|
|
67
68
|
const padding = ' '.repeat(column - target.length);
|
|
68
|
-
return `${target}${padding}(../shared/${name}.css)
|
|
69
|
+
return normalizePathSlashes(`${target}${padding}(../shared/${name}.css)`);
|
|
69
70
|
}
|
|
70
71
|
/**
|
|
71
72
|
* Registers a CSS file in browser/themes/shared/jar.inc.mn.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conservative secret redaction for the run-log FILE tee.
|
|
3
|
+
*
|
|
4
|
+
* A run log is a complete copy of mach's output, and mach echoes its
|
|
5
|
+
* environment in places (configure summaries, `mach env`, harness
|
|
6
|
+
* diagnostics that print the command line). The terminal already showed
|
|
7
|
+
* every byte to the operator who ran it; the file, by contrast, is retained
|
|
8
|
+
* twenty deep and gets attached to bug reports. So the file gets a narrow
|
|
9
|
+
* masking pass and the terminal does not.
|
|
10
|
+
*
|
|
11
|
+
* Deliberately minimal and pattern-based — see {@link redactRunLogText} for
|
|
12
|
+
* the exact contract. It is a seatbelt against the common accidental leak,
|
|
13
|
+
* not a guarantee that a log is free of secrets.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Masks the obvious secret shapes in run-log text, line by line.
|
|
17
|
+
*
|
|
18
|
+
* Redacted:
|
|
19
|
+
* - the value of an env-style assignment `KEY=value` whose KEY matches
|
|
20
|
+
* `/(TOKEN|SECRET|PASSWORD|PASSWD|API_KEY|AUTH)/i` — the value up to the
|
|
21
|
+
* next whitespace, or a whole quoted string — becomes `KEY=<redacted>`;
|
|
22
|
+
* the `--flag=value` and `-DNAME=value` spellings of the same shape are
|
|
23
|
+
* covered (`--password=x` → `--password=<redacted>`);
|
|
24
|
+
* - the credential of an `Authorization:` header (`Bearer`, `Basic` and
|
|
25
|
+
* `Token` schemes keep the scheme word; anything else masks the whole
|
|
26
|
+
* value).
|
|
27
|
+
*
|
|
28
|
+
* NOT redacted: secrets that appear without one of those key names or
|
|
29
|
+
* headers (a bare token in a URL query string, a space-separated
|
|
30
|
+
* `--password foo` CLI argument, JSON `"token": "..."` fields, cookies).
|
|
31
|
+
* Line structure and every other byte pass through unchanged; `\n`, `\r\n`
|
|
32
|
+
* and lone `\r` all delimit lines, so carriage-return-repainted progress
|
|
33
|
+
* output is masked per repaint.
|
|
34
|
+
*
|
|
35
|
+
* @param text - One or more complete lines
|
|
36
|
+
* @returns The same text with masked values
|
|
37
|
+
*/
|
|
38
|
+
export declare function redactRunLogText(text: string): string;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Conservative secret redaction for the run-log FILE tee.
|
|
4
|
+
*
|
|
5
|
+
* A run log is a complete copy of mach's output, and mach echoes its
|
|
6
|
+
* environment in places (configure summaries, `mach env`, harness
|
|
7
|
+
* diagnostics that print the command line). The terminal already showed
|
|
8
|
+
* every byte to the operator who ran it; the file, by contrast, is retained
|
|
9
|
+
* twenty deep and gets attached to bug reports. So the file gets a narrow
|
|
10
|
+
* masking pass and the terminal does not.
|
|
11
|
+
*
|
|
12
|
+
* Deliberately minimal and pattern-based — see {@link redactRunLogText} for
|
|
13
|
+
* the exact contract. It is a seatbelt against the common accidental leak,
|
|
14
|
+
* not a guarantee that a log is free of secrets.
|
|
15
|
+
*/
|
|
16
|
+
/** Env-style keys whose assigned value is masked. */
|
|
17
|
+
const SECRET_KEY_PATTERN = /(TOKEN|SECRET|PASSWORD|PASSWD|API_KEY|AUTH)/i;
|
|
18
|
+
/**
|
|
19
|
+
* `KEY=value` where KEY is an identifier and the value runs to the next
|
|
20
|
+
* whitespace, or is a single/double-quoted string. The leading group keeps
|
|
21
|
+
* the assignment anchored at a token boundary so `x.y=z` is not split
|
|
22
|
+
* mid-token. A hyphen IS a boundary: mach and configure echo their argv, so
|
|
23
|
+
* `--token=…`, `--password=…` and `-DAUTH_TOKEN=…` are exactly the forms a
|
|
24
|
+
* retained log would otherwise carry verbatim (the lead keeps the dash; the
|
|
25
|
+
* key is what follows it). Empty values are left alone (nothing to hide).
|
|
26
|
+
*/
|
|
27
|
+
const ENV_ASSIGNMENT_PATTERN = /(^|[\s"'([{,;-])([A-Za-z_][A-Za-z0-9_]*)=("(?:[^"\\]|\\.)*"|'[^']*'|[^\s"',;)\]}]+)/g;
|
|
28
|
+
/** `Authorization: <scheme> <credential>` (scheme optional) in HTTP dumps. */
|
|
29
|
+
const AUTHORIZATION_HEADER_PATTERN = /(authorization\s*:\s*)(?:(bearer|basic|token)\s+)?(\S+)/gi;
|
|
30
|
+
/** Marker written in place of a masked value. */
|
|
31
|
+
const REDACTED = '<redacted>';
|
|
32
|
+
function redactLine(line) {
|
|
33
|
+
return line
|
|
34
|
+
.replace(ENV_ASSIGNMENT_PATTERN, (whole, lead, key) => SECRET_KEY_PATTERN.test(key) ? `${lead}${key}=${REDACTED}` : whole)
|
|
35
|
+
.replace(AUTHORIZATION_HEADER_PATTERN, (_whole, lead, scheme) => scheme === undefined ? `${lead}${REDACTED}` : `${lead}${scheme} ${REDACTED}`);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Masks the obvious secret shapes in run-log text, line by line.
|
|
39
|
+
*
|
|
40
|
+
* Redacted:
|
|
41
|
+
* - the value of an env-style assignment `KEY=value` whose KEY matches
|
|
42
|
+
* `/(TOKEN|SECRET|PASSWORD|PASSWD|API_KEY|AUTH)/i` — the value up to the
|
|
43
|
+
* next whitespace, or a whole quoted string — becomes `KEY=<redacted>`;
|
|
44
|
+
* the `--flag=value` and `-DNAME=value` spellings of the same shape are
|
|
45
|
+
* covered (`--password=x` → `--password=<redacted>`);
|
|
46
|
+
* - the credential of an `Authorization:` header (`Bearer`, `Basic` and
|
|
47
|
+
* `Token` schemes keep the scheme word; anything else masks the whole
|
|
48
|
+
* value).
|
|
49
|
+
*
|
|
50
|
+
* NOT redacted: secrets that appear without one of those key names or
|
|
51
|
+
* headers (a bare token in a URL query string, a space-separated
|
|
52
|
+
* `--password foo` CLI argument, JSON `"token": "..."` fields, cookies).
|
|
53
|
+
* Line structure and every other byte pass through unchanged; `\n`, `\r\n`
|
|
54
|
+
* and lone `\r` all delimit lines, so carriage-return-repainted progress
|
|
55
|
+
* output is masked per repaint.
|
|
56
|
+
*
|
|
57
|
+
* @param text - One or more complete lines
|
|
58
|
+
* @returns The same text with masked values
|
|
59
|
+
*/
|
|
60
|
+
export function redactRunLogText(text) {
|
|
61
|
+
if (text.length === 0)
|
|
62
|
+
return text;
|
|
63
|
+
// Split with a capturing group so the terminators survive the round trip;
|
|
64
|
+
// odd indices are the terminators themselves.
|
|
65
|
+
return text
|
|
66
|
+
.split(/(\r\n|\n|\r)/)
|
|
67
|
+
.map((part, index) => (index % 2 === 0 ? redactLine(part) : part))
|
|
68
|
+
.join('');
|
|
69
|
+
}
|