@yoonion/mimi-seed-mcp 0.13.7 → 0.13.8

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.
@@ -166,6 +166,7 @@ export declare function submitVersionForReview(versionId: string): Promise<{
166
166
  versionId: string;
167
167
  reusedSubmission: boolean;
168
168
  itemAttached: boolean;
169
+ recoveredFromStaleSubmission: boolean;
169
170
  state: any;
170
171
  }>;
171
172
  export declare function addProductToReviewSubmission(args: {
@@ -599,38 +599,73 @@ export async function buildSubmitForReviewPreview(versionId) {
599
599
  whatsNewByLocale,
600
600
  };
601
601
  }
602
+ async function createReviewSubmission(appId, platform) {
603
+ const created = await apiPost('/reviewSubmissions', {
604
+ data: {
605
+ type: 'reviewSubmissions',
606
+ attributes: { platform },
607
+ relationships: {
608
+ app: { data: { type: 'apps', id: appId } },
609
+ },
610
+ },
611
+ });
612
+ const submissionId = created?.data?.id;
613
+ if (!submissionId) {
614
+ throw new Error(`reviewSubmission 생성 응답에 id가 없어: ${JSON.stringify(created)}`);
615
+ }
616
+ return submissionId;
617
+ }
618
+ function isItemAddRejected(error) {
619
+ const cause = error?.cause;
620
+ if (cause?.status !== 409)
621
+ return false;
622
+ return (cause.parsedErrors ?? []).some((e) => (e.code ?? '').startsWith('STATE_ERROR'));
623
+ }
602
624
  export async function submitVersionForReview(versionId) {
603
625
  const { appId, platform } = await getVersionAppAndPlatform(versionId);
604
- // 1. CREATED 상태의 reviewSubmission이 있으면 재사용, 없으면 새로 생성
626
+ // 1. 열린 reviewSubmission이 있으면 재사용, 없으면 새로 생성
605
627
  let submissionId = await findOpenReviewSubmission(appId, platform);
606
- const reusedSubmission = Boolean(submissionId);
628
+ let reusedSubmission = Boolean(submissionId);
629
+ // findOpenReviewSubmission 은 WAITING_FOR_REVIEW 도 잡아온다. 그 상태의 실제 진행도는
630
+ // API 의 state 필드보다 앞서 있을 수 있어(실측: 이미 심사 큐를 탄 옛 제출), 항목 추가
631
+ // 자체를 거부당하는 경우가 있다 — 아래 recoveredFromStaleSubmission 이 그 케이스다.
632
+ let recoveredFromStaleSubmission = false;
607
633
  if (!submissionId) {
608
- const created = await apiPost('/reviewSubmissions', {
609
- data: {
610
- type: 'reviewSubmissions',
611
- attributes: { platform },
612
- relationships: {
613
- app: { data: { type: 'apps', id: appId } },
614
- },
615
- },
616
- });
617
- submissionId = created?.data?.id;
618
- if (!submissionId) {
619
- throw new Error(`reviewSubmission 생성 응답에 id가 없어: ${JSON.stringify(created)}`);
620
- }
634
+ submissionId = await createReviewSubmission(appId, platform);
635
+ reusedSubmission = false;
621
636
  }
622
637
  // 2. 버전을 reviewSubmissionItems로 attach (이미 붙어있으면 skip)
623
- const alreadyAttached = reusedSubmission ? await isVersionAttached(submissionId, versionId) : false;
638
+ let alreadyAttached = reusedSubmission ? await isVersionAttached(submissionId, versionId) : false;
624
639
  if (!alreadyAttached) {
625
- await apiPost('/reviewSubmissionItems', {
626
- data: {
627
- type: 'reviewSubmissionItems',
628
- relationships: {
629
- reviewSubmission: { data: { type: 'reviewSubmissions', id: submissionId } },
630
- appStoreVersion: { data: { type: 'appStoreVersions', id: versionId } },
640
+ try {
641
+ await apiPost('/reviewSubmissionItems', {
642
+ data: {
643
+ type: 'reviewSubmissionItems',
644
+ relationships: {
645
+ reviewSubmission: { data: { type: 'reviewSubmissions', id: submissionId } },
646
+ appStoreVersion: { data: { type: 'appStoreVersions', id: versionId } },
647
+ },
631
648
  },
632
- },
633
- });
649
+ });
650
+ }
651
+ catch (error) {
652
+ if (!reusedSubmission || !isItemAddRejected(error))
653
+ throw error;
654
+ // 재사용하려던 묶음이 실제로는 잠겨 있었다 — 새 묶음을 만들어 한 번만 재시도한다.
655
+ submissionId = await createReviewSubmission(appId, platform);
656
+ reusedSubmission = false;
657
+ recoveredFromStaleSubmission = true;
658
+ alreadyAttached = false;
659
+ await apiPost('/reviewSubmissionItems', {
660
+ data: {
661
+ type: 'reviewSubmissionItems',
662
+ relationships: {
663
+ reviewSubmission: { data: { type: 'reviewSubmissions', id: submissionId } },
664
+ appStoreVersion: { data: { type: 'appStoreVersions', id: versionId } },
665
+ },
666
+ },
667
+ });
668
+ }
634
669
  }
635
670
  // 3. PATCH submitted=true → state: CREATED → WAITING_FOR_REVIEW
636
671
  const submitted = await apiPatch(`/reviewSubmissions/${submissionId}`, {
@@ -647,6 +682,7 @@ export async function submitVersionForReview(versionId) {
647
682
  versionId,
648
683
  reusedSubmission,
649
684
  itemAttached: !alreadyAttached,
685
+ recoveredFromStaleSubmission,
650
686
  state: submitted?.data?.attributes?.state ?? 'WAITING_FOR_REVIEW',
651
687
  };
652
688
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yoonion/mimi-seed-mcp",
3
- "version": "0.13.7",
3
+ "version": "0.13.8",
4
4
  "description": "Mimi Seed MCP server — Firebase + AdMob + Google Play + App Store management for Claude Code / Codex / Cursor / any MCP client.",
5
5
  "type": "module",
6
6
  "bin": {