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
|
|
6
|
+
import os6 from "os";
|
|
14
7
|
|
|
15
8
|
// src/credentials.ts
|
|
16
|
-
import
|
|
17
|
-
import
|
|
18
|
-
import
|
|
9
|
+
import fs3 from "fs";
|
|
10
|
+
import os2 from "os";
|
|
11
|
+
import path3 from "path";
|
|
19
12
|
|
|
20
|
-
// src/
|
|
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 =
|
|
71
|
+
let dir = path2.resolve(startDir);
|
|
44
72
|
for (let i = 0; i <= maxDepth; i++) {
|
|
45
|
-
const candidate =
|
|
46
|
-
if (
|
|
73
|
+
const candidate = path2.join(dir, MANIFEST_FILENAME);
|
|
74
|
+
if (fs2.existsSync(candidate)) {
|
|
47
75
|
try {
|
|
48
|
-
const obj = JSON.parse(
|
|
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 =
|
|
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
|
|
104
|
+
return path3.join(home, ".mimi-seed");
|
|
77
105
|
}
|
|
78
106
|
function hasFile(home, name) {
|
|
79
|
-
return
|
|
107
|
+
return fs3.existsSync(path3.join(credDir(home), name));
|
|
80
108
|
}
|
|
81
109
|
function anyFileStarting(home, prefix) {
|
|
82
110
|
try {
|
|
83
|
-
return
|
|
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(
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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(
|
|
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 =
|
|
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
|
|
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 ?
|
|
673
|
-
|
|
674
|
-
process.env.npm_execpath ?
|
|
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
|
|
718
|
-
import
|
|
719
|
-
import
|
|
720
|
-
var CONFIG_DIR =
|
|
721
|
-
var JENKINS_PATH =
|
|
722
|
-
var LEGACY_PATH =
|
|
723
|
-
function loadJenkinsConfig(home =
|
|
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 =
|
|
726
|
-
return JSON.parse(
|
|
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 =
|
|
732
|
-
const dir =
|
|
733
|
-
const jenkinsPath =
|
|
734
|
-
const legacyPath =
|
|
735
|
-
if (
|
|
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(
|
|
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
|
-
|
|
753
|
-
|
|
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
|
-
|
|
757
|
-
|
|
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
|
|
763
|
-
import
|
|
1007
|
+
import fs5 from "fs/promises";
|
|
1008
|
+
import path6 from "path";
|
|
764
1009
|
async function readIfExists(p) {
|
|
765
1010
|
try {
|
|
766
|
-
return await
|
|
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
|
|
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 =
|
|
788
|
-
const relative =
|
|
789
|
-
if (relative.startsWith(`..${
|
|
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
|
|
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(
|
|
1078
|
+
await visit(path6.join(dir, e.name), depth + 1);
|
|
834
1079
|
} else if (e.isFile() && match(e.name)) {
|
|
835
|
-
found.push(
|
|
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(
|
|
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 =
|
|
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: [
|
|
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: [
|
|
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: [
|
|
1160
|
+
hints.push({ bundleId: bid, source: [path6.relative(cwd, f)] });
|
|
916
1161
|
}
|
|
917
1162
|
}
|
|
918
1163
|
}
|
|
919
|
-
const unitySettings =
|
|
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: [
|
|
1188
|
+
source: [path6.relative(cwd, unitySettings)]
|
|
944
1189
|
});
|
|
945
1190
|
}
|
|
946
1191
|
}
|
|
947
|
-
const pkgJson = await readIfExists(
|
|
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(
|
|
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
|
|
992
|
-
import
|
|
993
|
-
import
|
|
994
|
-
var CONFIG_DIR2 =
|
|
995
|
-
var CONFIG_PATH =
|
|
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
|
|
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
|
|
1006
|
-
await
|
|
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
|
|
1253
|
+
await fs6.chmod(CONFIG_PATH, 384);
|
|
1009
1254
|
}
|
|
1010
1255
|
}
|
|
1011
1256
|
async function deleteConfig() {
|
|
1012
|
-
await
|
|
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
|
|
1032
|
-
import
|
|
1033
|
-
import
|
|
1034
|
-
var CI_CONFIG_PATH =
|
|
1035
|
-
var
|
|
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(
|
|
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 =
|
|
1067
|
-
if (!
|
|
1068
|
-
|
|
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
|
-
|
|
1384
|
+
fs8.writeFileSync(CI_CONFIG_PATH, JSON.stringify(normalized, null, 2));
|
|
1073
1385
|
if (process.platform !== "win32") {
|
|
1074
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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}
|
|
1381
|
-
const res = await fetch(url, {
|
|
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(
|
|
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}
|
|
1398
|
-
const res = await fetch(url, { headers: jenkinsHeaders(cfg) });
|
|
1399
|
-
if (!res.ok) throw new Error(
|
|
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(
|
|
1421
|
-
process.stdout.write(`\r \u26A0 ${
|
|
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 ${
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
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 =
|
|
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(
|
|
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(
|
|
1917
|
+
throw new Error(M3().workflowRequired);
|
|
1536
1918
|
}
|
|
1537
|
-
log(
|
|
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(
|
|
1925
|
+
throw new Error(M3().ghRunIdFailed);
|
|
1544
1926
|
}
|
|
1545
1927
|
runId = result2.runId;
|
|
1546
1928
|
runUrl = result2.url;
|
|
1547
|
-
log(kleur.dim(
|
|
1929
|
+
log(kleur.dim(M3().ghRunId(runId, runUrl)));
|
|
1548
1930
|
} else {
|
|
1549
|
-
log(
|
|
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(
|
|
1937
|
+
log(kleur.dim(M3().glPipelineId(runId, runUrl)));
|
|
1556
1938
|
}
|
|
1557
|
-
log(
|
|
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(
|
|
1948
|
+
log(kleur.green(M3().buildSucceeded(runId)));
|
|
1567
1949
|
return runId;
|
|
1568
1950
|
}
|
|
1569
|
-
log(kleur.red(
|
|
1951
|
+
log(kleur.red(M3().buildEnded(result)));
|
|
1570
1952
|
log(kleur.dim(` ${runUrl}`));
|
|
1571
|
-
log(kleur.dim(
|
|
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(
|
|
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(
|
|
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(
|
|
1994
|
+
log(kleur.green(M3().gitlabSaved));
|
|
1596
1995
|
return;
|
|
1597
1996
|
}
|
|
1598
|
-
|
|
1599
|
-
if (args.
|
|
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
|
-
|
|
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(
|
|
2010
|
+
log(kleur.dim(M3().ciLine(kind)));
|
|
1608
2011
|
if (kind === "jenkins") {
|
|
1609
2012
|
if (!jenkinsCfg?.url || !jenkinsCfg?.token) {
|
|
1610
|
-
log(kleur.yellow(
|
|
2013
|
+
log(kleur.yellow(M3().noJenkinsConfig));
|
|
1611
2014
|
process.exit(1);
|
|
1612
2015
|
}
|
|
1613
2016
|
const jenkins = jenkinsCfg;
|
|
1614
|
-
const
|
|
1615
|
-
|
|
1616
|
-
|
|
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(
|
|
2022
|
+
log(kleur.yellow(M3().noQueueItem));
|
|
1625
2023
|
} else {
|
|
1626
|
-
log(kleur.dim(
|
|
2024
|
+
log(kleur.dim(M3().queueItem(queueItemId)));
|
|
1627
2025
|
}
|
|
1628
2026
|
let buildNumber = null;
|
|
1629
2027
|
if (queueItemId) {
|
|
1630
|
-
log(
|
|
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(
|
|
2036
|
+
log(kleur.yellow(M3().noBuildNumber));
|
|
1639
2037
|
process.exit(1);
|
|
1640
2038
|
}
|
|
1641
|
-
log(
|
|
2039
|
+
log(M3().buildStarted(buildNumber));
|
|
1642
2040
|
const result = await pollBuildComplete(jenkins, jobName, buildNumber);
|
|
1643
2041
|
if (result !== "SUCCESS") {
|
|
1644
|
-
log(kleur.red(
|
|
1645
|
-
log(kleur.dim(
|
|
1646
|
-
log(kleur.dim(
|
|
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(
|
|
2047
|
+
log(kleur.green(M3().buildSucceeded(buildNumber)));
|
|
2048
|
+
deployBuildNumber = buildNumber;
|
|
1650
2049
|
if (!versionCode) {
|
|
1651
|
-
|
|
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(
|
|
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(
|
|
1662
|
-
log(kleur.dim(
|
|
1663
|
-
log(kleur.dim(
|
|
1664
|
-
log(kleur.dim(
|
|
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(
|
|
2068
|
+
log(kleur.red(M3().versionCodeUnknown));
|
|
1671
2069
|
process.exit(1);
|
|
1672
2070
|
}
|
|
1673
|
-
|
|
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(
|
|
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(
|
|
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 =
|
|
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,
|