{"version":3,"file":"watcher.cjs","names":["ANSIColor","prepareIntlayer","formatPath","existsSync","handleContentDeclarationFileMoved","writeContentDeclaration","handleAdditionalContentDeclarationFile","handleContentDeclarationFileChange","handleUnlinkedContentDeclarationFile"],"sources":["../../src/watcher.ts"],"sourcesContent":["import { existsSync, watch as fsWatch } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { basename, dirname, resolve } 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 { 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 = {\n  configuration?: IntlayerConfig;\n  configOptions?: GetConfigurationOptions;\n  skipPrepare?: boolean;\n  persistent?: boolean;\n};\n\n// awaitWriteFinish equivalent: debounce per path until the file is stable\nconst STABILITY_THRESHOLD = 1000;\n\nconst createStabilityDebounce = () => {\n  const pending = new Map<string, NodeJS.Timeout>();\n  return (path: string, handler: () => void) => {\n    const existing = pending.get(path);\n    if (existing) clearTimeout(existing);\n    pending.set(\n      path,\n      setTimeout(() => {\n        pending.delete(path);\n        handler();\n      }, STABILITY_THRESHOLD)\n    );\n  };\n};\n\n// Initialize @parcel/watcher (non-persistent until subscribed)\nexport const watch = async (options?: WatchOptions) => {\n  const { subscribe } = await import('@parcel/watcher');\n\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  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 normalizedMainDir = normalizePath(mainDir);\n  const normalizedIntlayerDir = normalizePath(dirname(mainDir));\n\n  const subscriptions: { unsubscribe: () => Promise<void> }[] = [];\n\n  const scheduleStable = createStabilityDebounce();\n\n  // ── mainDir watcher (depth 0) ──────────────────────────────────────────────\n  // Detects broken or missing entry-point files inside .intlayer/main\n  if (existsSync(mainDir)) {\n    const mainDirSub = await subscribe(normalizedMainDir, (err, events) => {\n      if (err || isProcessing) return;\n\n      for (const event of events) {\n        const eventPath = normalizePath(event.path);\n        // depth-0 filter: only files directly inside mainDir\n        const rel = eventPath.slice(normalizedMainDir.length + 1);\n        if (!rel || rel.includes('/')) continue;\n        // Temp files written by the bundler (write-then-rename) are build-internal;\n        // their deletion must not trigger a clean rebuild.\n        if (rel.endsWith('.tmp')) continue;\n\n        if (event.type === 'update') {\n          processEvent(async () => {\n            clearModuleCache(event.path);\n            try {\n              const fileUrl = pathToFileURL(event.path).href;\n              await import(`${fileUrl}?update=${Date.now()}`);\n            } catch {\n              appLogger(\n                `Entry point ${basename(event.path)} failed to load, running clean rebuild...`,\n                { level: 'warn' }\n              );\n              await prepareIntlayer(configuration, {\n                clean: true,\n                forceRun: true,\n              });\n            }\n          });\n        } else if (event.type === 'delete') {\n          processEvent(async () => {\n            appLogger(\n              [\n                'Entry point',\n                formatPath(basename(event.path)),\n                'was removed, running clean rebuild...',\n              ],\n              { level: 'warn' }\n            );\n            await prepareIntlayer(configuration, {\n              clean: true,\n              forceRun: true,\n            });\n          });\n        }\n      }\n    });\n    subscriptions.push(mainDirSub);\n  }\n\n  // ── baseDir watcher (depth 0) — detect .intlayer directory removal ─────────\n  // Native fs.watch is non-recursive and ideal for single-directory depth-0 detection.\n  const intlayerDirName = basename(normalizedIntlayerDir);\n  const fsDirWatcher = fsWatch(\n    baseDir,\n    { persistent: isWatchMode },\n    (eventType, filename) => {\n      if (isProcessing || !filename) return;\n      if (filename !== intlayerDirName) return;\n\n      const fullPath = normalizePath(resolve(baseDir, filename));\n      if (fullPath !== normalizedIntlayerDir) return;\n\n      if (eventType === 'rename' && !existsSync(normalizedIntlayerDir)) {\n        appLogger([\n          formatPath('.intlayer'),\n          'directory removed, running clean rebuild...',\n        ]);\n        processEvent(() =>\n          prepareIntlayer(configuration, { clean: true, forceRun: true })\n        );\n      }\n    }\n  );\n  subscriptions.push({\n    unsubscribe: async () => {\n      fsDirWatcher.close();\n    },\n  });\n\n  // ── main content watcher ───────────────────────────────────────────────────\n  // Ignore patterns for @parcel/watcher (micromatch globs)\n  const ignorePatterns = excludedSegments.map((s) => `**/${s}/**`);\n\n  const contentDirs = contentDir\n    .map((dir) => normalizePath(dir))\n    .filter(existsSync);\n\n  // Collect unique directories to subscribe to (dirs only, not file paths)\n  const dirsToWatch = new Set<string>(contentDirs);\n  if (normalizedConfigPath) {\n    dirsToWatch.add(normalizePath(dirname(normalizedConfigPath)));\n  }\n\n  const contentHandler = (\n    err: Error | null,\n    events: Array<{ type: string; path: string }>\n  ) => {\n    if (err) {\n      appLogger(`Watcher error: ${err}`, { level: 'error' });\n      appLogger('Restarting watcher');\n      prepareIntlayer(configuration);\n      return;\n    }\n\n    for (const event of events) {\n      const filePath = event.path;\n      const path = normalizePath(filePath);\n\n      const isConfigFile =\n        normalizedConfigPath && path === normalizedConfigPath;\n\n      if (!isConfigFile) {\n        // Must originate from a watched content directory\n        const isInContentDir = contentDirs.some(\n          (d) => path.startsWith(`${d}/`) || path === d\n        );\n        if (!isInContentDir) continue;\n\n        if (excludedSegments.some((segment) => path.includes(`/${segment}`)))\n          continue;\n\n        if (!fileExtensions.some((extension) => path.endsWith(extension)))\n          continue;\n      }\n\n      if (event.type === 'create') {\n        const fileName = basename(filePath);\n\n        // Move detection must happen synchronously before any debounce\n        let isMove = false;\n        let matchedOldPath: string | undefined;\n\n        for (const [oldPath] of pendingUnlinks) {\n          if (basename(oldPath) === fileName) {\n            matchedOldPath = oldPath;\n            break;\n          }\n        }\n\n        if (!matchedOldPath && pendingUnlinks.size === 1) {\n          matchedOldPath = pendingUnlinks.keys().next().value;\n        }\n\n        if (matchedOldPath) {\n          const pending = pendingUnlinks.get(matchedOldPath);\n          if (pending) {\n            clearTimeout(pending.timer);\n            pendingUnlinks.delete(matchedOldPath);\n          }\n          isMove = true;\n          appLogger(`File moved from ${matchedOldPath} to ${filePath}`);\n        }\n\n        if (isMove && matchedOldPath) {\n          processEvent(async () => {\n            await handleContentDeclarationFileMoved(\n              matchedOldPath!,\n              filePath,\n              configuration\n            );\n          });\n        } else {\n          // Debounce: wait for write to finish before reading the file\n          scheduleStable(path, () => {\n            processEvent(async () => {\n              const fileContent = await readFile(filePath, 'utf-8');\n              const isEmpty = fileContent === '';\n\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                  { key: name, content: {}, filePath },\n                  configuration\n                );\n              }\n\n              await handleAdditionalContentDeclarationFile(\n                filePath,\n                configuration\n              );\n            });\n          });\n        }\n      } else if (event.type === 'update') {\n        scheduleStable(path, () => {\n          processEvent(async () => {\n            if (isConfigFile) {\n              appLogger('Configuration file changed, repreparing Intlayer');\n\n              clearModuleCache(filePath);\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      } else if (event.type === 'delete') {\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 'create' event\n\n        pendingUnlinks.set(filePath, { timer, oldPath: filePath });\n      }\n    }\n  };\n\n  for (const dir of dirsToWatch) {\n    const sub = await subscribe(dir, contentHandler, {\n      ignore: ignorePatterns,\n    });\n\n    subscriptions.push(sub);\n  }\n\n  return subscriptions;\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":";;;;;;;;;;;;;;;;;;;;AA2BA,MAAM,iCAAiB,IAAI,KAGxB;AAGH,MAAM,YAAqC,EAAE;AAC7C,IAAI,eAAe;AAEnB,MAAM,eAAe,YAAY;AAC/B,KAAI,aAAc;AAClB,gBAAe;AACf,QAAO,UAAU,SAAS,GAAG;EAC3B,MAAM,OAAO,UAAU,OAAO;AAC9B,MAAI;AACF,SAAM,MAAM;WACL,OAAO;AACd,WAAQ,MAAM,MAAM;;;AAGxB,gBAAe;;AAGjB,MAAM,gBAAgB,SAA8B;AAClD,WAAU,KAAK,KAAK;AACpB,eAAc;;AAWhB,MAAM,sBAAsB;AAE5B,MAAM,gCAAgC;CACpC,MAAM,0BAAU,IAAI,KAA6B;AACjD,SAAQ,MAAc,YAAwB;EAC5C,MAAM,WAAW,QAAQ,IAAI,KAAK;AAClC,MAAI,SAAU,cAAa,SAAS;AACpC,UAAQ,IACN,MACA,iBAAiB;AACf,WAAQ,OAAO,KAAK;AACpB,YAAS;KACR,oBAAoB,CACxB;;;AAKL,MAAa,QAAQ,OAAO,YAA2B;CACrD,MAAM,EAAE,cAAc,MAAM,OAAO;CAEnC,MAAM,sEAA2C,SAAS,cAAc;CACxE,MAAM,wBAAwB,aAAa;CAC3C,IAAI,gBACF,SAAS,iBAAiB,aAAa;CACzC,MAAM,sDAAyB,cAAc;CAE7C,MAAM,EACJ,OAAO,aACP,gBACA,YACA,iBACE,cAAc;AAElB,KAAI,CAAC,cAAc,QAAQ,MAAO;AAElC,WAAU,yCAAyC;AAEnD,KAAI,cAAc,MAAM,aAAa,KACnC,WACE;EACE,yEAA4C,QAAQA,wBAAU,KAAK,CAAC;EACpE;EACA;wCACS,qBAAqBA,wBAAU,KAAK;EAC7C;wCACS,aAAaA,wBAAU,KAAK;EACrC;EACD,EACD,EACE,OAAO,QACR,CACF;CAIH,MAAM,mBAAmB,aAAa,KAAK,YACzC,QAAQ,QAAQ,WAAW,GAAG,CAAC,QAAQ,WAAW,GAAG,CACtD;CAED,MAAM,uBAAuB,kEACX,sBAAsB,GACpC;CAEJ,MAAM,EAAE,SAAS,YAAY,cAAc;CAC3C,MAAM,8DAAkC,QAAQ;CAChD,MAAM,yFAA8C,QAAQ,CAAC;CAE7D,MAAM,gBAAwD,EAAE;CAEhE,MAAM,iBAAiB,yBAAyB;AAIhD,6BAAe,QAAQ,EAAE;EACvB,MAAM,aAAa,MAAM,UAAU,oBAAoB,KAAK,WAAW;AACrE,OAAI,OAAO,aAAc;AAEzB,QAAK,MAAM,SAAS,QAAQ;IAG1B,MAAM,gDAF0B,MAAM,KAEjB,CAAC,MAAM,kBAAkB,SAAS,EAAE;AACzD,QAAI,CAAC,OAAO,IAAI,SAAS,IAAI,CAAE;AAG/B,QAAI,IAAI,SAAS,OAAO,CAAE;AAE1B,QAAI,MAAM,SAAS,SACjB,cAAa,YAAY;AACvB,kDAAiB,MAAM,KAAK;AAC5B,SAAI;AAEF,YAAM,OAAO,+BADiB,MAAM,KAAK,CAAC,KAClB,UAAU,KAAK,KAAK;aACtC;AACN,gBACE,uCAAwB,MAAM,KAAK,CAAC,4CACpC,EAAE,OAAO,QAAQ,CAClB;AACD,YAAMC,wCAAgB,eAAe;OACnC,OAAO;OACP,UAAU;OACX,CAAC;;MAEJ;aACO,MAAM,SAAS,SACxB,cAAa,YAAY;AACvB,eACE;MACE;MACAC,2DAAoB,MAAM,KAAK,CAAC;MAChC;MACD,EACD,EAAE,OAAO,QAAQ,CAClB;AACD,WAAMD,wCAAgB,eAAe;MACnC,OAAO;MACP,UAAU;MACX,CAAC;MACF;;IAGN;AACF,gBAAc,KAAK,WAAW;;CAKhC,MAAM,0CAA2B,sBAAsB;CACvD,MAAM,kCACJ,SACA,EAAE,YAAY,aAAa,GAC1B,WAAW,aAAa;AACvB,MAAI,gBAAgB,CAAC,SAAU;AAC/B,MAAI,aAAa,gBAAiB;AAGlC,uEADuC,SAAS,SAAS,CAC7C,KAAK,sBAAuB;AAExC,MAAI,cAAc,YAAY,yBAAY,sBAAsB,EAAE;AAChE,aAAU,CACRC,mCAAW,YAAY,EACvB,8CACD,CAAC;AACF,sBACED,wCAAgB,eAAe;IAAE,OAAO;IAAM,UAAU;IAAM,CAAC,CAChE;;GAGN;AACD,eAAc,KAAK,EACjB,aAAa,YAAY;AACvB,eAAa,OAAO;IAEvB,CAAC;CAIF,MAAM,iBAAiB,iBAAiB,KAAK,MAAM,MAAM,EAAE,KAAK;CAEhE,MAAM,cAAc,WACjB,KAAK,kDAAsB,IAAI,CAAC,CAChC,OAAOE,mBAAW;CAGrB,MAAM,cAAc,IAAI,IAAY,YAAY;AAChD,KAAI,qBACF,aAAY,qEAA0B,qBAAqB,CAAC,CAAC;CAG/D,MAAM,kBACJ,KACA,WACG;AACH,MAAI,KAAK;AACP,aAAU,kBAAkB,OAAO,EAAE,OAAO,SAAS,CAAC;AACtD,aAAU,qBAAqB;AAC/B,2CAAgB,cAAc;AAC9B;;AAGF,OAAK,MAAM,SAAS,QAAQ;GAC1B,MAAM,WAAW,MAAM;GACvB,MAAM,iDAAqB,SAAS;GAEpC,MAAM,eACJ,wBAAwB,SAAS;AAEnC,OAAI,CAAC,cAAc;AAKjB,QAAI,CAHmB,YAAY,MAChC,MAAM,KAAK,WAAW,GAAG,EAAE,GAAG,IAAI,SAAS,EAE3B,CAAE;AAErB,QAAI,iBAAiB,MAAM,YAAY,KAAK,SAAS,IAAI,UAAU,CAAC,CAClE;AAEF,QAAI,CAAC,eAAe,MAAM,cAAc,KAAK,SAAS,UAAU,CAAC,CAC/D;;AAGJ,OAAI,MAAM,SAAS,UAAU;IAC3B,MAAM,mCAAoB,SAAS;IAGnC,IAAI,SAAS;IACb,IAAI;AAEJ,SAAK,MAAM,CAAC,YAAY,eACtB,6BAAa,QAAQ,KAAK,UAAU;AAClC,sBAAiB;AACjB;;AAIJ,QAAI,CAAC,kBAAkB,eAAe,SAAS,EAC7C,kBAAiB,eAAe,MAAM,CAAC,MAAM,CAAC;AAGhD,QAAI,gBAAgB;KAClB,MAAM,UAAU,eAAe,IAAI,eAAe;AAClD,SAAI,SAAS;AACX,mBAAa,QAAQ,MAAM;AAC3B,qBAAe,OAAO,eAAe;;AAEvC,cAAS;AACT,eAAU,mBAAmB,eAAe,MAAM,WAAW;;AAG/D,QAAI,UAAU,eACZ,cAAa,YAAY;AACvB,WAAMC,4EACJ,gBACA,UACA,cACD;MACD;QAGF,gBAAe,YAAY;AACzB,kBAAa,YAAY;AAIvB,UAFgB,qCADmB,UAAU,QAAQ,KACrB,IAEnB;OACX,MAAM,mBAAmB,eACtB,KAAK,QAAQ,IAAI,QAAQ,OAAO,MAAM,CAAC,CACvC,KAAK,IAAI;AAMZ,aAAMC,gFACJ;QAAE,KANS,SAAS,QACpB,IAAI,OAAO,IAAI,iBAAiB,IAAI,EACpC,GAIW;QAAE,SAAS,EAAE;QAAE;QAAU,EACpC,cACD;;AAGH,YAAMC,sFACJ,UACA,cACD;OACD;MACF;cAEK,MAAM,SAAS,SACxB,gBAAe,YAAY;AACzB,iBAAa,YAAY;AACvB,SAAI,cAAc;AAChB,gBAAU,mDAAmD;AAE7D,mDAAiB,SAAS;AAC1B,iDAAe;MAEf,MAAM,EAAE,eAAe,4EACO,SAAS,cAAc;AAErD,sBAAgB,SAAS,iBAAiB;AAE1C,YAAML,wCAAgB,eAAe,EAAE,OAAO,OAAO,CAAC;YACjD;AAEL,mDAAiB,SAAS;AAE1B,iDAAe;AACf,wDAAsB;AACtB,YAAMM,8EAAmC,UAAU,cAAc;;MAEnE;KACF;YACO,MAAM,SAAS,UAAU;IAElC,MAAM,QAAQ,WAAW,YAAY;AAEnC,oBAAe,OAAO,SAAS;AAC/B,kBAAa,YACXC,kFAAqC,UAAU,cAAc,CAC9D;OACA,IAAI;AAEP,mBAAe,IAAI,UAAU;KAAE;KAAO,SAAS;KAAU,CAAC;;;;AAKhE,MAAK,MAAM,OAAO,aAAa;EAC7B,MAAM,MAAM,MAAM,UAAU,KAAK,gBAAgB,EAC/C,QAAQ,gBACT,CAAC;AAEF,gBAAc,KAAK,IAAI;;AAGzB,QAAO;;AAGT,MAAa,wBAAwB,OAAO,YAA2B;CACrE,MAAM,EAAE,aAAa,GAAG,SAAS,WAAW,EAAE;CAC9C,MAAM,gBACJ,SAAS,6DAAkC,SAAS,cAAc;AAEpE,KAAI,CAAC,YACH,OAAMP,wCAAgB,eAAe,EAAE,UAAU,MAAM,CAAC;AAQ1D,KAAI,SAAS,WACX,OAAM,MAAM;EAAE,GAAG;EAAM;EAAe,CAAC"}