@zalkera/cli 0.31.0 → 0.32.0
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 +1 -1
- package/dist/main.js +88 -31
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ zalkera publish # 올린 것을 새 버전으로 만든다(그래
|
|
|
31
31
|
zalkera rollback <판번호> # 라이브를 그 버전으로 되돌린다
|
|
32
32
|
zalkera discard # 사이트 쪽에서 편집 중인 것을 버린다(판은 안 옮긴다)
|
|
33
33
|
zalkera baseline # 기준 기록만 다시 세운다(파일은 안 건드린다)
|
|
34
|
-
zalkera logout
|
|
34
|
+
zalkera logout # 로그인 정보를 지우고 이 컴퓨터의 서버 쪽 로그인도 끊는다
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
`--site` 는 **폴더가 소속을 모를 때만** 필요하다. 한 번 받으면 폴더가 그것을 기억한다.
|
package/dist/main.js
CHANGED
|
@@ -552,6 +552,7 @@ function escapeHtml(value) {
|
|
|
552
552
|
// ../core/dist/auth/oauth.js
|
|
553
553
|
init_errors();
|
|
554
554
|
var REFRESH_SKEW_MS = 2e4;
|
|
555
|
+
var REVOKE_TIMEOUT_MS = 5e3;
|
|
555
556
|
async function login(config, store, options) {
|
|
556
557
|
const pkce = createPkce();
|
|
557
558
|
const state = createState();
|
|
@@ -633,7 +634,30 @@ async function refreshToken(config, store) {
|
|
|
633
634
|
}
|
|
634
635
|
async function logout(store) {
|
|
635
636
|
logoutEpoch.set(store, epochOf(store) + 1);
|
|
637
|
+
const current = await store.read().catch(() => null);
|
|
636
638
|
await store.clear();
|
|
639
|
+
const serverEnded = current?.clientId ? revokeRefreshToken(current, current.clientId) : Promise.resolve(false);
|
|
640
|
+
return { serverEnded };
|
|
641
|
+
}
|
|
642
|
+
async function revokeRefreshToken(tokens, clientId) {
|
|
643
|
+
const issuer = httpUrl(tokens.issuer);
|
|
644
|
+
if (!issuer)
|
|
645
|
+
return false;
|
|
646
|
+
try {
|
|
647
|
+
const response = await fetch(`${trimSlash(issuer.href)}/protocol/openid-connect/revoke`, {
|
|
648
|
+
method: "POST",
|
|
649
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
650
|
+
body: new URLSearchParams({
|
|
651
|
+
client_id: clientId,
|
|
652
|
+
token: tokens.refreshToken,
|
|
653
|
+
token_type_hint: "refresh_token"
|
|
654
|
+
}),
|
|
655
|
+
signal: AbortSignal.timeout(REVOKE_TIMEOUT_MS)
|
|
656
|
+
});
|
|
657
|
+
return response.ok;
|
|
658
|
+
} catch {
|
|
659
|
+
return false;
|
|
660
|
+
}
|
|
637
661
|
}
|
|
638
662
|
async function exchange(config, params) {
|
|
639
663
|
const body = new URLSearchParams({ client_id: config.clientId, ...params });
|
|
@@ -654,7 +678,8 @@ async function exchange(config, params) {
|
|
|
654
678
|
accessToken: token.access_token,
|
|
655
679
|
refreshToken: token.refresh_token,
|
|
656
680
|
expiresAt: Date.now() + token.expires_in * 1e3,
|
|
657
|
-
issuer: config.issuer
|
|
681
|
+
issuer: config.issuer,
|
|
682
|
+
clientId: config.clientId
|
|
658
683
|
};
|
|
659
684
|
}
|
|
660
685
|
async function safeText(response) {
|
|
@@ -2431,6 +2456,32 @@ async function startDevServerWithEnv(options, storefrontKey, envName) {
|
|
|
2431
2456
|
|
|
2432
2457
|
// ../core/dist/fetchSource.js
|
|
2433
2458
|
import { createHash as createHash6 } from "node:crypto";
|
|
2459
|
+
|
|
2460
|
+
// ../core/dist/emptyDir.js
|
|
2461
|
+
import { basename as basename2, dirname as dirname2, join as join13 } from "node:path";
|
|
2462
|
+
var OS_JUNK = /* @__PURE__ */ new Set([
|
|
2463
|
+
".DS_Store",
|
|
2464
|
+
// macOS
|
|
2465
|
+
"Thumbs.db",
|
|
2466
|
+
// Windows
|
|
2467
|
+
"desktop.ini"
|
|
2468
|
+
// Windows
|
|
2469
|
+
]);
|
|
2470
|
+
var IGNORED = /* @__PURE__ */ new Set([
|
|
2471
|
+
".vscode",
|
|
2472
|
+
// 편집기·확장이 만든다(우리가 만드는 쪽이다)
|
|
2473
|
+
...OS_JUNK
|
|
2474
|
+
]);
|
|
2475
|
+
var OWN_STATE_FILES = [SOURCE_MARK_PATH, SYNC_LEDGER_PATH];
|
|
2476
|
+
var OWN_STATE_DIR = (() => {
|
|
2477
|
+
const dirs = new Set(OWN_STATE_FILES.map((p) => dirname2(p)));
|
|
2478
|
+
if (dirs.size !== 1)
|
|
2479
|
+
throw new Error(`\uC790\uAE30 \uC0C1\uD0DC \uD30C\uC77C\uC774 \uD55C \uD3F4\uB354\uC5D0 \uC788\uC9C0 \uC54A\uB2E4: ${[...dirs].join(", ")}`);
|
|
2480
|
+
return [...dirs][0];
|
|
2481
|
+
})();
|
|
2482
|
+
var OWN_STATE_NAMES = new Set(OWN_STATE_FILES.map((p) => basename2(p)));
|
|
2483
|
+
|
|
2484
|
+
// ../core/dist/fetchSource.js
|
|
2434
2485
|
init_errors();
|
|
2435
2486
|
|
|
2436
2487
|
// ../core/dist/fetchTarget.js
|
|
@@ -2540,7 +2591,7 @@ init_notice();
|
|
|
2540
2591
|
// ../core/dist/npmChoice.js
|
|
2541
2592
|
import { execFileSync } from "node:child_process";
|
|
2542
2593
|
import { existsSync as existsSync7, realpathSync } from "node:fs";
|
|
2543
|
-
import { delimiter as delimiter2, isAbsolute as isAbsolute2, join as
|
|
2594
|
+
import { delimiter as delimiter2, isAbsolute as isAbsolute2, join as join14, normalize as normalize3, sep as sep5 } from "node:path";
|
|
2544
2595
|
var MIN_SYSTEM_NPM_MAJOR = 9;
|
|
2545
2596
|
function majorOf(version2) {
|
|
2546
2597
|
const m = /^(\d+)\./.exec((version2 ?? "").trim());
|
|
@@ -2603,7 +2654,7 @@ function npmArgvOf(choice, nodePath) {
|
|
|
2603
2654
|
return null;
|
|
2604
2655
|
}
|
|
2605
2656
|
}
|
|
2606
|
-
var PATH_OPS = { join:
|
|
2657
|
+
var PATH_OPS = { join: join14, isAbsolute: isAbsolute2, normalize: normalize3, sep: sep5 };
|
|
2607
2658
|
function probeSystemNpm(nodePath, excludeUnder = [], env = process.env) {
|
|
2608
2659
|
const entries = (env.PATH ?? "").split(delimiter2);
|
|
2609
2660
|
for (const step of systemNpmSearchSteps(entries, PATH_OPS, excludeUnder)) {
|
|
@@ -2682,12 +2733,12 @@ var UPGRADE_NOTICE_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
|
2682
2733
|
|
|
2683
2734
|
// ../core/dist/tokenPath.js
|
|
2684
2735
|
import { homedir as homedir2 } from "node:os";
|
|
2685
|
-
import { isAbsolute as isAbsolute3, join as
|
|
2736
|
+
import { isAbsolute as isAbsolute3, join as join15, resolve as resolve5, sep as sep6 } from "node:path";
|
|
2686
2737
|
function tokenPath(env = process.env, home = homedir2()) {
|
|
2687
|
-
const fallback =
|
|
2738
|
+
const fallback = join15(home, ".config");
|
|
2688
2739
|
const base = env.XDG_CONFIG_HOME;
|
|
2689
2740
|
const inside = base !== void 0 && isAbsolute3(base) && (resolve5(base) === resolve5(home) || resolve5(base).startsWith(resolve5(home) + sep6));
|
|
2690
|
-
return
|
|
2741
|
+
return join15(inside ? resolve5(base) : fallback, "zalkera", "auth.json");
|
|
2691
2742
|
}
|
|
2692
2743
|
|
|
2693
2744
|
// ../core/dist/index.js
|
|
@@ -2809,7 +2860,7 @@ function trimPaths(paths, cap = PATH_LIST_CAP, all = false) {
|
|
|
2809
2860
|
// ../core/dist/pull.js
|
|
2810
2861
|
import { lstat as lstat5, mkdir as mkdir5, readFile as readFile6, realpath as realpath2, rename as rename2, rmdir, unlink } from "node:fs/promises";
|
|
2811
2862
|
init_errors();
|
|
2812
|
-
import { basename as
|
|
2863
|
+
import { basename as basename3, dirname as dirname3, join as join16, relative as relative4, resolve as resolve6, sep as sep7 } from "node:path";
|
|
2813
2864
|
var SAVED_SUFFIX = ".zalkera-saved";
|
|
2814
2865
|
async function pullSiteSource(options) {
|
|
2815
2866
|
const report = options.onProgress ?? (() => {
|
|
@@ -2841,7 +2892,7 @@ async function pullSiteSource(options) {
|
|
|
2841
2892
|
const aside = await setAside(root, plan);
|
|
2842
2893
|
savedTo = aside.folder;
|
|
2843
2894
|
movedAway = aside.moved;
|
|
2844
|
-
report(`\uACE0\uCE5C \uB0B4\uC6A9\uC744 ${
|
|
2895
|
+
report(`\uACE0\uCE5C \uB0B4\uC6A9\uC744 ${basename3(savedTo)} \uC5D0 \uC62E\uACA8 \uB450\uC5C8\uC2B5\uB2C8\uB2E4.`);
|
|
2845
2896
|
}
|
|
2846
2897
|
const todo = savedTo ? applyAfterDiscard(plan, incoming) : { writes: plan.writes, deletes: plan.deletes };
|
|
2847
2898
|
await requireNoLinkedTargets(root, todo.writes);
|
|
@@ -2908,7 +2959,7 @@ async function requireNoLinkedTargets(root, paths) {
|
|
|
2908
2959
|
const segments = path.split("/").filter((s) => s !== "");
|
|
2909
2960
|
let current = root;
|
|
2910
2961
|
for (const part of segments) {
|
|
2911
|
-
current =
|
|
2962
|
+
current = join16(current, part);
|
|
2912
2963
|
if (checked.has(current))
|
|
2913
2964
|
continue;
|
|
2914
2965
|
checked.add(current);
|
|
@@ -2931,13 +2982,13 @@ function withoutExcluded(declared) {
|
|
|
2931
2982
|
return { incoming, dropped: dropped.sort() };
|
|
2932
2983
|
}
|
|
2933
2984
|
async function readLedger(root) {
|
|
2934
|
-
const text = await readFile6(
|
|
2985
|
+
const text = await readFile6(join16(root, SYNC_LEDGER_PATH), "utf8").catch(() => null);
|
|
2935
2986
|
return text === null ? null : parseSyncLedger(text);
|
|
2936
2987
|
}
|
|
2937
2988
|
async function writeLedger(root, ledger) {
|
|
2938
2989
|
try {
|
|
2939
2990
|
const dir = await ensureOwnDir(root, ...SYNC_LEDGER_PATH.split("/").slice(0, -1));
|
|
2940
|
-
await writeOwnFile(
|
|
2991
|
+
await writeOwnFile(join16(dir, basename3(SYNC_LEDGER_PATH)), serializeSyncLedger(ledger));
|
|
2941
2992
|
} catch {
|
|
2942
2993
|
return false;
|
|
2943
2994
|
}
|
|
@@ -2961,10 +3012,10 @@ async function applyDeletes(root, paths) {
|
|
|
2961
3012
|
await unlink(target).catch(() => {
|
|
2962
3013
|
});
|
|
2963
3014
|
count2 += 1;
|
|
2964
|
-
let dir =
|
|
3015
|
+
let dir = dirname3(target);
|
|
2965
3016
|
while (dir.length > root.length && dir.startsWith(root)) {
|
|
2966
3017
|
parents.add(dir);
|
|
2967
|
-
dir =
|
|
3018
|
+
dir = dirname3(dir);
|
|
2968
3019
|
}
|
|
2969
3020
|
}
|
|
2970
3021
|
for (const dir of [...parents].sort((a, b) => b.length - a.length)) {
|
|
@@ -2976,14 +3027,14 @@ async function applyDeletes(root, paths) {
|
|
|
2976
3027
|
async function setAside(root, plan) {
|
|
2977
3028
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
2978
3029
|
const real = await realpath2(root).catch(() => root);
|
|
2979
|
-
const saved =
|
|
3030
|
+
const saved = join16(dirname3(real), `${basename3(real)}${SAVED_SUFFIX}`, stamp);
|
|
2980
3031
|
const moved = [];
|
|
2981
3032
|
for (const conflict of plan.conflicts) {
|
|
2982
3033
|
const from = await resolveExisting(root, conflict.path);
|
|
2983
3034
|
if (!from)
|
|
2984
3035
|
continue;
|
|
2985
|
-
const to =
|
|
2986
|
-
await mkdir5(
|
|
3036
|
+
const to = join16(saved, conflict.path);
|
|
3037
|
+
await mkdir5(dirname3(to), { recursive: true });
|
|
2987
3038
|
await rename2(from, to);
|
|
2988
3039
|
moved.push(conflict.path);
|
|
2989
3040
|
}
|
|
@@ -3005,12 +3056,12 @@ ${list}`,
|
|
|
3005
3056
|
// ../core/dist/baseline.js
|
|
3006
3057
|
init_errors();
|
|
3007
3058
|
import { readFile as readFile7 } from "node:fs/promises";
|
|
3008
|
-
import { join as
|
|
3059
|
+
import { join as join17, resolve as resolve7 } from "node:path";
|
|
3009
3060
|
async function rebuildBaseline(options) {
|
|
3010
3061
|
const report = options.onProgress ?? (() => {
|
|
3011
3062
|
});
|
|
3012
3063
|
const root = resolve7(options.folder);
|
|
3013
|
-
const previousText = await readFile7(
|
|
3064
|
+
const previousText = await readFile7(join17(root, SYNC_LEDGER_PATH), "utf8").catch(() => null);
|
|
3014
3065
|
const previous = previousText === null ? null : parseSyncLedger(previousText);
|
|
3015
3066
|
report("\uD310\uC758 \uD30C\uC77C \uBAA9\uB85D\uC744 \uB2E4\uC2DC \uC77D\uB294 \uC911\uC785\uB2C8\uB2E4\u2026");
|
|
3016
3067
|
const tar = await fetchVerifiedSourceTar({
|
|
@@ -3111,7 +3162,7 @@ function ledgerCorrection(ledger, draft) {
|
|
|
3111
3162
|
init_errors();
|
|
3112
3163
|
init_notice();
|
|
3113
3164
|
import { readFile as readFile8, stat as stat3 } from "node:fs/promises";
|
|
3114
|
-
import { basename as
|
|
3165
|
+
import { basename as basename4, join as join18, resolve as resolve8 } from "node:path";
|
|
3115
3166
|
|
|
3116
3167
|
// ../core/dist/pushPlan.js
|
|
3117
3168
|
function effectiveSha(base, draft, path) {
|
|
@@ -3388,14 +3439,14 @@ async function bodyOf(root, edits) {
|
|
|
3388
3439
|
body.push({ path: edit.path, remove: true, ...edit.baseSha256 === null ? {} : { baseSha256: edit.baseSha256 } });
|
|
3389
3440
|
continue;
|
|
3390
3441
|
}
|
|
3391
|
-
const size = (await stat3(
|
|
3442
|
+
const size = (await stat3(join18(root, edit.path))).size;
|
|
3392
3443
|
if (size > MAX_PUSH_FILE_BYTES) {
|
|
3393
3444
|
throw new DevtoolsError("PUSH_TOO_LARGE", `\uD30C\uC77C \uD558\uB098\uAC00 \uB108\uBB34 \uD07D\uB2C8\uB2E4: ${edit.path} (${Math.round(size / 1024)}KB \xB7 \uC0C1\uD55C ${MAX_PUSH_FILE_BYTES / 1024}KB).`, "\uB3D9\uC601\uC0C1\xB7\uC6D0\uBCF8 \uC774\uBBF8\uC9C0\xB7\uBE4C\uB4DC \uC0B0\uCD9C\uBB3C\uC774 \uC18C\uC2A4 \uD3F4\uB354\uC5D0 \uB4E4\uC5B4 \uC788\uC9C0 \uC54A\uC740\uC9C0 \uD655\uC778\uD574 \uC8FC\uC138\uC694.");
|
|
3394
3445
|
}
|
|
3395
3446
|
if (total + size > MAX_PUSH_BYTES)
|
|
3396
3447
|
throw tooLarge();
|
|
3397
|
-
const raw = await readFile8(
|
|
3398
|
-
const breach = templateBreach(
|
|
3448
|
+
const raw = await readFile8(join18(root, edit.path));
|
|
3449
|
+
const breach = templateBreach(basename4(edit.path), raw);
|
|
3399
3450
|
if (breach !== null) {
|
|
3400
3451
|
throw new DevtoolsError("PUSH_SECRET_TEMPLATE", `${edit.path} \uC5D0 \uC2E4\uC81C \uAC12\uC774 \uB4E4\uC5B4 \uC788\uC5B4 \uC62C\uB9AC\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4(${breach}).`, "`.env.example` \uAC19\uC740 \uC11C\uC2DD \uD30C\uC77C\uC740 \uAC12\uC744 \uBE44\uC6CC \uB450\uC5B4\uC57C \uC62C\uB77C\uAC11\uB2C8\uB2E4. \uC2E4\uC81C \uAC12\uC740 \uCF58\uC194\uC758 \uD658\uACBD\uBCC0\uC218 \uD654\uBA74\uC5D0\uC11C \uAD00\uB9AC\uD574 \uC8FC\uC138\uC694.");
|
|
3401
3452
|
}
|
|
@@ -3663,11 +3714,11 @@ function flagValue(flags, name) {
|
|
|
3663
3714
|
import { readFileSync } from "node:fs";
|
|
3664
3715
|
import { readFile as readFile10 } from "node:fs/promises";
|
|
3665
3716
|
import { fileURLToPath } from "node:url";
|
|
3666
|
-
import { join as
|
|
3717
|
+
import { join as join20, resolve as resolve11 } from "node:path";
|
|
3667
3718
|
|
|
3668
3719
|
// src/tokenStore.ts
|
|
3669
3720
|
import { chmod as chmod4, readFile as readFile9, rm as rm6 } from "node:fs/promises";
|
|
3670
|
-
import { basename as
|
|
3721
|
+
import { basename as basename5, dirname as dirname4 } from "node:path";
|
|
3671
3722
|
var FileTokenStore = class {
|
|
3672
3723
|
// ⚠ 매개변수 프로퍼티(`constructor(private path)`)를 안 쓴다 — Node 의 타입 제거 실행기가
|
|
3673
3724
|
// 그 문법을 거절해 **시험이 아예 안 돈다.** 이 레포의 시험은 전부 그 실행기로 돈다.
|
|
@@ -3689,14 +3740,16 @@ var FileTokenStore = class {
|
|
|
3689
3740
|
accessToken: t.accessToken,
|
|
3690
3741
|
refreshToken: t.refreshToken,
|
|
3691
3742
|
expiresAt: t.expiresAt,
|
|
3692
|
-
issuer: t.issuer
|
|
3743
|
+
issuer: t.issuer,
|
|
3744
|
+
// 없으면 로그아웃이 서버 세션을 못 끊는다 — 옛 파일은 칸이 없어 로컬만 지운다.
|
|
3745
|
+
...typeof t.clientId === "string" ? { clientId: t.clientId } : {}
|
|
3693
3746
|
};
|
|
3694
3747
|
} catch {
|
|
3695
3748
|
return null;
|
|
3696
3749
|
}
|
|
3697
3750
|
}
|
|
3698
3751
|
async write(tokens) {
|
|
3699
|
-
const dir = await ensureOwnDir(
|
|
3752
|
+
const dir = await ensureOwnDir(dirname4(dirname4(this.path)), basename5(dirname4(this.path)));
|
|
3700
3753
|
await chmod4(dir, 448).catch(() => {
|
|
3701
3754
|
});
|
|
3702
3755
|
await writeOwnFile(this.path, JSON.stringify(tokens), 384);
|
|
@@ -3723,9 +3776,9 @@ function serverUrlOf(env = process.env) {
|
|
|
3723
3776
|
}
|
|
3724
3777
|
async function tenantOf(folder, explicit) {
|
|
3725
3778
|
if (explicit) return TENANT_CODE.test(explicit) ? explicit : null;
|
|
3726
|
-
const mark = parseSourceMark(await readFile10(
|
|
3779
|
+
const mark = parseSourceMark(await readFile10(join20(folder, SOURCE_MARK_PATH), "utf8").catch(() => null));
|
|
3727
3780
|
if (mark) return mark.tenant;
|
|
3728
|
-
const text = await readFile10(
|
|
3781
|
+
const text = await readFile10(join20(folder, SYNC_LEDGER_PATH), "utf8").catch(() => null);
|
|
3729
3782
|
return text === null ? null : parseSyncLedger(text)?.tenant ?? null;
|
|
3730
3783
|
}
|
|
3731
3784
|
async function openAuth(options = {}) {
|
|
@@ -3945,7 +3998,7 @@ function help() {
|
|
|
3945
3998
|
return `\uC798\uCEE4\uB77C \u2014 \uC0AC\uC774\uD2B8 \uC18C\uC2A4\uB97C \uB85C\uCEEC\uC5D0\uC11C \uB2E4\uB8E8\uB294 \uB3C4\uAD6C (v${version()})
|
|
3946
3999
|
|
|
3947
4000
|
zalkera login \uBE0C\uB77C\uC6B0\uC800\uB85C \uB85C\uADF8\uC778
|
|
3948
|
-
zalkera logout \uB85C\uADF8\uC778 \uC815\uBCF4\uB97C \uC9C0\
|
|
4001
|
+
zalkera logout \uB85C\uADF8\uC778 \uC815\uBCF4\uB97C \uC9C0\uC6B0\uACE0 \uC774 \uCEF4\uD4E8\uD130\uC758 \uC11C\uBC84 \uCABD \uB85C\uADF8\uC778\uB3C4 \uB04A\uB294\uB2E4
|
|
3949
4002
|
zalkera status \uC774 \uD3F4\uB354\uC640 \uC0AC\uC774\uD2B8\uAC00 \uC5B4\uB5BB\uAC8C \uB2E4\uB978\uC9C0 \uBCF8\uB2E4
|
|
3950
4003
|
zalkera pull \uC0AC\uC774\uD2B8\uC758 \uC9C0\uAE08 \uD310\uC744 \uC774 \uD3F4\uB354\uC5D0 \uBC1B\uB294\uB2E4
|
|
3951
4004
|
zalkera push \uC774 \uD3F4\uB354\uC5D0\uC11C \uACE0\uCE5C \uAC83\uC744 \uC0AC\uC774\uD2B8 \uCABD\uC5D0 \uC62C\uB9B0\uB2E4(\uCF1C\uC9C0\uC9C0\uB294 \uC54A\uB294\uB2E4)
|
|
@@ -4000,8 +4053,12 @@ async function main(argv) {
|
|
|
4000
4053
|
return 0;
|
|
4001
4054
|
}
|
|
4002
4055
|
case "logout": {
|
|
4003
|
-
|
|
4004
|
-
|
|
4056
|
+
const store = new FileTokenStore();
|
|
4057
|
+
const hadLogin = await store.read() !== null;
|
|
4058
|
+
const serverEnded = await (await logout(store)).serverEnded;
|
|
4059
|
+
process.stdout.write(
|
|
4060
|
+
!hadLogin ? "\uB85C\uADF8\uC778 \uC815\uBCF4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.\n" : serverEnded ? "\uB85C\uADF8\uC778 \uC815\uBCF4\uB97C \uC9C0\uC6B0\uACE0 \uC774 \uAE30\uAE30\uC758 \uC11C\uBC84 \uCABD \uB85C\uADF8\uC778\uB3C4 \uB05D\uB0C8\uC2B5\uB2C8\uB2E4.\n" : "\uB85C\uADF8\uC778 \uC815\uBCF4\uB97C \uC9C0\uC6E0\uC2B5\uB2C8\uB2E4. \uC11C\uBC84 \uCABD \uB85C\uADF8\uC778\uC740 \uB04A\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4(\uC5F0\uACB0 \uC5C6\uC74C\xB7\uC608\uC804 \uB85C\uADF8\uC778).\n"
|
|
4061
|
+
);
|
|
4005
4062
|
return 0;
|
|
4006
4063
|
}
|
|
4007
4064
|
case "status": {
|