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 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(process.env.NEXT_PUBLIC_GENCOW_URL || "http://localhost:5456");\n`;
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.dev\`에서 접근 가능\n\n`;
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 = "NEXT_PUBLIC_GENCOW_URL";
603
- const envLocalPath = resolve(process.cwd(), ".env.local");
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(envLocalPath, "utf-8"); } catch { /* doesn't exist yet */ }
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
- if (regex.test(content)) {
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(envLocalPath, content);
619
- success(`.env.local에 ${envKey} 자동 설정됨`);
620
- info(` ${DIM}Vercel/Netlify 배포 환경변수에도 추가하세요:${RESET}`);
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.local 업데이트 실패: ${e.message}`);
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 OS for AI Agents${RESET}
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 Deploy to production (confirmation required)${RESET}
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.dev";
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.local — GENCOW_URL for frontend
2117
- const envLocalPath = resolve(cwd, ".env.local");
2118
- const envLine = `NEXT_PUBLIC_GENCOW_URL=${data.url}`;
2119
- if (existsSync(envLocalPath)) {
2120
- let envContent = readFileSync(envLocalPath, "utf8");
2121
- if (!envContent.includes("NEXT_PUBLIC_GENCOW_URL")) {
2122
- writeFileSync(envLocalPath, envContent.trimEnd() + "\n" + envLine + "\n");
2123
- success(`.env.local — added NEXT_PUBLIC_GENCOW_URL`);
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(envLocalPath, `# Gencow app backend URL\n${envLine}\n`);
2127
- success(`.env.local — created`);
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.local: NEXT_PUBLIC_GENCOW_URL set
2285
+ ${GREEN}▸${RESET} .env: VITE_API_URL set
2156
2286
 
2157
2287
  ${DIM}pnpm gencow dev:remote — watch & auto-deploy${RESET}
2158
2288
  `);