Skip to content

Commit 6713e73

Browse files
committed
Add lint watch mode, and VSCode tasks
1 parent e2719d3 commit 6713e73

File tree

4 files changed

+250
-0
lines changed

4 files changed

+250
-0
lines changed

.vscode/tasks.json

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
{
2+
// See https://go.microsoft.com/fwlink/?LinkId=733558
3+
// for the documentation about the tasks.json format
4+
"version": "2.0.0",
5+
"tasks": [
6+
{
7+
"label": "./lint",
8+
"detail": "Runs all supplied linters. This is the same check that runs on all pull requests.",
9+
"type": "npm",
10+
"script": "lint",
11+
"problemMatcher": {
12+
"fileLocation": ["relative", "${workspaceFolder}"],
13+
"severity": "error",
14+
"owner": "lint-owner",
15+
"source": "lint-source",
16+
"pattern": {
17+
"regexp": "^(.*?):(\\d+)(?::(\\d+))? ((?:MD|VL)\\S+) (.*)$",
18+
"file": 1,
19+
"line": 2,
20+
"column": 3,
21+
"code": 4,
22+
"message": 5
23+
}
24+
}
25+
},
26+
{
27+
"label": "./lint (watch)",
28+
"detail": "Runs all linters. This is the same check that runs on all pull requests.",
29+
"type": "npm",
30+
"script": "lint:watch",
31+
"isBackground": true,
32+
"problemMatcher": {
33+
"fileLocation": ["relative", "${workspaceFolder}"],
34+
"severity": "error",
35+
"owner": "lint-owner",
36+
"source": "lint-source",
37+
"pattern": {
38+
"regexp": "^(.*?):(\\d+)(?::(\\d+))? ((?:MD|VL)\\S+) (.*)$",
39+
"file": 1,
40+
"line": 2,
41+
"column": 3,
42+
"code": 4,
43+
"message": 5
44+
},
45+
"background": {
46+
"activeOnStart": true,
47+
"beginsPattern": "^lint: RUN$",
48+
"endsPattern": "^lint: (PASS|FAIL)$"
49+
}
50+
}
51+
},
52+
{
53+
"label": "./lint validate-links",
54+
"detail": "Check markdown for broken internal links & images",
55+
"type": "npm",
56+
"script": "lint:validate-links",
57+
"problemMatcher": {
58+
"fileLocation": ["relative", "${workspaceFolder}"],
59+
"severity": "error",
60+
"owner": "lint-owner",
61+
"source": "lint-source",
62+
"pattern": {
63+
"regexp": "^(.*?):(\\d+)(?::(\\d+))? ((?:MD|VL)\\S+) (.*)$",
64+
"file": 1,
65+
"line": 2,
66+
"column": 3,
67+
"code": 4,
68+
"message": 5
69+
}
70+
}
71+
},
72+
{
73+
"label": "./lint validate-links (watch)",
74+
"detail": "Check markdown for broken internal links & images",
75+
"type": "npm",
76+
"script": "lint:validate-links:watch",
77+
"isBackground": true,
78+
"problemMatcher": {
79+
"fileLocation": ["relative", "${workspaceFolder}"],
80+
"severity": "error",
81+
"owner": "lint-owner",
82+
"source": "lint-source",
83+
"pattern": {
84+
"regexp": "^(.*?):(\\d+)(?::(\\d+))? ((?:MD|VL)\\S+) (.*)$",
85+
"file": 1,
86+
"line": 2,
87+
"column": 3,
88+
"code": 4,
89+
"message": 5
90+
},
91+
"background": {
92+
"activeOnStart": true,
93+
"beginsPattern": "^validate-links: RUN$",
94+
"endsPattern": "^validate-links: (PASS|FAIL)$"
95+
}
96+
}
97+
}
98+
]
99+
}

