@invarn/cibuild 1.5.0 → 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:"');
|
|
@@ -424,6 +472,10 @@ export class CachePushStepExecutor extends BaseStepExecutor {
|
|
|
424
472
|
commands.push(' find . -name "$(basename "$1")" -not -path "*/DerivedData/*" -not -path "*/.build/*" 2>/dev/null | head -1');
|
|
425
473
|
commands.push('}');
|
|
426
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('');
|
|
427
479
|
if (chain.length === 1 && chain[0].fingerprint && chain[0].fingerprint.length > 0) {
|
|
428
480
|
commands.push(...emitFingerprintKeyCommands(chain[0]));
|
|
429
481
|
}
|
|
@@ -432,10 +484,10 @@ export class CachePushStepExecutor extends BaseStepExecutor {
|
|
|
432
484
|
commands.push(`LOCKFILE=$(__ci_find_lockfile "${this.escapeBash(preset.lockfile)}")`);
|
|
433
485
|
commands.push('if [ -n "$LOCKFILE" ] && [ -f "$LOCKFILE" ]; then');
|
|
434
486
|
commands.push(` CHECKSUM=$(shasum -a 256 "$LOCKFILE" | cut -d ' ' -f1 | head -c 16)`);
|
|
435
|
-
commands.push(` CACHE_KEY="${this.escapeBash(preset.keyPrefix)}-\${CHECKSUM}"`);
|
|
487
|
+
commands.push(` CACHE_KEY="${this.escapeBash(preset.keyPrefix)}-\${__ci_proj}-\${CHECKSUM}"`);
|
|
436
488
|
commands.push('else');
|
|
437
489
|
commands.push(` echo "Warning: ${this.escapeBash(preset.lockfile)} not found, using fallback cache key"`);
|
|
438
|
-
commands.push(` CACHE_KEY="${this.escapeBash(preset.keyPrefix)}-no-lockfile"`);
|
|
490
|
+
commands.push(` CACHE_KEY="${this.escapeBash(preset.keyPrefix)}-\${__ci_proj}-no-lockfile"`);
|
|
439
491
|
commands.push('fi');
|
|
440
492
|
}
|
|
441
493
|
else {
|
|
@@ -446,11 +498,11 @@ export class CachePushStepExecutor extends BaseStepExecutor {
|
|
|
446
498
|
commands.push(`${cond} [ -n "$__ci_lockfile_candidate" ] && [ -f "$__ci_lockfile_candidate" ]; then`);
|
|
447
499
|
commands.push(' LOCKFILE="$__ci_lockfile_candidate"');
|
|
448
500
|
commands.push(` CHECKSUM=$(shasum -a 256 "$LOCKFILE" | cut -d ' ' -f1 | head -c 16)`);
|
|
449
|
-
commands.push(` CACHE_KEY="${this.escapeBash(preset.keyPrefix)}-\${CHECKSUM}"`);
|
|
501
|
+
commands.push(` CACHE_KEY="${this.escapeBash(preset.keyPrefix)}-\${__ci_proj}-\${CHECKSUM}"`);
|
|
450
502
|
}
|
|
451
503
|
commands.push('else');
|
|
452
504
|
commands.push(` echo "Warning: no lockfile found, using fallback cache key"`);
|
|
453
|
-
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"`);
|
|
454
506
|
commands.push('fi');
|
|
455
507
|
}
|
|
456
508
|
commands.push('');
|
|
@@ -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 () => {
|