chiwaa 0.1.6 → 0.1.8

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/README.md CHANGED
@@ -22,6 +22,8 @@ npx chiwaa deploy
22
22
  | `chiwaa domains` · `search` · `buy` · `add` · `check` | x-y.net 도메인 찾기·사기(결제는 x-y.net 결제창에서 직접)·붙이기 |
23
23
  | `chiwaa stats` | 방문자 통계(오늘·최근 7일·많이 본 페이지·들어온 곳) |
24
24
  | `chiwaa deploy --preview` | 운영 주소는 두고 `이름--preview.chiwaa.com` 에 미리보기 |
25
+ | `chiwaa env` · `env set 이름` · `env rm 이름` | 서버 함수(`api/*.js`) 비밀값 — 값은 화면에 안 보이게 입력 |
26
+ | `chiwaa logs` | 서버 함수 기록(오류·console.log) |
25
27
  | `chiwaa lock` / `unlock` | 사이트 비밀번호 잠금(공개 전 나만 보기) |
26
28
  | `chiwaa open` · `usage` | 관리 화면 열기 · 이번 달 사용량 |
27
29
  | `chiwaa agent` | AI 도구용 안내(AGENTS.md·CLAUDE.md) 만들기 — 처음 deploy 때 자동 |
package/bin/chiwaa.js CHANGED
@@ -92,7 +92,7 @@ function parseArgs(argv) {
92
92
  if (a.startsWith("--")) {
93
93
  const [k, v] = a.slice(2).split("=", 2);
94
94
  if (v !== undefined) out[k] = v;
95
- else if (["name", "token", "dir", "read", "write", "page", "days"].includes(k) && argv[i + 1] && !argv[i + 1].startsWith("-")) out[k] = argv[++i];
95
+ else if (["name", "token", "dir", "read", "write", "page", "days", "lines"].includes(k) && argv[i + 1] && !argv[i + 1].startsWith("-")) out[k] = argv[++i];
96
96
  else out[k] = true;
97
97
  } else if (a === "-y") out.yes = true;
98
98
  else if (a === "-h") out.help = true;
@@ -373,6 +373,14 @@ async function deploy(args) {
373
373
  const site = await resolveSite(dir, args);
374
374
  const { out, built } = buildIfNeeded(dir, args);
375
375
  const { files, total } = collect(out, !built);
376
+ // 서버 함수: 프로젝트 맨 위 api/ 는 빌드 결과에 안 들어가므로 따로 넣는다(서버가 사이트 파일에서 떼어 서버 함수로 돌린다)
377
+ const apiDir = path.join(dir, "api");
378
+ if (built && fs.existsSync(apiDir) && fs.statSync(apiDir).isDirectory() && !files.some((f) => f.rel === "api")) {
379
+ const extra = collect(apiDir, true);
380
+ files.push({ rel: "api", dir: true }, ...extra.files.map((f) => ({ ...f, rel: `api/${f.rel}` })));
381
+ }
382
+ const fnCount = files.filter((f) => !f.dir && /^api\/[^/_][^/]*\.(js|ts|mjs)$/.test(f.rel)).length;
383
+ if (fnCount) console.log(c.dim(`서버 함수 ${fnCount}개(api/) 함께 올림 → https://<사이트>/api/<이름>`));
376
384
  if (!files.some((f) => !f.dir && /^index\.html?$/i.test(f.rel)))
377
385
  console.log(c.dim("참고: 맨 위에 index.html 이 없습니다. 주소로 열면 첫 화면이 안 보일 수 있습니다."));
378
386
  const fileCount = files.filter((f) => !f.dir).length;
@@ -662,6 +670,60 @@ async function stats(args) {
662
670
  if (!s.total.views) console.log(c.dim("\n 아직 집계된 방문이 없습니다."));
663
671
  }
664
672
 
673
+ // ---------- logs (서버 함수 기록) ----------
674
+ async function logs(args) {
675
+ const name = await siteName(args);
676
+ const n = Math.min(Math.max(parseInt(args.lines, 10) || 30, 1), 500);
677
+ const r = await api("GET", `/api/v1/sites/${encodeURIComponent(name)}/logs?lines=${n}${args.preview ? "&preview=1" : ""}`);
678
+ if (r.status !== 200) fail(r.data.error || "기록을 받지 못했습니다.");
679
+ const d = r.data;
680
+ console.log(c.bold(`${d.site} 서버 함수 기록`), c.dim(`(최근 ${n}건 · 1분쯤 늦을 수 있음)`));
681
+ if (d.functions.length) console.log(c.dim(` 함수: ${d.functions.map((f) => "/api/" + f).join(" ")}`));
682
+ if (!d.entries.length) return console.log(c.dim(" 아직 기록이 없습니다. 오류가 나거나 console.log 를 쓰면 여기에 남습니다."));
683
+ for (const e of d.entries) {
684
+ const when = new Date(e.t);
685
+ const p2 = (x) => String(x).padStart(2, "0");
686
+ const t = isNaN(when) ? e.t : `${p2(when.getMonth() + 1)}-${p2(when.getDate())} ${p2(when.getHours())}:${p2(when.getMinutes())}:${p2(when.getSeconds())}`;
687
+ const st = e.status >= 500 ? c.red(String(e.status)) : String(e.status);
688
+ console.log(`${c.dim(t)} /api/${e.fn} ${st}${e.ms != null ? c.dim(` ${e.ms}ms`) : ""}${e.error ? " " + c.red(e.error) : ""}`);
689
+ for (const l of e.logs || []) console.log(` ${l}`);
690
+ }
691
+ }
692
+
693
+ // ---------- env (서버 함수 비밀값) ----------
694
+ async function env(args) {
695
+ const sub = args._[1] || "ls";
696
+ const name = await siteName(args);
697
+ const base = `/api/v1/sites/${encodeURIComponent(name)}/env`;
698
+ if (sub === "ls") {
699
+ const r = await api("GET", base);
700
+ if (r.status !== 200) fail(r.data.error || "비밀값 목록을 받지 못했습니다.");
701
+ const d = r.data;
702
+ console.log(c.bold(`${name}.chiwaa.com 서버 함수 비밀값`), c.dim(`(${d.names.length}/${d.limit}개 · 값은 다시 보여 주지 않음)`));
703
+ for (const n of d.names) console.log(` ${n}`);
704
+ if (!d.names.length) console.log(c.dim(" 없음. 넣기: npx chiwaa env set 이름 (값은 화면에 안 보이게 입력)"));
705
+ console.log(c.dim(`\n이번 달 서버 함수 실행 ${Number(d.calls_this_month).toLocaleString("ko-KR")}회 · 마지막 실행 ${d.last_used_at ? d.last_used_at.slice(0, 10) : "없음"}`));
706
+ if (d.idle_90d) console.log(c.red("⚠ 90일 넘게 서버 함수를 안 썼습니다. 안 쓰는 비밀값(결제 키 등)은 지우는 게 안전합니다: npx chiwaa env rm 이름"));
707
+ return;
708
+ }
709
+ const key = args._[2];
710
+ if (!key) fail(`이름을 적어 주세요. 예: npx chiwaa env ${sub} TOSS_SECRET_KEY`);
711
+ if (sub === "set") {
712
+ if (!/^[A-Z][A-Z0-9_]{0,63}$/.test(key)) fail("이름은 영문 대문자로 시작하고 대문자·숫자·밑줄 64자까지입니다. 예: TOSS_SECRET_KEY");
713
+ const value = await readSecret(`${key} 값(화면에 안 보임): `);
714
+ if (!value) fail("값이 비어 있습니다. (AI·자동화에서는 표준입력으로: echo 값 | npx chiwaa env set 이름)");
715
+ const r = await api("POST", base, { body: { name: key, value } });
716
+ if (r.status !== 200) fail(r.data.error || "넣지 못했습니다.");
717
+ return console.log(c.green("✓"), r.data.message);
718
+ }
719
+ if (sub === "rm") {
720
+ const r = await api("POST", `${base}/remove`, { body: { name: key } });
721
+ if (r.status !== 200) fail(r.data.error || "지우지 못했습니다.");
722
+ return console.log(c.green("✓"), r.data.message);
723
+ }
724
+ fail(`모르는 env 명령입니다: ${sub}\n 쓸 수 있는 것: ls · set · rm`);
725
+ }
726
+
665
727
  // ---------- lock / unlock / open / usage ----------
666
728
  function readSecret(prompt) {
667
729
  if (!process.stdin.isTTY) {
@@ -762,6 +824,8 @@ const HELP = `chiwaa ${VERSION} — chiwaa.kr 에 사이트를 바로 올립니
762
824
  chiwaa domains add <도메인> 도메인 붙이기(x-y.net 도메인은 바로, 다른 곳 도메인은 넣을 DNS 레코드 안내)
763
825
  chiwaa domains check 연결됐는지 확인 · domains remove -y 로 연결 끊기
764
826
  chiwaa stats 방문자 통계(오늘·최근 7일, 많이 본 페이지, 들어온 곳) --days 30
827
+ chiwaa logs 서버 함수 기록(오류·console.log) --lines 100, --preview
828
+ chiwaa env 서버 함수 비밀값 목록 · env set 이름(값은 안 보이게 입력) · env rm 이름
765
829
  chiwaa lock / unlock 사이트 비밀번호 잠금(아이디 chiwaa) / 풀기 — 공개 전 나만 보기
766
830
  chiwaa open 관리 화면 열기 (--site 는 사이트 열기)
767
831
  chiwaa usage 이번 달 전송량·한도, 요금제
@@ -778,7 +842,7 @@ async function main() {
778
842
  const cmd = args._[0];
779
843
  if (args.version) return console.log(VERSION);
780
844
  if (args.help || !cmd || cmd === "help") return console.log(HELP);
781
- const cmds = { deploy, login, list, ls: list, versions, rollback, db, domains, domain: domains, stats, lock, unlock, open, usage, agent, whoami, logout };
845
+ const cmds = { deploy, login, list, ls: list, versions, rollback, db, domains, domain: domains, stats, env, logs, lock, unlock, open, usage, agent, whoami, logout };
782
846
  if (!cmds[cmd]) fail(`모르는 명령입니다: ${cmd}\n\n${HELP}`);
783
847
  await cmds[cmd](args);
784
848
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chiwaa",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "chiwaa.kr 에 사이트를 터미널에서 바로 올리는 도구",
5
5
  "bin": {
6
6
  "chiwaa": "bin/chiwaa.js"