@celilo/cli 0.3.19 → 0.3.20

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": "@celilo/cli",
3
- "version": "0.3.19",
3
+ "version": "0.3.20",
4
4
  "description": "Celilo — home lab orchestration CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -28,7 +28,16 @@ import { log } from '../prompts';
28
28
  import type { CommandResult } from '../types';
29
29
 
30
30
  type UpgradeOutcome =
31
- | { status: 'success'; moduleId: string }
31
+ | {
32
+ status: 'success';
33
+ moduleId: string;
34
+ /** Version that was on disk before the upgrade (manifest.yml semver). */
35
+ previousVersion: string;
36
+ /** Version that's now installed. Includes +N revision when known
37
+ * (registry-driven upgrades pass the canonical "1.0.0+5" form;
38
+ * path-driven upgrades fall back to the manifest semver). */
39
+ newVersion: string;
40
+ }
32
41
  | { status: 'failed'; moduleId: string; error: string }
33
42
  // `skipped` means the path expanded from a glob but isn't an
34
43
  // upgradable target — either no manifest at all (probably a non-
@@ -37,6 +46,18 @@ type UpgradeOutcome =
37
46
  // `celilo module update modules/*` does what users expect.
38
47
  | { status: 'skipped'; moduleId: string; reason: string };
39
48
 
49
+ /**
50
+ * Tunables for `upgradeOne`. Quiet mode silences the per-call log
51
+ * lines so callers driving a batch (the registry sweep) can render
52
+ * their own structured output without duplicates. `displayVersion`
53
+ * lets the registry caller carry the canonical `+N` revision through
54
+ * to both the DB column and the success log.
55
+ */
56
+ interface UpgradeOpts {
57
+ quiet?: boolean;
58
+ displayVersion?: string;
59
+ }
60
+
40
61
  /**
41
62
  * Parse a celilo version string into [major, minor, patch, revision].
42
63
  * Celilo's published versions look like `1.0.0+3` — semver core plus a
@@ -108,7 +129,18 @@ async function fetchAndUpgrade(
108
129
  try {
109
130
  // Registry packages are pre-verified at publish time; skip the
110
131
  // signature check here to match `module import`'s registry path.
111
- return await upgradeOne(tmpPath, db, { ...flags, 'skip-verify': true });
132
+ // `quiet: true` suppresses upgradeOne's per-call log lines so the
133
+ // sweep can render its own structured per-module output without
134
+ // duplicates. `displayVersion: version` carries the registry's
135
+ // canonical "X.Y.Z+N" through to both the DB column and the success
136
+ // log line — without it, output would say "v1.0.0 → v1.0.0" because
137
+ // the manifest semver doesn't include the +N revision.
138
+ return await upgradeOne(
139
+ tmpPath,
140
+ db,
141
+ { ...flags, 'skip-verify': true },
142
+ { quiet: true, displayVersion: version },
143
+ );
112
144
  } finally {
113
145
  try {
114
146
  await unlink(tmpPath);
@@ -123,6 +155,7 @@ async function upgradeOne(
123
155
  sourcePath: string,
124
156
  db: ReturnType<typeof getDb>,
125
157
  flags: Record<string, string | boolean> = {},
158
+ opts: UpgradeOpts = {},
126
159
  ): Promise<UpgradeOutcome> {
127
160
  const originalCwd = process.env.CELILO_ORIGINAL_CWD || process.cwd();
128
161
  const importPath = resolve(originalCwd, sourcePath);
@@ -162,7 +195,7 @@ async function upgradeOne(
162
195
  error: verifyResult.error || 'Package verification failed',
163
196
  };
164
197
  }
165
- } else {
198
+ } else if (!opts.quiet) {
166
199
  log.warn('Skipping package signature verification (--skip-verify)');
167
200
  }
168
201
  }
@@ -207,8 +240,17 @@ async function upgradeOne(
207
240
  };
208
241
  }
209
242
 
210
- const oldManifest = module.manifestData as ModuleManifest;
211
- log.info(`Upgrading ${moduleId}: v${oldManifest.version} v${newManifest.version}`);
243
+ // Old version comes from the DB so we capture whatever was last
244
+ // recorded (which IS the registry-versioned form, e.g. "1.0.0+5",
245
+ // for registry-driven installs/upgrades).
246
+ const previousVersion = module.version;
247
+ // New version: prefer the caller-supplied display version (registry's
248
+ // canonical "X.Y.Z+N"), fall back to the manifest semver core when
249
+ // upgrading from a local path.
250
+ const newVersion = opts.displayVersion ?? newManifest.version;
251
+ if (!opts.quiet) {
252
+ log.info(`Upgrading ${moduleId}: ${previousVersion} → ${newVersion}`);
253
+ }
212
254
 
213
255
  // Copy new module files, preserving generated output and state
214
256
  const installedPath = module.sourcePath;
@@ -226,11 +268,13 @@ async function upgradeOne(
226
268
  // Clean up temp dir if we extracted a .netapp
227
269
  if (tempDir) await cleanupTempDir(tempDir);
228
270
 
229
- // Update manifest in database
271
+ // Update manifest in database. We persist the display version (with
272
+ // +N when known) so subsequent `module list` / `module update` calls
273
+ // see the same version string the registry reported.
230
274
  db.update(modules)
231
275
  .set({
232
276
  manifestData: newManifest as unknown as Record<string, unknown>,
233
- version: newManifest.version,
277
+ version: newVersion,
234
278
  name: newManifest.name,
235
279
  })
236
280
  .where(eq(modules.id, moduleId))
@@ -241,13 +285,18 @@ async function upgradeOne(
241
285
 
242
286
  if (newManifest.provides?.capabilities && newManifest.provides.capabilities.length > 0) {
243
287
  const regResult = await registerModuleCapabilities(moduleId, newManifest, db.$client);
244
- if (!regResult.success) {
288
+ if (!regResult.success && !opts.quiet) {
289
+ // Capability re-registration warnings are useful when upgrading
290
+ // from a path (operator iterating on dev module); for the
291
+ // registry sweep, the caller will surface them itself if needed.
245
292
  log.warn(` ${moduleId}: capability re-registration warning: ${regResult.error}`);
246
293
  }
247
294
  }
248
295
 
249
- log.success(`Upgraded ${moduleId} (v${oldManifest.version} → v${newManifest.version})`);
250
- return { status: 'success', moduleId };
296
+ if (!opts.quiet) {
297
+ log.success(`Upgraded ${moduleId} (${previousVersion} ${newVersion})`);
298
+ }
299
+ return { status: 'success', moduleId, previousVersion, newVersion };
251
300
  }
252
301
 
253
302
  /**
@@ -411,17 +460,18 @@ async function runRegistrySweep(
411
460
 
412
461
  if (nonBreaking.length > 0) {
413
462
  log.info(`Auto-applying ${nonBreaking.length} non-breaking update(s):`);
414
- for (const plan of nonBreaking) {
415
- console.log(
416
- ` ↑ ${plan.moduleId.padEnd(30)} ${plan.installedVersion} → ${plan.targetVersion} (${plan.classification})`,
417
- );
418
- }
419
463
  for (const plan of nonBreaking) {
420
464
  const result = await fetchAndUpgrade(client, plan.moduleId, plan.targetVersion, db, flags);
421
465
  if (result.status === 'failed') {
422
466
  failed.push({ moduleId: plan.moduleId, error: result.error });
467
+ console.log(
468
+ ` ✗ ${plan.moduleId.padEnd(30)} ${plan.installedVersion} → ${plan.targetVersion} (${plan.classification}, FAILED)`,
469
+ );
423
470
  } else if (result.status === 'success') {
424
471
  appliedNonBreaking++;
472
+ console.log(
473
+ ` ✓ ${plan.moduleId.padEnd(30)} ${result.previousVersion} → ${result.newVersion} (${plan.classification})`,
474
+ );
425
475
  }
426
476
  // status === 'skipped' shouldn't happen for registry-fetched packages
427
477
  // (we know the module is installed; the package definitely has a
@@ -453,8 +503,14 @@ async function runRegistrySweep(
453
503
  const result = await fetchAndUpgrade(client, plan.moduleId, plan.targetVersion, db, flags);
454
504
  if (result.status === 'failed') {
455
505
  failed.push({ moduleId: plan.moduleId, error: result.error });
506
+ console.log(
507
+ ` ✗ ${plan.moduleId.padEnd(30)} ${plan.installedVersion} → ${plan.targetVersion} (major, FAILED)`,
508
+ );
456
509
  } else if (result.status === 'success') {
457
510
  appliedBreaking++;
511
+ console.log(
512
+ ` ✓ ${plan.moduleId.padEnd(30)} ${result.previousVersion} → ${result.newVersion} (major)`,
513
+ );
458
514
  }
459
515
  }
460
516
  }
@@ -29,6 +29,9 @@ export interface ValidationResult {
29
29
  options?: Array<{ value: string; label: string; hint?: string }>;
30
30
  per_selection?: { key_pattern: string; prompt: string; type?: string; derive_from?: string };
31
31
  generate?: { method: string; length: number; encoding: string };
32
+ /** For `type: string-map` only — labels shown in the add-loop prompt. */
33
+ key_label?: string;
34
+ value_label?: string;
32
35
  }>;
