Repository navigation
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Verification results for 1. Lint, tests, and build
2. The new tests catch the old configWith 3. The real watcher on
|
| Operation | chokidar 4.0.3 |
chokidar 5.0.0 |
|---|---|---|
Change a.md |
1 | 1 |
Add b.md |
1 | 1 |
Unlink b.md |
1 | 1 |
Add .remote/c.md |
0 | 0 |
With the old ignored in the built dist/server/app.js (4.0.3), adding .remote/c.md sent 1 message.
Script
// Usage: node verify-watcher.js <qiita-cli dir>
const path = require("node:path");
const fs = require("node:fs");
const os = require("node:os");
const { createServer } = require("node:http");
const root = path.resolve(process.argv[2]);
const { startLocalChangeWatcher } = require(path.join(root, "dist/server/app.js"));
const WebSocket = require(path.join(root, "node_modules/ws"));
console.log("chokidar", require(path.join(root, "node_modules/chokidar/package.json")).version);
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
(async () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "watch-"));
fs.mkdirSync(path.join(dir, ".remote"));
fs.writeFileSync(path.join(dir, "a.md"), "first\n");
const server = createServer();
await new Promise((r) => server.listen(0, "127.0.0.1", r));
await startLocalChangeWatcher({ server, watchPaths: dir });
const ws = new WebSocket(`ws://127.0.0.1:${server.address().port}`);
const received = [];
ws.on("message", (m) => received.push(m.toString()));
await new Promise((r) => ws.on("open", r));
await sleep(1000); // initial scan
const step = async (label, fn) => {
const before = received.length;
fn();
await sleep(1000);
console.log(`${label}: ${received.length - before} message(s)`, received.slice(before));
};
received.length = 0; // ignore events from the initial scan
await step("change a.md", () => fs.appendFileSync(path.join(dir, "a.md"), "second\n"));
await step("add b.md", () => fs.writeFileSync(path.join(dir, "b.md"), "new\n"));
await step("unlink b.md", () => fs.unlinkSync(path.join(dir, "b.md")));
await step("add .remote/c.md", () => fs.writeFileSync(path.join(dir, ".remote/c.md"), "remote\n"));
ws.close();
server.close();
fs.rmSync(dir, { recursive: true, force: true });
process.exit(0);
})();4. Jest with chokidar 5.0.0
With node_modules/chokidar pointed to 5.0.0, src/server/app.test.ts and the tests under src/commands pass, because they stub chokidar.
Tests: 42 passed, 42 total
5. Publishing and updating an item from qiita preview
I ran qiita preview (2798536) against a local Qiita API, and called POST /api/items/:id to publish a new public item and then update it.
Next to the preview, I ran two watchers on the same root and logged their events: one with the old ignored, and one with the new one.
| Operation | Old ignored |
New ignored |
|---|---|---|
| Publish a new item | change <name>.md ×2, add .remote/<id>.md |
change <name>.md ×2 |
| Update the item | change .remote/<id>.md, change <name>.md |
change <name>.md |
I also logged the paths that the preview's own watcher notified.
In a second run, it notified only <name>.md: 2 times for publishing and 1 time for updating, and never .remote/<id>.md.
Notes:
- Publishing writes the item file twice: once to set its
id, and once when the item is synced from the response. So publishing sends 2 messages even without.remote. - On macOS, the item file itself sometimes emits two
changeevents 50 to 140 ms apart, so one write can send two messages. This also happens when the file is only edited locally, and it is unrelated to.remote.
Not verified
- How the browser reloads after the messages.
What
Make the file watcher of
qiita previewactually ignore the.remotedirectory, so that changes under it no longer sendlocal changedto the browser.How
chokidar4,ignoredcompares a string with the whole path. So the watcher lists<watched path>/.remoteitself.chokidardoes not descend into an ignored directory, and a directory named.remoteabove the watched path is not affected.chokidar5.0.0 handlesignoredin the same way, so this keeps working after Bump chokidar from 4.0.3 to 5.0.0 #448.src/server/app.test.tsstubschokidarand checks theignoredoption passed tochokidar.watch, in the same way as the existing tests stubchalk. Jest cannot load the ESM-onlychokidar5.0.0 without--experimental-vm-modules, so a test with the real watcher would break Bump chokidar from 4.0.3 to 5.0.0 #448. Whetherchokidarhonors the option is checked with the real watcher on 4.0.3 and 5.0.0, and the result is in a comment.Why
ignoredhas"**/.remote/**", written as a glob.chokidardropped glob support in 4.0.0 and now compares strings with the whole path, so this entry matches no path. It was added in #339, afterchokidarwas bumped to 4.0.3 in #234, so it has had no effect since it was added.As a result, a write under
.remotesendslocal changedto the browser, and the client fetches the item again. This happens when an item is published from the preview (it also writes.remote), and when another process such asnpx qiita pullsyncs while the preview is running. Nothing breaks, but the watcher does not do what the config says.Refs