gowalk-cicd 1.0.108 → 1.0.109

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/CLAUDE.md CHANGED
@@ -161,7 +161,8 @@ node /path/to/gowalk-cicd/bin/cli.mjs
161
161
  It retains the account proxy and allows four attempts within 60 seconds for manifests or 20 minutes
162
162
  for archives, with an 8 GiB archive bound. Interrupted archives resume private bytes with HTTP Range; an origin ignoring
163
163
  Range permits a bounded full restart. Only complete JSON or an archive matching the selected official
164
- manifest's SHA-256 enters action outputs. Authentication/certificate refusals remain terminal.
164
+ manifest's SHA-256 enters action outputs. Proxy CONNECT 502/503/504 share that retry budget even
165
+ when curl exits 22 with origin status 0. Authentication/certificate refusals remain terminal.
165
166
  `flutter_download_failed` emits fixed transport codes and retained-byte counts, never curl stderr.
166
167
  Archive progress emits retained byte counts every minute and safe results after each attempt;
167
168
  the transfer deadline leaves room for a failure receipt before a 30-minute validation job ends.
@@ -1 +1 @@
1
- 1.0.108
1
+ 1.0.109
@@ -1 +1 @@
1
- 1.0.108
1
+ 1.0.109
@@ -51,7 +51,8 @@ def download(arguments: list[str], directory: Path) -> int:
51
51
  if attempt == 4 or proxy_http in {401, 403, 407} or http in {401, 403}:
52
52
  break
53
53
  restart = kind == "archive" and code == 33 and http == 200
54
- if not restart and code not in RETRY_CODES and http not in RETRY_HTTP:
54
+ if (not restart and code not in RETRY_CODES and http not in RETRY_HTTP
55
+ and proxy_http not in {502, 503, 504}):
55
56
  break
56
57
  if kind == "manifest" or restart:
57
58
  destination.write_bytes(b"")
@@ -60,8 +60,10 @@ class Proxy(socketserver.StreamRequestHandler):
60
60
  with self.server.lock:
61
61
  self.server.connects += 1
62
62
  mode = self.server.modes.pop(0)
63
- if mode == "proxy_auth":
64
- self.wfile.write(b"HTTP/1.1 407 Proxy Authentication Required\r\nContent-Length: 0\r\n\r\n")
63
+ proxy_status = {"proxy_auth": 407, "proxy_401": 401, "proxy_403": 403,
64
+ "proxy_502": 502, "proxy_503": 503, "proxy_504": 504}.get(mode)
65
+ if proxy_status:
66
+ self.wfile.write(f"HTTP/1.1 {proxy_status} Fixture refusal\r\nContent-Length: 0\r\n\r\n".encode())
65
67
  return
66
68
  self.wfile.write(b"HTTP/1.1 200 Connection established\r\n\r\n")
67
69
  self.wfile.flush()
