@intlayer/chokidar 8.11.3 → 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.
- package/dist/cjs/utils/buildComponentFilesList.cjs +42 -17
- package/dist/cjs/utils/buildComponentFilesList.cjs.map +1 -1
- package/dist/cjs/watcher.cjs +142 -111
- package/dist/cjs/watcher.cjs.map +1 -1
- package/dist/esm/utils/buildComponentFilesList.mjs +43 -18
- package/dist/esm/utils/buildComponentFilesList.mjs.map +1 -1
- package/dist/esm/watcher.mjs +144 -113
- package/dist/esm/watcher.mjs.map +1 -1
- package/dist/types/utils/buildComponentFilesList.d.ts +10 -8
- package/dist/types/utils/buildComponentFilesList.d.ts.map +1 -1
- package/dist/types/watcher.d.ts +5 -3
- package/dist/types/watcher.d.ts.map +1 -1
- package/package.json +12 -12
|
@@ -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 `
|
|
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
|
-
* //
|
|
45
|
-
*
|
|
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
|
|
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 = [...
|
|
52
|
-
const
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
const
|
|
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:
|
|
77
|
+
ignore: baseExcludePatterns,
|
|
60
78
|
absolute: true,
|
|
61
79
|
dot: true
|
|
62
80
|
}));
|
|
63
|
-
|
|
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 `
|
|
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"}
|
package/dist/cjs/watcher.cjs
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
}
|
|
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 ?? {};
|
package/dist/cjs/watcher.cjs.map
CHANGED
|
@@ -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 `
|
|
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
|
-
* //
|
|
42
|
-
*
|
|
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
|
|
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 = [...
|
|
49
|
-
const
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
const
|
|
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:
|
|
74
|
+
ignore: baseExcludePatterns,
|
|
57
75
|
absolute: true,
|
|
58
76
|
dot: true
|
|
59
77
|
}));
|
|
60
|
-
|
|
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 `
|
|
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"}
|
package/dist/esm/watcher.mjs
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
}
|
|
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 ?? {};
|
package/dist/esm/watcher.mjs.map
CHANGED
|
@@ -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 `
|
|
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
|
-
* //
|
|
21
|
-
*
|
|
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":";;;;;
|
|
1
|
+
{"version":3,"file":"buildComponentFilesList.d.ts","names":[],"sources":["../../../src/utils/buildComponentFilesList.ts"],"mappings":";;;;;AAoEA;;;;;;;;AAE2B;;;;;;;;;;;cAFd,uBAAA,GACX,MAAA,EAAQ,cAAc,EACtB,cAAA"}
|
package/dist/types/watcher.d.ts
CHANGED
|
@@ -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 =
|
|
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<
|
|
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":"
|
|
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.
|
|
3
|
+
"version": "8.12.0-canary.0",
|
|
4
4
|
"private": false,
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Scans and builds Intlayer declaration files into dictionaries based on Intlayer configuration.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"intlayer",
|
|
8
|
-
"
|
|
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.
|
|
113
|
-
"@intlayer/config": "8.
|
|
114
|
-
"@intlayer/core": "8.
|
|
115
|
-
"@intlayer/dictionaries-entry": "8.
|
|
116
|
-
"@intlayer/remote-dictionaries-entry": "8.
|
|
117
|
-
"@intlayer/types": "8.
|
|
118
|
-
"@intlayer/unmerged-dictionaries-entry": "8.
|
|
119
|
-
"
|
|
112
|
+
"@intlayer/api": "8.12.0-canary.0",
|
|
113
|
+
"@intlayer/config": "8.12.0-canary.0",
|
|
114
|
+
"@intlayer/core": "8.12.0-canary.0",
|
|
115
|
+
"@intlayer/dictionaries-entry": "8.12.0-canary.0",
|
|
116
|
+
"@intlayer/remote-dictionaries-entry": "8.12.0-canary.0",
|
|
117
|
+
"@intlayer/types": "8.12.0-canary.0",
|
|
118
|
+
"@intlayer/unmerged-dictionaries-entry": "8.12.0-canary.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.
|
|
134
|
+
"vitest": "4.1.8",
|
|
135
135
|
"zod": "4.4.3"
|
|
136
136
|
},
|
|
137
137
|
"engines": {
|