@inlang/sdk 0.11.0 → 0.12.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.
@@ -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 bathPath = args.settingsFilePath.split("/").slice(0, -1).join("/");
16
+ const makeAbsolute = (path) => {
17
+ if (isAbsolutePath(path)) {
18
+ return path;
19
+ }
20
+ return normalizePath(bathPath + "/" + 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[];
@@ -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;AAahF,OAAO,EAA4B,KAAK,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAOjG;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW;sBACL,MAAM;eACb,uBAAuB;;qBAElB,MAAM,SAAS,OAAO,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI;MAC5D,QAAQ,aAAa,CA4LxB,CAAA;AAqGD,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAQtE"}
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;AAUjG;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW;sBACL,MAAM;eACb,uBAAuB;;qBAElB,MAAM,SAAS,OAAO,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI;MAC5D,QAAQ,aAAa,CAkMxB,CAAA;AAqGD,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAQtE"}
@@ -1,6 +1,6 @@
1
1
  import { resolveModules } from "./resolve-modules/index.js";
2
2
  import { TypeCompiler } from "@sinclair/typebox/compiler";
3
- import { ProjectSettingsFileJSONSyntaxError, ProjectSettingsFileNotFoundError, ProjectSettingsInvalidError, PluginLoadMessagesError, PluginSaveMessagesError, } from "./errors.js";
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,7 @@ 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 "./resolve-modules/createNodeishFsWithAbsolutePaths.js";
11
+ import { createNodeishFsWithAbsolutePaths, isAbsolutePath, } from "./createNodeishFsWithAbsolutePaths.js";
12
12
  const settingsCompiler = TypeCompiler.Compile(ProjectSettings);
13
13
  /**
14
14
  * Creates an inlang instance.
@@ -21,13 +21,17 @@ const settingsCompiler = TypeCompiler.Compile(ProjectSettings);
21
21
  *
22
22
  */
23
23
  export const loadProject = async (args) => {
24
+ // -- validation --------------------------------------------------------
25
+ //! the only place where throwing is acceptable because the project
26
+ //! won't even be loaded. do not throw anywhere else. otherwise, apps
27
+ //! can't handle errors gracefully.
28
+ if (!isAbsolutePath(args.settingsFilePath)) {
29
+ throw new LoadProjectInvalidArgument(`Expected an absolute path but received "${args.settingsFilePath}".`, { argument: "settingsFilePath" });
30
+ }
31
+ // -- load project ------------------------------------------------------
24
32
  return await createRoot(async () => {
25
33
  const [initialized, markInitAsComplete, markInitAsFailed] = createAwaitable();
26
- // -- absolute path env -----------------------------------------------
27
- const nodeishFs = createNodeishFsWithAbsolutePaths({
28
- nodeishFs: args.nodeishFs,
29
- basePath: args.settingsFilePath.replace(/(.*?)[^/]*\..*$/, "$1"), // get directory of settings file
30
- });
34
+ const nodeishFs = createNodeishFsWithAbsolutePaths(args);
31
35
  // -- settings ------------------------------------------------------------
32
36
  const [settings, _setSettings] = createSignal();
33
37
  createEffect(() => {
@@ -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();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@inlang/sdk",
3
3
  "type": "module",
4
- "version": "0.11.0",
4
+ "version": "0.12.0",
5
5
  "license": "Apache-2.0",
6
6
  "publishConfig": {
7
7
  "access": "public"
@@ -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 bathPath = args.settingsFilePath.split("/").slice(0, -1).join("/")
23
+
24
+ const makeAbsolute = (path: string) => {
25
+ if (isAbsolutePath(path)) {
26
+ return path
27
+ }
28
+
29
+ return normalizePath(bathPath + "/" + 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(
@@ -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()
@@ -13,6 +13,7 @@ import {
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,10 @@ 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 { createNodeishFsWithAbsolutePaths } from "./resolve-modules/createNodeishFsWithAbsolutePaths.js"
25
+ import {
26
+ createNodeishFsWithAbsolutePaths,
27
+ isAbsolutePath,
28
+ } from "./createNodeishFsWithAbsolutePaths.js"
25
29
 
26
30
  const settingsCompiler = TypeCompiler.Compile(ProjectSettings)
27
31
 
@@ -41,15 +45,21 @@ export const loadProject = async (args: {
41
45
  _import?: ImportFunction
42
46
  _capture?: (id: string, props: Record<string, unknown>) => void
43
47
  }): Promise<InlangProject> => {
48
+ // -- validation --------------------------------------------------------
49
+ //! the only place where throwing is acceptable because the project
50
+ //! won't even be loaded. do not throw anywhere else. otherwise, apps
51
+ //! can't handle errors gracefully.
52
+ if (!isAbsolutePath(args.settingsFilePath)) {
53
+ throw new LoadProjectInvalidArgument(
54
+ `Expected an absolute path but received "${args.settingsFilePath}".`,
55
+ { argument: "settingsFilePath" }
56
+ )
57
+ }
58
+
59
+ // -- load project ------------------------------------------------------
44
60
  return await createRoot(async () => {
45
61
  const [initialized, markInitAsComplete, markInitAsFailed] = createAwaitable()
46
-
47
- // -- absolute path env -----------------------------------------------
48
-
49
- const nodeishFs = createNodeishFsWithAbsolutePaths({
50
- nodeishFs: args.nodeishFs,
51
- basePath: args.settingsFilePath.replace(/(.*?)[^/]*\..*$/, "$1"), // get directory of settings file
52
- })
62
+ const nodeishFs = createNodeishFsWithAbsolutePaths(args)
53
63
 
54
64
  // -- settings ------------------------------------------------------------
55
65
 
@@ -338,5 +348,3 @@ export function createSubscribable<T>(signal: () => T): Subscribable<T> {
338
348
  },
339
349
  })
340
350
  }
341
-
342
-
@@ -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
- }