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
|
|
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
|
}
|
|
@@ -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 ? {
|
|
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 =
|
|
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
|
|
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(
|
|
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 =
|
|
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
|
|
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 ?
|
|
666
|
-
|
|
667
|
-
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") : ""
|
|
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
|
|
711
|
-
import
|
|
712
|
-
import
|
|
713
|
-
var CONFIG_DIR =
|
|
714
|
-
var JENKINS_PATH =
|
|
715
|
-
var LEGACY_PATH =
|
|
716
|
-
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()) {
|
|
717
969
|
try {
|
|
718
|
-
const p =
|
|
719
|
-
return JSON.parse(
|
|
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 =
|
|
725
|
-
const dir =
|
|
726
|
-
const jenkinsPath =
|
|
727
|
-
const legacyPath =
|
|
728
|
-
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;
|
|
729
981
|
let legacy;
|
|
730
982
|
try {
|
|
731
|
-
legacy = JSON.parse(
|
|
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
|
-
|
|
746
|
-
|
|
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
|
-
|
|
750
|
-
|
|
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
|
|
756
|
-
import
|
|
1007
|
+
import fs5 from "fs/promises";
|
|
1008
|
+
import path6 from "path";
|
|
757
1009
|
async function readIfExists(p) {
|
|
758
1010
|
try {
|
|
759
|
-
return await
|
|
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
|
|
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
|
|
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(
|
|
1078
|
+
await visit(path6.join(dir, e.name), depth + 1);
|
|
796
1079
|
} else if (e.isFile() && match(e.name)) {
|
|
797
|
-
found.push(
|
|
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(
|
|
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: [
|
|
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: [
|
|
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: [
|
|
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(
|
|
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(
|
|
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
|
|
915
|
-
import
|
|
916
|
-
import
|
|
917
|
-
var CONFIG_DIR2 =
|
|
918
|
-
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");
|
|
919
1241
|
async function readConfig() {
|
|
920
1242
|
try {
|
|
921
|
-
const txt = await
|
|
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
|
|
929
|
-
await
|
|
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
|
|
1253
|
+
await fs6.chmod(CONFIG_PATH, 384);
|
|
932
1254
|
}
|
|
933
1255
|
}
|
|
934
1256
|
async function deleteConfig() {
|
|
935
|
-
await
|
|
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
|
|
955
|
-
import
|
|
956
|
-
import
|
|
957
|
-
var CI_CONFIG_PATH =
|
|
958
|
-
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(
|
|
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(
|
|
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 =
|
|
990
|
-
if (!
|
|
991
|
-
|
|
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
|
-
|
|
1384
|
+
fs8.writeFileSync(CI_CONFIG_PATH, JSON.stringify(normalized, null, 2));
|
|
996
1385
|
if (process.platform !== "win32") {
|
|
997
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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}
|
|
1304
|
-
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
|
+
});
|
|
1305
1714
|
if (!res.ok) {
|
|
1306
|
-
throw new Error(
|
|
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}
|
|
1321
|
-
const res = await fetch(url, { headers: jenkinsHeaders(cfg) });
|
|
1322
|
-
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));
|
|
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(
|
|
1344
|
-
process.stdout.write(`\r \u26A0 ${
|
|
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 ${
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
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 =
|
|
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(
|
|
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(
|
|
1917
|
+
throw new Error(M3().workflowRequired);
|
|
1459
1918
|
}
|
|
1460
|
-
log(
|
|
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(
|
|
1925
|
+
throw new Error(M3().ghRunIdFailed);
|
|
1467
1926
|
}
|
|
1468
1927
|
runId = result2.runId;
|
|
1469
1928
|
runUrl = result2.url;
|
|
1470
|
-
log(kleur.dim(
|
|
1929
|
+
log(kleur.dim(M3().ghRunId(runId, runUrl)));
|
|
1471
1930
|
} else {
|
|
1472
|
-
log(
|
|
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(
|
|
1937
|
+
log(kleur.dim(M3().glPipelineId(runId, runUrl)));
|
|
1479
1938
|
}
|
|
1480
|
-
log(
|
|
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(
|
|
1948
|
+
log(kleur.green(M3().buildSucceeded(runId)));
|
|
1490
1949
|
return runId;
|
|
1491
1950
|
}
|
|
1492
|
-
log(kleur.red(
|
|
1951
|
+
log(kleur.red(M3().buildEnded(result)));
|
|
1493
1952
|
log(kleur.dim(` ${runUrl}`));
|
|
1494
|
-
log(kleur.dim(
|
|
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(
|
|
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(
|
|
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(
|
|
1994
|
+
log(kleur.green(M3().gitlabSaved));
|
|
1519
1995
|
return;
|
|
1520
1996
|
}
|
|
1521
|
-
|
|
1522
|
-
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));
|
|
1523
2002
|
log("");
|
|
1524
|
-
|
|
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(
|
|
2010
|
+
log(kleur.dim(M3().ciLine(kind)));
|
|
1531
2011
|
if (kind === "jenkins") {
|
|
1532
2012
|
if (!jenkinsCfg?.url || !jenkinsCfg?.token) {
|
|
1533
|
-
log(kleur.yellow(
|
|
2013
|
+
log(kleur.yellow(M3().noJenkinsConfig));
|
|
1534
2014
|
process.exit(1);
|
|
1535
2015
|
}
|
|
1536
2016
|
const jenkins = jenkinsCfg;
|
|
1537
|
-
const
|
|
1538
|
-
|
|
1539
|
-
|
|
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(
|
|
2022
|
+
log(kleur.yellow(M3().noQueueItem));
|
|
1548
2023
|
} else {
|
|
1549
|
-
log(kleur.dim(
|
|
2024
|
+
log(kleur.dim(M3().queueItem(queueItemId)));
|
|
1550
2025
|
}
|
|
1551
2026
|
let buildNumber = null;
|
|
1552
2027
|
if (queueItemId) {
|
|
1553
|
-
log(
|
|
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(
|
|
2036
|
+
log(kleur.yellow(M3().noBuildNumber));
|
|
1562
2037
|
process.exit(1);
|
|
1563
2038
|
}
|
|
1564
|
-
log(
|
|
2039
|
+
log(M3().buildStarted(buildNumber));
|
|
1565
2040
|
const result = await pollBuildComplete(jenkins, jobName, buildNumber);
|
|
1566
2041
|
if (result !== "SUCCESS") {
|
|
1567
|
-
log(kleur.red(
|
|
1568
|
-
log(kleur.dim(
|
|
1569
|
-
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)));
|
|
1570
2045
|
process.exit(1);
|
|
1571
2046
|
}
|
|
1572
|
-
log(kleur.green(
|
|
2047
|
+
log(kleur.green(M3().buildSucceeded(buildNumber)));
|
|
2048
|
+
deployBuildNumber = buildNumber;
|
|
1573
2049
|
if (!versionCode) {
|
|
1574
|
-
|
|
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(
|
|
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(
|
|
1585
|
-
log(kleur.dim(
|
|
1586
|
-
log(kleur.dim(
|
|
1587
|
-
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));
|
|
1588
2063
|
process.exit(1);
|
|
1589
2064
|
}
|
|
1590
2065
|
}
|
|
1591
2066
|
}
|
|
1592
2067
|
if (!versionCode) {
|
|
1593
|
-
log(kleur.red(
|
|
2068
|
+
log(kleur.red(M3().versionCodeUnknown));
|
|
1594
2069
|
process.exit(1);
|
|
1595
2070
|
}
|
|
1596
|
-
|
|
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(
|
|
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(
|
|
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 =
|
|
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,
|