@pleri/olam-cli 0.1.14 → 0.1.19

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.
Files changed (37) hide show
  1. package/dist/__tests__/auth-upgrade.test.js +236 -1
  2. package/dist/__tests__/auth-upgrade.test.js.map +1 -1
  3. package/dist/__tests__/install-root.test.d.ts +2 -0
  4. package/dist/__tests__/install-root.test.d.ts.map +1 -0
  5. package/dist/__tests__/install-root.test.js +119 -0
  6. package/dist/__tests__/install-root.test.js.map +1 -0
  7. package/dist/__tests__/upgrade.test.js +292 -2
  8. package/dist/__tests__/upgrade.test.js.map +1 -1
  9. package/dist/commands/__tests__/bootstrap.test.d.ts +2 -0
  10. package/dist/commands/__tests__/bootstrap.test.d.ts.map +1 -0
  11. package/dist/commands/__tests__/bootstrap.test.js +288 -0
  12. package/dist/commands/__tests__/bootstrap.test.js.map +1 -0
  13. package/dist/commands/__tests__/upgrade.rollback.test.js +1 -1
  14. package/dist/commands/__tests__/upgrade.swap.test.js +1 -1
  15. package/dist/commands/auth-upgrade.d.ts +41 -0
  16. package/dist/commands/auth-upgrade.d.ts.map +1 -1
  17. package/dist/commands/auth-upgrade.js +158 -6
  18. package/dist/commands/auth-upgrade.js.map +1 -1
  19. package/dist/commands/bootstrap.d.ts +95 -0
  20. package/dist/commands/bootstrap.d.ts.map +1 -0
  21. package/dist/commands/bootstrap.js +329 -0
  22. package/dist/commands/bootstrap.js.map +1 -0
  23. package/dist/commands/create.d.ts.map +1 -1
  24. package/dist/commands/create.js +19 -1
  25. package/dist/commands/create.js.map +1 -1
  26. package/dist/commands/upgrade.d.ts +76 -1
  27. package/dist/commands/upgrade.d.ts.map +1 -1
  28. package/dist/commands/upgrade.js +207 -6
  29. package/dist/commands/upgrade.js.map +1 -1
  30. package/dist/image-digests.json +8 -0
  31. package/dist/index.js +949 -199
  32. package/dist/index.js.map +1 -1
  33. package/dist/install-root.d.ts +74 -0
  34. package/dist/install-root.d.ts.map +1 -0
  35. package/dist/install-root.js +98 -0
  36. package/dist/install-root.js.map +1 -0
  37. package/package.json +1 -1
