@yoonion/mimi-seed-mcp 0.13.12 → 0.13.13
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/dist/appstore/errors.js +5 -2
- package/dist/appstore/tools.d.ts +9 -3
- package/dist/appstore/tools.js +41 -14
- package/dist/playstore/tools.d.ts +8 -1
- package/dist/playstore/tools.js +56 -4
- package/dist/registers/appstore.js +17 -2
- package/dist/registers/playstore.js +2 -0
- package/package.json +1 -1
package/dist/appstore/errors.js
CHANGED
|
@@ -34,8 +34,11 @@ const CODE_HINTS = {
|
|
|
34
34
|
NOT_FOUND: '대상 리소스를 찾을 수 없어요 — id/versionId/buildId 가 유효한지, 동일 앱 소속인지 확인.',
|
|
35
35
|
// 권한 부족 (API Key role 문제)
|
|
36
36
|
FORBIDDEN_ERROR: 'API Key 권한 부족 — App Store Connect > Users and Access 에서 키 role 확인.',
|
|
37
|
-
//
|
|
38
|
-
|
|
37
|
+
// 상태 전이 거부. cancel 은 WAITING_FOR_REVIEW 까지 되고, IN_REVIEW 이후만 막힌다.
|
|
38
|
+
// (예전 문구는 "큐 진입 후라 불가"였는데, 그건 cancel 이 submitted:false 를 보내
|
|
39
|
+
// 항상 실패하던 버그의 증상을 상태 탓으로 오해한 것이었다 — 2026-07-25 정정.)
|
|
40
|
+
STATE_ERROR: '현재 상태에서 허용되지 않는 작업. 제출된 묶음은 항목 제거(removed)가 막히니 '
|
|
41
|
+
+ 'appstore_cancel_review 로 묶음째 취소하세요. IN_REVIEW 이후면 그마저도 불가 — 심사 결과를 기다립니다.',
|
|
39
42
|
// JWT 거부 — 잘못된 자격증명
|
|
40
43
|
NOT_AUTHORIZED: 'API 키가 거부됐어요 — issuerId/keyId/.p8 조합 불일치 가능. appstore_verify_credentials 로 진단하고 mimi-seed auth appstore 로 재등록.',
|
|
41
44
|
};
|
package/dist/appstore/tools.d.ts
CHANGED
|
@@ -137,9 +137,11 @@ export declare function createReviewResponse(reviewId: string, responseBody: str
|
|
|
137
137
|
/**
|
|
138
138
|
* submit_for_review dry-run 프리뷰 — 비가역 제출 직전 사용자 확인용.
|
|
139
139
|
*
|
|
140
|
-
* 1.4.x 배포 사고 누적: submit 직후
|
|
141
|
-
*
|
|
142
|
-
*
|
|
140
|
+
* 1.4.x 배포 사고 누적: submit 직후 되돌리지 못해 새 versionString bump 으로 우회해야
|
|
141
|
+
* 하는 케이스가 반복됨 (reference_appstore_cancel_review_window). 그 원인이던
|
|
142
|
+
* cancel_review 버그(submitted:false → 항상 409)는 2026-07-25 에 canceled:true 로 고쳤고,
|
|
143
|
+
* 이제 WAITING_FOR_REVIEW 까지는 되돌릴 수 있다. 그래도 IN_REVIEW 이후는 못 되돌리니
|
|
144
|
+
* 호출자가 의도한 그 버전·빌드인지 미리 보여줘서 잘못된 versionId 제출을 차단한다.
|
|
143
145
|
*/
|
|
144
146
|
export declare function buildSubmitForReviewPreview(versionId: string): Promise<{
|
|
145
147
|
versionId: string;
|
|
@@ -189,6 +191,10 @@ export interface ReviewSubmissionSummary {
|
|
|
189
191
|
targetId?: string;
|
|
190
192
|
versionString?: string;
|
|
191
193
|
appVersionState?: string;
|
|
194
|
+
/** IAP/구독/구독그룹 항목의 식별자 — productId 또는 referenceName. */
|
|
195
|
+
label?: string;
|
|
196
|
+
/** 대상 리소스 자체의 상태 (상품이면 READY_TO_SUBMIT / WAITING_FOR_REVIEW 등). */
|
|
197
|
+
targetState?: string;
|
|
192
198
|
}>;
|
|
193
199
|
}
|
|
194
200
|
export declare function listReviewSubmissions(args: {
|
package/dist/appstore/tools.js
CHANGED
|
@@ -556,9 +556,11 @@ async function isVersionAttached(submissionId, versionId) {
|
|
|
556
556
|
/**
|
|
557
557
|
* submit_for_review dry-run 프리뷰 — 비가역 제출 직전 사용자 확인용.
|
|
558
558
|
*
|
|
559
|
-
* 1.4.x 배포 사고 누적: submit 직후
|
|
560
|
-
*
|
|
561
|
-
*
|
|
559
|
+
* 1.4.x 배포 사고 누적: submit 직후 되돌리지 못해 새 versionString bump 으로 우회해야
|
|
560
|
+
* 하는 케이스가 반복됨 (reference_appstore_cancel_review_window). 그 원인이던
|
|
561
|
+
* cancel_review 버그(submitted:false → 항상 409)는 2026-07-25 에 canceled:true 로 고쳤고,
|
|
562
|
+
* 이제 WAITING_FOR_REVIEW 까지는 되돌릴 수 있다. 그래도 IN_REVIEW 이후는 못 되돌리니
|
|
563
|
+
* 호출자가 의도한 그 버전·빌드인지 미리 보여줘서 잘못된 versionId 제출을 차단한다.
|
|
562
564
|
*/
|
|
563
565
|
export async function buildSubmitForReviewPreview(versionId) {
|
|
564
566
|
const { appId, platform } = await getVersionAppAndPlatform(versionId);
|
|
@@ -742,10 +744,21 @@ export async function addProductToReviewSubmission(args) {
|
|
|
742
744
|
const enriched = new Error([
|
|
743
745
|
`상품 ${internalId} (${productType}) 은 API 로 심사 제출할 수 없는 상태야 — Apple 응답: "no pending version for submission".`,
|
|
744
746
|
'',
|
|
745
|
-
'
|
|
746
|
-
'
|
|
747
|
-
'
|
|
748
|
-
'
|
|
747
|
+
'**앱 첫 심사** 케이스다. 한 번도 승인된 적 없는 상품은 공개 API 로 심사에 못 넣는다.',
|
|
748
|
+
'앱 버전을 심사 대기로 만들어도 이 벽은 그대로다 (2026-07-25 재확인).',
|
|
749
|
+
'',
|
|
750
|
+
'실제로 통하는 순서 (PenguinRun 2.0.6 실측):',
|
|
751
|
+
' 1. appstore_submit_for_review 로 앱 버전을 먼저 제출한다.',
|
|
752
|
+
' → 상품은 자동으로 안 딸려간다. 버전 1개짜리 묶음 A 가 생긴다. 이건 정상이다.',
|
|
753
|
+
' 2. 그래야 ASC 웹에 상품의 "심사 추가" UI 가 나타난다. 웹에서 상품을 담으면',
|
|
754
|
+
' 상품들만 든 **새 묶음 B** 가 READY_FOR_REVIEW(미제출)로 생성된다.',
|
|
755
|
+
' 3. B 를 그냥 제출하면 409: "an appStoreVersions must be included in this review submission".',
|
|
756
|
+
' 상품 묶음에는 앱 버전이 반드시 함께 있어야 한다.',
|
|
757
|
+
' 4. appstore_cancel_review 로 묶음 A 를 취소해 버전을 풀어준다.',
|
|
758
|
+
' 5. POST /reviewSubmissionItems 로 B 에 appStoreVersion 을 추가한다.',
|
|
759
|
+
' (상품 관계는 거부되지만 **버전 관계는 받는다**.)',
|
|
760
|
+
' 6. B 를 submitted=true 로 PATCH → 버전+상품이 한 묶음으로 제출된다.',
|
|
761
|
+
'',
|
|
749
762
|
'이미 승인된 적 있는 상품이라면: 변경분(pending version)이 실제로 있는지 확인.',
|
|
750
763
|
].join('\n'));
|
|
751
764
|
enriched.cause = error.cause;
|
|
@@ -779,22 +792,30 @@ export async function listReviewSubmissions(args) {
|
|
|
779
792
|
const submissions = (data?.data ?? []);
|
|
780
793
|
const result = [];
|
|
781
794
|
for (const sub of submissions) {
|
|
795
|
+
// 버전만 include 하면 IAP·구독·구독그룹 항목이 전부 "?" 로 남아, 정작 중요한
|
|
796
|
+
// "이 묶음에 상품이 들어갔나"를 이 도구로 판별할 수 없었다 (2026-07-25 실측).
|
|
782
797
|
const itemsData = await apiGet(`/reviewSubmissions/${sub.id}/items`, {
|
|
783
|
-
include: 'appStoreVersion',
|
|
798
|
+
include: 'appStoreVersion,inAppPurchaseV2,subscription,subscriptionGroup',
|
|
784
799
|
'fields[appStoreVersions]': 'versionString,appVersionState',
|
|
800
|
+
'fields[inAppPurchases]': 'productId,name,state',
|
|
801
|
+
'fields[subscriptions]': 'productId,name,state',
|
|
802
|
+
'fields[subscriptionGroups]': 'referenceName',
|
|
785
803
|
limit: '50',
|
|
786
804
|
}).catch(() => null);
|
|
787
805
|
const included = new Map((itemsData?.included ?? []).map((inc) => [`${inc.type}:${inc.id}`, inc]));
|
|
788
806
|
const items = (itemsData?.data ?? []).map((item) => {
|
|
789
807
|
const target = Object.entries(item.relationships ?? {}).find(([key, rel]) => key !== 'reviewSubmission' && rel?.data?.id)?.[1]?.data;
|
|
790
808
|
const inc = target?.type && target.id ? included.get(`${target.type}:${target.id}`) : undefined;
|
|
809
|
+
const a = inc?.attributes;
|
|
791
810
|
return {
|
|
792
811
|
id: item.id,
|
|
793
812
|
state: item.attributes?.state,
|
|
794
813
|
targetType: target?.type,
|
|
795
814
|
targetId: target?.id,
|
|
796
|
-
versionString:
|
|
797
|
-
appVersionState:
|
|
815
|
+
versionString: a?.versionString,
|
|
816
|
+
appVersionState: a?.appVersionState,
|
|
817
|
+
label: a?.productId ?? a?.referenceName ?? a?.name,
|
|
818
|
+
targetState: a?.state,
|
|
798
819
|
};
|
|
799
820
|
});
|
|
800
821
|
result.push({
|
|
@@ -846,7 +867,7 @@ async function releaseVersionFromStaleSubmissions(appId, platform, versionId) {
|
|
|
846
867
|
// ─── 심사 철회 (Cancel Review) ───
|
|
847
868
|
// WAITING_FOR_REVIEW 상태의 reviewSubmission에만 적용 가능.
|
|
848
869
|
// IN_REVIEW 진입 후에는 Apple API가 거부함 (409).
|
|
849
|
-
//
|
|
870
|
+
// PATCH attributes.canceled=true → state CANCELING → COMPLETE, version 은 편집 가능 상태로 복귀.
|
|
850
871
|
export async function cancelVersionReview(versionId) {
|
|
851
872
|
const { appId, platform } = await getVersionAppAndPlatform(versionId);
|
|
852
873
|
// 취소 가능한 상태(WAITING_FOR_REVIEW)의 submission 검색
|
|
@@ -866,15 +887,21 @@ export async function cancelVersionReview(versionId) {
|
|
|
866
887
|
}
|
|
867
888
|
const submissionId = submission.id;
|
|
868
889
|
const previousState = submission.attributes?.state ?? 'WAITING_FOR_REVIEW';
|
|
869
|
-
//
|
|
890
|
+
// 취소 속성은 canceled 다. submitted:false 는 Apple 이 거부한다:
|
|
891
|
+
// 409 ENTITY_ERROR.ATTRIBUTE.INVALID "submitted must be set to true if present"
|
|
892
|
+
// 예전 구현이 submitted:false 를 보내서 이 도구는 사실상 항상 실패했고, 그 탓에
|
|
893
|
+
// "큐 진입 후에는 웹에서만 취소 가능" 이라는 잘못된 통설이 굳어 있었다.
|
|
894
|
+
// 실제로는 WAITING_FOR_REVIEW 에서도 canceled:true 가 통한다 (2026-07-25 실측):
|
|
895
|
+
// 200 → state: CANCELING → (수십 초) → COMPLETE, 항목은 REMOVED, 버전은 편집 가능 복귀.
|
|
870
896
|
const patched = await apiPatch(`/reviewSubmissions/${submissionId}`, {
|
|
871
897
|
data: {
|
|
872
898
|
type: 'reviewSubmissions',
|
|
873
899
|
id: submissionId,
|
|
874
|
-
attributes: {
|
|
900
|
+
attributes: { canceled: true },
|
|
875
901
|
},
|
|
876
902
|
});
|
|
877
|
-
|
|
903
|
+
// CANCELING 은 비동기다 — 호출 직후엔 아직 COMPLETE 가 아니다.
|
|
904
|
+
const newState = patched?.data?.attributes?.state ?? 'CANCELING';
|
|
878
905
|
return { submissionId, previousState, newState, versionId };
|
|
879
906
|
}
|
|
880
907
|
// ─── 인앱 구매 (IAP) 생성 ───
|
|
@@ -22,7 +22,11 @@ export interface PlayStatisticsQuery {
|
|
|
22
22
|
userCohort?: 'OS_PUBLIC' | 'APP_TESTERS' | 'OS_BETA';
|
|
23
23
|
timeZone?: string;
|
|
24
24
|
}
|
|
25
|
-
export
|
|
25
|
+
export interface EditCommitInfo {
|
|
26
|
+
/** true 면 커밋은 됐지만 심사 전송은 Play Console 에서 사람이 눌러야 한다. */
|
|
27
|
+
changesNotSentForReview: boolean;
|
|
28
|
+
}
|
|
29
|
+
export declare function withEdit<T>(auth: OAuth2Client | JWT, packageName: string, fn: (editId: string) => Promise<T>, commit?: boolean, onCommit?: (info: EditCommitInfo) => void): Promise<T>;
|
|
26
30
|
export declare function getStatistics(auth: OAuth2Client | JWT, packageName: string, query: PlayStatisticsQuery): Promise<unknown>;
|
|
27
31
|
export declare function getAppDetails(auth: OAuth2Client | JWT, packageName: string): Promise<import("googleapis/build/src/apis/androidpublisher/v3.js").androidpublisher_v3.Schema$AppDetails>;
|
|
28
32
|
export declare function updateAppDetails(auth: OAuth2Client | JWT, packageName: string, data: {
|
|
@@ -95,6 +99,9 @@ export interface PromoteReleaseOptions {
|
|
|
95
99
|
copyReleaseNotes?: boolean;
|
|
96
100
|
}
|
|
97
101
|
export declare function promoteRelease(auth: OAuth2Client | JWT, packageName: string, fromTrack: string, toTrack: string, versionCode: string, options?: PromoteReleaseOptions): Promise<{
|
|
102
|
+
changesNotSentForReview: boolean;
|
|
103
|
+
nextAction: string | undefined;
|
|
104
|
+
warnings: string[] | undefined;
|
|
98
105
|
packageName: string;
|
|
99
106
|
fromTrack: string;
|
|
100
107
|
toTrack: string;
|
package/dist/playstore/tools.js
CHANGED
|
@@ -41,7 +41,26 @@ function dateToReportingDateTime(date, timeZone) {
|
|
|
41
41
|
timeZone: { id: timeZone },
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
|
-
|
|
44
|
+
// Play 는 앱/계정 상태에 따라 "커밋과 동시에 심사 전송"을 거부한다:
|
|
45
|
+
// Changes cannot be sent for review automatically.
|
|
46
|
+
// Please set the query parameter changesNotSentForReview to true.
|
|
47
|
+
// 이 경우 그 파라미터를 붙여야만 커밋이 통과한다. 예전 구현은 파라미터를 노출하지도,
|
|
48
|
+
// 폴백하지도 않아서 그런 앱에서는 promote/submit 계열이 **100% 실패**했다
|
|
49
|
+
// (PenguinRun 2026-07-25 실측 — 결국 Android Publisher API 를 직접 쳐서 우회해야 했다).
|
|
50
|
+
async function commitEdit(auth, packageName, editId) {
|
|
51
|
+
try {
|
|
52
|
+
await publisher().edits.commit({ auth, packageName, editId });
|
|
53
|
+
return { changesNotSentForReview: false };
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
const msg = String(err?.message ?? err);
|
|
57
|
+
if (!/changesNotSentForReview/i.test(msg))
|
|
58
|
+
throw err;
|
|
59
|
+
await publisher().edits.commit({ auth, packageName, editId, changesNotSentForReview: true });
|
|
60
|
+
return { changesNotSentForReview: true };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export async function withEdit(auth, packageName, fn, commit = false, onCommit) {
|
|
45
64
|
const res = await publisher().edits.insert({ auth, packageName });
|
|
46
65
|
const editId = res.data.id;
|
|
47
66
|
if (!editId)
|
|
@@ -49,7 +68,12 @@ export async function withEdit(auth, packageName, fn, commit = false) {
|
|
|
49
68
|
try {
|
|
50
69
|
const result = await fn(editId);
|
|
51
70
|
if (commit) {
|
|
52
|
-
|
|
71
|
+
// 반드시 먼저 커밋하고 나서 콜백에 넘긴다.
|
|
72
|
+
// onCommit?.(await commitEdit(...)) 로 쓰면 안 된다 — 옵셔널 호출은 수신자가
|
|
73
|
+
// nullish 일 때 인자 자체를 평가하지 않아서, onCommit 을 안 넘긴 호출자(대부분)에서
|
|
74
|
+
// 커밋이 통째로 사라진다. playstore-listing 테스트가 이걸 잡아냈다.
|
|
75
|
+
const info = await commitEdit(auth, packageName, editId);
|
|
76
|
+
onCommit?.(info);
|
|
53
77
|
}
|
|
54
78
|
return result;
|
|
55
79
|
}
|
|
@@ -379,7 +403,9 @@ export async function promoteRelease(auth, packageName, fromTrack, toTrack, vers
|
|
|
379
403
|
if (status === 'inProgress' && (userFraction == null || userFraction <= 0 || userFraction >= 1)) {
|
|
380
404
|
throw new Error('status="inProgress"일 때 userFraction은 0과 1 사이 필수 (예: 0.1 → 10%).');
|
|
381
405
|
}
|
|
382
|
-
|
|
406
|
+
let commitInfo = { changesNotSentForReview: false };
|
|
407
|
+
const warnings = [];
|
|
408
|
+
const out = await withEdit(auth, packageName, async (editId) => {
|
|
383
409
|
// 1. source 트랙에서 versionCode 매칭 release 찾기
|
|
384
410
|
const fromData = await publisher().edits.tracks.get({
|
|
385
411
|
auth, packageName, editId, track: fromTrack,
|
|
@@ -405,6 +431,20 @@ export async function promoteRelease(auth, packageName, fromTrack, toTrack, vers
|
|
|
405
431
|
if (status === 'inProgress' && userFraction != null) {
|
|
406
432
|
newRelease.userFraction = userFraction;
|
|
407
433
|
}
|
|
434
|
+
// 2-b. 릴리스 노트 회귀 가드.
|
|
435
|
+
// CI 가 internal 에 올릴 때 "v2.0.6 (70)" 같은 플레이스홀더를 한 언어만 넣어두는 경우가 흔한데,
|
|
436
|
+
// copyReleaseNotes 기본값(true)으로 production 에 승격하면 살아 있던 다국어 노트가
|
|
437
|
+
// 그 플레이스홀더로 통째 덮인다 (PenguinRun 2026-07-25 에 실제로 밟을 뻔한 함정).
|
|
438
|
+
// 막지는 않되(의도적일 수 있으므로) 반드시 눈에 띄게 알린다.
|
|
439
|
+
if (!releaseNotes && copyReleaseNotes) {
|
|
440
|
+
const copied = sourceRelease.releaseNotes ?? [];
|
|
441
|
+
const placeholder = copied.filter((n) => /^\s*v?\d+(\.\d+)*\s*(\(\d+\))?\s*$/.test(n.text ?? ''));
|
|
442
|
+
if (placeholder.length > 0) {
|
|
443
|
+
warnings.push(`${fromTrack} 의 릴리스 노트가 버전 문자열뿐이라 플레이스홀더로 보인다 ` +
|
|
444
|
+
`(${placeholder.map((n) => `${n.language}="${n.text}"`).join(', ')}). ` +
|
|
445
|
+
`이대로 ${toTrack} 에 덮어쓰면 기존 노트가 사라진다 — releaseNotes 를 직접 넘기거나 copyReleaseNotes:false 를 고려할 것.`);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
408
448
|
// 3. target 트랙 현재 상태 조회 후 merge
|
|
409
449
|
// - 같은 versionCode가 이미 target에 있으면 그 항목을 새 release로 교체
|
|
410
450
|
// - status='completed'면 활성 release가 1개여야 하므로 [newRelease]로 통째 교체
|
|
@@ -442,7 +482,19 @@ export async function promoteRelease(auth, packageName, fromTrack, toTrack, vers
|
|
|
442
482
|
committed: true,
|
|
443
483
|
result: updated.data,
|
|
444
484
|
};
|
|
445
|
-
}, true);
|
|
485
|
+
}, true, (info) => { commitInfo = info; });
|
|
486
|
+
if ((out.releaseNotesLanguages ?? []).length === 0) {
|
|
487
|
+
warnings.push(`${toTrack} 에 릴리스 노트 없이 승격됐다 — 스토어의 "새로운 기능"이 비어 보인다.`);
|
|
488
|
+
}
|
|
489
|
+
return {
|
|
490
|
+
...out,
|
|
491
|
+
changesNotSentForReview: commitInfo.changesNotSentForReview,
|
|
492
|
+
// Play 가 자동 심사 전송을 거부한 경우, 사람이 콘솔에서 눌러야 실제 심사가 시작된다.
|
|
493
|
+
nextAction: commitInfo.changesNotSentForReview
|
|
494
|
+
? 'Play Console 에서 "변경사항 검토 후 게시"(심사를 위해 전송)를 눌러야 심사가 시작된다.'
|
|
495
|
+
: undefined,
|
|
496
|
+
warnings: warnings.length ? warnings : undefined,
|
|
497
|
+
};
|
|
446
498
|
}
|
|
447
499
|
export async function listReviews(auth, packageName) {
|
|
448
500
|
const res = await publisher().reviews.list({ auth, packageName });
|
|
@@ -647,10 +647,23 @@ export function registerAppstoreTools(server) {
|
|
|
647
647
|
if (sub.items.length === 0) {
|
|
648
648
|
lines.push(' items: (없음)');
|
|
649
649
|
}
|
|
650
|
+
// 항목 종류별 요약. 상품이 몇 개 들어갔는지가 첫 심사에서 가장 중요한 정보다.
|
|
651
|
+
const kinds = new Map();
|
|
652
|
+
for (const item of sub.items) {
|
|
653
|
+
const k = item.targetType ?? 'unknown';
|
|
654
|
+
kinds.set(k, (kinds.get(k) ?? 0) + 1);
|
|
655
|
+
}
|
|
656
|
+
lines.push(` 항목 ${sub.items.length}개` +
|
|
657
|
+
(kinds.size ? ` — ${[...kinds].map(([k, n]) => `${k} ${n}`).join(', ')}` : ''));
|
|
658
|
+
if (sub.items.length === 0) {
|
|
659
|
+
lines.push(' items: (없음)');
|
|
660
|
+
}
|
|
650
661
|
for (const item of sub.items) {
|
|
651
662
|
const target = item.versionString
|
|
652
663
|
? `${item.targetType} ${item.versionString} (${item.appVersionState ?? '?'})`
|
|
653
|
-
:
|
|
664
|
+
: item.label
|
|
665
|
+
? `${item.targetType} ${item.label}${item.targetState ? ` (${item.targetState})` : ''}`
|
|
666
|
+
: `${item.targetType ?? '?'} ${item.targetId ?? ''}`;
|
|
654
667
|
lines.push(` - item ${item.id}`);
|
|
655
668
|
lines.push(` state: ${item.state ?? '?'} → ${target}`);
|
|
656
669
|
}
|
|
@@ -781,8 +794,10 @@ export function registerAppstoreTools(server) {
|
|
|
781
794
|
});
|
|
782
795
|
server.tool('appstore_cancel_review', [
|
|
783
796
|
'App Store 심사 제출을 철회 — WAITING_FOR_REVIEW 상태에서만 가능.',
|
|
784
|
-
'
|
|
797
|
+
'PATCH attributes.canceled=true → state 가 CANCELING 으로 바뀌고 수십 초 뒤 COMPLETE(항목 REMOVED), 버전은 편집 가능 상태로 복귀해 메타데이터/빌드 수정 가능.',
|
|
798
|
+
'CANCELING 은 비동기라 호출 직후엔 아직 COMPLETE 가 아니다 — 이어서 작업하려면 상태를 폴링할 것.',
|
|
785
799
|
'⚠️ IN_REVIEW 이상이면 Apple API가 409로 거부함 — 이 경우 App Store Connect 웹에서 직접 처리하거나 심사 결과를 기다려야 함.',
|
|
800
|
+
'제출된 묶음에서 항목만 빼내는 것(removed)은 막히므로, 버전을 다른 묶음으로 옮기려면 이 도구로 묶음째 취소한다.',
|
|
786
801
|
'철회 후 수정 완료 시 appstore_submit_for_review로 재제출 가능.',
|
|
787
802
|
].join(' '), {
|
|
788
803
|
versionId: z.string().describe('App Store 버전 ID (appstore_list_versions 결과)'),
|
|
@@ -563,6 +563,8 @@ export function registerPlaystoreTools(server) {
|
|
|
563
563
|
'status="completed" + production 으로 전체 출시, status="inProgress" + userFraction 으로 단계 출시(예: 0.1 = 10%).',
|
|
564
564
|
'target에 같은 versionCode가 이미 있으면 해당 항목을 교체. status="completed"면 target 활성 release를 통째로 새 것으로 대체.',
|
|
565
565
|
'⚠️ status="completed"는 비가역에 가까움. playstore_check_submission_risks로 사전 점검 권장.',
|
|
566
|
+
'Play 가 자동 심사 전송을 거부하는 앱이면 changesNotSentForReview=true 로 자동 폴백해 커밋한다 — 이때 응답의 changesNotSentForReview=true 와 nextAction 을 사용자에게 전달할 것(콘솔에서 "심사를 위해 전송"을 눌러야 심사 시작).',
|
|
567
|
+
'source 트랙 노트가 버전 문자열뿐인 플레이스홀더면 warnings 로 알린다 — 그대로 승격하면 대상 트랙의 다국어 노트가 덮인다.',
|
|
566
568
|
].join(' '), {
|
|
567
569
|
packageName: z.string().describe('패키지명 (예: gg.pryzm.coffee)'),
|
|
568
570
|
fromTrack: z.enum(['production', 'beta', 'alpha', 'internal']).describe('출처 트랙 (예: internal)'),
|
package/package.json
CHANGED