@inlang/sdk 2.9.2 → 2.9.3

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.
@@ -44,6 +44,61 @@ test("it should overwrite all files to the directory except the db.sqlite file",
44
44
  expect(updatedSettings.baseLocale).toBe("en");
45
45
  expect(updatedSettings.locales).toEqual(["en", "fr", "mock"]);
46
46
  });
47
+ test("accepts the node:fs style module with a promises namespace", async () => {
48
+ const volume = Volume.fromJSON({});
49
+ const project = await loadProjectInMemory({
50
+ blob: await newProject({
51
+ settings: {
52
+ baseLocale: "en",
53
+ locales: ["en"],
54
+ },
55
+ }),
56
+ });
57
+ await saveProjectToDirectory({
58
+ fs: volume,
59
+ project,
60
+ path: "/foo/bar.inlang",
61
+ });
62
+ const settings = await volume.promises.readFile("/foo/bar.inlang/settings.json", "utf-8");
63
+ expect(JSON.parse(settings).locales).toEqual(["en"]);
64
+ });
65
+ test("creates exporter target directories from pathPattern", async () => {
66
+ const volume = Volume.fromJSON({});
67
+ const mockPlugin = {
68
+ key: "mock",
69
+ exportFiles: async () => [
70
+ {
71
+ locale: "en",
72
+ name: "fallback.json",
73
+ content: new TextEncoder().encode(JSON.stringify({ greeting: "Hi" })),
74
+ },
75
+ ],
76
+ };
77
+ const project = await loadProjectInMemory({
78
+ blob: await newProject({
79
+ settings: {
80
+ baseLocale: "en",
81
+ locales: ["en"],
82
+ modules: [],
83
+ mock: {
84
+ pathPattern: "./messages/{locale}.json",
85
+ },
86
+ },
87
+ }),
88
+ providePlugins: [mockPlugin],
89
+ });
90
+ await project.db
91
+ .insertInto("bundle")
92
+ .values({ id: "greeting", declarations: [] })
93
+ .execute();
94
+ await saveProjectToDirectory({
95
+ fs: volume,
96
+ project,
97
+ path: "/foo/bar.inlang",
98
+ });
99
+ const exported = await volume.promises.readFile("/foo/messages/en.json", "utf-8");
100
+ expect(JSON.parse(exported)).toEqual({ greeting: "Hi" });
101
+ });
47
102
  // Users were confused by project_id, and without sync a stable id is rarely needed.
