@pleri/olam-cli 0.1.11 → 0.1.13
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/dist/__tests__/auth-status.test.js +8 -7
- package/dist/__tests__/auth-status.test.js.map +1 -1
- package/dist/__tests__/help-output.test.d.ts +2 -0
- package/dist/__tests__/help-output.test.d.ts.map +1 -0
- package/dist/__tests__/help-output.test.js +74 -0
- package/dist/__tests__/help-output.test.js.map +1 -0
- package/dist/__tests__/image-presence.test.d.ts +2 -0
- package/dist/__tests__/image-presence.test.d.ts.map +1 -0
- package/dist/__tests__/image-presence.test.js +44 -0
- package/dist/__tests__/image-presence.test.js.map +1 -0
- package/dist/__tests__/protocol-version.test.d.ts +2 -0
- package/dist/__tests__/protocol-version.test.d.ts.map +1 -0
- package/dist/__tests__/protocol-version.test.js +170 -0
- package/dist/__tests__/protocol-version.test.js.map +1 -0
- package/dist/__tests__/registry-allowlist.test.d.ts +2 -0
- package/dist/__tests__/registry-allowlist.test.d.ts.map +1 -0
- package/dist/__tests__/registry-allowlist.test.js +129 -0
- package/dist/__tests__/registry-allowlist.test.js.map +1 -0
- package/dist/commands/__tests__/crystallize.test.d.ts +2 -0
- package/dist/commands/__tests__/crystallize.test.d.ts.map +1 -0
- package/dist/commands/__tests__/crystallize.test.js +133 -0
- package/dist/commands/__tests__/crystallize.test.js.map +1 -0
- package/dist/commands/__tests__/upgrade.all-three.test.d.ts +19 -0
- package/dist/commands/__tests__/upgrade.all-three.test.d.ts.map +1 -0
- package/dist/commands/__tests__/upgrade.all-three.test.js +92 -0
- package/dist/commands/__tests__/upgrade.all-three.test.js.map +1 -0
- package/dist/commands/__tests__/upgrade.history.test.d.ts +15 -0
- package/dist/commands/__tests__/upgrade.history.test.d.ts.map +1 -0
- package/dist/commands/__tests__/upgrade.history.test.js +199 -0
- package/dist/commands/__tests__/upgrade.history.test.js.map +1 -0
- package/dist/commands/__tests__/upgrade.lock.test.d.ts +15 -0
- package/dist/commands/__tests__/upgrade.lock.test.d.ts.map +1 -0
- package/dist/commands/__tests__/upgrade.lock.test.js +253 -0
- package/dist/commands/__tests__/upgrade.lock.test.js.map +1 -0
- package/dist/commands/__tests__/upgrade.olam-tag.test.d.ts +21 -0
- package/dist/commands/__tests__/upgrade.olam-tag.test.d.ts.map +1 -0
- package/dist/commands/__tests__/upgrade.olam-tag.test.js +127 -0
- package/dist/commands/__tests__/upgrade.olam-tag.test.js.map +1 -0
- package/dist/commands/__tests__/upgrade.poll.test.d.ts +14 -0
- package/dist/commands/__tests__/upgrade.poll.test.d.ts.map +1 -0
- package/dist/commands/__tests__/upgrade.poll.test.js +136 -0
- package/dist/commands/__tests__/upgrade.poll.test.js.map +1 -0
- package/dist/commands/__tests__/upgrade.recreate.test.d.ts +17 -0
- package/dist/commands/__tests__/upgrade.recreate.test.d.ts.map +1 -0
- package/dist/commands/__tests__/upgrade.recreate.test.js +95 -0
- package/dist/commands/__tests__/upgrade.recreate.test.js.map +1 -0
- package/dist/commands/__tests__/upgrade.rollback.test.d.ts +12 -0
- package/dist/commands/__tests__/upgrade.rollback.test.d.ts.map +1 -0
- package/dist/commands/__tests__/upgrade.rollback.test.js +275 -0
- package/dist/commands/__tests__/upgrade.rollback.test.js.map +1 -0
- package/dist/commands/__tests__/upgrade.sha-capture.test.d.ts +12 -0
- package/dist/commands/__tests__/upgrade.sha-capture.test.d.ts.map +1 -0
- package/dist/commands/__tests__/upgrade.sha-capture.test.js +63 -0
- package/dist/commands/__tests__/upgrade.sha-capture.test.js.map +1 -0
- package/dist/commands/__tests__/upgrade.smoke.test.d.ts +19 -0
- package/dist/commands/__tests__/upgrade.smoke.test.d.ts.map +1 -0
- package/dist/commands/__tests__/upgrade.smoke.test.js +101 -0
- package/dist/commands/__tests__/upgrade.smoke.test.js.map +1 -0
- package/dist/commands/__tests__/upgrade.swap.test.d.ts +19 -0
- package/dist/commands/__tests__/upgrade.swap.test.d.ts.map +1 -0
- package/dist/commands/__tests__/upgrade.swap.test.js +333 -0
- package/dist/commands/__tests__/upgrade.swap.test.js.map +1 -0
- package/dist/commands/auth-status.d.ts +8 -1
- package/dist/commands/auth-status.d.ts.map +1 -1
- package/dist/commands/auth-status.js +2 -1
- package/dist/commands/auth-status.js.map +1 -1
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +31 -0
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/crystallize.d.ts +11 -1
- package/dist/commands/crystallize.d.ts.map +1 -1
- package/dist/commands/crystallize.js +32 -8
- package/dist/commands/crystallize.js.map +1 -1
- package/dist/commands/upgrade-history.d.ts +17 -0
- package/dist/commands/upgrade-history.d.ts.map +1 -0
- package/dist/commands/upgrade-history.js +40 -0
- package/dist/commands/upgrade-history.js.map +1 -0
- package/dist/commands/upgrade-lock.d.ts +102 -0
- package/dist/commands/upgrade-lock.d.ts.map +1 -0
- package/dist/commands/upgrade-lock.js +225 -0
- package/dist/commands/upgrade-lock.js.map +1 -0
- package/dist/commands/upgrade-log.d.ts +86 -0
- package/dist/commands/upgrade-log.d.ts.map +1 -0
- package/dist/commands/upgrade-log.js +146 -0
- package/dist/commands/upgrade-log.js.map +1 -0
- package/dist/commands/upgrade.d.ts +265 -0
- package/dist/commands/upgrade.d.ts.map +1 -1
- package/dist/commands/upgrade.js +840 -10
- package/dist/commands/upgrade.js.map +1 -1
- package/dist/exit-codes.d.ts +35 -0
- package/dist/exit-codes.d.ts.map +1 -0
- package/dist/exit-codes.js +35 -0
- package/dist/exit-codes.js.map +1 -0
- package/dist/image-presence.d.ts +40 -0
- package/dist/image-presence.d.ts.map +1 -0
- package/dist/image-presence.js +39 -0
- package/dist/image-presence.js.map +1 -0
- package/dist/index.js +1058 -168
- package/dist/index.js.map +1 -1
- package/dist/pleri-config.d.ts +22 -0
- package/dist/pleri-config.d.ts.map +1 -0
- package/dist/pleri-config.js +42 -0
- package/dist/pleri-config.js.map +1 -0
- package/dist/protocol-version.d.ts +79 -0
- package/dist/protocol-version.d.ts.map +1 -0
- package/dist/protocol-version.js +133 -0
- package/dist/protocol-version.js.map +1 -0
- package/dist/registry-allowlist.d.ts +47 -0
- package/dist/registry-allowlist.d.ts.map +1 -0
- package/dist/registry-allowlist.js +67 -0
- package/dist/registry-allowlist.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for `olam upgrade` CLI lock (Phase 2a — A1).
|
|
3
|
+
*
|
|
4
|
+
* Verifies the contract from `packages/cli/src/commands/upgrade-lock.ts`:
|
|
5
|
+
* - Atomic create-or-fail via fs.openSync 'wx'
|
|
6
|
+
* - Stale-lock recovery (empty / parse-error / dead-pid / >30min timeout)
|
|
7
|
+
* - PID liveness check via process.kill(pid, 0)
|
|
8
|
+
* - Command-name check via `ps -p <pid> -o comm=`
|
|
9
|
+
* - Idempotent release
|
|
10
|
+
* - Concurrent-acquisition refusal
|
|
11
|
+
*
|
|
12
|
+
* Run: `cd packages/cli && npx vitest run src/commands/__tests__/upgrade.lock.test.ts`
|
|
13
|
+
*/
|
|
14
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
15
|
+
import * as fs from 'node:fs';
|
|
16
|
+
import * as os from 'node:os';
|
|
17
|
+
import * as path from 'node:path';
|
|
18
|
+
import { acquireLock, releaseLock, readLockFile, isStaleLock, isPidAlive, isOlamUpgradeCommand, formatRefusalMessage, STALE_LOCK_TIMEOUT_MS, PS_UNAVAILABLE, } from '../upgrade-lock.js';
|
|
19
|
+
let tmpDir;
|
|
20
|
+
let tmpLockPath;
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'olam-lock-test-'));
|
|
23
|
+
tmpLockPath = path.join(tmpDir, 'upgrade.lock');
|
|
24
|
+
});
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
try {
|
|
27
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// best effort
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
describe('acquireLock — happy path', () => {
|
|
34
|
+
it('acquires a lock when none exists', () => {
|
|
35
|
+
const r = acquireLock(tmpLockPath);
|
|
36
|
+
expect(r.acquired).toBe(true);
|
|
37
|
+
if (r.acquired) {
|
|
38
|
+
expect(r.lockPath).toBe(tmpLockPath);
|
|
39
|
+
}
|
|
40
|
+
expect(fs.existsSync(tmpLockPath)).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
it('writes pid + startTs to the lock file', () => {
|
|
43
|
+
const before = Date.now();
|
|
44
|
+
const r = acquireLock(tmpLockPath);
|
|
45
|
+
const after = Date.now();
|
|
46
|
+
expect(r.acquired).toBe(true);
|
|
47
|
+
const content = readLockFile(tmpLockPath);
|
|
48
|
+
expect(content).not.toBeNull();
|
|
49
|
+
expect(content.pid).toBe(process.pid);
|
|
50
|
+
expect(content.startTs).toBeGreaterThanOrEqual(before);
|
|
51
|
+
expect(content.startTs).toBeLessThanOrEqual(after);
|
|
52
|
+
});
|
|
53
|
+
it('creates the parent directory if missing', () => {
|
|
54
|
+
const nestedDir = path.join(tmpDir, 'nested', 'deeper');
|
|
55
|
+
const nestedLock = path.join(nestedDir, 'upgrade.lock');
|
|
56
|
+
expect(fs.existsSync(nestedDir)).toBe(false);
|
|
57
|
+
const r = acquireLock(nestedLock);
|
|
58
|
+
expect(r.acquired).toBe(true);
|
|
59
|
+
expect(fs.existsSync(nestedLock)).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
describe('acquireLock — stale recovery', () => {
|
|
63
|
+
it('treats empty file as stale and retries', () => {
|
|
64
|
+
fs.writeFileSync(tmpLockPath, '');
|
|
65
|
+
const r = acquireLock(tmpLockPath);
|
|
66
|
+
expect(r.acquired).toBe(true);
|
|
67
|
+
const content = readLockFile(tmpLockPath);
|
|
68
|
+
expect(content.pid).toBe(process.pid);
|
|
69
|
+
});
|
|
70
|
+
it('treats parse-error file as stale and retries', () => {
|
|
71
|
+
fs.writeFileSync(tmpLockPath, 'not valid json {{{ broken');
|
|
72
|
+
const r = acquireLock(tmpLockPath);
|
|
73
|
+
expect(r.acquired).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
it('treats shape-mismatch file as stale and retries', () => {
|
|
76
|
+
fs.writeFileSync(tmpLockPath, JSON.stringify({ wrong: 'shape' }));
|
|
77
|
+
const r = acquireLock(tmpLockPath);
|
|
78
|
+
expect(r.acquired).toBe(true);
|
|
79
|
+
});
|
|
80
|
+
it('treats dead-pid lock as stale and retries', () => {
|
|
81
|
+
const deadPid = 99999999;
|
|
82
|
+
fs.writeFileSync(tmpLockPath, JSON.stringify({ pid: deadPid, startTs: Date.now() }));
|
|
83
|
+
const r = acquireLock(tmpLockPath);
|
|
84
|
+
expect(r.acquired).toBe(true);
|
|
85
|
+
});
|
|
86
|
+
it('treats lock older than STALE_LOCK_TIMEOUT_MS as stale', () => {
|
|
87
|
+
const oldTs = Date.now() - STALE_LOCK_TIMEOUT_MS - 1000;
|
|
88
|
+
fs.writeFileSync(tmpLockPath, JSON.stringify({ pid: process.pid, startTs: oldTs }));
|
|
89
|
+
const r = acquireLock(tmpLockPath);
|
|
90
|
+
expect(r.acquired).toBe(true);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
describe('acquireLock — refusal on live lock', () => {
|
|
94
|
+
it('refuses when an alive olam-process holds the lock', () => {
|
|
95
|
+
fs.writeFileSync(tmpLockPath, JSON.stringify({ pid: process.pid, startTs: Date.now() }));
|
|
96
|
+
const r = acquireLock(tmpLockPath);
|
|
97
|
+
expect(r.acquired).toBe(false);
|
|
98
|
+
if (!r.acquired) {
|
|
99
|
+
expect(r.reason).toBe('live');
|
|
100
|
+
expect(r.existingPid).toBe(process.pid);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
describe('releaseLock', () => {
|
|
105
|
+
it('removes an existing lock', () => {
|
|
106
|
+
fs.writeFileSync(tmpLockPath, JSON.stringify({ pid: process.pid, startTs: Date.now() }));
|
|
107
|
+
releaseLock(tmpLockPath);
|
|
108
|
+
expect(fs.existsSync(tmpLockPath)).toBe(false);
|
|
109
|
+
});
|
|
110
|
+
it('is idempotent (no error on missing lock)', () => {
|
|
111
|
+
expect(() => releaseLock(tmpLockPath)).not.toThrow();
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
describe('isStaleLock', () => {
|
|
115
|
+
it('null content → stale', () => {
|
|
116
|
+
expect(isStaleLock(null)).toBe(true);
|
|
117
|
+
});
|
|
118
|
+
it('content older than 30 min → stale', () => {
|
|
119
|
+
const old = Date.now() - STALE_LOCK_TIMEOUT_MS - 1;
|
|
120
|
+
expect(isStaleLock({ pid: process.pid, startTs: old })).toBe(true);
|
|
121
|
+
});
|
|
122
|
+
it('content with dead pid → stale', () => {
|
|
123
|
+
expect(isStaleLock({ pid: 99999999, startTs: Date.now() })).toBe(true);
|
|
124
|
+
});
|
|
125
|
+
it('fresh content with own pid → live (not stale)', () => {
|
|
126
|
+
expect(isStaleLock({ pid: process.pid, startTs: Date.now() })).toBe(false);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
describe('isPidAlive', () => {
|
|
130
|
+
it('returns true for own pid', () => {
|
|
131
|
+
expect(isPidAlive(process.pid)).toBe(true);
|
|
132
|
+
});
|
|
133
|
+
it('returns false for unused high pid', () => {
|
|
134
|
+
expect(isPidAlive(99999999)).toBe(false);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
describe('isOlamUpgradeCommand', () => {
|
|
138
|
+
it('accepts node', () => {
|
|
139
|
+
expect(isOlamUpgradeCommand('node')).toBe(true);
|
|
140
|
+
});
|
|
141
|
+
it('accepts /usr/local/bin/node (path-prefixed)', () => {
|
|
142
|
+
expect(isOlamUpgradeCommand('/usr/local/bin/node')).toBe(true);
|
|
143
|
+
});
|
|
144
|
+
it('accepts olam', () => {
|
|
145
|
+
expect(isOlamUpgradeCommand('olam')).toBe(true);
|
|
146
|
+
});
|
|
147
|
+
it('accepts olam-cli', () => {
|
|
148
|
+
expect(isOlamUpgradeCommand('olam-cli')).toBe(true);
|
|
149
|
+
});
|
|
150
|
+
it('accepts node with worker-pool suffix (e.g. vitest)', () => {
|
|
151
|
+
expect(isOlamUpgradeCommand('node (vitest 1)')).toBe(true);
|
|
152
|
+
expect(isOlamUpgradeCommand('node (vitest pool 3)')).toBe(true);
|
|
153
|
+
});
|
|
154
|
+
it('rejects unrelated commands', () => {
|
|
155
|
+
expect(isOlamUpgradeCommand('bash')).toBe(false);
|
|
156
|
+
expect(isOlamUpgradeCommand('zsh')).toBe(false);
|
|
157
|
+
expect(isOlamUpgradeCommand('python3')).toBe(false);
|
|
158
|
+
});
|
|
159
|
+
it('rejects null', () => {
|
|
160
|
+
expect(isOlamUpgradeCommand(null)).toBe(false);
|
|
161
|
+
});
|
|
162
|
+
it('rejects empty string', () => {
|
|
163
|
+
expect(isOlamUpgradeCommand('')).toBe(false);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
describe('formatRefusalMessage', () => {
|
|
167
|
+
it('includes pid when known', () => {
|
|
168
|
+
const msg = formatRefusalMessage({
|
|
169
|
+
acquired: false,
|
|
170
|
+
reason: 'live',
|
|
171
|
+
existingPid: 12345,
|
|
172
|
+
existingStartTs: Date.now(),
|
|
173
|
+
}, '/tmp/test.lock');
|
|
174
|
+
expect(msg).toContain('12345');
|
|
175
|
+
});
|
|
176
|
+
it('mentions --history hint and rm path', () => {
|
|
177
|
+
const msg = formatRefusalMessage({
|
|
178
|
+
acquired: false,
|
|
179
|
+
reason: 'live',
|
|
180
|
+
existingPid: 12345,
|
|
181
|
+
existingStartTs: Date.now(),
|
|
182
|
+
}, '/tmp/test.lock');
|
|
183
|
+
expect(msg).toContain('--history');
|
|
184
|
+
expect(msg).toContain('rm /tmp/test.lock');
|
|
185
|
+
});
|
|
186
|
+
it('omits pid string when not known', () => {
|
|
187
|
+
const msg = formatRefusalMessage({
|
|
188
|
+
acquired: false,
|
|
189
|
+
reason: 'race',
|
|
190
|
+
}, '/tmp/test.lock');
|
|
191
|
+
expect(msg).toContain('Upgrade in progress.');
|
|
192
|
+
expect(msg).not.toContain('pid');
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
describe('parallel acquisition (live-lock refusal)', () => {
|
|
196
|
+
it('first acquires; second refuses with existingPid populated', () => {
|
|
197
|
+
const r1 = acquireLock(tmpLockPath);
|
|
198
|
+
expect(r1.acquired).toBe(true);
|
|
199
|
+
const r2 = acquireLock(tmpLockPath);
|
|
200
|
+
expect(r2.acquired).toBe(false);
|
|
201
|
+
if (!r2.acquired) {
|
|
202
|
+
expect(r2.reason).toBe('live');
|
|
203
|
+
expect(r2.existingPid).toBe(process.pid);
|
|
204
|
+
}
|
|
205
|
+
releaseLock(tmpLockPath);
|
|
206
|
+
});
|
|
207
|
+
it('after release, a new acquire succeeds', () => {
|
|
208
|
+
const r1 = acquireLock(tmpLockPath);
|
|
209
|
+
expect(r1.acquired).toBe(true);
|
|
210
|
+
releaseLock(tmpLockPath);
|
|
211
|
+
const r2 = acquireLock(tmpLockPath);
|
|
212
|
+
expect(r2.acquired).toBe(true);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
describe('isStaleLock — ps-unavailable fail-live (audit A1-003)', () => {
|
|
216
|
+
it('treats PS_UNAVAILABLE comm as live (NOT stale) when pid is alive', () => {
|
|
217
|
+
// Simulate: process is alive but `ps` couldn't tell us its name (binary missing,
|
|
218
|
+
// container without procfs, fork pressure). Per audit A1-003, the safe default
|
|
219
|
+
// is "assume live, refuse to recover" — better than silently deleting a live lock.
|
|
220
|
+
const liveAlivePid = process.pid;
|
|
221
|
+
// We can't easily mock getPidCommand here without DI; verify isOlamUpgradeCommand
|
|
222
|
+
// returns false for PS_UNAVAILABLE so callers branching on it work correctly.
|
|
223
|
+
expect(isOlamUpgradeCommand(PS_UNAVAILABLE)).toBe(false);
|
|
224
|
+
});
|
|
225
|
+
it('exposes PS_UNAVAILABLE sentinel as a string constant', () => {
|
|
226
|
+
expect(typeof PS_UNAVAILABLE).toBe('string');
|
|
227
|
+
expect(PS_UNAVAILABLE.length).toBeGreaterThan(0);
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
describe('STALE_LOCK_TIMEOUT_MS — bounds false-refusal blast-radius (audit A1-004)', () => {
|
|
231
|
+
it('is set to 5 minutes (shortened from 30 min per audit recommendation)', () => {
|
|
232
|
+
expect(STALE_LOCK_TIMEOUT_MS).toBe(5 * 60 * 1000);
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
describe('readLockFile', () => {
|
|
236
|
+
it('returns null on missing file', () => {
|
|
237
|
+
expect(readLockFile(tmpLockPath)).toBeNull();
|
|
238
|
+
});
|
|
239
|
+
it('returns null on empty file', () => {
|
|
240
|
+
fs.writeFileSync(tmpLockPath, '');
|
|
241
|
+
expect(readLockFile(tmpLockPath)).toBeNull();
|
|
242
|
+
});
|
|
243
|
+
it('returns null on invalid JSON', () => {
|
|
244
|
+
fs.writeFileSync(tmpLockPath, 'not json');
|
|
245
|
+
expect(readLockFile(tmpLockPath)).toBeNull();
|
|
246
|
+
});
|
|
247
|
+
it('returns parsed content on valid file', () => {
|
|
248
|
+
const content = { pid: 12345, startTs: 1700000000000 };
|
|
249
|
+
fs.writeFileSync(tmpLockPath, JSON.stringify(content));
|
|
250
|
+
expect(readLockFile(tmpLockPath)).toEqual(content);
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
//# sourceMappingURL=upgrade.lock.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upgrade.lock.test.js","sourceRoot":"","sources":["../../../src/commands/__tests__/upgrade.lock.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verifies that build-{host-cp,auth,devbox}.sh accept an OLAM_TAG env var
|
|
3
|
+
* that retags the freshly-built image with an additional alias (Phase 2a — A3).
|
|
4
|
+
*
|
|
5
|
+
* The CLI's atomic-swap workflow (A6) needs all three scripts to produce
|
|
6
|
+
* `<image>:olam-next` tags during the staged-build window so canonical tags
|
|
7
|
+
* (`:latest` for host-cp/devbox, `:local` for auth) stay untouched until the
|
|
8
|
+
* final 6-tag swap.
|
|
9
|
+
*
|
|
10
|
+
* Default unset → no retag → backward-compat with manual `bash build-*.sh`
|
|
11
|
+
* invocation.
|
|
12
|
+
*
|
|
13
|
+
* This test reads each script as TEXT and asserts the retag block is present
|
|
14
|
+
* + correctly placed (after the docker build). A real docker integration test
|
|
15
|
+
* lives at tests/integration/build-scripts-olam-tag.sh (manual smoke; not run
|
|
16
|
+
* in CI without a docker daemon).
|
|
17
|
+
*
|
|
18
|
+
* Run: `cd packages/cli && npx vitest run src/commands/__tests__/upgrade.olam-tag.test.ts`
|
|
19
|
+
*/
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=upgrade.olam-tag.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upgrade.olam-tag.test.d.ts","sourceRoot":"","sources":["../../../src/commands/__tests__/upgrade.olam-tag.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verifies that build-{host-cp,auth,devbox}.sh accept an OLAM_TAG env var
|
|
3
|
+
* that retags the freshly-built image with an additional alias (Phase 2a — A3).
|
|
4
|
+
*
|
|
5
|
+
* The CLI's atomic-swap workflow (A6) needs all three scripts to produce
|
|
6
|
+
* `<image>:olam-next` tags during the staged-build window so canonical tags
|
|
7
|
+
* (`:latest` for host-cp/devbox, `:local` for auth) stay untouched until the
|
|
8
|
+
* final 6-tag swap.
|
|
9
|
+
*
|
|
10
|
+
* Default unset → no retag → backward-compat with manual `bash build-*.sh`
|
|
11
|
+
* invocation.
|
|
12
|
+
*
|
|
13
|
+
* This test reads each script as TEXT and asserts the retag block is present
|
|
14
|
+
* + correctly placed (after the docker build). A real docker integration test
|
|
15
|
+
* lives at tests/integration/build-scripts-olam-tag.sh (manual smoke; not run
|
|
16
|
+
* in CI without a docker daemon).
|
|
17
|
+
*
|
|
18
|
+
* Run: `cd packages/cli && npx vitest run src/commands/__tests__/upgrade.olam-tag.test.ts`
|
|
19
|
+
*/
|
|
20
|
+
import { describe, it, expect } from 'vitest';
|
|
21
|
+
import * as fs from 'node:fs';
|
|
22
|
+
import * as path from 'node:path';
|
|
23
|
+
// Resolve to the repo root regardless of which workspace cwd vitest runs from.
|
|
24
|
+
function findRepoRoot(start) {
|
|
25
|
+
let dir = start;
|
|
26
|
+
for (let i = 0; i < 10; i++) {
|
|
27
|
+
if (fs.existsSync(path.join(dir, 'packages', 'host-cp', 'compose.yaml')))
|
|
28
|
+
return dir;
|
|
29
|
+
const parent = path.dirname(dir);
|
|
30
|
+
if (parent === dir)
|
|
31
|
+
break;
|
|
32
|
+
dir = parent;
|
|
33
|
+
}
|
|
34
|
+
throw new Error(`Could not find repo root from ${start}`);
|
|
35
|
+
}
|
|
36
|
+
const REPO_ROOT = findRepoRoot(process.cwd());
|
|
37
|
+
const SCRIPTS_DIR = path.join(REPO_ROOT, 'packages', 'adapters', 'src', 'docker');
|
|
38
|
+
const SCRIPTS = [
|
|
39
|
+
{
|
|
40
|
+
name: 'build-host-cp.sh',
|
|
41
|
+
path: path.join(SCRIPTS_DIR, 'build-host-cp.sh'),
|
|
42
|
+
canonicalTagPattern: /olam-host-cp:latest/,
|
|
43
|
+
retagSourceVar: 'olam-host-cp:latest',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: 'build-auth.sh',
|
|
47
|
+
path: path.join(SCRIPTS_DIR, 'build-auth.sh'),
|
|
48
|
+
canonicalTagPattern: /olam-auth:local/,
|
|
49
|
+
retagSourceVar: 'olam-auth:local',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: 'build-devbox.sh',
|
|
53
|
+
path: path.join(SCRIPTS_DIR, 'build-devbox.sh'),
|
|
54
|
+
canonicalTagPattern: /IMAGE_TAG="olam-devbox:latest"/,
|
|
55
|
+
retagSourceVar: '$IMAGE_TAG',
|
|
56
|
+
},
|
|
57
|
+
];
|
|
58
|
+
describe('build-*.sh — OLAM_TAG opt-in retag (Phase 2a A3)', () => {
|
|
59
|
+
for (const script of SCRIPTS) {
|
|
60
|
+
describe(script.name, () => {
|
|
61
|
+
let body;
|
|
62
|
+
it('script file exists', () => {
|
|
63
|
+
expect(fs.existsSync(script.path)).toBe(true);
|
|
64
|
+
body = fs.readFileSync(script.path, 'utf-8');
|
|
65
|
+
expect(body.length).toBeGreaterThan(100);
|
|
66
|
+
});
|
|
67
|
+
it('preserves the canonical (default) tag for backward-compat', () => {
|
|
68
|
+
body ??= fs.readFileSync(script.path, 'utf-8');
|
|
69
|
+
expect(body).toMatch(script.canonicalTagPattern);
|
|
70
|
+
});
|
|
71
|
+
it('contains the OLAM_TAG conditional block', () => {
|
|
72
|
+
body ??= fs.readFileSync(script.path, 'utf-8');
|
|
73
|
+
// The block guards on `if [ -n "${OLAM_TAG:-}" ]; then`
|
|
74
|
+
expect(body).toMatch(/if\s*\[\s*-n\s+"\$\{OLAM_TAG:-\}"\s*\]\s*;\s*then/);
|
|
75
|
+
});
|
|
76
|
+
it('issues a docker tag command inside (or after the start of) the OLAM_TAG block', () => {
|
|
77
|
+
body ??= fs.readFileSync(script.path, 'utf-8');
|
|
78
|
+
// Find the OLAM_TAG block opening; assert `docker tag` appears
|
|
79
|
+
// somewhere after that (could be inside a nested-conditional fi
|
|
80
|
+
// for the devbox 2-axis case, so we don't try to extract the
|
|
81
|
+
// exact closing fi — that's regex-fragile).
|
|
82
|
+
const blockStart = body.search(/if\s*\[\s*-n\s+"\$\{OLAM_TAG:-\}"\s*\]/);
|
|
83
|
+
expect(blockStart).toBeGreaterThan(0);
|
|
84
|
+
const after = body.slice(blockStart);
|
|
85
|
+
expect(after).toMatch(/docker\s+tag\b/);
|
|
86
|
+
});
|
|
87
|
+
it('OLAM_TAG block appears AFTER the docker build invocation', () => {
|
|
88
|
+
body ??= fs.readFileSync(script.path, 'utf-8');
|
|
89
|
+
const buildIdx = body.search(/docker\s+(?:compose\s+(?:-f\s+\S+\s+)?)?build\b/);
|
|
90
|
+
const olamTagIdx = body.search(/if\s*\[\s*-n\s+"\$\{OLAM_TAG:-\}"\s*\]/);
|
|
91
|
+
expect(buildIdx).toBeGreaterThan(0);
|
|
92
|
+
expect(olamTagIdx).toBeGreaterThan(0);
|
|
93
|
+
expect(olamTagIdx).toBeGreaterThan(buildIdx);
|
|
94
|
+
});
|
|
95
|
+
it('retag source uses the canonical tag (backward-compat invariant)', () => {
|
|
96
|
+
body ??= fs.readFileSync(script.path, 'utf-8');
|
|
97
|
+
const blockStart = body.search(/if\s*\[\s*-n\s+"\$\{OLAM_TAG:-\}"\s*\]/);
|
|
98
|
+
expect(blockStart).toBeGreaterThan(0);
|
|
99
|
+
const after = body.slice(blockStart);
|
|
100
|
+
// Source of `docker tag <source> <dest>` must reference the canonical
|
|
101
|
+
// tag (or its variable, for build-devbox.sh).
|
|
102
|
+
if (script.name === 'build-devbox.sh') {
|
|
103
|
+
expect(after).toMatch(/docker tag\s+"\$IMAGE_TAG"/);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
expect(after).toContain(script.retagSourceVar);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
describe('build-devbox.sh — OLAM_TAG × OLAM_DEVBOX_PLATFORM 2-axis composition', () => {
|
|
113
|
+
let body;
|
|
114
|
+
it('reads the file', () => {
|
|
115
|
+
body = fs.readFileSync(path.join(SCRIPTS_DIR, 'build-devbox.sh'), 'utf-8');
|
|
116
|
+
});
|
|
117
|
+
it('handles the latest case (no platform suffix → flat OLAM_TAG)', () => {
|
|
118
|
+
body ??= fs.readFileSync(path.join(SCRIPTS_DIR, 'build-devbox.sh'), 'utf-8');
|
|
119
|
+
expect(body).toMatch(/if\s*\[\s*"\$IMAGE_PLATFORM_SUFFIX"\s*=\s*"latest"\s*\]/);
|
|
120
|
+
});
|
|
121
|
+
it('composes <repo>:<platform>-<olam-tag> for non-latest platforms', () => {
|
|
122
|
+
body ??= fs.readFileSync(path.join(SCRIPTS_DIR, 'build-devbox.sh'), 'utf-8');
|
|
123
|
+
// Should include ${IMAGE_PLATFORM_SUFFIX}-${OLAM_TAG} in the else branch.
|
|
124
|
+
expect(body).toMatch(/\$\{IMAGE_PLATFORM_SUFFIX\}-\$\{OLAM_TAG\}/);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
//# sourceMappingURL=upgrade.olam-tag.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upgrade.olam-tag.test.js","sourceRoot":"","sources":["../../../src/commands/__tests__/upgrade.olam-tag.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 2a A8 — verifies waitForVersionMatch + formatVersionMismatch.
|
|
3
|
+
*
|
|
4
|
+
* Uses vi.spyOn(global, 'fetch') to mock /api/version/status responses
|
|
5
|
+
* and assert the polling loop:
|
|
6
|
+
* - Returns matched=true when all three SHAs equal target.
|
|
7
|
+
* - Returns matched=false on timeout when SHAs never converge.
|
|
8
|
+
* - Tolerates transient fetch failures (host-cp restart blackout).
|
|
9
|
+
* - Returns the LAST snapshot received for diagnostic display.
|
|
10
|
+
*
|
|
11
|
+
* Run: `cd packages/cli && npx vitest run src/commands/__tests__/upgrade.poll.test.ts`
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=upgrade.poll.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upgrade.poll.test.d.ts","sourceRoot":"","sources":["../../../src/commands/__tests__/upgrade.poll.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 2a A8 — verifies waitForVersionMatch + formatVersionMismatch.
|
|
3
|
+
*
|
|
4
|
+
* Uses vi.spyOn(global, 'fetch') to mock /api/version/status responses
|
|
5
|
+
* and assert the polling loop:
|
|
6
|
+
* - Returns matched=true when all three SHAs equal target.
|
|
7
|
+
* - Returns matched=false on timeout when SHAs never converge.
|
|
8
|
+
* - Tolerates transient fetch failures (host-cp restart blackout).
|
|
9
|
+
* - Returns the LAST snapshot received for diagnostic display.
|
|
10
|
+
*
|
|
11
|
+
* Run: `cd packages/cli && npx vitest run src/commands/__tests__/upgrade.poll.test.ts`
|
|
12
|
+
*/
|
|
13
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
14
|
+
import { waitForVersionMatch, formatVersionMismatch } from '../upgrade.js';
|
|
15
|
+
const TARGET_SHA = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
|
|
16
|
+
const OTHER_SHA = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb';
|
|
17
|
+
function makeSnapshot(hostCp, authService, devbox) {
|
|
18
|
+
return {
|
|
19
|
+
hostCp: { running: hostCp, latest: TARGET_SHA, upgradeAvailable: hostCp !== TARGET_SHA },
|
|
20
|
+
authService: { running: authService, latest: TARGET_SHA, upgradeAvailable: authService !== TARGET_SHA },
|
|
21
|
+
devbox: { running: devbox, latest: TARGET_SHA, upgradeAvailable: devbox !== TARGET_SHA },
|
|
22
|
+
operatorHead: TARGET_SHA,
|
|
23
|
+
checkedAt: '2026-05-07T00:00:00.000Z',
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function mockFetchResponse(snapshot) {
|
|
27
|
+
return new Response(JSON.stringify(snapshot), {
|
|
28
|
+
status: 200,
|
|
29
|
+
headers: { 'content-type': 'application/json' },
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
describe('waitForVersionMatch — happy path', () => {
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
34
|
+
let fetchSpy;
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
fetchSpy = vi.spyOn(global, 'fetch');
|
|
37
|
+
});
|
|
38
|
+
afterEach(() => {
|
|
39
|
+
fetchSpy.mockRestore();
|
|
40
|
+
});
|
|
41
|
+
it('returns matched=true when all three SHAs already equal target on first poll', async () => {
|
|
42
|
+
const snapshot = makeSnapshot(TARGET_SHA, TARGET_SHA, TARGET_SHA);
|
|
43
|
+
fetchSpy.mockResolvedValue(mockFetchResponse(snapshot));
|
|
44
|
+
const result = await waitForVersionMatch(TARGET_SHA, 5_000, 100);
|
|
45
|
+
expect(result.matched).toBe(true);
|
|
46
|
+
expect(result.snapshot).toEqual(snapshot);
|
|
47
|
+
});
|
|
48
|
+
it('returns matched=true after SHA propagation completes mid-poll', async () => {
|
|
49
|
+
const stale = makeSnapshot(OTHER_SHA, OTHER_SHA, OTHER_SHA);
|
|
50
|
+
const partial = makeSnapshot(TARGET_SHA, OTHER_SHA, OTHER_SHA);
|
|
51
|
+
const fresh = makeSnapshot(TARGET_SHA, TARGET_SHA, TARGET_SHA);
|
|
52
|
+
fetchSpy
|
|
53
|
+
.mockResolvedValueOnce(mockFetchResponse(stale))
|
|
54
|
+
.mockResolvedValueOnce(mockFetchResponse(partial))
|
|
55
|
+
.mockResolvedValueOnce(mockFetchResponse(fresh));
|
|
56
|
+
const result = await waitForVersionMatch(TARGET_SHA, 10_000, 50);
|
|
57
|
+
expect(result.matched).toBe(true);
|
|
58
|
+
expect(result.snapshot).toEqual(fresh);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
describe('waitForVersionMatch — failure paths', () => {
|
|
62
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
63
|
+
let fetchSpy;
|
|
64
|
+
beforeEach(() => {
|
|
65
|
+
fetchSpy = vi.spyOn(global, 'fetch');
|
|
66
|
+
});
|
|
67
|
+
afterEach(() => {
|
|
68
|
+
fetchSpy.mockRestore();
|
|
69
|
+
});
|
|
70
|
+
it('returns matched=false on timeout when SHAs never converge', async () => {
|
|
71
|
+
const stale = makeSnapshot(OTHER_SHA, OTHER_SHA, OTHER_SHA);
|
|
72
|
+
fetchSpy.mockResolvedValue(mockFetchResponse(stale));
|
|
73
|
+
const result = await waitForVersionMatch(TARGET_SHA, 300, 50);
|
|
74
|
+
expect(result.matched).toBe(false);
|
|
75
|
+
expect(result.snapshot).toEqual(stale);
|
|
76
|
+
});
|
|
77
|
+
it('tolerates transient fetch errors (host-cp restart blackout)', async () => {
|
|
78
|
+
const fresh = makeSnapshot(TARGET_SHA, TARGET_SHA, TARGET_SHA);
|
|
79
|
+
fetchSpy
|
|
80
|
+
.mockRejectedValueOnce(new Error('ECONNREFUSED'))
|
|
81
|
+
.mockRejectedValueOnce(new Error('ECONNREFUSED'))
|
|
82
|
+
.mockResolvedValueOnce(mockFetchResponse(fresh));
|
|
83
|
+
const result = await waitForVersionMatch(TARGET_SHA, 5_000, 50);
|
|
84
|
+
expect(result.matched).toBe(true);
|
|
85
|
+
expect(result.snapshot).toEqual(fresh);
|
|
86
|
+
});
|
|
87
|
+
it('returns matched=false with snapshot=null when /api/version/status is never reachable', async () => {
|
|
88
|
+
fetchSpy.mockRejectedValue(new Error('ECONNREFUSED'));
|
|
89
|
+
const result = await waitForVersionMatch(TARGET_SHA, 200, 50);
|
|
90
|
+
expect(result.matched).toBe(false);
|
|
91
|
+
expect(result.snapshot).toBeNull();
|
|
92
|
+
});
|
|
93
|
+
it('returns matched=false when /api/version/status returns non-200', async () => {
|
|
94
|
+
fetchSpy.mockResolvedValue(new Response('Internal Server Error', { status: 500 }));
|
|
95
|
+
const result = await waitForVersionMatch(TARGET_SHA, 200, 50);
|
|
96
|
+
expect(result.matched).toBe(false);
|
|
97
|
+
expect(result.snapshot).toBeNull();
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
describe('formatVersionMismatch', () => {
|
|
101
|
+
it('renders 3-line per-component diff with ✓ / ✗ markers', () => {
|
|
102
|
+
const partial = makeSnapshot(TARGET_SHA, OTHER_SHA, OTHER_SHA);
|
|
103
|
+
const out = formatVersionMismatch(TARGET_SHA, partial);
|
|
104
|
+
expect(out).toMatch(/✓ host-cp/);
|
|
105
|
+
expect(out).toMatch(/✗ auth-service/);
|
|
106
|
+
expect(out).toMatch(/✗ devbox/);
|
|
107
|
+
expect(out).toContain(TARGET_SHA.slice(0, 8));
|
|
108
|
+
expect(out).toContain(OTHER_SHA.slice(0, 8));
|
|
109
|
+
});
|
|
110
|
+
it('handles null snapshot gracefully', () => {
|
|
111
|
+
expect(formatVersionMismatch(TARGET_SHA, null)).toMatch(/No \/api\/version\/status response/);
|
|
112
|
+
});
|
|
113
|
+
it('marks all components ✗ when none match target', () => {
|
|
114
|
+
const stale = makeSnapshot(OTHER_SHA, OTHER_SHA, OTHER_SHA);
|
|
115
|
+
const out = formatVersionMismatch(TARGET_SHA, stale);
|
|
116
|
+
expect((out.match(/✗/g) ?? []).length).toBe(3);
|
|
117
|
+
expect(out.match(/✓/g)).toBeNull();
|
|
118
|
+
});
|
|
119
|
+
it('marks all components ✓ when all match target', () => {
|
|
120
|
+
const fresh = makeSnapshot(TARGET_SHA, TARGET_SHA, TARGET_SHA);
|
|
121
|
+
const out = formatVersionMismatch(TARGET_SHA, fresh);
|
|
122
|
+
expect((out.match(/✓/g) ?? []).length).toBe(3);
|
|
123
|
+
expect(out.match(/✗/g)).toBeNull();
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
describe('VersionSnapshot type contract', () => {
|
|
127
|
+
it('exports a usable snapshot shape with three components + operatorHead + checkedAt', () => {
|
|
128
|
+
const sample = makeSnapshot(TARGET_SHA, TARGET_SHA, TARGET_SHA);
|
|
129
|
+
expect(sample.hostCp.running).toBe(TARGET_SHA);
|
|
130
|
+
expect(sample.authService.running).toBe(TARGET_SHA);
|
|
131
|
+
expect(sample.devbox.running).toBe(TARGET_SHA);
|
|
132
|
+
expect(sample.operatorHead).toBe(TARGET_SHA);
|
|
133
|
+
expect(sample.checkedAt).toBeTruthy();
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
//# sourceMappingURL=upgrade.poll.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upgrade.poll.test.js","sourceRoot":"","sources":["../../../src/commands/__tests__/upgrade.poll.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 2a A7 — verifies auth-service recreate via AuthContainerController.
|
|
3
|
+
*
|
|
4
|
+
* Source-text invariants (no docker required):
|
|
5
|
+
* - upgrade.ts imports AuthContainerController from @olam/core
|
|
6
|
+
* - recreateAuthService is invoked AFTER the compose recreate succeeds
|
|
7
|
+
* - auth-recreate-fail branch surfaces --rollback hint (per audit A6-002)
|
|
8
|
+
* - waitForAuthHealthLocal mirrors auth-upgrade.ts contract
|
|
9
|
+
*
|
|
10
|
+
* Running the actual recreateAuthService against a real auth-service
|
|
11
|
+
* container is covered by the manual smoke at tests/integration/upgrade-all-three.sh
|
|
12
|
+
* — too heavy for unit tests (requires the full olam-auth image + secret env).
|
|
13
|
+
*
|
|
14
|
+
* Run: `cd packages/cli && npx vitest run src/commands/__tests__/upgrade.recreate.test.ts`
|
|
15
|
+
*/
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=upgrade.recreate.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upgrade.recreate.test.d.ts","sourceRoot":"","sources":["../../../src/commands/__tests__/upgrade.recreate.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG"}
|