gencow 0.1.112 → 0.1.113

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/bin/gencow.mjs CHANGED
@@ -29,15 +29,37 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
29
29
 
30
30
  const CREDS_PATH = resolve(homedir(), ".gencow", "credentials.json");
31
31
 
32
+ // ─── Domain Helpers (Dev/Prod 환경 분리) ─────────────────
33
+ // platformUrl에서 기본 도메인을 추출하여 동적 URL 생성.
34
+ // DEV: https://gencow.dev → gencow.dev
35
+ // PROD: https://gencow.app → gencow.app
36
+
37
+ function getBaseDomain(platformUrl) {
38
+ try {
39
+ return new URL(platformUrl).hostname;
40
+ } catch {
41
+ return "gencow.app";
42
+ }
43
+ }
44
+
45
+ function getAppUrl(appName, platformUrl) {
46
+ const domain = getBaseDomain(platformUrl);
47
+ return `https://${appName}.${domain}`;
48
+ }
49
+
50
+ function getWsUrl(appName, platformUrl) {
51
+ const domain = getBaseDomain(platformUrl);
52
+ return `wss://${appName}.${domain}/ws`;
53
+ }
54
+
55
+ function getDashboardUrl(appName, platformUrl) {
56
+ const domain = getBaseDomain(platformUrl);
57
+ return `https://${domain}/apps/${appName}`;
58
+ }
59
+
32
60
  function loadCreds() {
33
61
  try {
34
- const creds = JSON.parse(readFileSync(CREDS_PATH, "utf8"));
35
- // gencow.dev → gencow.app 자동 마이그레이션 (301 리다이렉트 시 POST→GET 변환 방지)
36
- if (creds?.platformUrl?.includes("gencow.dev")) {
37
- creds.platformUrl = creds.platformUrl.replace("gencow.dev", "gencow.app");
38
- try { writeFileSync(CREDS_PATH, JSON.stringify(creds, null, 2)); } catch { }
39
- }
40
- return creds;
62
+ return JSON.parse(readFileSync(CREDS_PATH, "utf8"));
41
63
  } catch {
42
64
  return null;
43
65
  }
@@ -52,8 +74,8 @@ function requireCreds() {
52
74
  // CI/CD 환경: 환경변수 토큰 우선 (Deploy Token 또는 CLI Token)
53
75
  const envToken = process.env.GENCOW_TOKEN || process.env.GENCOW_DEPLOY_TOKEN;
54
76
  if (envToken) {
55
- const platformUrl = process.env.GENCOW_PLATFORM_URL || "https://gencow.app";
56
- return { apiKey: envToken, platformUrl };
77
+ const platformUrl = process.env.GENCOW_PLATFORM_URL || "https://gencow.app"; // 기본값은 PROD
78
+ return { apiKey: envToken, platformUrl: validatePlatformUrl(platformUrl) };
57
79
  }
58
80
 
59
81
  // 로컬: ~/.gencow/credentials.json
@@ -68,9 +90,27 @@ function requireCreds() {
68
90
  }
69
91
  process.exit(1);
70
92
  }
93
+ // 기존 credentials에도 platformUrl 검증 적용
94
+ if (creds.platformUrl) {
95
+ creds.platformUrl = validatePlatformUrl(creds.platformUrl);
96
+ }
71
97
  return creds;
72
98
  }
73
99
 
