@sebbro/paraglide-lite 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,18 @@
1
+ import type { ParaglideSettings } from "../messages/ast.js";
2
+ import { type CompiledBundleWithMessages } from "./compile-bundle.js";
3
+ /**
4
+ * Hand-rolled `.d.ts` for message modules (no TypeScript compiler).
5
+ *
6
+ * Covers:
7
+ * - `messages.js`
8
+ * - `messages/_index.js`
9
+ * - locale-modules: `messages/{locale}.js`
10
+ * - message-modules: `messages/{id}.js`
11
+ *
12
+ * Runtime / server / registry declarations still go through `emitTsDeclarations`.
13
+ */
14
+ export declare function emitMessageDeclarations(args: {
15
+ compiledBundles: CompiledBundleWithMessages[];
16
+ settings: Pick<ParaglideSettings, "locales" | "baseLocale">;
17
+ outputStructure: "locale-modules" | "message-modules";
18
+ }): Record<string, string>;
@@ -0,0 +1,166 @@
1
+ import { escapeForDoubleQuoteString } from "../services/codegen/escape.js";
2
+ import { buildMarkupSchemaType, bundleHasMarkup, toBundleInputTypeAliasName, } from "./compile-bundle.js";
3
+ import { inputsType } from "./jsdoc-types.js";
4
+ import { toSafeModuleId } from "./safe-module-id.js";
5
+ /**
6
+ * Hand-rolled `.d.ts` for message modules (no TypeScript compiler).
7
+ *
8
+ * Covers:
9
+ * - `messages.js`
10
+ * - `messages/_index.js`
11
+ * - locale-modules: `messages/{locale}.js`
12
+ * - message-modules: `messages/{id}.js`
13
+ *
14
+ * Runtime / server / registry declarations still go through `emitTsDeclarations`.
15
+ */
16
+ export function emitMessageDeclarations(args) {
17
+ const { compiledBundles, settings, outputStructure } = args;
18
+ const locales = settings.locales;
19
+ const localesUnion = locales.length === 0
20
+ ? "never"
21
+ : locales.map((locale) => `"${escapeForDoubleQuoteString(locale)}"`).join(" | ");
22
+ const optionsType = `{ locale?: ${localesUnion} }`;
23
+ const declarations = {
24
+ "messages.d.ts": [
25
+ `export * from "./messages/_index.js";`,
26
+ `export * as m from "./messages/_index.js";`,
27
+ ``,
28
+ ].join("\n"),
29
+ };
30
+ if (outputStructure === "message-modules") {
31
+ Object.assign(declarations, emitMessageModulesDeclarations(compiledBundles, optionsType));
32
+ }
33
+ else {
34
+ Object.assign(declarations, emitLocaleModulesDeclarations(compiledBundles, locales, optionsType));
35
+ }
36
+ return declarations;
37
+ }
38
+ function emitMessageModulesDeclarations(compiledBundles, optionsType) {
39
+ const declarations = {};
40
+ const indexExports = [];
41
+ for (const compiled of compiledBundles) {
42
+ const safeModuleId = toSafeModuleId(compiled.bundle.node.id);
43
+ const filename = `messages/${safeModuleId}.d.ts`;
44
+ declarations[filename] = emitBundleDispatcherDts(compiled, optionsType);
45
+ indexExports.push(`export * from "./${safeModuleId}.js";`);
46
+ }
47
+ declarations["messages/_index.d.ts"] = [
48
+ ...indexExports,
49
+ `export type LocalizedString = import("../runtime.js").LocalizedString;`,
50
+ ``,
51
+ ].join("\n");
52
+ return declarations;
53
+ }
54
+ function emitLocaleModulesDeclarations(compiledBundles, locales, optionsType) {
55
+ const declarations = {};
56
+ // Shared index: all message dispatchers
57
+ const indexParts = [];
58
+ const inputTypeAliases = new Map();
59
+ for (const compiled of compiledBundles) {
60
+ const { inputTypeAliasName, inputTypeDefinition } = bundleTypeParts(compiled);
61
+ inputTypeAliases.set(inputTypeAliasName, inputTypeDefinition);
62
+ indexParts.push(emitBundleDispatcherDtsBody(compiled, optionsType, {
63
+ // Index file declares aliases once at the bottom via export type
64
+ emitInputTypeAlias: false,
65
+ emitLocalizedStringAlias: false,
66
+ }));
67
+ }
68
+ const typeAliases = [
69
+ `export type LocalizedString = import("../runtime.js").LocalizedString;`,
70
+ ...[...inputTypeAliases.entries()].map(([name, def]) => `export type ${name} = ${def};`),
71
+ ];
72
+ declarations["messages/_index.d.ts"] = [...indexParts, ...typeAliases, ``].join("\n");
73
+ // Per-locale implementation modules
74
+ for (const locale of locales) {
75
+ const safeLocale = toSafeModuleId(locale);
76
+ const lines = [];
77
+ const localeInputAliases = new Map();
78
+ for (const compiled of compiledBundles) {
79
+ const { safeModuleId, inputTypeAliasName, inputTypeDefinition, hasMarkup } = bundleTypeParts(compiled);
80
+ localeInputAliases.set(inputTypeAliasName, inputTypeDefinition);
81
+ // Match JSDoc on locale message functions: inputs always present in the type.
82
+ if (hasMarkup) {
83
+ lines.push(`export const ${safeModuleId}: ((inputs: ${inputTypeAliasName}) => LocalizedString) & { parts: (inputs: ${inputTypeAliasName}) => import("../runtime.js").MessagePart[] };`);
84
+ }
85
+ else {
86
+ lines.push(`export const ${safeModuleId}: (inputs: ${inputTypeAliasName}) => LocalizedString;`);
87
+ }
88
+ }
89
+ declarations[`messages/${safeLocale}.d.ts`] = [
90
+ `export type LocalizedString = import("../runtime.js").LocalizedString;`,
91
+ ...[...localeInputAliases.entries()].map(([name, def]) => `export type ${name} = ${def};`),
92
+ ...lines,
93
+ ``,
94
+ ].join("\n");
95
+ }
96
+ return declarations;
97
+ }
98
+ function nestedBundle(compiled) {
99
+ // compileBundle stores the full BundleNested on `.node` even though the
100
+ // public Compiled<> type is typed as the shallow Bundle shape.
101
+ return compiled.bundle.node;
102
+ }
103
+ function bundleTypeParts(compiled) {
104
+ const bundle = nestedBundle(compiled);
105
+ const bundleId = bundle.id;
106
+ const safeModuleId = toSafeModuleId(bundleId);
107
+ const isSafeId = safeModuleId === bundleId;
108
+ const inputs = bundle.declarations?.filter((decl) => decl.type === "input-variable") ??
109
+ [];
110
+ const inputTypeAliasName = compiled.inputTypeAliasName ?? toBundleInputTypeAliasName(safeModuleId);
111
+ const inputTypeDefinition = inputsType(inputs, compiled.matchTypes);
112
+ const hasMarkup = bundleHasMarkup(bundle);
113
+ const markupSchemaType = buildMarkupSchemaType(bundle, compiled.matchTypes);
114
+ const hasInputs = inputs.length > 0;
115
+ return {
116
+ bundleId,
117
+ safeModuleId,
118
+ isSafeId,
119
+ inputs,
120
+ inputTypeAliasName,
121
+ inputTypeDefinition,
122
+ hasMarkup,
123
+ markupSchemaType,
124
+ hasInputs,
125
+ };
126
+ }
127
+ /**
128
+ * Full `.d.ts` file for a single message-module dispatcher.
129
+ */
130
+ function emitBundleDispatcherDts(compiled, optionsType) {
131
+ const body = emitBundleDispatcherDtsBody(compiled, optionsType, {
132
+ emitInputTypeAlias: true,
133
+ emitLocalizedStringAlias: true,
134
+ });
135
+ return body + (body.endsWith("\n") ? "" : "\n");
136
+ }
137
+ function emitBundleDispatcherDtsBody(compiled, optionsType, opts) {
138
+ const { bundleId, safeModuleId, isSafeId, inputTypeAliasName, inputTypeDefinition, hasMarkup, markupSchemaType, hasInputs, } = bundleTypeParts(compiled);
139
+ const inputsParam = hasInputs
140
+ ? `inputs: ${inputTypeAliasName}`
141
+ : `inputs?: ${inputTypeAliasName}`;
142
+ const callSignature = `(${inputsParam}, options?: ${optionsType}) => LocalizedString`;
143
+ const partsSignature = `(${inputsParam}, options?: ${optionsType}) => import("../runtime.js").MessagePart[]`;
144
+ const metadataType = `import("../runtime.js").MessageMetadata<${inputTypeAliasName}, ${optionsType}, ${markupSchemaType}>`;
145
+ const valueType = hasMarkup
146
+ ? `(${callSignature}) & { parts: ${partsSignature} } & ${metadataType}`
147
+ : `(${callSignature}) & ${metadataType}`;
148
+ const lines = [];
149
+ if (opts.emitLocalizedStringAlias) {
150
+ lines.push(`export type LocalizedString = import("../runtime.js").LocalizedString;`);
151
+ }
152
+ if (opts.emitInputTypeAlias) {
153
+ lines.push(`export type ${inputTypeAliasName} = ${inputTypeDefinition};`);
154
+ }
155
+ if (isSafeId) {
156
+ lines.push(`export const ${safeModuleId}: ${valueType};`);
157
+ }
158
+ else {
159
+ // Match TypeScript 7: declare const + quoted export alias
160
+ lines.push(`declare const ${safeModuleId}: ${valueType};`);
161
+ lines.push(`export { ${safeModuleId} as "${escapeForDoubleQuoteString(bundleId)}" };`);
162
+ }
163
+ lines.push(``);
164
+ return lines.join("\n");
165
+ }
166
+ //# sourceMappingURL=emit-message-dts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emit-message-dts.js","sourceRoot":"","sources":["../../src/compiler/emit-message-dts.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BundleNested, ParaglideSettings } from \"../messages/ast.js\";\nimport { escapeForDoubleQuoteString } from \"../services/codegen/escape.js\";\nimport {\n\tbuildMarkupSchemaType,\n\tbundleHasMarkup,\n\ttoBundleInputTypeAliasName,\n\ttype CompiledBundleWithMessages,\n} from \"./compile-bundle.js\";\nimport { inputsType } from \"./jsdoc-types.js\";\nimport { toSafeModuleId } from \"./safe-module-id.js\";\n\n/**\n * Hand-rolled `.d.ts` for message modules (no TypeScript compiler).\n *\n * Covers:\n * - `messages.js`\n * - `messages/_index.js`\n * - locale-modules: `messages/{locale}.js`\n * - message-modules: `messages/{id}.js`\n *\n * Runtime / server / registry declarations still go through `emitTsDeclarations`.\n */\nexport function emitMessageDeclarations(args: {\n\tcompiledBundles: CompiledBundleWithMessages[];\n\tsettings: Pick<ParaglideSettings, \"locales\" | \"baseLocale\">;\n\toutputStructure: \"locale-modules\" | \"message-modules\";\n}): Record<string, string> {\n\tconst { compiledBundles, settings, outputStructure } = args;\n\tconst locales = settings.locales;\n\tconst localesUnion =\n\t\tlocales.length === 0\n\t\t\t? \"never\"\n\t\t\t: locales.map((locale) => `\"${escapeForDoubleQuoteString(locale)}\"`).join(\" | \");\n\tconst optionsType = `{ locale?: ${localesUnion} }`;\n\n\tconst declarations: Record<string, string> = {\n\t\t\"messages.d.ts\": [\n\t\t\t`export * from \"./messages/_index.js\";`,\n\t\t\t`export * as m from \"./messages/_index.js\";`,\n\t\t\t``,\n\t\t].join(\"\\n\"),\n\t};\n\n\tif (outputStructure === \"message-modules\") {\n\t\tObject.assign(\n\t\t\tdeclarations,\n\t\t\temitMessageModulesDeclarations(compiledBundles, optionsType)\n\t\t);\n\t} else {\n\t\tObject.assign(\n\t\t\tdeclarations,\n\t\t\temitLocaleModulesDeclarations(compiledBundles, locales, optionsType)\n\t\t);\n\t}\n\n\treturn declarations;\n}\n\nfunction emitMessageModulesDeclarations(\n\tcompiledBundles: CompiledBundleWithMessages[],\n\toptionsType: string\n): Record<string, string> {\n\tconst declarations: Record<string, string> = {};\n\tconst indexExports: string[] = [];\n\n\tfor (const compiled of compiledBundles) {\n\t\tconst safeModuleId = toSafeModuleId(compiled.bundle.node.id);\n\t\tconst filename = `messages/${safeModuleId}.d.ts`;\n\t\tdeclarations[filename] = emitBundleDispatcherDts(compiled, optionsType);\n\t\tindexExports.push(`export * from \"./${safeModuleId}.js\";`);\n\t}\n\n\tdeclarations[\"messages/_index.d.ts\"] = [\n\t\t...indexExports,\n\t\t`export type LocalizedString = import(\"../runtime.js\").LocalizedString;`,\n\t\t``,\n\t].join(\"\\n\");\n\n\treturn declarations;\n}\n\nfunction emitLocaleModulesDeclarations(\n\tcompiledBundles: CompiledBundleWithMessages[],\n\tlocales: string[],\n\toptionsType: string\n): Record<string, string> {\n\tconst declarations: Record<string, string> = {};\n\n\t// Shared index: all message dispatchers\n\tconst indexParts: string[] = [];\n\tconst inputTypeAliases = new Map<string, string>();\n\n\tfor (const compiled of compiledBundles) {\n\t\tconst { inputTypeAliasName, inputTypeDefinition } =\n\t\t\tbundleTypeParts(compiled);\n\t\tinputTypeAliases.set(inputTypeAliasName, inputTypeDefinition);\n\t\tindexParts.push(\n\t\t\temitBundleDispatcherDtsBody(compiled, optionsType, {\n\t\t\t\t// Index file declares aliases once at the bottom via export type\n\t\t\t\temitInputTypeAlias: false,\n\t\t\t\temitLocalizedStringAlias: false,\n\t\t\t})\n\t\t);\n\t}\n\n\tconst typeAliases = [\n\t\t`export type LocalizedString = import(\"../runtime.js\").LocalizedString;`,\n\t\t...[...inputTypeAliases.entries()].map(\n\t\t\t([name, def]) => `export type ${name} = ${def};`\n\t\t),\n\t];\n\n\tdeclarations[\"messages/_index.d.ts\"] = [...indexParts, ...typeAliases, ``].join(\n\t\t\"\\n\"\n\t);\n\n\t// Per-locale implementation modules\n\tfor (const locale of locales) {\n\t\tconst safeLocale = toSafeModuleId(locale);\n\t\tconst lines: string[] = [];\n\t\tconst localeInputAliases = new Map<string, string>();\n\n\t\tfor (const compiled of compiledBundles) {\n\t\t\tconst { safeModuleId, inputTypeAliasName, inputTypeDefinition, hasMarkup } =\n\t\t\t\tbundleTypeParts(compiled);\n\t\t\tlocaleInputAliases.set(inputTypeAliasName, inputTypeDefinition);\n\n\t\t\t// Match JSDoc on locale message functions: inputs always present in the type.\n\t\t\tif (hasMarkup) {\n\t\t\t\tlines.push(\n\t\t\t\t\t`export const ${safeModuleId}: ((inputs: ${inputTypeAliasName}) => LocalizedString) & { parts: (inputs: ${inputTypeAliasName}) => import(\"../runtime.js\").MessagePart[] };`\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tlines.push(\n\t\t\t\t\t`export const ${safeModuleId}: (inputs: ${inputTypeAliasName}) => LocalizedString;`\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tdeclarations[`messages/${safeLocale}.d.ts`] = [\n\t\t\t`export type LocalizedString = import(\"../runtime.js\").LocalizedString;`,\n\t\t\t...[...localeInputAliases.entries()].map(\n\t\t\t\t([name, def]) => `export type ${name} = ${def};`\n\t\t\t),\n\t\t\t...lines,\n\t\t\t``,\n\t\t].join(\"\\n\");\n\t}\n\n\treturn declarations;\n}\n\nfunction nestedBundle(compiled: CompiledBundleWithMessages): BundleNested {\n\t// compileBundle stores the full BundleNested on `.node` even though the\n\t// public Compiled<> type is typed as the shallow Bundle shape.\n\treturn compiled.bundle.node as BundleNested;\n}\n\nfunction bundleTypeParts(compiled: CompiledBundleWithMessages) {\n\tconst bundle = nestedBundle(compiled);\n\tconst bundleId = bundle.id;\n\tconst safeModuleId = toSafeModuleId(bundleId);\n\tconst isSafeId = safeModuleId === bundleId;\n\tconst inputs =\n\t\tbundle.declarations?.filter((decl) => decl.type === \"input-variable\") ??\n\t\t[];\n\tconst inputTypeAliasName =\n\t\tcompiled.inputTypeAliasName ?? toBundleInputTypeAliasName(safeModuleId);\n\tconst inputTypeDefinition = inputsType(inputs, compiled.matchTypes);\n\tconst hasMarkup = bundleHasMarkup(bundle);\n\tconst markupSchemaType = buildMarkupSchemaType(bundle, compiled.matchTypes);\n\tconst hasInputs = inputs.length > 0;\n\n\treturn {\n\t\tbundleId,\n\t\tsafeModuleId,\n\t\tisSafeId,\n\t\tinputs,\n\t\tinputTypeAliasName,\n\t\tinputTypeDefinition,\n\t\thasMarkup,\n\t\tmarkupSchemaType,\n\t\thasInputs,\n\t};\n}\n\n/**\n * Full `.d.ts` file for a single message-module dispatcher.\n */\nfunction emitBundleDispatcherDts(\n\tcompiled: CompiledBundleWithMessages,\n\toptionsType: string\n): string {\n\tconst body = emitBundleDispatcherDtsBody(compiled, optionsType, {\n\t\temitInputTypeAlias: true,\n\t\temitLocalizedStringAlias: true,\n\t});\n\treturn body + (body.endsWith(\"\\n\") ? \"\" : \"\\n\");\n}\n\nfunction emitBundleDispatcherDtsBody(\n\tcompiled: CompiledBundleWithMessages,\n\toptionsType: string,\n\topts: {\n\t\temitInputTypeAlias: boolean;\n\t\temitLocalizedStringAlias: boolean;\n\t}\n): string {\n\tconst {\n\t\tbundleId,\n\t\tsafeModuleId,\n\t\tisSafeId,\n\t\tinputTypeAliasName,\n\t\tinputTypeDefinition,\n\t\thasMarkup,\n\t\tmarkupSchemaType,\n\t\thasInputs,\n\t} = bundleTypeParts(compiled);\n\n\tconst inputsParam = hasInputs\n\t\t? `inputs: ${inputTypeAliasName}`\n\t\t: `inputs?: ${inputTypeAliasName}`;\n\n\tconst callSignature = `(${inputsParam}, options?: ${optionsType}) => LocalizedString`;\n\tconst partsSignature = `(${inputsParam}, options?: ${optionsType}) => import(\"../runtime.js\").MessagePart[]`;\n\tconst metadataType = `import(\"../runtime.js\").MessageMetadata<${inputTypeAliasName}, ${optionsType}, ${markupSchemaType}>`;\n\n\tconst valueType = hasMarkup\n\t\t? `(${callSignature}) & { parts: ${partsSignature} } & ${metadataType}`\n\t\t: `(${callSignature}) & ${metadataType}`;\n\n\tconst lines: string[] = [];\n\n\tif (opts.emitLocalizedStringAlias) {\n\t\tlines.push(\n\t\t\t`export type LocalizedString = import(\"../runtime.js\").LocalizedString;`\n\t\t);\n\t}\n\tif (opts.emitInputTypeAlias) {\n\t\tlines.push(`export type ${inputTypeAliasName} = ${inputTypeDefinition};`);\n\t}\n\n\tif (isSafeId) {\n\t\tlines.push(`export const ${safeModuleId}: ${valueType};`);\n\t} else {\n\t\t// Match TypeScript 7: declare const + quoted export alias\n\t\tlines.push(`declare const ${safeModuleId}: ${valueType};`);\n\t\tlines.push(\n\t\t\t`export { ${safeModuleId} as \"${escapeForDoubleQuoteString(bundleId)}\" };`\n\t\t);\n\t}\n\n\tlines.push(``);\n\treturn lines.join(\"\\n\");\n}\n"]}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,283 @@
1
+ import { beforeEach, expect, test, vi } from "vitest";
2
+ import { createProject as typescriptProject, ts } from "@ts-morph/bootstrap";
3
+ import { createBundleNested } from "./test-helpers.js";
4
+ import { createRequire } from "node:module";
5
+ import path from "node:path";
6
+ import { compileProject } from "./compile-project.js";
7
+ import { emitMessageDeclarations } from "./emit-message-dts.js";
8
+ import { emitTsDeclarations } from "./emit-ts-declarations.js";
9
+ import { compileBundle } from "./compile-bundle.js";
10
+ import * as messageModules from "./output-structure/message-modules.js";
11
+ import * as localeModules from "./output-structure/locale-modules.js";
12
+ // Route infrastructure DTS through TypeScript 7 CLI (same as emit-ts-declarations.test.ts).
13
+ vi.mock("typescript", async () => {
14
+ const actual = await vi.importActual("typescript-go");
15
+ return {
16
+ ...actual,
17
+ createProgram: undefined,
18
+ createCompilerHost: undefined,
19
+ createSourceFile: undefined,
20
+ ScriptTarget: undefined,
21
+ ScriptKind: undefined,
22
+ ModuleKind: undefined,
23
+ ModuleResolutionKind: undefined,
24
+ };
25
+ });
26
+ const tscResolution = vi.hoisted(() => ({ tscJsPath: "" }));
27
+ vi.mock("./resolve-tsc-js-path.js", () => ({
28
+ resolveTscJsPath: () => tscResolution.tscJsPath,
29
+ }));
30
+ const typescriptGoTscJsPath = path.join(path.dirname(createRequire(import.meta.url).resolve("typescript-go/package.json")), "lib", "tsc.js");
31
+ beforeEach(() => {
32
+ tscResolution.tscJsPath = typescriptGoTscJsPath;
33
+ });
34
+ function testBundles() {
35
+ const quotedAliasBundle = createBundleNested({
36
+ id: "greeting.hello",
37
+ messages: [
38
+ {
39
+ locale: "en",
40
+ variants: [{ pattern: [{ type: "text", value: "Hello" }] }],
41
+ },
42
+ {
43
+ locale: "de",
44
+ variants: [{ pattern: [{ type: "text", value: "Hallo" }] }],
45
+ },
46
+ ],
47
+ });
48
+ const parameterizedBundle = createBundleNested({
49
+ id: "balance",
50
+ declarations: [{ type: "input-variable", name: "amount" }],
51
+ messages: [
52
+ {
53
+ locale: "en",
54
+ variants: [
55
+ {
56
+ pattern: [
57
+ { type: "text", value: "You have " },
58
+ {
59
+ type: "expression",
60
+ arg: { type: "variable-reference", name: "amount" },
61
+ },
62
+ { type: "text", value: " coins." },
63
+ ],
64
+ },
65
+ ],
66
+ },
67
+ {
68
+ locale: "de",
69
+ variants: [
70
+ {
71
+ pattern: [
72
+ { type: "text", value: "Du hast " },
73
+ {
74
+ type: "expression",
75
+ arg: { type: "variable-reference", name: "amount" },
76
+ },
77
+ { type: "text", value: " Münzen." },
78
+ ],
79
+ },
80
+ ],
81
+ },
82
+ ],
83
+ });
84
+ const plainBundle = createBundleNested({
85
+ id: "plain",
86
+ messages: [
87
+ {
88
+ locale: "en",
89
+ variants: [{ pattern: [{ type: "text", value: "Static" }] }],
90
+ },
91
+ ],
92
+ });
93
+ return {
94
+ bundles: [quotedAliasBundle, parameterizedBundle, plainBundle],
95
+ settings: { locales: ["en", "de"], baseLocale: "en" },
96
+ };
97
+ }
98
+ async function typecheckOutput(output, consumer) {
99
+ const tsProject = await typescriptProject({
100
+ useInMemoryFileSystem: true,
101
+ compilerOptions: {
102
+ module: ts.ModuleKind.Node16,
103
+ moduleResolution: ts.ModuleResolutionKind.Node16,
104
+ strict: true,
105
+ },
106
+ });
107
+ for (const [fileName, code] of Object.entries(output)) {
108
+ if (fileName.endsWith(".d.ts")) {
109
+ tsProject.createSourceFile(fileName, code);
110
+ }
111
+ }
112
+ tsProject.createSourceFile("test.ts", consumer);
113
+ const program = tsProject.createProgram();
114
+ const diagnostics = ts.getPreEmitDiagnostics(program).filter((d) => {
115
+ const text = d.messageText.toString();
116
+ return (!text.includes("Cannot find name 'URLPattern'") &&
117
+ !text.includes("Type 'string' is not assignable to type"));
118
+ });
119
+ return diagnostics;
120
+ }
121
+ for (const outputStructure of ["message-modules", "locale-modules"]) {
122
+ test(`hand-rolled message DTS typechecks (${outputStructure}) via compileProject + TS7 infra`, async () => {
123
+ const { bundles, settings } = testBundles();
124
+ const output = await compileProject({
125
+ bundles,
126
+ settings,
127
+ compilerOptions: {
128
+ emitTsDeclarations: true,
129
+ outputStructure,
130
+ emitGitIgnore: false,
131
+ emitPrettierIgnore: false,
132
+ emitReadme: false,
133
+ },
134
+ });
135
+ expect(output["messages.d.ts"]).toBeDefined();
136
+ expect(output["messages/_index.d.ts"]).toBeDefined();
137
+ expect(output["runtime.d.ts"]).toBeDefined();
138
+ if (outputStructure === "message-modules") {
139
+ expect(output["messages/balance.d.ts"]).toContain("BalanceInputs");
140
+ expect(output["messages/greeting_hello.d.ts"]).toContain(`as "greeting.hello"`);
141
+ }
142
+ else {
143
+ expect(output["messages/en.d.ts"]).toContain("export const balance");
144
+ expect(output["messages/_index.d.ts"]).toContain(`as "greeting.hello"`);
145
+ }
146
+ const diagnostics = await typecheckOutput(output, `
147
+ import { "greeting.hello" as greetingHello, balance, plain } from "./messages.js";
148
+
149
+ greetingHello() satisfies string;
150
+ plain() satisfies string;
151
+ balance({ amount: 5 }) satisfies string;
152
+ // @ts-expect-error amount is required
153
+ balance({});
154
+ `);
155
+ for (const diagnostic of diagnostics) {
156
+ console.error(diagnostic.messageText, diagnostic.file?.fileName);
157
+ }
158
+ expect(diagnostics.length).toEqual(0);
159
+ });
160
+ }
161
+ test("hand-rolled message DTS matches TS7 export surface (message-modules)", async () => {
162
+ const { bundles, settings } = testBundles();
163
+ const fallbackMap = Object.fromEntries(settings.locales.map((l) => [l, l === settings.baseLocale ? undefined : settings.baseLocale]));
164
+ // Fix fallback: base has undefined, others fall back to base
165
+ for (const locale of settings.locales) {
166
+ fallbackMap[locale] =
167
+ locale === settings.baseLocale ? undefined : settings.baseLocale;
168
+ }
169
+ const compiledBundles = bundles.map((bundle) => compileBundle({
170
+ bundle,
171
+ fallbackMap,
172
+ messageReferenceExpression: messageModules.messageReferenceExpression,
173
+ settings,
174
+ }));
175
+ const jsOutput = {
176
+ "messages.js": [
177
+ "export * from './messages/_index.js'",
178
+ "export * as m from './messages/_index.js'",
179
+ ].join("\n"),
180
+ ...messageModules.generateOutput(compiledBundles, settings, fallbackMap, false),
181
+ };
182
+ const handRolled = emitMessageDeclarations({
183
+ compiledBundles,
184
+ settings,
185
+ outputStructure: "message-modules",
186
+ });
187
+ const ts7 = await emitTsDeclarations(jsOutput);
188
+ // Every message .d.ts path hand-rolled must also exist from tsc
189
+ for (const fileName of Object.keys(handRolled)) {
190
+ expect(ts7[fileName], `TS7 missing ${fileName}`).toBeDefined();
191
+ }
192
+ // Key export shapes
193
+ expect(handRolled["messages/balance.d.ts"]).toMatch(/export const balance:/);
194
+ expect(handRolled["messages/balance.d.ts"]).toContain("amount:");
195
+ expect(handRolled["messages/greeting_hello.d.ts"]).toContain(`as "greeting.hello"`);
196
+ // TS7 includes JSDoc noise; hand-rolled is compact — both must typecheck
197
+ // the same consumer when paired with the same runtime.d.ts from a full compile.
198
+ const full = await compileProject({
199
+ bundles,
200
+ settings,
201
+ compilerOptions: {
202
+ emitTsDeclarations: true,
203
+ outputStructure: "message-modules",
204
+ emitGitIgnore: false,
205
+ emitPrettierIgnore: false,
206
+ emitReadme: false,
207
+ },
208
+ });
209
+ // Replace hand-rolled message dts with TS7 versions and typecheck both ways
210
+ const withTs7Messages = { ...full, ...pick(ts7, Object.keys(handRolled)) };
211
+ const withHandRolled = { ...full }; // already hand-rolled
212
+ const consumer = `
213
+ import { "greeting.hello" as greetingHello, balance, plain, m } from "./messages.js";
214
+ greetingHello() satisfies string;
215
+ balance({ amount: 1 }) satisfies string;
216
+ plain() satisfies string;
217
+ m.balance({ amount: 2 }) satisfies string;
218
+ `;
219
+ expect((await typecheckOutput(withHandRolled, consumer)).length).toBe(0);
220
+ expect((await typecheckOutput(withTs7Messages, consumer)).length).toBe(0);
221
+ });
222
+ test("hand-rolled message DTS matches TS7 export surface (locale-modules)", async () => {
223
+ const { bundles, settings } = testBundles();
224
+ const fallbackMap = {};
225
+ for (const locale of settings.locales) {
226
+ fallbackMap[locale] =
227
+ locale === settings.baseLocale ? undefined : settings.baseLocale;
228
+ }
229
+ const compiledBundles = bundles.map((bundle) => compileBundle({
230
+ bundle,
231
+ fallbackMap,
232
+ messageReferenceExpression: localeModules.messageReferenceExpression,
233
+ settings,
234
+ }));
235
+ const jsOutput = {
236
+ "messages.js": [
237
+ "export * from './messages/_index.js'",
238
+ "export * as m from './messages/_index.js'",
239
+ ].join("\n"),
240
+ ...localeModules.generateOutput(compiledBundles, settings, fallbackMap, false),
241
+ };
242
+ const handRolled = emitMessageDeclarations({
243
+ compiledBundles,
244
+ settings,
245
+ outputStructure: "locale-modules",
246
+ });
247
+ const ts7 = await emitTsDeclarations(jsOutput);
248
+ for (const fileName of Object.keys(handRolled)) {
249
+ expect(ts7[fileName], `TS7 missing ${fileName}`).toBeDefined();
250
+ }
251
+ expect(handRolled["messages/en.d.ts"]).toContain("export const balance");
252
+ expect(handRolled["messages/de.d.ts"]).toContain("export const plain");
253
+ expect(handRolled["messages/_index.d.ts"]).toContain("export const balance");
254
+ const full = await compileProject({
255
+ bundles,
256
+ settings,
257
+ compilerOptions: {
258
+ emitTsDeclarations: true,
259
+ outputStructure: "locale-modules",
260
+ emitGitIgnore: false,
261
+ emitPrettierIgnore: false,
262
+ emitReadme: false,
263
+ },
264
+ });
265
+ const consumer = `
266
+ import { "greeting.hello" as greetingHello, balance, plain } from "./messages.js";
267
+ greetingHello() satisfies string;
268
+ balance({ amount: 1 }) satisfies string;
269
+ plain() satisfies string;
270
+ `;
271
+ expect((await typecheckOutput(full, consumer)).length).toBe(0);
272
+ const withTs7Messages = { ...full, ...pick(ts7, Object.keys(handRolled)) };
273
+ expect((await typecheckOutput(withTs7Messages, consumer)).length).toBe(0);
274
+ });
275
+ function pick(obj, keys) {
276
+ const out = {};
277
+ for (const key of keys) {
278
+ if (obj[key] !== undefined)
279
+ out[key] = obj[key];
280
+ }
281
+ return out;
282
+ }
283
+ //# sourceMappingURL=emit-message-dts.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emit-message-dts.test.js","sourceRoot":"","sources":["../../src/compiler/emit-message-dts.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { beforeEach, expect, test, vi } from \"vitest\";\nimport { createProject as typescriptProject, ts } from \"@ts-morph/bootstrap\";\nimport type { BundleNested } from \"../messages/ast.js\";\nimport { createBundleNested } from \"./test-helpers.js\";\nimport { createRequire } from \"node:module\";\nimport path from \"node:path\";\nimport { compileProject } from \"./compile-project.js\";\nimport { emitMessageDeclarations } from \"./emit-message-dts.js\";\nimport { emitTsDeclarations } from \"./emit-ts-declarations.js\";\nimport { compileBundle } from \"./compile-bundle.js\";\nimport * as messageModules from \"./output-structure/message-modules.js\";\nimport * as localeModules from \"./output-structure/locale-modules.js\";\n\n// Route infrastructure DTS through TypeScript 7 CLI (same as emit-ts-declarations.test.ts).\nvi.mock(\"typescript\", async () => {\n\tconst actual =\n\t\tawait vi.importActual<typeof import(\"typescript-go\")>(\"typescript-go\");\n\treturn {\n\t\t...actual,\n\t\tcreateProgram: undefined,\n\t\tcreateCompilerHost: undefined,\n\t\tcreateSourceFile: undefined,\n\t\tScriptTarget: undefined,\n\t\tScriptKind: undefined,\n\t\tModuleKind: undefined,\n\t\tModuleResolutionKind: undefined,\n\t};\n});\n\nconst tscResolution = vi.hoisted(() => ({ tscJsPath: \"\" }));\n\nvi.mock(\"./resolve-tsc-js-path.js\", () => ({\n\tresolveTscJsPath: () => tscResolution.tscJsPath,\n}));\n\nconst typescriptGoTscJsPath = path.join(\n\tpath.dirname(\n\t\tcreateRequire(import.meta.url).resolve(\"typescript-go/package.json\")\n\t),\n\t\"lib\",\n\t\"tsc.js\"\n);\n\nbeforeEach(() => {\n\ttscResolution.tscJsPath = typescriptGoTscJsPath;\n});\n\nfunction testBundles(): {\n\tbundles: BundleNested[];\n\tsettings: { locales: string[]; baseLocale: string };\n} {\n\tconst quotedAliasBundle: BundleNested = createBundleNested({\n\t\tid: \"greeting.hello\",\n\t\tmessages: [\n\t\t\t{\n\t\t\t\tlocale: \"en\",\n\t\t\t\tvariants: [{ pattern: [{ type: \"text\", value: \"Hello\" }] }],\n\t\t\t},\n\t\t\t{\n\t\t\t\tlocale: \"de\",\n\t\t\t\tvariants: [{ pattern: [{ type: \"text\", value: \"Hallo\" }] }],\n\t\t\t},\n\t\t],\n\t});\n\n\tconst parameterizedBundle: BundleNested = createBundleNested({\n\t\tid: \"balance\",\n\t\tdeclarations: [{ type: \"input-variable\", name: \"amount\" }],\n\t\tmessages: [\n\t\t\t{\n\t\t\t\tlocale: \"en\",\n\t\t\t\tvariants: [\n\t\t\t\t\t{\n\t\t\t\t\t\tpattern: [\n\t\t\t\t\t\t\t{ type: \"text\", value: \"You have \" },\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttype: \"expression\",\n\t\t\t\t\t\t\t\targ: { type: \"variable-reference\", name: \"amount\" },\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{ type: \"text\", value: \" coins.\" },\n\t\t\t\t\t\t],\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t\t{\n\t\t\t\tlocale: \"de\",\n\t\t\t\tvariants: [\n\t\t\t\t\t{\n\t\t\t\t\t\tpattern: [\n\t\t\t\t\t\t\t{ type: \"text\", value: \"Du hast \" },\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttype: \"expression\",\n\t\t\t\t\t\t\t\targ: { type: \"variable-reference\", name: \"amount\" },\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{ type: \"text\", value: \" Münzen.\" },\n\t\t\t\t\t\t],\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t],\n\t});\n\n\tconst plainBundle: BundleNested = createBundleNested({\n\t\tid: \"plain\",\n\t\tmessages: [\n\t\t\t{\n\t\t\t\tlocale: \"en\",\n\t\t\t\tvariants: [{ pattern: [{ type: \"text\", value: \"Static\" }] }],\n\t\t\t},\n\t\t],\n\t});\n\n\treturn {\n\t\tbundles: [quotedAliasBundle, parameterizedBundle, plainBundle],\n\t\tsettings: { locales: [\"en\", \"de\"], baseLocale: \"en\" },\n\t};\n}\n\nasync function typecheckOutput(output: Record<string, string>, consumer: string) {\n\tconst tsProject = await typescriptProject({\n\t\tuseInMemoryFileSystem: true,\n\t\tcompilerOptions: {\n\t\t\tmodule: ts.ModuleKind.Node16,\n\t\t\tmoduleResolution: ts.ModuleResolutionKind.Node16,\n\t\t\tstrict: true,\n\t\t},\n\t});\n\n\tfor (const [fileName, code] of Object.entries(output)) {\n\t\tif (fileName.endsWith(\".d.ts\")) {\n\t\t\ttsProject.createSourceFile(fileName, code);\n\t\t}\n\t}\n\n\ttsProject.createSourceFile(\"test.ts\", consumer);\n\n\tconst program = tsProject.createProgram();\n\tconst diagnostics = ts.getPreEmitDiagnostics(program).filter((d) => {\n\t\tconst text = d.messageText.toString();\n\t\treturn (\n\t\t\t!text.includes(\"Cannot find name 'URLPattern'\") &&\n\t\t\t!text.includes(\"Type 'string' is not assignable to type\")\n\t\t);\n\t});\n\treturn diagnostics;\n}\n\nfor (const outputStructure of [\"message-modules\", \"locale-modules\"] as const) {\n\ttest(`hand-rolled message DTS typechecks (${outputStructure}) via compileProject + TS7 infra`, async () => {\n\t\tconst { bundles, settings } = testBundles();\n\t\tconst output = await compileProject({\n\t\t\tbundles,\n\t\t\tsettings,\n\t\t\tcompilerOptions: {\n\t\t\t\temitTsDeclarations: true,\n\t\t\t\toutputStructure,\n\t\t\t\temitGitIgnore: false,\n\t\t\t\temitPrettierIgnore: false,\n\t\t\t\temitReadme: false,\n\t\t\t},\n\t\t});\n\n\t\texpect(output[\"messages.d.ts\"]).toBeDefined();\n\t\texpect(output[\"messages/_index.d.ts\"]).toBeDefined();\n\t\texpect(output[\"runtime.d.ts\"]).toBeDefined();\n\n\t\tif (outputStructure === \"message-modules\") {\n\t\t\texpect(output[\"messages/balance.d.ts\"]).toContain(\"BalanceInputs\");\n\t\t\texpect(output[\"messages/greeting_hello.d.ts\"]).toContain(\n\t\t\t\t`as \"greeting.hello\"`\n\t\t\t);\n\t\t} else {\n\t\t\texpect(output[\"messages/en.d.ts\"]).toContain(\"export const balance\");\n\t\t\texpect(output[\"messages/_index.d.ts\"]).toContain(\n\t\t\t\t`as \"greeting.hello\"`\n\t\t\t);\n\t\t}\n\n\t\tconst diagnostics = await typecheckOutput(\n\t\t\toutput,\n\t\t\t`\n\t\t\t\timport { \"greeting.hello\" as greetingHello, balance, plain } from \"./messages.js\";\n\n\t\t\t\tgreetingHello() satisfies string;\n\t\t\t\tplain() satisfies string;\n\t\t\t\tbalance({ amount: 5 }) satisfies string;\n\t\t\t\t// @ts-expect-error amount is required\n\t\t\t\tbalance({});\n\t\t\t`\n\t\t);\n\n\t\tfor (const diagnostic of diagnostics) {\n\t\t\tconsole.error(diagnostic.messageText, diagnostic.file?.fileName);\n\t\t}\n\t\texpect(diagnostics.length).toEqual(0);\n\t});\n}\n\ntest(\"hand-rolled message DTS matches TS7 export surface (message-modules)\", async () => {\n\tconst { bundles, settings } = testBundles();\n\tconst fallbackMap = Object.fromEntries(\n\t\tsettings.locales.map((l) => [l, l === settings.baseLocale ? undefined : settings.baseLocale])\n\t) as Record<string, string | undefined>;\n\t// Fix fallback: base has undefined, others fall back to base\n\tfor (const locale of settings.locales) {\n\t\tfallbackMap[locale] =\n\t\t\tlocale === settings.baseLocale ? undefined : settings.baseLocale;\n\t}\n\n\tconst compiledBundles = bundles.map((bundle) =>\n\t\tcompileBundle({\n\t\t\tbundle,\n\t\t\tfallbackMap,\n\t\t\tmessageReferenceExpression: messageModules.messageReferenceExpression,\n\t\t\tsettings,\n\t\t})\n\t);\n\n\tconst jsOutput = {\n\t\t\"messages.js\": [\n\t\t\t\"export * from './messages/_index.js'\",\n\t\t\t\"export * as m from './messages/_index.js'\",\n\t\t].join(\"\\n\"),\n\t\t...messageModules.generateOutput(\n\t\t\tcompiledBundles,\n\t\t\tsettings,\n\t\t\tfallbackMap,\n\t\t\tfalse\n\t\t),\n\t};\n\n\tconst handRolled = emitMessageDeclarations({\n\t\tcompiledBundles,\n\t\tsettings,\n\t\toutputStructure: \"message-modules\",\n\t});\n\n\tconst ts7 = await emitTsDeclarations(jsOutput);\n\n\t// Every message .d.ts path hand-rolled must also exist from tsc\n\tfor (const fileName of Object.keys(handRolled)) {\n\t\texpect(ts7[fileName], `TS7 missing ${fileName}`).toBeDefined();\n\t}\n\n\t// Key export shapes\n\texpect(handRolled[\"messages/balance.d.ts\"]).toMatch(/export const balance:/);\n\texpect(handRolled[\"messages/balance.d.ts\"]).toContain(\"amount:\");\n\texpect(handRolled[\"messages/greeting_hello.d.ts\"]).toContain(\n\t\t`as \"greeting.hello\"`\n\t);\n\n\t// TS7 includes JSDoc noise; hand-rolled is compact — both must typecheck\n\t// the same consumer when paired with the same runtime.d.ts from a full compile.\n\tconst full = await compileProject({\n\t\tbundles,\n\t\tsettings,\n\t\tcompilerOptions: {\n\t\t\temitTsDeclarations: true,\n\t\t\toutputStructure: \"message-modules\",\n\t\t\temitGitIgnore: false,\n\t\t\temitPrettierIgnore: false,\n\t\t\temitReadme: false,\n\t\t},\n\t});\n\n\t// Replace hand-rolled message dts with TS7 versions and typecheck both ways\n\tconst withTs7Messages = { ...full, ...pick(ts7, Object.keys(handRolled)) };\n\tconst withHandRolled = { ...full }; // already hand-rolled\n\n\tconst consumer = `\n\t\timport { \"greeting.hello\" as greetingHello, balance, plain, m } from \"./messages.js\";\n\t\tgreetingHello() satisfies string;\n\t\tbalance({ amount: 1 }) satisfies string;\n\t\tplain() satisfies string;\n\t\tm.balance({ amount: 2 }) satisfies string;\n\t`;\n\n\texpect((await typecheckOutput(withHandRolled, consumer)).length).toBe(0);\n\texpect((await typecheckOutput(withTs7Messages, consumer)).length).toBe(0);\n});\n\ntest(\"hand-rolled message DTS matches TS7 export surface (locale-modules)\", async () => {\n\tconst { bundles, settings } = testBundles();\n\tconst fallbackMap: Record<string, string | undefined> = {};\n\tfor (const locale of settings.locales) {\n\t\tfallbackMap[locale] =\n\t\t\tlocale === settings.baseLocale ? undefined : settings.baseLocale;\n\t}\n\n\tconst compiledBundles = bundles.map((bundle) =>\n\t\tcompileBundle({\n\t\t\tbundle,\n\t\t\tfallbackMap,\n\t\t\tmessageReferenceExpression: localeModules.messageReferenceExpression,\n\t\t\tsettings,\n\t\t})\n\t);\n\n\tconst jsOutput = {\n\t\t\"messages.js\": [\n\t\t\t\"export * from './messages/_index.js'\",\n\t\t\t\"export * as m from './messages/_index.js'\",\n\t\t].join(\"\\n\"),\n\t\t...localeModules.generateOutput(\n\t\t\tcompiledBundles,\n\t\t\tsettings,\n\t\t\tfallbackMap,\n\t\t\tfalse\n\t\t),\n\t};\n\n\tconst handRolled = emitMessageDeclarations({\n\t\tcompiledBundles,\n\t\tsettings,\n\t\toutputStructure: \"locale-modules\",\n\t});\n\tconst ts7 = await emitTsDeclarations(jsOutput);\n\n\tfor (const fileName of Object.keys(handRolled)) {\n\t\texpect(ts7[fileName], `TS7 missing ${fileName}`).toBeDefined();\n\t}\n\n\texpect(handRolled[\"messages/en.d.ts\"]).toContain(\"export const balance\");\n\texpect(handRolled[\"messages/de.d.ts\"]).toContain(\"export const plain\");\n\texpect(handRolled[\"messages/_index.d.ts\"]).toContain(\"export const balance\");\n\n\tconst full = await compileProject({\n\t\tbundles,\n\t\tsettings,\n\t\tcompilerOptions: {\n\t\t\temitTsDeclarations: true,\n\t\t\toutputStructure: \"locale-modules\",\n\t\t\temitGitIgnore: false,\n\t\t\temitPrettierIgnore: false,\n\t\t\temitReadme: false,\n\t\t},\n\t});\n\n\tconst consumer = `\n\t\timport { \"greeting.hello\" as greetingHello, balance, plain } from \"./messages.js\";\n\t\tgreetingHello() satisfies string;\n\t\tbalance({ amount: 1 }) satisfies string;\n\t\tplain() satisfies string;\n\t`;\n\n\texpect((await typecheckOutput(full, consumer)).length).toBe(0);\n\n\tconst withTs7Messages = { ...full, ...pick(ts7, Object.keys(handRolled)) };\n\texpect((await typecheckOutput(withTs7Messages, consumer)).length).toBe(0);\n});\n\nfunction pick<T extends Record<string, string>>(\n\tobj: T,\n\tkeys: string[]\n): Record<string, string> {\n\tconst out: Record<string, string> = {};\n\tfor (const key of keys) {\n\t\tif (obj[key] !== undefined) out[key] = obj[key]!;\n\t}\n\treturn out;\n}\n"]}
@@ -1,9 +1,9 @@
1
1
  export type HostRoutingRuleResolved = {
2
- baseLocale: string;
3
- locales: string[];
2
+ baseLocale: Locale;
3
+ locales: Locale[];
4
4
  };
