gencow 0.1.16 → 0.1.17
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 +131 -0
- package/package.json +1 -1
- package/server/index.js +10 -0
- package/server/index.js.map +2 -2
package/bin/gencow.mjs
CHANGED
|
@@ -440,6 +440,24 @@ function generateReadmeMd(config, apiObj) {
|
|
|
440
440
|
md += `| \`/api/auth/sign-out\` | POST | 로그아웃 |\n\n`;
|
|
441
441
|
md += `> ⚠️ 모든 API 요청에 \`credentials: "include"\`를 포함해야 합니다 (세션 쿠키 전송)\n`;
|
|
442
442
|
md += `> ⚠️ JWT 토큰이나 \`/auth/register\` 같은 커스텀 경로를 만들지 마세요.\n\n`;
|
|
443
|
+
md += `### Next.js 프론트엔드 연동 (필수)\n\n`;
|
|
444
|
+
md += `프론트엔드(localhost:3000)와 백엔드(localhost:5456)가 다른 포트에서 실행되므로,\n`;
|
|
445
|
+
md += `\`next.config.ts\`에 rewrites 프록시를 설정해야 인증이 작동합니다:\n\n`;
|
|
446
|
+
md += `\`\`\`typescript\n`;
|
|
447
|
+
md += `// next.config.ts\n`;
|
|
448
|
+
md += `const nextConfig = {\n`;
|
|
449
|
+
md += ` async rewrites() {\n`;
|
|
450
|
+
md += ` return [\n`;
|
|
451
|
+
md += ` { source: "/api/:path*", destination: "http://localhost:5456/api/:path*" },\n`;
|
|
452
|
+
md += ` { source: "/ws", destination: "http://localhost:5456/ws" },\n`;
|
|
453
|
+
md += ` ];\n`;
|
|
454
|
+
md += ` },\n`;
|
|
455
|
+
md += `};\n`;
|
|
456
|
+
md += `\`\`\`\n\n`;
|
|
457
|
+
md += `또는 \`gencowAuth()\`에 명시적으로 baseUrl을 전달하세요:\n\n`;
|
|
458
|
+
md += `\`\`\`typescript\n`;
|
|
459
|
+
md += `const { signIn, useAuth } = gencowAuth(process.env.NEXT_PUBLIC_GENCOW_URL || "http://localhost:5456");\n`;
|
|
460
|
+
md += `\`\`\`\n\n`;
|
|
443
461
|
|
|
444
462
|
// ── 1-3. RPC 직접 호출 (접힘 — 비-React 환경용) ──────────
|
|
445
463
|
md += `---\n\n`;
|
|
@@ -1304,6 +1322,8 @@ ${BOLD}Dev commands:${RESET}
|
|
|
1304
1322
|
${GREEN}db:studio${RESET} Open Drizzle Studio ${DIM}(visual DB browser)${RESET}
|
|
1305
1323
|
${GREEN}add <comp...>${RESET} Add components ${DIM}(AI RAG Tools Memory Analytics)${RESET}
|
|
1306
1324
|
${GREEN}mcp${RESET} Start MCP server for AI agent integration ${DIM}(stdio)${RESET}
|
|
1325
|
+
${GREEN}codegen${RESET} Generate frontend-independent api.ts
|
|
1326
|
+
${DIM}--outdir, -o Output directory (default: src/gencow/)${RESET}
|
|
1307
1327
|
|
|
1308
1328
|
${BOLD}BaaS commands (login required):${RESET}
|
|
1309
1329
|
${GREEN}login${RESET} Login to Gencow Platform ${DIM}(browser → token)${RESET}
|
|
@@ -1883,6 +1903,117 @@ ${BOLD}Examples:${RESET}
|
|
|
1883
1903
|
success("Logged out");
|
|
1884
1904
|
},
|
|
1885
1905
|
|
|
1906
|
+
// ── codegen — 프론트엔드 전용 api.ts 생성 ─────────
|
|
1907
|
+
async codegen(...codegenArgs) {
|
|
1908
|
+
const config = loadConfig();
|
|
1909
|
+
const absoluteFunctions = resolve(process.cwd(), config.functionsDir);
|
|
1910
|
+
|
|
1911
|
+
// --outdir 옵션: 출력 디렉토리 지정
|
|
1912
|
+
let outdir = null;
|
|
1913
|
+
for (let i = 0; i < codegenArgs.length; i++) {
|
|
1914
|
+
if (codegenArgs[i] === "--outdir" || codegenArgs[i] === "-o") {
|
|
1915
|
+
outdir = codegenArgs[++i];
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
if (!outdir) {
|
|
1919
|
+
outdir = resolve(process.cwd(), "src", "gencow");
|
|
1920
|
+
} else {
|
|
1921
|
+
outdir = resolve(process.cwd(), outdir);
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
log(`\n${BOLD}${CYAN}Gencow Codegen${RESET}\n`);
|
|
1925
|
+
info(`Functions: ${config.functionsDir}/`);
|
|
1926
|
+
info(`Output: ${outdir}/api.ts`);
|
|
1927
|
+
log("");
|
|
1928
|
+
|
|
1929
|
+
// 1. extract 로직 — queries/mutations 목록 추출
|
|
1930
|
+
// 모노레포: findServerRoot() (packages/server에 @gencow/core 있음)
|
|
1931
|
+
// 독립 프로젝트: cwd (node_modules/gencow에서 @gencow/core 해석)
|
|
1932
|
+
let runtimeRoot;
|
|
1933
|
+
try { runtimeRoot = findServerRoot(); } catch { runtimeRoot = process.cwd(); }
|
|
1934
|
+
const extractTsPath = resolve(runtimeRoot, ".gencow-extract.ts");
|
|
1935
|
+
const script = `
|
|
1936
|
+
import { getRegisteredQueries, getRegisteredMutations } from "@gencow/core";
|
|
1937
|
+
import "${absoluteFunctions}/index.ts";
|
|
1938
|
+
console.log("GENCOW_API_JSON=" + JSON.stringify({
|
|
1939
|
+
queries: getRegisteredQueries(),
|
|
1940
|
+
mutations: getRegisteredMutations().map(m => m.name)
|
|
1941
|
+
}));
|
|
1942
|
+
process.exit(0);
|
|
1943
|
+
`;
|
|
1944
|
+
info("함수 목록 추출 중...");
|
|
1945
|
+
try {
|
|
1946
|
+
writeFileSync(extractTsPath, script);
|
|
1947
|
+
let outStr;
|
|
1948
|
+
try {
|
|
1949
|
+
outStr = execSync(`bun ${extractTsPath}`, {
|
|
1950
|
+
cwd: runtimeRoot,
|
|
1951
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1952
|
+
}).toString();
|
|
1953
|
+
} catch (execErr) {
|
|
1954
|
+
const stderr = execErr.stderr?.toString() || "";
|
|
1955
|
+
try { unlinkSync(extractTsPath); } catch { }
|
|
1956
|
+
throw new Error(`bun extract 실패:\n${stderr || execErr.message}`);
|
|
1957
|
+
}
|
|
1958
|
+
try { unlinkSync(extractTsPath); } catch { }
|
|
1959
|
+
|
|
1960
|
+
const match = outStr.match(/GENCOW_API_JSON=({.*})/);
|
|
1961
|
+
if (!match) throw new Error("Could not parse registry output");
|
|
1962
|
+
const { queries, mutations } = JSON.parse(match[1]);
|
|
1963
|
+
|
|
1964
|
+
// 2. namespace별 그룹핑
|
|
1965
|
+
const apiObj = {};
|
|
1966
|
+
for (const q of queries) {
|
|
1967
|
+
const [ns, name] = q.split(".");
|
|
1968
|
+
if (!apiObj[ns]) apiObj[ns] = { queries: [], mutations: [] };
|
|
1969
|
+
apiObj[ns].queries.push(name);
|
|
1970
|
+
}
|
|
1971
|
+
for (const m of mutations) {
|
|
1972
|
+
const [ns, name] = m.split(".");
|
|
1973
|
+
if (!apiObj[ns]) apiObj[ns] = { queries: [], mutations: [] };
|
|
1974
|
+
apiObj[ns].mutations.push(name);
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
// 3. 프론트엔드 전용 api.ts 생성 — 백엔드 import 없음
|
|
1978
|
+
let out = `/**\n`;
|
|
1979
|
+
out += ` * Generated by \`gencow codegen\`\n`;
|
|
1980
|
+
out += ` * Do not edit this file manually.\n`;
|
|
1981
|
+
out += ` *\n`;
|
|
1982
|
+
out += ` * 프론트엔드 전용 — 백엔드 의존성 없이 독립적으로 사용 가능합니다.\n`;
|
|
1983
|
+
out += ` * 타입을 추가하려면 제네릭에 인라인 타입을 지정하세요:\n`;
|
|
1984
|
+
out += ` * defineQuery<{ _return: Task[] }>("tasks.list")\n`;
|
|
1985
|
+
out += ` */\n`;
|
|
1986
|
+
out += `import { defineQuery, defineMutation } from "@gencow/react";\n\n`;
|
|
1987
|
+
|
|
1988
|
+
out += `export const api = {\n`;
|
|
1989
|
+
for (const [ns, fns] of Object.entries(apiObj)) {
|
|
1990
|
+
out += ` ${ns}: {\n`;
|
|
1991
|
+
for (const q of fns.queries) {
|
|
1992
|
+
out += ` ${q}: defineQuery("${ns}.${q}"),\n`;
|
|
1993
|
+
}
|
|
1994
|
+
for (const m of fns.mutations) {
|
|
1995
|
+
out += ` ${m}: defineMutation("${ns}.${m}"),\n`;
|
|
1996
|
+
}
|
|
1997
|
+
out += ` },\n`;
|
|
1998
|
+
}
|
|
1999
|
+
out += `} as const;\n`;
|
|
2000
|
+
|
|
2001
|
+
mkdirSync(outdir, { recursive: true });
|
|
2002
|
+
writeFileSync(resolve(outdir, "api.ts"), out);
|
|
2003
|
+
success(`Generated ${outdir}/api.ts`);
|
|
2004
|
+
log("");
|
|
2005
|
+
log(` ${DIM}Queries: ${queries.join(", ")}${RESET}`);
|
|
2006
|
+
log(` ${DIM}Mutations: ${mutations.join(", ")}${RESET}`);
|
|
2007
|
+
log("");
|
|
2008
|
+
info(`사용법: import { api } from "@/gencow/api";`);
|
|
2009
|
+
} catch (e) {
|
|
2010
|
+
try { unlinkSync(extractTsPath); } catch { }
|
|
2011
|
+
error(`Codegen 실패: ${e.message}`);
|
|
2012
|
+
process.exit(1);
|
|
2013
|
+
}
|
|
2014
|
+
},
|
|
2015
|
+
|
|
2016
|
+
|
|
1886
2017
|
// ── app (subcommands: create, list, delete) ────────
|
|
1887
2018
|
async app(subcmd, ...rest) {
|
|
1888
2019
|
const creds = requireCreds();
|
package/package.json
CHANGED
package/server/index.js
CHANGED
|
@@ -71702,6 +71702,11 @@ async function main() {
|
|
|
71702
71702
|
"OPENAI_API_KEY not found in .env. Add it to use ctx.ai.chat() in local development."
|
|
71703
71703
|
);
|
|
71704
71704
|
}
|
|
71705
|
+
if (localKey.length < 20 || /^sk-\.{2,}$/.test(localKey) || localKey.includes("your-key") || localKey.includes("YOUR_KEY")) {
|
|
71706
|
+
throw new Error(
|
|
71707
|
+
"OPENAI_API_KEY\uAC00 \uD50C\uB808\uC774\uC2A4\uD640\uB354\uC785\uB2C8\uB2E4. .env \uD30C\uC77C\uC5D0 \uC2E4\uC81C OpenAI API \uD0A4\uB97C \uC124\uC815\uD558\uC138\uC694. (https://platform.openai.com/api-keys)"
|
|
71708
|
+
);
|
|
71709
|
+
}
|
|
71705
71710
|
const { createOpenAI: createOpenAI2 } = await Promise.resolve().then(() => (init_dist4(), dist_exports));
|
|
71706
71711
|
const { generateText: generateText2 } = await Promise.resolve().then(() => (init_dist8(), dist_exports2));
|
|
71707
71712
|
const openai2 = createOpenAI2({ apiKey: localKey });
|
|
@@ -71845,6 +71850,11 @@ async function main() {
|
|
|
71845
71850
|
console.log(`[gencow] \u2192 Use { public: true } only for intentionally open endpoints.`);
|
|
71846
71851
|
}
|
|
71847
71852
|
console.log(`[gencow] \u{1F512} Secure by Default: ${protectedQueryCount} queries + ${protectedMutationCount} mutations require auth`);
|
|
71853
|
+
const _aiKey = process.env.OPENAI_API_KEY;
|
|
71854
|
+
if (_aiKey && (_aiKey.length < 20 || /^sk-\.{2,}$/.test(_aiKey) || _aiKey.includes("your-key") || _aiKey.includes("YOUR_KEY"))) {
|
|
71855
|
+
console.warn(`[gencow] \u26A0\uFE0F OPENAI_API_KEY\uAC00 \uD50C\uB808\uC774\uC2A4\uD640\uB354\uC785\uB2C8\uB2E4. .env\uC5D0 \uC2E4\uC81C \uD0A4\uB97C \uC124\uC815\uD558\uC138\uC694.`);
|
|
71856
|
+
console.warn(`[gencow] \u2192 https://platform.openai.com/api-keys`);
|
|
71857
|
+
}
|
|
71848
71858
|
app.route("/api", apiRouter);
|
|
71849
71859
|
const httpActions = getRegisteredHttpActions();
|
|
71850
71860
|
if (httpActions.length > 0) {
|