@intlayer/chokidar
Version:
Uses chokidar to scan and build Intlayer declaration files into dictionaries based on Intlayer configuration.
199 lines (197 loc) • 7.7 kB
JavaScript
import { formatPath } from "./utils/formatter.mjs";
import { handleContentDeclarationFileChange } from "./handleContentDeclarationFileChange.mjs";
import { handleContentDeclarationFileMoved } from "./handleContentDeclarationFileMoved.mjs";
import { handleAdditionalContentDeclarationFile } from "./handleAdditionalContentDeclarationFile.mjs";
import { handleUnlinkedContentDeclarationFile } from "./handleUnlinkedContentDeclarationFile.mjs";
import { prepareIntlayer } from "./prepareIntlayer.mjs";
import { writeContentDeclaration } from "./writeContentDeclaration/writeContentDeclaration.mjs";
import { readFile } from "node:fs/promises";
import { basename, dirname } from "node:path";
import { colorize, getAppLogger } from "@intlayer/config/logger";
import { getConfiguration, getConfigurationAndFilePath } from "@intlayer/config/node";
import { existsSync } from "node:fs";
import { clearAllCache, clearDiskCacheMemory, clearModuleCache, normalizePath } from "@intlayer/config/utils";
import * as ANSIColor from "@intlayer/config/colors";
import { pathToFileURL } from "node:url";
//#region src/watcher.ts
const pendingUnlinks = /* @__PURE__ */ new Map();
const taskQueue = [];
let isProcessing = false;
const processQueue = async () => {
if (isProcessing) return;
isProcessing = true;
while (taskQueue.length > 0) {
const task = taskQueue.shift();
try {
await task();
} catch (error) {
console.error(error);
}
}
isProcessing = false;
};
const processEvent = (task) => {
taskQueue.push(task);
processQueue();
};
const watch = async (options) => {
const { watch: chokidarWatch } = await import("chokidar");
const configResult = getConfigurationAndFilePath(options?.configOptions);
const configurationFilePath = configResult.configurationFilePath;
let configuration = options?.configuration ?? configResult.configuration;
const appLogger = getAppLogger(configuration);
const { watch: isWatchMode, fileExtensions, contentDir, excludedPath } = configuration.content;
const pathsToWatch = [...contentDir.map((dir) => normalizePath(dir)).filter(existsSync), ...configurationFilePath ? [configurationFilePath] : []];
if (!configuration.content.watch) return;
appLogger("Watching Intlayer content declarations");
if (configuration.build.optimize === true) appLogger([
`Build optimization is forced to ${colorize("true", ANSIColor.GREY)}, but watching is enabled too.`,
"It may lead to dev mode performance degradation as well as import errors.",
"Its recommended to keep the",
colorize("`build.optimized`", ANSIColor.BLUE),
"option",
colorize("undefined", ANSIColor.GREY),
"to get the best dev mode experience"
], { level: "warn" });
const excludedSegments = excludedPath.map((segment) => segment.replace(/^\*\*\//, "").replace(/\/\*\*$/, ""));
const normalizedConfigPath = configurationFilePath ? normalizePath(configurationFilePath) : null;
const { mainDir, baseDir } = configuration.system;
const normalizedIntlayerDir = normalizePath(dirname(mainDir));
if (existsSync(mainDir)) chokidarWatch(mainDir, {
persistent: isWatchMode,
ignoreInitial: true,
depth: 0
}).on("change", async (filePath) => {
if (isProcessing) return;
processEvent(async () => {
clearModuleCache(filePath);
try {
await import(`${pathToFileURL(filePath).href}?update=${Date.now()}`);
} catch {
appLogger(`Entry point ${basename(filePath)} failed to load, running clean rebuild...`, { level: "warn" });
await prepareIntlayer(configuration, {
clean: true,
forceRun: true
});
}
});
}).on("unlink", async (filePath) => {
if (isProcessing) return;
processEvent(async () => {
appLogger([
"Entry point",
formatPath(basename(filePath)),
"was removed, running clean rebuild..."
], { level: "warn" });
await prepareIntlayer(configuration, {
clean: true,
forceRun: true
});
});
});
chokidarWatch(baseDir, {
persistent: isWatchMode,
ignoreInitial: true,
depth: 0,
ignored: (filePath) => {
const path = normalizePath(filePath);
return path !== normalizePath(baseDir) && path !== normalizedIntlayerDir;
}
}).on("unlinkDir", async (dirPath) => {
if (isProcessing) return;
if (normalizePath(dirPath) === normalizedIntlayerDir) {
appLogger([formatPath(".intlayer"), "directory removed, running clean rebuild..."]);
processEvent(() => prepareIntlayer(configuration, {
clean: true,
forceRun: true
}));
}
});
return chokidarWatch(pathsToWatch, {
persistent: isWatchMode,
ignoreInitial: true,
awaitWriteFinish: {
stabilityThreshold: 1e3,
pollInterval: 100
},
ignored: (filePath, stats) => {
const path = normalizePath(filePath);
if (normalizedConfigPath && path === normalizedConfigPath) return false;
if (excludedSegments.some((segment) => path.includes(`/${segment}`))) return true;
if (stats?.isFile()) return !fileExtensions.some((extension) => path.endsWith(extension));
return false;
},
...options
}).on("add", async (filePath) => {
const fileName = basename(filePath);
let isMove = false;
let matchedOldPath;
for (const [oldPath] of pendingUnlinks) if (basename(oldPath) === fileName) {
matchedOldPath = oldPath;
break;
}
if (!matchedOldPath && pendingUnlinks.size === 1) matchedOldPath = pendingUnlinks.keys().next().value;
if (matchedOldPath) {
const pending = pendingUnlinks.get(matchedOldPath);
if (pending) {
clearTimeout(pending.timer);
pendingUnlinks.delete(matchedOldPath);
}
isMove = true;
appLogger(`File moved from ${matchedOldPath} to ${filePath}`);
}
processEvent(async () => {
if (isMove && matchedOldPath) await handleContentDeclarationFileMoved(matchedOldPath, filePath, configuration);
else {
if (await readFile(filePath, "utf-8") === "") {
const extensionPattern = fileExtensions.map((ext) => ext.replace(/\./g, "\\.")).join("|");
await writeContentDeclaration({
key: fileName.replace(new RegExp(`(${extensionPattern})$`), ""),
content: {},
filePath
}, configuration);
}
await handleAdditionalContentDeclarationFile(filePath, configuration);
}
});
}).on("change", async (filePath) => processEvent(async () => {
if (configurationFilePath && filePath === configurationFilePath) {
appLogger("Configuration file changed, repreparing Intlayer");
clearModuleCache(configurationFilePath);
clearAllCache();
const { configuration: newConfiguration } = getConfigurationAndFilePath(options?.configOptions);
configuration = options?.configuration ?? newConfiguration;
await prepareIntlayer(configuration, { clean: false });
} else {
clearModuleCache(filePath);
clearAllCache();
clearDiskCacheMemory();
await handleContentDeclarationFileChange(filePath, configuration);
}
})).on("unlink", async (filePath) => {
const timer = setTimeout(async () => {
pendingUnlinks.delete(filePath);
processEvent(async () => handleUnlinkedContentDeclarationFile(filePath, configuration));
}, 200);
pendingUnlinks.set(filePath, {
timer,
oldPath: filePath
});
}).on("error", async (error) => {
appLogger(`Watcher error: ${error}`, { level: "error" });
appLogger("Restarting watcher");
await prepareIntlayer(configuration);
});
};
const buildAndWatchIntlayer = async (options) => {
const { skipPrepare, ...rest } = options ?? {};
const configuration = options?.configuration ?? getConfiguration(options?.configOptions);
if (!skipPrepare) await prepareIntlayer(configuration, { forceRun: true });
if (options?.persistent) await watch({
...rest,
configuration
});
};
//#endregion
export { buildAndWatchIntlayer, watch };
//# sourceMappingURL=watcher.mjs.map