@inlang/sdk 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/adapter/solidAdapter.test.js +5 -5
  2. package/dist/createNodeishFsWithAbsolutePaths.d.ts +13 -0
  3. package/dist/createNodeishFsWithAbsolutePaths.d.ts.map +1 -0
  4. package/dist/createNodeishFsWithAbsolutePaths.js +29 -0
  5. package/dist/createNodeishFsWithAbsolutePaths.test.d.ts.map +1 -0
  6. package/dist/createNodeishFsWithAbsolutePaths.test.js +37 -0
  7. package/dist/errors.d.ts +5 -0
  8. package/dist/errors.d.ts.map +1 -1
  9. package/dist/errors.js +6 -0
  10. package/dist/loadProject.d.ts.map +1 -1
  11. package/dist/loadProject.js +29 -6
  12. package/dist/loadProject.test.js +34 -7
  13. package/dist/resolve-modules/plugins/errors.d.ts +0 -5
  14. package/dist/resolve-modules/plugins/errors.d.ts.map +1 -1
  15. package/dist/resolve-modules/plugins/errors.js +0 -6
  16. package/dist/resolve-modules/plugins/resolvePlugins.d.ts.map +1 -1
  17. package/dist/resolve-modules/plugins/resolvePlugins.js +1 -12
  18. package/dist/resolve-modules/plugins/resolvePlugins.test.js +1 -15
  19. package/dist/resolve-modules/plugins/types.d.ts +2 -2
  20. package/dist/resolve-modules/plugins/types.d.ts.map +1 -1
  21. package/package.json +2 -2
  22. package/src/adapter/solidAdapter.test.ts +7 -5
  23. package/src/createNodeishFsWithAbsolutePaths.test.ts +47 -0
  24. package/src/createNodeishFsWithAbsolutePaths.ts +40 -0
  25. package/src/errors.ts +7 -0
  26. package/src/loadProject.test.ts +43 -6
  27. package/src/loadProject.ts +41 -9
  28. package/src/resolve-modules/plugins/errors.ts +0 -7
  29. package/src/resolve-modules/plugins/resolvePlugins.test.ts +0 -18
  30. package/src/resolve-modules/plugins/resolvePlugins.ts +0 -15
  31. package/src/resolve-modules/plugins/types.ts +0 -2
  32. package/dist/resolve-modules/createNodeishFsWithAbsolutePaths.d.ts +0 -6
  33. package/dist/resolve-modules/createNodeishFsWithAbsolutePaths.d.ts.map +0 -1
  34. package/dist/resolve-modules/createNodeishFsWithAbsolutePaths.js +0 -20
  35. package/dist/resolve-modules/createNodeishFsWithAbsolutePaths.test.d.ts.map +0 -1
  36. package/dist/resolve-modules/createNodeishFsWithAbsolutePaths.test.js +0 -85
  37. package/src/resolve-modules/createNodeishFsWithAbsolutePaths.test.ts +0 -102
  38. package/src/resolve-modules/createNodeishFsWithAbsolutePaths.ts +0 -30
  39. /package/dist/{resolve-modules/createNodeishFsWithAbsolutePaths.test.d.ts → createNodeishFsWithAbsolutePaths.test.d.ts} +0 -0
@@ -88,7 +88,7 @@ describe("config", () => {
88
88
  const newConfig = { ...project.settings(), languageTags: ["en", "de"] };
89
89
  project.setSettings(newConfig);
90
90
  // TODO: how can we await `setConfig` correctly
91
- await new Promise((resolve) => setTimeout(resolve, 0));
91
+ await new Promise((resolve) => setTimeout(resolve, 510));
92
92
  expect(counter).toBe(2); // 2 times because effect creation + set
93
93
  expect(project.settings()).toStrictEqual(newConfig);
94
94
  });
@@ -162,11 +162,11 @@ describe("messages", () => {
162
162
  counter += 1;
163
163
  });
164
164
  expect(Object.values(project.query.messages.getAll()).length).toBe(2);
165
- project.setSettings({ ...project.settings(), languageTags: [] });
165
+ project.setSettings({ ...project.settings(), languageTags: ["en"] });
166
166
  // TODO: how can we await `setConfig` correctly
167
- await new Promise((resolve) => setTimeout(resolve, 0));
168
- expect(counter).toBe(2); // 2 times because effect creation + set
169
- expect(Object.values(project.query.messages.getAll()).length).toBe(0);
167
+ await new Promise((resolve) => setTimeout(resolve, 510));
168
+ expect(counter).toBe(1); // 2 times because effect creation + set
169
+ expect(Object.values(project.query.messages.getAll()).length).toBe(2);
170
170
  });
