@daemux/store-automator 0.10.88 → 0.10.89
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-plugin/marketplace.json +2 -2
- package/package.json +1 -1
- package/plugins/store-automator/.claude-plugin/plugin.json +1 -1
- package/templates/scripts/ci/ios-native/auto_detect.py +117 -0
- package/templates/scripts/ci/ios-native/manage_marketing_version.py +12 -1
- package/templates/scripts/ci/ios-native/next_build_number.py +9 -0
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
},
|
|
6
6
|
"metadata": {
|
|
7
7
|
"description": "App Store & Google Play automation for Flutter apps",
|
|
8
|
-
"version": "0.10.
|
|
8
|
+
"version": "0.10.89"
|
|
9
9
|
},
|
|
10
10
|
"plugins": [
|
|
11
11
|
{
|
|
12
12
|
"name": "store-automator",
|
|
13
13
|
"source": "./plugins/store-automator",
|
|
14
14
|
"description": "3 agents for app store publishing: reviewer, meta-creator, media-designer",
|
|
15
|
-
"version": "0.10.
|
|
15
|
+
"version": "0.10.89",
|
|
16
16
|
"keywords": [
|
|
17
17
|
"flutter",
|
|
18
18
|
"app-store",
|
package/package.json
CHANGED
|
@@ -69,12 +69,32 @@ def _clear_caches() -> None:
|
|
|
69
69
|
_settings_cache.clear()
|
|
70
70
|
|
|
71
71
|
|
|
72
|
+
def _find_xcodegen_spec(workspace: Path) -> Path | None:
|
|
73
|
+
"""Locate an xcodegen spec file (project.yml / Project.yml / project.yaml).
|
|
74
|
+
|
|
75
|
+
xcodegen accepts any of these names. We check them in this order. Case
|
|
76
|
+
mismatch on case-sensitive filesystems has bitten us before.
|
|
77
|
+
"""
|
|
78
|
+
for candidate in ("project.yml", "project.yaml", "Project.yml", "Project.yaml"):
|
|
79
|
+
path = workspace / candidate
|
|
80
|
+
if path.is_file():
|
|
81
|
+
return path
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
|
|
72
85
|
def auto_detect_project(workspace: Path) -> tuple[str, str]:
|
|
73
86
|
"""Discover the repo-root Xcode project / workspace.
|
|
74
87
|
|
|
75
88
|
Returns ``(project, workspace_file)`` where exactly one is a non-empty
|
|
76
89
|
filename. ``("", "")`` means nothing was found.
|
|
90
|
+
|
|
91
|
+
If no ``.xcworkspace``/``.xcodeproj`` is present but a ``project.yml``
|
|
92
|
+
exists (xcodegen spec), we invoke ``xcodegen generate`` once to
|
|
93
|
+
materialize the ``.xcodeproj`` and retry. xcodegen-based projects
|
|
94
|
+
commonly gitignore the generated ``.xcodeproj``, so the runner sees
|
|
95
|
+
only the spec.
|
|
77
96
|
"""
|
|
97
|
+
log(f"auto-detect: scanning workspace={workspace}")
|
|
78
98
|
workspaces = sorted(workspace.glob("*.xcworkspace"))
|
|
79
99
|
if workspaces:
|
|
80
100
|
picked = _pick_by_basename(workspaces, workspace, "xcworkspace")
|
|
@@ -82,6 +102,21 @@ def auto_detect_project(workspace: Path) -> tuple[str, str]:
|
|
|
82
102
|
return "", picked.name
|
|
83
103
|
|
|
84
104
|
projects = sorted(workspace.glob("*.xcodeproj"))
|
|
105
|
+
if not projects:
|
|
106
|
+
spec = _find_xcodegen_spec(workspace)
|
|
107
|
+
if spec is not None:
|
|
108
|
+
log(f"auto-detect: xcodegen spec found at {spec.name}")
|
|
109
|
+
if _run_xcodegen(workspace):
|
|
110
|
+
projects = sorted(workspace.glob("*.xcodeproj"))
|
|
111
|
+
else:
|
|
112
|
+
# Surface what we DID see so CI logs are actionable when the
|
|
113
|
+
# expected spec file is missing / misnamed / gitignored.
|
|
114
|
+
entries = sorted(p.name for p in workspace.iterdir() if not p.name.startswith("."))
|
|
115
|
+
log(
|
|
116
|
+
f"auto-detect: no .xcodeproj / .xcworkspace / project.yml at "
|
|
117
|
+
f"{workspace}; root entries={entries[:20]}"
|
|
118
|
+
)
|
|
119
|
+
|
|
85
120
|
if not projects:
|
|
86
121
|
return "", ""
|
|
87
122
|
picked = _pick_by_basename(projects, workspace, "xcodeproj")
|
|
@@ -89,6 +124,88 @@ def auto_detect_project(workspace: Path) -> tuple[str, str]:
|
|
|
89
124
|
return picked.name, ""
|
|
90
125
|
|
|
91
126
|
|
|
127
|
+
def _run_xcodegen(workspace: Path) -> bool:
|
|
128
|
+
"""Run ``xcodegen generate`` in ``workspace``. Returns True on success.
|
|
129
|
+
|
|
130
|
+
Logs a notice on failure but never raises — the caller will see an
|
|
131
|
+
empty project/workspace result and the normal ``::error::`` path
|
|
132
|
+
will guide the user.
|
|
133
|
+
"""
|
|
134
|
+
log("auto-detect: no .xcodeproj/.xcworkspace found, xcodegen spec present — running xcodegen")
|
|
135
|
+
try:
|
|
136
|
+
result = subprocess.run(
|
|
137
|
+
["xcodegen", "generate"],
|
|
138
|
+
cwd=str(workspace),
|
|
139
|
+
capture_output=True,
|
|
140
|
+
text=True,
|
|
141
|
+
timeout=LIST_TIMEOUT,
|
|
142
|
+
)
|
|
143
|
+
except FileNotFoundError:
|
|
144
|
+
notice(
|
|
145
|
+
"xcodegen not on PATH — installing via Homebrew (one-shot). "
|
|
146
|
+
"If this fails, pre-install xcodegen or commit the generated .xcodeproj."
|
|
147
|
+
)
|
|
148
|
+
if not _install_xcodegen():
|
|
149
|
+
return False
|
|
150
|
+
try:
|
|
151
|
+
result = subprocess.run(
|
|
152
|
+
["xcodegen", "generate"],
|
|
153
|
+
cwd=str(workspace),
|
|
154
|
+
capture_output=True,
|
|
155
|
+
text=True,
|
|
156
|
+
timeout=LIST_TIMEOUT,
|
|
157
|
+
)
|
|
158
|
+
except (OSError, subprocess.TimeoutExpired) as exc:
|
|
159
|
+
notice(f"xcodegen still unavailable after install: {exc!r}")
|
|
160
|
+
return False
|
|
161
|
+
except (OSError, subprocess.TimeoutExpired) as exc:
|
|
162
|
+
notice(
|
|
163
|
+
f"xcodegen not available or timed out ({exc!r}); "
|
|
164
|
+
"install xcodegen or commit the generated .xcodeproj."
|
|
165
|
+
)
|
|
166
|
+
return False
|
|
167
|
+
|
|
168
|
+
if result.returncode != 0:
|
|
169
|
+
notice(
|
|
170
|
+
f"xcodegen generate failed (rc={result.returncode}): "
|
|
171
|
+
f"{result.stderr.strip() or result.stdout.strip()}"
|
|
172
|
+
)
|
|
173
|
+
return False
|
|
174
|
+
|
|
175
|
+
log("auto-detect: xcodegen generate succeeded")
|
|
176
|
+
return True
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _install_xcodegen() -> bool:
|
|
180
|
+
"""Install xcodegen via Homebrew. Returns True on success.
|
|
181
|
+
|
|
182
|
+
Called only when `xcodegen` is missing from PATH. macOS GitHub runners
|
|
183
|
+
normally preinstall it, but when they don't we need a self-bootstrap
|
|
184
|
+
path — otherwise the entire action fails at auto-detect for
|
|
185
|
+
xcodegen-based projects with no actionable error.
|
|
186
|
+
"""
|
|
187
|
+
try:
|
|
188
|
+
result = subprocess.run(
|
|
189
|
+
["brew", "install", "xcodegen"],
|
|
190
|
+
capture_output=True,
|
|
191
|
+
text=True,
|
|
192
|
+
timeout=300,
|
|
193
|
+
)
|
|
194
|
+
except (OSError, subprocess.TimeoutExpired) as exc:
|
|
195
|
+
notice(f"brew install xcodegen failed: {exc!r}")
|
|
196
|
+
return False
|
|
197
|
+
|
|
198
|
+
if result.returncode != 0:
|
|
199
|
+
notice(
|
|
200
|
+
f"brew install xcodegen failed (rc={result.returncode}): "
|
|
201
|
+
f"{result.stderr.strip() or result.stdout.strip()}"
|
|
202
|
+
)
|
|
203
|
+
return False
|
|
204
|
+
|
|
205
|
+
log("auto-detect: xcodegen installed via Homebrew")
|
|
206
|
+
return True
|
|
207
|
+
|
|
208
|
+
|
|
92
209
|
def _pick_by_basename(matches: list[Path], workspace: Path, label: str) -> Path:
|
|
93
210
|
"""Pick the match whose name stem equals the repo basename, else first."""
|
|
94
211
|
if len(matches) == 1:
|
|
@@ -70,7 +70,18 @@ def _result(decision: str, version: str, vid: str) -> dict:
|
|
|
70
70
|
def env(name: str) -> str:
|
|
71
71
|
val = os.environ.get(name)
|
|
72
72
|
if not val:
|
|
73
|
-
|
|
73
|
+
if name == "APP_STORE_APPLE_ID":
|
|
74
|
+
print(
|
|
75
|
+
"::error::APP_STORE_APPLE_ID is empty. This usually means "
|
|
76
|
+
"auto-detect could not resolve PRODUCT_BUNDLE_IDENTIFIER from "
|
|
77
|
+
"your Xcode project (check the auto-detect: ... log lines in the "
|
|
78
|
+
"'Resolve credentials + auto-detect' step above). Fix options: "
|
|
79
|
+
"(1) commit your .xcodeproj or add an xcodegen project.yml; "
|
|
80
|
+
"(2) pass `app-store-apple-id:` explicitly via the action's `with:` block.",
|
|
81
|
+
file=sys.stderr,
|
|
82
|
+
)
|
|
83
|
+
else:
|
|
84
|
+
print(f"::error::Missing required env var: {name}", file=sys.stderr)
|
|
74
85
|
raise SystemExit(1)
|
|
75
86
|
return val
|
|
76
87
|
|
|
@@ -22,6 +22,15 @@ from asc_common import get_json, make_jwt
|
|
|
22
22
|
def env(name: str) -> str:
|
|
23
23
|
val = os.environ.get(name)
|
|
24
24
|
if not val:
|
|
25
|
+
if name == "APP_STORE_APPLE_ID":
|
|
26
|
+
raise SystemExit(
|
|
27
|
+
"::error::APP_STORE_APPLE_ID is empty. This usually means "
|
|
28
|
+
"auto-detect could not resolve PRODUCT_BUNDLE_IDENTIFIER from "
|
|
29
|
+
"your Xcode project (check the auto-detect: ... log lines in the "
|
|
30
|
+
"'Resolve credentials + auto-detect' step above). Fix options: "
|
|
31
|
+
"(1) commit your .xcodeproj or add an xcodegen project.yml; "
|
|
32
|
+
"(2) pass `app-store-apple-id:` explicitly via the action's `with:` block."
|
|
33
|
+
)
|
|
25
34
|
raise SystemExit(f"missing env var: {name}")
|
|
26
35
|
return val
|
|
27
36
|
|