@inlang/paraglide-js 2.24.1 → 2.25.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 (85) hide show
  1. package/dist/bundler-plugins/esbuild.d.ts +1 -1
  2. package/dist/bundler-plugins/per-locale-build/index.d.ts +3 -3
  3. package/dist/bundler-plugins/per-locale-build/index.js +59 -15
  4. package/dist/bundler-plugins/per-locale-build/index.js.map +1 -1
  5. package/dist/bundler-plugins/rolldown.d.ts +1 -1
  6. package/dist/bundler-plugins/rollup.d.ts +1 -1
  7. package/dist/bundler-plugins/rspack.d.ts +1 -1
  8. package/dist/bundler-plugins/unplugin.d.ts +35 -1
  9. package/dist/bundler-plugins/unplugin.js +404 -123
  10. package/dist/bundler-plugins/unplugin.js.map +1 -1
  11. package/dist/bundler-plugins/unplugin.test.js +357 -202
  12. package/dist/bundler-plugins/unplugin.test.js.map +1 -1
  13. package/dist/bundler-plugins/vite.d.ts +2 -2
  14. package/dist/bundler-plugins/vite.js.map +1 -1
  15. package/dist/bundler-plugins/vite.test.js +2 -1
  16. package/dist/bundler-plugins/vite.test.js.map +1 -1
  17. package/dist/bundler-plugins/webpack.d.ts +1 -1
  18. package/dist/cli/commands/compile/command.js +156 -36
  19. package/dist/cli/commands/compile/command.js.map +1 -1
  20. package/dist/cli/commands/compile/command.test.js +311 -7
  21. package/dist/cli/commands/compile/command.test.js.map +1 -1
  22. package/dist/cli/defaults.d.ts +0 -2
  23. package/dist/cli/defaults.js +0 -2
  24. package/dist/cli/defaults.js.map +1 -1
  25. package/dist/cli/steps/initialize-inlang-project.js +2 -1
  26. package/dist/cli/steps/initialize-inlang-project.js.map +1 -1
  27. package/dist/cli/steps/prompt-for-outdir.js +1 -1
  28. package/dist/cli/steps/prompt-for-outdir.js.map +1 -1
  29. package/dist/compiler/compile-annotation.d.ts +2 -2
  30. package/dist/compiler/compile-annotation.js +9 -9
  31. package/dist/compiler/compile-annotation.js.map +1 -1
  32. package/dist/compiler/compile-local-variable.d.ts +2 -1
  33. package/dist/compiler/compile-local-variable.js +4 -4
  34. package/dist/compiler/compile-local-variable.js.map +1 -1
  35. package/dist/compiler/compile-local-variable.test.js +82 -0
  36. package/dist/compiler/compile-local-variable.test.js.map +1 -1
  37. package/dist/compiler/compile-message.js +10 -2
  38. package/dist/compiler/compile-message.js.map +1 -1
  39. package/dist/compiler/compile-message.test.js +132 -2
  40. package/dist/compiler/compile-message.test.js.map +1 -1
  41. package/dist/compiler/compile-pattern.js +4 -2
  42. package/dist/compiler/compile-pattern.js.map +1 -1
  43. package/dist/compiler/compile-pattern.test.js +31 -0
  44. package/dist/compiler/compile-pattern.test.js.map +1 -1
  45. package/dist/compiler/compiler-options.d.ts +1 -1
  46. package/dist/compiler/compiler-options.js.map +1 -1
  47. package/dist/compiler/server/create-server-file.js +18 -4
  48. package/dist/compiler/server/create-server-file.js.map +1 -1
  49. package/dist/compiler/server/create-server-file.test.js +76 -0
  50. package/dist/compiler/server/create-server-file.test.js.map +1 -1
  51. package/dist/compiler/variable-access.d.ts +2 -0
  52. package/dist/compiler/variable-access.js +6 -0
  53. package/dist/compiler/variable-access.js.map +1 -1
  54. package/dist/index.d.ts +1 -0
  55. package/dist/index.js +1 -0
  56. package/dist/index.js.map +1 -1
  57. package/dist/services/config/config-schema.d.ts +69 -0
  58. package/dist/services/config/config-schema.js +78 -0
  59. package/dist/services/config/config-schema.js.map +1 -0
  60. package/dist/services/config/defaults.d.ts +11 -0
  61. package/dist/services/config/defaults.js +12 -0
  62. package/dist/services/config/defaults.js.map +1 -0
  63. package/dist/services/config/discover-config-file.d.ts +19 -0
  64. package/dist/services/config/discover-config-file.js +39 -0
  65. package/dist/services/config/discover-config-file.js.map +1 -0
  66. package/dist/services/config/index.d.ts +24 -0
  67. package/dist/services/config/index.js +25 -0
  68. package/dist/services/config/index.js.map +1 -0
  69. package/dist/services/config/load-paraglide-config.d.ts +44 -0
  70. package/dist/services/config/load-paraglide-config.js +109 -0
  71. package/dist/services/config/load-paraglide-config.js.map +1 -0
  72. package/dist/services/config/load-paraglide-config.test.d.ts +1 -0
  73. package/dist/services/config/load-paraglide-config.test.js +201 -0
  74. package/dist/services/config/load-paraglide-config.test.js.map +1 -0
  75. package/dist/services/config/resolve-compiler-options.d.ts +24 -0
  76. package/dist/services/config/resolve-compiler-options.js +51 -0
  77. package/dist/services/config/resolve-compiler-options.js.map +1 -0
  78. package/dist/services/config/resolve-compiler-options.test.d.ts +1 -0
  79. package/dist/services/config/resolve-compiler-options.test.js +60 -0
  80. package/dist/services/config/resolve-compiler-options.test.js.map +1 -0
  81. package/dist/services/env-variables/index.js +1 -1
  82. package/dist/services/env-variables/index.js.map +1 -1
  83. package/dist/services/logger/index.js +2 -0
  84. package/dist/services/logger/index.js.map +1 -1
  85. package/package.json +4 -2
