@jskit-ai/jskit-cli 0.2.26 → 0.2.28

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 (59) hide show
  1. package/package.json +3 -2
  2. package/src/server/cliRuntime/appState.js +226 -0
  3. package/src/server/cliRuntime/capabilitySupport.js +194 -0
  4. package/src/server/cliRuntime/descriptorValidation.js +150 -0
  5. package/src/server/cliRuntime/ioAndMigrations.js +381 -0
  6. package/src/server/cliRuntime/localPackageSupport.js +390 -0
  7. package/src/server/cliRuntime/mutationApplication.js +9 -0
  8. package/src/server/cliRuntime/mutationWhen.js +285 -0
  9. package/src/server/cliRuntime/mutations/fileMutations.js +247 -0
  10. package/src/server/cliRuntime/mutations/installMigrationMutation.js +213 -0
  11. package/src/server/cliRuntime/mutations/mutationPathUtils.js +12 -0
  12. package/src/server/cliRuntime/mutations/surfaceTargets.js +155 -0
  13. package/src/server/cliRuntime/mutations/templateContext.js +171 -0
  14. package/src/server/cliRuntime/mutations/textMutations.js +250 -0
  15. package/src/server/cliRuntime/packageInstallFlow.js +489 -0
  16. package/src/server/cliRuntime/packageIntrospection/exportEntries.js +259 -0
  17. package/src/server/cliRuntime/packageIntrospection/exportedSymbols.js +216 -0
  18. package/src/server/cliRuntime/packageIntrospection/placementNormalization.js +98 -0
  19. package/src/server/cliRuntime/packageIntrospection/providerBindingIntrospection.js +377 -0
  20. package/src/server/cliRuntime/packageIntrospection.js +137 -0
  21. package/src/server/cliRuntime/packageOptions.js +299 -0
  22. package/src/server/cliRuntime/packageRegistries.js +343 -0
  23. package/src/server/cliRuntime/packageTemplateResolution.js +131 -0
  24. package/src/server/cliRuntime/viteProxy.js +356 -0
  25. package/src/server/commandHandlers/health.js +292 -0
  26. package/src/server/commandHandlers/list.js +292 -0
  27. package/src/server/commandHandlers/package.js +23 -0
  28. package/src/server/commandHandlers/packageCommands/add.js +282 -0
  29. package/src/server/commandHandlers/packageCommands/create.js +155 -0
  30. package/src/server/commandHandlers/packageCommands/generate.js +116 -0
  31. package/src/server/commandHandlers/packageCommands/migrations.js +155 -0
  32. package/src/server/commandHandlers/packageCommands/position.js +103 -0
  33. package/src/server/commandHandlers/packageCommands/remove.js +181 -0
  34. package/src/server/commandHandlers/packageCommands/update.js +40 -0
  35. package/src/server/commandHandlers/shared.js +314 -0
  36. package/src/server/commandHandlers/show/payloads.js +92 -0
  37. package/src/server/commandHandlers/show/renderBundleText.js +16 -0
  38. package/src/server/commandHandlers/show/renderHelpers.js +82 -0
  39. package/src/server/commandHandlers/show/renderPackageCapabilities.js +124 -0
  40. package/src/server/commandHandlers/show/renderPackageExports.js +203 -0
  41. package/src/server/commandHandlers/show/renderPackageText.js +332 -0
  42. package/src/server/commandHandlers/show.js +114 -0
  43. package/src/server/core/argParser.js +144 -0
  44. package/src/server/{runtimeDeps.js → core/buildCommandDeps.js} +2 -1
  45. package/src/server/core/commandCatalog.js +47 -0
  46. package/src/server/core/createCliRunner.js +150 -0
  47. package/src/server/core/createCommandHandlers.js +43 -0
  48. package/src/server/{runCli.js → core/dispatchCli.js} +14 -1
  49. package/src/server/core/usageHelp.js +344 -0
  50. package/src/server/index.js +1 -1
  51. package/src/server/{optionInterpolation.js → shared/optionInterpolation.js} +12 -1
  52. package/src/server/{pathResolution.js → shared/pathResolution.js} +1 -1
  53. package/src/server/argParser.js +0 -206
  54. package/src/server/cliRuntime.js +0 -4853
  55. package/src/server/commandHandlers.js +0 -2109
  56. /package/src/server/{cliError.js → shared/cliError.js} +0 -0
  57. /package/src/server/{collectionUtils.js → shared/collectionUtils.js} +0 -0
  58. /package/src/server/{outputFormatting.js → shared/outputFormatting.js} +0 -0
  59. /package/src/server/{packageIdHelpers.js → shared/packageIdHelpers.js} +0 -0
