@inlang/sdk 0.36.1 → 0.36.2

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 (51) hide show
  1. package/dist/adapter/solidAdapter.d.ts.map +1 -1
  2. package/dist/createMessagesQuery.js +1 -1
  3. package/dist/createNewProject.d.ts.map +1 -1
  4. package/dist/createNewProject.js +19 -2
  5. package/dist/createNodeishFsWithWatcher.d.ts +1 -1
  6. package/dist/createNodeishFsWithWatcher.d.ts.map +1 -1
  7. package/dist/createNodeishFsWithWatcher.js +54 -45
  8. package/dist/createNodeishFsWithWatcher.test.js +1 -1
  9. package/dist/lint/message/lintMessages.d.ts.map +1 -1
  10. package/dist/lint/message/lintSingleMessage.d.ts.map +1 -1
  11. package/dist/listProjects.d.ts.map +1 -1
  12. package/dist/loadProject.d.ts.map +1 -1
  13. package/dist/loadProject.js +56 -50
  14. package/dist/loadProject.test.js +8 -13
  15. package/dist/migrations/maybeAddModuleCache.d.ts.map +1 -1
  16. package/dist/migrations/maybeAddModuleCache.js +24 -6
  17. package/dist/parseConfig.d.ts.map +1 -1
  18. package/dist/reactivity/map.d.ts +1 -0
  19. package/dist/reactivity/map.d.ts.map +1 -1
  20. package/dist/reactivity/solid.d.ts +11 -11
  21. package/dist/reactivity/solid.d.ts.map +1 -1
  22. package/dist/resolve-modules/cache.d.ts.map +1 -1
  23. package/dist/resolve-modules/cache.js +24 -16
  24. package/dist/resolve-modules/message-lint-rules/resolveMessageLintRules.d.ts +1 -1
  25. package/dist/resolve-modules/message-lint-rules/resolveMessageLintRules.d.ts.map +1 -1
  26. package/dist/resolve-modules/message-lint-rules/resolveMessageLintRules.js +1 -0
  27. package/dist/storage/helper.d.ts +1 -19
  28. package/dist/storage/helper.d.ts.map +1 -1
  29. package/dist/telemetry/capture.d.ts.map +1 -1
  30. package/dist/telemetry/groupIdentify.d.ts.map +1 -1
  31. package/dist/test-utilities/createMessage.d.ts.map +1 -1
  32. package/dist/v2/helper.d.ts +1 -89
  33. package/dist/v2/helper.d.ts.map +1 -1
  34. package/dist/validateProjectPath.d.ts +2 -8
  35. package/dist/validateProjectPath.d.ts.map +1 -1
  36. package/dist/validateProjectPath.js +0 -21
  37. package/dist/validateProjectPath.test.js +2 -18
  38. package/package.json +6 -6
  39. package/src/createMessagesQuery.ts +1 -1
  40. package/src/createNewProject.ts +19 -2
  41. package/src/createNodeishFsWithWatcher.test.ts +1 -1
  42. package/src/createNodeishFsWithWatcher.ts +53 -42
  43. package/src/loadProject.test.ts +11 -18
  44. package/src/loadProject.ts +69 -58
  45. package/src/migrations/maybeAddModuleCache.ts +21 -6
  46. package/src/persistence/filelock/acquireFileLock.ts +2 -2
  47. package/src/persistence/filelock/releaseLock.ts +1 -1
  48. package/src/resolve-modules/cache.ts +30 -14
  49. package/src/resolve-modules/message-lint-rules/resolveMessageLintRules.ts +1 -0
  50. package/src/validateProjectPath.test.ts +1 -19
  51. package/src/validateProjectPath.ts +4 -24
@@ -21,6 +21,7 @@ import { ProjectSettings, type NodeishFilesystemSubset } from "./versionedInterf
21
21
  import { tryCatch, type Result } from "@inlang/result"
22
22
  import { migrateIfOutdated } from "@inlang/project-settings/migration"
23
23
  import { createNodeishFsWithAbsolutePaths } from "./createNodeishFsWithAbsolutePaths.js"