100
+ /** platformUrl 보안 검증 — https:// 스킴 강제 (localhost 제외) */
101
+ function validatePlatformUrl(url) {
102
+ if (!url) return "https://gencow.app";
103
+ // localhost는 개발용으로 http 허용
104
+ if (url.startsWith("http://localhost")) return url;
105
+ // 그 외는 반드시 https://
106
+ if (!url.startsWith("https://")) {
107
+ error(`platformUrl must use https:// (got: ${url})`);
108
+ info("시크릿이 평문으로 전송될 수 있습니다. GENCOW_PLATFORM_URL을 확인하세요.");
109
+ process.exit(1);
110
+ }
111
+ return url;
112
+ }
113
+
74
114
  async function platformFetch(creds, path, opts = {}) {
75
115
  const url = `${creds.platformUrl}${path}`;
76
116
  const res = await fetch(url, {
@@ -1055,10 +1095,12 @@ ${hasPrompt ? `
1055
1095
  // ── Cloud 모드 (기본) ──
1056
1096
  const gencowJsonPath = resolve(process.cwd(), "gencow.json");
1057
1097
  let appId = null;
1098
+ let seedPlatformUrl = null;
1058
1099
  if (existsSync(gencowJsonPath)) {
1059
1100
  try {
1060
1101
  const gencowJson = JSON.parse(readFileSync(gencowJsonPath, "utf8"));
1061
1102
  appId = gencowJson.appId || gencowJson.appName;
1103
+ seedPlatformUrl = gencowJson.platformUrl || null;
1062
1104
  } catch { /* ignore parse error */ }
1063
1105
  }
1064
1106
 
@@ -1073,11 +1115,12 @@ ${hasPrompt ? `
1073
1115
  log(`\n${BOLD}${CYAN}Gencow DB Seed${RESET} ${DIM}(cloud: ${appId})${RESET}\n`);
1074
1116
  info("Running seed.ts on cloud app...");
1075
1117
 
1076
- const cloudUrl = `https://${appId}.gencow.app/_admin/seed`;
1118
+ const seedAppBaseUrl = getAppUrl(appId, seedPlatformUrl || "https://gencow.app");
1119
+ const cloudUrl = `${seedAppBaseUrl}/_admin/seed`;
1077
1120
 
1078
1121
  try {
1079
1122
  // 앱이 실행 중인지 확인 (status 체크)
1080
- const statusRes = await fetch(`https://${appId}.gencow.app/_admin/status`, {
1123
+ const statusRes = await fetch(`${seedAppBaseUrl}/_admin/status`, {
1081
1124
  signal: AbortSignal.timeout(5000),
1082
1125
  }).catch(() => null);
1083
1126
 
@@ -1110,7 +1153,7 @@ ${hasPrompt ? `
1110
1153
  }
1111
1154
  } catch (e) {
1112
1155
  error(`Cloud app connection failed: ${e.message}`);
1113
- info(`${DIM}앱이 실행 중인지 확인하세요: https://${appId}.gencow.app${RESET}`);
1156
+ info(`${DIM}앱이 실행 중인지 확인하세요: ${seedAppBaseUrl}${RESET}`);
1114
1157
  }
1115
1158
  log("");
1116
1159
  },
@@ -2115,6 +2158,18 @@ ${BOLD}Examples:${RESET}
2115
2158
  const bundleSize = statSync(tmpBundle).size;
2116
2159
  success(`번들 생성: ${(bundleSize / 1024).toFixed(1)} KB`);
2117
2160
 
2161
+ // 2-0. 번들 크기 경고 (Ph2: 리소스 미터링 — 차단 아님, 안내만)
2162
+ try {
2163
+ const { auditBundleSize, formatBundleSizeWarning } = await import("../lib/deploy-auditor.mjs");
2164
+ const sizeResult = auditBundleSize(bundleSize);
2165
+ const sizeMsg = formatBundleSizeWarning(sizeResult);
2166
+ if (sizeMsg) {
2167
+ log(`${YELLOW}${sizeMsg}${RESET}`);
2168
+ }
2169
+ } catch {
2170
+ // 번들 크기 검사 실패 시 무시 (배포 계속)
2171
+ }
2172
+
2118
2173
  // 2. 앱이 없으면 생성 (appId가 없는 경우)
2119
2174
  if (!appId) {
2120
2175
  info("앱 생성 중 (ID 자동 생성)...");
@@ -2464,7 +2519,7 @@ ${BOLD}Examples:${RESET}
2464
2519
  if (apiRefFiles.length > 5) log(` ${DIM}... 외 ${apiRefFiles.length - 5}개${RESET}`);
2465
2520
  log("");
2466
2521
  warn(`정적 호스팅에는 API 서버가 없어 404가 발생합니다.`);
2467
- info(`💡 백엔드가 필요하다면: ${CYAN}VITE_API_URL=https://{backend}.gencow.app npm run build${RESET} 후 배포`);
2522
+ info(`💡 백엔드가 필요하다면: ${CYAN}VITE_API_URL=https://{backend}.{도메인} npm run build${RESET} 후 배포`);
2468
2523
  info(`💡 별도 백엔드를 사용한다면: ${CYAN}VITE_API_URL${RESET} 환경변수로 빌드 대상을 지정하세요.`);
2469
2524
  log("");
2470
2525
 
@@ -3937,7 +3992,7 @@ process.exit(0);
3937
3992
  info(`${DIM}gencow.json 생성됨 (appId: ${appName})${RESET}`);
3938
3993
 
3939
3994
  // .env에 VITE_API_URL 자동 설정
3940
- updateEnvLocalUrl(`https://${appName}.gencow.app`);
3995
+ updateEnvLocalUrl(getAppUrl(appName, creds.platformUrl));
3941
3996
 
3942
3997
  // 프로비저닝 대기
3943
3998
  info("프로비저닝 대기 중...");
@@ -3951,7 +4006,7 @@ process.exit(0);
3951
4006
  process.exit(1);
3952
4007
  }
3953
4008
 
3954
- const appUrl = `https://${appName}.gencow.app`;
4009
+ const appUrl = getAppUrl(appName, creds.platformUrl);
3955
4010
 
3956
4011
  // ── 배포 함수 (번들 크기 + 소요 시간 표시) ────────────
3957
4012
  async function deploy(reason = "initial") {
@@ -4073,7 +4128,7 @@ process.exit(0);
4073
4128
 
4074
4129
  async function connectLogStream() {
4075
4130
  const { WebSocket: WS } = await import("ws");
4076
- const wsUrl = `wss://${appName}.gencow.app/ws`;
4131
+ const wsUrl = getWsUrl(appName, creds.platformUrl);
4077
4132
 
4078
4133
  try {
4079
4134
  logWs = new WS(wsUrl);
@@ -4150,7 +4205,7 @@ ${BOLD}${CYAN}🚀 Gencow Cloud Dev${RESET}
4150
4205
 
4151
4206
  ${GREEN}▸${RESET} App: ${BOLD}${appName}${RESET}
4152
4207
  ${GREEN}▸${RESET} URL: ${DIM}${appUrl}${RESET}
4153
- ${GREEN}▸${RESET} Dashboard: ${DIM}https://gencow.app/_cloud/apps/${appName}${RESET}
4208
+ ${GREEN}▸${RESET} Dashboard: ${DIM}${getDashboardUrl(appName, creds.platformUrl)}${RESET}
4154
4209
  ${GREEN}▸${RESET} Watching: ${DIM}${functionsDir}/ (${watchedFiles.join(", ") || "*.ts"})${RESET}
4155
4210
  ${GREEN}▸${RESET} Mode: ${DIM}Cloud (PostgreSQL)${RESET}
4156
4211