@invarn/cibuild 1.9.2 → 1.9.3

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;CAsLhC;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;CA2IhC"}
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;YA4JzF,iBAAiB;CA2IhC"}
@@ -86,6 +86,26 @@ function resolvePresetChain(technology) {
86
86
  }
87
87
  return chain;
88
88
  }
89
+ /**
90
+ * Emits the daemon exact-key pull branch (ADR 0001, Phase 3b). On a local
91
+ * miss, GET `<key>.tar.zst` from this runner's cache daemon ($CIBUILD_CACHE_DAEMON,
92
+ * injected by the runner). The daemon fans out to tailnet peers, persists the
93
+ * hit to ~/cache, and streams it back — so we pipe straight to extraction
94
+ * without re-writing it on the guest. The optional per-build bearer token
95
+ * ($CIBUILD_CACHE_TOKEN) is sent only when set. Inert when the env var is
96
+ * empty, so the same script runs with or without a daemon.
97
+ */
98
+ function emitDaemonPullBranch() {
99
+ return [
100
+ 'elif [ -n "$CIBUILD_CACHE_DAEMON" ] && curl -fsS ${CIBUILD_CACHE_TOKEN:+-H "Authorization: Bearer $CIBUILD_CACHE_TOKEN"} "$CIBUILD_CACHE_DAEMON/cache/$CACHE_KEY.tar.zst" | zstd -dc | tar -xf - -C /; then',
101
+ ' echo "Cache found (daemon), extracting..."',
102
+ // LRU bump the tarball the daemon just persisted to ~/cache (same dir as
103
+ // the --dir mount); tolerate its absence in case the daemon served it
104
+ // without a local copy.
105
+ ' touch "$CACHE_FILE" 2>/dev/null || true',
106
+ ' echo "CACHE_SOURCE=daemon CACHE_KEY=$CACHE_KEY"',
107
+ ];
108
+ }
89
109
  /**
90
110
  * Cache pull step executor
91
111
  * Restores cached files from cache directory
@@ -133,7 +153,8 @@ export class CachePullStepExecutor extends BaseStepExecutor {
133
153
  // Generate cache file name based on cache key
134
154
  commands.push('');
135
155
  commands.push('# Generate cache file path');
136
- commands.push(`CACHE_FILE="$CACHE_DIR/${this.escapeBash(cacheKey)}.tar.zst"`);
156
+ commands.push(`CACHE_KEY="${this.escapeBash(cacheKey)}"`);
157
+ commands.push('CACHE_FILE="$CACHE_DIR/$CACHE_KEY.tar.zst"');
137
158
  if (isDebugMode) {
138
159
  commands.push('echo "Debug mode enabled"');
139
160
  commands.push('echo "Cache file: $CACHE_FILE"');
@@ -155,6 +176,8 @@ export class CachePullStepExecutor extends BaseStepExecutor {
155
176
  }
156
177
  commands.push(' zstd -dc "$CACHE_FILE" | tar -xf - -C /');
157
178
  commands.push(' echo "CACHE_SOURCE=local"');
179
+ // Daemon hit (ADR 0001) — see emitDaemonPullBranch.
180
+ commands.push(...emitDaemonPullBranch());
158
181
  // Peer fallback
159
182
  if (peerCacheDir) {
160
183
  commands.push('elif [ -f "$PEER_CACHE_FILE" ] 2>/dev/null; then');
@@ -257,6 +280,13 @@ export class CachePullStepExecutor extends BaseStepExecutor {
257
280
  // hit daily would still be reaped on its 31st day from creation.
258
281
  commands.push(' touch "$CACHE_FILE"');
259
282
  commands.push(' echo "CACHE_SOURCE=local CACHE_KEY=$CACHE_KEY"');
283
+ // 1b. Daemon hit (ADR 0001) — on a local miss, ask this runner's cache
284
+ // daemon for the exact key. The daemon resolves it from a tailnet peer
285
+ // and atomically persists it to ~/cache (the same dir as the --dir mount),
286
+ // so we stream the response straight to extraction with no redundant
287
+ // guest-side write. Gated at runtime on the env var the runner injects, so
288
+ // the generated script is inert when no daemon is configured.
289
+ commands.push(...emitDaemonPullBranch());
260
290
  // 2. Peer hit — tee to warm local cache while extracting in one pass
261
291
  if (peerCacheDir) {
262
292
  commands.push('elif [ -f "$PEER_CACHE_FILE" ] 2>/dev/null; then');
@@ -268,6 +268,42 @@ describe('Step Implementations', () => {
268
268
  expect(result.script).toContain('exceeds 30d cap — going cold');
269
269
  expect(result.script).toContain('FALLBACK_REJECTED=');
270
270
  });
271
+ test('routes a preset pull miss through the cache daemon when CIBUILD_CACHE_DAEMON is set', async () => {
272
+ const executor = new CachePullStepExecutor();
273
+ const result = await executor.execute({ technology: 'kmm' }, {}, testConfig);
274
+ // Daemon branch is gated at runtime on the injected env var (so the same
275
+ // generated script is a no-op when the runner doesn't inject a daemon).
276
+ expect(result.script).toContain('[ -n "$CIBUILD_CACHE_DAEMON" ]');
277
+ // Exact-key GET against the per-runner daemon.
278
+ expect(result.script).toContain('"$CIBUILD_CACHE_DAEMON/cache/$CACHE_KEY.tar.zst"');
279
+ // Stream-extract: the daemon already persisted the tarball to ~/cache via
280
+ // resolveMiss, so we pipe straight to extraction with no redundant write.
281
+ expect(result.script).toContain('CACHE_SOURCE=daemon CACHE_KEY=$CACHE_KEY');
282
+ });
283
+ test('sends the per-build bearer token only when CIBUILD_CACHE_TOKEN is set', async () => {
284
+ const executor = new CachePullStepExecutor();
285
+ const result = await executor.execute({ technology: 'kmm' }, {}, testConfig);
286
+ // ${VAR:+...} expands the -H flag only when the token is non-empty, so an
287
+ // unauthenticated daemon isn't sent a bogus "Bearer " header.
288
+ expect(result.script).toContain('${CIBUILD_CACHE_TOKEN:+-H "Authorization: Bearer $CIBUILD_CACHE_TOKEN"}');
289
+ });
290
+ test('daemon branch is an elif after the local hit (exact-key, daemon, fallback order)', async () => {
291
+ const executor = new CachePullStepExecutor();
292
+ const result = await executor.execute({ technology: 'kmm' }, {}, testConfig);
293
+ const localAt = result.script.indexOf('CACHE_SOURCE=local');
294
+ const daemonAt = result.script.indexOf('CACHE_SOURCE=daemon');
295
+ const fallbackAt = result.script.indexOf('__ci_fb_scope=');
296
+ expect(localAt).toBeGreaterThanOrEqual(0);
297
+ expect(daemonAt).toBeGreaterThan(localAt);
298
+ expect(fallbackAt).toBeGreaterThan(daemonAt);
299
+ });
300
+ test('routes a manual cache_key pull miss through the cache daemon', async () => {
301
+ const executor = new CachePullStepExecutor();
302
+ const result = await executor.execute({ cache_key: 'my-key' }, {}, testConfig);
303
+ expect(result.script).toContain('[ -n "$CIBUILD_CACHE_DAEMON" ]');
304
+ expect(result.script).toContain('"$CIBUILD_CACHE_DAEMON/cache/$CACHE_KEY.tar.zst"');
305
+ expect(result.script).toContain('CACHE_SOURCE=daemon CACHE_KEY=$CACHE_KEY');
306
+ });
271
307
  });
272
308
  describe('CachePushStepExecutor', () => {
273
309
  test('should generate cache push script', async () => {
@@ -283,6 +319,15 @@ describe('Step Implementations', () => {
283
319
  expect(result.script).toContain('node-modules-v1');
284
320
  expect(result.script).toContain('.ci-cache');
285
321
  });
322
+ test('stays a local write — never PUTs to the cache daemon (ADR 0001 v1 scope)', async () => {
323
+ const executor = new CachePushStepExecutor();
324
+ const manual = await executor.execute({ cache_key: 'my-key', cache_paths: ['build'] }, {}, testConfig);
325
+ const preset = await executor.execute({ technology: 'kmm' }, {}, testConfig);
326
+ for (const result of [manual, preset]) {
327
+ expect(result.script).not.toContain('CIBUILD_CACHE_DAEMON');
328
+ expect(result.script).not.toContain('CIBUILD_CACHE_TOKEN');
329
+ }
330
+ });
286
331
  test('should skip gracefully when cache_key is missing', async () => {
287
332
  const executor = new CachePushStepExecutor();
288
333
  const inputs = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@invarn/cibuild",
3
- "version": "1.9.2",
3
+ "version": "1.9.3",
4
4
  "description": "CI Build CLI — local pipeline orchestration and validation",
5
5
  "type": "module",
6
6
  "main": "dist/cli.cjs",