24
+ import { createNodeishFsWithWatcher } from "./createNodeishFsWithWatcher.js"
24
25
  import { normalizePath } from "@lix-js/fs"
25
26
  import { assertValidProjectPath } from "./validateProjectPath.js"
26
27
 
@@ -90,43 +91,27 @@ export async function loadProject(args: {
90
91
 
91
92
  const [initialized, markInitAsComplete, markInitAsFailed] = createAwaitable()
92
93
  const [loadedSettings, markSettingsAsLoaded, markSettingsAsFailed] = createAwaitable()
94
+
95
+ const [resolvedModules, setResolvedModules] =
96
+ createSignal<Awaited<ReturnType<typeof resolveModules>>>()
93
97
  // -- settings ------------------------------------------------------------
94
98
 
95
99
  const [settings, _setSettings] = createSignal<ProjectSettings>()
96
100
  let v2Persistence = false
97
101
  let locales: string[] = []
98
102
 
99
- // This effect currently has no signals
100
- // TODO: replace createEffect with await loadSettings
101
- // https://github.com/opral/inlang-message-sdk/issues/77
102
- createEffect(() => {
103
- // TODO:
104
- // if (projectId) {
105
- // telemetryBrowser.group("project", projectId, {
106
- // name: projectId,
107
- // })
108
- // }
109
-
110
- loadSettings({ settingsFilePath: projectPath + "/settings.json", nodeishFs })
111
- .then((settings) => {
112
- setSettings(settings)
113
- markSettingsAsLoaded()
114
- })
115
- .catch((err) => {
116
- markInitAsFailed(err)
117
- markSettingsAsFailed(err)
118
- })
119
- })
120
- // TODO: create FS watcher and update settings on change
121
- // https://github.com/opral/inlang-message-sdk/issues/35
122
-
123
- const writeSettingsToDisk = skipFirst((settings: ProjectSettings) =>
124
- _writeSettingsToDisk({ nodeishFs, settings, projectPath })
125
- )
103
+ // TODO:
104
+ // if (projectId) {
105
+ // telemetryBrowser.group("project", projectId, {
106
+ // name: projectId,
107
+ // })
108
+ // }
126
109
 
127
- const setSettings = (settings: ProjectSettings): Result<void, ProjectSettingsInvalidError> => {
110
+ const setSettings = (
111
+ newSettings: ProjectSettings
112
+ ): Result<ProjectSettings, ProjectSettingsInvalidError> => {
128
113
  try {
129
- const validatedSettings = parseSettings(settings)
114
+ const validatedSettings = parseSettings(newSettings)
130
115
  v2Persistence = !!validatedSettings.experimental?.persistence
131
116
  locales = validatedSettings.languageTags
132
117
 
@@ -136,23 +121,56 @@ export async function loadProject(args: {
136
121
  _setSettings(validatedSettings)
137
122
  })
138
123
 
139
- writeSettingsToDisk(validatedSettings)
140
- return { data: undefined }
124
+ return { data: validatedSettings }
141
125
  } catch (error: unknown) {
142
126
  if (error instanceof ProjectSettingsInvalidError) {
143
127
  return { error }
144
128
  }
145
129
 
146
130
  throw new Error(
147
- "Unhandled error in setSettings. This is an internal bug. Please file an issue."
131
+ "Unhandled error in setSettings. This is an internal bug. Please file an issue.",
132
+ { cause: error }
148
133
  )
149
134
  }
150
135
  }
151
136
 
152
- // -- resolvedModules -----------------------------------------------------------
137
+ const nodeishFsWithWatchersForSettings = createNodeishFsWithWatcher({
138
+ nodeishFs: nodeishFs,
139
+ onChange: async () => {
140
+ const readSettingsResult = await tryCatch(
141
+ async () =>
142
+ await loadSettings({
143
+ settingsFilePath: projectPath + "/settings.json",
144
+ nodeishFs: nodeishFs,
145
+ })
146
+ )
153
147
 
154
- const [resolvedModules, setResolvedModules] =
155
- createSignal<Awaited<ReturnType<typeof resolveModules>>>()
148
+ if (readSettingsResult.error) return
149
+ const newSettings = readSettingsResult.data
150
+
151
+ if (JSON.stringify(newSettings) !== JSON.stringify(settings())) {
152
+ setSettings(newSettings)
153
+ }
154
+ },
155
+ })
156
+
157
+ const settingsResult = await tryCatch(
158
+ async () =>
159
+ await loadSettings({
160
+ settingsFilePath: projectPath + "/settings.json",
161
+ nodeishFs: nodeishFsWithWatchersForSettings,
162
+ })
163
+ )
164
+
165
+ if (settingsResult.error) {
166
+ markInitAsFailed(settingsResult.error)
167
+ markSettingsAsFailed(settingsResult.error)
168
+ } else {
169
+ setSettings(settingsResult.data)
170
+ markSettingsAsLoaded()
171
+ }
172
+
173
+ // -- resolvedModules -----------------------------------------------------------
156
174
 
157
175
  createEffect(() => {
158
176
  const _settings = settings()
@@ -318,7 +336,11 @@ export async function loadProject(args: {
318
336
  //...(lintErrors() ?? []),
319
337
  ]),
320
338
  settings: createSubscribable(() => settings() as ProjectSettings),
321
- setSettings,
339
+ setSettings: (newSettings: ProjectSettings): Result<void, ProjectSettingsInvalidError> => {
340
+ const result = setSettings(newSettings)
341
+ if (!result.error) writeSettingsToDisk({ nodeishFs, settings: result.data, projectPath })
342
+ return result.error ? result : { data: undefined }
343
+ },
322
344
  customApi: createSubscribable(() => resolvedModules()?.resolvedPluginApi.customApi || {}),
323
345
  query: {
324
346
  messages: messagesQuery,
@@ -355,6 +377,9 @@ const loadSettings = async (args: {
355
377
  return parseSettings(json.data)
356
378
  }
357
379
 
380
+ /**
381
+ * @throws If the settings are not valid
382
+ */
358
383
  const parseSettings = (settings: unknown) => {
359
384
  const withMigration = migrateIfOutdated(settings as any)
360
385
  if (settingsCompiler.Check(withMigration) === false) {
@@ -386,26 +411,24 @@ const parseSettings = (settings: unknown) => {
386
411
  return withMigration
387
412
  }
388
413
 
389
- const _writeSettingsToDisk = async (args: {
414
+ const writeSettingsToDisk = async (args: {
390
415
  projectPath: string
391
416
  nodeishFs: NodeishFilesystemSubset
392
417
  settings: ProjectSettings
393
418
  }) => {
394
- const { data: serializedSettings, error: serializeSettingsError } = tryCatch(() =>
419
+ const serializeResult = tryCatch(() =>
395
420
  // TODO: this will probably not match the original formatting
396
421
  JSON.stringify(args.settings, undefined, 2)
397
422
  )
398
- if (serializeSettingsError) {
399
- throw serializeSettingsError
400
- }
423
+ if (serializeResult.error) throw serializeResult.error
424
+ const serializedSettings = serializeResult.data
401
425
 
402
- const { error: writeSettingsError } = await tryCatch(async () =>
403
- args.nodeishFs.writeFile(args.projectPath + "/settings.json", serializedSettings)
426
+ const writeResult = await tryCatch(
427
+ async () =>
428
+ await args.nodeishFs.writeFile(args.projectPath + "/settings.json", serializedSettings)
404
429
  )
405
430
 
406
- if (writeSettingsError) {
407
- throw writeSettingsError
408
- }
431
+ if (writeResult.error) throw writeResult.error
409
432
  }
410
433
 
411
434
  // ------------------------------------------------------------------------------------------------
@@ -426,18 +449,6 @@ const createAwaitable = () => {
426
449
  ]
427
450
  }
428
451
 
429
- // Skip initial call, eg. to skip setup of a createEffect
430
- function skipFirst(func: (args: any) => any) {
431
- let initial = false
432
- return function (...args: any) {
433
- if (initial) {
434
- // @ts-ignore
435
- return func.apply(this, args)
436
- }
437
- initial = true
438
- }
439
- }
440
-
441
452
  export function createSubscribable<T>(signal: () => T): Subscribable<T> {
442
453
  return Object.assign(signal, {
443
454
  subscribe: (callback: (value: T) => void) => {
@@ -20,17 +20,32 @@ export async function maybeAddModuleCache(args: {
20
20
 
21
21
  if (gitignoreExists) {
22
22
  // non-destructively add any missing ignores
23
- const gitignore = await args.repo.nodeishFs.readFile(gitignorePath, { encoding: "utf-8" })
24
- const missingIgnores = EXPECTED_IGNORES.filter((ignore) => !gitignore.includes(ignore))
25
- if (missingIgnores.length > 0) {
26
- await args.repo.nodeishFs.appendFile(gitignorePath, "\n" + missingIgnores.join("\n"))
23
+ try {
24
+ const gitignore = await args.repo.nodeishFs.readFile(gitignorePath, { encoding: "utf-8" })
25
+ const missingIgnores = EXPECTED_IGNORES.filter((ignore) => !gitignore.includes(ignore))
26
+ if (missingIgnores.length > 0) {
27
+ await args.repo.nodeishFs.appendFile(gitignorePath, "\n" + missingIgnores.join("\n"))
28
+ }
29
+ } catch (error) {
30
+ throw new Error("[migrate:module-cache] Failed to update .gitignore", { cause: error })
27
31
  }
28
32
  } else {
29
- await args.repo.nodeishFs.writeFile(gitignorePath, EXPECTED_IGNORES.join("\n"))
33
+ try {
34
+ await args.repo.nodeishFs.writeFile(gitignorePath, EXPECTED_IGNORES.join("\n"))
35
+ } catch (e) {
36
+ // @ts-ignore
37
+ if (e.code && e.code !== "EISDIR" && e.code !== "EEXIST") {
38
+ throw new Error("[migrate:module-cache] Failed to create .gitignore", { cause: e })
39
+ }
40
+ }
30
41
  }
31
42
 
32
43
  if (!moduleCacheExists) {
33
- await args.repo.nodeishFs.mkdir(moduleCache, { recursive: true })
44
+ try {
45
+ await args.repo.nodeishFs.mkdir(moduleCache, { recursive: true })
46
+ } catch (e) {
47
+ throw new Error("[migrate:module-cache] Failed to create cache directory", { cause: e })
48
+ }
34
49
  }
35
50
  }
36
51
 
@@ -22,13 +22,13 @@ export async function acquireFileLock(
22
22
  debug(lockOrigin + " tries to acquire a lockfile Retry Nr.: " + tryCount)
23
23
  await fs.mkdir(lockDirPath)
24
24
  // NOTE: fs.stat does not need to be atomic since mkdir would crash atomically - if we are here its save to consider the lock held by this process
25
- const stats = await fs.stat(lockDirPath)
25
+ const stats = await fs.stat(lockDirPath)
26
26
  debug(lockOrigin + " acquired a lockfile Retry Nr.: " + tryCount)
27
27
 
28
28
  return stats.mtimeMs
29
29
  } catch (error: any) {
30
30
  if (error.code !== "EEXIST") {
31
- // NOTE in case we have an EEXIST - this is an expected error: the folder existed - another process already acquired the lock. Rethrow all other errors
31
+ // NOTE in case we have an EEXIST - this is an expected error: the folder existed - another process already acquired the lock. Rethrow all other errors
32
32
  throw error
33
33
  }
34
34
  }
@@ -13,7 +13,7 @@ export async function releaseLock(
13
13
  const stats = await fs.stat(lockDirPath)
14
14
  // I believe this check associates the lock with the aquirer
15
15
  if (stats.mtimeMs === lockTime) {
16
- // NOTE: since we have to use a timeout for stale detection (uTimes is not exposed via mermoryfs) the check for the locktime is not sufficient and can fail in rare cases when another process accuires a lock that was identifiert as tale between call to fs.state and rmDir
16
+ // NOTE: since we have to use a timeout for stale detection (uTimes is not exposed via mermoryfs) the check for the locktime is not sufficient and can fail in rare cases when another process accuires a lock that was identifiert as tale between call to fs.state and rmDir
17
17
  await fs.rmdir(lockDirPath)
18
18
  }
19
19
  } catch (statError: any) {
@@ -2,7 +2,6 @@ import type { NodeishFilesystemSubset } from "@inlang/plugin"
2
2
  import { type Result, tryCatch } from "@inlang/result"
3
3
 
4
4
  function escape(url: string) {
5
- // collect the bytes of the UTF-8 representation
6
5
  const bytes = new TextEncoder().encode(url)
7
6
 
8
7
  // 64-bit FNV1a hash to make the file-names shorter
@@ -36,15 +35,22 @@ async function writeModuleToCache(
36
35
  const moduleHash = escape(moduleURI)
37
36
  const filePath = projectPath + `/cache/modules/${moduleHash}`
38
37
 
39
- try {
40
- await writeFile(filePath, moduleContent)
41
- } catch (e) {
42
- // if ENONET -> likely means the parent directory does not exist yet
43
- if (!(e instanceof Error) || !e.message.includes("ENONET")) return
38
+ const writeFileResult = await tryCatch(() => writeFile(filePath, moduleContent))
39
+ if (writeFileResult.error) {
40
+ const dirPath = projectPath + `/cache/modules`
41
+ const createDirResult = await tryCatch(() => mkdir(dirPath, { recursive: true }))
44
42
 
45
- // create the parent directory & retry
46
- await mkdir(projectPath + `/cache/modules`, { recursive: true })
47
- await writeFile(filePath, moduleContent)
43
+ // @ts-ignore - If the directory exists we can ignore this error
44
+ if (createDirResult.error && createDirResult.error.code !== "EEXIST")
45
+ throw new Error("[sdk:module-cacke] failed to create cache-directory. Path: " + dirPath, {
46
+ cause: createDirResult.error,
47
+ })
48
+
49
+ const writeFileResult = await tryCatch(() => writeFile(filePath, moduleContent))
50
+ if (writeFileResult.error)
51
+ throw new Error("[sdk:module-cacke] failed to write cache-file. Path: " + filePath, {
52
+ cause: writeFileResult.error,
53
+ })
48
54
  }
49
55
  }
50
56
 
@@ -58,15 +64,25 @@ export function withCache(
58
64
  ): (uri: string) => Promise<string> {
59
65
  return async (uri: string) => {
60
66
  const cachePromise = readModuleFromCache(uri, projectPath, nodeishFs.readFile)
61
- const networkResult = await tryCatch(async () => await moduleLoader(uri))
67
+ const loaderResult = await tryCatch(async () => await moduleLoader(uri))
62
68
 
63
- if (networkResult.error) {
69
+ if (loaderResult.error) {
64
70
  const cacheResult = await cachePromise
65
71
  if (!cacheResult.error) return cacheResult.data
66
- else throw networkResult.error
72
+ else throw loaderResult.error
67
73
  } else {
68
- const moduleAsText = networkResult.data
69
- await writeModuleToCache(uri, moduleAsText, projectPath, nodeishFs.writeFile, nodeishFs.mkdir)
74
+ const moduleAsText = loaderResult.data
75
+ try {
76
+ await writeModuleToCache(
77
+ uri,
78
+ moduleAsText,
79
+ projectPath,
80
+ nodeishFs.writeFile,
81
+ nodeishFs.mkdir
82
+ )
83
+ } catch (error) {
84
+ // TODO trigger a warning
85
+ }
70
86
  return moduleAsText
71
87
  }
72
88
  }
@@ -12,6 +12,7 @@ export const resolveMessageLintRules = (args: { messageLintRules: Array<MessageL
12
12
  const errors = [...Value.Errors(MessageLintRule, rule)]
13
13
  result.errors.push(
14
14
  new MessageLintRuleIsInvalidError({
15
+ // @ts-ignore
15
16
  id: rule.id,
16
17
  errors,
17
18
  })
@@ -1,11 +1,9 @@
1
- import { assert, describe, expect, it } from "vitest"
1
+ import { assert, describe, it } from "vitest"
2
2
  import {
3
3
  assertValidProjectPath,
4
4
  isAbsolutePath,
5
5
  isInlangProjectPath,
6
- pathExists,
7
6
  } from "./validateProjectPath.js"
8
- import { mockRepo } from "@lix-js/client"
9
7
 
10
8
  describe("isAbsolutePath", () => {
11
9
  it("should correctly identify Unix absolute paths", () => {
@@ -50,19 +48,3 @@ describe("assertValidProjectPath", () => {
50
48
  assert.throws(() => assertValidProjectPath("/path/to/green-elephant.inlang/settings.json"))
51
49
  })
52
50
  })
53
-
54
- // moar tests in paraglide-js/src/services/file-handling/exists.test.ts
55
- describe("pathExists", () => {
56
- it("should work for files", async () => {
57
- const repo = await mockRepo()
58
- await repo.nodeishFs.writeFile("/test.txt", "hello")
59
- expect(await pathExists("/test.txt", repo.nodeishFs)).toBe(true)
60
- expect(await pathExists("/does-not-exist.txt", repo.nodeishFs)).toBe(false)
61
- })
62
- it("should work for directories", async () => {
63
- const repo = await mockRepo()
64
- await repo.nodeishFs.mkdir("/test/project.inlang", { recursive: true })
65
- expect(await pathExists("/test/project.inlang", repo.nodeishFs)).toBe(true)
66
- expect(await pathExists("/test/white-gorilla.inlang", repo.nodeishFs)).toBe(false)
67
- })
68
- })
@@ -1,11 +1,11 @@
1
- import type { NodeishFilesystem } from "@lix-js/fs"
2
-
3
1
  /**
4
2
  * validate that a project path is absolute and ends with {name}.inlang.
5
3
  *
6
4
  * @throws if the path is not valid.
7
5
  */
8
- export function assertValidProjectPath(projectPath: string) {
6
+ export function assertValidProjectPath(
7
+ projectPath: string
8
+ ): asserts projectPath is `${string}.inlang` {
9
9
  if (!isAbsolutePath(projectPath)) {
10
10
  throw new Error(`Expected an absolute path but received "${projectPath}".`)
11
11
  }
@@ -20,7 +20,7 @@ export function assertValidProjectPath(projectPath: string) {
20
20
  * tests whether a path ends with {name}.inlang
21
21
  * (does not remove trailing slash)
22
22
  */
23
- export function isInlangProjectPath(path: string) {
23
+ export function isInlangProjectPath(path: string): path is `${string}.inlang` {
24
24
  return /[^\\/]+\.inlang$/.test(path)
25
25
  }
26
26
 
@@ -36,23 +36,3 @@ export function isAbsolutePath(path: string) {
36
36
  // /^(?:[A-Za-z]:\\(?:[^\\]+\\)*[^\\]+|[A-Za-z]:\/(?:[^/]+\/)*[^/]+|\/(?:[^/]+\/)*[^/]+)$/
37
37
  // return matchPosixAndWindowsAbsolutePaths.test(path)
38
38
  }
39
-
40
- /**
41
- * Returns true if the path exists (file or directory), false otherwise.
42
- *
43
- */
44
- export async function pathExists(filePath: string, nodeishFs: NodeishFilesystem) {
45
- // from paraglide-js/src/services/file-handling/exists.ts
46
- // TODO: add fs.exists to @lix-js/fs
47
- try {
48
- await nodeishFs.stat(filePath)
49
- return true
50
- } catch (error) {
51
- //@ts-ignore
52
- if (error.code === "ENOENT") {
53
- return false
54
- } else {
55
- throw new Error(`Failed to check if path exists: ${error}`, { cause: error })
56
- }
57
- }
58
- }