171
171
  it("should react to changes in messages", async () => {
172
172
  const fs = createNodeishMemoryFs();
@@ -0,0 +1,13 @@
1
+ import type { NodeishFilesystemSubset } from "@inlang/plugin";
2
+ export declare const isAbsolutePath: (path: string) => boolean;
3
+ /**
4
+ * Wraps the nodeish filesystem subset with a function that intercepts paths
5
+ * and prepends the base path.
6
+ *
7
+ * The paths are resolved from the `settingsFilePath` argument.
8
+ */
9
+ export declare const createNodeishFsWithAbsolutePaths: (args: {
10
+ settingsFilePath: string;
11
+ nodeishFs: NodeishFilesystemSubset;
12
+ }) => NodeishFilesystemSubset;
13
+ //# sourceMappingURL=createNodeishFsWithAbsolutePaths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createNodeishFsWithAbsolutePaths.d.ts","sourceRoot":"","sources":["../src/createNodeishFsWithAbsolutePaths.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAA;AAG7D,eAAO,MAAM,cAAc,SAAU,MAAM,YAAwB,CAAA;AAEnE;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,SAAU;IACtD,gBAAgB,EAAE,MAAM,CAAA;IACxB,SAAS,EAAE,uBAAuB,CAAA;CAClC,KAAG,uBAyBH,CAAA"}
@@ -0,0 +1,29 @@
1
+ import { normalizePath } from "@lix-js/fs";
2
+ export const isAbsolutePath = (path) => /^[/\\]/.test(path);
3
+ /**
4
+ * Wraps the nodeish filesystem subset with a function that intercepts paths
5
+ * and prepends the base path.
6
+ *
7
+ * The paths are resolved from the `settingsFilePath` argument.
8
+ */
9
+ export const createNodeishFsWithAbsolutePaths = (args) => {
10
+ if (!isAbsolutePath(args.settingsFilePath)) {
11
+ throw new Error("The argument `settingsFilePath` must be an absolute path.");
12
+ }
13
+ // get the base path of the settings file by
14
+ // removing the file name from the path
15
+ const basePath = args.settingsFilePath.split("/").slice(0, -1).join("/");
16
+ const makeAbsolute = (path) => {
17
+ if (isAbsolutePath(path)) {
18
+ return normalizePath(path);
19
+ }
20
+ return normalizePath(basePath + "/" + path);
21
+ };
22
+ return {
23
+ // @ts-expect-error
24
+ readFile: (path, options) => args.nodeishFs.readFile(makeAbsolute(path), options),
25
+ readdir: (path) => args.nodeishFs.readdir(makeAbsolute(path)),
26
+ mkdir: (path) => args.nodeishFs.mkdir(makeAbsolute(path)),
27
+ writeFile: (path, data) => args.nodeishFs.writeFile(makeAbsolute(path), data),
28
+ };
29
+ };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createNodeishFsWithAbsolutePaths.test.d.ts","sourceRoot":"","sources":["../src/createNodeishFsWithAbsolutePaths.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,37 @@
1
+ import { it, expect, vi } from "vitest";
2
+ import { createNodeishFsWithAbsolutePaths } from "./createNodeishFsWithAbsolutePaths.js";
3
+ it("throws an error if settingsFilePath is not an absolute path", () => {
4
+ const relativePath = "relative/path";
5
+ expect(() => createNodeishFsWithAbsolutePaths({ settingsFilePath: relativePath, nodeishFs: {} })).toThrow();
6
+ });
7
+ it("intercepts paths correctly for readFile", async () => {
8
+ const settingsFilePath = `/Users/samuel/Documents/paraglide/example/project.inlang.json`;
9
+ const filePaths = [
10
+ ["file.txt", `/Users/samuel/Documents/paraglide/example/file.txt`],
11
+ ["./file.txt", `/Users/samuel/Documents/paraglide/example/file.txt`],
12
+ ["./folder/file.txt", `/Users/samuel/Documents/paraglide/example/folder/file.txt`],
13
+ ["../file.txt", `/Users/samuel/Documents/paraglide/file.txt`],
14
+ ["../folder/file.txt", `/Users/samuel/Documents/paraglide/folder/file.txt`],
15
+ ["../../file.txt", `/Users/samuel/Documents/file.txt`],
16
+ ["../../../file.txt", `/Users/samuel/file.txt`],
17
+ ];
18
+ const mockNodeishFs = {
19
+ readFile: vi.fn(),
20
+ readdir: vi.fn(),
21
+ mkdir: vi.fn(),
22
+ writeFile: vi.fn(),
23
+ };
24
+ const interceptedFs = createNodeishFsWithAbsolutePaths({
25
+ settingsFilePath,
26
+ nodeishFs: mockNodeishFs,
27
+ });
28
+ for (const [path, expectedPath] of filePaths) {
29
+ for (const fn of Object.keys(mockNodeishFs)) {
30
+ // @ts-expect-error
31
+ await interceptedFs[fn](path);
32
+ // @ts-expect-error
33
+ // expect the first argument to be the expectedPath
34
+ expect(mockNodeishFs[fn].mock.lastCall[0]).toBe(expectedPath);
35
+ }
36
+ }
37
+ });
package/dist/errors.d.ts CHANGED
@@ -1,4 +1,9 @@
1
1
  import type { ValueError } from "@sinclair/typebox/errors";
2
+ export declare class LoadProjectInvalidArgument extends Error {
3
+ constructor(message: string, options: {
4
+ argument: string;
5
+ });
6
+ }
2
7
  export declare class ProjectSettingsInvalidError extends Error {
3
8
  constructor(options: {
4
9
  errors: ValueError[];
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAE1D,qBAAa,2BAA4B,SAAQ,KAAK;gBACzC,OAAO,EAAE;QAAE,MAAM,EAAE,UAAU,EAAE,CAAA;KAAE;CAQ7C;AAED,qBAAa,kCAAmC,SAAQ,KAAK;gBAChD,OAAO,EAAE;QAAE,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE;CAOnE;AAED,qBAAa,gCAAiC,SAAQ,KAAK;gBAC9C,OAAO,EAAE;QAAE,KAAK,CAAC,EAAE,YAAY,CAAC,OAAO,CAAC,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE;CAIpE;AAED,qBAAa,uBAAwB,SAAQ,KAAK;gBACrC,OAAO,EAAE;QAAE,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,CAAA;KAAE;CAIrD;AAED,qBAAa,uBAAwB,SAAQ,KAAK;gBACrC,OAAO,EAAE;QAAE,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,CAAA;KAAE;CAIrD"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAE1D,qBAAa,0BAA2B,SAAQ,KAAK;gBACxC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE;CAI1D;AAED,qBAAa,2BAA4B,SAAQ,KAAK;gBACzC,OAAO,EAAE;QAAE,MAAM,EAAE,UAAU,EAAE,CAAA;KAAE;CAQ7C;AAED,qBAAa,kCAAmC,SAAQ,KAAK;gBAChD,OAAO,EAAE;QAAE,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE;CAOnE;AAED,qBAAa,gCAAiC,SAAQ,KAAK;gBAC9C,OAAO,EAAE;QAAE,KAAK,CAAC,EAAE,YAAY,CAAC,OAAO,CAAC,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE;CAIpE;AAED,qBAAa,uBAAwB,SAAQ,KAAK;gBACrC,OAAO,EAAE;QAAE,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,CAAA;KAAE;CAIrD;AAED,qBAAa,uBAAwB,SAAQ,KAAK;gBACrC,OAAO,EAAE;QAAE,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,CAAA;KAAE;CAIrD"}
package/dist/errors.js CHANGED
@@ -1,3 +1,9 @@
1
+ export class LoadProjectInvalidArgument extends Error {
2
+ constructor(message, options) {
3
+ super(`The argument "${options.argument}" of loadProject() is invalid: ${message}`);
4
+ this.name = "LoadProjectInvalidArgument";
5
+ }
6
+ }
1
7
  export class ProjectSettingsInvalidError extends Error {
2
8
  constructor(options) {
3
9
  super(`The project settings are invalid:\n\n${options.errors
@@ -1 +1 @@
1
- {"version":3,"file":"loadProject.d.ts","sourceRoot":"","sources":["../src/loadProject.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACX,aAAa,EAGb,YAAY,EACZ,MAAM,UAAU,CAAA;AACjB,OAAO,EAAE,KAAK,cAAc,EAAkB,MAAM,4BAA4B,CAAA;AAahF,OAAO,EAA4B,KAAK,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAOjG;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW;sBACL,MAAM;eACb,uBAAuB;;qBAElB,MAAM,SAAS,OAAO,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI;MAC5D,QAAQ,aAAa,CA4LxB,CAAA;AAqGD,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAQtE"}
1
+ {"version":3,"file":"loadProject.d.ts","sourceRoot":"","sources":["../src/loadProject.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACX,aAAa,EAGb,YAAY,EACZ,MAAM,UAAU,CAAA;AACjB,OAAO,EAAE,KAAK,cAAc,EAAkB,MAAM,4BAA4B,CAAA;AAchF,OAAO,EAA4B,KAAK,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAWjG;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW;sBACL,MAAM;eACb,uBAAuB;;qBAElB,MAAM,SAAS,OAAO,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI;MAC5D,QAAQ,aAAa,CAuMxB,CAAA;AAuHD,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAQtE"}
@@ -1,6 +1,6 @@
1
1
  import { resolveModules } from "./resolve-modules/index.js";
2
- import { TypeCompiler } from "@sinclair/typebox/compiler";
3
- import { ProjectSettingsFileJSONSyntaxError, ProjectSettingsFileNotFoundError, ProjectSettingsInvalidError, PluginLoadMessagesError, PluginSaveMessagesError, } from "./errors.js";
2
+ import { TypeCompiler, ValueErrorType } from "@sinclair/typebox/compiler";
3
+ import { ProjectSettingsFileJSONSyntaxError, ProjectSettingsFileNotFoundError, ProjectSettingsInvalidError, PluginLoadMessagesError, PluginSaveMessagesError, LoadProjectInvalidArgument, } from "./errors.js";
4
4
  import { createRoot, createSignal, createEffect } from "./reactivity/solid.js";
5
5
  import { createMessagesQuery } from "./createMessagesQuery.js";
6
6
  import { debounce } from "throttle-debounce";
@@ -8,7 +8,8 @@ import { createMessageLintReportsQuery } from "./createMessageLintReportsQuery.j
8
8
  import { ProjectSettings, Message } from "./versionedInterfaces.js";
9
9
  import { tryCatch } from "@inlang/result";
10
10
  import { migrateIfOutdated } from "@inlang/project-settings/migration";
11
- import { createNodeishFsWithAbsolutePaths } from "./resolve-modules/createNodeishFsWithAbsolutePaths.js";
11
+ import { createNodeishFsWithAbsolutePaths, isAbsolutePath, } from "./createNodeishFsWithAbsolutePaths.js";
12
+ import { normalizePath } from "@lix-js/fs";
12
13
  const settingsCompiler = TypeCompiler.Compile(ProjectSettings);
13
14
  /**
14
15
  * Creates an inlang instance.
@@ -21,17 +22,25 @@ const settingsCompiler = TypeCompiler.Compile(ProjectSettings);
21
22
  *
22
23
  */
23
24
  export const loadProject = async (args) => {
25
+ // -- validation --------------------------------------------------------
26
+ //! the only place where throwing is acceptable because the project
27
+ //! won't even be loaded. do not throw anywhere else. otherwise, apps
28
+ //! can't handle errors gracefully.
29
+ if (!isAbsolutePath(args.settingsFilePath)) {
30
+ throw new LoadProjectInvalidArgument(`Expected an absolute path but received "${args.settingsFilePath}".`, { argument: "settingsFilePath" });
31
+ }
32
+ const settingsFilePath = normalizePath(args.settingsFilePath);
33
+ // -- load project ------------------------------------------------------
24
34
  return await createRoot(async () => {
25
35
  const [initialized, markInitAsComplete, markInitAsFailed] = createAwaitable();
26
- // -- absolute path env -----------------------------------------------
27
36
  const nodeishFs = createNodeishFsWithAbsolutePaths({
37
+ settingsFilePath,
28
38
  nodeishFs: args.nodeishFs,
29
- basePath: args.settingsFilePath.replace(/(.*?)[^/]*\..*$/, "$1"), // get directory of settings file
30
39
  });
31
40
  // -- settings ------------------------------------------------------------
32
41
  const [settings, _setSettings] = createSignal();
33
42
  createEffect(() => {
34
- loadSettings({ settingsFilePath: args.settingsFilePath, nodeishFs })
43
+ loadSettings({ settingsFilePath, nodeishFs })
35
44
  .then((settings) => {
36
45
  setSettings(settings);
37
46
  // rename settings to get a convenient access to the data in Posthog
@@ -192,6 +201,20 @@ const parseSettings = (settings) => {
192
201
  });
193
202
  }
194
203
  }
204
+ const { sourceLanguageTag, languageTags } = settings;
205
+ if (!languageTags.includes(sourceLanguageTag)) {
206
+ throw new ProjectSettingsInvalidError({
207
+ errors: [
208
+ {
209
+ message: `The sourceLanguageTag "${sourceLanguageTag}" is not included in the languageTags "${languageTags.join('", "')}". Please add it to the languageTags.`,
210
+ type: ValueErrorType.String,
211
+ schema: ProjectSettings,
212
+ value: sourceLanguageTag,
213
+ path: "sourceLanguageTag",
214
+ },
215
+ ],
216
+ });
217
+ }
195
218
  return withMigration;
196
219
  };
197
220
  const _writeSettingsToDisk = async (args) => {
@@ -1,9 +1,10 @@
1
1
  /* eslint-disable @typescript-eslint/no-non-null-assertion */
2
2
  import { describe, it, expect, vi } from "vitest";
3
3
  import { loadProject } from "./loadProject.js";
4
- import { ProjectSettingsFileJSONSyntaxError, ProjectSettingsFileNotFoundError, ProjectSettingsInvalidError, } from "./errors.js";
4
+ import { LoadProjectInvalidArgument, ProjectSettingsFileJSONSyntaxError, ProjectSettingsFileNotFoundError, ProjectSettingsInvalidError, } from "./errors.js";
5
5
  import { createNodeishMemoryFs } from "@lix-js/fs";
6
6
  import { createMessage } from "./test-utilities/createMessage.js";
7
+ import { tryCatch } from "@inlang/result";
7
8
  // ------------------------------------------------------------------------------------------------
8
9
  const getValue = (subscribable) => {
9
10
  let value;
@@ -80,6 +81,16 @@ const _import = async (name) => ({
80
81
  });
81
82
  // ------------------------------------------------------------------------------------------------
82
83
  describe("initialization", () => {
84
+ it("should throw if settingsFilePath is not an absolute path", async () => {
85
+ const fs = createNodeishMemoryFs();
86
+ const result = await tryCatch(() => loadProject({
87
+ settingsFilePath: "relative/path",
88
+ nodeishFs: fs,
89
+ _import,
90
+ }));
91
+ expect(result.error).toBeInstanceOf(LoadProjectInvalidArgument);
92
+ expect(result.data).toBeUndefined();
93
+ });
83
94
  describe("settings", () => {
84
95
  it("should return an error if settings file is not found", async () => {
85
96
  const fs = createNodeishMemoryFs();
@@ -225,6 +236,23 @@ describe("functionality", () => {
225
236
  expect(result.data).toBeUndefined();
226
237
  expect(result.error).toBeInstanceOf(ProjectSettingsInvalidError);
227
238
  });
239
+ it("should throw an error if sourceLanguageTag is not in languageTags", async () => {
240
+ const fs = await createNodeishMemoryFs();
241
+ await fs.mkdir("/user/project", { recursive: true });
242
+ const settings = {
243
+ sourceLanguageTag: "en",
244
+ languageTags: ["de"],
245
+ modules: [],
246
+ };
247
+ await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings));
248
+ const project = await loadProject({
249
+ settingsFilePath: "/user/project/project.inlang.json",
250
+ nodeishFs: fs,
251
+ _import,
252
+ });
253
+ expect(project.errors()).toHaveLength(1);
254
+ expect(project.errors()[0]).toBeInstanceOf(ProjectSettingsInvalidError);
255
+ });
228
256
  it("should write settings to disk", async () => {
229
257
  const fs = await createNodeishMemoryFs();
230
258
  await fs.mkdir("/user/project", { recursive: true });
@@ -236,7 +264,7 @@ describe("functionality", () => {
236
264
  });
237
265
  const before = await fs.readFile("/user/project/project.inlang.json", { encoding: "utf-8" });
238
266
  expect(before).toBeDefined();
239
- const result = project.setSettings({ ...settings, languageTags: [] });
267
+ const result = project.setSettings({ ...settings, languageTags: ["en"] });
240
268
  expect(result.data).toBeUndefined();
241
269
  expect(result.error).toBeUndefined();
242
270
  // TODO: how to wait for fs.writeFile to finish?
@@ -587,15 +615,14 @@ describe("functionality", () => {
587
615
  sourceLanguageTag: "en",
588
616
  languageTags: ["en", "de"],
589
617
  modules: ["plugin.js"],
590
- "plugin.project.json": {
618
+ "plugin.placeholder.name": {
591
619
  pathPattern: "./resources/{languageTag}.json",
592
620
  },
593
621
  };
594
- await fs.mkdir("/user/project", { recursive: true });
595
- await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings));
622
+ await fs.writeFile("./project.inlang.json", JSON.stringify(settings));
596
623
  const mockSaveFn = vi.fn();
597
624
  const _mockPlugin = {
598
- id: "plugin.project.json",
625
+ id: "plugin.placeholder.name",
599
626
  description: "Mock plugin description",
600
627
  displayName: "Mock Plugin",
601
628
  loadMessages: () => [
@@ -611,7 +638,7 @@ describe("functionality", () => {
611
638
  };
612
639
  };
613
640
  const project = await loadProject({
614
- settingsFilePath: "/user/project/project.inlang.json",
641
+ settingsFilePath: "/project.inlang.json",
615
642
  nodeishFs: fs,
616
643
  _import,
617
644
  });
@@ -5,11 +5,6 @@ export declare class PluginHasInvalidIdError extends Error {
5
5
  id: Plugin["id"];
6
6
  });
7
7
  }
8
- export declare class PluginUsesReservedNamespaceError extends Error {
9
- constructor(options: {
10
- id: Plugin["id"];
11
- });
12
- }
13
8
  export declare class PluginHasInvalidSchemaError extends Error {
14
9
  constructor(options: {
15
10
  id: Plugin["id"];
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/resolve-modules/plugins/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAC5C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAE1D,qBAAa,uBAAwB,SAAQ,KAAK;gBACrC,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,CAAA;KAAE;CAMzC;AAED,qBAAa,gCAAiC,SAAQ,KAAK;gBAC9C,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,CAAA;KAAE;CAIzC;AAED,qBAAa,2BAA4B,SAAQ,KAAK;gBACzC,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;QAAC,MAAM,EAAE,UAAU,EAAE,CAAA;KAAE;CAQ/D;AAED,qBAAa,6CAA8C,SAAQ,KAAK;gBAC3D,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,CAAA;KAAE;CAMzC;AAED,qBAAa,6CAA8C,SAAQ,KAAK;gBAC3D,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,CAAA;KAAE;CAMzC;AAED,qBAAa,mCAAoC,SAAQ,KAAK;gBACjD,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;QAAC,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,CAAA;KAAE;CAIvE;AAED,qBAAa,0CAA2C,SAAQ,KAAK;;CAOpE"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/resolve-modules/plugins/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAC5C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAE1D,qBAAa,uBAAwB,SAAQ,KAAK;gBACrC,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,CAAA;KAAE;CAMzC;AAED,qBAAa,2BAA4B,SAAQ,KAAK;gBACzC,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;QAAC,MAAM,EAAE,UAAU,EAAE,CAAA;KAAE;CAQ/D;AAED,qBAAa,6CAA8C,SAAQ,KAAK;gBAC3D,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,CAAA;KAAE;CAMzC;AAED,qBAAa,6CAA8C,SAAQ,KAAK;gBAC3D,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,CAAA;KAAE;CAMzC;AAED,qBAAa,mCAAoC,SAAQ,KAAK;gBACjD,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;QAAC,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,CAAA;KAAE;CAIvE;AAED,qBAAa,0CAA2C,SAAQ,KAAK;;CAOpE"}
@@ -4,12 +4,6 @@ export class PluginHasInvalidIdError extends Error {
4
4
  this.name = "PluginHasInvalidIdError";
5
5
  }
6
6
  }
7
- export class PluginUsesReservedNamespaceError extends Error {
8
- constructor(options) {
9
- super(`Plugin ${options.id} uses reserved namespace 'inlang'.`);
10
- this.name = "PluginUsesReservedNamespaceError";
11
- }
12
- }
13
7
  export class PluginHasInvalidSchemaError extends Error {
14
8
  constructor(options) {
15
9
  super(`Plugin "${options.id}" has an invalid schema:\n\n${options.errors
@@ -1 +1 @@
1
- {"version":3,"file":"resolvePlugins.d.ts","sourceRoot":"","sources":["../../../src/resolve-modules/plugins/resolvePlugins.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAA;AAuBxD,eAAO,MAAM,cAAc,EAAE,sBAqH5B,CAAA"}
1
+ {"version":3,"file":"resolvePlugins.d.ts","sourceRoot":"","sources":["../../../src/resolve-modules/plugins/resolvePlugins.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAA;AAiBxD,eAAO,MAAM,cAAc,EAAE,sBA4G5B,CAAA"}
@@ -1,13 +1,8 @@
1
1
  import { Plugin } from "@inlang/plugin";
2
- import { PluginReturnedInvalidCustomApiError, PluginLoadMessagesFunctionAlreadyDefinedError, PluginSaveMessagesFunctionAlreadyDefinedError, PluginsDoNotProvideLoadOrSaveMessagesError, PluginHasInvalidIdError, PluginHasInvalidSchemaError, PluginUsesReservedNamespaceError, } from "./errors.js";
2
+ import { PluginReturnedInvalidCustomApiError, PluginLoadMessagesFunctionAlreadyDefinedError, PluginSaveMessagesFunctionAlreadyDefinedError, PluginsDoNotProvideLoadOrSaveMessagesError, PluginHasInvalidIdError, PluginHasInvalidSchemaError, } from "./errors.js";
3
3
  import { deepmerge } from "deepmerge-ts";
4
4
  import { TypeCompiler } from "@sinclair/typebox/compiler";
5
5
  import { tryCatch } from "@inlang/result";
6
- const whitelistedPlugins = [
7
- "plugin.inlang.json",
8
- "plugin.inlang.i18next",
9
- "plugin.inlang.paraglideJs",
10
- ];
11
6
  // @ts-ignore - type mismatch error
12
7
  const PluginCompiler = TypeCompiler.Compile(Plugin);
13
8
  export const resolvePlugins = async (args) => {
@@ -29,12 +24,6 @@ export const resolvePlugins = async (args) => {
29
24
  if (hasInvalidId) {
30
25
  result.errors.push(new PluginHasInvalidIdError({ id: plugin.id }));
31
26
  }
32
- // -- USES RESERVED NAMESPACE --
33
- if (plugin.id.includes("inlang") && !whitelistedPlugins.includes(plugin.id)) {
34
- result.errors.push(new PluginUsesReservedNamespaceError({
35
- id: plugin.id,
36
- }));
37
- }
38
27
  // -- USES INVALID SCHEMA --
39
28
  if (errors.length > 0) {
40
29
  result.errors.push(new PluginHasInvalidSchemaError({
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable @typescript-eslint/no-non-null-assertion */
2
2
  import { describe, expect, it } from "vitest";
3
3
  import { resolvePlugins } from "./resolvePlugins.js";
4
- import { PluginLoadMessagesFunctionAlreadyDefinedError, PluginSaveMessagesFunctionAlreadyDefinedError, PluginHasInvalidIdError, PluginUsesReservedNamespaceError, PluginReturnedInvalidCustomApiError, PluginHasInvalidSchemaError, PluginsDoNotProvideLoadOrSaveMessagesError, } from "./errors.js";
4
+ import { PluginLoadMessagesFunctionAlreadyDefinedError, PluginSaveMessagesFunctionAlreadyDefinedError, PluginHasInvalidIdError, PluginReturnedInvalidCustomApiError, PluginHasInvalidSchemaError, PluginsDoNotProvideLoadOrSaveMessagesError, } from "./errors.js";
5
5
  it("should return an error if a plugin uses an invalid id", async () => {
6
6
  const mockPlugin = {
7
7
  // @ts-expect-error - invalid id
@@ -37,20 +37,6 @@ it("should return an error if a plugin uses APIs that are not available", async
37
37
  });
38
38
  expect(resolved.errors[0]).toBeInstanceOf(PluginHasInvalidSchemaError);
39
39
  });
40
- it("should not initialize a plugin that uses the 'inlang' namespace except for inlang whitelisted plugins", async () => {
41
- const mockPlugin = {
42
- id: "plugin.inlang.notWhitelisted",
43
- description: { en: "My plugin description" },
44
- displayName: { en: "My plugin" },
45
- loadMessages: () => undefined,
46
- };
47
- const resolved = await resolvePlugins({
48
- plugins: [mockPlugin],
49
- settings: {},
50
- nodeishFs: {},
51
- });
52
- expect(resolved.errors[0]).toBeInstanceOf(PluginUsesReservedNamespaceError);
53
- });
54
40
  it("should expose the project settings including the plugin settings", async () => {
55
41
  const settings = {
56
42
  sourceLanguageTag: "en",
@@ -1,5 +1,5 @@
1
1
  import type { NodeishFilesystem } from "@lix-js/fs";
2
- import type { PluginReturnedInvalidCustomApiError, PluginLoadMessagesFunctionAlreadyDefinedError, PluginSaveMessagesFunctionAlreadyDefinedError, PluginHasInvalidIdError, PluginHasInvalidSchemaError, PluginUsesReservedNamespaceError, PluginsDoNotProvideLoadOrSaveMessagesError } from "./errors.js";
2
+ import type { PluginReturnedInvalidCustomApiError, PluginLoadMessagesFunctionAlreadyDefinedError, PluginSaveMessagesFunctionAlreadyDefinedError, PluginHasInvalidIdError, PluginHasInvalidSchemaError, PluginsDoNotProvideLoadOrSaveMessagesError } from "./errors.js";
3
3
  import type { Message } from "@inlang/message";
4
4
  import type { CustomApiInlangIdeExtension, Plugin } from "@inlang/plugin";
5
5
  import type { ProjectSettings } from "@inlang/project-settings";
@@ -18,7 +18,7 @@ export type ResolvePluginsFunction = (args: {
18
18
  nodeishFs: NodeishFilesystemSubset;
19
19
  }) => Promise<{
20
20
  data: ResolvedPluginApi;
21
- errors: Array<PluginReturnedInvalidCustomApiError | PluginLoadMessagesFunctionAlreadyDefinedError | PluginSaveMessagesFunctionAlreadyDefinedError | PluginHasInvalidIdError | PluginHasInvalidSchemaError | PluginUsesReservedNamespaceError | PluginsDoNotProvideLoadOrSaveMessagesError>;
21
+ errors: Array<PluginReturnedInvalidCustomApiError | PluginLoadMessagesFunctionAlreadyDefinedError | PluginSaveMessagesFunctionAlreadyDefinedError | PluginHasInvalidIdError | PluginHasInvalidSchemaError | PluginsDoNotProvideLoadOrSaveMessagesError>;
22
22
  }>;
23
23
  /**
24
24
  * The API after resolving the plugins.
@@ -1 +1 @@
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"}
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,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,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.11.0",
4
+ "version": "0.13.0",
5
5
  "license": "Apache-2.0",
6
6
  "publishConfig": {
7
7
  "access": "public"
@@ -38,7 +38,7 @@
38
38
  "@inlang/project-settings": "*",
39
39
  "@inlang/result": "*",
40
40
  "@lix-js/fs": "*",
41
- "@sinclair/typebox": "^0.31.0",
41
+ "@sinclair/typebox": "^0.31.17",
42
42
  "deepmerge-ts": "^5.1.0",
43
43
  "solid-js": "1.6.12",
44
44
  "throttle-debounce": "5.0.0",
@@ -109,8 +109,10 @@ describe("config", () => {
109
109
  const newConfig = { ...project.settings()!, languageTags: ["en", "de"] }
110
110
 
111
111
  project.setSettings(newConfig)
112
+
112
113
  // TODO: how can we await `setConfig` correctly
113
- await new Promise((resolve) => setTimeout(resolve, 0))
114
+ await new Promise((resolve) => setTimeout(resolve, 510))
115
+
114
116
  expect(counter).toBe(2) // 2 times because effect creation + set
115
117
  expect(project.settings()).toStrictEqual(newConfig)
116
118
  })
@@ -202,13 +204,13 @@ describe("messages", () => {
202
204
 
203
205
  expect(Object.values(project.query.messages.getAll()).length).toBe(2)
204
206
 
205
- project.setSettings({ ...project.settings()!, languageTags: [] })
207
+ project.setSettings({ ...project.settings()!, languageTags: ["en"] })
206
208
 
207
209
  // TODO: how can we await `setConfig` correctly
208
- await new Promise((resolve) => setTimeout(resolve, 0))
210
+ await new Promise((resolve) => setTimeout(resolve, 510))
209
211
 
210
- expect(counter).toBe(2) // 2 times because effect creation + set
211
- expect(Object.values(project.query.messages.getAll()).length).toBe(0)
212
+ expect(counter).toBe(1) // 2 times because effect creation + set
213
+ expect(Object.values(project.query.messages.getAll()).length).toBe(2)
212
214
  })
213
215
 
214
216
  it("should react to changes in messages", async () => {
@@ -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 basePath = args.settingsFilePath.split("/").slice(0, -1).join("/")
23
+
24
+ const makeAbsolute = (path: string) => {
25
+ if (isAbsolutePath(path)) {
26
+ return normalizePath(path)
27
+ }
28
+
29
+ return normalizePath(basePath + "/" + 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,12 +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"
13
14
  import { createMessage } from "./test-utilities/createMessage.js"
15
+ import { tryCatch } from "@inlang/result"
14
16
 
15
17
  // ------------------------------------------------------------------------------------------------
16
18
 
@@ -97,6 +99,20 @@ const _import: ImportFunction = async (name) =>
97
99
  // ------------------------------------------------------------------------------------------------
98
100
 
99
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
+
100
116
  describe("settings", () => {
101
117
  it("should return an error if settings file is not found", async () => {
102
118
  const fs = createNodeishMemoryFs()
@@ -273,6 +289,28 @@ describe("functionality", () => {
273
289
  expect(result.error).toBeInstanceOf(ProjectSettingsInvalidError)
274
290
  })
275
291
 
292
+ it("should throw an error if sourceLanguageTag is not in languageTags", async () => {
293
+ const fs = await createNodeishMemoryFs()
294
+ await fs.mkdir("/user/project", { recursive: true })
295
+
296
+ const settings: ProjectSettings = {
297
+ sourceLanguageTag: "en",
298
+ languageTags: ["de"],
299
+ modules: [],
300
+ }
301
+
302
+ await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings))
303
+
304
+ const project = await loadProject({
305
+ settingsFilePath: "/user/project/project.inlang.json",
306
+ nodeishFs: fs,
307
+ _import,
308
+ })
309
+
310
+ expect(project.errors()).toHaveLength(1)
311
+ expect(project.errors()![0]).toBeInstanceOf(ProjectSettingsInvalidError)
312
+ })
313
+
276
314
  it("should write settings to disk", async () => {
277
315
  const fs = await createNodeishMemoryFs()
278
316
  await fs.mkdir("/user/project", { recursive: true })
@@ -286,7 +324,7 @@ describe("functionality", () => {
286
324
  const before = await fs.readFile("/user/project/project.inlang.json", { encoding: "utf-8" })
287
325
  expect(before).toBeDefined()
288
326
 
289
- const result = project.setSettings({ ...settings, languageTags: [] })
327
+ const result = project.setSettings({ ...settings, languageTags: ["en"] })
290
328
  expect(result.data).toBeUndefined()
291
329
  expect(result.error).toBeUndefined()
292
330
 
@@ -685,18 +723,17 @@ describe("functionality", () => {
685
723
  sourceLanguageTag: "en",
686
724
  languageTags: ["en", "de"],
687
725
  modules: ["plugin.js"],
688
- "plugin.project.json": {
726
+ "plugin.placeholder.name": {
689
727
  pathPattern: "./resources/{languageTag}.json",
690
728
  },
691
729
  }
692
730
 
693
- await fs.mkdir("/user/project", { recursive: true })
694
- await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings))
731
+ await fs.writeFile("./project.inlang.json", JSON.stringify(settings))
695
732
 
696
733
  const mockSaveFn = vi.fn()
697
734
 
698
735
  const _mockPlugin: Plugin = {
699
- id: "plugin.project.json",
736
+ id: "plugin.placeholder.name",
700
737
  description: "Mock plugin description",
701
738
  displayName: "Mock Plugin",
702
739
  loadMessages: () => [
@@ -714,7 +751,7 @@ describe("functionality", () => {
714
751
  }
715
752
 
716
753
  const project = await loadProject({
717
- settingsFilePath: "/user/project/project.inlang.json",
754
+ settingsFilePath: "/project.inlang.json",
718
755
  nodeishFs: fs,
719
756
  _import,
720
757
  })
@@ -6,13 +6,14 @@ import type {
6
6
  Subscribable,
7
7
  } from "./api.js"
8
8
  import { type ImportFunction, resolveModules } from "./resolve-modules/index.js"
9
- import { TypeCompiler } from "@sinclair/typebox/compiler"
9
+ import { TypeCompiler, ValueErrorType } from "@sinclair/typebox/compiler"
10
10
  import {
11
11
  ProjectSettingsFileJSONSyntaxError,
12
12
  ProjectSettingsFileNotFoundError,
13
13
  ProjectSettingsInvalidError,
14
14
  PluginLoadMessagesError,
15
15
  PluginSaveMessagesError,
16
+ LoadProjectInvalidArgument,
16
17
  } from "./errors.js"
17
18
  import { createRoot, createSignal, createEffect } from "./reactivity/solid.js"
18
19
  import { createMessagesQuery } from "./createMessagesQuery.js"
@@ -21,7 +22,11 @@ import { createMessageLintReportsQuery } from "./createMessageLintReportsQuery.j
21
22
  import { ProjectSettings, Message, type NodeishFilesystemSubset } from "./versionedInterfaces.js"
22
23
  import { tryCatch, type Result } from "@inlang/result"
23
24
  import { migrateIfOutdated } from "@inlang/project-settings/migration"
24
- import { createNodeishFsWithAbsolutePaths } from "./resolve-modules/createNodeishFsWithAbsolutePaths.js"
25
+ import {
26
+ createNodeishFsWithAbsolutePaths,
27
+ isAbsolutePath,
28
+ } from "./createNodeishFsWithAbsolutePaths.js"
29
+ import { normalizePath } from "@lix-js/fs"
25
30
 
26
31
  const settingsCompiler = TypeCompiler.Compile(ProjectSettings)
27
32
 
@@ -41,21 +46,32 @@ export const loadProject = async (args: {
41
46
  _import?: ImportFunction
42
47
  _capture?: (id: string, props: Record<string, unknown>) => void
43
48
  }): Promise<InlangProject> => {
44
- return await createRoot(async () => {
45
- const [initialized, markInitAsComplete, markInitAsFailed] = createAwaitable()
49
+ // -- validation --------------------------------------------------------
50
+ //! the only place where throwing is acceptable because the project
51
+ //! won't even be loaded. do not throw anywhere else. otherwise, apps
52
+ //! can't handle errors gracefully.
53
+ if (!isAbsolutePath(args.settingsFilePath)) {
54
+ throw new LoadProjectInvalidArgument(
55
+ `Expected an absolute path but received "${args.settingsFilePath}".`,
56
+ { argument: "settingsFilePath" }
57
+ )
58
+ }
46
59
 
47
- // -- absolute path env -----------------------------------------------
60
+ const settingsFilePath = normalizePath(args.settingsFilePath)
48
61
 
62
+ // -- load project ------------------------------------------------------
63
+ return await createRoot(async () => {
64
+ const [initialized, markInitAsComplete, markInitAsFailed] = createAwaitable()
49
65
  const nodeishFs = createNodeishFsWithAbsolutePaths({
66
+ settingsFilePath,
50
67
  nodeishFs: args.nodeishFs,
51
- basePath: args.settingsFilePath.replace(/(.*?)[^/]*\..*$/, "$1"), // get directory of settings file
52
68
  })
53
69
 
54
70
  // -- settings ------------------------------------------------------------
55
71
 
56
72
  const [settings, _setSettings] = createSignal<ProjectSettings>()
57
73
  createEffect(() => {
58
- loadSettings({ settingsFilePath: args.settingsFilePath, nodeishFs })
74
+ loadSettings({ settingsFilePath, nodeishFs })
59
75
  .then((settings) => {
60
76
  setSettings(settings)
61
77
  // rename settings to get a convenient access to the data in Posthog
@@ -268,6 +284,24 @@ const parseSettings = (settings: unknown) => {
268
284
  })
269
285
  }
270
286
  }
287
+
288
+ const { sourceLanguageTag, languageTags } = settings as ProjectSettings
289
+ if (!languageTags.includes(sourceLanguageTag)) {
290
+ throw new ProjectSettingsInvalidError({
291
+ errors: [
292
+ {
293
+ message: `The sourceLanguageTag "${sourceLanguageTag}" is not included in the languageTags "${languageTags.join(
294
+ '", "'
295
+ )}". Please add it to the languageTags.`,
296
+ type: ValueErrorType.String,
297
+ schema: ProjectSettings,
298
+ value: sourceLanguageTag,
299
+ path: "sourceLanguageTag",
300
+ },
301
+ ],
302
+ })
303
+ }
304
+
271
305
  return withMigration
272
306
  }
273
307
 
@@ -338,5 +372,3 @@ export function createSubscribable<T>(signal: () => T): Subscribable<T> {
338
372
  },
339
373
  })
340
374
  }
341
-
342
-
@@ -10,13 +10,6 @@ export class PluginHasInvalidIdError extends Error {
10
10
  }
11
11
  }
12
12
 
13
- export class PluginUsesReservedNamespaceError extends Error {
14
- constructor(options: { id: Plugin["id"] }) {
15
- super(`Plugin ${options.id} uses reserved namespace 'inlang'.`)
16
- this.name = "PluginUsesReservedNamespaceError"
17
- }
18
- }
19
-
20
13
  export class PluginHasInvalidSchemaError extends Error {
21
14
  constructor(options: { id: Plugin["id"]; errors: ValueError[] }) {
22
15
  super(
@@ -5,7 +5,6 @@ import {
5
5
  PluginLoadMessagesFunctionAlreadyDefinedError,
6
6
  PluginSaveMessagesFunctionAlreadyDefinedError,
7
7
  PluginHasInvalidIdError,
8
- PluginUsesReservedNamespaceError,
9
8
  PluginReturnedInvalidCustomApiError,
10
9
  PluginHasInvalidSchemaError,
11
10
  PluginsDoNotProvideLoadOrSaveMessagesError,
@@ -54,23 +53,6 @@ it("should return an error if a plugin uses APIs that are not available", async
54
53
  expect(resolved.errors[0]).toBeInstanceOf(PluginHasInvalidSchemaError)
55
54
  })
56
55
 
57
- it("should not initialize a plugin that uses the 'inlang' namespace except for inlang whitelisted plugins", async () => {
58
- const mockPlugin: Plugin = {
59
- id: "plugin.inlang.notWhitelisted",
60
- description: { en: "My plugin description" },
61
- displayName: { en: "My plugin" },
62
- loadMessages: () => undefined as any,
63
- }
64
-
65
- const resolved = await resolvePlugins({
66
- plugins: [mockPlugin],
67
- settings: {} as any,
68
- nodeishFs: {} as any,
69
- })
70
-
71
- expect(resolved.errors[0]).toBeInstanceOf(PluginUsesReservedNamespaceError)
72
- })
73
-
74
56
  it("should expose the project settings including the plugin settings", async () => {
75
57
  const settings: ProjectSettings = {
76
58
  sourceLanguageTag: "en",
@@ -8,17 +8,11 @@ import {
8
8
  PluginsDoNotProvideLoadOrSaveMessagesError,
9
9
  PluginHasInvalidIdError,
10
10
  PluginHasInvalidSchemaError,
11
- PluginUsesReservedNamespaceError,
12
11
  } from "./errors.js"
13
12
  import { deepmerge } from "deepmerge-ts"
14
13
  import { TypeCompiler } from "@sinclair/typebox/compiler"
15
14
  import { tryCatch } from "@inlang/result"
16
15
 
17
- const whitelistedPlugins = [
18
- "plugin.inlang.json",
19
- "plugin.inlang.i18next",
20
- "plugin.inlang.paraglideJs",
21
- ]
22
16
  // @ts-ignore - type mismatch error
23
17
  const PluginCompiler = TypeCompiler.Compile(Plugin)
24
18
 
@@ -45,15 +39,6 @@ export const resolvePlugins: ResolvePluginsFunction = async (args) => {
45
39
  result.errors.push(new PluginHasInvalidIdError({ id: plugin.id }))
46
40
  }
47
41
 
48
- // -- USES RESERVED NAMESPACE --
49
- if (plugin.id.includes("inlang") && !whitelistedPlugins.includes(plugin.id)) {
50
- result.errors.push(
51
- new PluginUsesReservedNamespaceError({
52
- id: plugin.id,
53
- })
54
- )
55
- }
56
-
57
42
  // -- USES INVALID SCHEMA --
58
43
  if (errors.length > 0) {
59
44
  result.errors.push(
@@ -5,7 +5,6 @@ import type {
5
5
  PluginSaveMessagesFunctionAlreadyDefinedError,
6
6
  PluginHasInvalidIdError,
7
7
  PluginHasInvalidSchemaError,
8
- PluginUsesReservedNamespaceError,
9
8
  PluginsDoNotProvideLoadOrSaveMessagesError,
10
9
  } from "./errors.js"
11
10
  import type { Message } from "@inlang/message"
@@ -37,7 +36,6 @@ export type ResolvePluginsFunction = (args: {
37
36
  | PluginSaveMessagesFunctionAlreadyDefinedError
38
37
  | PluginHasInvalidIdError
39
38
  | PluginHasInvalidSchemaError
40
- | PluginUsesReservedNamespaceError
41
39
  | PluginsDoNotProvideLoadOrSaveMessagesError
42
40
  >
43
41
  }>
@@ -1,6 +0,0 @@
1
- import type { NodeishFilesystemSubset } from "@inlang/plugin";
2
- export declare const createNodeishFsWithAbsolutePaths: (args: {
3
- basePath: string;
4
- nodeishFs: NodeishFilesystemSubset;
5
- }) => NodeishFilesystemSubset;
6
- //# sourceMappingURL=createNodeishFsWithAbsolutePaths.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"createNodeishFsWithAbsolutePaths.d.ts","sourceRoot":"","sources":["../../src/resolve-modules/createNodeishFsWithAbsolutePaths.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAA;AAG7D,eAAO,MAAM,gCAAgC,SAAU;IACtD,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,uBAAuB,CAAA;CAClC,KAAG,uBAuBH,CAAA"}
@@ -1,20 +0,0 @@
1
- import { normalizePath } from "@lix-js/fs";
2
- export const createNodeishFsWithAbsolutePaths = (args) => {
3
- const isAbsolutePath = (path) => /^[/\\]/.test(path);
4
- if (!isAbsolutePath(args.basePath)) {
5
- throw new Error("The argument `settingsFilePath` of `loadProject()` must be an absolute path.");
6
- }
7
- const intercept = (path) => {
8
- if (isAbsolutePath(path)) {
9
- return path;
10
- }
11
- return normalizePath(args.basePath + "/" + path);
12
- };
13
- return {
14
- // @ts-expect-error
15
- readFile: (path, options) => args.nodeishFs.readFile(intercept(path), options),
16
- readdir: (path) => args.nodeishFs.readdir(intercept(path)),
17
- mkdir: (path) => args.nodeishFs.mkdir(intercept(path)),
18
- writeFile: (path, data) => args.nodeishFs.writeFile(intercept(path), data),
19
- };
20
- };
@@ -1 +0,0 @@
1
- {"version":3,"file":"createNodeishFsWithAbsolutePaths.test.d.ts","sourceRoot":"","sources":["../../src/resolve-modules/createNodeishFsWithAbsolutePaths.test.ts"],"names":[],"mappings":""}
@@ -1,85 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { createNodeishFsWithAbsolutePaths } from "./createNodeishFsWithAbsolutePaths.js";
3
- describe("createNodeishFsWithAbsolutePaths", () => {
4
- it("throws an error if basePath is not an absolute path", () => {
5
- const invalidBasePath = "relative/path";
6
- const nodeishFs = {
7
- // @ts-expect-error
8
- readFile: async () => Promise.resolve(new Uint8Array(0)),
9
- readdir: async () => Promise.resolve([]),
10
- mkdir: async () => Promise.resolve(""),
11
- writeFile: async () => Promise.resolve(),
12
- };
13
- expect(() => createNodeishFsWithAbsolutePaths({ basePath: invalidBasePath, nodeishFs })).toThrowError("The argument `settingsFilePath` of `loadProject()` must be an absolute path.");
14
- });
15
- it("intercepts paths correctly for readFile", async () => {
16
- const basePath = "/absolute/path";
17
- const filePath = "file.txt";
18
- const expectedPath = "/absolute/path/file.txt";
19
- const nodeishFs = {
20
- // @ts-expect-error
21
- readFile: async (path) => {
22
- expect(path).toBe(expectedPath);
23
- return Promise.resolve(new Uint8Array(0));
24
- },
25
- readdir: async () => Promise.resolve([]),
26
- mkdir: async () => Promise.resolve(""),
27
- writeFile: async () => Promise.resolve(),
28
- };
29
- const interceptedFs = createNodeishFsWithAbsolutePaths({ basePath, nodeishFs });
30
- await interceptedFs.readFile(filePath, { encoding: "utf-8" });
31
- });
32
- it("intercepts paths correctly for readdir", async () => {
33
- const basePath = "/absolute/path";
34
- const dirPath = "dir";
35
- const expectedPath = "/absolute/path/dir";
36
- const nodeishFs = {
37
- // @ts-expect-error
38
- readFile: async () => Promise.resolve(new Uint8Array(0)),
39
- readdir: async (path) => {
40
- expect(path).toBe(expectedPath);
41
- return Promise.resolve([]);
42
- },
43
- mkdir: async () => Promise.resolve(""),
44
- writeFile: async () => Promise.resolve(),
45
- };
46
- const interceptedFs = createNodeishFsWithAbsolutePaths({ basePath, nodeishFs });
47
- await interceptedFs.readdir(dirPath);
48
- });
49
- it("intercepts paths correctly for mkdir", async () => {
50
- const basePath = "/absolute/path";
51
- const dirPath = "newDir";
52
- const expectedPath = "/absolute/path/newDir";
53
- const nodeishFs = {
54
- // @ts-expect-error
55
- readFile: async () => Promise.resolve(new Uint8Array(0)),
56
- readdir: async () => Promise.resolve([]),
57
- mkdir: async (path) => {
58
- expect(path).toBe(expectedPath);
59
- return Promise.resolve("");
60
- },
61
- writeFile: async () => Promise.resolve(),
62
- };
63
- const interceptedFs = createNodeishFsWithAbsolutePaths({ basePath, nodeishFs });
64
- await interceptedFs.mkdir(dirPath);
65
- });
66
- it("intercepts paths correctly for writeFile", async () => {
67
- const basePath = "/absolute/path";
68
- const filePath = "file.txt";
69
- const expectedPath = "/absolute/path/file.txt";
70
- const data = "Hello, World!";
71
- const nodeishFs = {
72
- // @ts-expect-error
73
- readFile: async () => Promise.resolve(new Uint8Array(0)),
74
- readdir: async () => Promise.resolve([]),
75
- mkdir: async () => Promise.resolve(""),
76
- writeFile: async (path, content) => {
77
- expect(path).toBe(expectedPath);
78
- expect(content).toBe(data);
79
- return Promise.resolve();
80
- },
81
- };
82
- const interceptedFs = createNodeishFsWithAbsolutePaths({ basePath, nodeishFs });
83
- await interceptedFs.writeFile(filePath, data);
84
- });
85
- });
@@ -1,102 +0,0 @@
1
- import type { NodeishFilesystemSubset } from "@inlang/plugin"
2
- import { describe, it, expect } from "vitest"
3
- import { createNodeishFsWithAbsolutePaths } from "./createNodeishFsWithAbsolutePaths.js"
4
-
5
- describe("createNodeishFsWithAbsolutePaths", () => {
6
- it("throws an error if basePath is not an absolute path", () => {
7
- const invalidBasePath = "relative/path"
8
- const nodeishFs: NodeishFilesystemSubset = {
9
- // @ts-expect-error
10
- readFile: async () => Promise.resolve(new Uint8Array(0)),
11
- readdir: async () => Promise.resolve([]),
12
- mkdir: async () => Promise.resolve(""),
13
- writeFile: async () => Promise.resolve(),
14
- }
15
-
16
- expect(() =>
17
- createNodeishFsWithAbsolutePaths({ basePath: invalidBasePath, nodeishFs })
18
- ).toThrowError("The argument `settingsFilePath` of `loadProject()` must be an absolute path.")
19
- })
20
-
21
- it("intercepts paths correctly for readFile", async () => {
22
- const basePath = "/absolute/path"
23
- const filePath = "file.txt"
24
- const expectedPath = "/absolute/path/file.txt"
25
-
26
- const nodeishFs: NodeishFilesystemSubset = {
27
- // @ts-expect-error
28
- readFile: async (path) => {
29
- expect(path).toBe(expectedPath)
30
- return Promise.resolve(new Uint8Array(0))
31
- },
32
- readdir: async () => Promise.resolve([]),
33
- mkdir: async () => Promise.resolve(""),
34
- writeFile: async () => Promise.resolve(),
35
- }
36
-
37
- const interceptedFs = createNodeishFsWithAbsolutePaths({ basePath, nodeishFs })
38
- await interceptedFs.readFile(filePath, { encoding: "utf-8" })
39
- })
40
-
41
- it("intercepts paths correctly for readdir", async () => {
42
- const basePath = "/absolute/path"
43
- const dirPath = "dir"
44
- const expectedPath = "/absolute/path/dir"
45
-
46
- const nodeishFs: NodeishFilesystemSubset = {
47
- // @ts-expect-error
48
- readFile: async () => Promise.resolve(new Uint8Array(0)),
49
- readdir: async (path) => {
50
- expect(path).toBe(expectedPath)
51
- return Promise.resolve([])
52
- },
53
- mkdir: async () => Promise.resolve(""),
54
- writeFile: async () => Promise.resolve(),
55
- }
56
-
57
- const interceptedFs = createNodeishFsWithAbsolutePaths({ basePath, nodeishFs })
58
- await interceptedFs.readdir(dirPath)
59
- })
60
-
61
- it("intercepts paths correctly for mkdir", async () => {
62
- const basePath = "/absolute/path"
63
- const dirPath = "newDir"
64
- const expectedPath = "/absolute/path/newDir"
65
-
66
- const nodeishFs: NodeishFilesystemSubset = {
67
- // @ts-expect-error
68
- readFile: async () => Promise.resolve(new Uint8Array(0)),
69
- readdir: async () => Promise.resolve([]),
70
- mkdir: async (path) => {
71
- expect(path).toBe(expectedPath)
72
- return Promise.resolve("")
73
- },
74
- writeFile: async () => Promise.resolve(),
75
- }
76
-
77
- const interceptedFs = createNodeishFsWithAbsolutePaths({ basePath, nodeishFs })
78
- await interceptedFs.mkdir(dirPath)
79
- })
80
-
81
- it("intercepts paths correctly for writeFile", async () => {
82
- const basePath = "/absolute/path"
83
- const filePath = "file.txt"
84
- const expectedPath = "/absolute/path/file.txt"
85
- const data = "Hello, World!"
86
-
87
- const nodeishFs: NodeishFilesystemSubset = {
88
- // @ts-expect-error
89
- readFile: async () => Promise.resolve(new Uint8Array(0)),
90
- readdir: async () => Promise.resolve([]),
91
- mkdir: async () => Promise.resolve(""),
92
- writeFile: async (path, content) => {
93
- expect(path).toBe(expectedPath)
94
- expect(content).toBe(data)
95
- return Promise.resolve()
96
- },
97
- }
98
-
99
- const interceptedFs = createNodeishFsWithAbsolutePaths({ basePath, nodeishFs })
100
- await interceptedFs.writeFile(filePath, data)
101
- })
102
- })
@@ -1,30 +0,0 @@
1
- import type { NodeishFilesystemSubset } from "@inlang/plugin"
2
- import { normalizePath } from "@lix-js/fs"
3
-
4
- export const createNodeishFsWithAbsolutePaths = (args: {
5
- basePath: string
6
- nodeishFs: NodeishFilesystemSubset
7
- }): NodeishFilesystemSubset => {
8
- const isAbsolutePath = (path: string) => /^[/\\]/.test(path)
9
-
10
- if (!isAbsolutePath(args.basePath)) {
11
- throw new Error("The argument `settingsFilePath` of `loadProject()` must be an absolute path.")
12
- }
13
-
14
- const intercept = (path: string) => {
15
- if (isAbsolutePath(path)) {
16
- return path
17
- }
18
-
19
- return normalizePath(args.basePath + "/" + path)
20
- }
21
-
22
- return {
23
- // @ts-expect-error
24
- readFile: (path: string, options: { encoding: "utf-8" | "binary" }) =>
25
- args.nodeishFs.readFile(intercept(path), options),
26
- readdir: (path: string) => args.nodeishFs.readdir(intercept(path)),
27
- mkdir: (path: string) => args.nodeishFs.mkdir(intercept(path)),
28
- writeFile: (path: string, data: string) => args.nodeishFs.writeFile(intercept(path), data),
29
- }
30
- }