@invarn/cibuild 1.4.8 → 1.4.9

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.
@@ -5,11 +5,16 @@ import { BaseStepExecutor } from './base.js';
5
5
  import type { StepDef, CIConfig } from '../../types.js';
6
6
  /**
7
7
  * Built-in cache presets per technology.
8
- * When `technology` is set on a cache step, the lockfile is checksummed
9
- * at runtime to produce the cache key, and the listed paths are cached/restored.
8
+ * When `technology` is set on a cache step, either the single `lockfile` is
9
+ * checksummed, or the list of `fingerprint` filenames is discovered and
10
+ * content-hashed, to produce the cache key. The listed `paths` are then
11
+ * cached/restored under that key.
10
12
  */
11
13
  export interface CachePreset {
12
- lockfile: string;
14
+ /** Single lockfile path to checksum. Use for toolchains with authoritative lockfiles (Podfile.lock, package-lock.json…). */
15
+ lockfile?: string;
16
+ /** Filenames to discover+hash across the repo. Use when no single file captures the full build graph (Gradle/KMM). */
17
+ fingerprint?: string[];
13
18
  keyPrefix: string;
14
19
  paths: string[];
15
20
  /** Optional fallback preset name: if primary lockfile not found, use this preset's lockfile/key/paths */
@@ -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;AAExD;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,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;AAoBF;;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;CA8HhC;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;YAuKzF,iBAAiB;CAoHhC"}
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;AAgDF;;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;CA+HhC;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;YAuKzF,iBAAiB;CAqHhC"}
@@ -2,17 +2,59 @@
2
2
  * Cache step implementations (cache-pull and cache-push)
3
3
  */
4
4
  import { BaseStepExecutor } from './base.js';
5
+ /**
6
+ * Files that together define a Gradle/KMM build graph. Hashing these catches
7
+ * module additions/removals, plugin changes, and version-catalog bumps — things
8
+ * that invalidate reusability of the `~/.gradle/caches` contents across branches.
9
+ * Source file edits do NOT affect this hash; Gradle's task cache inside the
10
+ * tarball handles source-level reuse.
11
+ */
12
+ const GRADLE_FINGERPRINT = [
13
+ 'settings.gradle',
14
+ 'settings.gradle.kts',
15
+ 'build.gradle',
16
+ 'build.gradle.kts',
17
+ 'libs.versions.toml',
18
+ 'gradle-wrapper.properties',
19
+ ];
5
20
  export const CACHE_PRESETS = {
6
21
  ios: { lockfile: 'Podfile.lock', keyPrefix: 'pods', paths: ['Pods'], fallback: 'spm' },
7
22
  cocoapods: { lockfile: 'Podfile.lock', keyPrefix: 'pods', paths: ['Pods'] },
8
23
  carthage: { lockfile: 'Cartfile.resolved', keyPrefix: 'carthage', paths: ['Carthage'] },
9
24
  spm: { lockfile: 'Package.resolved', keyPrefix: 'spm', paths: ['~/Library/Developer/Xcode/DerivedData/*/SourcePackages'] },
10
- gradle: { lockfile: 'gradle/wrapper/gradle-wrapper.properties', keyPrefix: 'gradle', paths: ['~/.gradle/caches', '~/.gradle/wrapper/dists'] },
11
- kmm: { lockfile: 'gradle/wrapper/gradle-wrapper.properties', keyPrefix: 'kmm', paths: ['~/.gradle/caches', '~/.gradle/wrapper/dists', '~/.konan'] },
25
+ gradle: { fingerprint: GRADLE_FINGERPRINT, keyPrefix: 'gradle', paths: ['~/.gradle/caches', '~/.gradle/wrapper/dists'] },
26
+ kmm: { fingerprint: GRADLE_FINGERPRINT, keyPrefix: 'kmm', paths: ['~/.gradle/caches', '~/.gradle/wrapper/dists', '~/.konan'] },
12
27
  npm: { lockfile: 'package-lock.json', keyPrefix: 'npm', paths: ['node_modules'] },
13
28
  yarn: { lockfile: 'yarn.lock', keyPrefix: 'yarn', paths: ['node_modules'] },
14
29
  dart: { lockfile: 'pubspec.lock', keyPrefix: 'dart', paths: ['.dart_tool', '.pub-cache'] },
15
30
  };
31
+ /**
32
+ * Emits bash that discovers fingerprint filenames across the repo, sorts them,
33
+ * and hashes their concatenated names+contents into CACHE_KEY.
34
+ * Used by presets (Gradle, KMM) where no single lockfile captures the build graph.
35
+ */
36
+ function emitFingerprintKeyCommands(preset) {
37
+ const fp = preset.fingerprint ?? [];
38
+ const escape = (s) => s.replace(/'/g, "'\\''");
39
+ const nameArgs = fp
40
+ .map((n, i) => `${i === 0 ? '' : '-o '}-name '${escape(n)}'`)
41
+ .join(' ');
42
+ return [
43
+ `__ci_fp_files=$(find . -type f \\( ${nameArgs} \\) \\`,
44
+ ` -not -path '*/build/*' \\`,
45
+ ` -not -path '*/.gradle/*' \\`,
46
+ ` -not -path '*/node_modules/*' \\`,
47
+ ` -not -path '*/DerivedData/*' \\`,
48
+ ` 2>/dev/null | LC_ALL=C sort)`,
49
+ `if [ -n "$__ci_fp_files" ]; then`,
50
+ ` 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}"`,
52
+ `else`,
53
+ ` echo "Warning: no fingerprint files found, using fallback cache key"`,
54
+ ` CACHE_KEY="${escape(preset.keyPrefix)}-no-fingerprint"`,
55
+ `fi`,
56
+ ];
57
+ }
16
58
  /**
17
59
  * Resolves a preset with its fallback chain into a flat list of
18
60
  * {lockfile, keyPrefix, paths} entries to try in order at runtime.
@@ -139,7 +181,10 @@ export class CachePullStepExecutor extends BaseStepExecutor {
139
181
  commands.push(' find . -name "$(basename "$1")" -not -path "*/DerivedData/*" -not -path "*/.build/*" 2>/dev/null | head -1');
140
182
  commands.push('}');
141
183
  commands.push('');
142
- if (chain.length === 1) {
184
+ if (chain.length === 1 && chain[0].fingerprint && chain[0].fingerprint.length > 0) {
185
+ commands.push(...emitFingerprintKeyCommands(chain[0]));
186
+ }
187
+ else if (chain.length === 1) {
143
188
  const preset = chain[0];
144
189
  commands.push(`LOCKFILE=$(__ci_find_lockfile "${this.escapeBash(preset.lockfile)}")`);
145
190
  commands.push('if [ -n "$LOCKFILE" ] && [ -f "$LOCKFILE" ]; then');
@@ -171,7 +216,6 @@ export class CachePullStepExecutor extends BaseStepExecutor {
171
216
  }
172
217
  commands.push('');
173
218
  if (isDebugMode) {
174
- commands.push('echo "Lockfile: $LOCKFILE"');
175
219
  commands.push('echo "Cache key: $CACHE_KEY"');
176
220
  }
177
221
  commands.push('CACHE_FILE="$CACHE_DIR/$CACHE_KEY.tar.zst"');
@@ -391,7 +435,10 @@ export class CachePushStepExecutor extends BaseStepExecutor {
391
435
  commands.push(' find . -name "$(basename "$1")" -not -path "*/DerivedData/*" -not -path "*/.build/*" 2>/dev/null | head -1');
392
436
  commands.push('}');
393
437
  commands.push('');
394
- if (chain.length === 1) {
438
+ if (chain.length === 1 && chain[0].fingerprint && chain[0].fingerprint.length > 0) {
439
+ commands.push(...emitFingerprintKeyCommands(chain[0]));
440
+ }
441
+ else if (chain.length === 1) {
395
442
  const preset = chain[0];
396
443
  commands.push(`LOCKFILE=$(__ci_find_lockfile "${this.escapeBash(preset.lockfile)}")`);
397
444
  commands.push('if [ -n "$LOCKFILE" ] && [ -f "$LOCKFILE" ]; then');
@@ -419,7 +466,6 @@ export class CachePushStepExecutor extends BaseStepExecutor {
419
466
  }
420
467
  commands.push('');
421
468
  if (isDebugMode) {
422
- commands.push('echo "Lockfile: $LOCKFILE"');
423
469
  commands.push('echo "Cache key: $CACHE_KEY"');
424
470
  }
425
471
  commands.push('CACHE_FILE="$CACHE_DIR/$CACHE_KEY.tar.zst"');
@@ -138,7 +138,8 @@ describe('Step Implementations', () => {
138
138
  test('should auto-configure for gradle technology', async () => {
139
139
  const executor = new CachePullStepExecutor();
140
140
  const result = await executor.execute({ technology: 'gradle' }, {}, testConfig);
141
- expect(result.script).toContain('gradle/wrapper/gradle-wrapper.properties');
141
+ expect(result.script).toContain("-name 'gradle-wrapper.properties'");
142
+ expect(result.script).toContain("-name 'build.gradle.kts'");
142
143
  expect(result.script).toContain('gradle-');
143
144
  });
144
145
  test('should auto-configure for npm technology', async () => {
@@ -174,7 +175,10 @@ describe('Step Implementations', () => {
174
175
  test('should auto-configure for kmm technology', async () => {
175
176
  const executor = new CachePullStepExecutor();
176
177
  const result = await executor.execute({ technology: 'kmm' }, {}, testConfig);
177
- expect(result.script).toContain('gradle/wrapper/gradle-wrapper.properties');
178
+ expect(result.script).toContain("-name 'gradle-wrapper.properties'");
179
+ expect(result.script).toContain("-name 'settings.gradle'");
180
+ expect(result.script).toContain("-name 'build.gradle.kts'");
181
+ expect(result.script).toContain("-name 'libs.versions.toml'");
178
182
  expect(result.script).toContain('kmm-');
179
183
  });
180
184
  test('should use zstd compression format', async () => {
@@ -266,7 +270,8 @@ describe('Step Implementations', () => {
266
270
  test('should auto-configure for gradle technology', async () => {
267
271
  const executor = new CachePushStepExecutor();
268
272
  const result = await executor.execute({ technology: 'gradle' }, {}, testConfig);
269
- expect(result.script).toContain('gradle/wrapper/gradle-wrapper.properties');
273
+ expect(result.script).toContain("-name 'gradle-wrapper.properties'");
274
+ expect(result.script).toContain("-name 'build.gradle.kts'");
270
275
  expect(result.script).toContain('~/.gradle/caches');
271
276
  });
272
277
  test('should auto-configure for npm technology', async () => {
@@ -285,7 +290,9 @@ describe('Step Implementations', () => {
285
290
  test('should auto-configure for kmm technology', async () => {
286
291
  const executor = new CachePushStepExecutor();
287
292
  const result = await executor.execute({ technology: 'kmm' }, {}, testConfig);
288
- expect(result.script).toContain('gradle/wrapper/gradle-wrapper.properties');
293
+ expect(result.script).toContain("-name 'gradle-wrapper.properties'");
294
+ expect(result.script).toContain("-name 'settings.gradle.kts'");
295
+ expect(result.script).toContain("-name 'libs.versions.toml'");
289
296
  expect(result.script).toContain('kmm-');
290
297
  expect(result.script).toContain('~/.gradle/caches');
291
298
  expect(result.script).toContain('~/.gradle/wrapper/dists');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@invarn/cibuild",
3
- "version": "1.4.8",
3
+ "version": "1.4.9",
4
4
  "description": "CI Build CLI — local pipeline orchestration and validation",
5
5
  "type": "module",
6
6
  "main": "dist/cli.cjs",