@inlang/sdk 0.35.9 → 0.36.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.map +1 -1
- package/dist/createNewProject.js +6 -2
- package/dist/createNewProject.test.js +2 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/loadProject.d.ts.map +1 -1
- package/dist/loadProject.js +9 -1
- package/dist/migrations/maybeAddModuleCache.d.ts +6 -0
- package/dist/migrations/maybeAddModuleCache.d.ts.map +1 -0
- package/dist/migrations/maybeAddModuleCache.js +44 -0
- package/dist/resolve-modules/cache.d.ts +6 -0
- package/dist/resolve-modules/cache.d.ts.map +1 -0
- package/dist/resolve-modules/cache.js +40 -0
- package/dist/resolve-modules/import.d.ts +1 -11
- package/dist/resolve-modules/import.d.ts.map +1 -1
- package/dist/resolve-modules/import.js +78 -16
- package/dist/resolve-modules/import.test.js +3 -1
- package/dist/resolve-modules/resolveModules.d.ts.map +1 -1
- package/dist/resolve-modules/resolveModules.js +9 -11
- package/dist/resolve-modules/resolveModules.test.js +37 -6
- package/dist/resolve-modules/types.d.ts +1 -0
- package/dist/resolve-modules/types.d.ts.map +1 -1
- package/dist/v2/mocks/plural/bundle.d.ts.map +1 -1
- package/dist/v2/mocks/plural/bundle.js +22 -0
- package/dist/v2/mocks/plural/bundle.test.js +1 -1
- package/package.json +3 -3
- package/src/createNewProject.test.ts +2 -2
- package/src/createNewProject.ts +6 -3
- package/src/index.ts +7 -0
- package/src/loadProject.ts +11 -3
- package/src/migrations/maybeAddModuleCache.ts +53 -0
- package/src/resolve-modules/cache.ts +62 -0
- package/src/resolve-modules/import.test.ts +5 -1
- package/src/resolve-modules/import.ts +94 -24
- package/src/resolve-modules/resolveModules.test.ts +37 -6
- package/src/resolve-modules/resolveModules.ts +12 -17
- package/src/resolve-modules/types.ts +1 -0
- package/src/v2/mocks/plural/bundle.test.ts +1 -1
- package/src/v2/mocks/plural/bundle.ts +22 -0
|
@@ -1 +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;;;GAGG;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,
|
|
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;;;GAGG;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,CAehB"}
|
package/dist/createNewProject.js
CHANGED
|
@@ -11,7 +11,11 @@ export async function createNewProject(args) {
|
|
|
11
11
|
if (await pathExists(args.projectPath, nodeishFs)) {
|
|
12
12
|
throw new Error(`projectPath already exists, received "${args.projectPath}"`);
|
|
13
13
|
}
|
|
14
|
-
await nodeishFs.mkdir(args.projectPath, { recursive: true });
|
|
15
14
|
const settingsText = JSON.stringify(args.projectSettings ?? defaultProjectSettings, undefined, 2);
|
|
16
|
-
await nodeishFs.
|
|
15
|
+
await nodeishFs.mkdir(args.projectPath, { recursive: true });
|
|
16
|
+
await Promise.all([
|
|
17
|
+
nodeishFs.writeFile(`${args.projectPath}/settings.json`, settingsText),
|
|
18
|
+
nodeishFs.writeFile(`${args.projectPath}/.gitignore`, "cache"),
|
|
19
|
+
nodeishFs.mkdir(`${args.projectPath}/cache/modules`, { recursive: true }),
|
|
20
|
+
]);
|
|
17
21
|
}
|
|
@@ -70,14 +70,14 @@ describe("createNewProject", () => {
|
|
|
70
70
|
const projectPath = "/test/project.inlang";
|
|
71
71
|
await createNewProject({ projectPath, repo, projectSettings: defaultProjectSettings });
|
|
72
72
|
const project = await loadProject({ projectPath, repo });
|
|
73
|
-
expect(project.errors()
|
|
73
|
+
expect(project.errors()).toEqual([]);
|
|
74
74
|
});
|
|
75
75
|
it("should create messages inside the project directory", async () => {
|
|
76
76
|
const repo = await mockRepo();
|
|
77
77
|
const projectPath = "/test/project.inlang";
|
|
78
78
|
await createNewProject({ projectPath, repo, projectSettings: defaultProjectSettings });
|
|
79
79
|
const project = await loadProject({ projectPath, repo });
|
|
80
|
-
expect(project.errors()
|
|
80
|
+
expect(project.errors()).toEqual([]);
|
|
81
81
|
const testMessage = createMessage("test", { en: "test message" });
|
|
82
82
|
project.query.messages.create({ data: testMessage });
|
|
83
83
|
const messages = project.query.messages.getAll();
|
package/dist/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export { listProjects } from "./listProjects.js";
|
|
|
12
12
|
export { solidAdapter, type InlangProjectWithSolidAdapter } from "./adapter/solidAdapter.js";
|
|
13
13
|
export { createMessagesQuery } from "./createMessagesQuery.js";
|
|
14
14
|
export { ProjectSettingsFileJSONSyntaxError, ProjectSettingsFileNotFoundError, ProjectSettingsInvalidError, PluginLoadMessagesError, PluginSaveMessagesError, } from "./errors.js";
|
|
15
|
+
export { ModuleError, ModuleHasNoExportsError, ModuleImportError, ModuleExportIsInvalidError, ModuleSettingsAreInvalidError, } from "./resolve-modules/errors.js";
|
|
15
16
|
export { randomHumanId } from "./storage/human-id/human-readable-id.js";
|
|
16
17
|
export { normalizeMessage } from "./storage/helper.js";
|
|
17
18
|
export * from "./messages/variant.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,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;
|
|
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;AACpB,OAAO,EACN,WAAW,EACX,uBAAuB,EACvB,iBAAiB,EACjB,0BAA0B,EAC1B,6BAA6B,GAC7B,MAAM,6BAA6B,CAAA;AAEpC,OAAO,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAA;AACvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AACtD,cAAc,uBAAuB,CAAA;AACrC,cAAc,0BAA0B,CAAA;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,7 @@ export { listProjects } from "./listProjects.js";
|
|
|
11
11
|
export { solidAdapter } from "./adapter/solidAdapter.js";
|
|
12
12
|
export { createMessagesQuery } from "./createMessagesQuery.js";
|
|
13
13
|
export { ProjectSettingsFileJSONSyntaxError, ProjectSettingsFileNotFoundError, ProjectSettingsInvalidError, PluginLoadMessagesError, PluginSaveMessagesError, } from "./errors.js";
|
|
14
|
+
export { ModuleError, ModuleHasNoExportsError, ModuleImportError, ModuleExportIsInvalidError, ModuleSettingsAreInvalidError, } from "./resolve-modules/errors.js";
|
|
14
15
|
export { randomHumanId } from "./storage/human-id/human-readable-id.js";
|
|
15
16
|
export { normalizeMessage } from "./storage/helper.js";
|
|
16
17
|
export * from "./messages/variant.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loadProject.d.ts","sourceRoot":"","sources":["../src/loadProject.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACX,aAAa,EAGb,YAAY,EAGZ,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,EAGZ,MAAM,UAAU,CAAA;AACjB,OAAO,EAAE,KAAK,cAAc,EAAkB,MAAM,4BAA4B,CAAA;AAsBhF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAchD;;;;;;;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,CA+QzB;AA+GD,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAQtE"}
|
package/dist/loadProject.js
CHANGED
|
@@ -10,8 +10,10 @@ import { migrateIfOutdated } from "@inlang/project-settings/migration";
|
|
|
10
10
|
import { createNodeishFsWithAbsolutePaths } from "./createNodeishFsWithAbsolutePaths.js";
|
|
11
11
|
import { normalizePath } from "@lix-js/fs";
|
|
12
12
|
import { assertValidProjectPath } from "./validateProjectPath.js";
|
|
13
|
+
// Migrations
|
|
13
14
|
import { maybeMigrateToDirectory } from "./migrations/migrateToDirectory.js";
|
|
14
15
|
import { maybeCreateFirstProjectId } from "./migrations/maybeCreateFirstProjectId.js";
|
|
16
|
+
import { maybeAddModuleCache } from "./migrations/maybeAddModuleCache.js";
|
|
15
17
|
import { capture } from "./telemetry/capture.js";
|
|
16
18
|
import { identifyProject } from "./telemetry/groupIdentify.js";
|
|
17
19
|
import { stubMessagesQuery, stubMessageLintReportsQuery } from "./v2/stubQueryApi.js";
|
|
@@ -42,6 +44,7 @@ export async function loadProject(args) {
|
|
|
42
44
|
// -- migratations ------------------------------------------------
|
|
43
45
|
await maybeMigrateToDirectory({ nodeishFs, projectPath });
|
|
44
46
|
await maybeCreateFirstProjectId({ projectPath, repo: args.repo });
|
|
47
|
+
await maybeAddModuleCache({ projectPath, repo: args.repo });
|
|
45
48
|
// -- load project ------------------------------------------------------
|
|
46
49
|
return await createRoot(async () => {
|
|
47
50
|
// TODO remove tryCatch after https://github.com/opral/monorepo/issues/2013
|
|
@@ -103,7 +106,12 @@ export async function loadProject(args) {
|
|
|
103
106
|
const _settings = settings();
|
|
104
107
|
if (!_settings)
|
|
105
108
|
return;
|
|
106
|
-
resolveModules({
|
|
109
|
+
resolveModules({
|
|
110
|
+
settings: _settings,
|
|
111
|
+
nodeishFs,
|
|
112
|
+
_import: args._import,
|
|
113
|
+
projectPath,
|
|
114
|
+
})
|
|
107
115
|
.then((resolvedModules) => {
|
|
108
116
|
setResolvedModules(resolvedModules);
|
|
109
117
|
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"maybeAddModuleCache.d.ts","sourceRoot":"","sources":["../../src/migrations/maybeAddModuleCache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAKhD,wBAAsB,mBAAmB,CAAC,IAAI,EAAE;IAC/C,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,CAAC,EAAE,UAAU,CAAA;CACjB,GAAG,OAAO,CAAC,IAAI,CAAC,CA0BhB"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const EXPECTED_IGNORES = ["cache"];
|
|
2
|
+
export async function maybeAddModuleCache(args) {
|
|
3
|
+
if (args.repo === undefined)
|
|
4
|
+
return;
|
|
5
|
+
const projectExists = await directoryExists(args.projectPath, args.repo.nodeishFs);
|
|
6
|
+
if (!projectExists)
|
|
7
|
+
return;
|
|
8
|
+
const gitignorePath = args.projectPath + "/.gitignore";
|
|
9
|
+
const moduleCache = args.projectPath + "/cache/modules/";
|
|
10
|
+
const gitignoreExists = await fileExists(gitignorePath, args.repo.nodeishFs);
|
|
11
|
+
const moduleCacheExists = await directoryExists(moduleCache, args.repo.nodeishFs);
|
|
12
|
+
if (gitignoreExists) {
|
|
13
|
+
// non-destructively add any missing ignores
|
|
14
|
+
const gitignore = await args.repo.nodeishFs.readFile(gitignorePath, { encoding: "utf-8" });
|
|
15
|
+
const missingIgnores = EXPECTED_IGNORES.filter((ignore) => !gitignore.includes(ignore));
|
|
16
|
+
if (missingIgnores.length > 0) {
|
|
17
|
+
await args.repo.nodeishFs.appendFile(gitignorePath, "\n" + missingIgnores.join("\n"));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
await args.repo.nodeishFs.writeFile(gitignorePath, EXPECTED_IGNORES.join("\n"));
|
|
22
|
+
}
|
|
23
|
+
if (!moduleCacheExists) {
|
|
24
|
+
await args.repo.nodeishFs.mkdir(moduleCache, { recursive: true });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
async function fileExists(path, nodeishFs) {
|
|
28
|
+
try {
|
|
29
|
+
const stat = await nodeishFs.stat(path);
|
|
30
|
+
return stat.isFile();
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async function directoryExists(path, nodeishFs) {
|
|
37
|
+
try {
|
|
38
|
+
const stat = await nodeishFs.stat(path);
|
|
39
|
+
return stat.isDirectory();
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { NodeishFilesystemSubset } from "@inlang/plugin";
|
|
2
|
+
/**
|
|
3
|
+
* Implements a "Network-First" caching strategy.
|
|
4
|
+
*/
|
|
5
|
+
export declare function withCache(moduleLoader: (uri: string) => Promise<string>, projectPath: string, nodeishFs: Pick<NodeishFilesystemSubset, "readFile" | "writeFile">): (uri: string) => Promise<string>;
|
|
6
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/resolve-modules/cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAA;AAuC7D;;GAEG;AACH,wBAAgB,SAAS,CACxB,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,EAC9C,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,IAAI,CAAC,uBAAuB,EAAE,UAAU,GAAG,WAAW,CAAC,GAChE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAelC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { tryCatch } from "@inlang/result";
|
|
2
|
+
function escape(url) {
|
|
3
|
+
// collect the bytes of the UTF-8 representation
|
|
4
|
+
const bytes = new TextEncoder().encode(url);
|
|
5
|
+
// 64-bit FNV1a hash to make the file-names shorter
|
|
6
|
+
// https://en.wikipedia.org/wiki/FNV-1a
|
|
7
|
+
const hash = bytes.reduce((hash, byte) => BigInt.asUintN(64, (hash ^ BigInt(byte)) * 1099511628211n), 14695981039346656037n);
|
|
8
|
+
return hash.toString(36);
|
|
9
|
+
}
|
|
10
|
+
async function readModuleFromCache(moduleURI, projectPath, readFile) {
|
|
11
|
+
const moduleHash = escape(moduleURI);
|
|
12
|
+
const filePath = projectPath + `/cache/modules/${moduleHash}`;
|
|
13
|
+
return await tryCatch(async () => await readFile(filePath, { encoding: "utf-8" }));
|
|
14
|
+
}
|
|
15
|
+
async function writeModuleToCache(moduleURI, moduleContent, projectPath, writeFile) {
|
|
16
|
+
const moduleHash = escape(moduleURI);
|
|
17
|
+
const filePath = projectPath + `/cache/modules/${moduleHash}`;
|
|
18
|
+
await writeFile(filePath, moduleContent);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Implements a "Network-First" caching strategy.
|
|
22
|
+
*/
|
|
23
|
+
export function withCache(moduleLoader, projectPath, nodeishFs) {
|
|
24
|
+
return async (uri) => {
|
|
25
|
+
const cachePromise = readModuleFromCache(uri, projectPath, nodeishFs.readFile);
|
|
26
|
+
const networkResult = await tryCatch(async () => await moduleLoader(uri));
|
|
27
|
+
if (networkResult.error) {
|
|
28
|
+
const cacheResult = await cachePromise;
|
|
29
|
+
if (!cacheResult.error)
|
|
30
|
+
return cacheResult.data;
|
|
31
|
+
else
|
|
32
|
+
throw networkResult.error;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
const moduleAsText = networkResult.data;
|
|
36
|
+
await writeModuleToCache(uri, moduleAsText, projectPath, nodeishFs.writeFile);
|
|
37
|
+
return moduleAsText;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -15,15 +15,5 @@ export type ImportFunction = (uri: string) => Promise<any>;
|
|
|
15
15
|
* const $import = createImport({ readFile: fs.readFile, fetch });
|
|
16
16
|
* const module = await _import('./some-module.js');
|
|
17
17
|
*/
|
|
18
|
-
export declare function createImport(
|
|
19
|
-
/** the fs from which the file can be read */
|
|
20
|
-
readFile: NodeishFilesystemSubset["readFile"];
|
|
21
|
-
}): (uri: string) => ReturnType<typeof $import>;
|
|
22
|
-
declare function $import(uri: string, options: {
|
|
23
|
-
/**
|
|
24
|
-
* Required to import from a local path.
|
|
25
|
-
*/
|
|
26
|
-
readFile: NodeishFilesystemSubset["readFile"];
|
|
27
|
-
}): Promise<any>;
|
|
28
|
-
export {};
|
|
18
|
+
export declare function createImport(projectPath: string, nodeishFs: Pick<NodeishFilesystemSubset, "readFile" | "writeFile">): (uri: string) => Promise<any>;
|
|
29
19
|
//# sourceMappingURL=import.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"import.d.ts","sourceRoot":"","sources":["../../src/resolve-modules/import.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"import.d.ts","sourceRoot":"","sources":["../../src/resolve-modules/import.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAA;AAK7D;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,GAAG,CAAC,CAAA;AAE1D;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAC3B,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,IAAI,CAAC,uBAAuB,EAAE,UAAU,GAAG,WAAW,CAAC,SAErD,MAAM,kBACnB"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import dedent from "dedent";
|
|
2
2
|
import { ModuleImportError } from "./errors.js";
|
|
3
|
+
import { withCache } from "./cache.js";
|
|
4
|
+
import { tryCatch } from "@inlang/result";
|
|
3
5
|
/**
|
|
4
6
|
* Creates the import function.
|
|
5
7
|
*
|
|
@@ -9,20 +11,13 @@ import { ModuleImportError } from "./errors.js";
|
|
|
9
11
|
* const $import = createImport({ readFile: fs.readFile, fetch });
|
|
10
12
|
* const module = await _import('./some-module.js');
|
|
11
13
|
*/
|
|
12
|
-
export function createImport(
|
|
13
|
-
|
|
14
|
-
return (uri) => $import(uri, args);
|
|
14
|
+
export function createImport(projectPath, nodeishFs) {
|
|
15
|
+
return (uri) => $import(uri, projectPath, nodeishFs);
|
|
15
16
|
}
|
|
16
|
-
async function $import(uri,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
else {
|
|
22
|
-
moduleAsText = await options.readFile(uri, {
|
|
23
|
-
encoding: "utf-8",
|
|
24
|
-
});
|
|
25
|
-
}
|
|
17
|
+
async function $import(uri, projectPath, nodeishFs) {
|
|
18
|
+
const moduleAsText = uri.startsWith("http")
|
|
19
|
+
? await withCache(readModuleFromCDN, projectPath, nodeishFs)(uri)
|
|
20
|
+
: await readModulefromDisk(uri, nodeishFs.readFile);
|
|
26
21
|
const moduleWithMimeType = "data:application/javascript," + encodeURIComponent(moduleAsText);
|
|
27
22
|
try {
|
|
28
23
|
return await import(/* @vite-ignore */ moduleWithMimeType);
|
|
@@ -30,11 +25,78 @@ async function $import(uri, options) {
|
|
|
30
25
|
catch (error) {
|
|
31
26
|
if (error instanceof SyntaxError && uri.includes("jsdelivr")) {
|
|
32
27
|
error.message += dedent `\n\n
|
|
33
|
-
Are you sure that the file exists on JSDelivr?
|
|
28
|
+
Are you sure that the file exists on JSDelivr?
|
|
34
29
|
|
|
35
|
-
The error indicates that the imported file does not exist on JSDelivr. For non-existent files, JSDelivr returns a 404 text that JS cannot parse as a module and throws a SyntaxError
|
|
36
|
-
`;
|
|
30
|
+
The error indicates that the imported file does not exist on JSDelivr. For non-existent files, JSDelivr returns a 404 text that JS cannot parse as a module and throws a SyntaxError.`;
|
|
37
31
|
}
|
|
38
32
|
throw new ModuleImportError({ module: uri, cause: error });
|
|
39
33
|
}
|
|
40
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Tries to read the module from disk
|
|
37
|
+
* @throws {ModuleImportError}
|
|
38
|
+
*/
|
|
39
|
+
async function readModulefromDisk(uri, readFile) {
|
|
40
|
+
try {
|
|
41
|
+
return await readFile(uri, { encoding: "utf-8" });
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
throw new ModuleImportError({ module: uri, cause: error });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Reads a module from a CDN.
|
|
49
|
+
* Tries to read local cache first.
|
|
50
|
+
*
|
|
51
|
+
* @param uri A valid URL
|
|
52
|
+
* @throws {ModuleImportError}
|
|
53
|
+
*/
|
|
54
|
+
async function readModuleFromCDN(uri) {
|
|
55
|
+
if (!isValidUrl(uri))
|
|
56
|
+
throw new ModuleImportError({ module: uri, cause: new Error("Malformed URL") });
|
|
57
|
+
const result = await tryCatch(async () => await fetch(uri));
|
|
58
|
+
if (result.error) {
|
|
59
|
+
throw new ModuleImportError({
|
|
60
|
+
module: uri,
|
|
61
|
+
cause: result.error,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
const response = result.data;
|
|
65
|
+
if (!response.ok) {
|
|
66
|
+
throw new ModuleImportError({
|
|
67
|
+
module: uri,
|
|
68
|
+
cause: new Error(`Failed to fetch module. HTTP status: ${response.status}, Message: ${response.statusText}`),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
const JS_CONTENT_TYPES = [
|
|
72
|
+
"application/javascript",
|
|
73
|
+
"text/javascript",
|
|
74
|
+
"application/x-javascript",
|
|
75
|
+
"text/x-javascript",
|
|
76
|
+
];
|
|
77
|
+
// if there is no content-type header, assume it's a JavaScript module & hope for the best
|
|
78
|
+
const contentType = response.headers.get("content-type")?.toLowerCase();
|
|
79
|
+
if (contentType && !JS_CONTENT_TYPES.some((knownType) => contentType.includes(knownType))) {
|
|
80
|
+
throw new ModuleImportError({
|
|
81
|
+
module: uri,
|
|
82
|
+
cause: new Error(`Server responded with ${contentType} insetad of a JavaScript module`),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return await response.text();
|
|
86
|
+
}
|
|
87
|
+
function isValidUrl(url) {
|
|
88
|
+
// This dance is necessary to both support a fallback in case URL.canParse
|
|
89
|
+
// is not present (like in vitest), and also appease typescript
|
|
90
|
+
const URLConstructor = URL;
|
|
91
|
+
if ("canParse" in URL) {
|
|
92
|
+
return URL.canParse(url);
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
new URLConstructor(url);
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
catch (e) {
|
|
99
|
+
console.warn(`Invalid URL: ${url}`);
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -4,6 +4,7 @@ import { describe, expect, it } from "vitest";
|
|
|
4
4
|
import { createImport } from "./import.js";
|
|
5
5
|
describe("$import", async () => {
|
|
6
6
|
const fs = createNodeishMemoryFs();
|
|
7
|
+
await fs.mkdir("./project.inlang/cache/modules", { recursive: true });
|
|
7
8
|
await fs.writeFile("./mock-module.js", `
|
|
8
9
|
export function hello() {
|
|
9
10
|
return "hello";
|
|
@@ -16,7 +17,8 @@ describe("$import", async () => {
|
|
|
16
17
|
return "world";
|
|
17
18
|
}
|
|
18
19
|
`);
|
|
19
|
-
const _import = createImport({
|
|
20
|
+
const _import = createImport("/project.inlang", {
|
|
21
|
+
writeFile: fs.writeFile,
|
|
20
22
|
readFile: fs.readFile,
|
|
21
23
|
});
|
|
22
24
|
it("should import a module from a local path", async () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolveModules.d.ts","sourceRoot":"","sources":["../../src/resolve-modules/resolveModules.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAoBvD,eAAO,MAAM,cAAc,EAAE,
|
|
1
|
+
{"version":3,"file":"resolveModules.d.ts","sourceRoot":"","sources":["../../src/resolve-modules/resolveModules.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAoBvD,eAAO,MAAM,cAAc,EAAE,qBA4F5B,CAAA"}
|
|
@@ -8,30 +8,27 @@ import { TypeCompiler } from "@sinclair/typebox/compiler";
|
|
|
8
8
|
import { validatedModuleSettings } from "./validatedModuleSettings.js";
|
|
9
9
|
const ModuleCompiler = TypeCompiler.Compile(InlangModule);
|
|
10
10
|
export const resolveModules = async (args) => {
|
|
11
|
-
const _import = args._import ?? createImport(
|
|
12
|
-
const moduleErrors = [];
|
|
11
|
+
const _import = args._import ?? createImport(args.projectPath, args.nodeishFs);
|
|
13
12
|
const allPlugins = [];
|
|
14
13
|
const allMessageLintRules = [];
|
|
15
14
|
const meta = [];
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
* -------------- BEGIN SETUP --------------
|
|
19
|
-
*/
|
|
15
|
+
const moduleErrors = [];
|
|
16
|
+
async function resolveModule(module) {
|
|
20
17
|
const importedModule = await tryCatch(() => _import(module));
|
|
21
|
-
// -- IMPORT
|
|
18
|
+
// -- FAILED TO IMPORT --
|
|
22
19
|
if (importedModule.error) {
|
|
23
20
|
moduleErrors.push(new ModuleImportError({
|
|
24
21
|
module: module,
|
|
25
22
|
cause: importedModule.error,
|
|
26
23
|
}));
|
|
27
|
-
|
|
24
|
+
return;
|
|
28
25
|
}
|
|
29
26
|
// -- MODULE DOES NOT EXPORT ANYTHING --
|
|
30
27
|
if (importedModule.data?.default === undefined) {
|
|
31
28
|
moduleErrors.push(new ModuleHasNoExportsError({
|
|
32
29
|
module: module,
|
|
33
30
|
}));
|
|
34
|
-
|
|
31
|
+
return;
|
|
35
32
|
}
|
|
36
33
|
// -- CHECK IF MODULE IS SYNTACTIALLY VALID
|
|
37
34
|
const isValidModule = ModuleCompiler.Check(importedModule.data);
|
|
@@ -41,7 +38,7 @@ export const resolveModules = async (args) => {
|
|
|
41
38
|
module: module,
|
|
42
39
|
errors,
|
|
43
40
|
}));
|
|
44
|
-
|
|
41
|
+
return;
|
|
45
42
|
}
|
|
46
43
|
// -- VALIDATE MODULE SETTINGS
|
|
47
44
|
const result = validatedModuleSettings({
|
|
@@ -50,7 +47,7 @@ export const resolveModules = async (args) => {
|
|
|
50
47
|
});
|
|
51
48
|
if (result !== "isValid") {
|
|
52
49
|
moduleErrors.push(new ModuleSettingsAreInvalidError({ module: module, errors: result }));
|
|
53
|
-
|
|
50
|
+
return;
|
|
54
51
|
}
|
|
55
52
|
meta.push({
|
|
56
53
|
module: module,
|
|
@@ -66,6 +63,7 @@ export const resolveModules = async (args) => {
|
|
|
66
63
|
moduleErrors.push(new ModuleError(`Unimplemented module type ${importedModule.data.default.id}.The module has not been installed.`, { module: module }));
|
|
67
64
|
}
|
|
68
65
|
}
|
|
66
|
+
await Promise.all(args.settings.modules.map(resolveModule));
|
|
69
67
|
const resolvedPlugins = await resolvePlugins({
|
|
70
68
|
plugins: allPlugins,
|
|
71
69
|
settings: args.settings,
|
|
@@ -9,6 +9,7 @@ it("should return an error if a plugin cannot be imported", async () => {
|
|
|
9
9
|
modules: ["https://myplugin.com/index.js"],
|
|
10
10
|
};
|
|
11
11
|
const resolved = await resolveModules({
|
|
12
|
+
projectPath: "/project.inlang",
|
|
12
13
|
settings,
|
|
13
14
|
nodeishFs: {},
|
|
14
15
|
_import: () => {
|
|
@@ -58,7 +59,12 @@ it("should resolve plugins and message lint rules successfully", async () => {
|
|
|
58
59
|
}
|
|
59
60
|
};
|
|
60
61
|
// Call the function
|
|
61
|
-
const resolved = await resolveModules({
|
|
62
|
+
const resolved = await resolveModules({
|
|
63
|
+
projectPath: "/project.inlang",
|
|
64
|
+
settings,
|
|
65
|
+
_import,
|
|
66
|
+
nodeishFs: {},
|
|
67
|
+
});
|
|
62
68
|
// Assert results
|
|
63
69
|
expect(resolved.errors).toHaveLength(0);
|
|
64
70
|
// Check for the meta data of the plugin
|
|
@@ -81,7 +87,12 @@ it("should return an error if a module cannot be imported", async () => {
|
|
|
81
87
|
});
|
|
82
88
|
};
|
|
83
89
|
// Call the function
|
|
84
|
-
const resolved = await resolveModules({
|
|
90
|
+
const resolved = await resolveModules({
|
|
91
|
+
projectPath: "/project.inlang",
|
|
92
|
+
settings,
|
|
93
|
+
_import,
|
|
94
|
+
nodeishFs: {},
|
|
95
|
+
});
|
|
85
96
|
// Assert results
|
|
86
97
|
expect(resolved.errors[0]).toBeInstanceOf(ModuleImportError);
|
|
87
98
|
});
|
|
@@ -93,7 +104,12 @@ it("should return an error if a module does not export anything", async () => {
|
|
|
93
104
|
};
|
|
94
105
|
const _import = async () => ({});
|
|
95
106
|
// Call the function
|
|
96
|
-
const resolved = await resolveModules({
|
|
107
|
+
const resolved = await resolveModules({
|
|
108
|
+
projectPath: "/project.inlang",
|
|
109
|
+
settings,
|
|
110
|
+
_import,
|
|
111
|
+
nodeishFs: {},
|
|
112
|
+
});
|
|
97
113
|
// Assert results
|
|
98
114
|
expect(resolved.errors[0]).toBeInstanceOf(ModuleHasNoExportsError);
|
|
99
115
|
});
|
|
@@ -110,7 +126,12 @@ it("should return an error if a module exports an invalid plugin or lint rule",
|
|
|
110
126
|
description: { en: "Mock plugin description" },
|
|
111
127
|
},
|
|
112
128
|
});
|
|
113
|
-
const resolved = await resolveModules({
|
|
129
|
+
const resolved = await resolveModules({
|
|
130
|
+
projectPath: "/project.inlang",
|
|
131
|
+
settings,
|
|
132
|
+
_import,
|
|
133
|
+
nodeishFs: {},
|
|
134
|
+
});
|
|
114
135
|
expect(resolved.errors[0]).toBeInstanceOf(ModuleExportIsInvalidError);
|
|
115
136
|
});
|
|
116
137
|
it("should handle other unhandled errors during plugin resolution", async () => {
|
|
@@ -124,7 +145,12 @@ it("should handle other unhandled errors during plugin resolution", async () =>
|
|
|
124
145
|
throw new Error(errorMessage);
|
|
125
146
|
};
|
|
126
147
|
// Call the function
|
|
127
|
-
const resolved = await resolveModules({
|
|
148
|
+
const resolved = await resolveModules({
|
|
149
|
+
projectPath: "/project.inlang",
|
|
150
|
+
settings,
|
|
151
|
+
_import,
|
|
152
|
+
nodeishFs: {},
|
|
153
|
+
});
|
|
128
154
|
// Assert results
|
|
129
155
|
expect(resolved.errors[0]).toBeInstanceOf(ModuleError);
|
|
130
156
|
});
|
|
@@ -148,7 +174,12 @@ it("should return an error if a moduleSettings are invalid", async () => {
|
|
|
148
174
|
},
|
|
149
175
|
});
|
|
150
176
|
// Call the function
|
|
151
|
-
const resolved = await resolveModules({
|
|
177
|
+
const resolved = await resolveModules({
|
|
178
|
+
projectPath: "/project.inlang",
|
|
179
|
+
settings,
|
|
180
|
+
_import,
|
|
181
|
+
nodeishFs: {},
|
|
182
|
+
});
|
|
152
183
|
// Assert results
|
|
153
184
|
expect(resolved.errors[0]).toBeInstanceOf(ModuleSettingsAreInvalidError);
|
|
154
185
|
});
|
|
@@ -11,6 +11,7 @@ import type { resolveMessageLintRules } from "./message-lint-rules/resolveMessag
|
|
|
11
11
|
* Pass a custom `_import` function to override the default import function.
|
|
12
12
|
*/
|
|
13
13
|
export type ResolveModuleFunction = (args: {
|
|
14
|
+
projectPath: string;
|
|
14
15
|
settings: ProjectSettings;
|
|
15
16
|
nodeishFs: NodeishFilesystemSubset;
|
|
16
17
|
_import?: ImportFunction;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/resolve-modules/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAChE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAC5C,OAAO,KAAK,EACX,uBAAuB,EACvB,sBAAsB,EACtB,iBAAiB,EACjB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,KAAK,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAC7E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AACjD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,iDAAiD,CAAA;AAE9F;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,IAAI,EAAE;IAC1C,QAAQ,EAAE,eAAe,CAAA;IACzB,SAAS,EAAE,uBAAuB,CAAA;IAClC,OAAO,CAAC,EAAE,cAAc,CAAA;CACxB,KAAK,OAAO,CAAC;IACb;;;;;;;;OAQG;IACH,IAAI,EAAE,KAAK,CAAC;QACX;;;;WAIG;QACH,MAAM,EAAE,MAAM,CAAA;QACd;;WAEG;QACH,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,eAAe,CAAC,IAAI,CAAC,CAAA;KACxC,CAAC,CAAA;IACF;;OAEG;IACH,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;IACtB;;OAEG;IACH,gBAAgB,EAAE,KAAK,CAAC,eAAe,CAAC,CAAA;IACxC;;OAEG;IACH,iBAAiB,EAAE,iBAAiB,CAAA;IACpC;;;;;;;;OAQG;IACH,MAAM,EAAE,KAAK,CACV,uBAAuB,GACvB,iBAAiB,GACjB,OAAO,CAAC,UAAU,CAAC,sBAAsB,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAC7D,OAAO,CAAC,UAAU,CAAC,OAAO,uBAAuB,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CACvE,CAAA;CACD,CAAC,CAAA"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/resolve-modules/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAChE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAC5C,OAAO,KAAK,EACX,uBAAuB,EACvB,sBAAsB,EACtB,iBAAiB,EACjB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,KAAK,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAC7E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AACjD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,iDAAiD,CAAA;AAE9F;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,IAAI,EAAE;IAC1C,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,eAAe,CAAA;IACzB,SAAS,EAAE,uBAAuB,CAAA;IAClC,OAAO,CAAC,EAAE,cAAc,CAAA;CACxB,KAAK,OAAO,CAAC;IACb;;;;;;;;OAQG;IACH,IAAI,EAAE,KAAK,CAAC;QACX;;;;WAIG;QACH,MAAM,EAAE,MAAM,CAAA;QACd;;WAEG;QACH,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,eAAe,CAAC,IAAI,CAAC,CAAA;KACxC,CAAC,CAAA;IACF;;OAEG;IACH,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;IACtB;;OAEG;IACH,gBAAgB,EAAE,KAAK,CAAC,eAAe,CAAC,CAAA;IACxC;;OAEG;IACH,iBAAiB,EAAE,iBAAiB,CAAA;IACpC;;;;;;;;OAQG;IACH,MAAM,EAAE,KAAK,CACV,uBAAuB,GACvB,iBAAiB,GACjB,OAAO,CAAC,UAAU,CAAC,sBAAsB,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAC7D,OAAO,CAAC,UAAU,CAAC,OAAO,uBAAuB,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CACvE,CAAA;CACD,CAAC,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bundle.d.ts","sourceRoot":"","sources":["../../../../src/v2/mocks/plural/bundle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,eAAO,MAAM,YAAY,EAAE,
|
|
1
|
+
{"version":3,"file":"bundle.d.ts","sourceRoot":"","sources":["../../../../src/v2/mocks/plural/bundle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,eAAO,MAAM,YAAY,EAAE,aAiK1B,CAAA"}
|
|
@@ -18,6 +18,28 @@ export const pluralBundle = {
|
|
|
18
18
|
},
|
|
19
19
|
},
|
|
20
20
|
},
|
|
21
|
+
{
|
|
22
|
+
type: "input",
|
|
23
|
+
name: "count",
|
|
24
|
+
value: {
|
|
25
|
+
type: "expression",
|
|
26
|
+
arg: {
|
|
27
|
+
type: "variable",
|
|
28
|
+
name: "count",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
type: "input",
|
|
34
|
+
name: "projectCount",
|
|
35
|
+
value: {
|
|
36
|
+
type: "expression",
|
|
37
|
+
arg: {
|
|
38
|
+
type: "variable",
|
|
39
|
+
name: "projectCount",
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
21
43
|
],
|
|
22
44
|
selectors: [
|
|
23
45
|
{
|
|
@@ -8,7 +8,7 @@ describe("mock plural messageBundle", () => {
|
|
|
8
8
|
const messageBundle = pluralBundle;
|
|
9
9
|
expect(Value.Check(MessageBundle, messageBundle)).toBe(true);
|
|
10
10
|
expect(pluralBundle.messages.length).toBe(2);
|
|
11
|
-
expect(pluralBundle.messages[0].declarations.length).toBe(
|
|
11
|
+
expect(pluralBundle.messages[0].declarations.length).toBe(3);
|
|
12
12
|
expect(pluralBundle.messages[0].selectors.length).toBe(1);
|
|
13
13
|
expect(pluralBundle.messages[0].variants.length).toBe(3);
|
|
14
14
|
});
|
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.36.0",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
@@ -35,10 +35,10 @@
|
|
|
35
35
|
"solid-js": "1.6.12",
|
|
36
36
|
"throttle-debounce": "^5.0.0",
|
|
37
37
|
"@inlang/json-types": "1.1.0",
|
|
38
|
-
"@inlang/
|
|
38
|
+
"@inlang/language-tag": "1.5.1",
|
|
39
39
|
"@inlang/message-lint-rule": "1.4.7",
|
|
40
40
|
"@inlang/plugin": "2.4.13",
|
|
41
|
-
"@inlang/
|
|
41
|
+
"@inlang/message": "2.1.0",
|
|
42
42
|
"@inlang/module": "1.2.13",
|
|
43
43
|
"@inlang/project-settings": "2.4.2",
|
|
44
44
|
"@inlang/result": "1.1.0",
|
|
@@ -80,7 +80,7 @@ describe("createNewProject", () => {
|
|
|
80
80
|
await createNewProject({ projectPath, repo, projectSettings: defaultProjectSettings })
|
|
81
81
|
|
|
82
82
|
const project = await loadProject({ projectPath, repo })
|
|
83
|
-
expect(project.errors()
|
|
83
|
+
expect(project.errors()).toEqual([])
|
|
84
84
|
})
|
|
85
85
|
|
|
86
86
|
it("should create messages inside the project directory", async () => {
|
|
@@ -88,7 +88,7 @@ describe("createNewProject", () => {
|
|
|
88
88
|
const projectPath = "/test/project.inlang"
|
|
89
89
|
await createNewProject({ projectPath, repo, projectSettings: defaultProjectSettings })
|
|
90
90
|
const project = await loadProject({ projectPath, repo })
|
|
91
|
-
expect(project.errors()
|
|
91
|
+
expect(project.errors()).toEqual([])
|
|
92
92
|
|
|
93
93
|
const testMessage = createMessage("test", { en: "test message" })
|
|
94
94
|
project.query.messages.create({ data: testMessage })
|
package/src/createNewProject.ts
CHANGED
|
@@ -18,9 +18,12 @@ export async function createNewProject(args: {
|
|
|
18
18
|
if (await pathExists(args.projectPath, nodeishFs)) {
|
|
19
19
|
throw new Error(`projectPath already exists, received "${args.projectPath}"`)
|
|
20
20
|
}
|
|
21
|
-
await nodeishFs.mkdir(args.projectPath, { recursive: true })
|
|
22
|
-
|
|
23
21
|
const settingsText = JSON.stringify(args.projectSettings ?? defaultProjectSettings, undefined, 2)
|
|
24
22
|
|
|
25
|
-
await nodeishFs.
|
|
23
|
+
await nodeishFs.mkdir(args.projectPath, { recursive: true })
|
|
24
|
+
await Promise.all([
|
|
25
|
+
nodeishFs.writeFile(`${args.projectPath}/settings.json`, settingsText),
|
|
26
|
+
nodeishFs.writeFile(`${args.projectPath}/.gitignore`, "cache"),
|
|
27
|
+
nodeishFs.mkdir(`${args.projectPath}/cache/modules`, { recursive: true }),
|
|
28
|
+
])
|
|
26
29
|
}
|
package/src/index.ts
CHANGED
|
@@ -25,6 +25,13 @@ export {
|
|
|
25
25
|
PluginLoadMessagesError,
|
|
26
26
|
PluginSaveMessagesError,
|
|
27
27
|
} from "./errors.js"
|
|
28
|
+
export {
|
|
29
|
+
ModuleError,
|
|
30
|
+
ModuleHasNoExportsError,
|
|
31
|
+
ModuleImportError,
|
|
32
|
+
ModuleExportIsInvalidError,
|
|
33
|
+
ModuleSettingsAreInvalidError,
|
|
34
|
+
} from "./resolve-modules/errors.js"
|
|
28
35
|
|
|
29
36
|
export { randomHumanId } from "./storage/human-id/human-readable-id.js"
|
|
30
37
|
export { normalizeMessage } from "./storage/helper.js"
|
package/src/loadProject.ts
CHANGED
|
@@ -23,12 +23,14 @@ import { migrateIfOutdated } from "@inlang/project-settings/migration"
|
|
|
23
23
|
import { createNodeishFsWithAbsolutePaths } from "./createNodeishFsWithAbsolutePaths.js"
|
|
24
24
|
import { normalizePath } from "@lix-js/fs"
|
|
25
25
|
import { assertValidProjectPath } from "./validateProjectPath.js"
|
|
26
|
+
|
|
27
|
+
// Migrations
|
|
26
28
|
import { maybeMigrateToDirectory } from "./migrations/migrateToDirectory.js"
|
|
29
|
+
import { maybeCreateFirstProjectId } from "./migrations/maybeCreateFirstProjectId.js"
|
|
30
|
+
import { maybeAddModuleCache } from "./migrations/maybeAddModuleCache.js"
|
|
27
31
|
|
|
28
32
|
import type { Repository } from "@lix-js/client"
|
|
29
33
|
|
|
30
|
-
import { maybeCreateFirstProjectId } from "./migrations/maybeCreateFirstProjectId.js"
|
|
31
|
-
|
|
32
34
|
import { capture } from "./telemetry/capture.js"
|
|
33
35
|
import { identifyProject } from "./telemetry/groupIdentify.js"
|
|
34
36
|
|
|
@@ -74,6 +76,7 @@ export async function loadProject(args: {
|
|
|
74
76
|
|
|
75
77
|
await maybeMigrateToDirectory({ nodeishFs, projectPath })
|
|
76
78
|
await maybeCreateFirstProjectId({ projectPath, repo: args.repo })
|
|
79
|
+
await maybeAddModuleCache({ projectPath, repo: args.repo })
|
|
77
80
|
|
|
78
81
|
// -- load project ------------------------------------------------------
|
|
79
82
|
|
|
@@ -155,7 +158,12 @@ export async function loadProject(args: {
|
|
|
155
158
|
const _settings = settings()
|
|
156
159
|
if (!_settings) return
|
|
157
160
|
|
|
158
|
-
resolveModules({
|
|
161
|
+
resolveModules({
|
|
162
|
+
settings: _settings,
|
|
163
|
+
nodeishFs,
|
|
164
|
+
_import: args._import,
|
|
165
|
+
projectPath,
|
|
166
|
+
})
|
|
159
167
|
.then((resolvedModules) => {
|
|
160
168
|
setResolvedModules(resolvedModules)
|
|
161
169
|
})
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { Repository } from "@lix-js/client"
|
|
2
|
+
import type { NodeishFilesystem } from "@lix-js/fs"
|
|
3
|
+
|
|
4
|
+
const EXPECTED_IGNORES = ["cache"]
|
|
5
|
+
|
|
6
|
+
export async function maybeAddModuleCache(args: {
|
|
7
|
+
projectPath: string
|
|
8
|
+
repo?: Repository
|
|
9
|
+
}): Promise<void> {
|
|
10
|
+
if (args.repo === undefined) return
|
|
11
|
+
|
|
12
|
+
const projectExists = await directoryExists(args.projectPath, args.repo.nodeishFs)
|
|
13
|
+
if (!projectExists) return
|
|
14
|
+
|
|
15
|
+
const gitignorePath = args.projectPath + "/.gitignore"
|
|
16
|
+
const moduleCache = args.projectPath + "/cache/modules/"
|
|
17
|
+
|
|
18
|
+
const gitignoreExists = await fileExists(gitignorePath, args.repo.nodeishFs)
|
|
19
|
+
const moduleCacheExists = await directoryExists(moduleCache, args.repo.nodeishFs)
|
|
20
|
+
|
|
21
|
+
if (gitignoreExists) {
|
|
22
|
+
// non-destructively add any missing ignores
|
|
23
|
+
const gitignore = await args.repo.nodeishFs.readFile(gitignorePath, { encoding: "utf-8" })
|
|
24
|
+
const missingIgnores = EXPECTED_IGNORES.filter((ignore) => !gitignore.includes(ignore))
|
|
25
|
+
if (missingIgnores.length > 0) {
|
|
26
|
+
await args.repo.nodeishFs.appendFile(gitignorePath, "\n" + missingIgnores.join("\n"))
|
|
27
|
+
}
|
|
28
|
+
} else {
|
|
29
|
+
await args.repo.nodeishFs.writeFile(gitignorePath, EXPECTED_IGNORES.join("\n"))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!moduleCacheExists) {
|
|
33
|
+
await args.repo.nodeishFs.mkdir(moduleCache, { recursive: true })
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function fileExists(path: string, nodeishFs: NodeishFilesystem): Promise<boolean> {
|
|
38
|
+
try {
|
|
39
|
+
const stat = await nodeishFs.stat(path)
|
|
40
|
+
return stat.isFile()
|
|
41
|
+
} catch {
|
|
42
|
+
return false
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function directoryExists(path: string, nodeishFs: NodeishFilesystem): Promise<boolean> {
|
|
47
|
+
try {
|
|
48
|
+
const stat = await nodeishFs.stat(path)
|
|
49
|
+
return stat.isDirectory()
|
|
50
|
+
} catch {
|
|
51
|
+
return false
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { NodeishFilesystemSubset } from "@inlang/plugin"
|
|
2
|
+
import { type Result, tryCatch } from "@inlang/result"
|
|
3
|
+
|
|
4
|
+
function escape(url: string) {
|
|
5
|
+
// collect the bytes of the UTF-8 representation
|
|
6
|
+
const bytes = new TextEncoder().encode(url)
|
|
7
|
+
|
|
8
|
+
// 64-bit FNV1a hash to make the file-names shorter
|
|
9
|
+
// https://en.wikipedia.org/wiki/FNV-1a
|
|
10
|
+
const hash = bytes.reduce(
|
|
11
|
+
(hash, byte) => BigInt.asUintN(64, (hash ^ BigInt(byte)) * 1_099_511_628_211n),
|
|
12
|
+
14_695_981_039_346_656_037n
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
return hash.toString(36)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function readModuleFromCache(
|
|
19
|
+
moduleURI: string,
|
|
20
|
+
projectPath: string,
|
|
21
|
+
readFile: NodeishFilesystemSubset["readFile"]
|
|
22
|
+
): Promise<Result<string, Error>> {
|
|
23
|
+
const moduleHash = escape(moduleURI)
|
|
24
|
+
const filePath = projectPath + `/cache/modules/${moduleHash}`
|
|
25
|
+
|
|
26
|
+
return await tryCatch(async () => await readFile(filePath, { encoding: "utf-8" }))
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function writeModuleToCache(
|
|
30
|
+
moduleURI: string,
|
|
31
|
+
moduleContent: string,
|
|
32
|
+
projectPath: string,
|
|
33
|
+
writeFile: NodeishFilesystemSubset["writeFile"]
|
|
34
|
+
): Promise<void> {
|
|
35
|
+
const moduleHash = escape(moduleURI)
|
|
36
|
+
const filePath = projectPath + `/cache/modules/${moduleHash}`
|
|
37
|
+
await writeFile(filePath, moduleContent)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Implements a "Network-First" caching strategy.
|
|
42
|
+
*/
|
|
43
|
+
export function withCache(
|
|
44
|
+
moduleLoader: (uri: string) => Promise<string>,
|
|
45
|
+
projectPath: string,
|
|
46
|
+
nodeishFs: Pick<NodeishFilesystemSubset, "readFile" | "writeFile">
|
|
47
|
+
): (uri: string) => Promise<string> {
|
|
48
|
+
return async (uri: string) => {
|
|
49
|
+
const cachePromise = readModuleFromCache(uri, projectPath, nodeishFs.readFile)
|
|
50
|
+
const networkResult = await tryCatch(async () => await moduleLoader(uri))
|
|
51
|
+
|
|
52
|
+
if (networkResult.error) {
|
|
53
|
+
const cacheResult = await cachePromise
|
|
54
|
+
if (!cacheResult.error) return cacheResult.data
|
|
55
|
+
else throw networkResult.error
|
|
56
|
+
} else {
|
|
57
|
+
const moduleAsText = networkResult.data
|
|
58
|
+
await writeModuleToCache(uri, moduleAsText, projectPath, nodeishFs.writeFile)
|
|
59
|
+
return moduleAsText
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -6,6 +6,9 @@ import { createImport } from "./import.js"
|
|
|
6
6
|
|
|
7
7
|
describe("$import", async () => {
|
|
8
8
|
const fs = createNodeishMemoryFs()
|
|
9
|
+
|
|
10
|
+
await fs.mkdir("./project.inlang/cache/modules", { recursive: true })
|
|
11
|
+
|
|
9
12
|
await fs.writeFile(
|
|
10
13
|
"./mock-module.js",
|
|
11
14
|
`
|
|
@@ -26,7 +29,8 @@ describe("$import", async () => {
|
|
|
26
29
|
`
|
|
27
30
|
)
|
|
28
31
|
|
|
29
|
-
const _import = createImport({
|
|
32
|
+
const _import = createImport("/project.inlang", {
|
|
33
|
+
writeFile: fs.writeFile,
|
|
30
34
|
readFile: fs.readFile,
|
|
31
35
|
})
|
|
32
36
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import dedent from "dedent"
|
|
2
2
|
import type { NodeishFilesystemSubset } from "@inlang/plugin"
|
|
3
3
|
import { ModuleImportError } from "./errors.js"
|
|
4
|
+
import { withCache } from "./cache.js"
|
|
5
|
+
import { tryCatch } from "@inlang/result"
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* Importing ES modules either from a local path, or from a url.
|
|
@@ -19,32 +21,21 @@ export type ImportFunction = (uri: string) => Promise<any>
|
|
|
19
21
|
* const $import = createImport({ readFile: fs.readFile, fetch });
|
|
20
22
|
* const module = await _import('./some-module.js');
|
|
21
23
|
*/
|
|
22
|
-
export function createImport(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return (uri: string) => $import(uri, args)
|
|
24
|
+
export function createImport(
|
|
25
|
+
projectPath: string,
|
|
26
|
+
nodeishFs: Pick<NodeishFilesystemSubset, "readFile" | "writeFile">
|
|
27
|
+
) {
|
|
28
|
+
return (uri: string) => $import(uri, projectPath, nodeishFs)
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
async function $import(
|
|
31
32
|
uri: string,
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
* Required to import from a local path.
|
|
35
|
-
*/
|
|
36
|
-
readFile: NodeishFilesystemSubset["readFile"]
|
|
37
|
-
}
|
|
33
|
+
projectPath: string,
|
|
34
|
+
nodeishFs: Pick<NodeishFilesystemSubset, "readFile" | "writeFile">
|
|
38
35
|
): Promise<any> {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
moduleAsText = await (await fetch(uri)).text()
|
|
43
|
-
} else {
|
|
44
|
-
moduleAsText = await options.readFile(uri, {
|
|
45
|
-
encoding: "utf-8",
|
|
46
|
-
})
|
|
47
|
-
}
|
|
36
|
+
const moduleAsText = uri.startsWith("http")
|
|
37
|
+
? await withCache(readModuleFromCDN, projectPath, nodeishFs)(uri)
|
|
38
|
+
: await readModulefromDisk(uri, nodeishFs.readFile)
|
|
48
39
|
|
|
49
40
|
const moduleWithMimeType = "data:application/javascript," + encodeURIComponent(moduleAsText)
|
|
50
41
|
|
|
@@ -53,11 +44,90 @@ async function $import(
|
|
|
53
44
|
} catch (error) {
|
|
54
45
|
if (error instanceof SyntaxError && uri.includes("jsdelivr")) {
|
|
55
46
|
error.message += dedent`\n\n
|
|
56
|
-
Are you sure that the file exists on JSDelivr?
|
|
47
|
+
Are you sure that the file exists on JSDelivr?
|
|
57
48
|
|
|
58
|
-
The error indicates that the imported file does not exist on JSDelivr. For non-existent files, JSDelivr returns a 404 text that JS cannot parse as a module and throws a SyntaxError
|
|
59
|
-
`
|
|
49
|
+
The error indicates that the imported file does not exist on JSDelivr. For non-existent files, JSDelivr returns a 404 text that JS cannot parse as a module and throws a SyntaxError.`
|
|
60
50
|
}
|
|
61
51
|
throw new ModuleImportError({ module: uri, cause: error as Error })
|
|
62
52
|
}
|
|
63
53
|
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Tries to read the module from disk
|
|
57
|
+
* @throws {ModuleImportError}
|
|
58
|
+
*/
|
|
59
|
+
async function readModulefromDisk(
|
|
60
|
+
uri: string,
|
|
61
|
+
readFile: NodeishFilesystemSubset["readFile"]
|
|
62
|
+
): Promise<string> {
|
|
63
|
+
try {
|
|
64
|
+
return await readFile(uri, { encoding: "utf-8" })
|
|
65
|
+
} catch (error) {
|
|
66
|
+
throw new ModuleImportError({ module: uri, cause: error as Error })
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Reads a module from a CDN.
|
|
72
|
+
* Tries to read local cache first.
|
|
73
|
+
*
|
|
74
|
+
* @param uri A valid URL
|
|
75
|
+
* @throws {ModuleImportError}
|
|
76
|
+
*/
|
|
77
|
+
async function readModuleFromCDN(uri: string): Promise<string> {
|
|
78
|
+
if (!isValidUrl(uri))
|
|
79
|
+
throw new ModuleImportError({ module: uri, cause: new Error("Malformed URL") })
|
|
80
|
+
|
|
81
|
+
const result = await tryCatch(async () => await fetch(uri))
|
|
82
|
+
if (result.error) {
|
|
83
|
+
throw new ModuleImportError({
|
|
84
|
+
module: uri,
|
|
85
|
+
cause: result.error,
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const response = result.data
|
|
90
|
+
if (!response.ok) {
|
|
91
|
+
throw new ModuleImportError({
|
|
92
|
+
module: uri,
|
|
93
|
+
cause: new Error(
|
|
94
|
+
`Failed to fetch module. HTTP status: ${response.status}, Message: ${response.statusText}`
|
|
95
|
+
),
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const JS_CONTENT_TYPES = [
|
|
100
|
+
"application/javascript",
|
|
101
|
+
"text/javascript",
|
|
102
|
+
"application/x-javascript",
|
|
103
|
+
"text/x-javascript",
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
// if there is no content-type header, assume it's a JavaScript module & hope for the best
|
|
107
|
+
const contentType = response.headers.get("content-type")?.toLowerCase()
|
|
108
|
+
if (contentType && !JS_CONTENT_TYPES.some((knownType) => contentType.includes(knownType))) {
|
|
109
|
+
throw new ModuleImportError({
|
|
110
|
+
module: uri,
|
|
111
|
+
cause: new Error(`Server responded with ${contentType} insetad of a JavaScript module`),
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return await response.text()
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function isValidUrl(url: string) {
|
|
119
|
+
// This dance is necessary to both support a fallback in case URL.canParse
|
|
120
|
+
// is not present (like in vitest), and also appease typescript
|
|
121
|
+
const URLConstructor = URL
|
|
122
|
+
if ("canParse" in URL) {
|
|
123
|
+
return URL.canParse(url)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
new URLConstructor(url)
|
|
128
|
+
return true
|
|
129
|
+
} catch (e) {
|
|
130
|
+
console.warn(`Invalid URL: ${url}`)
|
|
131
|
+
return false
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -22,6 +22,7 @@ it("should return an error if a plugin cannot be imported", async () => {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
const resolved = await resolveModules({
|
|
25
|
+
projectPath: "/project.inlang",
|
|
25
26
|
settings,
|
|
26
27
|
nodeishFs: {} as any,
|
|
27
28
|
_import: () => {
|
|
@@ -78,7 +79,12 @@ it("should resolve plugins and message lint rules successfully", async () => {
|
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
// Call the function
|
|
81
|
-
const resolved = await resolveModules({
|
|
82
|
+
const resolved = await resolveModules({
|
|
83
|
+
projectPath: "/project.inlang",
|
|
84
|
+
settings,
|
|
85
|
+
_import,
|
|
86
|
+
nodeishFs: {} as any,
|
|
87
|
+
})
|
|
82
88
|
|
|
83
89
|
// Assert results
|
|
84
90
|
expect(resolved.errors).toHaveLength(0)
|
|
@@ -105,7 +111,12 @@ it("should return an error if a module cannot be imported", async () => {
|
|
|
105
111
|
}
|
|
106
112
|
|
|
107
113
|
// Call the function
|
|
108
|
-
const resolved = await resolveModules({
|
|
114
|
+
const resolved = await resolveModules({
|
|
115
|
+
projectPath: "/project.inlang",
|
|
116
|
+
settings,
|
|
117
|
+
_import,
|
|
118
|
+
nodeishFs: {} as any,
|
|
119
|
+
})
|
|
109
120
|
|
|
110
121
|
// Assert results
|
|
111
122
|
expect(resolved.errors[0]).toBeInstanceOf(ModuleImportError)
|
|
@@ -121,7 +132,12 @@ it("should return an error if a module does not export anything", async () => {
|
|
|
121
132
|
const _import = async () => ({})
|
|
122
133
|
|
|
123
134
|
// Call the function
|
|
124
|
-
const resolved = await resolveModules({
|
|
135
|
+
const resolved = await resolveModules({
|
|
136
|
+
projectPath: "/project.inlang",
|
|
137
|
+
settings,
|
|
138
|
+
_import,
|
|
139
|
+
nodeishFs: {} as any,
|
|
140
|
+
})
|
|
125
141
|
|
|
126
142
|
// Assert results
|
|
127
143
|
expect(resolved.errors[0]).toBeInstanceOf(ModuleHasNoExportsError)
|
|
@@ -142,7 +158,12 @@ it("should return an error if a module exports an invalid plugin or lint rule",
|
|
|
142
158
|
},
|
|
143
159
|
} satisfies InlangModule)
|
|
144
160
|
|
|
145
|
-
const resolved = await resolveModules({
|
|
161
|
+
const resolved = await resolveModules({
|
|
162
|
+
projectPath: "/project.inlang",
|
|
163
|
+
settings,
|
|
164
|
+
_import,
|
|
165
|
+
nodeishFs: {} as any,
|
|
166
|
+
})
|
|
146
167
|
expect(resolved.errors[0]).toBeInstanceOf(ModuleExportIsInvalidError)
|
|
147
168
|
})
|
|
148
169
|
|
|
@@ -159,7 +180,12 @@ it("should handle other unhandled errors during plugin resolution", async () =>
|
|
|
159
180
|
}
|
|
160
181
|
|
|
161
182
|
// Call the function
|
|
162
|
-
const resolved = await resolveModules({
|
|
183
|
+
const resolved = await resolveModules({
|
|
184
|
+
projectPath: "/project.inlang",
|
|
185
|
+
settings,
|
|
186
|
+
_import,
|
|
187
|
+
nodeishFs: {} as any,
|
|
188
|
+
})
|
|
163
189
|
|
|
164
190
|
// Assert results
|
|
165
191
|
expect(resolved.errors[0]).toBeInstanceOf(ModuleError)
|
|
@@ -186,7 +212,12 @@ it("should return an error if a moduleSettings are invalid", async () => {
|
|
|
186
212
|
})
|
|
187
213
|
|
|
188
214
|
// Call the function
|
|
189
|
-
const resolved = await resolveModules({
|
|
215
|
+
const resolved = await resolveModules({
|
|
216
|
+
projectPath: "/project.inlang",
|
|
217
|
+
settings,
|
|
218
|
+
_import,
|
|
219
|
+
nodeishFs: {} as any,
|
|
220
|
+
})
|
|
190
221
|
|
|
191
222
|
// Assert results
|
|
192
223
|
expect(resolved.errors[0]).toBeInstanceOf(ModuleSettingsAreInvalidError)
|
|
@@ -9,32 +9,27 @@ import {
|
|
|
9
9
|
} from "./errors.js"
|
|
10
10
|
import { tryCatch } from "@inlang/result"
|
|
11
11
|
import { resolveMessageLintRules } from "./message-lint-rules/resolveMessageLintRules.js"
|
|
12
|
-
import type { Plugin } from "@inlang/plugin"
|
|
13
12
|
import { createImport } from "./import.js"
|
|
14
|
-
import type { MessageLintRule } from "@inlang/message-lint-rule"
|
|
15
13
|
import { resolvePlugins } from "./plugins/resolvePlugins.js"
|
|
16
14
|
import { TypeCompiler } from "@sinclair/typebox/compiler"
|
|
17
15
|
import { validatedModuleSettings } from "./validatedModuleSettings.js"
|
|
16
|
+
import type { Plugin } from "@inlang/plugin"
|
|
17
|
+
import type { MessageLintRule } from "@inlang/message-lint-rule"
|
|
18
18
|
|
|
19
19
|
const ModuleCompiler = TypeCompiler.Compile(InlangModule)
|
|
20
20
|
|
|
21
21
|
export const resolveModules: ResolveModuleFunction = async (args) => {
|
|
22
|
-
const _import = args._import ?? createImport(
|
|
23
|
-
const moduleErrors: Array<ModuleError> = []
|
|
22
|
+
const _import = args._import ?? createImport(args.projectPath, args.nodeishFs)
|
|
24
23
|
|
|
25
24
|
const allPlugins: Array<Plugin> = []
|
|
26
25
|
const allMessageLintRules: Array<MessageLintRule> = []
|
|
27
|
-
|
|
28
26
|
const meta: Awaited<ReturnType<ResolveModuleFunction>>["meta"] = []
|
|
27
|
+
const moduleErrors: Array<ModuleError> = []
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* -------------- BEGIN SETUP --------------
|
|
33
|
-
*/
|
|
34
|
-
|
|
29
|
+
async function resolveModule(module: string) {
|
|
35
30
|
const importedModule = await tryCatch<InlangModule>(() => _import(module))
|
|
36
|
-
// -- IMPORT MODULE --
|
|
37
31
|
|
|
32
|
+
// -- FAILED TO IMPORT --
|
|
38
33
|
if (importedModule.error) {
|
|
39
34
|
moduleErrors.push(
|
|
40
35
|
new ModuleImportError({
|
|
@@ -42,22 +37,20 @@ export const resolveModules: ResolveModuleFunction = async (args) => {
|
|
|
42
37
|
cause: importedModule.error as Error,
|
|
43
38
|
})
|
|
44
39
|
)
|
|
45
|
-
|
|
40
|
+
return
|
|
46
41
|
}
|
|
47
42
|
|
|
48
43
|
// -- MODULE DOES NOT EXPORT ANYTHING --
|
|
49
|
-
|
|
50
44
|
if (importedModule.data?.default === undefined) {
|
|
51
45
|
moduleErrors.push(
|
|
52
46
|
new ModuleHasNoExportsError({
|
|
53
47
|
module: module,
|
|
54
48
|
})
|
|
55
49
|
)
|
|
56
|
-
|
|
50
|
+
return
|
|
57
51
|
}
|
|
58
52
|
|
|
59
53
|
// -- CHECK IF MODULE IS SYNTACTIALLY VALID
|
|
60
|
-
|
|
61
54
|
const isValidModule = ModuleCompiler.Check(importedModule.data)
|
|
62
55
|
if (isValidModule === false) {
|
|
63
56
|
const errors = [...ModuleCompiler.Errors(importedModule.data)]
|
|
@@ -68,7 +61,7 @@ export const resolveModules: ResolveModuleFunction = async (args) => {
|
|
|
68
61
|
})
|
|
69
62
|
)
|
|
70
63
|
|
|
71
|
-
|
|
64
|
+
return
|
|
72
65
|
}
|
|
73
66
|
|
|
74
67
|
// -- VALIDATE MODULE SETTINGS
|
|
@@ -79,7 +72,7 @@ export const resolveModules: ResolveModuleFunction = async (args) => {
|
|
|
79
72
|
})
|
|
80
73
|
if (result !== "isValid") {
|
|
81
74
|
moduleErrors.push(new ModuleSettingsAreInvalidError({ module: module, errors: result }))
|
|
82
|
-
|
|
75
|
+
return
|
|
83
76
|
}
|
|
84
77
|
|
|
85
78
|
meta.push({
|
|
@@ -100,6 +93,8 @@ export const resolveModules: ResolveModuleFunction = async (args) => {
|
|
|
100
93
|
)
|
|
101
94
|
}
|
|
102
95
|
}
|
|
96
|
+
|
|
97
|
+
await Promise.all(args.settings.modules.map(resolveModule))
|
|
103
98
|
const resolvedPlugins = await resolvePlugins({
|
|
104
99
|
plugins: allPlugins,
|
|
105
100
|
settings: args.settings,
|
|
@@ -16,6 +16,7 @@ import type { resolveMessageLintRules } from "./message-lint-rules/resolveMessag
|
|
|
16
16
|
* Pass a custom `_import` function to override the default import function.
|
|
17
17
|
*/
|
|
18
18
|
export type ResolveModuleFunction = (args: {
|
|
19
|
+
projectPath: string
|
|
19
20
|
settings: ProjectSettings
|
|
20
21
|
nodeishFs: NodeishFilesystemSubset
|
|
21
22
|
_import?: ImportFunction
|
|
@@ -11,7 +11,7 @@ describe("mock plural messageBundle", () => {
|
|
|
11
11
|
expect(Value.Check(MessageBundle, messageBundle)).toBe(true)
|
|
12
12
|
|
|
13
13
|
expect(pluralBundle.messages.length).toBe(2)
|
|
14
|
-
expect(pluralBundle.messages[0]!.declarations.length).toBe(
|
|
14
|
+
expect(pluralBundle.messages[0]!.declarations.length).toBe(3)
|
|
15
15
|
expect(pluralBundle.messages[0]!.selectors.length).toBe(1)
|
|
16
16
|
expect(pluralBundle.messages[0]!.variants.length).toBe(3)
|
|
17
17
|
})
|
|
@@ -20,6 +20,28 @@ export const pluralBundle: MessageBundle = {
|
|
|
20
20
|
},
|
|
21
21
|
},
|
|
22
22
|
},
|
|
23
|
+
{
|
|
24
|
+
type: "input",
|
|
25
|
+
name: "count",
|
|
26
|
+
value: {
|
|
27
|
+
type: "expression",
|
|
28
|
+
arg: {
|
|
29
|
+
type: "variable",
|
|
30
|
+
name: "count",
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
type: "input",
|
|
36
|
+
name: "projectCount",
|
|
37
|
+
value: {
|
|
38
|
+
type: "expression",
|
|
39
|
+
arg: {
|
|
40
|
+
type: "variable",
|
|
41
|
+
name: "projectCount",
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
23
45
|
],
|
|
24
46
|
selectors: [
|
|
25
47
|
{
|