@inlang/sdk 0.18.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 (31) 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 +4 -4
  5. package/dist/createNodeishFsWithAbsolutePaths.test.js +4 -4
  6. package/dist/createNodeishFsWithWatcher.d.ts +1 -1
  7. package/dist/createNodeishFsWithWatcher.d.ts.map +1 -1
  8. package/dist/createNodeishFsWithWatcher.js +6 -4
  9. package/dist/isAbsolutePath.test.js +1 -1
  10. package/dist/loadProject.d.ts +4 -4
  11. package/dist/loadProject.d.ts.map +1 -1
  12. package/dist/loadProject.js +23 -17
  13. package/dist/loadProject.test.js +108 -75
  14. package/dist/migrations/migrateToDirectory.d.ts +10 -0
  15. package/dist/migrations/migrateToDirectory.d.ts.map +1 -0
  16. package/dist/migrations/migrateToDirectory.js +46 -0
  17. package/dist/migrations/migrateToDirectory.test.d.ts +2 -0
  18. package/dist/migrations/migrateToDirectory.test.d.ts.map +1 -0
  19. package/dist/migrations/migrateToDirectory.test.js +48 -0
  20. package/dist/resolve-modules/validateModuleSettings.test.js +1 -1
  21. package/package.json +56 -56
  22. package/src/adapter/solidAdapter.test.ts +16 -15
  23. package/src/createNodeishFsWithAbsolutePaths.test.ts +4 -4
  24. package/src/createNodeishFsWithAbsolutePaths.ts +5 -5
  25. package/src/createNodeishFsWithWatcher.ts +6 -4
  26. package/src/isAbsolutePath.test.ts +1 -1
  27. package/src/loadProject.test.ts +116 -75
  28. package/src/loadProject.ts +36 -22
  29. package/src/migrations/migrateToDirectory.test.ts +54 -0
  30. package/src/migrations/migrateToDirectory.ts +59 -0
  31. package/src/resolve-modules/validateModuleSettings.test.ts +1 -1
@@ -105,13 +105,52 @@ const _import: ImportFunction = async (name) =>
105
105
 
106
106
  // ------------------------------------------------------------------------------------------------
107
107
 
