mimi-seed 0.5.0 → 0.7.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/dist/{chunk-T757WVSG.js → chunk-47TS72IP.js} +533 -204
- package/dist/{chunk-DW3LOLPJ.js → chunk-QU2IDWJ2.js} +8 -1
- package/dist/chunk-SZQFVTEW.js +252 -0
- package/dist/index.js +997 -385
- package/dist/{mcp-client-SLIKR42C.js → mcp-client-PVWDCYJN.js} +2 -1
- package/dist/{setup-ENJIMGJF.js → setup-NQHLA3AJ.js} +2 -1
- package/package.json +1 -1
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
catalog,
|
|
4
|
+
isLangUnset,
|
|
5
|
+
resolveLang,
|
|
6
|
+
t,
|
|
7
|
+
writeSettings
|
|
8
|
+
} from "./chunk-SZQFVTEW.js";
|
|
2
9
|
|
|
3
10
|
// src/setup.ts
|
|
4
11
|
import kleur2 from "kleur";
|
|
@@ -9,6 +16,15 @@ import os5 from "os";
|
|
|
9
16
|
import fs from "fs";
|
|
10
17
|
import os from "os";
|
|
11
18
|
import path from "path";
|
|
19
|
+
function credLabel(spec, lang = resolveLang()) {
|
|
20
|
+
return spec.label[lang];
|
|
21
|
+
}
|
|
22
|
+
function credNote(spec, lang = resolveLang()) {
|
|
23
|
+
return spec.note?.[lang];
|
|
24
|
+
}
|
|
25
|
+
function credObtain(spec, lang = resolveLang()) {
|
|
26
|
+
return spec.obtain[lang];
|
|
27
|
+
}
|
|
12
28
|
function credDir(home) {
|
|
13
29
|
return path.join(home, ".mimi-seed");
|
|
14
30
|
}
|
|
@@ -45,16 +61,25 @@ function ciDetect(home, want) {
|
|
|
45
61
|
var CREDENTIALS = [
|
|
46
62
|
{
|
|
47
63
|
id: "mimiseed",
|
|
48
|
-
label:
|
|
64
|
+
label: {
|
|
65
|
+
ko: "Mimi Seed \uACC4\uC815 (\uD074\uB77C\uC6B0\uB4DC)",
|
|
66
|
+
en: "Mimi Seed account (cloud)"
|
|
67
|
+
},
|
|
49
68
|
requirement: "required",
|
|
50
69
|
group: "core",
|
|
51
70
|
file: "~/.mimi-seed/config.json",
|
|
52
71
|
fix: "mimi-seed init",
|
|
53
72
|
setup: { kind: "command", run: "mimi-seed init" },
|
|
54
|
-
obtain:
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
73
|
+
obtain: {
|
|
74
|
+
ko: [
|
|
75
|
+
"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.",
|
|
76
|
+
"CI \uC5D0\uC11C\uB294 MIMI_SEED_TOKEN \uD658\uACBD\uBCC0\uC218\uB85C \uB300\uCCB4\uD560 \uC218 \uC788\uB2E4."
|
|
77
|
+
],
|
|
78
|
+
en: [
|
|
79
|
+
"Sign up / sign in at https://mimi-seed.pryzm.gg \u2014 the browser hands the token back for you.",
|
|
80
|
+
"In CI, set the MIMI_SEED_TOKEN environment variable instead."
|
|
81
|
+
]
|
|
82
|
+
},
|
|
58
83
|
docsAnchor: "cloud-pat",
|
|
59
84
|
detect: (home) => {
|
|
60
85
|
if (process.env.MIMI_SEED_TOKEN) return { present: true, detail: "MIMI_SEED_TOKEN (env)" };
|
|
@@ -64,37 +89,62 @@ var CREDENTIALS = [
|
|
|
64
89
|
},
|
|
65
90
|
{
|
|
66
91
|
id: "oauth",
|
|
67
|
-
label:
|
|
92
|
+
label: {
|
|
93
|
+
ko: "Google OAuth",
|
|
94
|
+
en: "Google OAuth"
|
|
95
|
+
},
|
|
68
96
|
requirement: "required",
|
|
69
97
|
group: "core",
|
|
70
98
|
file: "~/.mimi-seed/tokens.json",
|
|
71
99
|
fix: "mimi-seed auth login",
|
|
72
100
|
setup: { kind: "mcp-bin", bin: "mimi-seed-auth" },
|
|
73
|
-
obtain:
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
101
|
+
obtain: {
|
|
102
|
+
ko: [
|
|
103
|
+
"\uBBF8\uB9AC \uBC1C\uAE09\uBC1B\uC744 \uAC8C **\uC5C6\uB2E4**. \uBE0C\uB77C\uC6B0\uC800 \uB85C\uADF8\uC778 \uD55C \uBC88\uC774\uBA74 \uB05D.",
|
|
104
|
+
"\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.",
|
|
105
|
+
"\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",
|
|
106
|
+
" \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."
|
|
107
|
+
],
|
|
108
|
+
en: [
|
|
109
|
+
"**Nothing** to fetch beforehand. One browser sign-in is all it takes.",
|
|
110
|
+
"This single token unlocks Firebase \xB7 AdMob \xB7 Play \xB7 Google Ads \xB7 Search Console \xB7 GA4 \xB7 IAM \xB7 BigQuery.",
|
|
111
|
+
"\u26A0\uFE0F If the login screen blocks you (access_denied), the app is still in 'testing' mode \u2014",
|
|
112
|
+
" ask whoever operates it to add your Google account under Test users. Retrying alone never works."
|
|
113
|
+
]
|
|
114
|
+
},
|
|
79
115
|
docsAnchor: "google-oauth",
|
|
80
|
-
note:
|
|
116
|
+
note: {
|
|
117
|
+
ko: "8\uAC1C Google \uC11C\uBE44\uC2A4\uC758 \uACF5\uD1B5 \uAD00\uBB38",
|
|
118
|
+
en: "the shared gateway to 8 Google services"
|
|
119
|
+
},
|
|
81
120
|
detect: (home) => ({ present: hasFile(home, "tokens.json") })
|
|
82
121
|
},
|
|
83
122
|
{
|
|
84
123
|
id: "appstore",
|
|
85
|
-
label:
|
|
124
|
+
label: {
|
|
125
|
+
ko: "App Store Connect",
|
|
126
|
+
en: "App Store Connect"
|
|
127
|
+
},
|
|
86
128
|
requirement: "platform",
|
|
87
129
|
platform: "ios",
|
|
88
130
|
group: "core",
|
|
89
131
|
file: "~/.mimi-seed/appstore.json",
|
|
90
132
|
fix: "mimi-seed auth appstore",
|
|
91
133
|
setup: { kind: "mcp-bin", bin: "mimi-seed-appstore-auth" },
|
|
92
|
-
obtain:
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
134
|
+
obtain: {
|
|
135
|
+
ko: [
|
|
136
|
+
"\uC720\uB8CC Apple Developer Program \uBA64\uBC84\uC2ED + Admin/App Manager \uAD8C\uD55C\uC774 \uBA3C\uC800 \uD544\uC694\uD558\uB2E4.",
|
|
137
|
+
"App Store Connect \u2192 \uC0AC\uC6A9\uC790 \uBC0F \uC561\uC138\uC2A4 \u2192 \uD1B5\uD569 \u2192 App Store Connect API \u2192 \uD0A4 \uC0DD\uC131",
|
|
138
|
+
"\uAC00\uC838\uC62C \uAC83 3\uAC1C: Issuer ID \xB7 Key ID \xB7 .p8 \uD30C\uC77C",
|
|
139
|
+
"\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."
|
|
140
|
+
],
|
|
141
|
+
en: [
|
|
142
|
+
"You need a **paid** Apple Developer Program membership and the Admin / App Manager role first.",
|
|
143
|
+
"App Store Connect \u2192 Users and Access \u2192 Integrations \u2192 App Store Connect API \u2192 generate a key",
|
|
144
|
+
"Collect three things: Issuer ID \xB7 Key ID \xB7 the .p8 file",
|
|
145
|
+
"\u26A0\uFE0F The .p8 downloads **exactly once**. Lose it and you must revoke the key and issue a new one."
|
|
146
|
+
]
|
|
147
|
+
},
|
|
98
148
|
docsAnchor: "app-store-connect",
|
|
99
149
|
detect: (home) => {
|
|
100
150
|
const cfg = readJson(home, "appstore.json");
|
|
@@ -103,7 +153,10 @@ var CREDENTIALS = [
|
|
|
103
153
|
},
|
|
104
154
|
{
|
|
105
155
|
id: "playstore",
|
|
106
|
-
label:
|
|
156
|
+
label: {
|
|
157
|
+
ko: "Play \uC11C\uBE44\uC2A4 \uACC4\uC815",
|
|
158
|
+
en: "Play service account"
|
|
159
|
+
},
|
|
107
160
|
// 선택이다 — helpers.ts 의 requirePlayStoreAuth 가 SA → OAuth 로 폴백하고,
|
|
108
161
|
// 로그인 시 androidpublisher 스코프를 이미 받으므로 로컬 작업은 OAuth 로 된다.
|
|
109
162
|
requirement: "optional",
|
|
@@ -112,83 +165,141 @@ var CREDENTIALS = [
|
|
|
112
165
|
fallbackOn: ["oauth"],
|
|
113
166
|
fix: "mimi-seed auth playstore",
|
|
114
167
|
setup: { kind: "mcp-bin", bin: "mimi-seed-playstore-auth" },
|
|
115
|
-
obtain:
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
168
|
+
obtain: {
|
|
169
|
+
ko: [
|
|
170
|
+
"\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.",
|
|
171
|
+
"CI/\uD5E4\uB4DC\uB9AC\uC2A4(\uBE0C\uB77C\uC6B0\uC800 \uC5C6\uB294 \uD658\uACBD)\uC5D0\uC11C\uB9CC \uD544\uC694:",
|
|
172
|
+
" 1. GCP Console \u2192 IAM & Admin \u2192 \uC11C\uBE44\uC2A4 \uACC4\uC815 \u2192 \uD0A4 \u2192 \uC0C8 \uD0A4 \uB9CC\uB4E4\uAE30 \u2192 JSON",
|
|
173
|
+
" 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",
|
|
174
|
+
" 3. \uAD8C\uD55C \uC804\uD30C\uC5D0 ~5\uBD84 \uAC78\uB9B0\uB2E4. \uADF8 \uC804\uC5D4 403 \uC774 \uB728\uB294 \uAC8C \uC815\uC0C1\uC774\uB2E4.",
|
|
175
|
+
"Claude \uC5D0\uAC8C `setup_playstore_connection` \uC744 \uC2DC\uD0A4\uBA74 1\uBC88\uC744 \uC790\uB3D9\uD654\uD574\uC900\uB2E4."
|
|
176
|
+
],
|
|
177
|
+
en: [
|
|
178
|
+
"Working locally? You **don't need this** \u2014 your Google OAuth login covers most Play work.",
|
|
179
|
+
"Only needed in CI / headless environments (no browser):",
|
|
180
|
+
" 1. GCP Console \u2192 IAM & Admin \u2192 Service Accounts \u2192 Keys \u2192 Add key \u2192 Create new key \u2192 JSON",
|
|
181
|
+
" 2. Play Console \u2192 Settings \u2192 Users and permissions \u2192 invite that service account's email",
|
|
182
|
+
" 3. The grant takes ~5 min to propagate. Until then a 403 is expected, not a bug.",
|
|
183
|
+
"Ask Claude to run `setup_playstore_connection` and step 1 is done for you."
|
|
184
|
+
]
|
|
185
|
+
},
|
|
123
186
|
docsAnchor: "play-service-account",
|
|
124
|
-
note:
|
|
187
|
+
note: {
|
|
188
|
+
ko: "\uC120\uD0DD \u2014 \uB85C\uCEEC\uC740 OAuth \uB85C \uAC00\uB2A5, CI/\uD5E4\uB4DC\uB9AC\uC2A4\uB294 \uD544\uC218",
|
|
189
|
+
en: "optional \u2014 OAuth covers local; required for CI / headless"
|
|
190
|
+
},
|
|
125
191
|
detect: (home) => ({ present: hasPlaySa(home) })
|
|
126
192
|
},
|
|
127
193
|
{
|
|
128
194
|
id: "bigquery",
|
|
129
|
-
label:
|
|
195
|
+
label: {
|
|
196
|
+
ko: "BigQuery \uC11C\uBE44\uC2A4 \uACC4\uC815",
|
|
197
|
+
en: "BigQuery service account"
|
|
198
|
+
},
|
|
130
199
|
requirement: "optional",
|
|
131
200
|
group: "core",
|
|
132
201
|
file: "~/.mimi-seed/bigquery-service-account.json",
|
|
133
202
|
fallbackOn: ["oauth"],
|
|
134
203
|
fix: "mimi-seed auth bigquery",
|
|
135
204
|
setup: { kind: "mcp-bin", bin: "mimi-seed-bigquery-auth" },
|
|
136
|
-
obtain:
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
205
|
+
obtain: {
|
|
206
|
+
ko: [
|
|
207
|
+
"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.",
|
|
208
|
+
"GCP Console \u2192 IAM & Admin \u2192 \uC11C\uBE44\uC2A4 \uACC4\uC815 \u2192 \uD0A4 \u2192 \uC0C8 \uD0A4 \uB9CC\uB4E4\uAE30 \u2192 JSON",
|
|
209
|
+
"\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):",
|
|
210
|
+
" \u2022 roles/bigquery.jobUser (\uCFFC\uB9AC \uC2E4\uD589)",
|
|
211
|
+
" \u2022 roles/bigquery.dataViewer (\uB370\uC774\uD130\uC14B \uC77D\uAE30)"
|
|
212
|
+
],
|
|
213
|
+
en: [
|
|
214
|
+
"OAuth works too \u2014 a service account only matters if Workspace reauth policy (invalid_rapt) keeps blocking you.",
|
|
215
|
+
"GCP Console \u2192 IAM & Admin \u2192 Service Accounts \u2192 Keys \u2192 Add key \u2192 Create new key \u2192 JSON",
|
|
216
|
+
"You must grant that service account these IAM roles **yourself** (nothing does it for you):",
|
|
217
|
+
" \u2022 roles/bigquery.jobUser (run queries)",
|
|
218
|
+
" \u2022 roles/bigquery.dataViewer (read datasets)"
|
|
219
|
+
]
|
|
220
|
+
},
|
|
143
221
|
docsAnchor: "bigquery",
|
|
144
|
-
note:
|
|
222
|
+
note: {
|
|
223
|
+
ko: "\uC120\uD0DD \u2014 OAuth \uD3F4\uBC31 \uAC00\uB2A5",
|
|
224
|
+
en: "optional \u2014 falls back to OAuth"
|
|
225
|
+
},
|
|
145
226
|
detect: (home) => ({ present: anyFileStarting(home, "bigquery") })
|
|
146
227
|
},
|
|
147
228
|
{
|
|
148
229
|
id: "github",
|
|
149
|
-
label:
|
|
230
|
+
label: {
|
|
231
|
+
ko: "GitHub Actions",
|
|
232
|
+
en: "GitHub Actions"
|
|
233
|
+
},
|
|
150
234
|
requirement: "optional",
|
|
151
235
|
group: "ci",
|
|
152
236
|
file: "~/.mimi-seed/ci.json",
|
|
153
237
|
fix: "mimi-seed auth ci",
|
|
154
238
|
setup: { kind: "cli", handler: "github" },
|
|
155
|
-
obtain:
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
239
|
+
obtain: {
|
|
240
|
+
ko: [
|
|
241
|
+
"GitHub \u2192 Settings \u2192 Developer settings \u2192 Personal access tokens",
|
|
242
|
+
"\u26A0\uFE0F \uC2A4\uCF54\uD504 **`repo` \uC640 `workflow` \uB97C \uB458 \uB2E4** \uCCB4\uD06C\uD574\uC57C \uD55C\uB2E4.",
|
|
243
|
+
" `workflow` \uAC00 \uC5C6\uC73C\uBA74 \uC6CC\uD06C\uD50C\uB85C dispatch \uAC00 403 \uC73C\uB85C \uB9C9\uD78C\uB2E4.",
|
|
244
|
+
"\uADF8 \uC678: owner(\uC870\uC9C1/\uC0AC\uC6A9\uC790) \xB7 repo \uC774\uB984. GitHub Enterprise \uBA74 host URL \uB3C4."
|
|
245
|
+
],
|
|
246
|
+
en: [
|
|
247
|
+
"GitHub \u2192 Settings \u2192 Developer settings \u2192 Personal access tokens",
|
|
248
|
+
"\u26A0\uFE0F Check **both the `repo` and `workflow`** scopes.",
|
|
249
|
+
" Without `workflow`, dispatching a workflow fails with 403 (reading still works).",
|
|
250
|
+
"Also: owner (org/user) \xB7 repo name. Plus the host URL on GitHub Enterprise."
|
|
251
|
+
]
|
|
252
|
+
},
|
|
161
253
|
docsAnchor: "ci-github-gitlab",
|
|
162
254
|
detect: (home) => ciDetect(home, "github")
|
|
163
255
|
},
|
|
164
256
|
{
|
|
165
257
|
id: "gitlab",
|
|
166
|
-
label:
|
|
258
|
+
label: {
|
|
259
|
+
ko: "GitLab CI",
|
|
260
|
+
en: "GitLab CI"
|
|
261
|
+
},
|
|
167
262
|
requirement: "optional",
|
|
168
263
|
group: "ci",
|
|
169
264
|
file: "~/.mimi-seed/ci.json",
|
|
170
265
|
fix: "mimi-seed auth ci",
|
|
171
266
|
setup: { kind: "cli", handler: "gitlab" },
|
|
172
|
-
obtain:
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
267
|
+
obtain: {
|
|
268
|
+
ko: [
|
|
269
|
+
"GitLab \u2192 \uC0AC\uC6A9\uC790 \uC124\uC815 \u2192 Access Tokens \u2192 `api` \uC2A4\uCF54\uD504\uB85C \uBC1C\uAE09 (glpat-\u2026)",
|
|
270
|
+
"\uADF8 \uC678: namespace/group \xB7 \uD504\uB85C\uC81D\uD2B8 \uC774\uB984. self-hosted \uBA74 URL \uB3C4."
|
|
271
|
+
],
|
|
272
|
+
en: [
|
|
273
|
+
"GitLab \u2192 User settings \u2192 Access tokens \u2192 issue one with the `api` scope (glpat-\u2026)",
|
|
274
|
+
"Also: namespace/group \xB7 project name. Plus the URL if self-hosted."
|
|
275
|
+
]
|
|
276
|
+
},
|
|
176
277
|
docsAnchor: "ci-github-gitlab",
|
|
177
278
|
detect: (home) => ciDetect(home, "gitlab")
|
|
178
279
|
},
|
|
179
280
|
{
|
|
180
281
|
id: "jenkins",
|
|
181
|
-
label:
|
|
282
|
+
label: {
|
|
283
|
+
ko: "Jenkins",
|
|
284
|
+
en: "Jenkins"
|
|
285
|
+
},
|
|
182
286
|
requirement: "optional",
|
|
183
287
|
group: "ci",
|
|
184
288
|
file: "~/.mimi-seed/jenkins.json",
|
|
185
289
|
fix: "mimi-seed auth jenkins",
|
|
186
290
|
setup: { kind: "mcp-bin", bin: "mimi-seed-jenkins-auth" },
|
|
187
|
-
obtain:
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
291
|
+
obtain: {
|
|
292
|
+
ko: [
|
|
293
|
+
'Jenkins \u2192 [\uC0AC\uC6A9\uC790 \uC774\uB984] \u2192 \uC124\uC815 \u2192 API Token \u2192 "Add new Token"',
|
|
294
|
+
"\uBE44\uBC00\uBC88\uD638\uAC00 \uC544\uB2C8\uB77C **API Token** \uC774\uB2E4.",
|
|
295
|
+
"\uB85C\uCEEC Jenkins \uAC00 \uC5C6\uC5B4\uB3C4 \uD68C\uC0AC/\uC6D0\uACA9 \uC11C\uBC84 URL \uC744 \uADF8\uB300\uB85C \uC4F0\uBA74 \uB41C\uB2E4."
|
|
296
|
+
],
|
|
297
|
+
en: [
|
|
298
|
+
'Jenkins \u2192 [your name] \u2192 Configure \u2192 API Token \u2192 "Add new Token"',
|
|
299
|
+
"That is an **API token**, not your password.",
|
|
300
|
+
"No local Jenkins needed \u2014 a company or remote server URL works just as well."
|
|
301
|
+
]
|
|
302
|
+
},
|
|
192
303
|
docsAnchor: "jenkins",
|
|
193
304
|
detect: (home) => {
|
|
194
305
|
const cfg = readJson(home, "jenkins.json");
|
|
@@ -197,19 +308,31 @@ var CREDENTIALS = [
|
|
|
197
308
|
},
|
|
198
309
|
{
|
|
199
310
|
id: "googleads",
|
|
200
|
-
label:
|
|
311
|
+
label: {
|
|
312
|
+
ko: "Google Ads",
|
|
313
|
+
en: "Google Ads"
|
|
314
|
+
},
|
|
201
315
|
requirement: "optional",
|
|
202
316
|
group: "marketing",
|
|
203
317
|
file: "~/.mimi-seed/google-ads.json",
|
|
204
318
|
fix: "mimi-seed auth googleads",
|
|
205
319
|
setup: { kind: "mcp-bin", bin: "mimi-seed-googleads-auth" },
|
|
206
|
-
obtain:
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
320
|
+
obtain: {
|
|
321
|
+
ko: [
|
|
322
|
+
"Google Ads \u2192 \uB3C4\uAD6C \uBC0F \uC124\uC815 \u2192 \uC124\uC815 \u2192 API \uC13C\uD130 \u2192 Developer Token \uBC1C\uAE09",
|
|
323
|
+
"\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,",
|
|
324
|
+
" \uC2EC\uC0AC\uC5D0\uB294 \uC2DC\uAC04\uC774 \uAC78\uB9B0\uB2E4 (\uC989\uC2DC \uBC1C\uAE09\uB418\uC9C0 \uC54A\uB294\uB2E4).",
|
|
325
|
+
"\uADF8 \uC678: Customer ID (\uC608: 123-456-7890). MCC \uB97C \uC4F0\uBA74 \uAD00\uB9AC\uC790 \uACC4\uC815 ID \uB3C4.",
|
|
326
|
+
"\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."
|
|
327
|
+
],
|
|
328
|
+
en: [
|
|
329
|
+
"Google Ads \u2192 Tools and settings \u2192 Setup \u2192 API Center \u2192 apply for a developer token",
|
|
330
|
+
"\u26A0\uFE0F The token you get immediately is 'test' tier. Reaching real account data requires Google to **approve**",
|
|
331
|
+
" your application, and that takes time (it is not instant).",
|
|
332
|
+
"Also: your Customer ID (e.g. 123-456-7890), plus the manager (MCC) account ID if you use one.",
|
|
333
|
+
"Auth itself rides on Google OAuth's `adwords` scope \u2014 an older token may need a re-login."
|
|
334
|
+
]
|
|
335
|
+
},
|
|
213
336
|
docsAnchor: "google-ads",
|
|
214
337
|
detect: (home) => {
|
|
215
338
|
const cfg = readJson(home, "google-ads.json");
|
|
@@ -218,18 +341,29 @@ var CREDENTIALS = [
|
|
|
218
341
|
},
|
|
219
342
|
{
|
|
220
343
|
id: "facebook",
|
|
221
|
-
label:
|
|
344
|
+
label: {
|
|
345
|
+
ko: "Facebook \uD398\uC774\uC9C0",
|
|
346
|
+
en: "Facebook Page"
|
|
347
|
+
},
|
|
222
348
|
requirement: "optional",
|
|
223
349
|
group: "marketing",
|
|
224
350
|
file: "~/.mimi-seed/facebook.json",
|
|
225
351
|
fix: "mimi-seed auth facebook",
|
|
226
352
|
setup: { kind: "mcp-bin", bin: "mimi-seed-social-auth", args: ["facebook"] },
|
|
227
|
-
obtain:
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
353
|
+
obtain: {
|
|
354
|
+
ko: [
|
|
355
|
+
"Meta \uAC1C\uBC1C\uC790 \uC571 + \uB300\uC0C1 \uD398\uC774\uC9C0\uC758 \uAD00\uB9AC\uC790 \uAD8C\uD55C\uC774 \uBA3C\uC800 \uD544\uC694\uD558\uB2E4.",
|
|
356
|
+
"Graph API Explorer \u2192 \uAD8C\uD55C pages_show_list, pages_manage_posts, pages_read_engagement",
|
|
357
|
+
"\u2192 User Token \uC0DD\uC131 \u2192 /me/accounts \uD638\uCD9C \u2192 \uADF8 \uD398\uC774\uC9C0\uC758 access_token (EAA\u2026)",
|
|
358
|
+
"long-lived \uD1A0\uD070\uC744 \uAD8C\uC7A5\uD55C\uB2E4 (\uC57D 60\uC77C)."
|
|
359
|
+
],
|
|
360
|
+
en: [
|
|
361
|
+
"You need a Meta developer app and admin rights on the target Page first.",
|
|
362
|
+
"Graph API Explorer \u2192 permissions pages_show_list, pages_manage_posts, pages_read_engagement",
|
|
363
|
+
"\u2192 generate a User token \u2192 call /me/accounts \u2192 copy that Page's access_token (EAA\u2026)",
|
|
364
|
+
"Prefer a long-lived token (~60 days)."
|
|
365
|
+
]
|
|
366
|
+
},
|
|
233
367
|
docsAnchor: "facebook",
|
|
234
368
|
detect: (home) => {
|
|
235
369
|
const cfg = readJson(home, "facebook.json");
|
|
@@ -238,18 +372,29 @@ var CREDENTIALS = [
|
|
|
238
372
|
},
|
|
239
373
|
{
|
|
240
374
|
id: "instagram",
|
|
241
|
-
label:
|
|
375
|
+
label: {
|
|
376
|
+
ko: "Instagram",
|
|
377
|
+
en: "Instagram"
|
|
378
|
+
},
|
|
242
379
|
requirement: "optional",
|
|
243
380
|
group: "marketing",
|
|
244
381
|
file: "~/.mimi-seed/instagram.json",
|
|
245
382
|
fix: "mimi-seed auth instagram",
|
|
246
383
|
setup: { kind: "mcp-bin", bin: "mimi-seed-social-auth", args: ["instagram"] },
|
|
247
|
-
obtain:
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
384
|
+
obtain: {
|
|
385
|
+
ko: [
|
|
386
|
+
"long-lived \uD1A0\uD070 \uB450 \uD615\uC2DD \uBAA8\uB450 \uC9C0\uC6D0\uD55C\uB2E4 (\uC790\uB3D9 \uAC10\uC9C0):",
|
|
387
|
+
" \u2022 IGAA\u2026 \u2014 Instagram Login (Meta \uC2E0\uADDC \uBC29\uC2DD, Facebook \uD398\uC774\uC9C0 \uBD88\uD544\uC694)",
|
|
388
|
+
" \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)",
|
|
389
|
+
"\uD1A0\uD070 \uC218\uBA85\uC740 \uC57D 60\uC77C\uC774\uB2E4."
|
|
390
|
+
],
|
|
391
|
+
en: [
|
|
392
|
+
"Both long-lived token shapes are supported (auto-detected):",
|
|
393
|
+
" \u2022 IGAA\u2026 \u2014 Instagram Login (Meta's newer path, no Facebook Page required)",
|
|
394
|
+
" \u2022 EAA\u2026 \u2014 Facebook Login (needs an Instagram **Business** account linked to a FB Page)",
|
|
395
|
+
"Tokens last about 60 days."
|
|
396
|
+
]
|
|
397
|
+
},
|
|
253
398
|
docsAnchor: "instagram",
|
|
254
399
|
detect: (home) => {
|
|
255
400
|
const cfg = readJson(home, "instagram.json");
|
|
@@ -258,18 +403,31 @@ var CREDENTIALS = [
|
|
|
258
403
|
},
|
|
259
404
|
{
|
|
260
405
|
id: "anthropic",
|
|
261
|
-
label:
|
|
406
|
+
label: {
|
|
407
|
+
ko: "ANTHROPIC_API_KEY",
|
|
408
|
+
en: "ANTHROPIC_API_KEY"
|
|
409
|
+
},
|
|
262
410
|
requirement: "optional",
|
|
263
411
|
group: "marketing",
|
|
264
412
|
fix: "export ANTHROPIC_API_KEY=sk-ant-\u2026",
|
|
265
413
|
setup: { kind: "env", envVar: "ANTHROPIC_API_KEY" },
|
|
266
|
-
obtain:
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
414
|
+
obtain: {
|
|
415
|
+
ko: [
|
|
416
|
+
"Anthropic Console (console.anthropic.com) \u2192 API Keys \uC5D0\uC11C \uBC1C\uAE09.",
|
|
417
|
+
"\uD658\uACBD\uBCC0\uC218\uB85C\uB9CC \uC77D\uB294\uB2E4 \u2014 \uC124\uC815 \uBA85\uB839\uC774 \uB530\uB85C \uC5C6\uB2E4.",
|
|
418
|
+
"\uC5C6\uC5B4\uB3C4 \uB41C\uB2E4: `mimi-seed notes` \uB294 AI \uC5C6\uC774 \uCEE4\uBC0B \uBAA9\uB85D \uAE30\uBC18\uC73C\uB85C \uB3D9\uC791\uD55C\uB2E4."
|
|
419
|
+
],
|
|
420
|
+
en: [
|
|
421
|
+
"Anthropic Console (console.anthropic.com) \u2192 API Keys.",
|
|
422
|
+
"Read from the environment only \u2014 there is no setup command.",
|
|
423
|
+
"Optional: without it, `mimi-seed notes` still works, formatting your commits instead of writing prose."
|
|
424
|
+
]
|
|
425
|
+
},
|
|
271
426
|
docsAnchor: "anthropic-api-key",
|
|
272
|
-
note:
|
|
427
|
+
note: {
|
|
428
|
+
ko: "\uC120\uD0DD \u2014 AI \uB9B4\uB9AC\uC2A4 \uB178\uD2B8/\uB9AC\uBDF0 \uB2F5\uBCC0 \uC0DD\uC131\uC6A9",
|
|
429
|
+
en: "optional \u2014 for AI-drafted release notes / review replies"
|
|
430
|
+
},
|
|
273
431
|
detect: () => ({ present: Boolean(process.env.ANTHROPIC_API_KEY) })
|
|
274
432
|
}
|
|
275
433
|
];
|
|
@@ -323,11 +481,13 @@ async function runMcpBin(bin, extraArgs = []) {
|
|
|
323
481
|
const cmd = local ? bin : "npx";
|
|
324
482
|
const args = local ? extraArgs : ["-y", MCP_PKG, bin, ...extraArgs];
|
|
325
483
|
return new Promise((resolve) => {
|
|
326
|
-
const child = spawn(cmd, args, {
|
|
484
|
+
const child = spawn(cmd, args, {
|
|
485
|
+
stdio: "inherit",
|
|
486
|
+
shell: true,
|
|
487
|
+
env: { ...process.env, MIMI_SEED_LANG: resolveLang() }
|
|
488
|
+
});
|
|
327
489
|
child.on("error", (e) => {
|
|
328
|
-
process.stderr.write(
|
|
329
|
-
\u274C ${cmd} \uC2E4\uD589 \uC2E4\uD328: ${e.message}
|
|
330
|
-
`);
|
|
490
|
+
process.stderr.write(t().auth.npxFailed(cmd, e.message));
|
|
331
491
|
resolve(1);
|
|
332
492
|
});
|
|
333
493
|
child.on("exit", (code) => resolve(code ?? 0));
|
|
@@ -583,6 +743,21 @@ import fs5 from "fs";
|
|
|
583
743
|
import path5 from "path";
|
|
584
744
|
import os4 from "os";
|
|
585
745
|
var CI_CONFIG_PATH = path5.join(os4.homedir(), ".mimi-seed", "ci.json");
|
|
746
|
+
var M = catalog(
|
|
747
|
+
{
|
|
748
|
+
badToken: (provider, status) => `${provider} ${status} \u2014 \uD1A0\uD070\uC774 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC544`,
|
|
749
|
+
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.`,
|
|
750
|
+
pollFailed: (provider) => `${provider} API \uC5F0\uC18D \uC624\uB958 3\uD68C`,
|
|
751
|
+
pollFailedHttp: (provider, status) => `${provider} API \uC5F0\uC18D \uC624\uB958 (HTTP ${status})`
|
|
752
|
+
},
|
|
753
|
+
{
|
|
754
|
+
badToken: (provider, status) => `${provider} ${status} \u2014 the token is not valid`,
|
|
755
|
+
noScope: (scopes) => `The token is missing the \`workflow\` scope (currently: ${scopes || "none"}). Triggering a workflow will be blocked with a 403.`,
|
|
756
|
+
// 프로바이더 이름("GitHub API")은 두 언어 모두에 남는다 — 테스트가 그걸로 단언한다.
|
|
757
|
+
pollFailed: (provider) => `${provider} API failed 3 times in a row`,
|
|
758
|
+
pollFailedHttp: (provider, status) => `${provider} API failed 3 times in a row (HTTP ${status})`
|
|
759
|
+
}
|
|
760
|
+
);
|
|
586
761
|
function loadCiProviderConfig() {
|
|
587
762
|
try {
|
|
588
763
|
const cfg = JSON.parse(fs5.readFileSync(CI_CONFIG_PATH, "utf-8"));
|
|
@@ -618,21 +793,18 @@ async function verifyCiToken(cfg) {
|
|
|
618
793
|
headers: { Authorization: `Bearer ${probe.token}`, Accept: "application/vnd.github+json" }
|
|
619
794
|
});
|
|
620
795
|
if (!res2.ok) {
|
|
621
|
-
return { ok: false, reason:
|
|
796
|
+
return { ok: false, reason: M().badToken("GitHub", res2.status) };
|
|
622
797
|
}
|
|
623
798
|
const user2 = await res2.json();
|
|
624
799
|
const scopes = res2.headers.get("x-oauth-scopes");
|
|
625
800
|
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
|
-
};
|
|
801
|
+
return { ok: false, reason: M().noScope(scopes) };
|
|
630
802
|
}
|
|
631
803
|
return { ok: true, login: user2.login };
|
|
632
804
|
}
|
|
633
805
|
const res = await fetch(`${glBase(probe)}/user`, { headers: { "PRIVATE-TOKEN": probe.token } });
|
|
634
806
|
if (!res.ok) {
|
|
635
|
-
return { ok: false, reason:
|
|
807
|
+
return { ok: false, reason: M().badToken("GitLab", res.status) };
|
|
636
808
|
}
|
|
637
809
|
const user = await res.json();
|
|
638
810
|
return { ok: true, login: user.username };
|
|
@@ -690,12 +862,12 @@ async function ghPollRun(cfg, runId, onTick, timeoutMs = 30 * 60 * 1e3, interval
|
|
|
690
862
|
);
|
|
691
863
|
} catch {
|
|
692
864
|
consecutiveErrors++;
|
|
693
|
-
if (consecutiveErrors >= 3) throw new Error("GitHub
|
|
865
|
+
if (consecutiveErrors >= 3) throw new Error(M().pollFailed("GitHub"));
|
|
694
866
|
continue;
|
|
695
867
|
}
|
|
696
868
|
if (!res.ok) {
|
|
697
869
|
consecutiveErrors++;
|
|
698
|
-
if (consecutiveErrors >= 3) throw new Error(
|
|
870
|
+
if (consecutiveErrors >= 3) throw new Error(M().pollFailedHttp("GitHub", res.status));
|
|
699
871
|
continue;
|
|
700
872
|
}
|
|
701
873
|
consecutiveErrors = 0;
|
|
@@ -741,12 +913,12 @@ async function glPollPipeline(cfg, pipelineId, onTick, timeoutMs = 30 * 60 * 1e3
|
|
|
741
913
|
);
|
|
742
914
|
} catch {
|
|
743
915
|
consecutiveErrors++;
|
|
744
|
-
if (consecutiveErrors >= 3) throw new Error("GitLab
|
|
916
|
+
if (consecutiveErrors >= 3) throw new Error(M().pollFailed("GitLab"));
|
|
745
917
|
continue;
|
|
746
918
|
}
|
|
747
919
|
if (!res.ok) {
|
|
748
920
|
consecutiveErrors++;
|
|
749
|
-
if (consecutiveErrors >= 3) throw new Error(
|
|
921
|
+
if (consecutiveErrors >= 3) throw new Error(M().pollFailedHttp("GitLab", res.status));
|
|
750
922
|
continue;
|
|
751
923
|
}
|
|
752
924
|
consecutiveErrors = 0;
|
|
@@ -772,6 +944,144 @@ var PHASE_ICON = {
|
|
|
772
944
|
function log(msg) {
|
|
773
945
|
process.stdout.write(msg + "\n");
|
|
774
946
|
}
|
|
947
|
+
var M2 = catalog(
|
|
948
|
+
{
|
|
949
|
+
// Jenkins / 빌드
|
|
950
|
+
jenkinsTriggerFailed: (status, body) => `Jenkins \uD2B8\uB9AC\uAC70 \uC2E4\uD328 ${status}: ${body}`,
|
|
951
|
+
buildStatusFailed: (status) => `\uBE4C\uB4DC \uC0C1\uD0DC \uC870\uD68C \uC2E4\uD328 ${status}`,
|
|
952
|
+
jenkinsConnErrorFatal: "Jenkins \uC5F0\uACB0 \uC624\uB958 3\uD68C \uC5F0\uC18D \u2014 \uB124\uD2B8\uC6CC\uD06C\uB97C \uD655\uC778\uD558\uC138\uC694",
|
|
953
|
+
jenkinsConnErrorRetry: (n) => `Jenkins \uC5F0\uACB0 \uC624\uB958 (${n}/3\uD68C), \uC7AC\uC2DC\uB3C4...`,
|
|
954
|
+
buildRunning: "\uBE4C\uB4DC \uC9C4\uD589 \uC911",
|
|
955
|
+
buildTimeout: "\uBE4C\uB4DC \uD0C0\uC784\uC544\uC6C3 (30\uBD84)",
|
|
956
|
+
// 서버 스트림
|
|
957
|
+
serverDeployFailed: (status, body) => `\uC11C\uBC84 \uBC30\uD3EC \uC2E4\uD328 ${status}: ${body}`,
|
|
958
|
+
noSseStream: "SSE \uC2A4\uD2B8\uB9BC \uC5C6\uC74C",
|
|
959
|
+
// CI provider 설정 프롬프트
|
|
960
|
+
githubSetupTitle: "GitHub Actions \uC124\uC815",
|
|
961
|
+
gitlabSetupTitle: "GitLab CI \uC124\uC815",
|
|
962
|
+
githubTokenPrompt: " GitHub Personal Access Token (repo+workflow \uC2A4\uCF54\uD504): ",
|
|
963
|
+
gitlabTokenPrompt: " GitLab Personal Access Token: ",
|
|
964
|
+
githubOwnerPrompt: " Owner (org/user): ",
|
|
965
|
+
gitlabOwnerPrompt: " Namespace/group: ",
|
|
966
|
+
repoPrompt: " Repo \uC774\uB984 (\uACBD\uB85C \uC5C6\uC774): ",
|
|
967
|
+
githubHostPrompt: " GitHub Enterprise host (\uC120\uD0DD, \uC5D4\uD130=github.com): ",
|
|
968
|
+
gitlabHostPrompt: " GitLab self-hosted URL (\uC120\uD0DD, \uC5D4\uD130=gitlab.com): ",
|
|
969
|
+
githubSaved: "\u2705 GitHub Actions \uC124\uC815 \uC800\uC7A5\uB428 \u2192 ~/.mimi-seed/ci.json",
|
|
970
|
+
gitlabSaved: "\u2705 GitLab CI \uC124\uC815 \uC800\uC7A5\uB428 \u2192 ~/.mimi-seed/ci.json",
|
|
971
|
+
noCiConfig: "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",
|
|
972
|
+
// GitHub / GitLab 빌드
|
|
973
|
+
workflowRequired: "--workflow \uD544\uC694 (\uC608: --workflow deploy.yml)",
|
|
974
|
+
ghTrigger: (workflow, ref) => `\u{1F528} GitHub Actions \uD2B8\uB9AC\uAC70: ${workflow} @ ${ref}`,
|
|
975
|
+
ghRunIdFailed: "GitHub Actions run_id \uC870\uD68C \uC2E4\uD328. \uC7A0\uC2DC \uD6C4 ci_list_recent_builds \uB85C \uD655\uC778\uD558\uC138\uC694.",
|
|
976
|
+
ghRunId: (id, url) => ` Run ID: ${id} \u2192 ${url}`,
|
|
977
|
+
glTrigger: (ref) => `\u{1F528} GitLab Pipeline \uD2B8\uB9AC\uAC70: ${ref}`,
|
|
978
|
+
glPipelineId: (id, url) => ` Pipeline ID: ${id} \u2192 ${url}`,
|
|
979
|
+
waitingForCompletion: " \uC644\uB8CC \uB300\uAE30 \uC911...",
|
|
980
|
+
buildSucceeded: (n) => `\u2705 \uBE4C\uB4DC #${n} \uC131\uACF5`,
|
|
981
|
+
buildEnded: (result) => `\uBE4C\uB4DC \uC885\uB8CC: ${result}`,
|
|
982
|
+
alreadyBuiltHint: (versionCode, platform) => ` \uBE4C\uB4DC\uAC00 \uC774\uBBF8 \uC644\uB8CC\uB410\uB2E4\uBA74: mimi-seed deploy --skip-build --version-code ${versionCode} --platform ${platform}`,
|
|
983
|
+
// cmdDeploy
|
|
984
|
+
noAccount: "\uC5F0\uACB0\uB41C \uACC4\uC815 \uC5C6\uC74C. `mimi-seed init` \uC2E4\uD589.",
|
|
985
|
+
title: (platform) => `mimi-seed deploy \u2014 ${platform}`,
|
|
986
|
+
dryRunNotice: " [dry-run \uBAA8\uB4DC] \uC2E4\uC81C \uBC30\uD3EC\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4",
|
|
987
|
+
ciLine: (kind) => ` CI: ${kind}`,
|
|
988
|
+
noJenkinsConfig: "Jenkins \uC124\uC815 \uC5C6\uC74C. `mimi-seed deploy setup-jenkins` \uB85C \uC124\uC815\uD558\uAC70\uB098 --skip-build \uC0AC\uC6A9.",
|
|
989
|
+
noJenkinsJob: (platform) => `${platform} Jenkins job\uC774 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. setup-jenkins \uC2E4\uD589.`,
|
|
990
|
+
jenkinsTrigger: (job) => `\u{1F528} Jenkins \uBE4C\uB4DC \uD2B8\uB9AC\uAC70: ${job}`,
|
|
991
|
+
noQueueItem: " \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.",
|
|
992
|
+
queueItem: (id) => ` Queue item: ${id}`,
|
|
993
|
+
waitingForBuildNumber: " \uBE4C\uB4DC \uBC88\uD638 \uB300\uAE30 \uC911...",
|
|
994
|
+
noBuildNumber: " \uBE4C\uB4DC \uBC88\uD638\uB97C \uAC00\uC838\uC624\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. --skip-build + --version-code \uB85C \uC7AC\uC2DC\uB3C4 \uAC00\uB2A5.",
|
|
995
|
+
buildStarted: (n) => ` \uBE4C\uB4DC #${n} \uC2DC\uC791\uB428. \uC644\uB8CC \uB300\uAE30 \uC911...`,
|
|
996
|
+
buildFailed: (result) => `\uBE4C\uB4DC \uC2E4\uD328: ${result}`,
|
|
997
|
+
jenkinsLink: (url) => ` Jenkins: ${url}`,
|
|
998
|
+
versionCodeFromBuild: (n) => ` versionCode = buildNumber (${n})`,
|
|
999
|
+
noProviderConfig: (kind) => `${kind} \uC124\uC815\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. setup-${kind} \uC2E4\uD589.`,
|
|
1000
|
+
versionCodeUnsuitable: (kind, buildId) => `\u2717 versionCode \uBBF8\uC9C0\uC815 (${kind} run_id ${buildId}\uB294 versionCode\uB85C \uBD80\uC801\uD569)`,
|
|
1001
|
+
recommendation: " \uAD8C\uC7A5 \uC0AC\uD56D:",
|
|
1002
|
+
recommendationCi: " \u2022 CI \uC6CC\uD06C\uD50C\uB85C \uC548\uC5D0\uC11C versionCode\uB97C \uACB0\uC815\uD558\uACE0 \uACB0\uACFC\uB97C \uCD9C\uB825",
|
|
1003
|
+
recommendationNext: " \u2022 \uB2E4\uC74C \uC2E4\uD589: mimi-seed deploy --skip-build --version-code <N>",
|
|
1004
|
+
versionCodeUnknown: "versionCode\uB97C \uC54C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. --version-code <N> \uC73C\uB85C \uC9C0\uC815\uD558\uC138\uC694.",
|
|
1005
|
+
appLine: (name, id) => ` \uC571: ${name} (${id})`,
|
|
1006
|
+
noAppId: "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.",
|
|
1007
|
+
// 프로덕션 확인
|
|
1008
|
+
targetIos: "App Store \uC2EC\uC0AC \uC81C\uCD9C",
|
|
1009
|
+
targetAndroid: "Play Store production \uD2B8\uB799",
|
|
1010
|
+
realDeploy: (platform, versionCode, target) => `\u26A0 \uC2E4\uC81C \uBC30\uD3EC: ${platform} \xB7 versionCode ${versionCode} \u2192 ${target}`,
|
|
1011
|
+
confirmPrompt: "\uACC4\uC18D \uC9C4\uD589\uD560\uAE4C\uC694? [y/N]: ",
|
|
1012
|
+
confirmCancelled: "\uCDE8\uC18C\uB428. (--yes \uB85C \uD655\uC778 \uC0DD\uB7B5 \uAC00\uB2A5)",
|
|
1013
|
+
pipelineStarting: "\u{1F4E1} \uC11C\uBC84 \uBC30\uD3EC \uD30C\uC774\uD504\uB77C\uC778 \uC2DC\uC791...",
|
|
1014
|
+
done: "\uC644\uB8CC. Play Console\uC5D0\uC11C \uBC30\uD3EC \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uC138\uC694."
|
|
1015
|
+
},
|
|
1016
|
+
{
|
|
1017
|
+
// Jenkins / build
|
|
1018
|
+
jenkinsTriggerFailed: (status, body) => `Jenkins trigger failed ${status}: ${body}`,
|
|
1019
|
+
buildStatusFailed: (status) => `Failed to fetch build status ${status}`,
|
|
1020
|
+
jenkinsConnErrorFatal: "Jenkins connection failed 3 times in a row \u2014 check your network",
|
|
1021
|
+
jenkinsConnErrorRetry: (n) => `Jenkins connection error (${n}/3), retrying...`,
|
|
1022
|
+
buildRunning: "Build running",
|
|
1023
|
+
buildTimeout: "Build timed out (30 min)",
|
|
1024
|
+
// Server stream
|
|
1025
|
+
serverDeployFailed: (status, body) => `Server deploy failed ${status}: ${body}`,
|
|
1026
|
+
noSseStream: "No SSE stream",
|
|
1027
|
+
// CI provider setup prompts
|
|
1028
|
+
githubSetupTitle: "GitHub Actions setup",
|
|
1029
|
+
gitlabSetupTitle: "GitLab CI setup",
|
|
1030
|
+
githubTokenPrompt: " GitHub Personal Access Token (repo+workflow scopes): ",
|
|
1031
|
+
gitlabTokenPrompt: " GitLab Personal Access Token: ",
|
|
1032
|
+
githubOwnerPrompt: " Owner (org/user): ",
|
|
1033
|
+
gitlabOwnerPrompt: " Namespace/group: ",
|
|
1034
|
+
repoPrompt: " Repo name (without the path): ",
|
|
1035
|
+
githubHostPrompt: " GitHub Enterprise host (optional, Enter = github.com): ",
|
|
1036
|
+
gitlabHostPrompt: " GitLab self-hosted URL (optional, Enter = gitlab.com): ",
|
|
1037
|
+
githubSaved: "\u2705 GitHub Actions config saved \u2192 ~/.mimi-seed/ci.json",
|
|
1038
|
+
gitlabSaved: "\u2705 GitLab CI config saved \u2192 ~/.mimi-seed/ci.json",
|
|
1039
|
+
noCiConfig: "No CI configured. Run one of:\n \u2022 mimi-seed deploy setup-jenkins\n \u2022 mimi-seed deploy setup-github\n \u2022 mimi-seed deploy setup-gitlab",
|
|
1040
|
+
// GitHub / GitLab build
|
|
1041
|
+
workflowRequired: "--workflow is required (e.g. --workflow deploy.yml)",
|
|
1042
|
+
ghTrigger: (workflow, ref) => `\u{1F528} Triggering GitHub Actions: ${workflow} @ ${ref}`,
|
|
1043
|
+
ghRunIdFailed: "Could not resolve the GitHub Actions run_id. Check with ci_list_recent_builds in a moment.",
|
|
1044
|
+
ghRunId: (id, url) => ` Run ID: ${id} \u2192 ${url}`,
|
|
1045
|
+
glTrigger: (ref) => `\u{1F528} Triggering GitLab Pipeline: ${ref}`,
|
|
1046
|
+
glPipelineId: (id, url) => ` Pipeline ID: ${id} \u2192 ${url}`,
|
|
1047
|
+
waitingForCompletion: " Waiting for it to finish...",
|
|
1048
|
+
buildSucceeded: (n) => `\u2705 Build #${n} succeeded`,
|
|
1049
|
+
buildEnded: (result) => `Build ended: ${result}`,
|
|
1050
|
+
alreadyBuiltHint: (versionCode, platform) => ` If the build already finished: mimi-seed deploy --skip-build --version-code ${versionCode} --platform ${platform}`,
|
|
1051
|
+
// cmdDeploy
|
|
1052
|
+
noAccount: "No account connected. Run `mimi-seed init`.",
|
|
1053
|
+
title: (platform) => `mimi-seed deploy \u2014 ${platform}`,
|
|
1054
|
+
dryRunNotice: " [dry-run] nothing will actually be deployed",
|
|
1055
|
+
ciLine: (kind) => ` CI: ${kind}`,
|
|
1056
|
+
noJenkinsConfig: "Jenkins is not configured. Run `mimi-seed deploy setup-jenkins`, or use --skip-build.",
|
|
1057
|
+
noJenkinsJob: (platform) => `No Jenkins job configured for ${platform}. Run setup-jenkins.`,
|
|
1058
|
+
jenkinsTrigger: (job) => `\u{1F528} Triggering Jenkins build: ${job}`,
|
|
1059
|
+
noQueueItem: " \u26A0 Could not read the queue item ID. The build may have started anyway.",
|
|
1060
|
+
queueItem: (id) => ` Queue item: ${id}`,
|
|
1061
|
+
waitingForBuildNumber: " Waiting for the build number...",
|
|
1062
|
+
noBuildNumber: " Could not read the build number. Retry with --skip-build + --version-code.",
|
|
1063
|
+
buildStarted: (n) => ` Build #${n} started. Waiting for it to finish...`,
|
|
1064
|
+
buildFailed: (result) => `Build failed: ${result}`,
|
|
1065
|
+
jenkinsLink: (url) => ` Jenkins: ${url}`,
|
|
1066
|
+
versionCodeFromBuild: (n) => ` versionCode = buildNumber (${n})`,
|
|
1067
|
+
noProviderConfig: (kind) => `${kind} is not configured. Run setup-${kind}.`,
|
|
1068
|
+
versionCodeUnsuitable: (kind, buildId) => `\u2717 No versionCode given (${kind} run_id ${buildId} is not usable as a versionCode)`,
|
|
1069
|
+
recommendation: " Recommended:",
|
|
1070
|
+
recommendationCi: " \u2022 Decide the versionCode inside the CI workflow and print it",
|
|
1071
|
+
recommendationNext: " \u2022 Then run: mimi-seed deploy --skip-build --version-code <N>",
|
|
1072
|
+
versionCodeUnknown: "Unknown versionCode. Pass it with --version-code <N>.",
|
|
1073
|
+
appLine: (name, id) => ` App: ${name} (${id})`,
|
|
1074
|
+
noAppId: "Could not resolve the appId. Pass --app <id>, or register the app with `mimi-seed init`.",
|
|
1075
|
+
// Production confirmation
|
|
1076
|
+
targetIos: "App Store review submission",
|
|
1077
|
+
targetAndroid: "Play Store production track",
|
|
1078
|
+
realDeploy: (platform, versionCode, target) => `\u26A0 Real deploy: ${platform} \xB7 versionCode ${versionCode} \u2192 ${target}`,
|
|
1079
|
+
confirmPrompt: "Continue? [y/N]: ",
|
|
1080
|
+
confirmCancelled: "Cancelled. (use --yes to skip this confirmation)",
|
|
1081
|
+
pipelineStarting: "\u{1F4E1} Starting the server deploy pipeline...",
|
|
1082
|
+
done: "Done. Check the deploy status in Play Console."
|
|
1083
|
+
}
|
|
1084
|
+
);
|
|
775
1085
|
function jenkinsHeaders(cfg) {
|
|
776
1086
|
const creds = Buffer.from(`${cfg.username || "admin"}:${cfg.token}`).toString("base64");
|
|
777
1087
|
return { Authorization: `Basic ${creds}`, "Content-Type": "application/json" };
|
|
@@ -781,7 +1091,7 @@ async function triggerBuild(cfg, jobName, params) {
|
|
|
781
1091
|
const url = `${cfg.url}/job/${encodeURIComponent(jobName)}/buildWithParameters?${qs}`;
|
|
782
1092
|
const res = await fetch(url, { method: "POST", headers: jenkinsHeaders(cfg) });
|
|
783
1093
|
if (!res.ok) {
|
|
784
|
-
throw new Error(
|
|
1094
|
+
throw new Error(M2().jenkinsTriggerFailed(res.status, await res.text()));
|
|
785
1095
|
}
|
|
786
1096
|
const location = res.headers.get("Location") ?? "";
|
|
787
1097
|
const match = location.match(/\/queue\/item\/(\d+)\//);
|
|
@@ -797,7 +1107,7 @@ async function getQueueBuildNumber(cfg, queueItemId) {
|
|
|
797
1107
|
async function getBuildStatus(cfg, jobName, buildNumber) {
|
|
798
1108
|
const url = `${cfg.url}/job/${encodeURIComponent(jobName)}/${buildNumber}/api/json`;
|
|
799
1109
|
const res = await fetch(url, { headers: jenkinsHeaders(cfg) });
|
|
800
|
-
if (!res.ok) throw new Error(
|
|
1110
|
+
if (!res.ok) throw new Error(M2().buildStatusFailed(res.status));
|
|
801
1111
|
const data = await res.json();
|
|
802
1112
|
return {
|
|
803
1113
|
building: data.building ?? true,
|
|
@@ -818,18 +1128,18 @@ async function pollBuildComplete(cfg, jobName, buildNumber, timeoutMs = 30 * 60
|
|
|
818
1128
|
consecutiveErrors = 0;
|
|
819
1129
|
} catch {
|
|
820
1130
|
consecutiveErrors++;
|
|
821
|
-
if (consecutiveErrors >= 3) throw new Error(
|
|
822
|
-
process.stdout.write(`\r \u26A0
|
|
1131
|
+
if (consecutiveErrors >= 3) throw new Error(M2().jenkinsConnErrorFatal);
|
|
1132
|
+
process.stdout.write(`\r \u26A0 ${M2().jenkinsConnErrorRetry(consecutiveErrors)} `);
|
|
823
1133
|
continue;
|
|
824
1134
|
}
|
|
825
1135
|
dots = (dots + 1) % 4;
|
|
826
|
-
process.stdout.write(`\r \u23F3
|
|
1136
|
+
process.stdout.write(`\r \u23F3 ${M2().buildRunning}${".".repeat(dots + 1)} `);
|
|
827
1137
|
if (!status.building) {
|
|
828
1138
|
process.stdout.write("\n");
|
|
829
1139
|
return status.result ?? "FAILURE";
|
|
830
1140
|
}
|
|
831
1141
|
}
|
|
832
|
-
throw new Error(
|
|
1142
|
+
throw new Error(M2().buildTimeout);
|
|
833
1143
|
}
|
|
834
1144
|
async function streamDeploy(webBase, token, body) {
|
|
835
1145
|
const res = await fetch(`${webBase}/api/deploy`, {
|
|
@@ -842,10 +1152,10 @@ async function streamDeploy(webBase, token, body) {
|
|
|
842
1152
|
});
|
|
843
1153
|
if (!res.ok) {
|
|
844
1154
|
const text = await res.text().catch(() => "");
|
|
845
|
-
throw new Error(
|
|
1155
|
+
throw new Error(M2().serverDeployFailed(res.status, text.slice(0, 200)));
|
|
846
1156
|
}
|
|
847
1157
|
const reader = res.body?.getReader();
|
|
848
|
-
if (!reader) throw new Error(
|
|
1158
|
+
if (!reader) throw new Error(M2().noSseStream);
|
|
849
1159
|
const decoder = new TextDecoder();
|
|
850
1160
|
let buf = "";
|
|
851
1161
|
while (true) {
|
|
@@ -905,12 +1215,13 @@ async function promptGitProviderSetup(provider) {
|
|
|
905
1215
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
906
1216
|
const ask2 = (q) => new Promise((resolve) => rl.question(q, (a) => resolve(a.trim())));
|
|
907
1217
|
const isGh = provider === "github";
|
|
908
|
-
|
|
909
|
-
|
|
1218
|
+
const m = M2();
|
|
1219
|
+
log(kleur.bold(isGh ? m.githubSetupTitle : m.gitlabSetupTitle));
|
|
1220
|
+
const tokenLabel = isGh ? m.githubTokenPrompt : m.gitlabTokenPrompt;
|
|
910
1221
|
const token = await ask2(tokenLabel);
|
|
911
|
-
const owner = await ask2(isGh ?
|
|
912
|
-
const repo = await ask2(
|
|
913
|
-
const hostPrompt = isGh ?
|
|
1222
|
+
const owner = await ask2(isGh ? m.githubOwnerPrompt : m.gitlabOwnerPrompt);
|
|
1223
|
+
const repo = await ask2(m.repoPrompt);
|
|
1224
|
+
const hostPrompt = isGh ? m.githubHostPrompt : m.gitlabHostPrompt;
|
|
914
1225
|
const host = await ask2(hostPrompt);
|
|
915
1226
|
rl.close();
|
|
916
1227
|
return {
|
|
@@ -925,38 +1236,36 @@ function resolveCi(ciOption, jenkins, ciProvider) {
|
|
|
925
1236
|
if (ciOption !== "auto") return ciOption;
|
|
926
1237
|
if (jenkins?.url && jenkins.token) return "jenkins";
|
|
927
1238
|
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
|
-
);
|
|
1239
|
+
throw new Error(M2().noCiConfig);
|
|
931
1240
|
}
|
|
932
1241
|
async function runGitProviderBuild(cfg, args) {
|
|
933
1242
|
let runUrl = "";
|
|
934
1243
|
let runId;
|
|
935
1244
|
if (cfg.provider === "github") {
|
|
936
1245
|
if (!args.workflow) {
|
|
937
|
-
throw new Error(
|
|
1246
|
+
throw new Error(M2().workflowRequired);
|
|
938
1247
|
}
|
|
939
|
-
log(
|
|
1248
|
+
log(M2().ghTrigger(kleur.cyan(args.workflow), args.ref));
|
|
940
1249
|
const inputs = {};
|
|
941
1250
|
if (args.appId) inputs.MIMI_APP_ID = args.appId;
|
|
942
1251
|
inputs.PLATFORM = args.platform;
|
|
943
1252
|
const result2 = await ghTriggerWorkflow(cfg, args.workflow, args.ref, inputs);
|
|
944
1253
|
if (!result2) {
|
|
945
|
-
throw new Error(
|
|
1254
|
+
throw new Error(M2().ghRunIdFailed);
|
|
946
1255
|
}
|
|
947
1256
|
runId = result2.runId;
|
|
948
1257
|
runUrl = result2.url;
|
|
949
|
-
log(kleur.dim(
|
|
1258
|
+
log(kleur.dim(M2().ghRunId(runId, runUrl)));
|
|
950
1259
|
} else {
|
|
951
|
-
log(
|
|
1260
|
+
log(M2().glTrigger(args.ref));
|
|
952
1261
|
const variables = { PLATFORM: args.platform };
|
|
953
1262
|
if (args.appId) variables.MIMI_APP_ID = args.appId;
|
|
954
1263
|
const result2 = await glTriggerPipeline(cfg, args.ref, variables);
|
|
955
1264
|
runId = result2.pipelineId;
|
|
956
1265
|
runUrl = result2.url;
|
|
957
|
-
log(kleur.dim(
|
|
1266
|
+
log(kleur.dim(M2().glPipelineId(runId, runUrl)));
|
|
958
1267
|
}
|
|
959
|
-
log(
|
|
1268
|
+
log(M2().waitingForCompletion);
|
|
960
1269
|
let dots = 0;
|
|
961
1270
|
const onTick = (status) => {
|
|
962
1271
|
dots = (dots + 1) % 4;
|
|
@@ -965,19 +1274,19 @@ async function runGitProviderBuild(cfg, args) {
|
|
|
965
1274
|
const result = cfg.provider === "github" ? await ghPollRun(cfg, runId, onTick) : await glPollPipeline(cfg, runId, onTick);
|
|
966
1275
|
process.stdout.write("\n");
|
|
967
1276
|
if (result === "success") {
|
|
968
|
-
log(kleur.green(
|
|
1277
|
+
log(kleur.green(M2().buildSucceeded(runId)));
|
|
969
1278
|
return runId;
|
|
970
1279
|
}
|
|
971
|
-
log(kleur.red(
|
|
1280
|
+
log(kleur.red(M2().buildEnded(result)));
|
|
972
1281
|
log(kleur.dim(` ${runUrl}`));
|
|
973
|
-
log(kleur.dim(
|
|
1282
|
+
log(kleur.dim(M2().alreadyBuiltHint("<N>", args.platform)));
|
|
974
1283
|
process.exit(1);
|
|
975
1284
|
}
|
|
976
1285
|
async function cmdDeploy(argv) {
|
|
977
1286
|
const args = parseArgs(argv);
|
|
978
1287
|
const cfg = await getEffectiveConfig();
|
|
979
1288
|
if (!cfg) {
|
|
980
|
-
log(kleur.red(
|
|
1289
|
+
log(kleur.red(M2().noAccount));
|
|
981
1290
|
process.exit(1);
|
|
982
1291
|
}
|
|
983
1292
|
if (args.setupJenkins) {
|
|
@@ -988,17 +1297,17 @@ async function cmdDeploy(argv) {
|
|
|
988
1297
|
if (args.setupGithub) {
|
|
989
1298
|
const ciCfg = await promptGitProviderSetup("github");
|
|
990
1299
|
saveCiProviderConfig(ciCfg);
|
|
991
|
-
log(kleur.green(
|
|
1300
|
+
log(kleur.green(M2().githubSaved));
|
|
992
1301
|
return;
|
|
993
1302
|
}
|
|
994
1303
|
if (args.setupGitlab) {
|
|
995
1304
|
const ciCfg = await promptGitProviderSetup("gitlab");
|
|
996
1305
|
saveCiProviderConfig(ciCfg);
|
|
997
|
-
log(kleur.green(
|
|
1306
|
+
log(kleur.green(M2().gitlabSaved));
|
|
998
1307
|
return;
|
|
999
1308
|
}
|
|
1000
|
-
log(kleur.bold(
|
|
1001
|
-
if (args.dryRun) log(kleur.yellow(
|
|
1309
|
+
log(kleur.bold(M2().title(args.platform)));
|
|
1310
|
+
if (args.dryRun) log(kleur.yellow(M2().dryRunNotice));
|
|
1002
1311
|
log("");
|
|
1003
1312
|
let versionCode = args.versionCode;
|
|
1004
1313
|
if (!args.skipBuild) {
|
|
@@ -1006,30 +1315,30 @@ async function cmdDeploy(argv) {
|
|
|
1006
1315
|
migrateLegacyJenkins();
|
|
1007
1316
|
const jenkinsCfg = loadJenkinsConfig() ?? void 0;
|
|
1008
1317
|
const kind = resolveCi(args.ci, jenkinsCfg, ciProvider);
|
|
1009
|
-
log(kleur.dim(
|
|
1318
|
+
log(kleur.dim(M2().ciLine(kind)));
|
|
1010
1319
|
if (kind === "jenkins") {
|
|
1011
1320
|
if (!jenkinsCfg?.url || !jenkinsCfg?.token) {
|
|
1012
|
-
log(kleur.yellow(
|
|
1321
|
+
log(kleur.yellow(M2().noJenkinsConfig));
|
|
1013
1322
|
process.exit(1);
|
|
1014
1323
|
}
|
|
1015
1324
|
const jenkins = jenkinsCfg;
|
|
1016
1325
|
const jobName = args.platform === "android" ? jenkins.jobAndroid : jenkins.jobIos;
|
|
1017
1326
|
if (!jobName) {
|
|
1018
|
-
log(kleur.red(
|
|
1327
|
+
log(kleur.red(M2().noJenkinsJob(args.platform)));
|
|
1019
1328
|
process.exit(1);
|
|
1020
1329
|
}
|
|
1021
|
-
log(
|
|
1330
|
+
log(M2().jenkinsTrigger(kleur.cyan(jobName)));
|
|
1022
1331
|
const buildParams = {};
|
|
1023
1332
|
if (args.appId) buildParams.MIMI_APP_ID = args.appId;
|
|
1024
1333
|
const queueItemId = await triggerBuild(jenkins, jobName, buildParams);
|
|
1025
1334
|
if (!queueItemId) {
|
|
1026
|
-
log(kleur.yellow(
|
|
1335
|
+
log(kleur.yellow(M2().noQueueItem));
|
|
1027
1336
|
} else {
|
|
1028
|
-
log(kleur.dim(
|
|
1337
|
+
log(kleur.dim(M2().queueItem(queueItemId)));
|
|
1029
1338
|
}
|
|
1030
1339
|
let buildNumber = null;
|
|
1031
1340
|
if (queueItemId) {
|
|
1032
|
-
log(
|
|
1341
|
+
log(M2().waitingForBuildNumber);
|
|
1033
1342
|
for (let i = 0; i < 6; i++) {
|
|
1034
1343
|
await new Promise((r) => setTimeout(r, 5e3));
|
|
1035
1344
|
buildNumber = await getQueueBuildNumber(jenkins, queueItemId).catch(() => null);
|
|
@@ -1037,77 +1346,77 @@ async function cmdDeploy(argv) {
|
|
|
1037
1346
|
}
|
|
1038
1347
|
}
|
|
1039
1348
|
if (!buildNumber) {
|
|
1040
|
-
log(kleur.yellow(
|
|
1349
|
+
log(kleur.yellow(M2().noBuildNumber));
|
|
1041
1350
|
process.exit(1);
|
|
1042
1351
|
}
|
|
1043
|
-
log(
|
|
1352
|
+
log(M2().buildStarted(buildNumber));
|
|
1044
1353
|
const result = await pollBuildComplete(jenkins, jobName, buildNumber);
|
|
1045
1354
|
if (result !== "SUCCESS") {
|
|
1046
|
-
log(kleur.red(
|
|
1047
|
-
log(kleur.dim(
|
|
1048
|
-
log(kleur.dim(
|
|
1355
|
+
log(kleur.red(M2().buildFailed(result)));
|
|
1356
|
+
log(kleur.dim(M2().jenkinsLink(`${jenkins.url}/job/${encodeURIComponent(jobName)}/${buildNumber}/`)));
|
|
1357
|
+
log(kleur.dim(M2().alreadyBuiltHint(String(buildNumber), args.platform)));
|
|
1049
1358
|
process.exit(1);
|
|
1050
1359
|
}
|
|
1051
|
-
log(kleur.green(
|
|
1360
|
+
log(kleur.green(M2().buildSucceeded(buildNumber)));
|
|
1052
1361
|
if (!versionCode) {
|
|
1053
1362
|
versionCode = buildNumber;
|
|
1054
|
-
log(kleur.dim(
|
|
1363
|
+
log(kleur.dim(M2().versionCodeFromBuild(versionCode)));
|
|
1055
1364
|
}
|
|
1056
1365
|
} else {
|
|
1057
1366
|
if (!ciProvider) {
|
|
1058
|
-
log(kleur.red(
|
|
1367
|
+
log(kleur.red(M2().noProviderConfig(kind)));
|
|
1059
1368
|
process.exit(1);
|
|
1060
1369
|
}
|
|
1061
1370
|
const buildId = await runGitProviderBuild(ciProvider, args);
|
|
1062
1371
|
if (!versionCode) {
|
|
1063
|
-
log(kleur.red(
|
|
1064
|
-
log(kleur.dim(
|
|
1065
|
-
log(kleur.dim(
|
|
1066
|
-
log(kleur.dim(
|
|
1372
|
+
log(kleur.red(M2().versionCodeUnsuitable(kind, buildId)));
|
|
1373
|
+
log(kleur.dim(M2().recommendation));
|
|
1374
|
+
log(kleur.dim(M2().recommendationCi));
|
|
1375
|
+
log(kleur.dim(M2().recommendationNext));
|
|
1067
1376
|
process.exit(1);
|
|
1068
1377
|
}
|
|
1069
1378
|
}
|
|
1070
1379
|
}
|
|
1071
1380
|
if (!versionCode) {
|
|
1072
|
-
log(kleur.red(
|
|
1381
|
+
log(kleur.red(M2().versionCodeUnknown));
|
|
1073
1382
|
process.exit(1);
|
|
1074
1383
|
}
|
|
1075
1384
|
let appId = args.appId;
|
|
1076
1385
|
if (!appId) {
|
|
1077
|
-
const { mcpCall } = await import("./mcp-client-
|
|
1386
|
+
const { mcpCall } = await import("./mcp-client-PVWDCYJN.js");
|
|
1078
1387
|
const r = await mcpCall(cfg.endpoint, cfg.token, "list_apps", {});
|
|
1079
1388
|
if (!r.isError) {
|
|
1080
1389
|
try {
|
|
1081
1390
|
const apps = JSON.parse(r.text);
|
|
1082
1391
|
if (apps.length > 0) {
|
|
1083
1392
|
appId = apps[0].id;
|
|
1084
|
-
log(kleur.dim(
|
|
1393
|
+
log(kleur.dim(M2().appLine(apps[0].name, appId)));
|
|
1085
1394
|
}
|
|
1086
1395
|
} catch {
|
|
1087
1396
|
}
|
|
1088
1397
|
}
|
|
1089
1398
|
}
|
|
1090
1399
|
if (!appId) {
|
|
1091
|
-
log(kleur.red(
|
|
1400
|
+
log(kleur.red(M2().noAppId));
|
|
1092
1401
|
process.exit(1);
|
|
1093
1402
|
}
|
|
1094
1403
|
const needsConfirm = !args.dryRun && !args.yes && process.stdout.isTTY && !process.env.MIMI_SEED_TOKEN;
|
|
1095
1404
|
if (needsConfirm) {
|
|
1096
|
-
const target = args.platform === "ios" ?
|
|
1405
|
+
const target = args.platform === "ios" ? M2().targetIos : M2().targetAndroid;
|
|
1097
1406
|
log("");
|
|
1098
|
-
log(kleur.yellow(
|
|
1407
|
+
log(kleur.yellow(M2().realDeploy(args.platform, versionCode, target)));
|
|
1099
1408
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1100
1409
|
const answer = await new Promise(
|
|
1101
|
-
(resolve) => rl.question(kleur.bold(
|
|
1410
|
+
(resolve) => rl.question(kleur.bold(M2().confirmPrompt), (a) => resolve(a.trim().toLowerCase()))
|
|
1102
1411
|
);
|
|
1103
1412
|
rl.close();
|
|
1104
1413
|
if (answer !== "y" && answer !== "yes") {
|
|
1105
|
-
log(kleur.dim(
|
|
1414
|
+
log(kleur.dim(M2().confirmCancelled));
|
|
1106
1415
|
return;
|
|
1107
1416
|
}
|
|
1108
1417
|
}
|
|
1109
1418
|
log("");
|
|
1110
|
-
log(
|
|
1419
|
+
log(M2().pipelineStarting);
|
|
1111
1420
|
log("");
|
|
1112
1421
|
const deployBuildNumber = !args.skipBuild ? versionCode : void 0;
|
|
1113
1422
|
await streamDeploy(cfg.webBase, cfg.token, {
|
|
@@ -1121,7 +1430,7 @@ async function cmdDeploy(argv) {
|
|
|
1121
1430
|
dryRun: args.dryRun
|
|
1122
1431
|
});
|
|
1123
1432
|
log("");
|
|
1124
|
-
log(kleur.bold(
|
|
1433
|
+
log(kleur.bold(M2().done));
|
|
1125
1434
|
log(kleur.dim(" https://play.google.com/console/developers"));
|
|
1126
1435
|
}
|
|
1127
1436
|
|
|
@@ -1169,15 +1478,16 @@ function statusLine(spec, detected, platforms) {
|
|
|
1169
1478
|
else if (isSatisfied(spec, detected)) mark = kleur2.yellow("~");
|
|
1170
1479
|
else if (spec.requirement === "optional" || !relevant) mark = kleur2.dim("\xB7");
|
|
1171
1480
|
else mark = kleur2.red("\u2717");
|
|
1172
|
-
const tail = d.present ? kleur2.dim(d.detail ?? "") : isSatisfied(spec, detected) ? kleur2.dim(`${spec
|
|
1173
|
-
return ` ${mark} ${spec.
|
|
1481
|
+
const tail = d.present ? kleur2.dim(d.detail ?? "") : isSatisfied(spec, detected) ? kleur2.dim(`${credNote(spec) ?? t().setup.fallbackWorking}`) : kleur2.dim(`\u2192 ${spec.fix}`);
|
|
1482
|
+
return ` ${mark} ${credLabel(spec).padEnd(24)} ${tail}`;
|
|
1174
1483
|
}
|
|
1175
1484
|
function printStatus(detected, platforms) {
|
|
1176
|
-
|
|
1485
|
+
const m = t().setup;
|
|
1486
|
+
log2(kleur2.bold(m.statusTitle + " ") + kleur2.dim(m.statusDir));
|
|
1177
1487
|
log2();
|
|
1178
1488
|
for (const group of ["core", "ci", "marketing"]) {
|
|
1179
1489
|
const specs = CREDENTIALS.filter((c) => c.group === group);
|
|
1180
|
-
const title = group === "core" ?
|
|
1490
|
+
const title = group === "core" ? m.groupCore : group === "ci" ? m.groupCi : m.groupMarketing;
|
|
1181
1491
|
log2(kleur2.dim(` \u2500\u2500 ${title} \u2500\u2500`));
|
|
1182
1492
|
for (const spec of specs) log2(statusLine(spec, detected, platforms));
|
|
1183
1493
|
log2();
|
|
@@ -1186,55 +1496,68 @@ function printStatus(detected, platforms) {
|
|
|
1186
1496
|
function ask(q) {
|
|
1187
1497
|
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
1188
1498
|
return new Promise((resolve) => {
|
|
1189
|
-
|
|
1499
|
+
let done = false;
|
|
1500
|
+
const finish = (value) => {
|
|
1501
|
+
if (done) return;
|
|
1502
|
+
done = true;
|
|
1190
1503
|
rl.close();
|
|
1191
|
-
resolve(
|
|
1192
|
-
}
|
|
1193
|
-
rl.
|
|
1504
|
+
resolve(value);
|
|
1505
|
+
};
|
|
1506
|
+
rl.question(q, (a) => finish(a.trim()));
|
|
1507
|
+
rl.on("close", () => finish(""));
|
|
1194
1508
|
});
|
|
1195
1509
|
}
|
|
1196
1510
|
function printObtain(spec) {
|
|
1197
1511
|
log2();
|
|
1198
|
-
log2(kleur2.bold(
|
|
1199
|
-
for (const line of spec
|
|
1512
|
+
log2(kleur2.bold(t().setup.obtainTitle(credLabel(spec))));
|
|
1513
|
+
for (const line of credObtain(spec)) log2(kleur2.dim(` ${line}`));
|
|
1200
1514
|
if (spec.docsAnchor) {
|
|
1201
|
-
log2(kleur2.dim(
|
|
1515
|
+
log2(kleur2.dim(t().setup.obtainMore(spec.docsAnchor)));
|
|
1202
1516
|
}
|
|
1203
1517
|
log2();
|
|
1204
1518
|
}
|
|
1519
|
+
async function ensureLangChosen() {
|
|
1520
|
+
if (!isLangUnset()) return;
|
|
1521
|
+
const answer = await ask(t().lang.ask);
|
|
1522
|
+
const lang = answer.trim() === "2" || answer.trim().toLowerCase() === "en" ? "en" : "ko";
|
|
1523
|
+
writeSettings({ lang });
|
|
1524
|
+
process.env.MIMI_SEED_LANG = lang;
|
|
1525
|
+
log2(kleur2.green(t().lang.saved(lang)));
|
|
1526
|
+
log2();
|
|
1527
|
+
}
|
|
1205
1528
|
async function connectOne(spec) {
|
|
1206
1529
|
switch (spec.setup.kind) {
|
|
1207
1530
|
case "mcp-bin": {
|
|
1208
1531
|
const code = await runMcpBin(spec.setup.bin, spec.setup.args ?? []);
|
|
1209
1532
|
if (code !== 0) {
|
|
1210
|
-
log2(kleur2.yellow(
|
|
1533
|
+
log2(kleur2.yellow(t().setup.binFailed(credLabel(spec), code, spec.fix)));
|
|
1211
1534
|
}
|
|
1212
1535
|
return true;
|
|
1213
1536
|
}
|
|
1214
1537
|
case "cli": {
|
|
1215
1538
|
const provider = spec.setup.handler;
|
|
1216
1539
|
const cfg = await promptGitProviderSetup(provider);
|
|
1217
|
-
log2(kleur2.dim(
|
|
1540
|
+
log2(kleur2.dim(t().setup.verifying));
|
|
1218
1541
|
const check = await verifyCiToken(cfg);
|
|
1219
1542
|
if (!check.ok) {
|
|
1220
|
-
log2(kleur2.red(
|
|
1221
|
-
log2(kleur2.dim(
|
|
1543
|
+
log2(kleur2.red(t().setup.verifyFailed(check.reason ?? "")));
|
|
1544
|
+
log2(kleur2.dim(t().setup.notSaved(spec.fix)));
|
|
1222
1545
|
return true;
|
|
1223
1546
|
}
|
|
1224
1547
|
saveCiProviderConfig(cfg);
|
|
1225
|
-
log2(kleur2.green(
|
|
1548
|
+
log2(kleur2.green(t().setup.ciSaved(credLabel(spec), check.login ? ` (${check.login})` : "")));
|
|
1226
1549
|
return true;
|
|
1227
1550
|
}
|
|
1228
1551
|
case "command": {
|
|
1229
|
-
log2(kleur2.yellow(
|
|
1230
|
-
await ask(kleur2.dim(
|
|
1552
|
+
log2(kleur2.yellow(t().setup.runSeparately(kleur2.cyan(spec.setup.run))));
|
|
1553
|
+
await ask(kleur2.dim(t().setup.pressEnter));
|
|
1231
1554
|
return true;
|
|
1232
1555
|
}
|
|
1233
1556
|
case "env": {
|
|
1234
|
-
log2(kleur2.yellow(
|
|
1557
|
+
log2(kleur2.yellow(t().setup.envVar));
|
|
1235
1558
|
log2(kleur2.cyan(` export ${spec.setup.envVar}=...`));
|
|
1236
1559
|
log2(kleur2.dim(` (Windows PowerShell: $env:${spec.setup.envVar} = "..." )`));
|
|
1237
|
-
await ask(kleur2.dim(
|
|
1560
|
+
await ask(kleur2.dim(t().setup.pressEnter));
|
|
1238
1561
|
return true;
|
|
1239
1562
|
}
|
|
1240
1563
|
}
|
|
@@ -1243,13 +1566,16 @@ async function cmdSetup(argv) {
|
|
|
1243
1566
|
const opts = parseSetupArgs(argv);
|
|
1244
1567
|
const home = os5.homedir();
|
|
1245
1568
|
migrateLegacyJenkins(home);
|
|
1569
|
+
if (resolveMode(opts, process.env, process.stdin.isTTY) === "interactive") {
|
|
1570
|
+
await ensureLangChosen();
|
|
1571
|
+
}
|
|
1246
1572
|
const hints = await detectHints(process.cwd());
|
|
1247
1573
|
const platforms = opts.platforms ?? [
|
|
1248
1574
|
hints.some((h) => h.packageName) ? "android" : null,
|
|
1249
1575
|
hints.some((h) => h.bundleId) ? "ios" : null
|
|
1250
1576
|
].filter(Boolean);
|
|
1251
|
-
log2(kleur2.bold(
|
|
1252
|
-
if (platforms.length > 0) log2(kleur2.dim(
|
|
1577
|
+
log2(kleur2.bold(t().setup.title));
|
|
1578
|
+
if (platforms.length > 0) log2(kleur2.dim(t().setup.platformsDetected(platforms.join(", "))));
|
|
1253
1579
|
log2();
|
|
1254
1580
|
let detected = detectAll(home);
|
|
1255
1581
|
printStatus(detected, platforms);
|
|
@@ -1262,53 +1588,54 @@ async function cmdSetup(argv) {
|
|
|
1262
1588
|
if (mode === "report-only") {
|
|
1263
1589
|
const missing = missingRequired(detected, platforms);
|
|
1264
1590
|
if (missing.length > 0) {
|
|
1265
|
-
log2(kleur2.yellow(
|
|
1266
|
-
for (const spec of missing) log2(kleur2.yellow(` \u2022 ${spec
|
|
1591
|
+
log2(kleur2.yellow(t().setup.missingRequired));
|
|
1592
|
+
for (const spec of missing) log2(kleur2.yellow(` \u2022 ${credLabel(spec)} \u2192 ${spec.fix}`));
|
|
1267
1593
|
log2();
|
|
1268
1594
|
}
|
|
1269
1595
|
if (opts.only && plan.length > 0) {
|
|
1270
|
-
log2(kleur2.red(
|
|
1271
|
-
for (const spec of plan) log2(kleur2.red(` \u2022 ${spec
|
|
1272
|
-
log2(kleur2.dim(
|
|
1596
|
+
log2(kleur2.red(t().setup.cannotInteract));
|
|
1597
|
+
for (const spec of plan) log2(kleur2.red(` \u2022 ${credLabel(spec)}`));
|
|
1598
|
+
log2(kleur2.dim(t().setup.cannotInteractHint));
|
|
1273
1599
|
process.exit(1);
|
|
1274
1600
|
}
|
|
1275
|
-
log2(kleur2.dim(
|
|
1601
|
+
log2(kleur2.dim(t().setup.runInTerminal));
|
|
1276
1602
|
if (opts.failOnMissing && missing.length > 0) process.exit(1);
|
|
1277
1603
|
return;
|
|
1278
1604
|
}
|
|
1279
1605
|
if (plan.length === 0) {
|
|
1280
1606
|
if (opts.only) {
|
|
1281
|
-
log2(kleur2.green(
|
|
1282
|
-
log2(kleur2.dim(
|
|
1607
|
+
log2(kleur2.green(t().setup.onlyAlreadyDone));
|
|
1608
|
+
log2(kleur2.dim(t().setup.onlyReconnectHint));
|
|
1283
1609
|
} else {
|
|
1284
|
-
log2(kleur2.green(
|
|
1610
|
+
log2(kleur2.green(t().setup.allDone));
|
|
1285
1611
|
}
|
|
1286
|
-
log2(kleur2.dim("
|
|
1612
|
+
log2(kleur2.dim(" " + t().common.checkWith));
|
|
1287
1613
|
if (opts.failOnMissing && missingRequired(detected, platforms).length > 0) process.exit(1);
|
|
1288
1614
|
return;
|
|
1289
1615
|
}
|
|
1290
|
-
log2(kleur2.dim(
|
|
1616
|
+
log2(kleur2.dim(t().setup.planCount(plan.length)));
|
|
1291
1617
|
log2();
|
|
1292
1618
|
let aborted = false;
|
|
1293
1619
|
for (const spec of plan) {
|
|
1294
1620
|
if (aborted) break;
|
|
1295
|
-
const req = spec.requirement === "optional" ? kleur2.dim(
|
|
1621
|
+
const req = spec.requirement === "optional" ? kleur2.dim(`(${t().common.optional})`) : spec.requirement === "platform" ? kleur2.dim(t().setup.neededFor(spec.platform)) : kleur2.red(`(${t().common.required})`);
|
|
1296
1622
|
for (; ; ) {
|
|
1297
|
-
log2(kleur2.bold(`\u25B8 ${spec
|
|
1298
|
-
|
|
1299
|
-
|
|
1623
|
+
log2(kleur2.bold(`\u25B8 ${credLabel(spec)} ${req}`));
|
|
1624
|
+
const note = credNote(spec);
|
|
1625
|
+
if (note) log2(kleur2.dim(` ${note}`));
|
|
1626
|
+
const answer = (await ask(t().setup.prompt)).toLowerCase();
|
|
1300
1627
|
if (answer === "?" || answer === "h") {
|
|
1301
1628
|
printObtain(spec);
|
|
1302
1629
|
continue;
|
|
1303
1630
|
}
|
|
1304
1631
|
if (answer === "q") {
|
|
1305
1632
|
log2();
|
|
1306
|
-
log2(kleur2.dim(
|
|
1633
|
+
log2(kleur2.dim(t().setup.quit));
|
|
1307
1634
|
aborted = true;
|
|
1308
1635
|
break;
|
|
1309
1636
|
}
|
|
1310
1637
|
if (answer === "s" || answer === "") {
|
|
1311
|
-
log2(kleur2.dim(
|
|
1638
|
+
log2(kleur2.dim(t().setup.skipped(spec.fix)));
|
|
1312
1639
|
log2();
|
|
1313
1640
|
break;
|
|
1314
1641
|
}
|
|
@@ -1317,7 +1644,7 @@ async function cmdSetup(argv) {
|
|
|
1317
1644
|
log2();
|
|
1318
1645
|
break;
|
|
1319
1646
|
}
|
|
1320
|
-
log2(kleur2.dim(
|
|
1647
|
+
log2(kleur2.dim(t().setup.promptInvalid));
|
|
1321
1648
|
}
|
|
1322
1649
|
}
|
|
1323
1650
|
detected = detectAll(home);
|
|
@@ -1325,12 +1652,12 @@ async function cmdSetup(argv) {
|
|
|
1325
1652
|
printStatus(detected, platforms);
|
|
1326
1653
|
const stillMissing = missingRequired(detected, platforms);
|
|
1327
1654
|
if (stillMissing.length === 0) {
|
|
1328
|
-
log2(kleur2.green(
|
|
1655
|
+
log2(kleur2.green(t().setup.requiredDone));
|
|
1329
1656
|
} else {
|
|
1330
|
-
log2(kleur2.yellow(
|
|
1331
|
-
for (const spec of stillMissing) log2(kleur2.yellow(` \u2022 ${spec
|
|
1657
|
+
log2(kleur2.yellow(t().setup.stillMissing));
|
|
1658
|
+
for (const spec of stillMissing) log2(kleur2.yellow(` \u2022 ${credLabel(spec)} \u2192 ${spec.fix}`));
|
|
1332
1659
|
}
|
|
1333
|
-
log2(kleur2.dim(
|
|
1660
|
+
log2(kleur2.dim(t().setup.nextSteps));
|
|
1334
1661
|
log2();
|
|
1335
1662
|
if (opts.failOnMissing && stillMissing.length > 0) process.exit(1);
|
|
1336
1663
|
}
|
|
@@ -1342,6 +1669,8 @@ export {
|
|
|
1342
1669
|
deleteConfig,
|
|
1343
1670
|
CONFIG_LOCATION,
|
|
1344
1671
|
getEffectiveConfig,
|
|
1672
|
+
credLabel,
|
|
1673
|
+
credNote,
|
|
1345
1674
|
CREDENTIALS,
|
|
1346
1675
|
tryCredById,
|
|
1347
1676
|
detectAll,
|