styleproof 4.4.18 → 4.4.20

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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,23 @@ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [4.4.20] - 2026-07-14
11
+
12
+ ### Fixed
13
+
14
+ - **Map-store network operations can no longer wedge CI indefinitely.** Remote
15
+ lookup, clone, and push commands now have a bounded timeout, and a stalled
16
+ isolated push falls through to the authenticated consumer checkout path.
17
+
18
+ ## [4.4.19] - 2026-07-14
19
+
20
+ ### Fixed
21
+
22
+ - **Generated cache restores now evaluate each map in its own commit context.**
23
+ CI checks out the base before restoring the base bundle and returns to the head
24
+ before restoring the head bundle, so lockfile-changing pull requests reach the
25
+ browserless hot path after their first published pair.
26
+
10
27
  ## [4.4.18] - 2026-07-13
11
28
 
12
29
  ### Fixed
@@ -415,9 +415,16 @@ ${PM.setup}
415
415
  HEAD_SHA="\${{ github.event.pull_request.head.sha }}"
416
416
  MAP_ROOT="\${{ runner.temp }}/styleproof-maps"
417
417
  rm -rf "$MAP_ROOT"
418
+ # Compatibility keys include the checked-out lockfile. Resolve each
419
+ # exact-SHA bundle in that commit's own dependency context, while
420
+ # reusing the already-installed StyleProof binary from node_modules.
421
+ git checkout --force "$BASE_SHA"
418
422
  set +e
419
423
  PATH="$PWD/node_modules/.bin:$PATH" node node_modules/styleproof/bin/styleproof-map.mjs --restore --sha "$BASE_SHA" --dir base --base-dir "$MAP_ROOT" --spec ${specPath}
420
424
  base_code=$?
425
+ set -e
426
+ git checkout --force "$HEAD_SHA"
427
+ set +e
421
428
  PATH="$PWD/node_modules/.bin:$PATH" node node_modules/styleproof/bin/styleproof-map.mjs --restore --sha "$HEAD_SHA" --dir head --base-dir "$MAP_ROOT" --spec ${specPath}
422
429
  head_code=$?
423
430
  set -e
