@inlang/sdk 0.36.0 → 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.
- package/dist/adapter/solidAdapter.d.ts.map +1 -1
- package/dist/createMessagesQuery.js +1 -1
- package/dist/createNewProject.d.ts.map +1 -1
- package/dist/createNewProject.js +19 -2
- package/dist/createNodeishFsWithWatcher.d.ts +1 -1
- package/dist/createNodeishFsWithWatcher.d.ts.map +1 -1
- package/dist/createNodeishFsWithWatcher.js +54 -45
- package/dist/createNodeishFsWithWatcher.test.js +1 -1
- package/dist/lint/message/lintMessages.d.ts.map +1 -1
- package/dist/lint/message/lintSingleMessage.d.ts.map +1 -1
- package/dist/listProjects.d.ts.map +1 -1
- package/dist/loadProject.d.ts.map +1 -1
- package/dist/loadProject.js +56 -50
- package/dist/loadProject.test.js +8 -13
- package/dist/migrations/maybeAddModuleCache.d.ts.map +1 -1
- package/dist/migrations/maybeAddModuleCache.js +24 -6
- package/dist/parseConfig.d.ts.map +1 -1
- package/dist/reactivity/map.d.ts +1 -0
- package/dist/reactivity/map.d.ts.map +1 -1
- package/dist/reactivity/solid.d.ts +11 -11
- package/dist/reactivity/solid.d.ts.map +1 -1
- package/dist/resolve-modules/cache.d.ts +1 -1
- package/dist/resolve-modules/cache.d.ts.map +1 -1
- package/dist/resolve-modules/cache.js +26 -8
- package/dist/resolve-modules/import.d.ts +1 -1
- package/dist/resolve-modules/import.d.ts.map +1 -1
- package/dist/resolve-modules/import.test.js +1 -0
- package/dist/resolve-modules/message-lint-rules/resolveMessageLintRules.d.ts +1 -1
- package/dist/resolve-modules/message-lint-rules/resolveMessageLintRules.d.ts.map +1 -1
- package/dist/resolve-modules/message-lint-rules/resolveMessageLintRules.js +1 -0
- package/dist/storage/helper.d.ts +1 -19
- package/dist/storage/helper.d.ts.map +1 -1
- package/dist/telemetry/capture.d.ts.map +1 -1
- package/dist/telemetry/groupIdentify.d.ts.map +1 -1
- package/dist/test-utilities/createMessage.d.ts.map +1 -1
- package/dist/v2/helper.d.ts +1 -89
- package/dist/v2/helper.d.ts.map +1 -1
- package/dist/validateProjectPath.d.ts +2 -8
- package/dist/validateProjectPath.d.ts.map +1 -1
- package/dist/validateProjectPath.js +0 -21
- package/dist/validateProjectPath.test.js +2 -18
- package/package.json +5 -5
- package/src/createMessagesQuery.ts +1 -1
- package/src/createNewProject.ts +19 -2
- package/src/createNodeishFsWithWatcher.test.ts +1 -1
- package/src/createNodeishFsWithWatcher.ts +53 -42
- package/src/loadProject.test.ts +11 -18
- package/src/loadProject.ts +69 -58
- package/src/migrations/maybeAddModuleCache.ts +21 -6
- package/src/persistence/filelock/acquireFileLock.ts +2 -2
- package/src/persistence/filelock/releaseLock.ts +1 -1
- package/src/resolve-modules/cache.ts +36 -9
- package/src/resolve-modules/import.test.ts +1 -0
- package/src/resolve-modules/import.ts +2 -2
- package/src/resolve-modules/message-lint-rules/resolveMessageLintRules.ts +1 -0
- package/src/validateProjectPath.test.ts +1 -19
- package/src/validateProjectPath.ts +4 -24
package/src/loadProject.ts
CHANGED
|
@@ -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
|
-
//
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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 = (
|
|
110
|
+
const setSettings = (
|
|
111
|
+
newSettings: ProjectSettings
|
|
112
|
+
): Result<ProjectSettings, ProjectSettingsInvalidError> => {
|
|
128
113
|
try {
|
|
129
|
-
const validatedSettings = parseSettings(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
155
|
-
|
|
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
|
|
414
|
+
const writeSettingsToDisk = async (args: {
|
|
390
415
|
projectPath: string
|
|
391
416
|
nodeishFs: NodeishFilesystemSubset
|
|
392
417
|
settings: ProjectSettings
|
|
393
418
|
}) => {
|
|
394
|
-
const
|
|
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 (
|
|
399
|
-
|
|
400
|
-
}
|
|
423
|
+
if (serializeResult.error) throw serializeResult.error
|
|
424
|
+
const serializedSettings = serializeResult.data
|
|
401
425
|
|
|
402
|
-
const
|
|
403
|
-
|
|
426
|
+
const writeResult = await tryCatch(
|
|
427
|
+
async () =>
|
|
428
|
+
await args.nodeishFs.writeFile(args.projectPath + "/settings.json", serializedSettings)
|
|
404
429
|
)
|
|
405
430
|
|
|
406
|
-
if (
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
@@ -30,11 +29,29 @@ async function writeModuleToCache(
|
|
|
30
29
|
moduleURI: string,
|
|
31
30
|
moduleContent: string,
|
|
32
31
|
projectPath: string,
|
|
33
|
-
writeFile: NodeishFilesystemSubset["writeFile"]
|
|
32
|
+
writeFile: NodeishFilesystemSubset["writeFile"],
|
|
33
|
+
mkdir: NodeishFilesystemSubset["mkdir"]
|
|
34
34
|
): Promise<void> {
|
|
35
35
|
const moduleHash = escape(moduleURI)
|
|
36
36
|
const filePath = projectPath + `/cache/modules/${moduleHash}`
|
|
37
|
-
|
|
37
|
+
|
|
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 }))
|
|
42
|
+
|
|
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
|
+
})
|
|
54
|
+
}
|
|
38
55
|
}
|
|
39
56
|
|
|
40
57
|
/**
|
|
@@ -43,19 +60,29 @@ async function writeModuleToCache(
|
|
|
43
60
|
export function withCache(
|
|
44
61
|
moduleLoader: (uri: string) => Promise<string>,
|
|
45
62
|
projectPath: string,
|
|
46
|
-
nodeishFs: Pick<NodeishFilesystemSubset, "readFile" | "writeFile">
|
|
63
|
+
nodeishFs: Pick<NodeishFilesystemSubset, "readFile" | "writeFile" | "mkdir">
|
|
47
64
|
): (uri: string) => Promise<string> {
|
|
48
65
|
return async (uri: string) => {
|
|
49
66
|
const cachePromise = readModuleFromCache(uri, projectPath, nodeishFs.readFile)
|
|
50
|
-
const
|
|
67
|
+
const loaderResult = await tryCatch(async () => await moduleLoader(uri))
|
|
51
68
|
|
|
52
|
-
if (
|
|
69
|
+
if (loaderResult.error) {
|
|
53
70
|
const cacheResult = await cachePromise
|
|
54
71
|
if (!cacheResult.error) return cacheResult.data
|
|
55
|
-
else throw
|
|
72
|
+
else throw loaderResult.error
|
|
56
73
|
} else {
|
|
57
|
-
const moduleAsText =
|
|
58
|
-
|
|
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
|
+
}
|
|
59
86
|
return moduleAsText
|
|
60
87
|
}
|
|
61
88
|
}
|
|
@@ -23,7 +23,7 @@ export type ImportFunction = (uri: string) => Promise<any>
|
|
|
23
23
|
*/
|
|
24
24
|
export function createImport(
|
|
25
25
|
projectPath: string,
|
|
26
|
-
nodeishFs: Pick<NodeishFilesystemSubset, "readFile" | "writeFile">
|
|
26
|
+
nodeishFs: Pick<NodeishFilesystemSubset, "readFile" | "writeFile" | "mkdir">
|
|
27
27
|
) {
|
|
28
28
|
return (uri: string) => $import(uri, projectPath, nodeishFs)
|
|
29
29
|
}
|
|
@@ -31,7 +31,7 @@ export function createImport(
|
|
|
31
31
|
async function $import(
|
|
32
32
|
uri: string,
|
|
33
33
|
projectPath: string,
|
|
34
|
-
nodeishFs: Pick<NodeishFilesystemSubset, "readFile" | "writeFile">
|
|
34
|
+
nodeishFs: Pick<NodeishFilesystemSubset, "readFile" | "writeFile" | "mkdir">
|
|
35
35
|
): Promise<any> {
|
|
36
36
|
const moduleAsText = uri.startsWith("http")
|
|
37
37
|
? await withCache(readModuleFromCDN, projectPath, nodeishFs)(uri)
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import { assert, describe,
|
|
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(
|
|
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
|
-
}
|