@@ -0,0 +1,288 @@
1
+ // C1 + C3 (Phase C — fresh-install self-bootstrap): `olam bootstrap`
2
+ // orchestrator. Closes the user goal end-to-end:
3
+ // `npm install -g @pleri/olam-cli && olam init && olam bootstrap`.
4
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
5
+ import { runBootstrap, pullImageWithRetry, loadImageDigests, } from '../bootstrap.js';
6
+ import { EXIT_BOOTSTRAP_PULL_FAILED, EXIT_GENERIC_ERROR, EXIT_PROTOCOL_MISMATCH, } from '../../exit-codes.js';
7
+ // ── Test helpers ──────────────────────────────────────────────────
8
+ function mkDigest(seed) {
9
+ // Build a 64-hex digest deterministically from a seed for readable test
10
+ // output. Real digests are sha256 hex of the manifest content; tests
11
+ // just need stable strings of the right shape.
12
+ const hex = (seed + 'a'.repeat(64)).replace(/[^a-f0-9]/g, '0').slice(0, 64);
13
+ return `sha256:${hex}`;
14
+ }
15
+ const FIXTURE_DIGESTS = {
16
+ 'host-cp': mkDigest('host-cp-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'),
17
+ auth: mkDigest('auth-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'),
18
+ devbox: mkDigest('devbox-ccccccccccccccccccccccccccccccccccccccccccccccccccccccc'),
19
+ $registry: 'ghcr.io/pleri',
20
+ $published_version: '0.1.15',
21
+ };
22
+ function ok(stdout = '') {
23
+ return { exitCode: 0, stdout, stderr: '' };
24
+ }
25
+ function err(stderr, exitCode = 1) {
26
+ return { exitCode, stdout: '', stderr };
27
+ }
28
+ function makeMockDocker(opts = {}) {
29
+ const calls = opts.calls ?? { pull: [], inspect: [] };
30
+ return {
31
+ info: vi.fn(async () => (opts.infoOk === false ? err('cannot connect to docker') : ok())),
32
+ pull: vi.fn(async (ref) => {
33
+ calls.pull.push(ref);
34
+ return opts.pullResults?.get(ref) ?? ok();
35
+ }),
36
+ inspectLabel: vi.fn(async (ref, label) => {
37
+ calls.inspect.push(`${ref}#${label}`);
38
+ const value = opts.inspectLabels?.get(ref) ?? '1';
39
+ return ok(`${value}\n`);
40
+ }),
41
+ };
42
+ }
43
+ function silentSubprocess() {
44
+ const calls = [];
45
+ return {
46
+ calls,
47
+ runOlamSubcommand: (args) => {
48
+ calls.push([...args]);
49
+ return ok();
50
+ },
51
+ };
52
+ }
53
+ // Suppress real console output during tests — orchestrator uses ora +
54
+ // stdout/stderr writes that vitest reports as warnings otherwise.
55
+ beforeEach(() => {
56
+ vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
57
+ vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
58
+ });
59
+ afterEach(() => {
60
+ vi.restoreAllMocks();
61
+ });
62
+ // ── pullImageWithRetry — unit ────────────────────────────────────
63
+ describe('pullImageWithRetry — coalesce + retry', () => {
64
+ it('returns first attempt success without retrying', async () => {
65
+ let calls = 0;
66
+ const docker = {
67
+ info: async () => ok(),
68
+ pull: async () => {
69
+ calls++;
70
+ return ok();
71
+ },
72
+ inspectLabel: async () => ok('1'),
73
+ };
74
+ const result = await pullImageWithRetry('ghcr.io/test/img@sha256:xxx', docker);
75
+ expect(result.exitCode).toBe(0);
76
+ expect(calls).toBe(1);
77
+ });
78
+ it('retries once on transient (network) failure, then succeeds', async () => {
79
+ let calls = 0;
80
+ const docker = {
81
+ info: async () => ok(),
82
+ pull: async () => {
83
+ calls++;
84
+ if (calls === 1)
85
+ return err('connection refused', 1);
86
+ return ok();
87
+ },
88
+ inspectLabel: async () => ok('1'),
89
+ };
90
+ const result = await pullImageWithRetry('ghcr.io/test/img@sha256:xxx', docker);
91
+ expect(result.exitCode).toBe(0);
92
+ expect(calls).toBe(2);
93
+ });
94
+ it('does NOT retry on a non-transient failure (e.g. manifest unknown)', async () => {
95
+ let calls = 0;
96
+ const docker = {
97
+ info: async () => ok(),
98
+ pull: async () => {
99
+ calls++;
100
+ return err('manifest unknown', 1);
101
+ },
102
+ inspectLabel: async () => ok('1'),
103
+ };
104
+ const result = await pullImageWithRetry('ghcr.io/test/img@sha256:xxx', docker);
105
+ expect(result.exitCode).toBe(1);
106
+ expect(calls).toBe(1);
107
+ });
108
+ it('coalesces concurrent identical pulls into one in-flight promise', async () => {
109
+ let pullsStarted = 0;
110
+ const gate = new Promise((resolveGate) => setTimeout(resolveGate, 30));
111
+ const docker = {
112
+ info: async () => ok(),
113
+ pull: async () => {
114
+ pullsStarted++;
115
+ await gate;
116
+ return ok();
117
+ },
118
+ inspectLabel: async () => ok('1'),
119
+ };
120
+ const ref = 'ghcr.io/test/img@sha256:dedupeme';
121
+ const [a, b, c] = await Promise.all([
122
+ pullImageWithRetry(ref, docker),
123
+ pullImageWithRetry(ref, docker),
124
+ pullImageWithRetry(ref, docker),
125
+ ]);
126
+ expect(a.exitCode).toBe(0);
127
+ expect(b.exitCode).toBe(0);
128
+ expect(c.exitCode).toBe(0);
129
+ expect(pullsStarted).toBe(1); // coalesced
130
+ });
131
+ it('does not coalesce different image refs', async () => {
132
+ let pullsStarted = 0;
133
+ const docker = {
134
+ info: async () => ok(),
135
+ pull: async () => {
136
+ pullsStarted++;
137
+ return ok();
138
+ },
139
+ inspectLabel: async () => ok('1'),
140
+ };
141
+ await Promise.all([
142
+ pullImageWithRetry('ghcr.io/a@sha256:111', docker),
143
+ pullImageWithRetry('ghcr.io/b@sha256:222', docker),
144
+ pullImageWithRetry('ghcr.io/c@sha256:333', docker),
145
+ ]);
146
+ expect(pullsStarted).toBe(3);
147
+ });
148
+ });
149
+ // ── runBootstrap — orchestrator ───────────────────────────────────
150
+ describe('runBootstrap — happy path', () => {
151
+ it('exits 0 when daemon up + 3 pulls succeed + handshake passes + services start', async () => {
152
+ const docker = makeMockDocker();
153
+ const sub = silentSubprocess();
154
+ const result = await runBootstrap({ withSmoke: false, skipAuthLogin: true }, { docker, digests: FIXTURE_DIGESTS, runOlamSubcommand: sub.runOlamSubcommand });
155
+ expect(result.exitCode).toBe(0);
156
+ expect(sub.calls).toEqual([
157
+ ['host-cp', 'start'],
158
+ ['auth', 'up'],
159
+ ]);
160
+ });
161
+ it('with --with-smoke, dispatches a smoke-test world create', async () => {
162
+ const docker = makeMockDocker();
163
+ const sub = silentSubprocess();
164
+ const result = await runBootstrap({ withSmoke: true, skipAuthLogin: true }, { docker, digests: FIXTURE_DIGESTS, runOlamSubcommand: sub.runOlamSubcommand });
165
+ expect(result.exitCode).toBe(0);
166
+ const lastCall = sub.calls[sub.calls.length - 1] ?? [];
167
+ expect(lastCall[0]).toBe('create');
168
+ expect(lastCall).toContain('--workspace');
169
+ expect(lastCall).toContain('smoke');
170
+ });
171
+ it('without --skip-auth-login (and OLAM_BOOTSTRAP_SKIP_AUTH_LOGIN unset), runs auth login', async () => {
172
+ const docker = makeMockDocker();
173
+ const sub = silentSubprocess();
174
+ delete process.env.OLAM_BOOTSTRAP_SKIP_AUTH_LOGIN;
175
+ const result = await runBootstrap({ withSmoke: false, skipAuthLogin: false }, { docker, digests: FIXTURE_DIGESTS, runOlamSubcommand: sub.runOlamSubcommand });
176
+ expect(result.exitCode).toBe(0);
177
+ expect(sub.calls).toContainEqual(['auth', 'login']);
178
+ });
179
+ });
180
+ describe('runBootstrap — failure paths', () => {
181
+ it('exits EXIT_GENERIC_ERROR (1) when docker daemon not reachable', async () => {
182
+ const docker = makeMockDocker({ infoOk: false });
183
+ const sub = silentSubprocess();
184
+ const result = await runBootstrap({ withSmoke: false, skipAuthLogin: true }, { docker, digests: FIXTURE_DIGESTS, runOlamSubcommand: sub.runOlamSubcommand });
185
+ expect(result.exitCode).toBe(EXIT_GENERIC_ERROR);
186
+ expect(result.summary).toContain('docker daemon not reachable');
187
+ // No subcommands run — bailed at step 1.
188
+ expect(sub.calls).toEqual([]);
189
+ });
190
+ it('exits EXIT_BOOTSTRAP_PULL_FAILED (3) when an image pull fails terminally', async () => {
191
+ const calls = { pull: [], inspect: [] };
192
+ const docker = makeMockDocker({
193
+ pullResults: new Map([
194
+ [`ghcr.io/pleri/olam-host-cp@${FIXTURE_DIGESTS['host-cp']}`, err('manifest unknown', 1)],
195
+ ]),
196
+ calls,
197
+ });
198
+ const sub = silentSubprocess();
199
+ const result = await runBootstrap({ withSmoke: false, skipAuthLogin: true }, { docker, digests: FIXTURE_DIGESTS, runOlamSubcommand: sub.runOlamSubcommand });
200
+ expect(result.exitCode).toBe(EXIT_BOOTSTRAP_PULL_FAILED);
201
+ expect(result.summary).toContain('pull failed');
202
+ expect(sub.calls).toEqual([]);
203
+ });
204
+ it('exits EXIT_PROTOCOL_MISMATCH (4) when an image LABEL is missing', async () => {
205
+ const docker = makeMockDocker({
206
+ // Missing LABEL → docker inspect returns "<no value>".
207
+ inspectLabels: new Map([
208
+ [`ghcr.io/pleri/olam-devbox@${FIXTURE_DIGESTS.devbox}`, '<no value>'],
209
+ ]),
210
+ });
211
+ const sub = silentSubprocess();
212
+ const result = await runBootstrap({ withSmoke: false, skipAuthLogin: true }, { docker, digests: FIXTURE_DIGESTS, runOlamSubcommand: sub.runOlamSubcommand });
213
+ expect(result.exitCode).toBe(EXIT_PROTOCOL_MISMATCH);
214
+ expect(result.summary).toContain('devbox');
215
+ });
216
+ it('exits EXIT_PROTOCOL_MISMATCH when an image LABEL declares non-overlapping versions', async () => {
217
+ const docker = makeMockDocker({
218
+ inspectLabels: new Map([
219
+ [`ghcr.io/pleri/olam-auth@${FIXTURE_DIGESTS.auth}`, '99'],
220
+ ]),
221
+ });
222
+ const sub = silentSubprocess();
223
+ const result = await runBootstrap({ withSmoke: false, skipAuthLogin: true }, { docker, digests: FIXTURE_DIGESTS, runOlamSubcommand: sub.runOlamSubcommand });
224
+ expect(result.exitCode).toBe(EXIT_PROTOCOL_MISMATCH);
225
+ expect(result.summary).toContain('auth');
226
+ });
227
+ it('exits EXIT_GENERIC_ERROR when host-cp start fails', async () => {
228
+ const docker = makeMockDocker();
229
+ const calls = [];
230
+ const runOlamSubcommand = (args) => {
231
+ calls.push([...args]);
232
+ if (args[0] === 'host-cp')
233
+ return err('host-cp could not start', 1);
234
+ return ok();
235
+ };
236
+ const result = await runBootstrap({ withSmoke: false, skipAuthLogin: true }, { docker, digests: FIXTURE_DIGESTS, runOlamSubcommand });
237
+ expect(result.exitCode).toBe(EXIT_GENERIC_ERROR);
238
+ expect(result.summary).toContain('host-cp');
239
+ });
240
+ it('exits EXIT_GENERIC_ERROR when auth up fails', async () => {
241
+ const docker = makeMockDocker();
242
+ const runOlamSubcommand = (args) => {
243
+ if (args[0] === 'auth' && args[1] === 'up')
244
+ return err('auth-service docker port busy', 1);
245
+ return ok();
246
+ };
247
+ const result = await runBootstrap({ withSmoke: false, skipAuthLogin: true }, { docker, digests: FIXTURE_DIGESTS, runOlamSubcommand });
248
+ expect(result.exitCode).toBe(EXIT_GENERIC_ERROR);
249
+ expect(result.summary).toContain('auth up');
250
+ });
251
+ });
252
+ describe('runBootstrap — registry override + digest resolution', () => {
253
+ it('uses --registry flag when provided', async () => {
254
+ const calls = { pull: [], inspect: [] };
255
+ const docker = makeMockDocker({ calls });
256
+ const sub = silentSubprocess();
257
+ await runBootstrap({ withSmoke: false, skipAuthLogin: true, registry: 'localhost:5000/test' }, { docker, digests: FIXTURE_DIGESTS, runOlamSubcommand: sub.runOlamSubcommand });
258
+ expect(calls.pull[0]).toContain('localhost:5000/test/olam-host-cp');
259
+ });
260
+ it('falls back to digests.$registry when --registry not provided', async () => {
261
+ const calls = { pull: [], inspect: [] };
262
+ const docker = makeMockDocker({ calls });
263
+ const sub = silentSubprocess();
264
+ await runBootstrap({ withSmoke: false, skipAuthLogin: true }, {
265
+ docker,
266
+ digests: { ...FIXTURE_DIGESTS, $registry: 'ghcr.io/atlas-namespace' },
267
+ runOlamSubcommand: sub.runOlamSubcommand,
268
+ });
269
+ expect(calls.pull[0]).toContain('ghcr.io/atlas-namespace/olam-host-cp');
270
+ });
271
+ it('pulls all 3 images with the correct digest references', async () => {
272
+ const calls = { pull: [], inspect: [] };
273
+ const docker = makeMockDocker({ calls });
274
+ const sub = silentSubprocess();
275
+ await runBootstrap({ withSmoke: false, skipAuthLogin: true }, { docker, digests: FIXTURE_DIGESTS, runOlamSubcommand: sub.runOlamSubcommand });
276
+ expect(calls.pull).toHaveLength(3);
277
+ expect(calls.pull).toContain(`ghcr.io/pleri/olam-host-cp@${FIXTURE_DIGESTS['host-cp']}`);
278
+ expect(calls.pull).toContain(`ghcr.io/pleri/olam-auth@${FIXTURE_DIGESTS.auth}`);
279
+ expect(calls.pull).toContain(`ghcr.io/pleri/olam-devbox@${FIXTURE_DIGESTS.devbox}`);
280
+ });
281
+ });
282
+ // ── loadImageDigests — unit ───────────────────────────────────────
283
+ describe('loadImageDigests', () => {
284
+ it('throws when image-digests.json is absent', () => {
285
+ expect(() => loadImageDigests('/tmp/no-such-install-' + Date.now())).toThrow(/image-digests\.json missing/);
286
+ });
287
+ });
288
+ //# sourceMappingURL=bootstrap.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bootstrap.test.js","sourceRoot":"","sources":["../../../src/commands/__tests__/bootstrap.test.ts"],"names":[],"mappings":"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"}
@@ -67,7 +67,7 @@ function buildFixtureImage(tag, labelValue) {
67
67
  return result.status === 0;
68
68
  }
