@invarn/cibuild 1.5.1 → 1.5.2

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;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"}
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;CA2KhC;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;CA2HhC"}
@@ -288,14 +288,23 @@ export class CachePullStepExecutor extends BaseStepExecutor {
288
288
  }
289
289
  commands.push(' if [ -n "$__ci_fb" ] && [ -f "$__ci_fb" ]; then');
290
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"');
291
+ // Age cap: a fallback older than 30d likely carries obsolete .konan
292
+ // installs or Gradle entries from a pre-wrapper-bump world. Go cold
293
+ // rather than seed from ancient state. Exact-key hits are unaffected.
294
+ commands.push(' __ci_fb_age_days=$(( ($(date +%s) - $(stat -f %m "$__ci_fb")) / 86400 ))');
295
+ commands.push(' if [ "$__ci_fb_age_days" -le 30 ]; then');
296
+ commands.push(' echo "Cache fallback ($__ci_fb_src): using prior tarball $__ci_fb_key (${__ci_fb_age_days}d old)"');
292
297
  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"');
298
+ commands.push(' echo "Fallback size: $(du -h "$__ci_fb" | cut -f1)"');
299
+ }
300
+ commands.push(' zstd -dc "$__ci_fb" | tar -xf - -C /');
301
+ commands.push(' echo "CACHE_SOURCE=fallback_$__ci_fb_src CACHE_KEY=$CACHE_KEY FALLBACK_KEY=$__ci_fb_key"');
302
+ commands.push(' else');
303
+ commands.push(' echo "Cache fallback candidate $__ci_fb_key is ${__ci_fb_age_days}d old, exceeds 30d cap — going cold"');
304
+ commands.push(' echo "CACHE_SOURCE=cold CACHE_KEY=$CACHE_KEY FALLBACK_REJECTED=$__ci_fb_key"');
305
+ commands.push(' fi');
297
306
  commands.push(' else');
298
- // 4. Cold miss — no exact key, no fallback
307
+ // 4. Cold miss — no exact key, no fallback candidate
299
308
  commands.push(' echo "No cache found for key: $CACHE_KEY"');
300
309
  commands.push(' echo "CACHE_SOURCE=cold CACHE_KEY=$CACHE_KEY"');
301
310
  commands.push(' fi');
@@ -543,6 +552,18 @@ export class CachePushStepExecutor extends BaseStepExecutor {
543
552
  if (isDebugMode) {
544
553
  commands.push(' echo "Cache size: $(du -h "$CACHE_FILE" | cut -f1)"');
545
554
  }
555
+ // Retention: keep 5 newest tarballs per <keyPrefix>-<projectId> scope,
556
+ // delete older ones to bound disk usage. Runs inline after every push;
557
+ // no cron required.
558
+ commands.push(' __ci_ret_scope="${CACHE_KEY%-*}"');
559
+ commands.push(' __ci_ret_old=$(ls -t "$CACHE_DIR"/"$__ci_ret_scope"-*.tar.zst 2>/dev/null | tail -n +6)');
560
+ commands.push(' if [ -n "$__ci_ret_old" ]; then');
561
+ commands.push(' __ci_ret_count=$(printf "%s\\n" "$__ci_ret_old" | wc -l | tr -d " ")');
562
+ commands.push(' echo "Retention: removing $__ci_ret_count older tarball(s) from scope $__ci_ret_scope"');
563
+ commands.push(' printf "%s\\n" "$__ci_ret_old" | while IFS= read -r __ci_ret_f; do');
564
+ commands.push(' [ -f "$__ci_ret_f" ] && rm -f "$__ci_ret_f"');
565
+ commands.push(' done');
566
+ commands.push(' fi');
546
567
  commands.push(' else');
547
568
  commands.push(' echo "Warning: Failed to create cache file"');
548
569
  commands.push(' fi');
@@ -259,6 +259,15 @@ describe('Step Implementations', () => {
259
259
  expect(result.script).toContain('__ci_proj=$');
260
260
  expect(result.script).toContain('CACHE_KEY="pods-${__ci_proj}-${CHECKSUM}"');
261
261
  });
262
+ test('should enforce 30-day age cap on fallback, going cold when older', async () => {
263
+ const executor = new CachePullStepExecutor();
264
+ const result = await executor.execute({ technology: 'kmm' }, {}, testConfig);
265
+ // Computes age in days from stat mtime, rejects if over 30
266
+ expect(result.script).toContain('__ci_fb_age_days=$(( ($(date +%s) - $(stat -f %m "$__ci_fb")) / 86400 ))');
267
+ expect(result.script).toContain('if [ "$__ci_fb_age_days" -le 30 ]');
268
+ expect(result.script).toContain('exceeds 30d cap — going cold');
269
+ expect(result.script).toContain('FALLBACK_REJECTED=');
270
+ });
262
271
  });
263
272
  describe('CachePushStepExecutor', () => {
264
273
  test('should generate cache push script', async () => {
@@ -370,6 +379,21 @@ describe('Step Implementations', () => {
370
379
  expect(result.script).toContain('> "$CACHE_FILE.tmp"');
371
380
  expect(result.script).toContain('mv "$CACHE_FILE.tmp" "$CACHE_FILE"');
372
381
  });
382
+ test('should sweep retention (keep 5 newest per scope) after successful push', async () => {
383
+ const executor = new CachePushStepExecutor();
384
+ const result = await executor.execute({ technology: 'kmm' }, {}, testConfig);
385
+ // Scope = prefix + projectId; retention globs that scope and keeps 5 newest
386
+ expect(result.script).toContain('__ci_ret_scope="${CACHE_KEY%-*}"');
387
+ expect(result.script).toContain('ls -t "$CACHE_DIR"/"$__ci_ret_scope"-*.tar.zst');
388
+ expect(result.script).toContain('tail -n +6');
389
+ expect(result.script).toContain('rm -f "$__ci_ret_f"');
390
+ });
391
+ test('should not run retention for manual cache_key push (no scope)', async () => {
392
+ const executor = new CachePushStepExecutor();
393
+ const result = await executor.execute({ cache_key: 'my-key', cache_paths: ['build'] }, {}, testConfig);
394
+ expect(result.script).not.toContain('__ci_ret_scope');
395
+ expect(result.script).not.toContain('tail -n +6');
396
+ });
373
397
  test('should use atomic write (tmp + mv) for manual cache_key push', async () => {
374
398
  const executor = new CachePushStepExecutor();
375
399
  const result = await executor.execute({ cache_key: 'my-key', cache_paths: ['build'] }, {}, testConfig);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@invarn/cibuild",
3
- "version": "1.5.1",
3
+ "version": "1.5.2",
4
4
  "description": "CI Build CLI — local pipeline orchestration and validation",
5
5
  "type": "module",
6
6
  "main": "dist/cli.cjs",