@inlang/sdk 0.31.0 → 0.33.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/createNewProject.d.ts +17 -0
- package/dist/createNewProject.d.ts.map +1 -0
- package/dist/createNewProject.js +22 -0
- package/dist/createNewProject.test.d.ts +2 -0
- package/dist/createNewProject.test.d.ts.map +1 -0
- package/dist/createNewProject.test.js +93 -0
- package/dist/createNodeishFsWithAbsolutePaths.js +1 -1
- package/dist/defaultProjectSettings.d.ts +14 -0
- package/dist/defaultProjectSettings.d.ts.map +1 -0
- package/dist/defaultProjectSettings.js +23 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/loadProject.d.ts.map +1 -1
- package/dist/loadProject.js +3 -8
- package/dist/loadProject.test.js +4 -4
- package/dist/migrations/maybeCreateFirstProjectId.test.js +3 -6
- package/dist/migrations/migrateToDirectory.d.ts.map +1 -1
- package/dist/migrations/migrateToDirectory.js +2 -0
- package/dist/validateProjectPath.d.ts +23 -0
- package/dist/validateProjectPath.d.ts.map +1 -0
- package/dist/validateProjectPath.js +52 -0
- package/dist/validateProjectPath.test.d.ts +2 -0
- package/dist/validateProjectPath.test.d.ts.map +1 -0
- package/dist/validateProjectPath.test.js +56 -0
- package/package.json +5 -5
- package/src/api.ts +1 -1
- package/src/createNewProject.test.ts +108 -0
- package/src/createNewProject.ts +31 -0
- package/src/createNodeishFsWithAbsolutePaths.ts +1 -1
- package/src/defaultProjectSettings.ts +27 -0
- package/src/index.ts +2 -0
- package/src/loadProject.test.ts +5 -4
- package/src/loadProject.ts +2 -13
- package/src/migrations/maybeCreateFirstProjectId.test.ts +4 -7
- package/src/migrations/migrateToDirectory.ts +3 -0
- package/src/validateProjectPath.test.ts +68 -0
- package/src/validateProjectPath.ts +58 -0
- package/dist/isAbsolutePath.d.ts +0 -2
- package/dist/isAbsolutePath.d.ts.map +0 -1
- package/dist/isAbsolutePath.js +0 -4
- package/dist/isAbsolutePath.test.d.ts +0 -2
- package/dist/isAbsolutePath.test.d.ts.map +0 -1
- package/dist/isAbsolutePath.test.js +0 -20
- package/src/isAbsolutePath.test.ts +0 -23
- package/src/isAbsolutePath.ts +0 -5
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Repository } from "@lix-js/client";
|
|
2
|
+
import { ProjectSettings } from "@inlang/project-settings";
|
|
3
|
+
/**
|
|
4
|
+
* Creates a new project in the given directory.
|
|
5
|
+
* The directory must be an absolute path, must not exist, and must end with {name}.inlang
|
|
6
|
+
* Uses defaultProjectSettings unless projectSettings are provided.
|
|
7
|
+
*
|
|
8
|
+
* @param projectPath - Absolute path to the [name].inlang directory
|
|
9
|
+
* @param repo - An instance of a lix repo as returned by `openRepository`
|
|
10
|
+
* @param projectSettings - Optional project settings to use for the new project.
|
|
11
|
+
*/
|
|
12
|
+
export declare function createNewProject(args: {
|
|
13
|
+
projectPath: string;
|
|
14
|
+
repo: Repository;
|
|
15
|
+
projectSettings: ProjectSettings;
|
|
16
|
+
}): Promise<void>;
|
|
17
|
+
//# sourceMappingURL=createNewProject.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createNewProject.d.ts","sourceRoot":"","sources":["../src/createNewProject.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAI1D;;;;;;;;GAQG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE;IAC5C,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,UAAU,CAAA;IAChB,eAAe,EAAE,eAAe,CAAA;CAChC,GAAG,OAAO,CAAC,IAAI,CAAC,CAYhB"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ProjectSettings } from "@inlang/project-settings";
|
|
2
|
+
import { assertValidProjectPath, pathExists } from "./validateProjectPath.js";
|
|
3
|
+
import { defaultProjectSettings } from "./defaultProjectSettings.js";
|
|
4
|
+
/**
|
|
5
|
+
* Creates a new project in the given directory.
|
|
6
|
+
* The directory must be an absolute path, must not exist, and must end with {name}.inlang
|
|
7
|
+
* Uses defaultProjectSettings unless projectSettings are provided.
|
|
8
|
+
*
|
|
9
|
+
* @param projectPath - Absolute path to the [name].inlang directory
|
|
10
|
+
* @param repo - An instance of a lix repo as returned by `openRepository`
|
|
11
|
+
* @param projectSettings - Optional project settings to use for the new project.
|
|
12
|
+
*/
|
|
13
|
+
export async function createNewProject(args) {
|
|
14
|
+
assertValidProjectPath(args.projectPath);
|
|
15
|
+
const nodeishFs = args.repo.nodeishFs;
|
|
16
|
+
if (await pathExists(args.projectPath, nodeishFs)) {
|
|
17
|
+
throw new Error(`projectPath already exists, received "${args.projectPath}"`);
|
|
18
|
+
}
|
|
19
|
+
await nodeishFs.mkdir(args.projectPath, { recursive: true });
|
|
20
|
+
const settingsText = JSON.stringify(args.projectSettings ?? defaultProjectSettings, undefined, 2);
|
|
21
|
+
await nodeishFs.writeFile(`${args.projectPath}/settings.json`, settingsText);
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createNewProject.test.d.ts","sourceRoot":"","sources":["../src/createNewProject.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { createNewProject } from "./createNewProject.js";
|
|
3
|
+
import { mockRepo } from "@lix-js/client";
|
|
4
|
+
import { defaultProjectSettings } from "./defaultProjectSettings.js";
|
|
5
|
+
import { loadProject } from "./loadProject.js";
|
|
6
|
+
import { createMessage } from "./test-utilities/createMessage.js";
|
|
7
|
+
function sleep(ms) {
|
|
8
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
9
|
+
}
|
|
10
|
+
describe("createNewProject", () => {
|
|
11
|
+
it("should throw if a path does not end with .inlang", async () => {
|
|
12
|
+
const repo = await mockRepo();
|
|
13
|
+
const projectPath = "/test/project.inl";
|
|
14
|
+
try {
|
|
15
|
+
await createNewProject({ projectPath, repo, projectSettings: defaultProjectSettings });
|
|
16
|
+
// should not reach this point
|
|
17
|
+
throw new Error("Expected an error");
|
|
18
|
+
}
|
|
19
|
+
catch (e) {
|
|
20
|
+
expect(e.message).toMatch('Expected a path ending in "{name}.inlang" but received "/test/project.inl"');
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
it("should throw if projectPath is not an absolute path", async () => {
|
|
24
|
+
const repo = await mockRepo();
|
|
25
|
+
const projectPath = "test/project.inlang";
|
|
26
|
+
try {
|
|
27
|
+
await createNewProject({ projectPath, repo, projectSettings: defaultProjectSettings });
|
|
28
|
+
// should not reach this point
|
|
29
|
+
throw new Error("Expected an error");
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
expect(e.message).toMatch('Expected an absolute path but received "test/project.inlang"');
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
it("should throw if the path already exists", async () => {
|
|
36
|
+
const repo = await mockRepo();
|
|
37
|
+
const projectPath = "/test/project.inlang";
|
|
38
|
+
await repo.nodeishFs.mkdir(projectPath, { recursive: true });
|
|
39
|
+
try {
|
|
40
|
+
await createNewProject({ projectPath, repo, projectSettings: defaultProjectSettings });
|
|
41
|
+
// should not reach this point
|
|
42
|
+
throw new Error("Expected an error");
|
|
43
|
+
}
|
|
44
|
+
catch (e) {
|
|
45
|
+
expect(e.message).toMatch('projectPath already exists, received "/test/project.inlang"');
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
it("should create default defaultProjectSettings in projectPath", async () => {
|
|
49
|
+
const repo = await mockRepo();
|
|
50
|
+
const projectPath = "/test/project.inlang";
|
|
51
|
+
await createNewProject({ projectPath, repo, projectSettings: defaultProjectSettings });
|
|
52
|
+
const json = await repo.nodeishFs.readFile(`${projectPath}/settings.json`, {
|
|
53
|
+
encoding: "utf-8",
|
|
54
|
+
});
|
|
55
|
+
const settings = JSON.parse(json);
|
|
56
|
+
expect(settings).toEqual(defaultProjectSettings);
|
|
57
|
+
});
|
|
58
|
+
it("should create different projectSettings in projectPath", async () => {
|
|
59
|
+
const repo = await mockRepo();
|
|
60
|
+
const projectPath = "/test/project.inlang";
|
|
61
|
+
const projectSettings = { ...defaultProjectSettings, languageTags: ["en", "de", "fr"] };
|
|
62
|
+
await createNewProject({ projectPath, repo, projectSettings });
|
|
63
|
+
const json = await repo.nodeishFs.readFile(`${projectPath}/settings.json`, {
|
|
64
|
+
encoding: "utf-8",
|
|
65
|
+
});
|
|
66
|
+
const settings = JSON.parse(json);
|
|
67
|
+
expect(settings).toEqual(projectSettings);
|
|
68
|
+
expect(settings).not.toEqual(defaultProjectSettings);
|
|
69
|
+
});
|
|
70
|
+
it("should load the project after creating it", async () => {
|
|
71
|
+
const repo = await mockRepo();
|
|
72
|
+
const projectPath = "/test/project.inlang";
|
|
73
|
+
await createNewProject({ projectPath, repo, projectSettings: defaultProjectSettings });
|
|
74
|
+
const project = await loadProject({ projectPath, repo });
|
|
75
|
+
expect(project.errors().length).toBe(0);
|
|
76
|
+
});
|
|
77
|
+
it("should create messages inside the project directory", async () => {
|
|
78
|
+
const repo = await mockRepo();
|
|
79
|
+
const projectPath = "/test/project.inlang";
|
|
80
|
+
await createNewProject({ projectPath, repo, projectSettings: defaultProjectSettings });
|
|
81
|
+
const project = await loadProject({ projectPath, repo });
|
|
82
|
+
expect(project.errors().length).toBe(0);
|
|
83
|
+
const testMessage = createMessage("test", { en: "test message" });
|
|
84
|
+
project.query.messages.create({ data: testMessage });
|
|
85
|
+
const messages = project.query.messages.getAll();
|
|
86
|
+
expect(messages.length).toBe(1);
|
|
87
|
+
expect(messages[0]).toEqual(testMessage);
|
|
88
|
+
await sleep(20);
|
|
89
|
+
const json = await repo.nodeishFs.readFile("/test/messages/en.json", { encoding: "utf-8" });
|
|
90
|
+
const jsonMessages = JSON.parse(json);
|
|
91
|
+
expect(jsonMessages["test"]).toEqual("test message");
|
|
92
|
+
});
|
|
93
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { normalizePath } from "@lix-js/fs";
|
|
2
|
-
import { isAbsolutePath } from "./
|
|
2
|
+
import { isAbsolutePath } from "./validateProjectPath.js";
|
|
3
3
|
/**
|
|
4
4
|
* Wraps the nodeish filesystem subset with a function that intercepts paths
|
|
5
5
|
* and prepends the base path.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default project settings for createNewProject
|
|
3
|
+
* from paraglide-js/src/cli/commands/init/defaults.ts
|
|
4
|
+
*/
|
|
5
|
+
export declare const defaultProjectSettings: {
|
|
6
|
+
$schema: "https://inlang.com/schema/project-settings";
|
|
7
|
+
sourceLanguageTag: string;
|
|
8
|
+
languageTags: string[];
|
|
9
|
+
modules: string[];
|
|
10
|
+
"plugin.inlang.messageFormat": {
|
|
11
|
+
pathPattern: string;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=defaultProjectSettings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaultProjectSettings.d.ts","sourceRoot":"","sources":["../src/defaultProjectSettings.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;CAoBR,CAAA"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default project settings for createNewProject
|
|
3
|
+
* from paraglide-js/src/cli/commands/init/defaults.ts
|
|
4
|
+
*/
|
|
5
|
+
export const defaultProjectSettings = {
|
|
6
|
+
$schema: "https://inlang.com/schema/project-settings",
|
|
7
|
+
sourceLanguageTag: "en",
|
|
8
|
+
languageTags: ["en"],
|
|
9
|
+
modules: [
|
|
10
|
+
// for instant gratification, we're adding common rules
|
|
11
|
+
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-empty-pattern@latest/dist/index.js",
|
|
12
|
+
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-missing-translation@latest/dist/index.js",
|
|
13
|
+
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-without-source@latest/dist/index.js",
|
|
14
|
+
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-valid-js-identifier@latest/dist/index.js",
|
|
15
|
+
// default to the message format plugin because it supports all features
|
|
16
|
+
"https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@latest/dist/index.js",
|
|
17
|
+
// the m function matcher should be installed by default in case Sherlock (VS Code extension) is adopted
|
|
18
|
+
"https://cdn.jsdelivr.net/npm/@inlang/plugin-m-function-matcher@latest/dist/index.js",
|
|
19
|
+
],
|
|
20
|
+
"plugin.inlang.messageFormat": {
|
|
21
|
+
pathPattern: "./messages/{languageTag}.json",
|
|
22
|
+
},
|
|
23
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export type { InlangProject, InstalledMessageLintRule, InstalledPlugin, MessageQueryApi, Subscribable, } from "./api.js";
|
|
7
7
|
export { type ImportFunction, createImport } from "./resolve-modules/index.js";
|
|
8
|
+
export { createNewProject } from "./createNewProject.js";
|
|
9
|
+
export { defaultProjectSettings } from "./defaultProjectSettings.js";
|
|
8
10
|
export { loadProject } from "./loadProject.js";
|
|
9
11
|
export { listProjects } from "./listProjects.js";
|
|
10
12
|
export { solidAdapter, type InlangProjectWithSolidAdapter } from "./adapter/solidAdapter.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,YAAY,EACX,aAAa,EACb,wBAAwB,EACxB,eAAe,EACf,eAAe,EACf,YAAY,GACZ,MAAM,UAAU,CAAA;AACjB,OAAO,EAAE,KAAK,cAAc,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAC9E,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,EAAE,YAAY,EAAE,KAAK,6BAA6B,EAAE,MAAM,2BAA2B,CAAA;AAC5F,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,EACN,kCAAkC,EAClC,gCAAgC,EAChC,2BAA2B,EAC3B,uBAAuB,EACvB,uBAAuB,GACvB,MAAM,aAAa,CAAA;AAEpB,cAAc,uBAAuB,CAAA;AACrC,cAAc,0BAA0B,CAAA;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,YAAY,EACX,aAAa,EACb,wBAAwB,EACxB,eAAe,EACf,eAAe,EACf,YAAY,GACZ,MAAM,UAAU,CAAA;AACjB,OAAO,EAAE,KAAK,cAAc,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAC9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAA;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,EAAE,YAAY,EAAE,KAAK,6BAA6B,EAAE,MAAM,2BAA2B,CAAA;AAC5F,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,EACN,kCAAkC,EAClC,gCAAgC,EAChC,2BAA2B,EAC3B,uBAAuB,EACvB,uBAAuB,GACvB,MAAM,aAAa,CAAA;AAEpB,cAAc,uBAAuB,CAAA;AACrC,cAAc,0BAA0B,CAAA;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
*! EXPORT AS LITTLE AS POSSIBLE TO MINIMIZE THE CHANCE OF BREAKING CHANGES.
|
|
5
5
|
*/
|
|
6
6
|
export { createImport } from "./resolve-modules/index.js";
|
|
7
|
+
export { createNewProject } from "./createNewProject.js";
|
|
8
|
+
export { defaultProjectSettings } from "./defaultProjectSettings.js";
|
|
7
9
|
export { loadProject } from "./loadProject.js";
|
|
8
10
|
export { listProjects } from "./listProjects.js";
|
|
9
11
|
export { solidAdapter } from "./adapter/solidAdapter.js";
|
|
@@ -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;AAwBhF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAgChD;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE;IACvC,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,UAAU,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,cAAc,CAAA;CACxB,GAAG,OAAO,CAAC,aAAa,CAAC,CA2XzB;AAsHD,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
2
|
import { TypeCompiler, ValueErrorType } from "@sinclair/typebox/compiler";
|
|
3
|
-
import { ProjectSettingsFileJSONSyntaxError, ProjectSettingsFileNotFoundError, ProjectSettingsInvalidError, PluginSaveMessagesError,
|
|
3
|
+
import { ProjectSettingsFileJSONSyntaxError, ProjectSettingsFileNotFoundError, ProjectSettingsInvalidError, PluginSaveMessagesError, PluginLoadMessagesError, } from "./errors.js";
|
|
4
4
|
import { createRoot, createSignal, createEffect } from "./reactivity/solid.js";
|
|
5
5
|
import { createMessagesQuery } from "./createMessagesQuery.js";
|
|
6
6
|
import { createMessageLintReportsQuery } from "./createMessageLintReportsQuery.js";
|
|
@@ -9,7 +9,7 @@ import { tryCatch } from "@inlang/result";
|
|
|
9
9
|
import { migrateIfOutdated } from "@inlang/project-settings/migration";
|
|
10
10
|
import { createNodeishFsWithAbsolutePaths } from "./createNodeishFsWithAbsolutePaths.js";
|
|
11
11
|
import { normalizePath } from "@lix-js/fs";
|
|
12
|
-
import {
|
|
12
|
+
import { assertValidProjectPath } from "./validateProjectPath.js";
|
|
13
13
|
import { maybeMigrateToDirectory } from "./migrations/migrateToDirectory.js";
|
|
14
14
|
import { stringifyMessage as stringifyMessage } from "./storage/helper.js";
|
|
15
15
|
import { humanIdHash } from "./storage/human-id/human-readable-id.js";
|
|
@@ -43,12 +43,7 @@ export async function loadProject(args) {
|
|
|
43
43
|
// the only place where throwing is acceptable because the project
|
|
44
44
|
// won't even be loaded. do not throw anywhere else. otherwise, apps
|
|
45
45
|
// can't handle errors gracefully.
|
|
46
|
-
|
|
47
|
-
throw new LoadProjectInvalidArgument(`Expected an absolute path but received "${args.projectPath}".`, { argument: "projectPath" });
|
|
48
|
-
}
|
|
49
|
-
else if (/[^\\/]+\.inlang$/.test(projectPath) === false) {
|
|
50
|
-
throw new LoadProjectInvalidArgument(`Expected a path ending in "{name}.inlang" but received "${projectPath}".\n\nValid examples: \n- "/path/to/micky-mouse.inlang"\n- "/path/to/green-elephant.inlang\n`, { argument: "projectPath" });
|
|
51
|
-
}
|
|
46
|
+
assertValidProjectPath(projectPath);
|
|
52
47
|
const nodeishFs = createNodeishFsWithAbsolutePaths({
|
|
53
48
|
projectPath,
|
|
54
49
|
nodeishFs: args.repo.nodeishFs,
|
package/dist/loadProject.test.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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 {
|
|
4
|
+
import { ProjectSettingsFileJSONSyntaxError, ProjectSettingsFileNotFoundError, ProjectSettingsInvalidError, } from "./errors.js";
|
|
5
5
|
import { normalizePath } from "@lix-js/fs";
|
|
6
6
|
import { createMessage } from "./test-utilities/createMessage.js";
|
|
7
7
|
import { tryCatch } from "@inlang/result";
|
|
@@ -144,7 +144,7 @@ it("should throw if a project (path) does not have a name", async () => {
|
|
|
144
144
|
repo,
|
|
145
145
|
_import,
|
|
146
146
|
}));
|
|
147
|
-
expect(project
|
|
147
|
+
expect(project?.error?.message).toMatch('Expected a path ending in "{name}.inlang" but received ');
|
|
148
148
|
});
|
|
149
149
|
it("should throw if a project path does not end with .inlang", async () => {
|
|
150
150
|
const repo = await mockRepo();
|
|
@@ -159,7 +159,7 @@ it("should throw if a project path does not end with .inlang", async () => {
|
|
|
159
159
|
repo,
|
|
160
160
|
_import,
|
|
161
161
|
}));
|
|
162
|
-
expect(project.error).
|
|
162
|
+
expect(project.error?.message).toMatch('Expected a path ending in "{name}.inlang" but received ');
|
|
163
163
|
}
|
|
164
164
|
});
|
|
165
165
|
describe("initialization", () => {
|
|
@@ -170,7 +170,7 @@ describe("initialization", () => {
|
|
|
170
170
|
repo,
|
|
171
171
|
_import,
|
|
172
172
|
}));
|
|
173
|
-
expect(result.error).
|
|
173
|
+
expect(result.error?.message).toBe('Expected an absolute path but received "relative/path".');
|
|
174
174
|
expect(result.data).toBeUndefined();
|
|
175
175
|
});
|
|
176
176
|
it("should generate projectId on missing projectid", async () => {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { generateProjectId } from "./maybeCreateFirstProjectId.js";
|
|
2
2
|
import { it, expect } from "vitest";
|
|
3
|
-
import {
|
|
4
|
-
import { mockRepo, createNodeishMemoryFs } from "@lix-js/client";
|
|
3
|
+
import { mockRepo } from "@lix-js/client";
|
|
5
4
|
import {} from "@lix-js/fs";
|
|
6
5
|
// eslint-disable-next-line no-restricted-imports -- test
|
|
7
6
|
import { readFileSync } from "node:fs";
|
|
@@ -16,11 +15,9 @@ it("should generate a project id", async () => {
|
|
|
16
15
|
expect(projectId).toBe("432d7ef29c510e99d95e2d14ef57a0797a1603859b5a851b7dff7e77161b8c08");
|
|
17
16
|
});
|
|
18
17
|
it("should return undefined if repoMeta contains error", async () => {
|
|
19
|
-
|
|
20
|
-
nodeishFs: createNodeishMemoryFs(),
|
|
21
|
-
});
|
|
18
|
+
await repo.nodeishFs.rm("/.git", { recursive: true });
|
|
22
19
|
const projectId = await generateProjectId({
|
|
23
|
-
repo:
|
|
20
|
+
repo: repo,
|
|
24
21
|
projectPath: "mocked_project_path",
|
|
25
22
|
});
|
|
26
23
|
expect(projectId).toBeUndefined();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrateToDirectory.d.ts","sourceRoot":"","sources":["../../src/migrations/migrateToDirectory.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAEnD;;;GAGG;AACH,eAAO,MAAM,uBAAuB,SAAgB;IACnD,SAAS,EAAE,iBAAiB,CAAA;IAC5B,WAAW,EAAE,MAAM,CAAA;CACnB,
|
|
1
|
+
{"version":3,"file":"migrateToDirectory.d.ts","sourceRoot":"","sources":["../../src/migrations/migrateToDirectory.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAEnD;;;GAGG;AACH,eAAO,MAAM,uBAAuB,SAAgB;IACnD,SAAS,EAAE,iBAAiB,CAAA;IAC5B,WAAW,EAAE,MAAM,CAAA;CACnB,kBA6BA,CAAA"}
|
|
@@ -22,6 +22,8 @@ export const maybeMigrateToDirectory = async (args) => {
|
|
|
22
22
|
await args.nodeishFs.mkdir(args.projectPath);
|
|
23
23
|
await args.nodeishFs.writeFile(`${args.projectPath}/settings.json`, settingsFile.data);
|
|
24
24
|
await args.nodeishFs.writeFile(args.projectPath + ".README.md", readme);
|
|
25
|
+
// eslint-disable-next-line no-console
|
|
26
|
+
// console.log("🔔 migrated project.inlang to directory")
|
|
25
27
|
};
|
|
26
28
|
const readme = `
|
|
27
29
|
# DELETE THE \`project.inlang.json\` FILE
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { NodeishFilesystem } from "@lix-js/fs";
|
|
2
|
+
/**
|
|
3
|
+
* validate that a project path is absolute and ends with {name}.inlang.
|
|
4
|
+
*
|
|
5
|
+
* @throws if the path is not valid.
|
|
6
|
+
*/
|
|
7
|
+
export declare function assertValidProjectPath(projectPath: string): void;
|
|
8
|
+
/**
|
|
9
|
+
* tests whether a path ends with {name}.inlang
|
|
10
|
+
* (does not remove trailing slash)
|
|
11
|
+
*/
|
|
12
|
+
export declare function isInlangProjectPath(path: string): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* tests whether a path starts with a forward slash (/) or a windows-style
|
|
15
|
+
* drive letter (C: or D:, etc.) followed by a slash
|
|
16
|
+
*/
|
|
17
|
+
export declare function isAbsolutePath(path: string): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Returns true if the path exists (file or directory), false otherwise.
|
|
20
|
+
*
|
|
21
|
+
*/
|
|
22
|
+
export declare function pathExists(filePath: string, nodeishFs: NodeishFilesystem): Promise<boolean>;
|
|
23
|
+
//# sourceMappingURL=validateProjectPath.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validateProjectPath.d.ts","sourceRoot":"","sources":["../src/validateProjectPath.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAEnD;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,QASzD;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,WAE/C;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,WAO1C;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,iBAAiB,oBAc9E"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* validate that a project path is absolute and ends with {name}.inlang.
|
|
3
|
+
*
|
|
4
|
+
* @throws if the path is not valid.
|
|
5
|
+
*/
|
|
6
|
+
export function assertValidProjectPath(projectPath) {
|
|
7
|
+
if (!isAbsolutePath(projectPath)) {
|
|
8
|
+
throw new Error(`Expected an absolute path but received "${projectPath}".`);
|
|
9
|
+
}
|
|
10
|
+
if (!isInlangProjectPath(projectPath)) {
|
|
11
|
+
throw new Error(`Expected a path ending in "{name}.inlang" but received "${projectPath}".\n\nValid examples: \n- "/path/to/micky-mouse.inlang"\n- "/path/to/green-elephant.inlang\n`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* tests whether a path ends with {name}.inlang
|
|
16
|
+
* (does not remove trailing slash)
|
|
17
|
+
*/
|
|
18
|
+
export function isInlangProjectPath(path) {
|
|
19
|
+
return /[^\\/]+\.inlang$/.test(path);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* tests whether a path starts with a forward slash (/) or a windows-style
|
|
23
|
+
* drive letter (C: or D:, etc.) followed by a slash
|
|
24
|
+
*/
|
|
25
|
+
export function isAbsolutePath(path) {
|
|
26
|
+
return /^\/|^[A-Za-z]:[\\/]/.test(path);
|
|
27
|
+
// OG from sdk/src/isAbsolutePath.ts - TODO: find out where this regex came from
|
|
28
|
+
// const matchPosixAndWindowsAbsolutePaths =
|
|
29
|
+
// /^(?:[A-Za-z]:\\(?:[^\\]+\\)*[^\\]+|[A-Za-z]:\/(?:[^/]+\/)*[^/]+|\/(?:[^/]+\/)*[^/]+)$/
|
|
30
|
+
// return matchPosixAndWindowsAbsolutePaths.test(path)
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Returns true if the path exists (file or directory), false otherwise.
|
|
34
|
+
*
|
|
35
|
+
*/
|
|
36
|
+
export async function pathExists(filePath, nodeishFs) {
|
|
37
|
+
// from paraglide-js/src/services/file-handling/exists.ts
|
|
38
|
+
// TODO: add fs.exists to @lix-js/fs
|
|
39
|
+
try {
|
|
40
|
+
await nodeishFs.stat(filePath);
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
//@ts-ignore
|
|
45
|
+
if (error.code === "ENOENT") {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
throw new Error(`Failed to check if path exists: ${error}`, { cause: error });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validateProjectPath.test.d.ts","sourceRoot":"","sources":["../src/validateProjectPath.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { assert, describe, expect, it } from "vitest";
|
|
2
|
+
import { assertValidProjectPath, isAbsolutePath, isInlangProjectPath, pathExists, } from "./validateProjectPath.js";
|
|
3
|
+
import { mockRepo } from "@lix-js/client";
|
|
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
|
+
it("should correctly identify Windows absolute paths", () => {
|
|
11
|
+
assert.isTrue(isAbsolutePath("C:\\Users\\User\\Documents\\File.txt"));
|
|
12
|
+
assert.isTrue(isAbsolutePath("C:/Users/user/project.inlang/settings.json"));
|
|
13
|
+
assert.isFalse(isAbsolutePath("Projects\\Project1\\source\\file.txt"));
|
|
14
|
+
});
|
|
15
|
+
it("should handle edge cases", () => {
|
|
16
|
+
assert.isFalse(isAbsolutePath("")); // Empty path should return false
|
|
17
|
+
assert.isFalse(isAbsolutePath("relative/path/../file.txt")); // Relative path with ".." should return false
|
|
18
|
+
assert.isFalse(isAbsolutePath("../relative/path/to/file.txt"));
|
|
19
|
+
assert.isFalse(isAbsolutePath("./relative/path/to/file.txt"));
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
describe("isInlangProjectPath", () => {
|
|
23
|
+
it("should correctly identify valid inlang project paths", () => {
|
|
24
|
+
assert.isTrue(isInlangProjectPath("/path/to/orange-mouse.inlang"));
|
|
25
|
+
assert.isFalse(isInlangProjectPath("relative/path/to/file.txt"));
|
|
26
|
+
assert.isFalse(isInlangProjectPath("/path/to/.inlang"));
|
|
27
|
+
assert.isFalse(isInlangProjectPath("/path/to/white-elephant.inlang/"));
|
|
28
|
+
assert.isFalse(isInlangProjectPath("/path/to/blue-elephant.inlang/settings.json"));
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
describe("assertValidProjectPath", () => {
|
|
32
|
+
it("should not throw for valid project paths", () => {
|
|
33
|
+
assert.doesNotThrow(() => assertValidProjectPath("/path/to/brown-mouse.inlang"));
|
|
34
|
+
assert.doesNotThrow(() => assertValidProjectPath("/path/to/green-elephant.inlang"));
|
|
35
|
+
});
|
|
36
|
+
it("should throw for invalid project paths", () => {
|
|
37
|
+
assert.throws(() => assertValidProjectPath("relative/path/to/flying-lizard.inlang"));
|
|
38
|
+
assert.throws(() => assertValidProjectPath("/path/to/loud-mouse.inlang/"));
|
|
39
|
+
assert.throws(() => assertValidProjectPath("/path/to/green-elephant.inlang/settings.json"));
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
// moar tests in paraglide-js/src/services/file-handling/exists.test.ts
|
|
43
|
+
describe("pathExists", () => {
|
|
44
|
+
it("should work for files", async () => {
|
|
45
|
+
const repo = await mockRepo();
|
|
46
|
+
await repo.nodeishFs.writeFile("/test.txt", "hello");
|
|
47
|
+
expect(await pathExists("/test.txt", repo.nodeishFs)).toBe(true);
|
|
48
|
+
expect(await pathExists("/does-not-exist.txt", repo.nodeishFs)).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
it("should work for directories", async () => {
|
|
51
|
+
const repo = await mockRepo();
|
|
52
|
+
await repo.nodeishFs.mkdir("/test/project.inlang", { recursive: true });
|
|
53
|
+
expect(await pathExists("/test/project.inlang", repo.nodeishFs)).toBe(true);
|
|
54
|
+
expect(await pathExists("/test/white-gorilla.inlang", repo.nodeishFs)).toBe(false);
|
|
55
|
+
});
|
|
56
|
+
});
|
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.33.0",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
@@ -35,13 +35,13 @@
|
|
|
35
35
|
"@inlang/json-types": "1.1.0",
|
|
36
36
|
"@inlang/language-tag": "1.5.1",
|
|
37
37
|
"@inlang/message": "2.1.0",
|
|
38
|
+
"@inlang/message-lint-rule": "1.4.5",
|
|
38
39
|
"@inlang/module": "1.2.9",
|
|
39
|
-
"@inlang/project-settings": "2.4.0",
|
|
40
|
-
"@inlang/result": "1.1.0",
|
|
41
40
|
"@inlang/plugin": "2.4.9",
|
|
42
|
-
"@inlang/
|
|
43
|
-
"@
|
|
41
|
+
"@inlang/result": "1.1.0",
|
|
42
|
+
"@inlang/project-settings": "2.4.0",
|
|
44
43
|
"@inlang/translatable": "1.3.1",
|
|
44
|
+
"@lix-js/client": "1.2.1",
|
|
45
45
|
"@lix-js/fs": "1.0.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
package/src/api.ts
CHANGED
|
@@ -80,7 +80,7 @@ export type MessageQueryApi = {
|
|
|
80
80
|
}
|
|
81
81
|
includedMessageIds: Subscribable<Message["id"][]>
|
|
82
82
|
/*
|
|
83
|
-
* getAll is
|
|
83
|
+
* getAll is deprecated do not use it
|
|
84
84
|
*/
|
|
85
85
|
getAll: Subscribable<Readonly<Message[]>>
|
|
86
86
|
update: (args: { where: { id: Message["id"] }; data: Partial<Message> }) => boolean
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest"
|
|
2
|
+
import { createNewProject } from "./createNewProject.js"
|
|
3
|
+
import { mockRepo } from "@lix-js/client"
|
|
4
|
+
import { defaultProjectSettings } from "./defaultProjectSettings.js"
|
|
5
|
+
import { loadProject } from "./loadProject.js"
|
|
6
|
+
import { createMessage } from "./test-utilities/createMessage.js"
|
|
7
|
+
|
|
8
|
+
function sleep(ms: number) {
|
|
9
|
+
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
describe("createNewProject", () => {
|
|
13
|
+
it("should throw if a path does not end with .inlang", async () => {
|
|
14
|
+
const repo = await mockRepo()
|
|
15
|
+
const projectPath = "/test/project.inl"
|
|
16
|
+
try {
|
|
17
|
+
await createNewProject({ projectPath, repo, projectSettings: defaultProjectSettings })
|
|
18
|
+
// should not reach this point
|
|
19
|
+
throw new Error("Expected an error")
|
|
20
|
+
} catch (e) {
|
|
21
|
+
expect((e as Error).message).toMatch(
|
|
22
|
+
'Expected a path ending in "{name}.inlang" but received "/test/project.inl"'
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it("should throw if projectPath is not an absolute path", async () => {
|
|
28
|
+
const repo = await mockRepo()
|
|
29
|
+
const projectPath = "test/project.inlang"
|
|
30
|
+
try {
|
|
31
|
+
await createNewProject({ projectPath, repo, projectSettings: defaultProjectSettings })
|
|
32
|
+
// should not reach this point
|
|
33
|
+
throw new Error("Expected an error")
|
|
34
|
+
} catch (e) {
|
|
35
|
+
expect((e as Error).message).toMatch(
|
|
36
|
+
'Expected an absolute path but received "test/project.inlang"'
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it("should throw if the path already exists", async () => {
|
|
42
|
+
const repo = await mockRepo()
|
|
43
|
+
const projectPath = "/test/project.inlang"
|
|
44
|
+
await repo.nodeishFs.mkdir(projectPath, { recursive: true })
|
|
45
|
+
try {
|
|
46
|
+
await createNewProject({ projectPath, repo, projectSettings: defaultProjectSettings })
|
|
47
|
+
// should not reach this point
|
|
48
|
+
throw new Error("Expected an error")
|
|
49
|
+
} catch (e) {
|
|
50
|
+
expect((e as Error).message).toMatch(
|
|
51
|
+
'projectPath already exists, received "/test/project.inlang"'
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it("should create default defaultProjectSettings in projectPath", async () => {
|
|
57
|
+
const repo = await mockRepo()
|
|
58
|
+
const projectPath = "/test/project.inlang"
|
|
59
|
+
await createNewProject({ projectPath, repo, projectSettings: defaultProjectSettings })
|
|
60
|
+
const json = await repo.nodeishFs.readFile(`${projectPath}/settings.json`, {
|
|
61
|
+
encoding: "utf-8",
|
|
62
|
+
})
|
|
63
|
+
const settings = JSON.parse(json)
|
|
64
|
+
expect(settings).toEqual(defaultProjectSettings)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it("should create different projectSettings in projectPath", async () => {
|
|
68
|
+
const repo = await mockRepo()
|
|
69
|
+
const projectPath = "/test/project.inlang"
|
|
70
|
+
const projectSettings = { ...defaultProjectSettings, languageTags: ["en", "de", "fr"] }
|
|
71
|
+
await createNewProject({ projectPath, repo, projectSettings })
|
|
72
|
+
const json = await repo.nodeishFs.readFile(`${projectPath}/settings.json`, {
|
|
73
|
+
encoding: "utf-8",
|
|
74
|
+
})
|
|
75
|
+
const settings = JSON.parse(json)
|
|
76
|
+
expect(settings).toEqual(projectSettings)
|
|
77
|
+
expect(settings).not.toEqual(defaultProjectSettings)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it("should load the project after creating it", async () => {
|
|
81
|
+
const repo = await mockRepo()
|
|
82
|
+
const projectPath = "/test/project.inlang"
|
|
83
|
+
await createNewProject({ projectPath, repo, projectSettings: defaultProjectSettings })
|
|
84
|
+
|
|
85
|
+
const project = await loadProject({ projectPath, repo })
|
|
86
|
+
expect(project.errors().length).toBe(0)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it("should create messages inside the project directory", async () => {
|
|
90
|
+
const repo = await mockRepo()
|
|
91
|
+
const projectPath = "/test/project.inlang"
|
|
92
|
+
await createNewProject({ projectPath, repo, projectSettings: defaultProjectSettings })
|
|
93
|
+
const project = await loadProject({ projectPath, repo })
|
|
94
|
+
expect(project.errors().length).toBe(0)
|
|
95
|
+
|
|
96
|
+
const testMessage = createMessage("test", { en: "test message" })
|
|
97
|
+
project.query.messages.create({ data: testMessage })
|
|
98
|
+
const messages = project.query.messages.getAll()
|
|
99
|
+
expect(messages.length).toBe(1)
|
|
100
|
+
expect(messages[0]).toEqual(testMessage)
|
|
101
|
+
|
|
102
|
+
await sleep(20)
|
|
103
|
+
|
|
104
|
+
const json = await repo.nodeishFs.readFile("/test/messages/en.json", { encoding: "utf-8" })
|
|
105
|
+
const jsonMessages = JSON.parse(json)
|
|
106
|
+
expect(jsonMessages["test"]).toEqual("test message")
|
|
107
|
+
})
|
|
108
|
+
})
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Repository } from "@lix-js/client"
|
|
2
|
+
import { ProjectSettings } from "@inlang/project-settings"
|
|
3
|
+
import { assertValidProjectPath, pathExists } from "./validateProjectPath.js"
|
|
4
|
+
import { defaultProjectSettings } from "./defaultProjectSettings.js"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Creates a new project in the given directory.
|
|
8
|
+
* The directory must be an absolute path, must not exist, and must end with {name}.inlang
|
|
9
|
+
* Uses defaultProjectSettings unless projectSettings are provided.
|
|
10
|
+
*
|
|
11
|
+
* @param projectPath - Absolute path to the [name].inlang directory
|
|
12
|
+
* @param repo - An instance of a lix repo as returned by `openRepository`
|
|
13
|
+
* @param projectSettings - Optional project settings to use for the new project.
|
|
14
|
+
*/
|
|
15
|
+
export async function createNewProject(args: {
|
|
16
|
+
projectPath: string
|
|
17
|
+
repo: Repository
|
|
18
|
+
projectSettings: ProjectSettings
|
|
19
|
+
}): Promise<void> {
|
|
20
|
+
assertValidProjectPath(args.projectPath)
|
|
21
|
+
|
|
22
|
+
const nodeishFs = args.repo.nodeishFs
|
|
23
|
+
if (await pathExists(args.projectPath, nodeishFs)) {
|
|
24
|
+
throw new Error(`projectPath already exists, received "${args.projectPath}"`)
|
|
25
|
+
}
|
|
26
|
+
await nodeishFs.mkdir(args.projectPath, { recursive: true })
|
|
27
|
+
|
|
28
|
+
const settingsText = JSON.stringify(args.projectSettings ?? defaultProjectSettings, undefined, 2)
|
|
29
|
+
|
|
30
|
+
await nodeishFs.writeFile(`${args.projectPath}/settings.json`, settingsText)
|
|
31
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ProjectSettings } from "@inlang/project-settings"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Default project settings for createNewProject
|
|
5
|
+
* from paraglide-js/src/cli/commands/init/defaults.ts
|
|
6
|
+
*/
|
|
7
|
+
export const defaultProjectSettings = {
|
|
8
|
+
$schema: "https://inlang.com/schema/project-settings",
|
|
9
|
+
sourceLanguageTag: "en",
|
|
10
|
+
languageTags: ["en"],
|
|
11
|
+
modules: [
|
|
12
|
+
// for instant gratification, we're adding common rules
|
|
13
|
+
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-empty-pattern@latest/dist/index.js",
|
|
14
|
+
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-missing-translation@latest/dist/index.js",
|
|
15
|
+
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-without-source@latest/dist/index.js",
|
|
16
|
+
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-valid-js-identifier@latest/dist/index.js",
|
|
17
|
+
|
|
18
|
+
// default to the message format plugin because it supports all features
|
|
19
|
+
"https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@latest/dist/index.js",
|
|
20
|
+
|
|
21
|
+
// the m function matcher should be installed by default in case Sherlock (VS Code extension) is adopted
|
|
22
|
+
"https://cdn.jsdelivr.net/npm/@inlang/plugin-m-function-matcher@latest/dist/index.js",
|
|
23
|
+
],
|
|
24
|
+
"plugin.inlang.messageFormat": {
|
|
25
|
+
pathPattern: "./messages/{languageTag}.json",
|
|
26
|
+
},
|
|
27
|
+
} satisfies ProjectSettings
|
package/src/index.ts
CHANGED
|
@@ -12,6 +12,8 @@ export type {
|
|
|
12
12
|
Subscribable,
|
|
13
13
|
} from "./api.js"
|
|
14
14
|
export { type ImportFunction, createImport } from "./resolve-modules/index.js"
|
|
15
|
+
export { createNewProject } from "./createNewProject.js"
|
|
16
|
+
export { defaultProjectSettings } from "./defaultProjectSettings.js"
|
|
15
17
|
export { loadProject } from "./loadProject.js"
|
|
16
18
|
export { listProjects } from "./listProjects.js"
|
|
17
19
|
export { solidAdapter, type InlangProjectWithSolidAdapter } from "./adapter/solidAdapter.js"
|
package/src/loadProject.test.ts
CHANGED
|
@@ -11,7 +11,6 @@ import type {
|
|
|
11
11
|
import type { ImportFunction } from "./resolve-modules/index.js"
|
|
12
12
|
import type { InlangModule } from "@inlang/module"
|
|
13
13
|
import {
|
|
14
|
-
LoadProjectInvalidArgument,
|
|
15
14
|
ProjectSettingsFileJSONSyntaxError,
|
|
16
15
|
ProjectSettingsFileNotFoundError,
|
|
17
16
|
ProjectSettingsInvalidError,
|
|
@@ -175,7 +174,7 @@ it("should throw if a project (path) does not have a name", async () => {
|
|
|
175
174
|
_import,
|
|
176
175
|
})
|
|
177
176
|
)
|
|
178
|
-
expect(project
|
|
177
|
+
expect(project?.error?.message).toMatch('Expected a path ending in "{name}.inlang" but received ')
|
|
179
178
|
})
|
|
180
179
|
|
|
181
180
|
it("should throw if a project path does not end with .inlang", async () => {
|
|
@@ -195,7 +194,9 @@ it("should throw if a project path does not end with .inlang", async () => {
|
|
|
195
194
|
_import,
|
|
196
195
|
})
|
|
197
196
|
)
|
|
198
|
-
expect(project.error).
|
|
197
|
+
expect(project.error?.message).toMatch(
|
|
198
|
+
'Expected a path ending in "{name}.inlang" but received '
|
|
199
|
+
)
|
|
199
200
|
}
|
|
200
201
|
})
|
|
201
202
|
|
|
@@ -210,7 +211,7 @@ describe("initialization", () => {
|
|
|
210
211
|
_import,
|
|
211
212
|
})
|
|
212
213
|
)
|
|
213
|
-
expect(result.error).
|
|
214
|
+
expect(result.error?.message).toBe('Expected an absolute path but received "relative/path".')
|
|
214
215
|
expect(result.data).toBeUndefined()
|
|
215
216
|
})
|
|
216
217
|
|
package/src/loadProject.ts
CHANGED
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
ProjectSettingsFileNotFoundError,
|
|
13
13
|
ProjectSettingsInvalidError,
|
|
14
14
|
PluginSaveMessagesError,
|
|
15
|
-
LoadProjectInvalidArgument,
|
|
16
15
|
PluginLoadMessagesError,
|
|
17
16
|
} from "./errors.js"
|
|
18
17
|
import { createRoot, createSignal, createEffect } from "./reactivity/solid.js"
|
|
@@ -23,7 +22,7 @@ import { tryCatch, type Result } from "@inlang/result"
|
|
|
23
22
|
import { migrateIfOutdated } from "@inlang/project-settings/migration"
|
|
24
23
|
import { createNodeishFsWithAbsolutePaths } from "./createNodeishFsWithAbsolutePaths.js"
|
|
25
24
|
import { normalizePath, type NodeishFilesystem } from "@lix-js/fs"
|
|
26
|
-
import {
|
|
25
|
+
import { assertValidProjectPath } from "./validateProjectPath.js"
|
|
27
26
|
import { maybeMigrateToDirectory } from "./migrations/migrateToDirectory.js"
|
|
28
27
|
|
|
29
28
|
import { stringifyMessage as stringifyMessage } from "./storage/helper.js"
|
|
@@ -93,17 +92,7 @@ export async function loadProject(args: {
|
|
|
93
92
|
// won't even be loaded. do not throw anywhere else. otherwise, apps
|
|
94
93
|
// can't handle errors gracefully.
|
|
95
94
|
|
|
96
|
-
|
|
97
|
-
throw new LoadProjectInvalidArgument(
|
|
98
|
-
`Expected an absolute path but received "${args.projectPath}".`,
|
|
99
|
-
{ argument: "projectPath" }
|
|
100
|
-
)
|
|
101
|
-
} else if (/[^\\/]+\.inlang$/.test(projectPath) === false) {
|
|
102
|
-
throw new LoadProjectInvalidArgument(
|
|
103
|
-
`Expected a path ending in "{name}.inlang" but received "${projectPath}".\n\nValid examples: \n- "/path/to/micky-mouse.inlang"\n- "/path/to/green-elephant.inlang\n`,
|
|
104
|
-
{ argument: "projectPath" }
|
|
105
|
-
)
|
|
106
|
-
}
|
|
95
|
+
assertValidProjectPath(projectPath)
|
|
107
96
|
|
|
108
97
|
const nodeishFs = createNodeishFsWithAbsolutePaths({
|
|
109
98
|
projectPath,
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { generateProjectId } from "./maybeCreateFirstProjectId.js"
|
|
2
2
|
import { it, expect } from "vitest"
|
|
3
|
-
import {
|
|
4
|
-
import { mockRepo, createNodeishMemoryFs } from "@lix-js/client"
|
|
3
|
+
import { mockRepo } from "@lix-js/client"
|
|
5
4
|
import { type Snapshot } from "@lix-js/fs"
|
|
6
5
|
// eslint-disable-next-line no-restricted-imports -- test
|
|
7
6
|
import { readFileSync } from "node:fs"
|
|
@@ -22,13 +21,11 @@ it("should generate a project id", async () => {
|
|
|
22
21
|
})
|
|
23
22
|
|
|
24
23
|
it("should return undefined if repoMeta contains error", async () => {
|
|
25
|
-
|
|
26
|
-
nodeishFs: createNodeishMemoryFs(),
|
|
27
|
-
})
|
|
24
|
+
await repo.nodeishFs.rm("/.git", { recursive: true })
|
|
28
25
|
|
|
29
26
|
const projectId = await generateProjectId({
|
|
30
|
-
repo:
|
|
27
|
+
repo: repo,
|
|
31
28
|
projectPath: "mocked_project_path",
|
|
32
29
|
})
|
|
33
30
|
expect(projectId).toBeUndefined()
|
|
34
|
-
})
|
|
31
|
+
})
|
|
@@ -34,6 +34,9 @@ export const maybeMigrateToDirectory = async (args: {
|
|
|
34
34
|
await args.nodeishFs.mkdir(args.projectPath)
|
|
35
35
|
await args.nodeishFs.writeFile(`${args.projectPath}/settings.json`, settingsFile.data)
|
|
36
36
|
await args.nodeishFs.writeFile(args.projectPath + ".README.md", readme)
|
|
37
|
+
|
|
38
|
+
// eslint-disable-next-line no-console
|
|
39
|
+
// console.log("🔔 migrated project.inlang to directory")
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
const readme = `
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { assert, describe, expect, it } from "vitest"
|
|
2
|
+
import {
|
|
3
|
+
assertValidProjectPath,
|
|
4
|
+
isAbsolutePath,
|
|
5
|
+
isInlangProjectPath,
|
|
6
|
+
pathExists,
|
|
7
|
+
} from "./validateProjectPath.js"
|
|
8
|
+
import { mockRepo } from "@lix-js/client"
|
|
9
|
+
|
|
10
|
+
describe("isAbsolutePath", () => {
|
|
11
|
+
it("should correctly identify Unix absolute paths", () => {
|
|
12
|
+
assert.isTrue(isAbsolutePath("/home/user/documents/file.txt"))
|
|
13
|
+
assert.isTrue(isAbsolutePath("/usr/local/bin/script.sh"))
|
|
14
|
+
assert.isFalse(isAbsolutePath("relative/path/to/file.txt"))
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it("should correctly identify Windows absolute paths", () => {
|
|
18
|
+
assert.isTrue(isAbsolutePath("C:\\Users\\User\\Documents\\File.txt"))
|
|
19
|
+
assert.isTrue(isAbsolutePath("C:/Users/user/project.inlang/settings.json"))
|
|
20
|
+
assert.isFalse(isAbsolutePath("Projects\\Project1\\source\\file.txt"))
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it("should handle edge cases", () => {
|
|
24
|
+
assert.isFalse(isAbsolutePath("")) // Empty path should return false
|
|
25
|
+
assert.isFalse(isAbsolutePath("relative/path/../file.txt")) // Relative path with ".." should return false
|
|
26
|
+
assert.isFalse(isAbsolutePath("../relative/path/to/file.txt"))
|
|
27
|
+
assert.isFalse(isAbsolutePath("./relative/path/to/file.txt"))
|
|
28
|
+
})
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
describe("isInlangProjectPath", () => {
|
|
32
|
+
it("should correctly identify valid inlang project paths", () => {
|
|
33
|
+
assert.isTrue(isInlangProjectPath("/path/to/orange-mouse.inlang"))
|
|
34
|
+
assert.isFalse(isInlangProjectPath("relative/path/to/file.txt"))
|
|
35
|
+
assert.isFalse(isInlangProjectPath("/path/to/.inlang"))
|
|
36
|
+
assert.isFalse(isInlangProjectPath("/path/to/white-elephant.inlang/"))
|
|
37
|
+
assert.isFalse(isInlangProjectPath("/path/to/blue-elephant.inlang/settings.json"))
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
describe("assertValidProjectPath", () => {
|
|
42
|
+
it("should not throw for valid project paths", () => {
|
|
43
|
+
assert.doesNotThrow(() => assertValidProjectPath("/path/to/brown-mouse.inlang"))
|
|
44
|
+
assert.doesNotThrow(() => assertValidProjectPath("/path/to/green-elephant.inlang"))
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it("should throw for invalid project paths", () => {
|
|
48
|
+
assert.throws(() => assertValidProjectPath("relative/path/to/flying-lizard.inlang"))
|
|
49
|
+
assert.throws(() => assertValidProjectPath("/path/to/loud-mouse.inlang/"))
|
|
50
|
+
assert.throws(() => assertValidProjectPath("/path/to/green-elephant.inlang/settings.json"))
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
// moar tests in paraglide-js/src/services/file-handling/exists.test.ts
|
|
55
|
+
describe("pathExists", () => {
|
|
56
|
+
it("should work for files", async () => {
|
|
57
|
+
const repo = await mockRepo()
|
|
58
|
+
await repo.nodeishFs.writeFile("/test.txt", "hello")
|
|
59
|
+
expect(await pathExists("/test.txt", repo.nodeishFs)).toBe(true)
|
|
60
|
+
expect(await pathExists("/does-not-exist.txt", repo.nodeishFs)).toBe(false)
|
|
61
|
+
})
|
|
62
|
+
it("should work for directories", async () => {
|
|
63
|
+
const repo = await mockRepo()
|
|
64
|
+
await repo.nodeishFs.mkdir("/test/project.inlang", { recursive: true })
|
|
65
|
+
expect(await pathExists("/test/project.inlang", repo.nodeishFs)).toBe(true)
|
|
66
|
+
expect(await pathExists("/test/white-gorilla.inlang", repo.nodeishFs)).toBe(false)
|
|
67
|
+
})
|
|
68
|
+
})
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { NodeishFilesystem } from "@lix-js/fs"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* validate that a project path is absolute and ends with {name}.inlang.
|
|
5
|
+
*
|
|
6
|
+
* @throws if the path is not valid.
|
|
7
|
+
*/
|
|
8
|
+
export function assertValidProjectPath(projectPath: string) {
|
|
9
|
+
if (!isAbsolutePath(projectPath)) {
|
|
10
|
+
throw new Error(`Expected an absolute path but received "${projectPath}".`)
|
|
11
|
+
}
|
|
12
|
+
if (!isInlangProjectPath(projectPath)) {
|
|
13
|
+
throw new Error(
|
|
14
|
+
`Expected a path ending in "{name}.inlang" but received "${projectPath}".\n\nValid examples: \n- "/path/to/micky-mouse.inlang"\n- "/path/to/green-elephant.inlang\n`
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* tests whether a path ends with {name}.inlang
|
|
21
|
+
* (does not remove trailing slash)
|
|
22
|
+
*/
|
|
23
|
+
export function isInlangProjectPath(path: string) {
|
|
24
|
+
return /[^\\/]+\.inlang$/.test(path)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* tests whether a path starts with a forward slash (/) or a windows-style
|
|
29
|
+
* drive letter (C: or D:, etc.) followed by a slash
|
|
30
|
+
*/
|
|
31
|
+
export function isAbsolutePath(path: string) {
|
|
32
|
+
return /^\/|^[A-Za-z]:[\\/]/.test(path)
|
|
33
|
+
|
|
34
|
+
// OG from sdk/src/isAbsolutePath.ts - TODO: find out where this regex came from
|
|
35
|
+
// const matchPosixAndWindowsAbsolutePaths =
|
|
36
|
+
// /^(?:[A-Za-z]:\\(?:[^\\]+\\)*[^\\]+|[A-Za-z]:\/(?:[^/]+\/)*[^/]+|\/(?:[^/]+\/)*[^/]+)$/
|
|
37
|
+
// return matchPosixAndWindowsAbsolutePaths.test(path)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Returns true if the path exists (file or directory), false otherwise.
|
|
42
|
+
*
|
|
43
|
+
*/
|
|
44
|
+
export async function pathExists(filePath: string, nodeishFs: NodeishFilesystem) {
|
|
45
|
+
// from paraglide-js/src/services/file-handling/exists.ts
|
|
46
|
+
// TODO: add fs.exists to @lix-js/fs
|
|
47
|
+
try {
|
|
48
|
+
await nodeishFs.stat(filePath)
|
|
49
|
+
return true
|
|
50
|
+
} catch (error) {
|
|
51
|
+
//@ts-ignore
|
|
52
|
+
if (error.code === "ENOENT") {
|
|
53
|
+
return false
|
|
54
|
+
} else {
|
|
55
|
+
throw new Error(`Failed to check if path exists: ${error}`, { cause: error })
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
package/dist/isAbsolutePath.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"isAbsolutePath.d.ts","sourceRoot":"","sources":["../src/isAbsolutePath.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,SAAU,MAAM,YAI1C,CAAA"}
|
package/dist/isAbsolutePath.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"isAbsolutePath.test.d.ts","sourceRoot":"","sources":["../src/isAbsolutePath.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,20 +0,0 @@
|
|
|
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.inlang/settings.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,23 +0,0 @@
|
|
|
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.inlang/settings.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/isAbsolutePath.ts
DELETED