gowalk-cicd 1.0.59 → 1.0.60

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
@@ -96,6 +96,11 @@ node /path/to/gowalk-cicd/bin/cli.mjs
96
96
  - Keep the CLI surface minimal: `--dry-run`, `-v`, `-h`. Resist adding flags.
97
97
  - `.gitignore` in the consumer repo is git-tracked. Never auto-edit it.
98
98
  Detect and warn, tell the user to remove lines manually.
99
+ - Never repeat an `env:` key in another case (`NO_PROXY` beside `no_proxy`): GitHub
100
+ compares env keys case-insensitively and rejects the whole workflow or action file.
101
+ `test/env-keys.test.mjs` scans the templates and actions. Set lower-case spellings
102
+ or clear a lower-case bypass through `play_store_proxy.py -- <command>` (child
103
+ process) or `play_store_proxy.py --github-env` (later steps), never through YAML.
99
104
  - Store API calls belong in GitHub Actions. Local tests may build/sign but must
100
105
  never connect to App Store Connect or Google Play.
101
106
  - Every Apple/Google store API call, OAuth exchange and binary upload requires its
@@ -1 +1 @@
1
- 1.0.59
1
+ 1.0.60
@@ -1 +1 @@
1
- 1.0.59
1
+ 1.0.60
@@ -128,17 +128,16 @@ runs:
128
128
  if: ${{ steps.config.outputs.project_kind == 'flutter' }}
129
129
  shell: bash
130
130
  env:
131
+ # Only one spelling per key: GitHub compares env keys case-insensitively and
132
+ # rejects the file otherwise. play_store_proxy.py -- runs the build with both
133
+ # spellings set and both NO_PROXY spellings cleared for the child process.
131
134
  HTTPS_PROXY: ${{ env.GOOGLE_STORE_PROXY_URL }}
132
135
  HTTP_PROXY: ${{ env.GOOGLE_STORE_PROXY_URL }}
133
- https_proxy: ${{ env.GOOGLE_STORE_PROXY_URL }}
134
- http_proxy: ${{ env.GOOGLE_STORE_PROXY_URL }}
135
136
  NO_PROXY: ''
136
- no_proxy: ''
137
137
  BUILD_NAME: ${{ inputs.build-name }}
138
138
  DART_DEFINES: ${{ inputs.dart-defines }}
139
139
  DART_SYMBOLS_DIR: ${{ runner.temp }}/dart-symbols/android
140
140
  run: |
141
- python3 "${{ github.action_path }}/scripts/play_store_proxy.py"
142
141
  args=(
143
142
  build appbundle
144
143
  --release
@@ -158,7 +157,7 @@ runs:
158
157
  *) echo "::error::dart-defines entry '$define' is not KEY=VALUE"; exit 1 ;;
159
158
  esac
160
159
  done
