@celilo/cli 5.1.0 → 5.1.1

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": "5.1.0",
3
+ "version": "5.1.1",
4
4
  "description": "Celilo — home lab orchestration CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,4 +1,7 @@
1
1
  import { describe, expect, test } from 'bun:test';
2
+ import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
2
5
  import { findStaleBundledDeps, refreshAndVerifyBundledDeps } from './bundled-deps';
3
6
 
4
7
  describe('findStaleBundledDeps (ISS-0104)', () => {
@@ -52,4 +55,45 @@ describe('refreshAndVerifyBundledDeps (ISS-0104)', () => {
52
55
  expect(result).toEqual({ refreshed: false, mismatches: [] });
53
56
  expect(installs).toBe(0);
54
57
  });
58
+
59
+ // celilo#1447: the release run's sweep installs seconds after the same run
60
+ // published the packages it resolves, so bun's cached packument 404s them.
61
+ describe('just-published dep (celilo#1447)', () => {
62
+ function moduleWithScripts(): string {
63
+ const dir = mkdtempSync(join(tmpdir(), 'bundled-deps-'));
64
+ mkdirSync(join(dir, 'scripts'), { recursive: true });
65
+ writeFileSync(join(dir, 'scripts', 'package.json'), '{"name":"m"}');
66
+ return dir;
67
+ }
68
+
69
+ test('retries with --no-cache when the first install cannot resolve it', () => {
70
+ const cmds: string[] = [];
71
+ const slept: number[] = [];
72
+ refreshAndVerifyBundledDeps(
73
+ moduleWithScripts(),
74
+ (cmd) => {
75
+ cmds.push(cmd);
76
+ if (cmds.length === 1) throw new Error('failed to resolve (but package exists)');
77
+ },
78
+ (ms) => void slept.push(ms),
79
+ );
80
+ expect(cmds).toEqual(['bun install', 'bun install --no-cache']);
81
+ expect(slept).toEqual([2_000]);
82
+ });
83
+
84
+ test('rethrows once the retries are exhausted', () => {
85
+ const cmds: string[] = [];
86
+ expect(() =>
87
+ refreshAndVerifyBundledDeps(
88
+ moduleWithScripts(),
89
+ (cmd) => {
90
+ cmds.push(cmd);
91
+ throw new Error('still unresolvable');
92
+ },
93
+ () => {},
94
+ ),
95
+ ).toThrow('still unresolvable');
96
+ expect(cmds).toHaveLength(3);
97
+ });
98
+ });
55
99
  });
@@ -65,6 +65,44 @@ function findWorkspaceRoot(start: string): string | undefined {
65
65
  return undefined;
66
66
  }
67
67
 
68
+ /**
69
+ * celilo#1447: in a release run this install happens SECONDS after the same
70
+ * run published the workspace packages it must resolve. bun has already cached
71
+ * the pre-publish packument (the workspace install at the top of the job), so
72
+ * it fails with "@celilo/capabilities@^6.1.0 failed to resolve ... but package
73
+ * exists" — and the whole module sweep aborts AFTER npm and the CLI shipped,
74
+ * which is exactly the half-shipped release #1369 reordered the steps to
75
+ * prevent. `--no-cache` re-reads the registry; the backoff covers the case
76
+ * where npm's own metadata has not propagated yet.
77
+ *
78
+ * ponytail: fixed 3 attempts, no jitter. If propagation ever needs longer,
79
+ * raise RETRY_DELAYS_MS rather than adding a scheduler.
80
+ */
81
+ const RETRY_DELAYS_MS = [2_000, 8_000];
82
+
83
+ function installWithRetry(
84
+ run: (cmd: string, cwd: string) => void,
85
+ sleep: (ms: number) => void,
86
+ scriptsDir: string,
87
+ ): void {
88
+ try {
89
+ run('bun install', scriptsDir);
90
+ return;
91
+ } catch (err) {
92
+ let lastErr = err;
93
+ for (const delay of RETRY_DELAYS_MS) {
94
+ sleep(delay);
95
+ try {
96
+ run('bun install --no-cache', scriptsDir);
97
+ return;
98
+ } catch (retryErr) {
99
+ lastErr = retryErr;
100
+ }
101
+ }
102
+ throw lastErr;
103
+ }
104
+ }
105
+
68
106
  export interface RefreshResult {
69
107
  /** Whether the module had a scripts/ package to refresh. */
70
108
  refreshed: boolean;
@@ -84,6 +122,7 @@ export function refreshAndVerifyBundledDeps(
84
122
  moduleDir: string,
85
123
  run: (cmd: string, cwd: string) => void = (cmd, cwd) =>
86
124
  void execSync(cmd, { cwd, stdio: 'pipe' }),
125
+ sleep: (ms: number) => void = (ms) => Bun.sleepSync(ms),
87
126
  ): RefreshResult {
88
127
  const scriptsDir = join(moduleDir, 'scripts');
89
128
  if (!existsSync(join(scriptsDir, 'package.json'))) {
@@ -92,7 +131,7 @@ export function refreshAndVerifyBundledDeps(
92
131
 
93
132
  // Reconcile node_modules with the lockfile + package.json pins so the pack
94
133
  // bundles a CURRENT closure, not whatever the operator last installed.
95
- run('bun install', scriptsDir);
134
+ installWithRetry(run, sleep, scriptsDir);
96
135
 
97
136
  const workspaceRoot = findWorkspaceRoot(moduleDir);
98
137
  if (!workspaceRoot) {