@inlang/sdk 0.15.0 → 0.17.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.
@@ -32,4 +32,10 @@ export declare class ModuleExportIsInvalidError extends ModuleError {
32
32
  errors: ValueError[];
33
33
  });
34
34
  }
35
+ export declare class ModuleSettingsAreInvalidError extends ModuleError {
36
+ constructor(options: {
37
+ module: string;
38
+ errors: ValueError[];
39
+ });
40
+ }
35
41
  //# sourceMappingURL=errors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/resolve-modules/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAC1D,cAAc,qBAAqB,CAAA;AACnC,cAAc,gCAAgC,CAAA;AAE9C,qBAAa,WAAY,SAAQ,KAAK;IACrC,SAAgB,MAAM,EAAE,MAAM,CAAA;gBAElB,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,KAAK,CAAA;KAAE;CAMvE;AAED;;GAEG;AACH,qBAAa,uBAAwB,SAAQ,WAAW;gBAC3C,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,KAAK,CAAA;KAAE;CAOtD;AAED;;GAEG;AACH,qBAAa,iBAAkB,SAAQ,WAAW;gBACrC,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAA;KAAE;CAIrD;AAED,qBAAa,0BAA2B,SAAQ,WAAW;gBAC9C,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,UAAU,EAAE,CAAA;KAAE;CAS7D"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/resolve-modules/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAC1D,cAAc,qBAAqB,CAAA;AACnC,cAAc,gCAAgC,CAAA;AAE9C,qBAAa,WAAY,SAAQ,KAAK;IACrC,SAAgB,MAAM,EAAE,MAAM,CAAA;gBAElB,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,KAAK,CAAA;KAAE;CAMvE;AAED;;GAEG;AACH,qBAAa,uBAAwB,SAAQ,WAAW;gBAC3C,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,KAAK,CAAA;KAAE;CAOtD;AAED;;GAEG;AACH,qBAAa,iBAAkB,SAAQ,WAAW;gBACrC,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAA;KAAE;CAIrD;AAED,qBAAa,0BAA2B,SAAQ,WAAW;gBAC9C,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,UAAU,EAAE,CAAA;KAAE;CAY7D;AAED,qBAAa,6BAA8B,SAAQ,WAAW;gBACjD,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,UAAU,EAAE,CAAA;KAAE;CAc7D"}
@@ -30,8 +30,16 @@ export class ModuleImportError extends ModuleError {
30
30
  export class ModuleExportIsInvalidError extends ModuleError {
31
31
  constructor(options) {
32
32
  super(`The export(s) of "${options.module}" are invalid:\n\n${options.errors
33
- .map((error) => `"${error.path}" "${error.value}": "${error.message}"`)
33
+ .map((error) => `"${error.path}" "${JSON.stringify(error.value, undefined, 2)}": "${error.message}"`)
34
34
  .join("\n")}`, options);
35
35
  this.name = "ModuleExportIsInvalidError";
36
36
  }
37
37
  }
38
+ export class ModuleSettingsAreInvalidError extends ModuleError {
39
+ constructor(options) {
40
+ super(`The settings are invalid of "${options.module}" are invalid:\n\n${options.errors
41
+ .map((error) => `Path "${error.path}" with value "${JSON.stringify(error.value, undefined, 2)}": "${error.message}"`)
42
+ .join("\n")}`, options);
43
+ this.name = "ModuleSettingsAreInvalidError";
44
+ }
45
+ }
@@ -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, PluginReturnedInvalidCustomApiError, PluginHasInvalidSchemaError, PluginsDoNotProvideLoadOrSaveMessagesError, } from "./errors.js";
4
+ import { PluginLoadMessagesFunctionAlreadyDefinedError, PluginSaveMessagesFunctionAlreadyDefinedError, PluginHasInvalidIdError, PluginReturnedInvalidCustomApiError, 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
@@ -18,25 +18,6 @@ it("should return an error if a plugin uses an invalid id", async () => {
18
18
  });
19
19
  expect(resolved.errors[0]).toBeInstanceOf(PluginHasInvalidIdError);
20
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: {},
37
- });
38
- expect(resolved.errors[0]).toBeInstanceOf(PluginHasInvalidSchemaError);
39
- });
40
21
  it("should expose the project settings including the plugin settings", async () => {
41
22
  const settings = {
42
23
  sourceLanguageTag: "en",
@@ -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,qBA+E5B,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;AAoBvD,eAAO,MAAM,cAAc,EAAE,qBAiG5B,CAAA"}
@@ -1,10 +1,11 @@
1
1
  import { InlangModule } from "@inlang/module";
2
- import { ModuleError, ModuleImportError, ModuleHasNoExportsError, ModuleExportIsInvalidError, } from "./errors.js";
2
+ import { ModuleError, ModuleImportError, ModuleHasNoExportsError, ModuleExportIsInvalidError, ModuleSettingsAreInvalidError, } from "./errors.js";
3
3
  import { tryCatch } from "@inlang/result";
4
4
  import { resolveMessageLintRules } from "./message-lint-rules/resolveMessageLintRules.js";
5
5
  import { createImport } from "./import.js";
6
6
  import { resolvePlugins } from "./plugins/resolvePlugins.js";
7
7
  import { TypeCompiler } from "@sinclair/typebox/compiler";
8
+ import { validatedModuleSettings } from "./validatedModuleSettings.js";
8
9
  const ModuleCompiler = TypeCompiler.Compile(InlangModule);
9
10
  export const resolveModules = async (args) => {
10
11
  const _import = args._import ?? createImport({ readFile: args.nodeishFs.readFile });
@@ -32,6 +33,7 @@ export const resolveModules = async (args) => {
32
33
  }));
33
34
  continue;
34
35
  }
36
+ // -- CHECK IF MODULE IS SYNTACTIALLY VALID
35
37
  const isValidModule = ModuleCompiler.Check(importedModule.data);
36
38
  if (isValidModule === false) {
37
39
  const errors = [...ModuleCompiler.Errors(importedModule.data)];
@@ -41,6 +43,15 @@ export const resolveModules = async (args) => {
41
43
  }));
