@inlang/sdk 0.17.0 → 0.19.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.
Files changed (42) hide show
  1. package/dist/adapter/solidAdapter.test.js +16 -15
  2. package/dist/createNodeishFsWithAbsolutePaths.d.ts +2 -2
  3. package/dist/createNodeishFsWithAbsolutePaths.d.ts.map +1 -1
  4. package/dist/createNodeishFsWithAbsolutePaths.js +5 -4
  5. package/dist/createNodeishFsWithAbsolutePaths.test.js +5 -4
  6. package/dist/createNodeishFsWithWatcher.d.ts +12 -0
  7. package/dist/createNodeishFsWithWatcher.d.ts.map +1 -0
  8. package/dist/createNodeishFsWithWatcher.js +52 -0
  9. package/dist/createNodeishFsWithWatcher.test.d.ts +2 -0
  10. package/dist/createNodeishFsWithWatcher.test.d.ts.map +1 -0
  11. package/dist/createNodeishFsWithWatcher.test.js +32 -0
  12. package/dist/isAbsolutePath.test.js +1 -1
  13. package/dist/loadProject.d.ts +4 -4
  14. package/dist/loadProject.d.ts.map +1 -1
  15. package/dist/loadProject.js +45 -24
  16. package/dist/loadProject.test.js +175 -74
  17. package/dist/migrations/migrateToDirectory.d.ts +10 -0
  18. package/dist/migrations/migrateToDirectory.d.ts.map +1 -0
  19. package/dist/migrations/migrateToDirectory.js +46 -0
  20. package/dist/migrations/migrateToDirectory.test.d.ts +2 -0
  21. package/dist/migrations/migrateToDirectory.test.d.ts.map +1 -0
  22. package/dist/migrations/migrateToDirectory.test.js +48 -0
  23. package/dist/resolve-modules/plugins/resolvePlugins.js +1 -1
  24. package/dist/resolve-modules/plugins/resolvePlugins.test.js +2 -1
  25. package/dist/resolve-modules/plugins/types.d.ts +2 -1
  26. package/dist/resolve-modules/plugins/types.d.ts.map +1 -1
  27. package/dist/resolve-modules/validateModuleSettings.test.js +1 -1
  28. package/package.json +56 -56
  29. package/src/adapter/solidAdapter.test.ts +16 -15
  30. package/src/createNodeishFsWithAbsolutePaths.test.ts +5 -4
  31. package/src/createNodeishFsWithAbsolutePaths.ts +9 -5
  32. package/src/createNodeishFsWithWatcher.test.ts +40 -0
  33. package/src/createNodeishFsWithWatcher.ts +58 -0
  34. package/src/isAbsolutePath.test.ts +1 -1
  35. package/src/loadProject.test.ts +206 -75
  36. package/src/loadProject.ts +61 -31
  37. package/src/migrations/migrateToDirectory.test.ts +54 -0
  38. package/src/migrations/migrateToDirectory.ts +59 -0
  39. package/src/resolve-modules/plugins/resolvePlugins.test.ts +2 -1
  40. package/src/resolve-modules/plugins/resolvePlugins.ts +1 -1
  41. package/src/resolve-modules/plugins/types.ts +5 -2
  42. package/src/resolve-modules/validateModuleSettings.test.ts +1 -1
@@ -2,7 +2,7 @@
2
2
  import { describe, it, expect, vi } from "vitest";
3
3
  import { loadProject } from "./loadProject.js";
4
4
  import { LoadProjectInvalidArgument, ProjectSettingsFileJSONSyntaxError, ProjectSettingsFileNotFoundError, ProjectSettingsInvalidError, } from "./errors.js";
5
- import { createNodeishMemoryFs } from "@lix-js/fs";
5
+ import { createNodeishMemoryFs, normalizePath } from "@lix-js/fs";
6
6
  import { createMessage } from "./test-utilities/createMessage.js";
7
7
  import { tryCatch } from "@inlang/result";
8
8
  // ------------------------------------------------------------------------------------------------
@@ -80,11 +80,42 @@ const _import = async (name) => ({
80
80
  default: name === "plugin.js" ? mockPlugin : mockMessageLintRule,
81
81
  });
82
82
  // ------------------------------------------------------------------------------------------------
