@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.
- package/dist/adapter/solidAdapter.test.js +16 -15
- package/dist/createNodeishFsWithAbsolutePaths.d.ts +2 -2
- package/dist/createNodeishFsWithAbsolutePaths.d.ts.map +1 -1
- package/dist/createNodeishFsWithAbsolutePaths.js +5 -4
- package/dist/createNodeishFsWithAbsolutePaths.test.js +5 -4
- package/dist/createNodeishFsWithWatcher.d.ts +12 -0
- package/dist/createNodeishFsWithWatcher.d.ts.map +1 -0
- package/dist/createNodeishFsWithWatcher.js +52 -0
- package/dist/createNodeishFsWithWatcher.test.d.ts +2 -0
- package/dist/createNodeishFsWithWatcher.test.d.ts.map +1 -0
- package/dist/createNodeishFsWithWatcher.test.js +32 -0
- package/dist/isAbsolutePath.test.js +1 -1
- package/dist/loadProject.d.ts +4 -4
- package/dist/loadProject.d.ts.map +1 -1
- package/dist/loadProject.js +45 -24
- package/dist/loadProject.test.js +175 -74
- 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/plugins/resolvePlugins.js +1 -1
- package/dist/resolve-modules/plugins/resolvePlugins.test.js +2 -1
- package/dist/resolve-modules/plugins/types.d.ts +2 -1
- package/dist/resolve-modules/plugins/types.d.ts.map +1 -1
- package/dist/resolve-modules/validateModuleSettings.test.js +1 -1
- package/package.json +56 -56
- package/src/adapter/solidAdapter.test.ts +16 -15
- package/src/createNodeishFsWithAbsolutePaths.test.ts +5 -4
- package/src/createNodeishFsWithAbsolutePaths.ts +9 -5
- package/src/createNodeishFsWithWatcher.test.ts +40 -0
- package/src/createNodeishFsWithWatcher.ts +58 -0
- package/src/isAbsolutePath.test.ts +1 -1
- package/src/loadProject.test.ts +206 -75
- package/src/loadProject.ts +61 -31
- package/src/migrations/migrateToDirectory.test.ts +54 -0
- package/src/migrations/migrateToDirectory.ts +59 -0
- package/src/resolve-modules/plugins/resolvePlugins.test.ts +2 -1
- package/src/resolve-modules/plugins/resolvePlugins.ts +1 -1
- package/src/resolve-modules/plugins/types.ts +5 -2
- package/src/resolve-modules/validateModuleSettings.test.ts +1 -1
package/src/loadProject.ts
CHANGED
|
@@ -23,15 +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
|
+
import { createNodeishFsWithWatcher } from "./createNodeishFsWithWatcher.js"
|
|
29
|
+
import { maybeMigrateToDirectory } from "./migrations/migrateToDirectory.js"
|
|
28
30
|
|
|
29
31
|
const settingsCompiler = TypeCompiler.Compile(ProjectSettings)
|
|
30
32
|
|
|
31
33
|
/**
|
|
32
34
|
* Creates an inlang instance.
|
|
33
35
|
*
|
|
34
|
-
* @param
|
|
36
|
+
* @param projectPath - Absolute path to the inlang settings file.
|
|
35
37
|
* @param nodeishFs - Filesystem that implements the NodeishFilesystemSubset interface.
|
|
36
38
|
* @param _import - Use `_import` to pass a custom import function for testing,
|
|
37
39
|
* and supporting legacy resolvedModules such as CJS.
|
|
@@ -39,29 +41,39 @@ const settingsCompiler = TypeCompiler.Compile(ProjectSettings)
|
|
|
39
41
|
*
|
|
40
42
|
*/
|
|
41
43
|
export const loadProject = async (args: {
|
|
42
|
-
|
|
43
|
-
nodeishFs:
|
|
44
|
+
projectPath: string
|
|
45
|
+
nodeishFs: NodeishFilesystem
|
|
44
46
|
_import?: ImportFunction
|
|
45
47
|
_capture?: (id: string, props: Record<string, unknown>) => void
|
|
46
48
|
}): Promise<InlangProject> => {
|
|
49
|
+
const projectPath = normalizePath(args.projectPath)
|
|
50
|
+
|
|
51
|
+
// -- migrate if outdated ------------------------------------------------
|
|
52
|
+
|
|
53
|
+
await maybeMigrateToDirectory({ nodeishFs: args.nodeishFs, projectPath })
|
|
54
|
+
|
|
47
55
|
// -- validation --------------------------------------------------------
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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) {
|
|
52
66
|
throw new LoadProjectInvalidArgument(
|
|
53
|
-
`Expected
|
|
54
|
-
{ argument: "
|
|
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" }
|
|
55
69
|
)
|
|
56
70
|
}
|
|
57
71
|
|
|
58
|
-
const settingsFilePath = normalizePath(args.settingsFilePath)
|
|
59
|
-
|
|
60
72
|
// -- load project ------------------------------------------------------
|
|
61
73
|
return await createRoot(async () => {
|
|
62
74
|
const [initialized, markInitAsComplete, markInitAsFailed] = createAwaitable()
|
|
63
75
|
const nodeishFs = createNodeishFsWithAbsolutePaths({
|
|
64
|
-
|
|
76
|
+
projectPath,
|
|
65
77
|
nodeishFs: args.nodeishFs,
|
|
66
78
|
})
|
|
67
79
|
|
|
@@ -69,7 +81,7 @@ export const loadProject = async (args: {
|
|
|
69
81
|
|
|
70
82
|
const [settings, _setSettings] = createSignal<ProjectSettings>()
|
|
71
83
|
createEffect(() => {
|
|
72
|
-
loadSettings({ settingsFilePath, nodeishFs })
|
|
84
|
+
loadSettings({ settingsFilePath: projectPath + "/settings.json", nodeishFs })
|
|
73
85
|
.then((settings) => {
|
|
74
86
|
setSettings(settings)
|
|
75
87
|
// rename settings to get a convenient access to the data in Posthog
|
|
@@ -83,7 +95,7 @@ export const loadProject = async (args: {
|
|
|
83
95
|
// TODO: create FS watcher and update settings on change
|
|
84
96
|
|
|
85
97
|
const writeSettingsToDisk = skipFirst((settings: ProjectSettings) =>
|
|
86
|
-
_writeSettingsToDisk({ nodeishFs, settings })
|
|
98
|
+
_writeSettingsToDisk({ nodeishFs, settings, projectPath })
|
|
87
99
|
)
|
|
88
100
|
|
|
89
101
|
const setSettings = (settings: ProjectSettings): Result<void, ProjectSettingsInvalidError> => {
|
|
@@ -126,6 +138,7 @@ export const loadProject = async (args: {
|
|
|
126
138
|
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)
|
|
127
139
|
|
|
128
140
|
const [messages, setMessages] = createSignal<Message[]>()
|
|
141
|
+
|
|
129
142
|
createEffect(() => {
|
|
130
143
|
const conf = settings()
|
|
131
144
|
if (!conf) return
|
|
@@ -138,16 +151,28 @@ export const loadProject = async (args: {
|
|
|
138
151
|
return
|
|
139
152
|
}
|
|
140
153
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
154
|
+
const loadAndSetMessages = async (fs: NodeishFilesystemSubset) => {
|
|
155
|
+
makeTrulyAsync(
|
|
156
|
+
_resolvedModules.resolvedPluginApi.loadMessages({
|
|
157
|
+
settings: settingsValue,
|
|
158
|
+
nodeishFs: fs,
|
|
159
|
+
})
|
|
160
|
+
)
|
|
161
|
+
.then((messages) => {
|
|
162
|
+
setMessages(messages)
|
|
163
|
+
markInitAsComplete()
|
|
164
|
+
})
|
|
165
|
+
.catch((err) => markInitAsFailed(new PluginLoadMessagesError({ cause: err })))
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const fsWithWatcher = createNodeishFsWithWatcher({
|
|
169
|
+
nodeishFs: nodeishFs,
|
|
170
|
+
updateMessages: () => {
|
|
171
|
+
loadAndSetMessages(nodeishFs)
|
|
172
|
+
},
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
loadAndSetMessages(fsWithWatcher)
|
|
151
176
|
})
|
|
152
177
|
|
|
153
178
|
// -- installed items ----------------------------------------------------
|
|
@@ -198,18 +223,22 @@ export const loadProject = async (args: {
|
|
|
198
223
|
500,
|
|
199
224
|
async (newMessages) => {
|
|
200
225
|
try {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
226
|
+
if (JSON.stringify(newMessages) !== JSON.stringify(messages())) {
|
|
227
|
+
await resolvedModules()?.resolvedPluginApi.saveMessages({
|
|
228
|
+
settings: settingsValue,
|
|
229
|
+
messages: newMessages,
|
|
230
|
+
})
|
|
231
|
+
}
|
|
205
232
|
} catch (err) {
|
|
206
233
|
throw new PluginSaveMessagesError({
|
|
207
234
|
cause: err,
|
|
208
235
|
})
|
|
209
236
|
}
|
|
237
|
+
const abortController = new AbortController()
|
|
210
238
|
if (
|
|
211
239
|
newMessages.length !== 0 &&
|
|
212
|
-
JSON.stringify(newMessages) !== JSON.stringify(messages())
|
|
240
|
+
JSON.stringify(newMessages) !== JSON.stringify(messages()) &&
|
|
241
|
+
nodeishFs.watch("/", { signal: abortController.signal }) === undefined
|
|
213
242
|
) {
|
|
214
243
|
setMessages(newMessages)
|
|
215
244
|
}
|
|
@@ -304,6 +333,7 @@ const parseSettings = (settings: unknown) => {
|
|
|
304
333
|
}
|
|
305
334
|
|
|
306
335
|
const _writeSettingsToDisk = async (args: {
|
|
336
|
+
projectPath: string
|
|
307
337
|
nodeishFs: NodeishFilesystemSubset
|
|
308
338
|
settings: ProjectSettings
|
|
309
339
|
}) => {
|
|
@@ -316,7 +346,7 @@ const _writeSettingsToDisk = async (args: {
|
|
|
316
346
|
}
|
|
317
347
|
|
|
318
348
|
const { error: writeSettingsError } = await tryCatch(async () =>
|
|
319
|
-
args.nodeishFs.writeFile("
|
|
349
|
+
args.nodeishFs.writeFile(args.projectPath + "/settings.json", serializedSettings)
|
|
320
350
|
)
|
|
321
351
|
|
|
322
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
|
+
})
|
|
@@ -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
|
+
`
|
|
@@ -60,7 +60,7 @@ it("should expose the project settings including the plugin settings", async ()
|
|
|
60
60
|
settings: settings,
|
|
61
61
|
nodeishFs: {} as any,
|
|
62
62
|
})
|
|
63
|
-
await resolved.data.loadMessages!({ settings })
|
|
63
|
+
await resolved.data.loadMessages!({ settings, nodeishFs: {} as any })
|
|
64
64
|
await resolved.data.saveMessages!({ settings, messages: [] })
|
|
65
65
|
})
|
|
66
66
|
|
|
@@ -82,6 +82,7 @@ describe("loadMessages", () => {
|
|
|
82
82
|
expect(
|
|
83
83
|
await resolved.data.loadMessages!({
|
|
84
84
|
settings: {} as any,
|
|
85
|
+
nodeishFs: {} as any,
|
|
85
86
|
})
|
|
86
87
|
).toEqual([{ id: "test", expressions: [], selectors: [], variants: [] }])
|
|
87
88
|
})
|
|
@@ -91,7 +91,7 @@ export const resolvePlugins: ResolvePluginsFunction = async (args) => {
|
|
|
91
91
|
result.data.loadMessages = (_args) =>
|
|
92
92
|
plugin.loadMessages!({
|
|
93
93
|
..._args,
|
|
94
|
-
nodeishFs
|
|
94
|
+
// renoved nodeishFs from args because we need to pass custom wrapped fs that establishes a watcher
|
|
95
95
|
})
|
|
96
96
|
}
|
|
97
97
|
|
|
@@ -18,7 +18,7 @@ import type { ProjectSettings } from "@inlang/project-settings"
|
|
|
18
18
|
*/
|
|
19
19
|
export type NodeishFilesystemSubset = Pick<
|
|
20
20
|
NodeishFilesystem,
|
|
21
|
-
"readFile" | "readdir" | "mkdir" | "writeFile"
|
|
21
|
+
"readFile" | "readdir" | "mkdir" | "writeFile" | "watch"
|
|
22
22
|
>
|
|
23
23
|
|
|
24
24
|
/**
|
|
@@ -44,7 +44,10 @@ export type ResolvePluginsFunction = (args: {
|
|
|
44
44
|
* The API after resolving the plugins.
|
|
45
45
|
*/
|
|
46
46
|
export type ResolvedPluginApi = {
|
|
47
|
-
loadMessages: (args: {
|
|
47
|
+
loadMessages: (args: {
|
|
48
|
+
settings: ProjectSettings
|
|
49
|
+
nodeishFs: NodeishFilesystemSubset
|
|
50
|
+
}) => Promise<Message[]> | Message[]
|
|
48
51
|
saveMessages: (args: { settings: ProjectSettings; messages: Message[] }) => Promise<void> | void
|
|
49
52
|
/**
|
|
50
53
|
* App specific APIs.
|
|
@@ -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"
|