@inlang/sdk 0.8.0 → 0.10.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 (44) hide show
  1. package/dist/adapter/solidAdapter.test.js +1 -1
  2. package/dist/errors.d.ts +18 -5
  3. package/dist/errors.d.ts.map +1 -1
  4. package/dist/errors.js +12 -10
  5. package/dist/loadProject.d.ts.map +1 -1
  6. package/dist/loadProject.js +14 -10
  7. package/dist/loadProject.test.js +4 -5
  8. package/dist/resolve-modules/errors.d.ts +6 -5
  9. package/dist/resolve-modules/errors.d.ts.map +1 -1
  10. package/dist/resolve-modules/errors.js +10 -8
  11. package/dist/resolve-modules/import.d.ts.map +1 -1
  12. package/dist/resolve-modules/import.js +2 -3
  13. package/dist/resolve-modules/message-lint-rules/errors.d.ts +5 -4
  14. package/dist/resolve-modules/message-lint-rules/errors.d.ts.map +1 -1
  15. package/dist/resolve-modules/message-lint-rules/errors.js +2 -4
  16. package/dist/resolve-modules/message-lint-rules/resolveMessageLintRules.d.ts.map +1 -1
  17. package/dist/resolve-modules/message-lint-rules/resolveMessageLintRules.js +4 -2
  18. package/dist/resolve-modules/plugins/errors.d.ts +35 -28
  19. package/dist/resolve-modules/plugins/errors.d.ts.map +1 -1
  20. package/dist/resolve-modules/plugins/errors.js +23 -30
  21. package/dist/resolve-modules/plugins/resolvePlugins.d.ts.map +1 -1
  22. package/dist/resolve-modules/plugins/resolvePlugins.js +17 -18
  23. package/dist/resolve-modules/plugins/resolvePlugins.test.js +79 -49
  24. package/dist/resolve-modules/plugins/types.d.ts +4 -5
  25. package/dist/resolve-modules/plugins/types.d.ts.map +1 -1
  26. package/dist/resolve-modules/resolveModules.d.ts.map +1 -1
  27. package/dist/resolve-modules/resolveModules.js +5 -4
  28. package/dist/resolve-modules/resolveModules.test.js +2 -2
  29. package/dist/test-utilities/createMessage.d.ts +1 -1
  30. package/package.json +1 -1
  31. package/src/adapter/solidAdapter.test.ts +1 -1
  32. package/src/errors.ts +19 -10
  33. package/src/loadProject.test.ts +11 -14
  34. package/src/loadProject.ts +18 -17
  35. package/src/resolve-modules/errors.ts +14 -8
  36. package/src/resolve-modules/import.ts +2 -3
  37. package/src/resolve-modules/message-lint-rules/errors.ts +5 -5
  38. package/src/resolve-modules/message-lint-rules/resolveMessageLintRules.ts +4 -2
  39. package/src/resolve-modules/plugins/errors.ts +34 -36
  40. package/src/resolve-modules/plugins/resolvePlugins.test.ts +94 -62
  41. package/src/resolve-modules/plugins/resolvePlugins.ts +19 -50
  42. package/src/resolve-modules/plugins/types.ts +4 -8
  43. package/src/resolve-modules/resolveModules.test.ts +2 -2
  44. package/src/resolve-modules/resolveModules.ts +5 -6
@@ -2,56 +2,87 @@
2
2
  import { describe, expect, it } from "vitest";
3
3
  import { resolvePlugins } from "./resolvePlugins.js";
4
4
  import { PluginLoadMessagesFunctionAlreadyDefinedError, PluginSaveMessagesFunctionAlreadyDefinedError, PluginHasInvalidIdError, PluginUsesReservedNamespaceError, PluginReturnedInvalidCustomApiError, PluginHasInvalidSchemaError, PluginsDoNotProvideLoadOrSaveMessagesError, } from "./errors.js";
