@inlang/sdk 0.12.0 → 0.14.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/adapter/solidAdapter.test.js +5 -5
- package/dist/createNodeishFsWithAbsolutePaths.d.ts +0 -1
- package/dist/createNodeishFsWithAbsolutePaths.d.ts.map +1 -1
- package/dist/createNodeishFsWithAbsolutePaths.js +5 -5
- package/dist/isAbsolutePath.d.ts +2 -0
- package/dist/isAbsolutePath.d.ts.map +1 -0
- package/dist/isAbsolutePath.js +4 -0
- package/dist/isAbsolutePath.test.d.ts +2 -0
- package/dist/isAbsolutePath.test.d.ts.map +1 -0
- package/dist/isAbsolutePath.test.js +20 -0
- package/dist/loadProject.d.ts.map +1 -1
- package/dist/loadProject.js +24 -4
- package/dist/loadProject.test.js +34 -6
- package/dist/resolve-modules/plugins/errors.d.ts +0 -5
- package/dist/resolve-modules/plugins/errors.d.ts.map +1 -1
- package/dist/resolve-modules/plugins/errors.js +0 -6
- package/dist/resolve-modules/plugins/resolvePlugins.d.ts.map +1 -1
- package/dist/resolve-modules/plugins/resolvePlugins.js +1 -12
- package/dist/resolve-modules/plugins/resolvePlugins.test.js +1 -15
- package/dist/resolve-modules/plugins/types.d.ts +2 -2
- package/dist/resolve-modules/plugins/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/adapter/solidAdapter.test.ts +7 -5
- package/src/createNodeishFsWithAbsolutePaths.ts +5 -6
- package/src/isAbsolutePath.test.ts +23 -0
- package/src/isAbsolutePath.ts +5 -0
- package/src/loadProject.test.ts +44 -6
- package/src/loadProject.ts +29 -7
- package/src/resolve-modules/plugins/errors.ts +0 -7
- package/src/resolve-modules/plugins/resolvePlugins.test.ts +0 -18
- package/src/resolve-modules/plugins/resolvePlugins.ts +0 -15
- package/src/resolve-modules/plugins/types.ts +0 -2
|
@@ -88,7 +88,7 @@ describe("config", () => {
|
|
|
88
88
|
const newConfig = { ...project.settings(), languageTags: ["en", "de"] };
|
|
89
89
|
project.setSettings(newConfig);
|
|
90
90
|
// TODO: how can we await `setConfig` correctly
|
|
91
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
91
|
+
await new Promise((resolve) => setTimeout(resolve, 510));
|
|
92
92
|
expect(counter).toBe(2); // 2 times because effect creation + set
|
|
93
93
|
expect(project.settings()).toStrictEqual(newConfig);
|
|
94
94
|
});
|
|
@@ -162,11 +162,11 @@ describe("messages", () => {
|
|
|
162
162
|
counter += 1;
|
|
163
163
|
});
|
|
164
164
|
expect(Object.values(project.query.messages.getAll()).length).toBe(2);
|
|
165
|
-
project.setSettings({ ...project.settings(), languageTags: [] });
|
|
165
|
+
project.setSettings({ ...project.settings(), languageTags: ["en"] });
|
|
166
166
|
// TODO: how can we await `setConfig` correctly
|
|
167
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
168
|
-
expect(counter).toBe(
|
|
169
|
-
expect(Object.values(project.query.messages.getAll()).length).toBe(
|
|
167
|
+
await new Promise((resolve) => setTimeout(resolve, 510));
|
|
168
|
+
expect(counter).toBe(1); // 2 times because effect creation + set
|
|
169
|
+
expect(Object.values(project.query.messages.getAll()).length).toBe(2);
|
|
170
170
|
});
|
|
171
171
|
it("should react to changes in messages", async () => {
|
|
172
172
|
const fs = createNodeishMemoryFs();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createNodeishFsWithAbsolutePaths.d.ts","sourceRoot":"","sources":["../src/createNodeishFsWithAbsolutePaths.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"createNodeishFsWithAbsolutePaths.d.ts","sourceRoot":"","sources":["../src/createNodeishFsWithAbsolutePaths.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAA;AAI7D;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,SAAU;IACtD,gBAAgB,EAAE,MAAM,CAAA;IACxB,SAAS,EAAE,uBAAuB,CAAA;CAClC,KAAG,uBAyBH,CAAA"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { normalizePath } from "@lix-js/fs";
|
|
2
|
-
|
|
2
|
+
import { isAbsolutePath } from "./isAbsolutePath.js";
|
|
3
3
|
/**
|
|
4
4
|
* Wraps the nodeish filesystem subset with a function that intercepts paths
|
|
5
5
|
* and prepends the base path.
|
|
@@ -8,16 +8,16 @@ export const isAbsolutePath = (path) => /^[/\\]/.test(path);
|
|
|
8
8
|
*/
|
|
9
9
|
export const createNodeishFsWithAbsolutePaths = (args) => {
|
|
10
10
|
if (!isAbsolutePath(args.settingsFilePath)) {
|
|
11
|
-
throw new Error(
|
|
11
|
+
throw new Error(`Expected an absolute path but received "${args.settingsFilePath}".`);
|
|
12
12
|
}
|
|
13
13
|
// get the base path of the settings file by
|
|
14
14
|
// removing the file name from the path
|
|
15
|
-
const
|
|
15
|
+
const basePath = normalizePath(args.settingsFilePath).split("/").slice(0, -1).join("/");
|
|
16
16
|
const makeAbsolute = (path) => {
|
|
17
17
|
if (isAbsolutePath(path)) {
|
|
18
|
-
return path;
|
|
18
|
+
return normalizePath(path);
|
|
19
19
|
}
|
|
20
|
-
return normalizePath(
|
|
20
|
+
return normalizePath(basePath + "/" + path);
|
|
21
21
|
};
|
|
22
22
|
return {
|
|
23
23
|
// @ts-expect-error
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isAbsolutePath.d.ts","sourceRoot":"","sources":["../src/isAbsolutePath.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,SAAU,MAAM,YAI1C,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isAbsolutePath.test.d.ts","sourceRoot":"","sources":["../src/isAbsolutePath.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { assert, describe, it } from "vitest";
|
|
2
|
+
import { isAbsolutePath } from "./isAbsolutePath.js";
|
|
3
|
+
describe("isAbsolutePath", () => {
|
|
4
|
+
it("should correctly identify Unix absolute paths", () => {
|
|
5
|
+
assert.isTrue(isAbsolutePath("/home/user/documents/file.txt"));
|
|
6
|
+
assert.isTrue(isAbsolutePath("/usr/local/bin/script.sh"));
|
|
7
|
+
assert.isFalse(isAbsolutePath("relative/path/to/file.txt"));
|
|
8
|
+
});
|
|
9
|
+
it("should correctly identify Windows absolute paths", () => {
|
|
10
|
+
assert.isTrue(isAbsolutePath("C:\\Users\\User\\Documents\\File.txt"));
|
|
11
|
+
assert.isTrue(isAbsolutePath("C:/Users/user/project/project.inlang.json"));
|
|
12
|
+
assert.isFalse(isAbsolutePath("Projects\\Project1\\source\\file.txt"));
|
|
13
|
+
});
|
|
14
|
+
it("should handle edge cases", () => {
|
|
15
|
+
assert.isFalse(isAbsolutePath("")); // Empty path should return false
|
|
16
|
+
assert.isFalse(isAbsolutePath("relative/path/../file.txt")); // Relative path with ".." should return false
|
|
17
|
+
assert.isFalse(isAbsolutePath("../relative/path/to/file.txt"));
|
|
18
|
+
assert.isFalse(isAbsolutePath("./relative/path/to/file.txt"));
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loadProject.d.ts","sourceRoot":"","sources":["../src/loadProject.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACX,aAAa,EAGb,YAAY,EACZ,MAAM,UAAU,CAAA;AACjB,OAAO,EAAE,KAAK,cAAc,EAAkB,MAAM,4BAA4B,CAAA;AAchF,OAAO,EAA4B,KAAK,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;
|
|
1
|
+
{"version":3,"file":"loadProject.d.ts","sourceRoot":"","sources":["../src/loadProject.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACX,aAAa,EAGb,YAAY,EACZ,MAAM,UAAU,CAAA;AACjB,OAAO,EAAE,KAAK,cAAc,EAAkB,MAAM,4BAA4B,CAAA;AAchF,OAAO,EAA4B,KAAK,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AASjG;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW;sBACL,MAAM;eACb,uBAAuB;;qBAElB,MAAM,SAAS,OAAO,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI;MAC5D,QAAQ,aAAa,CAuMxB,CAAA;AAuHD,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAQtE"}
|
package/dist/loadProject.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { resolveModules } from "./resolve-modules/index.js";
|
|
2
|
-
import { TypeCompiler } from "@sinclair/typebox/compiler";
|
|
2
|
+
import { TypeCompiler, ValueErrorType } from "@sinclair/typebox/compiler";
|
|
3
3
|
import { ProjectSettingsFileJSONSyntaxError, ProjectSettingsFileNotFoundError, ProjectSettingsInvalidError, PluginLoadMessagesError, PluginSaveMessagesError, LoadProjectInvalidArgument, } from "./errors.js";
|
|
4
4
|
import { createRoot, createSignal, createEffect } from "./reactivity/solid.js";
|
|
5
5
|
import { createMessagesQuery } from "./createMessagesQuery.js";
|
|
@@ -8,7 +8,9 @@ import { createMessageLintReportsQuery } from "./createMessageLintReportsQuery.j
|
|
|
8
8
|
import { ProjectSettings, Message } from "./versionedInterfaces.js";
|
|
9
9
|
import { tryCatch } from "@inlang/result";
|
|
10
10
|
import { migrateIfOutdated } from "@inlang/project-settings/migration";
|
|
11
|
-
import { createNodeishFsWithAbsolutePaths
|
|
11
|
+
import { createNodeishFsWithAbsolutePaths } from "./createNodeishFsWithAbsolutePaths.js";
|
|
12
|
+
import { normalizePath } from "@lix-js/fs";
|
|
13
|
+
import { isAbsolutePath } from "./isAbsolutePath.js";
|
|
12
14
|
const settingsCompiler = TypeCompiler.Compile(ProjectSettings);
|
|
13
15
|
/**
|
|
14
16
|
* Creates an inlang instance.
|
|
@@ -28,14 +30,18 @@ export const loadProject = async (args) => {
|
|
|
28
30
|
if (!isAbsolutePath(args.settingsFilePath)) {
|
|
29
31
|
throw new LoadProjectInvalidArgument(`Expected an absolute path but received "${args.settingsFilePath}".`, { argument: "settingsFilePath" });
|
|
30
32
|
}
|
|
33
|
+
const settingsFilePath = normalizePath(args.settingsFilePath);
|
|
31
34
|
// -- load project ------------------------------------------------------
|
|
32
35
|
return await createRoot(async () => {
|
|
33
36
|
const [initialized, markInitAsComplete, markInitAsFailed] = createAwaitable();
|
|
34
|
-
const nodeishFs = createNodeishFsWithAbsolutePaths(
|
|
37
|
+
const nodeishFs = createNodeishFsWithAbsolutePaths({
|
|
38
|
+
settingsFilePath,
|
|
39
|
+
nodeishFs: args.nodeishFs,
|
|
40
|
+
});
|
|
35
41
|
// -- settings ------------------------------------------------------------
|
|
36
42
|
const [settings, _setSettings] = createSignal();
|
|
37
43
|
createEffect(() => {
|
|
38
|
-
loadSettings({ settingsFilePath
|
|
44
|
+
loadSettings({ settingsFilePath, nodeishFs })
|
|
39
45
|
.then((settings) => {
|
|
40
46
|
setSettings(settings);
|
|
41
47
|
// rename settings to get a convenient access to the data in Posthog
|
|
@@ -196,6 +202,20 @@ const parseSettings = (settings) => {
|
|
|
196
202
|
});
|
|
197
203
|
}
|
|
198
204
|
}
|
|
205
|
+
const { sourceLanguageTag, languageTags } = settings;
|
|
206
|
+
if (!languageTags.includes(sourceLanguageTag)) {
|
|
207
|
+
throw new ProjectSettingsInvalidError({
|
|
208
|
+
errors: [
|
|
209
|
+
{
|
|
210
|
+
message: `The sourceLanguageTag "${sourceLanguageTag}" is not included in the languageTags "${languageTags.join('", "')}". Please add it to the languageTags.`,
|
|
211
|
+
type: ValueErrorType.String,
|
|
212
|
+
schema: ProjectSettings,
|
|
213
|
+
value: sourceLanguageTag,
|
|
214
|
+
path: "sourceLanguageTag",
|
|
215
|
+
},
|
|
216
|
+
],
|
|
217
|
+
});
|
|
218
|
+
}
|
|
199
219
|
return withMigration;
|
|
200
220
|
};
|
|
201
221
|
const _writeSettingsToDisk = async (args) => {
|
package/dist/loadProject.test.js
CHANGED
|
@@ -91,6 +91,18 @@ describe("initialization", () => {
|
|
|
91
91
|
expect(result.error).toBeInstanceOf(LoadProjectInvalidArgument);
|
|
92
92
|
expect(result.data).toBeUndefined();
|
|
93
93
|
});
|
|
94
|
+
it("should resolve from a windows path", async () => {
|
|
95
|
+
const fs = createNodeishMemoryFs();
|
|
96
|
+
fs.mkdir("C:\\Users\\user\\project", { recursive: true });
|
|
97
|
+
fs.writeFile("C:\\Users\\user\\project\\project.inlang.json", JSON.stringify(settings));
|
|
98
|
+
const result = await tryCatch(() => loadProject({
|
|
99
|
+
settingsFilePath: "C:\\Users\\user\\project\\project.inlang.json",
|
|
100
|
+
nodeishFs: fs,
|
|
101
|
+
_import,
|
|
102
|
+
}));
|
|
103
|
+
expect(result.error).toBeUndefined();
|
|
104
|
+
expect(result.data).toBeDefined();
|
|
105
|
+
});
|
|
94
106
|
describe("settings", () => {
|
|
95
107
|
it("should return an error if settings file is not found", async () => {
|
|
96
108
|
const fs = createNodeishMemoryFs();
|
|
@@ -236,6 +248,23 @@ describe("functionality", () => {
|
|
|
236
248
|
expect(result.data).toBeUndefined();
|
|
237
249
|
expect(result.error).toBeInstanceOf(ProjectSettingsInvalidError);
|
|
238
250
|
});
|
|
251
|
+
it("should throw an error if sourceLanguageTag is not in languageTags", async () => {
|
|
252
|
+
const fs = await createNodeishMemoryFs();
|
|
253
|
+
await fs.mkdir("/user/project", { recursive: true });
|
|
254
|
+
const settings = {
|
|
255
|
+
sourceLanguageTag: "en",
|
|
256
|
+
languageTags: ["de"],
|
|
257
|
+
modules: [],
|
|
258
|
+
};
|
|
259
|
+
await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings));
|
|
260
|
+
const project = await loadProject({
|
|
261
|
+
settingsFilePath: "/user/project/project.inlang.json",
|
|
262
|
+
nodeishFs: fs,
|
|
263
|
+
_import,
|
|
264
|
+
});
|
|
265
|
+
expect(project.errors()).toHaveLength(1);
|
|
266
|
+
expect(project.errors()[0]).toBeInstanceOf(ProjectSettingsInvalidError);
|
|
267
|
+
});
|
|
239
268
|
it("should write settings to disk", async () => {
|
|
240
269
|
const fs = await createNodeishMemoryFs();
|
|
241
270
|
await fs.mkdir("/user/project", { recursive: true });
|
|
@@ -247,7 +276,7 @@ describe("functionality", () => {
|
|
|
247
276
|
});
|
|
248
277
|
const before = await fs.readFile("/user/project/project.inlang.json", { encoding: "utf-8" });
|
|
249
278
|
expect(before).toBeDefined();
|
|
250
|
-
const result = project.setSettings({ ...settings, languageTags: [] });
|
|
279
|
+
const result = project.setSettings({ ...settings, languageTags: ["en"] });
|
|
251
280
|
expect(result.data).toBeUndefined();
|
|
252
281
|
expect(result.error).toBeUndefined();
|
|
253
282
|
// TODO: how to wait for fs.writeFile to finish?
|
|
@@ -598,15 +627,14 @@ describe("functionality", () => {
|
|
|
598
627
|
sourceLanguageTag: "en",
|
|
599
628
|
languageTags: ["en", "de"],
|
|
600
629
|
modules: ["plugin.js"],
|
|
601
|
-
"plugin.
|
|
630
|
+
"plugin.placeholder.name": {
|
|
602
631
|
pathPattern: "./resources/{languageTag}.json",
|
|
603
632
|
},
|
|
604
633
|
};
|
|
605
|
-
await fs.
|
|
606
|
-
await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings));
|
|
634
|
+
await fs.writeFile("./project.inlang.json", JSON.stringify(settings));
|
|
607
635
|
const mockSaveFn = vi.fn();
|
|
608
636
|
const _mockPlugin = {
|
|
609
|
-
id: "plugin.
|
|
637
|
+
id: "plugin.placeholder.name",
|
|
610
638
|
description: "Mock plugin description",
|
|
611
639
|
displayName: "Mock Plugin",
|
|
612
640
|
loadMessages: () => [
|
|
@@ -622,7 +650,7 @@ describe("functionality", () => {
|
|
|
622
650
|
};
|
|
623
651
|
};
|
|
624
652
|
const project = await loadProject({
|
|
625
|
-
settingsFilePath: "/
|
|
653
|
+
settingsFilePath: "/project.inlang.json",
|
|
626
654
|
nodeishFs: fs,
|
|
627
655
|
_import,
|
|
628
656
|
});
|
|
@@ -5,11 +5,6 @@ export declare class PluginHasInvalidIdError extends Error {
|
|
|
5
5
|
id: Plugin["id"];
|
|
6
6
|
});
|
|
7
7
|
}
|
|
8
|
-
export declare class PluginUsesReservedNamespaceError extends Error {
|
|
9
|
-
constructor(options: {
|
|
10
|
-
id: Plugin["id"];
|
|
11
|
-
});
|
|
12
|
-
}
|
|
13
8
|
export declare class PluginHasInvalidSchemaError extends Error {
|
|
14
9
|
constructor(options: {
|
|
15
10
|
id: Plugin["id"];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/resolve-modules/plugins/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAC5C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAE1D,qBAAa,uBAAwB,SAAQ,KAAK;gBACrC,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,CAAA;KAAE;CAMzC;AAED,qBAAa,
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/resolve-modules/plugins/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAC5C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAE1D,qBAAa,uBAAwB,SAAQ,KAAK;gBACrC,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,CAAA;KAAE;CAMzC;AAED,qBAAa,2BAA4B,SAAQ,KAAK;gBACzC,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;QAAC,MAAM,EAAE,UAAU,EAAE,CAAA;KAAE;CAQ/D;AAED,qBAAa,6CAA8C,SAAQ,KAAK;gBAC3D,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,CAAA;KAAE;CAMzC;AAED,qBAAa,6CAA8C,SAAQ,KAAK;gBAC3D,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,CAAA;KAAE;CAMzC;AAED,qBAAa,mCAAoC,SAAQ,KAAK;gBACjD,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;QAAC,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,CAAA;KAAE;CAIvE;AAED,qBAAa,0CAA2C,SAAQ,KAAK;;CAOpE"}
|
|
@@ -4,12 +4,6 @@ export class PluginHasInvalidIdError extends Error {
|
|
|
4
4
|
this.name = "PluginHasInvalidIdError";
|
|
5
5
|
}
|
|
6
6
|
}
|
|
7
|
-
export class PluginUsesReservedNamespaceError extends Error {
|
|
8
|
-
constructor(options) {
|
|
9
|
-
super(`Plugin ${options.id} uses reserved namespace 'inlang'.`);
|
|
10
|
-
this.name = "PluginUsesReservedNamespaceError";
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
7
|
export class PluginHasInvalidSchemaError extends Error {
|
|
14
8
|
constructor(options) {
|
|
15
9
|
super(`Plugin "${options.id}" has an invalid schema:\n\n${options.errors
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolvePlugins.d.ts","sourceRoot":"","sources":["../../../src/resolve-modules/plugins/resolvePlugins.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"resolvePlugins.d.ts","sourceRoot":"","sources":["../../../src/resolve-modules/plugins/resolvePlugins.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAA;AAiBxD,eAAO,MAAM,cAAc,EAAE,sBA4G5B,CAAA"}
|
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
import { Plugin } from "@inlang/plugin";
|
|
2
|
-
import { PluginReturnedInvalidCustomApiError, PluginLoadMessagesFunctionAlreadyDefinedError, PluginSaveMessagesFunctionAlreadyDefinedError, PluginsDoNotProvideLoadOrSaveMessagesError, PluginHasInvalidIdError, PluginHasInvalidSchemaError,
|
|
2
|
+
import { PluginReturnedInvalidCustomApiError, PluginLoadMessagesFunctionAlreadyDefinedError, PluginSaveMessagesFunctionAlreadyDefinedError, PluginsDoNotProvideLoadOrSaveMessagesError, PluginHasInvalidIdError, PluginHasInvalidSchemaError, } from "./errors.js";
|
|
3
3
|
import { deepmerge } from "deepmerge-ts";
|
|
4
4
|
import { TypeCompiler } from "@sinclair/typebox/compiler";
|
|
5
5
|
import { tryCatch } from "@inlang/result";
|
|
6
|
-
const whitelistedPlugins = [
|
|
7
|
-
"plugin.inlang.json",
|
|
8
|
-
"plugin.inlang.i18next",
|
|
9
|
-
"plugin.inlang.paraglideJs",
|
|
10
|
-
];
|
|
11
6
|
// @ts-ignore - type mismatch error
|
|
12
7
|
const PluginCompiler = TypeCompiler.Compile(Plugin);
|
|
13
8
|
export const resolvePlugins = async (args) => {
|
|
@@ -29,12 +24,6 @@ export const resolvePlugins = async (args) => {
|
|
|
29
24
|
if (hasInvalidId) {
|
|
30
25
|
result.errors.push(new PluginHasInvalidIdError({ id: plugin.id }));
|
|
31
26
|
}
|
|
32
|
-
// -- USES RESERVED NAMESPACE --
|
|
33
|
-
if (plugin.id.includes("inlang") && !whitelistedPlugins.includes(plugin.id)) {
|
|
34
|
-
result.errors.push(new PluginUsesReservedNamespaceError({
|
|
35
|
-
id: plugin.id,
|
|
36
|
-
}));
|
|
37
|
-
}
|
|
38
27
|
// -- USES INVALID SCHEMA --
|
|
39
28
|
if (errors.length > 0) {
|
|
40
29
|
result.errors.push(new PluginHasInvalidSchemaError({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
2
2
|
import { describe, expect, it } from "vitest";
|
|
3
3
|
import { resolvePlugins } from "./resolvePlugins.js";
|
|
4
|
-
import { PluginLoadMessagesFunctionAlreadyDefinedError, PluginSaveMessagesFunctionAlreadyDefinedError, PluginHasInvalidIdError,
|
|
4
|
+
import { PluginLoadMessagesFunctionAlreadyDefinedError, PluginSaveMessagesFunctionAlreadyDefinedError, PluginHasInvalidIdError, PluginReturnedInvalidCustomApiError, PluginHasInvalidSchemaError, PluginsDoNotProvideLoadOrSaveMessagesError, } from "./errors.js";
|
|
5
5
|
it("should return an error if a plugin uses an invalid id", async () => {
|
|
6
6
|
const mockPlugin = {
|
|
7
7
|
// @ts-expect-error - invalid id
|
|
@@ -37,20 +37,6 @@ it("should return an error if a plugin uses APIs that are not available", async
|
|
|
37
37
|
});
|
|
38
38
|
expect(resolved.errors[0]).toBeInstanceOf(PluginHasInvalidSchemaError);
|
|
39
39
|
});
|
|
40
|
-
it("should not initialize a plugin that uses the 'inlang' namespace except for inlang whitelisted plugins", async () => {
|
|
41
|
-
const mockPlugin = {
|
|
42
|
-
id: "plugin.inlang.notWhitelisted",
|
|
43
|
-
description: { en: "My plugin description" },
|
|
44
|
-
displayName: { en: "My plugin" },
|
|
45
|
-
loadMessages: () => undefined,
|
|
46
|
-
};
|
|
47
|
-
const resolved = await resolvePlugins({
|
|
48
|
-
plugins: [mockPlugin],
|
|
49
|
-
settings: {},
|
|
50
|
-
nodeishFs: {},
|
|
51
|
-
});
|
|
52
|
-
expect(resolved.errors[0]).toBeInstanceOf(PluginUsesReservedNamespaceError);
|
|
53
|
-
});
|
|
54
40
|
it("should expose the project settings including the plugin settings", async () => {
|
|
55
41
|
const settings = {
|
|
56
42
|
sourceLanguageTag: "en",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { NodeishFilesystem } from "@lix-js/fs";
|
|
2
|
-
import type { PluginReturnedInvalidCustomApiError, PluginLoadMessagesFunctionAlreadyDefinedError, PluginSaveMessagesFunctionAlreadyDefinedError, PluginHasInvalidIdError, PluginHasInvalidSchemaError,
|
|
2
|
+
import type { PluginReturnedInvalidCustomApiError, PluginLoadMessagesFunctionAlreadyDefinedError, PluginSaveMessagesFunctionAlreadyDefinedError, PluginHasInvalidIdError, PluginHasInvalidSchemaError, PluginsDoNotProvideLoadOrSaveMessagesError } from "./errors.js";
|
|
3
3
|
import type { Message } from "@inlang/message";
|
|
4
4
|
import type { CustomApiInlangIdeExtension, Plugin } from "@inlang/plugin";
|
|
5
5
|
import type { ProjectSettings } from "@inlang/project-settings";
|
|
@@ -18,7 +18,7 @@ export type ResolvePluginsFunction = (args: {
|
|
|
18
18
|
nodeishFs: NodeishFilesystemSubset;
|
|
19
19
|
}) => Promise<{
|
|
20
20
|
data: ResolvedPluginApi;
|
|
21
|
-
errors: Array<PluginReturnedInvalidCustomApiError | PluginLoadMessagesFunctionAlreadyDefinedError | PluginSaveMessagesFunctionAlreadyDefinedError | PluginHasInvalidIdError | PluginHasInvalidSchemaError |
|
|
21
|
+
errors: Array<PluginReturnedInvalidCustomApiError | PluginLoadMessagesFunctionAlreadyDefinedError | PluginSaveMessagesFunctionAlreadyDefinedError | PluginHasInvalidIdError | PluginHasInvalidSchemaError | PluginsDoNotProvideLoadOrSaveMessagesError>;
|
|
22
22
|
}>;
|
|
23
23
|
/**
|
|
24
24
|
* The API after resolving the plugins.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/resolve-modules/plugins/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AACnD,OAAO,KAAK,EACX,mCAAmC,EACnC,6CAA6C,EAC7C,6CAA6C,EAC7C,uBAAuB,EACvB,2BAA2B,EAC3B,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/resolve-modules/plugins/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AACnD,OAAO,KAAK,EACX,mCAAmC,EACnC,6CAA6C,EAC7C,6CAA6C,EAC7C,uBAAuB,EACvB,2BAA2B,EAC3B,0CAA0C,EAC1C,MAAM,aAAa,CAAA;AACpB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AAC9C,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AACzE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAE/D;;;;GAIG;AACH,MAAM,MAAM,uBAAuB,GAAG,IAAI,CACzC,iBAAiB,EACjB,UAAU,GAAG,SAAS,GAAG,OAAO,GAAG,WAAW,CAC9C,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,IAAI,EAAE;IAC3C,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;IACtB,QAAQ,EAAE,eAAe,CAAA;IACzB,SAAS,EAAE,uBAAuB,CAAA;CAClC,KAAK,OAAO,CAAC;IACb,IAAI,EAAE,iBAAiB,CAAA;IACvB,MAAM,EAAE,KAAK,CACV,mCAAmC,GACnC,6CAA6C,GAC7C,6CAA6C,GAC7C,uBAAuB,GACvB,2BAA2B,GAC3B,0CAA0C,CAC5C,CAAA;CACD,CAAC,CAAA;AAEF;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC/B,YAAY,EAAE,CAAC,IAAI,EAAE;QAAE,QAAQ,EAAE,eAAe,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,GAAG,OAAO,EAAE,CAAA;IACrF,YAAY,EAAE,CAAC,IAAI,EAAE;QAAE,QAAQ,EAAE,eAAe,CAAC;QAAC,QAAQ,EAAE,OAAO,EAAE,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAChG;;;;;;;;;;;;;;;;OAgBG;IACH,SAAS,EAAE,MAAM,CAAC,OAAO,MAAM,IAAI,MAAM,EAAE,GAAG,WAAW,MAAM,IAAI,MAAM,EAAE,EAAE,OAAO,CAAC,GAAG;QACvF,yBAAyB,CAAC,EAAE,2BAA2B,CAAA;KACvD,CAAA;CACD,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inlang/sdk",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.14.0",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"@inlang/project-settings": "*",
|
|
39
39
|
"@inlang/result": "*",
|
|
40
40
|
"@lix-js/fs": "*",
|
|
41
|
-
"@sinclair/typebox": "^0.31.
|
|
41
|
+
"@sinclair/typebox": "^0.31.17",
|
|
42
42
|
"deepmerge-ts": "^5.1.0",
|
|
43
43
|
"solid-js": "1.6.12",
|
|
44
44
|
"throttle-debounce": "5.0.0",
|
|
@@ -109,8 +109,10 @@ describe("config", () => {
|
|
|
109
109
|
const newConfig = { ...project.settings()!, languageTags: ["en", "de"] }
|
|
110
110
|
|
|
111
111
|
project.setSettings(newConfig)
|
|
112
|
+
|
|
112
113
|
// TODO: how can we await `setConfig` correctly
|
|
113
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
114
|
+
await new Promise((resolve) => setTimeout(resolve, 510))
|
|
115
|
+
|
|
114
116
|
expect(counter).toBe(2) // 2 times because effect creation + set
|
|
115
117
|
expect(project.settings()).toStrictEqual(newConfig)
|
|
116
118
|
})
|
|
@@ -202,13 +204,13 @@ describe("messages", () => {
|
|
|
202
204
|
|
|
203
205
|
expect(Object.values(project.query.messages.getAll()).length).toBe(2)
|
|
204
206
|
|
|
205
|
-
project.setSettings({ ...project.settings()!, languageTags: [] })
|
|
207
|
+
project.setSettings({ ...project.settings()!, languageTags: ["en"] })
|
|
206
208
|
|
|
207
209
|
// TODO: how can we await `setConfig` correctly
|
|
208
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
210
|
+
await new Promise((resolve) => setTimeout(resolve, 510))
|
|
209
211
|
|
|
210
|
-
expect(counter).toBe(
|
|
211
|
-
expect(Object.values(project.query.messages.getAll()).length).toBe(
|
|
212
|
+
expect(counter).toBe(1) // 2 times because effect creation + set
|
|
213
|
+
expect(Object.values(project.query.messages.getAll()).length).toBe(2)
|
|
212
214
|
})
|
|
213
215
|
|
|
214
216
|
it("should react to changes in messages", async () => {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { NodeishFilesystemSubset } from "@inlang/plugin"
|
|
2
2
|
import { normalizePath } from "@lix-js/fs"
|
|
3
|
-
|
|
4
|
-
export const isAbsolutePath = (path: string) => /^[/\\]/.test(path)
|
|
3
|
+
import { isAbsolutePath } from "./isAbsolutePath.js"
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
6
|
* Wraps the nodeish filesystem subset with a function that intercepts paths
|
|
@@ -14,19 +13,19 @@ export const createNodeishFsWithAbsolutePaths = (args: {
|
|
|
14
13
|
nodeishFs: NodeishFilesystemSubset
|
|
15
14
|
}): NodeishFilesystemSubset => {
|
|
16
15
|
if (!isAbsolutePath(args.settingsFilePath)) {
|
|
17
|
-
throw new Error(
|
|
16
|
+
throw new Error(`Expected an absolute path but received "${args.settingsFilePath}".`)
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
// get the base path of the settings file by
|
|
21
20
|
// removing the file name from the path
|
|
22
|
-
const
|
|
21
|
+
const basePath = normalizePath(args.settingsFilePath).split("/").slice(0, -1).join("/")
|
|
23
22
|
|
|
24
23
|
const makeAbsolute = (path: string) => {
|
|
25
24
|
if (isAbsolutePath(path)) {
|
|
26
|
-
return path
|
|
25
|
+
return normalizePath(path)
|
|
27
26
|
}
|
|
28
27
|
|
|
29
|
-
return normalizePath(
|
|
28
|
+
return normalizePath(basePath + "/" + path)
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
return {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { assert, describe, it } from "vitest"
|
|
2
|
+
import { isAbsolutePath } from "./isAbsolutePath.js"
|
|
3
|
+
|
|
4
|
+
describe("isAbsolutePath", () => {
|
|
5
|
+
it("should correctly identify Unix absolute paths", () => {
|
|
6
|
+
assert.isTrue(isAbsolutePath("/home/user/documents/file.txt"))
|
|
7
|
+
assert.isTrue(isAbsolutePath("/usr/local/bin/script.sh"))
|
|
8
|
+
assert.isFalse(isAbsolutePath("relative/path/to/file.txt"))
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
it("should correctly identify Windows absolute paths", () => {
|
|
12
|
+
assert.isTrue(isAbsolutePath("C:\\Users\\User\\Documents\\File.txt"))
|
|
13
|
+
assert.isTrue(isAbsolutePath("C:/Users/user/project/project.inlang.json"))
|
|
14
|
+
assert.isFalse(isAbsolutePath("Projects\\Project1\\source\\file.txt"))
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it("should handle edge cases", () => {
|
|
18
|
+
assert.isFalse(isAbsolutePath("")) // Empty path should return false
|
|
19
|
+
assert.isFalse(isAbsolutePath("relative/path/../file.txt")) // Relative path with ".." should return false
|
|
20
|
+
assert.isFalse(isAbsolutePath("../relative/path/to/file.txt"))
|
|
21
|
+
assert.isFalse(isAbsolutePath("./relative/path/to/file.txt"))
|
|
22
|
+
})
|
|
23
|
+
})
|
package/src/loadProject.test.ts
CHANGED
|
@@ -113,6 +113,23 @@ describe("initialization", () => {
|
|
|
113
113
|
expect(result.data).toBeUndefined()
|
|
114
114
|
})
|
|
115
115
|
|
|
116
|
+
it("should resolve from a windows path", async () => {
|
|
117
|
+
const fs = createNodeishMemoryFs()
|
|
118
|
+
fs.mkdir("C:\\Users\\user\\project", { recursive: true })
|
|
119
|
+
fs.writeFile("C:\\Users\\user\\project\\project.inlang.json", JSON.stringify(settings))
|
|
120
|
+
|
|
121
|
+
const result = await tryCatch(() =>
|
|
122
|
+
loadProject({
|
|
123
|
+
settingsFilePath: "C:\\Users\\user\\project\\project.inlang.json",
|
|
124
|
+
nodeishFs: fs,
|
|
125
|
+
_import,
|
|
126
|
+
})
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
expect(result.error).toBeUndefined()
|
|
130
|
+
expect(result.data).toBeDefined()
|
|
131
|
+
})
|
|
132
|
+
|
|
116
133
|
describe("settings", () => {
|
|
117
134
|
it("should return an error if settings file is not found", async () => {
|
|
118
135
|
const fs = createNodeishMemoryFs()
|
|
@@ -289,6 +306,28 @@ describe("functionality", () => {
|
|
|
289
306
|
expect(result.error).toBeInstanceOf(ProjectSettingsInvalidError)
|
|
290
307
|
})
|
|
291
308
|
|
|
309
|
+
it("should throw an error if sourceLanguageTag is not in languageTags", async () => {
|
|
310
|
+
const fs = await createNodeishMemoryFs()
|
|
311
|
+
await fs.mkdir("/user/project", { recursive: true })
|
|
312
|
+
|
|
313
|
+
const settings: ProjectSettings = {
|
|
314
|
+
sourceLanguageTag: "en",
|
|
315
|
+
languageTags: ["de"],
|
|
316
|
+
modules: [],
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings))
|
|
320
|
+
|
|
321
|
+
const project = await loadProject({
|
|
322
|
+
settingsFilePath: "/user/project/project.inlang.json",
|
|
323
|
+
nodeishFs: fs,
|
|
324
|
+
_import,
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
expect(project.errors()).toHaveLength(1)
|
|
328
|
+
expect(project.errors()![0]).toBeInstanceOf(ProjectSettingsInvalidError)
|
|
329
|
+
})
|
|
330
|
+
|
|
292
331
|
it("should write settings to disk", async () => {
|
|
293
332
|
const fs = await createNodeishMemoryFs()
|
|
294
333
|
await fs.mkdir("/user/project", { recursive: true })
|
|
@@ -302,7 +341,7 @@ describe("functionality", () => {
|
|
|
302
341
|
const before = await fs.readFile("/user/project/project.inlang.json", { encoding: "utf-8" })
|
|
303
342
|
expect(before).toBeDefined()
|
|
304
343
|
|
|
305
|
-
const result = project.setSettings({ ...settings, languageTags: [] })
|
|
344
|
+
const result = project.setSettings({ ...settings, languageTags: ["en"] })
|
|
306
345
|
expect(result.data).toBeUndefined()
|
|
307
346
|
expect(result.error).toBeUndefined()
|
|
308
347
|
|
|
@@ -701,18 +740,17 @@ describe("functionality", () => {
|
|
|
701
740
|
sourceLanguageTag: "en",
|
|
702
741
|
languageTags: ["en", "de"],
|
|
703
742
|
modules: ["plugin.js"],
|
|
704
|
-
"plugin.
|
|
743
|
+
"plugin.placeholder.name": {
|
|
705
744
|
pathPattern: "./resources/{languageTag}.json",
|
|
706
745
|
},
|
|
707
746
|
}
|
|
708
747
|
|
|
709
|
-
await fs.
|
|
710
|
-
await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings))
|
|
748
|
+
await fs.writeFile("./project.inlang.json", JSON.stringify(settings))
|
|
711
749
|
|
|
712
750
|
const mockSaveFn = vi.fn()
|
|
713
751
|
|
|
714
752
|
const _mockPlugin: Plugin = {
|
|
715
|
-
id: "plugin.
|
|
753
|
+
id: "plugin.placeholder.name",
|
|
716
754
|
description: "Mock plugin description",
|
|
717
755
|
displayName: "Mock Plugin",
|
|
718
756
|
loadMessages: () => [
|
|
@@ -730,7 +768,7 @@ describe("functionality", () => {
|
|
|
730
768
|
}
|
|
731
769
|
|
|
732
770
|
const project = await loadProject({
|
|
733
|
-
settingsFilePath: "/
|
|
771
|
+
settingsFilePath: "/project.inlang.json",
|
|
734
772
|
nodeishFs: fs,
|
|
735
773
|
_import,
|
|
736
774
|
})
|
package/src/loadProject.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type {
|
|
|
6
6
|
Subscribable,
|
|
7
7
|
} from "./api.js"
|
|
8
8
|
import { type ImportFunction, resolveModules } from "./resolve-modules/index.js"
|
|
9
|
-
import { TypeCompiler } from "@sinclair/typebox/compiler"
|
|
9
|
+
import { TypeCompiler, ValueErrorType } from "@sinclair/typebox/compiler"
|
|
10
10
|
import {
|
|
11
11
|
ProjectSettingsFileJSONSyntaxError,
|
|
12
12
|
ProjectSettingsFileNotFoundError,
|
|
@@ -22,10 +22,9 @@ import { createMessageLintReportsQuery } from "./createMessageLintReportsQuery.j
|
|
|
22
22
|
import { ProjectSettings, Message, type NodeishFilesystemSubset } from "./versionedInterfaces.js"
|
|
23
23
|
import { tryCatch, type Result } from "@inlang/result"
|
|
24
24
|
import { migrateIfOutdated } from "@inlang/project-settings/migration"
|
|
25
|
-
import {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
} from "./createNodeishFsWithAbsolutePaths.js"
|
|
25
|
+
import { createNodeishFsWithAbsolutePaths } from "./createNodeishFsWithAbsolutePaths.js"
|
|
26
|
+
import { normalizePath } from "@lix-js/fs"
|
|
27
|
+
import { isAbsolutePath } from "./isAbsolutePath.js"
|
|
29
28
|
|
|
30
29
|
const settingsCompiler = TypeCompiler.Compile(ProjectSettings)
|
|
31
30
|
|
|
@@ -56,16 +55,21 @@ export const loadProject = async (args: {
|
|
|
56
55
|
)
|
|
57
56
|
}
|
|
58
57
|
|
|
58
|
+
const settingsFilePath = normalizePath(args.settingsFilePath)
|
|
59
|
+
|
|
59
60
|
// -- load project ------------------------------------------------------
|
|
60
61
|
return await createRoot(async () => {
|
|
61
62
|
const [initialized, markInitAsComplete, markInitAsFailed] = createAwaitable()
|
|
62
|
-
const nodeishFs = createNodeishFsWithAbsolutePaths(
|
|
63
|
+
const nodeishFs = createNodeishFsWithAbsolutePaths({
|
|
64
|
+
settingsFilePath,
|
|
65
|
+
nodeishFs: args.nodeishFs,
|
|
66
|
+
})
|
|
63
67
|
|
|
64
68
|
// -- settings ------------------------------------------------------------
|
|
65
69
|
|
|
66
70
|
const [settings, _setSettings] = createSignal<ProjectSettings>()
|
|
67
71
|
createEffect(() => {
|
|
68
|
-
loadSettings({ settingsFilePath
|
|
72
|
+
loadSettings({ settingsFilePath, nodeishFs })
|
|
69
73
|
.then((settings) => {
|
|
70
74
|
setSettings(settings)
|
|
71
75
|
// rename settings to get a convenient access to the data in Posthog
|
|
@@ -278,6 +282,24 @@ const parseSettings = (settings: unknown) => {
|
|
|
278
282
|
})
|
|
279
283
|
}
|
|
280
284
|
}
|
|
285
|
+
|
|
286
|
+
const { sourceLanguageTag, languageTags } = settings as ProjectSettings
|
|
287
|
+
if (!languageTags.includes(sourceLanguageTag)) {
|
|
288
|
+
throw new ProjectSettingsInvalidError({
|
|
289
|
+
errors: [
|
|
290
|
+
{
|
|
291
|
+
message: `The sourceLanguageTag "${sourceLanguageTag}" is not included in the languageTags "${languageTags.join(
|
|
292
|
+
'", "'
|
|
293
|
+
)}". Please add it to the languageTags.`,
|
|
294
|
+
type: ValueErrorType.String,
|
|
295
|
+
schema: ProjectSettings,
|
|
296
|
+
value: sourceLanguageTag,
|
|
297
|
+
path: "sourceLanguageTag",
|
|
298
|
+
},
|
|
299
|
+
],
|
|
300
|
+
})
|
|
301
|
+
}
|
|
302
|
+
|
|
281
303
|
return withMigration
|
|
282
304
|
}
|
|
283
305
|
|
|
@@ -10,13 +10,6 @@ export class PluginHasInvalidIdError extends Error {
|
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
export class PluginUsesReservedNamespaceError extends Error {
|
|
14
|
-
constructor(options: { id: Plugin["id"] }) {
|
|
15
|
-
super(`Plugin ${options.id} uses reserved namespace 'inlang'.`)
|
|
16
|
-
this.name = "PluginUsesReservedNamespaceError"
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
13
|
export class PluginHasInvalidSchemaError extends Error {
|
|
21
14
|
constructor(options: { id: Plugin["id"]; errors: ValueError[] }) {
|
|
22
15
|
super(
|
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
PluginLoadMessagesFunctionAlreadyDefinedError,
|
|
6
6
|
PluginSaveMessagesFunctionAlreadyDefinedError,
|
|
7
7
|
PluginHasInvalidIdError,
|
|
8
|
-
PluginUsesReservedNamespaceError,
|
|
9
8
|
PluginReturnedInvalidCustomApiError,
|
|
10
9
|
PluginHasInvalidSchemaError,
|
|
11
10
|
PluginsDoNotProvideLoadOrSaveMessagesError,
|
|
@@ -54,23 +53,6 @@ it("should return an error if a plugin uses APIs that are not available", async
|
|
|
54
53
|
expect(resolved.errors[0]).toBeInstanceOf(PluginHasInvalidSchemaError)
|
|
55
54
|
})
|
|
56
55
|
|
|
57
|
-
it("should not initialize a plugin that uses the 'inlang' namespace except for inlang whitelisted plugins", async () => {
|
|
58
|
-
const mockPlugin: Plugin = {
|
|
59
|
-
id: "plugin.inlang.notWhitelisted",
|
|
60
|
-
description: { en: "My plugin description" },
|
|
61
|
-
displayName: { en: "My plugin" },
|
|
62
|
-
loadMessages: () => undefined as any,
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const resolved = await resolvePlugins({
|
|
66
|
-
plugins: [mockPlugin],
|
|
67
|
-
settings: {} as any,
|
|
68
|
-
nodeishFs: {} as any,
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
expect(resolved.errors[0]).toBeInstanceOf(PluginUsesReservedNamespaceError)
|
|
72
|
-
})
|
|
73
|
-
|
|
74
56
|
it("should expose the project settings including the plugin settings", async () => {
|
|
75
57
|
const settings: ProjectSettings = {
|
|
76
58
|
sourceLanguageTag: "en",
|
|
@@ -8,17 +8,11 @@ import {
|
|
|
8
8
|
PluginsDoNotProvideLoadOrSaveMessagesError,
|
|
9
9
|
PluginHasInvalidIdError,
|
|
10
10
|
PluginHasInvalidSchemaError,
|
|
11
|
-
PluginUsesReservedNamespaceError,
|
|
12
11
|
} from "./errors.js"
|
|
13
12
|
import { deepmerge } from "deepmerge-ts"
|
|
14
13
|
import { TypeCompiler } from "@sinclair/typebox/compiler"
|
|
15
14
|
import { tryCatch } from "@inlang/result"
|
|
16
15
|
|
|
17
|
-
const whitelistedPlugins = [
|
|
18
|
-
"plugin.inlang.json",
|
|
19
|
-
"plugin.inlang.i18next",
|
|
20
|
-
"plugin.inlang.paraglideJs",
|
|
21
|
-
]
|
|
22
16
|
// @ts-ignore - type mismatch error
|
|
23
17
|
const PluginCompiler = TypeCompiler.Compile(Plugin)
|
|
24
18
|
|
|
@@ -45,15 +39,6 @@ export const resolvePlugins: ResolvePluginsFunction = async (args) => {
|
|
|
45
39
|
result.errors.push(new PluginHasInvalidIdError({ id: plugin.id }))
|
|
46
40
|
}
|
|
47
41
|
|
|
48
|
-
// -- USES RESERVED NAMESPACE --
|
|
49
|
-
if (plugin.id.includes("inlang") && !whitelistedPlugins.includes(plugin.id)) {
|
|
50
|
-
result.errors.push(
|
|
51
|
-
new PluginUsesReservedNamespaceError({
|
|
52
|
-
id: plugin.id,
|
|
53
|
-
})
|
|
54
|
-
)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
42
|
// -- USES INVALID SCHEMA --
|
|
58
43
|
if (errors.length > 0) {
|
|
59
44
|
result.errors.push(
|
|
@@ -5,7 +5,6 @@ import type {
|
|
|
5
5
|
PluginSaveMessagesFunctionAlreadyDefinedError,
|
|
6
6
|
PluginHasInvalidIdError,
|
|
7
7
|
PluginHasInvalidSchemaError,
|
|
8
|
-
PluginUsesReservedNamespaceError,
|
|
9
8
|
PluginsDoNotProvideLoadOrSaveMessagesError,
|
|
10
9
|
} from "./errors.js"
|
|
11
10
|
import type { Message } from "@inlang/message"
|
|
@@ -37,7 +36,6 @@ export type ResolvePluginsFunction = (args: {
|
|
|
37
36
|
| PluginSaveMessagesFunctionAlreadyDefinedError
|
|
38
37
|
| PluginHasInvalidIdError
|
|
39
38
|
| PluginHasInvalidSchemaError
|
|
40
|
-
| PluginUsesReservedNamespaceError
|
|
41
39
|
| PluginsDoNotProvideLoadOrSaveMessagesError
|
|
42
40
|
>
|
|
43
41
|
}>
|