@inlang/sdk 0.18.0 → 0.20.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.
- package/README.md +1 -1
- package/dist/adapter/solidAdapter.test.js +16 -15
- package/dist/createMessageLintReportsQuery.d.ts +2 -3
- package/dist/createMessageLintReportsQuery.d.ts.map +1 -1
- package/dist/createMessageLintReportsQuery.js +42 -21
- package/dist/createNodeishFsWithAbsolutePaths.d.ts +2 -2
- package/dist/createNodeishFsWithAbsolutePaths.d.ts.map +1 -1
- package/dist/createNodeishFsWithAbsolutePaths.js +4 -4
- package/dist/createNodeishFsWithAbsolutePaths.test.js +4 -4
- package/dist/createNodeishFsWithWatcher.d.ts +1 -1
- package/dist/createNodeishFsWithWatcher.d.ts.map +1 -1
- package/dist/createNodeishFsWithWatcher.js +6 -4
- package/dist/generateProjectId.d.ts +3 -0
- package/dist/generateProjectId.d.ts.map +1 -0
- package/dist/generateProjectId.js +11 -0
- package/dist/generateProjectId.test.d.ts +2 -0
- package/dist/generateProjectId.test.d.ts.map +1 -0
- package/dist/generateProjectId.test.js +18 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/isAbsolutePath.test.js +1 -1
- package/dist/listProjects.d.ts +5 -0
- package/dist/listProjects.d.ts.map +1 -0
- package/dist/listProjects.js +31 -0
- package/dist/listProjects.test.d.ts +2 -0
- package/dist/listProjects.test.d.ts.map +1 -0
- package/dist/listProjects.test.js +56 -0
- package/dist/loadProject.d.ts +6 -4
- package/dist/loadProject.d.ts.map +1 -1
- package/dist/loadProject.js +58 -22
- package/dist/loadProject.test.js +157 -75
- package/dist/migrations/migrateToDirectory.d.ts +10 -0
- package/dist/migrations/migrateToDirectory.d.ts.map +1 -0
- package/dist/migrations/migrateToDirectory.js +46 -0
- package/dist/migrations/migrateToDirectory.test.d.ts +2 -0
- package/dist/migrations/migrateToDirectory.test.d.ts.map +1 -0
- package/dist/migrations/migrateToDirectory.test.js +48 -0
- package/dist/resolve-modules/validateModuleSettings.test.js +1 -1
- package/package.json +57 -56
- package/src/adapter/solidAdapter.test.ts +16 -15
- package/src/createMessageLintReportsQuery.ts +57 -28
- package/src/createNodeishFsWithAbsolutePaths.test.ts +4 -4
- package/src/createNodeishFsWithAbsolutePaths.ts +5 -5
- package/src/createNodeishFsWithWatcher.ts +6 -4
- package/src/generateProjectId.test.ts +22 -0
- package/src/generateProjectId.ts +14 -0
- package/src/index.ts +1 -0
- package/src/isAbsolutePath.test.ts +1 -1
- package/src/listProjects.test.ts +69 -0
- package/src/listProjects.ts +39 -0
- package/src/loadProject.test.ts +182 -76
- package/src/loadProject.ts +77 -28
- package/src/migrations/migrateToDirectory.test.ts +54 -0
- package/src/migrations/migrateToDirectory.ts +59 -0
- package/src/resolve-modules/validateModuleSettings.test.ts +1 -1
package/src/loadProject.ts
CHANGED
|
@@ -23,16 +23,19 @@ 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"
|
|
30
|
+
import type { Repository } from "@lix-js/client"
|
|
31
|
+
import { generateProjectId } from "./generateProjectId.js"
|
|
29
32
|
|
|
30
33
|
const settingsCompiler = TypeCompiler.Compile(ProjectSettings)
|
|
31
34
|
|
|
32
35
|
/**
|
|
33
36
|
* Creates an inlang instance.
|
|
34
37
|
*
|
|
35
|
-
* @param
|
|
38
|
+
* @param projectPath - Absolute path to the inlang settings file.
|
|
36
39
|
* @param nodeishFs - Filesystem that implements the NodeishFilesystemSubset interface.
|
|
37
40
|
* @param _import - Use `_import` to pass a custom import function for testing,
|
|
38
41
|
* and supporting legacy resolvedModules such as CJS.
|
|
@@ -40,42 +43,81 @@ const settingsCompiler = TypeCompiler.Compile(ProjectSettings)
|
|
|
40
43
|
*
|
|
41
44
|
*/
|
|
42
45
|
export const loadProject = async (args: {
|
|
43
|
-
|
|
44
|
-
|
|
46
|
+
projectPath: string
|
|
47
|
+
repo?: Repository
|
|
48
|
+
nodeishFs: NodeishFilesystem
|
|
45
49
|
_import?: ImportFunction
|
|
46
50
|
_capture?: (id: string, props: Record<string, unknown>) => void
|
|
47
51
|
}): Promise<InlangProject> => {
|
|
52
|
+
const projectPath = normalizePath(args.projectPath)
|
|
53
|
+
|
|
54
|
+
// -- migrate if outdated ------------------------------------------------
|
|
55
|
+
|
|
56
|
+
await maybeMigrateToDirectory({ nodeishFs: args.nodeishFs, projectPath })
|
|
57
|
+
|
|
48
58
|
// -- validation --------------------------------------------------------
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
59
|
+
// the only place where throwing is acceptable because the project
|
|
60
|
+
// won't even be loaded. do not throw anywhere else. otherwise, apps
|
|
61
|
+
// can't handle errors gracefully.
|
|
62
|
+
|
|
63
|
+
if (!isAbsolutePath(args.projectPath)) {
|
|
64
|
+
throw new LoadProjectInvalidArgument(
|
|
65
|
+
`Expected an absolute path but received "${args.projectPath}".`,
|
|
66
|
+
{ argument: "projectPath" }
|
|
67
|
+
)
|
|
68
|
+
} else if (/[^\\/]+\.inlang$/.test(projectPath) === false) {
|
|
53
69
|
throw new LoadProjectInvalidArgument(
|
|
54
|
-
`Expected
|
|
55
|
-
{ argument: "
|
|
70
|
+
`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`,
|
|
71
|
+
{ argument: "projectPath" }
|
|
56
72
|
)
|
|
57
73
|
}
|
|
58
74
|
|
|
59
|
-
const settingsFilePath = normalizePath(args.settingsFilePath)
|
|
60
|
-
|
|
61
75
|
// -- load project ------------------------------------------------------
|
|
76
|
+
let idError: Error | undefined
|
|
62
77
|
return await createRoot(async () => {
|
|
63
78
|
const [initialized, markInitAsComplete, markInitAsFailed] = createAwaitable()
|
|
64
79
|
const nodeishFs = createNodeishFsWithAbsolutePaths({
|
|
65
|
-
|
|
80
|
+
projectPath,
|
|
66
81
|
nodeishFs: args.nodeishFs,
|
|
67
82
|
})
|
|
68
83
|
|
|
84
|
+
let projectId: string | undefined
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
projectId = await nodeishFs.readFile(projectPath + "/project_id", {
|
|
88
|
+
encoding: "utf-8",
|
|
89
|
+
})
|
|
90
|
+
} catch (error) {
|
|
91
|
+
// @ts-ignore
|
|
92
|
+
if (error.code === "ENOENT") {
|
|
93
|
+
if (args.repo) {
|
|
94
|
+
projectId = await generateProjectId(args.repo, projectPath)
|
|
95
|
+
if (projectId) {
|
|
96
|
+
await nodeishFs.writeFile(projectPath + "/project_id", projectId)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
idError = error as Error
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
69
104
|
// -- settings ------------------------------------------------------------
|
|
70
105
|
|
|
71
106
|
const [settings, _setSettings] = createSignal<ProjectSettings>()
|
|
72
107
|
createEffect(() => {
|
|
73
|
-
|
|
108
|
+
// TODO:
|
|
109
|
+
// if (projectId) {
|
|
110
|
+
// telemetryBrowser.group("project", projectId, {
|
|
111
|
+
// name: projectId,
|
|
112
|
+
// })
|
|
113
|
+
// }
|
|
114
|
+
|
|
115
|
+
loadSettings({ settingsFilePath: projectPath + "/settings.json", nodeishFs })
|
|
74
116
|
.then((settings) => {
|
|
75
117
|
setSettings(settings)
|
|
76
118
|
// rename settings to get a convenient access to the data in Posthog
|
|
77
119
|
const project_settings = settings
|
|
78
|
-
args._capture?.("SDK used settings", { project_settings })
|
|
120
|
+
args._capture?.("SDK used settings", { project_settings, group: projectId })
|
|
79
121
|
})
|
|
80
122
|
.catch((err) => {
|
|
81
123
|
markInitAsFailed(err)
|
|
@@ -84,7 +126,7 @@ export const loadProject = async (args: {
|
|
|
84
126
|
// TODO: create FS watcher and update settings on change
|
|
85
127
|
|
|
86
128
|
const writeSettingsToDisk = skipFirst((settings: ProjectSettings) =>
|
|
87
|
-
_writeSettingsToDisk({ nodeishFs, settings })
|
|
129
|
+
_writeSettingsToDisk({ nodeishFs, settings, projectPath })
|
|
88
130
|
)
|
|
89
131
|
|
|
90
132
|
const setSettings = (settings: ProjectSettings): Result<void, ProjectSettingsInvalidError> => {
|
|
@@ -126,12 +168,11 @@ export const loadProject = async (args: {
|
|
|
126
168
|
let settingsValue: ProjectSettings
|
|
127
169
|
createEffect(() => (settingsValue = settings()!)) // workaround to not run effects twice (e.g. settings change + modules change) (I'm sure there exists a solid way of doing this, but I haven't found it yet)
|
|
128
170
|
|
|
171
|
+
// please don't use this as source of truth, use the query instead
|
|
172
|
+
// needed for granular linting
|
|
129
173
|
const [messages, setMessages] = createSignal<Message[]>()
|
|
130
174
|
|
|
131
175
|
createEffect(() => {
|
|
132
|
-
const conf = settings()
|
|
133
|
-
if (!conf) return
|
|
134
|
-
|
|
135
176
|
const _resolvedModules = resolvedModules()
|
|
136
177
|
if (!_resolvedModules) return
|
|
137
178
|
|
|
@@ -199,12 +240,16 @@ export const loadProject = async (args: {
|
|
|
199
240
|
|
|
200
241
|
const initializeError: Error | undefined = await initialized.catch((error) => error)
|
|
201
242
|
|
|
243
|
+
const abortController = new AbortController()
|
|
244
|
+
const hasWatcher = nodeishFs.watch("/", { signal: abortController.signal }) !== undefined
|
|
245
|
+
|
|
202
246
|
const messagesQuery = createMessagesQuery(() => messages() || [])
|
|
203
247
|
const lintReportsQuery = createMessageLintReportsQuery(
|
|
204
|
-
|
|
248
|
+
messagesQuery,
|
|
205
249
|
settings as () => ProjectSettings,
|
|
206
250
|
installedMessageLintRules,
|
|
207
|
-
resolvedModules
|
|
251
|
+
resolvedModules,
|
|
252
|
+
hasWatcher
|
|
208
253
|
)
|
|
209
254
|
|
|
210
255
|
const debouncedSave = skipFirst(
|
|
@@ -223,12 +268,14 @@ export const loadProject = async (args: {
|
|
|
223
268
|
cause: err,
|
|
224
269
|
})
|
|
225
270
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
271
|
+
const abortController = new AbortController()
|
|
272
|
+
if (
|
|
273
|
+
newMessages.length !== 0 &&
|
|
274
|
+
JSON.stringify(newMessages) !== JSON.stringify(messages()) &&
|
|
275
|
+
nodeishFs.watch("/", { signal: abortController.signal }) !== undefined
|
|
276
|
+
) {
|
|
277
|
+
setMessages(newMessages)
|
|
278
|
+
}
|
|
232
279
|
},
|
|
233
280
|
{ atBegin: false }
|
|
234
281
|
)
|
|
@@ -245,6 +292,7 @@ export const loadProject = async (args: {
|
|
|
245
292
|
},
|
|
246
293
|
errors: createSubscribable(() => [
|
|
247
294
|
...(initializeError ? [initializeError] : []),
|
|
295
|
+
...(idError ? [idError] : []),
|
|
248
296
|
...(resolvedModules() ? resolvedModules()!.errors : []),
|
|
249
297
|
// have a query error exposed
|
|
250
298
|
//...(lintErrors() ?? []),
|
|
@@ -320,6 +368,7 @@ const parseSettings = (settings: unknown) => {
|
|
|
320
368
|
}
|
|
321
369
|
|
|
322
370
|
const _writeSettingsToDisk = async (args: {
|
|
371
|
+
projectPath: string
|
|
323
372
|
nodeishFs: NodeishFilesystemSubset
|
|
324
373
|
settings: ProjectSettings
|
|
325
374
|
}) => {
|
|
@@ -332,7 +381,7 @@ const _writeSettingsToDisk = async (args: {
|
|
|
332
381
|
}
|
|
333
382
|
|
|
334
383
|
const { error: writeSettingsError } = await tryCatch(async () =>
|
|
335
|
-
args.nodeishFs.writeFile("
|
|
384
|
+
args.nodeishFs.writeFile(args.projectPath + "/settings.json", serializedSettings)
|
|
336
385
|
)
|
|
337
386
|
|
|
338
387
|
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
|
+
})
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { tryCatch } from "@inlang/result"
|
|
2
|
+
import type { NodeishFilesystem } from "@lix-js/fs"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Migrates to the new project directory structure
|
|
6
|
+
* https://github.com/inlang/monorepo/issues/1678
|
|
7
|
+
*/
|
|
8
|
+
export const maybeMigrateToDirectory = async (args: {
|
|
9
|
+
nodeishFs: NodeishFilesystem
|
|
10
|
+
projectPath: string
|
|
11
|
+
}) => {
|
|
12
|
+
// the migration assumes that the projectPath ends with project.inlang
|
|
13
|
+
if (args.projectPath.endsWith("project.inlang") === false) {
|
|
14
|
+
return
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// we assume that stat will throw when the directory does not exist
|
|
18
|
+
const projectDirectory = await tryCatch(() => args.nodeishFs.stat(args.projectPath))
|
|
19
|
+
|
|
20
|
+
// the migration has already been conducted.
|
|
21
|
+
if (projectDirectory.data) {
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const settingsFile = await tryCatch(() =>
|
|
26
|
+
args.nodeishFs.readFile(args.projectPath + ".json", { encoding: "utf-8" })
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
// the settings file does not exist or something else is wrong, let loadProject handle it
|
|
30
|
+
if (settingsFile.error) {
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
await args.nodeishFs.mkdir(args.projectPath)
|
|
35
|
+
await args.nodeishFs.writeFile(`${args.projectPath}/settings.json`, settingsFile.data)
|
|
36
|
+
await args.nodeishFs.writeFile(args.projectPath + ".README.md", readme)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const readme = `
|
|
40
|
+
# DELETE THE \`project.inlang.json\` FILE
|
|
41
|
+
|
|
42
|
+
The \`project.inlang.json\` file is now contained in a project directory e.g. \`project.inlang/settings.json\`.
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
## What you need to do
|
|
46
|
+
|
|
47
|
+
1. Update the inlang CLI (if you use it) to use the new path \`project.inlang\` instead of \`project.inlang.json\`.
|
|
48
|
+
2. Delete the \`project.inlang.json\` file.
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
## Why is this happening?
|
|
52
|
+
|
|
53
|
+
See this RFC https://docs.google.com/document/d/1OYyA1wYfQRbIJOIBDliYoWjiUlkFBNxH_U2R4WpVRZ4/edit#heading=h.pecv6xb7ial6
|
|
54
|
+
and the following GitHub issue for more information https://github.com/inlang/monorepo/issues/1678.
|
|
55
|
+
|
|
56
|
+
- Monorepo support https://github.com/inlang/monorepo/discussions/258.
|
|
57
|
+
- Required for many other future features like caching, first class offline support, and more.
|
|
58
|
+
- Stablize the inlang project format.
|
|
59
|
+
`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
2
2
|
import { expect, test } from "vitest"
|
|
3
|
-
import { Plugin, MessageLintRule } from "
|
|
3
|
+
import { Plugin, MessageLintRule } from "../index.js"
|
|
4
4
|
// import { createNodeishMemoryFs } from "@lix-js/fs"
|
|
5
5
|
import { Type } from "@sinclair/typebox"
|
|
6
6
|
import { validatedModuleSettings } from "./validatedModuleSettings.js"
|