@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.
- package/dist/resolve-modules/errors.d.ts +6 -0
- package/dist/resolve-modules/errors.d.ts.map +1 -1
- package/dist/resolve-modules/errors.js +8 -0
- package/dist/resolve-modules/resolveModules.d.ts.map +1 -1
- package/dist/resolve-modules/resolveModules.js +13 -2
- package/dist/resolve-modules/resolveModules.test.js +26 -1
- package/dist/resolve-modules/validateModuleSettings.test.d.ts +2 -0
- package/dist/resolve-modules/validateModuleSettings.test.d.ts.map +1 -0
- package/dist/resolve-modules/validateModuleSettings.test.js +62 -0
- package/dist/resolve-modules/validatedModuleSettings.d.ts +7 -0
- package/dist/resolve-modules/validatedModuleSettings.d.ts.map +1 -0
- package/dist/resolve-modules/validatedModuleSettings.js +11 -0
- package/package.json +1 -1
- package/src/adapter/solidAdapter.test.ts +3 -0
- package/src/lint/message/lintMessages.test.ts +2 -0
- package/src/lint/message/lintSingleMessage.test.ts +2 -0
- package/src/loadProject.test.ts +6 -0
- package/src/resolve-modules/errors.ts +12 -0
- package/src/resolve-modules/plugins/resolvePlugins.test.ts +4 -3
- package/src/resolve-modules/resolveModules.test.ts +31 -0
- package/src/resolve-modules/resolveModules.ts +24 -4
- package/src/resolve-modules/validateModuleSettings.test.ts +76 -0
- package/src/resolve-modules/validatedModuleSettings.ts +19 -0
|
@@ -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;
|
|
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
|
-
|
|
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 @@
|
|
|
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
|
@@ -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
|
|
package/src/loadProject.test.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
+
}
|