gencow 0.1.39 → 0.1.41
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 +154 -24
- package/dashboard/assets/{index-Trhzwpt4.js → index-CNnjRL7-.js} +92 -102
- package/dashboard/assets/index-DSyIlR55.css +1 -0
- package/dashboard/index.html +7 -3
- package/package.json +29 -28
- package/server/index.js +136 -41
- package/server/index.js.map +3 -3
- package/dashboard/assets/index-SdVdRCfg.css +0 -1
package/bin/gencow.mjs
CHANGED
|
@@ -457,7 +457,7 @@ function generateReadmeMd(config, apiObj) {
|
|
|
457
457
|
md += `\`\`\`\n\n`;
|
|
458
458
|
md += `또는 \`gencowAuth()\`에 명시적으로 baseUrl을 전달하세요:\n\n`;
|
|
459
459
|
md += `\`\`\`typescript\n`;
|
|
460
|
-
md += `const { signIn, useAuth } = gencowAuth(
|
|
460
|
+
md += `const { signIn, useAuth } = gencowAuth(import.meta.env.VITE_API_URL || "http://localhost:5456");\n`;
|
|
461
461
|
md += `\`\`\`\n\n`;
|
|
462
462
|
|
|
463
463
|
// ── 1-3. RPC 직접 호출 (접힘 — 비-React 환경용) ──────────
|
|
@@ -561,7 +561,7 @@ function generateReadmeMd(config, apiObj) {
|
|
|
561
561
|
md += `npx gencow env set DATABASE_URL=postgres://...\n`;
|
|
562
562
|
md += `npx gencow env list\n`;
|
|
563
563
|
md += `\`\`\`\n\n`;
|
|
564
|
-
md += `> 배포 후 앱은 \`https://{앱이름}.gencow.
|
|
564
|
+
md += `> 배포 후 앱은 \`https://{앱이름}.gencow.app\`에서 접근 가능\n\n`;
|
|
565
565
|
md += `### 환경변수 관리\n\n`;
|
|
566
566
|
md += `| 명령어 | 설명 |\n`;
|
|
567
567
|
md += `| :--- | :--- |\n`;
|
|
@@ -599,28 +599,32 @@ function generateReadmeMd(config, apiObj) {
|
|
|
599
599
|
// ─── Deploy → .env.local 자동 업데이트 ────────────────────
|
|
600
600
|
function updateEnvLocalUrl(deployUrl) {
|
|
601
601
|
if (!deployUrl) return;
|
|
602
|
-
const envKey = "
|
|
603
|
-
const
|
|
602
|
+
const envKey = "VITE_API_URL";
|
|
603
|
+
const envPath = resolve(process.cwd(), ".env");
|
|
604
604
|
|
|
605
605
|
try {
|
|
606
606
|
let content = "";
|
|
607
|
-
try { content = readFileSync(
|
|
607
|
+
try { content = readFileSync(envPath, "utf-8"); } catch { /* doesn't exist yet */ }
|
|
608
608
|
|
|
609
609
|
const line = `${envKey}=${deployUrl}`;
|
|
610
610
|
const regex = new RegExp(`^${envKey}=.*$`, "m");
|
|
611
611
|
|
|
612
|
-
|
|
612
|
+
// 기존 NEXT_PUBLIC_GENCOW_URL 도 함께 치환 (레거시 호환)
|
|
613
|
+
const legacyRegex = /^NEXT_PUBLIC_GENCOW_URL=.*$/m;
|
|
614
|
+
if (legacyRegex.test(content)) {
|
|
615
|
+
content = content.replace(legacyRegex, line);
|
|
616
|
+
} else if (regex.test(content)) {
|
|
613
617
|
content = content.replace(regex, line);
|
|
614
618
|
} else {
|
|
615
619
|
content = content.trimEnd() + (content ? "\n" : "") + line + "\n";
|
|
616
620
|
}
|
|
617
621
|
|
|
618
|
-
writeFileSync(
|
|
619
|
-
success(`.env
|
|
620
|
-
info(` ${DIM}Vercel
|
|
622
|
+
writeFileSync(envPath, content);
|
|
623
|
+
success(`.env에 ${envKey} 자동 설정됨`);
|
|
624
|
+
info(` ${DIM}배포 플랫폼(Vercel 등) 환경변수에도 추가하세요:${RESET}`);
|
|
621
625
|
info(` ${envKey}=${deployUrl}`);
|
|
622
626
|
} catch (e) {
|
|
623
|
-
warn(`.env
|
|
627
|
+
warn(`.env 업데이트 실패: ${e.message}`);
|
|
624
628
|
}
|
|
625
629
|
}
|
|
626
630
|
|
|
@@ -1306,7 +1310,7 @@ ${hasPrompt ? `
|
|
|
1306
1310
|
// ── help ─────────────────────────────────────────────
|
|
1307
1311
|
help() {
|
|
1308
1312
|
log(`
|
|
1309
|
-
${BOLD}${CYAN}Gencow CLI${RESET} ${DIM}— Backend
|
|
1313
|
+
${BOLD}${CYAN}Gencow CLI${RESET} ${DIM}— AI Backend Engine${RESET}
|
|
1310
1314
|
|
|
1311
1315
|
${BOLD}Usage:${RESET}
|
|
1312
1316
|
gencow <command>
|
|
@@ -1331,8 +1335,9 @@ ${BOLD}BaaS commands (login required):${RESET}
|
|
|
1331
1335
|
${GREEN}logout${RESET} Clear credentials
|
|
1332
1336
|
${GREEN}whoami${RESET} Show current user info
|
|
1333
1337
|
${GREEN}deploy${RESET} Bundle gencow/ and deploy to platform
|
|
1334
|
-
${DIM}--prod
|
|
1338
|
+
${DIM}--prod Deploy to production (confirmation required)${RESET}
|
|
1335
1339
|
${DIM}--name, -n Specify app name${RESET}
|
|
1340
|
+
${DIM}--static [dir] Deploy static files (dist/, out/, build/)${RESET}
|
|
1336
1341
|
${GREEN}env list${RESET} List remote env vars ${DIM}(--prod for production)${RESET}
|
|
1337
1342
|
${GREEN}env set K=V${RESET} Set remote env var
|
|
1338
1343
|
${GREEN}env unset KEY${RESET} Remove remote env var
|
|
@@ -1357,7 +1362,7 @@ ${BOLD}Examples:${RESET}
|
|
|
1357
1362
|
async login() {
|
|
1358
1363
|
log(`\n${BOLD}${CYAN}Gencow Login${RESET}\n`);
|
|
1359
1364
|
|
|
1360
|
-
const platformUrl = process.env.GENCOW_PLATFORM_URL || "https://gencow.
|
|
1365
|
+
const platformUrl = process.env.GENCOW_PLATFORM_URL || "https://gencow.app";
|
|
1361
1366
|
info(`Platform: ${platformUrl}`);
|
|
1362
1367
|
|
|
1363
1368
|
// 1. auth-start → 인증 코드 생성
|
|
@@ -1502,11 +1507,22 @@ ${BOLD}Examples:${RESET}
|
|
|
1502
1507
|
let appId = null;
|
|
1503
1508
|
let displayName = null;
|
|
1504
1509
|
let envTarget = "dev";
|
|
1510
|
+
let staticDir = null; // --static 옵션
|
|
1511
|
+
let isStatic = false;
|
|
1505
1512
|
|
|
1506
1513
|
for (let i = 0; i < deployArgs.length; i++) {
|
|
1507
1514
|
const a = deployArgs[i];
|
|
1508
1515
|
if (a === "--prod") envTarget = "prod";
|
|
1509
1516
|
else if (a === "--app" || a === "-a") appId = deployArgs[++i];
|
|
1517
|
+
else if (a === "--static") {
|
|
1518
|
+
isStatic = true;
|
|
1519
|
+
// 다음 인자가 옵션이 아니면 디렉토리로 사용
|
|
1520
|
+
const next = deployArgs[i + 1];
|
|
1521
|
+
if (next && !next.startsWith("-")) {
|
|
1522
|
+
staticDir = next;
|
|
1523
|
+
i++;
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1510
1526
|
}
|
|
1511
1527
|
|
|
1512
1528
|
// gencow.json에서 appId 로드
|
|
@@ -1525,6 +1541,11 @@ ${BOLD}Examples:${RESET}
|
|
|
1525
1541
|
}
|
|
1526
1542
|
if (!displayName) displayName = basename(process.cwd());
|
|
1527
1543
|
|
|
1544
|
+
// ── Static Deploy 분기 ────────────────────────────────
|
|
1545
|
+
if (isStatic) {
|
|
1546
|
+
return await this._deployStatic(creds, appId, displayName, staticDir, gencowJsonPath);
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1528
1549
|
log(`\n${BOLD}${CYAN}Gencow Deploy${RESET}\n`);
|
|
1529
1550
|
if (appId) {
|
|
1530
1551
|
info(`앱 ID: ${appId}`);
|
|
@@ -1709,6 +1730,115 @@ ${BOLD}Examples:${RESET}
|
|
|
1709
1730
|
}
|
|
1710
1731
|
},
|
|
1711
1732
|
|
|
1733
|
+
// ── deploy --static 내부 헬퍼 ─────────────────────────
|
|
1734
|
+
async _deployStatic(creds, appId, displayName, staticDirArg, gencowJsonPath) {
|
|
1735
|
+
// 정적 폴더 자동 감지
|
|
1736
|
+
const AUTO_DETECT = ["dist", "out", "build", ".next/out"];
|
|
1737
|
+
let targetDir = staticDirArg;
|
|
1738
|
+
|
|
1739
|
+
if (!targetDir) {
|
|
1740
|
+
for (const candidate of AUTO_DETECT) {
|
|
1741
|
+
const p = resolve(process.cwd(), candidate);
|
|
1742
|
+
if (existsSync(p)) {
|
|
1743
|
+
targetDir = candidate;
|
|
1744
|
+
break;
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
if (!targetDir || !existsSync(resolve(process.cwd(), targetDir))) {
|
|
1750
|
+
error(`정적 파일 폴더를 찾을 수 없습니다.`);
|
|
1751
|
+
info(`자동 감지 순서: ${AUTO_DETECT.join(", ")}`);
|
|
1752
|
+
info(`수동 지정: gencow deploy --static <dir>`);
|
|
1753
|
+
process.exit(1);
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
log(`\n${BOLD}${CYAN}Gencow Static Deploy${RESET}\n`);
|
|
1757
|
+
info(`폴더: ${targetDir}/`);
|
|
1758
|
+
if (appId) {
|
|
1759
|
+
info(`앱 ID: ${appId}`);
|
|
1760
|
+
} else {
|
|
1761
|
+
info(`앱: 신규 (ID 자동 생성)`);
|
|
1762
|
+
}
|
|
1763
|
+
log("");
|
|
1764
|
+
|
|
1765
|
+
// 1. tar.gz 패키징
|
|
1766
|
+
info("정적 파일 패키징 중...");
|
|
1767
|
+
const { execSync: exec } = await import("child_process");
|
|
1768
|
+
const tmpBundle = resolve(process.cwd(), ".gencow", "static-bundle.tar.gz");
|
|
1769
|
+
mkdirSync(dirname(tmpBundle), { recursive: true });
|
|
1770
|
+
|
|
1771
|
+
try {
|
|
1772
|
+
exec(`tar -czf "${tmpBundle}" -C "${resolve(process.cwd(), targetDir)}" .`, { cwd: process.cwd() });
|
|
1773
|
+
} catch (e) {
|
|
1774
|
+
error(`패키징 실패: ${e.message}`);
|
|
1775
|
+
process.exit(1);
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
const bundleSize = statSync(tmpBundle).size;
|
|
1779
|
+
const MAX_SIZE = 100 * 1024 * 1024;
|
|
1780
|
+
if (bundleSize > MAX_SIZE) {
|
|
1781
|
+
error(`번들이 너무 큽니다: ${(bundleSize / (1024 * 1024)).toFixed(1)} MB (최대 100 MB)`);
|
|
1782
|
+
try { unlinkSync(tmpBundle); } catch { }
|
|
1783
|
+
process.exit(1);
|
|
1784
|
+
}
|
|
1785
|
+
success(`번들 생성: ${(bundleSize / 1024).toFixed(1)} KB`);
|
|
1786
|
+
|
|
1787
|
+
// 2. 앱이 없으면 생성
|
|
1788
|
+
if (!appId) {
|
|
1789
|
+
info("앱 생성 중 (ID 자동 생성)...");
|
|
1790
|
+
const createRes = await rpcMutation(creds, "apps.create", { name: displayName });
|
|
1791
|
+
if (!createRes.ok) {
|
|
1792
|
+
const createErr = await createRes.json().catch(() => ({}));
|
|
1793
|
+
error(`앱 생성 실패: ${createErr.error || createRes.statusText}`);
|
|
1794
|
+
process.exit(1);
|
|
1795
|
+
}
|
|
1796
|
+
const createData = await createRes.json();
|
|
1797
|
+
appId = createData.appId || createData.name;
|
|
1798
|
+
success(`앱 "${appId}" 생성 완료. 프로비저닝 대기 중...`);
|
|
1799
|
+
writeFileSync(gencowJsonPath, JSON.stringify({
|
|
1800
|
+
appId, displayName, platformUrl: creds.platformUrl,
|
|
1801
|
+
}, null, 2));
|
|
1802
|
+
info(`${DIM}gencow.json 생성됨 (appId: ${appId})${RESET}`);
|
|
1803
|
+
await new Promise(r => setTimeout(r, 3000));
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
// 3. 업로드
|
|
1807
|
+
info("정적 파일 배포 중...");
|
|
1808
|
+
const bundleBuffer = readFileSync(tmpBundle);
|
|
1809
|
+
|
|
1810
|
+
const deployRes = await platformFetch(creds, `/platform/apps/${appId}/deploy-static`, {
|
|
1811
|
+
method: "POST",
|
|
1812
|
+
headers: { "Content-Type": "application/octet-stream" },
|
|
1813
|
+
body: bundleBuffer,
|
|
1814
|
+
});
|
|
1815
|
+
|
|
1816
|
+
try { unlinkSync(tmpBundle); } catch { }
|
|
1817
|
+
|
|
1818
|
+
if (!deployRes.ok) {
|
|
1819
|
+
const errData = await deployRes.json().catch(() => ({}));
|
|
1820
|
+
error(`정적 배포 실패: ${errData.error || deployRes.statusText}`);
|
|
1821
|
+
process.exit(1);
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
const data = await deployRes.json();
|
|
1825
|
+
log("");
|
|
1826
|
+
success(`정적 배포 완료!`);
|
|
1827
|
+
info(`앱 ID: ${appId}`);
|
|
1828
|
+
info(`URL: ${data.url}`);
|
|
1829
|
+
info(`파일: ${data.files}개 (${(data.size / 1024).toFixed(1)} KB)`);
|
|
1830
|
+
info(`Hash: ${data.bundleHash}`);
|
|
1831
|
+
if (data.deployId) info(`ID: ${data.deployId}`);
|
|
1832
|
+
updateEnvLocalUrl(data.url);
|
|
1833
|
+
log("");
|
|
1834
|
+
|
|
1835
|
+
if (!existsSync(gencowJsonPath)) {
|
|
1836
|
+
writeFileSync(gencowJsonPath, JSON.stringify({
|
|
1837
|
+
appId, displayName, platformUrl: creds.platformUrl,
|
|
1838
|
+
}, null, 2));
|
|
1839
|
+
}
|
|
1840
|
+
},
|
|
1841
|
+
|
|
1712
1842
|
// ── env ───────────────────────────────────────────────
|
|
1713
1843
|
async env(...envArgs) {
|
|
1714
1844
|
const creds = requireCreds();
|
|
@@ -2113,18 +2243,18 @@ process.exit(0);
|
|
|
2113
2243
|
const cwd = process.cwd();
|
|
2114
2244
|
const config = loadConfig();
|
|
2115
2245
|
|
|
2116
|
-
// 1. .env
|
|
2117
|
-
const
|
|
2118
|
-
const envLine = `
|
|
2119
|
-
if (existsSync(
|
|
2120
|
-
let envContent = readFileSync(
|
|
2121
|
-
if (!envContent.includes("
|
|
2122
|
-
writeFileSync(
|
|
2123
|
-
success(`.env
|
|
2246
|
+
// 1. .env — GENCOW_URL for frontend
|
|
2247
|
+
const envFilePath = resolve(cwd, ".env");
|
|
2248
|
+
const envLine = `VITE_API_URL=${data.url}`;
|
|
2249
|
+
if (existsSync(envFilePath)) {
|
|
2250
|
+
let envContent = readFileSync(envFilePath, "utf8");
|
|
2251
|
+
if (!envContent.includes("VITE_API_URL")) {
|
|
2252
|
+
writeFileSync(envFilePath, envContent.trimEnd() + "\n" + envLine + "\n");
|
|
2253
|
+
success(`.env — added VITE_API_URL`);
|
|
2124
2254
|
}
|
|
2125
2255
|
} else {
|
|
2126
|
-
writeFileSync(
|
|
2127
|
-
success(`.env
|
|
2256
|
+
writeFileSync(envFilePath, `# Gencow app backend URL\n${envLine}\n`);
|
|
2257
|
+
success(`.env — created`);
|
|
2128
2258
|
}
|
|
2129
2259
|
|
|
2130
2260
|
// 2. gencow/ starter — create index.ts if folder is empty/missing
|
|
@@ -2152,7 +2282,7 @@ process.exit(0);
|
|
|
2152
2282
|
${BOLD}App ready:${RESET}
|
|
2153
2283
|
${GREEN}▸${RESET} API: ${data.url}
|
|
2154
2284
|
${GREEN}▸${RESET} Dashboard: ${data.dashboard}
|
|
2155
|
-
${GREEN}▸${RESET} .env
|
|
2285
|
+
${GREEN}▸${RESET} .env: VITE_API_URL set
|
|
2156
2286
|
|
|
2157
2287
|
${DIM}pnpm gencow dev:remote — watch & auto-deploy${RESET}
|
|
2158
2288
|
`);
|