@inlang/sdk 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"import.d.ts","sourceRoot":"","sources":["../../src/resolve-modules/import.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAA;AAG7D;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,GAAG,CAAC,CAAA;AAE1D;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE;IAClC,6CAA6C;IAC7C,QAAQ,EAAE,uBAAuB,CAAC,UAAU,CAAC,CAAA;CAC7C,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,UAAU,CAAC,OAAO,OAAO,CAAC,CAG9C;AAED,iBAAe,OAAO,CACrB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE;IACR;;OAEG;IACH,QAAQ,EAAE,uBAAuB,CAAC,UAAU,CAAC,CAAA;CAC7C,GACC,OAAO,CAAC,GAAG,CAAC,CAuBd"}
1
+ {"version":3,"file":"import.d.ts","sourceRoot":"","sources":["../../src/resolve-modules/import.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAA;AAG7D;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,GAAG,CAAC,CAAA;AAE1D;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE;IAClC,6CAA6C;IAC7C,QAAQ,EAAE,uBAAuB,CAAC,UAAU,CAAC,CAAA;CAC7C,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,UAAU,CAAC,OAAO,OAAO,CAAC,CAG9C;AAED,iBAAe,OAAO,CACrB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE;IACR;;OAEG;IACH,QAAQ,EAAE,uBAAuB,CAAC,UAAU,CAAC,CAAA;CAC7C,GACC,OAAO,CAAC,GAAG,CAAC,CAyBd"}
@@ -1,5 +1,4 @@
1
1
  import dedent from "dedent";
2
- import { normalizePath } from "@lix-js/fs";
3
2
  import { ModuleImportError } from "./errors.js";
4
3
  /**
5
4
  * Creates the import function.
@@ -20,7 +19,9 @@ async function $import(uri, options) {
20
19
  moduleAsText = await (await fetch(uri)).text();
21
20
  }
22
21
  else {
23
- moduleAsText = await options.readFile(normalizePath(uri), { encoding: "utf-8" });
22
+ moduleAsText = await options.readFile(uri, {
23
+ encoding: "utf-8",
24
+ });
24
25
  }
25
26
  const moduleWithMimeType = "data:application/javascript," + encodeURIComponent(moduleAsText);
26
27
  try {
@@ -1,4 +1,4 @@
1
- import type { NodeishFilesystem as LisaNodeishFilesystem } from "@lix-js/fs";
1
+ import type { NodeishFilesystem } from "@lix-js/fs";
2
2
  import type { PluginReturnedInvalidCustomApiError, PluginLoadMessagesFunctionAlreadyDefinedError, PluginSaveMessagesFunctionAlreadyDefinedError, PluginHasInvalidIdError, PluginHasInvalidSchemaError, PluginUsesReservedNamespaceError, PluginsDoNotProvideLoadOrSaveMessagesError } from "./errors.js";
3
3
  import type { Message } from "@inlang/message";
4
4
  import type { CustomApiInlangIdeExtension, Plugin } from "@inlang/plugin";
@@ -8,7 +8,7 @@ import type { ProjectSettings } from "@inlang/project-settings";
8
8
  *
9
9
  * - only uses minimally required functions to decrease the API footprint on the ecosystem.
10
10
  */
11
- export type NodeishFilesystemSubset = Pick<LisaNodeishFilesystem, "readFile" | "readdir" | "mkdir" | "writeFile">;
11
+ export type NodeishFilesystemSubset = Pick<NodeishFilesystem, "readFile" | "readdir" | "mkdir" | "writeFile">;
12
12
  /**
13
13
  * Function that resolves (imports and initializes) the plugins.
14
14
  */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/resolve-modules/plugins/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,IAAI,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAC5E,OAAO,KAAK,EACX,mCAAmC,EACnC,6CAA6C,EAC7C,6CAA6C,EAC7C,uBAAuB,EACvB,2BAA2B,EAC3B,gCAAgC,EAChC,0CAA0C,EAC1C,MAAM,aAAa,CAAA;AACpB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AAC9C,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AACzE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAE/D;;;;GAIG;AACH,MAAM,MAAM,uBAAuB,GAAG,IAAI,CACzC,qBAAqB,EACrB,UAAU,GAAG,SAAS,GAAG,OAAO,GAAG,WAAW,CAC9C,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,IAAI,EAAE;IAC3C,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;IACtB,QAAQ,EAAE,eAAe,CAAA;IACzB,SAAS,EAAE,uBAAuB,CAAA;CAClC,KAAK,OAAO,CAAC;IACb,IAAI,EAAE,iBAAiB,CAAA;IACvB,MAAM,EAAE,KAAK,CACV,mCAAmC,GACnC,6CAA6C,GAC7C,6CAA6C,GAC7C,uBAAuB,GACvB,2BAA2B,GAC3B,gCAAgC,GAChC,0CAA0C,CAC5C,CAAA;CACD,CAAC,CAAA;AAEF;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC/B,YAAY,EAAE,CAAC,IAAI,EAAE;QAAE,QAAQ,EAAE,eAAe,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,GAAG,OAAO,EAAE,CAAA;IACrF,YAAY,EAAE,CAAC,IAAI,EAAE;QAAE,QAAQ,EAAE,eAAe,CAAC;QAAC,QAAQ,EAAE,OAAO,EAAE,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAChG;;;;;;;;;;;;;;;;OAgBG;IACH,SAAS,EAAE,MAAM,CAAC,OAAO,MAAM,IAAI,MAAM,EAAE,GAAG,WAAW,MAAM,IAAI,MAAM,EAAE,EAAE,OAAO,CAAC,GAAG;QACvF,yBAAyB,CAAC,EAAE,2BAA2B,CAAA;KACvD,CAAA;CACD,CAAA"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/resolve-modules/plugins/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AACnD,OAAO,KAAK,EACX,mCAAmC,EACnC,6CAA6C,EAC7C,6CAA6C,EAC7C,uBAAuB,EACvB,2BAA2B,EAC3B,gCAAgC,EAChC,0CAA0C,EAC1C,MAAM,aAAa,CAAA;AACpB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AAC9C,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AACzE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAE/D;;;;GAIG;AACH,MAAM,MAAM,uBAAuB,GAAG,IAAI,CACzC,iBAAiB,EACjB,UAAU,GAAG,SAAS,GAAG,OAAO,GAAG,WAAW,CAC9C,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,IAAI,EAAE;IAC3C,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;IACtB,QAAQ,EAAE,eAAe,CAAA;IACzB,SAAS,EAAE,uBAAuB,CAAA;CAClC,KAAK,OAAO,CAAC;IACb,IAAI,EAAE,iBAAiB,CAAA;IACvB,MAAM,EAAE,KAAK,CACV,mCAAmC,GACnC,6CAA6C,GAC7C,6CAA6C,GAC7C,uBAAuB,GACvB,2BAA2B,GAC3B,gCAAgC,GAChC,0CAA0C,CAC5C,CAAA;CACD,CAAC,CAAA;AAEF;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC/B,YAAY,EAAE,CAAC,IAAI,EAAE;QAAE,QAAQ,EAAE,eAAe,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,GAAG,OAAO,EAAE,CAAA;IACrF,YAAY,EAAE,CAAC,IAAI,EAAE;QAAE,QAAQ,EAAE,eAAe,CAAC;QAAC,QAAQ,EAAE,OAAO,EAAE,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAChG;;;;;;;;;;;;;;;;OAgBG;IACH,SAAS,EAAE,MAAM,CAAC,OAAO,MAAM,IAAI,MAAM,EAAE,GAAG,WAAW,MAAM,IAAI,MAAM,EAAE,EAAE,OAAO,CAAC,GAAG;QACvF,yBAAyB,CAAC,EAAE,2BAA2B,CAAA;KACvD,CAAA;CACD,CAAA"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@inlang/sdk",
3
3
  "type": "module",
4
- "version": "0.10.0",
4
+ "version": "0.12.0",
5
5
  "license": "Apache-2.0",
6
6
  "publishConfig": {
7
7
  "access": "public"
@@ -88,10 +88,11 @@ const $import: ImportFunction = async (name) => ({
88
88
  describe("config", () => {
89
89
  it("should react to changes in config", async () => {
90
90
  const fs = createNodeishMemoryFs()
91
- await fs.writeFile("./project.inlang.json", JSON.stringify(config))
91
+ await fs.mkdir("/user/project", { recursive: true })
92
+ await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(config))
92
93
  const project = solidAdapter(
93
94
  await loadProject({
94
- settingsFilePath: "./project.inlang.json",
95
+ settingsFilePath: "/user/project/project.inlang.json",
95
96
  nodeishFs: fs,
96
97
  _import: $import,
97
98
  }),
@@ -118,10 +119,11 @@ describe("config", () => {
118
119
  describe("installed", () => {
119
120
  it("react to changes that are unrelated to installed items", async () => {
120
121
  const fs = createNodeishMemoryFs()
121
- await fs.writeFile("./project.inlang.json", JSON.stringify(config))
122
+ await fs.mkdir("/user/project", { recursive: true })
123
+ await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(config))
122
124
  const project = solidAdapter(
123
125
  await loadProject({
124
- settingsFilePath: "./project.inlang.json",
126
+ settingsFilePath: "/user/project/project.inlang.json",
125
127
  nodeishFs: fs,
126
128
  _import: $import,
127
129
  }),
@@ -181,10 +183,11 @@ describe("messages", () => {
181
183
 
182
184
  const mockImport: ImportFunction = async () => ({ default: mockPlugin })
183
185
 
184
- await fs.writeFile("./project.inlang.json", JSON.stringify(mockConfig))
186
+ await fs.mkdir("/user/project", { recursive: true })
187
+ await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(mockConfig))
185
188
  const project = solidAdapter(
186
189
  await loadProject({
187
- settingsFilePath: "./project.inlang.json",
190
+ settingsFilePath: "/user/project/project.inlang.json",
188
191
  nodeishFs: fs,
189
192
  _import: mockImport,
190
193
  }),
@@ -210,10 +213,11 @@ describe("messages", () => {
210
213
 
211
214
  it("should react to changes in messages", async () => {
212
215
  const fs = createNodeishMemoryFs()
213
- await fs.writeFile("./project.inlang.json", JSON.stringify(config))
216
+ await fs.mkdir("/user/project", { recursive: true })
217
+ await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(config))
214
218
  const project = solidAdapter(
215
219
  await loadProject({
216
- settingsFilePath: "./project.inlang.json",
220
+ settingsFilePath: "/user/project/project.inlang.json",
217
221
  nodeishFs: fs,
218
222
  _import: $import,
219
223
  }),
@@ -0,0 +1,47 @@
1
+ import { it, expect, vi } from "vitest"
2
+ import { createNodeishFsWithAbsolutePaths } from "./createNodeishFsWithAbsolutePaths.js"
3
+ import type { NodeishFilesystemSubset } from "./versionedInterfaces.js"
4
+
5
+ it("throws an error if settingsFilePath is not an absolute path", () => {
6
+ const relativePath = "relative/path"
7
+
8
+ expect(() =>
9
+ createNodeishFsWithAbsolutePaths({ settingsFilePath: relativePath, nodeishFs: {} as any })
10
+ ).toThrow()
11
+ })
12
+
13
+ it("intercepts paths correctly for readFile", async () => {
14
+ const settingsFilePath = `/Users/samuel/Documents/paraglide/example/project.inlang.json`
15
+
16
+ const filePaths = [
17
+ ["file.txt", `/Users/samuel/Documents/paraglide/example/file.txt`],
18
+ ["./file.txt", `/Users/samuel/Documents/paraglide/example/file.txt`],
19
+ ["./folder/file.txt", `/Users/samuel/Documents/paraglide/example/folder/file.txt`],
20
+ ["../file.txt", `/Users/samuel/Documents/paraglide/file.txt`],
21
+ ["../folder/file.txt", `/Users/samuel/Documents/paraglide/folder/file.txt`],
22
+ ["../../file.txt", `/Users/samuel/Documents/file.txt`],
23
+ ["../../../file.txt", `/Users/samuel/file.txt`],
24
+ ]
25
+
26
+ const mockNodeishFs = {
27
+ readFile: vi.fn(),
28
+ readdir: vi.fn(),
29
+ mkdir: vi.fn(),
30
+ writeFile: vi.fn(),
31
+ } satisfies Record<keyof NodeishFilesystemSubset, any>
32
+
33
+ const interceptedFs = createNodeishFsWithAbsolutePaths({
34
+ settingsFilePath,
35
+ nodeishFs: mockNodeishFs,
36
+ })
37
+
38
+ for (const [path, expectedPath] of filePaths) {
39
+ for (const fn of Object.keys(mockNodeishFs)) {
40
+ // @ts-expect-error
41
+ await interceptedFs[fn](path)
42
+ // @ts-expect-error
43
+ // expect the first argument to be the expectedPath
44
+ expect(mockNodeishFs[fn].mock.lastCall[0]).toBe(expectedPath)
45
+ }
46
+ }
47
+ })
@@ -0,0 +1,40 @@
1
+ import type { NodeishFilesystemSubset } from "@inlang/plugin"
2
+ import { normalizePath } from "@lix-js/fs"
3
+
4
+ export const isAbsolutePath = (path: string) => /^[/\\]/.test(path)
5
+
6
+ /**
7
+ * Wraps the nodeish filesystem subset with a function that intercepts paths
8
+ * and prepends the base path.
9
+ *
10
+ * The paths are resolved from the `settingsFilePath` argument.
11
+ */
12
+ export const createNodeishFsWithAbsolutePaths = (args: {
13
+ settingsFilePath: string
14
+ nodeishFs: NodeishFilesystemSubset
15
+ }): NodeishFilesystemSubset => {
16
+ if (!isAbsolutePath(args.settingsFilePath)) {
17
+ throw new Error("The argument `settingsFilePath` must be an absolute path.")
18
+ }
19
+
20
+ // get the base path of the settings file by
21
+ // removing the file name from the path
22
+ const bathPath = args.settingsFilePath.split("/").slice(0, -1).join("/")
23
+
24
+ const makeAbsolute = (path: string) => {
25
+ if (isAbsolutePath(path)) {
26
+ return path
27
+ }
28
+
29
+ return normalizePath(bathPath + "/" + path)
30
+ }
31
+
32
+ return {
33
+ // @ts-expect-error
34
+ readFile: (path: string, options: { encoding: "utf-8" | "binary" }) =>
35
+ args.nodeishFs.readFile(makeAbsolute(path), options),
36
+ readdir: (path: string) => args.nodeishFs.readdir(makeAbsolute(path)),
37
+ mkdir: (path: string) => args.nodeishFs.mkdir(makeAbsolute(path)),
38
+ writeFile: (path: string, data: string) => args.nodeishFs.writeFile(makeAbsolute(path), data),
39
+ }
40
+ }
package/src/errors.ts CHANGED
@@ -1,5 +1,12 @@
1
1
  import type { ValueError } from "@sinclair/typebox/errors"
2
2
 
3
+ export class LoadProjectInvalidArgument extends Error {
4
+ constructor(message: string, options: { argument: string }) {
5
+ super(`The argument "${options.argument}" of loadProject() is invalid: ${message}`)
6
+ this.name = "LoadProjectInvalidArgument"
7
+ }
8
+ }
9
+
3
10
  export class ProjectSettingsInvalidError extends Error {
4
11
  constructor(options: { errors: ValueError[] }) {
5
12
  super(
@@ -5,11 +5,14 @@ import type { ProjectSettings, Plugin, MessageLintRule, Message } from "./versio
5
5
  import type { ImportFunction } from "./resolve-modules/index.js"
6
6
  import type { InlangModule } from "@inlang/module"
7
7
  import {
8
+ LoadProjectInvalidArgument,
8
9
  ProjectSettingsFileJSONSyntaxError,
9
10
  ProjectSettingsFileNotFoundError,
10
11
  ProjectSettingsInvalidError,
11
12
  } from "./errors.js"
12
13
  import { createNodeishMemoryFs } from "@lix-js/fs"
14
+ import { createMessage } from "./test-utilities/createMessage.js"
15
+ import { tryCatch } from "@inlang/result"
13
16
 
14
17
  // ------------------------------------------------------------------------------------------------
15
18
 
@@ -96,12 +99,27 @@ const _import: ImportFunction = async (name) =>
96
99
  // ------------------------------------------------------------------------------------------------
97
100
 
98
101
  describe("initialization", () => {
102
+ it("should throw if settingsFilePath is not an absolute path", async () => {
103
+ const fs = createNodeishMemoryFs()
104
+
105
+ const result = await tryCatch(() =>
106
+ loadProject({
107
+ settingsFilePath: "relative/path",
108
+ nodeishFs: fs,
109
+ _import,
110
+ })
111
+ )
112
+ expect(result.error).toBeInstanceOf(LoadProjectInvalidArgument)
113
+ expect(result.data).toBeUndefined()
114
+ })
115
+
99
116
  describe("settings", () => {
100
117
  it("should return an error if settings file is not found", async () => {
101
118
  const fs = createNodeishMemoryFs()
119
+ fs.mkdir("/user/project", { recursive: true })
102
120
 
103
121
  const project = await loadProject({
104
- settingsFilePath: "./test.json",
122
+ settingsFilePath: "/user/project/test.json",
105
123
  nodeishFs: fs,
106
124
  _import,
107
125
  })
@@ -111,10 +129,11 @@ describe("initialization", () => {
111
129
 
112
130
  it("should return an error if settings file is not a valid JSON", async () => {
113
131
  const fs = await createNodeishMemoryFs()
114
- await fs.writeFile("./project.inlang.json", "invalid json")
132
+ await fs.mkdir("/user/project", { recursive: true })
133
+ await fs.writeFile("/user/project/project.inlang.json", "invalid json")
115
134
 
116
135
  const project = await loadProject({
117
- settingsFilePath: "./project.inlang.json",
136
+ settingsFilePath: "/user/project/project.inlang.json",
118
137
  nodeishFs: fs,
119
138
  _import,
120
139
  })
@@ -124,10 +143,11 @@ describe("initialization", () => {
124
143
 
125
144
  it("should return an error if settings file is does not match schema", async () => {
126
145
  const fs = await createNodeishMemoryFs()
127
- await fs.writeFile("./project.inlang.json", JSON.stringify({}))
146
+ await fs.mkdir("/user/project", { recursive: true })
147
+ await fs.writeFile("/user/project/project.inlang.json", JSON.stringify({}))
128
148
 
129
149
  const project = await loadProject({
130
- settingsFilePath: "./project.inlang.json",
150
+ settingsFilePath: "/user/project/project.inlang.json",
131
151
  nodeishFs: fs,
132
152
  _import,
133
153
  })
@@ -137,9 +157,10 @@ describe("initialization", () => {
137
157
 
138
158
  it("should return the parsed settings", async () => {
139
159
  const fs = await createNodeishMemoryFs()
140
- await fs.writeFile("./project.inlang.json", JSON.stringify(settings))
160
+ await fs.mkdir("/user/project", { recursive: true })
161
+ await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings))
141
162
  const project = await loadProject({
142
- settingsFilePath: "./project.inlang.json",
163
+ settingsFilePath: "/user/project/project.inlang.json",
143
164
  nodeishFs: fs,
144
165
  _import,
145
166
  })
@@ -150,22 +171,27 @@ describe("initialization", () => {
150
171
  it("should not re-write the settings to disk when initializing", async () => {
151
172
  const fs = await createNodeishMemoryFs()
152
173
  const settingsWithDeifferentFormatting = JSON.stringify(settings, undefined, 4)
153
- await fs.writeFile("./project.inlang.json", settingsWithDeifferentFormatting)
174
+ await fs.mkdir("/user/project", { recursive: true })
175
+ await fs.writeFile("/user/project/project.inlang.json", settingsWithDeifferentFormatting)
154
176
 
155
177
  const project = await loadProject({
156
- settingsFilePath: "./project.inlang.json",
178
+ settingsFilePath: "/user/project/project.inlang.json",
157
179
  nodeishFs: fs,
158
180
  _import,
159
181
  })
160
182
 
161
- const settingsOnDisk = await fs.readFile("./project.inlang.json", { encoding: "utf-8" })
183
+ const settingsOnDisk = await fs.readFile("/user/project/project.inlang.json", {
184
+ encoding: "utf-8",
185
+ })
162
186
  expect(settingsOnDisk).toBe(settingsWithDeifferentFormatting)
163
187
 
164
188
  project.setSettings(project.settings())
165
189
  // TODO: how can we await `setsettings` correctly
166
190
  await new Promise((resolve) => setTimeout(resolve, 0))
167
191
 
168
- const newsettingsOnDisk = await fs.readFile("./project.inlang.json", { encoding: "utf-8" })
192
+ const newsettingsOnDisk = await fs.readFile("/user/project/project.inlang.json", {
193
+ encoding: "utf-8",
194
+ })
169
195
  expect(newsettingsOnDisk).not.toBe(settingsWithDeifferentFormatting)
170
196
  })
171
197
  })
@@ -178,10 +204,11 @@ describe("initialization", () => {
178
204
  } satisfies InlangModule)
179
205
 
180
206
  const fs = createNodeishMemoryFs()
181
- await fs.writeFile("./project.inlang.json", JSON.stringify(settings))
207
+ await fs.mkdir("/user/project", { recursive: true })
208
+ await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings))
182
209
 
183
210
  const project = await loadProject({
184
- settingsFilePath: "./project.inlang.json",
211
+ settingsFilePath: "/user/project/project.inlang.json",
185
212
  nodeishFs: fs,
186
213
  _import: $badImport,
187
214
  })
@@ -210,9 +237,10 @@ describe("functionality", () => {
210
237
  describe("settings", () => {
211
238
  it("should return the settings", async () => {
212
239
  const fs = await createNodeishMemoryFs()
213
- await fs.writeFile("./project.inlang.json", JSON.stringify(settings))
240
+ await fs.mkdir("/user/project", { recursive: true })
241
+ await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings))
214
242
  const project = await loadProject({
215
- settingsFilePath: "./project.inlang.json",
243
+ settingsFilePath: "/user/project/project.inlang.json",
216
244
  nodeishFs: fs,
217
245
  _import,
218
246
  })
@@ -222,9 +250,10 @@ describe("functionality", () => {
222
250
 
223
251
  it("should set a new settings", async () => {
224
252
  const fs = await createNodeishMemoryFs()
225
- await fs.writeFile("./project.inlang.json", JSON.stringify(settings))
253
+ await fs.mkdir("/user/project", { recursive: true })
254
+ await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings))
226
255
  const project = await loadProject({
227
- settingsFilePath: "./project.inlang.json",
256
+ settingsFilePath: "/user/project/project.inlang.json",
228
257
  nodeishFs: fs,
229
258
  _import,
230
259
  })
@@ -247,9 +276,10 @@ describe("functionality", () => {
247
276
  describe("setSettings", () => {
248
277
  it("should fail if settings is not valid", async () => {
249
278
  const fs = await createNodeishMemoryFs()
250
- await fs.writeFile("./project.inlang.json", JSON.stringify(settings))
279
+ await fs.mkdir("/user/project", { recursive: true })
280
+ await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings))
251
281
  const project = await loadProject({
252
- settingsFilePath: "./project.inlang.json",
282
+ settingsFilePath: "/user/project/project.inlang.json",
253
283
  nodeishFs: fs,
254
284
  _import,
255
285
  })
@@ -261,14 +291,15 @@ describe("functionality", () => {
261
291
 
262
292
  it("should write settings to disk", async () => {
263
293
  const fs = await createNodeishMemoryFs()
264
- await fs.writeFile("./project.inlang.json", JSON.stringify(settings))
294
+ await fs.mkdir("/user/project", { recursive: true })
295
+ await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings))
265
296
  const project = await loadProject({
266
- settingsFilePath: "./project.inlang.json",
297
+ settingsFilePath: "/user/project/project.inlang.json",
267
298
  nodeishFs: fs,
268
299
  _import,
269
300
  })
270
301
 
271
- const before = await fs.readFile("./project.inlang.json", { encoding: "utf-8" })
302
+ const before = await fs.readFile("/user/project/project.inlang.json", { encoding: "utf-8" })
272
303
  expect(before).toBeDefined()
273
304
 
274
305
  const result = project.setSettings({ ...settings, languageTags: [] })
@@ -278,7 +309,7 @@ describe("functionality", () => {
278
309
  // TODO: how to wait for fs.writeFile to finish?
279
310
  await new Promise((resolve) => setTimeout(resolve, 0))
280
311
 
281
- const after = await fs.readFile("./project.inlang.json", { encoding: "utf-8" })
312
+ const after = await fs.readFile("/user/project/project.inlang.json", { encoding: "utf-8" })
282
313
  expect(after).toBeDefined()
283
314
  expect(after).not.toBe(before)
284
315
  })
@@ -292,9 +323,10 @@ describe("functionality", () => {
292
323
  languageTags: ["en"],
293
324
  modules: ["plugin.js", "lintRule.js"],
294
325
  }
295
- await fs.writeFile("./project.inlang.json", JSON.stringify(settings))
326
+ await fs.mkdir("/user/project", { recursive: true })
327
+ await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings))
296
328
  const project = await loadProject({
297
- settingsFilePath: "./project.inlang.json",
329
+ settingsFilePath: "/user/project/project.inlang.json",
298
330
  nodeishFs: fs,
299
331
  _import,
300
332
  })
@@ -324,10 +356,11 @@ describe("functionality", () => {
324
356
  modules: ["plugin.js", "lintRule.js"],
325
357
  }
326
358
 
327
- await fs.writeFile("./project.inlang.json", JSON.stringify(settings))
359
+ await fs.mkdir("/user/project", { recursive: true })
360
+ await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings))
328
361
 
329
362
  const project = await loadProject({
330
- settingsFilePath: "./project.inlang.json",
363
+ settingsFilePath: "/user/project/project.inlang.json",
331
364
  nodeishFs: fs,
332
365
  _import,
333
366
  })
@@ -357,8 +390,9 @@ describe("functionality", () => {
357
390
  saveMessages: () => undefined,
358
391
  }
359
392
  const fs = await createNodeishMemoryFs()
393
+ await fs.mkdir("/user/project", { recursive: true })
360
394
  await fs.writeFile(
361
- "./project.inlang.json",
395
+ "/user/project/project.inlang.json",
362
396
  JSON.stringify({
363
397
  sourceLanguageTag: "en",
364
398
  languageTags: ["en"],
@@ -372,7 +406,7 @@ describe("functionality", () => {
372
406
  } satisfies InlangModule
373
407
  }
374
408
  const project = await loadProject({
375
- settingsFilePath: "./project.inlang.json",
409
+ settingsFilePath: "/user/project/project.inlang.json",
376
410
  nodeishFs: fs,
377
411
  _import,
378
412
  })
@@ -405,8 +439,9 @@ describe("functionality", () => {
405
439
  saveMessages: () => undefined,
406
440
  }
407
441
  const fs = await createNodeishMemoryFs()
442
+ await fs.mkdir("/user/project", { recursive: true })
408
443
  await fs.writeFile(
409
- "./project.settings.json",
444
+ "/user/project/project.inlang.json",
410
445
  JSON.stringify({
411
446
  sourceLanguageTag: "en",
412
447
  languageTags: ["en"],
@@ -420,7 +455,7 @@ describe("functionality", () => {
420
455
  }
421
456
 
422
457
  const project = await loadProject({
423
- settingsFilePath: "./project.settings.json",
458
+ settingsFilePath: "/user/project/project.inlang.json",
424
459
  nodeishFs: fs,
425
460
  _import,
426
461
  })
@@ -436,9 +471,10 @@ describe("functionality", () => {
436
471
  describe("errors", () => {
437
472
  it("should return the errors", async () => {
438
473
  const fs = await createNodeishMemoryFs()
439
- await fs.writeFile("./project.inlang.json", JSON.stringify(settings))
474
+ await fs.mkdir("/user/project", { recursive: true })
475
+ await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings))
440
476
  const project = await loadProject({
441
- settingsFilePath: "./project.inlang.json",
477
+ settingsFilePath: "/user/project/project.inlang.json",
442
478
  nodeishFs: fs,
443
479
  _import,
444
480
  })
@@ -451,9 +487,10 @@ describe("functionality", () => {
451
487
  describe("customApi", () => {
452
488
  it("should return the app specific api", async () => {
453
489
  const fs = await createNodeishMemoryFs()
454
- await fs.writeFile("./project.inlang.json", JSON.stringify(settings))
490
+ await fs.mkdir("/user/project", { recursive: true })
491
+ await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings))
455
492
  const project = await loadProject({
456
- settingsFilePath: "./project.inlang.json",
493
+ settingsFilePath: "/user/project/project.inlang.json",
457
494
  nodeishFs: fs,
458
495
  _import,
459
496
  })
@@ -467,9 +504,10 @@ describe("functionality", () => {
467
504
  describe("messages", () => {
468
505
  it("should return the messages", async () => {
469
506
  const fs = await createNodeishMemoryFs()
470
- await fs.writeFile("./project.inlang.json", JSON.stringify(settings))
507
+ await fs.mkdir("/user/project", { recursive: true })
508
+ await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings))
471
509
  const project = await loadProject({
472
- settingsFilePath: "./project.inlang.json",
510
+ settingsFilePath: "/user/project/project.inlang.json",
473
511
  nodeishFs: fs,
474
512
  _import,
475
513
  })
@@ -491,7 +529,8 @@ describe("functionality", () => {
491
529
  },
492
530
  }
493
531
 
494
- await fs.writeFile("./project.inlang.json", JSON.stringify(settings))
532
+ await fs.mkdir("/user/project", { recursive: true })
533
+ await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings))
495
534
 
496
535
  await fs.mkdir("./resources")
497
536
 
@@ -512,7 +551,7 @@ describe("functionality", () => {
512
551
  }
513
552
 
514
553
  const project = await loadProject({
515
- settingsFilePath: "./project.inlang.json",
554
+ settingsFilePath: "/user/project/project.inlang.json",
516
555
  nodeishFs: fs,
517
556
  _import,
518
557
  })
@@ -639,14 +678,79 @@ describe("functionality", () => {
639
678
  },
640
679
  ])
641
680
  })
681
+
682
+ /*
683
+ * Passing all messages to saveMessages() simplifies plugins by an order of magnitude.
684
+ *
685
+ * The alternative would be to pass only the messages that changed to saveMessages().
686
+ * But, this would require plugins to maintain a separate data structure of messages
687
+ * and create optimizations, leading to (unjustified) complexity for plugin authors.
688
+ *
689
+ * Pros:
690
+ * - plugins don't need to transform the data (time complexity).
691
+ * - plugins don't to maintain a separate data structure (space complexity).
692
+ * - plugin authors don't need to deal with optimizations (ecosystem complexity).
693
+ *
694
+ * Cons:
695
+ * - Might be slow for a large number of messages. The requirement hasn't popped up yet though.
696
+ */
697
+ it("should pass all messages, regardless of which message changed, to saveMessages()", async () => {
698
+ const fs = createNodeishMemoryFs()
699
+
700
+ const settings: ProjectSettings = {
701
+ sourceLanguageTag: "en",
702
+ languageTags: ["en", "de"],
703
+ modules: ["plugin.js"],
704
+ "plugin.project.json": {
705
+ pathPattern: "./resources/{languageTag}.json",
706
+ },
707
+ }
708
+
709
+ await fs.mkdir("/user/project", { recursive: true })
710
+ await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings))
711
+
712
+ const mockSaveFn = vi.fn()
713
+
714
+ const _mockPlugin: Plugin = {
715
+ id: "plugin.project.json",
716
+ description: "Mock plugin description",
717
+ displayName: "Mock Plugin",
718
+ loadMessages: () => [
719
+ createMessage("first", { en: "first message" }),
720
+ createMessage("second", { en: "second message" }),
721
+ createMessage("third", { en: "third message" }),
722
+ ],
723
+ saveMessages: mockSaveFn,
724
+ }
725
+
726
+ const _import = async () => {
727
+ return {
728
+ default: _mockPlugin,
729
+ } satisfies InlangModule
730
+ }
731
+
732
+ const project = await loadProject({
733
+ settingsFilePath: "/user/project/project.inlang.json",
734
+ nodeishFs: fs,
735
+ _import,
736
+ })
737
+
738
+ project.query.messages.create({ data: createMessage("fourth", { en: "fourth message" }) })
739
+
740
+ await new Promise((resolve) => setTimeout(resolve, 510))
741
+
742
+ expect(mockSaveFn.mock.calls.length).toBe(1)
743
+ expect(mockSaveFn.mock.calls[0][0].messages).toHaveLength(4)
744
+ })
642
745
  })
643
746
 
644
747
  describe("lint", () => {
645
748
  it.todo("should throw if lint reports are not initialized yet", async () => {
646
749
  const fs = await createNodeishMemoryFs()
647
- await fs.writeFile("./project.inlang.json", JSON.stringify(settings))
750
+ await fs.mkdir("/user/project", { recursive: true })
751
+ await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings))
648
752
  const project = await loadProject({
649
- settingsFilePath: "./project.inlang.json",
753
+ settingsFilePath: "/user/project/project.inlang.json",
650
754
  nodeishFs: fs,
651
755
  _import,
652
756
  })
@@ -665,9 +769,10 @@ describe("functionality", () => {
665
769
  modules: ["lintRule.js"],
666
770
  }
667
771
  const fs = createNodeishMemoryFs()
668
- await fs.writeFile("./project.inlang.json", JSON.stringify(settings))
772
+ await fs.mkdir("/user/project", { recursive: true })
773
+ await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings))
669
774
  const project = await loadProject({
670
- settingsFilePath: "./project.inlang.json",
775
+ settingsFilePath: "/user/project/project.inlang.json",
671
776
  nodeishFs: fs,
672
777
  _import: async () => ({
673
778
  default: mockMessageLintRule,