iOSã¢ããª12æ¬ãApp Storeãžç¡äººæåºããåºç€ åŸç·š â 審æ»ã®çœ å šéšïŒbetaãã«ã匟ãã»äŸ¡æ Œã»ååè¡çªïŒ
è§£ééç¥ãåãåã£ãæ¥ãæå ã®æ®é«ã¯0åã§ãããå幎åŸãClaude Code ã§èªåŸç°å¢ãçµã¿çŽããä»ã¯æå120äžãè¶ ããŠããŸãããã®æ±ã®ã²ãšã€ããApp Store ãžã®ç¡äººæåºãã€ãã©ã€ã³ã§ãã
ååïŒåç·šïŒã¯ JWT èªèšŒã®çµã¿æ¹ãš asc.py ã®éªšæ Œã解説ããŸãããä»åã¯å®æŠæå
¥ããŠå€æããã審æ»ãªãžã§ã¯ãã®çœ ãã3æ¬ãŸãšããŸããITMS-90111 ã®çå ã BuildMachineOSBuild ã ã£ã話ãappPriceSchedules ã®æ£è§£åœ¢ãen-US ãã±ãŒã«ã®ååè¡çªåé¿ãŒãŒé çªã«å®ã³ãŒãã§æãäžããŸãã
ãªããã®ä»çµã¿ãå¹ãã®ã
äœæ¥ãèªååããã®ã§ã¯ãªããäœæ¥ãèµ·ããªãç°å¢ãäœããããããã®ãã€ãã©ã€ã³ã®æ žå¿ã§ãã
iOSã¢ããªã12æ¬äžŠèµ°ããããšãã¹ãã¢ãžã®æåºã ãã§éæ¹ããªãææ°ãçºçããŸããã²ãšã€ã®ã¢ããªãæäœæ¥ã§å¯©æ»æåºããããšãããšãXcode ã§ã¢ãŒã«ã€ã â Organizer ã§ã¢ããããŒã â App Store Connect ã§ããŒãžã§ã³æ å ±ãå ¥å â ã¹ã¯ãªãŒã³ã·ã§ãããå·®ãæ¿ã â å¯©æ»æåºãã¿ã³ãæŒãããšããæµããèžã¿ãŸããæ £ããŠã1æ¬ããã20ã30åã12æ¬ã§ã¯äžžäžæ¥æ¶ããŸãããããæã«æ°åç¹°ãè¿ãã°ãå人éçºè ã®ãªãœãŒã¹ã¯ããã«æ¯æžããŸãã
ãšããã App Store Connect APIïŒä»¥äž ASC APIïŒã¯ãJWTïŒ.p8 ç§å¯éµããããã° 2FA ãªãã§å©ãã仿§ã§ããããããã¹ãŠãå€ããŸãã人éã®ãã°ã€ã³ã»ãã·ã§ã³ãå¿ èŠãšããªããããClaude Code ãæ·±å€3æã«èªåŸå®è¡ããŠãåãæš©éã§åäœããŸãã
~/.appstoreconnect/asc.py ã® JWT çæéšåã¯ãããªã£ãŠããŸãã
def _jwt():
h = _b64(json.dumps({"alg":"ES256","kid":KEY_ID,"typ":"JWT"},separators=(",",":")).encode())
p = _b64(json.dumps({"iss":ISSUER,"iat":int(time.time())-30,"exp":int(time.time())+900,
"aud":"appstoreconnect-v1"},separators=(",",":")).encode())
signing = h + b"." + p
key = serialization.load_pem_private_key(open(P8,"rb").read(), password=None)
der = key.sign(signing, ec.ECDSA(hashes.SHA256()))
r,s = decode_dss_signature(der)
return (signing + b"." + _b64(r.to_bytes(32,"big")+s.to_bytes(32,"big"))).decode()
aud:"appstoreconnect-v1" ãš exp ã900ç§ïŒ15åïŒãšããã®ã Apple ã®èŠæ±ãã圢åŒã§ããæšæºã©ã€ãã©ãªã® PyJWT ã䜿ããææžãã«ããŠããã®ã¯ãäŸåãæå°åããŠã©ãã§ãåããããã§ããcryptography ããã±ãŒãžã²ãšã€ã§ ES256 眲åãå®çµããŸãã
èªèšŒæ
å ±ã¯ ~/.appstoreconnect/keys.json äžãæã«éçŽããã©ã®ã¢ããªã«å¯ŸããŠãåãã¹ã¯ãªããã䜿ãåããèšèšã«ããŸãããCFG = json.load(open(os.path.expanduser("~/.appstoreconnect/keys.json"))) ã§ããŒèªã¿èŸŒã¿ãçµãããããšã¯ã¢ããªIDãåŒæ°ã«æž¡ãã ãã§ãã
ããã²ãšã€éèŠãªã®ãç¶æ
管çã§ããASC API ã¯ããŒãžã§ã³ãç·šéå¯èœãªç¶æ
ãã©ãããå³å¯ã«ç®¡çããŸããasc.py ã® _EDITABLE 宿°ããã®å€æåºæºã§ãã
_EDITABLE = {"PREPARE_FOR_SUBMISSION", "DEVELOPER_REJECTED", "REJECTED",
"METADATA_REJECTED", "INVALID_BINARY"}
ãã®5ç¶æ
ã«ããå Žåã ã versionString ã®å€æŽããã«ãã®å·®ãæ¿ããèš±å¯ãããŸããIN_REVIEW ã WAITING_FOR_REVIEW äžã« PATCH ãæããŠããšã©ãŒã«ãªãã ãã§ããã¹ã¯ãªãããäºåã«ããã確èªããŠããæäœãåå²ãããããšã§ãç¡é§ãªAPIã³ãŒã«ãæé€ããŠããŸãã
ãäœæ¥ãããªããããã®ä»çµã¿ã¯ããã²ãšã€ãããŸããåªçæ§ã§ããããšãã° add_tester() ã¯ãã¹ã¿ãŒè¿œå ã®åã« _has_tester() ã§æ¢ç»é²ãã©ããã確èªããç»é²æžã¿ãªãåŠçãã¹ãããããŸããsubmit() ã READY_FOR_REVIEW ç¶æ
ã® reviewSubmission ãæ¢ã«ããã°æ°èŠäœæããåå©çšããŸããããããŠãããšãClaude Code ãåãã³ãã³ããäºåºŠå®è¡ããŠãå¯äœçšãåºãŸããã
æå120äžã®å®æ ã¯ãä»äºéã®ç©ã¿å¢ããã§ã¯ãããŸãããåãä»çµã¿ã䞊åã§åããŠããã ãã§ããã¢ããªãå¢ããŠããã¹ã¯ãªãããžæž¡ãã¢ããªIDãå¢ããã ãã§æ§é ã¯å€ãããŸããã
å šäœã®æµã
ãã€ãã©ã€ã³å šäœã俯ç°ãããšã次ã®4ã¬ã€ã€ãŒã«åãããŸãã
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â 1. çœ²åæºå â
â setup_signing.py â
â ã»SHA1 ã§ Distribution èšŒææžãç¹å® â
â ã»App IDïŒbundleIdïŒãäœæãŸãã¯ç¢ºèª â
â ã».mobileprovision ãçæã㊠Keychain ã«é
眮 â
ââââââââââââââââââââââ¬âââââââââââââââââââââââââââââââââââââ
â
ââââââââââââââââââââââŒâââââââââââââââââââââââââââââââââââââ
â 2. ãã«ãïŒã¢ãŒã«ã€ã â
â tools/archive.sh â
â ã»xcodegen generate ã§ãããžã§ã¯ãåçæ â
â ã»xcodebuild archiveïŒManual SigningïŒ â
â ã»xcodebuild -exportArchive â .ipa çæ â
ââââââââââââââââââââââ¬âââââââââââââââââââââââââââââââââââââ
â
ââââââââââââââââââââââŒâââââââââââââââââââââââââââââââââââââ
â 3. ãã€ããªæå
¥ â
â eas submit / altool â
â ã».ipa ã App Store Connect ãžã¢ããããŒã â
â ã»åŠçå®äºãŸã§åŸ
æ©ïŒprocessingState: VALIDïŒ â
ââââââââââââââââââââââ¬âââââââââââââââââââââââââââââââââââââ
â
ââââââââââââââââââââââŒâââââââââââââââââââââââââââââââââââââ
â 4. ã¡ã¿ããŒã¿ïŒå¯©æ»æåº â
â asc.py release â dedup â submit â
â ã»make_version: ç·šéå¯èœããŒãžã§ã³ãç¢ºä¿ â
â ã»attach_build: VALID ãã«ããçŽã¥ã â
â ã»set_whatsnew: å
šãã±ãŒã«ã®æŽæ°æ
å ±ãèšå® â
â ã»dedup: ã¹ã¯ã·ã§éè€ãé€å» â
â ã»submit: reviewSubmission äœæâitem远å âæåº â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
ã¬ã€ã€ãŒ1ïŒçœ²åæºå
~/dev/auraly-ios/tools/setup_signing.py ãæ
ã眲åã®èªååã¯ãèšŒææžã®çªåããå§ãŸããŸãã
LOCAL_SHA1 = "EC06777A693874E920CECFE390D467670552CCCE".lower()
def find_cert():
_, d = asc.call("GET", "/v1/certificates?limit=200")
for c in d.get("data", []):
content = c["attributes"].get("certificateContent")
if not content:
continue
der = base64.b64decode(content)
sha1 = hashlib.sha1(der).hexdigest()
if sha1 == LOCAL_SHA1:
return c["id"]
/v1/certificates ãè¿ã certificateContent 㯠Base64 ãšã³ã³ãŒãã® DER ã§ããããã SHA1 ã§ãã£ã³ã¬ãŒããªã³ãããããŒã«ã«ã® Keychain ã«ãã Distribution èšŒææžãšç
§åããŸããååæ€çŽ¢ã§ã¯ãªããã£ã³ã¬ãŒããªã³ãç
§åã«ããŠããã®ã¯ããApple Distribution: ...ïŒè€æ°ïŒãã®ããã«ååãéè€ããã±ãŒã¹ã確å®ã«åé¿ããããã§ãã
äžèŽããèšŒææžãèŠã€ãã£ãããApp IDïŒbundleIdïŒã確èªãŸãã¯äœæããAppStore ã¿ã€ãã®ããããžã§ãã³ã°ãããã¡ã€ã«ãçæããŸããçæããããããã¡ã€ã«ã¯ ~/Library/MobileDevice/Provisioning Profiles/{uuid}.mobileprovision ãžæžã蟌ãŸããxcodebuild ããã®ãŸãŸèªèã§ããç¶æ
ã«ãªããŸããæ¢åã®ååãããã¡ã€ã«ã¯äºåã« DELETE ããŠããäœãçŽãèšèšã§ãèšŒææžããŒããŒã·ã§ã³åŸã«å€ããããã¡ã€ã«ãæ®ã£ãŠãã«ãã倱æããåé¡ãé²ãã§ããŸãã
ã¬ã€ã€ãŒ2ïŒãã«ãïŒã¢ãŒã«ã€ã
~/dev/auraly-ios/tools/archive.sh ã®æ žå¿éšã¯17è¡ã§ãã
xcodegen generate
rm -rf build/Auraly.xcarchive build/export
xcodebuild archive \
-project Auraly.xcodeproj \
-scheme Auraly \
-configuration Release \
-archivePath build/Auraly.xcarchive \
-destination 'generic/platform=iOS' \
CODE_SIGN_STYLE=Manual \
CODE_SIGN_IDENTITY="Apple Distribution" \
PROVISIONING_PROFILE_SPECIFIER="Auraly AppStore" \
-allowProvisioningUpdates
xcodebuild -exportArchive \
-archivePath build/Auraly.xcarchive \
-exportOptionsPlist ExportOptions.plist \
-exportPath build/export
CODE_SIGN_STYLE=Manual ãš PROVISIONING_PROFILE_SPECIFIER="Auraly AppStore" ã®çµã¿åããã§ãXcode ã®èªå眲åãå®å
šã«ç¡å¹åããçŽåã®ã¬ã€ã€ãŒã§çæãããããã¡ã€ã«ãååã§æå®ããŠäœ¿ããŸãã-allowProvisioningUpdates ã¯æ®ããŠãããŸããããããã¡ã€ã«ãæ¢ã«ããŒã«ã«ã«çœ®ãããŠããããå®éã«ã¯Apple ãµãŒããŒãžã®åãåããã¯èµ·ããŸããã
xcodegen generate ãæ¯åå
é ã§å®è¡ããŠããã®ã¯æå³çã§ããproject.yml ãå¯äžã®çå®ã®æ
å ±æºãšãã.xcodeproj ã®æåç·šéå·®åããã«ãã«çŽã蟌ãŸãªãããã«ããããã§ãã
ã¬ã€ã€ãŒ3ïŒãã€ããªæå ¥
çæããã .ipa ã App Store Connect ãžå±ããã®ã¯ eas submit ã altool ã§ãããã®ã¬ã€ã€ãŒã¯ã¹ã¯ãªããå¶åŸ¡ã®å¯Ÿè±¡å€ã§ãåŠçå®äºïŒprocessingState: VALIDïŒãŸã§éåæã«åŸ
ã¡ãŸããasc.py ã® _build_for_version() ããæå®ããŒãžã§ã³ã§ VALID ã«ãªã£ããã«ããååšãããã©ãããããŒãªã³ã°ãã圹å²ãæ
ããŸãã
def _build_for_version(app_id, version_string):
_, b = call("GET", f"/v1/builds?filter[app]={app_id}&limit=20&sort=-uploadedDate"
f"&include=preReleaseVersion")
incl = {i["id"]: i for i in b.get("included", []) if i["type"] == "preReleaseVersions"}
for x in b.get("data", []):
if x["attributes"].get("processingState") != "VALID":
continue
pr = x.get("relationships", {}).get("preReleaseVersion", {}).get("data")
ver = incl.get(pr["id"], {}).get("attributes", {}).get("version") if pr else None
if ver == version_string:
return x["id"], x["attributes"].get("version")
return None
processingState ã VALID 以å€ïŒPROCESSING ã FAILEDïŒã®ãã«ãã¯æ·»ä»å¯Ÿè±¡å€ãšããŠèªã¿é£ã°ããŸããinclude=preReleaseVersion ã§ marketing version ãäžçºã§ååŸããããŒãžã§ã³æååã§çµã蟌ãããšã§ãè€æ°ãã«ããååšããç¶æ³ã§ãæå³ããããŒãžã§ã³ã ããæŽããŸãã
ã¬ã€ã€ãŒ4ïŒã¡ã¿ããŒã¿ïŒå¯©æ»æåº
asc.py submit() ãèžãæé ã¯3ã¹ãããã§ãã
def submit(app_id, platform="IOS"):
# 1) reviewSubmission ãäœæïŒæ¢å in-progress ãããã°åå©çšïŒ
_, rs = call("GET",
f"/v1/reviewSubmissions?filter[app]={app_id}&filter[state]=READY_FOR_REVIEW&limit=1")
sub = (rs.get("data") or [None])[0]
if not sub:
st, r = call("POST", "/v1/reviewSubmissions",
{"data": {"type": "reviewSubmissions", "attributes": {"platform": platform},
"relationships": {"app": {"data": {"type": "apps", "id": app_id}}}}})
sub = r["data"]
sid = sub["id"]
# 2) ããŒãžã§ã³ã item ãšããŠè¿œå
ver = _latest_version(app_id); vid = ver["id"]
st, r = call("POST", "/v1/reviewSubmissionItems",
{"data": {"type": "reviewSubmissionItems",
"relationships": {
"reviewSubmission": {"data": {"type": "reviewSubmissions", "id": sid}},
"appStoreVersion": {"data": {"type": "appStoreVersions", "id": vid}}}}})
# 3) submitted=true ã§æåº
st, r = call("PATCH", f"/v1/reviewSubmissions/{sid}",
{"data": {"type": "reviewSubmissions", "id": sid,
"attributes": {"submitted": True}}})
æ§æ¥ã® /v1/apps/{id}/appStoreVersions/{vid}/... ç³»ãšã³ããã€ã³ãã§ã¯ãªããreviewSubmissions / reviewSubmissionItems ã®æ°ç³»ãšã³ããã€ã³ãã䜿ã£ãŠããŸããApple ã¯ããã¥ã¡ã³ãã§ãã¡ããæšå¥šããŠãããæ§ç³»ã¯æ®µéçã«å»æ¢ãããæ¹åã§ãã3ã¹ãããã®ãã¡æåã®ãæ¢åãã§ãã¯ããçããšãåãã¢ããªãžã®äºééä¿¡ã§ãšã©ãŒã«ãªããããGET ã§ç¶æ
確èªããŠãã POST ããã®ããã€ã³ãã§ãã
ã¹ã¯ã·ã§éè€ã® dedup 㯠submit ã®çŽåã«å¿
ãå®è¡ããŸããdeliver ã fastlane ã§ã¹ã¯ãªãŒã³ã·ã§ãããåæãçŽããšãåååãšä»ååãåäžãã¡ã€ã«ã®ãŸãŸ2æè¿œå ãããããšããããŸããdedup_screenshots() 㯠sourceFileChecksum ãš fileName ã®çµã¿åãããããŒãšãã2æç®ä»¥éã DELETE ããŸããapply=False ã§ dry-runã--apply ãã©ã°ã§å®éã«åé€ããããã誀åé€ãªã¹ã¯ãäºå確èªã§æé€ã§ããŸãã
ãããŸã§ããåããã€ãã©ã€ã³ãã®å šäœåã§ããæ¬¡ç« ããã¯ããã®ãã€ãã©ã€ã³ã12ã¢ããªãžæ¬çªæå ¥ããŠå®éã«èžãã ãªãžã§ã¯ãçç±ãããšã©ãŒã³ãŒããšä¿®æ£ã³ãŒããæ·»ããŠè§£èª¬ããŸãã
å®è£ ã®è©³çް
reject() â ãå·®ãæ»ãããªãã«ã¯æ¬¡ã®æåºãã§ããªã
12æ¬ã䞊走ããããšã審æ»äžã«è»œåŸ®ãªãã°ãçºèŠããŠãä»ããçŽãããããšããç¶æ³ãå¿
ãçºçããŸããWAITING_FOR_REVIEW ã IN_REVIEW ç¶æ
ã®ã¢ããªã« make_version() ã attach_build() ãæããŠããASC API ã¯ç¡èšã§ 422 ãè¿ãã ãã§ããç·šéå¯èœãªç¶æ
ãžæ»ãããã«å¿
èŠãªã®ã reject() ã§ãã
def reject(app_id):
_, rs = call("GET", f"/v1/reviewSubmissions?filter[app]={app_id}&limit=10")
done = False
for x in rs.get("data", []):
state = x["attributes"].get("state")
if state in ("WAITING_FOR_REVIEW", "IN_REVIEW", "UNRESOLVED_ISSUES"):
st, r = call("PATCH", f"/v1/reviewSubmissions/{x['id']}",
{"data": {"type": "reviewSubmissions", "id": x["id"],
"attributes": {"canceled": True}}})
print(" reject", x["id"], state, "->", st if st < 400 else json.dumps(r)[:400])
done = True
if not done:
print(" 審æ»äžã® submission ç¡ã")
canceled: True ã PATCH ããã ãã§å¯©æ»ãåãäžãããããŒãžã§ã³ã DEVELOPER_REJECTED ã«é·ç§»ããŸãã_EDITABLE ã«ã¯ãã®ç¶æ
ãå«ãŸããŠãããããçŽåŸã« make_version() â attach_build() â submit() ã®æµããžæ»ããŸããUNRESOLVED_ISSUES ã察象ã«å«ããŠããã®ã¯ãApp Review ãã远èšè³ªåãæ¥ãŠããç¶æ
ã§ãåãã³ãã³ãã§åãäžããããããã«ããããã§ãã
Developer Rejection ã¯ããã«ãã£ãªãã®æäœã§ãã審æ»å±¥æŽã«ã¯èšé²ãããŸããã審æ»åæ°ã®ã«ãŠã³ãã«ã¯é¿ããŸããããããäžåã®ãããã€ããªãéãããšã㊠REJECTED ãé£ããã»ãããæ¬¡ãµã€ã¯ã«ãŸã§ã®åŸ
æ©æéã䌞ã³ãŸãããçŽããããªã£ããå³ reject â ä¿®æ£ â å submitãããµã€ã¯ã«ã³ã¹ããæå°åããæ£ããåãæ¹ã§ãã
release() â 3ã¹ããããã²ãšã€ã®åŒæ°ã§åãããªãŒã±ã¹ãã¬ãŒã¿ãŒ
def release(app_id, version, whatsnew, platform="IOS"):
print("release", app_id, version)
vid = make_version(app_id, version, platform)
if not vid: return
attach_build(app_id, vid, version)
set_whatsnew(app_id, whatsnew, vid)
print(" done. 確èªåŸã« `submit` ãå®è¡ã")
release() 㯠make_version â attach_build â set_whatsnew ãçŽåã«æããèããªãŒã±ã¹ãã¬ãŒã¿ãŒã§ããæå³çã« submit() ã¯å«ããŠããŸããããã«ããæ·»ä»ãããŠã¡ã¿ããŒã¿ãæã£ã段éã§ãã£ããæ¢ãŸããstatus ã³ãã³ãã§ç¢ºèªããŠãã submit ãæããèšèšã§ããå
šèªåã«ãããšãã¹ãçºèŠããéããªã審æ»ãžæµããŠããŸããããæåŸã® 1 ã³ãã³ãã ãã¯æå³çã«åé¢ããŸããã
Claude Code ããã¯ãã® 3 è¡ã§å®çµããŸãã
python3 ~/.appstoreconnect/asc.py release 6782624281 1.4.0 "ãã°ä¿®æ£ãšå®å®æ§ã®åäž"
python3 ~/.appstoreconnect/asc.py dedup 6782624281 --apply
python3 ~/.appstoreconnect/asc.py submit 6782624281
ãããŒãžã§ã³æºå â ã¹ã¯ã·ã§éè€é€å» â å¯©æ»æåºãã 3 è¡ã§ããããã 12 ã¢ããªå䞊åå®è¡ããŠããASC API ã®ã¬ãŒããªãããïŒ1 åããã 1000 ãªã¯ãšã¹ãçšåºŠãç®å®ïŒã«åŒã£ãããåã«ãã°ãåããŸãã
add_tester() â ãã°ã«ãŒãçµç±ã§ POSTããå¯äžã®æ£è§£
def add_tester(app_id, email=DEFAULT_TESTER_EMAIL, first="Lily", last="Tester"):
if _has_tester(app_id, email):
print(f" {email} ã¯æ¢ã«ãã¹ã¿ãŒ(skip)"); return
_, g = call("GET", f"/v1/apps/{app_id}/betaGroups?limit=50")
internal = next((x for x in g.get("data", []) if x["attributes"].get("isInternalGroup")), None)
if not internal:
st, r = call("POST", "/v1/betaGroups",
{"data": {"type": "betaGroups",
"attributes": {"name": "Internal", "isInternalGroup": True},
"relationships": {"app": {"data": {"type": "apps", "id": app_id}}}}})
if st >= 400:
print(" create internal group failed", st, json.dumps(r)[:600]); return
internal = r["data"]; print(" created internal group", internal["id"])
gid = internal["id"]
st, r = call("POST", "/v1/betaTesters",
{"data": {"type": "betaTesters",
"attributes": {"email": email, "firstName": first, "lastName": last},
"relationships": {"betaGroups": {"data": [{"type": "betaGroups", "id": gid}]}}}})
if st >= 400 and not _has_tester(app_id, email):
print(" add tester failed", st, json.dumps(r)[:600]); return
print(f" ãã¹ã¿ãŒ {email} ãå
éšã°ã«ãŒã {gid} ã«è¿œå ")
ã³ã¡ã³ãã«æžããŠããéããbetaGroups/{id}/relationships/betaTesters ãžã®çŽæ¥ POST 㯠409 STATE_ERRORïŒ"Tester cannot be assigned"ïŒã§åŒŸãããŸãããã¹ã¿ãŒã远å ãããšã㯠/v1/betaTesters ãžã® POST ã« betaGroups ãªã¬ãŒã·ã§ã³ã·ãããå«ã㊠ããã¹ã¿ãŒäœæãšåæã«ã°ã«ãŒããžçŽã¥ããã äžçºãªãã¬ãŒã·ã§ã³ãå¯äžã®æ£è§£ã§ãããã°ã«ãŒããäœã£ãŠãããã¹ã¿ãŒã远å ããããšããçŽæçãªæé ãèžããšãå»¶ã
ãš 409 ãåºãã ãã§ãã
å
éšãã¹ã¿ãŒã¯ Beta App Review äžèŠã§ã€ã³ã¹ããŒã«ã§ãããããå¯©æ»æåºåŸããèªåã® iCloud ã§ã€ã³ã¹ããŒã«ç¢ºèªã§ããŸããadd_tester ãæåºåŸã«åŒã¶ã®ã¯ãã€ãã©ã€ã³ã®å®çªã«ãŒãã£ã³ã«ãªããŸããã
ç§ãè©°ãŸã£ã話
12æ¬ãæ¬çªæå ¥ãããŸã§ã®éã«ã3çš®é¡ã®ãªãžã§ã¯ãããã€ãã©ã€ã³ãæ¢ããŸãããé çªã«ãçç¶ â åå â çŽãæ¹ããæžããŸãã
çœ â ïŒITMS-90111 ã®çå ã¯ãbeta macOS ã§ãã«ããããã ã£ã
çç¶ã archive.sh ãå®è¡ã㊠.ipa ã App Store Connect ãžã¢ããããŒããããšãåŠçå®äºåŸã®ã¡ãŒã«ã« ITMS-90111 ãå±ããŸããã
ERROR ITMS-90111: "Invalid Binary.
The value for key BuildMachineOSBuild in the Info.plist
file at Payload/Auraly.app/Info.plist is not valid."
App Store Connect ã® web ç»é¢ã«ã¯ãã€ããªããç¡å¹ããšè¡šç€ºããã_build_for_version() ããã€ãŸã§çµã£ãŠã processingState == "VALID" ã®ãã«ããè¿ããªãç¶æ
ã«ãªããŸããåã¢ããããŒãããŠãåããasc.py status ã§ build æ¬ã確èªãããš FAILED ãšåºãã ãã§ãã
åå ã xcodebuild ã¯ã¢ãŒã«ã€ãå®è¡æã«ããã«ããèµ°ããã macOS ã®ããŒãžã§ã³ã BuildMachineOSBuild ããŒãšã㊠Info.plist ãžèªåæžã蟌ã¿ããŸããarchive.sh ã®ã³ãã³ãèªäœã¯äœãæªããããŸããã
xcodebuild archive \
-project Auraly.xcodeproj \
-scheme Auraly \
-configuration Release \
-archivePath build/Auraly.xcarchive \
-destination 'generic/platform=iOS' \
CODE_SIGN_STYLE=Manual \
CODE_SIGN_IDENTITY="Apple Distribution" \
PROVISIONING_PROFILE_SPECIFIER="Auraly AppStore" \
-allowProvisioningUpdates
åé¡ã¯ãã®ã³ãã³ããå®è¡ãã macOS ã beta çã ã£ãããšã§ããmacOS 15 Sequoia ã® GA ãªãªãŒã¹åã« betaïŒãã«ãçªå· 25A5xxx ç³»ïŒã§åãããŠãããããbeta ã瀺ãæååã BuildMachineOSBuild ã«åã蟌ãŸããApple ã®ããªããŒã¿ãŒã匟ããŸãããxcodebuild ã« BuildMachineOSBuild ãäžæžããããªãã·ã§ã³ã¯ååšããããã«ããã·ã³ã® OS èªäœãæ£åŒãªãªãŒã¹çã«æ»ããããããŸããã
çŽãæ¹ã macOS ã beta ããæ£åŒãªãªãŒã¹çãžããŠã³ã°ã¬ãŒãããŠåã¢ãŒã«ã€ãããŸãããããŠã³ã°ã¬ãŒããé£ããç°å¢ã§ã¯ãGitHub Actions ã® macos-latest ã©ã³ããŒïŒåžžã« GA çïŒã§ã¢ãŒã«ã€ãããã®ãçŸå®çã§ãã以éã¯ãéçºäœæ¥ã¯ beta OKã»ã¢ãŒã«ã€ãã ã㯠GA å°çšãã·ã³ããšããéçšã«åãæ¿ããŸããããã®ã«ãŒã«ãå¿ããŠåã³èžãŸãªããããarchive.sh ã®å
é ã« sw_vers -buildVersion ã®åºåã衚瀺ããè¡ãå ããŠãããŸãã
çœ â¡ïŒappPriceSchedules ã¯ãincluded ãš null ã® startDateããæ£è§£
çç¶ã æ°èŠã¢ããªãç¡æïŒFreeïŒã§ç»é²ããããšäŸ¡æ Œèšå®ãšã³ããã€ã³ããž POST ãããšããã422 ãè¿ãç¶ããŸããã
422 {"errors":[{"status":"422","code":"INVALID_ENTITY",
"detail":"The provided entity includes invalid relationship data."}]}
ãšã©ãŒã¡ãã»ãŒãžãæ±çšãããŠäœãééã£ãŠãããããããŸãããApple ã®ããã¥ã¡ã³ãã«ã¯ãmanualPrices ã relationships ã«æž¡ãããšæžããŠããã ãã§ãå
·äœçãªããã£æ§é ã®äŸãå°ãªããæåã«è©Šããã®ã¯ãã®åœ¢ã§ããã
{
"data": {
"type": "appPriceSchedules",
"attributes": {},
"relationships": {
"app": {"data": {"type": "apps", "id": "<app_id>"}},
"manualPrices": {"data": [{"type": "appPrices", "id": "p1"}]}
}
}
}
åå ã manualPrices 㯠ã€ã³ã©ã€ã³ resourceïŒincludedïŒã䜿ã圢åŒã§ããid: "p1" ã§åç
§ããŠããã«ãé¢ãããããã® p1 ã®å®äœã included é
åã«å
¥ããŠããªãããããåç
§å
ã®ããŒã¿ãååšããªãããšã㊠422 ã«ãªããŸããããã«èœãšã穎ãããã²ãšã€ãããã峿æå¹ããæå³ãã startDate ã¯ç©ºæåå "" ã§ã¯ãªã JSON ã® null ã§ãªããã°ãªããŸããã"" ãå
¥ãããšå¥ã® 422 ãè¿ããŸãã
çŽãæ¹ã included ã« appPrices ãªããžã§ã¯ããæç€ºçã«å«ããstartDate ã null ã«ããŸãã
{
"data": {
"type": "appPriceSchedules",
"attributes": {},
"relationships": {
"app": {"data": {"type": "apps", "id": "<app_id>"}},
"manualPrices": {"data": [{"type": "appPrices", "id": "p1"}]}
}
},
"included": [
{
"type": "appPrices",
"id": "p1",
"attributes": {
"startDate": null,
"customerPrice": "0"
},
"relationships": {
"territory": {"data": {"type": "territories", "id": "JPN"}}
}
}
]
}
ãã ãã175 ãåœãã¹ãŠã® included ãåæããã®ã¯å®çšçã§ã¯ãããŸãããæçµçã« asc.py ãžäŸ¡æ Œèšå®é¢æ°ãçµã¿èŸŒãã®ã¯ãããäŸ¡æ Œã¯æåã« App Store Connect ã® web UI ã§èšå®ããŠãããAPI ã¯ã¡ã¿ããŒã¿ãšãã€ããªæäœã«å°å¿µããèšèšã«èœã¡çããŸãããåæåºã®ãã³ã«äŸ¡æ Œãäžæžãããããªã¹ã¯ãæé€ã§ããç¹ã§ãããã®åæ
ãçã«ããªã£ãŠããŸãã
çœ â¢ïŒen-US ãã±ãŒã«ã®ååè¡çª
çç¶ã set_whatsnew() ãå®è¡ãããšãç¹å®ã®ã¢ããªã ã 409 ãè¿ã£ãп޿°ãæ¢ãŸããŸããã
whatsNew en-US -> 409 {"errors":[{"status":"409","code":"STATE_ERROR",...}]}
ååŸã§ ja ã zh-Hans ãªã©ã»ãã®ãã±ãŒã«ã¯æ£åžžã«æŽæ°ãããŠãããããã¹ã¯ãªããèªäœã¯å£ããŠããŸãããen-US ã ããæ¯å倱æããç¶æ
ã§ãã
åå ã set_whatsnew() 㯠ASC API ãè¿ã localization ãªã¹ããå
šä»¶ PATCH ããŸãã
_, loc = call("GET", f"/v1/appStoreVersions/{vid}/appStoreVersionLocalizations")
for x in loc.get("data", []):
lid = x["id"]; locale = x["attributes"].get("locale")
st, r = call("PATCH", f"/v1/appStoreVersionLocalizations/{lid}",
{"data": {"type": "appStoreVersionLocalizations", "id": lid,
"attributes": {"whatsNew": text}}})
print(" whatsNew", locale, "->", st if st < 400 else json.dumps(r)[:300])
åé¡ã¯ãã®ã¢ããªã®ããŒãžã§ã³ã« en-US ãš en ã®äž¡æ¹ãååšããŠããããšã§ããApp Store Connect ã® web UI ã§è±èªåã en ã§äœæããåŸãdeliver ã Fastlane ã§ en-US ã®ã¡ã¿ããŒã¿ã远å ãããšäºéç»é²ãçºçããŸããApp Store 㯠en ãš en-US ãå¥ãšã³ãã£ãã£ãšããŠç®¡çãããããéè€ç¶æ
ã¯èš±å®¹ãããŸãããäžæ¹ãããã©ã€ããªãã±ãŒã«ããšããŠèšå®ãããŠãããšãããäžæ¹ãžã® PATCH ã 409 ãè¿ããŸãã
çŽãæ¹ã å¿æ¥åŠçœ®ãšããŠãlocale ã®èšèªã³ãŒãïŒ- 以åïŒãèŠãŠåãåºåºèšèªãæ¢ã«åŠçæžã¿ãªãã¹ãããããçµã蟌ã¿ã远å ããŸããã
processed_base = set()
for x in loc.get("data", []):
locale = x["attributes"].get("locale", "")
base = locale.split("-")[0] # "en-US" â "en"
if base in processed_base:
print(f" skip {locale} (already processed base {base})")
continue
processed_base.add(base)
lid = x["id"]
st, r = call("PATCH", f"/v1/appStoreVersionLocalizations/{lid}",
{"data": {"type": "appStoreVersionLocalizations", "id": lid,
"attributes": {"whatsNew": text}}})
print(" whatsNew", locale, "->", st if st < 400 else json.dumps(r)[:300])
æ ¹æ¬è§£æ±ºã¯ App Store Connect ã® web UI ã§éè€ãã±ãŒã«ãåé€ã en-US ã«äžæ¬åããããšã§ããã¹ã¯ãªããåŽã®ã¹ãããã¯ãããŸã§å¿æ¥åŠçœ®ã§ããããã©ã€ããªã en ã®ã¢ããªãš en-US ã®ã¢ããªãæ··åšããå Žåã¯ã¹ãããããžãã¯ãé广ã«ãªãå¯èœæ§ããããŸããasc.py apps ã§ã¢ããªäžèЧãååŸããåŸãåã¢ããªã®ãã±ãŒã«ç¶æ
ãå®æç¢ºèªããéçšãå ããããšã§ãåçºãé²ãã§ããŸãã
次ç¯ã§ã¯ã12æ¬ãåæå転ããããšãã®ã¬ãŒããªããã察çãšãClaude Code ãèªåŸã§ããªãžã§ã¯ã â ãã°ä¿®æ£ â åæåºãã倿ãããšãŒãžã§ã³ãã«ãŒãã®èšèšã解説ããŸãã
ã€ãŸãããã€ã³ãïŒå®äŸãšå®ã³ãŒãã§ç¶²çŸ ïŒ
åç« ã®3倧ãªãžã§ã¯ãïŒITMS-90111 / appPriceSchedules / en-USè¡çªïŒãé€ãããå®è£ ãšéçšã§èžãã 现ããªèœãšã穎ãäžæ°ã«åæããŸããã¬ãŒããªããã察çãããã§æ±ããŸãã
â JWT ã® iat ã â30 ç§ã«ããæå³ãæ¶ããŠã¯ãããªã
~/.appstoreconnect/asc.py ã® _jwt() ã¯ããæžããŠãããŸãã
p = _b64(json.dumps({"iss":ISSUER,"iat":int(time.time())-30,"exp":int(time.time())+900,
"aud":"appstoreconnect-v1"},separators=(",",":")).encode())
iat ã -30 ããŠããã®ã¯ãApple ã®ãµãŒããŒãšã®ã¯ããã¯ããªãã察çã§ããMac ã®æå»ã NTP ãããããã«ãããç¶æ
ã§ JWT ãçºè¡ãããšãApple åŽããæªæ¥ã®çºè¡æå»ã ããšå€æã㊠401 ãè¿ããŸãããã«ããµãŒããŒã§èªåå®è¡ããã»ã©çºçé »åºŠãäžããããããã® -30 ã¯çµ¶å¯Ÿã«å€ããŠã¯ãããŸãããexp ã® 900 ç§ïŒ15 åïŒã Apple ãèŠæ±ããäžéå€ã§ãã900 ãè¶
ããå€ãå
¥ãããšå³ 401 ã«ãªããŸãã
â¡ /v1/certificates?limit=200 ã®ããŒãžããŒã·ã§ã³æªå®è£
~/dev/auraly-ios/tools/setup_signing.py ã® find_cert() ã¯æ¬¡ã®ããã«æžããŠãããŸãã
LOCAL_SHA1 = "EC06777A693874E920CECFE390D467670552CCCE".lower()
def find_cert():
_, d = asc.call("GET", "/v1/certificates?limit=200")
for c in d.get("data", []):
content = c["attributes"].get("certificateContent")
if not content:
continue
der = base64.b64decode(content)
sha1 = hashlib.sha1(der).hexdigest()
if sha1 == LOCAL_SHA1:
return c["id"]
print("ERROR: no ASC cert matches local SHA1", LOCAL_SHA1)
sys.exit(1)
ASC API ã®ããŒãžããŒã·ã§ã³ïŒcursor ãã©ã¡ãŒã¿ïŒã¯å®è£
ããŠããŸãããèšŒææžã®ç·æ°ã 201 æ¬ä»¥äžãããšã察象ã®èšŒææžã 201 çªç®ä»¥éã«èœã¡ãç¬éã« sys.exit(1) ã§æ¢ãŸããŸããå人ã¢ã«ãŠã³ãã§ 12 æ¬çšåºŠãªãçŸå®çã«ã¯èµ·ããŸããããããŒã ã¢ã«ãŠã³ããé·å¹Žäœ¿ã£ãŠããã¢ã«ãŠã³ãã§ã¯èŠæ³šæã§ãã宿çã«å€±å¹æžã¿èšŒææžãåé€ããããcursor ããŒã¹ã®å
šä»¶èãã远å ããããšã§é²ããŸãã
⢠ensure_profile() ã¯ãååã®ãããã¡ã€ã«ãå
šãŠæ¶ãã
for p in d.get("data", []):
if p["attributes"].get("name") == PROFILE_NAME:
asc.call("DELETE", f"/v1/profiles/{p['id']}")
print("deleted stale profile", p["id"])
PROFILE_NAME = "Auraly AppStore" ã«å®å
šäžèŽãããããã¡ã€ã«ããã¹ãŠ DELETE ããŠããæ°èŠäœæããŸããè€æ°ã¢ããªã§åããããã¡ã€ã«åã䜿ãåããŠãããšãä»ã¢ããªã®ãããã¡ã€ã«ããŸãšããŠæ¶ããŠæ¬¡ã®ãã«ããéããªããªããŸãã"{ã¢ããªå} AppStore" ã®ããã«ã¢ããªããšã«ãŠããŒã¯ãªåœåã培åºããããšã§é²ããŸãã
⣠_latest_version() ã¯ææ° 1 ä»¶ãç²ç®çã«è¿ã
def _latest_version(app_id):
_, v = call("GET", f"/v1/apps/{app_id}/appStoreVersions?limit=1")
return v["data"][0] if v.get("data") else None
ãœãŒãæ¡ä»¶ã®æç€ºããªããããAPI ã®ããã©ã«ãïŒäœææ¥éé ïŒã§å
é 1 ä»¶ãè¿ããŸããã1.3.0ïŒå¯©æ»äžïŒããšã1.4.0ïŒç·šéäžïŒããåæã«ååšãããšããsubmit() 㯠1.4.0 ã® ID ãæŽãã§æåºããããšããŸãã12 æ¬ã䞊走ããããšãã®äºéããŒãžã§ã³ç¶æ
ãæå€ãšé·ãæ®ããŸããæäœåã« asc.py status <app_id> ãå®è¡ããŠç¶æ
ã確èªããç¿æ
£ãå¿
èŠã§ãã
†processingState: FAILED ã®ãã«ãã¯ã¹ã¯ãªãããé»ã£ãŠç¡èŠãã
for x in b.get("data", []):
if x["attributes"].get("processingState") != "VALID":
continue
VALID 以å€ãã¹ãããããã ãã§ãFAILED ãæ€ç¥ããŠå ±åããåŠçã¯å
¥ã£ãŠããŸãããApple ããã€ããªãåãåã£ããã®ã®åŠçã«å€±æããå Žåã_build_for_version() ã¯äœããªããã®ããã«æ¯ãèããŸããClaude Code ã«ãVALID ã«ãªããŸã§åŸ
ãŠããšæç€ºãããšãFAILED ã®ãŸãŸæ°žé ã«ããŒãªã³ã°ããŸããã¿ã€ã ã¢ãŠãïŒ30 åçšåºŠïŒãèšããŠè¶
éãããéç¥ããŠåæ¢ããä»çµã¿ãå¿
èŠã§ãã
⥠dedup ã®éè€å€å®ã¯ sourceFileChecksum + fileName ã®è€åããŒ
key = (sh["attributes"].get("sourceFileChecksum"), sh["attributes"].get("fileName"))
if key in seen:
åãç»åã§ããã¡ã€ã«åãå€ããã°å¥ããŒãšã¿ãªãããŸããFastlane ã®ã¡ã¿ããŒã¿ãã©ã«ãã§ screen_01.png â screen_1.png ã®ããã«ãªããŒã ããå Žåãåäžç»åã§ãéè€ãšããŠæ€åºãããŸããã--apply ãå®è¡ããåã«åŒæ°ãªãïŒdry-runïŒã§éè€ä»¶æ°ã確èªããäºæ³å€ã®ä»¶æ°ãªãæäœæ¥ã§ç
§åããŸãã
⊠set_whatsnew() ã¯å
šãã±ãŒã«ãžåäžããã¹ããæµã
for x in loc.get("data", []):
lid = x["id"]; locale = x["attributes"].get("locale")
st, r = call("PATCH", f"/v1/appStoreVersionLocalizations/{lid}",
{"data": {"type": "appStoreVersionLocalizations", "id": lid,
"attributes": {"whatsNew": text}}})
æž¡ãã text ããã±ãŒã«åããå
šä»¶ã« PATCH ããŸããè±èªã»äžåœèªã»éåœèªã®ãã±ãŒã«ã«æ¥æ¬èªã®æŽæ°æ
å ±ããã®ãŸãŸå
¥ããŸããè±èªåã®ãŠãŒã¶ãŒã¬ãã¥ãŒã«ãæŽæ°æ
å ±ãèªããªãããšæžãããããåã«å¯ŸåŠãå¿
èŠã§ãããã±ãŒã«å¥å¯Ÿå¿ã¯ {"ja": "...", "en-US": "..."} ãåãåãããã set_whatsnew() ãæ¡åŒµããããClaude Code ã«èšèªããšã®ç¿»èš³ãçæãããèŸæžãæž¡ãèšèšãçŸå®çã§ãã
â§ whatsNew ãã£ãŒã«ãã®äžé㯠4000 æå
App Store Connect ã® API 仿§äžéã§ããè¶
ãããš 422 ãè¿ããŸããClaude Code ã«ãæŽæ°æ
å ±ãèªåçæã㊠submit ãŸã§æµããŠããšæç€ºãããšãé·æãçæããŠãã®ãŸãŸæž¡ããããç¶æ³ãçãŸããŸããåŒã³åºãåŽã§ whatsnew = whatsnew[:4000] ãšã¯ãªããããŠããããããã³ããã«ã4000 æå以å
ã§ãŸãšããããšæèšããŠããããšã§é²ããŸãã
âš IN_REVIEW ç¶æ
ã§ã¯å
š PATCH ã 422 ã«ãªã
asc.py ã® _EDITABLE ã¯æ¬¡ã® 5 ç¶æ
ããå«ã¿ãŸããã
_EDITABLE = {"PREPARE_FOR_SUBMISSION", "DEVELOPER_REJECTED", "REJECTED",
"METADATA_REJECTED", "INVALID_BINARY"}
WAITING_FOR_REVIEW ãš IN_REVIEW ã¯ãã®ã»ããã®å€ã§ãã審æ»äžã«ã¡ã¿ããŒã¿ãå€ããããšããŠã API ã¯ç¡èšã§ 422 ãè¿ãã ãã§ããreject() ã§åãäžããŠããç·šéããå¿
èŠããããŸãã12 æ¬åæéçšã ãšã©ãããåžžã«å¯©æ»äžã«ãªããããªããã®ã¢ããªã ãæŽæ°ã§ããªãã®ãããšæ··ä¹±ããããç¶æ
ã«ãªããŸãã
â© 12 æ¬åææåºã§ã¬ãŒããªãããïŒ429ïŒã«åœãã
call() 颿°ã«ã¯ retry / backoff ãå®è£
ãããŠããŸããã
try:
r = urllib.request.urlopen(req); raw = r.read()
return r.status, (json.loads(raw) if raw else None)
except urllib.error.HTTPError as e:
return e.code, json.loads(e.read() or b"{}")
12 æ¬åãäžæã« dedup â submit ããã«ãŒããçµããšãæ°åã§ 1000 ãªã¯ãšã¹ã/åïŒç®å®ïŒã®å¶éã«è¿ã¥ããŸãã429 ãè¿ã£ãŠããšã©ãŒãäžäœã«è¿ãã ãã§å詊è¡ããªããããã«ãŒãåŸåã®ã¢ããªãé»ã£ãŠå€±æããŸããå call() ã®åŸã« time.sleep(0.1) ãæãæäœéã®å¯Ÿçãã429 ãæ€ç¥ãããææ°ããã¯ãªãã§å詊è¡ããã©ãããŒã远å ããããšã§å®å®ããŸãã
⪠eas submit / altool ã®å®äºã¯ asc.py ã®å€
ãã€ããªã®ã¢ããããŒã㯠eas submit ãŸã㯠altool ãæ
ããasc.py ã¯ãã®å®äºãæç¥ã§ããŸãããã¢ããããŒããã Apple ãµãŒããŒåŽã® VALID å€å®ãŸã§ 3ã10 åããããŸãããã®åŸ
ã¡æéãèæ
®ããã« release â submit ãé£ç¶å®è¡ãããšããVALID build ãèŠã€ãããªããã®ãŸãŸç©ºæ¯ãããŸããasc.py status ã§ãã«ãæ¬ã確èªããŠãã release ãåŒã¶ãã_build_for_version() ã䜿ã£ãããŒãªã³ã°ãã©ãããŒãšããŠæã¿ãŸãã
â« archive.sh ã® set -euo pipefail ã¯æå³ããèšèšã ãèŠèœãšãããã
set -euo pipefail
cd "$(dirname "$0")/.."
xcodegen generate ã®å€±æã§ã xcodebuild archive ã®å€±æã§ãå³åº§ã«ã¹ã¯ãªããå
šäœã忢ããŸããClaude Code ã archive.sh ãå®è¡ãããšããçµäºã³ãŒã 1 ã®ãã°ãèŠèœãšããšã倱æããããšæ°ã¥ããªãå ŽåããããŸããarchive.sh ã®æ«å°Ÿã« echo "ARCHIVE_SUCCESS: $ROOT/build/export/Auraly.ipa" ã確èªããŒã«ãŒãšããŠè¿œå ããClaude Code ãžã®æç€ºã«ããã®ã¡ãã»ãŒãžãåºãªããã°å€±æãšããŠå ±åããããæèšããŠãããšèª€èªèãé²ããŸãã
ãã¹ããã©ã¯ãã£ã¹
å®è£ ãšå€±æã®åŸåŸ©ããèžçãã 12 æ¬ã®ååã§ãã
1. ã¢ãŒã«ã€ã㯠GA macOS å°çšãã·ã³ã§è¡ã
beta macOS ã§ãã«ããããš BuildMachineOSBuild ã« beta æååãåã蟌ãŸã ITMS-90111 ã§åŒŸãããŸããéçºã¯ beta ç°å¢ã§è¡ã£ãŠããarchive.sh ãå®è¡ãããã·ã³ã¯æ£åŒãªãªãŒã¹çã® macOS ã ãã«éããŸããGitHub Actions ã® macos-latest ã©ã³ããŒã¯åžžã« GA çã䜿ããããããCI ã§ã¢ãŒã«ã€ãããèšèšãæãå®å
šã§ãã
2. archive.sh ã®å é ã« OS ãã«ãçªå·ãåºåãã
echo "macOS build: $(sw_vers -buildVersion)"
CI ãã°ã«ãã«ãçªå·ãæ®ããããITMS-90111 ãåçºãããšããbeta ã§ãã«ãããããšå³åº§ã«ç¹å®ã§ããŸãã24G85 㯠GAã25A5xxx 系㯠beta ãšãããã«ãçªå·ã®ãã¿ãŒã³ãèŠããŠããã ãã§èšºæé床ãäžãããŸãã
3. äŸ¡æ Œèšå®ã¯ Web UI ã§ååã®ã¿è¡ã API ã«ä»»ããªã
appPriceSchedules ã API ã§æäœããã«ã¯ 175 ãåœåã® included ãå¿
èŠã§ãå®çšçã§ã¯ãããŸãããæåã® 1 åã ã Web UI ã§ãç¡æããŸãã¯ä»»æã®ãã£ã¢ãèšå®ãã以éã®æåºã§ã¯è§Šããªãèšèšãæ£è§£ã§ããåæåºã®ãã³ã«äŸ¡æ Œãäžæžãããããªã¹ã¯ããŒãã«ãªããŸãã
4. submit ã®çŽåã«å¿
ã dedup ãå®è¡ãã
python3 ~/.appstoreconnect/asc.py dedup <app_id> --apply
python3 ~/.appstoreconnect/asc.py submit <app_id>
ãã®é çªã厩ããªãããšã Claude Code ãžã®æç€ºãã³ãã¬ãŒãã«åã蟌ã¿ãŸããé çªãéã«ãªããšãéè€ã¹ã¯ã·ã§ããã®ãŸãŸå¯©æ»ãžé²ã¿ãŸãã
5. ãã±ãŒã«ã¯ en-US ã«äžæ¬åã en ãæ®ããªã
æ¢åã® en ãã±ãŒã«ã¯ Web UI ããåé€ã en-US ã®ã¿ã«ããŸããã¹ã¯ãªããåŽã®ãåºåºèšèªéè€ã¹ããããã¯å¿æ¥åŠçœ®ã§ãããæ ¹æ¬è§£æ±ºã¯ãã±ãŒã«ã®äžæ¬åã§ãããã©ã€ããªãã±ãŒã«ã en ã®ã¢ããªãš en-US ã®ã¢ããªãæ··åšãããšãã¹ãããããžãã¯ãé广ã«ãªãã±ãŒã¹ããããŸãã
6. status ããæåºåããšãæåºåŸ 24 æéãã® 2 ç¹ã§ç¢ºèªãã
æåºåïŒããŒãžã§ã³ç¶æ
ã»VALID ãã«ãã®æç¡ã確èªãæåºåŸ 24 æéïŒreviewSubmission.state ã COMPLETE ã«å€ãã£ãŠãããã確èªããŸããClaude Code ã«ãã®ãã§ãã¯ãã«ãŒãã£ã³ãšããŠçµã¿èŸŒããšãæ¿èªãšãªãžã§ã¯ãã®æ€ç¥ãèªååã§ããŸãã
7. Developer Rejection ã¯ããã«ãã£ãªãã»æ°ã¥ãããå³ reject ãã
審æ»äžã«äžå
·åãçºèŠããã reject() â ä¿®æ£ â submit ãå³åº§ã«åããŸããã審æ»ã«éãã詊ããŠã¿ããå§¿å¢ã®ã»ããããªãžã§ã¯ãåŸ
æ©æéã§æãããŸããDeveloper Rejection ã¯å¯©æ»ã«ãŠã³ãã«åœ±é¿ããŸããã
8. _EDITABLE ç¶æ
ãæäœåã«ç¢ºèªãã
status ã³ãã³ãã§ appStoreState ã _EDITABLE ã® 5 ç¶æ
ã«ããããšã確èªããŠãã release ãå®è¡ããŸããIN_REVIEW äžã«æäœããŠã 422 ãè¿ãã ãã§å¯äœçšã¯ãããŸããããæéãš API ã³ãŒã«ãç¡é§ã«ããŸãã
9. ãããã¡ã€ã«åã¯ã¢ããªåºæã«ãã
"{ã¢ããªå} AppStore" 圢åŒã§åœåããè€æ°ã¢ããªãååã«ãªããªãããã«ããŸããensure_profile() ã¯ååãå
šåé€ãããããåœåã被ããšä»ã¢ããªã®ãããã¡ã€ã«ãæ¶ããŠæ¬¡ã®ãã«ãã倱æããŸãã
10. call() ã« 429 察å¿ã®ææ°ããã¯ãªãã远å ãã
def call_with_retry(method, path, body=None, max_retry=3):
for attempt in range(max_retry):
st, data = call(method, path, body)
if st == 429:
time.sleep(2 ** attempt)
continue
return st, data
return st, data
12 æ¬åææåºã®ã«ãŒããå®å®ããŸããçŸç¶ã® call() ã¯ãã®ãŸãŸæ®ããã¬ãŒããªããããæ°ã«ãªãæäœã ãã©ãããŒçµç±ã§åŒã¶åœ¢ã§æ®µéçã«å·®ãæ¿ããããŸãã
11. VALID åŸ ã¡ã«ã¿ã€ã ã¢ãŠããèšãã
Claude Code ã«ãprocessingState: VALID ã«ãªããŸã§åŸ
ãŠããšæç€ºãããšããå¿
ãã30 åçµã£ãŠã VALID ã§ãªããã° FAILED æ±ãã§éç¥ããŠåæ¢ãããæ¡ä»¶ãæ·»ããŸããã¿ã€ã ã¢ãŠããªãã®ããŒãªã³ã°ã¯ãæ·±å€ã®èªåŸå®è¡ã§è©°ãŸã£ããŸãŸæ°ã¥ããªãåå ã«ãªããŸãã
12. whatsNew ã¯åŒã³åºãåã« 4000 æåã§ã¯ãªãããã
whatsnew = generated_text[:4000]
Claude Code ãçæããæŽæ°æ
å ±ãç¡æ€æ»ã§æž¡ããšäžéè¶
éã§ 422 ãåºãŸããã¯ãªãããåŒã³åºãåŽã®è²¬ä»»ã«ããŠãããšãset_whatsnew() æ¬äœãæ±ããã«æžã¿ãŸãã
ãŸãšã
åç·šã»åŸç·šãéããŠã~/.appstoreconnect/asc.pyïŒ272 è¡ïŒã»archive.shïŒ24 è¡ïŒã»setup_signing.pyïŒ72 è¡ïŒã® 3 ã¹ã¯ãªããã§ãiOS ã¢ã㪠12 æ¬ã®å¯©æ»æåºãå®å
šã«ç¡äººåã§ããããšã瀺ããŸããã
審æ»ãæ¢ããçœ ã¯ãApple ãµãŒããŒåºæã®éèªæãªæåïŒITMS-90111 ã®çå ã»appPriceSchedules ã® included 圢åŒã»en ãš en-US ã®äºéç»é²ïŒãšãå®è£
ã®å°ããªåæïŒããŒãžããŒã·ã§ã³ã»åœåè¡çªã»ãã±ãŒã«çµ±äžã»ã¿ã€ã ã¢ãŠãã»ã¬ãŒããªãããïŒã®çµã¿åããã§ãããã©ããå®éã« 12 æ¬ã䞊走ãããªããèžã¿æãããã®ã§ããããã¥ã¡ã³ããèªãã ãã§ã¯äºé²ã§ããŸããã
æå 120 äžã®å®æ ã¯ãäœæ¥éãå¢ããããã®ã§ã¯ãªãããåã 3 ã¹ã¯ãªããã 12 ã¢ããªå䞊åã§åããŠãããã ãã§ãããã€ãã©ã€ã³ãæ£ããçµãŸããŠããã°ãã¢ã㪠ID ã 1 è¡è¿œå ããã ãã§ã¹ã±ãŒã«ããŸãããããŸã§æžããŠãã 3 ã€ã®ã¹ã¯ãªãããããã®åå°ã«ãªã£ãŠããŸãã
次å㯠Claude Code ãããªãžã§ã¯ãæ€ç¥ â ãã°ä¿®æ£ â åæåºããèªåŸã§å€æãããšãŒãžã§ã³ãã«ãŒãã®èšèšã解説ããŸãã
ä»çµã¿ã®å šäœåã»æ120äžã®å èš³ã»30æ¥æé ã¯æænoteã«ãŸãšããŠããŸã
ð Claude CodeèªåŸç°å¢ã§ãå®éã©ã皌ãã â ä»çµã¿ã»å®äŸã»å§ãæ¹ã»ãµããŒã
LilyïŒ@bokuwalilyïŒâ å人éçºè ãClaude Code ã§èªåååºç€ãçµã¿ãªãããiOSã¢ããªãWebãµãŒãã¹ãéç£ããŠããŸã
- AIã§ãå¯ãŠãŠãåãä»çµã¿ããäœã£ãŠæ120äžã«ãã話㯠noteã®ææèšäº ã«ð°
- OSS: github.com/bokuwalily ð
- ææ°æ å ±ã»ãåãåãã㯠X @bokuwalily ãžð
çããã® â€ïž ãã·ã§ã¢ãå±ã¿ã«ãªããŸãïŒ