@@ -0,0 +1,76 @@
1
+ """Real curl retries transient CONNECT failures without sending or publishing partial origin data."""
2
+ import hashlib
3
+ import os
4
+ from pathlib import Path
5
+ import subprocess
6
+ import sys
7
+ import tempfile
8
+ import unittest
9
+
10
+ from flutter_manifest_fixture import HOST, MANIFEST, tls_proxy
11
+
12
+ ROOT = Path(__file__).parent
13
+ ARCHIVE = bytes(range(256)) * 512
14
+
15
+
16
+ class ProxyRetryTests(unittest.TestCase):
17
+ def setUp(self):
18
+ directory = tempfile.TemporaryDirectory()
19
+ self.addCleanup(directory.cleanup)
20
+ self.root = Path(directory.name)
21
+
22
+ def invoke(self, server, cert, kind="manifest"):
23
+ proxy = f"http://fixture-user:fixture-secret@127.0.0.1:{server.server_address[1]}"
24
+ env = {**os.environ, "HOME": str(self.root), "RUNNER_TEMP": str(self.root),
25
+ "HTTPS_PROXY": proxy, "https_proxy": proxy, "NO_PROXY": "*", "no_proxy": "*",
26
+ "GOWALK_FLUTTER_ARCHIVE_SHA256": hashlib.sha256(ARCHIVE).hexdigest()}
27
+ for key in ("CURL_CA_BUNDLE", "SSL_CERT_FILE", "SSL_CERT_DIR", "GOWALK_FLUTTER_ARCHIVE_CACHE",
28
+ "GOWALK_FLUTTER_RELEASE_CACHE"):
29
+ env.pop(key, None)
30
+ env["CURL_CA_BUNDLE"] = str(cert)
31
+ (self.root / ".curlrc").write_text('noproxy = "*"\ninsecure\n')
32
+ path = "releases_linux.json" if kind == "manifest" else "stable/linux/flutter_linux_3.44.0-stable.tar.xz"
33
+ args = ["--connect-timeout", "15", "--retry", "5"]
34
+ if kind == "manifest":
35
+ args.insert(0, "--silent")
36
+ result = subprocess.run([sys.executable, str(ROOT / "flutter_manifest.py"), *args,
37
+ f"https://{HOST}/flutter_infra_release/releases/{path}"],
38
+ env=env, capture_output=True, timeout=20)
39
+ self.assertNotIn(b"fixture-secret", result.stdout + result.stderr)
40
+ self.assertFalse(list(self.root.glob("flutter-manifest-*")))
41
+ return result
42
+
43
+ def test_transient_connect_failures_recover_through_the_same_proxy(self):
44
+ for status in (502, 503, 504):
45
+ with self.subTest(status=status), tls_proxy(self.root, [f"proxy_{status}", "ok"]) as (server, cert):
46
+ result = self.invoke(server, cert)
47
+ self.assertEqual((result.returncode, result.stdout), (0, MANIFEST), result.stderr)
48
+ self.assertEqual((server.connects, len(server.requests)), (2, 1))
49
+ self.assertIn(b"flutter_manifest_recovered attempts=2", result.stderr)
50
+
51
+ def test_repeated_connect_failure_stops_after_four_attempts_without_origin_request(self):
52
+ with tls_proxy(self.root, ["proxy_502"] * 4) as (server, cert):
53
+ result = self.invoke(server, cert)
54
+ self.assertNotEqual(result.returncode, 0)
55
+ self.assertEqual((result.stdout, server.connects, server.requests), (b"", 4, []))
56
+ self.assertIn(b"http_status=0 proxy_status=502 attempts=4 retained_bytes=0", result.stderr)
57
+
58
+ def test_connect_failure_preserves_partial_archive_for_a_verified_range_resume(self):
59
+ with tls_proxy(self.root, ["partial", "proxy_502", "range"], ARCHIVE) as (server, cert):
60
+ result = self.invoke(server, cert, "archive")
61
+ self.assertEqual((result.returncode, result.stdout), (0, ARCHIVE), result.stderr)
62
+ self.assertEqual(server.connects, 3)
63
+ self.assertEqual(server.ranges, ["", "bytes=31-"])
64
+ self.assertIn(b"flutter_archive_recovered attempts=3", result.stderr)
65
+
66
+ def test_proxy_authorization_refusals_remain_terminal(self):
67
+ for mode in ("proxy_401", "proxy_403", "proxy_auth"):
68
+ with self.subTest(mode=mode), tls_proxy(self.root, [mode]) as (server, cert):
69
+ result = self.invoke(server, cert)
70
+ self.assertNotEqual(result.returncode, 0)
71
+ self.assertEqual((result.stdout, server.connects, server.requests), (b"", 1, []))
72
+ self.assertIn(b"attempts=1", result.stderr)
73
+
74
+
75
+ if __name__ == "__main__":
76
+ unittest.main()
@@ -1 +1 @@
1
- 1.0.108
1
+ 1.0.109
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gowalk-cicd",
3
- "version": "1.0.108",
3
+ "version": "1.0.109",
4
4
  "description": "Zero-config GitHub Actions delivery for iOS TestFlight and Android Google Play.",
5
5
  "type": "module",
6
6
  "bin": {