@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
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Argument checks `fireforge re-export` runs before it queues for the
|
|
4
|
+
* engine session lock.
|
|
5
|
+
*
|
|
6
|
+
* Under contention the lock wait runs to many minutes, and an argument the
|
|
7
|
+
* run was always going to refuse (an empty patch name from an unquoted
|
|
8
|
+
* `"$@"`, a changed-file path passed as a positional, a path flag that
|
|
9
|
+
* swallowed the next flag) used to be reported only after that wait. These
|
|
10
|
+
* checks read the manifest and a path-scoped `git status` without the lock.
|
|
11
|
+
* The command repeats its own resolution under the lock, so a manifest that
|
|
12
|
+
* changes in between is still read fresh there.
|
|
13
|
+
*/
|
|
14
|
+
import { readFile } from 'node:fs/promises';
|
|
15
|
+
import { join, resolve } from 'node:path';
|
|
16
|
+
import { getProjectPaths } from '../core/config.js';
|
|
17
|
+
import { isGitRepository } from '../core/git.js';
|
|
18
|
+
import { chunkPathspecs } from '../core/git-base.js';
|
|
19
|
+
import { getDirtyFiles } from '../core/git-status.js';
|
|
20
|
+
import { formatPatchNotFoundError } from '../core/patch-identifier-suggest.js';
|
|
21
|
+
import { loadPatchesManifest, resolvePatchIdentifier } from '../core/patch-manifest.js';
|
|
22
|
+
import { InvalidArgumentError } from '../errors/base.js';
|
|
23
|
+
import { toError } from '../utils/errors.js';
|
|
24
|
+
import { pathExists } from '../utils/fs.js';
|
|
25
|
+
import { normalizeReExportFilesPositionals, validateReExportOptionCombinations, } from './re-export-options.js';
|
|
26
|
+
import { normalizeEngineRelativeInput } from './re-export-scan.js';
|
|
27
|
+
import { splitScanFileOwner } from './re-export-scan-owner.js';
|
|
28
|
+
/**
|
|
29
|
+
* Reads a path list file: one engine-relative path per line, blank lines
|
|
30
|
+
* and `#` comment lines ignored. Backs `--expect-from`, which exists because
|
|
31
|
+
* a list held in one shell variable reaches the CLI as one argument under
|
|
32
|
+
* zsh, and splitting it by hand left every path after the first as a
|
|
33
|
+
* positional patch name.
|
|
34
|
+
*
|
|
35
|
+
* @param file - The list file, relative to the working directory
|
|
36
|
+
* @param flag - Flag name for error messages
|
|
37
|
+
*/
|
|
38
|
+
export async function readPathListFile(file, flag) {
|
|
39
|
+
let text;
|
|
40
|
+
try {
|
|
41
|
+
text = await readFile(resolve(file), 'utf8');
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
throw new InvalidArgumentError(`${flag} could not read ${file}: ${toError(error).message}`, flag);
|
|
45
|
+
}
|
|
46
|
+
const paths = text
|
|
47
|
+
.split(/\r?\n/)
|
|
48
|
+
.map((line) => line.trim())
|
|
49
|
+
.filter((line) => line.length > 0 && !line.startsWith('#'));
|
|
50
|
+
if (paths.length === 0) {
|
|
51
|
+
throw new InvalidArgumentError(`${flag} file ${file} lists no paths. An empty list is usually a generation step that ` +
|
|
52
|
+
`failed; write one engine-relative path per line, or drop ${flag}.`, flag);
|
|
53
|
+
}
|
|
54
|
+
return paths;
|
|
55
|
+
}
|
|
56
|
+
/** Engine-relative form of a positional, or undefined when it cannot be one. */
|
|
57
|
+
function asEnginePath(value) {
|
|
58
|
+
try {
|
|
59
|
+
return normalizeEngineRelativeInput(value, 'patch');
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async function resolvePositionals(engineDir, patches, manifest) {
|
|
66
|
+
for (const identifier of patches) {
|
|
67
|
+
if (resolvePatchIdentifier(identifier, manifest.patches))
|
|
68
|
+
continue;
|
|
69
|
+
const enginePath = asEnginePath(identifier);
|
|
70
|
+
if (enginePath !== undefined && (await pathExists(join(engineDir, enginePath)))) {
|
|
71
|
+
throw new InvalidArgumentError(`"${identifier}" is a file in engine/, not a patch. re-export takes patch identifiers ` +
|
|
72
|
+
`as positionals. To name a file whose drift this run expects, pass --expect ` +
|
|
73
|
+
`${enginePath} (one --expect per path), or list the paths one per line in a file ` +
|
|
74
|
+
'and pass --expect-from <file>.', identifier);
|
|
75
|
+
}
|
|
76
|
+
throw new InvalidArgumentError(formatPatchNotFoundError(identifier, manifest.patches), identifier);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Refuses a path flag value that names no changed file in engine/. Every
|
|
81
|
+
* one of these flags is about a changed file (drift to expect, an unmanaged
|
|
82
|
+
* file to admit, a file to adopt), so a clean or absent path is a typo or a
|
|
83
|
+
* swallowed argument, and the run would otherwise treat it as a no-op.
|
|
84
|
+
*/
|
|
85
|
+
async function assertFlagPathsInStatus(engineDir, options, manifest) {
|
|
86
|
+
const normalize = (flag, values) => [
|
|
87
|
+
flag,
|
|
88
|
+
(values ?? []).map((value) => normalizeEngineRelativeInput(value, flag)),
|
|
89
|
+
];
|
|
90
|
+
const expectNew = normalize('--expect-new', options.expectNew);
|
|
91
|
+
const byFlag = [
|
|
92
|
+
normalize('--expect', options.expect),
|
|
93
|
+
normalize('--expect-unmanaged', options.expectUnmanaged),
|
|
94
|
+
expectNew,
|
|
95
|
+
normalize('--scan-file', options.scanFiles?.map((value) => splitScanFileOwner(value).path)),
|
|
96
|
+
];
|
|
97
|
+
const all = [...new Set(byFlag.flatMap(([, values]) => values))];
|
|
98
|
+
if (all.length === 0 || !(await isGitRepository(engineDir)))
|
|
99
|
+
return;
|
|
100
|
+
const changed = new Set();
|
|
101
|
+
try {
|
|
102
|
+
for (const chunk of chunkPathspecs(all)) {
|
|
103
|
+
for (const file of await getDirtyFiles(engineDir, chunk))
|
|
104
|
+
changed.add(file);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
// An engine git cannot answer for (no HEAD yet, a broken checkout) is
|
|
109
|
+
// the locked run's engine precondition to report, with its own remedy.
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
for (const [flag, values] of byFlag) {
|
|
113
|
+
const absent = values.filter((value) => !changed.has(value));
|
|
114
|
+
if (absent.length > 0) {
|
|
115
|
+
throw new InvalidArgumentError(`${flag} names no changed file in engine/: ${absent.join(', ')}. Each value must be a ` +
|
|
116
|
+
'path git status lists (modified, deleted or untracked); a clean or missing path is a ' +
|
|
117
|
+
'typo or a list that ended early, and the run would have ignored it.', flag);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const owners = new Map();
|
|
121
|
+
for (const patch of manifest.patches) {
|
|
122
|
+
for (const file of patch.filesAffected)
|
|
123
|
+
owners.set(file, patch.filename);
|
|
124
|
+
}
|
|
125
|
+
const owned = expectNew[1].filter((file) => owners.has(file));
|
|
126
|
+
if (owned.length > 0) {
|
|
127
|
+
throw new InvalidArgumentError(`--expect-new names a file a patch already owns: ` +
|
|
128
|
+
`${owned.map((file) => `${file} (${owners.get(file) ?? ''})`).join(', ')}. ` +
|
|
129
|
+
'--expect-new is for files no patch owns yet; drop it for these.', '--expect-new');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Runs every lock-free argument check: empty or unknown patch names
|
|
134
|
+
* (naming an engine file passed by mistake), option combinations, path
|
|
135
|
+
* flag values that are flags, and path flag values that name no changed
|
|
136
|
+
* file. Throws the refusal the locked run would have thrown, only sooner.
|
|
137
|
+
*
|
|
138
|
+
* @param projectRoot - Project root
|
|
139
|
+
* @param patches - Positional patch arguments as given
|
|
140
|
+
* @param options - Options as the command layer builds them
|
|
141
|
+
*/
|
|
142
|
+
export async function preflightReExportArguments(projectRoot, patches, options) {
|
|
143
|
+
const empty = patches.filter((patch) => patch.trim().length === 0).length;
|
|
144
|
+
if (empty > 0) {
|
|
145
|
+
throw new InvalidArgumentError(`re-export was given ${empty} empty patch argument(s). An empty argument is usually a ` +
|
|
146
|
+
'shell expansion that produced nothing (an unquoted "$@" in a heredoc, an unset ' +
|
|
147
|
+
'variable); pass patch identifiers, or --all.', 'patches');
|
|
148
|
+
}
|
|
149
|
+
const normalized = normalizeReExportFilesPositionals(patches, options);
|
|
150
|
+
validateReExportOptionCombinations(normalized.patches, normalized.options);
|
|
151
|
+
const paths = getProjectPaths(projectRoot);
|
|
152
|
+
const manifest = await loadPatchesManifest(paths.patches);
|
|
153
|
+
// No manifest: the locked run reports that with its own remedy.
|
|
154
|
+
if (!manifest || manifest.patches.length === 0)
|
|
155
|
+
return;
|
|
156
|
+
if (options.all !== true && options.scanFilesManifest === undefined) {
|
|
157
|
+
await resolvePositionals(paths.engine, normalized.patches, manifest);
|
|
158
|
+
}
|
|
159
|
+
await assertFlagPathsInStatus(paths.engine, normalized.options, manifest);
|
|
160
|
+
}
|
|
@@ -11,6 +11,7 @@ import { Command, Option } from 'commander';
|
|
|
11
11
|
import { withEngineSessionLock } from '../core/engine-session-lock.js';
|
|
12
12
|
import { addWaitLockOption, pickDefined, resolveWaitLockSeconds, stringListOption, } from '../utils/options.js';
|
|
13
13
|
import { reExportCommand } from './re-export.js';
|
|
14
|
+
import { preflightReExportArguments, readPathListFile } from './re-export-preflight.js';
|
|
14
15
|
/** Registers the re-export command on the CLI program. */
|
|
15
16
|
export function registerReExport(program, { getProjectRoot, withErrorHandling }) {
|
|
16
17
|
const reExport = program
|
|
@@ -20,17 +21,20 @@ export function registerReExport(program, { getProjectRoot, withErrorHandling })
|
|
|
20
21
|
'rebase for source metadata stamping.')
|
|
21
22
|
.option('-a, --all', 'Re-export all patches')
|
|
22
23
|
.option('-s, --scan', 'Scan directories for new/removed files and update filesAffected')
|
|
23
|
-
.option('--scan-file <path>', 'With --scan, add this explicit engine-relative file to one
|
|
24
|
+
.option('--scan-file <path>', 'With --scan, add this explicit engine-relative file without collecting adjacent files. Prefix the owning patch (<patch>:<path>, required with several target patches) to adopt into one of them; selected patches without a --scan-file refresh scan-less. Repeatable.', ...stringListOption())
|
|
24
25
|
.option('--scan-files <manifest>', 'With --scan, bulk-assign generated files from a JSON manifest: {"assignments":[{"patch":"002-name.patch","files":["path"]}]}. Selects patches from the manifest.')
|
|
25
26
|
.option('--files <paths>', 'Restrict the re-exported filesAffected to this comma-separated list (single target patch only)', (value) => value
|
|
26
27
|
.split(',')
|
|
27
28
|
.map((v) => v.trim())
|
|
28
29
|
.filter((v) => v.length > 0))
|
|
29
|
-
.option('--refuse-adjacent-unmanaged', 'Refuse a scan-less re-export (non-zero exit, patch not written) when unmanaged files exist adjacent to the patch ownership, instead of warning. Mutually exclusive with --scan and --files.')
|
|
30
|
-
.option('--refuse-foreign-drift', "Refuse a
|
|
30
|
+
.option('--refuse-adjacent-unmanaged', 'Refuse a scan-less re-export (non-zero exit, patch not written) when unmanaged files exist adjacent to the patch ownership, instead of warning. Adjacent means the same directory as an owned file (not a sibling or nested directory), or named by a registration line the patch adds. Mutually exclusive with --scan and --files.')
|
|
31
|
+
.option('--refuse-foreign-drift', "Refuse a re-export (non-zero exit, patch not written) when the refreshed body would add or drop lines, in a file the patch already owns, that the old patch body did not carry — protects multi-session checkouts from silently capturing another session's uncommitted edits. Beside --scan it guards each patch's existing files (adopted files count as expected). Mutually exclusive with --files.")
|
|
31
32
|
.option('--expect <path>', "With --refuse-foreign-drift, name an engine-relative file whose drift is this session's intended edit: drift confined to --expect files proceeds, drift anywhere else still refuses. Repeatable, and the whitelist applies across EVERY patch in this invocation, not per patch — so pass all the patches and all their --expect paths in one call rather than looping one patch at a time.", ...stringListOption())
|
|
32
33
|
.option('--expect-unmanaged <path>', 'With --refuse-adjacent-unmanaged, name an engine-relative file that is a REVIEWED, recorded exception to the adjacency refusal: an approved unmanaged path is still listed, but no longer refuses. Repeatable, and the carve-out applies across EVERY patch in this invocation, not per patch. Use it to keep the belt armed for everything else instead of dropping --refuse-adjacent-unmanaged entirely.', ...stringListOption())
|
|
33
|
-
.option('--expect-
|
|
34
|
+
.option('--expect-from <file>', 'Read --expect paths from a file, one engine-relative path per line (blank lines and # comments ignored). Adds to any --expect flags.')
|
|
35
|
+
.option('--expect-new <path>', 'With --refuse-adjacent-unmanaged, name a file this session created and will adopt with its next export (or --scan-file): it passes the belt and is reported, while every other adjacent file still refuses. Must be changed in engine/ and owned by no patch. Repeatable.', ...stringListOption())
|
|
36
|
+
.option('--adjacent-ignore <path|glob>', 'With --refuse-adjacent-unmanaged, a reviewed ignore-list entry: an engine-relative path, or a glob whose * stays within one path segment (** is refused). Matches are reported, never refused. Repeatable.', ...stringListOption())
|
|
37
|
+
.option('--expect-removed <path>', 'Retire a file from the selected patch while keeping scan-less refusals armed: an absent file the patch created, or a patch-modified upstream file whose engine copy is back at HEAD. Repeatable.', ...stringListOption())
|
|
34
38
|
.option('--dry-run', 'Show what would change without writing')
|
|
35
39
|
.option('--skip-lint', 'Skip patch lint checks (downgrade errors to warnings)')
|
|
36
40
|
.option('--no-cache', 'Bypass per-patch lint result cache reads and writes for this re-export')
|
|
@@ -42,19 +46,40 @@ export function registerReExport(program, { getProjectRoot, withErrorHandling })
|
|
|
42
46
|
.addOption(new Option('--tier <tier>', 'Force a tier override on the selected patch (only "branding" recognised). Mutually exclusive with --all.').choices(['branding']))
|
|
43
47
|
.option('--lint-ignore <check-id>', 'Append a lint check ID to the patch\'s PatchMetadata.lintIgnore (union, de-duped, repeatable). Mutually exclusive with --all. Use "fireforge patch lint-ignore" for --remove / --clear.', ...stringListOption());
|
|
44
48
|
addWaitLockOption(reExport).action(withErrorHandling(async (patches, options) => {
|
|
45
|
-
const { tier, lintIgnore, scanFile, scanFiles, cache, expect, expectUnmanaged, ...rest } = options;
|
|
46
49
|
const projectRoot = getProjectRoot();
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
...(lintIgnore !== undefined && lintIgnore.length > 0 ? { lintIgnore } : {}),
|
|
53
|
-
...(expect !== undefined && expect.length > 0 ? { expect } : {}),
|
|
54
|
-
...(expectUnmanaged !== undefined && expectUnmanaged.length > 0
|
|
55
|
-
? { expectUnmanaged }
|
|
56
|
-
: {}),
|
|
57
|
-
...(cache === false ? { noCache: true } : {}),
|
|
58
|
-
}), { waitLockSeconds: resolveWaitLockSeconds(options.waitLock) });
|
|
50
|
+
const reExportOptions = await toReExportOptions(options);
|
|
51
|
+
// Refuse what the locked run would refuse before queueing for the
|
|
52
|
+
// lock, which can be a long wait under contention.
|
|
53
|
+
await preflightReExportArguments(projectRoot, patches, reExportOptions);
|
|
54
|
+
await withEngineSessionLock(projectRoot, 're-export', () => reExportCommand(projectRoot, patches, reExportOptions), { waitLockSeconds: resolveWaitLockSeconds(options.waitLock) });
|
|
59
55
|
}));
|
|
60
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Maps commander's option names onto {@link ReExportOptions}, reading the
|
|
59
|
+
* `--expect-from` list into `expect` and dropping empty repeatables.
|
|
60
|
+
*/
|
|
61
|
+
async function toReExportOptions(options) {
|
|
62
|
+
const { tier, lintIgnore, scanFile, scanFiles, cache, expect: expectFlags, expectFrom, expectUnmanaged, expectNew, adjacentIgnore, ...rest } = options;
|
|
63
|
+
const expect = [
|
|
64
|
+
...(expectFlags ?? []),
|
|
65
|
+
...(expectFrom !== undefined ? await readPathListFile(expectFrom, '--expect-from') : []),
|
|
66
|
+
];
|
|
67
|
+
return {
|
|
68
|
+
...pickDefined({
|
|
69
|
+
...rest,
|
|
70
|
+
scanFiles: nonEmpty(scanFile),
|
|
71
|
+
scanFilesManifest: scanFiles,
|
|
72
|
+
tier: tier,
|
|
73
|
+
lintIgnore: nonEmpty(lintIgnore),
|
|
74
|
+
expect: nonEmpty(expect),
|
|
75
|
+
expectUnmanaged: nonEmpty(expectUnmanaged),
|
|
76
|
+
expectNew: nonEmpty(expectNew),
|
|
77
|
+
adjacentIgnore: nonEmpty(adjacentIgnore),
|
|
78
|
+
}),
|
|
79
|
+
...(cache === false ? { noCache: true } : {}),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/** The list when it has entries, else undefined (which `pickDefined` drops). */
|
|
83
|
+
function nonEmpty(values) {
|
|
84
|
+
return values !== undefined && values.length > 0 ? values : undefined;
|
|
85
|
+
}
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import type { PatchMetadata, ReExportOptions } from '../types/commands/index.js';
|
|
2
2
|
import type { ProjectPaths } from '../types/config.js';
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* Validates every retirement before any patch is written; typos never
|
|
5
|
+
* silently grant a carve-out. Two shapes retire a file from its one
|
|
6
|
+
* selected owner:
|
|
7
|
+
*
|
|
8
|
+
* - a file the patch created (`new file mode` section), now absent from
|
|
9
|
+
* disk and untracked in engine HEAD;
|
|
10
|
+
* - an upstream file the patch modified or deleted, whose engine copy is
|
|
11
|
+
* back at HEAD (present, tracked, no diff). A scan-less re-export keeps
|
|
12
|
+
* `filesAffected` as it is, so without this the stale entry would stay
|
|
13
|
+
* in the manifest and in the reserved-range policy check.
|
|
14
|
+
*/
|
|
4
15
|
export declare function validateExpectedRemovals(paths: ProjectPaths, patches: PatchMetadata[], options: ReExportOptions): Promise<string[]>;
|
|
@@ -2,12 +2,24 @@
|
|
|
2
2
|
import { lstat } from 'node:fs/promises';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { listTrackedInHead } from '../core/git-file-ops.js';
|
|
5
|
+
import { getDirtyFiles } from '../core/git-status.js';
|
|
5
6
|
import { parseDiffSections } from '../core/patch-parse.js';
|
|
6
7
|
import { InvalidArgumentError } from '../errors/base.js';
|
|
7
8
|
import { getNodeErrorCode } from '../utils/errors.js';
|
|
8
9
|
import { readText } from '../utils/fs.js';
|
|
9
10
|
import { normalizeEngineRelativeInput } from './re-export-scan.js';
|
|
10
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* Validates every retirement before any patch is written; typos never
|
|
13
|
+
* silently grant a carve-out. Two shapes retire a file from its one
|
|
14
|
+
* selected owner:
|
|
15
|
+
*
|
|
16
|
+
* - a file the patch created (`new file mode` section), now absent from
|
|
17
|
+
* disk and untracked in engine HEAD;
|
|
18
|
+
* - an upstream file the patch modified or deleted, whose engine copy is
|
|
19
|
+
* back at HEAD (present, tracked, no diff). A scan-less re-export keeps
|
|
20
|
+
* `filesAffected` as it is, so without this the stale entry would stay
|
|
21
|
+
* in the manifest and in the reserved-range policy check.
|
|
22
|
+
*/
|
|
11
23
|
export async function validateExpectedRemovals(paths, patches, options) {
|
|
12
24
|
const removed = [
|
|
13
25
|
...new Set((options.expectRemoved ?? []).map((file) => normalizeEngineRelativeInput(file, '--expect-removed'))),
|
|
@@ -15,19 +27,19 @@ export async function validateExpectedRemovals(paths, patches, options) {
|
|
|
15
27
|
if (removed.length === 0)
|
|
16
28
|
return removed;
|
|
17
29
|
const tracked = await listTrackedInHead(paths.engine, removed);
|
|
30
|
+
const dirty = new Set(await getDirtyFiles(paths.engine, [...tracked]));
|
|
18
31
|
for (const file of removed) {
|
|
19
32
|
const owners = patches.filter((patch) => patch.filesAffected.includes(file));
|
|
20
|
-
if (owners.length !== 1 ||
|
|
21
|
-
tracked.has(file) ||
|
|
22
|
-
(await removalPathPresent(join(paths.engine, file)))) {
|
|
23
|
-
throw new InvalidArgumentError(`--expect-removed ${file} must be absent, untracked in engine HEAD, and owned by exactly one selected patch.`, '--expect-removed');
|
|
24
|
-
}
|
|
25
33
|
const owner = owners[0];
|
|
26
|
-
if (
|
|
27
|
-
throw new InvalidArgumentError(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
34
|
+
if (owners.length !== 1 || owner === undefined) {
|
|
35
|
+
throw new InvalidArgumentError(`--expect-removed ${file} must be owned by exactly one selected patch.`, '--expect-removed');
|
|
36
|
+
}
|
|
37
|
+
const section = parseDiffSections(await readText(join(paths.patches, owner.filename))).find((candidate) => candidate.targetPath === file);
|
|
38
|
+
if (tracked.has(file)) {
|
|
39
|
+
assertUpstreamFileBackAtHead(file, owner, section !== undefined && !section.isNewFile, dirty);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
await assertCreatedFileAbsent(paths, file, owner, section?.isNewFile === true);
|
|
31
43
|
}
|
|
32
44
|
if (owner.filesAffected.every((path) => removed.includes(path))) {
|
|
33
45
|
throw new InvalidArgumentError('Retiring every file would empty the patch; use patch delete instead.', '--expect-removed');
|
|
@@ -35,6 +47,24 @@ export async function validateExpectedRemovals(paths, patches, options) {
|
|
|
35
47
|
}
|
|
36
48
|
return removed;
|
|
37
49
|
}
|
|
50
|
+
async function assertCreatedFileAbsent(paths, file, owner, createdByOwner) {
|
|
51
|
+
if (await removalPathPresent(join(paths.engine, file))) {
|
|
52
|
+
throw new InvalidArgumentError(`--expect-removed ${file} is untracked in engine HEAD and still present on disk; ` +
|
|
53
|
+
'a patch-created file can be retired only once it is deleted.', '--expect-removed');
|
|
54
|
+
}
|
|
55
|
+
if (!createdByOwner) {
|
|
56
|
+
throw new InvalidArgumentError(`--expect-removed ${file} was not created by its selected patch ${owner.filename}.`, '--expect-removed');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function assertUpstreamFileBackAtHead(file, owner, changedByOwner, dirty) {
|
|
60
|
+
if (dirty.has(file)) {
|
|
61
|
+
throw new InvalidArgumentError(`--expect-removed ${file} is tracked in engine HEAD, but its engine copy still differs ` +
|
|
62
|
+
'from HEAD. Restore the HEAD copy first, then retire it.', '--expect-removed');
|
|
63
|
+
}
|
|
64
|
+
if (!changedByOwner) {
|
|
65
|
+
throw new InvalidArgumentError(`--expect-removed ${file} is not changed by its selected patch ${owner.filename}.`, '--expect-removed');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
38
68
|
/** A dangling symlink is still an existing owned path, not an approved absence. */
|
|
39
69
|
async function removalPathPresent(path) {
|
|
40
70
|
try {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-file owners for `re-export --scan --scan-file <patch>:<path>`.
|
|
3
|
+
*
|
|
4
|
+
* One change often spans a patch that creates files and a patch that
|
|
5
|
+
* registers or imports them. With `--scan-file` bound to a single target
|
|
6
|
+
* patch, that change took two invocations, and the adopting one could not
|
|
7
|
+
* carry the belts the other did. Naming the owner per file lets one call
|
|
8
|
+
* adopt each file into its patch while every other selected patch
|
|
9
|
+
* refreshes scan-less, all under the same `--refuse-foreign-drift` belt
|
|
10
|
+
* and in a single engine-lock hold.
|
|
11
|
+
*/
|
|
12
|
+
import type { PatchesManifest, PatchMetadata, ReExportOptions } from '../types/commands/index.js';
|
|
13
|
+
/** A `--scan-file` value split into its optional owner and its path. */
|
|
14
|
+
export interface ScanFileOwnerSpec {
|
|
15
|
+
owner: string | undefined;
|
|
16
|
+
path: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Splits `<patch>:<path>` into owner and path. The owner is everything
|
|
20
|
+
* before the first `:`, and only when that part holds no path separator, so
|
|
21
|
+
* a plain path (even one with a later `:`) stays a path. A one-letter
|
|
22
|
+
* prefix before `:/` or `:\` reads as a Windows drive, not a patch.
|
|
23
|
+
*/
|
|
24
|
+
export declare function splitScanFileOwner(raw: string): ScanFileOwnerSpec;
|
|
25
|
+
/** Rejoins a split value, so normalization can rewrite the path half only. */
|
|
26
|
+
export declare function joinScanFileOwner(spec: ScanFileOwnerSpec): string;
|
|
27
|
+
/**
|
|
28
|
+
* Argument-shape check, before any manifest is read: a multi-patch call has
|
|
29
|
+
* to say which patch each file joins.
|
|
30
|
+
*/
|
|
31
|
+
export declare function validateScanFileOwners(patches: readonly string[], scanFiles: readonly string[]): void;
|
|
32
|
+
/**
|
|
33
|
+
* Resolves each `--scan-file` value to its owning selected patch. Returns
|
|
34
|
+
* `undefined` when no `--scan-file` was given. An unprefixed path belongs
|
|
35
|
+
* to the single selected patch (the historical form).
|
|
36
|
+
*/
|
|
37
|
+
export declare function assignScanFileOwners(scanFiles: readonly string[] | undefined, selectedPatches: readonly PatchMetadata[], manifest: PatchesManifest): Map<string, string[]> | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* The options one patch of the loop runs with. A patch with assigned files
|
|
40
|
+
* adopts exactly those; a selected patch without any refreshes scan-less,
|
|
41
|
+
* because `--scan` beside per-file owners scopes adoption to the named
|
|
42
|
+
* files rather than arming a broad directory scan for every other patch.
|
|
43
|
+
*/
|
|
44
|
+
export declare function scanOptionsForPatch(options: ReExportOptions, patchFilename: string, assignments: ReadonlyMap<string, string[]> | undefined): ReExportOptions;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Per-file owners for `re-export --scan --scan-file <patch>:<path>`.
|
|
4
|
+
*
|
|
5
|
+
* One change often spans a patch that creates files and a patch that
|
|
6
|
+
* registers or imports them. With `--scan-file` bound to a single target
|
|
7
|
+
* patch, that change took two invocations, and the adopting one could not
|
|
8
|
+
* carry the belts the other did. Naming the owner per file lets one call
|
|
9
|
+
* adopt each file into its patch while every other selected patch
|
|
10
|
+
* refreshes scan-less, all under the same `--refuse-foreign-drift` belt
|
|
11
|
+
* and in a single engine-lock hold.
|
|
12
|
+
*/
|
|
13
|
+
import { formatPatchNotFoundError } from '../core/patch-identifier-suggest.js';
|
|
14
|
+
import { resolvePatchIdentifier } from '../core/patch-manifest.js';
|
|
15
|
+
import { InvalidArgumentError } from '../errors/base.js';
|
|
16
|
+
/**
|
|
17
|
+
* Splits `<patch>:<path>` into owner and path. The owner is everything
|
|
18
|
+
* before the first `:`, and only when that part holds no path separator, so
|
|
19
|
+
* a plain path (even one with a later `:`) stays a path. A one-letter
|
|
20
|
+
* prefix before `:/` or `:\` reads as a Windows drive, not a patch.
|
|
21
|
+
*/
|
|
22
|
+
export function splitScanFileOwner(raw) {
|
|
23
|
+
const colon = raw.indexOf(':');
|
|
24
|
+
if (colon <= 0)
|
|
25
|
+
return { owner: undefined, path: raw };
|
|
26
|
+
const owner = raw.slice(0, colon).trim();
|
|
27
|
+
const path = raw.slice(colon + 1);
|
|
28
|
+
if (owner.length === 0 || /[/\\]/.test(owner))
|
|
29
|
+
return { owner: undefined, path: raw };
|
|
30
|
+
if (/^[A-Za-z]$/.test(owner) && /^[/\\]/.test(path))
|
|
31
|
+
return { owner: undefined, path: raw };
|
|
32
|
+
return { owner, path };
|
|
33
|
+
}
|
|
34
|
+
/** Rejoins a split value, so normalization can rewrite the path half only. */
|
|
35
|
+
export function joinScanFileOwner(spec) {
|
|
36
|
+
return spec.owner === undefined ? spec.path : `${spec.owner}:${spec.path}`;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Argument-shape check, before any manifest is read: a multi-patch call has
|
|
40
|
+
* to say which patch each file joins.
|
|
41
|
+
*/
|
|
42
|
+
export function validateScanFileOwners(patches, scanFiles) {
|
|
43
|
+
if (patches.length === 0) {
|
|
44
|
+
throw new InvalidArgumentError('--scan-file needs explicit target patch identifiers.', '--scan-file');
|
|
45
|
+
}
|
|
46
|
+
if (patches.length === 1)
|
|
47
|
+
return;
|
|
48
|
+
const unowned = scanFiles.filter((file) => splitScanFileOwner(file).owner === undefined);
|
|
49
|
+
if (unowned.length > 0) {
|
|
50
|
+
throw new InvalidArgumentError(`--scan-file in a call with ${patches.length} patches must name each file's owning patch ` +
|
|
51
|
+
`as <patch>:<path> (for example --scan-file ${patches[0] ?? '201'}:${unowned[0] ?? '<path>'}). ` +
|
|
52
|
+
`Missing an owner: ${unowned.join(', ')}.`, '--scan-file');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Resolves each `--scan-file` value to its owning selected patch. Returns
|
|
57
|
+
* `undefined` when no `--scan-file` was given. An unprefixed path belongs
|
|
58
|
+
* to the single selected patch (the historical form).
|
|
59
|
+
*/
|
|
60
|
+
export function assignScanFileOwners(scanFiles, selectedPatches, manifest) {
|
|
61
|
+
if (scanFiles === undefined)
|
|
62
|
+
return undefined;
|
|
63
|
+
const selected = new Set(selectedPatches.map((patch) => patch.filename));
|
|
64
|
+
const byPatch = new Map();
|
|
65
|
+
const ownerByFile = new Map();
|
|
66
|
+
for (const raw of scanFiles) {
|
|
67
|
+
const { owner, path } = splitScanFileOwner(raw);
|
|
68
|
+
const filename = resolveOwner(owner, selectedPatches, manifest);
|
|
69
|
+
if (!selected.has(filename)) {
|
|
70
|
+
throw new InvalidArgumentError(`--scan-file ${raw}: ${filename} is not among the selected patches. ` +
|
|
71
|
+
'Pass it as a positional patch too.', '--scan-file');
|
|
72
|
+
}
|
|
73
|
+
const previous = ownerByFile.get(path);
|
|
74
|
+
if (previous !== undefined && previous !== filename) {
|
|
75
|
+
throw new InvalidArgumentError(`--scan-file path is assigned to more than one patch: ${path} (${previous}, ${filename})`, '--scan-file');
|
|
76
|
+
}
|
|
77
|
+
ownerByFile.set(path, filename);
|
|
78
|
+
const files = byPatch.get(filename) ?? [];
|
|
79
|
+
if (!files.includes(path))
|
|
80
|
+
files.push(path);
|
|
81
|
+
byPatch.set(filename, files);
|
|
82
|
+
}
|
|
83
|
+
for (const files of byPatch.values())
|
|
84
|
+
files.sort();
|
|
85
|
+
return byPatch;
|
|
86
|
+
}
|
|
87
|
+
function resolveOwner(owner, selectedPatches, manifest) {
|
|
88
|
+
if (owner === undefined) {
|
|
89
|
+
const only = selectedPatches.length === 1 ? selectedPatches[0] : undefined;
|
|
90
|
+
if (only === undefined) {
|
|
91
|
+
throw new InvalidArgumentError('--scan-file in a multi-patch call must name its owning patch as <patch>:<path>.', '--scan-file');
|
|
92
|
+
}
|
|
93
|
+
return only.filename;
|
|
94
|
+
}
|
|
95
|
+
const match = resolvePatchIdentifier(owner, manifest.patches);
|
|
96
|
+
if (!match) {
|
|
97
|
+
throw new InvalidArgumentError(`--scan-file: ${formatPatchNotFoundError(owner, manifest.patches)}`, '--scan-file');
|
|
98
|
+
}
|
|
99
|
+
return match.filename;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The options one patch of the loop runs with. A patch with assigned files
|
|
103
|
+
* adopts exactly those; a selected patch without any refreshes scan-less,
|
|
104
|
+
* because `--scan` beside per-file owners scopes adoption to the named
|
|
105
|
+
* files rather than arming a broad directory scan for every other patch.
|
|
106
|
+
*/
|
|
107
|
+
export function scanOptionsForPatch(options, patchFilename, assignments) {
|
|
108
|
+
if (assignments === undefined)
|
|
109
|
+
return options;
|
|
110
|
+
const assigned = assignments.get(patchFilename);
|
|
111
|
+
if (assigned !== undefined)
|
|
112
|
+
return { ...options, scanFiles: assigned };
|
|
113
|
+
const scanLess = { ...options, scan: false };
|
|
114
|
+
delete scanLess.scanFiles;
|
|
115
|
+
return scanLess;
|
|
116
|
+
}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
+
import { type PatchQueueContext } from '../core/patch-lint.js';
|
|
1
2
|
import type { PatchesManifest } from '../types/commands/index.js';
|
|
3
|
+
import type { FireForgeConfig } from '../types/config.js';
|
|
2
4
|
export interface ScanResult {
|
|
3
5
|
updated: string[];
|
|
4
6
|
added: string[];
|
|
5
7
|
removed: string[];
|
|
6
8
|
}
|
|
7
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* Normalizes repeatable `--scan-file` inputs into safe engine-relative
|
|
11
|
+
* paths, keeping an optional `<patch>:` owner prefix intact.
|
|
12
|
+
*/
|
|
8
13
|
export declare function normalizeScanFiles(scanFiles: readonly string[] | undefined): string[] | undefined;
|
|
9
14
|
/** Normalizes one CLI-provided path into a safe engine-relative path. */
|
|
10
15
|
export declare function normalizeEngineRelativeInput(rawPath: string, flagName: string): string;
|
|
@@ -44,15 +49,18 @@ export declare function assertScanAdoptionsHaveNoForwardImports(args: {
|
|
|
44
49
|
engineDir: string;
|
|
45
50
|
patchFilename: string;
|
|
46
51
|
added: readonly string[];
|
|
52
|
+
/**
|
|
53
|
+
* Threads `patchLint.forwardRegistration` so jar.mn registrations are judged
|
|
54
|
+
* too, and `patchPolicy`, so the remedy names a legal order.
|
|
55
|
+
*/
|
|
56
|
+
config?: FireForgeConfig;
|
|
47
57
|
}): Promise<void>;
|
|
48
|
-
/**
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
*/
|
|
55
|
-
export declare function formatForwardImportRemedy(reRunHint: string): string;
|
|
58
|
+
/** The manifest rows a queue context carries, for remedy computations. */
|
|
59
|
+
export declare function manifestFromQueue(ctx: PatchQueueContext): PatchesManifest;
|
|
60
|
+
/** `{ category }` of the named queue entry, or `{}` when it has no metadata. */
|
|
61
|
+
export declare function categoryOf(ctx: PatchQueueContext, filename: string): {
|
|
62
|
+
category?: string;
|
|
63
|
+
};
|
|
56
64
|
/** Refuses explicit `--scan-file` additions that did not produce patch hunks. */
|
|
57
65
|
export declare function assertScanFileAdditionsHaveDiffHunks(args: {
|
|
58
66
|
diffContent: string;
|