dist/watch.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { spawn } from "node:child_process";
2+
import { watch } from "node:fs";
3+
const args = process.argv.slice(2);
4+
if (args.length === 0) {
5+
console.error("Usage: node dist/watch.js COMMAND [ARGS...]");
6+
process.exit(1);
7+
}
8+
const cmdAndArgs = args;
9+
let state = { tag: "waiting" };
10+
const markAsDirty = () => {
11+
if (state.tag === "waiting") {
12+
setTimeout(startRun, 200);
13+
state = { tag: "delaying" };
14+
}
15+
else if (state.tag === "running-clean") {
16+
state = {
17+
...state,
18+
tag: "running-dirty",
19+
};
20+
}
21+
else if (state.tag === "running-dirty") {
22+
//
23+
}
24+
};
25+
const startRun = () => {
26+
console.log("Spawning child process");
27+
const child = spawn(cmdAndArgs[0], cmdAndArgs.slice(1), {
28+
stdio: ["ignore", "inherit", "inherit"],
29+
});
30+
child.on("close", (code, signal) => {
31+
if (code) {
32+
console.log(`Child process failed: exited with code ${code}`);
33+
}
34+
else if (signal) {
35+
console.log(`Child process failed: killed by ${signal}`);
36+
}
37+
else {
38+
console.log(`Child process succeeded`);
39+
}
40+
if (state.tag === "running-clean") {
41+
console.log("Waiting...");
42+
state = { tag: "waiting" };
43+
}
44+
else if (state.tag === "running-dirty") {
45+
startRun();
46+
}
47+
});
48+
state = {
49+
tag: "running-clean",
50+
child,
51+
};
52+
};
53+
const listener = (event, filename) => {
54+
console.log(`Watcher event: ${event} ${filename}`);
55+
if (filename?.startsWith(".git/"))
56+
return;
57+
if (filename?.startsWith("node_modules/"))
58+
return;
59+
markAsDirty();
60+
};
61+
watch(".", { recursive: true, encoding: "binary" }, listener);
62+
console.log("Initiating first run");
63+
startRun();

package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,12 @@
88
"@types/markdown-it": "^14.1.2",
99
"@types/node": "^22.14.1",
1010
"typescript": "^5.8.3"
11+
},
12+
"scripts": {
13+
"lint": "./lint",
14+
"lint:fix": "./lint --fix",
15+
"lint:validate-links": "./lint validate-links",
16+
"lint:validate-links:watch": "node dist/watch.js ./lint validate-links",
17+
"lint:watch": "node dist/watch.js ./lint"
1118
}
1219
}

watch.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { spawn, type ChildProcess } from "node:child_process";
2+
import { watch } from "node:fs";
3+
4+
const args = process.argv.slice(2);
5+
if (args.length === 0) {
6+
console.error("Usage: node dist/watch.js COMMAND [ARGS...]");
7+
process.exit(1);
8+
}
9+
10+
type State =
11+
| {
12+
readonly tag: "waiting";
13+
}
14+
| {
15+
readonly tag: "delaying";
16+
}
17+
| {
18+
readonly tag: "running-clean";
19+
readonly child: ChildProcess;
20+
}
21+
| {
22+
readonly tag: "running-dirty";
23+
readonly child: ChildProcess;
24+
};
25+
26+
const cmdAndArgs = args;
27+
let state: State = { tag: "waiting" };
28+
29+
const markAsDirty = () => {
30+
if (state.tag === "waiting") {
31+
setTimeout(startRun, 200);
32+
state = { tag: "delaying" };
33+
} else if (state.tag === "running-clean") {
34+
state = {
35+
...state,
36+
tag: "running-dirty",
37+
};
38+
} else if (state.tag === "running-dirty") {
39+
//
40+
}
41+
};
42+
43+
const startRun = () => {
44+
console.log("Spawning child process");
45+
const child = spawn(cmdAndArgs[0], cmdAndArgs.slice(1), {
46+
stdio: ["ignore", "inherit", "inherit"],
47+
});
48+
49+
child.on("close", (code, signal) => {
50+
if (code) {
51+
console.log(`Child process failed: exited with code ${code}`);
52+
} else if (signal) {
53+
console.log(`Child process failed: killed by ${signal}`);
54+
} else {
55+
console.log(`Child process succeeded`);
56+
}
57+
58+
if (state.tag === "running-clean") {
59+
console.log("Waiting...");
60+
state = { tag: "waiting" };
61+
} else if (state.tag === "running-dirty") {
62+
startRun();
63+
}
64+
});
65+
66+
state = {
67+
tag: "running-clean",
68+
child,
69+
};
70+
};
71+
72+
const listener = (event: string, filename: string | null) => {
73+
console.log(`Watcher event: ${event} ${filename}`);
74+
if (filename?.startsWith(".git/")) return;
75+
if (filename?.startsWith("node_modules/")) return;
76+
markAsDirty();
77+
};
78+
79+
watch(".", { recursive: true, encoding: "binary" }, listener);
80+
console.log("Initiating first run");
81+
startRun();

0 commit comments

Comments
 (0)