@hominis/fireforge 0.41.0 → 0.43.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 +81 -34
- package/dist/build-info.json +4 -4
- package/dist/src/cli.js +21 -2
- package/dist/src/commands/build.js +7 -1
- package/dist/src/commands/config.js +16 -27
- package/dist/src/commands/discard.js +1 -1
- package/dist/src/commands/export-flow.js +13 -3
- package/dist/src/commands/export-placement-conflicts.js +1 -1
- package/dist/src/commands/export-shared.d.ts +1 -1
- package/dist/src/commands/export-shared.js +4 -5
- package/dist/src/commands/furnace/chrome-doc-remove.js +3 -0
- package/dist/src/commands/furnace/chrome-doc.js +3 -0
- package/dist/src/commands/furnace/create.js +3 -0
- package/dist/src/commands/furnace/override.js +3 -0
- package/dist/src/commands/furnace/preview.js +3 -0
- package/dist/src/commands/furnace/refresh.js +3 -0
- package/dist/src/commands/furnace/remove.js +3 -0
- package/dist/src/commands/furnace/rename.js +3 -0
- package/dist/src/commands/furnace/scan.js +3 -0
- package/dist/src/commands/import.js +1 -1
- package/dist/src/commands/lint-per-patch.js +16 -8
- package/dist/src/commands/lint-per-run-checkjs.d.ts +4 -4
- package/dist/src/commands/lint-per-run-checkjs.js +4 -4
- package/dist/src/commands/lint-report.js +1 -1
- package/dist/src/commands/lint.js +2 -2
- package/dist/src/commands/patch/lint-ignore.js +1 -1
- package/dist/src/commands/patch/move-files-create.js +5 -3
- package/dist/src/commands/patch/move-files-into.d.ts +1 -1
- package/dist/src/commands/patch/move-files-into.js +5 -3
- package/dist/src/commands/patch/move-files.js +1 -1
- package/dist/src/commands/patch/rename.js +1 -1
- package/dist/src/commands/patch/reorder.d.ts +1 -2
- package/dist/src/commands/patch/reorder.js +1 -2
- package/dist/src/commands/patch/split.js +5 -3
- package/dist/src/commands/patch/staged-dependency-validate.js +1 -1
- package/dist/src/commands/patch/staged-dependency.js +4 -4
- package/dist/src/commands/re-export-adjacent.js +1 -1
- package/dist/src/commands/re-export-bulk-scan.d.ts +1 -1
- package/dist/src/commands/re-export-bulk-scan.js +5 -4
- package/dist/src/commands/re-export-drift.d.ts +4 -4
- package/dist/src/commands/re-export-drift.js +47 -8
- package/dist/src/commands/re-export-lint.d.ts +7 -5
- package/dist/src/commands/re-export-lint.js +9 -8
- package/dist/src/commands/re-export-single.d.ts +1 -1
- package/dist/src/commands/re-export-single.js +5 -4
- package/dist/src/commands/re-export.js +26 -14
- package/dist/src/commands/reset.js +1 -1
- package/dist/src/commands/run.js +1 -1
- package/dist/src/commands/source.js +6 -6
- package/dist/src/commands/status-check.d.ts +1 -1
- package/dist/src/commands/status-check.js +1 -1
- package/dist/src/commands/status-json.d.ts +2 -2
- package/dist/src/commands/status-json.js +1 -1
- package/dist/src/commands/status-output.d.ts +1 -1
- package/dist/src/commands/status-output.js +1 -1
- package/dist/src/commands/status.js +21 -7
- package/dist/src/commands/test-build-phase.d.ts +2 -2
- package/dist/src/commands/test-build-phase.js +9 -6
- package/dist/src/commands/test-diagnose.js +8 -1
- package/dist/src/commands/test-modes.d.ts +2 -2
- package/dist/src/commands/test-modes.js +3 -3
- package/dist/src/commands/test-register.js +7 -2
- package/dist/src/commands/test-run.d.ts +6 -0
- package/dist/src/commands/test-run.js +32 -3
- package/dist/src/commands/test-stale-gate.js +7 -2
- package/dist/src/commands/test.js +4 -4
- package/dist/src/commands/tree.js +2 -2
- package/dist/src/commands/typecheck.d.ts +2 -1
- package/dist/src/commands/typecheck.js +59 -3
- package/dist/src/commands/verify.js +26 -1
- package/dist/src/core/ast-utils.js +14 -2
- package/dist/src/core/branding.d.ts +16 -0
- package/dist/src/core/branding.js +18 -2
- package/dist/src/core/browser-wire.js +9 -1
- package/dist/src/core/build-baseline-types.d.ts +1 -1
- package/dist/src/core/build-baseline.d.ts +1 -1
- package/dist/src/core/build-baseline.js +1 -1
- package/dist/src/core/build-overwrite-guard.d.ts +49 -0
- package/dist/src/core/build-overwrite-guard.js +112 -0
- package/dist/src/core/build-prepare.d.ts +13 -5
- package/dist/src/core/build-prepare.js +42 -9
- package/dist/src/core/config-mutate.d.ts +3 -2
- package/dist/src/core/config-mutate.js +5 -2
- package/dist/src/core/config.d.ts +3 -2
- package/dist/src/core/config.js +2 -0
- package/dist/src/core/coverage-extend.d.ts +1 -1
- package/dist/src/core/coverage-extend.js +1 -1
- package/dist/src/core/destructive.d.ts +23 -0
- package/dist/src/core/destructive.js +45 -4
- package/dist/src/core/diff-hunks.js +31 -17
- package/dist/src/core/discard-baseline.d.ts +1 -1
- package/dist/src/core/discard-baseline.js +1 -1
- package/dist/src/core/display-state.d.ts +52 -0
- package/dist/src/core/display-state.js +89 -0
- package/dist/src/core/engine-session-lock.d.ts +20 -0
- package/dist/src/core/engine-session-lock.js +84 -14
- package/dist/src/core/export-moved-code-shape.d.ts +60 -0
- package/dist/src/core/export-moved-code-shape.js +129 -0
- package/dist/src/core/file-lock.d.ts +44 -0
- package/dist/src/core/file-lock.js +157 -42
- package/dist/src/core/furnace-apply-helpers.d.ts +1 -1
- package/dist/src/core/furnace-apply-helpers.js +1 -1
- package/dist/src/core/furnace-apply-output.js +1 -1
- package/dist/src/core/furnace-apply-overwrite-warn.d.ts +2 -2
- package/dist/src/core/furnace-apply-overwrite-warn.js +2 -2
- package/dist/src/core/furnace-apply-undeploy-actions.d.ts +1 -1
- package/dist/src/core/furnace-apply-undeploy-actions.js +1 -1
- package/dist/src/core/furnace-apply.js +13 -1
- package/dist/src/core/furnace-config-custom.d.ts +2 -1
- package/dist/src/core/furnace-config-order.d.ts +2 -1
- package/dist/src/core/furnace-config-order.js +9 -4
- package/dist/src/core/furnace-config-validate.d.ts +4 -3
- package/dist/src/core/furnace-config-validate.js +3 -3
- package/dist/src/core/furnace-config.js +5 -0
- package/dist/src/core/furnace-operation.d.ts +12 -0
- package/dist/src/core/furnace-operation.js +125 -1
- package/dist/src/core/furnace-rollback.d.ts +17 -0
- package/dist/src/core/furnace-rollback.js +35 -1
- package/dist/src/core/git-base.js +7 -2
- package/dist/src/core/git-diff.js +11 -2
- package/dist/src/core/git-readonly-index.d.ts +59 -0
- package/dist/src/core/git-readonly-index.js +137 -0
- package/dist/src/core/license-headers.d.ts +1 -1
- package/dist/src/core/license-headers.js +1 -1
- package/dist/src/core/lint-cache.d.ts +15 -5
- package/dist/src/core/lint-cache.js +35 -8
- package/dist/src/core/mach-known-noise-filter.d.ts +21 -0
- package/dist/src/core/mach-known-noise-filter.js +23 -0
- package/dist/src/core/mach-mozconfig.js +21 -3
- package/dist/src/core/module-resolution-preflight.d.ts +51 -0
- package/dist/src/core/module-resolution-preflight.js +155 -0
- package/dist/src/core/patch-export.js +2 -1
- package/dist/src/core/patch-identifier-suggest.js +29 -3
- package/dist/src/core/patch-lint-checkjs.d.ts +5 -5
- package/dist/src/core/patch-lint-checkjs.js +6 -6
- package/dist/src/core/patch-lint-creators.d.ts +1 -1
- package/dist/src/core/patch-lint-creators.js +1 -1
- package/dist/src/core/patch-lint-cross.d.ts +1 -1
- package/dist/src/core/patch-lint-cross.js +4 -4
- package/dist/src/core/patch-lint-css.js +1 -1
- package/dist/src/core/patch-lint-diff.d.ts +1 -1
- package/dist/src/core/patch-lint-diff.js +1 -1
- package/dist/src/core/patch-lint-jsdoc.js +4 -2
- package/dist/src/core/patch-lint-module-registration.d.ts +13 -0
- package/dist/src/core/patch-lint-module-registration.js +13 -4
- package/dist/src/core/patch-lint-mozbuild.d.ts +1 -1
- package/dist/src/core/patch-lint-mozbuild.js +1 -1
- package/dist/src/core/patch-lint-ownership.d.ts +2 -2
- package/dist/src/core/patch-lint-ownership.js +2 -2
- package/dist/src/core/patch-lint.d.ts +2 -2
- package/dist/src/core/patch-lint.js +3 -3
- package/dist/src/core/patch-lock.d.ts +21 -0
- package/dist/src/core/patch-lock.js +29 -2
- package/dist/src/core/patch-manifest-io.d.ts +2 -1
- package/dist/src/core/patch-manifest-io.js +49 -0
- package/dist/src/core/patch-parse.d.ts +18 -0
- package/dist/src/core/patch-parse.js +24 -1
- package/dist/src/core/patch-policy.d.ts +2 -2
- package/dist/src/core/patch-policy.js +2 -2
- package/dist/src/core/smoke-patterns.d.ts +1 -1
- package/dist/src/core/status-binary.d.ts +1 -1
- package/dist/src/core/status-binary.js +1 -1
- package/dist/src/core/status-classify.d.ts +1 -1
- package/dist/src/core/status-classify.js +2 -2
- package/dist/src/core/test-harness-crash.d.ts +69 -3
- package/dist/src/core/test-harness-crash.js +174 -9
- package/dist/src/core/test-stale-check.d.ts +1 -1
- package/dist/src/core/test-stale-check.js +1 -1
- package/dist/src/core/token-category.js +1 -1
- package/dist/src/core/token-manager.d.ts +1 -1
- package/dist/src/core/token-manager.js +5 -5
- package/dist/src/core/tree-cow.js +1 -1
- package/dist/src/core/tree-guard.js +2 -2
- package/dist/src/core/tree-store.js +1 -1
- package/dist/src/core/typecheck-shim.d.ts +2 -2
- package/dist/src/core/typecheck-shim.js +2 -2
- package/dist/src/core/typecheck.js +10 -0
- package/dist/src/core/xpcshell-profile-dir.js +1 -1
- package/dist/src/errors/base.d.ts +22 -1
- package/dist/src/errors/base.js +32 -1
- package/dist/src/errors/codes.d.ts +10 -0
- package/dist/src/errors/codes.js +10 -0
- package/dist/src/types/commands/options.d.ts +27 -11
- package/dist/src/types/commands/patches.d.ts +1 -1
- package/dist/src/types/config.d.ts +2 -2
- package/dist/src/types/furnace.d.ts +1 -1
- package/dist/src/types/json.d.ts +22 -0
- package/dist/src/types/json.js +11 -0
- package/dist/src/utils/assert.d.ts +46 -0
- package/dist/src/utils/assert.js +83 -0
- package/dist/src/utils/build-info.d.ts +1 -1
- package/dist/src/utils/build-info.js +1 -1
- package/dist/src/utils/logger.d.ts +20 -0
- package/dist/src/utils/logger.js +26 -0
- package/dist/src/utils/options.d.ts +12 -0
- package/dist/src/utils/options.js +39 -0
- package/dist/src/utils/stdio-drain.d.ts +1 -1
- package/dist/src/utils/stdio-drain.js +1 -1
- package/dist/src/utils/validation.d.ts +12 -1
- package/dist/src/utils/validation.js +13 -1
- package/package.json +3 -3
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* macOS display power-state probe for the headed no-output stall.
|
|
3
|
+
*
|
|
4
|
+
* `TIMEOUT … application timed out after N seconds with no output` /
|
|
5
|
+
* `Ran 0 checks` has three recorded causes, and one of them is purely
|
|
6
|
+
* environmental: a HEADED run on an unattended machine whose display is
|
|
7
|
+
* asleep or locked. A headed Firefox on a sleeping display never paints,
|
|
8
|
+
* never reaches its first test, and dies at the no-output timeout —
|
|
9
|
+
* indistinguishable, from the log alone, from a product hang.
|
|
10
|
+
*
|
|
11
|
+
* `caffeinate -disu` does not cure it. It PREVENTS sleep; it cannot WAKE a
|
|
12
|
+
* display that is already asleep, so wrapping the run changes nothing once
|
|
13
|
+
* the machine has already dimmed. `fireforge test` defaults to headed, so
|
|
14
|
+
* an unattended run walks straight into this and the operator burns whole
|
|
15
|
+
* cycles hunting a product cause.
|
|
16
|
+
*
|
|
17
|
+
* The probe is advisory and fail-open: anything unexpected reports
|
|
18
|
+
* `'unknown'` and the caller degrades to the generic triage list rather
|
|
19
|
+
* than asserting an environment it could not measure.
|
|
20
|
+
*/
|
|
21
|
+
/** Measured display power state, or `'unknown'` when nothing was measured. */
|
|
22
|
+
export type DisplaySleepState = 'asleep' | 'awake' | 'unknown';
|
|
23
|
+
/**
|
|
24
|
+
* Parses `pmset -g powerstate IODisplayWrangler` output.
|
|
25
|
+
*
|
|
26
|
+
* The command prints a header row and then one row per driver, e.g.
|
|
27
|
+
*
|
|
28
|
+
* ```
|
|
29
|
+
* Current power states:
|
|
30
|
+
* IODisplayWrangler 4 12 -1
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* Only the `IODisplayWrangler` row matters, and only its first numeric
|
|
34
|
+
* column (the current power state). Any other shape — no such row, a
|
|
35
|
+
* non-numeric column, an error message — is `'unknown'`, never a guess.
|
|
36
|
+
*
|
|
37
|
+
* Pure; exported for direct unit testing.
|
|
38
|
+
*
|
|
39
|
+
* @param stdout - Raw `pmset -g powerstate IODisplayWrangler` output
|
|
40
|
+
* @returns The measured state, or `'unknown'`
|
|
41
|
+
*/
|
|
42
|
+
export declare function parseDisplayPowerState(stdout: string): DisplaySleepState;
|
|
43
|
+
/**
|
|
44
|
+
* Probes the display's power state. Non-darwin platforms report
|
|
45
|
+
* `'unknown'` without spawning anything — the stall shape this serves is
|
|
46
|
+
* a macOS field report and no equivalent single-command probe is wired for
|
|
47
|
+
* other platforms.
|
|
48
|
+
*
|
|
49
|
+
* @param platform - `process.platform`-style id (injected for testability)
|
|
50
|
+
* @returns The measured state, or `'unknown'` when unmeasurable
|
|
51
|
+
*/
|
|
52
|
+
export declare function probeDisplaySleepState(platform: string): Promise<DisplaySleepState>;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* macOS display power-state probe for the headed no-output stall.
|
|
4
|
+
*
|
|
5
|
+
* `TIMEOUT … application timed out after N seconds with no output` /
|
|
6
|
+
* `Ran 0 checks` has three recorded causes, and one of them is purely
|
|
7
|
+
* environmental: a HEADED run on an unattended machine whose display is
|
|
8
|
+
* asleep or locked. A headed Firefox on a sleeping display never paints,
|
|
9
|
+
* never reaches its first test, and dies at the no-output timeout —
|
|
10
|
+
* indistinguishable, from the log alone, from a product hang.
|
|
11
|
+
*
|
|
12
|
+
* `caffeinate -disu` does not cure it. It PREVENTS sleep; it cannot WAKE a
|
|
13
|
+
* display that is already asleep, so wrapping the run changes nothing once
|
|
14
|
+
* the machine has already dimmed. `fireforge test` defaults to headed, so
|
|
15
|
+
* an unattended run walks straight into this and the operator burns whole
|
|
16
|
+
* cycles hunting a product cause.
|
|
17
|
+
*
|
|
18
|
+
* The probe is advisory and fail-open: anything unexpected reports
|
|
19
|
+
* `'unknown'` and the caller degrades to the generic triage list rather
|
|
20
|
+
* than asserting an environment it could not measure.
|
|
21
|
+
*/
|
|
22
|
+
import { toError } from '../utils/errors.js';
|
|
23
|
+
import { verbose } from '../utils/logger.js';
|
|
24
|
+
import { exec } from '../utils/process.js';
|
|
25
|
+
/** Bound on the probe so a wedged `pmset` cannot extend a failing run. */
|
|
26
|
+
const DISPLAY_PROBE_TIMEOUT_MS = 5_000;
|
|
27
|
+
/**
|
|
28
|
+
* IOKit power state at which `IODisplayWrangler` is fully lit. Lower
|
|
29
|
+
* states are dimmed or off; macOS reports 4 for a live display.
|
|
30
|
+
*/
|
|
31
|
+
const DISPLAY_AWAKE_POWER_STATE = 4;
|
|
32
|
+
/**
|
|
33
|
+
* Parses `pmset -g powerstate IODisplayWrangler` output.
|
|
34
|
+
*
|
|
35
|
+
* The command prints a header row and then one row per driver, e.g.
|
|
36
|
+
*
|
|
37
|
+
* ```
|
|
38
|
+
* Current power states:
|
|
39
|
+
* IODisplayWrangler 4 12 -1
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* Only the `IODisplayWrangler` row matters, and only its first numeric
|
|
43
|
+
* column (the current power state). Any other shape — no such row, a
|
|
44
|
+
* non-numeric column, an error message — is `'unknown'`, never a guess.
|
|
45
|
+
*
|
|
46
|
+
* Pure; exported for direct unit testing.
|
|
47
|
+
*
|
|
48
|
+
* @param stdout - Raw `pmset -g powerstate IODisplayWrangler` output
|
|
49
|
+
* @returns The measured state, or `'unknown'`
|
|
50
|
+
*/
|
|
51
|
+
export function parseDisplayPowerState(stdout) {
|
|
52
|
+
for (const line of stdout.split(/\r?\n/)) {
|
|
53
|
+
if (!line.includes('IODisplayWrangler'))
|
|
54
|
+
continue;
|
|
55
|
+
const match = /IODisplayWrangler\s+(\d+)/.exec(line);
|
|
56
|
+
const state = match?.[1];
|
|
57
|
+
if (state === undefined)
|
|
58
|
+
continue;
|
|
59
|
+
return Number.parseInt(state, 10) >= DISPLAY_AWAKE_POWER_STATE ? 'awake' : 'asleep';
|
|
60
|
+
}
|
|
61
|
+
return 'unknown';
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Probes the display's power state. Non-darwin platforms report
|
|
65
|
+
* `'unknown'` without spawning anything — the stall shape this serves is
|
|
66
|
+
* a macOS field report and no equivalent single-command probe is wired for
|
|
67
|
+
* other platforms.
|
|
68
|
+
*
|
|
69
|
+
* @param platform - `process.platform`-style id (injected for testability)
|
|
70
|
+
* @returns The measured state, or `'unknown'` when unmeasurable
|
|
71
|
+
*/
|
|
72
|
+
export async function probeDisplaySleepState(platform) {
|
|
73
|
+
if (platform !== 'darwin')
|
|
74
|
+
return 'unknown';
|
|
75
|
+
try {
|
|
76
|
+
const result = await exec('pmset', ['-g', 'powerstate', 'IODisplayWrangler'], {
|
|
77
|
+
timeout: DISPLAY_PROBE_TIMEOUT_MS,
|
|
78
|
+
});
|
|
79
|
+
if (result.exitCode !== 0) {
|
|
80
|
+
verbose(`Display power-state probe failed (pmset exit ${result.exitCode}).`);
|
|
81
|
+
return 'unknown';
|
|
82
|
+
}
|
|
83
|
+
return parseDisplayPowerState(result.stdout);
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
verbose(`Display power-state probe failed: ${toError(error).message}`);
|
|
87
|
+
return 'unknown';
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type LockStatusSnapshot } from './file-lock.js';
|
|
1
2
|
export interface EngineSessionLockOptions {
|
|
2
3
|
/**
|
|
3
4
|
* Wait up to this many seconds for the engine session lock instead of the
|
|
@@ -12,6 +13,19 @@ export interface EngineSessionLockOptions {
|
|
|
12
13
|
* one engine-mutating FireForge command touches `engine/` at a time.
|
|
13
14
|
*/
|
|
14
15
|
export declare function withEngineSessionLock<T>(projectRoot: string, command: string, operation: () => Promise<T>, options?: EngineSessionLockOptions): Promise<T>;
|
|
16
|
+
/**
|
|
17
|
+
* Inspects the project's engine-session lock without acquiring it —
|
|
18
|
+
* backing `fireforge status --lock`.
|
|
19
|
+
*
|
|
20
|
+
* @param projectRoot - Project root containing `.fireforge/`
|
|
21
|
+
* @returns Holder identity, hold duration, and queue depth
|
|
22
|
+
*/
|
|
23
|
+
export declare function readEngineSessionLockStatus(projectRoot: string): Promise<LockStatusSnapshot>;
|
|
24
|
+
/**
|
|
25
|
+
* Renders the engine-session lock snapshot as operator-facing lines.
|
|
26
|
+
* Pure, so the wording is unit-testable without a lock on disk.
|
|
27
|
+
*/
|
|
28
|
+
export declare function formatEngineSessionLockStatus(snapshot: LockStatusSnapshot): string[];
|
|
15
29
|
/**
|
|
16
30
|
* Captures an opaque token describing the engine's current commit and working
|
|
17
31
|
* tree, for comparison by {@link assertEngineGenerationUnchanged}.
|
|
@@ -21,6 +35,12 @@ export declare function withEngineSessionLock<T>(projectRoot: string, command: s
|
|
|
21
35
|
* because a probe failure means "nothing was measured", not "nothing changed".
|
|
22
36
|
*/
|
|
23
37
|
export declare function snapshotEngineGeneration(engineDir: string): Promise<string>;
|
|
38
|
+
/**
|
|
39
|
+
* True when a git failure is the transient index-lock contention shape
|
|
40
|
+
* (`Unable to create '…/.git/index.lock': File exists`) rather than a
|
|
41
|
+
* durable problem with the checkout. Exported for direct unit testing.
|
|
42
|
+
*/
|
|
43
|
+
export declare function isIndexLockError(error: unknown): boolean;
|
|
24
44
|
/**
|
|
25
45
|
* True when a generation token records a failed probe — i.e. nothing was
|
|
26
46
|
* measured, so the token must never be compared (or hashed) as if it
|
|
@@ -3,7 +3,7 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { GeneralError } from '../errors/base.js';
|
|
4
4
|
import { toError } from '../utils/errors.js';
|
|
5
5
|
import { info, warn } from '../utils/logger.js';
|
|
6
|
-
import { withFileLock } from './file-lock.js';
|
|
6
|
+
import { readLockStatus, withFileLock } from './file-lock.js';
|
|
7
7
|
import { git } from './git-base.js';
|
|
8
8
|
const ENGINE_SESSION_LOCK_PATH = join('.fireforge', 'engine-session.lock');
|
|
9
9
|
/**
|
|
@@ -12,13 +12,20 @@ const ENGINE_SESSION_LOCK_PATH = join('.fireforge', 'engine-session.lock');
|
|
|
12
12
|
* the lock's owner-metadata lines (`command=…`, `started=…`); an unreadable
|
|
13
13
|
* owner file degrades to the anonymous form.
|
|
14
14
|
*/
|
|
15
|
-
function formatWaitProgressLine(waitedMs, timeoutMs, holder) {
|
|
15
|
+
function formatWaitProgressLine(waitedMs, timeoutMs, holder, queue) {
|
|
16
16
|
const progress = `${String(Math.round(waitedMs / 1000))}s of up to ${String(Math.round(timeoutMs / 1000))}s`;
|
|
17
|
+
// Queue position: under several concurrent sessions the wait
|
|
18
|
+
// line alone left operators inferring their place from `ps`.
|
|
19
|
+
const position = queue === undefined || queue.depth === 0
|
|
20
|
+
? ''
|
|
21
|
+
: queue.ahead === 0
|
|
22
|
+
? ` You are next in a queue of ${String(queue.depth)}.`
|
|
23
|
+
: ` ${String(queue.ahead)} ahead of you (queue of ${String(queue.depth)}).`;
|
|
17
24
|
if (holder === undefined) {
|
|
18
|
-
return `Waiting for the FireForge engine lock — ${progress}
|
|
25
|
+
return `Waiting for the FireForge engine lock — ${progress}.${position}`;
|
|
19
26
|
}
|
|
20
27
|
const details = holder.metadata.length > 0 ? ` (${holder.metadata.join(', ')})` : '';
|
|
21
|
-
return `Waiting for the FireForge engine lock held by PID ${String(holder.pid)}${details} — ${progress}
|
|
28
|
+
return `Waiting for the FireForge engine lock held by PID ${String(holder.pid)}${details} — ${progress}.${position}`;
|
|
22
29
|
}
|
|
23
30
|
/**
|
|
24
31
|
* Runs `operation` while holding the project's engine-session lock, so only
|
|
@@ -43,13 +50,13 @@ export async function withEngineSessionLock(projectRoot, command, operation, opt
|
|
|
43
50
|
pollMs: 100,
|
|
44
51
|
pollMaxMs: 2000,
|
|
45
52
|
waitProgressMs: 5000,
|
|
46
|
-
onWaitProgress: ({ waitedMs, timeoutMs, holder }) => {
|
|
47
|
-
info(formatWaitProgressLine(waitedMs, timeoutMs, holder));
|
|
53
|
+
onWaitProgress: ({ waitedMs, timeoutMs, holder, queue }) => {
|
|
54
|
+
info(formatWaitProgressLine(waitedMs, timeoutMs, holder, queue));
|
|
48
55
|
},
|
|
49
56
|
}
|
|
50
57
|
: {}),
|
|
51
58
|
ownerMetadata: [`command=${command}`, `started=${new Date().toISOString()}`],
|
|
52
|
-
// Reason first, remedy second
|
|
59
|
+
// Reason first, remedy second — the no-flag default waits
|
|
53
60
|
// only ~1 s, so contention is the common case and `--wait-lock` is the
|
|
54
61
|
// genuine remedy. withFileLock appends the holder identification from
|
|
55
62
|
// the lock's owner metadata. The leading sentence is a message contract
|
|
@@ -60,6 +67,37 @@ export async function withEngineSessionLock(projectRoot, command, operation, opt
|
|
|
60
67
|
onStaleLockMessage: (ageMs) => `Removed stale FireForge engine session lock (${Math.round(ageMs / 1000)}s old).`,
|
|
61
68
|
});
|
|
62
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Inspects the project's engine-session lock without acquiring it —
|
|
72
|
+
* backing `fireforge status --lock`.
|
|
73
|
+
*
|
|
74
|
+
* @param projectRoot - Project root containing `.fireforge/`
|
|
75
|
+
* @returns Holder identity, hold duration, and queue depth
|
|
76
|
+
*/
|
|
77
|
+
export async function readEngineSessionLockStatus(projectRoot) {
|
|
78
|
+
return readLockStatus(join(projectRoot, ENGINE_SESSION_LOCK_PATH));
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Renders the engine-session lock snapshot as operator-facing lines.
|
|
82
|
+
* Pure, so the wording is unit-testable without a lock on disk.
|
|
83
|
+
*/
|
|
84
|
+
export function formatEngineSessionLockStatus(snapshot) {
|
|
85
|
+
if (!snapshot.held) {
|
|
86
|
+
return ['Engine session lock: free (no FireForge engine-mutating command is running).'];
|
|
87
|
+
}
|
|
88
|
+
const elapsed = snapshot.heldForMs === undefined
|
|
89
|
+
? 'unknown duration'
|
|
90
|
+
: `${String(Math.round(snapshot.heldForMs / 1000))}s`;
|
|
91
|
+
const holder = snapshot.holder;
|
|
92
|
+
const identity = holder === undefined
|
|
93
|
+
? 'held by an unidentified process (no readable owner record)'
|
|
94
|
+
: `held by PID ${String(holder.pid)}${holder.alive ? '' : ' (NOT RUNNING — stale lock)'}` +
|
|
95
|
+
(holder.metadata.length > 0 ? ` (${holder.metadata.join(', ')})` : '');
|
|
96
|
+
return [
|
|
97
|
+
`Engine session lock: ${identity}, for ${elapsed}.`,
|
|
98
|
+
`Queue depth: ${String(snapshot.queueDepth)} waiter(s).`,
|
|
99
|
+
];
|
|
100
|
+
}
|
|
63
101
|
/** Prefix marking a generation token whose probe failed. */
|
|
64
102
|
const UNAVAILABLE_PREFIX = 'unavailable:';
|
|
65
103
|
/**
|
|
@@ -71,14 +109,46 @@ const UNAVAILABLE_PREFIX = 'unavailable:';
|
|
|
71
109
|
* because a probe failure means "nothing was measured", not "nothing changed".
|
|
72
110
|
*/
|
|
73
111
|
export async function snapshotEngineGeneration(engineDir) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
112
|
+
let lastError;
|
|
113
|
+
for (let attempt = 0; attempt < GENERATION_PROBE_ATTEMPTS; attempt += 1) {
|
|
114
|
+
try {
|
|
115
|
+
const head = (await git(['rev-parse', 'HEAD'], engineDir)).trim();
|
|
116
|
+
const status = await git(['status', '--porcelain=v1', '-z'], engineDir);
|
|
117
|
+
return `${head}\0${status}`;
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
lastError = error;
|
|
121
|
+
// A contended `.git/index.lock` is TRANSIENT, not a state: some
|
|
122
|
+
// other writer held the index for the instant we looked.
|
|
123
|
+
// Treating it as "unmeasurable" turns a perfectly good suite into
|
|
124
|
+
// `FAIL reason=inconclusive` on a one-sided probe failure, which is
|
|
125
|
+
// exactly the false alarm this guard must not raise. Anything else
|
|
126
|
+
// — an unreadable `.git`, a non-git `engine/` — is a real state and
|
|
127
|
+
// is reported immediately.
|
|
128
|
+
if (!isIndexLockError(error) || attempt === GENERATION_PROBE_ATTEMPTS - 1)
|
|
129
|
+
break;
|
|
130
|
+
await delay(GENERATION_PROBE_RETRY_MS);
|
|
131
|
+
}
|
|
81
132
|
}
|
|
133
|
+
return `${UNAVAILABLE_PREFIX}${toError(lastError).message}`;
|
|
134
|
+
}
|
|
135
|
+
/** Probe attempts before an engine generation is declared unmeasurable. */
|
|
136
|
+
const GENERATION_PROBE_ATTEMPTS = 3;
|
|
137
|
+
/** Pause between generation-probe attempts. */
|
|
138
|
+
const GENERATION_PROBE_RETRY_MS = 250;
|
|
139
|
+
function delay(ms) {
|
|
140
|
+
return new Promise((resolveDelay) => {
|
|
141
|
+
setTimeout(resolveDelay, ms);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* True when a git failure is the transient index-lock contention shape
|
|
146
|
+
* (`Unable to create '…/.git/index.lock': File exists`) rather than a
|
|
147
|
+
* durable problem with the checkout. Exported for direct unit testing.
|
|
148
|
+
*/
|
|
149
|
+
export function isIndexLockError(error) {
|
|
150
|
+
const message = toError(error).message;
|
|
151
|
+
return /index\.lock/i.test(message);
|
|
82
152
|
}
|
|
83
153
|
/**
|
|
84
154
|
* True when a generation token records a failed probe — i.e. nothing was
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detects the new-file-plus-moved-code slice shape and names the working
|
|
3
|
+
* sequence through it.
|
|
4
|
+
*
|
|
5
|
+
* A slice that ADDS new files and MOVES lines into them from an existing
|
|
6
|
+
* patch has no path through the two refusal flags:
|
|
7
|
+
*
|
|
8
|
+
* - `re-export --refuse-adjacent-unmanaged` refuses while the new files
|
|
9
|
+
* are still unmanaged; and
|
|
10
|
+
* - exporting the new files as their own patch fails cross-patch lint,
|
|
11
|
+
* because at the projected placement the OLD patch still contains the
|
|
12
|
+
* moved code.
|
|
13
|
+
*
|
|
14
|
+
* Each guard is individually correct. Together they dead-end, and the way
|
|
15
|
+
* out is non-obvious: adopt the new files into the OLD patch first
|
|
16
|
+
* (`re-export --scan --scan-file`), then split them out into a patch of
|
|
17
|
+
* their own (`patch move-files --create --order N`).
|
|
18
|
+
*
|
|
19
|
+
* This module recognises the shape from evidence FireForge already has —
|
|
20
|
+
* the pending diff's new-file content versus the added lines the existing
|
|
21
|
+
* patches carry — so the refusal can name the sequence instead of leaving
|
|
22
|
+
* the operator to derive it.
|
|
23
|
+
*/
|
|
24
|
+
/** One existing patch that already carries lines the new files would add. */
|
|
25
|
+
export interface MovedCodeOverlap {
|
|
26
|
+
/** Filename of the patch that still contains the moved lines. */
|
|
27
|
+
sourcePatch: string;
|
|
28
|
+
/** New-file paths in the pending diff whose lines it overlaps. */
|
|
29
|
+
files: string[];
|
|
30
|
+
/** Count of distinct non-trivial lines shared. */
|
|
31
|
+
sharedLines: number;
|
|
32
|
+
}
|
|
33
|
+
/** Minimal queue view this detector needs. */
|
|
34
|
+
export interface MovedCodeQueueEntry {
|
|
35
|
+
filename: string;
|
|
36
|
+
diff: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Finds existing patches that still carry substantive lines the pending
|
|
40
|
+
* diff's NEW files would add — the fingerprint of code moved out of an
|
|
41
|
+
* existing patch into a new file.
|
|
42
|
+
*
|
|
43
|
+
* Pure. Overlaps are returned strongest-first so the message names the
|
|
44
|
+
* most likely source patch.
|
|
45
|
+
*
|
|
46
|
+
* @param pendingDiff - The diff the export would write
|
|
47
|
+
* @param entries - The existing queue's patches
|
|
48
|
+
* @returns Overlaps, or an empty list when the shape is not present
|
|
49
|
+
*/
|
|
50
|
+
export declare function detectMovedCodeOverlaps(pendingDiff: string, entries: readonly MovedCodeQueueEntry[]): MovedCodeOverlap[];
|
|
51
|
+
/**
|
|
52
|
+
* Renders the adopt-then-split remedy for the strongest overlap, as lines
|
|
53
|
+
* appended to the refusal's detail list.
|
|
54
|
+
*
|
|
55
|
+
* @param overlaps - Result of {@link detectMovedCodeOverlaps}
|
|
56
|
+
* @param newPatchName - The filename the pending export would take
|
|
57
|
+
* @param insertionOrder - The order the pending export would take
|
|
58
|
+
* @returns Detail lines, or an empty list when the shape is not present
|
|
59
|
+
*/
|
|
60
|
+
export declare function formatAdoptThenSplitRemedy(overlaps: readonly MovedCodeOverlap[], newPatchName: string, insertionOrder: number): string[];
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Detects the new-file-plus-moved-code slice shape and names the working
|
|
4
|
+
* sequence through it.
|
|
5
|
+
*
|
|
6
|
+
* A slice that ADDS new files and MOVES lines into them from an existing
|
|
7
|
+
* patch has no path through the two refusal flags:
|
|
8
|
+
*
|
|
9
|
+
* - `re-export --refuse-adjacent-unmanaged` refuses while the new files
|
|
10
|
+
* are still unmanaged; and
|
|
11
|
+
* - exporting the new files as their own patch fails cross-patch lint,
|
|
12
|
+
* because at the projected placement the OLD patch still contains the
|
|
13
|
+
* moved code.
|
|
14
|
+
*
|
|
15
|
+
* Each guard is individually correct. Together they dead-end, and the way
|
|
16
|
+
* out is non-obvious: adopt the new files into the OLD patch first
|
|
17
|
+
* (`re-export --scan --scan-file`), then split them out into a patch of
|
|
18
|
+
* their own (`patch move-files --create --order N`).
|
|
19
|
+
*
|
|
20
|
+
* This module recognises the shape from evidence FireForge already has —
|
|
21
|
+
* the pending diff's new-file content versus the added lines the existing
|
|
22
|
+
* patches carry — so the refusal can name the sequence instead of leaving
|
|
23
|
+
* the operator to derive it.
|
|
24
|
+
*/
|
|
25
|
+
import { extractAddedLinesPerFile } from './patch-lint-diff.js';
|
|
26
|
+
/**
|
|
27
|
+
* Lines too generic to be evidence of a move: closing braces, blank
|
|
28
|
+
* lines, bare keywords, import punctuation. Requiring substance keeps the
|
|
29
|
+
* detector from firing on every patch that happens to contain a `}`.
|
|
30
|
+
*/
|
|
31
|
+
function isSubstantiveLine(line) {
|
|
32
|
+
const trimmed = line.trim();
|
|
33
|
+
if (trimmed.length < 12)
|
|
34
|
+
return false;
|
|
35
|
+
if (/^[)\]};,]+$/.test(trimmed))
|
|
36
|
+
return false;
|
|
37
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*'))
|
|
38
|
+
return false;
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
/** Minimum shared substantive lines before the shape is claimed. */
|
|
42
|
+
const MOVED_CODE_LINE_THRESHOLD = 3;
|
|
43
|
+
/**
|
|
44
|
+
* Finds existing patches that still carry substantive lines the pending
|
|
45
|
+
* diff's NEW files would add — the fingerprint of code moved out of an
|
|
46
|
+
* existing patch into a new file.
|
|
47
|
+
*
|
|
48
|
+
* Pure. Overlaps are returned strongest-first so the message names the
|
|
49
|
+
* most likely source patch.
|
|
50
|
+
*
|
|
51
|
+
* @param pendingDiff - The diff the export would write
|
|
52
|
+
* @param entries - The existing queue's patches
|
|
53
|
+
* @returns Overlaps, or an empty list when the shape is not present
|
|
54
|
+
*/
|
|
55
|
+
export function detectMovedCodeOverlaps(pendingDiff, entries) {
|
|
56
|
+
const pendingByFile = extractAddedLinesPerFile(pendingDiff);
|
|
57
|
+
const pendingLines = new Map();
|
|
58
|
+
for (const [file, lines] of pendingByFile) {
|
|
59
|
+
const substantive = new Set(lines.map((line) => line.trim()).filter(isSubstantiveLine));
|
|
60
|
+
if (substantive.size > 0)
|
|
61
|
+
pendingLines.set(file, substantive);
|
|
62
|
+
}
|
|
63
|
+
if (pendingLines.size === 0)
|
|
64
|
+
return [];
|
|
65
|
+
const overlaps = [];
|
|
66
|
+
for (const entry of entries) {
|
|
67
|
+
const existing = new Set();
|
|
68
|
+
for (const lines of extractAddedLinesPerFile(entry.diff).values()) {
|
|
69
|
+
for (const line of lines) {
|
|
70
|
+
const trimmed = line.trim();
|
|
71
|
+
if (isSubstantiveLine(trimmed))
|
|
72
|
+
existing.add(trimmed);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (existing.size === 0)
|
|
76
|
+
continue;
|
|
77
|
+
const files = [];
|
|
78
|
+
const shared = new Set();
|
|
79
|
+
for (const [file, lines] of pendingLines) {
|
|
80
|
+
let fileShared = 0;
|
|
81
|
+
for (const line of lines) {
|
|
82
|
+
if (!existing.has(line))
|
|
83
|
+
continue;
|
|
84
|
+
shared.add(line);
|
|
85
|
+
fileShared += 1;
|
|
86
|
+
}
|
|
87
|
+
if (fileShared > 0)
|
|
88
|
+
files.push(file);
|
|
89
|
+
}
|
|
90
|
+
if (shared.size >= MOVED_CODE_LINE_THRESHOLD && files.length > 0) {
|
|
91
|
+
overlaps.push({ sourcePatch: entry.filename, files: files.sort(), sharedLines: shared.size });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return overlaps.sort((a, b) => b.sharedLines - a.sharedLines || a.sourcePatch.localeCompare(b.sourcePatch));
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Renders the adopt-then-split remedy for the strongest overlap, as lines
|
|
98
|
+
* appended to the refusal's detail list.
|
|
99
|
+
*
|
|
100
|
+
* @param overlaps - Result of {@link detectMovedCodeOverlaps}
|
|
101
|
+
* @param newPatchName - The filename the pending export would take
|
|
102
|
+
* @param insertionOrder - The order the pending export would take
|
|
103
|
+
* @returns Detail lines, or an empty list when the shape is not present
|
|
104
|
+
*/
|
|
105
|
+
export function formatAdoptThenSplitRemedy(overlaps, newPatchName, insertionOrder) {
|
|
106
|
+
const strongest = overlaps[0];
|
|
107
|
+
if (strongest === undefined)
|
|
108
|
+
return [];
|
|
109
|
+
const fileArgs = strongest.files.map((file) => `--scan-file ${file}`).join(' ');
|
|
110
|
+
const moveArgs = strongest.files.map((file) => `--file ${file}`).join(' ');
|
|
111
|
+
// `patch move-files --create` takes the new patch's NAME, not a
|
|
112
|
+
// filename: strip the order prefix and the `.patch` suffix so the
|
|
113
|
+
// suggested command is copy-pasteable.
|
|
114
|
+
const suggestedName = newPatchName.replace(/^\d+-/, '').replace(/\.patch$/, '');
|
|
115
|
+
return [
|
|
116
|
+
'',
|
|
117
|
+
`This looks like a new-file + moved-code slice: ${String(strongest.sharedLines)} substantive ` +
|
|
118
|
+
`line(s) the new file(s) would add are still carried by ${strongest.sourcePatch}. ` +
|
|
119
|
+
'Exporting them as their own patch cannot pass cross-patch lint while the source patch ' +
|
|
120
|
+
'still owns the same code, and re-exporting cannot adopt them while they are unmanaged — ' +
|
|
121
|
+
'each guard is right, and together they dead-end.',
|
|
122
|
+
'Working sequence (adopt, then split):',
|
|
123
|
+
` 1. fireforge re-export ${strongest.sourcePatch} --scan ${fileArgs}`,
|
|
124
|
+
` 2. fireforge patch move-files ${strongest.sourcePatch} ${suggestedName} --create --order ${String(insertionOrder)} ${moveArgs}`,
|
|
125
|
+
'Step 1 adopts the new files into the patch that already owns the moved lines; step 2 ' +
|
|
126
|
+
'splits them back out as one transaction, so the queue is never in a state where two ' +
|
|
127
|
+
'patches own the same code.',
|
|
128
|
+
];
|
|
129
|
+
}
|
|
@@ -9,6 +9,21 @@ export interface LockHolder {
|
|
|
9
9
|
alive: boolean;
|
|
10
10
|
metadata: string[];
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Live queue state around a contended lock.
|
|
14
|
+
*
|
|
15
|
+
* Under several concurrent sessions the lock itself behaved correctly; the
|
|
16
|
+
* gap was VISIBILITY — operators inferred queue state from `ps` and their
|
|
17
|
+
* own wait lines. Waiters advertise themselves in a sibling directory so
|
|
18
|
+
* both the wait output and `fireforge status --lock` can say how many are
|
|
19
|
+
* queued and how many are ahead of you.
|
|
20
|
+
*/
|
|
21
|
+
export interface LockQueueState {
|
|
22
|
+
/** Live waiters currently queued for the lock, including this process. */
|
|
23
|
+
depth: number;
|
|
24
|
+
/** Live waiters that started waiting BEFORE this process. */
|
|
25
|
+
ahead: number;
|
|
26
|
+
}
|
|
12
27
|
export interface FileLockOptions {
|
|
13
28
|
timeoutMs?: number;
|
|
14
29
|
pollMs?: number;
|
|
@@ -40,6 +55,8 @@ export interface FileLockOptions {
|
|
|
40
55
|
waitedMs: number;
|
|
41
56
|
timeoutMs: number;
|
|
42
57
|
holder: LockHolder | undefined;
|
|
58
|
+
/** Queue state at this poll, when the waiter registry is available. */
|
|
59
|
+
queue?: LockQueueState | undefined;
|
|
43
60
|
}) => void;
|
|
44
61
|
onTimeoutMessage?: string;
|
|
45
62
|
onStaleLockMessage?: (ageMs: number) => string | undefined;
|
|
@@ -55,5 +72,32 @@ export declare function createSiblingLockPath(filePath: string, suffix?: string)
|
|
|
55
72
|
* re-spelling `'pid'`; the file *format* is documented on {@link readLockOwner}.
|
|
56
73
|
*/
|
|
57
74
|
export declare const LOCK_PID_FILE = "pid";
|
|
75
|
+
/**
|
|
76
|
+
* Reads the live waiter queue around `lockPath`, dropping entries whose
|
|
77
|
+
* process is gone (a killed waiter must not inflate the depth forever).
|
|
78
|
+
*
|
|
79
|
+
* Advisory and fail-open: an unreadable registry reports an empty queue
|
|
80
|
+
* rather than failing whatever asked.
|
|
81
|
+
*
|
|
82
|
+
* @param lockPath - The lock directory whose queue to inspect
|
|
83
|
+
* @param selfStartedAt - This process's wait start, to compute `ahead`
|
|
84
|
+
*/
|
|
85
|
+
export declare function readLockQueue(lockPath: string, selfStartedAt?: number): Promise<LockQueueState>;
|
|
86
|
+
/**
|
|
87
|
+
* Snapshot of a lock for `fireforge status --lock`: who holds
|
|
88
|
+
* it, for how long, and how deep the queue behind it is.
|
|
89
|
+
*/
|
|
90
|
+
export interface LockStatusSnapshot {
|
|
91
|
+
held: boolean;
|
|
92
|
+
holder?: LockHolder | undefined;
|
|
93
|
+
/** Milliseconds since the lock directory was created, when held. */
|
|
94
|
+
heldForMs?: number | undefined;
|
|
95
|
+
queueDepth: number;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Inspects a lock without acquiring it. Read-only and fail-open: a missing
|
|
99
|
+
* lock is reported as free, never as an error.
|
|
100
|
+
*/
|
|
101
|
+
export declare function readLockStatus(lockPath: string): Promise<LockStatusSnapshot>;
|
|
58
102
|
/** Runs an async operation while holding a directory lock, with stale-lock recovery. */
|
|
59
103
|
export declare function withFileLock<T>(lockPath: string, operation: () => Promise<T>, options?: FileLockOptions): Promise<T>;
|