gowalk-cicd 1.0.103 → 1.0.105
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 +6 -0
- package/README.md +10 -0
- package/action/.daemux-version +1 -1
- package/action/scripts/test_flutter_native_preparation.py +107 -0
- package/android-action/.daemux-version +1 -1
- package/backend-action/.daemux-version +1 -1
- package/backend-action/deploy_evidence.sh +3 -2
- package/package.json +1 -1
- package/templates/deploy.yml +7 -0
package/CLAUDE.md
CHANGED
|
@@ -204,6 +204,8 @@ node /path/to/gowalk-cicd/bin/cli.mjs
|
|
|
204
204
|
secret. The action writes it to host-side `.runtime.env` with mode 0600 and
|
|
205
205
|
supplies it to Compose after the generated `.env`; never print its content.
|
|
206
206
|
Backend failures emit `backend_deploy_failed` with the fixed runner/host phase and exit code.
|
|
207
|
+
Its message uses the exact schema/phase/exit envelope documented in README, without JSON braces
|
|
208
|
+
that multiline secret masking can erase. Never reconstruct masked fields or include arbitrary text.
|
|
207
209
|
Native bundle/Pods failures emit `native_prepare_failed` with fixed refusal codes and observed
|
|
208
210
|
signals. Unknown signals remain unclassified; never copy child output or commands into annotations.
|
|
209
211
|
- The mobile workflow's feature-branch-only `backend-preview` job bootstraps a
|
|
@@ -262,6 +264,10 @@ node /path/to/gowalk-cicd/bin/cli.mjs
|
|
|
262
264
|
Both Flutter configuration and the Pods helper use that bundle in their child process,
|
|
263
265
|
preserving Podfile.lock and the parent job environment. Deliver the workflow wrapper and
|
|
264
266
|
action helper together; action-only updates cannot repair earlier custom configuration steps.
|
|
267
|
+
Flutter `--config-only` can query Xcode and install Pods before returning. In the workflow,
|
|
268
|
+
generate Flutter inputs with `pub get`, run the locked `native_pods.py` preparation and
|
|
269
|
+
completed graph guard, then configure the release with its existing obfuscation/define flags.
|
|
270
|
+
The graph guard still applies when there is no Podfile. Preserve this order in custom CI.
|
|
265
271
|
- Locked Pod installation retries once only when its own attempt reports curl 18 and a partial-transfer
|
|
266
272
|
symptom. Both attempts share the existing 900-second budget and recheck Podfile, Podfile.lock and
|
|
267
273
|
selected Ruby bundle inputs. TLS/auth/permission, checksum, version and deployment errors never retry.
|
package/README.md
CHANGED
|
@@ -58,6 +58,12 @@ When adopting this update, retain the `native_bundle.py` configuration wrapper i
|
|
|
58
58
|
custom workflows as well as updating the action files. Apps without a Gemfile keep
|
|
59
59
|
their existing CocoaPods runtime.
|
|
60
60
|
|
|
61
|
+
Flutter `--config-only` can query Xcode and invoke `pod install`. The workflow first
|
|
62
|
+
runs `flutter pub get` to generate the Podfile's Flutter inputs, then installs locked
|
|
63
|
+
Pods and verifies the selected workspace before release configuration. Preserve this
|
|
64
|
+
order in customized validation and deployment workflows; updating action directories
|
|
65
|
+
alone cannot repair a configuration step that runs before the action.
|
|
66
|
+
|
|
61
67
|
## Flutter web release
|
|
62
68
|
|
|
63
69
|
The conditional `deploy-web.yml` workflow runs on changes to Flutter source,
|
|
@@ -336,6 +342,10 @@ Native bundle and Pods failures emit `native_prepare_failed` with schema
|
|
|
336
342
|
Backend failures emit `backend_deploy_failed` with schema `gowalk-cicd/backend-deploy-failed.v1`,
|
|
337
343
|
the runner/host `phase` and `exit_code`. These receipts preserve the failed result and exclude
|
|
338
344
|
private command output, commands, hosts and runtime credentials. Empty signals are unclassified.
|
|
345
|
+
The backend message is `gowalk-cicd/backend-deploy-failed.v1 phase=<phase> exit_code=<code>`:
|
|
346
|
+
fixed fields without JSON braces, which GitHub can mask when a multiline secret has brace-only lines.
|
|
347
|
+
Consumers validate the complete envelope, the known phase and exit code 1–255; older JSON messages
|
|
348
|
+
remain readable by the panel. Masked fields are never reconstructed and credential masking stays enabled.
|
|
339
349
|
An interrupted locked Pod fetch can retry once within the existing 900-second install budget.
|
|
340
350
|
`native_pods_retry` (`gowalk-cicd/native-pods-retry.v1`) reports `attempt:2` and `reason:partial_transfer`;
|
|
341
351
|
successful preparation reports `pod_attempts`. Dependency locks, relay and TLS checks remain enforced.
|
package/action/.daemux-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.0.
|
|
1
|
+
1.0.105
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Execute the shipped Flutter preparation shell with real guards and offline tool fixtures."""
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
import tempfile
|
|
8
|
+
import textwrap
|
|
9
|
+
import unittest
|
|
10
|
+
|
|
11
|
+
import native_bundle
|
|
12
|
+
from test_native_dependency_guard import project
|
|
13
|
+
|
|
14
|
+
REPO = Path(__file__).resolve().parents[2]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class FlutterPreparationTests(unittest.TestCase):
|
|
18
|
+
def setUp(self):
|
|
19
|
+
temporary = tempfile.TemporaryDirectory(prefix="flutter-native-preparation-")
|
|
20
|
+
self.addCleanup(temporary.cleanup)
|
|
21
|
+
self.root = Path(temporary.name).resolve()
|
|
22
|
+
project(self.root, "ios/Runner.xcodeproj")
|
|
23
|
+
workspace = self.root / "ios/Runner.xcworkspace"
|
|
24
|
+
workspace.mkdir()
|
|
25
|
+
(workspace / "contents.xcworkspacedata").write_text(
|
|
26
|
+
'<Workspace><FileRef location="group:Runner.xcodeproj"/></Workspace>')
|
|
27
|
+
for name in ("Podfile", "Podfile.lock"):
|
|
28
|
+
(self.root / "ios" / name).write_text("fixture lock\n")
|
|
29
|
+
(self.root / "pubspec.yaml").write_text("name: fixture\n")
|
|
30
|
+
for argv in (["git", "init", "-q"], ["git", "add", "."],
|
|
31
|
+
["git", "-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid",
|
|
32
|
+
"commit", "-qm", "Freeze native fixture"]):
|
|
33
|
+
subprocess.run(argv, cwd=self.root, capture_output=True, check=True)
|
|
34
|
+
actions = self.root / ".github/actions"
|
|
35
|
+
actions.mkdir(parents=True)
|
|
36
|
+
(actions / "swift-app").symlink_to(REPO / "action", target_is_directory=True)
|
|
37
|
+
(actions / "android-app").symlink_to(REPO / "android-action", target_is_directory=True)
|
|
38
|
+
self.env = {**os.environ, "PATH": str(self.root) + os.pathsep + os.environ["PATH"],
|
|
39
|
+
"GITHUB_WORKSPACE": str(self.root), "RUNNER_TEMP": str(self.root / "scratch"),
|
|
40
|
+
"ONBOARDING_PUBLIC_ORIGIN": "https://flow.example.invalid",
|
|
41
|
+
"GOOGLE_STORE_PROXY_URL": "http://fixture:PRIVATE_SENTINEL@127.0.0.1:9"}
|
|
42
|
+
self.install_tools()
|
|
43
|
+
|
|
44
|
+
def executable(self, name, source):
|
|
45
|
+
path = self.root / name
|
|
46
|
+
path.write_text("#!" + sys.executable + "\n" + textwrap.dedent(source))
|
|
47
|
+
path.chmod(0o700)
|
|
48
|
+
|
|
49
|
+
def install_tools(self):
|
|
50
|
+
self.executable("flutter", '''
|
|
51
|
+
import json,sys
|
|
52
|
+
from pathlib import Path
|
|
53
|
+
if sys.argv[1:3] == ["pub", "get"]:
|
|
54
|
+
folder=Path("ios/Flutter");folder.mkdir()
|
|
55
|
+
(folder/"Generated.xcconfig").write_text("generated")
|
|
56
|
+
elif sys.argv[1:3] == ["build", "ios"]:
|
|
57
|
+
Path("configuration.json").write_text(json.dumps(sys.argv[1:]))
|
|
58
|
+
assert Path("ios/pods-ready").is_file(), "Configuration reached before locked Pods"
|
|
59
|
+
else:
|
|
60
|
+
raise SystemExit(9)
|
|
61
|
+
''')
|
|
62
|
+
self.executable("pod", '''
|
|
63
|
+
import json,os,sys
|
|
64
|
+
from pathlib import Path
|
|
65
|
+
assert Path("Flutter/Generated.xcconfig").is_file()
|
|
66
|
+
assert sys.argv[1:] == ["install", "--deployment", "--no-repo-update"]
|
|
67
|
+
assert os.environ["HTTPS_PROXY"].startswith("http://127.0.0.1:")
|
|
68
|
+
Path("pod-called").touch()
|
|
69
|
+
if os.environ.get("FAIL_PODS"):
|
|
70
|
+
raise SystemExit(7)
|
|
71
|
+
Path("pods-ready").touch()
|
|
72
|
+
''')
|
|
73
|
+
|
|
74
|
+
def run_preparation(self):
|
|
75
|
+
body = (REPO / "templates/deploy.yml").read_text().split(
|
|
76
|
+
"- name: Prepare Flutter iOS project\n", 1)[1].split("\n - ", 1)[0]
|
|
77
|
+
script = textwrap.dedent(body.split(" run: |\n", 1)[1])
|
|
78
|
+
result = subprocess.run([sys.executable, native_bundle.__file__, "--folder", "ios",
|
|
79
|
+
"--proxy-helper", str(REPO / "android-action/scripts/play_store_proxy.py"),
|
|
80
|
+
"--", "bash", "--noprofile", "--norc", "-e", "-o", "pipefail", "-c", script],
|
|
81
|
+
cwd=self.root, env=self.env, capture_output=True, text=True, timeout=20)
|
|
82
|
+
self.assertNotIn("PRIVATE_SENTINEL", result.stdout + result.stderr)
|
|
83
|
+
return result
|
|
84
|
+
|
|
85
|
+
def test_locked_pods_precede_configuration_without_losing_release_arguments(self):
|
|
86
|
+
result = self.run_preparation()
|
|
87
|
+
self.assertEqual(result.returncode, 0, result.stdout)
|
|
88
|
+
self.assertTrue((self.root / "ios/pod-called").exists())
|
|
89
|
+
args = json.loads((self.root / "configuration.json").read_text())
|
|
90
|
+
for arg in ("--release", "--no-codesign", "--config-only", "--obfuscate",
|
|
91
|
+
"--split-debug-info=" + str(self.root / "scratch/dart-symbols/ios"),
|
|
92
|
+
"ONBOARDING_PUBLIC_ORIGIN=https://flow.example.invalid"):
|
|
93
|
+
self.assertIn(arg, args)
|
|
94
|
+
self.assertEqual((self.root / "ios/Podfile.lock").read_text(), "fixture lock\n")
|
|
95
|
+
|
|
96
|
+
def test_failed_locked_install_stops_before_flutter_configuration(self):
|
|
97
|
+
self.env["FAIL_PODS"] = "1"
|
|
98
|
+
self.assertNotEqual(self.run_preparation().returncode, 0)
|
|
99
|
+
self.assertTrue((self.root / "ios/pod-called").exists())
|
|
100
|
+
self.assertFalse((self.root / "configuration.json").exists())
|
|
101
|
+
|
|
102
|
+
def test_remote_package_graph_refuses_before_configuration_without_podfile(self):
|
|
103
|
+
(self.root / "ios/Podfile").unlink()
|
|
104
|
+
project(self.root, "ios/Runner.xcodeproj", {"remote": {"isa": "XCRemoteSwiftPackageReference"}})
|
|
105
|
+
self.assertNotEqual(self.run_preparation().returncode, 0)
|
|
106
|
+
self.assertFalse((self.root / "ios/pod-called").exists())
|
|
107
|
+
self.assertFalse((self.root / "configuration.json").exists())
|
|
@@ -1 +1 @@
|
|
|
1
|
-
1.0.
|
|
1
|
+
1.0.105
|
|
@@ -1 +1 @@
|
|
|
1
|
-
1.0.
|
|
1
|
+
1.0.105
|
|
@@ -8,8 +8,9 @@ backend_report_failure() {
|
|
|
8
8
|
remote_setup|initialize_environment|compose_up|discover_port|local_health|configure_vhost|certificate|public_health) ;;
|
|
9
9
|
*) phase=unclassified ;;
|
|
10
10
|
esac
|
|
11
|
-
|
|
12
|
-
'
|
|
11
|
+
# Multiline JSON secrets can make GitHub mask standalone braces. Keep fixed evidence readable.
|
|
12
|
+
printf '::error title=backend_deploy_failed::gowalk-cicd/backend-deploy-failed.v1 phase=%s exit_code=%s\n' \
|
|
13
|
+
"$phase" "$status"
|
|
13
14
|
return "$status"
|
|
14
15
|
}
|
|
15
16
|
|
package/package.json
CHANGED
package/templates/deploy.yml
CHANGED
|
@@ -149,6 +149,13 @@ jobs:
|
|
|
149
149
|
ONBOARDING_PUBLIC_ORIGIN: ${{ vars.ONBOARDING_PUBLIC_ORIGIN }}
|
|
150
150
|
run: |
|
|
151
151
|
flutter pub get
|
|
152
|
+
# Even --config-only can query Xcode and run pod install. Pub generates
|
|
153
|
+
# the Flutter configuration needed by the Podfile without resolving Pods.
|
|
154
|
+
python3 .github/actions/swift-app/scripts/native_pods.py "$GITHUB_WORKSPACE" \
|
|
155
|
+
--container ios/Runner.xcworkspace \
|
|
156
|
+
--proxy-helper .github/actions/android-app/scripts/play_store_proxy.py
|
|
157
|
+
python3 .github/actions/swift-app/scripts/native_dependency_guard.py "$GITHUB_WORKSPACE" \
|
|
158
|
+
--container ios/Runner.xcworkspace
|
|
152
159
|
# Flutter removed the synthetic `package:flutter_gen`, so apps that use
|
|
153
160
|
# AppLocalizations now generate real sources. Nothing else generates them,
|
|
154
161
|
# and analyze/compile fail on the missing import if we skip this.
|