48
103
  test("it should not write project_id to disk", async () => {
49
104
  const mockFs = Volume.fromJSON({
@@ -306,6 +361,21 @@ test("emits a .meta.json file with the sdk version", async () => {
306
361
  const meta = JSON.parse(typeof metaRaw === "string" ? metaRaw : metaRaw.toString());
307
362
  expect(meta.highestSdkVersion).toBe(ENV_VARIABLES.SDK_VERSION);
308
363
  });
364
+ test("throws when saving translation data to a directory without an exporter plugin", async () => {
365
+ const fs = Volume.fromJSON({});
366
+ const project = await loadProjectInMemory({
367
+ blob: await newProject(),
368
+ });
369
+ await project.db
370
+ .insertInto("bundle")
371
+ .values({ id: "greeting", declarations: [] })
372
+ .execute();
373
+ await expect(saveProjectToDirectory({
374
+ fs: fs.promises,
375
+ project,
376
+ path: "/foo/bar.inlang",
377
+ })).rejects.toThrow("saveProjectToDirectory cannot write bundles, messages, or variants without an import/export plugin");
378
+ });
309
379
  test("updates an existing README.md file", async () => {
310
380
  const fs = Volume.fromJSON({
311
381
  "/foo/bar.inlang/README.md": "custom readme",
@@ -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\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(\"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\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.2",
3
+ SDK_VERSION: "2.9.3",
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.2\",\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,OAAO;CACpB,CAAA","sourcesContent":["\nexport const ENV_VARIABLES = {\n\tPUBLIC_INLANG_SDK_SENTRY_DSN: undefined,\n\tSDK_VERSION: \"2.9.3\",\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inlang/sdk",
3
- "version": "2.9.2",
3
+ "version": "2.9.3",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -116,7 +116,7 @@ async function handleForeignKeyViolation(args: {
116
116
  .selectAll()
117
117
  // heuristic that getting the last bundle value is fine
118
118
  // and using created_at is fine too. if the change is undesired
119
- // , a user can revert it with lix change control
119
+ // , a user can revert it with version control via lix
120
120
  .orderBy("created_at", "desc")
121
121
  .where("type", "=", type)
122
122
  .where((eb) => eb.ref("value", "->>").key("id"), "=", id)
@@ -12,7 +12,9 @@ This is an [unpacked (git-friendly)](https://inlang.com/docs/unpacked-project) i
12
12
  ## At a glance
13
13
 
14
14
  Purpose:
15
- - This folder stores inlang project configuration and plugin cache data.
15
+ - This folder is the Git-friendly representation of an \`.inlang\` project.
16
+ - The canonical \`.inlang\` format is a single binary file; this directory is the unpacked version for Git.
17
+ - This folder stores project configuration and plugin cache data.
16
18
  - Translation files live outside this folder and are referenced from \`settings.json\`.
17
19
 
18
20
  Safe to edit:
@@ -23,26 +25,44 @@ Do not edit:
23
25
  - \`.gitignore\`
24
26
 
25
27
  Key files:
26
- - \`settings.json\` — locales, plugins, file patterns (source of truth)
28
+ - \`settings.json\` — locales, plugins, file patterns
27
29
  - \`cache/\` — plugin caches (safe to delete)
28
30
  - \`.gitignore\` — generated
31
+ - \`README.md\` — generated, explains this folder
32
+ - \`.meta.json\` — generated SDK metadata
29
33
 
30
34
  \`\`\`
31
35
  *.inlang/
32
- ├── settings.json # Locales, plugins, and file patterns (source of truth)
33
- ├── cache/ # Plugin caches (gitignored)
34
- └── .gitignore # Ignores everything except settings.json
36
+ ├── settings.json # Locales, plugins, and file patterns; kept in Git
37
+ ├── .gitignore # Ignores everything except settings.json
38
+ ├── README.md # Generated, explains this folder
39
+ ├── .meta.json # Generated SDK metadata
40
+ └── cache/ # Plugin caches, usually cache/plugins/
35
41
  \`\`\`
36
42
 
37
43
  Translation files (like \`messages/en.json\`) live **outside** this folder and are referenced via plugins in \`settings.json\`.
38
44
 
39
45
  ## What is inlang?
40
46
 
41
- [Inlang](https://inlang.com) is an open file format for building custom localization (i18n) tooling. It provides:
47
+ [Inlang](https://inlang.com) is an open project file format for localization. An \`.inlang\` project is canonically a single binary file: a SQLite database with version control via [lix](https://lix.dev). Like \`.sqlite\` for relational data, \`.inlang\` packages localization data into one file that tools can share.
48
+
49
+ For Git repositories, that binary file can be unpacked into a directory of plain files. The packed file is the canonical format; this directory is the Git-friendly representation.
50
+
51
+ Use inlang when multiple tools, teams, automations, or agents need to use the same localization data. The \`@inlang/sdk\` is the reference implementation for reading and writing \`.inlang\` projects.
52
+
53
+ \`.inlang\` is the canonical project format. Plugins import and export external translation files for compatibility with existing runtimes and workflows. Messages, variants, and locale data live in the \`.inlang\` database; translation files such as \`messages/en.json\` live outside this folder and are connected through plugins. Version control via lix adds file-level history, merging, and change proposals to \`.inlang\` projects.
54
+
55
+ It provides:
42
56
 
43
57
  - **CRUD API** — Read and write translations programmatically via SQL
44
- - **Plugin system** — Import/export any format (JSON, XLIFF, etc.)
45
- - **Version control** — Built-in version control via [lix](https://lix.dev)
58
+ - **Plugin system** — Import/export external translation files (JSON, XLIFF, etc.)
59
+ - **Version control** — Version control via [lix](https://lix.dev)
60
+
61
+ Core data model:
62
+
63
+ - **Bundle** — one translatable unit across locales
64
+ - **Message** — locale-specific translation for a bundle
65
+ - **Variant** — text pattern plus selector matches
46
66
 
47
67
  \`\`\`
48
68
  ┌──────────┐ ┌───────────┐ ┌────────────┐
@@ -65,15 +85,45 @@ npm install @inlang/sdk
65
85
 
66
86
  \`\`\`ts
67
87
  import { loadProjectFromDirectory, saveProjectToDirectory } from "@inlang/sdk";
88
+ import fs from "node:fs";
68
89
 
69
- const project = await loadProjectFromDirectory({ path: "./project.inlang" });
70
- // Query messages with SQLite + [Kysely](https://kysely.dev/) under the hood.
90
+ const project = await loadProjectFromDirectory({
91
+ path: "./project.inlang",
92
+ fs,
93
+ });
94
+ // Query messages with the SDK.
71
95
  const messages = await project.db.selectFrom("message").selectAll().execute();
72
96
 
73
97
  // Use project.db to update messages.
74
- await saveProjectToDirectory({ path: "./project.inlang", project });
98
+ await saveProjectToDirectory({
99
+ path: "./project.inlang",
100
+ fs,
101
+ project,
102
+ });
103
+
104
+ await project.close();
75
105
  \`\`\`
76
106
 
107
+ \`loadProjectFromDirectory()\` and \`saveProjectToDirectory()\` both accept \`node:fs\`. \`saveProjectToDirectory()\` writes translation files through import/export plugins. If no exporter plugin is configured, save the canonical packed file with \`project.toBlob()\` instead.
108
+
109
+ ## For coding agents and tool builders
110
+
111
+ Target \`.inlang\` instead of inventing a custom JSON schema.
112
+
113
+ Why target \`.inlang\`?
114
+
115
+ - One file gives every tool the same data model for bundles, messages, variants, locales, metadata, and settings.
116
+ - CRUD operations are available through the SDK instead of custom parsing and rewriting.
117
+ - Version control via [lix](https://lix.dev) keeps localization changes reviewable and mergeable.
118
+ - Plugins handle JSON, ICU, i18next, XLIFF, and other formats for compatibility.
119
+
120
+ Do not:
121
+
122
+ - Invent a custom \`locales/en.json\` schema as the project format.
123
+ - Create a new localization config when a \`.inlang\` project already exists.
124
+ - Edit generated or cached files in this directory.
125
+ - Bypass \`@inlang/sdk\` when reading or writing \`.inlang\`.
126
+
77
127
  ## Ideas for custom tooling
78
128
 
79
129
  - Translation health dashboard (missing/empty/stale messages)
@@ -60,6 +60,76 @@ test("it should overwrite all files to the directory except the db.sqlite file",
60
60
  expect(updatedSettings.locales).toEqual(["en", "fr", "mock"]);
61
61
  });
62
62
 
63
+ test("accepts the node:fs style module with a promises namespace", async () => {
64
+ const volume = Volume.fromJSON({});
65
+
66
+ const project = await loadProjectInMemory({
67
+ blob: await newProject({
68
+ settings: {
69
+ baseLocale: "en",
70
+ locales: ["en"],
71
+ },
72
+ }),
73
+ });
74
+
75
+ await saveProjectToDirectory({
76
+ fs: volume as any,
77
+ project,
78
+ path: "/foo/bar.inlang",
79
+ });
80
+
81
+ const settings = await volume.promises.readFile(
82
+ "/foo/bar.inlang/settings.json",
83
+ "utf-8"
84
+ );
85
+ expect(JSON.parse(settings as string).locales).toEqual(["en"]);
86
+ });
87
+
88
+ test("creates exporter target directories from pathPattern", async () => {
89
+ const volume = Volume.fromJSON({});
90
+ const mockPlugin: InlangPlugin = {
91
+ key: "mock",
92
+ exportFiles: async () => [
93
+ {
94
+ locale: "en",
95
+ name: "fallback.json",
96
+ content: new TextEncoder().encode(JSON.stringify({ greeting: "Hi" })),
97
+ },
98
+ ],
99
+ };
100
+
101
+ const project = await loadProjectInMemory({
102
+ blob: await newProject({
103
+ settings: {
104
+ baseLocale: "en",
105
+ locales: ["en"],
106
+ modules: [],
107
+ mock: {
108
+ pathPattern: "./messages/{locale}.json",
109
+ },
110
+ },
111
+ }),
112
+ providePlugins: [mockPlugin],
113
+ });
114
+
115
+ await project.db
116
+ .insertInto("bundle")
117
+ .values({ id: "greeting", declarations: [] })
118
+ .execute();
119
+
120
+ await saveProjectToDirectory({
121
+ fs: volume as any,
122
+ project,
123
+ path: "/foo/bar.inlang",
124
+ });
125
+
126
+ const exported = await volume.promises.readFile(
127
+ "/foo/messages/en.json",
128
+ "utf-8"
129
+ );
130
+ expect(JSON.parse(exported as string)).toEqual({ greeting: "Hi" });
131
+ });
132
+
63
133
  // Users were confused by project_id, and without sync a stable id is rarely needed.
64
134
  test("it should not write project_id to disk", async () => {
65
135
  const mockFs = Volume.fromJSON({
@@ -404,6 +474,29 @@ test("emits a .meta.json file with the sdk version", async () => {
404
474
  expect(meta.highestSdkVersion).toBe(ENV_VARIABLES.SDK_VERSION);
405
475
  });
406
476
 
477
+ test("throws when saving translation data to a directory without an exporter plugin", async () => {
478
+ const fs = Volume.fromJSON({});
479
+
480
+ const project = await loadProjectInMemory({
481
+ blob: await newProject(),
482
+ });
483
+
484
+ await project.db
485
+ .insertInto("bundle")
486
+ .values({ id: "greeting", declarations: [] })
487
+ .execute();
488
+
489
+ await expect(
490
+ saveProjectToDirectory({
491
+ fs: fs.promises as any,
492
+ project,
493
+ path: "/foo/bar.inlang",
494
+ })
495
+ ).rejects.toThrow(
496
+ "saveProjectToDirectory cannot write bundles, messages, or variants without an import/export plugin"
497
+ );
498
+ });
499
+
407
500
  test("updates an existing README.md file", async () => {
408
501
  const fs = Volume.fromJSON({
409
502
  "/foo/bar.inlang/README.md": "custom readme",