chiwaa 0.1.17 → 0.1.18

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
@@ -14,6 +14,8 @@ npx chiwaa deploy --preview # 미리보기 주소
14
14
  | `vercel logs` · `vercel rollback` | `npx chiwaa logs` · `npx chiwaa rollback` |
15
15
  | `vercel domains add` | `npx chiwaa domains add` |
16
16
 
17
+ 서버 기능 있는 Next.js·Express(`npm start`)·Dockerfile 앱은 소스를 보내 컨테이너로 실행합니다(앱은 `PORT`=3000 에서 듣기). 기록은 `npx chiwaa logs -f`.
18
+
17
19
  AI 에이전트(Claude Code·Codex·Cursor)는 https://chiwaa.kr/llms.txt 를 읽고 배포합니다. CI 에서는 환경변수 `CHIWAA_TOKEN`.
18
20
 
19
21
  처음이면 브라우저에서 로그인을 승인하고 사이트 이름을 정합니다. 끝나면 `<이름>.chiwaa.com` 주소가 나옵니다.
package/bin/chiwaa.js CHANGED
@@ -241,6 +241,70 @@ function collect(root, skipDotEnv) {
241
241
  return { files, total };
242
242
  }
243
243
 
244
+ // ---------- 컨테이너(서버 앱) ----------
245
+ // 빌드 서버 chiwaa-deployd 의 판단과 같다: 서버 기능 Next.js · 정적 빌드 도구가 아닌 Node 서버(start) · 정적 결과 없는 Dockerfile 앱
246
+ function containerKind(dir) {
247
+ const pkg = readJSON(path.join(dir, "package.json"));
248
+ const deps = pkg ? { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) } : {};
249
+ const scripts = (pkg && pkg.scripts) || {};
250
+ if (deps.next && nextServerFeatures(dir).length) return "next";
251
+ if (fs.existsSync(path.join(dir, "index.html"))) return null;
252
+ const staticTool = OUTPUTS.some(([n]) => deps[n]);
253
+ if (!staticTool && fs.existsSync(path.join(dir, "Dockerfile"))) return "docker";
254
+ if (!staticTool && String(scripts.start || "").trim()) return "node";
255
+ if (["requirements.txt", "pyproject.toml", "Pipfile", "manage.py"].some((f) => fs.existsSync(path.join(dir, f))))
256
+ fail(`파이썬 앱은 폴더 맨 위에 Dockerfile 을 넣어 주세요. 앱은 환경변수 PORT(3000)에서 들어야 합니다.\n` +
257
+ `AI 에게 「이 파이썬 앱을 chiwaa 컨테이너로 올릴 수 있게 Dockerfile 을 만들어 줘(PORT 환경변수 사용). ${AI_GUIDE}」라고 말해 보세요.`);
258
+ return null;
259
+ }
260
+
261
+ // .gitignore 의 단순한 줄(이름·폴더/·*.확장자)을 따른다. 소스를 올릴 때 빼는 것.
262
+ const SOURCE_SKIP = new Set([...SKIP, ".next", "out", ".turbo", ".cache", ".DS_Store"]);
263
+ function ignoreRules(dir) {
264
+ const p = path.join(dir, ".gitignore");
265
+ if (!fs.existsSync(p)) return () => false;
266
+ const rules = fs.readFileSync(p, "utf8").split(/\r?\n/).map((l) => l.trim()).filter((l) => l && !l.startsWith("#") && !l.startsWith("!"));
267
+ const names = new Set(rules.filter((r) => !r.includes("*")).map((r) => r.replace(/^\/+|\/+$/g, "")).filter((r) => !r.includes("/")));
268
+ const exts = rules.filter((r) => /^\*\.[\w.]+$/.test(r)).map((r) => r.slice(1));
269
+ return (name) => names.has(name) || exts.some((x) => name.endsWith(x));
270
+ }
271
+ function collectSource(root) {
272
+ const ignored = ignoreRules(root);
273
+ const files = [];
274
+ let total = 0;
275
+ const walk = (d, rel) => {
276
+ for (const e of fs.readdirSync(d, { withFileTypes: true })) {
277
+ if (SOURCE_SKIP.has(e.name) || ignored(e.name) || /^\.env(\..*)?$/.test(e.name) || e.isSymbolicLink()) continue;
278
+ const abs = path.join(d, e.name);
279
+ const r = rel ? `${rel}/${e.name}` : e.name;
280
+ if (e.isDirectory()) { files.push({ rel: r, dir: true }); walk(abs, r); }
281
+ else if (e.isFile()) { const st = fs.statSync(abs); total += st.size; files.push({ rel: r, abs, size: st.size, mtime: Math.floor(st.mtimeMs / 1000) }); }
282
+ }
283
+ };
284
+ walk(root, "");
285
+ return { files, total };
286
+ }
287
+
288
+ async function deployContainer(dir, site, kind, args) {
289
+ const label = { next: "Next.js 서버", node: "Node 서버(npm start)", docker: "Dockerfile" }[kind];
290
+ const { files, total } = collectSource(dir);
291
+ const fileCount = files.filter((f) => !f.dir).length;
292
+ console.log(`${c.bold("컨테이너")}로 올립니다 — ${label}. 소스 ${fileCount}개(${(total / 1048576).toFixed(1)}MB)를 보내면 chiwaa 빌드 서버가 빌드합니다(보통 1~3분).`);
293
+ console.log(c.dim("node_modules·.git·.next·.env*·.gitignore 에 적힌 것은 보내지 않습니다. 앱은 환경변수 PORT(3000)에서 들어야 합니다."));
294
+ const gz = makeTarGz(files);
295
+ const t0 = Date.now();
296
+ const preview = !!args.preview;
297
+ const r = await api("POST", `/api/v1/sites/${encodeURIComponent(site.name)}/deploy?source=1${preview ? "&preview=1" : ""}`, {
298
+ body: gz,
299
+ headers: { "Content-Type": "application/gzip" },
300
+ });
301
+ if (r.status !== 200 || !r.data.ok) fail((r.data.message || r.data.error || `배포하지 못했습니다 (${r.status}).`) + `\n기록: npx chiwaa logs${preview ? " --preview" : ""}`);
302
+ console.log(c.green("✓"), `${preview ? "미리보기 " : ""}배포 완료 ${c.dim(`(${((Date.now() - t0) / 1000).toFixed(0)}초)`)}`);
303
+ console.log(c.dim(r.data.message || ""));
304
+ console.log(`\n ${c.bold(c.cyan(r.data.url))}\n`);
305
+ console.log(c.dim("앱 기록: npx chiwaa logs -f"));
306
+ }
307
+
244
308
  function makeTarGz(files) {
245
309
  const parts = [];
246
310
  const now = Math.floor(Date.now() / 1000);
@@ -328,9 +392,10 @@ function checkNext(dir) {
328
392
  const server = nextServerFeatures(dir);
329
393
  if (server.length) {
330
394
  const shown = server.slice(0, 4).join(", ") + (server.length > 4 ? ` 등 ${server.length}곳` : "");
331
- fail(`이 Next.js 앱은 서버에서 돌아가는 기능을 씁니다(${shown}). CLI 로는 아직 서버 코드를 올릴 수 없습니다.\n` +
332
- `· 그대로 돌리려면: 깃허브 저장소를 chiwaa.kr 에 연결하세요. 서버 기능이 있는 Next.js 를 컨테이너로 실행합니다(https://chiwaa.kr/help).\n` +
333
- `· CLI 로 올리려면: AI 에게 「이 Next.js 앱을 output: 'export' 정적 사이트로 바꾸고, 서버 기능은 맨 위 api/ 폴더의 chiwaa 서버 함수로 옮겨 줘. ${AI_GUIDE}」라고 말해 보세요.`);
395
+ // --dir·--no-build 로 결과물만 올리려 할 때만 여기 온다(그냥 deploy 하면 컨테이너로 올라감)
396
+ fail(`이 Next.js 앱은 서버에서 돌아가는 기능을 씁니다(${shown}). --dir·--no-build 로는 결과물(정적 파일)만 올라가 서버 기능이 빠집니다.\n` +
397
+ `· 그대로 돌리려면: 옵션 없이 \`npx chiwaa deploy\` — 컨테이너로 올라갑니다.\n` +
398
+ `· 정적 사이트로 바꾸려면: AI 에게 「이 Next.js 앱을 output: 'export' 정적 사이트로 바꾸고, 서버 기능은 맨 위 api/ 폴더의 chiwaa 서버 함수로 옮겨 줘. ${AI_GUIDE}」라고 말해 보세요.`);
334
399
  }
335
400
  }
336
401
 
@@ -464,7 +529,7 @@ function agentSection(name) {
464
529
 
465
530
  - 배포: \`npx chiwaa deploy -y\` — 끝나면 주소가 나온다. 잘못 올렸으면 \`npx chiwaa rollback\`.
466
531
  - 이 폴더는 \`.chiwaa.json\` 으로 사이트가 정해져 있다. 다른 이름(--name)으로 올리지 않는다.
467
- - CLI 로는 정적 사이트만 된다(HTML·CSS·JS, Vite·React·Vue·Astro·Next export 빌드) + 서버 함수(api/). Next.js SSR·Express·Dockerfile 같은 서버 코드는 깃허브 저장소를 연결하면 컨테이너로 돈다. PHP 는 안 된다.
532
+ - 정적 사이트(HTML·CSS·JS, Vite·React·Vue·Astro·Next export)와 서버 함수(api/)는 결과물을 올리고, 서버 기능 Next.js·Express(start)·Dockerfile 앱은 소스를 보내 컨테이너로 돈다(요금제별 개수). 앱은 PORT(3000)에서 듣는다. 기록은 \`npx chiwaa logs -f\`. PHP 는 안 된다.
468
533
  - 글 저장(문의·방명록·게시판)은 사이트 DB: \`npx chiwaa db on\` → \`npx chiwaa db create <표> --read public|owner --write public|owner\`.
469
534
  - 문의·신청서처럼 개인정보가 든 표는 반드시 \`--read owner\`. 사이트에서 안 읽힌다고 public 으로 바꾸지 않는다.
470
535
  - 방문자 글은 화면에 \`textContent\` 로만 넣는다. 비밀번호는 \`<input type="password">\` 로 받는다.
@@ -514,6 +579,8 @@ async function deploy(args) {
514
579
  await login(args);
515
580
  }
516
581
  const site = await resolveSite(dir, args);
582
+ const ckind = args["no-build"] || args.dir ? null : containerKind(dir);
583
+ if (ckind) return deployContainer(dir, site, ckind, args);
517
584
  const { out, built } = buildIfNeeded(dir, args);
518
585
  const { files, total } = collect(out, !built);
519
586
  // 서버 함수: 프로젝트 맨 위 api/ 는 빌드 결과에 안 들어가므로 따로 넣는다(서버가 사이트 파일에서 떼어 서버 함수로 돌린다)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chiwaa",
3
- "version": "0.1.17",
3
+ "version": "0.1.18",
4
4
  "description": "chiwaa.kr 에 사이트를 터미널에서 바로 올리는 도구",
5
5
  "bin": {
6
6
  "chiwaa": "bin/chiwaa.js"