gencow 0.1.64 → 0.1.65
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 +43 -11
- package/package.json +1 -1
- package/server/index.js +13 -1
- package/server/index.js.map +2 -2
- package/templates/SECURITY.md +1 -2
package/bin/gencow.mjs
CHANGED
|
@@ -343,9 +343,26 @@ process.exit(0);
|
|
|
343
343
|
let out = `/**\n * Generated by Gencow\n * Do not edit this file manually.\n */\n`;
|
|
344
344
|
out += `import { defineQuery, defineMutation } from "@gencow/react";\n\n`;
|
|
345
345
|
|
|
346
|
-
// Add import statements
|
|
346
|
+
// Add import statements — 실제 파일 존재 여부 확인 후 import 경로 결정
|
|
347
|
+
// 네임스페이스별 파일(e.g. products.ts)이 있으면 개별 import,
|
|
348
|
+
// 없으면 단일 파일(index.ts) 프로젝트로 판단하여 index.ts에서 import
|
|
349
|
+
const nsFileMap = new Map();
|
|
347
350
|
for (const ns of Object.keys(apiObj)) {
|
|
348
|
-
|
|
351
|
+
const nsFile = resolve(absoluteFunctions, `${ns}.ts`);
|
|
352
|
+
if (existsSync(nsFile)) {
|
|
353
|
+
nsFileMap.set(ns, `./${ns}.ts`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
const hasNamespaceFiles = nsFileMap.size > 0;
|
|
357
|
+
|
|
358
|
+
if (hasNamespaceFiles) {
|
|
359
|
+
for (const [ns, path] of nsFileMap) {
|
|
360
|
+
out += `import type * as ${ns} from "${path}";\n`;
|
|
361
|
+
}
|
|
362
|
+
// 네임스페이스 파일이 없는 namespace는 타입 생략 (typeof 참조 제거)
|
|
363
|
+
} else {
|
|
364
|
+
// 단일 파일 프로젝트 — index.ts에서 모든 export를 import
|
|
365
|
+
out += `import type * as _all from "./index.ts";\n`;
|
|
349
366
|
}
|
|
350
367
|
|
|
351
368
|
out += `\nexport const api = {\n`;
|
|
@@ -353,11 +370,24 @@ process.exit(0);
|
|
|
353
370
|
out += ` ${ns}: {\n`;
|
|
354
371
|
for (const q of fns.queries) {
|
|
355
372
|
const tsExport = q === "delete" ? "_delete" : q;
|
|
356
|
-
|
|
373
|
+
if (hasNamespaceFiles && nsFileMap.has(ns)) {
|
|
374
|
+
out += ` ${q}: defineQuery<typeof ${ns}.${tsExport}>("${ns}.${q}"),\n`;
|
|
375
|
+
} else if (!hasNamespaceFiles) {
|
|
376
|
+
out += ` ${q}: defineQuery<typeof _all.${tsExport}>("${ns}.${q}"),\n`;
|
|
377
|
+
} else {
|
|
378
|
+
// 네임스페이스 파일이 일부만 있고 이 ns에는 없는 경우
|
|
379
|
+
out += ` ${q}: defineQuery("${ns}.${q}"),\n`;
|
|
380
|
+
}
|
|
357
381
|
}
|
|
358
382
|
for (const m of fns.mutations) {
|
|
359
383
|
const tsExport = m === "delete" ? "_delete" : m;
|
|
360
|
-
|
|
384
|
+
if (hasNamespaceFiles && nsFileMap.has(ns)) {
|
|
385
|
+
out += ` ${m}: defineMutation<typeof ${ns}.${tsExport}>("${ns}.${m}"),\n`;
|
|
386
|
+
} else if (!hasNamespaceFiles) {
|
|
387
|
+
out += ` ${m}: defineMutation<typeof _all.${tsExport}>("${ns}.${m}"),\n`;
|
|
388
|
+
} else {
|
|
389
|
+
out += ` ${m}: defineMutation("${ns}.${m}"),\n`;
|
|
390
|
+
}
|
|
361
391
|
}
|
|
362
392
|
out += ` },\n`;
|
|
363
393
|
}
|
|
@@ -881,7 +911,7 @@ const commands = {
|
|
|
881
911
|
`);
|
|
882
912
|
success("Created gencow.config.ts");
|
|
883
913
|
|
|
884
|
-
// 4.5. drizzle.config.ts (
|
|
914
|
+
// 4.5. drizzle.config.ts (Cloud-first: DATABASE_URL 있으면 Cloud DB, 없으면 --local PGlite)
|
|
885
915
|
writeFileSync(resolve(projectDir, "drizzle.config.ts"),
|
|
886
916
|
`import { defineConfig } from "drizzle-kit";
|
|
887
917
|
|
|
@@ -889,8 +919,9 @@ export default defineConfig({
|
|
|
889
919
|
dialect: "postgresql",
|
|
890
920
|
schema: ["./gencow/schema.ts", "./gencow/auth-schema.ts"],
|
|
891
921
|
out: "./migrations",
|
|
892
|
-
|
|
893
|
-
|
|
922
|
+
...(process.env.DATABASE_URL
|
|
923
|
+
? { dbCredentials: { url: process.env.DATABASE_URL } }
|
|
924
|
+
: { driver: "pglite", dbCredentials: { url: "./.gencow/data" } }),
|
|
894
925
|
});
|
|
895
926
|
`);
|
|
896
927
|
success("Created drizzle.config.ts");
|
|
@@ -3247,10 +3278,11 @@ process.exit(0);
|
|
|
3247
3278
|
log(`
|
|
3248
3279
|
${BOLD}${CYAN}🚀 Gencow Cloud Dev${RESET}
|
|
3249
3280
|
|
|
3250
|
-
${GREEN}▸${RESET} App:
|
|
3251
|
-
${GREEN}▸${RESET} URL:
|
|
3252
|
-
${GREEN}▸${RESET}
|
|
3253
|
-
${GREEN}▸${RESET}
|
|
3281
|
+
${GREEN}▸${RESET} App: ${BOLD}${appName}${RESET}
|
|
3282
|
+
${GREEN}▸${RESET} URL: ${DIM}${appUrl}${RESET}
|
|
3283
|
+
${GREEN}▸${RESET} Dashboard: ${DIM}https://gencow.app/_cloud/apps/${appName}${RESET}
|
|
3284
|
+
${GREEN}▸${RESET} Watching: ${DIM}${functionsDir}/ (${watchedFiles.join(", ") || "*.ts"})${RESET}
|
|
3285
|
+
${GREEN}▸${RESET} Mode: ${DIM}Cloud (PostgreSQL)${RESET}
|
|
3254
3286
|
|
|
3255
3287
|
${DIM}Ctrl+C to stop${RESET}
|
|
3256
3288
|
|
package/package.json
CHANGED
package/server/index.js
CHANGED
|
@@ -72569,6 +72569,7 @@ async function main() {
|
|
|
72569
72569
|
}
|
|
72570
72570
|
const mutations = getRegisteredMutations();
|
|
72571
72571
|
const mutationMap = new Map(mutations.map((m) => [m.name, m]));
|
|
72572
|
+
const publicQueryWarned = /* @__PURE__ */ new Set();
|
|
72572
72573
|
const apiRouter = new Hono2().post("/query", async (c) => {
|
|
72573
72574
|
const body = await c.req.json().catch(() => ({}));
|
|
72574
72575
|
const { name: name21, args } = body;
|
|
@@ -72585,6 +72586,13 @@ async function main() {
|
|
|
72585
72586
|
try {
|
|
72586
72587
|
const validatedArgs = parseArgs(queryDef.argsSchema, args);
|
|
72587
72588
|
const result = await queryDef.handler(ctx, validatedArgs);
|
|
72589
|
+
if (queryDef.isPublic && !IS_BAAS && Array.isArray(result) && result.length >= 100) {
|
|
72590
|
+
if (!publicQueryWarned.has(name21)) {
|
|
72591
|
+
publicQueryWarned.add(name21);
|
|
72592
|
+
console.warn(`[gencow] \u26A0\uFE0F "${name21}" (public) returned ${result.length} rows without pagination.`);
|
|
72593
|
+
console.warn(`[gencow] \u{1F4A1} Add .limit(100) to prevent full table scans. See SECURITY.md`);
|
|
72594
|
+
}
|
|
72595
|
+
}
|
|
72588
72596
|
return c.json(result);
|
|
72589
72597
|
} catch (err) {
|
|
72590
72598
|
const status = err instanceof GencowValidationError ? 400 : 500;
|
|
@@ -72641,11 +72649,15 @@ async function main() {
|
|
|
72641
72649
|
console.log(`[gencow] \u26A0\uFE0F PUBLIC FUNCTIONS (no auth required):`);
|
|
72642
72650
|
if (publicQueries.length > 0) {
|
|
72643
72651
|
console.log(`[gencow] queries: ${publicQueries.join(", ")}`);
|
|
72652
|
+
console.log(`[gencow] \u{1F513} These queries return data WITHOUT authentication.`);
|
|
72653
|
+
console.log(`[gencow] Ensure no PII (personal data) is exposed.`);
|
|
72654
|
+
console.log(`[gencow] Add .limit() to prevent full table scans on large datasets.`);
|
|
72644
72655
|
}
|
|
72645
72656
|
if (publicMutations.length > 0) {
|
|
72646
72657
|
console.log(`[gencow] mutations: ${publicMutations.join(", ")}`);
|
|
72647
72658
|
}
|
|
72648
|
-
console.log(`[gencow] \u2192
|
|
72659
|
+
console.log(`[gencow] \u2192 Remove { public: true } if auth is intended.`);
|
|
72660
|
+
console.log(`[gencow] \u2192 \u{1F4C4} gencow/SECURITY.md \uCC38\uACE0`);
|
|
72649
72661
|
}
|
|
72650
72662
|
console.log(`[gencow] \u{1F512} Secure by Default: ${protectedQueryCount} queries + ${protectedMutationCount} mutations require auth`);
|
|
72651
72663
|
const _aiKey = process.env.OPENAI_API_KEY;
|