@inlang/sdk 2.9.3 → 2.10.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.
@@ -68,6 +68,19 @@ export type ExportFile = {
68
68
  name: string;
69
69
  /** The binary content of the resource */
70
70
  content: Uint8Array;
71
+ /**
72
+ * Metadata of the exported file.
73
+ *
74
+ * The counterpart of `ImportFile.toBeImportedFilesMetadata`. Plugins can
75
+ * use it to pass information to the writer. For example, a plugin that
76
+ * supports a namespaced `pathPattern` (`Record<namespace, pattern>`)
77
+ * provides `{ namespace }` so that `saveProjectToDirectory` can resolve
78
+ * the pattern each exported file belongs to. Plugins can also provide
79
+ * `{ pathPattern }` to override the configured pattern for one file.
80
+ *
81
+ * https://github.com/opral/inlang/issues/4356
82
+ */
83
+ metadata?: Record<string, any>;
71
84
  };
72
85
  /**
73
86
  * Minimal RxJS compatible (generic) subscription type.
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"/","sources":["project/api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAClE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAE7D,MAAM,MAAM,aAAa,GAAG;IAC3B,EAAE,EAAE,MAAM,CAAC,oBAAoB,CAAC,CAAC;IACjC;;;;;OAKG;IACH,OAAO,EAAE,kBAAkB,CAAC;IAC5B,EAAE,EAAE;QACH;;;WAGG;QACH,GAAG,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;KAC3B,CAAC;IACF,OAAO,EAAE;QACR,GAAG,EAAE,MAAM,OAAO,CAAC,SAAS,YAAY,EAAE,CAAC,CAAC;KAC5C,CAAC;IACF,MAAM,EAAE;QACP,GAAG,EAAE,MAAM,OAAO,CAAC,SAAS,KAAK,EAAE,CAAC,CAAC;KACrC,CAAC;IACF,QAAQ,EAAE;QACT,GAAG,EAAE,MAAM,OAAO,CAAC,eAAe,CAAC,CAAC;QACpC,GAAG,EAAE,CAAC,QAAQ,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;KAClD,CAAC;IACF,GAAG,EAAE,GAAG,CAAC;IACT,WAAW,EAAE,CAAC,IAAI,EAAE;QACnB,SAAS,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC;QAC/B,KAAK,EAAE,UAAU,EAAE,CAAC;KACpB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpB,WAAW,EAAE,CAAC,IAAI,EAAE;QACnB,SAAS,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC;KAC/B,KAAK,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IAC5B,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACxB,sCAAsC;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,yCAAyC;IACzC,OAAO,EAAE,UAAU,CAAC;IACpB;;;;;OAKG;IACH,yBAAyB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAChD,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACxB,sCAAsC;IACtC,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;OAOG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,yCAAyC;IACzC,OAAO,EAAE,UAAU,CAAC;CACpB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,KAAK;IAC/D,WAAW,EAAE,MAAM,IAAI,CAAC;CACxB,CAAC"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"/","sources":["project/api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAClE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAE7D,MAAM,MAAM,aAAa,GAAG;IAC3B,EAAE,EAAE,MAAM,CAAC,oBAAoB,CAAC,CAAC;IACjC;;;;;OAKG;IACH,OAAO,EAAE,kBAAkB,CAAC;IAC5B,EAAE,EAAE;QACH;;;WAGG;QACH,GAAG,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;KAC3B,CAAC;IACF,OAAO,EAAE;QACR,GAAG,EAAE,MAAM,OAAO,CAAC,SAAS,YAAY,EAAE,CAAC,CAAC;KAC5C,CAAC;IACF,MAAM,EAAE;QACP,GAAG,EAAE,MAAM,OAAO,CAAC,SAAS,KAAK,EAAE,CAAC,CAAC;KACrC,CAAC;IACF,QAAQ,EAAE;QACT,GAAG,EAAE,MAAM,OAAO,CAAC,eAAe,CAAC,CAAC;QACpC,GAAG,EAAE,CAAC,QAAQ,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;KAClD,CAAC;IACF,GAAG,EAAE,GAAG,CAAC;IACT,WAAW,EAAE,CAAC,IAAI,EAAE;QACnB,SAAS,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC;QAC/B,KAAK,EAAE,UAAU,EAAE,CAAC;KACpB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpB,WAAW,EAAE,CAAC,IAAI,EAAE;QACnB,SAAS,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC;KAC/B,KAAK,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IAC5B,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACxB,sCAAsC;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,yCAAyC;IACzC,OAAO,EAAE,UAAU,CAAC;IACpB;;;;;OAKG;IACH,yBAAyB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAChD,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACxB,sCAAsC;IACtC,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;OAOG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,yCAAyC;IACzC,OAAO,EAAE,UAAU,CAAC;IACpB;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC/B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,KAAK;IAC/D,WAAW,EAAE,MAAM,IAAI,CAAC;CACxB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"api.js","sourceRoot":"/","sources":["project/api.ts"],"names":[],"mappings":"","sourcesContent":["import type { Kysely } from \"kysely\";\nimport type { InlangDatabaseSchema } from \"../database/schema.js\";\nimport type { InlangPlugin } from \"../plugin/schema.js\";\nimport type { ProjectSettings } from \"../json-schema/settings.js\";\nimport type { Lix } from \"@lix-js/sdk\";\nimport type { SqliteWasmDatabase } from \"sqlite-wasm-kysely\";\n\nexport type InlangProject = {\n\tdb: Kysely<InlangDatabaseSchema>;\n\t/**\n\t * @deprecated Don't use this. Only an internal hack to unblock\n\t * fink v2.\n\t *\n\t * TODO remove this\n\t */\n\t_sqlite: SqliteWasmDatabase;\n\tid: {\n\t\t/**\n\t\t * Stable for packed `.inlang` files. For unpacked projects loaded from a\n\t\t * directory, the id is unstable because `project_id` is not persisted.\n\t\t */\n\t\tget: () => Promise<string>;\n\t};\n\tplugins: {\n\t\tget: () => Promise<readonly InlangPlugin[]>;\n\t};\n\terrors: {\n\t\tget: () => Promise<readonly Error[]>;\n\t};\n\tsettings: {\n\t\tget: () => Promise<ProjectSettings>;\n\t\tset: (settings: ProjectSettings) => Promise<void>;\n\t};\n\tlix: Lix;\n\timportFiles: (args: {\n\t\tpluginKey: InlangPlugin[\"key\"];\n\t\tfiles: ImportFile[];\n\t}) => Promise<void>;\n\texportFiles: (args: {\n\t\tpluginKey: InlangPlugin[\"key\"];\n\t}) => Promise<ExportFile[]>;\n\tclose: () => Promise<void>;\n\ttoBlob: () => Promise<Blob>;\n};\n\nexport type ImportFile = {\n\t/** The locale of the resource file */\n\tlocale: string;\n\t/** The binary content of the resource */\n\tcontent: Uint8Array;\n\t/**\n\t * The metadata of the file to be imported.\n\t *\n\t * Used to store additional information that is accessible in `importFiles` via `toBeImportedFilesMetadata`.\n\t * https://github.com/opral/inlang/issues/218\n\t */\n\ttoBeImportedFilesMetadata?: Record<string, any>;\n};\n\nexport type ExportFile = {\n\t/** The locale of the resource file */\n\tlocale: string;\n\t/**\n\t * The name of the file.\n\t *\n\t * @example\n\t * \"en.json\"\n\t * \"common-de.json\"\n\t *\n\t */\n\tname: string;\n\t/** The binary content of the resource */\n\tcontent: Uint8Array;\n};\n\n/**\n * Minimal RxJS compatible (generic) subscription type.\n */\nexport type Subscription<T> = (callback: (value: T) => void) => {\n\tunsubscribe: () => void;\n};\n"]}
1
+ {"version":3,"file":"api.js","sourceRoot":"/","sources":["project/api.ts"],"names":[],"mappings":"","sourcesContent":["import type { Kysely } from \"kysely\";\nimport type { InlangDatabaseSchema } from \"../database/schema.js\";\nimport type { InlangPlugin } from \"../plugin/schema.js\";\nimport type { ProjectSettings } from \"../json-schema/settings.js\";\nimport type { Lix } from \"@lix-js/sdk\";\nimport type { SqliteWasmDatabase } from \"sqlite-wasm-kysely\";\n\nexport type InlangProject = {\n\tdb: Kysely<InlangDatabaseSchema>;\n\t/**\n\t * @deprecated Don't use this. Only an internal hack to unblock\n\t * fink v2.\n\t *\n\t * TODO remove this\n\t */\n\t_sqlite: SqliteWasmDatabase;\n\tid: {\n\t\t/**\n\t\t * Stable for packed `.inlang` files. For unpacked projects loaded from a\n\t\t * directory, the id is unstable because `project_id` is not persisted.\n\t\t */\n\t\tget: () => Promise<string>;\n\t};\n\tplugins: {\n\t\tget: () => Promise<readonly InlangPlugin[]>;\n\t};\n\terrors: {\n\t\tget: () => Promise<readonly Error[]>;\n\t};\n\tsettings: {\n\t\tget: () => Promise<ProjectSettings>;\n\t\tset: (settings: ProjectSettings) => Promise<void>;\n\t};\n\tlix: Lix;\n\timportFiles: (args: {\n\t\tpluginKey: InlangPlugin[\"key\"];\n\t\tfiles: ImportFile[];\n\t}) => Promise<void>;\n\texportFiles: (args: {\n\t\tpluginKey: InlangPlugin[\"key\"];\n\t}) => Promise<ExportFile[]>;\n\tclose: () => Promise<void>;\n\ttoBlob: () => Promise<Blob>;\n};\n\nexport type ImportFile = {\n\t/** The locale of the resource file */\n\tlocale: string;\n\t/** The binary content of the resource */\n\tcontent: Uint8Array;\n\t/**\n\t * The metadata of the file to be imported.\n\t *\n\t * Used to store additional information that is accessible in `importFiles` via `toBeImportedFilesMetadata`.\n\t * https://github.com/opral/inlang/issues/218\n\t */\n\ttoBeImportedFilesMetadata?: Record<string, any>;\n};\n\nexport type ExportFile = {\n\t/** The locale of the resource file */\n\tlocale: string;\n\t/**\n\t * The name of the file.\n\t *\n\t * @example\n\t * \"en.json\"\n\t * \"common-de.json\"\n\t *\n\t */\n\tname: string;\n\t/** The binary content of the resource */\n\tcontent: Uint8Array;\n\t/**\n\t * Metadata of the exported file.\n\t *\n\t * The counterpart of `ImportFile.toBeImportedFilesMetadata`. Plugins can\n\t * use it to pass information to the writer. For example, a plugin that\n\t * supports a namespaced `pathPattern` (`Record<namespace, pattern>`)\n\t * provides `{ namespace }` so that `saveProjectToDirectory` can resolve\n\t * the pattern each exported file belongs to. Plugins can also provide\n\t * `{ pathPattern }` to override the configured pattern for one file.\n\t *\n\t * https://github.com/opral/inlang/issues/4356\n\t */\n\tmetadata?: Record<string, any>;\n};\n\n/**\n * Minimal RxJS compatible (generic) subscription type.\n */\nexport type Subscription<T> = (callback: (value: T) => void) => {\n\tunsubscribe: () => void;\n};\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"saveProjectToDirectory.d.ts","sourceRoot":"/","sources":["project/saveProjectToDirectory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,SAAS,CAAC;AAClC,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAmB9C,KAAK,aAAa,GAAG,OAAO,EAAE,GAAG,OAAO,MAAM,CAAC;AA2B/C;;;;;;;;;;;;GAYG;AACH,wBAAsB,sBAAsB,CAAC,IAAI,EAAE;IAClD;;;;OAIG;IACH,EAAE,EAAE,aAAa,CAAC;IAClB;;OAEG;IACH,OAAO,EAAE,aAAa,CAAC;IACvB;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACxB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsJhB"}
1
+ {"version":3,"file":"saveProjectToDirectory.d.ts","sourceRoot":"/","sources":["project/saveProjectToDirectory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,SAAS,CAAC;AAClC,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAmB9C,KAAK,aAAa,GAAG,OAAO,EAAE,GAAG,OAAO,MAAM,CAAC;AA2B/C;;;;;;;;;;;;GAYG;AACH,wBAAsB,sBAAsB,CAAC,IAAI,EAAE;IAClD;;;;OAIG;IACH,EAAE,EAAE,aAAa,CAAC;IAClB;;OAEG;IACH,OAAO,EAAE,aAAa,CAAC;IACvB;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACxB,GAAG,OAAO,CAAC,IAAI,CAAC,CA0KhB"}
@@ -123,15 +123,37 @@ export async function saveProjectToDirectory(args) {
123
123
  });
124
124
  for (const file of files) {
125
125
  const pathPattern = settings[plugin.key]?.pathPattern;
126
- // We need to check if pathPattern is a string or an array of strings
127
- // and handle both cases.
128
- const formattedPathPatterns = Array.isArray(pathPattern)
129
- ? pathPattern
130
- : [pathPattern];
131
- for (const pathPattern of formattedPathPatterns) {
132
- const p = pathPattern
133
- ? absolutePathFromProject(args.path, pathPattern.replace(/\{(languageTag|locale)\}/g, file.locale))
134
- : absolutePathFromProject(args.path, file.name);
126
+ const resolvePattern = (pattern) => absolutePathFromProject(args.path, pattern.replace(/\{(languageTag|locale)\}/g, file.locale));
127
+ // pathPattern can be a string, an array of strings, or a record
128
+ // mapping namespaces to patterns (e.g. plugin-i18next).
129
+ // https://github.com/opral/inlang/issues/4356
130
+ let targetPaths;
131
+ if (typeof file.metadata?.["pathPattern"] === "string") {
132
+ targetPaths = [resolvePattern(file.metadata["pathPattern"])];
133
+ }
134
+ else if (typeof pathPattern === "string") {
135
+ targetPaths = [resolvePattern(pathPattern)];
136
+ }
137
+ else if (Array.isArray(pathPattern)) {
138
+ // an empty array writes nothing
139
+ targetPaths = pathPattern.map(resolvePattern);
140
+ }
141
+ else if (typeof pathPattern === "object" && pathPattern !== null) {
142
+ const namespace = file.metadata?.["namespace"];
143
+ const namespacePattern = namespace
144
+ ? pathPattern[namespace]
145
+ : undefined;
146
+ // no pattern for this file (plugin didn't provide namespace
147
+ // metadata or the namespace is unknown) -> fall back to file.name
148
+ targetPaths =
149
+ typeof namespacePattern === "string"
150
+ ? [resolvePattern(namespacePattern)]
151
+ : [absolutePathFromProject(args.path, file.name)];
152
+ }
153
+ else {
154
+ targetPaths = [absolutePathFromProject(args.path, file.name)];
155
+ }
156
+ for (const p of targetPaths) {
135
157
  await fsModule.mkdir(path.dirname(p), { recursive: true });
136
158
  if (p.endsWith(".json")) {
137
159
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"saveProjectToDirectory.js","sourceRoot":"/","sources":["project/saveProjectToDirectory.ts"],"names":[],"mappings":"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","sourcesContent":["import type nodeFs from \"node:fs\";\nimport type fs from \"node:fs/promises\";\nimport type { InlangProject } from \"./api.js\";\nimport path from \"node:path\";\nimport { toMessageV1 } from \"../json-schema/old-v1-message/toMessageV1.js\";\nimport { absolutePathFromProject, withAbsolutePaths } from \"./path-helpers.js\";\nimport { detectJsonFormatting } from \"../utilities/detectJsonFormatting.js\";\nimport { selectBundleNested } from \"../query-utilities/selectBundleNested.js\";\nimport { README_CONTENT } from \"./README_CONTENT.js\";\nimport { ENV_VARIABLES } from \"../services/env-variables/index.js\";\nimport { compareSemver, pickHighestVersion, readProjectMeta } from \"./meta.js\";\n\nasync function fileExists(fsModule: typeof fs, filePath: string) {\n\ttry {\n\t\tawait fsModule.stat(filePath);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\ntype SaveProjectFs = typeof fs | typeof nodeFs;\n\nfunction getPromisesFs(fsModule: SaveProjectFs): typeof fs {\n\treturn \"promises\" in fsModule ? fsModule.promises : fsModule;\n}\n\nasync function assertTranslationDataCanBeExported(project: InlangProject) {\n\tconst plugins = await project.plugins.get();\n\tconst hasExporter = plugins.some(\n\t\t(plugin) => plugin.exportFiles || plugin.saveMessages\n\t);\n\tif (hasExporter) {\n\t\treturn;\n\t}\n\n\tconst [bundle, message, variant] = await Promise.all([\n\t\tproject.db.selectFrom(\"bundle\").select(\"id\").limit(1).executeTakeFirst(),\n\t\tproject.db.selectFrom(\"message\").select(\"id\").limit(1).executeTakeFirst(),\n\t\tproject.db.selectFrom(\"variant\").select(\"id\").limit(1).executeTakeFirst(),\n\t]);\n\tif (bundle || message || variant) {\n\t\tthrow new Error(\n\t\t\t\"saveProjectToDirectory cannot write bundles, messages, or variants without an import/export plugin. Add a plugin to settings.modules/providePlugins, or save the canonical .inlang file with project.toBlob().\"\n\t\t);\n\t}\n}\n\n/**\n * Saves a project to a directory.\n *\n * Writes all project files to disk and runs exporters to generate\n * resource files (e.g., JSON translation files).\n *\n * @example\n * await saveProjectToDirectory({\n * fs: await import(\"node:fs\"),\n * project,\n * path: \"./project.inlang\",\n * });\n */\nexport async function saveProjectToDirectory(args: {\n\t/**\n\t * The file system module to use for writing files.\n\t *\n\t * Accepts either `node:fs` or `node:fs/promises`.\n\t */\n\tfs: SaveProjectFs;\n\t/**\n\t * The inlang project to save.\n\t */\n\tproject: InlangProject;\n\t/**\n\t * The path to the inlang project directory. Must end with `.inlang`.\n\t */\n\tpath: string;\n\t/**\n\t * If `true`, skips running exporters and only writes internal project files.\n\t *\n\t * Useful when you only want to update project metadata without\n\t * regenerating resource files.\n\t */\n\tskipExporting?: boolean;\n}): Promise<void> {\n\tif (args.path.endsWith(\".inlang\") === false) {\n\t\tthrow new Error(\"The path must end with .inlang\");\n\t}\n\tif (!args.skipExporting) {\n\t\tawait assertTranslationDataCanBeExported(args.project);\n\t}\n\tconst fsModule = getPromisesFs(args.fs);\n\n\tconst files = await args.project.lix.db\n\t\t.selectFrom(\"file\")\n\t\t.selectAll()\n\t\t.execute();\n\n\tconst gitignoreContent = new TextEncoder().encode(\n\t\t\"# IF GIT SHOWED THAT THIS FILE CHANGED\\n#\\n# 1. RUN THE FOLLOWING COMMAND\\n#\\n# ---\\n# git rm --cached '**/*.inlang/.gitignore'\\n# ---\\n#\\n# 2. COMMIT THE CHANGE\\n#\\n# ---\\n# git commit -m \\\"fix: remove tracked .gitignore from inlang project\\\"\\n# ---\\n#\\n# Inlang handles the gitignore itself starting with version ^2.5.\\n#\\n# everything is ignored except settings.json\\n*\\n!settings.json\"\n\t);\n\n\tconst existingMeta = await readProjectMeta({\n\t\tfs: fsModule,\n\t\tprojectPath: args.path,\n\t});\n\tconst highestSdkVersion =\n\t\tpickHighestVersion([\n\t\t\texistingMeta?.highestSdkVersion,\n\t\t\tENV_VARIABLES.SDK_VERSION,\n\t\t]) ?? ENV_VARIABLES.SDK_VERSION;\n\tconst shouldWriteMetadata = (() => {\n\t\tconst comparison = compareSemver(\n\t\t\thighestSdkVersion,\n\t\t\tENV_VARIABLES.SDK_VERSION\n\t\t);\n\t\treturn comparison === null || comparison <= 0;\n\t})();\n\tconst readmePath = path.join(args.path, \"README.md\");\n\tconst gitignorePath = path.join(args.path, \".gitignore\");\n\tconst shouldWriteReadme =\n\t\tshouldWriteMetadata || !(await fileExists(fsModule, readmePath));\n\tconst shouldWriteGitignore =\n\t\tshouldWriteMetadata || !(await fileExists(fsModule, gitignorePath));\n\n\t// write all files to the directory\n\tfor (const file of files) {\n\t\tif (file.path.endsWith(\"db.sqlite\") || file.path === \"/project_id\") {\n\t\t\tcontinue;\n\t\t}\n\t\tconst p = path.join(args.path, file.path);\n\t\tawait fsModule.mkdir(path.dirname(p), { recursive: true });\n\t\tawait fsModule.writeFile(p, new Uint8Array(file.data));\n\t}\n\n\tif (shouldWriteGitignore) {\n\t\tawait fsModule.writeFile(gitignorePath, gitignoreContent);\n\t}\n\n\tif (shouldWriteReadme) {\n\t\t// Write README.md for coding agents\n\t\tawait fsModule.writeFile(\n\t\t\treadmePath,\n\t\t\tnew TextEncoder().encode(README_CONTENT)\n\t\t);\n\t}\n\n\tif (shouldWriteMetadata) {\n\t\tconst metaContent = JSON.stringify({ highestSdkVersion }, null, 2);\n\t\tawait fsModule.writeFile(\n\t\t\tpath.join(args.path, \".meta.json\"),\n\t\t\tnew TextEncoder().encode(metaContent)\n\t\t);\n\t}\n\n\tif (args.skipExporting) {\n\t\treturn;\n\t}\n\n\t// run exporters\n\tconst plugins = await args.project.plugins.get();\n\tconst settings = await args.project.settings.get();\n\n\tfor (const plugin of plugins) {\n\t\tif (plugin.exportFiles) {\n\t\t\tconst bundles = await args.project.db\n\t\t\t\t.selectFrom(\"bundle\")\n\t\t\t\t.selectAll()\n\t\t\t\t.execute();\n\t\t\tconst messages = await args.project.db\n\t\t\t\t.selectFrom(\"message\")\n\t\t\t\t.selectAll()\n\t\t\t\t.execute();\n\t\t\tconst variants = await args.project.db\n\t\t\t\t.selectFrom(\"variant\")\n\t\t\t\t.selectAll()\n\t\t\t\t.execute();\n\t\t\tconst files = await plugin.exportFiles({\n\t\t\t\tbundles,\n\t\t\t\tmessages,\n\t\t\t\tvariants,\n\t\t\t\tsettings,\n\t\t\t});\n\t\t\tfor (const file of files) {\n\t\t\t\tconst pathPattern = settings[plugin.key]?.pathPattern;\n\n\t\t\t\t// We need to check if pathPattern is a string or an array of strings\n\t\t\t\t// and handle both cases.\n\t\t\t\tconst formattedPathPatterns = Array.isArray(pathPattern)\n\t\t\t\t\t? pathPattern\n\t\t\t\t\t: [pathPattern];\n\n\t\t\t\tfor (const pathPattern of formattedPathPatterns) {\n\t\t\t\t\tconst p = pathPattern\n\t\t\t\t\t\t? absolutePathFromProject(\n\t\t\t\t\t\t\t\targs.path,\n\t\t\t\t\t\t\t\tpathPattern.replace(/\\{(languageTag|locale)\\}/g, file.locale)\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t: absolutePathFromProject(args.path, file.name);\n\t\t\t\t\tawait fsModule.mkdir(path.dirname(p), { recursive: true });\n\t\t\t\t\tif (p.endsWith(\".json\")) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst existing = await fsModule.readFile(p, \"utf-8\");\n\t\t\t\t\t\t\tconst stringify = detectJsonFormatting(existing);\n\t\t\t\t\t\t\tawait fsModule.writeFile(\n\t\t\t\t\t\t\t\tp,\n\t\t\t\t\t\t\t\tnew TextEncoder().encode(\n\t\t\t\t\t\t\t\t\tstringify(JSON.parse(new TextDecoder().decode(file.content)))\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// write the file to disk (json doesn't exist yet)\n\t\t\t\t\t\t\t// yeah ugly duplication of write file but it works.\n\t\t\t\t\t\t\tawait fsModule.writeFile(p, new Uint8Array(file.content));\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tawait fsModule.writeFile(p, new Uint8Array(file.content));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// old legacy remove with v3\n\t\telse if (plugin.saveMessages) {\n\t\t\t// in-efficient re-qeuery but it's a legacy function that will be removed.\n\t\t\t// the effort of adjusting the code to not re-query is not worth it.\n\t\t\tconst bundlesNested = await selectBundleNested(args.project.db).execute();\n\t\t\tawait plugin.saveMessages({\n\t\t\t\tmessages: bundlesNested.map((b) => toMessageV1(b)),\n\t\t\t\t// @ts-expect-error - legacy\n\t\t\t\tnodeishFs: withAbsolutePaths(fsModule, args.path),\n\t\t\t\tsettings,\n\t\t\t});\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"saveProjectToDirectory.js","sourceRoot":"/","sources":["project/saveProjectToDirectory.ts"],"names":[],"mappings":"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","sourcesContent":["import type nodeFs from \"node:fs\";\nimport type fs from \"node:fs/promises\";\nimport type { InlangProject } from \"./api.js\";\nimport path from \"node:path\";\nimport { toMessageV1 } from \"../json-schema/old-v1-message/toMessageV1.js\";\nimport { absolutePathFromProject, withAbsolutePaths } from \"./path-helpers.js\";\nimport { detectJsonFormatting } from \"../utilities/detectJsonFormatting.js\";\nimport { selectBundleNested } from \"../query-utilities/selectBundleNested.js\";\nimport { README_CONTENT } from \"./README_CONTENT.js\";\nimport { ENV_VARIABLES } from \"../services/env-variables/index.js\";\nimport { compareSemver, pickHighestVersion, readProjectMeta } from \"./meta.js\";\n\nasync function fileExists(fsModule: typeof fs, filePath: string) {\n\ttry {\n\t\tawait fsModule.stat(filePath);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\ntype SaveProjectFs = typeof fs | typeof nodeFs;\n\nfunction getPromisesFs(fsModule: SaveProjectFs): typeof fs {\n\treturn \"promises\" in fsModule ? fsModule.promises : fsModule;\n}\n\nasync function assertTranslationDataCanBeExported(project: InlangProject) {\n\tconst plugins = await project.plugins.get();\n\tconst hasExporter = plugins.some(\n\t\t(plugin) => plugin.exportFiles || plugin.saveMessages\n\t);\n\tif (hasExporter) {\n\t\treturn;\n\t}\n\n\tconst [bundle, message, variant] = await Promise.all([\n\t\tproject.db.selectFrom(\"bundle\").select(\"id\").limit(1).executeTakeFirst(),\n\t\tproject.db.selectFrom(\"message\").select(\"id\").limit(1).executeTakeFirst(),\n\t\tproject.db.selectFrom(\"variant\").select(\"id\").limit(1).executeTakeFirst(),\n\t]);\n\tif (bundle || message || variant) {\n\t\tthrow new Error(\n\t\t\t\"saveProjectToDirectory cannot write bundles, messages, or variants without an import/export plugin. Add a plugin to settings.modules/providePlugins, or save the canonical .inlang file with project.toBlob().\"\n\t\t);\n\t}\n}\n\n/**\n * Saves a project to a directory.\n *\n * Writes all project files to disk and runs exporters to generate\n * resource files (e.g., JSON translation files).\n *\n * @example\n * await saveProjectToDirectory({\n * fs: await import(\"node:fs\"),\n * project,\n * path: \"./project.inlang\",\n * });\n */\nexport async function saveProjectToDirectory(args: {\n\t/**\n\t * The file system module to use for writing files.\n\t *\n\t * Accepts either `node:fs` or `node:fs/promises`.\n\t */\n\tfs: SaveProjectFs;\n\t/**\n\t * The inlang project to save.\n\t */\n\tproject: InlangProject;\n\t/**\n\t * The path to the inlang project directory. Must end with `.inlang`.\n\t */\n\tpath: string;\n\t/**\n\t * If `true`, skips running exporters and only writes internal project files.\n\t *\n\t * Useful when you only want to update project metadata without\n\t * regenerating resource files.\n\t */\n\tskipExporting?: boolean;\n}): Promise<void> {\n\tif (args.path.endsWith(\".inlang\") === false) {\n\t\tthrow new Error(\"The path must end with .inlang\");\n\t}\n\tif (!args.skipExporting) {\n\t\tawait assertTranslationDataCanBeExported(args.project);\n\t}\n\tconst fsModule = getPromisesFs(args.fs);\n\n\tconst files = await args.project.lix.db\n\t\t.selectFrom(\"file\")\n\t\t.selectAll()\n\t\t.execute();\n\n\tconst gitignoreContent = new TextEncoder().encode(\n\t\t\"# IF GIT SHOWED THAT THIS FILE CHANGED\\n#\\n# 1. RUN THE FOLLOWING COMMAND\\n#\\n# ---\\n# git rm --cached '**/*.inlang/.gitignore'\\n# ---\\n#\\n# 2. COMMIT THE CHANGE\\n#\\n# ---\\n# git commit -m \\\"fix: remove tracked .gitignore from inlang project\\\"\\n# ---\\n#\\n# Inlang handles the gitignore itself starting with version ^2.5.\\n#\\n# everything is ignored except settings.json\\n*\\n!settings.json\"\n\t);\n\n\tconst existingMeta = await readProjectMeta({\n\t\tfs: fsModule,\n\t\tprojectPath: args.path,\n\t});\n\tconst highestSdkVersion =\n\t\tpickHighestVersion([\n\t\t\texistingMeta?.highestSdkVersion,\n\t\t\tENV_VARIABLES.SDK_VERSION,\n\t\t]) ?? ENV_VARIABLES.SDK_VERSION;\n\tconst shouldWriteMetadata = (() => {\n\t\tconst comparison = compareSemver(\n\t\t\thighestSdkVersion,\n\t\t\tENV_VARIABLES.SDK_VERSION\n\t\t);\n\t\treturn comparison === null || comparison <= 0;\n\t})();\n\tconst readmePath = path.join(args.path, \"README.md\");\n\tconst gitignorePath = path.join(args.path, \".gitignore\");\n\tconst shouldWriteReadme =\n\t\tshouldWriteMetadata || !(await fileExists(fsModule, readmePath));\n\tconst shouldWriteGitignore =\n\t\tshouldWriteMetadata || !(await fileExists(fsModule, gitignorePath));\n\n\t// write all files to the directory\n\tfor (const file of files) {\n\t\tif (file.path.endsWith(\"db.sqlite\") || file.path === \"/project_id\") {\n\t\t\tcontinue;\n\t\t}\n\t\tconst p = path.join(args.path, file.path);\n\t\tawait fsModule.mkdir(path.dirname(p), { recursive: true });\n\t\tawait fsModule.writeFile(p, new Uint8Array(file.data));\n\t}\n\n\tif (shouldWriteGitignore) {\n\t\tawait fsModule.writeFile(gitignorePath, gitignoreContent);\n\t}\n\n\tif (shouldWriteReadme) {\n\t\t// Write README.md for coding agents\n\t\tawait fsModule.writeFile(\n\t\t\treadmePath,\n\t\t\tnew TextEncoder().encode(README_CONTENT)\n\t\t);\n\t}\n\n\tif (shouldWriteMetadata) {\n\t\tconst metaContent = JSON.stringify({ highestSdkVersion }, null, 2);\n\t\tawait fsModule.writeFile(\n\t\t\tpath.join(args.path, \".meta.json\"),\n\t\t\tnew TextEncoder().encode(metaContent)\n\t\t);\n\t}\n\n\tif (args.skipExporting) {\n\t\treturn;\n\t}\n\n\t// run exporters\n\tconst plugins = await args.project.plugins.get();\n\tconst settings = await args.project.settings.get();\n\n\tfor (const plugin of plugins) {\n\t\tif (plugin.exportFiles) {\n\t\t\tconst bundles = await args.project.db\n\t\t\t\t.selectFrom(\"bundle\")\n\t\t\t\t.selectAll()\n\t\t\t\t.execute();\n\t\t\tconst messages = await args.project.db\n\t\t\t\t.selectFrom(\"message\")\n\t\t\t\t.selectAll()\n\t\t\t\t.execute();\n\t\t\tconst variants = await args.project.db\n\t\t\t\t.selectFrom(\"variant\")\n\t\t\t\t.selectAll()\n\t\t\t\t.execute();\n\t\t\tconst files = await plugin.exportFiles({\n\t\t\t\tbundles,\n\t\t\t\tmessages,\n\t\t\t\tvariants,\n\t\t\t\tsettings,\n\t\t\t});\n\t\t\tfor (const file of files) {\n\t\t\t\tconst pathPattern = settings[plugin.key]?.pathPattern;\n\n\t\t\t\tconst resolvePattern = (pattern: string) =>\n\t\t\t\t\tabsolutePathFromProject(\n\t\t\t\t\t\targs.path,\n\t\t\t\t\t\tpattern.replace(/\\{(languageTag|locale)\\}/g, file.locale)\n\t\t\t\t\t);\n\n\t\t\t\t// pathPattern can be a string, an array of strings, or a record\n\t\t\t\t// mapping namespaces to patterns (e.g. plugin-i18next).\n\t\t\t\t// https://github.com/opral/inlang/issues/4356\n\t\t\t\tlet targetPaths: string[];\n\t\t\t\tif (typeof file.metadata?.[\"pathPattern\"] === \"string\") {\n\t\t\t\t\ttargetPaths = [resolvePattern(file.metadata[\"pathPattern\"])];\n\t\t\t\t} else if (typeof pathPattern === \"string\") {\n\t\t\t\t\ttargetPaths = [resolvePattern(pathPattern)];\n\t\t\t\t} else if (Array.isArray(pathPattern)) {\n\t\t\t\t\t// an empty array writes nothing\n\t\t\t\t\ttargetPaths = pathPattern.map(resolvePattern);\n\t\t\t\t} else if (typeof pathPattern === \"object\" && pathPattern !== null) {\n\t\t\t\t\tconst namespace = file.metadata?.[\"namespace\"];\n\t\t\t\t\tconst namespacePattern = namespace\n\t\t\t\t\t\t? pathPattern[namespace]\n\t\t\t\t\t\t: undefined;\n\t\t\t\t\t// no pattern for this file (plugin didn't provide namespace\n\t\t\t\t\t// metadata or the namespace is unknown) -> fall back to file.name\n\t\t\t\t\ttargetPaths =\n\t\t\t\t\t\ttypeof namespacePattern === \"string\"\n\t\t\t\t\t\t\t? [resolvePattern(namespacePattern)]\n\t\t\t\t\t\t\t: [absolutePathFromProject(args.path, file.name)];\n\t\t\t\t} else {\n\t\t\t\t\ttargetPaths = [absolutePathFromProject(args.path, file.name)];\n\t\t\t\t}\n\n\t\t\t\tfor (const p of targetPaths) {\n\t\t\t\t\tawait fsModule.mkdir(path.dirname(p), { recursive: true });\n\t\t\t\t\tif (p.endsWith(\".json\")) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst existing = await fsModule.readFile(p, \"utf-8\");\n\t\t\t\t\t\t\tconst stringify = detectJsonFormatting(existing);\n\t\t\t\t\t\t\tawait fsModule.writeFile(\n\t\t\t\t\t\t\t\tp,\n\t\t\t\t\t\t\t\tnew TextEncoder().encode(\n\t\t\t\t\t\t\t\t\tstringify(JSON.parse(new TextDecoder().decode(file.content)))\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// write the file to disk (json doesn't exist yet)\n\t\t\t\t\t\t\t// yeah ugly duplication of write file but it works.\n\t\t\t\t\t\t\tawait fsModule.writeFile(p, new Uint8Array(file.content));\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tawait fsModule.writeFile(p, new Uint8Array(file.content));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// old legacy remove with v3\n\t\telse if (plugin.saveMessages) {\n\t\t\t// in-efficient re-qeuery but it's a legacy function that will be removed.\n\t\t\t// the effort of adjusting the code to not re-query is not worth it.\n\t\t\tconst bundlesNested = await selectBundleNested(args.project.db).execute();\n\t\t\tawait plugin.saveMessages({\n\t\t\t\tmessages: bundlesNested.map((b) => toMessageV1(b)),\n\t\t\t\t// @ts-expect-error - legacy\n\t\t\t\tnodeishFs: withAbsolutePaths(fsModule, args.path),\n\t\t\t\tsettings,\n\t\t\t});\n\t\t}\n\t}\n}\n"]}
@@ -99,6 +99,237 @@ test("creates exporter target directories from pathPattern", async () => {
99
99
  const exported = await volume.promises.readFile("/foo/messages/en.json", "utf-8");
100
100
  expect(JSON.parse(exported)).toEqual({ greeting: "Hi" });
101
101
  });
102
+ test("writes exported files to every pattern of a pathPattern array", async () => {
103
+ const volume = Volume.fromJSON({});
104
+ const mockPlugin = {
105
+ key: "mock",
106
+ exportFiles: async () => [
107
+ {
108
+ locale: "en",
109
+ name: "en.json",
110
+ content: new TextEncoder().encode(JSON.stringify({ greeting: "Hi" })),
111
+ },
112
+ ],
113
+ };
114
+ const project = await loadProjectInMemory({
115
+ blob: await newProject({
116
+ settings: {
117
+ baseLocale: "en",
118
+ locales: ["en"],
119
+ modules: [],
120
+ mock: {
121
+ pathPattern: ["./messages/{locale}.json", "./backup/{locale}.json"],
122
+ },
123
+ },
124
+ }),
125
+ providePlugins: [mockPlugin],
126
+ });
127
+ await saveProjectToDirectory({
128
+ fs: volume,
129
+ project,
130
+ path: "/foo/bar.inlang",
131
+ });
132
+ const messages = await volume.promises.readFile("/foo/messages/en.json", "utf-8");
133
+ const backup = await volume.promises.readFile("/foo/backup/en.json", "utf-8");
134
+ expect(JSON.parse(messages)).toEqual({ greeting: "Hi" });
135
+ expect(JSON.parse(backup)).toEqual({ greeting: "Hi" });
136
+ });
137
+ test("an empty pathPattern array writes nothing", async () => {
138
+ const volume = Volume.fromJSON({});
139
+ const mockPlugin = {
140
+ key: "mock",
141
+ exportFiles: async () => [
142
+ {
143
+ locale: "en",
144
+ name: "en.json",
145
+ content: new TextEncoder().encode(JSON.stringify({ greeting: "Hi" })),
146
+ },
147
+ ],
148
+ };
149
+ const project = await loadProjectInMemory({
150
+ blob: await newProject({
151
+ settings: {
152
+ baseLocale: "en",
153
+ locales: ["en"],
154
+ modules: [],
155
+ mock: {
156
+ pathPattern: [],
157
+ },
158
+ },
159
+ }),
160
+ providePlugins: [mockPlugin],
161
+ });
162
+ await saveProjectToDirectory({
163
+ fs: volume,
164
+ project,
165
+ path: "/foo/bar.inlang",
166
+ });
167
+ const files = await volume.promises.readdir("/foo");
168
+ expect(files).not.toContain("en.json");
169
+ });
170
+ // https://github.com/opral/inlang/issues/4356
171
+ test("resolves a namespaced pathPattern object via export file metadata", async () => {
172
+ const volume = Volume.fromJSON({});
173
+ const mockPlugin = {
174
+ key: "mock",
175
+ exportFiles: async () => [
176
+ {
177
+ locale: "en",
178
+ name: "common-en.json",
179
+ content: new TextEncoder().encode(JSON.stringify({ hello: "Hello" })),
180
+ metadata: { namespace: "common" },
181
+ },
182
+ {
183
+ locale: "en",
184
+ name: "app-en.json",
185
+ content: new TextEncoder().encode(JSON.stringify({ title: "My app" })),
186
+ metadata: { namespace: "app" },
187
+ },
188
+ ],
189
+ };
190
+ const project = await loadProjectInMemory({
191
+ blob: await newProject({
192
+ settings: {
193
+ baseLocale: "en",
194
+ locales: ["en"],
195
+ modules: [],
196
+ mock: {
197
+ pathPattern: {
198
+ common: "./{locale}/common.json",
199
+ app: "./{locale}/app.json",
200
+ },
201
+ },
202
+ },
203
+ }),
204
+ providePlugins: [mockPlugin],
205
+ });
206
+ await saveProjectToDirectory({
207
+ fs: volume,
208
+ project,
209
+ path: "/foo/bar.inlang",
210
+ });
211
+ const common = await volume.promises.readFile("/foo/en/common.json", "utf-8");
212
+ const app = await volume.promises.readFile("/foo/en/app.json", "utf-8");
213
+ expect(JSON.parse(common)).toEqual({ hello: "Hello" });
214
+ expect(JSON.parse(app)).toEqual({ title: "My app" });
215
+ });
216
+ test("resolves an export file pathPattern metadata override", async () => {
217
+ const volume = Volume.fromJSON({});
218
+ const mockPlugin = {
219
+ key: "mock",
220
+ exportFiles: async () => [
221
+ {
222
+ locale: "en",
223
+ name: "en.json",
224
+ content: new TextEncoder().encode(JSON.stringify({ hello: "Hello" })),
225
+ metadata: { pathPattern: "./main.json" },
226
+ },
227
+ {
228
+ locale: "de",
229
+ name: "de.json",
230
+ content: new TextEncoder().encode(JSON.stringify({ hello: "Hallo" })),
231
+ },
232
+ ],
233
+ };
234
+ const project = await loadProjectInMemory({
235
+ blob: await newProject({
236
+ settings: {
237
+ baseLocale: "en",
238
+ locales: ["en", "de"],
239
+ modules: [],
240
+ mock: {
241
+ pathPattern: "./{locale}.json",
242
+ },
243
+ },
244
+ }),
245
+ providePlugins: [mockPlugin],
246
+ });
247
+ await saveProjectToDirectory({
248
+ fs: volume,
249
+ project,
250
+ path: "/foo/bar.inlang",
251
+ });
252
+ const source = await volume.promises.readFile("/foo/main.json", "utf-8");
253
+ const target = await volume.promises.readFile("/foo/de.json", "utf-8");
254
+ expect(JSON.parse(source)).toEqual({ hello: "Hello" });
255
+ expect(JSON.parse(target)).toEqual({ hello: "Hallo" });
256
+ });
257
+ // old plugin versions don't provide namespace metadata. falling back to
258
+ // file.name is better than throwing "pathPattern.replace is not a function"
259
+ // https://github.com/opral/inlang/issues/4356
260
+ test("falls back to the file name when a namespaced pathPattern can't be resolved", async () => {
261
+ const volume = Volume.fromJSON({});
262
+ const mockPlugin = {
263
+ key: "mock",
264
+ exportFiles: async () => [
265
+ {
266
+ locale: "en",
267
+ name: "common-en.json",
268
+ content: new TextEncoder().encode(JSON.stringify({ hello: "Hello" })),
269
+ // no metadata, like plugin versions that predate ExportFile.metadata
270
+ },
271
+ ],
272
+ };
273
+ const project = await loadProjectInMemory({
274
+ blob: await newProject({
275
+ settings: {
276
+ baseLocale: "en",
277
+ locales: ["en"],
278
+ modules: [],
279
+ mock: {
280
+ pathPattern: {
281
+ common: "./{locale}/common.json",
282
+ },
283
+ },
284
+ },
285
+ }),
286
+ providePlugins: [mockPlugin],
287
+ });
288
+ await saveProjectToDirectory({
289
+ fs: volume,
290
+ project,
291
+ path: "/foo/bar.inlang",
292
+ });
293
+ const fallback = await volume.promises.readFile("/foo/common-en.json", "utf-8");
294
+ expect(JSON.parse(fallback)).toEqual({ hello: "Hello" });
295
+ });
296
+ // https://github.com/opral/inlang/issues/4356
297
+ test("falls back to the file name when the namespace is missing from the pathPattern object", async () => {
298
+ const volume = Volume.fromJSON({});
299
+ const mockPlugin = {
300
+ key: "mock",
301
+ exportFiles: async () => [
302
+ {
303
+ locale: "en",
304
+ name: "stray-en.json",
305
+ content: new TextEncoder().encode(JSON.stringify({ hello: "Hello" })),
306
+ metadata: { namespace: "stray" },
307
+ },
308
+ ],
309
+ };
310
+ const project = await loadProjectInMemory({
311
+ blob: await newProject({
312
+ settings: {
313
+ baseLocale: "en",
314
+ locales: ["en"],
315
+ modules: [],
316
+ mock: {
317
+ pathPattern: {
318
+ common: "./{locale}/common.json",
319
+ },
320
+ },
321
+ },
322
+ }),
323
+ providePlugins: [mockPlugin],
324
+ });
325
+ await saveProjectToDirectory({
326
+ fs: volume,
327
+ project,
328
+ path: "/foo/bar.inlang",
329
+ });
330
+ const fallback = await volume.promises.readFile("/foo/stray-en.json", "utf-8");
331
+ expect(JSON.parse(fallback)).toEqual({ hello: "Hello" });
332
+ });
102
333
  // Users were confused by project_id, and without sync a stable id is rarely needed.
103
334
  test("it should not write project_id to disk", async () => {
104
335
  const mockFs = Volume.fromJSON({
@@ -1 +1 @@
1
- {"version":3,"file":"saveProjectToDirectory.test.js","sourceRoot":"/","sources":["project/saveProjectToDirectory.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { test, expect, vi } from \"vitest\";\nimport { saveProjectToDirectory } from \"./saveProjectToDirectory.js\";\nimport { Volume } from \"memfs\";\nimport { loadProjectInMemory } from \"./loadProjectInMemory.js\";\nimport { newProject } from \"./newProject.js\";\nimport type { InlangPlugin } from \"../plugin/schema.js\";\nimport type { Bundle, NewMessage, Variant } from \"../database/schema.js\";\nimport { loadProjectFromDirectory } from \"./loadProjectFromDirectory.js\";\nimport { selectBundleNested } from \"../query-utilities/selectBundleNested.js\";\nimport type { ProjectSettings } from \"../json-schema/settings.js\";\nimport type { MessageV1 } from \"../json-schema/old-v1-message/schemaV1.js\";\nimport { ENV_VARIABLES } from \"../services/env-variables/index.js\";\n\ntest(\"it should throw if the path doesn't end with .inlang\", async () => {\n\tawait expect(() =>\n\t\tsaveProjectToDirectory({\n\t\t\tfs: {} as any,\n\t\t\tproject: {} as any,\n\t\t\tpath: \"/foo/bar\",\n\t\t})\n\t).rejects.toThrowError(\"The path must end with .inlang\");\n});\n\ntest(\"it should overwrite all files to the directory except the db.sqlite file\", async () => {\n\tconst mockFs = Volume.fromJSON({\n\t\t\"/foo/bar.inlang/settings.json\": JSON.stringify({\n\t\t\tbaseLocale: \"en\",\n\t\t\tlocales: [\"en\"],\n\t\t}),\n\t}).promises as any;\n\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject({\n\t\t\tsettings: {\n\t\t\t\tbaseLocale: \"en\",\n\t\t\t\tlocales: [\"en\", \"fr\", \"mock\"],\n\t\t\t},\n\t\t}),\n\t});\n\n\tawait saveProjectToDirectory({\n\t\tfs: mockFs,\n\t\tproject,\n\t\tpath: \"/foo/bar.inlang\",\n\t});\n\n\tconst files = await mockFs.readdir(\"/foo/bar.inlang\");\n\tconst updatedSettingsFile = await mockFs.readFile(\n\t\t\"/foo/bar.inlang/settings.json\",\n\t\t\"utf-8\"\n\t);\n\tconst updatedSettings = JSON.parse(updatedSettingsFile);\n\n\t// only testing known files at the time of the test.\n\t// this test should be updated for files that should NOT\n\t// be contained in the directory in the future\n\texpect(files).toContain(\"settings.json\");\n\texpect(files).not.toContain(\"db.sqlite\");\n\texpect(updatedSettings.baseLocale).toBe(\"en\");\n\texpect(updatedSettings.locales).toEqual([\"en\", \"fr\", \"mock\"]);\n});\n\ntest(\"accepts the node:fs style module with a promises namespace\", async () => {\n\tconst volume = Volume.fromJSON({});\n\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject({\n\t\t\tsettings: {\n\t\t\t\tbaseLocale: \"en\",\n\t\t\t\tlocales: [\"en\"],\n\t\t\t},\n\t\t}),\n\t});\n\n\tawait saveProjectToDirectory({\n\t\tfs: volume as any,\n\t\tproject,\n\t\tpath: \"/foo/bar.inlang\",\n\t});\n\n\tconst settings = await volume.promises.readFile(\n\t\t\"/foo/bar.inlang/settings.json\",\n\t\t\"utf-8\"\n\t);\n\texpect(JSON.parse(settings as string).locales).toEqual([\"en\"]);\n});\n\ntest(\"creates exporter target directories from pathPattern\", async () => {\n\tconst volume = Volume.fromJSON({});\n\tconst mockPlugin: InlangPlugin = {\n\t\tkey: \"mock\",\n\t\texportFiles: async () => [\n\t\t\t{\n\t\t\t\tlocale: \"en\",\n\t\t\t\tname: \"fallback.json\",\n\t\t\t\tcontent: new TextEncoder().encode(JSON.stringify({ greeting: \"Hi\" })),\n\t\t\t},\n\t\t],\n\t};\n\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject({\n\t\t\tsettings: {\n\t\t\t\tbaseLocale: \"en\",\n\t\t\t\tlocales: [\"en\"],\n\t\t\t\tmodules: [],\n\t\t\t\tmock: {\n\t\t\t\t\tpathPattern: \"./messages/{locale}.json\",\n\t\t\t\t},\n\t\t\t},\n\t\t}),\n\t\tprovidePlugins: [mockPlugin],\n\t});\n\n\tawait project.db\n\t\t.insertInto(\"bundle\")\n\t\t.values({ id: \"greeting\", declarations: [] })\n\t\t.execute();\n\n\tawait saveProjectToDirectory({\n\t\tfs: volume as any,\n\t\tproject,\n\t\tpath: \"/foo/bar.inlang\",\n\t});\n\n\tconst exported = await volume.promises.readFile(\n\t\t\"/foo/messages/en.json\",\n\t\t\"utf-8\"\n\t);\n\texpect(JSON.parse(exported as string)).toEqual({ greeting: \"Hi\" });\n});\n\n// Users were confused by project_id, and without sync a stable id is rarely needed.\ntest(\"it should not write project_id to disk\", async () => {\n\tconst mockFs = Volume.fromJSON({\n\t\t\"/foo/bar.inlang/settings.json\": JSON.stringify({\n\t\t\tbaseLocale: \"en\",\n\t\t\tlocales: [\"en\"],\n\t\t}),\n\t}).promises as any;\n\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject({\n\t\t\tsettings: {\n\t\t\t\tbaseLocale: \"en\",\n\t\t\t\tlocales: [\"en\", \"fr\"],\n\t\t\t},\n\t\t}),\n\t});\n\n\tawait saveProjectToDirectory({\n\t\tfs: mockFs,\n\t\tproject,\n\t\tpath: \"/foo/bar.inlang\",\n\t});\n\n\tconst files = await mockFs.readdir(\"/foo/bar.inlang\");\n\n\texpect(files).not.toContain(\"project_id\");\n});\n\ntest(\"a roundtrip should work\", async () => {\n\tconst bundles: Bundle[] = [{ id: \"mock-bundle\", declarations: [] }];\n\tconst messages: NewMessage[] = [{ bundleId: \"mock-bundle\", locale: \"en\" }];\n\tconst variants: Variant[] = [];\n\n\tconst volume = Volume.fromJSON({\n\t\t\"/mock-file.json\": JSON.stringify({ bundles, messages, variants }),\n\t});\n\n\tconst mockPlugin: InlangPlugin = {\n\t\tkey: \"mock-plugin\",\n\t\ttoBeImportedFiles: async () => {\n\t\t\treturn [{ path: \"/mock-file.json\", locale: \"mock\" }];\n\t\t},\n\t\timportFiles: async ({ files }) => {\n\t\t\tconst { bundles, messages, variants } = JSON.parse(\n\t\t\t\tnew TextDecoder().decode(files[0]?.content)\n\t\t\t);\n\t\t\treturn { bundles, messages, variants };\n\t\t},\n\t\texportFiles: async ({ bundles }) => {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tcontent: new TextEncoder().encode(JSON.stringify(bundles)),\n\t\t\t\t\tname: \"mock-file.json\",\n\t\t\t\t\tlocale: \"mock\",\n\t\t\t\t},\n\t\t\t];\n\t\t},\n\t};\n\n\tconst exportFilesSpy = vi.spyOn(mockPlugin, \"exportFiles\");\n\tconst importFilesSpy = vi.spyOn(mockPlugin, \"importFiles\");\n\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject(),\n\t\tprovidePlugins: [mockPlugin],\n\t});\n\n\tawait project.db.insertInto(\"bundle\").values(bundles).execute();\n\tawait project.db.insertInto(\"message\").values(messages).execute();\n\n\tawait saveProjectToDirectory({\n\t\tfs: volume.promises as any,\n\t\tproject,\n\t\tpath: \"/foo/bar.inlang\",\n\t});\n\n\t// const fileTree = volume.toJSON();\n\n\texpect(exportFilesSpy).toHaveBeenCalled();\n\texpect(importFilesSpy).not.toHaveBeenCalled();\n\n\t// TODO deactivated since mockBundleNested no longer contains the id of the messages\n\t// expect(fileTree).toEqual(\n\t// \texpect.objectContaining({\n\t// \t\t\"/foo/mock-file.json\": JSON.stringify([mockBundleNested]),\n\t// \t})\n\t// );\n\n\t// testing roundtrip\n\n\tconst project2 = await loadProjectFromDirectory({\n\t\tfs: volume as any,\n\t\tpath: \"/foo/bar.inlang\",\n\t\tprovidePlugins: [mockPlugin],\n\t});\n\n\texpect(mockPlugin.importFiles).toHaveBeenCalled();\n\n\tconst bundlesAfter = await project2.db\n\t\t.selectFrom(\"bundle\")\n\t\t.selectAll()\n\t\t.execute();\n\tconst messagesAfter = await project2.db\n\t\t.selectFrom(\"message\")\n\t\t.selectAll()\n\t\t.execute();\n\tconst variantsAfter = await project2.db\n\t\t.selectFrom(\"variant\")\n\t\t.selectAll()\n\t\t.execute();\n\n\texpect(bundlesAfter).lengthOf(1);\n\texpect(messagesAfter).lengthOf(1);\n\texpect(variantsAfter).lengthOf(0);\n\n\texpect(bundlesAfter[0]).toStrictEqual(expect.objectContaining(bundles[0]));\n\texpect(messagesAfter[0]).toStrictEqual(\n\t\texpect.objectContaining(messagesAfter[0])\n\t);\n});\n\ntest.todo(\n\t\"a roundtrip with legacy load and save messages should work\",\n\tasync () => {\n\t\tconst mockMessageV1: MessageV1 = {\n\t\t\tid: \"mock-legacy-message\",\n\t\t\talias: {},\n\t\t\tselectors: [],\n\t\t\tvariants: [\n\t\t\t\t{\n\t\t\t\t\tlanguageTag: \"en\",\n\t\t\t\t\tmatch: [],\n\t\t\t\t\tpattern: [{ type: \"Text\", value: \"Hello from legacy message\" }],\n\t\t\t\t},\n\t\t\t],\n\t\t};\n\n\t\tconst volume = Volume.fromJSON({\n\t\t\t\"/foo/bar.inlang/settings.json\": JSON.stringify({\n\t\t\t\tbaseLocale: \"en\",\n\t\t\t\tlocales: [\"en\"],\n\t\t\t} satisfies ProjectSettings),\n\t\t\t\"/foo/i18n/en.json\": JSON.stringify([mockMessageV1]),\n\t\t});\n\n\t\tconst mockPlugin: InlangPlugin = {\n\t\t\tid: \"mock-legacy-plugin\",\n\t\t\tkey: \"mock-legacy-plugin\",\n\t\t\tloadMessages: async ({ nodeishFs }) => {\n\t\t\t\t// expecting `loadMessages` to transform the relative path\n\t\t\t\t// to an absolute path `./i18n/en.json` -> `/foo/i18n/en.json`\n\t\t\t\tconst file = await nodeishFs.readFile(\"./i18n/en.json\", {\n\t\t\t\t\tencoding: \"utf-8\",\n\t\t\t\t});\n\t\t\t\treturn JSON.parse(file as string);\n\t\t\t},\n\t\t\tsaveMessages: async ({ messages, nodeishFs }) => {\n\t\t\t\tawait nodeishFs.writeFile(\n\t\t\t\t\t\"./i18n/en.json\",\n\t\t\t\t\tnew TextEncoder().encode(JSON.stringify(messages))\n\t\t\t\t\t\t.buffer as ArrayBuffer\n\t\t\t\t);\n\t\t\t},\n\t\t};\n\n\t\tconst loadMessagesSpy = vi.spyOn(mockPlugin, \"loadMessages\");\n\t\tconst saveMessagesSpy = vi.spyOn(mockPlugin, \"saveMessages\");\n\n\t\tconst project = await loadProjectFromDirectory({\n\t\t\tfs: volume as any,\n\t\t\tpath: \"/foo/bar.inlang\",\n\t\t\tprovidePlugins: [mockPlugin],\n\t\t});\n\n\t\texpect(loadMessagesSpy).toHaveBeenCalled();\n\t\texpect(saveMessagesSpy).not.toHaveBeenCalled();\n\n\t\tconst bundles1 = await selectBundleNested(project.db).execute();\n\n\t\texpect(bundles1[0]?.messages).lengthOf(1);\n\t\texpect(bundles1[0]?.messages[0]?.variants).toEqual([\n\t\t\texpect.objectContaining({\n\t\t\t\tpattern: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\tvalue: \"Hello from legacy message\",\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t}),\n\t\t]);\n\n\t\t// await project.db\n\t\t// \t.updateTable(\"variant\")\n\t\t// \t.set({\n\t\t// \t\tpattern: [{ type: \"text\", value: \"Updated message\" }],\n\t\t// \t})\n\t\t// \t.where(\"id\", \"=\", bundles1[0]?.messages[0]?.variants[0]?.id as string)\n\t\t// \t.execute();\n\n\t\t// testing the saveMessages function by removing the en.json file\n\t\tawait volume.promises.rm(\"/foo/i18n/en.json\");\n\n\t\tawait saveProjectToDirectory({\n\t\t\tfs: volume.promises as any,\n\t\t\tproject,\n\t\t\tpath: \"/foo/bar.inlang\",\n\t\t});\n\n\t\texpect(saveMessagesSpy).toHaveBeenCalled();\n\n\t\tconst fileTree = volume.toJSON();\n\t\tconst parsed = JSON.parse(fileTree[\"/foo/i18n/en.json\"] as string);\n\n\t\texpect(parsed).toEqual(expect.objectContaining([mockMessageV1]));\n\n\t\t// testing roundtrip\n\n\t\tconst project2 = await loadProjectFromDirectory({\n\t\t\tfs: volume as any,\n\t\t\tpath: \"/foo/bar.inlang\",\n\t\t\tprovidePlugins: [mockPlugin],\n\t\t});\n\n\t\tconst bundles2 = await selectBundleNested(project2.db).execute();\n\n\t\t// TODO deactivated since the ids must not be equal for separate imports - matching happens on language and matcher now\n\t\texpect(bundles1).toStrictEqual(bundles2);\n\t}\n);\n\ntest(\"it should preserve the formatting of existing json resource files\", async () => {\n\tconst mockJson =\n\t\tJSON.stringify(\n\t\t\t{ key: \"value\" },\n\t\t\tundefined,\n\t\t\t// tab spacing\n\t\t\t\"\\t\"\n\t\t) +\n\t\t// ends with new line\n\t\t\"\\n\";\n\n\tconst mockPlugin: InlangPlugin = {\n\t\tkey: \"mock\",\n\t\texportFiles: async () => {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tname: \"en.json\",\n\t\t\t\t\t// no beautified json\n\t\t\t\t\tcontent: new TextEncoder().encode(JSON.stringify({ key: \"value\" })),\n\t\t\t\t\tlocale: \"en\",\n\t\t\t\t},\n\t\t\t];\n\t\t},\n\t};\n\n\tconst volume = Volume.fromJSON({\n\t\t\"/foo/project.inlang/settings.json\": JSON.stringify({\n\t\t\tbaseLocale: \"en\",\n\t\t\tlocales: [\"en\"],\n\t\t} satisfies ProjectSettings),\n\t\t\"/foo/en.json\": mockJson,\n\t});\n\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject(),\n\t\tprovidePlugins: [mockPlugin],\n\t});\n\n\tawait saveProjectToDirectory({\n\t\tpath: \"/foo/project.inlang\",\n\t\tfs: volume.promises as any,\n\t\tproject,\n\t});\n\n\tconst fileAfterSave = await volume.promises.readFile(\"/foo/en.json\", \"utf-8\");\n\texpect(fileAfterSave).toBe(mockJson);\n});\n\ntest(\"adds a gitignore file if it doesn't exist\", async () => {\n\tconst fs = Volume.fromJSON({});\n\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject(),\n\t});\n\n\tawait saveProjectToDirectory({\n\t\tfs: fs.promises as any,\n\t\tproject,\n\t\tpath: \"/foo/bar.inlang\",\n\t});\n\n\tconst gitignore = await fs.promises.readFile(\n\t\t\"/foo/bar.inlang/.gitignore\",\n\t\t\"utf-8\"\n\t);\n\texpect(gitignore).toContain(\"*\");\n\texpect(gitignore).toContain(\"!settings.json\");\n});\n\ntest(\"emits a README.md file for coding agents\", async () => {\n\tconst fs = Volume.fromJSON({});\n\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject(),\n\t});\n\n\tawait saveProjectToDirectory({\n\t\tfs: fs.promises as any,\n\t\tproject,\n\t\tpath: \"/foo/bar.inlang\",\n\t});\n\n\tconst readme = await fs.promises.readFile(\n\t\t\"/foo/bar.inlang/README.md\",\n\t\t\"utf-8\"\n\t);\n\texpect(readme).toContain(\"## What is this folder?\");\n\texpect(readme).toContain(\"@inlang/sdk\");\n});\n\ntest(\"emits a .meta.json file with the sdk version\", async () => {\n\tconst fs = Volume.fromJSON({});\n\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject(),\n\t});\n\n\tawait saveProjectToDirectory({\n\t\tfs: fs.promises as any,\n\t\tproject,\n\t\tpath: \"/foo/bar.inlang\",\n\t});\n\n\tconst metaRaw = await fs.promises.readFile(\n\t\t\"/foo/bar.inlang/.meta.json\",\n\t\t\"utf-8\"\n\t);\n\tconst meta = JSON.parse(\n\t\ttypeof metaRaw === \"string\" ? metaRaw : metaRaw.toString()\n\t);\n\texpect(meta.highestSdkVersion).toBe(ENV_VARIABLES.SDK_VERSION);\n});\n\ntest(\"throws when saving translation data to a directory without an exporter plugin\", async () => {\n\tconst fs = Volume.fromJSON({});\n\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject(),\n\t});\n\n\tawait project.db\n\t\t.insertInto(\"bundle\")\n\t\t.values({ id: \"greeting\", declarations: [] })\n\t\t.execute();\n\n\tawait expect(\n\t\tsaveProjectToDirectory({\n\t\t\tfs: fs.promises as any,\n\t\t\tproject,\n\t\t\tpath: \"/foo/bar.inlang\",\n\t\t})\n\t).rejects.toThrow(\n\t\t\"saveProjectToDirectory cannot write bundles, messages, or variants without an import/export plugin\"\n\t);\n});\n\ntest(\"updates an existing README.md file\", async () => {\n\tconst fs = Volume.fromJSON({\n\t\t\"/foo/bar.inlang/README.md\": \"custom readme\",\n\t});\n\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject(),\n\t});\n\n\tawait saveProjectToDirectory({\n\t\tfs: fs.promises as any,\n\t\tproject,\n\t\tpath: \"/foo/bar.inlang\",\n\t});\n\n\tconst readme = await fs.promises.readFile(\n\t\t\"/foo/bar.inlang/README.md\",\n\t\t\"utf-8\"\n\t);\n\texpect(readme).not.toContain(\"custom readme\");\n});\n\ntest(\"does not overwrite README.md or .gitignore when meta has a higher sdk version\", async () => {\n\tconst fs = Volume.fromJSON({\n\t\t\"/foo/bar.inlang/.meta.json\": JSON.stringify({\n\t\t\thighestSdkVersion: \"99.0.0\",\n\t\t}),\n\t\t\"/foo/bar.inlang/README.md\": \"custom readme\",\n\t\t\"/foo/bar.inlang/.gitignore\": \"custom gitignore\",\n\t});\n\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject(),\n\t});\n\n\tawait saveProjectToDirectory({\n\t\tfs: fs.promises as any,\n\t\tproject,\n\t\tpath: \"/foo/bar.inlang\",\n\t});\n\n\tconst readme = await fs.promises.readFile(\n\t\t\"/foo/bar.inlang/README.md\",\n\t\t\"utf-8\"\n\t);\n\tconst gitignore = await fs.promises.readFile(\n\t\t\"/foo/bar.inlang/.gitignore\",\n\t\t\"utf-8\"\n\t);\n\tconst metaRaw = await fs.promises.readFile(\n\t\t\"/foo/bar.inlang/.meta.json\",\n\t\t\"utf-8\"\n\t);\n\tconst meta = JSON.parse(\n\t\ttypeof metaRaw === \"string\" ? metaRaw : metaRaw.toString()\n\t);\n\texpect(readme).toBe(\"custom readme\");\n\texpect(gitignore).toBe(\"custom gitignore\");\n\texpect(meta.highestSdkVersion).toBe(\"99.0.0\");\n});\n\ntest(\"recreates missing README.md and .gitignore when meta has a higher sdk version\", async () => {\n\tconst fs = Volume.fromJSON({\n\t\t\"/foo/bar.inlang/.meta.json\": JSON.stringify({\n\t\t\thighestSdkVersion: \"99.0.0\",\n\t\t}),\n\t});\n\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject(),\n\t});\n\n\tawait saveProjectToDirectory({\n\t\tfs: fs.promises as any,\n\t\tproject,\n\t\tpath: \"/foo/bar.inlang\",\n\t});\n\n\tconst readme = await fs.promises.readFile(\n\t\t\"/foo/bar.inlang/README.md\",\n\t\t\"utf-8\"\n\t);\n\tconst gitignore = await fs.promises.readFile(\n\t\t\"/foo/bar.inlang/.gitignore\",\n\t\t\"utf-8\"\n\t);\n\tconst metaRaw = await fs.promises.readFile(\n\t\t\"/foo/bar.inlang/.meta.json\",\n\t\t\"utf-8\"\n\t);\n\tconst meta = JSON.parse(\n\t\ttypeof metaRaw === \"string\" ? metaRaw : metaRaw.toString()\n\t);\n\n\texpect(readme).toContain(\"## What is this folder?\");\n\texpect(gitignore).toContain(\"*\");\n\texpect(gitignore).toContain(\"!settings.json\");\n\texpect(meta.highestSdkVersion).toBe(\"99.0.0\");\n});\n\ntest(\"README.md is gitignored\", async () => {\n\tconst fs = Volume.fromJSON({});\n\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject(),\n\t});\n\n\tawait saveProjectToDirectory({\n\t\tfs: fs.promises as any,\n\t\tproject,\n\t\tpath: \"/foo/bar.inlang\",\n\t});\n\n\tconst gitignore = await fs.promises.readFile(\n\t\t\"/foo/bar.inlang/.gitignore\",\n\t\t\"utf-8\"\n\t);\n\texpect(gitignore).toContain(\"# everything is ignored except settings.json\");\n\texpect(gitignore).toContain(\"*\");\n\texpect(gitignore).toContain(\"!settings.json\");\n\texpect(gitignore).not.toContain(\"!README.md\");\n});\n\ntest(\"overwrites existing .gitignore with generated entries\", async () => {\n\tconst fs = Volume.fromJSON({\n\t\t\"/foo/bar.inlang/.gitignore\": \"custom\\nnode_modules\",\n\t});\n\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject(),\n\t});\n\n\tawait saveProjectToDirectory({\n\t\tfs: fs.promises as any,\n\t\tproject,\n\t\tpath: \"/foo/bar.inlang\",\n\t});\n\n\tconst gitignore = await fs.promises.readFile(\n\t\t\"/foo/bar.inlang/.gitignore\",\n\t\t\"utf-8\"\n\t);\n\texpect(gitignore).toContain(\"*\");\n\texpect(gitignore).toContain(\"!settings.json\");\n});\n\ntest(\"uses exportFiles when both exportFiles and saveMessages are defined\", async () => {\n\tconst exportFilesSpy = vi.fn().mockResolvedValue([]);\n\tconst saveMessagesSpy = vi.fn();\n\tconst mockPlugin: InlangPlugin = {\n\t\tkey: \"mock\",\n\t\texportFiles: exportFilesSpy,\n\t\tsaveMessages: saveMessagesSpy,\n\t};\n\tconst volume = Volume.fromJSON({});\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject(),\n\t\tprovidePlugins: [mockPlugin],\n\t});\n\tawait saveProjectToDirectory({\n\t\tpath: \"/foo/project.inlang\",\n\t\tfs: volume.promises as any,\n\t\tproject,\n\t});\n\texpect(exportFilesSpy).toHaveBeenCalled();\n\texpect(saveMessagesSpy).not.toHaveBeenCalled();\n});\n\ntest(\"skipExporting prevents exporters from running\", async () => {\n\tconst exportFilesSpy = vi.fn().mockResolvedValue([]);\n\tconst saveMessagesSpy = vi.fn();\n\tconst mockPlugin: InlangPlugin = {\n\t\tkey: \"mock\",\n\t\texportFiles: exportFilesSpy,\n\t\tsaveMessages: saveMessagesSpy,\n\t};\n\tconst volume = Volume.fromJSON({});\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject(),\n\t\tprovidePlugins: [mockPlugin],\n\t});\n\tawait saveProjectToDirectory({\n\t\tpath: \"/foo/project.inlang\",\n\t\tfs: volume.promises as any,\n\t\tproject,\n\t\tskipExporting: true,\n\t});\n\texpect(exportFilesSpy).not.toHaveBeenCalled();\n\texpect(saveMessagesSpy).not.toHaveBeenCalled();\n});\n\ntest(\"uses saveMessages when exportFiles is not defined\", async () => {\n\tconst saveMessagesSpy = vi.fn().mockResolvedValue([]);\n\tconst mockPlugin: InlangPlugin = {\n\t\tkey: \"mock\",\n\t\tsaveMessages: saveMessagesSpy,\n\t};\n\tconst volume = Volume.fromJSON({});\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject(),\n\t\tprovidePlugins: [mockPlugin],\n\t});\n\tawait saveProjectToDirectory({\n\t\tpath: \"/foo/project.inlang\",\n\t\tfs: volume.promises as any,\n\t\tproject,\n\t});\n\texpect(saveMessagesSpy).toHaveBeenCalled();\n});\n"]}
1
+ {"version":3,"file":"saveProjectToDirectory.test.js","sourceRoot":"/","sources":["project/saveProjectToDirectory.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { test, expect, vi } from \"vitest\";\nimport { saveProjectToDirectory } from \"./saveProjectToDirectory.js\";\nimport { Volume } from \"memfs\";\nimport { loadProjectInMemory } from \"./loadProjectInMemory.js\";\nimport { newProject } from \"./newProject.js\";\nimport type { InlangPlugin } from \"../plugin/schema.js\";\nimport type { Bundle, NewMessage, Variant } from \"../database/schema.js\";\nimport { loadProjectFromDirectory } from \"./loadProjectFromDirectory.js\";\nimport { selectBundleNested } from \"../query-utilities/selectBundleNested.js\";\nimport type { ProjectSettings } from \"../json-schema/settings.js\";\nimport type { MessageV1 } from \"../json-schema/old-v1-message/schemaV1.js\";\nimport { ENV_VARIABLES } from \"../services/env-variables/index.js\";\n\ntest(\"it should throw if the path doesn't end with .inlang\", async () => {\n\tawait expect(() =>\n\t\tsaveProjectToDirectory({\n\t\t\tfs: {} as any,\n\t\t\tproject: {} as any,\n\t\t\tpath: \"/foo/bar\",\n\t\t})\n\t).rejects.toThrowError(\"The path must end with .inlang\");\n});\n\ntest(\"it should overwrite all files to the directory except the db.sqlite file\", async () => {\n\tconst mockFs = Volume.fromJSON({\n\t\t\"/foo/bar.inlang/settings.json\": JSON.stringify({\n\t\t\tbaseLocale: \"en\",\n\t\t\tlocales: [\"en\"],\n\t\t}),\n\t}).promises as any;\n\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject({\n\t\t\tsettings: {\n\t\t\t\tbaseLocale: \"en\",\n\t\t\t\tlocales: [\"en\", \"fr\", \"mock\"],\n\t\t\t},\n\t\t}),\n\t});\n\n\tawait saveProjectToDirectory({\n\t\tfs: mockFs,\n\t\tproject,\n\t\tpath: \"/foo/bar.inlang\",\n\t});\n\n\tconst files = await mockFs.readdir(\"/foo/bar.inlang\");\n\tconst updatedSettingsFile = await mockFs.readFile(\n\t\t\"/foo/bar.inlang/settings.json\",\n\t\t\"utf-8\"\n\t);\n\tconst updatedSettings = JSON.parse(updatedSettingsFile);\n\n\t// only testing known files at the time of the test.\n\t// this test should be updated for files that should NOT\n\t// be contained in the directory in the future\n\texpect(files).toContain(\"settings.json\");\n\texpect(files).not.toContain(\"db.sqlite\");\n\texpect(updatedSettings.baseLocale).toBe(\"en\");\n\texpect(updatedSettings.locales).toEqual([\"en\", \"fr\", \"mock\"]);\n});\n\ntest(\"accepts the node:fs style module with a promises namespace\", async () => {\n\tconst volume = Volume.fromJSON({});\n\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject({\n\t\t\tsettings: {\n\t\t\t\tbaseLocale: \"en\",\n\t\t\t\tlocales: [\"en\"],\n\t\t\t},\n\t\t}),\n\t});\n\n\tawait saveProjectToDirectory({\n\t\tfs: volume as any,\n\t\tproject,\n\t\tpath: \"/foo/bar.inlang\",\n\t});\n\n\tconst settings = await volume.promises.readFile(\n\t\t\"/foo/bar.inlang/settings.json\",\n\t\t\"utf-8\"\n\t);\n\texpect(JSON.parse(settings as string).locales).toEqual([\"en\"]);\n});\n\ntest(\"creates exporter target directories from pathPattern\", async () => {\n\tconst volume = Volume.fromJSON({});\n\tconst mockPlugin: InlangPlugin = {\n\t\tkey: \"mock\",\n\t\texportFiles: async () => [\n\t\t\t{\n\t\t\t\tlocale: \"en\",\n\t\t\t\tname: \"fallback.json\",\n\t\t\t\tcontent: new TextEncoder().encode(JSON.stringify({ greeting: \"Hi\" })),\n\t\t\t},\n\t\t],\n\t};\n\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject({\n\t\t\tsettings: {\n\t\t\t\tbaseLocale: \"en\",\n\t\t\t\tlocales: [\"en\"],\n\t\t\t\tmodules: [],\n\t\t\t\tmock: {\n\t\t\t\t\tpathPattern: \"./messages/{locale}.json\",\n\t\t\t\t},\n\t\t\t},\n\t\t}),\n\t\tprovidePlugins: [mockPlugin],\n\t});\n\n\tawait project.db\n\t\t.insertInto(\"bundle\")\n\t\t.values({ id: \"greeting\", declarations: [] })\n\t\t.execute();\n\n\tawait saveProjectToDirectory({\n\t\tfs: volume as any,\n\t\tproject,\n\t\tpath: \"/foo/bar.inlang\",\n\t});\n\n\tconst exported = await volume.promises.readFile(\n\t\t\"/foo/messages/en.json\",\n\t\t\"utf-8\"\n\t);\n\texpect(JSON.parse(exported as string)).toEqual({ greeting: \"Hi\" });\n});\n\ntest(\"writes exported files to every pattern of a pathPattern array\", async () => {\n\tconst volume = Volume.fromJSON({});\n\tconst mockPlugin: InlangPlugin = {\n\t\tkey: \"mock\",\n\t\texportFiles: async () => [\n\t\t\t{\n\t\t\t\tlocale: \"en\",\n\t\t\t\tname: \"en.json\",\n\t\t\t\tcontent: new TextEncoder().encode(JSON.stringify({ greeting: \"Hi\" })),\n\t\t\t},\n\t\t],\n\t};\n\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject({\n\t\t\tsettings: {\n\t\t\t\tbaseLocale: \"en\",\n\t\t\t\tlocales: [\"en\"],\n\t\t\t\tmodules: [],\n\t\t\t\tmock: {\n\t\t\t\t\tpathPattern: [\"./messages/{locale}.json\", \"./backup/{locale}.json\"],\n\t\t\t\t},\n\t\t\t},\n\t\t}),\n\t\tprovidePlugins: [mockPlugin],\n\t});\n\n\tawait saveProjectToDirectory({\n\t\tfs: volume as any,\n\t\tproject,\n\t\tpath: \"/foo/bar.inlang\",\n\t});\n\n\tconst messages = await volume.promises.readFile(\n\t\t\"/foo/messages/en.json\",\n\t\t\"utf-8\"\n\t);\n\tconst backup = await volume.promises.readFile(\"/foo/backup/en.json\", \"utf-8\");\n\texpect(JSON.parse(messages as string)).toEqual({ greeting: \"Hi\" });\n\texpect(JSON.parse(backup as string)).toEqual({ greeting: \"Hi\" });\n});\n\ntest(\"an empty pathPattern array writes nothing\", async () => {\n\tconst volume = Volume.fromJSON({});\n\tconst mockPlugin: InlangPlugin = {\n\t\tkey: \"mock\",\n\t\texportFiles: async () => [\n\t\t\t{\n\t\t\t\tlocale: \"en\",\n\t\t\t\tname: \"en.json\",\n\t\t\t\tcontent: new TextEncoder().encode(JSON.stringify({ greeting: \"Hi\" })),\n\t\t\t},\n\t\t],\n\t};\n\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject({\n\t\t\tsettings: {\n\t\t\t\tbaseLocale: \"en\",\n\t\t\t\tlocales: [\"en\"],\n\t\t\t\tmodules: [],\n\t\t\t\tmock: {\n\t\t\t\t\tpathPattern: [],\n\t\t\t\t},\n\t\t\t},\n\t\t}),\n\t\tprovidePlugins: [mockPlugin],\n\t});\n\n\tawait saveProjectToDirectory({\n\t\tfs: volume as any,\n\t\tproject,\n\t\tpath: \"/foo/bar.inlang\",\n\t});\n\n\tconst files = await volume.promises.readdir(\"/foo\");\n\texpect(files).not.toContain(\"en.json\");\n});\n\n// https://github.com/opral/inlang/issues/4356\ntest(\"resolves a namespaced pathPattern object via export file metadata\", async () => {\n\tconst volume = Volume.fromJSON({});\n\tconst mockPlugin: InlangPlugin = {\n\t\tkey: \"mock\",\n\t\texportFiles: async () => [\n\t\t\t{\n\t\t\t\tlocale: \"en\",\n\t\t\t\tname: \"common-en.json\",\n\t\t\t\tcontent: new TextEncoder().encode(JSON.stringify({ hello: \"Hello\" })),\n\t\t\t\tmetadata: { namespace: \"common\" },\n\t\t\t},\n\t\t\t{\n\t\t\t\tlocale: \"en\",\n\t\t\t\tname: \"app-en.json\",\n\t\t\t\tcontent: new TextEncoder().encode(JSON.stringify({ title: \"My app\" })),\n\t\t\t\tmetadata: { namespace: \"app\" },\n\t\t\t},\n\t\t],\n\t};\n\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject({\n\t\t\tsettings: {\n\t\t\t\tbaseLocale: \"en\",\n\t\t\t\tlocales: [\"en\"],\n\t\t\t\tmodules: [],\n\t\t\t\tmock: {\n\t\t\t\t\tpathPattern: {\n\t\t\t\t\t\tcommon: \"./{locale}/common.json\",\n\t\t\t\t\t\tapp: \"./{locale}/app.json\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}),\n\t\tprovidePlugins: [mockPlugin],\n\t});\n\n\tawait saveProjectToDirectory({\n\t\tfs: volume as any,\n\t\tproject,\n\t\tpath: \"/foo/bar.inlang\",\n\t});\n\n\tconst common = await volume.promises.readFile(\"/foo/en/common.json\", \"utf-8\");\n\tconst app = await volume.promises.readFile(\"/foo/en/app.json\", \"utf-8\");\n\texpect(JSON.parse(common as string)).toEqual({ hello: \"Hello\" });\n\texpect(JSON.parse(app as string)).toEqual({ title: \"My app\" });\n});\n\ntest(\"resolves an export file pathPattern metadata override\", async () => {\n\tconst volume = Volume.fromJSON({});\n\tconst mockPlugin: InlangPlugin = {\n\t\tkey: \"mock\",\n\t\texportFiles: async () => [\n\t\t\t{\n\t\t\t\tlocale: \"en\",\n\t\t\t\tname: \"en.json\",\n\t\t\t\tcontent: new TextEncoder().encode(JSON.stringify({ hello: \"Hello\" })),\n\t\t\t\tmetadata: { pathPattern: \"./main.json\" },\n\t\t\t},\n\t\t\t{\n\t\t\t\tlocale: \"de\",\n\t\t\t\tname: \"de.json\",\n\t\t\t\tcontent: new TextEncoder().encode(JSON.stringify({ hello: \"Hallo\" })),\n\t\t\t},\n\t\t],\n\t};\n\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject({\n\t\t\tsettings: {\n\t\t\t\tbaseLocale: \"en\",\n\t\t\t\tlocales: [\"en\", \"de\"],\n\t\t\t\tmodules: [],\n\t\t\t\tmock: {\n\t\t\t\t\tpathPattern: \"./{locale}.json\",\n\t\t\t\t},\n\t\t\t},\n\t\t}),\n\t\tprovidePlugins: [mockPlugin],\n\t});\n\n\tawait saveProjectToDirectory({\n\t\tfs: volume as any,\n\t\tproject,\n\t\tpath: \"/foo/bar.inlang\",\n\t});\n\n\tconst source = await volume.promises.readFile(\"/foo/main.json\", \"utf-8\");\n\tconst target = await volume.promises.readFile(\"/foo/de.json\", \"utf-8\");\n\texpect(JSON.parse(source as string)).toEqual({ hello: \"Hello\" });\n\texpect(JSON.parse(target as string)).toEqual({ hello: \"Hallo\" });\n});\n\n// old plugin versions don't provide namespace metadata. falling back to\n// file.name is better than throwing \"pathPattern.replace is not a function\"\n// https://github.com/opral/inlang/issues/4356\ntest(\"falls back to the file name when a namespaced pathPattern can't be resolved\", async () => {\n\tconst volume = Volume.fromJSON({});\n\tconst mockPlugin: InlangPlugin = {\n\t\tkey: \"mock\",\n\t\texportFiles: async () => [\n\t\t\t{\n\t\t\t\tlocale: \"en\",\n\t\t\t\tname: \"common-en.json\",\n\t\t\t\tcontent: new TextEncoder().encode(JSON.stringify({ hello: \"Hello\" })),\n\t\t\t\t// no metadata, like plugin versions that predate ExportFile.metadata\n\t\t\t},\n\t\t],\n\t};\n\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject({\n\t\t\tsettings: {\n\t\t\t\tbaseLocale: \"en\",\n\t\t\t\tlocales: [\"en\"],\n\t\t\t\tmodules: [],\n\t\t\t\tmock: {\n\t\t\t\t\tpathPattern: {\n\t\t\t\t\t\tcommon: \"./{locale}/common.json\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}),\n\t\tprovidePlugins: [mockPlugin],\n\t});\n\n\tawait saveProjectToDirectory({\n\t\tfs: volume as any,\n\t\tproject,\n\t\tpath: \"/foo/bar.inlang\",\n\t});\n\n\tconst fallback = await volume.promises.readFile(\n\t\t\"/foo/common-en.json\",\n\t\t\"utf-8\"\n\t);\n\texpect(JSON.parse(fallback as string)).toEqual({ hello: \"Hello\" });\n});\n\n// https://github.com/opral/inlang/issues/4356\ntest(\"falls back to the file name when the namespace is missing from the pathPattern object\", async () => {\n\tconst volume = Volume.fromJSON({});\n\tconst mockPlugin: InlangPlugin = {\n\t\tkey: \"mock\",\n\t\texportFiles: async () => [\n\t\t\t{\n\t\t\t\tlocale: \"en\",\n\t\t\t\tname: \"stray-en.json\",\n\t\t\t\tcontent: new TextEncoder().encode(JSON.stringify({ hello: \"Hello\" })),\n\t\t\t\tmetadata: { namespace: \"stray\" },\n\t\t\t},\n\t\t],\n\t};\n\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject({\n\t\t\tsettings: {\n\t\t\t\tbaseLocale: \"en\",\n\t\t\t\tlocales: [\"en\"],\n\t\t\t\tmodules: [],\n\t\t\t\tmock: {\n\t\t\t\t\tpathPattern: {\n\t\t\t\t\t\tcommon: \"./{locale}/common.json\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}),\n\t\tprovidePlugins: [mockPlugin],\n\t});\n\n\tawait saveProjectToDirectory({\n\t\tfs: volume as any,\n\t\tproject,\n\t\tpath: \"/foo/bar.inlang\",\n\t});\n\n\tconst fallback = await volume.promises.readFile(\n\t\t\"/foo/stray-en.json\",\n\t\t\"utf-8\"\n\t);\n\texpect(JSON.parse(fallback as string)).toEqual({ hello: \"Hello\" });\n});\n\n// Users were confused by project_id, and without sync a stable id is rarely needed.\ntest(\"it should not write project_id to disk\", async () => {\n\tconst mockFs = Volume.fromJSON({\n\t\t\"/foo/bar.inlang/settings.json\": JSON.stringify({\n\t\t\tbaseLocale: \"en\",\n\t\t\tlocales: [\"en\"],\n\t\t}),\n\t}).promises as any;\n\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject({\n\t\t\tsettings: {\n\t\t\t\tbaseLocale: \"en\",\n\t\t\t\tlocales: [\"en\", \"fr\"],\n\t\t\t},\n\t\t}),\n\t});\n\n\tawait saveProjectToDirectory({\n\t\tfs: mockFs,\n\t\tproject,\n\t\tpath: \"/foo/bar.inlang\",\n\t});\n\n\tconst files = await mockFs.readdir(\"/foo/bar.inlang\");\n\n\texpect(files).not.toContain(\"project_id\");\n});\n\ntest(\"a roundtrip should work\", async () => {\n\tconst bundles: Bundle[] = [{ id: \"mock-bundle\", declarations: [] }];\n\tconst messages: NewMessage[] = [{ bundleId: \"mock-bundle\", locale: \"en\" }];\n\tconst variants: Variant[] = [];\n\n\tconst volume = Volume.fromJSON({\n\t\t\"/mock-file.json\": JSON.stringify({ bundles, messages, variants }),\n\t});\n\n\tconst mockPlugin: InlangPlugin = {\n\t\tkey: \"mock-plugin\",\n\t\ttoBeImportedFiles: async () => {\n\t\t\treturn [{ path: \"/mock-file.json\", locale: \"mock\" }];\n\t\t},\n\t\timportFiles: async ({ files }) => {\n\t\t\tconst { bundles, messages, variants } = JSON.parse(\n\t\t\t\tnew TextDecoder().decode(files[0]?.content)\n\t\t\t);\n\t\t\treturn { bundles, messages, variants };\n\t\t},\n\t\texportFiles: async ({ bundles }) => {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tcontent: new TextEncoder().encode(JSON.stringify(bundles)),\n\t\t\t\t\tname: \"mock-file.json\",\n\t\t\t\t\tlocale: \"mock\",\n\t\t\t\t},\n\t\t\t];\n\t\t},\n\t};\n\n\tconst exportFilesSpy = vi.spyOn(mockPlugin, \"exportFiles\");\n\tconst importFilesSpy = vi.spyOn(mockPlugin, \"importFiles\");\n\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject(),\n\t\tprovidePlugins: [mockPlugin],\n\t});\n\n\tawait project.db.insertInto(\"bundle\").values(bundles).execute();\n\tawait project.db.insertInto(\"message\").values(messages).execute();\n\n\tawait saveProjectToDirectory({\n\t\tfs: volume.promises as any,\n\t\tproject,\n\t\tpath: \"/foo/bar.inlang\",\n\t});\n\n\t// const fileTree = volume.toJSON();\n\n\texpect(exportFilesSpy).toHaveBeenCalled();\n\texpect(importFilesSpy).not.toHaveBeenCalled();\n\n\t// TODO deactivated since mockBundleNested no longer contains the id of the messages\n\t// expect(fileTree).toEqual(\n\t// \texpect.objectContaining({\n\t// \t\t\"/foo/mock-file.json\": JSON.stringify([mockBundleNested]),\n\t// \t})\n\t// );\n\n\t// testing roundtrip\n\n\tconst project2 = await loadProjectFromDirectory({\n\t\tfs: volume as any,\n\t\tpath: \"/foo/bar.inlang\",\n\t\tprovidePlugins: [mockPlugin],\n\t});\n\n\texpect(mockPlugin.importFiles).toHaveBeenCalled();\n\n\tconst bundlesAfter = await project2.db\n\t\t.selectFrom(\"bundle\")\n\t\t.selectAll()\n\t\t.execute();\n\tconst messagesAfter = await project2.db\n\t\t.selectFrom(\"message\")\n\t\t.selectAll()\n\t\t.execute();\n\tconst variantsAfter = await project2.db\n\t\t.selectFrom(\"variant\")\n\t\t.selectAll()\n\t\t.execute();\n\n\texpect(bundlesAfter).lengthOf(1);\n\texpect(messagesAfter).lengthOf(1);\n\texpect(variantsAfter).lengthOf(0);\n\n\texpect(bundlesAfter[0]).toStrictEqual(expect.objectContaining(bundles[0]));\n\texpect(messagesAfter[0]).toStrictEqual(\n\t\texpect.objectContaining(messagesAfter[0])\n\t);\n});\n\ntest.todo(\n\t\"a roundtrip with legacy load and save messages should work\",\n\tasync () => {\n\t\tconst mockMessageV1: MessageV1 = {\n\t\t\tid: \"mock-legacy-message\",\n\t\t\talias: {},\n\t\t\tselectors: [],\n\t\t\tvariants: [\n\t\t\t\t{\n\t\t\t\t\tlanguageTag: \"en\",\n\t\t\t\t\tmatch: [],\n\t\t\t\t\tpattern: [{ type: \"Text\", value: \"Hello from legacy message\" }],\n\t\t\t\t},\n\t\t\t],\n\t\t};\n\n\t\tconst volume = Volume.fromJSON({\n\t\t\t\"/foo/bar.inlang/settings.json\": JSON.stringify({\n\t\t\t\tbaseLocale: \"en\",\n\t\t\t\tlocales: [\"en\"],\n\t\t\t} satisfies ProjectSettings),\n\t\t\t\"/foo/i18n/en.json\": JSON.stringify([mockMessageV1]),\n\t\t});\n\n\t\tconst mockPlugin: InlangPlugin = {\n\t\t\tid: \"mock-legacy-plugin\",\n\t\t\tkey: \"mock-legacy-plugin\",\n\t\t\tloadMessages: async ({ nodeishFs }) => {\n\t\t\t\t// expecting `loadMessages` to transform the relative path\n\t\t\t\t// to an absolute path `./i18n/en.json` -> `/foo/i18n/en.json`\n\t\t\t\tconst file = await nodeishFs.readFile(\"./i18n/en.json\", {\n\t\t\t\t\tencoding: \"utf-8\",\n\t\t\t\t});\n\t\t\t\treturn JSON.parse(file as string);\n\t\t\t},\n\t\t\tsaveMessages: async ({ messages, nodeishFs }) => {\n\t\t\t\tawait nodeishFs.writeFile(\n\t\t\t\t\t\"./i18n/en.json\",\n\t\t\t\t\tnew TextEncoder().encode(JSON.stringify(messages))\n\t\t\t\t\t\t.buffer as ArrayBuffer\n\t\t\t\t);\n\t\t\t},\n\t\t};\n\n\t\tconst loadMessagesSpy = vi.spyOn(mockPlugin, \"loadMessages\");\n\t\tconst saveMessagesSpy = vi.spyOn(mockPlugin, \"saveMessages\");\n\n\t\tconst project = await loadProjectFromDirectory({\n\t\t\tfs: volume as any,\n\t\t\tpath: \"/foo/bar.inlang\",\n\t\t\tprovidePlugins: [mockPlugin],\n\t\t});\n\n\t\texpect(loadMessagesSpy).toHaveBeenCalled();\n\t\texpect(saveMessagesSpy).not.toHaveBeenCalled();\n\n\t\tconst bundles1 = await selectBundleNested(project.db).execute();\n\n\t\texpect(bundles1[0]?.messages).lengthOf(1);\n\t\texpect(bundles1[0]?.messages[0]?.variants).toEqual([\n\t\t\texpect.objectContaining({\n\t\t\t\tpattern: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\tvalue: \"Hello from legacy message\",\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t}),\n\t\t]);\n\n\t\t// await project.db\n\t\t// \t.updateTable(\"variant\")\n\t\t// \t.set({\n\t\t// \t\tpattern: [{ type: \"text\", value: \"Updated message\" }],\n\t\t// \t})\n\t\t// \t.where(\"id\", \"=\", bundles1[0]?.messages[0]?.variants[0]?.id as string)\n\t\t// \t.execute();\n\n\t\t// testing the saveMessages function by removing the en.json file\n\t\tawait volume.promises.rm(\"/foo/i18n/en.json\");\n\n\t\tawait saveProjectToDirectory({\n\t\t\tfs: volume.promises as any,\n\t\t\tproject,\n\t\t\tpath: \"/foo/bar.inlang\",\n\t\t});\n\n\t\texpect(saveMessagesSpy).toHaveBeenCalled();\n\n\t\tconst fileTree = volume.toJSON();\n\t\tconst parsed = JSON.parse(fileTree[\"/foo/i18n/en.json\"] as string);\n\n\t\texpect(parsed).toEqual(expect.objectContaining([mockMessageV1]));\n\n\t\t// testing roundtrip\n\n\t\tconst project2 = await loadProjectFromDirectory({\n\t\t\tfs: volume as any,\n\t\t\tpath: \"/foo/bar.inlang\",\n\t\t\tprovidePlugins: [mockPlugin],\n\t\t});\n\n\t\tconst bundles2 = await selectBundleNested(project2.db).execute();\n\n\t\t// TODO deactivated since the ids must not be equal for separate imports - matching happens on language and matcher now\n\t\texpect(bundles1).toStrictEqual(bundles2);\n\t}\n);\n\ntest(\"it should preserve the formatting of existing json resource files\", async () => {\n\tconst mockJson =\n\t\tJSON.stringify(\n\t\t\t{ key: \"value\" },\n\t\t\tundefined,\n\t\t\t// tab spacing\n\t\t\t\"\\t\"\n\t\t) +\n\t\t// ends with new line\n\t\t\"\\n\";\n\n\tconst mockPlugin: InlangPlugin = {\n\t\tkey: \"mock\",\n\t\texportFiles: async () => {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tname: \"en.json\",\n\t\t\t\t\t// no beautified json\n\t\t\t\t\tcontent: new TextEncoder().encode(JSON.stringify({ key: \"value\" })),\n\t\t\t\t\tlocale: \"en\",\n\t\t\t\t},\n\t\t\t];\n\t\t},\n\t};\n\n\tconst volume = Volume.fromJSON({\n\t\t\"/foo/project.inlang/settings.json\": JSON.stringify({\n\t\t\tbaseLocale: \"en\",\n\t\t\tlocales: [\"en\"],\n\t\t} satisfies ProjectSettings),\n\t\t\"/foo/en.json\": mockJson,\n\t});\n\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject(),\n\t\tprovidePlugins: [mockPlugin],\n\t});\n\n\tawait saveProjectToDirectory({\n\t\tpath: \"/foo/project.inlang\",\n\t\tfs: volume.promises as any,\n\t\tproject,\n\t});\n\n\tconst fileAfterSave = await volume.promises.readFile(\"/foo/en.json\", \"utf-8\");\n\texpect(fileAfterSave).toBe(mockJson);\n});\n\ntest(\"adds a gitignore file if it doesn't exist\", async () => {\n\tconst fs = Volume.fromJSON({});\n\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject(),\n\t});\n\n\tawait saveProjectToDirectory({\n\t\tfs: fs.promises as any,\n\t\tproject,\n\t\tpath: \"/foo/bar.inlang\",\n\t});\n\n\tconst gitignore = await fs.promises.readFile(\n\t\t\"/foo/bar.inlang/.gitignore\",\n\t\t\"utf-8\"\n\t);\n\texpect(gitignore).toContain(\"*\");\n\texpect(gitignore).toContain(\"!settings.json\");\n});\n\ntest(\"emits a README.md file for coding agents\", async () => {\n\tconst fs = Volume.fromJSON({});\n\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject(),\n\t});\n\n\tawait saveProjectToDirectory({\n\t\tfs: fs.promises as any,\n\t\tproject,\n\t\tpath: \"/foo/bar.inlang\",\n\t});\n\n\tconst readme = await fs.promises.readFile(\n\t\t\"/foo/bar.inlang/README.md\",\n\t\t\"utf-8\"\n\t);\n\texpect(readme).toContain(\"## What is this folder?\");\n\texpect(readme).toContain(\"@inlang/sdk\");\n});\n\ntest(\"emits a .meta.json file with the sdk version\", async () => {\n\tconst fs = Volume.fromJSON({});\n\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject(),\n\t});\n\n\tawait saveProjectToDirectory({\n\t\tfs: fs.promises as any,\n\t\tproject,\n\t\tpath: \"/foo/bar.inlang\",\n\t});\n\n\tconst metaRaw = await fs.promises.readFile(\n\t\t\"/foo/bar.inlang/.meta.json\",\n\t\t\"utf-8\"\n\t);\n\tconst meta = JSON.parse(\n\t\ttypeof metaRaw === \"string\" ? metaRaw : metaRaw.toString()\n\t);\n\texpect(meta.highestSdkVersion).toBe(ENV_VARIABLES.SDK_VERSION);\n});\n\ntest(\"throws when saving translation data to a directory without an exporter plugin\", async () => {\n\tconst fs = Volume.fromJSON({});\n\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject(),\n\t});\n\n\tawait project.db\n\t\t.insertInto(\"bundle\")\n\t\t.values({ id: \"greeting\", declarations: [] })\n\t\t.execute();\n\n\tawait expect(\n\t\tsaveProjectToDirectory({\n\t\t\tfs: fs.promises as any,\n\t\t\tproject,\n\t\t\tpath: \"/foo/bar.inlang\",\n\t\t})\n\t).rejects.toThrow(\n\t\t\"saveProjectToDirectory cannot write bundles, messages, or variants without an import/export plugin\"\n\t);\n});\n\ntest(\"updates an existing README.md file\", async () => {\n\tconst fs = Volume.fromJSON({\n\t\t\"/foo/bar.inlang/README.md\": \"custom readme\",\n\t});\n\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject(),\n\t});\n\n\tawait saveProjectToDirectory({\n\t\tfs: fs.promises as any,\n\t\tproject,\n\t\tpath: \"/foo/bar.inlang\",\n\t});\n\n\tconst readme = await fs.promises.readFile(\n\t\t\"/foo/bar.inlang/README.md\",\n\t\t\"utf-8\"\n\t);\n\texpect(readme).not.toContain(\"custom readme\");\n});\n\ntest(\"does not overwrite README.md or .gitignore when meta has a higher sdk version\", async () => {\n\tconst fs = Volume.fromJSON({\n\t\t\"/foo/bar.inlang/.meta.json\": JSON.stringify({\n\t\t\thighestSdkVersion: \"99.0.0\",\n\t\t}),\n\t\t\"/foo/bar.inlang/README.md\": \"custom readme\",\n\t\t\"/foo/bar.inlang/.gitignore\": \"custom gitignore\",\n\t});\n\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject(),\n\t});\n\n\tawait saveProjectToDirectory({\n\t\tfs: fs.promises as any,\n\t\tproject,\n\t\tpath: \"/foo/bar.inlang\",\n\t});\n\n\tconst readme = await fs.promises.readFile(\n\t\t\"/foo/bar.inlang/README.md\",\n\t\t\"utf-8\"\n\t);\n\tconst gitignore = await fs.promises.readFile(\n\t\t\"/foo/bar.inlang/.gitignore\",\n\t\t\"utf-8\"\n\t);\n\tconst metaRaw = await fs.promises.readFile(\n\t\t\"/foo/bar.inlang/.meta.json\",\n\t\t\"utf-8\"\n\t);\n\tconst meta = JSON.parse(\n\t\ttypeof metaRaw === \"string\" ? metaRaw : metaRaw.toString()\n\t);\n\texpect(readme).toBe(\"custom readme\");\n\texpect(gitignore).toBe(\"custom gitignore\");\n\texpect(meta.highestSdkVersion).toBe(\"99.0.0\");\n});\n\ntest(\"recreates missing README.md and .gitignore when meta has a higher sdk version\", async () => {\n\tconst fs = Volume.fromJSON({\n\t\t\"/foo/bar.inlang/.meta.json\": JSON.stringify({\n\t\t\thighestSdkVersion: \"99.0.0\",\n\t\t}),\n\t});\n\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject(),\n\t});\n\n\tawait saveProjectToDirectory({\n\t\tfs: fs.promises as any,\n\t\tproject,\n\t\tpath: \"/foo/bar.inlang\",\n\t});\n\n\tconst readme = await fs.promises.readFile(\n\t\t\"/foo/bar.inlang/README.md\",\n\t\t\"utf-8\"\n\t);\n\tconst gitignore = await fs.promises.readFile(\n\t\t\"/foo/bar.inlang/.gitignore\",\n\t\t\"utf-8\"\n\t);\n\tconst metaRaw = await fs.promises.readFile(\n\t\t\"/foo/bar.inlang/.meta.json\",\n\t\t\"utf-8\"\n\t);\n\tconst meta = JSON.parse(\n\t\ttypeof metaRaw === \"string\" ? metaRaw : metaRaw.toString()\n\t);\n\n\texpect(readme).toContain(\"## What is this folder?\");\n\texpect(gitignore).toContain(\"*\");\n\texpect(gitignore).toContain(\"!settings.json\");\n\texpect(meta.highestSdkVersion).toBe(\"99.0.0\");\n});\n\ntest(\"README.md is gitignored\", async () => {\n\tconst fs = Volume.fromJSON({});\n\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject(),\n\t});\n\n\tawait saveProjectToDirectory({\n\t\tfs: fs.promises as any,\n\t\tproject,\n\t\tpath: \"/foo/bar.inlang\",\n\t});\n\n\tconst gitignore = await fs.promises.readFile(\n\t\t\"/foo/bar.inlang/.gitignore\",\n\t\t\"utf-8\"\n\t);\n\texpect(gitignore).toContain(\"# everything is ignored except settings.json\");\n\texpect(gitignore).toContain(\"*\");\n\texpect(gitignore).toContain(\"!settings.json\");\n\texpect(gitignore).not.toContain(\"!README.md\");\n});\n\ntest(\"overwrites existing .gitignore with generated entries\", async () => {\n\tconst fs = Volume.fromJSON({\n\t\t\"/foo/bar.inlang/.gitignore\": \"custom\\nnode_modules\",\n\t});\n\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject(),\n\t});\n\n\tawait saveProjectToDirectory({\n\t\tfs: fs.promises as any,\n\t\tproject,\n\t\tpath: \"/foo/bar.inlang\",\n\t});\n\n\tconst gitignore = await fs.promises.readFile(\n\t\t\"/foo/bar.inlang/.gitignore\",\n\t\t\"utf-8\"\n\t);\n\texpect(gitignore).toContain(\"*\");\n\texpect(gitignore).toContain(\"!settings.json\");\n});\n\ntest(\"uses exportFiles when both exportFiles and saveMessages are defined\", async () => {\n\tconst exportFilesSpy = vi.fn().mockResolvedValue([]);\n\tconst saveMessagesSpy = vi.fn();\n\tconst mockPlugin: InlangPlugin = {\n\t\tkey: \"mock\",\n\t\texportFiles: exportFilesSpy,\n\t\tsaveMessages: saveMessagesSpy,\n\t};\n\tconst volume = Volume.fromJSON({});\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject(),\n\t\tprovidePlugins: [mockPlugin],\n\t});\n\tawait saveProjectToDirectory({\n\t\tpath: \"/foo/project.inlang\",\n\t\tfs: volume.promises as any,\n\t\tproject,\n\t});\n\texpect(exportFilesSpy).toHaveBeenCalled();\n\texpect(saveMessagesSpy).not.toHaveBeenCalled();\n});\n\ntest(\"skipExporting prevents exporters from running\", async () => {\n\tconst exportFilesSpy = vi.fn().mockResolvedValue([]);\n\tconst saveMessagesSpy = vi.fn();\n\tconst mockPlugin: InlangPlugin = {\n\t\tkey: \"mock\",\n\t\texportFiles: exportFilesSpy,\n\t\tsaveMessages: saveMessagesSpy,\n\t};\n\tconst volume = Volume.fromJSON({});\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject(),\n\t\tprovidePlugins: [mockPlugin],\n\t});\n\tawait saveProjectToDirectory({\n\t\tpath: \"/foo/project.inlang\",\n\t\tfs: volume.promises as any,\n\t\tproject,\n\t\tskipExporting: true,\n\t});\n\texpect(exportFilesSpy).not.toHaveBeenCalled();\n\texpect(saveMessagesSpy).not.toHaveBeenCalled();\n});\n\ntest(\"uses saveMessages when exportFiles is not defined\", async () => {\n\tconst saveMessagesSpy = vi.fn().mockResolvedValue([]);\n\tconst mockPlugin: InlangPlugin = {\n\t\tkey: \"mock\",\n\t\tsaveMessages: saveMessagesSpy,\n\t};\n\tconst volume = Volume.fromJSON({});\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject(),\n\t\tprovidePlugins: [mockPlugin],\n\t});\n\tawait saveProjectToDirectory({\n\t\tpath: \"/foo/project.inlang\",\n\t\tfs: volume.promises as any,\n\t\tproject,\n\t});\n\texpect(saveMessagesSpy).toHaveBeenCalled();\n});\n"]}
@@ -1,5 +1,5 @@
1
1
  export const ENV_VARIABLES = {
2
2
  PUBLIC_INLANG_SDK_SENTRY_DSN: undefined,
3
- SDK_VERSION: "2.9.3",
3
+ SDK_VERSION: "2.10.1",
4
4
  };
5
5
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"/","sources":["services/env-variables/index.ts"],"names":[],"mappings":"AACA,MAAM,CAAC,MAAM,aAAa,GAAG;IAC5B,4BAA4B,EAAE,SAAS;IACvC,WAAW,EAAE,OAAO;CACpB,CAAA","sourcesContent":["\nexport const ENV_VARIABLES = {\n\tPUBLIC_INLANG_SDK_SENTRY_DSN: undefined,\n\tSDK_VERSION: \"2.9.3\",\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"/","sources":["services/env-variables/index.ts"],"names":[],"mappings":"AACA,MAAM,CAAC,MAAM,aAAa,GAAG;IAC5B,4BAA4B,EAAE,SAAS;IACvC,WAAW,EAAE,QAAQ;CACrB,CAAA","sourcesContent":["\nexport const ENV_VARIABLES = {\n\tPUBLIC_INLANG_SDK_SENTRY_DSN: undefined,\n\tSDK_VERSION: \"2.10.1\",\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inlang/sdk",
3
- "version": "2.9.3",
3
+ "version": "2.10.1",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -71,6 +71,19 @@ export type ExportFile = {
71
71
  name: string;
72
72
  /** The binary content of the resource */
73
73
  content: Uint8Array;
74
+ /**
75
+ * Metadata of the exported file.
76
+ *
77
+ * The counterpart of `ImportFile.toBeImportedFilesMetadata`. Plugins can
78
+ * use it to pass information to the writer. For example, a plugin that
79
+ * supports a namespaced `pathPattern` (`Record<namespace, pattern>`)
80
+ * provides `{ namespace }` so that `saveProjectToDirectory` can resolve
81
+ * the pattern each exported file belongs to. Plugins can also provide
82
+ * `{ pathPattern }` to override the configured pattern for one file.
83
+ *
84
+ * https://github.com/opral/inlang/issues/4356
85
+ */
86
+ metadata?: Record<string, any>;
74
87
  };
75
88
 
76
89
  /**
@@ -130,6 +130,270 @@ test("creates exporter target directories from pathPattern", async () => {
130
130
  expect(JSON.parse(exported as string)).toEqual({ greeting: "Hi" });
131
131
  });
132
132
 
133
+ test("writes exported files to every pattern of a pathPattern array", async () => {
134
+ const volume = Volume.fromJSON({});
135
+ const mockPlugin: InlangPlugin = {
136
+ key: "mock",
137
+ exportFiles: async () => [
138
+ {
139
+ locale: "en",
140
+ name: "en.json",
141
+ content: new TextEncoder().encode(JSON.stringify({ greeting: "Hi" })),
142
+ },
143
+ ],
144
+ };
145
+
146
+ const project = await loadProjectInMemory({
147
+ blob: await newProject({
148
+ settings: {
149
+ baseLocale: "en",
150
+ locales: ["en"],
151
+ modules: [],
152
+ mock: {
153
+ pathPattern: ["./messages/{locale}.json", "./backup/{locale}.json"],
154
+ },
155
+ },
156
+ }),
157
+ providePlugins: [mockPlugin],
158
+ });
159
+
160
+ await saveProjectToDirectory({
161
+ fs: volume as any,
162
+ project,
163
+ path: "/foo/bar.inlang",
164
+ });
165
+
166
+ const messages = await volume.promises.readFile(
167
+ "/foo/messages/en.json",
168
+ "utf-8"
169
+ );
170
+ const backup = await volume.promises.readFile("/foo/backup/en.json", "utf-8");
171
+ expect(JSON.parse(messages as string)).toEqual({ greeting: "Hi" });
172
+ expect(JSON.parse(backup as string)).toEqual({ greeting: "Hi" });
173
+ });
174
+
175
+ test("an empty pathPattern array writes nothing", async () => {
176
+ const volume = Volume.fromJSON({});
177
+ const mockPlugin: InlangPlugin = {
178
+ key: "mock",
179
+ exportFiles: async () => [
180
+ {
181
+ locale: "en",
182
+ name: "en.json",
183
+ content: new TextEncoder().encode(JSON.stringify({ greeting: "Hi" })),
184
+ },
185
+ ],
186
+ };
187
+
188
+ const project = await loadProjectInMemory({
189
+ blob: await newProject({
190
+ settings: {
191
+ baseLocale: "en",
192
+ locales: ["en"],
193
+ modules: [],
194
+ mock: {
195
+ pathPattern: [],
196
+ },
197
+ },
198
+ }),
199
+ providePlugins: [mockPlugin],
200
+ });
201
+
202
+ await saveProjectToDirectory({
203
+ fs: volume as any,
204
+ project,
205
+ path: "/foo/bar.inlang",
206
+ });
207
+
208
+ const files = await volume.promises.readdir("/foo");
209
+ expect(files).not.toContain("en.json");
210
+ });
211
+
212
+ // https://github.com/opral/inlang/issues/4356
213
+ test("resolves a namespaced pathPattern object via export file metadata", async () => {
214
+ const volume = Volume.fromJSON({});
215
+ const mockPlugin: InlangPlugin = {
216
+ key: "mock",
217
+ exportFiles: async () => [
218
+ {
219
+ locale: "en",
220
+ name: "common-en.json",
221
+ content: new TextEncoder().encode(JSON.stringify({ hello: "Hello" })),
222
+ metadata: { namespace: "common" },
223
+ },
224
+ {
225
+ locale: "en",
226
+ name: "app-en.json",
227
+ content: new TextEncoder().encode(JSON.stringify({ title: "My app" })),
228
+ metadata: { namespace: "app" },
229
+ },
230
+ ],
231
+ };
232
+
233
+ const project = await loadProjectInMemory({
234
+ blob: await newProject({
235
+ settings: {
236
+ baseLocale: "en",
237
+ locales: ["en"],
238
+ modules: [],
239
+ mock: {
240
+ pathPattern: {
241
+ common: "./{locale}/common.json",
242
+ app: "./{locale}/app.json",
243
+ },
244
+ },
245
+ },
246
+ }),
247
+ providePlugins: [mockPlugin],
248
+ });
249
+
250
+ await saveProjectToDirectory({
251
+ fs: volume as any,
252
+ project,
253
+ path: "/foo/bar.inlang",
254
+ });
255
+
256
+ const common = await volume.promises.readFile("/foo/en/common.json", "utf-8");
257
+ const app = await volume.promises.readFile("/foo/en/app.json", "utf-8");
258
+ expect(JSON.parse(common as string)).toEqual({ hello: "Hello" });
259
+ expect(JSON.parse(app as string)).toEqual({ title: "My app" });
260
+ });
261
+
262
+ test("resolves an export file pathPattern metadata override", async () => {
263
+ const volume = Volume.fromJSON({});
264
+ const mockPlugin: InlangPlugin = {
265
+ key: "mock",
266
+ exportFiles: async () => [
267
+ {
268
+ locale: "en",
269
+ name: "en.json",
270
+ content: new TextEncoder().encode(JSON.stringify({ hello: "Hello" })),
271
+ metadata: { pathPattern: "./main.json" },
272
+ },
273
+ {
274
+ locale: "de",
275
+ name: "de.json",
276
+ content: new TextEncoder().encode(JSON.stringify({ hello: "Hallo" })),
277
+ },
278
+ ],
279
+ };
280
+
281
+ const project = await loadProjectInMemory({
282
+ blob: await newProject({
283
+ settings: {
284
+ baseLocale: "en",
285
+ locales: ["en", "de"],
286
+ modules: [],
287
+ mock: {
288
+ pathPattern: "./{locale}.json",
289
+ },
290
+ },
291
+ }),
292
+ providePlugins: [mockPlugin],
293
+ });
294
+
295
+ await saveProjectToDirectory({
296
+ fs: volume as any,
297
+ project,
298
+ path: "/foo/bar.inlang",
299
+ });
300
+
301
+ const source = await volume.promises.readFile("/foo/main.json", "utf-8");
302
+ const target = await volume.promises.readFile("/foo/de.json", "utf-8");
303
+ expect(JSON.parse(source as string)).toEqual({ hello: "Hello" });
304
+ expect(JSON.parse(target as string)).toEqual({ hello: "Hallo" });
305
+ });
306
+
307
+ // old plugin versions don't provide namespace metadata. falling back to
308
+ // file.name is better than throwing "pathPattern.replace is not a function"
309
+ // https://github.com/opral/inlang/issues/4356
310
+ test("falls back to the file name when a namespaced pathPattern can't be resolved", async () => {
311
+ const volume = Volume.fromJSON({});
312
+ const mockPlugin: InlangPlugin = {
313
+ key: "mock",
314
+ exportFiles: async () => [
315
+ {
316
+ locale: "en",
317
+ name: "common-en.json",
318
+ content: new TextEncoder().encode(JSON.stringify({ hello: "Hello" })),
319
+ // no metadata, like plugin versions that predate ExportFile.metadata
320
+ },
321
+ ],
322
+ };
323
+
324
+ const project = await loadProjectInMemory({
325
+ blob: await newProject({
326
+ settings: {
327
+ baseLocale: "en",
328
+ locales: ["en"],
329
+ modules: [],
330
+ mock: {
331
+ pathPattern: {
332
+ common: "./{locale}/common.json",
333
+ },
334
+ },
335
+ },
336
+ }),
337
+ providePlugins: [mockPlugin],
338
+ });
339
+
340
+ await saveProjectToDirectory({
341
+ fs: volume as any,
342
+ project,
343
+ path: "/foo/bar.inlang",
344
+ });
345
+
346
+ const fallback = await volume.promises.readFile(
347
+ "/foo/common-en.json",
348
+ "utf-8"
349
+ );
350
+ expect(JSON.parse(fallback as string)).toEqual({ hello: "Hello" });
351
+ });
352
+
353
+ // https://github.com/opral/inlang/issues/4356
354
+ test("falls back to the file name when the namespace is missing from the pathPattern object", async () => {
355
+ const volume = Volume.fromJSON({});
356
+ const mockPlugin: InlangPlugin = {
357
+ key: "mock",
358
+ exportFiles: async () => [
359
+ {
360
+ locale: "en",
361
+ name: "stray-en.json",
362
+ content: new TextEncoder().encode(JSON.stringify({ hello: "Hello" })),
363
+ metadata: { namespace: "stray" },
364
+ },
365
+ ],
366
+ };
367
+
368
+ const project = await loadProjectInMemory({
369
+ blob: await newProject({
370
+ settings: {
371
+ baseLocale: "en",
372
+ locales: ["en"],
373
+ modules: [],
374
+ mock: {
375
+ pathPattern: {
376
+ common: "./{locale}/common.json",
377
+ },
378
+ },
379
+ },
380
+ }),
381
+ providePlugins: [mockPlugin],
382
+ });
383
+
384
+ await saveProjectToDirectory({
385
+ fs: volume as any,
386
+ project,
387
+ path: "/foo/bar.inlang",
388
+ });
389
+
390
+ const fallback = await volume.promises.readFile(
391
+ "/foo/stray-en.json",
392
+ "utf-8"
393
+ );
394
+ expect(JSON.parse(fallback as string)).toEqual({ hello: "Hello" });
395
+ });
396
+
133
397
  // Users were confused by project_id, and without sync a stable id is rarely needed.
134
398
  test("it should not write project_id to disk", async () => {
135
399
  const mockFs = Volume.fromJSON({
@@ -183,19 +183,39 @@ export async function saveProjectToDirectory(args: {
183
183
  for (const file of files) {
184
184
  const pathPattern = settings[plugin.key]?.pathPattern;
185
185
 
186
- // We need to check if pathPattern is a string or an array of strings
187
- // and handle both cases.
188
- const formattedPathPatterns = Array.isArray(pathPattern)
189
- ? pathPattern
190
- : [pathPattern];
191
-
192
- for (const pathPattern of formattedPathPatterns) {
193
- const p = pathPattern
194
- ? absolutePathFromProject(
195
- args.path,
196
- pathPattern.replace(/\{(languageTag|locale)\}/g, file.locale)
197
- )
198
- : absolutePathFromProject(args.path, file.name);
186
+ const resolvePattern = (pattern: string) =>
187
+ absolutePathFromProject(
188
+ args.path,
189
+ pattern.replace(/\{(languageTag|locale)\}/g, file.locale)
190
+ );
191
+
192
+ // pathPattern can be a string, an array of strings, or a record
193
+ // mapping namespaces to patterns (e.g. plugin-i18next).
194
+ // https://github.com/opral/inlang/issues/4356
195
+ let targetPaths: string[];
196
+ if (typeof file.metadata?.["pathPattern"] === "string") {
197
+ targetPaths = [resolvePattern(file.metadata["pathPattern"])];
198
+ } else if (typeof pathPattern === "string") {
199
+ targetPaths = [resolvePattern(pathPattern)];
200
+ } else if (Array.isArray(pathPattern)) {
201
+ // an empty array writes nothing
202
+ targetPaths = pathPattern.map(resolvePattern);
203
+ } else if (typeof pathPattern === "object" && pathPattern !== null) {
204
+ const namespace = file.metadata?.["namespace"];
205
+ const namespacePattern = namespace
206
+ ? pathPattern[namespace]
207
+ : undefined;
208
+ // no pattern for this file (plugin didn't provide namespace
209
+ // metadata or the namespace is unknown) -> fall back to file.name
210
+ targetPaths =
211
+ typeof namespacePattern === "string"
212
+ ? [resolvePattern(namespacePattern)]
213
+ : [absolutePathFromProject(args.path, file.name)];
214
+ } else {
215
+ targetPaths = [absolutePathFromProject(args.path, file.name)];
216
+ }
217
+
218
+ for (const p of targetPaths) {
199
219
  await fsModule.mkdir(path.dirname(p), { recursive: true });
200
220
  if (p.endsWith(".json")) {
201
221
  try {