faf-taf-git 2.0.4 → 2.2.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/.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 +28 -0
- package/CLAUDE.md +29 -21
- package/CODE_OF_CONDUCT.md +21 -0
- package/CONTRIBUTING.md +3 -0
- package/README.md +69 -12
- package/SECURITY.md +19 -0
- package/action.yml +6 -1
- 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/badge.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Badge Generator for faf-taf-git
|
|
4
|
+
*
|
|
5
|
+
* Generates shields.io-style SVG badges from .taf test history.
|
|
6
|
+
* Self-contained SVG with Verdana font, no external dependencies.
|
|
7
|
+
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.renderBadgeSvg = renderBadgeSvg;
|
|
43
|
+
exports.generateBadge = generateBadge;
|
|
44
|
+
const fs = __importStar(require("fs"));
|
|
45
|
+
const yaml = __importStar(require("yaml"));
|
|
46
|
+
const COLOR_PASS = '#003366'; // Navy blue (deep — white-text contrast; cyan was too light)
|
|
47
|
+
const COLOR_FAIL = '#E32400'; // Red
|
|
48
|
+
const COLOR_NONE = '#9F9F9F'; // Gray
|
|
49
|
+
/**
|
|
50
|
+
* Render a shields.io flat-style SVG badge.
|
|
51
|
+
*/
|
|
52
|
+
function renderBadgeSvg(label, value, color) {
|
|
53
|
+
// Approximate text widths using Verdana 11px (6.5px per char average)
|
|
54
|
+
const charWidth = 6.5;
|
|
55
|
+
const padding = 10;
|
|
56
|
+
const labelWidth = Math.round(label.length * charWidth + padding * 2);
|
|
57
|
+
const valueWidth = Math.round(value.length * charWidth + padding * 2);
|
|
58
|
+
const totalWidth = labelWidth + valueWidth;
|
|
59
|
+
const height = 20;
|
|
60
|
+
const labelX = labelWidth / 2;
|
|
61
|
+
const valueX = labelWidth + valueWidth / 2;
|
|
62
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${totalWidth}" height="${height}" role="img" aria-label="${label}: ${value}">
|
|
63
|
+
<title>${label}: ${value}</title>
|
|
64
|
+
<linearGradient id="s" x2="0" y2="100%">
|
|
65
|
+
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
|
|
66
|
+
<stop offset="1" stop-opacity=".1"/>
|
|
67
|
+
</linearGradient>
|
|
68
|
+
<clipPath id="r">
|
|
69
|
+
<rect width="${totalWidth}" height="${height}" rx="3" fill="#fff"/>
|
|
70
|
+
</clipPath>
|
|
71
|
+
<g clip-path="url(#r)">
|
|
72
|
+
<rect width="${labelWidth}" height="${height}" fill="#555"/>
|
|
73
|
+
<rect x="${labelWidth}" width="${valueWidth}" height="${height}" fill="${color}"/>
|
|
74
|
+
<rect width="${totalWidth}" height="${height}" fill="url(#s)"/>
|
|
75
|
+
</g>
|
|
76
|
+
<g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="11">
|
|
77
|
+
<text x="${labelX}" y="14" fill="#010101" fill-opacity=".3">${label}</text>
|
|
78
|
+
<text x="${labelX}" y="13" fill="#fff">${label}</text>
|
|
79
|
+
<text x="${valueX}" y="14" fill="#010101" fill-opacity=".3">${value}</text>
|
|
80
|
+
<text x="${valueX}" y="13" fill="#fff">${value}</text>
|
|
81
|
+
</g>
|
|
82
|
+
</svg>`;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Read a .taf file and extract the latest test run.
|
|
86
|
+
*/
|
|
87
|
+
function getLatestRun(tafPath) {
|
|
88
|
+
if (!fs.existsSync(tafPath)) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
const content = fs.readFileSync(tafPath, 'utf-8');
|
|
92
|
+
// Handle YAML document separators (same logic as taf-core.ts)
|
|
93
|
+
const lines = content.split('\n');
|
|
94
|
+
const yamlLines = [];
|
|
95
|
+
for (const line of lines) {
|
|
96
|
+
if (line.trim() === '---' && yamlLines.length > 0)
|
|
97
|
+
break;
|
|
98
|
+
if (line.trim() === '---' && yamlLines.length === 0)
|
|
99
|
+
continue;
|
|
100
|
+
yamlLines.push(line);
|
|
101
|
+
}
|
|
102
|
+
const parsed = yaml.parse(yamlLines.join('\n'));
|
|
103
|
+
if (!parsed || typeof parsed !== 'object')
|
|
104
|
+
return null;
|
|
105
|
+
if (!Array.isArray(parsed.test_history) || parsed.test_history.length === 0)
|
|
106
|
+
return null;
|
|
107
|
+
const latest = parsed.test_history[parsed.test_history.length - 1];
|
|
108
|
+
if (!latest.tests || typeof latest.tests.total !== 'number')
|
|
109
|
+
return null;
|
|
110
|
+
return {
|
|
111
|
+
passed: latest.tests.passed ?? 0,
|
|
112
|
+
total: latest.tests.total,
|
|
113
|
+
failed: latest.tests.failed ?? 0,
|
|
114
|
+
skipped: latest.tests.skipped ?? 0,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Generate a badge SVG from a .taf file.
|
|
119
|
+
*/
|
|
120
|
+
function generateBadge(options = {}) {
|
|
121
|
+
const label = options.label || 'TAF';
|
|
122
|
+
const tafPath = options.tafPath || '.taf';
|
|
123
|
+
try {
|
|
124
|
+
const run = getLatestRun(tafPath);
|
|
125
|
+
if (!run) {
|
|
126
|
+
return {
|
|
127
|
+
success: true,
|
|
128
|
+
svg: renderBadgeSvg(label, 'no data', COLOR_NONE),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
const skippedSuffix = run.skipped > 0 ? ` (${run.skipped} skipped)` : '';
|
|
132
|
+
const displayPassed = run.failed === 0 ? run.total : run.passed;
|
|
133
|
+
const value = `${displayPassed}/${run.total} passing${skippedSuffix}`;
|
|
134
|
+
const color = run.failed === 0 ? COLOR_PASS : COLOR_FAIL;
|
|
135
|
+
return {
|
|
136
|
+
success: true,
|
|
137
|
+
svg: renderBadgeSvg(label, value, color),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
return {
|
|
142
|
+
success: false,
|
|
143
|
+
error: error instanceof Error ? error.message : 'Failed to generate badge',
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=badge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"badge.js","sourceRoot":"","sources":["../src/badge.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* faf-taf-git -
|
|
3
|
+
* faf-taf-git - The Git-Native Receipt Printer
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* v2.x CLI: reads test output from file (pre-capture pattern)
|
|
6
|
+
* Platform-agnostic - works in ANY CI/CD environment or locally
|
|
7
7
|
*/
|
|
8
8
|
export interface CLIOptions {
|
|
9
|
-
|
|
9
|
+
file?: string;
|
|
10
10
|
autoCommit?: boolean;
|
|
11
11
|
commitMessage?: string;
|
|
12
12
|
cwd?: string;
|
|
@@ -25,7 +25,6 @@ export interface CLIResult {
|
|
|
25
25
|
error?: string;
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
|
-
* Main CLI function -
|
|
29
|
-
* Can be called from GitHub Actions, GitLab CI, or standalone
|
|
28
|
+
* Main CLI function - reads test output from file
|
|
30
29
|
*/
|
|
31
30
|
export declare function runTafGit(options?: CLIOptions): Promise<CLIResult>;
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"","sourceRoot":"","sources":["file:///Users/wolfejam/FAF/faf-taf-git/src/cli.ts"],"names":[],"mappings":";AACA;;;;;GAKG;AASH,MAAM,WAAW,UAAU;IACzB,
|
|
1
|
+
{"version":3,"file":"","sourceRoot":"","sources":["file:///Users/wolfejam/FAF/faf-taf-git/src/cli.ts"],"names":[],"mappings":";AACA;;;;;GAKG;AASH,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAED,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,UAAU,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,wBAAsB,SAAS,CAAC,OAAO,GAAE,UAAe,GAAG,OAAO,CAAC,SAAS,CAAC,CA8G5E"}
|
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
/**
|
|
4
|
-
* faf-taf-git -
|
|
4
|
+
* faf-taf-git - The Git-Native Receipt Printer
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* v2.x CLI: reads test output from file (pre-capture pattern)
|
|
7
|
+
* Platform-agnostic - works in ANY CI/CD environment or locally
|
|
8
8
|
*/
|
|
9
9
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
10
|
if (k2 === undefined) k2 = k;
|
|
@@ -41,132 +41,48 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
41
41
|
})();
|
|
42
42
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
43
|
exports.runTafGit = runTafGit;
|
|
44
|
-
const
|
|
44
|
+
const child_process_1 = require("child_process");
|
|
45
45
|
const fs = __importStar(require("fs"));
|
|
46
|
-
const os = __importStar(require("os"));
|
|
47
46
|
const path = __importStar(require("path"));
|
|
48
|
-
const
|
|
47
|
+
const parsers_1 = require("./parsers");
|
|
49
48
|
const taf_core_1 = require("./taf-core");
|
|
49
|
+
const badge_1 = require("./badge");
|
|
50
50
|
/**
|
|
51
|
-
* Main CLI function -
|
|
52
|
-
* Can be called from GitHub Actions, GitLab CI, or standalone
|
|
51
|
+
* Main CLI function - reads test output from file
|
|
53
52
|
*/
|
|
54
53
|
async function runTafGit(options = {}) {
|
|
55
|
-
const {
|
|
54
|
+
const { file, autoCommit = false, commitMessage = 'chore: update .taf with test results', cwd = process.cwd(), verbose = false, logger = console.log, } = options;
|
|
56
55
|
try {
|
|
57
|
-
if (
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
try {
|
|
63
|
-
// Parse command into executable and args
|
|
64
|
-
// For "npm test" -> ["npm", "test"]
|
|
65
|
-
const commandParts = command.split(' ');
|
|
66
|
-
const executable = commandParts[0];
|
|
67
|
-
const args = commandParts.slice(1);
|
|
68
|
-
const options = {
|
|
69
|
-
cwd,
|
|
70
|
-
silent: false, // Show output in logs
|
|
71
|
-
ignoreReturnCode: true // Don't throw on non-zero exit
|
|
56
|
+
if (!file) {
|
|
57
|
+
return {
|
|
58
|
+
success: false,
|
|
59
|
+
tafUpdated: false,
|
|
60
|
+
error: 'No test output file specified. Use --file <path>.\nExample: npm test 2>&1 | tee test-output.txt && npx faf-taf-git --file test-output.txt',
|
|
72
61
|
};
|
|
73
|
-
// --- [START NUCLEAR CANARY] ---
|
|
74
|
-
// Use getExecOutput to capture stdout/stderr
|
|
75
|
-
const result = await exec.getExecOutput(executable, args, options);
|
|
76
|
-
// THE NUCLEAR CANARY: Write directly to disk, bypassing stdout/stderr streams
|
|
77
|
-
// Write to MULTIPLE locations to ensure we find it!
|
|
78
|
-
const canaryContent = [
|
|
79
|
-
`=========================================`,
|
|
80
|
-
`TAF DEBUG CANARY REPORT`,
|
|
81
|
-
`Timestamp: ${new Date().toISOString()}`,
|
|
82
|
-
`CWD: ${cwd}`,
|
|
83
|
-
`Process CWD: ${process.cwd()}`,
|
|
84
|
-
`=========================================`,
|
|
85
|
-
`Exit Code: ${result.exitCode}`,
|
|
86
|
-
`Stdout Length: ${result.stdout.length}`,
|
|
87
|
-
`Stderr Length: ${result.stderr.length}`,
|
|
88
|
-
`-----------------------------------------`,
|
|
89
|
-
`STDOUT PREVIEW (First 500 chars):`,
|
|
90
|
-
`${result.stdout.substring(0, 500)}`,
|
|
91
|
-
`-----------------------------------------`,
|
|
92
|
-
`STDERR PREVIEW (First 500 chars):`,
|
|
93
|
-
`${result.stderr.substring(0, 500)}`,
|
|
94
|
-
`=========================================`
|
|
95
|
-
].join('\n');
|
|
96
|
-
try {
|
|
97
|
-
// Try multiple locations!
|
|
98
|
-
const locations = [
|
|
99
|
-
path.join(cwd, 'TAF_DEBUG_CANARY.txt'), // Original cwd
|
|
100
|
-
path.join(process.cwd(), 'TAF_DEBUG_CANARY.txt'), // Process cwd
|
|
101
|
-
path.join(os.tmpdir(), 'TAF_DEBUG_CANARY.txt'), // OS temp directory
|
|
102
|
-
'TAF_DEBUG_CANARY.txt' // Current directory
|
|
103
|
-
];
|
|
104
|
-
let writeSuccess = false;
|
|
105
|
-
for (const loc of locations) {
|
|
106
|
-
try {
|
|
107
|
-
fs.writeFileSync(loc, canaryContent);
|
|
108
|
-
writeSuccess = true;
|
|
109
|
-
}
|
|
110
|
-
catch (e) {
|
|
111
|
-
// Try next location
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
if (!writeSuccess) {
|
|
115
|
-
throw new Error('CRITICAL: Failed to write canary to ANY location!');
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
catch (fsError) {
|
|
119
|
-
// If we can't write to disk, we are truly doomed
|
|
120
|
-
throw new Error(`CRITICAL: Failed to write canary file: ${fsError.message}`);
|
|
121
|
-
}
|
|
122
|
-
// Continue with original logic
|
|
123
|
-
exitCode = result.exitCode;
|
|
124
|
-
output = result.stdout + result.stderr;
|
|
125
|
-
// --- [END NUCLEAR CANARY] ---
|
|
126
62
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (verbose) {
|
|
136
|
-
try {
|
|
137
|
-
const fs = require('fs');
|
|
138
|
-
const debugPath = path.join(os.tmpdir(), 'taf-debug-output.txt');
|
|
139
|
-
fs.writeFileSync(debugPath, output);
|
|
140
|
-
logger(`DEBUG: Wrote ${output.length} bytes to ${debugPath}`);
|
|
141
|
-
const lines = output.split('\n');
|
|
142
|
-
const testLine = lines.find(l => l.includes('Tests:'));
|
|
143
|
-
logger(`DEBUG: Output has ${lines.length} lines, Found "Tests:": ${testLine ? 'YES' : 'NO'}`);
|
|
144
|
-
if (testLine) {
|
|
145
|
-
logger(`DEBUG: Tests line: "${testLine}"`);
|
|
146
|
-
}
|
|
147
|
-
else {
|
|
148
|
-
logger(`DEBUG: Last 10 lines of output:`);
|
|
149
|
-
lines.slice(-10).forEach((line, i) => {
|
|
150
|
-
logger(` ${i}: ${line.substring(0, 100)}`);
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
catch (err) {
|
|
155
|
-
logger(`DEBUG: Error writing debug file: ${err}`);
|
|
156
|
-
}
|
|
63
|
+
// Resolve file path
|
|
64
|
+
const filePath = path.isAbsolute(file) ? file : path.join(cwd, file);
|
|
65
|
+
if (!fs.existsSync(filePath)) {
|
|
66
|
+
return {
|
|
67
|
+
success: false,
|
|
68
|
+
tafUpdated: false,
|
|
69
|
+
error: `Test output file not found: ${filePath}`,
|
|
70
|
+
};
|
|
157
71
|
}
|
|
72
|
+
const output = fs.readFileSync(filePath, 'utf-8');
|
|
73
|
+
if (verbose)
|
|
74
|
+
logger(`Read ${output.length} bytes from ${filePath}`);
|
|
158
75
|
// Parse test output
|
|
159
|
-
const testResults = (0,
|
|
76
|
+
const testResults = (0, parsers_1.parseTestOutput)(output);
|
|
160
77
|
if (!testResults) {
|
|
161
78
|
if (verbose) {
|
|
162
|
-
logger(`
|
|
163
|
-
logger(`
|
|
164
|
-
logger(`DEBUG: Output sample (last 500 chars): ${output.slice(-500)}`);
|
|
79
|
+
logger(`Parser returned null`);
|
|
80
|
+
logger(`Output sample (first 500 chars): ${output.slice(0, 500)}`);
|
|
165
81
|
}
|
|
166
82
|
return {
|
|
167
83
|
success: false,
|
|
168
84
|
tafUpdated: false,
|
|
169
|
-
error: 'Could not parse test output.
|
|
85
|
+
error: 'Could not parse test output. Supported: Jest, Vitest.',
|
|
170
86
|
};
|
|
171
87
|
}
|
|
172
88
|
if (verbose) {
|
|
@@ -191,12 +107,12 @@ async function runTafGit(options = {}) {
|
|
|
191
107
|
const updated = await (0, taf_core_1.updateTafFile)(tafPath, testResults);
|
|
192
108
|
if (updated) {
|
|
193
109
|
if (verbose)
|
|
194
|
-
logger('
|
|
110
|
+
logger('.taf file updated successfully');
|
|
195
111
|
// Commit changes if enabled
|
|
196
112
|
if (autoCommit) {
|
|
197
|
-
|
|
113
|
+
commitTafUpdate(cwd, commitMessage, verbose, logger);
|
|
198
114
|
if (verbose)
|
|
199
|
-
logger('
|
|
115
|
+
logger('Changes committed to git');
|
|
200
116
|
}
|
|
201
117
|
return {
|
|
202
118
|
success: true,
|
|
@@ -232,88 +148,107 @@ async function runTafGit(options = {}) {
|
|
|
232
148
|
}
|
|
233
149
|
}
|
|
234
150
|
/**
|
|
235
|
-
* Commit .taf file changes to git
|
|
151
|
+
* Commit .taf file changes to git
|
|
236
152
|
*/
|
|
237
|
-
|
|
153
|
+
function commitTafUpdate(cwd, message, verbose, logger = console.log) {
|
|
238
154
|
try {
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
// Stage .taf file
|
|
244
|
-
await exec.exec('git', ['add', '.taf'], execOptions);
|
|
245
|
-
// Check if there are changes to commit
|
|
246
|
-
let diffOutput = '';
|
|
247
|
-
const diffOptions = {
|
|
248
|
-
cwd,
|
|
249
|
-
listeners: {
|
|
250
|
-
stdout: (data) => {
|
|
251
|
-
diffOutput += data.toString();
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
};
|
|
255
|
-
await exec.exec('git', ['diff', '--cached', '--name-only'], diffOptions);
|
|
256
|
-
if (diffOutput.trim().length > 0) {
|
|
257
|
-
await exec.exec('git', ['commit', '-m', message], execOptions);
|
|
258
|
-
// Try to push (might fail in some environments, that's ok)
|
|
259
|
-
try {
|
|
260
|
-
await exec.exec('git', ['push'], execOptions);
|
|
261
|
-
}
|
|
262
|
-
catch (error) {
|
|
263
|
-
if (verbose) {
|
|
264
|
-
logger('Could not push changes (this is ok in some CI environments)');
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
else {
|
|
155
|
+
const opts = { cwd, stdio: 'pipe' };
|
|
156
|
+
(0, child_process_1.execSync)('git add .taf', opts);
|
|
157
|
+
const diff = (0, child_process_1.execSync)('git diff --cached --name-only', opts).toString().trim();
|
|
158
|
+
if (diff.length === 0) {
|
|
269
159
|
if (verbose)
|
|
270
160
|
logger('No changes to commit');
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
(0, child_process_1.execSync)(`git commit -m "${message}"`, opts);
|
|
164
|
+
try {
|
|
165
|
+
(0, child_process_1.execSync)('git push', opts);
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
if (verbose)
|
|
169
|
+
logger('Could not push changes (this is ok in some CI environments)');
|
|
271
170
|
}
|
|
272
171
|
}
|
|
273
172
|
catch (error) {
|
|
274
|
-
if (verbose)
|
|
173
|
+
if (verbose)
|
|
275
174
|
logger(`Error committing changes: ${error}`);
|
|
276
|
-
}
|
|
277
175
|
throw error;
|
|
278
176
|
}
|
|
279
177
|
}
|
|
280
178
|
/**
|
|
281
|
-
*
|
|
179
|
+
* Handle the 'badge' subcommand
|
|
282
180
|
*/
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
181
|
+
function handleBadgeCommand(args) {
|
|
182
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
183
|
+
console.log(`
|
|
184
|
+
faf-taf-git badge - Generate SVG badge from .taf history
|
|
185
|
+
|
|
186
|
+
USAGE:
|
|
187
|
+
npx faf-taf-git badge [OPTIONS]
|
|
188
|
+
|
|
189
|
+
OPTIONS:
|
|
190
|
+
--output <path> Write SVG to file (default: stdout)
|
|
191
|
+
--label <text> Badge label (default: TAF)
|
|
192
|
+
--taf <path> Path to .taf file (default: .taf)
|
|
193
|
+
--help, -h Show this help
|
|
194
|
+
`);
|
|
195
|
+
process.exit(0);
|
|
292
196
|
}
|
|
293
|
-
// Parse
|
|
294
|
-
|
|
295
|
-
|
|
197
|
+
// Parse --output
|
|
198
|
+
let outputPath;
|
|
199
|
+
const outputIndex = args.indexOf('--output');
|
|
200
|
+
if (outputIndex !== -1 && args[outputIndex + 1]) {
|
|
201
|
+
outputPath = args[outputIndex + 1];
|
|
296
202
|
}
|
|
297
|
-
// Parse
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
203
|
+
// Parse --label
|
|
204
|
+
let label;
|
|
205
|
+
const labelIndex = args.indexOf('--label');
|
|
206
|
+
if (labelIndex !== -1 && args[labelIndex + 1]) {
|
|
207
|
+
label = args[labelIndex + 1];
|
|
301
208
|
}
|
|
302
|
-
// Parse
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
209
|
+
// Parse --taf
|
|
210
|
+
let tafPath;
|
|
211
|
+
const tafIndex = args.indexOf('--taf');
|
|
212
|
+
if (tafIndex !== -1 && args[tafIndex + 1]) {
|
|
213
|
+
tafPath = args[tafIndex + 1];
|
|
214
|
+
}
|
|
215
|
+
const result = (0, badge_1.generateBadge)({ tafPath, label });
|
|
216
|
+
if (!result.success || !result.svg) {
|
|
217
|
+
console.error(`Error: ${result.error || 'Failed to generate badge'}`);
|
|
218
|
+
process.exit(1);
|
|
219
|
+
}
|
|
220
|
+
if (outputPath) {
|
|
221
|
+
fs.writeFileSync(outputPath, result.svg, 'utf-8');
|
|
222
|
+
console.log(`Badge written to ${outputPath}`);
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
process.stdout.write(result.svg);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* CLI entry point
|
|
230
|
+
*/
|
|
231
|
+
async function main() {
|
|
232
|
+
const args = process.argv.slice(2);
|
|
233
|
+
// Subcommand: badge
|
|
234
|
+
if (args[0] === 'badge') {
|
|
235
|
+
handleBadgeCommand(args.slice(1));
|
|
236
|
+
return;
|
|
306
237
|
}
|
|
307
238
|
// Show help
|
|
308
239
|
if (args.includes('--help') || args.includes('-h')) {
|
|
309
240
|
console.log(`
|
|
310
|
-
faf-taf-git -
|
|
241
|
+
faf-taf-git - The Git-Native Receipt Printer
|
|
311
242
|
|
|
312
243
|
USAGE:
|
|
313
|
-
|
|
244
|
+
npm test 2>&1 | tee test-output.txt
|
|
245
|
+
npx faf-taf-git --file test-output.txt [OPTIONS]
|
|
246
|
+
|
|
247
|
+
COMMANDS:
|
|
248
|
+
badge Generate a shields.io-style SVG badge from .taf
|
|
314
249
|
|
|
315
250
|
OPTIONS:
|
|
316
|
-
--
|
|
251
|
+
--file <path> Path to test output file (required)
|
|
317
252
|
--commit Auto-commit .taf changes to git
|
|
318
253
|
--message <msg> Custom commit message
|
|
319
254
|
--cwd <dir> Working directory (default: current)
|
|
@@ -321,49 +256,65 @@ OPTIONS:
|
|
|
321
256
|
--help, -h Show this help
|
|
322
257
|
|
|
323
258
|
EXAMPLES:
|
|
324
|
-
# Run tests
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
# Custom test command
|
|
328
|
-
npx faf-taf-git --command "npm run test:unit"
|
|
259
|
+
# Run tests, capture output, generate receipt
|
|
260
|
+
npm test 2>&1 | tee test-output.txt
|
|
261
|
+
npx faf-taf-git --file test-output.txt
|
|
329
262
|
|
|
330
263
|
# Auto-commit changes
|
|
331
|
-
npx faf-taf-git --commit
|
|
264
|
+
npx faf-taf-git --file test-output.txt --commit
|
|
332
265
|
|
|
333
266
|
# Custom commit message
|
|
334
|
-
npx faf-taf-git --commit --message "test: update TAF"
|
|
267
|
+
npx faf-taf-git --file test-output.txt --commit --message "test: update TAF"
|
|
335
268
|
|
|
336
|
-
#
|
|
337
|
-
npx faf-taf-git --
|
|
269
|
+
# Generate badge
|
|
270
|
+
npx faf-taf-git badge --output badge.svg
|
|
338
271
|
|
|
339
272
|
PLATFORM SUPPORT:
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
✅ Jenkins
|
|
344
|
-
✅ CircleCI
|
|
345
|
-
✅ Local development
|
|
346
|
-
✅ Any CI/CD that runs Node.js
|
|
273
|
+
Works in ANY CI/CD that runs Node.js:
|
|
274
|
+
GitHub Actions, GitLab CI, Jenkins, CircleCI,
|
|
275
|
+
Bitbucket Pipelines, Azure Pipelines, local dev
|
|
347
276
|
|
|
348
277
|
LEARN MORE:
|
|
349
278
|
https://github.com/Wolfe-Jam/faf-taf-git
|
|
350
279
|
`);
|
|
351
280
|
process.exit(0);
|
|
352
281
|
}
|
|
282
|
+
const options = {
|
|
283
|
+
verbose: args.includes('--verbose') || args.includes('-v'),
|
|
284
|
+
};
|
|
285
|
+
// Parse --file
|
|
286
|
+
const fileIndex = args.indexOf('--file');
|
|
287
|
+
if (fileIndex !== -1 && args[fileIndex + 1]) {
|
|
288
|
+
options.file = args[fileIndex + 1];
|
|
289
|
+
}
|
|
290
|
+
// Parse auto-commit flag
|
|
291
|
+
if (args.includes('--commit')) {
|
|
292
|
+
options.autoCommit = true;
|
|
293
|
+
}
|
|
294
|
+
// Parse commit message
|
|
295
|
+
const messageIndex = args.indexOf('--message');
|
|
296
|
+
if (messageIndex !== -1 && args[messageIndex + 1]) {
|
|
297
|
+
options.commitMessage = args[messageIndex + 1];
|
|
298
|
+
}
|
|
299
|
+
// Parse working directory
|
|
300
|
+
const cwdIndex = args.indexOf('--cwd');
|
|
301
|
+
if (cwdIndex !== -1 && args[cwdIndex + 1]) {
|
|
302
|
+
options.cwd = args[cwdIndex + 1];
|
|
303
|
+
}
|
|
353
304
|
// Run the tool
|
|
354
305
|
const result = await runTafGit(options);
|
|
355
306
|
if (result.success) {
|
|
356
307
|
if (result.testResults) {
|
|
357
|
-
console.log(
|
|
358
|
-
console.log(
|
|
308
|
+
console.log(`Tests: ${result.testResults.passed}/${result.testResults.total} passing`);
|
|
309
|
+
console.log(`Result: ${result.testResults.result}`);
|
|
359
310
|
}
|
|
360
311
|
if (result.tafUpdated) {
|
|
361
|
-
console.log('
|
|
312
|
+
console.log('.taf file updated');
|
|
362
313
|
}
|
|
363
314
|
process.exit(0);
|
|
364
315
|
}
|
|
365
316
|
else {
|
|
366
|
-
console.error(
|
|
317
|
+
console.error(`Error: ${result.error}`);
|
|
367
318
|
if (result.testResults) {
|
|
368
319
|
console.log(`Tests: ${result.testResults.passed}/${result.testResults.total} passing`);
|
|
369
320
|
}
|