@celilo/cli 0.3.21 → 0.3.22

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.21",
3
+ "version": "0.3.22",
4
4
  "description": "Celilo — home lab orchestration CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -108,7 +108,7 @@ export function classifyVersionChange(installed: string, latest: string): Versio
108
108
  * the standard upgradeOne path against it. Cleans the temp file in a
109
109
  * finally block so a mid-flight failure doesn't leak a tar.zst on disk.
110
110
  */
111
- async function fetchAndUpgrade(
111
+ export async function fetchAndUpgrade(
112
112
  client: RegistryClient,
113
113
  moduleId: string,
114
114
  version: string,
@@ -15,6 +15,7 @@
15
15
  * lives in `services/update/orchestrator.ts`.
16
16
  */
17
17
 
18
+ import { spawnSync } from 'node:child_process';
18
19
  import { existsSync, readFileSync } from 'node:fs';
19
20
  import { dirname, join } from 'node:path';
20
21
  import { fileURLToPath } from 'node:url';
@@ -38,6 +39,16 @@ import {
38
39
  import type { SystemUpdateResult } from '../../services/update/types';
39
40
  import { getFlag, hasFlag } from '../parser';
40
41
  import type { CommandResult } from '../types';
42
+ import { fetchAndUpgrade } from './module-upgrade';
43
+
44
+ /**
45
+ * Packages we manage via the global bun install. The self-update step
46
+ * runs `bun update -g` against all of these in one shot, so operators
47
+ * never have to remember the three-package incantation.
48
+ *
49
+ * Ordering matters cosmetically only — bun resolves them all together.
50
+ */
51
+ const MANAGED_PACKAGES = ['@celilo/cli', '@celilo/event-bus', '@celilo/e2e'] as const;
41
52
 
42
53
  function readInstalledCliVersion(): string {
43
54
  const here = dirname(fileURLToPath(import.meta.url));
@@ -141,16 +152,14 @@ async function buildSnapshots(
141
152
  *
142
153
  * - `backup` calls `createModuleBackup`. Modules without an
143
154
  * `on_backup` hook return ok (nothing to back up; not an error).
144
- * - `upgrade` is a no-op for now the in-place upgrade path lives
145
- * in `module import <name>` and needs a refactor before the
146
- * orchestrator can drive it cleanly. Deploy will re-converge
147
- * against whatever's installed, which is the right behavior for
148
- * "redeploy what's there."
155
+ * - `upgrade` fetches the latest version from the registry and runs
156
+ * the in-place upgrade (`fetchAndUpgrade` from module-upgrade.ts).
157
+ * Same code path that `module update` uses for its sweep mode.
149
158
  * - `deploy` calls `deployModule`.
150
159
  * - `health` calls `runModuleHealthCheck`.
151
160
  * - `snapshotCeliloDb` calls `createSystemStateBackup`.
152
161
  */
153
- function buildOps(): OrchestratorOps {
162
+ function buildOps(registry: RegistryClient): OrchestratorOps {
154
163
  return {
155
164
  backup: async (moduleId, _updateId) => {
156
165
  const db = getDb();
@@ -163,7 +172,35 @@ function buildOps(): OrchestratorOps {
163
172
  const result = await createModuleBackup(moduleId);
164
173
  return result.success ? { ok: true } : { ok: false, error: result.error };
165
174
  },
166
- upgrade: async (_id) => ({ ok: true }),
175
+ // Fetch the latest version from the registry and run the in-place
176
+ // upgrade (file-copy + DB update + capability re-register, preserving
177
+ // configs/secrets/infra). Reuses the same path `module update` runs
178
+ // for its sweep mode, so behavior is consistent regardless of which
179
+ // entry point the operator uses.
180
+ upgrade: async (moduleId) => {
181
+ const db = getDb();
182
+ try {
183
+ const entries = await registry.getIndex(moduleId);
184
+ if (entries.length === 0) {
185
+ return { ok: false, error: `${moduleId}: not in registry` };
186
+ }
187
+ const latest = registry.latestVersion(entries);
188
+ if (!latest) {
189
+ return { ok: false, error: `${moduleId}: no non-yanked version` };
190
+ }
191
+ const result = await fetchAndUpgrade(registry, moduleId, latest.vers, db, {});
192
+ if (result.status === 'success') return { ok: true };
193
+ if (result.status === 'failed') return { ok: false, error: result.error };
194
+ // 'skipped' isn't expected here (the module IS installed), but
195
+ // surface it as a non-fatal so the orchestrator can decide.
196
+ return { ok: false, error: `unexpected skip: ${result.reason}` };
197
+ } catch (err) {
198
+ return {
199
+ ok: false,
200
+ error: err instanceof Error ? err.message : String(err),
201
+ };
202
+ }
203
+ },
167
204
  deploy: async (id) => {
168
205
  const db = getDb();
169
206
  // The deploy interview runs through the bus; if config is
@@ -357,7 +394,7 @@ export async function handleSystemUpdate(
357
394
  };
358
395
  }
359
396
 
360
- const ops = buildOps();
397
+ const ops = buildOps(registry);
361
398
 
362
399
  const result = await runSystemUpdate({
363
400
  audit: auditDeps,
@@ -367,10 +404,23 @@ export async function handleSystemUpdate(
367
404
  selfUpdate: {
368
405
  installedVersion: readInstalledCliVersion(),
369
406
  fetcher: fetchLatestCliVersion,
370
- updater: async () => ({
371
- ok: false,
372
- stderr: 'self-update wiring lands in Phase 5',
373
- }),
407
+ // Refresh ALL managed @celilo/* packages in one bun-update call.
408
+ // The orchestrator only tracks @celilo/cli's from/to versions,
409
+ // but we sweep event-bus and e2e along with it so operators
410
+ // don't have to type the three-package incantation themselves.
411
+ // Failures from `bun update` propagate as stderr; the orchestrator
412
+ // surfaces them in the run summary.
413
+ updater: async () => {
414
+ const r = spawnSync('bun', ['update', '-g', ...MANAGED_PACKAGES], {
415
+ stdio: ['ignore', 'pipe', 'pipe'],
416
+ encoding: 'utf-8',
417
+ });
418
+ if (r.status === 0) return { ok: true, stderr: '' };
419
+ return {
420
+ ok: false,
421
+ stderr: (r.stderr ?? '').trim() || `bun update -g exited ${r.status}`,
422
+ };
423
+ },
374
424
  },
375
425
  progress: { emit() {} },
376
426
  noBackup,