@inlang/sdk 0.34.1 → 0.34.3
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/errors.d.ts.map +1 -1
- package/dist/errors.js +12 -2
- package/dist/loadProject.d.ts.map +1 -1
- package/dist/loadProject.js +21 -16
- package/dist/persistence/plugin.d.ts +31 -0
- package/dist/persistence/plugin.d.ts.map +1 -0
- package/dist/persistence/plugin.js +42 -0
- package/dist/persistence/plugin.test.d.ts +2 -0
- package/dist/persistence/plugin.test.d.ts.map +1 -0
- package/dist/persistence/plugin.test.js +49 -0
- package/dist/resolve-modules/plugins/resolvePlugins.d.ts.map +1 -1
- package/dist/resolve-modules/plugins/resolvePlugins.js +17 -9
- package/dist/resolve-modules/plugins/resolvePlugins.test.js +1 -1
- package/dist/resolve-modules/plugins/types.d.ts +1 -0
- package/dist/resolve-modules/plugins/types.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/errors.ts +16 -4
- package/src/loadProject.ts +31 -28
- package/src/persistence/plugin.test.ts +60 -0
- package/src/persistence/plugin.ts +56 -0
- package/src/resolve-modules/plugins/resolvePlugins.test.ts +1 -1
- package/src/resolve-modules/plugins/resolvePlugins.ts +22 -11
- package/src/resolve-modules/plugins/types.ts +5 -1
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAE1D,qBAAa,0BAA2B,SAAQ,KAAK;gBACxC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE;CAI1D;AAED,qBAAa,2BAA4B,SAAQ,KAAK;gBACzC,OAAO,EAAE;QAAE,MAAM,EAAE,UAAU,EAAE,CAAA;KAAE;CAW7C;AAYD,qBAAa,kCAAmC,SAAQ,KAAK;gBAChD,OAAO,EAAE;QAAE,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE;CAOnE;AAED,qBAAa,gCAAiC,SAAQ,KAAK;gBAC9C,OAAO,EAAE;QAAE,KAAK,CAAC,EAAE,YAAY,CAAC,OAAO,CAAC,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE;CAIpE;AAED,qBAAa,uBAAwB,SAAQ,KAAK;gBACrC,OAAO,EAAE;QAAE,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,CAAA;KAAE;CAIrD;AAED,qBAAa,uBAAwB,SAAQ,KAAK;gBACrC,OAAO,EAAE;QAAE,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,CAAA;KAAE;CAIrD;AAED,qBAAa,gBAAiB,SAAQ,KAAK;gBAC9B,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,CAAA;KAAE;CAOtF;AAED,qBAAa,gBAAiB,SAAQ,KAAK;gBAC9B,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,CAAA;KAAE;CAOtF"}
|
package/dist/errors.js
CHANGED
|
@@ -7,13 +7,23 @@ export class LoadProjectInvalidArgument extends Error {
|
|
|
7
7
|
export class ProjectSettingsInvalidError extends Error {
|
|
8
8
|
constructor(options) {
|
|
9
9
|
// TODO: beatufiy ValueErrors
|
|
10
|
-
super(`The project settings are invalid
|
|
10
|
+
super(`The project settings are invalid:
|
|
11
|
+
${options.errors
|
|
11
12
|
.filter((error) => error.path)
|
|
12
|
-
.map(
|
|
13
|
+
.map(FormatProjectSettingsError)
|
|
13
14
|
.join("\n")}`);
|
|
14
15
|
this.name = "ProjectSettingsInvalidError";
|
|
15
16
|
}
|
|
16
17
|
}
|
|
18
|
+
function FormatProjectSettingsError(error) {
|
|
19
|
+
let msg = `${error.message} at ${error.path}`;
|
|
20
|
+
if (error.path.startsWith("/modules/")) {
|
|
21
|
+
msg += `
|
|
22
|
+
value = "${error.value}"
|
|
23
|
+
- ${error.schema.allOf.map((o) => `${o.description ?? ""}`).join("\n- ")}`;
|
|
24
|
+
}
|
|
25
|
+
return msg;
|
|
26
|
+
}
|
|
17
27
|
export class ProjectSettingsFileJSONSyntaxError extends Error {
|
|
18
28
|
constructor(options) {
|
|
19
29
|
super(`The settings file at "${options.path}" is not a valid JSON file:\n\n${options.cause}`, options);
|
|
@@ -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;AAwBhF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,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;AAmChD;;;;;;;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,CAuXzB;AAsHD,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAQtE"}
|
package/dist/loadProject.js
CHANGED
|
@@ -45,6 +45,7 @@ export async function loadProject(args) {
|
|
|
45
45
|
// won't even be loaded. do not throw anywhere else. otherwise, apps
|
|
46
46
|
// can't handle errors gracefully.
|
|
47
47
|
assertValidProjectPath(projectPath);
|
|
48
|
+
debug(projectPath);
|
|
48
49
|
const nodeishFs = createNodeishFsWithAbsolutePaths({
|
|
49
50
|
projectPath,
|
|
50
51
|
nodeishFs: args.repo.nodeishFs,
|
|
@@ -79,6 +80,11 @@ export async function loadProject(args) {
|
|
|
79
80
|
const setSettings = (settings) => {
|
|
80
81
|
try {
|
|
81
82
|
const validatedSettings = parseSettings(settings);
|
|
83
|
+
if (validatedSettings.experimental?.persistence) {
|
|
84
|
+
settings["plugin.sdk.persistence"] = {
|
|
85
|
+
pathPattern: projectPath + "/messages.json",
|
|
86
|
+
};
|
|
87
|
+
}
|
|
82
88
|
_setSettings(validatedSettings);
|
|
83
89
|
writeSettingsToDisk(validatedSettings);
|
|
84
90
|
return { data: undefined };
|
|
@@ -118,15 +124,14 @@ export async function loadProject(args) {
|
|
|
118
124
|
const _resolvedModules = resolvedModules();
|
|
119
125
|
if (!_resolvedModules)
|
|
120
126
|
return;
|
|
121
|
-
|
|
127
|
+
const resolvedPluginApi = _resolvedModules.resolvedPluginApi;
|
|
128
|
+
if (!resolvedPluginApi.loadMessages) {
|
|
122
129
|
markInitAsFailed(undefined);
|
|
123
130
|
return;
|
|
124
131
|
}
|
|
125
132
|
const _settings = settings();
|
|
126
133
|
if (!_settings)
|
|
127
134
|
return;
|
|
128
|
-
// get plugin finding the plugin that provides loadMessages function
|
|
129
|
-
const loadMessagePlugin = _resolvedModules.plugins.find((plugin) => plugin.loadMessages !== undefined);
|
|
130
135
|
// TODO #1844 this watcher needs to get pruned when we have a change in the configs which will trigger this again
|
|
131
136
|
const fsWithWatcher = createNodeishFsWithWatcher({
|
|
132
137
|
nodeishFs: nodeishFs,
|
|
@@ -136,7 +141,7 @@ export async function loadProject(args) {
|
|
|
136
141
|
// preserving console.logs as comments pending #
|
|
137
142
|
debug("load messages because of a change in the message.json files");
|
|
138
143
|
loadMessagesViaPlugin(fsWithWatcher, messageLockDirPath, messageStates, messagesQuery, settings(), // NOTE we bang here - we don't expect the settings to become null during the livetime of a project
|
|
139
|
-
|
|
144
|
+
resolvedPluginApi)
|
|
140
145
|
.catch((e) => setLoadMessagesViaPluginError(new PluginLoadMessagesError({ cause: e })))
|
|
141
146
|
.then(() => {
|
|
142
147
|
if (loadMessagesViaPluginError() !== undefined) {
|
|
@@ -145,7 +150,7 @@ export async function loadProject(args) {
|
|
|
145
150
|
});
|
|
146
151
|
},
|
|
147
152
|
});
|
|
148
|
-
loadMessagesViaPlugin(fsWithWatcher, messageLockDirPath, messageStates, messagesQuery, _settings,
|
|
153
|
+
loadMessagesViaPlugin(fsWithWatcher, messageLockDirPath, messageStates, messagesQuery, _settings, resolvedPluginApi)
|
|
149
154
|
.then(() => {
|
|
150
155
|
markInitAsComplete();
|
|
151
156
|
})
|
|
@@ -193,10 +198,9 @@ export async function loadProject(args) {
|
|
|
193
198
|
const _resolvedModules = resolvedModules();
|
|
194
199
|
if (!_resolvedModules)
|
|
195
200
|
return;
|
|
201
|
+
const resolvedPluginApi = _resolvedModules.resolvedPluginApi;
|
|
196
202
|
const currentMessageIds = new Set(messagesQuery.includedMessageIds());
|
|
197
203
|
const deletedTrackedMessages = [...trackedMessages].filter((tracked) => !currentMessageIds.has(tracked[0]));
|
|
198
|
-
const saveMessagesPlugin = _resolvedModules.plugins.find((plugin) => plugin.saveMessages !== undefined);
|
|
199
|
-
const loadMessagesPlugin = _resolvedModules.plugins.find((plugin) => plugin.loadMessages !== undefined);
|
|
200
204
|
for (const messageId of currentMessageIds) {
|
|
201
205
|
if (!trackedMessages.has(messageId)) {
|
|
202
206
|
// we create a new root to be able to cleanup an effect for a message that got deleted
|
|
@@ -213,8 +217,9 @@ export async function loadProject(args) {
|
|
|
213
217
|
}
|
|
214
218
|
// don't trigger saves or set dirty flags during initial setup
|
|
215
219
|
if (!initialSetup) {
|
|
220
|
+
debug("message changed", messageId);
|
|
216
221
|
messageStates.messageDirtyFlags[message.id] = true;
|
|
217
|
-
saveMessagesViaPlugin(nodeishFs, messageLockDirPath, messageStates, messagesQuery, settings(),
|
|
222
|
+
saveMessagesViaPlugin(nodeishFs, messageLockDirPath, messageStates, messagesQuery, settings(), resolvedPluginApi)
|
|
218
223
|
.catch((e) => setSaveMessagesViaPluginError(new PluginSaveMessagesError({ cause: e })))
|
|
219
224
|
.then(() => {
|
|
220
225
|
if (saveMessagesViaPluginError() !== undefined) {
|
|
@@ -239,7 +244,7 @@ export async function loadProject(args) {
|
|
|
239
244
|
}
|
|
240
245
|
if (deletedTrackedMessages.length > 0) {
|
|
241
246
|
// we keep track of the latest save within the loadProject call to await it at the end - this is not used in subsequetial upserts
|
|
242
|
-
saveMessagesViaPlugin(nodeishFs, messageLockDirPath, messageStates, messagesQuery, settings(),
|
|
247
|
+
saveMessagesViaPlugin(nodeishFs, messageLockDirPath, messageStates, messagesQuery, settings(), resolvedPluginApi)
|
|
243
248
|
.catch((e) => setSaveMessagesViaPluginError(new PluginSaveMessagesError({ cause: e })))
|
|
244
249
|
.then(() => {
|
|
245
250
|
if (saveMessagesViaPluginError() !== undefined) {
|
|
@@ -408,7 +413,7 @@ export function createSubscribable(signal) {
|
|
|
408
413
|
* @param loadPlugin
|
|
409
414
|
* @returns void - updates the files and messages in of the project in place
|
|
410
415
|
*/
|
|
411
|
-
async function loadMessagesViaPlugin(fs, lockDirPath, messageState, messagesQuery, settingsValue,
|
|
416
|
+
async function loadMessagesViaPlugin(fs, lockDirPath, messageState, messagesQuery, settingsValue, resolvedPluginApi) {
|
|
412
417
|
const experimentalAliases = !!settingsValue.experimental?.aliases;
|
|
413
418
|
// loading is an asynchronous process - check if another load is in progress - queue this call if so
|
|
414
419
|
if (messageState.isLoading) {
|
|
@@ -423,7 +428,7 @@ async function loadMessagesViaPlugin(fs, lockDirPath, messageState, messagesQuer
|
|
|
423
428
|
let lockTime = undefined;
|
|
424
429
|
try {
|
|
425
430
|
lockTime = await acquireFileLock(fs, lockDirPath, "loadMessage");
|
|
426
|
-
const loadedMessages = await makeTrulyAsync(
|
|
431
|
+
const loadedMessages = await makeTrulyAsync(resolvedPluginApi.loadMessages({
|
|
427
432
|
settings: settingsValue,
|
|
428
433
|
nodeishFs: fs,
|
|
429
434
|
}));
|
|
@@ -506,7 +511,7 @@ async function loadMessagesViaPlugin(fs, lockDirPath, messageState, messagesQuer
|
|
|
506
511
|
// reset sheduling to except scheduling again
|
|
507
512
|
messageState.sheduledLoadMessagesViaPlugin = undefined;
|
|
508
513
|
// recall load unawaited to allow stack to pop
|
|
509
|
-
loadMessagesViaPlugin(fs, lockDirPath, messageState, messagesQuery, settingsValue,
|
|
514
|
+
loadMessagesViaPlugin(fs, lockDirPath, messageState, messagesQuery, settingsValue, resolvedPluginApi)
|
|
510
515
|
.then(() => {
|
|
511
516
|
// resolve the scheduled load message promise
|
|
512
517
|
executingScheduledMessages[1]();
|
|
@@ -517,7 +522,7 @@ async function loadMessagesViaPlugin(fs, lockDirPath, messageState, messagesQuer
|
|
|
517
522
|
});
|
|
518
523
|
}
|
|
519
524
|
}
|
|
520
|
-
async function saveMessagesViaPlugin(fs, lockDirPath, messageState, messagesQuery, settingsValue,
|
|
525
|
+
async function saveMessagesViaPlugin(fs, lockDirPath, messageState, messagesQuery, settingsValue, resolvedPluginApi) {
|
|
521
526
|
// queue next save if we have a save ongoing
|
|
522
527
|
if (messageState.isSaving) {
|
|
523
528
|
if (!messageState.sheduledSaveMessages) {
|
|
@@ -566,7 +571,7 @@ async function saveMessagesViaPlugin(fs, lockDirPath, messageState, messagesQuer
|
|
|
566
571
|
messageDirtyFlagsBeforeSave = { ...messageState.messageDirtyFlags };
|
|
567
572
|
messageState.messageDirtyFlags = {};
|
|
568
573
|
// NOTE: this assumes that the plugin will handle message ordering
|
|
569
|
-
await
|
|
574
|
+
await resolvedPluginApi.saveMessages({
|
|
570
575
|
settings: settingsValue,
|
|
571
576
|
messages: messagesToExport,
|
|
572
577
|
nodeishFs: fs,
|
|
@@ -581,7 +586,7 @@ async function saveMessagesViaPlugin(fs, lockDirPath, messageState, messagesQuer
|
|
|
581
586
|
// if there is a queued load, allow it to take the lock before we run additional saves.
|
|
582
587
|
if (messageState.sheduledLoadMessagesViaPlugin) {
|
|
583
588
|
debug("saveMessagesViaPlugin calling queued loadMessagesViaPlugin to share lock");
|
|
584
|
-
await loadMessagesViaPlugin(fs, lockDirPath, messageState, messagesQuery, settingsValue,
|
|
589
|
+
await loadMessagesViaPlugin(fs, lockDirPath, messageState, messagesQuery, settingsValue, resolvedPluginApi);
|
|
585
590
|
}
|
|
586
591
|
messageState.isSaving = false;
|
|
587
592
|
}
|
|
@@ -614,7 +619,7 @@ async function saveMessagesViaPlugin(fs, lockDirPath, messageState, messagesQuer
|
|
|
614
619
|
if (messageState.sheduledSaveMessages) {
|
|
615
620
|
const executingSheduledSaveMessages = messageState.sheduledSaveMessages;
|
|
616
621
|
messageState.sheduledSaveMessages = undefined;
|
|
617
|
-
saveMessagesViaPlugin(fs, lockDirPath, messageState, messagesQuery, settingsValue,
|
|
622
|
+
saveMessagesViaPlugin(fs, lockDirPath, messageState, messagesQuery, settingsValue, resolvedPluginApi)
|
|
618
623
|
.then(() => {
|
|
619
624
|
executingSheduledSaveMessages[1]();
|
|
620
625
|
})
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ProjectSettings, Message } from "@inlang/sdk";
|
|
2
|
+
import { type NodeishFilesystem } from "@lix-js/fs";
|
|
3
|
+
export declare const pluginId = "plugin.sdk.persistence";
|
|
4
|
+
export declare function loadMessages(args: {
|
|
5
|
+
settings: ProjectSettings;
|
|
6
|
+
nodeishFs: NodeishFilesystem;
|
|
7
|
+
}): Promise<{
|
|
8
|
+
id: string;
|
|
9
|
+
alias: Record<string, string>;
|
|
10
|
+
selectors: {
|
|
11
|
+
type: "VariableReference";
|
|
12
|
+
name: string;
|
|
13
|
+
}[];
|
|
14
|
+
variants: {
|
|
15
|
+
languageTag: string;
|
|
16
|
+
match: string[];
|
|
17
|
+
pattern: ({
|
|
18
|
+
type: "Text";
|
|
19
|
+
value: string;
|
|
20
|
+
} | {
|
|
21
|
+
type: "VariableReference";
|
|
22
|
+
name: string;
|
|
23
|
+
})[];
|
|
24
|
+
}[];
|
|
25
|
+
}[]>;
|
|
26
|
+
export declare function saveMessages(args: {
|
|
27
|
+
settings: ProjectSettings;
|
|
28
|
+
nodeishFs: NodeishFilesystem;
|
|
29
|
+
messages: Message[];
|
|
30
|
+
}): Promise<void>;
|
|
31
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/persistence/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAC3D,OAAO,EAAc,KAAK,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAM/D,eAAO,MAAM,QAAQ,2BAA2B,CAAA;AAEhD,wBAAsB,YAAY,CAAC,IAAI,EAAE;IACxC,QAAQ,EAAE,eAAe,CAAA;IACzB,SAAS,EAAE,iBAAiB,CAAA;CAC5B;;;;;;;;;;;;;;;;;;KAeA;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE;IACxC,QAAQ,EAAE,eAAe,CAAA;IACzB,SAAS,EAAE,iBAAiB,CAAA;IAC5B,QAAQ,EAAE,OAAO,EAAE,CAAA;CACnB,iBAcA"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { getDirname } from "@lix-js/fs";
|
|
2
|
+
import { normalizeMessage } from "../storage/helper.js";
|
|
3
|
+
import _debug from "debug";
|
|
4
|
+
const debug = _debug("sdk:persistence");
|
|
5
|
+
export const pluginId = "plugin.sdk.persistence";
|
|
6
|
+
export async function loadMessages(args) {
|
|
7
|
+
let result = [];
|
|
8
|
+
const pathPattern = args.settings[pluginId]?.pathPattern;
|
|
9
|
+
debug("loadMessages", pathPattern);
|
|
10
|
+
try {
|
|
11
|
+
const file = await args.nodeishFs.readFile(pathPattern, { encoding: "utf-8" });
|
|
12
|
+
result = JSON.parse(file);
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
if (error?.code !== "ENOENT") {
|
|
16
|
+
debug("loadMessages", error);
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
export async function saveMessages(args) {
|
|
23
|
+
const pathPattern = args.settings[pluginId]?.pathPattern;
|
|
24
|
+
debug("saveMessages", pathPattern);
|
|
25
|
+
try {
|
|
26
|
+
await createDirectoryIfNotExits(getDirname(pathPattern), args.nodeishFs);
|
|
27
|
+
await args.nodeishFs.writeFile(pathPattern,
|
|
28
|
+
// 2 spaces indentation
|
|
29
|
+
JSON.stringify(args.messages.map(normalizeMessage), undefined, 2));
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
debug("saveMessages", error);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function createDirectoryIfNotExits(path, nodeishFs) {
|
|
36
|
+
try {
|
|
37
|
+
await nodeishFs.mkdir(path, { recursive: true });
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// assume that the directory already exists
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.test.d.ts","sourceRoot":"","sources":["../../src/persistence/plugin.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { test, expect } from "vitest";
|
|
2
|
+
import { createMessage, createNodeishMemoryFs } from "../test-utilities/index.js";
|
|
3
|
+
import { normalizeMessage } from "../storage/helper.js";
|
|
4
|
+
import { pluginId } from "./plugin.js";
|
|
5
|
+
const mockMessages = [
|
|
6
|
+
createMessage("first_message", {
|
|
7
|
+
en: "If this fails I will be sad",
|
|
8
|
+
}),
|
|
9
|
+
createMessage("second_message", {
|
|
10
|
+
en: "Let's see if this works",
|
|
11
|
+
de: "Mal sehen ob das funktioniert",
|
|
12
|
+
}),
|
|
13
|
+
];
|
|
14
|
+
// the test ensures:
|
|
15
|
+
// - messages can be loaded
|
|
16
|
+
// - messages can be saved
|
|
17
|
+
// - after loading and saving messages, the state is the same as before (roundtrip)
|
|
18
|
+
test("roundtrip (saving/loading messages)", async () => {
|
|
19
|
+
const { loadMessages, saveMessages } = await import("./plugin.js");
|
|
20
|
+
const fs = createNodeishMemoryFs();
|
|
21
|
+
const projectDir = "/test/project.inlang";
|
|
22
|
+
const pathPattern = projectDir + "/messages.json";
|
|
23
|
+
const persistedMessages = JSON.stringify(mockMessages.map(normalizeMessage), undefined, 2);
|
|
24
|
+
const settings = {
|
|
25
|
+
sourceLanguageTag: "en",
|
|
26
|
+
languageTags: ["en", "de"],
|
|
27
|
+
modules: [],
|
|
28
|
+
[pluginId]: { pathPattern },
|
|
29
|
+
};
|
|
30
|
+
await fs.mkdir(projectDir, { recursive: true });
|
|
31
|
+
await fs.writeFile(pathPattern, persistedMessages);
|
|
32
|
+
const firstMessageLoad = await loadMessages({
|
|
33
|
+
settings,
|
|
34
|
+
nodeishFs: fs,
|
|
35
|
+
});
|
|
36
|
+
expect(firstMessageLoad).toStrictEqual(mockMessages);
|
|
37
|
+
await saveMessages({
|
|
38
|
+
settings,
|
|
39
|
+
nodeishFs: fs,
|
|
40
|
+
messages: firstMessageLoad,
|
|
41
|
+
});
|
|
42
|
+
const afterRoundtrip = await fs.readFile(pathPattern, { encoding: "utf-8" });
|
|
43
|
+
expect(afterRoundtrip).toStrictEqual(persistedMessages);
|
|
44
|
+
const messagesAfterRoundtrip = await loadMessages({
|
|
45
|
+
settings,
|
|
46
|
+
nodeishFs: fs,
|
|
47
|
+
});
|
|
48
|
+
expect(messagesAfterRoundtrip).toStrictEqual(firstMessageLoad);
|
|
49
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolvePlugins.d.ts","sourceRoot":"","sources":["../../../src/resolve-modules/plugins/resolvePlugins.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"resolvePlugins.d.ts","sourceRoot":"","sources":["../../../src/resolve-modules/plugins/resolvePlugins.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAA;AAwBxD,eAAO,MAAM,cAAc,EAAE,sBAgH5B,CAAA"}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { Plugin } from "@inlang/plugin";
|
|
2
|
+
import { loadMessages as sdkLoadMessages, saveMessages as sdkSaveMessages, } from "../../persistence/plugin.js";
|
|
2
3
|
import { PluginReturnedInvalidCustomApiError, PluginLoadMessagesFunctionAlreadyDefinedError, PluginSaveMessagesFunctionAlreadyDefinedError, PluginsDoNotProvideLoadOrSaveMessagesError, PluginHasInvalidIdError, PluginHasInvalidSchemaError, } from "./errors.js";
|
|
3
4
|
import { deepmerge } from "deepmerge-ts";
|
|
4
5
|
import { TypeCompiler } from "@sinclair/typebox/compiler";
|
|
5
6
|
import { tryCatch } from "@inlang/result";
|
|
7
|
+
import _debug from "debug";
|
|
8
|
+
const debug = _debug("sdk:resolvePlugins");
|
|
6
9
|
// @ts-ignore - type mismatch error
|
|
7
10
|
const PluginCompiler = TypeCompiler.Compile(Plugin);
|
|
8
11
|
export const resolvePlugins = async (args) => {
|
|
@@ -14,6 +17,10 @@ export const resolvePlugins = async (args) => {
|
|
|
14
17
|
},
|
|
15
18
|
errors: [],
|
|
16
19
|
};
|
|
20
|
+
const experimentalPersistence = !!args.settings.experimental?.persistence;
|
|
21
|
+
if (experimentalPersistence) {
|
|
22
|
+
debug("Using experimental persistence");
|
|
23
|
+
}
|
|
17
24
|
for (const plugin of args.plugins) {
|
|
18
25
|
const errors = [...PluginCompiler.Errors(plugin)];
|
|
19
26
|
/**
|
|
@@ -62,16 +69,10 @@ export const resolvePlugins = async (args) => {
|
|
|
62
69
|
* -------------- BEGIN ADDING TO RESULT --------------
|
|
63
70
|
*/
|
|
64
71
|
if (typeof plugin.loadMessages === "function") {
|
|
65
|
-
result.data.loadMessages =
|
|
66
|
-
..._args,
|
|
67
|
-
// renoved nodeishFs from args because we need to pass custom wrapped fs that establishes a watcher
|
|
68
|
-
});
|
|
72
|
+
result.data.loadMessages = plugin.loadMessages;
|
|
69
73
|
}
|
|
70
74
|
if (typeof plugin.saveMessages === "function") {
|
|
71
|
-
result.data.saveMessages =
|
|
72
|
-
..._args,
|
|
73
|
-
nodeishFs: args.nodeishFs,
|
|
74
|
-
});
|
|
75
|
+
result.data.saveMessages = plugin.saveMessages;
|
|
75
76
|
}
|
|
76
77
|
if (typeof plugin.addCustomApi === "function") {
|
|
77
78
|
const { data: customApi } = tryCatch(() => plugin.addCustomApi({
|
|
@@ -83,7 +84,14 @@ export const resolvePlugins = async (args) => {
|
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
86
|
// --- LOADMESSAGE / SAVEMESSAGE NOT DEFINED ---
|
|
86
|
-
if (
|
|
87
|
+
if (experimentalPersistence) {
|
|
88
|
+
debug("Override load/save for experimental persistence");
|
|
89
|
+
// @ts-ignore - type mismatch error
|
|
90
|
+
result.data.loadMessages = sdkLoadMessages;
|
|
91
|
+
// @ts-ignore - type mismatch error
|
|
92
|
+
result.data.saveMessages = sdkSaveMessages;
|
|
93
|
+
}
|
|
94
|
+
else if (typeof result.data.loadMessages !== "function" ||
|
|
87
95
|
typeof result.data.saveMessages !== "function") {
|
|
88
96
|
result.errors.push(new PluginsDoNotProvideLoadOrSaveMessagesError());
|
|
89
97
|
}
|
|
@@ -49,7 +49,7 @@ it("should expose the project settings including the plugin settings", async ()
|
|
|
49
49
|
nodeishFs: {},
|
|
50
50
|
});
|
|
51
51
|
await resolved.data.loadMessages({ settings, nodeishFs: {} });
|
|
52
|
-
await resolved.data.saveMessages({ settings, messages: [] });
|
|
52
|
+
await resolved.data.saveMessages({ settings, messages: [], nodeishFs: {} });
|
|
53
53
|
});
|
|
54
54
|
describe("loadMessages", () => {
|
|
55
55
|
it("should load messages from a local source", async () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/resolve-modules/plugins/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AACnD,OAAO,KAAK,EACX,mCAAmC,EACnC,6CAA6C,EAC7C,6CAA6C,EAC7C,uBAAuB,EACvB,2BAA2B,EAC3B,0CAA0C,EAC1C,MAAM,aAAa,CAAA;AACpB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AAC9C,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AACzE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAE/D;;;;GAIG;AACH,MAAM,MAAM,uBAAuB,GAAG,IAAI,CACzC,iBAAiB,EACjB,UAAU,GAAG,SAAS,GAAG,OAAO,GAAG,WAAW,GAAG,OAAO,CACxD,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,IAAI,EAAE;IAC3C,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;IACtB,QAAQ,EAAE,eAAe,CAAA;IACzB,SAAS,EAAE,uBAAuB,CAAA;CAClC,KAAK,OAAO,CAAC;IACb,IAAI,EAAE,iBAAiB,CAAA;IACvB,MAAM,EAAE,KAAK,CACV,mCAAmC,GACnC,6CAA6C,GAC7C,6CAA6C,GAC7C,uBAAuB,GACvB,2BAA2B,GAC3B,0CAA0C,CAC5C,CAAA;CACD,CAAC,CAAA;AAEF;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC/B,YAAY,EAAE,CAAC,IAAI,EAAE;QACpB,QAAQ,EAAE,eAAe,CAAA;QACzB,SAAS,EAAE,uBAAuB,CAAA;KAClC,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,GAAG,OAAO,EAAE,CAAA;IACpC,YAAY,EAAE,CAAC,IAAI,EAAE;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/resolve-modules/plugins/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AACnD,OAAO,KAAK,EACX,mCAAmC,EACnC,6CAA6C,EAC7C,6CAA6C,EAC7C,uBAAuB,EACvB,2BAA2B,EAC3B,0CAA0C,EAC1C,MAAM,aAAa,CAAA;AACpB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AAC9C,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AACzE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAE/D;;;;GAIG;AACH,MAAM,MAAM,uBAAuB,GAAG,IAAI,CACzC,iBAAiB,EACjB,UAAU,GAAG,SAAS,GAAG,OAAO,GAAG,WAAW,GAAG,OAAO,CACxD,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,IAAI,EAAE;IAC3C,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;IACtB,QAAQ,EAAE,eAAe,CAAA;IACzB,SAAS,EAAE,uBAAuB,CAAA;CAClC,KAAK,OAAO,CAAC;IACb,IAAI,EAAE,iBAAiB,CAAA;IACvB,MAAM,EAAE,KAAK,CACV,mCAAmC,GACnC,6CAA6C,GAC7C,6CAA6C,GAC7C,uBAAuB,GACvB,2BAA2B,GAC3B,0CAA0C,CAC5C,CAAA;CACD,CAAC,CAAA;AAEF;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC/B,YAAY,EAAE,CAAC,IAAI,EAAE;QACpB,QAAQ,EAAE,eAAe,CAAA;QACzB,SAAS,EAAE,uBAAuB,CAAA;KAClC,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,GAAG,OAAO,EAAE,CAAA;IACpC,YAAY,EAAE,CAAC,IAAI,EAAE;QACpB,QAAQ,EAAE,eAAe,CAAA;QACzB,QAAQ,EAAE,OAAO,EAAE,CAAA;QACnB,SAAS,EAAE,uBAAuB,CAAA;KAClC,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAC1B;;;;;;;;;;;;;;;;OAgBG;IACH,SAAS,EAAE,MAAM,CAAC,OAAO,MAAM,IAAI,MAAM,EAAE,GAAG,WAAW,MAAM,IAAI,MAAM,EAAE,EAAE,OAAO,CAAC,GAAG;QACvF,yBAAyB,CAAC,EAAE,2BAA2B,CAAA;KACvD,CAAA;CACD,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inlang/sdk",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.34.
|
|
4
|
+
"version": "0.34.3",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
@@ -34,14 +34,14 @@
|
|
|
34
34
|
"throttle-debounce": "^5.0.0",
|
|
35
35
|
"@inlang/json-types": "1.1.0",
|
|
36
36
|
"@inlang/language-tag": "1.5.1",
|
|
37
|
-
"@inlang/message-lint-rule": "1.4.6",
|
|
38
|
-
"@inlang/module": "1.2.10",
|
|
39
37
|
"@inlang/message": "2.1.0",
|
|
40
|
-
"@inlang/
|
|
41
|
-
"@inlang/
|
|
38
|
+
"@inlang/message-lint-rule": "1.4.7",
|
|
39
|
+
"@inlang/module": "1.2.11",
|
|
40
|
+
"@inlang/project-settings": "2.4.2",
|
|
42
41
|
"@inlang/result": "1.1.0",
|
|
43
|
-
"@
|
|
42
|
+
"@inlang/plugin": "2.4.11",
|
|
44
43
|
"@inlang/translatable": "1.3.1",
|
|
44
|
+
"@lix-js/client": "1.4.0",
|
|
45
45
|
"@lix-js/fs": "1.0.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
package/src/errors.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SchemaOptions } from "@sinclair/typebox"
|
|
1
2
|
import type { ValueError } from "@sinclair/typebox/errors"
|
|
2
3
|
|
|
3
4
|
export class LoadProjectInvalidArgument extends Error {
|
|
@@ -11,15 +12,26 @@ export class ProjectSettingsInvalidError extends Error {
|
|
|
11
12
|
constructor(options: { errors: ValueError[] }) {
|
|
12
13
|
// TODO: beatufiy ValueErrors
|
|
13
14
|
super(
|
|
14
|
-
`The project settings are invalid
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
`The project settings are invalid:
|
|
16
|
+
${options.errors
|
|
17
|
+
.filter((error) => error.path)
|
|
18
|
+
.map(FormatProjectSettingsError)
|
|
19
|
+
.join("\n")}`
|
|
18
20
|
)
|
|
19
21
|
this.name = "ProjectSettingsInvalidError"
|
|
20
22
|
}
|
|
21
23
|
}
|
|
22
24
|
|
|
25
|
+
function FormatProjectSettingsError(error: ValueError) {
|
|
26
|
+
let msg = `${error.message} at ${error.path}`
|
|
27
|
+
if (error.path.startsWith("/modules/")) {
|
|
28
|
+
msg += `
|
|
29
|
+
value = "${error.value}"
|
|
30
|
+
- ${error.schema.allOf.map((o: SchemaOptions) => `${o.description ?? ""}`).join("\n- ")}`
|
|
31
|
+
}
|
|
32
|
+
return msg
|
|
33
|
+
}
|
|
34
|
+
|
|
23
35
|
export class ProjectSettingsFileJSONSyntaxError extends Error {
|
|
24
36
|
constructor(options: { cause: ErrorOptions["cause"]; path: string }) {
|
|
25
37
|
super(
|
package/src/loadProject.ts
CHANGED
|
@@ -38,6 +38,8 @@ import { capture } from "./telemetry/capture.js"
|
|
|
38
38
|
import { identifyProject } from "./telemetry/groupIdentify.js"
|
|
39
39
|
import type { NodeishStats } from "@lix-js/fs"
|
|
40
40
|
|
|
41
|
+
import type { ResolvedPluginApi } from "./resolve-modules/plugins/types.js"
|
|
42
|
+
|
|
41
43
|
import _debug from "debug"
|
|
42
44
|
const debug = _debug("sdk:loadProject")
|
|
43
45
|
const debugLock = _debug("sdk:lockfile")
|
|
@@ -94,6 +96,7 @@ export async function loadProject(args: {
|
|
|
94
96
|
// can't handle errors gracefully.
|
|
95
97
|
|
|
96
98
|
assertValidProjectPath(projectPath)
|
|
99
|
+
debug(projectPath)
|
|
97
100
|
|
|
98
101
|
const nodeishFs = createNodeishFsWithAbsolutePaths({
|
|
99
102
|
projectPath,
|
|
@@ -142,6 +145,11 @@ export async function loadProject(args: {
|
|
|
142
145
|
const setSettings = (settings: ProjectSettings): Result<void, ProjectSettingsInvalidError> => {
|
|
143
146
|
try {
|
|
144
147
|
const validatedSettings = parseSettings(settings)
|
|
148
|
+
if (validatedSettings.experimental?.persistence) {
|
|
149
|
+
settings["plugin.sdk.persistence"] = {
|
|
150
|
+
pathPattern: projectPath + "/messages.json",
|
|
151
|
+
}
|
|
152
|
+
}
|
|
145
153
|
_setSettings(validatedSettings)
|
|
146
154
|
|
|
147
155
|
writeSettingsToDisk(validatedSettings)
|
|
@@ -200,7 +208,9 @@ export async function loadProject(args: {
|
|
|
200
208
|
const _resolvedModules = resolvedModules()
|
|
201
209
|
if (!_resolvedModules) return
|
|
202
210
|
|
|
203
|
-
|
|
211
|
+
const resolvedPluginApi = _resolvedModules.resolvedPluginApi
|
|
212
|
+
|
|
213
|
+
if (!resolvedPluginApi.loadMessages) {
|
|
204
214
|
markInitAsFailed(undefined)
|
|
205
215
|
return
|
|
206
216
|
}
|
|
@@ -208,11 +218,6 @@ export async function loadProject(args: {
|
|
|
208
218
|
const _settings = settings()
|
|
209
219
|
if (!_settings) return
|
|
210
220
|
|
|
211
|
-
// get plugin finding the plugin that provides loadMessages function
|
|
212
|
-
const loadMessagePlugin = _resolvedModules.plugins.find(
|
|
213
|
-
(plugin) => plugin.loadMessages !== undefined
|
|
214
|
-
)
|
|
215
|
-
|
|
216
221
|
// TODO #1844 this watcher needs to get pruned when we have a change in the configs which will trigger this again
|
|
217
222
|
const fsWithWatcher = createNodeishFsWithWatcher({
|
|
218
223
|
nodeishFs: nodeishFs,
|
|
@@ -227,7 +232,7 @@ export async function loadProject(args: {
|
|
|
227
232
|
messageStates,
|
|
228
233
|
messagesQuery,
|
|
229
234
|
settings()!, // NOTE we bang here - we don't expect the settings to become null during the livetime of a project
|
|
230
|
-
|
|
235
|
+
resolvedPluginApi
|
|
231
236
|
)
|
|
232
237
|
.catch((e) => setLoadMessagesViaPluginError(new PluginLoadMessagesError({ cause: e })))
|
|
233
238
|
.then(() => {
|
|
@@ -244,7 +249,7 @@ export async function loadProject(args: {
|
|
|
244
249
|
messageStates,
|
|
245
250
|
messagesQuery,
|
|
246
251
|
_settings,
|
|
247
|
-
|
|
252
|
+
resolvedPluginApi
|
|
248
253
|
)
|
|
249
254
|
.then(() => {
|
|
250
255
|
markInitAsComplete()
|
|
@@ -303,19 +308,13 @@ export async function loadProject(args: {
|
|
|
303
308
|
|
|
304
309
|
const _resolvedModules = resolvedModules()
|
|
305
310
|
if (!_resolvedModules) return
|
|
311
|
+
const resolvedPluginApi = _resolvedModules.resolvedPluginApi
|
|
306
312
|
|
|
307
313
|
const currentMessageIds = new Set(messagesQuery.includedMessageIds())
|
|
308
314
|
const deletedTrackedMessages = [...trackedMessages].filter(
|
|
309
315
|
(tracked) => !currentMessageIds.has(tracked[0])
|
|
310
316
|
)
|
|
311
317
|
|
|
312
|
-
const saveMessagesPlugin = _resolvedModules.plugins.find(
|
|
313
|
-
(plugin) => plugin.saveMessages !== undefined
|
|
314
|
-
)
|
|
315
|
-
const loadMessagesPlugin = _resolvedModules.plugins.find(
|
|
316
|
-
(plugin) => plugin.loadMessages !== undefined
|
|
317
|
-
)
|
|
318
|
-
|
|
319
318
|
for (const messageId of currentMessageIds) {
|
|
320
319
|
if (!trackedMessages!.has(messageId!)) {
|
|
321
320
|
// we create a new root to be able to cleanup an effect for a message that got deleted
|
|
@@ -334,6 +333,7 @@ export async function loadProject(args: {
|
|
|
334
333
|
|
|
335
334
|
// don't trigger saves or set dirty flags during initial setup
|
|
336
335
|
if (!initialSetup) {
|
|
336
|
+
debug("message changed", messageId)
|
|
337
337
|
messageStates.messageDirtyFlags[message.id] = true
|
|
338
338
|
saveMessagesViaPlugin(
|
|
339
339
|
nodeishFs,
|
|
@@ -341,8 +341,7 @@ export async function loadProject(args: {
|
|
|
341
341
|
messageStates,
|
|
342
342
|
messagesQuery,
|
|
343
343
|
settings()!,
|
|
344
|
-
|
|
345
|
-
loadMessagesPlugin
|
|
344
|
+
resolvedPluginApi
|
|
346
345
|
)
|
|
347
346
|
.catch((e) =>
|
|
348
347
|
setSaveMessagesViaPluginError(new PluginSaveMessagesError({ cause: e }))
|
|
@@ -379,8 +378,7 @@ export async function loadProject(args: {
|
|
|
379
378
|
messageStates,
|
|
380
379
|
messagesQuery,
|
|
381
380
|
settings()!,
|
|
382
|
-
|
|
383
|
-
loadMessagesPlugin
|
|
381
|
+
resolvedPluginApi
|
|
384
382
|
)
|
|
385
383
|
.catch((e) => setSaveMessagesViaPluginError(new PluginSaveMessagesError({ cause: e })))
|
|
386
384
|
.then(() => {
|
|
@@ -608,7 +606,7 @@ async function loadMessagesViaPlugin(
|
|
|
608
606
|
messageState: MessageState,
|
|
609
607
|
messagesQuery: InlangProject["query"]["messages"],
|
|
610
608
|
settingsValue: ProjectSettings,
|
|
611
|
-
|
|
609
|
+
resolvedPluginApi: ResolvedPluginApi
|
|
612
610
|
) {
|
|
613
611
|
const experimentalAliases = !!settingsValue.experimental?.aliases
|
|
614
612
|
|
|
@@ -628,7 +626,7 @@ async function loadMessagesViaPlugin(
|
|
|
628
626
|
try {
|
|
629
627
|
lockTime = await acquireFileLock(fs as NodeishFilesystem, lockDirPath, "loadMessage")
|
|
630
628
|
const loadedMessages = await makeTrulyAsync(
|
|
631
|
-
|
|
629
|
+
resolvedPluginApi.loadMessages({
|
|
632
630
|
settings: settingsValue,
|
|
633
631
|
nodeishFs: fs,
|
|
634
632
|
})
|
|
@@ -728,7 +726,14 @@ async function loadMessagesViaPlugin(
|
|
|
728
726
|
messageState.sheduledLoadMessagesViaPlugin = undefined
|
|
729
727
|
|
|
730
728
|
// recall load unawaited to allow stack to pop
|
|
731
|
-
loadMessagesViaPlugin(
|
|
729
|
+
loadMessagesViaPlugin(
|
|
730
|
+
fs,
|
|
731
|
+
lockDirPath,
|
|
732
|
+
messageState,
|
|
733
|
+
messagesQuery,
|
|
734
|
+
settingsValue,
|
|
735
|
+
resolvedPluginApi
|
|
736
|
+
)
|
|
732
737
|
.then(() => {
|
|
733
738
|
// resolve the scheduled load message promise
|
|
734
739
|
executingScheduledMessages[1]()
|
|
@@ -746,8 +751,7 @@ async function saveMessagesViaPlugin(
|
|
|
746
751
|
messageState: MessageState,
|
|
747
752
|
messagesQuery: InlangProject["query"]["messages"],
|
|
748
753
|
settingsValue: ProjectSettings,
|
|
749
|
-
|
|
750
|
-
loadPlugin: any
|
|
754
|
+
resolvedPluginApi: ResolvedPluginApi
|
|
751
755
|
): Promise<void> {
|
|
752
756
|
// queue next save if we have a save ongoing
|
|
753
757
|
if (messageState.isSaving) {
|
|
@@ -809,7 +813,7 @@ async function saveMessagesViaPlugin(
|
|
|
809
813
|
messageState.messageDirtyFlags = {}
|
|
810
814
|
|
|
811
815
|
// NOTE: this assumes that the plugin will handle message ordering
|
|
812
|
-
await
|
|
816
|
+
await resolvedPluginApi.saveMessages({
|
|
813
817
|
settings: settingsValue,
|
|
814
818
|
messages: messagesToExport,
|
|
815
819
|
nodeishFs: fs,
|
|
@@ -833,7 +837,7 @@ async function saveMessagesViaPlugin(
|
|
|
833
837
|
messageState,
|
|
834
838
|
messagesQuery,
|
|
835
839
|
settingsValue,
|
|
836
|
-
|
|
840
|
+
resolvedPluginApi
|
|
837
841
|
)
|
|
838
842
|
}
|
|
839
843
|
|
|
@@ -877,8 +881,7 @@ async function saveMessagesViaPlugin(
|
|
|
877
881
|
messageState,
|
|
878
882
|
messagesQuery,
|
|
879
883
|
settingsValue,
|
|
880
|
-
|
|
881
|
-
loadPlugin
|
|
884
|
+
resolvedPluginApi
|
|
882
885
|
)
|
|
883
886
|
.then(() => {
|
|
884
887
|
executingSheduledSaveMessages[1]()
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { test, expect } from "vitest"
|
|
2
|
+
import { createMessage, createNodeishMemoryFs } from "../test-utilities/index.js"
|
|
3
|
+
import { normalizeMessage } from "../storage/helper.js"
|
|
4
|
+
import { pluginId } from "./plugin.js"
|
|
5
|
+
|
|
6
|
+
const mockMessages = [
|
|
7
|
+
createMessage("first_message", {
|
|
8
|
+
en: "If this fails I will be sad",
|
|
9
|
+
}),
|
|
10
|
+
createMessage("second_message", {
|
|
11
|
+
en: "Let's see if this works",
|
|
12
|
+
de: "Mal sehen ob das funktioniert",
|
|
13
|
+
}),
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
// the test ensures:
|
|
17
|
+
// - messages can be loaded
|
|
18
|
+
// - messages can be saved
|
|
19
|
+
// - after loading and saving messages, the state is the same as before (roundtrip)
|
|
20
|
+
test("roundtrip (saving/loading messages)", async () => {
|
|
21
|
+
const { loadMessages, saveMessages } = await import("./plugin.js")
|
|
22
|
+
const fs = createNodeishMemoryFs()
|
|
23
|
+
const projectDir = "/test/project.inlang"
|
|
24
|
+
const pathPattern = projectDir + "/messages.json"
|
|
25
|
+
const persistedMessages = JSON.stringify(mockMessages.map(normalizeMessage), undefined, 2)
|
|
26
|
+
|
|
27
|
+
const settings = {
|
|
28
|
+
sourceLanguageTag: "en",
|
|
29
|
+
languageTags: ["en", "de"],
|
|
30
|
+
modules: [],
|
|
31
|
+
[pluginId]: { pathPattern },
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
await fs.mkdir(projectDir, { recursive: true })
|
|
35
|
+
await fs.writeFile(pathPattern, persistedMessages)
|
|
36
|
+
|
|
37
|
+
const firstMessageLoad = await loadMessages({
|
|
38
|
+
settings,
|
|
39
|
+
nodeishFs: fs,
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
expect(firstMessageLoad).toStrictEqual(mockMessages)
|
|
43
|
+
|
|
44
|
+
await saveMessages({
|
|
45
|
+
settings,
|
|
46
|
+
nodeishFs: fs,
|
|
47
|
+
messages: firstMessageLoad,
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
const afterRoundtrip = await fs.readFile(pathPattern, { encoding: "utf-8" })
|
|
51
|
+
|
|
52
|
+
expect(afterRoundtrip).toStrictEqual(persistedMessages)
|
|
53
|
+
|
|
54
|
+
const messagesAfterRoundtrip = await loadMessages({
|
|
55
|
+
settings,
|
|
56
|
+
nodeishFs: fs,
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
expect(messagesAfterRoundtrip).toStrictEqual(firstMessageLoad)
|
|
60
|
+
})
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { ProjectSettings, Message } from "@inlang/sdk"
|
|
2
|
+
import { getDirname, type NodeishFilesystem } from "@lix-js/fs"
|
|
3
|
+
import { normalizeMessage } from "../storage/helper.js"
|
|
4
|
+
|
|
5
|
+
import _debug from "debug"
|
|
6
|
+
const debug = _debug("sdk:persistence")
|
|
7
|
+
|
|
8
|
+
export const pluginId = "plugin.sdk.persistence"
|
|
9
|
+
|
|
10
|
+
export async function loadMessages(args: {
|
|
11
|
+
settings: ProjectSettings
|
|
12
|
+
nodeishFs: NodeishFilesystem
|
|
13
|
+
}) {
|
|
14
|
+
let result: Message[] = []
|
|
15
|
+
const pathPattern = args.settings[pluginId]?.pathPattern as string
|
|
16
|
+
|
|
17
|
+
debug("loadMessages", pathPattern)
|
|
18
|
+
try {
|
|
19
|
+
const file = await args.nodeishFs.readFile(pathPattern, { encoding: "utf-8" })
|
|
20
|
+
result = JSON.parse(file)
|
|
21
|
+
} catch (error) {
|
|
22
|
+
if ((error as any)?.code !== "ENOENT") {
|
|
23
|
+
debug("loadMessages", error)
|
|
24
|
+
throw error
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return result
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function saveMessages(args: {
|
|
31
|
+
settings: ProjectSettings
|
|
32
|
+
nodeishFs: NodeishFilesystem
|
|
33
|
+
messages: Message[]
|
|
34
|
+
}) {
|
|
35
|
+
const pathPattern = args.settings[pluginId]?.pathPattern as string
|
|
36
|
+
|
|
37
|
+
debug("saveMessages", pathPattern)
|
|
38
|
+
try {
|
|
39
|
+
await createDirectoryIfNotExits(getDirname(pathPattern), args.nodeishFs)
|
|
40
|
+
await args.nodeishFs.writeFile(
|
|
41
|
+
pathPattern,
|
|
42
|
+
// 2 spaces indentation
|
|
43
|
+
JSON.stringify(args.messages.map(normalizeMessage), undefined, 2)
|
|
44
|
+
)
|
|
45
|
+
} catch (error) {
|
|
46
|
+
debug("saveMessages", error)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function createDirectoryIfNotExits(path: string, nodeishFs: NodeishFilesystem) {
|
|
51
|
+
try {
|
|
52
|
+
await nodeishFs.mkdir(path, { recursive: true })
|
|
53
|
+
} catch {
|
|
54
|
+
// assume that the directory already exists
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -61,7 +61,7 @@ it("should expose the project settings including the plugin settings", async ()
|
|
|
61
61
|
nodeishFs: {} as any,
|
|
62
62
|
})
|
|
63
63
|
await resolved.data.loadMessages!({ settings, nodeishFs: {} as any })
|
|
64
|
-
await resolved.data.saveMessages!({ settings, messages: [] })
|
|
64
|
+
await resolved.data.saveMessages!({ settings, messages: [], nodeishFs: {} as any })
|
|
65
65
|
})
|
|
66
66
|
|
|
67
67
|
describe("loadMessages", () => {
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
2
2
|
import type { ResolvePluginsFunction } from "./types.js"
|
|
3
3
|
import { Plugin } from "@inlang/plugin"
|
|
4
|
+
import {
|
|
5
|
+
loadMessages as sdkLoadMessages,
|
|
6
|
+
saveMessages as sdkSaveMessages,
|
|
7
|
+
} from "../../persistence/plugin.js"
|
|
4
8
|
import {
|
|
5
9
|
PluginReturnedInvalidCustomApiError,
|
|
6
10
|
PluginLoadMessagesFunctionAlreadyDefinedError,
|
|
@@ -13,6 +17,9 @@ import { deepmerge } from "deepmerge-ts"
|
|
|
13
17
|
import { TypeCompiler } from "@sinclair/typebox/compiler"
|
|
14
18
|
import { tryCatch } from "@inlang/result"
|
|
15
19
|
|
|
20
|
+
import _debug from "debug"
|
|
21
|
+
const debug = _debug("sdk:resolvePlugins")
|
|
22
|
+
|
|
16
23
|
// @ts-ignore - type mismatch error
|
|
17
24
|
const PluginCompiler = TypeCompiler.Compile(Plugin)
|
|
18
25
|
|
|
@@ -26,6 +33,11 @@ export const resolvePlugins: ResolvePluginsFunction = async (args) => {
|
|
|
26
33
|
errors: [],
|
|
27
34
|
}
|
|
28
35
|
|
|
36
|
+
const experimentalPersistence = !!args.settings.experimental?.persistence
|
|
37
|
+
if (experimentalPersistence) {
|
|
38
|
+
debug("Using experimental persistence")
|
|
39
|
+
}
|
|
40
|
+
|
|
29
41
|
for (const plugin of args.plugins) {
|
|
30
42
|
const errors = [...PluginCompiler.Errors(plugin)]
|
|
31
43
|
|
|
@@ -88,19 +100,11 @@ export const resolvePlugins: ResolvePluginsFunction = async (args) => {
|
|
|
88
100
|
*/
|
|
89
101
|
|
|
90
102
|
if (typeof plugin.loadMessages === "function") {
|
|
91
|
-
result.data.loadMessages =
|
|
92
|
-
plugin.loadMessages!({
|
|
93
|
-
..._args,
|
|
94
|
-
// renoved nodeishFs from args because we need to pass custom wrapped fs that establishes a watcher
|
|
95
|
-
})
|
|
103
|
+
result.data.loadMessages = plugin.loadMessages
|
|
96
104
|
}
|
|
97
105
|
|
|
98
106
|
if (typeof plugin.saveMessages === "function") {
|
|
99
|
-
result.data.saveMessages =
|
|
100
|
-
plugin.saveMessages!({
|
|
101
|
-
..._args,
|
|
102
|
-
nodeishFs: args.nodeishFs,
|
|
103
|
-
})
|
|
107
|
+
result.data.saveMessages = plugin.saveMessages
|
|
104
108
|
}
|
|
105
109
|
|
|
106
110
|
if (typeof plugin.addCustomApi === "function") {
|
|
@@ -116,7 +120,14 @@ export const resolvePlugins: ResolvePluginsFunction = async (args) => {
|
|
|
116
120
|
}
|
|
117
121
|
|
|
118
122
|
// --- LOADMESSAGE / SAVEMESSAGE NOT DEFINED ---
|
|
119
|
-
|
|
123
|
+
|
|
124
|
+
if (experimentalPersistence) {
|
|
125
|
+
debug("Override load/save for experimental persistence")
|
|
126
|
+
// @ts-ignore - type mismatch error
|
|
127
|
+
result.data.loadMessages = sdkLoadMessages
|
|
128
|
+
// @ts-ignore - type mismatch error
|
|
129
|
+
result.data.saveMessages = sdkSaveMessages
|
|
130
|
+
} else if (
|
|
120
131
|
typeof result.data.loadMessages !== "function" ||
|
|
121
132
|
typeof result.data.saveMessages !== "function"
|
|
122
133
|
) {
|
|
@@ -48,7 +48,11 @@ export type ResolvedPluginApi = {
|
|
|
48
48
|
settings: ProjectSettings
|
|
49
49
|
nodeishFs: NodeishFilesystemSubset
|
|
50
50
|
}) => Promise<Message[]> | Message[]
|
|
51
|
-
saveMessages: (args: {
|
|
51
|
+
saveMessages: (args: {
|
|
52
|
+
settings: ProjectSettings
|
|
53
|
+
messages: Message[]
|
|
54
|
+
nodeishFs: NodeishFilesystemSubset
|
|
55
|
+
}) => Promise<void> | void
|
|
52
56
|
/**
|
|
53
57
|
* App specific APIs.
|
|
54
58
|
*
|