@hominis/fireforge 0.9.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 +7 -0
- package/LICENSE.md +294 -0
- package/README.md +435 -0
- package/dist/bin/fireforge.d.ts +10 -0
- package/dist/bin/fireforge.js +29 -0
- package/dist/src/cli.d.ts +33 -0
- package/dist/src/cli.js +180 -0
- package/dist/src/commands/bootstrap.d.ts +9 -0
- package/dist/src/commands/bootstrap.js +73 -0
- package/dist/src/commands/build.d.ts +11 -0
- package/dist/src/commands/build.js +102 -0
- package/dist/src/commands/config.d.ts +13 -0
- package/dist/src/commands/config.js +135 -0
- package/dist/src/commands/discard.d.ts +12 -0
- package/dist/src/commands/discard.js +84 -0
- package/dist/src/commands/doctor.d.ts +18 -0
- package/dist/src/commands/doctor.js +356 -0
- package/dist/src/commands/download.d.ts +11 -0
- package/dist/src/commands/download.js +127 -0
- package/dist/src/commands/export-all.d.ts +11 -0
- package/dist/src/commands/export-all.js +122 -0
- package/dist/src/commands/export-shared.d.ts +48 -0
- package/dist/src/commands/export-shared.js +208 -0
- package/dist/src/commands/export.d.ts +13 -0
- package/dist/src/commands/export.js +178 -0
- package/dist/src/commands/furnace/apply.d.ts +7 -0
- package/dist/src/commands/furnace/apply.js +80 -0
- package/dist/src/commands/furnace/create.d.ts +8 -0
- package/dist/src/commands/furnace/create.js +377 -0
- package/dist/src/commands/furnace/deploy.d.ts +8 -0
- package/dist/src/commands/furnace/deploy.js +338 -0
- package/dist/src/commands/furnace/diff.d.ts +7 -0
- package/dist/src/commands/furnace/diff.js +119 -0
- package/dist/src/commands/furnace/index.d.ts +16 -0
- package/dist/src/commands/furnace/index.js +121 -0
- package/dist/src/commands/furnace/list.d.ts +5 -0
- package/dist/src/commands/furnace/list.js +65 -0
- package/dist/src/commands/furnace/override.d.ts +8 -0
- package/dist/src/commands/furnace/override.js +188 -0
- package/dist/src/commands/furnace/preview.d.ts +7 -0
- package/dist/src/commands/furnace/preview.js +96 -0
- package/dist/src/commands/furnace/remove.d.ts +8 -0
- package/dist/src/commands/furnace/remove.js +159 -0
- package/dist/src/commands/furnace/scan.d.ts +5 -0
- package/dist/src/commands/furnace/scan.js +112 -0
- package/dist/src/commands/furnace/status.d.ts +7 -0
- package/dist/src/commands/furnace/status.js +137 -0
- package/dist/src/commands/furnace/validate.d.ts +6 -0
- package/dist/src/commands/furnace/validate.js +91 -0
- package/dist/src/commands/furnace/validation-output.d.ts +7 -0
- package/dist/src/commands/furnace/validation-output.js +22 -0
- package/dist/src/commands/import.d.ts +11 -0
- package/dist/src/commands/import.js +241 -0
- package/dist/src/commands/lint.d.ts +10 -0
- package/dist/src/commands/lint.js +118 -0
- package/dist/src/commands/package.d.ts +11 -0
- package/dist/src/commands/package.js +80 -0
- package/dist/src/commands/re-export.d.ts +12 -0
- package/dist/src/commands/re-export.js +242 -0
- package/dist/src/commands/rebase/abort.d.ts +7 -0
- package/dist/src/commands/rebase/abort.js +49 -0
- package/dist/src/commands/rebase/confirm.d.ts +18 -0
- package/dist/src/commands/rebase/confirm.js +33 -0
- package/dist/src/commands/rebase/continue.d.ts +7 -0
- package/dist/src/commands/rebase/continue.js +81 -0
- package/dist/src/commands/rebase/index.d.ts +22 -0
- package/dist/src/commands/rebase/index.js +127 -0
- package/dist/src/commands/rebase/patch-loop.d.ts +9 -0
- package/dist/src/commands/rebase/patch-loop.js +135 -0
- package/dist/src/commands/rebase/summary.d.ts +12 -0
- package/dist/src/commands/rebase/summary.js +43 -0
- package/dist/src/commands/rebase.d.ts +4 -0
- package/dist/src/commands/rebase.js +6 -0
- package/dist/src/commands/register.d.ts +13 -0
- package/dist/src/commands/register.js +67 -0
- package/dist/src/commands/reset.d.ts +11 -0
- package/dist/src/commands/reset.js +83 -0
- package/dist/src/commands/resolve.d.ts +9 -0
- package/dist/src/commands/resolve.js +124 -0
- package/dist/src/commands/run.d.ts +9 -0
- package/dist/src/commands/run.js +91 -0
- package/dist/src/commands/setup-support.d.ts +23 -0
- package/dist/src/commands/setup-support.js +310 -0
- package/dist/src/commands/setup.d.ts +11 -0
- package/dist/src/commands/setup.js +94 -0
- package/dist/src/commands/status.d.ts +11 -0
- package/dist/src/commands/status.js +268 -0
- package/dist/src/commands/test.d.ts +12 -0
- package/dist/src/commands/test.js +182 -0
- package/dist/src/commands/token-coverage.d.ts +5 -0
- package/dist/src/commands/token-coverage.js +57 -0
- package/dist/src/commands/token.d.ts +14 -0
- package/dist/src/commands/token.js +121 -0
- package/dist/src/commands/watch.d.ts +9 -0
- package/dist/src/commands/watch.js +112 -0
- package/dist/src/commands/wire.d.ts +13 -0
- package/dist/src/commands/wire.js +149 -0
- package/dist/src/core/ast-utils.d.ts +47 -0
- package/dist/src/core/ast-utils.js +57 -0
- package/dist/src/core/brand-validation.d.ts +7 -0
- package/dist/src/core/brand-validation.js +15 -0
- package/dist/src/core/branding.d.ts +49 -0
- package/dist/src/core/branding.js +229 -0
- package/dist/src/core/browser-wire.d.ts +40 -0
- package/dist/src/core/browser-wire.js +66 -0
- package/dist/src/core/build-prepare.d.ts +25 -0
- package/dist/src/core/build-prepare.js +93 -0
- package/dist/src/core/config-mutate.d.ts +15 -0
- package/dist/src/core/config-mutate.js +51 -0
- package/dist/src/core/config-paths.d.ts +28 -0
- package/dist/src/core/config-paths.js +65 -0
- package/dist/src/core/config-state.d.ts +28 -0
- package/dist/src/core/config-state.js +152 -0
- package/dist/src/core/config-validate.d.ts +11 -0
- package/dist/src/core/config-validate.js +141 -0
- package/dist/src/core/config.d.ts +39 -0
- package/dist/src/core/config.js +70 -0
- package/dist/src/core/file-lock.d.ts +11 -0
- package/dist/src/core/file-lock.js +80 -0
- package/dist/src/core/firefox-archive.d.ts +40 -0
- package/dist/src/core/firefox-archive.js +63 -0
- package/dist/src/core/firefox-cache.d.ts +23 -0
- package/dist/src/core/firefox-cache.js +134 -0
- package/dist/src/core/firefox-download.d.ts +21 -0
- package/dist/src/core/firefox-download.js +129 -0
- package/dist/src/core/firefox-extract.d.ts +21 -0
- package/dist/src/core/firefox-extract.js +53 -0
- package/dist/src/core/firefox.d.ts +34 -0
- package/dist/src/core/firefox.js +78 -0
- package/dist/src/core/furnace-apply-helpers.d.ts +21 -0
- package/dist/src/core/furnace-apply-helpers.js +244 -0
- package/dist/src/core/furnace-apply.d.ts +16 -0
- package/dist/src/core/furnace-apply.js +147 -0
- package/dist/src/core/furnace-config.d.ts +94 -0
- package/dist/src/core/furnace-config.js +372 -0
- package/dist/src/core/furnace-constants.d.ts +4 -0
- package/dist/src/core/furnace-constants.js +6 -0
- package/dist/src/core/furnace-registration-ast.d.ts +24 -0
- package/dist/src/core/furnace-registration-ast.js +218 -0
- package/dist/src/core/furnace-registration-remove.d.ts +14 -0
- package/dist/src/core/furnace-registration-remove.js +89 -0
- package/dist/src/core/furnace-registration-validate.d.ts +20 -0
- package/dist/src/core/furnace-registration-validate.js +40 -0
- package/dist/src/core/furnace-registration.d.ts +29 -0
- package/dist/src/core/furnace-registration.js +96 -0
- package/dist/src/core/furnace-rollback.d.ts +20 -0
- package/dist/src/core/furnace-rollback.js +66 -0
- package/dist/src/core/furnace-scanner.d.ts +40 -0
- package/dist/src/core/furnace-scanner.js +143 -0
- package/dist/src/core/furnace-stories.d.ts +37 -0
- package/dist/src/core/furnace-stories.js +185 -0
- package/dist/src/core/furnace-validate-accessibility.d.ts +6 -0
- package/dist/src/core/furnace-validate-accessibility.js +32 -0
- package/dist/src/core/furnace-validate-checks.d.ts +4 -0
- package/dist/src/core/furnace-validate-checks.js +7 -0
- package/dist/src/core/furnace-validate-compatibility.d.ts +6 -0
- package/dist/src/core/furnace-validate-compatibility.js +57 -0
- package/dist/src/core/furnace-validate-helpers.d.ts +28 -0
- package/dist/src/core/furnace-validate-helpers.js +129 -0
- package/dist/src/core/furnace-validate-registration.d.ts +37 -0
- package/dist/src/core/furnace-validate-registration.js +220 -0
- package/dist/src/core/furnace-validate-structure.d.ts +6 -0
- package/dist/src/core/furnace-validate-structure.js +66 -0
- package/dist/src/core/furnace-validate.d.ts +16 -0
- package/dist/src/core/furnace-validate.js +103 -0
- package/dist/src/core/git-base.d.ts +47 -0
- package/dist/src/core/git-base.js +50 -0
- package/dist/src/core/git-diff.d.ts +63 -0
- package/dist/src/core/git-diff.js +246 -0
- package/dist/src/core/git-file-ops.d.ts +65 -0
- package/dist/src/core/git-file-ops.js +141 -0
- package/dist/src/core/git-status.d.ts +65 -0
- package/dist/src/core/git-status.js +163 -0
- package/dist/src/core/git.d.ts +113 -0
- package/dist/src/core/git.js +363 -0
- package/dist/src/core/license-headers.d.ts +36 -0
- package/dist/src/core/license-headers.js +83 -0
- package/dist/src/core/mach-build-artifacts.d.ts +29 -0
- package/dist/src/core/mach-build-artifacts.js +117 -0
- package/dist/src/core/mach-mozconfig.d.ts +17 -0
- package/dist/src/core/mach-mozconfig.js +50 -0
- package/dist/src/core/mach-python.d.ts +16 -0
- package/dist/src/core/mach-python.js +126 -0
- package/dist/src/core/mach.d.ts +106 -0
- package/dist/src/core/mach.js +166 -0
- package/dist/src/core/manifest-helpers.d.ts +25 -0
- package/dist/src/core/manifest-helpers.js +96 -0
- package/dist/src/core/manifest-register.d.ts +30 -0
- package/dist/src/core/manifest-register.js +65 -0
- package/dist/src/core/manifest-rules.d.ts +39 -0
- package/dist/src/core/manifest-rules.js +151 -0
- package/dist/src/core/manifest-tokenizers.d.ts +34 -0
- package/dist/src/core/manifest-tokenizers.js +84 -0
- package/dist/src/core/parser-fallback.d.ts +36 -0
- package/dist/src/core/parser-fallback.js +43 -0
- package/dist/src/core/patch-apply-fuzz.d.ts +29 -0
- package/dist/src/core/patch-apply-fuzz.js +70 -0
- package/dist/src/core/patch-apply.d.ts +46 -0
- package/dist/src/core/patch-apply.js +235 -0
- package/dist/src/core/patch-export.d.ts +99 -0
- package/dist/src/core/patch-export.js +314 -0
- package/dist/src/core/patch-files.d.ts +11 -0
- package/dist/src/core/patch-files.js +51 -0
- package/dist/src/core/patch-lint.d.ts +72 -0
- package/dist/src/core/patch-lint.js +403 -0
- package/dist/src/core/patch-lock.d.ts +8 -0
- package/dist/src/core/patch-lock.js +29 -0
- package/dist/src/core/patch-manifest-consistency.d.ts +24 -0
- package/dist/src/core/patch-manifest-consistency.js +135 -0
- package/dist/src/core/patch-manifest-io.d.ts +36 -0
- package/dist/src/core/patch-manifest-io.js +77 -0
- package/dist/src/core/patch-manifest-query.d.ts +48 -0
- package/dist/src/core/patch-manifest-query.js +124 -0
- package/dist/src/core/patch-manifest-validate.d.ts +22 -0
- package/dist/src/core/patch-manifest-validate.js +72 -0
- package/dist/src/core/patch-manifest.d.ts +11 -0
- package/dist/src/core/patch-manifest.js +12 -0
- package/dist/src/core/patch-parse.d.ts +43 -0
- package/dist/src/core/patch-parse.js +143 -0
- package/dist/src/core/patch-transform.d.ts +21 -0
- package/dist/src/core/patch-transform.js +138 -0
- package/dist/src/core/rebase-session.d.ts +47 -0
- package/dist/src/core/rebase-session.js +65 -0
- package/dist/src/core/register-browser-content.d.ts +11 -0
- package/dist/src/core/register-browser-content.js +116 -0
- package/dist/src/core/register-module.d.ts +11 -0
- package/dist/src/core/register-module.js +76 -0
- package/dist/src/core/register-shared-css.d.ts +11 -0
- package/dist/src/core/register-shared-css.js +117 -0
- package/dist/src/core/register-test-manifest.d.ts +18 -0
- package/dist/src/core/register-test-manifest.js +99 -0
- package/dist/src/core/state-file.d.ts +4 -0
- package/dist/src/core/state-file.js +25 -0
- package/dist/src/core/token-coverage.d.ts +12 -0
- package/dist/src/core/token-coverage.js +74 -0
- package/dist/src/core/token-manager.d.ts +55 -0
- package/dist/src/core/token-manager.js +387 -0
- package/dist/src/core/wire-destroy.d.ts +21 -0
- package/dist/src/core/wire-destroy.js +103 -0
- package/dist/src/core/wire-dom-fragment.d.ts +23 -0
- package/dist/src/core/wire-dom-fragment.js +129 -0
- package/dist/src/core/wire-init.d.ts +23 -0
- package/dist/src/core/wire-init.js +201 -0
- package/dist/src/core/wire-subscript.d.ts +20 -0
- package/dist/src/core/wire-subscript.js +134 -0
- package/dist/src/core/wire-targets.d.ts +7 -0
- package/dist/src/core/wire-targets.js +9 -0
- package/dist/src/core/wire-utils.d.ts +88 -0
- package/dist/src/core/wire-utils.js +279 -0
- package/dist/src/errors/base.d.ts +60 -0
- package/dist/src/errors/base.js +87 -0
- package/dist/src/errors/build.d.ts +52 -0
- package/dist/src/errors/build.js +114 -0
- package/dist/src/errors/codes.d.ts +29 -0
- package/dist/src/errors/codes.js +30 -0
- package/dist/src/errors/config.d.ts +31 -0
- package/dist/src/errors/config.js +61 -0
- package/dist/src/errors/download.d.ts +42 -0
- package/dist/src/errors/download.js +95 -0
- package/dist/src/errors/furnace.d.ts +10 -0
- package/dist/src/errors/furnace.js +22 -0
- package/dist/src/errors/git.d.ts +41 -0
- package/dist/src/errors/git.js +99 -0
- package/dist/src/errors/patch.d.ts +10 -0
- package/dist/src/errors/patch.js +26 -0
- package/dist/src/errors/rebase.d.ts +20 -0
- package/dist/src/errors/rebase.js +30 -0
- package/dist/src/index.d.ts +21 -0
- package/dist/src/index.js +21 -0
- package/dist/src/types/cli.d.ts +14 -0
- package/dist/src/types/cli.js +2 -0
- package/dist/src/types/commands/index.d.ts +6 -0
- package/dist/src/types/commands/index.js +6 -0
- package/dist/src/types/commands/options.d.ts +239 -0
- package/dist/src/types/commands/options.js +6 -0
- package/dist/src/types/commands/patches.d.ts +89 -0
- package/dist/src/types/commands/patches.js +6 -0
- package/dist/src/types/commands/project.d.ts +71 -0
- package/dist/src/types/commands/project.js +6 -0
- package/dist/src/types/config.d.ts +101 -0
- package/dist/src/types/config.js +2 -0
- package/dist/src/types/furnace.d.ts +158 -0
- package/dist/src/types/furnace.js +2 -0
- package/dist/src/types/index.d.ts +6 -0
- package/dist/src/types/index.js +6 -0
- package/dist/src/utils/errors.d.ts +2 -0
- package/dist/src/utils/errors.js +15 -0
- package/dist/src/utils/fs.d.ts +72 -0
- package/dist/src/utils/fs.js +179 -0
- package/dist/src/utils/logger.d.ts +58 -0
- package/dist/src/utils/logger.js +120 -0
- package/dist/src/utils/options.d.ts +8 -0
- package/dist/src/utils/options.js +16 -0
- package/dist/src/utils/package-root.d.ts +10 -0
- package/dist/src/utils/package-root.js +53 -0
- package/dist/src/utils/parse.d.ts +110 -0
- package/dist/src/utils/parse.js +200 -0
- package/dist/src/utils/paths.d.ts +10 -0
- package/dist/src/utils/paths.js +43 -0
- package/dist/src/utils/platform.d.ts +38 -0
- package/dist/src/utils/platform.js +56 -0
- package/dist/src/utils/process.d.ts +80 -0
- package/dist/src/utils/process.js +188 -0
- package/dist/src/utils/regex.d.ts +24 -0
- package/dist/src/utils/regex.js +40 -0
- package/dist/src/utils/validation.d.ts +133 -0
- package/dist/src/utils/validation.js +250 -0
- package/package.json +106 -0
- package/templates/configs/common.mozconfig +24 -0
- package/templates/configs/darwin.mozconfig +10 -0
- package/templates/configs/linux.mozconfig +12 -0
- package/templates/configs/win32.mozconfig +14 -0
- package/templates/licenses/0BSD.md +14 -0
- package/templates/licenses/EUPL-1.2.md +294 -0
- package/templates/licenses/GPL-2.0-or-later.md +339 -0
- package/templates/licenses/MPL-2.0.md +383 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { toError } from '../utils/errors.js';
|
|
4
|
+
import { pathExists, readText } from '../utils/fs.js';
|
|
5
|
+
import { verbose } from '../utils/logger.js';
|
|
6
|
+
import { hasRawCssColors, stripJsComments } from '../utils/regex.js';
|
|
7
|
+
import { loadFurnaceConfig } from './furnace-config.js';
|
|
8
|
+
import { getLicenseHeader, hasAnyLicenseHeader } from './license-headers.js';
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Helpers
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
const JS_EXTENSIONS = ['.js', '.mjs', '.jsm'];
|
|
13
|
+
/**
|
|
14
|
+
* Returns true if the filename looks like a JS/MJS/JSM file.
|
|
15
|
+
* Handles `.sys.mjs` as well.
|
|
16
|
+
*/
|
|
17
|
+
function isJsFile(file) {
|
|
18
|
+
return JS_EXTENSIONS.some((ext) => file.endsWith(ext));
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Detects comment style from file extension for license header checks.
|
|
22
|
+
*/
|
|
23
|
+
export function commentStyleForFile(file) {
|
|
24
|
+
if (file.endsWith('.css'))
|
|
25
|
+
return 'css';
|
|
26
|
+
if (file.endsWith('.ftl'))
|
|
27
|
+
return 'hash';
|
|
28
|
+
if (isJsFile(file))
|
|
29
|
+
return 'js';
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Extracts new-file paths from a unified diff by scanning for `new file mode` markers.
|
|
34
|
+
*/
|
|
35
|
+
export function detectNewFilesInDiff(diffContent) {
|
|
36
|
+
const newFiles = new Set();
|
|
37
|
+
const lines = diffContent.split('\n');
|
|
38
|
+
let currentFile = null;
|
|
39
|
+
for (const line of lines) {
|
|
40
|
+
if (line.startsWith('diff --git')) {
|
|
41
|
+
const match = /^diff --git a\/.+ b\/(.+)$/.exec(line);
|
|
42
|
+
currentFile = match?.[1] ?? null;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (line.startsWith('new file mode') && currentFile) {
|
|
46
|
+
newFiles.add(currentFile);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return newFiles;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Extracts added lines per file from a unified diff.
|
|
53
|
+
* Returns a map of file path → array of added line contents (without the leading `+`).
|
|
54
|
+
*/
|
|
55
|
+
function extractAddedLinesPerFile(diffContent) {
|
|
56
|
+
const result = new Map();
|
|
57
|
+
const lines = diffContent.split('\n');
|
|
58
|
+
let currentFile = null;
|
|
59
|
+
let inHunk = false;
|
|
60
|
+
for (const line of lines) {
|
|
61
|
+
if (line.startsWith('diff --git')) {
|
|
62
|
+
const match = /^diff --git a\/.+ b\/(.+)$/.exec(line);
|
|
63
|
+
currentFile = match?.[1] ?? null;
|
|
64
|
+
inHunk = false;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (line.startsWith('@@')) {
|
|
68
|
+
inHunk = true;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (inHunk && currentFile && line.startsWith('+') && !line.startsWith('+++')) {
|
|
72
|
+
let arr = result.get(currentFile);
|
|
73
|
+
if (!arr) {
|
|
74
|
+
arr = [];
|
|
75
|
+
result.set(currentFile, arr);
|
|
76
|
+
}
|
|
77
|
+
arr.push(line.slice(1));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// CSS lint (existing — now with severity)
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
/**
|
|
86
|
+
* Lints patched CSS files for raw color values and non-tokenized custom properties.
|
|
87
|
+
*
|
|
88
|
+
* @param repoDir - Absolute path to the engine (repository) directory
|
|
89
|
+
* @param affectedFiles - File paths (relative to repoDir) affected by the patch
|
|
90
|
+
* @returns Array of lint issues found
|
|
91
|
+
*/
|
|
92
|
+
export async function lintPatchedCss(repoDir, affectedFiles) {
|
|
93
|
+
const cssFiles = affectedFiles.filter((f) => f.endsWith('.css'));
|
|
94
|
+
if (cssFiles.length === 0)
|
|
95
|
+
return [];
|
|
96
|
+
// Load furnace config gracefully — skip token-prefix check if unavailable
|
|
97
|
+
let tokenPrefix;
|
|
98
|
+
let tokenAllowlist;
|
|
99
|
+
try {
|
|
100
|
+
const root = join(repoDir, '..');
|
|
101
|
+
const config = await loadFurnaceConfig(root);
|
|
102
|
+
if (config.tokenPrefix) {
|
|
103
|
+
tokenPrefix = config.tokenPrefix;
|
|
104
|
+
tokenAllowlist = new Set(config.tokenAllowlist ?? []);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
verbose(`Skipping furnace token-prefix lint hints because furnace.json could not be loaded: ${toError(error).message}`);
|
|
109
|
+
}
|
|
110
|
+
const issues = [];
|
|
111
|
+
for (const file of cssFiles) {
|
|
112
|
+
const filePath = join(repoDir, file);
|
|
113
|
+
if (!(await pathExists(filePath)))
|
|
114
|
+
continue;
|
|
115
|
+
const rawCss = await readText(filePath);
|
|
116
|
+
// Strip block comments before scanning
|
|
117
|
+
const cssContent = rawCss.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
118
|
+
// Check for raw color values
|
|
119
|
+
if (hasRawCssColors(cssContent)) {
|
|
120
|
+
issues.push({
|
|
121
|
+
file,
|
|
122
|
+
check: 'raw-color-value',
|
|
123
|
+
message: 'Raw color value found. Use CSS custom properties (var(--...)) for design token consistency.',
|
|
124
|
+
severity: 'warning',
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
// Check for non-tokenized custom properties
|
|
128
|
+
if (tokenPrefix) {
|
|
129
|
+
const varPattern = /var\(\s*(--[\w-]+)/g;
|
|
130
|
+
let match;
|
|
131
|
+
while ((match = varPattern.exec(cssContent)) !== null) {
|
|
132
|
+
const prop = match[1];
|
|
133
|
+
if (prop && !prop.startsWith(tokenPrefix) && !tokenAllowlist?.has(prop)) {
|
|
134
|
+
issues.push({
|
|
135
|
+
file,
|
|
136
|
+
check: 'token-prefix-violation',
|
|
137
|
+
message: `CSS references var(${prop}) which does not match the required token prefix "${tokenPrefix}". Use a design token or add to tokenAllowlist.`,
|
|
138
|
+
severity: 'error',
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return issues;
|
|
145
|
+
}
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
// License header lint
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
/**
|
|
150
|
+
* Checks new files for required license headers.
|
|
151
|
+
*
|
|
152
|
+
* @param repoDir - Absolute path to the engine directory
|
|
153
|
+
* @param newFiles - New file paths (relative to repoDir)
|
|
154
|
+
* @param config - Project configuration
|
|
155
|
+
* @returns Array of lint issues
|
|
156
|
+
*/
|
|
157
|
+
export async function lintNewFileHeaders(repoDir, newFiles, config) {
|
|
158
|
+
const license = config.license ?? 'MPL-2.0';
|
|
159
|
+
const issues = [];
|
|
160
|
+
for (const file of newFiles) {
|
|
161
|
+
const style = commentStyleForFile(file);
|
|
162
|
+
if (!style)
|
|
163
|
+
continue;
|
|
164
|
+
const filePath = join(repoDir, file);
|
|
165
|
+
if (!(await pathExists(filePath)))
|
|
166
|
+
continue;
|
|
167
|
+
const content = await readText(filePath);
|
|
168
|
+
const expectedHeader = getLicenseHeader(license, style);
|
|
169
|
+
if (!content.startsWith(expectedHeader)) {
|
|
170
|
+
issues.push({
|
|
171
|
+
file,
|
|
172
|
+
check: 'missing-license-header',
|
|
173
|
+
message: `New file is missing the required ${license} license header.`,
|
|
174
|
+
severity: 'error',
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return issues;
|
|
179
|
+
}
|
|
180
|
+
// ---------------------------------------------------------------------------
|
|
181
|
+
// JS lint
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
/**
|
|
184
|
+
* Lints patched JS/MJS files for import conventions, file size, JSDoc, and
|
|
185
|
+
* observer topic naming.
|
|
186
|
+
*
|
|
187
|
+
* @param repoDir - Absolute path to the engine directory
|
|
188
|
+
* @param affectedFiles - File paths (relative to repoDir)
|
|
189
|
+
* @param newFiles - Set of files that are newly created in this patch
|
|
190
|
+
* @param config - Project configuration
|
|
191
|
+
* @returns Array of lint issues
|
|
192
|
+
*/
|
|
193
|
+
export async function lintPatchedJs(repoDir, affectedFiles, newFiles, config) {
|
|
194
|
+
const jsFiles = affectedFiles.filter(isJsFile);
|
|
195
|
+
if (jsFiles.length === 0)
|
|
196
|
+
return [];
|
|
197
|
+
const issues = [];
|
|
198
|
+
const binaryName = config.binaryName.toLowerCase();
|
|
199
|
+
for (const file of jsFiles) {
|
|
200
|
+
const filePath = join(repoDir, file);
|
|
201
|
+
if (!(await pathExists(filePath)))
|
|
202
|
+
continue;
|
|
203
|
+
const content = await readText(filePath);
|
|
204
|
+
const isNew = newFiles.has(file);
|
|
205
|
+
const isSysMjs = file.endsWith('.sys.mjs');
|
|
206
|
+
// 1. Relative import check
|
|
207
|
+
const strippedContent = stripJsComments(content);
|
|
208
|
+
const relativeImportPattern = /(?:ChromeUtils\.import(?:ESModule)?|Cu\.import)\s*\(\s*["'](?:\.\.?\/)/gm;
|
|
209
|
+
const esRelativePattern = /\bimport\s+.*?\s+from\s+["'](?:\.\.?\/)/gm;
|
|
210
|
+
if (relativeImportPattern.test(strippedContent) || esRelativePattern.test(strippedContent)) {
|
|
211
|
+
issues.push({
|
|
212
|
+
file,
|
|
213
|
+
check: 'relative-import',
|
|
214
|
+
message: `Relative imports are not allowed. Use "resource:///modules/${config.binaryName}/" for .sys.mjs or "chrome://browser/content/" for subscripts.`,
|
|
215
|
+
severity: 'error',
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
// 2. File size check (new files only)
|
|
219
|
+
if (isNew) {
|
|
220
|
+
const lineCount = content.split('\n').length;
|
|
221
|
+
if (lineCount > 650) {
|
|
222
|
+
issues.push({
|
|
223
|
+
file,
|
|
224
|
+
check: 'file-too-large',
|
|
225
|
+
message: `New file has ${lineCount} lines (recommended max: 650). Consider decomposing.`,
|
|
226
|
+
severity: 'warning',
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// 3. JSDoc on exports (new .sys.mjs files only)
|
|
231
|
+
if (isNew && isSysMjs) {
|
|
232
|
+
const lines = content.split('\n');
|
|
233
|
+
for (let i = 0; i < lines.length; i++) {
|
|
234
|
+
const line = lines[i] ?? '';
|
|
235
|
+
if (/^export\s+(function|class|const|let|var)\s/.test(line)) {
|
|
236
|
+
// Walk backwards to find JSDoc
|
|
237
|
+
let hasJsDoc = false;
|
|
238
|
+
for (let j = i - 1; j >= 0; j--) {
|
|
239
|
+
const prev = (lines[j] ?? '').trim();
|
|
240
|
+
if (prev === '')
|
|
241
|
+
continue;
|
|
242
|
+
if (prev.endsWith('*/')) {
|
|
243
|
+
hasJsDoc = true;
|
|
244
|
+
}
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
if (!hasJsDoc) {
|
|
248
|
+
issues.push({
|
|
249
|
+
file,
|
|
250
|
+
check: 'missing-jsdoc',
|
|
251
|
+
message: `Export at line ${i + 1} is missing a JSDoc comment with @param/@returns.`,
|
|
252
|
+
severity: 'warning',
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// 4. Observer topic naming
|
|
259
|
+
const topicPattern = /(?:addObserver|removeObserver|notifyObservers)\s*\([^)]*["']([^"']+)["']/g;
|
|
260
|
+
let topicMatch;
|
|
261
|
+
while ((topicMatch = topicPattern.exec(strippedContent)) !== null) {
|
|
262
|
+
const topic = topicMatch[1];
|
|
263
|
+
if (!topic)
|
|
264
|
+
continue;
|
|
265
|
+
// Only flag topics that contain the binaryName but don't follow convention
|
|
266
|
+
if (topic.toLowerCase().includes(binaryName) && !/^[\w]+-[a-z]+-[a-z]+/.test(topic)) {
|
|
267
|
+
issues.push({
|
|
268
|
+
file,
|
|
269
|
+
check: 'observer-topic-naming',
|
|
270
|
+
message: `Observer topic "${topic}" should follow "${binaryName}-<noun>-<verb>" naming convention.`,
|
|
271
|
+
severity: 'warning',
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return issues;
|
|
277
|
+
}
|
|
278
|
+
// ---------------------------------------------------------------------------
|
|
279
|
+
// Modification comment lint
|
|
280
|
+
// ---------------------------------------------------------------------------
|
|
281
|
+
/**
|
|
282
|
+
* Checks that modifications to existing (non-new) JS/MJS files include at
|
|
283
|
+
* least one `// BINARYNAME:` comment in the added lines.
|
|
284
|
+
*
|
|
285
|
+
* @param diffContent - Raw unified diff string
|
|
286
|
+
* @param config - Project configuration
|
|
287
|
+
* @returns Array of lint issues
|
|
288
|
+
*/
|
|
289
|
+
export function lintModificationComments(diffContent, config) {
|
|
290
|
+
const newFiles = detectNewFilesInDiff(diffContent);
|
|
291
|
+
const addedLines = extractAddedLinesPerFile(diffContent);
|
|
292
|
+
const issues = [];
|
|
293
|
+
const marker = `// ${config.binaryName.toUpperCase()}:`;
|
|
294
|
+
for (const [file, lines] of addedLines) {
|
|
295
|
+
// Only check JS/MJS files that are modifications (not new files)
|
|
296
|
+
if (!isJsFile(file) || newFiles.has(file))
|
|
297
|
+
continue;
|
|
298
|
+
const hasMarker = lines.some((line) => line.toUpperCase().includes(marker.toUpperCase()));
|
|
299
|
+
if (!hasMarker && lines.length > 0) {
|
|
300
|
+
issues.push({
|
|
301
|
+
file,
|
|
302
|
+
check: 'missing-modification-comment',
|
|
303
|
+
message: `Modified upstream file lacks a "${marker}" comment marking your changes.`,
|
|
304
|
+
severity: 'warning',
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return issues;
|
|
309
|
+
}
|
|
310
|
+
// ---------------------------------------------------------------------------
|
|
311
|
+
// Patch size lint (moved from export-shared.ts warnLargePatch)
|
|
312
|
+
// ---------------------------------------------------------------------------
|
|
313
|
+
/**
|
|
314
|
+
* Checks patch size and emits advisory warnings.
|
|
315
|
+
*/
|
|
316
|
+
export function lintPatchSize(filesAffected, lineCount) {
|
|
317
|
+
const issues = [];
|
|
318
|
+
if (filesAffected.length > 5) {
|
|
319
|
+
issues.push({
|
|
320
|
+
file: '(patch)',
|
|
321
|
+
check: 'large-patch-files',
|
|
322
|
+
message: `Patch affects ${filesAffected.length} files (recommended: ≤5). Consider splitting into smaller, focused patches.`,
|
|
323
|
+
severity: 'warning',
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
if (lineCount > 300) {
|
|
327
|
+
issues.push({
|
|
328
|
+
file: '(patch)',
|
|
329
|
+
check: 'large-patch-lines',
|
|
330
|
+
message: `Patch is ${lineCount} lines (recommended: ≤300). Consider splitting into smaller, focused patches.`,
|
|
331
|
+
severity: 'warning',
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
return issues;
|
|
335
|
+
}
|
|
336
|
+
// ---------------------------------------------------------------------------
|
|
337
|
+
// Modified file header lint
|
|
338
|
+
// ---------------------------------------------------------------------------
|
|
339
|
+
/**
|
|
340
|
+
* Checks that modified (non-new) files with a supported extension still
|
|
341
|
+
* start with a recognized license header.
|
|
342
|
+
*
|
|
343
|
+
* @param repoDir - Engine root directory
|
|
344
|
+
* @param affectedFiles - All files affected by the patch
|
|
345
|
+
* @param newFiles - Set of newly created files (excluded from this check)
|
|
346
|
+
* @returns Warning-level lint issues for files missing any recognized header
|
|
347
|
+
*/
|
|
348
|
+
export async function lintModifiedFileHeaders(repoDir, affectedFiles, newFiles) {
|
|
349
|
+
const issues = [];
|
|
350
|
+
for (const file of affectedFiles) {
|
|
351
|
+
if (newFiles.has(file))
|
|
352
|
+
continue;
|
|
353
|
+
const style = commentStyleForFile(file);
|
|
354
|
+
if (!style)
|
|
355
|
+
continue;
|
|
356
|
+
const filePath = join(repoDir, file);
|
|
357
|
+
if (!(await pathExists(filePath)))
|
|
358
|
+
continue;
|
|
359
|
+
const content = await readText(filePath);
|
|
360
|
+
if (!hasAnyLicenseHeader(content, style)) {
|
|
361
|
+
issues.push({
|
|
362
|
+
file,
|
|
363
|
+
check: 'modified-file-missing-header',
|
|
364
|
+
message: 'Modified upstream file appears to be missing a recognized license header.',
|
|
365
|
+
severity: 'warning',
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return issues;
|
|
370
|
+
}
|
|
371
|
+
// ---------------------------------------------------------------------------
|
|
372
|
+
// Orchestrator
|
|
373
|
+
// ---------------------------------------------------------------------------
|
|
374
|
+
/**
|
|
375
|
+
* Runs all patch lint checks and returns combined issues.
|
|
376
|
+
*
|
|
377
|
+
* @param repoDir - Absolute path to the engine directory
|
|
378
|
+
* @param affectedFiles - File paths (relative to repoDir) affected by the patch
|
|
379
|
+
* @param diffContent - Raw unified diff string
|
|
380
|
+
* @param config - Project configuration
|
|
381
|
+
* @returns Array of all lint issues found
|
|
382
|
+
*/
|
|
383
|
+
export async function lintExportedPatch(repoDir, affectedFiles, diffContent, config) {
|
|
384
|
+
const newFiles = detectNewFilesInDiff(diffContent);
|
|
385
|
+
const lineCount = diffContent.split('\n').length;
|
|
386
|
+
const [cssIssues, headerIssues, jsIssues, modifiedHeaderIssues] = await Promise.all([
|
|
387
|
+
lintPatchedCss(repoDir, affectedFiles),
|
|
388
|
+
lintNewFileHeaders(repoDir, [...newFiles], config),
|
|
389
|
+
lintPatchedJs(repoDir, affectedFiles, newFiles, config),
|
|
390
|
+
lintModifiedFileHeaders(repoDir, affectedFiles, newFiles),
|
|
391
|
+
]);
|
|
392
|
+
const modCommentIssues = lintModificationComments(diffContent, config);
|
|
393
|
+
const sizeIssues = lintPatchSize(affectedFiles, lineCount);
|
|
394
|
+
return [
|
|
395
|
+
...sizeIssues,
|
|
396
|
+
...cssIssues,
|
|
397
|
+
...headerIssues,
|
|
398
|
+
...modifiedHeaderIssues,
|
|
399
|
+
...jsIssues,
|
|
400
|
+
...modCommentIssues,
|
|
401
|
+
];
|
|
402
|
+
}
|
|
403
|
+
//# sourceMappingURL=patch-lint.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem-based lock for serializing patch directory mutations.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Runs a patch directory mutation while holding an exclusive filesystem lock.
|
|
6
|
+
* This serializes filename allocation and manifest writes across parallel exports.
|
|
7
|
+
*/
|
|
8
|
+
export declare function withPatchDirectoryLock<T>(patchesDir: string, operation: () => Promise<T>): Promise<T>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Filesystem-based lock for serializing patch directory mutations.
|
|
4
|
+
*/
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { PatchError } from '../errors/patch.js';
|
|
7
|
+
import { toError } from '../utils/errors.js';
|
|
8
|
+
import { withFileLock } from './file-lock.js';
|
|
9
|
+
const PATCH_DIRECTORY_LOCK = '.fireforge-patches.lock';
|
|
10
|
+
/**
|
|
11
|
+
* Runs a patch directory mutation while holding an exclusive filesystem lock.
|
|
12
|
+
* This serializes filename allocation and manifest writes across parallel exports.
|
|
13
|
+
*/
|
|
14
|
+
export async function withPatchDirectoryLock(patchesDir, operation) {
|
|
15
|
+
const lockDir = join(patchesDir, PATCH_DIRECTORY_LOCK);
|
|
16
|
+
return withFileLock(lockDir, operation, {
|
|
17
|
+
onTimeoutMessage: `Timed out waiting for another patch export to finish in ${patchesDir}.\n` +
|
|
18
|
+
`If no other fireforge process is running, the lock may be stale. ` +
|
|
19
|
+
`Remove it manually:\n rm -rf "${lockDir}"`,
|
|
20
|
+
onStaleLockMessage: (ageMs) => `Removing stale patch lock (age: ${Math.round(ageMs / 1000)}s). ` +
|
|
21
|
+
'A previous fireforge process may have crashed.',
|
|
22
|
+
}).catch((error) => {
|
|
23
|
+
if (error instanceof PatchError) {
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
throw new PatchError(toError(error).message);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=patch-lock.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manifest consistency checks and rebuild/recovery operations.
|
|
3
|
+
*/
|
|
4
|
+
import type { PatchesManifest } from '../types/commands/index.js';
|
|
5
|
+
/** Consistency issue codes for manifest validation. */
|
|
6
|
+
export interface PatchManifestConsistencyIssue {
|
|
7
|
+
code: 'manifest-invalid' | 'manifest-missing' | 'missing-patch-file' | 'untracked-patch-file' | 'files-affected-mismatch' | 'duplicate-manifest-entry';
|
|
8
|
+
filename: string;
|
|
9
|
+
message: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Validates that patches.json and the patch directory describe the same patch set.
|
|
13
|
+
* @param patchesDir - Path to the patches directory
|
|
14
|
+
* @returns Consistency issues between manifest metadata and on-disk patch files
|
|
15
|
+
*/
|
|
16
|
+
export declare function validatePatchesManifestConsistency(patchesDir: string): Promise<PatchManifestConsistencyIssue[]>;
|
|
17
|
+
/**
|
|
18
|
+
* Rebuilds patches.json from the patch files currently present on disk.
|
|
19
|
+
* Existing metadata is preserved when possible; missing entries are recovered
|
|
20
|
+
* from filename structure, patch contents, and file mtimes.
|
|
21
|
+
* @param patchesDir - Path to the patches directory
|
|
22
|
+
* @param fallbackSourceEsrVersion - ESR version to use for recovered entries
|
|
23
|
+
*/
|
|
24
|
+
export declare function rebuildPatchesManifest(patchesDir: string, fallbackSourceEsrVersion: string): Promise<PatchesManifest>;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Manifest consistency checks and rebuild/recovery operations.
|
|
4
|
+
*/
|
|
5
|
+
import { stat } from 'node:fs/promises';
|
|
6
|
+
import { discoverPatches, getAllTargetFilesFromPatch } from './patch-files.js';
|
|
7
|
+
import { loadPatchesManifestState, PATCHES_MANIFEST, savePatchesManifest, } from './patch-manifest-io.js';
|
|
8
|
+
import { inferPatchMetadataFromFilename } from './patch-manifest-validate.js';
|
|
9
|
+
/**
|
|
10
|
+
* Validates that patches.json and the patch directory describe the same patch set.
|
|
11
|
+
* @param patchesDir - Path to the patches directory
|
|
12
|
+
* @returns Consistency issues between manifest metadata and on-disk patch files
|
|
13
|
+
*/
|
|
14
|
+
export async function validatePatchesManifestConsistency(patchesDir) {
|
|
15
|
+
const manifestState = await loadPatchesManifestState(patchesDir);
|
|
16
|
+
const patches = await discoverPatches(patchesDir);
|
|
17
|
+
const issues = [];
|
|
18
|
+
if (manifestState.parseError) {
|
|
19
|
+
issues.push({
|
|
20
|
+
code: 'manifest-invalid',
|
|
21
|
+
filename: PATCHES_MANIFEST,
|
|
22
|
+
message: `patches.json exists but could not be parsed: ${manifestState.parseError.message}`,
|
|
23
|
+
});
|
|
24
|
+
return issues;
|
|
25
|
+
}
|
|
26
|
+
if (!manifestState.exists) {
|
|
27
|
+
if (patches.length > 0) {
|
|
28
|
+
issues.push({
|
|
29
|
+
code: 'manifest-missing',
|
|
30
|
+
filename: PATCHES_MANIFEST,
|
|
31
|
+
message: `patches.json is missing while ${patches.length} patch file(s) exist.`,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return issues;
|
|
35
|
+
}
|
|
36
|
+
const manifest = manifestState.manifest;
|
|
37
|
+
if (!manifest) {
|
|
38
|
+
return issues;
|
|
39
|
+
}
|
|
40
|
+
const patchByFilename = new Map(patches.map((patch) => [patch.filename, patch]));
|
|
41
|
+
const seenManifestEntries = new Set();
|
|
42
|
+
for (const metadata of manifest.patches) {
|
|
43
|
+
if (seenManifestEntries.has(metadata.filename)) {
|
|
44
|
+
issues.push({
|
|
45
|
+
code: 'duplicate-manifest-entry',
|
|
46
|
+
filename: metadata.filename,
|
|
47
|
+
message: `patches.json contains duplicate metadata entries for ${metadata.filename}.`,
|
|
48
|
+
});
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
seenManifestEntries.add(metadata.filename);
|
|
52
|
+
const patch = patchByFilename.get(metadata.filename);
|
|
53
|
+
if (!patch) {
|
|
54
|
+
issues.push({
|
|
55
|
+
code: 'missing-patch-file',
|
|
56
|
+
filename: metadata.filename,
|
|
57
|
+
message: `${metadata.filename} is listed in patches.json but the patch file is missing.`,
|
|
58
|
+
});
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const declaredFiles = normalizeFiles(metadata.filesAffected);
|
|
62
|
+
const actualFiles = normalizeFiles(await getAllTargetFilesFromPatch(patch.path));
|
|
63
|
+
if (!sameStringArray(declaredFiles, actualFiles)) {
|
|
64
|
+
issues.push({
|
|
65
|
+
code: 'files-affected-mismatch',
|
|
66
|
+
filename: metadata.filename,
|
|
67
|
+
message: `${metadata.filename} declares [${declaredFiles.join(', ')}] in patches.json ` +
|
|
68
|
+
`but the patch file targets [${actualFiles.join(', ')}].`,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
patchByFilename.delete(metadata.filename);
|
|
72
|
+
}
|
|
73
|
+
for (const orphanPatch of patchByFilename.values()) {
|
|
74
|
+
issues.push({
|
|
75
|
+
code: 'untracked-patch-file',
|
|
76
|
+
filename: orphanPatch.filename,
|
|
77
|
+
message: `${orphanPatch.filename} exists on disk but is not tracked in patches.json.`,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return issues;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Rebuilds patches.json from the patch files currently present on disk.
|
|
84
|
+
* Existing metadata is preserved when possible; missing entries are recovered
|
|
85
|
+
* from filename structure, patch contents, and file mtimes.
|
|
86
|
+
* @param patchesDir - Path to the patches directory
|
|
87
|
+
* @param fallbackSourceEsrVersion - ESR version to use for recovered entries
|
|
88
|
+
*/
|
|
89
|
+
export async function rebuildPatchesManifest(patchesDir, fallbackSourceEsrVersion) {
|
|
90
|
+
const manifestState = await loadPatchesManifestState(patchesDir);
|
|
91
|
+
const existingEntries = new Map();
|
|
92
|
+
if (manifestState.manifest) {
|
|
93
|
+
for (const entry of manifestState.manifest.patches) {
|
|
94
|
+
existingEntries.set(entry.filename, entry);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const patches = await discoverPatches(patchesDir);
|
|
98
|
+
const rebuiltPatches = [];
|
|
99
|
+
const highestFiniteOrder = patches.reduce((highest, patch) => {
|
|
100
|
+
return Number.isFinite(patch.order) ? Math.max(highest, patch.order) : highest;
|
|
101
|
+
}, 0);
|
|
102
|
+
let nextRecoveredOrder = highestFiniteOrder + 1;
|
|
103
|
+
for (const patch of patches) {
|
|
104
|
+
const existing = existingEntries.get(patch.filename);
|
|
105
|
+
const filesAffected = normalizeFiles(await getAllTargetFilesFromPatch(patch.path));
|
|
106
|
+
const patchStats = await stat(patch.path);
|
|
107
|
+
const inferred = inferPatchMetadataFromFilename(patch.filename);
|
|
108
|
+
const recoveredOrder = Number.isFinite(patch.order) ? patch.order : nextRecoveredOrder++;
|
|
109
|
+
rebuiltPatches.push({
|
|
110
|
+
filename: patch.filename,
|
|
111
|
+
order: recoveredOrder,
|
|
112
|
+
category: existing?.category ?? inferred.category,
|
|
113
|
+
name: existing?.name ?? inferred.name,
|
|
114
|
+
description: existing?.description ??
|
|
115
|
+
`Recovered manifest entry for ${patch.filename}. Review description and ESR version.`,
|
|
116
|
+
createdAt: existing?.createdAt ?? new Date(patchStats.mtimeMs).toISOString(),
|
|
117
|
+
sourceEsrVersion: existing?.sourceEsrVersion ?? fallbackSourceEsrVersion,
|
|
118
|
+
filesAffected,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
rebuiltPatches.sort((left, right) => left.order - right.order || left.filename.localeCompare(right.filename));
|
|
122
|
+
const rebuiltManifest = {
|
|
123
|
+
version: 1,
|
|
124
|
+
patches: rebuiltPatches,
|
|
125
|
+
};
|
|
126
|
+
await savePatchesManifest(patchesDir, rebuiltManifest);
|
|
127
|
+
return rebuiltManifest;
|
|
128
|
+
}
|
|
129
|
+
function normalizeFiles(files) {
|
|
130
|
+
return Array.from(new Set(files)).sort((left, right) => left.localeCompare(right));
|
|
131
|
+
}
|
|
132
|
+
function sameStringArray(left, right) {
|
|
133
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=patch-manifest-consistency.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manifest I/O: load, save, and add operations for patches.json.
|
|
3
|
+
*/
|
|
4
|
+
import type { PatchesManifest, PatchMetadata } from '../types/commands/index.js';
|
|
5
|
+
/** Filename for the patches manifest */
|
|
6
|
+
export declare const PATCHES_MANIFEST = "patches.json";
|
|
7
|
+
/** Internal state returned by loadPatchesManifestState. */
|
|
8
|
+
export interface LoadedManifestState {
|
|
9
|
+
exists: boolean;
|
|
10
|
+
manifest: PatchesManifest | null;
|
|
11
|
+
parseError: Error | undefined;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Loads and validates the patches manifest, returning full state information.
|
|
15
|
+
* @param patchesDir - Path to the patches directory
|
|
16
|
+
*/
|
|
17
|
+
export declare function loadPatchesManifestState(patchesDir: string): Promise<LoadedManifestState>;
|
|
18
|
+
/**
|
|
19
|
+
* Loads the patches manifest if it exists.
|
|
20
|
+
* @param patchesDir - Path to the patches directory
|
|
21
|
+
* @returns PatchesManifest or null if not found
|
|
22
|
+
*/
|
|
23
|
+
export declare function loadPatchesManifest(patchesDir: string): Promise<PatchesManifest | null>;
|
|
24
|
+
/**
|
|
25
|
+
* Saves the patches manifest.
|
|
26
|
+
* @param patchesDir - Path to the patches directory
|
|
27
|
+
* @param manifest - Manifest to save
|
|
28
|
+
*/
|
|
29
|
+
export declare function savePatchesManifest(patchesDir: string, manifest: PatchesManifest): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Adds or updates a patch entry in the manifest.
|
|
32
|
+
* @param patchesDir - Path to the patches directory
|
|
33
|
+
* @param metadata - Patch metadata to add/update
|
|
34
|
+
* @param removeFilenames - Optional filenames to remove in the same read-modify-write cycle
|
|
35
|
+
*/
|
|
36
|
+
export declare function addPatchToManifest(patchesDir: string, metadata: PatchMetadata, removeFilenames?: string[]): Promise<void>;
|