@md2do/cli 0.2.2 → 0.3.0
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 +43 -0
- package/coverage/coverage-final.json +9 -9
- package/coverage/index.html +7 -7
- package/coverage/lcov-report/index.html +7 -7
- package/coverage/lcov-report/src/cli.ts.html +53 -5
- package/coverage/lcov-report/src/commands/index.html +5 -5
- package/coverage/lcov-report/src/commands/index.ts.html +1 -1
- package/coverage/lcov-report/src/commands/list.ts.html +49 -10
- package/coverage/lcov-report/src/commands/stats.ts.html +1 -1
- package/coverage/lcov-report/src/commands/todoist.ts.html +1 -1
- package/coverage/lcov-report/src/formatters/index.html +1 -1
- package/coverage/lcov-report/src/formatters/json.ts.html +1 -1
- package/coverage/lcov-report/src/formatters/pretty.ts.html +1 -1
- package/coverage/lcov-report/src/index.html +5 -5
- package/coverage/lcov-report/src/index.ts.html +1 -1
- package/coverage/lcov-report/src/scanner.ts.html +1 -1
- package/coverage/lcov.info +31 -2
- package/coverage/src/cli.ts.html +53 -5
- package/coverage/src/commands/index.html +5 -5
- package/coverage/src/commands/index.ts.html +1 -1
- package/coverage/src/commands/list.ts.html +49 -10
- package/coverage/src/commands/stats.ts.html +1 -1
- package/coverage/src/commands/todoist.ts.html +1 -1
- package/coverage/src/formatters/index.html +1 -1
- package/coverage/src/formatters/json.ts.html +1 -1
- package/coverage/src/formatters/pretty.ts.html +1 -1
- package/coverage/src/index.html +5 -5
- package/coverage/src/index.ts.html +1 -1
- package/coverage/src/scanner.ts.html +1 -1
- package/dist/cli.js +34 -18
- package/dist/index.js +34 -18
- package/package.json +5 -5
- package/src/commands/list.ts +41 -12
- package/src/scanner.ts +6 -1
- package/tests/e2e/__snapshots__/warnings.test.ts.snap +104 -0
- package/tests/e2e/warnings.test.ts +186 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@md2do/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "CLI interface for md2do task manager",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"markdown",
|
|
@@ -40,16 +40,16 @@
|
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@doist/todoist-api-typescript": "^3.0.3",
|
|
43
|
-
"chalk": "^
|
|
43
|
+
"chalk": "^4.1.2",
|
|
44
44
|
"cli-table3": "^0.6.5",
|
|
45
45
|
"commander": "^11.1.0",
|
|
46
46
|
"cosmiconfig": "^9.0.0",
|
|
47
47
|
"date-fns": "^3.0.6",
|
|
48
48
|
"fast-glob": "^3.3.3",
|
|
49
49
|
"zod": "^3.22.4",
|
|
50
|
-
"@md2do/core": "0.
|
|
51
|
-
"@md2do/config": "0.
|
|
52
|
-
"@md2do/todoist": "0.
|
|
50
|
+
"@md2do/core": "0.3.0",
|
|
51
|
+
"@md2do/config": "0.3.0",
|
|
52
|
+
"@md2do/todoist": "0.3.0"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"tsup": "^8.0.1"
|
package/src/commands/list.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import { filters, sorting } from '@md2do/core';
|
|
2
|
+
import { filters, sorting, filterWarnings } from '@md2do/core';
|
|
3
|
+
import { loadConfig, DEFAULT_CONFIG } from '@md2do/config';
|
|
3
4
|
import { scanMarkdownFiles } from '../scanner.js';
|
|
4
5
|
import { formatAsPretty, formatAsTable } from '../formatters/pretty.js';
|
|
5
6
|
import { formatAsJson } from '../formatters/json.js';
|
|
@@ -25,6 +26,8 @@ interface ListCommandOptions {
|
|
|
25
26
|
colors?: boolean;
|
|
26
27
|
paths?: boolean;
|
|
27
28
|
context?: boolean;
|
|
29
|
+
warnings?: boolean;
|
|
30
|
+
allWarnings?: boolean;
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
export function createListCommand(): Command {
|
|
@@ -77,6 +80,10 @@ export function createListCommand(): Command {
|
|
|
77
80
|
.option('--no-paths', 'Hide file paths')
|
|
78
81
|
.option('--context', 'Show context information (project, person, heading)')
|
|
79
82
|
|
|
83
|
+
// Warning options
|
|
84
|
+
.option('--no-warnings', 'Hide all warnings')
|
|
85
|
+
.option('--all-warnings', 'Show all warnings (default shows first 5)')
|
|
86
|
+
|
|
80
87
|
.action(async (options: ListCommandOptions) => {
|
|
81
88
|
try {
|
|
82
89
|
// Scan markdown files
|
|
@@ -205,20 +212,42 @@ export function createListCommand(): Command {
|
|
|
205
212
|
|
|
206
213
|
console.log(output);
|
|
207
214
|
|
|
208
|
-
//
|
|
209
|
-
if (
|
|
210
|
-
|
|
211
|
-
|
|
215
|
+
// Load config and apply warning filters (unless --no-warnings overrides)
|
|
216
|
+
if (options.warnings !== false) {
|
|
217
|
+
const config = await loadConfig({
|
|
218
|
+
cwd: options.path || process.cwd(),
|
|
219
|
+
});
|
|
220
|
+
const warningConfig = config.warnings ?? DEFAULT_CONFIG.warnings;
|
|
221
|
+
|
|
222
|
+
// Apply config-based filtering
|
|
223
|
+
const filteredWarnings = filterWarnings(
|
|
224
|
+
scanResult.warnings,
|
|
225
|
+
warningConfig ?? {},
|
|
212
226
|
);
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
);
|
|
217
|
-
}
|
|
218
|
-
if (scanResult.warnings.length > 5) {
|
|
227
|
+
|
|
228
|
+
// Show warnings if any remain after filtering
|
|
229
|
+
if (filteredWarnings.length > 0) {
|
|
219
230
|
console.error(
|
|
220
|
-
|
|
231
|
+
`\n⚠️ ${filteredWarnings.length} warning${filteredWarnings.length > 1 ? 's' : ''} encountered during scanning`,
|
|
221
232
|
);
|
|
233
|
+
|
|
234
|
+
// Show all warnings if --all-warnings, otherwise show first 5
|
|
235
|
+
const warningsToShow = options.allWarnings
|
|
236
|
+
? filteredWarnings
|
|
237
|
+
: filteredWarnings.slice(0, 5);
|
|
238
|
+
|
|
239
|
+
for (const warning of warningsToShow) {
|
|
240
|
+
// Use message field (new) or fallback to reason (legacy)
|
|
241
|
+
const message =
|
|
242
|
+
warning.message || warning.reason || 'Unknown warning';
|
|
243
|
+
console.error(` ${warning.file}:${warning.line} - ${message}`);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (!options.allWarnings && filteredWarnings.length > 5) {
|
|
247
|
+
console.error(
|
|
248
|
+
` ... and ${filteredWarnings.length - 5} more warning${filteredWarnings.length - 5 > 1 ? 's' : ''} (use --all-warnings to see all)`,
|
|
249
|
+
);
|
|
250
|
+
}
|
|
222
251
|
}
|
|
223
252
|
}
|
|
224
253
|
} catch (error) {
|
package/src/scanner.ts
CHANGED
|
@@ -82,11 +82,16 @@ export async function scanMarkdownFiles(
|
|
|
82
82
|
allWarnings.push(...result.warnings);
|
|
83
83
|
} catch (error) {
|
|
84
84
|
// Add warning for files that couldn't be read
|
|
85
|
+
const message = `Failed to read file: ${error instanceof Error ? error.message : 'Unknown error'}`;
|
|
85
86
|
allWarnings.push({
|
|
87
|
+
severity: 'error',
|
|
88
|
+
source: 'md2do',
|
|
89
|
+
ruleId: 'file-read-error',
|
|
86
90
|
file,
|
|
87
91
|
line: 0,
|
|
88
92
|
text: '',
|
|
89
|
-
|
|
93
|
+
message,
|
|
94
|
+
reason: message,
|
|
90
95
|
});
|
|
91
96
|
}
|
|
92
97
|
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`E2E: Warning Configuration Profiles > custom: all disabled > all-disabled 1`] = `
|
|
4
|
+
"
|
|
5
|
+
Found 3 tasks
|
|
6
|
+
✓ 1 completed | ○ 2 incomplete
|
|
7
|
+
|
|
8
|
+
○ !! Valid task (2026-02-01) @alice #bug
|
|
9
|
+
tasks.md:6
|
|
10
|
+
|
|
11
|
+
✓ Completed task without completion date
|
|
12
|
+
tasks.md:7
|
|
13
|
+
|
|
14
|
+
○ Task without a due date
|
|
15
|
+
tasks.md:8
|
|
16
|
+
|
|
17
|
+
"
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
exports[`E2E: Warning Configuration Profiles > custom: only spacing errors > custom-space-only 1`] = `
|
|
21
|
+
"
|
|
22
|
+
Found 3 tasks
|
|
23
|
+
✓ 1 completed | ○ 2 incomplete
|
|
24
|
+
|
|
25
|
+
○ !! Valid task (2026-02-01) @alice #bug
|
|
26
|
+
tasks.md:6
|
|
27
|
+
|
|
28
|
+
✓ Completed task without completion date
|
|
29
|
+
tasks.md:7
|
|
30
|
+
|
|
31
|
+
○ Task without a due date
|
|
32
|
+
tasks.md:8
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
⚠️ 2 warnings encountered during scanning
|
|
36
|
+
tasks.md:4 - Missing space after checkbox. Use "- [x] Task" format.
|
|
37
|
+
tasks.md:5 - Missing space before checkbox. Use "- [x] Task" format.
|
|
38
|
+
"
|
|
39
|
+
`;
|
|
40
|
+
|
|
41
|
+
exports[`E2E: Warning Configuration Profiles > recommended profile (default) > recommended-default 1`] = `
|
|
42
|
+
"
|
|
43
|
+
Found 3 tasks
|
|
44
|
+
✓ 1 completed | ○ 2 incomplete
|
|
45
|
+
|
|
46
|
+
○ !! Valid task (2026-02-01) @alice #bug
|
|
47
|
+
tasks.md:6
|
|
48
|
+
|
|
49
|
+
✓ Completed task without completion date
|
|
50
|
+
tasks.md:7
|
|
51
|
+
|
|
52
|
+
○ Task without a due date
|
|
53
|
+
tasks.md:8
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
⚠️ 3 warnings encountered during scanning
|
|
57
|
+
tasks.md:3 - Unsupported bullet marker (* or +). Use dash (-) for task lists.
|
|
58
|
+
tasks.md:4 - Missing space after checkbox. Use "- [x] Task" format.
|
|
59
|
+
tasks.md:5 - Missing space before checkbox. Use "- [x] Task" format.
|
|
60
|
+
"
|
|
61
|
+
`;
|
|
62
|
+
|
|
63
|
+
exports[`E2E: Warning Configuration Profiles > strict profile > strict-all-warnings 1`] = `
|
|
64
|
+
"
|
|
65
|
+
Found 3 tasks
|
|
66
|
+
✓ 1 completed | ○ 2 incomplete
|
|
67
|
+
|
|
68
|
+
○ !! Valid task (2026-02-01) @alice #bug
|
|
69
|
+
tasks.md:6
|
|
70
|
+
|
|
71
|
+
✓ Completed task without completion date
|
|
72
|
+
tasks.md:7
|
|
73
|
+
|
|
74
|
+
○ Task without a due date
|
|
75
|
+
tasks.md:8
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
⚠️ 6 warnings encountered during scanning
|
|
79
|
+
tasks.md:3 - Unsupported bullet marker (* or +). Use dash (-) for task lists.
|
|
80
|
+
tasks.md:4 - Missing space after checkbox. Use "- [x] Task" format.
|
|
81
|
+
tasks.md:5 - Missing space before checkbox. Use "- [x] Task" format.
|
|
82
|
+
tasks.md:6 - Task has no due date. Add [due: YYYY-MM-DD] or place under a heading with a date.
|
|
83
|
+
tasks.md:7 - Completed task missing completion date. Add [completed: YYYY-MM-DD].
|
|
84
|
+
... and 1 more warning (use --all-warnings to see all)
|
|
85
|
+
"
|
|
86
|
+
`;
|
|
87
|
+
|
|
88
|
+
exports[`E2E: Warning Filtering Edge Cases > respects CLI --no-warnings override > cli-no-warnings-override 1`] = `
|
|
89
|
+
"No tasks found
|
|
90
|
+
"
|
|
91
|
+
`;
|
|
92
|
+
|
|
93
|
+
exports[`E2E: Warning Filtering Edge Cases > shows warnings correctly with truncation > truncated-warnings 1`] = `
|
|
94
|
+
"No tasks found
|
|
95
|
+
|
|
96
|
+
⚠️ 20 warnings encountered during scanning
|
|
97
|
+
many-warnings.md:1 - Unsupported bullet marker (* or +). Use dash (-) for task lists.
|
|
98
|
+
many-warnings.md:2 - Unsupported bullet marker (* or +). Use dash (-) for task lists.
|
|
99
|
+
many-warnings.md:3 - Unsupported bullet marker (* or +). Use dash (-) for task lists.
|
|
100
|
+
many-warnings.md:4 - Unsupported bullet marker (* or +). Use dash (-) for task lists.
|
|
101
|
+
many-warnings.md:5 - Unsupported bullet marker (* or +). Use dash (-) for task lists.
|
|
102
|
+
... and 15 more warnings (use --all-warnings to see all)
|
|
103
|
+
"
|
|
104
|
+
`;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2E Tests: Warning Configuration Profiles
|
|
3
|
+
*
|
|
4
|
+
* Tests warning filtering with different configuration profiles using snapshots.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect } from 'vitest';
|
|
8
|
+
import { execSync } from 'child_process';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
import { mkdtempSync, writeFileSync, rmSync } from 'fs';
|
|
11
|
+
import { tmpdir } from 'os';
|
|
12
|
+
|
|
13
|
+
function runCLIWithConfig(
|
|
14
|
+
config: object,
|
|
15
|
+
testFiles: Record<string, string>,
|
|
16
|
+
): string {
|
|
17
|
+
const tmpDir = mkdtempSync(join(tmpdir(), 'md2do-test-'));
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
// Write config
|
|
21
|
+
writeFileSync(join(tmpDir, '.md2do.json'), JSON.stringify(config, null, 2));
|
|
22
|
+
|
|
23
|
+
// Write test files
|
|
24
|
+
for (const [filename, content] of Object.entries(testFiles)) {
|
|
25
|
+
writeFileSync(join(tmpDir, filename), content);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Run CLI - need to capture both stdout and stderr since warnings go to stderr
|
|
29
|
+
const cliPath = join(__dirname, '../../dist/cli.js');
|
|
30
|
+
const output = execSync(
|
|
31
|
+
`node ${cliPath} list --path ${tmpDir} --no-colors 2>&1`,
|
|
32
|
+
{
|
|
33
|
+
encoding: 'utf-8',
|
|
34
|
+
},
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
// Normalize file paths for consistent snapshots across environments
|
|
38
|
+
// Replace absolute paths with relative paths
|
|
39
|
+
return output.replace(new RegExp('file://[^\\s]+/packages/cli/', 'g'), '');
|
|
40
|
+
} finally {
|
|
41
|
+
rmSync(tmpDir, { recursive: true });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
describe('E2E: Warning Configuration Profiles', () => {
|
|
46
|
+
const testFiles = {
|
|
47
|
+
'tasks.md': `
|
|
48
|
+
# Tasks
|
|
49
|
+
|
|
50
|
+
* [x] Wrong bullet type task
|
|
51
|
+
- [ ]Missing space after checkbox task
|
|
52
|
+
-[x] Missing space before checkbox task
|
|
53
|
+
- [ ] Valid task @alice !! #bug (2026-02-01)
|
|
54
|
+
- [x] Completed task without completion date
|
|
55
|
+
- [ ] Task without a due date
|
|
56
|
+
`.trim(),
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
it('recommended profile (default)', () => {
|
|
60
|
+
const output = runCLIWithConfig({}, testFiles);
|
|
61
|
+
|
|
62
|
+
// Format warnings shown
|
|
63
|
+
expect(output).toContain('⚠️');
|
|
64
|
+
expect(output).toContain('Unsupported bullet');
|
|
65
|
+
expect(output).toContain('Missing space');
|
|
66
|
+
|
|
67
|
+
// Metadata warnings NOT shown (stylistic choice)
|
|
68
|
+
expect(output).not.toContain('No due date');
|
|
69
|
+
expect(output).not.toContain('Completed without date');
|
|
70
|
+
|
|
71
|
+
expect(output).toMatchSnapshot('recommended-default');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('strict profile', () => {
|
|
75
|
+
const output = runCLIWithConfig(
|
|
76
|
+
{
|
|
77
|
+
warnings: {
|
|
78
|
+
enabled: true,
|
|
79
|
+
rules: {
|
|
80
|
+
'unsupported-bullet': 'error',
|
|
81
|
+
'malformed-checkbox': 'error',
|
|
82
|
+
'missing-space-after': 'error',
|
|
83
|
+
'missing-space-before': 'error',
|
|
84
|
+
'relative-date-no-context': 'error',
|
|
85
|
+
'missing-due-date': 'warn',
|
|
86
|
+
'missing-completed-date': 'warn',
|
|
87
|
+
'duplicate-todoist-id': 'error',
|
|
88
|
+
'file-read-error': 'error',
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
testFiles,
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
// ALL warnings shown
|
|
96
|
+
expect(output).toContain('⚠️');
|
|
97
|
+
expect(output).toContain('Unsupported bullet');
|
|
98
|
+
expect(output).toContain('Missing space');
|
|
99
|
+
expect(output).toContain('Task has no due date');
|
|
100
|
+
expect(output).toContain('Completed task missing completion date');
|
|
101
|
+
|
|
102
|
+
expect(output).toMatchSnapshot('strict-all-warnings');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('custom: all disabled', () => {
|
|
106
|
+
const output = runCLIWithConfig(
|
|
107
|
+
{
|
|
108
|
+
warnings: { enabled: false },
|
|
109
|
+
},
|
|
110
|
+
testFiles,
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
expect(output).not.toContain('⚠️');
|
|
114
|
+
expect(output).toMatchSnapshot('all-disabled');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('custom: only spacing errors', () => {
|
|
118
|
+
const output = runCLIWithConfig(
|
|
119
|
+
{
|
|
120
|
+
warnings: {
|
|
121
|
+
enabled: true,
|
|
122
|
+
rules: {
|
|
123
|
+
'unsupported-bullet': 'off',
|
|
124
|
+
'malformed-checkbox': 'off',
|
|
125
|
+
'missing-space-after': 'error',
|
|
126
|
+
'missing-space-before': 'error',
|
|
127
|
+
'relative-date-no-context': 'off',
|
|
128
|
+
'missing-due-date': 'off',
|
|
129
|
+
'missing-completed-date': 'off',
|
|
130
|
+
'duplicate-todoist-id': 'error',
|
|
131
|
+
'file-read-error': 'error',
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
testFiles,
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
// Only space warnings
|
|
139
|
+
expect(output).toContain('Missing space');
|
|
140
|
+
expect(output).not.toContain('Unsupported bullet');
|
|
141
|
+
expect(output).not.toContain('Task has no due date');
|
|
142
|
+
|
|
143
|
+
expect(output).toMatchSnapshot('custom-space-only');
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe('E2E: Warning Filtering Edge Cases', () => {
|
|
148
|
+
it('shows warnings correctly with truncation', () => {
|
|
149
|
+
// Create many warnings to test truncation
|
|
150
|
+
const testFiles = {
|
|
151
|
+
'many-warnings.md': Array.from(
|
|
152
|
+
{ length: 20 },
|
|
153
|
+
(_, i) => `* [ ] Task ${i}`,
|
|
154
|
+
).join('\n'),
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const output = runCLIWithConfig({}, testFiles);
|
|
158
|
+
|
|
159
|
+
// Should show first 5, then "... and N more"
|
|
160
|
+
expect(output).toMatch(/and \d+ more warning/);
|
|
161
|
+
expect(output).toMatchSnapshot('truncated-warnings');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('respects CLI --no-warnings override', () => {
|
|
165
|
+
const tmpDir = mkdtempSync(join(tmpdir(), 'md2do-test-'));
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
const testFile = '* [x] Wrong bullet';
|
|
169
|
+
writeFileSync(join(tmpDir, 'tasks.md'), testFile);
|
|
170
|
+
|
|
171
|
+
const cliPath = join(__dirname, '../../dist/cli.js');
|
|
172
|
+
const output = execSync(
|
|
173
|
+
`node ${cliPath} list --path ${tmpDir} --no-warnings --no-colors`,
|
|
174
|
+
{
|
|
175
|
+
encoding: 'utf-8',
|
|
176
|
+
},
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
// Should not show warnings even though config has them enabled
|
|
180
|
+
expect(output).not.toContain('⚠️');
|
|
181
|
+
expect(output).toMatchSnapshot('cli-no-warnings-override');
|
|
182
|
+
} finally {
|
|
183
|
+
rmSync(tmpDir, { recursive: true });
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
});
|