gowalk-cicd 1.0.70 → 1.0.71

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
@@ -135,6 +135,16 @@ node /path/to/gowalk-cicd/bin/cli.mjs
135
135
  localization, global activation and builds run through the existing owned relay.
136
136
  The helper's bootstrap and command modes require only Python's standard library;
137
137
  importing the Google API client must not become an unprotected bootstrap prerequisite.
138
+ - `android-action/flutter-setup/` owns Flutter setup. It forwards the official action's
139
+ inputs/cache identities while isolating restore/save from provider transport. Its step-local
140
+ `NODE_OPTIONS` preloader (`android-action/scripts/cache_*.cjs`) also protects CocoaPods cache
141
+ steps and verifies the exact pinned Actions cache entrypoint bytes and HTTPS
142
+ GitHub cache/Azure blob destinations before removing proxy variables in that cache process.
143
+ Unknown code or origins refuse; SDK setup, non-cache children and later steps retain their
144
+ proxy requirement. Never export this scope through `GITHUB_ENV` or add cache domains to
145
+ `NO_PROXY`. The preloader snapshot under `RUNNER_TEMP` also covers official post/save after
146
+ consumer autoupdate changes the action directory. The installer delivers this development
147
+ tooling for native iOS repositories too; it does not require an Android app.
138
148
  - Flutter setup scopes `SEGMENT_DOWNLOAD_TIMEOUT_MINS=1` to the composite action's optional
139
149
  SDK/pub cache restores. This bounds each cache segment, not the whole cache or SDK download.
140
150
  A cache timeout becomes a cache miss; normal proxied SDK setup remains mandatory and its
