@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
|
@@ -3,10 +3,13 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { loadConfig } from '../core/config.js';
|
|
4
4
|
import { getDiffForFilesAgainstHead } from '../core/git-diff.js';
|
|
5
5
|
import { withPrivateGitIndex } from '../core/git-readonly-index.js';
|
|
6
|
-
import { buildPatchQueueContext,
|
|
6
|
+
import { buildPatchQueueContext, formatPatchLintIssue, lintExportedPatch, lintPatchQueue, measurePatchSize, resolvePatchSizeTier, } from '../core/patch-lint.js';
|
|
7
7
|
import { buildPerPatchLintCacheKey, getCachedPerPatchLintIssues, getPerPatchLintCacheHeadSha, loadPerPatchLintCache, savePerPatchLintCache, setCachedPerPatchLintIssues, } from '../core/patch-lint-cache.js';
|
|
8
|
+
import { buildLintIgnoreChecks, evaluateLintIgnoreCeilings, formatLintIgnoreEntry, isLintIgnoreCeiling, lintIgnoreCacheTokens, removableCeilingIssues, } from '../core/patch-lint-ignore.js';
|
|
8
9
|
import { loadPatchesManifest } from '../core/patch-manifest.js';
|
|
9
10
|
import { evaluatePatchPolicy } from '../core/patch-policy.js';
|
|
11
|
+
import { evaluatePatchPolicyContent } from '../core/patch-policy-content.js';
|
|
12
|
+
import { selectPatchSubset } from '../core/patch-subset.js';
|
|
10
13
|
import { GeneralError } from '../errors/base.js';
|
|
11
14
|
import { mapWithConcurrency } from '../utils/concurrency.js';
|
|
12
15
|
import { pathExists } from '../utils/fs.js';
|
|
@@ -47,7 +50,7 @@ async function lintQueuedPatch(patch, lintCtx) {
|
|
|
47
50
|
usedCheckJs: false,
|
|
48
51
|
};
|
|
49
52
|
}
|
|
50
|
-
const ignore =
|
|
53
|
+
const ignore = buildLintIgnoreChecks(patch.lintIgnore);
|
|
51
54
|
let cacheKey;
|
|
52
55
|
if (cache) {
|
|
53
56
|
cacheKey = await buildPerPatchLintCacheKey({
|
|
@@ -60,7 +63,7 @@ async function lintQueuedPatch(patch, lintCtx) {
|
|
|
60
63
|
queueContext: ctx,
|
|
61
64
|
...(engineHeadSha === undefined ? {} : { engineHeadSha }),
|
|
62
65
|
});
|
|
63
|
-
const cached = getCachedPerPatchLintIssues(cache, patch.filename, cacheKey, ignore);
|
|
66
|
+
const cached = getCachedPerPatchLintIssues(cache, patch.filename, cacheKey, lintIgnoreCacheTokens(ignore));
|
|
64
67
|
if (cached) {
|
|
65
68
|
// Returning before the empty-diff probe below is safe: the cache key
|
|
66
69
|
// hashes every affected engine file's content plus engineHeadSha, so
|
|
@@ -106,8 +109,13 @@ async function lintQueuedPatch(patch, lintCtx) {
|
|
|
106
109
|
}
|
|
107
110
|
lintOptions.precomputedCheckJs = precomputedCheckJs;
|
|
108
111
|
}
|
|
112
|
+
// Measured from the working tree, like every other rule here: the files
|
|
113
|
+
// on disk are the would-be export, so a size tier fires mid-edit rather
|
|
114
|
+
// than first at the re-export.
|
|
115
|
+
const patchSize = await measurePatchSize(paths.engine, diff, existing, config);
|
|
109
116
|
const patchIssues = await lintExportedPatch(paths.engine, existing, diff, config, {
|
|
110
117
|
...lintOptions,
|
|
118
|
+
patchSizeMeasurement: patchSize,
|
|
111
119
|
patchQueueCtx: ctx,
|
|
112
120
|
...(ignore ? { ignoreChecks: ignore } : {}),
|
|
113
121
|
...(patch.tier ? { patchTier: patch.tier } : {}),
|
|
@@ -117,11 +125,11 @@ async function lintQueuedPatch(patch, lintCtx) {
|
|
|
117
125
|
existingFiles: existing,
|
|
118
126
|
rawIssues: patchIssues,
|
|
119
127
|
suppressedIssues,
|
|
120
|
-
lineCount:
|
|
128
|
+
lineCount: patchSize.lines,
|
|
121
129
|
usedCheckJs,
|
|
122
130
|
};
|
|
123
131
|
if (cache && cacheKey) {
|
|
124
|
-
result.cacheWrite = { key: cacheKey, lintIgnore:
|
|
132
|
+
result.cacheWrite = { key: cacheKey, lintIgnore: lintIgnoreCacheTokens(ignore) };
|
|
125
133
|
}
|
|
126
134
|
return result;
|
|
127
135
|
}
|
|
@@ -183,6 +191,9 @@ async function applyPerPatchResults(subset, results, issues, checkJs, cache, hel
|
|
|
183
191
|
for (const issue of result.rawIssues) {
|
|
184
192
|
issues.push({ ...issue, file: `${patch.filename} :: ${issue.file}` });
|
|
185
193
|
}
|
|
194
|
+
// A ceiling the patch no longer needs is reported as a notice naming
|
|
195
|
+
// the command that drops it.
|
|
196
|
+
issues.push(...removableCeilingIssues(patch.filename, evaluateLintIgnoreCeilings(patch.lintIgnore, [...result.rawIssues, ...result.suppressedIssues], { lines: result.lineCount, files: patch.filesAffected.length })));
|
|
186
197
|
// A waived size finding still reports its measurement: the finding stays
|
|
187
198
|
// suppressed (no exit-code / --max-warnings effect) but the current count
|
|
188
199
|
// is readable from the tool that enforces it, so a waiver's cited size
|
|
@@ -194,7 +205,9 @@ async function applyPerPatchResults(subset, results, issues, checkJs, cache, hel
|
|
|
194
205
|
heldNotices.push({ check: suppressedIssue.check, patch: patch.filename, suppressed: true });
|
|
195
206
|
continue;
|
|
196
207
|
}
|
|
197
|
-
|
|
208
|
+
const ceiling = patch.lintIgnore?.find((entry) => isLintIgnoreCeiling(entry) && entry.rule === suppressedIssue.check);
|
|
209
|
+
const waiver = ceiling === undefined ? 'lintIgnore' : `lintIgnore (${formatLintIgnoreEntry(ceiling)})`;
|
|
210
|
+
info(`NOTICE [${suppressedIssue.check}] ${patch.filename}: suppressed by ${waiver} — ${suppressedIssue.message}`);
|
|
198
211
|
}
|
|
199
212
|
totals.suppressed += result.suppressedIssues.length;
|
|
200
213
|
if (result.status === 'cached')
|
|
@@ -309,57 +322,9 @@ function reportNoticeSummary(heldNotices, issues) {
|
|
|
309
322
|
info(`NOTICE [${check}]: ${entry.count} across ${entry.patches.size} patch(es)${suppressed} (see --report)`);
|
|
310
323
|
}
|
|
311
324
|
}
|
|
312
|
-
/**
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
* `.patch`, the bare order number, or the manifest `name` field). Throws listing the available
|
|
316
|
-
* patches when a requested name matches none, so a typo fails loud rather
|
|
317
|
-
* than silently linting nothing.
|
|
318
|
-
*/
|
|
319
|
-
function selectPatchSubset(manifest, requested) {
|
|
320
|
-
const normalizedRequests = requested
|
|
321
|
-
.flatMap((entry) => entry.split(','))
|
|
322
|
-
.map((entry) => entry.trim())
|
|
323
|
-
.filter((entry) => entry.length > 0);
|
|
324
|
-
const patchAliases = (p) => {
|
|
325
|
-
const aliases = new Set([p.filename, p.filename.replace(/\.patch$/, ''), p.name]);
|
|
326
|
-
// The bare order number, both as the manifest records it and as the
|
|
327
|
-
// filename spells it (`2` and `002`). The refusal message offers stems,
|
|
328
|
-
// and an operator reading `102-ui-canvas-tiles.patch` in a lint finding
|
|
329
|
-
// reaches for `102` first.
|
|
330
|
-
aliases.add(String(p.order));
|
|
331
|
-
const match = /^(\d+)-([a-z]+)-(.+)\.patch$/.exec(p.filename);
|
|
332
|
-
if (match?.[1])
|
|
333
|
-
aliases.add(match[1]);
|
|
334
|
-
if (match?.[2] && match[3]) {
|
|
335
|
-
aliases.add(`${match[2]}-${match[3]}`);
|
|
336
|
-
aliases.add(match[3]);
|
|
337
|
-
}
|
|
338
|
-
return aliases;
|
|
339
|
-
};
|
|
340
|
-
const matches = (p, name) => {
|
|
341
|
-
const stem = name.replace(/\.patch$/, '');
|
|
342
|
-
return (patchAliases(p).has(name) || patchAliases(p).has(stem) || patchAliases(p).has(`${stem}.patch`));
|
|
343
|
-
};
|
|
344
|
-
const selected = [];
|
|
345
|
-
const seen = new Set();
|
|
346
|
-
for (const name of normalizedRequests) {
|
|
347
|
-
const found = manifest.patches.filter((p) => matches(p, name));
|
|
348
|
-
if (found.length === 0) {
|
|
349
|
-
const available = manifest.patches.map((p) => p.filename).join(', ');
|
|
350
|
-
throw new GeneralError(`No patch in the queue matches "${name}". In --per-patch mode, positional arguments ` +
|
|
351
|
-
`and --patches select patches (filename, stem, order number, manifest name, or slug) — not engine ` +
|
|
352
|
-
`files; drop --per-patch to lint engine paths. Available patches: ${available}`);
|
|
353
|
-
}
|
|
354
|
-
for (const p of found) {
|
|
355
|
-
if (!seen.has(p.filename)) {
|
|
356
|
-
seen.add(p.filename);
|
|
357
|
-
selected.push(p);
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
return selected;
|
|
362
|
-
}
|
|
325
|
+
/** The refusal sentence `selectPatchSubset` adds for a `lint --per-patch` miss. */
|
|
326
|
+
const PER_PATCH_SUBSET_HINT = 'In --per-patch mode, positional arguments and --patches select patches (filename, stem, ' +
|
|
327
|
+
'order number, manifest name, or slug) — not engine files; drop --per-patch to lint engine paths.';
|
|
363
328
|
/**
|
|
364
329
|
* Lints each patch in the queue as its own isolated diff, honouring
|
|
365
330
|
* per-patch `lintIgnore` entries. Cross-patch rules still run once over
|
|
@@ -382,7 +347,7 @@ async function lintPerPatchInner(projectRoot, paths, options = {}) {
|
|
|
382
347
|
return;
|
|
383
348
|
}
|
|
384
349
|
const subset = options.patches && options.patches.length > 0
|
|
385
|
-
? selectPatchSubset(manifest, options.patches)
|
|
350
|
+
? selectPatchSubset(manifest, options.patches, PER_PATCH_SUBSET_HINT)
|
|
386
351
|
: manifest.patches;
|
|
387
352
|
const subsetNames = new Set(subset.map((p) => p.filename));
|
|
388
353
|
const isSubset = subset.length !== manifest.patches.length;
|
|
@@ -405,7 +370,11 @@ async function lintPerPatchInner(projectRoot, paths, options = {}) {
|
|
|
405
370
|
const cache = options.noCache === true ? undefined : await loadPerPatchLintCache(projectRoot);
|
|
406
371
|
const engineHeadSha = cache ? await getPerPatchLintCacheHeadSha(paths.engine) : undefined;
|
|
407
372
|
const issues = [];
|
|
408
|
-
|
|
373
|
+
const policyIssues = [
|
|
374
|
+
...evaluatePatchPolicy(config, manifest),
|
|
375
|
+
...(await evaluatePatchPolicyContent(config, projectRoot, manifest, ctx.entries)),
|
|
376
|
+
];
|
|
377
|
+
for (const issue of policyIssues) {
|
|
409
378
|
if (isSubset && !subsetNames.has(issue.filename))
|
|
410
379
|
continue;
|
|
411
380
|
issues.push({
|
|
@@ -433,10 +402,12 @@ async function lintPerPatchInner(projectRoot, paths, options = {}) {
|
|
|
433
402
|
});
|
|
434
403
|
const heldNotices = options.notices === 'summary' ? [] : undefined;
|
|
435
404
|
const { linted, skipped, cacheDirty, reusedCacheEntries, suppressed } = await applyPerPatchResults(subset, results, issues, checkJs, cache, heldNotices);
|
|
405
|
+
const queueIssues = [];
|
|
436
406
|
for (const issue of lintPatchQueue(ctx)) {
|
|
437
407
|
if (isSubset && !subsetTouchedFiles.has(issue.file))
|
|
438
408
|
continue;
|
|
439
409
|
issues.push(issue);
|
|
410
|
+
queueIssues.push(issue);
|
|
440
411
|
}
|
|
441
412
|
if (cache && cacheDirty)
|
|
442
413
|
await savePerPatchLintCache(projectRoot, cache);
|
|
@@ -447,7 +418,7 @@ async function lintPerPatchInner(projectRoot, paths, options = {}) {
|
|
|
447
418
|
info(`Suppressed ${suppressed} issue(s) via per-patch lintIgnore.`);
|
|
448
419
|
}
|
|
449
420
|
if (options.report !== undefined) {
|
|
450
|
-
await writePerPatchLintReport(options.report, subset, results);
|
|
421
|
+
await writePerPatchLintReport(options.report, subset, results, config, queueIssues);
|
|
451
422
|
}
|
|
452
423
|
reportPerPatchOutcome(issues, linted, skipped, options, heldNotices);
|
|
453
424
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { PatchLintIssue, PatchMetadata } from '../types/commands/index.js';
|
|
2
|
+
import type { FireForgeConfig } from '../types/config.js';
|
|
2
3
|
/**
|
|
3
4
|
* Structural view of one per-patch lint outcome. Mirrors the relevant
|
|
4
5
|
* fields of lint-per-patch's QueuedPatchResult without importing it:
|
|
@@ -14,8 +15,11 @@ export interface PerPatchReportResult {
|
|
|
14
15
|
}
|
|
15
16
|
/**
|
|
16
17
|
* Writes the per-patch lint report JSON. Called after the per-patch
|
|
17
|
-
* results are assembled in patch order.
|
|
18
|
-
*
|
|
19
|
-
*
|
|
18
|
+
* results are assembled in patch order. Policy findings are
|
|
19
|
+
* patch-attributed by their `filename` field upstream and are not
|
|
20
|
+
* duplicated here. Cross-patch findings (`forward-import`,
|
|
21
|
+
* `forward-registration`, …) go in `queueIssues`, with the
|
|
22
|
+
* `stagedDeclarations` that discharge them, so the report can be handed to
|
|
23
|
+
* `patch staged-dependency --from` as is.
|
|
20
24
|
*/
|
|
21
|
-
export declare function writePerPatchLintReport(reportPath: string, subset: readonly PatchMetadata[], results: readonly PerPatchReportResult[]): Promise<void>;
|
|
25
|
+
export declare function writePerPatchLintReport(reportPath: string, subset: readonly PatchMetadata[], results: readonly PerPatchReportResult[], config: Pick<FireForgeConfig, 'patchLint'>, queueIssues?: readonly PatchLintIssue[]): Promise<void>;
|
|
@@ -10,16 +10,21 @@
|
|
|
10
10
|
* none is wanted.
|
|
11
11
|
*/
|
|
12
12
|
import { getPatchSizeThresholds, resolvePatchSizeTier, } from '../core/patch-lint.js';
|
|
13
|
+
import { evaluateLintIgnoreCeilings, } from '../core/patch-lint-ignore.js';
|
|
13
14
|
import { writeJson } from '../utils/fs.js';
|
|
14
15
|
import { info } from '../utils/logger.js';
|
|
15
16
|
const LINT_REPORT_SCHEMA_VERSION = 1;
|
|
16
17
|
/**
|
|
17
18
|
* Writes the per-patch lint report JSON. Called after the per-patch
|
|
18
|
-
* results are assembled in patch order.
|
|
19
|
-
*
|
|
20
|
-
*
|
|
19
|
+
* results are assembled in patch order. Policy findings are
|
|
20
|
+
* patch-attributed by their `filename` field upstream and are not
|
|
21
|
+
* duplicated here. Cross-patch findings (`forward-import`,
|
|
22
|
+
* `forward-registration`, …) go in `queueIssues`, with the
|
|
23
|
+
* `stagedDeclarations` that discharge them, so the report can be handed to
|
|
24
|
+
* `patch staged-dependency --from` as is.
|
|
21
25
|
*/
|
|
22
|
-
export async function writePerPatchLintReport(reportPath, subset, results) {
|
|
26
|
+
export async function writePerPatchLintReport(reportPath, subset, results, config, queueIssues = []) {
|
|
27
|
+
const metric = config.patchLint?.patchSizeMetric ?? 'diff';
|
|
23
28
|
const patches = [];
|
|
24
29
|
let errors = 0;
|
|
25
30
|
let warnings = 0;
|
|
@@ -36,12 +41,16 @@ export async function writePerPatchLintReport(reportPath, subset, results) {
|
|
|
36
41
|
filename: patch.filename,
|
|
37
42
|
name: patch.name,
|
|
38
43
|
status: result.status,
|
|
44
|
+
metric,
|
|
39
45
|
lineCount: result.lineCount,
|
|
40
46
|
filesAffected: patch.filesAffected.length,
|
|
41
47
|
tier,
|
|
42
|
-
thresholds: getPatchSizeThresholds(tier.tier),
|
|
48
|
+
thresholds: getPatchSizeThresholds(tier.tier, config),
|
|
43
49
|
issues: result.rawIssues,
|
|
44
50
|
suppressedIssues: result.suppressedIssues,
|
|
51
|
+
lintIgnoreCeilings: result.status === 'skipped'
|
|
52
|
+
? []
|
|
53
|
+
: evaluateLintIgnoreCeilings(patch.lintIgnore, [...result.rawIssues, ...result.suppressedIssues], { lines: result.lineCount, files: patch.filesAffected.length }),
|
|
45
54
|
});
|
|
46
55
|
if (result.status === 'skipped')
|
|
47
56
|
skipped++;
|
|
@@ -55,6 +64,7 @@ export async function writePerPatchLintReport(reportPath, subset, results) {
|
|
|
55
64
|
schemaVersion: LINT_REPORT_SCHEMA_VERSION,
|
|
56
65
|
generatedAt: new Date().toISOString(),
|
|
57
66
|
patches,
|
|
67
|
+
queueIssues,
|
|
58
68
|
totals: { linted, skipped, errors, warnings, suppressed },
|
|
59
69
|
});
|
|
60
70
|
info(`Per-patch lint report written to ${reportPath}.`);
|
|
@@ -10,9 +10,10 @@ import { getStatusWithCodes, hasChanges } from '../core/git.js';
|
|
|
10
10
|
import { getAllDiff, getDiffForFilesAgainstHead } from '../core/git-diff.js';
|
|
11
11
|
import { expandUntrackedDirectoryEntries, getModifiedFilesInDir, getUntrackedFiles, getUntrackedFilesInDir, getWorkingTreeStatus, } from '../core/git-status.js';
|
|
12
12
|
import { extractAffectedFiles } from '../core/patch-apply.js';
|
|
13
|
-
import { buildPatchQueueContext,
|
|
13
|
+
import { buildPatchQueueContext, formatPatchLintIssue, lintExportedPatch, lintPatchQueue, lintPatchSize, measurePatchSize, } from '../core/patch-lint.js';
|
|
14
14
|
import { clearPerPatchLintCache } from '../core/patch-lint-cache.js';
|
|
15
15
|
import { collectDiffFilePaths, tagLintIssues } from '../core/patch-lint-diff-tag.js';
|
|
16
|
+
import { buildLintIgnoreChecks, filterLintIgnoredByOwners, partitionLintIgnoredIssues, } from '../core/patch-lint-ignore.js';
|
|
16
17
|
import { GeneralError, InvalidArgumentError } from '../errors/base.js';
|
|
17
18
|
import { pathExists } from '../utils/fs.js';
|
|
18
19
|
import { info, intro, outro, success, warn } from '../utils/logger.js';
|
|
@@ -186,25 +187,7 @@ function buildMaxWarningsMessage(count, maxWarnings, scope) {
|
|
|
186
187
|
* @returns Filtered issue list and the count of dropped findings.
|
|
187
188
|
*/
|
|
188
189
|
export function applyAggregateLintIgnoreSuppression(issues, ctx) {
|
|
189
|
-
const
|
|
190
|
-
for (const entry of ctx.entries) {
|
|
191
|
-
const ignoreList = entry.metadata?.lintIgnore;
|
|
192
|
-
if (!ignoreList || ignoreList.length === 0)
|
|
193
|
-
continue;
|
|
194
|
-
for (const f of entry.metadata?.filesAffected ?? []) {
|
|
195
|
-
let bucket = suppressionsByFile.get(f);
|
|
196
|
-
if (!bucket) {
|
|
197
|
-
bucket = new Set();
|
|
198
|
-
suppressionsByFile.set(f, bucket);
|
|
199
|
-
}
|
|
200
|
-
for (const id of ignoreList)
|
|
201
|
-
bucket.add(id);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
if (suppressionsByFile.size === 0) {
|
|
205
|
-
return { issues, dropped: 0 };
|
|
206
|
-
}
|
|
207
|
-
const filtered = issues.filter((issue) => !suppressionsByFile.get(issue.file)?.has(issue.check));
|
|
190
|
+
const filtered = filterLintIgnoredByOwners(issues, ctx.entries.map((entry) => entry.metadata));
|
|
208
191
|
return { issues: filtered, dropped: issues.length - filtered.length };
|
|
209
192
|
}
|
|
210
193
|
/**
|
|
@@ -283,7 +266,7 @@ function downgradeAggregateSizeRules(issues, files, ctx) {
|
|
|
283
266
|
* @param ctx - Patch queue context used to attribute file → owning patch
|
|
284
267
|
* @returns Size issues, each attributed to its owning patch by message prefix
|
|
285
268
|
*/
|
|
286
|
-
async function lintOwningPatchSizes(engineDir, filesAffected, ctx) {
|
|
269
|
+
async function lintOwningPatchSizes(engineDir, filesAffected, ctx, config) {
|
|
287
270
|
const listed = new Set(filesAffected);
|
|
288
271
|
const owners = new Map();
|
|
289
272
|
const ownedListed = new Set();
|
|
@@ -302,26 +285,24 @@ async function lintOwningPatchSizes(engineDir, filesAffected, ctx) {
|
|
|
302
285
|
owners.set(entry.filename, entry);
|
|
303
286
|
}
|
|
304
287
|
const issues = [];
|
|
305
|
-
const
|
|
288
|
+
const sizeForFiles = async (relPaths) => {
|
|
306
289
|
const existing = [];
|
|
307
290
|
for (const f of relPaths) {
|
|
308
291
|
if (await pathExists(join(engineDir, f)))
|
|
309
292
|
existing.push(f);
|
|
310
293
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
return countNonBinaryDiffLines(diff).textLines;
|
|
294
|
+
const diff = existing.length > 0 ? await getDiffForFilesAgainstHead(engineDir, existing) : '';
|
|
295
|
+
// The file count stays the owner's whole `filesAffected`, as before.
|
|
296
|
+
return measurePatchSize(engineDir, diff, relPaths, config);
|
|
315
297
|
};
|
|
316
298
|
for (const entry of owners.values()) {
|
|
317
299
|
const md = entry.metadata;
|
|
318
300
|
if (!md)
|
|
319
301
|
continue;
|
|
320
|
-
const
|
|
321
|
-
const ignore =
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
continue;
|
|
302
|
+
const size = await sizeForFiles(md.filesAffected);
|
|
303
|
+
const ignore = buildLintIgnoreChecks(md.lintIgnore);
|
|
304
|
+
const sized = lintPatchSize(md.filesAffected, size, md.tier, config);
|
|
305
|
+
for (const issue of partitionLintIgnoredIssues(sized, ignore).kept) {
|
|
325
306
|
issues.push({ ...issue, message: `${entry.filename}: ${issue.message}` });
|
|
326
307
|
}
|
|
327
308
|
}
|
|
@@ -329,8 +310,8 @@ async function lintOwningPatchSizes(engineDir, filesAffected, ctx) {
|
|
|
329
310
|
// unit so a genuinely oversized fresh change still surfaces.
|
|
330
311
|
const unowned = filesAffected.filter((f) => !ownedListed.has(f));
|
|
331
312
|
if (unowned.length > 0) {
|
|
332
|
-
const
|
|
333
|
-
issues.push(...lintPatchSize(unowned,
|
|
313
|
+
const size = await sizeForFiles(unowned);
|
|
314
|
+
issues.push(...lintPatchSize(unowned, size, undefined, config));
|
|
334
315
|
}
|
|
335
316
|
return issues;
|
|
336
317
|
}
|
|
@@ -460,7 +441,7 @@ export async function lintCommand(projectRoot, files, options = {}) {
|
|
|
460
441
|
})),
|
|
461
442
|
];
|
|
462
443
|
if (files.length > 0 && ctx) {
|
|
463
|
-
issues.push(...(await lintOwningPatchSizes(paths.engine, filesAffected, ctx)));
|
|
444
|
+
issues.push(...(await lintOwningPatchSizes(paths.engine, filesAffected, ctx, config)));
|
|
464
445
|
}
|
|
465
446
|
// Cross-patch rules operate over the whole queue, so run them whenever a
|
|
466
447
|
// patches directory exists. They surface duplicate /dev/null creations
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { registerBootstrap } from './bootstrap.js';
|
|
2
2
|
import { registerBuild } from './build.js';
|
|
3
|
+
import { registerCheck } from './check.js';
|
|
3
4
|
import { registerConfig } from './config.js';
|
|
4
5
|
import { registerDiscard } from './discard.js';
|
|
5
6
|
import { registerDoctor } from './doctor.js';
|
|
@@ -60,5 +61,6 @@ export const COMMAND_MANIFEST = [
|
|
|
60
61
|
{ name: 'lint', group: 'diagnostics', register: registerLint },
|
|
61
62
|
{ name: 'typecheck', group: 'diagnostics', register: registerTypecheck },
|
|
62
63
|
{ name: 'verify', group: 'diagnostics', register: registerVerify },
|
|
64
|
+
{ name: 'check', group: 'diagnostics', register: registerCheck },
|
|
63
65
|
{ name: 'furnace', group: 'components', register: registerFurnace },
|
|
64
66
|
];
|
|
@@ -42,5 +42,7 @@ function formatGapNotice(targetFilename, message) {
|
|
|
42
42
|
const where = orders ? `a gap at ${orders}` : 'a numeric gap';
|
|
43
43
|
return (`deleting ${targetFilename} leaves ${range} with ${where}; the queue will fail ` +
|
|
44
44
|
'numeric-gap (patchPolicy.allowGaps is false) until ' +
|
|
45
|
-
`"fireforge export --order ${fill}" fills it or "fireforge patch compact" renumbers
|
|
45
|
+
`"fireforge export --order ${fill}" fills it or "fireforge patch compact" renumbers. ` +
|
|
46
|
+
`"fireforge patch retire ${targetFilename}" removes the patch and keeps its order as a ` +
|
|
47
|
+
'retired slot instead, so no gap opens.');
|
|
46
48
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Destructive: refuses when a later patch imports a module owned by the
|
|
5
5
|
* target (that would leave a dangling forward import), prompts for
|
|
6
6
|
* confirmation interactively, requires `--yes` for non-TTY, supports
|
|
7
|
-
* `--dry-run`, and appends to
|
|
7
|
+
* `--dry-run`, and appends to the patch history log on success.
|
|
8
8
|
*/
|
|
9
9
|
import { Command } from 'commander';
|
|
10
10
|
import type { CommandContext } from '../../types/cli.js';
|
|
@@ -5,14 +5,13 @@
|
|
|
5
5
|
* Destructive: refuses when a later patch imports a module owned by the
|
|
6
6
|
* target (that would leave a dangling forward import), prompts for
|
|
7
7
|
* confirmation interactively, requires `--yes` for non-TTY, supports
|
|
8
|
-
* `--dry-run`, and appends to
|
|
8
|
+
* `--dry-run`, and appends to the patch history log on success.
|
|
9
9
|
*/
|
|
10
|
-
import { basename } from 'node:path';
|
|
11
10
|
import { Command } from 'commander';
|
|
12
11
|
import { loadConfig } from '../../core/config.js';
|
|
13
12
|
import { confirmDestructive } from '../../core/destructive.js';
|
|
14
13
|
import { appendHistoryBestEffort } from '../../core/history-log.js';
|
|
15
|
-
import { buildPatchQueueContext
|
|
14
|
+
import { buildPatchQueueContext } from '../../core/patch-lint.js';
|
|
16
15
|
import { withPatchDirectoryLock } from '../../core/patch-lock.js';
|
|
17
16
|
import { removePatchFileAndManifest } from '../../core/patch-manifest.js';
|
|
18
17
|
import { GeneralError } from '../../errors/base.js';
|
|
@@ -21,6 +20,7 @@ import { addWaitLockOption, pickDefined, resolveWaitLockSeconds } from '../../ut
|
|
|
21
20
|
import { proceedAfterDecision } from '../destructive-decision.js';
|
|
22
21
|
import { describeProjectedGaps } from './delete-gap-notice.js';
|
|
23
22
|
import { requirePatchQueue, requirePatchTarget } from './patch-context.js';
|
|
23
|
+
import { collectRemovalDependents, queueSnapshot, removalConflict, warnDanglingStagedOwners, } from './removal-dependents.js';
|
|
24
24
|
/**
|
|
25
25
|
* Runs the `patch delete` command: removes a patch file and its manifest
|
|
26
26
|
* row atomically, refusing when a later patch imports a leaf owned by the
|
|
@@ -37,81 +37,15 @@ export async function patchDeleteCommand(projectRoot, identifier, options = {})
|
|
|
37
37
|
});
|
|
38
38
|
const target = requirePatchTarget(identifier, manifest.patches);
|
|
39
39
|
// Build the full queue context once so we can scan each patch's newFiles
|
|
40
|
-
// without re-parsing for the dependency check below.
|
|
40
|
+
// without re-parsing for the dependency check below. Hard refusal: an
|
|
41
|
+
// import owned by a later patch that points at any of the target's
|
|
42
|
+
// newly-created files is a dependency on the target.
|
|
41
43
|
const baseCtx = await buildPatchQueueContext(paths.patches);
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (!targetEntry || JSON.stringify(targetEntry.metadata) !== JSON.stringify(target)) {
|
|
48
|
-
throw new GeneralError('Patch queue changed while preparing deletion. Re-run patch delete to review the current target.');
|
|
49
|
-
}
|
|
50
|
-
const targetNewFileLeaves = new Set();
|
|
51
|
-
for (const fullPath of targetEntry.newFiles.keys()) {
|
|
52
|
-
targetNewFileLeaves.add(basename(fullPath));
|
|
53
|
-
}
|
|
54
|
-
// Scan every later patch's new files and its added lines on pre-existing
|
|
55
|
-
// files for import specifiers that resolve to a leaf owned by the target.
|
|
56
|
-
// Uses the shared specifier extractor so dynamic `import()` and
|
|
57
|
-
// `ChromeUtils.defineESModuleGetters` are picked up: the forward-import
|
|
58
|
-
// lint rule already covers those forms, and delete safety must match the
|
|
59
|
-
// same set or it silently drops dependencies.
|
|
60
|
-
//
|
|
61
|
-
// Both source-site maps are covered: `newFiles` (files the later patch
|
|
62
|
-
// creates) and `modifiedFileAdditions` (added lines against files that
|
|
63
|
-
// already exist). Scanning only newFiles misses a later patch adding
|
|
64
|
-
// `import "./TargetHelper.sys.mjs"` to an existing file.
|
|
65
|
-
const dependents = [];
|
|
66
|
-
const scanSite = (entryFilename, sitePath, content) => {
|
|
67
|
-
if (!isForwardImportableFile(sitePath))
|
|
68
|
-
return false;
|
|
69
|
-
const ignoredLines = findForwardImportIgnoreLines(content);
|
|
70
|
-
const specifiers = extractImportSpecifiersWithLines(content);
|
|
71
|
-
for (const { specifier, line } of specifiers) {
|
|
72
|
-
if (ignoredLines.has(line))
|
|
73
|
-
continue;
|
|
74
|
-
const cleaned = specifier.split(/[?#]/)[0] ?? specifier;
|
|
75
|
-
const leaf = basename(cleaned);
|
|
76
|
-
if (!leaf || !isForwardImportableFile(leaf))
|
|
77
|
-
continue;
|
|
78
|
-
if (targetNewFileLeaves.has(leaf)) {
|
|
79
|
-
dependents.push(`${entryFilename} (${sitePath}) imports "${specifier}" which would be deleted`);
|
|
80
|
-
return true;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
return false;
|
|
84
|
-
};
|
|
85
|
-
for (const entry of baseCtx.entries) {
|
|
86
|
-
if (entry.filename === target.filename)
|
|
87
|
-
continue;
|
|
88
|
-
if (entry.order < target.order)
|
|
89
|
-
continue;
|
|
90
|
-
let matched = false;
|
|
91
|
-
for (const [newFile, content] of entry.newFiles) {
|
|
92
|
-
if (scanSite(entry.filename, newFile, content)) {
|
|
93
|
-
matched = true;
|
|
94
|
-
break;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
if (matched)
|
|
98
|
-
continue;
|
|
99
|
-
for (const [modifiedPath, addedContent] of entry.modifiedFileAdditions) {
|
|
100
|
-
if (scanSite(entry.filename, modifiedPath, addedContent))
|
|
101
|
-
break;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
// Staged-dependency declarations on other patches may name the deleted
|
|
105
|
-
// patch as their forward-import owner. The dangling reference also
|
|
106
|
-
// surfaces via cross-patch lint later, but warning here puts the exact
|
|
107
|
-
// cleanup command in front of the operator at decision time.
|
|
108
|
-
const danglingOwnerHolders = baseCtx.entries.filter((entry) => entry.filename !== target.filename &&
|
|
109
|
-
(entry.metadata?.stagedDependencies?.forwardImports ?? []).some((fi) => fi.owner === target.filename));
|
|
110
|
-
for (const holder of danglingOwnerHolders) {
|
|
111
|
-
warn(`${holder.filename} declares a staged dependency with owner ${target.filename}; ` +
|
|
112
|
-
`after the delete, update it via "fireforge patch staged-dependency ${holder.filename} --remove ..." ` +
|
|
113
|
-
'or re-point the owner at the patch that will create the file.');
|
|
114
|
-
}
|
|
44
|
+
const dependents = collectRemovalDependents(baseCtx, target, {
|
|
45
|
+
noun: 'deletion',
|
|
46
|
+
verb: 'delete',
|
|
47
|
+
});
|
|
48
|
+
warnDanglingStagedOwners(baseCtx, target, 'delete');
|
|
115
49
|
// Under `patchPolicy.allowGaps: false` the removal can open a numeric gap
|
|
116
50
|
// that every later lint and re-export refuses on. Naming it here, in
|
|
117
51
|
// dry-run and at the prompt, puts the refill/compact choice in front of
|
|
@@ -120,20 +54,7 @@ export async function patchDeleteCommand(projectRoot, identifier, options = {})
|
|
|
120
54
|
for (const line of gapNotices) {
|
|
121
55
|
warn(line);
|
|
122
56
|
}
|
|
123
|
-
const conflicts = dependents
|
|
124
|
-
? {
|
|
125
|
-
// The wording spells out the runtime impact: `git apply`
|
|
126
|
-
// does not resolve imports and succeeds even when a later patch
|
|
127
|
-
// imports a file the target created, so the queue re-imports
|
|
128
|
-
// cleanly. The breakage surfaces at browser startup when
|
|
129
|
-
// `ChromeUtils.importESModule` cannot locate the deleted module.
|
|
130
|
-
// Operators who plan to re-introduce the imported files (rename,
|
|
131
|
-
// refactor) need to know this is the impact model rather than a
|
|
132
|
-
// patch-application failure.
|
|
133
|
-
reason: `${dependents.length} later patch(es) contain import statements that reference files created by ${target.filename}. Patch application itself will still succeed, but runtime imports will fail at browser startup until those files are re-introduced.`,
|
|
134
|
-
details: dependents,
|
|
135
|
-
}
|
|
136
|
-
: null;
|
|
57
|
+
const conflicts = removalConflict(dependents, target);
|
|
137
58
|
const summary = [
|
|
138
59
|
`delete ${target.filename} (category: ${target.category}, order: ${target.order})`,
|
|
139
60
|
`description: ${target.description || '(none)'}`,
|
|
@@ -164,15 +85,9 @@ export async function patchDeleteCommand(projectRoot, identifier, options = {})
|
|
|
164
85
|
// failure to the caller would mislead.
|
|
165
86
|
await withPatchDirectoryLock(paths.patches, async () => {
|
|
166
87
|
const current = await buildPatchQueueContext(paths.patches);
|
|
167
|
-
const snapshot = (context) => JSON.stringify(context.entries.map(({ filename, order, diff, metadata }) => ({
|
|
168
|
-
filename,
|
|
169
|
-
order,
|
|
170
|
-
diff,
|
|
171
|
-
metadata,
|
|
172
|
-
})));
|
|
173
88
|
// A changed body or manifest invalidates the dependency scan approved
|
|
174
89
|
// before the prompt. Recheck all of its inputs while holding the lock.
|
|
175
|
-
if (
|
|
90
|
+
if (queueSnapshot(current) !== queueSnapshot(baseCtx)) {
|
|
176
91
|
throw new GeneralError('Patch queue changed while waiting for confirmation or the lock. Re-run patch delete to review the current dependencies.');
|
|
177
92
|
}
|
|
178
93
|
await removePatchFileAndManifest(paths.patches, target.filename);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `fireforge patch <verb>` parent command. Groups single-patch mutations and
|
|
3
3
|
* planners (`compact`, `delete`, `lint-ignore`, `move-files`, `rename`,
|
|
4
|
-
* `reorder`, `staged-dependency`, `tier`) so they do not clutter the
|
|
4
|
+
* `reorder`, `retire`, `staged-dependency`, `tier`) so they do not clutter the
|
|
5
5
|
* top-level command list. Queue-level verbs like `lint`, `export`, `verify`,
|
|
6
6
|
* and `status` stay flat.
|
|
7
7
|
*/
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* `fireforge patch <verb>` parent command. Groups single-patch mutations and
|
|
4
4
|
* planners (`compact`, `delete`, `lint-ignore`, `move-files`, `rename`,
|
|
5
|
-
* `reorder`, `staged-dependency`, `tier`) so they do not clutter the
|
|
5
|
+
* `reorder`, `retire`, `staged-dependency`, `tier`) so they do not clutter the
|
|
6
6
|
* top-level command list. Queue-level verbs like `lint`, `export`, `verify`,
|
|
7
7
|
* and `status` stay flat.
|
|
8
8
|
*/
|
|
@@ -13,6 +13,7 @@ import { registerPatchLintIgnore } from './lint-ignore.js';
|
|
|
13
13
|
import { registerPatchMoveFiles } from './move-files.js';
|
|
14
14
|
import { registerPatchRename } from './rename.js';
|
|
15
15
|
import { registerPatchReorder } from './reorder.js';
|
|
16
|
+
import { registerPatchRetire } from './retire.js';
|
|
16
17
|
import { registerPatchSplit } from './split.js';
|
|
17
18
|
import { registerPatchStagedDependency } from './staged-dependency.js';
|
|
18
19
|
import { registerPatchTier } from './tier.js';
|
|
@@ -25,7 +26,7 @@ import { registerPatchTier } from './tier.js';
|
|
|
25
26
|
export function registerPatch(program, context) {
|
|
26
27
|
const patch = program
|
|
27
28
|
.command('patch')
|
|
28
|
-
.description('Manage individual patches in the queue (compact, delete, lint-ignore, move-files, rename, reorder, staged-dependency, tier)')
|
|
29
|
+
.description('Manage individual patches in the queue (compact, delete, lint-ignore, move-files, rename, reorder, retire, staged-dependency, tier)')
|
|
29
30
|
// Match `fireforge furnace`'s no-args contract: print the group's help
|
|
30
31
|
// and exit 0. Without this default action, commander routes
|
|
31
32
|
// `fireforge patch` (no subcommand) through its own help-then-exit-1
|
|
@@ -40,6 +41,7 @@ export function registerPatch(program, context) {
|
|
|
40
41
|
registerPatchMoveFiles(patch, context);
|
|
41
42
|
registerPatchRename(patch, context);
|
|
42
43
|
registerPatchReorder(patch, context);
|
|
44
|
+
registerPatchRetire(patch, context);
|
|
43
45
|
registerPatchSplit(patch, context);
|
|
44
46
|
registerPatchStagedDependency(patch, context);
|
|
45
47
|
registerPatchTier(patch, context);
|
|
@@ -11,7 +11,11 @@
|
|
|
11
11
|
* re-export's engine read + diff regeneration roundtrip is wasted.
|
|
12
12
|
*
|
|
13
13
|
* Modes are mutually exclusive: exactly one of `--add`, `--remove`, or
|
|
14
|
-
* `--clear` must be supplied per invocation.
|
|
14
|
+
* `--clear` must be supplied per invocation. `--add <rule> --max <n>
|
|
15
|
+
* --reason <text>` writes a size waiver with a ceiling (see
|
|
16
|
+
* `core/patch-lint-ignore.ts`) instead of a bare check ID; an `--add` for a
|
|
17
|
+
* rule that already has an entry replaces it, so the same command moves a
|
|
18
|
+
* ceiling or widens it back to a bare waiver. The read-modify-write
|
|
15
19
|
* happens inside the patch directory lock via {@link mutatePatchMetadata}
|
|
16
20
|
* so a concurrent writer cannot interleave between the read and the
|
|
17
21
|
* write, which matters when an operator scripts repeated invocations or
|