42
44
  continue;
43
45
  }
46
+ // -- VALIDATE MODULE SETTINGS
47
+ const result = validatedModuleSettings({
48
+ settingsSchema: importedModule.data.default.settingsSchema,
49
+ moduleSettings: args.settings[importedModule.data.default.id],
50
+ });
51
+ if (result !== "isValid") {
52
+ moduleErrors.push(new ModuleSettingsAreInvalidError({ module: module, errors: result }));
53
+ continue;
54
+ }
44
55
  meta.push({
45
56
  module: module,
46
57
  id: importedModule.data.default.id,
@@ -52,7 +63,7 @@ export const resolveModules = async (args) => {
52
63
  allMessageLintRules.push(importedModule.data.default);
53
64
  }
54
65
  else {
55
- throw new Error(`Unimplemented module type. Must start with "plugin." or "messageLintRule.`);
66
+ moduleErrors.push(new ModuleError(`Unimplemented module type ${importedModule.data.default.id}.The module has not been installed.`, { module: module }));
56
67
  }
57
68
  }
58
69
  const resolvedPlugins = await resolvePlugins({
@@ -1,6 +1,7 @@
1
1
  import { expect, it } from "vitest";
2
- import { ModuleError, ModuleExportIsInvalidError, ModuleHasNoExportsError, ModuleImportError, } from "./errors.js";
2
+ import { ModuleError, ModuleExportIsInvalidError, ModuleHasNoExportsError, ModuleImportError, ModuleSettingsAreInvalidError, } from "./errors.js";
3
3
  import { resolveModules } from "./resolveModules.js";
4
+ import { Type } from "@sinclair/typebox";
4
5
  it("should return an error if a plugin cannot be imported", async () => {
5
6
  const settings = {
6
7
  sourceLanguageTag: "en",
@@ -127,3 +128,27 @@ it("should handle other unhandled errors during plugin resolution", async () =>
127
128
  // Assert results
128
129
  expect(resolved.errors[0]).toBeInstanceOf(ModuleError);
129
130
  });
131
+ it("should return an error if a moduleSettings are invalid", async () => {
132
+ const settings = {
133
+ sourceLanguageTag: "en",
134
+ languageTags: ["de", "en"],
135
+ modules: ["plugin.js"],
136
+ "plugin.namespace.mock": {
137
+ ignore: ["invalid"],
138
+ },
139
+ };
140
+ const _import = async () => ({
141
+ default: {
142
+ id: "plugin.namespace.mock",
143
+ description: { en: "Mock plugin description" },
144
+ displayName: { en: "Mock Plugin" },
145
+ settingsSchema: Type.Object({
146
+ ignore: Type.String(),
147
+ }),
148
+ },
149
+ });
150
+ // Call the function
151
+ const resolved = await resolveModules({ settings, _import, nodeishFs: {} });
152
+ // Assert results
153
+ expect(resolved.errors[0]).toBeInstanceOf(ModuleSettingsAreInvalidError);
154
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=validateModuleSettings.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validateModuleSettings.test.d.ts","sourceRoot":"","sources":["../../src/resolve-modules/validateModuleSettings.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,71 @@
1
+ /* eslint-disable @typescript-eslint/no-non-null-assertion */
2
+ import { expect, test } from "vitest";
3
+ import { Plugin, MessageLintRule } from "@inlang/sdk";
4
+ // import { createNodeishMemoryFs } from "@lix-js/fs"
5
+ import { Type } from "@sinclair/typebox";
6
+ import { validatedModuleSettings } from "./validatedModuleSettings.js";
7
+ const mockPluginSchema = Type.Object({
8
+ pathPattern: Type.Union([
9
+ Type.String({
10
+ pattern: "^(\\./|\\../|/)[^*]*\\{languageTag\\}[^*]*\\.json",
11
+ description: "The pathPattern must contain `{languageTag}` and end with `.json`.",
12
+ }),
13
+ Type.Record(Type.String({
14
+ pattern: "^[^.]+$",
15
+ description: "Dots are not allowd ",
16
+ examples: ["website", "app", "homepage"],
17
+ }), Type.String({
18
+ pattern: "^(\\./|\\../|/)[^*]*\\{languageTag\\}[^*]*\\.json",
19
+ description: "The pathPattern must contain `{languageTag}` and end with `.json`.",
20
+ })),
21
+ ]),
22
+ variableReferencePattern: Type.Array(Type.String()),
23
+ });
24
+ const mockMessageLintRuleSchema = Type.Object({
25
+ ignore: Type.Array(Type.String({
26
+ pattern: '([^"]*)',
27
+ description: "All items in the array need quotaion marks at the end and beginning",
28
+ })),
29
+ });
30
+ test("if PluginSchema does match with the moduleSettings", async () => {
31
+ const isValid = await validatedModuleSettings({
32
+ settingsSchema: mockPluginSchema,
33
+ moduleSettings: {
34
+ pathPattern: "./examples/example01/{languageTag}.json",
35
+ variableReferencePattern: ["{", "}"],
36
+ },
37
+ });
38
+ expect(isValid).toBe("isValid");
39
+ });
40
+ test("if namespace settings are valide", async () => {
41
+ const isValid = validatedModuleSettings({
42
+ settingsSchema: mockPluginSchema,
43
+ moduleSettings: {
44
+ pathPattern: {
45
+ website: "./{languageTag}examplerFolder/ExampleFile.json",
46
+ app: "../{languageTag}examplerFolder/ExampleFile.json",
47
+ footer: "./{languageTag}examplerFolder/ExampleFile.json",
48
+ },
49
+ variableReferencePattern: ["{", "}"],
50
+ },
51
+ });
52
+ expect(isValid).toBe("isValid");
53
+ });
54
+ test(" if MessageLintRuleSchema match with the settings", async () => {
55
+ const isValid = await validatedModuleSettings({
56
+ settingsSchema: mockMessageLintRuleSchema,
57
+ moduleSettings: {
58
+ ignore: ["example", "warning"],
59
+ },
60
+ });
61
+ expect(isValid).toBe("isValid");
62
+ });
63
+ test("if messageLintRule settings are not matching to the settingsSchema would pass ", async () => {
64
+ const isValid = validatedModuleSettings({
65
+ settingsSchema: mockMessageLintRuleSchema,
66
+ moduleSettings: {
67
+ ignore: "example",
68
+ },
69
+ });
70
+ expect(isValid).not.toBe("isValid");
71
+ });
@@ -0,0 +1,7 @@
1
+ import type { InlangModule } from "@inlang/module";
2
+ import { type ValueError } from "@sinclair/typebox/value";
3
+ export declare const validatedModuleSettings: (args: {
4
+ settingsSchema: InlangModule["default"]["settingsSchema"];
5
+ moduleSettings: unknown;
6
+ }) => "isValid" | ValueError[];
7
+ //# sourceMappingURL=validatedModuleSettings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validatedModuleSettings.d.ts","sourceRoot":"","sources":["../../src/resolve-modules/validatedModuleSettings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAElD,OAAO,EAAS,KAAK,UAAU,EAAE,MAAM,yBAAyB,CAAA;AAEhE,eAAO,MAAM,uBAAuB,SAAU;IAC7C,cAAc,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC,gBAAgB,CAAC,CAAA;IACzD,cAAc,EAAE,OAAO,CAAA;CACvB,KAAG,SAAS,GAAG,UAAU,EAWzB,CAAA"}
@@ -0,0 +1,11 @@
1
+ import { Value } from "@sinclair/typebox/value";
2
+ export const validatedModuleSettings = (args) => {
3
+ if (args.settingsSchema && args.moduleSettings) {
4
+ const hasValidSettings = Value.Check(args.settingsSchema, args.moduleSettings);
5
+ if (hasValidSettings === false) {
6
+ const errors = [...Value.Errors(args.settingsSchema, args.moduleSettings)];
7
+ return errors;
8
+ }
9
+ }
10
+ return "isValid";
11
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@inlang/sdk",
3
3
  "type": "module",
4
- "version": "0.15.0",
4
+ "version": "0.17.0",
5
5
  "license": "Apache-2.0",
6
6
  "publishConfig": {
7
7
  "access": "public"
@@ -32,6 +32,7 @@ const mockPlugin: Plugin = {
32
32
  id: "plugin.project.i18next",
33
33
  description: { en: "Mock plugin description" },
34
34
  displayName: { en: "Mock Plugin" },
35
+
35
36
  loadMessages: () => exampleMessages,
36
37
  saveMessages: () => undefined,
37
38
  }
@@ -76,6 +77,7 @@ const mockLintRule: MessageLintRule = {
76
77
  id: "messageLintRule.namespace.mock",
77
78
  description: { en: "Mock lint rule description" },
78
79
  displayName: { en: "Mock Lint Rule" },
80
+
79
81
  run: () => undefined,
80
82
  }
81
83
 
@@ -179,6 +181,7 @@ describe("messages", () => {
179
181
  description: {
180
182
  en: "wo",
181
183
  },
184
+
182
185
  loadMessages: ({ settings }) => (settings.languageTags.length ? exampleMessages : []),
183
186
  saveMessages: () => undefined,
184
187
  }
@@ -7,6 +7,7 @@ const lintRule1 = {
7
7
  id: "messageLintRule.x.1",
8
8
  displayName: { en: "" },
9
9
  description: { en: "" },
10
+
10
11
  run: vi.fn(),
11
12
  } satisfies MessageLintRule
12
13
 
@@ -14,6 +15,7 @@ const lintRule2 = {
14
15
  id: "messageLintRule.x.2",
15
16
  displayName: { en: "" },
16
17
  description: { en: "" },
18
+
17
19
  run: vi.fn(),
18
20
  } satisfies MessageLintRule
19
21
 
@@ -8,6 +8,7 @@ const lintRule1 = {
8
8
  id: "messageLintRule.r.1",
9
9
  displayName: "",
10
10
  description: "",
11
+
11
12
  run: vi.fn(),
12
13
  } satisfies MessageLintRule
13
14
 
@@ -15,6 +16,7 @@ const lintRule2 = {
15
16
  id: "messageLintRule.r.2",
16
17
  displayName: "",
17
18
  description: "",
19
+
18
20
  run: vi.fn(),
19
21
  } satisfies MessageLintRule
20
22
 
@@ -39,6 +39,7 @@ const mockPlugin: Plugin = {
39
39
  id: "plugin.project.i18next",
40
40
  description: { en: "Mock plugin description" },
41
41
  displayName: { en: "Mock Plugin" },
42
+
42
43
  loadMessages: () => exampleMessages,
43
44
  saveMessages: () => undefined as any,
44
45
  addCustomApi: () => ({
@@ -425,6 +426,7 @@ describe("functionality", () => {
425
426
  id: "plugin.project.i18next",
426
427
  description: { en: "Mock plugin description" },
427
428
  displayName: { en: "Mock Plugin" },
429
+
428
430
  loadMessages: () => [{ id: "some-message", selectors: [], variants: [] }],
429
431
  saveMessages: () => undefined,
430
432
  }
@@ -462,6 +464,7 @@ describe("functionality", () => {
462
464
  id: "messageLintRule.namepsace.mock",
463
465
  description: { en: "Mock lint rule description" },
464
466
  displayName: { en: "Mock Lint Rule" },
467
+
465
468
  run: ({ report }) => {
466
469
  report({
467
470
  messageId: "some-message",
@@ -474,6 +477,7 @@ describe("functionality", () => {
474
477
  id: "plugin.project.i18next",
475
478
  description: { en: "Mock plugin description" },
476
479
  displayName: { en: "Mock Plugin" },
480
+
477
481
  loadMessages: () => [{ id: "some-message", selectors: [], variants: [] }],
478
482
  saveMessages: () => undefined,
479
483
  }
@@ -579,6 +583,7 @@ describe("functionality", () => {
579
583
  id: "plugin.project.json",
580
584
  description: { en: "Mock plugin description" },
581
585
  displayName: { en: "Mock Plugin" },
586
+
582
587
  loadMessages: () => exampleMessages,
583
588
  saveMessages: mockSaveFn,
584
589
  }
@@ -753,6 +758,7 @@ describe("functionality", () => {
753
758
  id: "plugin.placeholder.name",
754
759
  description: "Mock plugin description",
755
760
  displayName: "Mock Plugin",
761
+
756
762
  loadMessages: () => [
757
763
  createMessage("first", { en: "first message" }),
758
764
  createMessage("second", { en: "second message" }),
@@ -40,10 +40,30 @@ export class ModuleExportIsInvalidError extends ModuleError {
40
40
  constructor(options: { module: string; errors: ValueError[] }) {
41
41
  super(
42
42
  `The export(s) of "${options.module}" are invalid:\n\n${options.errors
43
- .map((error) => `"${error.path}" "${error.value}": "${error.message}"`)
43
+ .map(
44
+ (error) =>
45
+ `"${error.path}" "${JSON.stringify(error.value, undefined, 2)}": "${error.message}"`
46
+ )
44
47
  .join("\n")}`,
45
48
  options
46
49
  )
47
50
  this.name = "ModuleExportIsInvalidError"
48
51
  }
49
52
  }
53
+
54
+ export class ModuleSettingsAreInvalidError extends ModuleError {
55
+ constructor(options: { module: string; errors: ValueError[] }) {
56
+ super(
57
+ `The settings are invalid of "${options.module}" are invalid:\n\n${options.errors
58
+ .map(
59
+ (error) =>
60
+ `Path "${error.path}" with value "${JSON.stringify(error.value, undefined, 2)}": "${
61
+ error.message
62
+ }"`
63
+ )
64
+ .join("\n")}`,
65
+ options
66
+ )
67
+ this.name = "ModuleSettingsAreInvalidError"
68
+ }
69
+ }
@@ -6,7 +6,6 @@ import {
6
6
  PluginSaveMessagesFunctionAlreadyDefinedError,
7
7
  PluginHasInvalidIdError,
8
8
  PluginReturnedInvalidCustomApiError,
9
- PluginHasInvalidSchemaError,
10
9
  PluginsDoNotProvideLoadOrSaveMessagesError,
11
10
  } from "./errors.js"
12
11
  import type { Plugin } from "@inlang/plugin"
@@ -31,28 +30,6 @@ it("should return an error if a plugin uses an invalid id", async () => {
31
30
  expect(resolved.errors[0]).toBeInstanceOf(PluginHasInvalidIdError)
32
31
  })
33
32
 
34
- it("should return an error if a plugin uses APIs that are not available", async () => {
35
- const mockPlugin: Plugin = {
36
- id: "plugin.namespace.undefinedApi",
37
- description: { en: "My plugin description" },
38
- displayName: { en: "My plugin" },
39
- // @ts-expect-error the key is not available in type
40
- nonExistentKey: {
41
- nonexistentOptions: "value",
42
- },
43
- loadMessages: () => undefined as any,
44
- saveMessages: () => undefined as any,
45
- }
46
-
47
- const resolved = await resolvePlugins({
48
- plugins: [mockPlugin],
49
- settings: {} as any,
50
- nodeishFs: {} as any,
51
- })
52
-
53
- expect(resolved.errors[0]).toBeInstanceOf(PluginHasInvalidSchemaError)
54
- })
55
-
56
33
  it("should expose the project settings including the plugin settings", async () => {
57
34
  const settings: ProjectSettings = {
58
35
  sourceLanguageTag: "en",
@@ -114,12 +91,14 @@ describe("loadMessages", () => {
114
91
  id: "plugin.namepsace.loadMessagesFirst",
115
92
  description: { en: "My plugin description" },
116
93
  displayName: { en: "My plugin" },
94
+
117
95
  loadMessages: async () => undefined as any,
118
96
  }
119
97
  const mockPlugin2: Plugin = {
120
98
  id: "plugin.namepsace.loadMessagesSecond",
121
99
  description: { en: "My plugin description" },
122
100
  displayName: { en: "My plugin" },
101
+
123
102
  loadMessages: async () => undefined as any,
124
103
  }
125
104
 
@@ -137,6 +116,7 @@ describe("loadMessages", () => {
137
116
  id: "plugin.namepsace.loadMessagesFirst",
138
117
  description: { en: "My plugin description" },
139
118
  displayName: { en: "My plugin" },
119
+
140
120
  saveMessages: async () => undefined as any,
141
121
  }
142
122
 
@@ -181,7 +161,6 @@ describe("saveMessages", () => {
181
161
  id: "plugin.namepsace.saveMessages2",
182
162
  description: { en: "My plugin description" },
183
163
  displayName: { en: "My plugin" },
184
-
185
164
  saveMessages: async () => undefined as any,
186
165
  }
187
166
 
@@ -199,6 +178,7 @@ describe("saveMessages", () => {
199
178
  id: "plugin.namepsace.loadMessagesFirst",
200
179
  description: { en: "My plugin description" },
201
180
  displayName: { en: "My plugin" },
181
+
202
182
  loadMessages: async () => undefined as any,
203
183
  }
204
184
 
@@ -218,7 +198,6 @@ describe("addCustomApi", () => {
218
198
  id: "plugin.namespace.placeholder",
219
199
  description: { en: "My plugin description" },
220
200
  displayName: { en: "My plugin" },
221
-
222
201
  addCustomApi: () => ({
223
202
  "my-app": {
224
203
  messageReferenceMatcher: () => undefined as any,
@@ -253,7 +232,6 @@ describe("addCustomApi", () => {
253
232
  id: "plugin.namespace.placeholder2",
254
233
  description: { en: "My plugin description" },
255
234
  displayName: { en: "My plugin" },
256
-
257
235
  addCustomApi: () => ({
258
236
  "my-app-3": {
259
237
  functionOfMyApp3: () => undefined as any,
@@ -7,10 +7,12 @@ import {
7
7
  ModuleExportIsInvalidError,
8
8
  ModuleHasNoExportsError,
9
9
  ModuleImportError,
10
+ ModuleSettingsAreInvalidError,
10
11
  } from "./errors.js"
11
12
  import { resolveModules } from "./resolveModules.js"
12
13
  import type { ProjectSettings } from "@inlang/project-settings"
13
14
  import type { InlangModule } from "@inlang/module"
15
+ import { Type } from "@sinclair/typebox"
14
16
 
15
17
  it("should return an error if a plugin cannot be imported", async () => {
16
18
  const settings: ProjectSettings = {
@@ -39,6 +41,7 @@ it("should resolve plugins and message lint rules successfully", async () => {
39
41
  id: "plugin.namespace.mock",
40
42
  description: { en: "Mock plugin description" },
41
43
  displayName: { en: "Mock Plugin" },
44
+
42
45
  loadMessages: () => undefined as any,
43
46
  saveMessages: () => undefined as any,
44
47
  addCustomApi: () => ({
@@ -52,6 +55,7 @@ it("should resolve plugins and message lint rules successfully", async () => {
52
55
  id: "messageLintRule.namespace.mock",
53
56
  description: { en: "Mock lint rule description" },
54
57
  displayName: { en: "Mock Lint Rule" },
58
+
55
59
  run: () => undefined,
56
60
  }
57
61
 
@@ -160,3 +164,30 @@ it("should handle other unhandled errors during plugin resolution", async () =>
160
164
  // Assert results
161
165
  expect(resolved.errors[0]).toBeInstanceOf(ModuleError)
162
166
  })
167
+ it("should return an error if a moduleSettings are invalid", async () => {
168
+ const settings: ProjectSettings = {
169
+ sourceLanguageTag: "en",
170
+ languageTags: ["de", "en"],
171
+ modules: ["plugin.js"],
172
+ "plugin.namespace.mock": {
173
+ ignore: ["invalid"],
174
+ },
175
+ }
176
+
177
+ const _import = async () => ({
178
+ default: {
179
+ id: "plugin.namespace.mock",
180
+ description: { en: "Mock plugin description" },
181
+ displayName: { en: "Mock Plugin" },
182
+ settingsSchema: Type.Object({
183
+ ignore: Type.String(),
184
+ }),
185
+ },
186
+ })
187
+
188
+ // Call the function
189
+ const resolved = await resolveModules({ settings, _import, nodeishFs: {} as any })
190
+
191
+ // Assert results
192
+ expect(resolved.errors[0]).toBeInstanceOf(ModuleSettingsAreInvalidError)
193
+ })
@@ -5,6 +5,7 @@ import {
5
5
  ModuleImportError,
6
6
  ModuleHasNoExportsError,
7
7
  ModuleExportIsInvalidError,
8
+ ModuleSettingsAreInvalidError,
8
9
  } from "./errors.js"
9
10
  import { tryCatch } from "@inlang/result"
10
11
  import { resolveMessageLintRules } from "./message-lint-rules/resolveMessageLintRules.js"
@@ -13,6 +14,7 @@ import { createImport } from "./import.js"
13
14
  import type { MessageLintRule } from "@inlang/message-lint-rule"
14
15
  import { resolvePlugins } from "./plugins/resolvePlugins.js"
15
16
  import { TypeCompiler } from "@sinclair/typebox/compiler"
17
+ import { validatedModuleSettings } from "./validatedModuleSettings.js"
16
18
 
17
19
  const ModuleCompiler = TypeCompiler.Compile(InlangModule)
18
20
 
@@ -31,8 +33,8 @@ export const resolveModules: ResolveModuleFunction = async (args) => {
31
33
  */
32
34
 
33
35
  const importedModule = await tryCatch<InlangModule>(() => _import(module))
34
-
35
36
  // -- IMPORT MODULE --
37
+
36
38
  if (importedModule.error) {
37
39
  moduleErrors.push(
38
40
  new ModuleImportError({
@@ -44,6 +46,7 @@ export const resolveModules: ResolveModuleFunction = async (args) => {
44
46
  }
45
47
 
46
48
  // -- MODULE DOES NOT EXPORT ANYTHING --
49
+
47
50
  if (importedModule.data?.default === undefined) {
48
51
  moduleErrors.push(
49
52
  new ModuleHasNoExportsError({
@@ -53,8 +56,9 @@ export const resolveModules: ResolveModuleFunction = async (args) => {
53
56
  continue
54
57
  }
55
58
 
56
- const isValidModule = ModuleCompiler.Check(importedModule.data)
59
+ // -- CHECK IF MODULE IS SYNTACTIALLY VALID
57
60
 
61
+ const isValidModule = ModuleCompiler.Check(importedModule.data)
58
62
  if (isValidModule === false) {
59
63
  const errors = [...ModuleCompiler.Errors(importedModule.data)]
60
64
  moduleErrors.push(
@@ -63,6 +67,18 @@ export const resolveModules: ResolveModuleFunction = async (args) => {
63
67
  errors,
64
68
  })
65
69
  )
70
+
71
+ continue
72
+ }
73
+
74
+ // -- VALIDATE MODULE SETTINGS
75
+
76
+ const result = validatedModuleSettings({
77
+ settingsSchema: importedModule.data.default.settingsSchema,
78
+ moduleSettings: args.settings[importedModule.data.default.id],
79
+ })
80
+ if (result !== "isValid") {
81
+ moduleErrors.push(new ModuleSettingsAreInvalidError({ module: module, errors: result }))
66
82
  continue
67
83
  }
68
84
 
@@ -76,10 +92,14 @@ export const resolveModules: ResolveModuleFunction = async (args) => {
76
92
  } else if (importedModule.data.default.id.startsWith("messageLintRule.")) {
77
93
  allMessageLintRules.push(importedModule.data.default as MessageLintRule)
78
94
  } else {
79
- throw new Error(`Unimplemented module type. Must start with "plugin." or "messageLintRule.`)
95
+ moduleErrors.push(
96
+ new ModuleError(
97
+ `Unimplemented module type ${importedModule.data.default.id}.The module has not been installed.`,
98
+ { module: module }
99
+ )
100
+ )
80
101
  }
81
102
  }
82
-
83
103
  const resolvedPlugins = await resolvePlugins({
84
104
  plugins: allPlugins,
85
105
  settings: args.settings,
@@ -0,0 +1,85 @@
1
+ /* eslint-disable @typescript-eslint/no-non-null-assertion */
2
+ import { expect, test } from "vitest"
3
+ import { Plugin, MessageLintRule } from "@inlang/sdk"
4
+ // import { createNodeishMemoryFs } from "@lix-js/fs"
5
+ import { Type } from "@sinclair/typebox"
6
+ import { validatedModuleSettings } from "./validatedModuleSettings.js"
7
+
8
+ const mockPluginSchema: Plugin["settingsSchema"] = Type.Object({
9
+ pathPattern: Type.Union([
10
+ Type.String({
11
+ pattern: "^(\\./|\\../|/)[^*]*\\{languageTag\\}[^*]*\\.json",
12
+ description: "The pathPattern must contain `{languageTag}` and end with `.json`.",
13
+ }),
14
+ Type.Record(
15
+ Type.String({
16
+ pattern: "^[^.]+$",
17
+ description: "Dots are not allowd ",
18
+ examples: ["website", "app", "homepage"],
19
+ }),
20
+ Type.String({
21
+ pattern: "^(\\./|\\../|/)[^*]*\\{languageTag\\}[^*]*\\.json",
22
+ description: "The pathPattern must contain `{languageTag}` and end with `.json`.",
23
+ })
24
+ ),
25
+ ]),
26
+ variableReferencePattern: Type.Array(Type.String()),
27
+ })
28
+
29
+ const mockMessageLintRuleSchema: MessageLintRule["settingsSchema"] = Type.Object({
30
+ ignore: Type.Array(
31
+ Type.String({
32
+ pattern: '([^"]*)',
33
+ description: "All items in the array need quotaion marks at the end and beginning",
34
+ })
35
+ ),
36
+ })
37
+
38
+ test("if PluginSchema does match with the moduleSettings", async () => {
39
+ const isValid = await validatedModuleSettings({
40
+ settingsSchema: mockPluginSchema,
41
+ moduleSettings: {
42
+ pathPattern: "./examples/example01/{languageTag}.json",
43
+ variableReferencePattern: ["{", "}"],
44
+ },
45
+ })
46
+
47
+ expect(isValid).toBe("isValid")
48
+ })
49
+
50
+ test("if namespace settings are valide", async () => {
51
+ const isValid = validatedModuleSettings({
52
+ settingsSchema: mockPluginSchema,
53
+ moduleSettings: {
54
+ pathPattern: {
55
+ website: "./{languageTag}examplerFolder/ExampleFile.json",
56
+ app: "../{languageTag}examplerFolder/ExampleFile.json",
57
+ footer: "./{languageTag}examplerFolder/ExampleFile.json",
58
+ },
59
+ variableReferencePattern: ["{", "}"],
60
+ },
61
+ })
62
+ expect(isValid).toBe("isValid")
63
+ })
64
+
65
+ test(" if MessageLintRuleSchema match with the settings", async () => {
66
+ const isValid = await validatedModuleSettings({
67
+ settingsSchema: mockMessageLintRuleSchema,
68
+ moduleSettings: {
69
+ ignore: ["example", "warning"],
70
+ },
71
+ })
72
+
73
+ expect(isValid).toBe("isValid")
74
+ })
75
+
76
+ test("if messageLintRule settings are not matching to the settingsSchema would pass ", async () => {
77
+ const isValid = validatedModuleSettings({
78
+ settingsSchema: mockMessageLintRuleSchema,
79
+ moduleSettings: {
80
+ ignore: "example",
81
+ },
82
+ })
83
+
84
+ expect(isValid).not.toBe("isValid")
85
+ })
@@ -0,0 +1,19 @@
1
+ import type { InlangModule } from "@inlang/module"
2
+ import type { TSchema } from "@sinclair/typebox"
3
+ import { Value, type ValueError } from "@sinclair/typebox/value"
4
+
5
+ export const validatedModuleSettings = (args: {
6
+ settingsSchema: InlangModule["default"]["settingsSchema"]
7
+ moduleSettings: unknown
8
+ }): "isValid" | ValueError[] => {
9
+ if (args.settingsSchema && args.moduleSettings) {
10
+ const hasValidSettings = Value.Check(args.settingsSchema as TSchema, args.moduleSettings)
11
+
12
+ if (hasValidSettings === false) {
13
+ const errors = [...Value.Errors(args.settingsSchema as TSchema, args.moduleSettings)]
14
+
15
+ return errors
16
+ }
17
+ }
18
+ return "isValid"
19
+ }