161
- flutter "${args[@]}"
160
+ python3 "${{ github.action_path }}/scripts/play_store_proxy.py" -- flutter "${args[@]}"
162
161
  if ! ls "$DART_SYMBOLS_DIR"/*.symbols >/dev/null 2>&1; then
163
162
  echo "::error::flutter build wrote no Dart symbol files to $DART_SYMBOLS_DIR;" \
164
163
  "a crash from this obfuscated build could never be read. Refusing to ship it."
@@ -234,17 +233,17 @@ runs:
234
233
  shell: bash
235
234
  working-directory: ${{ steps.config.outputs.gradle_root || '.' }}
236
235
  env:
236
+ # Only one spelling per key: GitHub compares env keys case-insensitively and
237
+ # rejects the file otherwise. play_store_proxy.py -- runs the build with both
238
+ # spellings set and both NO_PROXY spellings cleared for the child process.
237
239
  HTTPS_PROXY: ${{ env.GOOGLE_STORE_PROXY_URL }}
238
240
  HTTP_PROXY: ${{ env.GOOGLE_STORE_PROXY_URL }}
239
- https_proxy: ${{ env.GOOGLE_STORE_PROXY_URL }}
240
- http_proxy: ${{ env.GOOGLE_STORE_PROXY_URL }}
241
241
  NO_PROXY: ''
242
- no_proxy: ''
243
242
  ANDROID_BUILD_NAME: ${{ inputs.build-name }}
244
243
  run: |
245
244
  chmod +x ./gradlew
246
- python3 "${{ github.action_path }}/scripts/play_store_proxy.py"
247
- ./gradlew "${ANDROID_GRADLE_MODULE}:bundleRelease" \
245
+ python3 "${{ github.action_path }}/scripts/play_store_proxy.py" -- \
246
+ ./gradlew "${ANDROID_GRADLE_MODULE}:bundleRelease" \
248
247
  --init-script "${{ github.action_path }}/scripts/version_override.init.gradle" \
249
248
  --console=plain --stacktrace
250
249
 
@@ -1,11 +1,27 @@
1
- """Google CI transport uses only the account-pinned proxy, including OAuth exchanges."""
1
+ """Google CI transport uses only the account-pinned proxy, including OAuth exchanges.
2
+
3
+ Three entry points, all from the one validated ``GOOGLE_STORE_PROXY_URL``:
4
+
5
+ * ``python3 play_store_proxy.py`` refuses a missing or malformed proxy.
6
+ * ``python3 play_store_proxy.py -- <command …>`` runs a build tool with the explicit
7
+ proxy in both spellings and both ``NO_PROXY`` spellings cleared. Setting the pairs
8
+ as YAML ``env`` keys is not an option: GitHub compares env keys case-insensitively
9
+ and rejects the workflow or action file as invalid.
10
+ * ``python3 play_store_proxy.py --github-env`` clears the lower-case ambient bypass
11
+ (``no_proxy``) for the rest of the job, so a JavaScript upload step that can only
12
+ carry upper-case ``env`` keys cannot be bypassed by a runner's ambient value.
13
+ """
2
14
  from __future__ import annotations
3
15
 
4
16
  import os
17
+ import subprocess
18
+ import sys
5
19
  from urllib.parse import urlsplit
6
20
 
7
21
  import requests
8
22
 
23
+ LOWERCASE_BYPASS = ("no_proxy",)
24
+
9
25
 
10
26
  def proxy_url() -> str:
11
27
  value = os.environ.get("GOOGLE_STORE_PROXY_URL", "").strip()
@@ -33,5 +49,37 @@ def session() -> requests.Session:
33
49
  return client
34
50
 
35
51
 
36
- if __name__ == "__main__":
52
+ def run(argv: list[str]) -> int:
53
+ """Run ``argv`` with the explicit proxy environment; the parent process is untouched."""
54
+ if not argv:
55
+ raise SystemExit("usage: play_store_proxy.py -- <command …>")
56
+ env = environment()
57
+ return subprocess.run(argv, env=env, check=False).returncode
58
+
59
+
60
+ def clear_github_env_bypass(path: str | None = None) -> list[str]:
61
+ """Append empty lower-case bypass assignments to ``$GITHUB_ENV`` for later steps."""
37
62
  proxy_url()
63
+ target = path or os.environ.get("GITHUB_ENV", "")
64
+ if not target:
65
+ raise SystemExit("GITHUB_ENV is not set; run this inside a GitHub Actions step")
66
+ lines = [f"{name}=" for name in LOWERCASE_BYPASS]
67
+ with open(target, "a", encoding="utf-8") as handle:
68
+ handle.write("".join(line + "\n" for line in lines))
69
+ return lines
70
+
71
+
72
+ def main(args: list[str]) -> int:
73
+ if args[:1] == ["--github-env"]:
74
+ clear_github_env_bypass()
75
+ return 0
76
+ if args[:1] == ["--"]:
77
+ return run(args[1:])
78
+ if args:
79
+ raise SystemExit("usage: play_store_proxy.py [--github-env | -- <command …>]")
80
+ proxy_url()
81
+ return 0
82
+
83
+
84
+ if __name__ == "__main__":
85
+ sys.exit(main(sys.argv[1:]))
@@ -35,6 +35,47 @@ class PlayProxyTests(unittest.TestCase):
35
35
  self.assertFalse(client.trust_env)
36
36
  self.assertEqual(client.proxies, {"http": value, "https": value})
37
37
 
38
+ def test_exec_form_gives_the_child_both_spellings_and_no_bypass(self):
39
+ value = "http://assigned.proxy.test:1234"
40
+ probe = ("import os, json; print(json.dumps({k: os.environ.get(k) for k in "
41
+ "['HTTPS_PROXY', 'https_proxy', 'HTTP_PROXY', 'http_proxy', 'NO_PROXY', 'no_proxy']}))")
42
+ with mock.patch.dict(os.environ, {"GOOGLE_STORE_PROXY_URL": value, "no_proxy": "*", "NO_PROXY": "*"}), \
43
+ mock.patch.object(play_store_proxy.subprocess, "run") as run:
44
+ run.return_value.returncode = 0
45
+ self.assertEqual(play_store_proxy.run([sys.executable, "-c", probe]), 0)
46
+ child = run.call_args.kwargs["env"]
47
+ self.assertEqual(child["https_proxy"], value)
48
+ self.assertEqual(child["HTTP_PROXY"], value)
49
+ self.assertEqual(child["no_proxy"], "")
50
+ self.assertEqual(child["NO_PROXY"], "")
51
+ self.assertEqual(os.environ["no_proxy"], "*")
52
+
53
+ def test_exec_form_refuses_without_a_proxy_before_running_anything(self):
54
+ with mock.patch.dict(os.environ, {"GOOGLE_STORE_PROXY_URL": ""}), \
55
+ mock.patch.object(play_store_proxy.subprocess, "run") as run:
56
+ with self.assertRaises(SystemExit):
57
+ play_store_proxy.run(["true"])
58
+ run.assert_not_called()
59
+
60
+ def test_github_env_mode_clears_only_the_lowercase_bypass_for_later_steps(self):
61
+ import tempfile
62
+ value = "http://assigned.proxy.test:1234"
63
+ with tempfile.TemporaryDirectory() as tmp:
64
+ target = os.path.join(tmp, "github.env")
65
+ with mock.patch.dict(os.environ, {"GOOGLE_STORE_PROXY_URL": value, "GITHUB_ENV": target, "no_proxy": "*"}):
66
+ self.assertEqual(play_store_proxy.clear_github_env_bypass(), ["no_proxy="])
67
+ self.assertEqual(os.environ["no_proxy"], "*")
68
+ with open(target, encoding="utf-8") as handle:
69
+ self.assertEqual(handle.read(), "no_proxy=\n")
70
+
71
+ def test_github_env_mode_refuses_without_a_proxy_or_target(self):
72
+ with mock.patch.dict(os.environ, {"GOOGLE_STORE_PROXY_URL": "", "GITHUB_ENV": "/nonexistent"}):
73
+ with self.assertRaises(SystemExit):
74
+ play_store_proxy.clear_github_env_bypass()
75
+ with mock.patch.dict(os.environ, {"GOOGLE_STORE_PROXY_URL": "http://assigned.proxy.test:1234", "GITHUB_ENV": ""}):
76
+ with self.assertRaises(SystemExit):
77
+ play_store_proxy.clear_github_env_bypass()
78
+
38
79
 
39
80
  if __name__ == "__main__":
40
81
  unittest.main()
@@ -1 +1 @@
1
- 1.0.59
1
+ 1.0.60
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gowalk-cicd",
3
- "version": "1.0.59",
3
+ "version": "1.0.60",
4
4
  "description": "Zero-config GitHub Actions delivery for iOS TestFlight and Android Google Play.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -453,6 +453,14 @@ jobs:
453
453
  # Note this also means nothing else should be creating edits against these
454
454
  # packages while a deploy runs — an external poller that opens and deletes an
455
455
  # edit will invalidate the one this step is holding.
456
+ # The upload action is JavaScript, so its proxy can only be the step's own
457
+ # upper-case env keys below. Node clients fall back to a lower-case no_proxy
458
+ # when NO_PROXY is empty, and GitHub refuses both spellings in one env map, so
459
+ # the ambient lower-case bypass is cleared through GITHUB_ENV instead.
460
+ - name: Clear ambient lower-case proxy bypass
461
+ if: ${{ steps.mode.outputs.mode == 'local' && steps.play.outputs.ready == 'true' }}
462
+ shell: bash
463
+ run: python3 .github/actions/android-app/scripts/play_store_proxy.py --github-env
456
464
  - name: Upload Android release to Google Play
457
465
  id: play-upload
458
466
  continue-on-error: true
@@ -461,8 +469,7 @@ jobs:
461
469
  env:
462
470
  HTTPS_PROXY: ${{ secrets.GOOGLE_STORE_PROXY_URL }}
463
471
  HTTP_PROXY: ${{ secrets.GOOGLE_STORE_PROXY_URL }}
464
- NO_PROXY: ''
465
- no_proxy: ''
472
+ NO_PROXY: '' # one spelling only: GitHub rejects env keys that differ by case
466
473
  with:
467
474
  serviceAccountJson: ${{ steps.android.outputs.play-service-account }}
468
475
  packageName: ${{ steps.android.outputs.package-name }}
@@ -490,8 +497,7 @@ jobs:
490
497
  env:
491
498
  HTTPS_PROXY: ${{ secrets.GOOGLE_STORE_PROXY_URL }}
492
499
  HTTP_PROXY: ${{ secrets.GOOGLE_STORE_PROXY_URL }}
493
- NO_PROXY: ''
494
- no_proxy: ''
500
+ NO_PROXY: '' # one spelling only: GitHub rejects env keys that differ by case
495
501
  with:
496
502
  serviceAccountJson: ${{ steps.android.outputs.play-service-account }}
497
503
  packageName: ${{ steps.android.outputs.package-name }}