@hominis/fireforge 0.10.1 → 0.11.1
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 +93 -1
- package/README.md +125 -238
- package/dist/bin/fireforge.js +26 -0
- package/dist/src/cli.d.ts +1 -1
- package/dist/src/cli.js +131 -52
- package/dist/src/commands/bootstrap.js +6 -2
- package/dist/src/commands/build.js +4 -2
- package/dist/src/commands/discard.js +16 -4
- package/dist/src/commands/doctor-furnace.d.ts +8 -0
- package/dist/src/commands/doctor-furnace.js +422 -0
- package/dist/src/commands/doctor.d.ts +115 -0
- package/dist/src/commands/doctor.js +327 -258
- package/dist/src/commands/download.js +16 -1
- package/dist/src/commands/export-all.js +15 -0
- package/dist/src/commands/export-flow.d.ts +91 -0
- package/dist/src/commands/export-flow.js +344 -0
- package/dist/src/commands/export.js +151 -5
- package/dist/src/commands/furnace/apply.d.ts +3 -2
- package/dist/src/commands/furnace/apply.js +169 -36
- package/dist/src/commands/furnace/create.js +162 -52
- package/dist/src/commands/furnace/deploy.js +156 -144
- package/dist/src/commands/furnace/diff.d.ts +8 -4
- package/dist/src/commands/furnace/diff.js +142 -73
- package/dist/src/commands/furnace/index.d.ts +6 -2
- package/dist/src/commands/furnace/index.js +76 -25
- package/dist/src/commands/furnace/init.d.ts +11 -0
- package/dist/src/commands/furnace/init.js +76 -0
- package/dist/src/commands/furnace/list.d.ts +4 -1
- package/dist/src/commands/furnace/list.js +35 -3
- package/dist/src/commands/furnace/override.d.ts +8 -0
- package/dist/src/commands/furnace/override.js +216 -26
- package/dist/src/commands/furnace/preview.js +184 -30
- package/dist/src/commands/furnace/refresh.d.ts +10 -0
- package/dist/src/commands/furnace/refresh.js +268 -0
- package/dist/src/commands/furnace/remove.js +285 -89
- package/dist/src/commands/furnace/rename.d.ts +5 -0
- package/dist/src/commands/furnace/rename.js +308 -0
- package/dist/src/commands/furnace/scan.d.ts +4 -1
- package/dist/src/commands/furnace/scan.js +72 -11
- package/dist/src/commands/furnace/status.js +85 -20
- package/dist/src/commands/furnace/sync.d.ts +12 -0
- package/dist/src/commands/furnace/sync.js +77 -0
- package/dist/src/commands/furnace/validate.d.ts +4 -1
- package/dist/src/commands/furnace/validate.js +99 -3
- package/dist/src/commands/furnace/validation-output.d.ts +24 -1
- package/dist/src/commands/furnace/validation-output.js +93 -1
- package/dist/src/commands/import.js +37 -4
- package/dist/src/commands/lint.js +11 -2
- package/dist/src/commands/manifest.d.ts +39 -0
- package/dist/src/commands/manifest.js +59 -0
- package/dist/src/commands/patch/delete.d.ts +28 -0
- package/dist/src/commands/patch/delete.js +209 -0
- package/dist/src/commands/patch/index.d.ts +17 -0
- package/dist/src/commands/patch/index.js +25 -0
- package/dist/src/commands/patch/reorder.d.ts +30 -0
- package/dist/src/commands/patch/reorder.js +377 -0
- package/dist/src/commands/re-export-files.d.ts +17 -0
- package/dist/src/commands/re-export-files.js +177 -0
- package/dist/src/commands/re-export.js +44 -0
- package/dist/src/commands/rebase/abort.d.ts +1 -1
- package/dist/src/commands/rebase/abort.js +12 -3
- package/dist/src/commands/rebase/confirm.d.ts +3 -3
- package/dist/src/commands/rebase/confirm.js +4 -4
- package/dist/src/commands/rebase/index.js +13 -4
- package/dist/src/commands/reset.js +20 -4
- package/dist/src/commands/run.js +46 -1
- package/dist/src/commands/setup-support.js +6 -5
- package/dist/src/commands/status.js +97 -6
- package/dist/src/commands/test.js +5 -37
- package/dist/src/commands/verify.d.ts +31 -0
- package/dist/src/commands/verify.js +126 -0
- package/dist/src/core/build-prepare.js +40 -16
- package/dist/src/core/destructive.d.ts +96 -0
- package/dist/src/core/destructive.js +137 -0
- package/dist/src/core/diff-hunks.d.ts +73 -0
- package/dist/src/core/diff-hunks.js +268 -0
- package/dist/src/core/firefox.d.ts +1 -1
- package/dist/src/core/firefox.js +1 -1
- package/dist/src/core/furnace-apply-helpers.d.ts +89 -6
- package/dist/src/core/furnace-apply-helpers.js +302 -57
- package/dist/src/core/furnace-apply-output.d.ts +16 -0
- package/dist/src/core/furnace-apply-output.js +57 -0
- package/dist/src/core/furnace-apply.d.ts +21 -3
- package/dist/src/core/furnace-apply.js +260 -29
- package/dist/src/core/furnace-checksum-utils.d.ts +4 -0
- package/dist/src/core/furnace-checksum-utils.js +24 -0
- package/dist/src/core/furnace-config.d.ts +28 -1
- package/dist/src/core/furnace-config.js +180 -17
- package/dist/src/core/furnace-constants.d.ts +22 -0
- package/dist/src/core/furnace-constants.js +36 -0
- package/dist/src/core/furnace-graph-utils.d.ts +11 -0
- package/dist/src/core/furnace-graph-utils.js +94 -0
- package/dist/src/core/furnace-operation.d.ts +108 -0
- package/dist/src/core/furnace-operation.js +220 -0
- package/dist/src/core/furnace-refresh.d.ts +20 -0
- package/dist/src/core/furnace-refresh.js +118 -0
- package/dist/src/core/furnace-registration-ast.d.ts +5 -0
- package/dist/src/core/furnace-registration-ast.js +134 -4
- package/dist/src/core/furnace-registration-remove.d.ts +25 -3
- package/dist/src/core/furnace-registration-remove.js +196 -62
- package/dist/src/core/furnace-registration-validate.d.ts +13 -1
- package/dist/src/core/furnace-registration-validate.js +15 -3
- package/dist/src/core/furnace-registration.d.ts +27 -4
- package/dist/src/core/furnace-registration.js +93 -11
- package/dist/src/core/furnace-rollback.d.ts +11 -0
- package/dist/src/core/furnace-rollback.js +78 -7
- package/dist/src/core/furnace-scanner.d.ts +8 -2
- package/dist/src/core/furnace-scanner.js +152 -55
- package/dist/src/core/furnace-stories.js +7 -5
- package/dist/src/core/furnace-validate-accessibility.js +7 -1
- package/dist/src/core/furnace-validate-compatibility.d.ts +1 -1
- package/dist/src/core/furnace-validate-compatibility.js +85 -1
- package/dist/src/core/furnace-validate-helpers.d.ts +4 -0
- package/dist/src/core/furnace-validate-helpers.js +31 -0
- package/dist/src/core/furnace-validate-registration.d.ts +17 -2
- package/dist/src/core/furnace-validate-registration.js +73 -3
- package/dist/src/core/furnace-validate-structure.d.ts +10 -2
- package/dist/src/core/furnace-validate-structure.js +45 -3
- package/dist/src/core/furnace-validate.d.ts +10 -1
- package/dist/src/core/furnace-validate.js +80 -6
- package/dist/src/core/furnace-version-drift.d.ts +55 -0
- package/dist/src/core/furnace-version-drift.js +101 -0
- package/dist/src/core/git-file-ops.d.ts +8 -0
- package/dist/src/core/git-file-ops.js +19 -6
- package/dist/src/core/lint-projection.d.ts +25 -0
- package/dist/src/core/lint-projection.js +44 -0
- package/dist/src/core/mach.d.ts +4 -2
- package/dist/src/core/mach.js +17 -2
- package/dist/src/core/markdown-table.d.ts +104 -0
- package/dist/src/core/markdown-table.js +266 -0
- package/dist/src/core/ownership-table.d.ts +53 -0
- package/dist/src/core/ownership-table.js +144 -0
- package/dist/src/core/patch-apply.d.ts +17 -3
- package/dist/src/core/patch-apply.js +86 -8
- package/dist/src/core/patch-export.d.ts +119 -5
- package/dist/src/core/patch-export.js +183 -25
- package/dist/src/core/patch-lint-cross.d.ts +195 -0
- package/dist/src/core/patch-lint-cross.js +428 -0
- package/dist/src/core/patch-lint-diff.d.ts +33 -0
- package/dist/src/core/patch-lint-diff.js +84 -0
- package/dist/src/core/patch-lint.d.ts +2 -4
- package/dist/src/core/patch-lint.js +12 -50
- package/dist/src/core/patch-lock.js +2 -1
- package/dist/src/core/patch-manifest-io.d.ts +102 -1
- package/dist/src/core/patch-manifest-io.js +270 -2
- package/dist/src/core/patch-manifest-query.d.ts +1 -1
- package/dist/src/core/patch-manifest-query.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-transform.d.ts +12 -0
- package/dist/src/core/patch-transform.js +21 -7
- package/dist/src/core/token-manager.js +67 -69
- package/dist/src/core/wire-destroy.js +6 -3
- package/dist/src/core/wire-init.js +10 -4
- package/dist/src/core/wire-subscript.js +9 -3
- package/dist/src/core/wire-utils.d.ts +52 -5
- package/dist/src/core/wire-utils.js +69 -6
- package/dist/src/errors/base.d.ts +20 -0
- package/dist/src/errors/base.js +24 -0
- package/dist/src/errors/furnace.js +7 -1
- package/dist/src/errors/rebase.js +6 -1
- package/dist/src/types/commands/index.d.ts +1 -1
- package/dist/src/types/commands/options.d.ts +125 -4
- package/dist/src/types/commands/patches.d.ts +11 -1
- package/dist/src/types/config.d.ts +1 -1
- package/dist/src/types/furnace.d.ts +55 -1
- package/dist/src/utils/fs.d.ts +12 -0
- package/dist/src/utils/fs.js +30 -1
- package/dist/src/utils/package-root.d.ts +5 -0
- package/dist/src/utils/package-root.js +12 -0
- package/dist/src/utils/process.js +9 -4
- package/dist/src/utils/validation.d.ts +20 -2
- package/dist/src/utils/validation.js +26 -3
- package/package.json +1 -1
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diffs projected lint issues against the baseline and returns those
|
|
3
|
+
* considered "new" regressions.
|
|
4
|
+
*
|
|
5
|
+
* The equality key is the issue fingerprint when present, otherwise the
|
|
6
|
+
* full `(check, file, message)` triple. Fingerprints are emitted by rules
|
|
7
|
+
* whose message text can drift between otherwise-equivalent runs (for
|
|
8
|
+
* example because the message embeds later-owner filenames or positional
|
|
9
|
+
* detail). Falling back to the full tuple keeps the helper conservative
|
|
10
|
+
* for older/non-fingerprinted rules: if their message changes, we would
|
|
11
|
+
* rather surface a potential regression than silently swallow it.
|
|
12
|
+
*
|
|
13
|
+
* Consumption order within the projected list is stable (the input
|
|
14
|
+
* order is preserved), so when baseline has N issues for a key and
|
|
15
|
+
* projected has N+M for the same key, the *last* M projected issues on
|
|
16
|
+
* that key are reported as regressions. That keeps the reporting
|
|
17
|
+
* deterministic and gives the operator at least one concrete message
|
|
18
|
+
* per regression even when only counts differ.
|
|
19
|
+
*
|
|
20
|
+
* @param baseline - Error-severity issues from the current queue
|
|
21
|
+
* @param projected - Error-severity issues from the projected queue
|
|
22
|
+
* @returns Subset of `projected` not matched by a baseline counterpart
|
|
23
|
+
*/
|
|
24
|
+
export function computeProjectedLintRegressions(baseline, projected) {
|
|
25
|
+
const issueKey = (i) => i.fingerprint ?? `${i.check}|${i.file}|${i.message}`;
|
|
26
|
+
const baselineCounts = new Map();
|
|
27
|
+
for (const issue of baseline) {
|
|
28
|
+
const key = issueKey(issue);
|
|
29
|
+
baselineCounts.set(key, (baselineCounts.get(key) ?? 0) + 1);
|
|
30
|
+
}
|
|
31
|
+
const regressions = [];
|
|
32
|
+
const consumedByKey = new Map();
|
|
33
|
+
for (const issue of projected) {
|
|
34
|
+
const key = issueKey(issue);
|
|
35
|
+
const allowed = baselineCounts.get(key) ?? 0;
|
|
36
|
+
const consumed = consumedByKey.get(key) ?? 0;
|
|
37
|
+
if (consumed >= allowed) {
|
|
38
|
+
regressions.push(issue);
|
|
39
|
+
}
|
|
40
|
+
consumedByKey.set(key, consumed + 1);
|
|
41
|
+
}
|
|
42
|
+
return regressions;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=lint-projection.js.map
|
package/dist/src/core/mach.d.ts
CHANGED
|
@@ -33,8 +33,10 @@ export interface MachCommandResult {
|
|
|
33
33
|
*/
|
|
34
34
|
export declare function runMach(args: string[], engineDir: string, options?: MachOptions): Promise<number>;
|
|
35
35
|
/**
|
|
36
|
-
* Runs a mach command while streaming output to the terminal and capturing
|
|
37
|
-
* for post-run diagnostics.
|
|
36
|
+
* Runs a mach command while streaming output to the terminal and capturing
|
|
37
|
+
* the tail of stdout/stderr for post-run diagnostics. Output beyond
|
|
38
|
+
* {@link CAPTURE_TAIL_LIMIT} is discarded from the head to prevent unbounded
|
|
39
|
+
* memory growth during long-lived processes like Storybook.
|
|
38
40
|
*/
|
|
39
41
|
export declare function runMachCapture(args: string[], engineDir: string, options?: Omit<MachOptions, 'inherit'>): Promise<MachCommandResult>;
|
|
40
42
|
/**
|
package/dist/src/core/mach.js
CHANGED
|
@@ -41,8 +41,17 @@ export async function runMach(args, engineDir, options = {}) {
|
|
|
41
41
|
return result.exitCode;
|
|
42
42
|
}
|
|
43
43
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
44
|
+
* Maximum bytes retained per stream in `runMachCapture`. Only the tail of
|
|
45
|
+
* output is kept so long-lived processes (Storybook, `mach build`) do not
|
|
46
|
+
* grow the Node heap without bound. 2 MB is enough for post-run error
|
|
47
|
+
* diagnosis while staying well below the 50 MB cap in `createStreamCollector`.
|
|
48
|
+
*/
|
|
49
|
+
const CAPTURE_TAIL_LIMIT = 2 * 1024 * 1024;
|
|
50
|
+
/**
|
|
51
|
+
* Runs a mach command while streaming output to the terminal and capturing
|
|
52
|
+
* the tail of stdout/stderr for post-run diagnostics. Output beyond
|
|
53
|
+
* {@link CAPTURE_TAIL_LIMIT} is discarded from the head to prevent unbounded
|
|
54
|
+
* memory growth during long-lived processes like Storybook.
|
|
46
55
|
*/
|
|
47
56
|
export async function runMachCapture(args, engineDir, options = {}) {
|
|
48
57
|
const python = await getPython(engineDir);
|
|
@@ -55,10 +64,16 @@ export async function runMachCapture(args, engineDir, options = {}) {
|
|
|
55
64
|
...(options.env ? { env: options.env } : {}),
|
|
56
65
|
onStdout: (data) => {
|
|
57
66
|
stdout += data;
|
|
67
|
+
if (stdout.length > CAPTURE_TAIL_LIMIT) {
|
|
68
|
+
stdout = stdout.slice(-CAPTURE_TAIL_LIMIT);
|
|
69
|
+
}
|
|
58
70
|
process.stdout.write(data);
|
|
59
71
|
},
|
|
60
72
|
onStderr: (data) => {
|
|
61
73
|
stderr += data;
|
|
74
|
+
if (stderr.length > CAPTURE_TAIL_LIMIT) {
|
|
75
|
+
stderr = stderr.slice(-CAPTURE_TAIL_LIMIT);
|
|
76
|
+
}
|
|
62
77
|
process.stderr.write(data);
|
|
63
78
|
},
|
|
64
79
|
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal Markdown pipe-table parser/writer used by token-manager.
|
|
3
|
+
*
|
|
4
|
+
* This is **not** a general Markdown parser. It understands exactly the
|
|
5
|
+
* slice of GitHub-flavoured Markdown that FireForge emits into token docs:
|
|
6
|
+
*
|
|
7
|
+
* - Leading `|` per row, trailing `|` optional.
|
|
8
|
+
* - A separator row of `|---|---|...|` immediately after the header.
|
|
9
|
+
* - Fenced code blocks (``` / ~~~) are recognised and skipped so that
|
|
10
|
+
* literal `|` lines inside code samples are never mistaken for rows.
|
|
11
|
+
* - A table ends on the first non-pipe line (blank line, heading, prose).
|
|
12
|
+
* - Literal `|` and `\` inside a cell are expressed as `\|` and `\\`
|
|
13
|
+
* respectively. The parser treats any other backslash as a literal
|
|
14
|
+
* so prose-style backslashes in cell values (e.g. a Windows path)
|
|
15
|
+
* still round-trip. Unescaping happens at split time; serialization
|
|
16
|
+
* re-applies escapes so round-trips are exact for well-formed input.
|
|
17
|
+
*
|
|
18
|
+
* The parser returns **positional metadata** (`startLine`, `endLine`) so
|
|
19
|
+
* callers can splice the table back into the surrounding document by
|
|
20
|
+
* range instead of hand-rolled line counters. Mutation operations return
|
|
21
|
+
* a fresh line array rather than mutating in place, matching the rest of
|
|
22
|
+
* the file-patching code in this codebase.
|
|
23
|
+
*
|
|
24
|
+
* Fallback tolerance: rows with more cells than the header get their
|
|
25
|
+
* overflow merged into the last column (using a literal `|` separator
|
|
26
|
+
* between the merged values). This is for the rare case where a FireForge
|
|
27
|
+
* writer emits an unescaped pipe — the parser still produces a consistent
|
|
28
|
+
* cell count rather than desynchronising the table. On re-serialize the
|
|
29
|
+
* merged cell's literal pipes are escaped, so a malformed input becomes
|
|
30
|
+
* a well-formed output after one round trip.
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* A parsed Markdown pipe table.
|
|
34
|
+
*/
|
|
35
|
+
export interface MarkdownTable {
|
|
36
|
+
/** Column headers, trimmed. Cells are in document order. */
|
|
37
|
+
headers: string[];
|
|
38
|
+
/**
|
|
39
|
+
* Data rows. Each row is an array with exactly `headers.length` entries,
|
|
40
|
+
* trimmed. Rows shorter than the header are right-padded with `''`;
|
|
41
|
+
* longer rows have trailing cells merged into the last column so the
|
|
42
|
+
* round-trip is faithful.
|
|
43
|
+
*/
|
|
44
|
+
rows: string[][];
|
|
45
|
+
/** 0-indexed line number of the header row in the source lines array. */
|
|
46
|
+
startLine: number;
|
|
47
|
+
/**
|
|
48
|
+
* 0-indexed line number *after* the final data row. `lines.slice(
|
|
49
|
+
* startLine, endLine)` yields exactly the table's lines.
|
|
50
|
+
*/
|
|
51
|
+
endLine: number;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Locates the next Markdown table in `lines` starting at or after
|
|
55
|
+
* `fromLine`, skipping fenced code blocks. Returns `null` when no table
|
|
56
|
+
* is found.
|
|
57
|
+
*
|
|
58
|
+
* @param lines - The full document split by newline
|
|
59
|
+
* @param fromLine - Line index to start scanning from (inclusive)
|
|
60
|
+
*/
|
|
61
|
+
export declare function findNextTable(lines: string[], fromLine: number): MarkdownTable | null;
|
|
62
|
+
/**
|
|
63
|
+
* Returns the first table whose header contains **all** of the provided
|
|
64
|
+
* column names (case-sensitive, order-insensitive). Useful when several
|
|
65
|
+
* pipe tables share a document and you want to select "the one with a
|
|
66
|
+
* Mode column".
|
|
67
|
+
*/
|
|
68
|
+
export declare function findTableByColumns(lines: string[], requiredColumns: string[]): MarkdownTable | null;
|
|
69
|
+
/**
|
|
70
|
+
* Returns the first table that appears **after** a heading matching the
|
|
71
|
+
* given regex. The heading regex is applied to raw lines (no trimming)
|
|
72
|
+
* so callers that want leading whitespace flexibility should include
|
|
73
|
+
* `^\s*` or `^#+\s*` as appropriate.
|
|
74
|
+
*/
|
|
75
|
+
export declare function findTableAfterHeading(lines: string[], headingPattern: RegExp): MarkdownTable | null;
|
|
76
|
+
/**
|
|
77
|
+
* Serialises a single row using the same `| a | b | c |` layout the rest
|
|
78
|
+
* of the token docs use. The leading and trailing pipes are always emitted
|
|
79
|
+
* so that concatenated rows line up consistently. Cell values containing
|
|
80
|
+
* literal `|` or `\` are escaped as `\|` and `\\` respectively so the
|
|
81
|
+
* output survives a round-trip through {@link findNextTable}.
|
|
82
|
+
*/
|
|
83
|
+
export declare function serializeRow(cells: string[]): string;
|
|
84
|
+
/**
|
|
85
|
+
* Inserts a new row into `table.rows` at the given zero-based index. A
|
|
86
|
+
* negative index counts from the end; `Infinity` appends. Mutates the
|
|
87
|
+
* table object so callers using {@link rewriteTableRows} see the update.
|
|
88
|
+
*/
|
|
89
|
+
export declare function insertRow(table: MarkdownTable, cells: string[], index: number): void;
|
|
90
|
+
/**
|
|
91
|
+
* Produces a new `lines` array with the given table region replaced by a
|
|
92
|
+
* freshly-serialized version of the table's current headers + rows. The
|
|
93
|
+
* separator row is regenerated from the current header count so edits
|
|
94
|
+
* that change the column count stay consistent.
|
|
95
|
+
*/
|
|
96
|
+
export declare function rewriteTableRows(lines: string[], table: MarkdownTable): string[];
|
|
97
|
+
/**
|
|
98
|
+
* Updates a specific cell in the first row whose `keyColumn` cell matches
|
|
99
|
+
* `keyValue`. Returns `true` when a row was updated.
|
|
100
|
+
*
|
|
101
|
+
* Used by token-manager to bump the mode-count table without relying on
|
|
102
|
+
* brittle regex patterns that assume specific whitespace.
|
|
103
|
+
*/
|
|
104
|
+
export declare function updateCellByKey(table: MarkdownTable, keyColumn: string, keyValue: string, targetColumn: string, newValue: string): boolean;
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Minimal Markdown pipe-table parser/writer used by token-manager.
|
|
4
|
+
*
|
|
5
|
+
* This is **not** a general Markdown parser. It understands exactly the
|
|
6
|
+
* slice of GitHub-flavoured Markdown that FireForge emits into token docs:
|
|
7
|
+
*
|
|
8
|
+
* - Leading `|` per row, trailing `|` optional.
|
|
9
|
+
* - A separator row of `|---|---|...|` immediately after the header.
|
|
10
|
+
* - Fenced code blocks (``` / ~~~) are recognised and skipped so that
|
|
11
|
+
* literal `|` lines inside code samples are never mistaken for rows.
|
|
12
|
+
* - A table ends on the first non-pipe line (blank line, heading, prose).
|
|
13
|
+
* - Literal `|` and `\` inside a cell are expressed as `\|` and `\\`
|
|
14
|
+
* respectively. The parser treats any other backslash as a literal
|
|
15
|
+
* so prose-style backslashes in cell values (e.g. a Windows path)
|
|
16
|
+
* still round-trip. Unescaping happens at split time; serialization
|
|
17
|
+
* re-applies escapes so round-trips are exact for well-formed input.
|
|
18
|
+
*
|
|
19
|
+
* The parser returns **positional metadata** (`startLine`, `endLine`) so
|
|
20
|
+
* callers can splice the table back into the surrounding document by
|
|
21
|
+
* range instead of hand-rolled line counters. Mutation operations return
|
|
22
|
+
* a fresh line array rather than mutating in place, matching the rest of
|
|
23
|
+
* the file-patching code in this codebase.
|
|
24
|
+
*
|
|
25
|
+
* Fallback tolerance: rows with more cells than the header get their
|
|
26
|
+
* overflow merged into the last column (using a literal `|` separator
|
|
27
|
+
* between the merged values). This is for the rare case where a FireForge
|
|
28
|
+
* writer emits an unescaped pipe — the parser still produces a consistent
|
|
29
|
+
* cell count rather than desynchronising the table. On re-serialize the
|
|
30
|
+
* merged cell's literal pipes are escaped, so a malformed input becomes
|
|
31
|
+
* a well-formed output after one round trip.
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* Matches the table separator row: optional leading pipe, one or more
|
|
35
|
+
* dash-only cells (with optional `:` alignment hints) separated by pipes,
|
|
36
|
+
* optional trailing pipe, allowing whitespace around each cell.
|
|
37
|
+
*/
|
|
38
|
+
const SEPARATOR_ROW = /^\s*\|?\s*:?-+:?\s*(\|\s*:?-+:?\s*)+\|?\s*$/;
|
|
39
|
+
/**
|
|
40
|
+
* Returns `true` if the given line looks like a table row (starts with
|
|
41
|
+
* `|`, ignoring leading whitespace). Does not validate cell contents —
|
|
42
|
+
* that is the caller's job.
|
|
43
|
+
*/
|
|
44
|
+
function isPipeRow(line) {
|
|
45
|
+
return /^\s*\|/.test(line);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Splits a pipe-delimited row into trimmed cell strings.
|
|
49
|
+
*
|
|
50
|
+
* Tolerates both `| a | b |` and `| a | b` (no trailing pipe). Leading
|
|
51
|
+
* empty cells (from a leading `|`) are dropped. A trailing empty cell
|
|
52
|
+
* (from a trailing `|`) is also dropped so that rows with and without
|
|
53
|
+
* the trailing pipe produce the same cell count.
|
|
54
|
+
*
|
|
55
|
+
* Escape handling: `\|` is consumed as a literal `|` inside a cell, and
|
|
56
|
+
* `\\` as a literal `\`. Any other backslash stays literal so prose-style
|
|
57
|
+
* uses of `\` survive untouched. Splitting and unescaping happen in the
|
|
58
|
+
* same pass so the returned cells are ready to hand back to callers.
|
|
59
|
+
*/
|
|
60
|
+
function splitRow(row) {
|
|
61
|
+
const trimmed = row.trim();
|
|
62
|
+
const cells = [];
|
|
63
|
+
let current = '';
|
|
64
|
+
for (let i = 0; i < trimmed.length; i++) {
|
|
65
|
+
const ch = trimmed[i];
|
|
66
|
+
if (ch === '\\' && i + 1 < trimmed.length) {
|
|
67
|
+
const next = trimmed[i + 1];
|
|
68
|
+
if (next === '|' || next === '\\') {
|
|
69
|
+
current += next;
|
|
70
|
+
i++;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (ch === '|') {
|
|
75
|
+
cells.push(current);
|
|
76
|
+
current = '';
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
current += ch ?? '';
|
|
80
|
+
}
|
|
81
|
+
cells.push(current);
|
|
82
|
+
// Leading `|` gives an empty first segment; drop it.
|
|
83
|
+
if (cells.length > 0 && cells[0] === '') {
|
|
84
|
+
cells.shift();
|
|
85
|
+
}
|
|
86
|
+
// Trailing `|` gives an empty last segment; drop it.
|
|
87
|
+
if (cells.length > 0 && cells[cells.length - 1] === '') {
|
|
88
|
+
cells.pop();
|
|
89
|
+
}
|
|
90
|
+
return cells.map((cell) => cell.trim());
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Escapes a cell value so `serializeRow`'s pipe delimiters can be told
|
|
94
|
+
* apart from literal `|` inside a cell on re-parse. `\` is escaped first
|
|
95
|
+
* (to `\\`) so that the subsequent `|` → `\|` substitution cannot produce
|
|
96
|
+
* an already-escaped sequence by accident — i.e. a cell containing
|
|
97
|
+
* literal `\` followed by literal `|` round-trips as `\\\|`, which
|
|
98
|
+
* unescape-on-split reads back as `\` + `|`, not as `\|`.
|
|
99
|
+
*/
|
|
100
|
+
function escapeCell(cell) {
|
|
101
|
+
return cell.replace(/\\/g, '\\\\').replace(/\|/g, '\\|');
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Fits a row of cells against a target column count. Extra cells are
|
|
105
|
+
* merged into the last column (so a stray `|` in a cell's value does not
|
|
106
|
+
* desynchronise the table), and missing cells are padded with `''`.
|
|
107
|
+
*/
|
|
108
|
+
function fitRowToColumns(cells, columnCount) {
|
|
109
|
+
if (columnCount <= 0)
|
|
110
|
+
return [];
|
|
111
|
+
if (cells.length === columnCount) {
|
|
112
|
+
return cells;
|
|
113
|
+
}
|
|
114
|
+
if (cells.length < columnCount) {
|
|
115
|
+
return [...cells, ...Array(columnCount - cells.length).fill('')];
|
|
116
|
+
}
|
|
117
|
+
// cells.length > columnCount — merge overflow into the last column.
|
|
118
|
+
const kept = cells.slice(0, columnCount - 1);
|
|
119
|
+
const tail = cells.slice(columnCount - 1).join(' | ');
|
|
120
|
+
return [...kept, tail];
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Locates the next Markdown table in `lines` starting at or after
|
|
124
|
+
* `fromLine`, skipping fenced code blocks. Returns `null` when no table
|
|
125
|
+
* is found.
|
|
126
|
+
*
|
|
127
|
+
* @param lines - The full document split by newline
|
|
128
|
+
* @param fromLine - Line index to start scanning from (inclusive)
|
|
129
|
+
*/
|
|
130
|
+
export function findNextTable(lines, fromLine) {
|
|
131
|
+
let inFence = false;
|
|
132
|
+
let fenceMarker = null;
|
|
133
|
+
for (let i = fromLine; i < lines.length; i++) {
|
|
134
|
+
const line = lines[i] ?? '';
|
|
135
|
+
const fenceMatch = /^\s*(```|~~~)/.exec(line);
|
|
136
|
+
if (fenceMatch) {
|
|
137
|
+
if (!inFence) {
|
|
138
|
+
inFence = true;
|
|
139
|
+
fenceMarker = fenceMatch[1] ?? '```';
|
|
140
|
+
}
|
|
141
|
+
else if (fenceMarker && line.trim().startsWith(fenceMarker)) {
|
|
142
|
+
inFence = false;
|
|
143
|
+
fenceMarker = null;
|
|
144
|
+
}
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (inFence)
|
|
148
|
+
continue;
|
|
149
|
+
if (!isPipeRow(line))
|
|
150
|
+
continue;
|
|
151
|
+
const separatorLine = lines[i + 1];
|
|
152
|
+
if (separatorLine === undefined || !SEPARATOR_ROW.test(separatorLine))
|
|
153
|
+
continue;
|
|
154
|
+
const headers = splitRow(line);
|
|
155
|
+
if (headers.length === 0)
|
|
156
|
+
continue;
|
|
157
|
+
const rows = [];
|
|
158
|
+
let endLine = i + 2;
|
|
159
|
+
for (let j = i + 2; j < lines.length; j++) {
|
|
160
|
+
const rowLine = lines[j] ?? '';
|
|
161
|
+
if (!isPipeRow(rowLine))
|
|
162
|
+
break;
|
|
163
|
+
// Another separator line would signal a malformed table — bail.
|
|
164
|
+
if (SEPARATOR_ROW.test(rowLine))
|
|
165
|
+
break;
|
|
166
|
+
rows.push(fitRowToColumns(splitRow(rowLine), headers.length));
|
|
167
|
+
endLine = j + 1;
|
|
168
|
+
}
|
|
169
|
+
return { headers, rows, startLine: i, endLine };
|
|
170
|
+
}
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Returns the first table whose header contains **all** of the provided
|
|
175
|
+
* column names (case-sensitive, order-insensitive). Useful when several
|
|
176
|
+
* pipe tables share a document and you want to select "the one with a
|
|
177
|
+
* Mode column".
|
|
178
|
+
*/
|
|
179
|
+
export function findTableByColumns(lines, requiredColumns) {
|
|
180
|
+
let cursor = 0;
|
|
181
|
+
for (;;) {
|
|
182
|
+
const table = findNextTable(lines, cursor);
|
|
183
|
+
if (!table)
|
|
184
|
+
return null;
|
|
185
|
+
const headerSet = new Set(table.headers);
|
|
186
|
+
const matches = requiredColumns.every((column) => headerSet.has(column));
|
|
187
|
+
if (matches)
|
|
188
|
+
return table;
|
|
189
|
+
cursor = table.endLine;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Returns the first table that appears **after** a heading matching the
|
|
194
|
+
* given regex. The heading regex is applied to raw lines (no trimming)
|
|
195
|
+
* so callers that want leading whitespace flexibility should include
|
|
196
|
+
* `^\s*` or `^#+\s*` as appropriate.
|
|
197
|
+
*/
|
|
198
|
+
export function findTableAfterHeading(lines, headingPattern) {
|
|
199
|
+
for (let i = 0; i < lines.length; i++) {
|
|
200
|
+
if (headingPattern.test(lines[i] ?? '')) {
|
|
201
|
+
return findNextTable(lines, i + 1);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Serialises a single row using the same `| a | b | c |` layout the rest
|
|
208
|
+
* of the token docs use. The leading and trailing pipes are always emitted
|
|
209
|
+
* so that concatenated rows line up consistently. Cell values containing
|
|
210
|
+
* literal `|` or `\` are escaped as `\|` and `\\` respectively so the
|
|
211
|
+
* output survives a round-trip through {@link findNextTable}.
|
|
212
|
+
*/
|
|
213
|
+
export function serializeRow(cells) {
|
|
214
|
+
if (cells.length === 0)
|
|
215
|
+
return '|';
|
|
216
|
+
return `| ${cells.map(escapeCell).join(' | ')} |`;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Inserts a new row into `table.rows` at the given zero-based index. A
|
|
220
|
+
* negative index counts from the end; `Infinity` appends. Mutates the
|
|
221
|
+
* table object so callers using {@link rewriteTableRows} see the update.
|
|
222
|
+
*/
|
|
223
|
+
export function insertRow(table, cells, index) {
|
|
224
|
+
const fitted = fitRowToColumns(cells, table.headers.length);
|
|
225
|
+
const target = Math.max(0, Math.min(index, table.rows.length));
|
|
226
|
+
table.rows.splice(target, 0, fitted);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Produces a new `lines` array with the given table region replaced by a
|
|
230
|
+
* freshly-serialized version of the table's current headers + rows. The
|
|
231
|
+
* separator row is regenerated from the current header count so edits
|
|
232
|
+
* that change the column count stay consistent.
|
|
233
|
+
*/
|
|
234
|
+
export function rewriteTableRows(lines, table) {
|
|
235
|
+
const headerLine = serializeRow(table.headers);
|
|
236
|
+
const separatorLine = serializeRow(table.headers.map(() => '---'));
|
|
237
|
+
const rowLines = table.rows.map((row) => serializeRow(row));
|
|
238
|
+
return [
|
|
239
|
+
...lines.slice(0, table.startLine),
|
|
240
|
+
headerLine,
|
|
241
|
+
separatorLine,
|
|
242
|
+
...rowLines,
|
|
243
|
+
...lines.slice(table.endLine),
|
|
244
|
+
];
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Updates a specific cell in the first row whose `keyColumn` cell matches
|
|
248
|
+
* `keyValue`. Returns `true` when a row was updated.
|
|
249
|
+
*
|
|
250
|
+
* Used by token-manager to bump the mode-count table without relying on
|
|
251
|
+
* brittle regex patterns that assume specific whitespace.
|
|
252
|
+
*/
|
|
253
|
+
export function updateCellByKey(table, keyColumn, keyValue, targetColumn, newValue) {
|
|
254
|
+
const keyIndex = table.headers.indexOf(keyColumn);
|
|
255
|
+
const targetIndex = table.headers.indexOf(targetColumn);
|
|
256
|
+
if (keyIndex === -1 || targetIndex === -1)
|
|
257
|
+
return false;
|
|
258
|
+
for (const row of table.rows) {
|
|
259
|
+
if (row[keyIndex] === keyValue) {
|
|
260
|
+
row[targetIndex] = newValue;
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
//# sourceMappingURL=markdown-table.js.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { PatchMetadata } from '../types/commands/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* A row in the flat path → owning-patch ownership table.
|
|
4
|
+
*/
|
|
5
|
+
export interface OwnershipRow {
|
|
6
|
+
path: string;
|
|
7
|
+
owners: string[];
|
|
8
|
+
conflict: boolean;
|
|
9
|
+
conflictReason: 'files-affected' | 'duplicate-create' | null;
|
|
10
|
+
unmanaged: boolean;
|
|
11
|
+
}
|
|
12
|
+
interface StatusFile {
|
|
13
|
+
status: string;
|
|
14
|
+
file: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Builds the flat ownership table from the manifest, worktree status, and
|
|
18
|
+
* the duplicate-new-file-creation map produced by cross-patch lint.
|
|
19
|
+
*
|
|
20
|
+
* Populated from three sources:
|
|
21
|
+
*
|
|
22
|
+
* 1. Every path in every patch's `filesAffected` — so managed paths
|
|
23
|
+
* show up even when they are not currently modified on disk.
|
|
24
|
+
* 2. Any worktree status entries not claimed by any patch (flagged as
|
|
25
|
+
* `unmanaged`).
|
|
26
|
+
* 3. Paths created by more than one patch in `new file mode`,
|
|
27
|
+
* surfaced as ownership conflicts with
|
|
28
|
+
* `conflictReason = 'duplicate-create'`. This is the alignment
|
|
29
|
+
* fix between `status --ownership` and `fireforge verify`: a
|
|
30
|
+
* queue that `verify` rejects for duplicate `/dev/null → b/foo.js`
|
|
31
|
+
* creations was previously reported as clean here because the
|
|
32
|
+
* check only walked `filesAffected`, not the patch bodies.
|
|
33
|
+
*
|
|
34
|
+
* A path claimed by more than one patch (either via `filesAffected` or
|
|
35
|
+
* as a duplicate creation) is flagged as `conflict` with the origin
|
|
36
|
+
* recorded in `conflictReason` so the output can disambiguate.
|
|
37
|
+
*
|
|
38
|
+
* @param manifestPatches - Manifest rows, each with its `filesAffected`
|
|
39
|
+
* @param worktreeFiles - Raw worktree status entries; untracked and
|
|
40
|
+
* modified both acceptable
|
|
41
|
+
* @param newFileCreatorsByPath - Map produced by
|
|
42
|
+
* {@link import('../core/patch-lint.js').collectNewFileCreatorsByPath};
|
|
43
|
+
* paths with a `.length > 1` owner list become duplicate-create conflicts
|
|
44
|
+
*/
|
|
45
|
+
export declare function buildOwnershipTable(manifestPatches: PatchMetadata[], worktreeFiles: StatusFile[], newFileCreatorsByPath: Map<string, string[]>): OwnershipRow[];
|
|
46
|
+
/**
|
|
47
|
+
* Renders the ownership table as a GitHub-flavored Markdown pipe table.
|
|
48
|
+
* Using markdown-table's own serializer would require a seed document to
|
|
49
|
+
* graft onto, which is overkill for ad-hoc status output; the rendering
|
|
50
|
+
* here is trivial enough to inline.
|
|
51
|
+
*/
|
|
52
|
+
export declare function renderOwnershipTable(rows: OwnershipRow[]): void;
|
|
53
|
+
export {};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { info } from '../utils/logger.js';
|
|
2
|
+
/**
|
|
3
|
+
* Builds the flat ownership table from the manifest, worktree status, and
|
|
4
|
+
* the duplicate-new-file-creation map produced by cross-patch lint.
|
|
5
|
+
*
|
|
6
|
+
* Populated from three sources:
|
|
7
|
+
*
|
|
8
|
+
* 1. Every path in every patch's `filesAffected` — so managed paths
|
|
9
|
+
* show up even when they are not currently modified on disk.
|
|
10
|
+
* 2. Any worktree status entries not claimed by any patch (flagged as
|
|
11
|
+
* `unmanaged`).
|
|
12
|
+
* 3. Paths created by more than one patch in `new file mode`,
|
|
13
|
+
* surfaced as ownership conflicts with
|
|
14
|
+
* `conflictReason = 'duplicate-create'`. This is the alignment
|
|
15
|
+
* fix between `status --ownership` and `fireforge verify`: a
|
|
16
|
+
* queue that `verify` rejects for duplicate `/dev/null → b/foo.js`
|
|
17
|
+
* creations was previously reported as clean here because the
|
|
18
|
+
* check only walked `filesAffected`, not the patch bodies.
|
|
19
|
+
*
|
|
20
|
+
* A path claimed by more than one patch (either via `filesAffected` or
|
|
21
|
+
* as a duplicate creation) is flagged as `conflict` with the origin
|
|
22
|
+
* recorded in `conflictReason` so the output can disambiguate.
|
|
23
|
+
*
|
|
24
|
+
* @param manifestPatches - Manifest rows, each with its `filesAffected`
|
|
25
|
+
* @param worktreeFiles - Raw worktree status entries; untracked and
|
|
26
|
+
* modified both acceptable
|
|
27
|
+
* @param newFileCreatorsByPath - Map produced by
|
|
28
|
+
* {@link import('../core/patch-lint.js').collectNewFileCreatorsByPath};
|
|
29
|
+
* paths with a `.length > 1` owner list become duplicate-create conflicts
|
|
30
|
+
*/
|
|
31
|
+
export function buildOwnershipTable(manifestPatches, worktreeFiles, newFileCreatorsByPath) {
|
|
32
|
+
const ownersByPath = new Map();
|
|
33
|
+
for (const patch of manifestPatches) {
|
|
34
|
+
for (const file of patch.filesAffected) {
|
|
35
|
+
const existing = ownersByPath.get(file) ?? [];
|
|
36
|
+
existing.push(patch.filename);
|
|
37
|
+
ownersByPath.set(file, existing);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Merge duplicate-new-file-creation findings. The structured helper
|
|
41
|
+
// returns all new-file paths, so we filter to the ones with more
|
|
42
|
+
// than one creator. Paths are added to the table even when no patch
|
|
43
|
+
// listed them in `filesAffected`, because the two-patch duplicate
|
|
44
|
+
// creation is exactly the shape that manifests as an ownership
|
|
45
|
+
// conflict without showing up in filesAffected (e.g. drift caused
|
|
46
|
+
// by manual patches.json editing).
|
|
47
|
+
const duplicateCreateByPath = new Map();
|
|
48
|
+
for (const [path, creators] of newFileCreatorsByPath) {
|
|
49
|
+
if (creators.length <= 1)
|
|
50
|
+
continue;
|
|
51
|
+
duplicateCreateByPath.set(path, creators);
|
|
52
|
+
if (!ownersByPath.has(path)) {
|
|
53
|
+
ownersByPath.set(path, [...creators]);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const worktreeSet = new Set(worktreeFiles.map((f) => f.file));
|
|
57
|
+
const unmanagedOnly = [];
|
|
58
|
+
for (const path of worktreeSet) {
|
|
59
|
+
if (!ownersByPath.has(path)) {
|
|
60
|
+
unmanagedOnly.push(path);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const rows = [];
|
|
64
|
+
for (const [path, owners] of ownersByPath) {
|
|
65
|
+
const duplicateOwners = duplicateCreateByPath.get(path);
|
|
66
|
+
const isFilesAffectedConflict = owners.length > 1;
|
|
67
|
+
const isDuplicateCreateConflict = duplicateOwners !== undefined;
|
|
68
|
+
// Prefer the filesAffected reason when both apply — it's the older
|
|
69
|
+
// source of drift and the operator will usually want to fix the
|
|
70
|
+
// manifest rows even when the bodies also duplicate-create.
|
|
71
|
+
const conflictReason = isFilesAffectedConflict
|
|
72
|
+
? 'files-affected'
|
|
73
|
+
: isDuplicateCreateConflict
|
|
74
|
+
? 'duplicate-create'
|
|
75
|
+
: null;
|
|
76
|
+
// If the duplicate-create finding introduced new patches not in
|
|
77
|
+
// filesAffected, merge them into owners so the rendered cell lists
|
|
78
|
+
// everyone responsible for the conflict, matching what `verify`
|
|
79
|
+
// prints.
|
|
80
|
+
const mergedOwners = duplicateOwners
|
|
81
|
+
? Array.from(new Set([...owners, ...duplicateOwners])).sort((a, b) => a.localeCompare(b))
|
|
82
|
+
: owners;
|
|
83
|
+
rows.push({
|
|
84
|
+
path,
|
|
85
|
+
owners: mergedOwners,
|
|
86
|
+
conflict: isFilesAffectedConflict || isDuplicateCreateConflict,
|
|
87
|
+
conflictReason,
|
|
88
|
+
unmanaged: false,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
for (const path of unmanagedOnly) {
|
|
92
|
+
rows.push({
|
|
93
|
+
path,
|
|
94
|
+
owners: [],
|
|
95
|
+
conflict: false,
|
|
96
|
+
conflictReason: null,
|
|
97
|
+
unmanaged: true,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
rows.sort((a, b) => a.path.localeCompare(b.path));
|
|
101
|
+
return rows;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Human-readable label for the conflict column. Distinguishes
|
|
105
|
+
* `files-affected` drift (two manifest rows claiming the same path) from
|
|
106
|
+
* `duplicate-create` drift (two patches both hitting `/dev/null → b/path`
|
|
107
|
+
* in their bodies) so the operator can tell at a glance which fix
|
|
108
|
+
* applies — the former wants `re-export --files`, the latter wants
|
|
109
|
+
* `patch delete`.
|
|
110
|
+
*/
|
|
111
|
+
function renderConflictCell(row) {
|
|
112
|
+
if (!row.conflict)
|
|
113
|
+
return row.unmanaged ? 'unmanaged' : '';
|
|
114
|
+
if (row.conflictReason === 'duplicate-create')
|
|
115
|
+
return 'CONFLICT (dup-create)';
|
|
116
|
+
return 'CONFLICT';
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Renders the ownership table as a GitHub-flavored Markdown pipe table.
|
|
120
|
+
* Using markdown-table's own serializer would require a seed document to
|
|
121
|
+
* graft onto, which is overkill for ad-hoc status output; the rendering
|
|
122
|
+
* here is trivial enough to inline.
|
|
123
|
+
*/
|
|
124
|
+
export function renderOwnershipTable(rows) {
|
|
125
|
+
if (rows.length === 0) {
|
|
126
|
+
info('No tracked or modified files.');
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const pathHeader = 'path';
|
|
130
|
+
const ownerHeader = 'owning patch';
|
|
131
|
+
const conflictHeader = 'conflict';
|
|
132
|
+
const pathWidth = Math.max(pathHeader.length, ...rows.map((r) => r.path.length));
|
|
133
|
+
const ownerWidth = Math.max(ownerHeader.length, ...rows.map((r) => (r.unmanaged ? 1 : r.owners.join(', ').length)));
|
|
134
|
+
const conflictWidth = Math.max(conflictHeader.length, ...rows.map((r) => renderConflictCell(r).length), 8);
|
|
135
|
+
const pad = (text, width) => text + ' '.repeat(width - text.length);
|
|
136
|
+
info(`| ${pad(pathHeader, pathWidth)} | ${pad(ownerHeader, ownerWidth)} | ${pad(conflictHeader, conflictWidth)} |`);
|
|
137
|
+
info(`| ${'-'.repeat(pathWidth)} | ${'-'.repeat(ownerWidth)} | ${'-'.repeat(conflictWidth)} |`);
|
|
138
|
+
for (const row of rows) {
|
|
139
|
+
const ownerCell = row.unmanaged ? '-' : row.owners.join(', ');
|
|
140
|
+
const conflictCell = renderConflictCell(row);
|
|
141
|
+
info(`| ${pad(row.path, pathWidth)} | ${pad(ownerCell, ownerWidth)} | ${pad(conflictCell, conflictWidth)} |`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=ownership-table.js.map
|