@invarn/cibuild 1.9.6 → 1.9.8

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;AAmFF;;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;YA0GzF,iBAAiB;CA8LhC;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;YAgKzF,iBAAiB;CA4HhC"}
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;AAmFF;;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;YA0GzF,iBAAiB;CAqMhC;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;YAgKzF,iBAAiB;CAmIhC"}
@@ -237,13 +237,20 @@ export class CachePullStepExecutor extends BaseStepExecutor {
237
237
  commands.push('fi');
238
238
  }
239
239
  else {
240
- // Fallback chain: try each lockfile in order, use the first one found
240
+ // Fallback chain: probe every lockfile up front, then branch on the
241
+ // results. The probes must precede the if/elif — emitting a probe
242
+ // inside a prior preset's `then` body (the old shape) meant the
243
+ // second lockfile was only checked when the FIRST one was found, so
244
+ // an SPM project with no Podfile.lock never reached the Package.resolved
245
+ // branch and fell through to `<first>-no-lockfile`.
246
+ for (let i = 0; i < chain.length; i++) {
247
+ commands.push(`__ci_lockfile_${i}=$(__ci_find_lockfile "${this.escapeBash(chain[i].lockfile)}")`);
248
+ }
241
249
  for (let i = 0; i < chain.length; i++) {
242
250
  const preset = chain[i];
243
251
  const cond = i === 0 ? 'if' : 'elif';
244
- commands.push(`__ci_lockfile_candidate=$(__ci_find_lockfile "${this.escapeBash(preset.lockfile)}")`);
245
- commands.push(`${cond} [ -n "$__ci_lockfile_candidate" ] && [ -f "$__ci_lockfile_candidate" ]; then`);
246
- commands.push(' LOCKFILE="$__ci_lockfile_candidate"');
252
+ commands.push(`${cond} [ -n "$__ci_lockfile_${i}" ] && [ -f "$__ci_lockfile_${i}" ]; then`);
253
+ commands.push(` LOCKFILE="$__ci_lockfile_${i}"`);
247
254
  commands.push(` CHECKSUM=$(shasum -a 256 "$LOCKFILE" | cut -d ' ' -f1 | head -c 16)`);
248
255
  commands.push(` CACHE_KEY="${this.escapeBash(preset.keyPrefix)}-\${__ci_proj}-\${CHECKSUM}"`);
249
256
  if (isDebugMode) {
@@ -545,12 +552,19 @@ export class CachePushStepExecutor extends BaseStepExecutor {
545
552
  commands.push('fi');
546
553
  }
547
554
  else {
555
+ // Fallback chain: probe every lockfile up front, then branch — same
556
+ // fix as cache-pull. A probe nested in a prior preset's `then` body
557
+ // only ran when the first lockfile was found, so push wrote the
558
+ // `<first>-no-lockfile` key for SPM projects, mismatching what a
559
+ // correct pull would look for.
560
+ for (let i = 0; i < chain.length; i++) {
561
+ commands.push(`__ci_lockfile_${i}=$(__ci_find_lockfile "${this.escapeBash(chain[i].lockfile)}")`);
562
+ }
548
563
  for (let i = 0; i < chain.length; i++) {
549
564
  const preset = chain[i];
550
565
  const cond = i === 0 ? 'if' : 'elif';
551
- commands.push(`__ci_lockfile_candidate=$(__ci_find_lockfile "${this.escapeBash(preset.lockfile)}")`);
552
- commands.push(`${cond} [ -n "$__ci_lockfile_candidate" ] && [ -f "$__ci_lockfile_candidate" ]; then`);
553
- commands.push(' LOCKFILE="$__ci_lockfile_candidate"');
566
+ commands.push(`${cond} [ -n "$__ci_lockfile_${i}" ] && [ -f "$__ci_lockfile_${i}" ]; then`);
567
+ commands.push(` LOCKFILE="$__ci_lockfile_${i}"`);
554
568
  commands.push(` CHECKSUM=$(shasum -a 256 "$LOCKFILE" | cut -d ' ' -f1 | head -c 16)`);
555
569
  commands.push(` CACHE_KEY="${this.escapeBash(preset.keyPrefix)}-\${__ci_proj}-\${CHECKSUM}"`);
556
570
  }
@@ -3,6 +3,10 @@
3
3
  * Tests each step executor with sample inputs
4
4
  */
5
5
  import { describe, test, expect } from '@jest/globals';
6
+ import { execFileSync } from 'node:child_process';
7
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
8
+ import { tmpdir } from 'node:os';
9
+ import { join } from 'node:path';
6
10
  import { GitCloneStepExecutor } from './git-clone.js';
7
11
  import { ScriptStepExecutor } from './script.js';
8
12
  import { CachePullStepExecutor, CachePushStepExecutor } from './cache.js';
@@ -166,6 +170,70 @@ describe('Step Implementations', () => {
166
170
  expect(result.script).toContain('Package.resolved');
167
171
  expect(result.script).toContain('spm-');
168
172
  });
173
+ // A static `toContain` check can't catch a control-flow bug in the
174
+ // fallback chain (the script "contains" both Podfile.lock and
175
+ // Package.resolved either way). So actually execute the generated
176
+ // key-derivation in a fixture repo and read the resolved key back.
177
+ describe('ios fallback chain (executed in a fixture repo)', () => {
178
+ async function resolveIosCacheKey(build) {
179
+ const script = (await new CachePullStepExecutor().execute({ technology: 'ios' }, {}, testConfigNoPeer)).script;
180
+ const dir = mkdtempSync(join(tmpdir(), 'cibuild-cache-'));
181
+ try {
182
+ build(dir);
183
+ const scriptPath = join(dir, '__cache_pull.sh');
184
+ writeFileSync(scriptPath, script);
185
+ let out = '';
186
+ try {
187
+ out = execFileSync('bash', [scriptPath], {
188
+ cwd: dir,
189
+ encoding: 'utf-8',
190
+ stdio: ['ignore', 'pipe', 'pipe'],
191
+ });
192
+ }
193
+ catch (e) {
194
+ const err = e;
195
+ out = `${err.stdout ?? ''}${err.stderr ?? ''}`;
196
+ }
197
+ const m = out.match(/CACHE_KEY=(\S+)/);
198
+ if (!m)
199
+ throw new Error(`No CACHE_KEY in output:\n${out}`);
200
+ return m[1];
201
+ }
202
+ finally {
203
+ rmSync(dir, { recursive: true, force: true });
204
+ }
205
+ }
206
+ test('SPM project (Package.resolved, no Podfile.lock) resolves an spm- key', async () => {
207
+ const key = await resolveIosCacheKey((dir) => {
208
+ // Real Xcode SPM location — Package.resolved committed under
209
+ // the shared xcworkspace data, no Podfile.lock anywhere.
210
+ const swiftpm = join(dir, 'Everli.xcodeproj/project.xcworkspace/xcshareddata/swiftpm');
211
+ mkdirSync(swiftpm, { recursive: true });
212
+ writeFileSync(join(swiftpm, 'Package.resolved'), '{"pins":[],"version":3}');
213
+ });
214
+ expect(key).toMatch(/^spm-/);
215
+ expect(key).not.toContain('no-lockfile');
216
+ });
217
+ test('CocoaPods project (Podfile.lock) resolves a pods- key', async () => {
218
+ const key = await resolveIosCacheKey((dir) => {
219
+ writeFileSync(join(dir, 'Podfile.lock'), 'PODS:\n - Alamofire\n');
220
+ });
221
+ expect(key).toMatch(/^pods-/);
222
+ expect(key).not.toContain('no-lockfile');
223
+ });
224
+ test('Podfile.lock wins over Package.resolved when both exist', async () => {
225
+ const key = await resolveIosCacheKey((dir) => {
226
+ writeFileSync(join(dir, 'Podfile.lock'), 'PODS:\n');
227
+ writeFileSync(join(dir, 'Package.resolved'), '{"pins":[]}');
228
+ });
229
+ expect(key).toMatch(/^pods-/);
230
+ expect(key).not.toContain('no-lockfile');
231
+ });
232
+ test('neither lockfile present falls back to a no-lockfile key', async () => {
233
+ const key = await resolveIosCacheKey(() => { });
234
+ expect(key).toContain('no-lockfile');
235
+ });
236
+ });
169
237
  test('should auto-configure for yarn technology', async () => {
170
238
  const executor = new CachePullStepExecutor();
171
239
  const result = await executor.execute({ technology: 'yarn' }, {}, testConfig);
package/package.json CHANGED
@@ -1,18 +1,20 @@
1
1
  {
2
2
  "name": "@invarn/cibuild",
3
- "version": "1.9.6",
3
+ "version": "1.9.8",
4
4
  "description": "CI Build CLI — local pipeline orchestration and validation",
5
5
  "type": "module",
6
6
  "main": "dist/cli.cjs",
7
7
  "exports": {
8
8
  ".": "./dist/cli.cjs",
9
9
  "./lib": {
10
+ "types": "./dist/src/lib.d.ts",
10
11
  "import": "./dist/src/lib.js",
11
- "types": "./dist/src/lib.d.ts"
12
+ "require": "./dist/src/lib.js"
12
13
  },
13
14
  "./commands": {
15
+ "types": "./dist/src/commands/index.d.ts",
14
16
  "import": "./dist/src/commands/index.js",
15
- "types": "./dist/src/commands/index.d.ts"
17
+ "require": "./dist/src/commands/index.js"
16
18
  }
17
19
  },
18
20
  "bin": {