@intlayer/config 9.3.2 → 9.4.0-canary.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +1 -1
  2. package/dist/cjs/configFile/buildBrowserConfiguration.cjs +13 -6
  3. package/dist/cjs/configFile/buildBrowserConfiguration.cjs.map +1 -1
  4. package/dist/cjs/configFile/buildConfigurationFields.cjs +31 -1
  5. package/dist/cjs/configFile/buildConfigurationFields.cjs.map +1 -1
  6. package/dist/cjs/defaultValues/analytics.cjs +3 -2
  7. package/dist/cjs/defaultValues/analytics.cjs.map +1 -1
  8. package/dist/cjs/defaultValues/build.cjs +4 -0
  9. package/dist/cjs/defaultValues/build.cjs.map +1 -1
  10. package/dist/cjs/defaultValues/dictionary.cjs +41 -0
  11. package/dist/cjs/defaultValues/dictionary.cjs.map +1 -1
  12. package/dist/cjs/defaultValues/index.cjs +6 -0
  13. package/dist/cjs/dictionaryPreload.cjs +149 -0
  14. package/dist/cjs/dictionaryPreload.cjs.map +1 -0
  15. package/dist/cjs/envVars/envVars.cjs.map +1 -1
  16. package/dist/cjs/utils/index.cjs +2 -0
  17. package/dist/cjs/utils/isPackageInstalled.cjs +39 -0
  18. package/dist/cjs/utils/isPackageInstalled.cjs.map +1 -0
  19. package/dist/esm/configFile/buildBrowserConfiguration.mjs +13 -6
  20. package/dist/esm/configFile/buildBrowserConfiguration.mjs.map +1 -1
  21. package/dist/esm/configFile/buildConfigurationFields.mjs +32 -2
  22. package/dist/esm/configFile/buildConfigurationFields.mjs.map +1 -1
  23. package/dist/esm/defaultValues/analytics.mjs +3 -2
  24. package/dist/esm/defaultValues/analytics.mjs.map +1 -1
  25. package/dist/esm/defaultValues/build.mjs +3 -1
  26. package/dist/esm/defaultValues/build.mjs.map +1 -1
  27. package/dist/esm/defaultValues/dictionary.mjs +38 -1
  28. package/dist/esm/defaultValues/dictionary.mjs.map +1 -1
  29. package/dist/esm/defaultValues/index.mjs +3 -3
  30. package/dist/esm/dictionaryPreload.mjs +144 -0
  31. package/dist/esm/dictionaryPreload.mjs.map +1 -0
  32. package/dist/esm/envVars/envVars.mjs.map +1 -1
  33. package/dist/esm/utils/index.mjs +2 -1
  34. package/dist/esm/utils/isPackageInstalled.mjs +38 -0
  35. package/dist/esm/utils/isPackageInstalled.mjs.map +1 -0
  36. package/dist/types/configFile/buildBrowserConfiguration.d.ts +11 -4
  37. package/dist/types/configFile/buildBrowserConfiguration.d.ts.map +1 -1
  38. package/dist/types/configFile/buildConfigurationFields.d.ts.map +1 -1
  39. package/dist/types/defaultValues/analytics.d.ts +3 -2
  40. package/dist/types/defaultValues/analytics.d.ts.map +1 -1
  41. package/dist/types/defaultValues/build.d.ts +3 -1
  42. package/dist/types/defaultValues/build.d.ts.map +1 -1
  43. package/dist/types/defaultValues/dictionary.d.ts +38 -1
  44. package/dist/types/defaultValues/dictionary.d.ts.map +1 -1
  45. package/dist/types/defaultValues/index.d.ts +3 -3
  46. package/dist/types/dictionaryPreload.d.ts +79 -0
  47. package/dist/types/dictionaryPreload.d.ts.map +1 -0
  48. package/dist/types/utils/index.d.ts +2 -1
  49. package/dist/types/utils/isPackageInstalled.d.ts +23 -0
  50. package/dist/types/utils/isPackageInstalled.d.ts.map +1 -0
  51. package/package.json +8 -3
