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.
Files changed (48) hide show
  1. package/.github/workflows/ci.yml +53 -0
  2. package/.github/workflows/claude-code-review.yml.disabled +29 -0
  3. package/.github/workflows/claude.yml +39 -0
  4. package/.github/workflows/homebrew.yml +19 -0
  5. package/.taf +79 -2
  6. package/CHANGELOG.md +28 -0
  7. package/CLAUDE.md +29 -21
  8. package/CODE_OF_CONDUCT.md +21 -0
  9. package/CONTRIBUTING.md +3 -0
  10. package/README.md +69 -12
  11. package/SECURITY.md +19 -0
  12. package/action.yml +6 -1
  13. package/badge.svg +21 -0
  14. package/dist/badge.d.ts +23 -0
  15. package/dist/badge.d.ts.map +1 -0
  16. package/dist/badge.js +147 -0
  17. package/dist/badge.js.map +1 -0
  18. package/dist/cli.d.ts +5 -6
  19. package/dist/cli.d.ts.map +1 -1
  20. package/dist/cli.js +144 -193
  21. package/dist/cli.js.map +1 -1
  22. package/dist/index.js +26045 -20566
  23. package/dist/index.js.map +1 -1
  24. package/dist/licenses.txt +0 -22
  25. package/dist/parsers/bun.d.ts +59 -0
  26. package/dist/parsers/bun.d.ts.map +1 -0
  27. package/dist/parsers/bun.js +100 -0
  28. package/dist/parsers/bun.js.map +1 -0
  29. package/dist/parsers/index.d.ts +20 -0
  30. package/dist/parsers/index.d.ts.map +1 -0
  31. package/dist/parsers/index.js +26 -0
  32. package/dist/parsers/index.js.map +1 -0
  33. package/dist/parsers/vitest.d.ts +17 -0
  34. package/dist/parsers/vitest.d.ts.map +1 -0
  35. package/dist/parsers/vitest.js +61 -0
  36. package/dist/parsers/vitest.js.map +1 -0
  37. package/package.json +6 -7
  38. package/project.faf +20 -24
  39. package/src/badge.ts +138 -0
  40. package/src/cli.ts +154 -200
  41. package/src/index.ts +79 -10
  42. package/src/parsers/bun.ts +103 -0
  43. package/src/parsers/index.ts +27 -0
  44. package/src/parsers/vitest.ts +65 -0
  45. package/tests/badge.test.ts +260 -0
  46. package/tests/bun-parser.test.ts +173 -0
  47. package/tests/vitest-parser.test.ts +112 -0
  48. 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 - Standalone CLI for TAF operations
3
+ * faf-taf-git - The Git-Native Receipt Printer
4
4
  *
5
- * Platform-agnostic core that works in ANY CI/CD environment
6
- * Can be used standalone or as a library by wrappers
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
- command?: string;
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 - platform-agnostic
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,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,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;;;GAGG;AACH,wBAAsB,SAAS,CAAC,OAAO,GAAE,UAAe,GAAG,OAAO,CAAC,SAAS,CAAC,CAmM5E"}
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 - Standalone CLI for TAF operations
4
+ * faf-taf-git - The Git-Native Receipt Printer
5
5
  *
6
- * Platform-agnostic core that works in ANY CI/CD environment
7
- * Can be used standalone or as a library by wrappers
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 exec = __importStar(require("@actions/exec"));
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 jest_1 = require("./parsers/jest");
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 - platform-agnostic
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 { command = 'npm test', autoCommit = false, commitMessage = 'chore: update .taf with test results', cwd = process.cwd(), verbose = false, logger = console.log, } = options;
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 (verbose)
58
- console.log(`Running test command: ${command}`);
59
- // Run tests and capture output using @actions/exec
60
- let output = '';
61
- let exitCode = 0;
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
- catch (error) {
128
- exitCode = 1;
129
- if (verbose) {
130
- logger(`Test command failed with error: ${error.message}`);
131
- logger(`Captured output length: ${output.length} bytes`);
132
- }
133
- }
134
- // Debug: Write entire output to file for inspection
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, jest_1.parseJestOutput)(output);
76
+ const testResults = (0, parsers_1.parseTestOutput)(output);
160
77
  if (!testResults) {
161
78
  if (verbose) {
162
- logger(`DEBUG: Parser returned null`);
163
- logger(`DEBUG: Output sample (first 500 chars): ${output.slice(0, 500)}`);
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. Ensure you are using Jest.',
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('✅ .taf file updated successfully');
110
+ logger('.taf file updated successfully');
195
111
  // Commit changes if enabled
196
112
  if (autoCommit) {
197
- await commitTafUpdate(cwd, commitMessage, verbose, logger);
113
+ commitTafUpdate(cwd, commitMessage, verbose, logger);
198
114
  if (verbose)
199
- logger('✅ Changes committed to git');
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 (platform-agnostic)
151
+ * Commit .taf file changes to git
236
152
  */
237
- async function commitTafUpdate(cwd, message, verbose, logger = console.log) {
153
+ function commitTafUpdate(cwd, message, verbose, logger = console.log) {
238
154
  try {
239
- const execOptions = { cwd };
240
- // Configure git if needed
241
- await exec.exec('git', ['config', '--global', 'user.name', 'faf-taf-git[bot]'], execOptions);
242
- await exec.exec('git', ['config', '--global', 'user.email', 'faf-taf-git[bot]@users.noreply.github.com'], execOptions);
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
- * CLI entry point - parse args and run
179
+ * Handle the 'badge' subcommand
282
180
  */
283
- async function main() {
284
- const args = process.argv.slice(2);
285
- const options = {
286
- verbose: args.includes('--verbose') || args.includes('-v'),
287
- };
288
- // Parse command flag
289
- const commandIndex = args.indexOf('--command');
290
- if (commandIndex !== -1 && args[commandIndex + 1]) {
291
- options.command = args[commandIndex + 1];
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 auto-commit flag
294
- if (args.includes('--commit')) {
295
- options.autoCommit = true;
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 commit message
298
- const messageIndex = args.indexOf('--message');
299
- if (messageIndex !== -1 && args[messageIndex + 1]) {
300
- options.commitMessage = args[messageIndex + 1];
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 working directory
303
- const cwdIndex = args.indexOf('--cwd');
304
- if (cwdIndex !== -1 && args[cwdIndex + 1]) {
305
- options.cwd = args[cwdIndex + 1];
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 - Platform-agnostic TAF updater
241
+ faf-taf-git - The Git-Native Receipt Printer
311
242
 
312
243
  USAGE:
313
- npx faf-taf-git [OPTIONS]
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
- --command <cmd> Test command to run (default: npm test)
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 and update .taf
325
- npx faf-taf-git
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
- # Verbose mode
337
- npx faf-taf-git --verbose
269
+ # Generate badge
270
+ npx faf-taf-git badge --output badge.svg
338
271
 
339
272
  PLATFORM SUPPORT:
340
- ✅ GitHub Actions
341
- ✅ GitLab CI
342
- ✅ Bitbucket Pipelines
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(`✅ Tests: ${result.testResults.passed}/${result.testResults.total} passing`);
358
- console.log(`✅ Result: ${result.testResults.result}`);
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('✅ .taf file updated');
312
+ console.log('.taf file updated');
362
313
  }
363
314
  process.exit(0);
364
315
  }
365
316
  else {
366
- console.error(`❌ Error: ${result.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
  }