@jskit-ai/jskit-cli 0.2.24 → 0.2.26

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.
@@ -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,
@@ -123,6 +124,18 @@ function createCommandHandlers(deps) {
123
124
  return sortStrings(dependents);
124
125
  }
125
126
 
127
+ function resolvePackageKind(packageEntry) {
128
+ const descriptor = ensureObject(packageEntry?.descriptor);
129
+ const normalizedKind = String(descriptor.kind || "").trim().toLowerCase();
130
+ if (normalizedKind === "runtime" || normalizedKind === "generator") {
131
+ return normalizedKind;
132
+ }
133
+ const packageId = String(packageEntry?.packageId || descriptor.packageId || "unknown-package").trim();
134
+ throw createCliError(
135
+ `Invalid package descriptor for ${packageId}: missing/invalid kind (expected runtime or generator).`
136
+ );
137
+ }
138
+
126
139
  function resolvePackageOptionNames(packageEntry) {
127
140
  const optionSchemas = ensureObject(packageEntry?.descriptor?.options);
128
141
  return Object.keys(optionSchemas);
@@ -176,6 +189,155 @@ function createCommandHandlers(deps) {
176
189
  : " This bundle does not accept inline options.";
177
190
  throw createCliError(`Unknown option(s) for bundle ${bundleId}: ${sortedUnknown.join(", ")}.${suffix}`);
178
191
  }
192
+
193
+ function collectDescriptorContainerTokens({ packageId, side, values, issues }) {
194
+ const declaredTokens = new Set();
195
+ const duplicateTokens = new Set();
196
+ let invalidCount = 0;
197
+
198
+ for (const rawValue of ensureArray(values)) {
199
+ if (typeof rawValue !== "string") {
200
+ invalidCount += 1;
201
+ continue;
202
+ }
203
+ const token = rawValue.trim();
204
+ if (!token) {
205
+ invalidCount += 1;
206
+ continue;
207
+ }
208
+ if (declaredTokens.has(token)) {
209
+ duplicateTokens.add(token);
210
+ continue;
211
+ }
212
+ declaredTokens.add(token);
213
+ }
214
+
215
+ if (invalidCount > 0) {
216
+ issues.push({
217
+ packageId,
218
+ side,
219
+ code: "descriptor-token-invalid",
220
+ message: `${packageId} (${side}): metadata.apiSummary.containerTokens includes ${invalidCount} non-string or empty token value(s).`
221
+ });
222
+ }
223
+ for (const token of sortStrings([...duplicateTokens])) {
224
+ issues.push({
225
+ packageId,
226
+ side,
227
+ code: "descriptor-token-duplicate",
228
+ token,
229
+ message: `${packageId} (${side}): descriptor token is declared more than once: ${token}.`
230
+ });
231
+ }
232
+
233
+ return declaredTokens;
234
+ }
235
+
236
+ function collectUsedContainerTokens({ packageId, side, bindings, issues }) {
237
+ const usedTokens = new Set();
238
+ for (const rawBinding of ensureArray(bindings)) {
239
+ const binding = ensureObject(rawBinding);
240
+ const tokenExpression = String(binding.tokenExpression || "").trim();
241
+ const token = String(binding.token || "").trim();
242
+ const location = String(binding.location || "").trim();
243
+ if (binding.tokenResolved !== true || !token) {
244
+ const expressionLabel = tokenExpression || "<empty>";
245
+ const locationSuffix = location ? ` at ${location}` : "";
246
+ issues.push({
247
+ packageId,
248
+ side,
249
+ code: "binding-token-unresolved",
250
+ tokenExpression: expressionLabel,
251
+ location,
252
+ message: `${packageId} (${side}): unresolved DI token expression "${expressionLabel}"${locationSuffix}.`
253
+ });
254
+ continue;
255
+ }
256
+ usedTokens.add(token);
257
+ }
258
+ return usedTokens;
259
+ }
260
+
261
+ function collectProviderIntrospectionIssues({ packageId, packageInsights, issues }) {
262
+ const introspection = ensureObject(packageInsights);
263
+ if (!introspection.available) {
264
+ issues.push({
265
+ packageId,
266
+ side: "",
267
+ code: "provider-introspection-unavailable",
268
+ message: `${packageId}: provider source introspection is unavailable, so DI token parity cannot be verified.`
269
+ });
270
+ return;
271
+ }
272
+
273
+ const notes = ensureArray(introspection.notes).map((value) => String(value || "").trim()).filter(Boolean);
274
+ for (const note of notes) {
275
+ if (
276
+ note.startsWith("Skipped wildcard provider entrypoint during introspection:") ||
277
+ note.startsWith("Provider file missing during introspection:") ||
278
+ note.startsWith("Failed reading provider ")
279
+ ) {
280
+ issues.push({
281
+ packageId,
282
+ side: "",
283
+ code: "provider-introspection-incomplete",
284
+ message: `${packageId}: ${note}`
285
+ });
286
+ }
287
+ }
288
+ }
289
+
290
+ function collectDiLabelParityIssuesForPackage({ packageEntry, packageInsights }) {
291
+ const packageId = String(packageEntry?.packageId || "").trim();
292
+ const descriptor = ensureObject(packageEntry?.descriptor);
293
+ const metadataApiSummary = ensureObject(ensureObject(descriptor.metadata).apiSummary);
294
+ const descriptorTokenSummary = ensureObject(metadataApiSummary.containerTokens);
295
+ const bindingSections = ensureObject(ensureObject(packageInsights).containerBindings);
296
+ const issues = [];
297
+ const sides = ["server", "client"];
298
+
299
+ collectProviderIntrospectionIssues({ packageId, packageInsights, issues });
300
+
301
+ for (const side of sides) {
302
+ const declaredTokens = collectDescriptorContainerTokens({
303
+ packageId,
304
+ side,
305
+ values: descriptorTokenSummary[side],
306
+ issues
307
+ });
308
+ const usedTokens = collectUsedContainerTokens({
309
+ packageId,
310
+ side,
311
+ bindings: bindingSections[side],
312
+ issues
313
+ });
314
+
315
+ for (const token of sortStrings([...usedTokens])) {
316
+ if (!declaredTokens.has(token)) {
317
+ issues.push({
318
+ packageId,
319
+ side,
320
+ code: "binding-token-undeclared",
321
+ token,
322
+ message: `${packageId} (${side}): token is used by providers but missing from metadata.apiSummary.containerTokens.${side}: ${token}.`
323
+ });
324
+ }
325
+ }
326
+ for (const token of sortStrings([...declaredTokens])) {
327
+ if (!usedTokens.has(token)) {
328
+ issues.push({
329
+ packageId,
330
+ side,
331
+ code: "descriptor-token-unused",
332
+ token,
333
+ message: `${packageId} (${side}): token is declared in metadata.apiSummary.containerTokens.${side} but never bound by providers: ${token}.`
334
+ });
335
+ }
336
+ }
337
+ }
338
+
339
+ return issues;
340
+ }
179
341
 
180
342
  async function commandList({ positional, options, cwd, stdout }) {
181
343
  const packageRegistry = await loadPackageRegistry();
@@ -206,8 +368,9 @@ function createCommandHandlers(deps) {
206
368
  const mode = String(positional[0] || "").trim();
207
369
  const shouldListBundles = !mode || mode === "bundles";
208
370
  const shouldListPackages = !mode || mode === "packages";
209
-
210
- if (!shouldListBundles && !shouldListPackages) {
371
+ const shouldListGenerators = !mode || mode === "generators";
372
+
373
+ if (!shouldListBundles && !shouldListPackages && !shouldListGenerators) {
211
374
  throw createCliError(`Unknown list mode: ${mode}`, { showUsage: true });
212
375
  }
213
376
 
@@ -237,8 +400,11 @@ function createCommandHandlers(deps) {
237
400
  if (lines.length > 0) {
238
401
  lines.push("");
239
402
  }
240
- lines.push(color.heading("Available packages:"));
241
- const packageIds = sortStrings([...packageRegistry.keys()]);
403
+ lines.push(color.heading("Available runtime packages:"));
404
+ const packageIds = sortStrings([...packageRegistry.keys()].filter((packageId) => {
405
+ const packageEntry = packageRegistry.get(packageId);
406
+ return resolvePackageKind(packageEntry) === "runtime";
407
+ }));
242
408
  for (const packageId of packageIds) {
243
409
  const packageEntry = packageRegistry.get(packageId);
244
410
  const installedLabel = installedPackages.has(packageId) ? " (installed)" : "";
@@ -280,6 +446,24 @@ function createCommandHandlers(deps) {
280
446
  }
281
447
  }
282
448
  }
449
+
450
+ if (shouldListGenerators) {
451
+ if (lines.length > 0) {
452
+ lines.push("");
453
+ }
454
+ lines.push(color.heading("Available generators:"));
455
+ const packageIds = sortStrings([...packageRegistry.keys()].filter((packageId) => {
456
+ const packageEntry = packageRegistry.get(packageId);
457
+ return resolvePackageKind(packageEntry) === "generator";
458
+ }));
459
+ for (const packageId of packageIds) {
460
+ const packageEntry = packageRegistry.get(packageId);
461
+ const installedLabel = installedPackages.has(packageId) ? " (installed)" : "";
462
+ lines.push(
463
+ `- ${color.item(packageId)} ${color.version(`(${packageEntry.version})`)}${installedLabel ? color.installed(installedLabel) : ""}`
464
+ );
465
+ }
466
+ }
283
467
 
284
468
  if (options.json) {
285
469
  const payload = {
@@ -298,14 +482,42 @@ function createCommandHandlers(deps) {
298
482
  })
299
483
  : [],
300
484
  packages: shouldListPackages
485
+ ? sortStrings([...packageRegistry.keys()])
486
+ .filter((packageId) => resolvePackageKind(packageRegistry.get(packageId)) === "runtime")
487
+ .map((packageId) => {
488
+ const packageEntry = packageRegistry.get(packageId);
489
+ return {
490
+ packageId,
491
+ version: packageEntry.version,
492
+ installed: installedPackages.has(packageId)
493
+ };
494
+ })
495
+ : [],
496
+ runtimePackages: shouldListPackages
301
497
  ? sortStrings([...packageRegistry.keys()]).map((packageId) => {
302
498
  const packageEntry = packageRegistry.get(packageId);
499
+ if (resolvePackageKind(packageEntry) !== "runtime") {
500
+ return null;
501
+ }
303
502
  return {
304
503
  packageId,
305
504
  version: packageEntry.version,
306
505
  installed: installedPackages.has(packageId)
307
506
  };
308
- })
507
+ }).filter(Boolean)
508
+ : [],
509
+ generators: shouldListGenerators
510
+ ? sortStrings([...packageRegistry.keys()]).map((packageId) => {
511
+ const packageEntry = packageRegistry.get(packageId);
512
+ if (resolvePackageKind(packageEntry) !== "generator") {
513
+ return null;
514
+ }
515
+ return {
516
+ packageId,
517
+ version: packageEntry.version,
518
+ installed: installedPackages.has(packageId)
519
+ };
520
+ }).filter(Boolean)
309
521
  : [],
310
522
  installedLocalPackages: shouldListPackages
311
523
  ? installedLocalPackageIds.map((packageId) => {
@@ -1102,10 +1314,16 @@ function createCommandHandlers(deps) {
1102
1314
  }
1103
1315
 
1104
1316
  async function commandAdd({ positional, options, cwd, io }) {
1317
+ const invocationMode = options?.commandMode === "generate" ? "generate" : "add";
1105
1318
  const targetType = String(positional[0] || "").trim();
1106
1319
  const targetId = String(positional[1] || "").trim();
1107
1320
 
1108
1321
  if (!targetType || !targetId) {
1322
+ if (invocationMode === "generate") {
1323
+ throw createCliError("generate requires a package id (generate <packageId>).", {
1324
+ showUsage: true
1325
+ });
1326
+ }
1109
1327
  throw createCliError("add requires target type and id (add bundle <id> | add package <id>).", {
1110
1328
  showUsage: true
1111
1329
  });
@@ -1113,6 +1331,11 @@ function createCommandHandlers(deps) {
1113
1331
  if (targetType !== "bundle" && targetType !== "package") {
1114
1332
  throw createCliError(`Unsupported add target type: ${targetType}`, { showUsage: true });
1115
1333
  }
1334
+ if (invocationMode === "generate" && targetType !== "package") {
1335
+ throw createCliError("generate requires a package id (generate <packageId>).", {
1336
+ showUsage: true
1337
+ });
1338
+ }
1116
1339
 
1117
1340
  const appRoot = await resolveAppRootFromCwd(cwd);
1118
1341
  const packageRegistry = await loadPackageRegistry();
@@ -1122,10 +1345,11 @@ function createCommandHandlers(deps) {
1122
1345
  const { packageJsonPath, packageJson } = await loadAppPackageJson(appRoot);
1123
1346
  const { lockPath, lock } = await loadLockFile(appRoot);
1124
1347
  let resolvedTargetPackageId = targetType === "package" ? resolvePackageIdInput(targetId, combinedPackageRegistry) : "";
1125
- if (targetType === "package" && !resolvedTargetPackageId) {
1348
+ if (targetType === "package") {
1349
+ const packageIdForNodeModulesLookup = resolvedTargetPackageId || targetId;
1126
1350
  const installedNodeModuleEntry = await resolveInstalledNodeModulePackageEntry({
1127
1351
  appRoot,
1128
- packageId: targetId
1352
+ packageId: packageIdForNodeModulesLookup
1129
1353
  });
1130
1354
  if (installedNodeModuleEntry) {
1131
1355
  combinedPackageRegistry.set(installedNodeModuleEntry.packageId, installedNodeModuleEntry);
@@ -1156,6 +1380,17 @@ function createCommandHandlers(deps) {
1156
1380
  if (!targetPackageEntry) {
1157
1381
  throw createCliError(`Unknown package: ${targetId}`);
1158
1382
  }
1383
+ const packageKind = resolvePackageKind(targetPackageEntry);
1384
+ if (invocationMode === "add" && packageKind === "generator") {
1385
+ throw createCliError(
1386
+ `Package ${resolvedTargetPackageId} is a generator. Use: jskit generate ${resolvedTargetPackageId}`
1387
+ );
1388
+ }
1389
+ if (invocationMode === "generate" && packageKind !== "generator") {
1390
+ throw createCliError(
1391
+ `Package ${resolvedTargetPackageId} is a runtime package. Use: jskit add package ${resolvedTargetPackageId}`
1392
+ );
1393
+ }
1159
1394
  validateInlineOptionsForPackage(targetPackageEntry, options.inlineOptions);
1160
1395
  }
1161
1396
 
@@ -1163,6 +1398,17 @@ function createCommandHandlers(deps) {
1163
1398
  targetPackageIds,
1164
1399
  combinedPackageRegistry
1165
1400
  );
1401
+ if (invocationMode === "add" && targetType === "bundle") {
1402
+ const bundledGenerators = resolvedPackageIds.filter((packageId) => {
1403
+ const packageEntry = combinedPackageRegistry.get(packageId);
1404
+ return resolvePackageKind(packageEntry) === "generator";
1405
+ });
1406
+ if (bundledGenerators.length > 0) {
1407
+ throw createCliError(
1408
+ `Bundle ${targetId} includes generator package(s): ${bundledGenerators.join(", ")}. Use: jskit generate <packageId>`
1409
+ );
1410
+ }
1411
+ }
1166
1412
  const plannedInstalledPackageIds = sortStrings([
1167
1413
  ...new Set([
1168
1414
  ...Object.keys(ensureObject(lock.installedPackages)).map((value) => String(value || "").trim()).filter(Boolean),
@@ -1172,7 +1418,7 @@ function createCommandHandlers(deps) {
1172
1418
  validatePlannedCapabilityClosure(
1173
1419
  plannedInstalledPackageIds,
1174
1420
  combinedPackageRegistry,
1175
- `add ${targetType} ${targetId}`
1421
+ `${invocationMode} ${targetType} ${targetId}`
1176
1422
  );
1177
1423
 
1178
1424
  if (targetType === "bundle") {
@@ -1251,14 +1497,18 @@ function createCommandHandlers(deps) {
1251
1497
  validatePlannedCapabilityClosure(
1252
1498
  postInstallPackageIds,
1253
1499
  combinedPackageRegistry,
1254
- `add ${targetType} ${targetId}`
1500
+ `${invocationMode} ${targetType} ${targetId}`
1255
1501
  );
1256
1502
  }
1257
1503
 
1258
1504
  const finalResolvedPackageIds = sortStrings([...resolvedPackageIds, ...adoptedPackageIds]);
1259
1505
 
1260
1506
  const touchedFileList = sortStrings([...touchedFiles]);
1261
- const successLabel = targetType === "bundle" ? "Added bundle" : "Added package";
1507
+ const successLabel = invocationMode === "generate"
1508
+ ? "Generated with"
1509
+ : targetType === "bundle"
1510
+ ? "Added bundle"
1511
+ : "Added package";
1262
1512
  const installWarnings = installedPackageRecords
1263
1513
  .flatMap((record) => ensureArray(ensureObject(record).warnings))
1264
1514
  .map((value) => String(value || "").trim())
@@ -1274,7 +1524,7 @@ function createCommandHandlers(deps) {
1274
1524
 
1275
1525
  if (options.json) {
1276
1526
  io.stdout.write(`${JSON.stringify({
1277
- targetType,
1527
+ targetType: invocationMode === "generate" ? "generator" : targetType,
1278
1528
  targetId,
1279
1529
  resolvedPackages: finalResolvedPackageIds,
1280
1530
  touchedFiles: touchedFileList,
@@ -1309,6 +1559,32 @@ function createCommandHandlers(deps) {
1309
1559
 
1310
1560
  return 0;
1311
1561
  }
1562
+
1563
+ async function commandGenerate({ positional, options, cwd, io }) {
1564
+ const firstToken = String(positional[0] || "").trim();
1565
+ const secondToken = String(positional[1] || "").trim();
1566
+ if (firstToken === "bundle") {
1567
+ throw createCliError("generate supports packages only (generate <packageId>).", {
1568
+ showUsage: true
1569
+ });
1570
+ }
1571
+ const targetId = firstToken === "package" ? secondToken : firstToken;
1572
+ if (!targetId) {
1573
+ throw createCliError("generate requires a package id (generate <packageId>).", {
1574
+ showUsage: true
1575
+ });
1576
+ }
1577
+
1578
+ return commandAdd({
1579
+ positional: ["package", targetId],
1580
+ options: {
1581
+ ...options,
1582
+ commandMode: "generate"
1583
+ },
1584
+ cwd,
1585
+ io
1586
+ });
1587
+ }
1312
1588
 
1313
1589
  async function commandUpdate({ positional, options, cwd, io }) {
1314
1590
  const targetType = String(positional[0] || "").trim();
@@ -1336,6 +1612,138 @@ function createCommandHandlers(deps) {
1336
1612
  });
1337
1613
  }
1338
1614
 
1615
+ async function commandMigrations({ positional, options, cwd, io }) {
1616
+ const scope = String(positional[0] || "").trim().toLowerCase();
1617
+ const targetId = String(positional[1] || "").trim();
1618
+ if (!scope || (scope !== "all" && scope !== "changed" && scope !== "package")) {
1619
+ throw createCliError("migrations requires: migrations <all|changed|package <packageId>>", {
1620
+ showUsage: true
1621
+ });
1622
+ }
1623
+ if (scope === "package" && !targetId) {
1624
+ throw createCliError("migrations package requires a package id.", {
1625
+ showUsage: true
1626
+ });
1627
+ }
1628
+ if (scope !== "package" && Object.keys(ensureObject(options.inlineOptions)).length > 0) {
1629
+ throw createCliError("Inline options are only supported with: migrations package <packageId>.");
1630
+ }
1631
+
1632
+ const appRoot = await resolveAppRootFromCwd(cwd);
1633
+ const packageRegistry = await loadPackageRegistry();
1634
+ const appLocalRegistry = await loadAppLocalPackageRegistry(appRoot);
1635
+ const combinedPackageRegistry = mergePackageRegistries(packageRegistry, appLocalRegistry);
1636
+ const { lockPath, lock } = await loadLockFile(appRoot);
1637
+ const installedPackages = ensureObject(lock.installedPackages);
1638
+ const installedPackageIds = sortStrings(Object.keys(installedPackages));
1639
+ await hydratePackageRegistryFromInstalledNodeModules({
1640
+ appRoot,
1641
+ packageRegistry: combinedPackageRegistry,
1642
+ seedPackageIds: installedPackageIds
1643
+ });
1644
+
1645
+ let requestedPackageIds = [];
1646
+ if (scope === "all") {
1647
+ requestedPackageIds = installedPackageIds;
1648
+ } else if (scope === "package") {
1649
+ const resolvedTargetId = resolveInstalledPackageIdInput(targetId, installedPackages);
1650
+ if (!resolvedTargetId) {
1651
+ throw createCliError(`Package is not installed: ${targetId}`);
1652
+ }
1653
+ requestedPackageIds = [resolvedTargetId];
1654
+ } else {
1655
+ requestedPackageIds = installedPackageIds.filter((packageId) => {
1656
+ const packageEntry = combinedPackageRegistry.get(packageId);
1657
+ if (!packageEntry) {
1658
+ return true;
1659
+ }
1660
+ const lockEntry = ensureObject(installedPackages[packageId]);
1661
+ const migrationSyncVersion = String(lockEntry.migrationSyncVersion || "").trim();
1662
+ return migrationSyncVersion !== String(packageEntry.version || "").trim();
1663
+ });
1664
+ }
1665
+
1666
+ const touchedFiles = new Set();
1667
+ const migratedRecords = [];
1668
+ const migrationWarnings = [];
1669
+ for (const packageId of requestedPackageIds) {
1670
+ const packageEntry = combinedPackageRegistry.get(packageId);
1671
+ if (!packageEntry) {
1672
+ throw createCliError(
1673
+ `Installed package descriptor not found: ${packageId}. Ensure it is available in catalog, app packages/, or node_modules.`
1674
+ );
1675
+ }
1676
+ validateInlineOptionsForPackage(packageEntry, options.inlineOptions);
1677
+ const installedRecord = ensureObject(installedPackages[packageId]);
1678
+ const mergedInlineOptions = scope === "package" ? ensureObject(options.inlineOptions) : {};
1679
+ const resolvedOptions = await resolvePackageOptions(
1680
+ packageEntry,
1681
+ {
1682
+ ...ensureObject(installedRecord.options),
1683
+ ...mergedInlineOptions
1684
+ },
1685
+ io,
1686
+ { appRoot }
1687
+ );
1688
+
1689
+ const managedRecord = await applyPackageMigrationsOnly({
1690
+ packageEntry,
1691
+ packageOptions: resolvedOptions,
1692
+ appRoot,
1693
+ lock,
1694
+ touchedFiles
1695
+ });
1696
+ migratedRecords.push(managedRecord);
1697
+ for (const warning of ensureArray(ensureObject(managedRecord).warnings)) {
1698
+ const normalizedWarning = String(warning || "").trim();
1699
+ if (!normalizedWarning) {
1700
+ continue;
1701
+ }
1702
+ migrationWarnings.push(normalizedWarning);
1703
+ }
1704
+ }
1705
+
1706
+ const touchedFileList = sortStrings([...touchedFiles]);
1707
+ if (!options.dryRun) {
1708
+ await writeJsonFile(lockPath, lock);
1709
+ }
1710
+
1711
+ if (options.json) {
1712
+ io.stdout.write(`${JSON.stringify({
1713
+ targetType: "migrations",
1714
+ scope,
1715
+ requestedPackages: requestedPackageIds,
1716
+ touchedFiles: touchedFileList,
1717
+ lockPath: normalizeRelativePath(appRoot, lockPath),
1718
+ dryRun: options.dryRun,
1719
+ migrated: migratedRecords,
1720
+ warnings: migrationWarnings
1721
+ }, null, 2)}\n`);
1722
+ } else {
1723
+ io.stdout.write(`Generated migrations (${scope}).\n`);
1724
+ io.stdout.write(`Resolved packages (${requestedPackageIds.length}):\n`);
1725
+ for (const packageId of requestedPackageIds) {
1726
+ io.stdout.write(`- ${packageId}\n`);
1727
+ }
1728
+ io.stdout.write(`Touched files (${touchedFileList.length}):\n`);
1729
+ for (const touchedFile of touchedFileList) {
1730
+ io.stdout.write(`- ${touchedFile}\n`);
1731
+ }
1732
+ io.stdout.write(`Lock file: ${normalizeRelativePath(appRoot, lockPath)}\n`);
1733
+ if (options.verbose && migrationWarnings.length > 0) {
1734
+ io.stdout.write(`Warnings (${migrationWarnings.length}):\n`);
1735
+ for (const warning of migrationWarnings) {
1736
+ io.stdout.write(`- ${warning}\n`);
1737
+ }
1738
+ }
1739
+ if (options.dryRun) {
1740
+ io.stdout.write("Dry run enabled: no files were written.\n");
1741
+ }
1742
+ }
1743
+
1744
+ return 0;
1745
+ }
1746
+
1339
1747
  async function commandPosition({ positional, options, cwd, io }) {
1340
1748
  const targetType = String(positional[0] || "").trim();
1341
1749
  const targetId = String(positional[1] || "").trim();
@@ -1627,19 +2035,58 @@ function createCommandHandlers(deps) {
1627
2035
  async function commandLintDescriptors({ options, stdout }) {
1628
2036
  const packageRegistry = await loadPackageRegistry();
1629
2037
  const bundleRegistry = await loadBundleRegistry();
2038
+ const shouldCheckDiLabels = options.checkDiLabels === true;
2039
+ let diLabelIssues = [];
2040
+ if (shouldCheckDiLabels) {
2041
+ const issues = [];
2042
+ for (const packageId of sortStrings([...packageRegistry.keys()])) {
2043
+ const packageEntry = packageRegistry.get(packageId);
2044
+ if (!packageEntry) {
2045
+ continue;
2046
+ }
2047
+ const packageInsights = await inspectPackageOfferings({ packageEntry });
2048
+ issues.push(...collectDiLabelParityIssuesForPackage({ packageEntry, packageInsights }));
2049
+ }
2050
+ diLabelIssues = issues;
2051
+ }
1630
2052
  const payload = {
1631
2053
  packageCount: packageRegistry.size,
1632
2054
  bundleCount: bundleRegistry.size,
1633
2055
  packages: sortStrings([...packageRegistry.keys()]),
1634
- bundles: sortStrings([...bundleRegistry.keys()])
2056
+ bundles: sortStrings([...bundleRegistry.keys()]),
2057
+ diLabelCheck: shouldCheckDiLabels
2058
+ ? {
2059
+ enabled: true,
2060
+ issueCount: diLabelIssues.length,
2061
+ issues: diLabelIssues
2062
+ }
2063
+ : {
2064
+ enabled: false
2065
+ }
1635
2066
  };
1636
2067
 
1637
2068
  if (options.json) {
1638
2069
  stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
1639
2070
  } else {
1640
- stdout.write(`Descriptor lint passed.\n`);
2071
+ const descriptorStatus = shouldCheckDiLabels && diLabelIssues.length > 0 ? "failed" : "passed";
2072
+ stdout.write(`Descriptor lint ${descriptorStatus}.\n`);
1641
2073
  stdout.write(`Packages: ${payload.packageCount}\n`);
1642
2074
  stdout.write(`Bundles: ${payload.bundleCount}\n`);
2075
+ if (shouldCheckDiLabels) {
2076
+ if (diLabelIssues.length === 0) {
2077
+ stdout.write("DI label parity check passed.\n");
2078
+ } else {
2079
+ stdout.write(`DI label parity check failed (${diLabelIssues.length} issue(s)).\n`);
2080
+ for (const issue of diLabelIssues) {
2081
+ const code = String(issue?.code || "").trim();
2082
+ const codeLabel = code ? `[${code}] ` : "";
2083
+ stdout.write(`- ${codeLabel}${String(issue?.message || "").trim()}\n`);
2084
+ }
2085
+ }
2086
+ }
2087
+ }
2088
+ if (shouldCheckDiLabels && diLabelIssues.length > 0) {
2089
+ return 1;
1643
2090
  }
1644
2091
  return 0;
1645
2092
  }
@@ -1649,6 +2096,8 @@ function createCommandHandlers(deps) {
1649
2096
  commandShow,
1650
2097
  commandCreate,
1651
2098
  commandAdd,
2099
+ commandGenerate,
2100
+ commandMigrations,
1652
2101
  commandPosition,
1653
2102
  commandUpdate,
1654
2103
  commandRemove,