33
36
  }
34
37
 
@@ -383,6 +386,9 @@ async function findMissingRequiredVariables(
383
386
  type?: string;
384
387
  options?: Array<{ value: string; label: string; hint?: string }>;
385
388
  per_selection?: { key_pattern: string; prompt: string; type?: string; derive_from?: string };
389
+ generate?: { method: string; length: number; encoding: string };
390
+ key_label?: string;
391
+ value_label?: string;
386
392
  }>
387
393
  > {
388
394
  const missing: Array<{
@@ -394,6 +400,8 @@ async function findMissingRequiredVariables(
394
400
  options?: Array<{ value: string; label: string; hint?: string }>;
395
401
  per_selection?: { key_pattern: string; prompt: string; type?: string; derive_from?: string };
396
402
  generate?: { method: string; length: number; encoding: string };
403
+ key_label?: string;
404
+ value_label?: string;
397
405
  }> = [];
398
406
 
399
407
  // Check declared variables (user config, capability, system, infrastructure)
@@ -460,6 +468,12 @@ async function findMissingRequiredVariables(
460
468
  source: 'secret',
461
469
  description: secret.description,
462
470
  generate: secret.generate,
471
+ // Pass through manifest-declared type + add-loop labels so the
472
+ // bus payload can drive the right responder UX (e.g. string-map
473
+ // gets the per-key add-loop instead of the JSON-blob prompt).
474
+ type: secret.type,
475
+ key_label: secret.key_label,
476
+ value_label: secret.value_label,
463
477
  });
464
478
  }
465
479
  }