5
- describe("generally", () => {
6
- it("should return an error if a plugin uses an invalid id", async () => {
7
- const mockPlugin = {
8
- // @ts-expect-error - invalid id
9
- id: "no-namespace",
10
- description: { en: "My plugin description" },
11
- displayName: { en: "My plugin" },
12
- loadMessages: () => undefined,
13
- saveMessages: () => undefined,
14
- };
15
- const resolved = await resolvePlugins({
16
- plugins: [mockPlugin],
17
- settings: {},
18
- nodeishFs: {},
19
- });
20
- expect(resolved.errors[0]).toBeInstanceOf(PluginHasInvalidIdError);
5
+ it("should return an error if a plugin uses an invalid id", async () => {
6
+ const mockPlugin = {
7
+ // @ts-expect-error - invalid id
8
+ id: "no-namespace",
9
+ description: { en: "My plugin description" },
10
+ displayName: { en: "My plugin" },
11
+ loadMessages: () => undefined,
12
+ saveMessages: () => undefined,
13
+ };
14
+ const resolved = await resolvePlugins({
15
+ plugins: [mockPlugin],
16
+ settings: {},
17
+ nodeishFs: {},
21
18
  });
22
- it("should return an error if a plugin uses APIs that are not available", async () => {
23
- const mockPlugin = {
24
- id: "plugin.namespace.undefinedApi",
25
- description: { en: "My plugin description" },
26
- displayName: { en: "My plugin" },
27
- // @ts-expect-error the key is not available in type
28
- nonExistentKey: {
29
- nonexistentOptions: "value",
30
- },
31
- loadMessages: () => undefined,
32
- saveMessages: () => undefined,
33
- };
34
- const resolved = await resolvePlugins({
35
- plugins: [mockPlugin],
36
- settings: {},
37
- nodeishFs: {},
38
- });
39
- expect(resolved.errors[0]).toBeInstanceOf(PluginHasInvalidSchemaError);
19
+ expect(resolved.errors[0]).toBeInstanceOf(PluginHasInvalidIdError);
20
+ });
21
+ it("should return an error if a plugin uses APIs that are not available", async () => {
22
+ const mockPlugin = {
23
+ id: "plugin.namespace.undefinedApi",
24
+ description: { en: "My plugin description" },
25
+ displayName: { en: "My plugin" },
26
+ // @ts-expect-error the key is not available in type
27
+ nonExistentKey: {
28
+ nonexistentOptions: "value",
29
+ },
30
+ loadMessages: () => undefined,
31
+ saveMessages: () => undefined,
32
+ };
33
+ const resolved = await resolvePlugins({
34
+ plugins: [mockPlugin],
35
+ settings: {},
36
+ nodeishFs: {},
40
37
  });
41
- it("should not initialize a plugin that uses the 'inlang' namespace except for inlang whitelisted plugins", async () => {
42
- const mockPlugin = {
43
- id: "plugin.inlang.notWhitelisted",
44
- description: { en: "My plugin description" },
45
- displayName: { en: "My plugin" },
46
- loadMessages: () => undefined,
47
- };
48
- const resolved = await resolvePlugins({
49
- plugins: [mockPlugin],
50
- settings: {},
51
- nodeishFs: {},
52
- });
53
- expect(resolved.errors[0]).toBeInstanceOf(PluginUsesReservedNamespaceError);
38
+ expect(resolved.errors[0]).toBeInstanceOf(PluginHasInvalidSchemaError);
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
+ it("should expose the project settings including the plugin settings", async () => {
55
+ const settings = {
56
+ sourceLanguageTag: "en",
57
+ languageTags: ["en", "de"],
58
+ modules: [],
59
+ "plugin.namespace.placeholder": {
60
+ myPluginSetting: "value",
61
+ },
62
+ };
63
+ const mockPlugin = {
64
+ id: "plugin.namespace.placeholder",
65
+ description: { en: "My plugin description" },
66
+ displayName: { en: "My plugin" },
67
+ saveMessages: async ({ settings }) => {
68
+ expect(settings).toStrictEqual(settings);
69
+ },
70
+ addCustomApi: ({ settings }) => {
71
+ expect(settings).toStrictEqual(settings);
72
+ return {};
73
+ },
74
+ loadMessages: async ({ settings }) => {
75
+ expect(settings).toStrictEqual(settings);
76
+ return [];
77
+ },
78
+ };
79
+ const resolved = await resolvePlugins({
80
+ plugins: [mockPlugin],
81
+ settings: settings,
82
+ nodeishFs: {},
54
83
  });
84
+ await resolved.data.loadMessages({ settings });
85
+ await resolved.data.saveMessages({ settings, messages: [] });
55
86
  });
56
87
  describe("loadMessages", () => {
57
88
  it("should load messages from a local source", async () => {
@@ -67,8 +98,7 @@ describe("loadMessages", () => {
67
98
  nodeishFs: {},
68
99
  });
69
100
  expect(await resolved.data.loadMessages({
70
- languageTags: ["en"],
71
- sourceLanguageTag: "en",
101
+ settings: {},
72
102
  })).toEqual([{ id: "test", expressions: [], selectors: [], variants: [] }]);
73
103
  });
74
104
  it("should collect an error if function is defined twice in multiple plugins", async () => {
@@ -1,9 +1,8 @@
1
- import type { LanguageTag } from "@inlang/language-tag";
2
1
  import type { NodeishFilesystem as LisaNodeishFilesystem } from "@lix-js/fs";
3
2
  import type { PluginReturnedInvalidCustomApiError, PluginLoadMessagesFunctionAlreadyDefinedError, PluginSaveMessagesFunctionAlreadyDefinedError, PluginHasInvalidIdError, PluginHasInvalidSchemaError, PluginUsesReservedNamespaceError, PluginsDoNotProvideLoadOrSaveMessagesError } from "./errors.js";
4
3
  import type { Message } from "@inlang/message";
5
- import type { JSONObject } from "@inlang/json-types";
6
4
  import type { CustomApiInlangIdeExtension, Plugin } from "@inlang/plugin";
5
+ import type { ProjectSettings } from "@inlang/project-settings";
7
6
  /**
8
7
  * The filesystem is a subset of project lisa's nodeish filesystem.
9
8
  *
@@ -15,7 +14,7 @@ export type NodeishFilesystemSubset = Pick<LisaNodeishFilesystem, "readFile" | "
15
14
  */
16
15
  export type ResolvePluginsFunction = (args: {
17
16
  plugins: Array<Plugin>;
18
- settings: Record<Plugin["id"], JSONObject>;
17
+ settings: ProjectSettings;
19
18
  nodeishFs: NodeishFilesystemSubset;
20
19
  }) => Promise<{
21
20
  data: ResolvedPluginApi;
@@ -26,10 +25,10 @@ export type ResolvePluginsFunction = (args: {
26
25
  */
27
26
  export type ResolvedPluginApi = {
28
27
  loadMessages: (args: {
29
- languageTags: LanguageTag[];
30
- sourceLanguageTag: LanguageTag;
28
+ settings: ProjectSettings;
31
29
  }) => Promise<Message[]> | Message[];
32
30
  saveMessages: (args: {
31
+ settings: ProjectSettings;
33
32
  messages: Message[];
34
33
  }) => Promise<void> | void;
35
34
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/resolve-modules/plugins/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AACvD,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,UAAU,EAAE,MAAM,oBAAoB,CAAA;AACpD,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAEzE;;;;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,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,UAAU,CAAC,CAAA;IAC1C,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;QACpB,YAAY,EAAE,WAAW,EAAE,CAAA;QAC3B,iBAAiB,EAAE,WAAW,CAAA;KAC9B,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,GAAG,OAAO,EAAE,CAAA;IACpC,YAAY,EAAE,CAAC,IAAI,EAAE;QAAE,QAAQ,EAAE,OAAO,EAAE,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IACrE;;;;;;;;;;;;;;;;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,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 +1 @@
1
- {"version":3,"file":"resolveModules.d.ts","sourceRoot":"","sources":["../../src/resolve-modules/resolveModules.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAkBvD,eAAO,MAAM,cAAc,EAAE,qBAgF5B,CAAA"}
1
+ {"version":3,"file":"resolveModules.d.ts","sourceRoot":"","sources":["../../src/resolve-modules/resolveModules.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAkBvD,eAAO,MAAM,cAAc,EAAE,qBA+E5B,CAAA"}
@@ -19,7 +19,7 @@ export const resolveModules = async (args) => {
19
19
  const importedModule = await tryCatch(() => _import(module));
20
20
  // -- IMPORT MODULE --
21
21
  if (importedModule.error) {
22
- moduleErrors.push(new ModuleImportError(`Couldn't import the plugin "${module}"`, {
22
+ moduleErrors.push(new ModuleImportError({
23
23
  module: module,
24
24
  cause: importedModule.error,
25
25
  }));
@@ -27,16 +27,17 @@ export const resolveModules = async (args) => {
27
27
  }
28
28
  // -- MODULE DOES NOT EXPORT ANYTHING --
29
29
  if (importedModule.data?.default === undefined) {
30
- moduleErrors.push(new ModuleHasNoExportsError(`Module "${module}" has no exports.`, {
30
+ moduleErrors.push(new ModuleHasNoExportsError({
31
31
  module: module,
32
32
  }));
33
33
  continue;
34
34
  }
35
35
  const isValidModule = ModuleCompiler.Check(importedModule.data);
36
36
  if (isValidModule === false) {
37
- const errors = [...ModuleCompiler.Errors(importedModule.data)].map((e) => `${e.path} ${e.message}`);
38
- moduleErrors.push(new ModuleExportIsInvalidError(`Module "${module}" is invalid: ` + errors.join("\n"), {
37
+ const errors = [...ModuleCompiler.Errors(importedModule.data)];
38
+ moduleErrors.push(new ModuleExportIsInvalidError({
39
39
  module: module,
40
+ errors,
40
41
  }));
41
42
  continue;
42
43
  }
@@ -11,7 +11,7 @@ it("should return an error if a plugin cannot be imported", async () => {
11
11
  settings,
12
12
  nodeishFs: {},
13
13
  _import: () => {
14
- throw new ModuleImportError("Could not import", {
14
+ throw new ModuleImportError({
15
15
  module: settings.modules[0],
16
16
  cause: new Error("Could not import"),
17
17
  });
@@ -74,7 +74,7 @@ it("should return an error if a module cannot be imported", async () => {
74
74
  modules: ["https://myplugin.com/index.js"],
75
75
  };
76
76
  const _import = async () => {
77
- throw new ModuleImportError("Could not import", {
77
+ throw new ModuleImportError({
78
78
  module: settings.modules[0],
79
79
  cause: new Error(),
80
80
  });
@@ -9,8 +9,8 @@ export declare const createMessage: (id: string, patterns: Record<string, Patter
9
9
  type: "Text";
10
10
  value: string;
11
11
  } | {
12
- name: string;
13
12
  type: "VariableReference";
13
+ name: string;
14
14
  })[];
15
15
  }[];
16
16
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@inlang/sdk",
3
3
  "type": "module",
4
- "version": "0.8.0",
4
+ "version": "0.10.0",
5
5
  "license": "Apache-2.0",
6
6
  "publishConfig": {
7
7
  "access": "public"
@@ -175,7 +175,7 @@ describe("messages", () => {
175
175
  description: {
176
176
  en: "wo",
177
177
  },
178
- loadMessages: ({ languageTags }) => (languageTags.length ? exampleMessages : []),
178
+ loadMessages: ({ settings }) => (settings.languageTags.length ? exampleMessages : []),
179
179
  saveMessages: () => undefined,
180
180
  }
181
181
 
package/src/errors.ts CHANGED
@@ -1,34 +1,43 @@
1
+ import type { ValueError } from "@sinclair/typebox/errors"
2
+
1
3
  export class ProjectSettingsInvalidError extends Error {
2
- constructor(message: string, options: ErrorOptions) {
3
- super(message, options)
4
+ constructor(options: { errors: ValueError[] }) {
5
+ super(
6
+ `The project settings are invalid:\n\n${options.errors
7
+ .map((error) => `The value of "${error.path}" is invalid:\n\n${error.message}`)
8
+ .join("\n")}`
9
+ )
4
10
  this.name = "ProjectSettingsInvalidError"
5
11
  }
6
12
  }
7
13
 
8
14
  export class ProjectSettingsFileJSONSyntaxError extends Error {
9
- constructor(message: string, options: ErrorOptions) {
10
- super(message, options)
15
+ constructor(options: { cause: ErrorOptions["cause"]; path: string }) {
16
+ super(
17
+ `The settings file at "${options.path}" is not a valid JSON file:\n\n${options.cause}`,
18
+ options
19
+ )
11
20
  this.name = "ProjectSettingsFileJSONSyntaxError"
12
21
  }
13
22
  }
14
23
 
15
24
  export class ProjectSettingsFileNotFoundError extends Error {
16
- constructor(message: string, options: ErrorOptions) {
17
- super(message, options)
25
+ constructor(options: { cause?: ErrorOptions["cause"]; path: string }) {
26
+ super(`The file at "${options.path}" could not be read. Does the file exists?`, options)
18
27
  this.name = "ProjectSettingsFileNotFoundError"
19
28
  }
20
29
  }
21
30
 
22
31
  export class PluginSaveMessagesError extends Error {
23
- constructor(message: string, options: ErrorOptions) {
24
- super(message, options)
32
+ constructor(options: { cause: ErrorOptions["cause"] }) {
33
+ super(`An error occured in saveMessages() caused by ${options.cause}.`, options)
25
34
  this.name = "PluginSaveMessagesError"
26
35
  }
27
36
  }
28
37
 
29
38
  export class PluginLoadMessagesError extends Error {
30
- constructor(message: string, options: ErrorOptions) {
31
- super(message, options)
39
+ constructor(options: { cause: ErrorOptions["cause"] }) {
40
+ super(`An error occured in loadMessages() caused by ${options.cause}.`, options)
32
41
  this.name = "PluginLoadMessagesError"
33
42
  }
34
43
  }
@@ -482,17 +482,16 @@ describe("functionality", () => {
482
482
  it("should call saveMessages() on updates", async () => {
483
483
  const fs = createNodeishMemoryFs()
484
484
 
485
- await fs.writeFile(
486
- "./project.inlang.json",
487
- JSON.stringify({
488
- sourceLanguageTag: "en",
489
- languageTags: ["en", "de"],
490
- modules: ["plugin.js"],
491
- "plugin.project.json": {
492
- pathPattern: "./resources/{languageTag}.json",
493
- },
494
- })
495
- )
485
+ const settings: ProjectSettings = {
486
+ sourceLanguageTag: "en",
487
+ languageTags: ["en", "de"],
488
+ modules: ["plugin.js"],
489
+ "plugin.project.json": {
490
+ pathPattern: "./resources/{languageTag}.json",
491
+ },
492
+ }
493
+
494
+ await fs.writeFile("./project.inlang.json", JSON.stringify(settings))
496
495
 
497
496
  await fs.mkdir("./resources")
498
497
 
@@ -583,9 +582,7 @@ describe("functionality", () => {
583
582
 
584
583
  expect(mockSaveFn.mock.calls.length).toBe(1)
585
584
 
586
- expect(mockSaveFn.mock.calls[0][0].settings).toStrictEqual({
587
- pathPattern: "./resources/{languageTag}.json",
588
- })
585
+ expect(mockSaveFn.mock.calls[0][0].settings).toStrictEqual(settings)
589
586
 
590
587
  expect(Object.values(mockSaveFn.mock.calls[0][0].messages)).toStrictEqual([
591
588
  {
@@ -73,7 +73,9 @@ export const loadProject = async (args: {
73
73
  return { error }
74
74
  }
75
75
 
76
- throw new Error("unhandled")
76
+ throw new Error(
77
+ "Unhandled error in setSettings. This is an internal bug. Please file an issue."
78
+ )
77
79
  }
78
80
  }
79
81
 
@@ -113,17 +115,14 @@ export const loadProject = async (args: {
113
115
 
114
116
  makeTrulyAsync(
115
117
  _resolvedModules.resolvedPluginApi.loadMessages({
116
- languageTags: settingsValue!.languageTags,
117
- sourceLanguageTag: settingsValue!.sourceLanguageTag,
118
+ settings: settingsValue,
118
119
  })
119
120
  )
120
121
  .then((messages) => {
121
122
  setMessages(messages)
122
123
  markInitAsComplete()
123
124
  })
124
- .catch((err) =>
125
- markInitAsFailed(new PluginLoadMessagesError("Error in load messages", { cause: err }))
126
- )
125
+ .catch((err) => markInitAsFailed(new PluginLoadMessagesError({ cause: err })))
127
126
  })
128
127
 
129
128
  // -- installed items ----------------------------------------------------
@@ -174,9 +173,12 @@ export const loadProject = async (args: {
174
173
  500,
175
174
  async (newMessages) => {
176
175
  try {
177
- await resolvedModules()?.resolvedPluginApi.saveMessages({ messages: newMessages })
176
+ await resolvedModules()?.resolvedPluginApi.saveMessages({
177
+ settings: settingsValue,
178
+ messages: newMessages,
179
+ })
178
180
  } catch (err) {
179
- throw new PluginSaveMessagesError("Error in saving messages", {
181
+ throw new PluginSaveMessagesError({
180
182
  cause: err,
181
183
  })
182
184
  }
@@ -229,18 +231,17 @@ const loadSettings = async (args: {
229
231
  async () => await args.nodeishFs.readFile(args.settingsFilePath, { encoding: "utf-8" })
230
232
  )
231
233
  if (settingsFileError)
232
- throw new ProjectSettingsFileNotFoundError(
233
- `Could not locate settings file in (${args.settingsFilePath}).`,
234
- {
235
- cause: settingsFileError,
236
- }
237
- )
234
+ throw new ProjectSettingsFileNotFoundError({
235
+ cause: settingsFileError,
236
+ path: args.settingsFilePath,
237
+ })
238
238
 
239
239
  const json = tryCatch(() => JSON.parse(settingsFile!))
240
240
 
241
241
  if (json.error) {
242
- throw new ProjectSettingsFileJSONSyntaxError(`The settings is not a valid JSON file.`, {
242
+ throw new ProjectSettingsFileJSONSyntaxError({
243
243
  cause: json.error,
244
+ path: args.settingsFilePath,
244
245
  })
245
246
  }
246
247
  return parseSettings(json.data)
@@ -251,8 +252,8 @@ const parseSettings = (settings: unknown) => {
251
252
  if (settingsCompiler.Check(withMigration) === false) {
252
253
  const typeErrors = [...settingsCompiler.Errors(settings)]
253
254
  if (typeErrors.length > 0) {
254
- throw new ProjectSettingsInvalidError(`The settings is invalid according to the schema.`, {
255
- cause: typeErrors,
255
+ throw new ProjectSettingsInvalidError({
256
+ errors: typeErrors,
256
257
  })
257
258
  }
258
259
  }
@@ -1,12 +1,13 @@
1
+ import type { ValueError } from "@sinclair/typebox/errors"
1
2
  export * from "./plugins/errors.js"
2
3
  export * from "./message-lint-rules/errors.js"
3
4
 
4
5
  export class ModuleError extends Error {
5
- public readonly Module: string
6
+ public readonly module: string
6
7
  constructor(message: string, options: { module: string; cause?: Error }) {
7
8
  super(message)
8
9
  this.name = "ModuleError"
9
- this.Module = options.module
10
+ this.module = options.module
10
11
  this.cause = options.cause
11
12
  }
12
13
  }
@@ -15,8 +16,8 @@ export class ModuleError extends Error {
15
16
  * Error when a Module does not export any plugins or lint rules.
16
17
  */
17
18
  export class ModuleHasNoExportsError extends ModuleError {
18
- constructor(message: string, options: { module: string; cause?: Error }) {
19
- super(message, options)
19
+ constructor(options: { module: string; cause?: Error }) {
20
+ super(`Module "${module}" has no exports. Every module must have an "export default".`, options)
20
21
  this.name = "ModuleHasNoExportsError"
21
22
  }
22
23
  }
@@ -25,15 +26,20 @@ export class ModuleHasNoExportsError extends ModuleError {
25
26
  * Error when a Module cannot be imported.
26
27
  */
27
28
  export class ModuleImportError extends ModuleError {
28
- constructor(message: string, options: { module: string; cause: Error }) {
29
- super(message, options)
29
+ constructor(options: { module: string; cause: Error }) {
30
+ super(`Couldn't import the plugin "${module}":\n\n${options.cause}`, options)
30
31
  this.name = "ModuleImportError"
31
32
  }
32
33
  }
33
34
 
34
35
  export class ModuleExportIsInvalidError extends ModuleError {
35
- constructor(message: string, options: { module: string; cause?: Error }) {
36
- super(message, options)
36
+ constructor(options: { module: string; errors: ValueError[] }) {
37
+ super(
38
+ `The export(s) of "${module}" are invalid:\n\n${options.errors
39
+ .map((error) => `Path "${error.path}" with value "${error.value}": "${error.message}"`)
40
+ .join("\n")}`,
41
+ options
42
+ )
37
43
  this.name = "ModuleExportIsInvalidError"
38
44
  }
39
45
  }
@@ -50,14 +50,13 @@ async function $import(
50
50
  try {
51
51
  return await import(/* @vite-ignore */ moduleWithMimeType)
52
52
  } catch (error) {
53
- let message = `Error while importing ${uri}: ${(error as Error)?.message ?? "Unknown error"}`
54
53
  if (error instanceof SyntaxError && uri.includes("jsdelivr")) {
55
- message += dedent`\n\n
54
+ error.message += dedent`\n\n
56
55
  Are you sure that the file exists on JSDelivr?
57
56
 
58
57
  The error indicates that the imported file does not exist on JSDelivr. For non-existent files, JSDelivr returns a 404 text that JS cannot parse as a module and throws a SyntaxError.
59
58
  `
60
59
  }
61
- throw new ModuleImportError(message, { module: uri, cause: error as Error })
60
+ throw new ModuleImportError({ module: uri, cause: error as Error })
62
61
  }
63
62
  }
@@ -1,9 +1,9 @@
1
- export class MessageLintRuleIsInvalidError extends Error {
2
- public readonly module: string
1
+ import type { MessageLintRule } from "@inlang/message-lint-rule"
2
+ import type { ValueError } from "@sinclair/typebox/errors"
3
3
 
4
- constructor(message: string, options: { module: string; cause?: Error }) {
5
- super(message)
6
- this.module = options.module
4
+ export class MessageLintRuleIsInvalidError extends Error {
5
+ constructor(options: { id: MessageLintRule["id"]; errors: ValueError[] }) {
6
+ super(`The message lint rule "${options.id}" is invalid:\n\n${options.errors.join("\n")}`)
7
7
  this.name = "MessageLintRuleIsInvalidError"
8
8
  }
9
9
  }
@@ -9,9 +9,11 @@ export const resolveMessageLintRules = (args: { messageLintRules: Array<MessageL
9
9
  }
10
10
  for (const rule of args.messageLintRules) {
11
11
  if (Value.Check(MessageLintRule, rule) === false) {
12
+ const errors = [...Value.Errors(MessageLintRule, rule)]
12
13
  result.errors.push(
13
- new MessageLintRuleIsInvalidError(`Couldn't parse lint rule "${rule.id}"`, {
14
- module: "not implemented",
14
+ new MessageLintRuleIsInvalidError({
15
+ id: rule.id,
16
+ errors,
15
17
  })
16
18
  )
17
19
  continue
@@ -1,65 +1,63 @@
1
1
  import type { Plugin } from "@inlang/plugin"
2
+ import type { ValueError } from "@sinclair/typebox/errors"
2
3
 
3
- type PluginErrorOptions = {
4
- plugin: Plugin["id"] | undefined
5
- } & Partial<Error>
6
-
7
- class PluginError extends Error {
8
- public readonly plugin: string
9
-
10
- constructor(message: string, options: PluginErrorOptions) {
11
- super(message)
12
- this.name = "PluginError"
13
- this.plugin = options.plugin ?? "unknown"
14
- }
15
- }
16
-
17
- export class PluginHasInvalidIdError extends PluginError {
18
- constructor(message: string, options: PluginErrorOptions) {
19
- super(message, options)
4
+ export class PluginHasInvalidIdError extends Error {
5
+ constructor(options: { id: Plugin["id"] }) {
6
+ super(
7
+ `Plugin "${options.id}" has an invalid id. The id must:\n1) Start with "plugin."\n2) camelCase\n3) Contain a namespace.\nAn example would be "plugin.namespace.myPlugin".`
8
+ )
20
9
  this.name = "PluginHasInvalidIdError"
21
10
  }
22
11
  }
23
12
 
24
- export class PluginUsesReservedNamespaceError extends PluginError {
25
- constructor(message: string, options: PluginErrorOptions) {
26
- super(message, options)
13
+ export class PluginUsesReservedNamespaceError extends Error {
14
+ constructor(options: { id: Plugin["id"] }) {
15
+ super(`Plugin ${options.id} uses reserved namespace 'inlang'.`)
27
16
  this.name = "PluginUsesReservedNamespaceError"
28
17
  }
29
18
  }
30
19
 
31
- export class PluginHasInvalidSchemaError extends PluginError {
32
- constructor(message: string, options: PluginErrorOptions) {
33
- super(message, options)
20
+ export class PluginHasInvalidSchemaError extends Error {
21
+ constructor(options: { id: Plugin["id"]; errors: ValueError[] }) {
22
+ super(
23
+ `Plugin "${options.id}" has an invalid schema:\n\n${options.errors
24
+ .map((error) => `Path "${error.path}" with value "${error.value}": "${error.message}"`)
25
+ .join("\n")})}\n\nPlease refer to the documentation for the correct schema.`
26
+ )
34
27
  this.name = "PluginHasInvalidSchemaError"
35
28
  }
36
29
  }
37
30
 
38
- export class PluginLoadMessagesFunctionAlreadyDefinedError extends PluginError {
39
- constructor(message: string, options: PluginErrorOptions) {
40
- super(message, options)
31
+ export class PluginLoadMessagesFunctionAlreadyDefinedError extends Error {
32
+ constructor(options: { id: Plugin["id"] }) {
33
+ super(
34
+ `Plugin "${options.id}" defines the \`loadMessages()\` function, but it was already defined by another plugin.\n\nInlang only allows one plugin to define the \`loadMessages()\` function.`
35
+ )
41
36
  this.name = "PluginLoadMessagesFunctionAlreadyDefinedError"
42
37
  }
43
38
  }
44
39
 
45
- export class PluginSaveMessagesFunctionAlreadyDefinedError extends PluginError {
46
- constructor(message: string, options: PluginErrorOptions) {
47
- super(message, options)
40
+ export class PluginSaveMessagesFunctionAlreadyDefinedError extends Error {
41
+ constructor(options: { id: Plugin["id"] }) {
42
+ super(
43
+ `Plugin "${options.id}" defines the \`saveMessages()\` function, but it was already defined by another plugin.\n\nInlang only allows one plugin to define the \`saveMessages()\` function.`
44
+ )
48
45
  this.name = "PluginSaveMessagesFunctionAlreadyDefinedError"
49
46
  }
50
47
  }
51
48
 
52
- export class PluginReturnedInvalidCustomApiError extends PluginError {
53
- constructor(message: string, options: PluginErrorOptions) {
54
- super(message, options)
49
+ export class PluginReturnedInvalidCustomApiError extends Error {
50
+ constructor(options: { id: Plugin["id"]; cause: ErrorOptions["cause"] }) {
51
+ super(`Plugin "${options.id}" returned an invalid custom API:\n\n${options.cause}`, options)
55
52
  this.name = "PluginReturnedInvalidCustomApiError"
56
53
  }
57
54
  }
58
55
 
59
- export class PluginsDoNotProvideLoadOrSaveMessagesError extends PluginError {
60
- constructor(message: string, options: PluginErrorOptions) {
61
- super(message, options)
56
+ export class PluginsDoNotProvideLoadOrSaveMessagesError extends Error {
57
+ constructor() {
58
+ super(
59
+ `No plugin provides a \`loadMessages()\` or \`saveMessages()\` function\n\nIn case no plugin threw an error, you likely forgot to add a plugin that handles the loading and saving of messages. Refer to the marketplace for available plugins https://inlang.com/marketplace.`
60
+ )
62
61
  this.name = "PluginsDoNotProvideLoadOrSaveMessagesError"
63
- options.plugin = "plugin.inlang.missing"
64
62
  }
65
63
  }