@daemux/store-automator 0.10.75 → 0.10.77

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.75"
8
+ "version": "0.10.77"
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.75",
15
+ "version": "0.10.77",
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.75",
3
+ "version": "0.10.77",
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.75",
3
+ "version": "0.10.77",
4
4
  "description": "App Store & Google Play automation agents for Flutter app publishing",
5
5
  "author": {
6
6
  "name": "Daemux"
@@ -1,24 +1,25 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
- Prepare iOS code signing on a fresh CI runner.
3
+ Prepare iOS code signing on a fresh CI runner (automatic-signing variant).
4
+
5
+ This script prepares ONLY the credentials Xcode needs to drive
6
+ `-allowProvisioningUpdates` against the App Store Connect API:
4
7
 
5
- Uses the App Store Connect API (auth via P8 key) to:
6
8
  1. Generate an RSA private key + CSR
7
9
  2. Create a new Apple Distribution certificate from the CSR; if the per-team
8
10
  cap is hit (409), revoke the newest existing DISTRIBUTION cert and retry.
9
- 3. Ensure a provisioning profile with a known name exists for the bundle ID,
10
- linked to the new cert. If it exists, delete+recreate to refresh it.
11
- 4. Install the profile into ~/Library/MobileDevice/Provisioning Profiles/
12
- 5. Import cert + private key into a dedicated temporary keychain and put
11
+ 3. Import cert + private key into a dedicated temporary keychain and put
13
12
  that keychain on the search list so codesign / xcodebuild can find it.
14
13
 
14
+ Provisioning profile creation + installation is DELEGATED to xcodebuild via
15
+ `-allowProvisioningUpdates` + ASC API auth. That path handles apps with any
16
+ number of targets (Network Extensions, Widgets, WatchKit extensions, etc)
17
+ without us having to know the full target graph up-front.
18
+
15
19
  Environment inputs:
16
20
  ASC_KEY_ID - App Store Connect API key ID
17
21
  ASC_ISSUER_ID - App Store Connect API issuer ID
18
22
  ASC_KEY_PATH - Path to the .p8 private key file
19
- TEAM_ID - Apple developer team ID
20
- BUNDLE_ID - App bundle identifier
21
- PROFILE_NAME - Desired provisioning profile name
22
23
  RUNNER_TEMP - GitHub Actions temp dir (for keychain + intermediates)
23
24
 
24
25
  Writes nothing to stdout that would leak secrets.
@@ -28,7 +29,6 @@ from __future__ import annotations
28
29
 
29
30
  import base64
30
31
  import os
31
- import plistlib
32
32
  import subprocess
33
33
  from pathlib import Path
34
34
 
@@ -136,68 +136,6 @@ def create_distribution_cert(token: str, csr_b64: str) -> tuple[str, bytes]:
136
136
  return cert_id, cert_der
137
137
 
138
138
 
139
- def find_bundle_id(token: str, identifier: str) -> str:
140
- data = get_json(
141
- "/bundleIds",
142
- token,
143
- params={"filter[identifier]": identifier, "limit": "5"},
144
- )
145
- for item in data.get("data", []):
146
- if item["attributes"]["identifier"] == identifier:
147
- return item["id"]
148
- raise SystemExit(f"bundle id {identifier!r} not found in ASC")
149
-
150
-
151
- def delete_profile_by_name(token: str, name: str) -> None:
152
- data = get_json("/profiles", token, params={"limit": "200"})
153
- for p in data.get("data", []):
154
- if (p["attributes"].get("name") or "") == name:
155
- pid = p["id"]
156
- print(f"Deleting existing profile {pid} ({name})")
157
- request("DELETE", f"/profiles/{pid}", token)
158
-
159
-
160
- def create_profile(
161
- token: str, name: str, bundle_id_pk: str, cert_id: str
162
- ) -> bytes:
163
- body = {
164
- "data": {
165
- "type": "profiles",
166
- "attributes": {
167
- "name": name,
168
- "profileType": "IOS_APP_STORE",
169
- },
170
- "relationships": {
171
- "bundleId": {"data": {"type": "bundleIds", "id": bundle_id_pk}},
172
- "certificates": {
173
- "data": [{"type": "certificates", "id": cert_id}]
174
- },
175
- },
176
- }
177
- }
178
- resp = request("POST", "/profiles", token, json_body=body)
179
- data = resp.json()["data"]
180
- profile_content_b64 = data["attributes"]["profileContent"]
181
- return base64.b64decode(profile_content_b64)
182
-
183
-
184
- def install_profile(profile_der: bytes) -> str:
185
- """Write the .mobileprovision file and return its uuid."""
186
- # Profile is CMS-signed plist; decode with `security cms` to read UUID.
187
- profiles_dir = Path.home() / "Library/MobileDevice/Provisioning Profiles"
188
- profiles_dir.mkdir(parents=True, exist_ok=True)
189
- tmp_path = profiles_dir / "tmp.mobileprovision"
190
- tmp_path.write_bytes(profile_der)
191
- decoded = subprocess.check_output(
192
- ["security", "cms", "-D", "-i", str(tmp_path)]
193
- )
194
- uuid = plistlib.loads(decoded)["UUID"]
195
- final_path = profiles_dir / f"{uuid}.mobileprovision"
196
- tmp_path.rename(final_path)
197
- print(f"Installed provisioning profile {uuid} at {final_path}")
198
- return uuid
199
-
200
-
201
139
  def write_p12(
202
140
  private_key: rsa.RSAPrivateKey, cert_der: bytes, out_path: Path, passwd: str
203
141
  ) -> None:
@@ -293,20 +231,16 @@ def main() -> None:
293
231
  key_id = env("ASC_KEY_ID")
294
232
  issuer_id = env("ASC_ISSUER_ID")
295
233
  asc_key_path = env("ASC_KEY_PATH")
296
- bundle_id = env("BUNDLE_ID")
297
- profile_name = env("PROFILE_NAME")
298
234
  runner_temp = env("RUNNER_TEMP")
299
235
 
300
236
  token = make_jwt(key_id, issuer_id, asc_key_path)
301
237
 
302
238
  private_key, csr_b64 = generate_key_and_csr()
303
- cert_id, cert_der = create_distribution_cert(token, csr_b64.decode())
304
-
305
- bundle_pk = find_bundle_id(token, bundle_id)
306
- delete_profile_by_name(token, profile_name)
307
- profile_der = create_profile(token, profile_name, bundle_pk, cert_id)
308
- install_profile(profile_der)
239
+ _cert_id, cert_der = create_distribution_cert(token, csr_b64.decode())
309
240
 
241
+ # Provisioning profile(s) are created on-demand by xcodebuild via
242
+ # `-allowProvisioningUpdates` — we only need the signing identity
243
+ # (cert + private key) to be present in a keychain Xcode can see.
310
244
  p12_pass = "ci"
311
245
  p12_path = Path(runner_temp) / "cert.p12"
312
246
  write_p12(private_key, cert_der, p12_path, p12_pass)