gencow 0.1.111 → 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 +72 -17
- package/dashboard/assets/{index-C26b7MIs.js → index-DDiSe7od.js} +67 -67
- package/dashboard/index.html +1 -1
- package/lib/__tests__/deploy-auditor.test.ts +91 -0
- package/lib/__tests__/readme-codegen.test.ts +3 -3
- package/lib/deploy-auditor.mjs +69 -0
- package/lib/readme-codegen.mjs +26 -23
- package/package.json +1 -1
- package/scripts/bundle-server.mjs +1 -1
- package/server/index.js +13217 -42551
- package/server/index.js.map +4 -4
- package/templates/ai-chat/README.md +7 -6
- package/templates/ai-chat/ai.ts +72 -14
- package/templates/ai-chat/prompt.md +13 -12
- package/templates/ai.ts +72 -14
- package/templates/default/README.md +10 -6
- package/templates/fullstack/README.md +6 -6
- package/templates/fullstack/ai.ts +72 -14
- package/templates/fullstack/prompt.md +15 -15
- package/templates/task-app/README.md +6 -6
- package/templates/task-app/prompt.md +15 -15
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
|
-
|
|
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
|
|
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(
|
|
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}앱이 실행 중인지 확인하세요:
|
|
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}.
|
|
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(
|
|
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 =
|
|
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 =
|
|
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}
|
|
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
|
|