@intlayer/engine 9.4.4 → 9.5.1
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/README.md +0 -2
- package/dist/cjs/createDictionaryEntryPoint/generateDictionaryListContent.cjs +1 -2
- package/dist/cjs/createDictionaryEntryPoint/generateDictionaryListContent.cjs.map +1 -1
- package/dist/cjs/init/index.cjs +14 -11
- package/dist/cjs/init/index.cjs.map +1 -1
- package/dist/cjs/init/utils/devScript.cjs +56 -0
- package/dist/cjs/init/utils/devScript.cjs.map +1 -0
- package/dist/cjs/init/utils/index.cjs +3 -0
- package/dist/cjs/loadDictionaries/loadContentDeclaration.cjs +1 -1
- package/dist/cjs/loadDictionaries/loadContentDeclaration.cjs.map +1 -1
- package/dist/cjs/loadDictionaries/loadMarkdownContentDeclaration.cjs +6 -5
- package/dist/cjs/loadDictionaries/loadMarkdownContentDeclaration.cjs.map +1 -1
- package/dist/cjs/syncPluginKit/createSyncPlugin.cjs +51 -3
- package/dist/cjs/syncPluginKit/createSyncPlugin.cjs.map +1 -1
- package/dist/cjs/utils/contentWatcherLock.cjs +172 -0
- package/dist/cjs/utils/contentWatcherLock.cjs.map +1 -0
- package/dist/cjs/utils/index.cjs +13 -1
- package/dist/cjs/utils/parseContentDeclarationFileName.cjs +35 -0
- package/dist/cjs/utils/parseContentDeclarationFileName.cjs.map +1 -0
- package/dist/cjs/utils/withFileLock.cjs +133 -0
- package/dist/cjs/utils/withFileLock.cjs.map +1 -0
- package/dist/cjs/watcher.cjs +7 -3
- package/dist/cjs/watcher.cjs.map +1 -1
- package/dist/esm/createDictionaryEntryPoint/generateDictionaryListContent.mjs +1 -2
- package/dist/esm/createDictionaryEntryPoint/generateDictionaryListContent.mjs.map +1 -1
- package/dist/esm/init/index.mjs +14 -11
- package/dist/esm/init/index.mjs.map +1 -1
- package/dist/esm/init/utils/devScript.mjs +54 -0
- package/dist/esm/init/utils/devScript.mjs.map +1 -0
- package/dist/esm/init/utils/index.mjs +2 -1
- package/dist/esm/loadDictionaries/loadContentDeclaration.mjs +1 -1
- package/dist/esm/loadDictionaries/loadContentDeclaration.mjs.map +1 -1
- package/dist/esm/loadDictionaries/loadMarkdownContentDeclaration.mjs +6 -5
- package/dist/esm/loadDictionaries/loadMarkdownContentDeclaration.mjs.map +1 -1
- package/dist/esm/syncPluginKit/createSyncPlugin.mjs +51 -3
- package/dist/esm/syncPluginKit/createSyncPlugin.mjs.map +1 -1
- package/dist/esm/utils/contentWatcherLock.mjs +163 -0
- package/dist/esm/utils/contentWatcherLock.mjs.map +1 -0
- package/dist/esm/utils/index.mjs +4 -1
- package/dist/esm/utils/parseContentDeclarationFileName.mjs +34 -0
- package/dist/esm/utils/parseContentDeclarationFileName.mjs.map +1 -0
- package/dist/esm/utils/withFileLock.mjs +132 -0
- package/dist/esm/utils/withFileLock.mjs.map +1 -0
- package/dist/esm/watcher.mjs +8 -4
- package/dist/esm/watcher.mjs.map +1 -1
- package/dist/types/formatDictionary.d.ts +2 -3
- package/dist/types/formatDictionary.d.ts.map +1 -1
- package/dist/types/init/index.d.ts.map +1 -1
- package/dist/types/init/utils/devScript.d.ts +48 -0
- package/dist/types/init/utils/devScript.d.ts.map +1 -0
- package/dist/types/init/utils/index.d.ts +2 -1
- package/dist/types/loadDictionaries/loadMarkdownContentDeclaration.d.ts +2 -1
- package/dist/types/loadDictionaries/loadMarkdownContentDeclaration.d.ts.map +1 -1
- package/dist/types/syncPluginKit/createSyncPlugin.d.ts.map +1 -1
- package/dist/types/syncPluginKit/index.d.ts +2 -2
- package/dist/types/syncPluginKit/types.d.ts +20 -6
- package/dist/types/syncPluginKit/types.d.ts.map +1 -1
- package/dist/types/utils/contentWatcherLock.d.ts +102 -0
- package/dist/types/utils/contentWatcherLock.d.ts.map +1 -0
- package/dist/types/utils/index.d.ts +4 -1
- package/dist/types/utils/parseContentDeclarationFileName.d.ts +23 -0
- package/dist/types/utils/parseContentDeclarationFileName.d.ts.map +1 -0
- package/dist/types/utils/withFileLock.d.ts +44 -0
- package/dist/types/utils/withFileLock.d.ts.map +1 -0
- package/dist/types/watcher.d.ts.map +1 -1
- package/package.json +10 -10
- package/dist/types/intlayer/dist/types/index.d.ts +0 -14
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { mkdir, readFile, unlink, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { rmSync } from "node:fs";
|
|
4
|
+
|
|
5
|
+
//#region src/utils/withFileLock.ts
|
|
6
|
+
const DEFAULT_OPTIONS = {
|
|
7
|
+
staleTimeoutMs: 3e4,
|
|
8
|
+
acquireTimeoutMs: 6e4
|
|
9
|
+
};
|
|
10
|
+
/** Delay between two attempts to take a lock held by someone else. */
|
|
11
|
+
const POLL_INTERVAL_MS = 25;
|
|
12
|
+
const delay = (durationMs) => new Promise((resolve) => setTimeout(resolve, durationMs));
|
|
13
|
+
/**
|
|
14
|
+
* Locks held by this process, released synchronously on exit so a crash never
|
|
15
|
+
* leaves a lock file that would stall every later run for `staleTimeoutMs`.
|
|
16
|
+
*/
|
|
17
|
+
const ownedLockFilePaths = /* @__PURE__ */ new Set();
|
|
18
|
+
process.on("exit", () => {
|
|
19
|
+
for (const lockFilePath of ownedLockFilePaths) try {
|
|
20
|
+
rmSync(lockFilePath, { force: true });
|
|
21
|
+
} catch {}
|
|
22
|
+
});
|
|
23
|
+
/**
|
|
24
|
+
* Whether the process holding the lock is still alive. An unreadable PID is
|
|
25
|
+
* reported as alive so {@link WithFileLockOptions.staleTimeoutMs} stays the
|
|
26
|
+
* only way to reclaim the lock.
|
|
27
|
+
*/
|
|
28
|
+
const getIsOwnerAlive = (pid) => {
|
|
29
|
+
if (!pid || pid === process.pid) return true;
|
|
30
|
+
try {
|
|
31
|
+
process.kill(pid, 0);
|
|
32
|
+
return true;
|
|
33
|
+
} catch (error) {
|
|
34
|
+
return error.code === "EPERM";
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const readLockOwner = async (lockFilePath) => {
|
|
38
|
+
try {
|
|
39
|
+
const raw = await readFile(lockFilePath, "utf8");
|
|
40
|
+
const parsed = JSON.parse(raw);
|
|
41
|
+
return {
|
|
42
|
+
pid: parsed.pid ?? 0,
|
|
43
|
+
acquiredAt: parsed.acquiredAt ?? 0
|
|
44
|
+
};
|
|
45
|
+
} catch {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const releaseLock = async (lockFilePath) => {
|
|
50
|
+
try {
|
|
51
|
+
await unlink(lockFilePath);
|
|
52
|
+
} catch {}
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Attempts to create the lock file. `wx` makes the creation atomic, so exactly
|
|
56
|
+
* one process wins even when several try at the same moment.
|
|
57
|
+
*/
|
|
58
|
+
const tryAcquireLock = async (lockFilePath) => {
|
|
59
|
+
const data = JSON.stringify({
|
|
60
|
+
pid: process.pid,
|
|
61
|
+
acquiredAt: Date.now()
|
|
62
|
+
});
|
|
63
|
+
for (let attempt = 0; attempt < 2; attempt++) try {
|
|
64
|
+
await mkdir(dirname(lockFilePath), { recursive: true });
|
|
65
|
+
await writeFile(lockFilePath, data, { flag: "wx" });
|
|
66
|
+
return true;
|
|
67
|
+
} catch (error) {
|
|
68
|
+
const code = error.code;
|
|
69
|
+
if (code === "EEXIST") return false;
|
|
70
|
+
if (code === "ENOENT" && attempt === 0) continue;
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Runs `callback` under a cross-process mutex materialised by a lock file.
|
|
77
|
+
*
|
|
78
|
+
* Unlike `runOnce`, which lets one process do the work and the others skip it,
|
|
79
|
+
* every caller here runs the callback — just never at the same time. This is
|
|
80
|
+
* what shared, read-modify-write outputs need (e.g. building the `.intlayer`
|
|
81
|
+
* dictionaries, which merges every dictionary on disk): concurrent runs would
|
|
82
|
+
* each read a half-written state.
|
|
83
|
+
*
|
|
84
|
+
* The lock is released even when the callback throws, and its error is
|
|
85
|
+
* re-thrown to the caller unchanged.
|
|
86
|
+
*
|
|
87
|
+
* @param lockFilePath - Path of the lock file coordinating the callers.
|
|
88
|
+
* @param callback - The critical section.
|
|
89
|
+
* @param options - Staleness and acquisition timeouts.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```typescript
|
|
93
|
+
* await withFileLock(join(cacheDir, 'build-dictionary.lock'), () =>
|
|
94
|
+
* buildDictionary([dictionary], configuration)
|
|
95
|
+
* );
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
const withFileLock = async (lockFilePath, callback, options) => {
|
|
99
|
+
const { staleTimeoutMs, acquireTimeoutMs } = {
|
|
100
|
+
...DEFAULT_OPTIONS,
|
|
101
|
+
...options ?? {}
|
|
102
|
+
};
|
|
103
|
+
const acquireDeadline = Date.now() + acquireTimeoutMs;
|
|
104
|
+
let hasAcquiredLock = false;
|
|
105
|
+
while (Date.now() < acquireDeadline) {
|
|
106
|
+
if (await tryAcquireLock(lockFilePath)) {
|
|
107
|
+
hasAcquiredLock = true;
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
const owner = await readLockOwner(lockFilePath);
|
|
111
|
+
if (owner) {
|
|
112
|
+
if (!getIsOwnerAlive(owner.pid) || Date.now() - owner.acquiredAt > staleTimeoutMs) {
|
|
113
|
+
await releaseLock(lockFilePath);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
await delay(POLL_INTERVAL_MS);
|
|
118
|
+
}
|
|
119
|
+
if (hasAcquiredLock) ownedLockFilePaths.add(lockFilePath);
|
|
120
|
+
try {
|
|
121
|
+
return await callback();
|
|
122
|
+
} finally {
|
|
123
|
+
if (hasAcquiredLock) {
|
|
124
|
+
ownedLockFilePaths.delete(lockFilePath);
|
|
125
|
+
await releaseLock(lockFilePath);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
//#endregion
|
|
131
|
+
export { withFileLock };
|
|
132
|
+
//# sourceMappingURL=withFileLock.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"withFileLock.mjs","names":[],"sources":["../../../src/utils/withFileLock.ts"],"sourcesContent":["import { rmSync } from 'node:fs';\nimport { mkdir, readFile, unlink, writeFile } from 'node:fs/promises';\nimport { dirname } from 'node:path';\n\n/** Payload written inside a lock file, used to detect abandoned locks. */\ntype LockOwnerData = {\n /** PID of the process currently holding the lock. */\n pid: number;\n /** Epoch milliseconds at which the lock was taken. */\n acquiredAt: number;\n};\n\ntype WithFileLockOptions = {\n /**\n * How long a lock may be held before it is considered abandoned and taken\n * over, in milliseconds.\n *\n * @default 30000 = 30 seconds\n */\n staleTimeoutMs?: number;\n /**\n * How long to wait for the lock before running the callback anyway, in\n * milliseconds. Giving up serialises nothing, but it is strictly better than\n * deadlocking a build behind a lock that never clears.\n *\n * @default 60000 = 1 minute\n */\n acquireTimeoutMs?: number;\n};\n\nconst DEFAULT_OPTIONS = {\n staleTimeoutMs: 30 * 1000,\n acquireTimeoutMs: 60 * 1000,\n} satisfies Required<WithFileLockOptions>;\n\n/** Delay between two attempts to take a lock held by someone else. */\nconst POLL_INTERVAL_MS = 25;\n\nconst delay = (durationMs: number): Promise<void> =>\n new Promise((resolve) => setTimeout(resolve, durationMs));\n\n/**\n * Locks held by this process, released synchronously on exit so a crash never\n * leaves a lock file that would stall every later run for `staleTimeoutMs`.\n */\nconst ownedLockFilePaths = new Set<string>();\n\nprocess.on('exit', () => {\n for (const lockFilePath of ownedLockFilePaths) {\n try {\n rmSync(lockFilePath, { force: true });\n } catch {}\n }\n});\n\n/**\n * Whether the process holding the lock is still alive. An unreadable PID is\n * reported as alive so {@link WithFileLockOptions.staleTimeoutMs} stays the\n * only way to reclaim the lock.\n */\nconst getIsOwnerAlive = (pid: number): boolean => {\n if (!pid || pid === process.pid) return true;\n\n try {\n // Signal 0 checks for existence without delivering a signal.\n process.kill(pid, 0);\n return true;\n } catch (error) {\n // EPERM means the process exists but belongs to another user.\n return (error as NodeJS.ErrnoException).code === 'EPERM';\n }\n};\n\nconst readLockOwner = async (\n lockFilePath: string\n): Promise<LockOwnerData | undefined> => {\n try {\n const raw = await readFile(lockFilePath, 'utf8');\n const parsed = JSON.parse(raw) as Partial<LockOwnerData>;\n\n return {\n pid: parsed.pid ?? 0,\n acquiredAt: parsed.acquiredAt ?? 0,\n };\n } catch {\n return undefined;\n }\n};\n\nconst releaseLock = async (lockFilePath: string): Promise<void> => {\n try {\n await unlink(lockFilePath);\n } catch {}\n};\n\n/**\n * Attempts to create the lock file. `wx` makes the creation atomic, so exactly\n * one process wins even when several try at the same moment.\n */\nconst tryAcquireLock = async (lockFilePath: string): Promise<boolean> => {\n const data = JSON.stringify({\n pid: process.pid,\n acquiredAt: Date.now(),\n } satisfies LockOwnerData);\n\n for (let attempt = 0; attempt < 2; attempt++) {\n try {\n await mkdir(dirname(lockFilePath), { recursive: true });\n await writeFile(lockFilePath, data, { flag: 'wx' });\n\n return true;\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code;\n\n if (code === 'EEXIST') return false;\n // The directory was removed between the mkdir and the write (e.g. a\n // concurrent output-directory clean); retry once.\n if (code === 'ENOENT' && attempt === 0) continue;\n\n throw error;\n }\n }\n\n return false;\n};\n\n/**\n * Runs `callback` under a cross-process mutex materialised by a lock file.\n *\n * Unlike `runOnce`, which lets one process do the work and the others skip it,\n * every caller here runs the callback — just never at the same time. This is\n * what shared, read-modify-write outputs need (e.g. building the `.intlayer`\n * dictionaries, which merges every dictionary on disk): concurrent runs would\n * each read a half-written state.\n *\n * The lock is released even when the callback throws, and its error is\n * re-thrown to the caller unchanged.\n *\n * @param lockFilePath - Path of the lock file coordinating the callers.\n * @param callback - The critical section.\n * @param options - Staleness and acquisition timeouts.\n *\n * @example\n * ```typescript\n * await withFileLock(join(cacheDir, 'build-dictionary.lock'), () =>\n * buildDictionary([dictionary], configuration)\n * );\n * ```\n */\nexport const withFileLock = async <T>(\n lockFilePath: string,\n callback: () => T | Promise<T>,\n options?: WithFileLockOptions\n): Promise<T> => {\n const { staleTimeoutMs, acquireTimeoutMs } = {\n ...DEFAULT_OPTIONS,\n ...(options ?? {}),\n };\n\n const acquireDeadline = Date.now() + acquireTimeoutMs;\n let hasAcquiredLock = false;\n\n while (Date.now() < acquireDeadline) {\n if (await tryAcquireLock(lockFilePath)) {\n hasAcquiredLock = true;\n break;\n }\n\n const owner = await readLockOwner(lockFilePath);\n\n // A missing owner means the lock was released between the two calls; loop\n // back and try to take it.\n if (owner) {\n const isAbandoned =\n !getIsOwnerAlive(owner.pid) ||\n Date.now() - owner.acquiredAt > staleTimeoutMs;\n\n if (isAbandoned) {\n await releaseLock(lockFilePath);\n continue;\n }\n }\n\n await delay(POLL_INTERVAL_MS);\n }\n\n if (hasAcquiredLock) ownedLockFilePaths.add(lockFilePath);\n\n try {\n return await callback();\n } finally {\n if (hasAcquiredLock) {\n ownedLockFilePaths.delete(lockFilePath);\n await releaseLock(lockFilePath);\n }\n }\n};\n"],"mappings":";;;;;AA8BA,MAAM,kBAAkB;CACtB,gBAAgB;CAChB,kBAAkB;AACpB;;AAGA,MAAM,mBAAmB;AAEzB,MAAM,SAAS,eACb,IAAI,SAAS,YAAY,WAAW,SAAS,UAAU,CAAC;;;;;AAM1D,MAAM,qCAAqB,IAAI,IAAY;AAE3C,QAAQ,GAAG,cAAc;CACvB,KAAK,MAAM,gBAAgB,oBACzB,IAAI;EACF,OAAO,cAAc,EAAE,OAAO,KAAK,CAAC;CACtC,QAAQ,CAAC;AAEb,CAAC;;;;;;AAOD,MAAM,mBAAmB,QAAyB;CAChD,IAAI,CAAC,OAAO,QAAQ,QAAQ,KAAK,OAAO;CAExC,IAAI;EAEF,QAAQ,KAAK,KAAK,CAAC;EACnB,OAAO;CACT,SAAS,OAAO;EAEd,OAAQ,MAAgC,SAAS;CACnD;AACF;AAEA,MAAM,gBAAgB,OACpB,iBACuC;CACvC,IAAI;EACF,MAAM,MAAM,MAAM,SAAS,cAAc,MAAM;EAC/C,MAAM,SAAS,KAAK,MAAM,GAAG;EAE7B,OAAO;GACL,KAAK,OAAO,OAAO;GACnB,YAAY,OAAO,cAAc;EACnC;CACF,QAAQ;EACN;CACF;AACF;AAEA,MAAM,cAAc,OAAO,iBAAwC;CACjE,IAAI;EACF,MAAM,OAAO,YAAY;CAC3B,QAAQ,CAAC;AACX;;;;;AAMA,MAAM,iBAAiB,OAAO,iBAA2C;CACvE,MAAM,OAAO,KAAK,UAAU;EAC1B,KAAK,QAAQ;EACb,YAAY,KAAK,IAAI;CACvB,CAAyB;CAEzB,KAAK,IAAI,UAAU,GAAG,UAAU,GAAG,WACjC,IAAI;EACF,MAAM,MAAM,QAAQ,YAAY,GAAG,EAAE,WAAW,KAAK,CAAC;EACtD,MAAM,UAAU,cAAc,MAAM,EAAE,MAAM,KAAK,CAAC;EAElD,OAAO;CACT,SAAS,OAAO;EACd,MAAM,OAAQ,MAAgC;EAE9C,IAAI,SAAS,UAAU,OAAO;EAG9B,IAAI,SAAS,YAAY,YAAY,GAAG;EAExC,MAAM;CACR;CAGF,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,MAAa,eAAe,OAC1B,cACA,UACA,YACe;CACf,MAAM,EAAE,gBAAgB,qBAAqB;EAC3C,GAAG;EACH,GAAI,WAAW,CAAC;CAClB;CAEA,MAAM,kBAAkB,KAAK,IAAI,IAAI;CACrC,IAAI,kBAAkB;CAEtB,OAAO,KAAK,IAAI,IAAI,iBAAiB;EACnC,IAAI,MAAM,eAAe,YAAY,GAAG;GACtC,kBAAkB;GAClB;EACF;EAEA,MAAM,QAAQ,MAAM,cAAc,YAAY;EAI9C,IAAI,OAKF;OAHE,CAAC,gBAAgB,MAAM,GAAG,KAC1B,KAAK,IAAI,IAAI,MAAM,aAAa,gBAEjB;IACf,MAAM,YAAY,YAAY;IAC9B;GACF;;EAGF,MAAM,MAAM,gBAAgB;CAC9B;CAEA,IAAI,iBAAiB,mBAAmB,IAAI,YAAY;CAExD,IAAI;EACF,OAAO,MAAM,SAAS;CACxB,UAAU;EACR,IAAI,iBAAiB;GACnB,mBAAmB,OAAO,YAAY;GACtC,MAAM,YAAY,YAAY;EAChC;CACF;AACF"}
|
package/dist/esm/watcher.mjs
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { formatPath } from "./utils/formatter.mjs";
|
|
2
|
+
import { getFormatFromExtension } from "./utils/getFormatFromExtension.mjs";
|
|
3
|
+
import { parseContentDeclarationFileName } from "./utils/parseContentDeclarationFileName.mjs";
|
|
2
4
|
import { handleContentDeclarationFileChange } from "./handleContentDeclarationFileChange.mjs";
|
|
3
5
|
import { handleContentDeclarationFileMoved } from "./handleContentDeclarationFileMoved.mjs";
|
|
4
6
|
import { handleAdditionalContentDeclarationFile } from "./handleAdditionalContentDeclarationFile.mjs";
|
|
@@ -6,7 +8,7 @@ import { handleUnlinkedContentDeclarationFile } from "./handleUnlinkedContentDec
|
|
|
6
8
|
import { prepareIntlayer } from "./prepareIntlayer.mjs";
|
|
7
9
|
import { writeContentDeclaration } from "./writeContentDeclaration/writeContentDeclaration.mjs";
|
|
8
10
|
import { readFile } from "node:fs/promises";
|
|
9
|
-
import { basename, dirname, resolve } from "node:path";
|
|
11
|
+
import { basename, dirname, extname, resolve } from "node:path";
|
|
10
12
|
import { colorize, getAppLogger } from "@intlayer/config/logger";
|
|
11
13
|
import { getConfiguration, getConfigurationAndFilePath } from "@intlayer/config/node";
|
|
12
14
|
import { existsSync, watch as watch$1 } from "node:fs";
|
|
@@ -166,10 +168,12 @@ const watch = async (options) => {
|
|
|
166
168
|
else scheduleStable(path, () => {
|
|
167
169
|
processEvent(async () => {
|
|
168
170
|
if (await readFile(filePath, "utf-8") === "") {
|
|
169
|
-
const
|
|
170
|
-
const
|
|
171
|
+
const { key, locale } = parseContentDeclarationFileName(filePath, configuration);
|
|
172
|
+
const isMarkdown = getFormatFromExtension(extname(filePath)) === "md";
|
|
173
|
+
const declaredLocale = locale ?? (isMarkdown ? configuration.internationalization.defaultLocale : void 0);
|
|
171
174
|
await writeContentDeclaration({
|
|
172
|
-
key
|
|
175
|
+
key,
|
|
176
|
+
...declaredLocale && { locale: declaredLocale },
|
|
173
177
|
content: {},
|
|
174
178
|
filePath
|
|
175
179
|
}, configuration);
|
package/dist/esm/watcher.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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 * as ANSIColor from '@intlayer/config/colors';\nimport { transpileTSToCJS } from '@intlayer/config/file';\nimport { colorize, getAppLogger } from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n getConfigurationAndFilePath,\n} from '@intlayer/config/node';\nimport {\n clearAllCache,\n clearDiskCacheMemory,\n clearModuleCache,\n normalizePath,\n} from '@intlayer/config/utils';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { handleAdditionalContentDeclarationFile } from './handleAdditionalContentDeclarationFile';\nimport { handleContentDeclarationFileChange } from './handleContentDeclarationFileChange';\nimport { handleContentDeclarationFileMoved } from './handleContentDeclarationFileMoved';\nimport { handleUnlinkedContentDeclarationFile } from './handleUnlinkedContentDeclarationFile';\nimport { prepareIntlayer } from './prepareIntlayer';\nimport { formatPath } from './utils';\nimport { writeContentDeclaration } from './writeContentDeclaration';\n\n// Map to track files that were recently unlinked: oldPath -> { timer, timestamp }\nconst pendingUnlinks = new Map<\n string,\n { timer: NodeJS.Timeout; oldPath: string }\n>();\n\n// Array-based sequential task queue — no Promise chain accumulation, no race conditions\nconst taskQueue: (() => Promise<void>)[] = [];\nlet isProcessing = false;\n\nconst processQueue = async () => {\n if (isProcessing) return;\n isProcessing = true;\n while (taskQueue.length > 0) {\n const task = taskQueue.shift()!;\n try {\n await task();\n } catch (error) {\n console.error(error);\n }\n }\n isProcessing = false;\n};\n\nconst processEvent = (task: () => Promise<void>) => {\n taskQueue.push(task);\n processQueue();\n};\n\ntype WatchOptions = {\n configuration?: IntlayerConfig;\n configOptions?: GetConfigurationOptions;\n skipPrepare?: boolean;\n persistent?: boolean;\n};\n\n// awaitWriteFinish equivalent: debounce per path until the file is stable\nconst STABILITY_THRESHOLD = 1000;\n\nconst createStabilityDebounce = () => {\n const pending = new Map<string, NodeJS.Timeout>();\n return (path: string, handler: () => void) => {\n const existing = pending.get(path);\n if (existing) clearTimeout(existing);\n pending.set(\n path,\n setTimeout(() => {\n pending.delete(path);\n handler();\n }, STABILITY_THRESHOLD)\n );\n };\n};\n\n// Initialize @parcel/watcher (non-persistent until subscribed)\nexport const watch = async (options?: WatchOptions) => {\n const { subscribe } = await import('@parcel/watcher');\n\n const configResult = getConfigurationAndFilePath(options?.configOptions);\n const configurationFilePath = configResult.configurationFilePath;\n let configuration: IntlayerConfig =\n options?.configuration ?? configResult.configuration;\n const appLogger = getAppLogger(configuration);\n\n const {\n watch: isWatchMode,\n fileExtensions,\n contentDir,\n excludedPath,\n } = configuration.content;\n\n if (!configuration.content.watch) return;\n\n appLogger('Watching Intlayer content declarations');\n\n if (configuration.build.optimize === true) {\n appLogger(\n [\n `Build optimization is forced to ${colorize('true', ANSIColor.GREY)}, but watching is enabled too.`,\n 'It may lead to dev mode performance degradation as well as import errors.',\n 'Its recommended to keep the',\n colorize('`build.optimized`', ANSIColor.BLUE),\n 'option',\n colorize('undefined', ANSIColor.GREY),\n 'to get the best dev mode experience',\n ],\n {\n level: 'warn',\n }\n );\n }\n\n // Strip glob markers from excludedPath entries to get plain segments (e.g. 'node_modules')\n const excludedSegments = excludedPath.map((segment) =>\n segment.replace(/^\\*\\*\\//, '').replace(/\\/\\*\\*$/, '')\n );\n\n const normalizedConfigPath = configurationFilePath\n ? normalizePath(configurationFilePath)\n : null;\n\n const { mainDir, baseDir } = configuration.system;\n const normalizedMainDir = normalizePath(mainDir);\n const normalizedIntlayerDir = normalizePath(dirname(mainDir));\n\n const subscriptions: { unsubscribe: () => Promise<void> }[] = [];\n\n const scheduleStable = createStabilityDebounce();\n\n // ── mainDir watcher (depth 0) ──────────────────────────────────────────────\n // Detects broken or missing entry-point files inside .intlayer/main\n if (existsSync(mainDir)) {\n const mainDirSub = await subscribe(normalizedMainDir, (err, events) => {\n if (err || isProcessing) return;\n\n for (const event of events) {\n const eventPath = normalizePath(event.path);\n // depth-0 filter: only files directly inside mainDir\n const rel = eventPath.slice(normalizedMainDir.length + 1);\n if (!rel || rel.includes('/')) continue;\n // Temp files written by the bundler (write-then-rename) are build-internal;\n // their deletion must not trigger a clean rebuild.\n if (rel.endsWith('.tmp')) continue;\n\n if (event.type === 'update') {\n processEvent(async () => {\n try {\n // Validate that the regenerated entry point — and the relative\n // dictionary graph it imports — still resolves, by bundling it\n // in-memory with esbuild (via the shared, freshness-checked\n // transpile cache). This replaces a cache-busting dynamic\n // `import()`, which leaked a permanent record into Node's ESM\n // registry on every rebuild since that registry is never evicted.\n const entryCode = await readFile(event.path, 'utf-8');\n await transpileTSToCJS(entryCode, event.path);\n } catch {\n appLogger(\n `Entry point ${basename(event.path)} failed to load, running clean rebuild...`,\n { level: 'warn' }\n );\n await prepareIntlayer(configuration, {\n clean: true,\n forceRun: true,\n });\n }\n });\n } else if (event.type === 'delete') {\n processEvent(async () => {\n appLogger(\n [\n 'Entry point',\n formatPath(basename(event.path)),\n 'was removed, running clean rebuild...',\n ],\n { level: 'warn' }\n );\n await prepareIntlayer(configuration, {\n clean: true,\n forceRun: true,\n });\n });\n }\n }\n });\n subscriptions.push(mainDirSub);\n }\n\n // ── baseDir watcher (depth 0) — detect .intlayer directory removal ─────────\n // Native fs.watch is non-recursive and ideal for single-directory depth-0 detection.\n const intlayerDirName = basename(normalizedIntlayerDir);\n const fsDirWatcher = fsWatch(\n baseDir,\n { persistent: isWatchMode },\n (eventType, filename) => {\n if (isProcessing || !filename) return;\n if (filename !== intlayerDirName) return;\n\n const fullPath = normalizePath(resolve(baseDir, filename));\n if (fullPath !== normalizedIntlayerDir) return;\n\n if (eventType === 'rename' && !existsSync(normalizedIntlayerDir)) {\n appLogger([\n formatPath('.intlayer'),\n 'directory removed, running clean rebuild...',\n ]);\n processEvent(() =>\n prepareIntlayer(configuration, { clean: true, forceRun: true })\n );\n }\n }\n );\n subscriptions.push({\n unsubscribe: async () => {\n fsDirWatcher.close();\n },\n });\n\n // ── main content watcher ───────────────────────────────────────────────────\n // Ignore patterns for @parcel/watcher (micromatch globs)\n const ignorePatterns = excludedSegments.map((s) => `**/${s}/**`);\n\n // Strip trailing slashes so directory matching stays exact. A user-supplied\n const contentDirs = contentDir\n .map((dir) => normalizePath(dir).replace(/\\/+$/, ''))\n .filter(existsSync);\n\n // Collect unique directories to subscribe to (dirs only, not file paths)\n const dirsToWatch = new Set<string>(contentDirs);\n if (normalizedConfigPath) {\n dirsToWatch.add(normalizePath(dirname(normalizedConfigPath)));\n }\n\n const contentHandler = (\n err: Error | null,\n events: Array<{ type: string; path: string }>\n ) => {\n if (err) {\n appLogger(`Watcher error: ${err}`, { level: 'error' });\n appLogger('Restarting watcher');\n prepareIntlayer(configuration);\n return;\n }\n\n for (const event of events) {\n const filePath = event.path;\n const path = normalizePath(filePath);\n\n const isConfigFile =\n normalizedConfigPath && path === normalizedConfigPath;\n\n if (!isConfigFile) {\n // Must originate from a watched content directory\n const isInContentDir = contentDirs.some(\n (d) => path.startsWith(`${d}/`) || path === d\n );\n if (!isInContentDir) continue;\n\n if (excludedSegments.some((segment) => path.includes(`/${segment}`)))\n continue;\n\n if (!fileExtensions.some((extension) => path.endsWith(extension)))\n continue;\n }\n\n if (event.type === 'create') {\n const fileName = basename(filePath);\n\n // Move detection must happen synchronously before any debounce\n let isMove = false;\n let matchedOldPath: string | undefined;\n\n for (const [oldPath] of pendingUnlinks) {\n if (basename(oldPath) === fileName) {\n matchedOldPath = oldPath;\n break;\n }\n }\n\n if (!matchedOldPath && pendingUnlinks.size === 1) {\n matchedOldPath = pendingUnlinks.keys().next().value;\n }\n\n if (matchedOldPath) {\n const pending = pendingUnlinks.get(matchedOldPath);\n if (pending) {\n clearTimeout(pending.timer);\n pendingUnlinks.delete(matchedOldPath);\n }\n isMove = true;\n appLogger(`File moved from ${matchedOldPath} to ${filePath}`);\n }\n\n if (isMove && matchedOldPath) {\n processEvent(async () => {\n await handleContentDeclarationFileMoved(\n matchedOldPath!,\n filePath,\n configuration\n );\n });\n } else {\n // Debounce: wait for write to finish before reading the file\n scheduleStable(path, () => {\n processEvent(async () => {\n const fileContent = await readFile(filePath, 'utf-8');\n const isEmpty = fileContent === '';\n\n if (isEmpty) {\n const extensionPattern = fileExtensions\n .map((ext) => ext.replace(/\\./g, '\\\\.'))\n .join('|');\n const name = fileName.replace(\n new RegExp(`(${extensionPattern})$`),\n ''\n );\n\n await writeContentDeclaration(\n { key: name, content: {}, filePath },\n configuration\n );\n }\n\n await handleAdditionalContentDeclarationFile(\n filePath,\n configuration\n );\n });\n });\n }\n } else if (event.type === 'update') {\n scheduleStable(path, () => {\n processEvent(async () => {\n if (isConfigFile) {\n appLogger('Configuration file changed, repreparing Intlayer');\n\n clearModuleCache(filePath);\n clearAllCache();\n\n const { configuration: newConfiguration } =\n getConfigurationAndFilePath(options?.configOptions);\n\n configuration = options?.configuration ?? newConfiguration;\n\n await prepareIntlayer(configuration, { clean: false });\n } else {\n // Clear module cache for the changed file to avoid stale require() results\n clearModuleCache(filePath);\n // Evict in-memory caches so loadContentDeclaration picks up fresh content\n clearAllCache();\n clearDiskCacheMemory();\n await handleContentDeclarationFileChange(filePath, configuration);\n }\n });\n });\n } else if (event.type === 'delete') {\n // Delay unlink processing to see if an 'add' event occurs (indicating a move)\n const timer = setTimeout(async () => {\n // If timer fires, the file was genuinely removed\n pendingUnlinks.delete(filePath);\n processEvent(async () =>\n handleUnlinkedContentDeclarationFile(filePath, configuration)\n );\n }, 200); // 200ms window to catch the 'create' event\n\n pendingUnlinks.set(filePath, { timer, oldPath: filePath });\n }\n }\n };\n\n for (const dir of dirsToWatch) {\n const sub = await subscribe(dir, contentHandler, {\n ignore: ignorePatterns,\n });\n\n subscriptions.push(sub);\n }\n\n return subscriptions;\n};\n\nexport const buildAndWatchIntlayer = async (options?: WatchOptions) => {\n const { skipPrepare, ...rest } = options ?? {};\n const configuration =\n options?.configuration ?? getConfiguration(options?.configOptions);\n\n if (!skipPrepare) {\n await prepareIntlayer(configuration, { forceRun: true });\n }\n\n // Only enter watch mode when the caller explicitly opts in via `persistent`.\n // `configuration.content.watch` is the dev-mode signal consumed by bundler\n // plugins (e.g. vite-intlayer's `configureServer`); it must not coerce\n // `intlayer build` (which passes `persistent: false`) into a persistent\n // watcher, since that prevents the build command from ever exiting.\n if (options?.persistent) {\n await watch({ ...rest, configuration });\n }\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AA2BA,MAAM,iCAAiB,IAAI,IAGzB;AAGF,MAAM,YAAqC,CAAC;AAC5C,IAAI,eAAe;AAEnB,MAAM,eAAe,YAAY;CAC/B,IAAI,cAAc;CAClB,eAAe;CACf,OAAO,UAAU,SAAS,GAAG;EAC3B,MAAM,OAAO,UAAU,MAAM;EAC7B,IAAI;GACF,MAAM,KAAK;EACb,SAAS,OAAO;GACd,QAAQ,MAAM,KAAK;EACrB;CACF;CACA,eAAe;AACjB;AAEA,MAAM,gBAAgB,SAA8B;CAClD,UAAU,KAAK,IAAI;CACnB,aAAa;AACf;AAUA,MAAM,sBAAsB;AAE5B,MAAM,gCAAgC;CACpC,MAAM,0BAAU,IAAI,IAA4B;CAChD,QAAQ,MAAc,YAAwB;EAC5C,MAAM,WAAW,QAAQ,IAAI,IAAI;EACjC,IAAI,UAAU,aAAa,QAAQ;EACnC,QAAQ,IACN,MACA,iBAAiB;GACf,QAAQ,OAAO,IAAI;GACnB,QAAQ;EACV,GAAG,mBAAmB,CACxB;CACF;AACF;AAGA,MAAa,QAAQ,OAAO,YAA2B;CACrD,MAAM,EAAE,cAAc,MAAM,OAAO;CAEnC,MAAM,eAAe,4BAA4B,SAAS,aAAa;CACvE,MAAM,wBAAwB,aAAa;CAC3C,IAAI,gBACF,SAAS,iBAAiB,aAAa;CACzC,MAAM,YAAY,aAAa,aAAa;CAE5C,MAAM,EACJ,OAAO,aACP,gBACA,YACA,iBACE,cAAc;CAElB,IAAI,CAAC,cAAc,QAAQ,OAAO;CAElC,UAAU,wCAAwC;CAElD,IAAI,cAAc,MAAM,aAAa,MACnC,UACE;EACE,mCAAmC,SAAS,QAAQ,UAAU,IAAI,EAAE;EACpE;EACA;EACA,SAAS,qBAAqB,UAAU,IAAI;EAC5C;EACA,SAAS,aAAa,UAAU,IAAI;EACpC;CACF,GACA,EACE,OAAO,OACT,CACF;CAIF,MAAM,mBAAmB,aAAa,KAAK,YACzC,QAAQ,QAAQ,WAAW,EAAE,CAAC,CAAC,QAAQ,WAAW,EAAE,CACtD;CAEA,MAAM,uBAAuB,wBACzB,cAAc,qBAAqB,IACnC;CAEJ,MAAM,EAAE,SAAS,YAAY,cAAc;CAC3C,MAAM,oBAAoB,cAAc,OAAO;CAC/C,MAAM,wBAAwB,cAAc,QAAQ,OAAO,CAAC;CAE5D,MAAM,gBAAwD,CAAC;CAE/D,MAAM,iBAAiB,wBAAwB;CAI/C,IAAI,WAAW,OAAO,GAAG;EACvB,MAAM,aAAa,MAAM,UAAU,oBAAoB,KAAK,WAAW;GACrE,IAAI,OAAO,cAAc;GAEzB,KAAK,MAAM,SAAS,QAAQ;IAG1B,MAAM,MAFY,cAAc,MAAM,IAElB,CAAC,CAAC,MAAM,kBAAkB,SAAS,CAAC;IACxD,IAAI,CAAC,OAAO,IAAI,SAAS,GAAG,GAAG;IAG/B,IAAI,IAAI,SAAS,MAAM,GAAG;IAE1B,IAAI,MAAM,SAAS,UACjB,aAAa,YAAY;KACvB,IAAI;MAOF,MAAM,YAAY,MAAM,SAAS,MAAM,MAAM,OAAO;MACpD,MAAM,iBAAiB,WAAW,MAAM,IAAI;KAC9C,QAAQ;MACN,UACE,eAAe,SAAS,MAAM,IAAI,EAAE,4CACpC,EAAE,OAAO,OAAO,CAClB;MACA,MAAM,gBAAgB,eAAe;OACnC,OAAO;OACP,UAAU;MACZ,CAAC;KACH;IACF,CAAC;SACI,IAAI,MAAM,SAAS,UACxB,aAAa,YAAY;KACvB,UACE;MACE;MACA,WAAW,SAAS,MAAM,IAAI,CAAC;MAC/B;KACF,GACA,EAAE,OAAO,OAAO,CAClB;KACA,MAAM,gBAAgB,eAAe;MACnC,OAAO;MACP,UAAU;KACZ,CAAC;IACH,CAAC;GAEL;EACF,CAAC;EACD,cAAc,KAAK,UAAU;CAC/B;CAIA,MAAM,kBAAkB,SAAS,qBAAqB;CACtD,MAAM,eAAeA,QACnB,SACA,EAAE,YAAY,YAAY,IACzB,WAAW,aAAa;EACvB,IAAI,gBAAgB,CAAC,UAAU;EAC/B,IAAI,aAAa,iBAAiB;EAGlC,IADiB,cAAc,QAAQ,SAAS,QAAQ,CAC7C,MAAM,uBAAuB;EAExC,IAAI,cAAc,YAAY,CAAC,WAAW,qBAAqB,GAAG;GAChE,UAAU,CACR,WAAW,WAAW,GACtB,6CACF,CAAC;GACD,mBACE,gBAAgB,eAAe;IAAE,OAAO;IAAM,UAAU;GAAK,CAAC,CAChE;EACF;CACF,CACF;CACA,cAAc,KAAK,EACjB,aAAa,YAAY;EACvB,aAAa,MAAM;CACrB,EACF,CAAC;CAID,MAAM,iBAAiB,iBAAiB,KAAK,MAAM,MAAM,EAAE,IAAI;CAG/D,MAAM,cAAc,WACjB,KAAK,QAAQ,cAAc,GAAG,CAAC,CAAC,QAAQ,QAAQ,EAAE,CAAC,CAAC,CACpD,OAAO,UAAU;CAGpB,MAAM,cAAc,IAAI,IAAY,WAAW;CAC/C,IAAI,sBACF,YAAY,IAAI,cAAc,QAAQ,oBAAoB,CAAC,CAAC;CAG9D,MAAM,kBACJ,KACA,WACG;EACH,IAAI,KAAK;GACP,UAAU,kBAAkB,OAAO,EAAE,OAAO,QAAQ,CAAC;GACrD,UAAU,oBAAoB;GAC9B,gBAAgB,aAAa;GAC7B;EACF;EAEA,KAAK,MAAM,SAAS,QAAQ;GAC1B,MAAM,WAAW,MAAM;GACvB,MAAM,OAAO,cAAc,QAAQ;GAEnC,MAAM,eACJ,wBAAwB,SAAS;GAEnC,IAAI,CAAC,cAAc;IAKjB,IAAI,CAHmB,YAAY,MAChC,MAAM,KAAK,WAAW,GAAG,EAAE,EAAE,KAAK,SAAS,CAE5B,GAAG;IAErB,IAAI,iBAAiB,MAAM,YAAY,KAAK,SAAS,IAAI,SAAS,CAAC,GACjE;IAEF,IAAI,CAAC,eAAe,MAAM,cAAc,KAAK,SAAS,SAAS,CAAC,GAC9D;GACJ;GAEA,IAAI,MAAM,SAAS,UAAU;IAC3B,MAAM,WAAW,SAAS,QAAQ;IAGlC,IAAI,SAAS;IACb,IAAI;IAEJ,KAAK,MAAM,CAAC,YAAY,gBACtB,IAAI,SAAS,OAAO,MAAM,UAAU;KAClC,iBAAiB;KACjB;IACF;IAGF,IAAI,CAAC,kBAAkB,eAAe,SAAS,GAC7C,iBAAiB,eAAe,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC;IAGhD,IAAI,gBAAgB;KAClB,MAAM,UAAU,eAAe,IAAI,cAAc;KACjD,IAAI,SAAS;MACX,aAAa,QAAQ,KAAK;MAC1B,eAAe,OAAO,cAAc;KACtC;KACA,SAAS;KACT,UAAU,mBAAmB,eAAe,MAAM,UAAU;IAC9D;IAEA,IAAI,UAAU,gBACZ,aAAa,YAAY;KACvB,MAAM,kCACJ,gBACA,UACA,aACF;IACF,CAAC;SAGD,eAAe,YAAY;KACzB,aAAa,YAAY;MAIvB,IAFgB,MADU,SAAS,UAAU,OAAO,MACpB,IAEnB;OACX,MAAM,mBAAmB,eACtB,KAAK,QAAQ,IAAI,QAAQ,OAAO,KAAK,CAAC,CAAC,CACvC,KAAK,GAAG;OACX,MAAM,OAAO,SAAS,QACpB,IAAI,OAAO,IAAI,iBAAiB,GAAG,GACnC,EACF;OAEA,MAAM,wBACJ;QAAE,KAAK;QAAM,SAAS,CAAC;QAAG;OAAS,GACnC,aACF;MACF;MAEA,MAAM,uCACJ,UACA,aACF;KACF,CAAC;IACH,CAAC;GAEL,OAAO,IAAI,MAAM,SAAS,UACxB,eAAe,YAAY;IACzB,aAAa,YAAY;KACvB,IAAI,cAAc;MAChB,UAAU,kDAAkD;MAE5D,iBAAiB,QAAQ;MACzB,cAAc;MAEd,MAAM,EAAE,eAAe,qBACrB,4BAA4B,SAAS,aAAa;MAEpD,gBAAgB,SAAS,iBAAiB;MAE1C,MAAM,gBAAgB,eAAe,EAAE,OAAO,MAAM,CAAC;KACvD,OAAO;MAEL,iBAAiB,QAAQ;MAEzB,cAAc;MACd,qBAAqB;MACrB,MAAM,mCAAmC,UAAU,aAAa;KAClE;IACF,CAAC;GACH,CAAC;QACI,IAAI,MAAM,SAAS,UAAU;IAElC,MAAM,QAAQ,WAAW,YAAY;KAEnC,eAAe,OAAO,QAAQ;KAC9B,aAAa,YACX,qCAAqC,UAAU,aAAa,CAC9D;IACF,GAAG,GAAG;IAEN,eAAe,IAAI,UAAU;KAAE;KAAO,SAAS;IAAS,CAAC;GAC3D;EACF;CACF;CAEA,KAAK,MAAM,OAAO,aAAa;EAC7B,MAAM,MAAM,MAAM,UAAU,KAAK,gBAAgB,EAC/C,QAAQ,eACV,CAAC;EAED,cAAc,KAAK,GAAG;CACxB;CAEA,OAAO;AACT;AAEA,MAAa,wBAAwB,OAAO,YAA2B;CACrE,MAAM,EAAE,aAAa,GAAG,SAAS,WAAW,CAAC;CAC7C,MAAM,gBACJ,SAAS,iBAAiB,iBAAiB,SAAS,aAAa;CAEnE,IAAI,CAAC,aACH,MAAM,gBAAgB,eAAe,EAAE,UAAU,KAAK,CAAC;CAQzD,IAAI,SAAS,YACX,MAAM,MAAM;EAAE,GAAG;EAAM;CAAc,CAAC;AAE1C"}
|
|
1
|
+
{"version":3,"file":"watcher.mjs","names":["fsWatch"],"sources":["../../src/watcher.ts"],"sourcesContent":["import { existsSync, watch as fsWatch } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { basename, dirname, extname, resolve } from 'node:path';\nimport * as ANSIColor from '@intlayer/config/colors';\nimport { transpileTSToCJS } from '@intlayer/config/file';\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 {\n formatPath,\n getFormatFromExtension,\n parseContentDeclarationFileName,\n} from './utils';\nimport { writeContentDeclaration } from './writeContentDeclaration';\n\n// Map to track files that were recently unlinked: oldPath -> { timer, timestamp }\nconst pendingUnlinks = new Map<\n string,\n { timer: NodeJS.Timeout; oldPath: string }\n>();\n\n// Array-based sequential task queue — no Promise chain accumulation, no race conditions\nconst taskQueue: (() => Promise<void>)[] = [];\nlet isProcessing = false;\n\nconst processQueue = async () => {\n if (isProcessing) return;\n isProcessing = true;\n while (taskQueue.length > 0) {\n const task = taskQueue.shift()!;\n try {\n await task();\n } catch (error) {\n console.error(error);\n }\n }\n isProcessing = false;\n};\n\nconst processEvent = (task: () => Promise<void>) => {\n taskQueue.push(task);\n processQueue();\n};\n\ntype WatchOptions = {\n configuration?: IntlayerConfig;\n configOptions?: GetConfigurationOptions;\n skipPrepare?: boolean;\n persistent?: boolean;\n};\n\n// awaitWriteFinish equivalent: debounce per path until the file is stable\nconst STABILITY_THRESHOLD = 1000;\n\nconst createStabilityDebounce = () => {\n const pending = new Map<string, NodeJS.Timeout>();\n return (path: string, handler: () => void) => {\n const existing = pending.get(path);\n if (existing) clearTimeout(existing);\n pending.set(\n path,\n setTimeout(() => {\n pending.delete(path);\n handler();\n }, STABILITY_THRESHOLD)\n );\n };\n};\n\n// Initialize @parcel/watcher (non-persistent until subscribed)\nexport const watch = async (options?: WatchOptions) => {\n const { subscribe } = await import('@parcel/watcher');\n\n const configResult = getConfigurationAndFilePath(options?.configOptions);\n const configurationFilePath = configResult.configurationFilePath;\n let configuration: IntlayerConfig =\n options?.configuration ?? configResult.configuration;\n const appLogger = getAppLogger(configuration);\n\n const {\n watch: isWatchMode,\n fileExtensions,\n contentDir,\n excludedPath,\n } = configuration.content;\n\n if (!configuration.content.watch) return;\n\n appLogger('Watching Intlayer content declarations');\n\n if (configuration.build.optimize === true) {\n appLogger(\n [\n `Build optimization is forced to ${colorize('true', ANSIColor.GREY)}, but watching is enabled too.`,\n 'It may lead to dev mode performance degradation as well as import errors.',\n 'Its recommended to keep the',\n colorize('`build.optimized`', ANSIColor.BLUE),\n 'option',\n colorize('undefined', ANSIColor.GREY),\n 'to get the best dev mode experience',\n ],\n {\n level: 'warn',\n }\n );\n }\n\n // Strip glob markers from excludedPath entries to get plain segments (e.g. 'node_modules')\n const excludedSegments = excludedPath.map((segment) =>\n segment.replace(/^\\*\\*\\//, '').replace(/\\/\\*\\*$/, '')\n );\n\n const normalizedConfigPath = configurationFilePath\n ? normalizePath(configurationFilePath)\n : null;\n\n const { mainDir, baseDir } = configuration.system;\n const normalizedMainDir = normalizePath(mainDir);\n const normalizedIntlayerDir = normalizePath(dirname(mainDir));\n\n const subscriptions: { unsubscribe: () => Promise<void> }[] = [];\n\n const scheduleStable = createStabilityDebounce();\n\n // ── mainDir watcher (depth 0) ──────────────────────────────────────────────\n // Detects broken or missing entry-point files inside .intlayer/main\n if (existsSync(mainDir)) {\n const mainDirSub = await subscribe(normalizedMainDir, (err, events) => {\n if (err || isProcessing) return;\n\n for (const event of events) {\n const eventPath = normalizePath(event.path);\n // depth-0 filter: only files directly inside mainDir\n const rel = eventPath.slice(normalizedMainDir.length + 1);\n if (!rel || rel.includes('/')) continue;\n // Temp files written by the bundler (write-then-rename) are build-internal;\n // their deletion must not trigger a clean rebuild.\n if (rel.endsWith('.tmp')) continue;\n\n if (event.type === 'update') {\n processEvent(async () => {\n try {\n // Validate that the regenerated entry point — and the relative\n // dictionary graph it imports — still resolves, by bundling it\n // in-memory with esbuild (via the shared, freshness-checked\n // transpile cache). This replaces a cache-busting dynamic\n // `import()`, which leaked a permanent record into Node's ESM\n // registry on every rebuild since that registry is never evicted.\n const entryCode = await readFile(event.path, 'utf-8');\n await transpileTSToCJS(entryCode, event.path);\n } catch {\n appLogger(\n `Entry point ${basename(event.path)} failed to load, running clean rebuild...`,\n { level: 'warn' }\n );\n await prepareIntlayer(configuration, {\n clean: true,\n forceRun: true,\n });\n }\n });\n } else if (event.type === 'delete') {\n processEvent(async () => {\n appLogger(\n [\n 'Entry point',\n formatPath(basename(event.path)),\n 'was removed, running clean rebuild...',\n ],\n { level: 'warn' }\n );\n await prepareIntlayer(configuration, {\n clean: true,\n forceRun: true,\n });\n });\n }\n }\n });\n subscriptions.push(mainDirSub);\n }\n\n // ── baseDir watcher (depth 0) — detect .intlayer directory removal ─────────\n // Native fs.watch is non-recursive and ideal for single-directory depth-0 detection.\n const intlayerDirName = basename(normalizedIntlayerDir);\n const fsDirWatcher = fsWatch(\n baseDir,\n { persistent: isWatchMode },\n (eventType, filename) => {\n if (isProcessing || !filename) return;\n if (filename !== intlayerDirName) return;\n\n const fullPath = normalizePath(resolve(baseDir, filename));\n if (fullPath !== normalizedIntlayerDir) return;\n\n if (eventType === 'rename' && !existsSync(normalizedIntlayerDir)) {\n appLogger([\n formatPath('.intlayer'),\n 'directory removed, running clean rebuild...',\n ]);\n processEvent(() =>\n prepareIntlayer(configuration, { clean: true, forceRun: true })\n );\n }\n }\n );\n subscriptions.push({\n unsubscribe: async () => {\n fsDirWatcher.close();\n },\n });\n\n // ── main content watcher ───────────────────────────────────────────────────\n // Ignore patterns for @parcel/watcher (micromatch globs)\n const ignorePatterns = excludedSegments.map((s) => `**/${s}/**`);\n\n // Strip trailing slashes so directory matching stays exact. A user-supplied\n const contentDirs = contentDir\n .map((dir) => normalizePath(dir).replace(/\\/+$/, ''))\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 { key, locale } = parseContentDeclarationFileName(\n filePath,\n configuration\n );\n\n // A markdown declaration carries a single locale body, so it\n // must always state which locale it holds.\n const isMarkdown =\n getFormatFromExtension(extname(filePath)) === 'md';\n const declaredLocale =\n locale ??\n (isMarkdown\n ? configuration.internationalization.defaultLocale\n : undefined);\n\n await writeContentDeclaration(\n {\n key,\n ...(declaredLocale && { locale: declaredLocale }),\n content: {},\n filePath,\n },\n configuration\n );\n }\n\n await handleAdditionalContentDeclarationFile(\n filePath,\n configuration\n );\n });\n });\n }\n } else if (event.type === 'update') {\n scheduleStable(path, () => {\n processEvent(async () => {\n if (isConfigFile) {\n appLogger('Configuration file changed, repreparing Intlayer');\n\n clearModuleCache(filePath);\n clearAllCache();\n\n const { configuration: newConfiguration } =\n getConfigurationAndFilePath(options?.configOptions);\n\n configuration = options?.configuration ?? newConfiguration;\n\n await prepareIntlayer(configuration, { clean: false });\n } else {\n // Clear module cache for the changed file to avoid stale require() results\n clearModuleCache(filePath);\n // Evict in-memory caches so loadContentDeclaration picks up fresh content\n clearAllCache();\n clearDiskCacheMemory();\n await handleContentDeclarationFileChange(filePath, configuration);\n }\n });\n });\n } else if (event.type === 'delete') {\n // Delay unlink processing to see if an 'add' event occurs (indicating a move)\n const timer = setTimeout(async () => {\n // If timer fires, the file was genuinely removed\n pendingUnlinks.delete(filePath);\n processEvent(async () =>\n handleUnlinkedContentDeclarationFile(filePath, configuration)\n );\n }, 200); // 200ms window to catch the 'create' event\n\n pendingUnlinks.set(filePath, { timer, oldPath: filePath });\n }\n }\n };\n\n for (const dir of dirsToWatch) {\n const sub = await subscribe(dir, contentHandler, {\n ignore: ignorePatterns,\n });\n\n subscriptions.push(sub);\n }\n\n return subscriptions;\n};\n\nexport const buildAndWatchIntlayer = async (options?: WatchOptions) => {\n const { skipPrepare, ...rest } = options ?? {};\n const configuration =\n options?.configuration ?? getConfiguration(options?.configOptions);\n\n if (!skipPrepare) {\n await prepareIntlayer(configuration, { forceRun: true });\n }\n\n // Only enter watch mode when the caller explicitly opts in via `persistent`.\n // `configuration.content.watch` is the dev-mode signal consumed by bundler\n // plugins (e.g. vite-intlayer's `configureServer`); it must not coerce\n // `intlayer build` (which passes `persistent: false`) into a persistent\n // watcher, since that prevents the build command from ever exiting.\n if (options?.persistent) {\n await watch({ ...rest, configuration });\n }\n};\n"],"mappings":"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"}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { LocalesValues } from "./intlayer/dist/types/index.js";
|
|
2
1
|
import { Dictionary } from "@intlayer/types/dictionary";
|
|
3
2
|
//#region src/formatDictionary.d.ts
|
|
4
3
|
export declare const formatDictionary: (dictionary: Dictionary) => Dictionary;
|
|
@@ -19,7 +18,7 @@ export declare const formatDictionaryOutput: (dictionary: Dictionary, format: Di
|
|
|
19
18
|
tags?: string[];
|
|
20
19
|
item?: number;
|
|
21
20
|
variant?: import("@intlayer/types").DictionaryVariantValue | import("@intlayer/types").DictionaryVariantValue[];
|
|
22
|
-
locale?: LocalesValues;
|
|
21
|
+
locale?: import("@intlayer/types").LocalesValues;
|
|
23
22
|
contentAutoTransformation?: import("@intlayer/types").ContentAutoTransformation;
|
|
24
23
|
fill?: import("@intlayer/types").Fill;
|
|
25
24
|
filled?: true;
|
|
@@ -46,7 +45,7 @@ export declare const formatDictionariesOutput: (dictionaries: Dictionary[], form
|
|
|
46
45
|
tags?: string[];
|
|
47
46
|
item?: number;
|
|
48
47
|
variant?: import("@intlayer/types").DictionaryVariantValue | import("@intlayer/types").DictionaryVariantValue[];
|
|
49
|
-
locale?: LocalesValues;
|
|
48
|
+
locale?: import("@intlayer/types").LocalesValues;
|
|
50
49
|
contentAutoTransformation?: import("@intlayer/types").ContentAutoTransformation;
|
|
51
50
|
fill?: import("@intlayer/types").Fill;
|
|
52
51
|
filled?: true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formatDictionary.d.ts","names":[],"sources":["../../src/formatDictionary.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"formatDictionary.d.ts","names":[],"sources":["../../src/formatDictionary.ts"],"mappings":";;qBAYa,mBAAgB,YAAgB,eAAa;qBAoC7C,qBAAkB,cACf,iBACb,QAAQ;qBAEE,yBAAsB,YACrB,YAAU,QACd;;;;;;;;;;;;;;;;;;;;;;;;;;IAmCT;qBAEY,2BAAwB,cACrB,cAAY,QAClB;;;;;;;;;;;;;;;;;;;;;;;;;;IAEoE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/init/index.ts"],"mappings":";;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/init/index.ts"],"mappings":";;;;;;;;;qBAiOa,2BAAwB,cACrB;;;;;;;;;;YAsBJ;;;;YAQA;EACV;;EAEA;;;;EAIA;;EAEA;;EAEA;;EAEA;;;;;EAKA;;;;;;EAMA;;;;;;EAMA,cAAc;;;;;;;EAOd,kBAAkB;;;;;qBAMP,eAAY,iBAAyB,UAAY,gBAAW"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
//#region src/init/utils/devScript.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Frameworks with no Intlayer bundler plugin to host the content watcher.
|
|
4
|
+
*
|
|
5
|
+
* Their dev server is a plain Node process, so the only way to rebuild
|
|
6
|
+
* `.intlayer` on a content change is to run the watcher next to it.
|
|
7
|
+
*/
|
|
8
|
+
export declare const BACKEND_INTLAYER_PACKAGES: string[];
|
|
9
|
+
export type ResolveDevScriptParams = {
|
|
10
|
+
/** The project's current `dev` script, if it has one. */
|
|
11
|
+
devScript?: string;
|
|
12
|
+
/** Combined prod + dev dependencies of the project. */
|
|
13
|
+
allDeps: Record<string, string>;
|
|
14
|
+
/** Whether a `next.config.*` file was found in the project. */
|
|
15
|
+
isNextJsProject: boolean;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Resolves the `dev` script `intlayer init` should write, or `null` when the
|
|
19
|
+
* project's script must be left alone.
|
|
20
|
+
*
|
|
21
|
+
* A Next.js app is always left alone: `withIntlayer` starts the content watcher
|
|
22
|
+
* itself on both bundlers, so wrapping the dev server in `intlayer watch --with`
|
|
23
|
+
* would only add a second watcher rebuilding the same dictionaries. This is checked on the
|
|
24
|
+
* `next` dependency as well as on the config file, since `next.config.*` is
|
|
25
|
+
* optional, and it takes precedence over the backend list, which a Next.js app
|
|
26
|
+
* with a custom server (or a monorepo root) can match too.
|
|
27
|
+
*
|
|
28
|
+
* @example a backend project gets its dev server wrapped
|
|
29
|
+
* ```ts
|
|
30
|
+
* resolveDevScript({
|
|
31
|
+
* devScript: 'node ./src/index.ts',
|
|
32
|
+
* allDeps: { 'express-intlayer': '9.0.0' },
|
|
33
|
+
* isNextJsProject: false,
|
|
34
|
+
* }); // → "intlayer watch --with 'node ./src/index.ts'"
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @example a Next.js project keeps its own script
|
|
38
|
+
* ```ts
|
|
39
|
+
* resolveDevScript({
|
|
40
|
+
* devScript: 'next dev',
|
|
41
|
+
* allDeps: { next: '16.0.0' },
|
|
42
|
+
* isNextJsProject: true,
|
|
43
|
+
* }); // → null
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export declare const resolveDevScript: ({ devScript, allDeps, isNextJsProject }: ResolveDevScriptParams) => string | null;
|
|
47
|
+
//#endregion
|
|
48
|
+
//# sourceMappingURL=devScript.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"devScript.d.ts","names":[],"sources":["../../../../src/init/utils/devScript.ts"],"mappings":";;;;;;;qBAMa;YAQD;;EAEV;;EAEA,SAAS;;EAET;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAgCW,qBAAgB,WAAA,SAAA,mBAI1B"}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { COMPAT_I18N_LIBRARIES, CompatI18nLibrary, CompatSyncConfig, CompatVitePluginConfig, DetectMissingPackagesOptions, IntlayerPackageAnalysis, PackageManager, detectCompatI18nLibraries, detectMissingIntlayerPackages, detectOutdatedIntlayerPackages, detectPackageManager, getInstalledPackageVersion, hasLintTooling, installPackages, isIntlayerPackageName, normalizeVersion, upgradePackages } from "./packageManager.js";
|
|
2
2
|
import { RoutingMode, enableIntlayerEditorConfig, getMetroConfigTemplate, hasIntlayerVitePlugin, replaceViteConfigPluginImportSource, setIntlayerConfigCompilerOutput, setIntlayerConfigRoutingMode, updateAstroConfig, updateIntlayerConfigWithSyncPlugin, updateMetroConfig, updateNextConfig, updateNextConfigForNextI18next, updateNextConfigForNextIntl, updateNextConfigForNextTranslate, updateNuxtConfig, updateNuxtConfigForNuxtjsI18n, updateViteConfig, updateViteConfigForCompatPlugin, updateViteConfigForVueI18n } from "./configManipulation.js";
|
|
3
3
|
import { ensureNamedImport, firstInsertIndex, isModuleScopeBinding } from "./astImports.js";
|
|
4
|
+
import { BACKEND_INTLAYER_PACKAGES, ResolveDevScriptParams, resolveDevScript } from "./devScript.js";
|
|
4
5
|
import { JsonLocalePattern, JsonLocalePatternType, LinguiCatalogPattern, NextIntlMessagesPattern, detectJsonLocalePattern, detectLinguiCatalogPattern, detectNextIntlMessagesPattern, ensureDirectory, exists, readFileFromRoot, writeFileToRoot } from "./fileSystem.js";
|
|
5
6
|
import { GITHUB_FILL_WORKFLOW_PATH, GITHUB_TEST_WORKFLOW_PATH, GithubWorkflowFile, GithubWorkflowsContext, GithubWorkflowsOptions, getGithubWorkflows, resolveGithubWorkflowsContext } from "./githubActions.js";
|
|
6
7
|
import { parseJSONWithComments } from "./jsonParser.js";
|
|
7
8
|
import { NEXT_INTLAYER_BABEL_CONFIG_CONTENT, setupNextCompilerBabelConfig } from "./nextCompilerBabel.js";
|
|
8
9
|
import { findTsConfigFiles } from "./tsConfig.js";
|
|
9
10
|
import { isVersionAtLeast } from "./version.js";
|
|
10
|
-
export { COMPAT_I18N_LIBRARIES, CompatI18nLibrary, CompatSyncConfig, CompatVitePluginConfig, DetectMissingPackagesOptions, GITHUB_FILL_WORKFLOW_PATH, GITHUB_TEST_WORKFLOW_PATH, GithubWorkflowFile, GithubWorkflowsContext, GithubWorkflowsOptions, IntlayerPackageAnalysis, JsonLocalePattern, JsonLocalePatternType, LinguiCatalogPattern, NEXT_INTLAYER_BABEL_CONFIG_CONTENT, NextIntlMessagesPattern, PackageManager, RoutingMode, detectCompatI18nLibraries, detectJsonLocalePattern, detectLinguiCatalogPattern, detectMissingIntlayerPackages, detectNextIntlMessagesPattern, detectOutdatedIntlayerPackages, detectPackageManager, enableIntlayerEditorConfig, ensureDirectory, ensureNamedImport, exists, findTsConfigFiles, firstInsertIndex, getGithubWorkflows, getInstalledPackageVersion, getMetroConfigTemplate, hasIntlayerVitePlugin, hasLintTooling, installPackages, isIntlayerPackageName, isModuleScopeBinding, isVersionAtLeast, normalizeVersion, parseJSONWithComments, readFileFromRoot, replaceViteConfigPluginImportSource, resolveGithubWorkflowsContext, setIntlayerConfigCompilerOutput, setIntlayerConfigRoutingMode, setupNextCompilerBabelConfig, updateAstroConfig, updateIntlayerConfigWithSyncPlugin, updateMetroConfig, updateNextConfig, updateNextConfigForNextI18next, updateNextConfigForNextIntl, updateNextConfigForNextTranslate, updateNuxtConfig, updateNuxtConfigForNuxtjsI18n, updateViteConfig, updateViteConfigForCompatPlugin, updateViteConfigForVueI18n, upgradePackages, writeFileToRoot };
|
|
11
|
+
export { BACKEND_INTLAYER_PACKAGES, COMPAT_I18N_LIBRARIES, CompatI18nLibrary, CompatSyncConfig, CompatVitePluginConfig, DetectMissingPackagesOptions, GITHUB_FILL_WORKFLOW_PATH, GITHUB_TEST_WORKFLOW_PATH, GithubWorkflowFile, GithubWorkflowsContext, GithubWorkflowsOptions, IntlayerPackageAnalysis, JsonLocalePattern, JsonLocalePatternType, LinguiCatalogPattern, NEXT_INTLAYER_BABEL_CONFIG_CONTENT, NextIntlMessagesPattern, PackageManager, ResolveDevScriptParams, RoutingMode, detectCompatI18nLibraries, detectJsonLocalePattern, detectLinguiCatalogPattern, detectMissingIntlayerPackages, detectNextIntlMessagesPattern, detectOutdatedIntlayerPackages, detectPackageManager, enableIntlayerEditorConfig, ensureDirectory, ensureNamedImport, exists, findTsConfigFiles, firstInsertIndex, getGithubWorkflows, getInstalledPackageVersion, getMetroConfigTemplate, hasIntlayerVitePlugin, hasLintTooling, installPackages, isIntlayerPackageName, isModuleScopeBinding, isVersionAtLeast, normalizeVersion, parseJSONWithComments, readFileFromRoot, replaceViteConfigPluginImportSource, resolveDevScript, resolveGithubWorkflowsContext, setIntlayerConfigCompilerOutput, setIntlayerConfigRoutingMode, setupNextCompilerBabelConfig, updateAstroConfig, updateIntlayerConfigWithSyncPlugin, updateMetroConfig, updateNextConfig, updateNextConfigForNextI18next, updateNextConfigForNextIntl, updateNextConfigForNextTranslate, updateNuxtConfig, updateNuxtConfigForNuxtjsI18n, updateViteConfig, updateViteConfigForCompatPlugin, updateViteConfigForVueI18n, upgradePackages, writeFileToRoot };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { IntlayerConfig } from "@intlayer/types/config";
|
|
1
2
|
import { Dictionary } from "@intlayer/types/dictionary";
|
|
2
3
|
//#region src/loadDictionaries/loadMarkdownContentDeclaration.d.ts
|
|
3
|
-
export declare const loadMarkdownContentDeclaration: (path: string) => Promise<Dictionary | undefined>;
|
|
4
|
+
export declare const loadMarkdownContentDeclaration: (path: string, configuration: IntlayerConfig) => Promise<Dictionary | undefined>;
|
|
4
5
|
//#endregion
|
|
5
6
|
//# sourceMappingURL=loadMarkdownContentDeclaration.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loadMarkdownContentDeclaration.d.ts","names":[],"sources":["../../../src/loadDictionaries/loadMarkdownContentDeclaration.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"loadMarkdownContentDeclaration.d.ts","names":[],"sources":["../../../src/loadDictionaries/loadMarkdownContentDeclaration.ts"],"mappings":";;;qBAqBa,iCAA8B,cAC7B,eACG,mBACd,QAAQ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createSyncPlugin.d.ts","names":[],"sources":["../../../src/syncPluginKit/createSyncPlugin.ts"],"mappings":";;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"createSyncPlugin.d.ts","names":[],"sources":["../../../src/syncPluginKit/createSyncPlugin.ts"],"mappings":";;;;;;;;;;;;;qBA4Ga,mBAAgB,SAAa,4BAA0B"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { ContentAdapter, ContentEntry, ContentTarget, CreateSyncPluginOptions, FormatCodec, SyncContent, SyncDirection, SyncPluginContext } from "./types.js";
|
|
1
|
+
import { ContentAdapter, ContentEntry, ContentTarget, CreateSyncPluginOptions, FormatCodec, SplitKeysMode, SyncContent, SyncDirection, SyncPluginContext } from "./types.js";
|
|
2
2
|
import { CreateFileAdapterOptions, FileAdapterDiscovery, createFileAdapter, createFileReader } from "./createFileAdapter.js";
|
|
3
3
|
import { createSyncPlugin } from "./createSyncPlugin.js";
|
|
4
4
|
import { extractKeyAndLocaleFromPath } from "./extractKeyAndLocaleFromPath.js";
|
|
5
5
|
import { buildFilePathPatternContext } from "./filePathPatternHelpers.js";
|
|
6
|
-
export { type ContentAdapter, type ContentEntry, type ContentTarget, type CreateFileAdapterOptions, type CreateSyncPluginOptions, type FileAdapterDiscovery, type FormatCodec, type SyncContent, type SyncDirection, type SyncPluginContext, buildFilePathPatternContext, createFileAdapter, createFileReader, createSyncPlugin, extractKeyAndLocaleFromPath };
|
|
6
|
+
export { type ContentAdapter, type ContentEntry, type ContentTarget, type CreateFileAdapterOptions, type CreateSyncPluginOptions, type FileAdapterDiscovery, type FormatCodec, type SplitKeysMode, type SyncContent, type SyncDirection, type SyncPluginContext, buildFilePathPatternContext, createFileAdapter, createFileReader, createSyncPlugin, extractKeyAndLocaleFromPath };
|
|
@@ -84,8 +84,23 @@ export type ContentAdapter = {
|
|
|
84
84
|
* set it explicitly (e.g. the file adapter splits when the source pattern
|
|
85
85
|
* has no `{{key}}` segment).
|
|
86
86
|
*/
|
|
87
|
-
detectSplitKeys?: () => Promise<
|
|
87
|
+
detectSplitKeys?: () => Promise<SplitKeysMode>;
|
|
88
88
|
};
|
|
89
|
+
/**
|
|
90
|
+
* How an entry's keys map onto dictionaries.
|
|
91
|
+
*
|
|
92
|
+
* - `false` — one dictionary holds the whole entry.
|
|
93
|
+
* - `true` — each *top-level* key becomes its own dictionary. The
|
|
94
|
+
* `next-intl` / `react-intl` namespace model, where a single
|
|
95
|
+
* `messages/{locale}.json` groups namespaces by first-level key.
|
|
96
|
+
* - `'key-prefix'` — the entry is a *flat* map whose keys are dotted ids
|
|
97
|
+
* (`'footer.github'`); each id's first dot-segment becomes a
|
|
98
|
+
* dictionary and the remainder becomes the key inside it. The
|
|
99
|
+
* single-catalog model of `lingui` and of i18next's default
|
|
100
|
+
* `translation` namespace, where the namespace lives in the id
|
|
101
|
+
* rather than in the file layout.
|
|
102
|
+
*/
|
|
103
|
+
export type SplitKeysMode = boolean | 'key-prefix';
|
|
89
104
|
/**
|
|
90
105
|
* Format layer of a sync plugin: how a raw string payload maps to structured
|
|
91
106
|
* dictionary content and back (JSON, PO, XLIFF…).
|
|
@@ -136,12 +151,11 @@ export type CreateSyncPluginOptions = {
|
|
|
136
151
|
/** Content format of the dictionaries (e.g. `'icu'`, `'po'`). */
|
|
137
152
|
format?: DictionaryFormat;
|
|
138
153
|
/**
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
* `
|
|
142
|
-
* {@link ContentAdapter.detectSplitKeys}, then to `false`.
|
|
154
|
+
* How the entry's keys map onto dictionaries — see {@link SplitKeysMode}.
|
|
155
|
+
* When omitted, falls back to {@link ContentAdapter.detectSplitKeys}, then
|
|
156
|
+
* to `false`.
|
|
143
157
|
*/
|
|
144
|
-
splitKeys?:
|
|
158
|
+
splitKeys?: SplitKeysMode;
|
|
145
159
|
/**
|
|
146
160
|
* Fixed locale applied to every generated dictionary, overriding the locale
|
|
147
161
|
* discovered per entry.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/syncPluginKit/types.ts"],"mappings":";;;;;;;YAOY,cAAc;;;;YAKd;EACV,eAAe;;;;;YAML;;EAEV;;EAEA,QAAQ;;;;;EAKR;;;;;;EAMA;;;;;YAMU;EACV;EACA,QAAQ;;;;;;;;YASE;;;;EAIV,OAAO,SAAS,sBAAsB,QAAQ;;;;;;EAO9C,OACE,OAAO,cACP,SAAS,sBACN,QAAQ;;;;;EAMb,QACE,QAAQ,eACR,SAAS,aACT,SAAS,sBACN;;;;;;;EAQL,cACE,QAAQ,eACR,SAAS,sBACN;;;;;;EAOL,kBAAkB,SAAS,sBAAsB;;;;;EAMjD,kBAAkB,SAAS,sBAAsB;;;;;;EAOjD,wBAAwB
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/syncPluginKit/types.ts"],"mappings":";;;;;;;YAOY,cAAc;;;;YAKd;EACV,eAAe;;;;;YAML;;EAEV;;EAEA,QAAQ;;;;;EAKR;;;;;;EAMA;;;;;YAMU;EACV;EACA,QAAQ;;;;;;;;YASE;;;;EAIV,OAAO,SAAS,sBAAsB,QAAQ;;;;;;EAO9C,OACE,OAAO,cACP,SAAS,sBACN,QAAQ;;;;;EAMb,QACE,QAAQ,eACR,SAAS,aACT,SAAS,sBACN;;;;;;;EAQL,cACE,QAAQ,eACR,SAAS,sBACN;;;;;;EAOL,kBAAkB,SAAS,sBAAsB;;;;;EAMjD,kBAAkB,SAAS,sBAAsB;;;;;;EAOjD,wBAAwB,QAAQ;;;;;;;;;;;;;;;;YAiBtB;;;;;YAMA;;;;;EAKV,QAAQ,gBAAgB;;;;EAKxB,YAAY,SAAS,aAAa;IAAW,QAAQ;;;;;;;;YAQ3C;;;;YAKA;;EAEV;;EAGA,SAAS;;;;;EAMT,YAAY;;;;;;EAOZ;;;;;EAMA;;EAGA,SAAS;;;;;;EAOT,YAAY;;;;;EAMZ,iBAAiB"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { IntlayerConfig } from "@intlayer/types/config";
|
|
2
|
+
//#region src/utils/contentWatcherLock.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* What kind of process is watching the content declarations.
|
|
5
|
+
*
|
|
6
|
+
* - `cli` — the `intlayer watch` / `intlayer build --watch` command.
|
|
7
|
+
* - `bundler` — an Intlayer bundler plugin (e.g. `withIntlayer` on Next.js).
|
|
8
|
+
*/
|
|
9
|
+
export type ContentWatcherSource = 'cli' | 'bundler';
|
|
10
|
+
export type ContentWatcherOwner = {
|
|
11
|
+
/** PID of the process holding the watcher. */
|
|
12
|
+
pid: number;
|
|
13
|
+
source: ContentWatcherSource;
|
|
14
|
+
/**
|
|
15
|
+
* How the owner should be named in a message, e.g. `intlayer watch` or
|
|
16
|
+
* `next-intlayer`.
|
|
17
|
+
*/
|
|
18
|
+
label: string;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Set on every process spawned by `intlayer watch --with` (and
|
|
22
|
+
* `intlayer build --watch --with`), so a bundler plugin running as that child
|
|
23
|
+
* knows a CLI watcher is already covering the project — without having to
|
|
24
|
+
* depend on the two processes reaching the lock file in a given order.
|
|
25
|
+
*
|
|
26
|
+
* Holds the label of the command that set it.
|
|
27
|
+
*/
|
|
28
|
+
export declare const CLI_CONTENT_WATCHER_ENV_VAR = "INTLAYER_CLI_CONTENT_WATCHER";
|
|
29
|
+
/**
|
|
30
|
+
* Path of the lock coordinating every content watcher of one project.
|
|
31
|
+
*
|
|
32
|
+
* Deliberately at the root of `.intlayer` rather than in its `cache`
|
|
33
|
+
* subdirectory: `cleanOutputDir` wipes every subdirectory of `.intlayer`, so a
|
|
34
|
+
* lock kept in `cache` would be erased by any `prepareIntlayer` that cleans —
|
|
35
|
+
* after which a bundler plugin would see no owner and start a second watcher
|
|
36
|
+
* next to the `intlayer watch` still running. The root itself is never removed.
|
|
37
|
+
*
|
|
38
|
+
* @param configuration - The resolved Intlayer configuration.
|
|
39
|
+
*/
|
|
40
|
+
export declare const getContentWatcherLockFilePath: (configuration: IntlayerConfig) => string;
|
|
41
|
+
/**
|
|
42
|
+
* Reads the lock, removing it when it describes a process that has since died.
|
|
43
|
+
*
|
|
44
|
+
* An unreadable or malformed lock is treated as abandoned: a corrupted file
|
|
45
|
+
* must never block the watcher for a whole dev session.
|
|
46
|
+
*
|
|
47
|
+
* @returns The live owner, or `null` when nothing is watching.
|
|
48
|
+
*/
|
|
49
|
+
export declare const getContentWatcherOwner: (configuration: IntlayerConfig) => Promise<ContentWatcherOwner | null>;
|
|
50
|
+
/**
|
|
51
|
+
* Tries to become the process that watches this project's content
|
|
52
|
+
* declarations.
|
|
53
|
+
*
|
|
54
|
+
* Creating the file with `wx` is atomic, so exactly one process wins even when
|
|
55
|
+
* several try at the same moment — which is the normal case on Next.js, where
|
|
56
|
+
* `next.config.*` is evaluated in more than one process.
|
|
57
|
+
*
|
|
58
|
+
* @param configuration - The resolved Intlayer configuration.
|
|
59
|
+
* @param owner - How this process should describe itself to the others.
|
|
60
|
+
* @returns `true` when this process now owns the watcher.
|
|
61
|
+
*/
|
|
62
|
+
export declare const acquireContentWatcherLock: (configuration: IntlayerConfig, owner: Omit<ContentWatcherOwner, 'pid'>) => Promise<boolean>;
|
|
63
|
+
/**
|
|
64
|
+
* The CLI watcher command this process was spawned by, or `null` when it was
|
|
65
|
+
* not started through `intlayer watch --with`.
|
|
66
|
+
*/
|
|
67
|
+
export declare const getCliContentWatcherLabel: () => string | null;
|
|
68
|
+
/**
|
|
69
|
+
* Registers a CLI command as the content watcher of this project.
|
|
70
|
+
*
|
|
71
|
+
* Call it *before* spawning a `--with` child: taking the lock first means the
|
|
72
|
+
* bundler plugin inside that child never races the command for it, and the
|
|
73
|
+
* environment marker makes the child's decision independent of the filesystem
|
|
74
|
+
* altogether.
|
|
75
|
+
*
|
|
76
|
+
* @param configuration - The resolved Intlayer configuration.
|
|
77
|
+
* @param label - How the command should be named in a message.
|
|
78
|
+
* @returns The bundler already watching this project, or `null` when the
|
|
79
|
+
* command took the watcher for itself. A CLI watcher never stands down on that
|
|
80
|
+
* answer — watching is what it was asked to do — but reporting it lets the user
|
|
81
|
+
* know the parallel command has become redundant.
|
|
82
|
+
*/
|
|
83
|
+
export declare const claimCliContentWatcher: (configuration: IntlayerConfig, label: string) => Promise<ContentWatcherOwner | null>;
|
|
84
|
+
type RedundantWatcherReport = {
|
|
85
|
+
/** Name of the CLI command watching, e.g. `intlayer watch`. */
|
|
86
|
+
cliLabel: string;
|
|
87
|
+
/** Name of the bundler integration watching, e.g. `next-intlayer`. */
|
|
88
|
+
bundlerLabel: string;
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Reports a CLI watcher running alongside a bundler integration that watches
|
|
92
|
+
* on its own, and points at the parallel command as the part to drop.
|
|
93
|
+
*
|
|
94
|
+
* Emitted at most once per process: a bundler config is evaluated several times
|
|
95
|
+
* per command, and callers may re-enter this on a retry loop.
|
|
96
|
+
*
|
|
97
|
+
* @param configuration - The resolved Intlayer configuration.
|
|
98
|
+
* @param report - Who is watching in parallel with whom.
|
|
99
|
+
*/
|
|
100
|
+
export declare const reportRedundantContentWatcher: (configuration: IntlayerConfig, { cliLabel, bundlerLabel }: RedundantWatcherReport) => void;
|
|
101
|
+
//#endregion
|
|
102
|
+
//# sourceMappingURL=contentWatcherLock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contentWatcherLock.d.ts","names":[],"sources":["../../../src/utils/contentWatcherLock.ts"],"mappings":";;;;;;;;YAaY;YAEA;;EAEV;EACA,QAAQ;;;;;EAKR;;;;;;;;;;qBAWW;;;;;;;;;;;;qBAkCA,gCAA6B,eACzB;;;;;;;;;qBA2BJ,yBAAsB,eAClB,mBACd,QAAQ;;;;;;;;;;;;;qBAsCE,4BAAyB,eACrB,gBAAc,OACtB,KAAK,gCACX;;;;;qBAkCU;;;;;;;;;;;;;;;;qBAkBA,yBAAsB,eAClB,gBAAc,kBAE5B,QAAQ;KAoBN;;EAEH;;EAEA;;;;;;;;;;;;qBAaW,gCAA6B,eACzB,kBAAc,UAAA,gBACD"}
|
|
@@ -3,6 +3,7 @@ import { autoDecorateContent } from "./autoDecorateContent.js";
|
|
|
3
3
|
import { buildComponentFilesList } from "./buildComponentFilesList.js";
|
|
4
4
|
import { chunkArray } from "./chunkArray.js";
|
|
5
5
|
import { JSONObject, JsonChunk, assembleJSON, chunkJSON, reconstructFromSingleChunk } from "./chunkJSON.js";
|
|
6
|
+
import { CLI_CONTENT_WATCHER_ENV_VAR, ContentWatcherOwner, ContentWatcherSource, acquireContentWatcherLock, claimCliContentWatcher, getCliContentWatcherLabel, getContentWatcherLockFilePath, getContentWatcherOwner, reportRedundantContentWatcher } from "./contentWatcherLock.js";
|
|
6
7
|
import { formatLocale, formatPath } from "./formatter.js";
|
|
7
8
|
import { getChunk } from "./getChunk.js";
|
|
8
9
|
import { getContentExtension } from "./getContentExtension.js";
|
|
@@ -11,6 +12,7 @@ import { mergeChunks } from "./mergeChunks.js";
|
|
|
11
12
|
import { parallelize } from "./parallelize.js";
|
|
12
13
|
import { Queue, pLimit } from "./pLimit.js";
|
|
13
14
|
import { getGlobalLimiter, getTaskLimiter, parallelizeGlobal } from "./parallelizeGlobal.js";
|
|
15
|
+
import { ContentDeclarationFileName, parseContentDeclarationFileName } from "./parseContentDeclarationFileName.js";
|
|
14
16
|
import { Primitive, Recursive, excludeObjectFormat, reduceObjectFormat } from "./reduceObjectFormat.js";
|
|
15
17
|
import { resolveObjectPromises } from "./resolveObjectPromises.js";
|
|
16
18
|
import { resolveRelativePath } from "./resolveRelativePath.js";
|
|
@@ -19,4 +21,5 @@ import { ParallelHandle, runParallel } from "./runParallel/index.js";
|
|
|
19
21
|
import { sortAlphabetically } from "./sortAlphabetically.js";
|
|
20
22
|
import { splitTextByLines } from "./splitTextByLine.js";
|
|
21
23
|
import { verifyIdenticObjectFormat } from "./verifyIdenticObjectFormat.js";
|
|
22
|
-
|
|
24
|
+
import { withFileLock } from "./withFileLock.js";
|
|
25
|
+
export { CLI_CONTENT_WATCHER_ENV_VAR, ContentDeclarationFileName, ContentWatcherOwner, ContentWatcherSource, Extension, Format, JSONObject, JsonChunk, ParallelHandle, Primitive, Queue, Recursive, RunOnceContext, acquireContentWatcherLock, assembleJSON, autoDecorateContent, buildComponentFilesList, chunkArray, chunkJSON, claimCliContentWatcher, excludeObjectFormat, formatLocale, formatPath, getChunk, getCliContentWatcherLabel, getContentExtension, getContentWatcherLockFilePath, getContentWatcherOwner, getExtensionFromFormat, getFormatFromExtension, getGlobalLimiter, getPathHash, getTaskLimiter, mergeChunks, pLimit, parallelize, parallelizeGlobal, parseContentDeclarationFileName, reconstructFromSingleChunk, reduceObjectFormat, reportRedundantContentWatcher, resolveObjectPromises, resolveRelativePath, runOnce, runParallel, sortAlphabetically, splitTextByLines, verifyIdenticObjectFormat, withFileLock };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { IntlayerConfig } from "@intlayer/types/config";
|
|
2
|
+
import { LocalesValues } from "@intlayer/types/module_augmentation";
|
|
3
|
+
//#region src/utils/parseContentDeclarationFileName.d.ts
|
|
4
|
+
export type ContentDeclarationFileName = {
|
|
5
|
+
/** Dictionary key derived from the file name, locale suffix excluded */
|
|
6
|
+
key: string;
|
|
7
|
+
/** Locale declared as a file name suffix, e.g. `about.fr.content.md` -> `fr` */
|
|
8
|
+
locale?: LocalesValues;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Splits a content declaration file name into its dictionary key and its
|
|
12
|
+
* optional locale suffix.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* parseContentDeclarationFileName('src/about.content.ts', configuration)
|
|
16
|
+
* // { key: 'about' }
|
|
17
|
+
*
|
|
18
|
+
* parseContentDeclarationFileName('src/about.fr.content.md', configuration)
|
|
19
|
+
* // { key: 'about', locale: 'fr' }
|
|
20
|
+
*/
|
|
21
|
+
export declare const parseContentDeclarationFileName: (filePath: string, configuration: IntlayerConfig) => ContentDeclarationFileName;
|
|
22
|
+
//#endregion
|
|
23
|
+
//# sourceMappingURL=parseContentDeclarationFileName.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parseContentDeclarationFileName.d.ts","names":[],"sources":["../../../src/utils/parseContentDeclarationFileName.ts"],"mappings":";;;YAIY;;EAEV;;EAEA,SAAS;;;;;;;;;;;;;qBAiBE,kCAA+B,kBAC1B,eACD,mBACd"}
|