@@ -1 +1 @@
1
- 1.0.70
1
+ 1.0.71
@@ -1 +1 @@
1
- 1.0.70
1
+ 1.0.71
@@ -0,0 +1,149 @@
1
+ name: Set up proxied Flutter
2
+ description: Set up Flutter with protected provider transport and isolated official GitHub caches
3
+ inputs:
4
+ channel:
5
+ description: The Flutter build release channel
6
+ required: false
7
+ default: stable
8
+ flutter-version:
9
+ description: The Flutter version to make available on the path
10
+ required: false
11
+ default: ''
12
+ flutter-version-file:
13
+ description: The pubspec.yaml or FVM config file with exact Flutter version defined
14
+ required: false
15
+ default: ''
16
+ architecture:
17
+ description: The architecture of Flutter SDK executable (x64 or arm64)
18
+ required: false
19
+ default: ${{ runner.arch }}
20
+ cache:
21
+ description: Cache the Flutter SDK
22
+ required: false
23
+ default: 'false'
24
+ cache-key:
25
+ description: Identifier for the Flutter SDK cache
26
+ required: false
27
+ default: ''
28
+ cache-path:
29
+ description: Flutter SDK cache path
30
+ required: false
31
+ default: ''
32
+ pub-cache:
33
+ description: Cache the Dart pub dependencies
34
+ required: false
35
+ default: ''
36
+ pub-cache-key:
37
+ description: Identifier for the Dart .pub-cache cache
38
+ required: false
39
+ default: ''
40
+ pub-cache-path:
41
+ description: Flutter pub cache path
42
+ required: false
43
+ default: default
44
+ dry-run:
45
+ description: If true, get outputs but do not install Flutter
46
+ required: false
47
+ default: 'false'
48
+ git-source:
49
+ description: Git clone source
50
+ required: false
51
+ default: https://github.com/flutter/flutter.git
52
+ outputs:
53
+ CHANNEL:
54
+ value: ${{ steps.flutter-action.outputs.CHANNEL }}
55
+ description: The selected Flutter release channel
56
+ VERSION:
57
+ value: ${{ steps.flutter-action.outputs.VERSION }}
58
+ description: The selected Flutter version
59
+ ARCHITECTURE:
60
+ value: ${{ steps.flutter-action.outputs.ARCHITECTURE }}
61
+ description: The selected Flutter CPU architecture
62
+ CACHE-KEY:
63
+ value: ${{ steps.flutter-action.outputs.CACHE-KEY }}
64
+ description: Key used to cache the Flutter SDK
65
+ CACHE-PATH:
66
+ value: ${{ steps.flutter-action.outputs.CACHE-PATH }}
67
+ description: Path to Flutter SDK
68
+ PUB-CACHE-KEY:
69
+ value: ${{ steps.flutter-action.outputs.PUB-CACHE-KEY }}
70
+ description: Key used to cache the pub dependencies
71
+ PUB-CACHE-PATH:
72
+ value: ${{ steps.flutter-action.outputs.PUB-CACHE-PATH }}
73
+ description: Path to pub cache
74
+ GIT_SOURCE:
75
+ value: ${{ steps.flutter-action.outputs.GIT_SOURCE }}
76
+ description: Git source of Flutter SDK repository to clone
77
+ CACHE-HIT:
78
+ value: ${{ steps.cache-flutter.outputs.cache-hit }}
79
+ description: '`true` if the flutter cache was a hit'
80
+ PUB-CACHE-HIT:
81
+ value: ${{ steps.cache-pub.outputs.cache-hit }}
82
+ description: '`true` if the pub cache was a hit'
83
+ runs:
84
+ using: composite
85
+ steps:
86
+ - name: Require provider transport
87
+ shell: bash
88
+ run: |
89
+ python3 "$GITHUB_ACTION_PATH/../scripts/play_store_proxy.py" --bootstrap-env
90
+ - name: Retain the cache process scope
91
+ id: scope
92
+ if: ${{ inputs.cache == 'true' || inputs.pub-cache == 'true' }}
93
+ continue-on-error: true
94
+ shell: bash
95
+ run: node "$GITHUB_ACTION_PATH/../scripts/cache_scope.cjs" --snapshot
96
+ - name: Resolve Flutter release and cache identities
97
+ id: flutter-action
98
+ uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2
99
+ with:
100
+ channel: ${{ inputs.channel }}
101
+ flutter-version: ${{ inputs.flutter-version }}
102
+ flutter-version-file: ${{ inputs.flutter-version-file }}
103
+ architecture: ${{ inputs.architecture }}
104
+ cache: 'false'
105
+ cache-key: ${{ inputs.cache-key }}
106
+ cache-path: ${{ inputs.cache-path }}
107
+ pub-cache: 'false'
108
+ pub-cache-key: ${{ inputs.pub-cache-key }}
109
+ pub-cache-path: ${{ inputs.pub-cache-path }}
110
+ dry-run: 'true'
111
+ git-source: ${{ inputs.git-source }}
112
+ - name: Cache Flutter
113
+ id: cache-flutter
114
+ if: ${{ inputs.cache == 'true' }}
115
+ uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25
116
+ continue-on-error: true
117
+ env:
118
+ NODE_OPTIONS: --require "${{ steps.scope.outputs.preloader }}"
119
+ SEGMENT_DOWNLOAD_TIMEOUT_MINS: '1'
120
+ with:
121
+ path: ${{ steps.flutter-action.outputs.CACHE-PATH }}
122
+ key: ${{ steps.flutter-action.outputs.CACHE-KEY }}
123
+ - name: Cache pub dependencies
124
+ id: cache-pub
125
+ if: ${{ (inputs.pub-cache == '' && inputs.cache == 'true') || inputs.pub-cache == 'true' }}
126
+ uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25
127
+ continue-on-error: true
128
+ env:
129
+ NODE_OPTIONS: --require "${{ steps.scope.outputs.preloader }}"
130
+ SEGMENT_DOWNLOAD_TIMEOUT_MINS: '1'
131
+ with:
132
+ path: ${{ steps.flutter-action.outputs.PUB-CACHE-PATH }}
133
+ key: ${{ steps.flutter-action.outputs.PUB-CACHE-KEY }}-${{ hashFiles('**/pubspec.lock') }}
134
+ - name: Install or reuse the resolved Flutter SDK
135
+ if: ${{ inputs.dry-run != 'true' }}
136
+ uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2
137
+ with:
138
+ channel: ${{ steps.flutter-action.outputs.CHANNEL }}
139
+ flutter-version: ${{ steps.flutter-action.outputs.VERSION }}
140
+ flutter-version-file: ''
141
+ architecture: ${{ steps.flutter-action.outputs.ARCHITECTURE }}
142
+ cache: 'false'
143
+ cache-key: ${{ inputs.cache-key }}
144
+ cache-path: ${{ steps.flutter-action.outputs.CACHE-PATH }}
145
+ pub-cache: 'false'
146
+ pub-cache-key: ${{ inputs.pub-cache-key }}
147
+ pub-cache-path: ${{ steps.flutter-action.outputs.PUB-CACHE-PATH }}
148
+ dry-run: 'false'
149
+ git-source: ${{ inputs.git-source }}
@@ -0,0 +1,10 @@
1
+ // NODE_OPTIONS applies this only to the cache step, including its official post/save action.
2
+ try {
3
+ const scope = require('./cache_scope.cjs');
4
+ scope.authorize(process.argv[1], process.env);
5
+ scope.guardRequests();
6
+ for (const key of scope.proxyKeys) delete process.env[key];
7
+ } catch {
8
+ process.stderr.write('github_cache_transport_scope_refused\n');
9
+ process.exit(1);
10
+ }
@@ -0,0 +1,77 @@
1
+ // Direct routing belongs only to these pinned first-party cache programs and HTTPS origins.
2
+ const {readFileSync, copyFileSync, mkdtempSync, appendFileSync} = require('node:fs');
3
+ const {createHash} = require('node:crypto');
4
+ const {basename, dirname, join} = require('node:path');
5
+ const hashes = new Map([
6
+ ['restore', 'a49301428b4c5477522c00f6464566457ed327d64ae5f80f8c12e762fb2ea9a2'],
7
+ ['save', 'b969065795aaf5c6d20ba158e910be5d353eefb02eb2785263eb32e13090c066'],
8
+ ]);
9
+ const proxyKeys = ['HTTP_PROXY', 'HTTPS_PROXY', 'http_proxy', 'https_proxy', 'ALL_PROXY', 'all_proxy'];
10
+ const refusal = () => new Error('github_cache_transport_scope_refused');
11
+
12
+ function allowedHost(host) {
13
+ return host === 'api.github.com'
14
+ || /^[a-z0-9.-]+\.actions\.githubusercontent\.com$/.test(host)
15
+ || /^[a-z0-9]{3,24}\.blob\.core\.windows\.net$/.test(host);
16
+ }
17
+
18
+ function requireOrigin(value, service = false) {
19
+ let url;
20
+ try { url = new URL(value); } catch { throw refusal(); }
21
+ if (url.protocol !== 'https:' || url.port || url.username || url.password || !allowedHost(url.hostname)
22
+ || (service && !url.hostname.endsWith('.actions.githubusercontent.com'))) throw refusal();
23
+ return url;
24
+ }
25
+
26
+ function authorize(entrypoint, env) {
27
+ if (!entrypoint || basename(entrypoint) !== 'index.js' || basename(dirname(dirname(entrypoint))) !== 'dist') {
28
+ throw refusal();
29
+ }
30
+ const expected = hashes.get(basename(dirname(entrypoint)));
31
+ if (!expected || createHash('sha256').update(readFileSync(entrypoint)).digest('hex') !== expected) throw refusal();
32
+ const origins = [env.ACTIONS_RESULTS_URL, env.ACTIONS_CACHE_URL].filter(Boolean);
33
+ if (!origins.length) throw refusal();
34
+ for (const origin of origins) requireOrigin(origin, true);
35
+ }
36
+
37
+ function requireRequest(args, protocol) {
38
+ const [input, extra] = args;
39
+ const options = typeof input === 'string' || input instanceof URL
40
+ ? {protocol, ...require('node:url').urlToHttpOptions(requireOrigin(input)),
41
+ ...(typeof extra === 'object' ? extra : {})}
42
+ : {...input};
43
+ if (!options || options.socketPath || (options.protocol || protocol) !== 'https:') throw refusal();
44
+ for (const host of [options.hostname, options.host].filter(Boolean)) {
45
+ requireOrigin(`https://${host}${options.port ? ':' + options.port : ''}`);
46
+ }
47
+ if (!options.hostname && !options.host) throw refusal();
48
+ }
49
+
50
+ function guardRequests() {
51
+ for (const [name, protocol] of [['node:http', 'http:'], ['node:https', 'https:']]) {
52
+ const client = require(name);
53
+ for (const verb of ['request', 'get']) {
54
+ const original = client[verb];
55
+ client[verb] = function (...args) { requireRequest(args, protocol); return original.apply(this, args); };
56
+ }
57
+ }
58
+ if (globalThis.fetch) {
59
+ const original = globalThis.fetch;
60
+ globalThis.fetch = function (input, init) {
61
+ requireOrigin(typeof input === 'string' || input instanceof URL ? input : input.url);
62
+ return original.call(this, input, {...init, redirect: 'error'});
63
+ };
64
+ }
65
+ }
66
+
67
+ function snapshot() {
68
+ const target = mkdtempSync(join(process.env.RUNNER_TEMP, 'gowalk-cache-scope-'));
69
+ for (const file of ['cache_env.cjs', 'cache_scope.cjs']) copyFileSync(join(__dirname, file), join(target, file));
70
+ appendFileSync(process.env.GITHUB_OUTPUT, `preloader=${join(target, 'cache_env.cjs')}\n`);
71
+ }
72
+
73
+ module.exports = {authorize, guardRequests, proxyKeys, requireOrigin};
74
+ if (require.main === module) {
75
+ try { if (process.argv[2] !== '--snapshot') throw refusal(); snapshot(); }
76
+ catch { process.stderr.write('github_cache_scope_snapshot_failed\n'); process.exitCode = 1; }
77
+ }
@@ -0,0 +1,125 @@
1
+ """Real pinned SDK setup reuses restored bytes; cold/missed cache stays on the owned proxy."""
2
+ import hashlib
3
+ import io
4
+ import json
5
+ import os
6
+ from pathlib import Path
7
+ import subprocess
8
+ import sys
9
+ import tarfile
10
+ import tempfile
11
+ import threading
12
+ import unittest
13
+ import urllib.request
14
+ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
15
+
16
+ SCRIPT = Path(__file__).with_name("play_store_proxy.py")
17
+ SETUP_SHA = "35f0cd9e9c1d7a643448223573b4a03879e63676fdc27872de3d0104d5bf89f9"
18
+ SETUP_URL = ("https://raw.githubusercontent.com/subosito/flutter-action/"
19
+ "1a449444c387b1966244ae4d4f8c696479add0b2/setup.sh")
20
+
21
+
22
+ class SdkSetupTests(unittest.TestCase):
23
+ @classmethod
24
+ def setUpClass(cls):
25
+ fixture = os.environ.get("GOWALK_CACHE_ACTION_FIXTURES")
26
+ if fixture:
27
+ cls.source = (Path(fixture) / "flutter-setup.sh").read_bytes()
28
+ else:
29
+ with urllib.request.urlopen(SETUP_URL, timeout=30) as response:
30
+ cls.source = response.read(128 * 1024)
31
+ assert hashlib.sha256(cls.source).hexdigest() == SETUP_SHA
32
+
33
+ def setUp(self):
34
+ self.directory = tempfile.TemporaryDirectory()
35
+ self.addCleanup(self.directory.cleanup)
36
+ self.path = Path(self.directory.name)
37
+ self.setup = self.path / "setup.sh"
38
+ self.setup.write_bytes(self.source)
39
+ self.sdk = self.path / "sdk"
40
+ self.env = {**os.environ, "RUNNER_TEMP": str(self.path), "RUNNER_OS": "Linux", "RUNNER_ARCH": "X64",
41
+ "RUNNER_TOOL_CACHE": str(self.path / "tools"), "PUB_CACHE": str(self.path / "pub"),
42
+ "FLUTTER_STORAGE_BASE_URL": "http://provider.invalid", "GITHUB_ENV": str(self.path / "env"),
43
+ "GITHUB_OUTPUT": str(self.path / "output"), "GITHUB_PATH": str(self.path / "path")}
44
+ self.calls = []
45
+ body = io.BytesIO()
46
+ with tarfile.open(fileobj=body, mode="w:gz") as archive:
47
+ entry = tarfile.TarInfo("flutter/bin/flutter")
48
+ content = b"#!/bin/sh\nexit 0\n"
49
+ entry.size, entry.mode = len(content), 0o755
50
+ archive.addfile(entry, io.BytesIO(content))
51
+ self.archive = body.getvalue()
52
+ self.server = ThreadingHTTPServer(("127.0.0.1", 0), self.handler())
53
+ threading.Thread(target=self.server.serve_forever, daemon=True).start()
54
+ self.addCleanup(self.server.server_close)
55
+ self.addCleanup(self.server.shutdown)
56
+ self.env["GOOGLE_STORE_PROXY_URL"] = f"http://127.0.0.1:{self.server.server_port}"
57
+
58
+ def handler(self):
59
+ owner = self
60
+
61
+ class Handler(BaseHTTPRequestHandler):
62
+ def do_GET(self):
63
+ owner.calls.append(self.path)
64
+ if self.path.endswith(".json"):
65
+ data = json.dumps({"releases": [{"channel": "stable", "version": "3.44.0",
66
+ "dart_sdk_arch": "x64", "hash": "fixture", "sha256": "fixture",
67
+ "archive": "sdk.tar.gz"}]}).encode()
68
+ else:
69
+ data = owner.archive
70
+ self.send_response(200)
71
+ self.send_header("Content-Length", str(len(data)))
72
+ self.end_headers()
73
+ self.wfile.write(data)
74
+
75
+ def log_message(self, *_args):
76
+ pass
77
+
78
+ return Handler
79
+
80
+ def bootstrap(self):
81
+ result = subprocess.run([sys.executable, "-S", str(SCRIPT), "--bootstrap-env"],
82
+ env=self.env, capture_output=True, text=True, timeout=10)
83
+ if result.returncode == 0:
84
+ self.env.update(line.split("=", 1) for line in (self.path / "env").read_text().splitlines())
85
+ return result
86
+
87
+ def install(self):
88
+ return subprocess.run(["bash", str(self.setup), "-n", "3.44.0", "-a", "x64", "-c", str(self.sdk),
89
+ "stable"], env=self.env, capture_output=True, text=True, timeout=10)
90
+
91
+ def test_restored_sdk_fetches_only_manifest_through_proxy(self):
92
+ target = self.sdk / "flutter/bin/flutter"
93
+ target.parent.mkdir(parents=True)
94
+ target.write_text("#!/bin/sh\nexit 0\n")
95
+ target.chmod(0o755)
96
+ self.assertEqual(self.bootstrap().returncode, 0)
97
+ result = self.install()
98
+ self.assertEqual(result.returncode, 0, result.stderr)
99
+ self.assertEqual(self.calls, ["http://provider.invalid/flutter_infra_release/releases/releases_linux.json"])
100
+
101
+ def test_cold_cache_downloads_and_installs_sdk_through_proxy(self):
102
+ self.assertEqual(self.bootstrap().returncode, 0)
103
+ result = self.install()
104
+ self.assertEqual(result.returncode, 0, result.stderr)
105
+ self.assertEqual([url.rsplit("/", 1)[-1] for url in self.calls], ["releases_linux.json", "sdk.tar.gz"])
106
+ self.assertTrue(os.access(self.sdk / "flutter/bin/flutter", os.X_OK))
107
+
108
+ def test_provider_failure_after_cache_miss_still_fails_setup(self):
109
+ self.archive = b"not-a-valid-sdk-archive"
110
+ self.assertEqual(self.bootstrap().returncode, 0)
111
+ result = self.install()
112
+ self.assertNotEqual(result.returncode, 0)
113
+ self.assertFalse((self.sdk / "flutter/bin/flutter").exists())
114
+ self.assertEqual(len(self.calls), 2)
115
+
116
+ def test_missing_pin_refuses_before_manifest_even_with_restored_sdk(self):
117
+ self.env.pop("GOOGLE_STORE_PROXY_URL")
118
+ result = self.bootstrap()
119
+ self.assertNotEqual(result.returncode, 0)
120
+ self.assertFalse((self.path / "env").exists())
121
+ self.assertEqual(self.calls, [])
122
+
123
+
124
+ if __name__ == "__main__":
125
+ unittest.main()
@@ -16,16 +16,55 @@ class BootstrapWiringTests(unittest.TestCase):
16
16
  for job in workflow["jobs"].values():
