mimi-seed 0.19.10 → 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-ZGMPOJRY.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
  }
@@ -242,7 +270,14 @@ var CREDENTIALS = [
242
270
  docsAnchor: "app-store-connect",
243
271
  detect: (home) => {
244
272
  const cfg = readJson(home, "appstore.json");
245
- return cfg ? { present: true, detail: cfg.keyId ? `keyId ${cfg.keyId}` : void 0 } : { present: false };
273
+ return cfg ? {
274
+ present: true,
275
+ detail: cfg.keyId ? `keyId ${cfg.keyId}` : void 0,
276
+ identity: Object.fromEntries([
277
+ ["keyId", cfg.keyId],
278
+ ["issuerId", cfg.issuerId]
279
+ ].filter((entry) => typeof entry[1] === "string"))
280
+ } : { present: false };
246
281
  }
247
282
  },
248
283
  {
@@ -594,7 +629,7 @@ var CREDENTIALS = [
594
629
  function tryCredById(id) {
595
630
  return CREDENTIALS.find((c) => c.id === id);
596
631
  }
597
- function detectAll(home = os.homedir(), startDir = process.cwd()) {
632
+ function detectAll(home = os2.homedir(), startDir = process.cwd()) {
598
633
  return new Map(CREDENTIALS.map((c) => [c.id, c.detect(home, startDir)]));
599
634
  }
600
635
  function isSatisfied(spec, detected) {
@@ -632,17 +667,234 @@ function planSetup(detected, opts = {}) {
632
667
  // src/mcp-bin.ts
633
668
  import { spawn, spawnSync } from "child_process";
634
669
  import { existsSync, readFileSync } from "fs";
635
- 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
636
888
  var MCP_PKG = "@yoonion/mimi-seed-mcp";
637
889
  function resolveOnPath(bin, honorForceNpx = true) {
638
890
  if (honorForceNpx && process.env.MIMI_SEED_FORCE_NPX) return null;
639
891
  if (process.platform === "win32") {
640
892
  const pathKey = Object.keys(process.env).find((key) => key.toLowerCase() === "path");
641
- const directories = (pathKey ? process.env[pathKey] ?? "" : "").split(path3.delimiter).filter(Boolean);
893
+ const directories = (pathKey ? process.env[pathKey] ?? "" : "").split(path4.delimiter).filter(Boolean);
642
894
  const names = bin.toLowerCase().endsWith(".cmd") ? [bin] : [`${bin}.cmd`, bin];
643
895
  for (const directory of directories) {
644
896
  for (const name of names) {
645
- const candidate = path3.join(directory.replace(/^"|"$/g, ""), name);
897
+ const candidate = path4.join(directory.replace(/^"|"$/g, ""), name);
646
898
  if (existsSync(candidate)) return candidate;
647
899
  }
648
900
  }
@@ -658,13 +910,13 @@ function resolveWindowsShimTarget(shimPath, source) {
658
910
  const matches = [...source.matchAll(/["']([^"']+\.js)["']\s+%\*/gi)];
659
911
  const raw = matches.at(-1)?.[1];
660
912
  if (!raw) return null;
661
- 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}`));
662
914
  }
663
915
  function npxCliPath(shimPath) {
664
916
  const candidates = [
665
- shimPath ? path3.join(path3.dirname(shimPath), "node_modules", "npm", "bin", "npx-cli.js") : "",
666
- path3.join(path3.dirname(process.execPath), "node_modules", "npm", "bin", "npx-cli.js"),
667
- 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") : ""
668
920
  ];
669
921
  return candidates.find((candidate) => candidate && existsSync(candidate)) ?? null;
670
922
  }
@@ -707,28 +959,28 @@ async function runMcpBin(bin, extraArgs = []) {
707
959
  }
708
960
 
709
961
  // src/jenkins-config.ts
710
- import fs3 from "fs";
711
- import os2 from "os";
712
- import path4 from "path";
713
- var CONFIG_DIR = path4.join(os2.homedir(), ".mimi-seed");
714
- var JENKINS_PATH = path4.join(CONFIG_DIR, "jenkins.json");
715
- var LEGACY_PATH = path4.join(CONFIG_DIR, "config.json");
716
- 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()) {
717
969
  try {
718
- const p = path4.join(home, ".mimi-seed", "jenkins.json");
719
- 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"));
720
972
  } catch {
721
973
  return null;
722
974
  }
723
975
  }
724
- function migrateLegacyJenkins(home = os2.homedir()) {
725
- const dir = path4.join(home, ".mimi-seed");
726
- const jenkinsPath = path4.join(dir, "jenkins.json");
727
- const legacyPath = path4.join(dir, "config.json");
728
- 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;
729
981
  let legacy;
730
982
  try {
731
- legacy = JSON.parse(fs3.readFileSync(legacyPath, "utf-8"));
983
+ legacy = JSON.parse(fs4.readFileSync(legacyPath, "utf-8"));
732
984
  } catch {
733
985
  return false;
734
986
  }
@@ -742,33 +994,64 @@ function migrateLegacyJenkins(home = os2.homedir()) {
742
994
  ...j.jobAndroid ? { jobAndroid: j.jobAndroid } : {},
743
995
  ...j.jobIos ? { jobIos: j.jobIos } : {}
744
996
  };
745
- fs3.mkdirSync(dir, { recursive: true, mode: 448 });
746
- 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 });
747
999
  delete legacy.jenkins;
748
1000
  const tmp = `${legacyPath}.tmp`;
749
- fs3.writeFileSync(tmp, JSON.stringify(legacy, null, 2), { mode: 384 });
750
- fs3.renameSync(tmp, legacyPath);
1001
+ fs4.writeFileSync(tmp, JSON.stringify(legacy, null, 2), { mode: 384 });
1002
+ fs4.renameSync(tmp, legacyPath);
751
1003
  return true;
752
1004
  }
753
1005
 
754
1006
  // src/detect.ts
755
- import fs4 from "fs/promises";
756
- import path5 from "path";
1007
+ import fs5 from "fs/promises";
1008
+ import path6 from "path";
757
1009
  async function readIfExists(p) {
758
1010
  try {
759
- return await fs4.readFile(p, "utf8");
1011
+ return await fs5.readFile(p, "utf8");
760
1012
  } catch {
761
1013
  return null;
762
1014
  }
763
1015
  }
764
1016
  async function pathExists(p) {
765
1017
  try {
766
- await fs4.access(p);
1018
+ await fs5.access(p);
767
1019
  return true;
768
1020
  } catch {
769
1021
  return false;
770
1022
  }
771
1023
  }
1024
+ async function importedJsonObjects(configPath, text, root) {
1025
+ const result = /* @__PURE__ */ new Map();
1026
+ const imports = [
1027
+ ...text.matchAll(/\bimport\s+([A-Za-z_$][\w$]*)\s+from\s+["']([^"']+\.json)["']/g),
1028
+ ...text.matchAll(/\bconst\s+([A-Za-z_$][\w$]*)\s*=\s*require\(\s*["']([^"']+\.json)["']\s*\)/g)
1029
+ ];
1030
+ for (const match of imports) {
1031
+ if (!match[2].startsWith(".")) 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;
1035
+ const jsonText = await readIfExists(candidate);
1036
+ if (!jsonText) continue;
1037
+ try {
1038
+ const json = JSON.parse(jsonText);
1039
+ if (json && typeof json === "object" && !Array.isArray(json)) {
1040
+ result.set(match[1], json);
1041
+ }
1042
+ } catch {
1043
+ }
1044
+ }
1045
+ return result;
1046
+ }
1047
+ function importedMember(text, block, field, imports) {
1048
+ const match = new RegExp(
1049
+ `\\b${block}\\s*:\\s*\\{[\\s\\S]{0,5000}?\\b${field}\\s*:\\s*([A-Za-z_$][\\w$]*)\\.([A-Za-z_$][\\w$]*)`
1050
+ ).exec(text);
1051
+ if (!match) return void 0;
1052
+ const value = imports.get(match[1])?.[match[2]];
1053
+ return typeof value === "string" ? value : void 0;
1054
+ }
772
1055
  async function walk(root, match, maxDepth = 5) {
773
1056
  const found = [];
774
1057
  const skipDirs = /* @__PURE__ */ new Set([
@@ -785,16 +1068,16 @@ async function walk(root, match, maxDepth = 5) {
785
1068
  if (depth > maxDepth) return;
786
1069
  let entries;
787
1070
  try {
788
- entries = await fs4.readdir(dir, { withFileTypes: true });
1071
+ entries = await fs5.readdir(dir, { withFileTypes: true });
789
1072
  } catch {
790
1073
  return;
791
1074
  }
792
1075
  for (const e of entries) {
793
1076
  if (e.isDirectory()) {
794
1077
  if (skipDirs.has(e.name)) continue;
795
- await visit(path5.join(dir, e.name), depth + 1);
1078
+ await visit(path6.join(dir, e.name), depth + 1);
796
1079
  } else if (e.isFile() && match(e.name)) {
797
- found.push(path5.join(dir, e.name));
1080
+ found.push(path6.join(dir, e.name));
798
1081
  }
799
1082
  }
800
1083
  }
@@ -804,7 +1087,7 @@ async function walk(root, match, maxDepth = 5) {
804
1087
  async function detectHints(cwd) {
805
1088
  const hints = [];
806
1089
  for (const fname of ["app.json", "app.config.json"]) {
807
- const txt = await readIfExists(path5.join(cwd, fname));
1090
+ const txt = await readIfExists(path6.join(cwd, fname));
808
1091
  if (!txt) continue;
809
1092
  try {
810
1093
  const json = JSON.parse(txt);
@@ -823,6 +1106,17 @@ async function detectHints(cwd) {
823
1106
  } catch {
824
1107
  }
825
1108
  }
1109
+ for (const fname of ["app.config.js", "app.config.cjs", "app.config.mjs", "app.config.ts"]) {
1110
+ const configPath = path6.join(cwd, fname);
1111
+ const txt = await readIfExists(configPath);
1112
+ if (!txt) continue;
1113
+ const imports = await importedJsonObjects(configPath, txt, cwd);
1114
+ const pkg = txt.match(/\bandroid\s*:\s*\{[\s\S]{0,5000}?\bpackage\s*:\s*["']([^"']+)["']/)?.[1] ?? importedMember(txt, "android", "package", imports);
1115
+ const bid = txt.match(/\bios\s*:\s*\{[\s\S]{0,5000}?\bbundleIdentifier\s*:\s*["']([^"']+)["']/)?.[1] ?? importedMember(txt, "ios", "bundleIdentifier", imports);
1116
+ if (pkg || bid) {
1117
+ hints.push({ packageName: pkg, bundleId: bid, source: [fname] });
1118
+ }
1119
+ }
826
1120
  const gradleFiles = await walk(
827
1121
  cwd,
828
1122
  (n) => n === "build.gradle" || n === "build.gradle.kts",
@@ -835,7 +1129,7 @@ async function detectHints(cwd) {
835
1129
  if (m?.[1]) {
836
1130
  const pkg = m[1];
837
1131
  if (!hints.some((h) => h.packageName === pkg)) {
838
- hints.push({ packageName: pkg, source: [path5.relative(cwd, f)] });
1132
+ hints.push({ packageName: pkg, source: [path6.relative(cwd, f)] });
839
1133
  }
840
1134
  }
841
1135
  }
@@ -850,7 +1144,7 @@ async function detectHints(cwd) {
850
1144
  const bid = m[1];
851
1145
  if (bid.includes("$(PRODUCT_BUNDLE_IDENTIFIER)")) continue;
852
1146
  if (!hints.some((h) => h.bundleId === bid)) {
853
- hints.push({ bundleId: bid, source: [path5.relative(cwd, f)] });
1147
+ hints.push({ bundleId: bid, source: [path6.relative(cwd, f)] });
854
1148
  }
855
1149
  }
856
1150
  }
@@ -863,11 +1157,39 @@ async function detectHints(cwd) {
863
1157
  const bid = m[1].trim().replace(/^["']|["']$/g, "");
864
1158
  if (!bid || bid.includes("$")) continue;
865
1159
  if (!hints.some((h) => h.bundleId === bid)) {
866
- hints.push({ bundleId: bid, source: [path5.relative(cwd, f)] });
1160
+ hints.push({ bundleId: bid, source: [path6.relative(cwd, f)] });
1161
+ }
1162
+ }
1163
+ }
1164
+ const unitySettings = path6.join(cwd, "ProjectSettings", "ProjectSettings.asset");
1165
+ const unityText = await readIfExists(unitySettings);
1166
+ if (unityText) {
1167
+ const lines = unityText.split(/\r?\n/);
1168
+ const identifierStart = lines.findIndex((line) => /^\s*applicationIdentifier:\s*$/.test(line));
1169
+ const baseIndent = identifierStart >= 0 ? lines[identifierStart].match(/^\s*/)?.[0].length ?? 0 : 0;
1170
+ let packageName;
1171
+ let bundleId;
1172
+ if (identifierStart >= 0) {
1173
+ for (const line of lines.slice(identifierStart + 1)) {
1174
+ if (!line.trim()) continue;
1175
+ const indent = line.match(/^\s*/)?.[0].length ?? 0;
1176
+ if (indent <= baseIndent) break;
1177
+ const entry = line.match(/^\s+(Android|iPhone|iOS):\s*([^\s#]+)\s*$/);
1178
+ if (entry?.[1] === "Android") packageName = entry[2];
1179
+ if (entry && entry[1] !== "Android") bundleId = entry[2];
867
1180
  }
868
1181
  }
1182
+ const name = unityText.match(/^\s*productName:\s*(.+?)\s*$/m)?.[1];
1183
+ if (packageName || bundleId) {
1184
+ hints.push({
1185
+ name,
1186
+ packageName,
1187
+ bundleId,
1188
+ source: [path6.relative(cwd, unitySettings)]
1189
+ });
1190
+ }
869
1191
  }
870
- const pkgJson = await readIfExists(path5.join(cwd, "package.json"));
1192
+ const pkgJson = await readIfExists(path6.join(cwd, "package.json"));
871
1193
  if (pkgJson) {
872
1194
  try {
873
1195
  const json = JSON.parse(pkgJson);
@@ -903,7 +1225,7 @@ async function detectHints(cwd) {
903
1225
  return merged.filter((h) => h.packageName || h.bundleId);
904
1226
  }
905
1227
  async function hasAnyProjectSignal(cwd) {
906
- 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"));
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"));
907
1229
  }
908
1230
 
909
1231
  // src/deploy.ts
@@ -911,28 +1233,28 @@ import kleur from "kleur";
911
1233
  import * as readline from "readline";
912
1234
 
913
1235
  // src/config.ts
914
- import fs5 from "fs/promises";
915
- import path6 from "path";
916
- import os3 from "os";
917
- var CONFIG_DIR2 = path6.join(os3.homedir(), ".mimi-seed");
918
- 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");
919
1241
  async function readConfig() {
920
1242
  try {
921
- const txt = await fs5.readFile(CONFIG_PATH, "utf8");
1243
+ const txt = await fs6.readFile(CONFIG_PATH, "utf8");
922
1244
  return JSON.parse(txt);
923
1245
  } catch {
924
1246
  return null;
925
1247
  }
926
1248
  }
927
1249
  async function writeConfig(cfg) {
928
- await fs5.mkdir(CONFIG_DIR2, { recursive: true });
929
- 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));
930
1252
  if (process.platform !== "win32") {
931
- await fs5.chmod(CONFIG_PATH, 384);
1253
+ await fs6.chmod(CONFIG_PATH, 384);
932
1254
  }
933
1255
  }
934
1256
  async function deleteConfig() {
935
- await fs5.rm(CONFIG_PATH, { force: true });
1257
+ await fs6.rm(CONFIG_PATH, { force: true });
936
1258
  }
937
1259
  var CONFIG_LOCATION = CONFIG_PATH;
938
1260
  async function getEffectiveConfig() {
@@ -950,12 +1272,79 @@ async function getEffectiveConfig() {
950
1272
  return readConfig();
951
1273
  }
952
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
+
953
1342
  // src/ci-providers.ts
954
- import fs6 from "fs";
955
- import path7 from "path";
956
- import os4 from "os";
957
- var CI_CONFIG_PATH = path7.join(os4.homedir(), ".mimi-seed", "ci.json");
958
- 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(
959
1348
  {
960
1349
  badToken: (provider, status) => `${provider} ${status} \u2014 \uD1A0\uD070\uC774 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC544`,
961
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.`,
@@ -972,7 +1361,7 @@ var M = catalog(
972
1361
  );
973
1362
  function loadCiProviderConfig() {
974
1363
  try {
975
- const cfg = JSON.parse(fs6.readFileSync(CI_CONFIG_PATH, "utf-8"));
1364
+ const cfg = JSON.parse(fs8.readFileSync(CI_CONFIG_PATH, "utf-8"));
976
1365
  const host = normalizeHost(cfg.host);
977
1366
  return host ? { ...cfg, host } : { ...cfg, host: void 0 };
978
1367
  } catch {
@@ -986,15 +1375,15 @@ function normalizeHost(host) {
986
1375
  return withScheme.replace(/\/+$/, "");
987
1376
  }
988
1377
  function saveCiProviderConfig(cfg) {
989
- const dir = path7.dirname(CI_CONFIG_PATH);
990
- if (!fs6.existsSync(dir)) {
991
- 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 });
992
1381
  }
993
1382
  const normalized = { ...cfg, host: normalizeHost(cfg.host) };
994
1383
  if (!normalized.host) delete normalized.host;
995
- fs6.writeFileSync(CI_CONFIG_PATH, JSON.stringify(normalized, null, 2));
1384
+ fs8.writeFileSync(CI_CONFIG_PATH, JSON.stringify(normalized, null, 2));
996
1385
  if (process.platform !== "win32") {
997
- fs6.chmodSync(CI_CONFIG_PATH, 384);
1386
+ fs8.chmodSync(CI_CONFIG_PATH, 384);
998
1387
  }
999
1388
  }
1000
1389
  async function verifyCiToken(cfg) {
@@ -1005,18 +1394,18 @@ async function verifyCiToken(cfg) {
1005
1394
  headers: { Authorization: `Bearer ${probe.token}`, Accept: "application/vnd.github+json" }
1006
1395
  });
1007
1396
  if (!res2.ok) {
1008
- return { ok: false, reason: M().badToken("GitHub", res2.status) };
1397
+ return { ok: false, reason: M2().badToken("GitHub", res2.status) };
1009
1398
  }
1010
1399
  const user2 = await res2.json();
1011
1400
  const scopes = res2.headers.get("x-oauth-scopes");
1012
1401
  if (scopes !== null && !scopes.split(/,\s*/).filter(Boolean).includes("workflow")) {
1013
- return { ok: false, reason: M().noScope(scopes) };
1402
+ return { ok: false, reason: M2().noScope(scopes) };
1014
1403
  }
1015
1404
  return { ok: true, login: user2.login };
1016
1405
  }
1017
1406
  const res = await fetch(`${glBase(probe)}/user`, { headers: { "PRIVATE-TOKEN": probe.token } });
1018
1407
  if (!res.ok) {
1019
- return { ok: false, reason: M().badToken("GitLab", res.status) };
1408
+ return { ok: false, reason: M2().badToken("GitLab", res.status) };
1020
1409
  }
1021
1410
  const user = await res.json();
1022
1411
  return { ok: true, login: user.username };
@@ -1074,12 +1463,12 @@ async function ghPollRun(cfg, runId, onTick, timeoutMs = 30 * 60 * 1e3, interval
1074
1463
  );
1075
1464
  } catch {
1076
1465
  consecutiveErrors++;
1077
- if (consecutiveErrors >= 3) throw new Error(M().pollFailed("GitHub"));
1466
+ if (consecutiveErrors >= 3) throw new Error(M2().pollFailed("GitHub"));
1078
1467
  continue;
1079
1468
  }
1080
1469
  if (!res.ok) {
1081
1470
  consecutiveErrors++;
1082
- if (consecutiveErrors >= 3) throw new Error(M().pollFailedHttp("GitHub", res.status));
1471
+ if (consecutiveErrors >= 3) throw new Error(M2().pollFailedHttp("GitHub", res.status));
1083
1472
  continue;
1084
1473
  }
1085
1474
  consecutiveErrors = 0;
@@ -1125,12 +1514,12 @@ async function glPollPipeline(cfg, pipelineId, onTick, timeoutMs = 30 * 60 * 1e3
1125
1514
  );
1126
1515
  } catch {
1127
1516
  consecutiveErrors++;
1128
- if (consecutiveErrors >= 3) throw new Error(M().pollFailed("GitLab"));
1517
+ if (consecutiveErrors >= 3) throw new Error(M2().pollFailed("GitLab"));
1129
1518
  continue;
1130
1519
  }
1131
1520
  if (!res.ok) {
1132
1521
  consecutiveErrors++;
1133
- if (consecutiveErrors >= 3) throw new Error(M().pollFailedHttp("GitLab", res.status));
1522
+ if (consecutiveErrors >= 3) throw new Error(M2().pollFailedHttp("GitLab", res.status));
1134
1523
  continue;
1135
1524
  }
1136
1525
  consecutiveErrors = 0;
@@ -1156,8 +1545,15 @@ var PHASE_ICON = {
1156
1545
  function log(msg) {
1157
1546
  process.stdout.write(msg + "\n");
1158
1547
  }
1159
- var M2 = catalog(
1548
+ var M3 = catalog(
1160
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.",
1161
1557
  // Jenkins / 빌드
1162
1558
  jenkinsTriggerFailed: (status, body) => `Jenkins \uD2B8\uB9AC\uAC70 \uC2E4\uD328 ${status}: ${body}`,
1163
1559
  buildStatusFailed: (status) => `\uBE4C\uB4DC \uC0C1\uD0DC \uC870\uD68C \uC2E4\uD328 ${status}`,
@@ -1207,7 +1603,7 @@ var M2 = catalog(
1207
1603
  buildStarted: (n) => ` \uBE4C\uB4DC #${n} \uC2DC\uC791\uB428. \uC644\uB8CC \uB300\uAE30 \uC911...`,
1208
1604
  buildFailed: (result) => `\uBE4C\uB4DC \uC2E4\uD328: ${result}`,
1209
1605
  jenkinsLink: (url) => ` Jenkins: ${url}`,
1210
- 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.",
1211
1607
  noProviderConfig: (kind) => `${kind} \uC124\uC815\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. setup-${kind} \uC2E4\uD589.`,
1212
1608
  versionCodeUnsuitable: (kind, buildId) => `\u2717 versionCode \uBBF8\uC9C0\uC815 (${kind} run_id ${buildId}\uB294 versionCode\uB85C \uBD80\uC801\uD569)`,
1213
1609
  recommendation: " \uAD8C\uC7A5 \uC0AC\uD56D:",
@@ -1226,6 +1622,13 @@ var M2 = catalog(
1226
1622
  done: "\uC644\uB8CC. Play Console\uC5D0\uC11C \uBC30\uD3EC \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uC138\uC694."
1227
1623
  },
1228
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.",
1229
1632
  // Jenkins / build
1230
1633
  jenkinsTriggerFailed: (status, body) => `Jenkins trigger failed ${status}: ${body}`,
1231
1634
  buildStatusFailed: (status) => `Failed to fetch build status ${status}`,
@@ -1275,7 +1678,7 @@ var M2 = catalog(
1275
1678
  buildStarted: (n) => ` Build #${n} started. Waiting for it to finish...`,
1276
1679
  buildFailed: (result) => `Build failed: ${result}`,
1277
1680
  jenkinsLink: (url) => ` Jenkins: ${url}`,
1278
- versionCodeFromBuild: (n) => ` versionCode = buildNumber (${n})`,
1681
+ explicitApproval: "Real CI execution and deployment require explicit --yes approval.",
1279
1682
  noProviderConfig: (kind) => `${kind} is not configured. Run setup-${kind}.`,
1280
1683
  versionCodeUnsuitable: (kind, buildId) => `\u2717 No versionCode given (${kind} run_id ${buildId} is not usable as a versionCode)`,
1281
1684
  recommendation: " Recommended:",
@@ -1300,10 +1703,16 @@ function jenkinsHeaders(cfg) {
1300
1703
  }
1301
1704
  async function triggerBuild(cfg, jobName, params) {
1302
1705
  const qs = new URLSearchParams(params).toString();
1303
- const url = `${cfg.url}/job/${encodeURIComponent(jobName)}/buildWithParameters?${qs}`;
1304
- 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
+ });
1305
1714
  if (!res.ok) {
1306
- throw new Error(M2().jenkinsTriggerFailed(res.status, await res.text()));
1715
+ throw new Error(M3().jenkinsTriggerFailed(res.status, await res.text()));
1307
1716
  }
1308
1717
  const location = res.headers.get("Location") ?? "";
1309
1718
  const match = location.match(/\/queue\/item\/(\d+)\//);
@@ -1311,15 +1720,15 @@ async function triggerBuild(cfg, jobName, params) {
1311
1720
  }
1312
1721
  async function getQueueBuildNumber(cfg, queueItemId) {
1313
1722
  const url = `${cfg.url}/queue/item/${queueItemId}/api/json`;
1314
- const res = await fetch(url, { headers: jenkinsHeaders(cfg) });
1723
+ const res = await fetch(url, { headers: jenkinsHeaders(cfg), redirect: "error", signal: AbortSignal.timeout(3e4) });
1315
1724
  if (!res.ok) return null;
1316
1725
  const data = await res.json();
1317
1726
  return data.executable?.number ?? null;
1318
1727
  }
1319
1728
  async function getBuildStatus(cfg, jobName, buildNumber) {
1320
- const url = `${cfg.url}/job/${encodeURIComponent(jobName)}/${buildNumber}/api/json`;
1321
- const res = await fetch(url, { headers: jenkinsHeaders(cfg) });
1322
- 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));
1323
1732
  const data = await res.json();
1324
1733
  return {
1325
1734
  building: data.building ?? true,
@@ -1340,18 +1749,18 @@ async function pollBuildComplete(cfg, jobName, buildNumber, timeoutMs = 30 * 60
1340
1749
  consecutiveErrors = 0;
1341
1750
  } catch {
1342
1751
  consecutiveErrors++;
1343
- if (consecutiveErrors >= 3) throw new Error(M2().jenkinsConnErrorFatal);
1344
- 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)} `);
1345
1754
  continue;
1346
1755
  }
1347
1756
  dots = (dots + 1) % 4;
1348
- process.stdout.write(`\r \u23F3 ${M2().buildRunning}${".".repeat(dots + 1)} `);
1757
+ process.stdout.write(`\r \u23F3 ${M3().buildRunning}${".".repeat(dots + 1)} `);
1349
1758
  if (!status.building) {
1350
1759
  process.stdout.write("\n");
1351
1760
  return status.result ?? "FAILURE";
1352
1761
  }
1353
1762
  }
1354
- throw new Error(M2().buildTimeout);
1763
+ throw new Error(M3().buildTimeout);
1355
1764
  }
1356
1765
  async function streamDeploy(webBase, token, body) {
1357
1766
  const res = await fetch(`${webBase}/api/deploy`, {
@@ -1364,10 +1773,10 @@ async function streamDeploy(webBase, token, body) {
1364
1773
  });
1365
1774
  if (!res.ok) {
1366
1775
  const text = await res.text().catch(() => "");
1367
- throw new Error(M2().serverDeployFailed(res.status, text.slice(0, 200)));
1776
+ throw new Error(M3().serverDeployFailed(res.status, text.slice(0, 200)));
1368
1777
  }
1369
1778
  const reader = res.body?.getReader();
1370
- if (!reader) throw new Error(M2().noSseStream);
1779
+ if (!reader) throw new Error(M3().noSseStream);
1371
1780
  const decoder = new TextDecoder();
1372
1781
  let buf = "";
1373
1782
  while (true) {
@@ -1391,6 +1800,7 @@ async function streamDeploy(webBase, token, body) {
1391
1800
  }
1392
1801
  }
1393
1802
  }
1803
+ var ANDROID_VERSION_CODE_MAX = 21e8;
1394
1804
  function parseArgs(argv) {
1395
1805
  const args = {
1396
1806
  platform: "android",
@@ -1405,29 +1815,78 @@ function parseArgs(argv) {
1405
1815
  ref: "main"
1406
1816
  };
1407
1817
  for (let i = 0; i < argv.length; i++) {
1408
- if ((argv[i] === "--platform" || argv[i] === "-p") && argv[i + 1]) args.platform = argv[++i];
1409
- if (argv[i] === "--app" && argv[i + 1]) args.appId = argv[++i];
1410
- if (argv[i] === "--version-code" && argv[i + 1]) args.versionCode = Number(argv[++i]);
1411
- if (argv[i] === "--from" && argv[i + 1]) args.fromRef = argv[++i];
1412
- if (argv[i] === "--to" && argv[i + 1]) args.toRef = argv[++i];
1413
- if (argv[i] === "--language" && argv[i + 1]) args.language = argv[++i];
1414
- if (argv[i] === "--dry-run") args.dryRun = true;
1415
- if (argv[i] === "--yes" || argv[i] === "-y") args.yes = true;
1416
- if (argv[i] === "--skip-build") args.skipBuild = true;
1417
- if (argv[i] === "--ci" && argv[i + 1]) args.ci = argv[++i];
1418
- if (argv[i] === "--workflow" && argv[i + 1]) args.workflow = argv[++i];
1419
- if (argv[i] === "--ref" && argv[i + 1]) args.ref = argv[++i];
1420
- if (argv[i] === "setup-jenkins") args.setupJenkins = true;
1421
- if (argv[i] === "setup-github") args.setupGithub = true;
1422
- 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
+ }
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);
1423
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);
1424
1883
  return args;
1425
1884
  }
1426
1885
  async function promptGitProviderSetup(provider) {
1427
1886
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1428
1887
  const ask2 = (q) => new Promise((resolve) => rl.question(q, (a) => resolve(a.trim())));
1429
1888
  const isGh = provider === "github";
1430
- const m = M2();
1889
+ const m = M3();
1431
1890
  log(kleur.bold(isGh ? m.githubSetupTitle : m.gitlabSetupTitle));
1432
1891
  const tokenLabel = isGh ? m.githubTokenPrompt : m.gitlabTokenPrompt;
1433
1892
  const token = await ask2(tokenLabel);
@@ -1448,36 +1907,36 @@ function resolveCi(ciOption, jenkins, ciProvider) {
1448
1907
  if (ciOption !== "auto") return ciOption;
1449
1908
  if (jenkins?.url && jenkins.token) return "jenkins";
1450
1909
  if (ciProvider) return ciProvider.provider;
1451
- throw new Error(M2().noCiConfig);
1910
+ throw new Error(M3().noCiConfig);
1452
1911
  }
1453
1912
  async function runGitProviderBuild(cfg, args) {
1454
1913
  let runUrl;
1455
1914
  let runId;
1456
1915
  if (cfg.provider === "github") {
1457
1916
  if (!args.workflow) {
1458
- throw new Error(M2().workflowRequired);
1917
+ throw new Error(M3().workflowRequired);
1459
1918
  }
1460
- log(M2().ghTrigger(kleur.cyan(args.workflow), args.ref));
1919
+ log(M3().ghTrigger(kleur.cyan(args.workflow), args.ref));
1461
1920
  const inputs = {};
1462
1921
  if (args.appId) inputs.MIMI_APP_ID = args.appId;
1463
1922
  inputs.PLATFORM = args.platform;
1464
1923
  const result2 = await ghTriggerWorkflow(cfg, args.workflow, args.ref, inputs);
1465
1924
  if (!result2) {
1466
- throw new Error(M2().ghRunIdFailed);
1925
+ throw new Error(M3().ghRunIdFailed);
1467
1926
  }
1468
1927
  runId = result2.runId;
1469
1928
  runUrl = result2.url;
1470
- log(kleur.dim(M2().ghRunId(runId, runUrl)));
1929
+ log(kleur.dim(M3().ghRunId(runId, runUrl)));
1471
1930
  } else {
1472
- log(M2().glTrigger(args.ref));
1931
+ log(M3().glTrigger(args.ref));
1473
1932
  const variables = { PLATFORM: args.platform };
1474
1933
  if (args.appId) variables.MIMI_APP_ID = args.appId;
1475
1934
  const result2 = await glTriggerPipeline(cfg, args.ref, variables);
1476
1935
  runId = result2.pipelineId;
1477
1936
  runUrl = result2.url;
1478
- log(kleur.dim(M2().glPipelineId(runId, runUrl)));
1937
+ log(kleur.dim(M3().glPipelineId(runId, runUrl)));
1479
1938
  }
1480
- log(M2().waitingForCompletion);
1939
+ log(M3().waitingForCompletion);
1481
1940
  let dots = 0;
1482
1941
  const onTick = (status) => {
1483
1942
  dots = (dots + 1) % 4;
@@ -1486,19 +1945,36 @@ async function runGitProviderBuild(cfg, args) {
1486
1945
  const result = cfg.provider === "github" ? await ghPollRun(cfg, runId, onTick) : await glPollPipeline(cfg, runId, onTick);
1487
1946
  process.stdout.write("\n");
1488
1947
  if (result === "success") {
1489
- log(kleur.green(M2().buildSucceeded(runId)));
1948
+ log(kleur.green(M3().buildSucceeded(runId)));
1490
1949
  return runId;
1491
1950
  }
1492
- log(kleur.red(M2().buildEnded(result)));
1951
+ log(kleur.red(M3().buildEnded(result)));
1493
1952
  log(kleur.dim(` ${runUrl}`));
1494
- log(kleur.dim(M2().alreadyBuiltHint("<N>", args.platform)));
1953
+ log(kleur.dim(M3().alreadyBuiltHint("<N>", args.platform)));
1495
1954
  process.exit(1);
1496
1955
  }
1497
1956
  async function cmdDeploy(argv) {
1498
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
+ }
1499
1975
  const cfg = await getEffectiveConfig();
1500
1976
  if (!cfg) {
1501
- log(kleur.red(M2().noAccount));
1977
+ log(kleur.red(M3().noAccount));
1502
1978
  process.exit(1);
1503
1979
  }
1504
1980
  if (args.setupJenkins) {
@@ -1509,48 +1985,47 @@ async function cmdDeploy(argv) {
1509
1985
  if (args.setupGithub) {
1510
1986
  const ciCfg = await promptGitProviderSetup("github");
1511
1987
  saveCiProviderConfig(ciCfg);
1512
- log(kleur.green(M2().githubSaved));
1988
+ log(kleur.green(M3().githubSaved));
1513
1989
  return;
1514
1990
  }
1515
1991
  if (args.setupGitlab) {
1516
1992
  const ciCfg = await promptGitProviderSetup("gitlab");
1517
1993
  saveCiProviderConfig(ciCfg);
1518
- log(kleur.green(M2().gitlabSaved));
1994
+ log(kleur.green(M3().gitlabSaved));
1519
1995
  return;
1520
1996
  }
1521
- log(kleur.bold(M2().title(args.platform)));
1522
- 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));
1523
2002
  log("");
1524
- let versionCode = args.versionCode;
2003
+ const versionCode = args.versionCode;
2004
+ let deployBuildNumber;
1525
2005
  if (!args.skipBuild) {
1526
2006
  const ciProvider = loadCiProviderConfig();
1527
2007
  migrateLegacyJenkins();
1528
2008
  const jenkinsCfg = loadJenkinsConfig() ?? void 0;
1529
2009
  const kind = resolveCi(args.ci, jenkinsCfg, ciProvider);
1530
- log(kleur.dim(M2().ciLine(kind)));
2010
+ log(kleur.dim(M3().ciLine(kind)));
1531
2011
  if (kind === "jenkins") {
1532
2012
  if (!jenkinsCfg?.url || !jenkinsCfg?.token) {
1533
- log(kleur.yellow(M2().noJenkinsConfig));
2013
+ log(kleur.yellow(M3().noJenkinsConfig));
1534
2014
  process.exit(1);
1535
2015
  }
1536
2016
  const jenkins = jenkinsCfg;
1537
- const jobName = args.platform === "android" ? jenkins.jobAndroid : jenkins.jobIos;
1538
- if (!jobName) {
1539
- log(kleur.red(M2().noJenkinsJob(args.platform)));
1540
- process.exit(1);
1541
- }
1542
- log(M2().jenkinsTrigger(kleur.cyan(jobName)));
1543
- const buildParams = {};
1544
- 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);
1545
2020
  const queueItemId = await triggerBuild(jenkins, jobName, buildParams);
1546
2021
  if (!queueItemId) {
1547
- log(kleur.yellow(M2().noQueueItem));
2022
+ log(kleur.yellow(M3().noQueueItem));
1548
2023
  } else {
1549
- log(kleur.dim(M2().queueItem(queueItemId)));
2024
+ log(kleur.dim(M3().queueItem(queueItemId)));
1550
2025
  }
1551
2026
  let buildNumber = null;
1552
2027
  if (queueItemId) {
1553
- log(M2().waitingForBuildNumber);
2028
+ log(M3().waitingForBuildNumber);
1554
2029
  for (let i = 0; i < 6; i++) {
1555
2030
  await new Promise((r) => setTimeout(r, 5e3));
1556
2031
  buildNumber = await getQueueBuildNumber(jenkins, queueItemId).catch(() => null);
@@ -1558,79 +2033,45 @@ async function cmdDeploy(argv) {
1558
2033
  }
1559
2034
  }
1560
2035
  if (!buildNumber) {
1561
- log(kleur.yellow(M2().noBuildNumber));
2036
+ log(kleur.yellow(M3().noBuildNumber));
1562
2037
  process.exit(1);
1563
2038
  }
1564
- log(M2().buildStarted(buildNumber));
2039
+ log(M3().buildStarted(buildNumber));
1565
2040
  const result = await pollBuildComplete(jenkins, jobName, buildNumber);
1566
2041
  if (result !== "SUCCESS") {
1567
- log(kleur.red(M2().buildFailed(result)));
1568
- log(kleur.dim(M2().jenkinsLink(`${jenkins.url}/job/${encodeURIComponent(jobName)}/${buildNumber}/`)));
1569
- 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)));
1570
2045
  process.exit(1);
1571
2046
  }
1572
- log(kleur.green(M2().buildSucceeded(buildNumber)));
2047
+ log(kleur.green(M3().buildSucceeded(buildNumber)));
2048
+ deployBuildNumber = buildNumber;
1573
2049
  if (!versionCode) {
1574
- versionCode = buildNumber;
1575
- log(kleur.dim(M2().versionCodeFromBuild(versionCode)));
2050
+ throw new Error(M3().versionCodeUnsuitable("Jenkins", buildNumber) + "\n" + M3().recommendationNext);
1576
2051
  }
1577
2052
  } else {
1578
2053
  if (!ciProvider) {
1579
- log(kleur.red(M2().noProviderConfig(kind)));
2054
+ log(kleur.red(M3().noProviderConfig(kind)));
1580
2055
  process.exit(1);
1581
2056
  }
1582
2057
  const buildId = await runGitProviderBuild(ciProvider, args);
1583
2058
  if (!versionCode) {
1584
- log(kleur.red(M2().versionCodeUnsuitable(kind, buildId)));
1585
- log(kleur.dim(M2().recommendation));
1586
- log(kleur.dim(M2().recommendationCi));
1587
- 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));
1588
2063
  process.exit(1);
1589
2064
  }
1590
2065
  }
1591
2066
  }
1592
2067
  if (!versionCode) {
1593
- log(kleur.red(M2().versionCodeUnknown));
2068
+ log(kleur.red(M3().versionCodeUnknown));
1594
2069
  process.exit(1);
1595
2070
  }
1596
- let appId = args.appId;
1597
- if (!appId) {
1598
- const { mcpCall } = await import("./mcp-client-DZNDKAGQ.js");
1599
- const r = await mcpCall(cfg.endpoint, cfg.token, "list_apps", {});
1600
- if (!r.isError) {
1601
- try {
1602
- const apps = JSON.parse(r.text);
1603
- if (apps.length > 0) {
1604
- appId = apps[0].id;
1605
- log(kleur.dim(M2().appLine(apps[0].name, appId)));
1606
- }
1607
- } catch {
1608
- }
1609
- }
1610
- }
1611
- if (!appId) {
1612
- log(kleur.red(M2().noAppId));
1613
- process.exit(1);
1614
- }
1615
- const needsConfirm = !args.dryRun && !args.yes && process.stdout.isTTY && !process.env.MIMI_SEED_TOKEN;
1616
- if (needsConfirm) {
1617
- const target = args.platform === "ios" ? M2().targetIos : M2().targetAndroid;
1618
- log("");
1619
- log(kleur.yellow(M2().realDeploy(args.platform, versionCode, target)));
1620
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1621
- const answer = await new Promise(
1622
- (resolve) => rl.question(kleur.bold(M2().confirmPrompt), (a) => resolve(a.trim().toLowerCase()))
1623
- );
1624
- rl.close();
1625
- if (answer !== "y" && answer !== "yes") {
1626
- log(kleur.dim(M2().confirmCancelled));
1627
- return;
1628
- }
1629
- }
2071
+ const appId = args.appId;
1630
2072
  log("");
1631
- log(M2().pipelineStarting);
2073
+ log(M3().pipelineStarting);
1632
2074
  log("");
1633
- const deployBuildNumber = !args.skipBuild ? versionCode : void 0;
1634
2075
  await streamDeploy(cfg.webBase, cfg.token, {
1635
2076
  appId,
1636
2077
  platform: args.platform,
@@ -1642,7 +2083,7 @@ async function cmdDeploy(argv) {
1642
2083
  dryRun: args.dryRun
1643
2084
  });
1644
2085
  log("");
1645
- log(kleur.bold(M2().done));
2086
+ log(kleur.bold(M3().done));
1646
2087
  log(kleur.dim(" https://play.google.com/console/developers"));
1647
2088
  }
1648
2089
 
@@ -1778,7 +2219,7 @@ async function connectOne(spec) {
1778
2219
  }
1779
2220
  async function cmdSetup(argv) {
1780
2221
  const opts = parseSetupArgs(argv);
1781
- const home = os5.homedir();
2222
+ const home = os6.homedir();
1782
2223
  migrateLegacyJenkins(home);
1783
2224
  if (resolveMode(opts, process.env, process.stdin.isTTY) === "interactive") {
1784
2225
  await ensureLangChosen();
@@ -1881,6 +2322,11 @@ async function cmdSetup(argv) {
1881
2322
  export {
1882
2323
  detectHints,
1883
2324
  hasAnyProjectSignal,
2325
+ writeSettings,
2326
+ isLang,
2327
+ resolveLang,
2328
+ catalog,
2329
+ t,
1884
2330
  writeConfig,
1885
2331
  deleteConfig,
1886
2332
  CONFIG_LOCATION,