gencow 0.1.87 → 0.1.89
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
|
@@ -851,11 +851,22 @@ ${hasPrompt ? `
|
|
|
851
851
|
|
|
852
852
|
// ── db:reset ─────────────────────────────────────────
|
|
853
853
|
// TRUNCATE all user tables + seed.ts 실행.
|
|
854
|
-
//
|
|
855
|
-
//
|
|
854
|
+
// 로컬 전용: Cloud DB reset은 위험하므로 CLI에서 미지원 (Dashboard에서만 가능)
|
|
855
|
+
// 사용: gencow db:reset --local
|
|
856
856
|
async "db:reset"(...args) {
|
|
857
857
|
const config = loadConfig();
|
|
858
858
|
const cwd = process.cwd();
|
|
859
|
+
const isLocal = args.includes("--local");
|
|
860
|
+
|
|
861
|
+
if (!isLocal) {
|
|
862
|
+
// Cloud reset은 미지원 — 안내 메시지
|
|
863
|
+
error("db:reset은 --local 플래그가 필요합니다.");
|
|
864
|
+
info(`${DIM}Cloud DB 초기화는 Dashboard에서만 가능합니다 (데이터 보호).${RESET}`);
|
|
865
|
+
info(`${DIM}로컬 DB를 초기화하려면: gencow db:reset --local${RESET}`);
|
|
866
|
+
log("");
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
|
|
859
870
|
const port = process.env.PORT || config.port || 5456;
|
|
860
871
|
|
|
861
872
|
log(`\n${BOLD}${CYAN}Gencow DB Reset${RESET}\n`);
|
|
@@ -925,47 +936,115 @@ ${hasPrompt ? `
|
|
|
925
936
|
|
|
926
937
|
// ── db:seed ──────────────────────────────────────────
|
|
927
938
|
// seed.ts 실행 (TRUNCATE 없이). 서버가 실행 중이어야 함.
|
|
928
|
-
//
|
|
929
|
-
async "db:seed"() {
|
|
939
|
+
// Cloud-first: 기본=Cloud, --local=로컬
|
|
940
|
+
async "db:seed"(...args) {
|
|
930
941
|
const config = loadConfig();
|
|
931
|
-
const
|
|
942
|
+
const isLocal = args.includes("--local");
|
|
932
943
|
|
|
933
|
-
|
|
944
|
+
if (isLocal) {
|
|
945
|
+
// ── 로컬 모드: localhost 서버에 seed ──
|
|
946
|
+
const port = process.env.PORT || config.port || 5456;
|
|
934
947
|
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
948
|
+
log(`\n${BOLD}${CYAN}Gencow DB Seed${RESET} ${DIM}(local)${RESET}\n`);
|
|
949
|
+
|
|
950
|
+
// 서버가 실행 중인지 확인
|
|
951
|
+
try {
|
|
952
|
+
const statusRes = await fetch(`http://localhost:${port}/_admin/status`, { signal: AbortSignal.timeout(2000) });
|
|
953
|
+
if (!statusRes.ok) throw new Error("Server not ready");
|
|
954
|
+
} catch {
|
|
955
|
+
error(`Server not running on :${port}`);
|
|
956
|
+
info(`${DIM}gencow dev --local 로 서버를 먼저 시작하세요.${RESET}`);
|
|
957
|
+
log("");
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
try {
|
|
962
|
+
const res = await fetch(`http://localhost:${port}/_admin/seed`, {
|
|
963
|
+
method: "POST",
|
|
964
|
+
headers: { "Content-Type": "application/json" },
|
|
965
|
+
});
|
|
966
|
+
const data = await res.json();
|
|
967
|
+
|
|
968
|
+
if (res.status === 404) {
|
|
969
|
+
warn("gencow/seed.ts not found");
|
|
970
|
+
info(`\n ${DIM}Create gencow/seed.ts:${RESET}\n`);
|
|
971
|
+
log(` ${DIM}import { tasks } from "./schema";${RESET}`);
|
|
972
|
+
log(` ${DIM}export default async function seed(ctx) {${RESET}`);
|
|
973
|
+
log(` ${DIM} await ctx.db.insert(tasks).values([${RESET}`);
|
|
974
|
+
log(` ${DIM} { title: "Hello World" },${RESET}`);
|
|
975
|
+
log(` ${DIM} ]);${RESET}`);
|
|
976
|
+
log(` ${DIM}};${RESET}`);
|
|
977
|
+
} else if (!res.ok) {
|
|
978
|
+
error(`Seed failed: ${data.error || "Unknown error"}`);
|
|
979
|
+
} else {
|
|
980
|
+
success("Seed 실행 완료 ✓");
|
|
981
|
+
}
|
|
982
|
+
} catch (e) {
|
|
983
|
+
error(`Server connection failed: ${e.message}`);
|
|
984
|
+
}
|
|
942
985
|
log("");
|
|
943
986
|
return;
|
|
944
987
|
}
|
|
945
988
|
|
|
989
|
+
// ── Cloud 모드 (기본) ──
|
|
990
|
+
const gencowJsonPath = resolve(process.cwd(), "gencow.json");
|
|
991
|
+
let appId = null;
|
|
992
|
+
if (existsSync(gencowJsonPath)) {
|
|
993
|
+
try {
|
|
994
|
+
const gencowJson = JSON.parse(readFileSync(gencowJsonPath, "utf8"));
|
|
995
|
+
appId = gencowJson.appId || gencowJson.appName;
|
|
996
|
+
} catch { /* ignore parse error */ }
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
if (!appId) {
|
|
1000
|
+
error("Cloud app not found — gencow.json이 없거나 appId가 설정되지 않았습니다.");
|
|
1001
|
+
info(`${DIM}먼저 gencow deploy 를 실행하세요.${RESET}`);
|
|
1002
|
+
info(`${DIM}로컬 서버에 seed하려면: gencow db:seed --local${RESET}`);
|
|
1003
|
+
log("");
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
log(`\n${BOLD}${CYAN}Gencow DB Seed${RESET} ${DIM}(cloud: ${appId})${RESET}\n`);
|
|
1008
|
+
info("Running seed.ts on cloud app...");
|
|
1009
|
+
|
|
1010
|
+
const cloudUrl = `https://${appId}.gencow.app/_admin/seed`;
|
|
1011
|
+
|
|
946
1012
|
try {
|
|
947
|
-
|
|
1013
|
+
// 앱이 실행 중인지 확인 (status 체크)
|
|
1014
|
+
const statusRes = await fetch(`https://${appId}.gencow.app/_admin/status`, {
|
|
1015
|
+
signal: AbortSignal.timeout(5000),
|
|
1016
|
+
}).catch(() => null);
|
|
1017
|
+
|
|
1018
|
+
if (!statusRes || !statusRes.ok) {
|
|
1019
|
+
error(`Cloud app "${appId}" is not running`);
|
|
1020
|
+
info(`${DIM}Dashboard → Apps → ${appId} → Resume 으로 앱을 시작하세요.${RESET}`);
|
|
1021
|
+
info(`${DIM}또는: gencow deploy 로 배포 후 시도하세요.${RESET}`);
|
|
1022
|
+
log("");
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
const res = await fetch(cloudUrl, {
|
|
948
1027
|
method: "POST",
|
|
949
1028
|
headers: { "Content-Type": "application/json" },
|
|
1029
|
+
signal: AbortSignal.timeout(30000), // seed는 시간이 걸릴 수 있음
|
|
950
1030
|
});
|
|
951
1031
|
const data = await res.json();
|
|
952
1032
|
|
|
953
1033
|
if (res.status === 404) {
|
|
954
|
-
warn("gencow/seed.ts not found");
|
|
955
|
-
info(`\n ${DIM}Create gencow/seed.ts:${RESET}\n`);
|
|
956
|
-
log(` ${DIM}import { tasks } from "./schema";${RESET}`);
|
|
1034
|
+
warn("gencow/seed.ts not found on cloud app");
|
|
1035
|
+
info(`\n ${DIM}Create gencow/seed.ts and redeploy:${RESET}\n`);
|
|
957
1036
|
log(` ${DIM}export default async function seed(ctx) {${RESET}`);
|
|
958
|
-
log(` ${DIM} await ctx.db.insert(tasks).values([
|
|
959
|
-
log(` ${DIM}
|
|
960
|
-
|
|
961
|
-
log(` ${DIM}};${RESET}`);
|
|
1037
|
+
log(` ${DIM} await ctx.db.insert(tasks).values([...]);${RESET}`);
|
|
1038
|
+
log(` ${DIM}};${RESET}\n`);
|
|
1039
|
+
info(`${DIM}After creating seed.ts: gencow deploy && gencow db:seed${RESET}`);
|
|
962
1040
|
} else if (!res.ok) {
|
|
963
|
-
error(`
|
|
1041
|
+
error(`Cloud seed failed: ${data.error || "Unknown error"}`);
|
|
964
1042
|
} else {
|
|
965
|
-
success("
|
|
1043
|
+
success("Cloud seed 실행 완료 ✓");
|
|
966
1044
|
}
|
|
967
1045
|
} catch (e) {
|
|
968
|
-
error(`
|
|
1046
|
+
error(`Cloud app connection failed: ${e.message}`);
|
|
1047
|
+
info(`${DIM}앱이 실행 중인지 확인하세요: https://${appId}.gencow.app${RESET}`);
|
|
969
1048
|
}
|
|
970
1049
|
log("");
|
|
971
1050
|
},
|
|
@@ -1242,10 +1321,22 @@ ${hasPrompt ? `
|
|
|
1242
1321
|
},
|
|
1243
1322
|
|
|
1244
1323
|
// ── db:push ──────────────────────────────────────────
|
|
1245
|
-
|
|
1324
|
+
// Cloud-first: 기본=Cloud, --local=로컬
|
|
1325
|
+
async "db:push"(...args) {
|
|
1246
1326
|
const config = loadConfig();
|
|
1327
|
+
const isLocal = args.includes("--local");
|
|
1247
1328
|
|
|
1248
|
-
|
|
1329
|
+
if (isLocal) {
|
|
1330
|
+
// ── 로컬 모드: 기존 동작 유지 ──
|
|
1331
|
+
log(`\n${BOLD}${CYAN}Gencow DB Push${RESET} ${DIM}(local)${RESET}\n`);
|
|
1332
|
+
info("Pushing schema.ts → database (no migration files)...");
|
|
1333
|
+
runInServer("pnpm db:push --force", buildEnv(config));
|
|
1334
|
+
success("Schema pushed!");
|
|
1335
|
+
log(` ${DIM}Tables are in sync with schema.ts${RESET}\n`);
|
|
1336
|
+
return;
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
// ── Cloud 모드 (기본) ──
|
|
1249
1340
|
const gencowJsonPath = resolve(process.cwd(), "gencow.json");
|
|
1250
1341
|
let appId = null;
|
|
1251
1342
|
if (existsSync(gencowJsonPath)) {
|
|
@@ -1255,74 +1346,73 @@ ${hasPrompt ? `
|
|
|
1255
1346
|
} catch { /* ignore parse error */ }
|
|
1256
1347
|
}
|
|
1257
1348
|
|
|
1258
|
-
if (appId) {
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
const functionsDir = config.functionsDir || "./gencow";
|
|
1266
|
-
const absoluteFunctions = resolve(process.cwd(), functionsDir);
|
|
1267
|
-
if (!existsSync(absoluteFunctions)) {
|
|
1268
|
-
error(`Functions directory not found: ${functionsDir}`);
|
|
1269
|
-
process.exit(1);
|
|
1270
|
-
}
|
|
1349
|
+
if (!appId) {
|
|
1350
|
+
error("Cloud app not found — gencow.json이 없거나 appId가 설정되지 않았습니다.");
|
|
1351
|
+
info(`${DIM}먼저 gencow deploy 를 실행하세요.${RESET}`);
|
|
1352
|
+
info(`${DIM}로컬 DB에 push하려면: gencow db:push --local${RESET}`);
|
|
1353
|
+
log("");
|
|
1354
|
+
return;
|
|
1355
|
+
}
|
|
1271
1356
|
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
process.exit(1);
|
|
1277
|
-
}
|
|
1357
|
+
// ── Cloud 모드: Platform API를 통해 Cloud DB에 schema push ──
|
|
1358
|
+
const creds = requireCreds();
|
|
1359
|
+
log(`\n${BOLD}${CYAN}Gencow DB Push${RESET} ${DIM}(cloud: ${appId})${RESET}\n`);
|
|
1360
|
+
info("Pushing schema.ts → cloud database...");
|
|
1278
1361
|
|
|
1279
|
-
|
|
1280
|
-
|
|
1362
|
+
// gencow/ 폴더를 tar.gz로 패키징
|
|
1363
|
+
const functionsDir = config.functionsDir || "./gencow";
|
|
1364
|
+
const absoluteFunctions = resolve(process.cwd(), functionsDir);
|
|
1365
|
+
if (!existsSync(absoluteFunctions)) {
|
|
1366
|
+
error(`Functions directory not found: ${functionsDir}`);
|
|
1367
|
+
process.exit(1);
|
|
1368
|
+
}
|
|
1281
1369
|
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
);
|
|
1370
|
+
// schema.ts 존재 확인
|
|
1371
|
+
const schemaPath = resolve(absoluteFunctions, "schema.ts");
|
|
1372
|
+
if (!existsSync(schemaPath)) {
|
|
1373
|
+
error("gencow/schema.ts not found — nothing to push");
|
|
1374
|
+
process.exit(1);
|
|
1375
|
+
}
|
|
1287
1376
|
|
|
1288
|
-
|
|
1289
|
-
|
|
1377
|
+
const tmpBundle = resolve(process.cwd(), ".gencow", "schema-bundle.tar.gz");
|
|
1378
|
+
mkdirSync(dirname(tmpBundle), { recursive: true });
|
|
1290
1379
|
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
method: "POST",
|
|
1297
|
-
headers: { "Content-Type": "application/octet-stream" },
|
|
1298
|
-
body: bundleData,
|
|
1299
|
-
}
|
|
1300
|
-
);
|
|
1380
|
+
// tar.gz 생성: gencow/ 폴더 전체 (schema.ts + import된 파일들)
|
|
1381
|
+
execSync(
|
|
1382
|
+
`tar -czf "${tmpBundle}" -C "${process.cwd()}" "${functionsDir.replace(/^\.\//,'')}/"`,
|
|
1383
|
+
{ stdio: ["ignore", "pipe", "pipe"] }
|
|
1384
|
+
);
|
|
1301
1385
|
|
|
1302
|
-
|
|
1386
|
+
const bundleData = readFileSync(tmpBundle);
|
|
1387
|
+
info(`Bundle: ${(bundleData.length / 1024).toFixed(1)} KB`);
|
|
1303
1388
|
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1389
|
+
try {
|
|
1390
|
+
const res = await platformFetch(
|
|
1391
|
+
creds,
|
|
1392
|
+
`/platform/apps/${appId}/schema-push`,
|
|
1393
|
+
{
|
|
1394
|
+
method: "POST",
|
|
1395
|
+
headers: { "Content-Type": "application/octet-stream" },
|
|
1396
|
+
body: bundleData,
|
|
1307
1397
|
}
|
|
1398
|
+
);
|
|
1308
1399
|
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
error(`
|
|
1313
|
-
info("서버가 실행 중인지 확인하세요.");
|
|
1400
|
+
const data = await res.json().catch(() => ({}));
|
|
1401
|
+
|
|
1402
|
+
if (!res.ok) {
|
|
1403
|
+
error(`Schema push failed: ${data.error || res.statusText}`);
|
|
1314
1404
|
process.exit(1);
|
|
1315
|
-
} finally {
|
|
1316
|
-
// 임시 번들 정리
|
|
1317
|
-
try { unlinkSync(tmpBundle); } catch { /* ignore */ }
|
|
1318
1405
|
}
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
log(`\n${BOLD}${CYAN}Gencow DB Push${RESET} ${DIM}(local)${RESET}\n`);
|
|
1322
|
-
info("Pushing schema.ts → database (no migration files)...");
|
|
1323
|
-
runInServer("pnpm db:push --force", buildEnv(config));
|
|
1324
|
-
success("Schema pushed!");
|
|
1406
|
+
|
|
1407
|
+
success("Schema pushed to cloud DB!");
|
|
1325
1408
|
log(` ${DIM}Tables are in sync with schema.ts${RESET}\n`);
|
|
1409
|
+
} catch (e) {
|
|
1410
|
+
error(`Platform 연결 실패: ${e.message}`);
|
|
1411
|
+
info("서버가 실행 중인지 확인하세요.");
|
|
1412
|
+
process.exit(1);
|
|
1413
|
+
} finally {
|
|
1414
|
+
// 임시 번들 정리
|
|
1415
|
+
try { unlinkSync(tmpBundle); } catch { /* ignore */ }
|
|
1326
1416
|
}
|
|
1327
1417
|
},
|
|
1328
1418
|
|
|
@@ -1458,10 +1548,13 @@ ${BOLD}Dev commands:${RESET}
|
|
|
1458
1548
|
${DIM}--local Use local PGlite instead of cloud${RESET}
|
|
1459
1549
|
${DIM}--verbose Show all HTTP logs (including admin/ws)${RESET}
|
|
1460
1550
|
${GREEN}dashboard${RESET} Open the Gencow admin dashboard in browser
|
|
1461
|
-
${GREEN}db:push${RESET} Sync schema.ts → DB
|
|
1551
|
+
${GREEN}db:push${RESET} Sync schema.ts → cloud DB ${DIM}(default: cloud)${RESET}
|
|
1552
|
+
${DIM}--local Push to local DB instead of cloud${RESET}
|
|
1462
1553
|
${GREEN}db:generate${RESET} Generate SQL migration files from schema.ts
|
|
1463
|
-
${GREEN}db:seed${RESET} Run gencow/seed.ts ${DIM}(
|
|
1464
|
-
|
|
1554
|
+
${GREEN}db:seed${RESET} Run gencow/seed.ts on cloud app ${DIM}(default: cloud)${RESET}
|
|
1555
|
+
${DIM}--local Seed local DB instead of cloud${RESET}
|
|
1556
|
+
${GREEN}db:reset${RESET} TRUNCATE all data + run seed.ts ${DIM}(local only)${RESET}
|
|
1557
|
+
${DIM}--local Required — local DB reset only (cloud: use Dashboard)${RESET}
|
|
1465
1558
|
${GREEN}db:studio${RESET} Open Drizzle Studio ${DIM}(visual DB browser)${RESET}
|
|
1466
1559
|
${GREEN}add <comp...>${RESET} Add components ${DIM}(AI RAG Tools Memory Analytics)${RESET}
|
|
1467
1560
|
${GREEN}mcp${RESET} Start MCP server for AI agent integration ${DIM}(stdio)${RESET}
|
|
@@ -3222,10 +3315,10 @@ process.exit(0);
|
|
|
3222
3315
|
info(`Use ${GREEN}gencow env set KEY=VALUE${RESET} to add one.`);
|
|
3223
3316
|
} else {
|
|
3224
3317
|
const maxLen = Math.max(...vars.map(v => v.key.length), 4);
|
|
3225
|
-
log(` ${DIM}${"NAME".padEnd(maxLen)}
|
|
3226
|
-
log(` ${DIM}${"─".repeat(maxLen)}
|
|
3318
|
+
log(` ${DIM}${"NAME".padEnd(maxLen)}${RESET}`);
|
|
3319
|
+
log(` ${DIM}${"─".repeat(maxLen)}${RESET}`);
|
|
3227
3320
|
for (const v of vars) {
|
|
3228
|
-
log(` ${BOLD}${v.key.padEnd(maxLen)}${RESET}
|
|
3321
|
+
log(` ${BOLD}${v.key.padEnd(maxLen)}${RESET}`);
|
|
3229
3322
|
}
|
|
3230
3323
|
}
|
|
3231
3324
|
log("");
|
|
@@ -3245,7 +3338,7 @@ process.exit(0);
|
|
|
3245
3338
|
for (const pair of pairs) {
|
|
3246
3339
|
const eqIdx = pair.indexOf("=");
|
|
3247
3340
|
if (eqIdx < 1) { error(`Invalid format: ${pair}. Use KEY=VALUE`); continue; }
|
|
3248
|
-
const name = pair.slice(0, eqIdx).trim();
|
|
3341
|
+
const name = pair.slice(0, eqIdx).trim().toUpperCase().replace(/[^A-Z0-9_]/g, "");
|
|
3249
3342
|
const value = pair.slice(eqIdx + 1).trim();
|
|
3250
3343
|
if (!name) { error("Key name cannot be empty"); continue; }
|
|
3251
3344
|
if (!value) { error(`Value for ${name} cannot be empty`); continue; }
|
|
@@ -3274,7 +3367,8 @@ process.exit(0);
|
|
|
3274
3367
|
if (subcmd === "unset" || subcmd === "remove" || subcmd === "delete") {
|
|
3275
3368
|
const keys = filteredArgs.slice(1);
|
|
3276
3369
|
if (keys.length === 0) { error("Usage: gencow env unset KEY [KEY2 ...]"); return; }
|
|
3277
|
-
for (const
|
|
3370
|
+
for (const rawKey of keys) {
|
|
3371
|
+
const key = rawKey.toUpperCase().replace(/[^A-Z0-9_]/g, "");
|
|
3278
3372
|
try {
|
|
3279
3373
|
const res = await platformFetch(creds, `/platform/apps/${appId}/env/${encodeURIComponent(key)}`, {
|
|
3280
3374
|
method: "DELETE",
|
|
@@ -3304,7 +3398,7 @@ process.exit(0);
|
|
|
3304
3398
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
3305
3399
|
const eqIdx = trimmed.indexOf("=");
|
|
3306
3400
|
if (eqIdx < 1) continue;
|
|
3307
|
-
const key = trimmed.slice(0, eqIdx).trim();
|
|
3401
|
+
const key = trimmed.slice(0, eqIdx).trim().toUpperCase().replace(/[^A-Z0-9_]/g, "");
|
|
3308
3402
|
let val = trimmed.slice(eqIdx + 1).trim();
|
|
3309
3403
|
if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
|
|
3310
3404
|
val = val.slice(1, -1);
|