@hominis/fireforge 0.10.1 → 0.11.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 +93 -1
- package/README.md +125 -238
- package/dist/bin/fireforge.js +26 -0
- package/dist/src/cli.d.ts +1 -1
- package/dist/src/cli.js +131 -52
- package/dist/src/commands/bootstrap.js +6 -2
- package/dist/src/commands/build.js +4 -2
- package/dist/src/commands/discard.js +16 -4
- package/dist/src/commands/doctor-furnace.d.ts +8 -0
- package/dist/src/commands/doctor-furnace.js +422 -0
- package/dist/src/commands/doctor.d.ts +115 -0
- package/dist/src/commands/doctor.js +327 -258
- package/dist/src/commands/download.js +16 -1
- package/dist/src/commands/export-all.js +15 -0
- package/dist/src/commands/export-flow.d.ts +91 -0
- package/dist/src/commands/export-flow.js +344 -0
- package/dist/src/commands/export.js +151 -5
- package/dist/src/commands/furnace/apply.d.ts +3 -2
- package/dist/src/commands/furnace/apply.js +169 -36
- package/dist/src/commands/furnace/create.js +162 -52
- package/dist/src/commands/furnace/deploy.js +156 -144
- package/dist/src/commands/furnace/diff.d.ts +8 -4
- package/dist/src/commands/furnace/diff.js +142 -73
- package/dist/src/commands/furnace/index.d.ts +6 -2
- package/dist/src/commands/furnace/index.js +76 -25
- package/dist/src/commands/furnace/init.d.ts +11 -0
- package/dist/src/commands/furnace/init.js +76 -0
- package/dist/src/commands/furnace/list.d.ts +4 -1
- package/dist/src/commands/furnace/list.js +35 -3
- package/dist/src/commands/furnace/override.d.ts +8 -0
- package/dist/src/commands/furnace/override.js +216 -26
- package/dist/src/commands/furnace/preview.js +184 -30
- package/dist/src/commands/furnace/refresh.d.ts +10 -0
- package/dist/src/commands/furnace/refresh.js +268 -0
- package/dist/src/commands/furnace/remove.js +285 -89
- package/dist/src/commands/furnace/rename.d.ts +5 -0
- package/dist/src/commands/furnace/rename.js +308 -0
- package/dist/src/commands/furnace/scan.d.ts +4 -1
- package/dist/src/commands/furnace/scan.js +72 -11
- package/dist/src/commands/furnace/status.js +85 -20
- package/dist/src/commands/furnace/sync.d.ts +12 -0
- package/dist/src/commands/furnace/sync.js +77 -0
- package/dist/src/commands/furnace/validate.d.ts +4 -1
- package/dist/src/commands/furnace/validate.js +99 -3
- package/dist/src/commands/furnace/validation-output.d.ts +24 -1
- package/dist/src/commands/furnace/validation-output.js +93 -1
- package/dist/src/commands/import.js +37 -4
- package/dist/src/commands/lint.js +11 -2
- package/dist/src/commands/manifest.d.ts +39 -0
- package/dist/src/commands/manifest.js +59 -0
- package/dist/src/commands/patch/delete.d.ts +28 -0
- package/dist/src/commands/patch/delete.js +209 -0
- package/dist/src/commands/patch/index.d.ts +17 -0
- package/dist/src/commands/patch/index.js +25 -0
- package/dist/src/commands/patch/reorder.d.ts +30 -0
- package/dist/src/commands/patch/reorder.js +377 -0
- package/dist/src/commands/re-export-files.d.ts +17 -0
- package/dist/src/commands/re-export-files.js +177 -0
- package/dist/src/commands/re-export.js +44 -0
- package/dist/src/commands/rebase/abort.d.ts +1 -1
- package/dist/src/commands/rebase/abort.js +12 -3
- package/dist/src/commands/rebase/confirm.d.ts +3 -3
- package/dist/src/commands/rebase/confirm.js +4 -4
- package/dist/src/commands/rebase/index.js +13 -4
- package/dist/src/commands/reset.js +20 -4
- package/dist/src/commands/run.js +46 -1
- package/dist/src/commands/setup-support.js +5 -5
- package/dist/src/commands/status.js +97 -6
- package/dist/src/commands/test.js +5 -37
- package/dist/src/commands/verify.d.ts +31 -0
- package/dist/src/commands/verify.js +126 -0
- package/dist/src/core/build-prepare.js +40 -16
- package/dist/src/core/destructive.d.ts +96 -0
- package/dist/src/core/destructive.js +137 -0
- package/dist/src/core/diff-hunks.d.ts +73 -0
- package/dist/src/core/diff-hunks.js +268 -0
- package/dist/src/core/firefox.d.ts +1 -1
- package/dist/src/core/firefox.js +1 -1
- package/dist/src/core/furnace-apply-helpers.d.ts +89 -6
- package/dist/src/core/furnace-apply-helpers.js +302 -57
- package/dist/src/core/furnace-apply-output.d.ts +16 -0
- package/dist/src/core/furnace-apply-output.js +57 -0
- package/dist/src/core/furnace-apply.d.ts +21 -3
- package/dist/src/core/furnace-apply.js +260 -29
- package/dist/src/core/furnace-checksum-utils.d.ts +4 -0
- package/dist/src/core/furnace-checksum-utils.js +24 -0
- package/dist/src/core/furnace-config.d.ts +28 -1
- package/dist/src/core/furnace-config.js +180 -17
- package/dist/src/core/furnace-constants.d.ts +22 -0
- package/dist/src/core/furnace-constants.js +36 -0
- package/dist/src/core/furnace-graph-utils.d.ts +11 -0
- package/dist/src/core/furnace-graph-utils.js +94 -0
- package/dist/src/core/furnace-operation.d.ts +108 -0
- package/dist/src/core/furnace-operation.js +220 -0
- package/dist/src/core/furnace-refresh.d.ts +20 -0
- package/dist/src/core/furnace-refresh.js +118 -0
- package/dist/src/core/furnace-registration-ast.d.ts +5 -0
- package/dist/src/core/furnace-registration-ast.js +134 -4
- package/dist/src/core/furnace-registration-remove.d.ts +25 -3
- package/dist/src/core/furnace-registration-remove.js +196 -62
- package/dist/src/core/furnace-registration-validate.d.ts +13 -1
- package/dist/src/core/furnace-registration-validate.js +15 -3
- package/dist/src/core/furnace-registration.d.ts +27 -4
- package/dist/src/core/furnace-registration.js +93 -11
- package/dist/src/core/furnace-rollback.d.ts +11 -0
- package/dist/src/core/furnace-rollback.js +78 -7
- package/dist/src/core/furnace-scanner.d.ts +8 -2
- package/dist/src/core/furnace-scanner.js +152 -55
- package/dist/src/core/furnace-stories.js +7 -5
- package/dist/src/core/furnace-validate-accessibility.js +7 -1
- package/dist/src/core/furnace-validate-compatibility.d.ts +1 -1
- package/dist/src/core/furnace-validate-compatibility.js +85 -1
- package/dist/src/core/furnace-validate-helpers.d.ts +4 -0
- package/dist/src/core/furnace-validate-helpers.js +31 -0
- package/dist/src/core/furnace-validate-registration.d.ts +17 -2
- package/dist/src/core/furnace-validate-registration.js +73 -3
- package/dist/src/core/furnace-validate-structure.d.ts +10 -2
- package/dist/src/core/furnace-validate-structure.js +45 -3
- package/dist/src/core/furnace-validate.d.ts +10 -1
- package/dist/src/core/furnace-validate.js +80 -6
- package/dist/src/core/furnace-version-drift.d.ts +55 -0
- package/dist/src/core/furnace-version-drift.js +101 -0
- package/dist/src/core/git-file-ops.d.ts +8 -0
- package/dist/src/core/git-file-ops.js +19 -6
- package/dist/src/core/lint-projection.d.ts +25 -0
- package/dist/src/core/lint-projection.js +44 -0
- package/dist/src/core/mach.d.ts +4 -2
- package/dist/src/core/mach.js +17 -2
- package/dist/src/core/markdown-table.d.ts +104 -0
- package/dist/src/core/markdown-table.js +266 -0
- package/dist/src/core/ownership-table.d.ts +53 -0
- package/dist/src/core/ownership-table.js +144 -0
- package/dist/src/core/patch-apply.d.ts +17 -3
- package/dist/src/core/patch-apply.js +86 -8
- package/dist/src/core/patch-export.d.ts +119 -5
- package/dist/src/core/patch-export.js +183 -25
- package/dist/src/core/patch-lint-cross.d.ts +195 -0
- package/dist/src/core/patch-lint-cross.js +428 -0
- package/dist/src/core/patch-lint-diff.d.ts +33 -0
- package/dist/src/core/patch-lint-diff.js +84 -0
- package/dist/src/core/patch-lint.d.ts +2 -4
- package/dist/src/core/patch-lint.js +12 -50
- package/dist/src/core/patch-lock.js +2 -1
- package/dist/src/core/patch-manifest-io.d.ts +102 -1
- package/dist/src/core/patch-manifest-io.js +270 -2
- package/dist/src/core/patch-manifest-query.d.ts +1 -1
- package/dist/src/core/patch-manifest-query.js +1 -1
- package/dist/src/core/patch-manifest.d.ts +1 -1
- package/dist/src/core/patch-manifest.js +1 -1
- package/dist/src/core/patch-transform.d.ts +12 -0
- package/dist/src/core/patch-transform.js +21 -7
- package/dist/src/core/token-manager.js +67 -69
- package/dist/src/core/wire-destroy.js +6 -3
- package/dist/src/core/wire-init.js +10 -4
- package/dist/src/core/wire-subscript.js +9 -3
- package/dist/src/core/wire-utils.d.ts +52 -5
- package/dist/src/core/wire-utils.js +69 -6
- package/dist/src/errors/base.d.ts +20 -0
- package/dist/src/errors/base.js +24 -0
- package/dist/src/errors/furnace.js +7 -1
- package/dist/src/errors/rebase.js +6 -1
- package/dist/src/types/commands/index.d.ts +1 -1
- package/dist/src/types/commands/options.d.ts +125 -4
- package/dist/src/types/commands/patches.d.ts +11 -1
- package/dist/src/types/config.d.ts +1 -1
- package/dist/src/types/furnace.d.ts +55 -1
- package/dist/src/utils/fs.d.ts +12 -0
- package/dist/src/utils/fs.js +30 -1
- package/dist/src/utils/package-root.d.ts +5 -0
- package/dist/src/utils/package-root.js +12 -0
- package/dist/src/utils/process.js +9 -4
- package/dist/src/utils/validation.d.ts +20 -2
- package/dist/src/utils/validation.js +26 -3
- package/package.json +1 -1
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
import { getProjectPaths, loadConfig } from '../../core/config.js';
|
|
3
|
+
import { applyAllComponents } from '../../core/furnace-apply.js';
|
|
4
|
+
import { logApplyResult } from '../../core/furnace-apply-output.js';
|
|
5
|
+
import { furnaceConfigExists, loadFurnaceConfig } from '../../core/furnace-config.js';
|
|
6
|
+
import { runFurnaceMutation } from '../../core/furnace-operation.js';
|
|
7
|
+
import { findOverrideBaseVersionDrift, formatOverrideBaseVersionDriftWarning, } from '../../core/furnace-version-drift.js';
|
|
8
|
+
import { FurnaceError } from '../../errors/furnace.js';
|
|
9
|
+
import { pathExists } from '../../utils/fs.js';
|
|
10
|
+
import { info, intro, outro, spinner, warn } from '../../utils/logger.js';
|
|
11
|
+
import { furnaceRefreshCommand } from './refresh.js';
|
|
12
|
+
/**
|
|
13
|
+
* Runs the furnace sync command: detects overrides with baseVersion drift,
|
|
14
|
+
* refreshes them (three-way merge), and re-applies all components.
|
|
15
|
+
*
|
|
16
|
+
* This is the recommended single command to run after `fireforge download`
|
|
17
|
+
* updates the Firefox source.
|
|
18
|
+
*
|
|
19
|
+
* @param projectRoot - Root directory of the project
|
|
20
|
+
* @param options - Sync options
|
|
21
|
+
*/
|
|
22
|
+
export async function furnaceSyncCommand(projectRoot, options = {}) {
|
|
23
|
+
intro('Furnace Sync');
|
|
24
|
+
// Pre-flight checks
|
|
25
|
+
const paths = getProjectPaths(projectRoot);
|
|
26
|
+
if (!(await pathExists(paths.engine))) {
|
|
27
|
+
throw new FurnaceError('Engine directory not found. Run "fireforge download" first.');
|
|
28
|
+
}
|
|
29
|
+
if (!(await furnaceConfigExists(projectRoot))) {
|
|
30
|
+
throw new FurnaceError('No furnace.json found. Run "fireforge furnace create" or "fireforge furnace override" to get started.');
|
|
31
|
+
}
|
|
32
|
+
const config = await loadFurnaceConfig(projectRoot);
|
|
33
|
+
const forgeConfig = await loadConfig(projectRoot);
|
|
34
|
+
const overrideCount = Object.keys(config.overrides).length;
|
|
35
|
+
const customCount = Object.keys(config.custom).length;
|
|
36
|
+
if (overrideCount === 0 && customCount === 0) {
|
|
37
|
+
info('No components to sync.');
|
|
38
|
+
outro('Done');
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
// Phase 1: Detect and report baseVersion drift
|
|
42
|
+
const driftEntries = findOverrideBaseVersionDrift(config, forgeConfig.firefox.version);
|
|
43
|
+
if (driftEntries.length > 0) {
|
|
44
|
+
info(`Found ${driftEntries.length} override(s) with baseVersion drift:`);
|
|
45
|
+
for (const entry of driftEntries) {
|
|
46
|
+
warn(formatOverrideBaseVersionDriftWarning(entry));
|
|
47
|
+
}
|
|
48
|
+
// Phase 2: Refresh drifted overrides via three-way merge
|
|
49
|
+
info('\nRefreshing drifted overrides...');
|
|
50
|
+
await furnaceRefreshCommand(projectRoot, undefined, {
|
|
51
|
+
all: true,
|
|
52
|
+
...(options.dryRun !== undefined ? { dryRun: options.dryRun } : {}),
|
|
53
|
+
...(options.strategy !== undefined ? { strategy: options.strategy } : {}),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
info('All overrides are up-to-date with the current Firefox version.');
|
|
58
|
+
}
|
|
59
|
+
// Phase 3: Re-apply all components to the engine
|
|
60
|
+
if (!options.dryRun) {
|
|
61
|
+
info('\nApplying all components to engine...');
|
|
62
|
+
const applySpinner = spinner('Applying components...');
|
|
63
|
+
const result = await runFurnaceMutation(projectRoot, 'apply-rollback', (ctx) => applyAllComponents(projectRoot, false, { operationContext: ctx }), { dryRun: false });
|
|
64
|
+
applySpinner.stop('Components applied');
|
|
65
|
+
logApplyResult(result, false);
|
|
66
|
+
const appliedWithStepErrorsCount = result.applied.filter((entry) => (entry.stepErrors?.length ?? 0) > 0).length;
|
|
67
|
+
const totalFailures = result.errors.length + appliedWithStepErrorsCount;
|
|
68
|
+
if (totalFailures > 0) {
|
|
69
|
+
throw new FurnaceError(`${totalFailures} component${totalFailures === 1 ? '' : 's'} failed to apply cleanly`);
|
|
70
|
+
}
|
|
71
|
+
outro(`Sync complete — ${result.applied.length} applied, ${result.skipped.length} skipped`);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
outro('Dry run complete');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=sync.js.map
|
|
@@ -2,5 +2,8 @@
|
|
|
2
2
|
* Runs the furnace validate command to perform static analysis on components.
|
|
3
3
|
* @param projectRoot - Root directory of the project
|
|
4
4
|
* @param name - Optional component name to validate (validates all if omitted)
|
|
5
|
+
* @param options - Optional command options (e.g. --fix)
|
|
5
6
|
*/
|
|
6
|
-
export declare function furnaceValidateCommand(projectRoot: string, name?: string
|
|
7
|
+
export declare function furnaceValidateCommand(projectRoot: string, name?: string, options?: {
|
|
8
|
+
fix?: boolean;
|
|
9
|
+
}): Promise<void>;
|
|
@@ -1,17 +1,96 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
import { readdir } from 'node:fs/promises';
|
|
2
3
|
import { join } from 'node:path';
|
|
4
|
+
import { getProjectPaths } from '../../core/config.js';
|
|
3
5
|
import { furnaceConfigExists, getFurnacePaths, loadFurnaceConfig, } from '../../core/furnace-config.js';
|
|
6
|
+
import { addCustomElementRegistration, addJarMnEntries } from '../../core/furnace-registration.js';
|
|
4
7
|
import { validateAllComponents, validateComponent } from '../../core/furnace-validate.js';
|
|
5
8
|
import { FurnaceError } from '../../errors/furnace.js';
|
|
6
9
|
import { pathExists } from '../../utils/fs.js';
|
|
7
|
-
import { info, intro, note, outro, success } from '../../utils/logger.js';
|
|
10
|
+
import { info, intro, note, outro, success, warn } from '../../utils/logger.js';
|
|
8
11
|
import { displayValidationIssues } from './validation-output.js';
|
|
12
|
+
/** Checks that auto-fix can correct. */
|
|
13
|
+
const FIXABLE_CHECKS = new Set([
|
|
14
|
+
'missing-jar-mn-mjs',
|
|
15
|
+
'missing-jar-mn-css',
|
|
16
|
+
'wrong-registration-pattern',
|
|
17
|
+
]);
|
|
18
|
+
/**
|
|
19
|
+
* Auto-fixes registration issues that have deterministic solutions.
|
|
20
|
+
* @returns Number of issues fixed
|
|
21
|
+
*/
|
|
22
|
+
async function autoFixIssues(projectRoot, issues) {
|
|
23
|
+
const { engine: engineDir } = getProjectPaths(projectRoot);
|
|
24
|
+
const config = await loadFurnaceConfig(projectRoot);
|
|
25
|
+
const furnacePaths = getFurnacePaths(projectRoot);
|
|
26
|
+
let fixed = 0;
|
|
27
|
+
// Group jar.mn fixes per component to batch them
|
|
28
|
+
const jarMnFixesByComponent = new Map();
|
|
29
|
+
for (const issue of issues) {
|
|
30
|
+
if (!FIXABLE_CHECKS.has(issue.check))
|
|
31
|
+
continue;
|
|
32
|
+
const customConfig = config.custom[issue.component];
|
|
33
|
+
if (!customConfig)
|
|
34
|
+
continue;
|
|
35
|
+
if (issue.check === 'missing-jar-mn-mjs' || issue.check === 'missing-jar-mn-css') {
|
|
36
|
+
const ext = issue.check === 'missing-jar-mn-mjs' ? '.mjs' : '.css';
|
|
37
|
+
const fileName = `${issue.component}${ext}`;
|
|
38
|
+
const existing = jarMnFixesByComponent.get(issue.component) ?? [];
|
|
39
|
+
existing.push(fileName);
|
|
40
|
+
jarMnFixesByComponent.set(issue.component, existing);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// Fix jar.mn entries
|
|
44
|
+
for (const [componentName, files] of jarMnFixesByComponent) {
|
|
45
|
+
try {
|
|
46
|
+
await addJarMnEntries(engineDir, componentName, files);
|
|
47
|
+
fixed += files.length;
|
|
48
|
+
info(`Fixed: added ${files.join(', ')} to jar.mn for ${componentName}`);
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
warn(`Could not fix jar.mn for ${componentName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Fix missing customElements.js registrations
|
|
55
|
+
for (const issue of issues) {
|
|
56
|
+
if (issue.check !== 'wrong-registration-pattern')
|
|
57
|
+
continue;
|
|
58
|
+
// wrong-registration-pattern means it IS registered, but in the wrong block.
|
|
59
|
+
// We don't auto-fix this as it requires moving code between blocks, which
|
|
60
|
+
// is too risky. Only fix truly missing registrations.
|
|
61
|
+
}
|
|
62
|
+
// Check for components that are missing from customElements.js entirely
|
|
63
|
+
// (detected by post-apply consistency, not by validate — but we can check here)
|
|
64
|
+
for (const [componentName, customConfig] of Object.entries(config.custom)) {
|
|
65
|
+
if (!customConfig.register)
|
|
66
|
+
continue;
|
|
67
|
+
const componentDir = join(furnacePaths.customDir, componentName);
|
|
68
|
+
if (!(await pathExists(componentDir)))
|
|
69
|
+
continue;
|
|
70
|
+
const entries = await readdir(componentDir, { withFileTypes: true });
|
|
71
|
+
const hasMjs = entries.some((e) => e.isFile() && e.name === `${componentName}.mjs`);
|
|
72
|
+
if (!hasMjs)
|
|
73
|
+
continue;
|
|
74
|
+
const modulePath = `chrome://global/content/elements/${componentName}.mjs`;
|
|
75
|
+
try {
|
|
76
|
+
await addCustomElementRegistration(engineDir, componentName, modulePath);
|
|
77
|
+
// addCustomElementRegistration is idempotent — it returns without error
|
|
78
|
+
// if already registered. We only count it as fixed if a matching issue
|
|
79
|
+
// existed in the input.
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// Ignore — idempotent call, may already be registered
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return fixed;
|
|
86
|
+
}
|
|
9
87
|
/**
|
|
10
88
|
* Runs the furnace validate command to perform static analysis on components.
|
|
11
89
|
* @param projectRoot - Root directory of the project
|
|
12
90
|
* @param name - Optional component name to validate (validates all if omitted)
|
|
91
|
+
* @param options - Optional command options (e.g. --fix)
|
|
13
92
|
*/
|
|
14
|
-
export async function furnaceValidateCommand(projectRoot, name) {
|
|
93
|
+
export async function furnaceValidateCommand(projectRoot, name, options = {}) {
|
|
15
94
|
intro('Furnace Validate');
|
|
16
95
|
if (!(await furnaceConfigExists(projectRoot))) {
|
|
17
96
|
throw new FurnaceError('No furnace.json found. Run "fireforge furnace create" or "fireforge furnace override" to get started.');
|
|
@@ -21,6 +100,7 @@ export async function furnaceValidateCommand(projectRoot, name) {
|
|
|
21
100
|
let totalErrors = 0;
|
|
22
101
|
let totalWarnings = 0;
|
|
23
102
|
let componentCount;
|
|
103
|
+
let allIssues = [];
|
|
24
104
|
if (name) {
|
|
25
105
|
// --- Single component validation ---
|
|
26
106
|
let type;
|
|
@@ -46,6 +126,7 @@ export async function furnaceValidateCommand(projectRoot, name) {
|
|
|
46
126
|
}
|
|
47
127
|
const issues = await validateComponent(componentDir, name, type, config, projectRoot);
|
|
48
128
|
componentCount = 1;
|
|
129
|
+
allIssues = issues;
|
|
49
130
|
if (issues.length === 0) {
|
|
50
131
|
success(`${name} — all checks passed`);
|
|
51
132
|
}
|
|
@@ -70,6 +151,7 @@ export async function furnaceValidateCommand(projectRoot, name) {
|
|
|
70
151
|
const results = await validateAllComponents(projectRoot);
|
|
71
152
|
componentCount = results.size;
|
|
72
153
|
for (const [componentName, issues] of results) {
|
|
154
|
+
allIssues.push(...issues);
|
|
73
155
|
if (issues.length === 0) {
|
|
74
156
|
success(`${componentName} — all checks passed`);
|
|
75
157
|
}
|
|
@@ -80,10 +162,24 @@ export async function furnaceValidateCommand(projectRoot, name) {
|
|
|
80
162
|
}
|
|
81
163
|
}
|
|
82
164
|
}
|
|
165
|
+
// Auto-fix fixable issues when --fix is passed
|
|
166
|
+
if (options.fix && allIssues.length > 0) {
|
|
167
|
+
const fixableIssues = allIssues.filter((issue) => FIXABLE_CHECKS.has(issue.check));
|
|
168
|
+
if (fixableIssues.length > 0) {
|
|
169
|
+
const fixedCount = await autoFixIssues(projectRoot, fixableIssues);
|
|
170
|
+
if (fixedCount > 0) {
|
|
171
|
+
info(`\nAuto-fixed ${fixedCount} issue(s). Re-run validate to confirm.`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
info('\nNo auto-fixable issues found. Remaining issues require manual resolution.');
|
|
176
|
+
}
|
|
177
|
+
}
|
|
83
178
|
// Summary
|
|
84
179
|
note(`${totalErrors} error(s), ${totalWarnings} warning(s) across ${componentCount} component(s)`, 'Validation Summary');
|
|
85
180
|
if (totalErrors > 0) {
|
|
86
|
-
|
|
181
|
+
const fixHint = options.fix ? '' : ' Use --fix to auto-correct registration issues.';
|
|
182
|
+
info(`Fix the errors above and run "fireforge furnace validate" again.${fixHint}`);
|
|
87
183
|
throw new FurnaceError(`Validation failed with ${totalErrors} error(s).`);
|
|
88
184
|
}
|
|
89
185
|
outro('Validation passed');
|
|
@@ -1,7 +1,30 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { getFurnacePaths } from '../../core/furnace-config.js';
|
|
2
|
+
import type { FurnaceConfig, ValidationIssue } from '../../types/furnace.js';
|
|
3
|
+
import { type SpinnerHandle } from '../../utils/logger.js';
|
|
2
4
|
/**
|
|
3
5
|
* Displays validation issues and returns aggregated error and warning counts.
|
|
4
6
|
* @param issues - Validation issues to render
|
|
5
7
|
* @returns Tuple of [errorCount, warningCount]
|
|
6
8
|
*/
|
|
7
9
|
export declare function displayValidationIssues(issues: ValidationIssue[]): [number, number];
|
|
10
|
+
export type ValidationResult = {
|
|
11
|
+
done: true;
|
|
12
|
+
} | {
|
|
13
|
+
done: false;
|
|
14
|
+
totalErrors: number;
|
|
15
|
+
totalWarnings: number;
|
|
16
|
+
componentCount: number;
|
|
17
|
+
skippedValidationCount: number;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Runs the validation phase of a furnace deploy, checking all or a single component.
|
|
21
|
+
* @param validateSpinner - Active spinner handle for progress display
|
|
22
|
+
* @param name - Optional component name (validates all if omitted)
|
|
23
|
+
* @param config - Loaded Furnace configuration
|
|
24
|
+
* @param furnacePaths - Resolved Furnace workspace paths
|
|
25
|
+
* @param failedComponents - Names of components whose apply step failed
|
|
26
|
+
* @param isDryRun - Whether deploy is running in dry-run mode
|
|
27
|
+
* @param projectRoot - Root directory of the project
|
|
28
|
+
* @returns Validation counts, or `done: true` if the caller should early-return
|
|
29
|
+
*/
|
|
30
|
+
export declare function runDeployValidation(validateSpinner: SpinnerHandle, name: string | undefined, config: FurnaceConfig, furnacePaths: ReturnType<typeof getFurnacePaths>, failedComponents: Set<string>, isDryRun: boolean, projectRoot: string): Promise<ValidationResult>;
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { validateAllComponents, validateComponent } from '../../core/furnace-validate.js';
|
|
4
|
+
import { FurnaceError } from '../../errors/furnace.js';
|
|
5
|
+
import { pathExists } from '../../utils/fs.js';
|
|
6
|
+
import { error, info, outro, success, warn } from '../../utils/logger.js';
|
|
2
7
|
/**
|
|
3
8
|
* Displays validation issues and returns aggregated error and warning counts.
|
|
4
9
|
* @param issues - Validation issues to render
|
|
@@ -19,4 +24,91 @@ export function displayValidationIssues(issues) {
|
|
|
19
24
|
}
|
|
20
25
|
return [errors, warnings];
|
|
21
26
|
}
|
|
27
|
+
function resolveNamedValidationTarget(name, config, furnacePaths) {
|
|
28
|
+
if (name in config.overrides) {
|
|
29
|
+
return {
|
|
30
|
+
type: 'override',
|
|
31
|
+
componentDir: join(furnacePaths.overridesDir, name),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
if (name in config.custom) {
|
|
35
|
+
return {
|
|
36
|
+
type: 'custom',
|
|
37
|
+
componentDir: join(furnacePaths.customDir, name),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
if (config.stock.includes(name)) {
|
|
41
|
+
return 'stock';
|
|
42
|
+
}
|
|
43
|
+
throw new FurnaceError(`Component "${name}" not found in furnace.json.`, name);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Runs the validation phase of a furnace deploy, checking all or a single component.
|
|
47
|
+
* @param validateSpinner - Active spinner handle for progress display
|
|
48
|
+
* @param name - Optional component name (validates all if omitted)
|
|
49
|
+
* @param config - Loaded Furnace configuration
|
|
50
|
+
* @param furnacePaths - Resolved Furnace workspace paths
|
|
51
|
+
* @param failedComponents - Names of components whose apply step failed
|
|
52
|
+
* @param isDryRun - Whether deploy is running in dry-run mode
|
|
53
|
+
* @param projectRoot - Root directory of the project
|
|
54
|
+
* @returns Validation counts, or `done: true` if the caller should early-return
|
|
55
|
+
*/
|
|
56
|
+
export async function runDeployValidation(validateSpinner, name, config, furnacePaths, failedComponents, isDryRun, projectRoot) {
|
|
57
|
+
let totalErrors = 0;
|
|
58
|
+
let totalWarnings = 0;
|
|
59
|
+
let componentCount = 0;
|
|
60
|
+
let skippedValidationCount = 0;
|
|
61
|
+
if (name && failedComponents.has(name)) {
|
|
62
|
+
skippedValidationCount = 1;
|
|
63
|
+
validateSpinner.stop('Validation skipped');
|
|
64
|
+
warn(`Skipping validation for ${name} because apply failed.`);
|
|
65
|
+
}
|
|
66
|
+
else if (name) {
|
|
67
|
+
const target = resolveNamedValidationTarget(name, config, furnacePaths);
|
|
68
|
+
if (target === 'stock') {
|
|
69
|
+
validateSpinner.stop('Validation skipped');
|
|
70
|
+
info(`"${name}" is a stock component. Stock components are not validated locally.`);
|
|
71
|
+
outro(isDryRun ? 'Dry run complete' : 'Deploy complete');
|
|
72
|
+
return { done: true };
|
|
73
|
+
}
|
|
74
|
+
if (!(await pathExists(target.componentDir))) {
|
|
75
|
+
validateSpinner.stop('Validation failed');
|
|
76
|
+
throw new FurnaceError(`Component directory not found for "${name}".`, name);
|
|
77
|
+
}
|
|
78
|
+
const issues = await validateComponent(target.componentDir, name, target.type, config, projectRoot);
|
|
79
|
+
componentCount = 1;
|
|
80
|
+
validateSpinner.stop('Validation complete');
|
|
81
|
+
if (issues.length === 0) {
|
|
82
|
+
success(`${name} — all checks passed`);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
const [errors, warnings] = displayValidationIssues(issues);
|
|
86
|
+
totalErrors += errors;
|
|
87
|
+
totalWarnings += warnings;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
const results = await validateAllComponents(projectRoot);
|
|
92
|
+
validateSpinner.stop('Validation complete');
|
|
93
|
+
for (const [componentName, issues] of results) {
|
|
94
|
+
if (failedComponents.has(componentName)) {
|
|
95
|
+
skippedValidationCount++;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
componentCount++;
|
|
99
|
+
if (issues.length === 0) {
|
|
100
|
+
success(`${componentName} — all checks passed`);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
const [errors, warnings] = displayValidationIssues(issues);
|
|
104
|
+
totalErrors += errors;
|
|
105
|
+
totalWarnings += warnings;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (skippedValidationCount > 0) {
|
|
109
|
+
warn(`Skipped validation for ${skippedValidationCount} component(s) because their apply step failed.`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return { done: false, totalErrors, totalWarnings, componentCount, skippedValidationCount };
|
|
113
|
+
}
|
|
22
114
|
//# sourceMappingURL=validation-output.js.map
|
|
@@ -138,7 +138,8 @@ async function checkEngineDrift(engineDir, baseCommit, forceImport) {
|
|
|
138
138
|
* @param options - Import options
|
|
139
139
|
*/
|
|
140
140
|
export async function importCommand(projectRoot, options = {}) {
|
|
141
|
-
|
|
141
|
+
const isDryRun = options.dryRun === true;
|
|
142
|
+
intro(isDryRun ? 'FireForge Import (dry run)' : 'FireForge Import');
|
|
142
143
|
const continueOnFailure = options.continue ?? false;
|
|
143
144
|
const forceImport = options.force ?? false;
|
|
144
145
|
const paths = getProjectPaths(projectRoot);
|
|
@@ -148,7 +149,7 @@ export async function importCommand(projectRoot, options = {}) {
|
|
|
148
149
|
}
|
|
149
150
|
// Engine consistency check before applying patches
|
|
150
151
|
const state = await loadState(projectRoot);
|
|
151
|
-
if (state.baseCommit) {
|
|
152
|
+
if (state.baseCommit && !isDryRun) {
|
|
152
153
|
const shouldContinue = await checkEngineDrift(paths.engine, state.baseCommit, forceImport);
|
|
153
154
|
if (!shouldContinue)
|
|
154
155
|
return;
|
|
@@ -195,10 +196,33 @@ export async function importCommand(projectRoot, options = {}) {
|
|
|
195
196
|
}
|
|
196
197
|
info('Run "fireforge doctor" for more details.\n');
|
|
197
198
|
}
|
|
199
|
+
// Dry-run: list patches that would be applied and exit
|
|
200
|
+
if (isDryRun) {
|
|
201
|
+
if (manifest) {
|
|
202
|
+
const patches = options.until
|
|
203
|
+
? manifest.patches.filter((p) => {
|
|
204
|
+
const untilPatch = manifest.patches.find((u) => u.filename === options.until || u.filename === `${options.until}.patch`);
|
|
205
|
+
return untilPatch ? p.order <= untilPatch.order : true;
|
|
206
|
+
})
|
|
207
|
+
: manifest.patches;
|
|
208
|
+
info(`\n[dry-run] Would apply ${patches.length} patch(es) in order:`);
|
|
209
|
+
for (const patch of patches) {
|
|
210
|
+
info(` ${patch.filename} (${patch.filesAffected.length} file${patch.filesAffected.length === 1 ? '' : 's'})`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
info(`\n[dry-run] Would apply ${patchCount} patch(es)`);
|
|
215
|
+
}
|
|
216
|
+
outro('Dry run complete — no changes made');
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
198
219
|
await checkUncommittedPatchFiles(paths.engine, paths.patches, forceImport);
|
|
199
220
|
const s = spinner('Applying patches...');
|
|
200
221
|
try {
|
|
201
|
-
const summary = await applyPatchesWithContinue(paths.patches, paths.engine,
|
|
222
|
+
const summary = await applyPatchesWithContinue(paths.patches, paths.engine, {
|
|
223
|
+
continueOnFailure,
|
|
224
|
+
untilFilename: options.until,
|
|
225
|
+
});
|
|
202
226
|
// Handle failures
|
|
203
227
|
if (summary.failed.length > 0) {
|
|
204
228
|
s.error(`${summary.failed.length} patch(es) failed`);
|
|
@@ -234,8 +258,17 @@ export function registerImport(program, { getProjectRoot, withErrorHandling }) {
|
|
|
234
258
|
.description('Apply patches from the patches directory')
|
|
235
259
|
.option('--continue', 'Continue applying patches even if one fails')
|
|
236
260
|
.option('-f, --force', 'Proceed despite engine drift and overwrite unmanaged changes in patch-touched files')
|
|
261
|
+
.option('--until <patch>', 'Apply patches only up to and including this patch (alias: --stop-at)')
|
|
262
|
+
.option('--stop-at <patch>', 'Alias for --until')
|
|
263
|
+
.option('--dry-run', 'Preview which patches would be applied without modifying the engine')
|
|
237
264
|
.action(withErrorHandling(async (options) => {
|
|
238
|
-
|
|
265
|
+
// Accept both spellings; --until wins when both are passed.
|
|
266
|
+
const merged = { ...options };
|
|
267
|
+
if (merged.until === undefined && merged.stopAt !== undefined) {
|
|
268
|
+
merged.until = merged.stopAt;
|
|
269
|
+
}
|
|
270
|
+
delete merged.stopAt;
|
|
271
|
+
await importCommand(getProjectRoot(), pickDefined(merged));
|
|
239
272
|
}));
|
|
240
273
|
}
|
|
241
274
|
//# sourceMappingURL=import.js.map
|
|
@@ -6,7 +6,7 @@ import { getStatusWithCodes, hasChanges, isGitRepository } from '../core/git.js'
|
|
|
6
6
|
import { getAllDiff, getDiffForFilesAgainstHead } from '../core/git-diff.js';
|
|
7
7
|
import { getModifiedFilesInDir, getUntrackedFiles, getUntrackedFilesInDir, } from '../core/git-status.js';
|
|
8
8
|
import { extractAffectedFiles } from '../core/patch-apply.js';
|
|
9
|
-
import { lintExportedPatch } from '../core/patch-lint.js';
|
|
9
|
+
import { buildPatchQueueContext, lintExportedPatch, lintPatchQueue } from '../core/patch-lint.js';
|
|
10
10
|
import { GeneralError } from '../errors/base.js';
|
|
11
11
|
import { pathExists } from '../utils/fs.js';
|
|
12
12
|
import { info, intro, outro, success, warn } from '../utils/logger.js';
|
|
@@ -85,7 +85,16 @@ export async function lintCommand(projectRoot, files) {
|
|
|
85
85
|
}
|
|
86
86
|
const config = await loadConfig(projectRoot);
|
|
87
87
|
const filesAffected = extractAffectedFiles(diff);
|
|
88
|
-
const issues =
|
|
88
|
+
const issues = [
|
|
89
|
+
...(await lintExportedPatch(paths.engine, filesAffected, diff, config)),
|
|
90
|
+
];
|
|
91
|
+
// Cross-patch rules operate over the whole queue, so run them whenever a
|
|
92
|
+
// patches directory exists — they surface duplicate /dev/null creations
|
|
93
|
+
// and forward-import chains that the per-patch orchestrator cannot see.
|
|
94
|
+
if (await pathExists(paths.patches)) {
|
|
95
|
+
const ctx = await buildPatchQueueContext(paths.patches);
|
|
96
|
+
issues.push(...lintPatchQueue(ctx));
|
|
97
|
+
}
|
|
89
98
|
if (issues.length === 0) {
|
|
90
99
|
success('No lint issues found.');
|
|
91
100
|
outro('Lint passed');
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Central manifest of every top-level FireForge command.
|
|
3
|
+
*
|
|
4
|
+
* The manifest is iterated from {@link createProgram} in cli.ts so that
|
|
5
|
+
* adding a new command is a one-line change here instead of a three-line
|
|
6
|
+
* edit (import + registration call + ordering) spread across cli.ts. It
|
|
7
|
+
* also gives documentation tooling and tests a single authoritative list
|
|
8
|
+
* of commands to enumerate.
|
|
9
|
+
*
|
|
10
|
+
* The order of entries in {@link COMMAND_MANIFEST} is the order commands
|
|
11
|
+
* appear in `fireforge --help`; it is intentional, not alphabetical, and
|
|
12
|
+
* groups related commands together.
|
|
13
|
+
*/
|
|
14
|
+
import type { CommandRegistrar } from '../types/cli.js';
|
|
15
|
+
/**
|
|
16
|
+
* A single entry in the command manifest.
|
|
17
|
+
*/
|
|
18
|
+
export interface CommandManifestEntry {
|
|
19
|
+
/**
|
|
20
|
+
* Human-readable command name, matching the first token of the
|
|
21
|
+
* command line (e.g. `build`, `furnace`). Informational only — the
|
|
22
|
+
* authoritative command string lives inside each registrar's
|
|
23
|
+
* `.command(...)` call — but useful for documentation, manifest
|
|
24
|
+
* introspection, and test assertions.
|
|
25
|
+
*/
|
|
26
|
+
name: string;
|
|
27
|
+
/**
|
|
28
|
+
* Short one-line group label, used purely for grouping in generated
|
|
29
|
+
* documentation. Not surfaced in the CLI itself.
|
|
30
|
+
*/
|
|
31
|
+
group: 'project' | 'workflow' | 'engine' | 'diagnostics' | 'components';
|
|
32
|
+
/** Registers the command (and any subcommands) on the Commander program. */
|
|
33
|
+
register: CommandRegistrar;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Ordered list of every top-level FireForge command. cli.ts iterates this
|
|
37
|
+
* array to register commands in a single loop.
|
|
38
|
+
*/
|
|
39
|
+
export declare const COMMAND_MANIFEST: readonly CommandManifestEntry[];
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { registerBootstrap } from './bootstrap.js';
|
|
2
|
+
import { registerBuild } from './build.js';
|
|
3
|
+
import { registerConfig } from './config.js';
|
|
4
|
+
import { registerDiscard } from './discard.js';
|
|
5
|
+
import { registerDoctor } from './doctor.js';
|
|
6
|
+
import { registerDownload } from './download.js';
|
|
7
|
+
import { registerExport } from './export.js';
|
|
8
|
+
import { registerExportAll } from './export-all.js';
|
|
9
|
+
import { registerFurnace } from './furnace/index.js';
|
|
10
|
+
import { registerImport } from './import.js';
|
|
11
|
+
import { registerLint } from './lint.js';
|
|
12
|
+
import { registerPackage } from './package.js';
|
|
13
|
+
import { registerPatch } from './patch/index.js';
|
|
14
|
+
import { registerReExport } from './re-export.js';
|
|
15
|
+
import { registerRebase } from './rebase.js';
|
|
16
|
+
import { registerRegister } from './register.js';
|
|
17
|
+
import { registerReset } from './reset.js';
|
|
18
|
+
import { registerResolve } from './resolve.js';
|
|
19
|
+
import { registerRun } from './run.js';
|
|
20
|
+
import { registerSetup } from './setup.js';
|
|
21
|
+
import { registerStatus } from './status.js';
|
|
22
|
+
import { registerTest } from './test.js';
|
|
23
|
+
import { registerToken } from './token.js';
|
|
24
|
+
import { registerVerify } from './verify.js';
|
|
25
|
+
import { registerWatch } from './watch.js';
|
|
26
|
+
import { registerWire } from './wire.js';
|
|
27
|
+
/**
|
|
28
|
+
* Ordered list of every top-level FireForge command. cli.ts iterates this
|
|
29
|
+
* array to register commands in a single loop.
|
|
30
|
+
*/
|
|
31
|
+
export const COMMAND_MANIFEST = [
|
|
32
|
+
{ name: 'setup', group: 'project', register: registerSetup },
|
|
33
|
+
{ name: 'download', group: 'engine', register: registerDownload },
|
|
34
|
+
{ name: 'bootstrap', group: 'engine', register: registerBootstrap },
|
|
35
|
+
{ name: 'import', group: 'workflow', register: registerImport },
|
|
36
|
+
{ name: 'resolve', group: 'workflow', register: registerResolve },
|
|
37
|
+
{ name: 'build', group: 'workflow', register: registerBuild },
|
|
38
|
+
{ name: 'run', group: 'workflow', register: registerRun },
|
|
39
|
+
{ name: 'status', group: 'workflow', register: registerStatus },
|
|
40
|
+
{ name: 'reset', group: 'workflow', register: registerReset },
|
|
41
|
+
{ name: 'discard', group: 'workflow', register: registerDiscard },
|
|
42
|
+
{ name: 'export', group: 'workflow', register: registerExport },
|
|
43
|
+
{ name: 'export-all', group: 'workflow', register: registerExportAll },
|
|
44
|
+
{ name: 're-export', group: 'workflow', register: registerReExport },
|
|
45
|
+
{ name: 'patch', group: 'workflow', register: registerPatch },
|
|
46
|
+
{ name: 'rebase', group: 'workflow', register: registerRebase },
|
|
47
|
+
{ name: 'package', group: 'workflow', register: registerPackage },
|
|
48
|
+
{ name: 'watch', group: 'workflow', register: registerWatch },
|
|
49
|
+
{ name: 'test', group: 'workflow', register: registerTest },
|
|
50
|
+
{ name: 'config', group: 'project', register: registerConfig },
|
|
51
|
+
{ name: 'doctor', group: 'diagnostics', register: registerDoctor },
|
|
52
|
+
{ name: 'register', group: 'workflow', register: registerRegister },
|
|
53
|
+
{ name: 'wire', group: 'workflow', register: registerWire },
|
|
54
|
+
{ name: 'token', group: 'components', register: registerToken },
|
|
55
|
+
{ name: 'lint', group: 'diagnostics', register: registerLint },
|
|
56
|
+
{ name: 'verify', group: 'diagnostics', register: registerVerify },
|
|
57
|
+
{ name: 'furnace', group: 'components', register: registerFurnace },
|
|
58
|
+
];
|
|
59
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `fireforge patch delete <name>` — removes a patch from the queue.
|
|
3
|
+
*
|
|
4
|
+
* Destructive: refuses when a later patch imports a module owned by the
|
|
5
|
+
* target (that would leave a dangling forward import), prompts for
|
|
6
|
+
* confirmation interactively, requires `--yes` for non-TTY, supports
|
|
7
|
+
* `--dry-run`, and appends to `patches/.fireforge-history.jsonl` on success.
|
|
8
|
+
*/
|
|
9
|
+
import { Command } from 'commander';
|
|
10
|
+
import type { CommandContext } from '../../types/cli.js';
|
|
11
|
+
import type { PatchDeleteOptions } from '../../types/commands/index.js';
|
|
12
|
+
/**
|
|
13
|
+
* Runs the `patch delete` command: removes a patch file and its manifest
|
|
14
|
+
* row atomically, refusing when a later patch imports a leaf owned by the
|
|
15
|
+
* target.
|
|
16
|
+
*
|
|
17
|
+
* @param projectRoot - Project root directory
|
|
18
|
+
* @param identifier - Patch filename or ordinal number to delete
|
|
19
|
+
* @param options - Command options
|
|
20
|
+
*/
|
|
21
|
+
export declare function patchDeleteCommand(projectRoot: string, identifier: string, options?: PatchDeleteOptions): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Registers the `patch delete` subcommand on the `patch` parent.
|
|
24
|
+
*
|
|
25
|
+
* @param parent - Parent Commander command
|
|
26
|
+
* @param context - Shared CLI registration context
|
|
27
|
+
*/
|
|
28
|
+
export declare function registerPatchDelete(parent: Command, context: CommandContext): void;
|