@hominis/fireforge 0.49.1 → 0.50.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 +41 -0
- package/README.md +1 -1
- package/dist/bin/fireforge.js +6 -0
- package/dist/build-info.json +4 -4
- package/dist/src/commands/build-prelock.d.ts +20 -0
- package/dist/src/commands/build-prelock.js +50 -0
- package/dist/src/commands/build.d.ts +3 -1
- package/dist/src/commands/build.js +13 -6
- package/dist/src/commands/check-report.d.ts +28 -0
- package/dist/src/commands/check-report.js +78 -0
- package/dist/src/commands/check-run.d.ts +28 -0
- package/dist/src/commands/check-run.js +104 -0
- package/dist/src/commands/check-steps.d.ts +67 -0
- package/dist/src/commands/check-steps.js +175 -0
- package/dist/src/commands/check.d.ts +32 -0
- package/dist/src/commands/check.js +160 -0
- package/dist/src/commands/config.js +5 -6
- package/dist/src/commands/doctor-engine-eslint.js +6 -1
- package/dist/src/commands/doctor-source-pin.js +4 -1
- package/dist/src/commands/export-adopt-check.d.ts +38 -0
- package/dist/src/commands/export-adopt-check.js +98 -0
- package/dist/src/commands/export-flow.js +29 -14
- package/dist/src/commands/export-placement-gate.d.ts +6 -2
- package/dist/src/commands/export-shared.d.ts +2 -1
- package/dist/src/commands/export-staged-registration.d.ts +46 -0
- package/dist/src/commands/export-staged-registration.js +91 -0
- package/dist/src/commands/forward-import-remedy.d.ts +32 -0
- package/dist/src/commands/forward-import-remedy.js +63 -0
- package/dist/src/commands/furnace/apply.js +3 -3
- package/dist/src/commands/furnace/chrome-doc-remove.d.ts +2 -1
- package/dist/src/commands/furnace/chrome-doc-remove.js +2 -2
- package/dist/src/commands/furnace/chrome-doc.d.ts +2 -1
- package/dist/src/commands/furnace/chrome-doc.js +2 -2
- package/dist/src/commands/furnace/create-templates.d.ts +8 -3
- package/dist/src/commands/furnace/create-templates.js +8 -23
- package/dist/src/commands/furnace/create.js +2 -2
- package/dist/src/commands/furnace/deploy-named.d.ts +48 -0
- package/dist/src/commands/furnace/deploy-named.js +107 -0
- package/dist/src/commands/furnace/deploy.d.ts +3 -2
- package/dist/src/commands/furnace/deploy.js +50 -86
- package/dist/src/commands/furnace/diff.js +26 -3
- package/dist/src/commands/furnace/index.js +66 -41
- package/dist/src/commands/furnace/init.js +13 -6
- package/dist/src/commands/furnace/override.js +4 -2
- package/dist/src/commands/furnace/preview.js +4 -3
- package/dist/src/commands/furnace/refresh.js +2 -2
- package/dist/src/commands/furnace/remove.js +4 -3
- package/dist/src/commands/furnace/rename-dry-run.d.ts +29 -0
- package/dist/src/commands/furnace/rename-dry-run.js +127 -0
- package/dist/src/commands/furnace/rename-helpers.d.ts +7 -0
- package/dist/src/commands/furnace/rename-helpers.js +15 -1
- package/dist/src/commands/furnace/rename-registrations.d.ts +41 -0
- package/dist/src/commands/furnace/rename-registrations.js +93 -0
- package/dist/src/commands/furnace/rename.d.ts +2 -1
- package/dist/src/commands/furnace/rename.js +28 -66
- package/dist/src/commands/furnace/scan.d.ts +9 -4
- package/dist/src/commands/furnace/scan.js +11 -9
- package/dist/src/commands/furnace/sync.js +3 -2
- package/dist/src/commands/furnace/validate-report.d.ts +45 -0
- package/dist/src/commands/furnace/validate-report.js +91 -0
- package/dist/src/commands/furnace/validate.d.ts +1 -1
- package/dist/src/commands/furnace/validate.js +127 -114
- package/dist/src/commands/furnace/validation-output.js +14 -3
- package/dist/src/commands/lint-per-patch.js +31 -60
- package/dist/src/commands/lint-report.d.ts +8 -4
- package/dist/src/commands/lint-report.js +15 -5
- package/dist/src/commands/lint.js +15 -34
- package/dist/src/commands/manifest.js +2 -0
- package/dist/src/commands/patch/delete-gap-notice.js +3 -1
- package/dist/src/commands/patch/delete.d.ts +1 -1
- package/dist/src/commands/patch/delete.js +13 -98
- package/dist/src/commands/patch/index.d.ts +1 -1
- package/dist/src/commands/patch/index.js +4 -2
- package/dist/src/commands/patch/lint-ignore.d.ts +5 -1
- package/dist/src/commands/patch/lint-ignore.js +65 -28
- package/dist/src/commands/patch/move-files-create.js +26 -17
- package/dist/src/commands/patch/move-files-into.js +5 -3
- package/dist/src/commands/patch/move-files.js +3 -1
- package/dist/src/commands/patch/removal-dependents.d.ts +63 -0
- package/dist/src/commands/patch/removal-dependents.js +133 -0
- package/dist/src/commands/patch/rename.js +7 -0
- package/dist/src/commands/patch/retire.d.ts +34 -0
- package/dist/src/commands/patch/retire.js +128 -0
- package/dist/src/commands/patch/split-plan.d.ts +7 -7
- package/dist/src/commands/patch/split-plan.js +45 -28
- package/dist/src/commands/patch/split-staged.d.ts +108 -0
- package/dist/src/commands/patch/split-staged.js +202 -0
- package/dist/src/commands/patch/split.d.ts +5 -0
- package/dist/src/commands/patch/split.js +51 -15
- package/dist/src/commands/patch/staged-dependency-batch.d.ts +48 -0
- package/dist/src/commands/patch/staged-dependency-batch.js +176 -0
- package/dist/src/commands/patch/staged-dependency-entries.d.ts +36 -0
- package/dist/src/commands/patch/staged-dependency-entries.js +52 -0
- package/dist/src/commands/patch/staged-dependency-from.d.ts +14 -0
- package/dist/src/commands/patch/staged-dependency-from.js +108 -0
- package/dist/src/commands/patch/staged-dependency.js +14 -41
- package/dist/src/commands/re-export-adjacent-registrations.d.ts +20 -0
- package/dist/src/commands/re-export-adjacent-registrations.js +75 -0
- package/dist/src/commands/re-export-adjacent.d.ts +45 -2
- package/dist/src/commands/re-export-adjacent.js +156 -37
- package/dist/src/commands/re-export-drift.d.ts +36 -8
- package/dist/src/commands/re-export-drift.js +73 -49
- package/dist/src/commands/re-export-files.js +11 -8
- package/dist/src/commands/re-export-lint.d.ts +2 -1
- package/dist/src/commands/re-export-lint.js +23 -10
- package/dist/src/commands/re-export-options.js +45 -4
- package/dist/src/commands/re-export-preflight.d.ts +23 -0
- package/dist/src/commands/re-export-preflight.js +160 -0
- package/dist/src/commands/re-export-register.js +42 -17
- package/dist/src/commands/re-export-removed.d.ts +12 -1
- package/dist/src/commands/re-export-removed.js +41 -11
- package/dist/src/commands/re-export-scan-owner.d.ts +44 -0
- package/dist/src/commands/re-export-scan-owner.js +116 -0
- package/dist/src/commands/re-export-scan.d.ts +17 -9
- package/dist/src/commands/re-export-scan.js +56 -23
- package/dist/src/commands/re-export-single.js +22 -6
- package/dist/src/commands/re-export-summary.d.ts +28 -0
- package/dist/src/commands/re-export-summary.js +36 -0
- package/dist/src/commands/re-export.js +37 -24
- package/dist/src/commands/rebase/index.js +17 -17
- package/dist/src/commands/run.js +16 -2
- package/dist/src/commands/status-check.d.ts +55 -16
- package/dist/src/commands/status-check.js +92 -37
- package/dist/src/commands/status-json.d.ts +22 -9
- package/dist/src/commands/status-json.js +47 -32
- package/dist/src/commands/status-output.js +3 -1
- package/dist/src/commands/status-scope.d.ts +37 -0
- package/dist/src/commands/status-scope.js +54 -0
- package/dist/src/commands/status.js +66 -23
- package/dist/src/commands/test-browser-preflight.js +19 -3
- package/dist/src/commands/test-build-furnace.d.ts +21 -0
- package/dist/src/commands/test-build-furnace.js +48 -0
- package/dist/src/commands/test-build-phase.js +47 -41
- package/dist/src/commands/test-checkjs.js +7 -0
- package/dist/src/commands/test-diagnose.js +7 -0
- package/dist/src/commands/test-modes.d.ts +16 -1
- package/dist/src/commands/test-modes.js +24 -1
- package/dist/src/commands/test-multi-verdict.d.ts +61 -0
- package/dist/src/commands/test-multi-verdict.js +111 -0
- package/dist/src/commands/test-orchestrate.d.ts +43 -0
- package/dist/src/commands/test-orchestrate.js +222 -0
- package/dist/src/commands/test-perf-scope.d.ts +29 -0
- package/dist/src/commands/test-perf-scope.js +83 -0
- package/dist/src/commands/test-prelock.d.ts +51 -0
- package/dist/src/commands/test-prelock.js +177 -0
- package/dist/src/commands/test-register.js +54 -10
- package/dist/src/commands/test-run.js +4 -1
- package/dist/src/commands/test-stale-gate.js +57 -27
- package/dist/src/commands/test-verdict.d.ts +10 -1
- package/dist/src/commands/test-verdict.js +28 -1
- package/dist/src/commands/test.d.ts +6 -1
- package/dist/src/commands/test.js +66 -60
- package/dist/src/commands/token-lint.d.ts +14 -0
- package/dist/src/commands/token-lint.js +111 -0
- package/dist/src/commands/token.js +10 -0
- package/dist/src/commands/verify-scope.d.ts +28 -0
- package/dist/src/commands/verify-scope.js +67 -0
- package/dist/src/commands/verify.d.ts +10 -2
- package/dist/src/commands/verify.js +59 -35
- package/dist/src/core/build-baseline-types.d.ts +46 -0
- package/dist/src/core/build-baseline-validate.js +19 -0
- package/dist/src/core/build-baseline.d.ts +5 -1
- package/dist/src/core/build-baseline.js +3 -1
- package/dist/src/core/build-overwrite-guard.d.ts +50 -0
- package/dist/src/core/build-overwrite-guard.js +84 -4
- package/dist/src/core/build-prepare.d.ts +7 -0
- package/dist/src/core/build-prepare.js +14 -8
- package/dist/src/core/build-run-record.d.ts +74 -0
- package/dist/src/core/build-run-record.js +136 -0
- package/dist/src/core/checkjs-support-heads.d.ts +41 -0
- package/dist/src/core/checkjs-support-heads.js +104 -0
- package/dist/src/core/config-paths.d.ts +2 -2
- package/dist/src/core/config-paths.js +8 -0
- package/dist/src/core/config-validate-check.d.ts +8 -0
- package/dist/src/core/config-validate-check.js +143 -0
- package/dist/src/core/config-validate-file-size.d.ts +9 -6
- package/dist/src/core/config-validate-file-size.js +91 -2
- package/dist/src/core/config-validate-history.d.ts +11 -0
- package/dist/src/core/config-validate-history.js +38 -0
- package/dist/src/core/config-validate-patch-policy.js +100 -0
- package/dist/src/core/config-validate-test-toolchains.js +7 -0
- package/dist/src/core/config-validate-token-scales.d.ts +6 -0
- package/dist/src/core/config-validate-token-scales.js +50 -0
- package/dist/src/core/config-validate.js +14 -5
- package/dist/src/core/config.d.ts +5 -0
- package/dist/src/core/config.js +16 -0
- package/dist/src/core/coverage-dispatch.d.ts +24 -0
- package/dist/src/core/coverage-dispatch.js +44 -0
- package/dist/src/core/coverage-extend.d.ts +70 -45
- package/dist/src/core/coverage-extend.js +199 -144
- package/dist/src/core/coverage-records.d.ts +97 -0
- package/dist/src/core/coverage-records.js +350 -0
- package/dist/src/core/destructive.d.ts +7 -4
- package/dist/src/core/destructive.js +10 -10
- package/dist/src/core/engine-session-lock.d.ts +10 -1
- package/dist/src/core/engine-session-lock.js +28 -8
- package/dist/src/core/export-moved-code-shape.d.ts +49 -11
- package/dist/src/core/export-moved-code-shape.js +135 -44
- package/dist/src/core/file-lock-queue.d.ts +67 -0
- package/dist/src/core/file-lock-queue.js +221 -0
- package/dist/src/core/file-lock.d.ts +4 -25
- package/dist/src/core/file-lock.js +105 -138
- package/dist/src/core/furnace-apply-output.d.ts +10 -0
- package/dist/src/core/furnace-apply-output.js +31 -1
- package/dist/src/core/furnace-apply.js +12 -2
- package/dist/src/core/furnace-composed-source.d.ts +76 -0
- package/dist/src/core/furnace-composed-source.js +167 -0
- package/dist/src/core/furnace-config-validate-exceptions.d.ts +9 -0
- package/dist/src/core/furnace-config-validate-exceptions.js +71 -0
- package/dist/src/core/furnace-config-validate.js +8 -0
- package/dist/src/core/furnace-css-fragments.d.ts +10 -0
- package/dist/src/core/furnace-css-fragments.js +26 -0
- package/dist/src/core/furnace-operation.d.ts +3 -3
- package/dist/src/core/furnace-registration-remove.d.ts +22 -1
- package/dist/src/core/furnace-registration-remove.js +53 -17
- package/dist/src/core/furnace-registration-rename.d.ts +26 -0
- package/dist/src/core/furnace-registration-rename.js +77 -0
- package/dist/src/core/furnace-registration.d.ts +14 -4
- package/dist/src/core/furnace-registration.js +44 -10
- package/dist/src/core/furnace-staleness.d.ts +7 -0
- package/dist/src/core/furnace-staleness.js +34 -24
- package/dist/src/core/furnace-validate-accessibility.d.ts +4 -2
- package/dist/src/core/furnace-validate-accessibility.js +10 -1
- package/dist/src/core/furnace-validate-exceptions.d.ts +40 -0
- package/dist/src/core/furnace-validate-exceptions.js +43 -0
- package/dist/src/core/furnace-validate-icon-button.d.ts +7 -0
- package/dist/src/core/furnace-validate-icon-button.js +49 -0
- package/dist/src/core/furnace-validate.js +1 -1
- package/dist/src/core/harness-port-fallback.d.ts +60 -0
- package/dist/src/core/harness-port-fallback.js +96 -0
- package/dist/src/core/history-log-file.d.ts +61 -0
- package/dist/src/core/history-log-file.js +157 -0
- package/dist/src/core/history-log.d.ts +2 -1
- package/dist/src/core/history-log.js +2 -1
- package/dist/src/core/json-preserving-edit.d.ts +22 -0
- package/dist/src/core/json-preserving-edit.js +199 -0
- package/dist/src/core/mach.js +8 -1
- package/dist/src/core/negative-control-diff.d.ts +39 -0
- package/dist/src/core/negative-control-diff.js +136 -0
- package/dist/src/core/negative-control.d.ts +138 -0
- package/dist/src/core/negative-control.js +356 -0
- package/dist/src/core/objdir-install-manifests.d.ts +73 -0
- package/dist/src/core/objdir-install-manifests.js +226 -0
- package/dist/src/core/patch-export-update.d.ts +2 -0
- package/dist/src/core/patch-export-update.js +1 -0
- package/dist/src/core/patch-export.js +3 -4
- package/dist/src/core/patch-lint-checkjs-any-import.d.ts +37 -0
- package/dist/src/core/patch-lint-checkjs-any-import.js +61 -0
- package/dist/src/core/patch-lint-checkjs.js +8 -11
- package/dist/src/core/patch-lint-code-lines.d.ts +59 -0
- package/dist/src/core/patch-lint-code-lines.js +105 -0
- package/dist/src/core/patch-lint-cross.js +12 -4
- package/dist/src/core/patch-lint-css.d.ts +8 -2
- package/dist/src/core/patch-lint-css.js +10 -4
- package/dist/src/core/patch-lint-file-size.d.ts +12 -5
- package/dist/src/core/patch-lint-file-size.js +22 -11
- package/dist/src/core/patch-lint-forward-registration-extended.d.ts +18 -0
- package/dist/src/core/patch-lint-forward-registration-extended.js +35 -10
- package/dist/src/core/patch-lint-forward-registration.js +10 -0
- package/dist/src/core/patch-lint-ignore.d.ts +152 -0
- package/dist/src/core/patch-lint-ignore.js +299 -0
- package/dist/src/core/patch-lint-size-thresholds.d.ts +52 -0
- package/dist/src/core/patch-lint-size-thresholds.js +110 -0
- package/dist/src/core/patch-lint-size.d.ts +117 -0
- package/dist/src/core/patch-lint-size.js +323 -0
- package/dist/src/core/patch-lint-staged-registration.d.ts +18 -1
- package/dist/src/core/patch-lint-staged-registration.js +20 -0
- package/dist/src/core/patch-lint-token-scale.d.ts +39 -0
- package/dist/src/core/patch-lint-token-scale.js +109 -0
- package/dist/src/core/patch-lint.d.ts +11 -83
- package/dist/src/core/patch-lint.js +64 -286
- package/dist/src/core/patch-lock.js +7 -6
- package/dist/src/core/patch-manifest-consistency.js +7 -6
- package/dist/src/core/patch-manifest-io.d.ts +6 -3
- package/dist/src/core/patch-manifest-io.js +19 -8
- package/dist/src/core/patch-manifest-validate.js +34 -2
- package/dist/src/core/patch-order-legal.d.ts +25 -0
- package/dist/src/core/patch-order-legal.js +83 -0
- package/dist/src/core/patch-policy-content.d.ts +40 -0
- package/dist/src/core/patch-policy-content.js +216 -0
- package/dist/src/core/patch-policy.d.ts +16 -3
- package/dist/src/core/patch-policy.js +16 -9
- package/dist/src/core/patch-retired.d.ts +35 -0
- package/dist/src/core/patch-retired.js +54 -0
- package/dist/src/core/patch-subset.d.ts +16 -0
- package/dist/src/core/patch-subset.js +66 -0
- package/dist/src/core/preflight-wait.d.ts +20 -0
- package/dist/src/core/preflight-wait.js +38 -0
- package/dist/src/core/rebase-from-version.d.ts +46 -0
- package/dist/src/core/rebase-from-version.js +71 -0
- package/dist/src/core/run-log.d.ts +20 -0
- package/dist/src/core/run-log.js +89 -3
- package/dist/src/core/run-profile.d.ts +7 -0
- package/dist/src/core/run-profile.js +7 -1
- package/dist/src/core/smoke-patterns.d.ts +5 -0
- package/dist/src/core/smoke-patterns.js +13 -0
- package/dist/src/core/test-harness-crash.d.ts +18 -1
- package/dist/src/core/test-harness-crash.js +6 -4
- package/dist/src/core/test-harness-signature.d.ts +17 -0
- package/dist/src/core/test-harness-signature.js +4 -0
- package/dist/src/core/test-run-completeness.d.ts +57 -0
- package/dist/src/core/test-run-completeness.js +144 -0
- package/dist/src/core/test-stale-check.d.ts +27 -1
- package/dist/src/core/test-stale-check.js +39 -49
- package/dist/src/core/test-stale-exempt.d.ts +40 -0
- package/dist/src/core/test-stale-exempt.js +70 -0
- package/dist/src/core/token-lint.d.ts +50 -0
- package/dist/src/core/token-lint.js +126 -0
- package/dist/src/core/tree-guard.js +3 -0
- package/dist/src/core/typecheck-shim.d.ts +1 -1
- package/dist/src/core/typecheck-shim.js +90 -2
- package/dist/src/index.d.ts +8 -3
- package/dist/src/index.js +3 -1
- package/dist/src/types/commands/index.d.ts +2 -2
- package/dist/src/types/commands/options.d.ts +148 -21
- package/dist/src/types/commands/patches.d.ts +75 -1
- package/dist/src/types/config.d.ts +192 -0
- package/dist/src/types/config.js +9 -0
- package/dist/src/types/furnace.d.ts +43 -1
- package/dist/src/types/furnace.js +7 -0
- package/dist/src/types/index.d.ts +1 -1
- package/dist/src/utils/options.d.ts +34 -6
- package/dist/src/utils/options.js +86 -5
- package/dist/src/utils/path-glob.d.ts +15 -0
- package/dist/src/utils/path-glob.js +47 -0
- package/dist/src/utils/regex.d.ts +15 -3
- package/dist/src/utils/regex.js +37 -16
- package/docs/configuration.md +282 -9
- package/docs/exit-codes.md +18 -1
- package/docs/furnace.md +97 -3
- package/docs/lifecycle-invariants.md +52 -19
- package/docs/machine-output.md +74 -12
- package/docs/patch-workflow.md +297 -22
- package/docs/run-logs.md +20 -1
- package/docs/testing.md +276 -20
- package/package.json +2 -2
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
2
|
import { loadConfig } from '../../core/config.js';
|
|
3
3
|
import { applyAllComponents } from '../../core/furnace-apply.js';
|
|
4
|
-
import { logApplyResult } from '../../core/furnace-apply-output.js';
|
|
4
|
+
import { formatSkipBreakdown, logApplyResult } from '../../core/furnace-apply-output.js';
|
|
5
5
|
import { getFurnacePaths } from '../../core/furnace-config.js';
|
|
6
6
|
import { reportJsconfigPathsSync } from '../../core/furnace-jsconfig.js';
|
|
7
|
-
import { runFurnaceMutation, waitLockMutationOptions
|
|
7
|
+
import { runFurnaceMutation, waitLockMutationOptions } from '../../core/furnace-operation.js';
|
|
8
8
|
import { assertFurnaceReady } from '../../core/furnace-precondition.js';
|
|
9
|
-
import {
|
|
9
|
+
import { getStepFailureCount } from '../../core/furnace-state-persist.js';
|
|
10
10
|
import { hasBlockingStepErrors } from '../../core/furnace-step-errors.js';
|
|
11
11
|
import { findOverrideBaseVersionDrift, formatOverrideBaseVersionDriftError, formatOverrideBaseVersionDriftWarning, } from '../../core/furnace-version-drift.js';
|
|
12
12
|
import { FurnaceError } from '../../errors/furnace.js';
|
|
13
13
|
import { info, intro, note, notice, outro, spinner, warn } from '../../utils/logger.js';
|
|
14
|
-
import { formatFragmentIncluderNotice
|
|
14
|
+
import { formatFragmentIncluderNotice } from './deploy-fragment-includers.js';
|
|
15
|
+
import { deployNamedComponents } from './deploy-named.js';
|
|
15
16
|
import { runDeployValidation } from './validation-output.js';
|
|
16
17
|
/**
|
|
17
18
|
* Builds the final deploy failure summary from apply and validation error counts.
|
|
@@ -39,51 +40,6 @@ function getFailedComponentNames(result) {
|
|
|
39
40
|
}
|
|
40
41
|
return failed;
|
|
41
42
|
}
|
|
42
|
-
/**
|
|
43
|
-
* Applies a single named override or custom component in targeted deploy
|
|
44
|
-
* mode.
|
|
45
|
-
*
|
|
46
|
-
* Delegates to {@link applyAllComponents} with a `componentName` filter so
|
|
47
|
-
* targeted deploys run the exact same pipeline as deploy-all, including
|
|
48
|
-
* workspace-deletion detection, engine orphan undeploy, and jar.mn /
|
|
49
|
-
* customElements.js re-sync. Calling the per-component apply helpers
|
|
50
|
-
* directly never prunes: renaming a helper file in the workspace then leaves
|
|
51
|
-
* the old deployed file and its stale jar.mn line in the engine.
|
|
52
|
-
*
|
|
53
|
-
* `persistState: false` matters here: the batch persist path replaces
|
|
54
|
-
* `appliedChecksums` wholesale with only this run's entries, which for a
|
|
55
|
-
* named deploy would wipe every other component's state. Named deploy keeps
|
|
56
|
-
* its per-component state merge ({@link persistSingleComponentState}) and
|
|
57
|
-
* its atomicity gate ({@link shouldPersistSingleComponentState}) at the call
|
|
58
|
-
* site. Rollback on failure happens inside `applyAllComponents`. The journal
|
|
59
|
-
* returned on success is ignored (the deploy keeps its files).
|
|
60
|
-
*
|
|
61
|
-
* The caller then runs the fragment-includer follow-up
|
|
62
|
-
* ({@link refreshStaleFragmentIncluders}) ONLY after this component's state
|
|
63
|
-
* is persisted: each includer is applied and persisted as its own unit, so
|
|
64
|
-
* a signal mid-loop restores at most the includer in flight and leaves every
|
|
65
|
-
* earlier unit committed. Moving the persist after the includer loop would
|
|
66
|
-
* silently break that atomicity.
|
|
67
|
-
*
|
|
68
|
-
* @param name - Component name to apply
|
|
69
|
-
* @param config - Loaded Furnace configuration
|
|
70
|
-
* @param isDryRun - Whether file writes should be skipped
|
|
71
|
-
* @returns Apply result for the named component, or `stock` for stock-only
|
|
72
|
-
* entries
|
|
73
|
-
*/
|
|
74
|
-
async function applyNamedComponent(name, config, isDryRun, projectRoot, operationContext) {
|
|
75
|
-
if (!(name in config.overrides) && !(name in config.custom)) {
|
|
76
|
-
if (config.stock.includes(name)) {
|
|
77
|
-
return 'stock';
|
|
78
|
-
}
|
|
79
|
-
throw new FurnaceError(`Component "${name}" not found in furnace.json.`, name);
|
|
80
|
-
}
|
|
81
|
-
return applyAllComponents(projectRoot, isDryRun, {
|
|
82
|
-
componentName: name,
|
|
83
|
-
persistState: false,
|
|
84
|
-
...(operationContext ? { operationContext } : {}),
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
43
|
/**
|
|
88
44
|
* Prints the deploy summary after apply and validation complete.
|
|
89
45
|
* @param result - Aggregate apply result
|
|
@@ -109,7 +65,7 @@ function printDeploymentSummary(result, totalErrors, totalWarnings, componentCou
|
|
|
109
65
|
: ''), 'Dry Run Summary');
|
|
110
66
|
}
|
|
111
67
|
else {
|
|
112
|
-
note(`Applied ${appliedCount}, skipped ${skippedCount}\n` +
|
|
68
|
+
note(`Applied ${appliedCount}, skipped ${skippedCount}${formatSkipBreakdown(result.skipped)}\n` +
|
|
113
69
|
`${applyErrors} apply error(s)` +
|
|
114
70
|
(stepErrorCount > 0 ? `, ${stepErrorCount} registration step error(s)` : '') +
|
|
115
71
|
`\n` +
|
|
@@ -132,14 +88,25 @@ function enforceScopedOverrideVersionDriftPreflight(scopedDrift, force) {
|
|
|
132
88
|
throw new FurnaceError(formatOverrideBaseVersionDriftError(scopedDrift));
|
|
133
89
|
}
|
|
134
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Normalises the positional argument: a single name (programmatic callers)
|
|
93
|
+
* or the variadic list commander passes. An empty list means "deploy all".
|
|
94
|
+
*/
|
|
95
|
+
function normalizeDeployNames(names) {
|
|
96
|
+
if (names === undefined)
|
|
97
|
+
return [];
|
|
98
|
+
return typeof names === 'string' ? [names] : [...names];
|
|
99
|
+
}
|
|
135
100
|
/**
|
|
136
101
|
* Runs the furnace deploy command: apply components then validate in one step.
|
|
137
102
|
* @param projectRoot - Root directory of the project
|
|
138
|
-
* @param
|
|
103
|
+
* @param names - Component name(s) to deploy, all under one lock hold
|
|
104
|
+
* (deploys all when omitted or empty)
|
|
139
105
|
* @param options - Command options
|
|
140
106
|
*/
|
|
141
|
-
export async function furnaceDeployCommand(projectRoot,
|
|
107
|
+
export async function furnaceDeployCommand(projectRoot, names, options = {}) {
|
|
142
108
|
const isDryRun = options.dryRun ?? false;
|
|
109
|
+
const requested = normalizeDeployNames(names);
|
|
143
110
|
intro(isDryRun ? 'Furnace Deploy (dry run)' : 'Furnace Deploy');
|
|
144
111
|
const { config } = await assertFurnaceReady(projectRoot);
|
|
145
112
|
const furnacePaths = getFurnacePaths(projectRoot);
|
|
@@ -157,7 +124,9 @@ export async function furnaceDeployCommand(projectRoot, name, options = {}) {
|
|
|
157
124
|
const forgeConfig = await loadConfig(projectRoot);
|
|
158
125
|
const driftEntries = findOverrideBaseVersionDrift(config, forgeConfig.firefox.version);
|
|
159
126
|
const force = options.force ?? false;
|
|
160
|
-
const scopedDrift =
|
|
127
|
+
const scopedDrift = requested.length > 0
|
|
128
|
+
? driftEntries.filter((entry) => requested.includes(entry.name))
|
|
129
|
+
: driftEntries;
|
|
161
130
|
enforceScopedOverrideVersionDriftPreflight(scopedDrift, force);
|
|
162
131
|
// --- Step 1: Apply ---
|
|
163
132
|
const applySpinner = spinner(isDryRun ? 'Calculating planned actions...' : 'Applying components to engine...');
|
|
@@ -166,55 +135,37 @@ export async function furnaceDeployCommand(projectRoot, name, options = {}) {
|
|
|
166
135
|
// phase below is read-only and runs outside the lock so two concurrent
|
|
167
136
|
// `furnace deploy` runs only contend on the actual mutation.
|
|
168
137
|
const applyOutcome = await runFurnaceMutation(projectRoot, 'deploy-rollback', async (ctx) => {
|
|
169
|
-
if (
|
|
170
|
-
|
|
171
|
-
if (namedApplyResult === 'stock') {
|
|
172
|
-
return { kind: 'stock' };
|
|
173
|
-
}
|
|
174
|
-
// Named deploy is atomic: state is persisted only when every apply
|
|
175
|
-
// step succeeded. Any rollback triggered by applyNamedComponent has
|
|
176
|
-
// already restored the engine to its pre-deploy state, so persisting
|
|
177
|
-
// partial checksums here would mis-report the next status/apply run
|
|
178
|
-
// against a workspace that was never actually deployed.
|
|
179
|
-
const primaryClean = shouldPersistSingleComponentState(namedApplyResult, isDryRun);
|
|
180
|
-
if (primaryClean) {
|
|
181
|
-
await persistSingleComponentState(projectRoot, getPersistableAppliedEntry('Deploy', name, namedApplyResult.applied[0]), furnacePaths);
|
|
182
|
-
}
|
|
183
|
-
// Other deployed includers of a fragment this component includes
|
|
184
|
-
// would otherwise keep a stale expansion. Only after a clean,
|
|
185
|
-
// persisted primary apply (dry-run always previews).
|
|
186
|
-
if (!primaryClean && !isDryRun) {
|
|
187
|
-
return { kind: 'result', result: namedApplyResult };
|
|
188
|
-
}
|
|
189
|
-
const includers = await refreshStaleFragmentIncluders({
|
|
138
|
+
if (requested.length > 0) {
|
|
139
|
+
return deployNamedComponents({
|
|
190
140
|
projectRoot,
|
|
191
|
-
|
|
141
|
+
names: requested,
|
|
192
142
|
config,
|
|
193
143
|
furnacePaths,
|
|
194
144
|
isDryRun,
|
|
195
145
|
operationContext: ctx,
|
|
196
146
|
});
|
|
197
|
-
return {
|
|
198
|
-
kind: 'result',
|
|
199
|
-
result: mergeApplyResults(namedApplyResult, includers.results),
|
|
200
|
-
includers,
|
|
201
|
-
};
|
|
202
147
|
}
|
|
203
148
|
const allResult = await applyAllComponents(projectRoot, isDryRun, {
|
|
204
149
|
operationContext: ctx,
|
|
205
150
|
});
|
|
206
|
-
return { kind: '
|
|
151
|
+
return { kind: 'all', result: allResult };
|
|
207
152
|
}, { dryRun: isDryRun, ...waitLockMutationOptions(options.waitLockSeconds) });
|
|
153
|
+
const warnStock = () => {
|
|
154
|
+
for (const stockName of applyOutcome.kind === 'all' ? [] : applyOutcome.stock) {
|
|
155
|
+
warn(`"${stockName}" is a stock component. Stock components are not applied locally.`);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
208
158
|
if (applyOutcome.kind === 'stock') {
|
|
209
159
|
applySpinner.stop('Apply skipped');
|
|
210
|
-
|
|
160
|
+
warnStock();
|
|
211
161
|
outro(isDryRun ? 'Dry run complete (no files modified)' : 'Deploy complete');
|
|
212
162
|
return;
|
|
213
163
|
}
|
|
214
164
|
const result = applyOutcome.result;
|
|
215
165
|
applySpinner.stop(isDryRun ? 'Planned actions calculated' : 'Components applied');
|
|
166
|
+
warnStock();
|
|
216
167
|
logApplyResult(result, isDryRun);
|
|
217
|
-
if (applyOutcome.
|
|
168
|
+
if (applyOutcome.kind === 'result') {
|
|
218
169
|
for (const line of formatFragmentIncluderNotice(applyOutcome.includers, isDryRun)) {
|
|
219
170
|
notice(line);
|
|
220
171
|
}
|
|
@@ -235,20 +186,33 @@ export async function furnaceDeployCommand(projectRoot, name, options = {}) {
|
|
|
235
186
|
}
|
|
236
187
|
const validateSpinner = spinner(isDryRun ? 'Validating (read-only)...' : 'Validating...');
|
|
237
188
|
const failedComponents = getFailedComponentNames(result);
|
|
189
|
+
// A named deploy validates every named component that applied, then the
|
|
190
|
+
// includers it refreshed. The first clean name leads (the validation
|
|
191
|
+
// runner skips the whole named branch when its lead failed), and every
|
|
192
|
+
// failed name is counted as skipped.
|
|
193
|
+
const deployed = applyOutcome.kind === 'result' ? applyOutcome.deployed : [];
|
|
194
|
+
const failedNamed = deployed.filter((tag) => failedComponents.has(tag));
|
|
195
|
+
const lead = deployed.find((tag) => !failedComponents.has(tag)) ?? deployed[0];
|
|
238
196
|
const validation = await runDeployValidation({
|
|
239
197
|
validateSpinner,
|
|
240
|
-
name,
|
|
198
|
+
name: lead,
|
|
241
199
|
config,
|
|
242
200
|
furnacePaths,
|
|
243
201
|
failedComponents,
|
|
244
202
|
isDryRun,
|
|
245
203
|
projectRoot,
|
|
246
204
|
dryRunActions: result.actions,
|
|
247
|
-
alsoValidate: applyOutcome.
|
|
205
|
+
alsoValidate: applyOutcome.kind === 'result'
|
|
206
|
+
? [...deployed.filter((tag) => tag !== lead), ...applyOutcome.includers.refreshed]
|
|
207
|
+
: [],
|
|
248
208
|
});
|
|
249
209
|
if (validation.done)
|
|
250
210
|
return;
|
|
251
|
-
const { totalErrors, totalWarnings, componentCount
|
|
211
|
+
const { totalErrors, totalWarnings, componentCount } = validation;
|
|
212
|
+
// The runner counts a failed lead itself; failed extras it drops silently.
|
|
213
|
+
const skippedValidationCount = lead !== undefined && failedComponents.has(lead)
|
|
214
|
+
? failedNamed.length
|
|
215
|
+
: validation.skippedValidationCount + failedNamed.length;
|
|
252
216
|
// --- Step 3: Summary ---
|
|
253
217
|
printDeploymentSummary(result, totalErrors, totalWarnings, componentCount, skippedValidationCount, isDryRun);
|
|
254
218
|
}
|
|
@@ -4,6 +4,8 @@ import { join } from 'node:path';
|
|
|
4
4
|
import { getProjectPaths, loadState } from '../../core/config.js';
|
|
5
5
|
import { diffLines, renderHunks } from '../../core/diff-hunks.js';
|
|
6
6
|
import { getOverrideEngineTargetPath } from '../../core/furnace-apply-helpers.js';
|
|
7
|
+
import { normalizeForChecksum } from '../../core/furnace-checksum-utils.js';
|
|
8
|
+
import { composeComponentSource } from '../../core/furnace-composed-source.js';
|
|
7
9
|
import { getFurnacePaths, loadFurnaceConfig } from '../../core/furnace-config.js';
|
|
8
10
|
import { isComponentSourceFile, resolveFtlDir } from '../../core/furnace-constants.js';
|
|
9
11
|
import { getFileContentAtRef } from '../../core/git-file-ops.js';
|
|
@@ -108,6 +110,14 @@ async function diffOverride(name, projectRoot, config) {
|
|
|
108
110
|
* copy. Shows what would change (or has changed) on the next
|
|
109
111
|
* `furnace apply`.
|
|
110
112
|
*
|
|
113
|
+
* The workspace side is the Furnace-composed source: a stylesheet with
|
|
114
|
+
* `@fireforge-include` directives is compared in its fragment-expanded
|
|
115
|
+
* form, exactly as apply deploys it. Comparing the raw source instead
|
|
116
|
+
* reported every freshly deployed fragment includer as a large difference
|
|
117
|
+
* (the inlined fragment bodies and end markers), so the diff could not show
|
|
118
|
+
* whether a deploy would change anything. Line endings and a BOM are
|
|
119
|
+
* ignored, as apply's own drift check ignores them.
|
|
120
|
+
*
|
|
111
121
|
* `.ftl` files deploy to `engine/<ftlDir>/<name>.ftl` via
|
|
112
122
|
* `applyCustomFtlFile`, not to `customConfig.targetPath`, so the
|
|
113
123
|
* deployment-target lookup has to branch on extension. Without the branch, a
|
|
@@ -136,7 +146,19 @@ async function diffCustom(name, projectRoot, config) {
|
|
|
136
146
|
if (!isComponentSourceFile(entry.name))
|
|
137
147
|
continue;
|
|
138
148
|
const workspacePath = join(customDir, entry.name);
|
|
139
|
-
const
|
|
149
|
+
const rawWorkspace = await readText(workspacePath);
|
|
150
|
+
let workspaceContent = rawWorkspace;
|
|
151
|
+
let composedNote = '';
|
|
152
|
+
try {
|
|
153
|
+
workspaceContent = await composeComponentSource(rawWorkspace, entry.name, furnacePaths.sharedDir);
|
|
154
|
+
if (workspaceContent !== rawWorkspace)
|
|
155
|
+
composedNote = ' (composed: fragments expanded)';
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
// A missing fragment: no composed form exists, so show the raw
|
|
159
|
+
// source and say why; `furnace validate` reports the fragment itself.
|
|
160
|
+
composedNote = ` (raw: ${toError(error).message})`;
|
|
161
|
+
}
|
|
140
162
|
// `.ftl` files deploy to the locale tree, not the component's
|
|
141
163
|
// targetPath. Mirror `applyCustomFtlFile`'s target computation so the
|
|
142
164
|
// diff header and the existence probe name the same path apply
|
|
@@ -154,13 +176,14 @@ async function diffCustom(name, projectRoot, config) {
|
|
|
154
176
|
hasDifferences = true;
|
|
155
177
|
continue;
|
|
156
178
|
}
|
|
157
|
-
const deployedContent = await readText(deployedPath);
|
|
179
|
+
const deployedContent = normalizeForChecksum(await readText(deployedPath));
|
|
180
|
+
workspaceContent = normalizeForChecksum(workspaceContent);
|
|
158
181
|
if (workspaceContent === deployedContent) {
|
|
159
182
|
continue;
|
|
160
183
|
}
|
|
161
184
|
hasDifferences = true;
|
|
162
185
|
info(`--- ${deployedDisplayPath}`);
|
|
163
|
-
info(`+++ components/custom/${name}/${entry.name}`);
|
|
186
|
+
info(`+++ components/custom/${name}/${entry.name}${composedNote}`);
|
|
164
187
|
for (const line of formatUnifiedDiff(deployedContent, workspaceContent)) {
|
|
165
188
|
info(line);
|
|
166
189
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
2
|
import { Command, Option } from 'commander';
|
|
3
3
|
import { withEngineSessionLock } from '../../core/engine-session-lock.js';
|
|
4
|
-
import { addWaitLockOption, pickDefined, resolveWaitLockSeconds } from '../../utils/options.js';
|
|
4
|
+
import { addWaitLockOption, pickDefined, resolveWaitLockSeconds, } from '../../utils/options.js';
|
|
5
5
|
import { furnaceApplyCommand } from './apply.js';
|
|
6
6
|
import { furnaceChromeDocCreateCommand } from './chrome-doc.js';
|
|
7
7
|
import { furnaceChromeDocRemoveCommand } from './chrome-doc-remove.js';
|
|
@@ -19,6 +19,18 @@ import { furnaceScanCommand } from './scan.js';
|
|
|
19
19
|
import { furnaceStatusCommand } from './status.js';
|
|
20
20
|
import { furnaceSyncCommand } from './sync.js';
|
|
21
21
|
import { furnaceValidateCommand } from './validate.js';
|
|
22
|
+
import { parseMaxWarnings } from './validate-report.js';
|
|
23
|
+
/**
|
|
24
|
+
* Splits the raw `--wait-lock` value off a furnace leaf's parsed options and
|
|
25
|
+
* resolves it into `waitLockSeconds` (flag, else `FIREFORGE_WAIT_LOCK`, else
|
|
26
|
+
* undefined) for the furnace lock `runFurnaceMutation` takes. Used by the
|
|
27
|
+
* leaves that take only Furnace's lock, where the flag used to be accepted
|
|
28
|
+
* and ignored.
|
|
29
|
+
*/
|
|
30
|
+
function withFurnaceLockBudget(options) {
|
|
31
|
+
const { waitLock, ...rest } = options;
|
|
32
|
+
return pickDefined({ ...rest, waitLockSeconds: resolveWaitLockSeconds(waitLock) });
|
|
33
|
+
}
|
|
22
34
|
async function runEngineLockedFurnaceCommand(context, command, operation, waitLockSeconds) {
|
|
23
35
|
const projectRoot = context.getProjectRoot();
|
|
24
36
|
await withEngineSessionLock(projectRoot, command, () => operation(projectRoot), {
|
|
@@ -51,16 +63,24 @@ function registerFurnaceApplyCommand(furnace, context) {
|
|
|
51
63
|
function registerFurnaceDeployCommand(furnace, context) {
|
|
52
64
|
const { getProjectRoot, withErrorHandling } = context;
|
|
53
65
|
const deploy = furnace
|
|
54
|
-
.command('deploy [
|
|
55
|
-
.description('Apply components and validate in one step')
|
|
66
|
+
.command('deploy [names...]')
|
|
67
|
+
.description('Apply components and validate in one step (name several to deploy them under one lock hold)')
|
|
56
68
|
.option('--dry-run', 'Show what would be changed without writing (reads may overlap concurrent mutations)')
|
|
57
69
|
.option('--force', 'Proceed despite baseVersion drift (stale overrides)')
|
|
58
|
-
.option('--skip-validate', 'Skip the validation step (apply only)')
|
|
59
|
-
|
|
70
|
+
.option('--skip-validate', 'Skip the validation step (apply only)')
|
|
71
|
+
.addHelpText('after', [
|
|
72
|
+
'',
|
|
73
|
+
'Deploy registers every .mjs and .css file in a custom component directory in',
|
|
74
|
+
'toolkit/content/jar.mn, including extra modules beside the main one (e.g.',
|
|
75
|
+
'components/custom/<tag>/<tag>-rows.mjs). Add the file to the directory and run',
|
|
76
|
+
'`fireforge furnace deploy <tag>` instead of writing the jar.mn line by hand; a line',
|
|
77
|
+
'that already exists anywhere in jar.mn is kept and never duplicated.',
|
|
78
|
+
].join('\n'));
|
|
79
|
+
addWaitLockOption(deploy).action(withErrorHandling(async (names, options) => {
|
|
60
80
|
const { waitLock, ...rest } = options ?? {};
|
|
61
81
|
const waitLockSeconds = resolveWaitLockSeconds(waitLock);
|
|
62
82
|
const parsed = pickDefined({ ...rest, waitLockSeconds });
|
|
63
|
-
const run = (projectRoot) => furnaceDeployCommand(projectRoot,
|
|
83
|
+
const run = (projectRoot) => furnaceDeployCommand(projectRoot, names, parsed);
|
|
64
84
|
if (parsed.dryRun === true) {
|
|
65
85
|
await run(getProjectRoot());
|
|
66
86
|
return;
|
|
@@ -103,15 +123,15 @@ function registerFurnaceInfoCommands(furnace, context) {
|
|
|
103
123
|
}));
|
|
104
124
|
registerFurnaceApplyCommand(furnace, context);
|
|
105
125
|
registerFurnaceDeployCommand(furnace, context);
|
|
106
|
-
furnace
|
|
126
|
+
const scan = furnace
|
|
107
127
|
.command('scan')
|
|
108
128
|
.description('Scan engine for available components. Report-only by default: persist the discovered ' +
|
|
109
129
|
'inventory into the stock section of furnace.json with --track (or the interactive ' +
|
|
110
130
|
'prompt); deploy/validate consume the inventory from there.')
|
|
111
131
|
.option('--deep', 'Search additional Firefox directories beyond the default widgets path')
|
|
112
|
-
.option('--track', 'Persist every discovered untracked component into the stock section of furnace.json (non-interactive)')
|
|
113
|
-
|
|
114
|
-
await furnaceScanCommand(getProjectRoot(),
|
|
132
|
+
.option('--track', 'Persist every discovered untracked component into the stock section of furnace.json (non-interactive)');
|
|
133
|
+
addWaitLockOption(scan).action(withErrorHandling(async (options) => {
|
|
134
|
+
await furnaceScanCommand(getProjectRoot(), withFurnaceLockBudget(options));
|
|
115
135
|
}));
|
|
116
136
|
furnace
|
|
117
137
|
.command('init')
|
|
@@ -122,7 +142,7 @@ function registerFurnaceInfoCommands(furnace, context) {
|
|
|
122
142
|
.action(withErrorHandling(async (options) => {
|
|
123
143
|
await furnaceInitCommand(getProjectRoot(), options);
|
|
124
144
|
}));
|
|
125
|
-
furnace
|
|
145
|
+
const create = furnace
|
|
126
146
|
.command('create [name]')
|
|
127
147
|
.description('Create a new custom component')
|
|
128
148
|
.option('-d, --description <desc>', 'Component description')
|
|
@@ -140,9 +160,9 @@ function registerFurnaceInfoCommands(furnace, context) {
|
|
|
140
160
|
.option('--compose <tags>', 'Record stock tags composed internally (metadata only, comma-separated)', (val) => val.split(',').map((s) => s.trim()))
|
|
141
161
|
.option('--shared-ftl <path>', 'Participate in an existing feature-scoped .ftl at this path (e.g. "browser/mybrowser-dock.ftl"); skips the per-component .ftl scaffold (implies --localized)')
|
|
142
162
|
.option('--dry-run', 'Show the planned file set and furnace.json changes without writing')
|
|
143
|
-
.option('--allow-prefix-mismatch', 'Create the component even when its name does not start with the configured `componentPrefix` in furnace.json. Without this flag the command refuses to write anything on a prefix mismatch.')
|
|
144
|
-
|
|
145
|
-
await furnaceCreateCommand(getProjectRoot(), name, options);
|
|
163
|
+
.option('--allow-prefix-mismatch', 'Create the component even when its name does not start with the configured `componentPrefix` in furnace.json. Without this flag the command refuses to write anything on a prefix mismatch.');
|
|
164
|
+
addWaitLockOption(create).action(withErrorHandling(async (name, options) => {
|
|
165
|
+
await furnaceCreateCommand(getProjectRoot(), name, withFurnaceLockBudget(options));
|
|
146
166
|
}));
|
|
147
167
|
registerChromeDocCommands(furnace, context);
|
|
148
168
|
}
|
|
@@ -151,23 +171,23 @@ function registerChromeDocCommands(furnace, context) {
|
|
|
151
171
|
const chromeDoc = furnace
|
|
152
172
|
.command('chrome-doc')
|
|
153
173
|
.description('Scaffold top-level chrome documents (xhtml + js + css + ftl + jar.mn)');
|
|
154
|
-
chromeDoc
|
|
174
|
+
const chromeDocCreate = chromeDoc
|
|
155
175
|
.command('create <name>')
|
|
156
176
|
.description('Scaffold a new top-level chrome document')
|
|
157
177
|
.option('--no-titlebar', 'Frameless overlay-style document (omits titlebar-buttonbox)')
|
|
158
178
|
.option('--browser-window', 'Emit the browser.xhtml-like main-window skeleton (<html id="main-window"> with windowtype/chromehidden/persist root attributes platform C++ reads before scripts run) for the document configured as the fork\'s main browser window; implies the titlebar markup')
|
|
159
179
|
.option('--with-tests', 'Scaffold an xpcshell packaging-verification test that probes XCurProcD/chrome/browser/... directly (bypasses the xpcshell chrome:// URI limitation).')
|
|
160
|
-
.option('--dry-run', 'Show the chrome-doc scaffold plan without writing')
|
|
161
|
-
|
|
162
|
-
await furnaceChromeDocCreateCommand(getProjectRoot(), name,
|
|
180
|
+
.option('--dry-run', 'Show the chrome-doc scaffold plan without writing');
|
|
181
|
+
addWaitLockOption(chromeDocCreate).action(withErrorHandling(async (name, options) => {
|
|
182
|
+
await furnaceChromeDocCreateCommand(getProjectRoot(), name, withFurnaceLockBudget(options));
|
|
163
183
|
}));
|
|
164
|
-
chromeDoc
|
|
184
|
+
const chromeDocRemove = chromeDoc
|
|
165
185
|
.command('remove <name>')
|
|
166
186
|
.description('Remove a scaffolded top-level chrome document')
|
|
167
187
|
.option('-y, --yes', 'Skip confirmation')
|
|
168
|
-
.option('--dry-run', 'Show the chrome-doc removal plan without writing')
|
|
169
|
-
|
|
170
|
-
await furnaceChromeDocRemoveCommand(getProjectRoot(), name,
|
|
188
|
+
.option('--dry-run', 'Show the chrome-doc removal plan without writing');
|
|
189
|
+
addWaitLockOption(chromeDocRemove).action(withErrorHandling(async (name, options) => {
|
|
190
|
+
await furnaceChromeDocRemoveCommand(getProjectRoot(), name, withFurnaceLockBudget(options));
|
|
171
191
|
}));
|
|
172
192
|
}
|
|
173
193
|
/**
|
|
@@ -178,12 +198,13 @@ function registerChromeDocCommands(furnace, context) {
|
|
|
178
198
|
*/
|
|
179
199
|
function registerFurnaceModifyCommands(furnace, context) {
|
|
180
200
|
const { getProjectRoot, withErrorHandling } = context;
|
|
181
|
-
furnace
|
|
201
|
+
const override = furnace
|
|
182
202
|
.command('override [names...]')
|
|
183
203
|
.description('Fork one or more existing components for modification')
|
|
184
204
|
.addOption(new Option('-t, --type <type>', 'Override type').choices(['css-only', 'full']))
|
|
185
|
-
.option('-d, --description <desc>', 'Description')
|
|
186
|
-
|
|
205
|
+
.option('-d, --description <desc>', 'Description');
|
|
206
|
+
addWaitLockOption(override).action(withErrorHandling(async (names, rawOptions) => {
|
|
207
|
+
const options = withFurnaceLockBudget(rawOptions);
|
|
187
208
|
if (names.length <= 1) {
|
|
188
209
|
await furnaceOverrideCommand(getProjectRoot(), names[0], options);
|
|
189
210
|
}
|
|
@@ -198,26 +219,29 @@ function registerFurnaceModifyCommands(furnace, context) {
|
|
|
198
219
|
.action(withErrorHandling(async (options) => {
|
|
199
220
|
await furnaceListCommand(getProjectRoot(), options);
|
|
200
221
|
}));
|
|
201
|
-
furnace
|
|
222
|
+
const remove = furnace
|
|
202
223
|
.command('remove <name>')
|
|
203
224
|
.description('Remove a component from the workspace')
|
|
204
|
-
.option('-y, --yes', 'Skip confirmation')
|
|
205
|
-
|
|
206
|
-
await furnaceRemoveCommand(getProjectRoot(), name, options);
|
|
225
|
+
.option('-y, --yes', 'Skip confirmation');
|
|
226
|
+
addWaitLockOption(remove).action(withErrorHandling(async (name, options) => {
|
|
227
|
+
await furnaceRemoveCommand(getProjectRoot(), name, withFurnaceLockBudget(options));
|
|
207
228
|
}));
|
|
208
|
-
furnace
|
|
229
|
+
const preview = furnace
|
|
209
230
|
.command('preview')
|
|
210
231
|
.description('Start component preview (Storybook)')
|
|
211
|
-
.option('--install', 'Force reinstall Storybook dependencies')
|
|
212
|
-
|
|
213
|
-
await furnacePreviewCommand(getProjectRoot(), options);
|
|
232
|
+
.option('--install', 'Force reinstall Storybook dependencies');
|
|
233
|
+
addWaitLockOption(preview).action(withErrorHandling(async (options) => {
|
|
234
|
+
await furnacePreviewCommand(getProjectRoot(), withFurnaceLockBudget(options));
|
|
214
235
|
}));
|
|
215
236
|
furnace
|
|
216
237
|
.command('validate [name]')
|
|
217
238
|
.description('Run accessibility and compatibility checks')
|
|
218
239
|
.option('--fix', 'Auto-fix registration issues (missing jar.mn entries, customElements.js)')
|
|
240
|
+
.option('--json', 'Write the result as one JSON document (findings, excepted findings, stale exceptions, verdict) to stdout')
|
|
241
|
+
.option('--max-warnings <n>', 'Fail when more than <n> warnings remain after furnace.json validationExceptions (0 for a warning-clean gate)')
|
|
219
242
|
.action(withErrorHandling(async (name, options) => {
|
|
220
|
-
|
|
243
|
+
const { maxWarnings, ...rest } = options ?? {};
|
|
244
|
+
await furnaceValidateCommand(getProjectRoot(), name, pickDefined({ ...rest, maxWarnings: parseMaxWarnings(maxWarnings) }));
|
|
221
245
|
}));
|
|
222
246
|
furnace
|
|
223
247
|
.command('diff [name]')
|
|
@@ -225,21 +249,22 @@ function registerFurnaceModifyCommands(furnace, context) {
|
|
|
225
249
|
.action(withErrorHandling(async (name) => {
|
|
226
250
|
await furnaceDiffCommand(getProjectRoot(), name);
|
|
227
251
|
}));
|
|
228
|
-
furnace
|
|
252
|
+
const refresh = furnace
|
|
229
253
|
.command('refresh [name]')
|
|
230
254
|
.description('Merge upstream Firefox changes into overrides (three-way merge)')
|
|
231
255
|
.option('--dry-run', 'Show what would change without modifying files')
|
|
232
256
|
.option('-a, --all', 'Refresh all overrides in a single batch')
|
|
233
257
|
.addOption(new Option('-s, --strategy <strategy>', 'Auto-resolve conflicts (ours = keep local, theirs = accept upstream)').choices(['ours', 'theirs']))
|
|
234
|
-
.option('--reset-base', 'Reset baseline to current engine HEAD (skips three-way merge, recovers from missing baseCommit)')
|
|
235
|
-
|
|
236
|
-
await furnaceRefreshCommand(getProjectRoot(), name,
|
|
258
|
+
.option('--reset-base', 'Reset baseline to current engine HEAD (skips three-way merge, recovers from missing baseCommit)');
|
|
259
|
+
addWaitLockOption(refresh).action(withErrorHandling(async (name, options) => {
|
|
260
|
+
await furnaceRefreshCommand(getProjectRoot(), name, withFurnaceLockBudget(options));
|
|
237
261
|
}));
|
|
238
|
-
furnace
|
|
262
|
+
const rename = furnace
|
|
239
263
|
.command('rename <old-name> <new-name>')
|
|
240
264
|
.description('Rename a component (updates files, config, and registrations)')
|
|
241
|
-
.
|
|
242
|
-
|
|
265
|
+
.option('--dry-run', 'Show the planned file moves, furnace.json change and engine registration edits without writing');
|
|
266
|
+
addWaitLockOption(rename).action(withErrorHandling(async (oldName, newName, options) => {
|
|
267
|
+
await furnaceRenameCommand(getProjectRoot(), oldName, newName, withFurnaceLockBudget(options));
|
|
243
268
|
}));
|
|
244
269
|
registerFurnaceSyncCommand(furnace, context);
|
|
245
270
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { stat } from 'node:fs/promises';
|
|
3
3
|
import { basename, dirname, isAbsolute, join, posix } from 'node:path';
|
|
4
4
|
import { text } from '@clack/prompts';
|
|
5
|
-
import { getProjectPaths, loadConfig, mutateConfig,
|
|
5
|
+
import { getProjectPaths, loadConfig, loadRawConfigDocument, mutateConfig, validateConfig, withConfigFileLock, writeConfigDocument, } from '../../core/config.js';
|
|
6
6
|
import { stdioIsInteractive } from '../../core/destructive.js';
|
|
7
7
|
import { createDefaultFurnaceConfig, furnaceConfigExists, writeFurnaceConfig, } from '../../core/furnace-config.js';
|
|
8
8
|
import { DEFAULT_LICENSE } from '../../core/license-headers.js';
|
|
@@ -239,14 +239,21 @@ async function scaffoldTokensCss(projectRoot) {
|
|
|
239
239
|
// `fireforge lint` after a `token add` fails on raw-color-value issues for
|
|
240
240
|
// tokens the operator just created. The add is idempotent, so re-running
|
|
241
241
|
// `furnace init --force` does not duplicate the entry.
|
|
242
|
+
// The edit seeds from the raw document under the config lock, like
|
|
243
|
+
// `fireforge config`: a validated round-trip would drop keys written with
|
|
244
|
+
// `config --force`, and an unlocked one could lose a concurrent write.
|
|
242
245
|
try {
|
|
243
246
|
const existingAllowlist = forgeConfig.patchLint?.rawColorAllowlist ?? [];
|
|
244
247
|
if (!existingAllowlist.includes(tokensCssPath)) {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
248
|
+
await withConfigFileLock(projectRoot, async () => {
|
|
249
|
+
const raw = await loadRawConfigDocument(projectRoot);
|
|
250
|
+
const current = validateConfig(raw).patchLint?.rawColorAllowlist ?? [];
|
|
251
|
+
if (current.includes(tokensCssPath))
|
|
252
|
+
return;
|
|
253
|
+
const updated = mutateConfig(raw, 'patchLint.rawColorAllowlist', [...current, tokensCssPath], true);
|
|
254
|
+
validateConfig(updated);
|
|
255
|
+
await writeConfigDocument(projectRoot, updated);
|
|
256
|
+
});
|
|
250
257
|
info(`Added ${tokensCssPath} to patchLint.rawColorAllowlist`);
|
|
251
258
|
}
|
|
252
259
|
}
|
|
@@ -6,7 +6,7 @@ import { getProjectPaths, loadConfig, loadState } from '../../core/config.js';
|
|
|
6
6
|
import { stdioIsInteractive } from '../../core/destructive.js';
|
|
7
7
|
import { getFurnacePaths, writeFurnaceConfig } from '../../core/furnace-config.js';
|
|
8
8
|
import { resolveFtlDir } from '../../core/furnace-constants.js';
|
|
9
|
-
import { completeJournalRollback, runFurnaceMutation } from '../../core/furnace-operation.js';
|
|
9
|
+
import { completeJournalRollback, runFurnaceMutation, waitLockMutationOptions, } from '../../core/furnace-operation.js';
|
|
10
10
|
import { assertFurnaceEngineReady } from '../../core/furnace-precondition.js';
|
|
11
11
|
import { CUSTOM_ELEMENT_TAG_PATTERN, CUSTOM_ELEMENT_TAG_RULES, } from '../../core/furnace-registration-validate.js';
|
|
12
12
|
import { createRollbackJournal, recordCreatedDir, snapshotFile, } from '../../core/furnace-rollback.js';
|
|
@@ -160,7 +160,7 @@ async function performOverrideMutations(args) {
|
|
|
160
160
|
subject: `component "${args.componentName}"`,
|
|
161
161
|
});
|
|
162
162
|
}
|
|
163
|
-
});
|
|
163
|
+
}, waitLockMutationOptions(args.waitLockSeconds));
|
|
164
164
|
}
|
|
165
165
|
/**
|
|
166
166
|
* Throws if `componentName` is already classified as something `override`
|
|
@@ -322,6 +322,7 @@ export async function furnaceOverrideCommand(projectRoot, name, options = {}) {
|
|
|
322
322
|
ftlDir,
|
|
323
323
|
firefoxVersion: forgeConfig.firefox.version,
|
|
324
324
|
...(state.baseCommit ? { baseCommit: state.baseCommit } : {}),
|
|
325
|
+
waitLockSeconds: options.waitLockSeconds,
|
|
325
326
|
});
|
|
326
327
|
// --- Success ---
|
|
327
328
|
note(`Files copied to components/overrides/${componentName}/:\n` +
|
|
@@ -421,6 +422,7 @@ export async function furnaceBatchOverrideCommand(projectRoot, names, options =
|
|
|
421
422
|
ftlDir,
|
|
422
423
|
firefoxVersion: forgeConfig.firefox.version,
|
|
423
424
|
...(state.baseCommit ? { baseCommit: state.baseCommit } : {}),
|
|
425
|
+
waitLockSeconds: options.waitLockSeconds,
|
|
424
426
|
});
|
|
425
427
|
succeeded.push(componentName);
|
|
426
428
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { applyAllComponents } from '../../core/furnace-apply.js';
|
|
4
4
|
import { updateFurnaceState } from '../../core/furnace-config.js';
|
|
5
|
-
import { runFurnaceMutation } from '../../core/furnace-operation.js';
|
|
5
|
+
import { runFurnaceMutation, waitLockMutationOptions, } from '../../core/furnace-operation.js';
|
|
6
6
|
import { assertFurnaceReady } from '../../core/furnace-precondition.js';
|
|
7
7
|
import { restoreRollbackJournal } from '../../core/furnace-rollback.js';
|
|
8
8
|
import { countEntriesWithBlockingStepErrors } from '../../core/furnace-step-errors.js';
|
|
@@ -224,7 +224,7 @@ export async function furnacePreviewCommand(projectRoot, options = {}) {
|
|
|
224
224
|
// entrypoint's global signal handlers consulting the lifecycle registry.
|
|
225
225
|
// The body's own try/catch + teardown path handles the normal exit case
|
|
226
226
|
// (mach storybook returns or throws).
|
|
227
|
-
|
|
227
|
+
const mutate = async (ctx) => {
|
|
228
228
|
// Register the stories cleanup as an extra teardown hook so the signal
|
|
229
229
|
// handler can wipe the staged stories directory in addition to the
|
|
230
230
|
// journal restore.
|
|
@@ -336,7 +336,8 @@ export async function furnacePreviewCommand(projectRoot, options = {}) {
|
|
|
336
336
|
// holds and downstream formatters always see a message/stack pair.
|
|
337
337
|
throw toError(primaryError);
|
|
338
338
|
}
|
|
339
|
-
}
|
|
339
|
+
};
|
|
340
|
+
await runFurnaceMutation(projectRoot, 'preview-teardown', mutate, waitLockMutationOptions(options.waitLockSeconds));
|
|
340
341
|
if (previewResult &&
|
|
341
342
|
previewResult.exitCode !== 0 &&
|
|
342
343
|
previewResult.exitCode !== 130 &&
|
|
@@ -6,7 +6,7 @@ import { getOverrideEngineTargetPath } from '../../core/furnace-apply-helpers.js
|
|
|
6
6
|
import { getFurnacePaths, loadFurnaceConfig, writeFurnaceConfig, } from '../../core/furnace-config.js';
|
|
7
7
|
import { resolveFtlDir } from '../../core/furnace-constants.js';
|
|
8
8
|
import { isComponentSourceFile } from '../../core/furnace-constants.js';
|
|
9
|
-
import { completeJournalRollback, runFurnaceMutation } from '../../core/furnace-operation.js';
|
|
9
|
+
import { completeJournalRollback, runFurnaceMutation, waitLockMutationOptions, } from '../../core/furnace-operation.js';
|
|
10
10
|
import { assertFurnaceEngineReady } from '../../core/furnace-precondition.js';
|
|
11
11
|
import { refreshOverrideFile } from '../../core/furnace-refresh.js';
|
|
12
12
|
import { createRollbackJournal, restoreRollbackJournal, snapshotDir, snapshotFile, } from '../../core/furnace-rollback.js';
|
|
@@ -165,7 +165,7 @@ async function refreshSingleOverride(projectRoot, name, options = {}) {
|
|
|
165
165
|
}
|
|
166
166
|
throw error;
|
|
167
167
|
}
|
|
168
|
-
}, { dryRun });
|
|
168
|
+
}, { dryRun, ...waitLockMutationOptions(options.waitLockSeconds) });
|
|
169
169
|
return { results, currentVersion };
|
|
170
170
|
}
|
|
171
171
|
/**
|