@daemux/store-automator 0.10.86 → 0.10.87

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.
@@ -5,14 +5,14 @@
5
5
  },
6
6
  "metadata": {
7
7
  "description": "App Store & Google Play automation for Flutter apps",
8
- "version": "0.10.86"
8
+ "version": "0.10.87"
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.86",
15
+ "version": "0.10.87",
16
16
  "keywords": [
17
17
  "flutter",
18
18
  "app-store",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@daemux/store-automator",
3
- "version": "0.10.86",
3
+ "version": "0.10.87",
4
4
  "description": "Full App Store & Google Play automation for Flutter apps with Claude Code agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "store-automator",
3
- "version": "0.10.86",
3
+ "version": "0.10.87",
4
4
  "description": "App Store & Google Play automation agents for Flutter app publishing",
5
5
  "author": {
6
6
  "name": "Daemux"
@@ -1,6 +1,8 @@
1
1
  # iOS Native TestFlight CI — Setup Guide
2
2
 
3
- Drop-in TestFlight automation for native Swift/SwiftUI iOS apps. One workflow, one config file, one credential file. All logic lives in the shared composite action `daemux/daemux-plugins/.github/actions/ios-native-testflight`.
3
+ Drop-in TestFlight automation for native Swift/SwiftUI iOS apps. One workflow, one credential file and optionally a config file. All logic lives in the shared composite action `daemux/daemux-plugins/.github/actions/ios-native-testflight`.
4
+
5
+ **As of v0.18.0, `ci.config.yaml` is entirely optional.** With just `creds/AuthKey_*.p8` at the repo root and the 18-line `deploy.yml`, CI auto-detects your project, workspace, scheme, bundle id, team id, and App Store Connect app id. Add a `ci.config.yaml` only when you need to override auto-detection (e.g., multiple `.xcodeproj` at root, a non-application scheme, a pinned configuration).
4
6
 
5
7
  ## Prerequisites
6
8
 
@@ -13,14 +15,16 @@ Drop-in TestFlight automation for native Swift/SwiftUI iOS apps. One workflow, o
13
15
 
14
16
  Copy `.github/workflows/deploy.yml` verbatim from this template. No edits required. It triggers on push to `main` and via `workflow_dispatch`.
15
17
 
16
- ## Step 2 — Copy `ci.config.yaml`
18
+ ## Step 2 — (Optional) Copy `ci.config.yaml`
17
19
 
18
- Copy `ios-native-ci.config.yaml.template` to your repo root as `ci.config.yaml`. Fill in:
20
+ **Skip this step entirely** unless you need to override auto-detection. The CI figures out sane defaults for every field from your Xcode project.
19
21
 
20
- - `app.bundle_id` REQUIRED (must match your App Store Connect app record).
21
- - `xcode.scheme` — REQUIRED (the shared scheme the CI should archive).
22
+ If you want explicit control, copy `ios-native-ci.config.yaml.template` to your repo root as `ci.config.yaml` and set just the fields you want to pin:
22
23
 
23
- Every other field has a sensible default. `xcode.project` auto-detects if there is exactly one `*.xcodeproj` at the repo root. `app.app_store_apple_id` is auto-discovered via the ASC API from the bundle id.
24
+ - `xcode.project` if you have multiple `*.xcodeproj` at the repo root.
25
+ - `xcode.scheme` — if you have multiple application-type schemes.
26
+ - `app.bundle_id` — if auto-detect picks the wrong one (rare).
27
+ - Everything else has a sensible default.
24
28
 
25
29
  ## Step 3 — Drop in the ASC API key
26
30
 
@@ -49,6 +53,16 @@ Leave your Xcode project's `MARKETING_VERSION` at `1.0` initially. CI auto-rolls
49
53
 
50
54
  ## Step 6 — Push to `main`
51
55
 
56
+ Minimal (no config file):
57
+
58
+ ```bash
59
+ git add .github/workflows/deploy.yml creds/AuthKey_*.p8
60
+ git commit -m "ci: add iOS TestFlight pipeline"
61
+ git push origin main
62
+ ```
63
+
64
+ With an optional config file:
65
+
52
66
  ```bash
53
67
  git add .github/workflows/deploy.yml ci.config.yaml creds/AuthKey_*.p8
54
68
  git commit -m "ci: add iOS TestFlight pipeline"
@@ -1,16 +1,29 @@
1
- # iOS TestFlight CI configuration — read by daemux/daemux-plugins/.github/actions/ios-native-testflight
2
- # Place this at the repo root as ci.config.yaml.
3
- # Also place your ASC API key at creds/AuthKey_<KEY_ID>_Issuer_<ISSUER_UUID>.p8
1
+ # iOS TestFlight CI configuration — OPTIONAL
2
+ #
3
+ # This file is entirely OPTIONAL. Omit it to let CI auto-detect everything
4
+ # from your Xcode project:
5
+ #
6
+ # - xcode.project -> glob *.xcodeproj / *.xcworkspace at repo root
7
+ # - xcode.scheme -> pick the single application-type scheme
8
+ # - app.bundle_id -> PRODUCT_BUNDLE_IDENTIFIER from xcodebuild
9
+ # - app.team_id -> derived from your ASC API key
10
+ # - app.app_store_apple_id -> looked up via ASC API by bundle_id
11
+ #
12
+ # Set any subset of values below to override auto-detection. All fields are
13
+ # optional; only add what you need to disambiguate or pin.
14
+ #
15
+ # Consumed by daemux/daemux-plugins/.github/actions/ios-native-testflight.
16
+ # Required regardless: creds/AuthKey_<KEY_ID>_Issuer_<ISSUER_UUID>.p8
4
17
 
5
18
  app:
6
- bundle_id: "com.example.myapp" # REQUIRED
7
- team_id: "ABCDE12345" # optional — auto-derived from ASC API key when omitted
8
- app_store_apple_id: "" # optional — auto-discovered via ASC API by bundle_id
19
+ bundle_id: "com.example.myapp" # optional — auto-detected from xcodebuild
20
+ team_id: "ABCDE12345" # optional — auto-derived from ASC API key
21
+ app_store_apple_id: "" # optional — auto-discovered via ASC API
9
22
 
10
23
  xcode:
11
- project: "" # auto-detected if exactly one *.xcodeproj at root
12
- workspace: "" # wins over project when set
13
- scheme: "MyApp" # REQUIREDno safe auto-detect
24
+ project: "" # optional — auto-detected (*.xcodeproj at root)
25
+ workspace: "" # optional — wins over project when set
26
+ scheme: "MyApp" # optionalauto-picks application-type scheme
14
27
  configuration: "Release" # default: Release
15
28
  profile_name: "" # default: "<scheme> CI"
16
29
 
@@ -0,0 +1,274 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Xcode project / scheme / bundle_id auto-detection.
4
+
5
+ Ported from gowalk-step/fastlane_standalone.sh — keeps us in Python (our
6
+ ecosystem) while still shelling out to ``xcodebuild`` for the two queries
7
+ only it can reliably answer:
8
+
9
+ ``xcodebuild -list -json`` -> enumerate schemes
10
+ ``xcodebuild -showBuildSettings -json`` -> PRODUCT_TYPE + bundle id
11
+
12
+ The detection lets ``ci.config.yaml`` become entirely optional: with just
13
+ ``creds/AuthKey_*.p8`` + the 18-line ``deploy.yml`` the pipeline can still
14
+ figure out what to build.
15
+
16
+ Rules (kept intentionally boring — if auto-detect is ambiguous we log a
17
+ warning and return a deterministic choice, falling back to a clear
18
+ ``::error::`` only when truly nothing works):
19
+
20
+ 1. ``auto_detect_project(workspace)``
21
+ - Prefer a single ``*.xcworkspace`` at the repo root.
22
+ - Else a single ``*.xcodeproj``.
23
+ - When multiple ``.xcodeproj`` exist, prefer one matching the repo
24
+ basename, else alphabetically first (with a warning).
25
+
26
+ 2. ``auto_detect_scheme(workspace, project, workspace_file)``
27
+ - ``xcodebuild -list -json`` to enumerate schemes.
28
+ - For each scheme, ``-showBuildSettings -json`` and keep those whose
29
+ ``PRODUCT_TYPE == com.apple.product-type.application``.
30
+ - Single application scheme -> that one. Multiple: prefer repo-basename
31
+ match, else alphabetical first.
32
+
33
+ 3. ``auto_detect_bundle_id(workspace, project, workspace_file, scheme, configuration)``
34
+ - Read ``PRODUCT_BUNDLE_IDENTIFIER`` from the same ``-showBuildSettings``
35
+ invocation (cached from step 2 when possible).
36
+ - Reject unresolved variable references like ``$(PRODUCT_NAME)``.
37
+
38
+ All xcodebuild invocations are cached per-process so repeated calls in the
39
+ same ``read_config.py`` run don't re-shell.
40
+ """
41
+
42
+ from __future__ import annotations
43
+
44
+ import json
45
+ import subprocess
46
+ from pathlib import Path
47
+ from typing import Optional
48
+
49
+ from cfg_io import log, notice
50
+
51
+
52
+ APP_PRODUCT_TYPE = "com.apple.product-type.application"
53
+ # Timeouts (seconds) — xcodebuild can hang on missing toolchains or
54
+ # package resolution. CI should fail fast rather than spin forever.
55
+ LIST_TIMEOUT = 60
56
+ SETTINGS_TIMEOUT = 120
57
+
58
+ # Process-local caches keyed by (project, workspace_file) or
59
+ # (project, workspace_file, scheme, configuration). Cleared between test
60
+ # cases via ``_clear_caches`` but otherwise persist for the lifetime of
61
+ # the read_config.py process.
62
+ _list_cache: dict[tuple, Optional[list[str]]] = {}
63
+ _settings_cache: dict[tuple, Optional[dict]] = {}
64
+
65
+
66
+ def _clear_caches() -> None:
67
+ """Reset caches — used by tests. Not part of the public API."""
68
+ _list_cache.clear()
69
+ _settings_cache.clear()
70
+
71
+
72
+ def auto_detect_project(workspace: Path) -> tuple[str, str]:
73
+ """Discover the repo-root Xcode project / workspace.
74
+
75
+ Returns ``(project, workspace_file)`` where exactly one is a non-empty
76
+ filename. ``("", "")`` means nothing was found.
77
+ """
78
+ workspaces = sorted(workspace.glob("*.xcworkspace"))
79
+ if workspaces:
80
+ picked = _pick_by_basename(workspaces, workspace, "xcworkspace")
81
+ log(f"auto-detect: workspace={picked.name}")
82
+ return "", picked.name
83
+
84
+ projects = sorted(workspace.glob("*.xcodeproj"))
85
+ if not projects:
86
+ return "", ""
87
+ picked = _pick_by_basename(projects, workspace, "xcodeproj")
88
+ log(f"auto-detect: project={picked.name}")
89
+ return picked.name, ""
90
+
91
+
92
+ def _pick_by_basename(matches: list[Path], workspace: Path, label: str) -> Path:
93
+ """Pick the match whose name stem equals the repo basename, else first."""
94
+ if len(matches) == 1:
95
+ return matches[0]
96
+ repo_base = workspace.resolve().name.lower()
97
+ for match in matches:
98
+ if match.stem.lower() == repo_base:
99
+ return match
100
+ notice(
101
+ f"multiple *.{label} found; picking {matches[0].name} alphabetically. "
102
+ f"Set xcode.project in ci.config.yaml to disambiguate."
103
+ )
104
+ return matches[0]
105
+
106
+
107
+ def _list_schemes(
108
+ workspace: Path, project: str, workspace_file: str
109
+ ) -> Optional[list[str]]:
110
+ """Run ``xcodebuild -list -json`` and return the schemes list (cached)."""
111
+ key = (project, workspace_file)
112
+ if key in _list_cache:
113
+ return _list_cache[key]
114
+
115
+ cmd = ["xcodebuild", "-list", "-json"]
116
+ if workspace_file:
117
+ cmd += ["-workspace", workspace_file]
118
+ elif project:
119
+ cmd += ["-project", project]
120
+ else:
121
+ _list_cache[key] = None
122
+ return None
123
+
124
+ try:
125
+ result = subprocess.run(
126
+ cmd, cwd=str(workspace), capture_output=True, text=True,
127
+ timeout=LIST_TIMEOUT,
128
+ )
129
+ except (OSError, subprocess.TimeoutExpired) as exc:
130
+ log(f"auto-detect: xcodebuild -list failed: {exc!r}")
131
+ _list_cache[key] = None
132
+ return None
133
+
134
+ if result.returncode != 0:
135
+ log(
136
+ f"auto-detect: xcodebuild -list returned {result.returncode}: "
137
+ f"{result.stderr.strip()}"
138
+ )
139
+ _list_cache[key] = None
140
+ return None
141
+
142
+ try:
143
+ data = json.loads(result.stdout)
144
+ except json.JSONDecodeError as exc:
145
+ log(f"auto-detect: xcodebuild -list produced invalid JSON: {exc!r}")
146
+ _list_cache[key] = None
147
+ return None
148
+
149
+ # Workspace output has {"workspace": {"schemes": [...]}}; project has
150
+ # {"project": {"schemes": [...]}}.
151
+ container = data.get("workspace") or data.get("project") or {}
152
+ schemes = container.get("schemes") or []
153
+ _list_cache[key] = list(schemes)
154
+ return _list_cache[key]
155
+
156
+
157
+ def _show_build_settings(
158
+ workspace: Path,
159
+ project: str,
160
+ workspace_file: str,
161
+ scheme: str,
162
+ configuration: str,
163
+ ) -> Optional[dict]:
164
+ """Run ``xcodebuild -showBuildSettings -json`` and return buildSettings (cached)."""
165
+ key = (project, workspace_file, scheme, configuration or "Release")
166
+ if key in _settings_cache:
167
+ return _settings_cache[key]
168
+
169
+ cmd = ["xcodebuild", "-showBuildSettings", "-json", "-scheme", scheme]
170
+ if configuration:
171
+ cmd += ["-configuration", configuration]
172
+ if workspace_file:
173
+ cmd += ["-workspace", workspace_file]
174
+ elif project:
175
+ cmd += ["-project", project]
176
+ else:
177
+ _settings_cache[key] = None
178
+ return None
179
+
180
+ try:
181
+ result = subprocess.run(
182
+ cmd, cwd=str(workspace), capture_output=True, text=True,
183
+ timeout=SETTINGS_TIMEOUT,
184
+ )
185
+ except (OSError, subprocess.TimeoutExpired) as exc:
186
+ log(f"auto-detect: showBuildSettings({scheme!r}) failed: {exc!r}")
187
+ _settings_cache[key] = None
188
+ return None
189
+
190
+ if result.returncode != 0:
191
+ log(
192
+ f"auto-detect: showBuildSettings({scheme!r}) returned "
193
+ f"{result.returncode}: {result.stderr.strip()}"
194
+ )
195
+ _settings_cache[key] = None
196
+ return None
197
+
198
+ try:
199
+ data = json.loads(result.stdout)
200
+ except json.JSONDecodeError as exc:
201
+ log(f"auto-detect: showBuildSettings({scheme!r}) invalid JSON: {exc!r}")
202
+ _settings_cache[key] = None
203
+ return None
204
+
205
+ if not isinstance(data, list) or not data:
206
+ _settings_cache[key] = None
207
+ return None
208
+ settings = data[0].get("buildSettings") or {}
209
+ _settings_cache[key] = settings if isinstance(settings, dict) else None
210
+ return _settings_cache[key]
211
+
212
+
213
+ def auto_detect_scheme(
214
+ workspace: Path, project: str, workspace_file: str
215
+ ) -> Optional[str]:
216
+ """Pick the single ``com.apple.product-type.application`` scheme."""
217
+ schemes = _list_schemes(workspace, project, workspace_file)
218
+ if not schemes:
219
+ return None
220
+
221
+ app_schemes: list[str] = []
222
+ for scheme in schemes:
223
+ settings = _show_build_settings(
224
+ workspace, project, workspace_file, scheme, "Release",
225
+ )
226
+ if not settings:
227
+ continue
228
+ if settings.get("PRODUCT_TYPE") == APP_PRODUCT_TYPE:
229
+ app_schemes.append(scheme)
230
+
231
+ if not app_schemes:
232
+ return None
233
+ if len(app_schemes) == 1:
234
+ log(f"auto-detect: scheme={app_schemes[0]}")
235
+ return app_schemes[0]
236
+
237
+ repo_base = workspace.resolve().name.lower()
238
+ for scheme in app_schemes:
239
+ if scheme.lower() == repo_base:
240
+ log(f"auto-detect: scheme={scheme} (basename match)")
241
+ return scheme
242
+
243
+ picked = sorted(app_schemes)[0]
244
+ notice(
245
+ f"multiple application schemes found ({', '.join(sorted(app_schemes))}); "
246
+ f"picking {picked} alphabetically. Set xcode.scheme in ci.config.yaml "
247
+ f"to disambiguate."
248
+ )
249
+ return picked
250
+
251
+
252
+ def auto_detect_bundle_id(
253
+ workspace: Path,
254
+ project: str,
255
+ workspace_file: str,
256
+ scheme: str,
257
+ configuration: str,
258
+ ) -> Optional[str]:
259
+ """Extract ``PRODUCT_BUNDLE_IDENTIFIER`` for the given scheme/config."""
260
+ settings = _show_build_settings(
261
+ workspace, project, workspace_file, scheme, configuration or "Release",
262
+ )
263
+ if not settings:
264
+ return None
265
+ bundle = settings.get("PRODUCT_BUNDLE_IDENTIFIER")
266
+ if not bundle or not isinstance(bundle, str):
267
+ return None
268
+ # xcodebuild sometimes emits unresolved variable references when build
269
+ # settings depend on xcconfig files that aren't in the default context.
270
+ if "$(" in bundle or bundle.startswith("$") or "=" in bundle:
271
+ log(f"auto-detect: bundle_id {bundle!r} contains unresolved variable — rejecting")
272
+ return None
273
+ log(f"auto-detect: bundle_id={bundle}")
274
+ return bundle
@@ -53,6 +53,7 @@ from __future__ import annotations
53
53
  import os
54
54
  from pathlib import Path
55
55
 
56
+ import auto_detect
56
57
  from cfg_io import fail, log
57
58
  from cfg_resolve import (
58
59
  as_str_bool,
@@ -107,8 +108,49 @@ def emit_credentials(env_file: Path, cfg: dict, workspace: Path) -> dict:
107
108
  return {"key_id": str(asc_key_id), "issuer_id": str(asc_issuer), "key_path": str(p8_path)}
108
109
 
109
110
 
111
+ def _auto_detect_bundle_if_empty(
112
+ bundle: tuple[str, str],
113
+ workspace: Path | None,
114
+ xcode: dict | None,
115
+ ) -> tuple[str, str]:
116
+ """Call auto_detect_bundle_id only when bundle is unresolved."""
117
+ if bundle[0] or workspace is None or xcode is None or not xcode.get("scheme"):
118
+ return bundle
119
+ detected = auto_detect.auto_detect_bundle_id(
120
+ workspace,
121
+ xcode.get("project", ""),
122
+ xcode.get("workspace", ""),
123
+ xcode["scheme"],
124
+ xcode.get("configuration", "Release"),
125
+ )
126
+ if detected:
127
+ return detected, "auto-detect"
128
+ return bundle
129
+
130
+
131
+ def _auto_detect_project_workspace(
132
+ workspace: Path,
133
+ project: tuple[str, str],
134
+ ws_val: tuple[str, str],
135
+ ) -> tuple[tuple[str, str], tuple[str, str]]:
136
+ """Run auto_detect.auto_detect_project when both are unset; return the pair."""
137
+ if project[0] or ws_val[0]:
138
+ return project, ws_val
139
+ auto_proj, auto_ws = auto_detect.auto_detect_project(workspace)
140
+ if auto_ws:
141
+ return project, (auto_ws, "auto-detect")
142
+ if auto_proj:
143
+ return (auto_proj, "auto-detect"), ws_val
144
+ return project, ws_val
145
+
146
+
110
147
  def resolve_xcode(cfg: dict, workspace: Path, inp: dict) -> dict:
111
- """Resolve project / workspace / scheme / configuration / profile_name."""
148
+ """Resolve project / workspace / scheme / configuration / profile_name.
149
+
150
+ Falls back to ``auto_detect`` (glob + xcodebuild -list + showBuildSettings)
151
+ when neither input nor config nor the older single-suffix glob finds a
152
+ value. This is what lets ``ci.config.yaml`` be omitted entirely.
153
+ """
112
154
  proj_cfg, proj_src = pick_with_source(cfg, ("xcode", "project"), ("ios", "native", "project"))
113
155
  ws_cfg, ws_src = pick_with_source(cfg, ("xcode", "workspace"), ("ios", "native", "workspace"))
114
156
  scheme_cfg, scheme_src = pick_with_source(cfg, ("xcode", "scheme"), ("ios", "native", "scheme"))
@@ -127,10 +169,18 @@ def resolve_xcode(cfg: dict, workspace: Path, inp: dict) -> dict:
127
169
  inp["WORKSPACE"], ws_cfg, cfg_source=ws_src,
128
170
  auto_val=auto_project_glob(workspace, ".xcworkspace"),
129
171
  )
130
- scheme = resolve(inp["SCHEME"], scheme_cfg, cfg_source=scheme_src)
172
+ project, ws_val = _auto_detect_project_workspace(workspace, project, ws_val)
173
+
131
174
  configuration = resolve(
132
175
  inp["CONFIGURATION"], config_cfg, cfg_source=config_src, default="Release",
133
176
  )
177
+
178
+ scheme = resolve(inp["SCHEME"], scheme_cfg, cfg_source=scheme_src)
179
+ if not scheme[0] and (project[0] or ws_val[0]):
180
+ detected = auto_detect.auto_detect_scheme(workspace, project[0], ws_val[0])
181
+ if detected:
182
+ scheme = (detected, "auto-detect")
183
+
134
184
  default_profile = f"{scheme[0]} CI" if scheme[0] else ""
135
185
  profile_name = resolve(
136
186
  inp["PROFILE_NAME"], profile_cfg, cfg_source=profile_src, default=default_profile,
@@ -144,8 +194,21 @@ def resolve_xcode(cfg: dict, workspace: Path, inp: dict) -> dict:
144
194
  }
145
195
 
146
196
 
147
- def resolve_app(cfg: dict, inp: dict, creds: dict, scripts_dir: Path) -> dict:
148
- """Resolve bundle_id / team_id / app_store_apple_id (with ASC-API fallback)."""
197
+ def resolve_app(
198
+ cfg: dict,
199
+ inp: dict,
200
+ creds: dict,
201
+ scripts_dir: Path,
202
+ *,
203
+ workspace: Path | None = None,
204
+ xcode: dict | None = None,
205
+ ) -> dict:
206
+ """Resolve bundle_id / team_id / app_store_apple_id (with ASC-API fallback).
207
+
208
+ When ``workspace`` and ``xcode`` are passed and bundle_id is unset from
209
+ both input and config, we call ``auto_detect_bundle_id`` to ask
210
+ xcodebuild for ``PRODUCT_BUNDLE_IDENTIFIER``.
211
+ """
149
212
  bundle_cfg, bundle_src = pick_with_source(cfg, ("app", "bundle_id"), ("ios", "native", "bundle_id"))
150
213
  team_cfg, team_src = pick_with_source(cfg, ("app", "team_id"), ("ios", "native", "team_id"))
151
214
  apple_cfg, apple_src = pick_with_source(
@@ -153,6 +216,7 @@ def resolve_app(cfg: dict, inp: dict, creds: dict, scripts_dir: Path) -> dict:
153
216
  )
154
217
 
155
218
  bundle = resolve(inp["BUNDLE_ID"], bundle_cfg, cfg_source=bundle_src)
219
+ bundle = _auto_detect_bundle_if_empty(bundle, workspace, xcode)
156
220
  team = resolve(inp["TEAM_ID"], team_cfg, cfg_source=team_src)
157
221
 
158
222
  # team_id is documented as optional because the ASC API key is bound to
@@ -277,7 +341,11 @@ def main() -> None:
277
341
 
278
342
  creds = emit_credentials(env_file, cfg, workspace)
279
343
  xc = resolve_xcode(cfg, workspace, inputs)
280
- app = resolve_app(cfg, inputs, creds, scripts_dir)
344
+ xc_values = {k: v[0] for k, v in xc.items()}
345
+ app = resolve_app(
346
+ cfg, inputs, creds, scripts_dir,
347
+ workspace=workspace, xcode=xc_values,
348
+ )
281
349
  tf = resolve_testflight(cfg, inputs)
282
350
  ios = resolve_ios(cfg, inputs)
283
351
  tests = resolve_tests(cfg, inputs)