@invarn/cibuild 1.4.9 → 1.5.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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/cache.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAkBxD;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,4HAA4H;IAC5H,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sHAAsH;IACtH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,yGAAyG;IACzG,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAUrD,CAAC;
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/cache.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAkBxD;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,4HAA4H;IAC5H,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sHAAsH;IACtH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,yGAAyG;IACzG,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAUrD,CAAC;AA8DF;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC;AAED;;;GAGG;AACH,qBAAa,qBAAsB,SAAQ,gBAAgB;IACnD,OAAO,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;YAsGzF,iBAAiB;CAkKhC;AAED;;;GAGG;AACH,qBAAa,qBAAsB,SAAQ,gBAAgB;IACnD,OAAO,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;YA4JzF,iBAAiB;CA+GhC"}
|
|
@@ -28,9 +28,22 @@ export const CACHE_PRESETS = {
|
|
|
28
28
|
yarn: { lockfile: 'yarn.lock', keyPrefix: 'yarn', paths: ['node_modules'] },
|
|
29
29
|
dart: { lockfile: 'pubspec.lock', keyPrefix: 'dart', paths: ['.dart_tool', '.pub-cache'] },
|
|
30
30
|
};
|
|
31
|
+
/**
|
|
32
|
+
* Emits bash that derives a stable short project id from the git remote URL
|
|
33
|
+
* (with embedded credentials stripped, so token rotations don't churn the key).
|
|
34
|
+
* Sets $__ci_proj to an 8-char hex. Falls back to "no-remote" if no git remote.
|
|
35
|
+
*/
|
|
36
|
+
function emitProjectScopeCommands() {
|
|
37
|
+
return [
|
|
38
|
+
'__ci_git_remote=$(git config --get remote.origin.url 2>/dev/null | sed -E "s#(://)[^@/]+@#\\1#" || echo "no-remote")',
|
|
39
|
+
'__ci_proj=$(printf "%s" "${__ci_git_remote:-no-remote}" | shasum -a 256 | cut -c1-8)',
|
|
40
|
+
];
|
|
41
|
+
}
|
|
31
42
|
/**
|
|
32
43
|
* Emits bash that discovers fingerprint filenames across the repo, sorts them,
|
|
33
44
|
* and hashes their concatenated names+contents into CACHE_KEY.
|
|
45
|
+
* Key format: <keyPrefix>-<projectId>-<fingerprint>, so tarballs are scoped
|
|
46
|
+
* per-repo and fallback globbing can't cross-contaminate between projects.
|
|
34
47
|
* Used by presets (Gradle, KMM) where no single lockfile captures the build graph.
|
|
35
48
|
*/
|
|
36
49
|
function emitFingerprintKeyCommands(preset) {
|
|
@@ -48,10 +61,10 @@ function emitFingerprintKeyCommands(preset) {
|
|
|
48
61
|
` 2>/dev/null | LC_ALL=C sort)`,
|
|
49
62
|
`if [ -n "$__ci_fp_files" ]; then`,
|
|
50
63
|
` CHECKSUM=$(echo "$__ci_fp_files" | xargs shasum -a 256 2>/dev/null | shasum -a 256 | cut -c1-16)`,
|
|
51
|
-
` CACHE_KEY="${escape(preset.keyPrefix)}-\${CHECKSUM}"`,
|
|
64
|
+
` CACHE_KEY="${escape(preset.keyPrefix)}-\${__ci_proj}-\${CHECKSUM}"`,
|
|
52
65
|
`else`,
|
|
53
66
|
` echo "Warning: no fingerprint files found, using fallback cache key"`,
|
|
54
|
-
` CACHE_KEY="${escape(preset.keyPrefix)}-no-fingerprint"`,
|
|
67
|
+
` CACHE_KEY="${escape(preset.keyPrefix)}-\${__ci_proj}-no-fingerprint"`,
|
|
55
68
|
`fi`,
|
|
56
69
|
];
|
|
57
70
|
}
|
|
@@ -181,6 +194,11 @@ export class CachePullStepExecutor extends BaseStepExecutor {
|
|
|
181
194
|
commands.push(' find . -name "$(basename "$1")" -not -path "*/DerivedData/*" -not -path "*/.build/*" 2>/dev/null | head -1');
|
|
182
195
|
commands.push('}');
|
|
183
196
|
commands.push('');
|
|
197
|
+
// Project scope: include a stable short hash of the git remote in every
|
|
198
|
+
// cache key so different repos on the same runner don't share tarballs
|
|
199
|
+
// (prevents fallback cross-contamination across projects).
|
|
200
|
+
commands.push(...emitProjectScopeCommands());
|
|
201
|
+
commands.push('');
|
|
184
202
|
if (chain.length === 1 && chain[0].fingerprint && chain[0].fingerprint.length > 0) {
|
|
185
203
|
commands.push(...emitFingerprintKeyCommands(chain[0]));
|
|
186
204
|
}
|
|
@@ -189,10 +207,10 @@ export class CachePullStepExecutor extends BaseStepExecutor {
|
|
|
189
207
|
commands.push(`LOCKFILE=$(__ci_find_lockfile "${this.escapeBash(preset.lockfile)}")`);
|
|
190
208
|
commands.push('if [ -n "$LOCKFILE" ] && [ -f "$LOCKFILE" ]; then');
|
|
191
209
|
commands.push(` CHECKSUM=$(shasum -a 256 "$LOCKFILE" | cut -d ' ' -f1 | head -c 16)`);
|
|
192
|
-
commands.push(` CACHE_KEY="${this.escapeBash(preset.keyPrefix)}-\${CHECKSUM}"`);
|
|
210
|
+
commands.push(` CACHE_KEY="${this.escapeBash(preset.keyPrefix)}-\${__ci_proj}-\${CHECKSUM}"`);
|
|
193
211
|
commands.push('else');
|
|
194
212
|
commands.push(` echo "Warning: ${this.escapeBash(preset.lockfile)} not found, using fallback cache key"`);
|
|
195
|
-
commands.push(` CACHE_KEY="${this.escapeBash(preset.keyPrefix)}-no-lockfile"`);
|
|
213
|
+
commands.push(` CACHE_KEY="${this.escapeBash(preset.keyPrefix)}-\${__ci_proj}-no-lockfile"`);
|
|
196
214
|
commands.push('fi');
|
|
197
215
|
}
|
|
198
216
|
else {
|
|
@@ -204,14 +222,14 @@ export class CachePullStepExecutor extends BaseStepExecutor {
|
|
|
204
222
|
commands.push(`${cond} [ -n "$__ci_lockfile_candidate" ] && [ -f "$__ci_lockfile_candidate" ]; then`);
|
|
205
223
|
commands.push(' LOCKFILE="$__ci_lockfile_candidate"');
|
|
206
224
|
commands.push(` CHECKSUM=$(shasum -a 256 "$LOCKFILE" | cut -d ' ' -f1 | head -c 16)`);
|
|
207
|
-
commands.push(` CACHE_KEY="${this.escapeBash(preset.keyPrefix)}-\${CHECKSUM}"`);
|
|
225
|
+
commands.push(` CACHE_KEY="${this.escapeBash(preset.keyPrefix)}-\${__ci_proj}-\${CHECKSUM}"`);
|
|
208
226
|
if (isDebugMode) {
|
|
209
227
|
commands.push(` echo "Detected: ${this.escapeBash(preset.keyPrefix)} (lockfile: $LOCKFILE)"`);
|
|
210
228
|
}
|
|
211
229
|
}
|
|
212
230
|
commands.push('else');
|
|
213
231
|
commands.push(` echo "Warning: no lockfile found, using fallback cache key"`);
|
|
214
|
-
commands.push(` CACHE_KEY="${this.escapeBash(chain[0].keyPrefix)}-no-lockfile"`);
|
|
232
|
+
commands.push(` CACHE_KEY="${this.escapeBash(chain[0].keyPrefix)}-\${__ci_proj}-no-lockfile"`);
|
|
215
233
|
commands.push('fi');
|
|
216
234
|
}
|
|
217
235
|
commands.push('');
|
|
@@ -247,10 +265,40 @@ export class CachePullStepExecutor extends BaseStepExecutor {
|
|
|
247
265
|
commands.push(' mv "$CACHE_FILE.tmp" "$CACHE_FILE"');
|
|
248
266
|
commands.push(' echo "CACHE_SOURCE=peer CACHE_KEY=$CACHE_KEY"');
|
|
249
267
|
}
|
|
250
|
-
// 3.
|
|
268
|
+
// 3. Fallback — no exact-key tarball, try the newest prior tarball scoped
|
|
269
|
+
// to the same <keyPrefix>-<projectId> (so fallback stays within this repo
|
|
270
|
+
// and doesn't pick up a different KMM project's cache). Gradle will
|
|
271
|
+
// re-hash task inputs on top of the warm ~/.gradle/caches directory,
|
|
272
|
+
// hitting the unchanged tasks and recompiling only what actually changed.
|
|
273
|
+
// Deliberately does NOT warm $CACHE_FILE — that way cache-push still runs
|
|
274
|
+
// and creates a fresh tarball under the new key for future exact hits.
|
|
251
275
|
commands.push('else');
|
|
252
|
-
commands.push('
|
|
253
|
-
commands.push('
|
|
276
|
+
commands.push(' __ci_fb_scope="${CACHE_KEY%-*}"');
|
|
277
|
+
commands.push(' __ci_fb=$(ls -t "$CACHE_DIR"/"$__ci_fb_scope"-*.tar.zst 2>/dev/null | head -1)');
|
|
278
|
+
if (peerCacheDir) {
|
|
279
|
+
commands.push(' if [ -z "$__ci_fb" ] || [ ! -f "$__ci_fb" ]; then');
|
|
280
|
+
commands.push(' __ci_fb=$(ls -t "$PEER_CACHE_DIR"/"$__ci_fb_scope"-*.tar.zst 2>/dev/null | head -1)');
|
|
281
|
+
commands.push(' __ci_fb_src=peer');
|
|
282
|
+
commands.push(' else');
|
|
283
|
+
commands.push(' __ci_fb_src=local');
|
|
284
|
+
commands.push(' fi');
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
commands.push(' __ci_fb_src=local');
|
|
288
|
+
}
|
|
289
|
+
commands.push(' if [ -n "$__ci_fb" ] && [ -f "$__ci_fb" ]; then');
|
|
290
|
+
commands.push(' __ci_fb_key=$(basename "$__ci_fb" .tar.zst)');
|
|
291
|
+
commands.push(' echo "Cache fallback ($__ci_fb_src): using prior tarball $__ci_fb_key"');
|
|
292
|
+
if (isDebugMode) {
|
|
293
|
+
commands.push(' echo "Fallback size: $(du -h "$__ci_fb" | cut -f1)"');
|
|
294
|
+
}
|
|
295
|
+
commands.push(' zstd -dc "$__ci_fb" | tar -xf - -C /');
|
|
296
|
+
commands.push(' echo "CACHE_SOURCE=fallback_$__ci_fb_src CACHE_KEY=$CACHE_KEY FALLBACK_KEY=$__ci_fb_key"');
|
|
297
|
+
commands.push(' else');
|
|
298
|
+
// 4. Cold miss — no exact key, no fallback
|
|
299
|
+
commands.push(' echo "No cache found for key: $CACHE_KEY"');
|
|
300
|
+
commands.push(' echo "CACHE_SOURCE=cold CACHE_KEY=$CACHE_KEY"');
|
|
301
|
+
commands.push(' fi');
|
|
254
302
|
commands.push('fi');
|
|
255
303
|
if (isDebugMode) {
|
|
256
304
|
commands.push('echo "Restored paths:"');
|
|
@@ -386,20 +434,9 @@ export class CachePushStepExecutor extends BaseStepExecutor {
|
|
|
386
434
|
commands.push(' echo "Paths to cache:"');
|
|
387
435
|
commands.push(' printf " %s\\n" "${PATHS_TO_CACHE[@]}"');
|
|
388
436
|
}
|
|
389
|
-
//
|
|
390
|
-
|
|
391
|
-
commands.push(' __ci_lock_tries=0');
|
|
392
|
-
commands.push(' while ! shlock -f "$LOCK_FILE" -p $$; do');
|
|
393
|
-
commands.push(' __ci_lock_tries=$((__ci_lock_tries + 1))');
|
|
394
|
-
commands.push(' if [ "$__ci_lock_tries" -ge 30 ]; then');
|
|
395
|
-
commands.push(' echo "Warning: cache push lock timeout, skipping"');
|
|
396
|
-
commands.push(' rm -f "$LOCK_FILE"');
|
|
397
|
-
commands.push(' exit 0');
|
|
398
|
-
commands.push(' fi');
|
|
399
|
-
commands.push(' sleep 1');
|
|
400
|
-
commands.push(' done');
|
|
437
|
+
// Atomic write: .tmp + rename. No lock needed — runs are serialized per runner,
|
|
438
|
+
// and cross-writer races resolve to last-writer-wins without corruption.
|
|
401
439
|
commands.push(' tar -cf - "${PATHS_TO_CACHE[@]}" 2>/dev/null | zstd -3 > "$CACHE_FILE.tmp" && mv "$CACHE_FILE.tmp" "$CACHE_FILE" || true');
|
|
402
|
-
commands.push(' rm -f "$LOCK_FILE"');
|
|
403
440
|
commands.push(' if [ -f "$CACHE_FILE" ]; then');
|
|
404
441
|
commands.push(' echo "Cache created successfully"');
|
|
405
442
|
if (isDebugMode) {
|
|
@@ -435,6 +472,10 @@ export class CachePushStepExecutor extends BaseStepExecutor {
|
|
|
435
472
|
commands.push(' find . -name "$(basename "$1")" -not -path "*/DerivedData/*" -not -path "*/.build/*" 2>/dev/null | head -1');
|
|
436
473
|
commands.push('}');
|
|
437
474
|
commands.push('');
|
|
475
|
+
// Project scope: include a stable short hash of the git remote in every
|
|
476
|
+
// cache key so different repos on the same runner don't share tarballs.
|
|
477
|
+
commands.push(...emitProjectScopeCommands());
|
|
478
|
+
commands.push('');
|
|
438
479
|
if (chain.length === 1 && chain[0].fingerprint && chain[0].fingerprint.length > 0) {
|
|
439
480
|
commands.push(...emitFingerprintKeyCommands(chain[0]));
|
|
440
481
|
}
|
|
@@ -443,10 +484,10 @@ export class CachePushStepExecutor extends BaseStepExecutor {
|
|
|
443
484
|
commands.push(`LOCKFILE=$(__ci_find_lockfile "${this.escapeBash(preset.lockfile)}")`);
|
|
444
485
|
commands.push('if [ -n "$LOCKFILE" ] && [ -f "$LOCKFILE" ]; then');
|
|
445
486
|
commands.push(` CHECKSUM=$(shasum -a 256 "$LOCKFILE" | cut -d ' ' -f1 | head -c 16)`);
|
|
446
|
-
commands.push(` CACHE_KEY="${this.escapeBash(preset.keyPrefix)}-\${CHECKSUM}"`);
|
|
487
|
+
commands.push(` CACHE_KEY="${this.escapeBash(preset.keyPrefix)}-\${__ci_proj}-\${CHECKSUM}"`);
|
|
447
488
|
commands.push('else');
|
|
448
489
|
commands.push(` echo "Warning: ${this.escapeBash(preset.lockfile)} not found, using fallback cache key"`);
|
|
449
|
-
commands.push(` CACHE_KEY="${this.escapeBash(preset.keyPrefix)}-no-lockfile"`);
|
|
490
|
+
commands.push(` CACHE_KEY="${this.escapeBash(preset.keyPrefix)}-\${__ci_proj}-no-lockfile"`);
|
|
450
491
|
commands.push('fi');
|
|
451
492
|
}
|
|
452
493
|
else {
|
|
@@ -457,11 +498,11 @@ export class CachePushStepExecutor extends BaseStepExecutor {
|
|
|
457
498
|
commands.push(`${cond} [ -n "$__ci_lockfile_candidate" ] && [ -f "$__ci_lockfile_candidate" ]; then`);
|
|
458
499
|
commands.push(' LOCKFILE="$__ci_lockfile_candidate"');
|
|
459
500
|
commands.push(` CHECKSUM=$(shasum -a 256 "$LOCKFILE" | cut -d ' ' -f1 | head -c 16)`);
|
|
460
|
-
commands.push(` CACHE_KEY="${this.escapeBash(preset.keyPrefix)}-\${CHECKSUM}"`);
|
|
501
|
+
commands.push(` CACHE_KEY="${this.escapeBash(preset.keyPrefix)}-\${__ci_proj}-\${CHECKSUM}"`);
|
|
461
502
|
}
|
|
462
503
|
commands.push('else');
|
|
463
504
|
commands.push(` echo "Warning: no lockfile found, using fallback cache key"`);
|
|
464
|
-
commands.push(` CACHE_KEY="${this.escapeBash(chain[0].keyPrefix)}-no-lockfile"`);
|
|
505
|
+
commands.push(` CACHE_KEY="${this.escapeBash(chain[0].keyPrefix)}-\${__ci_proj}-no-lockfile"`);
|
|
465
506
|
commands.push('fi');
|
|
466
507
|
}
|
|
467
508
|
commands.push('');
|
|
@@ -493,21 +534,10 @@ export class CachePushStepExecutor extends BaseStepExecutor {
|
|
|
493
534
|
commands.push(' echo "Cache already up to date for key: $CACHE_KEY"');
|
|
494
535
|
commands.push('elif [ ${#PATHS_TO_CACHE[@]} -gt 0 ]; then');
|
|
495
536
|
commands.push(' echo "Caching ${#PATHS_TO_CACHE[@]} path(s)..."');
|
|
496
|
-
//
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
commands.push(' while ! shlock -f "$LOCK_FILE" -p $$; do');
|
|
500
|
-
commands.push(' __ci_lock_tries=$((__ci_lock_tries + 1))');
|
|
501
|
-
commands.push(' if [ "$__ci_lock_tries" -ge 30 ]; then');
|
|
502
|
-
commands.push(' echo "Warning: cache push lock timeout, skipping"');
|
|
503
|
-
commands.push(' rm -f "$LOCK_FILE"');
|
|
504
|
-
commands.push(' exit 0');
|
|
505
|
-
commands.push(' fi');
|
|
506
|
-
commands.push(' sleep 1');
|
|
507
|
-
commands.push(' done');
|
|
508
|
-
// Atomic write: compress to .tmp, then mv (peers never see half-written files)
|
|
537
|
+
// Atomic write: compress to .tmp, then mv. No lock needed — runs are
|
|
538
|
+
// serialized per runner and cross-writer races resolve to last-writer-wins
|
|
539
|
+
// without corruption. shlock was flaky on the virtiofs cache mount.
|
|
509
540
|
commands.push(' tar -cf - "${PATHS_TO_CACHE[@]}" 2>/dev/null | zstd -3 > "$CACHE_FILE.tmp" && mv "$CACHE_FILE.tmp" "$CACHE_FILE" || true');
|
|
510
|
-
commands.push(' rm -f "$LOCK_FILE"');
|
|
511
541
|
commands.push(' if [ -f "$CACHE_FILE" ]; then');
|
|
512
542
|
commands.push(' echo "Cache created successfully"');
|
|
513
543
|
if (isDebugMode) {
|
|
@@ -218,6 +218,47 @@ describe('Step Implementations', () => {
|
|
|
218
218
|
expect(result.script).toContain('CACHE_SOURCE=peer');
|
|
219
219
|
expect(result.script).toContain('tee "$CACHE_FILE.tmp" < "$PEER_CACHE_FILE"');
|
|
220
220
|
});
|
|
221
|
+
test('should fall back to newest prior tarball scoped to <keyPrefix>-<projectId> on exact-key miss', async () => {
|
|
222
|
+
const executor = new CachePullStepExecutor();
|
|
223
|
+
const result = await executor.execute({ technology: 'kmm' }, {}, testConfig);
|
|
224
|
+
// Scope = everything before the last dash (prefix+projectId, not just prefix),
|
|
225
|
+
// so fallback cannot cross-contaminate between different projects
|
|
226
|
+
expect(result.script).toContain('__ci_fb_scope="${CACHE_KEY%-*}"');
|
|
227
|
+
expect(result.script).toContain('ls -t "$CACHE_DIR"/"$__ci_fb_scope"-*.tar.zst');
|
|
228
|
+
expect(result.script).toContain('CACHE_SOURCE=fallback_');
|
|
229
|
+
expect(result.script).toContain('FALLBACK_KEY=');
|
|
230
|
+
// Fallback must NOT warm $CACHE_FILE (otherwise cache-push would skip)
|
|
231
|
+
const fallbackSection = result.script.split('__ci_fb_scope=')[1] ?? '';
|
|
232
|
+
expect(fallbackSection).not.toContain('mv "$CACHE_FILE.tmp" "$CACHE_FILE"');
|
|
233
|
+
});
|
|
234
|
+
test('should try peer dir for fallback when local has no prior tarball', async () => {
|
|
235
|
+
const executor = new CachePullStepExecutor();
|
|
236
|
+
const result = await executor.execute({ technology: 'kmm' }, {}, testConfig);
|
|
237
|
+
expect(result.script).toContain('ls -t "$PEER_CACHE_DIR"/"$__ci_fb_scope"-*.tar.zst');
|
|
238
|
+
expect(result.script).toContain('__ci_fb_src=peer');
|
|
239
|
+
});
|
|
240
|
+
test('should skip peer fallback branch when peerCacheDir is not set', async () => {
|
|
241
|
+
const executor = new CachePullStepExecutor();
|
|
242
|
+
const result = await executor.execute({ technology: 'kmm' }, {}, testConfigNoPeer);
|
|
243
|
+
expect(result.script).not.toContain('ls -t "$PEER_CACHE_DIR"');
|
|
244
|
+
// Local fallback still available
|
|
245
|
+
expect(result.script).toContain('ls -t "$CACHE_DIR"/"$__ci_fb_scope"-*.tar.zst');
|
|
246
|
+
});
|
|
247
|
+
test('should include project-scope id ($__ci_proj) in cache key', async () => {
|
|
248
|
+
const executor = new CachePullStepExecutor();
|
|
249
|
+
const result = await executor.execute({ technology: 'kmm' }, {}, testConfig);
|
|
250
|
+
// Project id derived from stripped git remote URL, 8-char hash
|
|
251
|
+
expect(result.script).toContain('git config --get remote.origin.url');
|
|
252
|
+
expect(result.script).toContain('__ci_proj=$');
|
|
253
|
+
// Key format: <prefix>-<projid>-<fp>
|
|
254
|
+
expect(result.script).toContain('CACHE_KEY="kmm-${__ci_proj}-${CHECKSUM}"');
|
|
255
|
+
});
|
|
256
|
+
test('should include project-scope id in lockfile-based preset keys', async () => {
|
|
257
|
+
const executor = new CachePullStepExecutor();
|
|
258
|
+
const result = await executor.execute({ technology: 'cocoapods' }, {}, testConfig);
|
|
259
|
+
expect(result.script).toContain('__ci_proj=$');
|
|
260
|
+
expect(result.script).toContain('CACHE_KEY="pods-${__ci_proj}-${CHECKSUM}"');
|
|
261
|
+
});
|
|
221
262
|
});
|
|
222
263
|
describe('CachePushStepExecutor', () => {
|
|
223
264
|
test('should generate cache push script', async () => {
|
|
@@ -310,21 +351,18 @@ describe('Step Implementations', () => {
|
|
|
310
351
|
const executor = new CachePushStepExecutor();
|
|
311
352
|
await expect(executor.execute({ technology: 'unknown' }, {}, testConfig)).rejects.toThrow("Unknown cache technology 'unknown'");
|
|
312
353
|
});
|
|
313
|
-
test('should use shlock
|
|
354
|
+
test('should not use shlock (flaky on virtiofs cache mount) for preset push', async () => {
|
|
314
355
|
const executor = new CachePushStepExecutor();
|
|
315
356
|
const result = await executor.execute({ technology: 'cocoapods' }, {}, testConfig);
|
|
316
|
-
expect(result.script).toContain('shlock
|
|
317
|
-
expect(result.script).toContain('LOCK_FILE
|
|
318
|
-
expect(result.script).toContain('
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
expect(result.script).toContain('-ge 30');
|
|
322
|
-
});
|
|
323
|
-
test('should use shlock locking for manual cache_key push', async () => {
|
|
357
|
+
expect(result.script).not.toContain('shlock');
|
|
358
|
+
expect(result.script).not.toContain('LOCK_FILE');
|
|
359
|
+
expect(result.script).not.toContain('__ci_lock_tries');
|
|
360
|
+
});
|
|
361
|
+
test('should not use shlock for manual cache_key push', async () => {
|
|
324
362
|
const executor = new CachePushStepExecutor();
|
|
325
363
|
const result = await executor.execute({ cache_key: 'my-key', cache_paths: ['build'] }, {}, testConfig);
|
|
326
|
-
expect(result.script).toContain('shlock
|
|
327
|
-
expect(result.script).toContain('
|
|
364
|
+
expect(result.script).not.toContain('shlock');
|
|
365
|
+
expect(result.script).not.toContain('LOCK_FILE');
|
|
328
366
|
});
|
|
329
367
|
test('should use atomic write (tmp + mv) for preset push', async () => {
|
|
330
368
|
const executor = new CachePushStepExecutor();
|
|
@@ -332,6 +370,12 @@ describe('Step Implementations', () => {
|
|
|
332
370
|
expect(result.script).toContain('> "$CACHE_FILE.tmp"');
|
|
333
371
|
expect(result.script).toContain('mv "$CACHE_FILE.tmp" "$CACHE_FILE"');
|
|
334
372
|
});
|
|
373
|
+
test('should use atomic write (tmp + mv) for manual cache_key push', async () => {
|
|
374
|
+
const executor = new CachePushStepExecutor();
|
|
375
|
+
const result = await executor.execute({ cache_key: 'my-key', cache_paths: ['build'] }, {}, testConfig);
|
|
376
|
+
expect(result.script).toContain('> "$CACHE_FILE.tmp"');
|
|
377
|
+
expect(result.script).toContain('mv "$CACHE_FILE.tmp" "$CACHE_FILE"');
|
|
378
|
+
});
|
|
335
379
|
});
|
|
336
380
|
describe('XcodeBuildStepExecutor', () => {
|
|
337
381
|
test('should generate xcodebuild script', async () => {
|