@@ -0,0 +1,131 @@
1
+ import { readdir } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { createCliError } from "../shared/cliError.js";
4
+ import { CLI_PACKAGE_ROOT } from "../shared/pathResolution.js";
5
+ import {
6
+ fileExists,
7
+ readJsonFile
8
+ } from "./ioAndMigrations.js";
9
+
10
+ const LOCAL_WORKSPACE_PACKAGE_ROOTS = new Map();
11
+ let LOCAL_WORKSPACE_PACKAGE_ID_INDEX = null;
12
+
13
+ async function resolvePackageRootFromNodeModules({ appRoot, packageId }) {
14
+ const normalizedAppRoot = path.resolve(String(appRoot || "").trim());
15
+ const normalizedPackageId = String(packageId || "").trim();
16
+ if (!normalizedAppRoot || !normalizedPackageId) {
17
+ return "";
18
+ }
19
+
20
+ const candidateRoot = path.resolve(normalizedAppRoot, "node_modules", ...normalizedPackageId.split("/"));
21
+ const candidateDescriptorPath = path.join(candidateRoot, "package.descriptor.mjs");
22
+ if (!(await fileExists(candidateDescriptorPath))) {
23
+ return "";
24
+ }
25
+
26
+ return candidateRoot;
27
+ }
28
+
29
+ async function loadLocalWorkspacePackageIdIndex() {
30
+ if (LOCAL_WORKSPACE_PACKAGE_ID_INDEX instanceof Map) {
31
+ return LOCAL_WORKSPACE_PACKAGE_ID_INDEX;
32
+ }
33
+
34
+ const repoRoot = path.resolve(CLI_PACKAGE_ROOT, "..", "..");
35
+ const parentDirectories = [
36
+ path.join(repoRoot, "packages"),
37
+ path.join(repoRoot, "tooling")
38
+ ];
39
+ const packageIdIndex = new Map();
40
+
41
+ for (const parentDirectory of parentDirectories) {
42
+ if (!(await fileExists(parentDirectory))) {
43
+ continue;
44
+ }
45
+
46
+ const entries = await readdir(parentDirectory, { withFileTypes: true });
47
+ for (const entry of entries) {
48
+ if (!entry.isDirectory() || entry.name.startsWith(".")) {
49
+ continue;
50
+ }
51
+
52
+ const candidateRoot = path.join(parentDirectory, entry.name);
53
+ const packageJsonPath = path.join(candidateRoot, "package.json");
54
+ const descriptorPath = path.join(candidateRoot, "package.descriptor.mjs");
55
+ if (!(await fileExists(packageJsonPath)) || !(await fileExists(descriptorPath))) {
56
+ continue;
57
+ }
58
+
59
+ let packageJson = {};
60
+ try {
61
+ packageJson = await readJsonFile(packageJsonPath);
62
+ } catch {
63
+ continue;
64
+ }
65
+
66
+ const packageId = String(packageJson?.name || "").trim();
67
+ if (!packageId.startsWith("@jskit-ai/")) {
68
+ continue;
69
+ }
70
+ if (packageIdIndex.has(packageId)) {
71
+ continue;
72
+ }
73
+ packageIdIndex.set(packageId, candidateRoot);
74
+ }
75
+ }
76
+
77
+ LOCAL_WORKSPACE_PACKAGE_ID_INDEX = packageIdIndex;
78
+ return packageIdIndex;
79
+ }
80
+
81
+ async function resolvePackageRootFromLocalWorkspace({ packageId }) {
82
+ const normalizedPackageId = String(packageId || "").trim();
83
+ if (!normalizedPackageId.startsWith("@jskit-ai/")) {
84
+ return "";
85
+ }
86
+ if (LOCAL_WORKSPACE_PACKAGE_ROOTS.has(normalizedPackageId)) {
87
+ return LOCAL_WORKSPACE_PACKAGE_ROOTS.get(normalizedPackageId);
88
+ }
89
+
90
+ const packageIdIndex = await loadLocalWorkspacePackageIdIndex();
91
+ const packageRoot = String(packageIdIndex.get(normalizedPackageId) || "").trim();
92
+ LOCAL_WORKSPACE_PACKAGE_ROOTS.set(normalizedPackageId, packageRoot);
93
+ return packageRoot;
94
+ }
95
+
96
+ async function resolvePackageTemplateRoot({ packageEntry, appRoot }) {
97
+ const packageRoot = String(packageEntry?.rootDir || "").trim();
98
+ if (packageRoot) {
99
+ return packageRoot;
100
+ }
101
+
102
+ const installedPackageRoot = await resolvePackageRootFromNodeModules({
103
+ appRoot,
104
+ packageId: packageEntry?.packageId
105
+ });
106
+ if (installedPackageRoot) {
107
+ return installedPackageRoot;
108
+ }
109
+
110
+ const localWorkspacePackageRoot = await resolvePackageRootFromLocalWorkspace({
111
+ packageId: packageEntry?.packageId
112
+ });
113
+ if (localWorkspacePackageRoot) {
114
+ return localWorkspacePackageRoot;
115
+ }
116
+
117
+ throw createCliError(
118
+ `Unable to resolve local template source for ${String(packageEntry?.packageId || "unknown package")}. ` +
119
+ "Install it in node_modules or ensure it exists in the local jskit-ai workspace."
120
+ );
121
+ }
122
+
123
+ async function cleanupMaterializedPackageRoots() {
124
+ LOCAL_WORKSPACE_PACKAGE_ROOTS.clear();
125
+ LOCAL_WORKSPACE_PACKAGE_ID_INDEX = null;
126
+ }
127
+
128
+ export {
129
+ cleanupMaterializedPackageRoots,
130
+ resolvePackageTemplateRoot
131
+ };
@@ -0,0 +1,356 @@
1
+ import { rm } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { createCliError } from "../shared/cliError.js";
4
+ import {
5
+ ensureArray,
6
+ ensureObject
7
+ } from "../shared/collectionUtils.js";
8
+ import {
9
+ interpolateOptionValue
10
+ } from "../shared/optionInterpolation.js";
11
+ import {
12
+ fileExists,
13
+ readFileBufferIfExists,
14
+ writeJsonFile,
15
+ normalizeRelativePath,
16
+ loadMutationWhenConfigContext
17
+ } from "./ioAndMigrations.js";
18
+ import {
19
+ normalizeMutationWhen,
20
+ shouldApplyMutationWhen
21
+ } from "./mutationWhen.js";
22
+
23
+ const VITE_DEV_PROXY_CONFIG_RELATIVE_PATH = ".jskit/vite.dev.proxy.json";
24
+ const VITE_DEV_PROXY_CONFIG_VERSION = 1;
25
+
26
+ function createEmptyViteDevProxyConfig() {
27
+ return Object.freeze({
28
+ version: VITE_DEV_PROXY_CONFIG_VERSION,
29
+ entries: Object.freeze([])
30
+ });
31
+ }
32
+
33
+ function normalizeViteDevProxyPath(value = "", { context = "vite proxy entry" } = {}) {
34
+ const normalizedPath = String(value || "").trim();
35
+ if (!normalizedPath || !normalizedPath.startsWith("/")) {
36
+ throw createCliError(`${context} requires "path" starting with "/".`);
37
+ }
38
+ return normalizedPath.replace(/\/{2,}/g, "/");
39
+ }
40
+
41
+ function normalizeViteDevProxyEntry(value = {}, { context = "vite proxy entry" } = {}) {
42
+ const source = ensureObject(value);
43
+ const packageId = String(source.packageId || "").trim();
44
+ const entryId = String(source.id || "").trim();
45
+ if (!packageId) {
46
+ throw createCliError(`${context} requires "packageId".`);
47
+ }
48
+ if (!entryId) {
49
+ throw createCliError(`${context} requires "id".`);
50
+ }
51
+
52
+ const normalized = {
53
+ packageId,
54
+ id: entryId,
55
+ path: normalizeViteDevProxyPath(source.path, {
56
+ context: `${context} (${packageId}:${entryId})`
57
+ })
58
+ };
59
+
60
+ const target = String(source.target || "").trim();
61
+ if (target) {
62
+ normalized.target = target;
63
+ }
64
+ if (Object.prototype.hasOwnProperty.call(source, "changeOrigin")) {
65
+ normalized.changeOrigin = source.changeOrigin === true;
66
+ }
67
+ if (Object.prototype.hasOwnProperty.call(source, "ws")) {
68
+ normalized.ws = source.ws === true;
69
+ }
70
+
71
+ return Object.freeze(normalized);
72
+ }
73
+
74
+ function normalizeViteDevProxyConfig(value = {}, { context = "vite proxy config" } = {}) {
75
+ const source = ensureObject(value);
76
+ const normalizedEntries = [];
77
+ const seenEntryKeys = new Set();
78
+ const seenPaths = new Set();
79
+
80
+ for (const [index, entry] of ensureArray(source.entries).entries()) {
81
+ const normalizedEntry = normalizeViteDevProxyEntry(entry, {
82
+ context: `${context}.entries[${index}]`
83
+ });
84
+ const entryKey = `${normalizedEntry.packageId}::${normalizedEntry.id}`;
85
+ if (seenEntryKeys.has(entryKey)) {
86
+ throw createCliError(`${context} has duplicate entry "${entryKey}".`);
87
+ }
88
+ if (seenPaths.has(normalizedEntry.path)) {
89
+ throw createCliError(`${context} has duplicate path "${normalizedEntry.path}".`);
90
+ }
91
+
92
+ seenEntryKeys.add(entryKey);
93
+ seenPaths.add(normalizedEntry.path);
94
+ normalizedEntries.push(normalizedEntry);
95
+ }
96
+
97
+ normalizedEntries.sort((left, right) => {
98
+ const pathDiff = left.path.localeCompare(right.path);
99
+ if (pathDiff !== 0) {
100
+ return pathDiff;
101
+ }
102
+ const packageDiff = left.packageId.localeCompare(right.packageId);
103
+ if (packageDiff !== 0) {
104
+ return packageDiff;
105
+ }
106
+ return left.id.localeCompare(right.id);
107
+ });
108
+
109
+ return Object.freeze({
110
+ version: VITE_DEV_PROXY_CONFIG_VERSION,
111
+ entries: Object.freeze(normalizedEntries)
112
+ });
113
+ }
114
+
115
+ function resolveViteDevProxyConfigAbsolutePath(appRoot) {
116
+ return path.join(appRoot, VITE_DEV_PROXY_CONFIG_RELATIVE_PATH);
117
+ }
118
+
119
+ async function loadViteDevProxyConfig(appRoot, { context = "vite proxy config" } = {}) {
120
+ const absolutePath = resolveViteDevProxyConfigAbsolutePath(appRoot);
121
+ const existing = await readFileBufferIfExists(absolutePath);
122
+ if (!existing.exists) {
123
+ return Object.freeze({
124
+ absolutePath,
125
+ exists: false,
126
+ config: createEmptyViteDevProxyConfig()
127
+ });
128
+ }
129
+
130
+ const relativePath = normalizeRelativePath(appRoot, absolutePath);
131
+ let parsed = {};
132
+ try {
133
+ parsed = JSON.parse(existing.buffer.toString("utf8"));
134
+ } catch {
135
+ throw createCliError(`Invalid ${context} at ${relativePath}: expected valid JSON.`);
136
+ }
137
+
138
+ return Object.freeze({
139
+ absolutePath,
140
+ exists: true,
141
+ config: normalizeViteDevProxyConfig(parsed, {
142
+ context: `${context} (${relativePath})`
143
+ })
144
+ });
145
+ }
146
+
147
+ async function writeViteDevProxyConfig(appRoot, config = {}, touchedFiles = null) {
148
+ const absolutePath = resolveViteDevProxyConfigAbsolutePath(appRoot);
149
+ const relativePath = normalizeRelativePath(appRoot, absolutePath);
150
+ const normalizedConfig = normalizeViteDevProxyConfig(config);
151
+
152
+ if (normalizedConfig.entries.length < 1) {
153
+ if (await fileExists(absolutePath)) {
154
+ await rm(absolutePath);
155
+ if (touchedFiles && typeof touchedFiles.add === "function") {
156
+ touchedFiles.add(relativePath);
157
+ }
158
+ }
159
+ return;
160
+ }
161
+
162
+ await writeJsonFile(absolutePath, normalizedConfig);
163
+ if (touchedFiles && typeof touchedFiles.add === "function") {
164
+ touchedFiles.add(relativePath);
165
+ }
166
+ }
167
+
168
+ function normalizeViteProxyMutationRecord(value = {}) {
169
+ const source = ensureObject(value);
170
+ const changeOrigin = Object.prototype.hasOwnProperty.call(source, "changeOrigin")
171
+ ? source.changeOrigin === true
172
+ : undefined;
173
+ const ws = Object.prototype.hasOwnProperty.call(source, "ws") ? source.ws === true : undefined;
174
+ return Object.freeze({
175
+ id: String(source.id || "").trim(),
176
+ path: String(source.path || "").trim(),
177
+ target: String(source.target || "").trim(),
178
+ changeOrigin,
179
+ ws,
180
+ when: normalizeMutationWhen(source.when)
181
+ });
182
+ }
183
+
184
+ async function applyViteMutations(packageEntry, appRoot, viteMutations, options, managedVite, touchedFiles) {
185
+ const mutations = ensureArray(ensureObject(viteMutations).proxy).map((entry) => normalizeViteProxyMutationRecord(entry));
186
+ if (mutations.length < 1) {
187
+ return;
188
+ }
189
+
190
+ const { config: currentConfig } = await loadViteDevProxyConfig(appRoot, {
191
+ context: `vite proxy config for ${packageEntry.packageId}`
192
+ });
193
+ const nextEntries = [...currentConfig.entries];
194
+ let changed = false;
195
+
196
+ for (const mutation of mutations) {
197
+ const configContext = mutation.when?.config ? await loadMutationWhenConfigContext(appRoot) : {};
198
+ if (
199
+ !shouldApplyMutationWhen(mutation.when, {
200
+ options,
201
+ configContext,
202
+ packageId: packageEntry.packageId,
203
+ mutationContext: "vite proxy mutation"
204
+ })
205
+ ) {
206
+ continue;
207
+ }
208
+
209
+ const normalizedId = interpolateOptionValue(
210
+ mutation.id,
211
+ options,
212
+ packageEntry.packageId,
213
+ "mutations.vite.proxy.id"
214
+ );
215
+ if (!normalizedId) {
216
+ throw createCliError(`Invalid vite proxy mutation in ${packageEntry.packageId}: "id" is required.`);
217
+ }
218
+
219
+ const normalizedPath = normalizeViteDevProxyPath(
220
+ interpolateOptionValue(
221
+ mutation.path,
222
+ options,
223
+ packageEntry.packageId,
224
+ `mutations.vite.proxy.${normalizedId}.path`
225
+ ),
226
+ {
227
+ context: `Invalid vite proxy mutation in ${packageEntry.packageId} (${normalizedId})`
228
+ }
229
+ );
230
+
231
+ const normalizedTarget = mutation.target
232
+ ? String(
233
+ interpolateOptionValue(
234
+ mutation.target,
235
+ options,
236
+ packageEntry.packageId,
237
+ `mutations.vite.proxy.${normalizedId}.target`
238
+ ) || ""
239
+ ).trim()
240
+ : "";
241
+
242
+ for (let index = nextEntries.length - 1; index >= 0; index -= 1) {
243
+ const entry = nextEntries[index];
244
+ if (entry.packageId === packageEntry.packageId && entry.id === normalizedId) {
245
+ nextEntries.splice(index, 1);
246
+ changed = true;
247
+ }
248
+ }
249
+
250
+ const conflictingEntry = nextEntries.find((entry) => entry.path === normalizedPath);
251
+ if (conflictingEntry) {
252
+ throw createCliError(
253
+ `Invalid vite proxy mutation in ${packageEntry.packageId}: path "${normalizedPath}" conflicts with ${conflictingEntry.packageId} (${conflictingEntry.id}).`
254
+ );
255
+ }
256
+
257
+ nextEntries.push(
258
+ Object.freeze({
259
+ packageId: packageEntry.packageId,
260
+ id: normalizedId,
261
+ path: normalizedPath,
262
+ ...(normalizedTarget ? { target: normalizedTarget } : {}),
263
+ ...(typeof mutation.changeOrigin === "boolean" ? { changeOrigin: mutation.changeOrigin } : {}),
264
+ ...(typeof mutation.ws === "boolean" ? { ws: mutation.ws } : {})
265
+ })
266
+ );
267
+ changed = true;
268
+
269
+ const mutationKey = `${normalizedPath}::${normalizedId}`;
270
+ managedVite[mutationKey] = Object.freeze({
271
+ op: "upsert-vite-proxy",
272
+ id: normalizedId,
273
+ path: normalizedPath
274
+ });
275
+ }
276
+
277
+ if (!changed) {
278
+ return;
279
+ }
280
+
281
+ const nextConfig = normalizeViteDevProxyConfig(
282
+ {
283
+ entries: nextEntries
284
+ },
285
+ {
286
+ context: `vite proxy config for ${packageEntry.packageId}`
287
+ }
288
+ );
289
+
290
+ if (JSON.stringify(currentConfig) === JSON.stringify(nextConfig)) {
291
+ return;
292
+ }
293
+
294
+ await writeViteDevProxyConfig(appRoot, nextConfig, touchedFiles);
295
+ }
296
+
297
+ async function removeManagedViteProxyEntries({ appRoot, packageId, managedViteChanges = {}, touchedFiles = null } = {}) {
298
+ const managedChanges = Object.values(ensureObject(managedViteChanges))
299
+ .map((entry) => ensureObject(entry))
300
+ .filter((entry) => String(entry.op || "").trim() === "upsert-vite-proxy");
301
+ if (managedChanges.length < 1) {
302
+ return;
303
+ }
304
+
305
+ const { exists, config: currentConfig } = await loadViteDevProxyConfig(appRoot, {
306
+ context: `vite proxy config while removing ${packageId}`
307
+ });
308
+ if (!exists) {
309
+ return;
310
+ }
311
+
312
+ let nextEntries = [...currentConfig.entries];
313
+ for (const change of managedChanges) {
314
+ const changeId = String(change.id || "").trim();
315
+ const changePath = String(change.path || "").trim();
316
+ if (!changeId) {
317
+ continue;
318
+ }
319
+ nextEntries = nextEntries.filter((entry) => {
320
+ if (entry.packageId !== packageId || entry.id !== changeId) {
321
+ return true;
322
+ }
323
+ if (changePath && entry.path !== changePath) {
324
+ return true;
325
+ }
326
+ return false;
327
+ });
328
+ }
329
+
330
+ const nextConfig = normalizeViteDevProxyConfig(
331
+ {
332
+ entries: nextEntries
333
+ },
334
+ {
335
+ context: `vite proxy config while removing ${packageId}`
336
+ }
337
+ );
338
+ if (JSON.stringify(currentConfig) === JSON.stringify(nextConfig)) {
339
+ return;
340
+ }
341
+
342
+ await writeViteDevProxyConfig(appRoot, nextConfig, touchedFiles);
343
+ }
344
+
345
+ export {
346
+ createEmptyViteDevProxyConfig,
347
+ normalizeViteDevProxyPath,
348
+ normalizeViteDevProxyEntry,
349
+ normalizeViteDevProxyConfig,
350
+ resolveViteDevProxyConfigAbsolutePath,
351
+ loadViteDevProxyConfig,
352
+ writeViteDevProxyConfig,
353
+ normalizeViteProxyMutationRecord,
354
+ applyViteMutations,
355
+ removeManagedViteProxyEntries
356
+ };