@@ -0,0 +1,78 @@
1
+ import * as v from "valibot";
2
+ /**
3
+ * Strategy names that are built into the runtime. Custom strategies are
4
+ * prefixed with `custom-` and are matched separately.
5
+ */
6
+ const BUILTIN_STRATEGY_NAMES = [
7
+ "cookie",
8
+ "baseLocale",
9
+ "globalVariable",
10
+ "url",
11
+ "preferredLanguage",
12
+ "localStorage",
13
+ ];
14
+ // Value-position pins so the type-level asserts below are actually
15
+ // evaluated by tsc — an unused alias resolving to `never` would compile.
16
+ export const allStrategiesCovered = true;
17
+ /**
18
+ * Matches strategy names like `custom-myStrategy`.
19
+ *
20
+ * The pattern mirrors the runtime's `isCustomStrategy` check
21
+ * (`custom-[A-Za-z0-9_-]+`) so the config file rejects names the runtime
22
+ * would refuse.
23
+ *
24
+ * Valibot has no template-literal schema, so the regex-checked string
25
+ * schema is narrowed to the compiler's type here.
26
+ */
27
+ const customStrategySchema = v.pipe(v.string(), v.regex(/^custom-[A-Za-z0-9_-]+$/, 'Custom strategies must match "custom-" followed by alphanumeric characters, hyphens, or underscores.'));
28
+ export const strategyNameSchema = v.union([
29
+ v.picklist(BUILTIN_STRATEGY_NAMES),
30
+ customStrategySchema,
31
+ ]);
32
+ const routeStrategySchema = v.union([
33
+ v.object({
34
+ match: v.string(),
35
+ strategy: v.array(strategyNameSchema),
36
+ exclude: v.optional(v.never()),
37
+ }),
38
+ v.object({
39
+ match: v.string(),
40
+ exclude: v.literal(true),
41
+ strategy: v.optional(v.never()),
42
+ }),
43
+ ]);
44
+ /**
45
+ * The valibot schema behind {@link ParaglideConfig}.
46
+ *
47
+ * Every option is optional — values that are not provided fall back to
48
+ * the bundler plugin options / command line flags and ultimately to
49
+ * `defaultCompilerOptions`.
50
+ */
51
+ export const paraglideConfigSchema = v.object({
52
+ outdir: v.optional(v.pipe(v.string(), v.nonEmpty('"outdir" must not be empty'))),
53
+ strategy: v.optional(v.array(strategyNameSchema)),
54
+ routeStrategies: v.optional(v.array(routeStrategySchema)),
55
+ experimentalMiddlewareLocaleSplitting: v.optional(v.boolean()),
56
+ localStorageKey: v.optional(v.string()),
57
+ isServer: v.optional(v.string()),
58
+ experimentalStaticLocale: v.optional(v.string()),
59
+ cookieName: v.optional(v.string()),
60
+ cookieMaxAge: v.optional(v.number()),
61
+ cookieDomain: v.optional(v.string()),
62
+ additionalFiles: v.optional(v.record(v.string(), v.string())),
63
+ emitPrettierIgnore: v.optional(v.boolean()),
64
+ emitReadme: v.optional(v.boolean()),
65
+ emitTsDeclarations: v.optional(v.boolean()),
66
+ urlPatterns: v.optional(v.array(v.object({
67
+ pattern: v.string(),
68
+ localized: v.array(v.tuple([v.string(), v.string()])),
69
+ }))),
70
+ trailingSlash: v.optional(v.picklist(["always", "never"])),
71
+ includeEslintDisableComment: v.optional(v.boolean()),
72
+ disableAsyncLocalStorage: v.optional(v.boolean()),
73
+ emitGitIgnore: v.optional(v.boolean()),
74
+ outputStructure: v.optional(v.picklist(["message-modules", "locale-modules"])),
75
+ cleanOutdir: v.optional(v.boolean()),
76
+ });
77
+ export const schemaMatchesCompilerOptions = true;
78
+ //# sourceMappingURL=config-schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-schema.js","sourceRoot":"","sources":["../../../src/services/config/config-schema.ts"],"names":[],"mappings":"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","sourcesContent":["import * as v from \"valibot\";\nimport type { CompilerOptions } from \"../../compiler/compiler-options.js\";\nimport type { Runtime } from \"../../compiler/runtime/type.js\";\n\n/**\n * The schema of a `paraglide.config.js/ts` file inside the project\n * directory.\n *\n * The config cannot specify `project` — it lives inside the inlang project\n * directory and therefore cannot name it. Values that are not provided\n * fall back to the bundler plugin options / command line flags and\n * ultimately to the built-in defaults.\n */\nexport type ParaglideConfig = Partial<Omit<CompilerOptions, \"fs\" | \"project\">>;\n\n/**\n * Strategy names that are built into the runtime. Custom strategies are\n * prefixed with `custom-` and are matched separately.\n */\nconst BUILTIN_STRATEGY_NAMES = [\n\t\"cookie\",\n\t\"baseLocale\",\n\t\"globalVariable\",\n\t\"url\",\n\t\"preferredLanguage\",\n\t\"localStorage\",\n] as const satisfies readonly Exclude<\n\tRuntime[\"strategy\"][number],\n\t`custom-${string}`\n>[];\n\n// Fails to compile when the runtime gains a strategy name that is neither\n// built-in above nor a `custom-` prefixed name.\ntype UncoveredStrategies = Exclude<\n\tRuntime[\"strategy\"][number],\n\t(typeof BUILTIN_STRATEGY_NAMES)[number] | `custom-${string}`\n>;\nexport type AllStrategiesCovered = [UncoveredStrategies] extends [never]\n\t? true\n\t: never;\n\n// Value-position pins so the type-level asserts below are actually\n// evaluated by tsc — an unused alias resolving to `never` would compile.\nexport const allStrategiesCovered: AllStrategiesCovered = true;\n\n/**\n * Matches strategy names like `custom-myStrategy`.\n *\n * The pattern mirrors the runtime's `isCustomStrategy` check\n * (`custom-[A-Za-z0-9_-]+`) so the config file rejects names the runtime\n * would refuse.\n *\n * Valibot has no template-literal schema, so the regex-checked string\n * schema is narrowed to the compiler's type here.\n */\nconst customStrategySchema = v.pipe(\n\tv.string(),\n\tv.regex(\n\t\t/^custom-[A-Za-z0-9_-]+$/,\n\t\t'Custom strategies must match \"custom-\" followed by alphanumeric characters, hyphens, or underscores.'\n\t)\n) as v.BaseSchema<\n\tstring,\n\t`custom-${string}`,\n\tv.StringIssue | v.RegexIssue<string>\n>;\n\nexport const strategyNameSchema = v.union([\n\tv.picklist(BUILTIN_STRATEGY_NAMES),\n\tcustomStrategySchema,\n]);\n\nconst routeStrategySchema = v.union([\n\tv.object({\n\t\tmatch: v.string(),\n\t\tstrategy: v.array(strategyNameSchema),\n\t\texclude: v.optional(v.never()),\n\t}),\n\tv.object({\n\t\tmatch: v.string(),\n\t\texclude: v.literal(true),\n\t\tstrategy: v.optional(v.never()),\n\t}),\n]);\n\n/**\n * The valibot schema behind {@link ParaglideConfig}.\n *\n * Every option is optional — values that are not provided fall back to\n * the bundler plugin options / command line flags and ultimately to\n * `defaultCompilerOptions`.\n */\nexport const paraglideConfigSchema = v.object({\n\toutdir: v.optional(\n\t\tv.pipe(v.string(), v.nonEmpty('\"outdir\" must not be empty'))\n\t),\n\tstrategy: v.optional(v.array(strategyNameSchema)),\n\trouteStrategies: v.optional(v.array(routeStrategySchema)),\n\texperimentalMiddlewareLocaleSplitting: v.optional(v.boolean()),\n\tlocalStorageKey: v.optional(v.string()),\n\tisServer: v.optional(v.string()),\n\texperimentalStaticLocale: v.optional(v.string()),\n\tcookieName: v.optional(v.string()),\n\tcookieMaxAge: v.optional(v.number()),\n\tcookieDomain: v.optional(v.string()),\n\tadditionalFiles: v.optional(v.record(v.string(), v.string())),\n\temitPrettierIgnore: v.optional(v.boolean()),\n\temitReadme: v.optional(v.boolean()),\n\temitTsDeclarations: v.optional(v.boolean()),\n\turlPatterns: v.optional(\n\t\tv.array(\n\t\t\tv.object({\n\t\t\t\tpattern: v.string(),\n\t\t\t\tlocalized: v.array(v.tuple([v.string(), v.string()])),\n\t\t\t})\n\t\t)\n\t),\n\ttrailingSlash: v.optional(v.picklist([\"always\", \"never\"])),\n\tincludeEslintDisableComment: v.optional(v.boolean()),\n\tdisableAsyncLocalStorage: v.optional(v.boolean()),\n\temitGitIgnore: v.optional(v.boolean()),\n\toutputStructure: v.optional(\n\t\tv.picklist([\"message-modules\", \"locale-modules\"])\n\t),\n\tcleanOutdir: v.optional(v.boolean()),\n});\n\n// Fails to compile when the schema and the compiler options drift apart:\n// every compiler option (except the internal `fs` and the `project` path,\n// which the config cannot specify about itself) must be represented in the\n// schema, the schema must not contain extra options, and the parsed\n// values must match the compiler option types in both directions — a\n// schema narrower than a widened compiler option would wrongly reject\n// valid values.\ntype SchemaOutput = v.InferOutput<typeof paraglideConfigSchema>;\n// `keyof ParaglideConfig` already excludes `fs` and `project` (see its\n// definition) — this assertion is the only place that needs the\n// configurable-keys view.\nexport type SchemaMatchesCompilerOptions = [keyof ParaglideConfig] extends [\n\tkeyof SchemaOutput,\n]\n\t? [keyof SchemaOutput] extends [keyof ParaglideConfig]\n\t\t? SchemaOutput extends ParaglideConfig\n\t\t\t? ParaglideConfig extends SchemaOutput\n\t\t\t\t? true\n\t\t\t\t: never\n\t\t\t: never\n\t\t: never\n\t: never;\n\nexport const schemaMatchesCompilerOptions: SchemaMatchesCompilerOptions = true;\n"]}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Default path to the inlang project directory, relative to the working
3
+ * directory of the integration (CLI: process cwd; bundler plugins: the
4
+ * tool's project root).
5
+ */
6
+ export declare const DEFAULT_PROJECT_PATH = "./project.inlang";
7
+ /**
8
+ * Default output directory for compiled messages, relative to the working
9
+ * directory of the integration.
10
+ */
11
+ export declare const DEFAULT_OUTDIR = "./src/paraglide";
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Default path to the inlang project directory, relative to the working
3
+ * directory of the integration (CLI: process cwd; bundler plugins: the
4
+ * tool's project root).
5
+ */
6
+ export const DEFAULT_PROJECT_PATH = "./project.inlang";
7
+ /**
8
+ * Default output directory for compiled messages, relative to the working
9
+ * directory of the integration.
10
+ */
11
+ export const DEFAULT_OUTDIR = "./src/paraglide";
12
+ //# sourceMappingURL=defaults.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaults.js","sourceRoot":"","sources":["../../../src/services/config/defaults.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,kBAAkB,CAAC;AAEvD;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,iBAAiB,CAAC","sourcesContent":["/**\n * Default path to the inlang project directory, relative to the working\n * directory of the integration (CLI: process cwd; bundler plugins: the\n * tool's project root).\n */\nexport const DEFAULT_PROJECT_PATH = \"./project.inlang\";\n\n/**\n * Default output directory for compiled messages, relative to the working\n * directory of the integration.\n */\nexport const DEFAULT_OUTDIR = \"./src/paraglide\";\n"]}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Config file names that are resolved inside the project directory, in
3
+ * order of precedence. Do not reorder without updating the documentation.
4
+ */
5
+ export declare const CONFIG_FILE_NAMES: readonly ["paraglide.config.js", "paraglide.config.mjs", "paraglide.config.ts", "paraglide.config.cjs"];
6
+ /**
7
+ * Resolves the active paraglide config file inside a project directory.
8
+ *
9
+ * The config lives inside `<projectDir>` (the inlang project itself), so
10
+ * there is no searching and no ambiguity: candidates are checked in
11
+ * precedence order and the first existing file wins. Returns `undefined`
12
+ * when no config file exists.
13
+ */
14
+ export declare function resolveConfigCandidate(projectDir: string): Promise<string | undefined>;
15
+ /**
16
+ * Checks whether a changed path is one of the config file names. Used to
17
+ * filter filesystem watch events in the project directory.
18
+ */
19
+ export declare function isConfigFileName(path: string): boolean;
@@ -0,0 +1,39 @@
1
+ import { stat } from "node:fs/promises";
2
+ import { basename, join } from "node:path";
3
+ /**
4
+ * Config file names that are resolved inside the project directory, in
5
+ * order of precedence. Do not reorder without updating the documentation.
6
+ */
7
+ export const CONFIG_FILE_NAMES = [
8
+ "paraglide.config.js",
9
+ "paraglide.config.mjs",
10
+ "paraglide.config.ts",
11
+ "paraglide.config.cjs",
12
+ ];
13
+ /**
14
+ * Resolves the active paraglide config file inside a project directory.
15
+ *
16
+ * The config lives inside `<projectDir>` (the inlang project itself), so
17
+ * there is no searching and no ambiguity: candidates are checked in
18
+ * precedence order and the first existing file wins. Returns `undefined`
19
+ * when no config file exists.
20
+ */
21
+ export async function resolveConfigCandidate(projectDir) {
22
+ for (const fileName of CONFIG_FILE_NAMES) {
23
+ const filePath = join(projectDir, fileName);
24
+ // stat follows symlinks; a symlinked config is followed once and
25
+ // validated to be a file.
26
+ const stats = await stat(filePath).catch(() => undefined);
27
+ if (stats?.isFile())
28
+ return filePath;
29
+ }
30
+ return undefined;
31
+ }
32
+ /**
33
+ * Checks whether a changed path is one of the config file names. Used to
34
+ * filter filesystem watch events in the project directory.
35
+ */
36
+ export function isConfigFileName(path) {
37
+ return CONFIG_FILE_NAMES.includes(basename(path));
38
+ }
39
+ //# sourceMappingURL=discover-config-file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discover-config-file.js","sourceRoot":"","sources":["../../../src/services/config/discover-config-file.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE3C;;;GAGG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAChC,qBAAqB;IACrB,sBAAsB;IACtB,qBAAqB;IACrB,sBAAsB;CACb,CAAC;AAEX;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC3C,UAAkB;IAElB,KAAK,MAAM,QAAQ,IAAI,iBAAiB,EAAE,CAAC;QAC1C,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QAC5C,iEAAiE;QACjE,0BAA0B;QAC1B,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAC1D,IAAI,KAAK,EAAE,MAAM,EAAE;YAAE,OAAO,QAAQ,CAAC;IACtC,CAAC;IACD,OAAO,SAAS,CAAC;AAClB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC5C,OAAQ,iBAAuC,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;AAC1E,CAAC","sourcesContent":["import { stat } from \"node:fs/promises\";\nimport { basename, join } from \"node:path\";\n\n/**\n * Config file names that are resolved inside the project directory, in\n * order of precedence. Do not reorder without updating the documentation.\n */\nexport const CONFIG_FILE_NAMES = [\n\t\"paraglide.config.js\",\n\t\"paraglide.config.mjs\",\n\t\"paraglide.config.ts\",\n\t\"paraglide.config.cjs\",\n] as const;\n\n/**\n * Resolves the active paraglide config file inside a project directory.\n *\n * The config lives inside `<projectDir>` (the inlang project itself), so\n * there is no searching and no ambiguity: candidates are checked in\n * precedence order and the first existing file wins. Returns `undefined`\n * when no config file exists.\n */\nexport async function resolveConfigCandidate(\n\tprojectDir: string\n): Promise<string | undefined> {\n\tfor (const fileName of CONFIG_FILE_NAMES) {\n\t\tconst filePath = join(projectDir, fileName);\n\t\t// stat follows symlinks; a symlinked config is followed once and\n\t\t// validated to be a file.\n\t\tconst stats = await stat(filePath).catch(() => undefined);\n\t\tif (stats?.isFile()) return filePath;\n\t}\n\treturn undefined;\n}\n\n/**\n * Checks whether a changed path is one of the config file names. Used to\n * filter filesystem watch events in the project directory.\n */\nexport function isConfigFileName(path: string): boolean {\n\treturn (CONFIG_FILE_NAMES as readonly string[]).includes(basename(path));\n}\n"]}
@@ -0,0 +1,24 @@
1
+ import type { ParaglideConfig } from "./config-schema.js";
2
+ export { CONFIG_FILE_NAMES, isConfigFileName, resolveConfigCandidate, } from "./discover-config-file.js";
3
+ export { strategyNameSchema } from "./config-schema.js";
4
+ export { loadParaglideConfig, clearParaglideConfigCache, type LoadedParaglideConfig, } from "./load-paraglide-config.js";
5
+ export { resolveCompilerOptions } from "./resolve-compiler-options.js";
6
+ export { DEFAULT_OUTDIR, DEFAULT_PROJECT_PATH } from "./defaults.js";
7
+ export type { ParaglideConfig } from "./config-schema.js";
8
+ /**
9
+ * Helper for defining a paraglide configuration in
10
+ * `<project>/paraglide.config.js` or `.ts` with type inference and
11
+ * validation.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * // project.inlang/paraglide.config.ts
16
+ * import { defineConfig } from "@inlang/paraglide-js";
17
+ *
18
+ * export default defineConfig({
19
+ * outdir: "./src/paraglide",
20
+ * strategy: ["cookie", "preferredLanguage", "baseLocale"],
21
+ * });
22
+ * ```
23
+ */
24
+ export declare function defineConfig(config: ParaglideConfig): ParaglideConfig;
@@ -0,0 +1,25 @@
1
+ export { CONFIG_FILE_NAMES, isConfigFileName, resolveConfigCandidate, } from "./discover-config-file.js";
2
+ export { strategyNameSchema } from "./config-schema.js";
3
+ export { loadParaglideConfig, clearParaglideConfigCache, } from "./load-paraglide-config.js";
4
+ export { resolveCompilerOptions } from "./resolve-compiler-options.js";
5
+ export { DEFAULT_OUTDIR, DEFAULT_PROJECT_PATH } from "./defaults.js";
6
+ /**
7
+ * Helper for defining a paraglide configuration in
8
+ * `<project>/paraglide.config.js` or `.ts` with type inference and
9
+ * validation.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * // project.inlang/paraglide.config.ts
14
+ * import { defineConfig } from "@inlang/paraglide-js";
15
+ *
16
+ * export default defineConfig({
17
+ * outdir: "./src/paraglide",
18
+ * strategy: ["cookie", "preferredLanguage", "baseLocale"],
19
+ * });
20
+ * ```
21
+ */
22
+ export function defineConfig(config) {
23
+ return config;
24
+ }
25
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/services/config/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACN,iBAAiB,EACjB,gBAAgB,EAChB,sBAAsB,GACtB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EACN,mBAAmB,EACnB,yBAAyB,GAEzB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAGrE;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,YAAY,CAAC,MAAuB;IACnD,OAAO,MAAM,CAAC;AACf,CAAC","sourcesContent":["import type { ParaglideConfig } from \"./config-schema.js\";\n\nexport {\n\tCONFIG_FILE_NAMES,\n\tisConfigFileName,\n\tresolveConfigCandidate,\n} from \"./discover-config-file.js\";\nexport { strategyNameSchema } from \"./config-schema.js\";\nexport {\n\tloadParaglideConfig,\n\tclearParaglideConfigCache,\n\ttype LoadedParaglideConfig,\n} from \"./load-paraglide-config.js\";\nexport { resolveCompilerOptions } from \"./resolve-compiler-options.js\";\nexport { DEFAULT_OUTDIR, DEFAULT_PROJECT_PATH } from \"./defaults.js\";\nexport type { ParaglideConfig } from \"./config-schema.js\";\n\n/**\n * Helper for defining a paraglide configuration in\n * `<project>/paraglide.config.js` or `.ts` with type inference and\n * validation.\n *\n * @example\n * ```ts\n * // project.inlang/paraglide.config.ts\n * import { defineConfig } from \"@inlang/paraglide-js\";\n *\n * export default defineConfig({\n * outdir: \"./src/paraglide\",\n * strategy: [\"cookie\", \"preferredLanguage\", \"baseLocale\"],\n * });\n * ```\n */\nexport function defineConfig(config: ParaglideConfig): ParaglideConfig {\n\treturn config;\n}\n"]}
@@ -0,0 +1,44 @@
1
+ import { Logger } from "../logger/index.js";
2
+ import { type ParaglideConfig } from "./config-schema.js";
3
+ export type LoadedParaglideConfig = {
4
+ /**
5
+ * Absolute path of the loaded config file.
6
+ */
7
+ path: string;
8
+ /**
9
+ * The validated config. Paths stay as written — the integration
10
+ * resolves them against its working directory/root.
11
+ */
12
+ config: ParaglideConfig;
13
+ };
14
+ /**
15
+ * Loads the paraglide config file from a project directory.
16
+ *
17
+ * The config lives inside `<projectDir>` as one of `paraglide.config.js`,
18
+ * `paraglide.config.mjs`, `paraglide.config.ts`, or `paraglide.config.cjs`
19
+ * (checked in this order). Returns `undefined` when no config file exists.
20
+ *
21
+ * Results are cached per process. Watch-mode integrations clear the cache
22
+ * when the config changed on disk — see {@link clearParaglideConfigCache}.
23
+ */
24
+ export declare function loadParaglideConfig(args: {
25
+ /**
26
+ * Absolute path of the inlang project directory that contains the
27
+ * config file.
28
+ */
29
+ projectDir: string;
30
+ /**
31
+ * Logger used for warnings (e.g. unknown options). Defaults to a
32
+ * module-scoped logger. Pass a silent logger to suppress warnings.
33
+ */
34
+ logger?: Logger | undefined;
35
+ }): Promise<LoadedParaglideConfig | undefined>;
36
+ /**
37
+ * Clears the paraglide config cache.
38
+ *
39
+ * When called without arguments the entire cache is cleared. When
40
+ * `projectDir` is provided only that entry is evicted.
41
+ */
42
+ export declare function clearParaglideConfigCache(args?: {
43
+ projectDir?: string;
44
+ }): void;
@@ -0,0 +1,109 @@
1
+ import { createJiti } from "jiti";
2
+ import * as v from "valibot";
3
+ import { createRequire } from "node:module";
4
+ import { resolve } from "node:path";
5
+ import { nodeNormalizePath } from "../../utilities/node-normalize-path.js";
6
+ import { Logger } from "../logger/index.js";
7
+ import { resolveConfigCandidate } from "./discover-config-file.js";
8
+ import { paraglideConfigSchema, } from "./config-schema.js";
9
+ const defaultLogger = new Logger();
10
+ const configCache = new Map();
11
+ /**
12
+ * Loads the paraglide config file from a project directory.
13
+ *
14
+ * The config lives inside `<projectDir>` as one of `paraglide.config.js`,
15
+ * `paraglide.config.mjs`, `paraglide.config.ts`, or `paraglide.config.cjs`
16
+ * (checked in this order). Returns `undefined` when no config file exists.
17
+ *
18
+ * Results are cached per process. Watch-mode integrations clear the cache
19
+ * when the config changed on disk — see {@link clearParaglideConfigCache}.
20
+ */
21
+ export async function loadParaglideConfig(args) {
22
+ // Normalized so callers handing over differently-separated paths
23
+ // (Windows) hit the same cache entry.
24
+ const cacheKey = nodeNormalizePath(resolve(args.projectDir));
25
+ let cached = configCache.get(cacheKey);
26
+ if (cached === undefined) {
27
+ cached = load(cacheKey, args.logger ?? defaultLogger).catch((error) => {
28
+ configCache.delete(cacheKey);
29
+ throw error;
30
+ });
31
+ configCache.set(cacheKey, cached);
32
+ }
33
+ return cached;
34
+ }
35
+ /**
36
+ * Clears the paraglide config cache.
37
+ *
38
+ * When called without arguments the entire cache is cleared. When
39
+ * `projectDir` is provided only that entry is evicted.
40
+ */
41
+ export function clearParaglideConfigCache(args) {
42
+ if (args?.projectDir === undefined) {
43
+ configCache.clear();
44
+ return;
45
+ }
46
+ configCache.delete(nodeNormalizePath(resolve(args.projectDir)));
47
+ }
48
+ async function load(projectDir, logger) {
49
+ const configPath = await resolveConfigCandidate(projectDir);
50
+ if (configPath === undefined)
51
+ return undefined;
52
+ // A fresh jiti instance per load with a disabled module cache ensures
53
+ // that reloading a changed config file picks up the new content.
54
+ const jiti = createJiti(configPath, { moduleCache: false });
55
+ evictConfigModule(configPath);
56
+ let exported;
57
+ try {
58
+ // Loaded synchronously (instead of `jiti.import`): async imports are
59
+ // cached by Node's ESM module registry, which cannot be invalidated.
60
+ const mod = jiti(configPath);
61
+ exported = mod?.default ?? mod;
62
+ }
63
+ catch (error) {
64
+ throw new Error(`Failed to load paraglide config file "${configPath}": ${error?.message ?? String(error)}`, { cause: error });
65
+ }
66
+ return {
67
+ path: configPath,
68
+ config: validateConfig(exported, configPath, logger),
69
+ };
70
+ }
71
+ function validateConfig(exported, filePath, logger) {
72
+ if (typeof exported !== "object" ||
73
+ exported === null ||
74
+ Array.isArray(exported)) {
75
+ throw new Error(`Invalid paraglide config file "${filePath}": expected an object with options as the default export.`);
76
+ }
77
+ const result = v.safeParse(paraglideConfigSchema, exported);
78
+ if (!result.success) {
79
+ const details = result.issues
80
+ .map((issue) => `"${v.getDotPath(issue) ?? "(root)"}": ${issue.message}`)
81
+ .join(", ");
82
+ throw new Error(`Invalid paraglide config file "${filePath}": ${details}.`);
83
+ }
84
+ const knownKeys = new Set(Object.keys(paraglideConfigSchema.entries));
85
+ const unknownKeys = Object.keys(exported).filter((key) => !knownKeys.has(key) && Object.prototype.hasOwnProperty.call(exported, key));
86
+ if (unknownKeys.length > 0) {
87
+ logger.warn(`Unknown option${unknownKeys.length === 1 ? "" : "s"} ${unknownKeys.map((key) => `"${key}"`).join(", ")} in paraglide config file "${filePath}".`);
88
+ }
89
+ return result.output;
90
+ }
91
+ /**
92
+ * Clears only the config module itself, never unrelated project modules.
93
+ *
94
+ * Required despite `moduleCache: false`: jiti delegates plain-CJS files
95
+ * (.cjs) to Node's native `require`, whose module cache is unaffected by
96
+ * jiti's option. Without this eviction, reloading a changed `.cjs` config
97
+ * serves the stale first load forever.
98
+ */
99
+ function evictConfigModule(configPath) {
100
+ const require = createRequire(configPath);
101
+ delete require.cache[configPath];
102
+ try {
103
+ delete require.cache[require.resolve(configPath)];
104
+ }
105
+ catch {
106
+ // The file was removed between discovery and loading.
107
+ }
108
+ }
109
+ //# sourceMappingURL=load-paraglide-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-paraglide-config.js","sourceRoot":"","sources":["../../../src/services/config/load-paraglide-config.ts"],"names":[],"mappings":"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","sourcesContent":["import { createJiti } from \"jiti\";\nimport * as v from \"valibot\";\nimport { createRequire } from \"node:module\";\nimport { resolve } from \"node:path\";\nimport { nodeNormalizePath } from \"../../utilities/node-normalize-path.js\";\nimport { Logger } from \"../logger/index.js\";\nimport { resolveConfigCandidate } from \"./discover-config-file.js\";\nimport {\n\tparaglideConfigSchema,\n\ttype ParaglideConfig,\n} from \"./config-schema.js\";\n\nconst defaultLogger = new Logger();\n\nexport type LoadedParaglideConfig = {\n\t/**\n\t * Absolute path of the loaded config file.\n\t */\n\tpath: string;\n\t/**\n\t * The validated config. Paths stay as written — the integration\n\t * resolves them against its working directory/root.\n\t */\n\tconfig: ParaglideConfig;\n};\n\nconst configCache = new Map<\n\tstring,\n\tPromise<LoadedParaglideConfig | undefined>\n>();\n\n/**\n * Loads the paraglide config file from a project directory.\n *\n * The config lives inside `<projectDir>` as one of `paraglide.config.js`,\n * `paraglide.config.mjs`, `paraglide.config.ts`, or `paraglide.config.cjs`\n * (checked in this order). Returns `undefined` when no config file exists.\n *\n * Results are cached per process. Watch-mode integrations clear the cache\n * when the config changed on disk — see {@link clearParaglideConfigCache}.\n */\nexport async function loadParaglideConfig(args: {\n\t/**\n\t * Absolute path of the inlang project directory that contains the\n\t * config file.\n\t */\n\tprojectDir: string;\n\t/**\n\t * Logger used for warnings (e.g. unknown options). Defaults to a\n\t * module-scoped logger. Pass a silent logger to suppress warnings.\n\t */\n\tlogger?: Logger | undefined;\n}): Promise<LoadedParaglideConfig | undefined> {\n\t// Normalized so callers handing over differently-separated paths\n\t// (Windows) hit the same cache entry.\n\tconst cacheKey = nodeNormalizePath(resolve(args.projectDir));\n\n\tlet cached = configCache.get(cacheKey);\n\tif (cached === undefined) {\n\t\tcached = load(cacheKey, args.logger ?? defaultLogger).catch(\n\t\t\t(error: unknown) => {\n\t\t\t\tconfigCache.delete(cacheKey);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t);\n\t\tconfigCache.set(cacheKey, cached);\n\t}\n\treturn cached;\n}\n\n/**\n * Clears the paraglide config cache.\n *\n * When called without arguments the entire cache is cleared. When\n * `projectDir` is provided only that entry is evicted.\n */\nexport function clearParaglideConfigCache(args?: {\n\tprojectDir?: string;\n}): void {\n\tif (args?.projectDir === undefined) {\n\t\tconfigCache.clear();\n\t\treturn;\n\t}\n\tconfigCache.delete(nodeNormalizePath(resolve(args.projectDir)));\n}\n\nasync function load(\n\tprojectDir: string,\n\tlogger: Logger\n): Promise<LoadedParaglideConfig | undefined> {\n\tconst configPath = await resolveConfigCandidate(projectDir);\n\tif (configPath === undefined) return undefined;\n\n\t// A fresh jiti instance per load with a disabled module cache ensures\n\t// that reloading a changed config file picks up the new content.\n\tconst jiti = createJiti(configPath, { moduleCache: false });\n\tevictConfigModule(configPath);\n\n\tlet exported: unknown;\n\ttry {\n\t\t// Loaded synchronously (instead of `jiti.import`): async imports are\n\t\t// cached by Node's ESM module registry, which cannot be invalidated.\n\t\tconst mod = jiti(configPath) as { default?: unknown } | undefined;\n\t\texported = mod?.default ?? mod;\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`Failed to load paraglide config file \"${configPath}\": ${\n\t\t\t\t(error as Error)?.message ?? String(error)\n\t\t\t}`,\n\t\t\t{ cause: error }\n\t\t);\n\t}\n\n\treturn {\n\t\tpath: configPath,\n\t\tconfig: validateConfig(exported, configPath, logger),\n\t};\n}\n\nfunction validateConfig(\n\texported: unknown,\n\tfilePath: string,\n\tlogger: Logger\n): ParaglideConfig {\n\tif (\n\t\ttypeof exported !== \"object\" ||\n\t\texported === null ||\n\t\tArray.isArray(exported)\n\t) {\n\t\tthrow new Error(\n\t\t\t`Invalid paraglide config file \"${filePath}\": expected an object with options as the default export.`\n\t\t);\n\t}\n\n\tconst result = v.safeParse(paraglideConfigSchema, exported);\n\tif (!result.success) {\n\t\tconst details = result.issues\n\t\t\t.map((issue) => `\"${v.getDotPath(issue) ?? \"(root)\"}\": ${issue.message}`)\n\t\t\t.join(\", \");\n\t\tthrow new Error(`Invalid paraglide config file \"${filePath}\": ${details}.`);\n\t}\n\n\tconst knownKeys = new Set(Object.keys(paraglideConfigSchema.entries));\n\tconst unknownKeys = Object.keys(exported).filter(\n\t\t(key) =>\n\t\t\t!knownKeys.has(key) && Object.prototype.hasOwnProperty.call(exported, key)\n\t);\n\tif (unknownKeys.length > 0) {\n\t\tlogger.warn(\n\t\t\t`Unknown option${\n\t\t\t\tunknownKeys.length === 1 ? \"\" : \"s\"\n\t\t\t} ${unknownKeys.map((key) => `\"${key}\"`).join(\", \")} in paraglide config file \"${filePath}\".`\n\t\t);\n\t}\n\n\treturn result.output;\n}\n\n/**\n * Clears only the config module itself, never unrelated project modules.\n *\n * Required despite `moduleCache: false`: jiti delegates plain-CJS files\n * (.cjs) to Node's native `require`, whose module cache is unaffected by\n * jiti's option. Without this eviction, reloading a changed `.cjs` config\n * serves the stale first load forever.\n */\nfunction evictConfigModule(configPath: string): void {\n\tconst require = createRequire(configPath);\n\tdelete require.cache[configPath];\n\ttry {\n\t\tdelete require.cache[require.resolve(configPath)];\n\t} catch {\n\t\t// The file was removed between discovery and loading.\n\t}\n}\n"]}
@@ -0,0 +1,201 @@
1
+ import { describe, expect, test, vi, beforeEach, afterEach } from "vitest";
2
+ import { mkdtemp, rm, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import path from "node:path";
5
+ import consola from "consola";
6
+ import { clearParaglideConfigCache, loadParaglideConfig, CONFIG_FILE_NAMES, resolveConfigCandidate, isConfigFileName, } from "./index.js";
7
+ import { Logger } from "../logger/index.js";
8
+ let testDirectories = [];
9
+ async function createProjectDirectory() {
10
+ const directory = await mkdtemp(path.join(tmpdir(), "paraglide-config-"));
11
+ testDirectories.push(directory);
12
+ return directory;
13
+ }
14
+ beforeEach(() => {
15
+ clearParaglideConfigCache();
16
+ vi.spyOn(consola, "warn").mockImplementation(() => { });
17
+ });
18
+ afterEach(async () => {
19
+ vi.restoreAllMocks();
20
+ for (const directory of testDirectories.splice(0)) {
21
+ await rm(directory, { recursive: true, force: true });
22
+ }
23
+ });
24
+ describe("resolveConfigCandidate", () => {
25
+ test("returns undefined when no config file exists", async () => {
26
+ const projectDir = await createProjectDirectory();
27
+ expect(await resolveConfigCandidate(projectDir)).toBeUndefined();
28
+ });
29
+ test("prefers .js over .mjs over .ts over .cjs", async () => {
30
+ const projectDir = await createProjectDirectory();
31
+ for (const fileName of CONFIG_FILE_NAMES) {
32
+ await writeFile(path.join(projectDir, fileName), "export default {}");
33
+ }
34
+ expect(await resolveConfigCandidate(projectDir)).toBe(path.join(projectDir, "paraglide.config.js"));
35
+ await rm(path.join(projectDir, "paraglide.config.js"));
36
+ expect(await resolveConfigCandidate(projectDir)).toBe(path.join(projectDir, "paraglide.config.mjs"));
37
+ await rm(path.join(projectDir, "paraglide.config.mjs"));
38
+ expect(await resolveConfigCandidate(projectDir)).toBe(path.join(projectDir, "paraglide.config.ts"));
39
+ await rm(path.join(projectDir, "paraglide.config.ts"));
40
+ expect(await resolveConfigCandidate(projectDir)).toBe(path.join(projectDir, "paraglide.config.cjs"));
41
+ });
42
+ test("isConfigFileName matches exactly the config names", () => {
43
+ expect(isConfigFileName("/x/paraglide.config.js")).toBe(true);
44
+ expect(isConfigFileName("/x/paraglide.config.cjs")).toBe(true);
45
+ expect(isConfigFileName("/x/README.md")).toBe(false);
46
+ expect(isConfigFileName("/x/other.config.js")).toBe(false);
47
+ });
48
+ });
49
+ describe("loadParaglideConfig", () => {
50
+ test("returns undefined when no config file exists", async () => {
51
+ const projectDir = await createProjectDirectory();
52
+ expect(await loadParaglideConfig({ projectDir })).toBeUndefined();
53
+ });
54
+ test("loads every supported file format with a default export", async () => {
55
+ const cases = [
56
+ ["paraglide.config.js", `export default { outdir: './out-js' }`],
57
+ ["paraglide.config.mjs", `export default { outdir: './out-mjs' }`],
58
+ ["paraglide.config.cjs", `module.exports = { outdir: './out-cjs' }`],
59
+ [
60
+ "paraglide.config.ts",
61
+ [
62
+ "const config = {",
63
+ ' outdir: "./out-ts",',
64
+ ' strategy: ["cookie", "baseLocale"],',
65
+ "}",
66
+ "export default config",
67
+ ].join("\n"),
68
+ ],
69
+ ];
70
+ for (const [fileName, content] of cases) {
71
+ const projectDir = await createProjectDirectory();
72
+ await writeFile(path.join(projectDir, fileName), content);
73
+ const loaded = await loadParaglideConfig({ projectDir });
74
+ expect(loaded?.path).toBe(path.join(projectDir, fileName));
75
+ }
76
+ });
77
+ test("leaves outdir paths untouched — the integration resolves them", async () => {
78
+ const projectDir = await createProjectDirectory();
79
+ await writeFile(path.join(projectDir, "paraglide.config.ts"), "export default { outdir: '../out' }");
80
+ expect((await loadParaglideConfig({ projectDir }))?.config.outdir).toBe("../out");
81
+ clearParaglideConfigCache();
82
+ const absoluteOut = path.join(projectDir, "abs-out");
83
+ await writeFile(path.join(projectDir, "paraglide.config.ts"), `export default { outdir: ${JSON.stringify(absoluteOut)} }`);
84
+ expect((await loadParaglideConfig({ projectDir }))?.config.outdir).toBe(absoluteOut);
85
+ });
86
+ test("throws when the config file has no object export", async () => {
87
+ const projectDir = await createProjectDirectory();
88
+ await writeFile(path.join(projectDir, "paraglide.config.ts"), "export default 42");
89
+ await expect(loadParaglideConfig({ projectDir })).rejects.toThrow(/expected an object/);
90
+ });
91
+ test("throws when an option has an invalid value", async () => {
92
+ const projectDir = await createProjectDirectory();
93
+ await writeFile(path.join(projectDir, "paraglide.config.ts"), "export default { cookieMaxAge: 'forever' }");
94
+ await expect(loadParaglideConfig({ projectDir })).rejects.toThrow(/"cookieMaxAge"/);
95
+ });
96
+ test("throws when a strategy name is unknown", async () => {
97
+ const projectDir = await createProjectDirectory();
98
+ await writeFile(path.join(projectDir, "paraglide.config.ts"), "export default { strategy: ['cookie', 'cookies'] }");
99
+ await expect(loadParaglideConfig({ projectDir })).rejects.toThrow(/"strategy\.1"/);
100
+ });
101
+ test("accepts custom strategies matching the runtime pattern", async () => {
102
+ const projectDir = await createProjectDirectory();
103
+ await writeFile(path.join(projectDir, "paraglide.config.ts"), "export default { strategy: ['custom-server', 'custom-my_strategy-2', 'cookie'] }");
104
+ const loaded = await loadParaglideConfig({ projectDir });
105
+ expect(loaded?.config.strategy).toEqual([
106
+ "custom-server",
107
+ "custom-my_strategy-2",
108
+ "cookie",
109
+ ]);
110
+ });
111
+ test("rejects malformed custom strategy names", async () => {
112
+ const projectDir = await createProjectDirectory();
113
+ await writeFile(path.join(projectDir, "paraglide.config.ts"), "export default { strategy: ['custom-', 'custom-my strategy'] }");
114
+ await expect(loadParaglideConfig({ projectDir })).rejects.toThrow(/"strategy\.0"/);
115
+ });
116
+ test("throws when routeStrategies combine strategy and exclude", async () => {
117
+ const projectDir = await createProjectDirectory();
118
+ await writeFile(path.join(projectDir, "paraglide.config.ts"), [
119
+ "export default {",
120
+ " routeStrategies: [{ match: '/admin', strategy: ['url'], exclude: true }],",
121
+ "}",
122
+ ].join("\n"));
123
+ await expect(loadParaglideConfig({ projectDir })).rejects.toThrow(/routeStrategies/);
124
+ });
125
+ test("throws when outputStructure is invalid", async () => {
126
+ const projectDir = await createProjectDirectory();
127
+ await writeFile(path.join(projectDir, "paraglide.config.ts"), "export default { outputStructure: 'modules' }");
128
+ await expect(loadParaglideConfig({ projectDir })).rejects.toThrow(/"outputStructure"/);
129
+ });
130
+ test("rejects an empty outdir", async () => {
131
+ const projectDir = await createProjectDirectory();
132
+ await writeFile(path.join(projectDir, "paraglide.config.ts"), "export default { outdir: '' }");
133
+ await expect(loadParaglideConfig({ projectDir })).rejects.toThrow(/"outdir"/);
134
+ });
135
+ test("warns about unknown options but loads the rest", async () => {
136
+ const projectDir = await createProjectDirectory();
137
+ await writeFile(path.join(projectDir, "paraglide.config.ts"), "export default { outdir: './out', outdirKye: './typo' }");
138
+ const loaded = await loadParaglideConfig({ projectDir });
139
+ expect(loaded?.config.outdir).toBe("./out");
140
+ expect(consola.warn).toHaveBeenCalledWith(expect.stringContaining('"outdirKye"'));
141
+ });
142
+ test("treats a legacy project key as an unknown option", async () => {
143
+ const projectDir = await createProjectDirectory();
144
+ await writeFile(path.join(projectDir, "paraglide.config.ts"), "export default { project: './elsewhere.inlang', outdir: './out' }");
145
+ const loaded = await loadParaglideConfig({ projectDir });
146
+ expect(loaded?.config.outdir).toBe("./out");
147
+ expect(consola.warn).toHaveBeenCalledWith(expect.stringContaining('"project"'));
148
+ });
149
+ test("accepts cleanOutdir silently (it is ignored)", async () => {
150
+ const projectDir = await createProjectDirectory();
151
+ await writeFile(path.join(projectDir, "paraglide.config.ts"), "export default { outdir: './out', cleanOutdir: true }");
152
+ const loaded = await loadParaglideConfig({ projectDir });
153
+ expect(loaded?.config.cleanOutdir).toBe(true);
154
+ for (const call of vi.mocked(consola.warn).mock.calls) {
155
+ expect(String(call[0])).not.toContain("cleanOutdir");
156
+ }
157
+ });
158
+ test("respects a silent logger for warnings", async () => {
159
+ const projectDir = await createProjectDirectory();
160
+ await writeFile(path.join(projectDir, "paraglide.config.ts"), "export default { outdir: './out', cleanOutdir: true, typoKey: 1 }");
161
+ const loaded = await loadParaglideConfig({
162
+ projectDir,
163
+ logger: new Logger({ silent: true, prefix: true }),
164
+ });
165
+ expect(loaded?.config.outdir).toBe("./out");
166
+ expect(consola.warn).not.toHaveBeenCalled();
167
+ });
168
+ test("caches results until the cache is cleared", async () => {
169
+ const projectDir = await createProjectDirectory();
170
+ const configPath = path.join(projectDir, "paraglide.config.ts");
171
+ await writeFile(configPath, "export default { outdir: './first' }");
172
+ const first = await loadParaglideConfig({ projectDir });
173
+ const second = await loadParaglideConfig({ projectDir });
174
+ expect(second).toBe(first);
175
+ await writeFile(configPath, "export default { outdir: './second' }");
176
+ const stale = await loadParaglideConfig({ projectDir });
177
+ expect(stale?.config.outdir).toBe("./first");
178
+ clearParaglideConfigCache();
179
+ const fresh = await loadParaglideConfig({ projectDir });
180
+ expect(fresh?.config.outdir).toBe("./second");
181
+ });
182
+ // Reloading must pick up new content for every config file format —
183
+ // async module imports would keep serving stale content for .mjs/.cjs.
184
+ test("reloading picks up changed content for every file format", async () => {
185
+ for (const fileName of CONFIG_FILE_NAMES) {
186
+ const projectDir = await createProjectDirectory();
187
+ const configPath = path.join(projectDir, fileName);
188
+ const write = (outdir) => writeFile(configPath, fileName.endsWith(".cjs")
189
+ ? `module.exports = { outdir: './${outdir}' }`
190
+ : `export default { outdir: './${outdir}' }`);
191
+ await write("first");
192
+ const first = await loadParaglideConfig({ projectDir });
193
+ expect(first?.config.outdir).toBe("./first");
194
+ clearParaglideConfigCache();
195
+ await write("second");
196
+ const second = await loadParaglideConfig({ projectDir });
197
+ expect(second?.config.outdir).toBe("./second");
198
+ }
199
+ });
200
+ });
201
+ //# sourceMappingURL=load-paraglide-config.test.js.map