mimi-seed 0.2.5 → 0.2.7

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.
Files changed (2) hide show
  1. package/dist/index.js +359 -61
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4,7 +4,9 @@ import {
4
4
  } from "./chunk-Q5YGYAFK.js";
5
5
 
6
6
  // src/index.ts
7
- import os3 from "os";
7
+ import os4 from "os";
8
+ import fs5 from "fs";
9
+ import path5 from "path";
8
10
  import kleur8 from "kleur";
9
11
  import open from "open";
10
12
 
@@ -851,6 +853,145 @@ async function cmdAuth(args) {
851
853
  // src/deploy.ts
852
854
  import kleur6 from "kleur";
853
855
  import * as readline from "readline";
856
+
857
+ // src/ci-providers.ts
858
+ import fs3 from "fs";
859
+ import path3 from "path";
860
+ import os2 from "os";
861
+ var CI_CONFIG_PATH = path3.join(os2.homedir(), ".mimi-seed", "ci.json");
862
+ function loadCiProviderConfig() {
863
+ try {
864
+ return JSON.parse(fs3.readFileSync(CI_CONFIG_PATH, "utf-8"));
865
+ } catch {
866
+ return null;
867
+ }
868
+ }
869
+ function saveCiProviderConfig(cfg) {
870
+ const dir = path3.dirname(CI_CONFIG_PATH);
871
+ if (!fs3.existsSync(dir)) {
872
+ fs3.mkdirSync(dir, { recursive: true, mode: 448 });
873
+ }
874
+ fs3.writeFileSync(CI_CONFIG_PATH, JSON.stringify(cfg, null, 2));
875
+ if (process.platform !== "win32") {
876
+ fs3.chmodSync(CI_CONFIG_PATH, 384);
877
+ }
878
+ }
879
+ function ghBase(cfg) {
880
+ if (cfg.host) return `${cfg.host.replace(/\/$/, "")}/api/v3`;
881
+ return "https://api.github.com";
882
+ }
883
+ function ghHeaders(token) {
884
+ return {
885
+ Authorization: `Bearer ${token}`,
886
+ Accept: "application/vnd.github+json",
887
+ "X-GitHub-Api-Version": "2022-11-28",
888
+ "Content-Type": "application/json"
889
+ };
890
+ }
891
+ async function ghTriggerWorkflow(cfg, workflow, ref, inputs = {}) {
892
+ const startTime = /* @__PURE__ */ new Date();
893
+ const wfId = /^\d+$/.test(workflow) ? Number(workflow) : workflow;
894
+ const dispatchRes = await fetch(
895
+ `${ghBase(cfg)}/repos/${cfg.owner}/${cfg.repo}/actions/workflows/${wfId}/dispatches`,
896
+ {
897
+ method: "POST",
898
+ headers: ghHeaders(cfg.token),
899
+ body: JSON.stringify({ ref, inputs })
900
+ }
901
+ );
902
+ if (!dispatchRes.ok) {
903
+ throw new Error(`GitHub dispatch ${dispatchRes.status}: ${await dispatchRes.text()}`);
904
+ }
905
+ await new Promise((r) => setTimeout(r, 3e3));
906
+ const runsRes = await fetch(
907
+ `${ghBase(cfg)}/repos/${cfg.owner}/${cfg.repo}/actions/workflows/${wfId}/runs?per_page=5`,
908
+ { headers: ghHeaders(cfg.token) }
909
+ );
910
+ if (!runsRes.ok) return null;
911
+ const data = await runsRes.json();
912
+ const run = data.workflow_runs.find((r) => new Date(r.created_at) >= startTime) ?? data.workflow_runs[0];
913
+ if (!run) return null;
914
+ return { runId: run.id, url: run.html_url };
915
+ }
916
+ async function ghPollRun(cfg, runId, onTick, timeoutMs = 30 * 60 * 1e3) {
917
+ const start = Date.now();
918
+ let consecutiveErrors = 0;
919
+ while (Date.now() - start < timeoutMs) {
920
+ await new Promise((r) => setTimeout(r, 15e3));
921
+ try {
922
+ const res = await fetch(
923
+ `${ghBase(cfg)}/repos/${cfg.owner}/${cfg.repo}/actions/runs/${runId}`,
924
+ { headers: ghHeaders(cfg.token) }
925
+ );
926
+ if (!res.ok) {
927
+ consecutiveErrors++;
928
+ if (consecutiveErrors >= 3) throw new Error("GitHub API \uC5F0\uC18D \uC624\uB958");
929
+ continue;
930
+ }
931
+ consecutiveErrors = 0;
932
+ const data = await res.json();
933
+ onTick?.(data.status);
934
+ if (data.status === "completed") {
935
+ if (data.conclusion === "success") return "success";
936
+ if (data.conclusion === "cancelled") return "cancelled";
937
+ return "failure";
938
+ }
939
+ } catch {
940
+ consecutiveErrors++;
941
+ if (consecutiveErrors >= 3) throw new Error("GitHub API \uC5F0\uC18D \uC624\uB958 3\uD68C");
942
+ }
943
+ }
944
+ return "timeout";
945
+ }
946
+ function glBase(cfg) {
947
+ return `${cfg.host ?? "https://gitlab.com"}/api/v4`;
948
+ }
949
+ function glProjectId(cfg) {
950
+ return encodeURIComponent(`${cfg.owner}/${cfg.repo}`);
951
+ }
952
+ async function glTriggerPipeline(cfg, ref, variables = {}) {
953
+ const vars = Object.entries(variables).map(([key, value]) => ({ key, value }));
954
+ const body = { ref };
955
+ if (vars.length > 0) body.variables = vars;
956
+ const res = await fetch(`${glBase(cfg)}/projects/${glProjectId(cfg)}/pipeline`, {
957
+ method: "POST",
958
+ headers: { "PRIVATE-TOKEN": cfg.token, "Content-Type": "application/json" },
959
+ body: JSON.stringify(body)
960
+ });
961
+ if (!res.ok) throw new Error(`GitLab trigger ${res.status}: ${await res.text()}`);
962
+ const data = await res.json();
963
+ return { pipelineId: data.id, url: data.web_url };
964
+ }
965
+ async function glPollPipeline(cfg, pipelineId, onTick, timeoutMs = 30 * 60 * 1e3) {
966
+ const start = Date.now();
967
+ let consecutiveErrors = 0;
968
+ while (Date.now() - start < timeoutMs) {
969
+ await new Promise((r) => setTimeout(r, 15e3));
970
+ try {
971
+ const res = await fetch(
972
+ `${glBase(cfg)}/projects/${glProjectId(cfg)}/pipelines/${pipelineId}`,
973
+ { headers: { "PRIVATE-TOKEN": cfg.token } }
974
+ );
975
+ if (!res.ok) {
976
+ consecutiveErrors++;
977
+ if (consecutiveErrors >= 3) throw new Error("GitLab API \uC5F0\uC18D \uC624\uB958");
978
+ continue;
979
+ }
980
+ consecutiveErrors = 0;
981
+ const data = await res.json();
982
+ onTick?.(data.status);
983
+ if (data.status === "success") return "success";
984
+ if (data.status === "failed") return "failure";
985
+ if (data.status === "canceled" || data.status === "skipped") return "cancelled";
986
+ } catch {
987
+ consecutiveErrors++;
988
+ if (consecutiveErrors >= 3) throw new Error("GitLab API \uC5F0\uC18D \uC624\uB958 3\uD68C");
989
+ }
990
+ }
991
+ return "timeout";
992
+ }
993
+
994
+ // src/deploy.ts
854
995
  var PHASE_ICON = {
855
996
  init: "\u{1F680}",
856
997
  verify: "\u{1F50D}",
@@ -961,7 +1102,17 @@ async function streamDeploy(webBase, token, body) {
961
1102
  }
962
1103
  }
963
1104
  function parseArgs4(argv) {
964
- const args = { platform: "android", language: "ko-KR", dryRun: false, skipBuild: false, setupJenkins: false };
1105
+ const args = {
1106
+ platform: "android",
1107
+ language: "ko-KR",
1108
+ dryRun: false,
1109
+ skipBuild: false,
1110
+ setupJenkins: false,
1111
+ setupGithub: false,
1112
+ setupGitlab: false,
1113
+ ci: "auto",
1114
+ ref: "main"
1115
+ };
965
1116
  for (let i = 0; i < argv.length; i++) {
966
1117
  if ((argv[i] === "--platform" || argv[i] === "-p") && argv[i + 1]) args.platform = argv[++i];
967
1118
  if (argv[i] === "--app" && argv[i + 1]) args.appId = argv[++i];
@@ -971,7 +1122,12 @@ function parseArgs4(argv) {
971
1122
  if (argv[i] === "--language" && argv[i + 1]) args.language = argv[++i];
972
1123
  if (argv[i] === "--dry-run") args.dryRun = true;
973
1124
  if (argv[i] === "--skip-build") args.skipBuild = true;
1125
+ if (argv[i] === "--ci" && argv[i + 1]) args.ci = argv[++i];
1126
+ if (argv[i] === "--workflow" && argv[i + 1]) args.workflow = argv[++i];
1127
+ if (argv[i] === "--ref" && argv[i + 1]) args.ref = argv[++i];
974
1128
  if (argv[i] === "setup-jenkins") args.setupJenkins = true;
1129
+ if (argv[i] === "setup-github") args.setupGithub = true;
1130
+ if (argv[i] === "setup-gitlab") args.setupGitlab = true;
975
1131
  }
976
1132
  return args;
977
1133
  }
@@ -987,6 +1143,78 @@ async function promptJenkinsSetup() {
987
1143
  rl.close();
988
1144
  return { url, user, token, jobAndroid: jobAndroid || void 0, jobIos: jobIos || void 0 };
989
1145
  }
1146
+ async function promptGitProviderSetup(provider) {
1147
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1148
+ const ask = (q) => new Promise((resolve) => rl.question(q, (a) => resolve(a.trim())));
1149
+ const isGh = provider === "github";
1150
+ log2(kleur6.bold(isGh ? "GitHub Actions \uC124\uC815" : "GitLab CI \uC124\uC815"));
1151
+ const tokenLabel = isGh ? " GitHub Personal Access Token (repo+workflow \uC2A4\uCF54\uD504): " : " GitLab Personal Access Token: ";
1152
+ const token = await ask(tokenLabel);
1153
+ const owner = await ask(isGh ? " Owner (org/user): " : " Namespace/group: ");
1154
+ const repo = await ask(" Repo \uC774\uB984 (\uACBD\uB85C \uC5C6\uC774): ");
1155
+ const hostPrompt = isGh ? " GitHub Enterprise host (\uC120\uD0DD, \uC5D4\uD130=github.com): " : " GitLab self-hosted URL (\uC120\uD0DD, \uC5D4\uD130=gitlab.com): ";
1156
+ const host = await ask(hostPrompt);
1157
+ rl.close();
1158
+ return {
1159
+ provider,
1160
+ token,
1161
+ owner,
1162
+ repo,
1163
+ host: host || void 0
1164
+ };
1165
+ }
1166
+ function resolveCi(ciOption, jenkins, ciProvider) {
1167
+ if (ciOption !== "auto") return ciOption;
1168
+ if (jenkins?.url && jenkins.token) return "jenkins";
1169
+ if (ciProvider) return ciProvider.provider;
1170
+ throw new Error(
1171
+ "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"
1172
+ );
1173
+ }
1174
+ async function runGitProviderBuild(cfg, args) {
1175
+ let runUrl = "";
1176
+ let runId;
1177
+ if (cfg.provider === "github") {
1178
+ if (!args.workflow) {
1179
+ throw new Error("--workflow \uD544\uC694 (\uC608: --workflow deploy.yml)");
1180
+ }
1181
+ log2(`\u{1F528} GitHub Actions \uD2B8\uB9AC\uAC70: ${kleur6.cyan(args.workflow)} @ ${args.ref}`);
1182
+ const inputs = {};
1183
+ if (args.appId) inputs.MIMI_APP_ID = args.appId;
1184
+ inputs.PLATFORM = args.platform;
1185
+ const result2 = await ghTriggerWorkflow(cfg, args.workflow, args.ref, inputs);
1186
+ if (!result2) {
1187
+ throw new Error("GitHub Actions run_id \uC870\uD68C \uC2E4\uD328. \uC7A0\uC2DC \uD6C4 ci_list_recent_builds \uB85C \uD655\uC778\uD558\uC138\uC694.");
1188
+ }
1189
+ runId = result2.runId;
1190
+ runUrl = result2.url;
1191
+ log2(kleur6.dim(` Run ID: ${runId} \u2192 ${runUrl}`));
1192
+ } else {
1193
+ log2(`\u{1F528} GitLab Pipeline \uD2B8\uB9AC\uAC70: ${args.ref}`);
1194
+ const variables = { PLATFORM: args.platform };
1195
+ if (args.appId) variables.MIMI_APP_ID = args.appId;
1196
+ const result2 = await glTriggerPipeline(cfg, args.ref, variables);
1197
+ runId = result2.pipelineId;
1198
+ runUrl = result2.url;
1199
+ log2(kleur6.dim(` Pipeline ID: ${runId} \u2192 ${runUrl}`));
1200
+ }
1201
+ log2(" \uC644\uB8CC \uB300\uAE30 \uC911...");
1202
+ let dots = 0;
1203
+ const onTick = (status) => {
1204
+ dots = (dots + 1) % 4;
1205
+ process.stdout.write(`\r \u23F3 ${status}${".".repeat(dots + 1)} `);
1206
+ };
1207
+ const result = cfg.provider === "github" ? await ghPollRun(cfg, runId, onTick) : await glPollPipeline(cfg, runId, onTick);
1208
+ process.stdout.write("\n");
1209
+ if (result === "success") {
1210
+ log2(kleur6.green(`\u2705 \uBE4C\uB4DC #${runId} \uC131\uACF5`));
1211
+ return runId;
1212
+ }
1213
+ log2(kleur6.red(`\uBE4C\uB4DC \uC885\uB8CC: ${result}`));
1214
+ log2(kleur6.dim(` ${runUrl}`));
1215
+ log2(kleur6.dim(` \uBE4C\uB4DC\uAC00 \uC774\uBBF8 \uC644\uB8CC\uB410\uB2E4\uBA74: mimi-seed deploy --skip-build --version-code <N> --platform ${args.platform}`));
1216
+ process.exit(1);
1217
+ }
990
1218
  async function cmdDeploy(argv) {
991
1219
  const args = parseArgs4(argv);
992
1220
  const cfg = await getEffectiveConfig();
@@ -1000,56 +1228,84 @@ async function cmdDeploy(argv) {
1000
1228
  log2(kleur6.green("\u2705 Jenkins \uC124\uC815 \uC800\uC7A5\uB428"));
1001
1229
  return;
1002
1230
  }
1231
+ if (args.setupGithub) {
1232
+ const ciCfg = await promptGitProviderSetup("github");
1233
+ saveCiProviderConfig(ciCfg);
1234
+ log2(kleur6.green(`\u2705 GitHub Actions \uC124\uC815 \uC800\uC7A5\uB428 \u2192 ~/.mimi-seed/ci.json`));
1235
+ return;
1236
+ }
1237
+ if (args.setupGitlab) {
1238
+ const ciCfg = await promptGitProviderSetup("gitlab");
1239
+ saveCiProviderConfig(ciCfg);
1240
+ log2(kleur6.green(`\u2705 GitLab CI \uC124\uC815 \uC800\uC7A5\uB428 \u2192 ~/.mimi-seed/ci.json`));
1241
+ return;
1242
+ }
1003
1243
  log2(kleur6.bold(`mimi-seed deploy \u2014 ${args.platform}`));
1004
1244
  if (args.dryRun) log2(kleur6.yellow(" [dry-run \uBAA8\uB4DC] \uC2E4\uC81C \uBC30\uD3EC\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4"));
1005
1245
  log2("");
1006
1246
  let versionCode = args.versionCode;
1007
1247
  if (!args.skipBuild) {
1008
- if (!cfg.jenkins?.url || !cfg.jenkins?.token) {
1009
- log2(kleur6.yellow("Jenkins \uC124\uC815 \uC5C6\uC74C. `mimi-seed deploy setup-jenkins` \uB85C \uC124\uC815\uD558\uAC70\uB098 --skip-build \uC0AC\uC6A9."));
1010
- log2(kleur6.dim(" \uB610\uB294 \uC11C\uBC84 /workspace/integrations\uC5D0\uC11C jenkins \uD504\uB85C\uBC14\uC774\uB354 \uB4F1\uB85D \uD6C4 \uC11C\uBC84\uC0AC\uC774\uB4DC \uD2B8\uB9AC\uAC70 \uAC00\uB2A5."));
1011
- process.exit(1);
1012
- }
1013
- const jenkins = cfg.jenkins;
1014
- const jobName = args.platform === "android" ? jenkins.jobAndroid : jenkins.jobIos;
1015
- if (!jobName) {
1016
- log2(kleur6.red(`${args.platform} Jenkins job\uC774 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. setup-jenkins \uC2E4\uD589.`));
1017
- process.exit(1);
1018
- }
1019
- log2(`\u{1F528} Jenkins \uBE4C\uB4DC \uD2B8\uB9AC\uAC70: ${kleur6.cyan(jobName)}`);
1020
- const buildParams = {};
1021
- if (args.appId) buildParams.MIMI_APP_ID = args.appId;
1022
- const queueItemId = await triggerBuild(jenkins, jobName, buildParams);
1023
- if (!queueItemId) {
1024
- 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."));
1248
+ const ciProvider = loadCiProviderConfig();
1249
+ const kind = resolveCi(args.ci, cfg.jenkins, ciProvider);
1250
+ log2(kleur6.dim(` CI: ${kind}`));
1251
+ if (kind === "jenkins") {
1252
+ if (!cfg.jenkins?.url || !cfg.jenkins?.token) {
1253
+ log2(kleur6.yellow("Jenkins \uC124\uC815 \uC5C6\uC74C. `mimi-seed deploy setup-jenkins` \uB85C \uC124\uC815\uD558\uAC70\uB098 --skip-build \uC0AC\uC6A9."));
1254
+ process.exit(1);
1255
+ }
1256
+ const jenkins = cfg.jenkins;
1257
+ const jobName = args.platform === "android" ? jenkins.jobAndroid : jenkins.jobIos;
1258
+ if (!jobName) {
1259
+ log2(kleur6.red(`${args.platform} Jenkins job\uC774 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. setup-jenkins \uC2E4\uD589.`));
1260
+ process.exit(1);
1261
+ }
1262
+ log2(`\u{1F528} Jenkins \uBE4C\uB4DC \uD2B8\uB9AC\uAC70: ${kleur6.cyan(jobName)}`);
1263
+ const buildParams = {};
1264
+ if (args.appId) buildParams.MIMI_APP_ID = args.appId;
1265
+ const queueItemId = await triggerBuild(jenkins, jobName, buildParams);
1266
+ if (!queueItemId) {
1267
+ 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."));
1268
+ } else {
1269
+ log2(kleur6.dim(` Queue item: ${queueItemId}`));
1270
+ }
1271
+ let buildNumber = null;
1272
+ if (queueItemId) {
1273
+ log2(" \uBE4C\uB4DC \uBC88\uD638 \uB300\uAE30 \uC911...");
1274
+ for (let i = 0; i < 6; i++) {
1275
+ await new Promise((r) => setTimeout(r, 5e3));
1276
+ buildNumber = await getQueueBuildNumber(jenkins, queueItemId).catch(() => null);
1277
+ if (buildNumber) break;
1278
+ }
1279
+ }
1280
+ if (!buildNumber) {
1281
+ 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."));
1282
+ process.exit(1);
1283
+ }
1284
+ log2(` \uBE4C\uB4DC #${buildNumber} \uC2DC\uC791\uB428. \uC644\uB8CC \uB300\uAE30 \uC911...`);
1285
+ const result = await pollBuildComplete(jenkins, jobName, buildNumber);
1286
+ if (result !== "SUCCESS") {
1287
+ log2(kleur6.red(`\uBE4C\uB4DC \uC2E4\uD328: ${result}`));
1288
+ log2(kleur6.dim(` Jenkins: ${jenkins.url}/job/${encodeURIComponent(jobName)}/${buildNumber}/`));
1289
+ log2(kleur6.dim(` \uBE4C\uB4DC\uAC00 \uC774\uBBF8 \uC644\uB8CC\uB410\uB2E4\uBA74: mimi-seed deploy --skip-build --version-code ${buildNumber} --platform ${args.platform}`));
1290
+ process.exit(1);
1291
+ }
1292
+ log2(kleur6.green(`\u2705 \uBE4C\uB4DC #${buildNumber} \uC131\uACF5`));
1293
+ if (!versionCode) {
1294
+ versionCode = buildNumber;
1295
+ log2(kleur6.dim(` versionCode = buildNumber (${versionCode})`));
1296
+ }
1025
1297
  } else {
1026
- log2(kleur6.dim(` Queue item: ${queueItemId}`));
1027
- }
1028
- let buildNumber = null;
1029
- if (queueItemId) {
1030
- log2(" \uBE4C\uB4DC \uBC88\uD638 \uB300\uAE30 \uC911...");
1031
- for (let i = 0; i < 6; i++) {
1032
- await new Promise((r) => setTimeout(r, 5e3));
1033
- buildNumber = await getQueueBuildNumber(jenkins, queueItemId).catch(() => null);
1034
- if (buildNumber) break;
1298
+ if (!ciProvider) {
1299
+ log2(kleur6.red(`${kind} \uC124\uC815\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. setup-${kind} \uC2E4\uD589.`));
1300
+ process.exit(1);
1301
+ }
1302
+ const buildId = await runGitProviderBuild(ciProvider, args);
1303
+ if (!versionCode) {
1304
+ versionCode = buildId;
1305
+ log2(kleur6.dim(` versionCode = build id (${versionCode})`));
1306
+ log2(kleur6.dim(` \uC8FC\uC758: GitHub run ID / GitLab pipeline ID\uB294 versionCode\uB85C \uC801\uD569\uD558\uC9C0 \uC54A\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4.`));
1307
+ log2(kleur6.dim(` --version-code <N> \uB85C \uBA85\uC2DC \uAD8C\uC7A5.`));
1035
1308
  }
1036
- }
1037
- if (!buildNumber) {
1038
- 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."));
1039
- process.exit(1);
1040
- }
1041
- log2(` \uBE4C\uB4DC #${buildNumber} \uC2DC\uC791\uB428. \uC644\uB8CC \uB300\uAE30 \uC911...`);
1042
- const result = await pollBuildComplete(jenkins, jobName, buildNumber);
1043
- if (result !== "SUCCESS") {
1044
- log2(kleur6.red(`\uBE4C\uB4DC \uC2E4\uD328: ${result}`));
1045
- log2(kleur6.dim(` Jenkins: ${jenkins.url}/job/${encodeURIComponent(jobName)}/${buildNumber}/`));
1046
- log2(kleur6.dim(` \uBE4C\uB4DC\uAC00 \uC774\uBBF8 \uC644\uB8CC\uB410\uB2E4\uBA74: mimi-seed deploy --skip-build --version-code ${buildNumber} --platform ${args.platform}`));
1047
- process.exit(1);
1048
- }
1049
- log2(kleur6.green(`\u2705 \uBE4C\uB4DC #${buildNumber} \uC131\uACF5`));
1050
- if (!versionCode) {
1051
- versionCode = buildNumber;
1052
- log2(kleur6.dim(` versionCode = buildNumber (${versionCode})`));
1053
1309
  }
1054
1310
  }
1055
1311
  if (!versionCode) {
@@ -1096,17 +1352,17 @@ async function cmdDeploy(argv) {
1096
1352
 
1097
1353
  // src/mcp-restart.ts
1098
1354
  import { execSync as execSync2 } from "child_process";
1099
- import fs3 from "fs";
1100
- import os2 from "os";
1101
- import path3 from "path";
1355
+ import fs4 from "fs";
1356
+ import os3 from "os";
1357
+ import path4 from "path";
1102
1358
  import kleur7 from "kleur";
1103
1359
  function log3(msg) {
1104
1360
  process.stdout.write(msg + "\n");
1105
1361
  }
1106
1362
  function readClaudeJson() {
1107
- const p = path3.join(os2.homedir(), ".claude.json");
1363
+ const p = path4.join(os3.homedir(), ".claude.json");
1108
1364
  try {
1109
- return JSON.parse(fs3.readFileSync(p, "utf8"));
1365
+ return JSON.parse(fs4.readFileSync(p, "utf8"));
1110
1366
  } catch {
1111
1367
  return {};
1112
1368
  }
@@ -1122,7 +1378,7 @@ function findProcessMarker(cfg) {
1122
1378
  return meaningful.at(-1) ?? null;
1123
1379
  }
1124
1380
  function killByMarker(marker) {
1125
- const isWin = os2.platform() === "win32";
1381
+ const isWin = os3.platform() === "win32";
1126
1382
  if (isWin) {
1127
1383
  const escaped = marker.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
1128
1384
  let pids = [];
@@ -1233,7 +1489,7 @@ async function cmdInit() {
1233
1489
  return;
1234
1490
  }
1235
1491
  log4("\u{1F510} \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uB85C\uADF8\uC778 \uB300\uAE30...");
1236
- const hostName = os3.hostname().slice(0, 32);
1492
+ const hostName = os4.hostname().slice(0, 32);
1237
1493
  const name = `cli-${hostName}`;
1238
1494
  const { port, promise } = await awaitHandshake(5 * 60 * 1e3);
1239
1495
  const callback = `http://127.0.0.1:${port}/cb`;
@@ -1269,6 +1525,43 @@ async function cmdInit() {
1269
1525
  }
1270
1526
  log4("");
1271
1527
  }
1528
+ const claudeDir = path5.join(cwd, ".claude");
1529
+ const agentMdPath = path5.join(claudeDir, "mimi-seed.md");
1530
+ if (!fs5.existsSync(agentMdPath)) {
1531
+ fs5.mkdirSync(claudeDir, { recursive: true });
1532
+ const appLines = hints.flatMap((h) => {
1533
+ const parts = [
1534
+ h.name && ` name: ${h.name}`,
1535
+ h.packageName && ` packageName: ${h.packageName}`,
1536
+ h.bundleId && ` bundleId: ${h.bundleId}`
1537
+ ].filter(Boolean);
1538
+ return parts;
1539
+ });
1540
+ const agentMd = [
1541
+ "# Mimi Seed Agent",
1542
+ "",
1543
+ "Mimi Seed MCP\uAC00 \uC774 \uD504\uB85C\uC81D\uD2B8\uC5D0 \uC5F0\uACB0\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.",
1544
+ "Google Play \xB7 App Store \xB7 Firebase \xB7 AdMob\uC744 \uB3C4\uAD6C\uB85C \uC9C1\uC811 \uC81C\uC5B4\uD569\uB2C8\uB2E4.",
1545
+ "",
1546
+ "## \uCD9C\uC2DC \uC694\uCCAD \uCC98\uB9AC \uC21C\uC11C",
1547
+ "",
1548
+ "1. \uD56D\uC0C1 `playstore_check_submission_risks` / `appstore_check_submission_risks` \uB85C \uBE14\uB85C\uCEE4 \uBA3C\uC800 \uD655\uC778",
1549
+ "2. \uB9B4\uB9AC\uC988 \uB178\uD2B8: `generate_release_notes_from_commits` \u2192 \uC0AC\uC6A9\uC790 \uD655\uC778 \uD6C4 \uC801\uC6A9",
1550
+ "3. \uC2A4\uD1A0\uC5B4 **\uC4F0\uAE30** \uC791\uC5C5(submit, apply, reply)\uC740 \uBC18\uB4DC\uC2DC \uC0AC\uC6A9\uC790 \uBA85\uC2DC \uB3D9\uC758 \uD6C4 \uC2E4\uD589",
1551
+ "4. \uC644\uB8CC \uD6C4 \uACB0\uACFC \uC694\uC57D \uC81C\uACF5",
1552
+ "",
1553
+ "## \uC571 \uC815\uBCF4",
1554
+ ...appLines.length > 0 ? appLines : [" (mimi-seed status \uB85C \uD655\uC778)"],
1555
+ "",
1556
+ "## \uC2AC\uB798\uC2DC \uCEE4\uB9E8\uB4DC",
1557
+ "",
1558
+ "- `/mimi-seed:deploy` \u2014 \uC804\uCCB4 \uCD9C\uC2DC \uD30C\uC774\uD504\uB77C\uC778",
1559
+ "- `/mimi-seed:health` \u2014 \uC5F0\uACB0 \uC0C1\uD0DC \uBE60\uB978 \uD655\uC778",
1560
+ "- `/mimi-seed:review-inbox` \u2014 \uBBF8\uB2F5\uBCC0 \uB9AC\uBDF0 \uB2F5\uBCC0"
1561
+ ].join("\n");
1562
+ fs5.writeFileSync(agentMdPath, agentMd, { mode: 420 });
1563
+ log4(kleur8.dim(` \uC5D0\uC774\uC804\uD2B8 \uC124\uC815: .claude/mimi-seed.md`));
1564
+ }
1272
1565
  log4(kleur8.bold("\u2713 \uC900\uBE44 \uC644\uB8CC."));
1273
1566
  log4("");
1274
1567
  log4("Claude Code\uC5D0\uC11C \uC774\uB807\uAC8C \uBB3C\uC5B4\uBCF4\uC138\uC694:");
@@ -1348,15 +1641,20 @@ ${kleur8.bold("mimi-seed review \uC635\uC158:")}
1348
1641
  --no-interactive CI \uBAA8\uB4DC
1349
1642
 
1350
1643
  ${kleur8.bold("mimi-seed deploy \uC635\uC158:")}
1351
- --platform android|ios \uBC30\uD3EC \uD50C\uB7AB\uD3FC (\uAE30\uBCF8: android)
1352
- --app <id> \uC571 ID \uC9C0\uC815
1353
- --version-code <n> \uBE4C\uB4DC \uBC88\uD638 \uC9C1\uC811 \uC9C0\uC815 (--skip-build \uC640 \uD568\uAED8)
1354
- --from <ref> \uCEE4\uBC0B \uBC94\uC704 \uC2DC\uC791 (\uB9B4\uB9AC\uC988 \uB178\uD2B8\uC6A9)
1355
- --to <ref> \uCEE4\uBC0B \uBC94\uC704 \uB05D (\uAE30\uBCF8: HEAD)
1356
- --language <\uCF54\uB4DC> \uB9B4\uB9AC\uC988 \uB178\uD2B8 \uC5B8\uC5B4 (\uAE30\uBCF8: ko-KR)
1357
- --dry-run \uC2E4\uC81C \uBC30\uD3EC \uC5C6\uC774 \uD30C\uC774\uD504\uB77C\uC778 \uD14C\uC2A4\uD2B8
1358
- --skip-build Jenkins \uBE4C\uB4DC \uAC74\uB108\uB700 (--version-code \uD544\uC218)
1359
- setup-jenkins Jenkins \uC124\uC815 \uB300\uD654\uD615 \uB4F1\uB85D
1644
+ --platform android|ios \uBC30\uD3EC \uD50C\uB7AB\uD3FC (\uAE30\uBCF8: android)
1645
+ --app <id> \uC571 ID \uC9C0\uC815
1646
+ --version-code <n> \uBE4C\uB4DC \uBC88\uD638 \uC9C1\uC811 \uC9C0\uC815 (--skip-build \uC640 \uD568\uAED8)
1647
+ --from <ref> \uCEE4\uBC0B \uBC94\uC704 \uC2DC\uC791 (\uB9B4\uB9AC\uC988 \uB178\uD2B8\uC6A9)
1648
+ --to <ref> \uCEE4\uBC0B \uBC94\uC704 \uB05D (\uAE30\uBCF8: HEAD)
1649
+ --language <\uCF54\uB4DC> \uB9B4\uB9AC\uC988 \uB178\uD2B8 \uC5B8\uC5B4 (\uAE30\uBCF8: ko-KR)
1650
+ --dry-run \uC2E4\uC81C \uBC30\uD3EC \uC5C6\uC774 \uD30C\uC774\uD504\uB77C\uC778 \uD14C\uC2A4\uD2B8
1651
+ --skip-build CI \uBE4C\uB4DC \uAC74\uB108\uB700 (--version-code \uD544\uC218)
1652
+ --ci jenkins|github|gitlab CI \uAC15\uC81C \uC120\uD0DD (\uAE30\uBCF8: auto)
1653
+ --workflow <file> GitHub workflow \uD30C\uC77C (\uC608: deploy.yml)
1654
+ --ref <branch|tag> GitHub/GitLab \uBE0C\uB79C\uCE58/\uD0DC\uADF8 (\uAE30\uBCF8: main)
1655
+ setup-jenkins Jenkins \uC124\uC815 \uB300\uD654\uD615 \uB4F1\uB85D
1656
+ setup-github GitHub Actions \uC124\uC815 \uB300\uD654\uD615 \uB4F1\uB85D
1657
+ setup-gitlab GitLab CI \uC124\uC815 \uB300\uD654\uD615 \uB4F1\uB85D
1360
1658
 
1361
1659
  ${kleur8.bold("\uD658\uACBD\uBCC0\uC218:")}
1362
1660
  MIMI_SEED_TOKEN PAT \uD1A0\uD070 (CI/CD \uBB34\uC778\uC99D \uBAA8\uB4DC)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mimi-seed",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "description": "Mimi Seed CLI — Claude Code에서 앱 출시 운영을 관리합니다.",
5
5
  "bin": {
6
6
  "mimi-seed": "dist/index.js"