@inlang/sdk 2.0.0 → 2.0.1
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/json-schema/settings.d.ts +8 -10
- package/dist/json-schema/settings.d.ts.map +1 -1
- package/dist/json-schema/settings.js +4 -27
- package/dist/json-schema/settings.js.map +1 -1
- package/dist/json-schema/settings.test.d.ts +2 -0
- package/dist/json-schema/settings.test.d.ts.map +1 -0
- package/dist/json-schema/settings.test.js +26 -0
- package/dist/json-schema/settings.test.js.map +1 -0
- package/dist/plugin/importPlugins.test.js +2 -3
- package/dist/plugin/importPlugins.test.js.map +1 -1
- package/dist/project/loadProjectFromDirectory.d.ts +2 -1
- package/dist/project/loadProjectFromDirectory.d.ts.map +1 -1
- package/dist/project/loadProjectFromDirectory.js +40 -52
- package/dist/project/loadProjectFromDirectory.js.map +1 -1
- package/dist/project/loadProjectFromDirectory.test.js +59 -3
- package/dist/project/loadProjectFromDirectory.test.js.map +1 -1
- package/dist/services/env-variables/index.js +3 -3
- package/dist/services/env-variables/index.js.map +1 -1
- package/package.json +4 -5
- package/src/json-schema/settings.test.ts +26 -0
- package/src/json-schema/settings.ts +3 -36
- package/src/plugin/importPlugins.test.ts +0 -1
- package/src/project/loadProjectFromDirectory.test.ts +69 -1
- package/src/project/loadProjectFromDirectory.ts +48 -97
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { TypeCompiler } from "@sinclair/typebox/compiler";
|
|
2
|
+
import { ProjectSettings } from "./settings.js";
|
|
3
|
+
import { test, expect } from "vitest";
|
|
4
|
+
|
|
5
|
+
const C = TypeCompiler.Compile(ProjectSettings);
|
|
6
|
+
|
|
7
|
+
test("valid settings file", () => {
|
|
8
|
+
const settings: ProjectSettings = {
|
|
9
|
+
baseLocale: "en",
|
|
10
|
+
locales: ["en", "de"],
|
|
11
|
+
modules: [
|
|
12
|
+
"https://cdn.jsdelivr.net/npm/@inlang/plugin-i18next@3/dist/index.js",
|
|
13
|
+
"https://cdn.jsdelivr.net/npm/@inlang/plugin-csv@1/dist/index.js",
|
|
14
|
+
],
|
|
15
|
+
"plugin.something": {
|
|
16
|
+
key: "value",
|
|
17
|
+
nested: {
|
|
18
|
+
moreNested: {},
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const errors = [...C.Errors(settings)];
|
|
24
|
+
|
|
25
|
+
expect(errors).toEqual([]);
|
|
26
|
+
});
|
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type Static,
|
|
3
|
-
Type,
|
|
4
|
-
type TTemplateLiteral,
|
|
5
|
-
type TLiteral,
|
|
6
|
-
} from "@sinclair/typebox";
|
|
1
|
+
import { type Static, Type } from "@sinclair/typebox";
|
|
7
2
|
|
|
8
3
|
const SDKSettings = Type.Object({
|
|
9
4
|
// TODO SDK-v2 SETTINGS do we need to generate a settings v2 schema?
|
|
@@ -59,10 +54,6 @@ const SDKSettings = Type.Object({
|
|
|
59
54
|
description: "The module must end with `.js`.",
|
|
60
55
|
pattern: ".*\\.js$",
|
|
61
56
|
}),
|
|
62
|
-
Type.String({
|
|
63
|
-
description: "The module can only contain a major version number.",
|
|
64
|
-
pattern: "^(?!.*@\\d\\.)[^]*$",
|
|
65
|
-
}),
|
|
66
57
|
]),
|
|
67
58
|
{
|
|
68
59
|
uniqueItems: true,
|
|
@@ -100,30 +91,6 @@ const SDKSettings = Type.Object({
|
|
|
100
91
|
*/
|
|
101
92
|
});
|
|
102
93
|
|
|
103
|
-
/**
|
|
104
|
-
* Settings defined via apps, plugins, lint rules, etc.
|
|
105
|
-
*
|
|
106
|
-
* Using external settings to only allow `plugin.*` keys
|
|
107
|
-
* and don't block the SDK from adding new settings.
|
|
108
|
-
*/
|
|
109
|
-
const ExternalSettings = Type.Record(
|
|
110
|
-
Type.String({
|
|
111
|
-
pattern: `^((plugin)\\.([a-z][a-zA-Z0-9]*(?:[A-Z][a-z0-9]*)*)|\\$schema|${
|
|
112
|
-
// pattern must include the settings properties
|
|
113
|
-
Object.keys(SDKSettings.properties)
|
|
114
|
-
.map((key) => key.replaceAll(".", "\\."))
|
|
115
|
-
.join("|")
|
|
116
|
-
})$`,
|
|
117
|
-
description: "The key must be conform to `plugin.*`.",
|
|
118
|
-
examples: ["plugin.csv-importer", "plugin.i18next"],
|
|
119
|
-
}) as unknown as TTemplateLiteral<[TLiteral<`${"plugin"}.${string}`>]>,
|
|
120
|
-
// Using JSON (array and object) as a workaround to make the
|
|
121
|
-
// intersection between `InternalSettings`, which contains an array,
|
|
122
|
-
// and `ExternalSettings` which are objects possible
|
|
123
|
-
Type.Record(Type.String(), Type.Any()),
|
|
124
|
-
{ description: "Settings defined by apps, plugins, etc." }
|
|
125
|
-
);
|
|
126
|
-
|
|
127
94
|
export type ProjectSettings = Omit<
|
|
128
95
|
Static<typeof ProjectSettings>,
|
|
129
96
|
"languageTags" | "sourceLanguageTag"
|
|
@@ -134,5 +101,5 @@ export type ProjectSettings = Omit<
|
|
|
134
101
|
languageTags?: string[];
|
|
135
102
|
/** @deprecated This will soon be replaced by `Lix Validation Rules` */
|
|
136
103
|
messageLintRuleLevels?: Record<string, "error" | "warning">;
|
|
137
|
-
}
|
|
138
|
-
export const ProjectSettings =
|
|
104
|
+
} & Record<string, any>;
|
|
105
|
+
export const ProjectSettings = SDKSettings;
|
|
@@ -658,7 +658,6 @@ test("it should pass toBeImportedMetadata on import", async () => {
|
|
|
658
658
|
expect.objectContaining({
|
|
659
659
|
files: [
|
|
660
660
|
expect.objectContaining({
|
|
661
|
-
name: "en.json",
|
|
662
661
|
toBeImportedFilesMetadata: {
|
|
663
662
|
foo: "bar",
|
|
664
663
|
},
|
|
@@ -852,3 +851,72 @@ test("it can import plugins via http", async () => {
|
|
|
852
851
|
"expecting the plugin to be cached"
|
|
853
852
|
).toBe(true);
|
|
854
853
|
});
|
|
854
|
+
|
|
855
|
+
test("plugins that provide both loadMessages and importFiles should be allowed and the importFiles should be called", async () => {
|
|
856
|
+
const mockRepo = {
|
|
857
|
+
"/project.inlang/settings.json": JSON.stringify({
|
|
858
|
+
baseLocale: "en",
|
|
859
|
+
locales: ["en"],
|
|
860
|
+
modules: [],
|
|
861
|
+
} satisfies ProjectSettings),
|
|
862
|
+
};
|
|
863
|
+
|
|
864
|
+
const fs = Volume.fromJSON(mockRepo);
|
|
865
|
+
|
|
866
|
+
const mockPlugin: InlangPlugin = {
|
|
867
|
+
key: "mock-plugin",
|
|
868
|
+
loadMessages: vi.fn(async () => []),
|
|
869
|
+
importFiles: vi.fn(async () => {
|
|
870
|
+
return { bundles: [], messages: [], variants: [] };
|
|
871
|
+
}),
|
|
872
|
+
};
|
|
873
|
+
|
|
874
|
+
const project = await loadProjectFromDirectory({
|
|
875
|
+
fs: fs as any,
|
|
876
|
+
path: "/project.inlang",
|
|
877
|
+
providePlugins: [mockPlugin],
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
expect(mockPlugin.importFiles).toHaveBeenCalled();
|
|
881
|
+
expect(mockPlugin.loadMessages).not.toHaveBeenCalled();
|
|
882
|
+
});
|
|
883
|
+
|
|
884
|
+
test("providing multiple plugins that have legacy loadMessages and saveMessages function should be possible if they have import/export functions", async () => {
|
|
885
|
+
const mockRepo = {
|
|
886
|
+
"/project.inlang/settings.json": JSON.stringify({
|
|
887
|
+
baseLocale: "en",
|
|
888
|
+
locales: ["en"],
|
|
889
|
+
modules: [],
|
|
890
|
+
} satisfies ProjectSettings),
|
|
891
|
+
};
|
|
892
|
+
|
|
893
|
+
const fs = Volume.fromJSON(mockRepo);
|
|
894
|
+
|
|
895
|
+
const mockPlugin1: InlangPlugin = {
|
|
896
|
+
key: "mock-plugin1",
|
|
897
|
+
loadMessages: vi.fn(async () => []),
|
|
898
|
+
importFiles: vi.fn(async () => {
|
|
899
|
+
return { bundles: [], messages: [], variants: [] };
|
|
900
|
+
}),
|
|
901
|
+
};
|
|
902
|
+
|
|
903
|
+
const mockPlugin2: InlangPlugin = {
|
|
904
|
+
key: "mock-plugin2",
|
|
905
|
+
loadMessages: vi.fn(async () => []),
|
|
906
|
+
importFiles: vi.fn(async () => {
|
|
907
|
+
return { bundles: [], messages: [], variants: [] };
|
|
908
|
+
}),
|
|
909
|
+
};
|
|
910
|
+
|
|
911
|
+
const project = await loadProjectFromDirectory({
|
|
912
|
+
fs: fs as any,
|
|
913
|
+
path: "/project.inlang",
|
|
914
|
+
providePlugins: [mockPlugin1, mockPlugin2],
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
expect(mockPlugin1.importFiles).toHaveBeenCalled();
|
|
918
|
+
expect(mockPlugin1.loadMessages).not.toHaveBeenCalled();
|
|
919
|
+
|
|
920
|
+
expect(mockPlugin2.importFiles).toHaveBeenCalled();
|
|
921
|
+
expect(mockPlugin2.loadMessages).not.toHaveBeenCalled();
|
|
922
|
+
});
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { newProject } from "./newProject.js";
|
|
2
2
|
import { loadProjectInMemory } from "./loadProjectInMemory.js";
|
|
3
3
|
import { type Lix } from "@lix-js/sdk";
|
|
4
|
-
|
|
5
4
|
import fs from "node:fs";
|
|
6
|
-
|
|
7
5
|
import nodePath from "node:path";
|
|
8
6
|
import type {
|
|
9
7
|
InlangPlugin,
|
|
@@ -14,6 +12,7 @@ import type { ProjectSettings } from "../json-schema/settings.js";
|
|
|
14
12
|
import type { PreprocessPluginBeforeImportFunction } from "../plugin/importPlugins.js";
|
|
15
13
|
import { PluginImportError } from "../plugin/errors.js";
|
|
16
14
|
import { upsertBundleNestedMatchByProperties } from "../import-export/upsertBundleNestedMatchByProperties.js";
|
|
15
|
+
import type { ImportFile } from "./api.js";
|
|
17
16
|
|
|
18
17
|
/**
|
|
19
18
|
* Loads a project from a directory.
|
|
@@ -62,69 +61,39 @@ export async function loadProjectFromDirectory(
|
|
|
62
61
|
syncInterval: args.syncInterval,
|
|
63
62
|
});
|
|
64
63
|
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
importPlugins,
|
|
69
|
-
exportPlugins,
|
|
70
|
-
} = categorizePlugins(await project.plugins.get());
|
|
64
|
+
const allPlugins = await project.plugins.get();
|
|
65
|
+
const { loadSavePlugins, importExportPlugins } =
|
|
66
|
+
categorizePlugins(allPlugins);
|
|
71
67
|
|
|
72
68
|
// TODO i guess we should move this validation logic into sdk2/src/project/loadProject.ts
|
|
73
69
|
// Two scenarios could arise:
|
|
74
70
|
// 1. set settings is called from an app - it should detect and reject the setting of settings -> app need to be able to validate before calling set
|
|
75
71
|
// 2. the settings file loaded from disc here is corrupted -> user has to fix the file on disc
|
|
76
|
-
if (
|
|
72
|
+
if (loadSavePlugins.length > 1) {
|
|
77
73
|
throw new Error(
|
|
78
74
|
"Max one loadMessages (found: " +
|
|
79
|
-
|
|
75
|
+
loadSavePlugins.length +
|
|
80
76
|
") and one saveMessages plugins (found: " +
|
|
81
|
-
|
|
77
|
+
loadSavePlugins.length +
|
|
82
78
|
") are allowed "
|
|
83
79
|
);
|
|
84
80
|
}
|
|
85
81
|
const importedResourceFileErrors: Error[] = [];
|
|
86
82
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
"Plugins for loadMessages (found: " +
|
|
93
|
-
loadMessagesPlugins.length +
|
|
94
|
-
") and saveMessages plugins (found: " +
|
|
95
|
-
saveMessagesPlugins.length +
|
|
96
|
-
") must not coexist with import (found: " +
|
|
97
|
-
importPlugins.length +
|
|
98
|
-
") or export (found: " +
|
|
99
|
-
exportPlugins.length +
|
|
100
|
-
") "
|
|
101
|
-
);
|
|
102
|
-
} else if (loadMessagesPlugins.length > 1 || saveMessagesPlugins.length > 1) {
|
|
103
|
-
throw new Error(
|
|
104
|
-
"Max one loadMessages (found: " +
|
|
105
|
-
loadMessagesPlugins.length +
|
|
106
|
-
") and one saveMessages plugins (found: " +
|
|
107
|
-
saveMessagesPlugins.length +
|
|
108
|
-
") are allowed "
|
|
109
|
-
);
|
|
110
|
-
} else if (importPlugins[0]) {
|
|
111
|
-
const importer = importPlugins[0];
|
|
112
|
-
const files = [];
|
|
113
|
-
|
|
114
|
-
if (importer.toBeImportedFiles) {
|
|
115
|
-
const toBeImportedFiles = await importer.toBeImportedFiles({
|
|
83
|
+
// import files from local fs
|
|
84
|
+
for (const plugin of importExportPlugins) {
|
|
85
|
+
const files: ImportFile[] = [];
|
|
86
|
+
if (plugin.toBeImportedFiles) {
|
|
87
|
+
const toBeImportedFiles = await plugin.toBeImportedFiles({
|
|
116
88
|
settings: await project.settings.get(),
|
|
117
89
|
});
|
|
118
90
|
for (const toBeImported of toBeImportedFiles) {
|
|
119
91
|
const absolute = absolutePathFromProject(args.path, toBeImported.path);
|
|
120
92
|
try {
|
|
121
93
|
const data = await args.fs.promises.readFile(absolute);
|
|
122
|
-
const name = nodePath.basename(toBeImported.path);
|
|
123
94
|
files.push({
|
|
124
|
-
name,
|
|
125
95
|
locale: toBeImported.locale,
|
|
126
96
|
content: data,
|
|
127
|
-
pluginKey: importer.key,
|
|
128
97
|
toBeImportedFilesMetadata: toBeImported.metadata,
|
|
129
98
|
});
|
|
130
99
|
} catch (e) {
|
|
@@ -143,17 +112,18 @@ export async function loadProjectFromDirectory(
|
|
|
143
112
|
}
|
|
144
113
|
|
|
145
114
|
await project.importFiles({
|
|
146
|
-
pluginKey:
|
|
147
|
-
files
|
|
115
|
+
pluginKey: plugin.key,
|
|
116
|
+
files,
|
|
148
117
|
});
|
|
149
|
-
}
|
|
150
|
-
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
for (const plugin of loadSavePlugins) {
|
|
151
121
|
await loadLegacyMessages({
|
|
152
122
|
project,
|
|
123
|
+
pluginKey: plugin.key ?? plugin.id,
|
|
124
|
+
loadMessagesFn: plugin.loadMessages!,
|
|
153
125
|
projectPath: args.path,
|
|
154
126
|
fs: args.fs,
|
|
155
|
-
pluginKey: loadMessagesPlugins[0].key ?? loadMessagesPlugins[0].id,
|
|
156
|
-
loadMessagesFn: loadMessagesPlugins[0].loadMessages,
|
|
157
127
|
});
|
|
158
128
|
}
|
|
159
129
|
|
|
@@ -569,9 +539,14 @@ async function upsertFileInLix(
|
|
|
569
539
|
path: string,
|
|
570
540
|
data: ArrayBuffer
|
|
571
541
|
) {
|
|
572
|
-
//
|
|
573
|
-
//
|
|
574
|
-
|
|
542
|
+
// force posix path when upserting into lix
|
|
543
|
+
// https://github.com/opral/inlang-sdk/issues/229
|
|
544
|
+
let posixPath = path.split(nodePath.win32.sep).join(nodePath.posix.sep);
|
|
545
|
+
|
|
546
|
+
if (posixPath.startsWith("/") === false) {
|
|
547
|
+
posixPath = "/" + posixPath;
|
|
548
|
+
}
|
|
549
|
+
|
|
575
550
|
await args.lix.db
|
|
576
551
|
.insertInto("file") // change queue
|
|
577
552
|
.values({
|
|
@@ -583,52 +558,28 @@ async function upsertFileInLix(
|
|
|
583
558
|
)
|
|
584
559
|
.execute();
|
|
585
560
|
}
|
|
561
|
+
/**
|
|
562
|
+
* Filters legacy load and save messages plugins.
|
|
563
|
+
*
|
|
564
|
+
* Legacy plugins are plugins that implement loadMessages and saveMessages but not importFiles and exportFiles.
|
|
565
|
+
*/
|
|
566
|
+
function categorizePlugins(plugins: readonly InlangPlugin[]) {
|
|
567
|
+
const loadSavePlugins: InlangPlugin[] = [];
|
|
568
|
+
const importExportPlugins: InlangPlugin[] = [];
|
|
569
|
+
|
|
570
|
+
for (const plugin of plugins) {
|
|
571
|
+
if (
|
|
572
|
+
plugin.loadMessages &&
|
|
573
|
+
plugin.saveMessages &&
|
|
574
|
+
!(plugin.importFiles && plugin.exportFiles)
|
|
575
|
+
) {
|
|
576
|
+
loadSavePlugins.push(plugin);
|
|
577
|
+
} else if (plugin.importFiles || plugin.exportFiles) {
|
|
578
|
+
importExportPlugins.push(plugin);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
586
581
|
|
|
587
|
-
|
|
588
|
-
function categorizePlugins(plugins: readonly InlangPlugin[]): {
|
|
589
|
-
loadMessagesPlugins: (InlangPlugin &
|
|
590
|
-
Required<Pick<InlangPlugin, "loadMessages">>)[];
|
|
591
|
-
saveMessagesPlugins: (InlangPlugin &
|
|
592
|
-
Required<Pick<InlangPlugin, "saveMessages">>)[];
|
|
593
|
-
importPlugins: (InlangPlugin &
|
|
594
|
-
Required<Pick<InlangPlugin, "importFiles" | "toBeImportedFiles">>)[];
|
|
595
|
-
exportPlugins: (InlangPlugin & Required<Pick<InlangPlugin, "exportFiles">>)[];
|
|
596
|
-
} {
|
|
597
|
-
const loadMessagesPlugins = plugins.filter(
|
|
598
|
-
(
|
|
599
|
-
plugin
|
|
600
|
-
): plugin is InlangPlugin & Required<Pick<InlangPlugin, "loadMessages">> =>
|
|
601
|
-
plugin.loadMessages !== undefined
|
|
602
|
-
);
|
|
603
|
-
|
|
604
|
-
const saveMessagesPlugins = plugins.filter(
|
|
605
|
-
(
|
|
606
|
-
plugin
|
|
607
|
-
): plugin is InlangPlugin & Required<Pick<InlangPlugin, "saveMessages">> =>
|
|
608
|
-
plugin.saveMessages !== undefined
|
|
609
|
-
);
|
|
610
|
-
|
|
611
|
-
const importPlugins = plugins.filter(
|
|
612
|
-
(
|
|
613
|
-
plugin
|
|
614
|
-
): plugin is InlangPlugin &
|
|
615
|
-
Required<Pick<InlangPlugin, "importFiles" | "toBeImportedFiles">> =>
|
|
616
|
-
plugin.importFiles !== undefined && plugin.toBeImportedFiles !== undefined
|
|
617
|
-
);
|
|
618
|
-
|
|
619
|
-
const exportPlugins = plugins.filter(
|
|
620
|
-
(
|
|
621
|
-
plugin
|
|
622
|
-
): plugin is InlangPlugin & Required<Pick<InlangPlugin, "exportFiles">> =>
|
|
623
|
-
plugin.exportFiles !== undefined
|
|
624
|
-
);
|
|
625
|
-
|
|
626
|
-
return {
|
|
627
|
-
loadMessagesPlugins,
|
|
628
|
-
saveMessagesPlugins,
|
|
629
|
-
importPlugins,
|
|
630
|
-
exportPlugins,
|
|
631
|
-
};
|
|
582
|
+
return { loadSavePlugins, importExportPlugins };
|
|
632
583
|
}
|
|
633
584
|
|
|
634
585
|
/**
|