@inlang/sdk 0.15.0 → 0.16.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;CAS7D;AAED,qBAAa,6BAA8B,SAAQ,WAAW;gBACjD,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,UAAU,EAAE,CAAA;KAAE;CAS7D"}
@@ -35,3 +35,11 @@ export class ModuleExportIsInvalidError extends ModuleError {
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 "${module}" are invalid:\n\n${options.errors
41
+ .map((error) => `Path "${error.path}" with value "${error.value}": "${error.message}"`)
42
+ .join("\n")}`, options);
43
+ this.name = "ModuleSettingsAreInvalidError";
44
+ }
45
+ }
@@ -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,62 @@
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 PluginSettings must contain `{languageTag}` and end with `.json`.",
12
+ }),
13
+ Type.Record(Type.String({}), Type.String({
14
+ pattern: "^[^*]*\\{languageTag\\}[^*]*\\.json",
15
+ description: "The PluginSettings must contain `{languageTag}` and end with `.json`.",
16
+ })),
17
+ ]),
18
+ variableReferencePattern: Type.Array(Type.String()),
19
+ });
20
+ const mockMessageLintRuleSchema = Type.Object({
21
+ ignore: Type.Array(Type.String({
22
+ pattern: '([^"]*)',
23
+ description: "All items in the array need quotaion marks at the end and beginning",
24
+ })),
25
+ });
26
+ test("if PluginSchema does match with the moduleSettings", async () => {
27
+ const isValid = await validatedModuleSettings({
28
+ settingsSchema: mockPluginSchema,
29
+ moduleSettings: {
30
+ pathPattern: "./examples/example01/{languageTag}.json",
31
+ variableReferencePattern: ["{", "}"],
32
+ },
33
+ });
34
+ expect(isValid).toBe("isValid");
35
+ });
36
+ test("if invalid module settings would pass", async () => {
37
+ const isValid = validatedModuleSettings({
38
+ settingsSchema: mockPluginSchema,
39
+ moduleSettings: {
40
+ pathPattern: "./examples/example01/{languageTag}.json",
41
+ },
42
+ });
43
+ expect(isValid).not.toBe("isValid");
44
+ });
45
+ test(" if MessageLintRuleSchema match with the settings", async () => {
46
+ const isValid = await validatedModuleSettings({
47
+ settingsSchema: mockMessageLintRuleSchema,
48
+ moduleSettings: {
49
+ ignore: ["example", "warning"],
50
+ },
51
+ });
52
+ expect(isValid).toBe("isValid");
53
+ });
54
+ test("if messageLintRule settings are not matching to the settingsSchema would pass ", async () => {
55
+ const isValid = validatedModuleSettings({
56
+ settingsSchema: mockMessageLintRuleSchema,
57
+ moduleSettings: {
58
+ ignore: "example",
59
+ },
60
+ });
61
+ expect(isValid).not.toBe("isValid");
62
+ });
@@ -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,EAAG,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAEnD,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.16.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" }),
@@ -47,3 +47,15 @@ export class ModuleExportIsInvalidError extends ModuleError {
47
47
  this.name = "ModuleExportIsInvalidError"
48
48
  }
49
49
  }
50
+
51
+ export class ModuleSettingsAreInvalidError extends ModuleError {
52
+ constructor(options: { module: string; errors: ValueError[] }) {
53
+ super(
54
+ `The settings are invalid of "${module}" are invalid:\n\n${options.errors
55
+ .map((error) => `Path "${error.path}" with value "${error.value}": "${error.message}"`)
56
+ .join("\n")}`,
57
+ options
58
+ )
59
+ this.name = "ModuleSettingsAreInvalidError"
60
+ }
61
+ }
@@ -114,12 +114,14 @@ describe("loadMessages", () => {
114
114
  id: "plugin.namepsace.loadMessagesFirst",
115
115
  description: { en: "My plugin description" },
116
116
  displayName: { en: "My plugin" },
117
+
117
118
  loadMessages: async () => undefined as any,
118
119
  }
119
120
  const mockPlugin2: Plugin = {
120
121
  id: "plugin.namepsace.loadMessagesSecond",
121
122
  description: { en: "My plugin description" },
122
123
  displayName: { en: "My plugin" },
124
+
123
125
  loadMessages: async () => undefined as any,
124
126
  }
125
127
 
@@ -137,6 +139,7 @@ describe("loadMessages", () => {
137
139
  id: "plugin.namepsace.loadMessagesFirst",
138
140
  description: { en: "My plugin description" },
139
141
  displayName: { en: "My plugin" },
142
+
140
143
  saveMessages: async () => undefined as any,
141
144
  }
142
145
 
@@ -181,7 +184,6 @@ describe("saveMessages", () => {
181
184
  id: "plugin.namepsace.saveMessages2",
182
185
  description: { en: "My plugin description" },
183
186
  displayName: { en: "My plugin" },
184
-
185
187
  saveMessages: async () => undefined as any,
186
188
  }
187
189
 
@@ -199,6 +201,7 @@ describe("saveMessages", () => {
199
201
  id: "plugin.namepsace.loadMessagesFirst",
200
202
  description: { en: "My plugin description" },
201
203
  displayName: { en: "My plugin" },
204
+
202
205
  loadMessages: async () => undefined as any,
203
206
  }
204
207
 
@@ -218,7 +221,6 @@ describe("addCustomApi", () => {
218
221
  id: "plugin.namespace.placeholder",
219
222
  description: { en: "My plugin description" },
220
223
  displayName: { en: "My plugin" },
221
-
222
224
  addCustomApi: () => ({
223
225
  "my-app": {
224
226
  messageReferenceMatcher: () => undefined as any,
@@ -253,7 +255,6 @@ describe("addCustomApi", () => {
253
255
  id: "plugin.namespace.placeholder2",
254
256
  description: { en: "My plugin description" },
255
257
  displayName: { en: "My plugin" },
256
-
257
258
  addCustomApi: () => ({
258
259
  "my-app-3": {
259
260
  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,76 @@
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 PluginSettings must contain `{languageTag}` and end with `.json`.",
13
+ }),
14
+ Type.Record(
15
+ Type.String({}),
16
+ Type.String({
17
+ pattern: "^[^*]*\\{languageTag\\}[^*]*\\.json",
18
+ description: "The PluginSettings must contain `{languageTag}` and end with `.json`.",
19
+ })
20
+ ),
21
+ ]),
22
+ variableReferencePattern: Type.Array(Type.String()),
23
+ })
24
+
25
+ const mockMessageLintRuleSchema: MessageLintRule["settingsSchema"] = Type.Object({
26
+ ignore: Type.Array(
27
+ Type.String({
28
+ pattern: '([^"]*)',
29
+ description: "All items in the array need quotaion marks at the end and beginning",
30
+ })
31
+ ),
32
+ })
33
+
34
+ test("if PluginSchema does match with the moduleSettings", async () => {
35
+ const isValid = await validatedModuleSettings({
36
+ settingsSchema: mockPluginSchema,
37
+ moduleSettings: {
38
+ pathPattern: "./examples/example01/{languageTag}.json",
39
+ variableReferencePattern: ["{", "}"],
40
+ },
41
+ })
42
+
43
+ expect(isValid).toBe("isValid")
44
+ })
45
+
46
+ test("if invalid module settings would pass", async () => {
47
+ const isValid = validatedModuleSettings({
48
+ settingsSchema: mockPluginSchema,
49
+ moduleSettings: {
50
+ pathPattern: "./examples/example01/{languageTag}.json",
51
+ },
52
+ })
53
+ expect(isValid).not.toBe("isValid")
54
+ })
55
+
56
+ test(" if MessageLintRuleSchema match with the settings", async () => {
57
+ const isValid = await validatedModuleSettings({
58
+ settingsSchema: mockMessageLintRuleSchema,
59
+ moduleSettings: {
60
+ ignore: ["example", "warning"],
61
+ },
62
+ })
63
+
64
+ expect(isValid).toBe("isValid")
65
+ })
66
+
67
+ test("if messageLintRule settings are not matching to the settingsSchema would pass ", async () => {
68
+ const isValid = validatedModuleSettings({
69
+ settingsSchema: mockMessageLintRuleSchema,
70
+ moduleSettings: {
71
+ ignore: "example",
72
+ },
73
+ })
74
+
75
+ expect(isValid).not.toBe("isValid")
76
+ })
@@ -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
+ }