faf-taf-git 2.0.4 → 2.2.2
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/.github/workflows/ci.yml +53 -0
- package/.github/workflows/claude-code-review.yml.disabled +29 -0
- package/.github/workflows/claude.yml +39 -0
- package/.github/workflows/homebrew.yml +19 -0
- package/.taf +79 -2
- package/CHANGELOG.md +32 -0
- package/CLAUDE.md +29 -21
- package/CODE_OF_CONDUCT.md +21 -0
- package/CONTRIBUTING.md +3 -0
- package/README.md +75 -12
- package/SECURITY.md +19 -0
- package/action.yml +7 -2
- package/badge.svg +21 -0
- package/dist/badge.d.ts +23 -0
- package/dist/badge.d.ts.map +1 -0
- package/dist/badge.js +147 -0
- package/dist/badge.js.map +1 -0
- package/dist/cli.d.ts +5 -6
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +144 -193
- package/dist/cli.js.map +1 -1
- package/dist/index.js +26045 -20566
- package/dist/index.js.map +1 -1
- package/dist/licenses.txt +0 -22
- package/dist/parsers/bun.d.ts +59 -0
- package/dist/parsers/bun.d.ts.map +1 -0
- package/dist/parsers/bun.js +100 -0
- package/dist/parsers/bun.js.map +1 -0
- package/dist/parsers/index.d.ts +20 -0
- package/dist/parsers/index.d.ts.map +1 -0
- package/dist/parsers/index.js +26 -0
- package/dist/parsers/index.js.map +1 -0
- package/dist/parsers/vitest.d.ts +17 -0
- package/dist/parsers/vitest.d.ts.map +1 -0
- package/dist/parsers/vitest.js +61 -0
- package/dist/parsers/vitest.js.map +1 -0
- package/package.json +6 -7
- package/project.faf +20 -24
- package/src/badge.ts +138 -0
- package/src/cli.ts +154 -200
- package/src/index.ts +79 -10
- package/src/parsers/bun.ts +103 -0
- package/src/parsers/index.ts +27 -0
- package/src/parsers/vitest.ts +65 -0
- package/tests/badge.test.ts +260 -0
- package/tests/bun-parser.test.ts +173 -0
- package/tests/vitest-parser.test.ts +112 -0
- package/.faf +0 -126
package/dist/licenses.txt
CHANGED
|
@@ -59,28 +59,6 @@ The above copyright notice and this permission notice shall be included in all c
|
|
|
59
59
|
|
|
60
60
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
61
61
|
|
|
62
|
-
@fastify/busboy
|
|
63
|
-
MIT
|
|
64
|
-
Copyright Brian White. All rights reserved.
|
|
65
|
-
|
|
66
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
67
|
-
of this software and associated documentation files (the "Software"), to
|
|
68
|
-
deal in the Software without restriction, including without limitation the
|
|
69
|
-
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
70
|
-
sell copies of the Software, and to permit persons to whom the Software is
|
|
71
|
-
furnished to do so, subject to the following conditions:
|
|
72
|
-
|
|
73
|
-
The above copyright notice and this permission notice shall be included in
|
|
74
|
-
all copies or substantial portions of the Software.
|
|
75
|
-
|
|
76
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
77
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
78
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
79
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
80
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
81
|
-
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
82
|
-
IN THE SOFTWARE.
|
|
83
|
-
|
|
84
62
|
tunnel
|
|
85
63
|
MIT
|
|
86
64
|
The MIT License (MIT)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bun test output parser
|
|
3
|
+
*
|
|
4
|
+
* Extracts test counts from `bun test` CLI output.
|
|
5
|
+
*/
|
|
6
|
+
import { TestResults } from './jest';
|
|
7
|
+
/**
|
|
8
|
+
* Parse Bun test output to extract test results.
|
|
9
|
+
*
|
|
10
|
+
* Bun output format (summary block at the end of a run):
|
|
11
|
+
*
|
|
12
|
+
* ```
|
|
13
|
+
* 232 pass
|
|
14
|
+
* 1 skip
|
|
15
|
+
* 1 todo
|
|
16
|
+
* 0 fail
|
|
17
|
+
* 848 expect() calls
|
|
18
|
+
* Ran 234 tests across 9 files. [21.53s]
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* Notes:
|
|
22
|
+
* - Anchored on `Ran <N> tests across <M> files.` — the canonical truthful
|
|
23
|
+
* total (includes skip + todo + pass + fail). Without this line, the parser
|
|
24
|
+
* returns null and the dispatcher falls through to jest/vitest.
|
|
25
|
+
* - Per-category counts (`pass`, `fail`, `skip`, `todo`) come from their own
|
|
26
|
+
* lines elsewhere in the same output. Singular forms — bun does not use
|
|
27
|
+
* `passed`/`failed`/`skipped` (intentional differentiator from jest/vitest).
|
|
28
|
+
* - `skipped` aggregates `skip` + `todo` (matches the convention vitest.ts
|
|
29
|
+
* already uses for parity in the shared TestResults shape).
|
|
30
|
+
*/
|
|
31
|
+
export declare function parseBunOutput(output: string): TestResults | null;
|
|
32
|
+
/**
|
|
33
|
+
* Example Bun outputs for testing:
|
|
34
|
+
*
|
|
35
|
+
* All passing:
|
|
36
|
+
* ```
|
|
37
|
+
* 173 pass
|
|
38
|
+
* 0 fail
|
|
39
|
+
* Ran 173 tests across 8 files. [4.2s]
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* Mixed (the grok-faf-mcp 1.4.9 shape):
|
|
43
|
+
* ```
|
|
44
|
+
* 232 pass
|
|
45
|
+
* 1 skip
|
|
46
|
+
* 1 todo
|
|
47
|
+
* 0 fail
|
|
48
|
+
* 848 expect() calls
|
|
49
|
+
* Ran 234 tests across 9 files. [21.53s]
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* Failing:
|
|
53
|
+
* ```
|
|
54
|
+
* 170 pass
|
|
55
|
+
* 3 fail
|
|
56
|
+
* Ran 173 tests across 8 files. [5.1s]
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
//# sourceMappingURL=bun.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bun.d.ts","sourceRoot":"","sources":["file:///Users/wolfejam/FAF/faf-taf-git/src/parsers/bun.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAErC;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CA0CjE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Bun test output parser
|
|
4
|
+
*
|
|
5
|
+
* Extracts test counts from `bun test` CLI output.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.parseBunOutput = parseBunOutput;
|
|
9
|
+
/**
|
|
10
|
+
* Parse Bun test output to extract test results.
|
|
11
|
+
*
|
|
12
|
+
* Bun output format (summary block at the end of a run):
|
|
13
|
+
*
|
|
14
|
+
* ```
|
|
15
|
+
* 232 pass
|
|
16
|
+
* 1 skip
|
|
17
|
+
* 1 todo
|
|
18
|
+
* 0 fail
|
|
19
|
+
* 848 expect() calls
|
|
20
|
+
* Ran 234 tests across 9 files. [21.53s]
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* Notes:
|
|
24
|
+
* - Anchored on `Ran <N> tests across <M> files.` — the canonical truthful
|
|
25
|
+
* total (includes skip + todo + pass + fail). Without this line, the parser
|
|
26
|
+
* returns null and the dispatcher falls through to jest/vitest.
|
|
27
|
+
* - Per-category counts (`pass`, `fail`, `skip`, `todo`) come from their own
|
|
28
|
+
* lines elsewhere in the same output. Singular forms — bun does not use
|
|
29
|
+
* `passed`/`failed`/`skipped` (intentional differentiator from jest/vitest).
|
|
30
|
+
* - `skipped` aggregates `skip` + `todo` (matches the convention vitest.ts
|
|
31
|
+
* already uses for parity in the shared TestResults shape).
|
|
32
|
+
*/
|
|
33
|
+
function parseBunOutput(output) {
|
|
34
|
+
// Strip ANSI color codes + normalize line endings (parity with siblings).
|
|
35
|
+
// eslint-disable-next-line no-control-regex
|
|
36
|
+
let cleanOutput = output.replace(/\x1b\[[0-9;]*m/g, '');
|
|
37
|
+
cleanOutput = cleanOutput.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
38
|
+
// Canonical anchor: bun-unique "Ran N tests across M files." summary line.
|
|
39
|
+
// Without this, this isn't bun output — let the dispatcher try the others.
|
|
40
|
+
const ranMatch = cleanOutput.match(/^Ran\s+(\d+)\s+tests?\s+across\s+\d+\s+files?/m);
|
|
41
|
+
if (!ranMatch)
|
|
42
|
+
return null;
|
|
43
|
+
const total = parseInt(ranMatch[1], 10);
|
|
44
|
+
if (total === 0)
|
|
45
|
+
return null;
|
|
46
|
+
let passed = 0;
|
|
47
|
+
let failed = 0;
|
|
48
|
+
let skipped = 0;
|
|
49
|
+
// Per-line counts. Each line is `<leading-ws><N> <singular-word>` on its own.
|
|
50
|
+
// Anchored with ^...$ + multiline to avoid catching the "expect() calls" line
|
|
51
|
+
// or any noise inside test descriptions.
|
|
52
|
+
const passedMatch = cleanOutput.match(/^\s*(\d+)\s+pass\s*$/m);
|
|
53
|
+
if (passedMatch)
|
|
54
|
+
passed = parseInt(passedMatch[1], 10);
|
|
55
|
+
const failedMatch = cleanOutput.match(/^\s*(\d+)\s+fail\s*$/m);
|
|
56
|
+
if (failedMatch)
|
|
57
|
+
failed = parseInt(failedMatch[1], 10);
|
|
58
|
+
const skipMatch = cleanOutput.match(/^\s*(\d+)\s+skip\s*$/m);
|
|
59
|
+
if (skipMatch)
|
|
60
|
+
skipped += parseInt(skipMatch[1], 10);
|
|
61
|
+
const todoMatch = cleanOutput.match(/^\s*(\d+)\s+todo\s*$/m);
|
|
62
|
+
if (todoMatch)
|
|
63
|
+
skipped += parseInt(todoMatch[1], 10);
|
|
64
|
+
const result = failed > 0 ? 'FAILED' : 'PASSED';
|
|
65
|
+
return {
|
|
66
|
+
total,
|
|
67
|
+
passed,
|
|
68
|
+
failed,
|
|
69
|
+
skipped: skipped > 0 ? skipped : undefined,
|
|
70
|
+
result,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Example Bun outputs for testing:
|
|
75
|
+
*
|
|
76
|
+
* All passing:
|
|
77
|
+
* ```
|
|
78
|
+
* 173 pass
|
|
79
|
+
* 0 fail
|
|
80
|
+
* Ran 173 tests across 8 files. [4.2s]
|
|
81
|
+
* ```
|
|
82
|
+
*
|
|
83
|
+
* Mixed (the grok-faf-mcp 1.4.9 shape):
|
|
84
|
+
* ```
|
|
85
|
+
* 232 pass
|
|
86
|
+
* 1 skip
|
|
87
|
+
* 1 todo
|
|
88
|
+
* 0 fail
|
|
89
|
+
* 848 expect() calls
|
|
90
|
+
* Ran 234 tests across 9 files. [21.53s]
|
|
91
|
+
* ```
|
|
92
|
+
*
|
|
93
|
+
* Failing:
|
|
94
|
+
* ```
|
|
95
|
+
* 170 pass
|
|
96
|
+
* 3 fail
|
|
97
|
+
* Ran 173 tests across 8 files. [5.1s]
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
//# sourceMappingURL=bun.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bun.js","sourceRoot":"","sources":["../../src/parsers/bun.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;AA4BH,wCA0CC;AAlED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,SAAgB,cAAc,CAAC,MAAc;IAC3C,0EAA0E;IAC1E,4CAA4C;IAC5C,IAAI,WAAW,GAAG,MAAM,CAAC,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC,CAAC;IACxD,WAAW,GAAG,WAAW,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAEtE,2EAA2E;IAC3E,2EAA2E;IAC3E,MAAM,QAAQ,GAAG,WAAW,CAAC,KAAK,CAAC,gDAAgD,CAAC,CAAC;IACrF,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAE3B,MAAM,KAAK,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACxC,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAE7B,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,OAAO,GAAG,CAAC,CAAC;IAEhB,8EAA8E;IAC9E,8EAA8E;IAC9E,yCAAyC;IACzC,MAAM,WAAW,GAAG,WAAW,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;IAC/D,IAAI,WAAW;QAAE,MAAM,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEvD,MAAM,WAAW,GAAG,WAAW,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;IAC/D,IAAI,WAAW;QAAE,MAAM,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEvD,MAAM,SAAS,GAAG,WAAW,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;IAC7D,IAAI,SAAS;QAAE,OAAO,IAAI,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAErD,MAAM,SAAS,GAAG,WAAW,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;IAC7D,IAAI,SAAS;QAAE,OAAO,IAAI,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAErD,MAAM,MAAM,GAAwB,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC;IAErE,OAAO;QACL,KAAK;QACL,MAAM;QACN,MAAM;QACN,OAAO,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;QAC1C,MAAM;KACP,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified test output parser
|
|
3
|
+
*
|
|
4
|
+
* Tries each framework parser in order until one succeeds.
|
|
5
|
+
*/
|
|
6
|
+
import { TestResults } from './jest';
|
|
7
|
+
export { TestResults } from './jest';
|
|
8
|
+
/**
|
|
9
|
+
* Parse test output from any supported framework.
|
|
10
|
+
*
|
|
11
|
+
* Order matters: Bun is tried first because its `Ran N tests across M files`
|
|
12
|
+
* anchor is bun-unique — it never false-matches on jest/vitest output. Putting
|
|
13
|
+
* it first also lets bun consumers ship truthful totals even when their CI
|
|
14
|
+
* still has a legacy `Tests: N passed, N total` jest-shim line appended to the
|
|
15
|
+
* test output (the bun parser ignores it and reads the canonical `Ran` line).
|
|
16
|
+
*
|
|
17
|
+
* Tries parsers in order: Bun, Jest, Vitest.
|
|
18
|
+
*/
|
|
19
|
+
export declare function parseTestOutput(output: string): TestResults | null;
|
|
20
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["file:///Users/wolfejam/FAF/faf-taf-git/src/parsers/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAKrC,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAErC;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAElE"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Unified test output parser
|
|
4
|
+
*
|
|
5
|
+
* Tries each framework parser in order until one succeeds.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.parseTestOutput = parseTestOutput;
|
|
9
|
+
const bun_1 = require("./bun");
|
|
10
|
+
const jest_1 = require("./jest");
|
|
11
|
+
const vitest_1 = require("./vitest");
|
|
12
|
+
/**
|
|
13
|
+
* Parse test output from any supported framework.
|
|
14
|
+
*
|
|
15
|
+
* Order matters: Bun is tried first because its `Ran N tests across M files`
|
|
16
|
+
* anchor is bun-unique — it never false-matches on jest/vitest output. Putting
|
|
17
|
+
* it first also lets bun consumers ship truthful totals even when their CI
|
|
18
|
+
* still has a legacy `Tests: N passed, N total` jest-shim line appended to the
|
|
19
|
+
* test output (the bun parser ignores it and reads the canonical `Ran` line).
|
|
20
|
+
*
|
|
21
|
+
* Tries parsers in order: Bun, Jest, Vitest.
|
|
22
|
+
*/
|
|
23
|
+
function parseTestOutput(output) {
|
|
24
|
+
return (0, bun_1.parseBunOutput)(output) || (0, jest_1.parseJestOutput)(output) || (0, vitest_1.parseVitestOutput)(output);
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/parsers/index.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;AAoBH,0CAEC;AAnBD,+BAAuC;AACvC,iCAAyC;AACzC,qCAA6C;AAI7C;;;;;;;;;;GAUG;AACH,SAAgB,eAAe,CAAC,MAAc;IAC5C,OAAO,IAAA,oBAAc,EAAC,MAAM,CAAC,IAAI,IAAA,sBAAe,EAAC,MAAM,CAAC,IAAI,IAAA,0BAAiB,EAAC,MAAM,CAAC,CAAC;AACxF,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vitest output parser
|
|
3
|
+
*
|
|
4
|
+
* Extracts test counts from Vitest CLI output
|
|
5
|
+
*/
|
|
6
|
+
import { TestResults } from './jest';
|
|
7
|
+
/**
|
|
8
|
+
* Parse Vitest output to extract test results
|
|
9
|
+
*
|
|
10
|
+
* Vitest output formats:
|
|
11
|
+
* - " Tests 8 passed (8)"
|
|
12
|
+
* - " Tests 2 failed | 6 passed (8)"
|
|
13
|
+
* - " Tests 1 skipped | 7 passed (8)"
|
|
14
|
+
* - " Tests 1 failed | 2 skipped | 5 passed (8)"
|
|
15
|
+
*/
|
|
16
|
+
export declare function parseVitestOutput(output: string): TestResults | null;
|
|
17
|
+
//# sourceMappingURL=vitest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vitest.d.ts","sourceRoot":"","sources":["file:///Users/wolfejam/FAF/faf-taf-git/src/parsers/vitest.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAErC;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CA+CpE"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Vitest output parser
|
|
4
|
+
*
|
|
5
|
+
* Extracts test counts from Vitest CLI output
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.parseVitestOutput = parseVitestOutput;
|
|
9
|
+
/**
|
|
10
|
+
* Parse Vitest output to extract test results
|
|
11
|
+
*
|
|
12
|
+
* Vitest output formats:
|
|
13
|
+
* - " Tests 8 passed (8)"
|
|
14
|
+
* - " Tests 2 failed | 6 passed (8)"
|
|
15
|
+
* - " Tests 1 skipped | 7 passed (8)"
|
|
16
|
+
* - " Tests 1 failed | 2 skipped | 5 passed (8)"
|
|
17
|
+
*/
|
|
18
|
+
function parseVitestOutput(output) {
|
|
19
|
+
// Strip ANSI color codes
|
|
20
|
+
// eslint-disable-next-line no-control-regex
|
|
21
|
+
let cleanOutput = output.replace(/\x1b\[[0-9;]*m/g, '');
|
|
22
|
+
cleanOutput = cleanOutput.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
23
|
+
// Vitest summary pattern: " Tests ... (N)" where N is total in parens
|
|
24
|
+
const testSummaryPattern = /^\s*Tests\s+(.+?\(\d+\))/m;
|
|
25
|
+
const testLineMatch = cleanOutput.match(testSummaryPattern);
|
|
26
|
+
if (!testLineMatch || !testLineMatch[1]) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
const testLine = testLineMatch[1];
|
|
30
|
+
// Extract total from parentheses at end
|
|
31
|
+
const totalMatch = testLine.match(/\((\d+)\)\s*$/);
|
|
32
|
+
if (!totalMatch)
|
|
33
|
+
return null;
|
|
34
|
+
const total = parseInt(totalMatch[1], 10);
|
|
35
|
+
if (total === 0)
|
|
36
|
+
return null;
|
|
37
|
+
let passed = 0;
|
|
38
|
+
let failed = 0;
|
|
39
|
+
let skipped = 0;
|
|
40
|
+
const passedMatch = testLine.match(/(\d+)\s+passed/i);
|
|
41
|
+
if (passedMatch)
|
|
42
|
+
passed = parseInt(passedMatch[1], 10);
|
|
43
|
+
const failedMatch = testLine.match(/(\d+)\s+failed/i);
|
|
44
|
+
if (failedMatch)
|
|
45
|
+
failed = parseInt(failedMatch[1], 10);
|
|
46
|
+
const skippedMatch = testLine.match(/(\d+)\s+skipped/i);
|
|
47
|
+
if (skippedMatch)
|
|
48
|
+
skipped = parseInt(skippedMatch[1], 10);
|
|
49
|
+
const todoMatch = testLine.match(/(\d+)\s+todo/i);
|
|
50
|
+
if (todoMatch)
|
|
51
|
+
skipped += parseInt(todoMatch[1], 10);
|
|
52
|
+
let result = failed > 0 ? 'FAILED' : 'PASSED';
|
|
53
|
+
return {
|
|
54
|
+
total,
|
|
55
|
+
passed,
|
|
56
|
+
failed,
|
|
57
|
+
skipped: skipped > 0 ? skipped : undefined,
|
|
58
|
+
result,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=vitest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vitest.js","sourceRoot":"","sources":["../../src/parsers/vitest.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;AAaH,8CA+CC;AAxDD;;;;;;;;GAQG;AACH,SAAgB,iBAAiB,CAAC,MAAc;IAC9C,yBAAyB;IACzB,4CAA4C;IAC5C,IAAI,WAAW,GAAG,MAAM,CAAC,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC,CAAC;IACxD,WAAW,GAAG,WAAW,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAEtE,uEAAuE;IACvE,MAAM,kBAAkB,GAAG,2BAA2B,CAAC;IACvD,MAAM,aAAa,GAAG,WAAW,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC;IAE5D,IAAI,CAAC,aAAa,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC;QACxC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,QAAQ,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;IAElC,wCAAwC;IACxC,MAAM,UAAU,GAAG,QAAQ,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;IACnD,IAAI,CAAC,UAAU;QAAE,OAAO,IAAI,CAAC;IAC7B,MAAM,KAAK,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC1C,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAE7B,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,OAAO,GAAG,CAAC,CAAC;IAEhB,MAAM,WAAW,GAAG,QAAQ,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;IACtD,IAAI,WAAW;QAAE,MAAM,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEvD,MAAM,WAAW,GAAG,QAAQ,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;IACtD,IAAI,WAAW;QAAE,MAAM,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEvD,MAAM,YAAY,GAAG,QAAQ,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC;IACxD,IAAI,YAAY;QAAE,OAAO,GAAG,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAE1D,MAAM,SAAS,GAAG,QAAQ,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;IAClD,IAAI,SAAS;QAAE,OAAO,IAAI,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAErD,IAAI,MAAM,GAAwB,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC;IAEnE,OAAO;QACL,KAAK;QACL,MAAM;QACN,MAAM;QACN,OAAO,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;QAC1C,MAAM;KACP,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "faf-taf-git",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.2.2",
|
|
4
|
+
"description": "A Test Receipt Printer for git. Every CI run prints a receipt to .taf — append-only, timestamped, cannot be gamed. Proof over time.",
|
|
5
5
|
"main": "dist/cli.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"faf-taf-git": "dist/cli.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"build": "tsc",
|
|
11
|
-
"test": "jest"
|
|
12
|
-
"package": "ncc build src/index.ts -o dist --source-map --license licenses.txt"
|
|
10
|
+
"build": "tsc && ncc build src/index.ts -o dist --source-map --license licenses.txt",
|
|
11
|
+
"test": "jest"
|
|
13
12
|
},
|
|
14
13
|
"repository": {
|
|
15
14
|
"type": "git",
|
|
@@ -31,9 +30,9 @@
|
|
|
31
30
|
"author": "wolfejam",
|
|
32
31
|
"license": "MIT",
|
|
33
32
|
"dependencies": {
|
|
34
|
-
"@actions/core": "^
|
|
33
|
+
"@actions/core": "^2.0.3",
|
|
35
34
|
"@actions/exec": "^1.1.1",
|
|
36
|
-
"@actions/github": "^6.0.
|
|
35
|
+
"@actions/github": "^6.0.1",
|
|
37
36
|
"yaml": "^2.3.4"
|
|
38
37
|
},
|
|
39
38
|
"devDependencies": {
|
package/project.faf
CHANGED
|
@@ -4,7 +4,7 @@ ai_score: 65%
|
|
|
4
4
|
ai_confidence: MODERATE
|
|
5
5
|
ai_value: 30_seconds_replaces_20_minutes_of_questions
|
|
6
6
|
ai_tldr:
|
|
7
|
-
project: faf-taf-git v2.
|
|
7
|
+
project: faf-taf-git v2.1.1 - A Test Receipt Printer for git. Proof over time, cannot be gamed.
|
|
8
8
|
stack: TypeScript CLI + GitHub Action (pre-capture architecture)
|
|
9
9
|
quality_bar: ZERO_ERRORS_F1_STANDARDS
|
|
10
10
|
status: PRODUCTION_READY (working in faf-cli repo)
|
|
@@ -12,15 +12,13 @@ ai_tldr:
|
|
|
12
12
|
current_focus: Ecosystem adoption and user onboarding
|
|
13
13
|
your_role: Support users, fix bugs, add test framework support
|
|
14
14
|
instant_context:
|
|
15
|
-
what_building:
|
|
15
|
+
what_building: A Test Receipt Printer for git. CLI runs on any CI/CD; prints append-only test receipts to .taf, kept in git. FAF-aware optionally — receipts can carry FAF score/identity, but TAF works on any repo.
|
|
16
16
|
tech_stack: TypeScript
|
|
17
17
|
main_language: TypeScript
|
|
18
18
|
deployment: npm + GitHub Actions Marketplace
|
|
19
19
|
architecture: v2.0.0 pre-capture pattern (separate test execution from receipt generation)
|
|
20
|
-
use_cases: Standalone CLI, GitHub Action, GitLab CI, Jenkins, CircleCI, Local
|
|
21
|
-
|
|
22
|
-
test_support: Jest (fully supported) with plans for Mocha, Vitest, Pytest, Go
|
|
23
|
-
test, Rust cargo
|
|
20
|
+
use_cases: Standalone CLI, GitHub Action, GitLab CI, Jenkins, CircleCI, Local development
|
|
21
|
+
test_support: Jest (fully supported) with plans for Mocha, Vitest, Pytest, Go test, Rust cargo
|
|
24
22
|
golden_triangle: .faf (project DNA) + repo (code) + .taf (test proof)
|
|
25
23
|
key_files:
|
|
26
24
|
- src/index.ts (v2.0.0 rewrite - reads test output file)
|
|
@@ -40,19 +38,18 @@ context_quality:
|
|
|
40
38
|
missing_context: []
|
|
41
39
|
project:
|
|
42
40
|
name: faf-taf-git
|
|
43
|
-
goal: Platform-agnostic
|
|
41
|
+
goal: Platform-agnostic CLI + GitHub Action for TAF updates
|
|
44
42
|
innovation: On-the-fly Testing Audit Files - Unheard of in CI/CD
|
|
45
43
|
taf_meaning:
|
|
46
44
|
file: Testing Audit File (.taf)
|
|
47
45
|
feed: Testing Activity Feed (live updates)
|
|
48
46
|
main_language: TypeScript
|
|
49
|
-
type:
|
|
47
|
+
type: cli
|
|
50
48
|
generated: 2025-10-28T01:26:05.656Z
|
|
51
49
|
repository: https://github.com/Wolfe-Jam/faf-taf-git
|
|
52
50
|
license: MIT
|
|
53
51
|
author: wolfejam
|
|
54
|
-
keywords: testing, taf, ci-cd, github-actions, gitlab-ci, platform-agnostic,
|
|
55
|
-
jest, faf, git
|
|
52
|
+
keywords: testing, taf, ci-cd, github-actions, gitlab-ci, platform-agnostic, jest, faf, git
|
|
56
53
|
mission: 🚀 Make Your AI Happy! 🧡 Trust-Driven 🤖
|
|
57
54
|
revolution: 30 seconds replaces 20 minutes of questions
|
|
58
55
|
brand: F1-Inspired Software Engineering - Championship AI Context
|
|
@@ -71,13 +68,14 @@ ai_instructions:
|
|
|
71
68
|
- All TypeScript must pass strict mode
|
|
72
69
|
- Test coverage required for new features
|
|
73
70
|
stack:
|
|
74
|
-
frontend:
|
|
75
|
-
css_framework:
|
|
76
|
-
ui_library:
|
|
77
|
-
state_management:
|
|
78
|
-
backend:
|
|
71
|
+
frontend: slotignored
|
|
72
|
+
css_framework: slotignored
|
|
73
|
+
ui_library: slotignored
|
|
74
|
+
state_management: slotignored
|
|
75
|
+
backend: slotignored
|
|
79
76
|
runtime: Node.js
|
|
80
|
-
database:
|
|
77
|
+
database: slotignored
|
|
78
|
+
connection: slotignored
|
|
81
79
|
build: TypeScript Compiler
|
|
82
80
|
package_manager: npm
|
|
83
81
|
api_type: REST
|
|
@@ -130,8 +128,8 @@ taf_format:
|
|
|
130
128
|
snapshots_faf_score: true
|
|
131
129
|
closed_loop_system: .faf (DNA) + repo (code) + .taf (proof) = Golden Triangle
|
|
132
130
|
deployment:
|
|
133
|
-
npm_package: faf-taf-git@2.
|
|
134
|
-
github_action: Wolfe-Jam/faf-taf-git@v2.
|
|
131
|
+
npm_package: faf-taf-git@2.1.1
|
|
132
|
+
github_action: Wolfe-Jam/faf-taf-git@v2.1.1
|
|
135
133
|
marketplaces:
|
|
136
134
|
- npm Registry
|
|
137
135
|
- GitHub Actions Marketplace
|
|
@@ -184,7 +182,7 @@ resources:
|
|
|
184
182
|
discussions: https://github.com/Wolfe-Jam/faf/discussions
|
|
185
183
|
state:
|
|
186
184
|
phase: production
|
|
187
|
-
version: 2.
|
|
185
|
+
version: 2.1.1
|
|
188
186
|
focus: ecosystem_adoption
|
|
189
187
|
status: production_ready
|
|
190
188
|
achievements:
|
|
@@ -208,15 +206,13 @@ tags:
|
|
|
208
206
|
- open-source
|
|
209
207
|
user_defined: null
|
|
210
208
|
human_context:
|
|
211
|
-
who: Development teams
|
|
209
|
+
who: Development teams adopting test-receipt provenance — open-source maintainers proving honest test history, regulated industries (SOC 2 / ISO / HIPAA) needing append-only audit trails, supply-chain verifiers proving tests ran before artifacts shipped, AI-agent reviewers verifying agent code passes tests consistently, and FAF-ecosystem projects completing the CAR Framework (Claim → Audit → Receipt).
|
|
212
210
|
what: TAF file updates in CI/CD pipelines
|
|
213
211
|
why: Improve development efficiency
|
|
214
212
|
where: Any CI/CD platform (GitHub, GitLab, Bitbucket)
|
|
215
213
|
when: During CI/CD test runs
|
|
216
214
|
how: Test-driven development
|
|
217
|
-
additional_context: Part of Golden Triangle (.faf + repo + .taf). Tracks test
|
|
218
|
-
runs permanently in git. Supports Jest, works as CLI or GitHub Action.
|
|
219
|
-
Platform-agnostic core - runs on ANY CI/CD with Node.js.
|
|
215
|
+
additional_context: Part of Golden Triangle (.faf + repo + .taf). Tracks test runs permanently in git. Supports Jest, works as CLI or GitHub Action. Platform-agnostic core - runs on ANY CI/CD with Node.js.
|
|
220
216
|
context_score: 0
|
|
221
217
|
total_prd_score: 65
|
|
222
218
|
success_rate: 50%
|
|
@@ -242,7 +238,7 @@ faf_dna:
|
|
|
242
238
|
birth_date: 2025-10-28T01:26:05.692Z
|
|
243
239
|
current_score: 50
|
|
244
240
|
projectName: faf-taf-git
|
|
245
|
-
projectGoal:
|
|
241
|
+
projectGoal: A Test Receipt Printer for git — append-only test receipts on every CI run. Proof over time, cannot be gamed.
|
|
246
242
|
generated: 2026-02-17T20:14:09.215Z
|
|
247
243
|
last_major_update: v2.0.4 (2026-02-17)
|
|
248
244
|
production_status: Working in faf-cli repository
|
package/src/badge.ts
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Badge Generator for faf-taf-git
|
|
3
|
+
*
|
|
4
|
+
* Generates shields.io-style SVG badges from .taf test history.
|
|
5
|
+
* Self-contained SVG with Verdana font, no external dependencies.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import * as yaml from 'yaml';
|
|
10
|
+
|
|
11
|
+
export interface BadgeOptions {
|
|
12
|
+
tafPath?: string;
|
|
13
|
+
label?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface BadgeResult {
|
|
17
|
+
success: boolean;
|
|
18
|
+
svg?: string;
|
|
19
|
+
error?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface LatestRun {
|
|
23
|
+
passed: number;
|
|
24
|
+
total: number;
|
|
25
|
+
failed: number;
|
|
26
|
+
skipped: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const COLOR_PASS = '#003366'; // Navy blue (deep — white-text contrast; cyan was too light)
|
|
30
|
+
const COLOR_FAIL = '#E32400'; // Red
|
|
31
|
+
const COLOR_NONE = '#9F9F9F'; // Gray
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Render a shields.io flat-style SVG badge.
|
|
35
|
+
*/
|
|
36
|
+
export function renderBadgeSvg(label: string, value: string, color: string): string {
|
|
37
|
+
// Approximate text widths using Verdana 11px (6.5px per char average)
|
|
38
|
+
const charWidth = 6.5;
|
|
39
|
+
const padding = 10;
|
|
40
|
+
const labelWidth = Math.round(label.length * charWidth + padding * 2);
|
|
41
|
+
const valueWidth = Math.round(value.length * charWidth + padding * 2);
|
|
42
|
+
const totalWidth = labelWidth + valueWidth;
|
|
43
|
+
const height = 20;
|
|
44
|
+
|
|
45
|
+
const labelX = labelWidth / 2;
|
|
46
|
+
const valueX = labelWidth + valueWidth / 2;
|
|
47
|
+
|
|
48
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${totalWidth}" height="${height}" role="img" aria-label="${label}: ${value}">
|
|
49
|
+
<title>${label}: ${value}</title>
|
|
50
|
+
<linearGradient id="s" x2="0" y2="100%">
|
|
51
|
+
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
|
|
52
|
+
<stop offset="1" stop-opacity=".1"/>
|
|
53
|
+
</linearGradient>
|
|
54
|
+
<clipPath id="r">
|
|
55
|
+
<rect width="${totalWidth}" height="${height}" rx="3" fill="#fff"/>
|
|
56
|
+
</clipPath>
|
|
57
|
+
<g clip-path="url(#r)">
|
|
58
|
+
<rect width="${labelWidth}" height="${height}" fill="#555"/>
|
|
59
|
+
<rect x="${labelWidth}" width="${valueWidth}" height="${height}" fill="${color}"/>
|
|
60
|
+
<rect width="${totalWidth}" height="${height}" fill="url(#s)"/>
|
|
61
|
+
</g>
|
|
62
|
+
<g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="11">
|
|
63
|
+
<text x="${labelX}" y="14" fill="#010101" fill-opacity=".3">${label}</text>
|
|
64
|
+
<text x="${labelX}" y="13" fill="#fff">${label}</text>
|
|
65
|
+
<text x="${valueX}" y="14" fill="#010101" fill-opacity=".3">${value}</text>
|
|
66
|
+
<text x="${valueX}" y="13" fill="#fff">${value}</text>
|
|
67
|
+
</g>
|
|
68
|
+
</svg>`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Read a .taf file and extract the latest test run.
|
|
73
|
+
*/
|
|
74
|
+
function getLatestRun(tafPath: string): LatestRun | null {
|
|
75
|
+
if (!fs.existsSync(tafPath)) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const content = fs.readFileSync(tafPath, 'utf-8');
|
|
80
|
+
|
|
81
|
+
// Handle YAML document separators (same logic as taf-core.ts)
|
|
82
|
+
const lines = content.split('\n');
|
|
83
|
+
const yamlLines: string[] = [];
|
|
84
|
+
for (const line of lines) {
|
|
85
|
+
if (line.trim() === '---' && yamlLines.length > 0) break;
|
|
86
|
+
if (line.trim() === '---' && yamlLines.length === 0) continue;
|
|
87
|
+
yamlLines.push(line);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const parsed = yaml.parse(yamlLines.join('\n'));
|
|
91
|
+
|
|
92
|
+
if (!parsed || typeof parsed !== 'object') return null;
|
|
93
|
+
if (!Array.isArray(parsed.test_history) || parsed.test_history.length === 0) return null;
|
|
94
|
+
|
|
95
|
+
const latest = parsed.test_history[parsed.test_history.length - 1];
|
|
96
|
+
if (!latest.tests || typeof latest.tests.total !== 'number') return null;
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
passed: latest.tests.passed ?? 0,
|
|
100
|
+
total: latest.tests.total,
|
|
101
|
+
failed: latest.tests.failed ?? 0,
|
|
102
|
+
skipped: latest.tests.skipped ?? 0,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Generate a badge SVG from a .taf file.
|
|
108
|
+
*/
|
|
109
|
+
export function generateBadge(options: BadgeOptions = {}): BadgeResult {
|
|
110
|
+
const label = options.label || 'TAF';
|
|
111
|
+
const tafPath = options.tafPath || '.taf';
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
const run = getLatestRun(tafPath);
|
|
115
|
+
|
|
116
|
+
if (!run) {
|
|
117
|
+
return {
|
|
118
|
+
success: true,
|
|
119
|
+
svg: renderBadgeSvg(label, 'no data', COLOR_NONE),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const skippedSuffix = run.skipped > 0 ? ` (${run.skipped} skipped)` : '';
|
|
124
|
+
const displayPassed = run.failed === 0 ? run.total : run.passed;
|
|
125
|
+
const value = `${displayPassed}/${run.total} passing${skippedSuffix}`;
|
|
126
|
+
const color = run.failed === 0 ? COLOR_PASS : COLOR_FAIL;
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
success: true,
|
|
130
|
+
svg: renderBadgeSvg(label, value, color),
|
|
131
|
+
};
|
|
132
|
+
} catch (error) {
|
|
133
|
+
return {
|
|
134
|
+
success: false,
|
|
135
|
+
error: error instanceof Error ? error.message : 'Failed to generate badge',
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
}
|