@inlang/sdk 0.11.0 → 0.13.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 +13 -0
- package/dist/createNodeishFsWithAbsolutePaths.d.ts.map +1 -0
- package/dist/createNodeishFsWithAbsolutePaths.js +29 -0
- package/dist/createNodeishFsWithAbsolutePaths.test.d.ts.map +1 -0
- package/dist/createNodeishFsWithAbsolutePaths.test.js +37 -0
- package/dist/errors.d.ts +5 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +6 -0
- package/dist/loadProject.d.ts.map +1 -1
- package/dist/loadProject.js +29 -6
- package/dist/loadProject.test.js +34 -7
- 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.test.ts +47 -0
- package/src/createNodeishFsWithAbsolutePaths.ts +40 -0
- package/src/errors.ts +7 -0
- package/src/loadProject.test.ts +43 -6
- package/src/loadProject.ts +41 -9
- 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
- package/dist/resolve-modules/createNodeishFsWithAbsolutePaths.d.ts +0 -6
- package/dist/resolve-modules/createNodeishFsWithAbsolutePaths.d.ts.map +0 -1
- package/dist/resolve-modules/createNodeishFsWithAbsolutePaths.js +0 -20
- package/dist/resolve-modules/createNodeishFsWithAbsolutePaths.test.d.ts.map +0 -1
- package/dist/resolve-modules/createNodeishFsWithAbsolutePaths.test.js +0 -85
- package/src/resolve-modules/createNodeishFsWithAbsolutePaths.test.ts +0 -102
- package/src/resolve-modules/createNodeishFsWithAbsolutePaths.ts +0 -30
- /package/dist/{resolve-modules/createNodeishFsWithAbsolutePaths.test.d.ts → createNodeishFsWithAbsolutePaths.test.d.ts} +0 -0
|
@@ -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();
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { NodeishFilesystemSubset } from "@inlang/plugin";
|
|
2
|
+
export declare const isAbsolutePath: (path: string) => boolean;
|
|
3
|
+
/**
|
|
4
|
+
* Wraps the nodeish filesystem subset with a function that intercepts paths
|
|
5
|
+
* and prepends the base path.
|
|
6
|
+
*
|
|
7
|
+
* The paths are resolved from the `settingsFilePath` argument.
|
|
8
|
+
*/
|
|
9
|
+
export declare const createNodeishFsWithAbsolutePaths: (args: {
|
|
10
|
+
settingsFilePath: string;
|
|
11
|
+
nodeishFs: NodeishFilesystemSubset;
|
|
12
|
+
}) => NodeishFilesystemSubset;
|
|
13
|
+
//# sourceMappingURL=createNodeishFsWithAbsolutePaths.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createNodeishFsWithAbsolutePaths.d.ts","sourceRoot":"","sources":["../src/createNodeishFsWithAbsolutePaths.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAA;AAG7D,eAAO,MAAM,cAAc,SAAU,MAAM,YAAwB,CAAA;AAEnE;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,SAAU;IACtD,gBAAgB,EAAE,MAAM,CAAA;IACxB,SAAS,EAAE,uBAAuB,CAAA;CAClC,KAAG,uBAyBH,CAAA"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { normalizePath } from "@lix-js/fs";
|
|
2
|
+
export const isAbsolutePath = (path) => /^[/\\]/.test(path);
|
|
3
|
+
/**
|
|
4
|
+
* Wraps the nodeish filesystem subset with a function that intercepts paths
|
|
5
|
+
* and prepends the base path.
|
|
6
|
+
*
|
|
7
|
+
* The paths are resolved from the `settingsFilePath` argument.
|
|
8
|
+
*/
|
|
9
|
+
export const createNodeishFsWithAbsolutePaths = (args) => {
|
|
10
|
+
if (!isAbsolutePath(args.settingsFilePath)) {
|
|
11
|
+
throw new Error("The argument `settingsFilePath` must be an absolute path.");
|
|
12
|
+
}
|
|
13
|
+
// get the base path of the settings file by
|
|
14
|
+
// removing the file name from the path
|
|
15
|
+
const basePath = args.settingsFilePath.split("/").slice(0, -1).join("/");
|
|
16
|
+
const makeAbsolute = (path) => {
|
|
17
|
+
if (isAbsolutePath(path)) {
|
|
18
|
+
return normalizePath(path);
|
|
19
|
+
}
|
|
20
|
+
return normalizePath(basePath + "/" + path);
|
|
21
|
+
};
|
|
22
|
+
return {
|
|
23
|
+
// @ts-expect-error
|
|
24
|
+
readFile: (path, options) => args.nodeishFs.readFile(makeAbsolute(path), options),
|
|
25
|
+
readdir: (path) => args.nodeishFs.readdir(makeAbsolute(path)),
|
|
26
|
+
mkdir: (path) => args.nodeishFs.mkdir(makeAbsolute(path)),
|
|
27
|
+
writeFile: (path, data) => args.nodeishFs.writeFile(makeAbsolute(path), data),
|
|
28
|
+
};
|
|
29
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createNodeishFsWithAbsolutePaths.test.d.ts","sourceRoot":"","sources":["../src/createNodeishFsWithAbsolutePaths.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { it, expect, vi } from "vitest";
|
|
2
|
+
import { createNodeishFsWithAbsolutePaths } from "./createNodeishFsWithAbsolutePaths.js";
|
|
3
|
+
it("throws an error if settingsFilePath is not an absolute path", () => {
|
|
4
|
+
const relativePath = "relative/path";
|
|
5
|
+
expect(() => createNodeishFsWithAbsolutePaths({ settingsFilePath: relativePath, nodeishFs: {} })).toThrow();
|
|
6
|
+
});
|
|
7
|
+
it("intercepts paths correctly for readFile", async () => {
|
|
8
|
+
const settingsFilePath = `/Users/samuel/Documents/paraglide/example/project.inlang.json`;
|
|
9
|
+
const filePaths = [
|
|
10
|
+
["file.txt", `/Users/samuel/Documents/paraglide/example/file.txt`],
|
|
11
|
+
["./file.txt", `/Users/samuel/Documents/paraglide/example/file.txt`],
|
|
12
|
+
["./folder/file.txt", `/Users/samuel/Documents/paraglide/example/folder/file.txt`],
|
|
13
|
+
["../file.txt", `/Users/samuel/Documents/paraglide/file.txt`],
|
|
14
|
+
["../folder/file.txt", `/Users/samuel/Documents/paraglide/folder/file.txt`],
|
|
15
|
+
["../../file.txt", `/Users/samuel/Documents/file.txt`],
|
|
16
|
+
["../../../file.txt", `/Users/samuel/file.txt`],
|
|
17
|
+
];
|
|
18
|
+
const mockNodeishFs = {
|
|
19
|
+
readFile: vi.fn(),
|
|
20
|
+
readdir: vi.fn(),
|
|
21
|
+
mkdir: vi.fn(),
|
|
22
|
+
writeFile: vi.fn(),
|
|
23
|
+
};
|
|
24
|
+
const interceptedFs = createNodeishFsWithAbsolutePaths({
|
|
25
|
+
settingsFilePath,
|
|
26
|
+
nodeishFs: mockNodeishFs,
|
|
27
|
+
});
|
|
28
|
+
for (const [path, expectedPath] of filePaths) {
|
|
29
|
+
for (const fn of Object.keys(mockNodeishFs)) {
|
|
30
|
+
// @ts-expect-error
|
|
31
|
+
await interceptedFs[fn](path);
|
|
32
|
+
// @ts-expect-error
|
|
33
|
+
// expect the first argument to be the expectedPath
|
|
34
|
+
expect(mockNodeishFs[fn].mock.lastCall[0]).toBe(expectedPath);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
});
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import type { ValueError } from "@sinclair/typebox/errors";
|
|
2
|
+
export declare class LoadProjectInvalidArgument extends Error {
|
|
3
|
+
constructor(message: string, options: {
|
|
4
|
+
argument: string;
|
|
5
|
+
});
|
|
6
|
+
}
|
|
2
7
|
export declare class ProjectSettingsInvalidError extends Error {
|
|
3
8
|
constructor(options: {
|
|
4
9
|
errors: ValueError[];
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAE1D,qBAAa,2BAA4B,SAAQ,KAAK;gBACzC,OAAO,EAAE;QAAE,MAAM,EAAE,UAAU,EAAE,CAAA;KAAE;CAQ7C;AAED,qBAAa,kCAAmC,SAAQ,KAAK;gBAChD,OAAO,EAAE;QAAE,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE;CAOnE;AAED,qBAAa,gCAAiC,SAAQ,KAAK;gBAC9C,OAAO,EAAE;QAAE,KAAK,CAAC,EAAE,YAAY,CAAC,OAAO,CAAC,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE;CAIpE;AAED,qBAAa,uBAAwB,SAAQ,KAAK;gBACrC,OAAO,EAAE;QAAE,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,CAAA;KAAE;CAIrD;AAED,qBAAa,uBAAwB,SAAQ,KAAK;gBACrC,OAAO,EAAE;QAAE,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,CAAA;KAAE;CAIrD"}
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAE1D,qBAAa,0BAA2B,SAAQ,KAAK;gBACxC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE;CAI1D;AAED,qBAAa,2BAA4B,SAAQ,KAAK;gBACzC,OAAO,EAAE;QAAE,MAAM,EAAE,UAAU,EAAE,CAAA;KAAE;CAQ7C;AAED,qBAAa,kCAAmC,SAAQ,KAAK;gBAChD,OAAO,EAAE;QAAE,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE;CAOnE;AAED,qBAAa,gCAAiC,SAAQ,KAAK;gBAC9C,OAAO,EAAE;QAAE,KAAK,CAAC,EAAE,YAAY,CAAC,OAAO,CAAC,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE;CAIpE;AAED,qBAAa,uBAAwB,SAAQ,KAAK;gBACrC,OAAO,EAAE;QAAE,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,CAAA;KAAE;CAIrD;AAED,qBAAa,uBAAwB,SAAQ,KAAK;gBACrC,OAAO,EAAE;QAAE,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,CAAA;KAAE;CAIrD"}
|
package/dist/errors.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
export class LoadProjectInvalidArgument extends Error {
|
|
2
|
+
constructor(message, options) {
|
|
3
|
+
super(`The argument "${options.argument}" of loadProject() is invalid: ${message}`);
|
|
4
|
+
this.name = "LoadProjectInvalidArgument";
|
|
5
|
+
}
|
|
6
|
+
}
|
|
1
7
|
export class ProjectSettingsInvalidError extends Error {
|
|
2
8
|
constructor(options) {
|
|
3
9
|
super(`The project settings are invalid:\n\n${options.errors
|
|
@@ -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;
|
|
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;AAWjG;;;;;;;;;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,6 +1,6 @@
|
|
|
1
1
|
import { resolveModules } from "./resolve-modules/index.js";
|
|
2
|
-
import { TypeCompiler } from "@sinclair/typebox/compiler";
|
|
3
|
-
import { ProjectSettingsFileJSONSyntaxError, ProjectSettingsFileNotFoundError, ProjectSettingsInvalidError, PluginLoadMessagesError, PluginSaveMessagesError, } from "./errors.js";
|
|
2
|
+
import { TypeCompiler, ValueErrorType } from "@sinclair/typebox/compiler";
|
|
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";
|
|
6
6
|
import { debounce } from "throttle-debounce";
|
|
@@ -8,7 +8,8 @@ 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 } from "./
|
|
11
|
+
import { createNodeishFsWithAbsolutePaths, isAbsolutePath, } from "./createNodeishFsWithAbsolutePaths.js";
|
|
12
|
+
import { normalizePath } from "@lix-js/fs";
|
|
12
13
|
const settingsCompiler = TypeCompiler.Compile(ProjectSettings);
|
|
13
14
|
/**
|
|
14
15
|
* Creates an inlang instance.
|
|
@@ -21,17 +22,25 @@ const settingsCompiler = TypeCompiler.Compile(ProjectSettings);
|
|
|
21
22
|
*
|
|
22
23
|
*/
|
|
23
24
|
export const loadProject = async (args) => {
|
|
25
|
+
// -- validation --------------------------------------------------------
|
|
26
|
+
//! the only place where throwing is acceptable because the project
|
|
27
|
+
//! won't even be loaded. do not throw anywhere else. otherwise, apps
|
|
28
|
+
//! can't handle errors gracefully.
|
|
29
|
+
if (!isAbsolutePath(args.settingsFilePath)) {
|
|
30
|
+
throw new LoadProjectInvalidArgument(`Expected an absolute path but received "${args.settingsFilePath}".`, { argument: "settingsFilePath" });
|
|
31
|
+
}
|
|
32
|
+
const settingsFilePath = normalizePath(args.settingsFilePath);
|
|
33
|
+
// -- load project ------------------------------------------------------
|
|
24
34
|
return await createRoot(async () => {
|
|
25
35
|
const [initialized, markInitAsComplete, markInitAsFailed] = createAwaitable();
|
|
26
|
-
// -- absolute path env -----------------------------------------------
|
|
27
36
|
const nodeishFs = createNodeishFsWithAbsolutePaths({
|
|
37
|
+
settingsFilePath,
|
|
28
38
|
nodeishFs: args.nodeishFs,
|
|
29
|
-
basePath: args.settingsFilePath.replace(/(.*?)[^/]*\..*$/, "$1"), // get directory of settings file
|
|
30
39
|
});
|
|
31
40
|
// -- settings ------------------------------------------------------------
|
|
32
41
|
const [settings, _setSettings] = createSignal();
|
|
33
42
|
createEffect(() => {
|
|
34
|
-
loadSettings({ settingsFilePath
|
|
43
|
+
loadSettings({ settingsFilePath, nodeishFs })
|
|
35
44
|
.then((settings) => {
|
|
36
45
|
setSettings(settings);
|
|
37
46
|
// rename settings to get a convenient access to the data in Posthog
|
|
@@ -192,6 +201,20 @@ const parseSettings = (settings) => {
|
|
|
192
201
|
});
|
|
193
202
|
}
|
|
194
203
|
}
|
|
204
|
+
const { sourceLanguageTag, languageTags } = settings;
|
|
205
|
+
if (!languageTags.includes(sourceLanguageTag)) {
|
|
206
|
+
throw new ProjectSettingsInvalidError({
|
|
207
|
+
errors: [
|
|
208
|
+
{
|
|
209
|
+
message: `The sourceLanguageTag "${sourceLanguageTag}" is not included in the languageTags "${languageTags.join('", "')}". Please add it to the languageTags.`,
|
|
210
|
+
type: ValueErrorType.String,
|
|
211
|
+
schema: ProjectSettings,
|
|
212
|
+
value: sourceLanguageTag,
|
|
213
|
+
path: "sourceLanguageTag",
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
});
|
|
217
|
+
}
|
|
195
218
|
return withMigration;
|
|
196
219
|
};
|
|
197
220
|
const _writeSettingsToDisk = async (args) => {
|
package/dist/loadProject.test.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
2
2
|
import { describe, it, expect, vi } from "vitest";
|
|
3
3
|
import { loadProject } from "./loadProject.js";
|
|
4
|
-
import { ProjectSettingsFileJSONSyntaxError, ProjectSettingsFileNotFoundError, ProjectSettingsInvalidError, } from "./errors.js";
|
|
4
|
+
import { LoadProjectInvalidArgument, ProjectSettingsFileJSONSyntaxError, ProjectSettingsFileNotFoundError, ProjectSettingsInvalidError, } from "./errors.js";
|
|
5
5
|
import { createNodeishMemoryFs } from "@lix-js/fs";
|
|
6
6
|
import { createMessage } from "./test-utilities/createMessage.js";
|
|
7
|
+
import { tryCatch } from "@inlang/result";
|
|
7
8
|
// ------------------------------------------------------------------------------------------------
|
|
8
9
|
const getValue = (subscribable) => {
|
|
9
10
|
let value;
|
|
@@ -80,6 +81,16 @@ const _import = async (name) => ({
|
|
|
80
81
|
});
|
|
81
82
|
// ------------------------------------------------------------------------------------------------
|
|
82
83
|
describe("initialization", () => {
|
|
84
|
+
it("should throw if settingsFilePath is not an absolute path", async () => {
|
|
85
|
+
const fs = createNodeishMemoryFs();
|
|
86
|
+
const result = await tryCatch(() => loadProject({
|
|
87
|
+
settingsFilePath: "relative/path",
|
|
88
|
+
nodeishFs: fs,
|
|
89
|
+
_import,
|
|
90
|
+
}));
|
|
91
|
+
expect(result.error).toBeInstanceOf(LoadProjectInvalidArgument);
|
|
92
|
+
expect(result.data).toBeUndefined();
|
|
93
|
+
});
|
|
83
94
|
describe("settings", () => {
|
|
84
95
|
it("should return an error if settings file is not found", async () => {
|
|
85
96
|
const fs = createNodeishMemoryFs();
|
|
@@ -225,6 +236,23 @@ describe("functionality", () => {
|
|
|
225
236
|
expect(result.data).toBeUndefined();
|
|
226
237
|
expect(result.error).toBeInstanceOf(ProjectSettingsInvalidError);
|
|
227
238
|
});
|
|
239
|
+
it("should throw an error if sourceLanguageTag is not in languageTags", async () => {
|
|
240
|
+
const fs = await createNodeishMemoryFs();
|
|
241
|
+
await fs.mkdir("/user/project", { recursive: true });
|
|
242
|
+
const settings = {
|
|
243
|
+
sourceLanguageTag: "en",
|
|
244
|
+
languageTags: ["de"],
|
|
245
|
+
modules: [],
|
|
246
|
+
};
|
|
247
|
+
await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings));
|
|
248
|
+
const project = await loadProject({
|
|
249
|
+
settingsFilePath: "/user/project/project.inlang.json",
|
|
250
|
+
nodeishFs: fs,
|
|
251
|
+
_import,
|
|
252
|
+
});
|
|
253
|
+
expect(project.errors()).toHaveLength(1);
|
|
254
|
+
expect(project.errors()[0]).toBeInstanceOf(ProjectSettingsInvalidError);
|
|
255
|
+
});
|
|
228
256
|
it("should write settings to disk", async () => {
|
|
229
257
|
const fs = await createNodeishMemoryFs();
|
|
230
258
|
await fs.mkdir("/user/project", { recursive: true });
|
|
@@ -236,7 +264,7 @@ describe("functionality", () => {
|
|
|
236
264
|
});
|
|
237
265
|
const before = await fs.readFile("/user/project/project.inlang.json", { encoding: "utf-8" });
|
|
238
266
|
expect(before).toBeDefined();
|
|
239
|
-
const result = project.setSettings({ ...settings, languageTags: [] });
|
|
267
|
+
const result = project.setSettings({ ...settings, languageTags: ["en"] });
|
|
240
268
|
expect(result.data).toBeUndefined();
|
|
241
269
|
expect(result.error).toBeUndefined();
|
|
242
270
|
// TODO: how to wait for fs.writeFile to finish?
|
|
@@ -587,15 +615,14 @@ describe("functionality", () => {
|
|
|
587
615
|
sourceLanguageTag: "en",
|
|
588
616
|
languageTags: ["en", "de"],
|
|
589
617
|
modules: ["plugin.js"],
|
|
590
|
-
"plugin.
|
|
618
|
+
"plugin.placeholder.name": {
|
|
591
619
|
pathPattern: "./resources/{languageTag}.json",
|
|
592
620
|
},
|
|
593
621
|
};
|
|
594
|
-
await fs.
|
|
595
|
-
await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings));
|
|
622
|
+
await fs.writeFile("./project.inlang.json", JSON.stringify(settings));
|
|
596
623
|
const mockSaveFn = vi.fn();
|
|
597
624
|
const _mockPlugin = {
|
|
598
|
-
id: "plugin.
|
|
625
|
+
id: "plugin.placeholder.name",
|
|
599
626
|
description: "Mock plugin description",
|
|
600
627
|
displayName: "Mock Plugin",
|
|
601
628
|
loadMessages: () => [
|
|
@@ -611,7 +638,7 @@ describe("functionality", () => {
|
|
|
611
638
|
};
|
|
612
639
|
};
|
|
613
640
|
const project = await loadProject({
|
|
614
|
-
settingsFilePath: "/
|
|
641
|
+
settingsFilePath: "/project.inlang.json",
|
|
615
642
|
nodeishFs: fs,
|
|
616
643
|
_import,
|
|
617
644
|
});
|
|
@@ -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.13.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 () => {
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { it, expect, vi } from "vitest"
|
|
2
|
+
import { createNodeishFsWithAbsolutePaths } from "./createNodeishFsWithAbsolutePaths.js"
|
|
3
|
+
import type { NodeishFilesystemSubset } from "./versionedInterfaces.js"
|
|
4
|
+
|
|
5
|
+
it("throws an error if settingsFilePath is not an absolute path", () => {
|
|
6
|
+
const relativePath = "relative/path"
|
|
7
|
+
|
|
8
|
+
expect(() =>
|
|
9
|
+
createNodeishFsWithAbsolutePaths({ settingsFilePath: relativePath, nodeishFs: {} as any })
|
|
10
|
+
).toThrow()
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it("intercepts paths correctly for readFile", async () => {
|
|
14
|
+
const settingsFilePath = `/Users/samuel/Documents/paraglide/example/project.inlang.json`
|
|
15
|
+
|
|
16
|
+
const filePaths = [
|
|
17
|
+
["file.txt", `/Users/samuel/Documents/paraglide/example/file.txt`],
|
|
18
|
+
["./file.txt", `/Users/samuel/Documents/paraglide/example/file.txt`],
|
|
19
|
+
["./folder/file.txt", `/Users/samuel/Documents/paraglide/example/folder/file.txt`],
|
|
20
|
+
["../file.txt", `/Users/samuel/Documents/paraglide/file.txt`],
|
|
21
|
+
["../folder/file.txt", `/Users/samuel/Documents/paraglide/folder/file.txt`],
|
|
22
|
+
["../../file.txt", `/Users/samuel/Documents/file.txt`],
|
|
23
|
+
["../../../file.txt", `/Users/samuel/file.txt`],
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
const mockNodeishFs = {
|
|
27
|
+
readFile: vi.fn(),
|
|
28
|
+
readdir: vi.fn(),
|
|
29
|
+
mkdir: vi.fn(),
|
|
30
|
+
writeFile: vi.fn(),
|
|
31
|
+
} satisfies Record<keyof NodeishFilesystemSubset, any>
|
|
32
|
+
|
|
33
|
+
const interceptedFs = createNodeishFsWithAbsolutePaths({
|
|
34
|
+
settingsFilePath,
|
|
35
|
+
nodeishFs: mockNodeishFs,
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
for (const [path, expectedPath] of filePaths) {
|
|
39
|
+
for (const fn of Object.keys(mockNodeishFs)) {
|
|
40
|
+
// @ts-expect-error
|
|
41
|
+
await interceptedFs[fn](path)
|
|
42
|
+
// @ts-expect-error
|
|
43
|
+
// expect the first argument to be the expectedPath
|
|
44
|
+
expect(mockNodeishFs[fn].mock.lastCall[0]).toBe(expectedPath)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
})
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { NodeishFilesystemSubset } from "@inlang/plugin"
|
|
2
|
+
import { normalizePath } from "@lix-js/fs"
|
|
3
|
+
|
|
4
|
+
export const isAbsolutePath = (path: string) => /^[/\\]/.test(path)
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Wraps the nodeish filesystem subset with a function that intercepts paths
|
|
8
|
+
* and prepends the base path.
|
|
9
|
+
*
|
|
10
|
+
* The paths are resolved from the `settingsFilePath` argument.
|
|
11
|
+
*/
|
|
12
|
+
export const createNodeishFsWithAbsolutePaths = (args: {
|
|
13
|
+
settingsFilePath: string
|
|
14
|
+
nodeishFs: NodeishFilesystemSubset
|
|
15
|
+
}): NodeishFilesystemSubset => {
|
|
16
|
+
if (!isAbsolutePath(args.settingsFilePath)) {
|
|
17
|
+
throw new Error("The argument `settingsFilePath` must be an absolute path.")
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// get the base path of the settings file by
|
|
21
|
+
// removing the file name from the path
|
|
22
|
+
const basePath = args.settingsFilePath.split("/").slice(0, -1).join("/")
|
|
23
|
+
|
|
24
|
+
const makeAbsolute = (path: string) => {
|
|
25
|
+
if (isAbsolutePath(path)) {
|
|
26
|
+
return normalizePath(path)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return normalizePath(basePath + "/" + path)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
// @ts-expect-error
|
|
34
|
+
readFile: (path: string, options: { encoding: "utf-8" | "binary" }) =>
|
|
35
|
+
args.nodeishFs.readFile(makeAbsolute(path), options),
|
|
36
|
+
readdir: (path: string) => args.nodeishFs.readdir(makeAbsolute(path)),
|
|
37
|
+
mkdir: (path: string) => args.nodeishFs.mkdir(makeAbsolute(path)),
|
|
38
|
+
writeFile: (path: string, data: string) => args.nodeishFs.writeFile(makeAbsolute(path), data),
|
|
39
|
+
}
|
|
40
|
+
}
|
package/src/errors.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import type { ValueError } from "@sinclair/typebox/errors"
|
|
2
2
|
|
|
3
|
+
export class LoadProjectInvalidArgument extends Error {
|
|
4
|
+
constructor(message: string, options: { argument: string }) {
|
|
5
|
+
super(`The argument "${options.argument}" of loadProject() is invalid: ${message}`)
|
|
6
|
+
this.name = "LoadProjectInvalidArgument"
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
3
10
|
export class ProjectSettingsInvalidError extends Error {
|
|
4
11
|
constructor(options: { errors: ValueError[] }) {
|
|
5
12
|
super(
|
package/src/loadProject.test.ts
CHANGED
|
@@ -5,12 +5,14 @@ import type { ProjectSettings, Plugin, MessageLintRule, Message } from "./versio
|
|
|
5
5
|
import type { ImportFunction } from "./resolve-modules/index.js"
|
|
6
6
|
import type { InlangModule } from "@inlang/module"
|
|
7
7
|
import {
|
|
8
|
+
LoadProjectInvalidArgument,
|
|
8
9
|
ProjectSettingsFileJSONSyntaxError,
|
|
9
10
|
ProjectSettingsFileNotFoundError,
|
|
10
11
|
ProjectSettingsInvalidError,
|
|
11
12
|
} from "./errors.js"
|
|
12
13
|
import { createNodeishMemoryFs } from "@lix-js/fs"
|
|
13
14
|
import { createMessage } from "./test-utilities/createMessage.js"
|
|
15
|
+
import { tryCatch } from "@inlang/result"
|
|
14
16
|
|
|
15
17
|
// ------------------------------------------------------------------------------------------------
|
|
16
18
|
|
|
@@ -97,6 +99,20 @@ const _import: ImportFunction = async (name) =>
|
|
|
97
99
|
// ------------------------------------------------------------------------------------------------
|
|
98
100
|
|
|
99
101
|
describe("initialization", () => {
|
|
102
|
+
it("should throw if settingsFilePath is not an absolute path", async () => {
|
|
103
|
+
const fs = createNodeishMemoryFs()
|
|
104
|
+
|
|
105
|
+
const result = await tryCatch(() =>
|
|
106
|
+
loadProject({
|
|
107
|
+
settingsFilePath: "relative/path",
|
|
108
|
+
nodeishFs: fs,
|
|
109
|
+
_import,
|
|
110
|
+
})
|
|
111
|
+
)
|
|
112
|
+
expect(result.error).toBeInstanceOf(LoadProjectInvalidArgument)
|
|
113
|
+
expect(result.data).toBeUndefined()
|
|
114
|
+
})
|
|
115
|
+
|
|
100
116
|
describe("settings", () => {
|
|
101
117
|
it("should return an error if settings file is not found", async () => {
|
|
102
118
|
const fs = createNodeishMemoryFs()
|
|
@@ -273,6 +289,28 @@ describe("functionality", () => {
|
|
|
273
289
|
expect(result.error).toBeInstanceOf(ProjectSettingsInvalidError)
|
|
274
290
|
})
|
|
275
291
|
|
|
292
|
+
it("should throw an error if sourceLanguageTag is not in languageTags", async () => {
|
|
293
|
+
const fs = await createNodeishMemoryFs()
|
|
294
|
+
await fs.mkdir("/user/project", { recursive: true })
|
|
295
|
+
|
|
296
|
+
const settings: ProjectSettings = {
|
|
297
|
+
sourceLanguageTag: "en",
|
|
298
|
+
languageTags: ["de"],
|
|
299
|
+
modules: [],
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings))
|
|
303
|
+
|
|
304
|
+
const project = await loadProject({
|
|
305
|
+
settingsFilePath: "/user/project/project.inlang.json",
|
|
306
|
+
nodeishFs: fs,
|
|
307
|
+
_import,
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
expect(project.errors()).toHaveLength(1)
|
|
311
|
+
expect(project.errors()![0]).toBeInstanceOf(ProjectSettingsInvalidError)
|
|
312
|
+
})
|
|
313
|
+
|
|
276
314
|
it("should write settings to disk", async () => {
|
|
277
315
|
const fs = await createNodeishMemoryFs()
|
|
278
316
|
await fs.mkdir("/user/project", { recursive: true })
|
|
@@ -286,7 +324,7 @@ describe("functionality", () => {
|
|
|
286
324
|
const before = await fs.readFile("/user/project/project.inlang.json", { encoding: "utf-8" })
|
|
287
325
|
expect(before).toBeDefined()
|
|
288
326
|
|
|
289
|
-
const result = project.setSettings({ ...settings, languageTags: [] })
|
|
327
|
+
const result = project.setSettings({ ...settings, languageTags: ["en"] })
|
|
290
328
|
expect(result.data).toBeUndefined()
|
|
291
329
|
expect(result.error).toBeUndefined()
|
|
292
330
|
|
|
@@ -685,18 +723,17 @@ describe("functionality", () => {
|
|
|
685
723
|
sourceLanguageTag: "en",
|
|
686
724
|
languageTags: ["en", "de"],
|
|
687
725
|
modules: ["plugin.js"],
|
|
688
|
-
"plugin.
|
|
726
|
+
"plugin.placeholder.name": {
|
|
689
727
|
pathPattern: "./resources/{languageTag}.json",
|
|
690
728
|
},
|
|
691
729
|
}
|
|
692
730
|
|
|
693
|
-
await fs.
|
|
694
|
-
await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings))
|
|
731
|
+
await fs.writeFile("./project.inlang.json", JSON.stringify(settings))
|
|
695
732
|
|
|
696
733
|
const mockSaveFn = vi.fn()
|
|
697
734
|
|
|
698
735
|
const _mockPlugin: Plugin = {
|
|
699
|
-
id: "plugin.
|
|
736
|
+
id: "plugin.placeholder.name",
|
|
700
737
|
description: "Mock plugin description",
|
|
701
738
|
displayName: "Mock Plugin",
|
|
702
739
|
loadMessages: () => [
|
|
@@ -714,7 +751,7 @@ describe("functionality", () => {
|
|
|
714
751
|
}
|
|
715
752
|
|
|
716
753
|
const project = await loadProject({
|
|
717
|
-
settingsFilePath: "/
|
|
754
|
+
settingsFilePath: "/project.inlang.json",
|
|
718
755
|
nodeishFs: fs,
|
|
719
756
|
_import,
|
|
720
757
|
})
|
package/src/loadProject.ts
CHANGED
|
@@ -6,13 +6,14 @@ 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,
|
|
13
13
|
ProjectSettingsInvalidError,
|
|
14
14
|
PluginLoadMessagesError,
|
|
15
15
|
PluginSaveMessagesError,
|
|
16
|
+
LoadProjectInvalidArgument,
|
|
16
17
|
} from "./errors.js"
|
|
17
18
|
import { createRoot, createSignal, createEffect } from "./reactivity/solid.js"
|
|
18
19
|
import { createMessagesQuery } from "./createMessagesQuery.js"
|
|
@@ -21,7 +22,11 @@ import { createMessageLintReportsQuery } from "./createMessageLintReportsQuery.j
|
|
|
21
22
|
import { ProjectSettings, Message, type NodeishFilesystemSubset } from "./versionedInterfaces.js"
|
|
22
23
|
import { tryCatch, type Result } from "@inlang/result"
|
|
23
24
|
import { migrateIfOutdated } from "@inlang/project-settings/migration"
|
|
24
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
createNodeishFsWithAbsolutePaths,
|
|
27
|
+
isAbsolutePath,
|
|
28
|
+
} from "./createNodeishFsWithAbsolutePaths.js"
|
|
29
|
+
import { normalizePath } from "@lix-js/fs"
|
|
25
30
|
|
|
26
31
|
const settingsCompiler = TypeCompiler.Compile(ProjectSettings)
|
|
27
32
|
|
|
@@ -41,21 +46,32 @@ export const loadProject = async (args: {
|
|
|
41
46
|
_import?: ImportFunction
|
|
42
47
|
_capture?: (id: string, props: Record<string, unknown>) => void
|
|
43
48
|
}): Promise<InlangProject> => {
|
|
44
|
-
|
|
45
|
-
|
|
49
|
+
// -- validation --------------------------------------------------------
|
|
50
|
+
//! the only place where throwing is acceptable because the project
|
|
51
|
+
//! won't even be loaded. do not throw anywhere else. otherwise, apps
|
|
52
|
+
//! can't handle errors gracefully.
|
|
53
|
+
if (!isAbsolutePath(args.settingsFilePath)) {
|
|
54
|
+
throw new LoadProjectInvalidArgument(
|
|
55
|
+
`Expected an absolute path but received "${args.settingsFilePath}".`,
|
|
56
|
+
{ argument: "settingsFilePath" }
|
|
57
|
+
)
|
|
58
|
+
}
|
|
46
59
|
|
|
47
|
-
|
|
60
|
+
const settingsFilePath = normalizePath(args.settingsFilePath)
|
|
48
61
|
|
|
62
|
+
// -- load project ------------------------------------------------------
|
|
63
|
+
return await createRoot(async () => {
|
|
64
|
+
const [initialized, markInitAsComplete, markInitAsFailed] = createAwaitable()
|
|
49
65
|
const nodeishFs = createNodeishFsWithAbsolutePaths({
|
|
66
|
+
settingsFilePath,
|
|
50
67
|
nodeishFs: args.nodeishFs,
|
|
51
|
-
basePath: args.settingsFilePath.replace(/(.*?)[^/]*\..*$/, "$1"), // get directory of settings file
|
|
52
68
|
})
|
|
53
69
|
|
|
54
70
|
// -- settings ------------------------------------------------------------
|
|
55
71
|
|
|
56
72
|
const [settings, _setSettings] = createSignal<ProjectSettings>()
|
|
57
73
|
createEffect(() => {
|
|
58
|
-
loadSettings({ settingsFilePath
|
|
74
|
+
loadSettings({ settingsFilePath, nodeishFs })
|
|
59
75
|
.then((settings) => {
|
|
60
76
|
setSettings(settings)
|
|
61
77
|
// rename settings to get a convenient access to the data in Posthog
|
|
@@ -268,6 +284,24 @@ const parseSettings = (settings: unknown) => {
|
|
|
268
284
|
})
|
|
269
285
|
}
|
|
270
286
|
}
|
|
287
|
+
|
|
288
|
+
const { sourceLanguageTag, languageTags } = settings as ProjectSettings
|
|
289
|
+
if (!languageTags.includes(sourceLanguageTag)) {
|
|
290
|
+
throw new ProjectSettingsInvalidError({
|
|
291
|
+
errors: [
|
|
292
|
+
{
|
|
293
|
+
message: `The sourceLanguageTag "${sourceLanguageTag}" is not included in the languageTags "${languageTags.join(
|
|
294
|
+
'", "'
|
|
295
|
+
)}". Please add it to the languageTags.`,
|
|
296
|
+
type: ValueErrorType.String,
|
|
297
|
+
schema: ProjectSettings,
|
|
298
|
+
value: sourceLanguageTag,
|
|
299
|
+
path: "sourceLanguageTag",
|
|
300
|
+
},
|
|
301
|
+
],
|
|
302
|
+
})
|
|
303
|
+
}
|
|
304
|
+
|
|
271
305
|
return withMigration
|
|
272
306
|
}
|
|
273
307
|
|
|
@@ -338,5 +372,3 @@ export function createSubscribable<T>(signal: () => T): Subscribable<T> {
|
|
|
338
372
|
},
|
|
339
373
|
})
|
|
340
374
|
}
|
|
341
|
-
|
|
342
|
-
|
|
@@ -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
|
}>
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { NodeishFilesystemSubset } from "@inlang/plugin";
|
|
2
|
-
export declare const createNodeishFsWithAbsolutePaths: (args: {
|
|
3
|
-
basePath: string;
|
|
4
|
-
nodeishFs: NodeishFilesystemSubset;
|
|
5
|
-
}) => NodeishFilesystemSubset;
|
|
6
|
-
//# sourceMappingURL=createNodeishFsWithAbsolutePaths.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"createNodeishFsWithAbsolutePaths.d.ts","sourceRoot":"","sources":["../../src/resolve-modules/createNodeishFsWithAbsolutePaths.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAA;AAG7D,eAAO,MAAM,gCAAgC,SAAU;IACtD,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,uBAAuB,CAAA;CAClC,KAAG,uBAuBH,CAAA"}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { normalizePath } from "@lix-js/fs";
|
|
2
|
-
export const createNodeishFsWithAbsolutePaths = (args) => {
|
|
3
|
-
const isAbsolutePath = (path) => /^[/\\]/.test(path);
|
|
4
|
-
if (!isAbsolutePath(args.basePath)) {
|
|
5
|
-
throw new Error("The argument `settingsFilePath` of `loadProject()` must be an absolute path.");
|
|
6
|
-
}
|
|
7
|
-
const intercept = (path) => {
|
|
8
|
-
if (isAbsolutePath(path)) {
|
|
9
|
-
return path;
|
|
10
|
-
}
|
|
11
|
-
return normalizePath(args.basePath + "/" + path);
|
|
12
|
-
};
|
|
13
|
-
return {
|
|
14
|
-
// @ts-expect-error
|
|
15
|
-
readFile: (path, options) => args.nodeishFs.readFile(intercept(path), options),
|
|
16
|
-
readdir: (path) => args.nodeishFs.readdir(intercept(path)),
|
|
17
|
-
mkdir: (path) => args.nodeishFs.mkdir(intercept(path)),
|
|
18
|
-
writeFile: (path, data) => args.nodeishFs.writeFile(intercept(path), data),
|
|
19
|
-
};
|
|
20
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"createNodeishFsWithAbsolutePaths.test.d.ts","sourceRoot":"","sources":["../../src/resolve-modules/createNodeishFsWithAbsolutePaths.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { createNodeishFsWithAbsolutePaths } from "./createNodeishFsWithAbsolutePaths.js";
|
|
3
|
-
describe("createNodeishFsWithAbsolutePaths", () => {
|
|
4
|
-
it("throws an error if basePath is not an absolute path", () => {
|
|
5
|
-
const invalidBasePath = "relative/path";
|
|
6
|
-
const nodeishFs = {
|
|
7
|
-
// @ts-expect-error
|
|
8
|
-
readFile: async () => Promise.resolve(new Uint8Array(0)),
|
|
9
|
-
readdir: async () => Promise.resolve([]),
|
|
10
|
-
mkdir: async () => Promise.resolve(""),
|
|
11
|
-
writeFile: async () => Promise.resolve(),
|
|
12
|
-
};
|
|
13
|
-
expect(() => createNodeishFsWithAbsolutePaths({ basePath: invalidBasePath, nodeishFs })).toThrowError("The argument `settingsFilePath` of `loadProject()` must be an absolute path.");
|
|
14
|
-
});
|
|
15
|
-
it("intercepts paths correctly for readFile", async () => {
|
|
16
|
-
const basePath = "/absolute/path";
|
|
17
|
-
const filePath = "file.txt";
|
|
18
|
-
const expectedPath = "/absolute/path/file.txt";
|
|
19
|
-
const nodeishFs = {
|
|
20
|
-
// @ts-expect-error
|
|
21
|
-
readFile: async (path) => {
|
|
22
|
-
expect(path).toBe(expectedPath);
|
|
23
|
-
return Promise.resolve(new Uint8Array(0));
|
|
24
|
-
},
|
|
25
|
-
readdir: async () => Promise.resolve([]),
|
|
26
|
-
mkdir: async () => Promise.resolve(""),
|
|
27
|
-
writeFile: async () => Promise.resolve(),
|
|
28
|
-
};
|
|
29
|
-
const interceptedFs = createNodeishFsWithAbsolutePaths({ basePath, nodeishFs });
|
|
30
|
-
await interceptedFs.readFile(filePath, { encoding: "utf-8" });
|
|
31
|
-
});
|
|
32
|
-
it("intercepts paths correctly for readdir", async () => {
|
|
33
|
-
const basePath = "/absolute/path";
|
|
34
|
-
const dirPath = "dir";
|
|
35
|
-
const expectedPath = "/absolute/path/dir";
|
|
36
|
-
const nodeishFs = {
|
|
37
|
-
// @ts-expect-error
|
|
38
|
-
readFile: async () => Promise.resolve(new Uint8Array(0)),
|
|
39
|
-
readdir: async (path) => {
|
|
40
|
-
expect(path).toBe(expectedPath);
|
|
41
|
-
return Promise.resolve([]);
|
|
42
|
-
},
|
|
43
|
-
mkdir: async () => Promise.resolve(""),
|
|
44
|
-
writeFile: async () => Promise.resolve(),
|
|
45
|
-
};
|
|
46
|
-
const interceptedFs = createNodeishFsWithAbsolutePaths({ basePath, nodeishFs });
|
|
47
|
-
await interceptedFs.readdir(dirPath);
|
|
48
|
-
});
|
|
49
|
-
it("intercepts paths correctly for mkdir", async () => {
|
|
50
|
-
const basePath = "/absolute/path";
|
|
51
|
-
const dirPath = "newDir";
|
|
52
|
-
const expectedPath = "/absolute/path/newDir";
|
|
53
|
-
const nodeishFs = {
|
|
54
|
-
// @ts-expect-error
|
|
55
|
-
readFile: async () => Promise.resolve(new Uint8Array(0)),
|
|
56
|
-
readdir: async () => Promise.resolve([]),
|
|
57
|
-
mkdir: async (path) => {
|
|
58
|
-
expect(path).toBe(expectedPath);
|
|
59
|
-
return Promise.resolve("");
|
|
60
|
-
},
|
|
61
|
-
writeFile: async () => Promise.resolve(),
|
|
62
|
-
};
|
|
63
|
-
const interceptedFs = createNodeishFsWithAbsolutePaths({ basePath, nodeishFs });
|
|
64
|
-
await interceptedFs.mkdir(dirPath);
|
|
65
|
-
});
|
|
66
|
-
it("intercepts paths correctly for writeFile", async () => {
|
|
67
|
-
const basePath = "/absolute/path";
|
|
68
|
-
const filePath = "file.txt";
|
|
69
|
-
const expectedPath = "/absolute/path/file.txt";
|
|
70
|
-
const data = "Hello, World!";
|
|
71
|
-
const nodeishFs = {
|
|
72
|
-
// @ts-expect-error
|
|
73
|
-
readFile: async () => Promise.resolve(new Uint8Array(0)),
|
|
74
|
-
readdir: async () => Promise.resolve([]),
|
|
75
|
-
mkdir: async () => Promise.resolve(""),
|
|
76
|
-
writeFile: async (path, content) => {
|
|
77
|
-
expect(path).toBe(expectedPath);
|
|
78
|
-
expect(content).toBe(data);
|
|
79
|
-
return Promise.resolve();
|
|
80
|
-
},
|
|
81
|
-
};
|
|
82
|
-
const interceptedFs = createNodeishFsWithAbsolutePaths({ basePath, nodeishFs });
|
|
83
|
-
await interceptedFs.writeFile(filePath, data);
|
|
84
|
-
});
|
|
85
|
-
});
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import type { NodeishFilesystemSubset } from "@inlang/plugin"
|
|
2
|
-
import { describe, it, expect } from "vitest"
|
|
3
|
-
import { createNodeishFsWithAbsolutePaths } from "./createNodeishFsWithAbsolutePaths.js"
|
|
4
|
-
|
|
5
|
-
describe("createNodeishFsWithAbsolutePaths", () => {
|
|
6
|
-
it("throws an error if basePath is not an absolute path", () => {
|
|
7
|
-
const invalidBasePath = "relative/path"
|
|
8
|
-
const nodeishFs: NodeishFilesystemSubset = {
|
|
9
|
-
// @ts-expect-error
|
|
10
|
-
readFile: async () => Promise.resolve(new Uint8Array(0)),
|
|
11
|
-
readdir: async () => Promise.resolve([]),
|
|
12
|
-
mkdir: async () => Promise.resolve(""),
|
|
13
|
-
writeFile: async () => Promise.resolve(),
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
expect(() =>
|
|
17
|
-
createNodeishFsWithAbsolutePaths({ basePath: invalidBasePath, nodeishFs })
|
|
18
|
-
).toThrowError("The argument `settingsFilePath` of `loadProject()` must be an absolute path.")
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
it("intercepts paths correctly for readFile", async () => {
|
|
22
|
-
const basePath = "/absolute/path"
|
|
23
|
-
const filePath = "file.txt"
|
|
24
|
-
const expectedPath = "/absolute/path/file.txt"
|
|
25
|
-
|
|
26
|
-
const nodeishFs: NodeishFilesystemSubset = {
|
|
27
|
-
// @ts-expect-error
|
|
28
|
-
readFile: async (path) => {
|
|
29
|
-
expect(path).toBe(expectedPath)
|
|
30
|
-
return Promise.resolve(new Uint8Array(0))
|
|
31
|
-
},
|
|
32
|
-
readdir: async () => Promise.resolve([]),
|
|
33
|
-
mkdir: async () => Promise.resolve(""),
|
|
34
|
-
writeFile: async () => Promise.resolve(),
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const interceptedFs = createNodeishFsWithAbsolutePaths({ basePath, nodeishFs })
|
|
38
|
-
await interceptedFs.readFile(filePath, { encoding: "utf-8" })
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
it("intercepts paths correctly for readdir", async () => {
|
|
42
|
-
const basePath = "/absolute/path"
|
|
43
|
-
const dirPath = "dir"
|
|
44
|
-
const expectedPath = "/absolute/path/dir"
|
|
45
|
-
|
|
46
|
-
const nodeishFs: NodeishFilesystemSubset = {
|
|
47
|
-
// @ts-expect-error
|
|
48
|
-
readFile: async () => Promise.resolve(new Uint8Array(0)),
|
|
49
|
-
readdir: async (path) => {
|
|
50
|
-
expect(path).toBe(expectedPath)
|
|
51
|
-
return Promise.resolve([])
|
|
52
|
-
},
|
|
53
|
-
mkdir: async () => Promise.resolve(""),
|
|
54
|
-
writeFile: async () => Promise.resolve(),
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const interceptedFs = createNodeishFsWithAbsolutePaths({ basePath, nodeishFs })
|
|
58
|
-
await interceptedFs.readdir(dirPath)
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
it("intercepts paths correctly for mkdir", async () => {
|
|
62
|
-
const basePath = "/absolute/path"
|
|
63
|
-
const dirPath = "newDir"
|
|
64
|
-
const expectedPath = "/absolute/path/newDir"
|
|
65
|
-
|
|
66
|
-
const nodeishFs: NodeishFilesystemSubset = {
|
|
67
|
-
// @ts-expect-error
|
|
68
|
-
readFile: async () => Promise.resolve(new Uint8Array(0)),
|
|
69
|
-
readdir: async () => Promise.resolve([]),
|
|
70
|
-
mkdir: async (path) => {
|
|
71
|
-
expect(path).toBe(expectedPath)
|
|
72
|
-
return Promise.resolve("")
|
|
73
|
-
},
|
|
74
|
-
writeFile: async () => Promise.resolve(),
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const interceptedFs = createNodeishFsWithAbsolutePaths({ basePath, nodeishFs })
|
|
78
|
-
await interceptedFs.mkdir(dirPath)
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
it("intercepts paths correctly for writeFile", async () => {
|
|
82
|
-
const basePath = "/absolute/path"
|
|
83
|
-
const filePath = "file.txt"
|
|
84
|
-
const expectedPath = "/absolute/path/file.txt"
|
|
85
|
-
const data = "Hello, World!"
|
|
86
|
-
|
|
87
|
-
const nodeishFs: NodeishFilesystemSubset = {
|
|
88
|
-
// @ts-expect-error
|
|
89
|
-
readFile: async () => Promise.resolve(new Uint8Array(0)),
|
|
90
|
-
readdir: async () => Promise.resolve([]),
|
|
91
|
-
mkdir: async () => Promise.resolve(""),
|
|
92
|
-
writeFile: async (path, content) => {
|
|
93
|
-
expect(path).toBe(expectedPath)
|
|
94
|
-
expect(content).toBe(data)
|
|
95
|
-
return Promise.resolve()
|
|
96
|
-
},
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const interceptedFs = createNodeishFsWithAbsolutePaths({ basePath, nodeishFs })
|
|
100
|
-
await interceptedFs.writeFile(filePath, data)
|
|
101
|
-
})
|
|
102
|
-
})
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import type { NodeishFilesystemSubset } from "@inlang/plugin"
|
|
2
|
-
import { normalizePath } from "@lix-js/fs"
|
|
3
|
-
|
|
4
|
-
export const createNodeishFsWithAbsolutePaths = (args: {
|
|
5
|
-
basePath: string
|
|
6
|
-
nodeishFs: NodeishFilesystemSubset
|
|
7
|
-
}): NodeishFilesystemSubset => {
|
|
8
|
-
const isAbsolutePath = (path: string) => /^[/\\]/.test(path)
|
|
9
|
-
|
|
10
|
-
if (!isAbsolutePath(args.basePath)) {
|
|
11
|
-
throw new Error("The argument `settingsFilePath` of `loadProject()` must be an absolute path.")
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const intercept = (path: string) => {
|
|
15
|
-
if (isAbsolutePath(path)) {
|
|
16
|
-
return path
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return normalizePath(args.basePath + "/" + path)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return {
|
|
23
|
-
// @ts-expect-error
|
|
24
|
-
readFile: (path: string, options: { encoding: "utf-8" | "binary" }) =>
|
|
25
|
-
args.nodeishFs.readFile(intercept(path), options),
|
|
26
|
-
readdir: (path: string) => args.nodeishFs.readdir(intercept(path)),
|
|
27
|
-
mkdir: (path: string) => args.nodeishFs.mkdir(intercept(path)),
|
|
28
|
-
writeFile: (path: string, data: string) => args.nodeishFs.writeFile(intercept(path), data),
|
|
29
|
-
}
|
|
30
|
-
}
|