@dizzlkheinz/ynab-mcpb 0.18.3 → 0.18.4
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 +17 -0
- package/dist/bundle/index.cjs +40 -40
- package/dist/tools/reconcileAdapter.js +3 -0
- package/dist/tools/reconciliation/analyzer.js +72 -7
- package/dist/tools/reconciliation/reportFormatter.js +26 -2
- package/dist/tools/reconciliation/types.d.ts +3 -0
- package/dist/tools/transactionSchemas.d.ts +309 -0
- package/dist/tools/transactionSchemas.js +215 -0
- package/dist/tools/transactionTools.d.ts +3 -281
- package/dist/tools/transactionTools.js +4 -559
- package/dist/tools/transactionUtils.d.ts +31 -0
- package/dist/tools/transactionUtils.js +349 -0
- package/docs/plans/2025-12-25-transaction-tools-refactor-design.md +211 -0
- package/docs/plans/2025-12-25-transaction-tools-refactor.md +905 -0
- package/package.json +4 -2
- package/scripts/run-all-tests.js +196 -0
- package/src/tools/__tests__/transactionSchemas.test.ts +1188 -0
- package/src/tools/__tests__/transactionUtils.test.ts +989 -0
- package/src/tools/reconcileAdapter.ts +6 -0
- package/src/tools/reconciliation/__tests__/adapter.causes.test.ts +22 -8
- package/src/tools/reconciliation/__tests__/adapter.test.ts +3 -0
- package/src/tools/reconciliation/__tests__/analyzer.test.ts +65 -0
- package/src/tools/reconciliation/__tests__/recommendationEngine.test.ts +3 -0
- package/src/tools/reconciliation/__tests__/reportFormatter.test.ts +4 -1
- package/src/tools/reconciliation/__tests__/scenarios/adapterCurrency.scenario.test.ts +3 -0
- package/src/tools/reconciliation/__tests__/scenarios/extremes.scenario.test.ts +5 -1
- package/src/tools/reconciliation/__tests__/schemaUrl.test.ts +22 -8
- package/src/tools/reconciliation/analyzer.ts +127 -11
- package/src/tools/reconciliation/reportFormatter.ts +39 -2
- package/src/tools/reconciliation/types.ts +6 -0
- package/src/tools/transactionSchemas.ts +453 -0
- package/src/tools/transactionTools.ts +102 -823
- package/src/tools/transactionUtils.ts +536 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dizzlkheinz/ynab-mcpb",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.4",
|
|
4
4
|
"description": "Model Context Protocol server for YNAB (You Need A Budget) integration",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -46,7 +46,9 @@
|
|
|
46
46
|
"test:coverage": "vitest run --coverage --project unit",
|
|
47
47
|
"test:performance": "vitest run src/__tests__/performance.test.ts",
|
|
48
48
|
"test:comprehensive": "tsx src/__tests__/testRunner.ts",
|
|
49
|
-
"test:all": "
|
|
49
|
+
"test:all": "node scripts/run-all-tests.js",
|
|
50
|
+
"test:all:full": "node scripts/run-all-tests.js --full",
|
|
51
|
+
"test:all:verbose": "npm run test:unit && npm run test:integration:core && npm run test:e2e && npm run test:performance",
|
|
50
52
|
"filter-test-results": "node -e \"const fs = require('fs'); try { const results = JSON.parse(fs.readFileSync('test-results.json', 'utf-8')); if (!results.quickStats || !results.quickStats.success) { console.warn('Tests failed. See test-results/failed-tests.json for details.'); } } catch (e) { console.warn('Could not read test results:', e.message); }\"",
|
|
51
53
|
"generate:mcpb": "node scripts/run-generate-mcpb.js",
|
|
52
54
|
"bundle": "node esbuild.config.mjs",
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Clean test runner for all test suites
|
|
4
|
+
* Runs unit, integration, e2e, and performance tests with summarized output
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Filter out DEP0190 deprecation warning for shell spawn (safe in this controlled context)
|
|
8
|
+
// We use shell:true intentionally for cross-platform npm execution
|
|
9
|
+
// Must remove default listeners first, then add filtered handler
|
|
10
|
+
process.removeAllListeners('warning');
|
|
11
|
+
process.on('warning', (warning) => {
|
|
12
|
+
// Check both code and message for DEP0190 (shell spawn with args)
|
|
13
|
+
const isDep0190 =
|
|
14
|
+
warning.code === 'DEP0190' ||
|
|
15
|
+
(warning.name === 'DeprecationWarning' && warning.message?.includes('DEP0190'));
|
|
16
|
+
if (isDep0190) {
|
|
17
|
+
return; // Suppress only this specific warning
|
|
18
|
+
}
|
|
19
|
+
console.warn(warning);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
import { spawn } from 'child_process';
|
|
23
|
+
|
|
24
|
+
const isFull = process.argv.includes('--full');
|
|
25
|
+
|
|
26
|
+
const SUITES = [
|
|
27
|
+
{ name: 'Unit', script: 'test:unit' },
|
|
28
|
+
{ name: 'Integration', script: isFull ? 'test:integration:full' : 'test:integration:core' },
|
|
29
|
+
{ name: 'E2E', script: 'test:e2e' },
|
|
30
|
+
{ name: 'Performance', script: 'test:performance' },
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const results = [];
|
|
34
|
+
let hasFailure = false;
|
|
35
|
+
|
|
36
|
+
async function runSuite(suite) {
|
|
37
|
+
return new Promise((resolve) => {
|
|
38
|
+
const start = Date.now();
|
|
39
|
+
process.stdout.write(`Running ${suite.name} tests... `);
|
|
40
|
+
|
|
41
|
+
const proc = spawn('npm', ['run', suite.script], {
|
|
42
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
43
|
+
shell: true,
|
|
44
|
+
cwd: process.cwd(),
|
|
45
|
+
env: { ...process.env, FORCE_COLOR: '0' },
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
let stdout = '';
|
|
49
|
+
let stderr = '';
|
|
50
|
+
|
|
51
|
+
proc.stdout?.on('data', (data) => {
|
|
52
|
+
stdout += data.toString();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
proc.stderr?.on('data', (data) => {
|
|
56
|
+
stderr += data.toString();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
proc.on('error', (err) => {
|
|
60
|
+
console.log('\x1b[31m✗\x1b[0m (spawn error)');
|
|
61
|
+
hasFailure = true;
|
|
62
|
+
results.push({
|
|
63
|
+
name: suite.name,
|
|
64
|
+
passed: 0,
|
|
65
|
+
failed: 0,
|
|
66
|
+
skipped: 0,
|
|
67
|
+
duration: ((Date.now() - start) / 1000).toFixed(1),
|
|
68
|
+
success: false,
|
|
69
|
+
stdout: '',
|
|
70
|
+
stderr: err.message,
|
|
71
|
+
});
|
|
72
|
+
resolve();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
proc.on('close', (code) => {
|
|
76
|
+
const duration = ((Date.now() - start) / 1000).toFixed(1);
|
|
77
|
+
|
|
78
|
+
// Parse results from output
|
|
79
|
+
// Vitest format: "Tests 1584 passed | 13 skipped (1597)"
|
|
80
|
+
// Also handles: "Tests 1584 passed (1584)"
|
|
81
|
+
let passed = 0;
|
|
82
|
+
let failed = 0;
|
|
83
|
+
let skipped = 0;
|
|
84
|
+
|
|
85
|
+
// Look for the Tests summary line (last occurrence)
|
|
86
|
+
const lines = stdout.split('\n');
|
|
87
|
+
for (const line of lines) {
|
|
88
|
+
// Skip "Test Files" line, we want "Tests" line
|
|
89
|
+
if (line.includes('Tests') && !line.includes('Test Files')) {
|
|
90
|
+
const passMatch = line.match(/(\d+)\s+passed/);
|
|
91
|
+
const failMatch = line.match(/(\d+)\s+failed/);
|
|
92
|
+
const skipMatch = line.match(/(\d+)\s+skipped/);
|
|
93
|
+
|
|
94
|
+
if (passMatch) passed = parseInt(passMatch[1], 10);
|
|
95
|
+
if (failMatch) failed = parseInt(failMatch[1], 10);
|
|
96
|
+
if (skipMatch) skipped = parseInt(skipMatch[1], 10);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const success = code === 0;
|
|
101
|
+
if (!success) hasFailure = true;
|
|
102
|
+
|
|
103
|
+
const status = success ? '\x1b[32m✓\x1b[0m' : '\x1b[31m✗\x1b[0m';
|
|
104
|
+
console.log(`${status} (${duration}s)`);
|
|
105
|
+
|
|
106
|
+
results.push({
|
|
107
|
+
name: suite.name,
|
|
108
|
+
passed,
|
|
109
|
+
failed,
|
|
110
|
+
skipped,
|
|
111
|
+
duration,
|
|
112
|
+
success,
|
|
113
|
+
stdout: success ? '' : stdout,
|
|
114
|
+
stderr: success ? '' : stderr,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
resolve();
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function main() {
|
|
123
|
+
const title = isFull ? 'Running All Tests (Full Suite)' : 'Running All Tests';
|
|
124
|
+
console.log(`\n\x1b[1m━━━ ${title} ━━━\x1b[0m\n`);
|
|
125
|
+
|
|
126
|
+
for (const suite of SUITES) {
|
|
127
|
+
await runSuite(suite);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Print summary
|
|
131
|
+
console.log('\n\x1b[1m━━━ Test Summary ━━━\x1b[0m\n');
|
|
132
|
+
|
|
133
|
+
const totalPassed = results.reduce((sum, r) => sum + r.passed, 0);
|
|
134
|
+
const totalFailed = results.reduce((sum, r) => sum + r.failed, 0);
|
|
135
|
+
const totalSkipped = results.reduce((sum, r) => sum + r.skipped, 0);
|
|
136
|
+
const totalDuration = results.reduce((sum, r) => sum + parseFloat(r.duration), 0).toFixed(1);
|
|
137
|
+
|
|
138
|
+
// Table header
|
|
139
|
+
console.log('Suite Passed Failed Skipped Time');
|
|
140
|
+
console.log('─'.repeat(50));
|
|
141
|
+
|
|
142
|
+
for (const r of results) {
|
|
143
|
+
const status = r.success ? '\x1b[32m✓\x1b[0m' : '\x1b[31m✗\x1b[0m';
|
|
144
|
+
const name = r.name.padEnd(12);
|
|
145
|
+
const passed = String(r.passed).padStart(6);
|
|
146
|
+
const failed =
|
|
147
|
+
r.failed > 0
|
|
148
|
+
? `\x1b[31m${String(r.failed).padStart(8)}\x1b[0m`
|
|
149
|
+
: String(r.failed).padStart(8);
|
|
150
|
+
const skipped = String(r.skipped).padStart(9);
|
|
151
|
+
const time = `${r.duration}s`.padStart(7);
|
|
152
|
+
console.log(`${status} ${name} ${passed} ${failed} ${skipped} ${time}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
console.log('─'.repeat(50));
|
|
156
|
+
|
|
157
|
+
const totalStatus = hasFailure ? '\x1b[31m✗\x1b[0m' : '\x1b[32m✓\x1b[0m';
|
|
158
|
+
const totalLabel = 'Total'.padEnd(12);
|
|
159
|
+
const passedStr = String(totalPassed).padStart(6);
|
|
160
|
+
const failedStr =
|
|
161
|
+
totalFailed > 0
|
|
162
|
+
? `\x1b[31m${String(totalFailed).padStart(8)}\x1b[0m`
|
|
163
|
+
: String(totalFailed).padStart(8);
|
|
164
|
+
const skippedStr = String(totalSkipped).padStart(9);
|
|
165
|
+
const timeStr = `${totalDuration}s`.padStart(7);
|
|
166
|
+
console.log(`${totalStatus} ${totalLabel} ${passedStr} ${failedStr} ${skippedStr} ${timeStr}`);
|
|
167
|
+
|
|
168
|
+
console.log();
|
|
169
|
+
|
|
170
|
+
// Print failures if any
|
|
171
|
+
if (hasFailure) {
|
|
172
|
+
console.log('\x1b[31m━━━ Failures ━━━\x1b[0m\n');
|
|
173
|
+
for (const r of results.filter((r) => !r.success)) {
|
|
174
|
+
console.log(`\x1b[1m${r.name}:\x1b[0m`);
|
|
175
|
+
if (r.stdout) {
|
|
176
|
+
// Show last 3000 chars of stdout to capture test failures without overwhelming output
|
|
177
|
+
const trimmedStdout = r.stdout.length > 3000 ? '...' + r.stdout.slice(-3000) : r.stdout;
|
|
178
|
+
console.log('\x1b[2mOutput:\x1b[0m');
|
|
179
|
+
console.log(trimmedStdout);
|
|
180
|
+
}
|
|
181
|
+
if (r.stderr) {
|
|
182
|
+
console.log('\x1b[2mErrors:\x1b[0m');
|
|
183
|
+
console.log(r.stderr);
|
|
184
|
+
}
|
|
185
|
+
console.log();
|
|
186
|
+
}
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
console.log('\x1b[32mAll tests passed!\x1b[0m\n');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
main().catch((err) => {
|
|
194
|
+
console.error('Test runner error:', err);
|
|
195
|
+
process.exit(1);
|
|
196
|
+
});
|