@@ -0,0 +1,149 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ const require_defaultValues_dictionary = require('./defaultValues/dictionary.cjs');
3
+ let node_path = require("node:path");
4
+
5
+ //#region src/dictionaryPreload.ts
6
+ /**
7
+ * Matches a generated dynamic entry point, whose layout is
8
+ * `<dynamicDictionariesDir>/<key>.mjs`. The per-locale JSON chunks it imports
9
+ * live one directory deeper, under `json/`, and are deliberately excluded.
10
+ */
11
+ const DYNAMIC_ENTRY_PATTERN = /\/(?<key>[^/]+)\.mjs$/;
12
+ /** Escapes a literal path segment for embedding in a regular expression. */
13
+ const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
14
+ /**
15
+ * Builds a filter matching only the generated dynamic entry points, for
16
+ * bundlers whose load hook is selected by a regular expression over the raw
17
+ * file path (esbuild).
18
+ *
19
+ * A hook registered on every `.mjs` would call back into JavaScript for each
20
+ * such file in the build, `node_modules` included, so the filter is anchored on
21
+ * the dictionaries directory name. It matches both path separators because
22
+ * esbuild tests the platform path, which is backslash-separated on Windows —
23
+ * the directory name itself never contains one.
24
+ *
25
+ * The name alone is not proof of location, so the callback must still confirm
26
+ * the full path lies inside `dynamicDictionariesDir`.
27
+ *
28
+ * @param dynamicDictionariesDir - Absolute dictionaries root, any separator.
29
+ */
30
+ const createDynamicEntryFilter = (dynamicDictionariesDir) => {
31
+ const directoryName = dynamicDictionariesDir.split(/[\\/]/).filter(Boolean).pop() ?? "";
32
+ return new RegExp(`[\\\\/]${escapeRegExp(directoryName)}[\\\\/][^\\\\/]+\\.mjs$`);
33
+ };
34
+ /**
35
+ * Specifier of the module the injected preamble reads the locale from, as the
36
+ * bundler plugins are expected to resolve it.
37
+ *
38
+ * Never emitted as-is: a generated entry point lives in the *application's*
39
+ * `.intlayer` directory, and an application depends on `intlayer` and its
40
+ * framework binding — not on `@intlayer/core` — so a bare specifier would
41
+ * resolve from a directory where the package is absent, or, worse, silently
42
+ * hit an unrelated copy hoisted higher up the tree.
43
+ */
44
+ const PRELOAD_MODULE_SPECIFIER = "@intlayer/core/localization";
45
+ /** Package and subpath {@link PRELOAD_MODULE_SPECIFIER} is composed of. */
46
+ const PRELOAD_PACKAGE_NAME = "@intlayer/core";
47
+ const PRELOAD_PACKAGE_SUBPATH = "./localization";
48
+ /**
49
+ * Resolves {@link PRELOAD_MODULE_SPECIFIER} to an absolute id the injected
50
+ * preamble can import, from a package that actually depends on it.
51
+ *
52
+ * Deliberately not `require.resolve(PRELOAD_MODULE_SPECIFIER)`: that applies
53
+ * the `require` condition and lands on the package's CommonJS build, which the
54
+ * preamble would then pull into a *browser* bundle behind a CommonJS interop
55
+ * wrapper — a second copy of the module alongside the ESM one the application
56
+ * already imports, each with its own memoized locale. The manifest is resolved
57
+ * instead (a condition-free subpath) and its `import` entry read directly.
58
+ *
59
+ * @param requireFn - `require`, or `createRequire(import.meta.url)`, from the
60
+ * plugin package.
61
+ * @returns Absolute path to the ESM build, falling back to whatever the
62
+ * standard resolution yields if the package ever stops publishing one.
63
+ */
64
+ const resolvePreloadModuleId = (requireFn) => {
65
+ const manifestPath = requireFn.resolve(`${PRELOAD_PACKAGE_NAME}/package.json`);
66
+ const subpathExport = requireFn(manifestPath).exports?.[PRELOAD_PACKAGE_SUBPATH];
67
+ const esmEntry = typeof subpathExport === "string" ? subpathExport : subpathExport?.import;
68
+ if (!esmEntry) return requireFn.resolve(PRELOAD_MODULE_SPECIFIER);
69
+ return (0, node_path.resolve)((0, node_path.dirname)(manifestPath), esmEntry);
70
+ };
71
+ /**
72
+ * Builds the import the injected preamble relies on, hoisted ahead of the
73
+ * generated source.
74
+ *
75
+ * @param preloadModuleId - Specifier resolving to {@link PRELOAD_MODULE_SPECIFIER}
76
+ * from the plugin package, which does depend on it.
77
+ */
78
+ const buildPreloadImport = (preloadModuleId) => `import { getPreloadLocale as __intlayerGetPreloadLocale } from '${preloadModuleId}';\n`;
79
+ /**
80
+ * Preamble appended to a generated dynamic entry point.
81
+ *
82
+ * The load is started while the entry point evaluates, but is deliberately
83
+ * *not* awaited at the top level. Awaiting would make the entry point an async
84
+ * module, which delays the resolution of every chunk that statically imports
85
+ * it — including the chunks behind a `React.lazy` boundary. A boundary whose
86
+ * module resolves that late during hydration stays on its fallback for good:
87
+ * the framework has already swapped the server markup for the pending state and
88
+ * does not retry, so the section never appears. Starting the request here and
89
+ * letting the reader pick the result up keeps the whole benefit of the early
90
+ * fetch without ever holding up module evaluation.
91
+ *
92
+ * The gain is in *when* the request starts: it leaves as soon as the chunk that
93
+ * needs the dictionary is evaluated — on a router's `import()` of a route,
94
+ * including a preload on hover — rather than once the consuming component
95
+ * renders. By the time the reader runs, the marker is usually already set and
96
+ * the read is synchronous, so no loading state is revealed.
97
+ *
98
+ * Only the resolved locale is requested, so a page still downloads exactly the
99
+ * language it renders.
100
+ *
101
+ * A failed load is swallowed: leaving the marker unset drops the read back onto
102
+ * the asynchronous path, where the existing loading state surfaces the error.
103
+ */
104
+ const PRELOAD_PREAMBLE = [
105
+ "",
106
+ "// Injected by Intlayer: starts the browsing locale load with this module.",
107
+ "const __intlayerLocale = __intlayerGetPreloadLocale();",
108
+ `const __intlayerLoader = ${require_defaultValues_dictionary.DYNAMIC_ENTRY_LOADER_MAP_IDENTIFIER}[__intlayerLocale];`,
109
+ "",
110
+ `if (typeof window !== 'undefined' && typeof __intlayerLoader === 'function') {`,
111
+ " __intlayerLoader().then(",
112
+ " (__intlayerDictionary) => {",
113
+ ` ${require_defaultValues_dictionary.DYNAMIC_ENTRY_LOADER_MAP_IDENTIFIER}['${require_defaultValues_dictionary.PRELOADED_DYNAMIC_KEY}'] = {`,
114
+ " locale: __intlayerLocale,",
115
+ " dictionary: __intlayerDictionary,",
116
+ " };",
117
+ " },",
118
+ " () => undefined",
119
+ " );",
120
+ "}",
121
+ ""
122
+ ].join("\n");
123
+ /**
124
+ * Adds the browsing-locale preload to a generated dynamic entry point.
125
+ *
126
+ * Bundler-agnostic on purpose: the Vite plugin and the esbuild plugin apply the
127
+ * identical transform, and keeping one implementation means the emitted shape
128
+ * cannot drift between them. The caller decides *which* modules to hand over
129
+ * (the id is matched against the dynamic dictionaries directory) and how to
130
+ * report a skip.
131
+ *
132
+ * @param code - Source of the generated `<key>.mjs` entry point.
133
+ * @param preloadModuleId - Specifier the preamble imports the locale resolver
134
+ * from, already resolved against the plugin package.
135
+ * @returns The transformed source, or the reason it was left alone.
136
+ */
137
+ const addDynamicEntryPreload = (code, preloadModuleId) => {
138
+ if (code.includes("__intlayerQualifierTypes")) return { skipped: "qualified" };
139
+ if (!code.includes(`const ${"content"} =`)) return { skipped: "unrecognized-shape" };
140
+ return { code: buildPreloadImport(preloadModuleId) + code + PRELOAD_PREAMBLE };
141
+ };
142
+
143
+ //#endregion
144
+ exports.DYNAMIC_ENTRY_PATTERN = DYNAMIC_ENTRY_PATTERN;
145
+ exports.PRELOAD_MODULE_SPECIFIER = PRELOAD_MODULE_SPECIFIER;
146
+ exports.addDynamicEntryPreload = addDynamicEntryPreload;
147
+ exports.createDynamicEntryFilter = createDynamicEntryFilter;
148
+ exports.resolvePreloadModuleId = resolvePreloadModuleId;
149
+ //# sourceMappingURL=dictionaryPreload.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dictionaryPreload.cjs","names":["resolve","dirname","DYNAMIC_ENTRY_LOADER_MAP_IDENTIFIER","PRELOADED_DYNAMIC_KEY"],"sources":["../../src/dictionaryPreload.ts"],"sourcesContent":["import { dirname, resolve } from 'node:path';\nimport {\n DYNAMIC_ENTRY_LOADER_MAP_IDENTIFIER,\n PRELOADED_DYNAMIC_KEY,\n QUALIFIER_DYNAMIC_TYPES_KEY,\n} from './defaultValues/dictionary';\n\n/**\n * Matches a generated dynamic entry point, whose layout is\n * `<dynamicDictionariesDir>/<key>.mjs`. The per-locale JSON chunks it imports\n * live one directory deeper, under `json/`, and are deliberately excluded.\n */\nexport const DYNAMIC_ENTRY_PATTERN = /\\/(?<key>[^/]+)\\.mjs$/;\n\n/** Escapes a literal path segment for embedding in a regular expression. */\nconst escapeRegExp = (value: string): string =>\n value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n/**\n * Builds a filter matching only the generated dynamic entry points, for\n * bundlers whose load hook is selected by a regular expression over the raw\n * file path (esbuild).\n *\n * A hook registered on every `.mjs` would call back into JavaScript for each\n * such file in the build, `node_modules` included, so the filter is anchored on\n * the dictionaries directory name. It matches both path separators because\n * esbuild tests the platform path, which is backslash-separated on Windows —\n * the directory name itself never contains one.\n *\n * The name alone is not proof of location, so the callback must still confirm\n * the full path lies inside `dynamicDictionariesDir`.\n *\n * @param dynamicDictionariesDir - Absolute dictionaries root, any separator.\n */\nexport const createDynamicEntryFilter = (\n dynamicDictionariesDir: string\n): RegExp => {\n const directoryName =\n dynamicDictionariesDir.split(/[\\\\/]/).filter(Boolean).pop() ?? '';\n\n return new RegExp(\n `[\\\\\\\\/]${escapeRegExp(directoryName)}[\\\\\\\\/][^\\\\\\\\/]+\\\\.mjs$`\n );\n};\n\n/**\n * Specifier of the module the injected preamble reads the locale from, as the\n * bundler plugins are expected to resolve it.\n *\n * Never emitted as-is: a generated entry point lives in the *application's*\n * `.intlayer` directory, and an application depends on `intlayer` and its\n * framework binding — not on `@intlayer/core` — so a bare specifier would\n * resolve from a directory where the package is absent, or, worse, silently\n * hit an unrelated copy hoisted higher up the tree.\n */\nexport const PRELOAD_MODULE_SPECIFIER = '@intlayer/core/localization';\n\n/** Package and subpath {@link PRELOAD_MODULE_SPECIFIER} is composed of. */\nconst PRELOAD_PACKAGE_NAME = '@intlayer/core';\nconst PRELOAD_PACKAGE_SUBPATH = './localization';\n\n/** The subset of a `package.json` this resolution reads. */\ntype PackageManifest = {\n exports?: Record<string, { import?: string } | string | undefined>;\n};\n\n/**\n * Resolves {@link PRELOAD_MODULE_SPECIFIER} to an absolute id the injected\n * preamble can import, from a package that actually depends on it.\n *\n * Deliberately not `require.resolve(PRELOAD_MODULE_SPECIFIER)`: that applies\n * the `require` condition and lands on the package's CommonJS build, which the\n * preamble would then pull into a *browser* bundle behind a CommonJS interop\n * wrapper — a second copy of the module alongside the ESM one the application\n * already imports, each with its own memoized locale. The manifest is resolved\n * instead (a condition-free subpath) and its `import` entry read directly.\n *\n * @param requireFn - `require`, or `createRequire(import.meta.url)`, from the\n * plugin package.\n * @returns Absolute path to the ESM build, falling back to whatever the\n * standard resolution yields if the package ever stops publishing one.\n */\nexport const resolvePreloadModuleId = (requireFn: NodeRequire): string => {\n const manifestPath = requireFn.resolve(\n `${PRELOAD_PACKAGE_NAME}/package.json`\n );\n const manifest = requireFn(manifestPath) as PackageManifest;\n const subpathExport = manifest.exports?.[PRELOAD_PACKAGE_SUBPATH];\n\n const esmEntry =\n typeof subpathExport === 'string' ? subpathExport : subpathExport?.import;\n\n if (!esmEntry) return requireFn.resolve(PRELOAD_MODULE_SPECIFIER);\n\n return resolve(dirname(manifestPath), esmEntry);\n};\n\n/**\n * Builds the import the injected preamble relies on, hoisted ahead of the\n * generated source.\n *\n * @param preloadModuleId - Specifier resolving to {@link PRELOAD_MODULE_SPECIFIER}\n * from the plugin package, which does depend on it.\n */\nconst buildPreloadImport = (preloadModuleId: string): string =>\n `import { getPreloadLocale as __intlayerGetPreloadLocale } from '${preloadModuleId}';\\n`;\n\n/**\n * Preamble appended to a generated dynamic entry point.\n *\n * The load is started while the entry point evaluates, but is deliberately\n * *not* awaited at the top level. Awaiting would make the entry point an async\n * module, which delays the resolution of every chunk that statically imports\n * it — including the chunks behind a `React.lazy` boundary. A boundary whose\n * module resolves that late during hydration stays on its fallback for good:\n * the framework has already swapped the server markup for the pending state and\n * does not retry, so the section never appears. Starting the request here and\n * letting the reader pick the result up keeps the whole benefit of the early\n * fetch without ever holding up module evaluation.\n *\n * The gain is in *when* the request starts: it leaves as soon as the chunk that\n * needs the dictionary is evaluated — on a router's `import()` of a route,\n * including a preload on hover — rather than once the consuming component\n * renders. By the time the reader runs, the marker is usually already set and\n * the read is synchronous, so no loading state is revealed.\n *\n * Only the resolved locale is requested, so a page still downloads exactly the\n * language it renders.\n *\n * A failed load is swallowed: leaving the marker unset drops the read back onto\n * the asynchronous path, where the existing loading state surfaces the error.\n */\nconst PRELOAD_PREAMBLE = [\n '',\n '// Injected by Intlayer: starts the browsing locale load with this module.',\n 'const __intlayerLocale = __intlayerGetPreloadLocale();',\n `const __intlayerLoader = ${DYNAMIC_ENTRY_LOADER_MAP_IDENTIFIER}[__intlayerLocale];`,\n '',\n `if (typeof window !== 'undefined' && typeof __intlayerLoader === 'function') {`,\n ' __intlayerLoader().then(',\n ' (__intlayerDictionary) => {',\n ` ${DYNAMIC_ENTRY_LOADER_MAP_IDENTIFIER}['${PRELOADED_DYNAMIC_KEY}'] = {`,\n ' locale: __intlayerLocale,',\n ' dictionary: __intlayerDictionary,',\n ' };',\n ' },',\n ' () => undefined',\n ' );',\n '}',\n '',\n].join('\\n');\n\n/** Why a generated entry point was left untransformed. */\nexport type PreloadSkipReason = 'qualified' | 'unrecognized-shape';\n\nexport type PreloadTransformResult =\n | { code: string; skipped?: undefined }\n | { code?: undefined; skipped: PreloadSkipReason };\n\n/**\n * Adds the browsing-locale preload to a generated dynamic entry point.\n *\n * Bundler-agnostic on purpose: the Vite plugin and the esbuild plugin apply the\n * identical transform, and keeping one implementation means the emitted shape\n * cannot drift between them. The caller decides *which* modules to hand over\n * (the id is matched against the dynamic dictionaries directory) and how to\n * report a skip.\n *\n * @param code - Source of the generated `<key>.mjs` entry point.\n * @param preloadModuleId - Specifier the preamble imports the locale resolver\n * from, already resolved against the plugin package.\n * @returns The transformed source, or the reason it was left alone.\n */\nexport const addDynamicEntryPreload = (\n code: string,\n preloadModuleId: string\n): PreloadTransformResult => {\n // Qualified entry points (collections, variants) expose a nested tree of\n // loaders per locale rather than one loader, so preloading them would have to\n // guess which coordinate the call site targets — and would fetch every chunk\n // to avoid guessing wrong.\n if (code.includes(QUALIFIER_DYNAMIC_TYPES_KEY)) {\n return { skipped: 'qualified' };\n }\n\n // The preamble mutates the generated binding; if the code generator ever\n // stops emitting it, skipping keeps the build correct (and merely\n // unoptimized) instead of emitting a reference error.\n if (!code.includes(`const ${DYNAMIC_ENTRY_LOADER_MAP_IDENTIFIER} =`)) {\n return { skipped: 'unrecognized-shape' };\n }\n\n return {\n code: buildPreloadImport(preloadModuleId) + code + PRELOAD_PREAMBLE,\n };\n};\n"],"mappings":";;;;;;;;;;AAYA,MAAa,wBAAwB;;AAGrC,MAAM,gBAAgB,UACpB,MAAM,QAAQ,uBAAuB,MAAM;;;;;;;;;;;;;;;;;AAkB7C,MAAa,4BACX,2BACW;CACX,MAAM,gBACJ,uBAAuB,MAAM,OAAO,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,IAAI,KAAK;CAEjE,OAAO,IAAI,OACT,UAAU,aAAa,aAAa,EAAE,wBACxC;AACF;;;;;;;;;;;AAYA,MAAa,2BAA2B;;AAGxC,MAAM,uBAAuB;AAC7B,MAAM,0BAA0B;;;;;;;;;;;;;;;;;AAuBhC,MAAa,0BAA0B,cAAmC;CACxE,MAAM,eAAe,UAAU,QAC7B,GAAG,qBAAqB,cAC1B;CAEA,MAAM,gBADW,UAAU,YACE,CAAC,CAAC,UAAU;CAEzC,MAAM,WACJ,OAAO,kBAAkB,WAAW,gBAAgB,eAAe;CAErE,IAAI,CAAC,UAAU,OAAO,UAAU,QAAQ,wBAAwB;CAEhE,WAAOA,uBAAQC,mBAAQ,YAAY,GAAG,QAAQ;AAChD;;;;;;;;AASA,MAAM,sBAAsB,oBAC1B,mEAAmE,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BrF,MAAM,mBAAmB;CACvB;CACA;CACA;CACA,4BAA4BC,qEAAoC;CAChE;CACA;CACA;CACA;CACA,SAASA,qEAAoC,IAAIC,uDAAsB;CACvE;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC,CAAC,KAAK,IAAI;;;;;;;;;;;;;;;AAuBX,MAAa,0BACX,MACA,oBAC2B;CAK3B,IAAI,KAAK,mCAAoC,GAC3C,OAAO,EAAE,SAAS,YAAY;CAMhC,IAAI,CAAC,KAAK,SAAS,mBAA6C,GAAG,GACjE,OAAO,EAAE,SAAS,qBAAqB;CAGzC,OAAO,EACL,MAAM,mBAAmB,eAAe,IAAI,OAAO,iBACrD;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"envVars.cjs","names":[],"sources":["../../../src/envVars/envVars.ts"],"sourcesContent":["import type { IntlayerConfig } from '@intlayer/types/config';\n\n/**\n * Converts a camelCase node-type string to SCREAMING_SNAKE_CASE so that\n * the generated env-var name matches what the plugin source files check.\n *\n * @example\n * toScreamingSnakeCase('reactNode') // 'REACT_NODE'\n * toScreamingSnakeCase('markdown') // 'MARKDOWN'\n */\nconst toScreamingSnakeCase = (str: string): string =>\n str\n .replace(/([A-Z])/g, '_$1')\n .toUpperCase()\n .replace(/^_/, ''); // strip any leading underscore\n\n/**\n * Converts a list of unused NodeType keys into env-var definitions.\n * Set to `\"false\"` so bundlers can eliminate the corresponding plugin code.\n *\n * @example\n * formatNodeTypeToEnvVar(['enumeration'])\n * // { 'INTLAYER_NODE_TYPE_ENUMERATION': '\"false\"' }\n *\n * formatNodeTypeToEnvVar(['reactNode'], (k) => `process.env.${k}`, (v) => `\"${v}\"`)\n * // { 'process.env.INTLAYER_NODE_TYPE_REACT_NODE': '\"false\"' }\n */\nexport const formatNodeTypeToEnvVar = (\n nodeTypes: string[],\n wrapKey = (key: string) => key,\n wrapValue = (value: string) => value\n): Record<string, string> =>\n nodeTypes.reduce(\n (acc, nodeType) => {\n acc[wrapKey(`INTLAYER_NODE_TYPE_${toScreamingSnakeCase(nodeType)}`)] =\n wrapValue('false');\n return acc;\n },\n {} as Record<string, string>\n );\n\n/**\n * Returns the env-var definition disabling the dictionary-selector resolution\n * path (collections, variants) when no built dictionary declares\n * a qualifier. Set to `\"false\"` so bundlers can dead-code-eliminate the\n * selector branch in `getIntlayer` / `useIntlayer`.\n *\n * Emits nothing when selectors are used, leaving the runtime default in place.\n *\n * @example\n * formatDictionarySelectorEnvVar(false)\n * // { INTLAYER_DICTIONARY_SELECTOR: '\"false\"' }\n *\n * formatDictionarySelectorEnvVar(true)\n * // {}\n *\n * formatDictionarySelectorEnvVar(false, (k) => `process.env.${k}`, (v) => `\"${v}\"`)\n * // { 'process.env.INTLAYER_DICTIONARY_SELECTOR': '\"false\"' }\n */\nexport const formatDictionarySelectorEnvVar = (\n hasDictionarySelector: boolean,\n wrapKey = (key: string) => key,\n wrapValue = (value: string) => value\n): Record<string, string> =>\n hasDictionarySelector\n ? {}\n : { [wrapKey('INTLAYER_DICTIONARY_SELECTOR')]: wrapValue('false') };\n\n/**\n * Returns the env-var definition selecting the local `nest()` resolver, which\n * reads the nest targets the optimizer attached to each dictionary instead of\n * looking them up in the global registry.\n *\n * Emit it only when the optimize transform actually runs, since it is that\n * transform which injects the attachment. Setting it to `\"true\"` lets bundlers\n * dead-code-eliminate the registry-based resolver, and with it the\n * `@intlayer/dictionaries-entry` import — which is what allows the dictionaries\n * entry to be emptied while nesting keeps working.\n *\n * @example\n * formatOptimizedNestingEnvVar(true)\n * // { INTLAYER_OPTIMIZED_NESTING: '\"true\"' }\n *\n * formatOptimizedNestingEnvVar(false)\n * // {}\n *\n * formatOptimizedNestingEnvVar(true, (k) => `process.env.${k}`, (v) => `\"${v}\"`)\n * // { 'process.env.INTLAYER_OPTIMIZED_NESTING': '\"true\"' }\n */\nexport const formatOptimizedNestingEnvVar = (\n isOptimized: boolean,\n wrapKey = (key: string) => key,\n wrapValue = (value: string) => value\n): Record<string, string> =>\n isOptimized\n ? { [wrapKey('INTLAYER_OPTIMIZED_NESTING')]: wrapValue('true') }\n : {};\n\n/**\n * Returns env-var definitions for the full Intlayer config to be injected at\n * build time. Allows bundlers to dead-code-eliminate unused routing modes,\n * rewrite logic, storage mechanisms, and editor code.\n *\n * @example\n * getConfigEnvVars(config)\n * // { INTLAYER_ROUTING_MODE: '\"prefix-no-default\"', INTLAYER_ROUTING_REWRITE_RULES: '\"false\"', ... }\n *\n * getConfigEnvVars(config, true)\n * // { 'process.env.INTLAYER_ROUTING_MODE': '\"prefix-no-default\"', ... }\n */\nexport const getConfigEnvVars = (\n config: IntlayerConfig,\n wrapKey = (key: string) => key,\n wrapValue = (value: string) => value\n): Record<string, string> => {\n const { routing, editor, analytics } = config;\n\n const envVars: Record<string, string> = {\n [wrapKey('INTLAYER_ROUTING_MODE')]: wrapValue(routing.mode),\n };\n\n // Only the two explicit states are emitted. An absent variable means the\n // option was left unset, which `resolveProxyMode` reads as auto mode — it\n // then defers to the configuration value available at runtime.\n if (routing.enableProxy === false) {\n envVars[wrapKey('INTLAYER_ROUTING_ENABLE_PROXY')] = wrapValue('false');\n } else if (routing.enableProxy === true) {\n envVars[wrapKey('INTLAYER_ROUTING_ENABLE_PROXY')] = wrapValue('true');\n }\n\n if (!routing.rewrite) {\n envVars[wrapKey('INTLAYER_ROUTING_REWRITE_RULES')] = wrapValue('false');\n }\n\n if (!routing.domains || Object.keys(routing.domains).length === 0) {\n envVars[wrapKey('INTLAYER_ROUTING_DOMAINS')] = wrapValue('false');\n }\n\n if (!routing.storage.cookies || routing.storage.cookies.length === 0) {\n envVars[wrapKey('INTLAYER_ROUTING_STORAGE_COOKIES')] = wrapValue('false');\n }\n\n if (\n !routing.storage.localStorage ||\n routing.storage.localStorage.length === 0\n ) {\n envVars[wrapKey('INTLAYER_ROUTING_STORAGE_LOCALSTORAGE')] =\n wrapValue('false');\n }\n\n if (\n !routing.storage.sessionStorage ||\n routing.storage.sessionStorage.length === 0\n ) {\n envVars[wrapKey('INTLAYER_ROUTING_STORAGE_SESSIONSTORAGE')] =\n wrapValue('false');\n }\n\n if (!routing.storage.headers || routing.storage.headers.length === 0) {\n envVars[wrapKey('INTLAYER_ROUTING_STORAGE_HEADERS')] = wrapValue('false');\n }\n\n if (editor?.enabled === false) {\n envVars[wrapKey('INTLAYER_EDITOR_ENABLED')] = wrapValue('false');\n }\n\n // Analytics is strictly opt-in (`analytics.enabled === true`) and requires a\n // project key (`editor.clientId`) for attribution. Otherwise, disable it so\n // bundlers dead-code-eliminate the whole `@intlayer/analytics` integration\n // (providers + node plugins).\n if (analytics?.enabled !== true || !editor?.clientId) {\n envVars[wrapKey('INTLAYER_ANALYTICS_ENABLED')] = wrapValue('false');\n }\n\n return envVars;\n};\n"],"mappings":";;;;;;;;;;AAUA,MAAM,wBAAwB,QAC5B,IACG,QAAQ,YAAY,KAAK,CAAC,CAC1B,YAAY,CAAC,CACb,QAAQ,MAAM,EAAE;;;;;;;;;;;;AAarB,MAAa,0BACX,WACA,WAAW,QAAgB,KAC3B,aAAa,UAAkB,UAE/B,UAAU,QACP,KAAK,aAAa;CACjB,IAAI,QAAQ,sBAAsB,qBAAqB,QAAQ,GAAG,KAChE,UAAU,OAAO;CACnB,OAAO;AACT,GACA,CAAC,CACH;;;;;;;;;;;;;;;;;;;AAoBF,MAAa,kCACX,uBACA,WAAW,QAAgB,KAC3B,aAAa,UAAkB,UAE/B,wBACI,CAAC,IACD,GAAG,QAAQ,8BAA8B,IAAI,UAAU,OAAO,EAAE;;;;;;;;;;;;;;;;;;;;;;AAuBtE,MAAa,gCACX,aACA,WAAW,QAAgB,KAC3B,aAAa,UAAkB,UAE/B,cACI,GAAG,QAAQ,4BAA4B,IAAI,UAAU,MAAM,EAAE,IAC7D,CAAC;;;;;;;;;;;;;AAcP,MAAa,oBACX,QACA,WAAW,QAAgB,KAC3B,aAAa,UAAkB,UACJ;CAC3B,MAAM,EAAE,SAAS,QAAQ,cAAc;CAEvC,MAAM,UAAkC,GACrC,QAAQ,uBAAuB,IAAI,UAAU,QAAQ,IAAI,EAC5D;CAKA,IAAI,QAAQ,gBAAgB,OAC1B,QAAQ,QAAQ,+BAA+B,KAAK,UAAU,OAAO;MAChE,IAAI,QAAQ,gBAAgB,MACjC,QAAQ,QAAQ,+BAA+B,KAAK,UAAU,MAAM;CAGtE,IAAI,CAAC,QAAQ,SACX,QAAQ,QAAQ,gCAAgC,KAAK,UAAU,OAAO;CAGxE,IAAI,CAAC,QAAQ,WAAW,OAAO,KAAK,QAAQ,OAAO,CAAC,CAAC,WAAW,GAC9D,QAAQ,QAAQ,0BAA0B,KAAK,UAAU,OAAO;CAGlE,IAAI,CAAC,QAAQ,QAAQ,WAAW,QAAQ,QAAQ,QAAQ,WAAW,GACjE,QAAQ,QAAQ,kCAAkC,KAAK,UAAU,OAAO;CAG1E,IACE,CAAC,QAAQ,QAAQ,gBACjB,QAAQ,QAAQ,aAAa,WAAW,GAExC,QAAQ,QAAQ,uCAAuC,KACrD,UAAU,OAAO;CAGrB,IACE,CAAC,QAAQ,QAAQ,kBACjB,QAAQ,QAAQ,eAAe,WAAW,GAE1C,QAAQ,QAAQ,yCAAyC,KACvD,UAAU,OAAO;CAGrB,IAAI,CAAC,QAAQ,QAAQ,WAAW,QAAQ,QAAQ,QAAQ,WAAW,GACjE,QAAQ,QAAQ,kCAAkC,KAAK,UAAU,OAAO;CAG1E,IAAI,QAAQ,YAAY,OACtB,QAAQ,QAAQ,yBAAyB,KAAK,UAAU,OAAO;CAOjE,IAAI,WAAW,YAAY,QAAQ,CAAC,QAAQ,UAC1C,QAAQ,QAAQ,4BAA4B,KAAK,UAAU,OAAO;CAGpE,OAAO;AACT"}
1
+ {"version":3,"file":"envVars.cjs","names":[],"sources":["../../../src/envVars/envVars.ts"],"sourcesContent":["import type { IntlayerConfig } from '@intlayer/types/config';\n\n/**\n * Converts a camelCase node-type string to SCREAMING_SNAKE_CASE so that\n * the generated env-var name matches what the plugin source files check.\n *\n * @example\n * toScreamingSnakeCase('reactNode') // 'REACT_NODE'\n * toScreamingSnakeCase('markdown') // 'MARKDOWN'\n */\nconst toScreamingSnakeCase = (str: string): string =>\n str\n .replace(/([A-Z])/g, '_$1')\n .toUpperCase()\n .replace(/^_/, ''); // strip any leading underscore\n\n/**\n * Converts a list of unused NodeType keys into env-var definitions.\n * Set to `\"false\"` so bundlers can eliminate the corresponding plugin code.\n *\n * @example\n * formatNodeTypeToEnvVar(['enumeration'])\n * // { 'INTLAYER_NODE_TYPE_ENUMERATION': '\"false\"' }\n *\n * formatNodeTypeToEnvVar(['reactNode'], (k) => `process.env.${k}`, (v) => `\"${v}\"`)\n * // { 'process.env.INTLAYER_NODE_TYPE_REACT_NODE': '\"false\"' }\n */\nexport const formatNodeTypeToEnvVar = (\n nodeTypes: string[],\n wrapKey = (key: string) => key,\n wrapValue = (value: string) => value\n): Record<string, string> =>\n nodeTypes.reduce(\n (acc, nodeType) => {\n acc[wrapKey(`INTLAYER_NODE_TYPE_${toScreamingSnakeCase(nodeType)}`)] =\n wrapValue('false');\n return acc;\n },\n {} as Record<string, string>\n );\n\n/**\n * Returns the env-var definition disabling the dictionary-selector resolution\n * path (collections, variants) when no built dictionary declares\n * a qualifier. Set to `\"false\"` so bundlers can dead-code-eliminate the\n * selector branch in `getIntlayer` / `useIntlayer`.\n *\n * Emits nothing when selectors are used, leaving the runtime default in place.\n *\n * @example\n * formatDictionarySelectorEnvVar(false)\n * // { INTLAYER_DICTIONARY_SELECTOR: '\"false\"' }\n *\n * formatDictionarySelectorEnvVar(true)\n * // {}\n *\n * formatDictionarySelectorEnvVar(false, (k) => `process.env.${k}`, (v) => `\"${v}\"`)\n * // { 'process.env.INTLAYER_DICTIONARY_SELECTOR': '\"false\"' }\n */\nexport const formatDictionarySelectorEnvVar = (\n hasDictionarySelector: boolean,\n wrapKey = (key: string) => key,\n wrapValue = (value: string) => value\n): Record<string, string> =>\n hasDictionarySelector\n ? {}\n : { [wrapKey('INTLAYER_DICTIONARY_SELECTOR')]: wrapValue('false') };\n\n/**\n * Returns the env-var definition selecting the local `nest()` resolver, which\n * reads the nest targets the optimizer attached to each dictionary instead of\n * looking them up in the global registry.\n *\n * Emit it only when the optimize transform actually runs, since it is that\n * transform which injects the attachment. Setting it to `\"true\"` lets bundlers\n * dead-code-eliminate the registry-based resolver, and with it the\n * `@intlayer/dictionaries-entry` import — which is what allows the dictionaries\n * entry to be emptied while nesting keeps working.\n *\n * @example\n * formatOptimizedNestingEnvVar(true)\n * // { INTLAYER_OPTIMIZED_NESTING: '\"true\"' }\n *\n * formatOptimizedNestingEnvVar(false)\n * // {}\n *\n * formatOptimizedNestingEnvVar(true, (k) => `process.env.${k}`, (v) => `\"${v}\"`)\n * // { 'process.env.INTLAYER_OPTIMIZED_NESTING': '\"true\"' }\n */\nexport const formatOptimizedNestingEnvVar = (\n isOptimized: boolean,\n wrapKey = (key: string) => key,\n wrapValue = (value: string) => value\n): Record<string, string> =>\n isOptimized\n ? { [wrapKey('INTLAYER_OPTIMIZED_NESTING')]: wrapValue('true') }\n : {};\n\n/**\n * Returns env-var definitions for the full Intlayer config to be injected at\n * build time. Allows bundlers to dead-code-eliminate unused routing modes,\n * rewrite logic, storage mechanisms, and editor code.\n *\n * @example\n * getConfigEnvVars(config)\n * // { INTLAYER_ROUTING_MODE: '\"prefix-no-default\"', INTLAYER_ROUTING_REWRITE_RULES: '\"false\"', ... }\n *\n * getConfigEnvVars(config, true)\n * // { 'process.env.INTLAYER_ROUTING_MODE': '\"prefix-no-default\"', ... }\n */\nexport const getConfigEnvVars = (\n config: IntlayerConfig,\n wrapKey = (key: string) => key,\n wrapValue = (value: string) => value\n): Record<string, string> => {\n const { routing, editor, analytics } = config;\n\n const envVars: Record<string, string> = {\n [wrapKey('INTLAYER_ROUTING_MODE')]: wrapValue(routing.mode),\n };\n\n // Only the two explicit states are emitted. An absent variable means the\n // option was left unset, which `resolveProxyMode` reads as auto mode — it\n // then defers to the configuration value available at runtime.\n if (routing.enableProxy === false) {\n envVars[wrapKey('INTLAYER_ROUTING_ENABLE_PROXY')] = wrapValue('false');\n } else if (routing.enableProxy === true) {\n envVars[wrapKey('INTLAYER_ROUTING_ENABLE_PROXY')] = wrapValue('true');\n }\n\n if (!routing.rewrite) {\n envVars[wrapKey('INTLAYER_ROUTING_REWRITE_RULES')] = wrapValue('false');\n }\n\n if (!routing.domains || Object.keys(routing.domains).length === 0) {\n envVars[wrapKey('INTLAYER_ROUTING_DOMAINS')] = wrapValue('false');\n }\n\n if (!routing.storage.cookies || routing.storage.cookies.length === 0) {\n envVars[wrapKey('INTLAYER_ROUTING_STORAGE_COOKIES')] = wrapValue('false');\n }\n\n if (\n !routing.storage.localStorage ||\n routing.storage.localStorage.length === 0\n ) {\n envVars[wrapKey('INTLAYER_ROUTING_STORAGE_LOCALSTORAGE')] =\n wrapValue('false');\n }\n\n if (\n !routing.storage.sessionStorage ||\n routing.storage.sessionStorage.length === 0\n ) {\n envVars[wrapKey('INTLAYER_ROUTING_STORAGE_SESSIONSTORAGE')] =\n wrapValue('false');\n }\n\n if (!routing.storage.headers || routing.storage.headers.length === 0) {\n envVars[wrapKey('INTLAYER_ROUTING_STORAGE_HEADERS')] = wrapValue('false');\n }\n\n if (editor?.enabled === false) {\n envVars[wrapKey('INTLAYER_EDITOR_ENABLED')] = wrapValue('false');\n }\n\n // Analytics is opt-out: `analytics.enabled` defaults to `true` and is\n // resolved to `false` when the optional `@intlayer/analytics` package is not\n // installed. It also requires a project key (`editor.clientId`) for\n // attribution. Otherwise, disable it so bundlers dead-code-eliminate the\n // whole `@intlayer/analytics` integration (providers + node plugins).\n if (analytics?.enabled !== true || !editor?.clientId) {\n envVars[wrapKey('INTLAYER_ANALYTICS_ENABLED')] = wrapValue('false');\n }\n\n return envVars;\n};\n"],"mappings":";;;;;;;;;;AAUA,MAAM,wBAAwB,QAC5B,IACG,QAAQ,YAAY,KAAK,CAAC,CAC1B,YAAY,CAAC,CACb,QAAQ,MAAM,EAAE;;;;;;;;;;;;AAarB,MAAa,0BACX,WACA,WAAW,QAAgB,KAC3B,aAAa,UAAkB,UAE/B,UAAU,QACP,KAAK,aAAa;CACjB,IAAI,QAAQ,sBAAsB,qBAAqB,QAAQ,GAAG,KAChE,UAAU,OAAO;CACnB,OAAO;AACT,GACA,CAAC,CACH;;;;;;;;;;;;;;;;;;;AAoBF,MAAa,kCACX,uBACA,WAAW,QAAgB,KAC3B,aAAa,UAAkB,UAE/B,wBACI,CAAC,IACD,GAAG,QAAQ,8BAA8B,IAAI,UAAU,OAAO,EAAE;;;;;;;;;;;;;;;;;;;;;;AAuBtE,MAAa,gCACX,aACA,WAAW,QAAgB,KAC3B,aAAa,UAAkB,UAE/B,cACI,GAAG,QAAQ,4BAA4B,IAAI,UAAU,MAAM,EAAE,IAC7D,CAAC;;;;;;;;;;;;;AAcP,MAAa,oBACX,QACA,WAAW,QAAgB,KAC3B,aAAa,UAAkB,UACJ;CAC3B,MAAM,EAAE,SAAS,QAAQ,cAAc;CAEvC,MAAM,UAAkC,GACrC,QAAQ,uBAAuB,IAAI,UAAU,QAAQ,IAAI,EAC5D;CAKA,IAAI,QAAQ,gBAAgB,OAC1B,QAAQ,QAAQ,+BAA+B,KAAK,UAAU,OAAO;MAChE,IAAI,QAAQ,gBAAgB,MACjC,QAAQ,QAAQ,+BAA+B,KAAK,UAAU,MAAM;CAGtE,IAAI,CAAC,QAAQ,SACX,QAAQ,QAAQ,gCAAgC,KAAK,UAAU,OAAO;CAGxE,IAAI,CAAC,QAAQ,WAAW,OAAO,KAAK,QAAQ,OAAO,CAAC,CAAC,WAAW,GAC9D,QAAQ,QAAQ,0BAA0B,KAAK,UAAU,OAAO;CAGlE,IAAI,CAAC,QAAQ,QAAQ,WAAW,QAAQ,QAAQ,QAAQ,WAAW,GACjE,QAAQ,QAAQ,kCAAkC,KAAK,UAAU,OAAO;CAG1E,IACE,CAAC,QAAQ,QAAQ,gBACjB,QAAQ,QAAQ,aAAa,WAAW,GAExC,QAAQ,QAAQ,uCAAuC,KACrD,UAAU,OAAO;CAGrB,IACE,CAAC,QAAQ,QAAQ,kBACjB,QAAQ,QAAQ,eAAe,WAAW,GAE1C,QAAQ,QAAQ,yCAAyC,KACvD,UAAU,OAAO;CAGrB,IAAI,CAAC,QAAQ,QAAQ,WAAW,QAAQ,QAAQ,QAAQ,WAAW,GACjE,QAAQ,QAAQ,kCAAkC,KAAK,UAAU,OAAO;CAG1E,IAAI,QAAQ,YAAY,OACtB,QAAQ,QAAQ,yBAAyB,KAAK,UAAU,OAAO;CAQjE,IAAI,WAAW,YAAY,QAAQ,CAAC,QAAQ,UAC1C,QAAQ,QAAQ,4BAA4B,KAAK,UAAU,OAAO;CAGpE,OAAO;AACT"}
@@ -13,6 +13,7 @@ const require_utils_extractErrorMessage = require('./extractErrorMessage.cjs');
13
13
  const require_utils_getDictionarySelectorUsage = require('./getDictionarySelectorUsage.cjs');
