@hominis/fireforge 0.40.0 → 0.42.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 +85 -0
- package/README.md +16 -10
- package/dist/bin/fireforge.js +25 -4
- package/dist/build-info.json +9 -0
- package/dist/src/cli.js +20 -3
- package/dist/src/commands/bootstrap.js +5 -1
- package/dist/src/commands/discard.js +2 -22
- package/dist/src/commands/doctor/post-rebase-audit.js +27 -4
- package/dist/src/commands/doctor-check-core.d.ts +20 -0
- package/dist/src/commands/doctor-check-core.js +28 -0
- package/dist/src/commands/doctor-external-toolchains.js +2 -0
- package/dist/src/commands/doctor-furnace-manifest-sync.js +144 -46
- package/dist/src/commands/doctor-furnace.js +13 -11
- package/dist/src/commands/doctor-orphaned-harness.js +2 -1
- package/dist/src/commands/doctor.js +2 -2
- package/dist/src/commands/export-all.js +9 -1
- package/dist/src/commands/export-flow.js +13 -3
- package/dist/src/commands/export-placement-conflicts.d.ts +17 -0
- package/dist/src/commands/export-placement-conflicts.js +74 -0
- package/dist/src/commands/export-placement-gate.js +1 -1
- package/dist/src/commands/export-shared.d.ts +8 -1
- package/dist/src/commands/export-shared.js +16 -7
- package/dist/src/commands/export.js +2 -2
- package/dist/src/commands/furnace/apply.js +4 -3
- package/dist/src/commands/furnace/chrome-doc-remove.js +13 -2
- package/dist/src/commands/furnace/chrome-doc.js +13 -2
- package/dist/src/commands/furnace/create-browser-test.d.ts +5 -0
- package/dist/src/commands/furnace/create-browser-test.js +5 -0
- package/dist/src/commands/furnace/create.js +2 -2
- package/dist/src/commands/furnace/diff.js +2 -1
- package/dist/src/commands/furnace/init.js +4 -6
- package/dist/src/commands/furnace/override.js +2 -2
- package/dist/src/commands/furnace/remove-mutations.d.ts +40 -0
- package/dist/src/commands/furnace/remove-mutations.js +171 -0
- package/dist/src/commands/furnace/remove.js +21 -128
- package/dist/src/commands/furnace/rename-browser-test.js +54 -5
- package/dist/src/commands/furnace/scan.js +27 -36
- package/dist/src/commands/furnace/status.js +30 -28
- package/dist/src/commands/furnace/validate.js +25 -18
- package/dist/src/commands/import.js +6 -20
- package/dist/src/commands/lint-per-patch.js +12 -59
- package/dist/src/commands/lint-per-run-checkjs.d.ts +64 -0
- package/dist/src/commands/lint-per-run-checkjs.js +102 -0
- package/dist/src/commands/lint.js +4 -4
- package/dist/src/commands/manifest.js +1 -1
- package/dist/src/commands/package.js +9 -16
- package/dist/src/commands/patch/compact.js +5 -5
- package/dist/src/commands/patch/delete.js +5 -5
- package/dist/src/commands/patch/lint-ignore.js +3 -2
- package/dist/src/commands/patch/move-files-create.js +22 -4
- package/dist/src/commands/patch/move-files-into.js +14 -10
- package/dist/src/commands/patch/move-files.js +6 -5
- package/dist/src/commands/patch/rename.d.ts +2 -2
- package/dist/src/commands/patch/rename.js +151 -50
- package/dist/src/commands/patch/reorder.d.ts +9 -0
- package/dist/src/commands/patch/reorder.js +14 -10
- package/dist/src/commands/patch/split-plan.d.ts +13 -6
- package/dist/src/commands/patch/split-plan.js +22 -12
- package/dist/src/commands/patch/split.d.ts +1 -1
- package/dist/src/commands/patch/split.js +23 -9
- package/dist/src/commands/patch/staged-dependency-validate.d.ts +15 -0
- package/dist/src/commands/patch/staged-dependency-validate.js +49 -0
- package/dist/src/commands/patch/staged-dependency.js +10 -0
- package/dist/src/commands/re-export-adjacent.js +5 -6
- package/dist/src/commands/re-export-bulk-scan.d.ts +13 -0
- package/dist/src/commands/re-export-bulk-scan.js +142 -2
- package/dist/src/commands/re-export-drift.d.ts +74 -0
- package/dist/src/commands/re-export-drift.js +234 -0
- package/dist/src/commands/re-export-files.js +5 -3
- package/dist/src/commands/re-export-lint.d.ts +97 -0
- package/dist/src/commands/re-export-lint.js +210 -0
- package/dist/src/commands/re-export-options.js +8 -0
- package/dist/src/commands/re-export-register.d.ts +18 -0
- package/dist/src/commands/re-export-register.js +61 -0
- package/dist/src/commands/re-export-scan.js +13 -7
- package/dist/src/commands/re-export-single.d.ts +20 -0
- package/dist/src/commands/re-export-single.js +261 -0
- package/dist/src/commands/re-export.d.ts +0 -4
- package/dist/src/commands/re-export.js +113 -268
- package/dist/src/commands/rebase/abort.js +13 -4
- package/dist/src/commands/rebase/continue.js +11 -4
- package/dist/src/commands/rebase/index.js +19 -29
- package/dist/src/commands/rebase/patch-loop.js +12 -0
- package/dist/src/commands/reset.js +4 -23
- package/dist/src/commands/resolve.js +2 -9
- package/dist/src/commands/run.js +11 -21
- package/dist/src/commands/source.js +3 -9
- package/dist/src/commands/status-check.d.ts +13 -0
- package/dist/src/commands/status-check.js +31 -13
- package/dist/src/commands/status-json.d.ts +31 -0
- package/dist/src/commands/status-json.js +76 -0
- package/dist/src/commands/status-output.d.ts +1 -0
- package/dist/src/commands/status-output.js +10 -1
- package/dist/src/commands/status-ownership.d.ts +52 -0
- package/dist/src/commands/status-ownership.js +57 -0
- package/dist/src/commands/status.js +184 -175
- package/dist/src/commands/test-build-phase.d.ts +19 -0
- package/dist/src/commands/test-build-phase.js +168 -0
- package/dist/src/commands/test-diagnose.js +30 -2
- package/dist/src/commands/test-modes.d.ts +9 -1
- package/dist/src/commands/test-modes.js +46 -13
- package/dist/src/commands/test-register.js +59 -27
- package/dist/src/commands/test-run.d.ts +29 -3
- package/dist/src/commands/test-run.js +41 -4
- package/dist/src/commands/test-verdict.d.ts +32 -0
- package/dist/src/commands/test-verdict.js +48 -0
- package/dist/src/commands/test.d.ts +9 -0
- package/dist/src/commands/test.js +100 -123
- package/dist/src/commands/token-coverage.js +4 -9
- package/dist/src/commands/tree.d.ts +3 -0
- package/dist/src/commands/tree.js +171 -25
- package/dist/src/commands/verify.js +2 -2
- package/dist/src/commands/watch.js +22 -21
- package/dist/src/commands/wire.js +1 -1
- package/dist/src/core/ast-utils.d.ts +11 -0
- package/dist/src/core/ast-utils.js +11 -0
- package/dist/src/core/branding.d.ts +16 -0
- package/dist/src/core/branding.js +18 -2
- package/dist/src/core/build-audit-platform.js +6 -1
- package/dist/src/core/build-audit-registration.js +2 -0
- package/dist/src/core/build-audit-resolve.d.ts +13 -0
- package/dist/src/core/build-audit-resolve.js +34 -13
- package/dist/src/core/build-audit.js +4 -20
- package/dist/src/core/build-baseline-types.d.ts +29 -8
- package/dist/src/core/build-baseline-types.js +2 -1
- package/dist/src/core/build-baseline.d.ts +10 -2
- package/dist/src/core/build-baseline.js +39 -8
- package/dist/src/core/build-prepare.d.ts +4 -0
- package/dist/src/core/build-prepare.js +7 -1
- package/dist/src/core/config-paths.d.ts +1 -1
- package/dist/src/core/config-paths.js +2 -0
- package/dist/src/core/config-validate-test-toolchains.js +3 -2
- package/dist/src/core/config-validate.js +34 -36
- package/dist/src/core/config.js +16 -3
- package/dist/src/core/coverage-extend.d.ts +89 -0
- package/dist/src/core/coverage-extend.js +188 -0
- package/dist/src/core/destructive.js +3 -1
- package/dist/src/core/discard-baseline.js +18 -4
- package/dist/src/core/engine-precondition.d.ts +35 -0
- package/dist/src/core/engine-precondition.js +50 -0
- package/dist/src/core/engine-session-lock.d.ts +38 -1
- package/dist/src/core/engine-session-lock.js +78 -4
- package/dist/src/core/file-lock.d.ts +7 -0
- package/dist/src/core/file-lock.js +20 -30
- package/dist/src/core/firefox.js +5 -2
- package/dist/src/core/furnace-apply-helpers.d.ts +12 -1
- package/dist/src/core/furnace-apply-helpers.js +12 -3
- package/dist/src/core/furnace-apply-output.js +9 -0
- package/dist/src/core/furnace-apply-overwrite-warn.d.ts +63 -0
- package/dist/src/core/furnace-apply-overwrite-warn.js +123 -0
- package/dist/src/core/furnace-apply-undeploy-actions.d.ts +11 -0
- package/dist/src/core/furnace-apply-undeploy-actions.js +49 -0
- package/dist/src/core/furnace-apply.d.ts +16 -4
- package/dist/src/core/furnace-apply.js +39 -43
- package/dist/src/core/furnace-config.js +2 -0
- package/dist/src/core/furnace-css-fragments.js +2 -0
- package/dist/src/core/furnace-jsconfig.js +14 -7
- package/dist/src/core/furnace-registration-ast.js +8 -8
- package/dist/src/core/furnace-registration-remove.js +6 -3
- package/dist/src/core/furnace-registration.js +9 -12
- package/dist/src/core/furnace-rollback.js +1 -1
- package/dist/src/core/furnace-scanner.js +2 -1
- package/dist/src/core/furnace-stale-export.js +2 -1
- package/dist/src/core/furnace-state-persist.d.ts +4 -6
- package/dist/src/core/furnace-validate-compatibility.js +4 -7
- package/dist/src/core/furnace-validate-helpers.js +33 -1
- package/dist/src/core/furnace-validate-registration.js +16 -0
- package/dist/src/core/furnace-validate.js +4 -2
- package/dist/src/core/git-diff.js +37 -2
- package/dist/src/core/lint-cache.d.ts +1 -1
- package/dist/src/core/lint-cache.js +33 -10
- package/dist/src/core/mach-build-artifacts.d.ts +63 -1
- package/dist/src/core/mach-build-artifacts.js +108 -2
- package/dist/src/core/mach-mozconfig.js +9 -1
- package/dist/src/core/mach-resource-shim.js +1 -0
- package/dist/src/core/marionette-port.d.ts +16 -0
- package/dist/src/core/marionette-port.js +93 -8
- package/dist/src/core/marionette-preflight.js +6 -5
- package/dist/src/core/patch-apply.d.ts +20 -13
- package/dist/src/core/patch-apply.js +32 -33
- package/dist/src/core/patch-export.js +3 -5
- package/dist/src/core/patch-lint-checkjs.d.ts +25 -3
- package/dist/src/core/patch-lint-checkjs.js +78 -7
- package/dist/src/core/patch-lint-chrome-jsdoc.js +9 -0
- package/dist/src/core/patch-lint-creators.d.ts +30 -0
- package/dist/src/core/patch-lint-creators.js +45 -0
- package/dist/src/core/patch-lint-cross.d.ts +1 -17
- package/dist/src/core/patch-lint-cross.js +16 -32
- package/dist/src/core/patch-lint-jsdoc.d.ts +3 -1
- package/dist/src/core/patch-lint-jsdoc.js +15 -5
- package/dist/src/core/patch-lint-module-registration.d.ts +15 -0
- package/dist/src/core/patch-lint-module-registration.js +88 -0
- package/dist/src/core/patch-lint-mozbuild.js +99 -11
- package/dist/src/core/patch-lint-reexports.d.ts +1 -0
- package/dist/src/core/patch-lint-reexports.js +1 -0
- package/dist/src/core/patch-lint.d.ts +8 -0
- package/dist/src/core/patch-lint.js +84 -6
- package/dist/src/core/patch-lock.d.ts +12 -1
- package/dist/src/core/patch-lock.js +40 -4
- package/dist/src/core/patch-manifest-consistency.js +15 -3
- package/dist/src/core/patch-parse.d.ts +8 -0
- package/dist/src/core/patch-parse.js +10 -0
- package/dist/src/core/patch-policy.d.ts +7 -0
- package/dist/src/core/patch-policy.js +4 -1
- package/dist/src/core/patch-transform.d.ts +10 -0
- package/dist/src/core/patch-transform.js +13 -2
- package/dist/src/core/rebase-session.d.ts +44 -2
- package/dist/src/core/rebase-session.js +114 -17
- package/dist/src/core/register-browser-content.js +2 -1
- package/dist/src/core/register-shared-css.js +2 -1
- package/dist/src/core/smoke-patterns.d.ts +0 -8
- package/dist/src/core/smoke-patterns.js +3 -2
- package/dist/src/core/status-binary.d.ts +55 -0
- package/dist/src/core/status-binary.js +124 -0
- package/dist/src/core/status-classify.d.ts +6 -1
- package/dist/src/core/status-classify.js +101 -73
- package/dist/src/core/test-harness-crash.d.ts +32 -1
- package/dist/src/core/test-harness-crash.js +59 -4
- package/dist/src/core/test-path-scope.js +2 -0
- package/dist/src/core/test-stale-check.js +1 -20
- package/dist/src/core/toolchain-preflight.js +24 -26
- package/dist/src/core/tree-guard.d.ts +1 -1
- package/dist/src/core/tree-guard.js +67 -12
- package/dist/src/core/tree-store.d.ts +86 -10
- package/dist/src/core/tree-store.js +310 -72
- package/dist/src/core/typecheck.js +2 -1
- package/dist/src/core/wire-destroy.js +14 -3
- package/dist/src/core/wire-init.js +37 -40
- package/dist/src/core/wire-subscript.js +11 -3
- package/dist/src/core/wire-utils.js +4 -4
- package/dist/src/core/xpcshell-appdir.js +3 -0
- package/dist/src/errors/base.d.ts +13 -0
- package/dist/src/errors/base.js +13 -0
- package/dist/src/errors/rebase.d.ts +13 -0
- package/dist/src/errors/rebase.js +17 -0
- package/dist/src/types/commands/options.d.ts +86 -1
- package/dist/src/types/commands/patches.d.ts +8 -0
- package/dist/src/types/furnace.d.ts +7 -0
- package/dist/src/utils/build-info.d.ts +31 -0
- package/dist/src/utils/build-info.js +98 -0
- package/dist/src/utils/concurrency.d.ts +8 -0
- package/dist/src/utils/concurrency.js +21 -0
- package/dist/src/utils/errors.d.ts +39 -0
- package/dist/src/utils/errors.js +55 -0
- package/dist/src/utils/fs.js +5 -12
- package/dist/src/utils/logger.d.ts +14 -2
- package/dist/src/utils/logger.js +43 -13
- package/dist/src/utils/options.d.ts +21 -4
- package/dist/src/utils/options.js +26 -6
- package/dist/src/utils/package-root.js +7 -4
- package/dist/src/utils/process-group.js +13 -2
- package/dist/src/utils/stdio-drain.d.ts +28 -0
- package/dist/src/utils/stdio-drain.js +54 -0
- package/dist/src/utils/validation.d.ts +23 -2
- package/dist/src/utils/validation.js +29 -3
- package/package.json +9 -9
|
@@ -9,10 +9,13 @@ import { buildModifiedFileAdditionsFromDiff, buildPatchQueueContext, detectNewFi
|
|
|
9
9
|
import { getClaimedFiles } from '../core/patch-manifest.js';
|
|
10
10
|
import { extractNewFileContentFromDiff } from '../core/patch-transform.js';
|
|
11
11
|
import { GeneralError, InvalidArgumentError } from '../errors/base.js';
|
|
12
|
+
import { mapWithConcurrency } from '../utils/concurrency.js';
|
|
12
13
|
import { pathExists } from '../utils/fs.js';
|
|
13
14
|
import { cancel, info, isCancel, warn } from '../utils/logger.js';
|
|
14
15
|
import { isContainedRelativePath, normalizePathSlashes, stripEnginePrefix, } from '../utils/paths.js';
|
|
15
16
|
const SCAN_ADD_COUNT_THRESHOLD = 3;
|
|
17
|
+
/** Concurrency bound for existence probes (matches the classify/lint pools). */
|
|
18
|
+
const PATH_PROBE_CONCURRENCY = 8;
|
|
16
19
|
const SCAN_DIR_COUNT_THRESHOLD = 2;
|
|
17
20
|
/** Normalizes repeatable `--scan-file` inputs into safe engine-relative paths. */
|
|
18
21
|
export function normalizeScanFiles(scanFiles) {
|
|
@@ -70,11 +73,18 @@ async function scanPatchFilesTargeted(args) {
|
|
|
70
73
|
const currentSet = new Set(currentFilesAffected);
|
|
71
74
|
const claimedByOthers = getClaimedFiles(manifest, patchFilename);
|
|
72
75
|
const added = [];
|
|
76
|
+
// Phase-split for bounded concurrency: the sync claimed-by-others check
|
|
77
|
+
// first (first offender in argument order, as before), then pooled
|
|
78
|
+
// existence probes with the refusal chosen by ordered iteration so the
|
|
79
|
+
// FIRST missing file in argument order is named deterministically.
|
|
73
80
|
for (const file of scanFiles) {
|
|
74
81
|
if (claimedByOthers.has(file)) {
|
|
75
82
|
throw new InvalidArgumentError(`--scan-file path is already claimed by another patch: ${file}`, '--scan-file');
|
|
76
83
|
}
|
|
77
|
-
|
|
84
|
+
}
|
|
85
|
+
const exists = await mapWithConcurrency(scanFiles, PATH_PROBE_CONCURRENCY, (file) => pathExists(join(engineDir, file)));
|
|
86
|
+
for (const [index, file] of scanFiles.entries()) {
|
|
87
|
+
if (exists[index] !== true) {
|
|
78
88
|
throw new InvalidArgumentError(`--scan-file path not found in engine/: ${file}`, '--scan-file');
|
|
79
89
|
}
|
|
80
90
|
if (!currentSet.has(file))
|
|
@@ -84,12 +94,8 @@ async function scanPatchFilesTargeted(args) {
|
|
|
84
94
|
return reportScanResult(currentFilesAffected, patchFilename, isDryRun, added.sort(), removed);
|
|
85
95
|
}
|
|
86
96
|
async function findRemovedFiles(files, engineDir) {
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
if (!(await pathExists(join(engineDir, file))))
|
|
90
|
-
removed.push(file);
|
|
91
|
-
}
|
|
92
|
-
return removed.sort();
|
|
97
|
+
const exists = await mapWithConcurrency(files, PATH_PROBE_CONCURRENCY, (file) => pathExists(join(engineDir, file)));
|
|
98
|
+
return files.filter((_, index) => exists[index] !== true).sort();
|
|
93
99
|
}
|
|
94
100
|
function reportScanResult(currentFilesAffected, patchFilename, isDryRun, added, removed) {
|
|
95
101
|
for (const f of added)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single-patch refresh core for `fireforge re-export`, split out of
|
|
3
|
+
* `re-export.ts` (at the per-file line budget after the FORGE J1/J2
|
|
4
|
+
* hoisted-lint and foreign-drift wiring). The orchestrator loop calls
|
|
5
|
+
* {@link reExportSinglePatchWithIndexLockRetry} per selected patch.
|
|
6
|
+
*/
|
|
7
|
+
import { getProjectPaths } from '../core/config.js';
|
|
8
|
+
import type { PatchesManifest, PatchMetadata, ReExportOptions } from '../types/commands/index.js';
|
|
9
|
+
import type { FireForgeConfig } from '../types/config.js';
|
|
10
|
+
import type { AdjacentUnmanagedContext } from './re-export-adjacent.js';
|
|
11
|
+
import type { ForeignDriftContext } from './re-export-drift.js';
|
|
12
|
+
import { type ReExportLintContext } from './re-export-lint.js';
|
|
13
|
+
/**
|
|
14
|
+
* Retries a single patch re-export exactly once when the failure is
|
|
15
|
+
* transient git `index.lock` contention (an external git process holding
|
|
16
|
+
* the engine repo's index). The retry re-enters `reExportSinglePatch`
|
|
17
|
+
* from the top, so it re-reads clean state; a second lock failure
|
|
18
|
+
* propagates to the loop's honest "N of M" accounting.
|
|
19
|
+
*/
|
|
20
|
+
export declare function reExportSinglePatchWithIndexLockRetry(patch: PatchMetadata, paths: ReturnType<typeof getProjectPaths>, manifest: PatchesManifest, options: ReExportOptions, isDryRun: boolean, config: FireForgeConfig, adjacentCtx: AdjacentUnmanagedContext, driftCtx: ForeignDriftContext, lintCtx: ReExportLintContext): Promise<boolean>;
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Single-patch refresh core for `fireforge re-export`, split out of
|
|
4
|
+
* `re-export.ts` (at the per-file line budget after the FORGE J1/J2
|
|
5
|
+
* hoisted-lint and foreign-drift wiring). The orchestrator loop calls
|
|
6
|
+
* {@link reExportSinglePatchWithIndexLockRetry} per selected patch.
|
|
7
|
+
*/
|
|
8
|
+
import { getProjectPaths } from '../core/config.js';
|
|
9
|
+
import { enforceFreshFurnaceSources } from '../core/furnace-stale-export.js';
|
|
10
|
+
import { getDiffForFilesAgainstHead } from '../core/git-diff.js';
|
|
11
|
+
import { updatePatchAndMetadata } from '../core/patch-export.js';
|
|
12
|
+
import { buildProjectedManifest, enforcePatchPolicy } from '../core/patch-policy.js';
|
|
13
|
+
import { isGitIndexLockConflict } from '../errors/git.js';
|
|
14
|
+
import { info, success, warn } from '../utils/logger.js';
|
|
15
|
+
import { findMissingFiles, reportAdjacentUnmanagedFiles } from './re-export-adjacent.js';
|
|
16
|
+
import { reportForeignDrift } from './re-export-drift.js';
|
|
17
|
+
import { lintReExportedPatch, refreshQueueCtxEntry, storeReExportLintResult, } from './re-export-lint.js';
|
|
18
|
+
import { assertScanAdoptionsHaveNoForwardImports, assertScanFileAdditionsHaveDiffHunks, confirmBroadScanAdditions, scanPatchFilesForReExport, } from './re-export-scan.js';
|
|
19
|
+
const GIT_INDEX_LOCK_RETRY_MS = 300;
|
|
20
|
+
/**
|
|
21
|
+
* Retries a single patch re-export exactly once when the failure is
|
|
22
|
+
* transient git `index.lock` contention (an external git process holding
|
|
23
|
+
* the engine repo's index). The retry re-enters `reExportSinglePatch`
|
|
24
|
+
* from the top, so it re-reads clean state; a second lock failure
|
|
25
|
+
* propagates to the loop's honest "N of M" accounting.
|
|
26
|
+
*/
|
|
27
|
+
export async function reExportSinglePatchWithIndexLockRetry(patch, paths, manifest, options, isDryRun, config, adjacentCtx, driftCtx, lintCtx) {
|
|
28
|
+
try {
|
|
29
|
+
return await reExportSinglePatch(patch, paths, manifest, options, isDryRun, config, adjacentCtx, driftCtx, lintCtx);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
if (!isGitIndexLockConflict(error)) {
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
warn(`${patch.filename}: git index.lock contention detected — retrying once after ${String(GIT_INDEX_LOCK_RETRY_MS)} ms...`);
|
|
36
|
+
await new Promise((resolve) => setTimeout(resolve, GIT_INDEX_LOCK_RETRY_MS));
|
|
37
|
+
return await reExportSinglePatch(patch, paths, manifest, options, isDryRun, config, adjacentCtx, driftCtx, lintCtx);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async function reExportSinglePatch(patch, paths, manifest, options, isDryRun, config, adjacentCtx, driftCtx, lintCtx) {
|
|
41
|
+
let currentFilesAffected = [...patch.filesAffected];
|
|
42
|
+
// --- Scan for new/removed files ---
|
|
43
|
+
if (options.scan) {
|
|
44
|
+
const scanResult = await scanPatchFilesForReExport({
|
|
45
|
+
currentFilesAffected,
|
|
46
|
+
engineDir: paths.engine,
|
|
47
|
+
manifest,
|
|
48
|
+
patchFilename: patch.filename,
|
|
49
|
+
isDryRun,
|
|
50
|
+
...(options.scanFiles !== undefined ? { scanFiles: options.scanFiles } : {}),
|
|
51
|
+
});
|
|
52
|
+
// Forward-import gate at adoption time (runs in dry-run too): refuse
|
|
53
|
+
// to adopt unmanaged files that import modules created by LATER
|
|
54
|
+
// patches — the after-the-fact lint failure this leaves behind is the
|
|
55
|
+
// field-reported footgun. Applies to broad --scan, --scan-file, and
|
|
56
|
+
// the --scan-files bulk assignments alike.
|
|
57
|
+
await assertScanAdoptionsHaveNoForwardImports({
|
|
58
|
+
patchesDir: paths.patches,
|
|
59
|
+
engineDir: paths.engine,
|
|
60
|
+
patchFilename: patch.filename,
|
|
61
|
+
added: scanResult.added,
|
|
62
|
+
});
|
|
63
|
+
if (options.scanFiles === undefined) {
|
|
64
|
+
const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
|
|
65
|
+
const proceed = await confirmBroadScanAdditions({
|
|
66
|
+
patchFilename: patch.filename,
|
|
67
|
+
added: scanResult.added,
|
|
68
|
+
isDryRun,
|
|
69
|
+
yes: options.yes === true,
|
|
70
|
+
isInteractive,
|
|
71
|
+
});
|
|
72
|
+
if (!proceed) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
currentFilesAffected = scanResult.updated;
|
|
77
|
+
}
|
|
78
|
+
else if (options.files === undefined) {
|
|
79
|
+
// Finding #16: when neither `--scan` nor `--files` is set and some
|
|
80
|
+
// of the manifest's claimed files no longer exist on disk, the
|
|
81
|
+
// re-export silently writes a refreshed body whose filesAffected
|
|
82
|
+
// still names the vanished paths. That is the documented contract,
|
|
83
|
+
// but it is also a footgun — a later `verify` then fails on
|
|
84
|
+
// manifest-consistency with no obvious trigger. Emit one advisory
|
|
85
|
+
// warning up-front when we can detect the drift cheaply, so the
|
|
86
|
+
// operator has a chance to re-run with `--scan` or `--files`
|
|
87
|
+
// before the stale filesAffected lands in patches.json.
|
|
88
|
+
const args = { patch, paths, manifest, currentFilesAffected, ctx: adjacentCtx };
|
|
89
|
+
if (await reportAdjacentUnmanagedFiles(args))
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
// --- Explicit file-subset path ---
|
|
93
|
+
// When --files is given, the target filesAffected is authoritative — drop
|
|
94
|
+
// anything not in the list, add anything new. This is the surgical repair
|
|
95
|
+
// primitive that replaces hand-editing patches.json; the user has already
|
|
96
|
+
// acknowledged via confirmDestructive (done in the caller) that any drop
|
|
97
|
+
// is intentional.
|
|
98
|
+
if (options.files !== undefined) {
|
|
99
|
+
const requested = [...new Set(options.files)].sort();
|
|
100
|
+
currentFilesAffected = requested;
|
|
101
|
+
const removed = patch.filesAffected.filter((f) => !requested.includes(f));
|
|
102
|
+
const added = requested.filter((f) => !patch.filesAffected.includes(f));
|
|
103
|
+
for (const f of added)
|
|
104
|
+
info(` + ${f}`);
|
|
105
|
+
for (const f of removed)
|
|
106
|
+
info(` - ${f}`);
|
|
107
|
+
}
|
|
108
|
+
// Stale-furnace-source gate (0.37.0 item 4): re-export captures deployed
|
|
109
|
+
// engine copies, so a component source edited after the last furnace
|
|
110
|
+
// apply would land in the patch as its OLD deployed content. Refuse (or
|
|
111
|
+
// warn under --allow-stale-furnace) before diffing. Runs in dry-run too
|
|
112
|
+
// so the failure surfaces early.
|
|
113
|
+
await enforceFreshFurnaceSources(paths.root, currentFilesAffected, options.allowStaleFurnace === true, 're-export');
|
|
114
|
+
const missingFiles = await findMissingFiles(paths.engine, currentFilesAffected);
|
|
115
|
+
if (missingFiles.length === currentFilesAffected.length) {
|
|
116
|
+
warn(`Skipped ${patch.filename}: all affected files missing`);
|
|
117
|
+
warn(`Missing files: ${missingFiles.join(', ')}`);
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
if (missingFiles.length > 0) {
|
|
121
|
+
warn(`${patch.filename}: missing files will be skipped: ${missingFiles.join(', ')}`);
|
|
122
|
+
}
|
|
123
|
+
const missingSet = new Set(missingFiles);
|
|
124
|
+
const existingFiles = currentFilesAffected.filter((f) => !missingSet.has(f));
|
|
125
|
+
const diffContent = await getDiffForFilesAgainstHead(paths.engine, existingFiles);
|
|
126
|
+
assertScanFileAdditionsHaveDiffHunks({
|
|
127
|
+
diffContent,
|
|
128
|
+
patchFilename: patch.filename,
|
|
129
|
+
previousFilesAffected: patch.filesAffected,
|
|
130
|
+
scanFiles: options.scanFiles,
|
|
131
|
+
});
|
|
132
|
+
if (!diffContent.trim()) {
|
|
133
|
+
warn(`Skipped ${patch.filename}: no changes (files unchanged from HEAD)`);
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
// Foreign-drift preview + optional hard stop (FORGE J2): show which
|
|
137
|
+
// payload lines are about to enter the body that the old body did not
|
|
138
|
+
// carry — the case where a concurrent session's uncommitted edits in
|
|
139
|
+
// OWNED files would be silently absorbed. Always previews (including
|
|
140
|
+
// dry-run and --scan); refuses only under --refuse-foreign-drift.
|
|
141
|
+
if (await reportForeignDrift({
|
|
142
|
+
patch,
|
|
143
|
+
patchesDir: paths.patches,
|
|
144
|
+
newDiffContent: diffContent,
|
|
145
|
+
ctx: driftCtx,
|
|
146
|
+
})) {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
const { updates, ignoreChecks, effectiveTier, existingIgnoreSet, flagIgnoreSet } = computeReExportUpdates(patch, options, currentFilesAffected);
|
|
150
|
+
enforcePatchPolicy({
|
|
151
|
+
config,
|
|
152
|
+
manifest: buildProjectedManifest(manifest, manifest.patches.map((entry) => entry.filename === patch.filename ? { ...entry, ...updates } : entry)),
|
|
153
|
+
command: 're-export',
|
|
154
|
+
forceUnsafe: options.forceUnsafe === true,
|
|
155
|
+
});
|
|
156
|
+
// Lint against the ONCE-per-invocation hoisted context (queue context +
|
|
157
|
+
// queue-wide checkJs program + per-patch result cache) instead of
|
|
158
|
+
// rebuilding ~37 s of setup for every patch in the loop (FORGE J1).
|
|
159
|
+
const projectedMetadata = { ...patch, ...updates };
|
|
160
|
+
const lintResult = await lintReExportedPatch({
|
|
161
|
+
lintCtx,
|
|
162
|
+
patch,
|
|
163
|
+
projectedMetadata,
|
|
164
|
+
existingFiles,
|
|
165
|
+
diffContent,
|
|
166
|
+
...(options.skipLint !== undefined ? { skipLint: options.skipLint } : {}),
|
|
167
|
+
...(ignoreChecks !== undefined ? { ignoreChecks } : {}),
|
|
168
|
+
...(effectiveTier !== undefined ? { patchTier: effectiveTier } : {}),
|
|
169
|
+
});
|
|
170
|
+
if (isDryRun) {
|
|
171
|
+
info(`[dry-run] ${patch.filename}: ${existingFiles.length} file(s)`);
|
|
172
|
+
if (effectiveTier !== undefined && effectiveTier !== patch.tier) {
|
|
173
|
+
info(`[dry-run] ${patch.filename}: tier would become ${effectiveTier}`);
|
|
174
|
+
}
|
|
175
|
+
const addedIgnores = [...flagIgnoreSet].filter((id) => !existingIgnoreSet.has(id));
|
|
176
|
+
if (addedIgnores.length > 0) {
|
|
177
|
+
info(`[dry-run] ${patch.filename}: lintIgnore would gain ${addedIgnores.join(', ')}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
await commitRefreshedPatch({
|
|
182
|
+
patch,
|
|
183
|
+
paths,
|
|
184
|
+
manifest,
|
|
185
|
+
options,
|
|
186
|
+
config,
|
|
187
|
+
lintCtx,
|
|
188
|
+
diffContent,
|
|
189
|
+
updates,
|
|
190
|
+
projectedMetadata,
|
|
191
|
+
existingFiles,
|
|
192
|
+
lintResult,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Writes the refreshed body + manifest row atomically under the patch
|
|
199
|
+
* directory lock (a split write would let a concurrent queue mutation
|
|
200
|
+
* leave body and `filesAffected` disagreeing), then keeps the run's
|
|
201
|
+
* in-memory state honest: the queue context entry is refreshed and the
|
|
202
|
+
* fresh lint result is stored keyed against the just-written body — the
|
|
203
|
+
* state a repeat invocation will observe (FORGE J1) — and the in-memory
|
|
204
|
+
* manifest row mirrors the on-disk write for later loop iterations
|
|
205
|
+
* (notably `--all --scan`, where `getClaimedFiles` reads this manifest).
|
|
206
|
+
*/
|
|
207
|
+
async function commitRefreshedPatch(args) {
|
|
208
|
+
const { patch, paths, manifest, options, config, lintCtx, diffContent, updates } = args;
|
|
209
|
+
await updatePatchAndMetadata(paths.patches, patch.filename, diffContent, updates, undefined, {
|
|
210
|
+
config,
|
|
211
|
+
command: 're-export',
|
|
212
|
+
forceUnsafe: options.forceUnsafe === true,
|
|
213
|
+
});
|
|
214
|
+
refreshQueueCtxEntry(lintCtx, patch.filename, diffContent);
|
|
215
|
+
if (args.lintResult !== null) {
|
|
216
|
+
await storeReExportLintResult(lintCtx, patch.filename, args.projectedMetadata, args.existingFiles, args.lintResult);
|
|
217
|
+
}
|
|
218
|
+
const patchIndex = manifest.patches.findIndex((pm) => pm.filename === patch.filename);
|
|
219
|
+
if (patchIndex !== -1) {
|
|
220
|
+
const existingEntry = manifest.patches[patchIndex];
|
|
221
|
+
if (existingEntry) {
|
|
222
|
+
manifest.patches[patchIndex] = {
|
|
223
|
+
...existingEntry,
|
|
224
|
+
...updates,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
success(`Re-exported ${patch.filename}`);
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Threads the patch's own `lintIgnore` list through so the per-patch
|
|
232
|
+
* suppression honoured by export/export-all is also honoured here — a
|
|
233
|
+
* re-export could otherwise not refresh an advisory-noisy but intentional
|
|
234
|
+
* patch without the blunt `--skip-lint`. The paired `patch.tier` threads
|
|
235
|
+
* the explicit branding-threshold opt-in the same way. The CLI flags
|
|
236
|
+
* `--tier` and `--lint-ignore` participate with append/union semantics
|
|
237
|
+
* (explicit removal lives on `fireforge patch lint-ignore`), computed
|
|
238
|
+
* before the lint pass so the new intent takes effect on this invocation.
|
|
239
|
+
*/
|
|
240
|
+
function computeReExportUpdates(patch, options, currentFilesAffected) {
|
|
241
|
+
const existingIgnoreSet = new Set(patch.lintIgnore ?? []);
|
|
242
|
+
const flagIgnoreSet = new Set(options.lintIgnore ?? []);
|
|
243
|
+
const mergedIgnoreSet = new Set([...existingIgnoreSet, ...flagIgnoreSet]);
|
|
244
|
+
const effectiveLintIgnore = mergedIgnoreSet.size > 0 ? [...mergedIgnoreSet] : undefined;
|
|
245
|
+
const updates = {
|
|
246
|
+
filesAffected: currentFilesAffected,
|
|
247
|
+
};
|
|
248
|
+
if (options.tier !== undefined) {
|
|
249
|
+
updates.tier = options.tier;
|
|
250
|
+
}
|
|
251
|
+
if (effectiveLintIgnore !== undefined && flagIgnoreSet.size > 0) {
|
|
252
|
+
updates.lintIgnore = effectiveLintIgnore;
|
|
253
|
+
}
|
|
254
|
+
return {
|
|
255
|
+
updates,
|
|
256
|
+
ignoreChecks: effectiveLintIgnore ? new Set(effectiveLintIgnore) : undefined,
|
|
257
|
+
effectiveTier: options.tier ?? patch.tier,
|
|
258
|
+
existingIgnoreSet,
|
|
259
|
+
flagIgnoreSet,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import type { CommandContext } from '../types/cli.js';
|
|
3
1
|
import type { ReExportOptions } from '../types/commands/index.js';
|
|
4
2
|
/**
|
|
5
3
|
* Runs the re-export command to regenerate existing patches from current engine state.
|
|
@@ -8,5 +6,3 @@ import type { ReExportOptions } from '../types/commands/index.js';
|
|
|
8
6
|
* @param options - Re-export options
|
|
9
7
|
*/
|
|
10
8
|
export declare function reExportCommand(projectRoot: string, patches: string[], options: ReExportOptions): Promise<void>;
|
|
11
|
-
/** Registers the re-export command on the CLI program. */
|
|
12
|
-
export declare function registerReExport(program: Command, { getProjectRoot, withErrorHandling }: CommandContext): void;
|