@inlang/sdk 0.8.0 → 0.9.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.
@@ -143,7 +143,7 @@ describe("messages", () => {
143
143
  description: {
144
144
  en: "wo",
145
145
  },
146
- loadMessages: ({ languageTags }) => (languageTags.length ? exampleMessages : []),
146
+ loadMessages: ({ settings }) => (settings.languageTags.length ? exampleMessages : []),
147
147
  saveMessages: () => undefined,
148
148
  };
149
149
  const mockImport = async () => ({ default: mockPlugin });
@@ -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;AAahF,OAAO,EAA4B,KAAK,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAMjG;;;;;;GAMG;AACH,eAAO,MAAM,WAAW;sBACL,MAAM;eACb,uBAAuB;;qBAElB,MAAM,SAAS,OAAO,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI;MAC5D,QAAQ,aAAa,CAmLxB,CAAA;AAsGD,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAQtE"}
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;AAahF,OAAO,EAA4B,KAAK,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAMjG;;;;;;GAMG;AACH,eAAO,MAAM,WAAW;sBACL,MAAM;eACb,uBAAuB;;qBAElB,MAAM,SAAS,OAAO,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI;MAC5D,QAAQ,aAAa,CAqLxB,CAAA;AAsGD,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAQtE"}
@@ -77,8 +77,7 @@ export const loadProject = async (args) => {
77
77
  return;
78
78
  }
79
79
  makeTrulyAsync(_resolvedModules.resolvedPluginApi.loadMessages({
80
- languageTags: settingsValue.languageTags,
81
- sourceLanguageTag: settingsValue.sourceLanguageTag,
80
+ settings: settingsValue,
82
81
  }))
83
82
  .then((messages) => {
84
83
  setMessages(messages);
@@ -117,7 +116,10 @@ export const loadProject = async (args) => {
117
116
  const lintReportsQuery = createMessageLintReportsQuery(messages, settings, installedMessageLintRules, resolvedModules);
118
117
  const debouncedSave = skipFirst(debounce(500, async (newMessages) => {
119
118
  try {
120
- await resolvedModules()?.resolvedPluginApi.saveMessages({ messages: newMessages });
119
+ await resolvedModules()?.resolvedPluginApi.saveMessages({
120
+ settings: settingsValue,
121
+ messages: newMessages,
122
+ });
121
123
  }
122
124
  catch (err) {
123
125
  throw new PluginSaveMessagesError("Error in saving messages", {
@@ -399,14 +399,15 @@ describe("functionality", () => {
399
399
  describe("query", () => {
400
400
  it("should call saveMessages() on updates", async () => {
401
401
  const fs = createNodeishMemoryFs();
402
- await fs.writeFile("./project.inlang.json", JSON.stringify({
402
+ const settings = {
403
403
  sourceLanguageTag: "en",
404
404
  languageTags: ["en", "de"],
405
405
  modules: ["plugin.js"],
406
406
  "plugin.project.json": {
407
407
  pathPattern: "./resources/{languageTag}.json",
408
408
  },
409
- }));
409
+ };
410
+ await fs.writeFile("./project.inlang.json", JSON.stringify(settings));
410
411
  await fs.mkdir("./resources");
411
412
  const mockSaveFn = vi.fn();
412
413
  const _mockPlugin = {
@@ -486,9 +487,7 @@ describe("functionality", () => {
486
487
  });
487
488
  await new Promise((resolve) => setTimeout(resolve, 510));
488
489
  expect(mockSaveFn.mock.calls.length).toBe(1);
489
- expect(mockSaveFn.mock.calls[0][0].settings).toStrictEqual({
490
- pathPattern: "./resources/{languageTag}.json",
491
- });
490
+ expect(mockSaveFn.mock.calls[0][0].settings).toStrictEqual(settings);
492
491
  expect(Object.values(mockSaveFn.mock.calls[0][0].messages)).toStrictEqual([
493
492
  {
494
493
  id: "a",
@@ -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;AAuBxD,eAAO,MAAM,cAAc,EAAE,sBAoJ5B,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;AAuBxD,eAAO,MAAM,cAAc,EAAE,sBAkJ5B,CAAA"}
@@ -27,7 +27,7 @@ export const resolvePlugins = async (args) => {
27
27
  // -- INVALID ID in META --
28
28
  const hasInvalidId = errors.some((error) => error.path === "/id");
29
29
  if (hasInvalidId) {
30
- result.errors.push(new PluginHasInvalidIdError(`Plugin ${plugin.id} has an invalid id "${plugin.id}". It must be kebap-case and contain a namespace like project.my-plugin.`, { plugin: plugin.id }));
30
+ result.errors.push(new PluginHasInvalidIdError(`Plugin ${plugin.id} has an invalid id "${plugin.id}". It must be camelCase and contain a namespace like plugin.namespace.myPlugin.`, { plugin: plugin.id }));
31
31
  }
32
32
  // -- USES RESERVED NAMESPACE --
33
33
  if (plugin.id.includes("inlang") && !whitelistedPlugins.includes(plugin.id)) {
@@ -53,7 +53,7 @@ export const resolvePlugins = async (args) => {
53
53
  if (typeof plugin.addCustomApi === "function") {
54
54
  // TODO: why do we call this function 2 times (here for validation and later for retrieving the actual value)?
55
55
  const { data: customApi, error } = tryCatch(() => plugin.addCustomApi({
56
- settings: args.settings?.[plugin.id] ?? {},
56
+ settings: args.settings,
57
57
  }));
58
58
  if (error) {
59
59
  // @ts-ignore
@@ -74,20 +74,18 @@ export const resolvePlugins = async (args) => {
74
74
  if (typeof plugin.loadMessages === "function") {
75
75
  result.data.loadMessages = (_args) => plugin.loadMessages({
76
76
  ..._args,
77
- settings: args.settings?.[plugin.id] ?? {},
78
77
  nodeishFs: args.nodeishFs,
79
78
  });
80
79
  }
81
80
  if (typeof plugin.saveMessages === "function") {
82
81
  result.data.saveMessages = (_args) => plugin.saveMessages({
83
82
  ..._args,
84
- settings: args.settings?.[plugin.id] ?? {},
85
83
  nodeishFs: args.nodeishFs,
86
84
  });
87
85
  }
88
86
  if (typeof plugin.addCustomApi === "function") {
89
87
  const { data: customApi } = tryCatch(() => plugin.addCustomApi({
90
- settings: args.settings?.[plugin.id] ?? {},
88
+ settings: args.settings,
91
89
  }));
92
90
  if (customApi) {
93
91
  result.data.customApi = deepmerge(result.data.customApi, customApi);
@@ -2,56 +2,87 @@
2
2
  import { describe, expect, it } from "vitest";
3
3
  import { resolvePlugins } from "./resolvePlugins.js";
4
4
  import { PluginLoadMessagesFunctionAlreadyDefinedError, PluginSaveMessagesFunctionAlreadyDefinedError, PluginHasInvalidIdError, PluginUsesReservedNamespaceError, PluginReturnedInvalidCustomApiError, PluginHasInvalidSchemaError, PluginsDoNotProvideLoadOrSaveMessagesError, } from "./errors.js";
5
- describe("generally", () => {
6
- it("should return an error if a plugin uses an invalid id", async () => {
7
- const mockPlugin = {
8
- // @ts-expect-error - invalid id
9
- id: "no-namespace",
10
- description: { en: "My plugin description" },
11
- displayName: { en: "My plugin" },
12
- loadMessages: () => undefined,
13
- saveMessages: () => undefined,
14
- };
15
- const resolved = await resolvePlugins({
16
- plugins: [mockPlugin],
17
- settings: {},
18
- nodeishFs: {},
19
- });
20
- expect(resolved.errors[0]).toBeInstanceOf(PluginHasInvalidIdError);
5
+ it("should return an error if a plugin uses an invalid id", async () => {
6
+ const mockPlugin = {
7
+ // @ts-expect-error - invalid id
8
+ id: "no-namespace",
9
+ description: { en: "My plugin description" },
10
+ displayName: { en: "My plugin" },
11
+ loadMessages: () => undefined,
12
+ saveMessages: () => undefined,
13
+ };
14
+ const resolved = await resolvePlugins({
15
+ plugins: [mockPlugin],
16
+ settings: {},
17
+ nodeishFs: {},
21
18
  });
22
- it("should return an error if a plugin uses APIs that are not available", async () => {
23
- const mockPlugin = {
24
- id: "plugin.namespace.undefinedApi",
25
- description: { en: "My plugin description" },
26
- displayName: { en: "My plugin" },
27
- // @ts-expect-error the key is not available in type
28
- nonExistentKey: {
29
- nonexistentOptions: "value",
30
- },
31
- loadMessages: () => undefined,
32
- saveMessages: () => undefined,
33
- };
34
- const resolved = await resolvePlugins({
35
- plugins: [mockPlugin],
36
- settings: {},
37
- nodeishFs: {},
38
- });
39
- expect(resolved.errors[0]).toBeInstanceOf(PluginHasInvalidSchemaError);
19
+ expect(resolved.errors[0]).toBeInstanceOf(PluginHasInvalidIdError);
20
+ });
21
+ it("should return an error if a plugin uses APIs that are not available", async () => {
22
+ const mockPlugin = {
23
+ id: "plugin.namespace.undefinedApi",
24
+ description: { en: "My plugin description" },
25
+ displayName: { en: "My plugin" },
26
+ // @ts-expect-error the key is not available in type
27
+ nonExistentKey: {
28
+ nonexistentOptions: "value",
29
+ },
30
+ loadMessages: () => undefined,
31
+ saveMessages: () => undefined,
32
+ };
33
+ const resolved = await resolvePlugins({
34
+ plugins: [mockPlugin],
35
+ settings: {},
36
+ nodeishFs: {},
40
37
  });
41
- it("should not initialize a plugin that uses the 'inlang' namespace except for inlang whitelisted plugins", async () => {
42
- const mockPlugin = {
43
- id: "plugin.inlang.notWhitelisted",
44
- description: { en: "My plugin description" },
45
- displayName: { en: "My plugin" },
46
- loadMessages: () => undefined,
47
- };
48
- const resolved = await resolvePlugins({
49
- plugins: [mockPlugin],
50
- settings: {},
51
- nodeishFs: {},
52
- });
53
- expect(resolved.errors[0]).toBeInstanceOf(PluginUsesReservedNamespaceError);
38
+ expect(resolved.errors[0]).toBeInstanceOf(PluginHasInvalidSchemaError);
39
+ });
40
+ it("should not initialize a plugin that uses the 'inlang' namespace except for inlang whitelisted plugins", async () => {
41
+ const mockPlugin = {
42
+ id: "plugin.inlang.notWhitelisted",
43
+ description: { en: "My plugin description" },
44
+ displayName: { en: "My plugin" },
45
+ loadMessages: () => undefined,
46
+ };
47
+ const resolved = await resolvePlugins({
48
+ plugins: [mockPlugin],
49
+ settings: {},
50
+ nodeishFs: {},
51
+ });
52
+ expect(resolved.errors[0]).toBeInstanceOf(PluginUsesReservedNamespaceError);
53
+ });
54
+ it("should expose the project settings including the plugin settings", async () => {
55
+ const settings = {
56
+ sourceLanguageTag: "en",
57
+ languageTags: ["en", "de"],
58
+ modules: [],
59
+ "plugin.namespace.placeholder": {
60
+ myPluginSetting: "value",
61
+ },
62
+ };
63
+ const mockPlugin = {
64
+ id: "plugin.namespace.placeholder",
65
+ description: { en: "My plugin description" },
66
+ displayName: { en: "My plugin" },
67
+ saveMessages: async ({ settings }) => {
68
+ expect(settings).toStrictEqual(settings);
69
+ },
70
+ addCustomApi: ({ settings }) => {
71
+ expect(settings).toStrictEqual(settings);
72
+ return {};
73
+ },
74
+ loadMessages: async ({ settings }) => {
75
+ expect(settings).toStrictEqual(settings);
76
+ return [];
77
+ },
78
+ };
79
+ const resolved = await resolvePlugins({
80
+ plugins: [mockPlugin],
81
+ settings: settings,
82
+ nodeishFs: {},
54
83
  });
84
+ await resolved.data.loadMessages({ settings });
85
+ await resolved.data.saveMessages({ settings, messages: [] });
55
86
  });
56
87
  describe("loadMessages", () => {
57
88
  it("should load messages from a local source", async () => {
@@ -67,8 +98,7 @@ describe("loadMessages", () => {
67
98
  nodeishFs: {},
68
99
  });
69
100
  expect(await resolved.data.loadMessages({
70
- languageTags: ["en"],
71
- sourceLanguageTag: "en",
101
+ settings: {},
72
102
  })).toEqual([{ id: "test", expressions: [], selectors: [], variants: [] }]);
73
103
  });
74
104
  it("should collect an error if function is defined twice in multiple plugins", async () => {
@@ -1,9 +1,8 @@
1
- import type { LanguageTag } from "@inlang/language-tag";
2
1
  import type { NodeishFilesystem as LisaNodeishFilesystem } from "@lix-js/fs";
3
2
  import type { PluginReturnedInvalidCustomApiError, PluginLoadMessagesFunctionAlreadyDefinedError, PluginSaveMessagesFunctionAlreadyDefinedError, PluginHasInvalidIdError, PluginHasInvalidSchemaError, PluginUsesReservedNamespaceError, PluginsDoNotProvideLoadOrSaveMessagesError } from "./errors.js";
4
3
  import type { Message } from "@inlang/message";
5
- import type { JSONObject } from "@inlang/json-types";
6
4
  import type { CustomApiInlangIdeExtension, Plugin } from "@inlang/plugin";
5
+ import type { ProjectSettings } from "@inlang/project-settings";
7
6
  /**
8
7
  * The filesystem is a subset of project lisa's nodeish filesystem.
9
8
  *
@@ -15,7 +14,7 @@ export type NodeishFilesystemSubset = Pick<LisaNodeishFilesystem, "readFile" | "
15
14
  */
16
15
  export type ResolvePluginsFunction = (args: {
17
16
  plugins: Array<Plugin>;
18
- settings: Record<Plugin["id"], JSONObject>;
17
+ settings: ProjectSettings;
19
18
  nodeishFs: NodeishFilesystemSubset;
20
19
  }) => Promise<{
21
20
  data: ResolvedPluginApi;
@@ -26,10 +25,10 @@ export type ResolvePluginsFunction = (args: {
26
25
  */
27
26
  export type ResolvedPluginApi = {
28
27
  loadMessages: (args: {
29
- languageTags: LanguageTag[];
30
- sourceLanguageTag: LanguageTag;
28
+ settings: ProjectSettings;
31
29
  }) => Promise<Message[]> | Message[];
32
30
  saveMessages: (args: {
31
+ settings: ProjectSettings;
33
32
  messages: Message[];
34
33
  }) => Promise<void> | void;
35
34
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/resolve-modules/plugins/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,KAAK,EAAE,iBAAiB,IAAI,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAC5E,OAAO,KAAK,EACX,mCAAmC,EACnC,6CAA6C,EAC7C,6CAA6C,EAC7C,uBAAuB,EACvB,2BAA2B,EAC3B,gCAAgC,EAChC,0CAA0C,EAC1C,MAAM,aAAa,CAAA;AACpB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AACpD,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAEzE;;;;GAIG;AACH,MAAM,MAAM,uBAAuB,GAAG,IAAI,CACzC,qBAAqB,EACrB,UAAU,GAAG,SAAS,GAAG,OAAO,GAAG,WAAW,CAC9C,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,IAAI,EAAE;IAC3C,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;IACtB,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,UAAU,CAAC,CAAA;IAC1C,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,gCAAgC,GAChC,0CAA0C,CAC5C,CAAA;CACD,CAAC,CAAA;AAEF;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC/B,YAAY,EAAE,CAAC,IAAI,EAAE;QACpB,YAAY,EAAE,WAAW,EAAE,CAAA;QAC3B,iBAAiB,EAAE,WAAW,CAAA;KAC9B,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,GAAG,OAAO,EAAE,CAAA;IACpC,YAAY,EAAE,CAAC,IAAI,EAAE;QAAE,QAAQ,EAAE,OAAO,EAAE,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IACrE;;;;;;;;;;;;;;;;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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/resolve-modules/plugins/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,IAAI,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAC5E,OAAO,KAAK,EACX,mCAAmC,EACnC,6CAA6C,EAC7C,6CAA6C,EAC7C,uBAAuB,EACvB,2BAA2B,EAC3B,gCAAgC,EAChC,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,qBAAqB,EACrB,UAAU,GAAG,SAAS,GAAG,OAAO,GAAG,WAAW,CAC9C,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,gCAAgC,GAChC,0CAA0C,CAC5C,CAAA;CACD,CAAC,CAAA;AAEF;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC/B,YAAY,EAAE,CAAC,IAAI,EAAE;QAAE,QAAQ,EAAE,eAAe,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,GAAG,OAAO,EAAE,CAAA;IACrF,YAAY,EAAE,CAAC,IAAI,EAAE;QAAE,QAAQ,EAAE,eAAe,CAAC;QAAC,QAAQ,EAAE,OAAO,EAAE,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAChG;;;;;;;;;;;;;;;;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.8.0",
4
+ "version": "0.9.0",
5
5
  "license": "Apache-2.0",
6
6
  "publishConfig": {
7
7
  "access": "public"
@@ -175,7 +175,7 @@ describe("messages", () => {
175
175
  description: {
176
176
  en: "wo",
177
177
  },
178
- loadMessages: ({ languageTags }) => (languageTags.length ? exampleMessages : []),
178
+ loadMessages: ({ settings }) => (settings.languageTags.length ? exampleMessages : []),
179
179
  saveMessages: () => undefined,
180
180
  }
181
181
 
@@ -482,17 +482,16 @@ describe("functionality", () => {
482
482
  it("should call saveMessages() on updates", async () => {
483
483
  const fs = createNodeishMemoryFs()
484
484
 
485
- await fs.writeFile(
486
- "./project.inlang.json",
487
- JSON.stringify({
488
- sourceLanguageTag: "en",
489
- languageTags: ["en", "de"],
490
- modules: ["plugin.js"],
491
- "plugin.project.json": {
492
- pathPattern: "./resources/{languageTag}.json",
493
- },
494
- })
495
- )
485
+ const settings: ProjectSettings = {
486
+ sourceLanguageTag: "en",
487
+ languageTags: ["en", "de"],
488
+ modules: ["plugin.js"],
489
+ "plugin.project.json": {
490
+ pathPattern: "./resources/{languageTag}.json",
491
+ },
492
+ }
493
+
494
+ await fs.writeFile("./project.inlang.json", JSON.stringify(settings))
496
495
 
497
496
  await fs.mkdir("./resources")
498
497
 
@@ -583,9 +582,7 @@ describe("functionality", () => {
583
582
 
584
583
  expect(mockSaveFn.mock.calls.length).toBe(1)
585
584
 
586
- expect(mockSaveFn.mock.calls[0][0].settings).toStrictEqual({
587
- pathPattern: "./resources/{languageTag}.json",
588
- })
585
+ expect(mockSaveFn.mock.calls[0][0].settings).toStrictEqual(settings)
589
586
 
590
587
  expect(Object.values(mockSaveFn.mock.calls[0][0].messages)).toStrictEqual([
591
588
  {
@@ -113,8 +113,7 @@ export const loadProject = async (args: {
113
113
 
114
114
  makeTrulyAsync(
115
115
  _resolvedModules.resolvedPluginApi.loadMessages({
116
- languageTags: settingsValue!.languageTags,
117
- sourceLanguageTag: settingsValue!.sourceLanguageTag,
116
+ settings: settingsValue,
118
117
  })
119
118
  )
120
119
  .then((messages) => {
@@ -174,7 +173,10 @@ export const loadProject = async (args: {
174
173
  500,
175
174
  async (newMessages) => {
176
175
  try {
177
- await resolvedModules()?.resolvedPluginApi.saveMessages({ messages: newMessages })
176
+ await resolvedModules()?.resolvedPluginApi.saveMessages({
177
+ settings: settingsValue,
178
+ messages: newMessages,
179
+ })
178
180
  } catch (err) {
179
181
  throw new PluginSaveMessagesError("Error in saving messages", {
180
182
  cause: err,
@@ -11,65 +11,98 @@ import {
11
11
  PluginsDoNotProvideLoadOrSaveMessagesError,
12
12
  } from "./errors.js"
13
13
  import type { Plugin } from "@inlang/plugin"
14
+ import type { ProjectSettings } from "@inlang/project-settings"
15
+
16
+ it("should return an error if a plugin uses an invalid id", async () => {
17
+ const mockPlugin: Plugin = {
18
+ // @ts-expect-error - invalid id
19
+ id: "no-namespace",
20
+ description: { en: "My plugin description" },
21
+ displayName: { en: "My plugin" },
22
+ loadMessages: () => undefined as any,
23
+ saveMessages: () => undefined as any,
24
+ }
25
+
26
+ const resolved = await resolvePlugins({
27
+ plugins: [mockPlugin],
28
+ settings: {} as any as any,
29
+ nodeishFs: {} as any,
30
+ })
14
31
 
15
- describe("generally", () => {
16
- it("should return an error if a plugin uses an invalid id", async () => {
17
- const mockPlugin: Plugin = {
18
- // @ts-expect-error - invalid id
19
- id: "no-namespace",
20
- description: { en: "My plugin description" },
21
- displayName: { en: "My plugin" },
22
- loadMessages: () => undefined as any,
23
- saveMessages: () => undefined as any,
24
- }
25
-
26
- const resolved = await resolvePlugins({
27
- plugins: [mockPlugin],
28
- settings: {},
29
- nodeishFs: {} as any,
30
- })
32
+ expect(resolved.errors[0]).toBeInstanceOf(PluginHasInvalidIdError)
33
+ })
31
34
 
32
- expect(resolved.errors[0]).toBeInstanceOf(PluginHasInvalidIdError)
35
+ it("should return an error if a plugin uses APIs that are not available", async () => {
36
+ const mockPlugin: Plugin = {
37
+ id: "plugin.namespace.undefinedApi",
38
+ description: { en: "My plugin description" },
39
+ displayName: { en: "My plugin" },
40
+ // @ts-expect-error the key is not available in type
41
+ nonExistentKey: {
42
+ nonexistentOptions: "value",
43
+ },
44
+ loadMessages: () => undefined as any,
45
+ saveMessages: () => undefined as any,
46
+ }
47
+
48
+ const resolved = await resolvePlugins({
49
+ plugins: [mockPlugin],
50
+ settings: {} as any,
51
+ nodeishFs: {} as any,
33
52
  })
34
53
 
35
- it("should return an error if a plugin uses APIs that are not available", async () => {
36
- const mockPlugin: Plugin = {
37
- id: "plugin.namespace.undefinedApi",
38
- description: { en: "My plugin description" },
39
- displayName: { en: "My plugin" },
40
- // @ts-expect-error the key is not available in type
41
- nonExistentKey: {
42
- nonexistentOptions: "value",
43
- },
44
- loadMessages: () => undefined as any,
45
- saveMessages: () => undefined as any,
46
- }
47
-
48
- const resolved = await resolvePlugins({
49
- plugins: [mockPlugin],
50
- settings: {},
51
- nodeishFs: {} as any,
52
- })
54
+ expect(resolved.errors[0]).toBeInstanceOf(PluginHasInvalidSchemaError)
55
+ })
53
56
 
54
- expect(resolved.errors[0]).toBeInstanceOf(PluginHasInvalidSchemaError)
57
+ it("should not initialize a plugin that uses the 'inlang' namespace except for inlang whitelisted plugins", async () => {
58
+ const mockPlugin: Plugin = {
59
+ id: "plugin.inlang.notWhitelisted",
60
+ description: { en: "My plugin description" },
61
+ displayName: { en: "My plugin" },
62
+ loadMessages: () => undefined as any,
63
+ }
64
+
65
+ const resolved = await resolvePlugins({
66
+ plugins: [mockPlugin],
67
+ settings: {} as any,
68
+ nodeishFs: {} as any,
55
69
  })
56
70
 
57
- it("should not initialize a plugin that uses the 'inlang' namespace except for inlang whitelisted plugins", async () => {
58
- const mockPlugin: Plugin = {
59
- id: "plugin.inlang.notWhitelisted",
60
- description: { en: "My plugin description" },
61
- displayName: { en: "My plugin" },
62
- loadMessages: () => undefined as any,
63
- }
64
-
65
- const resolved = await resolvePlugins({
66
- plugins: [mockPlugin],
67
- settings: {},
68
- nodeishFs: {} as any,
69
- })
71
+ expect(resolved.errors[0]).toBeInstanceOf(PluginUsesReservedNamespaceError)
72
+ })
70
73
 
71
- expect(resolved.errors[0]).toBeInstanceOf(PluginUsesReservedNamespaceError)
74
+ it("should expose the project settings including the plugin settings", async () => {
75
+ const settings: ProjectSettings = {
76
+ sourceLanguageTag: "en",
77
+ languageTags: ["en", "de"],
78
+ modules: [],
79
+ "plugin.namespace.placeholder": {
80
+ myPluginSetting: "value",
81
+ },
82
+ }
83
+ const mockPlugin: Plugin = {
84
+ id: "plugin.namespace.placeholder",
85
+ description: { en: "My plugin description" },
86
+ displayName: { en: "My plugin" },
87
+ saveMessages: async ({ settings }) => {
88
+ expect(settings).toStrictEqual(settings)
89
+ },
90
+ addCustomApi: ({ settings }) => {
91
+ expect(settings).toStrictEqual(settings)
92
+ return {}
93
+ },
94
+ loadMessages: async ({ settings }) => {
95
+ expect(settings).toStrictEqual(settings)
96
+ return []
97
+ },
98
+ }
99
+ const resolved = await resolvePlugins({
100
+ plugins: [mockPlugin],
101
+ settings: settings,
102
+ nodeishFs: {} as any,
72
103
  })
104
+ await resolved.data.loadMessages!({ settings })
105
+ await resolved.data.saveMessages!({ settings, messages: [] })
73
106
  })
74
107
 
75
108
  describe("loadMessages", () => {
@@ -83,14 +116,13 @@ describe("loadMessages", () => {
83
116
 
84
117
  const resolved = await resolvePlugins({
85
118
  plugins: [mockPlugin],
86
- settings: {},
119
+ settings: {} as any,
87
120
  nodeishFs: {} as any,
88
121
  })
89
122
 
90
123
  expect(
91
124
  await resolved.data.loadMessages!({
92
- languageTags: ["en"],
93
- sourceLanguageTag: "en",
125
+ settings: {} as any,
94
126
  })
95
127
  ).toEqual([{ id: "test", expressions: [], selectors: [], variants: [] }])
96
128
  })
@@ -112,7 +144,7 @@ describe("loadMessages", () => {
112
144
  const resolved = await resolvePlugins({
113
145
  plugins: [mockPlugin, mockPlugin2],
114
146
  nodeishFs: {} as any,
115
- settings: {},
147
+ settings: {} as any,
116
148
  })
117
149
 
118
150
  expect(resolved.errors[0]).toBeInstanceOf(PluginLoadMessagesFunctionAlreadyDefinedError)
@@ -129,7 +161,7 @@ describe("loadMessages", () => {
129
161
  const resolved = await resolvePlugins({
130
162
  plugins: [mockPlugin],
131
163
  nodeishFs: {} as any,
132
- settings: {},
164
+ settings: {} as any,
133
165
  })
134
166
 
135
167
  expect(resolved.errors).toHaveLength(1)
@@ -150,7 +182,7 @@ describe("saveMessages", () => {
150
182
  const resolved = await resolvePlugins({
151
183
  plugins: [mockPlugin],
152
184
  nodeishFs: {} as any,
153
- settings: {},
185
+ settings: {} as any,
154
186
  })
155
187
 
156
188
  expect(resolved.errors).toHaveLength(0)
@@ -173,7 +205,7 @@ describe("saveMessages", () => {
173
205
 
174
206
  const resolved = await resolvePlugins({
175
207
  plugins: [mockPlugin, mockPlugin2],
176
- settings: {},
208
+ settings: {} as any,
177
209
  nodeishFs: {} as any,
178
210
  })
179
211
 
@@ -191,7 +223,7 @@ describe("saveMessages", () => {
191
223
  const resolved = await resolvePlugins({
192
224
  plugins: [mockPlugin],
193
225
  nodeishFs: {} as any,
194
- settings: {},
226
+ settings: {} as any,
195
227
  })
196
228
  expect(resolved.errors).toHaveLength(1)
197
229
  expect(resolved.errors[0]).toBeInstanceOf(PluginsDoNotProvideLoadOrSaveMessagesError)
@@ -214,7 +246,7 @@ describe("addCustomApi", () => {
214
246
 
215
247
  const resolved = await resolvePlugins({
216
248
  plugins: [mockPlugin],
217
- settings: {},
249
+ settings: {} as any,
218
250
  nodeishFs: {} as any,
219
251
  })
220
252
 
@@ -249,7 +281,7 @@ describe("addCustomApi", () => {
249
281
 
250
282
  const resolved = await resolvePlugins({
251
283
  plugins: [mockPlugin, mockPlugin2],
252
- settings: {},
284
+ settings: {} as any,
253
285
  nodeishFs: {} as any,
254
286
  })
255
287
 
@@ -269,7 +301,7 @@ describe("addCustomApi", () => {
269
301
 
270
302
  const resolved = await resolvePlugins({
271
303
  plugins: [mockPlugin],
272
- settings: {},
304
+ settings: {} as any,
273
305
  nodeishFs: {} as any,
274
306
  })
275
307
 
@@ -292,7 +324,7 @@ describe("addCustomApi", () => {
292
324
 
293
325
  const resolved = await resolvePlugins({
294
326
  plugins: [mockPlugin],
295
- settings: {},
327
+ settings: {} as any,
296
328
  nodeishFs: {} as any,
297
329
  })
298
330
 
@@ -44,7 +44,7 @@ export const resolvePlugins: ResolvePluginsFunction = async (args) => {
44
44
  if (hasInvalidId) {
45
45
  result.errors.push(
46
46
  new PluginHasInvalidIdError(
47
- `Plugin ${plugin.id} has an invalid id "${plugin.id}". It must be kebap-case and contain a namespace like project.my-plugin.`,
47
+ `Plugin ${plugin.id} has an invalid id "${plugin.id}". It must be camelCase and contain a namespace like plugin.namespace.myPlugin.`,
48
48
  { plugin: plugin.id }
49
49
  )
50
50
  )
@@ -99,7 +99,7 @@ export const resolvePlugins: ResolvePluginsFunction = async (args) => {
99
99
  // TODO: why do we call this function 2 times (here for validation and later for retrieving the actual value)?
100
100
  const { data: customApi, error } = tryCatch(() =>
101
101
  plugin.addCustomApi!({
102
- settings: args.settings?.[plugin.id] ?? {},
102
+ settings: args.settings,
103
103
  })
104
104
  )
105
105
  if (error) {
@@ -130,7 +130,6 @@ export const resolvePlugins: ResolvePluginsFunction = async (args) => {
130
130
  result.data.loadMessages = (_args) =>
131
131
  plugin.loadMessages!({
132
132
  ..._args,
133
- settings: args.settings?.[plugin.id] ?? {},
134
133
  nodeishFs: args.nodeishFs,
135
134
  })
136
135
  }
@@ -139,7 +138,6 @@ export const resolvePlugins: ResolvePluginsFunction = async (args) => {
139
138
  result.data.saveMessages = (_args) =>
140
139
  plugin.saveMessages!({
141
140
  ..._args,
142
- settings: args.settings?.[plugin.id] ?? {},
143
141
  nodeishFs: args.nodeishFs,
144
142
  })
145
143
  }
@@ -147,7 +145,7 @@ export const resolvePlugins: ResolvePluginsFunction = async (args) => {
147
145
  if (typeof plugin.addCustomApi === "function") {
148
146
  const { data: customApi } = tryCatch(() =>
149
147
  plugin.addCustomApi!({
150
- settings: args.settings?.[plugin.id] ?? {},
148
+ settings: args.settings,
151
149
  })
152
150
  )
153
151
  if (customApi) {
@@ -1,4 +1,3 @@
1
- import type { LanguageTag } from "@inlang/language-tag"
2
1
  import type { NodeishFilesystem as LisaNodeishFilesystem } from "@lix-js/fs"
3
2
  import type {
4
3
  PluginReturnedInvalidCustomApiError,
@@ -10,8 +9,8 @@ import type {
10
9
  PluginsDoNotProvideLoadOrSaveMessagesError,
11
10
  } from "./errors.js"
12
11
  import type { Message } from "@inlang/message"
13
- import type { JSONObject } from "@inlang/json-types"
14
12
  import type { CustomApiInlangIdeExtension, Plugin } from "@inlang/plugin"
13
+ import type { ProjectSettings } from "@inlang/project-settings"
15
14
 
16
15
  /**
17
16
  * The filesystem is a subset of project lisa's nodeish filesystem.
@@ -28,7 +27,7 @@ export type NodeishFilesystemSubset = Pick<
28
27
  */
29
28
  export type ResolvePluginsFunction = (args: {
30
29
  plugins: Array<Plugin>
31
- settings: Record<Plugin["id"], JSONObject>
30
+ settings: ProjectSettings
32
31
  nodeishFs: NodeishFilesystemSubset
33
32
  }) => Promise<{
34
33
  data: ResolvedPluginApi
@@ -47,11 +46,8 @@ export type ResolvePluginsFunction = (args: {
47
46
  * The API after resolving the plugins.
48
47
  */
49
48
  export type ResolvedPluginApi = {
50
- loadMessages: (args: {
51
- languageTags: LanguageTag[]
52
- sourceLanguageTag: LanguageTag
53
- }) => Promise<Message[]> | Message[]
54
- saveMessages: (args: { messages: Message[] }) => Promise<void> | void
49
+ loadMessages: (args: { settings: ProjectSettings }) => Promise<Message[]> | Message[]
50
+ saveMessages: (args: { settings: ProjectSettings; messages: Message[] }) => Promise<void> | void
55
51
  /**
56
52
  * App specific APIs.
57
53
  *