5
5
  /**
6
- * @typedef {{ baseLocale: string, locales: string[] }} HostRoutingRuleResolved
6
+ * @typedef {{ baseLocale: Locale, locales: Locale[] }} HostRoutingRuleResolved
7
7
  */
8
8
  /**
9
9
  * @param {string} hostname
@@ -1,7 +1,7 @@
1
1
  import { toLocale } from "./check-locale.js";
2
2
  import { baseLocale, locales, routing, } from "./variables.js";
3
3
  /**
4
- * @typedef {{ baseLocale: string, locales: string[] }} HostRoutingRuleResolved
4
+ * @typedef {{ baseLocale: Locale, locales: Locale[] }} HostRoutingRuleResolved
5
5
  */
6
6
  /**
7
7
  * @param {string} hostname
@@ -11,7 +11,7 @@ export function getRoutingRule(hostname) {
11
11
  return (routing[hostname] ??
12
12
  routing["*"] ?? {
13
13
  baseLocale,
14
- locales: /** @type {string[]} */ ([...locales]),
14
+ locales: [...locales],
15
15
  });
16
16
  }
17
17
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"host-routing.js","sourceRoot":"","sources":["../../../src/compiler/runtime/host-routing.js"],"names":[],"mappings":"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","sourcesContent":["import { toLocale } from \"./check-locale.js\";\nimport {\n\tbaseLocale,\n\tlocales,\n\trouting,\n} from \"./variables.js\";\n\n/**\n * @typedef {{ baseLocale: string, locales: string[] }} HostRoutingRuleResolved\n */\n\n/**\n * @param {string} hostname\n * @returns {HostRoutingRuleResolved}\n */\nexport function getRoutingRule(hostname) {\n\treturn (\n\t\trouting[hostname] ??\n\t\trouting[\"*\"] ?? {\n\t\t\tbaseLocale,\n\t\t\tlocales: /** @type {string[]} */ ([...locales]),\n\t\t}\n\t);\n}\n\n/**\n * @param {string[]} segments\n * @param {HostRoutingRuleResolved} rule\n * @returns {Locale | undefined}\n */\nfunction localeFromFirstSegment(segments, rule) {\n\tif (segments.length === 0) {\n\t\treturn undefined;\n\t}\n\tconst canonical = toLocale(segments[0]);\n\tif (canonical && rule.locales.includes(canonical)) {\n\t\treturn canonical;\n\t}\n\treturn undefined;\n}\n\n/**\n * @param {string | URL} url\n * @returns {Locale}\n */\nexport function extractLocaleFromUrlHostRouting(url) {\n\tconst urlObj =\n\t\ttypeof url === \"string\" ? new URL(url, \"http://localhost\") : url;\n\tconst rule = getRoutingRule(urlObj.hostname);\n\tconst segments = urlObj.pathname.split(\"/\").filter(Boolean);\n\treturn localeFromFirstSegment(segments, rule) ?? rule.baseLocale;\n}\n\n/**\n * @param {string | URL} url\n * @param {Locale} locale\n * @returns {URL}\n */\nexport function localizeUrlHostRouting(url, locale) {\n\tconst urlObj =\n\t\ttypeof url === \"string\" ? new URL(url) : new URL(url.href);\n\tconst rule = getRoutingRule(urlObj.hostname);\n\tconst segments = urlObj.pathname.split(\"/\").filter(Boolean);\n\n\tif (localeFromFirstSegment(segments, rule)) {\n\t\tsegments.shift();\n\t}\n\n\tif (locale === rule.baseLocale) {\n\t\turlObj.pathname = \"/\" + segments.join(\"/\");\n\t} else if (segments.length === 0) {\n\t\turlObj.pathname = \"/\" + locale;\n\t} else {\n\t\turlObj.pathname = \"/\" + locale + \"/\" + segments.join(\"/\");\n\t}\n\n\treturn urlObj;\n}\n\n/**\n * @param {string | URL} url\n * @returns {URL}\n */\nexport function deLocalizeUrlHostRouting(url) {\n\tconst urlObj =\n\t\ttypeof url === \"string\" ? new URL(url) : new URL(url.href);\n\tconst rule = getRoutingRule(urlObj.hostname);\n\tconst segments = urlObj.pathname.split(\"/\").filter(Boolean);\n\n\tif (localeFromFirstSegment(segments, rule)) {\n\t\tsegments.shift();\n\t\turlObj.pathname = \"/\" + segments.join(\"/\");\n\t}\n\n\treturn urlObj;\n}\n"]}
1
+ {"version":3,"file":"host-routing.js","sourceRoot":"","sources":["../../../src/compiler/runtime/host-routing.js"],"names":[],"mappings":"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","sourcesContent":["import { toLocale } from \"./check-locale.js\";\nimport {\n\tbaseLocale,\n\tlocales,\n\trouting,\n} from \"./variables.js\";\n\n/**\n * @typedef {{ baseLocale: Locale, locales: Locale[] }} HostRoutingRuleResolved\n */\n\n/**\n * @param {string} hostname\n * @returns {HostRoutingRuleResolved}\n */\nexport function getRoutingRule(hostname) {\n\treturn (\n\t\trouting[hostname] ??\n\t\trouting[\"*\"] ?? {\n\t\t\tbaseLocale,\n\t\t\tlocales: [...locales],\n\t\t}\n\t);\n}\n\n/**\n * @param {string[]} segments\n * @param {HostRoutingRuleResolved} rule\n * @returns {Locale | undefined}\n */\nfunction localeFromFirstSegment(segments, rule) {\n\tif (segments.length === 0) {\n\t\treturn undefined;\n\t}\n\tconst canonical = toLocale(segments[0]);\n\tif (canonical && rule.locales.includes(canonical)) {\n\t\treturn canonical;\n\t}\n\treturn undefined;\n}\n\n/**\n * @param {string | URL} url\n * @returns {Locale}\n */\nexport function extractLocaleFromUrlHostRouting(url) {\n\tconst urlObj =\n\t\ttypeof url === \"string\" ? new URL(url, \"http://localhost\") : url;\n\tconst rule = getRoutingRule(urlObj.hostname);\n\tconst segments = urlObj.pathname.split(\"/\").filter(Boolean);\n\treturn localeFromFirstSegment(segments, rule) ?? rule.baseLocale;\n}\n\n/**\n * @param {string | URL} url\n * @param {Locale} locale\n * @returns {URL}\n */\nexport function localizeUrlHostRouting(url, locale) {\n\tconst urlObj =\n\t\ttypeof url === \"string\" ? new URL(url) : new URL(url.href);\n\tconst rule = getRoutingRule(urlObj.hostname);\n\tconst segments = urlObj.pathname.split(\"/\").filter(Boolean);\n\n\tif (localeFromFirstSegment(segments, rule)) {\n\t\tsegments.shift();\n\t}\n\n\tif (locale === rule.baseLocale) {\n\t\turlObj.pathname = \"/\" + segments.join(\"/\");\n\t} else if (segments.length === 0) {\n\t\turlObj.pathname = \"/\" + locale;\n\t} else {\n\t\turlObj.pathname = \"/\" + locale + \"/\" + segments.join(\"/\");\n\t}\n\n\treturn urlObj;\n}\n\n/**\n * @param {string | URL} url\n * @returns {URL}\n */\nexport function deLocalizeUrlHostRouting(url) {\n\tconst urlObj =\n\t\ttypeof url === \"string\" ? new URL(url) : new URL(url.href);\n\tconst rule = getRoutingRule(urlObj.hostname);\n\tconst segments = urlObj.pathname.split(\"/\").filter(Boolean);\n\n\tif (localeFromFirstSegment(segments, rule)) {\n\t\tsegments.shift();\n\t\turlObj.pathname = \"/\" + segments.join(\"/\");\n\t}\n\n\treturn urlObj;\n}\n"]}
@@ -1,5 +1,7 @@
1
1
  import { test, expect } from "vitest";