14
14
  const require_utils_getStorageAttributes = require('./getStorageAttributes.cjs');
15
15
  const require_utils_getUsedNodeTypes = require('./getUsedNodeTypes.cjs');
16
+ const require_utils_isPackageInstalled = require('./isPackageInstalled.cjs');
16
17
  const require_utils_logStack = require('./logStack.cjs');
17
18
  const require_utils_parseFilePathPattern = require('./parseFilePathPattern.cjs');
18
19
  const require_utils_pathSecurity = require('./pathSecurity.cjs');
@@ -49,6 +50,7 @@ exports.getUnusedNodeTypesAsync = require_utils_getUsedNodeTypes.getUnusedNodeTy
49
50
  exports.getUsedNodeTypes = require_utils_getUsedNodeTypes.getUsedNodeTypes;
50
51
  exports.getUsedNodeTypesAsync = require_utils_getUsedNodeTypes.getUsedNodeTypesAsync;
51
52
  exports.isESModule = require_utils_ESMxCJSHelpers.isESModule;
53
+ exports.isPackageInstalled = require_utils_isPackageInstalled.isPackageInstalled;
52
54
  exports.kebabCaseToCamelCase = require_utils_stringFormatter_kebabCaseToCamelCase.kebabCaseToCamelCase;
53
55
  exports.logStack = require_utils_logStack.logStack;
