@hominis/fireforge 0.50.0 → 0.51.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 +47 -0
- package/dist/build-info.json +4 -4
- package/dist/src/commands/check-report.d.ts +17 -4
- package/dist/src/commands/check-report.js +19 -6
- package/dist/src/commands/check-run.d.ts +5 -0
- package/dist/src/commands/check-run.js +9 -1
- package/dist/src/commands/check-steps.d.ts +8 -0
- package/dist/src/commands/check-steps.js +31 -6
- package/dist/src/commands/check.js +1 -1
- package/dist/src/commands/declarations-report.d.ts +43 -0
- package/dist/src/commands/declarations-report.js +76 -0
- package/dist/src/commands/export-placement-gate.js +8 -0
- package/dist/src/commands/furnace/rename-registrations.js +7 -2
- package/dist/src/commands/furnace/validate.js +26 -1
- package/dist/src/commands/lint-per-patch.js +6 -2
- package/dist/src/commands/lint-per-run-checkjs.js +2 -2
- package/dist/src/commands/lint-report.js +7 -2
- package/dist/src/commands/manifest.js +2 -0
- package/dist/src/commands/park.d.ts +22 -0
- package/dist/src/commands/park.js +232 -0
- package/dist/src/commands/patch/delete.js +11 -6
- package/dist/src/commands/patch/index.d.ts +3 -3
- package/dist/src/commands/patch/index.js +8 -4
- package/dist/src/commands/patch/lint-ignore-from.d.ts +44 -0
- package/dist/src/commands/patch/lint-ignore-from.js +142 -0
- package/dist/src/commands/patch/lint-ignore.js +39 -29
- package/dist/src/commands/patch/move-files-create.js +11 -4
- package/dist/src/commands/patch/move-files-into.js +10 -4
- package/dist/src/commands/patch/move-files.js +3 -1
- package/dist/src/commands/patch/removal-created-files.d.ts +48 -0
- package/dist/src/commands/patch/removal-created-files.js +83 -0
- package/dist/src/commands/patch/rename.js +15 -3
- package/dist/src/commands/patch/reserve.d.ts +33 -0
- package/dist/src/commands/patch/reserve.js +152 -0
- package/dist/src/commands/patch/retire.js +11 -6
- package/dist/src/commands/patch/split-plan.d.ts +10 -1
- package/dist/src/commands/patch/split-plan.js +16 -1
- package/dist/src/commands/patch/split.js +14 -5
- package/dist/src/commands/patch/stage.d.ts +34 -0
- package/dist/src/commands/patch/stage.js +153 -0
- package/dist/src/commands/patch/staged-dependency-remove.d.ts +31 -0
- package/dist/src/commands/patch/staged-dependency-remove.js +80 -0
- package/dist/src/commands/patch/staged-dependency.js +40 -98
- package/dist/src/commands/re-export-adjacent.d.ts +5 -0
- package/dist/src/commands/re-export-adjacent.js +4 -3
- package/dist/src/commands/re-export-bulk-scan.d.ts +2 -1
- package/dist/src/commands/re-export-bulk-scan.js +22 -7
- package/dist/src/commands/re-export-hunks.d.ts +35 -0
- package/dist/src/commands/re-export-hunks.js +220 -0
- package/dist/src/commands/re-export-options.js +29 -2
- package/dist/src/commands/re-export-purity-scope.d.ts +18 -0
- package/dist/src/commands/re-export-purity-scope.js +131 -0
- package/dist/src/commands/re-export-register.js +15 -1
- package/dist/src/commands/re-export-removed.d.ts +1 -1
- package/dist/src/commands/re-export-removed.js +17 -2
- package/dist/src/commands/re-export-scan.d.ts +2 -0
- package/dist/src/commands/re-export-scan.js +4 -1
- package/dist/src/commands/re-export-single.js +102 -42
- package/dist/src/commands/re-export-stale-registrations.d.ts +18 -0
- package/dist/src/commands/re-export-stale-registrations.js +62 -0
- package/dist/src/commands/re-export.js +35 -34
- package/dist/src/commands/test-build-furnace.d.ts +3 -2
- package/dist/src/commands/test-build-furnace.js +8 -2
- package/dist/src/commands/test-build-phase.js +24 -7
- package/dist/src/commands/test-checkjs.js +3 -2
- package/dist/src/commands/test-dispatch.d.ts +39 -0
- package/dist/src/commands/test-dispatch.js +211 -0
- package/dist/src/commands/test-furnace-reach.d.ts +37 -0
- package/dist/src/commands/test-furnace-reach.js +132 -0
- package/dist/src/commands/test-orchestrate.d.ts +9 -0
- package/dist/src/commands/test-orchestrate.js +22 -4
- package/dist/src/commands/test-perf-scope.d.ts +21 -0
- package/dist/src/commands/test-perf-scope.js +55 -0
- package/dist/src/commands/test-prelock.js +100 -4
- package/dist/src/commands/test-register.js +18 -3
- package/dist/src/commands/test-results.d.ts +62 -0
- package/dist/src/commands/test-results.js +62 -0
- package/dist/src/commands/test-run.d.ts +13 -55
- package/dist/src/commands/test-run.js +73 -101
- package/dist/src/commands/test-shards.d.ts +110 -0
- package/dist/src/commands/test-shards.js +255 -0
- package/dist/src/commands/test-stale-gate.d.ts +5 -1
- package/dist/src/commands/test-stale-gate.js +4 -2
- package/dist/src/commands/test-verdict.d.ts +2 -1
- package/dist/src/commands/test.d.ts +1 -1
- package/dist/src/commands/test.js +65 -75
- package/dist/src/commands/token-lint.js +48 -8
- package/dist/src/commands/verify.js +9 -2
- package/dist/src/core/build-prepare.d.ts +6 -0
- package/dist/src/core/build-prepare.js +29 -3
- 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-check.js +83 -7
- package/dist/src/core/config-validate-test-toolchains.js +14 -0
- package/dist/src/core/config-validate-token-scales.d.ts +3 -0
- package/dist/src/core/config-validate-token-scales.js +47 -5
- package/dist/src/core/config-validate.js +6 -0
- package/dist/src/core/coverage-extend.js +2 -2
- package/dist/src/core/coverage-records.d.ts +11 -0
- package/dist/src/core/coverage-records.js +34 -0
- package/dist/src/core/engine-session-lock.d.ts +6 -0
- package/dist/src/core/engine-session-lock.js +10 -2
- package/dist/src/core/file-lock-shared.d.ts +57 -0
- package/dist/src/core/file-lock-shared.js +117 -0
- package/dist/src/core/file-lock.d.ts +9 -0
- package/dist/src/core/file-lock.js +58 -2
- package/dist/src/core/furnace-apply.js +10 -10
- package/dist/src/core/furnace-config-validate-exceptions.d.ts +10 -3
- package/dist/src/core/furnace-config-validate-exceptions.js +39 -8
- package/dist/src/core/furnace-config-validate.js +7 -1
- package/dist/src/core/furnace-registration-jar-inplace.d.ts +12 -0
- package/dist/src/core/furnace-registration-jar-inplace.js +80 -0
- package/dist/src/core/furnace-validate-accessibility.d.ts +4 -2
- package/dist/src/core/furnace-validate-accessibility.js +61 -27
- package/dist/src/core/furnace-validate-exceptions.d.ts +12 -4
- package/dist/src/core/furnace-validate-exceptions.js +41 -4
- package/dist/src/core/furnace-validate-icon-button.js +30 -1
- package/dist/src/core/history-log-file.js +8 -1
- package/dist/src/core/line-prefixer.d.ts +18 -0
- package/dist/src/core/line-prefixer.js +34 -0
- package/dist/src/core/mach.d.ts +7 -0
- package/dist/src/core/mach.js +24 -9
- package/dist/src/core/module-resolution-preflight.d.ts +19 -3
- package/dist/src/core/module-resolution-preflight.js +24 -6
- package/dist/src/core/negative-control-compiled.d.ts +27 -0
- package/dist/src/core/negative-control-compiled.js +73 -0
- package/dist/src/core/negative-control-diff.d.ts +14 -2
- package/dist/src/core/negative-control-diff.js +49 -6
- package/dist/src/core/negative-control.d.ts +12 -0
- package/dist/src/core/negative-control.js +48 -5
- package/dist/src/core/park-store.d.ts +63 -0
- package/dist/src/core/park-store.js +216 -0
- package/dist/src/core/patch-export-update.d.ts +4 -0
- package/dist/src/core/patch-export-update.js +12 -3
- package/dist/src/core/patch-export.d.ts +3 -2
- package/dist/src/core/patch-export.js +21 -14
- package/dist/src/core/patch-files.js +26 -8
- package/dist/src/core/patch-lint-checkjs-any-import.d.ts +6 -4
- package/dist/src/core/patch-lint-checkjs-any-import.js +9 -7
- package/dist/src/core/patch-lint-checkjs-diagnostics.d.ts +17 -0
- package/dist/src/core/patch-lint-checkjs-diagnostics.js +32 -0
- package/dist/src/core/patch-lint-checkjs.d.ts +10 -1
- package/dist/src/core/patch-lint-checkjs.js +13 -3
- package/dist/src/core/patch-lint-cross.d.ts +3 -1
- package/dist/src/core/patch-lint-cross.js +5 -0
- package/dist/src/core/patch-lint-css.js +14 -10
- package/dist/src/core/patch-lint-forward-registration-extended.d.ts +5 -0
- package/dist/src/core/patch-lint-forward-registration-extended.js +1 -1
- package/dist/src/core/patch-lint-ignore.d.ts +56 -12
- package/dist/src/core/patch-lint-ignore.js +173 -43
- package/dist/src/core/patch-lint-ownership.d.ts +10 -0
- package/dist/src/core/patch-lint-ownership.js +26 -0
- package/dist/src/core/patch-lint-size.js +9 -1
- package/dist/src/core/patch-lint-token-scale.d.ts +26 -8
- package/dist/src/core/patch-lint-token-scale.js +206 -29
- package/dist/src/core/patch-lint.js +2 -2
- package/dist/src/core/patch-manifest-io.js +8 -1
- package/dist/src/core/patch-manifest-key-order.d.ts +24 -0
- package/dist/src/core/patch-manifest-key-order.js +61 -0
- package/dist/src/core/patch-manifest-stage.d.ts +46 -0
- package/dist/src/core/patch-manifest-stage.js +96 -0
- package/dist/src/core/patch-manifest-validate.js +27 -1
- package/dist/src/core/patch-order-legal.d.ts +2 -1
- package/dist/src/core/patch-order-legal.js +7 -2
- package/dist/src/core/patch-policy.d.ts +6 -2
- package/dist/src/core/patch-policy.js +14 -7
- package/dist/src/core/patch-retired.d.ts +26 -4
- package/dist/src/core/patch-retired.js +40 -2
- package/dist/src/core/run-log.d.ts +6 -0
- package/dist/src/core/run-log.js +17 -1
- package/dist/src/core/test-harness-crash.d.ts +2 -1
- package/dist/src/core/test-harness-crash.js +5 -8
- package/dist/src/core/test-harness-signature.d.ts +16 -0
- package/dist/src/core/test-harness-signature.js +19 -1
- package/dist/src/core/test-raw-log.d.ts +66 -0
- package/dist/src/core/test-raw-log.js +122 -0
- package/dist/src/core/test-run-completeness.d.ts +36 -3
- package/dist/src/core/test-run-completeness.js +57 -17
- package/dist/src/core/test-xpcshell-retry.d.ts +2 -0
- package/dist/src/core/test-xpcshell-retry.js +2 -1
- package/dist/src/core/token-lint.d.ts +5 -2
- package/dist/src/core/token-lint.js +8 -3
- package/dist/src/core/token-sibling-sheets.d.ts +46 -0
- package/dist/src/core/token-sibling-sheets.js +119 -0
- package/dist/src/core/tree-guard.js +2 -0
- package/dist/src/core/typecheck-shim.d.ts +5 -1
- package/dist/src/core/typecheck-shim.js +115 -2
- package/dist/src/core/typecheck.js +3 -1
- package/dist/src/index.d.ts +9 -3
- package/dist/src/index.js +4 -1
- package/dist/src/types/commands/index.d.ts +2 -2
- package/dist/src/types/commands/options.d.ts +68 -7
- package/dist/src/types/commands/patches.d.ts +43 -0
- package/dist/src/types/config.d.ts +74 -2
- package/dist/src/types/furnace.d.ts +20 -0
- package/dist/src/types/index.d.ts +1 -1
- package/dist/src/utils/options.d.ts +3 -2
- package/dist/src/utils/options.js +9 -4
- package/docs/configuration.md +94 -9
- package/docs/furnace.md +43 -1
- package/docs/lifecycle-invariants.md +13 -11
- package/docs/machine-output.md +64 -18
- package/docs/patch-workflow.md +174 -13
- package/docs/testing.md +73 -4
- package/package.json +3 -3
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import type { CommandContext } from '../types/cli.js';
|
|
3
|
+
/** Options for `fireforge park`. */
|
|
4
|
+
export interface ParkOptions {
|
|
5
|
+
label?: string;
|
|
6
|
+
hunks?: string;
|
|
7
|
+
restore?: string;
|
|
8
|
+
list?: boolean;
|
|
9
|
+
drop?: string;
|
|
10
|
+
dryRun?: boolean;
|
|
11
|
+
waitLock?: number | boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Runs `fireforge park`.
|
|
15
|
+
*
|
|
16
|
+
* @param projectRoot - Project root
|
|
17
|
+
* @param identifier - The patch to park (omitted for --restore/--list/--drop)
|
|
18
|
+
* @param options - See {@link ParkOptions}
|
|
19
|
+
*/
|
|
20
|
+
export declare function parkCommand(projectRoot: string, identifier: string | undefined, options: ParkOptions): Promise<void>;
|
|
21
|
+
/** Registers `fireforge park`. */
|
|
22
|
+
export declare function registerPark(program: Command, { getProjectRoot, withErrorHandling }: CommandContext): void;
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* `fireforge park`: set one session's edits in a patch's files aside and
|
|
4
|
+
* put them back after the owner commits.
|
|
5
|
+
*
|
|
6
|
+
* park <patch> [--label <l>] [--hunks <diff>] park and restore baseline
|
|
7
|
+
* park --restore <label> three-way merge them back
|
|
8
|
+
* park --list | park --drop <label>
|
|
9
|
+
*
|
|
10
|
+
* Without `--hunks` every drifted file of the patch goes back to its
|
|
11
|
+
* queue-applied baseline. With `--hunks` only the named hunks are taken out
|
|
12
|
+
* of the working tree. Either way the park warns when a hunk it takes out
|
|
13
|
+
* removes a Fluent message id, or a whole file, that files outside the
|
|
14
|
+
* patch still reference: a parked string still used by markup another
|
|
15
|
+
* patch owns makes `document.l10n.ready` reject in every new window.
|
|
16
|
+
*/
|
|
17
|
+
import { basename, join } from 'node:path';
|
|
18
|
+
import { getProjectPaths } from '../core/config.js';
|
|
19
|
+
import { withEngineSessionLock } from '../core/engine-session-lock.js';
|
|
20
|
+
import { appendHistoryBestEffort } from '../core/history-log.js';
|
|
21
|
+
import { dropPark, fluentIds, listParks, planRestore, readEngineFile, readPark, removedLines, writeEngineFile, writePark, } from '../core/park-store.js';
|
|
22
|
+
import { createPatchedContentContext } from '../core/patch-apply.js';
|
|
23
|
+
import { formatPatchNotFoundError } from '../core/patch-identifier-suggest.js';
|
|
24
|
+
import { loadPatchesManifest, resolvePatchIdentifier } from '../core/patch-manifest.js';
|
|
25
|
+
import { parseDiffSections } from '../core/patch-parse.js';
|
|
26
|
+
import { GeneralError, InvalidArgumentError } from '../errors/base.js';
|
|
27
|
+
import { pathExists, readText } from '../utils/fs.js';
|
|
28
|
+
import { info, intro, outro, success, warn } from '../utils/logger.js';
|
|
29
|
+
import { addWaitLockOption, resolveWaitLockSeconds } from '../utils/options.js';
|
|
30
|
+
import { applyHunksByContext } from './re-export-hunks.js';
|
|
31
|
+
import { normalizeEngineRelativeInput } from './re-export-scan.js';
|
|
32
|
+
/** The patch-applied baseline of each of `patch`'s files (null = absent). */
|
|
33
|
+
async function baselinesOf(paths, patch) {
|
|
34
|
+
const { computePatched } = await createPatchedContentContext(paths.patches, paths.engine);
|
|
35
|
+
const deleted = new Set(parseDiffSections(await readText(join(paths.patches, patch.filename)))
|
|
36
|
+
.filter((section) => section.isDeletedFile)
|
|
37
|
+
.map((section) => section.targetPath));
|
|
38
|
+
const baselines = new Map();
|
|
39
|
+
for (const file of patch.filesAffected) {
|
|
40
|
+
baselines.set(file, deleted.has(file) ? null : await computePatched(file));
|
|
41
|
+
}
|
|
42
|
+
return baselines;
|
|
43
|
+
}
|
|
44
|
+
/** The files a park would change, with both sides. */
|
|
45
|
+
async function planPark(paths, patch, hunksPath) {
|
|
46
|
+
if (hunksPath === undefined) {
|
|
47
|
+
const baselines = await baselinesOf(paths, patch);
|
|
48
|
+
const files = [];
|
|
49
|
+
for (const [path, after] of baselines) {
|
|
50
|
+
const before = await readEngineFile(paths.engine, path);
|
|
51
|
+
if (before !== after)
|
|
52
|
+
files.push({ path, before, after });
|
|
53
|
+
}
|
|
54
|
+
return files;
|
|
55
|
+
}
|
|
56
|
+
if (!(await pathExists(hunksPath))) {
|
|
57
|
+
throw new InvalidArgumentError(`--hunks ${hunksPath} does not exist.`, '--hunks');
|
|
58
|
+
}
|
|
59
|
+
const owned = new Set(patch.filesAffected);
|
|
60
|
+
const files = [];
|
|
61
|
+
for (const section of parseDiffSections(await readText(hunksPath))) {
|
|
62
|
+
const path = normalizeEngineRelativeInput(section.targetPath, '--hunks');
|
|
63
|
+
if (!owned.has(path)) {
|
|
64
|
+
throw new InvalidArgumentError(`--hunks names ${path}, which ${patch.filename} does not own.`, '--hunks');
|
|
65
|
+
}
|
|
66
|
+
const before = await readEngineFile(paths.engine, path);
|
|
67
|
+
const after = applyHunksByContext(before ?? '', section.hunks, true, path);
|
|
68
|
+
if (before !== after)
|
|
69
|
+
files.push({ path, before, after });
|
|
70
|
+
}
|
|
71
|
+
return files;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Referrers outside the patch of what the park takes away: Fluent ids its
|
|
75
|
+
* removed lines declare, and files it removes outright.
|
|
76
|
+
*/
|
|
77
|
+
async function findOutsideReferrers(paths, manifest, patch, files) {
|
|
78
|
+
const needles = new Map();
|
|
79
|
+
for (const file of files) {
|
|
80
|
+
if (file.path.endsWith('.ftl')) {
|
|
81
|
+
for (const id of fluentIds(removedLines(file.before, file.after))) {
|
|
82
|
+
needles.set(id, `Fluent id "${id}" (${file.path})`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (file.before !== null && file.after === null) {
|
|
86
|
+
needles.set(basename(file.path), `file ${file.path}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (needles.size === 0)
|
|
90
|
+
return [];
|
|
91
|
+
const own = new Set(patch.filesAffected);
|
|
92
|
+
const outside = [...new Set(manifest.patches.flatMap((entry) => entry.filesAffected))].filter((file) => !own.has(file));
|
|
93
|
+
const findings = [];
|
|
94
|
+
for (const file of outside) {
|
|
95
|
+
const content = await readEngineFile(paths.engine, file);
|
|
96
|
+
if (content === null)
|
|
97
|
+
continue;
|
|
98
|
+
for (const [needle, label] of needles) {
|
|
99
|
+
if (content.includes(needle))
|
|
100
|
+
findings.push(`${label} is still referenced by ${file}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return findings;
|
|
104
|
+
}
|
|
105
|
+
async function runPark(projectRoot, identifier, options) {
|
|
106
|
+
const paths = getProjectPaths(projectRoot);
|
|
107
|
+
const manifest = await loadPatchesManifest(paths.patches);
|
|
108
|
+
if (!manifest)
|
|
109
|
+
throw new GeneralError('No patches/patches.json; nothing to park against.');
|
|
110
|
+
const patch = resolvePatchIdentifier(identifier, manifest.patches);
|
|
111
|
+
if (!patch) {
|
|
112
|
+
throw new InvalidArgumentError(formatPatchNotFoundError(identifier, manifest.patches), 'park');
|
|
113
|
+
}
|
|
114
|
+
const files = await planPark(paths, patch, options.hunks);
|
|
115
|
+
if (files.length === 0) {
|
|
116
|
+
info(`${patch.filename}: nothing to park; its files match ${options.hunks ?? 'the baseline'}.`);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
for (const finding of await findOutsideReferrers(paths, manifest, patch, files)) {
|
|
120
|
+
warn(`Parking removes what is still in use: ${finding}. Move it somewhere you own first.`);
|
|
121
|
+
}
|
|
122
|
+
const label = options.label ?? `${patch.filename.replace(/\.patch$/, '')}-${Date.now().toString(36)}`;
|
|
123
|
+
for (const file of files)
|
|
124
|
+
info(` ${file.path}`);
|
|
125
|
+
if (options.dryRun === true) {
|
|
126
|
+
info(`[dry-run] would park ${files.length} file(s) of ${patch.filename} as "${label}".`);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const dir = await writePark(projectRoot, {
|
|
130
|
+
label,
|
|
131
|
+
patch: patch.filename,
|
|
132
|
+
createdAt: new Date().toISOString(),
|
|
133
|
+
mode: options.hunks === undefined ? 'baseline' : 'hunks',
|
|
134
|
+
files,
|
|
135
|
+
});
|
|
136
|
+
for (const file of files)
|
|
137
|
+
await writeEngineFile(paths.engine, file.path, file.after);
|
|
138
|
+
await appendHistoryBestEffort(paths.patches, {
|
|
139
|
+
operation: 'park',
|
|
140
|
+
args: { label, patch: patch.filename, files: files.map((file) => file.path) },
|
|
141
|
+
result: 'ok',
|
|
142
|
+
}, `park ${label}`);
|
|
143
|
+
success(`Parked ${files.length} file(s) of ${patch.filename} as "${label}" (${dir}).`);
|
|
144
|
+
info(`Restore with: fireforge park --restore ${label}`);
|
|
145
|
+
}
|
|
146
|
+
async function runRestore(projectRoot, label, dryRun) {
|
|
147
|
+
const paths = getProjectPaths(projectRoot);
|
|
148
|
+
const park = await readPark(projectRoot, label);
|
|
149
|
+
const conflicts = [];
|
|
150
|
+
const plans = [];
|
|
151
|
+
for (const file of park.files) {
|
|
152
|
+
const now = await readEngineFile(paths.engine, file.path);
|
|
153
|
+
const plan = await planRestore(file, now);
|
|
154
|
+
plans.push({ file, ...plan });
|
|
155
|
+
if (plan.outcome === 'conflict')
|
|
156
|
+
conflicts.push(file.path);
|
|
157
|
+
}
|
|
158
|
+
for (const { file, outcome } of plans)
|
|
159
|
+
info(` ${file.path}: ${outcome}`);
|
|
160
|
+
if (dryRun) {
|
|
161
|
+
info(`[dry-run] restoring "${label}" would leave ${conflicts.length} conflict(s).`);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
for (const { file, content, outcome } of plans) {
|
|
165
|
+
if (outcome !== 'unchanged')
|
|
166
|
+
await writeEngineFile(paths.engine, file.path, content);
|
|
167
|
+
}
|
|
168
|
+
await appendHistoryBestEffort(paths.patches, {
|
|
169
|
+
operation: 'park-restore',
|
|
170
|
+
args: { label, patch: park.patch, conflicts },
|
|
171
|
+
result: conflicts.length === 0 ? 'ok' : 'conflict',
|
|
172
|
+
}, `park --restore ${label}`);
|
|
173
|
+
if (conflicts.length > 0) {
|
|
174
|
+
throw new GeneralError(`Restoring "${label}" left conflict markers in ${conflicts.join(', ')}. Resolve them, ` +
|
|
175
|
+
`then drop the park with "fireforge park --drop ${label}".`);
|
|
176
|
+
}
|
|
177
|
+
await dropPark(projectRoot, label);
|
|
178
|
+
success(`Restored "${label}" into ${park.files.length} file(s) of ${park.patch}.`);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Runs `fireforge park`.
|
|
182
|
+
*
|
|
183
|
+
* @param projectRoot - Project root
|
|
184
|
+
* @param identifier - The patch to park (omitted for --restore/--list/--drop)
|
|
185
|
+
* @param options - See {@link ParkOptions}
|
|
186
|
+
*/
|
|
187
|
+
export async function parkCommand(projectRoot, identifier, options) {
|
|
188
|
+
intro('FireForge park');
|
|
189
|
+
const modes = [identifier !== undefined, options.restore, options.list, options.drop].filter((mode) => mode !== undefined && mode !== false);
|
|
190
|
+
if (modes.length !== 1) {
|
|
191
|
+
throw new InvalidArgumentError('Pass exactly one of: a patch to park, --restore <label>, --list or --drop <label>.', 'park');
|
|
192
|
+
}
|
|
193
|
+
if (options.list === true) {
|
|
194
|
+
const parks = await listParks(projectRoot);
|
|
195
|
+
if (parks.length === 0)
|
|
196
|
+
info('No parks.');
|
|
197
|
+
for (const park of parks) {
|
|
198
|
+
info(`${park.label} ${park.patch} ${park.files.length} file(s) ${park.createdAt}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
else if (options.drop !== undefined) {
|
|
202
|
+
await dropPark(projectRoot, options.drop);
|
|
203
|
+
success(`Dropped park "${options.drop}".`);
|
|
204
|
+
}
|
|
205
|
+
else if (options.restore !== undefined) {
|
|
206
|
+
await runRestore(projectRoot, options.restore, options.dryRun === true);
|
|
207
|
+
}
|
|
208
|
+
else if (identifier !== undefined) {
|
|
209
|
+
await runPark(projectRoot, identifier, options);
|
|
210
|
+
}
|
|
211
|
+
outro('Park complete');
|
|
212
|
+
}
|
|
213
|
+
/** Registers `fireforge park`. */
|
|
214
|
+
export function registerPark(program, { getProjectRoot, withErrorHandling }) {
|
|
215
|
+
const command = program
|
|
216
|
+
.command('park [patch]')
|
|
217
|
+
.description("Set a session's edits in a patch's files aside (back to the queue-applied baseline, or minus the hunks a diff names) and three-way merge them back later")
|
|
218
|
+
.option('--label <label>', 'Name for the park (default: <patch>-<time>)')
|
|
219
|
+
.option('--hunks <diff>', 'Park only the hunks this diff names, not every drifted line')
|
|
220
|
+
.option('--restore <label>', 'Three-way merge a park back into the engine, then drop it')
|
|
221
|
+
.option('--list', 'List the parks')
|
|
222
|
+
.option('--drop <label>', 'Delete a park without restoring it')
|
|
223
|
+
.option('--dry-run', 'Show what would be parked or restored');
|
|
224
|
+
addWaitLockOption(command).action(withErrorHandling(async (patch, options) => {
|
|
225
|
+
const projectRoot = getProjectRoot();
|
|
226
|
+
if (options.list === true || options.drop !== undefined || options.dryRun === true) {
|
|
227
|
+
await parkCommand(projectRoot, patch, options);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
await withEngineSessionLock(projectRoot, 'park', () => parkCommand(projectRoot, patch, options), { waitLockSeconds: resolveWaitLockSeconds(options.waitLock) });
|
|
231
|
+
}));
|
|
232
|
+
}
|
|
@@ -20,6 +20,7 @@ import { addWaitLockOption, pickDefined, resolveWaitLockSeconds } from '../../ut
|
|
|
20
20
|
import { proceedAfterDecision } from '../destructive-decision.js';
|
|
21
21
|
import { describeProjectedGaps } from './delete-gap-notice.js';
|
|
22
22
|
import { requirePatchQueue, requirePatchTarget } from './patch-context.js';
|
|
23
|
+
import { deleteCreatedFiles, planClaimedFiles, withCreatedFileLock, } from './removal-created-files.js';
|
|
23
24
|
import { collectRemovalDependents, queueSnapshot, removalConflict, warnDanglingStagedOwners, } from './removal-dependents.js';
|
|
24
25
|
/**
|
|
25
26
|
* Runs the `patch delete` command: removes a patch file and its manifest
|
|
@@ -55,14 +56,12 @@ export async function patchDeleteCommand(projectRoot, identifier, options = {})
|
|
|
55
56
|
warn(line);
|
|
56
57
|
}
|
|
57
58
|
const conflicts = removalConflict(dependents, target);
|
|
59
|
+
const claimed = await planClaimedFiles(baseCtx, target, paths.engine, options.deleteCreated === true);
|
|
58
60
|
const summary = [
|
|
59
61
|
`delete ${target.filename} (category: ${target.category}, order: ${target.order})`,
|
|
60
62
|
`description: ${target.description || '(none)'}`,
|
|
61
|
-
|
|
63
|
+
...claimed.summary,
|
|
62
64
|
];
|
|
63
|
-
for (const file of target.filesAffected) {
|
|
64
|
-
summary.push(` ${file} → will become unmanaged`);
|
|
65
|
-
}
|
|
66
65
|
summary.push(...gapNotices);
|
|
67
66
|
const decision = await confirmDestructive({
|
|
68
67
|
operation: 'patch-delete',
|
|
@@ -83,7 +82,8 @@ export async function patchDeleteCommand(projectRoot, identifier, options = {})
|
|
|
83
82
|
// mutation with no audit trail. A history append failure is warned but not
|
|
84
83
|
// re-thrown: by that point the mutation has committed, and reporting
|
|
85
84
|
// failure to the caller would mislead.
|
|
86
|
-
|
|
85
|
+
const waitLockSeconds = resolveWaitLockSeconds(options.waitLock);
|
|
86
|
+
await withCreatedFileLock(projectRoot, 'patch delete', claimed, waitLockSeconds, () => withPatchDirectoryLock(paths.patches, async () => {
|
|
87
87
|
const current = await buildPatchQueueContext(paths.patches);
|
|
88
88
|
// A changed body or manifest invalidates the dependency scan approved
|
|
89
89
|
// before the prompt. Recheck all of its inputs while holding the lock.
|
|
@@ -91,18 +91,22 @@ export async function patchDeleteCommand(projectRoot, identifier, options = {})
|
|
|
91
91
|
throw new GeneralError('Patch queue changed while waiting for confirmation or the lock. Re-run patch delete to review the current dependencies.');
|
|
92
92
|
}
|
|
93
93
|
await removePatchFileAndManifest(paths.patches, target.filename);
|
|
94
|
+
await deleteCreatedFiles(paths.engine, claimed.deleteOnCommit);
|
|
94
95
|
await appendHistoryBestEffort(paths.patches, {
|
|
95
96
|
operation: 'patch-delete',
|
|
96
97
|
args: {
|
|
97
98
|
filename: target.filename,
|
|
98
99
|
order: target.order,
|
|
99
100
|
filesAffected: target.filesAffected,
|
|
101
|
+
...(claimed.deleteOnCommit.length > 0
|
|
102
|
+
? { deletedCreatedFiles: claimed.deleteOnCommit }
|
|
103
|
+
: {}),
|
|
100
104
|
},
|
|
101
105
|
...(options.yes === true ? { yes: true } : {}),
|
|
102
106
|
...(options.forceUnsafe === true ? { unsafeOverride: true } : {}),
|
|
103
107
|
result: 'ok',
|
|
104
108
|
}, `patch delete committed (${target.filename})`);
|
|
105
|
-
}, { waitLockSeconds
|
|
109
|
+
}, { waitLockSeconds, command: 'patch delete' }));
|
|
106
110
|
info(`Deleted ${target.filename}.`);
|
|
107
111
|
outro('Delete complete');
|
|
108
112
|
}
|
|
@@ -119,6 +123,7 @@ export function registerPatchDelete(parent, context) {
|
|
|
119
123
|
.description('Delete a patch from the queue (destructive)')
|
|
120
124
|
.option('--dry-run', 'Show what would happen without writing')
|
|
121
125
|
.option('-y, --yes', 'Skip confirmation prompt (required for non-TTY)')
|
|
126
|
+
.option('--delete-created', 'Also delete the engine files this patch creates that are still on disk, instead of leaving them unmanaged')
|
|
122
127
|
.option('--force-unsafe', 'Bypass the refusal when a later patch depends on this patch (last resort)');
|
|
123
128
|
addWaitLockOption(command).action(withErrorHandling(async (name, options) => {
|
|
124
129
|
await patchDeleteCommand(getProjectRoot(), name, pickDefined(options));
|
|
@@ -1,9 +1,9 @@
|
|
|
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`, `retire`, `staged-dependency`,
|
|
5
|
-
* top-level command list. Queue-level
|
|
6
|
-
* and `status` stay flat.
|
|
4
|
+
* `reorder`, `reserve`, `retire`, `split`, `stage`, `staged-dependency`,
|
|
5
|
+
* `tier`) so they do not clutter the top-level command list. Queue-level
|
|
6
|
+
* verbs like `lint`, `export`, `verify`, and `status` stay flat.
|
|
7
7
|
*/
|
|
8
8
|
import { Command } from 'commander';
|
|
9
9
|
import type { CommandContext } from '../../types/cli.js';
|
|
@@ -2,9 +2,9 @@
|
|
|
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`, `retire`, `staged-dependency`,
|
|
6
|
-
* top-level command list. Queue-level
|
|
7
|
-
* and `status` stay flat.
|
|
5
|
+
* `reorder`, `reserve`, `retire`, `split`, `stage`, `staged-dependency`,
|
|
6
|
+
* `tier`) so they do not clutter the top-level command list. Queue-level
|
|
7
|
+
* verbs like `lint`, `export`, `verify`, and `status` stay flat.
|
|
8
8
|
*/
|
|
9
9
|
import { Command } from 'commander';
|
|
10
10
|
import { registerPatchCompact } from './compact.js';
|
|
@@ -13,8 +13,10 @@ 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 { registerPatchReserve } from './reserve.js';
|
|
16
17
|
import { registerPatchRetire } from './retire.js';
|
|
17
18
|
import { registerPatchSplit } from './split.js';
|
|
19
|
+
import { registerPatchStage } from './stage.js';
|
|
18
20
|
import { registerPatchStagedDependency } from './staged-dependency.js';
|
|
19
21
|
import { registerPatchTier } from './tier.js';
|
|
20
22
|
/**
|
|
@@ -26,7 +28,7 @@ import { registerPatchTier } from './tier.js';
|
|
|
26
28
|
export function registerPatch(program, context) {
|
|
27
29
|
const patch = program
|
|
28
30
|
.command('patch')
|
|
29
|
-
.description('Manage individual patches in the queue (compact, delete, lint-ignore, move-files, rename, reorder, retire, staged-dependency, tier)')
|
|
31
|
+
.description('Manage individual patches in the queue (compact, delete, lint-ignore, move-files, rename, reorder, reserve, retire, split, stage, staged-dependency, tier)')
|
|
30
32
|
// Match `fireforge furnace`'s no-args contract: print the group's help
|
|
31
33
|
// and exit 0. Without this default action, commander routes
|
|
32
34
|
// `fireforge patch` (no subcommand) through its own help-then-exit-1
|
|
@@ -41,8 +43,10 @@ export function registerPatch(program, context) {
|
|
|
41
43
|
registerPatchMoveFiles(patch, context);
|
|
42
44
|
registerPatchRename(patch, context);
|
|
43
45
|
registerPatchReorder(patch, context);
|
|
46
|
+
registerPatchReserve(patch, context);
|
|
44
47
|
registerPatchRetire(patch, context);
|
|
45
48
|
registerPatchSplit(patch, context);
|
|
49
|
+
registerPatchStage(patch, context);
|
|
46
50
|
registerPatchStagedDependency(patch, context);
|
|
47
51
|
registerPatchTier(patch, context);
|
|
48
52
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Batch and per-entry input for `patch lint-ignore --add`. Split out of
|
|
3
|
+
* `lint-ignore.ts` for the per-file line budget.
|
|
4
|
+
*
|
|
5
|
+
* A rebaseline that sets a ceiling per rule used to take one call per rule,
|
|
6
|
+
* each taking the patch lock and rewriting `patches.json` (138 calls on one
|
|
7
|
+
* consumer's queue), because `--max` applied to every `--add` of a call.
|
|
8
|
+
* Two forms lift that:
|
|
9
|
+
*
|
|
10
|
+
* - `--add <rule>=<max>` gives each entry its own ceiling in one call.
|
|
11
|
+
* - `--from <json-file>` reads every entry from a file, under one lock hold
|
|
12
|
+
* and one write. The file is a JSON array of entries (a check ID string,
|
|
13
|
+
* or `{ rule, max, reason?, share?, meta?, patch? }`), or a
|
|
14
|
+
* `lint --per-patch --report` document, whose `lintIgnoreCeilings` rows
|
|
15
|
+
* for the target patch are rebaselined to their current measurement.
|
|
16
|
+
*/
|
|
17
|
+
import type { LintIgnoreEntry, PatchLintIgnoreOptions, PatchSizeShare } from '../../types/commands/index.js';
|
|
18
|
+
/**
|
|
19
|
+
* Turns `--add` values into entries: `<rule>=<max>` is a ceiling at `max`,
|
|
20
|
+
* a bare ID is a ceiling at `--max` when that is given and a bare waiver
|
|
21
|
+
* otherwise. Every ceiling takes `--reason` (required) and `--share`.
|
|
22
|
+
*
|
|
23
|
+
* @param ids - The `--add` values
|
|
24
|
+
* @param options - Command options (`max`, `reason`, `share`)
|
|
25
|
+
* @returns The entries to upsert
|
|
26
|
+
*/
|
|
27
|
+
export declare function resolveAddEntries(ids: readonly string[], options: Pick<PatchLintIgnoreOptions, 'max' | 'reason' | 'share'>): LintIgnoreEntry[];
|
|
28
|
+
/** Entries read from a `--from` file for one target patch. */
|
|
29
|
+
export interface LintIgnoreBatch {
|
|
30
|
+
entries: LintIgnoreEntry[];
|
|
31
|
+
/** Entries naming a different patch, left out. */
|
|
32
|
+
skippedForOtherPatches: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Reads a `--from` file into entries for `targetFilename`.
|
|
36
|
+
*
|
|
37
|
+
* @param path - JSON file path
|
|
38
|
+
* @param targetFilename - The patch the command edits
|
|
39
|
+
* @param defaultReason - `--reason`, used for object entries without one
|
|
40
|
+
* @returns The validated entries and the count left out for other patches
|
|
41
|
+
*/
|
|
42
|
+
export declare function readLintIgnoreBatch(path: string, targetFilename: string, defaultReason: string | undefined): Promise<LintIgnoreBatch>;
|
|
43
|
+
/** Parses `--share`. */
|
|
44
|
+
export declare function parseShare(value: string): PatchSizeShare;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Batch and per-entry input for `patch lint-ignore --add`. Split out of
|
|
4
|
+
* `lint-ignore.ts` for the per-file line budget.
|
|
5
|
+
*
|
|
6
|
+
* A rebaseline that sets a ceiling per rule used to take one call per rule,
|
|
7
|
+
* each taking the patch lock and rewriting `patches.json` (138 calls on one
|
|
8
|
+
* consumer's queue), because `--max` applied to every `--add` of a call.
|
|
9
|
+
* Two forms lift that:
|
|
10
|
+
*
|
|
11
|
+
* - `--add <rule>=<max>` gives each entry its own ceiling in one call.
|
|
12
|
+
* - `--from <json-file>` reads every entry from a file, under one lock hold
|
|
13
|
+
* and one write. The file is a JSON array of entries (a check ID string,
|
|
14
|
+
* or `{ rule, max, reason?, share?, meta?, patch? }`), or a
|
|
15
|
+
* `lint --per-patch --report` document, whose `lintIgnoreCeilings` rows
|
|
16
|
+
* for the target patch are rebaselined to their current measurement.
|
|
17
|
+
*/
|
|
18
|
+
import { LINT_IGNORE_CEILING_RULES, parseLintIgnoreEntries } from '../../core/patch-lint-ignore.js';
|
|
19
|
+
import { InvalidArgumentError } from '../../errors/base.js';
|
|
20
|
+
import { toError } from '../../utils/errors.js';
|
|
21
|
+
import { readJson } from '../../utils/fs.js';
|
|
22
|
+
import { isArray, isObject } from '../../utils/validation.js';
|
|
23
|
+
const RULE_MAX_PATTERN = /^(?<rule>[^=\s]+)=(?<max>[1-9]\d*)$/;
|
|
24
|
+
/**
|
|
25
|
+
* Turns `--add` values into entries: `<rule>=<max>` is a ceiling at `max`,
|
|
26
|
+
* a bare ID is a ceiling at `--max` when that is given and a bare waiver
|
|
27
|
+
* otherwise. Every ceiling takes `--reason` (required) and `--share`.
|
|
28
|
+
*
|
|
29
|
+
* @param ids - The `--add` values
|
|
30
|
+
* @param options - Command options (`max`, `reason`, `share`)
|
|
31
|
+
* @returns The entries to upsert
|
|
32
|
+
*/
|
|
33
|
+
export function resolveAddEntries(ids, options) {
|
|
34
|
+
const parsed = ids.map((id) => {
|
|
35
|
+
if (id.includes('=')) {
|
|
36
|
+
const match = RULE_MAX_PATTERN.exec(id);
|
|
37
|
+
if (match?.groups === undefined) {
|
|
38
|
+
throw new InvalidArgumentError(`--add ${id}: expected <rule>=<max> with a positive integer max.`, '--add');
|
|
39
|
+
}
|
|
40
|
+
return { rule: match.groups['rule'], max: Number(match.groups['max']) };
|
|
41
|
+
}
|
|
42
|
+
return { rule: id, max: options.max };
|
|
43
|
+
});
|
|
44
|
+
const ceilings = parsed.filter((entry) => entry.max !== undefined);
|
|
45
|
+
if (ceilings.length === 0) {
|
|
46
|
+
if (options.reason !== undefined || options.share !== undefined) {
|
|
47
|
+
throw new InvalidArgumentError('--reason and --share apply to ceilings: pass --max <n> or --add <rule>=<max>.', options.reason !== undefined ? '--reason' : '--share');
|
|
48
|
+
}
|
|
49
|
+
return parsed.map((entry) => entry.rule);
|
|
50
|
+
}
|
|
51
|
+
if (options.reason === undefined || options.reason.trim() === '') {
|
|
52
|
+
throw new InvalidArgumentError('A lintIgnore ceiling needs --reason <text> (with --max <n> or --add <rule>=<max>).', '--reason');
|
|
53
|
+
}
|
|
54
|
+
const unmeasured = ceilings.filter((entry) => !LINT_IGNORE_CEILING_RULES.includes(entry.rule));
|
|
55
|
+
if (unmeasured.length > 0) {
|
|
56
|
+
throw new InvalidArgumentError(`A ceiling only applies to the measured size rules (${LINT_IGNORE_CEILING_RULES.join(', ')}); ` +
|
|
57
|
+
`got ${unmeasured.map((entry) => entry.rule).join(', ')}.`, '--max');
|
|
58
|
+
}
|
|
59
|
+
const { reason, share } = options;
|
|
60
|
+
return validated(parsed.map((entry) => entry.max === undefined
|
|
61
|
+
? entry.rule
|
|
62
|
+
: { rule: entry.rule, max: entry.max, reason, ...(share !== undefined ? { share } : {}) }), '--add');
|
|
63
|
+
}
|
|
64
|
+
/** Runs the manifest validator over CLI-built entries, as an argument error. */
|
|
65
|
+
function validated(entries, flag) {
|
|
66
|
+
try {
|
|
67
|
+
return parseLintIgnoreEntries(entries, flag);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
throw new InvalidArgumentError(toError(error).message, flag);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** The target patch's ceiling rows of a lint report, rebaselined to their measurement. */
|
|
74
|
+
function rebaselineFromReport(data, targetFilename) {
|
|
75
|
+
const patches = isArray(data['patches']) ? data['patches'] : [];
|
|
76
|
+
const row = patches.find((patch) => isObject(patch) && patch['filename'] === targetFilename);
|
|
77
|
+
const ceilings = isObject(row) && isArray(row['lintIgnoreCeilings']) ? row['lintIgnoreCeilings'] : [];
|
|
78
|
+
const raw = [];
|
|
79
|
+
for (const ceiling of ceilings) {
|
|
80
|
+
if (!isObject(ceiling) || typeof ceiling['measurement'] !== 'number')
|
|
81
|
+
continue;
|
|
82
|
+
if (ceiling['measurement'] <= 0)
|
|
83
|
+
continue;
|
|
84
|
+
const { measurement, rule, reason, share, meta } = ceiling;
|
|
85
|
+
raw.push({
|
|
86
|
+
rule,
|
|
87
|
+
max: measurement,
|
|
88
|
+
reason,
|
|
89
|
+
...(share !== undefined ? { share } : {}),
|
|
90
|
+
...(meta !== undefined ? { meta } : {}),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return { raw, skipped: patches.length - (row === undefined ? 0 : 1) };
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Reads a `--from` file into entries for `targetFilename`.
|
|
97
|
+
*
|
|
98
|
+
* @param path - JSON file path
|
|
99
|
+
* @param targetFilename - The patch the command edits
|
|
100
|
+
* @param defaultReason - `--reason`, used for object entries without one
|
|
101
|
+
* @returns The validated entries and the count left out for other patches
|
|
102
|
+
*/
|
|
103
|
+
export async function readLintIgnoreBatch(path, targetFilename, defaultReason) {
|
|
104
|
+
let data;
|
|
105
|
+
try {
|
|
106
|
+
data = await readJson(path);
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
throw new InvalidArgumentError(`Cannot read --from ${path}: ${toError(error).message}`, '--from');
|
|
110
|
+
}
|
|
111
|
+
if (isObject(data)) {
|
|
112
|
+
const { raw, skipped } = rebaselineFromReport(data, targetFilename);
|
|
113
|
+
return { entries: validated(raw, '--from'), skippedForOtherPatches: skipped };
|
|
114
|
+
}
|
|
115
|
+
if (!isArray(data)) {
|
|
116
|
+
throw new InvalidArgumentError('--from must hold a JSON array of lintIgnore entries or a "lint --per-patch --report" document.', '--from');
|
|
117
|
+
}
|
|
118
|
+
const raw = [];
|
|
119
|
+
let skipped = 0;
|
|
120
|
+
for (const entry of data) {
|
|
121
|
+
if (!isObject(entry)) {
|
|
122
|
+
raw.push(entry);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const { patch, ...rest } = entry;
|
|
126
|
+
if (patch !== undefined && patch !== targetFilename) {
|
|
127
|
+
skipped++;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
raw.push(rest['reason'] === undefined && defaultReason !== undefined
|
|
131
|
+
? { ...rest, reason: defaultReason }
|
|
132
|
+
: rest);
|
|
133
|
+
}
|
|
134
|
+
return { entries: validated(raw, '--from'), skippedForOtherPatches: skipped };
|
|
135
|
+
}
|
|
136
|
+
/** Parses `--share`. */
|
|
137
|
+
export function parseShare(value) {
|
|
138
|
+
if (value !== 'test' && value !== 'product') {
|
|
139
|
+
throw new InvalidArgumentError(`--share must be "test" or "product" (got "${value}").`, '--share');
|
|
140
|
+
}
|
|
141
|
+
return value;
|
|
142
|
+
}
|