mimi-seed 0.4.2 → 0.5.0
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.
- package/README.md +33 -4
- package/dist/{chunk-Q5YGYAFK.js → chunk-DW3LOLPJ.js} +10 -0
- package/dist/chunk-T757WVSG.js +1356 -0
- package/dist/index.js +263 -992
- package/dist/{mcp-client-26VCPY4L.js → mcp-client-SLIKR42C.js} +1 -1
- package/dist/setup-ENJIMGJF.js +11 -0
- package/package.json +2 -2
|
@@ -0,0 +1,1356 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/setup.ts
|
|
4
|
+
import kleur2 from "kleur";
|
|
5
|
+
import * as readline2 from "readline";
|
|
6
|
+
import os5 from "os";
|
|
7
|
+
|
|
8
|
+
// src/credentials.ts
|
|
9
|
+
import fs from "fs";
|
|
10
|
+
import os from "os";
|
|
11
|
+
import path from "path";
|
|
12
|
+
function credDir(home) {
|
|
13
|
+
return path.join(home, ".mimi-seed");
|
|
14
|
+
}
|
|
15
|
+
function hasFile(home, name) {
|
|
16
|
+
return fs.existsSync(path.join(credDir(home), name));
|
|
17
|
+
}
|
|
18
|
+
function anyFileStarting(home, prefix) {
|
|
19
|
+
try {
|
|
20
|
+
return fs.readdirSync(credDir(home)).some((f) => f.startsWith(prefix));
|
|
21
|
+
} catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function readJson(home, name) {
|
|
26
|
+
try {
|
|
27
|
+
return JSON.parse(fs.readFileSync(path.join(credDir(home), name), "utf-8"));
|
|
28
|
+
} catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function hasPlaySa(home) {
|
|
33
|
+
if (hasFile(home, "play-service-account.json")) return true;
|
|
34
|
+
try {
|
|
35
|
+
return fs.readdirSync(path.join(credDir(home), "play-service-accounts")).some((f) => f.endsWith(".json"));
|
|
36
|
+
} catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function ciDetect(home, want) {
|
|
41
|
+
const ci = readJson(home, "ci.json");
|
|
42
|
+
if (!ci || ci.provider !== want) return { present: false };
|
|
43
|
+
return { present: true, detail: ci.owner && ci.repo ? `${ci.owner}/${ci.repo}` : void 0 };
|
|
44
|
+
}
|
|
45
|
+
var CREDENTIALS = [
|
|
46
|
+
{
|
|
47
|
+
id: "mimiseed",
|
|
48
|
+
label: "Mimi Seed \uACC4\uC815 (\uD074\uB77C\uC6B0\uB4DC)",
|
|
49
|
+
requirement: "required",
|
|
50
|
+
group: "core",
|
|
51
|
+
file: "~/.mimi-seed/config.json",
|
|
52
|
+
fix: "mimi-seed init",
|
|
53
|
+
setup: { kind: "command", run: "mimi-seed init" },
|
|
54
|
+
obtain: [
|
|
55
|
+
"https://mimi-seed.pryzm.gg \uC5D0\uC11C \uAC00\uC785/\uB85C\uADF8\uC778\uD558\uBA74 \uB05D \u2014 \uBE0C\uB77C\uC6B0\uC800\uAC00 \uC54C\uC544\uC11C \uD1A0\uD070\uC744 \uB118\uACA8\uC900\uB2E4.",
|
|
56
|
+
"CI \uC5D0\uC11C\uB294 MIMI_SEED_TOKEN \uD658\uACBD\uBCC0\uC218\uB85C \uB300\uCCB4\uD560 \uC218 \uC788\uB2E4."
|
|
57
|
+
],
|
|
58
|
+
docsAnchor: "cloud-pat",
|
|
59
|
+
detect: (home) => {
|
|
60
|
+
if (process.env.MIMI_SEED_TOKEN) return { present: true, detail: "MIMI_SEED_TOKEN (env)" };
|
|
61
|
+
const cfg = readJson(home, "config.json");
|
|
62
|
+
return cfg?.prefix ? { present: true, detail: `${cfg.prefix}\u2026` } : { present: false };
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: "oauth",
|
|
67
|
+
label: "Google OAuth",
|
|
68
|
+
requirement: "required",
|
|
69
|
+
group: "core",
|
|
70
|
+
file: "~/.mimi-seed/tokens.json",
|
|
71
|
+
fix: "mimi-seed auth login",
|
|
72
|
+
setup: { kind: "mcp-bin", bin: "mimi-seed-auth" },
|
|
73
|
+
obtain: [
|
|
74
|
+
"\uBBF8\uB9AC \uBC1C\uAE09\uBC1B\uC744 \uAC8C **\uC5C6\uB2E4**. \uBE0C\uB77C\uC6B0\uC800 \uB85C\uADF8\uC778 \uD55C \uBC88\uC774\uBA74 \uB05D.",
|
|
75
|
+
"\uC774 \uD1A0\uD070 \uD558\uB098\uAC00 Firebase \xB7 AdMob \xB7 Play \xB7 Google Ads \xB7 Search Console \xB7 GA4 \xB7 IAM \xB7 BigQuery \uB97C \uBAA8\uB450 \uC5F0\uB2E4.",
|
|
76
|
+
"\u26A0\uFE0F \uB85C\uADF8\uC778 \uD654\uBA74\uC5D0\uC11C \uAC70\uBD80(access_denied) \uB2F9\uD558\uBA74 \uC571\uC774 \uC544\uC9C1 '\uD14C\uC2A4\uD305' \uBAA8\uB4DC\uB77C \uADF8\uB807\uB2E4 \u2014",
|
|
77
|
+
" \uC6B4\uC601\uC790\uC5D0\uAC8C \uB124 Google \uACC4\uC815\uC744 Test users \uC5D0 \uCD94\uAC00\uD574\uB2EC\uB77C\uACE0 \uC694\uCCAD\uD574\uC57C \uD55C\uB2E4."
|
|
78
|
+
],
|
|
79
|
+
docsAnchor: "google-oauth",
|
|
80
|
+
note: "8\uAC1C Google \uC11C\uBE44\uC2A4\uC758 \uACF5\uD1B5 \uAD00\uBB38",
|
|
81
|
+
detect: (home) => ({ present: hasFile(home, "tokens.json") })
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
id: "appstore",
|
|
85
|
+
label: "App Store Connect",
|
|
86
|
+
requirement: "platform",
|
|
87
|
+
platform: "ios",
|
|
88
|
+
group: "core",
|
|
89
|
+
file: "~/.mimi-seed/appstore.json",
|
|
90
|
+
fix: "mimi-seed auth appstore",
|
|
91
|
+
setup: { kind: "mcp-bin", bin: "mimi-seed-appstore-auth" },
|
|
92
|
+
obtain: [
|
|
93
|
+
"\uC720\uB8CC Apple Developer Program \uBA64\uBC84\uC2ED + Admin/App Manager \uAD8C\uD55C\uC774 \uBA3C\uC800 \uD544\uC694\uD558\uB2E4.",
|
|
94
|
+
"App Store Connect \u2192 \uC0AC\uC6A9\uC790 \uBC0F \uC561\uC138\uC2A4 \u2192 \uD1B5\uD569 \u2192 App Store Connect API \u2192 \uD0A4 \uC0DD\uC131",
|
|
95
|
+
"\uAC00\uC838\uC62C \uAC83 3\uAC1C: Issuer ID \xB7 Key ID \xB7 .p8 \uD30C\uC77C",
|
|
96
|
+
"\u26A0\uFE0F .p8 \uC740 **\uB531 \uD55C \uBC88\uB9CC** \uB2E4\uC6B4\uB85C\uB4DC\uB41C\uB2E4. \uC783\uC5B4\uBC84\uB9AC\uBA74 \uD0A4\uB97C \uD3D0\uAE30\uD558\uACE0 \uC0C8\uB85C \uB9CC\uB4E4\uC5B4\uC57C \uD55C\uB2E4."
|
|
97
|
+
],
|
|
98
|
+
docsAnchor: "app-store-connect",
|
|
99
|
+
detect: (home) => {
|
|
100
|
+
const cfg = readJson(home, "appstore.json");
|
|
101
|
+
return cfg ? { present: true, detail: cfg.keyId ? `keyId ${cfg.keyId}` : void 0 } : { present: false };
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: "playstore",
|
|
106
|
+
label: "Play \uC11C\uBE44\uC2A4 \uACC4\uC815",
|
|
107
|
+
// 선택이다 — helpers.ts 의 requirePlayStoreAuth 가 SA → OAuth 로 폴백하고,
|
|
108
|
+
// 로그인 시 androidpublisher 스코프를 이미 받으므로 로컬 작업은 OAuth 로 된다.
|
|
109
|
+
requirement: "optional",
|
|
110
|
+
group: "core",
|
|
111
|
+
file: "~/.mimi-seed/play-service-account.json",
|
|
112
|
+
fallbackOn: ["oauth"],
|
|
113
|
+
fix: "mimi-seed auth playstore",
|
|
114
|
+
setup: { kind: "mcp-bin", bin: "mimi-seed-playstore-auth" },
|
|
115
|
+
obtain: [
|
|
116
|
+
"\uB85C\uCEEC\uC5D0\uC11C \uC4F8 \uAC70\uBA74 **\uD544\uC694 \uC5C6\uB2E4** \u2014 Google OAuth \uB85C \uB300\uBD80\uBD84\uC758 Play \uC791\uC5C5\uC774 \uB41C\uB2E4.",
|
|
117
|
+
"CI/\uD5E4\uB4DC\uB9AC\uC2A4(\uBE0C\uB77C\uC6B0\uC800 \uC5C6\uB294 \uD658\uACBD)\uC5D0\uC11C\uB9CC \uD544\uC694:",
|
|
118
|
+
" 1. GCP Console \u2192 IAM & Admin \u2192 \uC11C\uBE44\uC2A4 \uACC4\uC815 \u2192 \uD0A4 \u2192 \uC0C8 \uD0A4 \uB9CC\uB4E4\uAE30 \u2192 JSON",
|
|
119
|
+
" 2. Play Console \u2192 \uC124\uC815 \u2192 \uC0AC\uC6A9\uC790 \uBC0F \uAD8C\uD55C \u2192 \uADF8 \uC11C\uBE44\uC2A4 \uACC4\uC815 \uC774\uBA54\uC77C\uC744 \uCD08\uB300",
|
|
120
|
+
" 3. \uAD8C\uD55C \uC804\uD30C\uC5D0 ~5\uBD84 \uAC78\uB9B0\uB2E4. \uADF8 \uC804\uC5D4 403 \uC774 \uB728\uB294 \uAC8C \uC815\uC0C1\uC774\uB2E4.",
|
|
121
|
+
"Claude \uC5D0\uAC8C `setup_playstore_connection` \uC744 \uC2DC\uD0A4\uBA74 1\uBC88\uC744 \uC790\uB3D9\uD654\uD574\uC900\uB2E4."
|
|
122
|
+
],
|
|
123
|
+
docsAnchor: "play-service-account",
|
|
124
|
+
note: "\uC120\uD0DD \u2014 \uB85C\uCEEC\uC740 OAuth \uB85C \uAC00\uB2A5, CI/\uD5E4\uB4DC\uB9AC\uC2A4\uB294 \uD544\uC218",
|
|
125
|
+
detect: (home) => ({ present: hasPlaySa(home) })
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
id: "bigquery",
|
|
129
|
+
label: "BigQuery \uC11C\uBE44\uC2A4 \uACC4\uC815",
|
|
130
|
+
requirement: "optional",
|
|
131
|
+
group: "core",
|
|
132
|
+
file: "~/.mimi-seed/bigquery-service-account.json",
|
|
133
|
+
fallbackOn: ["oauth"],
|
|
134
|
+
fix: "mimi-seed auth bigquery",
|
|
135
|
+
setup: { kind: "mcp-bin", bin: "mimi-seed-bigquery-auth" },
|
|
136
|
+
obtain: [
|
|
137
|
+
"OAuth \uB85C\uB3C4 \uB3D9\uC791\uD558\uC9C0\uB9CC, Workspace \uC7AC\uC778\uC99D \uC815\uCC45(invalid_rapt)\uC5D0 \uB9C9\uD788\uB294 \uD658\uACBD\uC774\uBA74 \uC11C\uBE44\uC2A4 \uACC4\uC815\uC774 \uD544\uC694\uD558\uB2E4.",
|
|
138
|
+
"GCP Console \u2192 IAM & Admin \u2192 \uC11C\uBE44\uC2A4 \uACC4\uC815 \u2192 \uD0A4 \u2192 \uC0C8 \uD0A4 \uB9CC\uB4E4\uAE30 \u2192 JSON",
|
|
139
|
+
"\uADF8 \uC11C\uBE44\uC2A4 \uACC4\uC815\uC5D0 IAM \uC5ED\uD560\uC744 **\uC9C1\uC811** \uBD80\uC5EC\uD574\uC57C \uD55C\uB2E4 (\uC790\uB3D9\uC73C\uB85C \uC548 \uB41C\uB2E4):",
|
|
140
|
+
" \u2022 roles/bigquery.jobUser (\uCFFC\uB9AC \uC2E4\uD589)",
|
|
141
|
+
" \u2022 roles/bigquery.dataViewer (\uB370\uC774\uD130\uC14B \uC77D\uAE30)"
|
|
142
|
+
],
|
|
143
|
+
docsAnchor: "bigquery",
|
|
144
|
+
note: "\uC120\uD0DD \u2014 OAuth \uD3F4\uBC31 \uAC00\uB2A5",
|
|
145
|
+
detect: (home) => ({ present: anyFileStarting(home, "bigquery") })
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
id: "github",
|
|
149
|
+
label: "GitHub Actions",
|
|
150
|
+
requirement: "optional",
|
|
151
|
+
group: "ci",
|
|
152
|
+
file: "~/.mimi-seed/ci.json",
|
|
153
|
+
fix: "mimi-seed auth ci",
|
|
154
|
+
setup: { kind: "cli", handler: "github" },
|
|
155
|
+
obtain: [
|
|
156
|
+
"GitHub \u2192 Settings \u2192 Developer settings \u2192 Personal access tokens",
|
|
157
|
+
"\u26A0\uFE0F \uC2A4\uCF54\uD504 **`repo` \uC640 `workflow` \uB97C \uB458 \uB2E4** \uCCB4\uD06C\uD574\uC57C \uD55C\uB2E4.",
|
|
158
|
+
" `workflow` \uAC00 \uC5C6\uC73C\uBA74 \uC6CC\uD06C\uD50C\uB85C dispatch \uAC00 403 \uC73C\uB85C \uB9C9\uD78C\uB2E4.",
|
|
159
|
+
"\uADF8 \uC678: owner(\uC870\uC9C1/\uC0AC\uC6A9\uC790) \xB7 repo \uC774\uB984. GitHub Enterprise \uBA74 host URL \uB3C4."
|
|
160
|
+
],
|
|
161
|
+
docsAnchor: "ci-github-gitlab",
|
|
162
|
+
detect: (home) => ciDetect(home, "github")
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
id: "gitlab",
|
|
166
|
+
label: "GitLab CI",
|
|
167
|
+
requirement: "optional",
|
|
168
|
+
group: "ci",
|
|
169
|
+
file: "~/.mimi-seed/ci.json",
|
|
170
|
+
fix: "mimi-seed auth ci",
|
|
171
|
+
setup: { kind: "cli", handler: "gitlab" },
|
|
172
|
+
obtain: [
|
|
173
|
+
"GitLab \u2192 \uC0AC\uC6A9\uC790 \uC124\uC815 \u2192 Access Tokens \u2192 `api` \uC2A4\uCF54\uD504\uB85C \uBC1C\uAE09 (glpat-\u2026)",
|
|
174
|
+
"\uADF8 \uC678: namespace/group \xB7 \uD504\uB85C\uC81D\uD2B8 \uC774\uB984. self-hosted \uBA74 URL \uB3C4."
|
|
175
|
+
],
|
|
176
|
+
docsAnchor: "ci-github-gitlab",
|
|
177
|
+
detect: (home) => ciDetect(home, "gitlab")
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
id: "jenkins",
|
|
181
|
+
label: "Jenkins",
|
|
182
|
+
requirement: "optional",
|
|
183
|
+
group: "ci",
|
|
184
|
+
file: "~/.mimi-seed/jenkins.json",
|
|
185
|
+
fix: "mimi-seed auth jenkins",
|
|
186
|
+
setup: { kind: "mcp-bin", bin: "mimi-seed-jenkins-auth" },
|
|
187
|
+
obtain: [
|
|
188
|
+
'Jenkins \u2192 [\uC0AC\uC6A9\uC790 \uC774\uB984] \u2192 \uC124\uC815 \u2192 API Token \u2192 "Add new Token"',
|
|
189
|
+
"\uBE44\uBC00\uBC88\uD638\uAC00 \uC544\uB2C8\uB77C **API Token** \uC774\uB2E4.",
|
|
190
|
+
"\uB85C\uCEEC Jenkins \uAC00 \uC5C6\uC5B4\uB3C4 \uD68C\uC0AC/\uC6D0\uACA9 \uC11C\uBC84 URL \uC744 \uADF8\uB300\uB85C \uC4F0\uBA74 \uB41C\uB2E4."
|
|
191
|
+
],
|
|
192
|
+
docsAnchor: "jenkins",
|
|
193
|
+
detect: (home) => {
|
|
194
|
+
const cfg = readJson(home, "jenkins.json");
|
|
195
|
+
return cfg?.url ? { present: true, detail: cfg.url } : { present: false };
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
id: "googleads",
|
|
200
|
+
label: "Google Ads",
|
|
201
|
+
requirement: "optional",
|
|
202
|
+
group: "marketing",
|
|
203
|
+
file: "~/.mimi-seed/google-ads.json",
|
|
204
|
+
fix: "mimi-seed auth googleads",
|
|
205
|
+
setup: { kind: "mcp-bin", bin: "mimi-seed-googleads-auth" },
|
|
206
|
+
obtain: [
|
|
207
|
+
"Google Ads \u2192 \uB3C4\uAD6C \uBC0F \uC124\uC815 \u2192 \uC124\uC815 \u2192 API \uC13C\uD130 \u2192 Developer Token \uBC1C\uAE09",
|
|
208
|
+
"\u26A0\uFE0F \uCD5C\uCD08 \uBC1C\uAE09 \uD1A0\uD070\uC740 '\uD14C\uC2A4\uD2B8' \uB4F1\uAE09\uC774\uB2E4. \uC2E4\uACC4\uC815 \uB370\uC774\uD130\uB97C \uBCF4\uB824\uBA74 **\uC2B9\uC778 \uC2EC\uC0AC**\uB97C \uD1B5\uACFC\uD574\uC57C \uD558\uACE0,",
|
|
209
|
+
" \uC2EC\uC0AC\uC5D0\uB294 \uC2DC\uAC04\uC774 \uAC78\uB9B0\uB2E4 (\uC989\uC2DC \uBC1C\uAE09\uB418\uC9C0 \uC54A\uB294\uB2E4).",
|
|
210
|
+
"\uADF8 \uC678: Customer ID (\uC608: 123-456-7890). MCC \uB97C \uC4F0\uBA74 \uAD00\uB9AC\uC790 \uACC4\uC815 ID \uB3C4.",
|
|
211
|
+
"\uC778\uC99D \uC790\uCCB4\uB294 Google OAuth \uC758 `adwords` \uC2A4\uCF54\uD504\uB97C \uD0C4\uB2E4 \u2014 \uC61B \uD1A0\uD070\uC774\uBA74 \uC7AC\uB85C\uADF8\uC778\uC774 \uD544\uC694\uD560 \uC218 \uC788\uB2E4."
|
|
212
|
+
],
|
|
213
|
+
docsAnchor: "google-ads",
|
|
214
|
+
detect: (home) => {
|
|
215
|
+
const cfg = readJson(home, "google-ads.json");
|
|
216
|
+
return cfg?.customerId ? { present: true, detail: cfg.customerId } : { present: false };
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
id: "facebook",
|
|
221
|
+
label: "Facebook \uD398\uC774\uC9C0",
|
|
222
|
+
requirement: "optional",
|
|
223
|
+
group: "marketing",
|
|
224
|
+
file: "~/.mimi-seed/facebook.json",
|
|
225
|
+
fix: "mimi-seed auth facebook",
|
|
226
|
+
setup: { kind: "mcp-bin", bin: "mimi-seed-social-auth", args: ["facebook"] },
|
|
227
|
+
obtain: [
|
|
228
|
+
"Meta \uAC1C\uBC1C\uC790 \uC571 + \uB300\uC0C1 \uD398\uC774\uC9C0\uC758 \uAD00\uB9AC\uC790 \uAD8C\uD55C\uC774 \uBA3C\uC800 \uD544\uC694\uD558\uB2E4.",
|
|
229
|
+
"Graph API Explorer \u2192 \uAD8C\uD55C pages_show_list, pages_manage_posts, pages_read_engagement",
|
|
230
|
+
"\u2192 User Token \uC0DD\uC131 \u2192 /me/accounts \uD638\uCD9C \u2192 \uADF8 \uD398\uC774\uC9C0\uC758 access_token (EAA\u2026)",
|
|
231
|
+
"long-lived \uD1A0\uD070\uC744 \uAD8C\uC7A5\uD55C\uB2E4 (\uC57D 60\uC77C)."
|
|
232
|
+
],
|
|
233
|
+
docsAnchor: "facebook",
|
|
234
|
+
detect: (home) => {
|
|
235
|
+
const cfg = readJson(home, "facebook.json");
|
|
236
|
+
return cfg?.pageId ? { present: true, detail: cfg.pageName ?? cfg.pageId } : { present: false };
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
id: "instagram",
|
|
241
|
+
label: "Instagram",
|
|
242
|
+
requirement: "optional",
|
|
243
|
+
group: "marketing",
|
|
244
|
+
file: "~/.mimi-seed/instagram.json",
|
|
245
|
+
fix: "mimi-seed auth instagram",
|
|
246
|
+
setup: { kind: "mcp-bin", bin: "mimi-seed-social-auth", args: ["instagram"] },
|
|
247
|
+
obtain: [
|
|
248
|
+
"long-lived \uD1A0\uD070 \uB450 \uD615\uC2DD \uBAA8\uB450 \uC9C0\uC6D0\uD55C\uB2E4 (\uC790\uB3D9 \uAC10\uC9C0):",
|
|
249
|
+
" \u2022 IGAA\u2026 \u2014 Instagram Login (Meta \uC2E0\uADDC \uBC29\uC2DD, Facebook \uD398\uC774\uC9C0 \uBD88\uD544\uC694)",
|
|
250
|
+
" \u2022 EAA\u2026 \u2014 Facebook Login (IG **\uBE44\uC988\uB2C8\uC2A4** \uACC4\uC815\uC774 FB \uD398\uC774\uC9C0\uC5D0 \uC5F0\uACB0\uB3FC \uC788\uC5B4\uC57C \uD568)",
|
|
251
|
+
"\uD1A0\uD070 \uC218\uBA85\uC740 \uC57D 60\uC77C\uC774\uB2E4."
|
|
252
|
+
],
|
|
253
|
+
docsAnchor: "instagram",
|
|
254
|
+
detect: (home) => {
|
|
255
|
+
const cfg = readJson(home, "instagram.json");
|
|
256
|
+
return cfg?.userId ? { present: true, detail: cfg.username ?? cfg.userId } : { present: false };
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
id: "anthropic",
|
|
261
|
+
label: "ANTHROPIC_API_KEY",
|
|
262
|
+
requirement: "optional",
|
|
263
|
+
group: "marketing",
|
|
264
|
+
fix: "export ANTHROPIC_API_KEY=sk-ant-\u2026",
|
|
265
|
+
setup: { kind: "env", envVar: "ANTHROPIC_API_KEY" },
|
|
266
|
+
obtain: [
|
|
267
|
+
"Anthropic Console (console.anthropic.com) \u2192 API Keys \uC5D0\uC11C \uBC1C\uAE09.",
|
|
268
|
+
"\uD658\uACBD\uBCC0\uC218\uB85C\uB9CC \uC77D\uB294\uB2E4 \u2014 \uC124\uC815 \uBA85\uB839\uC774 \uB530\uB85C \uC5C6\uB2E4.",
|
|
269
|
+
"\uC5C6\uC5B4\uB3C4 \uB41C\uB2E4: `mimi-seed notes` \uB294 AI \uC5C6\uC774 \uCEE4\uBC0B \uBAA9\uB85D \uAE30\uBC18\uC73C\uB85C \uB3D9\uC791\uD55C\uB2E4."
|
|
270
|
+
],
|
|
271
|
+
docsAnchor: "anthropic-api-key",
|
|
272
|
+
note: "\uC120\uD0DD \u2014 AI \uB9B4\uB9AC\uC2A4 \uB178\uD2B8/\uB9AC\uBDF0 \uB2F5\uBCC0 \uC0DD\uC131\uC6A9",
|
|
273
|
+
detect: () => ({ present: Boolean(process.env.ANTHROPIC_API_KEY) })
|
|
274
|
+
}
|
|
275
|
+
];
|
|
276
|
+
function tryCredById(id) {
|
|
277
|
+
return CREDENTIALS.find((c) => c.id === id);
|
|
278
|
+
}
|
|
279
|
+
function detectAll(home = os.homedir()) {
|
|
280
|
+
return new Map(CREDENTIALS.map((c) => [c.id, c.detect(home)]));
|
|
281
|
+
}
|
|
282
|
+
function isSatisfied(spec, detected) {
|
|
283
|
+
if (detected.get(spec.id)?.present) return true;
|
|
284
|
+
return (spec.fallbackOn ?? []).some((f) => detected.get(f)?.present);
|
|
285
|
+
}
|
|
286
|
+
function missingRequired(detected, platforms) {
|
|
287
|
+
return CREDENTIALS.filter((spec) => {
|
|
288
|
+
if (spec.requirement === "optional") return false;
|
|
289
|
+
if (spec.requirement === "platform" && !platforms.includes(spec.platform)) return false;
|
|
290
|
+
return !isSatisfied(spec, detected);
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
var GROUP_ORDER = { core: 0, ci: 1, marketing: 2 };
|
|
294
|
+
var REQ_ORDER = { required: 0, platform: 1, optional: 2 };
|
|
295
|
+
function planSetup(detected, opts = {}) {
|
|
296
|
+
const reconnect = new Set(opts.reconnect ?? []);
|
|
297
|
+
const only = opts.only ? new Set(opts.only) : null;
|
|
298
|
+
const platforms = opts.platforms ?? [];
|
|
299
|
+
return CREDENTIALS.filter((spec) => {
|
|
300
|
+
if (only && !only.has(spec.id)) return false;
|
|
301
|
+
if (reconnect.has(spec.id)) return true;
|
|
302
|
+
if (detected.get(spec.id)?.present) return false;
|
|
303
|
+
if (spec.requirement === "platform" && platforms.length > 0 && !platforms.includes(spec.platform)) {
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
return true;
|
|
307
|
+
}).sort(
|
|
308
|
+
(a, b) => GROUP_ORDER[a.group] - GROUP_ORDER[b.group] || REQ_ORDER[a.requirement] - REQ_ORDER[b.requirement]
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// src/mcp-bin.ts
|
|
313
|
+
import { spawn, spawnSync } from "child_process";
|
|
314
|
+
var MCP_PKG = "@yoonion/mimi-seed-mcp";
|
|
315
|
+
function resolveOnPath(bin) {
|
|
316
|
+
if (process.env.MIMI_SEED_FORCE_NPX) return false;
|
|
317
|
+
const probe = process.platform === "win32" ? "where" : "which";
|
|
318
|
+
const r = spawnSync(probe, [bin], { stdio: "ignore", shell: true });
|
|
319
|
+
return r.status === 0;
|
|
320
|
+
}
|
|
321
|
+
async function runMcpBin(bin, extraArgs = []) {
|
|
322
|
+
const local = resolveOnPath(bin);
|
|
323
|
+
const cmd = local ? bin : "npx";
|
|
324
|
+
const args = local ? extraArgs : ["-y", MCP_PKG, bin, ...extraArgs];
|
|
325
|
+
return new Promise((resolve) => {
|
|
326
|
+
const child = spawn(cmd, args, { stdio: "inherit", shell: true });
|
|
327
|
+
child.on("error", (e) => {
|
|
328
|
+
process.stderr.write(`
|
|
329
|
+
\u274C ${cmd} \uC2E4\uD589 \uC2E4\uD328: ${e.message}
|
|
330
|
+
`);
|
|
331
|
+
resolve(1);
|
|
332
|
+
});
|
|
333
|
+
child.on("exit", (code) => resolve(code ?? 0));
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// src/jenkins-config.ts
|
|
338
|
+
import fs2 from "fs";
|
|
339
|
+
import os2 from "os";
|
|
340
|
+
import path2 from "path";
|
|
341
|
+
var CONFIG_DIR = path2.join(os2.homedir(), ".mimi-seed");
|
|
342
|
+
var JENKINS_PATH = path2.join(CONFIG_DIR, "jenkins.json");
|
|
343
|
+
var LEGACY_PATH = path2.join(CONFIG_DIR, "config.json");
|
|
344
|
+
function loadJenkinsConfig(home = os2.homedir()) {
|
|
345
|
+
try {
|
|
346
|
+
const p = path2.join(home, ".mimi-seed", "jenkins.json");
|
|
347
|
+
return JSON.parse(fs2.readFileSync(p, "utf-8"));
|
|
348
|
+
} catch {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
function migrateLegacyJenkins(home = os2.homedir()) {
|
|
353
|
+
const dir = path2.join(home, ".mimi-seed");
|
|
354
|
+
const jenkinsPath = path2.join(dir, "jenkins.json");
|
|
355
|
+
const legacyPath = path2.join(dir, "config.json");
|
|
356
|
+
if (fs2.existsSync(jenkinsPath)) return false;
|
|
357
|
+
let legacy;
|
|
358
|
+
try {
|
|
359
|
+
legacy = JSON.parse(fs2.readFileSync(legacyPath, "utf-8"));
|
|
360
|
+
} catch {
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
const j = legacy.jenkins;
|
|
364
|
+
if (!j?.url || !j?.token) return false;
|
|
365
|
+
const migrated = {
|
|
366
|
+
url: j.url,
|
|
367
|
+
username: j.username ?? j.user ?? "admin",
|
|
368
|
+
// 필드명 리네임: user → username
|
|
369
|
+
token: j.token,
|
|
370
|
+
...j.jobAndroid ? { jobAndroid: j.jobAndroid } : {},
|
|
371
|
+
...j.jobIos ? { jobIos: j.jobIos } : {}
|
|
372
|
+
};
|
|
373
|
+
fs2.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
374
|
+
fs2.writeFileSync(jenkinsPath, JSON.stringify(migrated, null, 2), { mode: 384 });
|
|
375
|
+
delete legacy.jenkins;
|
|
376
|
+
const tmp = `${legacyPath}.tmp`;
|
|
377
|
+
fs2.writeFileSync(tmp, JSON.stringify(legacy, null, 2), { mode: 384 });
|
|
378
|
+
fs2.renameSync(tmp, legacyPath);
|
|
379
|
+
return true;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// src/detect.ts
|
|
383
|
+
import fs3 from "fs/promises";
|
|
384
|
+
import path3 from "path";
|
|
385
|
+
async function readIfExists(p) {
|
|
386
|
+
try {
|
|
387
|
+
return await fs3.readFile(p, "utf8");
|
|
388
|
+
} catch {
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
async function pathExists(p) {
|
|
393
|
+
try {
|
|
394
|
+
await fs3.access(p);
|
|
395
|
+
return true;
|
|
396
|
+
} catch {
|
|
397
|
+
return false;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
async function walk(root, match, maxDepth = 5) {
|
|
401
|
+
const found = [];
|
|
402
|
+
const skipDirs = /* @__PURE__ */ new Set([
|
|
403
|
+
"node_modules",
|
|
404
|
+
".git",
|
|
405
|
+
"build",
|
|
406
|
+
"dist",
|
|
407
|
+
".next",
|
|
408
|
+
".expo",
|
|
409
|
+
"Pods",
|
|
410
|
+
"DerivedData"
|
|
411
|
+
]);
|
|
412
|
+
async function visit(dir, depth) {
|
|
413
|
+
if (depth > maxDepth) return;
|
|
414
|
+
let entries;
|
|
415
|
+
try {
|
|
416
|
+
entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
417
|
+
} catch {
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
for (const e of entries) {
|
|
421
|
+
if (e.isDirectory()) {
|
|
422
|
+
if (skipDirs.has(e.name)) continue;
|
|
423
|
+
await visit(path3.join(dir, e.name), depth + 1);
|
|
424
|
+
} else if (e.isFile() && match(e.name)) {
|
|
425
|
+
found.push(path3.join(dir, e.name));
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
await visit(root, 0);
|
|
430
|
+
return found;
|
|
431
|
+
}
|
|
432
|
+
async function detectHints(cwd) {
|
|
433
|
+
const hints = [];
|
|
434
|
+
for (const fname of ["app.json", "app.config.json"]) {
|
|
435
|
+
const txt = await readIfExists(path3.join(cwd, fname));
|
|
436
|
+
if (!txt) continue;
|
|
437
|
+
try {
|
|
438
|
+
const json = JSON.parse(txt);
|
|
439
|
+
const expo = json.expo ?? json;
|
|
440
|
+
const pkg = expo?.android?.package;
|
|
441
|
+
const bid = expo?.ios?.bundleIdentifier;
|
|
442
|
+
const name = expo?.name;
|
|
443
|
+
if (pkg || bid) {
|
|
444
|
+
hints.push({
|
|
445
|
+
name,
|
|
446
|
+
packageName: typeof pkg === "string" ? pkg : void 0,
|
|
447
|
+
bundleId: typeof bid === "string" ? bid : void 0,
|
|
448
|
+
source: [fname]
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
} catch {
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
const gradleFiles = await walk(
|
|
455
|
+
cwd,
|
|
456
|
+
(n) => n === "build.gradle" || n === "build.gradle.kts",
|
|
457
|
+
4
|
|
458
|
+
);
|
|
459
|
+
for (const f of gradleFiles) {
|
|
460
|
+
const txt = await readIfExists(f);
|
|
461
|
+
if (!txt) continue;
|
|
462
|
+
const m = txt.match(/applicationId[\s=]+["']([^"']+)["']/);
|
|
463
|
+
if (m?.[1]) {
|
|
464
|
+
const pkg = m[1];
|
|
465
|
+
if (!hints.some((h) => h.packageName === pkg)) {
|
|
466
|
+
hints.push({ packageName: pkg, source: [path3.relative(cwd, f)] });
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
const plistFiles = await walk(cwd, (n) => n === "Info.plist", 5);
|
|
471
|
+
for (const f of plistFiles) {
|
|
472
|
+
const txt = await readIfExists(f);
|
|
473
|
+
if (!txt) continue;
|
|
474
|
+
const m = txt.match(
|
|
475
|
+
/<key>CFBundleIdentifier<\/key>\s*<string>([^<]+)<\/string>/
|
|
476
|
+
);
|
|
477
|
+
if (m?.[1]) {
|
|
478
|
+
let bid = m[1];
|
|
479
|
+
if (bid.includes("$(PRODUCT_BUNDLE_IDENTIFIER)")) continue;
|
|
480
|
+
if (!hints.some((h) => h.bundleId === bid)) {
|
|
481
|
+
hints.push({ bundleId: bid, source: [path3.relative(cwd, f)] });
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
const pbxFiles = await walk(cwd, (n) => n === "project.pbxproj", 5);
|
|
486
|
+
for (const f of pbxFiles) {
|
|
487
|
+
const txt = await readIfExists(f);
|
|
488
|
+
if (!txt) continue;
|
|
489
|
+
const matches = [...txt.matchAll(/PRODUCT_BUNDLE_IDENTIFIER = ([^;]+);/g)];
|
|
490
|
+
for (const m of matches) {
|
|
491
|
+
const bid = m[1].trim().replace(/^["']|["']$/g, "");
|
|
492
|
+
if (!bid || bid.includes("$")) continue;
|
|
493
|
+
if (!hints.some((h) => h.bundleId === bid)) {
|
|
494
|
+
hints.push({ bundleId: bid, source: [path3.relative(cwd, f)] });
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
const pkgJson = await readIfExists(path3.join(cwd, "package.json"));
|
|
499
|
+
if (pkgJson) {
|
|
500
|
+
try {
|
|
501
|
+
const json = JSON.parse(pkgJson);
|
|
502
|
+
if (typeof json.name === "string") {
|
|
503
|
+
for (const h of hints) if (!h.name) h.name = json.name;
|
|
504
|
+
}
|
|
505
|
+
} catch {
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
const merged = [];
|
|
509
|
+
const androidOnly = hints.filter((h) => h.packageName && !h.bundleId);
|
|
510
|
+
const iosOnly = hints.filter((h) => h.bundleId && !h.packageName);
|
|
511
|
+
const both = hints.filter((h) => h.packageName && h.bundleId);
|
|
512
|
+
merged.push(...both);
|
|
513
|
+
for (const a of androidOnly) {
|
|
514
|
+
const match = iosOnly.find((i) => i.bundleId === a.packageName);
|
|
515
|
+
if (match) {
|
|
516
|
+
merged.push({
|
|
517
|
+
name: a.name ?? match.name,
|
|
518
|
+
packageName: a.packageName,
|
|
519
|
+
bundleId: match.bundleId,
|
|
520
|
+
source: [...a.source, ...match.source]
|
|
521
|
+
});
|
|
522
|
+
} else {
|
|
523
|
+
merged.push(a);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
for (const i of iosOnly) {
|
|
527
|
+
if (!androidOnly.some((a) => a.packageName === i.bundleId)) {
|
|
528
|
+
merged.push(i);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
return merged.filter((h) => h.packageName || h.bundleId);
|
|
532
|
+
}
|
|
533
|
+
async function hasAnyProjectSignal(cwd) {
|
|
534
|
+
return await pathExists(path3.join(cwd, "package.json")) || await pathExists(path3.join(cwd, "app.json")) || await pathExists(path3.join(cwd, "android")) || await pathExists(path3.join(cwd, "ios"));
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// src/deploy.ts
|
|
538
|
+
import kleur from "kleur";
|
|
539
|
+
import * as readline from "readline";
|
|
540
|
+
|
|
541
|
+
// src/config.ts
|
|
542
|
+
import fs4 from "fs/promises";
|
|
543
|
+
import path4 from "path";
|
|
544
|
+
import os3 from "os";
|
|
545
|
+
var CONFIG_DIR2 = path4.join(os3.homedir(), ".mimi-seed");
|
|
546
|
+
var CONFIG_PATH = path4.join(CONFIG_DIR2, "config.json");
|
|
547
|
+
async function readConfig() {
|
|
548
|
+
try {
|
|
549
|
+
const txt = await fs4.readFile(CONFIG_PATH, "utf8");
|
|
550
|
+
return JSON.parse(txt);
|
|
551
|
+
} catch {
|
|
552
|
+
return null;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
async function writeConfig(cfg) {
|
|
556
|
+
await fs4.mkdir(CONFIG_DIR2, { recursive: true });
|
|
557
|
+
await fs4.writeFile(CONFIG_PATH, JSON.stringify(cfg, null, 2));
|
|
558
|
+
if (process.platform !== "win32") {
|
|
559
|
+
await fs4.chmod(CONFIG_PATH, 384);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
async function deleteConfig() {
|
|
563
|
+
await fs4.rm(CONFIG_PATH, { force: true });
|
|
564
|
+
}
|
|
565
|
+
var CONFIG_LOCATION = CONFIG_PATH;
|
|
566
|
+
async function getEffectiveConfig() {
|
|
567
|
+
const envToken = process.env.MIMI_SEED_TOKEN;
|
|
568
|
+
if (envToken) {
|
|
569
|
+
const webBase = process.env.MIMI_SEED_WEB_BASE ?? "https://mimi-seed.pryzm.gg";
|
|
570
|
+
return {
|
|
571
|
+
token: envToken,
|
|
572
|
+
prefix: envToken.slice(0, 8),
|
|
573
|
+
endpoint: `${webBase}/api/mcp`,
|
|
574
|
+
webBase,
|
|
575
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
return readConfig();
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// src/ci-providers.ts
|
|
582
|
+
import fs5 from "fs";
|
|
583
|
+
import path5 from "path";
|
|
584
|
+
import os4 from "os";
|
|
585
|
+
var CI_CONFIG_PATH = path5.join(os4.homedir(), ".mimi-seed", "ci.json");
|
|
586
|
+
function loadCiProviderConfig() {
|
|
587
|
+
try {
|
|
588
|
+
const cfg = JSON.parse(fs5.readFileSync(CI_CONFIG_PATH, "utf-8"));
|
|
589
|
+
const host = normalizeHost(cfg.host);
|
|
590
|
+
return host ? { ...cfg, host } : { ...cfg, host: void 0 };
|
|
591
|
+
} catch {
|
|
592
|
+
return null;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
function normalizeHost(host) {
|
|
596
|
+
const h = host?.trim();
|
|
597
|
+
if (!h) return void 0;
|
|
598
|
+
const withScheme = /^https?:\/\//i.test(h) ? h : `https://${h}`;
|
|
599
|
+
return withScheme.replace(/\/+$/, "");
|
|
600
|
+
}
|
|
601
|
+
function saveCiProviderConfig(cfg) {
|
|
602
|
+
const dir = path5.dirname(CI_CONFIG_PATH);
|
|
603
|
+
if (!fs5.existsSync(dir)) {
|
|
604
|
+
fs5.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
605
|
+
}
|
|
606
|
+
const normalized = { ...cfg, host: normalizeHost(cfg.host) };
|
|
607
|
+
if (!normalized.host) delete normalized.host;
|
|
608
|
+
fs5.writeFileSync(CI_CONFIG_PATH, JSON.stringify(normalized, null, 2));
|
|
609
|
+
if (process.platform !== "win32") {
|
|
610
|
+
fs5.chmodSync(CI_CONFIG_PATH, 384);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
async function verifyCiToken(cfg) {
|
|
614
|
+
const probe = { ...cfg, host: normalizeHost(cfg.host) };
|
|
615
|
+
try {
|
|
616
|
+
if (probe.provider === "github") {
|
|
617
|
+
const res2 = await fetch(`${ghBase(probe)}/user`, {
|
|
618
|
+
headers: { Authorization: `Bearer ${probe.token}`, Accept: "application/vnd.github+json" }
|
|
619
|
+
});
|
|
620
|
+
if (!res2.ok) {
|
|
621
|
+
return { ok: false, reason: `GitHub ${res2.status} \u2014 \uD1A0\uD070\uC774 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC544` };
|
|
622
|
+
}
|
|
623
|
+
const user2 = await res2.json();
|
|
624
|
+
const scopes = res2.headers.get("x-oauth-scopes");
|
|
625
|
+
if (scopes !== null && !scopes.split(/,\s*/).filter(Boolean).includes("workflow")) {
|
|
626
|
+
return {
|
|
627
|
+
ok: false,
|
|
628
|
+
reason: `\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.`
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
return { ok: true, login: user2.login };
|
|
632
|
+
}
|
|
633
|
+
const res = await fetch(`${glBase(probe)}/user`, { headers: { "PRIVATE-TOKEN": probe.token } });
|
|
634
|
+
if (!res.ok) {
|
|
635
|
+
return { ok: false, reason: `GitLab ${res.status} \u2014 \uD1A0\uD070\uC774 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC544` };
|
|
636
|
+
}
|
|
637
|
+
const user = await res.json();
|
|
638
|
+
return { ok: true, login: user.username };
|
|
639
|
+
} catch (e) {
|
|
640
|
+
return { ok: false, reason: e instanceof Error ? e.message : String(e) };
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
function ghBase(cfg) {
|
|
644
|
+
if (cfg.host) return `${cfg.host.replace(/\/$/, "")}/api/v3`;
|
|
645
|
+
return "https://api.github.com";
|
|
646
|
+
}
|
|
647
|
+
function ghHeaders(token) {
|
|
648
|
+
return {
|
|
649
|
+
Authorization: `Bearer ${token}`,
|
|
650
|
+
Accept: "application/vnd.github+json",
|
|
651
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
652
|
+
"Content-Type": "application/json"
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
async function ghTriggerWorkflow(cfg, workflow, ref, inputs = {}) {
|
|
656
|
+
const startTime = /* @__PURE__ */ new Date();
|
|
657
|
+
const wfId = /^\d+$/.test(workflow) ? Number(workflow) : workflow;
|
|
658
|
+
const dispatchRes = await fetch(
|
|
659
|
+
`${ghBase(cfg)}/repos/${cfg.owner}/${cfg.repo}/actions/workflows/${wfId}/dispatches`,
|
|
660
|
+
{
|
|
661
|
+
method: "POST",
|
|
662
|
+
headers: ghHeaders(cfg.token),
|
|
663
|
+
body: JSON.stringify({ ref, inputs })
|
|
664
|
+
}
|
|
665
|
+
);
|
|
666
|
+
if (!dispatchRes.ok) {
|
|
667
|
+
throw new Error(`GitHub dispatch ${dispatchRes.status}: ${await dispatchRes.text()}`);
|
|
668
|
+
}
|
|
669
|
+
await new Promise((r) => setTimeout(r, 3e3));
|
|
670
|
+
const runsRes = await fetch(
|
|
671
|
+
`${ghBase(cfg)}/repos/${cfg.owner}/${cfg.repo}/actions/workflows/${wfId}/runs?per_page=5`,
|
|
672
|
+
{ headers: ghHeaders(cfg.token) }
|
|
673
|
+
);
|
|
674
|
+
if (!runsRes.ok) return null;
|
|
675
|
+
const data = await runsRes.json();
|
|
676
|
+
const run = data.workflow_runs.find((r) => new Date(r.created_at) >= startTime) ?? data.workflow_runs[0];
|
|
677
|
+
if (!run) return null;
|
|
678
|
+
return { runId: run.id, url: run.html_url };
|
|
679
|
+
}
|
|
680
|
+
async function ghPollRun(cfg, runId, onTick, timeoutMs = 30 * 60 * 1e3, intervalMs = 15e3) {
|
|
681
|
+
const start = Date.now();
|
|
682
|
+
let consecutiveErrors = 0;
|
|
683
|
+
while (Date.now() - start < timeoutMs) {
|
|
684
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
685
|
+
let res;
|
|
686
|
+
try {
|
|
687
|
+
res = await fetch(
|
|
688
|
+
`${ghBase(cfg)}/repos/${cfg.owner}/${cfg.repo}/actions/runs/${runId}`,
|
|
689
|
+
{ headers: ghHeaders(cfg.token) }
|
|
690
|
+
);
|
|
691
|
+
} catch {
|
|
692
|
+
consecutiveErrors++;
|
|
693
|
+
if (consecutiveErrors >= 3) throw new Error("GitHub API \uC5F0\uC18D \uC624\uB958 3\uD68C");
|
|
694
|
+
continue;
|
|
695
|
+
}
|
|
696
|
+
if (!res.ok) {
|
|
697
|
+
consecutiveErrors++;
|
|
698
|
+
if (consecutiveErrors >= 3) throw new Error(`GitHub API \uC5F0\uC18D \uC624\uB958 (HTTP ${res.status})`);
|
|
699
|
+
continue;
|
|
700
|
+
}
|
|
701
|
+
consecutiveErrors = 0;
|
|
702
|
+
const data = await res.json();
|
|
703
|
+
onTick?.(data.status);
|
|
704
|
+
if (data.status === "completed") {
|
|
705
|
+
if (data.conclusion === "success") return "success";
|
|
706
|
+
if (data.conclusion === "cancelled") return "cancelled";
|
|
707
|
+
return "failure";
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
return "timeout";
|
|
711
|
+
}
|
|
712
|
+
function glBase(cfg) {
|
|
713
|
+
return `${cfg.host ?? "https://gitlab.com"}/api/v4`;
|
|
714
|
+
}
|
|
715
|
+
function glProjectId(cfg) {
|
|
716
|
+
return encodeURIComponent(`${cfg.owner}/${cfg.repo}`);
|
|
717
|
+
}
|
|
718
|
+
async function glTriggerPipeline(cfg, ref, variables = {}) {
|
|
719
|
+
const vars = Object.entries(variables).map(([key, value]) => ({ key, value }));
|
|
720
|
+
const body = { ref };
|
|
721
|
+
if (vars.length > 0) body.variables = vars;
|
|
722
|
+
const res = await fetch(`${glBase(cfg)}/projects/${glProjectId(cfg)}/pipeline`, {
|
|
723
|
+
method: "POST",
|
|
724
|
+
headers: { "PRIVATE-TOKEN": cfg.token, "Content-Type": "application/json" },
|
|
725
|
+
body: JSON.stringify(body)
|
|
726
|
+
});
|
|
727
|
+
if (!res.ok) throw new Error(`GitLab trigger ${res.status}: ${await res.text()}`);
|
|
728
|
+
const data = await res.json();
|
|
729
|
+
return { pipelineId: data.id, url: data.web_url };
|
|
730
|
+
}
|
|
731
|
+
async function glPollPipeline(cfg, pipelineId, onTick, timeoutMs = 30 * 60 * 1e3, intervalMs = 15e3) {
|
|
732
|
+
const start = Date.now();
|
|
733
|
+
let consecutiveErrors = 0;
|
|
734
|
+
while (Date.now() - start < timeoutMs) {
|
|
735
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
736
|
+
let res;
|
|
737
|
+
try {
|
|
738
|
+
res = await fetch(
|
|
739
|
+
`${glBase(cfg)}/projects/${glProjectId(cfg)}/pipelines/${pipelineId}`,
|
|
740
|
+
{ headers: { "PRIVATE-TOKEN": cfg.token } }
|
|
741
|
+
);
|
|
742
|
+
} catch {
|
|
743
|
+
consecutiveErrors++;
|
|
744
|
+
if (consecutiveErrors >= 3) throw new Error("GitLab API \uC5F0\uC18D \uC624\uB958 3\uD68C");
|
|
745
|
+
continue;
|
|
746
|
+
}
|
|
747
|
+
if (!res.ok) {
|
|
748
|
+
consecutiveErrors++;
|
|
749
|
+
if (consecutiveErrors >= 3) throw new Error(`GitLab API \uC5F0\uC18D \uC624\uB958 (HTTP ${res.status})`);
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
752
|
+
consecutiveErrors = 0;
|
|
753
|
+
const data = await res.json();
|
|
754
|
+
onTick?.(data.status);
|
|
755
|
+
if (data.status === "success") return "success";
|
|
756
|
+
if (data.status === "failed") return "failure";
|
|
757
|
+
if (data.status === "canceled" || data.status === "skipped") return "cancelled";
|
|
758
|
+
}
|
|
759
|
+
return "timeout";
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// src/deploy.ts
|
|
763
|
+
var PHASE_ICON = {
|
|
764
|
+
init: "\u{1F680}",
|
|
765
|
+
verify: "\u{1F50D}",
|
|
766
|
+
notes: "\u{1F4DD}",
|
|
767
|
+
apply: "\u{1F4E4}",
|
|
768
|
+
promote: "\u{1F3AF}",
|
|
769
|
+
done: "\u{1F389}",
|
|
770
|
+
error: "\u274C"
|
|
771
|
+
};
|
|
772
|
+
function log(msg) {
|
|
773
|
+
process.stdout.write(msg + "\n");
|
|
774
|
+
}
|
|
775
|
+
function jenkinsHeaders(cfg) {
|
|
776
|
+
const creds = Buffer.from(`${cfg.username || "admin"}:${cfg.token}`).toString("base64");
|
|
777
|
+
return { Authorization: `Basic ${creds}`, "Content-Type": "application/json" };
|
|
778
|
+
}
|
|
779
|
+
async function triggerBuild(cfg, jobName, params) {
|
|
780
|
+
const qs = new URLSearchParams(params).toString();
|
|
781
|
+
const url = `${cfg.url}/job/${encodeURIComponent(jobName)}/buildWithParameters?${qs}`;
|
|
782
|
+
const res = await fetch(url, { method: "POST", headers: jenkinsHeaders(cfg) });
|
|
783
|
+
if (!res.ok) {
|
|
784
|
+
throw new Error(`Jenkins \uD2B8\uB9AC\uAC70 \uC2E4\uD328 ${res.status}: ${await res.text()}`);
|
|
785
|
+
}
|
|
786
|
+
const location = res.headers.get("Location") ?? "";
|
|
787
|
+
const match = location.match(/\/queue\/item\/(\d+)\//);
|
|
788
|
+
return match?.[1] ?? "";
|
|
789
|
+
}
|
|
790
|
+
async function getQueueBuildNumber(cfg, queueItemId) {
|
|
791
|
+
const url = `${cfg.url}/queue/item/${queueItemId}/api/json`;
|
|
792
|
+
const res = await fetch(url, { headers: jenkinsHeaders(cfg) });
|
|
793
|
+
if (!res.ok) return null;
|
|
794
|
+
const data = await res.json();
|
|
795
|
+
return data.executable?.number ?? null;
|
|
796
|
+
}
|
|
797
|
+
async function getBuildStatus(cfg, jobName, buildNumber) {
|
|
798
|
+
const url = `${cfg.url}/job/${encodeURIComponent(jobName)}/${buildNumber}/api/json`;
|
|
799
|
+
const res = await fetch(url, { headers: jenkinsHeaders(cfg) });
|
|
800
|
+
if (!res.ok) throw new Error(`\uBE4C\uB4DC \uC0C1\uD0DC \uC870\uD68C \uC2E4\uD328 ${res.status}`);
|
|
801
|
+
const data = await res.json();
|
|
802
|
+
return {
|
|
803
|
+
building: data.building ?? true,
|
|
804
|
+
result: data.result ?? null,
|
|
805
|
+
duration: data.duration ?? 0
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
async function pollBuildComplete(cfg, jobName, buildNumber, timeoutMs = 30 * 60 * 1e3) {
|
|
809
|
+
const start = Date.now();
|
|
810
|
+
const intervalMs = 15e3;
|
|
811
|
+
let dots = 0;
|
|
812
|
+
let consecutiveErrors = 0;
|
|
813
|
+
while (Date.now() - start < timeoutMs) {
|
|
814
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
815
|
+
let status;
|
|
816
|
+
try {
|
|
817
|
+
status = await getBuildStatus(cfg, jobName, buildNumber);
|
|
818
|
+
consecutiveErrors = 0;
|
|
819
|
+
} catch {
|
|
820
|
+
consecutiveErrors++;
|
|
821
|
+
if (consecutiveErrors >= 3) throw new Error("Jenkins \uC5F0\uACB0 \uC624\uB958 3\uD68C \uC5F0\uC18D \u2014 \uB124\uD2B8\uC6CC\uD06C\uB97C \uD655\uC778\uD558\uC138\uC694");
|
|
822
|
+
process.stdout.write(`\r \u26A0 Jenkins \uC5F0\uACB0 \uC624\uB958 (${consecutiveErrors}/3\uD68C), \uC7AC\uC2DC\uB3C4... `);
|
|
823
|
+
continue;
|
|
824
|
+
}
|
|
825
|
+
dots = (dots + 1) % 4;
|
|
826
|
+
process.stdout.write(`\r \u23F3 \uBE4C\uB4DC \uC9C4\uD589 \uC911${".".repeat(dots + 1)} `);
|
|
827
|
+
if (!status.building) {
|
|
828
|
+
process.stdout.write("\n");
|
|
829
|
+
return status.result ?? "FAILURE";
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
throw new Error("\uBE4C\uB4DC \uD0C0\uC784\uC544\uC6C3 (30\uBD84)");
|
|
833
|
+
}
|
|
834
|
+
async function streamDeploy(webBase, token, body) {
|
|
835
|
+
const res = await fetch(`${webBase}/api/deploy`, {
|
|
836
|
+
method: "POST",
|
|
837
|
+
headers: {
|
|
838
|
+
"Content-Type": "application/json",
|
|
839
|
+
Authorization: `Bearer ${token}`
|
|
840
|
+
},
|
|
841
|
+
body: JSON.stringify(body)
|
|
842
|
+
});
|
|
843
|
+
if (!res.ok) {
|
|
844
|
+
const text = await res.text().catch(() => "");
|
|
845
|
+
throw new Error(`\uC11C\uBC84 \uBC30\uD3EC \uC2E4\uD328 ${res.status}: ${text.slice(0, 200)}`);
|
|
846
|
+
}
|
|
847
|
+
const reader = res.body?.getReader();
|
|
848
|
+
if (!reader) throw new Error("SSE \uC2A4\uD2B8\uB9BC \uC5C6\uC74C");
|
|
849
|
+
const decoder = new TextDecoder();
|
|
850
|
+
let buf = "";
|
|
851
|
+
while (true) {
|
|
852
|
+
const { done, value } = await reader.read();
|
|
853
|
+
if (done) break;
|
|
854
|
+
buf += decoder.decode(value, { stream: true });
|
|
855
|
+
const lines = buf.split("\n");
|
|
856
|
+
buf = lines.pop() ?? "";
|
|
857
|
+
for (const line of lines) {
|
|
858
|
+
if (!line.startsWith("data: ")) continue;
|
|
859
|
+
try {
|
|
860
|
+
const event = JSON.parse(line.slice(6));
|
|
861
|
+
const icon = PHASE_ICON[event.phase] ?? "\u25B8";
|
|
862
|
+
const color = event.status === "done" ? kleur.green : event.status === "failed" ? kleur.red : event.status === "skipped" ? kleur.yellow : kleur.dim;
|
|
863
|
+
log(` ${icon} ${color(event.message)}`);
|
|
864
|
+
if (event.phase === "error" || event.phase === "verify" && event.status === "failed" || event.phase === "promote" && event.status === "failed") {
|
|
865
|
+
process.exit(1);
|
|
866
|
+
}
|
|
867
|
+
} catch {
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
function parseArgs(argv) {
|
|
873
|
+
const args = {
|
|
874
|
+
platform: "android",
|
|
875
|
+
language: "ko-KR",
|
|
876
|
+
dryRun: false,
|
|
877
|
+
yes: false,
|
|
878
|
+
skipBuild: false,
|
|
879
|
+
setupJenkins: false,
|
|
880
|
+
setupGithub: false,
|
|
881
|
+
setupGitlab: false,
|
|
882
|
+
ci: "auto",
|
|
883
|
+
ref: "main"
|
|
884
|
+
};
|
|
885
|
+
for (let i = 0; i < argv.length; i++) {
|
|
886
|
+
if ((argv[i] === "--platform" || argv[i] === "-p") && argv[i + 1]) args.platform = argv[++i];
|
|
887
|
+
if (argv[i] === "--app" && argv[i + 1]) args.appId = argv[++i];
|
|
888
|
+
if (argv[i] === "--version-code" && argv[i + 1]) args.versionCode = Number(argv[++i]);
|
|
889
|
+
if (argv[i] === "--from" && argv[i + 1]) args.fromRef = argv[++i];
|
|
890
|
+
if (argv[i] === "--to" && argv[i + 1]) args.toRef = argv[++i];
|
|
891
|
+
if (argv[i] === "--language" && argv[i + 1]) args.language = argv[++i];
|
|
892
|
+
if (argv[i] === "--dry-run") args.dryRun = true;
|
|
893
|
+
if (argv[i] === "--yes" || argv[i] === "-y") args.yes = true;
|
|
894
|
+
if (argv[i] === "--skip-build") args.skipBuild = true;
|
|
895
|
+
if (argv[i] === "--ci" && argv[i + 1]) args.ci = argv[++i];
|
|
896
|
+
if (argv[i] === "--workflow" && argv[i + 1]) args.workflow = argv[++i];
|
|
897
|
+
if (argv[i] === "--ref" && argv[i + 1]) args.ref = argv[++i];
|
|
898
|
+
if (argv[i] === "setup-jenkins") args.setupJenkins = true;
|
|
899
|
+
if (argv[i] === "setup-github") args.setupGithub = true;
|
|
900
|
+
if (argv[i] === "setup-gitlab") args.setupGitlab = true;
|
|
901
|
+
}
|
|
902
|
+
return args;
|
|
903
|
+
}
|
|
904
|
+
async function promptGitProviderSetup(provider) {
|
|
905
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
906
|
+
const ask2 = (q) => new Promise((resolve) => rl.question(q, (a) => resolve(a.trim())));
|
|
907
|
+
const isGh = provider === "github";
|
|
908
|
+
log(kleur.bold(isGh ? "GitHub Actions \uC124\uC815" : "GitLab CI \uC124\uC815"));
|
|
909
|
+
const tokenLabel = isGh ? " GitHub Personal Access Token (repo+workflow \uC2A4\uCF54\uD504): " : " GitLab Personal Access Token: ";
|
|
910
|
+
const token = await ask2(tokenLabel);
|
|
911
|
+
const owner = await ask2(isGh ? " Owner (org/user): " : " Namespace/group: ");
|
|
912
|
+
const repo = await ask2(" Repo \uC774\uB984 (\uACBD\uB85C \uC5C6\uC774): ");
|
|
913
|
+
const hostPrompt = isGh ? " GitHub Enterprise host (\uC120\uD0DD, \uC5D4\uD130=github.com): " : " GitLab self-hosted URL (\uC120\uD0DD, \uC5D4\uD130=gitlab.com): ";
|
|
914
|
+
const host = await ask2(hostPrompt);
|
|
915
|
+
rl.close();
|
|
916
|
+
return {
|
|
917
|
+
provider,
|
|
918
|
+
token,
|
|
919
|
+
owner,
|
|
920
|
+
repo,
|
|
921
|
+
host: host || void 0
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
function resolveCi(ciOption, jenkins, ciProvider) {
|
|
925
|
+
if (ciOption !== "auto") return ciOption;
|
|
926
|
+
if (jenkins?.url && jenkins.token) return "jenkins";
|
|
927
|
+
if (ciProvider) return ciProvider.provider;
|
|
928
|
+
throw new Error(
|
|
929
|
+
"CI \uC124\uC815 \uC5C6\uC74C. \uB2E4\uC74C \uC911 \uD558\uB098 \uC2E4\uD589:\n \u2022 mimi-seed deploy setup-jenkins\n \u2022 mimi-seed deploy setup-github\n \u2022 mimi-seed deploy setup-gitlab"
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
async function runGitProviderBuild(cfg, args) {
|
|
933
|
+
let runUrl = "";
|
|
934
|
+
let runId;
|
|
935
|
+
if (cfg.provider === "github") {
|
|
936
|
+
if (!args.workflow) {
|
|
937
|
+
throw new Error("--workflow \uD544\uC694 (\uC608: --workflow deploy.yml)");
|
|
938
|
+
}
|
|
939
|
+
log(`\u{1F528} GitHub Actions \uD2B8\uB9AC\uAC70: ${kleur.cyan(args.workflow)} @ ${args.ref}`);
|
|
940
|
+
const inputs = {};
|
|
941
|
+
if (args.appId) inputs.MIMI_APP_ID = args.appId;
|
|
942
|
+
inputs.PLATFORM = args.platform;
|
|
943
|
+
const result2 = await ghTriggerWorkflow(cfg, args.workflow, args.ref, inputs);
|
|
944
|
+
if (!result2) {
|
|
945
|
+
throw new Error("GitHub Actions run_id \uC870\uD68C \uC2E4\uD328. \uC7A0\uC2DC \uD6C4 ci_list_recent_builds \uB85C \uD655\uC778\uD558\uC138\uC694.");
|
|
946
|
+
}
|
|
947
|
+
runId = result2.runId;
|
|
948
|
+
runUrl = result2.url;
|
|
949
|
+
log(kleur.dim(` Run ID: ${runId} \u2192 ${runUrl}`));
|
|
950
|
+
} else {
|
|
951
|
+
log(`\u{1F528} GitLab Pipeline \uD2B8\uB9AC\uAC70: ${args.ref}`);
|
|
952
|
+
const variables = { PLATFORM: args.platform };
|
|
953
|
+
if (args.appId) variables.MIMI_APP_ID = args.appId;
|
|
954
|
+
const result2 = await glTriggerPipeline(cfg, args.ref, variables);
|
|
955
|
+
runId = result2.pipelineId;
|
|
956
|
+
runUrl = result2.url;
|
|
957
|
+
log(kleur.dim(` Pipeline ID: ${runId} \u2192 ${runUrl}`));
|
|
958
|
+
}
|
|
959
|
+
log(" \uC644\uB8CC \uB300\uAE30 \uC911...");
|
|
960
|
+
let dots = 0;
|
|
961
|
+
const onTick = (status) => {
|
|
962
|
+
dots = (dots + 1) % 4;
|
|
963
|
+
process.stdout.write(`\r \u23F3 ${status}${".".repeat(dots + 1)} `);
|
|
964
|
+
};
|
|
965
|
+
const result = cfg.provider === "github" ? await ghPollRun(cfg, runId, onTick) : await glPollPipeline(cfg, runId, onTick);
|
|
966
|
+
process.stdout.write("\n");
|
|
967
|
+
if (result === "success") {
|
|
968
|
+
log(kleur.green(`\u2705 \uBE4C\uB4DC #${runId} \uC131\uACF5`));
|
|
969
|
+
return runId;
|
|
970
|
+
}
|
|
971
|
+
log(kleur.red(`\uBE4C\uB4DC \uC885\uB8CC: ${result}`));
|
|
972
|
+
log(kleur.dim(` ${runUrl}`));
|
|
973
|
+
log(kleur.dim(` \uBE4C\uB4DC\uAC00 \uC774\uBBF8 \uC644\uB8CC\uB410\uB2E4\uBA74: mimi-seed deploy --skip-build --version-code <N> --platform ${args.platform}`));
|
|
974
|
+
process.exit(1);
|
|
975
|
+
}
|
|
976
|
+
async function cmdDeploy(argv) {
|
|
977
|
+
const args = parseArgs(argv);
|
|
978
|
+
const cfg = await getEffectiveConfig();
|
|
979
|
+
if (!cfg) {
|
|
980
|
+
log(kleur.red("\uC5F0\uACB0\uB41C \uACC4\uC815 \uC5C6\uC74C. `mimi-seed init` \uC2E4\uD589."));
|
|
981
|
+
process.exit(1);
|
|
982
|
+
}
|
|
983
|
+
if (args.setupJenkins) {
|
|
984
|
+
const code = await runMcpBin("mimi-seed-jenkins-auth");
|
|
985
|
+
if (code !== 0) process.exit(code);
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
if (args.setupGithub) {
|
|
989
|
+
const ciCfg = await promptGitProviderSetup("github");
|
|
990
|
+
saveCiProviderConfig(ciCfg);
|
|
991
|
+
log(kleur.green(`\u2705 GitHub Actions \uC124\uC815 \uC800\uC7A5\uB428 \u2192 ~/.mimi-seed/ci.json`));
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
if (args.setupGitlab) {
|
|
995
|
+
const ciCfg = await promptGitProviderSetup("gitlab");
|
|
996
|
+
saveCiProviderConfig(ciCfg);
|
|
997
|
+
log(kleur.green(`\u2705 GitLab CI \uC124\uC815 \uC800\uC7A5\uB428 \u2192 ~/.mimi-seed/ci.json`));
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
log(kleur.bold(`mimi-seed deploy \u2014 ${args.platform}`));
|
|
1001
|
+
if (args.dryRun) log(kleur.yellow(" [dry-run \uBAA8\uB4DC] \uC2E4\uC81C \uBC30\uD3EC\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4"));
|
|
1002
|
+
log("");
|
|
1003
|
+
let versionCode = args.versionCode;
|
|
1004
|
+
if (!args.skipBuild) {
|
|
1005
|
+
const ciProvider = loadCiProviderConfig();
|
|
1006
|
+
migrateLegacyJenkins();
|
|
1007
|
+
const jenkinsCfg = loadJenkinsConfig() ?? void 0;
|
|
1008
|
+
const kind = resolveCi(args.ci, jenkinsCfg, ciProvider);
|
|
1009
|
+
log(kleur.dim(` CI: ${kind}`));
|
|
1010
|
+
if (kind === "jenkins") {
|
|
1011
|
+
if (!jenkinsCfg?.url || !jenkinsCfg?.token) {
|
|
1012
|
+
log(kleur.yellow("Jenkins \uC124\uC815 \uC5C6\uC74C. `mimi-seed deploy setup-jenkins` \uB85C \uC124\uC815\uD558\uAC70\uB098 --skip-build \uC0AC\uC6A9."));
|
|
1013
|
+
process.exit(1);
|
|
1014
|
+
}
|
|
1015
|
+
const jenkins = jenkinsCfg;
|
|
1016
|
+
const jobName = args.platform === "android" ? jenkins.jobAndroid : jenkins.jobIos;
|
|
1017
|
+
if (!jobName) {
|
|
1018
|
+
log(kleur.red(`${args.platform} Jenkins job\uC774 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. setup-jenkins \uC2E4\uD589.`));
|
|
1019
|
+
process.exit(1);
|
|
1020
|
+
}
|
|
1021
|
+
log(`\u{1F528} Jenkins \uBE4C\uB4DC \uD2B8\uB9AC\uAC70: ${kleur.cyan(jobName)}`);
|
|
1022
|
+
const buildParams = {};
|
|
1023
|
+
if (args.appId) buildParams.MIMI_APP_ID = args.appId;
|
|
1024
|
+
const queueItemId = await triggerBuild(jenkins, jobName, buildParams);
|
|
1025
|
+
if (!queueItemId) {
|
|
1026
|
+
log(kleur.yellow(" \u26A0 Queue item ID\uB97C \uAC00\uC838\uC624\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uBE4C\uB4DC\uB294 \uC2DC\uC791\uB410\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4."));
|
|
1027
|
+
} else {
|
|
1028
|
+
log(kleur.dim(` Queue item: ${queueItemId}`));
|
|
1029
|
+
}
|
|
1030
|
+
let buildNumber = null;
|
|
1031
|
+
if (queueItemId) {
|
|
1032
|
+
log(" \uBE4C\uB4DC \uBC88\uD638 \uB300\uAE30 \uC911...");
|
|
1033
|
+
for (let i = 0; i < 6; i++) {
|
|
1034
|
+
await new Promise((r) => setTimeout(r, 5e3));
|
|
1035
|
+
buildNumber = await getQueueBuildNumber(jenkins, queueItemId).catch(() => null);
|
|
1036
|
+
if (buildNumber) break;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
if (!buildNumber) {
|
|
1040
|
+
log(kleur.yellow(" \uBE4C\uB4DC \uBC88\uD638\uB97C \uAC00\uC838\uC624\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. --skip-build + --version-code \uB85C \uC7AC\uC2DC\uB3C4 \uAC00\uB2A5."));
|
|
1041
|
+
process.exit(1);
|
|
1042
|
+
}
|
|
1043
|
+
log(` \uBE4C\uB4DC #${buildNumber} \uC2DC\uC791\uB428. \uC644\uB8CC \uB300\uAE30 \uC911...`);
|
|
1044
|
+
const result = await pollBuildComplete(jenkins, jobName, buildNumber);
|
|
1045
|
+
if (result !== "SUCCESS") {
|
|
1046
|
+
log(kleur.red(`\uBE4C\uB4DC \uC2E4\uD328: ${result}`));
|
|
1047
|
+
log(kleur.dim(` Jenkins: ${jenkins.url}/job/${encodeURIComponent(jobName)}/${buildNumber}/`));
|
|
1048
|
+
log(kleur.dim(` \uBE4C\uB4DC\uAC00 \uC774\uBBF8 \uC644\uB8CC\uB410\uB2E4\uBA74: mimi-seed deploy --skip-build --version-code ${buildNumber} --platform ${args.platform}`));
|
|
1049
|
+
process.exit(1);
|
|
1050
|
+
}
|
|
1051
|
+
log(kleur.green(`\u2705 \uBE4C\uB4DC #${buildNumber} \uC131\uACF5`));
|
|
1052
|
+
if (!versionCode) {
|
|
1053
|
+
versionCode = buildNumber;
|
|
1054
|
+
log(kleur.dim(` versionCode = buildNumber (${versionCode})`));
|
|
1055
|
+
}
|
|
1056
|
+
} else {
|
|
1057
|
+
if (!ciProvider) {
|
|
1058
|
+
log(kleur.red(`${kind} \uC124\uC815\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. setup-${kind} \uC2E4\uD589.`));
|
|
1059
|
+
process.exit(1);
|
|
1060
|
+
}
|
|
1061
|
+
const buildId = await runGitProviderBuild(ciProvider, args);
|
|
1062
|
+
if (!versionCode) {
|
|
1063
|
+
log(kleur.red(`\u2717 versionCode \uBBF8\uC9C0\uC815 (${kind} run_id ${buildId}\uB294 versionCode\uB85C \uBD80\uC801\uD569)`));
|
|
1064
|
+
log(kleur.dim(" \uAD8C\uC7A5 \uC0AC\uD56D:"));
|
|
1065
|
+
log(kleur.dim(" \u2022 CI \uC6CC\uD06C\uD50C\uB85C \uC548\uC5D0\uC11C versionCode\uB97C \uACB0\uC815\uD558\uACE0 \uACB0\uACFC\uB97C \uCD9C\uB825"));
|
|
1066
|
+
log(kleur.dim(" \u2022 \uB2E4\uC74C \uC2E4\uD589: mimi-seed deploy --skip-build --version-code <N>"));
|
|
1067
|
+
process.exit(1);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
if (!versionCode) {
|
|
1072
|
+
log(kleur.red("versionCode\uB97C \uC54C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. --version-code <N> \uC73C\uB85C \uC9C0\uC815\uD558\uC138\uC694."));
|
|
1073
|
+
process.exit(1);
|
|
1074
|
+
}
|
|
1075
|
+
let appId = args.appId;
|
|
1076
|
+
if (!appId) {
|
|
1077
|
+
const { mcpCall } = await import("./mcp-client-SLIKR42C.js");
|
|
1078
|
+
const r = await mcpCall(cfg.endpoint, cfg.token, "list_apps", {});
|
|
1079
|
+
if (!r.isError) {
|
|
1080
|
+
try {
|
|
1081
|
+
const apps = JSON.parse(r.text);
|
|
1082
|
+
if (apps.length > 0) {
|
|
1083
|
+
appId = apps[0].id;
|
|
1084
|
+
log(kleur.dim(` \uC571: ${apps[0].name} (${appId})`));
|
|
1085
|
+
}
|
|
1086
|
+
} catch {
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
if (!appId) {
|
|
1091
|
+
log(kleur.red("appId\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. --app <id> \uB85C \uC9C0\uC815\uD558\uAC70\uB098 `mimi-seed init` \uC73C\uB85C \uC571 \uB4F1\uB85D."));
|
|
1092
|
+
process.exit(1);
|
|
1093
|
+
}
|
|
1094
|
+
const needsConfirm = !args.dryRun && !args.yes && process.stdout.isTTY && !process.env.MIMI_SEED_TOKEN;
|
|
1095
|
+
if (needsConfirm) {
|
|
1096
|
+
const target = args.platform === "ios" ? "App Store \uC2EC\uC0AC \uC81C\uCD9C" : "Play Store production \uD2B8\uB799";
|
|
1097
|
+
log("");
|
|
1098
|
+
log(kleur.yellow(`\u26A0 \uC2E4\uC81C \uBC30\uD3EC: ${args.platform} \xB7 versionCode ${versionCode} \u2192 ${target}`));
|
|
1099
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1100
|
+
const answer = await new Promise(
|
|
1101
|
+
(resolve) => rl.question(kleur.bold("\uACC4\uC18D \uC9C4\uD589\uD560\uAE4C\uC694? [y/N]: "), (a) => resolve(a.trim().toLowerCase()))
|
|
1102
|
+
);
|
|
1103
|
+
rl.close();
|
|
1104
|
+
if (answer !== "y" && answer !== "yes") {
|
|
1105
|
+
log(kleur.dim("\uCDE8\uC18C\uB428. (--yes \uB85C \uD655\uC778 \uC0DD\uB7B5 \uAC00\uB2A5)"));
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
log("");
|
|
1110
|
+
log("\u{1F4E1} \uC11C\uBC84 \uBC30\uD3EC \uD30C\uC774\uD504\uB77C\uC778 \uC2DC\uC791...");
|
|
1111
|
+
log("");
|
|
1112
|
+
const deployBuildNumber = !args.skipBuild ? versionCode : void 0;
|
|
1113
|
+
await streamDeploy(cfg.webBase, cfg.token, {
|
|
1114
|
+
appId,
|
|
1115
|
+
platform: args.platform,
|
|
1116
|
+
versionCode,
|
|
1117
|
+
...deployBuildNumber ? { buildNumber: deployBuildNumber } : {},
|
|
1118
|
+
fromRef: args.fromRef,
|
|
1119
|
+
toRef: args.toRef,
|
|
1120
|
+
language: args.language,
|
|
1121
|
+
dryRun: args.dryRun
|
|
1122
|
+
});
|
|
1123
|
+
log("");
|
|
1124
|
+
log(kleur.bold("\uC644\uB8CC. Play Console\uC5D0\uC11C \uBC30\uD3EC \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uC138\uC694."));
|
|
1125
|
+
log(kleur.dim(" https://play.google.com/console/developers"));
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
// src/setup.ts
|
|
1129
|
+
function log2(msg = "") {
|
|
1130
|
+
process.stdout.write(msg + "\n");
|
|
1131
|
+
}
|
|
1132
|
+
var ALL_IDS = new Set(CREDENTIALS.map((c) => c.id));
|
|
1133
|
+
function parseIds(raw) {
|
|
1134
|
+
return raw.split(",").map((s) => s.trim()).filter((s) => ALL_IDS.has(s));
|
|
1135
|
+
}
|
|
1136
|
+
function parseSetupArgs(argv) {
|
|
1137
|
+
const opts = {
|
|
1138
|
+
yes: false,
|
|
1139
|
+
nonInteractive: false,
|
|
1140
|
+
interactive: false,
|
|
1141
|
+
failOnMissing: false
|
|
1142
|
+
};
|
|
1143
|
+
for (let i = 0; i < argv.length; i++) {
|
|
1144
|
+
const a = argv[i];
|
|
1145
|
+
if (a === "--yes" || a === "-y") opts.yes = true;
|
|
1146
|
+
else if (a === "--non-interactive") opts.nonInteractive = true;
|
|
1147
|
+
else if (a === "--interactive") opts.interactive = true;
|
|
1148
|
+
else if (a === "--fail-on-missing") opts.failOnMissing = true;
|
|
1149
|
+
else if (a === "--only" && argv[i + 1]) opts.only = parseIds(argv[++i]);
|
|
1150
|
+
else if (a === "--reconnect" && argv[i + 1]) opts.reconnect = parseIds(argv[++i]);
|
|
1151
|
+
else if (a === "--platform" && argv[i + 1]) {
|
|
1152
|
+
opts.platforms = argv[++i].split(",").map((s) => s.trim()).filter((s) => s === "android" || s === "ios");
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
return opts;
|
|
1156
|
+
}
|
|
1157
|
+
function resolveMode(opts, env, stdinIsTTY) {
|
|
1158
|
+
if (opts.nonInteractive || opts.yes) return "report-only";
|
|
1159
|
+
if (opts.interactive) return "interactive";
|
|
1160
|
+
if (env.CI) return "report-only";
|
|
1161
|
+
if (!stdinIsTTY) return "report-only";
|
|
1162
|
+
return "interactive";
|
|
1163
|
+
}
|
|
1164
|
+
function statusLine(spec, detected, platforms) {
|
|
1165
|
+
const d = detected.get(spec.id);
|
|
1166
|
+
const relevant = spec.requirement !== "platform" || platforms.length === 0 || platforms.includes(spec.platform);
|
|
1167
|
+
let mark;
|
|
1168
|
+
if (d.present) mark = kleur2.green("\u2713");
|
|
1169
|
+
else if (isSatisfied(spec, detected)) mark = kleur2.yellow("~");
|
|
1170
|
+
else if (spec.requirement === "optional" || !relevant) mark = kleur2.dim("\xB7");
|
|
1171
|
+
else mark = kleur2.red("\u2717");
|
|
1172
|
+
const tail = d.present ? kleur2.dim(d.detail ?? "") : isSatisfied(spec, detected) ? kleur2.dim(`${spec.note ?? "\uD3F4\uBC31\uC73C\uB85C \uB3D9\uC791 \uC911"}`) : kleur2.dim(`\u2192 ${spec.fix}`);
|
|
1173
|
+
return ` ${mark} ${spec.label.padEnd(24)} ${tail}`;
|
|
1174
|
+
}
|
|
1175
|
+
function printStatus(detected, platforms) {
|
|
1176
|
+
log2(kleur2.bold("\uC5F0\uACB0 \uC0C1\uD0DC ") + kleur2.dim("(~/.mimi-seed)"));
|
|
1177
|
+
log2();
|
|
1178
|
+
for (const group of ["core", "ci", "marketing"]) {
|
|
1179
|
+
const specs = CREDENTIALS.filter((c) => c.group === group);
|
|
1180
|
+
const title = group === "core" ? "\uD575\uC2EC" : group === "ci" ? "\uBE4C\uB4DC / CI" : "\uB9C8\uCF00\uD305 \xB7 AI";
|
|
1181
|
+
log2(kleur2.dim(` \u2500\u2500 ${title} \u2500\u2500`));
|
|
1182
|
+
for (const spec of specs) log2(statusLine(spec, detected, platforms));
|
|
1183
|
+
log2();
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
function ask(q) {
|
|
1187
|
+
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
1188
|
+
return new Promise((resolve) => {
|
|
1189
|
+
rl.question(q, (a) => {
|
|
1190
|
+
rl.close();
|
|
1191
|
+
resolve(a.trim());
|
|
1192
|
+
});
|
|
1193
|
+
rl.on("close", () => resolve(""));
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
1196
|
+
function printObtain(spec) {
|
|
1197
|
+
log2();
|
|
1198
|
+
log2(kleur2.bold(` ${spec.label} \u2014 \uBBF8\uB9AC \uC900\uBE44\uD560 \uAC83`));
|
|
1199
|
+
for (const line of spec.obtain) log2(kleur2.dim(` ${line}`));
|
|
1200
|
+
if (spec.docsAnchor) {
|
|
1201
|
+
log2(kleur2.dim(` \uC790\uC138\uD788: docs/credentials.md#${spec.docsAnchor}`));
|
|
1202
|
+
}
|
|
1203
|
+
log2();
|
|
1204
|
+
}
|
|
1205
|
+
async function connectOne(spec) {
|
|
1206
|
+
switch (spec.setup.kind) {
|
|
1207
|
+
case "mcp-bin": {
|
|
1208
|
+
const code = await runMcpBin(spec.setup.bin, spec.setup.args ?? []);
|
|
1209
|
+
if (code !== 0) {
|
|
1210
|
+
log2(kleur2.yellow(` \u26A0 ${spec.label} \uC124\uC815\uC774 \uC644\uB8CC\uB418\uC9C0 \uC54A\uC558\uC5B4 (exit ${code}). \uB098\uC911\uC5D0 \uB2E4\uC2DC: ${spec.fix}`));
|
|
1211
|
+
}
|
|
1212
|
+
return true;
|
|
1213
|
+
}
|
|
1214
|
+
case "cli": {
|
|
1215
|
+
const provider = spec.setup.handler;
|
|
1216
|
+
const cfg = await promptGitProviderSetup(provider);
|
|
1217
|
+
log2(kleur2.dim(" \u{1F50E} \uD1A0\uD070 \uAC80\uC99D \uC911..."));
|
|
1218
|
+
const check = await verifyCiToken(cfg);
|
|
1219
|
+
if (!check.ok) {
|
|
1220
|
+
log2(kleur2.red(` \u274C \uD1A0\uD070 \uAC80\uC99D \uC2E4\uD328: ${check.reason}`));
|
|
1221
|
+
log2(kleur2.dim(` \uC800\uC7A5\uD558\uC9C0 \uC54A\uC558\uC5B4. \uB2E4\uC2DC: ${spec.fix}`));
|
|
1222
|
+
return true;
|
|
1223
|
+
}
|
|
1224
|
+
saveCiProviderConfig(cfg);
|
|
1225
|
+
log2(kleur2.green(` \u2705 ${spec.label} \uC5F0\uACB0\uB428${check.login ? ` (${check.login})` : ""} \u2192 ~/.mimi-seed/ci.json`));
|
|
1226
|
+
return true;
|
|
1227
|
+
}
|
|
1228
|
+
case "command": {
|
|
1229
|
+
log2(kleur2.yellow(` \uC774\uAC74 \uBCC4\uB3C4 \uBA85\uB839\uC73C\uB85C \uC2E4\uD589\uD574\uC918: ${kleur2.cyan(spec.setup.run)}`));
|
|
1230
|
+
await ask(kleur2.dim(" (\uC5D4\uD130\uB97C \uB204\uB974\uBA74 \uACC4\uC18D) "));
|
|
1231
|
+
return true;
|
|
1232
|
+
}
|
|
1233
|
+
case "env": {
|
|
1234
|
+
log2(kleur2.yellow(` \uD658\uACBD\uBCC0\uC218\uB85C \uC124\uC815\uD558\uB294 \uD56D\uBAA9\uC774\uC57C:`));
|
|
1235
|
+
log2(kleur2.cyan(` export ${spec.setup.envVar}=...`));
|
|
1236
|
+
log2(kleur2.dim(` (Windows PowerShell: $env:${spec.setup.envVar} = "..." )`));
|
|
1237
|
+
await ask(kleur2.dim(" (\uC5D4\uD130\uB97C \uB204\uB974\uBA74 \uACC4\uC18D) "));
|
|
1238
|
+
return true;
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
async function cmdSetup(argv) {
|
|
1243
|
+
const opts = parseSetupArgs(argv);
|
|
1244
|
+
const home = os5.homedir();
|
|
1245
|
+
migrateLegacyJenkins(home);
|
|
1246
|
+
const hints = await detectHints(process.cwd());
|
|
1247
|
+
const platforms = opts.platforms ?? [
|
|
1248
|
+
hints.some((h) => h.packageName) ? "android" : null,
|
|
1249
|
+
hints.some((h) => h.bundleId) ? "ios" : null
|
|
1250
|
+
].filter(Boolean);
|
|
1251
|
+
log2(kleur2.bold("mimi-seed setup"));
|
|
1252
|
+
if (platforms.length > 0) log2(kleur2.dim(` \uAC10\uC9C0\uB41C \uD50C\uB7AB\uD3FC: ${platforms.join(", ")}`));
|
|
1253
|
+
log2();
|
|
1254
|
+
let detected = detectAll(home);
|
|
1255
|
+
printStatus(detected, platforms);
|
|
1256
|
+
const mode = resolveMode(opts, process.env, process.stdin.isTTY);
|
|
1257
|
+
const plan = planSetup(detected, {
|
|
1258
|
+
only: opts.only,
|
|
1259
|
+
reconnect: opts.reconnect,
|
|
1260
|
+
platforms
|
|
1261
|
+
});
|
|
1262
|
+
if (mode === "report-only") {
|
|
1263
|
+
const missing = missingRequired(detected, platforms);
|
|
1264
|
+
if (missing.length > 0) {
|
|
1265
|
+
log2(kleur2.yellow(" \uD544\uC218 \uD56D\uBAA9 \uB204\uB77D:"));
|
|
1266
|
+
for (const spec of missing) log2(kleur2.yellow(` \u2022 ${spec.label} \u2192 ${spec.fix}`));
|
|
1267
|
+
log2();
|
|
1268
|
+
}
|
|
1269
|
+
if (opts.only && plan.length > 0) {
|
|
1270
|
+
log2(kleur2.red(" \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:"));
|
|
1271
|
+
for (const spec of plan) log2(kleur2.red(` \u2022 ${spec.label}`));
|
|
1272
|
+
log2(kleur2.dim(" \uD130\uBBF8\uB110\uC5D0\uC11C \uC2E4\uD589\uD574\uC918 (Git Bash \uB4F1 TTY \uBBF8\uAC10\uC9C0 \uD658\uACBD\uC774\uBA74 --interactive)."));
|
|
1273
|
+
process.exit(1);
|
|
1274
|
+
}
|
|
1275
|
+
log2(kleur2.dim(" \uB300\uD654\uD615\uC73C\uB85C \uC5F0\uACB0\uD558\uB824\uBA74 \uD130\uBBF8\uB110\uC5D0\uC11C: mimi-seed setup"));
|
|
1276
|
+
if (opts.failOnMissing && missing.length > 0) process.exit(1);
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1279
|
+
if (plan.length === 0) {
|
|
1280
|
+
if (opts.only) {
|
|
1281
|
+
log2(kleur2.green(" \u2705 \uC694\uCCAD\uD55C \uD56D\uBAA9\uC740 \uC774\uBBF8 \uC5F0\uACB0\uB3FC \uC788\uC5B4."));
|
|
1282
|
+
log2(kleur2.dim(" \uB2E4\uC2DC \uC124\uC815\uD558\uB824\uBA74: mimi-seed setup --reconnect <id>"));
|
|
1283
|
+
} else {
|
|
1284
|
+
log2(kleur2.green(" \u2705 \uC5F0\uACB0\uD560 \uAC8C \uB354 \uC5C6\uC5B4. \uB2E4 \uB410\uB2E4."));
|
|
1285
|
+
}
|
|
1286
|
+
log2(kleur2.dim(" \uC810\uAC80: mimi-seed doctor"));
|
|
1287
|
+
if (opts.failOnMissing && missingRequired(detected, platforms).length > 0) process.exit(1);
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1290
|
+
log2(kleur2.dim(` ${plan.length}\uAC1C \uD56D\uBAA9\uC744 \uC21C\uC11C\uB300\uB85C \uBB3C\uC5B4\uBCFC\uAC8C. \uC5B8\uC81C\uB4E0 s=\uAC74\uB108\uB6F0\uAE30, q=\uC885\uB8CC.`));
|
|
1291
|
+
log2();
|
|
1292
|
+
let aborted = false;
|
|
1293
|
+
for (const spec of plan) {
|
|
1294
|
+
if (aborted) break;
|
|
1295
|
+
const req = spec.requirement === "optional" ? kleur2.dim("(\uC120\uD0DD)") : spec.requirement === "platform" ? kleur2.dim(`(${spec.platform} \uBC30\uD3EC\uC5D0 \uD544\uC694)`) : kleur2.red("(\uD544\uC218)");
|
|
1296
|
+
for (; ; ) {
|
|
1297
|
+
log2(kleur2.bold(`\u25B8 ${spec.label} ${req}`));
|
|
1298
|
+
if (spec.note) log2(kleur2.dim(` ${spec.note}`));
|
|
1299
|
+
const answer = (await ask(" [c] \uC5F0\uACB0 [s] \uAC74\uB108\uB6F0\uAE30 [?] \uC774\uAC74 \uC5B4\uB5BB\uAC8C \uAD6C\uD558\uB098\uC694 [q] \uC885\uB8CC : ")).toLowerCase();
|
|
1300
|
+
if (answer === "?" || answer === "h") {
|
|
1301
|
+
printObtain(spec);
|
|
1302
|
+
continue;
|
|
1303
|
+
}
|
|
1304
|
+
if (answer === "q") {
|
|
1305
|
+
log2();
|
|
1306
|
+
log2(kleur2.dim(" \uC911\uB2E8\uD588\uC5B4. \uC774\uC5B4\uC11C \uD558\uB824\uBA74 \uB2E4\uC2DC: mimi-seed setup"));
|
|
1307
|
+
aborted = true;
|
|
1308
|
+
break;
|
|
1309
|
+
}
|
|
1310
|
+
if (answer === "s" || answer === "") {
|
|
1311
|
+
log2(kleur2.dim(` \uAC74\uB108\uB700. \uB098\uC911\uC5D0: ${spec.fix}`));
|
|
1312
|
+
log2();
|
|
1313
|
+
break;
|
|
1314
|
+
}
|
|
1315
|
+
if (answer === "c") {
|
|
1316
|
+
await connectOne(spec);
|
|
1317
|
+
log2();
|
|
1318
|
+
break;
|
|
1319
|
+
}
|
|
1320
|
+
log2(kleur2.dim(" c / s / ? / q \uC911\uC5D0\uC11C \uACE8\uB77C\uC918."));
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
detected = detectAll(home);
|
|
1324
|
+
log2();
|
|
1325
|
+
printStatus(detected, platforms);
|
|
1326
|
+
const stillMissing = missingRequired(detected, platforms);
|
|
1327
|
+
if (stillMissing.length === 0) {
|
|
1328
|
+
log2(kleur2.green(" \u2705 \uD544\uC218 \uC5F0\uACB0 \uC644\uB8CC."));
|
|
1329
|
+
} else {
|
|
1330
|
+
log2(kleur2.yellow(" \uC544\uC9C1 \uD544\uC218 \uD56D\uBAA9\uC774 \uB0A8\uC544 \uC788\uC5B4:"));
|
|
1331
|
+
for (const spec of stillMissing) log2(kleur2.yellow(` \u2022 ${spec.label} \u2192 ${spec.fix}`));
|
|
1332
|
+
}
|
|
1333
|
+
log2(kleur2.dim(" \uC810\uAC80: mimi-seed doctor \xB7 \uBC30\uD3EC: mimi-seed deploy"));
|
|
1334
|
+
log2();
|
|
1335
|
+
if (opts.failOnMissing && stillMissing.length > 0) process.exit(1);
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
export {
|
|
1339
|
+
detectHints,
|
|
1340
|
+
hasAnyProjectSignal,
|
|
1341
|
+
writeConfig,
|
|
1342
|
+
deleteConfig,
|
|
1343
|
+
CONFIG_LOCATION,
|
|
1344
|
+
getEffectiveConfig,
|
|
1345
|
+
CREDENTIALS,
|
|
1346
|
+
tryCredById,
|
|
1347
|
+
detectAll,
|
|
1348
|
+
isSatisfied,
|
|
1349
|
+
migrateLegacyJenkins,
|
|
1350
|
+
MCP_PKG,
|
|
1351
|
+
runMcpBin,
|
|
1352
|
+
cmdDeploy,
|
|
1353
|
+
parseSetupArgs,
|
|
1354
|
+
resolveMode,
|
|
1355
|
+
cmdSetup
|
|
1356
|
+
};
|