@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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/main.js +88 -31
  3. 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 join13, normalize as normalize3, sep as sep5 } from "node:path";
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: join13, isAbsolute: isAbsolute2, normalize: normalize3, sep: sep5 };
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 join14, resolve as resolve5, sep as sep6 } from "node:path";
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 = join14(home, ".config");
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 join14(inside ? resolve5(base) : fallback, "zalkera", "auth.json");
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 basename2, dirname as dirname2, join as join15, relative as relative4, resolve as resolve6, sep as sep7 } from "node:path";
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 ${basename2(savedTo)} \uC5D0 \uC62E\uACA8 \uB450\uC5C8\uC2B5\uB2C8\uB2E4.`);
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 = join15(current, part);
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(join15(root, SYNC_LEDGER_PATH), "utf8").catch(() => null);
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(join15(dir, basename2(SYNC_LEDGER_PATH)), serializeSyncLedger(ledger));
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 = dirname2(target);
3015
+ let dir = dirname3(target);
2965
3016
  while (dir.length > root.length && dir.startsWith(root)) {
2966
3017
  parents.add(dir);
2967
- dir = dirname2(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 = join15(dirname2(real), `${basename2(real)}${SAVED_SUFFIX}`, stamp);
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 = join15(saved, conflict.path);
2986
- await mkdir5(dirname2(to), { recursive: true });
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 join16, resolve as resolve7 } from "node:path";
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(join16(root, SYNC_LEDGER_PATH), "utf8").catch(() => null);
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 basename3, join as join17, resolve as resolve8 } from "node:path";
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(join17(root, edit.path))).size;
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(join17(root, edit.path));
3398
- const breach = templateBreach(basename3(edit.path), raw);
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 join19, resolve as resolve11 } from "node:path";
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 basename4, dirname as dirname3 } from "node:path";
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(dirname3(dirname3(this.path)), basename4(dirname3(this.path)));
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(join19(folder, SOURCE_MARK_PATH), "utf8").catch(() => null));
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(join19(folder, SYNC_LEDGER_PATH), "utf8").catch(() => null);
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\uC6B4\uB2E4
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
- await logout(new FileTokenStore());
4004
- process.stdout.write("\uB85C\uADF8\uC778 \uC815\uBCF4\uB97C \uC9C0\uC6E0\uC2B5\uB2C8\uB2E4.\n");
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": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zalkera/cli",
3
- "version": "0.31.0",
3
+ "version": "0.32.0",
4
4
  "description": "잘커라 사이트 소스를 터미널에서 받고·올리고·발행합니다. 편집기 없이, 스크립트에서도.",
5
5
  "license": "MIT",
6
6
  "type": "module",