@jskit-ai/jskit-cli 0.2.23 → 0.2.25

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/jskit-cli",
3
- "version": "0.2.23",
3
+ "version": "0.2.25",
4
4
  "description": "Bundle and package orchestration CLI for JSKIT apps.",
5
5
  "type": "module",
6
6
  "files": [
@@ -20,7 +20,7 @@
20
20
  "test": "node --test"
21
21
  },
22
22
  "dependencies": {
23
- "@jskit-ai/jskit-catalog": "0.1.23"
23
+ "@jskit-ai/jskit-catalog": "0.1.25"
24
24
  },
25
25
  "engines": {
26
26
  "node": "20.x"
@@ -4,6 +4,7 @@ const KNOWN_COMMANDS = new Set([
4
4
  "list",
5
5
  "show",
6
6
  "view",
7
+ "migrations",
7
8
  "add",
8
9
  "position",
9
10
  "update",
@@ -165,6 +166,7 @@ function printUsage(stream = process.stderr) {
165
166
  stream.write(" position element <packageId> Re-apply positioning mutations for one installed package\n");
166
167
  stream.write(" show <id> Show details for bundle id or package id\n");
167
168
  stream.write(" view <id> Alias of show <id>\n");
169
+ stream.write(" migrations <scope> Generate managed migrations only (scope: all | changed | package <packageId>)\n");
168
170
  stream.write(" update package <packageId> Re-apply one installed package\n");
169
171
  stream.write(" remove package <packageId> Remove one installed package\n");
170
172
  stream.write(" doctor Validate lockfile + managed files\n");
@@ -4298,6 +4298,79 @@ async function applyPackagePositioning({
4298
4298
  return managedRecord;
4299
4299
  }
4300
4300
 
4301
+ async function applyPackageMigrationsOnly({
4302
+ packageEntry,
4303
+ packageOptions,
4304
+ appRoot,
4305
+ lock,
4306
+ touchedFiles
4307
+ }) {
4308
+ const existingInstall = ensureObject(lock.installedPackages[packageEntry.packageId]);
4309
+ if (Object.keys(existingInstall).length < 1) {
4310
+ throw createCliError(`Package is not installed: ${packageEntry.packageId}`);
4311
+ }
4312
+
4313
+ const existingManaged = ensureObject(existingInstall.managed);
4314
+ const existingPackageJsonManaged = ensureObject(existingManaged.packageJson);
4315
+ const nextManaged = {
4316
+ packageJson: {
4317
+ dependencies: cloneManagedMap(existingPackageJsonManaged.dependencies),
4318
+ devDependencies: cloneManagedMap(existingPackageJsonManaged.devDependencies),
4319
+ scripts: cloneManagedMap(existingPackageJsonManaged.scripts)
4320
+ },
4321
+ text: cloneManagedMap(existingManaged.text),
4322
+ vite: cloneManagedMap(existingManaged.vite),
4323
+ files: cloneManagedArray(existingManaged.files),
4324
+ migrations: cloneManagedArray(existingManaged.migrations)
4325
+ };
4326
+
4327
+ const templateRoot = await resolvePackageTemplateRoot({ packageEntry, appRoot });
4328
+ const packageEntryForMutations =
4329
+ templateRoot === packageEntry.rootDir
4330
+ ? packageEntry
4331
+ : {
4332
+ ...packageEntry,
4333
+ rootDir: templateRoot
4334
+ };
4335
+ const mutations = ensureObject(packageEntry.descriptor.mutations);
4336
+ const migrationFileMutations = ensureArray(mutations.files).filter((mutationValue) => {
4337
+ const normalized = normalizeFileMutationRecord(mutationValue);
4338
+ const operation = String(normalized.op || "copy-file").trim();
4339
+ return operation === "install-migration";
4340
+ });
4341
+ const mutationWarnings = [];
4342
+
4343
+ if (migrationFileMutations.length > 0) {
4344
+ await applyFileMutations(
4345
+ packageEntryForMutations,
4346
+ packageOptions,
4347
+ appRoot,
4348
+ migrationFileMutations,
4349
+ [],
4350
+ nextManaged.migrations,
4351
+ touchedFiles,
4352
+ mutationWarnings
4353
+ );
4354
+ }
4355
+
4356
+ const managedRecord = {
4357
+ ...existingInstall,
4358
+ packageId: packageEntry.packageId,
4359
+ source: resolveManagedSourceRecord(packageEntry, existingInstall),
4360
+ managed: nextManaged,
4361
+ options: {
4362
+ ...ensureObject(packageOptions)
4363
+ },
4364
+ migrationSyncVersion: packageEntry.version,
4365
+ installedAt: String(existingInstall.installedAt || new Date().toISOString())
4366
+ };
4367
+ lock.installedPackages[packageEntry.packageId] = managedRecord;
4368
+ if (mutationWarnings.length > 0) {
4369
+ managedRecord.warnings = mutationWarnings;
4370
+ }
4371
+ return managedRecord;
4372
+ }
4373
+
4301
4374
  async function applyPackageInstall({
4302
4375
  packageEntry,
4303
4376
  packageOptions,
@@ -4462,6 +4535,7 @@ async function applyPackageInstall({
4462
4535
  if (cloneOnlyPackage) {
4463
4536
  delete lock.installedPackages[packageEntry.packageId];
4464
4537
  } else {
4538
+ managedRecord.migrationSyncVersion = packageEntry.version;
4465
4539
  lock.installedPackages[packageEntry.packageId] = managedRecord;
4466
4540
  }
4467
4541
  if (mutationWarnings.length > 0) {
@@ -4522,6 +4596,7 @@ const commandHandlers = createCommandHandlers(
4522
4596
  validatePlannedCapabilityClosure,
4523
4597
  resolvePackageOptions,
4524
4598
  applyPackageInstall,
4599
+ applyPackageMigrationsOnly,
4525
4600
  applyPackagePositioning,
4526
4601
  adoptAppLocalPackageDependencies,
4527
4602
  loadAppPackageJson,
@@ -28,6 +28,7 @@ function createCommandHandlers(deps) {
28
28
  validatePlannedCapabilityClosure,
29
29
  resolvePackageOptions,
30
30
  applyPackageInstall,
31
+ applyPackageMigrationsOnly,
31
32
  applyPackagePositioning,
32
33
  adoptAppLocalPackageDependencies,
33
34
  loadAppPackageJson,
@@ -1336,6 +1337,138 @@ function createCommandHandlers(deps) {
1336
1337
  });
1337
1338
  }
1338
1339
 
1340
+ async function commandMigrations({ positional, options, cwd, io }) {
1341
+ const scope = String(positional[0] || "").trim().toLowerCase();
1342
+ const targetId = String(positional[1] || "").trim();
1343
+ if (!scope || (scope !== "all" && scope !== "changed" && scope !== "package")) {
1344
+ throw createCliError("migrations requires: migrations <all|changed|package <packageId>>", {
1345
+ showUsage: true
1346
+ });
1347
+ }
1348
+ if (scope === "package" && !targetId) {
1349
+ throw createCliError("migrations package requires a package id.", {
1350
+ showUsage: true
1351
+ });
1352
+ }
1353
+ if (scope !== "package" && Object.keys(ensureObject(options.inlineOptions)).length > 0) {
1354
+ throw createCliError("Inline options are only supported with: migrations package <packageId>.");
1355
+ }
1356
+
1357
+ const appRoot = await resolveAppRootFromCwd(cwd);
1358
+ const packageRegistry = await loadPackageRegistry();
1359
+ const appLocalRegistry = await loadAppLocalPackageRegistry(appRoot);
1360
+ const combinedPackageRegistry = mergePackageRegistries(packageRegistry, appLocalRegistry);
1361
+ const { lockPath, lock } = await loadLockFile(appRoot);
1362
+ const installedPackages = ensureObject(lock.installedPackages);
1363
+ const installedPackageIds = sortStrings(Object.keys(installedPackages));
1364
+ await hydratePackageRegistryFromInstalledNodeModules({
1365
+ appRoot,
1366
+ packageRegistry: combinedPackageRegistry,
1367
+ seedPackageIds: installedPackageIds
1368
+ });
1369
+
1370
+ let requestedPackageIds = [];
1371
+ if (scope === "all") {
1372
+ requestedPackageIds = installedPackageIds;
1373
+ } else if (scope === "package") {
1374
+ const resolvedTargetId = resolveInstalledPackageIdInput(targetId, installedPackages);
1375
+ if (!resolvedTargetId) {
1376
+ throw createCliError(`Package is not installed: ${targetId}`);
1377
+ }
1378
+ requestedPackageIds = [resolvedTargetId];
1379
+ } else {
1380
+ requestedPackageIds = installedPackageIds.filter((packageId) => {
1381
+ const packageEntry = combinedPackageRegistry.get(packageId);
1382
+ if (!packageEntry) {
1383
+ return true;
1384
+ }
1385
+ const lockEntry = ensureObject(installedPackages[packageId]);
1386
+ const migrationSyncVersion = String(lockEntry.migrationSyncVersion || "").trim();
1387
+ return migrationSyncVersion !== String(packageEntry.version || "").trim();
1388
+ });
1389
+ }
1390
+
1391
+ const touchedFiles = new Set();
1392
+ const migratedRecords = [];
1393
+ const migrationWarnings = [];
1394
+ for (const packageId of requestedPackageIds) {
1395
+ const packageEntry = combinedPackageRegistry.get(packageId);
1396
+ if (!packageEntry) {
1397
+ throw createCliError(
1398
+ `Installed package descriptor not found: ${packageId}. Ensure it is available in catalog, app packages/, or node_modules.`
1399
+ );
1400
+ }
1401
+ validateInlineOptionsForPackage(packageEntry, options.inlineOptions);
1402
+ const installedRecord = ensureObject(installedPackages[packageId]);
1403
+ const mergedInlineOptions = scope === "package" ? ensureObject(options.inlineOptions) : {};
1404
+ const resolvedOptions = await resolvePackageOptions(
1405
+ packageEntry,
1406
+ {
1407
+ ...ensureObject(installedRecord.options),
1408
+ ...mergedInlineOptions
1409
+ },
1410
+ io,
1411
+ { appRoot }
1412
+ );
1413
+
1414
+ const managedRecord = await applyPackageMigrationsOnly({
1415
+ packageEntry,
1416
+ packageOptions: resolvedOptions,
1417
+ appRoot,
1418
+ lock,
1419
+ touchedFiles
1420
+ });
1421
+ migratedRecords.push(managedRecord);
1422
+ for (const warning of ensureArray(ensureObject(managedRecord).warnings)) {
1423
+ const normalizedWarning = String(warning || "").trim();
1424
+ if (!normalizedWarning) {
1425
+ continue;
1426
+ }
1427
+ migrationWarnings.push(normalizedWarning);
1428
+ }
1429
+ }
1430
+
1431
+ const touchedFileList = sortStrings([...touchedFiles]);
1432
+ if (!options.dryRun) {
1433
+ await writeJsonFile(lockPath, lock);
1434
+ }
1435
+
1436
+ if (options.json) {
1437
+ io.stdout.write(`${JSON.stringify({
1438
+ targetType: "migrations",
1439
+ scope,
1440
+ requestedPackages: requestedPackageIds,
1441
+ touchedFiles: touchedFileList,
1442
+ lockPath: normalizeRelativePath(appRoot, lockPath),
1443
+ dryRun: options.dryRun,
1444
+ migrated: migratedRecords,
1445
+ warnings: migrationWarnings
1446
+ }, null, 2)}\n`);
1447
+ } else {
1448
+ io.stdout.write(`Generated migrations (${scope}).\n`);
1449
+ io.stdout.write(`Resolved packages (${requestedPackageIds.length}):\n`);
1450
+ for (const packageId of requestedPackageIds) {
1451
+ io.stdout.write(`- ${packageId}\n`);
1452
+ }
1453
+ io.stdout.write(`Touched files (${touchedFileList.length}):\n`);
1454
+ for (const touchedFile of touchedFileList) {
1455
+ io.stdout.write(`- ${touchedFile}\n`);
1456
+ }
1457
+ io.stdout.write(`Lock file: ${normalizeRelativePath(appRoot, lockPath)}\n`);
1458
+ if (migrationWarnings.length > 0) {
1459
+ io.stdout.write(`Warnings (${migrationWarnings.length}):\n`);
1460
+ for (const warning of migrationWarnings) {
1461
+ io.stdout.write(`- ${warning}\n`);
1462
+ }
1463
+ }
1464
+ if (options.dryRun) {
1465
+ io.stdout.write("Dry run enabled: no files were written.\n");
1466
+ }
1467
+ }
1468
+
1469
+ return 0;
1470
+ }
1471
+
1339
1472
  async function commandPosition({ positional, options, cwd, io }) {
1340
1473
  const targetType = String(positional[0] || "").trim();
1341
1474
  const targetId = String(positional[1] || "").trim();
@@ -1649,6 +1782,7 @@ function createCommandHandlers(deps) {
1649
1782
  commandShow,
1650
1783
  commandCreate,
1651
1784
  commandAdd,
1785
+ commandMigrations,
1652
1786
  commandPosition,
1653
1787
  commandUpdate,
1654
1788
  commandRemove,
@@ -48,6 +48,14 @@ function createRunCli({
48
48
  if (command === "show") {
49
49
  return await commandHandlers.commandShow({ positional, options, stdout });
50
50
  }
51
+ if (command === "migrations") {
52
+ return await commandHandlers.commandMigrations({
53
+ positional,
54
+ options,
55
+ cwd,
56
+ io: { stdin, stdout, stderr }
57
+ });
58
+ }
51
59
  if (command === "add") {
52
60
  return await commandHandlers.commandAdd({
53
61
  positional,
@@ -21,6 +21,7 @@ function createCommandHandlerDeps(deps = {}) {
21
21
  validatePlannedCapabilityClosure: deps.validatePlannedCapabilityClosure,
22
22
  resolvePackageOptions: deps.resolvePackageOptions,
23
23
  applyPackageInstall: deps.applyPackageInstall,
24
+ applyPackageMigrationsOnly: deps.applyPackageMigrationsOnly,
24
25
  applyPackagePositioning: deps.applyPackagePositioning,
25
26
  adoptAppLocalPackageDependencies: deps.adoptAppLocalPackageDependencies,
26
27
  loadAppPackageJson: deps.loadAppPackageJson,