@md2do/cli 0.2.3 → 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 +17 -0
- package/dist/cli.js +33 -18
- package/dist/index.js +33 -18
- package/package.json +5 -5
- package/src/commands/list.ts +33 -17
- 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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @md2do/cli
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Add comprehensive warning system for markdown validation and VSCode extension
|
|
8
|
+
- Add warning configuration system with customizable rules
|
|
9
|
+
- Add VSCode extension with task explorer, diagnostics, hover tooltips, and smart completion
|
|
10
|
+
- Support for validating malformed checkboxes, missing metadata, duplicate IDs, and more
|
|
11
|
+
- Add .vsix distribution for beta testing
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- Updated dependencies []:
|
|
16
|
+
- @md2do/core@0.3.0
|
|
17
|
+
- @md2do/config@0.3.0
|
|
18
|
+
- @md2do/todoist@0.3.0
|
|
19
|
+
|
|
3
20
|
## 0.2.3
|
|
4
21
|
|
|
5
22
|
### Patch Changes
|
package/dist/cli.js
CHANGED
|
@@ -31,6 +31,7 @@ var import_path = require("path");
|
|
|
31
31
|
// src/commands/list.ts
|
|
32
32
|
var import_commander = require("commander");
|
|
33
33
|
var import_core2 = require("@md2do/core");
|
|
34
|
+
var import_config = require("@md2do/config");
|
|
34
35
|
|
|
35
36
|
// src/scanner.ts
|
|
36
37
|
var import_promises = require("fs/promises");
|
|
@@ -68,11 +69,16 @@ async function scanMarkdownFiles(options = {}) {
|
|
|
68
69
|
allTasks.push(...result.tasks);
|
|
69
70
|
allWarnings.push(...result.warnings);
|
|
70
71
|
} catch (error) {
|
|
72
|
+
const message = `Failed to read file: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
71
73
|
allWarnings.push({
|
|
74
|
+
severity: "error",
|
|
75
|
+
source: "md2do",
|
|
76
|
+
ruleId: "file-read-error",
|
|
72
77
|
file,
|
|
73
78
|
line: 0,
|
|
74
79
|
text: "",
|
|
75
|
-
|
|
80
|
+
message,
|
|
81
|
+
reason: message
|
|
76
82
|
});
|
|
77
83
|
}
|
|
78
84
|
}
|
|
@@ -388,21 +394,30 @@ function createListCommand() {
|
|
|
388
394
|
break;
|
|
389
395
|
}
|
|
390
396
|
console.log(output);
|
|
391
|
-
if (options.warnings !== false
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
397
|
+
if (options.warnings !== false) {
|
|
398
|
+
const config = await (0, import_config.loadConfig)({
|
|
399
|
+
cwd: options.path || process.cwd()
|
|
400
|
+
});
|
|
401
|
+
const warningConfig = config.warnings ?? import_config.DEFAULT_CONFIG.warnings;
|
|
402
|
+
const filteredWarnings = (0, import_core2.filterWarnings)(
|
|
403
|
+
scanResult.warnings,
|
|
404
|
+
warningConfig ?? {}
|
|
395
405
|
);
|
|
396
|
-
|
|
397
|
-
for (const warning of warningsToShow) {
|
|
398
|
-
console.error(
|
|
399
|
-
` ${warning.file}:${warning.line} - ${warning.reason}`
|
|
400
|
-
);
|
|
401
|
-
}
|
|
402
|
-
if (!options.allWarnings && scanResult.warnings.length > 5) {
|
|
406
|
+
if (filteredWarnings.length > 0) {
|
|
403
407
|
console.error(
|
|
404
|
-
`
|
|
408
|
+
`
|
|
409
|
+
\u26A0\uFE0F ${filteredWarnings.length} warning${filteredWarnings.length > 1 ? "s" : ""} encountered during scanning`
|
|
405
410
|
);
|
|
411
|
+
const warningsToShow = options.allWarnings ? filteredWarnings : filteredWarnings.slice(0, 5);
|
|
412
|
+
for (const warning of warningsToShow) {
|
|
413
|
+
const message = warning.message || warning.reason || "Unknown warning";
|
|
414
|
+
console.error(` ${warning.file}:${warning.line} - ${message}`);
|
|
415
|
+
}
|
|
416
|
+
if (!options.allWarnings && filteredWarnings.length > 5) {
|
|
417
|
+
console.error(
|
|
418
|
+
` ... and ${filteredWarnings.length - 5} more warning${filteredWarnings.length - 5 > 1 ? "s" : ""} (use --all-warnings to see all)`
|
|
419
|
+
);
|
|
420
|
+
}
|
|
406
421
|
}
|
|
407
422
|
}
|
|
408
423
|
} catch (error) {
|
|
@@ -652,7 +667,7 @@ function capitalize(str) {
|
|
|
652
667
|
|
|
653
668
|
// src/commands/todoist.ts
|
|
654
669
|
var import_commander3 = require("commander");
|
|
655
|
-
var
|
|
670
|
+
var import_config2 = require("@md2do/config");
|
|
656
671
|
var import_todoist = require("@md2do/todoist");
|
|
657
672
|
var import_core4 = require("@md2do/core");
|
|
658
673
|
var import_promises2 = __toESM(require("fs/promises"));
|
|
@@ -681,7 +696,7 @@ function createTodoistListCommand() {
|
|
|
681
696
|
return command;
|
|
682
697
|
}
|
|
683
698
|
async function todoistListAction(options) {
|
|
684
|
-
const config = await (0,
|
|
699
|
+
const config = await (0, import_config2.loadConfig)();
|
|
685
700
|
if (!config.todoist?.apiToken) {
|
|
686
701
|
console.error("\u274C Error: Todoist API token not configured");
|
|
687
702
|
console.error("");
|
|
@@ -812,7 +827,7 @@ function createTodoistAddCommand() {
|
|
|
812
827
|
return command;
|
|
813
828
|
}
|
|
814
829
|
async function todoistAddAction(taskContent, options) {
|
|
815
|
-
const config = await (0,
|
|
830
|
+
const config = await (0, import_config2.loadConfig)();
|
|
816
831
|
if (!config.todoist?.apiToken) {
|
|
817
832
|
console.error("\u274C Error: Todoist API token not configured");
|
|
818
833
|
console.error("");
|
|
@@ -905,7 +920,7 @@ async function todoistImportAction(location, options) {
|
|
|
905
920
|
console.error(`\u274C Error: Invalid line number: ${lineStr}`);
|
|
906
921
|
process.exit(1);
|
|
907
922
|
}
|
|
908
|
-
const config = await (0,
|
|
923
|
+
const config = await (0, import_config2.loadConfig)();
|
|
909
924
|
if (!config.todoist?.apiToken) {
|
|
910
925
|
console.error("\u274C Error: Todoist API token not configured");
|
|
911
926
|
console.error("");
|
|
@@ -1019,7 +1034,7 @@ function createTodoistSyncCommand() {
|
|
|
1019
1034
|
return command;
|
|
1020
1035
|
}
|
|
1021
1036
|
async function todoistSyncAction(options) {
|
|
1022
|
-
const config = await (0,
|
|
1037
|
+
const config = await (0, import_config2.loadConfig)();
|
|
1023
1038
|
if (!config.todoist?.apiToken) {
|
|
1024
1039
|
console.error("\u274C Error: Todoist API token not configured");
|
|
1025
1040
|
console.error("");
|
package/dist/index.js
CHANGED
|
@@ -39,6 +39,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
39
39
|
// src/commands/list.ts
|
|
40
40
|
var import_commander = require("commander");
|
|
41
41
|
var import_core2 = require("@md2do/core");
|
|
42
|
+
var import_config = require("@md2do/config");
|
|
42
43
|
|
|
43
44
|
// src/scanner.ts
|
|
44
45
|
var import_promises = require("fs/promises");
|
|
@@ -76,11 +77,16 @@ async function scanMarkdownFiles(options = {}) {
|
|
|
76
77
|
allTasks.push(...result.tasks);
|
|
77
78
|
allWarnings.push(...result.warnings);
|
|
78
79
|
} catch (error) {
|
|
80
|
+
const message = `Failed to read file: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
79
81
|
allWarnings.push({
|
|
82
|
+
severity: "error",
|
|
83
|
+
source: "md2do",
|
|
84
|
+
ruleId: "file-read-error",
|
|
80
85
|
file,
|
|
81
86
|
line: 0,
|
|
82
87
|
text: "",
|
|
83
|
-
|
|
88
|
+
message,
|
|
89
|
+
reason: message
|
|
84
90
|
});
|
|
85
91
|
}
|
|
86
92
|
}
|
|
@@ -396,21 +402,30 @@ function createListCommand() {
|
|
|
396
402
|
break;
|
|
397
403
|
}
|
|
398
404
|
console.log(output);
|
|
399
|
-
if (options.warnings !== false
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
405
|
+
if (options.warnings !== false) {
|
|
406
|
+
const config = await (0, import_config.loadConfig)({
|
|
407
|
+
cwd: options.path || process.cwd()
|
|
408
|
+
});
|
|
409
|
+
const warningConfig = config.warnings ?? import_config.DEFAULT_CONFIG.warnings;
|
|
410
|
+
const filteredWarnings = (0, import_core2.filterWarnings)(
|
|
411
|
+
scanResult.warnings,
|
|
412
|
+
warningConfig ?? {}
|
|
403
413
|
);
|
|
404
|
-
|
|
405
|
-
for (const warning of warningsToShow) {
|
|
406
|
-
console.error(
|
|
407
|
-
` ${warning.file}:${warning.line} - ${warning.reason}`
|
|
408
|
-
);
|
|
409
|
-
}
|
|
410
|
-
if (!options.allWarnings && scanResult.warnings.length > 5) {
|
|
414
|
+
if (filteredWarnings.length > 0) {
|
|
411
415
|
console.error(
|
|
412
|
-
`
|
|
416
|
+
`
|
|
417
|
+
\u26A0\uFE0F ${filteredWarnings.length} warning${filteredWarnings.length > 1 ? "s" : ""} encountered during scanning`
|
|
413
418
|
);
|
|
419
|
+
const warningsToShow = options.allWarnings ? filteredWarnings : filteredWarnings.slice(0, 5);
|
|
420
|
+
for (const warning of warningsToShow) {
|
|
421
|
+
const message = warning.message || warning.reason || "Unknown warning";
|
|
422
|
+
console.error(` ${warning.file}:${warning.line} - ${message}`);
|
|
423
|
+
}
|
|
424
|
+
if (!options.allWarnings && filteredWarnings.length > 5) {
|
|
425
|
+
console.error(
|
|
426
|
+
` ... and ${filteredWarnings.length - 5} more warning${filteredWarnings.length - 5 > 1 ? "s" : ""} (use --all-warnings to see all)`
|
|
427
|
+
);
|
|
428
|
+
}
|
|
414
429
|
}
|
|
415
430
|
}
|
|
416
431
|
} catch (error) {
|
|
@@ -660,7 +675,7 @@ function capitalize(str) {
|
|
|
660
675
|
|
|
661
676
|
// src/commands/todoist.ts
|
|
662
677
|
var import_commander3 = require("commander");
|
|
663
|
-
var
|
|
678
|
+
var import_config2 = require("@md2do/config");
|
|
664
679
|
var import_todoist = require("@md2do/todoist");
|
|
665
680
|
var import_core4 = require("@md2do/core");
|
|
666
681
|
var import_promises2 = __toESM(require("fs/promises"));
|
|
@@ -689,7 +704,7 @@ function createTodoistListCommand() {
|
|
|
689
704
|
return command;
|
|
690
705
|
}
|
|
691
706
|
async function todoistListAction(options) {
|
|
692
|
-
const config = await (0,
|
|
707
|
+
const config = await (0, import_config2.loadConfig)();
|
|
693
708
|
if (!config.todoist?.apiToken) {
|
|
694
709
|
console.error("\u274C Error: Todoist API token not configured");
|
|
695
710
|
console.error("");
|
|
@@ -820,7 +835,7 @@ function createTodoistAddCommand() {
|
|
|
820
835
|
return command;
|
|
821
836
|
}
|
|
822
837
|
async function todoistAddAction(taskContent, options) {
|
|
823
|
-
const config = await (0,
|
|
838
|
+
const config = await (0, import_config2.loadConfig)();
|
|
824
839
|
if (!config.todoist?.apiToken) {
|
|
825
840
|
console.error("\u274C Error: Todoist API token not configured");
|
|
826
841
|
console.error("");
|
|
@@ -913,7 +928,7 @@ async function todoistImportAction(location, options) {
|
|
|
913
928
|
console.error(`\u274C Error: Invalid line number: ${lineStr}`);
|
|
914
929
|
process.exit(1);
|
|
915
930
|
}
|
|
916
|
-
const config = await (0,
|
|
931
|
+
const config = await (0, import_config2.loadConfig)();
|
|
917
932
|
if (!config.todoist?.apiToken) {
|
|
918
933
|
console.error("\u274C Error: Todoist API token not configured");
|
|
919
934
|
console.error("");
|
|
@@ -1027,7 +1042,7 @@ function createTodoistSyncCommand() {
|
|
|
1027
1042
|
return command;
|
|
1028
1043
|
}
|
|
1029
1044
|
async function todoistSyncAction(options) {
|
|
1030
|
-
const config = await (0,
|
|
1045
|
+
const config = await (0, import_config2.loadConfig)();
|
|
1031
1046
|
if (!config.todoist?.apiToken) {
|
|
1032
1047
|
console.error("\u274C Error: Todoist API token not configured");
|
|
1033
1048
|
console.error("");
|
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/
|
|
51
|
-
"@md2do/
|
|
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';
|
|
@@ -211,27 +212,42 @@ export function createListCommand(): Command {
|
|
|
211
212
|
|
|
212
213
|
console.log(output);
|
|
213
214
|
|
|
214
|
-
//
|
|
215
|
-
if (options.warnings !== false
|
|
216
|
-
|
|
217
|
-
|
|
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 ?? {},
|
|
218
226
|
);
|
|
219
227
|
|
|
220
|
-
// Show
|
|
221
|
-
|
|
222
|
-
? scanResult.warnings
|
|
223
|
-
: scanResult.warnings.slice(0, 5);
|
|
224
|
-
|
|
225
|
-
for (const warning of warningsToShow) {
|
|
228
|
+
// Show warnings if any remain after filtering
|
|
229
|
+
if (filteredWarnings.length > 0) {
|
|
226
230
|
console.error(
|
|
227
|
-
|
|
231
|
+
`\n⚠️ ${filteredWarnings.length} warning${filteredWarnings.length > 1 ? 's' : ''} encountered during scanning`,
|
|
228
232
|
);
|
|
229
|
-
}
|
|
230
233
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
+
}
|
|
235
251
|
}
|
|
236
252
|
}
|
|
237
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
|
+
});
|