mimi-seed 0.19.11 → 0.19.12

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.
@@ -1,25 +1,53 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- catalog,
4
- isLangUnset,
5
- resolveLang,
6
- t,
7
- writeSettings
8
- } from "./chunk-TQHLWSFR.js";
9
2
 
10
3
  // src/setup.ts
11
4
  import kleur2 from "kleur";
12
5
  import * as readline2 from "readline";
13
- import os5 from "os";
6
+ import os6 from "os";
14
7
 
15
8
  // src/credentials.ts
16
- import fs2 from "fs";
17
- import os from "os";
18
- import path2 from "path";
9
+ import fs3 from "fs";
10
+ import os2 from "os";
11
+ import path3 from "path";
19
12
 
20
- // src/project-manifest.ts
13
+ // src/settings.ts
21
14
  import fs from "fs";
15
+ import os from "os";
22
16
  import path from "path";
17
+ var DEFAULT_LANG = "ko";
18
+ function settingsPath(home) {
19
+ return path.join(home, ".mimi-seed", "settings.json");
20
+ }
21
+ function readSettings(home = os.homedir()) {
22
+ try {
23
+ return JSON.parse(fs.readFileSync(settingsPath(home), "utf-8"));
24
+ } catch {
25
+ return {};
26
+ }
27
+ }
28
+ function writeSettings(next, home = os.homedir()) {
29
+ const dir = path.join(home, ".mimi-seed");
30
+ fs.mkdirSync(dir, { recursive: true, mode: 448 });
31
+ const merged = { ...readSettings(home), ...next };
32
+ fs.writeFileSync(settingsPath(home), JSON.stringify(merged, null, 2));
33
+ }
34
+ function isLangUnset(home = os.homedir()) {
35
+ return !process.env.MIMI_SEED_LANG && !readSettings(home).lang;
36
+ }
37
+ function isLang(v) {
38
+ return v === "ko" || v === "en";
39
+ }
40
+ function resolveLang(home = os.homedir()) {
41
+ const env = process.env.MIMI_SEED_LANG?.toLowerCase();
42
+ if (isLang(env)) return env;
43
+ const saved = readSettings(home).lang;
44
+ if (isLang(saved)) return saved;
45
+ return DEFAULT_LANG;
46
+ }
47
+
48
+ // src/project-manifest.ts
49
+ import fs2 from "fs";
50
+ import path2 from "path";
23
51
  var MANIFEST_FILENAME = ".mimi-seed.json";
