@hominis/fireforge 0.18.9 → 0.18.11
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/README.md +20 -7
- package/dist/src/commands/doctor.js +1 -1
- package/dist/src/commands/furnace/index.js +1 -1
- package/dist/src/commands/lint.d.ts +36 -0
- package/dist/src/commands/lint.js +61 -1
- package/dist/src/commands/manifest.js +2 -0
- package/dist/src/commands/package.js +1 -1
- package/dist/src/commands/patch/index.d.ts +5 -3
- package/dist/src/commands/patch/index.js +8 -4
- package/dist/src/commands/patch/lint-ignore.d.ts +8 -0
- package/dist/src/commands/patch/lint-ignore.js +8 -4
- package/dist/src/commands/patch/rename.d.ts +36 -0
- package/dist/src/commands/patch/rename.js +244 -0
- package/dist/src/commands/test.js +8 -8
- package/dist/src/commands/typecheck.d.ts +52 -0
- package/dist/src/commands/typecheck.js +115 -0
- package/dist/src/core/config-paths.d.ts +2 -2
- package/dist/src/core/config-paths.js +5 -0
- package/dist/src/core/config-validate.js +64 -0
- package/dist/src/core/license-headers.d.ts +5 -0
- package/dist/src/core/license-headers.js +46 -5
- package/dist/src/core/mach-build-artifacts.d.ts +2 -2
- package/dist/src/core/mach-build-artifacts.js +2 -2
- package/dist/src/core/mach-error-hints.js +7 -8
- package/dist/src/core/marionette-port.js +4 -4
- package/dist/src/core/patch-export.d.ts +10 -0
- package/dist/src/core/patch-export.js +8 -2
- package/dist/src/core/patch-lint-checkjs.d.ts +14 -2
- package/dist/src/core/patch-lint-checkjs.js +40 -73
- package/dist/src/core/patch-lint-cross.js +6 -1
- package/dist/src/core/patch-lint.js +6 -4
- package/dist/src/core/typecheck-shim.d.ts +70 -0
- package/dist/src/core/typecheck-shim.js +112 -0
- package/dist/src/core/typecheck.d.ts +65 -0
- package/dist/src/core/typecheck.js +302 -0
- package/dist/src/core/xpcshell-appdir.d.ts +2 -2
- package/dist/src/core/xpcshell-appdir.js +2 -2
- package/dist/src/types/commands/index.d.ts +1 -1
- package/dist/src/types/commands/options.d.ts +29 -1
- package/dist/src/types/config.d.ts +33 -0
- package/dist/src/types/furnace.d.ts +1 -1
- package/dist/src/types/typecheck.d.ts +51 -0
- package/dist/src/types/typecheck.js +15 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ Inspired by [fern.js](https://github.com/ghostery/user-agent-desktop) and [Melon
|
|
|
24
24
|
|
|
25
25
|
- **Design token management** Track CSS custom property coverage across your modified files.
|
|
26
26
|
|
|
27
|
-
- **Quality checks** `fireforge lint` catches fork-specific issues (raw colours, missing licence headers, relative imports, large patches, cross-patch ordering problems) before you export. `fireforge verify` runs a read-only integrity check over the whole patch queue. `fireforge doctor` diagnoses project health including Furnace component validation.
|
|
27
|
+
- **Quality checks** `fireforge lint` catches fork-specific issues (raw colours, missing licence headers, relative imports, large patches, cross-patch ordering problems) before you export. `fireforge typecheck` runs CI-grade JS type checking against project-supplied jsconfig.json files (web components, chrome scripts) — separate from `lint`'s patch-hygiene checkJs pass. `fireforge verify` runs a read-only integrity check over the whole patch queue. `fireforge doctor` diagnoses project health including Furnace component validation.
|
|
28
28
|
|
|
29
29
|
- **Built and validated against real Firefox code** Developed by editing a real Firefox ESR codebase, learning from existing patch tools, observing the breakages and edge cases that surfaced and turning those findings into a realistic test suite. In-repo tests are thus grounded in actual development scenarios. Yes, we mock quite a bit, but when building a tool that modifies a separate code base, I think it's a solid compromise for the time being. Full end-to-end runs are currently run locally, as they require about 30 GB of disk and significant compute for multiple full builds. Full end-to-end via Github Actions will be added soonishlyTM.
|
|
30
30
|
|
|
@@ -57,7 +57,7 @@ Your project now has `fireforge.json`, an `engine/` directory with Firefox sourc
|
|
|
57
57
|
|
|
58
58
|
#### Known upstream build issues
|
|
59
59
|
|
|
60
|
-
- **macOS 15 (Darwin 25+) — `gecko-profiler` bindgen error `cannot find type _CharT in this scope`.** An Apple toolchain update changed `std::__CharT_pointer` to `_CharT_pointer` in the libc++ headers Firefox's bindgen walks, so `toolkit/library/rust/target-objects` fails during `mach build` even on a clean `fireforge bootstrap`. This is an upstream Firefox issue, not a FireForge bug. Two workarounds: pin Xcode's command line tools to a pre-September-2025 release via `xcode-select --install` / [Apple developer downloads](https://developer.apple.com/download/all/), or apply a one-line bindgen-basic-string-workaround patch (
|
|
60
|
+
- **macOS 15 (Darwin 25+) — `gecko-profiler` bindgen error `cannot find type _CharT in this scope`.** An Apple toolchain update changed `std::__CharT_pointer` to `_CharT_pointer` in the libc++ headers Firefox's bindgen walks, so `toolkit/library/rust/target-objects` fails during `mach build` even on a clean `fireforge bootstrap`. This is an upstream Firefox issue, not a FireForge bug. Two workarounds: pin Xcode's command line tools to a pre-September-2025 release via `xcode-select --install` / [Apple developer downloads](https://developer.apple.com/download/all/), or apply a one-line bindgen-basic-string-workaround patch (a downstream consumer may ship one in its patch queue). If you interrupt the resulting `fireforge build` and re-run `fireforge doctor`, the download/engine state is unaffected — the failure is isolated to the Rust compile phase.
|
|
61
61
|
|
|
62
62
|
### Workflow Overview
|
|
63
63
|
|
|
@@ -255,7 +255,20 @@ By default, a standalone `fireforge lint` (no arguments) lints the **aggregate**
|
|
|
255
255
|
|
|
256
256
|
**JSDoc validation** uses AST-based analysis (Acorn) to validate exported APIs in patch-owned `.sys.mjs` files. A file is "patch-owned" if it was newly created by the current diff or by an existing patch in the queue. Functions must document every `@param` (names must match) and include `@returns` when the function returns a value. Exported constants and classes require a JSDoc block.
|
|
257
257
|
|
|
258
|
-
**Optional `checkJs` pass.** Enable a TypeScript-esque bastardization of type checking for patch-owned `.sys.mjs` files by adding `"patchLint": { "checkJs": true }` to `fireforge.json`. This uses the TypeScript compiler API with `allowJs + checkJs + noEmit`, scoped only to patch-owned files. Firefox globals (`Services`, `ChromeUtils`, `lazy`, etc.) are shimmed automatically. Module-resolution errors from Firefox's `resource://` and `chrome://` URL schemes are suppressed since TypeScript cannot follow these. This pass solely focuses on type errors within the patch-owned code itself (mismatched JSDoc types, wrong argument counts, unreachable code, etc.).
|
|
258
|
+
**Optional `checkJs` pass.** Enable a TypeScript-esque bastardization of type checking for patch-owned `.sys.mjs` files by adding `"patchLint": { "checkJs": true }` to `fireforge.json`. This uses the TypeScript compiler API with `allowJs + checkJs + noEmit`, scoped only to patch-owned files. Firefox globals (`Services`, `ChromeUtils`, `lazy`, etc.) are shimmed automatically. Module-resolution errors from Firefox's `resource://` and `chrome://` URL schemes are suppressed since TypeScript cannot follow these. This pass solely focuses on type errors within the patch-owned code itself (mismatched JSDoc types, wrong argument counts, unreachable code, etc.). Projects that need to extend the built-in shim (e.g. for `MozLitElement`, `MozXULElement`, or fork-specific component bases) can point at an additional `.d.ts` via `"patchLint": { "checkJsExtraShim": "tools/types/<fork>-globals.d.ts" }`; the file is concatenated to the built-in shim — augment, don't redeclare.
|
|
259
|
+
|
|
260
|
+
**Whole-project type checking — `fireforge typecheck`.** `patchLint.checkJs` is patch-hygiene: scoped to patch-owned `.sys.mjs`, suppresses module-resolution noise, and runs every time `fireforge lint` runs. `fireforge typecheck` is the CI-grade complement: it runs whole projects you point at via `typecheck.projects` in `fireforge.json`, honours each jsconfig's strictness/include/exclude/`paths`, and is intended as a CI gate. The two are complementary; the recommended setup is `fireforge lint` on every patch export and `fireforge typecheck` on CI for the project-level baseline.
|
|
261
|
+
|
|
262
|
+
```jsonc
|
|
263
|
+
{
|
|
264
|
+
"typecheck": {
|
|
265
|
+
"projects": ["components/custom/jsconfig.json", "engine/browser/base/content/jsconfig.json"],
|
|
266
|
+
"extraShim": "tools/types/<fork>-globals.d.ts",
|
|
267
|
+
},
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
The command resolves each `projects` entry through TypeScript's own config parser (`readConfigFile` + `parseJsonConfigFileContent`), so `paths` mappings, `include`/`exclude` globs, and `lib` settings all behave the same as `tsc --noEmit -p <path>`. FireForge forces `noEmit: true` and defaults `allowJs`/`checkJs` to `true` only when the user has not set them — explicit `"checkJs": false` in a jsconfig is honoured (one notice, no diagnostics) so the IDE-noise opt-out remains an opt-out. The same `FIREFOX_GLOBALS_SHIM` and the same eight suppressed diagnostic codes (2304 / 2305 / 2306 / 2307 / 2552 / 2580 / 2792 / 7016 — module-resolution + global-name noise) apply, so a file that lints clean cannot fail typecheck for an inferable-only-from-source reason. Pass `--project <path>` for a one-off run against a single jsconfig (replaces the configured list, preserves `extraShim`). Exits non-zero on any error-severity diagnostic; warnings print but do not fail. TypeScript stays a dev-dependency — install it (`npm i -D typescript`) before running. The command does not honour `--since` or any patch-diff filter: it is whole-project by design.
|
|
259
272
|
|
|
260
273
|
**Optional `jsdocClassMethods` enforcement.** Set `"patchLint": { "jsdocClassMethods": "warning" | "error" }` in `fireforge.json` to extend JSDoc validation to class-method exports inside patch-owned `.sys.mjs` files. Every public method (instance and static), parameter-bearing constructor, getter, and setter must carry a leading JSDoc block; `@param` names must match the parameter list, and `@returns` is required when a method returns a value (getters and setters are exempt from `@returns`). Methods whose name starts with `_` or `#`, methods carrying `@private` or `@internal` in their JSDoc, and zero-parameter constructors are exempt. Defaults to `"off"`, so upgrading is a no-op until the knob is set.
|
|
261
274
|
|
|
@@ -667,10 +680,10 @@ The summary block reports two allowlist counters so operators can tell whether a
|
|
|
667
680
|
A feature with multiple components (e.g. an eight-component dock) typically wants one shared `.ftl` per feature rather than eight per-component stubs. `furnace create <tag> --localized --shared-ftl <chrome-uri>` participates in an existing feature-scoped bundle:
|
|
668
681
|
|
|
669
682
|
```bash
|
|
670
|
-
fireforge furnace create
|
|
683
|
+
fireforge furnace create mybrowser-dock-button --localized --shared-ftl browser/mybrowser-dock.ftl
|
|
671
684
|
```
|
|
672
685
|
|
|
673
|
-
The generated `.mjs` calls `insertFTLIfNeeded("browser/
|
|
686
|
+
The generated `.mjs` calls `insertFTLIfNeeded("browser/mybrowser-dock.ftl")` instead of the per-component path. No `<tag>.ftl` stub is written. The `furnace.json` `custom` entry carries a new `sharedFtl` field so apply, validate, and remove all honour the participation:
|
|
674
687
|
|
|
675
688
|
- `furnace apply` does not copy a per-component `.ftl` into the FTL tree nor add a locale `jar.mn` entry — the shared file is registered by whoever owns the feature bundle.
|
|
676
689
|
- `furnace remove` early-returns from the locale `jar.mn` cleanup, so dropping our component's reference does not orphan the bundle for every other participant.
|
|
@@ -685,9 +698,9 @@ The generated `.mjs` calls `insertFTLIfNeeded("browser/hominis-dock.ftl")` inste
|
|
|
685
698
|
When the wrapped inner element is hand-authored or is a non-stock `moz-*` widget that does not appear in `composes`, the explicit `keyboardCovered: true` field on the component's `furnace.json` entry forces the same skip:
|
|
686
699
|
|
|
687
700
|
```json
|
|
688
|
-
"
|
|
701
|
+
"mybrowser-dock-button": {
|
|
689
702
|
"description": "Dock button wrapper",
|
|
690
|
-
"targetPath": "components/custom/
|
|
703
|
+
"targetPath": "components/custom/mybrowser-dock-button",
|
|
691
704
|
"register": true,
|
|
692
705
|
"localized": false,
|
|
693
706
|
"composes": ["moz-button"],
|
|
@@ -337,7 +337,7 @@ const DOCTOR_CHECKS = [
|
|
|
337
337
|
for (const filename of repaired.recoveredFilenames) {
|
|
338
338
|
// 2026-04-24 eval Finding 6: the repair path used to tell the
|
|
339
339
|
// operator to hand-edit patches.json, which contradicts the
|
|
340
|
-
// README +
|
|
340
|
+
// README + downstream docs that treat the manifest as
|
|
341
341
|
// FireForge-owned. Point at the existing `re-export` /
|
|
342
342
|
// `export` workflow instead so the fix stays inside the tool:
|
|
343
343
|
// re-exporting the same files with an explicit `--description`
|
|
@@ -81,7 +81,7 @@ function registerFurnaceInfoCommands(furnace, context) {
|
|
|
81
81
|
return value;
|
|
82
82
|
})
|
|
83
83
|
.option('--compose <tags>', 'Record stock tags composed internally (metadata only, comma-separated)', (val) => val.split(',').map((s) => s.trim()))
|
|
84
|
-
.option('--shared-ftl <path>', 'Participate in an existing feature-scoped .ftl at this path (e.g. "browser/
|
|
84
|
+
.option('--shared-ftl <path>', 'Participate in an existing feature-scoped .ftl at this path (e.g. "browser/mybrowser-dock.ftl"); skips the per-component .ftl scaffold (implies --localized)')
|
|
85
85
|
.option('--dry-run', 'Show the planned file set and furnace.json changes without writing')
|
|
86
86
|
.option('--allow-prefix-mismatch', 'Create the component even when its name does not start with the configured `componentPrefix` in furnace.json. Without this flag the command refuses to write anything on a prefix mismatch.')
|
|
87
87
|
.action(withErrorHandling(async (name, options) => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import type { CommandContext } from '../types/cli.js';
|
|
3
|
+
import type { PatchLintIssue } from '../types/commands/index.js';
|
|
3
4
|
/** Options controlling how the lint command filters and tags its output. */
|
|
4
5
|
export interface LintCommandOptions {
|
|
5
6
|
/**
|
|
@@ -46,6 +47,41 @@ export interface LintCommandOptions {
|
|
|
46
47
|
*/
|
|
47
48
|
perPatch?: boolean;
|
|
48
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Result of {@link applyAggregateLintIgnoreSuppression}.
|
|
52
|
+
*/
|
|
53
|
+
export interface AggregateLintIgnoreResult {
|
|
54
|
+
/** Issues remaining after suppression. */
|
|
55
|
+
issues: PatchLintIssue[];
|
|
56
|
+
/** Number of issues dropped because an owning patch listed the check in `lintIgnore`. */
|
|
57
|
+
dropped: number;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Filters aggregate-mode lint issues against per-patch `lintIgnore`
|
|
61
|
+
* lists drawn from the manifest. An issue is dropped when at least one
|
|
62
|
+
* patch whose `filesAffected` covers `issue.file` lists `issue.check`
|
|
63
|
+
* in its `lintIgnore`.
|
|
64
|
+
*
|
|
65
|
+
* Mirrors the per-patch contract: `--per-patch` mode threads each
|
|
66
|
+
* patch's `lintIgnore` directly into `lintExportedPatch`, so a check
|
|
67
|
+
* the operator explicitly waived in `patches.json` does not surface.
|
|
68
|
+
* Aggregate `--since` mode previously rediscovered the suppressed
|
|
69
|
+
* warning every CI run because the diff was treated as a single unit
|
|
70
|
+
* with no patch-level scope. Attributing each issue's file to its
|
|
71
|
+
* owning patch via `filesAffected` re-establishes the same suppression
|
|
72
|
+
* semantics. Cross-patch findings (forward-import, duplicate-creation)
|
|
73
|
+
* still attribute via `issue.file` because the `file` field is the
|
|
74
|
+
* offending site, which is owned by some patch.
|
|
75
|
+
*
|
|
76
|
+
* Multiple owners: an issue is dropped if **any** owning patch waived
|
|
77
|
+
* the rule. Conservative — never adds new findings, only drops
|
|
78
|
+
* already-explicitly-waived ones.
|
|
79
|
+
*
|
|
80
|
+
* @param issues - Issues collected from the aggregate lint run.
|
|
81
|
+
* @param ctx - Patch queue context used to attribute file → patch.
|
|
82
|
+
* @returns Filtered issue list and the count of dropped findings.
|
|
83
|
+
*/
|
|
84
|
+
export declare function applyAggregateLintIgnoreSuppression(issues: PatchLintIssue[], ctx: import('../core/patch-lint.js').PatchQueueContext): AggregateLintIgnoreResult;
|
|
49
85
|
/**
|
|
50
86
|
* Runs the lint command to check engine changes against patch quality rules.
|
|
51
87
|
* @param projectRoot - Root directory of the project
|
|
@@ -160,6 +160,53 @@ async function resolveLintDiff(engineDir, files, binaryName, furnacePrefixes) {
|
|
|
160
160
|
}
|
|
161
161
|
return diff;
|
|
162
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* Filters aggregate-mode lint issues against per-patch `lintIgnore`
|
|
165
|
+
* lists drawn from the manifest. An issue is dropped when at least one
|
|
166
|
+
* patch whose `filesAffected` covers `issue.file` lists `issue.check`
|
|
167
|
+
* in its `lintIgnore`.
|
|
168
|
+
*
|
|
169
|
+
* Mirrors the per-patch contract: `--per-patch` mode threads each
|
|
170
|
+
* patch's `lintIgnore` directly into `lintExportedPatch`, so a check
|
|
171
|
+
* the operator explicitly waived in `patches.json` does not surface.
|
|
172
|
+
* Aggregate `--since` mode previously rediscovered the suppressed
|
|
173
|
+
* warning every CI run because the diff was treated as a single unit
|
|
174
|
+
* with no patch-level scope. Attributing each issue's file to its
|
|
175
|
+
* owning patch via `filesAffected` re-establishes the same suppression
|
|
176
|
+
* semantics. Cross-patch findings (forward-import, duplicate-creation)
|
|
177
|
+
* still attribute via `issue.file` because the `file` field is the
|
|
178
|
+
* offending site, which is owned by some patch.
|
|
179
|
+
*
|
|
180
|
+
* Multiple owners: an issue is dropped if **any** owning patch waived
|
|
181
|
+
* the rule. Conservative — never adds new findings, only drops
|
|
182
|
+
* already-explicitly-waived ones.
|
|
183
|
+
*
|
|
184
|
+
* @param issues - Issues collected from the aggregate lint run.
|
|
185
|
+
* @param ctx - Patch queue context used to attribute file → patch.
|
|
186
|
+
* @returns Filtered issue list and the count of dropped findings.
|
|
187
|
+
*/
|
|
188
|
+
export function applyAggregateLintIgnoreSuppression(issues, ctx) {
|
|
189
|
+
const suppressionsByFile = new Map();
|
|
190
|
+
for (const entry of ctx.entries) {
|
|
191
|
+
const ignoreList = entry.metadata?.lintIgnore;
|
|
192
|
+
if (!ignoreList || ignoreList.length === 0)
|
|
193
|
+
continue;
|
|
194
|
+
for (const f of entry.metadata?.filesAffected ?? []) {
|
|
195
|
+
let bucket = suppressionsByFile.get(f);
|
|
196
|
+
if (!bucket) {
|
|
197
|
+
bucket = new Set();
|
|
198
|
+
suppressionsByFile.set(f, bucket);
|
|
199
|
+
}
|
|
200
|
+
for (const id of ignoreList)
|
|
201
|
+
bucket.add(id);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (suppressionsByFile.size === 0) {
|
|
205
|
+
return { issues, dropped: 0 };
|
|
206
|
+
}
|
|
207
|
+
const filtered = issues.filter((issue) => !suppressionsByFile.get(issue.file)?.has(issue.check));
|
|
208
|
+
return { issues: filtered, dropped: issues.length - filtered.length };
|
|
209
|
+
}
|
|
163
210
|
/**
|
|
164
211
|
* Runs the lint command to check engine changes against patch quality rules.
|
|
165
212
|
* @param projectRoot - Root directory of the project
|
|
@@ -217,7 +264,7 @@ export async function lintCommand(projectRoot, files, options = {}) {
|
|
|
217
264
|
if (await pathExists(paths.patches)) {
|
|
218
265
|
ctx = await buildPatchQueueContext(paths.patches);
|
|
219
266
|
}
|
|
220
|
-
|
|
267
|
+
let issues = [
|
|
221
268
|
...(await lintExportedPatch(paths.engine, filesAffected, diff, config, ctx)),
|
|
222
269
|
];
|
|
223
270
|
// Cross-patch rules operate over the whole queue, so run them whenever a
|
|
@@ -226,6 +273,19 @@ export async function lintCommand(projectRoot, files, options = {}) {
|
|
|
226
273
|
if (ctx) {
|
|
227
274
|
issues.push(...lintPatchQueue(ctx));
|
|
228
275
|
}
|
|
276
|
+
// Honor per-patch `lintIgnore` in aggregate mode by attributing each
|
|
277
|
+
// issue's file to its owning patches via the manifest's
|
|
278
|
+
// `filesAffected`. Per-patch mode threads `lintIgnore` directly into
|
|
279
|
+
// `lintExportedPatch`; aggregate mode previously had no patch-level
|
|
280
|
+
// scope to consult, so a check an operator had explicitly waived in
|
|
281
|
+
// `patches.json` re-surfaced on every `--since` run (CI default).
|
|
282
|
+
if (ctx) {
|
|
283
|
+
const result = applyAggregateLintIgnoreSuppression(issues, ctx);
|
|
284
|
+
issues = result.issues;
|
|
285
|
+
if (result.dropped > 0) {
|
|
286
|
+
info(`Suppressed ${result.dropped} issue(s) via per-patch lintIgnore (aggregate mode).`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
229
289
|
// When a queue manifest exists AND files were NOT scoped explicitly, the
|
|
230
290
|
// "diff" we just linted is every applied patch summed together. Patch-
|
|
231
291
|
// size rules (`large-patch-lines`, `large-patch-files`) then fire against
|
|
@@ -21,6 +21,7 @@ import { registerSetup } from './setup.js';
|
|
|
21
21
|
import { registerStatus } from './status.js';
|
|
22
22
|
import { registerTest } from './test.js';
|
|
23
23
|
import { registerToken } from './token.js';
|
|
24
|
+
import { registerTypecheck } from './typecheck.js';
|
|
24
25
|
import { registerVerify } from './verify.js';
|
|
25
26
|
import { registerWatch } from './watch.js';
|
|
26
27
|
import { registerWire } from './wire.js';
|
|
@@ -53,6 +54,7 @@ export const COMMAND_MANIFEST = [
|
|
|
53
54
|
{ name: 'wire', group: 'workflow', register: registerWire },
|
|
54
55
|
{ name: 'token', group: 'components', register: registerToken },
|
|
55
56
|
{ name: 'lint', group: 'diagnostics', register: registerLint },
|
|
57
|
+
{ name: 'typecheck', group: 'diagnostics', register: registerTypecheck },
|
|
56
58
|
{ name: 'verify', group: 'diagnostics', register: registerVerify },
|
|
57
59
|
{ name: 'furnace', group: 'components', register: registerFurnace },
|
|
58
60
|
];
|
|
@@ -57,7 +57,7 @@ export async function packageCommand(projectRoot, options) {
|
|
|
57
57
|
// only, so a targeted hint translator could not see the failure text.
|
|
58
58
|
// The captured stderr is fed through `explainMachError` below so
|
|
59
59
|
// recognised failure modes (notably the `packager.py` NoneType trip
|
|
60
|
-
// the evaluator hit on `
|
|
60
|
+
// the evaluator hit on `mybrowser/`) get an actionable hint prepended
|
|
61
61
|
// to the raw mach output the operator already saw.
|
|
62
62
|
result = await machPackageCapture(paths.engine);
|
|
63
63
|
}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `fireforge patch <verb>` parent command. Groups single-patch
|
|
3
|
-
* mutations (`compact`, `delete`, `lint-ignore`, `
|
|
4
|
-
* they do not clutter the top-level command list.
|
|
5
|
-
* like `lint`, `export`, `verify`, and `status` stay
|
|
3
|
+
* mutations (`compact`, `delete`, `lint-ignore`, `rename`, `reorder`,
|
|
4
|
+
* `tier`) so they do not clutter the top-level command list.
|
|
5
|
+
* Queue-level verbs like `lint`, `export`, `verify`, and `status` stay
|
|
6
|
+
* flat.
|
|
6
7
|
*/
|
|
7
8
|
import { Command } from 'commander';
|
|
8
9
|
import type { CommandContext } from '../../types/cli.js';
|
|
9
10
|
export { patchCompactCommand } from './compact.js';
|
|
10
11
|
export { patchDeleteCommand } from './delete.js';
|
|
11
12
|
export { patchLintIgnoreCommand } from './lint-ignore.js';
|
|
13
|
+
export { patchRenameCommand } from './rename.js';
|
|
12
14
|
export { patchReorderCommand } from './reorder.js';
|
|
13
15
|
export { patchTierCommand } from './tier.js';
|
|
14
16
|
/**
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
2
|
/**
|
|
3
3
|
* `fireforge patch <verb>` parent command. Groups single-patch
|
|
4
|
-
* mutations (`compact`, `delete`, `lint-ignore`, `
|
|
5
|
-
* they do not clutter the top-level command list.
|
|
6
|
-
* like `lint`, `export`, `verify`, and `status` stay
|
|
4
|
+
* mutations (`compact`, `delete`, `lint-ignore`, `rename`, `reorder`,
|
|
5
|
+
* `tier`) so they do not clutter the top-level command list.
|
|
6
|
+
* Queue-level verbs like `lint`, `export`, `verify`, and `status` stay
|
|
7
|
+
* flat.
|
|
7
8
|
*/
|
|
8
9
|
import { registerPatchCompact } from './compact.js';
|
|
9
10
|
import { registerPatchDelete } from './delete.js';
|
|
10
11
|
import { registerPatchLintIgnore } from './lint-ignore.js';
|
|
12
|
+
import { registerPatchRename } from './rename.js';
|
|
11
13
|
import { registerPatchReorder } from './reorder.js';
|
|
12
14
|
import { registerPatchTier } from './tier.js';
|
|
13
15
|
export { patchCompactCommand } from './compact.js';
|
|
14
16
|
export { patchDeleteCommand } from './delete.js';
|
|
15
17
|
export { patchLintIgnoreCommand } from './lint-ignore.js';
|
|
18
|
+
export { patchRenameCommand } from './rename.js';
|
|
16
19
|
export { patchReorderCommand } from './reorder.js';
|
|
17
20
|
export { patchTierCommand } from './tier.js';
|
|
18
21
|
/**
|
|
@@ -24,7 +27,7 @@ export { patchTierCommand } from './tier.js';
|
|
|
24
27
|
export function registerPatch(program, context) {
|
|
25
28
|
const patch = program
|
|
26
29
|
.command('patch')
|
|
27
|
-
.description('Manage individual patches in the queue (compact, delete, lint-ignore, reorder, tier)')
|
|
30
|
+
.description('Manage individual patches in the queue (compact, delete, lint-ignore, rename, reorder, tier)')
|
|
28
31
|
// Match `fireforge furnace`'s no-args contract: print the group's help and
|
|
29
32
|
// exit 0. Without this default action, commander routes `fireforge patch`
|
|
30
33
|
// (no subcommand) through its own help-then-exit-1 path, so scripts that
|
|
@@ -37,6 +40,7 @@ export function registerPatch(program, context) {
|
|
|
37
40
|
registerPatchCompact(patch, context);
|
|
38
41
|
registerPatchDelete(patch, context);
|
|
39
42
|
registerPatchLintIgnore(patch, context);
|
|
43
|
+
registerPatchRename(patch, context);
|
|
40
44
|
registerPatchReorder(patch, context);
|
|
41
45
|
registerPatchTier(patch, context);
|
|
42
46
|
}
|
|
@@ -21,6 +21,13 @@
|
|
|
21
21
|
import { Command } from 'commander';
|
|
22
22
|
import type { CommandContext } from '../../types/cli.js';
|
|
23
23
|
import type { PatchLintIgnoreOptions } from '../../types/commands/index.js';
|
|
24
|
+
type LintIgnoreMode = 'add' | 'remove' | 'clear';
|
|
25
|
+
/**
|
|
26
|
+
* Renders a one-line summary of the planned change for use in
|
|
27
|
+
* `info()` / dry-run / history args. Exported for unit-testing the
|
|
28
|
+
* message format directly without mocking the logger transport.
|
|
29
|
+
*/
|
|
30
|
+
export declare function describeChange(before: ReadonlyArray<string>, after: ReadonlyArray<string>, mode: LintIgnoreMode, values: ReadonlyArray<string>): string;
|
|
24
31
|
/**
|
|
25
32
|
* Runs the `patch lint-ignore` command: reads the patch's existing
|
|
26
33
|
* `lintIgnore`, applies the requested mode, and writes the manifest.
|
|
@@ -37,3 +44,4 @@ export declare function patchLintIgnoreCommand(projectRoot: string, identifier:
|
|
|
37
44
|
* @param context - Shared CLI registration context
|
|
38
45
|
*/
|
|
39
46
|
export declare function registerPatchLintIgnore(parent: Command, context: CommandContext): void;
|
|
47
|
+
export {};
|
|
@@ -53,9 +53,10 @@ function applyMode(existing, mode, values) {
|
|
|
53
53
|
}
|
|
54
54
|
/**
|
|
55
55
|
* Renders a one-line summary of the planned change for use in
|
|
56
|
-
* `info()` / dry-run / history args.
|
|
56
|
+
* `info()` / dry-run / history args. Exported for unit-testing the
|
|
57
|
+
* message format directly without mocking the logger transport.
|
|
57
58
|
*/
|
|
58
|
-
function describeChange(before, after, mode, values) {
|
|
59
|
+
export function describeChange(before, after, mode, values) {
|
|
59
60
|
const beforeSet = new Set(before);
|
|
60
61
|
const afterSet = new Set(after);
|
|
61
62
|
if (mode === 'clear') {
|
|
@@ -63,17 +64,20 @@ function describeChange(before, after, mode, values) {
|
|
|
63
64
|
? 'lintIgnore was already empty — no change'
|
|
64
65
|
: `lintIgnore cleared (was ${before.join(', ')})`;
|
|
65
66
|
}
|
|
67
|
+
const currentLabel = before.length > 0 ? `[${before.join(', ')}]` : '(empty)';
|
|
66
68
|
if (mode === 'add') {
|
|
67
69
|
const added = values.filter((v) => !beforeSet.has(v));
|
|
68
70
|
if (added.length === 0) {
|
|
69
|
-
|
|
71
|
+
// Surface the existing list so a no-op `--add` does not require a
|
|
72
|
+
// follow-up `patches.json` read to confirm what was already present.
|
|
73
|
+
return `lintIgnore unchanged (current: ${currentLabel}; all requested IDs already present)`;
|
|
70
74
|
}
|
|
71
75
|
return `lintIgnore += ${added.join(', ')} → ${[...afterSet].join(', ') || '(empty)'}`;
|
|
72
76
|
}
|
|
73
77
|
// mode === 'remove'
|
|
74
78
|
const removed = values.filter((v) => beforeSet.has(v));
|
|
75
79
|
if (removed.length === 0) {
|
|
76
|
-
return
|
|
80
|
+
return `lintIgnore unchanged (current: ${currentLabel}; none of the requested IDs were present)`;
|
|
77
81
|
}
|
|
78
82
|
return `lintIgnore −= ${removed.join(', ')} → ${[...afterSet].join(', ') || '(empty)'}`;
|
|
79
83
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `fireforge patch rename <name>` — relabels a patch's filename, manifest
|
|
3
|
+
* `name`, and (optionally) `description` without rewriting the `.patch`
|
|
4
|
+
* file body.
|
|
5
|
+
*
|
|
6
|
+
* Companion to `re-export --files <subset>`. Re-export shrinks the body
|
|
7
|
+
* + `filesAffected`, but leaves the patch's identity describing the
|
|
8
|
+
* pre-shrink scope. Before this verb existed, the only workaround for
|
|
9
|
+
* that drift was `delete` + re-export, which briefly removed the patch
|
|
10
|
+
* from the queue (any forward-import dependent would refuse the
|
|
11
|
+
* re-export until the deleted patch's siblings were rewritten).
|
|
12
|
+
*
|
|
13
|
+
* The filename rename and the manifest mutation happen under the patch
|
|
14
|
+
* directory lock so concurrent exports cannot allocate the new
|
|
15
|
+
* filename, and a filesystem rename failure rolls back before the
|
|
16
|
+
* manifest is touched.
|
|
17
|
+
*/
|
|
18
|
+
import { Command } from 'commander';
|
|
19
|
+
import type { CommandContext } from '../../types/cli.js';
|
|
20
|
+
import type { PatchRenameOptions } from '../../types/commands/index.js';
|
|
21
|
+
/**
|
|
22
|
+
* Runs the `patch rename` command: relabels filename + manifest entry
|
|
23
|
+
* for a single patch atomically.
|
|
24
|
+
*
|
|
25
|
+
* @param projectRoot - Project root directory
|
|
26
|
+
* @param identifier - Patch filename, ordinal, or manifest `name`
|
|
27
|
+
* @param options - Command options (`--to <new-name>` is required)
|
|
28
|
+
*/
|
|
29
|
+
export declare function patchRenameCommand(projectRoot: string, identifier: string, options?: PatchRenameOptions): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Registers the `patch rename` subcommand on the `patch` parent.
|
|
32
|
+
*
|
|
33
|
+
* @param parent - Parent Commander command
|
|
34
|
+
* @param context - Shared CLI registration context
|
|
35
|
+
*/
|
|
36
|
+
export declare function registerPatchRename(parent: Command, context: CommandContext): void;
|