17
17
  steps = job.get("steps", [])
18
18
  for index, step in enumerate(steps):
19
- if not step.get("uses", "").startswith("subosito/flutter-action@"):
19
+ if not step.get("uses", "").endswith("/android-app/flutter-setup"):
20
20
  continue
21
21
  checked += 1
22
- guard = steps[index - 1]
23
- self.assertEqual(guard.get("if"), step.get("if"))
24
22
  self.assertEqual(job["env"]["GOOGLE_STORE_PROXY_URL"],
25
23
  "${{ secrets.GOOGLE_STORE_PROXY_URL }}")
26
- self.assertTrue(guard["run"].endswith("play_store_proxy.py --bootstrap-env"))
27
- self.assertNotIn("continue-on-error", guard)
24
+ self.assertNotIn("continue-on-error", step)
28
25
  self.assertEqual(checked, 3)
26
+ setup = yaml.safe_load((ROOT / "android-action/flutter-setup/action.yml").read_text())
27
+ guard, scope, resolve, sdk_cache, pub_cache, install = setup["runs"]["steps"]
28
+ self.assertIn('play_store_proxy.py" --bootstrap-env', guard["run"])
29
+ self.assertNotIn("continue-on-error", guard)
30
+ self.assertTrue(scope["continue-on-error"])
31
+ self.assertIn("--snapshot", scope["run"])
32
+ self.assertEqual(resolve["with"]["dry-run"], "true")
33
+ for step in (resolve, install):
34
+ self.assertNotIn("env", step)
35
+ self.assertEqual(step["with"]["cache"], "false")
36
+ self.assertEqual(step["with"]["pub-cache"], "false")
37
+ self.assertNotIn("continue-on-error", step)
38
+ for step in (sdk_cache, pub_cache):
39
+ self.assertTrue(step["continue-on-error"])
40
+ self.assertIn("steps.scope.outputs.preloader", step["env"]["NODE_OPTIONS"])
41
+ self.assertEqual(step["env"]["SEGMENT_DOWNLOAD_TIMEOUT_MINS"], "1")
42
+
43
+ def test_official_flutter_custom_input_and_output_contract_is_preserved(self):
44
+ setup = yaml.safe_load((ROOT / "android-action/flutter-setup/action.yml").read_text())
45
+ inputs = {"channel", "flutter-version", "flutter-version-file", "architecture", "cache", "cache-key",
46
+ "cache-path", "pub-cache", "pub-cache-key", "pub-cache-path", "dry-run", "git-source"}
47
+ self.assertEqual(set(setup["inputs"]), inputs)
48
+ resolve = next(step for step in setup["runs"]["steps"] if step.get("id") == "flutter-action")
49
+ for name in inputs - {"cache", "pub-cache", "dry-run"}:
50
+ self.assertEqual(resolve["with"][name], "${{ inputs." + name + " }}")
51
+ self.assertEqual(setup["outputs"]["CACHE-HIT"]["value"], "${{ steps.cache-flutter.outputs.cache-hit }}")
52
+ self.assertEqual(setup["outputs"]["PUB-CACHE-HIT"]["value"], "${{ steps.cache-pub.outputs.cache-hit }}")
53
+
54
+ def test_pods_cache_keeps_identity_and_uses_the_same_optional_scope(self):
55
+ workflow = yaml.safe_load((ROOT / "templates/deploy.yml").read_text())
56
+ steps = workflow["jobs"]["ios"]["steps"]
57
+ cache = next(step for step in steps if step.get("name") == "Cache CocoaPods")
58
+ scope = next(step for step in steps if step.get("id") == "pods-cache-scope")
59
+ self.assertTrue(scope["continue-on-error"])
60
+ self.assertTrue(cache["continue-on-error"])
61
+ self.assertEqual(scope["if"], cache["if"])
62
+ self.assertIn("scripts/cache_scope.cjs --snapshot", scope["run"])
63
+ self.assertIn("steps.pods-cache-scope.outputs.preloader", cache["env"]["NODE_OPTIONS"])
64
+ self.assertEqual(cache["uses"], "actions/cache@caa296126883cff596d87d8935842f9db880ef25")
65
+ self.assertEqual(cache["with"]["path"], "ios/Pods\n~/Library/Caches/CocoaPods\n")
66
+ self.assertEqual(cache["with"]["key"],
67
+ "pods-${{ runner.os }}-${{ hashFiles('ios/Podfile.lock', 'pubspec.lock') }}")
29
68
 
