@hominis/fireforge 0.49.2 → 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 +40 -1
- 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.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 +1 -1
|
@@ -15,15 +15,26 @@ import { mapWithConcurrency } from '../utils/concurrency.js';
|
|
|
15
15
|
import { pathExists } from '../utils/fs.js';
|
|
16
16
|
import { cancel, info, isCancel, warn } from '../utils/logger.js';
|
|
17
17
|
import { isContainedRelativePath, normalizePathSlashes, stripEnginePrefix, } from '../utils/paths.js';
|
|
18
|
+
import { formatForwardImportRemedy } from './forward-import-remedy.js';
|
|
19
|
+
import { joinScanFileOwner, splitScanFileOwner } from './re-export-scan-owner.js';
|
|
18
20
|
const SCAN_ADD_COUNT_THRESHOLD = 3;
|
|
19
21
|
/** Concurrency bound for existence probes (matches the classify/lint pools). */
|
|
20
22
|
const PATH_PROBE_CONCURRENCY = 8;
|
|
21
23
|
const SCAN_DIR_COUNT_THRESHOLD = 2;
|
|
22
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Normalizes repeatable `--scan-file` inputs into safe engine-relative
|
|
26
|
+
* paths, keeping an optional `<patch>:` owner prefix intact.
|
|
27
|
+
*/
|
|
23
28
|
export function normalizeScanFiles(scanFiles) {
|
|
24
|
-
const normalized = [
|
|
25
|
-
.map((file) =>
|
|
26
|
-
|
|
29
|
+
const normalized = [
|
|
30
|
+
...new Set((scanFiles ?? []).map((file) => {
|
|
31
|
+
const spec = splitScanFileOwner(file);
|
|
32
|
+
return joinScanFileOwner({
|
|
33
|
+
owner: spec.owner,
|
|
34
|
+
path: normalizeEngineRelativeInput(spec.path, '--scan-file'),
|
|
35
|
+
});
|
|
36
|
+
})),
|
|
37
|
+
].sort();
|
|
27
38
|
return normalized.length > 0 ? normalized : undefined;
|
|
28
39
|
}
|
|
29
40
|
/** Normalizes one CLI-provided path into a safe engine-relative path. */
|
|
@@ -171,7 +182,7 @@ function scanAdditionsNeedConfirmation(added) {
|
|
|
171
182
|
* (all adopt through the same scan result), including dry-run.
|
|
172
183
|
*/
|
|
173
184
|
export async function assertScanAdoptionsHaveNoForwardImports(args) {
|
|
174
|
-
const { patchesDir, engineDir, patchFilename, added } = args;
|
|
185
|
+
const { patchesDir, engineDir, patchFilename, added, config } = args;
|
|
175
186
|
if (added.length === 0)
|
|
176
187
|
return;
|
|
177
188
|
const candidateDiff = await getDiffForFilesAgainstHead(engineDir, [...added]);
|
|
@@ -180,7 +191,7 @@ export async function assertScanAdoptionsHaveNoForwardImports(args) {
|
|
|
180
191
|
const candidateNewFiles = buildNewFileTextProjection(candidateDiff);
|
|
181
192
|
const candidateCreations = detectNewFilesInDiff(candidateDiff);
|
|
182
193
|
const candidateAdditions = buildModifiedFileAdditionsFromDiff(candidateDiff);
|
|
183
|
-
const baseCtx = await buildPatchQueueContext(patchesDir);
|
|
194
|
+
const baseCtx = await buildPatchQueueContext(patchesDir, config);
|
|
184
195
|
const projectedEntries = baseCtx.entries.map((entry) => {
|
|
185
196
|
if (entry.filename !== patchFilename)
|
|
186
197
|
return entry;
|
|
@@ -193,27 +204,49 @@ export async function assertScanAdoptionsHaveNoForwardImports(args) {
|
|
|
193
204
|
};
|
|
194
205
|
});
|
|
195
206
|
const baselineIssues = lintPatchQueue(baseCtx).filter((i) => i.severity === 'error');
|
|
196
|
-
const projectedIssues = lintPatchQueue({ entries: projectedEntries }).filter((i) => i.severity === 'error');
|
|
207
|
+
const projectedIssues = lintPatchQueue({ ...baseCtx, entries: projectedEntries }).filter((i) => i.severity === 'error');
|
|
197
208
|
const addedSet = new Set(added);
|
|
198
|
-
const
|
|
199
|
-
|
|
209
|
+
const regressions = computeProjectedLintRegressions(baselineIssues, projectedIssues);
|
|
210
|
+
const offending = regressions.filter((issue) => issue.check === 'forward-import' && addedSet.has(issue.file));
|
|
211
|
+
// A forward registration (jar.mn packaging, a test manifest, ...) only
|
|
212
|
+
// regresses here because the candidates moved the registered file's
|
|
213
|
+
// creation to this patch, or because a candidate registers it: either way
|
|
214
|
+
// the adoption caused it, and the operator should declare it in the same
|
|
215
|
+
// pass as the import instead of meeting it later in the queue audit.
|
|
216
|
+
const registrations = regressions.filter((issue) => issue.check === 'forward-registration');
|
|
217
|
+
if (offending.length === 0 && registrations.length === 0)
|
|
200
218
|
return;
|
|
201
219
|
const details = offending.map((issue) => ` - ${issue.file}: ${issue.message}`).join('\n');
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
220
|
+
const registrationDetails = registrations
|
|
221
|
+
.map((issue) => ` - ${issue.file}: ${issue.message}`)
|
|
222
|
+
.join('\n');
|
|
223
|
+
const parts = [];
|
|
224
|
+
if (offending.length > 0) {
|
|
225
|
+
parts.push(`${offending.length} scanned file${offending.length === 1 ? '' : 's'} import modules created by later patches:\n${details}`);
|
|
226
|
+
}
|
|
227
|
+
if (registrations.length > 0) {
|
|
228
|
+
parts.push(`${registrations.length} forward registration${registrations.length === 1 ? '' : 's'} the adoption would leave dangling:\n${registrationDetails}`);
|
|
229
|
+
}
|
|
230
|
+
throw new GeneralError(`Refusing to adopt scanned files into ${patchFilename}: ${parts.join('\n')}\n` +
|
|
231
|
+
formatForwardImportRemedy({
|
|
232
|
+
reRunHint: 're-running the scan',
|
|
233
|
+
issues: [...offending, ...registrations],
|
|
234
|
+
manifest: manifestFromQueue(baseCtx),
|
|
235
|
+
...(args.config !== undefined ? { config: args.config } : {}),
|
|
236
|
+
...categoryOf(baseCtx, patchFilename),
|
|
237
|
+
}));
|
|
205
238
|
}
|
|
206
|
-
/**
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
239
|
+
/** The manifest rows a queue context carries, for remedy computations. */
|
|
240
|
+
export function manifestFromQueue(ctx) {
|
|
241
|
+
return {
|
|
242
|
+
version: 1,
|
|
243
|
+
patches: ctx.entries.flatMap((entry) => (entry.metadata ? [entry.metadata] : [])),
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
/** `{ category }` of the named queue entry, or `{}` when it has no metadata. */
|
|
247
|
+
export function categoryOf(ctx, filename) {
|
|
248
|
+
const category = ctx.entries.find((entry) => entry.filename === filename)?.metadata?.category;
|
|
249
|
+
return category !== undefined ? { category } : {};
|
|
217
250
|
}
|
|
218
251
|
/** Refuses explicit `--scan-file` additions that did not produce patch hunks. */
|
|
219
252
|
export function assertScanFileAdditionsHaveDiffHunks(args) {
|
|
@@ -10,6 +10,7 @@ import { enforceFreshFurnaceSources } from '../core/furnace-stale-export.js';
|
|
|
10
10
|
import { getDiffForFilesAgainstHead } from '../core/git-diff.js';
|
|
11
11
|
import { listTrackedInHead } from '../core/git-file-ops.js';
|
|
12
12
|
import { updatePatchAndMetadata } from '../core/patch-export.js';
|
|
13
|
+
import { buildLintIgnoreChecks, lintIgnoreRule, mergeLintIgnoreIds, } from '../core/patch-lint-ignore.js';
|
|
13
14
|
import { buildProjectedManifest, enforcePatchPolicy } from '../core/patch-policy.js';
|
|
14
15
|
import { isGitIndexLockConflict } from '../errors/git.js';
|
|
15
16
|
import { info, success, warn } from '../utils/logger.js';
|
|
@@ -42,7 +43,9 @@ export async function reExportSinglePatchWithIndexLockRetry(args) {
|
|
|
42
43
|
async function reExportSinglePatch(args) {
|
|
43
44
|
const { patch, paths, manifest, options, isDryRun, config, adjacentCtx, driftCtx, lintCtx } = args;
|
|
44
45
|
const removed = new Set(options.expectRemoved ?? []);
|
|
45
|
-
|
|
46
|
+
const retainedFiles = patch.filesAffected.filter((file) => !removed.has(file));
|
|
47
|
+
let currentFilesAffected = retainedFiles;
|
|
48
|
+
let adoptedFiles = [];
|
|
46
49
|
for (const file of patch.filesAffected.filter((path) => removed.has(path)))
|
|
47
50
|
info(` - ${file} (--expect-removed)`);
|
|
48
51
|
// --- Scan for new/removed files ---
|
|
@@ -65,6 +68,7 @@ async function reExportSinglePatch(args) {
|
|
|
65
68
|
engineDir: paths.engine,
|
|
66
69
|
patchFilename: patch.filename,
|
|
67
70
|
added: scanResult.added,
|
|
71
|
+
config,
|
|
68
72
|
});
|
|
69
73
|
if (options.scanFiles === undefined) {
|
|
70
74
|
const isInteractive = stdioIsInteractive();
|
|
@@ -80,6 +84,7 @@ async function reExportSinglePatch(args) {
|
|
|
80
84
|
}
|
|
81
85
|
}
|
|
82
86
|
currentFilesAffected = scanResult.updated;
|
|
87
|
+
adoptedFiles = scanResult.added;
|
|
83
88
|
}
|
|
84
89
|
else if (options.files === undefined) {
|
|
85
90
|
// When neither `--scan` nor `--files` is set and some of the manifest's
|
|
@@ -153,13 +158,17 @@ async function reExportSinglePatch(args) {
|
|
|
153
158
|
// payload lines are about to enter the body that the old body did not
|
|
154
159
|
// carry, the case where a concurrent session's uncommitted edits in
|
|
155
160
|
// owned files would be silently absorbed. Always previews (including
|
|
156
|
-
// dry-run and --scan). Refuses only under --refuse-foreign-drift.
|
|
161
|
+
// dry-run and --scan). Refuses only under --refuse-foreign-drift. Covers
|
|
162
|
+
// the files the patch already owned; adopted and retired files are the
|
|
163
|
+
// run's stated intent, not drift.
|
|
157
164
|
if (await reportForeignDrift({
|
|
158
165
|
patch,
|
|
159
166
|
patchesDir: paths.patches,
|
|
160
167
|
engineDir: paths.engine,
|
|
161
168
|
newDiffContent: diffContent,
|
|
162
169
|
ctx: driftCtx,
|
|
170
|
+
ownedFiles: retainedFiles,
|
|
171
|
+
adoptedFiles,
|
|
163
172
|
})) {
|
|
164
173
|
return false;
|
|
165
174
|
}
|
|
@@ -228,6 +237,13 @@ function enforceProjectedManifestPolicy(args) {
|
|
|
228
237
|
manifest: buildProjectedManifest(manifest, manifest.patches.map((entry) => entry.filename === patch.filename ? { ...entry, ...updates } : entry)),
|
|
229
238
|
command: 're-export',
|
|
230
239
|
forceUnsafe: options.forceUnsafe === true,
|
|
240
|
+
// A re-export changes only this row's contents, so only its findings
|
|
241
|
+
// can be new; another patch's stale row must not fail this one.
|
|
242
|
+
scope: [patch.filename],
|
|
243
|
+
hints: {
|
|
244
|
+
'reserved-files': 'A path whose engine copy is back at HEAD can be dropped from the patch with ' +
|
|
245
|
+
'--expect-removed <path>; any other path needs its reserved allowlist entry.',
|
|
246
|
+
},
|
|
231
247
|
});
|
|
232
248
|
}
|
|
233
249
|
/**
|
|
@@ -252,6 +268,7 @@ async function commitRefreshedPatch(args) {
|
|
|
252
268
|
config,
|
|
253
269
|
command: 're-export',
|
|
254
270
|
forceUnsafe: options.forceUnsafe === true,
|
|
271
|
+
scope: [patch.filename],
|
|
255
272
|
},
|
|
256
273
|
});
|
|
257
274
|
refreshQueueCtxEntry(lintCtx, patch.filename, diffContent);
|
|
@@ -289,10 +306,9 @@ async function commitRefreshedPatch(args) {
|
|
|
289
306
|
* pass so the new intent takes effect on this invocation.
|
|
290
307
|
*/
|
|
291
308
|
function computeReExportUpdates(patch, options, currentFilesAffected) {
|
|
292
|
-
const existingIgnoreSet = new Set(patch.lintIgnore ?? []);
|
|
309
|
+
const existingIgnoreSet = new Set((patch.lintIgnore ?? []).map(lintIgnoreRule));
|
|
293
310
|
const flagIgnoreSet = new Set(options.lintIgnore ?? []);
|
|
294
|
-
const
|
|
295
|
-
const effectiveLintIgnore = mergedIgnoreSet.size > 0 ? [...mergedIgnoreSet] : undefined;
|
|
311
|
+
const effectiveLintIgnore = mergeLintIgnoreIds(patch.lintIgnore, [...flagIgnoreSet]);
|
|
296
312
|
const updates = {
|
|
297
313
|
filesAffected: currentFilesAffected,
|
|
298
314
|
};
|
|
@@ -304,7 +320,7 @@ function computeReExportUpdates(patch, options, currentFilesAffected) {
|
|
|
304
320
|
}
|
|
305
321
|
return {
|
|
306
322
|
updates,
|
|
307
|
-
ignoreChecks:
|
|
323
|
+
ignoreChecks: buildLintIgnoreChecks(effectiveLintIgnore),
|
|
308
324
|
effectiveTier: options.tier ?? patch.tier,
|
|
309
325
|
existingIgnoreSet,
|
|
310
326
|
flagIgnoreSet,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one-line end-of-run summary of a multi-patch `re-export`.
|
|
3
|
+
*
|
|
4
|
+
* A long run interleaves per-patch lint output, drift previews and
|
|
5
|
+
* refusals, and the patches it wrote were easy to confuse with the one it
|
|
6
|
+
* did not. The summary names both sets and why each unwritten patch was
|
|
7
|
+
* not written, so the re-run list is one line to read.
|
|
8
|
+
*/
|
|
9
|
+
/** What became of each selected patch. */
|
|
10
|
+
export interface ReExportRunTally {
|
|
11
|
+
/** Selected patches, in run order. */
|
|
12
|
+
selected: readonly string[];
|
|
13
|
+
/** Patches written (or, in a dry run, that would be written). */
|
|
14
|
+
written: readonly string[];
|
|
15
|
+
/** Patches whose re-export threw. */
|
|
16
|
+
failed: readonly string[];
|
|
17
|
+
/** Patches refused by `--refuse-adjacent-unmanaged`. */
|
|
18
|
+
refusedAdjacent: readonly string[];
|
|
19
|
+
/** Patches refused by `--refuse-foreign-drift`. */
|
|
20
|
+
refusedDrift: readonly string[];
|
|
21
|
+
/** Whether the run is a dry run. */
|
|
22
|
+
isDryRun: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Renders the summary, or undefined for a single-patch run, where the
|
|
26
|
+
* outcome line already says everything.
|
|
27
|
+
*/
|
|
28
|
+
export declare function formatReExportRunSummary(tally: ReExportRunTally): string | undefined;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* The one-line end-of-run summary of a multi-patch `re-export`.
|
|
4
|
+
*
|
|
5
|
+
* A long run interleaves per-patch lint output, drift previews and
|
|
6
|
+
* refusals, and the patches it wrote were easy to confuse with the one it
|
|
7
|
+
* did not. The summary names both sets and why each unwritten patch was
|
|
8
|
+
* not written, so the re-run list is one line to read.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Renders the summary, or undefined for a single-patch run, where the
|
|
12
|
+
* outcome line already says everything.
|
|
13
|
+
*/
|
|
14
|
+
export function formatReExportRunSummary(tally) {
|
|
15
|
+
if (tally.selected.length <= 1)
|
|
16
|
+
return undefined;
|
|
17
|
+
const written = new Set(tally.written);
|
|
18
|
+
const reasons = new Map();
|
|
19
|
+
for (const name of tally.failed)
|
|
20
|
+
reasons.set(name, 'failed');
|
|
21
|
+
for (const name of tally.refusedAdjacent)
|
|
22
|
+
reasons.set(name, 'refused: adjacent unmanaged');
|
|
23
|
+
for (const name of tally.refusedDrift)
|
|
24
|
+
reasons.set(name, 'refused: foreign drift');
|
|
25
|
+
const notWritten = tally.selected
|
|
26
|
+
.filter((name) => !written.has(name))
|
|
27
|
+
.map((name) => `${name} (${reasons.get(name) ?? 'skipped'})`);
|
|
28
|
+
const verb = tally.isDryRun ? 'would write' : 'wrote';
|
|
29
|
+
const writtenPart = tally.written.length > 0
|
|
30
|
+
? `${verb} ${tally.written.length} of ${tally.selected.length}: ${tally.written.join(', ')}`
|
|
31
|
+
: `${verb} 0 of ${tally.selected.length}`;
|
|
32
|
+
const notWrittenPart = notWritten.length > 0
|
|
33
|
+
? `; ${tally.isDryRun ? 'would NOT write' : 'NOT written'}: ${notWritten.join(', ')}`
|
|
34
|
+
: '';
|
|
35
|
+
return `Re-export summary: ${writtenPart}${notWrittenPart}.`;
|
|
36
|
+
}
|
|
@@ -10,6 +10,7 @@ import { GeneralError, InvalidArgumentError } from '../errors/base.js';
|
|
|
10
10
|
import { elapsedSince } from '../utils/elapsed.js';
|
|
11
11
|
import { toError } from '../utils/errors.js';
|
|
12
12
|
import { cancel, info, intro, isCancel, outro, spinner, success, warn, } from '../utils/logger.js';
|
|
13
|
+
import { ADJACENT_MEANS, buildAdjacentUnmanagedContext, reportAdjacentCarveOutUsage, } from './re-export-adjacent.js';
|
|
13
14
|
import { loadScanFilesAssignments, withDryRunPurityGuard, withDryRunReExportLock, } from './re-export-bulk-scan.js';
|
|
14
15
|
import { warnUnseenExpectedDrift } from './re-export-drift.js';
|
|
15
16
|
import { reExportFilesInPlace } from './re-export-files.js';
|
|
@@ -17,7 +18,9 @@ import { buildReExportLintContext, finishReExportLint } from './re-export-lint.j
|
|
|
17
18
|
import { applyReExportFilesPositionalFolding, validateReExportOptionCombinations, } from './re-export-options.js';
|
|
18
19
|
import { validateExpectedRemovals } from './re-export-removed.js';
|
|
19
20
|
import { normalizeEngineRelativeInput, normalizeScanFiles } from './re-export-scan.js';
|
|
21
|
+
import { assignScanFileOwners, scanOptionsForPatch } from './re-export-scan-owner.js';
|
|
20
22
|
import { reExportSinglePatchWithIndexLockRetry } from './re-export-single.js';
|
|
23
|
+
import { formatReExportRunSummary } from './re-export-summary.js';
|
|
21
24
|
async function resolveSelectedPatches(patches, options, manifest) {
|
|
22
25
|
if (options.all) {
|
|
23
26
|
return [...manifest.patches];
|
|
@@ -73,7 +76,9 @@ export async function reExportCommand(projectRoot, patches, options) {
|
|
|
73
76
|
options = cleanedOptions;
|
|
74
77
|
}
|
|
75
78
|
const isDryRun = options.dryRun === true;
|
|
76
|
-
|
|
79
|
+
// The banner says which mode is in force: a path flag that swallowed
|
|
80
|
+
// `--dry-run` as its value once turned a preview into a write unnoticed.
|
|
81
|
+
intro(isDryRun ? 'FireForge Re-export (DRY RUN)' : 'FireForge Re-export (WRITING)');
|
|
77
82
|
// Accept export-style space-separated paths after --files by folding
|
|
78
83
|
// path-shaped extra positionals into the file list.
|
|
79
84
|
({ patches, options } = applyReExportFilesPositionalFolding(patches, options));
|
|
@@ -94,6 +99,7 @@ export async function reExportCommand(projectRoot, patches, options) {
|
|
|
94
99
|
: await resolveSelectedPatches(patches, options, manifest);
|
|
95
100
|
if (!selectedPatches)
|
|
96
101
|
return;
|
|
102
|
+
const scanAssignments = scanFilesByPatch ?? assignScanFileOwners(options.scanFiles, selectedPatches, manifest);
|
|
97
103
|
if (selectedPatches.length === 0) {
|
|
98
104
|
warn('No patches selected');
|
|
99
105
|
outro('Nothing to re-export');
|
|
@@ -125,14 +131,7 @@ export async function reExportCommand(projectRoot, patches, options) {
|
|
|
125
131
|
const config = await loadConfig(projectRoot);
|
|
126
132
|
// Classification inputs for the scan-less adjacency advisory, computed
|
|
127
133
|
// once for the whole run.
|
|
128
|
-
const adjacentCtx =
|
|
129
|
-
binaryName: config.binaryName,
|
|
130
|
-
furnacePrefixes: await collectFurnaceManagedPrefixes(paths.root),
|
|
131
|
-
refuseAdjacentUnmanaged: options.refuseAdjacentUnmanaged === true,
|
|
132
|
-
approvedUnmanaged: new Set((options.expectUnmanaged ?? []).map((file) => normalizeEngineRelativeInput(file, '--expect-unmanaged'))),
|
|
133
|
-
approvedSeen: new Set(),
|
|
134
|
-
refusals: [],
|
|
135
|
-
};
|
|
134
|
+
const adjacentCtx = buildAdjacentUnmanagedContext(options, config.binaryName, await collectFurnaceManagedPrefixes(paths.root));
|
|
136
135
|
const driftCtx = buildForeignDriftContext(options);
|
|
137
136
|
// Hoisted lint context, one per run: queue context + checkJs
|
|
138
137
|
// program + per-patch result cache, shared across every loop iteration.
|
|
@@ -146,8 +145,7 @@ export async function reExportCommand(projectRoot, patches, options) {
|
|
|
146
145
|
const progress = spinner('Preparing re-export...');
|
|
147
146
|
await withDryRunReExportLock(paths.fireforgeDir, isDryRun, async () => withDryRunPurityGuard(paths.engine, paths.patches, isDryRun, async () => {
|
|
148
147
|
for (const [index, patch] of selectedPatches.entries()) {
|
|
149
|
-
const
|
|
150
|
-
const patchOptions = assignedScanFiles !== undefined ? { ...options, scanFiles: assignedScanFiles } : options;
|
|
148
|
+
const patchOptions = scanOptionsForPatch(options, patch.filename, scanAssignments);
|
|
151
149
|
progress.message(`Re-exporting ${index + 1}/${selectedPatches.length}: ${patch.filename} (${patch.filesAffected.length} file(s), ${elapsedSince(startedAt)} elapsed)...`);
|
|
152
150
|
try {
|
|
153
151
|
const exported = await reExportSinglePatchWithIndexLockRetry({
|
|
@@ -176,6 +174,16 @@ export async function reExportCommand(projectRoot, patches, options) {
|
|
|
176
174
|
await finishReExportLint(lintCtx);
|
|
177
175
|
warnUnseenExpectedDrift(driftCtx);
|
|
178
176
|
warnUnseenApprovedUnmanaged(adjacentCtx);
|
|
177
|
+
const summary = formatReExportRunSummary({
|
|
178
|
+
selected: selectedPatches.map((patch) => patch.filename),
|
|
179
|
+
written: reExportedFilenames,
|
|
180
|
+
failed: failedFilenames,
|
|
181
|
+
refusedAdjacent: adjacentCtx.refusals.map((r) => r.patchFilename),
|
|
182
|
+
refusedDrift: driftCtx.refusals.map((r) => r.patchFilename),
|
|
183
|
+
isDryRun,
|
|
184
|
+
});
|
|
185
|
+
if (summary !== undefined)
|
|
186
|
+
info(summary);
|
|
179
187
|
throwRunLevelRefusals(adjacentCtx, driftCtx, progress);
|
|
180
188
|
if (reExported === 0 && selectedPatches.length > 0) {
|
|
181
189
|
progress.error('Re-export failed');
|
|
@@ -198,6 +206,7 @@ export async function reExportCommand(projectRoot, patches, options) {
|
|
|
198
206
|
selectedCount: selectedPatches.length,
|
|
199
207
|
reExportedFilenames,
|
|
200
208
|
failedFilenames,
|
|
209
|
+
selectedFilenames: selectedPatches.map((patch) => patch.filename),
|
|
201
210
|
shouldStamp,
|
|
202
211
|
stampRequested: options.stamp === true,
|
|
203
212
|
config,
|
|
@@ -224,9 +233,11 @@ function buildForeignDriftContext(options) {
|
|
|
224
233
|
* admit, and reads as an approval that is in force when it is not.
|
|
225
234
|
* A warning rather than a refusal: an approved path legitimately goes unmet
|
|
226
235
|
* when the reviewed file has since been adopted into a patch, which is the
|
|
227
|
-
* outcome the carve-out exists to be retired by.
|
|
236
|
+
* outcome the carve-out exists to be retired by. The `--expect-new` and
|
|
237
|
+
* `--adjacent-ignore` accounting is reported first, alongside.
|
|
228
238
|
*/
|
|
229
239
|
function warnUnseenApprovedUnmanaged(adjacentCtx) {
|
|
240
|
+
reportAdjacentCarveOutUsage(adjacentCtx);
|
|
230
241
|
const unseen = [...adjacentCtx.approvedUnmanaged].filter((file) => !adjacentCtx.approvedSeen.has(file));
|
|
231
242
|
if (unseen.length === 0)
|
|
232
243
|
return;
|
|
@@ -244,17 +255,18 @@ function warnUnseenApprovedUnmanaged(adjacentCtx) {
|
|
|
244
255
|
function throwRunLevelRefusals(adjacentCtx, driftCtx, progress) {
|
|
245
256
|
if (adjacentCtx.refusals.length > 0) {
|
|
246
257
|
progress.error('Re-export refused');
|
|
247
|
-
// Each offender is named with the owned directory
|
|
248
|
-
// adjacent: an unattended run reads only this
|
|
249
|
-
// anchor it cannot tell which of a
|
|
250
|
-
// the file
|
|
258
|
+
// Each offender is named with the owned directory (or registering
|
|
259
|
+
// carrier) that made it adjacent: an unattended run reads only this
|
|
260
|
+
// line, and without the anchor it cannot tell which of a
|
|
261
|
+
// multi-directory patch's locations the file belongs to.
|
|
251
262
|
const offenders = adjacentCtx.refusals
|
|
252
263
|
.map((r) => `${r.patchFilename} (${r.anchored.join(', ')})`)
|
|
253
264
|
.join('; ');
|
|
254
265
|
throw new GeneralError(`Refused ${adjacentCtx.refusals.length} patch(es) with adjacent unmanaged files ` +
|
|
255
|
-
`(--refuse-adjacent-unmanaged): ${offenders}.
|
|
256
|
-
`
|
|
257
|
-
|
|
266
|
+
`(--refuse-adjacent-unmanaged): ${offenders}. ${ADJACENT_MEANS} ` +
|
|
267
|
+
`Adopt reviewed files with --scan --scan-file, or export them as a new patch ` +
|
|
268
|
+
`(name a file your next export adopts with --expect-new, and a reviewed, deliberately ` +
|
|
269
|
+
`unmanaged file with --expect-unmanaged or --adjacent-ignore).`);
|
|
258
270
|
}
|
|
259
271
|
if (driftCtx.refusals.length > 0) {
|
|
260
272
|
progress.error('Re-export refused');
|
|
@@ -263,11 +275,11 @@ function throwRunLevelRefusals(adjacentCtx, driftCtx, progress) {
|
|
|
263
275
|
const unreadablePart = unreadable.length > 0
|
|
264
276
|
? ` ${unreadable.length} of these refused because the old patch body was missing or unreadable, so the drift comparison could not run (fail-closed).`
|
|
265
277
|
: '';
|
|
266
|
-
throw new GeneralError(`Refused ${driftCtx.refusals.length} patch(es) whose refreshed body would
|
|
267
|
-
`
|
|
278
|
+
throw new GeneralError(`Refused ${driftCtx.refusals.length} patch(es) whose refreshed body would add or drop ` +
|
|
279
|
+
`lines relative to the old patch body (--refuse-foreign-drift): ${names}.${unreadablePart}\n\n` +
|
|
268
280
|
'The flag cannot tell WHO wrote those lines, and the two populations have opposite ' +
|
|
269
281
|
'remedies:\n' +
|
|
270
|
-
' - Lines you
|
|
282
|
+
' - Lines you changed yourself since the last export (the per-file report tags these ' +
|
|
271
283
|
'"edited since your last export"): name them with --expect <path> so this run captures ' +
|
|
272
284
|
'them, or re-run without the flag to capture every drifting file.\n' +
|
|
273
285
|
" - Lines another session owns: do NOT proceed — commit or stash that session's work " +
|
|
@@ -305,9 +317,10 @@ function reportReExportOutcome(args) {
|
|
|
305
317
|
outro('Re-export complete');
|
|
306
318
|
}
|
|
307
319
|
if (reExported < selectedCount) {
|
|
308
|
-
const skippedCount = selectedCount - reExported - args.failedFilenames.length;
|
|
309
320
|
const failedPart = args.failedFilenames.length > 0 ? ` Failed: ${args.failedFilenames.join(', ')}.` : '';
|
|
310
|
-
const
|
|
321
|
+
const accounted = new Set([...args.reExportedFilenames, ...args.failedFilenames]);
|
|
322
|
+
const skipped = args.selectedFilenames.filter((name) => !accounted.has(name));
|
|
323
|
+
const skippedPart = skipped.length > 0 ? ` Skipped: ${skipped.join(', ')} (see warnings above).` : '';
|
|
311
324
|
throw new GeneralError(`${isDryRun ? '[dry-run] Would re-export' : 'Re-exported'} only ${reExported} of ` +
|
|
312
325
|
`${selectedCount} selected patch(es).${failedPart}${skippedPart} ` +
|
|
313
326
|
'Partial re-export exits non-zero so it cannot pass as success in scripts; ' +
|
|
@@ -19,12 +19,13 @@ import { discoverPatches } from '../../core/patch-files.js';
|
|
|
19
19
|
import { loadPatchesManifest } from '../../core/patch-manifest.js';
|
|
20
20
|
import { getPatchSourceVersion } from '../../core/patch-source-metadata.js';
|
|
21
21
|
import { replayQueueIndexOnly } from '../../core/rebase-dry-run.js';
|
|
22
|
+
import { formatPatchStampSpread, readCommittedFirefoxPin, resolveRebaseFromVersion, tallyPatchStamps, } from '../../core/rebase-from-version.js';
|
|
22
23
|
import { getRebaseSessionPath, readRebaseSession, saveRebaseSession, } from '../../core/rebase-session.js';
|
|
23
24
|
import { GeneralError } from '../../errors/base.js';
|
|
24
25
|
import { CorruptRebaseSessionError, RebaseDryRunRejectError, RebaseSessionExistsError, } from '../../errors/rebase.js';
|
|
25
26
|
import { info, intro, outro, spinner, warn } from '../../utils/logger.js';
|
|
26
27
|
import { addWaitLockOption, commanderArgParser, pickDefined, resolveWaitLockSeconds, } from '../../utils/options.js';
|
|
27
|
-
import {
|
|
28
|
+
import { parsePositiveIntegerFlag } from '../../utils/validation.js';
|
|
28
29
|
import { handleAbort } from './abort.js';
|
|
29
30
|
import { confirmDirtyEngineReset } from './confirm.js';
|
|
30
31
|
import { handleContinue } from './continue.js';
|
|
@@ -58,27 +59,26 @@ async function handleFreshStart(projectRoot, options) {
|
|
|
58
59
|
if (!manifest || manifest.patches.length === 0) {
|
|
59
60
|
throw new GeneralError('No patches found in manifest. Nothing to rebase.');
|
|
60
61
|
}
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const fromVersion = sortedVersions[0] ?? currentVersion;
|
|
72
|
-
const fromProduct = manifest.patches.find((p) => getPatchSourceVersion(p) === fromVersion)?.sourceProduct ??
|
|
73
|
-
config.firefox.product;
|
|
74
|
-
if (versionCounts.size === 1 && fromVersion === currentVersion) {
|
|
62
|
+
// The queue can carry several stamps: patches re-exported since the last
|
|
63
|
+
// hop are stamped newer than untouched ones. "Nothing to rebase" is a fact
|
|
64
|
+
// about the stamps; the "from" label is the previous pin (the committed
|
|
65
|
+
// `firefox.version`), with the oldest stamp only as the fallback.
|
|
66
|
+
const spread = tallyPatchStamps(manifest.patches.map((p) => getPatchSourceVersion(p)));
|
|
67
|
+
const oldestStamp = spread.versions[0] ?? currentVersion;
|
|
68
|
+
const from = resolveRebaseFromVersion(currentVersion, await readCommittedFirefoxPin(projectRoot), spread, manifest.patches.find((p) => getPatchSourceVersion(p) === oldestStamp)?.sourceProduct);
|
|
69
|
+
const fromVersion = from.version;
|
|
70
|
+
const fromProduct = from.product ?? config.firefox.product;
|
|
71
|
+
if (spread.versions.length === 1 && oldestStamp === currentVersion) {
|
|
75
72
|
info('All patches already match the current Firefox version. Nothing to rebase.');
|
|
76
73
|
outro('Rebase not needed');
|
|
77
74
|
return;
|
|
78
75
|
}
|
|
79
76
|
info(`Rebasing patches: ${fromVersion} → ${currentVersion}`);
|
|
80
|
-
if (
|
|
81
|
-
info(
|
|
77
|
+
if (from.origin === 'patch-stamps') {
|
|
78
|
+
info('From-version is the oldest patch stamp: no committed fireforge.json pin precedes the target.');
|
|
79
|
+
}
|
|
80
|
+
if (from.origin === 'committed-pin' || spread.versions.length > 1) {
|
|
81
|
+
info(`Patch stamps: ${formatPatchStampSpread(spread)}`);
|
|
82
82
|
}
|
|
83
83
|
// The rebase replays onto whatever engine/ holds. The pin is the target,
|
|
84
84
|
// so an engine still on another version means the download step has not
|
package/dist/src/commands/run.js
CHANGED
|
@@ -9,7 +9,7 @@ import { warnIfFurnaceStale } from '../core/furnace-staleness.js';
|
|
|
9
9
|
import { hasBuildArtifacts, hasRunnableBundle, run, runMachSmoke } from '../core/mach.js';
|
|
10
10
|
import { assertBuildArtifacts } from '../core/mach-build-artifacts.js';
|
|
11
11
|
import { resolveRunProfile, sweepAbandonedRunProfiles, } from '../core/run-profile.js';
|
|
12
|
-
import { compileAllowlistFromFile, compileAllowlistFromStrings, findOverCapAllowlistEntries, matchAllowlist, matchesSmokeError, } from '../core/smoke-patterns.js';
|
|
12
|
+
import { compileAllowlistFromFile, compileAllowlistFromStrings, findOverCapAllowlistEntries, isBrowserConsoleLine, matchAllowlist, matchesSmokeError, } from '../core/smoke-patterns.js';
|
|
13
13
|
import { GeneralError, InvalidArgumentError } from '../errors/base.js';
|
|
14
14
|
import { BuildError } from '../errors/build.js';
|
|
15
15
|
import { ExitCode } from '../errors/codes.js';
|
|
@@ -105,6 +105,7 @@ export async function runCommand(projectRoot, options = {}) {
|
|
|
105
105
|
const profile = await resolveRunProfile({
|
|
106
106
|
profile: options.profile,
|
|
107
107
|
tempProfile: options.tempProfile,
|
|
108
|
+
purgeCaches: options.purgeCaches,
|
|
108
109
|
smoke: options.smokeExit !== undefined,
|
|
109
110
|
});
|
|
110
111
|
try {
|
|
@@ -188,6 +189,7 @@ async function runSmokeExit(engineDir, options, profile) {
|
|
|
188
189
|
warn(`--capture-console stream error: ${err.message}`);
|
|
189
190
|
});
|
|
190
191
|
const findings = [];
|
|
192
|
+
let browserConsoleLines = 0;
|
|
191
193
|
let allowlistedErrorHits = 0;
|
|
192
194
|
let allowlistedTotalHits = 0;
|
|
193
195
|
const allowlistHits = new Array(allowlist.length).fill(0);
|
|
@@ -197,6 +199,8 @@ async function runSmokeExit(engineDir, options, profile) {
|
|
|
197
199
|
// mirror preserves stdout/stderr separation for downstream piping.
|
|
198
200
|
const sink = stream === 'stdout' ? process.stdout : process.stderr;
|
|
199
201
|
sink.write(`${line}\n`);
|
|
202
|
+
if (isBrowserConsoleLine(line))
|
|
203
|
+
browserConsoleLines += 1;
|
|
200
204
|
// Count allowlist hits up-front, regardless of error-pattern match.
|
|
201
205
|
// Incrementing only when the line also matches an error pattern makes an
|
|
202
206
|
// allowlist regex that visibly matches `console.warn: RSLoader:` report 0
|
|
@@ -269,6 +273,15 @@ async function runSmokeExit(engineDir, options, profile) {
|
|
|
269
273
|
return;
|
|
270
274
|
}
|
|
271
275
|
if (result.exitCode === 0 || result.exitCode === 130 || result.exitCode === 143) {
|
|
276
|
+
// A clean status before the window with no browser output at all is a
|
|
277
|
+
// launch that never reached a window (a profile lock, a startup-crash
|
|
278
|
+
// streak's silent exit, a self-restart), not a clean run.
|
|
279
|
+
if (browserConsoleLines === 0) {
|
|
280
|
+
throw new SmokeRunError(`Browser exited with code ${result.exitCode} after ${(elapsedMs / 1000).toFixed(1)}s, ` +
|
|
281
|
+
'before the smoke-exit window elapsed, without any console output. A silent early ' +
|
|
282
|
+
'exit means the launch never reached a window: check the profile for a stale ' +
|
|
283
|
+
'.parentlock or a startup-crash streak (toolkit.startup.recent_crashes).', ExitCode.SMOKE_LAUNCH_FAILURE);
|
|
284
|
+
}
|
|
272
285
|
return;
|
|
273
286
|
}
|
|
274
287
|
throw new SmokeRunError(`Browser exited with code ${result.exitCode} before smoke-exit window elapsed.`, ExitCode.SMOKE_LAUNCH_FAILURE);
|
|
@@ -392,7 +405,8 @@ export function registerRun(program, { getProjectRoot, withErrorHandling }) {
|
|
|
392
405
|
.option('--console-allow-file <path>', 'Newline-delimited allowlist regex file. Blank lines and # comments are ignored. A "# max-hits: N" comment directly above an entry caps how many lines it may match in one run; more than that fails the run with exit 12.')
|
|
393
406
|
.option('--capture-console <file>', 'Mirror captured console output to <file> for post-exit inspection.')
|
|
394
407
|
.option('--headless', 'Launch the browser with --headless. Recommended for --smoke-exit on a shared desktop: input into a headed smoke window contaminates the console capture.')
|
|
395
|
-
.option('--profile <path>', 'Launch with this profile directory (passed to Firefox as -profile). Left in place after the run. With --smoke-exit it replaces the default fresh profile.')
|
|
408
|
+
.option('--profile <path>', 'Launch with this profile directory (passed to Firefox as -profile). Left in place after the run. With --smoke-exit it replaces the default fresh profile. Firefox gets -purgecaches too, so the profile does not run chrome scripts an earlier build cached.')
|
|
409
|
+
.option('--no-purge-caches', 'With --profile, keep the profile startup cache (do not pass -purgecaches).')
|
|
396
410
|
.option('--temp-profile', 'Launch with a fresh temporary profile that is removed when the run ends. The default for --smoke-exit.')
|
|
397
411
|
.action(withErrorHandling(
|
|
398
412
|
// `args` is the variadic positional, not a Commander flag, so it
|