24
52
  function isValidSocialProfileId(value) {
25
53
  return /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(value);
@@ -40,18 +68,18 @@ function manifestSocialProfile(m, platform) {
40
68
  return value;
41
69
  }
42
70
  function findProjectManifest(startDir = process.cwd(), maxDepth = 8) {
43
- let dir = path.resolve(startDir);
71
+ let dir = path2.resolve(startDir);
44
72
  for (let i = 0; i <= maxDepth; i++) {
45
- const candidate = path.join(dir, MANIFEST_FILENAME);
46
- if (fs.existsSync(candidate)) {
73
+ const candidate = path2.join(dir, MANIFEST_FILENAME);
74
+ if (fs2.existsSync(candidate)) {
47
75
  try {
48
- const obj = JSON.parse(fs.readFileSync(candidate, "utf-8"));
76
+ const obj = JSON.parse(fs2.readFileSync(candidate, "utf-8"));
49
77
  if (obj && typeof obj === "object") return { manifest: obj, filePath: candidate };
50
78
  } catch {
51
79
  }
52
80
  return null;
53
81
  }
54
- const parent = path.dirname(dir);
82
+ const parent = path2.dirname(dir);
55
83
  if (parent === dir) break;
56
84
  dir = parent;
57
85
  }
@@ -73,21 +101,21 @@ function credObtain(spec, lang = resolveLang()) {
73
101
  return spec.obtain[lang];
74
102
  }
75
103
  function credDir(home) {
76
- return path2.join(home, ".mimi-seed");
104
+ return path3.join(home, ".mimi-seed");
77
105
  }
78
106
  function hasFile(home, name) {
79
- return fs2.existsSync(path2.join(credDir(home), name));
107
+ return fs3.existsSync(path3.join(credDir(home), name));
80
108
  }
81
109
  function anyFileStarting(home, prefix) {
82
110
  try {
83
- return fs2.readdirSync(credDir(home)).some((f) => f.startsWith(prefix));
111
+ return fs3.readdirSync(credDir(home)).some((f) => f.startsWith(prefix));
84
112
  } catch {
85
113
  return false;
86
114
  }
87
115
  }
88
116
  function readJson(home, name) {
89
117
  try {
90
- return JSON.parse(fs2.readFileSync(path2.join(credDir(home), name), "utf-8"));
118
+ return JSON.parse(fs3.readFileSync(path3.join(credDir(home), name), "utf-8"));
91
119
  } catch {
92
120
  return null;
93
121
  }
@@ -129,7 +157,7 @@ function detectProjectSocialToken(home, startDir, platform) {
129
157
  if (!profile) return detectSocialToken(home, `${platform}.json`, idKey, tokenKey);
130
158
  const detected = detectSocialToken(
131
159
  home,
132
- path2.join("social-profiles", `${profile}.json`),
160
+ path3.join("social-profiles", `${profile}.json`),
133
161
  idKey,
134
162
  tokenKey,
135
163
  platform
@@ -142,7 +170,7 @@ function detectProjectSocialToken(home, startDir, platform) {
142
170
  function hasPlaySa(home) {
143
171
  if (hasFile(home, "play-service-account.json")) return true;
144
172
  try {
145
- return fs2.readdirSync(path2.join(credDir(home), "play-service-accounts")).some((f) => f.endsWith(".json"));
173
+ return fs3.readdirSync(path3.join(credDir(home), "play-service-accounts")).some((f) => f.endsWith(".json"));
146
174
  } catch {
147
175
  return false;
148
176
  }
@@ -601,7 +629,7 @@ var CREDENTIALS = [
601
629
  function tryCredById(id) {
602
630
  return CREDENTIALS.find((c) => c.id === id);
603
631
  }
604
- function detectAll(home = os.homedir(), startDir = process.cwd()) {
632
+ function detectAll(home = os2.homedir(), startDir = process.cwd()) {
605
633
  return new Map(CREDENTIALS.map((c) => [c.id, c.detect(home, startDir)]));
606
634
  }
607
635
  function isSatisfied(spec, detected) {
@@ -639,17 +667,234 @@ function planSetup(detected, opts = {}) {
639
667
  // src/mcp-bin.ts
640
668
  import { spawn, spawnSync } from "child_process";
641
669
  import { existsSync, readFileSync } from "fs";
642
- import path3 from "path";
670
+ import path4 from "path";
671
+
672
+ // src/i18n.ts
673
+ function catalog(ko2, en2) {
674
+ return () => resolveLang() === "en" ? en2 : ko2;
675
+ }
676
+ var ko = {
677
+ common: {
678
+ yes: "y",
679
+ optional: "\uC120\uD0DD",
680
+ required: "\uD544\uC218",
681
+ skip: "\uAC74\uB108\uB700",
682
+ cancelled: "\uCDE8\uC18C\uB428",
683
+ unknownCommand: (cmd) => `\uC54C \uC218 \uC5C6\uB294 \uBA85\uB839: ${cmd}`,
684
+ error: (msg) => `\uC624\uB958: ${msg}`,
685
+ checkWith: "\uC810\uAC80: mimi-seed doctor"
686
+ },
687
+ lang: {
688
+ ask: " \uC5B8\uC5B4\uB97C \uC120\uD0DD\uD574\uC918 [1] \uD55C\uAD6D\uC5B4 [2] English (\uC5D4\uD130=\uD55C\uAD6D\uC5B4): ",
689
+ saved: (l) => ` \u2705 \uC5B8\uC5B4: ${l === "ko" ? "\uD55C\uAD6D\uC5B4" : "English"} (\uB098\uC911\uC5D0 \uBC14\uAFB8\uAE30: mimi-seed lang en)`,
690
+ usage: `${"mimi-seed lang"} \u2014 CLI \uCD9C\uB825 \uC5B8\uC5B4
691
+
692
+ mimi-seed lang \uD604\uC7AC \uC5B8\uC5B4 \uD45C\uC2DC
693
+ mimi-seed lang ko \uD55C\uAD6D\uC5B4
694
+ mimi-seed lang en English
695
+
696
+ \uD658\uACBD\uBCC0\uC218 MIMI_SEED_LANG \uAC00 \uC788\uC73C\uBA74 \uADF8\uAC8C \uC6B0\uC120\uD569\uB2C8\uB2E4.`,
697
+ current: (l) => `\uD604\uC7AC \uC5B8\uC5B4: ${l === "ko" ? "\uD55C\uAD6D\uC5B4 (ko)" : "English (en)"}`,
698
+ invalid: (v) => `\uC54C \uC218 \uC5C6\uB294 \uC5B8\uC5B4: ${v} (ko \uB610\uB294 en)`
699
+ },
700
+ setup: {
701
+ title: "mimi-seed setup",
702
+ platformsDetected: (p) => ` \uAC10\uC9C0\uB41C \uD50C\uB7AB\uD3FC: ${p}`,
703
+ statusTitle: "\uC5F0\uACB0 \uC0C1\uD0DC",
704
+ statusDir: "(~/.mimi-seed)",
705
+ groupCore: "\uD575\uC2EC",
706
+ groupCi: "\uBE4C\uB4DC / CI",
707
+ groupMarketing: "\uB9C8\uCF00\uD305 \xB7 AI",
708
+ fallbackWorking: "\uD3F4\uBC31\uC73C\uB85C \uB3D9\uC791 \uC911",
709
+ missingRequired: " \uD544\uC218 \uD56D\uBAA9 \uB204\uB77D:",
710
+ cannotInteract: " \u2717 \uC774 \uC790\uACA9\uC99D\uBA85\uC740 \uB300\uD654\uD615 \uC785\uB825\uC774 \uD544\uC694\uD574\uC11C \uC5EC\uAE30\uC11C\uB294 \uC124\uC815\uD560 \uC218 \uC5C6\uC5B4:",
711
+ cannotInteractHint: " \uD130\uBBF8\uB110\uC5D0\uC11C \uC2E4\uD589\uD574\uC918 (Git Bash \uB4F1 TTY \uBBF8\uAC10\uC9C0 \uD658\uACBD\uC774\uBA74 --interactive).",
712
+ runInTerminal: " \uB300\uD654\uD615\uC73C\uB85C \uC5F0\uACB0\uD558\uB824\uBA74 \uD130\uBBF8\uB110\uC5D0\uC11C: mimi-seed setup (Git Bash/mintty \uB294 TTY \uBBF8\uAC10\uC9C0 \u2014 --interactive \uB97C \uBD99\uC774\uAC70\uB098 PowerShell \uC5D0\uC11C)",
713
+ onlyAlreadyDone: " \u2705 \uC694\uCCAD\uD55C \uD56D\uBAA9\uC740 \uC774\uBBF8 \uC5F0\uACB0\uB3FC \uC788\uC5B4.",
714
+ onlyReconnectHint: " \uB2E4\uC2DC \uC124\uC815\uD558\uB824\uBA74: mimi-seed setup --reconnect <id>",
715
+ tokenExpired: "\uD1A0\uD070 \uB9CC\uB8CC \u2014 \uB2E4\uC2DC \uC5F0\uACB0 \uD544\uC694",
716
+ tokenExpiring: (days) => `\uD1A0\uD070 ${days}\uC77C \uD6C4 \uB9CC\uB8CC \u2014 \uC9C0\uAE08 \uAC31\uC2E0 \uAD8C\uC7A5`,
717
+ allDone: " \u2705 \uC5F0\uACB0\uD560 \uAC8C \uB354 \uC5C6\uC5B4. \uB2E4 \uB410\uB2E4.",
718
+ planCount: (n) => ` ${n}\uAC1C \uD56D\uBAA9\uC744 \uC21C\uC11C\uB300\uB85C \uBB3C\uC5B4\uBCFC\uAC8C. \uC5B8\uC81C\uB4E0 s=\uAC74\uB108\uB6F0\uAE30, q=\uC885\uB8CC.`,
719
+ prompt: " [c] \uC5F0\uACB0 [s] \uAC74\uB108\uB6F0\uAE30 [?] \uC774\uAC74 \uC5B4\uB5BB\uAC8C \uAD6C\uD558\uB098\uC694 [q] \uC885\uB8CC : ",
720
+ promptInvalid: " c / s / ? / q \uC911\uC5D0\uC11C \uACE8\uB77C\uC918.",
721
+ quit: " \uC911\uB2E8\uD588\uC5B4. \uC774\uC5B4\uC11C \uD558\uB824\uBA74 \uB2E4\uC2DC: mimi-seed setup",
722
+ skipped: (fix) => ` \uAC74\uB108\uB700. \uB098\uC911\uC5D0: ${fix}`,
723
+ obtainTitle: (label) => ` ${label} \u2014 \uBBF8\uB9AC \uC900\uBE44\uD560 \uAC83`,
724
+ obtainMore: (anchor) => ` \uC790\uC138\uD788: docs/credentials.md#${anchor}`,
725
+ neededFor: (platform) => `(${platform} \uBC30\uD3EC\uC5D0 \uD544\uC694)`,
726
+ binFailed: (label, code, fix) => ` \u26A0 ${label} \uC124\uC815\uC774 \uC644\uB8CC\uB418\uC9C0 \uC54A\uC558\uC5B4 (exit ${code}). \uB098\uC911\uC5D0 \uB2E4\uC2DC: ${fix}`,
727
+ verifying: " \u{1F50E} \uD1A0\uD070 \uAC80\uC99D \uC911...",
728
+ verifyFailed: (reason) => ` \u274C \uD1A0\uD070 \uAC80\uC99D \uC2E4\uD328: ${reason}`,
729
+ notSaved: (fix) => ` \uC800\uC7A5\uD558\uC9C0 \uC54A\uC558\uC5B4. \uB2E4\uC2DC: ${fix}`,
730
+ ciSaved: (label, who) => ` \u2705 ${label} \uC5F0\uACB0\uB428${who} \u2192 ~/.mimi-seed/ci.json`,
731
+ runSeparately: (cmd) => ` \uC774\uAC74 \uBCC4\uB3C4 \uBA85\uB839\uC73C\uB85C \uC2E4\uD589\uD574\uC918: ${cmd}`,
732
+ envVar: " \uD658\uACBD\uBCC0\uC218\uB85C \uC124\uC815\uD558\uB294 \uD56D\uBAA9\uC774\uC57C:",
733
+ pressEnter: " (\uC5D4\uD130\uB97C \uB204\uB974\uBA74 \uACC4\uC18D) ",
734
+ stillMissing: " \uC544\uC9C1 \uD544\uC218 \uD56D\uBAA9\uC774 \uB0A8\uC544 \uC788\uC5B4:",
735
+ requiredDone: " \u2705 \uD544\uC218 \uC5F0\uACB0 \uC644\uB8CC.",
736
+ nextSteps: " \uC810\uAC80: mimi-seed doctor \xB7 \uBC30\uD3EC: mimi-seed deploy",
737
+ tryPrompt: ' \uCCAB \uD504\uB86C\uD504\uD2B8 \u2014 Claude Code / Codex \uC0C8 \uC138\uC158\uC5D0\uC11C: "\uB0B4 \uC571 \uCD9C\uC2DC \uC900\uBE44\uB410\uC5B4?"'
738
+ },
739
+ doctor: {
740
+ title: "mimi-seed doctor",
741
+ secAuth: "\uC778\uC99D",
742
+ secCreds: "\uB85C\uCEEC \uC790\uACA9\uC99D\uBA85 (~/.mimi-seed)",
743
+ secEnv: "\uB85C\uCEEC \uD658\uACBD",
744
+ secApps: "\uC571 \uAC10\uC9C0",
745
+ noToken: "Mimi Seed \uD1A0\uD070 \uC5C6\uC74C",
746
+ noTokenFix: "`mimi-seed init` \uC2E4\uD589 \uD544\uC694",
747
+ tokenSaved: "\uD1A0\uD070 \uC800\uC7A5\uB428",
748
+ endpoint: "\uC5D4\uB4DC\uD3EC\uC778\uD2B8",
749
+ ciMode: "CI \uBAA8\uB4DC",
750
+ ciModeDetail: "MIMI_SEED_TOKEN \uD658\uACBD\uBCC0\uC218 \uC0AC\uC6A9 \uC911",
751
+ tokenInvalid: "\uD1A0\uD070 \uAC80\uC99D \uC2E4\uD328",
752
+ serverOk: "Mimi Seed \uC11C\uBC84 \uC5F0\uACB0\uB428",
753
+ appCount: (n) => `\uC571 ${n}\uAC1C`,
754
+ unknownService: (id) => `${id} (\uC54C \uC218 \uC5C6\uB294 \uC11C\uBE44\uC2A4)`,
755
+ credentialMismatch: (field, expected, actual) => `${field} \uBD88\uC77C\uCE58 \u2014 \uC694\uAD6C\uAC12 ${expected}, \uD604\uC7AC\uAC12 ${actual ?? "\uC5C6\uC74C"}`,
756
+ credsHint: " \uC804\uBD80 \uC5F0\uACB0\uD558\uAE30: mimi-seed setup \xB7 OAuth \uC2E0\uC120\uB3C4: mimi-seed auth status\n",
757
+ nodeTooOld: (v) => `${v} \u2014 v20 \uC774\uC0C1 \uD544\uC694 (.nvmrc \uCC38\uACE0)`,
758
+ gitRepo: "Git \uC800\uC7A5\uC18C",
759
+ gitTag: (t2) => `\uCD5C\uC2E0 \uD0DC\uADF8: ${t2}`,
760
+ gitCommits: (n) => `\uCEE4\uBC0B ${n}\uAC1C`,
761
+ noGit: "Git \uC800\uC7A5\uC18C \uC5C6\uC74C",
762
+ noGitDetail: "mimi-seed notes \uC0AC\uC6A9 \uBD88\uAC00",
763
+ noApp: "\uC571 \uAC10\uC9C0 \uC5C6\uC74C",
764
+ noAppDetail: "Expo / Gradle / Xcode / Unity \uC571 \uC124\uC815 \uC5C6\uC74C",
765
+ unnamed: "(\uC774\uB984 \uBBF8\uC0C1)",
766
+ requirements: (proj) => `${proj} \uC694\uAD6C\uC0AC\uD56D (.mimi-seed.json)`,
767
+ thisProject: "\uC774 \uD504\uB85C\uC81D\uD2B8"
768
+ },
769
+ auth: {
770
+ title: "mimi-seed auth \u2014 \uB85C\uCEEC \uC790\uACA9\uC99D\uBA85 \uC778\uC99D/\uAD00\uB9AC",
771
+ statusTitle: "\uB85C\uCEEC \uC790\uACA9\uC99D\uBA85 \uC0C1\uD0DC",
772
+ connectAll: "\n \uD55C \uBC88\uC5D0 \uC5F0\uACB0: mimi-seed setup",
773
+ unknownSub: (sub) => `\uC54C \uC218 \uC5C6\uB294 auth \uC11C\uBE0C\uBA85\uB839: ${sub}`,
774
+ npxFailed: (cmd, msg) => `
775
+ \u274C ${cmd} \uC2E4\uD589 \uC2E4\uD328: ${msg}
776
+ `
777
+ }
778
+ };
779
+ var en = {
780
+ common: {
781
+ yes: "y",
782
+ optional: "optional",
783
+ required: "required",
784
+ skip: "skipped",
785
+ cancelled: "Cancelled",
786
+ unknownCommand: (cmd) => `Unknown command: ${cmd}`,
787
+ error: (msg) => `Error: ${msg}`,
788
+ checkWith: "Check with: mimi-seed doctor"
789
+ },
790
+ lang: {
791
+ ask: " Choose a language [1] \uD55C\uAD6D\uC5B4 [2] English (Enter = \uD55C\uAD6D\uC5B4): ",
792
+ saved: (l) => ` \u2705 Language: ${l === "ko" ? "\uD55C\uAD6D\uC5B4" : "English"} (change later: mimi-seed lang ko)`,
793
+ usage: `mimi-seed lang \u2014 CLI output language
794
+
795
+ mimi-seed lang show current language
796
+ mimi-seed lang ko \uD55C\uAD6D\uC5B4
797
+ mimi-seed lang en English
798
+
799
+ MIMI_SEED_LANG takes precedence when set.`,
800
+ current: (l) => `Current language: ${l === "ko" ? "\uD55C\uAD6D\uC5B4 (ko)" : "English (en)"}`,
801
+ invalid: (v) => `Unknown language: ${v} (use ko or en)`
802
+ },
803
+ setup: {
804
+ title: "mimi-seed setup",
805
+ platformsDetected: (p) => ` Detected platforms: ${p}`,
806
+ statusTitle: "Connection status",
807
+ statusDir: "(~/.mimi-seed)",
808
+ groupCore: "Core",
809
+ groupCi: "Build / CI",
810
+ groupMarketing: "Marketing \xB7 AI",
811
+ fallbackWorking: "working via fallback",
812
+ missingRequired: " Missing required:",
813
+ cannotInteract: " \u2717 These need interactive input and cannot be set up here:",
814
+ cannotInteractHint: " Run it in a terminal (add --interactive if your shell hides the TTY, e.g. Git Bash).",
815
+ runInTerminal: " To connect interactively, run: mimi-seed setup (Git Bash/mintty hide the TTY \u2014 add --interactive, or use PowerShell)",
816
+ onlyAlreadyDone: " \u2705 What you asked for is already connected.",
817
+ onlyReconnectHint: " To redo it: mimi-seed setup --reconnect <id>",
818
+ tokenExpired: "token expired \u2014 reconnect required",
819
+ tokenExpiring: (days) => `token expires in ${days} day(s) \u2014 refresh now`,
820
+ allDone: " \u2705 Nothing left to connect. You're set.",
821
+ planCount: (n) => ` I'll walk you through ${n} item(s). s = skip, q = quit, anytime.`,
822
+ prompt: " [c] connect [s] skip [?] how do I get this [q] quit : ",
823
+ promptInvalid: " Please choose c / s / ? / q.",
824
+ quit: " Stopped. To pick up where you left off: mimi-seed setup",
825
+ skipped: (fix) => ` Skipped. Later: ${fix}`,
826
+ obtainTitle: (label) => ` ${label} \u2014 what to get first`,
827
+ obtainMore: (anchor) => ` Details: docs/credentials.md#${anchor}`,
828
+ neededFor: (platform) => `(needed to ship to ${platform})`,
829
+ binFailed: (label, code, fix) => ` \u26A0 ${label} was not completed (exit ${code}). Try again later: ${fix}`,
830
+ verifying: " \u{1F50E} Verifying token...",
831
+ verifyFailed: (reason) => ` \u274C Token verification failed: ${reason}`,
832
+ notSaved: (fix) => ` Nothing was saved. Retry: ${fix}`,
833
+ ciSaved: (label, who) => ` \u2705 ${label} connected${who} \u2192 ~/.mimi-seed/ci.json`,
834
+ runSeparately: (cmd) => ` Run this one separately: ${cmd}`,
835
+ envVar: " This one is set through an environment variable:",
836
+ pressEnter: " (press Enter to continue) ",
837
+ stillMissing: " Still missing, and required:",
838
+ requiredDone: " \u2705 All required credentials connected.",
839
+ nextSteps: " Check: mimi-seed doctor \xB7 Ship: mimi-seed deploy",
840
+ tryPrompt: ' First prompt \u2014 in a new Claude Code / Codex session: "Is my app ready to ship?"'
841
+ },
842
+ doctor: {
843
+ title: "mimi-seed doctor",
844
+ secAuth: "Account",
845
+ secCreds: "Local credentials (~/.mimi-seed)",
846
+ secEnv: "Environment",
847
+ secApps: "App detection",
848
+ noToken: "No Mimi Seed token",
849
+ noTokenFix: "run `mimi-seed init`",
850
+ tokenSaved: "Token stored",
851
+ endpoint: "Endpoint",
852
+ ciMode: "CI mode",
853
+ ciModeDetail: "using MIMI_SEED_TOKEN",
854
+ tokenInvalid: "Token rejected",
855
+ serverOk: "Connected to Mimi Seed",
856
+ appCount: (n) => `${n} app(s)`,
857
+ unknownService: (id) => `${id} (unknown service)`,
858
+ credentialMismatch: (field, expected, actual) => `${field} mismatch \u2014 expected ${expected}, current ${actual ?? "missing"}`,
859
+ credsHint: " Connect everything: mimi-seed setup \xB7 OAuth freshness: mimi-seed auth status\n",
860
+ nodeTooOld: (v) => `${v} \u2014 v20+ required (see .nvmrc)`,
861
+ gitRepo: "Git repository",
862
+ gitTag: (t2) => `latest tag: ${t2}`,
863
+ gitCommits: (n) => `${n} commit(s)`,
864
+ noGit: "Not a git repository",
865
+ noGitDetail: "mimi-seed notes is unavailable",
866
+ noApp: "No app detected",
867
+ noAppDetail: "no Expo / Gradle / Xcode / Unity app configuration",
868
+ unnamed: "(unnamed)",
869
+ requirements: (proj) => `${proj} requirements (.mimi-seed.json)`,
870
+ thisProject: "This project"
871
+ },
872
+ auth: {
873
+ title: "mimi-seed auth \u2014 local credential setup",
874
+ statusTitle: "Local credential status",
875
+ connectAll: "\n Connect everything at once: mimi-seed setup",
876
+ unknownSub: (sub) => `Unknown auth subcommand: ${sub}`,
877
+ npxFailed: (cmd, msg) => `
878
+ \u274C Failed to run ${cmd}: ${msg}
879
+ `
880
+ }
881
+ };
882
+ var CATALOGS = { ko, en };
883
+ function t() {
884
+ return CATALOGS[resolveLang()];
885
+ }
886
+
887
+ // src/mcp-bin.ts
643
888
  var MCP_PKG = "@yoonion/mimi-seed-mcp";
644
889
  function resolveOnPath(bin, honorForceNpx = true) {
645
890
  if (honorForceNpx && process.env.MIMI_SEED_FORCE_NPX) return null;
646
891
  if (process.platform === "win32") {
647
892
  const pathKey = Object.keys(process.env).find((key) => key.toLowerCase() === "path");
648
- const directories = (pathKey ? process.env[pathKey] ?? "" : "").split(path3.delimiter).filter(Boolean);
893
+ const directories = (pathKey ? process.env[pathKey] ?? "" : "").split(path4.delimiter).filter(Boolean);
649
894
  const names = bin.toLowerCase().endsWith(".cmd") ? [bin] : [`${bin}.cmd`, bin];
650
895
  for (const directory of directories) {
651
896
  for (const name of names) {
652
- const candidate = path3.join(directory.replace(/^"|"$/g, ""), name);
897
+ const candidate = path4.join(directory.replace(/^"|"$/g, ""), name);
653
898
  if (existsSync(candidate)) return candidate;
654
899
  }
655
900
  }
@@ -665,13 +910,13 @@ function resolveWindowsShimTarget(shimPath, source) {
665
910
  const matches = [...source.matchAll(/["']([^"']+\.js)["']\s+%\*/gi)];
666
911
  const raw = matches.at(-1)?.[1];
667
912
  if (!raw) return null;
668
- return path3.win32.normalize(raw.replace(/%~?dp0%?/gi, `${path3.win32.dirname(shimPath)}${path3.win32.sep}`));
913
+ return path4.win32.normalize(raw.replace(/%~?dp0%?/gi, `${path4.win32.dirname(shimPath)}${path4.win32.sep}`));
669
914
  }
670
915
  function npxCliPath(shimPath) {
671
916
  const candidates = [
672
- shimPath ? path3.join(path3.dirname(shimPath), "node_modules", "npm", "bin", "npx-cli.js") : "",
673
- path3.join(path3.dirname(process.execPath), "node_modules", "npm", "bin", "npx-cli.js"),
674
- process.env.npm_execpath ? path3.join(path3.dirname(process.env.npm_execpath), "npx-cli.js") : ""
917
+ shimPath ? path4.join(path4.dirname(shimPath), "node_modules", "npm", "bin", "npx-cli.js") : "",
918
+ path4.join(path4.dirname(process.execPath), "node_modules", "npm", "bin", "npx-cli.js"),
919
+ process.env.npm_execpath ? path4.join(path4.dirname(process.env.npm_execpath), "npx-cli.js") : ""
675
920
  ];
676
921
  return candidates.find((candidate) => candidate && existsSync(candidate)) ?? null;
677
922
  }
@@ -714,28 +959,28 @@ async function runMcpBin(bin, extraArgs = []) {
714
959
  }
715
960
 
716
961
  // src/jenkins-config.ts
717
- import fs3 from "fs";
718
- import os2 from "os";
719
- import path4 from "path";
720
- var CONFIG_DIR = path4.join(os2.homedir(), ".mimi-seed");
721
- var JENKINS_PATH = path4.join(CONFIG_DIR, "jenkins.json");
722
- var LEGACY_PATH = path4.join(CONFIG_DIR, "config.json");
723
- function loadJenkinsConfig(home = os2.homedir()) {
962
+ import fs4 from "fs";
963
+ import os3 from "os";
964
+ import path5 from "path";
965
+ var CONFIG_DIR = path5.join(os3.homedir(), ".mimi-seed");
966
+ var JENKINS_PATH = path5.join(CONFIG_DIR, "jenkins.json");
967
+ var LEGACY_PATH = path5.join(CONFIG_DIR, "config.json");
968
+ function loadJenkinsConfig(home = os3.homedir()) {
724
969
  try {
725
- const p = path4.join(home, ".mimi-seed", "jenkins.json");
726
- return JSON.parse(fs3.readFileSync(p, "utf-8"));
970
+ const p = path5.join(home, ".mimi-seed", "jenkins.json");
971
+ return JSON.parse(fs4.readFileSync(p, "utf-8"));
727
972
  } catch {
728
973
  return null;
729
974
  }
730
975
  }
731
- function migrateLegacyJenkins(home = os2.homedir()) {
732
- const dir = path4.join(home, ".mimi-seed");
733
- const jenkinsPath = path4.join(dir, "jenkins.json");
734
- const legacyPath = path4.join(dir, "config.json");
735
- if (fs3.existsSync(jenkinsPath)) return false;
976
+ function migrateLegacyJenkins(home = os3.homedir()) {
977
+ const dir = path5.join(home, ".mimi-seed");
978
+ const jenkinsPath = path5.join(dir, "jenkins.json");
979
+ const legacyPath = path5.join(dir, "config.json");
980
+ if (fs4.existsSync(jenkinsPath)) return false;
736
981
  let legacy;
737
982
  try {
738
- legacy = JSON.parse(fs3.readFileSync(legacyPath, "utf-8"));
983
+ legacy = JSON.parse(fs4.readFileSync(legacyPath, "utf-8"));
739
984
  } catch {
740
985
  return false;
741
986
  }
@@ -749,28 +994,28 @@ function migrateLegacyJenkins(home = os2.homedir()) {
749
994
  ...j.jobAndroid ? { jobAndroid: j.jobAndroid } : {},
750
995
  ...j.jobIos ? { jobIos: j.jobIos } : {}
751
996
  };
752
- fs3.mkdirSync(dir, { recursive: true, mode: 448 });
753
- fs3.writeFileSync(jenkinsPath, JSON.stringify(migrated, null, 2), { mode: 384 });
997
+ fs4.mkdirSync(dir, { recursive: true, mode: 448 });
998
+ fs4.writeFileSync(jenkinsPath, JSON.stringify(migrated, null, 2), { mode: 384 });
754
999
  delete legacy.jenkins;
755
1000
  const tmp = `${legacyPath}.tmp`;
756
- fs3.writeFileSync(tmp, JSON.stringify(legacy, null, 2), { mode: 384 });
757
- fs3.renameSync(tmp, legacyPath);
1001
+ fs4.writeFileSync(tmp, JSON.stringify(legacy, null, 2), { mode: 384 });
1002
+ fs4.renameSync(tmp, legacyPath);
758
1003
  return true;
759
1004
  }
760
1005
 
761
1006
  // src/detect.ts
762
- import fs4 from "fs/promises";
763
- import path5 from "path";
1007
+ import fs5 from "fs/promises";
1008
+ import path6 from "path";
764
1009
  async function readIfExists(p) {
765
1010
  try {
766
- return await fs4.readFile(p, "utf8");
1011
+ return await fs5.readFile(p, "utf8");
767
1012
  } catch {
768
1013
  return null;
769
1014
  }
770
1015
  }
771
1016
  async function pathExists(p) {
772
1017
  try {
773
- await fs4.access(p);
1018
+ await fs5.access(p);
774
1019
  return true;
775
1020
  } catch {
776
1021
  return false;
@@ -784,9 +1029,9 @@ async function importedJsonObjects(configPath, text, root) {
784
1029
  ];
785
1030
  for (const match of imports) {
786
1031
  if (!match[2].startsWith(".")) continue;
787
- const candidate = path5.resolve(path5.dirname(configPath), match[2]);
788
- const relative = path5.relative(root, candidate);
789
- if (relative.startsWith(`..${path5.sep}`) || relative === ".." || path5.isAbsolute(relative)) continue;
1032
+ const candidate = path6.resolve(path6.dirname(configPath), match[2]);
1033
+ const relative = path6.relative(root, candidate);
1034
+ if (relative.startsWith(`..${path6.sep}`) || relative === ".." || path6.isAbsolute(relative)) continue;
790
1035
  const jsonText = await readIfExists(candidate);
791
1036
  if (!jsonText) continue;
792
1037
  try {
@@ -823,16 +1068,16 @@ async function walk(root, match, maxDepth = 5) {
823
1068
  if (depth > maxDepth) return;
824
1069
  let entries;
825
1070
  try {
826
- entries = await fs4.readdir(dir, { withFileTypes: true });
1071
+ entries = await fs5.readdir(dir, { withFileTypes: true });
827
1072
  } catch {
828
1073
  return;
829
1074
  }
830
1075
  for (const e of entries) {
831
1076
  if (e.isDirectory()) {
832
1077
  if (skipDirs.has(e.name)) continue;
833
- await visit(path5.join(dir, e.name), depth + 1);
1078
+ await visit(path6.join(dir, e.name), depth + 1);
834
1079
  } else if (e.isFile() && match(e.name)) {
835
- found.push(path5.join(dir, e.name));
1080
+ found.push(path6.join(dir, e.name));
836
1081
  }
837
1082
  }
838
1083
  }
@@ -842,7 +1087,7 @@ async function walk(root, match, maxDepth = 5) {
842
1087
  async function detectHints(cwd) {
843
1088
  const hints = [];
844
1089
  for (const fname of ["app.json", "app.config.json"]) {
845
- const txt = await readIfExists(path5.join(cwd, fname));
1090
+ const txt = await readIfExists(path6.join(cwd, fname));
846
1091
  if (!txt) continue;
847
1092
  try {
848
1093
  const json = JSON.parse(txt);
@@ -862,7 +1107,7 @@ async function detectHints(cwd) {
862
1107
  }
863
1108
  }
864
1109
  for (const fname of ["app.config.js", "app.config.cjs", "app.config.mjs", "app.config.ts"]) {
865
- const configPath = path5.join(cwd, fname);
1110
+ const configPath = path6.join(cwd, fname);
866
1111
  const txt = await readIfExists(configPath);
867
1112
  if (!txt) continue;
868
1113
  const imports = await importedJsonObjects(configPath, txt, cwd);
@@ -884,7 +1129,7 @@ async function detectHints(cwd) {
884
1129
  if (m?.[1]) {
885
1130
  const pkg = m[1];
886
1131
  if (!hints.some((h) => h.packageName === pkg)) {
887
- hints.push({ packageName: pkg, source: [path5.relative(cwd, f)] });
1132
+ hints.push({ packageName: pkg, source: [path6.relative(cwd, f)] });
888
1133
  }
889
1134
  }
890
1135
  }
@@ -899,7 +1144,7 @@ async function detectHints(cwd) {
899
1144
  const bid = m[1];
900
1145
  if (bid.includes("$(PRODUCT_BUNDLE_IDENTIFIER)")) continue;
901
1146
  if (!hints.some((h) => h.bundleId === bid)) {
902
- hints.push({ bundleId: bid, source: [path5.relative(cwd, f)] });
1147
+ hints.push({ bundleId: bid, source: [path6.relative(cwd, f)] });
903
1148
  }
904
1149
  }
905
1150
  }
@@ -912,11 +1157,11 @@ async function detectHints(cwd) {
912
1157
  const bid = m[1].trim().replace(/^["']|["']$/g, "");
913
1158
  if (!bid || bid.includes("$")) continue;
914
1159
  if (!hints.some((h) => h.bundleId === bid)) {
915
- hints.push({ bundleId: bid, source: [path5.relative(cwd, f)] });
1160
+ hints.push({ bundleId: bid, source: [path6.relative(cwd, f)] });
916
1161
  }
917
1162
  }
918
1163
  }
919
- const unitySettings = path5.join(cwd, "ProjectSettings", "ProjectSettings.asset");
1164
+ const unitySettings = path6.join(cwd, "ProjectSettings", "ProjectSettings.asset");
920
1165
  const unityText = await readIfExists(unitySettings);
921
1166
  if (unityText) {
922
1167
  const lines = unityText.split(/\r?\n/);
@@ -940,11 +1185,11 @@ async function detectHints(cwd) {
940
1185
  name,
941
1186
  packageName,
942
1187
  bundleId,
943
- source: [path5.relative(cwd, unitySettings)]
1188
+ source: [path6.relative(cwd, unitySettings)]
944
1189
  });
945
1190
  }
946
1191
  }
947
- const pkgJson = await readIfExists(path5.join(cwd, "package.json"));
1192
+ const pkgJson = await readIfExists(path6.join(cwd, "package.json"));
948
1193
  if (pkgJson) {
949
1194
  try {
950
1195
  const json = JSON.parse(pkgJson);
@@ -980,7 +1225,7 @@ async function detectHints(cwd) {
980
1225
  return merged.filter((h) => h.packageName || h.bundleId);
981
1226
  }
982
1227
  async function hasAnyProjectSignal(cwd) {
983
- return await pathExists(path5.join(cwd, "package.json")) || await pathExists(path5.join(cwd, "app.json")) || await pathExists(path5.join(cwd, "android")) || await pathExists(path5.join(cwd, "ios")) || await pathExists(path5.join(cwd, "ProjectSettings", "ProjectSettings.asset"));
1228
+ return await pathExists(path6.join(cwd, "package.json")) || await pathExists(path6.join(cwd, "app.json")) || await pathExists(path6.join(cwd, "android")) || await pathExists(path6.join(cwd, "ios")) || await pathExists(path6.join(cwd, "ProjectSettings", "ProjectSettings.asset"));
984
1229
  }
985
1230
 
986
1231
  // src/deploy.ts
@@ -988,28 +1233,28 @@ import kleur from "kleur";
988
1233
  import * as readline from "readline";
989
1234
 
990
1235
  // src/config.ts
991
- import fs5 from "fs/promises";
992
- import path6 from "path";
993
- import os3 from "os";
994
- var CONFIG_DIR2 = path6.join(os3.homedir(), ".mimi-seed");
995
- var CONFIG_PATH = path6.join(CONFIG_DIR2, "config.json");
1236
+ import fs6 from "fs/promises";
1237
+ import path7 from "path";
1238
+ import os4 from "os";
1239
+ var CONFIG_DIR2 = path7.join(os4.homedir(), ".mimi-seed");
1240
+ var CONFIG_PATH = path7.join(CONFIG_DIR2, "config.json");
996
1241
  async function readConfig() {
997
1242
  try {
998
- const txt = await fs5.readFile(CONFIG_PATH, "utf8");
1243
+ const txt = await fs6.readFile(CONFIG_PATH, "utf8");
999
1244
  return JSON.parse(txt);
1000
1245
  } catch {
1001
1246
  return null;
1002
1247
  }
1003
1248
  }
1004
1249
  async function writeConfig(cfg) {
1005
- await fs5.mkdir(CONFIG_DIR2, { recursive: true });
1006
- await fs5.writeFile(CONFIG_PATH, JSON.stringify(cfg, null, 2));
1250
+ await fs6.mkdir(CONFIG_DIR2, { recursive: true });
1251
+ await fs6.writeFile(CONFIG_PATH, JSON.stringify(cfg, null, 2));
1007
1252
  if (process.platform !== "win32") {
1008
- await fs5.chmod(CONFIG_PATH, 384);
1253
+ await fs6.chmod(CONFIG_PATH, 384);
1009
1254
  }
1010
1255
  }
1011
1256
  async function deleteConfig() {
1012
- await fs5.rm(CONFIG_PATH, { force: true });
1257
+ await fs6.rm(CONFIG_PATH, { force: true });
1013
1258
  }
1014
1259
  var CONFIG_LOCATION = CONFIG_PATH;
1015
1260
  async function getEffectiveConfig() {
@@ -1027,12 +1272,79 @@ async function getEffectiveConfig() {
1027
1272
  return readConfig();
1028
1273
  }
1029
1274
 
1275
+ // src/jenkins-project.ts
1276
+ import fs7 from "fs";
1277
+ import path8 from "path";
1278
+ var M = catalog({
1279
+ invalidManifest: "\uD504\uB85C\uC81D\uD2B8 .mimi-seed.json\uC740 \uC62C\uBC14\uB978 JSON \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4.",
1280
+ controllerMismatch: "\uD504\uB85C\uC81D\uD2B8 Jenkins URL\uACFC \uB85C\uCEEC\uC5D0\uC11C \uC778\uC99D\uB41C \uCEE8\uD2B8\uB864\uB7EC\uAC00 \uB2E4\uB985\uB2C8\uB2E4.",
1281
+ invalidJob: ".mimi-seed.json\uC758 services.jenkins.jobAndroid/jobIos\uC5D0 \uC62C\uBC14\uB978 \uC7A1 \uACBD\uB85C\uB97C \uC124\uC815\uD558\uC138\uC694.",
1282
+ invalidRef: "Jenkins --ref\uC5D0\uB294 \uCEE4\uBC0B \uD574\uC2DC\xB7\uD45C\uD604\uC2DD\uC774 \uC544\uB2CC \uBE0C\uB79C\uCE58 \uC774\uB984\uC744 \uC9C0\uC815\uD558\uC138\uC694."
1283
+ }, {
1284
+ invalidManifest: "Project .mimi-seed.json must be a valid JSON object.",
1285
+ controllerMismatch: "Project Jenkins URL differs from the locally authenticated controller.",
1286
+ invalidJob: "Configure services.jenkins.jobAndroid/jobIos in .mimi-seed.json with a valid job path.",
1287
+ invalidRef: "Jenkins --ref must be a branch name, not a commit hash or revision expression."
1288
+ });
1289
+ function resolveProjectJenkins(cfg, platform, cwd = process.cwd()) {
1290
+ const field = platform === "android" ? "jobAndroid" : "jobIos";
1291
+ let dir = path8.resolve(cwd);
1292
+ for (let depth = 0; depth <= 8; depth++) {
1293
+ const file = path8.join(dir, MANIFEST_FILENAME);
1294
+ if (fs7.existsSync(file)) {
1295
+ let manifest;
1296
+ try {
1297
+ manifest = JSON.parse(fs7.readFileSync(file, "utf8"));
1298
+ } catch {
1299
+ throw new Error(M().invalidManifest);
1300
+ }
1301
+ if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
1302
+ throw new Error(M().invalidManifest);
1303
+ }
1304
+ const service = manifest.services?.jenkins;
1305
+ if (service?.url !== void 0 && (typeof service.url !== "string" || service.url.replace(/\/+$/, "") !== cfg.url.replace(/\/+$/, ""))) {
1306
+ throw new Error(M().controllerMismatch);
1307
+ }
1308
+ return { job: validateJob(service?.[field]), source: file };
1309
+ }
1310
+ const parent = path8.dirname(dir);
1311
+ if (parent === dir) break;
1312
+ dir = parent;
1313
+ }
1314
+ return { job: validateJob(cfg[field]), source: "jenkins.json" };
1315
+ }
1316
+ function validateJob(job) {
1317
+ if (typeof job !== "string" || !job || job.split("/").some((part) => !part || part === "." || part === ".." || /[\\?#%]/.test(part) || [...part].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127))) {
1318
+ throw new Error(M().invalidJob);
1319
+ }
1320
+ return job;
1321
+ }
1322
+ function jenkinsJobPath(job) {
1323
+ return validateJob(job).split("/").map((part) => `job/${encodeURIComponent(part)}`).join("/");
1324
+ }
1325
+ function jenkinsBuildParameters(platform, ref, appId) {
1326
+ if (!ref || /^[a-f0-9]{7,40}$/i.test(ref) || ref.startsWith("-") || /[\s~^:?*[\\]|\.\.|@\{/.test(ref)) {
1327
+ throw new Error(M().invalidRef);
1328
+ }
1329
+ return {
1330
+ BUILD_TARGET: platform,
1331
+ SRC_GIT_COMMIT: ref,
1332
+ ANDROID_PUBLISH_TO_GOOGLEPLAY: String(platform === "android"),
1333
+ IOS_UPLOAD_TO_TESTFLIGHT: String(platform === "ios"),
1334
+ ANDROID_UPLOAD_TO_WEBFILE: "false",
1335
+ IOS_UPLOAD_TO_WEBFILE: "false",
1336
+ ANNOUNCE_UPDATE_TO_USERS: "false",
1337
+ TURN_OFF_SLACK_NOTI: "true",
1338
+ ...appId ? { MIMI_APP_ID: appId } : {}
1339
+ };
1340
+ }
1341
+
1030
1342
  // src/ci-providers.ts
1031
- import fs6 from "fs";
1032
- import path7 from "path";
1033
- import os4 from "os";
1034
- var CI_CONFIG_PATH = path7.join(os4.homedir(), ".mimi-seed", "ci.json");
1035
- var M = catalog(
1343
+ import fs8 from "fs";
1344
+ import path9 from "path";
1345
+ import os5 from "os";
1346
+ var CI_CONFIG_PATH = path9.join(os5.homedir(), ".mimi-seed", "ci.json");
1347
+ var M2 = catalog(
1036
1348
  {
1037
1349
  badToken: (provider, status) => `${provider} ${status} \u2014 \uD1A0\uD070\uC774 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC544`,
1038
1350
  noScope: (scopes) => `\uD1A0\uD070\uC5D0 \`workflow\` \uC2A4\uCF54\uD504\uAC00 \uC5C6\uC5B4 (\uD604\uC7AC: ${scopes || "\uC5C6\uC74C"}). \uC6CC\uD06C\uD50C\uB85C \uC2E4\uD589\uC774 403 \uC73C\uB85C \uB9C9\uD78C\uB2E4.`,
@@ -1049,7 +1361,7 @@ var M = catalog(
1049
1361
  );
1050
1362
  function loadCiProviderConfig() {
1051
1363
  try {
1052
- const cfg = JSON.parse(fs6.readFileSync(CI_CONFIG_PATH, "utf-8"));
1364
+ const cfg = JSON.parse(fs8.readFileSync(CI_CONFIG_PATH, "utf-8"));
1053
1365
  const host = normalizeHost(cfg.host);
1054
1366
  return host ? { ...cfg, host } : { ...cfg, host: void 0 };
1055
1367
  } catch {
@@ -1063,15 +1375,15 @@ function normalizeHost(host) {
1063
1375
  return withScheme.replace(/\/+$/, "");
1064
1376
  }
1065
1377
  function saveCiProviderConfig(cfg) {
1066
- const dir = path7.dirname(CI_CONFIG_PATH);
1067
- if (!fs6.existsSync(dir)) {
1068
- fs6.mkdirSync(dir, { recursive: true, mode: 448 });
1378
+ const dir = path9.dirname(CI_CONFIG_PATH);
1379
+ if (!fs8.existsSync(dir)) {
1380
+ fs8.mkdirSync(dir, { recursive: true, mode: 448 });
1069
1381
  }
1070
1382
  const normalized = { ...cfg, host: normalizeHost(cfg.host) };
1071
1383
  if (!normalized.host) delete normalized.host;
1072
- fs6.writeFileSync(CI_CONFIG_PATH, JSON.stringify(normalized, null, 2));
1384
+ fs8.writeFileSync(CI_CONFIG_PATH, JSON.stringify(normalized, null, 2));
1073
1385
  if (process.platform !== "win32") {
1074
- fs6.chmodSync(CI_CONFIG_PATH, 384);
1386
+ fs8.chmodSync(CI_CONFIG_PATH, 384);
1075
1387
  }
1076
1388
  }
1077
1389
  async function verifyCiToken(cfg) {
@@ -1082,18 +1394,18 @@ async function verifyCiToken(cfg) {
1082
1394
  headers: { Authorization: `Bearer ${probe.token}`, Accept: "application/vnd.github+json" }
1083
1395
  });
1084
1396
  if (!res2.ok) {
1085
- return { ok: false, reason: M().badToken("GitHub", res2.status) };
1397
+ return { ok: false, reason: M2().badToken("GitHub", res2.status) };
1086
1398
  }
1087
1399
  const user2 = await res2.json();
1088
1400
  const scopes = res2.headers.get("x-oauth-scopes");
1089
1401
  if (scopes !== null && !scopes.split(/,\s*/).filter(Boolean).includes("workflow")) {
1090
- return { ok: false, reason: M().noScope(scopes) };
1402
+ return { ok: false, reason: M2().noScope(scopes) };
1091
1403
  }
1092
1404
  return { ok: true, login: user2.login };
1093
1405
  }
1094
1406
  const res = await fetch(`${glBase(probe)}/user`, { headers: { "PRIVATE-TOKEN": probe.token } });
1095
1407
  if (!res.ok) {
1096
- return { ok: false, reason: M().badToken("GitLab", res.status) };
1408
+ return { ok: false, reason: M2().badToken("GitLab", res.status) };
1097
1409
  }
1098
1410
  const user = await res.json();
1099
1411
  return { ok: true, login: user.username };
@@ -1151,12 +1463,12 @@ async function ghPollRun(cfg, runId, onTick, timeoutMs = 30 * 60 * 1e3, interval
1151
1463
  );
1152
1464
  } catch {
1153
1465
  consecutiveErrors++;
1154
- if (consecutiveErrors >= 3) throw new Error(M().pollFailed("GitHub"));
1466
+ if (consecutiveErrors >= 3) throw new Error(M2().pollFailed("GitHub"));
1155
1467
  continue;
1156
1468
  }
1157
1469
  if (!res.ok) {
1158
1470
  consecutiveErrors++;
1159
- if (consecutiveErrors >= 3) throw new Error(M().pollFailedHttp("GitHub", res.status));
1471
+ if (consecutiveErrors >= 3) throw new Error(M2().pollFailedHttp("GitHub", res.status));
1160
1472
  continue;
1161
1473
  }
1162
1474
  consecutiveErrors = 0;
@@ -1202,12 +1514,12 @@ async function glPollPipeline(cfg, pipelineId, onTick, timeoutMs = 30 * 60 * 1e3
1202
1514
  );
1203
1515
  } catch {
1204
1516
  consecutiveErrors++;
1205
- if (consecutiveErrors >= 3) throw new Error(M().pollFailed("GitLab"));
1517
+ if (consecutiveErrors >= 3) throw new Error(M2().pollFailed("GitLab"));
1206
1518
  continue;
1207
1519
  }
1208
1520
  if (!res.ok) {
1209
1521
  consecutiveErrors++;
1210
- if (consecutiveErrors >= 3) throw new Error(M().pollFailedHttp("GitLab", res.status));
1522
+ if (consecutiveErrors >= 3) throw new Error(M2().pollFailedHttp("GitLab", res.status));
1211
1523
  continue;
1212
1524
  }
1213
1525
  consecutiveErrors = 0;
@@ -1233,8 +1545,15 @@ var PHASE_ICON = {
1233
1545
  function log(msg) {
1234
1546
  process.stdout.write(msg + "\n");
1235
1547
  }
1236
- var M2 = catalog(
1548
+ var M3 = catalog(
1237
1549
  {
1550
+ missingValue: (option) => `${option} \uB4A4\uC5D0 \uAC12\uC744 \uC785\uB825\uD558\uC138\uC694.`,
1551
+ unknownOption: (option) => `\uC54C \uC218 \uC5C6\uB294 deploy \uC635\uC158: ${option}`,
1552
+ invalidPlatform: "--platform\uC740 android \uB610\uB294 ios\uC5EC\uC57C \uD569\uB2C8\uB2E4.",
1553
+ invalidCi: "\uC62C\uBC14\uB978 --ci \uD504\uB85C\uBC14\uC774\uB354\uB97C \uC9C0\uC815\uD558\uC138\uC694.",
1554
+ invalidVersion: "--version-code\uB294 \uD5C8\uC6A9 \uBC94\uC704\uC758 \uC591\uC758 \uC815\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4.",
1555
+ multipleSetup: "setup \uBA85\uB839\uC740 \uD558\uB098\uB9CC \uC120\uD0DD\uD558\uC138\uC694.",
1556
+ drySetup: "--dry-run\uACFC setup\uC744 \uD568\uAED8 \uC2E4\uD589\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
1238
1557
  // Jenkins / 빌드
1239
1558
  jenkinsTriggerFailed: (status, body) => `Jenkins \uD2B8\uB9AC\uAC70 \uC2E4\uD328 ${status}: ${body}`,
1240
1559
  buildStatusFailed: (status) => `\uBE4C\uB4DC \uC0C1\uD0DC \uC870\uD68C \uC2E4\uD328 ${status}`,
@@ -1284,7 +1603,7 @@ var M2 = catalog(
1284
1603
  buildStarted: (n) => ` \uBE4C\uB4DC #${n} \uC2DC\uC791\uB428. \uC644\uB8CC \uB300\uAE30 \uC911...`,
1285
1604
  buildFailed: (result) => `\uBE4C\uB4DC \uC2E4\uD328: ${result}`,
1286
1605
  jenkinsLink: (url) => ` Jenkins: ${url}`,
1287
- versionCodeFromBuild: (n) => ` versionCode = buildNumber (${n})`,
1606
+ explicitApproval: "\uC2E4\uC81C CI \uC2E4\uD589\xB7\uBC30\uD3EC\uC5D0\uB294 --yes \uC2B9\uC778\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.",
1288
1607
  noProviderConfig: (kind) => `${kind} \uC124\uC815\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. setup-${kind} \uC2E4\uD589.`,
1289
1608
  versionCodeUnsuitable: (kind, buildId) => `\u2717 versionCode \uBBF8\uC9C0\uC815 (${kind} run_id ${buildId}\uB294 versionCode\uB85C \uBD80\uC801\uD569)`,
1290
1609
  recommendation: " \uAD8C\uC7A5 \uC0AC\uD56D:",
@@ -1303,6 +1622,13 @@ var M2 = catalog(
1303
1622
  done: "\uC644\uB8CC. Play Console\uC5D0\uC11C \uBC30\uD3EC \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uC138\uC694."
1304
1623
  },
1305
1624
  {
1625
+ missingValue: (option) => `Missing value: ${option}`,
1626
+ unknownOption: (option) => `Unknown deploy option: ${option}`,
1627
+ invalidPlatform: "--platform must be android or ios.",
1628
+ invalidCi: "Specify a valid --ci provider.",
1629
+ invalidVersion: "--version-code must be a positive integer within the allowed range.",
1630
+ multipleSetup: "Choose one setup command.",
1631
+ drySetup: "--dry-run cannot run setup.",
1306
1632
  // Jenkins / build
1307
1633
  jenkinsTriggerFailed: (status, body) => `Jenkins trigger failed ${status}: ${body}`,
1308
1634
  buildStatusFailed: (status) => `Failed to fetch build status ${status}`,
@@ -1352,7 +1678,7 @@ var M2 = catalog(
1352
1678
  buildStarted: (n) => ` Build #${n} started. Waiting for it to finish...`,
1353
1679
  buildFailed: (result) => `Build failed: ${result}`,
1354
1680
  jenkinsLink: (url) => ` Jenkins: ${url}`,
1355
- versionCodeFromBuild: (n) => ` versionCode = buildNumber (${n})`,
1681
+ explicitApproval: "Real CI execution and deployment require explicit --yes approval.",
1356
1682
  noProviderConfig: (kind) => `${kind} is not configured. Run setup-${kind}.`,
1357
1683
  versionCodeUnsuitable: (kind, buildId) => `\u2717 No versionCode given (${kind} run_id ${buildId} is not usable as a versionCode)`,
1358
1684
  recommendation: " Recommended:",
@@ -1377,10 +1703,16 @@ function jenkinsHeaders(cfg) {
1377
1703
  }
1378
1704
  async function triggerBuild(cfg, jobName, params) {
1379
1705
  const qs = new URLSearchParams(params).toString();
1380
- const url = `${cfg.url}/job/${encodeURIComponent(jobName)}/buildWithParameters?${qs}`;
1381
- const res = await fetch(url, { method: "POST", headers: jenkinsHeaders(cfg) });
1706
+ const url = `${cfg.url.replace(/\/+$/, "")}/${jenkinsJobPath(jobName)}/buildWithParameters`;
1707
+ const res = await fetch(url, {
1708
+ method: "POST",
1709
+ redirect: "error",
1710
+ signal: AbortSignal.timeout(3e4),
1711
+ headers: { ...jenkinsHeaders(cfg), "Content-Type": "application/x-www-form-urlencoded" },
1712
+ body: qs
1713
+ });
1382
1714
  if (!res.ok) {
1383
- throw new Error(M2().jenkinsTriggerFailed(res.status, await res.text()));
1715
+ throw new Error(M3().jenkinsTriggerFailed(res.status, await res.text()));
1384
1716
  }
1385
1717
  const location = res.headers.get("Location") ?? "";
1386
1718
  const match = location.match(/\/queue\/item\/(\d+)\//);
@@ -1388,15 +1720,15 @@ async function triggerBuild(cfg, jobName, params) {
1388
1720
  }
1389
1721
  async function getQueueBuildNumber(cfg, queueItemId) {
1390
1722
  const url = `${cfg.url}/queue/item/${queueItemId}/api/json`;
1391
- const res = await fetch(url, { headers: jenkinsHeaders(cfg) });
1723
+ const res = await fetch(url, { headers: jenkinsHeaders(cfg), redirect: "error", signal: AbortSignal.timeout(3e4) });
1392
1724
  if (!res.ok) return null;
1393
1725
  const data = await res.json();
1394
1726
  return data.executable?.number ?? null;
1395
1727
  }
1396
1728
  async function getBuildStatus(cfg, jobName, buildNumber) {
1397
- const url = `${cfg.url}/job/${encodeURIComponent(jobName)}/${buildNumber}/api/json`;
1398
- const res = await fetch(url, { headers: jenkinsHeaders(cfg) });
1399
- if (!res.ok) throw new Error(M2().buildStatusFailed(res.status));
1729
+ const url = `${cfg.url}/${jenkinsJobPath(jobName)}/${buildNumber}/api/json`;
1730
+ const res = await fetch(url, { headers: jenkinsHeaders(cfg), redirect: "error", signal: AbortSignal.timeout(3e4) });
1731
+ if (!res.ok) throw new Error(M3().buildStatusFailed(res.status));
1400
1732
  const data = await res.json();
1401
1733
  return {
1402
1734
  building: data.building ?? true,
@@ -1417,18 +1749,18 @@ async function pollBuildComplete(cfg, jobName, buildNumber, timeoutMs = 30 * 60
1417
1749
  consecutiveErrors = 0;
1418
1750
  } catch {
1419
1751
  consecutiveErrors++;
1420
- if (consecutiveErrors >= 3) throw new Error(M2().jenkinsConnErrorFatal);
1421
- process.stdout.write(`\r \u26A0 ${M2().jenkinsConnErrorRetry(consecutiveErrors)} `);
1752
+ if (consecutiveErrors >= 3) throw new Error(M3().jenkinsConnErrorFatal);
1753
+ process.stdout.write(`\r \u26A0 ${M3().jenkinsConnErrorRetry(consecutiveErrors)} `);
1422
1754
  continue;
1423
1755
  }
1424
1756
  dots = (dots + 1) % 4;
1425
- process.stdout.write(`\r \u23F3 ${M2().buildRunning}${".".repeat(dots + 1)} `);
1757
+ process.stdout.write(`\r \u23F3 ${M3().buildRunning}${".".repeat(dots + 1)} `);
1426
1758
  if (!status.building) {
1427
1759
  process.stdout.write("\n");
1428
1760
  return status.result ?? "FAILURE";
1429
1761
  }
1430
1762
  }
1431
- throw new Error(M2().buildTimeout);
1763
+ throw new Error(M3().buildTimeout);
1432
1764
  }
1433
1765
  async function streamDeploy(webBase, token, body) {
1434
1766
  const res = await fetch(`${webBase}/api/deploy`, {
@@ -1441,10 +1773,10 @@ async function streamDeploy(webBase, token, body) {
1441
1773
  });
1442
1774
  if (!res.ok) {
1443
1775
  const text = await res.text().catch(() => "");
1444
- throw new Error(M2().serverDeployFailed(res.status, text.slice(0, 200)));
1776
+ throw new Error(M3().serverDeployFailed(res.status, text.slice(0, 200)));
1445
1777
  }
1446
1778
  const reader = res.body?.getReader();
1447
- if (!reader) throw new Error(M2().noSseStream);
1779
+ if (!reader) throw new Error(M3().noSseStream);
1448
1780
  const decoder = new TextDecoder();
1449
1781
  let buf = "";
1450
1782
  while (true) {
@@ -1468,6 +1800,7 @@ async function streamDeploy(webBase, token, body) {
1468
1800
  }
1469
1801
  }
1470
1802
  }
1803
+ var ANDROID_VERSION_CODE_MAX = 21e8;
1471
1804
  function parseArgs(argv) {
1472
1805
  const args = {
1473
1806
  platform: "android",
@@ -1482,29 +1815,78 @@ function parseArgs(argv) {
1482
1815
  ref: "main"
1483
1816
  };
1484
1817
  for (let i = 0; i < argv.length; i++) {
1485
- if ((argv[i] === "--platform" || argv[i] === "-p") && argv[i + 1]) args.platform = argv[++i];
1486
- if (argv[i] === "--app" && argv[i + 1]) args.appId = argv[++i];
1487
- if (argv[i] === "--version-code" && argv[i + 1]) args.versionCode = Number(argv[++i]);
1488
- if (argv[i] === "--from" && argv[i + 1]) args.fromRef = argv[++i];
1489
- if (argv[i] === "--to" && argv[i + 1]) args.toRef = argv[++i];
1490
- if (argv[i] === "--language" && argv[i + 1]) args.language = argv[++i];
1491
- if (argv[i] === "--dry-run") args.dryRun = true;
1492
- if (argv[i] === "--yes" || argv[i] === "-y") args.yes = true;
1493
- if (argv[i] === "--skip-build") args.skipBuild = true;
1494
- if (argv[i] === "--ci" && argv[i + 1]) args.ci = argv[++i];
1495
- if (argv[i] === "--workflow" && argv[i + 1]) args.workflow = argv[++i];
1496
- if (argv[i] === "--ref" && argv[i + 1]) args.ref = argv[++i];
1497
- if (argv[i] === "setup-jenkins") args.setupJenkins = true;
1498
- if (argv[i] === "setup-github") args.setupGithub = true;
1499
- if (argv[i] === "setup-gitlab") args.setupGitlab = true;
1818
+ const option = argv[i];
1819
+ const value = () => {
1820
+ const next = argv[++i];
1821
+ if (!next?.trim() || next.startsWith("--")) throw new Error(M3().missingValue(option));
1822
+ return next;
1823
+ };
1824
+ switch (option) {
1825
+ case "--platform":
1826
+ case "-p":
1827
+ args.platform = value();
1828
+ break;
1829
+ case "--app":
1830
+ args.appId = value();
1831
+ break;
1832
+ case "--version-code":
1833
+ args.versionCode = Number(value());
1834
+ break;
1835
+ case "--from":
1836
+ args.fromRef = value();
1837
+ break;
1838
+ case "--to":
1839
+ args.toRef = value();
1840
+ break;
1841
+ case "--language":
1842
+ args.language = value();
1843
+ break;
1844
+ case "--dry-run":
1845
+ args.dryRun = true;
1846
+ break;
1847
+ case "--yes":
1848
+ case "-y":
1849
+ args.yes = true;
1850
+ break;
1851
+ case "--skip-build":
1852
+ args.skipBuild = true;
1853
+ break;
1854
+ case "--ci":
1855
+ args.ci = value();
1856
+ break;
1857
+ case "--workflow":
1858
+ args.workflow = value();
1859
+ break;
1860
+ case "--ref":
1861
+ args.ref = value();
1862
+ break;
1863
+ case "setup-jenkins":
1864
+ args.setupJenkins = true;
1865
+ break;
1866
+ case "setup-github":
1867
+ args.setupGithub = true;
1868
+ break;
1869
+ case "setup-gitlab":
1870
+ args.setupGitlab = true;
1871
+ break;
1872
+ default:
1873
+ throw new Error(M3().unknownOption(option));
1874
+ }
1500
1875
  }
1876
+ if (!["android", "ios"].includes(args.platform)) throw new Error(M3().invalidPlatform);
1877
+ if (!["auto", "jenkins", "github", "gitlab"].includes(args.ci)) throw new Error(M3().invalidCi);
1878
+ if (args.versionCode !== void 0 && (!Number.isSafeInteger(args.versionCode) || args.versionCode < 1 || args.versionCode > ANDROID_VERSION_CODE_MAX)) {
1879
+ throw new Error(M3().invalidVersion);
1880
+ }
1881
+ if ([args.setupJenkins, args.setupGithub, args.setupGitlab].filter(Boolean).length > 1) throw new Error(M3().multipleSetup);
1882
+ if (args.dryRun && (args.setupJenkins || args.setupGithub || args.setupGitlab)) throw new Error(M3().drySetup);
1501
1883
  return args;
1502
1884
  }
1503
1885
  async function promptGitProviderSetup(provider) {
1504
1886
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1505
1887
  const ask2 = (q) => new Promise((resolve) => rl.question(q, (a) => resolve(a.trim())));
1506
1888
  const isGh = provider === "github";
1507
- const m = M2();
1889
+ const m = M3();
1508
1890
  log(kleur.bold(isGh ? m.githubSetupTitle : m.gitlabSetupTitle));
1509
1891
  const tokenLabel = isGh ? m.githubTokenPrompt : m.gitlabTokenPrompt;
1510
1892
  const token = await ask2(tokenLabel);
@@ -1525,36 +1907,36 @@ function resolveCi(ciOption, jenkins, ciProvider) {
1525
1907
  if (ciOption !== "auto") return ciOption;
1526
1908
  if (jenkins?.url && jenkins.token) return "jenkins";
1527
1909
  if (ciProvider) return ciProvider.provider;
1528
- throw new Error(M2().noCiConfig);
1910
+ throw new Error(M3().noCiConfig);
1529
1911
  }
1530
1912
  async function runGitProviderBuild(cfg, args) {
1531
1913
  let runUrl;
1532
1914
  let runId;
1533
1915
  if (cfg.provider === "github") {
1534
1916
  if (!args.workflow) {
1535
- throw new Error(M2().workflowRequired);
1917
+ throw new Error(M3().workflowRequired);
1536
1918
  }
1537
- log(M2().ghTrigger(kleur.cyan(args.workflow), args.ref));
1919
+ log(M3().ghTrigger(kleur.cyan(args.workflow), args.ref));
1538
1920
  const inputs = {};
1539
1921
  if (args.appId) inputs.MIMI_APP_ID = args.appId;
1540
1922
  inputs.PLATFORM = args.platform;
1541
1923
  const result2 = await ghTriggerWorkflow(cfg, args.workflow, args.ref, inputs);
1542
1924
  if (!result2) {
1543
- throw new Error(M2().ghRunIdFailed);
1925
+ throw new Error(M3().ghRunIdFailed);
1544
1926
  }
1545
1927
  runId = result2.runId;
1546
1928
  runUrl = result2.url;
1547
- log(kleur.dim(M2().ghRunId(runId, runUrl)));
1929
+ log(kleur.dim(M3().ghRunId(runId, runUrl)));
1548
1930
  } else {
1549
- log(M2().glTrigger(args.ref));
1931
+ log(M3().glTrigger(args.ref));
1550
1932
  const variables = { PLATFORM: args.platform };
1551
1933
  if (args.appId) variables.MIMI_APP_ID = args.appId;
1552
1934
  const result2 = await glTriggerPipeline(cfg, args.ref, variables);
1553
1935
  runId = result2.pipelineId;
1554
1936
  runUrl = result2.url;
1555
- log(kleur.dim(M2().glPipelineId(runId, runUrl)));
1937
+ log(kleur.dim(M3().glPipelineId(runId, runUrl)));
1556
1938
  }
1557
- log(M2().waitingForCompletion);
1939
+ log(M3().waitingForCompletion);
1558
1940
  let dots = 0;
1559
1941
  const onTick = (status) => {
1560
1942
  dots = (dots + 1) % 4;
@@ -1563,19 +1945,36 @@ async function runGitProviderBuild(cfg, args) {
1563
1945
  const result = cfg.provider === "github" ? await ghPollRun(cfg, runId, onTick) : await glPollPipeline(cfg, runId, onTick);
1564
1946
  process.stdout.write("\n");
1565
1947
  if (result === "success") {
1566
- log(kleur.green(M2().buildSucceeded(runId)));
1948
+ log(kleur.green(M3().buildSucceeded(runId)));
1567
1949
  return runId;
1568
1950
  }
1569
- log(kleur.red(M2().buildEnded(result)));
1951
+ log(kleur.red(M3().buildEnded(result)));
1570
1952
  log(kleur.dim(` ${runUrl}`));
1571
- log(kleur.dim(M2().alreadyBuiltHint("<N>", args.platform)));
1953
+ log(kleur.dim(M3().alreadyBuiltHint("<N>", args.platform)));
1572
1954
  process.exit(1);
1573
1955
  }
1574
1956
  async function cmdDeploy(argv) {
1575
1957
  const args = parseArgs(argv);
1958
+ if (args.dryRun) {
1959
+ const jenkins = loadJenkinsConfig() ?? void 0;
1960
+ const ci = args.skipBuild ? null : resolveCi(args.ci, jenkins, loadCiProviderConfig());
1961
+ if (ci === "jenkins" && !jenkins) throw new Error(M3().noJenkinsConfig);
1962
+ const plan = ci === "jenkins" && jenkins ? { ...resolveProjectJenkins(jenkins, args.platform), parameters: jenkinsBuildParameters(args.platform, args.ref, args.appId) } : void 0;
1963
+ log(M3().dryRunNotice);
1964
+ log(JSON.stringify({
1965
+ platform: args.platform,
1966
+ appId: args.appId ?? null,
1967
+ ci,
1968
+ ref: args.ref,
1969
+ jenkins: plan,
1970
+ versionCode: args.versionCode ?? null,
1971
+ versionSource: "explicit --version-code; never CI run ID"
1972
+ }, null, 2));
1973
+ return;
1974
+ }
1576
1975
  const cfg = await getEffectiveConfig();
1577
1976
  if (!cfg) {
1578
- log(kleur.red(M2().noAccount));
1977
+ log(kleur.red(M3().noAccount));
1579
1978
  process.exit(1);
1580
1979
  }
1581
1980
  if (args.setupJenkins) {
@@ -1586,48 +1985,47 @@ async function cmdDeploy(argv) {
1586
1985
  if (args.setupGithub) {
1587
1986
  const ciCfg = await promptGitProviderSetup("github");
1588
1987
  saveCiProviderConfig(ciCfg);
1589
- log(kleur.green(M2().githubSaved));
1988
+ log(kleur.green(M3().githubSaved));
1590
1989
  return;
1591
1990
  }
1592
1991
  if (args.setupGitlab) {
1593
1992
  const ciCfg = await promptGitProviderSetup("gitlab");
1594
1993
  saveCiProviderConfig(ciCfg);
1595
- log(kleur.green(M2().gitlabSaved));
1994
+ log(kleur.green(M3().gitlabSaved));
1596
1995
  return;
1597
1996
  }
1598
- log(kleur.bold(M2().title(args.platform)));
1599
- if (args.dryRun) log(kleur.yellow(M2().dryRunNotice));
1997
+ if (!args.appId) throw new Error(M3().noAppId);
1998
+ if (!args.yes) throw new Error(M3().explicitApproval);
1999
+ if (args.skipBuild && !args.versionCode) throw new Error(M3().versionCodeUnknown);
2000
+ log(kleur.bold(M3().title(args.platform)));
2001
+ if (args.dryRun) log(kleur.yellow(M3().dryRunNotice));
1600
2002
  log("");
1601
- let versionCode = args.versionCode;
2003
+ const versionCode = args.versionCode;
2004
+ let deployBuildNumber;
1602
2005
  if (!args.skipBuild) {
1603
2006
  const ciProvider = loadCiProviderConfig();
1604
2007
  migrateLegacyJenkins();
1605
2008
  const jenkinsCfg = loadJenkinsConfig() ?? void 0;
1606
2009
  const kind = resolveCi(args.ci, jenkinsCfg, ciProvider);
1607
- log(kleur.dim(M2().ciLine(kind)));
2010
+ log(kleur.dim(M3().ciLine(kind)));
1608
2011
  if (kind === "jenkins") {
1609
2012
  if (!jenkinsCfg?.url || !jenkinsCfg?.token) {
1610
- log(kleur.yellow(M2().noJenkinsConfig));
2013
+ log(kleur.yellow(M3().noJenkinsConfig));
1611
2014
  process.exit(1);
1612
2015
  }
1613
2016
  const jenkins = jenkinsCfg;
1614
- const jobName = args.platform === "android" ? jenkins.jobAndroid : jenkins.jobIos;
1615
- if (!jobName) {
1616
- log(kleur.red(M2().noJenkinsJob(args.platform)));
1617
- process.exit(1);
1618
- }
1619
- log(M2().jenkinsTrigger(kleur.cyan(jobName)));
1620
- const buildParams = {};
1621
- if (args.appId) buildParams.MIMI_APP_ID = args.appId;
2017
+ const { job: jobName } = resolveProjectJenkins(jenkins, args.platform);
2018
+ log(M3().jenkinsTrigger(kleur.cyan(jobName)));
2019
+ const buildParams = jenkinsBuildParameters(args.platform, args.ref, args.appId);
1622
2020
  const queueItemId = await triggerBuild(jenkins, jobName, buildParams);
1623
2021
  if (!queueItemId) {
1624
- log(kleur.yellow(M2().noQueueItem));
2022
+ log(kleur.yellow(M3().noQueueItem));
1625
2023
  } else {
1626
- log(kleur.dim(M2().queueItem(queueItemId)));
2024
+ log(kleur.dim(M3().queueItem(queueItemId)));
1627
2025
  }
1628
2026
  let buildNumber = null;
1629
2027
  if (queueItemId) {
1630
- log(M2().waitingForBuildNumber);
2028
+ log(M3().waitingForBuildNumber);
1631
2029
  for (let i = 0; i < 6; i++) {
1632
2030
  await new Promise((r) => setTimeout(r, 5e3));
1633
2031
  buildNumber = await getQueueBuildNumber(jenkins, queueItemId).catch(() => null);
@@ -1635,79 +2033,45 @@ async function cmdDeploy(argv) {
1635
2033
  }
1636
2034
  }
1637
2035
  if (!buildNumber) {
1638
- log(kleur.yellow(M2().noBuildNumber));
2036
+ log(kleur.yellow(M3().noBuildNumber));
1639
2037
  process.exit(1);
1640
2038
  }
1641
- log(M2().buildStarted(buildNumber));
2039
+ log(M3().buildStarted(buildNumber));
1642
2040
  const result = await pollBuildComplete(jenkins, jobName, buildNumber);
1643
2041
  if (result !== "SUCCESS") {
1644
- log(kleur.red(M2().buildFailed(result)));
1645
- log(kleur.dim(M2().jenkinsLink(`${jenkins.url}/job/${encodeURIComponent(jobName)}/${buildNumber}/`)));
1646
- log(kleur.dim(M2().alreadyBuiltHint(String(buildNumber), args.platform)));
2042
+ log(kleur.red(M3().buildFailed(result)));
2043
+ log(kleur.dim(M3().jenkinsLink(`${jenkins.url}/job/${encodeURIComponent(jobName)}/${buildNumber}/`)));
2044
+ log(kleur.dim(M3().alreadyBuiltHint("<N>", args.platform)));
1647
2045
  process.exit(1);
1648
2046
  }
1649
- log(kleur.green(M2().buildSucceeded(buildNumber)));
2047
+ log(kleur.green(M3().buildSucceeded(buildNumber)));
2048
+ deployBuildNumber = buildNumber;
1650
2049
  if (!versionCode) {
1651
- versionCode = buildNumber;
1652
- log(kleur.dim(M2().versionCodeFromBuild(versionCode)));
2050
+ throw new Error(M3().versionCodeUnsuitable("Jenkins", buildNumber) + "\n" + M3().recommendationNext);
1653
2051
  }
1654
2052
  } else {
1655
2053
  if (!ciProvider) {
1656
- log(kleur.red(M2().noProviderConfig(kind)));
2054
+ log(kleur.red(M3().noProviderConfig(kind)));
1657
2055
  process.exit(1);
1658
2056
  }
1659
2057
  const buildId = await runGitProviderBuild(ciProvider, args);
1660
2058
  if (!versionCode) {
1661
- log(kleur.red(M2().versionCodeUnsuitable(kind, buildId)));
1662
- log(kleur.dim(M2().recommendation));
1663
- log(kleur.dim(M2().recommendationCi));
1664
- log(kleur.dim(M2().recommendationNext));
2059
+ log(kleur.red(M3().versionCodeUnsuitable(kind, buildId)));
2060
+ log(kleur.dim(M3().recommendation));
2061
+ log(kleur.dim(M3().recommendationCi));
2062
+ log(kleur.dim(M3().recommendationNext));
1665
2063
  process.exit(1);
1666
2064
  }
1667
2065
  }
1668
2066
  }
1669
2067
  if (!versionCode) {
1670
- log(kleur.red(M2().versionCodeUnknown));
2068
+ log(kleur.red(M3().versionCodeUnknown));
1671
2069
  process.exit(1);
1672
2070
  }
1673
- let appId = args.appId;
1674
- if (!appId) {
1675
- const { mcpCall } = await import("./mcp-client-L5BQ5VG4.js");
1676
- const r = await mcpCall(cfg.endpoint, cfg.token, "list_apps", {});
1677
- if (!r.isError) {
1678
- try {
1679
- const apps = JSON.parse(r.text);
1680
- if (apps.length > 0) {
1681
- appId = apps[0].id;
1682
- log(kleur.dim(M2().appLine(apps[0].name, appId)));
1683
- }
1684
- } catch {
1685
- }
1686
- }
1687
- }
1688
- if (!appId) {
1689
- log(kleur.red(M2().noAppId));
1690
- process.exit(1);
1691
- }
1692
- const needsConfirm = !args.dryRun && !args.yes && process.stdout.isTTY && !process.env.MIMI_SEED_TOKEN;
1693
- if (needsConfirm) {
1694
- const target = args.platform === "ios" ? M2().targetIos : M2().targetAndroid;
1695
- log("");
1696
- log(kleur.yellow(M2().realDeploy(args.platform, versionCode, target)));
1697
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1698
- const answer = await new Promise(
1699
- (resolve) => rl.question(kleur.bold(M2().confirmPrompt), (a) => resolve(a.trim().toLowerCase()))
1700
- );
1701
- rl.close();
1702
- if (answer !== "y" && answer !== "yes") {
1703
- log(kleur.dim(M2().confirmCancelled));
1704
- return;
1705
- }
1706
- }
2071
+ const appId = args.appId;
1707
2072
  log("");
1708
- log(M2().pipelineStarting);
2073
+ log(M3().pipelineStarting);
1709
2074
  log("");
1710
- const deployBuildNumber = !args.skipBuild ? versionCode : void 0;
1711
2075
  await streamDeploy(cfg.webBase, cfg.token, {
1712
2076
  appId,
1713
2077
  platform: args.platform,
@@ -1719,7 +2083,7 @@ async function cmdDeploy(argv) {
1719
2083
  dryRun: args.dryRun
1720
2084
  });
1721
2085
  log("");
1722
- log(kleur.bold(M2().done));
2086
+ log(kleur.bold(M3().done));
1723
2087
  log(kleur.dim(" https://play.google.com/console/developers"));
1724
2088
  }
1725
2089
 
@@ -1855,7 +2219,7 @@ async function connectOne(spec) {
1855
2219
  }
1856
2220
  async function cmdSetup(argv) {
1857
2221
  const opts = parseSetupArgs(argv);
1858
- const home = os5.homedir();
2222
+ const home = os6.homedir();
1859
2223
  migrateLegacyJenkins(home);
1860
2224
  if (resolveMode(opts, process.env, process.stdin.isTTY) === "interactive") {
1861
2225
  await ensureLangChosen();
@@ -1958,6 +2322,11 @@ async function cmdSetup(argv) {
1958
2322
  export {
1959
2323
  detectHints,
1960
2324
  hasAnyProjectSignal,
2325
+ writeSettings,
2326
+ isLang,
2327
+ resolveLang,
2328
+ catalog,
2329
+ t,
1961
2330
  writeConfig,
1962
2331
  deleteConfig,
1963
2332
  CONFIG_LOCATION,