@inlang/sdk 0.1.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 +25 -0
- package/dist/adapter/solidAdapter.d.ts +32 -0
- package/dist/adapter/solidAdapter.d.ts.map +1 -0
- package/dist/adapter/solidAdapter.js +39 -0
- package/dist/adapter/solidAdapter.test.d.ts +2 -0
- package/dist/adapter/solidAdapter.test.d.ts.map +1 -0
- package/dist/adapter/solidAdapter.test.js +284 -0
- package/dist/api.d.ts +88 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +1 -0
- package/dist/createMessageLintReportsQuery.d.ts +9 -0
- package/dist/createMessageLintReportsQuery.d.ts.map +1 -0
- package/dist/createMessageLintReportsQuery.js +48 -0
- package/dist/createMessagesQuery.d.ts +7 -0
- package/dist/createMessagesQuery.d.ts.map +1 -0
- package/dist/createMessagesQuery.js +57 -0
- package/dist/createMessagesQuery.test.d.ts +2 -0
- package/dist/createMessagesQuery.test.d.ts.map +1 -0
- package/dist/createMessagesQuery.test.js +304 -0
- package/dist/errors.d.ts +22 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +39 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/lint/index.d.ts +3 -0
- package/dist/lint/index.d.ts.map +1 -0
- package/dist/lint/index.js +2 -0
- package/dist/lint/message/errors.d.ts +7 -0
- package/dist/lint/message/errors.d.ts.map +1 -0
- package/dist/lint/message/errors.js +9 -0
- package/dist/lint/message/lintMessages.d.ts +17 -0
- package/dist/lint/message/lintMessages.d.ts.map +1 -0
- package/dist/lint/message/lintMessages.js +12 -0
- package/dist/lint/message/lintMessages.test.d.ts +2 -0
- package/dist/lint/message/lintMessages.test.d.ts.map +1 -0
- package/dist/lint/message/lintMessages.test.js +105 -0
- package/dist/lint/message/lintSingleMessage.d.ts +23 -0
- package/dist/lint/message/lintSingleMessage.d.ts.map +1 -0
- package/dist/lint/message/lintSingleMessage.js +36 -0
- package/dist/lint/message/lintSingleMessage.test.d.ts +2 -0
- package/dist/lint/message/lintSingleMessage.test.d.ts.map +1 -0
- package/dist/lint/message/lintSingleMessage.test.js +155 -0
- package/dist/messages/errors.d.ts +13 -0
- package/dist/messages/errors.d.ts.map +1 -0
- package/dist/messages/errors.js +18 -0
- package/dist/messages/index.d.ts +3 -0
- package/dist/messages/index.d.ts.map +1 -0
- package/dist/messages/index.js +2 -0
- package/dist/messages/variant.d.ts +46 -0
- package/dist/messages/variant.d.ts.map +1 -0
- package/dist/messages/variant.js +177 -0
- package/dist/messages/variant.test.d.ts +2 -0
- package/dist/messages/variant.test.d.ts.map +1 -0
- package/dist/messages/variant.test.js +379 -0
- package/dist/openInlangProject.d.ts +18 -0
- package/dist/openInlangProject.d.ts.map +1 -0
- package/dist/openInlangProject.js +226 -0
- package/dist/openInlangProject.test.d.ts +2 -0
- package/dist/openInlangProject.test.d.ts.map +1 -0
- package/dist/openInlangProject.test.js +627 -0
- package/dist/parseConfig.d.ts +8 -0
- package/dist/parseConfig.d.ts.map +1 -0
- package/dist/parseConfig.js +26 -0
- package/dist/reactivity/map.d.ts +66 -0
- package/dist/reactivity/map.d.ts.map +1 -0
- package/dist/reactivity/map.js +143 -0
- package/dist/reactivity/solid.d.ts +12 -0
- package/dist/reactivity/solid.d.ts.map +1 -0
- package/dist/reactivity/solid.js +13 -0
- package/dist/reactivity/trigger.d.ts +11 -0
- package/dist/reactivity/trigger.d.ts.map +1 -0
- package/dist/reactivity/trigger.js +46 -0
- package/dist/resolve-modules/errors.d.ts +34 -0
- package/dist/resolve-modules/errors.d.ts.map +1 -0
- package/dist/resolve-modules/errors.js +35 -0
- package/dist/resolve-modules/import.d.ts +35 -0
- package/dist/resolve-modules/import.d.ts.map +1 -0
- package/dist/resolve-modules/import.js +40 -0
- package/dist/resolve-modules/import.test.d.ts +2 -0
- package/dist/resolve-modules/import.test.d.ts.map +1 -0
- package/dist/resolve-modules/import.test.js +45 -0
- package/dist/resolve-modules/index.d.ts +3 -0
- package/dist/resolve-modules/index.d.ts.map +1 -0
- package/dist/resolve-modules/index.js +2 -0
- package/dist/resolve-modules/message-lint-rules/errors.d.ts +8 -0
- package/dist/resolve-modules/message-lint-rules/errors.d.ts.map +1 -0
- package/dist/resolve-modules/message-lint-rules/errors.js +8 -0
- package/dist/resolve-modules/message-lint-rules/resolveMessageLintRules.d.ts +9 -0
- package/dist/resolve-modules/message-lint-rules/resolveMessageLintRules.d.ts.map +1 -0
- package/dist/resolve-modules/message-lint-rules/resolveMessageLintRules.js +21 -0
- package/dist/resolve-modules/plugins/errors.d.ts +28 -0
- package/dist/resolve-modules/plugins/errors.d.ts.map +1 -0
- package/dist/resolve-modules/plugins/errors.js +44 -0
- package/dist/resolve-modules/plugins/resolvePlugins.d.ts +3 -0
- package/dist/resolve-modules/plugins/resolvePlugins.d.ts.map +1 -0
- package/dist/resolve-modules/plugins/resolvePlugins.js +108 -0
- package/dist/resolve-modules/plugins/resolvePlugins.test.d.ts +2 -0
- package/dist/resolve-modules/plugins/resolvePlugins.test.d.ts.map +1 -0
- package/dist/resolve-modules/plugins/resolvePlugins.test.js +289 -0
- package/dist/resolve-modules/plugins/types.d.ts +60 -0
- package/dist/resolve-modules/plugins/types.d.ts.map +1 -0
- package/dist/resolve-modules/plugins/types.js +1 -0
- package/dist/resolve-modules/plugins/types.test.d.ts +2 -0
- package/dist/resolve-modules/plugins/types.test.d.ts.map +1 -0
- package/dist/resolve-modules/plugins/types.test.js +49 -0
- package/dist/resolve-modules/resolveModules.d.ts +3 -0
- package/dist/resolve-modules/resolveModules.d.ts.map +1 -0
- package/dist/resolve-modules/resolveModules.js +70 -0
- package/dist/resolve-modules/resolveModules.test.d.ts +2 -0
- package/dist/resolve-modules/resolveModules.test.d.ts.map +1 -0
- package/dist/resolve-modules/resolveModules.test.js +143 -0
- package/dist/resolve-modules/types.d.ts +62 -0
- package/dist/resolve-modules/types.d.ts.map +1 -0
- package/dist/resolve-modules/types.js +1 -0
- package/dist/test-utilities/createMessage.d.ts +17 -0
- package/dist/test-utilities/createMessage.d.ts.map +1 -0
- package/dist/test-utilities/createMessage.js +16 -0
- package/dist/test-utilities/createMessage.test.d.ts +2 -0
- package/dist/test-utilities/createMessage.test.d.ts.map +1 -0
- package/dist/test-utilities/createMessage.test.js +91 -0
- package/dist/test-utilities/index.d.ts +2 -0
- package/dist/test-utilities/index.d.ts.map +1 -0
- package/dist/test-utilities/index.js +1 -0
- package/dist/versionedInterfaces.d.ts +8 -0
- package/dist/versionedInterfaces.d.ts.map +1 -0
- package/dist/versionedInterfaces.js +8 -0
- package/package.json +58 -0
- package/src/adapter/solidAdapter.test.ts +363 -0
- package/src/adapter/solidAdapter.ts +77 -0
- package/src/api.ts +86 -0
- package/src/createMessageLintReportsQuery.ts +77 -0
- package/src/createMessagesQuery.test.ts +435 -0
- package/src/createMessagesQuery.ts +64 -0
- package/src/errors.ts +46 -0
- package/src/index.ts +29 -0
- package/src/lint/index.ts +2 -0
- package/src/lint/message/errors.ts +9 -0
- package/src/lint/message/lintMessages.test.ts +122 -0
- package/src/lint/message/lintMessages.ts +33 -0
- package/src/lint/message/lintSingleMessage.test.ts +183 -0
- package/src/lint/message/lintSingleMessage.ts +62 -0
- package/src/messages/errors.ts +25 -0
- package/src/messages/index.ts +2 -0
- package/src/messages/variant.test.ts +444 -0
- package/src/messages/variant.ts +242 -0
- package/src/openInlangProject.test.ts +734 -0
- package/src/openInlangProject.ts +337 -0
- package/src/parseConfig.ts +33 -0
- package/src/reactivity/map.ts +135 -0
- package/src/reactivity/solid.ts +36 -0
- package/src/reactivity/trigger.ts +46 -0
- package/src/resolve-modules/errors.ts +39 -0
- package/src/resolve-modules/import.test.ts +58 -0
- package/src/resolve-modules/import.ts +69 -0
- package/src/resolve-modules/index.ts +2 -0
- package/src/resolve-modules/message-lint-rules/errors.ts +9 -0
- package/src/resolve-modules/message-lint-rules/resolveMessageLintRules.ts +24 -0
- package/src/resolve-modules/plugins/errors.ts +57 -0
- package/src/resolve-modules/plugins/resolvePlugins.test.ts +340 -0
- package/src/resolve-modules/plugins/resolvePlugins.ts +170 -0
- package/src/resolve-modules/plugins/types.test.ts +57 -0
- package/src/resolve-modules/plugins/types.ts +77 -0
- package/src/resolve-modules/resolveModules.test.ts +176 -0
- package/src/resolve-modules/resolveModules.ts +97 -0
- package/src/resolve-modules/types.ts +71 -0
- package/src/test-utilities/createMessage.test.ts +100 -0
- package/src/test-utilities/createMessage.ts +20 -0
- package/src/test-utilities/index.ts +1 -0
- package/src/versionedInterfaces.ts +9 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
2
|
+
import type {
|
|
3
|
+
InlangProject,
|
|
4
|
+
InstalledMessageLintRule,
|
|
5
|
+
InstalledPlugin,
|
|
6
|
+
Subscribable,
|
|
7
|
+
} from "./api.js"
|
|
8
|
+
import { type ImportFunction, resolveModules } from "./resolve-modules/index.js"
|
|
9
|
+
import { TypeCompiler } from "@sinclair/typebox/compiler"
|
|
10
|
+
import { Value } from "@sinclair/typebox/value"
|
|
11
|
+
import {
|
|
12
|
+
ProjectFilePathNotFoundError,
|
|
13
|
+
ProjectFileJSONSyntaxError,
|
|
14
|
+
InvalidConfigError,
|
|
15
|
+
NoPluginProvidesLoadOrSaveMessagesError,
|
|
16
|
+
PluginLoadMessagesError,
|
|
17
|
+
PluginSaveMessagesError,
|
|
18
|
+
} from "./errors.js"
|
|
19
|
+
import { createRoot, createSignal, createEffect } from "./reactivity/solid.js"
|
|
20
|
+
import { createMessagesQuery } from "./createMessagesQuery.js"
|
|
21
|
+
import { debounce } from "throttle-debounce"
|
|
22
|
+
import { createMessageLintReportsQuery } from "./createMessageLintReportsQuery.js"
|
|
23
|
+
import { ProjectConfig, Message, type NodeishFilesystemSubset } from "./versionedInterfaces.js"
|
|
24
|
+
import { tryCatch, type Result } from "@inlang/result"
|
|
25
|
+
|
|
26
|
+
const ConfigCompiler = TypeCompiler.Compile(ProjectConfig)
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Creates an inlang instance.
|
|
30
|
+
*
|
|
31
|
+
* - Use `_import` to pass a custom import function for testing,
|
|
32
|
+
* and supporting legacy resolvedModules such as CJS.
|
|
33
|
+
*
|
|
34
|
+
*/
|
|
35
|
+
export const openInlangProject = async (args: {
|
|
36
|
+
projectFilePath: string
|
|
37
|
+
nodeishFs: NodeishFilesystemSubset
|
|
38
|
+
_import?: ImportFunction
|
|
39
|
+
_capture?: (id: string, props: Record<string, unknown>) => void
|
|
40
|
+
}): Promise<InlangProject> => {
|
|
41
|
+
return await createRoot(async () => {
|
|
42
|
+
const [initialized, markInitAsComplete, markInitAsFailed] = createAwaitable()
|
|
43
|
+
|
|
44
|
+
// -- config ------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
const [config, _setConfig] = createSignal<ProjectConfig>()
|
|
47
|
+
createEffect(() => {
|
|
48
|
+
loadConfig({ projectFilePath: args.projectFilePath, nodeishFs: args.nodeishFs })
|
|
49
|
+
.then((config) => {
|
|
50
|
+
setConfig(config)
|
|
51
|
+
args._capture?.("SDK used config", config)
|
|
52
|
+
})
|
|
53
|
+
.catch((err) => {
|
|
54
|
+
markInitAsFailed(err)
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
// TODO: create FS watcher and update config on change
|
|
58
|
+
|
|
59
|
+
const writeConfigToDisk = skipFirst((config: ProjectConfig) =>
|
|
60
|
+
_writeConfigToDisk({ nodeishFs: args.nodeishFs, config }),
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
const setConfig = (config: ProjectConfig): Result<void, InvalidConfigError> => {
|
|
64
|
+
try {
|
|
65
|
+
const validatedConfig = validateConfig(config)
|
|
66
|
+
_setConfig(validatedConfig)
|
|
67
|
+
|
|
68
|
+
writeConfigToDisk(validatedConfig)
|
|
69
|
+
return { data: undefined }
|
|
70
|
+
} catch (error: unknown) {
|
|
71
|
+
if (error instanceof InvalidConfigError) {
|
|
72
|
+
return { error }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
throw new Error("unhandled")
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// -- resolvedModules -----------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
const [resolvedModules, setResolvedModules] =
|
|
82
|
+
createSignal<Awaited<ReturnType<typeof resolveModules>>>()
|
|
83
|
+
|
|
84
|
+
createEffect(() => {
|
|
85
|
+
const conf = config()
|
|
86
|
+
if (!conf) return
|
|
87
|
+
|
|
88
|
+
loadModules({ config: conf, nodeishFs: args.nodeishFs, _import: args._import })
|
|
89
|
+
.then((resolvedModules) => {
|
|
90
|
+
// TODO move to resolveModules
|
|
91
|
+
if (
|
|
92
|
+
!resolvedModules.resolvedPluginApi.loadMessages ||
|
|
93
|
+
!resolvedModules.resolvedPluginApi.saveMessages
|
|
94
|
+
) {
|
|
95
|
+
throw new NoPluginProvidesLoadOrSaveMessagesError()
|
|
96
|
+
}
|
|
97
|
+
setResolvedModules(resolvedModules)
|
|
98
|
+
|
|
99
|
+
// TODO: handle `detectedLanguageTags`
|
|
100
|
+
})
|
|
101
|
+
.catch((err) => markInitAsFailed(err))
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
// -- messages ----------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
let configValue: ProjectConfig
|
|
107
|
+
createEffect(() => (configValue = config()!)) // workaround to not run effects twice (e.g. config change + modules change) (I'm sure there exists a solid way of doing this, but I haven't found it yet)
|
|
108
|
+
|
|
109
|
+
const [messages, setMessages] = createSignal<Message[]>()
|
|
110
|
+
createEffect(() => {
|
|
111
|
+
const conf = config()
|
|
112
|
+
if (!conf) return
|
|
113
|
+
|
|
114
|
+
const _resolvedModules = resolvedModules()
|
|
115
|
+
if (!_resolvedModules) return
|
|
116
|
+
|
|
117
|
+
if (!_resolvedModules.resolvedPluginApi.loadMessages) {
|
|
118
|
+
markInitAsFailed(undefined)
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
makeTrulyAsync(
|
|
123
|
+
_resolvedModules.resolvedPluginApi.loadMessages({
|
|
124
|
+
languageTags: configValue!.languageTags,
|
|
125
|
+
sourceLanguageTag: configValue!.sourceLanguageTag,
|
|
126
|
+
}),
|
|
127
|
+
)
|
|
128
|
+
.then((messages) => {
|
|
129
|
+
setMessages(messages)
|
|
130
|
+
markInitAsComplete()
|
|
131
|
+
})
|
|
132
|
+
.catch((err) =>
|
|
133
|
+
markInitAsFailed(new PluginLoadMessagesError("Error in load messages", { cause: err })),
|
|
134
|
+
)
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
// -- installed items ----------------------------------------------------
|
|
138
|
+
|
|
139
|
+
const installedMessageLintRules = () => {
|
|
140
|
+
if (!resolvedModules()) return []
|
|
141
|
+
return resolvedModules()!.messageLintRules.map(
|
|
142
|
+
(rule) =>
|
|
143
|
+
({
|
|
144
|
+
meta: rule.meta,
|
|
145
|
+
module:
|
|
146
|
+
resolvedModules()?.meta.find((m) => m.id.includes(rule.meta.id))?.module ??
|
|
147
|
+
"Unknown module. You stumbled on a bug in inlang's source code. Please open an issue.",
|
|
148
|
+
|
|
149
|
+
// default to warning, see https://github.com/inlang/inlang/issues/1254
|
|
150
|
+
lintLevel:
|
|
151
|
+
configValue.settings["project.messageLintRuleLevels"]?.[rule.meta.id] ?? "warning",
|
|
152
|
+
} satisfies InstalledMessageLintRule),
|
|
153
|
+
) satisfies Array<InstalledMessageLintRule>
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const installedPlugins = () => {
|
|
157
|
+
if (!resolvedModules()) return []
|
|
158
|
+
return resolvedModules()!.plugins.map((plugin) => ({
|
|
159
|
+
meta: plugin.meta,
|
|
160
|
+
module:
|
|
161
|
+
resolvedModules()?.meta.find((m) => m.id.includes(plugin.meta.id))?.module ??
|
|
162
|
+
"Unknown module. You stumbled on a bug in inlang's source code. Please open an issue.",
|
|
163
|
+
})) satisfies Array<InstalledPlugin>
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// -- app ---------------------------------------------------------------
|
|
167
|
+
|
|
168
|
+
const initializeError: Error | undefined = await initialized.catch((error) => error)
|
|
169
|
+
|
|
170
|
+
const messagesQuery = createMessagesQuery(() => messages() || [])
|
|
171
|
+
const lintReportsQuery = createMessageLintReportsQuery(
|
|
172
|
+
messages,
|
|
173
|
+
config,
|
|
174
|
+
installedMessageLintRules,
|
|
175
|
+
resolvedModules,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
const debouncedSave = skipFirst(
|
|
179
|
+
debounce(
|
|
180
|
+
500,
|
|
181
|
+
async (newMessages) => {
|
|
182
|
+
try {
|
|
183
|
+
await resolvedModules()!.resolvedPluginApi.saveMessages({ messages: newMessages })
|
|
184
|
+
} catch (err) {
|
|
185
|
+
throw new PluginSaveMessagesError("Error in saving messages", {
|
|
186
|
+
cause: err,
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
if (
|
|
190
|
+
newMessages.length !== 0 &&
|
|
191
|
+
JSON.stringify(newMessages) !== JSON.stringify(messages())
|
|
192
|
+
) {
|
|
193
|
+
setMessages(newMessages)
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
{ atBegin: false },
|
|
197
|
+
),
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
createEffect(() => {
|
|
201
|
+
debouncedSave(messagesQuery.getAll())
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
installed: {
|
|
206
|
+
plugins: createSubscribable(() => installedPlugins()),
|
|
207
|
+
messageLintRules: createSubscribable(() => installedMessageLintRules()),
|
|
208
|
+
},
|
|
209
|
+
errors: createSubscribable(() => [
|
|
210
|
+
...(initializeError ? [initializeError] : []),
|
|
211
|
+
...(resolvedModules() ? resolvedModules()!.errors : []),
|
|
212
|
+
// have a query error exposed
|
|
213
|
+
//...(lintErrors() ?? []),
|
|
214
|
+
]),
|
|
215
|
+
config: createSubscribable(() => config()),
|
|
216
|
+
setConfig,
|
|
217
|
+
customApi: createSubscribable(() => resolvedModules()?.resolvedPluginApi.customApi || {}),
|
|
218
|
+
query: {
|
|
219
|
+
messages: messagesQuery,
|
|
220
|
+
messageLintReports: lintReportsQuery,
|
|
221
|
+
},
|
|
222
|
+
} satisfies InlangProject
|
|
223
|
+
})
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
//const x = {} as InlangProject
|
|
227
|
+
|
|
228
|
+
// ------------------------------------------------------------------------------------------------
|
|
229
|
+
|
|
230
|
+
const loadConfig = async (args: {
|
|
231
|
+
projectFilePath: string
|
|
232
|
+
nodeishFs: NodeishFilesystemSubset
|
|
233
|
+
}) => {
|
|
234
|
+
const { data: configFile, error: configFileError } = await tryCatch(
|
|
235
|
+
async () => await args.nodeishFs.readFile(args.projectFilePath, { encoding: "utf-8" }),
|
|
236
|
+
)
|
|
237
|
+
if (configFileError)
|
|
238
|
+
throw new ProjectFilePathNotFoundError(
|
|
239
|
+
`Could not locate config file in (${args.projectFilePath}).`,
|
|
240
|
+
{
|
|
241
|
+
cause: configFileError,
|
|
242
|
+
},
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
const { data: parsedConfig, error: parseConfigError } = tryCatch(() => JSON.parse(configFile!))
|
|
246
|
+
if (parseConfigError)
|
|
247
|
+
throw new ProjectFileJSONSyntaxError(`The config is not a valid JSON file.`, {
|
|
248
|
+
cause: parseConfigError,
|
|
249
|
+
})
|
|
250
|
+
return validateConfig(parsedConfig)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const validateConfig = (config: unknown) => {
|
|
254
|
+
const typeErrors = [...ConfigCompiler.Errors(config)]
|
|
255
|
+
if (typeErrors.length > 0) {
|
|
256
|
+
throw new InvalidConfigError(`The config is invalid according to the schema.`, {
|
|
257
|
+
cause: typeErrors,
|
|
258
|
+
})
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// @ts-ignore - fix after refactor
|
|
262
|
+
return Value.Cast(ProjectConfig, config)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const _writeConfigToDisk = async (args: {
|
|
266
|
+
nodeishFs: NodeishFilesystemSubset
|
|
267
|
+
config: ProjectConfig
|
|
268
|
+
}) => {
|
|
269
|
+
const { data: serializedConfig, error: serializeConfigError } = tryCatch(() =>
|
|
270
|
+
// TODO: this will probably not match the original formatting
|
|
271
|
+
JSON.stringify(args.config, undefined, 2),
|
|
272
|
+
)
|
|
273
|
+
if (serializeConfigError) throw serializeConfigError
|
|
274
|
+
|
|
275
|
+
const { error: writeConfigError } = await tryCatch(async () =>
|
|
276
|
+
args.nodeishFs.writeFile("./project.inlang.json", serializedConfig!),
|
|
277
|
+
)
|
|
278
|
+
if (writeConfigError) throw writeConfigError
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ------------------------------------------------------------------------------------------------
|
|
282
|
+
|
|
283
|
+
const loadModules = async (args: {
|
|
284
|
+
config: ProjectConfig
|
|
285
|
+
nodeishFs: NodeishFilesystemSubset
|
|
286
|
+
_import?: ImportFunction
|
|
287
|
+
}) =>
|
|
288
|
+
resolveModules({
|
|
289
|
+
config: args.config,
|
|
290
|
+
nodeishFs: args.nodeishFs,
|
|
291
|
+
_import: args._import,
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
const createAwaitable = () => {
|
|
295
|
+
let resolve: () => void
|
|
296
|
+
let reject: () => void
|
|
297
|
+
|
|
298
|
+
const promise = new Promise<void>((res, rej) => {
|
|
299
|
+
resolve = res
|
|
300
|
+
reject = rej
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
return [promise, resolve!, reject!] as [
|
|
304
|
+
awaitable: Promise<void>,
|
|
305
|
+
resolve: () => void,
|
|
306
|
+
reject: (e: unknown) => void,
|
|
307
|
+
]
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// ------------------------------------------------------------------------------------------------
|
|
311
|
+
|
|
312
|
+
// TODO: create global util type
|
|
313
|
+
type MaybePromise<T> = T | Promise<T>
|
|
314
|
+
|
|
315
|
+
const makeTrulyAsync = <T>(fn: MaybePromise<T>): Promise<T> => (async () => fn)()
|
|
316
|
+
|
|
317
|
+
// Skip initial call, eg. to skip setup of a createEffect
|
|
318
|
+
function skipFirst(func: (args: any) => any) {
|
|
319
|
+
let initial = false
|
|
320
|
+
return function (...args: any) {
|
|
321
|
+
if (initial) {
|
|
322
|
+
// @ts-ignore
|
|
323
|
+
return func.apply(this, args)
|
|
324
|
+
}
|
|
325
|
+
initial = true
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export function createSubscribable<T>(signal: () => T): Subscribable<T> {
|
|
330
|
+
return Object.assign(signal, {
|
|
331
|
+
subscribe: (callback: (value: T) => void) => {
|
|
332
|
+
createEffect(() => {
|
|
333
|
+
callback(signal())
|
|
334
|
+
})
|
|
335
|
+
},
|
|
336
|
+
})
|
|
337
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ProjectConfig } from "./versionedInterfaces.js"
|
|
2
|
+
import type { Result } from "@inlang/result"
|
|
3
|
+
import { TypeCompiler } from "@sinclair/typebox/compiler"
|
|
4
|
+
|
|
5
|
+
export class ParseConfigError extends Error {
|
|
6
|
+
readonly #id = "ParseConfigException"
|
|
7
|
+
|
|
8
|
+
constructor(message: string, cause?: string) {
|
|
9
|
+
super(message)
|
|
10
|
+
this.name = this.#id
|
|
11
|
+
this.cause = cause
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// @ts-ignore - fix after refactor
|
|
16
|
+
const ConfigCompiler = TypeCompiler.Compile(ProjectConfig)
|
|
17
|
+
|
|
18
|
+
export const parseConfig = (config: ProjectConfig): Result<ProjectConfig, ParseConfigError> => {
|
|
19
|
+
if (ConfigCompiler.Check(config)) {
|
|
20
|
+
return {
|
|
21
|
+
data: config,
|
|
22
|
+
error: undefined as never,
|
|
23
|
+
}
|
|
24
|
+
} else {
|
|
25
|
+
return {
|
|
26
|
+
error: new ParseConfigError(
|
|
27
|
+
"The inlang config is not valid.",
|
|
28
|
+
[...ConfigCompiler.Errors(config)].toString(),
|
|
29
|
+
),
|
|
30
|
+
data: undefined as never,
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
|
|
4
|
+
import { batch } from "./solid.js"
|
|
5
|
+
import { TriggerCache } from "./trigger.js"
|
|
6
|
+
|
|
7
|
+
// src/index.ts
|
|
8
|
+
var $KEYS = Symbol("track-keys")
|
|
9
|
+
var ReactiveMap = class extends Map {
|
|
10
|
+
#keyTriggers = new TriggerCache()
|
|
11
|
+
#valueTriggers = new TriggerCache()
|
|
12
|
+
constructor(initial) {
|
|
13
|
+
super()
|
|
14
|
+
if (initial) for (const v of initial) super.set(v[0], v[1])
|
|
15
|
+
}
|
|
16
|
+
// reads
|
|
17
|
+
has(key) {
|
|
18
|
+
this.#keyTriggers.track(key)
|
|
19
|
+
return super.has(key)
|
|
20
|
+
}
|
|
21
|
+
get(key) {
|
|
22
|
+
this.#valueTriggers.track(key)
|
|
23
|
+
return super.get(key)
|
|
24
|
+
}
|
|
25
|
+
get size() {
|
|
26
|
+
this.#keyTriggers.track($KEYS)
|
|
27
|
+
return super.size
|
|
28
|
+
}
|
|
29
|
+
keys() {
|
|
30
|
+
this.#keyTriggers.track($KEYS)
|
|
31
|
+
return super.keys()
|
|
32
|
+
}
|
|
33
|
+
values() {
|
|
34
|
+
this.#keyTriggers.track($KEYS)
|
|
35
|
+
for (const v of super.keys()) this.#valueTriggers.track(v)
|
|
36
|
+
return super.values()
|
|
37
|
+
}
|
|
38
|
+
entries() {
|
|
39
|
+
this.#keyTriggers.track($KEYS)
|
|
40
|
+
for (const v of super.keys()) this.#valueTriggers.track(v)
|
|
41
|
+
return super.entries()
|
|
42
|
+
}
|
|
43
|
+
// writes
|
|
44
|
+
set(key, value) {
|
|
45
|
+
batch(() => {
|
|
46
|
+
if (super.has(key)) {
|
|
47
|
+
if (super.get(key) === value) return
|
|
48
|
+
} else {
|
|
49
|
+
this.#keyTriggers.dirty(key)
|
|
50
|
+
this.#keyTriggers.dirty($KEYS)
|
|
51
|
+
}
|
|
52
|
+
this.#valueTriggers.dirty(key)
|
|
53
|
+
super.set(key, value)
|
|
54
|
+
})
|
|
55
|
+
return this
|
|
56
|
+
}
|
|
57
|
+
delete(key) {
|
|
58
|
+
const r = super.delete(key)
|
|
59
|
+
if (r) {
|
|
60
|
+
batch(() => {
|
|
61
|
+
this.#keyTriggers.dirty(key)
|
|
62
|
+
this.#keyTriggers.dirty($KEYS)
|
|
63
|
+
this.#valueTriggers.dirty(key)
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
return r
|
|
67
|
+
}
|
|
68
|
+
clear() {
|
|
69
|
+
if (super.size) {
|
|
70
|
+
batch(() => {
|
|
71
|
+
for (const v of super.keys()) {
|
|
72
|
+
this.#keyTriggers.dirty(v)
|
|
73
|
+
this.#valueTriggers.dirty(v)
|
|
74
|
+
}
|
|
75
|
+
super.clear()
|
|
76
|
+
this.#keyTriggers.dirty($KEYS)
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// callback
|
|
81
|
+
forEach(callbackfn) {
|
|
82
|
+
this.#keyTriggers.track($KEYS)
|
|
83
|
+
super.forEach((value, key) => callbackfn(value, key, this))
|
|
84
|
+
}
|
|
85
|
+
[Symbol.iterator]() {
|
|
86
|
+
return this.entries()
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
var ReactiveWeakMap = class extends WeakMap {
|
|
90
|
+
#keyTriggers = new TriggerCache(WeakMap)
|
|
91
|
+
#valueTriggers = new TriggerCache(WeakMap)
|
|
92
|
+
constructor(initial) {
|
|
93
|
+
super()
|
|
94
|
+
if (initial) for (const v of initial) super.set(v[0], v[1])
|
|
95
|
+
}
|
|
96
|
+
has(key) {
|
|
97
|
+
this.#keyTriggers.track(key)
|
|
98
|
+
return super.has(key)
|
|
99
|
+
}
|
|
100
|
+
get(key) {
|
|
101
|
+
this.#valueTriggers.track(key)
|
|
102
|
+
return super.get(key)
|
|
103
|
+
}
|
|
104
|
+
set(key, value) {
|
|
105
|
+
batch(() => {
|
|
106
|
+
if (super.has(key)) {
|
|
107
|
+
if (super.get(key) === value) return
|
|
108
|
+
} else this.#keyTriggers.dirty(key)
|
|
109
|
+
this.#valueTriggers.dirty(key)
|
|
110
|
+
super.set(key, value)
|
|
111
|
+
})
|
|
112
|
+
return this
|
|
113
|
+
}
|
|
114
|
+
delete(key) {
|
|
115
|
+
const r = super.delete(key)
|
|
116
|
+
if (r) {
|
|
117
|
+
batch(() => {
|
|
118
|
+
this.#keyTriggers.dirty(key)
|
|
119
|
+
this.#valueTriggers.dirty(key)
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
return r
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function createMap(initial) {
|
|
126
|
+
const map = new ReactiveMap(initial)
|
|
127
|
+
return new Proxy(() => [...map], {
|
|
128
|
+
get: (_, b) => map[b],
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
function createWeakMap(initial) {
|
|
132
|
+
return new ReactiveWeakMap(initial)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export { ReactiveMap, ReactiveWeakMap, createMap, createWeakMap }
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createSignal as _createSignal,
|
|
3
|
+
createMemo as _createMemo,
|
|
4
|
+
createRoot as _createRoot,
|
|
5
|
+
createEffect as _createEffect,
|
|
6
|
+
observable as _observable,
|
|
7
|
+
batch as _batch,
|
|
8
|
+
from as _from,
|
|
9
|
+
getListener as _getListener,
|
|
10
|
+
onCleanup as _onCleanup,
|
|
11
|
+
DEV,
|
|
12
|
+
// @ts-ignore
|
|
13
|
+
} from "solid-js/dist/solid.js"
|
|
14
|
+
|
|
15
|
+
const createSignal = _createSignal as typeof import("solid-js")["createSignal"]
|
|
16
|
+
const createMemo = _createMemo as typeof import("solid-js")["createMemo"]
|
|
17
|
+
const createRoot = _createRoot as typeof import("solid-js")["createRoot"]
|
|
18
|
+
const createEffect = _createEffect as typeof import("solid-js")["createEffect"]
|
|
19
|
+
const observable = _observable as typeof import("solid-js")["observable"]
|
|
20
|
+
const from = _from as typeof import("solid-js")["from"]
|
|
21
|
+
const batch = _batch as typeof import("solid-js")["batch"]
|
|
22
|
+
const getListener = _getListener as typeof import("solid-js")["getListener"]
|
|
23
|
+
const onCleanup = _onCleanup as typeof import("solid-js")["onCleanup"]
|
|
24
|
+
|
|
25
|
+
export {
|
|
26
|
+
createSignal,
|
|
27
|
+
createMemo,
|
|
28
|
+
createRoot,
|
|
29
|
+
createEffect,
|
|
30
|
+
observable,
|
|
31
|
+
from,
|
|
32
|
+
batch,
|
|
33
|
+
getListener,
|
|
34
|
+
onCleanup,
|
|
35
|
+
DEV,
|
|
36
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
|
|
4
|
+
import { createSignal, getListener, onCleanup, DEV } from "./solid.js"
|
|
5
|
+
|
|
6
|
+
const noop = () => {}
|
|
7
|
+
|
|
8
|
+
const isServer = false
|
|
9
|
+
|
|
10
|
+
// src/index.ts
|
|
11
|
+
var triggerOptions = !isServer && DEV ? { equals: false, name: "trigger" } : { equals: false }
|
|
12
|
+
var triggerCacheOptions = !isServer && DEV ? { equals: false, internal: true } : triggerOptions
|
|
13
|
+
function createTrigger() {
|
|
14
|
+
if (isServer) {
|
|
15
|
+
return [noop, noop]
|
|
16
|
+
}
|
|
17
|
+
return createSignal(void 0, triggerOptions)
|
|
18
|
+
}
|
|
19
|
+
var TriggerCache = class {
|
|
20
|
+
#map
|
|
21
|
+
constructor(mapConstructor = Map) {
|
|
22
|
+
this.#map = new mapConstructor()
|
|
23
|
+
}
|
|
24
|
+
dirty(key) {
|
|
25
|
+
if (isServer) return
|
|
26
|
+
this.#map.get(key)?.$$()
|
|
27
|
+
}
|
|
28
|
+
track(key) {
|
|
29
|
+
if (!getListener()) return
|
|
30
|
+
let trigger = this.#map.get(key)
|
|
31
|
+
if (!trigger) {
|
|
32
|
+
const [$, $$] = createSignal(void 0, triggerCacheOptions)
|
|
33
|
+
this.#map.set(key, (trigger = { $, $$, n: 1 }))
|
|
34
|
+
} else trigger.n++
|
|
35
|
+
onCleanup(() => {
|
|
36
|
+
if (trigger.n-- === 1) queueMicrotask(() => trigger.n === 0 && this.#map.delete(key))
|
|
37
|
+
})
|
|
38
|
+
trigger.$()
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function createTriggerCache(mapConstructor = Map) {
|
|
42
|
+
const map = new TriggerCache(mapConstructor)
|
|
43
|
+
return [map.track.bind(map), map.dirty.bind(map)]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export { TriggerCache, createTrigger, createTriggerCache }
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export * from "./plugins/errors.js"
|
|
2
|
+
export * from "./message-lint-rules/errors.js"
|
|
3
|
+
|
|
4
|
+
export class ModuleError extends Error {
|
|
5
|
+
public readonly Module: string
|
|
6
|
+
constructor(message: string, options: { module: string; cause?: Error }) {
|
|
7
|
+
super(message)
|
|
8
|
+
this.name = "ModuleError"
|
|
9
|
+
this.Module = options.module
|
|
10
|
+
this.cause = options.cause
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Error when a Module does not export any plugins or lint rules.
|
|
16
|
+
*/
|
|
17
|
+
export class ModuleHasNoExportsError extends ModuleError {
|
|
18
|
+
constructor(message: string, options: { module: string; cause?: Error }) {
|
|
19
|
+
super(message, options)
|
|
20
|
+
this.name = "ModuleHasNoExportsError"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Error when a Module cannot be imported.
|
|
26
|
+
*/
|
|
27
|
+
export class ModuleImportError extends ModuleError {
|
|
28
|
+
constructor(message: string, options: { module: string; cause: Error }) {
|
|
29
|
+
super(message, options)
|
|
30
|
+
this.name = "ModuleImportError"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class ModuleExportIsInvalidError extends ModuleError {
|
|
35
|
+
constructor(message: string, options: { module: string; cause?: Error }) {
|
|
36
|
+
super(message, options)
|
|
37
|
+
this.name = "ModuleExportIsInvalidError"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { createNodeishMemoryFs } from "@lix-js/fs"
|
|
4
|
+
import { describe, expect, it } from "vitest"
|
|
5
|
+
import { createImport } from "./import.js"
|
|
6
|
+
|
|
7
|
+
describe("$import", async () => {
|
|
8
|
+
const fs = createNodeishMemoryFs()
|
|
9
|
+
await fs.writeFile(
|
|
10
|
+
"./mock-module.js",
|
|
11
|
+
`
|
|
12
|
+
export function hello() {
|
|
13
|
+
return "hello";
|
|
14
|
+
}
|
|
15
|
+
`,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
// mock module in a directory
|
|
19
|
+
await fs.mkdir("./nested")
|
|
20
|
+
await fs.writeFile(
|
|
21
|
+
"./nested/mock-module-two.js",
|
|
22
|
+
`
|
|
23
|
+
export function hello() {
|
|
24
|
+
return "world";
|
|
25
|
+
}
|
|
26
|
+
`,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
const _import = createImport({
|
|
30
|
+
readFile: fs.readFile,
|
|
31
|
+
fetch,
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it("should import a module from a local path", async () => {
|
|
35
|
+
const module = await _import("./mock-module.js")
|
|
36
|
+
expect(module.hello()).toBe("hello")
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it("should import a module from a nested local path", async () => {
|
|
40
|
+
const module = await _import("./nested/mock-module-two.js")
|
|
41
|
+
expect(module.hello()).toBe("world")
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it("should import an ES module from a url", async () => {
|
|
45
|
+
const module = await _import("https://cdn.jsdelivr.net/npm/normalize-url@7.2.0/index.js")
|
|
46
|
+
const normalizeUrl = module.default
|
|
47
|
+
expect(normalizeUrl("inlang.com")).toBe("http://inlang.com")
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it("should throw if a module is loaded that is not an ES module", async () => {
|
|
51
|
+
try {
|
|
52
|
+
await _import("https://cdn.jsdelivr.net/npm/lodash@4.17.21")
|
|
53
|
+
throw "function did not throw"
|
|
54
|
+
} catch {
|
|
55
|
+
expect(true).toBe(true)
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
})
|