UNPKG

@intlayer/chokidar

Version:

Scans and builds Intlayer declaration files into dictionaries based on Intlayer configuration.

226 lines (224 loc) 8.99 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, resolve } from "node:path"; import { colorize, getAppLogger } from "@intlayer/config/logger"; import { getConfiguration, getConfigurationAndFilePath } from "@intlayer/config/node"; import { existsSync, watch as watch$1 } 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 STABILITY_THRESHOLD = 1e3; const createStabilityDebounce = () => { const pending = /* @__PURE__ */ new Map(); return (path, handler) => { const existing = pending.get(path); if (existing) clearTimeout(existing); pending.set(path, setTimeout(() => { pending.delete(path); handler(); }, STABILITY_THRESHOLD)); }; }; const watch = async (options) => { const { subscribe } = await import("@parcel/watcher"); 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; 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 normalizedMainDir = normalizePath(mainDir); const normalizedIntlayerDir = normalizePath(dirname(mainDir)); const subscriptions = []; const scheduleStable = createStabilityDebounce(); if (existsSync(mainDir)) { const mainDirSub = await subscribe(normalizedMainDir, (err, events) => { if (err || isProcessing) return; for (const event of events) { const rel = normalizePath(event.path).slice(normalizedMainDir.length + 1); if (!rel || rel.includes("/")) continue; if (rel.endsWith(".tmp")) continue; if (event.type === "update") processEvent(async () => { clearModuleCache(event.path); try { await import(`${pathToFileURL(event.path).href}?update=${Date.now()}`); } catch { appLogger(`Entry point ${basename(event.path)} failed to load, running clean rebuild...`, { level: "warn" }); await prepareIntlayer(configuration, { clean: true, forceRun: true }); } }); else if (event.type === "delete") processEvent(async () => { appLogger([ "Entry point", formatPath(basename(event.path)), "was removed, running clean rebuild..." ], { level: "warn" }); await prepareIntlayer(configuration, { clean: true, forceRun: true }); }); } }); subscriptions.push(mainDirSub); } const intlayerDirName = basename(normalizedIntlayerDir); const fsDirWatcher = watch$1(baseDir, { persistent: isWatchMode }, (eventType, filename) => { if (isProcessing || !filename) return; if (filename !== intlayerDirName) return; if (normalizePath(resolve(baseDir, filename)) !== normalizedIntlayerDir) return; if (eventType === "rename" && !existsSync(normalizedIntlayerDir)) { appLogger([formatPath(".intlayer"), "directory removed, running clean rebuild..."]); processEvent(() => prepareIntlayer(configuration, { clean: true, forceRun: true })); } }); subscriptions.push({ unsubscribe: async () => { fsDirWatcher.close(); } }); const ignorePatterns = excludedSegments.map((s) => `**/${s}/**`); const contentDirs = contentDir.map((dir) => normalizePath(dir)).filter(existsSync); const dirsToWatch = new Set(contentDirs); if (normalizedConfigPath) dirsToWatch.add(normalizePath(dirname(normalizedConfigPath))); const contentHandler = (err, events) => { if (err) { appLogger(`Watcher error: ${err}`, { level: "error" }); appLogger("Restarting watcher"); prepareIntlayer(configuration); return; } for (const event of events) { const filePath = event.path; const path = normalizePath(filePath); const isConfigFile = normalizedConfigPath && path === normalizedConfigPath; if (!isConfigFile) { if (!contentDirs.some((d) => path.startsWith(`${d}/`) || path === d)) continue; if (excludedSegments.some((segment) => path.includes(`/${segment}`))) continue; if (!fileExtensions.some((extension) => path.endsWith(extension))) continue; } if (event.type === "create") { 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}`); } if (isMove && matchedOldPath) processEvent(async () => { await handleContentDeclarationFileMoved(matchedOldPath, filePath, configuration); }); else scheduleStable(path, () => { processEvent(async () => { 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); }); }); } else if (event.type === "update") scheduleStable(path, () => { processEvent(async () => { if (isConfigFile) { appLogger("Configuration file changed, repreparing Intlayer"); clearModuleCache(filePath); 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); } }); }); else if (event.type === "delete") { const timer = setTimeout(async () => { pendingUnlinks.delete(filePath); processEvent(async () => handleUnlinkedContentDeclarationFile(filePath, configuration)); }, 200); pendingUnlinks.set(filePath, { timer, oldPath: filePath }); } } }; for (const dir of dirsToWatch) { const sub = await subscribe(dir, contentHandler, { ignore: ignorePatterns }); subscriptions.push(sub); } return subscriptions; }; 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