@jskit-ai/jskit-cli 0.2.27 → 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 -4956
  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,381 @@
1
+ import { createHash } from "node:crypto";
2
+ import {
3
+ access,
4
+ constants as fsConstants,
5
+ mkdir,
6
+ readFile,
7
+ readdir,
8
+ writeFile
9
+ } from "node:fs/promises";
10
+ import path from "node:path";
11
+ import { pathToFileURL } from "node:url";
12
+ import { createCliError } from "../shared/cliError.js";
13
+ import {
14
+ ensureArray,
15
+ ensureObject
16
+ } from "../shared/collectionUtils.js";
17
+ import { normalizeFileMutationRecord } from "./mutationWhen.js";
18
+
19
+ const PUBLIC_APP_CONFIG_RELATIVE_PATH = "config/public.js";
20
+ const SERVER_APP_CONFIG_RELATIVE_PATH = "config/server.js";
21
+ const MIGRATION_ID_PATTERN = /^[a-z0-9._-]+$/;
22
+
23
+ function normalizeRelativePosixPath(pathValue) {
24
+ return String(pathValue || "")
25
+ .trim()
26
+ .replace(/\\/g, "/")
27
+ .replace(/^\.\/+/, "")
28
+ .replace(/\/{2,}/g, "/")
29
+ .replace(/\/$/, "");
30
+ }
31
+
32
+ function buildFileWriteGroups(fileMutations, { packageId = "" } = {}) {
33
+ const groups = [];
34
+ const groupsByKey = new Map();
35
+
36
+ for (const mutation of ensureArray(fileMutations)) {
37
+ const normalized = normalizeFileMutationRecord(mutation);
38
+ if (normalized.op === "install-migration") {
39
+ if (!normalized.from || !normalized.id) {
40
+ continue;
41
+ }
42
+ } else if (!normalized.from || (!normalized.to && !normalized.toSurface)) {
43
+ continue;
44
+ }
45
+
46
+ const destinationLabel = normalized.to
47
+ ? normalized.to
48
+ : normalized.toSurfaceRoot
49
+ ? `surface:${normalized.toSurface}.root`
50
+ : `surface:${normalized.toSurface}/${normalized.toSurfacePath}`;
51
+
52
+ const key = normalized.id
53
+ ? `id:${normalized.id}`
54
+ : normalized.category || normalized.reason
55
+ ? `meta:${normalized.category}::${normalized.reason}`
56
+ : `path:${destinationLabel}`;
57
+
58
+ let group = groupsByKey.get(key);
59
+ if (!group) {
60
+ group = {
61
+ id: normalized.id,
62
+ category: normalized.category,
63
+ reason: normalized.reason,
64
+ files: []
65
+ };
66
+ groupsByKey.set(key, group);
67
+ groups.push(group);
68
+ } else {
69
+ if (!group.category && normalized.category) {
70
+ group.category = normalized.category;
71
+ }
72
+ if (!group.reason && normalized.reason) {
73
+ group.reason = normalized.reason;
74
+ }
75
+ }
76
+
77
+ if (normalized.op === "install-migration") {
78
+ const toDir = normalized.toDir || "migrations";
79
+ const extension = normalized.extension || ".cjs";
80
+ group.files.push({
81
+ from: normalized.from,
82
+ to: buildManagedMigrationRelativePathLabel({
83
+ toDir,
84
+ packageId,
85
+ migrationId: normalized.id,
86
+ extension
87
+ })
88
+ });
89
+ continue;
90
+ }
91
+
92
+ group.files.push({
93
+ from: normalized.from,
94
+ to: destinationLabel
95
+ });
96
+ }
97
+
98
+ return groups;
99
+ }
100
+
101
+ function normalizeRelativePath(fromRoot, absolutePath) {
102
+ return path.relative(fromRoot, absolutePath).split(path.sep).join("/");
103
+ }
104
+
105
+ function hashBuffer(buffer) {
106
+ return createHash("sha256").update(buffer).digest("hex");
107
+ }
108
+
109
+ function normalizeMigrationExtension(value = "", fallback = ".cjs") {
110
+ const normalizedFallback = String(fallback || ".cjs").trim() || ".cjs";
111
+ const raw = String(value || "").trim();
112
+ const candidate = raw ? (raw.startsWith(".") ? raw : `.${raw}`) : normalizedFallback;
113
+ if (!/^\.[a-z0-9]+$/i.test(candidate)) {
114
+ throw createCliError(`Invalid install-migration extension: ${candidate}`);
115
+ }
116
+ return candidate.toLowerCase();
117
+ }
118
+
119
+ function normalizeMigrationId(value, packageId) {
120
+ const normalized = String(value || "").trim();
121
+ if (!normalized) {
122
+ throw createCliError(`Invalid install-migration mutation in ${packageId}: \"id\" is required.`);
123
+ }
124
+ if (!MIGRATION_ID_PATTERN.test(normalized)) {
125
+ throw createCliError(
126
+ `Invalid install-migration mutation in ${packageId}: "id" must match ${MIGRATION_ID_PATTERN.source}.`
127
+ );
128
+ }
129
+ return normalized;
130
+ }
131
+
132
+ function resolveAppRelativePathWithinRoot(appRoot, relativePath, contextLabel = "path") {
133
+ const normalized = normalizeRelativePosixPath(String(relativePath || "").trim());
134
+ if (!normalized) {
135
+ throw createCliError(`Invalid ${contextLabel}: path is required.`);
136
+ }
137
+ const segments = normalized.split("/");
138
+ if (segments.some((segment) => !segment || segment === "." || segment === "..")) {
139
+ throw createCliError(`Invalid ${contextLabel}: path must be a safe relative path.`);
140
+ }
141
+
142
+ const appRootAbsolute = path.resolve(appRoot);
143
+ const absolutePath = path.resolve(appRootAbsolute, normalized);
144
+ const relativeFromRoot = path.relative(appRootAbsolute, absolutePath);
145
+ if (
146
+ relativeFromRoot === ".." ||
147
+ relativeFromRoot.startsWith(`..${path.sep}`) ||
148
+ path.isAbsolute(relativeFromRoot)
149
+ ) {
150
+ throw createCliError(`Invalid ${contextLabel}: path must stay within app root.`);
151
+ }
152
+
153
+ return {
154
+ relativePath: normalized,
155
+ absolutePath
156
+ };
157
+ }
158
+
159
+ function normalizeMigrationDirectory(value, packageId) {
160
+ const normalized = normalizeRelativePosixPath(String(value || "").trim() || "migrations");
161
+ if (!normalized) {
162
+ throw createCliError(`Invalid install-migration mutation in ${packageId}: "toDir" cannot be empty.`);
163
+ }
164
+
165
+ const segments = normalized.split("/");
166
+ if (segments.some((segment) => !segment || segment === "." || segment === "..")) {
167
+ throw createCliError(`Invalid install-migration mutation in ${packageId}: "toDir" must be a safe relative path.`);
168
+ }
169
+
170
+ return segments.join("/");
171
+ }
172
+
173
+ function formatMigrationTimestamp(date = new Date()) {
174
+ const source = date instanceof Date && !Number.isNaN(date.getTime()) ? date : new Date();
175
+ const year = String(source.getUTCFullYear()).padStart(4, "0");
176
+ const month = String(source.getUTCMonth() + 1).padStart(2, "0");
177
+ const day = String(source.getUTCDate()).padStart(2, "0");
178
+ const hour = String(source.getUTCHours()).padStart(2, "0");
179
+ const minute = String(source.getUTCMinutes()).padStart(2, "0");
180
+ const second = String(source.getUTCSeconds()).padStart(2, "0");
181
+ return `${year}${month}${day}${hour}${minute}${second}`;
182
+ }
183
+
184
+ function buildManagedMigrationFileName({ packageId = "", migrationId = "", extension = ".cjs", timestamp = "" } = {}) {
185
+ const normalizedMigrationId = normalizeMigrationId(migrationId, packageId);
186
+ const normalizedExtension = normalizeMigrationExtension(extension, ".cjs");
187
+ const normalizedTimestamp = String(timestamp || "").trim();
188
+ if (!/^\d{14}$/.test(normalizedTimestamp)) {
189
+ throw createCliError(
190
+ `Invalid install-migration mutation in ${packageId}: timestamp must be a 14-digit UTC string (YYYYMMDDHHmmss).`
191
+ );
192
+ }
193
+ return `${normalizedTimestamp}_${normalizedMigrationId}${normalizedExtension}`;
194
+ }
195
+
196
+ function buildManagedMigrationRelativePath({ toDir = "migrations", packageId = "", migrationId = "", extension = ".cjs", timestamp = "" } = {}) {
197
+ const normalizedDirectory = normalizeMigrationDirectory(toDir, packageId);
198
+ const fileName = buildManagedMigrationFileName({
199
+ packageId,
200
+ migrationId,
201
+ extension,
202
+ timestamp
203
+ });
204
+ return path.posix.join(normalizedDirectory, fileName);
205
+ }
206
+
207
+ function buildManagedMigrationRelativePathLabel({ toDir = "migrations", migrationId = "", extension = ".cjs" } = {}) {
208
+ const directory = normalizeRelativePosixPath(String(toDir || "").trim() || "migrations") || "migrations";
209
+ const id = String(migrationId || "<migration-id>").trim() || "<migration-id>";
210
+ const rawExtension = String(extension || ".cjs").trim() || ".cjs";
211
+ const ext = rawExtension.startsWith(".") ? rawExtension : `.${rawExtension}`;
212
+ return `${directory}/<timestamp>_${id}${ext}`.replace(/\/{2,}/g, "/");
213
+ }
214
+
215
+ async function findExistingManagedMigrationPathById({
216
+ appRoot,
217
+ toDir = "migrations",
218
+ packageId = "",
219
+ migrationId = "",
220
+ extension = ".cjs"
221
+ } = {}) {
222
+ const normalizedDirectory = normalizeMigrationDirectory(toDir, packageId);
223
+ const resolvedDirectory = resolveAppRelativePathWithinRoot(
224
+ appRoot,
225
+ normalizedDirectory,
226
+ `${packageId} migration directory for ${migrationId}`
227
+ );
228
+ if (!(await fileExists(resolvedDirectory.absolutePath))) {
229
+ return null;
230
+ }
231
+
232
+ const normalizedMigrationId = normalizeMigrationId(migrationId, packageId);
233
+ const normalizedExtension = normalizeMigrationExtension(extension, ".cjs");
234
+ const suffix = `_${normalizedMigrationId}${normalizedExtension}`;
235
+ const entries = await readdir(resolvedDirectory.absolutePath, { withFileTypes: true }).catch(() => []);
236
+ const matches = [];
237
+
238
+ for (const entry of entries) {
239
+ if (!entry.isFile()) {
240
+ continue;
241
+ }
242
+ const fileName = String(entry.name || "").trim();
243
+ if (!fileName.endsWith(suffix)) {
244
+ continue;
245
+ }
246
+ const timestamp = fileName.slice(0, fileName.length - suffix.length);
247
+ if (!/^\d{14}$/.test(timestamp)) {
248
+ continue;
249
+ }
250
+ matches.push({
251
+ relativePath: path.posix.join(resolvedDirectory.relativePath, fileName),
252
+ absolutePath: path.join(resolvedDirectory.absolutePath, fileName)
253
+ });
254
+ }
255
+
256
+ matches.sort((left, right) => left.relativePath.localeCompare(right.relativePath));
257
+ if (matches.length > 1) {
258
+ throw createCliError(
259
+ `${packageId}: found multiple migration files for ${normalizedMigrationId} in ${resolvedDirectory.relativePath}. Keep one file for this migration id.`
260
+ );
261
+ }
262
+ return matches[0] || null;
263
+ }
264
+
265
+ function upsertManagedMigrationRecord(managedMigrations, record) {
266
+ const records = ensureArray(managedMigrations);
267
+ const normalizedId = String(ensureObject(record).id || "").trim();
268
+ if (!normalizedId) {
269
+ return;
270
+ }
271
+
272
+ const nextRecord = {
273
+ ...ensureObject(record),
274
+ id: normalizedId
275
+ };
276
+ const existingIndex = records.findIndex(
277
+ (entry) => String(ensureObject(entry).id || "").trim() === normalizedId
278
+ );
279
+ if (existingIndex >= 0) {
280
+ records[existingIndex] = nextRecord;
281
+ return;
282
+ }
283
+
284
+ records.push(nextRecord);
285
+ }
286
+
287
+ async function fileExists(absolutePath) {
288
+ try {
289
+ await access(absolutePath, fsConstants.F_OK);
290
+ return true;
291
+ } catch {
292
+ return false;
293
+ }
294
+ }
295
+
296
+ async function readJsonFile(absolutePath) {
297
+ const source = await readFile(absolutePath, "utf8");
298
+ return JSON.parse(source);
299
+ }
300
+
301
+ async function writeJsonFile(absolutePath, value) {
302
+ await mkdir(path.dirname(absolutePath), { recursive: true });
303
+ await writeFile(absolutePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
304
+ }
305
+
306
+ async function readFileBufferIfExists(absolutePath) {
307
+ if (!(await fileExists(absolutePath))) {
308
+ return {
309
+ exists: false,
310
+ buffer: Buffer.alloc(0)
311
+ };
312
+ }
313
+
314
+ return {
315
+ exists: true,
316
+ buffer: await readFile(absolutePath)
317
+ };
318
+ }
319
+
320
+ async function loadAppConfigModuleConfig(appRoot, relativePath) {
321
+ const absolutePath = path.join(appRoot, relativePath);
322
+ if (!(await fileExists(absolutePath))) {
323
+ return {};
324
+ }
325
+
326
+ let moduleNamespace = null;
327
+ try {
328
+ moduleNamespace = await import(`${pathToFileURL(absolutePath).href}?t=${Date.now()}_${Math.random()}`);
329
+ } catch (error) {
330
+ throw createCliError(
331
+ `Unable to load ${relativePath}: ${String(error?.message || error || "unknown error")}`
332
+ );
333
+ }
334
+
335
+ const defaultExport = ensureObject(moduleNamespace?.default);
336
+ const fromNamedExport = ensureObject(moduleNamespace?.config);
337
+ const fromDefaultConfig = ensureObject(defaultExport?.config);
338
+
339
+ if (Object.keys(fromNamedExport).length > 0) {
340
+ return fromNamedExport;
341
+ }
342
+ if (Object.keys(fromDefaultConfig).length > 0) {
343
+ return fromDefaultConfig;
344
+ }
345
+ return defaultExport;
346
+ }
347
+
348
+ async function loadMutationWhenConfigContext(appRoot) {
349
+ const publicConfig = await loadAppConfigModuleConfig(appRoot, PUBLIC_APP_CONFIG_RELATIVE_PATH);
350
+ const serverConfig = await loadAppConfigModuleConfig(appRoot, SERVER_APP_CONFIG_RELATIVE_PATH);
351
+ return {
352
+ public: publicConfig,
353
+ server: serverConfig,
354
+ merged: {
355
+ ...publicConfig,
356
+ ...serverConfig
357
+ }
358
+ };
359
+ }
360
+
361
+ export {
362
+ buildFileWriteGroups,
363
+ normalizeRelativePath,
364
+ hashBuffer,
365
+ normalizeMigrationExtension,
366
+ normalizeMigrationId,
367
+ resolveAppRelativePathWithinRoot,
368
+ normalizeMigrationDirectory,
369
+ formatMigrationTimestamp,
370
+ buildManagedMigrationFileName,
371
+ buildManagedMigrationRelativePath,
372
+ buildManagedMigrationRelativePathLabel,
373
+ findExistingManagedMigrationPathById,
374
+ upsertManagedMigrationRecord,
375
+ fileExists,
376
+ readJsonFile,
377
+ writeJsonFile,
378
+ readFileBufferIfExists,
379
+ loadAppConfigModuleConfig,
380
+ loadMutationWhenConfigContext
381
+ };