108
+ /**
109
+ * Dear Developers,
110
+ *
111
+ * Inlang projects (folders) are not like .vscode, .git, or .github folders. Treat em
112
+ * like files: they can be renamed and moved around.
113
+ */
114
+ it("should throw if a project (path) does not have a name", async () => {
115
+ const fs = createNodeishMemoryFs()
116
+ const project = await tryCatch(() =>
117
+ loadProject({
118
+ projectPath: "/source-code/.inlang",
119
+ nodeishFs: fs,
120
+ _import,
121
+ })
122
+ )
123
+ expect(project.error).toBeInstanceOf(LoadProjectInvalidArgument)
124
+ })
125
+
126
+ it("should throw if a project path does not end with .inlang", async () => {
127
+ const fs = createNodeishMemoryFs()
128
+
129
+ const invalidPaths = [
130
+ "/source-code/frontend.inlang/settings",
131
+ "/source-code/frontend.inlang/settings.json",
132
+ "/source-code/frontend.inlang.md",
133
+ ]
134
+
135
+ for (const invalidPath of invalidPaths) {
136
+ const project = await tryCatch(() =>
137
+ loadProject({
138
+ projectPath: invalidPath,
139
+ nodeishFs: fs,
140
+ _import,
141
+ })
142
+ )
143
+ expect(project.error).toBeInstanceOf(LoadProjectInvalidArgument)
144
+ }
145
+ })
146
+
108
147
  describe("initialization", () => {
109
- it("should throw if settingsFilePath is not an absolute path", async () => {
148
+ it("should throw if projectPath is not an absolute path", async () => {
110
149
  const fs = createNodeishMemoryFs()
111
150
 
112
151
  const result = await tryCatch(() =>
113
152
  loadProject({
114
- settingsFilePath: "relative/path",
153
+ projectPath: "relative/path",
115
154
  nodeishFs: fs,
116
155
  _import,
117
156
  })
@@ -122,12 +161,12 @@ describe("initialization", () => {
122
161
 
123
162
  it("should resolve from a windows path", async () => {
124
163
  const fs = createNodeishMemoryFs()
125
- fs.mkdir("C:\\Users\\user\\project", { recursive: true })
126
- fs.writeFile("C:\\Users\\user\\project\\project.inlang.json", JSON.stringify(settings))
164
+ fs.mkdir("C:\\Users\\user\\project.inlang", { recursive: true })
165
+ fs.writeFile("C:\\Users\\user\\project.inlang\\settings.json", JSON.stringify(settings))
127
166
 
128
167
  const result = await tryCatch(() =>
129
168
  loadProject({
130
- settingsFilePath: "C:\\Users\\user\\project\\project.inlang.json",
169
+ projectPath: "C:\\Users\\user\\project.inlang",
131
170
  nodeishFs: fs,
132
171
  _import,
133
172
  })
@@ -143,7 +182,7 @@ describe("initialization", () => {
143
182
  fs.mkdir("/user/project", { recursive: true })
144
183
 
145
184
  const project = await loadProject({
146
- settingsFilePath: "/user/project/test.json",
185
+ projectPath: "/user/non-existend-project.inlang",
147
186
  nodeishFs: fs,
148
187
  _import,
149
188
  })
@@ -153,11 +192,11 @@ describe("initialization", () => {
153
192
 
154
193
  it("should return an error if settings file is not a valid JSON", async () => {
155
194
  const fs = await createNodeishMemoryFs()
156
- await fs.mkdir("/user/project", { recursive: true })
157
- await fs.writeFile("/user/project/project.inlang.json", "invalid json")
195
+ await fs.mkdir("/user/project.inlang", { recursive: true })
196
+ await fs.writeFile("/user/project.inlang/settings.json", "invalid json")
158
197
 
159
198
  const project = await loadProject({
160
- settingsFilePath: "/user/project/project.inlang.json",
199
+ projectPath: "/user/project.inlang",
161
200
  nodeishFs: fs,
162
201
  _import,
163
202
  })
@@ -167,11 +206,11 @@ describe("initialization", () => {
167
206
 
168
207
  it("should return an error if settings file is does not match schema", async () => {
169
208
  const fs = await createNodeishMemoryFs()
170
- await fs.mkdir("/user/project", { recursive: true })
171
- await fs.writeFile("/user/project/project.inlang.json", JSON.stringify({}))
209
+ await fs.mkdir("/user/project.inlang", { recursive: true })
210
+ await fs.writeFile("/user/project.inlang/settings.json", JSON.stringify({}))
172
211
 
173
212
  const project = await loadProject({
174
- settingsFilePath: "/user/project/project.inlang.json",
213
+ projectPath: "/user/project.inlang",
175
214
  nodeishFs: fs,
176
215
  _import,
177
216
  })
@@ -181,10 +220,10 @@ describe("initialization", () => {
181
220
 
182
221
  it("should return the parsed settings", async () => {
183
222
  const fs = await createNodeishMemoryFs()
184
- await fs.mkdir("/user/project", { recursive: true })
185
- await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings))
223
+ await fs.mkdir("/user/project.inlang", { recursive: true })
224
+ await fs.writeFile("/user/project.inlang/settings.json", JSON.stringify(settings))
186
225
  const project = await loadProject({
187
- settingsFilePath: "/user/project/project.inlang.json",
226
+ projectPath: "/user/project.inlang",
188
227
  nodeishFs: fs,
189
228
  _import,
190
229
  })
@@ -195,16 +234,16 @@ describe("initialization", () => {
195
234
  it("should not re-write the settings to disk when initializing", async () => {
196
235
  const fs = await createNodeishMemoryFs()
197
236
  const settingsWithDeifferentFormatting = JSON.stringify(settings, undefined, 4)
198
- await fs.mkdir("/user/project", { recursive: true })
199
- await fs.writeFile("/user/project/project.inlang.json", settingsWithDeifferentFormatting)
237
+ await fs.mkdir("/user/project.inlang", { recursive: true })
238
+ await fs.writeFile("/user/project.inlang/settings.json", settingsWithDeifferentFormatting)
200
239
 
201
240
  const project = await loadProject({
202
- settingsFilePath: "/user/project/project.inlang.json",
241
+ projectPath: "/user/project.inlang",
203
242
  nodeishFs: fs,
204
243
  _import,
205
244
  })
206
245
 
207
- const settingsOnDisk = await fs.readFile("/user/project/project.inlang.json", {
246
+ const settingsOnDisk = await fs.readFile("/user/project.inlang/settings.json", {
208
247
  encoding: "utf-8",
209
248
  })
210
249
  expect(settingsOnDisk).toBe(settingsWithDeifferentFormatting)
@@ -213,7 +252,7 @@ describe("initialization", () => {
213
252
  // TODO: how can we await `setsettings` correctly
214
253
  await new Promise((resolve) => setTimeout(resolve, 0))
215
254
 
216
- const newsettingsOnDisk = await fs.readFile("/user/project/project.inlang.json", {
255
+ const newsettingsOnDisk = await fs.readFile("/user/project.inlang/settings.json", {
217
256
  encoding: "utf-8",
218
257
  })
219
258
  expect(newsettingsOnDisk).not.toBe(settingsWithDeifferentFormatting)
@@ -228,11 +267,11 @@ describe("initialization", () => {
228
267
  } satisfies InlangModule)
229
268
 
230
269
  const fs = createNodeishMemoryFs()
231
- await fs.mkdir("/user/project", { recursive: true })
232
- await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings))
270
+ await fs.mkdir("/user/project.inlang", { recursive: true })
271
+ await fs.writeFile("/user/project.inlang/settings.json", JSON.stringify(settings))
233
272
 
234
273
  const project = await loadProject({
235
- settingsFilePath: "/user/project/project.inlang.json",
274
+ projectPath: "/user/project.inlang",
236
275
  nodeishFs: fs,
237
276
  _import: $badImport,
238
277
  })
@@ -261,10 +300,10 @@ describe("functionality", () => {
261
300
  describe("settings", () => {
262
301
  it("should return the settings", async () => {
263
302
  const fs = await createNodeishMemoryFs()
264
- await fs.mkdir("/user/project", { recursive: true })
265
- await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings))
303
+ await fs.mkdir("/user/project.inlang", { recursive: true })
304
+ await fs.writeFile("/user/project.inlang/settings.json", JSON.stringify(settings))
266
305
  const project = await loadProject({
267
- settingsFilePath: "/user/project/project.inlang.json",
306
+ projectPath: "/user/project.inlang",
268
307
  nodeishFs: fs,
269
308
  _import,
270
309
  })
@@ -274,10 +313,10 @@ describe("functionality", () => {
274
313
 
275
314
  it("should set a new settings", async () => {
276
315
  const fs = await createNodeishMemoryFs()
277
- await fs.mkdir("/user/project", { recursive: true })
278
- await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings))
316
+ await fs.mkdir("/user/project.inlang", { recursive: true })
317
+ await fs.writeFile("/user/project.inlang/settings.json", JSON.stringify(settings))
279
318
  const project = await loadProject({
280
- settingsFilePath: "/user/project/project.inlang.json",
319
+ projectPath: "/user/project.inlang",
281
320
  nodeishFs: fs,
282
321
  _import,
283
322
  })
@@ -298,12 +337,12 @@ describe("functionality", () => {
298
337
  })
299
338
 
300
339
  describe("setSettings", () => {
301
- it("should fail if settings is not valid", async () => {
340
+ it("should fail if settings are not valid", async () => {
302
341
  const fs = await createNodeishMemoryFs()
303
- await fs.mkdir("/user/project", { recursive: true })
304
- await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings))
342
+ await fs.mkdir("/user/project.inlang", { recursive: true })
343
+ await fs.writeFile("/user/project.inlang/settings.json", JSON.stringify(settings))
305
344
  const project = await loadProject({
306
- settingsFilePath: "/user/project/project.inlang.json",
345
+ projectPath: "/user/project.inlang",
307
346
  nodeishFs: fs,
308
347
  _import,
309
348
  })
@@ -315,7 +354,7 @@ describe("functionality", () => {
315
354
 
316
355
  it("should throw an error if sourceLanguageTag is not in languageTags", async () => {
317
356
  const fs = await createNodeishMemoryFs()
318
- await fs.mkdir("/user/project", { recursive: true })
357
+ await fs.mkdir("/user/project.inlang", { recursive: true })
319
358
 
320
359
  const settings: ProjectSettings = {
321
360
  sourceLanguageTag: "en",
@@ -323,10 +362,10 @@ describe("functionality", () => {
323
362
  modules: [],
324
363
  }
325
364
 
326
- await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings))
365
+ await fs.writeFile("/user/project.inlang/settings.json", JSON.stringify(settings))
327
366
 
328
367
  const project = await loadProject({
329
- settingsFilePath: "/user/project/project.inlang.json",
368
+ projectPath: "/user/project.inlang",
330
369
  nodeishFs: fs,
331
370
  _import,
332
371
  })
@@ -337,25 +376,25 @@ describe("functionality", () => {
337
376
 
338
377
  it("should write settings to disk", async () => {
339
378
  const fs = await createNodeishMemoryFs()
340
- await fs.mkdir("/user/project", { recursive: true })
341
- await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings))
379
+ await fs.mkdir("/user/project.inlang", { recursive: true })
380
+ await fs.writeFile("/user/project.inlang/settings.json", JSON.stringify(settings))
342
381
  const project = await loadProject({
343
- settingsFilePath: "/user/project/project.inlang.json",
382
+ projectPath: "/user/project.inlang",
344
383
  nodeishFs: fs,
345
384
  _import,
346
385
  })
347
386
 
348
- const before = await fs.readFile("/user/project/project.inlang.json", { encoding: "utf-8" })
387
+ const before = await fs.readFile("/user/project.inlang/settings.json", { encoding: "utf-8" })
349
388
  expect(before).toBeDefined()
350
389
 
351
- const result = project.setSettings({ ...settings, languageTags: ["en"] })
390
+ const result = project.setSettings({ ...settings, languageTags: ["en", "nl", "de"] })
352
391
  expect(result.data).toBeUndefined()
353
392
  expect(result.error).toBeUndefined()
354
393
 
355
394
  // TODO: how to wait for fs.writeFile to finish?
356
- await new Promise((resolve) => setTimeout(resolve, 0))
395
+ await new Promise((resolve) => setTimeout(resolve, 50))
357
396
 
358
- const after = await fs.readFile("/user/project/project.inlang.json", { encoding: "utf-8" })
397
+ const after = await fs.readFile("/user/project.inlang/settings.json", { encoding: "utf-8" })
359
398
  expect(after).toBeDefined()
360
399
  expect(after).not.toBe(before)
361
400
  })
@@ -369,10 +408,10 @@ describe("functionality", () => {
369
408
  languageTags: ["en"],
370
409
  modules: ["plugin.js", "lintRule.js"],
371
410
  }
372
- await fs.mkdir("/user/project", { recursive: true })
373
- await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings))
411
+ await fs.mkdir("/user/project.inlang", { recursive: true })
412
+ await fs.writeFile("/user/project.inlang/settings.json", JSON.stringify(settings))
374
413
  const project = await loadProject({
375
- settingsFilePath: "/user/project/project.inlang.json",
414
+ projectPath: "/user/project.inlang",
376
415
  nodeishFs: fs,
377
416
  _import,
378
417
  })
@@ -402,11 +441,11 @@ describe("functionality", () => {
402
441
  modules: ["plugin.js", "lintRule.js"],
403
442
  }
404
443
 
405
- await fs.mkdir("/user/project", { recursive: true })
406
- await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings))
444
+ await fs.mkdir("/user/project.inlang", { recursive: true })
445
+ await fs.writeFile("/user/project.inlang/settings.json", JSON.stringify(settings))
407
446
 
408
447
  const project = await loadProject({
409
- settingsFilePath: "/user/project/project.inlang.json",
448
+ projectPath: "/user/project.inlang",
410
449
  nodeishFs: fs,
411
450
  _import,
412
451
  })
@@ -437,9 +476,9 @@ describe("functionality", () => {
437
476
  saveMessages: () => undefined,
438
477
  }
439
478
  const fs = await createNodeishMemoryFs()
440
- await fs.mkdir("/user/project", { recursive: true })
479
+ await fs.mkdir("/user/project.inlang", { recursive: true })
441
480
  await fs.writeFile(
442
- "/user/project/project.inlang.json",
481
+ "/user/project.inlang/settings.json",
443
482
  JSON.stringify({
444
483
  sourceLanguageTag: "en",
445
484
  languageTags: ["en"],
@@ -453,7 +492,7 @@ describe("functionality", () => {
453
492
  } satisfies InlangModule
454
493
  }
455
494
  const project = await loadProject({
456
- settingsFilePath: "/user/project/project.inlang.json",
495
+ projectPath: "/user/project.inlang",
457
496
  nodeishFs: fs,
458
497
  _import,
459
498
  })
@@ -488,9 +527,9 @@ describe("functionality", () => {
488
527
  saveMessages: () => undefined,
489
528
  }
490
529
  const fs = await createNodeishMemoryFs()
491
- await fs.mkdir("/user/project", { recursive: true })
530
+ await fs.mkdir("/user/project.inlang", { recursive: true })
492
531
  await fs.writeFile(
493
- "/user/project/project.inlang.json",
532
+ "/user/project.inlang/settings.json",
494
533
  JSON.stringify({
495
534
  sourceLanguageTag: "en",
496
535
  languageTags: ["en"],
@@ -504,7 +543,7 @@ describe("functionality", () => {
504
543
  }
505
544
 
506
545
  const project = await loadProject({
507
- settingsFilePath: "/user/project/project.inlang.json",
546
+ projectPath: "/user/project.inlang",
508
547
  nodeishFs: fs,
509
548
  _import,
510
549
  })
@@ -520,10 +559,10 @@ describe("functionality", () => {
520
559
  describe("errors", () => {
521
560
  it("should return the errors", async () => {
522
561
  const fs = await createNodeishMemoryFs()
523
- await fs.mkdir("/user/project", { recursive: true })
524
- await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings))
562
+ await fs.mkdir("/user/project.inlang", { recursive: true })
563
+ await fs.writeFile("/user/project.inlang/settings.json", JSON.stringify(settings))
525
564
  const project = await loadProject({
526
- settingsFilePath: "/user/project/project.inlang.json",
565
+ projectPath: "/user/project.inlang",
527
566
  nodeishFs: fs,
528
567
  _import,
529
568
  })
@@ -536,10 +575,10 @@ describe("functionality", () => {
536
575
  describe("customApi", () => {
537
576
  it("should return the app specific api", async () => {
538
577
  const fs = await createNodeishMemoryFs()
539
- await fs.mkdir("/user/project", { recursive: true })
540
- await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings))
578
+ await fs.mkdir("/user/project.inlang", { recursive: true })
579
+ await fs.writeFile("/user/project.inlang/settings.json", JSON.stringify(settings))
541
580
  const project = await loadProject({
542
- settingsFilePath: "/user/project/project.inlang.json",
581
+ projectPath: "/user/project.inlang",
543
582
  nodeishFs: fs,
544
583
  _import,
545
584
  })
@@ -553,10 +592,10 @@ describe("functionality", () => {
553
592
  describe("messages", () => {
554
593
  it("should return the messages", async () => {
555
594
  const fs = await createNodeishMemoryFs()
556
- await fs.mkdir("/user/project", { recursive: true })
557
- await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings))
595
+ await fs.mkdir("/user/project.inlang", { recursive: true })
596
+ await fs.writeFile("/user/project.inlang/settings.json", JSON.stringify(settings))
558
597
  const project = await loadProject({
559
- settingsFilePath: "/user/project/project.inlang.json",
598
+ projectPath: "/user/project.inlang",
560
599
  nodeishFs: fs,
561
600
  _import,
562
601
  })
@@ -578,8 +617,8 @@ describe("functionality", () => {
578
617
  },
579
618
  }
580
619
 
581
- await fs.mkdir("/user/project", { recursive: true })
582
- await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings))
620
+ await fs.mkdir("/user/project.inlang", { recursive: true })
621
+ await fs.writeFile("/user/project.inlang/settings.json", JSON.stringify(settings))
583
622
 
584
623
  await fs.mkdir("./resources")
585
624
 
@@ -601,7 +640,7 @@ describe("functionality", () => {
601
640
  }
602
641
 
603
642
  const project = await loadProject({
604
- settingsFilePath: "/user/project/project.inlang.json",
643
+ projectPath: "/user/project.inlang",
605
644
  nodeishFs: fs,
606
645
  _import,
607
646
  })
@@ -756,7 +795,8 @@ describe("functionality", () => {
756
795
  },
757
796
  }
758
797
 
759
- await fs.writeFile("./project.inlang.json", JSON.stringify(settings))
798
+ await fs.mkdir("./project.inlang", { recursive: true })
799
+ await fs.writeFile("./project.inlang/settings.json", JSON.stringify(settings))
760
800
 
761
801
  const mockSaveFn = vi.fn()
762
802
 
@@ -780,7 +820,7 @@ describe("functionality", () => {
780
820
  }
781
821
 
782
822
  const project = await loadProject({
783
- settingsFilePath: "/project.inlang.json",
823
+ projectPath: "/project.inlang",
784
824
  nodeishFs: fs,
785
825
  _import,
786
826
  })
@@ -800,7 +840,7 @@ describe("functionality", () => {
800
840
  await fs.mkdir("/user/project", { recursive: true })
801
841
  await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings))
802
842
  const project = await loadProject({
803
- settingsFilePath: "/user/project/project.inlang.json",
843
+ projectPath: "/user/project/project.inlang.json",
804
844
  nodeishFs: fs,
805
845
  _import,
806
846
  })
@@ -819,10 +859,10 @@ describe("functionality", () => {
819
859
  modules: ["lintRule.js"],
820
860
  }
821
861
  const fs = createNodeishMemoryFs()
822
- await fs.mkdir("/user/project", { recursive: true })
823
- await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings))
862
+ await fs.mkdir("/user/project.inlang", { recursive: true })
863
+ await fs.writeFile("/user/project.inlang/settings.json", JSON.stringify(settings))
824
864
  const project = await loadProject({
825
- settingsFilePath: "/user/project/project.inlang.json",
865
+ projectPath: "/user/project.inlang",
826
866
  nodeishFs: fs,
827
867
  _import: async () => ({
828
868
  default: mockMessageLintRule,
@@ -884,11 +924,12 @@ describe("functionality", () => {
884
924
  },
885
925
  }
886
926
 
887
- await fs.writeFile("./project.inlang.json", JSON.stringify(settings))
927
+ await fs.mkdir("./project.inlang", { recursive: true })
928
+ await fs.writeFile("./project.inlang/settings.json", JSON.stringify(settings))
888
929
 
889
930
  // establish watcher
890
931
  const project = await loadProject({
891
- settingsFilePath: normalizePath("/project.inlang.json"),
932
+ projectPath: normalizePath("/project.inlang"),
892
933
  nodeishFs: fs,
893
934
  _import: async () => ({
894
935
  default: mockMessageFormatPlugin,
@@ -23,16 +23,17 @@ import { ProjectSettings, Message, type NodeishFilesystemSubset } from "./versio
23
23
  import { tryCatch, type Result } from "@inlang/result"
24
24
  import { migrateIfOutdated } from "@inlang/project-settings/migration"
25
25
  import { createNodeishFsWithAbsolutePaths } from "./createNodeishFsWithAbsolutePaths.js"
26
- import { normalizePath } from "@lix-js/fs"
26
+ import { normalizePath, type NodeishFilesystem } from "@lix-js/fs"
27
27
  import { isAbsolutePath } from "./isAbsolutePath.js"
28
28
  import { createNodeishFsWithWatcher } from "./createNodeishFsWithWatcher.js"
29
+ import { maybeMigrateToDirectory } from "./migrations/migrateToDirectory.js"
29
30
 
30
31
  const settingsCompiler = TypeCompiler.Compile(ProjectSettings)
31
32
 
32
33
  /**
33
34
  * Creates an inlang instance.
34
35
  *
35
- * @param settingsFilePath - Absolute path to the inlang settings file.
36
+ * @param projectPath - Absolute path to the inlang settings file.
36
37
  * @param nodeishFs - Filesystem that implements the NodeishFilesystemSubset interface.
37
38
  * @param _import - Use `_import` to pass a custom import function for testing,
38
39
  * and supporting legacy resolvedModules such as CJS.
@@ -40,29 +41,39 @@ const settingsCompiler = TypeCompiler.Compile(ProjectSettings)
40
41
  *
41
42
  */
42
43
  export const loadProject = async (args: {
43
- settingsFilePath: string
44
- nodeishFs: NodeishFilesystemSubset
44
+ projectPath: string
45
+ nodeishFs: NodeishFilesystem
45
46
  _import?: ImportFunction
46
47
  _capture?: (id: string, props: Record<string, unknown>) => void
47
48
  }): Promise<InlangProject> => {
49
+ const projectPath = normalizePath(args.projectPath)
50
+
51
+ // -- migrate if outdated ------------------------------------------------
52
+
53
+ await maybeMigrateToDirectory({ nodeishFs: args.nodeishFs, projectPath })
54
+
48
55
  // -- validation --------------------------------------------------------
49
- //! the only place where throwing is acceptable because the project
50
- //! won't even be loaded. do not throw anywhere else. otherwise, apps
51
- //! can't handle errors gracefully.
52
- if (!isAbsolutePath(args.settingsFilePath)) {
56
+ // the only place where throwing is acceptable because the project
57
+ // won't even be loaded. do not throw anywhere else. otherwise, apps
58
+ // can't handle errors gracefully.
59
+
60
+ if (!isAbsolutePath(args.projectPath)) {
61
+ throw new LoadProjectInvalidArgument(
62
+ `Expected an absolute path but received "${args.projectPath}".`,
63
+ { argument: "projectPath" }
64
+ )
65
+ } else if (/[^\\/]+\.inlang$/.test(projectPath) === false) {
53
66
  throw new LoadProjectInvalidArgument(
54
- `Expected an absolute path but received "${args.settingsFilePath}".`,
55
- { argument: "settingsFilePath" }
67
+ `Expected a path ending in "{name}.inlang" but received "${projectPath}".\n\nValid examples: \n- "/path/to/micky-mouse.inlang"\n- "/path/to/green-elephant.inlang\n`,
68
+ { argument: "projectPath" }
56
69
  )
57
70
  }
58
71
 
59
- const settingsFilePath = normalizePath(args.settingsFilePath)
60
-
61
72
  // -- load project ------------------------------------------------------
62
73
  return await createRoot(async () => {
63
74
  const [initialized, markInitAsComplete, markInitAsFailed] = createAwaitable()
64
75
  const nodeishFs = createNodeishFsWithAbsolutePaths({
65
- settingsFilePath,
76
+ projectPath,
66
77
  nodeishFs: args.nodeishFs,
67
78
  })
68
79
 
@@ -70,7 +81,7 @@ export const loadProject = async (args: {
70
81
 
71
82
  const [settings, _setSettings] = createSignal<ProjectSettings>()
72
83
  createEffect(() => {
73
- loadSettings({ settingsFilePath, nodeishFs })
84
+ loadSettings({ settingsFilePath: projectPath + "/settings.json", nodeishFs })
74
85
  .then((settings) => {
75
86
  setSettings(settings)
76
87
  // rename settings to get a convenient access to the data in Posthog
@@ -84,7 +95,7 @@ export const loadProject = async (args: {
84
95
  // TODO: create FS watcher and update settings on change
85
96
 
86
97
  const writeSettingsToDisk = skipFirst((settings: ProjectSettings) =>
87
- _writeSettingsToDisk({ nodeishFs, settings })
98
+ _writeSettingsToDisk({ nodeishFs, settings, projectPath })
88
99
  )
89
100
 
90
101
  const setSettings = (settings: ProjectSettings): Result<void, ProjectSettingsInvalidError> => {
@@ -223,12 +234,14 @@ export const loadProject = async (args: {
223
234
  cause: err,
224
235
  })
225
236
  }
226
- // if (
227
- // newMessages.length !== 0 &&
228
- // JSON.stringify(newMessages) !== JSON.stringify(messages())
229
- // ) {
230
- // setMessages(newMessages)
231
- // }
237
+ const abortController = new AbortController()
238
+ if (
239
+ newMessages.length !== 0 &&
240
+ JSON.stringify(newMessages) !== JSON.stringify(messages()) &&
241
+ nodeishFs.watch("/", { signal: abortController.signal }) === undefined
242
+ ) {
243
+ setMessages(newMessages)
244
+ }
232
245
  },
233
246
  { atBegin: false }
234
247
  )
@@ -320,6 +333,7 @@ const parseSettings = (settings: unknown) => {
320
333
  }
321
334
 
322
335
  const _writeSettingsToDisk = async (args: {
336
+ projectPath: string
323
337
  nodeishFs: NodeishFilesystemSubset
324
338
  settings: ProjectSettings
325
339
  }) => {
@@ -332,7 +346,7 @@ const _writeSettingsToDisk = async (args: {
332
346
  }
333
347
 
334
348
  const { error: writeSettingsError } = await tryCatch(async () =>
335
- args.nodeishFs.writeFile("./project.inlang.json", serializedSettings)
349
+ args.nodeishFs.writeFile(args.projectPath + "/settings.json", serializedSettings)
336
350
  )
337
351
 
338
352
  if (writeSettingsError) {
@@ -0,0 +1,54 @@
1
+ import { test, expect, vi } from "vitest"
2
+ import { maybeMigrateToDirectory } from "./migrateToDirectory.js"
3
+ import { createNodeishMemoryFs } from "@lix-js/fs"
4
+ import type { ProjectSettings } from "@inlang/project-settings"
5
+
6
+ test("it should return if the settings file has an error (let loadProject handle it)", async () => {
7
+ const projectPath = "./project.inlang"
8
+ const mockFs = {
9
+ stat: vi.fn(() => {}),
10
+ readFile: vi.fn(() => {
11
+ throw Error()
12
+ }),
13
+ }
14
+ await maybeMigrateToDirectory({ nodeishFs: mockFs as any, projectPath })
15
+ // something goes wrong in readFile
16
+ expect(mockFs.readFile).toHaveBeenCalled()
17
+ })
18
+
19
+ test("it should create the project directory if it does not exist and a project settings file exists", async () => {
20
+ const projectPath = "./project.inlang"
21
+ const mockFs = {
22
+ readFile: vi.fn(
23
+ () => `{
24
+ "sourceLanguageTag": "en",
25
+ "languageTags": ["en", "de"],
26
+ "modules": []
27
+ }`
28
+ ),
29
+ stat: vi.fn(() => {
30
+ throw Error()
31
+ }),
32
+ mkdir: vi.fn(),
33
+ writeFile: vi.fn(),
34
+ }
35
+ await maybeMigrateToDirectory({ nodeishFs: mockFs as any, projectPath })
36
+ expect(mockFs.mkdir).toHaveBeenCalled()
37
+ expect(mockFs.writeFile).toHaveBeenCalled()
38
+ })
39
+
40
+ test("it should write the settings file to the new path", async () => {
41
+ const fs = createNodeishMemoryFs()
42
+ const mockSettings: ProjectSettings = {
43
+ sourceLanguageTag: "en",
44
+ languageTags: ["en", "de"],
45
+ modules: [],
46
+ }
47
+ await fs.writeFile("./project.inlang.json", JSON.stringify(mockSettings))
48
+ await maybeMigrateToDirectory({ nodeishFs: fs, projectPath: "./project.inlang" })
49
+ const migratedSettingsFile = await fs.readFile("./project.inlang/settings.json", {
50
+ encoding: "utf-8",
51
+ })
52
+ expect(migratedSettingsFile).toEqual(JSON.stringify(mockSettings))
53
+ expect(await fs.stat("./project.inlang.README.md")).toBeDefined()
54
+ })