2
+ import { createProject as typescriptProject, ts, } from "@ts-morph/bootstrap";
2
3
  import { createParaglide } from "../create-paraglide.js";
4
+ import { defaultCompilerOptions } from "../compiler-options.js";
3
5
  import { createRuntimeFile, normalizeRouting } from "./create-runtime.js";
4
6
  test("normalizeRouting fills * fallback and resolves defaults", () => {
5
7
  const resolved = normalizeRouting({
@@ -125,4 +127,81 @@ test("localizeHref works with host routing under ALS origin", async () => {
125
127
  expect(runtime.localizeHref("/about", { locale: "en" })).toBe("/en/about");
126
128
  expect(runtime.localizeHref("/en/about", { locale: "fi" })).toBe("/about");
127
129
  });
130
+ /**
131
+ * Host routing types must stay narrowed to Locale in generated runtime.js.
132
+ * Runtime tests alone miss this — it is a checkJs type error, not a runtime failure.
133
+ */
134
+ test("generated host-routing runtime typechecks with narrow Locale types", async () => {
135
+ const project = await typescriptProject({
136
+ useInMemoryFileSystem: true,
137
+ compilerOptions: {
138
+ allowJs: true,
139
+ checkJs: true,
140
+ noEmit: true,
141
+ strict: true,
142
+ module: ts.ModuleKind.Node16,
143
+ moduleResolution: ts.ModuleResolutionKind.Node16,
144
+ target: ts.ScriptTarget.ES2022,
145
+ },
146
+ });
147
+ const runtime = createRuntimeFile({
148
+ baseLocale: "en",
149
+ locales: ["en", "sv", "fi"],
150
+ compilerOptions: {
151
+ ...defaultCompilerOptions,
152
+ strategy: ["url", "baseLocale"],
153
+ routing: {
154
+ "fin.example.com": { baseLocale: "fi" },
155
+ "app.example.com": {
156
+ baseLocale: "sv",
157
+ locales: ["sv", "en"],
158
+ },
159
+ },
160
+ },
161
+ }).replace('import * as pathToRegexp from "@sebbro/paraglide-lite/path-to-regexp";', "");
162
+ project.createSourceFile("./runtime.js", runtime);
163
+ project.createSourceFile("./consumer.ts", `
164
+ import {
165
+ generateStaticLocalizedUrls,
166
+ localizeUrl,
167
+ routing,
168
+ } from "./runtime.js";
169
+
170
+ // routing table values must retain Locale, not widen to string
171
+ const rule = routing["app.example.com"]!;
172
+ rule.baseLocale satisfies "en" | "sv" | "fi";
173
+ // Function that promises Locale accepts rule.baseLocale (not string)
174
+ const fallback: "en" | "sv" | "fi" = rule.baseLocale;
175
+
176
+ for (const locale of rule.locales) {
177
+ locale satisfies "en" | "sv" | "fi";
178
+ // localizeUrl requires Locale — must not reject rule.locales members
179
+ localizeUrl("https://app.example.com/shop", { locale });
180
+ }
181
+
182
+ // host-routing path inside generateStaticLocalizedUrls uses rule.locales as Locale[]
183
+ generateStaticLocalizedUrls(["https://fin.example.com/about"]);
184
+
185
+ void fallback;
186
+ `);
187
+ const program = project.createProgram();
188
+ const diagnostics = ts
189
+ .getPreEmitDiagnostics(program)
190
+ .filter((d) => {
191
+ const message = typeof d.messageText === "string"
192
+ ? d.messageText
193
+ : d.messageText.messageText;
194
+ // path-to-regexp import is stripped for the in-memory typecheck
195
+ return !message.includes("path-to-regexp");
196
+ });
197
+ for (const diagnostic of diagnostics) {
198
+ const message = typeof diagnostic.messageText === "string"
199
+ ? diagnostic.messageText
200
+ : diagnostic.messageText.messageText;
201
+ console.error(message, diagnostic.file?.fileName, diagnostic.start !== undefined && diagnostic.file
202
+ ? diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start)
203
+ : undefined);
204
+ }
205
+ expect(diagnostics).toEqual([]);
206
+ });
128
207
  //# sourceMappingURL=host-routing.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"host-routing.test.js","sourceRoot":"","sources":["../../../src/compiler/runtime/host-routing.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE1E,IAAI,CAAC,yDAAyD,EAAE,GAAG,EAAE;IACpE,MAAM,QAAQ,GAAG,gBAAgB,CAAC;QACjC,OAAO,EAAE;YACR,iBAAiB,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE;YACvC,iBAAiB,EAAE;gBAClB,UAAU,EAAE,IAAI;gBAChB,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC;aAC9B;SACD;QACD,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC;QAC1C,UAAU,EAAE,IAAI;KAChB,CAAC,CAAC;IAEH,MAAM,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC;QACxB,iBAAiB,EAAE;YAClB,UAAU,EAAE,IAAI;YAChB,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC;SAC1C;QACD,iBAAiB,EAAE;YAClB,UAAU,EAAE,IAAI;YAChB,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC;SAC9B;QACD,GAAG,EAAE;YACJ,UAAU,EAAE,IAAI;YAChB,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC;SAC1C;KACD,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,0CAA0C,EAAE,GAAG,EAAE;IACrD,MAAM,CAAC,GAAG,EAAE,CACX,gBAAgB,CAAC;QAChB,OAAO,EAAE;YACR,iBAAiB,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE;SACzC;QACD,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC;QACrB,UAAU,EAAE,IAAI;KAChB,CAAC,CACF,CAAC,OAAO,CAAC,2BAA2B,CAAC,CAAC;AACxC,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,0DAA0D,EAAE,GAAG,EAAE;IACrE,MAAM,CAAC,GAAG,EAAE,CACX,iBAAiB,CAAC;QACjB,UAAU,EAAE,IAAI;QAChB,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC;QACrB,eAAe,EAAE;YAChB,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,CAAC;YAC/B,UAAU,EAAE,kBAAkB;YAC9B,YAAY,EAAE,GAAG;YACjB,YAAY,EAAE,EAAE;YAChB,eAAe,EAAE,kBAAkB;YACnC,QAAQ,EAAE,+BAA+B;YACzC,qCAAqC,EAAE,KAAK;YAC5C,wBAAwB,EAAE,KAAK;YAC/B,OAAO,EAAE,EAAE,GAAG,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE;YACtC,WAAW,EAAE;gBACZ;oBACC,OAAO,EAAE,aAAa;oBACtB,SAAS,EAAE;wBACV,CAAC,IAAI,EAAE,aAAa,CAAC;wBACrB,CAAC,IAAI,EAAE,gBAAgB,CAAC;qBACxB;iBACD;aACD;SACD;KACD,CAAC,CACF,CAAC,OAAO,CAAC,6CAA6C,CAAC,CAAC;AAC1D,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,gEAAgE,EAAE,KAAK,IAAI,EAAE;IACjF,MAAM,OAAO,GAAG,MAAM,eAAe,CAAC;QACrC,UAAU,EAAE,IAAI;QAChB,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC;QACjC,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,CAAC;QAC/B,OAAO,EAAE;YACR,iBAAiB,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE;YACvC,iBAAiB,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE;YACvC,GAAG,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE;SACzB;KACD,CAAC,CAAC;IAEH,MAAM,CAAC,OAAO,CAAC,oBAAoB,CAAC,0BAA0B,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5E,MAAM,CAAC,OAAO,CAAC,oBAAoB,CAAC,+BAA+B,CAAC,CAAC,CAAC,IAAI,CACzE,IAAI,CACJ,CAAC;IACF,MAAM,CAAC,OAAO,CAAC,oBAAoB,CAAC,4BAA4B,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC9E,MAAM,CACL,OAAO,CAAC,oBAAoB,CAAC,kCAAkC,CAAC,CAChE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACb,MAAM,CAAC,OAAO,CAAC,oBAAoB,CAAC,0BAA0B,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5E,MAAM,CAAC,OAAO,CAAC,oBAAoB,CAAC,gCAAgC,CAAC,CAAC,CAAC,IAAI,CAC1E,IAAI,CACJ,CAAC;IAEF,MAAM,CACL,OAAO,CAAC,WAAW,CAAC,+BAA+B,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAC3E,CAAC,IAAI,CAAC,kCAAkC,CAAC,CAAC;IAC3C,MAAM,CACL,OAAO,CAAC,WAAW,CAAC,kCAAkC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;SACvE,IAAI,CACN,CAAC,IAAI,CAAC,+BAA+B,CAAC,CAAC;IACxC,MAAM,CACL,OAAO,CAAC,WAAW,CAAC,+BAA+B,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAC3E,CAAC,IAAI,CAAC,+BAA+B,CAAC,CAAC;IAExC,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,kCAAkC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAC1E,+BAA+B,CAC/B,CAAC;IACF,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,+BAA+B,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CACvE,+BAA+B,CAC/B,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,qDAAqD,EAAE,KAAK,IAAI,EAAE;IACtE,MAAM,OAAO,GAAG,MAAM,eAAe,CAAC;QACrC,UAAU,EAAE,IAAI;QAChB,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC;QACpC,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,CAAC;QAC/B,OAAO,EAAE;YACR,iBAAiB,EAAE;gBAClB,UAAU,EAAE,IAAI;gBAChB,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC;aAC9B;SACD;KACD,CAAC,CAAC;IAEH,MAAM,CACL,OAAO,CAAC,oBAAoB,CAAC,oCAAoC,CAAC,CAClE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAChB,wEAAwE;IACxE,MAAM,CAAC,OAAO,CAAC,oBAAoB,CAAC,iCAAiC,CAAC,CAAC,CAAC,IAAI,CAC3E,IAAI,CACJ,CAAC;IACF,MAAM,CACL,OAAO,CAAC,WAAW,CAAC,8BAA8B,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;SACtE,IAAI,CACN,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC;AAC9C,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,uDAAuD,EAAE,KAAK,IAAI,EAAE;IACxE,MAAM,OAAO,GAAG,MAAM,eAAe,CAAC;QACrC,UAAU,EAAE,IAAI;QAChB,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC;QACrB,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,CAAC;QAC/B,OAAO,EAAE;YACR,iBAAiB,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE;SACvC;KACD,CAAC,CAAC;IAEH,OAAO,CAAC,gCAAgC,CAAC;QACxC,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC;YAChB,MAAM,EAAE,IAAI;YACZ,MAAM,EAAE,yBAAyB;SACjC,CAAC;QACF,GAAG,EAAE,GAAG,EAAE,CAAC,SAAS;KACpB,CAAC,CAAC;IAEH,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACtD,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC3E,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AAC5E,CAAC,CAAC,CAAC","sourcesContent":["import { test, expect } from \"vitest\";\nimport { createParaglide } from \"../create-paraglide.js\";\nimport { createRuntimeFile, normalizeRouting } from \"./create-runtime.js\";\n\ntest(\"normalizeRouting fills * fallback and resolves defaults\", () => {\n\tconst resolved = normalizeRouting({\n\t\trouting: {\n\t\t\t\"fin.example.com\": { baseLocale: \"fi\" },\n\t\t\t\"app.example.com\": {\n\t\t\t\tbaseLocale: \"sv\",\n\t\t\t\tlocales: [\"sv\", \"en\", \"pt-PT\"],\n\t\t\t},\n\t\t},\n\t\tlocales: [\"en\", \"fi\", \"sv\", \"pt-PT\", \"de\"],\n\t\tbaseLocale: \"en\",\n\t});\n\n\texpect(resolved).toEqual({\n\t\t\"fin.example.com\": {\n\t\t\tbaseLocale: \"fi\",\n\t\t\tlocales: [\"en\", \"fi\", \"sv\", \"pt-PT\", \"de\"],\n\t\t},\n\t\t\"app.example.com\": {\n\t\t\tbaseLocale: \"sv\",\n\t\t\tlocales: [\"sv\", \"en\", \"pt-PT\"],\n\t\t},\n\t\t\"*\": {\n\t\t\tbaseLocale: \"en\",\n\t\t\tlocales: [\"en\", \"fi\", \"sv\", \"pt-PT\", \"de\"],\n\t\t},\n\t});\n});\n\ntest(\"normalizeRouting rejects unknown locales\", () => {\n\texpect(() =>\n\t\tnormalizeRouting({\n\t\t\trouting: {\n\t\t\t\t\"fin.example.com\": { baseLocale: \"nope\" },\n\t\t\t},\n\t\t\tlocales: [\"en\", \"fi\"],\n\t\t\tbaseLocale: \"en\",\n\t\t})\n\t).toThrow(/Invalid baseLocale \"nope\"/);\n});\n\ntest(\"createRuntimeFile rejects routing + urlPatterns together\", () => {\n\texpect(() =>\n\t\tcreateRuntimeFile({\n\t\t\tbaseLocale: \"en\",\n\t\t\tlocales: [\"en\", \"de\"],\n\t\t\tcompilerOptions: {\n\t\t\t\tstrategy: [\"url\", \"baseLocale\"],\n\t\t\t\tcookieName: \"PARAGLIDE_LOCALE\",\n\t\t\t\tcookieMaxAge: 100,\n\t\t\t\tcookieDomain: \"\",\n\t\t\t\tlocalStorageKey: \"PARAGLIDE_LOCALE\",\n\t\t\t\tisServer: \"typeof window === 'undefined'\",\n\t\t\t\texperimentalMiddlewareLocaleSplitting: false,\n\t\t\t\tdisableAsyncLocalStorage: false,\n\t\t\t\trouting: { \"*\": { baseLocale: \"en\" } },\n\t\t\t\turlPatterns: [\n\t\t\t\t\t{\n\t\t\t\t\t\tpattern: \"/:path(.*)?\",\n\t\t\t\t\t\tlocalized: [\n\t\t\t\t\t\t\t[\"en\", \"/:path(.*)?\"],\n\t\t\t\t\t\t\t[\"de\", \"/de/:path(.*)?\"],\n\t\t\t\t\t\t],\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t})\n\t).toThrow(/Cannot set both \"routing\" and \"urlPatterns\"/);\n});\n\ntest(\"per-host baseLocale: bare path is host default, /en is english\", async () => {\n\tconst runtime = await createParaglide({\n\t\tbaseLocale: \"en\",\n\t\tlocales: [\"en\", \"fi\", \"sv\", \"de\"],\n\t\tstrategy: [\"url\", \"baseLocale\"],\n\t\trouting: {\n\t\t\t\"fin.example.com\": { baseLocale: \"fi\" },\n\t\t\t\"app.example.com\": { baseLocale: \"sv\" },\n\t\t\t\"*\": { baseLocale: \"en\" },\n\t\t},\n\t});\n\n\texpect(runtime.extractLocaleFromUrl(\"https://fin.example.com/\")).toBe(\"fi\");\n\texpect(runtime.extractLocaleFromUrl(\"https://fin.example.com/about\")).toBe(\n\t\t\"fi\"\n\t);\n\texpect(runtime.extractLocaleFromUrl(\"https://fin.example.com/en\")).toBe(\"en\");\n\texpect(\n\t\truntime.extractLocaleFromUrl(\"https://fin.example.com/en/about\")\n\t).toBe(\"en\");\n\texpect(runtime.extractLocaleFromUrl(\"https://app.example.com/\")).toBe(\"sv\");\n\texpect(runtime.extractLocaleFromUrl(\"https://other.example.com/de/x\")).toBe(\n\t\t\"de\"\n\t);\n\n\texpect(\n\t\truntime.localizeUrl(\"https://fin.example.com/about\", { locale: \"en\" }).href\n\t).toBe(\"https://fin.example.com/en/about\");\n\texpect(\n\t\truntime.localizeUrl(\"https://fin.example.com/en/about\", { locale: \"fi\" })\n\t\t\t.href\n\t).toBe(\"https://fin.example.com/about\");\n\texpect(\n\t\truntime.localizeUrl(\"https://fin.example.com/about\", { locale: \"fi\" }).href\n\t).toBe(\"https://fin.example.com/about\");\n\n\texpect(runtime.deLocalizeUrl(\"https://fin.example.com/en/about\").href).toBe(\n\t\t\"https://fin.example.com/about\"\n\t);\n\texpect(runtime.deLocalizeUrl(\"https://fin.example.com/about\").href).toBe(\n\t\t\"https://fin.example.com/about\"\n\t);\n});\n\ntest(\"host locales restrict which prefixes are recognized\", async () => {\n\tconst runtime = await createParaglide({\n\t\tbaseLocale: \"en\",\n\t\tlocales: [\"en\", \"sv\", \"pt-PT\", \"de\"],\n\t\tstrategy: [\"url\", \"baseLocale\"],\n\t\trouting: {\n\t\t\t\"app.example.com\": {\n\t\t\t\tbaseLocale: \"sv\",\n\t\t\t\tlocales: [\"sv\", \"en\", \"pt-PT\"],\n\t\t\t},\n\t\t},\n\t});\n\n\texpect(\n\t\truntime.extractLocaleFromUrl(\"https://app.example.com/pt-PT/shop\")\n\t).toBe(\"pt-PT\");\n\t// de is a project locale but not allowed on this host → treated as path\n\texpect(runtime.extractLocaleFromUrl(\"https://app.example.com/de/shop\")).toBe(\n\t\t\"sv\"\n\t);\n\texpect(\n\t\truntime.localizeUrl(\"https://app.example.com/shop\", { locale: \"pt-PT\" })\n\t\t\t.href\n\t).toBe(\"https://app.example.com/pt-PT/shop\");\n});\n\ntest(\"localizeHref works with host routing under ALS origin\", async () => {\n\tconst runtime = await createParaglide({\n\t\tbaseLocale: \"en\",\n\t\tlocales: [\"en\", \"fi\"],\n\t\tstrategy: [\"url\", \"baseLocale\"],\n\t\trouting: {\n\t\t\t\"fin.example.com\": { baseLocale: \"fi\" },\n\t\t},\n\t});\n\n\truntime.overwriteServerAsyncLocalStorage({\n\t\tgetStore: () => ({\n\t\t\tlocale: \"fi\",\n\t\t\torigin: \"https://fin.example.com\",\n\t\t}),\n\t\trun: () => undefined,\n\t});\n\n\texpect(runtime.localizeHref(\"/about\")).toBe(\"/about\");\n\texpect(runtime.localizeHref(\"/about\", { locale: \"en\" })).toBe(\"/en/about\");\n\texpect(runtime.localizeHref(\"/en/about\", { locale: \"fi\" })).toBe(\"/about\");\n});\n"]}
1
+ {"version":3,"file":"host-routing.test.js","sourceRoot":"","sources":["../../../src/compiler/runtime/host-routing.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EACN,aAAa,IAAI,iBAAiB,EAClC,EAAE,GACF,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE1E,IAAI,CAAC,yDAAyD,EAAE,GAAG,EAAE;IACpE,MAAM,QAAQ,GAAG,gBAAgB,CAAC;QACjC,OAAO,EAAE;YACR,iBAAiB,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE;YACvC,iBAAiB,EAAE;gBAClB,UAAU,EAAE,IAAI;gBAChB,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC;aAC9B;SACD;QACD,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC;QAC1C,UAAU,EAAE,IAAI;KAChB,CAAC,CAAC;IAEH,MAAM,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC;QACxB,iBAAiB,EAAE;YAClB,UAAU,EAAE,IAAI;YAChB,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC;SAC1C;QACD,iBAAiB,EAAE;YAClB,UAAU,EAAE,IAAI;YAChB,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC;SAC9B;QACD,GAAG,EAAE;YACJ,UAAU,EAAE,IAAI;YAChB,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC;SAC1C;KACD,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,0CAA0C,EAAE,GAAG,EAAE;IACrD,MAAM,CAAC,GAAG,EAAE,CACX,gBAAgB,CAAC;QAChB,OAAO,EAAE;YACR,iBAAiB,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE;SACzC;QACD,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC;QACrB,UAAU,EAAE,IAAI;KAChB,CAAC,CACF,CAAC,OAAO,CAAC,2BAA2B,CAAC,CAAC;AACxC,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,0DAA0D,EAAE,GAAG,EAAE;IACrE,MAAM,CAAC,GAAG,EAAE,CACX,iBAAiB,CAAC;QACjB,UAAU,EAAE,IAAI;QAChB,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC;QACrB,eAAe,EAAE;YAChB,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,CAAC;YAC/B,UAAU,EAAE,kBAAkB;YAC9B,YAAY,EAAE,GAAG;YACjB,YAAY,EAAE,EAAE;YAChB,eAAe,EAAE,kBAAkB;YACnC,QAAQ,EAAE,+BAA+B;YACzC,qCAAqC,EAAE,KAAK;YAC5C,wBAAwB,EAAE,KAAK;YAC/B,OAAO,EAAE,EAAE,GAAG,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE;YACtC,WAAW,EAAE;gBACZ;oBACC,OAAO,EAAE,aAAa;oBACtB,SAAS,EAAE;wBACV,CAAC,IAAI,EAAE,aAAa,CAAC;wBACrB,CAAC,IAAI,EAAE,gBAAgB,CAAC;qBACxB;iBACD;aACD;SACD;KACD,CAAC,CACF,CAAC,OAAO,CAAC,6CAA6C,CAAC,CAAC;AAC1D,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,gEAAgE,EAAE,KAAK,IAAI,EAAE;IACjF,MAAM,OAAO,GAAG,MAAM,eAAe,CAAC;QACrC,UAAU,EAAE,IAAI;QAChB,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC;QACjC,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,CAAC;QAC/B,OAAO,EAAE;YACR,iBAAiB,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE;YACvC,iBAAiB,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE;YACvC,GAAG,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE;SACzB;KACD,CAAC,CAAC;IAEH,MAAM,CAAC,OAAO,CAAC,oBAAoB,CAAC,0BAA0B,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5E,MAAM,CAAC,OAAO,CAAC,oBAAoB,CAAC,+BAA+B,CAAC,CAAC,CAAC,IAAI,CACzE,IAAI,CACJ,CAAC;IACF,MAAM,CAAC,OAAO,CAAC,oBAAoB,CAAC,4BAA4B,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC9E,MAAM,CACL,OAAO,CAAC,oBAAoB,CAAC,kCAAkC,CAAC,CAChE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACb,MAAM,CAAC,OAAO,CAAC,oBAAoB,CAAC,0BAA0B,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5E,MAAM,CAAC,OAAO,CAAC,oBAAoB,CAAC,gCAAgC,CAAC,CAAC,CAAC,IAAI,CAC1E,IAAI,CACJ,CAAC;IAEF,MAAM,CACL,OAAO,CAAC,WAAW,CAAC,+BAA+B,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAC3E,CAAC,IAAI,CAAC,kCAAkC,CAAC,CAAC;IAC3C,MAAM,CACL,OAAO,CAAC,WAAW,CAAC,kCAAkC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;SACvE,IAAI,CACN,CAAC,IAAI,CAAC,+BAA+B,CAAC,CAAC;IACxC,MAAM,CACL,OAAO,CAAC,WAAW,CAAC,+BAA+B,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAC3E,CAAC,IAAI,CAAC,+BAA+B,CAAC,CAAC;IAExC,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,kCAAkC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAC1E,+BAA+B,CAC/B,CAAC;IACF,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,+BAA+B,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CACvE,+BAA+B,CAC/B,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,qDAAqD,EAAE,KAAK,IAAI,EAAE;IACtE,MAAM,OAAO,GAAG,MAAM,eAAe,CAAC;QACrC,UAAU,EAAE,IAAI;QAChB,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC;QACpC,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,CAAC;QAC/B,OAAO,EAAE;YACR,iBAAiB,EAAE;gBAClB,UAAU,EAAE,IAAI;gBAChB,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC;aAC9B;SACD;KACD,CAAC,CAAC;IAEH,MAAM,CACL,OAAO,CAAC,oBAAoB,CAAC,oCAAoC,CAAC,CAClE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAChB,wEAAwE;IACxE,MAAM,CAAC,OAAO,CAAC,oBAAoB,CAAC,iCAAiC,CAAC,CAAC,CAAC,IAAI,CAC3E,IAAI,CACJ,CAAC;IACF,MAAM,CACL,OAAO,CAAC,WAAW,CAAC,8BAA8B,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;SACtE,IAAI,CACN,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC;AAC9C,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,uDAAuD,EAAE,KAAK,IAAI,EAAE;IACxE,MAAM,OAAO,GAAG,MAAM,eAAe,CAAC;QACrC,UAAU,EAAE,IAAI;QAChB,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC;QACrB,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,CAAC;QAC/B,OAAO,EAAE;YACR,iBAAiB,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE;SACvC;KACD,CAAC,CAAC;IAEH,OAAO,CAAC,gCAAgC,CAAC;QACxC,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC;YAChB,MAAM,EAAE,IAAI;YACZ,MAAM,EAAE,yBAAyB;SACjC,CAAC;QACF,GAAG,EAAE,GAAG,EAAE,CAAC,SAAS;KACpB,CAAC,CAAC;IAEH,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACtD,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC3E,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AAC5E,CAAC,CAAC,CAAC;AAEH;;;GAGG;AACH,IAAI,CAAC,oEAAoE,EAAE,KAAK,IAAI,EAAE;IACrF,MAAM,OAAO,GAAG,MAAM,iBAAiB,CAAC;QACvC,qBAAqB,EAAE,IAAI;QAC3B,eAAe,EAAE;YAChB,OAAO,EAAE,IAAI;YACb,OAAO,EAAE,IAAI;YACb,MAAM,EAAE,IAAI;YACZ,MAAM,EAAE,IAAI;YACZ,MAAM,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM;YAC5B,gBAAgB,EAAE,EAAE,CAAC,oBAAoB,CAAC,MAAM;YAChD,MAAM,EAAE,EAAE,CAAC,YAAY,CAAC,MAAM;SAC9B;KACD,CAAC,CAAC;IAEH,MAAM,OAAO,GAAG,iBAAiB,CAAC;QACjC,UAAU,EAAE,IAAI;QAChB,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC;QAC3B,eAAe,EAAE;YAChB,GAAG,sBAAsB;YACzB,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,CAAC;YAC/B,OAAO,EAAE;gBACR,iBAAiB,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE;gBACvC,iBAAiB,EAAE;oBAClB,UAAU,EAAE,IAAI;oBAChB,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC;iBACrB;aACD;SACD;KACD,CAAC,CAAC,OAAO,CACT,wEAAwE,EACxE,EAAE,CACF,CAAC;IAEF,OAAO,CAAC,gBAAgB,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;IAClD,OAAO,CAAC,gBAAgB,CACvB,eAAe,EACf;;;;;;;;;;;;;;;;;;;;;;;GAuBC,CACD,CAAC;IAEF,MAAM,OAAO,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IACxC,MAAM,WAAW,GAAG,EAAE;SACpB,qBAAqB,CAAC,OAAO,CAAC;SAC9B,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;QACb,MAAM,OAAO,GACZ,OAAO,CAAC,CAAC,WAAW,KAAK,QAAQ;YAChC,CAAC,CAAC,CAAC,CAAC,WAAW;YACf,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,WAAW,CAAC;QAC9B,gEAAgE;QAChE,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEJ,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;QACtC,MAAM,OAAO,GACZ,OAAO,UAAU,CAAC,WAAW,KAAK,QAAQ;YACzC,CAAC,CAAC,UAAU,CAAC,WAAW;YACxB,CAAC,CAAC,UAAU,CAAC,WAAW,CAAC,WAAW,CAAC;QACvC,OAAO,CAAC,KAAK,CACZ,OAAO,EACP,UAAU,CAAC,IAAI,EAAE,QAAQ,EACzB,UAAU,CAAC,KAAK,KAAK,SAAS,IAAI,UAAU,CAAC,IAAI;YAChD,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,6BAA6B,CAAC,UAAU,CAAC,KAAK,CAAC;YACjE,CAAC,CAAC,SAAS,CACZ,CAAC;IACH,CAAC;IACD,MAAM,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;AACjC,CAAC,CAAC,CAAC","sourcesContent":["import { test, expect } from \"vitest\";\nimport {\n\tcreateProject as typescriptProject,\n\tts,\n} from \"@ts-morph/bootstrap\";\nimport { createParaglide } from \"../create-paraglide.js\";\nimport { defaultCompilerOptions } from \"../compiler-options.js\";\nimport { createRuntimeFile, normalizeRouting } from \"./create-runtime.js\";\n\ntest(\"normalizeRouting fills * fallback and resolves defaults\", () => {\n\tconst resolved = normalizeRouting({\n\t\trouting: {\n\t\t\t\"fin.example.com\": { baseLocale: \"fi\" },\n\t\t\t\"app.example.com\": {\n\t\t\t\tbaseLocale: \"sv\",\n\t\t\t\tlocales: [\"sv\", \"en\", \"pt-PT\"],\n\t\t\t},\n\t\t},\n\t\tlocales: [\"en\", \"fi\", \"sv\", \"pt-PT\", \"de\"],\n\t\tbaseLocale: \"en\",\n\t});\n\n\texpect(resolved).toEqual({\n\t\t\"fin.example.com\": {\n\t\t\tbaseLocale: \"fi\",\n\t\t\tlocales: [\"en\", \"fi\", \"sv\", \"pt-PT\", \"de\"],\n\t\t},\n\t\t\"app.example.com\": {\n\t\t\tbaseLocale: \"sv\",\n\t\t\tlocales: [\"sv\", \"en\", \"pt-PT\"],\n\t\t},\n\t\t\"*\": {\n\t\t\tbaseLocale: \"en\",\n\t\t\tlocales: [\"en\", \"fi\", \"sv\", \"pt-PT\", \"de\"],\n\t\t},\n\t});\n});\n\ntest(\"normalizeRouting rejects unknown locales\", () => {\n\texpect(() =>\n\t\tnormalizeRouting({\n\t\t\trouting: {\n\t\t\t\t\"fin.example.com\": { baseLocale: \"nope\" },\n\t\t\t},\n\t\t\tlocales: [\"en\", \"fi\"],\n\t\t\tbaseLocale: \"en\",\n\t\t})\n\t).toThrow(/Invalid baseLocale \"nope\"/);\n});\n\ntest(\"createRuntimeFile rejects routing + urlPatterns together\", () => {\n\texpect(() =>\n\t\tcreateRuntimeFile({\n\t\t\tbaseLocale: \"en\",\n\t\t\tlocales: [\"en\", \"de\"],\n\t\t\tcompilerOptions: {\n\t\t\t\tstrategy: [\"url\", \"baseLocale\"],\n\t\t\t\tcookieName: \"PARAGLIDE_LOCALE\",\n\t\t\t\tcookieMaxAge: 100,\n\t\t\t\tcookieDomain: \"\",\n\t\t\t\tlocalStorageKey: \"PARAGLIDE_LOCALE\",\n\t\t\t\tisServer: \"typeof window === 'undefined'\",\n\t\t\t\texperimentalMiddlewareLocaleSplitting: false,\n\t\t\t\tdisableAsyncLocalStorage: false,\n\t\t\t\trouting: { \"*\": { baseLocale: \"en\" } },\n\t\t\t\turlPatterns: [\n\t\t\t\t\t{\n\t\t\t\t\t\tpattern: \"/:path(.*)?\",\n\t\t\t\t\t\tlocalized: [\n\t\t\t\t\t\t\t[\"en\", \"/:path(.*)?\"],\n\t\t\t\t\t\t\t[\"de\", \"/de/:path(.*)?\"],\n\t\t\t\t\t\t],\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t})\n\t).toThrow(/Cannot set both \"routing\" and \"urlPatterns\"/);\n});\n\ntest(\"per-host baseLocale: bare path is host default, /en is english\", async () => {\n\tconst runtime = await createParaglide({\n\t\tbaseLocale: \"en\",\n\t\tlocales: [\"en\", \"fi\", \"sv\", \"de\"],\n\t\tstrategy: [\"url\", \"baseLocale\"],\n\t\trouting: {\n\t\t\t\"fin.example.com\": { baseLocale: \"fi\" },\n\t\t\t\"app.example.com\": { baseLocale: \"sv\" },\n\t\t\t\"*\": { baseLocale: \"en\" },\n\t\t},\n\t});\n\n\texpect(runtime.extractLocaleFromUrl(\"https://fin.example.com/\")).toBe(\"fi\");\n\texpect(runtime.extractLocaleFromUrl(\"https://fin.example.com/about\")).toBe(\n\t\t\"fi\"\n\t);\n\texpect(runtime.extractLocaleFromUrl(\"https://fin.example.com/en\")).toBe(\"en\");\n\texpect(\n\t\truntime.extractLocaleFromUrl(\"https://fin.example.com/en/about\")\n\t).toBe(\"en\");\n\texpect(runtime.extractLocaleFromUrl(\"https://app.example.com/\")).toBe(\"sv\");\n\texpect(runtime.extractLocaleFromUrl(\"https://other.example.com/de/x\")).toBe(\n\t\t\"de\"\n\t);\n\n\texpect(\n\t\truntime.localizeUrl(\"https://fin.example.com/about\", { locale: \"en\" }).href\n\t).toBe(\"https://fin.example.com/en/about\");\n\texpect(\n\t\truntime.localizeUrl(\"https://fin.example.com/en/about\", { locale: \"fi\" })\n\t\t\t.href\n\t).toBe(\"https://fin.example.com/about\");\n\texpect(\n\t\truntime.localizeUrl(\"https://fin.example.com/about\", { locale: \"fi\" }).href\n\t).toBe(\"https://fin.example.com/about\");\n\n\texpect(runtime.deLocalizeUrl(\"https://fin.example.com/en/about\").href).toBe(\n\t\t\"https://fin.example.com/about\"\n\t);\n\texpect(runtime.deLocalizeUrl(\"https://fin.example.com/about\").href).toBe(\n\t\t\"https://fin.example.com/about\"\n\t);\n});\n\ntest(\"host locales restrict which prefixes are recognized\", async () => {\n\tconst runtime = await createParaglide({\n\t\tbaseLocale: \"en\",\n\t\tlocales: [\"en\", \"sv\", \"pt-PT\", \"de\"],\n\t\tstrategy: [\"url\", \"baseLocale\"],\n\t\trouting: {\n\t\t\t\"app.example.com\": {\n\t\t\t\tbaseLocale: \"sv\",\n\t\t\t\tlocales: [\"sv\", \"en\", \"pt-PT\"],\n\t\t\t},\n\t\t},\n\t});\n\n\texpect(\n\t\truntime.extractLocaleFromUrl(\"https://app.example.com/pt-PT/shop\")\n\t).toBe(\"pt-PT\");\n\t// de is a project locale but not allowed on this host → treated as path\n\texpect(runtime.extractLocaleFromUrl(\"https://app.example.com/de/shop\")).toBe(\n\t\t\"sv\"\n\t);\n\texpect(\n\t\truntime.localizeUrl(\"https://app.example.com/shop\", { locale: \"pt-PT\" })\n\t\t\t.href\n\t).toBe(\"https://app.example.com/pt-PT/shop\");\n});\n\ntest(\"localizeHref works with host routing under ALS origin\", async () => {\n\tconst runtime = await createParaglide({\n\t\tbaseLocale: \"en\",\n\t\tlocales: [\"en\", \"fi\"],\n\t\tstrategy: [\"url\", \"baseLocale\"],\n\t\trouting: {\n\t\t\t\"fin.example.com\": { baseLocale: \"fi\" },\n\t\t},\n\t});\n\n\truntime.overwriteServerAsyncLocalStorage({\n\t\tgetStore: () => ({\n\t\t\tlocale: \"fi\",\n\t\t\torigin: \"https://fin.example.com\",\n\t\t}),\n\t\trun: () => undefined,\n\t});\n\n\texpect(runtime.localizeHref(\"/about\")).toBe(\"/about\");\n\texpect(runtime.localizeHref(\"/about\", { locale: \"en\" })).toBe(\"/en/about\");\n\texpect(runtime.localizeHref(\"/en/about\", { locale: \"fi\" })).toBe(\"/about\");\n});\n\n/**\n * Host routing types must stay narrowed to Locale in generated runtime.js.\n * Runtime tests alone miss this — it is a checkJs type error, not a runtime failure.\n */\ntest(\"generated host-routing runtime typechecks with narrow Locale types\", async () => {\n\tconst project = await typescriptProject({\n\t\tuseInMemoryFileSystem: true,\n\t\tcompilerOptions: {\n\t\t\tallowJs: true,\n\t\t\tcheckJs: true,\n\t\t\tnoEmit: true,\n\t\t\tstrict: true,\n\t\t\tmodule: ts.ModuleKind.Node16,\n\t\t\tmoduleResolution: ts.ModuleResolutionKind.Node16,\n\t\t\ttarget: ts.ScriptTarget.ES2022,\n\t\t},\n\t});\n\n\tconst runtime = createRuntimeFile({\n\t\tbaseLocale: \"en\",\n\t\tlocales: [\"en\", \"sv\", \"fi\"],\n\t\tcompilerOptions: {\n\t\t\t...defaultCompilerOptions,\n\t\t\tstrategy: [\"url\", \"baseLocale\"],\n\t\t\trouting: {\n\t\t\t\t\"fin.example.com\": { baseLocale: \"fi\" },\n\t\t\t\t\"app.example.com\": {\n\t\t\t\t\tbaseLocale: \"sv\",\n\t\t\t\t\tlocales: [\"sv\", \"en\"],\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}).replace(\n\t\t'import * as pathToRegexp from \"@sebbro/paraglide-lite/path-to-regexp\";',\n\t\t\"\"\n\t);\n\n\tproject.createSourceFile(\"./runtime.js\", runtime);\n\tproject.createSourceFile(\n\t\t\"./consumer.ts\",\n\t\t`\n\t\timport {\n\t\t\tgenerateStaticLocalizedUrls,\n\t\t\tlocalizeUrl,\n\t\t\trouting,\n\t\t} from \"./runtime.js\";\n\n\t\t// routing table values must retain Locale, not widen to string\n\t\tconst rule = routing[\"app.example.com\"]!;\n\t\trule.baseLocale satisfies \"en\" | \"sv\" | \"fi\";\n\t\t// Function that promises Locale accepts rule.baseLocale (not string)\n\t\tconst fallback: \"en\" | \"sv\" | \"fi\" = rule.baseLocale;\n\n\t\tfor (const locale of rule.locales) {\n\t\t\tlocale satisfies \"en\" | \"sv\" | \"fi\";\n\t\t\t// localizeUrl requires Locale — must not reject rule.locales members\n\t\t\tlocalizeUrl(\"https://app.example.com/shop\", { locale });\n\t\t}\n\n\t\t// host-routing path inside generateStaticLocalizedUrls uses rule.locales as Locale[]\n\t\tgenerateStaticLocalizedUrls([\"https://fin.example.com/about\"]);\n\n\t\tvoid fallback;\n\t\t`\n\t);\n\n\tconst program = project.createProgram();\n\tconst diagnostics = ts\n\t\t.getPreEmitDiagnostics(program)\n\t\t.filter((d) => {\n\t\t\tconst message =\n\t\t\t\ttypeof d.messageText === \"string\"\n\t\t\t\t\t? d.messageText\n\t\t\t\t\t: d.messageText.messageText;\n\t\t\t// path-to-regexp import is stripped for the in-memory typecheck\n\t\t\treturn !message.includes(\"path-to-regexp\");\n\t\t});\n\n\tfor (const diagnostic of diagnostics) {\n\t\tconst message =\n\t\t\ttypeof diagnostic.messageText === \"string\"\n\t\t\t\t? diagnostic.messageText\n\t\t\t\t: diagnostic.messageText.messageText;\n\t\tconsole.error(\n\t\t\tmessage,\n\t\t\tdiagnostic.file?.fileName,\n\t\t\tdiagnostic.start !== undefined && diagnostic.file\n\t\t\t\t? diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start)\n\t\t\t\t: undefined\n\t\t);\n\t}\n\texpect(diagnostics).toEqual([]);\n});\n"]}
@@ -56,11 +56,11 @@ export declare const urlPatterns: Array<{
56
56
  /**
57
57
  * Per-host routing table for the fast string-based URL strategy.
58
58
  *
59
- * @type {Record<string, { baseLocale: string, locales: string[] }>}
59
+ * @type {Record<string, { baseLocale: Locale, locales: Locale[] }>}
60
60
  */
61
61
  export declare const routing: Record<string, {
62
- baseLocale: string;
63
- locales: string[];
62
+ baseLocale: Locale;
63
+ locales: Locale[];
64
64
  }>;
65
65
  export type ParaglideAsyncLocalStorage = {
66
66
  getStore(): {
@@ -49,7 +49,7 @@ export const urlPatterns = [];
49
49
  /**
50
50
  * Per-host routing table for the fast string-based URL strategy.
51
51
  *
52
- * @type {Record<string, { baseLocale: string, locales: string[] }>}
52
+ * @type {Record<string, { baseLocale: Locale, locales: Locale[] }>}
53
53
  */
54
54
  export const routing = {};
55
55
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"variables.js","sourceRoot":"","sources":["../../../src/compiler/runtime/variables.js"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,IAAI,CAAC;AAE/B;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,gCAAgC,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;AAEvE,qBAAqB;AACrB,MAAM,CAAC,MAAM,UAAU,GAAG,eAAe,CAAC;AAE1C,qBAAqB;AACrB,MAAM,CAAC,MAAM,YAAY,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,GAAG,CAAC;AAE/C,qBAAqB;AACrB,MAAM,CAAC,MAAM,YAAY,GAAG,iBAAiB,CAAC;AAE9C,qBAAqB;AACrB,MAAM,CAAC,MAAM,eAAe,GAAG,kBAAkB,CAAC;AAElD;;GAEG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,gBAAgB,CAAC,CAAC;AAE3C;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,EAAE,CAAC;AAElC;;;;GAIG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC;AAE9B;;;;GAIG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,EAAE,CAAC;AAE1B;;;;;;;;;;GAUG;AAEH;;;;;;;GAOG;AACH,MAAM,CAAC,IAAI,uBAAuB,GAAG,SAAS,CAAC;AAE/C;;;;;;;GAOG;AACH,MAAM,UAAU,0BAA0B;IACzC,OAAO,uBAAuB,CAAC;AAChC,CAAC;AAED,MAAM,CAAC,MAAM,wBAAwB,GAAG,KAAK,CAAC;AAE9C,MAAM,CAAC,MAAM,qCAAqC,GAAG,KAAK,CAAC;AAE3D,MAAM,CAAC,MAAM,QAAQ,GAAG,OAAO,MAAM,KAAK,WAAW,CAAC;AAEtD,iCAAiC;AACjC,MAAM,CAAC,MAAM,wBAAwB,GAAG,SAAS,CAAC;AAElD;;;;;;;;;GASG;AACH,MAAM,UAAU,gCAAgC,CAAC,KAAK;IACrD,uBAAuB,GAAG,KAAK,CAAC;AACjC,CAAC;AAED,MAAM,CAAC,MAAM,+BAA+B,GAAG,KAAK,CAAC;AAErD,MAAM,CAAC,MAAM,4BAA4B,GAAG,KAAK,CAAC;AAElD,MAAM,CAAC,MAAM,wCAAwC,GAAG,KAAK,CAAC;AAE9D,MAAM,CAAC,MAAM,2CAA2C,GAAG,KAAK,CAAC;AAEjE,MAAM,CAAC,MAAM,mCAAmC,GAAG,KAAK,CAAC;AAEzD,MAAM,CAAC,MAAM,4BAA4B,GAAG,KAAK,CAAC;AAElD,MAAM,CAAC,MAAM,sCAAsC,GAAG,KAAK,CAAC","sourcesContent":["/**\n * The project's base locale.\n *\n * @example\n * if (locale === baseLocale) {\n * // do something\n * }\n */\nexport const baseLocale = \"en\";\n\n/**\n * The project's locales that have been specified in the settings.\n *\n * @example\n * if (locales.includes(userSelectedLocale) === false) {\n * throw new Error('Locale is not available');\n * }\n */\nexport const locales = /** @type {readonly string[]} */ ([\"en\", \"de\"]);\n\n/** @type {string} */\nexport const cookieName = \"<cookie-name>\";\n\n/** @type {number} */\nexport const cookieMaxAge = 60 * 60 * 24 * 400;\n\n/** @type {string} */\nexport const cookieDomain = \"<cookie-domain>\";\n\n/** @type {string} */\nexport const localStorageKey = \"PARAGLIDE_LOCALE\";\n\n/**\n * @type {Array<\"cookie\" | \"baseLocale\" | \"globalVariable\" | \"url\" | \"preferredLanguage\" | \"localStorage\" | `custom-${string}`>}\n */\nexport const strategy = [\"globalVariable\"];\n\n/**\n * Route-level strategy overrides.\n *\n * `match` uses URLPattern syntax.\n *\n * @type {Array<{\n * match: string;\n * strategy?: Array<\"cookie\" | \"baseLocale\" | \"globalVariable\" | \"url\" | \"preferredLanguage\" | \"localStorage\" | `custom-${string}`>;\n * exclude?: boolean;\n * }>}\n */\nexport const routeStrategies = [];\n\n/**\n * The used URL patterns.\n *\n * @type {Array<{ pattern: string, localized: Array<[Locale, string]> }>}\n */\nexport const urlPatterns = [];\n\n/**\n * Per-host routing table for the fast string-based URL strategy.\n *\n * @type {Record<string, { baseLocale: string, locales: string[] }>}\n */\nexport const routing = {};\n\n/**\n * @typedef {{\n * \t\tgetStore(): {\n * \t\tlocale?: Locale,\n * \t\t\torigin?: string,\n * \t\t\tmessageCalls?: Set<string>\n * \t} | undefined,\n * \t\trun: (store: { locale?: Locale, origin?: string, messageCalls?: Set<string>},\n * cb: any) => any\n * }} ParaglideAsyncLocalStorage\n */\n\n/**\n * Server side async local storage that is set by `serverMiddleware()`.\n *\n * The variable is used to retrieve the locale and origin in a server-side\n * rendering context without effecting other requests.\n *\n * @type {ParaglideAsyncLocalStorage | undefined}\n */\nexport let serverAsyncLocalStorage = undefined;\n\n/**\n * Returns the current server-side async local storage instance.\n *\n * Accessing the mutable value through a function keeps it observable when\n * module interceptors wrap exported bindings and snapshot their initial value.\n *\n * @returns {ParaglideAsyncLocalStorage | undefined}\n */\nexport function getServerAsyncLocalStorage() {\n\treturn serverAsyncLocalStorage;\n}\n\nexport const disableAsyncLocalStorage = false;\n\nexport const experimentalMiddlewareLocaleSplitting = false;\n\nexport const isServer = typeof window === \"undefined\";\n\n/** @type {Locale | undefined} */\nexport const experimentalStaticLocale = undefined;\n\n/**\n * Sets the server side async local storage.\n *\n * The function is needed because the `runtime.js` file\n * must define the `serverAsyncLocalStorage` variable to\n * avoid a circular import between `runtime.js` and\n * `server.js` files.\n *\n * @param {ParaglideAsyncLocalStorage | undefined} value\n */\nexport function overwriteServerAsyncLocalStorage(value) {\n\tserverAsyncLocalStorage = value;\n}\n\nexport const TREE_SHAKE_COOKIE_STRATEGY_USED = false;\n\nexport const TREE_SHAKE_URL_STRATEGY_USED = false;\n\nexport const TREE_SHAKE_GLOBAL_VARIABLE_STRATEGY_USED = false;\n\nexport const TREE_SHAKE_PREFERRED_LANGUAGE_STRATEGY_USED = false;\n\nexport const TREE_SHAKE_DEFAULT_URL_PATTERN_USED = false;\n\nexport const TREE_SHAKE_HOST_ROUTING_USED = false;\n\nexport const TREE_SHAKE_LOCAL_STORAGE_STRATEGY_USED = false;\n"]}
1
+ {"version":3,"file":"variables.js","sourceRoot":"","sources":["../../../src/compiler/runtime/variables.js"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,IAAI,CAAC;AAE/B;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,gCAAgC,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;AAEvE,qBAAqB;AACrB,MAAM,CAAC,MAAM,UAAU,GAAG,eAAe,CAAC;AAE1C,qBAAqB;AACrB,MAAM,CAAC,MAAM,YAAY,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,GAAG,CAAC;AAE/C,qBAAqB;AACrB,MAAM,CAAC,MAAM,YAAY,GAAG,iBAAiB,CAAC;AAE9C,qBAAqB;AACrB,MAAM,CAAC,MAAM,eAAe,GAAG,kBAAkB,CAAC;AAElD;;GAEG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,gBAAgB,CAAC,CAAC;AAE3C;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,EAAE,CAAC;AAElC;;;;GAIG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC;AAE9B;;;;GAIG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,EAAE,CAAC;AAE1B;;;;;;;;;;GAUG;AAEH;;;;;;;GAOG;AACH,MAAM,CAAC,IAAI,uBAAuB,GAAG,SAAS,CAAC;AAE/C;;;;;;;GAOG;AACH,MAAM,UAAU,0BAA0B;IACzC,OAAO,uBAAuB,CAAC;AAChC,CAAC;AAED,MAAM,CAAC,MAAM,wBAAwB,GAAG,KAAK,CAAC;AAE9C,MAAM,CAAC,MAAM,qCAAqC,GAAG,KAAK,CAAC;AAE3D,MAAM,CAAC,MAAM,QAAQ,GAAG,OAAO,MAAM,KAAK,WAAW,CAAC;AAEtD,iCAAiC;AACjC,MAAM,CAAC,MAAM,wBAAwB,GAAG,SAAS,CAAC;AAElD;;;;;;;;;GASG;AACH,MAAM,UAAU,gCAAgC,CAAC,KAAK;IACrD,uBAAuB,GAAG,KAAK,CAAC;AACjC,CAAC;AAED,MAAM,CAAC,MAAM,+BAA+B,GAAG,KAAK,CAAC;AAErD,MAAM,CAAC,MAAM,4BAA4B,GAAG,KAAK,CAAC;AAElD,MAAM,CAAC,MAAM,wCAAwC,GAAG,KAAK,CAAC;AAE9D,MAAM,CAAC,MAAM,2CAA2C,GAAG,KAAK,CAAC;AAEjE,MAAM,CAAC,MAAM,mCAAmC,GAAG,KAAK,CAAC;AAEzD,MAAM,CAAC,MAAM,4BAA4B,GAAG,KAAK,CAAC;AAElD,MAAM,CAAC,MAAM,sCAAsC,GAAG,KAAK,CAAC","sourcesContent":["/**\n * The project's base locale.\n *\n * @example\n * if (locale === baseLocale) {\n * // do something\n * }\n */\nexport const baseLocale = \"en\";\n\n/**\n * The project's locales that have been specified in the settings.\n *\n * @example\n * if (locales.includes(userSelectedLocale) === false) {\n * throw new Error('Locale is not available');\n * }\n */\nexport const locales = /** @type {readonly string[]} */ ([\"en\", \"de\"]);\n\n/** @type {string} */\nexport const cookieName = \"<cookie-name>\";\n\n/** @type {number} */\nexport const cookieMaxAge = 60 * 60 * 24 * 400;\n\n/** @type {string} */\nexport const cookieDomain = \"<cookie-domain>\";\n\n/** @type {string} */\nexport const localStorageKey = \"PARAGLIDE_LOCALE\";\n\n/**\n * @type {Array<\"cookie\" | \"baseLocale\" | \"globalVariable\" | \"url\" | \"preferredLanguage\" | \"localStorage\" | `custom-${string}`>}\n */\nexport const strategy = [\"globalVariable\"];\n\n/**\n * Route-level strategy overrides.\n *\n * `match` uses URLPattern syntax.\n *\n * @type {Array<{\n * match: string;\n * strategy?: Array<\"cookie\" | \"baseLocale\" | \"globalVariable\" | \"url\" | \"preferredLanguage\" | \"localStorage\" | `custom-${string}`>;\n * exclude?: boolean;\n * }>}\n */\nexport const routeStrategies = [];\n\n/**\n * The used URL patterns.\n *\n * @type {Array<{ pattern: string, localized: Array<[Locale, string]> }>}\n */\nexport const urlPatterns = [];\n\n/**\n * Per-host routing table for the fast string-based URL strategy.\n *\n * @type {Record<string, { baseLocale: Locale, locales: Locale[] }>}\n */\nexport const routing = {};\n\n/**\n * @typedef {{\n * \t\tgetStore(): {\n * \t\tlocale?: Locale,\n * \t\t\torigin?: string,\n * \t\t\tmessageCalls?: Set<string>\n * \t} | undefined,\n * \t\trun: (store: { locale?: Locale, origin?: string, messageCalls?: Set<string>},\n * cb: any) => any\n * }} ParaglideAsyncLocalStorage\n */\n\n/**\n * Server side async local storage that is set by `serverMiddleware()`.\n *\n * The variable is used to retrieve the locale and origin in a server-side\n * rendering context without effecting other requests.\n *\n * @type {ParaglideAsyncLocalStorage | undefined}\n */\nexport let serverAsyncLocalStorage = undefined;\n\n/**\n * Returns the current server-side async local storage instance.\n *\n * Accessing the mutable value through a function keeps it observable when\n * module interceptors wrap exported bindings and snapshot their initial value.\n *\n * @returns {ParaglideAsyncLocalStorage | undefined}\n */\nexport function getServerAsyncLocalStorage() {\n\treturn serverAsyncLocalStorage;\n}\n\nexport const disableAsyncLocalStorage = false;\n\nexport const experimentalMiddlewareLocaleSplitting = false;\n\nexport const isServer = typeof window === \"undefined\";\n\n/** @type {Locale | undefined} */\nexport const experimentalStaticLocale = undefined;\n\n/**\n * Sets the server side async local storage.\n *\n * The function is needed because the `runtime.js` file\n * must define the `serverAsyncLocalStorage` variable to\n * avoid a circular import between `runtime.js` and\n * `server.js` files.\n *\n * @param {ParaglideAsyncLocalStorage | undefined} value\n */\nexport function overwriteServerAsyncLocalStorage(value) {\n\tserverAsyncLocalStorage = value;\n}\n\nexport const TREE_SHAKE_COOKIE_STRATEGY_USED = false;\n\nexport const TREE_SHAKE_URL_STRATEGY_USED = false;\n\nexport const TREE_SHAKE_GLOBAL_VARIABLE_STRATEGY_USED = false;\n\nexport const TREE_SHAKE_PREFERRED_LANGUAGE_STRATEGY_USED = false;\n\nexport const TREE_SHAKE_DEFAULT_URL_PATTERN_USED = false;\n\nexport const TREE_SHAKE_HOST_ROUTING_USED = false;\n\nexport const TREE_SHAKE_LOCAL_STORAGE_STRATEGY_USED = false;\n"]}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Removes JSDoc block comments (`/** ... *​/`) from generated source.
3
+ *
4
+ * Leaves single-star blocks alone (e.g. `/* eslint-disable *​/`, `/* @__PURE__ *​/`).
5
+ * Used when `.d.ts` files already carry types (`emitTsDeclarations`).
6
+ */
7
+ export declare function stripJsDocComments(source: string): string;
8
+ /** Message-related paths that only need types via hand-rolled `.d.ts`. */
9
+ export declare function isMessageJsPath(fileName: string): boolean;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Removes JSDoc block comments (`/** ... *​/`) from generated source.
3
+ *
4
+ * Leaves single-star blocks alone (e.g. `/* eslint-disable *​/`, `/* @__PURE__ *​/`).
5
+ * Used when `.d.ts` files already carry types (`emitTsDeclarations`).
6
+ */
7
+ export function stripJsDocComments(source) {
8
+ // Non-greedy: each JSDoc block is independent. Generated output never nests comments.
9
+ const withoutJsDoc = source.replace(/\/\*\*[\s\S]*?\*\//g, "");
10
+ // Collapse runs of blank lines left behind by removed comment blocks.
11
+ return withoutJsDoc.replace(/[ \t]*\n(?:[ \t]*\n){2,}/g, "\n\n").replace(/^\n+/, "");
12
+ }
13
+ /** Message-related paths that only need types via hand-rolled `.d.ts`. */
14
+ export function isMessageJsPath(fileName) {
15
+ const normalized = fileName.replaceAll("\\", "/");
16
+ return (normalized === "messages.js" ||
17
+ (normalized.startsWith("messages/") && normalized.endsWith(".js")));
18
+ }
19
+ //# sourceMappingURL=strip-jsdoc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strip-jsdoc.js","sourceRoot":"","sources":["../../src/compiler/strip-jsdoc.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAc;IAChD,sFAAsF;IACtF,MAAM,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC,qBAAqB,EAAE,EAAE,CAAC,CAAC;IAC/D,sEAAsE;IACtE,OAAO,YAAY,CAAC,OAAO,CAAC,2BAA2B,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACtF,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,eAAe,CAAC,QAAgB;IAC/C,MAAM,UAAU,GAAG,QAAQ,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAClD,OAAO,CACN,UAAU,KAAK,aAAa;QAC5B,CAAC,UAAU,CAAC,UAAU,CAAC,WAAW,CAAC,IAAI,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAClE,CAAC;AACH,CAAC","sourcesContent":["/**\n * Removes JSDoc block comments (`/** ... *​/`) from generated source.\n *\n * Leaves single-star blocks alone (e.g. `/* eslint-disable *​/`, `/* @__PURE__ *​/`).\n * Used when `.d.ts` files already carry types (`emitTsDeclarations`).\n */\nexport function stripJsDocComments(source: string): string {\n\t// Non-greedy: each JSDoc block is independent. Generated output never nests comments.\n\tconst withoutJsDoc = source.replace(/\\/\\*\\*[\\s\\S]*?\\*\\//g, \"\");\n\t// Collapse runs of blank lines left behind by removed comment blocks.\n\treturn withoutJsDoc.replace(/[ \\t]*\\n(?:[ \\t]*\\n){2,}/g, \"\\n\\n\").replace(/^\\n+/, \"\");\n}\n\n/** Message-related paths that only need types via hand-rolled `.d.ts`. */\nexport function isMessageJsPath(fileName: string): boolean {\n\tconst normalized = fileName.replaceAll(\"\\\\\", \"/\");\n\treturn (\n\t\tnormalized === \"messages.js\" ||\n\t\t(normalized.startsWith(\"messages/\") && normalized.endsWith(\".js\"))\n\t);\n}\n"]}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,95 @@
1
+ import { expect, test } from "vitest";
2
+ import { isMessageJsPath, stripJsDocComments } from "./strip-jsdoc.js";
3
+ import { compileProject } from "./compile-project.js";
4
+ import { createBundleNested } from "./test-helpers.js";
5
+ test("stripJsDocComments removes JSDoc but keeps eslint and pure annotations", () => {
6
+ const source = `/* eslint-disable */
7
+ /** @typedef {{ x: number }} X */
8
+ /**
9
+ * multi
10
+ * line
11
+ */
12
+ export const hello = /** @type {(i: X) => string} */ ((i) => {
13
+ return /** @type {string} */ (\`hi \${i.x}\`)
14
+ });
15
+ const f = /* @__PURE__ */ Object.assign(() => 1, {});
16
+ `;
17
+ const stripped = stripJsDocComments(source);
18
+ expect(stripped).toContain("/* eslint-disable */");
19
+ expect(stripped).toContain("/* @__PURE__ */");
20
+ expect(stripped).not.toContain("@typedef");
21
+ expect(stripped).not.toContain("@type");
22
+ expect(stripped).toContain("export const hello");
23
+ expect(stripped).toContain("Object.assign");
24
+ });
25
+ test("isMessageJsPath", () => {
26
+ expect(isMessageJsPath("messages.js")).toBe(true);
27
+ expect(isMessageJsPath("messages/_index.js")).toBe(true);
28
+ expect(isMessageJsPath("messages/en.js")).toBe(true);
29
+ expect(isMessageJsPath("runtime.js")).toBe(false);
30
+ expect(isMessageJsPath("server.js")).toBe(false);
31
+ });
32
+ test("emitTsDeclarations strips JSDoc from message JS only", async () => {
33
+ const bundles = [
34
+ createBundleNested({
35
+ id: "hello",
36
+ declarations: [{ type: "input-variable", name: "username" }],
37
+ messages: [
38
+ {
39
+ locale: "en",
40
+ variants: [
41
+ {
42
+ pattern: [
43
+ { type: "text", value: "Hello " },
44
+ {
45
+ type: "expression",
46
+ arg: { type: "variable-reference", name: "username" },
47
+ },
48
+ ],
49
+ },
50
+ ],
51
+ },
52
+ ],
53
+ }),
54
+ ];
55
+ const settings = { locales: ["en"], baseLocale: "en" };
56
+ const withDts = await compileProject({
57
+ bundles,
58
+ settings,
59
+ compilerOptions: {
60
+ emitTsDeclarations: true,
61
+ outputStructure: "locale-modules",
62
+ emitGitIgnore: false,
63
+ emitPrettierIgnore: false,
64
+ emitReadme: false,
65
+ },
66
+ });
67
+ const withoutDts = await compileProject({
68
+ bundles,
69
+ settings,
70
+ compilerOptions: {
71
+ emitTsDeclarations: false,
72
+ outputStructure: "locale-modules",
73
+ emitGitIgnore: false,
74
+ emitPrettierIgnore: false,
75
+ emitReadme: false,
76
+ },
77
+ });
78
+ expect(withDts["messages/_index.js"]).not.toContain("@param");
79
+ expect(withDts["messages/_index.js"]).not.toContain("@typedef");
80
+ expect(withDts["messages/_index.js"]).not.toMatch(/\/\*\*/);
81
+ expect(withDts["messages/en.js"]).not.toMatch(/\/\*\*/);
82
+ // eslint header kept
83
+ expect(withDts["messages/_index.js"]).toContain("/* eslint-disable */");
84
+ // Without DTS, JSDoc remains for typing
85
+ expect(withoutDts["messages/_index.js"]).toContain("@param");
86
+ expect(withoutDts["messages/_index.js"]).toMatch(/\/\*\*/);
87
+ // Runtime still has its own comments (not stripped)
88
+ expect(withDts["runtime.js"]).toMatch(/\/\*\*/);
89
+ // Types still present via DTS
90
+ expect(withDts["messages/_index.d.ts"]).toContain("export const hello");
91
+ expect(withDts["messages/_index.d.ts"]).toContain("username");
92
+ // Smaller message JS when stripped
93
+ expect(withDts["messages/_index.js"].length).toBeLessThan(withoutDts["messages/_index.js"].length);
94
+ });
95
+ //# sourceMappingURL=strip-jsdoc.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strip-jsdoc.test.js","sourceRoot":"","sources":["../../src/compiler/strip-jsdoc.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect, test } from \"vitest\";\nimport { isMessageJsPath, stripJsDocComments } from \"./strip-jsdoc.js\";\nimport { compileProject } from \"./compile-project.js\";\nimport { createBundleNested } from \"./test-helpers.js\";\n\ntest(\"stripJsDocComments removes JSDoc but keeps eslint and pure annotations\", () => {\n\tconst source = `/* eslint-disable */\n/** @typedef {{ x: number }} X */\n/**\n * multi\n * line\n */\nexport const hello = /** @type {(i: X) => string} */ ((i) => {\n\treturn /** @type {string} */ (\\`hi \\${i.x}\\`)\n});\nconst f = /* @__PURE__ */ Object.assign(() => 1, {});\n`;\n\n\tconst stripped = stripJsDocComments(source);\n\texpect(stripped).toContain(\"/* eslint-disable */\");\n\texpect(stripped).toContain(\"/* @__PURE__ */\");\n\texpect(stripped).not.toContain(\"@typedef\");\n\texpect(stripped).not.toContain(\"@type\");\n\texpect(stripped).toContain(\"export const hello\");\n\texpect(stripped).toContain(\"Object.assign\");\n});\n\ntest(\"isMessageJsPath\", () => {\n\texpect(isMessageJsPath(\"messages.js\")).toBe(true);\n\texpect(isMessageJsPath(\"messages/_index.js\")).toBe(true);\n\texpect(isMessageJsPath(\"messages/en.js\")).toBe(true);\n\texpect(isMessageJsPath(\"runtime.js\")).toBe(false);\n\texpect(isMessageJsPath(\"server.js\")).toBe(false);\n});\n\ntest(\"emitTsDeclarations strips JSDoc from message JS only\", async () => {\n\tconst bundles = [\n\t\tcreateBundleNested({\n\t\t\tid: \"hello\",\n\t\t\tdeclarations: [{ type: \"input-variable\", name: \"username\" }],\n\t\t\tmessages: [\n\t\t\t\t{\n\t\t\t\t\tlocale: \"en\",\n\t\t\t\t\tvariants: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpattern: [\n\t\t\t\t\t\t\t\t{ type: \"text\", value: \"Hello \" },\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: \"expression\",\n\t\t\t\t\t\t\t\t\targ: { type: \"variable-reference\", name: \"username\" },\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t],\n\t\t}),\n\t];\n\tconst settings = { locales: [\"en\"], baseLocale: \"en\" };\n\n\tconst withDts = await compileProject({\n\t\tbundles,\n\t\tsettings,\n\t\tcompilerOptions: {\n\t\t\temitTsDeclarations: true,\n\t\t\toutputStructure: \"locale-modules\",\n\t\t\temitGitIgnore: false,\n\t\t\temitPrettierIgnore: false,\n\t\t\temitReadme: false,\n\t\t},\n\t});\n\n\tconst withoutDts = await compileProject({\n\t\tbundles,\n\t\tsettings,\n\t\tcompilerOptions: {\n\t\t\temitTsDeclarations: false,\n\t\t\toutputStructure: \"locale-modules\",\n\t\t\temitGitIgnore: false,\n\t\t\temitPrettierIgnore: false,\n\t\t\temitReadme: false,\n\t\t},\n\t});\n\n\texpect(withDts[\"messages/_index.js\"]).not.toContain(\"@param\");\n\texpect(withDts[\"messages/_index.js\"]).not.toContain(\"@typedef\");\n\texpect(withDts[\"messages/_index.js\"]).not.toMatch(/\\/\\*\\*/);\n\texpect(withDts[\"messages/en.js\"]).not.toMatch(/\\/\\*\\*/);\n\t// eslint header kept\n\texpect(withDts[\"messages/_index.js\"]).toContain(\"/* eslint-disable */\");\n\n\t// Without DTS, JSDoc remains for typing\n\texpect(withoutDts[\"messages/_index.js\"]).toContain(\"@param\");\n\texpect(withoutDts[\"messages/_index.js\"]).toMatch(/\\/\\*\\*/);\n\n\t// Runtime still has its own comments (not stripped)\n\texpect(withDts[\"runtime.js\"]).toMatch(/\\/\\*\\*/);\n\n\t// Types still present via DTS\n\texpect(withDts[\"messages/_index.d.ts\"]).toContain(\"export const hello\");\n\texpect(withDts[\"messages/_index.d.ts\"]).toContain(\"username\");\n\n\t// Smaller message JS when stripped\n\texpect(withDts[\"messages/_index.js\"]!.length).toBeLessThan(\n\t\twithoutDts[\"messages/_index.js\"]!.length\n\t);\n});\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sebbro/paraglide-lite",
3
3
  "type": "module",
4
- "version": "0.1.0",
4
+ "version": "0.1.1",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
7
7
  "access": "public",