54
56
  exports.normalizePath = require_utils_normalizePath.normalizePath;
@@ -0,0 +1,39 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ const require_utils_ESMxCJSHelpers = require('./ESMxCJSHelpers.cjs');
3
+
4
+ //#region src/utils/isPackageInstalled.ts
5
+ /**
6
+ * Checks whether a package can be resolved from the user project.
7
+ *
8
+ * Used to detect optional companion packages (e.g. `@intlayer/analytics`)
9
+ * whose mere presence in the dependency tree opts the project into a feature.
10
+ *
11
+ * Resolution is attempted on the package manifest first, so packages that
12
+ * expose no root entry point — or whose entry point cannot be loaded in the
13
+ * current module format — are still detected.
14
+ *
15
+ * Note: resolution starts at the project root, so a package installed only as
16
+ * a transitive dependency may stay invisible under strict package managers
17
+ * (pnpm). Callers should treat a `false` result as a default, not as a veto.
18
+ *
19
+ * @param packageName - Name of the package to look up, e.g. `'@intlayer/analytics'`.
20
+ * @param baseDir - Directory the resolution starts from. Defaults to the current working directory.
21
+ * @returns `true` when the package is installed in the project, `false` otherwise.
22
+ */
23
+ const isPackageInstalled = (packageName, baseDir) => {
24
+ try {
25
+ const projectRequire = require_utils_ESMxCJSHelpers.getProjectRequire(baseDir);
26
+ try {
27
+ projectRequire.resolve(`${packageName}/package.json`);
28
+ } catch {
29
+ projectRequire.resolve(packageName);
30
+ }
31
+ return true;
32
+ } catch {
33
+ return false;
34
+ }
35
+ };
36
+
37
+ //#endregion
38
+ exports.isPackageInstalled = isPackageInstalled;
39
+ //# sourceMappingURL=isPackageInstalled.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"isPackageInstalled.cjs","names":["getProjectRequire"],"sources":["../../../src/utils/isPackageInstalled.ts"],"sourcesContent":["import { getProjectRequire } from './ESMxCJSHelpers';\n\n/**\n * Checks whether a package can be resolved from the user project.\n *\n * Used to detect optional companion packages (e.g. `@intlayer/analytics`)\n * whose mere presence in the dependency tree opts the project into a feature.\n *\n * Resolution is attempted on the package manifest first, so packages that\n * expose no root entry point — or whose entry point cannot be loaded in the\n * current module format — are still detected.\n *\n * Note: resolution starts at the project root, so a package installed only as\n * a transitive dependency may stay invisible under strict package managers\n * (pnpm). Callers should treat a `false` result as a default, not as a veto.\n *\n * @param packageName - Name of the package to look up, e.g. `'@intlayer/analytics'`.\n * @param baseDir - Directory the resolution starts from. Defaults to the current working directory.\n * @returns `true` when the package is installed in the project, `false` otherwise.\n */\nexport const isPackageInstalled = (\n packageName: string,\n baseDir?: string\n): boolean => {\n try {\n const projectRequire = getProjectRequire(baseDir);\n\n try {\n projectRequire.resolve(`${packageName}/package.json`);\n } catch {\n projectRequire.resolve(packageName);\n }\n\n return true;\n } catch {\n return false;\n }\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAoBA,MAAa,sBACX,aACA,YACY;CACZ,IAAI;EACF,MAAM,iBAAiBA,+CAAkB,OAAO;EAEhD,IAAI;GACF,eAAe,QAAQ,GAAG,YAAY,cAAc;EACtD,QAAQ;GACN,eAAe,QAAQ,WAAW;EACpC;EAEA,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF"}
@@ -281,20 +281,27 @@ const buildEditorFields = (customConfiguration) => {
281
281
  /**
282
282
  * Build the analytics section of the Intlayer configuration.
283
283
  *
284
- * Analytics is strictly opt-in: `enabled` defaults to `false` and, even when
285
- * enabled, the runtime additionally requires a project key (`editor.clientId`)
286
- * for attribution before collecting anything.
284
+ * Analytics is opt-out: `enabled` defaults to `true`, so installing the
285
+ * optional `@intlayer/analytics` package is enough to turn it on. The runtime
286
+ * additionally requires a project key (`editor.clientId`) for attribution
287
+ * before collecting anything.
288
+ *
289
+ * An explicit `enabled` value always wins, so a project whose package manager
290
+ * hides the package from the project root can still opt back in.
287
291
  *
288
292
  * @param customConfiguration - Partial user-supplied analytics config.
293
+ * @param isPackageInstalled - Whether `@intlayer/analytics` is installed in the
294
+ * project. Browser callers cannot resolve packages and pass `true`, letting
295
+ * the runtime dynamic `import()` fail silently when it is absent.
289
296
  * @returns A fully-defaulted {@link AnalyticsConfig}.
290
297
  */
291
- const buildAnalyticsFields = (customConfiguration) => ({
298
+ const buildAnalyticsFields = (customConfiguration, isPackageInstalled = true) => ({
292
299
  /**
293
300
  * Enables analytics collection (page views, content exposures, A/B events).
294
301
  *
295
- * Default: false
302
+ * Default: true, as soon as `@intlayer/analytics` is installed.
296
303
  */
297
- enabled: customConfiguration?.enabled ?? false,
304
+ enabled: customConfiguration?.enabled ?? (true && isPackageInstalled),
298
305
  /**
299
306
  * Milliseconds between automatic batched flushes to the backend.
300
307
  *
@@ -1 +1 @@
1
- {"version":3,"file":"buildBrowserConfiguration.mjs","names":[],"sources":["../../../src/configFile/buildBrowserConfiguration.ts"],"sourcesContent":["import type {\n AnalyticsConfig,\n CustomIntlayerConfig,\n CustomRoutingConfig,\n EditorConfig,\n InternationalizationConfig,\n IntlayerConfig,\n LogConfig,\n LogFunctions,\n RoutingConfig,\n} from '@intlayer/types/config';\nimport {\n ANALYTICS_ENABLED,\n ANALYTICS_FLUSH_INTERVAL,\n ANALYTICS_SAMPLE_RATE,\n} from '../defaultValues/analytics';\nimport {\n APPLICATION_URL,\n BACKEND_URL,\n CMS_URL,\n DICTIONARY_PRIORITY_STRATEGY,\n EDITOR_URL,\n IS_ENABLED,\n LIVE_SYNC,\n LIVE_SYNC_PORT,\n PORT,\n} from '../defaultValues/editor';\nimport {\n DEFAULT_LOCALE,\n LOCALES,\n REQUIRED_LOCALES,\n STRICT_MODE,\n} from '../defaultValues/internationalization';\nimport { MODE, PREFIX } from '../defaultValues/log';\nimport {\n BASE_PATH,\n ENABLE_PROXY,\n ROUTING_MODE,\n STORAGE,\n} from '../defaultValues/routing';\nimport { getStorageAttributes } from '../utils/getStorageAttributes';\n\n// ---------------------------------------------------------------------------\n// Type\n// ---------------------------------------------------------------------------\n\n/**\n * Browser-safe subset of {@link IntlayerConfig}.\n *\n * Excludes server-only fields (`system`, `content`, `build`, `compiler`,\n * `dictionary`, `ai`) and sensitive editor credentials (`clientId`,\n * `clientSecret`) that must never be shipped to the browser.\n */\nexport type BrowserIntlayerConfig = {\n internationalization: Pick<\n InternationalizationConfig,\n 'locales' | 'defaultLocale'\n >;\n routing: RoutingConfig;\n editor: Omit<EditorConfig, 'clientId' | 'clientSecret'>;\n analytics: AnalyticsConfig;\n log: Pick<LogConfig, 'mode' | 'prefix'>;\n};\n\ndeclare global {\n interface Window {\n /** Browser-safe Intlayer configuration injected by a build plugin or `installIntlayer`. */\n INTLAYER_CONFIG?: BrowserIntlayerConfig;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Shared field builders (browser-safe — no Node.js APIs)\n//\n// These functions are re-used by both `buildBrowserConfiguration` (browser)\n// and `buildConfigurationFields` (server) to avoid duplication.\n// ---------------------------------------------------------------------------\n\n/**\n * Build the internationalization section of the Intlayer configuration.\n *\n * @param customConfiguration - Partial user-supplied internationalization config.\n * @returns A fully-defaulted {@link InternationalizationConfig}.\n */\nexport const buildInternationalizationFields = (\n customConfiguration?: Partial<InternationalizationConfig>\n): InternationalizationConfig => ({\n /**\n * Locales available in the application\n *\n * Default: ['en']\n *\n */\n locales: customConfiguration?.locales ?? LOCALES,\n\n /**\n * Locales required by TypeScript to ensure strong implementations of internationalized content using typescript.\n *\n * Default: []\n *\n * If empty, all locales are required in `strict` mode.\n *\n * Ensure required locales are also defined in the `locales` field.\n */\n requiredLocales:\n customConfiguration?.requiredLocales ??\n customConfiguration?.locales ??\n REQUIRED_LOCALES,\n\n /**\n * Ensure strong implementations of internationalized content using typescript.\n * - If set to \"strict\", the translation `t` function will require each declared locales to be defined. If one locale is missing, or if a locale is not declared in your config, it will throw an error.\n * - If set to \"inclusive\", the translation `t` function will require each declared locales to be defined. If one locale is missing, it will throw a warning. But will accept if a locale is not declared in your config, but exist.\n * - If set to \"loose\", the translation `t` function will accept any existing locale.\n *\n * Default: \"inclusive\"\n */\n strictMode: customConfiguration?.strictMode ?? STRICT_MODE,\n\n /**\n * Default locale of the application for fallback\n *\n * Default: 'en'\n */\n defaultLocale: customConfiguration?.defaultLocale ?? DEFAULT_LOCALE,\n});\n\n/**\n * Build the routing section of the Intlayer configuration.\n *\n * @param customConfiguration - Partial user-supplied routing config.\n * @returns A fully-defaulted {@link RoutingConfig}.\n */\nexport const buildRoutingFields = (\n customConfiguration?: Partial<CustomRoutingConfig>\n): RoutingConfig => {\n const storage = customConfiguration?.storage ?? STORAGE;\n\n return {\n /**\n * URL routing mode for locale handling\n *\n * Controls how locales are represented in application URLs:\n * - 'prefix-no-default': Prefix all locales except the default locale (default)\n * - en → /dashboard\n * - fr → /fr/dashboard\n *\n * - 'prefix-all': Prefix all locales including the default locale\n * - en → /en/dashboard\n * - fr → /fr/dashboard\n *\n * - 'search-params': Use search parameters for locale handling\n * - en → /dashboard?locale=en\n * - fr → /fr/dashboard?locale=fr\n *\n * - 'no-prefix': No locale prefixing in URLs\n * - en → /dashboard\n * - fr → /dashboard\n *\n * Default: 'prefix-no-default'\n */\n mode: customConfiguration?.mode ?? ROUTING_MODE,\n\n /**\n * Enables the Intlayer locale-routing proxy (middleware).\n *\n * When enabled, the build-tool integration (e.g. the `intlayer()` Vite plugin)\n * automatically wires the locale-detection / redirect / rewrite middleware in\n * development, preview, and production SSR.\n *\n * - `undefined` (auto, default): the proxy runs, but development and preview\n * servers ignore the stored locale (cookie / header) as a redirect source.\n * - `true`: full proxy behaviour in every environment.\n * - `false`: no locale routing; the proxy is not registered.\n *\n * Default: undefined (auto)\n */\n enableProxy: customConfiguration?.enableProxy ?? ENABLE_PROXY,\n\n /**\n * Configuration for storing the locale in the client (localStorage or sessionStorage)\n *\n * If false, the locale will not be stored by the middleware.\n * If true, the locale storage will consider all default values. (cookie and header)\n *\n * Default: ['cookie', 'header']\n *\n */\n storage: getStorageAttributes(storage),\n\n /**\n * Base path of the application URL\n *\n * Default: ''\n *\n * Example:\n * - If the application is hosted at https://example.com/my-app\n * - The base path is '/my-app'\n * - The URL will be https://example.com/my-app/en\n * - If the base path is not set, the URL will be https://example.com/en\n */\n basePath: customConfiguration?.basePath ?? BASE_PATH,\n\n /**\n * Custom URL rewriting rules that override the default routing mode for specific paths.\n * Allows you to define locale-specific paths that differ from the standard routing behavior.\n * Supports dynamic route parameters using `[param]` syntax.\n *\n * Default: undefined\n *\n * Example:\n * ```typescript\n * rewrite: {\n * \"/about\": {\n * en: \"/about\",\n * fr: \"/a-propos\",\n * },\n * \"/product/[slug]\": {\n * en: \"/product/[slug]\",\n * fr: \"/produit/[slug]\",\n * },\n * }\n * ```\n *\n * Note:\n * - The rewrite rules take precedence over the default `mode` behavior.\n * - If a path matches a rewrite rule, the localized path from the rewrite configuration will be used.\n * - Dynamic route parameters are supported using bracket notation (e.g., `[slug]`, `[id]`).\n * - Works with both Next.js and Vite applications.\n */\n rewrite: customConfiguration?.rewrite,\n\n /**\n * Maps locales to specific domain hostnames for domain-based routing.\n *\n * Default: undefined\n */\n domains: customConfiguration?.domains,\n };\n};\n\n/**\n * Build the editor section of the Intlayer configuration.\n *\n * Returns the **full** {@link EditorConfig} including sensitive fields\n * (`clientId`, `clientSecret`). The browser-safe {@link BrowserIntlayerConfig}\n * omits those fields when exposing config to the client.\n *\n * `clientId` / `clientSecret` are never inferred from the environment: the\n * configuration file is the single source of truth. A project that wants to\n * authenticate from its `.env` file must wire the variables explicitly, e.g.\n * `clientId: process.env.INTLAYER_CLIENT_ID`. Commenting those lines out is\n * therefore enough to disable every credentialed behaviour (remote dictionary\n * fetching, CMS access) even when the variables remain defined in the\n * environment.\n *\n * @param customConfiguration - Partial user-supplied editor config.\n * @returns A fully-defaulted {@link EditorConfig}.\n */\nexport const buildEditorFields = (\n customConfiguration?: Partial<EditorConfig>\n): EditorConfig => {\n const liveSyncPort = customConfiguration?.liveSyncPort ?? LIVE_SYNC_PORT;\n return {\n /**\n * URL of the application. Used to restrict the origin of the editor for security reasons.\n *\n * > '*' means that the editor is accessible from any origin\n *\n * Default: '*'\n */\n applicationURL: customConfiguration?.applicationURL || APPLICATION_URL,\n\n /**\n * URL of the editor server. Used to restrict the origin of the editor for security reasons.\n *\n * > '*' means that the editor is accessible from any origin\n *\n * Default: '*'\n */\n editorURL: customConfiguration?.editorURL || EDITOR_URL,\n\n /**\n * URL of the CMS server. Used to restrict the origin of the editor for security reasons.\n */\n cmsURL: customConfiguration?.cmsURL || CMS_URL,\n\n /**\n * URL of the editor server\n *\n * Default: 'https://back.intlayer.org'\n */\n backendURL: customConfiguration?.backendURL || BACKEND_URL,\n\n /** Port of the editor server\n *\n * Default: 8000\n */\n port: customConfiguration?.port ?? PORT,\n\n /**\n * Indicates if the application interact with the visual editor\n *\n * Default: false;\n *\n * If true, the editor will be able to interact with the application.\n * If false, the editor will not be able to interact with the application.\n * In any case, the editor can only be enabled by the visual editor.\n * Disabling the editor for specific environments is a way to enforce the security.\n *\n * Usage:\n * ```js\n * {\n * // Other configurations\n * editor: {\n * enabled: process.env.NODE_ENV !== 'production',\n * }\n * };\n * ```\n */\n enabled: customConfiguration?.enabled ?? IS_ENABLED,\n\n /**\n * clientId and clientSecret allow the intlayer packages to authenticate with the backend using oAuth2 authentication.\n * An access token is use to authenticate the user related to the project.\n * To get an access token, go to https://app.intlayer.org/project and create an account.\n *\n * Default: undefined\n *\n * > Important: The clientId and clientSecret should be kept secret and not shared publicly. Please ensure to keep them in a secure location, such as environment variables.\n */\n clientId: customConfiguration?.clientId,\n\n /**\n * clientId and clientSecret allow the intlayer packages to authenticate with the backend using oAuth2 authentication.\n * An access token is use to authenticate the user related to the project.\n * To get an access token, go to https://app.intlayer.org/project and create an account.\n *\n * Default: undefined\n *\n * > Important: The clientId and clientSecret should be kept secret and not shared publicly. Please ensure to keep them in a secure location, such as environment variables.\n */\n clientSecret: customConfiguration?.clientSecret,\n\n /**\n * Strategy for prioritizing dictionaries. If a dictionary is both present online and locally, the content will be merge.\n * However, is a field is defined in both dictionary, this setting determines which fields takes the priority over the other.\n *\n * Default: 'local_first'\n *\n * The strategy for prioritizing dictionaries. It can be either 'local_first' or 'distant_first'.\n * - 'local_first': The first dictionary found in the locale is used.\n * - 'distant_first': The first dictionary found in the distant locales is used.\n */\n dictionaryPriorityStrategy:\n customConfiguration?.dictionaryPriorityStrategy ??\n DICTIONARY_PRIORITY_STRATEGY,\n\n /**\n * Indicates if the application should hot reload the locale configurations when a change is detected.\n * For example, when a new dictionary is added or updated, the application will update the content tu display in the page.\n *\n * The hot reload is only available for clients of the `enterprise` plan.\n *\n * Default: false\n */\n liveSync: customConfiguration?.liveSync ?? LIVE_SYNC,\n\n /**\n * Port of the live sync server\n *\n * Default: 4000\n */\n liveSyncPort,\n\n /**\n * URL of the live sync server in case of remote live sync server\n *\n * Default: `http://localhost:${LIVE_SYNC_PORT}`\n */\n liveSyncURL:\n customConfiguration?.liveSyncURL ?? `http://localhost:${liveSyncPort}`,\n };\n};\n\n/**\n * Build the analytics section of the Intlayer configuration.\n *\n * Analytics is strictly opt-in: `enabled` defaults to `false` and, even when\n * enabled, the runtime additionally requires a project key (`editor.clientId`)\n * for attribution before collecting anything.\n *\n * @param customConfiguration - Partial user-supplied analytics config.\n * @returns A fully-defaulted {@link AnalyticsConfig}.\n */\nexport const buildAnalyticsFields = (\n customConfiguration?: Partial<AnalyticsConfig>\n): AnalyticsConfig => ({\n /**\n * Enables analytics collection (page views, content exposures, A/B events).\n *\n * Default: false\n */\n enabled: customConfiguration?.enabled ?? ANALYTICS_ENABLED,\n\n /**\n * Milliseconds between automatic batched flushes to the backend.\n *\n * Default: 20000\n */\n flushInterval: customConfiguration?.flushInterval ?? ANALYTICS_FLUSH_INTERVAL,\n\n /**\n * Fraction of sessions to record, from 0 (none) to 1 (all).\n *\n * Default: 1\n */\n sampleRate: customConfiguration?.sampleRate ?? ANALYTICS_SAMPLE_RATE,\n});\n\n/**\n * Build the log section of the Intlayer configuration.\n *\n * @param customConfiguration - Partial user-supplied log config.\n * @param logFunctions - Optional custom log function overrides (server-only).\n * @returns A fully-defaulted {@link LogConfig}.\n */\nexport const buildLogFields = (\n customConfiguration?: Partial<LogConfig>,\n logFunctions?: LogFunctions\n): LogConfig => ({\n /**\n * Indicates if the logger is enabled\n *\n * Default: 'prefix-no-default'\n *\n * If 'default', the logger is enabled and can be used.\n * If 'verbose', the logger will be enabled and can be used, but will log more information.\n * If 'disabled', the logger is disabled and cannot be used.\n */\n mode: customConfiguration?.mode ?? MODE,\n\n /**\n * Prefix of the logger\n *\n * Default: '[intlayer]'\n *\n * The prefix of the logger.\n */\n prefix: customConfiguration?.prefix ?? PREFIX,\n\n /**\n * Functions to log\n */\n error: logFunctions?.error,\n log: logFunctions?.log,\n info: logFunctions?.info,\n warn: logFunctions?.warn,\n});\n\n// ---------------------------------------------------------------------------\n// Browser configuration builders\n// ---------------------------------------------------------------------------\n\n/**\n * Build a browser-safe {@link BrowserIntlayerConfig} from a raw user config.\n *\n * Applies defaults for every field and strips all server-only or sensitive\n * information (`system`, `content`, `build`, `compiler`, `dictionary`, `ai`,\n * `editor.clientId`, `editor.clientSecret`).\n *\n * This is the browser counterpart of `buildConfigurationFields`. It is safe\n * to call in browser environments because it has no Node.js dependencies.\n *\n * @param customConfig - Optional partial user-supplied Intlayer config.\n * @returns A browser-safe configuration object ready for `window.INTLAYER_CONFIG`.\n *\n * @example\n * ```ts\n * import { buildBrowserConfiguration } from '@intlayer/config/client';\n *\n * window.INTLAYER_CONFIG = buildBrowserConfiguration({\n * internationalization: { locales: ['en', 'fr'], defaultLocale: 'en' },\n * });\n * ```\n */\nexport const buildBrowserConfiguration = (\n customConfig?: CustomIntlayerConfig\n): BrowserIntlayerConfig => {\n const { locales, defaultLocale } = buildInternationalizationFields(\n customConfig?.internationalization\n );\n const routing = buildRoutingFields(customConfig?.routing);\n const {\n clientId: _clientId,\n clientSecret: _clientSecret,\n ...editorPublic\n } = buildEditorFields(customConfig?.editor);\n const analytics = buildAnalyticsFields(customConfig?.analytics);\n const { mode, prefix } = buildLogFields(customConfig?.log);\n\n return {\n internationalization: { locales, defaultLocale },\n routing,\n editor: editorPublic,\n analytics,\n log: { mode, prefix },\n };\n};\n\n/**\n * Extract a {@link BrowserIntlayerConfig} from an already-built full\n * {@link IntlayerConfig}.\n *\n * Used by build plugins (`vite-intlayer`, `withIntlayer`) which already hold\n * the full server-side config and need to inject the browser-safe subset at\n * compile time via a bundler `define`.\n *\n * @param config - A fully-built server-side Intlayer configuration.\n * @returns The browser-safe subset of that configuration.\n */\nexport const extractBrowserConfiguration = (\n config: IntlayerConfig\n): BrowserIntlayerConfig => ({\n internationalization: {\n locales: config.internationalization.locales,\n defaultLocale: config.internationalization.defaultLocale,\n },\n routing: {\n mode: config.routing.mode,\n enableProxy: config.routing.enableProxy,\n storage: config.routing.storage,\n basePath: config.routing.basePath,\n rewrite: config.routing.rewrite,\n },\n editor: {\n applicationURL: config.editor.applicationURL,\n editorURL: config.editor.editorURL,\n cmsURL: config.editor.cmsURL,\n backendURL: config.editor.backendURL,\n port: config.editor.port,\n enabled: config.editor.enabled,\n dictionaryPriorityStrategy: config.editor.dictionaryPriorityStrategy,\n liveSync: config.editor.liveSync,\n liveSyncPort: config.editor.liveSyncPort,\n liveSyncURL: config.editor.liveSyncURL,\n },\n analytics: buildAnalyticsFields(config.analytics),\n log: {\n mode: config.log.mode,\n prefix: config.log.prefix,\n },\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"buildBrowserConfiguration.mjs","names":[],"sources":["../../../src/configFile/buildBrowserConfiguration.ts"],"sourcesContent":["import type {\n AnalyticsConfig,\n CustomIntlayerConfig,\n CustomRoutingConfig,\n EditorConfig,\n InternationalizationConfig,\n IntlayerConfig,\n LogConfig,\n LogFunctions,\n RoutingConfig,\n} from '@intlayer/types/config';\nimport {\n ANALYTICS_ENABLED,\n ANALYTICS_FLUSH_INTERVAL,\n ANALYTICS_SAMPLE_RATE,\n} from '../defaultValues/analytics';\nimport {\n APPLICATION_URL,\n BACKEND_URL,\n CMS_URL,\n DICTIONARY_PRIORITY_STRATEGY,\n EDITOR_URL,\n IS_ENABLED,\n LIVE_SYNC,\n LIVE_SYNC_PORT,\n PORT,\n} from '../defaultValues/editor';\nimport {\n DEFAULT_LOCALE,\n LOCALES,\n REQUIRED_LOCALES,\n STRICT_MODE,\n} from '../defaultValues/internationalization';\nimport { MODE, PREFIX } from '../defaultValues/log';\nimport {\n BASE_PATH,\n ENABLE_PROXY,\n ROUTING_MODE,\n STORAGE,\n} from '../defaultValues/routing';\nimport { getStorageAttributes } from '../utils/getStorageAttributes';\n\n// ---------------------------------------------------------------------------\n// Type\n// ---------------------------------------------------------------------------\n\n/**\n * Browser-safe subset of {@link IntlayerConfig}.\n *\n * Excludes server-only fields (`system`, `content`, `build`, `compiler`,\n * `dictionary`, `ai`) and sensitive editor credentials (`clientId`,\n * `clientSecret`) that must never be shipped to the browser.\n */\nexport type BrowserIntlayerConfig = {\n internationalization: Pick<\n InternationalizationConfig,\n 'locales' | 'defaultLocale'\n >;\n routing: RoutingConfig;\n editor: Omit<EditorConfig, 'clientId' | 'clientSecret'>;\n analytics: AnalyticsConfig;\n log: Pick<LogConfig, 'mode' | 'prefix'>;\n};\n\ndeclare global {\n interface Window {\n /** Browser-safe Intlayer configuration injected by a build plugin or `installIntlayer`. */\n INTLAYER_CONFIG?: BrowserIntlayerConfig;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Shared field builders (browser-safe — no Node.js APIs)\n//\n// These functions are re-used by both `buildBrowserConfiguration` (browser)\n// and `buildConfigurationFields` (server) to avoid duplication.\n// ---------------------------------------------------------------------------\n\n/**\n * Build the internationalization section of the Intlayer configuration.\n *\n * @param customConfiguration - Partial user-supplied internationalization config.\n * @returns A fully-defaulted {@link InternationalizationConfig}.\n */\nexport const buildInternationalizationFields = (\n customConfiguration?: Partial<InternationalizationConfig>\n): InternationalizationConfig => ({\n /**\n * Locales available in the application\n *\n * Default: ['en']\n *\n */\n locales: customConfiguration?.locales ?? LOCALES,\n\n /**\n * Locales required by TypeScript to ensure strong implementations of internationalized content using typescript.\n *\n * Default: []\n *\n * If empty, all locales are required in `strict` mode.\n *\n * Ensure required locales are also defined in the `locales` field.\n */\n requiredLocales:\n customConfiguration?.requiredLocales ??\n customConfiguration?.locales ??\n REQUIRED_LOCALES,\n\n /**\n * Ensure strong implementations of internationalized content using typescript.\n * - If set to \"strict\", the translation `t` function will require each declared locales to be defined. If one locale is missing, or if a locale is not declared in your config, it will throw an error.\n * - If set to \"inclusive\", the translation `t` function will require each declared locales to be defined. If one locale is missing, it will throw a warning. But will accept if a locale is not declared in your config, but exist.\n * - If set to \"loose\", the translation `t` function will accept any existing locale.\n *\n * Default: \"inclusive\"\n */\n strictMode: customConfiguration?.strictMode ?? STRICT_MODE,\n\n /**\n * Default locale of the application for fallback\n *\n * Default: 'en'\n */\n defaultLocale: customConfiguration?.defaultLocale ?? DEFAULT_LOCALE,\n});\n\n/**\n * Build the routing section of the Intlayer configuration.\n *\n * @param customConfiguration - Partial user-supplied routing config.\n * @returns A fully-defaulted {@link RoutingConfig}.\n */\nexport const buildRoutingFields = (\n customConfiguration?: Partial<CustomRoutingConfig>\n): RoutingConfig => {\n const storage = customConfiguration?.storage ?? STORAGE;\n\n return {\n /**\n * URL routing mode for locale handling\n *\n * Controls how locales are represented in application URLs:\n * - 'prefix-no-default': Prefix all locales except the default locale (default)\n * - en → /dashboard\n * - fr → /fr/dashboard\n *\n * - 'prefix-all': Prefix all locales including the default locale\n * - en → /en/dashboard\n * - fr → /fr/dashboard\n *\n * - 'search-params': Use search parameters for locale handling\n * - en → /dashboard?locale=en\n * - fr → /fr/dashboard?locale=fr\n *\n * - 'no-prefix': No locale prefixing in URLs\n * - en → /dashboard\n * - fr → /dashboard\n *\n * Default: 'prefix-no-default'\n */\n mode: customConfiguration?.mode ?? ROUTING_MODE,\n\n /**\n * Enables the Intlayer locale-routing proxy (middleware).\n *\n * When enabled, the build-tool integration (e.g. the `intlayer()` Vite plugin)\n * automatically wires the locale-detection / redirect / rewrite middleware in\n * development, preview, and production SSR.\n *\n * - `undefined` (auto, default): the proxy runs, but development and preview\n * servers ignore the stored locale (cookie / header) as a redirect source.\n * - `true`: full proxy behaviour in every environment.\n * - `false`: no locale routing; the proxy is not registered.\n *\n * Default: undefined (auto)\n */\n enableProxy: customConfiguration?.enableProxy ?? ENABLE_PROXY,\n\n /**\n * Configuration for storing the locale in the client (localStorage or sessionStorage)\n *\n * If false, the locale will not be stored by the middleware.\n * If true, the locale storage will consider all default values. (cookie and header)\n *\n * Default: ['cookie', 'header']\n *\n */\n storage: getStorageAttributes(storage),\n\n /**\n * Base path of the application URL\n *\n * Default: ''\n *\n * Example:\n * - If the application is hosted at https://example.com/my-app\n * - The base path is '/my-app'\n * - The URL will be https://example.com/my-app/en\n * - If the base path is not set, the URL will be https://example.com/en\n */\n basePath: customConfiguration?.basePath ?? BASE_PATH,\n\n /**\n * Custom URL rewriting rules that override the default routing mode for specific paths.\n * Allows you to define locale-specific paths that differ from the standard routing behavior.\n * Supports dynamic route parameters using `[param]` syntax.\n *\n * Default: undefined\n *\n * Example:\n * ```typescript\n * rewrite: {\n * \"/about\": {\n * en: \"/about\",\n * fr: \"/a-propos\",\n * },\n * \"/product/[slug]\": {\n * en: \"/product/[slug]\",\n * fr: \"/produit/[slug]\",\n * },\n * }\n * ```\n *\n * Note:\n * - The rewrite rules take precedence over the default `mode` behavior.\n * - If a path matches a rewrite rule, the localized path from the rewrite configuration will be used.\n * - Dynamic route parameters are supported using bracket notation (e.g., `[slug]`, `[id]`).\n * - Works with both Next.js and Vite applications.\n */\n rewrite: customConfiguration?.rewrite,\n\n /**\n * Maps locales to specific domain hostnames for domain-based routing.\n *\n * Default: undefined\n */\n domains: customConfiguration?.domains,\n };\n};\n\n/**\n * Build the editor section of the Intlayer configuration.\n *\n * Returns the **full** {@link EditorConfig} including sensitive fields\n * (`clientId`, `clientSecret`). The browser-safe {@link BrowserIntlayerConfig}\n * omits those fields when exposing config to the client.\n *\n * `clientId` / `clientSecret` are never inferred from the environment: the\n * configuration file is the single source of truth. A project that wants to\n * authenticate from its `.env` file must wire the variables explicitly, e.g.\n * `clientId: process.env.INTLAYER_CLIENT_ID`. Commenting those lines out is\n * therefore enough to disable every credentialed behaviour (remote dictionary\n * fetching, CMS access) even when the variables remain defined in the\n * environment.\n *\n * @param customConfiguration - Partial user-supplied editor config.\n * @returns A fully-defaulted {@link EditorConfig}.\n */\nexport const buildEditorFields = (\n customConfiguration?: Partial<EditorConfig>\n): EditorConfig => {\n const liveSyncPort = customConfiguration?.liveSyncPort ?? LIVE_SYNC_PORT;\n return {\n /**\n * URL of the application. Used to restrict the origin of the editor for security reasons.\n *\n * > '*' means that the editor is accessible from any origin\n *\n * Default: '*'\n */\n applicationURL: customConfiguration?.applicationURL || APPLICATION_URL,\n\n /**\n * URL of the editor server. Used to restrict the origin of the editor for security reasons.\n *\n * > '*' means that the editor is accessible from any origin\n *\n * Default: '*'\n */\n editorURL: customConfiguration?.editorURL || EDITOR_URL,\n\n /**\n * URL of the CMS server. Used to restrict the origin of the editor for security reasons.\n */\n cmsURL: customConfiguration?.cmsURL || CMS_URL,\n\n /**\n * URL of the editor server\n *\n * Default: 'https://back.intlayer.org'\n */\n backendURL: customConfiguration?.backendURL || BACKEND_URL,\n\n /** Port of the editor server\n *\n * Default: 8000\n */\n port: customConfiguration?.port ?? PORT,\n\n /**\n * Indicates if the application interact with the visual editor\n *\n * Default: false;\n *\n * If true, the editor will be able to interact with the application.\n * If false, the editor will not be able to interact with the application.\n * In any case, the editor can only be enabled by the visual editor.\n * Disabling the editor for specific environments is a way to enforce the security.\n *\n * Usage:\n * ```js\n * {\n * // Other configurations\n * editor: {\n * enabled: process.env.NODE_ENV !== 'production',\n * }\n * };\n * ```\n */\n enabled: customConfiguration?.enabled ?? IS_ENABLED,\n\n /**\n * clientId and clientSecret allow the intlayer packages to authenticate with the backend using oAuth2 authentication.\n * An access token is use to authenticate the user related to the project.\n * To get an access token, go to https://app.intlayer.org/project and create an account.\n *\n * Default: undefined\n *\n * > Important: The clientId and clientSecret should be kept secret and not shared publicly. Please ensure to keep them in a secure location, such as environment variables.\n */\n clientId: customConfiguration?.clientId,\n\n /**\n * clientId and clientSecret allow the intlayer packages to authenticate with the backend using oAuth2 authentication.\n * An access token is use to authenticate the user related to the project.\n * To get an access token, go to https://app.intlayer.org/project and create an account.\n *\n * Default: undefined\n *\n * > Important: The clientId and clientSecret should be kept secret and not shared publicly. Please ensure to keep them in a secure location, such as environment variables.\n */\n clientSecret: customConfiguration?.clientSecret,\n\n /**\n * Strategy for prioritizing dictionaries. If a dictionary is both present online and locally, the content will be merge.\n * However, is a field is defined in both dictionary, this setting determines which fields takes the priority over the other.\n *\n * Default: 'local_first'\n *\n * The strategy for prioritizing dictionaries. It can be either 'local_first' or 'distant_first'.\n * - 'local_first': The first dictionary found in the locale is used.\n * - 'distant_first': The first dictionary found in the distant locales is used.\n */\n dictionaryPriorityStrategy:\n customConfiguration?.dictionaryPriorityStrategy ??\n DICTIONARY_PRIORITY_STRATEGY,\n\n /**\n * Indicates if the application should hot reload the locale configurations when a change is detected.\n * For example, when a new dictionary is added or updated, the application will update the content tu display in the page.\n *\n * The hot reload is only available for clients of the `enterprise` plan.\n *\n * Default: false\n */\n liveSync: customConfiguration?.liveSync ?? LIVE_SYNC,\n\n /**\n * Port of the live sync server\n *\n * Default: 4000\n */\n liveSyncPort,\n\n /**\n * URL of the live sync server in case of remote live sync server\n *\n * Default: `http://localhost:${LIVE_SYNC_PORT}`\n */\n liveSyncURL:\n customConfiguration?.liveSyncURL ?? `http://localhost:${liveSyncPort}`,\n };\n};\n\n/**\n * Build the analytics section of the Intlayer configuration.\n *\n * Analytics is opt-out: `enabled` defaults to `true`, so installing the\n * optional `@intlayer/analytics` package is enough to turn it on. The runtime\n * additionally requires a project key (`editor.clientId`) for attribution\n * before collecting anything.\n *\n * An explicit `enabled` value always wins, so a project whose package manager\n * hides the package from the project root can still opt back in.\n *\n * @param customConfiguration - Partial user-supplied analytics config.\n * @param isPackageInstalled - Whether `@intlayer/analytics` is installed in the\n * project. Browser callers cannot resolve packages and pass `true`, letting\n * the runtime dynamic `import()` fail silently when it is absent.\n * @returns A fully-defaulted {@link AnalyticsConfig}.\n */\nexport const buildAnalyticsFields = (\n customConfiguration?: Partial<AnalyticsConfig>,\n isPackageInstalled = true\n): AnalyticsConfig => ({\n /**\n * Enables analytics collection (page views, content exposures, A/B events).\n *\n * Default: true, as soon as `@intlayer/analytics` is installed.\n */\n enabled:\n customConfiguration?.enabled ?? (ANALYTICS_ENABLED && isPackageInstalled),\n\n /**\n * Milliseconds between automatic batched flushes to the backend.\n *\n * Default: 20000\n */\n flushInterval: customConfiguration?.flushInterval ?? ANALYTICS_FLUSH_INTERVAL,\n\n /**\n * Fraction of sessions to record, from 0 (none) to 1 (all).\n *\n * Default: 1\n */\n sampleRate: customConfiguration?.sampleRate ?? ANALYTICS_SAMPLE_RATE,\n});\n\n/**\n * Build the log section of the Intlayer configuration.\n *\n * @param customConfiguration - Partial user-supplied log config.\n * @param logFunctions - Optional custom log function overrides (server-only).\n * @returns A fully-defaulted {@link LogConfig}.\n */\nexport const buildLogFields = (\n customConfiguration?: Partial<LogConfig>,\n logFunctions?: LogFunctions\n): LogConfig => ({\n /**\n * Indicates if the logger is enabled\n *\n * Default: 'prefix-no-default'\n *\n * If 'default', the logger is enabled and can be used.\n * If 'verbose', the logger will be enabled and can be used, but will log more information.\n * If 'disabled', the logger is disabled and cannot be used.\n */\n mode: customConfiguration?.mode ?? MODE,\n\n /**\n * Prefix of the logger\n *\n * Default: '[intlayer]'\n *\n * The prefix of the logger.\n */\n prefix: customConfiguration?.prefix ?? PREFIX,\n\n /**\n * Functions to log\n */\n error: logFunctions?.error,\n log: logFunctions?.log,\n info: logFunctions?.info,\n warn: logFunctions?.warn,\n});\n\n// ---------------------------------------------------------------------------\n// Browser configuration builders\n// ---------------------------------------------------------------------------\n\n/**\n * Build a browser-safe {@link BrowserIntlayerConfig} from a raw user config.\n *\n * Applies defaults for every field and strips all server-only or sensitive\n * information (`system`, `content`, `build`, `compiler`, `dictionary`, `ai`,\n * `editor.clientId`, `editor.clientSecret`).\n *\n * This is the browser counterpart of `buildConfigurationFields`. It is safe\n * to call in browser environments because it has no Node.js dependencies.\n *\n * @param customConfig - Optional partial user-supplied Intlayer config.\n * @returns A browser-safe configuration object ready for `window.INTLAYER_CONFIG`.\n *\n * @example\n * ```ts\n * import { buildBrowserConfiguration } from '@intlayer/config/client';\n *\n * window.INTLAYER_CONFIG = buildBrowserConfiguration({\n * internationalization: { locales: ['en', 'fr'], defaultLocale: 'en' },\n * });\n * ```\n */\nexport const buildBrowserConfiguration = (\n customConfig?: CustomIntlayerConfig\n): BrowserIntlayerConfig => {\n const { locales, defaultLocale } = buildInternationalizationFields(\n customConfig?.internationalization\n );\n const routing = buildRoutingFields(customConfig?.routing);\n const {\n clientId: _clientId,\n clientSecret: _clientSecret,\n ...editorPublic\n } = buildEditorFields(customConfig?.editor);\n const analytics = buildAnalyticsFields(customConfig?.analytics);\n const { mode, prefix } = buildLogFields(customConfig?.log);\n\n return {\n internationalization: { locales, defaultLocale },\n routing,\n editor: editorPublic,\n analytics,\n log: { mode, prefix },\n };\n};\n\n/**\n * Extract a {@link BrowserIntlayerConfig} from an already-built full\n * {@link IntlayerConfig}.\n *\n * Used by build plugins (`vite-intlayer`, `withIntlayer`) which already hold\n * the full server-side config and need to inject the browser-safe subset at\n * compile time via a bundler `define`.\n *\n * @param config - A fully-built server-side Intlayer configuration.\n * @returns The browser-safe subset of that configuration.\n */\nexport const extractBrowserConfiguration = (\n config: IntlayerConfig\n): BrowserIntlayerConfig => ({\n internationalization: {\n locales: config.internationalization.locales,\n defaultLocale: config.internationalization.defaultLocale,\n },\n routing: {\n mode: config.routing.mode,\n enableProxy: config.routing.enableProxy,\n storage: config.routing.storage,\n basePath: config.routing.basePath,\n rewrite: config.routing.rewrite,\n },\n editor: {\n applicationURL: config.editor.applicationURL,\n editorURL: config.editor.editorURL,\n cmsURL: config.editor.cmsURL,\n backendURL: config.editor.backendURL,\n port: config.editor.port,\n enabled: config.editor.enabled,\n dictionaryPriorityStrategy: config.editor.dictionaryPriorityStrategy,\n liveSync: config.editor.liveSync,\n liveSyncPort: config.editor.liveSyncPort,\n liveSyncURL: config.editor.liveSyncURL,\n },\n analytics: buildAnalyticsFields(config.analytics),\n log: {\n mode: config.log.mode,\n prefix: config.log.prefix,\n },\n});\n"],"mappings":"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"}
@@ -1,10 +1,11 @@
1
1
  import { __require } from "../_virtual/_rolldown/runtime.mjs";
2
- import { BUILD_MODE, CACHE, MINIFY, OUTPUT_FORMAT, PURGE, TRAVERSE_PATTERN, TYPE_CHECKING } from "../defaultValues/build.mjs";
2
+ import { BUILD_MODE, CACHE, CHUNK_GROUPING, DICTIONARIES_PRELOAD, MINIFY, OUTPUT_FORMAT, PURGE, TRAVERSE_PATTERN, TYPE_CHECKING } from "../defaultValues/build.mjs";
3
3
  import { COMPILER_DICTIONARY_KEY_PREFIX, COMPILER_ENABLED, COMPILER_NO_METADATA, COMPILER_SAVE_COMPONENTS } from "../defaultValues/compiler.mjs";
4
4
  import { CODE_DIR, CONTENT_DIR, EXCLUDED_PATHS, FILE_EXTENSIONS, WATCH } from "../defaultValues/content.mjs";
5
5
  import { CONTENT_AUTO_TRANSFORMATION, FILL, IMPORT_MODE, LOCATION } from "../defaultValues/dictionary.mjs";
6
6
  import { CACHE_DIR, CONFIG_DIR, DICTIONARIES_DIR, DYNAMIC_DICTIONARIES_DIR, FETCH_DICTIONARIES_DIR, MAIN_DIR, MODULE_AUGMENTATION_DIR, REMOTE_DICTIONARIES_DIR, TEMP_DIR, TYPES_DIR, UNMERGED_DICTIONARIES_DIR } from "../defaultValues/system.mjs";
7
7
  import { getProjectRequire } from "../utils/ESMxCJSHelpers.mjs";
8
+ import { isPackageInstalled } from "../utils/isPackageInstalled.mjs";
8
9
  import { buildAnalyticsFields, buildBrowserConfiguration, buildEditorFields, buildInternationalizationFields, buildLogFields, buildRoutingFields, extractBrowserConfiguration } from "./buildBrowserConfiguration.mjs";
9
10
  import { intlayerConfigSchema } from "./configurationSchema.mjs";
10
11
  import { statSync } from "node:fs";
@@ -13,6 +14,11 @@ import { dirname, isAbsolute, join } from "node:path";
13
14
  //#region src/configFile/buildConfigurationFields.ts
14
15
  let storedConfiguration;
15
16
  /**
17
+ * Optional companion package whose presence in the project dependency tree
18
+ * opts the application into analytics collection.
19
+ */
20
+ const ANALYTICS_PACKAGE_NAME = "@intlayer/analytics";
21
+ /**
16
22
  * Build the `system` section of the Intlayer configuration.
17
23
  *
18
24
  * Resolves all directory paths (dictionaries, types, cache, …) relative to
@@ -256,6 +262,30 @@ const buildBuildFields = (customConfiguration) => ({
256
262
  */
257
263
  purge: customConfiguration?.purge ?? false,
258
264
  /**
265
+ * Group the per-locale dictionary chunks by the code-split boundary that uses
266
+ * them, so a lazily loaded page fetches its content in one request.
267
+ *
268
+ * Default: true
269
+ *
270
+ * Note:
271
+ * - Only applies to dictionaries using `importMode: 'dynamic'`.
272
+ * - Only applies to the client build, and only when bundling (not in dev).
273
+ */
274
+ chunkGrouping: customConfiguration?.chunkGrouping ?? true,
275
+ /**
276
+ * Load a dictionary together with the chunk that uses it, instead of fetching
277
+ * it once that chunk renders — so a reader renders synchronously rather than
278
+ * suspending, and navigating no longer flashes a loading state.
279
+ *
280
+ * Default: true
281
+ *
282
+ * Note:
283
+ * - Only applies to dictionaries using `importMode: 'dynamic'`.
284
+ * - Only applies to the client build; the server keeps loading on demand.
285
+ * - Requires a bundler supporting top-level await (Vite, esbuild).
286
+ */
287
+ dictionariesPreload: customConfiguration?.dictionariesPreload ?? true,
288
+ /**
259
289
  * Pattern to traverse the code to optimize.
260
290
  *
261
291
  * Allows to avoid to traverse the code that is not relevant to the optimization.
@@ -570,7 +600,7 @@ const buildConfigurationFields = (customConfiguration, baseDir, logFunctions) =>
570
600
  routing: browserConfig.routing,
571
601
  internationalization: internationalizationConfig,
572
602
  editor: editorConfig,
573
- analytics: buildAnalyticsFields(customConfiguration?.analytics),
603
+ analytics: buildAnalyticsFields(customConfiguration?.analytics, isPackageInstalled(ANALYTICS_PACKAGE_NAME, systemConfig.baseDir)),
574
604
  log: logConfig,
575
605
  system: systemConfig,
576
606
  content: contentConfig,