83
+ /**
84
+ * Dear Developers,
85
+ *
86
+ * Inlang projects (folders) are not like .vscode, .git, or .github folders. Treat em
87
+ * like files: they can be renamed and moved around.
88
+ */
89
+ it("should throw if a project (path) does not have a name", async () => {
90
+ const fs = createNodeishMemoryFs();
91
+ const project = await tryCatch(() => loadProject({
92
+ projectPath: "/source-code/.inlang",
93
+ nodeishFs: fs,
94
+ _import,
95
+ }));
96
+ expect(project.error).toBeInstanceOf(LoadProjectInvalidArgument);
97
+ });
98
+ it("should throw if a project path does not end with .inlang", async () => {
99
+ const fs = createNodeishMemoryFs();
100
+ const invalidPaths = [
101
+ "/source-code/frontend.inlang/settings",
102
+ "/source-code/frontend.inlang/settings.json",
103
+ "/source-code/frontend.inlang.md",
104
+ ];
105
+ for (const invalidPath of invalidPaths) {
106
+ const project = await tryCatch(() => loadProject({
107
+ projectPath: invalidPath,
108
+ nodeishFs: fs,
109
+ _import,
110
+ }));
111
+ expect(project.error).toBeInstanceOf(LoadProjectInvalidArgument);
112
+ }
113
+ });
83
114
  describe("initialization", () => {
84
- it("should throw if settingsFilePath is not an absolute path", async () => {
115
+ it("should throw if projectPath is not an absolute path", async () => {
85
116
  const fs = createNodeishMemoryFs();
86
117
  const result = await tryCatch(() => loadProject({
87
- settingsFilePath: "relative/path",
118
+ projectPath: "relative/path",
88
119
  nodeishFs: fs,
89
120
  _import,
90
121
  }));
@@ -93,10 +124,10 @@ describe("initialization", () => {
93
124
  });
94
125
  it("should resolve from a windows path", async () => {
95
126
  const fs = createNodeishMemoryFs();
96
- fs.mkdir("C:\\Users\\user\\project", { recursive: true });
97
- fs.writeFile("C:\\Users\\user\\project\\project.inlang.json", JSON.stringify(settings));
127
+ fs.mkdir("C:\\Users\\user\\project.inlang", { recursive: true });
128
+ fs.writeFile("C:\\Users\\user\\project.inlang\\settings.json", JSON.stringify(settings));
98
129
  const result = await tryCatch(() => loadProject({
99
- settingsFilePath: "C:\\Users\\user\\project\\project.inlang.json",
130
+ projectPath: "C:\\Users\\user\\project.inlang",
100
131
  nodeishFs: fs,
101
132
  _import,
102
133
  }));
@@ -108,7 +139,7 @@ describe("initialization", () => {
108
139
  const fs = createNodeishMemoryFs();
109
140
  fs.mkdir("/user/project", { recursive: true });
110
141
  const project = await loadProject({
111
- settingsFilePath: "/user/project/test.json",
142
+ projectPath: "/user/non-existend-project.inlang",
112
143
  nodeishFs: fs,
113
144
  _import,
114
145
  });
@@ -116,10 +147,10 @@ describe("initialization", () => {
116
147
  });
117
148
  it("should return an error if settings file is not a valid JSON", async () => {
118
149
  const fs = await createNodeishMemoryFs();
119
- await fs.mkdir("/user/project", { recursive: true });
120
- await fs.writeFile("/user/project/project.inlang.json", "invalid json");
150
+ await fs.mkdir("/user/project.inlang", { recursive: true });
151
+ await fs.writeFile("/user/project.inlang/settings.json", "invalid json");
121
152
  const project = await loadProject({
122
- settingsFilePath: "/user/project/project.inlang.json",
153
+ projectPath: "/user/project.inlang",
123
154
  nodeishFs: fs,
124
155
  _import,
125
156
  });
@@ -127,10 +158,10 @@ describe("initialization", () => {
127
158
  });
128
159
  it("should return an error if settings file is does not match schema", async () => {
129
160
  const fs = await createNodeishMemoryFs();
130
- await fs.mkdir("/user/project", { recursive: true });
131
- await fs.writeFile("/user/project/project.inlang.json", JSON.stringify({}));
161
+ await fs.mkdir("/user/project.inlang", { recursive: true });
162
+ await fs.writeFile("/user/project.inlang/settings.json", JSON.stringify({}));
132
163
  const project = await loadProject({
133
- settingsFilePath: "/user/project/project.inlang.json",
164
+ projectPath: "/user/project.inlang",
134
165
  nodeishFs: fs,
135
166
  _import,
136
167
  });
@@ -138,10 +169,10 @@ describe("initialization", () => {
138
169
  });
139
170
  it("should return the parsed settings", async () => {
140
171
  const fs = await createNodeishMemoryFs();
141
- await fs.mkdir("/user/project", { recursive: true });
142
- await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings));
172
+ await fs.mkdir("/user/project.inlang", { recursive: true });
173
+ await fs.writeFile("/user/project.inlang/settings.json", JSON.stringify(settings));
143
174
  const project = await loadProject({
144
- settingsFilePath: "/user/project/project.inlang.json",
175
+ projectPath: "/user/project.inlang",
145
176
  nodeishFs: fs,
146
177
  _import,
147
178
  });
@@ -150,21 +181,21 @@ describe("initialization", () => {
150
181
  it("should not re-write the settings to disk when initializing", async () => {
151
182
  const fs = await createNodeishMemoryFs();
152
183
  const settingsWithDeifferentFormatting = JSON.stringify(settings, undefined, 4);
153
- await fs.mkdir("/user/project", { recursive: true });
154
- await fs.writeFile("/user/project/project.inlang.json", settingsWithDeifferentFormatting);
184
+ await fs.mkdir("/user/project.inlang", { recursive: true });
185
+ await fs.writeFile("/user/project.inlang/settings.json", settingsWithDeifferentFormatting);
155
186
  const project = await loadProject({
156
- settingsFilePath: "/user/project/project.inlang.json",
187
+ projectPath: "/user/project.inlang",
157
188
  nodeishFs: fs,
158
189
  _import,
159
190
  });
160
- const settingsOnDisk = await fs.readFile("/user/project/project.inlang.json", {
191
+ const settingsOnDisk = await fs.readFile("/user/project.inlang/settings.json", {
161
192
  encoding: "utf-8",
162
193
  });
163
194
  expect(settingsOnDisk).toBe(settingsWithDeifferentFormatting);
164
195
  project.setSettings(project.settings());
165
196
  // TODO: how can we await `setsettings` correctly
166
197
  await new Promise((resolve) => setTimeout(resolve, 0));
167
- const newsettingsOnDisk = await fs.readFile("/user/project/project.inlang.json", {
198
+ const newsettingsOnDisk = await fs.readFile("/user/project.inlang/settings.json", {
168
199
  encoding: "utf-8",
169
200
  });
170
201
  expect(newsettingsOnDisk).not.toBe(settingsWithDeifferentFormatting);
@@ -176,10 +207,10 @@ describe("initialization", () => {
176
207
  default: {},
177
208
  });
178
209
  const fs = createNodeishMemoryFs();
179
- await fs.mkdir("/user/project", { recursive: true });
180
- await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings));
210
+ await fs.mkdir("/user/project.inlang", { recursive: true });
211
+ await fs.writeFile("/user/project.inlang/settings.json", JSON.stringify(settings));
181
212
  const project = await loadProject({
182
- settingsFilePath: "/user/project/project.inlang.json",
213
+ projectPath: "/user/project.inlang",
183
214
  nodeishFs: fs,
184
215
  _import: $badImport,
185
216
  });
@@ -204,10 +235,10 @@ describe("functionality", () => {
204
235
  describe("settings", () => {
205
236
  it("should return the settings", async () => {
206
237
  const fs = await createNodeishMemoryFs();
207
- await fs.mkdir("/user/project", { recursive: true });
208
- await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings));
238
+ await fs.mkdir("/user/project.inlang", { recursive: true });
239
+ await fs.writeFile("/user/project.inlang/settings.json", JSON.stringify(settings));
209
240
  const project = await loadProject({
210
- settingsFilePath: "/user/project/project.inlang.json",
241
+ projectPath: "/user/project.inlang",
211
242
  nodeishFs: fs,
212
243
  _import,
213
244
  });
@@ -215,10 +246,10 @@ describe("functionality", () => {
215
246
  });
216
247
  it("should set a new settings", async () => {
217
248
  const fs = await createNodeishMemoryFs();
218
- await fs.mkdir("/user/project", { recursive: true });
219
- await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings));
249
+ await fs.mkdir("/user/project.inlang", { recursive: true });
250
+ await fs.writeFile("/user/project.inlang/settings.json", JSON.stringify(settings));
220
251
  const project = await loadProject({
221
- settingsFilePath: "/user/project/project.inlang.json",
252
+ projectPath: "/user/project.inlang",
222
253
  nodeishFs: fs,
223
254
  _import,
224
255
  });
@@ -235,12 +266,12 @@ describe("functionality", () => {
235
266
  });
236
267
  });
237
268
  describe("setSettings", () => {
238
- it("should fail if settings is not valid", async () => {
269
+ it("should fail if settings are not valid", async () => {
239
270
  const fs = await createNodeishMemoryFs();
240
- await fs.mkdir("/user/project", { recursive: true });
241
- await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings));
271
+ await fs.mkdir("/user/project.inlang", { recursive: true });
272
+ await fs.writeFile("/user/project.inlang/settings.json", JSON.stringify(settings));
242
273
  const project = await loadProject({
243
- settingsFilePath: "/user/project/project.inlang.json",
274
+ projectPath: "/user/project.inlang",
244
275
  nodeishFs: fs,
245
276
  _import,
246
277
  });
@@ -250,15 +281,15 @@ describe("functionality", () => {
250
281
  });
251
282
  it("should throw an error if sourceLanguageTag is not in languageTags", async () => {
252
283
  const fs = await createNodeishMemoryFs();
253
- await fs.mkdir("/user/project", { recursive: true });
284
+ await fs.mkdir("/user/project.inlang", { recursive: true });
254
285
  const settings = {
255
286
  sourceLanguageTag: "en",
256
287
  languageTags: ["de"],
257
288
  modules: [],
258
289
  };
259
- await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings));
290
+ await fs.writeFile("/user/project.inlang/settings.json", JSON.stringify(settings));
260
291
  const project = await loadProject({
261
- settingsFilePath: "/user/project/project.inlang.json",
292
+ projectPath: "/user/project.inlang",
262
293
  nodeishFs: fs,
263
294
  _import,
264
295
  });
@@ -267,21 +298,21 @@ describe("functionality", () => {
267
298
  });
268
299
  it("should write settings to disk", async () => {
269
300
  const fs = await createNodeishMemoryFs();
270
- await fs.mkdir("/user/project", { recursive: true });
271
- await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings));
301
+ await fs.mkdir("/user/project.inlang", { recursive: true });
302
+ await fs.writeFile("/user/project.inlang/settings.json", JSON.stringify(settings));
272
303
  const project = await loadProject({
273
- settingsFilePath: "/user/project/project.inlang.json",
304
+ projectPath: "/user/project.inlang",
274
305
  nodeishFs: fs,
275
306
  _import,
276
307
  });
277
- const before = await fs.readFile("/user/project/project.inlang.json", { encoding: "utf-8" });
308
+ const before = await fs.readFile("/user/project.inlang/settings.json", { encoding: "utf-8" });
278
309
  expect(before).toBeDefined();
279
- const result = project.setSettings({ ...settings, languageTags: ["en"] });
310
+ const result = project.setSettings({ ...settings, languageTags: ["en", "nl", "de"] });
280
311
  expect(result.data).toBeUndefined();
281
312
  expect(result.error).toBeUndefined();
282
313
  // TODO: how to wait for fs.writeFile to finish?
283
- await new Promise((resolve) => setTimeout(resolve, 0));
284
- const after = await fs.readFile("/user/project/project.inlang.json", { encoding: "utf-8" });
314
+ await new Promise((resolve) => setTimeout(resolve, 50));
315
+ const after = await fs.readFile("/user/project.inlang/settings.json", { encoding: "utf-8" });
285
316
  expect(after).toBeDefined();
286
317
  expect(after).not.toBe(before);
287
318
  });
@@ -294,10 +325,10 @@ describe("functionality", () => {
294
325
  languageTags: ["en"],
295
326
  modules: ["plugin.js", "lintRule.js"],
296
327
  };
297
- await fs.mkdir("/user/project", { recursive: true });
298
- await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings));
328
+ await fs.mkdir("/user/project.inlang", { recursive: true });
329
+ await fs.writeFile("/user/project.inlang/settings.json", JSON.stringify(settings));
299
330
  const project = await loadProject({
300
- settingsFilePath: "/user/project/project.inlang.json",
331
+ projectPath: "/user/project.inlang",
301
332
  nodeishFs: fs,
302
333
  _import,
303
334
  });
@@ -322,10 +353,10 @@ describe("functionality", () => {
322
353
  languageTags: ["en"],
323
354
  modules: ["plugin.js", "lintRule.js"],
324
355
  };
325
- await fs.mkdir("/user/project", { recursive: true });
326
- await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings));
356
+ await fs.mkdir("/user/project.inlang", { recursive: true });
357
+ await fs.writeFile("/user/project.inlang/settings.json", JSON.stringify(settings));
327
358
  const project = await loadProject({
328
- settingsFilePath: "/user/project/project.inlang.json",
359
+ projectPath: "/user/project.inlang",
329
360
  nodeishFs: fs,
330
361
  _import,
331
362
  });
@@ -353,8 +384,8 @@ describe("functionality", () => {
353
384
  saveMessages: () => undefined,
354
385
  };
355
386
  const fs = await createNodeishMemoryFs();
356
- await fs.mkdir("/user/project", { recursive: true });
357
- await fs.writeFile("/user/project/project.inlang.json", JSON.stringify({
387
+ await fs.mkdir("/user/project.inlang", { recursive: true });
388
+ await fs.writeFile("/user/project.inlang/settings.json", JSON.stringify({
358
389
  sourceLanguageTag: "en",
359
390
  languageTags: ["en"],
360
391
  modules: ["plugin.js", "lintRule.js"],
@@ -365,7 +396,7 @@ describe("functionality", () => {
365
396
  };
366
397
  };
367
398
  const project = await loadProject({
368
- settingsFilePath: "/user/project/project.inlang.json",
399
+ projectPath: "/user/project.inlang",
369
400
  nodeishFs: fs,
370
401
  _import,
371
402
  });
@@ -395,8 +426,8 @@ describe("functionality", () => {
395
426
  saveMessages: () => undefined,
396
427
  };
397
428
  const fs = await createNodeishMemoryFs();
398
- await fs.mkdir("/user/project", { recursive: true });
399
- await fs.writeFile("/user/project/project.inlang.json", JSON.stringify({
429
+ await fs.mkdir("/user/project.inlang", { recursive: true });
430
+ await fs.writeFile("/user/project.inlang/settings.json", JSON.stringify({
400
431
  sourceLanguageTag: "en",
401
432
  languageTags: ["en"],
402
433
  modules: ["plugin.js", "lintRule.js"],
@@ -407,7 +438,7 @@ describe("functionality", () => {
407
438
  };
408
439
  };
409
440
  const project = await loadProject({
410
- settingsFilePath: "/user/project/project.inlang.json",
441
+ projectPath: "/user/project.inlang",
411
442
  nodeishFs: fs,
412
443
  _import,
413
444
  });
@@ -418,10 +449,10 @@ describe("functionality", () => {
418
449
  describe("errors", () => {
419
450
  it("should return the errors", async () => {
420
451
  const fs = await createNodeishMemoryFs();
421
- await fs.mkdir("/user/project", { recursive: true });
422
- await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings));
452
+ await fs.mkdir("/user/project.inlang", { recursive: true });
453
+ await fs.writeFile("/user/project.inlang/settings.json", JSON.stringify(settings));
423
454
  const project = await loadProject({
424
- settingsFilePath: "/user/project/project.inlang.json",
455
+ projectPath: "/user/project.inlang",
425
456
  nodeishFs: fs,
426
457
  _import,
427
458
  });
@@ -433,10 +464,10 @@ describe("functionality", () => {
433
464
  describe("customApi", () => {
434
465
  it("should return the app specific api", async () => {
435
466
  const fs = await createNodeishMemoryFs();
436
- await fs.mkdir("/user/project", { recursive: true });
437
- await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings));
467
+ await fs.mkdir("/user/project.inlang", { recursive: true });
468
+ await fs.writeFile("/user/project.inlang/settings.json", JSON.stringify(settings));
438
469
  const project = await loadProject({
439
- settingsFilePath: "/user/project/project.inlang.json",
470
+ projectPath: "/user/project.inlang",
440
471
  nodeishFs: fs,
441
472
  _import,
442
473
  });
@@ -448,10 +479,10 @@ describe("functionality", () => {
448
479
  describe("messages", () => {
449
480
  it("should return the messages", async () => {
450
481
  const fs = await createNodeishMemoryFs();
451
- await fs.mkdir("/user/project", { recursive: true });
452
- await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings));
482
+ await fs.mkdir("/user/project.inlang", { recursive: true });
483
+ await fs.writeFile("/user/project.inlang/settings.json", JSON.stringify(settings));
453
484
  const project = await loadProject({
454
- settingsFilePath: "/user/project/project.inlang.json",
485
+ projectPath: "/user/project.inlang",
455
486
  nodeishFs: fs,
456
487
  _import,
457
488
  });
@@ -469,8 +500,8 @@ describe("functionality", () => {
469
500
  pathPattern: "./resources/{languageTag}.json",
470
501
  },
471
502
  };
472
- await fs.mkdir("/user/project", { recursive: true });
473
- await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings));
503
+ await fs.mkdir("/user/project.inlang", { recursive: true });
504
+ await fs.writeFile("/user/project.inlang/settings.json", JSON.stringify(settings));
474
505
  await fs.mkdir("./resources");
475
506
  const mockSaveFn = vi.fn();
476
507
  const _mockPlugin = {
@@ -486,7 +517,7 @@ describe("functionality", () => {
486
517
  };
487
518
  };
488
519
  const project = await loadProject({
489
- settingsFilePath: "/user/project/project.inlang.json",
520
+ projectPath: "/user/project.inlang",
490
521
  nodeishFs: fs,
491
522
  _import,
492
523
  });
@@ -631,7 +662,8 @@ describe("functionality", () => {
631
662
  pathPattern: "./resources/{languageTag}.json",
632
663
  },
633
664
  };
634
- await fs.writeFile("./project.inlang.json", JSON.stringify(settings));
665
+ await fs.mkdir("./project.inlang", { recursive: true });
666
+ await fs.writeFile("./project.inlang/settings.json", JSON.stringify(settings));
635
667
  const mockSaveFn = vi.fn();
636
668
  const _mockPlugin = {
637
669
  id: "plugin.placeholder.name",
@@ -650,7 +682,7 @@ describe("functionality", () => {
650
682
  };
651
683
  };
652
684
  const project = await loadProject({
653
- settingsFilePath: "/project.inlang.json",
685
+ projectPath: "/project.inlang",
654
686
  nodeishFs: fs,
655
687
  _import,
656
688
  });
@@ -666,7 +698,7 @@ describe("functionality", () => {
666
698
  await fs.mkdir("/user/project", { recursive: true });
667
699
  await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings));
668
700
  const project = await loadProject({
669
- settingsFilePath: "/user/project/project.inlang.json",
701
+ projectPath: "/user/project/project.inlang.json",
670
702
  nodeishFs: fs,
671
703
  _import,
672
704
  });
@@ -686,10 +718,10 @@ describe("functionality", () => {
686
718
  modules: ["lintRule.js"],
687
719
  };
688
720
  const fs = createNodeishMemoryFs();
689
- await fs.mkdir("/user/project", { recursive: true });
690
- await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings));
721
+ await fs.mkdir("/user/project.inlang", { recursive: true });
722
+ await fs.writeFile("/user/project.inlang/settings.json", JSON.stringify(settings));
691
723
  const project = await loadProject({
692
- settingsFilePath: "/user/project/project.inlang.json",
724
+ projectPath: "/user/project.inlang",
693
725
  nodeishFs: fs,
694
726
  _import: async () => ({
695
727
  default: mockMessageLintRule,
@@ -699,4 +731,73 @@ describe("functionality", () => {
699
731
  project.query.messageLintReports.getAll.subscribe((r) => expect(r).toEqual([]));
700
732
  });
701
733
  });
734
+ describe("watcher", () => {
735
+ it("changing files in resources should trigger callback of message query", async () => {
736
+ const fs = createNodeishMemoryFs();
737
+ const messages = {
738
+ $schema: "https://inlang.com/schema/inlang-message-format",
739
+ data: [
740
+ {
741
+ id: "test",
742
+ selectors: [],
743
+ variants: [
744
+ {
745
+ match: [],
746
+ languageTag: "en",
747
+ pattern: [
748
+ {
749
+ type: "Text",
750
+ value: "test",
751
+ },
752
+ ],
753
+ },
754
+ ],
755
+ },
756
+ ],
757
+ };
758
+ await fs.writeFile("./messages.json", JSON.stringify(messages));
759
+ const getMessages = async (customFs) => {
760
+ const file = await customFs.readFile("./messages.json", { encoding: "utf-8" });
761
+ return JSON.parse(file.toString()).data;
762
+ };
763
+ const mockMessageFormatPlugin = {
764
+ id: "plugin.inlang.messageFormat",
765
+ description: { en: "Mock plugin description" },
766
+ displayName: { en: "Mock Plugin" },
767
+ loadMessages: async (args) => await getMessages(args.nodeishFs),
768
+ saveMessages: () => undefined,
769
+ };
770
+ const settings = {
771
+ sourceLanguageTag: "en",
772
+ languageTags: ["en"],
773
+ modules: ["plugin.js"],
774
+ "plugin.inlang.messageFormat": {
775
+ filePath: "./messages.json",
776
+ },
777
+ };
778
+ await fs.mkdir("./project.inlang", { recursive: true });
779
+ await fs.writeFile("./project.inlang/settings.json", JSON.stringify(settings));
780
+ // establish watcher
781
+ const project = await loadProject({
782
+ projectPath: normalizePath("/project.inlang"),
783
+ nodeishFs: fs,
784
+ _import: async () => ({
785
+ default: mockMessageFormatPlugin,
786
+ }),
787
+ });
788
+ let counter = 0;
789
+ project.query.messages.getAll.subscribe(() => {
790
+ counter = counter + 1;
791
+ });
792
+ expect(counter).toBe(1);
793
+ // change file
794
+ await fs.writeFile("./messages.json", JSON.stringify(messages));
795
+ await new Promise((resolve) => setTimeout(resolve, 0));
796
+ expect(counter).toBe(2);
797
+ // change file
798
+ await fs.writeFile("./messages.json", JSON.stringify(messages));
799
+ await new Promise((resolve) => setTimeout(resolve, 0));
800
+ expect(counter).toBe(3);
801
+ });
802
+ });
702
803
  });
@@ -0,0 +1,10 @@
1
+ import type { NodeishFilesystem } from "@lix-js/fs";
2
+ /**
3
+ * Migrates to the new project directory structure
4
+ * https://github.com/inlang/monorepo/issues/1678
5
+ */
6
+ export declare const maybeMigrateToDirectory: (args: {
7
+ nodeishFs: NodeishFilesystem;
8
+ projectPath: string;
9
+ }) => Promise<void>;
10
+ //# sourceMappingURL=migrateToDirectory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrateToDirectory.d.ts","sourceRoot":"","sources":["../../src/migrations/migrateToDirectory.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAEnD;;;GAGG;AACH,eAAO,MAAM,uBAAuB,SAAgB;IACnD,SAAS,EAAE,iBAAiB,CAAA;IAC5B,WAAW,EAAE,MAAM,CAAA;CACnB,kBA0BA,CAAA"}
@@ -0,0 +1,46 @@
1
+ import { tryCatch } from "@inlang/result";
2
+ /**
3
+ * Migrates to the new project directory structure
4
+ * https://github.com/inlang/monorepo/issues/1678
5
+ */
6
+ export const maybeMigrateToDirectory = async (args) => {
7
+ // the migration assumes that the projectPath ends with project.inlang
8
+ if (args.projectPath.endsWith("project.inlang") === false) {
9
+ return;
10
+ }
11
+ // we assume that stat will throw when the directory does not exist
12
+ const projectDirectory = await tryCatch(() => args.nodeishFs.stat(args.projectPath));
13
+ // the migration has already been conducted.
14
+ if (projectDirectory.data) {
15
+ return;
16
+ }
17
+ const settingsFile = await tryCatch(() => args.nodeishFs.readFile(args.projectPath + ".json", { encoding: "utf-8" }));
18
+ // the settings file does not exist or something else is wrong, let loadProject handle it
19
+ if (settingsFile.error) {
20
+ return;
21
+ }
22
+ await args.nodeishFs.mkdir(args.projectPath);
23
+ await args.nodeishFs.writeFile(`${args.projectPath}/settings.json`, settingsFile.data);
24
+ await args.nodeishFs.writeFile(args.projectPath + ".README.md", readme);
25
+ };
26
+ const readme = `
27
+ # DELETE THE \`project.inlang.json\` FILE
28
+
29
+ The \`project.inlang.json\` file is now contained in a project directory e.g. \`project.inlang/settings.json\`.
30
+
31
+
32
+ ## What you need to do
33
+
34
+ 1. Update the inlang CLI (if you use it) to use the new path \`project.inlang\` instead of \`project.inlang.json\`.
35
+ 2. Delete the \`project.inlang.json\` file.
36
+
37
+
38
+ ## Why is this happening?
39
+
40
+ See this RFC https://docs.google.com/document/d/1OYyA1wYfQRbIJOIBDliYoWjiUlkFBNxH_U2R4WpVRZ4/edit#heading=h.pecv6xb7ial6
41
+ and the following GitHub issue for more information https://github.com/inlang/monorepo/issues/1678.
42
+
43
+ - Monorepo support https://github.com/inlang/monorepo/discussions/258.
44
+ - Required for many other future features like caching, first class offline support, and more.
45
+ - Stablize the inlang project format.
46
+ `;
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=migrateToDirectory.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrateToDirectory.test.d.ts","sourceRoot":"","sources":["../../src/migrations/migrateToDirectory.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,48 @@
1
+ import { test, expect, vi } from "vitest";
2
+ import { maybeMigrateToDirectory } from "./migrateToDirectory.js";
3
+ import { createNodeishMemoryFs } from "@lix-js/fs";
4
+ test("it should return if the settings file has an error (let loadProject handle it)", async () => {
5
+ const projectPath = "./project.inlang";
6
+ const mockFs = {
7
+ stat: vi.fn(() => { }),
8
+ readFile: vi.fn(() => {
9
+ throw Error();
10
+ }),
11
+ };
12
+ await maybeMigrateToDirectory({ nodeishFs: mockFs, projectPath });
13
+ // something goes wrong in readFile
14
+ expect(mockFs.readFile).toHaveBeenCalled();
15
+ });
16
+ test("it should create the project directory if it does not exist and a project settings file exists", async () => {
17
+ const projectPath = "./project.inlang";
18
+ const mockFs = {
19
+ readFile: vi.fn(() => `{
20
+ "sourceLanguageTag": "en",
21
+ "languageTags": ["en", "de"],
22
+ "modules": []
23
+ }`),
24
+ stat: vi.fn(() => {
25
+ throw Error();
26
+ }),
27
+ mkdir: vi.fn(),
28
+ writeFile: vi.fn(),
29
+ };
30
+ await maybeMigrateToDirectory({ nodeishFs: mockFs, projectPath });
31
+ expect(mockFs.mkdir).toHaveBeenCalled();
32
+ expect(mockFs.writeFile).toHaveBeenCalled();
33
+ });
34
+ test("it should write the settings file to the new path", async () => {
35
+ const fs = createNodeishMemoryFs();
36
+ const mockSettings = {
37
+ sourceLanguageTag: "en",
38
+ languageTags: ["en", "de"],
39
+ modules: [],
40
+ };
41
+ await fs.writeFile("./project.inlang.json", JSON.stringify(mockSettings));
42
+ await maybeMigrateToDirectory({ nodeishFs: fs, projectPath: "./project.inlang" });
43
+ const migratedSettingsFile = await fs.readFile("./project.inlang/settings.json", {
44
+ encoding: "utf-8",
45
+ });
46
+ expect(migratedSettingsFile).toEqual(JSON.stringify(mockSettings));
47
+ expect(await fs.stat("./project.inlang.README.md")).toBeDefined();
48
+ });
@@ -64,7 +64,7 @@ export const resolvePlugins = async (args) => {
64
64
  if (typeof plugin.loadMessages === "function") {
65
65
  result.data.loadMessages = (_args) => plugin.loadMessages({
66
66
  ..._args,
67
- nodeishFs: args.nodeishFs,
67
+ // renoved nodeishFs from args because we need to pass custom wrapped fs that establishes a watcher
68
68
  });
69
69
  }
70
70
  if (typeof plugin.saveMessages === "function") {
@@ -48,7 +48,7 @@ it("should expose the project settings including the plugin settings", async ()
48
48
  settings: settings,
49
49
  nodeishFs: {},
50
50
  });
51
- await resolved.data.loadMessages({ settings });
51
+ await resolved.data.loadMessages({ settings, nodeishFs: {} });
52
52
  await resolved.data.saveMessages({ settings, messages: [] });
53
53
  });
54
54
  describe("loadMessages", () => {
@@ -66,6 +66,7 @@ describe("loadMessages", () => {
66
66
  });
67
67
  expect(await resolved.data.loadMessages({
68
68
  settings: {},
69
+ nodeishFs: {},
69
70
  })).toEqual([{ id: "test", expressions: [], selectors: [], variants: [] }]);
70
71
  });
71
72
  it("should collect an error if function is defined twice in multiple plugins", async () => {