@inlang/sdk 2.0.0-beta.4 → 2.0.0-beta.6

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.
@@ -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
- loadMessagesPlugins,
67
- saveMessagesPlugins,
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 (loadMessagesPlugins.length > 1 || saveMessagesPlugins.length > 1) {
72
+ if (loadSavePlugins.length > 1) {
77
73
  throw new Error(
78
74
  "Max one loadMessages (found: " +
79
- loadMessagesPlugins.length +
75
+ loadSavePlugins.length +
80
76
  ") and one saveMessages plugins (found: " +
81
- saveMessagesPlugins.length +
77
+ loadSavePlugins.length +
82
78
  ") are allowed "
83
79
  );
84
80
  }
85
81
  const importedResourceFileErrors: Error[] = [];
86
82
 
87
- if (
88
- (loadMessagesPlugins.length > 0 || saveMessagesPlugins.length > 0) &&
89
- (exportPlugins.length > 0 || importPlugins.length > 0)
90
- ) {
91
- throw new Error(
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: importer.key,
147
- files: files as any,
115
+ pluginKey: plugin.key,
116
+ files,
148
117
  });
149
- } else if (loadMessagesPlugins[0] !== undefined) {
150
- // TODO create resource files from loadMessageFn call - to poll?
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
 
@@ -583,52 +553,28 @@ async function upsertFileInLix(
583
553
  )
584
554
  .execute();
585
555
  }
556
+ /**
557
+ * Filters legacy load and save messages plugins.
558
+ *
559
+ * Legacy plugins are plugins that implement loadMessages and saveMessages but not importFiles and exportFiles.
560
+ */
561
+ function categorizePlugins(plugins: readonly InlangPlugin[]) {
562
+ const loadSavePlugins: InlangPlugin[] = [];
563
+ const importExportPlugins: InlangPlugin[] = [];
564
+
565
+ for (const plugin of plugins) {
566
+ if (
567
+ plugin.loadMessages &&
568
+ plugin.saveMessages &&
569
+ !(plugin.importFiles && plugin.exportFiles)
570
+ ) {
571
+ loadSavePlugins.push(plugin);
572
+ } else if (plugin.importFiles || plugin.exportFiles) {
573
+ importExportPlugins.push(plugin);
574
+ }
575
+ }
586
576
 
587
- // TODO i guess we should move this validation logic into sdk2/src/project/loadProject.ts
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
- };
577
+ return { loadSavePlugins, importExportPlugins };
632
578
  }
633
579
 
634
580
  /**