UNPKG

@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
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