@lvce-editor/file-watcher-process 4.13.0 → 4.14.1

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.
Files changed (2) hide show
  1. package/dist/index.js +97 -46
  2. package/package.json +3 -2
package/dist/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import { fileURLToPath, pathToFileURL } from 'node:url';
2
2
  import * as fs from 'node:fs/promises';
3
+ import { readFile } from 'node:fs/promises';
3
4
  import { FSWatcher } from 'chokidar';
4
- import { join } from 'node:path';
5
+ import ignore from 'ignore';
6
+ import { join, relative } from 'node:path';
5
7
 
6
8
  const handleUncaughtExceptionMonitor = error => {
7
9
  console.error(`[file-watcher-process] Uncaught Exception: ${error}`);
@@ -1253,7 +1255,7 @@ const normalizeEvent = (eventName, path) => {
1253
1255
  };
1254
1256
  };
1255
1257
 
1256
- const waitForWatcherToBeReady = async watcher => {
1258
+ const waitForWatcherToBeReady = async (watcher, signal) => {
1257
1259
  const {
1258
1260
  promise,
1259
1261
  reject,
@@ -1262,6 +1264,7 @@ const waitForWatcherToBeReady = async watcher => {
1262
1264
  const cleanup = () => {
1263
1265
  watcher.off('ready', handleReady);
1264
1266
  watcher.off('error', handleError);
1267
+ signal?.removeEventListener('abort', handleAbort);
1265
1268
  };
1266
1269
  const handleReady = () => {
1267
1270
  cleanup();
@@ -1271,6 +1274,13 @@ const waitForWatcherToBeReady = async watcher => {
1271
1274
  cleanup();
1272
1275
  reject(error);
1273
1276
  };
1277
+ const handleAbort = () => {
1278
+ handleError(signal?.reason);
1279
+ };
1280
+ signal?.throwIfAborted();
1281
+ signal?.addEventListener('abort', handleAbort, {
1282
+ once: true
1283
+ });
1274
1284
  watcher.on('ready', handleReady);
1275
1285
  watcher.on('error', handleError);
1276
1286
  await promise;
@@ -1324,7 +1334,7 @@ const callBackInternal = async (eventName, path, stats) => {
1324
1334
  const callback = (eventName, path, stats) => {
1325
1335
  void callBackInternal(eventName, path);
1326
1336
  };
1327
- const errorCallback$1 = error => {
1337
+ const errorCallback = error => {
1328
1338
  console.error(`[file-watcher-process] ${error}`);
1329
1339
  };
1330
1340
  const watchFolder = async uri => {
@@ -1333,7 +1343,7 @@ const watchFolder = async uri => {
1333
1343
  const path = fileURLToPath(uri);
1334
1344
  watcher = new FSWatcher();
1335
1345
  watcher.on('all', callback);
1336
- watcher.on('error', errorCallback$1);
1346
+ watcher.on('error', errorCallback);
1337
1347
  const readyPromise = waitForWatcherToBeReady(watcher);
1338
1348
  watcher.add(path);
1339
1349
  await readyPromise;
@@ -1388,43 +1398,99 @@ const normalizeEvent2 = (id, eventName, path) => {
1388
1398
  };
1389
1399
  };
1390
1400
 
1391
- const errorCallback = error => {
1392
- console.error(`[file-watcher-process] ${error}`);
1401
+ const createIgnore = ignore;
1402
+ const loadGitIgnore = async (root, enabled) => {
1403
+ try {
1404
+ const content = await readFile(`${root}/.gitignore`, 'utf8');
1405
+ return createIgnore().add(content);
1406
+ } catch {
1407
+ return undefined;
1408
+ }
1393
1409
  };
1394
- const createIgnored = exclude => {
1410
+ const createExcludedNamesFilter = exclude => {
1395
1411
  const excludedNames = new Set(exclude);
1396
- return path => {
1397
- return path.split(/[\\/]/).some(part => excludedNames.has(part));
1412
+ return path => path.split(/[\\/]/).some(part => excludedNames.has(part));
1413
+ };
1414
+ const createIgnored = async (root, exclude, useGitIgnore) => {
1415
+ const isExcludedName = createExcludedNamesFilter(exclude);
1416
+ const gitIgnore = await loadGitIgnore(root);
1417
+ return (path, stats) => {
1418
+ if (isExcludedName(path)) {
1419
+ return true;
1420
+ }
1421
+ if (!gitIgnore) {
1422
+ return false;
1423
+ }
1424
+ const relativePath = relative(root, path).replaceAll('\\', '/');
1425
+ if (!relativePath || relativePath.startsWith('../')) {
1426
+ return false;
1427
+ }
1428
+ const candidate = stats?.isDirectory() ? `${relativePath}/` : relativePath;
1429
+ return gitIgnore.ignores(candidate);
1398
1430
  };
1399
1431
  };
1432
+ const send = event => {
1433
+ void invoke('FileWatcher.handleChange', event).catch(error => {
1434
+ console.error(`[file-watcher-process] ${error}`);
1435
+ });
1436
+ };
1400
1437
  const watchFolders = async ({
1401
1438
  exclude,
1402
1439
  id,
1403
- roots
1440
+ roots,
1441
+ useGitIgnore = false
1404
1442
  }) => {
1405
- const callBackInternal = async (eventName, path, stats) => {
1406
- const event = normalizeEvent2(id, eventName, path);
1407
-
1408
- // TODO maybe just use send instead of invoke or
1409
- // send the message to a websocket or message channel directly
1410
- // instead of routing events through shared process
1411
- // which might lock up the shared process in case there are many events
1412
- await invoke('FileWatcher.handleChange', event);
1443
+ if (has(id)) {
1444
+ return fromError(new Error(`watcher ${id} is already registered`));
1445
+ }
1446
+ const controller = new AbortController();
1447
+ const watcherEntries = [];
1448
+ let ready = false;
1449
+ let closing;
1450
+ const callback = (eventName, path) => {
1451
+ if (!controller.signal.aborted) {
1452
+ send(normalizeEvent2(id, eventName, path));
1453
+ }
1413
1454
  };
1414
- const callback = (eventName, path, stats) => {
1415
- void callBackInternal(eventName, path);
1455
+ const errorCallback = error => {
1456
+ console.error(`[file-watcher-process] ${error}`);
1457
+ if (controller.signal.aborted) return;
1458
+ if (ready) {
1459
+ send({
1460
+ ...fromError(error),
1461
+ eventName: 'error',
1462
+ id,
1463
+ uri: ''
1464
+ });
1465
+ }
1466
+ controller.abort(error);
1416
1467
  };
1417
- const watcherEntries = [];
1468
+ controller.signal.addEventListener('abort', () => {
1469
+ remove(id);
1470
+ closing = Promise.allSettled(watcherEntries.map(({
1471
+ watcher
1472
+ }) => watcher.close()));
1473
+ }, {
1474
+ once: true
1475
+ });
1476
+ // Register before asynchronous setup so disposal also cancels a pending watch.
1477
+ add(id, controller);
1418
1478
  try {
1419
1479
  for (const root of roots) {
1480
+ controller.signal.throwIfAborted();
1420
1481
  const path = fileURLToPath(root);
1482
+ const ignored = useGitIgnore ? await createIgnored(path, exclude, true) : createExcludedNamesFilter(exclude);
1483
+ controller.signal.throwIfAborted();
1421
1484
  const watcher = new FSWatcher({
1422
- ignored: createIgnored(exclude),
1485
+ ignored,
1423
1486
  ignoreInitial: true,
1424
- ignorePermissionErrors: true
1487
+ ignorePermissionErrors: false
1425
1488
  });
1426
1489
  watcher.on('error', errorCallback);
1427
- const readyPromise = waitForWatcherToBeReady(watcher);
1490
+ watcher.on('all', callback);
1491
+ const readyPromise = waitForWatcherToBeReady(watcher, controller.signal);
1492
+ // Later roots can fail before Promise.all is reached.
1493
+ void readyPromise.catch(() => {});
1428
1494
  watcherEntries.push({
1429
1495
  readyPromise,
1430
1496
  watcher
@@ -1434,31 +1500,16 @@ const watchFolders = async ({
1434
1500
  await Promise.all(watcherEntries.map(({
1435
1501
  readyPromise
1436
1502
  }) => readyPromise));
1503
+ controller.signal.throwIfAborted();
1504
+ ready = true;
1505
+ const inotifyWatchCount = await getInotifyWatchCount();
1506
+ controller.signal.throwIfAborted();
1507
+ return inotifyWatchCount === undefined ? success : successWithInotifyWatchCount(inotifyWatchCount);
1437
1508
  } catch (error) {
1438
- await Promise.allSettled(watcherEntries.map(({
1439
- watcher
1440
- }) => watcher.close()));
1509
+ controller.abort(error);
1510
+ await closing;
1441
1511
  return fromError(error);
1442
1512
  }
1443
- for (const {
1444
- watcher
1445
- } of watcherEntries) {
1446
- watcher.on('all', callback);
1447
- }
1448
- const controller = new AbortController();
1449
- controller.signal.addEventListener('abort', () => {
1450
- void Promise.allSettled(watcherEntries.map(({
1451
- watcher
1452
- }) => watcher.close()));
1453
- }, {
1454
- once: true
1455
- });
1456
- add(id, controller);
1457
- const inotifyWatchCount = await getInotifyWatchCount();
1458
- if (inotifyWatchCount === undefined) {
1459
- return success;
1460
- }
1461
- return successWithInotifyWatchCount(inotifyWatchCount);
1462
1513
  };
1463
1514
 
1464
1515
  const commandMap = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/file-watcher-process",
3
- "version": "4.13.0",
3
+ "version": "4.14.1",
4
4
  "description": "File Watcher Process",
5
5
  "keywords": [
6
6
  "Lvce Editor",
@@ -19,7 +19,8 @@
19
19
  },
20
20
  "dependencies": {
21
21
  "@lvce-editor/assert": "^1.5.1",
22
- "chokidar": "^5.0.0"
22
+ "chokidar": "^5.0.0",
23
+ "ignore": "^5.3.2"
23
24
  },
24
25
  "engines": {
25
26
  "node": ">=22"