@intlayer/chokidar 8.11.3 → 8.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,6 +3,7 @@ const require_runtime = require('../_virtual/_rolldown/runtime.cjs');
3
3
  let node_path = require("node:path");
4
4
  let fast_glob = require("fast-glob");
5
5
  fast_glob = require_runtime.__toESM(fast_glob);
6
+ let _intlayer_config_defaultValues = require("@intlayer/config/defaultValues");
6
7
 
7
8
  //#region src/utils/buildComponentFilesList.ts
8
9
  /**
@@ -26,41 +27,65 @@ const getDistinctRootDirs = (dirs) => {
26
27
  }, []);
27
28
  };
28
29
  /**
30
+ * Returns true when the resolved path passes through a `node_modules` segment.
31
+ * Works on both POSIX and Windows paths.
32
+ */
33
+ const isInsideNodeModules = (dir) => (0, node_path.resolve)(dir).split(node_path.sep).includes("node_modules");
34
+ /**
35
+ * Default exclude patterns derived from TRAVERSE_PATTERN.
36
+ * Extracted once so the function body can reference them without re-computing.
37
+ */
38
+ const DEFAULT_EXCLUDE_PATTERNS = _intlayer_config_defaultValues.TRAVERSE_PATTERN.filter((p) => typeof p === "string" && p.startsWith("!")).map((p) => p.slice(1));
39
+ /**
29
40
  * Builds a deduplicated list of absolute file paths matching the given patterns.
30
41
  *
31
42
  * Handles multiple root directories (deduplicates overlapping roots), exclude
32
- * patterns, negation patterns embedded in `transformPattern`, and optional
43
+ * patterns, negation patterns embedded in `traversePattern`, and optional
33
44
  * dot-file inclusion.
34
45
  *
46
+ * Special case: `codeDir` entries that live inside `node_modules` (e.g. a
47
+ * design-system package installed as a workspace dependency) are scanned as
48
+ * their own explicit roots. They are NOT collapsed into the project root so
49
+ * the `*\/node_modules\/**` exclusion does not silently drop them.
50
+ *
35
51
  * @example
36
52
  * // Single root with excludes
37
- * const files = buildComponentFilesList({
38
- * transformPattern: 'src/**\/*.{ts,tsx}',
39
- * excludePattern: ['**\/node_modules\/**'],
40
- * baseDir: '/path/to/project',
41
- * });
53
+ * const files = buildComponentFilesList(config);
42
54
  *
43
55
  * @example
44
- * // Multiple roots (e.g. baseDir + codeDir), dot files included
45
- * const files = buildComponentFilesList(config, ['**\/node_modules\/**']);
56
+ * // Design-system package inside node_modules is still scanned
57
+ * // intlayer.config.ts: { content: { codeDir: ['node_modules/my-ds/src'] } }
58
+ * const files = buildComponentFilesList(config);
46
59
  */
