gencow 0.1.90 → 0.1.91

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
@@ -131,34 +131,13 @@ function loadConfig() {
131
131
  const configPath = resolve(cwd, "gencow.config.ts");
132
132
  const jsConfigPath = resolve(cwd, "gencow.config.js");
133
133
 
134
- // Detect project type: if we're inside a monorepo with packages/server, use monorepo defaults.
135
- // Otherwise use standalone project defaults (created by `gencow init`).
136
- const isMonorepo = (() => {
137
- let dir = cwd;
138
- for (let i = 0; i < 10; i++) {
139
- if (existsSync(resolve(dir, "packages/server"))) return true;
140
- const parent = resolve(dir, "..");
141
- if (parent === dir) break;
142
- dir = parent;
143
- }
144
- return false;
145
- })();
146
-
147
- const defaults = isMonorepo
148
- ? {
149
- functionsDir: "./apps/sample/gencow",
150
- schema: "./apps/sample/gencow/schema.ts",
151
- storage: "./apps/sample/uploads",
152
- db: { url: "./.gencow/data" },
153
- port: 5456,
154
- }
155
- : {
156
- functionsDir: "./gencow",
157
- schema: "./gencow/schema.ts",
158
- storage: "./.gencow/uploads",
159
- db: { url: "./.gencow/data" },
160
- port: 5456,
161
- };
134
+ const defaults = {
135
+ functionsDir: "./gencow",
136
+ schema: "./gencow/schema.ts",
137
+ storage: "./.gencow/uploads",
138
+ db: { url: "./.gencow/data" },
139
+ port: 5456,
140
+ };
162
141
 
163
142
  // Try reading .ts config — extract values via simple regex (no full TS execution needed)
164
143
  const path = existsSync(configPath) ? configPath : existsSync(jsConfigPath) ? jsConfigPath : null;
@@ -234,7 +213,7 @@ function findServerRoot() {
234
213
  function buildEnv(config) {
235
214
  const cwd = process.cwd();
236
215
 
237
- // Load .env from the app directory (e.g. apps/sample/.env)
216
+ // Load .env from the app directory (e.g. gencow/.env)
238
217
  // so that OPENAI_API_KEY and other user-defined env vars are available to the server
239
218
  const appEnv = {};
240
219
  const appDir = resolve(cwd, config.functionsDir, "..");
@@ -490,6 +469,7 @@ const commands = {
490
469
  _templates: [
491
470
  { id: "default", label: "빈 프로젝트", deps: {} },
492
471
  { id: "task-app", label: "Task + Files CRUD 백엔드", deps: {} },
472
+ { id: "admin-tool", label: "관리자/내부 도구 (인증 없음)", deps: {} },
493
473
  { id: "fullstack", label: "Tasks + Files + AI Chat + Agent 백엔드", deps: { "ai": "^4.0.0", "@ai-sdk/openai": "^1.0.0" } },
494
474
  { id: "ai-chat", label: "AI 챗봇 + Memory 백엔드", deps: { "ai": "^4.0.0", "@ai-sdk/openai": "^1.0.0" } },
495
475
  ],
@@ -514,10 +514,11 @@ export function buildCronSection() {
514
514
  md += `\`\`\`\n\n`;
515
515
  md += `> ⚠️ \`export default crons\`가 없으면 서버가 cron을 인식하지 않습니다.\n`;
516
516
  md += `> ⚠️ action 문자열은 기존 mutation 이름과 정확히 매칭되어야 합니다.\n\n`;
517
- md += `> 💡 cron 핸들러에서 mutation을 호출하려면 self-fetch를 사용하세요:\n`;
517
+ md += `> 💡 cron 핸들러에서 다른 모듈의 함수를 호출하려면 직접 import하세요:\n`;
518
518
  md += `> \`\`\`typescript\n`;
519
- md += `> const baseUrl = process.env.GENCOW_INTERNAL_URL; // 서버 부팅 시 자동 설정\n`;
520
- md += `> await fetch(\`\${baseUrl}/api/mutation\`, { method: "POST", ... });\n`;
519
+ md += `> import { fetchNews } from "./crawler";\n`;
520
+ md += `> const result = await fetchNews.handler(ctx, { keyword });\n`;
521
+ md += `> // ❌ fetch("/api/mutation") self-fetch는 사용하지 마세요\n`;
521
522
  md += `> \`\`\`\n\n`;
522
523
  return md;
523
524
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gencow",
3
- "version": "0.1.90",
3
+ "version": "0.1.91",
4
4
  "description": "Gencow — AI Backend Engine",
5
5
  "type": "module",
6
6
  "bin": {
package/server/index.js CHANGED
@@ -72155,9 +72155,20 @@ function printAuditReport(result, functionsPath) {
72155
72155
  function auditSchemaPatterns(schemaPath) {
72156
72156
  try {
72157
72157
  const fs2 = __require("fs");
72158
+ const path2 = __require("path");
72158
72159
  const content = fs2.readFileSync(schemaPath, "utf-8");
72159
- const tableRegex = /export\s+const\s+(\w+)\s*=\s*pgTable\s*\(\s*"([^"]+)"\s*,\s*\{([^}]+(?:\{[^}]*\}[^}]*)*)\}/g;
72160
+ const fileName = path2.basename(schemaPath);
72161
+ if (fileName === "auth-schema.ts") return;
72160
72162
  const warnings = [];
72163
+ const hasPgTableImport = /import\s*\{[^}]*\bpgTable\b[^}]*\}\s*from\s*["']drizzle-orm\/pg-core["']/.test(content);
72164
+ const hasGencowTableImport = /import\s*\{[^}]*\bgencowTable\b[^}]*\}\s*from\s*["']@gencow\/core["']/.test(content);
72165
+ if (hasPgTableImport && !hasGencowTableImport) {
72166
+ warnings.push(` \u26D4 pgTable\uC744 \uC9C1\uC811 \uC0AC\uC6A9\uD558\uACE0 \uC788\uC2B5\uB2C8\uB2E4. gencowTable\uB85C \uC804\uD658\uD558\uC138\uC694.`);
72167
+ warnings.push(` \u2192 import { gencowTable } from "@gencow/core";`);
72168
+ warnings.push(` \u2192 \uAD00\uB9AC\uC790 \uC571: gencowTable("name", columns, { filter: () => true })`);
72169
+ warnings.push(` \u2192 \uBA40\uD2F0\uC720\uC800 \uC571: gencowTable("name", columns, ownerFilter("userId"))`);
72170
+ }
72171
+ const tableRegex = /export\s+const\s+(\w+)\s*=\s*pgTable\s*\(\s*"([^"]+)"\s*,\s*\{([^}]+(?:\{[^}]*\}[^}]*)*)\}/g;
72161
72172
  const SYSTEM_TABLES = /* @__PURE__ */ new Set([
72162
72173
  "user",
72163
72174
  "session",
@@ -72195,7 +72206,7 @@ function auditSchemaPatterns(schemaPath) {
72195
72206
  }
72196
72207
  if (warnings.length > 0) {
72197
72208
  console.log(`
72198
- [auditor] \u{1F50D} \uC2A4\uD0A4\uB9C8 \uBCF4\uC548 \uD328\uD134 \uC810\uAC80 (${schemaPath}):`);
72209
+ [auditor] \u{1F50D} \uC2A4\uD0A4\uB9C8 \uBCF4\uC548 \uD328\uD134 \uC810\uAC80 (${fileName}):`);
72199
72210
  for (const w of warnings) {
72200
72211
  console.log(`[auditor] ${w}`);
72201
72212
  }
@@ -72205,6 +72216,112 @@ function auditSchemaPatterns(schemaPath) {
72205
72216
  } catch {
72206
72217
  }
72207
72218
  }
72219
+ function auditSelfFetch(functionsDir) {
72220
+ const usages = [];
72221
+ try {
72222
+ const fs2 = __require("fs");
72223
+ const path2 = __require("path");
72224
+ const files = findTsFiles(functionsDir);
72225
+ const selfFetchPattern = /\/api\/(query|mutation)/;
72226
+ for (const file3 of files) {
72227
+ const content = fs2.readFileSync(file3, "utf-8");
72228
+ const lines = content.split("\n");
72229
+ for (let i = 0; i < lines.length; i++) {
72230
+ const line = lines[i];
72231
+ const trimmed = line.trim();
72232
+ if (trimmed.startsWith("//") || trimmed.startsWith("*")) continue;
72233
+ if (line.includes("fetch") && selfFetchPattern.test(line)) {
72234
+ usages.push({
72235
+ file: path2.relative(functionsDir, file3),
72236
+ line: i + 1,
72237
+ snippet: trimmed
72238
+ });
72239
+ }
72240
+ }
72241
+ }
72242
+ } catch {
72243
+ }
72244
+ return { usages };
72245
+ }
72246
+ function printSelfFetchReport(result) {
72247
+ if (result.usages.length === 0) return;
72248
+ console.log(`
72249
+ [auditor] \u26A0\uFE0F Self-fetch detected in ${result.usages.length} location(s):`);
72250
+ for (const u of result.usages) {
72251
+ console.log(`[auditor] ${u.file}:${u.line} \u2192 ${u.snippet}`);
72252
+ }
72253
+ console.log(`[auditor] \u2192 \uAC19\uC740 \uC11C\uBC84\uC758 \uD568\uC218\uB294 \uC9C1\uC811 import\uD558\uC138\uC694:`);
72254
+ console.log(`[auditor] import { fetchNews } from "./crawler";`);
72255
+ console.log(`[auditor] const result = await fetchNews.handler(ctx, { keyword });`);
72256
+ console.log(`[auditor] \u2192 fetch("/api/mutation") \uD328\uD134\uC740 \uBD88\uD544\uC694\uD55C \uB124\uD2B8\uC6CC\uD06C \uC6B0\uD68C\uC785\uB2C8\uB2E4.
72257
+ `);
72258
+ }
72259
+ function auditFrontendAntiPatterns(projectDir) {
72260
+ const issues = [];
72261
+ try {
72262
+ const fs2 = __require("fs");
72263
+ const path2 = __require("path");
72264
+ const srcDir = path2.join(projectDir, "src");
72265
+ if (!fs2.existsSync(srcDir)) return { issues };
72266
+ const SUSPECT_NAMES = /* @__PURE__ */ new Set([
72267
+ "api.ts",
72268
+ "api.tsx",
72269
+ "api-client.ts",
72270
+ "api-client.tsx",
72271
+ "rpc.ts",
72272
+ "rpc.tsx",
72273
+ "client.ts",
72274
+ "client.tsx"
72275
+ ]);
72276
+ const selfFetchPattern = /\/api\/(query|mutation)/;
72277
+ const allFiles = findTsFiles(srcDir);
72278
+ for (const file3 of allFiles) {
72279
+ const basename = path2.basename(file3);
72280
+ const relPath = path2.relative(projectDir, file3);
72281
+ if (relPath.startsWith("gencow/") || relPath.startsWith("gencow\\")) continue;
72282
+ if (SUSPECT_NAMES.has(basename)) {
72283
+ const content = fs2.readFileSync(file3, "utf-8");
72284
+ if (selfFetchPattern.test(content)) {
72285
+ issues.push({
72286
+ file: relPath,
72287
+ reason: `fetch("/api/query" \uB610\uB294 "/api/mutation") \uC9C1\uC811 \uD638\uCD9C \uAC10\uC9C0 \u2014 \uC774 \uD30C\uC77C\uC744 \uC0AD\uC81C\uD558\uC138\uC694`
72288
+ });
72289
+ }
72290
+ }
72291
+ }
72292
+ } catch {
72293
+ }
72294
+ return { issues };
72295
+ }
72296
+ function printFrontendAntiPatternReport(result) {
72297
+ if (result.issues.length === 0) return;
72298
+ console.log(`
72299
+ [auditor] \u26A0\uFE0F Custom API client detected:`);
72300
+ for (const issue3 of result.issues) {
72301
+ console.log(`[auditor] ${issue3.file} \u2014 ${issue3.reason}`);
72302
+ }
72303
+ console.log(`[auditor] \u2192 gencow/api.ts (\uC790\uB3D9 \uC0DD\uC131)\uC640 useQuery/useMutation\uC744 \uC0AC\uC6A9\uD558\uC138\uC694`);
72304
+ console.log(`[auditor] \u2192 \u{1F4C4} README.md "\u274C \uC808\uB300 \uD558\uC9C0 \uB9C8\uC138\uC694" \uC139\uC158 \uCC38\uC870
72305
+ `);
72306
+ }
72307
+ function findTsFiles(dir) {
72308
+ const fs2 = __require("fs");
72309
+ const path2 = __require("path");
72310
+ const results = [];
72311
+ try {
72312
+ const entries = fs2.readdirSync(dir, { withFileTypes: true });
72313
+ for (const entry of entries) {
72314
+ const fullPath = path2.join(dir, entry.name);
72315
+ if (entry.isDirectory() && entry.name !== "node_modules" && !entry.name.startsWith(".")) {
72316
+ results.push(...findTsFiles(fullPath));
72317
+ } else if (entry.isFile() && (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx"))) {
72318
+ results.push(fullPath);
72319
+ }
72320
+ }
72321
+ } catch {
72322
+ }
72323
+ return results;
72324
+ }
72208
72325
 
72209
72326
  // ../server/src/admin.ts
72210
72327
  init_src();
@@ -73083,6 +73200,11 @@ async function main() {
73083
73200
  if (existsSync4(schemaFile)) {
73084
73201
  auditSchemaPatterns(schemaFile);
73085
73202
  }
73203
+ const selfFetchResult = auditSelfFetch(functionsPath);
73204
+ printSelfFetchReport(selfFetchResult);
73205
+ const projectDir = resolve5(functionsPath, "..");
73206
+ const frontendResult = auditFrontendAntiPatterns(projectDir);
73207
+ printFrontendAntiPatternReport(frontendResult);
73086
73208
  appModule = await import(functionsPath);
73087
73209
  const regQ = getRegisteredQueries().length;
73088
73210
  const regM = getRegisteredMutations().length;