mimi-seed 0.4.3 → 0.6.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-R3LS4FY2.js +1776 -0
- package/dist/index.js +200 -880
- package/dist/setup-G2JY35UW.js +11 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -2,169 +2,37 @@
|
|
|
2
2
|
import {
|
|
3
3
|
mcpCall
|
|
4
4
|
} from "./chunk-DW3LOLPJ.js";
|
|
5
|
+
import {
|
|
6
|
+
CONFIG_LOCATION,
|
|
7
|
+
CREDENTIALS,
|
|
8
|
+
MCP_PKG,
|
|
9
|
+
cmdDeploy,
|
|
10
|
+
cmdSetup,
|
|
11
|
+
credLabel,
|
|
12
|
+
credNote,
|
|
13
|
+
deleteConfig,
|
|
14
|
+
detectAll,
|
|
15
|
+
detectHints,
|
|
16
|
+
getEffectiveConfig,
|
|
17
|
+
hasAnyProjectSignal,
|
|
18
|
+
isLang,
|
|
19
|
+
isSatisfied,
|
|
20
|
+
migrateLegacyJenkins,
|
|
21
|
+
resolveLang,
|
|
22
|
+
runMcpBin,
|
|
23
|
+
t,
|
|
24
|
+
tryCredById,
|
|
25
|
+
writeConfig,
|
|
26
|
+
writeSettings
|
|
27
|
+
} from "./chunk-R3LS4FY2.js";
|
|
5
28
|
|
|
6
29
|
// src/index.ts
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
30
|
+
import os3 from "os";
|
|
31
|
+
import fs5 from "fs";
|
|
32
|
+
import path5 from "path";
|
|
10
33
|
import kleur9 from "kleur";
|
|
11
34
|
import open from "open";
|
|
12
35
|
|
|
13
|
-
// src/detect.ts
|
|
14
|
-
import fs from "fs/promises";
|
|
15
|
-
import path from "path";
|
|
16
|
-
async function readIfExists(p) {
|
|
17
|
-
try {
|
|
18
|
-
return await fs.readFile(p, "utf8");
|
|
19
|
-
} catch {
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
async function pathExists(p) {
|
|
24
|
-
try {
|
|
25
|
-
await fs.access(p);
|
|
26
|
-
return true;
|
|
27
|
-
} catch {
|
|
28
|
-
return false;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
async function walk(root, match, maxDepth = 5) {
|
|
32
|
-
const found = [];
|
|
33
|
-
const skipDirs = /* @__PURE__ */ new Set([
|
|
34
|
-
"node_modules",
|
|
35
|
-
".git",
|
|
36
|
-
"build",
|
|
37
|
-
"dist",
|
|
38
|
-
".next",
|
|
39
|
-
".expo",
|
|
40
|
-
"Pods",
|
|
41
|
-
"DerivedData"
|
|
42
|
-
]);
|
|
43
|
-
async function visit(dir, depth) {
|
|
44
|
-
if (depth > maxDepth) return;
|
|
45
|
-
let entries;
|
|
46
|
-
try {
|
|
47
|
-
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
48
|
-
} catch {
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
for (const e of entries) {
|
|
52
|
-
if (e.isDirectory()) {
|
|
53
|
-
if (skipDirs.has(e.name)) continue;
|
|
54
|
-
await visit(path.join(dir, e.name), depth + 1);
|
|
55
|
-
} else if (e.isFile() && match(e.name)) {
|
|
56
|
-
found.push(path.join(dir, e.name));
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
await visit(root, 0);
|
|
61
|
-
return found;
|
|
62
|
-
}
|
|
63
|
-
async function detectHints(cwd) {
|
|
64
|
-
const hints = [];
|
|
65
|
-
for (const fname of ["app.json", "app.config.json"]) {
|
|
66
|
-
const txt = await readIfExists(path.join(cwd, fname));
|
|
67
|
-
if (!txt) continue;
|
|
68
|
-
try {
|
|
69
|
-
const json = JSON.parse(txt);
|
|
70
|
-
const expo = json.expo ?? json;
|
|
71
|
-
const pkg = expo?.android?.package;
|
|
72
|
-
const bid = expo?.ios?.bundleIdentifier;
|
|
73
|
-
const name = expo?.name;
|
|
74
|
-
if (pkg || bid) {
|
|
75
|
-
hints.push({
|
|
76
|
-
name,
|
|
77
|
-
packageName: typeof pkg === "string" ? pkg : void 0,
|
|
78
|
-
bundleId: typeof bid === "string" ? bid : void 0,
|
|
79
|
-
source: [fname]
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
} catch {
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
const gradleFiles = await walk(
|
|
86
|
-
cwd,
|
|
87
|
-
(n) => n === "build.gradle" || n === "build.gradle.kts",
|
|
88
|
-
4
|
|
89
|
-
);
|
|
90
|
-
for (const f of gradleFiles) {
|
|
91
|
-
const txt = await readIfExists(f);
|
|
92
|
-
if (!txt) continue;
|
|
93
|
-
const m = txt.match(/applicationId[\s=]+["']([^"']+)["']/);
|
|
94
|
-
if (m?.[1]) {
|
|
95
|
-
const pkg = m[1];
|
|
96
|
-
if (!hints.some((h) => h.packageName === pkg)) {
|
|
97
|
-
hints.push({ packageName: pkg, source: [path.relative(cwd, f)] });
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
const plistFiles = await walk(cwd, (n) => n === "Info.plist", 5);
|
|
102
|
-
for (const f of plistFiles) {
|
|
103
|
-
const txt = await readIfExists(f);
|
|
104
|
-
if (!txt) continue;
|
|
105
|
-
const m = txt.match(
|
|
106
|
-
/<key>CFBundleIdentifier<\/key>\s*<string>([^<]+)<\/string>/
|
|
107
|
-
);
|
|
108
|
-
if (m?.[1]) {
|
|
109
|
-
let bid = m[1];
|
|
110
|
-
if (bid.includes("$(PRODUCT_BUNDLE_IDENTIFIER)")) continue;
|
|
111
|
-
if (!hints.some((h) => h.bundleId === bid)) {
|
|
112
|
-
hints.push({ bundleId: bid, source: [path.relative(cwd, f)] });
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
const pbxFiles = await walk(cwd, (n) => n === "project.pbxproj", 5);
|
|
117
|
-
for (const f of pbxFiles) {
|
|
118
|
-
const txt = await readIfExists(f);
|
|
119
|
-
if (!txt) continue;
|
|
120
|
-
const matches = [...txt.matchAll(/PRODUCT_BUNDLE_IDENTIFIER = ([^;]+);/g)];
|
|
121
|
-
for (const m of matches) {
|
|
122
|
-
const bid = m[1].trim().replace(/^["']|["']$/g, "");
|
|
123
|
-
if (!bid || bid.includes("$")) continue;
|
|
124
|
-
if (!hints.some((h) => h.bundleId === bid)) {
|
|
125
|
-
hints.push({ bundleId: bid, source: [path.relative(cwd, f)] });
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
const pkgJson = await readIfExists(path.join(cwd, "package.json"));
|
|
130
|
-
if (pkgJson) {
|
|
131
|
-
try {
|
|
132
|
-
const json = JSON.parse(pkgJson);
|
|
133
|
-
if (typeof json.name === "string") {
|
|
134
|
-
for (const h of hints) if (!h.name) h.name = json.name;
|
|
135
|
-
}
|
|
136
|
-
} catch {
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
const merged = [];
|
|
140
|
-
const androidOnly = hints.filter((h) => h.packageName && !h.bundleId);
|
|
141
|
-
const iosOnly = hints.filter((h) => h.bundleId && !h.packageName);
|
|
142
|
-
const both = hints.filter((h) => h.packageName && h.bundleId);
|
|
143
|
-
merged.push(...both);
|
|
144
|
-
for (const a of androidOnly) {
|
|
145
|
-
const match = iosOnly.find((i) => i.bundleId === a.packageName);
|
|
146
|
-
if (match) {
|
|
147
|
-
merged.push({
|
|
148
|
-
name: a.name ?? match.name,
|
|
149
|
-
packageName: a.packageName,
|
|
150
|
-
bundleId: match.bundleId,
|
|
151
|
-
source: [...a.source, ...match.source]
|
|
152
|
-
});
|
|
153
|
-
} else {
|
|
154
|
-
merged.push(a);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
for (const i of iosOnly) {
|
|
158
|
-
if (!androidOnly.some((a) => a.packageName === i.bundleId)) {
|
|
159
|
-
merged.push(i);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
return merged.filter((h) => h.packageName || h.bundleId);
|
|
163
|
-
}
|
|
164
|
-
async function hasAnyProjectSignal(cwd) {
|
|
165
|
-
return await pathExists(path.join(cwd, "package.json")) || await pathExists(path.join(cwd, "app.json")) || await pathExists(path.join(cwd, "android")) || await pathExists(path.join(cwd, "ios"));
|
|
166
|
-
}
|
|
167
|
-
|
|
168
36
|
// src/handshake.ts
|
|
169
37
|
import http from "http";
|
|
170
38
|
async function awaitHandshake(timeoutMs) {
|
|
@@ -214,50 +82,7 @@ async function awaitHandshake(timeoutMs) {
|
|
|
214
82
|
return { port, promise };
|
|
215
83
|
}
|
|
216
84
|
|
|
217
|
-
// src/config.ts
|
|
218
|
-
import fs2 from "fs/promises";
|
|
219
|
-
import path2 from "path";
|
|
220
|
-
import os from "os";
|
|
221
|
-
var CONFIG_DIR = path2.join(os.homedir(), ".mimi-seed");
|
|
222
|
-
var CONFIG_PATH = path2.join(CONFIG_DIR, "config.json");
|
|
223
|
-
async function readConfig() {
|
|
224
|
-
try {
|
|
225
|
-
const txt = await fs2.readFile(CONFIG_PATH, "utf8");
|
|
226
|
-
return JSON.parse(txt);
|
|
227
|
-
} catch {
|
|
228
|
-
return null;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
async function writeConfig(cfg) {
|
|
232
|
-
await fs2.mkdir(CONFIG_DIR, { recursive: true });
|
|
233
|
-
await fs2.writeFile(CONFIG_PATH, JSON.stringify(cfg, null, 2));
|
|
234
|
-
if (process.platform !== "win32") {
|
|
235
|
-
await fs2.chmod(CONFIG_PATH, 384);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
async function deleteConfig() {
|
|
239
|
-
await fs2.rm(CONFIG_PATH, { force: true });
|
|
240
|
-
}
|
|
241
|
-
var CONFIG_LOCATION = CONFIG_PATH;
|
|
242
|
-
async function getEffectiveConfig() {
|
|
243
|
-
const envToken = process.env.MIMI_SEED_TOKEN;
|
|
244
|
-
if (envToken) {
|
|
245
|
-
const webBase = process.env.MIMI_SEED_WEB_BASE ?? "https://mimi-seed.pryzm.gg";
|
|
246
|
-
return {
|
|
247
|
-
token: envToken,
|
|
248
|
-
prefix: envToken.slice(0, 8),
|
|
249
|
-
endpoint: `${webBase}/api/mcp`,
|
|
250
|
-
webBase,
|
|
251
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
return readConfig();
|
|
255
|
-
}
|
|
256
|
-
|
|
257
85
|
// src/doctor.ts
|
|
258
|
-
import fs4 from "fs";
|
|
259
|
-
import os2 from "os";
|
|
260
|
-
import path4 from "path";
|
|
261
86
|
import kleur from "kleur";
|
|
262
87
|
|
|
263
88
|
// src/git.ts
|
|
@@ -301,22 +126,22 @@ function formatCommitsForPrompt(commits) {
|
|
|
301
126
|
}
|
|
302
127
|
|
|
303
128
|
// src/project-manifest.ts
|
|
304
|
-
import
|
|
305
|
-
import
|
|
129
|
+
import fs from "fs";
|
|
130
|
+
import path from "path";
|
|
306
131
|
var MANIFEST_FILENAME = ".mimi-seed.json";
|
|
307
132
|
function findProjectManifest(startDir = process.cwd(), maxDepth = 8) {
|
|
308
|
-
let dir =
|
|
133
|
+
let dir = path.resolve(startDir);
|
|
309
134
|
for (let i = 0; i <= maxDepth; i++) {
|
|
310
|
-
const candidate =
|
|
311
|
-
if (
|
|
135
|
+
const candidate = path.join(dir, MANIFEST_FILENAME);
|
|
136
|
+
if (fs.existsSync(candidate)) {
|
|
312
137
|
try {
|
|
313
|
-
const obj = JSON.parse(
|
|
138
|
+
const obj = JSON.parse(fs.readFileSync(candidate, "utf-8"));
|
|
314
139
|
if (obj && typeof obj === "object") return { manifest: obj, filePath: candidate };
|
|
315
140
|
} catch {
|
|
316
141
|
}
|
|
317
142
|
return null;
|
|
318
143
|
}
|
|
319
|
-
const parent =
|
|
144
|
+
const parent = path.dirname(dir);
|
|
320
145
|
if (parent === dir) break;
|
|
321
146
|
dir = parent;
|
|
322
147
|
}
|
|
@@ -360,114 +185,81 @@ function manifestDetail(id, svc) {
|
|
|
360
185
|
}
|
|
361
186
|
async function cmdDoctor() {
|
|
362
187
|
const cwd = process.cwd();
|
|
363
|
-
|
|
364
|
-
|
|
188
|
+
const m = t().doctor;
|
|
189
|
+
process.stdout.write(kleur.bold(m.title + "\n\n"));
|
|
190
|
+
section(m.secAuth);
|
|
365
191
|
const cfg = await getEffectiveConfig();
|
|
366
192
|
if (!cfg) {
|
|
367
|
-
fail(
|
|
193
|
+
fail(m.noToken, m.noTokenFix);
|
|
368
194
|
} else {
|
|
369
|
-
ok(
|
|
370
|
-
ok(
|
|
195
|
+
ok(m.tokenSaved, `${cfg.prefix}\u2026 (${cfg.createdAt.slice(0, 10)})`);
|
|
196
|
+
ok(m.endpoint, cfg.endpoint);
|
|
371
197
|
if (process.env.MIMI_SEED_TOKEN) {
|
|
372
|
-
ok(
|
|
198
|
+
ok(m.ciMode, m.ciModeDetail);
|
|
373
199
|
}
|
|
374
200
|
const r = await mcpCall(cfg.endpoint, cfg.token, "list_apps", {});
|
|
375
201
|
if (r.isError) {
|
|
376
|
-
fail(
|
|
202
|
+
fail(m.tokenInvalid, r.text.slice(0, 80));
|
|
377
203
|
} else {
|
|
378
204
|
const lines = r.text.split("\n").filter(Boolean);
|
|
379
|
-
ok(
|
|
205
|
+
ok(m.serverOk, m.appCount(lines.length));
|
|
380
206
|
}
|
|
381
207
|
}
|
|
382
|
-
section(
|
|
383
|
-
|
|
384
|
-
const
|
|
385
|
-
const
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
return false;
|
|
397
|
-
}
|
|
398
|
-
})();
|
|
399
|
-
const oauthPresent = hasFile("tokens.json");
|
|
400
|
-
const appstorePresent = hasFile("appstore.json");
|
|
401
|
-
const bigquerySaPresent = hasPrefix("bigquery");
|
|
402
|
-
const creds = [
|
|
403
|
-
["Google OAuth (Firebase/AdMob/Play/Ads)", oauthPresent, "mimi-seed auth login"],
|
|
404
|
-
["App Store Connect", appstorePresent, "mimi-seed auth appstore"],
|
|
405
|
-
["Play \uC11C\uBE44\uC2A4 \uACC4\uC815 (\uC120\uD0DD \u2014 OAuth\uB85C\uB3C4 \uAC00\uB2A5)", hasPlaySa, "mimi-seed auth playstore"],
|
|
406
|
-
["BigQuery \uC11C\uBE44\uC2A4 \uACC4\uC815", bigquerySaPresent, "mimi-seed auth bigquery"]
|
|
407
|
-
];
|
|
408
|
-
for (const [label, present, cmd] of creds) {
|
|
409
|
-
if (present) ok(label);
|
|
410
|
-
else warn(label, `\u2192 ${cmd}`);
|
|
411
|
-
}
|
|
412
|
-
process.stdout.write(kleur.dim(" OAuth \uD1A0\uD070 \uC2E0\uC120\uB3C4 \uD655\uC778: mimi-seed auth status\n"));
|
|
208
|
+
section(m.secCreds);
|
|
209
|
+
migrateLegacyJenkins();
|
|
210
|
+
const detected = detectAll();
|
|
211
|
+
for (const spec of CREDENTIALS) {
|
|
212
|
+
const d = detected.get(spec.id);
|
|
213
|
+
const base = credLabel(spec);
|
|
214
|
+
const note = credNote(spec);
|
|
215
|
+
const label = note ? `${base} (${note})` : base;
|
|
216
|
+
if (d.present) ok(base, d.detail);
|
|
217
|
+
else if (isSatisfied(spec, detected)) warn(label, t().setup.fallbackWorking);
|
|
218
|
+
else if (spec.requirement === "optional") warn(`${label}`, `\u2192 ${spec.fix}`);
|
|
219
|
+
else fail(base, `\u2192 ${spec.fix}`);
|
|
220
|
+
}
|
|
221
|
+
process.stdout.write(kleur.dim(m.credsHint));
|
|
413
222
|
const loaded = findProjectManifest(cwd);
|
|
414
223
|
if (loaded) {
|
|
415
|
-
const projName = loaded.manifest.displayName ?? loaded.manifest.project ??
|
|
416
|
-
section(
|
|
417
|
-
const connectedOf = {
|
|
418
|
-
oauth: oauthPresent,
|
|
419
|
-
bigquery: bigquerySaPresent || oauthPresent,
|
|
420
|
-
playstore: hasPlaySa || oauthPresent,
|
|
421
|
-
appstore: appstorePresent,
|
|
422
|
-
jenkins: false
|
|
423
|
-
// mimi-seed 로컬 자격증명 대상 아님(별도 MCP) — note 로만 안내
|
|
424
|
-
};
|
|
425
|
-
const fixOf = {
|
|
426
|
-
oauth: "mimi-seed auth login",
|
|
427
|
-
bigquery: "mimi-seed auth bigquery",
|
|
428
|
-
playstore: "mimi-seed auth playstore",
|
|
429
|
-
appstore: "mimi-seed auth appstore",
|
|
430
|
-
jenkins: "claude mcp add <your-jenkins-mcp> -s user"
|
|
431
|
-
};
|
|
224
|
+
const projName = loaded.manifest.displayName ?? loaded.manifest.project ?? m.thisProject;
|
|
225
|
+
section(m.requirements(projName));
|
|
432
226
|
for (const [id, svc] of manifestServiceEntries(loaded.manifest)) {
|
|
433
227
|
const required = svc.required !== false;
|
|
434
|
-
const connected = connectedOf[id];
|
|
435
228
|
const detail = manifestDetail(id, svc);
|
|
229
|
+
const spec = tryCredById(id);
|
|
230
|
+
if (!spec) {
|
|
231
|
+
warn(m.unknownService(id), svc.note ?? detail);
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
const connected = isSatisfied(spec, detected);
|
|
436
235
|
if (connected) ok(id, detail);
|
|
437
|
-
else if (!required) warn(`${id} (
|
|
438
|
-
else fail(id, `\u2192 ${
|
|
236
|
+
else if (!required) warn(`${id} (${t().common.optional})`, svc.note ?? detail);
|
|
237
|
+
else fail(id, `\u2192 ${spec.fix}${detail ? " " + detail : ""}`);
|
|
439
238
|
}
|
|
440
239
|
}
|
|
441
|
-
section(
|
|
240
|
+
section(m.secEnv);
|
|
442
241
|
const nodeVer = process.version;
|
|
443
242
|
const [, major] = nodeVer.match(/v(\d+)/) ?? [];
|
|
444
243
|
if (Number(major) >= 20) {
|
|
445
244
|
ok("Node.js", nodeVer);
|
|
446
|
-
} else if (Number(major) >= 18) {
|
|
447
|
-
warn("Node.js", `${nodeVer} \u2014 CLI \uB294 \uB3D9\uC791\uD558\uC9C0\uB9CC Local MCP \uC11C\uBC84(@yoonion/mimi-seed-mcp)\uB294 v20 \uC774\uC0C1 \uD544\uC694`);
|
|
448
245
|
} else {
|
|
449
|
-
fail("Node.js",
|
|
246
|
+
fail("Node.js", m.nodeTooOld(nodeVer));
|
|
450
247
|
}
|
|
451
248
|
if (isGitRepo(cwd)) {
|
|
452
249
|
const latestTag = getLatestTag(cwd);
|
|
453
250
|
const commits = getGitLog(cwd, { limit: 5 });
|
|
454
|
-
ok(
|
|
455
|
-
} else {
|
|
456
|
-
warn("Git \uC800\uC7A5\uC18C \uC5C6\uC74C", "mimi-seed notes \uC0AC\uC6A9 \uBD88\uAC00");
|
|
457
|
-
}
|
|
458
|
-
if (process.env.ANTHROPIC_API_KEY) {
|
|
459
|
-
ok("ANTHROPIC_API_KEY", "AI \uB9B4\uB9AC\uC988 \uB178\uD2B8 \uC0DD\uC131 \uC0AC\uC6A9 \uAC00\uB2A5");
|
|
251
|
+
ok(m.gitRepo, latestTag ? m.gitTag(latestTag) : m.gitCommits(commits.length));
|
|
460
252
|
} else {
|
|
461
|
-
warn(
|
|
253
|
+
warn(m.noGit, m.noGitDetail);
|
|
462
254
|
}
|
|
463
|
-
section(
|
|
255
|
+
section(m.secApps);
|
|
464
256
|
const hints = await detectHints(cwd);
|
|
465
257
|
if (hints.length === 0) {
|
|
466
|
-
warn(
|
|
258
|
+
warn(m.noApp, m.noAppDetail);
|
|
467
259
|
} else {
|
|
468
260
|
for (const h of hints) {
|
|
469
261
|
const ids = [h.packageName && `android:${h.packageName}`, h.bundleId && `ios:${h.bundleId}`].filter(Boolean).join(" ");
|
|
470
|
-
ok(h.name ??
|
|
262
|
+
ok(h.name ?? m.unnamed, ids);
|
|
471
263
|
}
|
|
472
264
|
}
|
|
473
265
|
process.stdout.write("\n");
|
|
@@ -910,13 +702,7 @@ async function cmdReview(argv) {
|
|
|
910
702
|
}
|
|
911
703
|
|
|
912
704
|
// src/auth.ts
|
|
913
|
-
import { spawn } from "child_process";
|
|
914
|
-
import fs5 from "fs";
|
|
915
|
-
import os3 from "os";
|
|
916
|
-
import path5 from "path";
|
|
917
705
|
import kleur5 from "kleur";
|
|
918
|
-
var MCP_PKG = "@yoonion/mimi-seed-mcp";
|
|
919
|
-
var MIMI_DIR = path5.join(os3.homedir(), ".mimi-seed");
|
|
920
706
|
function log(msg) {
|
|
921
707
|
process.stdout.write(msg + "\n");
|
|
922
708
|
}
|
|
@@ -934,8 +720,18 @@ ${kleur5.bold("\uD50C\uB7AB\uD3FC\uBCC4 \uC790\uACA9\uC99D\uBA85:")}
|
|
|
934
720
|
${kleur5.cyan("mimi-seed auth playstore")} Play \uC11C\uBE44\uC2A4 \uACC4\uC815 JSON \uB4F1\uB85D
|
|
935
721
|
${kleur5.cyan("mimi-seed auth bigquery")} BigQuery \uC11C\uBE44\uC2A4 \uACC4\uC815 (Crashlytics export \uB4F1)
|
|
936
722
|
|
|
723
|
+
${kleur5.bold("\uBE4C\uB4DC / \uB9C8\uCF00\uD305:")}
|
|
724
|
+
${kleur5.cyan("mimi-seed auth jenkins")} Jenkins \uC5F0\uACB0 (jenkins.json)
|
|
725
|
+
${kleur5.cyan("mimi-seed auth ci")} GitHub Actions / GitLab CI (ci.json)
|
|
726
|
+
${kleur5.cyan("mimi-seed auth googleads")} Google Ads (google-ads.json)
|
|
727
|
+
${kleur5.cyan("mimi-seed auth facebook")} Facebook \uD398\uC774\uC9C0
|
|
728
|
+
${kleur5.cyan("mimi-seed auth instagram")} Instagram
|
|
729
|
+
|
|
937
730
|
${kleur5.bold("\uC804\uCCB4 \uC0C1\uD0DC:")}
|
|
938
|
-
${kleur5.cyan("mimi-seed auth status --all")}
|
|
731
|
+
${kleur5.cyan("mimi-seed auth status --all")} \uBAA8\uB4E0 \uC790\uACA9\uC99D\uBA85 \uBCF4\uC720 \uC5EC\uBD80 \uD55C\uB208\uC5D0
|
|
732
|
+
|
|
733
|
+
${kleur5.bold("\uD55C \uBC88\uC5D0 \uB2E4 \uC5F0\uACB0:")}
|
|
734
|
+
${kleur5.cyan("mimi-seed setup")} \uC548\uB0B4\uB97C \uB530\uB77C\uAC00\uBA70 \uC21C\uC11C\uB300\uB85C \uC5F0\uACB0 (\uAD8C\uC7A5)
|
|
939
735
|
|
|
940
736
|
${kleur5.bold("login \uC635\uC158:")}
|
|
941
737
|
--no-browser URL \uC790\uB3D9 \uC624\uD508 \uC548 \uD568 (\uC9C1\uC811 \uBCF5\uBD99)
|
|
@@ -944,44 +740,16 @@ ${kleur5.bold("login \uC635\uC158:")}
|
|
|
944
740
|
|
|
945
741
|
${kleur5.dim(`\uB0B4\uBD80\uC801\uC73C\uB85C ${MCP_PKG} \uC758 mimi-seed-*-auth CLI\uB97C \uD638\uCD9C\uD569\uB2C8\uB2E4.`)}`);
|
|
946
742
|
}
|
|
947
|
-
async function runMcpBin(bin, extraArgs) {
|
|
948
|
-
return new Promise((resolve) => {
|
|
949
|
-
const child = spawn("npx", ["-y", `${MCP_PKG}`, bin, ...extraArgs], {
|
|
950
|
-
stdio: "inherit",
|
|
951
|
-
shell: true
|
|
952
|
-
});
|
|
953
|
-
child.on("error", (e) => {
|
|
954
|
-
process.stderr.write(`
|
|
955
|
-
\u274C npx \uC2E4\uD589 \uC2E4\uD328: ${e.message}
|
|
956
|
-
`);
|
|
957
|
-
resolve(1);
|
|
958
|
-
});
|
|
959
|
-
child.on("exit", (code) => resolve(code ?? 0));
|
|
960
|
-
});
|
|
961
|
-
}
|
|
962
|
-
function fileExists(name) {
|
|
963
|
-
return fs5.existsSync(path5.join(MIMI_DIR, name));
|
|
964
|
-
}
|
|
965
|
-
function anyFileStarting(prefix) {
|
|
966
|
-
try {
|
|
967
|
-
return fs5.readdirSync(MIMI_DIR).some((f) => f.startsWith(prefix));
|
|
968
|
-
} catch {
|
|
969
|
-
return false;
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
743
|
function printAllCredStatus() {
|
|
973
|
-
log(kleur5.bold(
|
|
974
|
-
const
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
for (const [label, ok2, cmd] of rows) {
|
|
981
|
-
const mark = ok2 ? kleur5.green("\u2713") : kleur5.red("\u2717");
|
|
982
|
-
const hint = ok2 ? "" : kleur5.dim(` \u2192 ${cmd}`);
|
|
983
|
-
log(` ${mark} ${label}${hint}`);
|
|
744
|
+
log(kleur5.bold(t().auth.statusTitle + " ") + kleur5.dim("(~/.mimi-seed)"));
|
|
745
|
+
const detected = detectAll();
|
|
746
|
+
for (const spec of CREDENTIALS) {
|
|
747
|
+
const d = detected.get(spec.id);
|
|
748
|
+
const mark = d.present ? kleur5.green("\u2713") : isSatisfied(spec, detected) ? kleur5.yellow("~") : spec.requirement === "optional" ? kleur5.dim("\xB7") : kleur5.red("\u2717");
|
|
749
|
+
const tail = d.present ? kleur5.dim(d.detail ?? "") : kleur5.dim(`\u2192 ${spec.fix}`);
|
|
750
|
+
log(` ${mark} ${credLabel(spec).padEnd(24)} ${tail}`);
|
|
984
751
|
}
|
|
752
|
+
log(kleur5.dim("\n \uD55C \uBC88\uC5D0 \uC5F0\uACB0: mimi-seed setup"));
|
|
985
753
|
}
|
|
986
754
|
async function cmdAuth(args) {
|
|
987
755
|
const sub = args[0];
|
|
@@ -997,6 +765,15 @@ async function cmdAuth(args) {
|
|
|
997
765
|
if (sub === "appstore") return void exitWith(await runMcpBin("mimi-seed-appstore-auth", rest));
|
|
998
766
|
if (sub === "playstore") return void exitWith(await runMcpBin("mimi-seed-playstore-auth", rest));
|
|
999
767
|
if (sub === "bigquery") return void exitWith(await runMcpBin("mimi-seed-bigquery-auth", rest));
|
|
768
|
+
if (sub === "jenkins") return void exitWith(await runMcpBin("mimi-seed-jenkins-auth", rest));
|
|
769
|
+
if (sub === "googleads") return void exitWith(await runMcpBin("mimi-seed-googleads-auth", rest));
|
|
770
|
+
if (sub === "facebook") return void exitWith(await runMcpBin("mimi-seed-social-auth", ["facebook"]));
|
|
771
|
+
if (sub === "instagram") return void exitWith(await runMcpBin("mimi-seed-social-auth", ["instagram"]));
|
|
772
|
+
if (sub === "ci") {
|
|
773
|
+
const { cmdSetup: cmdSetup2 } = await import("./setup-G2JY35UW.js");
|
|
774
|
+
await cmdSetup2(["--only", "github,gitlab", "--reconnect", "github,gitlab"]);
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
1000
777
|
let mcpArgs;
|
|
1001
778
|
switch (sub) {
|
|
1002
779
|
case void 0:
|
|
@@ -1013,7 +790,7 @@ async function cmdAuth(args) {
|
|
|
1013
790
|
mcpArgs = ["--logout", ...rest];
|
|
1014
791
|
break;
|
|
1015
792
|
default:
|
|
1016
|
-
log(kleur5.red(
|
|
793
|
+
log(kleur5.red(t().auth.unknownSub(sub ?? "")));
|
|
1017
794
|
printAuthHelp();
|
|
1018
795
|
process.exit(1);
|
|
1019
796
|
}
|
|
@@ -1023,12 +800,39 @@ function exitWith(code) {
|
|
|
1023
800
|
if (code !== 0) process.exit(code);
|
|
1024
801
|
}
|
|
1025
802
|
|
|
803
|
+
// src/lang.ts
|
|
804
|
+
import kleur6 from "kleur";
|
|
805
|
+
function log2(msg) {
|
|
806
|
+
process.stdout.write(msg + "\n");
|
|
807
|
+
}
|
|
808
|
+
async function cmdLang(args) {
|
|
809
|
+
const arg = args[0]?.toLowerCase();
|
|
810
|
+
if (!arg) {
|
|
811
|
+
log2(t().lang.current(resolveLang()));
|
|
812
|
+
if (process.env.MIMI_SEED_LANG) {
|
|
813
|
+
log2(kleur6.dim(" (MIMI_SEED_LANG)"));
|
|
814
|
+
}
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
if (arg === "--help" || arg === "-h") {
|
|
818
|
+
log2(t().lang.usage);
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
if (!isLang(arg)) {
|
|
822
|
+
log2(kleur6.red(t().lang.invalid(arg)));
|
|
823
|
+
process.exit(1);
|
|
824
|
+
}
|
|
825
|
+
writeSettings({ lang: arg });
|
|
826
|
+
process.env.MIMI_SEED_LANG = arg;
|
|
827
|
+
log2(kleur6.green(t().lang.saved(arg)));
|
|
828
|
+
}
|
|
829
|
+
|
|
1026
830
|
// src/cloud.ts
|
|
1027
|
-
import { spawn
|
|
831
|
+
import { spawn } from "child_process";
|
|
1028
832
|
var MCP_PKG2 = "@yoonion/mimi-seed-mcp";
|
|
1029
833
|
async function runMcpBin2(bin, extraArgs) {
|
|
1030
834
|
return new Promise((resolve) => {
|
|
1031
|
-
const child =
|
|
835
|
+
const child = spawn("npx", ["-y", `${MCP_PKG2}`, bin, ...extraArgs], {
|
|
1032
836
|
stdio: "inherit",
|
|
1033
837
|
shell: true
|
|
1034
838
|
});
|
|
@@ -1054,541 +858,19 @@ async function cmdGa4(args) {
|
|
|
1054
858
|
exitWith2(await runMcpBin2("mimi-seed-ga4", args));
|
|
1055
859
|
}
|
|
1056
860
|
|
|
1057
|
-
// src/deploy.ts
|
|
1058
|
-
import kleur6 from "kleur";
|
|
1059
|
-
import * as readline from "readline";
|
|
1060
|
-
|
|
1061
|
-
// src/ci-providers.ts
|
|
1062
|
-
import fs6 from "fs";
|
|
1063
|
-
import path6 from "path";
|
|
1064
|
-
import os4 from "os";
|
|
1065
|
-
var CI_CONFIG_PATH = path6.join(os4.homedir(), ".mimi-seed", "ci.json");
|
|
1066
|
-
function loadCiProviderConfig() {
|
|
1067
|
-
try {
|
|
1068
|
-
return JSON.parse(fs6.readFileSync(CI_CONFIG_PATH, "utf-8"));
|
|
1069
|
-
} catch {
|
|
1070
|
-
return null;
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
function saveCiProviderConfig(cfg) {
|
|
1074
|
-
const dir = path6.dirname(CI_CONFIG_PATH);
|
|
1075
|
-
if (!fs6.existsSync(dir)) {
|
|
1076
|
-
fs6.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
1077
|
-
}
|
|
1078
|
-
fs6.writeFileSync(CI_CONFIG_PATH, JSON.stringify(cfg, null, 2));
|
|
1079
|
-
if (process.platform !== "win32") {
|
|
1080
|
-
fs6.chmodSync(CI_CONFIG_PATH, 384);
|
|
1081
|
-
}
|
|
1082
|
-
}
|
|
1083
|
-
function ghBase(cfg) {
|
|
1084
|
-
if (cfg.host) return `${cfg.host.replace(/\/$/, "")}/api/v3`;
|
|
1085
|
-
return "https://api.github.com";
|
|
1086
|
-
}
|
|
1087
|
-
function ghHeaders(token) {
|
|
1088
|
-
return {
|
|
1089
|
-
Authorization: `Bearer ${token}`,
|
|
1090
|
-
Accept: "application/vnd.github+json",
|
|
1091
|
-
"X-GitHub-Api-Version": "2022-11-28",
|
|
1092
|
-
"Content-Type": "application/json"
|
|
1093
|
-
};
|
|
1094
|
-
}
|
|
1095
|
-
async function ghTriggerWorkflow(cfg, workflow, ref, inputs = {}) {
|
|
1096
|
-
const startTime = /* @__PURE__ */ new Date();
|
|
1097
|
-
const wfId = /^\d+$/.test(workflow) ? Number(workflow) : workflow;
|
|
1098
|
-
const dispatchRes = await fetch(
|
|
1099
|
-
`${ghBase(cfg)}/repos/${cfg.owner}/${cfg.repo}/actions/workflows/${wfId}/dispatches`,
|
|
1100
|
-
{
|
|
1101
|
-
method: "POST",
|
|
1102
|
-
headers: ghHeaders(cfg.token),
|
|
1103
|
-
body: JSON.stringify({ ref, inputs })
|
|
1104
|
-
}
|
|
1105
|
-
);
|
|
1106
|
-
if (!dispatchRes.ok) {
|
|
1107
|
-
throw new Error(`GitHub dispatch ${dispatchRes.status}: ${await dispatchRes.text()}`);
|
|
1108
|
-
}
|
|
1109
|
-
await new Promise((r) => setTimeout(r, 3e3));
|
|
1110
|
-
const runsRes = await fetch(
|
|
1111
|
-
`${ghBase(cfg)}/repos/${cfg.owner}/${cfg.repo}/actions/workflows/${wfId}/runs?per_page=5`,
|
|
1112
|
-
{ headers: ghHeaders(cfg.token) }
|
|
1113
|
-
);
|
|
1114
|
-
if (!runsRes.ok) return null;
|
|
1115
|
-
const data = await runsRes.json();
|
|
1116
|
-
const run = data.workflow_runs.find((r) => new Date(r.created_at) >= startTime) ?? data.workflow_runs[0];
|
|
1117
|
-
if (!run) return null;
|
|
1118
|
-
return { runId: run.id, url: run.html_url };
|
|
1119
|
-
}
|
|
1120
|
-
async function ghPollRun(cfg, runId, onTick, timeoutMs = 30 * 60 * 1e3, intervalMs = 15e3) {
|
|
1121
|
-
const start = Date.now();
|
|
1122
|
-
let consecutiveErrors = 0;
|
|
1123
|
-
while (Date.now() - start < timeoutMs) {
|
|
1124
|
-
await new Promise((r) => setTimeout(r, intervalMs));
|
|
1125
|
-
let res;
|
|
1126
|
-
try {
|
|
1127
|
-
res = await fetch(
|
|
1128
|
-
`${ghBase(cfg)}/repos/${cfg.owner}/${cfg.repo}/actions/runs/${runId}`,
|
|
1129
|
-
{ headers: ghHeaders(cfg.token) }
|
|
1130
|
-
);
|
|
1131
|
-
} catch {
|
|
1132
|
-
consecutiveErrors++;
|
|
1133
|
-
if (consecutiveErrors >= 3) throw new Error("GitHub API \uC5F0\uC18D \uC624\uB958 3\uD68C");
|
|
1134
|
-
continue;
|
|
1135
|
-
}
|
|
1136
|
-
if (!res.ok) {
|
|
1137
|
-
consecutiveErrors++;
|
|
1138
|
-
if (consecutiveErrors >= 3) throw new Error(`GitHub API \uC5F0\uC18D \uC624\uB958 (HTTP ${res.status})`);
|
|
1139
|
-
continue;
|
|
1140
|
-
}
|
|
1141
|
-
consecutiveErrors = 0;
|
|
1142
|
-
const data = await res.json();
|
|
1143
|
-
onTick?.(data.status);
|
|
1144
|
-
if (data.status === "completed") {
|
|
1145
|
-
if (data.conclusion === "success") return "success";
|
|
1146
|
-
if (data.conclusion === "cancelled") return "cancelled";
|
|
1147
|
-
return "failure";
|
|
1148
|
-
}
|
|
1149
|
-
}
|
|
1150
|
-
return "timeout";
|
|
1151
|
-
}
|
|
1152
|
-
function glBase(cfg) {
|
|
1153
|
-
return `${cfg.host ?? "https://gitlab.com"}/api/v4`;
|
|
1154
|
-
}
|
|
1155
|
-
function glProjectId(cfg) {
|
|
1156
|
-
return encodeURIComponent(`${cfg.owner}/${cfg.repo}`);
|
|
1157
|
-
}
|
|
1158
|
-
async function glTriggerPipeline(cfg, ref, variables = {}) {
|
|
1159
|
-
const vars = Object.entries(variables).map(([key, value]) => ({ key, value }));
|
|
1160
|
-
const body = { ref };
|
|
1161
|
-
if (vars.length > 0) body.variables = vars;
|
|
1162
|
-
const res = await fetch(`${glBase(cfg)}/projects/${glProjectId(cfg)}/pipeline`, {
|
|
1163
|
-
method: "POST",
|
|
1164
|
-
headers: { "PRIVATE-TOKEN": cfg.token, "Content-Type": "application/json" },
|
|
1165
|
-
body: JSON.stringify(body)
|
|
1166
|
-
});
|
|
1167
|
-
if (!res.ok) throw new Error(`GitLab trigger ${res.status}: ${await res.text()}`);
|
|
1168
|
-
const data = await res.json();
|
|
1169
|
-
return { pipelineId: data.id, url: data.web_url };
|
|
1170
|
-
}
|
|
1171
|
-
async function glPollPipeline(cfg, pipelineId, onTick, timeoutMs = 30 * 60 * 1e3, intervalMs = 15e3) {
|
|
1172
|
-
const start = Date.now();
|
|
1173
|
-
let consecutiveErrors = 0;
|
|
1174
|
-
while (Date.now() - start < timeoutMs) {
|
|
1175
|
-
await new Promise((r) => setTimeout(r, intervalMs));
|
|
1176
|
-
let res;
|
|
1177
|
-
try {
|
|
1178
|
-
res = await fetch(
|
|
1179
|
-
`${glBase(cfg)}/projects/${glProjectId(cfg)}/pipelines/${pipelineId}`,
|
|
1180
|
-
{ headers: { "PRIVATE-TOKEN": cfg.token } }
|
|
1181
|
-
);
|
|
1182
|
-
} catch {
|
|
1183
|
-
consecutiveErrors++;
|
|
1184
|
-
if (consecutiveErrors >= 3) throw new Error("GitLab API \uC5F0\uC18D \uC624\uB958 3\uD68C");
|
|
1185
|
-
continue;
|
|
1186
|
-
}
|
|
1187
|
-
if (!res.ok) {
|
|
1188
|
-
consecutiveErrors++;
|
|
1189
|
-
if (consecutiveErrors >= 3) throw new Error(`GitLab API \uC5F0\uC18D \uC624\uB958 (HTTP ${res.status})`);
|
|
1190
|
-
continue;
|
|
1191
|
-
}
|
|
1192
|
-
consecutiveErrors = 0;
|
|
1193
|
-
const data = await res.json();
|
|
1194
|
-
onTick?.(data.status);
|
|
1195
|
-
if (data.status === "success") return "success";
|
|
1196
|
-
if (data.status === "failed") return "failure";
|
|
1197
|
-
if (data.status === "canceled" || data.status === "skipped") return "cancelled";
|
|
1198
|
-
}
|
|
1199
|
-
return "timeout";
|
|
1200
|
-
}
|
|
1201
|
-
|
|
1202
|
-
// src/deploy.ts
|
|
1203
|
-
var PHASE_ICON = {
|
|
1204
|
-
init: "\u{1F680}",
|
|
1205
|
-
verify: "\u{1F50D}",
|
|
1206
|
-
notes: "\u{1F4DD}",
|
|
1207
|
-
apply: "\u{1F4E4}",
|
|
1208
|
-
promote: "\u{1F3AF}",
|
|
1209
|
-
done: "\u{1F389}",
|
|
1210
|
-
error: "\u274C"
|
|
1211
|
-
};
|
|
1212
|
-
function log2(msg) {
|
|
1213
|
-
process.stdout.write(msg + "\n");
|
|
1214
|
-
}
|
|
1215
|
-
function jenkinsHeaders(cfg) {
|
|
1216
|
-
const creds = Buffer.from(`${cfg.user ?? "admin"}:${cfg.token}`).toString("base64");
|
|
1217
|
-
return { Authorization: `Basic ${creds}`, "Content-Type": "application/json" };
|
|
1218
|
-
}
|
|
1219
|
-
async function triggerBuild(cfg, jobName, params) {
|
|
1220
|
-
const qs = new URLSearchParams(params).toString();
|
|
1221
|
-
const url = `${cfg.url}/job/${encodeURIComponent(jobName)}/buildWithParameters?${qs}`;
|
|
1222
|
-
const res = await fetch(url, { method: "POST", headers: jenkinsHeaders(cfg) });
|
|
1223
|
-
if (!res.ok) {
|
|
1224
|
-
throw new Error(`Jenkins \uD2B8\uB9AC\uAC70 \uC2E4\uD328 ${res.status}: ${await res.text()}`);
|
|
1225
|
-
}
|
|
1226
|
-
const location = res.headers.get("Location") ?? "";
|
|
1227
|
-
const match = location.match(/\/queue\/item\/(\d+)\//);
|
|
1228
|
-
return match?.[1] ?? "";
|
|
1229
|
-
}
|
|
1230
|
-
async function getQueueBuildNumber(cfg, queueItemId) {
|
|
1231
|
-
const url = `${cfg.url}/queue/item/${queueItemId}/api/json`;
|
|
1232
|
-
const res = await fetch(url, { headers: jenkinsHeaders(cfg) });
|
|
1233
|
-
if (!res.ok) return null;
|
|
1234
|
-
const data = await res.json();
|
|
1235
|
-
return data.executable?.number ?? null;
|
|
1236
|
-
}
|
|
1237
|
-
async function getBuildStatus(cfg, jobName, buildNumber) {
|
|
1238
|
-
const url = `${cfg.url}/job/${encodeURIComponent(jobName)}/${buildNumber}/api/json`;
|
|
1239
|
-
const res = await fetch(url, { headers: jenkinsHeaders(cfg) });
|
|
1240
|
-
if (!res.ok) throw new Error(`\uBE4C\uB4DC \uC0C1\uD0DC \uC870\uD68C \uC2E4\uD328 ${res.status}`);
|
|
1241
|
-
const data = await res.json();
|
|
1242
|
-
return {
|
|
1243
|
-
building: data.building ?? true,
|
|
1244
|
-
result: data.result ?? null,
|
|
1245
|
-
duration: data.duration ?? 0
|
|
1246
|
-
};
|
|
1247
|
-
}
|
|
1248
|
-
async function pollBuildComplete(cfg, jobName, buildNumber, timeoutMs = 30 * 60 * 1e3) {
|
|
1249
|
-
const start = Date.now();
|
|
1250
|
-
const intervalMs = 15e3;
|
|
1251
|
-
let dots = 0;
|
|
1252
|
-
let consecutiveErrors = 0;
|
|
1253
|
-
while (Date.now() - start < timeoutMs) {
|
|
1254
|
-
await new Promise((r) => setTimeout(r, intervalMs));
|
|
1255
|
-
let status;
|
|
1256
|
-
try {
|
|
1257
|
-
status = await getBuildStatus(cfg, jobName, buildNumber);
|
|
1258
|
-
consecutiveErrors = 0;
|
|
1259
|
-
} catch {
|
|
1260
|
-
consecutiveErrors++;
|
|
1261
|
-
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");
|
|
1262
|
-
process.stdout.write(`\r \u26A0 Jenkins \uC5F0\uACB0 \uC624\uB958 (${consecutiveErrors}/3\uD68C), \uC7AC\uC2DC\uB3C4... `);
|
|
1263
|
-
continue;
|
|
1264
|
-
}
|
|
1265
|
-
dots = (dots + 1) % 4;
|
|
1266
|
-
process.stdout.write(`\r \u23F3 \uBE4C\uB4DC \uC9C4\uD589 \uC911${".".repeat(dots + 1)} `);
|
|
1267
|
-
if (!status.building) {
|
|
1268
|
-
process.stdout.write("\n");
|
|
1269
|
-
return status.result ?? "FAILURE";
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
|
-
throw new Error("\uBE4C\uB4DC \uD0C0\uC784\uC544\uC6C3 (30\uBD84)");
|
|
1273
|
-
}
|
|
1274
|
-
async function streamDeploy(webBase, token, body) {
|
|
1275
|
-
const res = await fetch(`${webBase}/api/deploy`, {
|
|
1276
|
-
method: "POST",
|
|
1277
|
-
headers: {
|
|
1278
|
-
"Content-Type": "application/json",
|
|
1279
|
-
Authorization: `Bearer ${token}`
|
|
1280
|
-
},
|
|
1281
|
-
body: JSON.stringify(body)
|
|
1282
|
-
});
|
|
1283
|
-
if (!res.ok) {
|
|
1284
|
-
const text = await res.text().catch(() => "");
|
|
1285
|
-
throw new Error(`\uC11C\uBC84 \uBC30\uD3EC \uC2E4\uD328 ${res.status}: ${text.slice(0, 200)}`);
|
|
1286
|
-
}
|
|
1287
|
-
const reader = res.body?.getReader();
|
|
1288
|
-
if (!reader) throw new Error("SSE \uC2A4\uD2B8\uB9BC \uC5C6\uC74C");
|
|
1289
|
-
const decoder = new TextDecoder();
|
|
1290
|
-
let buf = "";
|
|
1291
|
-
while (true) {
|
|
1292
|
-
const { done, value } = await reader.read();
|
|
1293
|
-
if (done) break;
|
|
1294
|
-
buf += decoder.decode(value, { stream: true });
|
|
1295
|
-
const lines = buf.split("\n");
|
|
1296
|
-
buf = lines.pop() ?? "";
|
|
1297
|
-
for (const line of lines) {
|
|
1298
|
-
if (!line.startsWith("data: ")) continue;
|
|
1299
|
-
try {
|
|
1300
|
-
const event = JSON.parse(line.slice(6));
|
|
1301
|
-
const icon = PHASE_ICON[event.phase] ?? "\u25B8";
|
|
1302
|
-
const color = event.status === "done" ? kleur6.green : event.status === "failed" ? kleur6.red : event.status === "skipped" ? kleur6.yellow : kleur6.dim;
|
|
1303
|
-
log2(` ${icon} ${color(event.message)}`);
|
|
1304
|
-
if (event.phase === "error" || event.phase === "verify" && event.status === "failed" || event.phase === "promote" && event.status === "failed") {
|
|
1305
|
-
process.exit(1);
|
|
1306
|
-
}
|
|
1307
|
-
} catch {
|
|
1308
|
-
}
|
|
1309
|
-
}
|
|
1310
|
-
}
|
|
1311
|
-
}
|
|
1312
|
-
function parseArgs4(argv) {
|
|
1313
|
-
const args = {
|
|
1314
|
-
platform: "android",
|
|
1315
|
-
language: "ko-KR",
|
|
1316
|
-
dryRun: false,
|
|
1317
|
-
yes: false,
|
|
1318
|
-
skipBuild: false,
|
|
1319
|
-
setupJenkins: false,
|
|
1320
|
-
setupGithub: false,
|
|
1321
|
-
setupGitlab: false,
|
|
1322
|
-
ci: "auto",
|
|
1323
|
-
ref: "main"
|
|
1324
|
-
};
|
|
1325
|
-
for (let i = 0; i < argv.length; i++) {
|
|
1326
|
-
if ((argv[i] === "--platform" || argv[i] === "-p") && argv[i + 1]) args.platform = argv[++i];
|
|
1327
|
-
if (argv[i] === "--app" && argv[i + 1]) args.appId = argv[++i];
|
|
1328
|
-
if (argv[i] === "--version-code" && argv[i + 1]) args.versionCode = Number(argv[++i]);
|
|
1329
|
-
if (argv[i] === "--from" && argv[i + 1]) args.fromRef = argv[++i];
|
|
1330
|
-
if (argv[i] === "--to" && argv[i + 1]) args.toRef = argv[++i];
|
|
1331
|
-
if (argv[i] === "--language" && argv[i + 1]) args.language = argv[++i];
|
|
1332
|
-
if (argv[i] === "--dry-run") args.dryRun = true;
|
|
1333
|
-
if (argv[i] === "--yes" || argv[i] === "-y") args.yes = true;
|
|
1334
|
-
if (argv[i] === "--skip-build") args.skipBuild = true;
|
|
1335
|
-
if (argv[i] === "--ci" && argv[i + 1]) args.ci = argv[++i];
|
|
1336
|
-
if (argv[i] === "--workflow" && argv[i + 1]) args.workflow = argv[++i];
|
|
1337
|
-
if (argv[i] === "--ref" && argv[i + 1]) args.ref = argv[++i];
|
|
1338
|
-
if (argv[i] === "setup-jenkins") args.setupJenkins = true;
|
|
1339
|
-
if (argv[i] === "setup-github") args.setupGithub = true;
|
|
1340
|
-
if (argv[i] === "setup-gitlab") args.setupGitlab = true;
|
|
1341
|
-
}
|
|
1342
|
-
return args;
|
|
1343
|
-
}
|
|
1344
|
-
async function promptJenkinsSetup() {
|
|
1345
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1346
|
-
const ask = (q) => new Promise((resolve) => rl.question(q, (a) => resolve(a.trim())));
|
|
1347
|
-
log2(kleur6.bold("Jenkins \uC124\uC815"));
|
|
1348
|
-
const url = await ask(" Jenkins URL (\uC608: http://your-jenkins.example.com:8080): ");
|
|
1349
|
-
const user = await ask(" Jenkins \uC0AC\uC6A9\uC790\uBA85 (\uC608: admin): ");
|
|
1350
|
-
const token = await ask(" Jenkins API Token: ");
|
|
1351
|
-
const jobAndroid = await ask(" Android Job \uC774\uB984 (\uC608: my-app-android): ");
|
|
1352
|
-
const jobIos = await ask(" iOS Job \uC774\uB984 (\uC120\uD0DD, \uC5D4\uD130 \uC2A4\uD0B5): ");
|
|
1353
|
-
rl.close();
|
|
1354
|
-
return { url, user, token, jobAndroid: jobAndroid || void 0, jobIos: jobIos || void 0 };
|
|
1355
|
-
}
|
|
1356
|
-
async function promptGitProviderSetup(provider) {
|
|
1357
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1358
|
-
const ask = (q) => new Promise((resolve) => rl.question(q, (a) => resolve(a.trim())));
|
|
1359
|
-
const isGh = provider === "github";
|
|
1360
|
-
log2(kleur6.bold(isGh ? "GitHub Actions \uC124\uC815" : "GitLab CI \uC124\uC815"));
|
|
1361
|
-
const tokenLabel = isGh ? " GitHub Personal Access Token (repo+workflow \uC2A4\uCF54\uD504): " : " GitLab Personal Access Token: ";
|
|
1362
|
-
const token = await ask(tokenLabel);
|
|
1363
|
-
const owner = await ask(isGh ? " Owner (org/user): " : " Namespace/group: ");
|
|
1364
|
-
const repo = await ask(" Repo \uC774\uB984 (\uACBD\uB85C \uC5C6\uC774): ");
|
|
1365
|
-
const hostPrompt = isGh ? " GitHub Enterprise host (\uC120\uD0DD, \uC5D4\uD130=github.com): " : " GitLab self-hosted URL (\uC120\uD0DD, \uC5D4\uD130=gitlab.com): ";
|
|
1366
|
-
const host = await ask(hostPrompt);
|
|
1367
|
-
rl.close();
|
|
1368
|
-
return {
|
|
1369
|
-
provider,
|
|
1370
|
-
token,
|
|
1371
|
-
owner,
|
|
1372
|
-
repo,
|
|
1373
|
-
host: host || void 0
|
|
1374
|
-
};
|
|
1375
|
-
}
|
|
1376
|
-
function resolveCi(ciOption, jenkins, ciProvider) {
|
|
1377
|
-
if (ciOption !== "auto") return ciOption;
|
|
1378
|
-
if (jenkins?.url && jenkins.token) return "jenkins";
|
|
1379
|
-
if (ciProvider) return ciProvider.provider;
|
|
1380
|
-
throw new Error(
|
|
1381
|
-
"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"
|
|
1382
|
-
);
|
|
1383
|
-
}
|
|
1384
|
-
async function runGitProviderBuild(cfg, args) {
|
|
1385
|
-
let runUrl = "";
|
|
1386
|
-
let runId;
|
|
1387
|
-
if (cfg.provider === "github") {
|
|
1388
|
-
if (!args.workflow) {
|
|
1389
|
-
throw new Error("--workflow \uD544\uC694 (\uC608: --workflow deploy.yml)");
|
|
1390
|
-
}
|
|
1391
|
-
log2(`\u{1F528} GitHub Actions \uD2B8\uB9AC\uAC70: ${kleur6.cyan(args.workflow)} @ ${args.ref}`);
|
|
1392
|
-
const inputs = {};
|
|
1393
|
-
if (args.appId) inputs.MIMI_APP_ID = args.appId;
|
|
1394
|
-
inputs.PLATFORM = args.platform;
|
|
1395
|
-
const result2 = await ghTriggerWorkflow(cfg, args.workflow, args.ref, inputs);
|
|
1396
|
-
if (!result2) {
|
|
1397
|
-
throw new Error("GitHub Actions run_id \uC870\uD68C \uC2E4\uD328. \uC7A0\uC2DC \uD6C4 ci_list_recent_builds \uB85C \uD655\uC778\uD558\uC138\uC694.");
|
|
1398
|
-
}
|
|
1399
|
-
runId = result2.runId;
|
|
1400
|
-
runUrl = result2.url;
|
|
1401
|
-
log2(kleur6.dim(` Run ID: ${runId} \u2192 ${runUrl}`));
|
|
1402
|
-
} else {
|
|
1403
|
-
log2(`\u{1F528} GitLab Pipeline \uD2B8\uB9AC\uAC70: ${args.ref}`);
|
|
1404
|
-
const variables = { PLATFORM: args.platform };
|
|
1405
|
-
if (args.appId) variables.MIMI_APP_ID = args.appId;
|
|
1406
|
-
const result2 = await glTriggerPipeline(cfg, args.ref, variables);
|
|
1407
|
-
runId = result2.pipelineId;
|
|
1408
|
-
runUrl = result2.url;
|
|
1409
|
-
log2(kleur6.dim(` Pipeline ID: ${runId} \u2192 ${runUrl}`));
|
|
1410
|
-
}
|
|
1411
|
-
log2(" \uC644\uB8CC \uB300\uAE30 \uC911...");
|
|
1412
|
-
let dots = 0;
|
|
1413
|
-
const onTick = (status) => {
|
|
1414
|
-
dots = (dots + 1) % 4;
|
|
1415
|
-
process.stdout.write(`\r \u23F3 ${status}${".".repeat(dots + 1)} `);
|
|
1416
|
-
};
|
|
1417
|
-
const result = cfg.provider === "github" ? await ghPollRun(cfg, runId, onTick) : await glPollPipeline(cfg, runId, onTick);
|
|
1418
|
-
process.stdout.write("\n");
|
|
1419
|
-
if (result === "success") {
|
|
1420
|
-
log2(kleur6.green(`\u2705 \uBE4C\uB4DC #${runId} \uC131\uACF5`));
|
|
1421
|
-
return runId;
|
|
1422
|
-
}
|
|
1423
|
-
log2(kleur6.red(`\uBE4C\uB4DC \uC885\uB8CC: ${result}`));
|
|
1424
|
-
log2(kleur6.dim(` ${runUrl}`));
|
|
1425
|
-
log2(kleur6.dim(` \uBE4C\uB4DC\uAC00 \uC774\uBBF8 \uC644\uB8CC\uB410\uB2E4\uBA74: mimi-seed deploy --skip-build --version-code <N> --platform ${args.platform}`));
|
|
1426
|
-
process.exit(1);
|
|
1427
|
-
}
|
|
1428
|
-
async function cmdDeploy(argv) {
|
|
1429
|
-
const args = parseArgs4(argv);
|
|
1430
|
-
const cfg = await getEffectiveConfig();
|
|
1431
|
-
if (!cfg) {
|
|
1432
|
-
log2(kleur6.red("\uC5F0\uACB0\uB41C \uACC4\uC815 \uC5C6\uC74C. `mimi-seed init` \uC2E4\uD589."));
|
|
1433
|
-
process.exit(1);
|
|
1434
|
-
}
|
|
1435
|
-
if (args.setupJenkins) {
|
|
1436
|
-
const jenkins = await promptJenkinsSetup();
|
|
1437
|
-
await writeConfig({ ...cfg, jenkins });
|
|
1438
|
-
log2(kleur6.green("\u2705 Jenkins \uC124\uC815 \uC800\uC7A5\uB428"));
|
|
1439
|
-
return;
|
|
1440
|
-
}
|
|
1441
|
-
if (args.setupGithub) {
|
|
1442
|
-
const ciCfg = await promptGitProviderSetup("github");
|
|
1443
|
-
saveCiProviderConfig(ciCfg);
|
|
1444
|
-
log2(kleur6.green(`\u2705 GitHub Actions \uC124\uC815 \uC800\uC7A5\uB428 \u2192 ~/.mimi-seed/ci.json`));
|
|
1445
|
-
return;
|
|
1446
|
-
}
|
|
1447
|
-
if (args.setupGitlab) {
|
|
1448
|
-
const ciCfg = await promptGitProviderSetup("gitlab");
|
|
1449
|
-
saveCiProviderConfig(ciCfg);
|
|
1450
|
-
log2(kleur6.green(`\u2705 GitLab CI \uC124\uC815 \uC800\uC7A5\uB428 \u2192 ~/.mimi-seed/ci.json`));
|
|
1451
|
-
return;
|
|
1452
|
-
}
|
|
1453
|
-
log2(kleur6.bold(`mimi-seed deploy \u2014 ${args.platform}`));
|
|
1454
|
-
if (args.dryRun) log2(kleur6.yellow(" [dry-run \uBAA8\uB4DC] \uC2E4\uC81C \uBC30\uD3EC\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4"));
|
|
1455
|
-
log2("");
|
|
1456
|
-
let versionCode = args.versionCode;
|
|
1457
|
-
if (!args.skipBuild) {
|
|
1458
|
-
const ciProvider = loadCiProviderConfig();
|
|
1459
|
-
const kind = resolveCi(args.ci, cfg.jenkins, ciProvider);
|
|
1460
|
-
log2(kleur6.dim(` CI: ${kind}`));
|
|
1461
|
-
if (kind === "jenkins") {
|
|
1462
|
-
if (!cfg.jenkins?.url || !cfg.jenkins?.token) {
|
|
1463
|
-
log2(kleur6.yellow("Jenkins \uC124\uC815 \uC5C6\uC74C. `mimi-seed deploy setup-jenkins` \uB85C \uC124\uC815\uD558\uAC70\uB098 --skip-build \uC0AC\uC6A9."));
|
|
1464
|
-
process.exit(1);
|
|
1465
|
-
}
|
|
1466
|
-
const jenkins = cfg.jenkins;
|
|
1467
|
-
const jobName = args.platform === "android" ? jenkins.jobAndroid : jenkins.jobIos;
|
|
1468
|
-
if (!jobName) {
|
|
1469
|
-
log2(kleur6.red(`${args.platform} Jenkins job\uC774 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. setup-jenkins \uC2E4\uD589.`));
|
|
1470
|
-
process.exit(1);
|
|
1471
|
-
}
|
|
1472
|
-
log2(`\u{1F528} Jenkins \uBE4C\uB4DC \uD2B8\uB9AC\uAC70: ${kleur6.cyan(jobName)}`);
|
|
1473
|
-
const buildParams = {};
|
|
1474
|
-
if (args.appId) buildParams.MIMI_APP_ID = args.appId;
|
|
1475
|
-
const queueItemId = await triggerBuild(jenkins, jobName, buildParams);
|
|
1476
|
-
if (!queueItemId) {
|
|
1477
|
-
log2(kleur6.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."));
|
|
1478
|
-
} else {
|
|
1479
|
-
log2(kleur6.dim(` Queue item: ${queueItemId}`));
|
|
1480
|
-
}
|
|
1481
|
-
let buildNumber = null;
|
|
1482
|
-
if (queueItemId) {
|
|
1483
|
-
log2(" \uBE4C\uB4DC \uBC88\uD638 \uB300\uAE30 \uC911...");
|
|
1484
|
-
for (let i = 0; i < 6; i++) {
|
|
1485
|
-
await new Promise((r) => setTimeout(r, 5e3));
|
|
1486
|
-
buildNumber = await getQueueBuildNumber(jenkins, queueItemId).catch(() => null);
|
|
1487
|
-
if (buildNumber) break;
|
|
1488
|
-
}
|
|
1489
|
-
}
|
|
1490
|
-
if (!buildNumber) {
|
|
1491
|
-
log2(kleur6.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."));
|
|
1492
|
-
process.exit(1);
|
|
1493
|
-
}
|
|
1494
|
-
log2(` \uBE4C\uB4DC #${buildNumber} \uC2DC\uC791\uB428. \uC644\uB8CC \uB300\uAE30 \uC911...`);
|
|
1495
|
-
const result = await pollBuildComplete(jenkins, jobName, buildNumber);
|
|
1496
|
-
if (result !== "SUCCESS") {
|
|
1497
|
-
log2(kleur6.red(`\uBE4C\uB4DC \uC2E4\uD328: ${result}`));
|
|
1498
|
-
log2(kleur6.dim(` Jenkins: ${jenkins.url}/job/${encodeURIComponent(jobName)}/${buildNumber}/`));
|
|
1499
|
-
log2(kleur6.dim(` \uBE4C\uB4DC\uAC00 \uC774\uBBF8 \uC644\uB8CC\uB410\uB2E4\uBA74: mimi-seed deploy --skip-build --version-code ${buildNumber} --platform ${args.platform}`));
|
|
1500
|
-
process.exit(1);
|
|
1501
|
-
}
|
|
1502
|
-
log2(kleur6.green(`\u2705 \uBE4C\uB4DC #${buildNumber} \uC131\uACF5`));
|
|
1503
|
-
if (!versionCode) {
|
|
1504
|
-
versionCode = buildNumber;
|
|
1505
|
-
log2(kleur6.dim(` versionCode = buildNumber (${versionCode})`));
|
|
1506
|
-
}
|
|
1507
|
-
} else {
|
|
1508
|
-
if (!ciProvider) {
|
|
1509
|
-
log2(kleur6.red(`${kind} \uC124\uC815\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. setup-${kind} \uC2E4\uD589.`));
|
|
1510
|
-
process.exit(1);
|
|
1511
|
-
}
|
|
1512
|
-
const buildId = await runGitProviderBuild(ciProvider, args);
|
|
1513
|
-
if (!versionCode) {
|
|
1514
|
-
log2(kleur6.red(`\u2717 versionCode \uBBF8\uC9C0\uC815 (${kind} run_id ${buildId}\uB294 versionCode\uB85C \uBD80\uC801\uD569)`));
|
|
1515
|
-
log2(kleur6.dim(" \uAD8C\uC7A5 \uC0AC\uD56D:"));
|
|
1516
|
-
log2(kleur6.dim(" \u2022 CI \uC6CC\uD06C\uD50C\uB85C \uC548\uC5D0\uC11C versionCode\uB97C \uACB0\uC815\uD558\uACE0 \uACB0\uACFC\uB97C \uCD9C\uB825"));
|
|
1517
|
-
log2(kleur6.dim(" \u2022 \uB2E4\uC74C \uC2E4\uD589: mimi-seed deploy --skip-build --version-code <N>"));
|
|
1518
|
-
process.exit(1);
|
|
1519
|
-
}
|
|
1520
|
-
}
|
|
1521
|
-
}
|
|
1522
|
-
if (!versionCode) {
|
|
1523
|
-
log2(kleur6.red("versionCode\uB97C \uC54C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. --version-code <N> \uC73C\uB85C \uC9C0\uC815\uD558\uC138\uC694."));
|
|
1524
|
-
process.exit(1);
|
|
1525
|
-
}
|
|
1526
|
-
let appId = args.appId;
|
|
1527
|
-
if (!appId) {
|
|
1528
|
-
const { mcpCall: mcpCall2 } = await import("./mcp-client-SLIKR42C.js");
|
|
1529
|
-
const r = await mcpCall2(cfg.endpoint, cfg.token, "list_apps", {});
|
|
1530
|
-
if (!r.isError) {
|
|
1531
|
-
try {
|
|
1532
|
-
const apps = JSON.parse(r.text);
|
|
1533
|
-
if (apps.length > 0) {
|
|
1534
|
-
appId = apps[0].id;
|
|
1535
|
-
log2(kleur6.dim(` \uC571: ${apps[0].name} (${appId})`));
|
|
1536
|
-
}
|
|
1537
|
-
} catch {
|
|
1538
|
-
}
|
|
1539
|
-
}
|
|
1540
|
-
}
|
|
1541
|
-
if (!appId) {
|
|
1542
|
-
log2(kleur6.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."));
|
|
1543
|
-
process.exit(1);
|
|
1544
|
-
}
|
|
1545
|
-
const needsConfirm = !args.dryRun && !args.yes && process.stdout.isTTY && !process.env.MIMI_SEED_TOKEN;
|
|
1546
|
-
if (needsConfirm) {
|
|
1547
|
-
const target = args.platform === "ios" ? "App Store \uC2EC\uC0AC \uC81C\uCD9C" : "Play Store production \uD2B8\uB799";
|
|
1548
|
-
log2("");
|
|
1549
|
-
log2(kleur6.yellow(`\u26A0 \uC2E4\uC81C \uBC30\uD3EC: ${args.platform} \xB7 versionCode ${versionCode} \u2192 ${target}`));
|
|
1550
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1551
|
-
const answer = await new Promise(
|
|
1552
|
-
(resolve) => rl.question(kleur6.bold("\uACC4\uC18D \uC9C4\uD589\uD560\uAE4C\uC694? [y/N]: "), (a) => resolve(a.trim().toLowerCase()))
|
|
1553
|
-
);
|
|
1554
|
-
rl.close();
|
|
1555
|
-
if (answer !== "y" && answer !== "yes") {
|
|
1556
|
-
log2(kleur6.dim("\uCDE8\uC18C\uB428. (--yes \uB85C \uD655\uC778 \uC0DD\uB7B5 \uAC00\uB2A5)"));
|
|
1557
|
-
return;
|
|
1558
|
-
}
|
|
1559
|
-
}
|
|
1560
|
-
log2("");
|
|
1561
|
-
log2("\u{1F4E1} \uC11C\uBC84 \uBC30\uD3EC \uD30C\uC774\uD504\uB77C\uC778 \uC2DC\uC791...");
|
|
1562
|
-
log2("");
|
|
1563
|
-
const deployBuildNumber = !args.skipBuild ? versionCode : void 0;
|
|
1564
|
-
await streamDeploy(cfg.webBase, cfg.token, {
|
|
1565
|
-
appId,
|
|
1566
|
-
platform: args.platform,
|
|
1567
|
-
versionCode,
|
|
1568
|
-
...deployBuildNumber ? { buildNumber: deployBuildNumber } : {},
|
|
1569
|
-
fromRef: args.fromRef,
|
|
1570
|
-
toRef: args.toRef,
|
|
1571
|
-
language: args.language,
|
|
1572
|
-
dryRun: args.dryRun
|
|
1573
|
-
});
|
|
1574
|
-
log2("");
|
|
1575
|
-
log2(kleur6.bold("\uC644\uB8CC. Play Console\uC5D0\uC11C \uBC30\uD3EC \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uC138\uC694."));
|
|
1576
|
-
log2(kleur6.dim(" https://play.google.com/console/developers"));
|
|
1577
|
-
}
|
|
1578
|
-
|
|
1579
861
|
// src/mcp-restart.ts
|
|
1580
862
|
import { execSync as execSync2 } from "child_process";
|
|
1581
|
-
import
|
|
1582
|
-
import
|
|
1583
|
-
import
|
|
863
|
+
import fs2 from "fs";
|
|
864
|
+
import os from "os";
|
|
865
|
+
import path2 from "path";
|
|
1584
866
|
import kleur7 from "kleur";
|
|
1585
867
|
function log3(msg) {
|
|
1586
868
|
process.stdout.write(msg + "\n");
|
|
1587
869
|
}
|
|
1588
870
|
function readClaudeJson() {
|
|
1589
|
-
const p =
|
|
871
|
+
const p = path2.join(os.homedir(), ".claude.json");
|
|
1590
872
|
try {
|
|
1591
|
-
return JSON.parse(
|
|
873
|
+
return JSON.parse(fs2.readFileSync(p, "utf8"));
|
|
1592
874
|
} catch {
|
|
1593
875
|
return {};
|
|
1594
876
|
}
|
|
@@ -1604,7 +886,7 @@ function findProcessMarker(cfg) {
|
|
|
1604
886
|
return meaningful.at(-1) ?? null;
|
|
1605
887
|
}
|
|
1606
888
|
function killByMarker(marker) {
|
|
1607
|
-
const isWin =
|
|
889
|
+
const isWin = os.platform() === "win32";
|
|
1608
890
|
if (isWin) {
|
|
1609
891
|
const escaped = marker.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
1610
892
|
let pids = [];
|
|
@@ -1675,9 +957,9 @@ async function cmdRestart(args) {
|
|
|
1675
957
|
}
|
|
1676
958
|
|
|
1677
959
|
// src/mcp-config.ts
|
|
1678
|
-
import
|
|
1679
|
-
import
|
|
1680
|
-
import
|
|
960
|
+
import fs3 from "fs/promises";
|
|
961
|
+
import path3 from "path";
|
|
962
|
+
import os2 from "os";
|
|
1681
963
|
import kleur8 from "kleur";
|
|
1682
964
|
var SERVER_NAME = "mimi-seed";
|
|
1683
965
|
function tomlString(value) {
|
|
@@ -1721,24 +1003,24 @@ function printMcpSetup(cfg) {
|
|
|
1721
1003
|
);
|
|
1722
1004
|
}
|
|
1723
1005
|
async function writeCodexMcpConfig(cfg) {
|
|
1724
|
-
const configDir =
|
|
1725
|
-
const configPath =
|
|
1726
|
-
await
|
|
1006
|
+
const configDir = path3.join(os2.homedir(), ".codex");
|
|
1007
|
+
const configPath = path3.join(configDir, "config.toml");
|
|
1008
|
+
await fs3.mkdir(configDir, { recursive: true });
|
|
1727
1009
|
let current = "";
|
|
1728
1010
|
try {
|
|
1729
|
-
current = await
|
|
1011
|
+
current = await fs3.readFile(configPath, "utf8");
|
|
1730
1012
|
} catch {
|
|
1731
1013
|
current = "";
|
|
1732
1014
|
}
|
|
1733
1015
|
const next = replaceTomlBlock(current, `mcp_servers.${SERVER_NAME}`, codexBlock(cfg));
|
|
1734
|
-
await
|
|
1016
|
+
await fs3.writeFile(configPath, next);
|
|
1735
1017
|
return configPath;
|
|
1736
1018
|
}
|
|
1737
1019
|
|
|
1738
1020
|
// src/release-manifest.ts
|
|
1739
|
-
import
|
|
1740
|
-
import
|
|
1741
|
-
var RELEASE_MANIFEST_RELATIVE_PATH =
|
|
1021
|
+
import fs4 from "fs/promises";
|
|
1022
|
+
import path4 from "path";
|
|
1023
|
+
var RELEASE_MANIFEST_RELATIVE_PATH = path4.join("docs", "releases.json");
|
|
1742
1024
|
var RELEASE_MANIFEST_TEMPLATE = {
|
|
1743
1025
|
$schema: {
|
|
1744
1026
|
_comment: "Mimi Seed release notes SSOT. Store notes, App Store What's New, in-app announcements, and app-version rollout metadata can share this file.",
|
|
@@ -1764,14 +1046,14 @@ var RELEASE_MANIFEST_TEMPLATE = {
|
|
|
1764
1046
|
versions: {}
|
|
1765
1047
|
};
|
|
1766
1048
|
async function ensureReleaseManifest(cwd) {
|
|
1767
|
-
const filePath =
|
|
1049
|
+
const filePath = path4.join(cwd, RELEASE_MANIFEST_RELATIVE_PATH);
|
|
1768
1050
|
try {
|
|
1769
|
-
await
|
|
1051
|
+
await fs4.access(filePath);
|
|
1770
1052
|
return { path: filePath, created: false };
|
|
1771
1053
|
} catch {
|
|
1772
1054
|
}
|
|
1773
|
-
await
|
|
1774
|
-
await
|
|
1055
|
+
await fs4.mkdir(path4.dirname(filePath), { recursive: true });
|
|
1056
|
+
await fs4.writeFile(filePath, `${JSON.stringify(RELEASE_MANIFEST_TEMPLATE, null, 2)}
|
|
1775
1057
|
`, {
|
|
1776
1058
|
mode: 420
|
|
1777
1059
|
});
|
|
@@ -1822,7 +1104,7 @@ async function cmdInit(args) {
|
|
|
1822
1104
|
return;
|
|
1823
1105
|
}
|
|
1824
1106
|
log4("\u{1F510} \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uB85C\uADF8\uC778 \uB300\uAE30...");
|
|
1825
|
-
const hostName =
|
|
1107
|
+
const hostName = os3.hostname().slice(0, 32);
|
|
1826
1108
|
const name = `cli-${hostName}`;
|
|
1827
1109
|
const { port, promise } = await awaitHandshake(5 * 60 * 1e3);
|
|
1828
1110
|
const callback = `http://127.0.0.1:${port}/cb`;
|
|
@@ -1900,16 +1182,16 @@ async function cmdInit(args) {
|
|
|
1900
1182
|
"- `/mimi-seed:health` \u2014 \uC5F0\uACB0 \uC0C1\uD0DC \uBE60\uB978 \uD655\uC778",
|
|
1901
1183
|
"- `/mimi-seed:review-inbox` \u2014 \uBBF8\uB2F5\uBCC0 \uB9AC\uBDF0 \uB2F5\uBCC0"
|
|
1902
1184
|
].join("\n");
|
|
1903
|
-
const claudeDir =
|
|
1904
|
-
const claudeAgentPath =
|
|
1905
|
-
if (!
|
|
1906
|
-
|
|
1907
|
-
|
|
1185
|
+
const claudeDir = path5.join(cwd, ".claude");
|
|
1186
|
+
const claudeAgentPath = path5.join(claudeDir, "mimi-seed.md");
|
|
1187
|
+
if (!fs5.existsSync(claudeAgentPath)) {
|
|
1188
|
+
fs5.mkdirSync(claudeDir, { recursive: true });
|
|
1189
|
+
fs5.writeFileSync(claudeAgentPath, agentMd, { mode: 420 });
|
|
1908
1190
|
log4(kleur9.dim(` Claude \uC5D0\uC774\uC804\uD2B8 \uC124\uC815: .claude/mimi-seed.md`));
|
|
1909
1191
|
}
|
|
1910
|
-
const codexAgentPath =
|
|
1911
|
-
if (!
|
|
1912
|
-
|
|
1192
|
+
const codexAgentPath = path5.join(cwd, "AGENTS.md");
|
|
1193
|
+
if (!fs5.existsSync(codexAgentPath)) {
|
|
1194
|
+
fs5.writeFileSync(codexAgentPath, agentMd, { mode: 420 });
|
|
1913
1195
|
log4(kleur9.dim(` Codex \uC5D0\uC774\uC804\uD2B8 \uC124\uC815: AGENTS.md`));
|
|
1914
1196
|
}
|
|
1915
1197
|
const manifest = await ensureReleaseManifest(cwd);
|
|
@@ -1935,6 +1217,10 @@ async function cmdInit(args) {
|
|
|
1935
1217
|
log4("2) \uB85C\uCEEC MCP \uC11C\uBC84 \uB4F1\uB85D (\uC6D0\uACA9 'mimi-seed' \uC640 \uBCC4\uAC1C):");
|
|
1936
1218
|
log4(kleur9.cyan(" claude mcp add mimi-seed-local -- npx -y @yoonion/mimi-seed-mcp"));
|
|
1937
1219
|
log4(kleur9.dim(' Codex: ~/.codex/config.toml \uC5D0 [mcp_servers.mimi-seed-local] command="npx", args=["-y","@yoonion/mimi-seed-mcp"]'));
|
|
1220
|
+
log4("");
|
|
1221
|
+
log4("3) \uB098\uBA38\uC9C0 \uACC4\uC815 \uC5F0\uACB0 (App Store / Play / Jenkins / CI / \uC18C\uC15C \u2026):");
|
|
1222
|
+
log4(kleur9.cyan(" mimi-seed setup"));
|
|
1223
|
+
log4(kleur9.dim(" \uAC01 \uD56D\uBAA9\uC5D0\uC11C [?] \uB97C \uB204\uB974\uBA74 \uD1A0\uD070 \uBC1C\uAE09 \uBC29\uBC95\uC744 \uC54C\uB824\uC90D\uB2C8\uB2E4."));
|
|
1938
1224
|
}
|
|
1939
1225
|
}
|
|
1940
1226
|
async function cmdStatus() {
|
|
@@ -2005,6 +1291,29 @@ var CMD_USAGE = {
|
|
|
2005
1291
|
|
|
2006
1292
|
\uC635\uC158:
|
|
2007
1293
|
--local \uCD94\uAC00\uB85C Google OAuth \uB85C\uADF8\uC778 + \uB85C\uCEEC MCP(\uC2A4\uD1A0\uC5B4 \uC4F0\uAE30 \uB3C4\uAD6C \uC804\uCCB4) \uB4F1\uB85D \uC548\uB0B4`,
|
|
1294
|
+
setup: `${kleur9.bold("mimi-seed setup")} \u2014 \uAC00\uC9C4 \uACC4\uC815\uC744 \uD55C \uBC88\uC5D0 \uC5F0\uACB0 (\uC548\uB0B4\uD615 \uB9C8\uBC95\uC0AC)
|
|
1295
|
+
|
|
1296
|
+
\uC5F0\uACB0 \uC0C1\uD0DC\uB97C \uBA3C\uC800 \uBCF4\uC5EC\uC8FC\uACE0, \uC544\uC9C1 \uC5F0\uACB0\uB418\uC9C0 \uC54A\uC740 \uAC83\uB9CC \uC21C\uC11C\uB300\uB85C \uBB3C\uC5B4\uBCF8\uB2E4.
|
|
1297
|
+
\uAC01 \uD56D\uBAA9\uC5D0\uC11C [?] \uB97C \uB204\uB974\uBA74 "\uADF8 \uD1A0\uD070\uC744 \uC5B4\uB514\uC11C \uC5B4\uB5BB\uAC8C \uBC1C\uAE09\uBC1B\uB294\uC9C0"\uB97C \uC54C\uB824\uC900\uB2E4.
|
|
1298
|
+
\uC774\uBBF8 \uC5F0\uACB0\uB41C \uD56D\uBAA9\uC740 \uAC74\uB108\uB6F0\uBBC0\uB85C, \uC911\uAC04\uC5D0 \uADF8\uB9CC\uB450\uACE0 \uB098\uC911\uC5D0 \uB2E4\uC2DC \uC2E4\uD589\uD574\uB3C4 \uB41C\uB2E4.
|
|
1299
|
+
|
|
1300
|
+
\uC635\uC158:
|
|
1301
|
+
--only <ids> \uC9C0\uC815\uD55C \uC790\uACA9\uC99D\uBA85\uB9CC (\uC27C\uD45C \uAD6C\uBD84: oauth,appstore,jenkins \u2026)
|
|
1302
|
+
--reconnect <ids> \uC774\uBBF8 \uC5F0\uACB0\uB3FC \uC788\uC5B4\uB3C4 \uB2E4\uC2DC \uC124\uC815
|
|
1303
|
+
--platform android,ios \uD50C\uB7AB\uD3FC \uAC15\uC81C \uC9C0\uC815 (\uAE30\uBCF8: \uD504\uB85C\uC81D\uD2B8\uC5D0\uC11C \uC790\uB3D9 \uAC10\uC9C0)
|
|
1304
|
+
--yes, -y \uC544\uBB34\uAC83\uB3C4 \uBB3B\uC9C0 \uC54A\uACE0 \uC0C1\uD0DC\uD45C\uB9CC \uCD9C\uB825
|
|
1305
|
+
--non-interactive \uC704\uC640 \uB3D9\uC77C (CI \uC6A9)
|
|
1306
|
+
--fail-on-missing \uD544\uC218 \uC790\uACA9\uC99D\uBA85\uC774 \uC5C6\uC73C\uBA74 exit 1 (CI \uAC8C\uC774\uD2B8)
|
|
1307
|
+
|
|
1308
|
+
${kleur9.dim("\uBE44TTY / CI \uD658\uACBD\uC5D0\uC11C\uB294 \uD504\uB86C\uD504\uD2B8 \uC5C6\uC774 \uC0C1\uD0DC\uD45C\uB9CC \uCD9C\uB825\uD55C\uB2E4.")}`,
|
|
1309
|
+
lang: `${kleur9.bold("mimi-seed lang")} \u2014 CLI \uCD9C\uB825 \uC5B8\uC5B4 (\uD55C\uAD6D\uC5B4 / English)
|
|
1310
|
+
|
|
1311
|
+
mimi-seed lang \uD604\uC7AC \uC5B8\uC5B4 \uD45C\uC2DC
|
|
1312
|
+
mimi-seed lang ko \uD55C\uAD6D\uC5B4 (\uAE30\uBCF8)
|
|
1313
|
+
mimi-seed lang en English
|
|
1314
|
+
|
|
1315
|
+
${kleur9.dim("~/.mimi-seed/settings.json \uC5D0 \uC800\uC7A5\uB429\uB2C8\uB2E4. \uD658\uACBD\uBCC0\uC218 MIMI_SEED_LANG \uAC00 \uC788\uC73C\uBA74 \uADF8\uAC8C \uC6B0\uC120\uD569\uB2C8\uB2E4.")}
|
|
1316
|
+
${kleur9.dim("setup \uB9C8\uBC95\uC0AC\uAC00 \uCCAB \uC2E4\uD589 \uB54C \uBB3C\uC5B4\uBCF4\uBBC0\uB85C \uBCF4\uD1B5\uC740 \uC9C1\uC811 \uCE60 \uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.")}`,
|
|
2008
1317
|
status: `${kleur9.bold("mimi-seed status")} \u2014 \uC5F0\uACB0 \uC0C1\uD0DC + \uB4F1\uB85D \uC571 \uBAA9\uB85D. \uC635\uC158 \uC5C6\uC74C.`,
|
|
2009
1318
|
doctor: `${kleur9.bold("mimi-seed doctor")} \u2014 \uD658\uACBD \uC9C4\uB2E8 (\uD1A0\uD070\xB7Node\xB7Git\xB7\uD504\uB85C\uC81D\uD2B8\xB7CI). \uC635\uC158 \uC5C6\uC74C.`,
|
|
2010
1319
|
logout: `${kleur9.bold("mimi-seed logout")} \u2014 \uB85C\uCEEC \uC124\uC815(config.json) \uC0AD\uC81C. \uC635\uC158 \uC5C6\uC74C.`,
|
|
@@ -2072,8 +1381,10 @@ function printHelp() {
|
|
|
2072
1381
|
|
|
2073
1382
|
${kleur9.bold("\uBA85\uB839\uC5B4:")}
|
|
2074
1383
|
${kleur9.cyan("mimi-seed init")} \uD604\uC7AC \uD504\uB85C\uC81D\uD2B8\uB97C Mimi Seed\uC5D0 \uC5F0\uACB0
|
|
1384
|
+
${kleur9.cyan("mimi-seed setup")} \uAC00\uC9C4 \uACC4\uC815\uC744 \uD55C \uBC88\uC5D0 \uC5F0\uACB0 (\uC548\uB0B4\uD615 \uB9C8\uBC95\uC0AC)
|
|
1385
|
+
${kleur9.cyan("mimi-seed lang")} \uCD9C\uB825 \uC5B8\uC5B4 (ko / en)
|
|
2075
1386
|
${kleur9.cyan("mimi-seed status")} \uC5F0\uACB0 \uC0C1\uD0DC + \uB4F1\uB85D \uC571 \uBAA9\uB85D
|
|
2076
|
-
${kleur9.cyan("mimi-seed auth")} \
|
|
1387
|
+
${kleur9.cyan("mimi-seed auth")} \uC790\uACA9\uC99D\uBA85 \uAC1C\uBCC4 \uC778\uC99D (Google / App Store / Play / Jenkins / CI \u2026)
|
|
2077
1388
|
${kleur9.cyan("mimi-seed firebase")} Firebase \uC571 \uC0DD\uC131\xB7config \uB2E4\uC6B4\uB85C\uB4DC\xB7GA4 \uB9C1\uD06C
|
|
2078
1389
|
${kleur9.cyan("mimi-seed admob")} AdMob \uACC4\uC815\xB7\uC571\xB7\uAD11\uACE0\uB2E8\uC704 \uC870\uD68C \uBC0F \uC0DD\uC131
|
|
2079
1390
|
${kleur9.cyan("mimi-seed ga4")} GA4 property\xB7data stream \uC0DD\uC131\xB7\uC870\uD68C
|
|
@@ -2092,6 +1403,9 @@ ${kleur9.bold("\uD658\uACBD\uBCC0\uC218:")}
|
|
|
2092
1403
|
MIMI_SEED_TOKEN PAT \uD1A0\uD070 (CI/CD \uBB34\uC778\uC99D \uBAA8\uB4DC)
|
|
2093
1404
|
MIMI_SEED_WEB_BASE \uC11C\uBC84 \uC8FC\uC18C (\uAE30\uBCF8: https://mimi-seed.pryzm.gg)
|
|
2094
1405
|
ANTHROPIC_API_KEY AI \uB178\uD2B8 \uC0DD\uC131 \uD65C\uC131\uD654 (\uC120\uD0DD)
|
|
1406
|
+
MIMI_SEED_LANG \uCD9C\uB825 \uC5B8\uC5B4 \uAC15\uC81C (ko / en) \u2014 settings.json \uBCF4\uB2E4 \uC6B0\uC120
|
|
1407
|
+
MIMI_SEED_GOOGLE_CLIENT_ID / _SECRET
|
|
1408
|
+
\uC9C1\uC811 \uB9CC\uB4E0 Google OAuth \uD074\uB77C\uC774\uC5B8\uD2B8 \uC0AC\uC6A9 (\uBBF8\uC9C0\uC815 \uC2DC \uB85C\uADF8\uC778 \uB54C \uC6F9 \uCF58\uC194\uC5D0\uC11C \uBC1B\uC544\uC634)
|
|
2095
1409
|
`);
|
|
2096
1410
|
}
|
|
2097
1411
|
async function main() {
|
|
@@ -2105,6 +1419,12 @@ async function main() {
|
|
|
2105
1419
|
case "init":
|
|
2106
1420
|
await cmdInit(restArgs);
|
|
2107
1421
|
break;
|
|
1422
|
+
case "setup":
|
|
1423
|
+
await cmdSetup(restArgs);
|
|
1424
|
+
break;
|
|
1425
|
+
case "lang":
|
|
1426
|
+
await cmdLang(restArgs);
|
|
1427
|
+
break;
|
|
2108
1428
|
case "status":
|
|
2109
1429
|
await cmdStatus();
|
|
2110
1430
|
break;
|