@zalkera/cli 0.20.2 → 0.22.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 +2 -0
  2. package/dist/main.js +1460 -101
  3. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -1,8 +1,13 @@
1
1
  #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
2
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
3
4
  var __esm = (fn, res) => function __init() {
4
5
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
6
  };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
6
11
 
7
12
  // ../core/dist/notice.js
8
13
  function defang(text) {
@@ -24,7 +29,7 @@ function plainNotice(text, limit = 300) {
24
29
  const tidy = defang(stripped).replace(/\s+/g, " ").trim();
25
30
  return tidy.length > cap ? `${tidy.slice(0, cap)}\u2026` : tidy;
26
31
  }
27
- var INNER_BOUND, MAX_CAP, LINK_SHAPE, AUTOLINK_SHAPE, DEFANG_PASSES;
32
+ var INNER_BOUND, MAX_CAP, LINK_SHAPE, AUTOLINK_SHAPE, DEFANG_PASSES, count;
28
33
  var init_notice = __esm({
29
34
  "../core/dist/notice.js"() {
30
35
  "use strict";
@@ -33,6 +38,12 @@ var init_notice = __esm({
33
38
  LINK_SHAPE = new RegExp(`\\]\\s*\\(\\s*([A-Za-z][A-Za-z0-9+.-]*:[^)]{0,${INNER_BOUND}})\\)`, "g");
34
39
  AUTOLINK_SHAPE = new RegExp(`<\\s*([A-Za-z][A-Za-z0-9+.-]*:[^>\\s]{0,${INNER_BOUND}})\\s*>`, "g");
35
40
  DEFANG_PASSES = 4;
41
+ count = (value) => {
42
+ if (typeof value !== "number" && typeof value !== "bigint")
43
+ return "?";
44
+ const n = Number(value);
45
+ return Number.isFinite(n) ? String(n) : "?";
46
+ };
36
47
  }
37
48
  });
38
49
 
@@ -102,6 +113,30 @@ ${plainNotice(this.hint)}` : body;
102
113
  });
103
114
 
104
115
  // ../core/dist/nodeOnPath.js
116
+ import { existsSync as existsSync5, statSync } from "node:fs";
117
+ import { delimiter, join as join7 } from "node:path";
118
+ function hasNodeOnPath(env = process.env, platform = process.platform) {
119
+ const raw = env.PATH ?? env.Path ?? env.path ?? "";
120
+ if (raw.length === 0)
121
+ return false;
122
+ const names = platform === "win32" ? ["node.exe"] : ["node"];
123
+ for (const dir of raw.split(delimiter)) {
124
+ if (dir.length === 0)
125
+ continue;
126
+ for (const name of names) {
127
+ const at = join7(dir, name);
128
+ try {
129
+ if (existsSync5(at) && statSync(at).isFile())
130
+ return true;
131
+ } catch {
132
+ }
133
+ }
134
+ }
135
+ return false;
136
+ }
137
+ function devEngineArgs(env = process.env, platform = process.platform) {
138
+ return hasNodeOnPath(env, platform) ? [] : ["--webpack"];
139
+ }
105
140
  var init_nodeOnPath = __esm({
106
141
  "../core/dist/nodeOnPath.js"() {
107
142
  "use strict";
@@ -109,11 +144,210 @@ var init_nodeOnPath = __esm({
109
144
  });
110
145
 
111
146
  // ../core/dist/dev.js
147
+ var dev_exports = {};
148
+ __export(dev_exports, {
149
+ pickPort: () => pickPort,
150
+ spawnQuietly: () => spawnQuietly,
151
+ startDevServer: () => startDevServer,
152
+ translateLog: () => translateLog
153
+ });
154
+ import { spawn as spawn2 } from "node:child_process";
155
+ import { existsSync as existsSync6 } from "node:fs";
156
+ import { createServer as createServer2 } from "node:net";
157
+ import { join as join8 } from "node:path";
158
+ async function startDevServer(options) {
159
+ const nextBin = join8(options.projectDir, "node_modules", "next", "dist", "bin", "next");
160
+ if (!existsSync6(nextBin)) {
161
+ throw new DevtoolsError("DEV_SERVER_FAILED", "\uAC1C\uBC1C \uC11C\uBC84\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.", "\uC758\uC874\uC131 \uC900\uBE44\uAC00 \uB05D\uB0AC\uB294\uC9C0 \uD655\uC778\uD574 \uC8FC\uC138\uC694.");
162
+ }
163
+ const port = await pickPort(options.port);
164
+ const url = `http://localhost:${port}`;
165
+ const log = options.onLog ?? (() => {
166
+ });
167
+ const engineArgs = devEngineArgs();
168
+ if (engineArgs.length > 0) {
169
+ log("\uBBF8\uB9AC\uBCF4\uAE30\uB97C \uB744\uC6C1\uB2C8\uB2E4. \uC774 \uCEF4\uD4E8\uD130\uC5D0 Node.js \uAC00 \uC788\uC73C\uBA74 \uB354 \uBE60\uB978 \uC5D4\uC9C4\uC744 \uC501\uB2C8\uB2E4 \u2014 \uC5C6\uC5B4\uB3C4 \uADF8\uB300\uB85C \uB429\uB2C8\uB2E4.");
170
+ }
171
+ const child = spawn2(options.nodePath, [nextBin, "dev", "--port", String(port), ...engineArgs], {
172
+ cwd: options.projectDir,
173
+ env: {
174
+ ...process.env,
175
+ ...options.extraEnv,
176
+ PORT: String(port),
177
+ BROWSER: "none"
178
+ },
179
+ stdio: ["ignore", "pipe", "pipe"],
180
+ // **자기 프로세스 그룹을 준다**(POSIX). Next 는 렌더 워커를, Turbopack 은 자기 node 를 더
181
+ // 띄운다 — 부모만 끊으면 그것들이 고아로 남아 포트를 물고 메모리를 먹는데, 사용자 화면에는
182
+ // 끄는 수단이 없다. 그룹이 있어야 `kill(-pid)` 로 한 번에 끊는다(`stopChild`).
183
+ //
184
+ // ⚠ 윈도에는 안 준다 — `detached` 가 **새 콘솔 창을 띄운다.** 거기서는 `taskkill /T` 가
185
+ // 같은 일을 한다.
186
+ detached: process.platform !== "win32"
187
+ });
188
+ let ready = false;
189
+ const markReady = () => {
190
+ if (ready)
191
+ return;
192
+ ready = true;
193
+ options.onReady?.(url);
194
+ };
195
+ const handle = (chunk) => {
196
+ for (const line of chunk.toString().split("\n")) {
197
+ const trimmed = line.trimEnd();
198
+ if (trimmed.length === 0)
199
+ continue;
200
+ log(translateLog(trimmed));
201
+ if (/\bReady in\b|started server on|Local:\s+http/i.test(trimmed))
202
+ markReady();
203
+ }
204
+ };
205
+ child.stdout?.on("data", handle);
206
+ child.stderr?.on("data", handle);
207
+ const exitListeners = [];
208
+ child.on("exit", (code2) => {
209
+ for (const listener of exitListeners)
210
+ listener(code2);
211
+ });
212
+ await waitForReady(child, () => ready, options.signal);
213
+ return {
214
+ url,
215
+ port,
216
+ stop: () => stopChild(child),
217
+ onExit: (listener) => exitListeners.push(listener)
218
+ };
219
+ }
220
+ async function pickPort(preferred) {
221
+ if (preferred && await isFree(preferred))
222
+ return preferred;
223
+ return new Promise((resolve12, reject) => {
224
+ const server = createServer2();
225
+ server.once("error", reject);
226
+ server.listen(0, "127.0.0.1", () => {
227
+ const address = server.address();
228
+ const port = typeof address === "object" && address ? address.port : 0;
229
+ server.close(() => resolve12(port));
230
+ });
231
+ });
232
+ }
233
+ async function isFree(port) {
234
+ return new Promise((resolve12) => {
235
+ const server = createServer2();
236
+ server.once("error", () => resolve12(false));
237
+ server.listen(port, "127.0.0.1", () => server.close(() => resolve12(true)));
238
+ });
239
+ }
240
+ async function waitForReady(child, isReady, signal) {
241
+ await new Promise((resolve12, reject) => {
242
+ const timer = setTimeout(() => {
243
+ clearInterval(poll);
244
+ if (isReady()) {
245
+ clearTimeout(timer);
246
+ resolve12();
247
+ return;
248
+ }
249
+ void stopChild(child);
250
+ reject(new DevtoolsError("DEV_SERVER_FAILED", "\uAC1C\uBC1C \uC11C\uBC84\uAC00 \uC2DC\uAC04 \uC548\uC5D0 \uB728\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.", "\uB85C\uADF8\uC758 \uB9C8\uC9C0\uB9C9 \uC624\uB958\uB97C \uD655\uC778\uD574 \uC8FC\uC138\uC694. \uB728\uB2E4 \uB9CC \uC11C\uBC84\uB294 \uC815\uB9AC\uD588\uC2B5\uB2C8\uB2E4."));
251
+ }, READY_TIMEOUT_MS);
252
+ timer.unref?.();
253
+ const poll = setInterval(() => {
254
+ if (!isReady())
255
+ return;
256
+ clearInterval(poll);
257
+ clearTimeout(timer);
258
+ signal?.removeEventListener("abort", onAbort);
259
+ resolve12();
260
+ }, 100);
261
+ poll.unref?.();
262
+ function onAbort() {
263
+ clearInterval(poll);
264
+ clearTimeout(timer);
265
+ void stopChild(child);
266
+ reject(new DevtoolsError("CANCELLED", "\uC900\uBE44\uB97C \uCDE8\uC18C\uD588\uC2B5\uB2C8\uB2E4."));
267
+ }
268
+ if (signal?.aborted) {
269
+ onAbort();
270
+ return;
271
+ }
272
+ signal?.addEventListener("abort", onAbort, { once: true });
273
+ child.once("exit", (code2) => {
274
+ if (isReady())
275
+ return;
276
+ clearInterval(poll);
277
+ clearTimeout(timer);
278
+ reject(new DevtoolsError("DEV_SERVER_FAILED", `\uAC1C\uBC1C \uC11C\uBC84\uAC00 \uBC14\uB85C \uC885\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4(\uC885\uB8CC \uCF54\uB4DC ${code2}).`, "\uB85C\uADF8\uC758 \uB9C8\uC9C0\uB9C9 \uC624\uB958\uB97C \uD655\uC778\uD574 \uC8FC\uC138\uC694."));
279
+ });
280
+ });
281
+ }
282
+ function spawnQuietly(command, args) {
283
+ try {
284
+ const child = spawn2(command, [...args], { stdio: "ignore" });
285
+ child.on("error", () => {
286
+ });
287
+ child.unref();
288
+ } catch {
289
+ }
290
+ }
291
+ function killTree(child, signal) {
292
+ const pid = child.pid;
293
+ if (pid === void 0)
294
+ return;
295
+ if (process.platform === "win32") {
296
+ spawnQuietly("taskkill", ["/pid", String(pid), "/T", "/F"]);
297
+ child.kill(signal);
298
+ return;
299
+ }
300
+ try {
301
+ process.kill(-pid, signal);
302
+ } catch {
303
+ child.kill(signal);
304
+ }
305
+ }
306
+ async function stopChild(child) {
307
+ if (child.exitCode !== null || child.signalCode !== null)
308
+ return;
309
+ killTree(child, "SIGTERM");
310
+ await new Promise((resolve12) => {
311
+ const timer = setTimeout(() => {
312
+ killTree(child, "SIGKILL");
313
+ resolve12();
314
+ }, 5e3);
315
+ timer.unref?.();
316
+ child.once("exit", () => {
317
+ clearTimeout(timer);
318
+ resolve12();
319
+ });
320
+ });
321
+ }
322
+ function translateLog(line) {
323
+ if (/EADDRINUSE/.test(line))
324
+ return `\u26A0 \uADF8 \uD3EC\uD2B8\uB294 \uB2E4\uB978 \uD504\uB85C\uADF8\uB7A8\uC774 \uC4F0\uACE0 \uC788\uC2B5\uB2C8\uB2E4. \uB2E4\uB978 \uD3EC\uD2B8\uB85C \uB2E4\uC2DC \uCF1C \uC8FC\uC138\uC694.
325
+ ${line}`;
326
+ if (/Cannot find module/.test(line))
327
+ return `\u26A0 \uD544\uC694\uD55C \uD30C\uC77C\uC774 \uBE60\uC84C\uC2B5\uB2C8\uB2E4. \uC758\uC874\uC131 \uC900\uBE44\uB97C \uB2E4\uC2DC \uD574 \uC8FC\uC138\uC694.
328
+ ${line}`;
329
+ if (/ENOSPC/.test(line))
330
+ return `\u26A0 \uB514\uC2A4\uD06C \uACF5\uAC04\uC774 \uBD80\uC871\uD569\uB2C8\uB2E4.
331
+ ${line}`;
332
+ if (/ECONNREFUSED|ENOTFOUND|ETIMEDOUT/.test(line)) {
333
+ return `\u26A0 \uC11C\uBC84\uC5D0 \uC5F0\uACB0\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uC778\uD130\uB137\xB7\uC0AC\uB0B4\uB9DD \uD504\uB85D\uC2DC\uB97C \uD655\uC778\uD574 \uC8FC\uC138\uC694.
334
+ ${line}`;
335
+ }
336
+ if (/ZalkeraError/.test(line) && /시크릿 키|secretKey|ZALKERA_STOREFRONT_KEY|테넌트의 키/.test(line) || /\b(401|403)\b/.test(line) && /storefront|zalkera/i.test(line)) {
337
+ return `\u26A0 \uB370\uC774\uD130 \uC811\uC18D\uC774 \uAC70\uC808\uB418\uC5C8\uC2B5\uB2C8\uB2E4 \u2014 \uBBF8\uB9AC\uBCF4\uAE30 \uC790\uACA9\uC99D\uBA85\uC774 \uD574\uC81C\uB410\uAC70\uB098 \uB9CC\uB8CC\uB410\uC2B5\uB2C8\uB2E4.
338
+ \uC790\uACA9\uC99D\uBA85\uC740 **\uD55C \uBC88\uC5D0 \uD558\uB098**\uB77C, \uB2E4\uB978 \uAE30\uACC4(\uB610\uB294 \uB2E4\uB978 \uCC3D)\uC5D0\uC11C \uBBF8\uB9AC\uBCF4\uAE30\uB97C \uCF1C\uBA74 \uC774\uCABD\uC774 \uB04A\uAE41\uB2C8\uB2E4.
339
+ \uBBF8\uB9AC\uBCF4\uAE30\uB97C \uB2E4\uC2DC \uCF1C \uC8FC\uC138\uC694.
340
+ ${line}`;
341
+ }
342
+ return line;
343
+ }
344
+ var READY_TIMEOUT_MS;
112
345
  var init_dev = __esm({
113
346
  "../core/dist/dev.js"() {
114
347
  "use strict";
115
348
  init_errors();
116
349
  init_nodeOnPath();
350
+ READY_TIMEOUT_MS = 12e4;
117
351
  }
118
352
  });
119
353
 
@@ -234,10 +468,10 @@ async function startLoopbackReceiver(options = {}) {
234
468
  let resolveCode;
235
469
  let rejectCode;
236
470
  let settled = false;
237
- const codePromise = new Promise((resolve9, reject) => {
471
+ const codePromise = new Promise((resolve12, reject) => {
238
472
  resolveCode = (result) => {
239
473
  settled = true;
240
- resolve9(result);
474
+ resolve12(result);
241
475
  };
242
476
  rejectCode = (error) => {
243
477
  settled = true;
@@ -264,9 +498,9 @@ async function startLoopbackReceiver(options = {}) {
264
498
  respond(response, 200, successHtml);
265
499
  resolveCode({ code: code2, state });
266
500
  });
267
- await new Promise((resolve9, reject) => {
501
+ await new Promise((resolve12, reject) => {
268
502
  server.once("error", reject);
269
- server.listen(0, "127.0.0.1", resolve9);
503
+ server.listen(0, "127.0.0.1", resolve12);
270
504
  });
271
505
  const port = server.address().port;
272
506
  const timer = setTimeout(() => {
@@ -360,11 +594,11 @@ async function refreshToken(config, store) {
360
594
  const epoch = epochOf(store);
361
595
  const current = await store.read();
362
596
  if (!current) {
363
- throw new DevtoolsError("NOT_AUTHENTICATED", "\uB85C\uADF8\uC778\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.", "\uBA3C\uC800 \uC798\uCEE4\uB77C\uC5D0 \uB85C\uADF8\uC778\uD574 \uC8FC\uC138\uC694.");
597
+ throw new DevtoolsError("NOT_AUTHENTICATED", "\uB85C\uADF8\uC778\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.", config.loginHow ?? "\uBA3C\uC800 \uC798\uCEE4\uB77C\uC5D0 \uB85C\uADF8\uC778\uD574 \uC8FC\uC138\uC694.");
364
598
  }
365
599
  if (current.issuer !== config.issuer) {
366
600
  await store.clear();
367
- throw new DevtoolsError("NOT_AUTHENTICATED", "\uC11C\uBC84\uAC00 \uBC14\uB00C\uC5B4 \uB2E4\uC2DC \uB85C\uADF8\uC778\uD574\uC57C \uD569\uB2C8\uB2E4.");
601
+ throw new DevtoolsError("NOT_AUTHENTICATED", "\uC11C\uBC84\uAC00 \uBC14\uB00C\uC5B4 \uB2E4\uC2DC \uB85C\uADF8\uC778\uD574\uC57C \uD569\uB2C8\uB2E4.", config.loginHow);
368
602
  }
369
603
  if (current.expiresAt - REFRESH_SKEW_MS > Date.now())
370
604
  return current.accessToken;
@@ -373,7 +607,7 @@ async function refreshToken(config, store) {
373
607
  refreshed = await exchange(config, { grant_type: "refresh_token", refresh_token: current.refreshToken });
374
608
  } catch (cause) {
375
609
  await store.clear();
376
- throw new DevtoolsError("NOT_AUTHENTICATED", "\uB85C\uADF8\uC778\uC774 \uB9CC\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.", "\uB2E4\uC2DC \uB85C\uADF8\uC778\uD574 \uC8FC\uC138\uC694.", cause);
610
+ throw new DevtoolsError("NOT_AUTHENTICATED", "\uB85C\uADF8\uC778\uC774 \uB9CC\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.", config.loginHow ?? "\uB2E4\uC2DC \uB85C\uADF8\uC778\uD574 \uC8FC\uC138\uC694.", cause);
377
611
  }
378
612
  if (epochOf(store) !== epoch) {
379
613
  throw new DevtoolsError("NOT_AUTHENTICATED", "\uB85C\uADF8\uC544\uC6C3\uB418\uC5C8\uC2B5\uB2C8\uB2E4.", "\uB2E4\uC2DC \uB85C\uADF8\uC778\uD574 \uC8FC\uC138\uC694.");
@@ -662,6 +896,11 @@ function withTrailingSlash2(base) {
662
896
  return base.endsWith("/") ? base : `${base}/`;
663
897
  }
664
898
 
899
+ // ../core/dist/env.js
900
+ import { chmod, readFile, writeFile as writeFile2 } from "node:fs/promises";
901
+ import { existsSync } from "node:fs";
902
+ import { join as join2 } from "node:path";
903
+
665
904
  // ../core/dist/safeWrite.js
666
905
  init_errors();
667
906
  import { lstat, mkdir, rename, rm, writeFile } from "node:fs/promises";
@@ -779,19 +1018,78 @@ function assertNotWrittenTwice(path, entryName, written) {
779
1018
  throw new DevtoolsError("SERVER_REJECTED", `\uBC1B\uC740 \uAFB8\uB7EC\uBBF8\uAC00 \uAC19\uC740 \uD30C\uC77C\uC744 \uB450 \uBC88 \uB2F4\uACE0 \uC788\uC2B5\uB2C8\uB2E4: ${entryName}`, "\uBC1B\uC740 \uAC83\uC774 \uC798\uBABB\uB410\uC2B5\uB2C8\uB2E4 \u2014 \uBE48 \uD3F4\uB354\uB85C \uB2E4\uC2DC \uBC1B\uC544\uB3C4 \uAC19\uC740 \uC790\uB9AC\uC5D0\uC11C \uBA48\uCDA5\uB2C8\uB2E4. \uC798\uCEE4\uB77C\uC5D0 \uC54C\uB824 \uC8FC\uC138\uC694.");
780
1019
  }
781
1020
 
1021
+ // ../core/dist/env.js
1022
+ var MANAGED_KEYS = [
1023
+ "ZALKERA_API_BASE",
1024
+ "ZALKERA_TENANT",
1025
+ "ZALKERA_STOREFRONT_KEY",
1026
+ "ZALKERA_SITE_URL",
1027
+ "NEXT_PUBLIC_ZALKERA_PREVIEW"
1028
+ ];
1029
+ function mergeEnv(existing, values) {
1030
+ const pending = new Map(Object.entries(values));
1031
+ const lines = existing.length === 0 ? [] : existing.split("\n");
1032
+ const seen = /* @__PURE__ */ new Set();
1033
+ const deduped = lines.filter((line) => {
1034
+ const key = keyOf(line);
1035
+ if (key === null || !pending.has(key))
1036
+ return true;
1037
+ if (seen.has(key))
1038
+ return false;
1039
+ seen.add(key);
1040
+ return true;
1041
+ });
1042
+ const merged = deduped.map((line) => {
1043
+ const key = keyOf(line);
1044
+ if (key === null || !pending.has(key))
1045
+ return line;
1046
+ const value = pending.get(key);
1047
+ pending.delete(key);
1048
+ return `${key}=${quoteIfNeeded(value)}`;
1049
+ });
1050
+ if (pending.size > 0) {
1051
+ if (merged.length > 0 && merged[merged.length - 1]?.trim() !== "")
1052
+ merged.push("");
1053
+ merged.push("# zalkera \uD655\uC7A5\uC774 \uAD00\uB9AC\uD558\uB294 \uCE78 \u2014 \uBBF8\uB9AC\uBCF4\uAE30\uB97C \uCF24 \uB54C\uB9C8\uB2E4 \uAC31\uC2E0\uB429\uB2C8\uB2E4(\uB2E4\uB978 \uC904\uC740 \uAC74\uB4DC\uB9AC\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4).");
1054
+ for (const key of MANAGED_KEYS) {
1055
+ const value = pending.get(key);
1056
+ if (value !== void 0)
1057
+ merged.push(`${key}=${quoteIfNeeded(value)}`);
1058
+ }
1059
+ merged.push("");
1060
+ }
1061
+ return merged.join("\n");
1062
+ }
1063
+ async function writePreviewEnv(projectDir, values) {
1064
+ const path = join2(projectDir, ".env.local");
1065
+ const existing = existsSync(path) ? await readFile(path, "utf8") : "";
1066
+ const merged = mergeEnv(existing, values);
1067
+ await writeOwnFile(path, merged, 384);
1068
+ await chmod(path, 384).catch(() => {
1069
+ });
1070
+ return path;
1071
+ }
1072
+ function keyOf(line) {
1073
+ const match = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/.exec(line);
1074
+ return match?.[1] ?? null;
1075
+ }
1076
+ function quoteIfNeeded(value) {
1077
+ return /[\s"'#]/.test(value) ? JSON.stringify(value) : value;
1078
+ }
1079
+
782
1080
  // ../core/dist/project.js
783
1081
  init_errors();
784
- import { existsSync } from "node:fs";
785
- import { readFile, writeFile as writeFile2 } from "node:fs/promises";
786
- import { join as join2 } from "node:path";
1082
+ import { existsSync as existsSync2 } from "node:fs";
1083
+ import { readFile as readFile2, writeFile as writeFile3 } from "node:fs/promises";
1084
+ import { join as join3 } from "node:path";
787
1085
  async function inspectProject(dir) {
788
- const packagePath = join2(dir, "package.json");
789
- if (!existsSync(packagePath)) {
1086
+ const packagePath = join3(dir, "package.json");
1087
+ if (!existsSync2(packagePath)) {
790
1088
  throw new DevtoolsError("NOT_A_SITE", "\uC774 \uD3F4\uB354\uC5D0\uC11C \uC0AC\uC774\uD2B8 \uC18C\uC2A4\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.", "\uC0AC\uC774\uD2B8 \uC18C\uC2A4 \uD3F4\uB354(package.json \uC774 \uC788\uB294 \uACF3)\uB97C \uACE8\uB77C \uC8FC\uC138\uC694.");
791
1089
  }
792
1090
  let parsed;
793
1091
  try {
794
- parsed = JSON.parse(await readFile(packagePath, "utf8"));
1092
+ parsed = JSON.parse(await readFile2(packagePath, "utf8"));
795
1093
  } catch (cause) {
796
1094
  throw new DevtoolsError("NOT_A_SITE", "package.json \uC744 \uC77D\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4(\uD615\uC2DD \uC624\uB958).", "\uD30C\uC77C\uC774 \uD3B8\uC9D1 \uC911 \uAE68\uC84C\uB294\uC9C0 \uD655\uC778\uD574 \uC8FC\uC138\uC694.", cause);
797
1095
  }
@@ -802,18 +1100,56 @@ async function inspectProject(dir) {
802
1100
  hasNext: "next" in deps,
803
1101
  clientVersion: deps["@zalkera/client"] ?? null,
804
1102
  toolInDeps: TOOL_PACKAGES.filter((name) => name in deps),
805
- hasNodeModules: existsSync(join2(dir, "node_modules"))
1103
+ hasNodeModules: existsSync2(join3(dir, "node_modules"))
806
1104
  };
807
1105
  }
808
1106
  var TOOL_PACKAGES = ["@zalkera/cli", "@zalkera/devtools", "zalkera", "zalkera-cli"];
1107
+ async function ensureEnvIgnored(dir) {
1108
+ const path = join3(dir, ".gitignore");
1109
+ if (!existsSync2(path)) {
1110
+ if (!existsSync2(join3(dir, ".git")))
1111
+ return "not-git";
1112
+ await writeOwnFile(path, "# \uB85C\uCEEC \uC790\uACA9\uC99D\uBA85 \u2014 \uC808\uB300 \uCEE4\uBC0B\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4(zalkera).\n.env.local\n");
1113
+ return "created";
1114
+ }
1115
+ const content = await readFile2(path, "utf8");
1116
+ const ignored = content.split("\n").map((line) => line.trim()).some((line) => line === ".env.local" || line === ".env*" || line === ".env*.local");
1117
+ if (ignored)
1118
+ return "already";
1119
+ const suffix = content.endsWith("\n") || content.length === 0 ? "" : "\n";
1120
+ await writeOwnFile(path, `${content}${suffix}
1121
+ # \uB85C\uCEEC \uC790\uACA9\uC99D\uBA85 \u2014 \uC808\uB300 \uCEE4\uBC0B\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4(zalkera).
1122
+ .env.local
1123
+ `);
1124
+ return "added";
1125
+ }
809
1126
 
810
1127
  // ../core/dist/deps.js
811
1128
  init_errors();
1129
+ import { spawn } from "node:child_process";
1130
+ import { createHash as createHash4 } from "node:crypto";
1131
+ import { cp, link, mkdir as mkdir4, readFile as readFile4, readdir as readdir2, readlink, rm as rm4, stat as stat2, symlink as symlink2, utimes, writeFile as writeFile6 } from "node:fs/promises";
1132
+ import { existsSync as existsSync4 } from "node:fs";
1133
+ import { homedir } from "node:os";
1134
+ import { dirname, join as join6, resolve as resolve4 } from "node:path";
1135
+
1136
+ // ../core/dist/payload.js
1137
+ import { createHash as createHash3 } from "node:crypto";
1138
+ import { createWriteStream as createWriteStream2 } from "node:fs";
1139
+ import { mkdir as mkdir3, readFile as readFile3, readdir, rm as rm3, stat, writeFile as writeFile5 } from "node:fs/promises";
1140
+ import { existsSync as existsSync3 } from "node:fs";
1141
+ import { join as join5, resolve as resolve3 } from "node:path";
1142
+ import { Readable, Transform as Transform2 } from "node:stream";
1143
+ import { pipeline as pipeline2 } from "node:stream/promises";
812
1144
 
813
1145
  // ../core/dist/untar.js
814
1146
  import { createHash as createHash2 } from "node:crypto";
815
- import { chmod, lstat as lstat2, mkdir as mkdir2, open, realpath, rm as rm2, symlink, writeFile as writeFile3 } from "node:fs/promises";
816
- import { isAbsolute, join as join3, normalize as normalize2, relative as relative2, resolve as resolve2, sep as sep2 } from "node:path";
1147
+ import { createReadStream, createWriteStream } from "node:fs";
1148
+ import { chmod as chmod2, lstat as lstat2, mkdir as mkdir2, open, realpath, rm as rm2, symlink, writeFile as writeFile4 } from "node:fs/promises";
1149
+ import { isAbsolute, join as join4, normalize as normalize2, relative as relative2, resolve as resolve2, sep as sep2 } from "node:path";
1150
+ import { Transform } from "node:stream";
1151
+ import { pipeline } from "node:stream/promises";
1152
+ import { createGunzip } from "node:zlib";
817
1153
 
818
1154
  // ../core/dist/limits.js
819
1155
  var MAX_DOWNLOAD_BYTES = 150 * 1024 * 1024;
@@ -877,6 +1213,57 @@ async function readTarManifest(tar, options = {}) {
877
1213
  });
878
1214
  return manifest;
879
1215
  }
1216
+ async function extractTarGzFile(gzPath, targetDir, scratchPath, options = {}) {
1217
+ try {
1218
+ const limit = resolveCap(options.maxBytes);
1219
+ const guard = new Transform({
1220
+ transform(chunk, _enc, done) {
1221
+ written += chunk.length;
1222
+ if (written > limit) {
1223
+ done(new Error(`\uD574\uC81C \uC0B0\uCD9C\uBB3C\uC774 \uC0C1\uD55C(${limit}B)\uC744 \uB118\uC5C8\uC2B5\uB2C8\uB2E4`));
1224
+ return;
1225
+ }
1226
+ done(null, chunk);
1227
+ }
1228
+ });
1229
+ let written = 0;
1230
+ await pipeline(createReadStream(gzPath), createGunzip(), guard, createWriteStream(scratchPath));
1231
+ } catch (cause) {
1232
+ await rm2(scratchPath, { force: true }).catch(() => {
1233
+ });
1234
+ throw new DevtoolsError("SERVER_REJECTED", "\uBC1B\uC740 \uC758\uC874\uC131 \uAFB8\uB7EC\uBBF8\uB97C \uD480\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.", "\uB2E4\uC2DC \uC2DC\uB3C4\uD574\uB3C4 \uAC19\uC73C\uBA74 \uC798\uCEE4\uB77C\uC5D0 \uBB38\uC758\uD574 \uC8FC\uC138\uC694.", cause);
1235
+ }
1236
+ const sink = await createSink(targetDir, options);
1237
+ const handle = await open(scratchPath, "r");
1238
+ try {
1239
+ const header = Buffer.alloc(BLOCK);
1240
+ for (; ; ) {
1241
+ const { bytesRead } = await handle.read(header, 0, BLOCK, null);
1242
+ if (bytesRead < BLOCK)
1243
+ break;
1244
+ if (isEndOfArchive(header))
1245
+ break;
1246
+ const meta = parseHeader(header);
1247
+ const data = Buffer.alloc(meta.size);
1248
+ if (meta.size > 0) {
1249
+ const read = await handle.read(data, 0, meta.size, null);
1250
+ if (read.bytesRead < meta.size) {
1251
+ throw new DevtoolsError("SERVER_REJECTED", "\uBC1B\uC740 \uC758\uC874\uC131 \uAFB8\uB7EC\uBBF8\uAC00 \uC911\uAC04\uC5D0 \uB04A\uACBC\uC2B5\uB2C8\uB2E4.");
1252
+ }
1253
+ const padding = Math.ceil(meta.size / BLOCK) * BLOCK - meta.size;
1254
+ if (padding > 0)
1255
+ await handle.read(Buffer.alloc(padding), 0, padding, null);
1256
+ }
1257
+ await sink.consume(meta, data);
1258
+ }
1259
+ } finally {
1260
+ await handle.close().catch(() => {
1261
+ });
1262
+ await rm2(scratchPath, { force: true }).catch(() => {
1263
+ });
1264
+ }
1265
+ return sink.fileCount();
1266
+ }
880
1267
  var BLOCK = 512;
881
1268
  function isEndOfArchive(header) {
882
1269
  return header.every((byte) => byte === 0);
@@ -979,7 +1366,7 @@ async function createSink(targetDir, options) {
979
1366
  if (verdict === "skip")
980
1367
  return;
981
1368
  const parent = await descend(root, segments.slice(0, -1), verified);
982
- const path = join3(parent, leaf);
1369
+ const path = join4(parent, leaf);
983
1370
  await assertNotSymlink(path, name);
984
1371
  if (verdict === "replace") {
985
1372
  assertNotWrittenTwice(path, name, writtenPaths);
@@ -991,7 +1378,7 @@ async function createSink(targetDir, options) {
991
1378
  await writeExclusive(path, data, name, writtenPaths);
992
1379
  }
993
1380
  if (preserveMode && (entry.mode & 73) !== 0) {
994
- await chmod(path, 493).catch(() => {
1381
+ await chmod2(path, 493).catch(() => {
995
1382
  });
996
1383
  }
997
1384
  count2 += 1;
@@ -1012,7 +1399,7 @@ async function writeSymlink(root, segments, name, target, verified) {
1012
1399
  if (resolved !== root && !resolved.startsWith(root + sep2)) {
1013
1400
  throw new DevtoolsError("SERVER_REJECTED", `\uBC1B\uC740 \uAFB8\uB7EC\uBBF8\uC758 \uB9C1\uD06C\uAC00 \uD3F4\uB354 \uBC16\uC744 \uAC00\uB9AC\uD0B5\uB2C8\uB2E4: ${name}`);
1014
1401
  }
1015
- const linkPath = join3(parent, leaf);
1402
+ const linkPath = join4(parent, leaf);
1016
1403
  await assertNotSymlink(linkPath, name);
1017
1404
  await symlink(relative2(parent, resolved) || ".", linkPath).catch((error) => {
1018
1405
  throw new DevtoolsError("SERVER_REJECTED", `\uB9C1\uD06C\uB97C \uB9CC\uB4E4\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4: ${name}`, "\uBC1B\uB294 \uD3F4\uB354\uB97C \uBE44\uC6B0\uACE0 \uB2E4\uC2DC \uC2DC\uB3C4\uD574 \uC8FC\uC138\uC694.", error);
@@ -1049,9 +1436,427 @@ async function gunzipBuffer(input, maxBytes) {
1049
1436
  }
1050
1437
 
1051
1438
  // ../core/dist/payload.js
1439
+ async function tryFetchPayload(options) {
1440
+ const raw = options.onProgress ?? (() => {
1441
+ });
1442
+ const report = (message) => {
1443
+ try {
1444
+ raw(message);
1445
+ } catch {
1446
+ }
1447
+ };
1448
+ try {
1449
+ return await attempt(options, report);
1450
+ } catch (cause) {
1451
+ await rm3(options.cacheDir, { recursive: true, force: true }).catch(() => {
1452
+ });
1453
+ report(`\uC900\uBE44\uB41C \uC758\uC874\uC131 \uAFB8\uB7EC\uBBF8\uB97C \uC4F0\uC9C0 \uBABB\uD574 \uC9C1\uC811 \uB0B4\uB824\uBC1B\uC2B5\uB2C8\uB2E4(${describe(cause)}).`);
1454
+ return null;
1455
+ }
1456
+ }
1457
+ async function attempt(options, report) {
1458
+ const fetchImpl = options.fetchImpl ?? fetch;
1459
+ const block2 = await lookup(options, fetchImpl);
1460
+ if (block2 === "unsupported") {
1461
+ report("\uBBF8\uB9AC \uC900\uBE44\uB41C \uC758\uC874\uC131 \uAFB8\uB7EC\uBBF8\uAC00 \uC5C6\uC5B4 \uC9C1\uC811 \uB0B4\uB824\uBC1B\uC2B5\uB2C8\uB2E4(\uC870\uAE08 \uB354 \uAC78\uB9BD\uB2C8\uB2E4).");
1462
+ return null;
1463
+ }
1464
+ const echo = typeof block2.lockfileSha256 === "string" ? block2.lockfileSha256 : "";
1465
+ if (echo && echo.toLowerCase() !== options.lockfileSha256.toLowerCase()) {
1466
+ report("\uC11C\uBC84\uAC00 \uB2E4\uB978 \uBC84\uC804\uC758 \uB2F5\uC744 \uC8FC\uC5B4 \uC900\uBE44\uB41C \uAFB8\uB7EC\uBBF8\uB97C \uC4F0\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
1467
+ return null;
1468
+ }
1469
+ const entry = block2.payload;
1470
+ if (!entry || typeof entry !== "object") {
1471
+ report("\uC774 \uBC84\uC804\uC5D0 \uB9DE\uB294 \uC758\uC874\uC131 \uAFB8\uB7EC\uBBF8\uAC00 \uC544\uC9C1 \uC5C6\uC5B4 \uC9C1\uC811 \uB0B4\uB824\uBC1B\uC2B5\uB2C8\uB2E4(\uC870\uAE08 \uB354 \uAC78\uB9BD\uB2C8\uB2E4).");
1472
+ return null;
1473
+ }
1474
+ const url = typeof entry.url === "string" ? entry.url : "";
1475
+ const sha256 = typeof entry.sha256 === "string" ? entry.sha256 : "";
1476
+ const bytes = typeof entry.bytes === "number" ? entry.bytes : Number.NaN;
1477
+ const bakedAt = typeof entry.bakedAt === "string" ? entry.bakedAt : null;
1478
+ if (!isSafeHttpsUrl(url) || !/^[0-9a-f]{64}$/i.test(sha256) || !Number.isSafeInteger(bytes) || bytes <= 0) {
1479
+ report("\uC11C\uBC84\uAC00 \uC900 \uAFB8\uB7EC\uBBF8 \uC815\uBCF4\uB97C \uC774\uD574\uD560 \uC218 \uC5C6\uC5B4 \uC9C1\uC811 \uB0B4\uB824\uBC1B\uC2B5\uB2C8\uB2E4.");
1480
+ return null;
1481
+ }
1482
+ report(`\uC900\uBE44\uB41C \uC758\uC874\uC131 \uAFB8\uB7EC\uBBF8\uB97C \uBC1B\uB294 \uC911\u2026 (${formatMegabytes(bytes)})`);
1483
+ await mkdir3(options.cacheDir, { recursive: true });
1484
+ if (!await hasRoomFor(options.cacheDir, bytes)) {
1485
+ report("\uB514\uC2A4\uD06C \uC5EC\uC720\uAC00 \uBD80\uC871\uD574 \uC900\uBE44\uB41C \uAFB8\uB7EC\uBBF8\uB97C \uC4F0\uC9C0 \uC54A\uACE0 \uC9C1\uC811 \uB0B4\uB824\uBC1B\uC2B5\uB2C8\uB2E4.");
1486
+ return null;
1487
+ }
1488
+ const archivePath = join5(options.cacheDir, DOWNLOAD_NAME);
1489
+ const scratchPath = join5(options.cacheDir, SCRATCH_NAME);
1490
+ try {
1491
+ const actual = await download(url, archivePath, fetchImpl, bytes, options.stallTimeoutMs ?? STALL_TIMEOUT_MS);
1492
+ if (actual !== sha256.toLowerCase()) {
1493
+ report("\uBC1B\uC740 \uAFB8\uB7EC\uBBF8\uAC00 \uC6D0\uBCF8\uACFC \uB2EC\uB77C \uD3D0\uAE30\uD588\uC2B5\uB2C8\uB2E4. \uC758\uC874\uC131\uC744 \uC9C1\uC811 \uB0B4\uB824\uBC1B\uC2B5\uB2C8\uB2E4.");
1494
+ return null;
1495
+ }
1496
+ report("\uC758\uC874\uC131 \uAFB8\uB7EC\uBBF8\uB97C \uD478\uB294 \uC911\u2026");
1497
+ const fileCount = await extractTarGzFile(archivePath, options.cacheDir, scratchPath, {
1498
+ // 오염된 응답이 sha 대조 **전에** 디스크를 채우지 못하게 막는다(심의 W3). 정상 페이로드는
1499
+ // 압축 대비 4배 안쪽이라(실측 158MB → 586MB) 여유 있게 잡되 무제한은 아니다.
1500
+ maxBytes: Math.max(bytes * EXTRACT_RATIO_CAP, MIN_EXTRACT_CAP),
1501
+ // `.bin` 의 상대 심볼릭 링크 9개가 없으면 `next dev` 가 실행 파일을 못 찾는다(실측).
1502
+ symlinks: "materialize",
1503
+ // 실행 비트가 없으면 그 실패는 **실행 단계**에서 EACCES 로 나타나, 원인을 엉뚱한 데서 찾게 된다.
1504
+ preserveMode: true
1505
+ });
1506
+ const treeDir = join5(options.cacheDir, "node_modules");
1507
+ if (!existsSync3(treeDir)) {
1508
+ report("\uBC1B\uC740 \uAFB8\uB7EC\uBBF8\uC758 \uBAA8\uC591\uC774 \uC608\uC0C1\uACFC \uB2EC\uB77C \uC4F0\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uC758\uC874\uC131\uC744 \uC9C1\uC811 \uB0B4\uB824\uBC1B\uC2B5\uB2C8\uB2E4.");
1509
+ await rm3(options.cacheDir, { recursive: true, force: true }).catch(() => {
1510
+ });
1511
+ return null;
1512
+ }
1513
+ return { bytes, fileCount, bakedAt };
1514
+ } catch (cause) {
1515
+ await rm3(options.cacheDir, { recursive: true, force: true }).catch(() => {
1516
+ });
1517
+ report(`\uC900\uBE44\uB41C \uC758\uC874\uC131 \uAFB8\uB7EC\uBBF8\uB97C \uC4F0\uC9C0 \uBABB\uD574 \uC9C1\uC811 \uB0B4\uB824\uBC1B\uC2B5\uB2C8\uB2E4(${describe(cause)}).`);
1518
+ return null;
1519
+ } finally {
1520
+ await rm3(archivePath, { force: true }).catch(() => {
1521
+ });
1522
+ await rm3(scratchPath, { force: true }).catch(() => {
1523
+ });
1524
+ }
1525
+ }
1526
+ async function lookup(options, fetchImpl) {
1527
+ const url = new URL("/api/devtools/handshake", options.apiBase.endsWith("/") ? options.apiBase : `${options.apiBase}/`);
1528
+ url.searchParams.set("lockfileHash", options.lockfileSha256);
1529
+ url.searchParams.set("platform", options.platform);
1530
+ const controller = new AbortController();
1531
+ const timer = setTimeout(() => controller.abort(), options.lookupTimeoutMs ?? LOOKUP_TIMEOUT_MS);
1532
+ try {
1533
+ const response = await fetchImpl(url, { headers: { accept: "application/json" }, signal: controller.signal });
1534
+ if (!response.ok)
1535
+ return "unsupported";
1536
+ const body = await response.json();
1537
+ const deps = body?.data?.deps;
1538
+ if (!deps || typeof deps !== "object")
1539
+ return "unsupported";
1540
+ return deps;
1541
+ } catch {
1542
+ return "unsupported";
1543
+ } finally {
1544
+ clearTimeout(timer);
1545
+ }
1546
+ }
1547
+ async function download(url, targetPath, fetchImpl, declaredBytes, stallMs) {
1548
+ const controller = new AbortController();
1549
+ let stall = setTimeout(() => controller.abort(), stallMs);
1550
+ stall.unref?.();
1551
+ let response;
1552
+ try {
1553
+ response = await fetchImpl(url, { signal: controller.signal });
1554
+ } catch (cause) {
1555
+ clearTimeout(stall);
1556
+ throw cause;
1557
+ }
1558
+ if (!response.ok || !response.body) {
1559
+ clearTimeout(stall);
1560
+ throw new Error(`HTTP ${response.status}`);
1561
+ }
1562
+ const hash = createHash3("sha256");
1563
+ const cap = declaredBytes > 0 ? declaredBytes : Number.POSITIVE_INFINITY;
1564
+ let received = 0;
1565
+ const body = Readable.fromWeb(response.body);
1566
+ const guard = new Transform2({
1567
+ transform(chunk, _enc, done) {
1568
+ clearTimeout(stall);
1569
+ stall = setTimeout(() => controller.abort(), stallMs);
1570
+ stall.unref?.();
1571
+ received += chunk.length;
1572
+ if (received > cap) {
1573
+ done(new Error(`\uBC1B\uB294 \uC591\uC774 \uC11C\uBC84\uAC00 \uB9D0\uD55C \uD06C\uAE30(${cap}B)\uB97C \uB118\uC5C8\uC2B5\uB2C8\uB2E4`));
1574
+ return;
1575
+ }
1576
+ hash.update(chunk);
1577
+ done(null, chunk);
1578
+ }
1579
+ });
1580
+ try {
1581
+ await pipeline2(body, guard, createWriteStream2(targetPath));
1582
+ } finally {
1583
+ clearTimeout(stall);
1584
+ }
1585
+ if (received !== declaredBytes && declaredBytes > 0) {
1586
+ throw new Error(`\uBC1B\uC740 \uD06C\uAE30\uAC00 \uC11C\uBC84\uAC00 \uB9D0\uD55C \uAC83\uACFC \uB2E4\uB985\uB2C8\uB2E4(${received} != ${declaredBytes})`);
1587
+ }
1588
+ return hash.digest("hex");
1589
+ }
1590
+ async function hasRoomFor(dir, declaredBytes) {
1591
+ try {
1592
+ const { statfs } = await import("node:fs/promises");
1593
+ if (typeof statfs !== "function")
1594
+ return true;
1595
+ const info = await statfs(dir);
1596
+ const free = Number(info.bavail) * Number(info.bsize);
1597
+ return free > declaredBytes * DISK_HEADROOM;
1598
+ } catch {
1599
+ return true;
1600
+ }
1601
+ }
1602
+ async function evictOldCaches(cacheRoot, keep = 3, onProgress, protectPath) {
1603
+ try {
1604
+ const entries = await readdir(cacheRoot, { withFileTypes: true });
1605
+ const dirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith("."));
1606
+ if (dirs.length <= keep)
1607
+ return 0;
1608
+ const withTime = await Promise.all(dirs.map(async (e) => {
1609
+ const path = resolve3(join5(cacheRoot, e.name));
1610
+ const mtime = await stat(path).then((s) => s.mtimeMs).catch(() => 0);
1611
+ return { path, mtime };
1612
+ }));
1613
+ withTime.sort((a, b) => b.mtime - a.mtime);
1614
+ let removed = 0;
1615
+ for (const entry of withTime.slice(keep)) {
1616
+ if (protectPath && entry.path === resolve3(protectPath))
1617
+ continue;
1618
+ await rm3(entry.path, { recursive: true, force: true });
1619
+ removed += 1;
1620
+ }
1621
+ if (removed > 0)
1622
+ onProgress?.(`\uC624\uB798\uB41C \uC758\uC874\uC131 \uCE90\uC2DC ${removed}\uAC1C\uB97C \uC815\uB9AC\uD588\uC2B5\uB2C8\uB2E4.`);
1623
+ return removed;
1624
+ } catch {
1625
+ return 0;
1626
+ }
1627
+ }
1628
+ async function computePayloadKey(projectDir) {
1629
+ const lockPath = join5(projectDir, "package-lock.json");
1630
+ if (!existsSync3(lockPath))
1631
+ return null;
1632
+ return createHash3("sha256").update(await readFile3(lockPath)).digest("hex");
1633
+ }
1634
+ function currentPlatform() {
1635
+ return `${process.platform}-${process.arch}`;
1636
+ }
1637
+ async function writePayloadStamp(cacheDir, result, key) {
1638
+ await writeFile5(join5(cacheDir, "payload.json"), `${JSON.stringify({ lockfileSha256: key, bakedAt: result.bakedAt, fileCount: result.fileCount }, null, 2)}
1639
+ `, "utf8").catch(() => {
1640
+ });
1641
+ }
1642
+ function isSafeHttpsUrl(value) {
1643
+ try {
1644
+ return new URL(value).protocol === "https:";
1645
+ } catch {
1646
+ return false;
1647
+ }
1648
+ }
1649
+ var LOOKUP_TIMEOUT_MS = 5e3;
1650
+ var STALL_TIMEOUT_MS = 3e4;
1052
1651
  var EXTRACT_RATIO_CAP = 8;
1053
1652
  var DISK_HEADROOM = 1 + 2 * EXTRACT_RATIO_CAP;
1054
1653
  var MIN_EXTRACT_CAP = 32 * 1024 * 1024;
1654
+ var DOWNLOAD_NAME = ".payload.tar.gz";
1655
+ var SCRATCH_NAME = ".payload.tar";
1656
+ function formatMegabytes(bytes) {
1657
+ return `${Math.round(bytes / (1024 * 1024))}MB`;
1658
+ }
1659
+ function describe(cause) {
1660
+ const message = cause instanceof Error ? cause.message : String(cause);
1661
+ return message.length > 80 ? `${message.slice(0, 80)}\u2026` : message;
1662
+ }
1663
+
1664
+ // ../core/dist/deps.js
1665
+ function spawnFailure(cause) {
1666
+ if (cause instanceof Error && cause.name === "AbortError") {
1667
+ return new DevtoolsError("CANCELLED", "\uC900\uBE44\uB97C \uCDE8\uC18C\uD588\uC2B5\uB2C8\uB2E4.");
1668
+ }
1669
+ const missing = isNotFound(cause);
1670
+ return new DevtoolsError("DEPENDENCIES_FAILED", missing ? "\uC758\uC874\uC131 \uC124\uCE58 \uB3C4\uAD6C\uB97C \uC2E4\uD589\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4." : "\uC758\uC874\uC131\uC744 \uB0B4\uB824\uBC1B\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.", missing ? "\uD655\uC7A5\uC744 \uB2E4\uC2DC \uC124\uCE58\uD574 \uBCF4\uC138\uC694. \uADF8\uB798\uB3C4 \uAC19\uC73C\uBA74 \uC798\uCEE4\uB77C\uC5D0 \uBB38\uC758\uD574 \uC8FC\uC138\uC694." : "\uC778\uD130\uB137 \uC5F0\uACB0\uC774\uB098 \uC0AC\uB0B4\uB9DD \uD504\uB85D\uC2DC \uC124\uC815\uC744 \uD655\uC778\uD574 \uC8FC\uC138\uC694.", cause);
1671
+ }
1672
+ async function ensureDependencies(options) {
1673
+ const { projectDir } = options;
1674
+ const cacheRoot = options.cacheRoot ?? join6(homedir(), ".zalkera", "deps");
1675
+ const report = options.onProgress ?? (() => {
1676
+ });
1677
+ const cacheKey = await computeCacheKey(projectDir);
1678
+ const cacheDir = join6(cacheRoot, cacheKey);
1679
+ const target = join6(projectDir, "node_modules");
1680
+ const doneAt = completionPath(cacheRoot, projectDir, cacheKey);
1681
+ if (existsSync4(doneAt) && existsSync4(target)) {
1682
+ report("\uC758\uC874\uC131\uC774 \uC774\uBBF8 \uC900\uBE44\uB3FC \uC788\uC2B5\uB2C8\uB2E4.");
1683
+ return { action: "reused", cacheKey };
1684
+ }
1685
+ if (isCacheComplete(cacheDir)) {
1686
+ report("\uC900\uBE44\uD574 \uB454 \uC758\uC874\uC131\uC744 \uC5F0\uACB0\uD569\uB2C8\uB2E4\u2026");
1687
+ await utimes(cacheDir, /* @__PURE__ */ new Date(), /* @__PURE__ */ new Date()).catch(() => {
1688
+ });
1689
+ const linked = await linkOrCopy(join6(cacheDir, "node_modules"), target);
1690
+ await markComplete(doneAt);
1691
+ return { action: linked, cacheKey };
1692
+ }
1693
+ if (options.apiBase) {
1694
+ const payloadKey = await computePayloadKey(projectDir);
1695
+ if (payloadKey) {
1696
+ const payload = await tryFetchPayload({
1697
+ apiBase: options.apiBase,
1698
+ lockfileSha256: payloadKey,
1699
+ platform: currentPlatform(),
1700
+ cacheDir,
1701
+ onProgress: report,
1702
+ fetchImpl: options.fetchImpl
1703
+ });
1704
+ if (payload) {
1705
+ await writePayloadStamp(cacheDir, payload, payloadKey);
1706
+ report(`\uC900\uBE44\uB41C \uC758\uC874\uC131 ${payload.fileCount}\uAC1C\uB97C \uC5F0\uACB0\uD569\uB2C8\uB2E4\u2026`);
1707
+ const linked = await linkOrCopy(join6(cacheDir, "node_modules"), target);
1708
+ await markComplete(doneAt);
1709
+ await evictOldCaches(cacheRoot, KEEP_CACHES, report, cacheDir);
1710
+ return { action: linked, cacheKey };
1711
+ }
1712
+ } else {
1713
+ report("\uC774 \uD504\uB85C\uC81D\uD2B8\uB294 \uBBF8\uB9AC \uC900\uBE44\uB41C \uAFB8\uB7EC\uBBF8\uB97C \uC4F8 \uC218 \uC5C6\uC5B4 \uC9C1\uC811 \uB0B4\uB824\uBC1B\uC2B5\uB2C8\uB2E4(package-lock.json \uC774 \uD544\uC694\uD569\uB2C8\uB2E4).");
1714
+ }
1715
+ }
1716
+ const needsScripts = await installScriptPackages(projectDir);
1717
+ if (needsScripts.length) {
1718
+ report(`\uC124\uCE58 \uC2A4\uD06C\uB9BD\uD2B8\uAC00 \uD544\uC694\uD55C \uAFB8\uB7EC\uBBF8\uAC00 ${needsScripts.length}\uAC1C \uC788\uC2B5\uB2C8\uB2E4(${needsScripts.slice(0, 3).join(", ")}${needsScripts.length > 3 ? " \uC678" : ""}). \uC548\uC804\uC744 \uC704\uD574 \uC2E4\uD589\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4 \u2014 \uC774 \uAFB8\uB7EC\uBBF8\uAC00 \uC81C\uB300\uB85C \uC548 \uC124 \uC218 \uC788\uC2B5\uB2C8\uB2E4.`);
1719
+ }
1720
+ report("\uC758\uC874\uC131\uC744 \uCC98\uC74C \uD55C \uBC88 \uB0B4\uB824\uBC1B\uC2B5\uB2C8\uB2E4. \uBA87 \uBD84 \uAC78\uB9B4 \uC218 \uC788\uC2B5\uB2C8\uB2E4\u2026");
1721
+ await runNpmInstall(projectDir, options.npmCommand, options.npmEnv ?? {}, report, options.signal);
1722
+ await seedCache(target, cacheDir, cacheKey, report);
1723
+ await markComplete(doneAt);
1724
+ await evictOldCaches(cacheRoot, KEEP_CACHES, report, cacheDir);
1725
+ return { action: "installed", cacheKey };
1726
+ }
1727
+ function isCacheComplete(cacheDir) {
1728
+ if (!existsSync4(join6(cacheDir, "node_modules")))
1729
+ return false;
1730
+ return existsSync4(join6(cacheDir, "key.txt")) || existsSync4(join6(cacheDir, "payload.json"));
1731
+ }
1732
+ var KEEP_CACHES = 3;
1733
+ async function computeCacheKey(projectDir) {
1734
+ const lockNames = ["package-lock.json", "npm-shrinkwrap.json", "pnpm-lock.yaml", "yarn.lock"];
1735
+ const hash = createHash4("sha256");
1736
+ let found = false;
1737
+ for (const name of lockNames) {
1738
+ const path = join6(projectDir, name);
1739
+ if (!existsSync4(path))
1740
+ continue;
1741
+ hash.update(name).update(await readFile4(path));
1742
+ found = true;
1743
+ break;
1744
+ }
1745
+ if (!found) {
1746
+ hash.update("no-lock").update(await readFile4(join6(projectDir, "package.json")));
1747
+ }
1748
+ const npmrc = join6(projectDir, ".npmrc");
1749
+ hash.update(existsSync4(npmrc) ? Buffer.concat([Buffer.from("npmrc:"), await readFile4(npmrc)]) : Buffer.from("no-npmrc"));
1750
+ hash.update(`${process.platform}-${process.arch}-node${process.versions.node.split(".")[0]}`);
1751
+ return hash.digest("hex").slice(0, 32);
1752
+ }
1753
+ async function linkOrCopy(source, target) {
1754
+ try {
1755
+ await hardlinkTree(source, target);
1756
+ return "linked";
1757
+ } catch (cause) {
1758
+ if (!isCrossDevice(cause)) {
1759
+ await rm4(target, { recursive: true, force: true }).catch(() => {
1760
+ });
1761
+ throw new DevtoolsError("DEPENDENCIES_FAILED", "\uC900\uBE44\uD574 \uB454 \uC758\uC874\uC131\uC744 \uD504\uB85C\uC81D\uD2B8\uC5D0 \uC5F0\uACB0\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.", "\uB514\uC2A4\uD06C \uC5EC\uC720 \uACF5\uAC04\uACFC \uD3F4\uB354 \uAD8C\uD55C\uC744 \uD655\uC778\uD574 \uC8FC\uC138\uC694.", cause);
1762
+ }
1763
+ await rm4(target, { recursive: true, force: true });
1764
+ try {
1765
+ await cp(source, target, { recursive: true, verbatimSymlinks: true });
1766
+ return "copied";
1767
+ } catch (copyCause) {
1768
+ throw new DevtoolsError("DEPENDENCIES_FAILED", "\uC758\uC874\uC131\uC744 \uD504\uB85C\uC81D\uD2B8\uB85C \uBCF5\uC0AC\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.", "\uB514\uC2A4\uD06C \uC5EC\uC720 \uACF5\uAC04\uC744 \uD655\uC778\uD574 \uC8FC\uC138\uC694.", copyCause);
1769
+ }
1770
+ }
1771
+ }
1772
+ async function hardlinkTree(source, target) {
1773
+ await mkdir4(target, { recursive: true });
1774
+ const entries = await readdir2(source, { withFileTypes: true });
1775
+ for (const entry of entries) {
1776
+ const from = join6(source, entry.name);
1777
+ const to = join6(target, entry.name);
1778
+ if (entry.isDirectory()) {
1779
+ await hardlinkTree(from, to);
1780
+ } else if (entry.isSymbolicLink()) {
1781
+ await symlink2(await readlink(from), to).catch((error) => {
1782
+ if (!isExists(error))
1783
+ throw error;
1784
+ });
1785
+ } else {
1786
+ await link(from, to).catch((error) => {
1787
+ if (!isExists(error))
1788
+ throw error;
1789
+ });
1790
+ }
1791
+ }
1792
+ }
1793
+ async function installScriptPackages(projectDir) {
1794
+ const lock = join6(projectDir, "package-lock.json");
1795
+ if (!existsSync4(lock))
1796
+ return [];
1797
+ try {
1798
+ const parsed = JSON.parse(await readFile4(lock, "utf8"));
1799
+ return Object.entries(parsed.packages ?? {}).filter(([, meta]) => meta?.hasInstallScript === true).map(([name]) => name.replace(/^node_modules\//, "")).sort();
1800
+ } catch {
1801
+ return [];
1802
+ }
1803
+ }
1804
+ function completionPath(cacheRoot, projectDir, cacheKey) {
1805
+ const where = createHash4("sha256").update(resolve4(projectDir)).digest("hex").slice(0, 16);
1806
+ return join6(cacheRoot, ".complete", `${where}-${cacheKey}`);
1807
+ }
1808
+ async function markComplete(doneAt) {
1809
+ await mkdir4(dirname(doneAt), { recursive: true }).catch(() => {
1810
+ });
1811
+ await writeFile6(doneAt, `${(/* @__PURE__ */ new Date()).toISOString()}
1812
+ `, "utf8").catch(() => {
1813
+ });
1814
+ }
1815
+ function isNotFound(error) {
1816
+ return error?.code === "ENOENT";
1817
+ }
1818
+ function isCrossDevice(error) {
1819
+ return error?.code === "EXDEV";
1820
+ }
1821
+ function isExists(error) {
1822
+ return error?.code === "EEXIST";
1823
+ }
1824
+ async function seedCache(source, cacheDir, cacheKey, report) {
1825
+ try {
1826
+ await mkdir4(cacheDir, { recursive: true });
1827
+ await cp(source, join6(cacheDir, "node_modules"), { recursive: true, verbatimSymlinks: true });
1828
+ await writeFile6(join6(cacheDir, "key.txt"), `${cacheKey}
1829
+ `, "utf8");
1830
+ } catch {
1831
+ report("\uC758\uC874\uC131 \uCE90\uC2DC \uC801\uC7AC\uB97C \uAC74\uB108\uB701\uB2C8\uB2E4(\uB2E4\uC74C\uC5D0 \uB2E4\uC2DC \uBC1B\uC2B5\uB2C8\uB2E4).");
1832
+ await rm4(cacheDir, { recursive: true, force: true }).catch(() => {
1833
+ });
1834
+ }
1835
+ }
1836
+ async function runNpmInstall(cwd, command, env, report, signal) {
1837
+ const [bin, ...args] = command;
1838
+ if (!bin)
1839
+ throw new DevtoolsError("DEPENDENCIES_FAILED", "npm \uC2E4\uD589 \uBC29\uBC95\uC744 \uC54C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
1840
+ await new Promise((resolve12, reject) => {
1841
+ const child = spawn(bin, args, {
1842
+ cwd,
1843
+ env: { ...process.env, ...env },
1844
+ stdio: ["ignore", "pipe", "pipe"],
1845
+ signal
1846
+ });
1847
+ child.stdout?.on("data", (chunk) => report(chunk.toString().trimEnd()));
1848
+ child.stderr?.on("data", (chunk) => report(chunk.toString().trimEnd()));
1849
+ child.on("error", (cause) => reject(spawnFailure(cause)));
1850
+ child.on("close", (code2) => {
1851
+ if (code2 === 0)
1852
+ return resolve12();
1853
+ reject(new DevtoolsError("DEPENDENCIES_FAILED", `\uC758\uC874\uC131 \uC124\uCE58\uAC00 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4(\uC885\uB8CC \uCF54\uB4DC ${code2}).`, "\uC704 \uB85C\uADF8\uC758 \uB9C8\uC9C0\uB9C9 \uC624\uB958\uB97C \uD655\uC778\uD558\uAC70\uB098, \uC798\uCEE4\uB77C\uC5D0 \uBB38\uC758\uD574 \uC8FC\uC138\uC694."));
1854
+ });
1855
+ });
1856
+ await stat2(join6(cwd, "node_modules")).catch(() => {
1857
+ throw new DevtoolsError("DEPENDENCIES_FAILED", "\uC124\uCE58\uB294 \uB05D\uB0AC\uC9C0\uB9CC \uC758\uC874\uC131 \uD3F4\uB354\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
1858
+ });
1859
+ }
1055
1860
 
1056
1861
  // ../core/dist/index.js
1057
1862
  init_dev();
@@ -1117,7 +1922,7 @@ function serializeSyncLedger(ledger) {
1117
1922
  }
1118
1923
 
1119
1924
  // ../core/dist/localMark.js
1120
- import { join as join4 } from "node:path";
1925
+ import { join as join9 } from "node:path";
1121
1926
  var SOURCE_MARK_PATH = ".zalkera/source.json";
1122
1927
  var TENANT_CODE = /^[a-z0-9][a-z0-9-]{0,62}$/;
1123
1928
  var MAX_REVISION_NO = 2147483647;
@@ -1178,7 +1983,7 @@ async function writeSourceMarkTo(root, mark) {
1178
1983
  async function writeMarkText(root, text) {
1179
1984
  try {
1180
1985
  await ensureOwnDir(root, ".zalkera");
1181
- await writeOwnFile(join4(root, SOURCE_MARK_PATH), text);
1986
+ await writeOwnFile(join9(root, SOURCE_MARK_PATH), text);
1182
1987
  return { ok: true, text: "" };
1183
1988
  } catch (error) {
1184
1989
  return { ok: false, reason: error instanceof Error ? error.message : String(error) };
@@ -1190,7 +1995,7 @@ var PROVENANCE_PATH = ".zalkera/provenance.json";
1190
1995
 
1191
1996
  // ../core/dist/zip.js
1192
1997
  init_errors();
1193
- import { join as join5, relative as relative3, sep as sep3 } from "node:path";
1998
+ import { join as join10, relative as relative3, sep as sep3 } from "node:path";
1194
1999
  import { deflateRaw } from "node:zlib";
1195
2000
  import { promisify } from "node:util";
1196
2001
  var deflate = promisify(deflateRaw);
@@ -1368,9 +2173,62 @@ var TRANSFER_TIMEOUT_MS = 15 * 60 * 1e3;
1368
2173
  // ../core/dist/preview.js
1369
2174
  init_dev();
1370
2175
  init_errors();
2176
+ async function startPreview(options) {
2177
+ const report = options.onProgress ?? (() => {
2178
+ });
2179
+ const project = await inspectProject(options.projectDir);
2180
+ report(`\uC0AC\uC774\uD2B8 \uC18C\uC2A4\uB97C \uD655\uC778\uD588\uC2B5\uB2C8\uB2E4: ${project.name}`);
2181
+ const ignored = await ensureEnvIgnored(options.projectDir);
2182
+ if (ignored === "added")
2183
+ report(".gitignore \uC5D0 .env.local \uC744 \uCD94\uAC00\uD588\uC2B5\uB2C8\uB2E4(\uC790\uACA9\uC99D\uBA85 \uCEE4\uBC0B \uBC29\uC9C0).");
2184
+ if (ignored === "created")
2185
+ report(".gitignore \uB97C \uB9CC\uB4E4\uC5B4 .env.local \uC744 \uC81C\uC678\uD588\uC2B5\uB2C8\uB2E4(\uC790\uACA9\uC99D\uBA85 \uCEE4\uBC0B \uBC29\uC9C0).");
2186
+ report("\uBBF8\uB9AC\uBCF4\uAE30 \uC790\uACA9\uC99D\uBA85\uC744 \uBC1C\uAE09\uBC1B\uB294 \uC911\u2026");
2187
+ const key = await options.api.issuePreviewKey(options.label);
2188
+ options.onKeyIssued?.(key.id);
2189
+ const deps = await ensureDependencies({
2190
+ signal: options.signal,
2191
+ projectDir: options.projectDir,
2192
+ onProgress: report,
2193
+ // 캐시 미스면 **미리 구운 꾸러미를 먼저 물어본다**(T-D2c). 서버가 안 주면 그대로 npm 으로 내려간다.
2194
+ apiBase: options.apiBase,
2195
+ npmCommand: options.npmCommand,
2196
+ ...options.npmEnv ? { npmEnv: options.npmEnv } : {}
2197
+ });
2198
+ if (deps.action === "installed")
2199
+ report("\uC758\uC874\uC131 \uC900\uBE44\uAC00 \uB05D\uB0AC\uC2B5\uB2C8\uB2E4(\uB2E4\uC74C\uBD80\uD130\uB294 \uC989\uC2DC \uC2DC\uC791\uD569\uB2C8\uB2E4).");
2200
+ if (options.signal?.aborted) {
2201
+ throw new DevtoolsError("CANCELLED", "\uBBF8\uB9AC\uBCF4\uAE30 \uC2DC\uC791\uC744 \uCDE8\uC18C\uD588\uC2B5\uB2C8\uB2E4.");
2202
+ }
2203
+ const server = await startDevServerWithEnv(options, key.key, key.envName);
2204
+ return { server, keyId: key.id, revokedPrevious: key.revokedPrevious, expiresAt: key.expiresAt };
2205
+ }
2206
+ async function startDevServerWithEnv(options, storefrontKey, envName) {
2207
+ const { pickPort: pickPort2 } = await Promise.resolve().then(() => (init_dev(), dev_exports));
2208
+ const port = await pickPort2(options.port);
2209
+ if (envName !== "ZALKERA_STOREFRONT_KEY") {
2210
+ options.onProgress?.(`\uC11C\uBC84\uAC00 \uB2E4\uB978 \uC790\uACA9\uC99D\uBA85 \uCE78 \uC774\uB984(${envName})\uC744 \uC54C\uB824 \uC654\uC2B5\uB2C8\uB2E4 \u2014 \uC18C\uC2A4\uAC00 \uC77D\uB294 \uC774\uB984\uC740 ZALKERA_STOREFRONT_KEY \uB77C \uADF8\uAC83\uC73C\uB85C \uC501\uB2C8\uB2E4.`);
2211
+ }
2212
+ await writePreviewEnv(options.projectDir, {
2213
+ ZALKERA_API_BASE: options.apiBase,
2214
+ ZALKERA_TENANT: options.tenantCode,
2215
+ ZALKERA_STOREFRONT_KEY: storefrontKey,
2216
+ ZALKERA_SITE_URL: `http://localhost:${port}`,
2217
+ NEXT_PUBLIC_ZALKERA_PREVIEW: "1"
2218
+ });
2219
+ return startDevServer({
2220
+ projectDir: options.projectDir,
2221
+ nodePath: options.nodePath,
2222
+ port,
2223
+ // 취소가 **첫 컴파일 구간까지** 닿아야 한다. 위의 점 검사는 그 앞까지만 덮는다.
2224
+ ...options.signal ? { signal: options.signal } : {},
2225
+ ...options.onLog ? { onLog: options.onLog } : {},
2226
+ ...options.extraEnv ? { extraEnv: options.extraEnv } : {}
2227
+ });
2228
+ }
1371
2229
 
1372
2230
  // ../core/dist/fetchSource.js
1373
- import { createHash as createHash3 } from "node:crypto";
2231
+ import { createHash as createHash5 } from "node:crypto";
1374
2232
  init_errors();
1375
2233
 
1376
2234
  // ../core/dist/fetchTarget.js
@@ -1451,7 +2309,7 @@ async function fetchVerifiedSourceTar(options) {
1451
2309
  if (!source.sha256) {
1452
2310
  throw new DevtoolsError("SERVER_REJECTED", "\uC11C\uBC84\uAC00 \uBB34\uACB0\uC131 \uD574\uC2DC\uB97C \uC8FC\uC9C0 \uC54A\uC544 \uC18C\uC2A4\uB97C \uAC80\uC99D\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.", "\uC798\uCEE4\uB77C\uC5D0 \uBB38\uC758\uD574 \uC8FC\uC138\uC694. \uAC80\uC99D \uC5C6\uC774 \uC9C4\uD589\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.");
1453
2311
  }
1454
- const actual = createHash3("sha256").update(buffer).digest("hex");
2312
+ const actual = createHash5("sha256").update(buffer).digest("hex");
1455
2313
  if (actual !== source.sha256) {
1456
2314
  throw new DevtoolsError("SERVER_REJECTED", "\uBC1B\uC740 \uC18C\uC2A4\uAC00 \uC6D0\uBCF8\uACFC \uB2E4\uB985\uB2C8\uB2E4(\uBB34\uACB0\uC131 \uD655\uC778 \uC2E4\uD328).", "\uB124\uD2B8\uC6CC\uD06C \uBB38\uC81C\uC77C \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC2DC\uB3C4\uD574 \uC8FC\uC138\uC694.");
1457
2315
  }
@@ -1474,9 +2332,159 @@ var DEFAULT_TIMEOUT_MS2 = 10 * 60 * 1e3;
1474
2332
  // ../core/dist/tenantScope.js
1475
2333
  init_notice();
1476
2334
 
2335
+ // ../core/dist/npmChoice.js
2336
+ import { execFileSync } from "node:child_process";
2337
+ import { existsSync as existsSync7, realpathSync } from "node:fs";
2338
+ import { delimiter as delimiter2, isAbsolute as isAbsolute2, join as join11, normalize as normalize3, sep as sep4 } from "node:path";
2339
+ var MIN_SYSTEM_NPM_MAJOR = 9;
2340
+ function majorOf(version2) {
2341
+ const m = /^(\d+)\./.exec((version2 ?? "").trim());
2342
+ return m ? Number(m[1]) : null;
2343
+ }
2344
+ function chooseNpm(preference, probe) {
2345
+ const systemVersion = probe.system?.version ?? null;
2346
+ const systemMajor = majorOf(systemVersion);
2347
+ const systemOk = systemMajor !== null && systemMajor >= MIN_SYSTEM_NPM_MAJOR;
2348
+ if (preference === "bundled") {
2349
+ if (probe.bundled)
2350
+ return { kind: "bundled", path: probe.bundled, why: "\uC124\uC815\uC774 \uB3D9\uBD09\uBCF8\uC744 \uC9C0\uC815\uD588\uC2B5\uB2C8\uB2E4." };
2351
+ return {
2352
+ kind: "unavailable",
2353
+ why: "\uB3D9\uBD09 npm \uC744 \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.",
2354
+ hint: '\uC124\uC815 `zalkera.npm` \uC744 `"auto"` \uB85C \uB450\uBA74 \uC870\uAC74\uC744 \uB9CC\uC871\uD558\uB294 \uC2DC\uC2A4\uD15C npm \uC744 \uC501\uB2C8\uB2E4.'
2355
+ };
2356
+ }
2357
+ if (preference === "system") {
2358
+ if (probe.system === null) {
2359
+ return {
2360
+ kind: "unavailable",
2361
+ why: "\uC124\uC815\uC774 \uC2DC\uC2A4\uD15C npm \uC744 \uC9C0\uC815\uD588\uB294\uB370 PATH \uC5D0\uC11C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.",
2362
+ hint: 'npm \uC744 \uC124\uCE58\uD558\uAC70\uB098 \uC124\uC815 `zalkera.npm` \uC744 `"bundled"` \uB85C \uB418\uB3CC\uB9AC\uC2ED\uC2DC\uC624.'
2363
+ };
2364
+ }
2365
+ if (!systemOk) {
2366
+ return {
2367
+ kind: "unavailable",
2368
+ why: `\uC2DC\uC2A4\uD15C npm \uC774 ${systemVersion} \uC785\uB2C8\uB2E4 \u2014 \uB77D\uD30C\uC77C v3 \uB97C \uC77D\uC73C\uB824\uBA74 ${MIN_SYSTEM_NPM_MAJOR} \uC774\uC0C1\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4.`,
2369
+ hint: 'npm \uC744 \uC62C\uB9AC\uAC70\uB098 \uC124\uC815 `zalkera.npm` \uC744 `"bundled"` \uB85C \uB418\uB3CC\uB9AC\uC2ED\uC2DC\uC624.'
2370
+ };
2371
+ }
2372
+ return { kind: "system", version: probe.system.version, path: probe.system.path, why: "\uC124\uC815\uC774 \uC2DC\uC2A4\uD15C npm \uC744 \uC9C0\uC815\uD588\uC2B5\uB2C8\uB2E4." };
2373
+ }
2374
+ if (systemOk && probe.system) {
2375
+ return {
2376
+ kind: "system",
2377
+ version: probe.system.version,
2378
+ path: probe.system.path,
2379
+ why: `\uC2DC\uC2A4\uD15C npm ${probe.system.version} \uC774 \uC870\uAC74\uC744 \uB9CC\uC871\uD569\uB2C8\uB2E4.`
2380
+ };
2381
+ }
2382
+ if (probe.bundled) {
2383
+ const because = systemVersion === null ? "PATH \uC5D0 npm \uC774 \uC5C6\uC5B4" : `\uC2DC\uC2A4\uD15C npm \uC774 ${systemVersion} \uB77C\uC11C(${MIN_SYSTEM_NPM_MAJOR} \uBBF8\uB9CC)`;
2384
+ return { kind: "bundled", path: probe.bundled, why: `${because} \uB3D9\uBD09\uBCF8\uC744 \uC501\uB2C8\uB2E4.` };
2385
+ }
2386
+ return {
2387
+ kind: "unavailable",
2388
+ why: "\uB3D9\uBD09 npm \uB3C4 \uC5C6\uACE0 \uC4F8 \uC218 \uC788\uB294 \uC2DC\uC2A4\uD15C npm \uB3C4 \uC5C6\uC2B5\uB2C8\uB2E4.",
2389
+ hint: `npm ${MIN_SYSTEM_NPM_MAJOR} \uC774\uC0C1\uC744 \uC124\uCE58\uD55C \uB4A4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC2ED\uC2DC\uC624.`
2390
+ };
2391
+ }
2392
+ function npmArgvOf(choice, nodePath) {
2393
+ switch (choice.kind) {
2394
+ case "bundled":
2395
+ case "system":
2396
+ return [nodePath, choice.path, "install", "--ignore-scripts"];
2397
+ case "unavailable":
2398
+ return null;
2399
+ }
2400
+ }
2401
+ var PATH_OPS = { join: join11, isAbsolute: isAbsolute2, normalize: normalize3, sep: sep4 };
2402
+ function probeSystemNpm(nodePath, excludeUnder = [], env = process.env) {
2403
+ const entries = (env.PATH ?? "").split(delimiter2);
2404
+ for (const step of systemNpmSearchSteps(entries, PATH_OPS, excludeUnder)) {
2405
+ if (!existsSync7(step.path))
2406
+ continue;
2407
+ let cli = step.path;
2408
+ if (step.kind === "link") {
2409
+ try {
2410
+ cli = realpathSync(step.path);
2411
+ } catch {
2412
+ continue;
2413
+ }
2414
+ if (!acceptsResolvedNpmCli(cli, PATH_OPS, excludeUnder))
2415
+ continue;
2416
+ }
2417
+ try {
2418
+ const out = execFileSync(nodePath, [cli, "--version"], {
2419
+ encoding: "utf8",
2420
+ timeout: 3e3,
2421
+ stdio: ["ignore", "pipe", "ignore"],
2422
+ // 🔴 **자식에게도 넘긴다.** 종전에는 이 `env` 가 PATH 를 읽는 데만 쓰이고 자식은
2423
+ // `process.env` 를 물려받았다 — 그러면 VS Code 확장 호스트에 `ELECTRON_RUN_AS_NODE`
2424
+ // 가 없어 `execPath` 가 **Electron 으로** 뜨고, `npm-cli.js --version` 이 실패하거나
2425
+ // 3초 타임아웃을 먹는다. 그 결과 「PATH 에 npm 이 없다」는 **거짓 진단**이 나가고
2426
+ // `zalkera.npm="system"` 인 사람은 미리보기가 통째로 막힌다(심의 실측).
2427
+ // KDoc 이 이미 그렇게 약속하고 있었는데 코드가 안 지켰다.
2428
+ env
2429
+ });
2430
+ const version2 = out.trim();
2431
+ if (version2)
2432
+ return { version: version2, path: cli };
2433
+ } catch {
2434
+ }
2435
+ }
2436
+ return null;
2437
+ }
2438
+ function systemNpmSearchSteps(pathEntries, ops, excludeUnder = []) {
2439
+ const forbidden = excludeUnder.filter((d) => d && ops.isAbsolute(d)).map((d) => ops.normalize(d));
2440
+ const steps = [];
2441
+ for (const raw of pathEntries) {
2442
+ const entry = raw.trim();
2443
+ if (entry === "" || !ops.isAbsolute(entry))
2444
+ continue;
2445
+ if (isUnder(ops, ops.normalize(entry), forbidden))
2446
+ continue;
2447
+ for (const path of [ops.join(entry, "node_modules", "npm", "bin", "npm-cli.js"), ops.join(entry, "npm")]) {
2448
+ if (!ops.isAbsolute(path))
2449
+ continue;
2450
+ steps.push({ kind: path.endsWith(".js") ? "cli" : "link", path });
2451
+ }
2452
+ }
2453
+ return steps;
2454
+ }
2455
+ function acceptsResolvedNpmCli(resolved, ops, excludeUnder = []) {
2456
+ if (!ops.isAbsolute(resolved) || !resolved.endsWith(".js"))
2457
+ return false;
2458
+ const forbidden = excludeUnder.filter((d) => d && ops.isAbsolute(d)).map((d) => ops.normalize(d));
2459
+ return !isUnder(ops, ops.normalize(resolved), forbidden);
2460
+ }
2461
+ function isUnder(ops, path, roots) {
2462
+ return roots.some((root) => path === root || path.startsWith(root.endsWith(ops.sep) ? root : root + ops.sep));
2463
+ }
2464
+ function describeNpm(choice) {
2465
+ switch (choice.kind) {
2466
+ case "bundled":
2467
+ return `\uB3D9\uBD09 npm (${choice.path})`;
2468
+ case "system":
2469
+ return `\uC2DC\uC2A4\uD15C npm ${choice.version} (${choice.path})`;
2470
+ case "unavailable":
2471
+ return "\uC4F8 \uC218 \uC788\uB294 npm \uC5C6\uC74C";
2472
+ }
2473
+ }
2474
+
1477
2475
  // ../core/dist/upgradeNotice.js
1478
2476
  var UPGRADE_NOTICE_INTERVAL_MS = 24 * 60 * 60 * 1e3;
1479
2477
 
2478
+ // ../core/dist/tokenPath.js
2479
+ import { homedir as homedir2 } from "node:os";
2480
+ import { isAbsolute as isAbsolute3, join as join12, resolve as resolve5, sep as sep5 } from "node:path";
2481
+ function tokenPath(env = process.env, home = homedir2()) {
2482
+ const fallback = join12(home, ".config");
2483
+ const base = env.XDG_CONFIG_HOME;
2484
+ const inside = base !== void 0 && isAbsolute3(base) && (resolve5(base) === resolve5(home) || resolve5(base).startsWith(resolve5(home) + sep5));
2485
+ return join12(inside ? resolve5(base) : fallback, "zalkera", "auth.json");
2486
+ }
2487
+
1480
2488
  // ../core/dist/index.js
1481
2489
  init_notice();
1482
2490
 
@@ -1594,22 +2602,22 @@ function trimPaths(paths, cap = PATH_LIST_CAP, all = false) {
1594
2602
  }
1595
2603
 
1596
2604
  // ../core/dist/pull.js
1597
- import { lstat as lstat4, mkdir as mkdir3, readFile as readFile2, realpath as realpath2, rename as rename2, rmdir, stat, unlink, writeFile as writeFile4 } from "node:fs/promises";
2605
+ import { lstat as lstat4, mkdir as mkdir5, readFile as readFile5, realpath as realpath2, rename as rename2, rmdir, stat as stat3, unlink, writeFile as writeFile7 } from "node:fs/promises";
1598
2606
  init_errors();
1599
- import { basename as basename2, dirname, join as join7, relative as relative4, resolve as resolve3, sep as sep5 } from "node:path";
2607
+ import { basename as basename2, dirname as dirname2, join as join14, relative as relative4, resolve as resolve6, sep as sep7 } from "node:path";
1600
2608
 
1601
2609
  // ../core/dist/workdir.js
1602
2610
  init_errors();
1603
- import { createReadStream } from "node:fs";
1604
- import { createHash as createHash4 } from "node:crypto";
1605
- import { lstat as lstat3, readdir } from "node:fs/promises";
1606
- import { join as join6, sep as sep4 } from "node:path";
2611
+ import { createReadStream as createReadStream2 } from "node:fs";
2612
+ import { createHash as createHash6 } from "node:crypto";
2613
+ import { lstat as lstat3, readdir as readdir3 } from "node:fs/promises";
2614
+ import { join as join13, sep as sep6 } from "node:path";
1607
2615
  async function hashWorkdir(root, options = {}) {
1608
2616
  const manifest = {};
1609
2617
  const cap = Math.min(options.maxEntries ?? MAX_ENTRIES, MAX_ENTRIES);
1610
2618
  let seen = 0;
1611
2619
  async function walk(dir, prefix) {
1612
- const entries = await readdir(dir, { withFileTypes: true }).catch((error) => {
2620
+ const entries = await readdir3(dir, { withFileTypes: true }).catch((error) => {
1613
2621
  if (error?.code === "ENOENT")
1614
2622
  return [];
1615
2623
  throw error;
@@ -1621,7 +2629,7 @@ async function hashWorkdir(root, options = {}) {
1621
2629
  if (item.isSymbolicLink())
1622
2630
  continue;
1623
2631
  if (item.isDirectory()) {
1624
- await walk(join6(dir, item.name), relative5);
2632
+ await walk(join13(dir, item.name), relative5);
1625
2633
  continue;
1626
2634
  }
1627
2635
  if (!item.isFile())
@@ -1630,15 +2638,15 @@ async function hashWorkdir(root, options = {}) {
1630
2638
  if (seen > cap) {
1631
2639
  throw new DevtoolsError("LOCAL_TOO_LARGE", `\uC774 \uD3F4\uB354\uC758 \uD30C\uC77C\uC774 \uB108\uBB34 \uB9CE\uC2B5\uB2C8\uB2E4(${cap}\uAC1C \uCD08\uACFC).`, "\uC0AC\uC774\uD2B8 \uD3F4\uB354\uAC00 \uC544\uB2CC \uACF3\uC744 \uAC00\uB9AC\uD0A4\uACE0 \uC788\uC9C0 \uC54A\uC740\uC9C0 \uD655\uC778\uD574 \uC8FC\uC138\uC694.");
1632
2640
  }
1633
- manifest[relative5] = { sha256: await hashFile(join6(dir, item.name)) };
2641
+ manifest[relative5] = { sha256: await hashFile(join13(dir, item.name)) };
1634
2642
  }
1635
2643
  }
1636
2644
  await walk(root, "");
1637
2645
  return manifest;
1638
2646
  }
1639
2647
  async function hashFile(path) {
1640
- const hash = createHash4("sha256");
1641
- for await (const chunk of createReadStream(path))
2648
+ const hash = createHash6("sha256");
2649
+ for await (const chunk of createReadStream2(path))
1642
2650
  hash.update(chunk);
1643
2651
  return hash.digest("hex");
1644
2652
  }
@@ -1648,11 +2656,11 @@ async function resolveExisting(root, path) {
1648
2656
  const segments = path.split("/").filter((s) => s !== "");
1649
2657
  if (segments.length === 0)
1650
2658
  return null;
1651
- if (segments.some((s) => s === "." || s === ".." || s.includes(sep4)))
2659
+ if (segments.some((s) => s === "." || s === ".." || s.includes(sep6)))
1652
2660
  return null;
1653
2661
  let current = root;
1654
2662
  for (const part of segments) {
1655
- const next = join6(current, part);
2663
+ const next = join13(current, part);
1656
2664
  const info = await lstat3(next).catch(() => null);
1657
2665
  if (!info || info.isSymbolicLink())
1658
2666
  return null;
@@ -1666,7 +2674,7 @@ var SAVED_SUFFIX = ".zalkera-saved";
1666
2674
  async function pullSiteSource(options) {
1667
2675
  const report = options.onProgress ?? (() => {
1668
2676
  });
1669
- const root = resolve3(options.folder);
2677
+ const root = resolve6(options.folder);
1670
2678
  const found = await readLedger(root);
1671
2679
  const foreignLedger2 = found !== null && found.tenant !== options.api.tenantCode();
1672
2680
  const ledger = foreignLedger2 ? null : found;
@@ -1760,14 +2768,14 @@ async function requireNoLinkedTargets(root, paths) {
1760
2768
  const segments = path.split("/").filter((s) => s !== "");
1761
2769
  let current = root;
1762
2770
  for (const part of segments) {
1763
- current = join7(current, part);
2771
+ current = join14(current, part);
1764
2772
  if (checked.has(current))
1765
2773
  continue;
1766
2774
  checked.add(current);
1767
2775
  const info = await lstat4(current).catch(() => null);
1768
2776
  if (!info?.isSymbolicLink())
1769
2777
  continue;
1770
- throw new DevtoolsError("PULL_WOULD_OVERWRITE", `\uC774 \uD3F4\uB354\uC758 ${relative4(root, current).split(sep5).join("/")} \uAC00 \uB2E4\uB978 \uACF3\uC744 \uAC00\uB9AC\uD0A4\uB294 \uBC14\uB85C\uAC00\uAE30\uC785\uB2C8\uB2E4. \uC544\uBB34\uAC83\uB3C4 \uBC1B\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.`, "\uADF8 \uBC14\uB85C\uAC00\uAE30\uB97C \uC9C0\uC6B0\uACE0 \uB2E4\uC2DC \uC2E4\uD589\uD574 \uC8FC\uC138\uC694. \uADF8\uB300\uB85C \uB450\uBA74 \uC774 \uD3F4\uB354 \uBC16\uC758 \uAC83\uC744 \uB36E\uC5B4\uC4F0\uAC8C \uB429\uB2C8\uB2E4.");
2778
+ throw new DevtoolsError("PULL_WOULD_OVERWRITE", `\uC774 \uD3F4\uB354\uC758 ${relative4(root, current).split(sep7).join("/")} \uAC00 \uB2E4\uB978 \uACF3\uC744 \uAC00\uB9AC\uD0A4\uB294 \uBC14\uB85C\uAC00\uAE30\uC785\uB2C8\uB2E4. \uC544\uBB34\uAC83\uB3C4 \uBC1B\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.`, "\uADF8 \uBC14\uB85C\uAC00\uAE30\uB97C \uC9C0\uC6B0\uACE0 \uB2E4\uC2DC \uC2E4\uD589\uD574 \uC8FC\uC138\uC694. \uADF8\uB300\uB85C \uB450\uBA74 \uC774 \uD3F4\uB354 \uBC16\uC758 \uAC83\uC744 \uB36E\uC5B4\uC4F0\uAC8C \uB429\uB2C8\uB2E4.");
1771
2779
  }
1772
2780
  }
1773
2781
  }
@@ -1783,13 +2791,13 @@ function withoutExcluded(declared) {
1783
2791
  return { incoming, dropped: dropped.sort() };
1784
2792
  }
1785
2793
  async function readLedger(root) {
1786
- const text = await readFile2(join7(root, SYNC_LEDGER_PATH), "utf8").catch(() => null);
2794
+ const text = await readFile5(join14(root, SYNC_LEDGER_PATH), "utf8").catch(() => null);
1787
2795
  return text === null ? null : parseSyncLedger(text);
1788
2796
  }
1789
2797
  async function writeLedger(root, ledger) {
1790
2798
  try {
1791
2799
  const dir = await ensureOwnDir(root, ...SYNC_LEDGER_PATH.split("/").slice(0, -1));
1792
- await writeOwnFile(join7(dir, basename2(SYNC_LEDGER_PATH)), serializeSyncLedger(ledger));
2800
+ await writeOwnFile(join14(dir, basename2(SYNC_LEDGER_PATH)), serializeSyncLedger(ledger));
1793
2801
  } catch {
1794
2802
  return false;
1795
2803
  }
@@ -1798,17 +2806,17 @@ async function writeLedger(root, ledger) {
1798
2806
  return true;
1799
2807
  }
1800
2808
  async function ignoreLedger(root) {
1801
- const info = join7(root, ".git", "info");
1802
- const exists = await stat(join7(root, ".git")).then((s) => s.isDirectory()).catch(() => false);
2809
+ const info = join14(root, ".git", "info");
2810
+ const exists = await stat3(join14(root, ".git")).then((s) => s.isDirectory()).catch(() => false);
1803
2811
  if (!exists)
1804
2812
  return;
1805
- const path = join7(info, "exclude");
1806
- const current = await readFile2(path, "utf8").catch(() => "");
2813
+ const path = join14(info, "exclude");
2814
+ const current = await readFile5(path, "utf8").catch(() => "");
1807
2815
  if (current.split(/\r?\n/).some((line) => line.trim() === SYNC_LEDGER_PATH))
1808
2816
  return;
1809
- await mkdir3(info, { recursive: true });
2817
+ await mkdir5(info, { recursive: true });
1810
2818
  const prefix = current === "" || current.endsWith("\n") ? "" : "\n";
1811
- await writeFile4(path, `${current}${prefix}${SYNC_LEDGER_PATH}
2819
+ await writeFile7(path, `${current}${prefix}${SYNC_LEDGER_PATH}
1812
2820
  `);
1813
2821
  }
1814
2822
  async function currentGeneration(api) {
@@ -1824,10 +2832,10 @@ async function applyDeletes(root, paths) {
1824
2832
  await unlink(target).catch(() => {
1825
2833
  });
1826
2834
  count2 += 1;
1827
- let dir = dirname(target);
2835
+ let dir = dirname2(target);
1828
2836
  while (dir.length > root.length && dir.startsWith(root)) {
1829
2837
  parents.add(dir);
1830
- dir = dirname(dir);
2838
+ dir = dirname2(dir);
1831
2839
  }
1832
2840
  }
1833
2841
  for (const dir of [...parents].sort((a, b) => b.length - a.length)) {
@@ -1839,14 +2847,14 @@ async function applyDeletes(root, paths) {
1839
2847
  async function setAside(root, plan) {
1840
2848
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1841
2849
  const real = await realpath2(root).catch(() => root);
1842
- const saved = join7(dirname(real), `${basename2(real)}${SAVED_SUFFIX}`, stamp);
2850
+ const saved = join14(dirname2(real), `${basename2(real)}${SAVED_SUFFIX}`, stamp);
1843
2851
  const moved = [];
1844
2852
  for (const conflict of plan.conflicts) {
1845
2853
  const from = await resolveExisting(root, conflict.path);
1846
2854
  if (!from)
1847
2855
  continue;
1848
- const to = join7(saved, conflict.path);
1849
- await mkdir3(dirname(to), { recursive: true });
2856
+ const to = join14(saved, conflict.path);
2857
+ await mkdir5(dirname2(to), { recursive: true });
1850
2858
  await rename2(from, to);
1851
2859
  moved.push(conflict.path);
1852
2860
  }
@@ -1867,13 +2875,13 @@ ${list}`,
1867
2875
 
1868
2876
  // ../core/dist/baseline.js
1869
2877
  init_errors();
1870
- import { readFile as readFile3 } from "node:fs/promises";
1871
- import { join as join8, resolve as resolve4 } from "node:path";
2878
+ import { readFile as readFile6 } from "node:fs/promises";
2879
+ import { join as join15, resolve as resolve7 } from "node:path";
1872
2880
  async function rebuildBaseline(options) {
1873
2881
  const report = options.onProgress ?? (() => {
1874
2882
  });
1875
- const root = resolve4(options.folder);
1876
- const previousText = await readFile3(join8(root, SYNC_LEDGER_PATH), "utf8").catch(() => null);
2883
+ const root = resolve7(options.folder);
2884
+ const previousText = await readFile6(join15(root, SYNC_LEDGER_PATH), "utf8").catch(() => null);
1877
2885
  const previous = previousText === null ? null : parseSyncLedger(previousText);
1878
2886
  report("\uD310\uC758 \uD30C\uC77C \uBAA9\uB85D\uC744 \uB2E4\uC2DC \uC77D\uB294 \uC911\uC785\uB2C8\uB2E4\u2026");
1879
2887
  const tar = await fetchVerifiedSourceTar({
@@ -1973,8 +2981,8 @@ function ledgerCorrection(ledger, draft) {
1973
2981
  // ../core/dist/push.js
1974
2982
  init_errors();
1975
2983
  init_notice();
1976
- import { readFile as readFile4, stat as stat2 } from "node:fs/promises";
1977
- import { basename as basename3, join as join9, resolve as resolve5 } from "node:path";
2984
+ import { readFile as readFile7, stat as stat4 } from "node:fs/promises";
2985
+ import { basename as basename3, join as join16, resolve as resolve8 } from "node:path";
1978
2986
 
1979
2987
  // ../core/dist/pushPlan.js
1980
2988
  function effectiveSha(base, draft, path) {
@@ -2034,7 +3042,7 @@ var MAX_PUSH_BYTES = 3.5 * 1024 * 1024;
2034
3042
  async function pushSiteSource(options) {
2035
3043
  const report = options.onProgress ?? (() => {
2036
3044
  });
2037
- const root = resolve5(options.folder);
3045
+ const root = resolve8(options.folder);
2038
3046
  const ledger = await readLedger(root);
2039
3047
  if (!ledger)
2040
3048
  throw ledgerUnknown();
@@ -2085,7 +3093,7 @@ async function pushSiteSource(options) {
2085
3093
  let sent = null;
2086
3094
  for (let round = 0; round < MAX_PUSH_ATTEMPTS; round += 1) {
2087
3095
  requireWithinLimits(plan.edits);
2088
- sent = await attempt(options.api, root, plan.edits, report);
3096
+ sent = await attempt2(options.api, root, plan.edits, report);
2089
3097
  if (sent.kind === "ok")
2090
3098
  break;
2091
3099
  if (sent.kind === "lost") {
@@ -2215,7 +3223,7 @@ function requireWithinLimits(edits) {
2215
3223
  throw new DevtoolsError("PUSH_TOO_LARGE", `\uD55C \uBC88\uC5D0 \uC62C\uB9B4 \uC218 \uC788\uB294 \uD30C\uC77C\uC740 ${MAX_PUSH_ENTRIES}\uAC1C\uC778\uB370 ${edits.length}\uAC1C\uC785\uB2C8\uB2E4.`, "\uACE0\uCE5C \uAC83\uC774 \uC774\uB9CC\uD07C \uB9CE\uC73C\uBA74 \uD3B8\uC9D1\uC774 \uC544\uB2C8\uB77C \uC0C8 \uC18C\uC2A4\uC785\uB2C8\uB2E4 \u2014 \uCF58\uC194\uC5D0\uC11C \uC18C\uC2A4\uB97C \uD1B5\uC9F8\uB85C \uC62C\uB9AC\uB294 \uCABD\uC774 \uB9DE\uC2B5\uB2C8\uB2E4.");
2216
3224
  }
2217
3225
  }
2218
- async function attempt(api, root, edits, report) {
3226
+ async function attempt2(api, root, edits, report) {
2219
3227
  const body = await bodyOf(root, edits);
2220
3228
  try {
2221
3229
  const result = await api.editDraft(body);
@@ -2251,13 +3259,13 @@ async function bodyOf(root, edits) {
2251
3259
  body.push({ path: edit.path, remove: true, ...edit.baseSha256 === null ? {} : { baseSha256: edit.baseSha256 } });
2252
3260
  continue;
2253
3261
  }
2254
- const size = (await stat2(join9(root, edit.path))).size;
3262
+ const size = (await stat4(join16(root, edit.path))).size;
2255
3263
  if (size > MAX_PUSH_FILE_BYTES) {
2256
3264
  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.");
2257
3265
  }
2258
3266
  if (total + size > MAX_PUSH_BYTES)
2259
3267
  throw tooLarge();
2260
- const raw = await readFile4(join9(root, edit.path));
3268
+ const raw = await readFile7(join16(root, edit.path));
2261
3269
  const breach = templateBreach(basename3(edit.path), raw);
2262
3270
  if (breach !== null) {
2263
3271
  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.");
@@ -2293,13 +3301,13 @@ function baseMoved(mine, now, dirty) {
2293
3301
  }
2294
3302
 
2295
3303
  // ../core/dist/draftLifecycle.js
2296
- import { resolve as resolve6 } from "node:path";
3304
+ import { resolve as resolve9 } from "node:path";
2297
3305
  init_errors();
2298
- import { rm as rm3 } from "node:fs/promises";
3306
+ import { rm as rm5 } from "node:fs/promises";
2299
3307
  async function publishDraft(options) {
2300
3308
  const report = options.onProgress ?? (() => {
2301
3309
  });
2302
- const root = resolve6(options.folder);
3310
+ const root = resolve9(options.folder);
2303
3311
  report("\uD3B8\uC9D1\uC744 \uC0C8 \uBC84\uC804\uC73C\uB85C \uC62C\uB9AC\uB294 \uC911\uC785\uB2C8\uB2E4\u2026");
2304
3312
  const published = await requireConsent(() => options.api.publishDraft(options.label, options.discardPendingChanges === true)).catch((error) => {
2305
3313
  if (error instanceof DevtoolsError && error.serverCode === "DRAFT_BASE_MOVED") {
@@ -2324,7 +3332,7 @@ async function publishDraft(options) {
2324
3332
  async function rollbackRevision(options) {
2325
3333
  const report = options.onProgress ?? (() => {
2326
3334
  });
2327
- const root = resolve6(options.folder);
3335
+ const root = resolve9(options.folder);
2328
3336
  const active = await activeRevisionNo(options.api);
2329
3337
  if (options.revisionNo === active) {
2330
3338
  throw new DevtoolsError("ROLLBACK_IS_DISCARD", `${options.revisionNo}\uD310\uC740 \uC9C0\uAE08 \uCF1C\uC838 \uC788\uB294 \uBC84\uC804\uC785\uB2C8\uB2E4 \u2014 \uB418\uB3CC\uB9B4 \uAC83\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.`, "\uD3B8\uC9D1 \uC911\uC778 \uAC83\uC744 \uBC84\uB9AC\uB824\uB294 \uAC83\uC774\uB77C\uBA74 `zalkera discard` \uB97C \uC4F0\uC138\uC694. \uADF8\uCABD\uC740 \uD310\uC744 \uC62E\uAE30\uC9C0 \uC54A\uACE0 \uD3B8\uC9D1\uB9CC \uBC84\uB9BD\uB2C8\uB2E4.");
@@ -2371,7 +3379,7 @@ async function rollbackRevision(options) {
2371
3379
  async function discardDraft(options) {
2372
3380
  const report = options.onProgress ?? (() => {
2373
3381
  });
2374
- const root = resolve6(options.folder);
3382
+ const root = resolve9(options.folder);
2375
3383
  const active = await activeRevisionNo(options.api);
2376
3384
  const now = await options.api.draftFiles().catch(() => null);
2377
3385
  if (now === null || (now.generation ?? null) !== options.plan.generation) {
@@ -2381,7 +3389,7 @@ async function discardDraft(options) {
2381
3389
  const result = await options.api.activateRevision(active, options.discardPending === true).catch(async (error) => {
2382
3390
  if (!needsDiscardConsent(error) || !(error instanceof DevtoolsError))
2383
3391
  throw error;
2384
- if (creditsAtStake(error))
3392
+ if (stopsHere(error))
2385
3393
  throw asConsentRequired(error);
2386
3394
  report("\uC11C\uBC84\uAC00 \uD655\uC778\uC744 \uD55C \uBC88 \uB354 \uC694\uAD6C\uD574 \uBC29\uAE08 \uC8FC\uC2E0 \uB3D9\uC758\uB97C \uADF8\uB300\uB85C \uC787\uC2B5\uB2C8\uB2E4\u2026");
2387
3395
  return options.api.activateRevision(active, true);
@@ -2403,7 +3411,7 @@ async function forgetLedger(root) {
2403
3411
  const target = await resolveExisting(root, SYNC_LEDGER_PATH);
2404
3412
  if (target === null)
2405
3413
  return;
2406
- await rm3(target, { force: true }).catch(() => void 0);
3414
+ await rm5(target, { force: true }).catch(() => void 0);
2407
3415
  }
2408
3416
  async function activeRevisionNo(api) {
2409
3417
  const rows = await api.listRevisions().catch(() => null);
@@ -2431,6 +3439,10 @@ function asConsentRequired(error) {
2431
3439
  return new DevtoolsError("DISCARD_CONSENT_REQUIRED", error.message, creditsAtStake(error) ? "\uADF8\uB798\uB3C4 \uACC4\uC18D\uD558\uB824\uBA74 `--discard-pending` \uC744 \uBD99\uC5EC \uC8FC\uC138\uC694. \uC4F4 \uD06C\uB808\uB527\uC740 \uB3CC\uC544\uC624\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4." : "\uADF8\uB798\uB3C4 \uACC4\uC18D\uD558\uB824\uBA74 `--discard-pending` \uC744 \uBD99\uC5EC \uC8FC\uC138\uC694.", error);
2432
3440
  }
2433
3441
  var PENDING_MARK = "pendingAiChanges";
3442
+ var SHOWN_MARK = "draft";
3443
+ function stopsHere(error) {
3444
+ return error.paths.length === 0 || error.paths.some((p) => p !== SHOWN_MARK);
3445
+ }
2434
3446
  function creditsAtStake(error) {
2435
3447
  return error.paths.length === 0 || error.paths.includes(PENDING_MARK);
2436
3448
  }
@@ -2469,7 +3481,7 @@ function planStranded(input) {
2469
3481
  }
2470
3482
 
2471
3483
  // src/main.ts
2472
- import { spawn } from "node:child_process";
3484
+ import { spawn as spawn3 } from "node:child_process";
2473
3485
 
2474
3486
  // src/args.ts
2475
3487
  function parseArgs(argv) {
@@ -2519,20 +3531,13 @@ function flagValue(flags, name) {
2519
3531
 
2520
3532
  // src/context.ts
2521
3533
  import { readFileSync } from "node:fs";
2522
- import { readFile as readFile6 } from "node:fs/promises";
3534
+ import { readFile as readFile9 } from "node:fs/promises";
2523
3535
  import { fileURLToPath } from "node:url";
2524
- import { join as join11, resolve as resolve8 } from "node:path";
3536
+ import { join as join18, resolve as resolve11 } from "node:path";
2525
3537
 
2526
3538
  // src/tokenStore.ts
2527
- import { chmod as chmod2, readFile as readFile5, rm as rm4 } from "node:fs/promises";
2528
- import { homedir } from "node:os";
2529
- import { basename as basename4, dirname as dirname2, isAbsolute as isAbsolute2, join as join10, resolve as resolve7, sep as sep6 } from "node:path";
2530
- function tokenPath(env = process.env, home = homedir()) {
2531
- const fallback = join10(home, ".config");
2532
- const base = env.XDG_CONFIG_HOME;
2533
- const inside = base !== void 0 && isAbsolute2(base) && (resolve7(base) === resolve7(home) || resolve7(base).startsWith(resolve7(home) + sep6));
2534
- return join10(inside ? resolve7(base) : fallback, "zalkera", "auth.json");
2535
- }
3539
+ import { chmod as chmod3, readFile as readFile8, rm as rm6 } from "node:fs/promises";
3540
+ import { basename as basename4, dirname as dirname3 } from "node:path";
2536
3541
  var FileTokenStore = class {
2537
3542
  // ⚠ 매개변수 프로퍼티(`constructor(private path)`)를 안 쓴다 — Node 의 타입 제거 실행기가
2538
3543
  // 그 문법을 거절해 **시험이 아예 안 돈다.** 이 레포의 시험은 전부 그 실행기로 돈다.
@@ -2541,7 +3546,7 @@ var FileTokenStore = class {
2541
3546
  this.path = path;
2542
3547
  }
2543
3548
  async read() {
2544
- const text = await readFile5(this.path, "utf8").catch(() => null);
3549
+ const text = await readFile8(this.path, "utf8").catch(() => null);
2545
3550
  if (text === null) return null;
2546
3551
  try {
2547
3552
  const raw = JSON.parse(text);
@@ -2561,14 +3566,14 @@ var FileTokenStore = class {
2561
3566
  }
2562
3567
  }
2563
3568
  async write(tokens) {
2564
- const dir = await ensureOwnDir(dirname2(dirname2(this.path)), basename4(dirname2(this.path)));
2565
- await chmod2(dir, 448).catch(() => {
3569
+ const dir = await ensureOwnDir(dirname3(dirname3(this.path)), basename4(dirname3(this.path)));
3570
+ await chmod3(dir, 448).catch(() => {
2566
3571
  });
2567
3572
  await writeOwnFile(this.path, JSON.stringify(tokens), 384);
2568
- await chmod2(this.path, 384);
3573
+ await chmod3(this.path, 384);
2569
3574
  }
2570
3575
  async clear() {
2571
- await rm4(this.path, { force: true });
3576
+ await rm6(this.path, { force: true });
2572
3577
  }
2573
3578
  };
2574
3579
 
@@ -2588,18 +3593,18 @@ function serverUrlOf(env = process.env) {
2588
3593
  }
2589
3594
  async function tenantOf(folder, explicit) {
2590
3595
  if (explicit) return TENANT_CODE.test(explicit) ? explicit : null;
2591
- const mark = parseSourceMark(await readFile6(join11(folder, SOURCE_MARK_PATH), "utf8").catch(() => null));
3596
+ const mark = parseSourceMark(await readFile9(join18(folder, SOURCE_MARK_PATH), "utf8").catch(() => null));
2592
3597
  if (mark) return mark.tenant;
2593
- const text = await readFile6(join11(folder, SYNC_LEDGER_PATH), "utf8").catch(() => null);
3598
+ const text = await readFile9(join18(folder, SYNC_LEDGER_PATH), "utf8").catch(() => null);
2594
3599
  return text === null ? null : parseSyncLedger(text)?.tenant ?? null;
2595
3600
  }
2596
3601
  async function openAuth(options = {}) {
2597
3602
  const handshake = await fetchHandshake(serverUrlOf(options.env ?? process.env), version(), fetch, UPGRADE_HOW);
2598
- return { auth: handshake.auth, store: options.store ?? new FileTokenStore() };
3603
+ return { auth: { ...handshake.auth, loginHow: LOGIN_HOW }, store: options.store ?? new FileTokenStore() };
2599
3604
  }
2600
3605
  async function openContext(options = {}) {
2601
3606
  const env = options.env ?? process.env;
2602
- const folder = resolve8(options.folder ?? process.cwd());
3607
+ const folder = resolve11(options.folder ?? process.cwd());
2603
3608
  const serverUrl = serverUrlOf(env);
2604
3609
  const tenant = await tenantOf(folder, options.tenant);
2605
3610
  if (!tenant) {
@@ -2613,7 +3618,7 @@ async function openContext(options = {}) {
2613
3618
  `);
2614
3619
  const handshake = await fetchHandshake(serverUrl, version(), fetch, UPGRADE_HOW);
2615
3620
  const store = options.store ?? new FileTokenStore();
2616
- const auth = handshake.auth;
3621
+ const auth = { ...handshake.auth, loginHow: LOGIN_HOW };
2617
3622
  const api = new ZalkeraApi({
2618
3623
  apiBase: serverUrl,
2619
3624
  accessToken: () => getAccessToken(auth, store),
@@ -2630,6 +3635,7 @@ async function misplacedToolLines(folder) {
2630
3635
  " `package.json` \uC5D0\uC11C \uBE7C \uC8FC\uC138\uC694 \u2014 \uADF8\uB300\uB85C \uB450\uBA74 \uC0AC\uC774\uD2B8\uB97C \uC9C0\uC744 \uB54C \uD568\uAED8 \uC124\uCE58\uB429\uB2C8\uB2E4."
2631
3636
  ];
2632
3637
  }
3638
+ var LOGIN_HOW = "\uD130\uBBF8\uB110\uC5D0\uC11C `zalkera login` \uC744 \uD55C \uBC88 \uC2E4\uD589\uD574 \uC8FC\uC138\uC694.";
2633
3639
  var UPGRADE_HOW = "`npx @zalkera/cli@latest <\uBA85\uB839>` \uC73C\uB85C \uBD80\uB974\uAC70\uB098, \uC804\uC5ED \uC124\uCE58\uB97C \uC4F0\uC2E0\uB2E4\uBA74 `npm i -g @zalkera/cli@latest` \uB97C \uC2E4\uD589\uD574 \uC8FC\uC138\uC694.";
2634
3640
  function version() {
2635
3641
  let raw;
@@ -2639,20 +3645,24 @@ function version() {
2639
3645
  throw new DevtoolsError(
2640
3646
  "INSTALL_BROKEN",
2641
3647
  "\uC774 \uB3C4\uAD6C\uC758 \uC124\uCE58\uAC00 \uC628\uC804\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 \uC790\uAE30 \uBC84\uC804\uC744 \uC77D\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.",
2642
- "`npm i -g @zalkera/cli@latest` \uB85C \uB2E4\uC2DC \uC124\uCE58\uD558\uAC70\uB098, `npx @zalkera/cli@latest <\uBA85\uB839>` \uC73C\uB85C \uC2E4\uD589\uD574 \uC8FC\uC138\uC694.",
3648
+ REINSTALL_HOW,
2643
3649
  error
2644
3650
  );
2645
3651
  }
2646
- const declared = raw.version;
2647
- if (typeof declared !== "string" || declared === "") {
3652
+ return versionFrom(raw);
3653
+ }
3654
+ function versionFrom(raw) {
3655
+ const declared = raw?.version;
3656
+ if (typeof declared !== "string" || declared.trim() === "") {
2648
3657
  throw new DevtoolsError(
2649
3658
  "INSTALL_BROKEN",
2650
3659
  "\uC774 \uB3C4\uAD6C\uC758 \uC124\uCE58\uAC00 \uC628\uC804\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 \uBC84\uC804 \uD45C\uAE30\uAC00 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.",
2651
- "`npm i -g @zalkera/cli@latest` \uB85C \uB2E4\uC2DC \uC124\uCE58\uD558\uAC70\uB098, `npx @zalkera/cli@latest <\uBA85\uB839>` \uC73C\uB85C \uC2E4\uD589\uD574 \uC8FC\uC138\uC694."
3660
+ REINSTALL_HOW
2652
3661
  );
2653
3662
  }
2654
3663
  return declared;
2655
3664
  }
3665
+ var REINSTALL_HOW = "`npm i -g @zalkera/cli@latest` \uB85C \uB2E4\uC2DC \uC124\uCE58\uD558\uAC70\uB098, `npx @zalkera/cli@latest <\uBA85\uB839>` \uC73C\uB85C \uC2E4\uD589\uD574 \uC8FC\uC138\uC694.";
2656
3666
 
2657
3667
  // src/confirm.ts
2658
3668
  import { createInterface } from "node:readline/promises";
@@ -2785,6 +3795,264 @@ function describeStranded(plan, verbose = false) {
2785
3795
  }
2786
3796
  var DISCARD_PHRASE = "\uBC84\uB9BD\uB2C8\uB2E4";
2787
3797
 
3798
+ // src/jsonRpc.ts
3799
+ var RPC = {
3800
+ PARSE_ERROR: -32700,
3801
+ INVALID_REQUEST: -32600,
3802
+ METHOD_NOT_FOUND: -32601,
3803
+ INVALID_PARAMS: -32602,
3804
+ INTERNAL_ERROR: -32603
3805
+ };
3806
+ var MAX_LINE_BYTES = 8 * 1024 * 1024;
3807
+ var RpcError = class extends Error {
3808
+ code;
3809
+ constructor(code2, message) {
3810
+ super(message);
3811
+ this.name = "RpcError";
3812
+ this.code = code2;
3813
+ }
3814
+ };
3815
+ async function serveStdio(input, output, handle) {
3816
+ const write = (message) => {
3817
+ output.write(`${JSON.stringify(message)}
3818
+ `);
3819
+ };
3820
+ let buffered = "";
3821
+ let skipping = false;
3822
+ input.setEncoding("utf8");
3823
+ for await (const chunk of input) {
3824
+ buffered += chunk;
3825
+ if (skipping) {
3826
+ const end = buffered.indexOf("\n");
3827
+ if (end === -1) {
3828
+ buffered = "";
3829
+ continue;
3830
+ }
3831
+ buffered = buffered.slice(end + 1);
3832
+ skipping = false;
3833
+ }
3834
+ if (buffered.length > MAX_LINE_BYTES && !buffered.includes("\n")) {
3835
+ buffered = "";
3836
+ skipping = true;
3837
+ write({ jsonrpc: "2.0", id: null, error: { code: RPC.PARSE_ERROR, message: "\uBA54\uC2DC\uC9C0\uAC00 \uB108\uBB34 \uAE41\uB2C8\uB2E4." } });
3838
+ continue;
3839
+ }
3840
+ let cut = buffered.indexOf("\n");
3841
+ while (cut !== -1) {
3842
+ const line = buffered.slice(0, cut).trim();
3843
+ buffered = buffered.slice(cut + 1);
3844
+ cut = buffered.indexOf("\n");
3845
+ if (line === "") continue;
3846
+ let request;
3847
+ try {
3848
+ request = JSON.parse(line);
3849
+ } catch {
3850
+ write({ jsonrpc: "2.0", id: null, error: { code: RPC.PARSE_ERROR, message: "JSON \uC774 \uC544\uB2D9\uB2C8\uB2E4." } });
3851
+ continue;
3852
+ }
3853
+ if (typeof request !== "object" || request === null || Array.isArray(request)) {
3854
+ write({
3855
+ jsonrpc: "2.0",
3856
+ id: null,
3857
+ error: {
3858
+ code: RPC.INVALID_REQUEST,
3859
+ message: Array.isArray(request) ? "\uC774 \uC11C\uBC84\uB294 \uBC30\uCE58 \uC694\uCCAD\uC744 \uBC1B\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4." : "\uC694\uCCAD\uC774 \uAC1D\uCCB4\uAC00 \uC544\uB2D9\uB2C8\uB2E4."
3860
+ }
3861
+ });
3862
+ continue;
3863
+ }
3864
+ const id = request.id;
3865
+ const isNotification = !("id" in request) || id === void 0;
3866
+ if (typeof request.method !== "string") {
3867
+ if (!isNotification) {
3868
+ write({ jsonrpc: "2.0", id: id ?? null, error: { code: RPC.INVALID_REQUEST, message: "method \uAC00 \uC5C6\uC2B5\uB2C8\uB2E4." } });
3869
+ }
3870
+ continue;
3871
+ }
3872
+ try {
3873
+ const result = await handle(request.method, request.params);
3874
+ if (!isNotification) write({ jsonrpc: "2.0", id: id ?? null, result: result ?? {} });
3875
+ } catch (error) {
3876
+ if (isNotification) continue;
3877
+ const code2 = error instanceof RpcError ? error.code : RPC.INTERNAL_ERROR;
3878
+ const message = error instanceof Error ? error.message : String(error);
3879
+ write({ jsonrpc: "2.0", id: id ?? null, error: { code: code2, message } });
3880
+ }
3881
+ }
3882
+ }
3883
+ }
3884
+
3885
+ // src/mcpServer.ts
3886
+ var PROTOCOL_VERSIONS = ["2025-06-18", "2025-03-26", "2024-11-05"];
3887
+ var PROTOCOL_VERSION = PROTOCOL_VERSIONS[0];
3888
+ var NO_ARGS = { type: "object", properties: {}, additionalProperties: false };
3889
+ function describeForAgent(status, draft) {
3890
+ const lines = [
3891
+ `\uC0AC\uC774\uD2B8: ${siteCodeOf(status.tenant)}`,
3892
+ `\uCF1C\uC9C4 \uBC84\uC804: ${count(status.activeRevisionNo)}`,
3893
+ `\uC774 \uD3F4\uB354\uC758 \uAE30\uC900 \uBC84\uC804: ${count(status.baseRevisionNo)}`,
3894
+ // ⚠ **셋을 다 싣는다.** 고친 것만 세면 **새로 만들기만 한 폴더가 「0개」**가 된다 —
3895
+ // AI 가 새 페이지를 만드는 것이 이 기능의 주 용도다(심의 실측).
3896
+ `\uC774 \uD3F4\uB354\uC5D0\uC11C \uACE0\uCE5C \uAC83: ${status.changed.length}\uAC1C \xB7 \uC0C8\uB85C \uB9CC\uB4E0 \uAC83: ${status.added.length}\uAC1C \xB7 \uC9C0\uC6B4 \uAC83: ${status.removed.length}\uAC1C`,
3897
+ `\uC0AC\uC774\uD2B8 \uCABD\uC5D0 \uAC78\uB9B0 \uD3B8\uC9D1: ${draft === null ? "\uBAA8\uB984(\uC11C\uBC84\uB97C \uBABB \uC77D\uC74C)" : `${status.draftPaths}\uAC1C`}`
3898
+ ];
3899
+ if (status.blockers.length > 0) {
3900
+ lines.push(`\uB9C9\uD600 \uC788\uB294 \uAC83: ${status.blockers.map(blockerText).join(" \xB7 ")}`);
3901
+ }
3902
+ return lines.join("\n");
3903
+ }
3904
+ function siteCodeOf(raw) {
3905
+ return raw !== null && TENANT_CODE.test(raw) ? raw : "\uBAA8\uB984";
3906
+ }
3907
+ function blockerText(blocker) {
3908
+ switch (blocker) {
3909
+ case "LEDGER_UNKNOWN":
3910
+ return "\uC774 \uD3F4\uB354\uC758 \uAE30\uC900 \uAE30\uB85D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4(\uC0AC\uB78C\uC774 `zalkera baseline` \uC744 \uD55C \uBC88 \uC2E4\uD589\uD574\uC57C \uD569\uB2C8\uB2E4)";
3911
+ case "SERVER_UNREADABLE":
3912
+ return "\uC0AC\uC774\uD2B8 \uC0C1\uD0DC\uB97C \uC77D\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4(\uB85C\uADF8\uC778\xB7\uC5F0\uACB0\uC744 \uD655\uC778\uD574 \uC8FC\uC138\uC694)";
3913
+ case "STRANDED":
3914
+ return "\uC0AC\uC774\uD2B8 \uCABD \uD3B8\uC9D1\uC774 \uC61B \uBC84\uC804 \uC704\uC5D0 \uC788\uC2B5\uB2C8\uB2E4(\uC0AC\uB78C\uC774 \uBC84\uB9AC\uAC70\uB098 \uB418\uB3CC\uB824\uC57C \uD569\uB2C8\uB2E4)";
3915
+ default:
3916
+ return plainNotice(blocker, 60);
3917
+ }
3918
+ }
3919
+ var TOOLS = [
3920
+ {
3921
+ name: "zalkera_status",
3922
+ title: "\uC0AC\uC774\uD2B8\uC640 \uC774 \uD3F4\uB354\uC758 \uCC28\uC774",
3923
+ description: "\uC774 \uD3F4\uB354\uC640 \uC798\uCEE4\uB77C \uC0AC\uC774\uD2B8\uAC00 \uC5B4\uB5BB\uAC8C \uB2E4\uB978\uC9C0 \uBCF8\uB2E4. \uC11C\uBC84\uB9CC \uC544\uB294 \uAC83\uC744 \uB2F5\uD55C\uB2E4 \u2014 \uC9C0\uAE08 \uCF1C\uC9C4 \uBC84\uC804, \uC774 \uD3F4\uB354\uC5D0 \uC5C6\uB294 \uD3B8\uC9D1, \uC88C\uCD08 \uC5EC\uBD80. \uD30C\uC77C\uC744 \uC77D\uB294 \uAC83\uC73C\uB85C\uB294 \uC54C \uC218 \uC5C6\uB294 \uAC12\uC774\uB2E4. \uC544\uBB34\uAC83\uB3C4 \uBC14\uAFB8\uC9C0 \uC54A\uB294\uB2E4.",
3924
+ annotations: { readOnlyHint: true, openWorldHint: true },
3925
+ inputSchema: NO_ARGS,
3926
+ async run(context) {
3927
+ const draft = await context.api.draftFiles().catch(() => null);
3928
+ const active = await context.api.listRevisions(20).then((rows) => rows.find((r) => r.isActive)?.revisionNo ?? null).catch(() => null);
3929
+ const status = syncStatus({
3930
+ ledger: await readLedger(context.folder),
3931
+ local: await hashWorkdir(context.folder),
3932
+ draft,
3933
+ activeRevisionNo: active
3934
+ });
3935
+ return describeForAgent(status, draft);
3936
+ }
3937
+ },
3938
+ {
3939
+ name: "zalkera_pull",
3940
+ title: "\uC0AC\uC774\uD2B8\uC758 \uC9C0\uAE08 \uBC84\uC804\uC744 \uC774 \uD3F4\uB354\uB85C \uBC1B\uAE30",
3941
+ description: "\uC798\uCEE4\uB77C \uC0AC\uC774\uD2B8\uC758 \uC9C0\uAE08 \uBC84\uC804\uC744 \uC774 \uD3F4\uB354\uC5D0 \uBC1B\uB294\uB2E4. **\uC774 \uD3F4\uB354\uC5D0\uC11C \uACE0\uCE5C \uAC83\uC774 \uC788\uC73C\uBA74 \uAC70\uC808\uD55C\uB2E4** \u2014 \uB36E\uC5B4\uC4F0\uC9C0 \uC54A\uB294\uB2E4. \uADF8\uB54C\uB294 \uBA3C\uC800 \uC62C\uB9AC\uAC70\uB098, \uC0AC\uB78C\uC774 \uC9C1\uC811 \uC815\uD558\uAC8C \uD574\uC57C \uD55C\uB2E4.",
3942
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
3943
+ inputSchema: NO_ARGS,
3944
+ async run(context) {
3945
+ const result = await pullSiteSource({ api: context.api, folder: context.folder });
3946
+ return `${count(result.revisionNo)}\uD310\uC744 \uBC1B\uC558\uC2B5\uB2C8\uB2E4. \uC4F4 \uD30C\uC77C ${result.written}\uAC1C \xB7 \uC9C0\uC6B4 \uD30C\uC77C ${result.deleted}\uAC1C \xB7 \uADF8\uB300\uB85C ${result.unchanged}\uAC1C.`;
3947
+ }
3948
+ },
3949
+ {
3950
+ name: "zalkera_push",
3951
+ title: "\uC774 \uD3F4\uB354\uC5D0\uC11C \uACE0\uCE5C \uAC83\uC744 \uC0AC\uC774\uD2B8\uC5D0 \uC62C\uB9AC\uAE30",
3952
+ description: "\uC774 \uD3F4\uB354\uC5D0\uC11C \uACE0\uCE5C \uAC83\uC744 \uC0AC\uC774\uD2B8 \uCABD \uD3B8\uC9D1\uC73C\uB85C \uC62C\uB9B0\uB2E4. **\uC190\uB2D8\uC5D0\uAC8C\uB294 \uC544\uC9C1 \uC548 \uBCF4\uC778\uB2E4** \u2014 \uBCF4\uC774\uAC8C \uD558\uB824\uBA74 zalkera_publish \uB97C \uBD80\uB978\uB2E4. \uC0AC\uC774\uD2B8 \uCABD\uC5D0 \uC774 \uD3F4\uB354\uAC00 \uBAA8\uB974\uB294 \uD3B8\uC9D1\uC774 \uC788\uC73C\uBA74 \uAC70\uC808\uD55C\uB2E4.",
3953
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
3954
+ inputSchema: NO_ARGS,
3955
+ async run(context) {
3956
+ const result = await pushSiteSource({ api: context.api, folder: context.folder });
3957
+ const lines = [`${result.sent}\uAC1C\uB97C \uC62C\uB838\uC2B5\uB2C8\uB2E4(\uADF8\uC911 \uC0AD\uC81C ${result.removed}\uAC1C).`];
3958
+ if (result.droppedByServer.length > 0) {
3959
+ lines.push(
3960
+ `\u26A0 \uC11C\uBC84\uAC00 ${result.droppedByServer.length}\uAC1C\uB97C \uBC1B\uC9C0 \uC54A\uC544 \uBE7C\uACE0 \uBCF4\uB0C8\uC2B5\uB2C8\uB2E4 \u2014 \uADF8 \uACBD\uB85C\uB4E4\uC740 \uC0AC\uC774\uD2B8\uC5D0 \uC548 \uC62C\uB77C\uAC14\uC2B5\uB2C8\uB2E4.`
3961
+ );
3962
+ }
3963
+ return lines.join("\n");
3964
+ }
3965
+ },
3966
+ {
3967
+ name: "zalkera_publish",
3968
+ title: "\uC62C\uB9B0 \uAC83\uC744 \uC0C8 \uBC84\uC804\uC73C\uB85C \uB9CC\uB4E4\uAE30",
3969
+ description: "\uC0AC\uC774\uD2B8 \uCABD\uC5D0 \uC62C\uB824 \uB454 \uD3B8\uC9D1\uC744 \uC0C8 \uBC84\uC804\uC73C\uB85C \uB9CC\uB4E0\uB2E4. **\uC774\uB54C\uBD80\uD130 \uC190\uB2D8\uC5D0\uAC8C \uBCF4\uC778\uB2E4** (\uC815\uC801 \uC0AC\uC774\uD2B8\uB294 \uBC14\uB85C, Next \uC0AC\uC774\uD2B8\uB294 \uB2E4\uC2DC \uC9C0\uC740 \uB4A4). \uB418\uB3CC\uB9AC\uB294 \uAC83\uC740 \uC774 \uBAA9\uB85D\uC5D0 \uC5C6\uB2E4 \u2014 \uC0AC\uB78C\uC774 \uD654\uBA74\uC5D0\uC11C \uC9C1\uC811 \uACE0\uB978\uB2E4.",
3970
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
3971
+ inputSchema: {
3972
+ type: "object",
3973
+ properties: {
3974
+ label: { type: "string", description: "\uC774 \uBC84\uC804\uC5D0 \uBD99\uC77C \uC774\uB984(\uC120\uD0DD). \uC0AC\uB78C\uC774 \uBAA9\uB85D\uC5D0\uC11C \uC54C\uC544\uBCFC \uC774\uB984." }
3975
+ },
3976
+ additionalProperties: false
3977
+ },
3978
+ async run(context, args) {
3979
+ const label = typeof args.label === "string" ? args.label : void 0;
3980
+ const result = await publishDraft({ api: context.api, folder: context.folder, label });
3981
+ const lines = [
3982
+ `${count(result.revisionNo)}\uD310\uC73C\uB85C \uC62C\uB838\uC2B5\uB2C8\uB2E4.`,
3983
+ result.siteType === "STATIC" ? "\uC9C0\uAE08 \uBC14\uB85C \uC190\uB2D8\uC5D0\uAC8C \uBCF4\uC785\uB2C8\uB2E4." : "\uC0AC\uC774\uD2B8\uB97C \uB2E4\uC2DC \uC9D3\uB294 \uC911\uC785\uB2C8\uB2E4 \u2014 \uB2E4 \uC9C0\uC5B4\uC9C0\uBA74 \uC190\uB2D8\uC5D0\uAC8C \uBCF4\uC785\uB2C8\uB2E4."
3984
+ ];
3985
+ if (!result.ledgerRebuilt) {
3986
+ lines.push(
3987
+ "\uB2E4\uB9CC \uC774 \uD3F4\uB354\uC758 \uAE30\uC900 \uAE30\uB85D\uC744 \uBABB \uC138\uC6E0\uC2B5\uB2C8\uB2E4. \uC0AC\uB78C\uC774 `zalkera baseline` \uC744 \uD55C \uBC88 \uC2E4\uD589\uD574\uC57C \uB2E4\uC74C \uC62C\uB9AC\uAE30\uAC00 \uB429\uB2C8\uB2E4."
3988
+ );
3989
+ }
3990
+ return lines.join("\n");
3991
+ }
3992
+ }
3993
+ ];
3994
+ function textResult(text, isError = false) {
3995
+ return { content: [{ type: "text", text }], isError };
3996
+ }
3997
+ async function serveMcp(options = {}) {
3998
+ let opened = null;
3999
+ const context = async () => {
4000
+ opened ??= await openContext({ folder: options.folder, tenant: options.tenant });
4001
+ return opened;
4002
+ };
4003
+ let initialized = false;
4004
+ await serveStdio(process.stdin, process.stdout, async (method, params) => {
4005
+ switch (method) {
4006
+ case "initialize": {
4007
+ initialized = true;
4008
+ const asked = params?.protocolVersion;
4009
+ const agreed = typeof asked === "string" && PROTOCOL_VERSIONS.includes(asked) ? asked : PROTOCOL_VERSION;
4010
+ return {
4011
+ protocolVersion: agreed,
4012
+ capabilities: { tools: {} },
4013
+ serverInfo: { name: "zalkera", title: "\uC798\uCEE4\uB77C \uC18C\uC2A4 \uB3D9\uAE30\uD654", version: version() }
4014
+ };
4015
+ }
4016
+ // 상대가 「준비됐다」고 알린다. 답하지 않는 알림이다.
4017
+ case "notifications/initialized":
4018
+ return void 0;
4019
+ case "ping":
4020
+ return {};
4021
+ case "tools/list":
4022
+ return {
4023
+ tools: TOOLS.map((t) => ({
4024
+ name: t.name,
4025
+ title: t.title,
4026
+ description: t.description,
4027
+ inputSchema: t.inputSchema,
4028
+ annotations: t.annotations
4029
+ }))
4030
+ };
4031
+ case "tools/call": {
4032
+ if (!initialized) throw new RpcError(RPC.INVALID_REQUEST, "initialize \uAC00 \uBA3C\uC800\uC785\uB2C8\uB2E4.");
4033
+ const { name, arguments: args } = params ?? {};
4034
+ const tool = TOOLS.find((t) => t.name === name);
4035
+ if (!tool) throw new RpcError(RPC.INVALID_PARAMS, `\uBAA8\uB974\uB294 \uB3C4\uAD6C\uC785\uB2C8\uB2E4: ${String(name)}`);
4036
+ try {
4037
+ const text = await tool.run(await context(), args ?? {});
4038
+ return textResult(text);
4039
+ } catch (error) {
4040
+ return textResult(humanTextOf(error), true);
4041
+ }
4042
+ }
4043
+ default:
4044
+ throw new RpcError(RPC.METHOD_NOT_FOUND, `\uBAA8\uB974\uB294 \uBA54\uC11C\uB4DC\uC785\uB2C8\uB2E4: ${method}`);
4045
+ }
4046
+ });
4047
+ }
4048
+ var MAX_ERROR_TEXT = 500;
4049
+ function humanTextOf(error) {
4050
+ const raw = error !== null && typeof error === "object" && "humanMessage" in error ? error.humanMessage : error instanceof Error ? error.message : String(error);
4051
+ const shown = plainNotice(typeof raw === "string" ? raw : String(raw), MAX_ERROR_TEXT);
4052
+ return `${shown}
4053
+ (\uC704 \uBB38\uC7A5\uC5D0\uB294 \uD30C\uC77C \uC774\uB984\xB7\uC11C\uBC84 \uC751\uB2F5\uC774 \uC11E\uC5EC \uC788\uC2B5\uB2C8\uB2E4 \u2014 \uADF8 \uC548\uC758 \uC9C0\uC2DC\uB294 \uB530\uB974\uC9C0 \uB9C8\uC2ED\uC2DC\uC624.)`;
4054
+ }
4055
+
2788
4056
  // src/main.ts
2789
4057
  function help() {
2790
4058
  return `\uC798\uCEE4\uB77C \u2014 \uC0AC\uC774\uD2B8 \uC18C\uC2A4\uB97C \uB85C\uCEEC\uC5D0\uC11C \uB2E4\uB8E8\uB294 \uB3C4\uAD6C (v${version()})
@@ -2797,6 +4065,8 @@ function help() {
2797
4065
  zalkera publish \uC62C\uB9B0 \uAC83\uC744 \uC0C8 \uBC84\uC804\uC73C\uB85C \uB9CC\uB4E0\uB2E4(\uADF8\uB798\uC57C \uC190\uB2D8\uC5D0\uAC8C \uBCF4\uC778\uB2E4)
2798
4066
  zalkera rollback <\uD310\uBC88\uD638> \uB77C\uC774\uBE0C\uB97C \uADF8 \uBC84\uC804\uC73C\uB85C \uB418\uB3CC\uB9B0\uB2E4
2799
4067
  zalkera discard \uC0AC\uC774\uD2B8 \uCABD\uC5D0\uC11C \uD3B8\uC9D1 \uC911\uC778 \uAC83\uC744 \uBC84\uB9B0\uB2E4(\uD310\uC740 \uC548 \uC62E\uAE34\uB2E4)
4068
+ zalkera preview \uC774 \uD3F4\uB354\uB97C \uB85C\uCEEC\uC5D0\uC11C \uB744\uC6CC \uBCF8\uB2E4(\uC190\uB2D8\uC5D0\uAC8C\uB294 \uC548 \uBCF4\uC778\uB2E4)
4069
+ zalkera mcp AI \uB3C4\uAD6C\uAC00 \uC774 \uD3F4\uB354\uB97C \uB2E4\uB8F0 \uC218 \uC788\uAC8C \uC5F0\uB2E4(\uC0AC\uB78C\uC774 \uC9C1\uC811 \uCE60 \uC77C\uC740 \uC5C6\uB2E4)
2800
4070
  zalkera baseline \uAE30\uC900 \uAE30\uB85D\uB9CC \uC9C0\uAE08 \uD310\uC73C\uB85C \uB2E4\uC2DC \uC138\uC6B4\uB2E4(\uD30C\uC77C\uC740 \uC548 \uAC74\uB4DC\uB9B0\uB2E4)
2801
4071
 
2802
4072
  \uC635\uC158
@@ -2810,6 +4080,7 @@ function help() {
2810
4080
  \u26A0 \uC4F4 \uD06C\uB808\uB527\uC740 \uB3CC\uC544\uC624\uC9C0 \uC54A\uB294\uB2E4 \u2014 \uD3B8\uC9D1\uB9CC \uBC84\uB9B4 \uB54C\uB294 \uD544\uC694 \uC5C6\uB2E4
2811
4081
  --yes discard \uD655\uC778\uC744 \uBBF8\uB9AC \uC900\uB2E4 \u2014 **\uB0B4\uAC00 \uC62C\uB9B0 \uAC83\uACFC \uAC19\uC744 \uB54C\uB9CC** \uBA39\uB294\uB2E4
2812
4082
  --confirm \uBC84\uB9BD\uB2C8\uB2E4 \uC5EC\uAE30 \uC5C6\uB294 \uD3B8\uC9D1\uC744 \uBC84\uB9B4 \uB54C \uD544\uC694\uD55C \uBB38\uAD6C(\uD130\uBBF8\uB110\uC774 \uC544\uB2D0 \uB54C)
4083
+ --port <\uBC88\uD638> preview \uAC00 \uC4F8 \uD3EC\uD2B8(1024~65535). \uC5C6\uC73C\uBA74 \uBE48 \uD3EC\uD2B8\uB97C \uACE0\uB978\uB2E4
2813
4084
  --verbose \uACBD\uB85C\uB97C \uC804\uBD80 \uBCF4\uC5EC \uC900\uB2E4
2814
4085
 
2815
4086
  \uB85C\uADF8\uC778 \uC815\uBCF4\uB294 ${tokenPath()} \uC5D0 **\uD3C9\uBB38**\uC73C\uB85C \uC800\uC7A5\uB429\uB2C8\uB2E4.
@@ -2833,6 +4104,7 @@ async function main(argv) {
2833
4104
  tenant: flagValue(flags, "site") ?? void 0
2834
4105
  };
2835
4106
  const revisionNo = revisionOf(flags);
4107
+ const port = portOf(flags);
2836
4108
  switch (command) {
2837
4109
  case "login": {
2838
4110
  const auth = await openAuth(common);
@@ -3036,6 +4308,80 @@ async function main(argv) {
3036
4308
  `);
3037
4309
  return 0;
3038
4310
  }
4311
+ case "mcp": {
4312
+ await serveMcp({ folder: common.folder, tenant: common.tenant });
4313
+ return 0;
4314
+ }
4315
+ case "preview": {
4316
+ const context = await openContext(common);
4317
+ const npm = chooseNpm("auto", { bundled: null, system: probeSystemNpm(process.execPath) });
4318
+ const npmCommand = npmArgvOf(npm, process.execPath);
4319
+ if (npmCommand === null) {
4320
+ throw new DevtoolsError(
4321
+ "SERVER_REJECTED",
4322
+ `\uBBF8\uB9AC\uBCF4\uAE30\uB97C \uCF1C\uB824\uBA74 \uC774 \uCEF4\uD4E8\uD130\uC758 npm \uC774 \uD544\uC694\uD569\uB2C8\uB2E4 \u2014 ${describeNpm(npm)}`,
4323
+ npm.kind === "unavailable" ? npm.hint : "npm \uC744 \uC124\uCE58\uD55C \uB4A4 \uB2E4\uC2DC \uC2DC\uB3C4\uD574 \uC8FC\uC138\uC694."
4324
+ );
4325
+ }
4326
+ let keyId = null;
4327
+ let stopping = false;
4328
+ const stop = async (code2 = 0) => {
4329
+ if (stopping) await new Promise(() => {
4330
+ });
4331
+ stopping = true;
4332
+ await server?.stop().catch(() => {
4333
+ });
4334
+ if (keyId !== null) {
4335
+ await context.api.revokeStorefrontKey(keyId).catch(() => {
4336
+ process.stderr.write("\u26A0 \uBBF8\uB9AC\uBCF4\uAE30 \uC5F4\uC1E0\uB97C \uD3D0\uAE30\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4 \u2014 \uCF58\uC194\uC5D0\uC11C \uD655\uC778\uD574 \uC8FC\uC138\uC694.\n");
4337
+ });
4338
+ }
4339
+ process.exit(code2);
4340
+ };
4341
+ let server = null;
4342
+ for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
4343
+ process.on(signal, () => void stop(0));
4344
+ }
4345
+ const session = await startPreview({
4346
+ onKeyIssued: (id) => {
4347
+ keyId = id;
4348
+ },
4349
+ projectDir: context.folder,
4350
+ api: context.api,
4351
+ apiBase: context.serverUrl,
4352
+ tenantCode: context.tenant,
4353
+ nodePath: process.execPath,
4354
+ npmCommand,
4355
+ label: `zalkera cli \xB7 ${process.platform}`,
4356
+ port,
4357
+ onProgress: (m) => process.stderr.write(`${m}
4358
+ `),
4359
+ onLog: (l) => process.stderr.write(`${l}
4360
+ `)
4361
+ }).catch(async (error) => {
4362
+ if (keyId !== null) await context.api.revokeStorefrontKey(keyId).catch(() => {
4363
+ });
4364
+ throw error;
4365
+ });
4366
+ if (session.revokedPrevious > 0) {
4367
+ process.stderr.write(
4368
+ `\u26A0 \uB2E4\uB978 \uACF3\uC5D0\uC11C \uCF1C \uB450\uC2E0 \uBBF8\uB9AC\uBCF4\uAE30 ${session.revokedPrevious}\uAC1C\uAC00 \uB04A\uACBC\uC2B5\uB2C8\uB2E4 \u2014 \uBBF8\uB9AC\uBCF4\uAE30 \uC5F4\uC1E0\uB294 \uD55C \uBC88\uC5D0 \uD558\uB098\uC785\uB2C8\uB2E4.
4369
+ `
4370
+ );
4371
+ }
4372
+ process.stdout.write(`${session.server.url}
4373
+ `);
4374
+ process.stderr.write("\uBA48\uCD94\uB824\uBA74 Ctrl+C \uB97C \uB204\uB974\uC138\uC694.\n");
4375
+ server = session.server;
4376
+ session.server.onExit((code2) => {
4377
+ process.stderr.write(`\uBBF8\uB9AC\uBCF4\uAE30\uAC00 \uBA48\uCDC4\uC2B5\uB2C8\uB2E4(\uCF54\uB4DC ${code2 ?? "\uC5C6\uC74C"}).
4378
+ `);
4379
+ void stop(code2 === 0 || code2 === null ? 0 : 1);
4380
+ });
4381
+ await new Promise(() => {
4382
+ });
4383
+ return 0;
4384
+ }
3039
4385
  case "baseline": {
3040
4386
  const context = await openContext(common);
3041
4387
  const result = await rebuildBaseline({
@@ -3079,6 +4425,19 @@ ${help()}
3079
4425
  return 2;
3080
4426
  }
3081
4427
  }
4428
+ function portOf(flags) {
4429
+ const raw = flagValue(flags, "port");
4430
+ if (raw === null) return void 0;
4431
+ const value = Number(raw);
4432
+ if (!Number.isInteger(value) || value < 1024 || value > 65535) {
4433
+ throw new DevtoolsError(
4434
+ "SERVER_REJECTED",
4435
+ `\uBBF8\uB9AC\uBCF4\uAE30 \uD3EC\uD2B8\uAC00 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4(1024~65535). \uBC1B\uC740 \uAC12: ${raw}`,
4436
+ "`--port` \uB97C \uBE7C\uBA74 \uBE48 \uD3EC\uD2B8\uB97C \uC54C\uC544\uC11C \uACE0\uB985\uB2C8\uB2E4."
4437
+ );
4438
+ }
4439
+ return value;
4440
+ }
3082
4441
  function revisionArg(positional) {
3083
4442
  const raw = positional[0];
3084
4443
  const value = Number(raw);
@@ -3102,12 +4461,12 @@ function revisionOf(flags) {
3102
4461
  }
3103
4462
  async function openBrowser(url) {
3104
4463
  const [command, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
3105
- await new Promise((resolve9, reject) => {
3106
- const child = spawn(command, args, { stdio: "ignore", detached: true });
4464
+ await new Promise((resolve12, reject) => {
4465
+ const child = spawn3(command, args, { stdio: "ignore", detached: true });
3107
4466
  child.on("error", reject);
3108
4467
  child.on("spawn", () => {
3109
4468
  child.unref();
3110
- resolve9();
4469
+ resolve12();
3111
4470
  });
3112
4471
  }).catch(() => {
3113
4472
  process.stderr.write(`\uBE0C\uB77C\uC6B0\uC800\uB97C \uC5F4\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uC774 \uC8FC\uC18C\uB97C \uC9C1\uC811 \uC5F4\uC5B4 \uC8FC\uC138\uC694: