@intlayer/chokidar 8.11.2 → 8.12.0-canary.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.
@@ -0,0 +1,55 @@
1
+ import path from "node:path";
2
+ import { promises } from "node:fs";
3
+
4
+ //#region src/installLSP.ts
5
+ const VSCODE_DIR = ".vscode";
6
+ const SETTINGS_FILENAME = "settings.json";
7
+ const LSP_COMMAND_KEY = "intlayer.languageServer.command";
8
+ const LSP_ARGS_KEY = "intlayer.languageServer.args";
9
+ const LSP_COMMAND_VALUE = "npx";
10
+ const LSP_ARGS_VALUE = ["@intlayer/lsp"];
11
+ /**
12
+ * Writes the Intlayer LSP configuration to `.vscode/settings.json`.
13
+ * Creates the file (and the `.vscode/` directory) if they don't exist.
14
+ * Does not overwrite keys that are already present.
15
+ *
16
+ * Returns a human-readable summary of what was done plus next-step instructions.
17
+ */
18
+ const installLSP = async (projectRoot) => {
19
+ const settingsPath = path.join(projectRoot, VSCODE_DIR, SETTINGS_FILENAME);
20
+ await promises.mkdir(path.dirname(settingsPath), { recursive: true });
21
+ let settings = {};
22
+ try {
23
+ const raw = await promises.readFile(settingsPath, "utf-8");
24
+ settings = JSON.parse(raw);
25
+ } catch {}
26
+ const updated = [];
27
+ if (!settings[LSP_COMMAND_KEY]) {
28
+ settings[LSP_COMMAND_KEY] = LSP_COMMAND_VALUE;
29
+ updated.push(LSP_COMMAND_KEY);
30
+ }
31
+ if (!settings[LSP_ARGS_KEY]) {
32
+ settings[LSP_ARGS_KEY] = LSP_ARGS_VALUE;
33
+ updated.push(LSP_ARGS_KEY);
34
+ }
35
+ await promises.writeFile(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
36
+ return [
37
+ updated.length > 0 ? `Added to ${settingsPath}:\n${updated.map((k) => ` "${k}"`).join("\n")}` : `${settingsPath} already contains the LSP configuration — no changes made.`,
38
+ "",
39
+ "Next steps:",
40
+ "",
41
+ "1. Install the language server binary (required once):",
42
+ " npm install -g @intlayer/lsp",
43
+ "",
44
+ "2. (Optional) Register the Intlayer Claude Code plugin and enable Go-to-Definition inside Claude Code:",
45
+ " claude plugin marketplace add intlayer@github:aymericzip/intlayer",
46
+ " claude plugin install intlayer-lsp@intlayer",
47
+ " claude plugin enable intlayer-lsp@intlayer",
48
+ "",
49
+ "3. Reload your editor window so the new settings take effect."
50
+ ].join("\n");
51
+ };
52
+
53
+ //#endregion
54
+ export { installLSP };
55
+ //# sourceMappingURL=installLSP.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"installLSP.mjs","names":["fs"],"sources":["../../src/installLSP.ts"],"sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\n\nconst VSCODE_DIR = '.vscode';\nconst SETTINGS_FILENAME = 'settings.json';\n\nconst LSP_COMMAND_KEY = 'intlayer.languageServer.command';\nconst LSP_ARGS_KEY = 'intlayer.languageServer.args';\nconst LSP_COMMAND_VALUE = 'npx';\nconst LSP_ARGS_VALUE = ['@intlayer/lsp'];\n\n/**\n * Writes the Intlayer LSP configuration to `.vscode/settings.json`.\n * Creates the file (and the `.vscode/` directory) if they don't exist.\n * Does not overwrite keys that are already present.\n *\n * Returns a human-readable summary of what was done plus next-step instructions.\n */\nexport const installLSP = async (projectRoot: string): Promise<string> => {\n const settingsPath = path.join(projectRoot, VSCODE_DIR, SETTINGS_FILENAME);\n\n await fs.mkdir(path.dirname(settingsPath), { recursive: true });\n\n let settings: Record<string, unknown> = {};\n try {\n const raw = await fs.readFile(settingsPath, 'utf-8');\n settings = JSON.parse(raw);\n } catch {\n // File does not exist or is invalid JSON — start from an empty object.\n }\n\n const updated: string[] = [];\n\n if (!settings[LSP_COMMAND_KEY]) {\n settings[LSP_COMMAND_KEY] = LSP_COMMAND_VALUE;\n updated.push(LSP_COMMAND_KEY);\n }\n\n if (!settings[LSP_ARGS_KEY]) {\n settings[LSP_ARGS_KEY] = LSP_ARGS_VALUE;\n updated.push(LSP_ARGS_KEY);\n }\n\n await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2), 'utf-8');\n\n const configSummary =\n updated.length > 0\n ? `Added to ${settingsPath}:\\n${updated.map((k) => ` \"${k}\"`).join('\\n')}`\n : `${settingsPath} already contains the LSP configuration — no changes made.`;\n\n return [\n configSummary,\n '',\n 'Next steps:',\n '',\n '1. Install the language server binary (required once):',\n ' npm install -g @intlayer/lsp',\n '',\n '2. (Optional) Register the Intlayer Claude Code plugin and enable Go-to-Definition inside Claude Code:',\n ' claude plugin marketplace add intlayer@github:aymericzip/intlayer',\n ' claude plugin install intlayer-lsp@intlayer',\n ' claude plugin enable intlayer-lsp@intlayer',\n '',\n '3. Reload your editor window so the new settings take effect.',\n ].join('\\n');\n};\n"],"mappings":";;;;AAGA,MAAM,aAAa;AACnB,MAAM,oBAAoB;AAE1B,MAAM,kBAAkB;AACxB,MAAM,eAAe;AACrB,MAAM,oBAAoB;AAC1B,MAAM,iBAAiB,CAAC,eAAe;;;;;;;;AASvC,MAAa,aAAa,OAAO,gBAAyC;CACxE,MAAM,eAAe,KAAK,KAAK,aAAa,YAAY,iBAAiB;CAEzE,MAAMA,SAAG,MAAM,KAAK,QAAQ,YAAY,GAAG,EAAE,WAAW,KAAK,CAAC;CAE9D,IAAI,WAAoC,CAAC;CACzC,IAAI;EACF,MAAM,MAAM,MAAMA,SAAG,SAAS,cAAc,OAAO;EACnD,WAAW,KAAK,MAAM,GAAG;CAC3B,QAAQ,CAER;CAEA,MAAM,UAAoB,CAAC;CAE3B,IAAI,CAAC,SAAS,kBAAkB;EAC9B,SAAS,mBAAmB;EAC5B,QAAQ,KAAK,eAAe;CAC9B;CAEA,IAAI,CAAC,SAAS,eAAe;EAC3B,SAAS,gBAAgB;EACzB,QAAQ,KAAK,YAAY;CAC3B;CAEA,MAAMA,SAAG,UAAU,cAAc,KAAK,UAAU,UAAU,MAAM,CAAC,GAAG,OAAO;CAO3E,OAAO;EAJL,QAAQ,SAAS,IACb,YAAY,aAAa,KAAK,QAAQ,KAAK,MAAM,MAAM,EAAE,EAAE,EAAE,KAAK,IAAI,MACtE,GAAG,aAAa;EAIpB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,EAAE,KAAK,IAAI;AACb"}
@@ -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":";;;;;;;;;;;;;;;;;AA4BA,MAAM,iCAAiB,IAAI,IAGzB;AAGF,MAAM,YAAqC,CAAC;AAC5C,IAAI,eAAe;AAEnB,MAAM,eAAe,YAAY;CAC/B,IAAI,cAAc;CAClB,eAAe;CACf,OAAO,UAAU,SAAS,GAAG;EAC3B,MAAM,OAAO,UAAU,MAAM;EAC7B,IAAI;GACF,MAAM,KAAK;EACb,SAAS,OAAO;GACd,QAAQ,MAAM,KAAK;EACrB;CACF;CACA,eAAe;AACjB;AAEA,MAAM,gBAAgB,SAA8B;CAClD,UAAU,KAAK,IAAI;CACnB,aAAa;AACf;AASA,MAAa,QAAQ,OAAO,YAA2B;CACrD,MAAM,EAAE,OAAO,kBAAkB,MAAM,OAAO;CAC9C,MAAM,eAAe,4BAA4B,SAAS,aAAa;CACvE,MAAM,wBAAwB,aAAa;CAC3C,IAAI,gBACF,SAAS,iBAAiB,aAAa;CACzC,MAAM,YAAY,aAAa,aAAa;CAE5C,MAAM,EACJ,OAAO,aACP,gBACA,YACA,iBACE,cAAc;CAGlB,MAAM,eAAe,CACnB,GAAG,WAAW,KAAK,QAAQ,cAAc,GAAG,CAAC,EAAE,OAAO,UAAU,GAChE,GAAI,wBAAwB,CAAC,qBAAqB,IAAI,CAAC,CACzD;CAEA,IAAI,CAAC,cAAc,QAAQ,OAAO;CAElC,UAAU,wCAAwC;CAElD,IAAI,cAAc,MAAM,aAAa,MACnC,UACE;EACE,mCAAmC,SAAS,QAAQ,UAAU,IAAI,EAAE;EACpE;EACA;EACA,SAAS,qBAAqB,UAAU,IAAI;EAC5C;EACA,SAAS,aAAa,UAAU,IAAI;EACpC;CACF,GACA,EACE,OAAO,OACT,CACF;CAIF,MAAM,mBAAmB,aAAa,KAAK,YACzC,QAAQ,QAAQ,WAAW,EAAE,EAAE,QAAQ,WAAW,EAAE,CACtD;CAEA,MAAM,uBAAuB,wBACzB,cAAc,qBAAqB,IACnC;CAEJ,MAAM,EAAE,SAAS,YAAY,cAAc;CAC3C,MAAM,wBAAwB,cAAc,QAAQ,OAAO,CAAC;CAG5D,IAAI,WAAW,OAAO,GACpB,cAAc,SAAS;EACrB,YAAY;EACZ,eAAe;EACf,OAAO;CACT,CAAC,EACE,GAAG,UAAU,OAAO,aAAa;EAChC,IAAI,cAAc;EAElB,aAAa,YAAY;GACvB,iBAAiB,QAAQ;GACzB,IAAI;IAKF,MAAM,OAAO,GAHG,cAAc,QAAQ,EAAE,KAGhB,UAAU,KAAK,IAAI;GAC7C,QAAQ;IACN,UACE,eAAe,SAAS,QAAQ,EAAE,4CAClC,EAAE,OAAO,OAAO,CAClB;IACA,MAAM,gBAAgB,eAAe;KACnC,OAAO;KACP,UAAU;IACZ,CAAC;GACH;EACF,CAAC;CACH,CAAC,EACA,GAAG,UAAU,OAAO,aAAa;EAChC,IAAI,cAAc;EAElB,aAAa,YAAY;GACvB,UACE;IACE;IACA,WAAW,SAAS,QAAQ,CAAC;IAC7B;GACF,GACA,EAAE,OAAO,OAAO,CAClB;GACA,MAAM,gBAAgB,eAAe;IAAE,OAAO;IAAM,UAAU;GAAK,CAAC;EACtE,CAAC;CACH,CAAC;CAIL,cAAc,SAAS;EACrB,YAAY;EACZ,eAAe;EACf,OAAO;EACP,UAAU,aAAqB;GAC7B,MAAM,OAAO,cAAc,QAAQ;GACnC,OAAO,SAAS,cAAc,OAAO,KAAK,SAAS;EACrD;CACF,CAAC,EAAE,GAAG,aAAa,OAAO,YAAY;EACpC,IAAI,cAAc;EAElB,IAAI,cAAc,OAAO,MAAM,uBAAuB;GACpD,UAAU,CACR,WAAW,WAAW,GACtB,6CACF,CAAC;GAED,mBACE,gBAAgB,eAAe;IAAE,OAAO;IAAM,UAAU;GAAK,CAAC,CAChE;EACF;CACF,CAAC;CAED,OAAO,cAAc,cAAc;EACjC,YAAY;EACZ,eAAe;EACf,kBAAkB;GAChB,oBAAoB;GACpB,cAAc;EAChB;EACA,UAAU,UAAkB,UAAoC;GAC9D,MAAM,OAAO,cAAc,QAAQ;GAEnC,IAAI,wBAAwB,SAAS,sBAAsB,OAAO;GAElE,IAAI,iBAAiB,MAAM,YAAY,KAAK,SAAS,IAAI,SAAS,CAAC,GACjE,OAAO;GAET,IAAI,OAAO,OAAO,GAChB,OAAO,CAAC,eAAe,MAAM,cAAc,KAAK,SAAS,SAAS,CAAC;GAGrE,OAAO;EACT;EACA,GAAG;CACL,CAAC,EACE,GAAG,OAAO,OAAO,aAAa;EAC7B,MAAM,WAAW,SAAS,QAAQ;EAClC,IAAI,SAAS;EAMb,IAAI;EAGJ,KAAK,MAAM,CAAC,YAAY,gBACtB,IAAI,SAAS,OAAO,MAAM,UAAU;GAClC,iBAAiB;GACjB;EACF;EAIF,IAAI,CAAC,kBAAkB,eAAe,SAAS,GAC7C,iBAAiB,eAAe,KAAK,EAAE,KAAK,EAAE;EAGhD,IAAI,gBAAgB;GAElB,MAAM,UAAU,eAAe,IAAI,cAAc;GACjD,IAAI,SAAS;IACX,aAAa,QAAQ,KAAK;IAC1B,eAAe,OAAO,cAAc;GACtC;GAEA,SAAS;GACT,UAAU,mBAAmB,eAAe,MAAM,UAAU;EAC9D;EAEA,aAAa,YAAY;GACvB,IAAI,UAAU,gBACZ,MAAM,kCACJ,gBACA,UACA,aACF;QACK;IAKL,IAHgB,MADU,SAAS,UAAU,OAAO,MACpB,IAGnB;KACX,MAAM,mBAAmB,eACtB,KAAK,QAAQ,IAAI,QAAQ,OAAO,KAAK,CAAC,EACtC,KAAK,GAAG;KAMX,MAAM,wBACJ;MACE,KAPS,SAAS,QACpB,IAAI,OAAO,IAAI,iBAAiB,GAAG,GACnC,EAKU;MACR,SAAS,CAAC;MACV;KACF,GACA,aACF;IACF;IAEA,MAAM,uCAAuC,UAAU,aAAa;GACtE;EACF,CAAC;CACH,CAAC,EACA,GAAG,UAAU,OAAO,aACnB,aAAa,YAAY;EACvB,IAAI,yBAAyB,aAAa,uBAAuB;GAC/D,UAAU,kDAAkD;GAE5D,iBAAiB,qBAAqB;GACtC,cAAc;GAEd,MAAM,EAAE,eAAe,qBACrB,4BAA4B,SAAS,aAAa;GAEpD,gBAAgB,SAAS,iBAAiB;GAE1C,MAAM,gBAAgB,eAAe,EAAE,OAAO,MAAM,CAAC;EACvD,OAAO;GAEL,iBAAiB,QAAQ;GAEzB,cAAc;GACd,qBAAqB;GACrB,MAAM,mCAAmC,UAAU,aAAa;EAClE;CACF,CAAC,CACH,EACC,GAAG,UAAU,OAAO,aAAa;EAEhC,MAAM,QAAQ,WAAW,YAAY;GAEnC,eAAe,OAAO,QAAQ;GAC9B,aAAa,YACX,qCAAqC,UAAU,aAAa,CAC9D;EACF,GAAG,GAAG;EAEN,eAAe,IAAI,UAAU;GAAE;GAAO,SAAS;EAAS,CAAC;CAC3D,CAAC,EACA,GAAG,SAAS,OAAO,UAAU;EAC5B,UAAU,kBAAkB,SAAS,EACnC,OAAO,QACT,CAAC;EAED,UAAU,oBAAoB;EAE9B,MAAM,gBAAgB,aAAa;CACrC,CAAC;AACL;AAEA,MAAa,wBAAwB,OAAO,YAA2B;CACrE,MAAM,EAAE,aAAa,GAAG,SAAS,WAAW,CAAC;CAC7C,MAAM,gBACJ,SAAS,iBAAiB,iBAAiB,SAAS,aAAa;CAEnE,IAAI,CAAC,aACH,MAAM,gBAAgB,eAAe,EAAE,UAAU,KAAK,CAAC;CAQzD,IAAI,SAAS,YACX,MAAM,MAAM;EAAE,GAAG;EAAM;CAAc,CAAC;AAE1C"}
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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"}
@@ -7,10 +7,11 @@ import { writeJSFile } from "./writeContentDeclaration/writeJSFile.js";
7
7
  import { detectFormatCommand } from "./detectFormatCommand.js";
8
8
  import { getContentDeclarationFileTemplate } from "./getContentDeclarationFileTemplate/getContentDeclarationFileTemplate.js";
9
9
  import { InitOptions, initIntlayer } from "./init/index.js";
10
+ import { installLSP } from "./installLSP.js";
10
11
  import { PLATFORMS, PLATFORMS_METADATA, Platform, PlatformMetadata, SKILLS, SKILLS_METADATA, Skill, getInitialSkills, installSkills } from "./installSkills/index.js";
11
12
  import { MCPTransport, installMCP } from "./installMCP/installMCP.js";
12
13
  import { listDictionaries, listDictionariesWithStats } from "./listDictionariesPath.js";
13
14
  import { DiffMode, ListGitFilesOptions, ListGitLinesOptions, listGitFiles, listGitLines } from "./listGitFiles.js";
14
15
  import { ListProjectsOptions, listProjects } from "./listProjects.js";
15
16
  import { logConfigDetails } from "./logConfigDetails.js";
16
- export { DictionaryStatus, DiffMode, InitOptions, ListGitFilesOptions, ListGitLinesOptions, ListProjectsOptions, MCPTransport, PLATFORMS, PLATFORMS_METADATA, Platform, PlatformMetadata, SKILLS, SKILLS_METADATA, Skill, detectExportedComponentName, detectFormatCommand, getContentDeclarationFileTemplate, getInitialSkills, initIntlayer, installMCP, installSkills, listDictionaries, listDictionariesWithStats, listGitFiles, listGitLines, listProjects, logConfigDetails, prepareIntlayer, transformJSFile, writeContentDeclaration, writeJSFile };
17
+ export { DictionaryStatus, DiffMode, InitOptions, ListGitFilesOptions, ListGitLinesOptions, ListProjectsOptions, MCPTransport, PLATFORMS, PLATFORMS_METADATA, Platform, PlatformMetadata, SKILLS, SKILLS_METADATA, Skill, detectExportedComponentName, detectFormatCommand, getContentDeclarationFileTemplate, getInitialSkills, initIntlayer, installLSP, installMCP, installSkills, listDictionaries, listDictionariesWithStats, listGitFiles, listGitLines, listProjects, logConfigDetails, prepareIntlayer, transformJSFile, writeContentDeclaration, writeJSFile };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/init/index.ts"],"mappings":";;AA+JA;;KAAY,WAAA;EACV,WAAW;AAAA;AAMb;;;AAAA,cAAa,YAAA,GAAsB,OAAA,UAAiB,OAAA,GAAU,WAAA,KAAW,OAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/init/index.ts"],"mappings":";;AAkKA;;KAAY,WAAA;EACV,WAAW;AAAA;AAMb;;;AAAA,cAAa,YAAA,GAAsB,OAAA,UAAiB,OAAA,GAAU,WAAA,KAAW,OAAA"}
@@ -0,0 +1,12 @@
1
+ //#region src/installLSP.d.ts
2
+ /**
3
+ * Writes the Intlayer LSP configuration to `.vscode/settings.json`.
4
+ * Creates the file (and the `.vscode/` directory) if they don't exist.
5
+ * Does not overwrite keys that are already present.
6
+ *
7
+ * Returns a human-readable summary of what was done plus next-step instructions.
8
+ */
9
+ declare const installLSP: (projectRoot: string) => Promise<string>;
10
+ //#endregion
11
+ export { installLSP };
12
+ //# sourceMappingURL=installLSP.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"installLSP.d.ts","names":[],"sources":["../../src/installLSP.ts"],"mappings":";;AAkBA;;;;AAA8D;;cAAjD,UAAA,GAAoB,WAAA,aAAsB,OAAO"}