69
69
  function bakedSha(tag) {
70
- const result = spawnSync('docker', ['inspect', '--format', '{{index .Config.Labels "olam_build_sha"}}', tag], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
70
+ const result = spawnSync('docker', ['inspect', '--format', '{{index .Config.Labels "olam.build.sha"}}', tag], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
71
71
  if (result.status !== 0)
72
72
  return null;
73
73
  return (result.stdout ?? '').trim();
@@ -87,7 +87,7 @@ function imageExists(tag) {
87
87
  return imageId(tag) !== null;
88
88
  }
89
89
  function bakedSha(tag) {
90
- const result = spawnSync('docker', ['inspect', '--format', '{{index .Config.Labels "olam_build_sha"}}', tag], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
90
+ const result = spawnSync('docker', ['inspect', '--format', '{{index .Config.Labels "olam.build.sha"}}', tag], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
91
91
  if (result.status !== 0)
92
92
  return null;
93
93
  return (result.stdout ?? '').trim();
@@ -9,14 +9,22 @@
9
9
  * smoke test: POST /credentials/add provider=codex must NOT return a Claude URL
10
10
  */
11
11
  import type { Command } from 'commander';
12
+ import { type DockerImpl, type ImageDigests } from './bootstrap.js';
12
13
  export interface AuthUpgradeOpts {
13
14
  readonly yes: boolean;
14
15
  readonly skipRecreate: boolean;
16
+ /**
17
+ * Phase C — C6: opt into the legacy source-build path for auth-service.
18
+ * Only works when isDevMode() is true. Default false → pull
19
+ * `olam-auth@sha256:<digest>` from ghcr.io and recreate.
20
+ */
21
+ readonly fromSource: boolean;
15
22
  }
16
23
  /** Normalise raw Commander option object into typed opts. */
17
24
  export declare function parseAuthUpgradeOpts(raw: {
18
25
  yes?: boolean;
19
26
  skipRecreate?: boolean;
27
+ fromSource?: boolean;
20
28
  }): AuthUpgradeOpts;
21
29
  /**
22
30
  * Check cwd looks like the olam repo root for auth-service.
@@ -43,5 +51,38 @@ export declare function smokeTestCodexProvider(authSecret: string | null): Promi
43
51
  ok: false;
44
52
  error: string;
45
53
  }>;
54
+ /**
55
+ * Phase C — C6: default `olam auth upgrade` flow for installed CLIs.
56
+ *
57
+ * Pulls `olam-auth@sha256:<digest>` from ghcr.io (via bootstrap.ts's
58
+ * retry-throttle-coalesce helper), runs the protocol-version handshake,
59
+ * re-tags to `olam-auth:local` (the canonical tag the
60
+ * AuthContainerController expects), then docker stop + docker rm +
61
+ * controller.start() — same recreate path as the legacy --from-source
62
+ * flow uses post-build.
63
+ */
64
+ export interface AuthUpgradePullDeps {
65
+ readonly docker?: DockerImpl;
66
+ readonly digests?: ImageDigests;
67
+ readonly registry?: string;
68
+ /**
69
+ * Recreate driver. Default: stop + rm + AuthContainerController.start
70
+ * + waitForAuthHealth. Tests stub to avoid real docker calls.
71
+ */
72
+ readonly recreateAuth?: () => Promise<{
73
+ ok: boolean;
74
+ error?: string;
75
+ }>;
76
+ /** Test-only: skip the docker-tag step (e.g. when injecting). */
77
+ readonly tagImpl?: (from: string, to: string) => {
78
+ ok: boolean;
79
+ error?: string;
80
+ };
81
+ }
82
+ export interface AuthUpgradePullResult {
83
+ readonly exitCode: number;
84
+ readonly summary: string;
85
+ }
86
+ export declare function runAuthUpgradePullByDigest(deps?: AuthUpgradePullDeps): Promise<AuthUpgradePullResult>;
46
87
  export declare function registerAuthUpgrade(auth: Command): void;
47
88
  //# sourceMappingURL=auth-upgrade.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth-upgrade.d.ts","sourceRoot":"","sources":["../../src/commands/auth-upgrade.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAezC,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;CAChC;AAED,6DAA6D;AAC7D,wBAAgB,oBAAoB,CAAC,GAAG,EAAE;IACxC,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,GAAG,eAAe,CAKlB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAW7F;AAED,uEAAuE;AACvE,wBAAsB,iBAAiB,CAAC,SAAS,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,CAc5E;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAC1C,UAAU,EAAE,MAAM,GAAG,IAAI,GACxB,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAuCtD;AAiMD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CASvD"}
1
+ {"version":3,"file":"auth-upgrade.d.ts","sourceRoot":"","sources":["../../src/commands/auth-upgrade.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASzC,OAAO,EAIL,KAAK,UAAU,EACf,KAAK,YAAY,EAClB,MAAM,gBAAgB,CAAC;AAexB,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;CAC9B;AAED,6DAA6D;AAC7D,wBAAgB,oBAAoB,CAAC,GAAG,EAAE;IACxC,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,GAAG,eAAe,CAMlB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAW7F;AAED,uEAAuE;AACvE,wBAAsB,iBAAiB,CAAC,SAAS,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,CAc5E;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAC1C,UAAU,EAAE,MAAM,GAAG,IAAI,GACxB,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAuCtD;AA4DD;;;;;;;;;GASG;AAEH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,OAAO,CAAC,EAAE,YAAY,CAAC;IAChC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACvE,iEAAiE;IACjE,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAClF;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAsB,0BAA0B,CAC9C,IAAI,GAAE,mBAAwB,GAC7B,OAAO,CAAC,qBAAqB,CAAC,CAqFhC;AAgMD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAqCvD"}
@@ -11,10 +11,15 @@
11
11
  import * as fs from 'node:fs';
12
12
  import * as path from 'node:path';
13
13
  import { spawnSync } from 'node:child_process';
14
+ import ora from 'ora';
14
15
  import pc from 'picocolors';
15
- import { printError, printSuccess, printInfo, printWarning } from '../output.js';
16
+ import { printError, printSuccess, printInfo, printWarning, printHeader } from '../output.js';
16
17
  import { readAuthSecret } from './host-cp.js';
17
18
  import { AuthContainerController } from '@olam/core/src/auth/index.js';
19
+ import { loadImageDigests, pullImageWithRetry, realDocker, } from './bootstrap.js';
20
+ import { EXIT_BOOTSTRAP_PULL_FAILED, EXIT_GENERIC_ERROR, EXIT_PROTOCOL_MISMATCH, } from '../exit-codes.js';
21
+ import { checkProtocolOverlap, parseProtocolVersionsLabel } from '../protocol-version.js';
22
+ import { isDevMode, MissingBuildScriptError } from '../install-root.js';
18
23
  const AUTH_PORT = 9999;
19
24
  const AUTH_HEALTH_URL = `http://127.0.0.1:${AUTH_PORT}/health`;
20
25
  const AUTH_ADD_URL = `http://127.0.0.1:${AUTH_PORT}/credentials/add`;
@@ -23,6 +28,7 @@ export function parseAuthUpgradeOpts(raw) {
23
28
  return {
24
29
  yes: raw.yes === true,
25
30
  skipRecreate: raw.skipRecreate === true,
31
+ fromSource: raw.fromSource === true,
26
32
  };
27
33
  }
28
34
  /**
@@ -143,6 +149,117 @@ function printTimings(timings) {
143
149
  }
144
150
  printInfo('total', `${(total / 1000).toFixed(1)}s`);
145
151
  }
152
+ export async function runAuthUpgradePullByDigest(deps = {}) {
153
+ const docker = deps.docker ?? realDocker;
154
+ const digests = deps.digests ?? loadImageDigests();
155
+ const registry = deps.registry ?? digests.$registry ?? 'ghcr.io/pleri';
156
+ printHeader('olam auth upgrade (pull-by-digest)');
157
+ // Step 1 — daemon smoke.
158
+ const infoSpinner = ora('Checking docker daemon').start();
159
+ const info = await docker.info();
160
+ if (info.exitCode !== 0) {
161
+ infoSpinner.fail('docker daemon not reachable');
162
+ process.stderr.write(`${pc.red('error')} docker info exited with ${info.exitCode}.\n` +
163
+ ' Ensure Docker is running, then retry.\n');
164
+ return { exitCode: EXIT_GENERIC_ERROR, summary: 'docker daemon not reachable' };
165
+ }
166
+ infoSpinner.succeed('docker daemon reachable');
167
+ // Step 2 — pull olam-auth by digest.
168
+ const ref = `${registry}/olam-auth@${digests.auth}`;
169
+ const pullSpinner = ora(`Pulling ${ref.slice(0, 60)}…`).start();
170
+ const pullResult = await pullImageWithRetry(ref, docker);
171
+ if (pullResult.exitCode !== 0) {
172
+ pullSpinner.fail('Pull failed');
173
+ process.stderr.write(`${pc.red('error')} pull failed (exit ${pullResult.exitCode}):\n` +
174
+ ` ${pullResult.stderr.split('\n')[0] ?? '(empty)'}\n` +
175
+ '\n Remedy: re-run after resolving network / GHCR availability.\n' +
176
+ ' For monorepo dev, `OLAM_DEV=1 olam auth upgrade --from-source` builds locally.\n');
177
+ return { exitCode: EXIT_BOOTSTRAP_PULL_FAILED, summary: 'pull failed: auth' };
178
+ }
179
+ pullSpinner.succeed('Image pulled');
180
+ // Step 3 — protocol-version handshake.
181
+ const handshakeSpinner = ora('Verifying olam.protocol.versions handshake').start();
182
+ const inspect = await docker.inspectLabel(ref, 'olam.protocol.versions');
183
+ if (inspect.exitCode !== 0) {
184
+ handshakeSpinner.fail('Could not inspect auth image');
185
+ process.stderr.write(`${pc.red('error')} docker inspect ${ref} failed: ${inspect.stderr}\n`);
186
+ return { exitCode: EXIT_GENERIC_ERROR, summary: 'inspect failed: auth' };
187
+ }
188
+ const labelValue = (inspect.stdout || '').trim();
189
+ const versions = parseProtocolVersionsLabel(labelValue === '<no value>' ? '' : labelValue);
190
+ const decision = checkProtocolOverlap(versions);
191
+ if (!decision.compatible) {
192
+ handshakeSpinner.fail('Protocol mismatch on auth image');
193
+ process.stderr.write(`${pc.red('error')} ${decision.remedy}\n`);
194
+ return { exitCode: EXIT_PROTOCOL_MISMATCH, summary: 'protocol mismatch: auth' };
195
+ }
196
+ handshakeSpinner.succeed('Protocol handshake passed');
197
+ // Step 4 — re-tag pulled-by-digest to olam-auth:local.
198
+ const tagSpinner = ora('Tagging digest → olam-auth:local').start();
199
+ const tagger = deps.tagImpl ?? defaultTagAuth;
200
+ const tagResult = tagger(ref, 'olam-auth:local');
201
+ if (!tagResult.ok) {
202
+ tagSpinner.fail('docker tag failed');
203
+ process.stderr.write(`${pc.red('error')} ${tagResult.error ?? 'docker tag failed'}\n`);
204
+ return { exitCode: EXIT_GENERIC_ERROR, summary: 'tag failed: auth' };
205
+ }
206
+ tagSpinner.succeed('Re-tagged → olam-auth:local');
207
+ // Step 5 — recreate auth-service container.
208
+ const authSpinner = ora('Recreating auth-service container').start();
209
+ const recreate = deps.recreateAuth ?? defaultRecreateAuth;
210
+ const recreateResult = await recreate();
211
+ if (!recreateResult.ok) {
212
+ authSpinner.fail('auth-service recreate failed');
213
+ process.stderr.write(`${pc.red('error')} ${recreateResult.error ?? 'unknown failure'}\n`);
214
+ return { exitCode: EXIT_GENERIC_ERROR, summary: 'auth recreate failed' };
215
+ }
216
+ authSpinner.succeed('auth-service running on new image');
217
+ printSuccess('Auth upgrade complete (pull-by-digest)');
218
+ printInfo('digest', digests.auth.slice(0, 32) + '…');
219
+ return { exitCode: 0, summary: 'auth upgraded' };
220
+ }
221
+ function defaultTagAuth(from, to) {
222
+ try {
223
+ const r = spawnSync('docker', ['tag', from, to], {
224
+ encoding: 'utf-8',
225
+ stdio: ['ignore', 'ignore', 'pipe'],
226
+ });
227
+ if (r.status === 0 && r.error === undefined)
228
+ return { ok: true };
229
+ return {
230
+ ok: false,
231
+ error: (r.stderr ?? '').trim() || r.error?.message || 'docker tag failed',
232
+ };
233
+ }
234
+ catch (err) {
235
+ return {
236
+ ok: false,
237
+ error: err instanceof Error ? `spawnSync threw: ${err.message}` : 'spawnSync threw',
238
+ };
239
+ }
240
+ }
241
+ async function defaultRecreateAuth() {
242
+ try {
243
+ spawnSync('docker', ['stop', 'olam-auth'], {
244
+ encoding: 'utf-8',
245
+ stdio: ['ignore', 'ignore', 'ignore'],
246
+ });
247
+ spawnSync('docker', ['rm', 'olam-auth'], {
248
+ encoding: 'utf-8',
249
+ stdio: ['ignore', 'ignore', 'ignore'],
250
+ });
251
+ const controller = new AuthContainerController();
252
+ controller.start();
253
+ const healthy = await waitForAuthHealth(15_000);
254
+ if (!healthy) {
255
+ return { ok: false, error: 'auth-service /health did not respond within 15s' };
256
+ }
257
+ return { ok: true };
258
+ }
259
+ catch (err) {
260
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
261
+ }
262
+ }
146
263
  // ── Main handler ──────────────────────────────────────────────────
147
264
  async function handleAuthUpgrade(opts) {
148
265
  const cwd = process.cwd();
@@ -175,8 +292,23 @@ async function handleAuthUpgrade(opts) {
175
292
  }
176
293
  }
177
294
  const timings = [];
178
- // Step a: build the auth image.
179
- const buildScript = path.join(cwd, 'packages/adapters/src/docker/build-auth.sh');
295
+ // Step a: build the auth image. Phase B6: resolve the build script via
296
+ // installRoot + 2-condition dev-mode check (OLAM_DEV=1 + monorepo
297
+ // siblings). Outside dev mode, surface the named remedy from
298
+ // MissingBuildScriptError and exit cleanly — published-image upgrades
299
+ // (Phase C's C6) take the no-flag default path.
300
+ let buildScript;
301
+ try {
302
+ const { resolveBuildScript } = await import('../install-root.js');
303
+ buildScript = resolveBuildScript({
304
+ scriptRelPath: 'packages/adapters/src/docker/build-auth.sh',
305
+ });
306
+ }
307
+ catch (err) {
308
+ printError(err instanceof Error ? err.message : String(err));
309
+ process.exitCode = 1;
310
+ return;
311
+ }
180
312
  const imageResult = runStep('bash build-auth.sh', 'bash', [buildScript], { cwd });
181
313
  timings.push({ label: 'docker image build', durationMs: imageResult.durationMs });
182
314
  if (!imageResult.ok) {
@@ -267,11 +399,31 @@ async function handleAuthUpgrade(opts) {
267
399
  export function registerAuthUpgrade(auth) {
268
400
  auth
269
401
  .command('upgrade')
270
- .description('Rebuild the olam-auth image and force-recreate the container')
402
+ .description('Upgrade the olam-auth container. Default: pull olam-auth@<digest> from ghcr.io and recreate. ' +
403
+ 'Use --from-source to rebuild from monorepo source (requires OLAM_DEV=1).')
271
404
  .option('-y, --yes', 'Skip the confirmation prompt')
272
- .option('--skip-recreate', 'Build the image only; skip container stop/rm/start')
405
+ .option('--skip-recreate', 'Build the image only; skip container stop/rm/start (only with --from-source)')
406
+ .option('--from-source', 'Build olam-auth from monorepo source (legacy path).\n' +
407
+ ' Requires OLAM_DEV=1 + a `packages/` sibling at the install root.')
273
408
  .action(async (opts) => {
274
- await handleAuthUpgrade(parseAuthUpgradeOpts(opts));
409
+ const parsed = parseAuthUpgradeOpts(opts);
410
+ if (parsed.fromSource) {
411
+ if (!isDevMode()) {
412
+ printError(new MissingBuildScriptError('packages/adapters/src/docker/build-auth.sh').message);
413
+ process.exitCode = 1;
414
+ return;
415
+ }
416
+ await handleAuthUpgrade(parsed);
417
+ return;
418
+ }
419
+ try {
420
+ const result = await runAuthUpgradePullByDigest();
421
+ process.exitCode = result.exitCode;
422
+ }
423
+ catch (err) {
424
+ printError(err instanceof Error ? err.message : String(err));
425
+ process.exitCode = EXIT_GENERIC_ERROR;
426
+ }
275
427
  });
276
428
  }
277
429
  //# sourceMappingURL=auth-upgrade.js.map