@hominis/fireforge 0.12.0 → 0.13.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 +17 -0
- package/README.md +32 -32
- package/dist/src/commands/bootstrap-checks.d.ts +16 -0
- package/dist/src/commands/bootstrap-checks.js +66 -0
- package/dist/src/commands/bootstrap.js +27 -9
- package/dist/src/commands/doctor.d.ts +8 -0
- package/dist/src/commands/doctor.js +7 -1
- package/dist/src/commands/export-flow.js +3 -11
- package/dist/src/commands/export-shared.js +4 -0
- package/dist/src/commands/lint.js +4 -0
- package/dist/src/commands/patch/delete.js +1 -15
- package/dist/src/commands/patch/reorder.js +1 -9
- package/dist/src/commands/re-export.js +1 -17
- package/dist/src/commands/verify.js +2 -2
- package/dist/src/core/patch-lint.d.ts +7 -1
- package/dist/src/core/patch-lint.js +86 -17
- package/dist/src/core/patch-manifest-resolve.d.ts +5 -0
- package/dist/src/core/patch-manifest-resolve.js +12 -0
- package/dist/src/core/patch-manifest.d.ts +1 -0
- package/dist/src/core/patch-manifest.js +1 -0
- package/dist/src/types/commands/patches.d.ts +2 -2
- package/dist/src/types/config.d.ts +2 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.13.0
|
|
4
|
+
|
|
5
|
+
### Setup
|
|
6
|
+
|
|
7
|
+
- **`fireforge bootstrap` now runs targeted post-bootstrap checks** instead of pattern-matching output text. When `mach bootstrap` exits successfully but sub-downloads fail (e.g. HTTP 403 from Apple's CDN), FireForge validates actual system state — checking whether a macOS SDK is available via Xcode — and reports actionable results using the same `✓`/`!`/`✗` severity rendering as `fireforge doctor`. Non-critical issues (SDK download failed but Xcode provides one) are reported as warnings rather than alarming "did not complete successfully" errors.
|
|
8
|
+
|
|
9
|
+
### Lint fixes
|
|
10
|
+
|
|
11
|
+
- **`file-too-large` now uses tiered severity thresholds.** The old single 650-line warning is replaced with a three-tier system (notice / warning / error) that distinguishes general files from test files. General files: 500–749 lines notice, 750–899 warning, 900+ error. Test files (paths containing `/test/`, or filenames matching `browser_*.js`, `test_*.js`, `xpcshell_*.js`): 1200–1399 notice, 1400–1599 warning, 1600+ error. Messages include the applicable thresholds so users know where they stand. The new `notice` severity is displayed but does not count toward warning or error totals and does not block export.
|
|
12
|
+
- **`observer-topic-naming` no longer matches across newlines.** The regex that extracts topic strings from `notifyObservers`/`addObserver`/`removeObserver` calls now anchors to a single line, preventing false positives when the call spans multiple lines and an unrelated string literal appears later.
|
|
13
|
+
- **`raw-color-value` now supports a file allowlist and inline suppression.** New `patchLint.rawColorAllowlist` config array in `fireforge.json` exempts file paths (exact or basename match) from the raw-color check — intended for design token files that must contain raw color values. Individual declarations can also be suppressed with an inline `/* fireforge-ignore: raw-color-value */` comment.
|
|
14
|
+
- **`large-patch-lines` now uses tiered severity thresholds.** The old single >300-line warning is replaced with a three-tier system matching the `file-too-large` pattern. General patches: 800+ lines notice, 1500+ warning, 3000+ error. Test-only patches (all files match test patterns): 1500+ notice, 3000+ warning, 6000+ error. The previous threshold was too restrictive relative to file LOC limits — creating a single new file at the `file-too-large` notice tier (500 LOC) already exceeded it. Messages now include the applicable soft and hard limits.
|
|
15
|
+
|
|
16
|
+
### General Improvements
|
|
17
|
+
|
|
18
|
+
- **Minor Refactor**
|
|
19
|
+
|
|
3
20
|
## 0.12.0
|
|
4
21
|
|
|
5
22
|
### JSDoc validation (breaking)
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
**Build and maintain your own Firefox-based browser with a patch-first workflow**
|
|
10
10
|
|
|
11
|
-
FireForge gives you a toolkit for forking Firefox: download a specific ESR release, manage your customisations as a series of patches, survive version upgrades with semi-automated rebase, wire custom code into Mozilla's startup paths
|
|
11
|
+
FireForge gives you a toolkit for forking Firefox: download a specific ESR release, manage your customisations as a series of patches, survive version upgrades with semi-automated rebase, wire custom code into Mozilla's startup paths and build the result. It also ships **Furnace**, a component system for creating and overriding Firefox custom elements under `toolkit/content/widgets`.
|
|
12
12
|
|
|
13
13
|
Inspired by [fern.js](https://github.com/ghostery/user-agent-desktop) and [Melon](https://github.com/dothq/melon).
|
|
14
14
|
|
|
@@ -18,7 +18,7 @@ Inspired by [fern.js](https://github.com/ghostery/user-agent-desktop) and [Melon
|
|
|
18
18
|
|
|
19
19
|
- **Semi-automated ESR rebase** `fireforge rebase` replays your patch stack onto new Firefox source with escalating fuzz matching. When a patch fails, you fix it manually and `--continue`. The full stack gets re-exported with updated version stamps.
|
|
20
20
|
|
|
21
|
-
- **Wiring and registration** `fireforge wire` and `fireforge register` inject your code into Mozilla's startup paths, build manifests
|
|
21
|
+
- **Wiring and registration** `fireforge wire` and `fireforge register` inject your code into Mozilla's startup paths, build manifests and JAR files with a single command. The injection is AST-based (via Acorn), so it survives formatting changes applied between versions.
|
|
22
22
|
|
|
23
23
|
- **Furnace component system** Override existing Firefox custom elements or create new ones under `toolkit/content/widgets` (CSS-only restyles, full behavioural forks, or entirely new widgets).
|
|
24
24
|
|
|
@@ -26,7 +26,7 @@ Inspired by [fern.js](https://github.com/ghostery/user-agent-desktop) and [Melon
|
|
|
26
26
|
|
|
27
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.
|
|
28
28
|
|
|
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
|
|
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
|
|
|
31
31
|
## Quick Start
|
|
32
32
|
|
|
@@ -52,7 +52,7 @@ npx fireforge build # build the browser
|
|
|
52
52
|
npx fireforge run # launch it
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
Your project now has `fireforge.json`, an `engine/` directory with Firefox source
|
|
55
|
+
Your project now has `fireforge.json`, an `engine/` directory with Firefox source and a `patches/` directory with an empty `patches.json` manifest ready for your first customisation.
|
|
56
56
|
|
|
57
57
|
### Workflow Overview
|
|
58
58
|
|
|
@@ -71,14 +71,14 @@ npx fireforge export browser/base/content/browser.js \
|
|
|
71
71
|
npx fireforge reset --yes
|
|
72
72
|
npx fireforge import # --dry-run to preview without applying
|
|
73
73
|
|
|
74
|
-
# 5. When Firefox releases a new ESR, update fireforge.json, re-download
|
|
74
|
+
# 5. When Firefox releases a new ESR, update fireforge.json, re-download and rebase
|
|
75
75
|
npx fireforge download --force
|
|
76
76
|
npx fireforge rebase
|
|
77
77
|
```
|
|
78
78
|
|
|
79
79
|
## Patch Workflow
|
|
80
80
|
|
|
81
|
-
Patches live in `patches/`, applied by numeric filename prefix
|
|
81
|
+
Patches live in `patches/`, applied by numeric filename prefix and tracked in `patches/patches.json`:
|
|
82
82
|
|
|
83
83
|
```
|
|
84
84
|
patches/
|
|
@@ -186,30 +186,30 @@ If the manifest drifts after an interrupted export or manual edits, `fireforge i
|
|
|
186
186
|
<details>
|
|
187
187
|
<summary>Patch lint checks</summary>
|
|
188
188
|
|
|
189
|
-
`fireforge lint` runs automatically during export, export-all
|
|
190
|
-
|
|
191
|
-
| Check | Scope
|
|
192
|
-
| ------------------------------ |
|
|
193
|
-
| `missing-license-header` | New files (JS/CSS/FTL)
|
|
194
|
-
| `relative-import` | JS/MJS files
|
|
195
|
-
| `token-prefix-violation` | CSS files (with furnace)
|
|
196
|
-
| `raw-color-value` | Introduced CSS color values
|
|
197
|
-
| `duplicate-new-file-creation` | Same path created by multiple patches
|
|
198
|
-
| `forward-import` | Patch imports from a later-patch file
|
|
199
|
-
| `missing-jsdoc` | Exports in patch-owned `.sys.mjs`
|
|
200
|
-
| `jsdoc-param-mismatch` | Exports in patch-owned `.sys.mjs`
|
|
201
|
-
| `jsdoc-missing-returns` | Exports in patch-owned `.sys.mjs`
|
|
202
|
-
| `checkjs-type-error` | Patch-owned `.sys.mjs` (opt-in)
|
|
203
|
-
| `missing-modification-comment` | Modified upstream JS/MJS
|
|
204
|
-
| `modified-file-missing-header` | Modified upstream files (JS/CSS/FTL)
|
|
205
|
-
| `file-too-large` | New files
|
|
206
|
-
| `observer-topic-naming` | Observer topics with binaryName
|
|
207
|
-
| `large-patch-files` | Patches affecting >5 files
|
|
208
|
-
| `large-patch-lines` |
|
|
189
|
+
`fireforge lint` runs automatically during export, export-all and re-export. Use `--skip-lint` to downgrade errors to warnings. Errors block the export; warnings are printed but do not block.
|
|
190
|
+
|
|
191
|
+
| Check | Scope | Severity |
|
|
192
|
+
| ------------------------------ | --------------------------------------------------------------------- | ------------------------ |
|
|
193
|
+
| `missing-license-header` | New files (JS/CSS/FTL) | error |
|
|
194
|
+
| `relative-import` | JS/MJS files | error |
|
|
195
|
+
| `token-prefix-violation` | CSS files (with furnace) | error |
|
|
196
|
+
| `raw-color-value` | Introduced CSS color values | error |
|
|
197
|
+
| `duplicate-new-file-creation` | Same path created by multiple patches | error |
|
|
198
|
+
| `forward-import` | Patch imports from a later-patch file | error |
|
|
199
|
+
| `missing-jsdoc` | Exports in patch-owned `.sys.mjs` | error |
|
|
200
|
+
| `jsdoc-param-mismatch` | Exports in patch-owned `.sys.mjs` | error |
|
|
201
|
+
| `jsdoc-missing-returns` | Exports in patch-owned `.sys.mjs` | error |
|
|
202
|
+
| `checkjs-type-error` | Patch-owned `.sys.mjs` (opt-in) | error |
|
|
203
|
+
| `missing-modification-comment` | Modified upstream JS/MJS | warning |
|
|
204
|
+
| `modified-file-missing-header` | Modified upstream files (JS/CSS/FTL) | warning |
|
|
205
|
+
| `file-too-large` | New files (tiered: 500/750/900 general, 1200/1400/1600 test) | notice / warning / error |
|
|
206
|
+
| `observer-topic-naming` | Observer topics with binaryName | warning |
|
|
207
|
+
| `large-patch-files` | Patches affecting >5 files | warning |
|
|
208
|
+
| `large-patch-lines` | Patch line count (tiered: 800/1500/3000 general, 1500/3000/6000 test) | notice / warning / error |
|
|
209
209
|
|
|
210
210
|
**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.
|
|
211
211
|
|
|
212
|
-
**Optional `checkJs` pass.** Enable TypeScript-
|
|
212
|
+
**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.).
|
|
213
213
|
|
|
214
214
|
The two cross-patch rules (`duplicate-new-file-creation` and `forward-import`) run over the whole patch queue rather than a single diff, catching ordering issues that only surface during `import`. Forward-import detection compares leaf filenames, so a false positive is theoretically possible when two patches create files with the same basename in different directories. Suppress with an inline `// fireforge-ignore: forward-import` comment on or above the import line. This is currently the only lint rule that supports inline suppression.
|
|
215
215
|
|
|
@@ -217,7 +217,7 @@ The two cross-patch rules (`duplicate-new-file-creation` and `forward-import`) r
|
|
|
217
217
|
|
|
218
218
|
### Repairing a broken patch queue
|
|
219
219
|
|
|
220
|
-
When a patch queue drifts
|
|
220
|
+
When a patch queue drifts, e.g. due to overlapping new-file creations, forward imports, manifest desync, etc. start with diagnosing the root cause:
|
|
221
221
|
|
|
222
222
|
```bash
|
|
223
223
|
fireforge verify # fsck: manifest + cross-patch lint
|
|
@@ -237,7 +237,7 @@ Then fix with the appropriate primitive:
|
|
|
237
237
|
| Manifest references a missing patch file | `fireforge doctor --repair-patches-manifest` |
|
|
238
238
|
| Unmanaged changes you want to discard | `fireforge discard <file>` or `fireforge reset` |
|
|
239
239
|
|
|
240
|
-
Every destructive command defaults to an interactive confirmation with a change summary. `--dry-run` previews without writing; `--yes` skips the prompt for CI; `--force-unsafe` bypasses structural refusals when you have context the linter cannot see. Do not hand-edit `patches.json`
|
|
240
|
+
Every destructive command defaults to an interactive confirmation with a change summary. `--dry-run` previews without writing; `--yes` skips the prompt for CI; `--force-unsafe` bypasses structural refusals when you have context the linter cannot see. Do not hand-edit `patches.json` as the file is owned by FireForge.
|
|
241
241
|
|
|
242
242
|
## Wiring Custom Code
|
|
243
243
|
|
|
@@ -278,7 +278,7 @@ fireforge register browser/modules/mybrowser/MyStore.sys.mjs
|
|
|
278
278
|
|
|
279
279
|
## Furnace (UI Component System)
|
|
280
280
|
|
|
281
|
-
Furnace manages Firefox custom elements (`MozLitElement`) under `toolkit/content/widgets`. You can override existing components or create new ones. Changes feed into the same patch workflow as everything else
|
|
281
|
+
Furnace manages Firefox custom elements (`MozLitElement`) under `toolkit/content/widgets`. You can override existing components or create new ones. Changes feed into the same patch workflow as everything else, Furnace is not a separate persistence layer.
|
|
282
282
|
|
|
283
283
|
There are three component types:
|
|
284
284
|
|
|
@@ -297,11 +297,11 @@ fireforge furnace status # workspace vs engine drift
|
|
|
297
297
|
fireforge furnace diff moz-button # unified diff against baseline
|
|
298
298
|
```
|
|
299
299
|
|
|
300
|
-
`furnace deploy` validates components before applying
|
|
300
|
+
`furnace deploy` validates components before applying. As always, errors block, warnings are advisory. `fireforge build` and `fireforge test --build` run apply automatically. Use `fireforge doctor --repair-furnace` if the engine gets out of sync.
|
|
301
301
|
|
|
302
302
|
## Additional Commands
|
|
303
303
|
|
|
304
|
-
The commands below cover project configuration, patch queue management, build packaging
|
|
304
|
+
The commands below cover project configuration, patch queue management, build packaging and development utilities. Run `fireforge <command> --help` for full option details.
|
|
305
305
|
|
|
306
306
|
### Configuration
|
|
307
307
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { DoctorCheck } from '../types/commands/index.js';
|
|
2
|
+
/** Tags representing distinct issues detected in bootstrap output. */
|
|
3
|
+
export type BootstrapIssue = 'sdk-fetch-403' | 'python-traceback' | 'missing-origin-remote';
|
|
4
|
+
/**
|
|
5
|
+
* Scans bootstrap output for known failure patterns and returns structured
|
|
6
|
+
* issue tags. A Python traceback paired with an HTTP 403 is collapsed into
|
|
7
|
+
* a single `sdk-fetch-403` tag since the traceback is just the stack trace
|
|
8
|
+
* from the HTTP error.
|
|
9
|
+
*/
|
|
10
|
+
export declare function detectBootstrapIssues(output: string): BootstrapIssue[];
|
|
11
|
+
/**
|
|
12
|
+
* Runs targeted post-bootstrap checks based on the detected issues.
|
|
13
|
+
* Returns doctor-compatible check results so the caller can render them
|
|
14
|
+
* with the standard `reportDoctorResults` display.
|
|
15
|
+
*/
|
|
16
|
+
export declare function runPostBootstrapChecks(issues: BootstrapIssue[]): Promise<DoctorCheck[]>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
import { execFile } from 'node:child_process';
|
|
3
|
+
import { failure, warning } from './doctor.js';
|
|
4
|
+
/**
|
|
5
|
+
* Scans bootstrap output for known failure patterns and returns structured
|
|
6
|
+
* issue tags. A Python traceback paired with an HTTP 403 is collapsed into
|
|
7
|
+
* a single `sdk-fetch-403` tag since the traceback is just the stack trace
|
|
8
|
+
* from the HTTP error.
|
|
9
|
+
*/
|
|
10
|
+
export function detectBootstrapIssues(output) {
|
|
11
|
+
const normalized = output.replace(/\r\n/g, '\n');
|
|
12
|
+
const issues = [];
|
|
13
|
+
const hasTraceback = /traceback \(most recent call last\):/i.test(normalized);
|
|
14
|
+
const has403 = /\bhttp(?:\s+error)?\s*403\b/i.test(normalized) || /\b403\b.*forbidden/i.test(normalized);
|
|
15
|
+
if (has403) {
|
|
16
|
+
// The traceback is just the stack trace from the HTTP error — report once.
|
|
17
|
+
issues.push('sdk-fetch-403');
|
|
18
|
+
}
|
|
19
|
+
else if (hasTraceback) {
|
|
20
|
+
issues.push('python-traceback');
|
|
21
|
+
}
|
|
22
|
+
if (/no such remote ['"]origin['"]/i.test(normalized) ||
|
|
23
|
+
/remote ['"]origin['"] does not exist/i.test(normalized) ||
|
|
24
|
+
/missing git remote ['"]origin['"]/i.test(normalized)) {
|
|
25
|
+
issues.push('missing-origin-remote');
|
|
26
|
+
}
|
|
27
|
+
return issues;
|
|
28
|
+
}
|
|
29
|
+
/** Checks whether `xcrun --show-sdk-path` returns a valid macOS SDK path. */
|
|
30
|
+
async function hasMacOsSdk() {
|
|
31
|
+
return new Promise((resolve) => {
|
|
32
|
+
execFile('xcrun', ['--show-sdk-path'], { timeout: 10_000 }, (err, stdout) => {
|
|
33
|
+
resolve(!err && stdout.trim().length > 0);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Runs targeted post-bootstrap checks based on the detected issues.
|
|
39
|
+
* Returns doctor-compatible check results so the caller can render them
|
|
40
|
+
* with the standard `reportDoctorResults` display.
|
|
41
|
+
*/
|
|
42
|
+
export async function runPostBootstrapChecks(issues) {
|
|
43
|
+
const checks = [];
|
|
44
|
+
for (const issue of issues) {
|
|
45
|
+
switch (issue) {
|
|
46
|
+
case 'sdk-fetch-403': {
|
|
47
|
+
const sdkAvailable = await hasMacOsSdk();
|
|
48
|
+
if (sdkAvailable) {
|
|
49
|
+
checks.push(warning('macOS SDK download', "SDK download from Apple's CDN failed (HTTP 403), but a macOS SDK was found via Xcode. This is safe to ignore."));
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
checks.push(failure('macOS SDK', 'SDK download failed and no macOS SDK found on your system.', 'Install Xcode Command Line Tools with "xcode-select --install"'));
|
|
53
|
+
}
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
case 'python-traceback':
|
|
57
|
+
checks.push(warning('Python traceback', 'Bootstrap emitted a Python traceback. This may indicate a non-critical issue.', 'Review the bootstrap output above for details.'));
|
|
58
|
+
break;
|
|
59
|
+
case 'missing-origin-remote':
|
|
60
|
+
checks.push(failure('Git remote', 'Bootstrap expected an "origin" git remote in the Firefox source checkout.', 'Run "git remote add origin <url>" in the engine directory.'));
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return checks;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=bootstrap-checks.js.map
|
|
@@ -4,7 +4,13 @@ import { bootstrapWithOutput } from '../core/mach.js';
|
|
|
4
4
|
import { GeneralError } from '../errors/base.js';
|
|
5
5
|
import { BootstrapError } from '../errors/build.js';
|
|
6
6
|
import { pathExists } from '../utils/fs.js';
|
|
7
|
-
import { error, info, intro, outro } from '../utils/logger.js';
|
|
7
|
+
import { error, info, intro, outro, warn } from '../utils/logger.js';
|
|
8
|
+
import { detectBootstrapIssues, runPostBootstrapChecks } from './bootstrap-checks.js';
|
|
9
|
+
import { reportDoctorResults } from './doctor.js';
|
|
10
|
+
/**
|
|
11
|
+
* Builds a human-readable failure message for hard failures (non-zero exit).
|
|
12
|
+
* Used only when mach bootstrap itself reports failure.
|
|
13
|
+
*/
|
|
8
14
|
function buildBootstrapFailureMessage(output) {
|
|
9
15
|
const normalized = output.replace(/\r\n/g, '\n');
|
|
10
16
|
const issues = [];
|
|
@@ -52,15 +58,27 @@ export async function bootstrapCommand(projectRoot) {
|
|
|
52
58
|
throw new BootstrapError();
|
|
53
59
|
}
|
|
54
60
|
// mach bootstrap may exit 0 even when sub-downloads fail (e.g. HTTP 403).
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
|
|
61
|
+
// Instead of guessing from output text, detect what went wrong and run
|
|
62
|
+
// targeted checks to determine whether the issues are actually actionable.
|
|
63
|
+
const output = `${result.stdout}\n${result.stderr}`;
|
|
64
|
+
const issues = detectBootstrapIssues(output);
|
|
65
|
+
if (issues.length > 0) {
|
|
66
|
+
const checks = await runPostBootstrapChecks(issues);
|
|
67
|
+
const hasErrors = checks.some((c) => c.severity === 'error' || (!c.passed && !c.warning));
|
|
58
68
|
info('');
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
69
|
+
if (hasErrors) {
|
|
70
|
+
warn('Bootstrap completed with issues:');
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
warn('Bootstrap completed with warnings:');
|
|
74
|
+
}
|
|
75
|
+
reportDoctorResults(checks);
|
|
76
|
+
if (hasErrors) {
|
|
77
|
+
outro('Build dependencies installed with errors');
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
outro('Build dependencies installed with warnings');
|
|
81
|
+
}
|
|
64
82
|
return;
|
|
65
83
|
}
|
|
66
84
|
outro('Build dependencies installed successfully!');
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
+
import { ExitCode } from '../errors/codes.js';
|
|
2
3
|
import type { CommandContext } from '../types/cli.js';
|
|
3
4
|
import type { DoctorCheck, DoctorOptions } from '../types/commands/index.js';
|
|
4
5
|
import type { FireForgeConfig, FireForgeState, ProjectPaths } from '../types/config.js';
|
|
@@ -116,6 +117,13 @@ export declare function failure(name: string, message: string, fix?: string): Do
|
|
|
116
117
|
* think through the consequences.
|
|
117
118
|
*/
|
|
118
119
|
export declare const DOCTOR_CHECK_ORDER: readonly string[];
|
|
120
|
+
/**
|
|
121
|
+
* Renders a list of doctor checks to the console and returns the
|
|
122
|
+
* appropriate exit code (success when no errors, general error otherwise).
|
|
123
|
+
* @param checks - The check results to display
|
|
124
|
+
* @returns The exit code reflecting the overall result
|
|
125
|
+
*/
|
|
126
|
+
export declare function reportDoctorResults(checks: DoctorCheck[]): ExitCode;
|
|
119
127
|
/**
|
|
120
128
|
* Result of the doctor command, carrying the exit code so the caller
|
|
121
129
|
* (or test) can inspect it without relying on process.exitCode.
|
|
@@ -342,7 +342,13 @@ validateCheckDependencies(DOCTOR_CHECKS);
|
|
|
342
342
|
* think through the consequences.
|
|
343
343
|
*/
|
|
344
344
|
export const DOCTOR_CHECK_ORDER = DOCTOR_CHECKS.map((check) => check.name);
|
|
345
|
-
|
|
345
|
+
/**
|
|
346
|
+
* Renders a list of doctor checks to the console and returns the
|
|
347
|
+
* appropriate exit code (success when no errors, general error otherwise).
|
|
348
|
+
* @param checks - The check results to display
|
|
349
|
+
* @returns The exit code reflecting the overall result
|
|
350
|
+
*/
|
|
351
|
+
export function reportDoctorResults(checks) {
|
|
346
352
|
info('');
|
|
347
353
|
let passedCount = 0;
|
|
348
354
|
let warningCount = 0;
|
|
@@ -11,7 +11,7 @@ import { join } from 'node:path';
|
|
|
11
11
|
import { findAllPatchesForFilesWithDetails, planExport } from '../core/patch-export.js';
|
|
12
12
|
import { buildModifiedFileAdditionsFromDiff, buildPatchQueueContext, detectNewFilesInDiff, lintPatchQueue, } from '../core/patch-lint.js';
|
|
13
13
|
import { withPatchDirectoryLock } from '../core/patch-lock.js';
|
|
14
|
-
import { addPatchToManifest, loadPatchesManifest, renumberPatchesInManifest, savePatchesManifest, } from '../core/patch-manifest.js';
|
|
14
|
+
import { addPatchToManifest, loadPatchesManifest, renumberPatchesInManifest, resolvePatchIdentifier, savePatchesManifest, } from '../core/patch-manifest.js';
|
|
15
15
|
import { extractNewFileContentFromDiff } from '../core/patch-transform.js';
|
|
16
16
|
import { InvalidArgumentError } from '../errors/base.js';
|
|
17
17
|
import { toError } from '../utils/errors.js';
|
|
@@ -32,14 +32,6 @@ function buildFilenameForPlacement(category, name, order, width) {
|
|
|
32
32
|
const padded = String(order).padStart(Math.max(3, width), '0');
|
|
33
33
|
return `${padded}-${category}-${sanitizeExportName(name)}.patch`;
|
|
34
34
|
}
|
|
35
|
-
function resolvePatchByIdentifier(identifier, patches) {
|
|
36
|
-
if (/^\d+$/.test(identifier)) {
|
|
37
|
-
const order = parseInt(identifier, 10);
|
|
38
|
-
return patches.find((p) => p.order === order) ?? null;
|
|
39
|
-
}
|
|
40
|
-
const normalized = identifier.endsWith('.patch') ? identifier : `${identifier}.patch`;
|
|
41
|
-
return patches.find((p) => p.filename === normalized) ?? null;
|
|
42
|
-
}
|
|
43
35
|
function getSortedRenameEntries(renameMap) {
|
|
44
36
|
return Array.from(renameMap.entries()).sort((a, b) => a[1].newOrder - b[1].newOrder);
|
|
45
37
|
}
|
|
@@ -115,7 +107,7 @@ export async function resolvePlacementPlan(patchesDir, options, category, name)
|
|
|
115
107
|
targetOrder = options.order;
|
|
116
108
|
}
|
|
117
109
|
else if (options.before !== undefined) {
|
|
118
|
-
const anchor =
|
|
110
|
+
const anchor = resolvePatchIdentifier(options.before, existingPatches);
|
|
119
111
|
if (!anchor) {
|
|
120
112
|
throw new InvalidArgumentError(`--before anchor "${options.before}" not found.`, '--before');
|
|
121
113
|
}
|
|
@@ -126,7 +118,7 @@ export async function resolvePlacementPlan(patchesDir, options, category, name)
|
|
|
126
118
|
if (afterAnchorId === undefined) {
|
|
127
119
|
throw new InvalidArgumentError('Placement flag resolver reached --after branch with no value set.', '--after');
|
|
128
120
|
}
|
|
129
|
-
const anchor =
|
|
121
|
+
const anchor = resolvePatchIdentifier(afterAnchorId, existingPatches);
|
|
130
122
|
if (!anchor) {
|
|
131
123
|
throw new InvalidArgumentError(`--after anchor "${afterAnchorId}" not found.`, '--after');
|
|
132
124
|
}
|
|
@@ -25,6 +25,10 @@ export async function runPatchLint(engineDir, filesAffected, diffContent, config
|
|
|
25
25
|
return;
|
|
26
26
|
const errors = issues.filter((i) => i.severity === 'error');
|
|
27
27
|
const warnings = issues.filter((i) => i.severity === 'warning');
|
|
28
|
+
const notices = issues.filter((i) => i.severity === 'notice');
|
|
29
|
+
for (const issue of notices) {
|
|
30
|
+
info(`NOTICE [${issue.check}] ${issue.file}: ${issue.message}`);
|
|
31
|
+
}
|
|
28
32
|
for (const issue of warnings) {
|
|
29
33
|
warn(`[${issue.check}] ${issue.file}: ${issue.message}`);
|
|
30
34
|
}
|
|
@@ -107,6 +107,10 @@ export async function lintCommand(projectRoot, files) {
|
|
|
107
107
|
}
|
|
108
108
|
const errors = issues.filter((i) => i.severity === 'error');
|
|
109
109
|
const warnings = issues.filter((i) => i.severity === 'warning');
|
|
110
|
+
const notices = issues.filter((i) => i.severity === 'notice');
|
|
111
|
+
for (const issue of notices) {
|
|
112
|
+
info(`NOTICE [${issue.check}] ${issue.file}: ${issue.message}`);
|
|
113
|
+
}
|
|
110
114
|
for (const issue of warnings) {
|
|
111
115
|
warn(`[${issue.check}] ${issue.file}: ${issue.message}`);
|
|
112
116
|
}
|
|
@@ -12,26 +12,12 @@ import { getProjectPaths } from '../../core/config.js';
|
|
|
12
12
|
import { appendHistory, confirmDestructive } from '../../core/destructive.js';
|
|
13
13
|
import { buildPatchQueueContext, extractImportSpecifiersWithLines, findForwardImportIgnoreLines, isForwardImportableFile, } from '../../core/patch-lint.js';
|
|
14
14
|
import { withPatchDirectoryLock } from '../../core/patch-lock.js';
|
|
15
|
-
import { loadPatchesManifest, removePatchFileAndManifest } from '../../core/patch-manifest.js';
|
|
15
|
+
import { loadPatchesManifest, removePatchFileAndManifest, resolvePatchIdentifier, } from '../../core/patch-manifest.js';
|
|
16
16
|
import { GeneralError, InvalidArgumentError } from '../../errors/base.js';
|
|
17
17
|
import { toError } from '../../utils/errors.js';
|
|
18
18
|
import { pathExists } from '../../utils/fs.js';
|
|
19
19
|
import { info, intro, outro, warn } from '../../utils/logger.js';
|
|
20
20
|
import { pickDefined } from '../../utils/options.js';
|
|
21
|
-
/**
|
|
22
|
-
* Resolves `<name>` (ordinal number or filename) to a manifest entry.
|
|
23
|
-
* Mirrors re-export's `resolvePatchIdentifier` so the two resolvers behave
|
|
24
|
-
* consistently — future work can lift this into a shared helper once a
|
|
25
|
-
* third consumer appears.
|
|
26
|
-
*/
|
|
27
|
-
function resolvePatchIdentifier(identifier, patches) {
|
|
28
|
-
if (/^\d+$/.test(identifier)) {
|
|
29
|
-
const order = parseInt(identifier, 10);
|
|
30
|
-
return patches.find((p) => p.order === order) ?? null;
|
|
31
|
-
}
|
|
32
|
-
const normalized = identifier.endsWith('.patch') ? identifier : `${identifier}.patch`;
|
|
33
|
-
return patches.find((p) => p.filename === normalized) ?? null;
|
|
34
|
-
}
|
|
35
21
|
/**
|
|
36
22
|
* Runs the `patch delete` command: removes a patch file and its manifest
|
|
37
23
|
* row atomically, refusing when a later patch imports a leaf owned by the
|
|
@@ -13,21 +13,13 @@ import { getProjectPaths } from '../../core/config.js';
|
|
|
13
13
|
import { appendHistory, confirmDestructive, } from '../../core/destructive.js';
|
|
14
14
|
import { buildPatchQueueContext, lintPatchQueue, } from '../../core/patch-lint.js';
|
|
15
15
|
import { withPatchDirectoryLock } from '../../core/patch-lock.js';
|
|
16
|
-
import { loadPatchesManifest, renumberPatchesInManifest, } from '../../core/patch-manifest.js';
|
|
16
|
+
import { loadPatchesManifest, renumberPatchesInManifest, resolvePatchIdentifier, } from '../../core/patch-manifest.js';
|
|
17
17
|
import { GeneralError, InvalidArgumentError } from '../../errors/base.js';
|
|
18
18
|
import { toError } from '../../utils/errors.js';
|
|
19
19
|
import { pathExists } from '../../utils/fs.js';
|
|
20
20
|
import { info, intro, outro, warn } from '../../utils/logger.js';
|
|
21
21
|
import { pickDefined } from '../../utils/options.js';
|
|
22
22
|
import { parsePositiveIntegerFlag } from '../../utils/validation.js';
|
|
23
|
-
function resolvePatchIdentifier(identifier, patches) {
|
|
24
|
-
if (/^\d+$/.test(identifier)) {
|
|
25
|
-
const order = parseInt(identifier, 10);
|
|
26
|
-
return patches.find((p) => p.order === order) ?? null;
|
|
27
|
-
}
|
|
28
|
-
const normalized = identifier.endsWith('.patch') ? identifier : `${identifier}.patch`;
|
|
29
|
-
return patches.find((p) => p.filename === normalized) ?? null;
|
|
30
|
-
}
|
|
31
23
|
function padOrder(value, width) {
|
|
32
24
|
return String(value).padStart(width, '0');
|
|
33
25
|
}
|
|
@@ -6,7 +6,7 @@ import { isGitRepository } from '../core/git.js';
|
|
|
6
6
|
import { getDiffForFilesAgainstHead } from '../core/git-diff.js';
|
|
7
7
|
import { getModifiedFilesInDir, getUntrackedFilesInDir } from '../core/git-status.js';
|
|
8
8
|
import { updatePatch, updatePatchMetadata } from '../core/patch-export.js';
|
|
9
|
-
import { getClaimedFiles, loadPatchesManifest } from '../core/patch-manifest.js';
|
|
9
|
+
import { getClaimedFiles, loadPatchesManifest, resolvePatchIdentifier, } from '../core/patch-manifest.js';
|
|
10
10
|
import { GeneralError, InvalidArgumentError } from '../errors/base.js';
|
|
11
11
|
import { toError } from '../utils/errors.js';
|
|
12
12
|
import { pathExists } from '../utils/fs.js';
|
|
@@ -14,22 +14,6 @@ import { cancel, info, intro, isCancel, outro, spinner, success, warn } from '..
|
|
|
14
14
|
import { pickDefined } from '../utils/options.js';
|
|
15
15
|
import { runPatchLint } from './export-shared.js';
|
|
16
16
|
import { reExportFilesInPlace } from './re-export-files.js';
|
|
17
|
-
/**
|
|
18
|
-
* Resolves patch identifiers (numbers or filenames) to manifest entries.
|
|
19
|
-
* @param identifier - Patch number (e.g. "005") or filename (e.g. "005-ui-storage-modules.patch")
|
|
20
|
-
* @param patches - All patches from the manifest
|
|
21
|
-
* @returns Matching patch metadata
|
|
22
|
-
*/
|
|
23
|
-
function resolvePatchIdentifier(identifier, patches) {
|
|
24
|
-
// If all digits, match by order number
|
|
25
|
-
if (/^\d+$/.test(identifier)) {
|
|
26
|
-
const order = parseInt(identifier, 10);
|
|
27
|
-
return patches.find((p) => p.order === order) ?? null;
|
|
28
|
-
}
|
|
29
|
-
// Match by filename (with or without .patch suffix)
|
|
30
|
-
const normalized = identifier.endsWith('.patch') ? identifier : `${identifier}.patch`;
|
|
31
|
-
return patches.find((p) => p.filename === normalized) ?? null;
|
|
32
|
-
}
|
|
33
17
|
async function scanPatchFiles(currentFilesAffected, engineDir, manifest, patchFilename, isDryRun) {
|
|
34
18
|
const parentDirs = [...new Set(currentFilesAffected.map((f) => dirname(f)))];
|
|
35
19
|
const claimedByOthers = getClaimedFiles(manifest, patchFilename);
|
|
@@ -89,11 +89,11 @@ export async function verifyCommand(projectRoot) {
|
|
|
89
89
|
if (lintIssues.length > 0) {
|
|
90
90
|
warn(`Cross-patch lint issues (${lintIssues.length}):`);
|
|
91
91
|
for (const issue of lintIssues) {
|
|
92
|
-
const label = issue.severity === 'error' ? 'ERROR' : 'WARN';
|
|
92
|
+
const label = issue.severity === 'error' ? 'ERROR' : issue.severity === 'warning' ? 'WARN' : 'NOTICE';
|
|
93
93
|
warn(` ${label} [${issue.check}] ${issue.file}: ${issue.message}`);
|
|
94
94
|
if (issue.severity === 'error')
|
|
95
95
|
errorCount += 1;
|
|
96
|
-
else
|
|
96
|
+
else if (issue.severity === 'warning')
|
|
97
97
|
warningCount += 1;
|
|
98
98
|
}
|
|
99
99
|
}
|
|
@@ -6,6 +6,12 @@ export { buildPatchQueueContext, collectNewFileCreatorsByPath, type ExtractedSpe
|
|
|
6
6
|
export { buildModifiedFileAdditionsFromDiff, detectNewFilesInDiff } from './patch-lint-diff.js';
|
|
7
7
|
export { type JsDocCheck, type JsDocIssue, validateExportJsDoc } from './patch-lint-jsdoc.js';
|
|
8
8
|
export { resolvePatchOwnedSysMjs } from './patch-lint-ownership.js';
|
|
9
|
+
/**
|
|
10
|
+
* Returns true if the file path looks like a test file.
|
|
11
|
+
* Matches paths containing `/test/` or filenames starting with
|
|
12
|
+
* `browser_`, `test_`, or `xpcshell_` (all `.js`).
|
|
13
|
+
*/
|
|
14
|
+
export declare function isTestFile(file: string): boolean;
|
|
9
15
|
/**
|
|
10
16
|
* Detects comment style from file extension for license header checks.
|
|
11
17
|
*/
|
|
@@ -19,7 +25,7 @@ export declare function commentStyleForFile(file: string): CommentStyle | null;
|
|
|
19
25
|
* @param diffContent - Optional unified diff used to scope raw color checks to introduced lines
|
|
20
26
|
* @returns Array of lint issues found
|
|
21
27
|
*/
|
|
22
|
-
export declare function lintPatchedCss(repoDir: string, affectedFiles: string[], diffContent?: string): Promise<PatchLintIssue[]>;
|
|
28
|
+
export declare function lintPatchedCss(repoDir: string, affectedFiles: string[], diffContent?: string, config?: FireForgeConfig): Promise<PatchLintIssue[]>;
|
|
23
29
|
/**
|
|
24
30
|
* Checks new files for required license headers.
|
|
25
31
|
*
|
|
@@ -28,6 +28,14 @@ export { resolvePatchOwnedSysMjs } from './patch-lint-ownership.js';
|
|
|
28
28
|
// Helpers
|
|
29
29
|
// ---------------------------------------------------------------------------
|
|
30
30
|
const JS_EXTENSIONS = ['.js', '.mjs', '.jsm'];
|
|
31
|
+
const FILE_SIZE_THRESHOLDS = {
|
|
32
|
+
general: { notice: 500, warning: 750, error: 900 },
|
|
33
|
+
test: { notice: 1200, warning: 1400, error: 1600 },
|
|
34
|
+
};
|
|
35
|
+
const PATCH_LINE_THRESHOLDS = {
|
|
36
|
+
general: { notice: 800, warning: 1500, error: 3000 },
|
|
37
|
+
test: { notice: 1500, warning: 3000, error: 6000 },
|
|
38
|
+
};
|
|
31
39
|
/**
|
|
32
40
|
* Returns true if the filename looks like a JS/MJS/JSM file.
|
|
33
41
|
* Handles `.sys.mjs` as well.
|
|
@@ -35,6 +43,17 @@ const JS_EXTENSIONS = ['.js', '.mjs', '.jsm'];
|
|
|
35
43
|
function isJsFile(file) {
|
|
36
44
|
return JS_EXTENSIONS.some((ext) => file.endsWith(ext));
|
|
37
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Returns true if the file path looks like a test file.
|
|
48
|
+
* Matches paths containing `/test/` or filenames starting with
|
|
49
|
+
* `browser_`, `test_`, or `xpcshell_` (all `.js`).
|
|
50
|
+
*/
|
|
51
|
+
export function isTestFile(file) {
|
|
52
|
+
if (file.includes('/test/'))
|
|
53
|
+
return true;
|
|
54
|
+
const basename = file.split('/').pop() ?? '';
|
|
55
|
+
return /^(?:browser_|test_|xpcshell_).*\.js$/.test(basename);
|
|
56
|
+
}
|
|
38
57
|
/**
|
|
39
58
|
* Detects comment style from file extension for license header checks.
|
|
40
59
|
*/
|
|
@@ -59,7 +78,7 @@ export function commentStyleForFile(file) {
|
|
|
59
78
|
* @param diffContent - Optional unified diff used to scope raw color checks to introduced lines
|
|
60
79
|
* @returns Array of lint issues found
|
|
61
80
|
*/
|
|
62
|
-
export async function lintPatchedCss(repoDir, affectedFiles, diffContent) {
|
|
81
|
+
export async function lintPatchedCss(repoDir, affectedFiles, diffContent, config) {
|
|
63
82
|
const cssFiles = affectedFiles.filter((f) => f.endsWith('.css'));
|
|
64
83
|
if (cssFiles.length === 0)
|
|
65
84
|
return [];
|
|
@@ -86,17 +105,29 @@ export async function lintPatchedCss(repoDir, affectedFiles, diffContent) {
|
|
|
86
105
|
const rawCss = await readText(filePath);
|
|
87
106
|
// Strip block comments before scanning
|
|
88
107
|
const cssContent = rawCss.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
89
|
-
const rawColorContent = addedLinesByFile
|
|
90
|
-
? (addedLinesByFile.get(file) ?? []).join('\n').replace(/\/\*[\s\S]*?\*\//g, '')
|
|
91
|
-
: cssContent;
|
|
92
108
|
// Check only introduced raw color values when diff context is available.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
109
|
+
// Skip files on the raw-color allowlist (exact path or basename match).
|
|
110
|
+
const allowlist = config?.patchLint?.rawColorAllowlist;
|
|
111
|
+
const isAllowlisted = allowlist?.some((entry) => file === entry || file.endsWith('/' + entry));
|
|
112
|
+
if (!isAllowlisted) {
|
|
113
|
+
// Strip lines with inline fireforge-ignore: raw-color-value suppression.
|
|
114
|
+
// Check against rawCss (before comment stripping) so the CSS comment marker is still present.
|
|
115
|
+
const sourceForSuppression = addedLinesByFile
|
|
116
|
+
? (addedLinesByFile.get(file) ?? []).join('\n')
|
|
117
|
+
: rawCss;
|
|
118
|
+
const suppressedContent = sourceForSuppression
|
|
119
|
+
.split('\n')
|
|
120
|
+
.filter((line) => !line.includes('fireforge-ignore: raw-color-value'))
|
|
121
|
+
.join('\n')
|
|
122
|
+
.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
123
|
+
if (hasRawCssColors(suppressedContent)) {
|
|
124
|
+
issues.push({
|
|
125
|
+
file,
|
|
126
|
+
check: 'raw-color-value',
|
|
127
|
+
message: 'Raw color value found. Use CSS custom properties (var(--...)) for design token consistency.',
|
|
128
|
+
severity: 'error',
|
|
129
|
+
});
|
|
130
|
+
}
|
|
100
131
|
}
|
|
101
132
|
// Check for non-tokenized custom properties
|
|
102
133
|
if (tokenPrefix) {
|
|
@@ -193,14 +224,34 @@ export async function lintPatchedJs(repoDir, affectedFiles, newFiles, config, pa
|
|
|
193
224
|
// 2. File size check (new files only)
|
|
194
225
|
if (isNew) {
|
|
195
226
|
const lineCount = content.split('\n').length;
|
|
196
|
-
|
|
227
|
+
const isTest = isTestFile(file);
|
|
228
|
+
const thresholds = isTest ? FILE_SIZE_THRESHOLDS.test : FILE_SIZE_THRESHOLDS.general;
|
|
229
|
+
const label = isTest ? 'Test file' : 'New file';
|
|
230
|
+
const verb = isTest ? 'splitting' : 'decomposing';
|
|
231
|
+
if (lineCount >= thresholds.error) {
|
|
197
232
|
issues.push({
|
|
198
233
|
file,
|
|
199
234
|
check: 'file-too-large',
|
|
200
|
-
message:
|
|
235
|
+
message: `${label} has ${lineCount} lines (hard limit: ${thresholds.error}). Consider ${verb}.`,
|
|
236
|
+
severity: 'error',
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
else if (lineCount >= thresholds.warning) {
|
|
240
|
+
issues.push({
|
|
241
|
+
file,
|
|
242
|
+
check: 'file-too-large',
|
|
243
|
+
message: `${label} has ${lineCount} lines (soft limit: ${thresholds.warning}, hard limit: ${thresholds.error}). Consider ${verb}.`,
|
|
201
244
|
severity: 'warning',
|
|
202
245
|
});
|
|
203
246
|
}
|
|
247
|
+
else if (lineCount >= thresholds.notice) {
|
|
248
|
+
issues.push({
|
|
249
|
+
file,
|
|
250
|
+
check: 'file-too-large',
|
|
251
|
+
message: `${label} has ${lineCount} lines (soft limit: ${thresholds.warning}, hard limit: ${thresholds.error}). Consider ${verb}.`,
|
|
252
|
+
severity: 'notice',
|
|
253
|
+
});
|
|
254
|
+
}
|
|
204
255
|
}
|
|
205
256
|
// 3. JSDoc on exports (patch-owned .sys.mjs files)
|
|
206
257
|
const isOwned = patchOwnedFiles ? patchOwnedFiles.has(file) : isNew;
|
|
@@ -216,7 +267,7 @@ export async function lintPatchedJs(repoDir, affectedFiles, newFiles, config, pa
|
|
|
216
267
|
}
|
|
217
268
|
}
|
|
218
269
|
// 4. Observer topic naming
|
|
219
|
-
const topicPattern = /(?:addObserver|removeObserver|notifyObservers)\s*\([^)]*["']([^"']+)["']/g;
|
|
270
|
+
const topicPattern = /(?:addObserver|removeObserver|notifyObservers)\s*\([^)\n]*["']([^"']+)["']/g;
|
|
220
271
|
let topicMatch;
|
|
221
272
|
while ((topicMatch = topicPattern.exec(strippedContent)) !== null) {
|
|
222
273
|
const topic = topicMatch[1];
|
|
@@ -283,14 +334,32 @@ export function lintPatchSize(filesAffected, lineCount) {
|
|
|
283
334
|
severity: 'warning',
|
|
284
335
|
});
|
|
285
336
|
}
|
|
286
|
-
|
|
337
|
+
const allTests = filesAffected.length > 0 && filesAffected.every(isTestFile);
|
|
338
|
+
const thresholds = allTests ? PATCH_LINE_THRESHOLDS.test : PATCH_LINE_THRESHOLDS.general;
|
|
339
|
+
if (lineCount >= thresholds.error) {
|
|
340
|
+
issues.push({
|
|
341
|
+
file: '(patch)',
|
|
342
|
+
check: 'large-patch-lines',
|
|
343
|
+
message: `Patch is ${lineCount} lines (hard limit: ${thresholds.error}). Consider splitting into smaller, focused patches.`,
|
|
344
|
+
severity: 'error',
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
else if (lineCount >= thresholds.warning) {
|
|
287
348
|
issues.push({
|
|
288
349
|
file: '(patch)',
|
|
289
350
|
check: 'large-patch-lines',
|
|
290
|
-
message: `Patch is ${lineCount} lines (
|
|
351
|
+
message: `Patch is ${lineCount} lines (soft limit: ${thresholds.warning}, hard limit: ${thresholds.error}). Consider splitting into smaller, focused patches.`,
|
|
291
352
|
severity: 'warning',
|
|
292
353
|
});
|
|
293
354
|
}
|
|
355
|
+
else if (lineCount >= thresholds.notice) {
|
|
356
|
+
issues.push({
|
|
357
|
+
file: '(patch)',
|
|
358
|
+
check: 'large-patch-lines',
|
|
359
|
+
message: `Patch is ${lineCount} lines (soft limit: ${thresholds.warning}, hard limit: ${thresholds.error}). Consider splitting into smaller, focused patches.`,
|
|
360
|
+
severity: 'notice',
|
|
361
|
+
});
|
|
362
|
+
}
|
|
294
363
|
return issues;
|
|
295
364
|
}
|
|
296
365
|
// ---------------------------------------------------------------------------
|
|
@@ -346,7 +415,7 @@ export async function lintExportedPatch(repoDir, affectedFiles, diffContent, con
|
|
|
346
415
|
const lineCount = diffContent.split('\n').length;
|
|
347
416
|
const patchOwnedFiles = resolvePatchOwnedSysMjs(newFiles, patchQueueCtx);
|
|
348
417
|
const [cssIssues, headerIssues, jsIssues, modifiedHeaderIssues] = await Promise.all([
|
|
349
|
-
lintPatchedCss(repoDir, affectedFiles, diffContent),
|
|
418
|
+
lintPatchedCss(repoDir, affectedFiles, diffContent, config),
|
|
350
419
|
lintNewFileHeaders(repoDir, [...newFiles], config),
|
|
351
420
|
lintPatchedJs(repoDir, affectedFiles, newFiles, config, patchOwnedFiles),
|
|
352
421
|
lintModifiedFileHeaders(repoDir, affectedFiles, newFiles),
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { PatchMetadata } from '../types/commands/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Resolves a patch identifier (ordinal number or filename) to its manifest entry.
|
|
4
|
+
*/
|
|
5
|
+
export declare function resolvePatchIdentifier(identifier: string, patches: PatchMetadata[]): PatchMetadata | null;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves a patch identifier (ordinal number or filename) to its manifest entry.
|
|
3
|
+
*/
|
|
4
|
+
export function resolvePatchIdentifier(identifier, patches) {
|
|
5
|
+
if (/^\d+$/.test(identifier)) {
|
|
6
|
+
const order = parseInt(identifier, 10);
|
|
7
|
+
return patches.find((p) => p.order === order) ?? null;
|
|
8
|
+
}
|
|
9
|
+
const normalized = identifier.endsWith('.patch') ? identifier : `${identifier}.patch`;
|
|
10
|
+
return patches.find((p) => p.filename === normalized) ?? null;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=patch-manifest-resolve.js.map
|
|
@@ -8,4 +8,5 @@ export type { PatchManifestConsistencyIssue } from './patch-manifest-consistency
|
|
|
8
8
|
export { rebuildPatchesManifest, validatePatchesManifestConsistency, } from './patch-manifest-consistency.js';
|
|
9
9
|
export { addPatchToManifest, loadPatchesManifest, PatchDeleteRollbackError, PATCHES_MANIFEST, type PatchRenameEntry, removePatchFileAndManifest, removePatchFromManifest, renumberPatchesInManifest, savePatchesManifest, } from './patch-manifest-io.js';
|
|
10
10
|
export { checkVersionCompatibility, findPatchesAffectingFile, getClaimedFiles, stampPatchVersions, validatePatchIntegrity, } from './patch-manifest-query.js';
|
|
11
|
+
export { resolvePatchIdentifier } from './patch-manifest-resolve.js';
|
|
11
12
|
export { validatePatchesManifest } from './patch-manifest-validate.js';
|
|
@@ -8,5 +8,6 @@
|
|
|
8
8
|
export { rebuildPatchesManifest, validatePatchesManifestConsistency, } from './patch-manifest-consistency.js';
|
|
9
9
|
export { addPatchToManifest, loadPatchesManifest, PatchDeleteRollbackError, PATCHES_MANIFEST, removePatchFileAndManifest, removePatchFromManifest, renumberPatchesInManifest, savePatchesManifest, } from './patch-manifest-io.js';
|
|
10
10
|
export { checkVersionCompatibility, findPatchesAffectingFile, getClaimedFiles, stampPatchVersions, validatePatchIntegrity, } from './patch-manifest-query.js';
|
|
11
|
+
export { resolvePatchIdentifier } from './patch-manifest-resolve.js';
|
|
11
12
|
export { validatePatchesManifest } from './patch-manifest-validate.js';
|
|
12
13
|
//# sourceMappingURL=patch-manifest.js.map
|
|
@@ -94,6 +94,6 @@ export interface PatchLintIssue {
|
|
|
94
94
|
fingerprint?: string;
|
|
95
95
|
/** Human-readable description of the issue */
|
|
96
96
|
message: string;
|
|
97
|
-
/** Severity: errors block export, warnings are advisory */
|
|
98
|
-
severity: 'error' | 'warning';
|
|
97
|
+
/** Severity: errors block export, warnings are advisory, notices are informational (not counted) */
|
|
98
|
+
severity: 'error' | 'warning' | 'notice';
|
|
99
99
|
}
|
|
@@ -58,6 +58,8 @@ export interface WireConfig {
|
|
|
58
58
|
export interface PatchLintConfig {
|
|
59
59
|
/** Enable TypeScript checkJs pass on patch-owned .sys.mjs files */
|
|
60
60
|
checkJs?: boolean;
|
|
61
|
+
/** File paths exempt from the raw-color-value check (exact or basename match) */
|
|
62
|
+
rawColorAllowlist?: string[];
|
|
61
63
|
}
|
|
62
64
|
/**
|
|
63
65
|
* Build mode for mach.
|