30
69
  def test_remaining_flutter_commands_run_inside_the_owned_relay(self):
31
70
  groups = []
@@ -1 +1 @@
1
- 1.0.70
1
+ 1.0.71
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gowalk-cicd",
3
- "version": "1.0.70",
3
+ "version": "1.0.71",
4
4
  "description": "Zero-config GitHub Actions delivery for iOS TestFlight and Android Google Play.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -30,12 +30,7 @@ jobs:
30
30
  GOOGLE_STORE_PROXY_URL: ${{ secrets.GOOGLE_STORE_PROXY_URL }}
31
31
  steps:
32
32
  - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
33
- - name: Require the Google proxy before Flutter SDK setup
34
- shell: bash
35
- run: python3 .github/actions/android-app/scripts/play_store_proxy.py --bootstrap-env
36
- - uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0
37
- env:
38
- SEGMENT_DOWNLOAD_TIMEOUT_MINS: "1"
33
+ - uses: ./.github/actions/android-app/flutter-setup
39
34
  with:
40
35
  channel: stable
41
36
  cache: true
@@ -77,22 +77,25 @@ jobs:
77
77
  else
78
78
  echo "enabled=false" >> "$GITHUB_OUTPUT"
79
79
  fi
80
- - name: Require the Google proxy before Flutter SDK setup
81
- if: ${{ steps.flutter.outputs.enabled == 'true' }}
82
- shell: bash
83
- run: python3 .github/actions/android-app/scripts/play_store_proxy.py --bootstrap-env
84
80
  - name: Set up Flutter
