@hominis/fireforge 0.30.1 → 0.32.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 +36 -0
- package/README.md +22 -0
- package/dist/src/commands/export-all.js +9 -16
- package/dist/src/commands/export-flow.d.ts +6 -0
- package/dist/src/commands/export-flow.js +6 -1
- package/dist/src/commands/export-placement-gate.d.ts +38 -0
- package/dist/src/commands/export-placement-gate.js +105 -0
- package/dist/src/commands/export-shared.d.ts +28 -0
- package/dist/src/commands/export-shared.js +46 -1
- package/dist/src/commands/export.js +52 -113
- package/dist/src/commands/furnace/chrome-doc-templates.d.ts +0 -13
- package/dist/src/commands/furnace/chrome-doc-templates.js +1 -1
- package/dist/src/commands/furnace/create-dry-run.d.ts +1 -1
- package/dist/src/commands/furnace/create.d.ts +1 -2
- package/dist/src/commands/furnace/deploy.js +36 -114
- package/dist/src/commands/furnace/refresh.js +52 -32
- package/dist/src/commands/furnace/sync.js +2 -0
- package/dist/src/commands/import.js +108 -73
- package/dist/src/commands/lint-per-patch.d.ts +3 -1
- package/dist/src/commands/lint-per-patch.js +265 -74
- package/dist/src/commands/lint.d.ts +1 -58
- package/dist/src/commands/lint.js +193 -88
- package/dist/src/commands/patch/compact.d.ts +5 -2
- package/dist/src/commands/patch/compact.js +85 -25
- package/dist/src/commands/patch/delete.js +17 -17
- package/dist/src/commands/patch/index.js +2 -0
- package/dist/src/commands/patch/lint-ignore.js +3 -16
- package/dist/src/commands/patch/move-files.js +2 -0
- package/dist/src/commands/patch/patch-context.d.ts +41 -0
- package/dist/src/commands/patch/patch-context.js +53 -0
- package/dist/src/commands/patch/rename.js +10 -15
- package/dist/src/commands/patch/reorder.d.ts +0 -2
- package/dist/src/commands/patch/reorder.js +18 -19
- package/dist/src/commands/patch/split-plan.d.ts +66 -0
- package/dist/src/commands/patch/split-plan.js +178 -0
- package/dist/src/commands/patch/split.d.ts +30 -0
- package/dist/src/commands/patch/split.js +283 -0
- package/dist/src/commands/patch/staged-dependency.d.ts +1 -7
- package/dist/src/commands/patch/staged-dependency.js +4 -17
- package/dist/src/commands/patch/tier.js +4 -17
- package/dist/src/commands/re-export-files.js +4 -1
- package/dist/src/commands/re-export-scan.js +8 -1
- package/dist/src/commands/re-export.js +8 -1
- package/dist/src/commands/rebase/summary.d.ts +1 -5
- package/dist/src/commands/rebase/summary.js +1 -1
- package/dist/src/commands/status-output.js +77 -68
- package/dist/src/commands/test-diagnose.d.ts +23 -0
- package/dist/src/commands/test-diagnose.js +210 -0
- package/dist/src/commands/test-run.d.ts +68 -0
- package/dist/src/commands/test-run.js +97 -0
- package/dist/src/commands/test.js +214 -263
- package/dist/src/commands/token.js +15 -1
- package/dist/src/commands/wire.js +109 -78
- package/dist/src/core/build-audit.d.ts +1 -1
- package/dist/src/core/build-audit.js +2 -46
- package/dist/src/core/build-baseline-types.d.ts +38 -0
- package/dist/src/core/build-baseline-types.js +10 -0
- package/dist/src/core/build-baseline.d.ts +1 -31
- package/dist/src/core/build-prepare.d.ts +1 -1
- package/dist/src/core/build-prepare.js +2 -45
- package/dist/src/core/config-paths.d.ts +0 -8
- package/dist/src/core/config-paths.js +4 -4
- package/dist/src/core/config-state.d.ts +0 -6
- package/dist/src/core/config-state.js +1 -1
- package/dist/src/core/config-validate-patch-policy.js +12 -13
- package/dist/src/core/config-validate.js +74 -28
- package/dist/src/core/engine-changes.d.ts +24 -0
- package/dist/src/core/engine-changes.js +64 -0
- package/dist/src/core/firefox-cache.d.ts +0 -5
- package/dist/src/core/firefox-cache.js +1 -1
- package/dist/src/core/firefox-download.d.ts +0 -6
- package/dist/src/core/firefox-download.js +1 -1
- package/dist/src/core/furnace-apply-helpers.d.ts +1 -8
- package/dist/src/core/furnace-apply-helpers.js +11 -20
- package/dist/src/core/furnace-apply.d.ts +1 -1
- package/dist/src/core/furnace-apply.js +1 -1
- package/dist/src/core/furnace-checksum-utils.d.ts +7 -0
- package/dist/src/core/furnace-checksum-utils.js +15 -0
- package/dist/src/core/furnace-config-validate.d.ts +31 -0
- package/dist/src/core/furnace-config-validate.js +133 -0
- package/dist/src/core/furnace-config.d.ts +4 -32
- package/dist/src/core/furnace-config.js +15 -111
- package/dist/src/core/furnace-constants.d.ts +0 -10
- package/dist/src/core/furnace-constants.js +2 -2
- package/dist/src/core/furnace-css-fragments.d.ts +79 -0
- package/dist/src/core/furnace-css-fragments.js +243 -0
- package/dist/src/core/furnace-jsconfig.d.ts +63 -0
- package/dist/src/core/furnace-jsconfig.js +191 -0
- package/dist/src/core/furnace-validate-helpers.d.ts +16 -14
- package/dist/src/core/furnace-validate-helpers.js +40 -1
- package/dist/src/core/furnace-validate-registration.js +16 -1
- package/dist/src/core/furnace-validate.js +54 -2
- package/dist/src/core/git-base.d.ts +15 -0
- package/dist/src/core/git-base.js +32 -0
- package/dist/src/core/git-diff.d.ts +8 -0
- package/dist/src/core/git-diff.js +224 -59
- package/dist/src/core/git-file-ops.d.ts +39 -12
- package/dist/src/core/git-file-ops.js +84 -3
- package/dist/src/core/lint-cache.d.ts +0 -13
- package/dist/src/core/lint-cache.js +5 -5
- package/dist/src/core/mach.d.ts +22 -1
- package/dist/src/core/mach.js +27 -2
- package/dist/src/core/manifest-register.d.ts +5 -16
- package/dist/src/core/manifest-register.js +3 -1
- package/dist/src/core/patch-lint-checkjs.d.ts +75 -21
- package/dist/src/core/patch-lint-checkjs.js +263 -71
- package/dist/src/core/patch-lint-css.d.ts +23 -0
- package/dist/src/core/patch-lint-css.js +172 -0
- package/dist/src/core/patch-lint-jsdoc.js +63 -4
- package/dist/src/core/patch-lint-observer.d.ts +37 -0
- package/dist/src/core/patch-lint-observer.js +168 -0
- package/dist/src/core/patch-lint.d.ts +34 -11
- package/dist/src/core/patch-lint.js +24 -161
- package/dist/src/core/patch-manifest-io.d.ts +16 -0
- package/dist/src/core/patch-manifest-io.js +44 -2
- package/dist/src/core/patch-manifest-validate.d.ts +1 -8
- package/dist/src/core/patch-manifest-validate.js +1 -1
- package/dist/src/core/patch-manifest.d.ts +1 -1
- package/dist/src/core/patch-manifest.js +1 -1
- package/dist/src/core/patch-policy.d.ts +0 -4
- package/dist/src/core/patch-policy.js +10 -4
- package/dist/src/core/register-browser-content.d.ts +1 -1
- package/dist/src/core/register-module.d.ts +1 -1
- package/dist/src/core/register-result.d.ts +21 -0
- package/dist/src/core/register-result.js +9 -0
- package/dist/src/core/register-shared-css.d.ts +1 -1
- package/dist/src/core/register-test-manifest.d.ts +1 -1
- package/dist/src/core/test-harness-crash.d.ts +61 -0
- package/dist/src/core/test-harness-crash.js +140 -0
- package/dist/src/core/test-stale-check.d.ts +1 -1
- package/dist/src/core/test-stale-check.js +2 -46
- package/dist/src/core/test-xpcshell-retry.d.ts +9 -2
- package/dist/src/core/test-xpcshell-retry.js +10 -3
- package/dist/src/core/token-dark-mode.js +14 -26
- package/dist/src/core/token-manager.d.ts +4 -0
- package/dist/src/core/token-manager.js +70 -16
- package/dist/src/core/typecheck-shim.d.ts +3 -22
- package/dist/src/core/typecheck-shim.js +69 -7
- package/dist/src/core/wire-utils.js +37 -44
- package/dist/src/types/commands/index.d.ts +1 -1
- package/dist/src/types/commands/options.d.ts +122 -0
- package/dist/src/types/config.d.ts +11 -2
- package/dist/src/types/furnace.d.ts +12 -1
- package/dist/src/utils/elapsed.d.ts +0 -2
- package/dist/src/utils/elapsed.js +1 -1
- package/dist/src/utils/fs.d.ts +0 -5
- package/dist/src/utils/fs.js +1 -1
- package/dist/src/utils/regex.d.ts +0 -6
- package/dist/src/utils/regex.js +3 -3
- package/dist/src/utils/validation.d.ts +0 -8
- package/dist/src/utils/validation.js +2 -2
- package/package.json +6 -4
|
@@ -7,8 +7,8 @@ import { toError } from '../utils/errors.js';
|
|
|
7
7
|
import { pathExists, readText } from '../utils/fs.js';
|
|
8
8
|
import { verbose } from '../utils/logger.js';
|
|
9
9
|
import { exec } from '../utils/process.js';
|
|
10
|
-
import { ensureGit, git } from './git-base.js';
|
|
11
|
-
import { fileExistsInHead, isBinaryFile } from './git-file-ops.js';
|
|
10
|
+
import { chunkPathspecs, ensureGit, git } from './git-base.js';
|
|
11
|
+
import { fileExistsInHead, hashObjectBatch, isBinaryFile, listTrackedInHead, } from './git-file-ops.js';
|
|
12
12
|
import { getUntrackedFiles, getUntrackedFilesInDir } from './git-status.js';
|
|
13
13
|
async function execGitWithAllowedExitCodes(repoDir, args, allowedExitCodes = [0]) {
|
|
14
14
|
const result = await exec('git', args, { cwd: repoDir });
|
|
@@ -28,35 +28,33 @@ export async function getFileDiff(repoDir, filePath) {
|
|
|
28
28
|
return git(['diff', 'HEAD', '--', filePath], repoDir);
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
31
|
+
* Abbreviates a full git blob hash to the 10-character form used in the
|
|
32
|
+
* synthesized `index 0000000000..<hash>` line, falling back to the all-zero
|
|
33
|
+
* placeholder when no usable hash is available. Mirrors the original per-file
|
|
34
|
+
* truncation exactly.
|
|
35
|
+
* @param fullHash - Full blob hash, or undefined when hashing failed
|
|
36
|
+
* @returns 10-character abbreviated hash or the zero placeholder
|
|
37
|
+
*/
|
|
38
|
+
function abbreviateBlobHash(fullHash) {
|
|
39
|
+
if (fullHash !== undefined && fullHash.length >= 10) {
|
|
40
|
+
return fullHash.slice(0, 10);
|
|
41
|
+
}
|
|
42
|
+
return '0000000000';
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Pure formatter for a new (untracked) file's unified diff. Extracted from
|
|
46
|
+
* {@link generateNewFileDiff} so the batched cold-run path in
|
|
47
|
+
* {@link getDiffForFilesAgainstHead} and the standalone path share one source of
|
|
48
|
+
* truth — the only thing that differs between them is where `blobHash` comes
|
|
49
|
+
* from (a single batched `git hash-object` vs a per-file one), never the
|
|
50
|
+
* formatting. Preserves the empty-file form, the trailing-newline handling, and
|
|
51
|
+
* the "No newline at end of file" marker byte-for-byte.
|
|
33
52
|
* @param filePath - Path to the file (relative to repo)
|
|
53
|
+
* @param content - File content
|
|
54
|
+
* @param blobHash - Abbreviated blob hash for the index line
|
|
34
55
|
* @returns Diff content in unified diff format
|
|
35
56
|
*/
|
|
36
|
-
|
|
37
|
-
const fullPath = join(repoDir, filePath);
|
|
38
|
-
// Defensive check: a directory here means a caller bypassed the
|
|
39
|
-
// expansion layers and handed the leaf reader a path it cannot
|
|
40
|
-
// read. Surface it with an actionable message naming the offending
|
|
41
|
-
// path rather than the raw `EISDIR` that `readText` would throw —
|
|
42
|
-
// recurring bug class (see the belt-and-suspenders note in
|
|
43
|
-
// `getDiffForFilesAgainstHead`).
|
|
44
|
-
const fileStat = await stat(fullPath);
|
|
45
|
-
if (fileStat.isDirectory()) {
|
|
46
|
-
throw new GitError(`expected a file but found a directory at '${filePath}' — caller must expand directory entries before diffing`, `hash-object ${filePath}`);
|
|
47
|
-
}
|
|
48
|
-
const content = await readText(fullPath);
|
|
49
|
-
// Compute the abbreviated git blob hash for the index line
|
|
50
|
-
let blobHash = '0000000000';
|
|
51
|
-
try {
|
|
52
|
-
const fullHash = (await git(['hash-object', fullPath], repoDir)).trim();
|
|
53
|
-
if (fullHash.length >= 10) {
|
|
54
|
-
blobHash = fullHash.slice(0, 10);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
catch (error) {
|
|
58
|
-
verbose(`git hash-object failed for ${filePath}; falling back to zero blob hash: ${toError(error).message}`);
|
|
59
|
-
}
|
|
57
|
+
function buildNewFileDiffBody(filePath, content, blobHash) {
|
|
60
58
|
// Handle empty files
|
|
61
59
|
if (content.length === 0) {
|
|
62
60
|
return [
|
|
@@ -91,6 +89,36 @@ export async function generateNewFileDiff(repoDir, filePath) {
|
|
|
91
89
|
}
|
|
92
90
|
return diffLines.join('\n') + '\n';
|
|
93
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* Generates a unified diff for a new (untracked) file.
|
|
94
|
+
* @param repoDir - Repository directory
|
|
95
|
+
* @param filePath - Path to the file (relative to repo)
|
|
96
|
+
* @returns Diff content in unified diff format
|
|
97
|
+
*/
|
|
98
|
+
export async function generateNewFileDiff(repoDir, filePath) {
|
|
99
|
+
const fullPath = join(repoDir, filePath);
|
|
100
|
+
// Defensive check: a directory here means a caller bypassed the
|
|
101
|
+
// expansion layers and handed the leaf reader a path it cannot
|
|
102
|
+
// read. Surface it with an actionable message naming the offending
|
|
103
|
+
// path rather than the raw `EISDIR` that `readText` would throw —
|
|
104
|
+
// recurring bug class (see the belt-and-suspenders note in
|
|
105
|
+
// `getDiffForFilesAgainstHead`).
|
|
106
|
+
const fileStat = await stat(fullPath);
|
|
107
|
+
if (fileStat.isDirectory()) {
|
|
108
|
+
throw new GitError(`expected a file but found a directory at '${filePath}' — caller must expand directory entries before diffing`, `hash-object ${filePath}`);
|
|
109
|
+
}
|
|
110
|
+
const content = await readText(fullPath);
|
|
111
|
+
// Compute the abbreviated git blob hash for the index line
|
|
112
|
+
let blobHash = '0000000000';
|
|
113
|
+
try {
|
|
114
|
+
const fullHash = (await git(['hash-object', fullPath], repoDir)).trim();
|
|
115
|
+
blobHash = abbreviateBlobHash(fullHash);
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
verbose(`git hash-object failed for ${filePath}; falling back to zero blob hash: ${toError(error).message}`);
|
|
119
|
+
}
|
|
120
|
+
return buildNewFileDiffBody(filePath, content, blobHash);
|
|
121
|
+
}
|
|
94
122
|
/**
|
|
95
123
|
* Generates a patch for a file.
|
|
96
124
|
* If the file is tracked in HEAD, it generates a standard contextual diff.
|
|
@@ -185,10 +213,93 @@ export async function getAllDiff(repoDir) {
|
|
|
185
213
|
const combined = allDiffs.join('');
|
|
186
214
|
return combined.endsWith('\n') ? combined : combined + '\n';
|
|
187
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* Splits a combined `git diff` body into one string per file section,
|
|
218
|
+
* preserving exact bytes. A section runs from a column-0 `diff --git ` line up
|
|
219
|
+
* to (but not including) the next such line. Boundary detection is anchored to
|
|
220
|
+
* column 0 because every diff *body* line is prefixed by a space, `+`, `-`,
|
|
221
|
+
* `\`, or `@`, so a context or added line that merely contains the text
|
|
222
|
+
* `diff --git` can never be mistaken for a header. Paths are deliberately NOT
|
|
223
|
+
* parsed out of the header here — see {@link buildTrackedSections}.
|
|
224
|
+
* @param combined - Combined `git diff` stdout
|
|
225
|
+
* @returns File sections in git's emission order
|
|
226
|
+
*/
|
|
227
|
+
function splitDiffSections(combined) {
|
|
228
|
+
const marker = 'diff --git ';
|
|
229
|
+
const sections = [];
|
|
230
|
+
let start = -1;
|
|
231
|
+
for (let i = 0; i < combined.length; i++) {
|
|
232
|
+
if ((i === 0 || combined[i - 1] === '\n') && combined.startsWith(marker, i)) {
|
|
233
|
+
if (start !== -1)
|
|
234
|
+
sections.push(combined.slice(start, i));
|
|
235
|
+
start = i;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (start !== -1)
|
|
239
|
+
sections.push(combined.slice(start, combined.length));
|
|
240
|
+
return sections;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Runs one `git diff --no-renames HEAD` over the tracked files (chunked under
|
|
244
|
+
* ARG_MAX) and returns a `Map<path, section>` whose sections are byte-identical
|
|
245
|
+
* to the per-file `git diff HEAD -- <file>` they replace.
|
|
246
|
+
*
|
|
247
|
+
* `--no-renames` is load-bearing: a multi-path diff under a user's
|
|
248
|
+
* `diff.renames=true`/`=copies` could otherwise emit a single 2-path rename
|
|
249
|
+
* section (`a/<old> b/<new>`) that a single-path `git diff HEAD -- <file>` can
|
|
250
|
+
* never produce; `--no-renames` re-splits it into the same delete + add bytes
|
|
251
|
+
* the per-file loop emitted.
|
|
252
|
+
*
|
|
253
|
+
* Sections are attributed to paths by POSITION against a companion
|
|
254
|
+
* `git diff --no-renames HEAD -z --name-only` (raw, unquoted, NUL-delimited
|
|
255
|
+
* paths, emitted in the same order as the sections) — never by parsing the
|
|
256
|
+
* `diff --git` header, which is ambiguous or unparseable under `core.quotePath`
|
|
257
|
+
* (non-ASCII paths are C-quoted), paths containing spaces, or
|
|
258
|
+
* `diff.noprefix`/`diff.mnemonicPrefix`. If the section and name counts ever
|
|
259
|
+
* disagree (an unmodeled config), that chunk falls back to per-file
|
|
260
|
+
* {@link getFileDiff} so no file's diff is ever silently dropped.
|
|
261
|
+
* @param repoDir - Repository directory
|
|
262
|
+
* @param trackedFiles - Repo-relative files known to exist in HEAD
|
|
263
|
+
* @returns Map from path to its exact diff section (changed files only)
|
|
264
|
+
*/
|
|
265
|
+
async function buildTrackedSections(repoDir, trackedFiles) {
|
|
266
|
+
const sectionsByPath = new Map();
|
|
267
|
+
for (const chunk of chunkPathspecs(trackedFiles)) {
|
|
268
|
+
const combined = await git(['diff', '--no-renames', 'HEAD', '--', ...chunk], repoDir);
|
|
269
|
+
const namesOutput = await git(['diff', '--no-renames', 'HEAD', '-z', '--name-only', '--', ...chunk], repoDir);
|
|
270
|
+
const names = namesOutput.split('\0').filter((name) => name.length > 0);
|
|
271
|
+
const sections = splitDiffSections(combined);
|
|
272
|
+
if (sections.length === names.length) {
|
|
273
|
+
for (let i = 0; i < names.length; i++) {
|
|
274
|
+
// Exact-path keys (raw git bytes, same encoding as the inputs) — never
|
|
275
|
+
// a substring/startsWith match, so `foo.txt` cannot capture
|
|
276
|
+
// `foo.txt.bak`'s section.
|
|
277
|
+
sectionsByPath.set(names[i], sections[i]);
|
|
278
|
+
}
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
// Counts disagree — recover the exact pre-batch per-file bytes for this
|
|
282
|
+
// chunk rather than risk dropping or mis-keying a section.
|
|
283
|
+
for (const file of chunk) {
|
|
284
|
+
const diff = await getFileDiff(repoDir, file);
|
|
285
|
+
if (diff.trim())
|
|
286
|
+
sectionsByPath.set(file, diff);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return sectionsByPath;
|
|
290
|
+
}
|
|
188
291
|
/**
|
|
189
292
|
* Builds a combined diff against HEAD for the provided files without touching
|
|
190
293
|
* the real git index. Tracked files use `git diff HEAD`; untracked files use
|
|
191
294
|
* synthesized new-file diffs.
|
|
295
|
+
*
|
|
296
|
+
* Performance: the work is batched into a handful of `git` invocations
|
|
297
|
+
* (one `ls-tree` to classify, one `diff` over all tracked files, one
|
|
298
|
+
* `hash-object` over all new text files) rather than the ~2 spawns per file the
|
|
299
|
+
* previous per-file loop issued — that fan-out dominated the cold-run cost on a
|
|
300
|
+
* Firefox-sized checkout (~700 serial spawns, ~99s). Binary, directory, and
|
|
301
|
+
* recursion paths stay per-file because they are rare and (for binary) mutate
|
|
302
|
+
* the index.
|
|
192
303
|
* @param repoDir - Repository directory
|
|
193
304
|
* @param files - File paths to diff (relative to repo root)
|
|
194
305
|
* @returns Combined diff content
|
|
@@ -215,15 +326,24 @@ export async function getDiffForFilesAgainstHead(repoDir, files) {
|
|
|
215
326
|
expandedFiles.push(file);
|
|
216
327
|
}
|
|
217
328
|
const uniqueFiles = [...new Set(expandedFiles)].sort();
|
|
218
|
-
|
|
329
|
+
if (uniqueFiles.length === 0)
|
|
330
|
+
return '';
|
|
331
|
+
// Batch 1: classify tracked-vs-new for the whole set in one `ls-tree` pass,
|
|
332
|
+
// replacing one `fileExistsInHead` spawn per file.
|
|
333
|
+
const tracked = await listTrackedInHead(repoDir, uniqueFiles);
|
|
334
|
+
// Batch 2: one diff over every tracked file, split back to exact per-file
|
|
335
|
+
// sections keyed by path.
|
|
336
|
+
const trackedSections = await buildTrackedSections(repoDir, uniqueFiles.filter((file) => tracked.has(file)));
|
|
337
|
+
// Classify the non-tracked files. Directory and binary entries keep their
|
|
338
|
+
// per-file handling (rare, and binary patches mutate the index so they must
|
|
339
|
+
// stay serial); plain new text files are collected for a single batched
|
|
340
|
+
// `git hash-object`. Results land in `sectionByFile`, keyed by path, to be
|
|
341
|
+
// emitted in sorted order below.
|
|
342
|
+
const sectionByFile = new Map();
|
|
343
|
+
const newTextFiles = [];
|
|
219
344
|
for (const file of uniqueFiles) {
|
|
220
|
-
if (
|
|
221
|
-
const diff = await getFileDiff(repoDir, file);
|
|
222
|
-
if (diff.trim()) {
|
|
223
|
-
diffs.push(diff);
|
|
224
|
-
}
|
|
345
|
+
if (tracked.has(file))
|
|
225
346
|
continue;
|
|
226
|
-
}
|
|
227
347
|
const fullPath = join(repoDir, file);
|
|
228
348
|
if (!(await pathExists(fullPath))) {
|
|
229
349
|
continue;
|
|
@@ -244,17 +364,42 @@ export async function getDiffForFilesAgainstHead(repoDir, files) {
|
|
|
244
364
|
throw new GitError(`'${file}' is a directory with no untracked content (submodule or gitignored?) — cannot diff as a file`, `ls-files --others -- ${file}`);
|
|
245
365
|
}
|
|
246
366
|
const innerDiff = await getDiffForFilesAgainstHead(repoDir, innerFiles);
|
|
247
|
-
if (innerDiff.trim())
|
|
248
|
-
|
|
249
|
-
}
|
|
367
|
+
if (innerDiff.trim())
|
|
368
|
+
sectionByFile.set(file, innerDiff);
|
|
250
369
|
continue;
|
|
251
370
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
371
|
+
if (await isBinaryFile(repoDir, file)) {
|
|
372
|
+
const diff = await generateBinaryFilePatch(repoDir, file);
|
|
373
|
+
if (diff.trim())
|
|
374
|
+
sectionByFile.set(file, diff);
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
newTextFiles.push(file);
|
|
378
|
+
}
|
|
379
|
+
// Batch 3: blob hashes for every new text file in one `git hash-object`.
|
|
380
|
+
// A miss (rare: a path became unreadable after the stat above) falls back to
|
|
381
|
+
// the zero hash with the same verbose log the per-file path emitted.
|
|
382
|
+
const blobHashes = await hashObjectBatch(repoDir, newTextFiles.map((file) => join(repoDir, file)));
|
|
383
|
+
for (const file of newTextFiles) {
|
|
384
|
+
const fullPath = join(repoDir, file);
|
|
385
|
+
const fullHash = blobHashes.get(fullPath);
|
|
386
|
+
if (fullHash === undefined) {
|
|
387
|
+
verbose(`git hash-object failed for ${file}; falling back to zero blob hash`);
|
|
257
388
|
}
|
|
389
|
+
const body = buildNewFileDiffBody(file, await readText(fullPath), abbreviateBlobHash(fullHash));
|
|
390
|
+
if (body.trim())
|
|
391
|
+
sectionByFile.set(file, body);
|
|
392
|
+
}
|
|
393
|
+
// Reassemble in the sorted `uniqueFiles` order — NOT git's section order,
|
|
394
|
+
// which uses git's own collation and diverges from JS `.sort()` for
|
|
395
|
+
// non-ASCII paths. Driving emission off `uniqueFiles` (as the previous
|
|
396
|
+
// per-file loop did) keeps the combined output byte-identical. Do not change
|
|
397
|
+
// this to push sections in git's emission order.
|
|
398
|
+
const diffs = [];
|
|
399
|
+
for (const file of uniqueFiles) {
|
|
400
|
+
const section = trackedSections.get(file) ?? sectionByFile.get(file);
|
|
401
|
+
if (section && section.trim())
|
|
402
|
+
diffs.push(section);
|
|
258
403
|
}
|
|
259
404
|
if (diffs.length === 0) {
|
|
260
405
|
return '';
|
|
@@ -275,6 +420,22 @@ export async function getStagedDiffForFiles(repoDir, files) {
|
|
|
275
420
|
await ensureGit();
|
|
276
421
|
return git(['diff', '--cached', 'HEAD', '--', ...files], repoDir);
|
|
277
422
|
}
|
|
423
|
+
/**
|
|
424
|
+
* Serializes the index-mutating untracked-binary path. The bounded per-patch
|
|
425
|
+
* lint pool can call {@link getDiffForFilesAgainstHead} (and thus this) for
|
|
426
|
+
* several patches at once; two concurrent `git add --intent-to-add` / `git
|
|
427
|
+
* reset` sequences would collide on `.git/index.lock` (a hard failure) or
|
|
428
|
+
* interleave one file's stage with another's unstage. This process-level
|
|
429
|
+
* promise chain runs the staging sequences one at a time. Read-only callers
|
|
430
|
+
* (`git diff --binary HEAD`) do not need it. Binary patches are rare, so the
|
|
431
|
+
* serialization cost is negligible.
|
|
432
|
+
*/
|
|
433
|
+
let binaryStagingLock = Promise.resolve();
|
|
434
|
+
function runWithBinaryStagingLock(task) {
|
|
435
|
+
const result = binaryStagingLock.then(task, task);
|
|
436
|
+
binaryStagingLock = result.then(() => undefined, () => undefined);
|
|
437
|
+
return result;
|
|
438
|
+
}
|
|
278
439
|
/**
|
|
279
440
|
* Generates a GIT binary patch for a binary file.
|
|
280
441
|
* For tracked files, uses `git diff --binary HEAD`.
|
|
@@ -285,7 +446,7 @@ export async function getStagedDiffForFiles(repoDir, files) {
|
|
|
285
446
|
*/
|
|
286
447
|
export async function generateBinaryFilePatch(repoDir, filePath) {
|
|
287
448
|
await ensureGit();
|
|
288
|
-
// Try tracked file diff first
|
|
449
|
+
// Try tracked file diff first (read-only — no index lock needed)
|
|
289
450
|
const result = await execGitWithAllowedExitCodes(repoDir, [
|
|
290
451
|
'diff',
|
|
291
452
|
'--binary',
|
|
@@ -295,20 +456,24 @@ export async function generateBinaryFilePatch(repoDir, filePath) {
|
|
|
295
456
|
]);
|
|
296
457
|
if (result.stdout.trim())
|
|
297
458
|
return result.stdout;
|
|
298
|
-
// For untracked files, stage temporarily to produce a binary diff
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
'--
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
459
|
+
// For untracked files, stage temporarily to produce a binary diff. The
|
|
460
|
+
// stage/unstage pair mutates the index, so it must not interleave with
|
|
461
|
+
// another concurrent binary patch (see runWithBinaryStagingLock).
|
|
462
|
+
return runWithBinaryStagingLock(async () => {
|
|
463
|
+
try {
|
|
464
|
+
await execGitWithAllowedExitCodes(repoDir, ['add', '--intent-to-add', '--', filePath]);
|
|
465
|
+
const diffResult = await execGitWithAllowedExitCodes(repoDir, [
|
|
466
|
+
'diff',
|
|
467
|
+
'--binary',
|
|
468
|
+
'--',
|
|
469
|
+
filePath,
|
|
470
|
+
]);
|
|
471
|
+
return diffResult.stdout;
|
|
472
|
+
}
|
|
473
|
+
finally {
|
|
474
|
+
// Always unstage, even if diff fails
|
|
475
|
+
await execGitWithAllowedExitCodes(repoDir, ['reset', 'HEAD', '--', filePath]);
|
|
476
|
+
}
|
|
477
|
+
});
|
|
313
478
|
}
|
|
314
479
|
//# sourceMappingURL=git-diff.js.map
|
|
@@ -5,18 +5,6 @@ import type { GitStatusEntry } from './git-base.js';
|
|
|
5
5
|
* @param filePath - Path to the file (relative to repo)
|
|
6
6
|
*/
|
|
7
7
|
export declare function restoreTrackedPath(repoDir: string, filePath: string): Promise<void>;
|
|
8
|
-
/**
|
|
9
|
-
* Removes an untracked path from disk.
|
|
10
|
-
* @param repoDir - Repository directory
|
|
11
|
-
* @param filePath - Path to the file (relative to repo)
|
|
12
|
-
*/
|
|
13
|
-
export declare function removeUntrackedPath(repoDir: string, filePath: string): Promise<void>;
|
|
14
|
-
/**
|
|
15
|
-
* Removes a path that is present only in the index/worktree and not in HEAD.
|
|
16
|
-
* @param repoDir - Repository directory
|
|
17
|
-
* @param filePath - Path to remove
|
|
18
|
-
*/
|
|
19
|
-
export declare function removeAddedPath(repoDir: string, filePath: string): Promise<void>;
|
|
20
8
|
/**
|
|
21
9
|
* Discards a status entry according to its git state.
|
|
22
10
|
* @param repoDir - Repository directory
|
|
@@ -42,6 +30,45 @@ export declare function unstageFiles(repoDir: string, files: string[]): Promise<
|
|
|
42
30
|
* @returns true if file exists in HEAD
|
|
43
31
|
*/
|
|
44
32
|
export declare function fileExistsInHead(repoDir: string, filePath: string): Promise<boolean>;
|
|
33
|
+
/**
|
|
34
|
+
* Batched equivalent of {@link fileExistsInHead}: returns the subset of `files`
|
|
35
|
+
* that are tracked in HEAD, using a single `git ls-tree` per ARG_MAX chunk
|
|
36
|
+
* instead of one spawn per file. This is the cold-run hot path — a Firefox-sized
|
|
37
|
+
* checkout has hundreds of affected files, and the old per-file fan-out spent
|
|
38
|
+
* ~99s in serial `git ls-tree`/`git diff` spawns.
|
|
39
|
+
*
|
|
40
|
+
* `-r` lists nested blobs by full repo-relative path; `--name-only -z` makes the
|
|
41
|
+
* output a trivial NUL-split with no quoting to undo. Membership in the returned
|
|
42
|
+
* Set is exactly `await fileExistsInHead(repoDir, file)` for any non-directory
|
|
43
|
+
* `file`. Throws (via {@link git}) when HEAD itself is unresolvable, matching the
|
|
44
|
+
* per-file helper's failure mode.
|
|
45
|
+
* @param repoDir - Repository directory
|
|
46
|
+
* @param files - Repo-relative paths to classify
|
|
47
|
+
* @returns The subset of `files` present in HEAD
|
|
48
|
+
*/
|
|
49
|
+
export declare function listTrackedInHead(repoDir: string, files: string[]): Promise<Set<string>>;
|
|
50
|
+
/**
|
|
51
|
+
* Batched equivalent of the per-file `git hash-object` in
|
|
52
|
+
* {@link import('./git-diff.js').generateNewFileDiff}: computes the git blob
|
|
53
|
+
* hash for every path in one spawn per ARG_MAX chunk and returns a
|
|
54
|
+
* `Map<fullPath, fullHash>`.
|
|
55
|
+
*
|
|
56
|
+
* Uses {@link import('../utils/process.js').exec} rather than {@link git}
|
|
57
|
+
* (which throws on a non-zero exit) because `git hash-object f1 f2 …` is
|
|
58
|
+
* all-or-nothing: it aborts at the first unreadable path and emits nothing for
|
|
59
|
+
* the rest. To preserve the old per-file contract — where one bad path zeroed
|
|
60
|
+
* only its own index line — a chunk that does not return exactly one hash per
|
|
61
|
+
* input falls back to hashing that chunk's paths individually. A path that is
|
|
62
|
+
* still unhashable is simply left out of the map; the caller applies the
|
|
63
|
+
* `0000000000` zero-hash fallback (and the same verbose log) for any miss, so
|
|
64
|
+
* the blob hash is byte-identical to `git hash-object` (filters/.gitattributes
|
|
65
|
+
* are applied per path) without the risk of in-process hashing, which would
|
|
66
|
+
* diverge under `core.autocrlf`/`text` attributes.
|
|
67
|
+
* @param repoDir - Repository directory
|
|
68
|
+
* @param fullPaths - Absolute file paths to hash
|
|
69
|
+
* @returns Map from each input path to its full blob hash (misses omitted)
|
|
70
|
+
*/
|
|
71
|
+
export declare function hashObjectBatch(repoDir: string, fullPaths: string[]): Promise<Map<string, string>>;
|
|
45
72
|
/**
|
|
46
73
|
* Gets the content of a file at a specific git ref (HEAD by default).
|
|
47
74
|
* @param repoDir - Repository directory
|
|
@@ -4,7 +4,7 @@ import { join } from 'node:path';
|
|
|
4
4
|
import { GitError } from '../errors/git.js';
|
|
5
5
|
import { removeFile } from '../utils/fs.js';
|
|
6
6
|
import { exec } from '../utils/process.js';
|
|
7
|
-
import { ensureGit, git } from './git-base.js';
|
|
7
|
+
import { chunkPathspecs, ensureGit, git } from './git-base.js';
|
|
8
8
|
/**
|
|
9
9
|
* Restores a tracked path from HEAD, including staged changes.
|
|
10
10
|
* @param repoDir - Repository directory
|
|
@@ -19,7 +19,7 @@ export async function restoreTrackedPath(repoDir, filePath) {
|
|
|
19
19
|
* @param repoDir - Repository directory
|
|
20
20
|
* @param filePath - Path to the file (relative to repo)
|
|
21
21
|
*/
|
|
22
|
-
|
|
22
|
+
async function removeUntrackedPath(repoDir, filePath) {
|
|
23
23
|
const fullPath = join(repoDir, filePath);
|
|
24
24
|
await removeFile(fullPath);
|
|
25
25
|
}
|
|
@@ -28,7 +28,7 @@ export async function removeUntrackedPath(repoDir, filePath) {
|
|
|
28
28
|
* @param repoDir - Repository directory
|
|
29
29
|
* @param filePath - Path to remove
|
|
30
30
|
*/
|
|
31
|
-
|
|
31
|
+
async function removeAddedPath(repoDir, filePath) {
|
|
32
32
|
await ensureGit();
|
|
33
33
|
await git(['reset', 'HEAD', '--', filePath], repoDir);
|
|
34
34
|
await removeUntrackedPath(repoDir, filePath);
|
|
@@ -87,6 +87,87 @@ export async function fileExistsInHead(repoDir, filePath) {
|
|
|
87
87
|
await ensureGit();
|
|
88
88
|
return (await git(['ls-tree', 'HEAD', '--', filePath], repoDir)).trim().length > 0;
|
|
89
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Batched equivalent of {@link fileExistsInHead}: returns the subset of `files`
|
|
92
|
+
* that are tracked in HEAD, using a single `git ls-tree` per ARG_MAX chunk
|
|
93
|
+
* instead of one spawn per file. This is the cold-run hot path — a Firefox-sized
|
|
94
|
+
* checkout has hundreds of affected files, and the old per-file fan-out spent
|
|
95
|
+
* ~99s in serial `git ls-tree`/`git diff` spawns.
|
|
96
|
+
*
|
|
97
|
+
* `-r` lists nested blobs by full repo-relative path; `--name-only -z` makes the
|
|
98
|
+
* output a trivial NUL-split with no quoting to undo. Membership in the returned
|
|
99
|
+
* Set is exactly `await fileExistsInHead(repoDir, file)` for any non-directory
|
|
100
|
+
* `file`. Throws (via {@link git}) when HEAD itself is unresolvable, matching the
|
|
101
|
+
* per-file helper's failure mode.
|
|
102
|
+
* @param repoDir - Repository directory
|
|
103
|
+
* @param files - Repo-relative paths to classify
|
|
104
|
+
* @returns The subset of `files` present in HEAD
|
|
105
|
+
*/
|
|
106
|
+
export async function listTrackedInHead(repoDir, files) {
|
|
107
|
+
const tracked = new Set();
|
|
108
|
+
if (files.length === 0)
|
|
109
|
+
return tracked;
|
|
110
|
+
await ensureGit();
|
|
111
|
+
const wanted = new Set(files);
|
|
112
|
+
for (const chunk of chunkPathspecs(files)) {
|
|
113
|
+
const output = await git(['ls-tree', '-r', 'HEAD', '--name-only', '-z', '--', ...chunk], repoDir);
|
|
114
|
+
for (const name of output.split('\0')) {
|
|
115
|
+
// `ls-tree -r` can surface entries beyond the literal inputs only when an
|
|
116
|
+
// input is itself a directory in HEAD; intersect with `wanted` so the
|
|
117
|
+
// result is always a subset of the requested files, never a superset.
|
|
118
|
+
if (name.length > 0 && wanted.has(name))
|
|
119
|
+
tracked.add(name);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return tracked;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Batched equivalent of the per-file `git hash-object` in
|
|
126
|
+
* {@link import('./git-diff.js').generateNewFileDiff}: computes the git blob
|
|
127
|
+
* hash for every path in one spawn per ARG_MAX chunk and returns a
|
|
128
|
+
* `Map<fullPath, fullHash>`.
|
|
129
|
+
*
|
|
130
|
+
* Uses {@link import('../utils/process.js').exec} rather than {@link git}
|
|
131
|
+
* (which throws on a non-zero exit) because `git hash-object f1 f2 …` is
|
|
132
|
+
* all-or-nothing: it aborts at the first unreadable path and emits nothing for
|
|
133
|
+
* the rest. To preserve the old per-file contract — where one bad path zeroed
|
|
134
|
+
* only its own index line — a chunk that does not return exactly one hash per
|
|
135
|
+
* input falls back to hashing that chunk's paths individually. A path that is
|
|
136
|
+
* still unhashable is simply left out of the map; the caller applies the
|
|
137
|
+
* `0000000000` zero-hash fallback (and the same verbose log) for any miss, so
|
|
138
|
+
* the blob hash is byte-identical to `git hash-object` (filters/.gitattributes
|
|
139
|
+
* are applied per path) without the risk of in-process hashing, which would
|
|
140
|
+
* diverge under `core.autocrlf`/`text` attributes.
|
|
141
|
+
* @param repoDir - Repository directory
|
|
142
|
+
* @param fullPaths - Absolute file paths to hash
|
|
143
|
+
* @returns Map from each input path to its full blob hash (misses omitted)
|
|
144
|
+
*/
|
|
145
|
+
export async function hashObjectBatch(repoDir, fullPaths) {
|
|
146
|
+
const hashes = new Map();
|
|
147
|
+
if (fullPaths.length === 0)
|
|
148
|
+
return hashes;
|
|
149
|
+
await ensureGit();
|
|
150
|
+
for (const chunk of chunkPathspecs(fullPaths)) {
|
|
151
|
+
const result = await exec('git', ['hash-object', '--', ...chunk], { cwd: repoDir });
|
|
152
|
+
const lines = result.stdout.split('\n').filter((line) => line.length > 0);
|
|
153
|
+
if (result.exitCode === 0 && lines.length === chunk.length) {
|
|
154
|
+
for (let i = 0; i < chunk.length; i++) {
|
|
155
|
+
hashes.set(chunk[i], lines[i]);
|
|
156
|
+
}
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
// Batch aborted partway (a path became unreadable between the caller's stat
|
|
160
|
+
// and here, or otherwise failed). Recover per file so one bad path only
|
|
161
|
+
// loses its own hash, exactly as the pre-batch per-file code behaved.
|
|
162
|
+
for (const path of chunk) {
|
|
163
|
+
const single = await exec('git', ['hash-object', '--', path], { cwd: repoDir });
|
|
164
|
+
const hash = single.stdout.trim();
|
|
165
|
+
if (single.exitCode === 0 && hash.length > 0)
|
|
166
|
+
hashes.set(path, hash);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return hashes;
|
|
170
|
+
}
|
|
90
171
|
/**
|
|
91
172
|
* Gets the content of a file at a specific git ref (HEAD by default).
|
|
92
173
|
* @param repoDir - Repository directory
|
|
@@ -2,7 +2,6 @@ import type { PatchLintIssue, PatchMetadata } from '../types/commands/index.js';
|
|
|
2
2
|
import type { FireForgeConfig } from '../types/config.js';
|
|
3
3
|
import { type PatchQueueContext } from './patch-lint.js';
|
|
4
4
|
export declare const LINT_CACHE_SCHEMA_VERSION = 1;
|
|
5
|
-
export declare const LINT_IMPLEMENTATION_VERSION = 1;
|
|
6
5
|
export interface PerPatchLintCacheEntry {
|
|
7
6
|
key: string;
|
|
8
7
|
patchFilename: string;
|
|
@@ -24,15 +23,6 @@ export interface PerPatchLintCacheKeyInput {
|
|
|
24
23
|
engineHeadSha?: string;
|
|
25
24
|
packageVersion?: string;
|
|
26
25
|
}
|
|
27
|
-
type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
28
|
-
[key: string]: JsonValue | undefined;
|
|
29
|
-
};
|
|
30
|
-
/** Computes a SHA-256 hex digest for text or binary content. */
|
|
31
|
-
export declare function sha256Hex(content: string | Buffer): string;
|
|
32
|
-
/** Computes a stable SHA-256 digest for JSON-compatible data. */
|
|
33
|
-
export declare function stableHash(value: JsonValue): string;
|
|
34
|
-
/** Returns the repo-local per-patch lint cache file path. */
|
|
35
|
-
export declare function getPerPatchLintCachePath(projectRoot: string): string;
|
|
36
26
|
/** Returns the engine git HEAD identity used to guard diff-derived cache hits. */
|
|
37
27
|
export declare function getPerPatchLintCacheHeadSha(engineDir: string): Promise<string>;
|
|
38
28
|
/**
|
|
@@ -40,8 +30,6 @@ export declare function getPerPatchLintCacheHeadSha(engineDir: string): Promise<
|
|
|
40
30
|
* The key includes source, metadata, config, engine state, and ownership inputs.
|
|
41
31
|
*/
|
|
42
32
|
export declare function buildPerPatchLintCacheKey(input: PerPatchLintCacheKeyInput): Promise<string>;
|
|
43
|
-
/** Creates an empty cache document using the current cache schema. */
|
|
44
|
-
export declare function createEmptyPerPatchLintCache(): PerPatchLintCacheFile;
|
|
45
33
|
/** Loads the per-patch lint cache, treating missing or invalid files as empty. */
|
|
46
34
|
export declare function loadPerPatchLintCache(projectRoot: string): Promise<PerPatchLintCacheFile>;
|
|
47
35
|
/** Persists the per-patch lint cache atomically through the shared JSON writer. */
|
|
@@ -52,4 +40,3 @@ export declare function clearPerPatchLintCache(projectRoot: string): Promise<voi
|
|
|
52
40
|
export declare function getCachedPerPatchLintIssues(cache: PerPatchLintCacheFile, patchFilename: string, key: string): PatchLintIssue[] | undefined;
|
|
53
41
|
/** Stores per-patch lint issues after a successful lint calculation. */
|
|
54
42
|
export declare function setCachedPerPatchLintIssues(cache: PerPatchLintCacheFile, patchFilename: string, key: string, issues: PatchLintIssue[]): void;
|
|
55
|
-
export {};
|
|
@@ -8,7 +8,7 @@ import { getFurnacePaths } from './furnace-config.js';
|
|
|
8
8
|
import { git } from './git-base.js';
|
|
9
9
|
import { collectNewFileCreatorsByPath } from './patch-lint.js';
|
|
10
10
|
export const LINT_CACHE_SCHEMA_VERSION = 1;
|
|
11
|
-
|
|
11
|
+
const LINT_IMPLEMENTATION_VERSION = 1;
|
|
12
12
|
const LINT_CACHE_DIRNAME = 'lint-cache';
|
|
13
13
|
const PER_PATCH_CACHE_FILENAME = 'per-patch-v1.json';
|
|
14
14
|
function stableJson(value) {
|
|
@@ -26,15 +26,15 @@ function stableJson(value) {
|
|
|
26
26
|
.join(',')}}`;
|
|
27
27
|
}
|
|
28
28
|
/** Computes a SHA-256 hex digest for text or binary content. */
|
|
29
|
-
|
|
29
|
+
function sha256Hex(content) {
|
|
30
30
|
return createHash('sha256').update(content).digest('hex');
|
|
31
31
|
}
|
|
32
32
|
/** Computes a stable SHA-256 digest for JSON-compatible data. */
|
|
33
|
-
|
|
33
|
+
function stableHash(value) {
|
|
34
34
|
return sha256Hex(stableJson(value));
|
|
35
35
|
}
|
|
36
36
|
/** Returns the repo-local per-patch lint cache file path. */
|
|
37
|
-
|
|
37
|
+
function getPerPatchLintCachePath(projectRoot) {
|
|
38
38
|
return join(projectRoot, '.fireforge', LINT_CACHE_DIRNAME, PER_PATCH_CACHE_FILENAME);
|
|
39
39
|
}
|
|
40
40
|
/** Returns the engine git HEAD identity used to guard diff-derived cache hits. */
|
|
@@ -108,7 +108,7 @@ export async function buildPerPatchLintCacheKey(input) {
|
|
|
108
108
|
});
|
|
109
109
|
}
|
|
110
110
|
/** Creates an empty cache document using the current cache schema. */
|
|
111
|
-
|
|
111
|
+
function createEmptyPerPatchLintCache() {
|
|
112
112
|
return { schemaVersion: LINT_CACHE_SCHEMA_VERSION, entries: {} };
|
|
113
113
|
}
|
|
114
114
|
function isCacheEntry(value) {
|
package/dist/src/core/mach.d.ts
CHANGED
|
@@ -193,5 +193,26 @@ export declare function watchWithOutput(engineDir: string, options?: {
|
|
|
193
193
|
export declare function test(engineDir: string, testPaths?: string[], args?: string[]): Promise<number>;
|
|
194
194
|
/**
|
|
195
195
|
* Runs mach test while capturing streamed output for better diagnostics.
|
|
196
|
+
*
|
|
197
|
+
* @param env - Optional extra environment variables for the mach process
|
|
198
|
+
* (merged over `process.env` by the exec layer). Used by
|
|
199
|
+
* `fireforge test --perf-samples` to publish the artifact-path contract.
|
|
200
|
+
*/
|
|
201
|
+
export declare function testWithOutput(engineDir: string, testPaths?: string[], args?: string[], env?: Record<string, string>): Promise<MachCommandResult>;
|
|
202
|
+
/**
|
|
203
|
+
* Runs `mach xpcshell-test` (the suite-specific xpcshell command) while
|
|
204
|
+
* capturing output. Unlike the generic `mach test`, the suite-specific
|
|
205
|
+
* commands degrade a broken mozlog resource monitor to a warning instead of
|
|
206
|
+
* crashing at startup, so `fireforge test` dispatches single-suite runs here
|
|
207
|
+
* to stay resilient to the host psutil failure (field report E1).
|
|
208
|
+
*
|
|
209
|
+
* Signature mirrors {@link testWithOutput} so the two are interchangeable in
|
|
210
|
+
* the dispatch path.
|
|
211
|
+
*/
|
|
212
|
+
export declare function xpcshellTestWithOutput(engineDir: string, testPaths?: string[], args?: string[], env?: Record<string, string>): Promise<MachCommandResult>;
|
|
213
|
+
/**
|
|
214
|
+
* Runs `mach mochitest` (covers browser-chrome / mochitest flavors) while
|
|
215
|
+
* capturing output. The suite-specific counterpart to {@link testWithOutput}
|
|
216
|
+
* for non-xpcshell single-suite runs — see {@link xpcshellTestWithOutput}.
|
|
196
217
|
*/
|
|
197
|
-
export declare function
|
|
218
|
+
export declare function mochitestWithOutput(engineDir: string, testPaths?: string[], args?: string[], env?: Record<string, string>): Promise<MachCommandResult>;
|