gencow 0.1.67 β†’ 0.1.69

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
@@ -523,8 +523,9 @@ function generateReadmeMd(config, apiObj) {
523
523
  md += `<summary>πŸ“‘ RPC 직접 호좜 (Node.js / cURL / λΉ„-React ν™˜κ²½)</summary>\n\n`;
524
524
  md += `> ⚠️ Reactμ—μ„œλŠ” 이 방법을 μ‚¬μš©ν•˜μ§€ λ§ˆμ„Έμš”. μœ„μ˜ \`useQuery\`/\`useMutation\`을 μ‚¬μš©ν•˜μ„Έμš”.\n\n`;
525
525
  md += `\`\`\`typescript\n`;
526
- md += `// Query 호좜\n`;
527
- md += `const res = await fetch("http://localhost:5456/api/query", {\n`;
526
+ md += `// Query 호좜 (self-fetch: μ„œλ²„ λ‚΄λΆ€μ—μ„œ λ‹€λ₯Έ ν•¨μˆ˜ 호좜 μ‹œ)\n`;
527
+ md += `const baseUrl = process.env.GENCOW_INTERNAL_URL || "http://localhost:5456";\n`;
528
+ md += `const res = await fetch(\`\${baseUrl}/api/query\`, {\n`;
528
529
  md += ` method: "POST",\n`;
529
530
  md += ` headers: { "Content-Type": "application/json" },\n`;
530
531
  md += ` credentials: "include",\n`;
@@ -537,7 +538,7 @@ function generateReadmeMd(config, apiObj) {
537
538
  if (Object.values(apiObj)[0]?.mutations?.length > 0) {
538
539
  const firstMut = Object.values(apiObj)[0].mutations[0];
539
540
  md += `// Mutation 호좜\n`;
540
- md += `const res = await fetch("http://localhost:5456/api/mutation", {\n`;
541
+ md += `const res = await fetch(\`\${baseUrl}/api/mutation\`, {\n`;
541
542
  md += ` method: "POST",\n`;
542
543
  md += ` headers: { "Content-Type": "application/json" },\n`;
543
544
  md += ` credentials: "include",\n`;
@@ -566,6 +567,13 @@ function generateReadmeMd(config, apiObj) {
566
567
  md += `- λ°˜λ“œμ‹œ useQuery와 useMutation을 μ‚¬μš©ν•΄μ„œ 데이터와 μ—°κ²°ν•΄μ€˜.\n`;
567
568
  md += `- fetch()λ₯Ό 직접 ν˜ΈμΆœν•˜κ±°λ‚˜ apiPost() 같은 래퍼λ₯Ό λ§Œλ“€μ§€ 마.\n`;
568
569
  md += `- gencow/api.tsλŠ” μžλ™ μƒμ„±λœ νŒŒμΌμ΄μ•Ό. μˆ˜λ™μœΌλ‘œ λ§Œλ“€μ§€ 마.\n`;
570
+ md += `- gencow/index.ts의 re-exportλŠ” export * as moduleName from "./moduleName" νŒ¨ν„΄μ„ 써.\n`;
571
+ md += ` Module, Mod 같은 접미사λ₯Ό 뢙이지 마.\n`;
572
+ md += `\n`;
573
+ md += `⚠️ mutation μ œν•œ:\n`;
574
+ md += `- mutation은 10초 이내에 μ™„λ£Œλ˜μ–΄μ•Ό ν•΄. μ™ΈλΆ€ APIλ‚˜ LLM 호좜이 κΈΈλ©΄ λ‹¨κ³„λ³„λ‘œ 뢄리해.\n`;
575
+ md += `- κΈ΄ μž‘μ—…μ€ ctx.scheduler.runAfter(0, "module.nextStep", { sessionId }) 둜 λ‹€μŒ 단계λ₯Ό μ˜ˆμ•½.\n`;
576
+ md += `- 예: 크둀링(Step1) β†’ 필터링(Step2) β†’ μš”μ•½(Step3) 각각 별도 mutation으둜 뢄리.\n`;
569
577
  md += `\n`;
570
578
  md += `크둠 작 (μ˜ˆμ•½ μž‘μ—…):\n`;
571
579
  md += ` gencow/crons.tsμ—μ„œ cronJobs()둜 μ„ μ–Έ\n`;
@@ -578,7 +586,11 @@ function generateReadmeMd(config, apiObj) {
578
586
  md += `μœ„ APIλ₯Ό 기반으둜 Next.js + Tailwind CSS UI μ»΄ν¬λ„ŒνŠΈλ₯Ό λ§Œλ“€μ–΄μ€˜.\n`;
579
587
  md += `- TypeScript νƒ€μž…μ„ μ΅œλŒ€ν•œ ν™œμš©ν•˜κ³ , λ‘œλ”©/μ—λŸ¬ μƒνƒœλ„ μ²˜λ¦¬ν•΄μ€˜.\n`;
580
588
  md += `- ctx.ai.chat()을 μ‚¬μš©ν•΄μ„œ AIλ₯Ό ν˜ΈμΆœν•˜κ³ , OpenAI SDKλ₯Ό 직접 μ„€μΉ˜ν•˜μ§€ 마.\n`;
581
- md += `- λ°°ν¬λŠ” \`npx gencow deploy\`둜 ν•΄.\n`;
589
+ md += `\n`;
590
+ md += `배포 κ·œμΉ™:\n`;
591
+ md += `- λ°±μ—”λ“œ: \`npx gencow deploy\` (gencow/ ν΄λ”λ§Œ 배포됨. ν”„λ‘ νŠΈμ—”λ“œλŠ” 포함 μ•ˆ 됨)\n`;
592
+ md += `- ν”„λ‘ νŠΈμ—”λ“œ: VITE_API_URL=https://{μ•±ID}.gencow.app npm run build ν›„ \`npx gencow deploy --static dist/\`\n`;
593
+ md += `- gencow deployλŠ” ν”„λ‘ νŠΈμ—”λ“œλ₯Ό ν¬ν•¨ν•˜μ§€ μ•Šμ•„. λ°˜λ“œμ‹œ λ³„λ„λ‘œ --static 배포해.\n`;
582
594
  md += `- ν™˜κ²½λ³€μˆ˜λŠ” \`npx gencow env set KEY=VALUE\`둜 관리해.\n`;
583
595
  md += `- .env νŒŒμΌμ€ 둜컬 개발 μ „μš©μ΄μ•Ό. μ„œλ²„μ—λŠ” gencow env push둜 올렀.\n`;
584
596
  md += `\`\`\`\n\n`;
@@ -654,13 +666,45 @@ function generateReadmeMd(config, apiObj) {
654
666
  md += `\`\`\`\n\n`;
655
667
  md += `> ⚠️ \`export default crons\`κ°€ μ—†μœΌλ©΄ μ„œλ²„κ°€ cron을 μΈμ‹ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.\n`;
656
668
  md += `> ⚠️ action λ¬Έμžμ—΄μ€ κΈ°μ‘΄ mutation 이름과 μ •ν™•νžˆ λ§€μΉ­λ˜μ–΄μ•Ό ν•©λ‹ˆλ‹€.\n\n`;
669
+ md += `> πŸ’‘ cron ν•Έλ“€λŸ¬μ—μ„œ mutation을 ν˜ΈμΆœν•˜λ €λ©΄ self-fetchλ₯Ό μ‚¬μš©ν•˜μ„Έμš”:\n`;
670
+ md += `> \`\`\`typescript\n`;
671
+ md += `> const baseUrl = process.env.GENCOW_INTERNAL_URL || "http://localhost:5456";\n`;
672
+ md += `> await fetch(\`\${baseUrl}/api/mutation\`, { method: "POST", ... });\n`;
673
+ md += `> \`\`\`\n\n`;
674
+
675
+ // ── 7. 배포 ──────────────────────────────────────────────
676
+ md += `---\n\n## πŸš€ 배포\n\n`;
677
+ md += `### λ°±μ—”λ“œ API 배포\n`;
678
+ md += `\`\`\`bash\n`;
679
+ md += `gencow deploy # gencow/ 폴더 β†’ ν΄λΌμš°λ“œ μ„œλ²„ 배포\n`;
680
+ md += `\`\`\`\n\n`;
681
+ md += `### ν”„λ‘ νŠΈμ—”λ“œ 배포 (frontend/ μžˆλŠ” 경우)\n`;
682
+ md += `\`\`\`bash\n`;
683
+ md += `# 1. λ°±μ—”λ“œ URL을 ν™˜κ²½λ³€μˆ˜λ‘œ λΉŒλ“œ\n`;
684
+ md += `cd frontend\n`;
685
+ md += `VITE_API_URL=https://{μ•±ID}.gencow.app npm run build\n\n`;
686
+ md += `# 2. λΉŒλ“œ κ²°κ³Όλ¬Ό 정적 배포\n`;
687
+ md += `gencow deploy --static dist/\n`;
688
+ md += `\`\`\`\n\n`;
689
+ md += `### 정적 μ‚¬μ΄νŠΈ μ „μš© (API μ—†λŠ” 경우)\n`;
690
+ md += `\`\`\`bash\n`;
691
+ md += `gencow deploy --static dist/ # 순수 HTML/CSS/JS만 배포\n`;
692
+ md += `\`\`\`\n\n`;
693
+ md += `> ⚠️ ν”„λ‘ νŠΈμ—”λ“œμ—μ„œ APIλ₯Ό ν˜ΈμΆœν•˜λ €λ©΄ λΉŒλ“œ μ‹œ \`VITE_API_URL\`을 λ°˜λ“œμ‹œ μ„€μ •ν•˜μ„Έμš”.\n\n`;
694
+ md += `### CORS μ„€μ •\n\n`;
695
+ md += `- \`*.gencow.app\` μ„œλΈŒλ„λ©”μΈ κ°„ μš”μ²­μ€ **μžλ™ ν—ˆμš©**λ©λ‹ˆλ‹€.\n`;
696
+ md += `- μ»€μŠ€ν…€ λ„λ©”μΈμ—μ„œ APIλ₯Ό ν˜ΈμΆœν•˜λ €λ©΄ ν™˜κ²½λ³€μˆ˜λ₯Ό μ„€μ •ν•˜μ„Έμš”:\n\n`;
697
+ md += `\`\`\`bash\n`;
698
+ md += `gencow env set CORS_ORIGINS=https://myapp.com,https://www.myapp.com\n`;
699
+ md += `\`\`\`\n\n`;
657
700
 
658
- // ── 7. Dev Tips ────────────────────────────────────────
701
+ // ── 8. Dev Tips ────────────────────────────────────────
659
702
  md += `---\n\n## πŸ’‘ 개발 팁\n\n`;
660
703
  md += `- \`gencow/\` 폴더 λ‚΄ νŒŒμΌμ„ μˆ˜μ •ν•˜λ©΄ \`api.ts\`와 이 READMEκ°€ **μžλ™μœΌλ‘œ μž¬μƒμ„±**λ©λ‹ˆλ‹€.\n`;
661
704
  md += `- μŠ€ν‚€λ§ˆ λ³€κ²½ ν›„ \`gencow db:push\`λ₯Ό μ‹€ν–‰ν•˜λ©΄ DBκ°€ μ¦‰μ‹œ λ™κΈ°ν™”λ©λ‹ˆλ‹€.\n`;
662
705
  md += `- MCP μ„œλ²„λ₯Ό μ‚¬μš©ν•˜λ©΄ AIκ°€ 이 ꡬ쑰λ₯Ό μžλ™μœΌλ‘œ μΈμ‹ν•©λ‹ˆλ‹€.\n`;
663
706
  md += `- 둜컬 개발: \`gencow dev\` β†’ \`http://localhost:5456\`\n`;
707
+ md += `- Self-fetch: \`process.env.GENCOW_INTERNAL_URL\` β€” cron/mutationμ—μ„œ λ‹€λ₯Έ ν•¨μˆ˜ 호좜 μ‹œ μ‚¬μš©\n`;
664
708
  md += `- .env νŒŒμΌμ€ 둜컬 μ „μš©. μ„œλ²„μ—λŠ” \`gencow env push\`둜 μ˜¬λ¦¬μ„Έμš”.\n`;
665
709
 
666
710
  // ── 5. κΈ°μ‘΄ μ»΄ν¬λ„ŒνŠΈ μ„Ήμ…˜ 보쑴 (gencow add둜 μΆ”κ°€λœ λΆ€λΆ„) ──
@@ -919,6 +963,39 @@ const commands = {
919
963
  success("Created gencow/SECURITY.md (λ³΄μ•ˆ κ°€μ΄λ“œ)");
920
964
  }
921
965
 
966
+ // 3.7. crons.ts scaffold (크둠 μŠ€μΌ€μ€„λŸ¬ β€” 빈 ν…œν”Œλ¦Ώ)
967
+ const cronsPath = resolve(gencowDir, "crons.ts");
968
+ if (!existsSync(cronsPath)) {
969
+ writeFileSync(cronsPath, `/**
970
+ * gencow/crons.ts
971
+ * 크둠 μŠ€μΌ€μ€„λŸ¬ μ •μ˜ β€” 주기적으둜 μ‹€ν–‰ν•  μž‘μ—…μ„ λ“±λ‘ν•©λ‹ˆλ‹€.
972
+ *
973
+ * ⚠️ export default crons κ°€ μ—†μœΌλ©΄ μ„œλ²„κ°€ cron을 μΈμ‹ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
974
+ * ⚠️ action λ¬Έμžμ—΄μ€ κΈ°μ‘΄ mutation 이름과 μ •ν™•νžˆ λ§€μΉ­λ˜μ–΄μ•Ό ν•©λ‹ˆλ‹€.
975
+ */
976
+ import { cronJobs } from "@gencow/core";
977
+
978
+ const crons = cronJobs();
979
+
980
+ // ─── 예제 (ν•„μš”ν•œ κ²ƒλ§Œ 주석 ν•΄μ œ) ──────────────────────
981
+
982
+ // 30λΆ„λ§ˆλ‹€ μ‹€ν–‰
983
+ // crons.interval("crawlNews", { minutes: 30 }, "crawlPipeline.runStep1");
984
+
985
+ // 맀일 KST 07:00 (UTC 22:00) μ‹€ν–‰
986
+ // crons.daily("generateDigest", { hour: 22 }, "llmActions.generateDigest");
987
+
988
+ // λ§€μ£Ό μ›”μš”μΌ 09:00 (UTC 00:00) μ‹€ν–‰
989
+ // crons.weekly("weeklyReport", { dayOfWeek: 1, hour: 0 }, "reports.generateWeekly");
990
+
991
+ // cron ν‘œν˜„μ‹ 직접 μ‚¬μš©
992
+ // crons.cron("customJob", "0 */6 * * *", "module.mutation");
993
+
994
+ export default crons;
995
+ `);
996
+ success("Created gencow/crons.ts (크둠 μŠ€μΌ€μ€„λŸ¬ ν…œν”Œλ¦Ώ)");
997
+ }
998
+
922
999
  // 3.7. auth.ts (auth μ„€μ • β€” defineAuth 기반, shadcn νŒ¨ν„΄μœΌλ‘œ μ‚¬μš©μž μ†Œμœ )
923
1000
  const authConfigSource = resolve(__dirname, "..", "templates", "auth.ts");
924
1001
  if (existsSync(authConfigSource)) {
@@ -2118,6 +2195,67 @@ ${BOLD}Examples:${RESET}
2118
2195
  }
2119
2196
  log("");
2120
2197
 
2198
+ // ── κ°€λ“œλ ˆμΌ 1: λΉŒλ“œ κ²°κ³Όλ¬Ό API μ°Έμ‘° μŠ€μΊ” ──────────────
2199
+ try {
2200
+ const fullTargetDir = resolve(process.cwd(), targetDir);
2201
+ const scanDir = (dir) => {
2202
+ const entries = readdirSync(dir, { withFileTypes: true });
2203
+ const files = [];
2204
+ for (const e of entries) {
2205
+ const p = resolve(dir, e.name);
2206
+ if (e.isDirectory() && e.name !== "node_modules") {
2207
+ files.push(...scanDir(p));
2208
+ } else if (e.isFile() && e.name.endsWith(".js")) {
2209
+ files.push(p);
2210
+ }
2211
+ }
2212
+ return files;
2213
+ };
2214
+ const jsFiles = scanDir(fullTargetDir);
2215
+ const apiRefFiles = [];
2216
+ for (const f of jsFiles) {
2217
+ const content = readFileSync(f, "utf8");
2218
+ if (content.includes("/api/query") || content.includes("/api/mutation")) {
2219
+ apiRefFiles.push(f.replace(fullTargetDir + "/", ""));
2220
+ }
2221
+ }
2222
+ if (apiRefFiles.length > 0) {
2223
+ log("");
2224
+ warn(`λΉŒλ“œ νŒŒμΌμ—μ„œ API μ°Έμ‘°κ°€ λ°œκ²¬λ˜μ—ˆμŠ΅λ‹ˆλ‹€:`);
2225
+ for (const f of apiRefFiles.slice(0, 5)) log(` ${DIM}- ${f}${RESET}`);
2226
+ if (apiRefFiles.length > 5) log(` ${DIM}... μ™Έ ${apiRefFiles.length - 5}개${RESET}`);
2227
+ log("");
2228
+ warn(`정적 ν˜ΈμŠ€νŒ…μ—λŠ” API μ„œλ²„κ°€ μ—†μ–΄ 404κ°€ λ°œμƒν•©λ‹ˆλ‹€.`);
2229
+ info(`πŸ’‘ λ°±μ—”λ“œκ°€ ν•„μš”ν•˜λ‹€λ©΄: ${CYAN}VITE_API_URL=https://{backend}.gencow.app npm run build${RESET} ν›„ 배포`);
2230
+ info(`πŸ’‘ 별도 λ°±μ—”λ“œλ₯Ό μ‚¬μš©ν•œλ‹€λ©΄: ${CYAN}VITE_API_URL${RESET} ν™˜κ²½λ³€μˆ˜λ‘œ λΉŒλ“œ λŒ€μƒμ„ μ§€μ •ν•˜μ„Έμš”.`);
2231
+ log("");
2232
+
2233
+ const { createInterface } = await import("readline");
2234
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
2235
+ const answer = await new Promise(resolve => {
2236
+ rl.question(` ${YELLOW}⚠${RESET} κ·Έλž˜λ„ 정적 배포λ₯Ό κ³„μ†ν•˜μ‹œκ² μŠ΅λ‹ˆκΉŒ? (y/N) `, resolve);
2237
+ });
2238
+ rl.close();
2239
+ if (answer.toLowerCase() !== "y") {
2240
+ info("배포 μ·¨μ†Œλ¨.");
2241
+ return;
2242
+ }
2243
+ log("");
2244
+ }
2245
+ } catch { /* μŠ€μΊ” μ‹€νŒ¨ β€” λ¬΄μ‹œν•˜κ³  μ§„ν–‰ */ }
2246
+
2247
+ // ── κ°€λ“œλ ˆμΌ 2: 동일 ν”„λ‘œμ νŠΈ λ°±μ—”λ“œ 감지 ─────────────
2248
+ const parentDir = resolve(process.cwd(), "..");
2249
+ const hasParentBackend = existsSync(resolve(parentDir, "gencow"))
2250
+ || existsSync(resolve(parentDir, "gencow.config.ts"));
2251
+ const hasSelfBackend = existsSync(resolve(process.cwd(), "..", "gencow"))
2252
+ && process.cwd().includes("frontend");
2253
+ if (hasParentBackend || hasSelfBackend) {
2254
+ warn(`μƒμœ„ 디렉토리에 Gencow λ°±μ—”λ“œ ν”„λ‘œμ νŠΈκ°€ κ°μ§€λ˜μ—ˆμŠ΅λ‹ˆλ‹€.`);
2255
+ info(`πŸ’‘ 톡합 배포: ${CYAN}cd ${hasSelfBackend ? ".." : resolve(parentDir)} && gencow deploy${RESET}`);
2256
+ log("");
2257
+ }
2258
+
2121
2259
  // 1. tar.gz νŒ¨ν‚€μ§•
2122
2260
  info("정적 파일 νŒ¨ν‚€μ§• 쀑...");
2123
2261
  const { execSync: exec } = await import("child_process");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gencow",
3
- "version": "0.1.67",
3
+ "version": "0.1.69",
4
4
  "description": "Gencow β€” AI Backend Engine",
5
5
  "type": "module",
6
6
  "bin": {
package/server/index.js CHANGED
@@ -72587,6 +72587,7 @@ var websocket2 = {
72587
72587
  app.use("*", cors({
72588
72588
  origin: (origin) => {
72589
72589
  if (!origin || origin.startsWith("http://localhost")) return origin;
72590
+ if (origin.endsWith(".gencow.app")) return origin;
72590
72591
  const allowed = process.env.CORS_ORIGINS?.split(",") ?? [];
72591
72592
  return allowed.includes(origin) ? origin : "";
72592
72593
  },
@@ -73420,6 +73421,9 @@ async function main() {
73420
73421
  );
73421
73422
  }
73422
73423
  const port = Number(process.env.PORT || 5456);
73424
+ if (!process.env.GENCOW_INTERNAL_URL) {
73425
+ process.env.GENCOW_INTERNAL_URL = `http://localhost:${port}`;
73426
+ }
73423
73427
  if (IS_BAAS && !process.env.PORT) {
73424
73428
  console.error("[FATAL] BaaS app started without PORT env var. All apps would collide on :5456. Exiting.");
73425
73429
  process.exit(1);