chiwaa 0.1.12 → 0.1.14
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/chiwaa.js +121 -28
- package/package.json +1 -1
package/bin/chiwaa.js
CHANGED
|
@@ -124,32 +124,65 @@ async function login(args) {
|
|
|
124
124
|
console.log(c.green("✓"), `${me.data.user} 계정으로 로그인했습니다.`);
|
|
125
125
|
return;
|
|
126
126
|
}
|
|
127
|
-
|
|
128
|
-
if (
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
127
|
+
// 앞서 시작한 로그인이 아직 살아 있으면 새 코드를 만들지 않고 이어서 기다린다(AI 도구가 대기 중에 명령을 끊는 경우)
|
|
128
|
+
if (await resumePendingLogin()) return;
|
|
129
|
+
let s = readJSON(PENDING_FILE);
|
|
130
|
+
const resumed = !!s;
|
|
131
|
+
if (!s) {
|
|
132
|
+
const r = await api("POST", "/api/v1/login", { auth: false });
|
|
133
|
+
if (r.status !== 200) fail(r.data.error || "로그인을 시작하지 못했습니다.");
|
|
134
|
+
s = { ...r.data, until: Date.now() + r.data.expires_in * 1000 };
|
|
135
|
+
fs.mkdirSync(CONF_DIR, { recursive: true });
|
|
136
|
+
fs.writeFileSync(PENDING_FILE, JSON.stringify(s), { mode: 0o600 });
|
|
137
|
+
}
|
|
138
|
+
console.log(`브라우저에서 아래 주소를 열고 코드를 확인한 뒤 ${c.bold("승인")}을 눌러 주세요.${resumed ? c.dim(" (앞서 연 로그인을 이어서 기다립니다)") : ""}\n`);
|
|
139
|
+
console.log(` ${c.cyan(s.verify_url)}`);
|
|
140
|
+
console.log(` 코드: ${c.bold(s.user_code)}\n`);
|
|
141
|
+
console.log(c.dim(`승인할 때까지 이 명령이 기다립니다(최대 ${Math.ceil((s.until - Date.now()) / 60000)}분). 중간에 끊겼으면 같은 명령을 다시 실행하면 이어집니다 — 다시 승인할 필요 없음.`));
|
|
142
|
+
if (!args["no-browser"] && !resumed) openBrowser(s.verify_url);
|
|
134
143
|
process.stdout.write(c.dim("승인을 기다리는 중…"));
|
|
135
|
-
while (Date.now() < until) {
|
|
136
|
-
await new Promise((r) => setTimeout(r, (s.
|
|
137
|
-
const
|
|
138
|
-
if (
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
if (p.status !== 202) {
|
|
145
|
-
process.stdout.write("\n");
|
|
146
|
-
fail(p.data.error || "로그인하지 못했습니다.");
|
|
147
|
-
}
|
|
144
|
+
while (Date.now() < s.until) {
|
|
145
|
+
await new Promise((r) => setTimeout(r, (s.interval || 3) * 1000));
|
|
146
|
+
const done = await pollLogin(s);
|
|
147
|
+
if (done === "wait") continue;
|
|
148
|
+
process.stdout.write("\n");
|
|
149
|
+
if (done) return console.log(c.green("✓"), `${done} 계정으로 로그인했습니다.`);
|
|
150
|
+
fail("로그인하지 못했습니다. 다시 `chiwaa login` 해 주세요.");
|
|
148
151
|
}
|
|
152
|
+
fs.rmSync(PENDING_FILE, { force: true });
|
|
149
153
|
process.stdout.write("\n");
|
|
150
154
|
fail("시간이 지났습니다. 다시 `chiwaa login` 해 주세요.");
|
|
151
155
|
}
|
|
152
156
|
|
|
157
|
+
// 로그인 승인을 한 번 확인한다: 계정 이름(성공) · "wait"(아직) · null(끝남, 대기 파일 지움)
|
|
158
|
+
const PENDING_FILE = path.join(CONF_DIR, "login-pending.json");
|
|
159
|
+
async function pollLogin(s) {
|
|
160
|
+
const p = await api("POST", "/api/v1/login/poll", { auth: false, body: { device_code: s.device_code } });
|
|
161
|
+
if (p.status === 202) return "wait";
|
|
162
|
+
fs.rmSync(PENDING_FILE, { force: true });
|
|
163
|
+
if (p.status === 200 && p.data.token) {
|
|
164
|
+
saveToken(p.data.token);
|
|
165
|
+
return p.data.user || "내";
|
|
166
|
+
}
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// 승인은 했는데 기다리던 명령이 끊긴 경우: 다음 명령에서 승인 결과를 받아 로그인을 마친다
|
|
171
|
+
async function resumePendingLogin() {
|
|
172
|
+
const s = readJSON(PENDING_FILE);
|
|
173
|
+
if (!s || token()) return false;
|
|
174
|
+
if (!(s.until > Date.now())) {
|
|
175
|
+
fs.rmSync(PENDING_FILE, { force: true });
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
const done = await pollLogin(s);
|
|
179
|
+
if (done && done !== "wait") {
|
|
180
|
+
console.log(c.green("✓"), `앞서 승인한 로그인을 이어받았습니다(${done} 계정).`);
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
|
|
153
186
|
// ---------- tar.gz (의존성 없이 직접 만든다) ----------
|
|
154
187
|
function tarHeader(name, size, mode, type, mtime) {
|
|
155
188
|
const b = Buffer.alloc(512);
|
|
@@ -236,12 +269,20 @@ function buildIfNeeded(dir, args) {
|
|
|
236
269
|
const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
237
270
|
const found = OUTPUTS.find(([n]) => deps[n]);
|
|
238
271
|
let outName = args.dir || (found ? found[1] : "dist");
|
|
239
|
-
|
|
272
|
+
let restore = null;
|
|
273
|
+
if (found && found[0] === "next" && !args.dir) {
|
|
274
|
+
checkNext(dir);
|
|
275
|
+
if (!args["no-build"]) restore = wrapNextConfig(dir);
|
|
276
|
+
}
|
|
240
277
|
if (args["no-build"]) {
|
|
241
278
|
console.log(c.dim("빌드는 건너뜁니다(--no-build)."));
|
|
242
279
|
} else {
|
|
243
|
-
|
|
244
|
-
|
|
280
|
+
try {
|
|
281
|
+
if (!fs.existsSync(path.join(dir, "node_modules"))) run("npm install", dir);
|
|
282
|
+
run("npm run build", dir);
|
|
283
|
+
} finally {
|
|
284
|
+
if (restore) restore();
|
|
285
|
+
}
|
|
245
286
|
}
|
|
246
287
|
let out = path.join(dir, outName);
|
|
247
288
|
if (!fs.existsSync(out)) {
|
|
@@ -289,8 +330,56 @@ function checkNext(dir) {
|
|
|
289
330
|
fail(`이 Next.js 앱은 서버에서 돌아가는 기능을 씁니다(${shown}). chiwaa 는 화면 파일(HTML·JS)을 올리는 곳이라 이대로는 열리지 않습니다.\n` +
|
|
290
331
|
`AI 에게 「이 Next.js 앱을 chiwaa 에 올릴 수 있게 output: 'export' 정적 사이트로 바꾸고, API·middleware 같은 서버 기능은 맨 위 api/ 폴더의 chiwaa 서버 함수로 옮겨 줘. ${AI_GUIDE}」라고 말해 보세요.`);
|
|
291
332
|
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// 서버 기능이 없는 Next.js 는 next.config 를 그대로 두고 이번 빌드만 정적 내보내기로 덧씌운다(빌드 서버 prepare_next 와 같은 덧씌우기).
|
|
336
|
+
// 빌드가 끝나면(실패·Ctrl+C 포함) 원래 설정으로 되돌린다. 되돌리는 함수, 덧씌울 필요가 없으면 null.
|
|
337
|
+
function wrapNextConfig(dir) {
|
|
292
338
|
const conf = ["next.config.js", "next.config.mjs", "next.config.cjs", "next.config.ts"].find((n) => fs.existsSync(path.join(dir, n)));
|
|
293
|
-
if (
|
|
339
|
+
if (conf && /output\s*:\s*['"]export['"]/.test(fs.readFileSync(path.join(dir, conf), "utf8"))) return null;
|
|
340
|
+
const ext = conf ? path.extname(conf) : ".mjs";
|
|
341
|
+
const target = path.join(dir, conf || "next.config.mjs");
|
|
342
|
+
const orig = `next.config.chiwaa-orig${ext}`;
|
|
343
|
+
const body = [
|
|
344
|
+
' const base = (typeof orig === "function" ? await orig(phase, ctx) : await orig) || {};',
|
|
345
|
+
" // cacheComponents·partialPrefetching(PPR)은 정적 내보내기와 함께 쓸 수 없다(Next 16 기본 설정에 들어 있음)",
|
|
346
|
+
" const { cacheComponents, partialPrefetching, ...rest } = base;",
|
|
347
|
+
' return { ...rest, output: "export", images: { ...(rest.images || {}), unoptimized: true } };',
|
|
348
|
+
"",
|
|
349
|
+
].join("\n");
|
|
350
|
+
let text;
|
|
351
|
+
if (!conf) {
|
|
352
|
+
text = 'export default { output: "export", images: { unoptimized: true } };\n';
|
|
353
|
+
} else {
|
|
354
|
+
const pkg = readJSON(path.join(dir, "package.json")) || {};
|
|
355
|
+
const cjs = ext === ".cjs" || (ext === ".js" && pkg.type !== "module");
|
|
356
|
+
const from = ext === ".ts" ? "next.config.chiwaa-orig" : orig;
|
|
357
|
+
text = cjs
|
|
358
|
+
? `// @ts-nocheck
|
|
359
|
+
const orig = require("./${from}");
|
|
360
|
+
module.exports = async (phase, ctx) => {
|
|
361
|
+
${body}};
|
|
362
|
+
`
|
|
363
|
+
: `// @ts-nocheck
|
|
364
|
+
import orig from "./${from}";
|
|
365
|
+
export default async function chiwaa(phase, ctx) {
|
|
366
|
+
${body}}
|
|
367
|
+
`;
|
|
368
|
+
fs.renameSync(target, path.join(dir, orig));
|
|
369
|
+
}
|
|
370
|
+
fs.writeFileSync(target, text);
|
|
371
|
+
let done = false;
|
|
372
|
+
const restore = () => {
|
|
373
|
+
if (done) return;
|
|
374
|
+
done = true;
|
|
375
|
+
fs.rmSync(target, { force: true });
|
|
376
|
+
if (conf) fs.renameSync(path.join(dir, orig), target);
|
|
377
|
+
process.removeListener("SIGINT", onInt);
|
|
378
|
+
};
|
|
379
|
+
const onInt = () => { restore(); process.exit(130); };
|
|
380
|
+
process.on("SIGINT", onInt);
|
|
381
|
+
console.log(c.dim("next.config 는 그대로 두고, 이번 빌드만 정적 사이트로 내보냅니다(output: 'export')."));
|
|
382
|
+
return restore;
|
|
294
383
|
}
|
|
295
384
|
|
|
296
385
|
// 첫 화면(index.html)이 없는 폴더를 올리려 하면, 앱이나 HTML 이 들어 있는 하위 폴더를 알려 주고 멈춘다
|
|
@@ -419,7 +508,7 @@ async function deploy(args) {
|
|
|
419
508
|
const dir = path.resolve(args._[1] || ".");
|
|
420
509
|
if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) fail(`폴더가 없습니다: ${dir}`);
|
|
421
510
|
if (!token()) {
|
|
422
|
-
if (!TTY) fail("로그인이 필요합니다. 먼저 아래 명령으로 로그인하고(브라우저에서 승인), 같은 배포 명령을 다시 실행하세요.\n\n npx chiwaa login\n\n (
|
|
511
|
+
if (!TTY) fail("로그인이 필요합니다. 먼저 아래 명령으로 로그인하고(브라우저에서 승인), 같은 배포 명령을 다시 실행하세요.\n\n npx chiwaa login\n\n (login 은 승인할 때까지 기다리는 명령입니다. 끊겼으면 다시 실행하면 같은 승인을 이어서 기다립니다. 자동화에서는 환경변수 CHIWAA_TOKEN 으로도 됩니다)");
|
|
423
512
|
await login(args);
|
|
424
513
|
}
|
|
425
514
|
const site = await resolveSite(dir, args);
|
|
@@ -432,7 +521,7 @@ async function deploy(args) {
|
|
|
432
521
|
files.push({ rel: "api", dir: true }, ...extra.files.map((f) => ({ ...f, rel: `api/${f.rel}` })));
|
|
433
522
|
}
|
|
434
523
|
const fnCount = files.filter((f) => !f.dir && /^api\/[^/_][^/]*\.(js|ts|mjs)$/.test(f.rel)).length;
|
|
435
|
-
if (fnCount) console.log(c.dim(`서버 함수 ${fnCount}개(api/) 함께 올림 → https://<사이트>/api
|
|
524
|
+
if (fnCount) console.log(c.dim(`서버 함수 ${fnCount}개(api/) 함께 올림 → https://<사이트>/api/<이름> (서버 함수는 1분 안에 켜집니다 — 그 전에는 404 일 수 있음)`));
|
|
436
525
|
if (!files.some((f) => !f.dir && f.rel === "index.html")) fail(noIndexMessage(out));
|
|
437
526
|
const fileCount = files.filter((f) => !f.dir).length;
|
|
438
527
|
console.log(c.dim(`올리는 중: ${path.relative(process.cwd(), out) || "."} — 파일 ${fileCount}개, ${(total / 1048576).toFixed(1)}MB`));
|
|
@@ -513,7 +602,7 @@ function snippet(table, read, write) {
|
|
|
513
602
|
<button type="submit">보내기</button>
|
|
514
603
|
</form>
|
|
515
604
|
|
|
516
|
-
|
|
605
|
+
${read === "public" ? `<!-- 글 목록 보기 -->
|
|
517
606
|
<ul id="${table}-list"></ul>
|
|
518
607
|
<script>
|
|
519
608
|
chiwaa.table("${table}").list({ limit: 20 }).then(function (items) {
|
|
@@ -526,7 +615,10 @@ function snippet(table, read, write) {
|
|
|
526
615
|
});
|
|
527
616
|
</script>
|
|
528
617
|
|
|
529
|
-
|
|
618
|
+
` : `<!-- 읽기가 「나만」이라 받은 글을 사이트에 보여 주지 않습니다(목록을 붙이지 마세요).
|
|
619
|
+
받은 글 보기: chiwaa.kr → 내 사이트 → 이 사이트 「자세히」 → 「DB」 → ${table} 줄의 「데이터 보기」, 또는 npx chiwaa db rows ${table} -->
|
|
620
|
+
|
|
621
|
+
`}<!-- 스크립트로 직접:
|
|
530
622
|
chiwaa.table("${table}").add({ 제목, 내용, _pw }) 글쓰기(_pw 를 넣으면 그 비밀번호로 수정·삭제 가능)
|
|
531
623
|
chiwaa.table("${table}").list({ limit: 20, before: 마지막id }) 목록(최신순, 다음 쪽은 before)
|
|
532
624
|
chiwaa.table("${table}").get(id) 글 하나(상세 화면)
|
|
@@ -963,6 +1055,7 @@ async function main() {
|
|
|
963
1055
|
if (args.help || !cmd || cmd === "help") return console.log(HELP);
|
|
964
1056
|
const cmds = { deploy, login, list, ls: list, versions, rollback, db, files, domains, domain: domains, stats, env, logs, lock, unlock, open, usage, agent, whoami, logout };
|
|
965
1057
|
if (!cmds[cmd]) fail(`모르는 명령입니다: ${cmd}\n\n${HELP}`);
|
|
1058
|
+
if (cmd !== "login" && cmd !== "logout") await resumePendingLogin();
|
|
966
1059
|
await cmds[cmd](args);
|
|
967
1060
|
}
|
|
968
1061
|
|