package/dist/map-store.js CHANGED
@@ -52,6 +52,33 @@ function gitProcessEnvironment() {
52
52
  function runGit(cwd, args, maxBuffer = 1 << 28) {
53
53
  return spawnSync('git', args, { cwd, encoding: 'utf8', maxBuffer, env: gitProcessEnvironment() });
54
54
  }
55
+ const DEFAULT_MAP_STORE_GIT_TIMEOUT_MILLISECONDS = 30_000;
56
+ function mapStoreGitTimeoutMilliseconds() {
57
+ const configuredTimeout = Number(process.env.STYLEPROOF_MAP_STORE_GIT_TIMEOUT_MS);
58
+ return Number.isFinite(configuredTimeout) && configuredTimeout > 0
59
+ ? Math.floor(configuredTimeout)
60
+ : DEFAULT_MAP_STORE_GIT_TIMEOUT_MILLISECONDS;
61
+ }
62
+ function runMapStoreNetworkGit(cwd, args, maxBuffer = 1 << 20) {
63
+ return spawnSync('git', args, {
64
+ cwd,
65
+ encoding: 'utf8',
66
+ maxBuffer,
67
+ timeout: mapStoreGitTimeoutMilliseconds(),
68
+ env: {
69
+ ...gitProcessEnvironment(),
70
+ GIT_TERMINAL_PROMPT: '0',
71
+ },
72
+ });
73
+ }
74
+ function gitFailureMessage(result, fallback) {
75
+ const standardError = typeof result.stderr === 'string' ? result.stderr.trim() : '';
76
+ if (standardError)
77
+ return standardError;
78
+ if (result.error)
79
+ return result.error.message;
80
+ return fallback;
81
+ }
55
82
  const WORKFLOW_TOKEN_CREDENTIAL_HELPER = '!f() { if [ "$1" = get ]; then printf \'%s\\n\' username=x-access-token "password=$STYLEPROOF_MAP_STORE_TOKEN"; fi; }; f';
56
83
  export function workflowTokenCredentialArguments() {
57
84
  return ['-c', 'credential.helper=', '-c', `credential.helper=${WORKFLOW_TOKEN_CREDENTIAL_HELPER}`];
@@ -459,19 +486,20 @@ function checkoutMapStore(cwd, remote, branch, sparseSegment) {
459
486
  const httpExtraHeaders = effectiveGitHttpExtraHeaders(cwd);
460
487
  const authenticationArguments = httpExtraHeaders.flatMap(({ key, value }) => ['-c', `${key}=${value}`]);
461
488
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'styleproof-map-store-'));
462
- const branchExists = runGit(cwd, ['ls-remote', '--exit-code', '--heads', remote, branch], 1 << 20).status === 0;
489
+ const branchLookup = runMapStoreNetworkGit(cwd, ['ls-remote', '--exit-code', '--heads', remote, branch], 1 << 20);
490
+ if (branchLookup.status !== 0 && branchLookup.status !== 2) {
491
+ fs.rmSync(tmp, { recursive: true, force: true });
492
+ throw new MapStoreError(gitFailureMessage(branchLookup, 'could not query map store branch'));
493
+ }
494
+ const branchExists = branchLookup.status === 0;
463
495
  if (branchExists) {
464
496
  const cloneArguments = sparseSegment
465
497
  ? ['clone', '-q', '--filter=blob:none', '--no-checkout', '--depth', '1', '--single-branch', '--branch', branch]
466
498
  : ['clone', '-q', '--depth', '1', '--branch', branch];
467
- const clone = spawnSync('git', [...authenticationArguments, ...cloneArguments, remoteUrl, tmp], {
468
- encoding: 'utf8',
469
- maxBuffer: 1 << 20,
470
- env: gitProcessEnvironment(),
471
- });
499
+ const clone = runMapStoreNetworkGit(cwd, [...authenticationArguments, ...cloneArguments, remoteUrl, tmp]);
472
500
  if (clone.status !== 0) {
473
501
  fs.rmSync(tmp, { recursive: true, force: true });
474
- throw new MapStoreError(clone.stderr.trim() || 'could not clone map store branch');
502
+ throw new MapStoreError(gitFailureMessage(clone, 'could not clone map store branch'));
475
503
  }
476
504
  }
477
505
  else {
@@ -486,15 +514,15 @@ function checkoutMapStore(cwd, remote, branch, sparseSegment) {
486
514
  }
487
515
  function pushMapStoreCommit(cwd, temporaryCheckout, remote, branch, authenticationArguments) {
488
516
  const pushFailures = [];
489
- const isolatedPush = runGit(temporaryCheckout, [...authenticationArguments, 'push', '-q', 'origin', `HEAD:${branch}`], 1 << 20);
517
+ const isolatedPush = runMapStoreNetworkGit(temporaryCheckout, [...authenticationArguments, 'push', '-q', 'origin', `HEAD:${branch}`], 1 << 20);
490
518
  if (isolatedPush.status === 0)
491
519
  return isolatedPush;
492
- pushFailures.push(`isolated map-store push: ${isolatedPush.stderr.trim() || `git exited ${isolatedPush.status}`}`);
520
+ pushFailures.push(`isolated map-store push: ${gitFailureMessage(isolatedPush, `git exited ${isolatedPush.status}`)}`);
493
521
  const mapStoreToken = process.env.STYLEPROOF_MAP_STORE_TOKEN;
494
522
  if (mapStoreToken) {
495
523
  const githubExtraHeaderKey = ['http.https:', '', 'github.com', '.extraheader'].join('/');
496
524
  runGit(temporaryCheckout, ['config', '--local', '--unset-all', githubExtraHeaderKey], 1 << 20);
497
- const credentialPush = spawnSync('git', [
525
+ const credentialPush = runMapStoreNetworkGit(temporaryCheckout, [
498
526
  '-c',
499
527
  `${githubExtraHeaderKey}=`,
500
528
  ...workflowTokenCredentialArguments(),
@@ -502,18 +530,10 @@ function pushMapStoreCommit(cwd, temporaryCheckout, remote, branch, authenticati
502
530
  '-q',
503
531
  'origin',
504
532
  `HEAD:${branch}`,
505
- ], {
506
- cwd: temporaryCheckout,
507
- encoding: 'utf8',
508
- maxBuffer: 1 << 20,
509
- env: {
510
- ...gitProcessEnvironment(),
511
- GIT_TERMINAL_PROMPT: '0',
512
- },
513
- });
533
+ ]);
514
534
  if (credentialPush.status === 0)
515
535
  return credentialPush;
516
- pushFailures.push(`workflow-token credential push: ${credentialPush.stderr.trim() || `git exited ${credentialPush.status}`}`);
536
+ pushFailures.push(`workflow-token credential push: ${gitFailureMessage(credentialPush, `git exited ${credentialPush.status}`)}`);
517
537
  }
518
538
  const mapStoreCommit = gitOutput(temporaryCheckout, ['rev-parse', 'HEAD']);
519
539
  const importCommit = runGit(cwd, ['fetch', '-q', '--no-write-fetch-head', temporaryCheckout, mapStoreCommit], 1 << 20);
@@ -526,14 +546,14 @@ function pushMapStoreCommit(cwd, temporaryCheckout, remote, branch, authenticati
526
546
  ].join('\n'),
527
547
  };
528
548
  }
529
- const consumerCheckoutPush = runGit(cwd, ['push', '-q', remote, `${mapStoreCommit}:${branch}`], 1 << 20);
549
+ const consumerCheckoutPush = runMapStoreNetworkGit(cwd, ['push', '-q', remote, `${mapStoreCommit}:${branch}`], 1 << 20);
530
550
  if (consumerCheckoutPush.status === 0)
531
551
  return consumerCheckoutPush;
532
552
  return {
533
553
  ...consumerCheckoutPush,
534
554
  stderr: [
535
555
  ...pushFailures,
536
- `consumer checkout push: ${consumerCheckoutPush.stderr.trim() || `git exited ${consumerCheckoutPush.status}`}`,
556
+ `consumer checkout push: ${gitFailureMessage(consumerCheckoutPush, `git exited ${consumerCheckoutPush.status}`)}`,
537
557
  ].join('\n'),
538
558
  };
539
559
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "styleproof",
3
- "version": "4.4.18",
3
+ "version": "4.4.20",
4
4
  "description": "Catch every CSS change before it ships — review PRs and certify refactors by the browser's computed styles, not pixels. Works with any styling system.",
5
5
  "keywords": [
6
6
  "playwright",