47
60
  const buildComponentFilesList = (config, excludePattern) => {
48
- const transformPattern = config.build.traversePattern;
61
+ const traversePattern = config.build.traversePattern;
49
62
  const compilerTransformPattern = config.compiler.transformPattern;
50
63
  const contentDeclarationPattern = config.content.fileExtensions.map((ext) => `/**/*${ext}`);
51
- const patterns = [...transformPattern, ...normalizeToArray(compilerTransformPattern)].filter((pattern) => typeof pattern === "string").filter((pattern) => !pattern.startsWith("!")).map((pattern) => (0, node_path.normalize)(pattern));
52
- const excludePatterns = [
64
+ const patterns = [...traversePattern, ...normalizeToArray(compilerTransformPattern)].filter((pattern) => typeof pattern === "string").filter((pattern) => !pattern.startsWith("!")).map((pattern) => (0, node_path.normalize)(pattern));
65
+ const userExcludes = traversePattern.filter((pattern) => typeof pattern === "string" && pattern.startsWith("!")).map((pattern) => pattern.slice(1));
66
+ const baseExcludePatterns = Array.from(new Set([
67
+ ...DEFAULT_EXCLUDE_PATTERNS,
68
+ ...userExcludes,
53
69
  ...excludePattern ?? [],
54
- ...contentDeclarationPattern,
55
- ...transformPattern.filter((pattern) => typeof pattern === "string" && pattern.startsWith("!")).map((pattern) => pattern.slice(1))
56
- ].filter((pattern) => typeof pattern === "string").map((pattern) => (0, node_path.normalize)(pattern));
57
- const fileList = getDistinctRootDirs([config.system.baseDir, ...config.content.codeDir]).flatMap((root) => fast_glob.default.sync(patterns, {
70
+ ...contentDeclarationPattern
71
+ ])).filter((pattern) => typeof pattern === "string").map((pattern) => (0, node_path.normalize)(pattern));
72
+ const resolvedCodeDirs = (config.content.codeDir ?? []).map((dir) => (0, node_path.resolve)(dir));
73
+ const inNodeModulesCodeDirs = resolvedCodeDirs.filter(isInsideNodeModules);
74
+ const normalCodeDirs = resolvedCodeDirs.filter((dir) => !isInsideNodeModules(dir));
75
+ const normalFiles = getDistinctRootDirs([config.system.baseDir, ...normalCodeDirs]).flatMap((root) => fast_glob.default.sync(patterns, {
58
76
  cwd: root,
59
- ignore: excludePatterns,
77
+ ignore: baseExcludePatterns,
60
78
  absolute: true,
61
79
  dot: true
62
80
  }));
63
- return Array.from(new Set(fileList));
81
+ const nodeModulesExcludePatterns = baseExcludePatterns.filter((pattern) => !pattern.includes("node_modules"));
82
+ const nodeModulesFiles = inNodeModulesCodeDirs.flatMap((dir) => fast_glob.default.sync(patterns, {
83
+ cwd: dir,
84
+ ignore: nodeModulesExcludePatterns,
85
+ absolute: true,
86
+ dot: false
87
+ }));
88
+ return Array.from(new Set([...normalFiles, ...nodeModulesFiles]));
64
89
  };
65
90
 
66
91
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"buildComponentFilesList.cjs","names":["fg"],"sources":["../../../src/utils/buildComponentFilesList.ts"],"sourcesContent":["import { isAbsolute, normalize, relative, resolve } from 'node:path';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport fg from 'fast-glob';\n\n/**\n * Normalizes a pattern value to an array\n */\nconst normalizeToArray = <T>(value: T | T[]): T[] =>\n Array.isArray(value) ? value : [value];\n\n/**\n * Remove directories that are subdirectories of others in the list so files\n * are never scanned twice.\n * Example: ['/root', '/root/src'] → ['/root']\n */\nconst getDistinctRootDirs = (dirs: string[]): string[] => {\n const uniqueDirs = Array.from(new Set(dirs.map((dir) => resolve(dir))));\n uniqueDirs.sort((a, b) => a.length - b.length);\n\n return uniqueDirs.reduce((acc: string[], dir) => {\n const isNested = acc.some((parent) => {\n const rel = relative(parent, dir);\n\n return !rel.startsWith('..') && !isAbsolute(rel) && rel !== '';\n });\n if (!isNested) acc.push(dir);\n\n return acc;\n }, []);\n};\n\n/**\n * Builds a deduplicated list of absolute file paths matching the given patterns.\n *\n * Handles multiple root directories (deduplicates overlapping roots), exclude\n * patterns, negation patterns embedded in `transformPattern`, and optional\n * dot-file inclusion.\n *\n * @example\n * // Single root with excludes\n * const files = buildComponentFilesList({\n * transformPattern: 'src/**\\/*.{ts,tsx}',\n * excludePattern: ['**\\/node_modules\\/**'],\n * baseDir: '/path/to/project',\n * });\n *\n * @example\n * // Multiple roots (e.g. baseDir + codeDir), dot files included\n * const files = buildComponentFilesList(config, ['**\\/node_modules\\/**']);\n */\nexport const buildComponentFilesList = (\n config: IntlayerConfig,\n excludePattern?: string[]\n): string[] => {\n const transformPattern = config.build.traversePattern;\n const compilerTransformPattern = config.compiler.transformPattern;\n const contentDeclarationPattern = config.content.fileExtensions.map(\n (ext) => `/**/*${ext}`\n );\n\n const patterns = [\n ...transformPattern,\n ...normalizeToArray(compilerTransformPattern),\n ]\n .filter((pattern) => typeof pattern === 'string')\n .filter((pattern) => !pattern.startsWith('!'))\n .map((pattern) => normalize(pattern)); // Ensure it works with Windows\n\n const excludePatterns = [\n ...(excludePattern ?? []),\n ...contentDeclarationPattern,\n // Treat negation entries in transformPattern as additional excludes\n ...transformPattern\n .filter(\n (pattern) => typeof pattern === 'string' && pattern.startsWith('!')\n )\n .map((pattern) => pattern.slice(1)),\n ]\n .filter((pattern) => typeof pattern === 'string')\n .map((pattern) => normalize(pattern)); // Ensure it works with Windows\n\n const roots = getDistinctRootDirs([\n config.system.baseDir,\n ...config.content.codeDir,\n ]);\n\n const fileList = roots.flatMap((root) =>\n fg.sync(patterns, {\n cwd: root,\n ignore: excludePatterns,\n absolute: true,\n dot: true, // include dot files like .next / .intlayer\n })\n );\n\n return Array.from(new Set(fileList));\n};\n"],"mappings":";;;;;;;;;;AAOA,MAAM,oBAAuB,UAC3B,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;;;;;;AAOvC,MAAM,uBAAuB,SAA6B;CACxD,MAAM,aAAa,MAAM,KAAK,IAAI,IAAI,KAAK,KAAK,+BAAgB,GAAG,CAAC,CAAC,CAAC;CACtE,WAAW,MAAM,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM;CAE7C,OAAO,WAAW,QAAQ,KAAe,QAAQ;EAM/C,IAAI,CALa,IAAI,MAAM,WAAW;GACpC,MAAM,8BAAe,QAAQ,GAAG;GAEhC,OAAO,CAAC,IAAI,WAAW,IAAI,KAAK,2BAAY,GAAG,KAAK,QAAQ;EAC9D,CACY,GAAG,IAAI,KAAK,GAAG;EAE3B,OAAO;CACT,GAAG,CAAC,CAAC;AACP;;;;;;;;;;;;;;;;;;;;AAqBA,MAAa,2BACX,QACA,mBACa;CACb,MAAM,mBAAmB,OAAO,MAAM;CACtC,MAAM,2BAA2B,OAAO,SAAS;CACjD,MAAM,4BAA4B,OAAO,QAAQ,eAAe,KAC7D,QAAQ,QAAQ,KACnB;CAEA,MAAM,WAAW,CACf,GAAG,kBACH,GAAG,iBAAiB,wBAAwB,CAC9C,EACG,QAAQ,YAAY,OAAO,YAAY,QAAQ,EAC/C,QAAQ,YAAY,CAAC,QAAQ,WAAW,GAAG,CAAC,EAC5C,KAAK,qCAAsB,OAAO,CAAC;CAEtC,MAAM,kBAAkB;EACtB,GAAI,kBAAkB,CAAC;EACvB,GAAG;EAEH,GAAG,iBACA,QACE,YAAY,OAAO,YAAY,YAAY,QAAQ,WAAW,GAAG,CACpE,EACC,KAAK,YAAY,QAAQ,MAAM,CAAC,CAAC;CACtC,EACG,QAAQ,YAAY,OAAO,YAAY,QAAQ,EAC/C,KAAK,qCAAsB,OAAO,CAAC;CAOtC,MAAM,WALQ,oBAAoB,CAChC,OAAO,OAAO,SACd,GAAG,OAAO,QAAQ,OACpB,CAEqB,EAAE,SAAS,SAC9BA,kBAAG,KAAK,UAAU;EAChB,KAAK;EACL,QAAQ;EACR,UAAU;EACV,KAAK;CACP,CAAC,CACH;CAEA,OAAO,MAAM,KAAK,IAAI,IAAI,QAAQ,CAAC;AACrC"}
1
+ {"version":3,"file":"buildComponentFilesList.cjs","names":["sep","TRAVERSE_PATTERN","fg"],"sources":["../../../src/utils/buildComponentFilesList.ts"],"sourcesContent":["import { isAbsolute, normalize, relative, resolve, sep } from 'node:path';\nimport { TRAVERSE_PATTERN } from '@intlayer/config/defaultValues';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport fg from 'fast-glob';\n\n/**\n * Normalizes a pattern value to an array\n */\nconst normalizeToArray = <T>(value: T | T[]): T[] =>\n Array.isArray(value) ? value : [value];\n\n/**\n * Remove directories that are subdirectories of others in the list so files\n * are never scanned twice.\n * Example: ['/root', '/root/src'] → ['/root']\n */\nconst getDistinctRootDirs = (dirs: string[]): string[] => {\n const uniqueDirs = Array.from(new Set(dirs.map((dir) => resolve(dir))));\n uniqueDirs.sort((a, b) => a.length - b.length);\n\n return uniqueDirs.reduce((acc: string[], dir) => {\n const isNested = acc.some((parent) => {\n const rel = relative(parent, dir);\n\n return !rel.startsWith('..') && !isAbsolute(rel) && rel !== '';\n });\n if (!isNested) acc.push(dir);\n\n return acc;\n }, []);\n};\n\n/**\n * Returns true when the resolved path passes through a `node_modules` segment.\n * Works on both POSIX and Windows paths.\n */\nconst isInsideNodeModules = (dir: string): boolean =>\n resolve(dir).split(sep).includes('node_modules');\n\n/**\n * Default exclude patterns derived from TRAVERSE_PATTERN.\n * Extracted once so the function body can reference them without re-computing.\n */\nconst DEFAULT_EXCLUDE_PATTERNS: string[] = TRAVERSE_PATTERN.filter(\n (p): p is string => typeof p === 'string' && p.startsWith('!')\n).map((p) => p.slice(1));\n\n/**\n * Builds a deduplicated list of absolute file paths matching the given patterns.\n *\n * Handles multiple root directories (deduplicates overlapping roots), exclude\n * patterns, negation patterns embedded in `traversePattern`, and optional\n * dot-file inclusion.\n *\n * Special case: `codeDir` entries that live inside `node_modules` (e.g. a\n * design-system package installed as a workspace dependency) are scanned as\n * their own explicit roots. They are NOT collapsed into the project root so\n * the `*\\/node_modules\\/**` exclusion does not silently drop them.\n *\n * @example\n * // Single root with excludes\n * const files = buildComponentFilesList(config);\n *\n * @example\n * // Design-system package inside node_modules is still scanned\n * // intlayer.config.ts: { content: { codeDir: ['node_modules/my-ds/src'] } }\n * const files = buildComponentFilesList(config);\n */\nexport const buildComponentFilesList = (\n config: IntlayerConfig,\n excludePattern?: string[]\n): string[] => {\n const traversePattern = config.build.traversePattern;\n const compilerTransformPattern = config.compiler.transformPattern;\n const contentDeclarationPattern = config.content.fileExtensions.map(\n (ext) => `/**/*${ext}`\n );\n\n const patterns = [\n ...traversePattern,\n ...normalizeToArray(compilerTransformPattern),\n ]\n .filter((pattern) => typeof pattern === 'string')\n .filter((pattern) => !pattern.startsWith('!'))\n .map((pattern) => normalize(pattern));\n\n // User-supplied negations from traversePattern\n const userExcludes = traversePattern\n .filter(\n (pattern): pattern is string =>\n typeof pattern === 'string' && pattern.startsWith('!')\n )\n .map((pattern) => pattern.slice(1));\n\n // Full exclude list: defaults (from TRAVERSE_PATTERN) + user overrides + caller extras + content files.\n // DEFAULT_EXCLUDE_PATTERNS acts as a safety floor — if the user provides a\n // partial traversePattern that omits some defaults, they are still applied.\n const baseExcludePatterns = Array.from(\n new Set([\n ...DEFAULT_EXCLUDE_PATTERNS,\n ...userExcludes,\n ...(excludePattern ?? []),\n ...contentDeclarationPattern,\n ])\n )\n .filter((pattern): pattern is string => typeof pattern === 'string')\n .map((pattern) => normalize(pattern));\n\n // Separate codeDir entries that live inside node_modules.\n // getDistinctRootDirs would collapse them into the project root, after which\n // the **/node_modules/** ignore would silently exclude them.\n const resolvedCodeDirs = (config.content.codeDir ?? []).map((dir) =>\n resolve(dir)\n );\n const inNodeModulesCodeDirs = resolvedCodeDirs.filter(isInsideNodeModules);\n const normalCodeDirs = resolvedCodeDirs.filter(\n (dir) => !isInsideNodeModules(dir)\n );\n\n // Normal roots: project base + regular codeDir entries, deduplicated.\n const normalRoots = getDistinctRootDirs([\n config.system.baseDir,\n ...normalCodeDirs,\n ]);\n\n const normalFiles = normalRoots.flatMap((root) =>\n fg.sync(patterns, {\n cwd: root,\n ignore: baseExcludePatterns,\n absolute: true,\n dot: true, // needed for .intlayer and similar\n })\n );\n\n // node_modules codeDir roots: scanned directly from the package root.\n // **/node_modules/** is removed from the ignore list so it doesn't block\n // the scan (it is still applied inside the package for nested node_modules).\n const nodeModulesExcludePatterns = baseExcludePatterns.filter(\n (pattern) => !pattern.includes('node_modules')\n );\n\n const nodeModulesFiles = inNodeModulesCodeDirs.flatMap((dir) =>\n fg.sync(patterns, {\n cwd: dir,\n ignore: nodeModulesExcludePatterns,\n absolute: true,\n dot: false,\n })\n );\n\n return Array.from(new Set([...normalFiles, ...nodeModulesFiles]));\n};\n"],"mappings":";;;;;;;;;;;AAQA,MAAM,oBAAuB,UAC3B,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;;;;;;AAOvC,MAAM,uBAAuB,SAA6B;CACxD,MAAM,aAAa,MAAM,KAAK,IAAI,IAAI,KAAK,KAAK,+BAAgB,GAAG,CAAC,CAAC,CAAC;CACtE,WAAW,MAAM,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM;CAE7C,OAAO,WAAW,QAAQ,KAAe,QAAQ;EAM/C,IAAI,CALa,IAAI,MAAM,WAAW;GACpC,MAAM,8BAAe,QAAQ,GAAG;GAEhC,OAAO,CAAC,IAAI,WAAW,IAAI,KAAK,2BAAY,GAAG,KAAK,QAAQ;EAC9D,CACY,GAAG,IAAI,KAAK,GAAG;EAE3B,OAAO;CACT,GAAG,CAAC,CAAC;AACP;;;;;AAMA,MAAM,uBAAuB,+BACnB,GAAG,EAAE,MAAMA,aAAG,EAAE,SAAS,cAAc;;;;;AAMjD,MAAM,2BAAqCC,gDAAiB,QACzD,MAAmB,OAAO,MAAM,YAAY,EAAE,WAAW,GAAG,CAC/D,EAAE,KAAK,MAAM,EAAE,MAAM,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;AAuBvB,MAAa,2BACX,QACA,mBACa;CACb,MAAM,kBAAkB,OAAO,MAAM;CACrC,MAAM,2BAA2B,OAAO,SAAS;CACjD,MAAM,4BAA4B,OAAO,QAAQ,eAAe,KAC7D,QAAQ,QAAQ,KACnB;CAEA,MAAM,WAAW,CACf,GAAG,iBACH,GAAG,iBAAiB,wBAAwB,CAC9C,EACG,QAAQ,YAAY,OAAO,YAAY,QAAQ,EAC/C,QAAQ,YAAY,CAAC,QAAQ,WAAW,GAAG,CAAC,EAC5C,KAAK,qCAAsB,OAAO,CAAC;CAGtC,MAAM,eAAe,gBAClB,QACE,YACC,OAAO,YAAY,YAAY,QAAQ,WAAW,GAAG,CACzD,EACC,KAAK,YAAY,QAAQ,MAAM,CAAC,CAAC;CAKpC,MAAM,sBAAsB,MAAM,KAChC,IAAI,IAAI;EACN,GAAG;EACH,GAAG;EACH,GAAI,kBAAkB,CAAC;EACvB,GAAG;CACL,CAAC,CACH,EACG,QAAQ,YAA+B,OAAO,YAAY,QAAQ,EAClE,KAAK,qCAAsB,OAAO,CAAC;CAKtC,MAAM,oBAAoB,OAAO,QAAQ,WAAW,CAAC,GAAG,KAAK,+BACnD,GAAG,CACb;CACA,MAAM,wBAAwB,iBAAiB,OAAO,mBAAmB;CACzE,MAAM,iBAAiB,iBAAiB,QACrC,QAAQ,CAAC,oBAAoB,GAAG,CACnC;CAQA,MAAM,cALc,oBAAoB,CACtC,OAAO,OAAO,SACd,GAAG,cACL,CAE8B,EAAE,SAAS,SACvCC,kBAAG,KAAK,UAAU;EAChB,KAAK;EACL,QAAQ;EACR,UAAU;EACV,KAAK;CACP,CAAC,CACH;CAKA,MAAM,6BAA6B,oBAAoB,QACpD,YAAY,CAAC,QAAQ,SAAS,cAAc,CAC/C;CAEA,MAAM,mBAAmB,sBAAsB,SAAS,QACtDA,kBAAG,KAAK,UAAU;EAChB,KAAK;EACL,QAAQ;EACR,UAAU;EACV,KAAK;CACP,CAAC,CACH;CAEA,OAAO,MAAM,KAAK,IAAI,IAAI,CAAC,GAAG,aAAa,GAAG,gBAAgB,CAAC,CAAC;AAClE"}
@@ -38,14 +38,25 @@ const processEvent = (task) => {
38
38
  taskQueue.push(task);
39
39
  processQueue();
40
40
  };
41
+ const STABILITY_THRESHOLD = 1e3;
42
+ const createStabilityDebounce = () => {
43
+ const pending = /* @__PURE__ */ new Map();
44
+ return (path, handler) => {
45
+ const existing = pending.get(path);
46
+ if (existing) clearTimeout(existing);
47
+ pending.set(path, setTimeout(() => {
48
+ pending.delete(path);
49
+ handler();
50
+ }, STABILITY_THRESHOLD));
51
+ };
52
+ };
41
53
  const watch = async (options) => {
42
- const { watch: chokidarWatch } = await import("chokidar");
54
+ const { subscribe } = await import("@parcel/watcher");
43
55
  const configResult = (0, _intlayer_config_node.getConfigurationAndFilePath)(options?.configOptions);
44
56
  const configurationFilePath = configResult.configurationFilePath;
45
57
  let configuration = options?.configuration ?? configResult.configuration;
46
58
  const appLogger = (0, _intlayer_config_logger.getAppLogger)(configuration);
47
59
  const { watch: isWatchMode, fileExtensions, contentDir, excludedPath } = configuration.content;
48
- const pathsToWatch = [...contentDir.map((dir) => (0, _intlayer_config_utils.normalizePath)(dir)).filter(node_fs.existsSync), ...configurationFilePath ? [configurationFilePath] : []];
49
60
  if (!configuration.content.watch) return;
50
61
  appLogger("Watching Intlayer content declarations");
51
62
  if (configuration.build.optimize === true) appLogger([
@@ -60,50 +71,51 @@ const watch = async (options) => {
60
71
  const excludedSegments = excludedPath.map((segment) => segment.replace(/^\*\*\//, "").replace(/\/\*\*$/, ""));
61
72
  const normalizedConfigPath = configurationFilePath ? (0, _intlayer_config_utils.normalizePath)(configurationFilePath) : null;
62
73
  const { mainDir, baseDir } = configuration.system;
74
+ const normalizedMainDir = (0, _intlayer_config_utils.normalizePath)(mainDir);
63
75
  const normalizedIntlayerDir = (0, _intlayer_config_utils.normalizePath)((0, node_path.dirname)(mainDir));
64
- if ((0, node_fs.existsSync)(mainDir)) chokidarWatch(mainDir, {
65
- persistent: isWatchMode,
66
- ignoreInitial: true,
67
- depth: 0
68
- }).on("change", async (filePath) => {
69
- if (isProcessing) return;
70
- processEvent(async () => {
71
- (0, _intlayer_config_utils.clearModuleCache)(filePath);
72
- try {
73
- await import(`${(0, node_url.pathToFileURL)(filePath).href}?update=${Date.now()}`);
74
- } catch {
75
- appLogger(`Entry point ${(0, node_path.basename)(filePath)} failed to load, running clean rebuild...`, { level: "warn" });
76
- await require_prepareIntlayer.prepareIntlayer(configuration, {
77
- clean: true,
78
- forceRun: true
76
+ const subscriptions = [];
77
+ const fsWatchers = [];
78
+ const scheduleStable = createStabilityDebounce();
79
+ if ((0, node_fs.existsSync)(mainDir)) {
80
+ const mainDirSub = await subscribe(normalizedMainDir, (err, events) => {
81
+ if (err || isProcessing) return;
82
+ for (const event of events) {
83
+ const rel = (0, _intlayer_config_utils.normalizePath)(event.path).slice(normalizedMainDir.length + 1);
84
+ if (!rel || rel.includes("/")) continue;
85
+ if (rel.endsWith(".tmp")) continue;
86
+ if (event.type === "update") processEvent(async () => {
87
+ (0, _intlayer_config_utils.clearModuleCache)(event.path);
88
+ try {
89
+ await import(`${(0, node_url.pathToFileURL)(event.path).href}?update=${Date.now()}`);
90
+ } catch {
91
+ appLogger(`Entry point ${(0, node_path.basename)(event.path)} failed to load, running clean rebuild...`, { level: "warn" });
92
+ await require_prepareIntlayer.prepareIntlayer(configuration, {
93
+ clean: true,
94
+ forceRun: true
95
+ });
96
+ }
97
+ });
98
+ else if (event.type === "delete") processEvent(async () => {
99
+ appLogger([
100
+ "Entry point",
101
+ require_utils_formatter.formatPath((0, node_path.basename)(event.path)),
102
+ "was removed, running clean rebuild..."
103
+ ], { level: "warn" });
104
+ await require_prepareIntlayer.prepareIntlayer(configuration, {
105
+ clean: true,
106
+ forceRun: true
107
+ });
79
108
  });
80
109
  }
81
110
  });
82
- }).on("unlink", async (filePath) => {
83
- if (isProcessing) return;
84
- processEvent(async () => {
85
- appLogger([
86
- "Entry point",
87
- require_utils_formatter.formatPath((0, node_path.basename)(filePath)),
88
- "was removed, running clean rebuild..."
89
- ], { level: "warn" });
90
- await require_prepareIntlayer.prepareIntlayer(configuration, {
91
- clean: true,
92
- forceRun: true
93
- });
94
- });
95
- });
96
- chokidarWatch(baseDir, {
97
- persistent: isWatchMode,
98
- ignoreInitial: true,
99
- depth: 0,
100
- ignored: (filePath) => {
101
- const path = (0, _intlayer_config_utils.normalizePath)(filePath);
102
- return path !== (0, _intlayer_config_utils.normalizePath)(baseDir) && path !== normalizedIntlayerDir;
103
- }
104
- }).on("unlinkDir", async (dirPath) => {
105
- if (isProcessing) return;
106
- if ((0, _intlayer_config_utils.normalizePath)(dirPath) === normalizedIntlayerDir) {
111
+ subscriptions.push(mainDirSub);
112
+ }
113
+ const intlayerDirName = (0, node_path.basename)(normalizedIntlayerDir);
114
+ const fsDirWatcher = (0, node_fs.watch)(baseDir, { persistent: isWatchMode }, (eventType, filename) => {
115
+ if (isProcessing || !filename) return;
116
+ if (filename !== intlayerDirName) return;
117
+ if ((0, _intlayer_config_utils.normalizePath)((0, node_path.resolve)(baseDir, filename)) !== normalizedIntlayerDir) return;
118
+ if (eventType === "rename" && !(0, node_fs.existsSync)(normalizedIntlayerDir)) {
107
119
  appLogger([require_utils_formatter.formatPath(".intlayer"), "directory removed, running clean rebuild..."]);
108
120
  processEvent(() => require_prepareIntlayer.prepareIntlayer(configuration, {
109
121
  clean: true,
@@ -111,81 +123,100 @@ const watch = async (options) => {
111
123
  }));
112
124
  }
113
125
  });
114
- return chokidarWatch(pathsToWatch, {
115
- persistent: isWatchMode,
116
- ignoreInitial: true,
117
- awaitWriteFinish: {
118
- stabilityThreshold: 1e3,
119
- pollInterval: 100
120
- },
121
- ignored: (filePath, stats) => {
122
- const path = (0, _intlayer_config_utils.normalizePath)(filePath);
123
- if (normalizedConfigPath && path === normalizedConfigPath) return false;
124
- if (excludedSegments.some((segment) => path.includes(`/${segment}`))) return true;
125
- if (stats?.isFile()) return !fileExtensions.some((extension) => path.endsWith(extension));
126
- return false;
127
- },
128
- ...options
129
- }).on("add", async (filePath) => {
130
- const fileName = (0, node_path.basename)(filePath);
131
- let isMove = false;
132
- let matchedOldPath;
133
- for (const [oldPath] of pendingUnlinks) if ((0, node_path.basename)(oldPath) === fileName) {
134
- matchedOldPath = oldPath;
135
- break;
126
+ fsWatchers.push(fsDirWatcher);
127
+ const ignorePatterns = excludedSegments.map((s) => `**/${s}/**`);
128
+ const contentDirs = contentDir.map((dir) => (0, _intlayer_config_utils.normalizePath)(dir)).filter(node_fs.existsSync);
129
+ const dirsToWatch = new Set(contentDirs);
130
+ if (normalizedConfigPath) dirsToWatch.add((0, _intlayer_config_utils.normalizePath)((0, node_path.dirname)(normalizedConfigPath)));
131
+ const contentHandler = (err, events) => {
132
+ if (err) {
133
+ appLogger(`Watcher error: ${err}`, { level: "error" });
134
+ appLogger("Restarting watcher");
135
+ require_prepareIntlayer.prepareIntlayer(configuration);
136
+ return;
136
137
  }
137
- if (!matchedOldPath && pendingUnlinks.size === 1) matchedOldPath = pendingUnlinks.keys().next().value;
138
- if (matchedOldPath) {
139
- const pending = pendingUnlinks.get(matchedOldPath);
140
- if (pending) {
141
- clearTimeout(pending.timer);
142
- pendingUnlinks.delete(matchedOldPath);
138
+ for (const event of events) {
139
+ const filePath = event.path;
140
+ const path = (0, _intlayer_config_utils.normalizePath)(filePath);
141
+ const isConfigFile = normalizedConfigPath && path === normalizedConfigPath;
142
+ if (!isConfigFile) {
143
+ if (!contentDirs.some((d) => path.startsWith(`${d}/`) || path === d)) continue;
144
+ if (excludedSegments.some((segment) => path.includes(`/${segment}`))) continue;
145
+ if (!fileExtensions.some((extension) => path.endsWith(extension))) continue;
143
146
  }
144
- isMove = true;
145
- appLogger(`File moved from ${matchedOldPath} to ${filePath}`);
146
- }
147
- processEvent(async () => {
148
- if (isMove && matchedOldPath) await require_handleContentDeclarationFileMoved.handleContentDeclarationFileMoved(matchedOldPath, filePath, configuration);
149
- else {
150
- if (await (0, node_fs_promises.readFile)(filePath, "utf-8") === "") {
151
- const extensionPattern = fileExtensions.map((ext) => ext.replace(/\./g, "\\.")).join("|");
152
- await require_writeContentDeclaration_writeContentDeclaration.writeContentDeclaration({
153
- key: fileName.replace(new RegExp(`(${extensionPattern})$`), ""),
154
- content: {},
155
- filePath
156
- }, configuration);
147
+ if (event.type === "create") {
148
+ const fileName = (0, node_path.basename)(filePath);
149
+ let isMove = false;
150
+ let matchedOldPath;
151
+ for (const [oldPath] of pendingUnlinks) if ((0, node_path.basename)(oldPath) === fileName) {
152
+ matchedOldPath = oldPath;
153
+ break;
154
+ }
155
+ if (!matchedOldPath && pendingUnlinks.size === 1) matchedOldPath = pendingUnlinks.keys().next().value;
156
+ if (matchedOldPath) {
157
+ const pending = pendingUnlinks.get(matchedOldPath);
158
+ if (pending) {
159
+ clearTimeout(pending.timer);
160
+ pendingUnlinks.delete(matchedOldPath);
161
+ }
162
+ isMove = true;
163
+ appLogger(`File moved from ${matchedOldPath} to ${filePath}`);
157
164
  }
158
- await require_handleAdditionalContentDeclarationFile.handleAdditionalContentDeclarationFile(filePath, configuration);
165
+ if (isMove && matchedOldPath) processEvent(async () => {
166
+ await require_handleContentDeclarationFileMoved.handleContentDeclarationFileMoved(matchedOldPath, filePath, configuration);
167
+ });
168
+ else scheduleStable(path, () => {
169
+ processEvent(async () => {
170
+ if (await (0, node_fs_promises.readFile)(filePath, "utf-8") === "") {
171
+ const extensionPattern = fileExtensions.map((ext) => ext.replace(/\./g, "\\.")).join("|");
172
+ await require_writeContentDeclaration_writeContentDeclaration.writeContentDeclaration({
173
+ key: fileName.replace(new RegExp(`(${extensionPattern})$`), ""),
174
+ content: {},
175
+ filePath
176
+ }, configuration);
177
+ }
178
+ await require_handleAdditionalContentDeclarationFile.handleAdditionalContentDeclarationFile(filePath, configuration);
179
+ });
180
+ });
181
+ } else if (event.type === "update") scheduleStable(path, () => {
182
+ processEvent(async () => {
183
+ if (isConfigFile) {
184
+ appLogger("Configuration file changed, repreparing Intlayer");
185
+ (0, _intlayer_config_utils.clearModuleCache)(filePath);
186
+ (0, _intlayer_config_utils.clearAllCache)();
187
+ const { configuration: newConfiguration } = (0, _intlayer_config_node.getConfigurationAndFilePath)(options?.configOptions);
188
+ configuration = options?.configuration ?? newConfiguration;
189
+ await require_prepareIntlayer.prepareIntlayer(configuration, { clean: false });
190
+ } else {
191
+ (0, _intlayer_config_utils.clearModuleCache)(filePath);
192
+ (0, _intlayer_config_utils.clearAllCache)();
193
+ (0, _intlayer_config_utils.clearDiskCacheMemory)();
194
+ await require_handleContentDeclarationFileChange.handleContentDeclarationFileChange(filePath, configuration);
195
+ }
196
+ });
197
+ });
198
+ else if (event.type === "delete") {
199
+ const timer = setTimeout(async () => {
200
+ pendingUnlinks.delete(filePath);
201
+ processEvent(async () => require_handleUnlinkedContentDeclarationFile.handleUnlinkedContentDeclarationFile(filePath, configuration));
202
+ }, 200);
203
+ pendingUnlinks.set(filePath, {
204
+ timer,
205
+ oldPath: filePath
206
+ });
159
207
  }
160
- });
161
- }).on("change", async (filePath) => processEvent(async () => {
162
- if (configurationFilePath && filePath === configurationFilePath) {
163
- appLogger("Configuration file changed, repreparing Intlayer");
164
- (0, _intlayer_config_utils.clearModuleCache)(configurationFilePath);
165
- (0, _intlayer_config_utils.clearAllCache)();
166
- const { configuration: newConfiguration } = (0, _intlayer_config_node.getConfigurationAndFilePath)(options?.configOptions);
167
- configuration = options?.configuration ?? newConfiguration;
168
- await require_prepareIntlayer.prepareIntlayer(configuration, { clean: false });
169
- } else {
170
- (0, _intlayer_config_utils.clearModuleCache)(filePath);
171
- (0, _intlayer_config_utils.clearAllCache)();
172
- (0, _intlayer_config_utils.clearDiskCacheMemory)();
173
- await require_handleContentDeclarationFileChange.handleContentDeclarationFileChange(filePath, configuration);
174
208
  }
175
- })).on("unlink", async (filePath) => {
176
- const timer = setTimeout(async () => {
177
- pendingUnlinks.delete(filePath);
178
- processEvent(async () => require_handleUnlinkedContentDeclarationFile.handleUnlinkedContentDeclarationFile(filePath, configuration));
179
- }, 200);
180
- pendingUnlinks.set(filePath, {
181
- timer,
182
- oldPath: filePath
209
+ };
210
+ for (const dir of dirsToWatch) {
211
+ const sub = await subscribe(dir, contentHandler, { ignore: ignorePatterns });
212
+ subscriptions.push(sub);
213
+ }
214
+ return { unsubscribe: async () => {
215
+ await Promise.all(subscriptions.map((subscription) => subscription.unsubscribe()));
216
+ fsWatchers.forEach((watcher) => {
217
+ watcher.close();
183
218
  });
184
- }).on("error", async (error) => {
185
- appLogger(`Watcher error: ${error}`, { level: "error" });
186
- appLogger("Restarting watcher");
187
- await require_prepareIntlayer.prepareIntlayer(configuration);
188
- });
219
+ } };
189
220
  };
190
221
  const buildAndWatchIntlayer = async (options) => {
191
222
  const { skipPrepare, ...rest } = options ?? {};
@@ -1 +1 @@
1
- {"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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"}
1
+ {"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 const fsWatchers: import('node:fs').FSWatcher[] = [];\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 fsWatchers.push(fsDirWatcher);\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 {\n unsubscribe: async () => {\n await Promise.all(\n subscriptions.map((subscription) => subscription.unsubscribe())\n );\n\n fsWatchers.forEach((watcher) => {\n watcher.close();\n });\n },\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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"}
@@ -1,5 +1,6 @@
1
- import { isAbsolute, normalize, relative, resolve } from "node:path";
1
+ import { isAbsolute, normalize, relative, resolve, sep } from "node:path";
2
2
  import fg from "fast-glob";
3
+ import { TRAVERSE_PATTERN } from "@intlayer/config/defaultValues";
3
4
 
4
5
  //#region src/utils/buildComponentFilesList.ts
5
6
  /**
@@ -23,41 +24,65 @@ const getDistinctRootDirs = (dirs) => {
23
24
  }, []);
24
25
  };
25
26
  /**
27
+ * Returns true when the resolved path passes through a `node_modules` segment.
28
+ * Works on both POSIX and Windows paths.
29
+ */
30
+ const isInsideNodeModules = (dir) => resolve(dir).split(sep).includes("node_modules");
31
+ /**
32
+ * Default exclude patterns derived from TRAVERSE_PATTERN.
33
+ * Extracted once so the function body can reference them without re-computing.
34
+ */
35
+ const DEFAULT_EXCLUDE_PATTERNS = TRAVERSE_PATTERN.filter((p) => typeof p === "string" && p.startsWith("!")).map((p) => p.slice(1));
36
+ /**
26
37
  * Builds a deduplicated list of absolute file paths matching the given patterns.
27
38
  *
28
39
  * Handles multiple root directories (deduplicates overlapping roots), exclude
29
- * patterns, negation patterns embedded in `transformPattern`, and optional
40
+ * patterns, negation patterns embedded in `traversePattern`, and optional
30
41
  * dot-file inclusion.
31
42
  *
43
+ * Special case: `codeDir` entries that live inside `node_modules` (e.g. a
44
+ * design-system package installed as a workspace dependency) are scanned as
45
+ * their own explicit roots. They are NOT collapsed into the project root so
46
+ * the `*\/node_modules\/**` exclusion does not silently drop them.
47
+ *
32
48
  * @example
33
49
  * // Single root with excludes
34
- * const files = buildComponentFilesList({
35
- * transformPattern: 'src/**\/*.{ts,tsx}',
36
- * excludePattern: ['**\/node_modules\/**'],
37
- * baseDir: '/path/to/project',
38
- * });
50
+ * const files = buildComponentFilesList(config);
39
51
  *
40
52
  * @example
41
- * // Multiple roots (e.g. baseDir + codeDir), dot files included
42
- * const files = buildComponentFilesList(config, ['**\/node_modules\/**']);
53
+ * // Design-system package inside node_modules is still scanned
54
+ * // intlayer.config.ts: { content: { codeDir: ['node_modules/my-ds/src'] } }
55
+ * const files = buildComponentFilesList(config);
43
56
  */
44
57
  const buildComponentFilesList = (config, excludePattern) => {
45
- const transformPattern = config.build.traversePattern;
58
+ const traversePattern = config.build.traversePattern;
46
59
  const compilerTransformPattern = config.compiler.transformPattern;
47
60
  const contentDeclarationPattern = config.content.fileExtensions.map((ext) => `/**/*${ext}`);
48
- const patterns = [...transformPattern, ...normalizeToArray(compilerTransformPattern)].filter((pattern) => typeof pattern === "string").filter((pattern) => !pattern.startsWith("!")).map((pattern) => normalize(pattern));
49
- const excludePatterns = [
61
+ const patterns = [...traversePattern, ...normalizeToArray(compilerTransformPattern)].filter((pattern) => typeof pattern === "string").filter((pattern) => !pattern.startsWith("!")).map((pattern) => normalize(pattern));
62
+ const userExcludes = traversePattern.filter((pattern) => typeof pattern === "string" && pattern.startsWith("!")).map((pattern) => pattern.slice(1));
63
+ const baseExcludePatterns = Array.from(new Set([
64
+ ...DEFAULT_EXCLUDE_PATTERNS,
65
+ ...userExcludes,
50
66
  ...excludePattern ?? [],
51
- ...contentDeclarationPattern,
52
- ...transformPattern.filter((pattern) => typeof pattern === "string" && pattern.startsWith("!")).map((pattern) => pattern.slice(1))
53
- ].filter((pattern) => typeof pattern === "string").map((pattern) => normalize(pattern));
54
- const fileList = getDistinctRootDirs([config.system.baseDir, ...config.content.codeDir]).flatMap((root) => fg.sync(patterns, {
67
+ ...contentDeclarationPattern
68
+ ])).filter((pattern) => typeof pattern === "string").map((pattern) => normalize(pattern));
69
+ const resolvedCodeDirs = (config.content.codeDir ?? []).map((dir) => resolve(dir));
70
+ const inNodeModulesCodeDirs = resolvedCodeDirs.filter(isInsideNodeModules);
71
+ const normalCodeDirs = resolvedCodeDirs.filter((dir) => !isInsideNodeModules(dir));
72
+ const normalFiles = getDistinctRootDirs([config.system.baseDir, ...normalCodeDirs]).flatMap((root) => fg.sync(patterns, {
55
73
  cwd: root,
56
- ignore: excludePatterns,
74
+ ignore: baseExcludePatterns,
57
75
  absolute: true,
58
76
  dot: true
59
77
  }));
60
- return Array.from(new Set(fileList));
78
+ const nodeModulesExcludePatterns = baseExcludePatterns.filter((pattern) => !pattern.includes("node_modules"));
79
+ const nodeModulesFiles = inNodeModulesCodeDirs.flatMap((dir) => fg.sync(patterns, {
80
+ cwd: dir,
81
+ ignore: nodeModulesExcludePatterns,
82
+ absolute: true,
83
+ dot: false
84
+ }));
85
+ return Array.from(new Set([...normalFiles, ...nodeModulesFiles]));
61
86
  };
62
87
 
63
88
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"buildComponentFilesList.mjs","names":[],"sources":["../../../src/utils/buildComponentFilesList.ts"],"sourcesContent":["import { isAbsolute, normalize, relative, resolve } from 'node:path';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport fg from 'fast-glob';\n\n/**\n * Normalizes a pattern value to an array\n */\nconst normalizeToArray = <T>(value: T | T[]): T[] =>\n Array.isArray(value) ? value : [value];\n\n/**\n * Remove directories that are subdirectories of others in the list so files\n * are never scanned twice.\n * Example: ['/root', '/root/src'] → ['/root']\n */\nconst getDistinctRootDirs = (dirs: string[]): string[] => {\n const uniqueDirs = Array.from(new Set(dirs.map((dir) => resolve(dir))));\n uniqueDirs.sort((a, b) => a.length - b.length);\n\n return uniqueDirs.reduce((acc: string[], dir) => {\n const isNested = acc.some((parent) => {\n const rel = relative(parent, dir);\n\n return !rel.startsWith('..') && !isAbsolute(rel) && rel !== '';\n });\n if (!isNested) acc.push(dir);\n\n return acc;\n }, []);\n};\n\n/**\n * Builds a deduplicated list of absolute file paths matching the given patterns.\n *\n * Handles multiple root directories (deduplicates overlapping roots), exclude\n * patterns, negation patterns embedded in `transformPattern`, and optional\n * dot-file inclusion.\n *\n * @example\n * // Single root with excludes\n * const files = buildComponentFilesList({\n * transformPattern: 'src/**\\/*.{ts,tsx}',\n * excludePattern: ['**\\/node_modules\\/**'],\n * baseDir: '/path/to/project',\n * });\n *\n * @example\n * // Multiple roots (e.g. baseDir + codeDir), dot files included\n * const files = buildComponentFilesList(config, ['**\\/node_modules\\/**']);\n */\nexport const buildComponentFilesList = (\n config: IntlayerConfig,\n excludePattern?: string[]\n): string[] => {\n const transformPattern = config.build.traversePattern;\n const compilerTransformPattern = config.compiler.transformPattern;\n const contentDeclarationPattern = config.content.fileExtensions.map(\n (ext) => `/**/*${ext}`\n );\n\n const patterns = [\n ...transformPattern,\n ...normalizeToArray(compilerTransformPattern),\n ]\n .filter((pattern) => typeof pattern === 'string')\n .filter((pattern) => !pattern.startsWith('!'))\n .map((pattern) => normalize(pattern)); // Ensure it works with Windows\n\n const excludePatterns = [\n ...(excludePattern ?? []),\n ...contentDeclarationPattern,\n // Treat negation entries in transformPattern as additional excludes\n ...transformPattern\n .filter(\n (pattern) => typeof pattern === 'string' && pattern.startsWith('!')\n )\n .map((pattern) => pattern.slice(1)),\n ]\n .filter((pattern) => typeof pattern === 'string')\n .map((pattern) => normalize(pattern)); // Ensure it works with Windows\n\n const roots = getDistinctRootDirs([\n config.system.baseDir,\n ...config.content.codeDir,\n ]);\n\n const fileList = roots.flatMap((root) =>\n fg.sync(patterns, {\n cwd: root,\n ignore: excludePatterns,\n absolute: true,\n dot: true, // include dot files like .next / .intlayer\n })\n );\n\n return Array.from(new Set(fileList));\n};\n"],"mappings":";;;;;;;AAOA,MAAM,oBAAuB,UAC3B,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;;;;;;AAOvC,MAAM,uBAAuB,SAA6B;CACxD,MAAM,aAAa,MAAM,KAAK,IAAI,IAAI,KAAK,KAAK,QAAQ,QAAQ,GAAG,CAAC,CAAC,CAAC;CACtE,WAAW,MAAM,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM;CAE7C,OAAO,WAAW,QAAQ,KAAe,QAAQ;EAM/C,IAAI,CALa,IAAI,MAAM,WAAW;GACpC,MAAM,MAAM,SAAS,QAAQ,GAAG;GAEhC,OAAO,CAAC,IAAI,WAAW,IAAI,KAAK,CAAC,WAAW,GAAG,KAAK,QAAQ;EAC9D,CACY,GAAG,IAAI,KAAK,GAAG;EAE3B,OAAO;CACT,GAAG,CAAC,CAAC;AACP;;;;;;;;;;;;;;;;;;;;AAqBA,MAAa,2BACX,QACA,mBACa;CACb,MAAM,mBAAmB,OAAO,MAAM;CACtC,MAAM,2BAA2B,OAAO,SAAS;CACjD,MAAM,4BAA4B,OAAO,QAAQ,eAAe,KAC7D,QAAQ,QAAQ,KACnB;CAEA,MAAM,WAAW,CACf,GAAG,kBACH,GAAG,iBAAiB,wBAAwB,CAC9C,EACG,QAAQ,YAAY,OAAO,YAAY,QAAQ,EAC/C,QAAQ,YAAY,CAAC,QAAQ,WAAW,GAAG,CAAC,EAC5C,KAAK,YAAY,UAAU,OAAO,CAAC;CAEtC,MAAM,kBAAkB;EACtB,GAAI,kBAAkB,CAAC;EACvB,GAAG;EAEH,GAAG,iBACA,QACE,YAAY,OAAO,YAAY,YAAY,QAAQ,WAAW,GAAG,CACpE,EACC,KAAK,YAAY,QAAQ,MAAM,CAAC,CAAC;CACtC,EACG,QAAQ,YAAY,OAAO,YAAY,QAAQ,EAC/C,KAAK,YAAY,UAAU,OAAO,CAAC;CAOtC,MAAM,WALQ,oBAAoB,CAChC,OAAO,OAAO,SACd,GAAG,OAAO,QAAQ,OACpB,CAEqB,EAAE,SAAS,SAC9B,GAAG,KAAK,UAAU;EAChB,KAAK;EACL,QAAQ;EACR,UAAU;EACV,KAAK;CACP,CAAC,CACH;CAEA,OAAO,MAAM,KAAK,IAAI,IAAI,QAAQ,CAAC;AACrC"}
1
+ {"version":3,"file":"buildComponentFilesList.mjs","names":[],"sources":["../../../src/utils/buildComponentFilesList.ts"],"sourcesContent":["import { isAbsolute, normalize, relative, resolve, sep } from 'node:path';\nimport { TRAVERSE_PATTERN } from '@intlayer/config/defaultValues';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport fg from 'fast-glob';\n\n/**\n * Normalizes a pattern value to an array\n */\nconst normalizeToArray = <T>(value: T | T[]): T[] =>\n Array.isArray(value) ? value : [value];\n\n/**\n * Remove directories that are subdirectories of others in the list so files\n * are never scanned twice.\n * Example: ['/root', '/root/src'] → ['/root']\n */\nconst getDistinctRootDirs = (dirs: string[]): string[] => {\n const uniqueDirs = Array.from(new Set(dirs.map((dir) => resolve(dir))));\n uniqueDirs.sort((a, b) => a.length - b.length);\n\n return uniqueDirs.reduce((acc: string[], dir) => {\n const isNested = acc.some((parent) => {\n const rel = relative(parent, dir);\n\n return !rel.startsWith('..') && !isAbsolute(rel) && rel !== '';\n });\n if (!isNested) acc.push(dir);\n\n return acc;\n }, []);\n};\n\n/**\n * Returns true when the resolved path passes through a `node_modules` segment.\n * Works on both POSIX and Windows paths.\n */\nconst isInsideNodeModules = (dir: string): boolean =>\n resolve(dir).split(sep).includes('node_modules');\n\n/**\n * Default exclude patterns derived from TRAVERSE_PATTERN.\n * Extracted once so the function body can reference them without re-computing.\n */\nconst DEFAULT_EXCLUDE_PATTERNS: string[] = TRAVERSE_PATTERN.filter(\n (p): p is string => typeof p === 'string' && p.startsWith('!')\n).map((p) => p.slice(1));\n\n/**\n * Builds a deduplicated list of absolute file paths matching the given patterns.\n *\n * Handles multiple root directories (deduplicates overlapping roots), exclude\n * patterns, negation patterns embedded in `traversePattern`, and optional\n * dot-file inclusion.\n *\n * Special case: `codeDir` entries that live inside `node_modules` (e.g. a\n * design-system package installed as a workspace dependency) are scanned as\n * their own explicit roots. They are NOT collapsed into the project root so\n * the `*\\/node_modules\\/**` exclusion does not silently drop them.\n *\n * @example\n * // Single root with excludes\n * const files = buildComponentFilesList(config);\n *\n * @example\n * // Design-system package inside node_modules is still scanned\n * // intlayer.config.ts: { content: { codeDir: ['node_modules/my-ds/src'] } }\n * const files = buildComponentFilesList(config);\n */\nexport const buildComponentFilesList = (\n config: IntlayerConfig,\n excludePattern?: string[]\n): string[] => {\n const traversePattern = config.build.traversePattern;\n const compilerTransformPattern = config.compiler.transformPattern;\n const contentDeclarationPattern = config.content.fileExtensions.map(\n (ext) => `/**/*${ext}`\n );\n\n const patterns = [\n ...traversePattern,\n ...normalizeToArray(compilerTransformPattern),\n ]\n .filter((pattern) => typeof pattern === 'string')\n .filter((pattern) => !pattern.startsWith('!'))\n .map((pattern) => normalize(pattern));\n\n // User-supplied negations from traversePattern\n const userExcludes = traversePattern\n .filter(\n (pattern): pattern is string =>\n typeof pattern === 'string' && pattern.startsWith('!')\n )\n .map((pattern) => pattern.slice(1));\n\n // Full exclude list: defaults (from TRAVERSE_PATTERN) + user overrides + caller extras + content files.\n // DEFAULT_EXCLUDE_PATTERNS acts as a safety floor — if the user provides a\n // partial traversePattern that omits some defaults, they are still applied.\n const baseExcludePatterns = Array.from(\n new Set([\n ...DEFAULT_EXCLUDE_PATTERNS,\n ...userExcludes,\n ...(excludePattern ?? []),\n ...contentDeclarationPattern,\n ])\n )\n .filter((pattern): pattern is string => typeof pattern === 'string')\n .map((pattern) => normalize(pattern));\n\n // Separate codeDir entries that live inside node_modules.\n // getDistinctRootDirs would collapse them into the project root, after which\n // the **/node_modules/** ignore would silently exclude them.\n const resolvedCodeDirs = (config.content.codeDir ?? []).map((dir) =>\n resolve(dir)\n );\n const inNodeModulesCodeDirs = resolvedCodeDirs.filter(isInsideNodeModules);\n const normalCodeDirs = resolvedCodeDirs.filter(\n (dir) => !isInsideNodeModules(dir)\n );\n\n // Normal roots: project base + regular codeDir entries, deduplicated.\n const normalRoots = getDistinctRootDirs([\n config.system.baseDir,\n ...normalCodeDirs,\n ]);\n\n const normalFiles = normalRoots.flatMap((root) =>\n fg.sync(patterns, {\n cwd: root,\n ignore: baseExcludePatterns,\n absolute: true,\n dot: true, // needed for .intlayer and similar\n })\n );\n\n // node_modules codeDir roots: scanned directly from the package root.\n // **/node_modules/** is removed from the ignore list so it doesn't block\n // the scan (it is still applied inside the package for nested node_modules).\n const nodeModulesExcludePatterns = baseExcludePatterns.filter(\n (pattern) => !pattern.includes('node_modules')\n );\n\n const nodeModulesFiles = inNodeModulesCodeDirs.flatMap((dir) =>\n fg.sync(patterns, {\n cwd: dir,\n ignore: nodeModulesExcludePatterns,\n absolute: true,\n dot: false,\n })\n );\n\n return Array.from(new Set([...normalFiles, ...nodeModulesFiles]));\n};\n"],"mappings":";;;;;;;;AAQA,MAAM,oBAAuB,UAC3B,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;;;;;;AAOvC,MAAM,uBAAuB,SAA6B;CACxD,MAAM,aAAa,MAAM,KAAK,IAAI,IAAI,KAAK,KAAK,QAAQ,QAAQ,GAAG,CAAC,CAAC,CAAC;CACtE,WAAW,MAAM,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM;CAE7C,OAAO,WAAW,QAAQ,KAAe,QAAQ;EAM/C,IAAI,CALa,IAAI,MAAM,WAAW;GACpC,MAAM,MAAM,SAAS,QAAQ,GAAG;GAEhC,OAAO,CAAC,IAAI,WAAW,IAAI,KAAK,CAAC,WAAW,GAAG,KAAK,QAAQ;EAC9D,CACY,GAAG,IAAI,KAAK,GAAG;EAE3B,OAAO;CACT,GAAG,CAAC,CAAC;AACP;;;;;AAMA,MAAM,uBAAuB,QAC3B,QAAQ,GAAG,EAAE,MAAM,GAAG,EAAE,SAAS,cAAc;;;;;AAMjD,MAAM,2BAAqC,iBAAiB,QACzD,MAAmB,OAAO,MAAM,YAAY,EAAE,WAAW,GAAG,CAC/D,EAAE,KAAK,MAAM,EAAE,MAAM,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;AAuBvB,MAAa,2BACX,QACA,mBACa;CACb,MAAM,kBAAkB,OAAO,MAAM;CACrC,MAAM,2BAA2B,OAAO,SAAS;CACjD,MAAM,4BAA4B,OAAO,QAAQ,eAAe,KAC7D,QAAQ,QAAQ,KACnB;CAEA,MAAM,WAAW,CACf,GAAG,iBACH,GAAG,iBAAiB,wBAAwB,CAC9C,EACG,QAAQ,YAAY,OAAO,YAAY,QAAQ,EAC/C,QAAQ,YAAY,CAAC,QAAQ,WAAW,GAAG,CAAC,EAC5C,KAAK,YAAY,UAAU,OAAO,CAAC;CAGtC,MAAM,eAAe,gBAClB,QACE,YACC,OAAO,YAAY,YAAY,QAAQ,WAAW,GAAG,CACzD,EACC,KAAK,YAAY,QAAQ,MAAM,CAAC,CAAC;CAKpC,MAAM,sBAAsB,MAAM,KAChC,IAAI,IAAI;EACN,GAAG;EACH,GAAG;EACH,GAAI,kBAAkB,CAAC;EACvB,GAAG;CACL,CAAC,CACH,EACG,QAAQ,YAA+B,OAAO,YAAY,QAAQ,EAClE,KAAK,YAAY,UAAU,OAAO,CAAC;CAKtC,MAAM,oBAAoB,OAAO,QAAQ,WAAW,CAAC,GAAG,KAAK,QAC3D,QAAQ,GAAG,CACb;CACA,MAAM,wBAAwB,iBAAiB,OAAO,mBAAmB;CACzE,MAAM,iBAAiB,iBAAiB,QACrC,QAAQ,CAAC,oBAAoB,GAAG,CACnC;CAQA,MAAM,cALc,oBAAoB,CACtC,OAAO,OAAO,SACd,GAAG,cACL,CAE8B,EAAE,SAAS,SACvC,GAAG,KAAK,UAAU;EAChB,KAAK;EACL,QAAQ;EACR,UAAU;EACV,KAAK;CACP,CAAC,CACH;CAKA,MAAM,6BAA6B,oBAAoB,QACpD,YAAY,CAAC,QAAQ,SAAS,cAAc,CAC/C;CAEA,MAAM,mBAAmB,sBAAsB,SAAS,QACtD,GAAG,KAAK,UAAU;EAChB,KAAK;EACL,QAAQ;EACR,UAAU;EACV,KAAK;CACP,CAAC,CACH;CAEA,OAAO,MAAM,KAAK,IAAI,IAAI,CAAC,GAAG,aAAa,GAAG,gBAAgB,CAAC,CAAC;AAClE"}
@@ -6,10 +6,10 @@ import { handleUnlinkedContentDeclarationFile } from "./handleUnlinkedContentDec
6
6
  import { prepareIntlayer } from "./prepareIntlayer.mjs";
7
7
  import { writeContentDeclaration } from "./writeContentDeclaration/writeContentDeclaration.mjs";
8
8
  import { readFile } from "node:fs/promises";
9
- import { basename, dirname } from "node:path";
9
+ import { basename, dirname, resolve } from "node:path";
10
10
  import { colorize, getAppLogger } from "@intlayer/config/logger";
11
11
  import { getConfiguration, getConfigurationAndFilePath } from "@intlayer/config/node";
12
- import { existsSync } from "node:fs";
12
+ import { existsSync, watch as watch$1 } from "node:fs";
13
13
  import { clearAllCache, clearDiskCacheMemory, clearModuleCache, normalizePath } from "@intlayer/config/utils";
14
14
  import * as ANSIColor from "@intlayer/config/colors";
15
15
  import { pathToFileURL } from "node:url";
@@ -35,14 +35,25 @@ const processEvent = (task) => {
35
35
  taskQueue.push(task);
36
36
  processQueue();
37
37
  };
38
+ const STABILITY_THRESHOLD = 1e3;
39
+ const createStabilityDebounce = () => {
40
+ const pending = /* @__PURE__ */ new Map();
41
+ return (path, handler) => {
42
+ const existing = pending.get(path);
43
+ if (existing) clearTimeout(existing);
44
+ pending.set(path, setTimeout(() => {
45
+ pending.delete(path);
46
+ handler();
47
+ }, STABILITY_THRESHOLD));
48
+ };
49
+ };
38
50
  const watch = async (options) => {
39
- const { watch: chokidarWatch } = await import("chokidar");
51
+ const { subscribe } = await import("@parcel/watcher");
40
52
  const configResult = getConfigurationAndFilePath(options?.configOptions);
41
53
  const configurationFilePath = configResult.configurationFilePath;
42
54
  let configuration = options?.configuration ?? configResult.configuration;
43
55
  const appLogger = getAppLogger(configuration);
44
56
  const { watch: isWatchMode, fileExtensions, contentDir, excludedPath } = configuration.content;
45
- const pathsToWatch = [...contentDir.map((dir) => normalizePath(dir)).filter(existsSync), ...configurationFilePath ? [configurationFilePath] : []];
46
57
  if (!configuration.content.watch) return;
47
58
  appLogger("Watching Intlayer content declarations");
48
59
  if (configuration.build.optimize === true) appLogger([
@@ -57,50 +68,51 @@ const watch = async (options) => {
57
68
  const excludedSegments = excludedPath.map((segment) => segment.replace(/^\*\*\//, "").replace(/\/\*\*$/, ""));
58
69
  const normalizedConfigPath = configurationFilePath ? normalizePath(configurationFilePath) : null;
59
70
  const { mainDir, baseDir } = configuration.system;
71
+ const normalizedMainDir = normalizePath(mainDir);
60
72
  const normalizedIntlayerDir = normalizePath(dirname(mainDir));
61
- if (existsSync(mainDir)) chokidarWatch(mainDir, {
62
- persistent: isWatchMode,
63
- ignoreInitial: true,
64
- depth: 0
65
- }).on("change", async (filePath) => {
66
- if (isProcessing) return;
67
- processEvent(async () => {
68
- clearModuleCache(filePath);
69
- try {
70
- await import(`${pathToFileURL(filePath).href}?update=${Date.now()}`);
71
- } catch {
72
- appLogger(`Entry point ${basename(filePath)} failed to load, running clean rebuild...`, { level: "warn" });
73
- await prepareIntlayer(configuration, {
74
- clean: true,
75
- forceRun: true
73
+ const subscriptions = [];
74
+ const fsWatchers = [];
75
+ const scheduleStable = createStabilityDebounce();
76
+ if (existsSync(mainDir)) {
77
+ const mainDirSub = await subscribe(normalizedMainDir, (err, events) => {
78
+ if (err || isProcessing) return;
79
+ for (const event of events) {
80
+ const rel = normalizePath(event.path).slice(normalizedMainDir.length + 1);
81
+ if (!rel || rel.includes("/")) continue;
82
+ if (rel.endsWith(".tmp")) continue;
83
+ if (event.type === "update") processEvent(async () => {
84
+ clearModuleCache(event.path);
85
+ try {
86
+ await import(`${pathToFileURL(event.path).href}?update=${Date.now()}`);
87
+ } catch {
88
+ appLogger(`Entry point ${basename(event.path)} failed to load, running clean rebuild...`, { level: "warn" });
89
+ await prepareIntlayer(configuration, {
90
+ clean: true,
91
+ forceRun: true
92
+ });
93
+ }
94
+ });
95
+ else if (event.type === "delete") processEvent(async () => {
96
+ appLogger([
97
+ "Entry point",
98
+ formatPath(basename(event.path)),
99
+ "was removed, running clean rebuild..."
100
+ ], { level: "warn" });
101
+ await prepareIntlayer(configuration, {
102
+ clean: true,
103
+ forceRun: true
104
+ });
76
105
  });
77
106
  }
78
107
  });
79
- }).on("unlink", async (filePath) => {
80
- if (isProcessing) return;
81
- processEvent(async () => {
82
- appLogger([
83
- "Entry point",
84
- formatPath(basename(filePath)),
85
- "was removed, running clean rebuild..."
86
- ], { level: "warn" });
87
- await prepareIntlayer(configuration, {
88
- clean: true,
89
- forceRun: true
90
- });
91
- });
92
- });
93
- chokidarWatch(baseDir, {
94
- persistent: isWatchMode,
95
- ignoreInitial: true,
96
- depth: 0,
97
- ignored: (filePath) => {
98
- const path = normalizePath(filePath);
99
- return path !== normalizePath(baseDir) && path !== normalizedIntlayerDir;
100
- }
101
- }).on("unlinkDir", async (dirPath) => {
102
- if (isProcessing) return;
103
- if (normalizePath(dirPath) === normalizedIntlayerDir) {
108
+ subscriptions.push(mainDirSub);
109
+ }
110
+ const intlayerDirName = basename(normalizedIntlayerDir);
111
+ const fsDirWatcher = watch$1(baseDir, { persistent: isWatchMode }, (eventType, filename) => {
112
+ if (isProcessing || !filename) return;
113
+ if (filename !== intlayerDirName) return;
114
+ if (normalizePath(resolve(baseDir, filename)) !== normalizedIntlayerDir) return;
115
+ if (eventType === "rename" && !existsSync(normalizedIntlayerDir)) {
104
116
  appLogger([formatPath(".intlayer"), "directory removed, running clean rebuild..."]);
105
117
  processEvent(() => prepareIntlayer(configuration, {
106
118
  clean: true,
@@ -108,81 +120,100 @@ const watch = async (options) => {
108
120
  }));
109
121
  }
110
122
  });
111
- return chokidarWatch(pathsToWatch, {
112
- persistent: isWatchMode,
113
- ignoreInitial: true,
114
- awaitWriteFinish: {
115
- stabilityThreshold: 1e3,
116
- pollInterval: 100
117
- },
118
- ignored: (filePath, stats) => {
119
- const path = normalizePath(filePath);
120
- if (normalizedConfigPath && path === normalizedConfigPath) return false;
121
- if (excludedSegments.some((segment) => path.includes(`/${segment}`))) return true;
122
- if (stats?.isFile()) return !fileExtensions.some((extension) => path.endsWith(extension));
123
- return false;
124
- },
125
- ...options
126
- }).on("add", async (filePath) => {
127
- const fileName = basename(filePath);
128
- let isMove = false;
129
- let matchedOldPath;
130
- for (const [oldPath] of pendingUnlinks) if (basename(oldPath) === fileName) {
131
- matchedOldPath = oldPath;
132
- break;
123
+ fsWatchers.push(fsDirWatcher);
124
+ const ignorePatterns = excludedSegments.map((s) => `**/${s}/**`);
125
+ const contentDirs = contentDir.map((dir) => normalizePath(dir)).filter(existsSync);
126
+ const dirsToWatch = new Set(contentDirs);
127
+ if (normalizedConfigPath) dirsToWatch.add(normalizePath(dirname(normalizedConfigPath)));
128
+ const contentHandler = (err, events) => {
129
+ if (err) {
130
+ appLogger(`Watcher error: ${err}`, { level: "error" });
131
+ appLogger("Restarting watcher");
132
+ prepareIntlayer(configuration);
133
+ return;
133
134
  }
134
- if (!matchedOldPath && pendingUnlinks.size === 1) matchedOldPath = pendingUnlinks.keys().next().value;
135
- if (matchedOldPath) {
136
- const pending = pendingUnlinks.get(matchedOldPath);
137
- if (pending) {
138
- clearTimeout(pending.timer);
139
- pendingUnlinks.delete(matchedOldPath);
135
+ for (const event of events) {
136
+ const filePath = event.path;
137
+ const path = normalizePath(filePath);
138
+ const isConfigFile = normalizedConfigPath && path === normalizedConfigPath;
139
+ if (!isConfigFile) {
140
+ if (!contentDirs.some((d) => path.startsWith(`${d}/`) || path === d)) continue;
141
+ if (excludedSegments.some((segment) => path.includes(`/${segment}`))) continue;
142
+ if (!fileExtensions.some((extension) => path.endsWith(extension))) continue;
140
143
  }
141
- isMove = true;
142
- appLogger(`File moved from ${matchedOldPath} to ${filePath}`);
143
- }
144
- processEvent(async () => {
145
- if (isMove && matchedOldPath) await handleContentDeclarationFileMoved(matchedOldPath, filePath, configuration);
146
- else {
147
- if (await readFile(filePath, "utf-8") === "") {
148
- const extensionPattern = fileExtensions.map((ext) => ext.replace(/\./g, "\\.")).join("|");
149
- await writeContentDeclaration({
150
- key: fileName.replace(new RegExp(`(${extensionPattern})$`), ""),
151
- content: {},
152
- filePath
153
- }, configuration);
144
+ if (event.type === "create") {
145
+ const fileName = basename(filePath);
146
+ let isMove = false;
147
+ let matchedOldPath;
148
+ for (const [oldPath] of pendingUnlinks) if (basename(oldPath) === fileName) {
149
+ matchedOldPath = oldPath;
150
+ break;
151
+ }
152
+ if (!matchedOldPath && pendingUnlinks.size === 1) matchedOldPath = pendingUnlinks.keys().next().value;
153
+ if (matchedOldPath) {
154
+ const pending = pendingUnlinks.get(matchedOldPath);
155
+ if (pending) {
156
+ clearTimeout(pending.timer);
157
+ pendingUnlinks.delete(matchedOldPath);
158
+ }
159
+ isMove = true;
160
+ appLogger(`File moved from ${matchedOldPath} to ${filePath}`);
154
161
  }
155
- await handleAdditionalContentDeclarationFile(filePath, configuration);
162
+ if (isMove && matchedOldPath) processEvent(async () => {
163
+ await handleContentDeclarationFileMoved(matchedOldPath, filePath, configuration);
164
+ });
165
+ else scheduleStable(path, () => {
166
+ processEvent(async () => {
167
+ if (await readFile(filePath, "utf-8") === "") {
168
+ const extensionPattern = fileExtensions.map((ext) => ext.replace(/\./g, "\\.")).join("|");
169
+ await writeContentDeclaration({
170
+ key: fileName.replace(new RegExp(`(${extensionPattern})$`), ""),
171
+ content: {},
172
+ filePath
173
+ }, configuration);
174
+ }
175
+ await handleAdditionalContentDeclarationFile(filePath, configuration);
176
+ });
177
+ });
178
+ } else if (event.type === "update") scheduleStable(path, () => {
179
+ processEvent(async () => {
180
+ if (isConfigFile) {
181
+ appLogger("Configuration file changed, repreparing Intlayer");
182
+ clearModuleCache(filePath);
183
+ clearAllCache();
184
+ const { configuration: newConfiguration } = getConfigurationAndFilePath(options?.configOptions);
185
+ configuration = options?.configuration ?? newConfiguration;
186
+ await prepareIntlayer(configuration, { clean: false });
187
+ } else {
188
+ clearModuleCache(filePath);
189
+ clearAllCache();
190
+ clearDiskCacheMemory();
191
+ await handleContentDeclarationFileChange(filePath, configuration);
192
+ }
193
+ });
194
+ });
195
+ else if (event.type === "delete") {
196
+ const timer = setTimeout(async () => {
197
+ pendingUnlinks.delete(filePath);
198
+ processEvent(async () => handleUnlinkedContentDeclarationFile(filePath, configuration));
199
+ }, 200);
200
+ pendingUnlinks.set(filePath, {
201
+ timer,
202
+ oldPath: filePath
203
+ });
156
204
  }
157
- });
158
- }).on("change", async (filePath) => processEvent(async () => {
159
- if (configurationFilePath && filePath === configurationFilePath) {
160
- appLogger("Configuration file changed, repreparing Intlayer");
161
- clearModuleCache(configurationFilePath);
162
- clearAllCache();
163
- const { configuration: newConfiguration } = getConfigurationAndFilePath(options?.configOptions);
164
- configuration = options?.configuration ?? newConfiguration;
165
- await prepareIntlayer(configuration, { clean: false });
166
- } else {
167
- clearModuleCache(filePath);
168
- clearAllCache();
169
- clearDiskCacheMemory();
170
- await handleContentDeclarationFileChange(filePath, configuration);
171
205
  }
172
- })).on("unlink", async (filePath) => {
173
- const timer = setTimeout(async () => {
174
- pendingUnlinks.delete(filePath);
175
- processEvent(async () => handleUnlinkedContentDeclarationFile(filePath, configuration));
176
- }, 200);
177
- pendingUnlinks.set(filePath, {
178
- timer,
179
- oldPath: filePath
206
+ };
207
+ for (const dir of dirsToWatch) {
208
+ const sub = await subscribe(dir, contentHandler, { ignore: ignorePatterns });
209
+ subscriptions.push(sub);
210
+ }
211
+ return { unsubscribe: async () => {
212
+ await Promise.all(subscriptions.map((subscription) => subscription.unsubscribe()));
213
+ fsWatchers.forEach((watcher) => {
214
+ watcher.close();
180
215
  });
181
- }).on("error", async (error) => {
182
- appLogger(`Watcher error: ${error}`, { level: "error" });
183
- appLogger("Restarting watcher");
184
- await prepareIntlayer(configuration);
185
- });
216
+ } };
186
217
  };
187
218
  const buildAndWatchIntlayer = async (options) => {
188
219
  const { skipPrepare, ...rest } = options ?? {};
@@ -1 +1 @@
1
- {"version":3,"file":"watcher.mjs","names":[],"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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"}
1
+ {"version":3,"file":"watcher.mjs","names":["fsWatch"],"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 const fsWatchers: import('node:fs').FSWatcher[] = [];\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 fsWatchers.push(fsDirWatcher);\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 {\n unsubscribe: async () => {\n await Promise.all(\n subscriptions.map((subscription) => subscription.unsubscribe())\n );\n\n fsWatchers.forEach((watcher) => {\n watcher.close();\n });\n },\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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"}
@@ -5,20 +5,22 @@ import { IntlayerConfig } from "@intlayer/types/config";
5
5
  * Builds a deduplicated list of absolute file paths matching the given patterns.
6
6
  *
7
7
  * Handles multiple root directories (deduplicates overlapping roots), exclude
8
- * patterns, negation patterns embedded in `transformPattern`, and optional
8
+ * patterns, negation patterns embedded in `traversePattern`, and optional
9
9
  * dot-file inclusion.
10
10
  *
11
+ * Special case: `codeDir` entries that live inside `node_modules` (e.g. a
12
+ * design-system package installed as a workspace dependency) are scanned as
13
+ * their own explicit roots. They are NOT collapsed into the project root so
14
+ * the `*\/node_modules\/**` exclusion does not silently drop them.
15
+ *
11
16
  * @example
12
17
  * // Single root with excludes
13
- * const files = buildComponentFilesList({
14
- * transformPattern: 'src/**\/*.{ts,tsx}',
15
- * excludePattern: ['**\/node_modules\/**'],
16
- * baseDir: '/path/to/project',
17
- * });
18
+ * const files = buildComponentFilesList(config);
18
19
  *
19
20
  * @example
20
- * // Multiple roots (e.g. baseDir + codeDir), dot files included
21
- * const files = buildComponentFilesList(config, ['**\/node_modules\/**']);
21
+ * // Design-system package inside node_modules is still scanned
22
+ * // intlayer.config.ts: { content: { codeDir: ['node_modules/my-ds/src'] } }
23
+ * const files = buildComponentFilesList(config);
22
24
  */
23
25
  declare const buildComponentFilesList: (config: IntlayerConfig, excludePattern?: string[]) => string[];
24
26
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"buildComponentFilesList.d.ts","names":[],"sources":["../../../src/utils/buildComponentFilesList.ts"],"mappings":";;;;;AAkDA;;;;;;;;AAE2B;;;;;;;;;cAFd,uBAAA,GACX,MAAA,EAAQ,cAAc,EACtB,cAAA"}
1
+ {"version":3,"file":"buildComponentFilesList.d.ts","names":[],"sources":["../../../src/utils/buildComponentFilesList.ts"],"mappings":";;;;;AAoEA;;;;;;;;AAE2B;;;;;;;;;;;cAFd,uBAAA,GACX,MAAA,EAAQ,cAAc,EACtB,cAAA"}
@@ -1,14 +1,16 @@
1
1
  import { IntlayerConfig } from "@intlayer/types/config";
2
2
  import { GetConfigurationOptions } from "@intlayer/config/node";
3
- import { ChokidarOptions } from "chokidar";
4
3
 
5
4
  //#region src/watcher.d.ts
6
- type WatchOptions = ChokidarOptions & {
5
+ type WatchOptions = {
7
6
  configuration?: IntlayerConfig;
8
7
  configOptions?: GetConfigurationOptions;
9
8
  skipPrepare?: boolean;
9
+ persistent?: boolean;
10
10
  };
11
- declare const watch: (options?: WatchOptions) => Promise<import("chokidar").FSWatcher>;
11
+ declare const watch: (options?: WatchOptions) => Promise<{
12
+ unsubscribe: () => Promise<void>;
13
+ }>;
12
14
  declare const buildAndWatchIntlayer: (options?: WatchOptions) => Promise<void>;
13
15
  //#endregion
14
16
  export { buildAndWatchIntlayer, watch };
@@ -1 +1 @@
1
- {"version":3,"file":"watcher.d.ts","names":[],"sources":["../../src/watcher.ts"],"mappings":";;;;;KAwDK,YAAA,GAAe,eAAA;EAClB,aAAA,GAAgB,cAAA;EAChB,aAAA,GAAgB,uBAAA;EAChB,WAAA;AAAA;AAAA,cAIW,KAAA,GAAe,OAAA,GAAU,YAAA,KAAY,OAAA,oBAAA,SAAA;AAAA,cAyQrC,qBAAA,GAA+B,OAAA,GAAU,YAAA,KAAY,OAAA"}
1
+ {"version":3,"file":"watcher.d.ts","names":[],"sources":["../../src/watcher.ts"],"mappings":";;;;KAuDK,YAAA;EACH,aAAA,GAAgB,cAAA;EAChB,aAAA,GAAgB,uBAAuB;EACvC,WAAA;EACA,UAAA;AAAA;AAAA,cAsBW,KAAA,GAAe,OAAA,GAAU,YAAA,KAAY,OAAA;;;cAkTrC,qBAAA,GAA+B,OAAA,GAAU,YAAA,KAAY,OAAA"}
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@intlayer/chokidar",
3
- "version": "8.11.3",
3
+ "version": "8.12.0",
4
4
  "private": false,
5
- "description": "Uses chokidar to scan and build Intlayer declaration files into dictionaries based on Intlayer configuration.",
5
+ "description": "Scans and builds Intlayer declaration files into dictionaries based on Intlayer configuration.",
6
6
  "keywords": [
7
7
  "intlayer",
8
- "chokidar",
8
+ "watcher",
9
9
  "application",
10
10
  "transpile",
11
11
  "typescript",
@@ -109,14 +109,14 @@
109
109
  "typecheck": "tsc --noEmit --project tsconfig.types.json"
110
110
  },
111
111
  "dependencies": {
112
- "@intlayer/api": "8.11.3",
113
- "@intlayer/config": "8.11.3",
114
- "@intlayer/core": "8.11.3",
115
- "@intlayer/dictionaries-entry": "8.11.3",
116
- "@intlayer/remote-dictionaries-entry": "8.11.3",
117
- "@intlayer/types": "8.11.3",
118
- "@intlayer/unmerged-dictionaries-entry": "8.11.3",
119
- "chokidar": "5.0.0",
112
+ "@intlayer/api": "8.12.0",
113
+ "@intlayer/config": "8.12.0",
114
+ "@intlayer/core": "8.12.0",
115
+ "@intlayer/dictionaries-entry": "8.12.0",
116
+ "@intlayer/remote-dictionaries-entry": "8.12.0",
117
+ "@intlayer/types": "8.12.0",
118
+ "@intlayer/unmerged-dictionaries-entry": "8.12.0",
119
+ "@parcel/watcher": "2.5.6",
120
120
  "defu": "6.1.7",
121
121
  "fast-glob": "3.3.3",
122
122
  "recast": "^0.23.11",
@@ -131,7 +131,7 @@
131
131
  "rimraf": "6.1.3",
132
132
  "tsdown": "0.22.1",
133
133
  "typescript": "6.0.3",
134
- "vitest": "4.1.7",
134
+ "vitest": "4.1.8",
135
135
  "zod": "4.4.3"
136
136
  },
137
137
  "engines": {