@intlayer/engine 9.5.11 → 9.6.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/assets/installSkills/skills/bundle-optimization.md +123 -0
- package/dist/assets/installSkills/skills/cli.md +28 -1
- package/dist/assets/installSkills/skills/compat.md +1 -5
- package/dist/assets/installSkills/skills/content.md +8 -2
- package/dist/assets/installSkills/skills/dev-tools.md +8 -6
- package/dist/assets/installSkills/skills/next-js.md +2 -2
- package/dist/assets/installSkills/skills/usage.md +1 -1
- package/dist/cjs/build.cjs +5 -0
- package/dist/cjs/cli.cjs +3 -0
- package/dist/cjs/init/index.cjs +14 -14
- package/dist/cjs/init/index.cjs.map +1 -1
- package/dist/cjs/init/utils/backendPackages.cjs +15 -0
- package/dist/cjs/init/utils/backendPackages.cjs.map +1 -0
- package/dist/cjs/init/utils/index.cjs +2 -3
- package/dist/cjs/init/utils/packageManager.cjs +1 -1
- package/dist/cjs/init/utils/packageManager.cjs.map +1 -1
- package/dist/cjs/installMCP/installMCP.cjs +35 -25
- package/dist/cjs/installMCP/installMCP.cjs.map +1 -1
- package/dist/cjs/installSkills/index.cjs +28 -1
- package/dist/cjs/installSkills/index.cjs.map +1 -1
- package/dist/cjs/listGitFiles.cjs +3 -5
- package/dist/cjs/listGitFiles.cjs.map +1 -1
- package/dist/cjs/listProjects.cjs +1 -2
- package/dist/cjs/listProjects.cjs.map +1 -1
- package/dist/cjs/loadDictionaries/loadDictionaries.cjs +9 -8
- package/dist/cjs/loadDictionaries/loadDictionaries.cjs.map +1 -1
- package/dist/cjs/loadDictionaries/loadRemoteDictionaries.cjs +3 -5
- package/dist/cjs/loadDictionaries/loadRemoteDictionaries.cjs.map +1 -1
- package/dist/cjs/loadDictionaries/remoteDictionaryClient.cjs +18 -0
- package/dist/cjs/loadDictionaries/remoteDictionaryClient.cjs.map +1 -0
- package/dist/cjs/prepareIntlayerServer.cjs +132 -0
- package/dist/cjs/prepareIntlayerServer.cjs.map +1 -0
- package/dist/cjs/utils/getProcessChainCommands.cjs +117 -0
- package/dist/cjs/utils/getProcessChainCommands.cjs.map +1 -0
- package/dist/cjs/utils/index.cjs +2 -0
- package/dist/cjs/utils/startContentWatcher.cjs +99 -0
- package/dist/cjs/utils/startContentWatcher.cjs.map +1 -0
- package/dist/cjs/watcher.cjs +2 -2
- package/dist/cjs/watcher.cjs.map +1 -1
- package/dist/cjs/writeJsonIfChanged.cjs.map +1 -1
- package/dist/esm/build.mjs +2 -1
- package/dist/esm/cli.mjs +4 -4
- package/dist/esm/init/index.mjs +13 -13
- package/dist/esm/init/index.mjs.map +1 -1
- package/dist/esm/init/utils/backendPackages.mjs +14 -0
- package/dist/esm/init/utils/backendPackages.mjs.map +1 -0
- package/dist/esm/init/utils/index.mjs +2 -2
- package/dist/esm/init/utils/packageManager.mjs +1 -1
- package/dist/esm/init/utils/packageManager.mjs.map +1 -1
- package/dist/esm/installMCP/installMCP.mjs +35 -25
- package/dist/esm/installMCP/installMCP.mjs.map +1 -1
- package/dist/esm/installSkills/index.mjs +29 -3
- package/dist/esm/installSkills/index.mjs.map +1 -1
- package/dist/esm/listGitFiles.mjs +1 -1
- package/dist/esm/listGitFiles.mjs.map +1 -1
- package/dist/esm/listProjects.mjs +1 -1
- package/dist/esm/listProjects.mjs.map +1 -1
- package/dist/esm/loadDictionaries/loadDictionaries.mjs +9 -8
- package/dist/esm/loadDictionaries/loadDictionaries.mjs.map +1 -1
- package/dist/esm/loadDictionaries/loadRemoteDictionaries.mjs +2 -4
- package/dist/esm/loadDictionaries/loadRemoteDictionaries.mjs.map +1 -1
- package/dist/esm/loadDictionaries/remoteDictionaryClient.mjs +16 -0
- package/dist/esm/loadDictionaries/remoteDictionaryClient.mjs.map +1 -0
- package/dist/esm/prepareIntlayerServer.mjs +128 -0
- package/dist/esm/prepareIntlayerServer.mjs.map +1 -0
- package/dist/esm/utils/getProcessChainCommands.mjs +115 -0
- package/dist/esm/utils/getProcessChainCommands.mjs.map +1 -0
- package/dist/esm/utils/index.mjs +2 -1
- package/dist/esm/utils/runParallel/pidTree.mjs +3 -3
- package/dist/esm/utils/runParallel/pidTree.mjs.map +1 -1
- package/dist/esm/utils/runParallel/ps.mjs +2 -2
- package/dist/esm/utils/runParallel/ps.mjs.map +1 -1
- package/dist/esm/utils/runParallel/wmic.mjs +2 -2
- package/dist/esm/utils/runParallel/wmic.mjs.map +1 -1
- package/dist/esm/utils/startContentWatcher.mjs +98 -0
- package/dist/esm/utils/startContentWatcher.mjs.map +1 -0
- package/dist/esm/watcher.mjs +1 -1
- package/dist/esm/watcher.mjs.map +1 -1
- package/dist/esm/writeJsonIfChanged.mjs +1 -1
- package/dist/esm/writeJsonIfChanged.mjs.map +1 -1
- package/dist/types/build.d.ts +2 -1
- package/dist/types/cli.d.ts +4 -4
- package/dist/types/init/index.d.ts +1 -1
- package/dist/types/init/index.d.ts.map +1 -1
- package/dist/types/init/utils/backendPackages.d.ts +5 -0
- package/dist/types/init/utils/backendPackages.d.ts.map +1 -0
- package/dist/types/init/utils/index.d.ts +2 -2
- package/dist/types/init/utils/packageManager.d.ts +5 -0
- package/dist/types/init/utils/packageManager.d.ts.map +1 -1
- package/dist/types/installMCP/installMCP.d.ts +4 -0
- package/dist/types/installMCP/installMCP.d.ts.map +1 -1
- package/dist/types/installSkills/index.d.ts +7 -0
- package/dist/types/installSkills/index.d.ts.map +1 -1
- package/dist/types/loadDictionaries/loadRemoteDictionaries.d.ts.map +1 -1
- package/dist/types/loadDictionaries/remoteDictionaryClient.d.ts +14 -0
- package/dist/types/loadDictionaries/remoteDictionaryClient.d.ts.map +1 -0
- package/dist/types/prepareIntlayerServer.d.ts +67 -0
- package/dist/types/prepareIntlayerServer.d.ts.map +1 -0
- package/dist/types/utils/getProcessChainCommands.d.ts +35 -0
- package/dist/types/utils/getProcessChainCommands.d.ts.map +1 -0
- package/dist/types/utils/index.d.ts +2 -1
- package/dist/types/utils/startContentWatcher.d.ts +40 -0
- package/dist/types/utils/startContentWatcher.d.ts.map +1 -0
- package/dist/types/watcher.d.ts.map +1 -1
- package/package.json +9 -9
- package/dist/cjs/init/utils/devScript.cjs +0 -57
- package/dist/cjs/init/utils/devScript.cjs.map +0 -1
- package/dist/esm/init/utils/devScript.mjs +0 -55
- package/dist/esm/init/utils/devScript.mjs.map +0 -1
- package/dist/types/init/utils/devScript.d.ts +0 -48
- package/dist/types/init/utils/devScript.d.ts.map +0 -1
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
const require_utils_startContentWatcher = require('./utils/startContentWatcher.cjs');
|
|
3
|
+
const require_prepareIntlayer = require('./prepareIntlayer.cjs');
|
|
4
|
+
const require_utils_getProcessChainCommands = require('./utils/getProcessChainCommands.cjs');
|
|
5
|
+
let _intlayer_config_logger = require("@intlayer/config/logger");
|
|
6
|
+
|
|
7
|
+
//#region src/prepareIntlayerServer.ts
|
|
8
|
+
/**
|
|
9
|
+
* Explicit override, for any setup the detection below does not recognise:
|
|
10
|
+
* `INTLAYER_WATCH=true` forces the content watcher on, `INTLAYER_WATCH=false`
|
|
11
|
+
* forces it off.
|
|
12
|
+
*/
|
|
13
|
+
const INTLAYER_WATCH_ENV_VAR = "INTLAYER_WATCH";
|
|
14
|
+
/** Runtime flags that restart or reload the process on file changes. */
|
|
15
|
+
const WATCH_MODE_EXEC_ARGUMENTS = [
|
|
16
|
+
"--watch",
|
|
17
|
+
"--watch-path",
|
|
18
|
+
"--hot"
|
|
19
|
+
];
|
|
20
|
+
/**
|
|
21
|
+
* Environment variables set on the child process by a restarting watcher:
|
|
22
|
+
* `node --watch` and `ts-node-dev`.
|
|
23
|
+
*/
|
|
24
|
+
const WATCH_MODE_ENV_VARS = ["WATCH_REPORT_DEPENDENCIES", "TS_NODE_DEV"];
|
|
25
|
+
/**
|
|
26
|
+
* Package script names that start a dev server (`dev`, `start:dev`,
|
|
27
|
+
* `dev:api`, `watch`…), matched on the `npm_lifecycle_event` that npm, pnpm,
|
|
28
|
+
* yarn and bun set for `<pm> run <script>`.
|
|
29
|
+
*/
|
|
30
|
+
const DEV_SCRIPT_NAME_PATTERN = /\b(dev|develop|development|watch)\b/;
|
|
31
|
+
/**
|
|
32
|
+
* Command lines of the tools that restart a server on file changes, matched on
|
|
33
|
+
* this process and the ones above it. Needed because most of them leave no
|
|
34
|
+
* trace in the child itself.
|
|
35
|
+
*
|
|
36
|
+
* - Dedicated restarters, matched on their executable name (`nodemon`,
|
|
37
|
+
* `watchexec`, `entr`…).
|
|
38
|
+
* - `tsx watch`, whose watch mode is a subcommand.
|
|
39
|
+
* - Any runtime or CLI given `--watch` / `--hot`: `deno run --watch` (which
|
|
40
|
+
* restarts in-process), `nest start --watch`, `tsup --watch --onSuccess`…
|
|
41
|
+
* - The `-w` shorthand of build tools that run the server on each rebuild
|
|
42
|
+
* (`tsdown -w --on-success "node dist/index.mjs"`).
|
|
43
|
+
*/
|
|
44
|
+
const DEV_WATCHER_COMMAND_PATTERNS = [
|
|
45
|
+
/(^|[\s/])(nodemon|ts-node-dev|tsnd|node-dev|tsc-watch|supervisor|watchexec|entr|chokidar|onchange)(\.c?m?js)?(\s|$)/,
|
|
46
|
+
/(^|[\s/])tsx(\/dist\/cli\.m?js)?\s+watch(\s|$)/,
|
|
47
|
+
/\s--(watch|watch-path|hot)(=|\s|$)/,
|
|
48
|
+
/(^|[\s/])(tsdown|tsup|tsc|rollup|rolldown)(\/\S*)?(\s+\S+)*\s+-w(\s|$)/
|
|
49
|
+
];
|
|
50
|
+
/**
|
|
51
|
+
* Whether a command line is a watcher that restarts its child on changes.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```ts
|
|
55
|
+
* getIsDevWatcherCommand('node /app/node_modules/.bin/nodemon src/index.ts'); // → true
|
|
56
|
+
* getIsDevWatcherCommand('node dist/index.js'); // → false
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
const getIsDevWatcherCommand = (command) => DEV_WATCHER_COMMAND_PATTERNS.some((pattern) => pattern.test(command));
|
|
60
|
+
/**
|
|
61
|
+
* Whether this process runs under a watcher that restarts or reloads it
|
|
62
|
+
* (`bun --watch`/`--hot`, `node --watch`, `ts-node-dev`).
|
|
63
|
+
*/
|
|
64
|
+
const getIsWatchModeProcess = () => process.execArgv.some((argument) => WATCH_MODE_EXEC_ARGUMENTS.some((flag) => argument === flag || argument.startsWith(`${flag}=`))) || WATCH_MODE_ENV_VARS.some((envVar) => Boolean(process.env[envVar]));
|
|
65
|
+
/**
|
|
66
|
+
* Whether a server process runs in development, and so should watch the
|
|
67
|
+
* content declarations.
|
|
68
|
+
*
|
|
69
|
+
* `INTLAYER_WATCH=true|false` decides first, then `NODE_ENV` when set. An unset
|
|
70
|
+
* `NODE_ENV` is not a sign of development (`bun dist/index.mjs` rarely sets
|
|
71
|
+
* it), so the process must then show a positive signal, cheapest first:
|
|
72
|
+
* 1. a runtime watch flag or marker (`bun --watch`/`--hot`, `node --watch`,
|
|
73
|
+
* `ts-node-dev`);
|
|
74
|
+
* 2. a `dev`-like package script;
|
|
75
|
+
* 3. a restarting watcher in its own command line or its ancestors'
|
|
76
|
+
* (`deno run --watch`, `nodemon`, `tsx watch`…), for those started
|
|
77
|
+
* directly rather than through a script.
|
|
78
|
+
*/
|
|
79
|
+
const getIsServerDevelopment = async () => {
|
|
80
|
+
const watchOverride = process.env[INTLAYER_WATCH_ENV_VAR]?.toLowerCase();
|
|
81
|
+
if (watchOverride === "true" || watchOverride === "1") return true;
|
|
82
|
+
if (watchOverride === "false" || watchOverride === "0") return false;
|
|
83
|
+
const nodeEnv = process.env.NODE_ENV;
|
|
84
|
+
if (nodeEnv) return nodeEnv === "development";
|
|
85
|
+
if (getIsWatchModeProcess()) return true;
|
|
86
|
+
if (DEV_SCRIPT_NAME_PATTERN.test(process.env["npm_lifecycle_event"] ?? "")) return true;
|
|
87
|
+
return (await require_utils_getProcessChainCommands.getProcessChainCommands()).some(getIsDevWatcherCommand);
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* The dev check of this process, computed once: its answer cannot change for
|
|
91
|
+
* the life of the process, and Fastify, Elysia or Remix may register the
|
|
92
|
+
* integration more than once.
|
|
93
|
+
*/
|
|
94
|
+
let serverDevelopmentPromise;
|
|
95
|
+
const getIsServerDevelopmentOnce = () => {
|
|
96
|
+
serverDevelopmentPromise ??= getIsServerDevelopment();
|
|
97
|
+
return serverDevelopmentPromise;
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Prepares the dictionaries for a server integration (Express, Fastify, Hono…)
|
|
101
|
+
* and, in development, keeps them in sync with the content declarations.
|
|
102
|
+
*
|
|
103
|
+
* Replaces wrapping the dev server in `intlayer watch --with`: the watcher runs
|
|
104
|
+
* inside the server, deduplicated through the project's watcher lock, so a
|
|
105
|
+
* server restarted by `tsx watch` / `node --watch` hands it over to its
|
|
106
|
+
* successor, and a remaining `intlayer watch` makes it stand down.
|
|
107
|
+
*
|
|
108
|
+
* Fire-and-forget: the server must not wait on the build to start listening.
|
|
109
|
+
*
|
|
110
|
+
* @param configuration - The resolved Intlayer configuration.
|
|
111
|
+
* @param options - How the integration identifies itself, and whether to watch.
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```ts
|
|
115
|
+
* prepareIntlayerServer(getConfiguration(), { label: 'express-intlayer' });
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
const prepareIntlayerServer = (configuration, { label, watch }) => {
|
|
119
|
+
require_prepareIntlayer.prepareIntlayer(configuration).then(async () => {
|
|
120
|
+
if (!configuration.content.watch) return;
|
|
121
|
+
if (watch ?? await getIsServerDevelopmentOnce()) require_utils_startContentWatcher.startContentWatcher(configuration, { label });
|
|
122
|
+
}).catch((error) => {
|
|
123
|
+
(0, _intlayer_config_logger.getAppLogger)(configuration)(["Failed to prepare Intlayer dictionaries:", error], { level: "error" });
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
//#endregion
|
|
128
|
+
exports.INTLAYER_WATCH_ENV_VAR = INTLAYER_WATCH_ENV_VAR;
|
|
129
|
+
exports.getIsDevWatcherCommand = getIsDevWatcherCommand;
|
|
130
|
+
exports.getIsServerDevelopment = getIsServerDevelopment;
|
|
131
|
+
exports.prepareIntlayerServer = prepareIntlayerServer;
|
|
132
|
+
//# sourceMappingURL=prepareIntlayerServer.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prepareIntlayerServer.cjs","names":["getProcessChainCommands"],"sources":["../../src/prepareIntlayerServer.ts"],"sourcesContent":["import { getAppLogger } from '@intlayer/config/logger';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { prepareIntlayer } from './prepareIntlayer';\nimport { getProcessChainCommands } from './utils/getProcessChainCommands';\nimport { startContentWatcher } from './utils/startContentWatcher';\n\n/**\n * Explicit override, for any setup the detection below does not recognise:\n * `INTLAYER_WATCH=true` forces the content watcher on, `INTLAYER_WATCH=false`\n * forces it off.\n */\nexport const INTLAYER_WATCH_ENV_VAR = 'INTLAYER_WATCH';\n\n/** Runtime flags that restart or reload the process on file changes. */\nconst WATCH_MODE_EXEC_ARGUMENTS = ['--watch', '--watch-path', '--hot'];\n\n/**\n * Environment variables set on the child process by a restarting watcher:\n * `node --watch` and `ts-node-dev`.\n */\nconst WATCH_MODE_ENV_VARS = ['WATCH_REPORT_DEPENDENCIES', 'TS_NODE_DEV'];\n\n/**\n * Package script names that start a dev server (`dev`, `start:dev`,\n * `dev:api`, `watch`…), matched on the `npm_lifecycle_event` that npm, pnpm,\n * yarn and bun set for `<pm> run <script>`.\n */\nconst DEV_SCRIPT_NAME_PATTERN = /\\b(dev|develop|development|watch)\\b/;\n\n/**\n * Command lines of the tools that restart a server on file changes, matched on\n * this process and the ones above it. Needed because most of them leave no\n * trace in the child itself.\n *\n * - Dedicated restarters, matched on their executable name (`nodemon`,\n * `watchexec`, `entr`…).\n * - `tsx watch`, whose watch mode is a subcommand.\n * - Any runtime or CLI given `--watch` / `--hot`: `deno run --watch` (which\n * restarts in-process), `nest start --watch`, `tsup --watch --onSuccess`…\n * - The `-w` shorthand of build tools that run the server on each rebuild\n * (`tsdown -w --on-success \"node dist/index.mjs\"`).\n */\nconst DEV_WATCHER_COMMAND_PATTERNS = [\n /(^|[\\s/])(nodemon|ts-node-dev|tsnd|node-dev|tsc-watch|supervisor|watchexec|entr|chokidar|onchange)(\\.c?m?js)?(\\s|$)/,\n /(^|[\\s/])tsx(\\/dist\\/cli\\.m?js)?\\s+watch(\\s|$)/,\n /\\s--(watch|watch-path|hot)(=|\\s|$)/,\n // `-w` alone is too common a flag (`grep -w`), so it only counts after a\n // build tool whose `-w` means watch (`tsdown -w --on-success …`, `tsup -w`).\n /(^|[\\s/])(tsdown|tsup|tsc|rollup|rolldown)(\\/\\S*)?(\\s+\\S+)*\\s+-w(\\s|$)/,\n];\n\n/**\n * Whether a command line is a watcher that restarts its child on changes.\n *\n * @example\n * ```ts\n * getIsDevWatcherCommand('node /app/node_modules/.bin/nodemon src/index.ts'); // → true\n * getIsDevWatcherCommand('node dist/index.js'); // → false\n * ```\n */\nexport const getIsDevWatcherCommand = (command: string): boolean =>\n DEV_WATCHER_COMMAND_PATTERNS.some((pattern) => pattern.test(command));\n\n/**\n * Whether this process runs under a watcher that restarts or reloads it\n * (`bun --watch`/`--hot`, `node --watch`, `ts-node-dev`).\n */\nconst getIsWatchModeProcess = (): boolean =>\n process.execArgv.some((argument) =>\n WATCH_MODE_EXEC_ARGUMENTS.some(\n (flag) => argument === flag || argument.startsWith(`${flag}=`)\n )\n ) || WATCH_MODE_ENV_VARS.some((envVar) => Boolean(process.env[envVar]));\n\n/**\n * Whether a server process runs in development, and so should watch the\n * content declarations.\n *\n * `INTLAYER_WATCH=true|false` decides first, then `NODE_ENV` when set. An unset\n * `NODE_ENV` is not a sign of development (`bun dist/index.mjs` rarely sets\n * it), so the process must then show a positive signal, cheapest first:\n * 1. a runtime watch flag or marker (`bun --watch`/`--hot`, `node --watch`,\n * `ts-node-dev`);\n * 2. a `dev`-like package script;\n * 3. a restarting watcher in its own command line or its ancestors'\n * (`deno run --watch`, `nodemon`, `tsx watch`…), for those started\n * directly rather than through a script.\n */\nexport const getIsServerDevelopment = async (): Promise<boolean> => {\n const watchOverride = process.env[INTLAYER_WATCH_ENV_VAR]?.toLowerCase();\n\n if (watchOverride === 'true' || watchOverride === '1') return true;\n if (watchOverride === 'false' || watchOverride === '0') return false;\n\n const nodeEnv = process.env['NODE_ENV'];\n\n if (nodeEnv) return nodeEnv === 'development';\n\n if (getIsWatchModeProcess()) return true;\n\n if (DEV_SCRIPT_NAME_PATTERN.test(process.env['npm_lifecycle_event'] ?? '')) {\n return true;\n }\n\n const processChainCommands = await getProcessChainCommands();\n\n return processChainCommands.some(getIsDevWatcherCommand);\n};\n\n/**\n * The dev check of this process, computed once: its answer cannot change for\n * the life of the process, and Fastify, Elysia or Remix may register the\n * integration more than once.\n */\nlet serverDevelopmentPromise: Promise<boolean> | undefined;\n\nconst getIsServerDevelopmentOnce = (): Promise<boolean> => {\n serverDevelopmentPromise ??= getIsServerDevelopment();\n\n return serverDevelopmentPromise;\n};\n\nexport type PrepareIntlayerServerOptions = {\n /**\n * How the integration names itself in the watcher lock and in messages, e.g.\n * `express-intlayer`.\n */\n label: string;\n /**\n * Whether to watch the content declarations after preparing them.\n * Defaults to {@link getIsServerDevelopment}.\n */\n watch?: boolean;\n};\n\n/**\n * Prepares the dictionaries for a server integration (Express, Fastify, Hono…)\n * and, in development, keeps them in sync with the content declarations.\n *\n * Replaces wrapping the dev server in `intlayer watch --with`: the watcher runs\n * inside the server, deduplicated through the project's watcher lock, so a\n * server restarted by `tsx watch` / `node --watch` hands it over to its\n * successor, and a remaining `intlayer watch` makes it stand down.\n *\n * Fire-and-forget: the server must not wait on the build to start listening.\n *\n * @param configuration - The resolved Intlayer configuration.\n * @param options - How the integration identifies itself, and whether to watch.\n *\n * @example\n * ```ts\n * prepareIntlayerServer(getConfiguration(), { label: 'express-intlayer' });\n * ```\n */\nexport const prepareIntlayerServer = (\n configuration: IntlayerConfig,\n { label, watch }: PrepareIntlayerServerOptions\n): void => {\n prepareIntlayer(configuration)\n .then(async () => {\n if (!configuration.content.watch) return;\n\n const shouldWatch = watch ?? (await getIsServerDevelopmentOnce());\n\n if (shouldWatch) startContentWatcher(configuration, { label });\n })\n .catch((error: unknown) => {\n getAppLogger(configuration)(\n ['Failed to prepare Intlayer dictionaries:', error],\n { level: 'error' }\n );\n });\n};\n"],"mappings":";;;;;;;;;;;;AAWA,MAAa,yBAAyB;;AAGtC,MAAM,4BAA4B;CAAC;CAAW;CAAgB;AAAO;;;;;AAMrE,MAAM,sBAAsB,CAAC,6BAA6B,aAAa;;;;;;AAOvE,MAAM,0BAA0B;;;;;;;;;;;;;;AAehC,MAAM,+BAA+B;CACnC;CACA;CACA;CAGA;AACF;;;;;;;;;;AAWA,MAAa,0BAA0B,YACrC,6BAA6B,MAAM,YAAY,QAAQ,KAAK,OAAO,CAAC;;;;;AAMtE,MAAM,8BACJ,QAAQ,SAAS,MAAM,aACrB,0BAA0B,MACvB,SAAS,aAAa,QAAQ,SAAS,WAAW,GAAG,KAAK,EAAE,CAC/D,CACF,KAAK,oBAAoB,MAAM,WAAW,QAAQ,QAAQ,IAAI,OAAO,CAAC;;;;;;;;;;;;;;;AAgBxE,MAAa,yBAAyB,YAA8B;CAClE,MAAM,gBAAgB,QAAQ,IAAI,uBAAuB,EAAE,YAAY;CAEvE,IAAI,kBAAkB,UAAU,kBAAkB,KAAK,OAAO;CAC9D,IAAI,kBAAkB,WAAW,kBAAkB,KAAK,OAAO;CAE/D,MAAM;CAEN,IAAI,SAAS,OAAO,YAAY;CAEhC,IAAI,sBAAsB,GAAG,OAAO;CAEpC,IAAI,wBAAwB,KAAK,QAAQ,IAAI,0BAA0B,EAAE,GACvE,OAAO;CAKT,QAAO,MAF4BA,8DAAwB,EAEhC,CAAC,KAAK,sBAAsB;AACzD;;;;;;AAOA,IAAI;AAEJ,MAAM,mCAAqD;CACzD,6BAA6B,uBAAuB;CAEpD,OAAO;AACT;;;;;;;;;;;;;;;;;;;;AAkCA,MAAa,yBACX,eACA,EAAE,OAAO,YACA;CACT,wCAAgB,aAAa,CAAC,CAC3B,KAAK,YAAY;EAChB,IAAI,CAAC,cAAc,QAAQ,OAAO;EAIlC,IAFoB,SAAU,MAAM,2BAA2B,GAE9C,sDAAoB,eAAe,EAAE,MAAM,CAAC;CAC/D,CAAC,CAAC,CACD,OAAO,UAAmB;EACzB,0CAAa,aAAa,CAAC,CACzB,CAAC,4CAA4C,KAAK,GAClD,EAAE,OAAO,QAAQ,CACnB;CACF,CAAC;AACL"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
let node_fs_promises = require("node:fs/promises");
|
|
3
|
+
let node_child_process = require("node:child_process");
|
|
4
|
+
let node_util = require("node:util");
|
|
5
|
+
|
|
6
|
+
//#region src/utils/getProcessChainCommands.ts
|
|
7
|
+
/** How long listing the processes may take before giving up. */
|
|
8
|
+
const PROCESS_LIST_TIMEOUT_MS = 2e3;
|
|
9
|
+
/**
|
|
10
|
+
* Parses the output of `ps -A -o pid=,ppid=,command=`.
|
|
11
|
+
*
|
|
12
|
+
* @param output - One `<pid> <ppid> <command>` row per line.
|
|
13
|
+
* @returns The processes, keyed by PID.
|
|
14
|
+
*/
|
|
15
|
+
const parseProcessTable = (output) => {
|
|
16
|
+
const processes = /* @__PURE__ */ new Map();
|
|
17
|
+
for (const line of output.split("\n")) {
|
|
18
|
+
const match = /^\s*(\d+)\s+(\d+)\s+(.*)$/.exec(line);
|
|
19
|
+
if (!match) continue;
|
|
20
|
+
processes.set(Number(match[1]), {
|
|
21
|
+
parentPid: Number(match[2]),
|
|
22
|
+
command: match[3].trim()
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
return processes;
|
|
26
|
+
};
|
|
27
|
+
/** Reads one process from `/proc` (Linux): no subprocess needed. */
|
|
28
|
+
const readLinuxProcess = async (pid) => {
|
|
29
|
+
try {
|
|
30
|
+
const stat = await (0, node_fs_promises.readFile)(`/proc/${pid}/stat`, "utf8");
|
|
31
|
+
const [, parentPidField] = stat.slice(stat.lastIndexOf(")") + 2).split(" ");
|
|
32
|
+
const commandLine = await (0, node_fs_promises.readFile)(`/proc/${pid}/cmdline`, "utf8");
|
|
33
|
+
return {
|
|
34
|
+
parentPid: Number(parentPidField),
|
|
35
|
+
command: commandLine.split("\0").join(" ").trim()
|
|
36
|
+
};
|
|
37
|
+
} catch {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
/** Lists every process through one `ps` call (macOS, BSD). */
|
|
42
|
+
const readProcessTable = async () => {
|
|
43
|
+
try {
|
|
44
|
+
const { stdout } = await (0, node_util.promisify)(node_child_process.execFile)("ps", [
|
|
45
|
+
"-A",
|
|
46
|
+
"-o",
|
|
47
|
+
"pid=,ppid=,command="
|
|
48
|
+
], {
|
|
49
|
+
timeout: PROCESS_LIST_TIMEOUT_MS,
|
|
50
|
+
maxBuffer: 16777216
|
|
51
|
+
});
|
|
52
|
+
return parseProcessTable(stdout);
|
|
53
|
+
} catch {
|
|
54
|
+
return /* @__PURE__ */ new Map();
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Whether reading the process list is allowed without side effects.
|
|
59
|
+
*
|
|
60
|
+
* Under Deno, a missing permission makes an interactive process stop and
|
|
61
|
+
* prompt the user, so the list is only read when access is already granted.
|
|
62
|
+
* Node and Bun have no permission model on these calls.
|
|
63
|
+
*/
|
|
64
|
+
const getCanReadProcessList = () => {
|
|
65
|
+
const permissions = globalThis.Deno?.permissions;
|
|
66
|
+
if (!permissions) return true;
|
|
67
|
+
try {
|
|
68
|
+
const descriptor = process.platform === "linux" ? {
|
|
69
|
+
name: "read",
|
|
70
|
+
path: "/proc"
|
|
71
|
+
} : {
|
|
72
|
+
name: "run",
|
|
73
|
+
command: "ps"
|
|
74
|
+
};
|
|
75
|
+
return permissions.querySync?.(descriptor).state === "granted";
|
|
76
|
+
} catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Command lines of this process and of the processes above it, closest first.
|
|
82
|
+
*
|
|
83
|
+
* This process is included because some runtimes restart the script inside
|
|
84
|
+
* the same process, leaving their watch flag only on their own command line
|
|
85
|
+
* (`deno run --watch`).
|
|
86
|
+
*
|
|
87
|
+
* Best effort: resolves to an empty list on Windows, or when the process list
|
|
88
|
+
* cannot be read (sandbox, missing `ps`, Deno without the permission), and
|
|
89
|
+
* never throws.
|
|
90
|
+
*
|
|
91
|
+
* @param maxDepth - How many processes to read at most, this one included.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```ts
|
|
95
|
+
* await getProcessChainCommands();
|
|
96
|
+
* // → ['node src/index.js', 'node …/nodemon src/index.js', 'npm run dev']
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
const getProcessChainCommands = async (maxDepth = 6) => {
|
|
100
|
+
if (process.platform === "win32") return [];
|
|
101
|
+
if (!getCanReadProcessList()) return [];
|
|
102
|
+
const getProcess = process.platform === "linux" ? readLinuxProcess : await readProcessTable().then((processes) => async (pid) => processes.get(pid));
|
|
103
|
+
const commands = [];
|
|
104
|
+
let pid = process.pid;
|
|
105
|
+
while (pid > 1 && commands.length < maxDepth) {
|
|
106
|
+
const entry = await getProcess(pid);
|
|
107
|
+
if (!entry) break;
|
|
108
|
+
commands.push(entry.command);
|
|
109
|
+
pid = entry.parentPid;
|
|
110
|
+
}
|
|
111
|
+
return commands;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
//#endregion
|
|
115
|
+
exports.getProcessChainCommands = getProcessChainCommands;
|
|
116
|
+
exports.parseProcessTable = parseProcessTable;
|
|
117
|
+
//# sourceMappingURL=getProcessChainCommands.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getProcessChainCommands.cjs","names":["readFile","promisify","execFile"],"sources":["../../../src/utils/getProcessChainCommands.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport { readFile } from 'node:fs/promises';\nimport { promisify } from 'node:util';\n\n/** Process as listed by the OS: its parent and the command line it runs. */\nexport type ProcessEntry = {\n parentPid: number;\n command: string;\n};\n\n/** How long listing the processes may take before giving up. */\nconst PROCESS_LIST_TIMEOUT_MS = 2000;\n\n/**\n * Parses the output of `ps -A -o pid=,ppid=,command=`.\n *\n * @param output - One `<pid> <ppid> <command>` row per line.\n * @returns The processes, keyed by PID.\n */\nexport const parseProcessTable = (\n output: string\n): Map<number, ProcessEntry> => {\n const processes = new Map<number, ProcessEntry>();\n\n for (const line of output.split('\\n')) {\n const match = /^\\s*(\\d+)\\s+(\\d+)\\s+(.*)$/.exec(line);\n\n if (!match) continue;\n\n processes.set(Number(match[1]), {\n parentPid: Number(match[2]),\n command: match[3]!.trim(),\n });\n }\n\n return processes;\n};\n\n/** Reads one process from `/proc` (Linux): no subprocess needed. */\nconst readLinuxProcess = async (\n pid: number\n): Promise<ProcessEntry | undefined> => {\n try {\n const stat = await readFile(`/proc/${pid}/stat`, 'utf8');\n // The command name is wrapped in parentheses and may contain spaces, so the\n // fields are read after the last `)`: `<state> <ppid> …`.\n const [, parentPidField] = stat.slice(stat.lastIndexOf(')') + 2).split(' ');\n const commandLine = await readFile(`/proc/${pid}/cmdline`, 'utf8');\n\n return {\n parentPid: Number(parentPidField),\n command: commandLine.split('\\0').join(' ').trim(),\n };\n } catch {\n return undefined;\n }\n};\n\n/** Lists every process through one `ps` call (macOS, BSD). */\nconst readProcessTable = async (): Promise<Map<number, ProcessEntry>> => {\n try {\n const { stdout } = await promisify(execFile)(\n 'ps',\n ['-A', '-o', 'pid=,ppid=,command='],\n { timeout: PROCESS_LIST_TIMEOUT_MS, maxBuffer: 16 * 1024 * 1024 }\n );\n\n return parseProcessTable(stdout);\n } catch {\n return new Map();\n }\n};\n\ntype DenoPermissionState = 'granted' | 'prompt' | 'denied';\n\ntype DenoPermissions = {\n querySync?: (descriptor: {\n name: 'run' | 'read';\n command?: string;\n path?: string;\n }) => { state: DenoPermissionState };\n};\n\n/**\n * Whether reading the process list is allowed without side effects.\n *\n * Under Deno, a missing permission makes an interactive process stop and\n * prompt the user, so the list is only read when access is already granted.\n * Node and Bun have no permission model on these calls.\n */\nconst getCanReadProcessList = (): boolean => {\n const permissions = (\n globalThis as { Deno?: { permissions?: DenoPermissions } }\n ).Deno?.permissions;\n\n if (!permissions) return true;\n\n try {\n const descriptor =\n process.platform === 'linux'\n ? ({ name: 'read', path: '/proc' } as const)\n : ({ name: 'run', command: 'ps' } as const);\n\n return permissions.querySync?.(descriptor).state === 'granted';\n } catch {\n return false;\n }\n};\n\n/**\n * Command lines of this process and of the processes above it, closest first.\n *\n * This process is included because some runtimes restart the script inside\n * the same process, leaving their watch flag only on their own command line\n * (`deno run --watch`).\n *\n * Best effort: resolves to an empty list on Windows, or when the process list\n * cannot be read (sandbox, missing `ps`, Deno without the permission), and\n * never throws.\n *\n * @param maxDepth - How many processes to read at most, this one included.\n *\n * @example\n * ```ts\n * await getProcessChainCommands();\n * // → ['node src/index.js', 'node …/nodemon src/index.js', 'npm run dev']\n * ```\n */\nexport const getProcessChainCommands = async (\n maxDepth = 6\n): Promise<string[]> => {\n if (process.platform === 'win32') return [];\n if (!getCanReadProcessList()) return [];\n\n const getProcess: (pid: number) => Promise<ProcessEntry | undefined> =\n process.platform === 'linux'\n ? readLinuxProcess\n : await readProcessTable().then(\n (processes) => async (pid: number) => processes.get(pid)\n );\n\n const commands: string[] = [];\n let pid = process.pid;\n\n // PID 1 is init/launchd: nothing above it is a dev tool.\n while (pid > 1 && commands.length < maxDepth) {\n const entry = await getProcess(pid);\n\n if (!entry) break;\n\n commands.push(entry.command);\n pid = entry.parentPid;\n }\n\n return commands;\n};\n"],"mappings":";;;;;;;AAWA,MAAM,0BAA0B;;;;;;;AAQhC,MAAa,qBACX,WAC8B;CAC9B,MAAM,4BAAY,IAAI,IAA0B;CAEhD,KAAK,MAAM,QAAQ,OAAO,MAAM,IAAI,GAAG;EACrC,MAAM,QAAQ,4BAA4B,KAAK,IAAI;EAEnD,IAAI,CAAC,OAAO;EAEZ,UAAU,IAAI,OAAO,MAAM,EAAE,GAAG;GAC9B,WAAW,OAAO,MAAM,EAAE;GAC1B,SAAS,MAAM,EAAE,CAAE,KAAK;EAC1B,CAAC;CACH;CAEA,OAAO;AACT;;AAGA,MAAM,mBAAmB,OACvB,QACsC;CACtC,IAAI;EACF,MAAM,OAAO,UAAMA,2BAAS,SAAS,IAAI,QAAQ,MAAM;EAGvD,MAAM,GAAG,kBAAkB,KAAK,MAAM,KAAK,YAAY,GAAG,IAAI,CAAC,CAAC,CAAC,MAAM,GAAG;EAC1E,MAAM,cAAc,UAAMA,2BAAS,SAAS,IAAI,WAAW,MAAM;EAEjE,OAAO;GACL,WAAW,OAAO,cAAc;GAChC,SAAS,YAAY,MAAM,IAAI,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK;EAClD;CACF,QAAQ;EACN;CACF;AACF;;AAGA,MAAM,mBAAmB,YAAgD;CACvE,IAAI;EACF,MAAM,EAAE,WAAW,UAAMC,qBAAUC,2BAAQ,CAAC,CAC1C,MACA;GAAC;GAAM;GAAM;EAAqB,GAClC;GAAE,SAAS;GAAyB,WAAW;EAAiB,CAClE;EAEA,OAAO,kBAAkB,MAAM;CACjC,QAAQ;EACN,uBAAO,IAAI,IAAI;CACjB;AACF;;;;;;;;AAmBA,MAAM,8BAAuC;CAC3C,MAAM,cACJ,WACA,MAAM;CAER,IAAI,CAAC,aAAa,OAAO;CAEzB,IAAI;EACF,MAAM,aACJ,QAAQ,aAAa,UAChB;GAAE,MAAM;GAAQ,MAAM;EAAQ,IAC9B;GAAE,MAAM;GAAO,SAAS;EAAK;EAEpC,OAAO,YAAY,YAAY,UAAU,CAAC,CAAC,UAAU;CACvD,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;;;;;;;;;;;;;;AAqBA,MAAa,0BAA0B,OACrC,WAAW,MACW;CACtB,IAAI,QAAQ,aAAa,SAAS,OAAO,CAAC;CAC1C,IAAI,CAAC,sBAAsB,GAAG,OAAO,CAAC;CAEtC,MAAM,aACJ,QAAQ,aAAa,UACjB,mBACA,MAAM,iBAAiB,CAAC,CAAC,MACtB,cAAc,OAAO,QAAgB,UAAU,IAAI,GAAG,CACzD;CAEN,MAAM,WAAqB,CAAC;CAC5B,IAAI,MAAM,QAAQ;CAGlB,OAAO,MAAM,KAAK,SAAS,SAAS,UAAU;EAC5C,MAAM,QAAQ,MAAM,WAAW,GAAG;EAElC,IAAI,CAAC,OAAO;EAEZ,SAAS,KAAK,MAAM,OAAO;EAC3B,MAAM,MAAM;CACd;CAEA,OAAO;AACT"}
|
package/dist/cjs/utils/index.cjs
CHANGED
|
@@ -21,6 +21,7 @@ const require_utils_resolveRelativePath = require('./resolveRelativePath.cjs');
|
|
|
21
21
|
const require_utils_runOnce = require('./runOnce.cjs');
|
|
22
22
|
const require_utils_runParallel_index = require('./runParallel/index.cjs');
|
|
23
23
|
const require_utils_sortAlphabetically = require('./sortAlphabetically.cjs');
|
|
24
|
+
const require_utils_startContentWatcher = require('./startContentWatcher.cjs');
|
|
24
25
|
const require_utils_verifyIdenticObjectFormat = require('./verifyIdenticObjectFormat.cjs');
|
|
25
26
|
const require_utils_withFileLock = require('./withFileLock.cjs');
|
|
26
27
|
|
|
@@ -60,5 +61,6 @@ exports.runOnce = require_utils_runOnce.runOnce;
|
|
|
60
61
|
exports.runParallel = require_utils_runParallel_index.runParallel;
|
|
61
62
|
exports.sortAlphabetically = require_utils_sortAlphabetically.sortAlphabetically;
|
|
62
63
|
exports.splitTextByLines = require_utils_splitTextByLine.splitTextByLines;
|
|
64
|
+
exports.startContentWatcher = require_utils_startContentWatcher.startContentWatcher;
|
|
63
65
|
exports.verifyIdenticObjectFormat = require_utils_verifyIdenticObjectFormat.verifyIdenticObjectFormat;
|
|
64
66
|
exports.withFileLock = require_utils_withFileLock.withFileLock;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
const require_utils_contentWatcherLock = require('./contentWatcherLock.cjs');
|
|
3
|
+
let _intlayer_config_logger = require("@intlayer/config/logger");
|
|
4
|
+
|
|
5
|
+
//#region src/utils/startContentWatcher.ts
|
|
6
|
+
/**
|
|
7
|
+
* How often a process that lost the ownership race checks whether the owner is
|
|
8
|
+
* gone, so it can take over once that process stops (e.g. a short-lived config
|
|
9
|
+
* process, or a second dev server).
|
|
10
|
+
*/
|
|
11
|
+
const TAKEOVER_POLL_INTERVAL_MS = 5e3;
|
|
12
|
+
/**
|
|
13
|
+
* Per-process state, kept on `globalThis` rather than in module scope: a process
|
|
14
|
+
* can load both the CJS and ESM builds of this package, and hot-reloading
|
|
15
|
+
* servers (`bun --hot`, Vite SSR) re-evaluate modules without restarting — each
|
|
16
|
+
* copy must still see the watcher the first one started.
|
|
17
|
+
*/
|
|
18
|
+
const getContentWatcherState = () => {
|
|
19
|
+
const stateKey = Symbol.for("intlayer.contentWatcher");
|
|
20
|
+
const globalScope = globalThis;
|
|
21
|
+
globalScope[stateKey] ??= {
|
|
22
|
+
isWatching: false,
|
|
23
|
+
isClaimInFlight: false
|
|
24
|
+
};
|
|
25
|
+
return globalScope[stateKey];
|
|
26
|
+
};
|
|
27
|
+
const claimAndWatch = async (configuration, options) => {
|
|
28
|
+
const state = getContentWatcherState();
|
|
29
|
+
if (!await require_utils_contentWatcherLock.acquireContentWatcherLock(configuration, {
|
|
30
|
+
source: "bundler",
|
|
31
|
+
label: options.label
|
|
32
|
+
})) {
|
|
33
|
+
const owner = await require_utils_contentWatcherLock.getContentWatcherOwner(configuration);
|
|
34
|
+
if (owner?.source === "cli") {
|
|
35
|
+
require_utils_contentWatcherLock.reportRedundantContentWatcher(configuration, {
|
|
36
|
+
cliLabel: owner.label,
|
|
37
|
+
bundlerLabel: options.label
|
|
38
|
+
});
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
setTimeout(() => {
|
|
42
|
+
startContentWatcher(configuration, options);
|
|
43
|
+
}, TAKEOVER_POLL_INTERVAL_MS).unref?.();
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
state.isWatching = true;
|
|
47
|
+
try {
|
|
48
|
+
const { watch } = await Promise.resolve().then(() => require("../watcher.cjs"));
|
|
49
|
+
await watch({ configuration });
|
|
50
|
+
} catch (error) {
|
|
51
|
+
state.isWatching = false;
|
|
52
|
+
(0, _intlayer_config_logger.getAppLogger)(configuration)(["Failed to watch Intlayer content declarations:", error], { level: "error" });
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Starts the content declaration watcher from an integration (bundler plugin or
|
|
57
|
+
* server middleware), making sure only one watcher rebuilds a project's
|
|
58
|
+
* dictionaries.
|
|
59
|
+
*
|
|
60
|
+
* - Idempotent within a process, across module copies and hot reloads.
|
|
61
|
+
* - Stands down, with an explanation, when `intlayer watch` already covers the
|
|
62
|
+
* project, whether it spawned this process (`--with`) or runs elsewhere.
|
|
63
|
+
* - Otherwise the first process to take the project lock watches; the others
|
|
64
|
+
* retry in the background to take over once it stops.
|
|
65
|
+
*
|
|
66
|
+
* Not awaitable on purpose: a dev server must not wait on the watcher to serve.
|
|
67
|
+
*
|
|
68
|
+
* @param configuration - The resolved Intlayer configuration.
|
|
69
|
+
* @param options - How the integration identifies itself.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```ts
|
|
73
|
+
* configureServer() {
|
|
74
|
+
* startContentWatcher(configuration, { label: 'vite-intlayer' });
|
|
75
|
+
* }
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
const startContentWatcher = (configuration, options) => {
|
|
79
|
+
const state = getContentWatcherState();
|
|
80
|
+
if (state.isWatching || state.isClaimInFlight) return;
|
|
81
|
+
if (!configuration.content.watch) return;
|
|
82
|
+
if (options.getShouldSkip?.()) return;
|
|
83
|
+
const cliWatcherLabel = require_utils_contentWatcherLock.getCliContentWatcherLabel();
|
|
84
|
+
if (cliWatcherLabel) {
|
|
85
|
+
require_utils_contentWatcherLock.reportRedundantContentWatcher(configuration, {
|
|
86
|
+
cliLabel: cliWatcherLabel,
|
|
87
|
+
bundlerLabel: options.label
|
|
88
|
+
});
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
state.isClaimInFlight = true;
|
|
92
|
+
claimAndWatch(configuration, options).finally(() => {
|
|
93
|
+
state.isClaimInFlight = false;
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
//#endregion
|
|
98
|
+
exports.startContentWatcher = startContentWatcher;
|
|
99
|
+
//# sourceMappingURL=startContentWatcher.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"startContentWatcher.cjs","names":["acquireContentWatcherLock","getContentWatcherOwner","getCliContentWatcherLabel"],"sources":["../../../src/utils/startContentWatcher.ts"],"sourcesContent":["import { getAppLogger } from '@intlayer/config/logger';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport {\n acquireContentWatcherLock,\n getCliContentWatcherLabel,\n getContentWatcherOwner,\n reportRedundantContentWatcher,\n} from './contentWatcherLock';\n\n/**\n * How often a process that lost the ownership race checks whether the owner is\n * gone, so it can take over once that process stops (e.g. a short-lived config\n * process, or a second dev server).\n */\nconst TAKEOVER_POLL_INTERVAL_MS = 5000;\n\nexport type StartContentWatcherOptions = {\n /**\n * How the integration names itself in the lock and in messages, e.g.\n * `vite-intlayer`.\n */\n label: string;\n /**\n * Whether this process must never own the watcher, re-checked on every\n * attempt (e.g. a helper process that outlives the dev server).\n */\n getShouldSkip?: () => boolean;\n};\n\ntype ContentWatcherState = {\n /** Set once the watcher is running in this process. */\n isWatching: boolean;\n /**\n * Set while an ownership attempt is in flight: taking the lock is\n * asynchronous, and integrations re-enter on config reloads or server\n * restarts.\n */\n isClaimInFlight: boolean;\n};\n\n/**\n * Per-process state, kept on `globalThis` rather than in module scope: a process\n * can load both the CJS and ESM builds of this package, and hot-reloading\n * servers (`bun --hot`, Vite SSR) re-evaluate modules without restarting — each\n * copy must still see the watcher the first one started.\n */\nconst getContentWatcherState = (): ContentWatcherState => {\n const stateKey = Symbol.for('intlayer.contentWatcher');\n const globalScope = globalThis as typeof globalThis & {\n [key: symbol]: ContentWatcherState | undefined;\n };\n\n globalScope[stateKey] ??= { isWatching: false, isClaimInFlight: false };\n\n return globalScope[stateKey];\n};\n\nconst claimAndWatch = async (\n configuration: IntlayerConfig,\n options: StartContentWatcherOptions\n): Promise<void> => {\n const state = getContentWatcherState();\n\n const hasAcquiredLock = await acquireContentWatcherLock(configuration, {\n source: 'bundler',\n label: options.label,\n });\n\n if (!hasAcquiredLock) {\n const owner = await getContentWatcherOwner(configuration);\n\n // A CLI watcher started from another terminal is just as redundant as one\n // wrapping this command, and worth the same explanation.\n if (owner?.source === 'cli') {\n reportRedundantContentWatcher(configuration, {\n cliLabel: owner.label,\n bundlerLabel: options.label,\n });\n return;\n }\n\n // Another integration owns the watcher: retry in the background so this\n // process picks it up if that one stops first.\n const retryTimer = setTimeout(() => {\n startContentWatcher(configuration, options);\n }, TAKEOVER_POLL_INTERVAL_MS);\n\n // Unreferenced so a process whose only remaining work is this retry can\n // still exit.\n retryTimer.unref?.();\n\n return;\n }\n\n state.isWatching = true;\n\n try {\n // Imported lazily: the watcher pulls in `@parcel/watcher`, a native module\n // with no business being loaded during a production build.\n const { watch } = await import('../watcher');\n\n await watch({ configuration });\n } catch (error) {\n state.isWatching = false;\n\n getAppLogger(configuration)(\n ['Failed to watch Intlayer content declarations:', error],\n { level: 'error' }\n );\n }\n};\n\n/**\n * Starts the content declaration watcher from an integration (bundler plugin or\n * server middleware), making sure only one watcher rebuilds a project's\n * dictionaries.\n *\n * - Idempotent within a process, across module copies and hot reloads.\n * - Stands down, with an explanation, when `intlayer watch` already covers the\n * project, whether it spawned this process (`--with`) or runs elsewhere.\n * - Otherwise the first process to take the project lock watches; the others\n * retry in the background to take over once it stops.\n *\n * Not awaitable on purpose: a dev server must not wait on the watcher to serve.\n *\n * @param configuration - The resolved Intlayer configuration.\n * @param options - How the integration identifies itself.\n *\n * @example\n * ```ts\n * configureServer() {\n * startContentWatcher(configuration, { label: 'vite-intlayer' });\n * }\n * ```\n */\nexport const startContentWatcher = (\n configuration: IntlayerConfig,\n options: StartContentWatcherOptions\n): void => {\n const state = getContentWatcherState();\n\n if (state.isWatching || state.isClaimInFlight) return;\n if (!configuration.content.watch) return;\n if (options.getShouldSkip?.()) return;\n\n // Spawned by `intlayer watch --with`: the CLI watcher covers the project, and\n // this is known from the environment alone — no need to touch the lock.\n const cliWatcherLabel = getCliContentWatcherLabel();\n\n if (cliWatcherLabel) {\n reportRedundantContentWatcher(configuration, {\n cliLabel: cliWatcherLabel,\n bundlerLabel: options.label,\n });\n return;\n }\n\n state.isClaimInFlight = true;\n\n claimAndWatch(configuration, options).finally(() => {\n state.isClaimInFlight = false;\n });\n};\n"],"mappings":";;;;;;;;;;AAcA,MAAM,4BAA4B;;;;;;;AAgClC,MAAM,+BAAoD;CACxD,MAAM,WAAW,OAAO,IAAI,yBAAyB;CACrD,MAAM,cAAc;CAIpB,YAAY,cAAc;EAAE,YAAY;EAAO,iBAAiB;CAAM;CAEtE,OAAO,YAAY;AACrB;AAEA,MAAM,gBAAgB,OACpB,eACA,YACkB;CAClB,MAAM,QAAQ,uBAAuB;CAOrC,IAAI,CAAC,MALyBA,2DAA0B,eAAe;EACrE,QAAQ;EACR,OAAO,QAAQ;CACjB,CAAC,GAEqB;EACpB,MAAM,QAAQ,MAAMC,wDAAuB,aAAa;EAIxD,IAAI,OAAO,WAAW,OAAO;GAC3B,+DAA8B,eAAe;IAC3C,UAAU,MAAM;IAChB,cAAc,QAAQ;GACxB,CAAC;GACD;EACF;EAUA,AANmB,iBAAiB;GAClC,oBAAoB,eAAe,OAAO;EAC5C,GAAG,yBAIM,CAAC,CAAC,QAAQ;EAEnB;CACF;CAEA,MAAM,aAAa;CAEnB,IAAI;EAGF,MAAM,EAAE,UAAU,2CAAM;EAExB,MAAM,MAAM,EAAE,cAAc,CAAC;CAC/B,SAAS,OAAO;EACd,MAAM,aAAa;EAEnB,0CAAa,aAAa,CAAC,CACzB,CAAC,kDAAkD,KAAK,GACxD,EAAE,OAAO,QAAQ,CACnB;CACF;AACF;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,MAAa,uBACX,eACA,YACS;CACT,MAAM,QAAQ,uBAAuB;CAErC,IAAI,MAAM,cAAc,MAAM,iBAAiB;CAC/C,IAAI,CAAC,cAAc,QAAQ,OAAO;CAClC,IAAI,QAAQ,gBAAgB,GAAG;CAI/B,MAAM,kBAAkBC,2DAA0B;CAElD,IAAI,iBAAiB;EACnB,+DAA8B,eAAe;GAC3C,UAAU;GACV,cAAc,QAAQ;EACxB,CAAC;EACD;CACF;CAEA,MAAM,kBAAkB;CAExB,cAAc,eAAe,OAAO,CAAC,CAAC,cAAc;EAClD,MAAM,kBAAkB;CAC1B,CAAC;AACH"}
|
package/dist/cjs/watcher.cjs
CHANGED
|
@@ -4,7 +4,6 @@ const require_utils_formatter = require('./utils/formatter.cjs');
|
|
|
4
4
|
const require_utils_getFormatFromExtension = require('./utils/getFormatFromExtension.cjs');
|
|
5
5
|
const require_utils_parseContentDeclarationFileName = require('./utils/parseContentDeclarationFileName.cjs');
|
|
6
6
|
const require_prepareIntlayer = require('./prepareIntlayer.cjs');
|
|
7
|
-
const require_writeContentDeclaration_writeContentDeclaration = require('./writeContentDeclaration/writeContentDeclaration.cjs');
|
|
8
7
|
const require_handleContentDeclarationFileChange = require('./handleContentDeclarationFileChange.cjs');
|
|
9
8
|
const require_handleAdditionalContentDeclarationFile = require('./handleAdditionalContentDeclarationFile.cjs');
|
|
10
9
|
const require_handleContentDeclarationFileMoved = require('./handleContentDeclarationFileMoved.cjs');
|
|
@@ -174,7 +173,8 @@ const watch = async (options) => {
|
|
|
174
173
|
const { key, locale } = require_utils_parseContentDeclarationFileName.parseContentDeclarationFileName(filePath, configuration);
|
|
175
174
|
const isMarkdown = require_utils_getFormatFromExtension.getFormatFromExtension((0, node_path.extname)(filePath)) === "md";
|
|
176
175
|
const declaredLocale = locale ?? (isMarkdown ? configuration.internationalization.defaultLocale : void 0);
|
|
177
|
-
await
|
|
176
|
+
const { writeContentDeclaration } = await Promise.resolve().then(() => require("./writeContentDeclaration/index.cjs"));
|
|
177
|
+
await writeContentDeclaration({
|
|
178
178
|
key,
|
|
179
179
|
...declaredLocale && { locale: declaredLocale },
|
|
180
180
|
content: {},
|
package/dist/cjs/watcher.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"watcher.cjs","names":["getConfigurationAndFilePath","getAppLogger","colorize","ANSIColor","normalizePath","dirname","existsSync","readFile","transpileTSToCJS","basename","prepareIntlayer","formatPath","fsWatch","resolve","handleContentDeclarationFileMoved","parseContentDeclarationFileName","getFormatFromExtension","extname","writeContentDeclaration","handleAdditionalContentDeclarationFile","handleContentDeclarationFileChange","handleUnlinkedContentDeclarationFile","getConfiguration"],"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
|
+
{"version":3,"file":"watcher.cjs","names":["getConfigurationAndFilePath","getAppLogger","colorize","ANSIColor","normalizePath","dirname","existsSync","readFile","transpileTSToCJS","basename","prepareIntlayer","formatPath","fsWatch","resolve","handleContentDeclarationFileMoved","parseContentDeclarationFileName","getFormatFromExtension","extname","handleAdditionalContentDeclarationFile","handleContentDeclarationFileChange","handleUnlinkedContentDeclarationFile","getConfiguration"],"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';\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 // Lazy: pulls recast/ast-types, only needed for new empty files\n const { writeContentDeclaration } = await import(\n './writeContentDeclaration'\n );\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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"writeJsonIfChanged.cjs","names":["writeFileIfChanged"],"sources":["../../src/writeJsonIfChanged.ts"],"sourcesContent":["import { writeFileIfChanged } from './writeFileIfChanged';\n\nexport const writeJsonIfChanged = async <T>(\n path: string,\n data: T,\n { pretty = process.env.NODE_ENV === 'development' } = {}\n): Promise<boolean> => {\n const space = pretty ? 2 : undefined;\n\n let json = '{}';\n\n try {\n json = JSON.stringify(data, null, space);\n } catch (error) {\n console.error(`Error while parsing data to JSON for ${path}:`, error);\n }\n\n return await writeFileIfChanged(path, json);\n};\n"],"mappings":";;;;AAEA,MAAa,qBAAqB,OAChC,MACA,MACA,EAAE,
|
|
1
|
+
{"version":3,"file":"writeJsonIfChanged.cjs","names":["writeFileIfChanged"],"sources":["../../src/writeJsonIfChanged.ts"],"sourcesContent":["import { writeFileIfChanged } from './writeFileIfChanged';\n\nexport const writeJsonIfChanged = async <T>(\n path: string,\n data: T,\n { pretty = process.env.NODE_ENV === 'development' } = {}\n): Promise<boolean> => {\n const space = pretty ? 2 : undefined;\n\n let json = '{}';\n\n try {\n json = JSON.stringify(data, null, space);\n } catch (error) {\n console.error(`Error while parsing data to JSON for ${path}:`, error);\n }\n\n return await writeFileIfChanged(path, json);\n};\n"],"mappings":";;;;AAEA,MAAa,qBAAqB,OAChC,MACA,MACA,EAAE,kCAAkC,kBAAkB,CAAC,MAClC;CACrB,MAAM,QAAQ,SAAS,IAAI;CAE3B,IAAI,OAAO;CAEX,IAAI;EACF,OAAO,KAAK,UAAU,MAAM,MAAM,KAAK;CACzC,SAAS,OAAO;EACd,QAAQ,MAAM,wCAAwC,KAAK,IAAI,KAAK;CACtE;CAEA,OAAO,MAAMA,8CAAmB,MAAM,IAAI;AAC5C"}
|
package/dist/esm/build.mjs
CHANGED
|
@@ -18,9 +18,10 @@ import { loadDictionaries } from "./loadDictionaries/loadDictionaries.mjs";
|
|
|
18
18
|
import { loadLocalDictionaries } from "./loadDictionaries/loadLocalDictionaries.mjs";
|
|
19
19
|
import { isCachedConfigurationUpToDate, writeConfiguration } from "./writeConfiguration/index.mjs";
|
|
20
20
|
import { prepareIntlayer } from "./prepareIntlayer.mjs";
|
|
21
|
+
import { INTLAYER_WATCH_ENV_VAR, getIsDevWatcherCommand, getIsServerDevelopment, prepareIntlayerServer } from "./prepareIntlayerServer.mjs";
|
|
21
22
|
import { detectExportedComponentName } from "./writeContentDeclaration/detectExportedComponentName.mjs";
|
|
22
23
|
import { transformJSFile } from "./writeContentDeclaration/transformJSFile.mjs";
|
|
23
24
|
import { writeJSFile } from "./writeContentDeclaration/writeJSFile.mjs";
|
|
24
25
|
import { writeContentDeclaration } from "./writeContentDeclaration/writeContentDeclaration.mjs";
|
|
25
26
|
|
|
26
|
-
export { buildDictionary, cleanOutputDir, createDictionaryEntryPoint, createModuleAugmentation, createTypes, detectExportedComponentName, ensureIntlayerBundle, formatDictionaries, formatDictionariesOutput, formatDictionary, formatDictionaryOutput, formatDistantDictionaries, formatDomains, formatLocalDictionaries, formatRewriteRules, generateDictionaryListContent, generateTypeScriptType, getBuiltDictionariesPath, getBuiltDynamicDictionariesPath, getBuiltFetchDictionariesPath, getBuiltRemoteDictionariesPath, getBuiltUnmergedDictionariesPath, getTypeName, isCachedConfigurationUpToDate, loadContentDeclaration, loadContentDeclarations, loadDictionaries, loadLocalDictionaries, loadRemoteDictionaries, prepareIntlayer, processContentDeclaration, readDictionariesFromDisk, transformJSFile, writeConfiguration, writeContentDeclaration, writeJSFile };
|
|
27
|
+
export { INTLAYER_WATCH_ENV_VAR, buildDictionary, cleanOutputDir, createDictionaryEntryPoint, createModuleAugmentation, createTypes, detectExportedComponentName, ensureIntlayerBundle, formatDictionaries, formatDictionariesOutput, formatDictionary, formatDictionaryOutput, formatDistantDictionaries, formatDomains, formatLocalDictionaries, formatRewriteRules, generateDictionaryListContent, generateTypeScriptType, getBuiltDictionariesPath, getBuiltDynamicDictionariesPath, getBuiltFetchDictionariesPath, getBuiltRemoteDictionariesPath, getBuiltUnmergedDictionariesPath, getIsDevWatcherCommand, getIsServerDevelopment, getTypeName, isCachedConfigurationUpToDate, loadContentDeclaration, loadContentDeclarations, loadDictionaries, loadLocalDictionaries, loadRemoteDictionaries, prepareIntlayer, prepareIntlayerServer, processContentDeclaration, readDictionariesFromDisk, transformJSFile, writeConfiguration, writeContentDeclaration, writeJSFile };
|
package/dist/esm/cli.mjs
CHANGED
|
@@ -10,14 +10,14 @@ import { writeContentDeclaration } from "./writeContentDeclaration/writeContentD
|
|
|
10
10
|
import { getGitRootDir, listProjects } from "./listProjects.mjs";
|
|
11
11
|
import { enableEditorInConfig, setupCmsCredentials, writeCmsCredentialsToEnv } from "./init/cms.mjs";
|
|
12
12
|
import { DocumentationRouter, getDocumentationUrl } from "./init/documentationRouter.mjs";
|
|
13
|
-
import { COMPAT_I18N_LIBRARIES, detectCompatI18nLibraries, detectPackageManager, hasLintTooling, installPackages } from "./init/utils/packageManager.mjs";
|
|
13
|
+
import { COMPAT_I18N_LIBRARIES, detectCompatI18nLibraries, detectPackageManager, findLockFileDir, hasLintTooling, installPackages } from "./init/utils/packageManager.mjs";
|
|
14
14
|
import { NEXT_INTLAYER_BABEL_CONFIG_CONTENT } from "./init/utils/nextCompilerBabel.mjs";
|
|
15
15
|
import { hasUrlRoutingFramework } from "./init/utils/urlRoutingFramework.mjs";
|
|
16
16
|
import { upgradeIntlayerPackages } from "./init/upgradeIntlayerPackages.mjs";
|
|
17
17
|
import { getDefaultApplicationURL, initIntlayer, logInitSuccessMessage } from "./init/index.mjs";
|
|
18
18
|
import { installLSP } from "./installLSP.mjs";
|
|
19
|
-
import { PLATFORMS, PLATFORMS_METADATA, SKILLS, SKILLS_METADATA, getInitialSkills, installSkills } from "./installSkills/index.mjs";
|
|
20
|
-
import { installMCP } from "./installMCP/installMCP.mjs";
|
|
19
|
+
import { PLATFORMS, PLATFORMS_METADATA, SKILLS, SKILLS_METADATA, detectPlatform, getInitialSkills, installSkills } from "./installSkills/index.mjs";
|
|
20
|
+
import { MCP_TRANSPORTS, installMCP } from "./installMCP/installMCP.mjs";
|
|
21
21
|
import { listGitFiles, listGitLines } from "./listGitFiles.mjs";
|
|
22
22
|
|
|
23
|
-
export { COMPAT_I18N_LIBRARIES, DocumentationRouter, NEXT_INTLAYER_BABEL_CONFIG_CONTENT, PLATFORMS, PLATFORMS_METADATA, SKILLS, SKILLS_METADATA, detectCompatI18nLibraries, detectExportedComponentName, detectFormatCommand, detectPackageManager, enableEditorInConfig, getContentDeclarationFileTemplate, getDefaultApplicationURL, getDocumentationUrl, getGitRootDir, getInitialSkills, hasLintTooling, hasUrlRoutingFramework, initIntlayer, installLSP, installMCP, installPackages, installSkills, listDictionaries, listDictionariesWithStats, listGitFiles, listGitLines, listProjects, logConfigDetails, logInitSuccessMessage, prepareIntlayer, setupCmsCredentials, transformJSFile, upgradeIntlayerPackages, writeCmsCredentialsToEnv, writeContentDeclaration, writeJSFile };
|
|
23
|
+
export { COMPAT_I18N_LIBRARIES, DocumentationRouter, MCP_TRANSPORTS, NEXT_INTLAYER_BABEL_CONFIG_CONTENT, PLATFORMS, PLATFORMS_METADATA, SKILLS, SKILLS_METADATA, detectCompatI18nLibraries, detectExportedComponentName, detectFormatCommand, detectPackageManager, detectPlatform, enableEditorInConfig, findLockFileDir, getContentDeclarationFileTemplate, getDefaultApplicationURL, getDocumentationUrl, getGitRootDir, getInitialSkills, hasLintTooling, hasUrlRoutingFramework, initIntlayer, installLSP, installMCP, installPackages, installSkills, listDictionaries, listDictionariesWithStats, listGitFiles, listGitLines, listProjects, logConfigDetails, logInitSuccessMessage, prepareIntlayer, setupCmsCredentials, transformJSFile, upgradeIntlayerPackages, writeCmsCredentialsToEnv, writeContentDeclaration, writeJSFile };
|