85
81
  if: ${{ steps.flutter.outputs.enabled == 'true' }}
86
- uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0
87
- env:
88
- # Optional SDK/pub cache segments must finish promptly; normal SDK setup still fails on errors.
89
- SEGMENT_DOWNLOAD_TIMEOUT_MINS: "1"
82
+ uses: ./.github/actions/android-app/flutter-setup
90
83
  with:
91
84
  channel: stable
92
85
  cache: true
86
+ - name: Retain CocoaPods cache process scope
87
+ id: pods-cache-scope
88
+ if: ${{ steps.flutter.outputs.enabled == 'true' }}
89
+ continue-on-error: true
90
+ shell: bash
91
+ run: node .github/actions/android-app/scripts/cache_scope.cjs --snapshot
93
92
  - name: Cache CocoaPods
94
93
  if: ${{ steps.flutter.outputs.enabled == 'true' }}
95
- uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
94
+ uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5, same pinned cache process as Flutter
95
+ continue-on-error: true
96
+ env:
97
+ NODE_OPTIONS: --require "${{ steps.pods-cache-scope.outputs.preloader }}"
98
+ SEGMENT_DOWNLOAD_TIMEOUT_MINS: "1"
96
99
  with:
97
100
  path: |
98
101
  ios/Pods
@@ -335,15 +338,9 @@ jobs:
335
338
  distribution: temurin
336
339
  java-version: ${{ steps.jdk.outputs.version }}
337
340
  cache: gradle
338
- - name: Require the Google proxy before Flutter SDK setup
339
- if: ${{ steps.mode.outputs.mode == 'local' && steps.flutter.outputs.enabled == 'true' }}
340
- shell: bash
341
- run: python3 .github/actions/android-app/scripts/play_store_proxy.py --bootstrap-env
342
341
  - name: Set up Flutter
343
342
  if: ${{ steps.mode.outputs.mode == 'local' && steps.flutter.outputs.enabled == 'true' }}
344
- uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0
345
- env:
346
- SEGMENT_DOWNLOAD_TIMEOUT_MINS: "1"
343
+ uses: ./.github/actions/android-app/flutter-setup
347
344
  with:
348
345
  channel: stable
349
346
  cache: true