{"version":3,"file":"watcher.cjs","names":["existsSync","ANSIColor","prepareIntlayer","formatPath","handleContentDeclarationFileMoved","writeContentDeclaration","handleAdditionalContentDeclarationFile","handleContentDeclarationFileChange","handleUnlinkedContentDeclarationFile"],"sources":["../../src/watcher.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { basename, dirname } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport * as ANSIColor from '@intlayer/config/colors';\nimport { colorize, getAppLogger } from '@intlayer/config/logger';\nimport {\n  type GetConfigurationOptions,\n  getConfiguration,\n  getConfigurationAndFilePath,\n} from '@intlayer/config/node';\nimport {\n  clearAllCache,\n  clearDiskCacheMemory,\n  clearModuleCache,\n  normalizePath,\n} from '@intlayer/config/utils';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { ChokidarOptions } from 'chokidar';\nimport { handleAdditionalContentDeclarationFile } from './handleAdditionalContentDeclarationFile';\nimport { handleContentDeclarationFileChange } from './handleContentDeclarationFileChange';\nimport { handleContentDeclarationFileMoved } from './handleContentDeclarationFileMoved';\nimport { handleUnlinkedContentDeclarationFile } from './handleUnlinkedContentDeclarationFile';\nimport { prepareIntlayer } from './prepareIntlayer';\nimport { formatPath } from './utils';\nimport { writeContentDeclaration } from './writeContentDeclaration';\n\n// Map to track files that were recently unlinked: oldPath -> { timer, timestamp }\nconst pendingUnlinks = new Map<\n  string,\n  { timer: NodeJS.Timeout; oldPath: string }\n>();\n\n// Array-based sequential task queue — no Promise chain accumulation, no race conditions\nconst taskQueue: (() => Promise<void>)[] = [];\nlet isProcessing = false;\n\nconst processQueue = async () => {\n  if (isProcessing) return;\n  isProcessing = true;\n  while (taskQueue.length > 0) {\n    const task = taskQueue.shift()!;\n    try {\n      await task();\n    } catch (error) {\n      console.error(error);\n    }\n  }\n  isProcessing = false;\n};\n\nconst processEvent = (task: () => Promise<void>) => {\n  taskQueue.push(task);\n  processQueue();\n};\n\ntype WatchOptions = ChokidarOptions & {\n  configuration?: IntlayerConfig;\n  configOptions?: GetConfigurationOptions;\n  skipPrepare?: boolean;\n};\n\n// Initialize chokidar watcher (non-persistent)\nexport const watch = async (options?: WatchOptions) => {\n  const { watch: chokidarWatch } = await import('chokidar');\n  const configResult = getConfigurationAndFilePath(options?.configOptions);\n  const configurationFilePath = configResult.configurationFilePath;\n  let configuration: IntlayerConfig =\n    options?.configuration ?? configResult.configuration;\n  const appLogger = getAppLogger(configuration);\n\n  const {\n    watch: isWatchMode,\n    fileExtensions,\n    contentDir,\n    excludedPath,\n  } = configuration.content;\n\n  // chokidar v5 dropped glob support — use fs to resolve dirs, filter extensions via ignored\n  const pathsToWatch = [\n    ...contentDir.map((dir) => normalizePath(dir)).filter(existsSync),\n    ...(configurationFilePath ? [configurationFilePath] : []),\n  ];\n\n  if (!configuration.content.watch) return;\n\n  appLogger('Watching Intlayer content declarations');\n\n  if (configuration.build.optimize === true) {\n    appLogger(\n      [\n        `Build optimization is forced to ${colorize('true', ANSIColor.GREY)}, but watching is enabled too.`,\n        'It may lead to dev mode performance degradation as well as import errors.',\n        'Its recommended to keep the',\n        colorize('`build.optimized`', ANSIColor.BLUE),\n        'option',\n        colorize('undefined', ANSIColor.GREY),\n        'to get the best dev mode experience',\n      ],\n      {\n        level: 'warn',\n      }\n    );\n  }\n\n  // Strip glob markers from excludedPath entries to get plain segments (e.g. 'node_modules')\n  const excludedSegments = excludedPath.map((segment) =>\n    segment.replace(/^\\*\\*\\//, '').replace(/\\/\\*\\*$/, '')\n  );\n\n  const normalizedConfigPath = configurationFilePath\n    ? normalizePath(configurationFilePath)\n    : null;\n\n  const { mainDir, baseDir } = configuration.system;\n  const normalizedIntlayerDir = normalizePath(dirname(mainDir));\n\n  // Watch mainDir to detect broken or missing entry point files\n  if (existsSync(mainDir)) {\n    chokidarWatch(mainDir, {\n      persistent: isWatchMode,\n      ignoreInitial: true,\n      depth: 0,\n    })\n      .on('change', async (filePath) => {\n        if (isProcessing) return;\n\n        processEvent(async () => {\n          clearModuleCache(filePath);\n          try {\n            // Convert absolute path to a valid file:// URL\n            const fileUrl = pathToFileURL(filePath).href;\n\n            // Append a timestamp to bypass the ESM cache\n            await import(`${fileUrl}?update=${Date.now()}`);\n          } catch {\n            appLogger(\n              `Entry point ${basename(filePath)} failed to load, running clean rebuild...`,\n              { level: 'warn' }\n            );\n            await prepareIntlayer(configuration, {\n              clean: true,\n              forceRun: true,\n            });\n          }\n        });\n      })\n      .on('unlink', async (filePath) => {\n        if (isProcessing) return;\n\n        processEvent(async () => {\n          appLogger(\n            [\n              'Entry point',\n              formatPath(basename(filePath)),\n              'was removed, running clean rebuild...',\n            ],\n            { level: 'warn' }\n          );\n          await prepareIntlayer(configuration, { clean: true, forceRun: true });\n        });\n      });\n  }\n\n  // Watch baseDir at depth 0 to detect the entire .intlayer folder being removed\n  chokidarWatch(baseDir, {\n    persistent: isWatchMode,\n    ignoreInitial: true,\n    depth: 0,\n    ignored: (filePath: string) => {\n      const path = normalizePath(filePath);\n      return path !== normalizePath(baseDir) && path !== normalizedIntlayerDir;\n    },\n  }).on('unlinkDir', async (dirPath) => {\n    if (isProcessing) return;\n\n    if (normalizePath(dirPath) === normalizedIntlayerDir) {\n      appLogger([\n        formatPath('.intlayer'),\n        'directory removed, running clean rebuild...',\n      ]);\n\n      processEvent(() =>\n        prepareIntlayer(configuration, { clean: true, forceRun: true })\n      );\n    }\n  });\n\n  return chokidarWatch(pathsToWatch, {\n    persistent: isWatchMode, // Make the watcher persistent\n    ignoreInitial: true, // Process existing files\n    awaitWriteFinish: {\n      stabilityThreshold: 1000,\n      pollInterval: 100,\n    },\n    ignored: (filePath: string, stats?: import('node:fs').Stats) => {\n      const path = normalizePath(filePath);\n\n      if (normalizedConfigPath && path === normalizedConfigPath) return false;\n\n      if (excludedSegments.some((segment) => path.includes(`/${segment}`)))\n        return true;\n\n      if (stats?.isFile()) {\n        return !fileExtensions.some((extension) => path.endsWith(extension));\n      }\n\n      return false;\n    },\n    ...options,\n  })\n    .on('add', async (filePath) => {\n      const fileName = basename(filePath);\n      let isMove = false;\n\n      // Check if this Add corresponds to a pending Unlink (Move/Rename detection)\n      // Heuristic:\n      // - Priority A: Exact basename match (Moved to different folder)\n      // - Priority B: Single entry in pendingUnlinks (Renamed file)\n      let matchedOldPath: string | undefined;\n\n      // Search for basename match\n      for (const [oldPath] of pendingUnlinks) {\n        if (basename(oldPath) === fileName) {\n          matchedOldPath = oldPath;\n          break;\n        }\n      }\n\n      // If no basename match, but exactly one file was recently unlinked, assume it's a rename\n      if (!matchedOldPath && pendingUnlinks.size === 1) {\n        matchedOldPath = pendingUnlinks.keys().next().value;\n      }\n\n      if (matchedOldPath) {\n        // It is a move! Cancel the unlink handler\n        const pending = pendingUnlinks.get(matchedOldPath);\n        if (pending) {\n          clearTimeout(pending.timer);\n          pendingUnlinks.delete(matchedOldPath);\n        }\n\n        isMove = true;\n        appLogger(`File moved from ${matchedOldPath} to ${filePath}`);\n      }\n\n      processEvent(async () => {\n        if (isMove && matchedOldPath) {\n          await handleContentDeclarationFileMoved(\n            matchedOldPath,\n            filePath,\n            configuration\n          );\n        } else {\n          const fileContent = await readFile(filePath, 'utf-8');\n          const isEmpty = fileContent === '';\n\n          // Fill template content declaration file if it is empty\n          if (isEmpty) {\n            const extensionPattern = fileExtensions\n              .map((ext) => ext.replace(/\\./g, '\\\\.'))\n              .join('|');\n            const name = fileName.replace(\n              new RegExp(`(${extensionPattern})$`),\n              ''\n            );\n\n            await writeContentDeclaration(\n              {\n                key: name,\n                content: {},\n                filePath,\n              },\n              configuration\n            );\n          }\n\n          await handleAdditionalContentDeclarationFile(filePath, configuration);\n        }\n      });\n    })\n    .on('change', async (filePath) =>\n      processEvent(async () => {\n        if (configurationFilePath && filePath === configurationFilePath) {\n          appLogger('Configuration file changed, repreparing Intlayer');\n\n          clearModuleCache(configurationFilePath);\n          clearAllCache();\n\n          const { configuration: newConfiguration } =\n            getConfigurationAndFilePath(options?.configOptions);\n\n          configuration = options?.configuration ?? newConfiguration;\n\n          await prepareIntlayer(configuration, { clean: false });\n        } else {\n          // Clear module cache for the changed file to avoid stale require() results\n          clearModuleCache(filePath);\n          // Evict in-memory caches so loadContentDeclaration picks up fresh content\n          clearAllCache();\n          clearDiskCacheMemory();\n          await handleContentDeclarationFileChange(filePath, configuration);\n        }\n      })\n    )\n    .on('unlink', async (filePath) => {\n      // Delay unlink processing to see if an 'add' event occurs (indicating a move)\n      const timer = setTimeout(async () => {\n        // If timer fires, the file was genuinely removed\n        pendingUnlinks.delete(filePath);\n        processEvent(async () =>\n          handleUnlinkedContentDeclarationFile(filePath, configuration)\n        );\n      }, 200); // 200ms window to catch the 'add' event\n\n      pendingUnlinks.set(filePath, { timer, oldPath: filePath });\n    })\n    .on('error', async (error) => {\n      appLogger(`Watcher error: ${error}`, {\n        level: 'error',\n      });\n\n      appLogger('Restarting watcher');\n\n      await prepareIntlayer(configuration);\n    });\n};\n\nexport const buildAndWatchIntlayer = async (options?: WatchOptions) => {\n  const { skipPrepare, ...rest } = options ?? {};\n  const configuration =\n    options?.configuration ?? getConfiguration(options?.configOptions);\n\n  if (!skipPrepare) {\n    await prepareIntlayer(configuration, { forceRun: true });\n  }\n\n  // Only enter watch mode when the caller explicitly opts in via `persistent`.\n  // `configuration.content.watch` is the dev-mode signal consumed by bundler\n  // plugins (e.g. vite-intlayer's `configureServer`); it must not coerce\n  // `intlayer build` (which passes `persistent: false`) into a persistent\n  // watcher, since that prevents the build command from ever exiting.\n  if (options?.persistent) {\n    await watch({ ...rest, configuration });\n  }\n};\n"],"mappings":"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"}