@bifos/nhncloud-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1047 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/index.ts
27
+ var import_commander7 = require("commander");
28
+ var import_chalk2 = __toESM(require("chalk"));
29
+
30
+ // src/utils/spinner.ts
31
+ var import_ora = __toESM(require("ora"));
32
+ var current = null;
33
+ var quiet = false;
34
+ var noopSpinner = new Proxy({}, {
35
+ get(_target, prop) {
36
+ if (prop === "text" || prop === "prefixText" || prop === "suffixText") return "";
37
+ if (prop === "isSpinning") return false;
38
+ return () => noopSpinner;
39
+ },
40
+ set() {
41
+ return true;
42
+ }
43
+ });
44
+ function setQuiet(value) {
45
+ quiet = value;
46
+ }
47
+ function startSpinner(text) {
48
+ if (quiet) return noopSpinner;
49
+ current = (0, import_ora.default)({ text, stream: process.stderr }).start();
50
+ return current;
51
+ }
52
+ function stopSpinner(success, text) {
53
+ if (!current) return;
54
+ if (success === false) current.fail(text);
55
+ else current.succeed(text);
56
+ current = null;
57
+ }
58
+
59
+ // src/utils/errors.ts
60
+ var NhnCloudCliError = class extends Error {
61
+ constructor(message, exitCode) {
62
+ super(message);
63
+ this.exitCode = exitCode;
64
+ this.name = "NhnCloudCliError";
65
+ }
66
+ exitCode;
67
+ };
68
+
69
+ // src/commands/configure.ts
70
+ var import_commander = require("commander");
71
+ var import_chalk = __toESM(require("chalk"));
72
+ var import_core = require("@inquirer/core");
73
+
74
+ // src/config/credentials.ts
75
+ var import_promises = require("fs/promises");
76
+ var import_node_os = require("os");
77
+ var import_node_path = require("path");
78
+
79
+ // src/utils/exit-codes.ts
80
+ var EXIT_API_ERROR = 1;
81
+ var EXIT_AUTH_ERROR = 2;
82
+ var EXIT_PARAM_ERROR = 3;
83
+ var EXIT_CONFIG_ERROR = 4;
84
+
85
+ // src/config/credentials.ts
86
+ var CREDENTIALS_PATH = (0, import_node_path.join)((0, import_node_os.homedir)(), ".nhncloud", "credentials.json");
87
+ var CONFIG_PATH = (0, import_node_path.join)((0, import_node_os.homedir)(), ".nhncloud", "config.json");
88
+ function isCredentials(value) {
89
+ if (typeof value !== "object" || value === null) return false;
90
+ const obj = value;
91
+ return obj["version"] === 1 && typeof obj["profiles"] === "object" && obj["profiles"] !== null;
92
+ }
93
+ function isConfig(value) {
94
+ if (typeof value !== "object" || value === null) return false;
95
+ const obj = value;
96
+ return obj["version"] === 1;
97
+ }
98
+ function isUserAccessKey(value) {
99
+ if (typeof value !== "object" || value === null) return false;
100
+ const obj = value;
101
+ return typeof obj["id"] === "string" && obj["id"].length > 0 && typeof obj["secret"] === "string" && obj["secret"].length > 0;
102
+ }
103
+ async function loadCredentials() {
104
+ let raw;
105
+ try {
106
+ raw = await (0, import_promises.readFile)(CREDENTIALS_PATH, "utf-8");
107
+ } catch {
108
+ throw new NhnCloudCliError(
109
+ `\uC790\uACA9\uC99D\uBA85 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${CREDENTIALS_PATH}
110
+ nhncloud configure \uB97C \uC2E4\uD589\uD574 \uC790\uACA9\uC99D\uBA85\uC744 \uC124\uC815\uD558\uAC70\uB098, \uB2E4\uC74C \uD615\uC2DD\uC73C\uB85C \uD30C\uC77C\uC744 \uC0DD\uC131\uD558\uC138\uC694:
111
+ ` + JSON.stringify(
112
+ {
113
+ version: 1,
114
+ profiles: {
115
+ default: {
116
+ userAccessKey: { id: "<uak-id>", secret: "<uak-secret>" },
117
+ logncrash: { appkey: "<appkey>", secret: "<secretkey>" }
118
+ }
119
+ }
120
+ },
121
+ null,
122
+ 2
123
+ ),
124
+ EXIT_CONFIG_ERROR
125
+ );
126
+ }
127
+ let parsed;
128
+ try {
129
+ parsed = JSON.parse(raw);
130
+ } catch {
131
+ throw new NhnCloudCliError(
132
+ `\uC790\uACA9\uC99D\uBA85 \uD30C\uC77C \uD30C\uC2F1 \uC624\uB958: ${CREDENTIALS_PATH} \u2014 \uC62C\uBC14\uB978 JSON \uD615\uC2DD\uC778\uC9C0 \uD655\uC778\uD558\uC138\uC694.`,
133
+ EXIT_CONFIG_ERROR
134
+ );
135
+ }
136
+ if (!isCredentials(parsed)) {
137
+ throw new NhnCloudCliError(
138
+ `\uC790\uACA9\uC99D\uBA85 \uD30C\uC77C \uD615\uC2DD \uC624\uB958: ${CREDENTIALS_PATH} \u2014 version: 1 \uACFC profiles \uD544\uB4DC\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.`,
139
+ EXIT_CONFIG_ERROR
140
+ );
141
+ }
142
+ return parsed;
143
+ }
144
+ async function loadConfig() {
145
+ let raw;
146
+ try {
147
+ raw = await (0, import_promises.readFile)(CONFIG_PATH, "utf-8");
148
+ } catch {
149
+ return null;
150
+ }
151
+ let parsed;
152
+ try {
153
+ parsed = JSON.parse(raw);
154
+ } catch {
155
+ throw new NhnCloudCliError(
156
+ `\uC124\uC815 \uD30C\uC77C \uD30C\uC2F1 \uC624\uB958: ${CONFIG_PATH} \u2014 \uC62C\uBC14\uB978 JSON \uD615\uC2DD\uC778\uC9C0 \uD655\uC778\uD558\uC138\uC694.`,
157
+ EXIT_CONFIG_ERROR
158
+ );
159
+ }
160
+ if (!isConfig(parsed)) {
161
+ return null;
162
+ }
163
+ return parsed;
164
+ }
165
+ async function resolveProfileName(cliProfile) {
166
+ if (cliProfile) return cliProfile;
167
+ const envProfile = process.env["NHNCLOUD_PROFILE"];
168
+ if (envProfile) return envProfile;
169
+ const config = await loadConfig();
170
+ if (config?.defaultProfile) return config.defaultProfile;
171
+ return "default";
172
+ }
173
+ async function getUserAccessKey(profileName) {
174
+ const credentials = await loadCredentials();
175
+ const profile = credentials.profiles[profileName];
176
+ if (!profile) {
177
+ throw new NhnCloudCliError(
178
+ `profile "${profileName}" \uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.
179
+ ${CREDENTIALS_PATH} \uC5D0\uC11C profiles.${profileName} \uBE14\uB85D\uC744 \uCD94\uAC00\uD558\uAC70\uB098 nhncloud configure \uB97C \uC2E4\uD589\uD558\uC138\uC694.`,
180
+ EXIT_CONFIG_ERROR
181
+ );
182
+ }
183
+ const uak = profile["userAccessKey"];
184
+ if (!isUserAccessKey(uak)) {
185
+ throw new NhnCloudCliError(
186
+ `profile "${profileName}" \uC5D0 userAccessKey \uAC00 \uC5C6\uAC70\uB098 \uBD88\uC644\uC804\uD569\uB2C8\uB2E4.
187
+ nhncloud configure \uB97C \uC2E4\uD589\uD574 UAK id/secret \uC744 \uC124\uC815\uD558\uC138\uC694.`,
188
+ EXIT_CONFIG_ERROR
189
+ );
190
+ }
191
+ return uak;
192
+ }
193
+ async function getServiceCredential(service, profileName) {
194
+ if (service === "userAccessKey") {
195
+ throw new NhnCloudCliError(
196
+ "userAccessKey \uB294 \uC11C\uBE44\uC2A4 \uC790\uACA9\uC99D\uBA85\uC774 \uC544\uB2D9\uB2C8\uB2E4 \u2014 getUserAccessKey \uB97C \uC0AC\uC6A9\uD558\uC138\uC694.",
197
+ EXIT_PARAM_ERROR
198
+ );
199
+ }
200
+ const credentials = await loadCredentials();
201
+ const profile = credentials.profiles[profileName];
202
+ if (!profile) {
203
+ throw new NhnCloudCliError(
204
+ `profile "${profileName}" \uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.
205
+ ${CREDENTIALS_PATH} \uC5D0\uC11C profiles.${profileName} \uBE14\uB85D\uC744 \uCD94\uAC00\uD558\uC138\uC694.`,
206
+ EXIT_CONFIG_ERROR
207
+ );
208
+ }
209
+ const cred = profile[service];
210
+ if (!cred) {
211
+ throw new NhnCloudCliError(
212
+ `profile "${profileName}" \uC5D0 "${service}" \uC790\uACA9\uC99D\uBA85\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.
213
+ ${CREDENTIALS_PATH} \uC5D0\uC11C profiles.${profileName}.${service} \uBE14\uB85D\uC744 \uCD94\uAC00\uD558\uC138\uC694.
214
+ \uC608\uC2DC: { "appkey": "<appkey>", "secret": "<secretkey>" }`,
215
+ EXIT_CONFIG_ERROR
216
+ );
217
+ }
218
+ return cred;
219
+ }
220
+ async function loadCredentialsOrEmpty() {
221
+ try {
222
+ const raw = await (0, import_promises.readFile)(CREDENTIALS_PATH, "utf-8");
223
+ let parsed;
224
+ try {
225
+ parsed = JSON.parse(raw);
226
+ } catch {
227
+ throw new NhnCloudCliError(
228
+ `\uC790\uACA9\uC99D\uBA85 \uD30C\uC77C \uD30C\uC2F1 \uC624\uB958: ${CREDENTIALS_PATH} \u2014 \uC62C\uBC14\uB978 JSON \uD615\uC2DD\uC778\uC9C0 \uD655\uC778\uD558\uC138\uC694.`,
229
+ EXIT_CONFIG_ERROR
230
+ );
231
+ }
232
+ if (isCredentials(parsed)) return parsed;
233
+ return { version: 1, profiles: {} };
234
+ } catch (err) {
235
+ if (err instanceof NhnCloudCliError) throw err;
236
+ return { version: 1, profiles: {} };
237
+ }
238
+ }
239
+ async function saveCredentials(creds) {
240
+ await (0, import_promises.mkdir)((0, import_node_path.dirname)(CREDENTIALS_PATH), { recursive: true, mode: 448 });
241
+ await (0, import_promises.writeFile)(CREDENTIALS_PATH, JSON.stringify(creds, null, 2), {
242
+ encoding: "utf-8",
243
+ mode: 384
244
+ });
245
+ }
246
+ async function setUserAccessKey(profileName, uak) {
247
+ const creds = await loadCredentialsOrEmpty();
248
+ const profile = creds.profiles[profileName] ?? {};
249
+ creds.profiles[profileName] = { ...profile, userAccessKey: uak };
250
+ await saveCredentials(creds);
251
+ }
252
+ async function setServiceCredential(profileName, service, cred) {
253
+ const creds = await loadCredentialsOrEmpty();
254
+ const profile = creds.profiles[profileName] ?? {};
255
+ creds.profiles[profileName] = { ...profile, [service]: cred };
256
+ await saveCredentials(creds);
257
+ }
258
+ async function getDeployTarget(name) {
259
+ const config = await loadConfig();
260
+ const targets = config?.deploy?.targets;
261
+ const target = targets?.[name];
262
+ if (!target) {
263
+ const available = targets ? Object.keys(targets) : [];
264
+ const hint = available.length > 0 ? `\uC0AC\uC6A9 \uAC00\uB2A5\uD55C target: ${available.join(", ")}` : `config.json \uC5D0 deploy.targets \uBE14\uB85D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. ${CONFIG_PATH} \uB97C \uD655\uC778\uD558\uC138\uC694.`;
265
+ throw new NhnCloudCliError(
266
+ `deploy target "${name}" \uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. ${hint}`,
267
+ EXIT_PARAM_ERROR
268
+ );
269
+ }
270
+ return target;
271
+ }
272
+
273
+ // src/api/oauth.ts
274
+ var import_ky2 = __toESM(require("ky"));
275
+
276
+ // src/cache/token-store.ts
277
+ var import_promises2 = require("fs/promises");
278
+ var import_node_path2 = require("path");
279
+ var import_node_os2 = require("os");
280
+ var import_node_crypto = require("crypto");
281
+ var CACHE_DIR = (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".nhncloud", "cache");
282
+ function tokenCachePath(profile) {
283
+ return (0, import_node_path2.join)(CACHE_DIR, `deploy-token-${profile}.json`);
284
+ }
285
+ function isTokenCache(val) {
286
+ if (typeof val !== "object" || val === null) return false;
287
+ const obj = val;
288
+ return typeof obj["accessToken"] === "string" && typeof obj["expiresAt"] === "string";
289
+ }
290
+ async function readToken(profile) {
291
+ const filePath = tokenCachePath(profile);
292
+ try {
293
+ const raw = await (0, import_promises2.readFile)(filePath, "utf-8");
294
+ const parsed = JSON.parse(raw);
295
+ if (!isTokenCache(parsed)) return null;
296
+ const expiresAt = new Date(parsed.expiresAt).getTime();
297
+ const now = Date.now();
298
+ const BUFFER_MS = 6e4;
299
+ if (expiresAt - now < BUFFER_MS) return null;
300
+ return { accessToken: parsed.accessToken, expiresAt: parsed.expiresAt };
301
+ } catch {
302
+ return null;
303
+ }
304
+ }
305
+ async function writeToken(profile, accessToken, expiresAt) {
306
+ const filePath = tokenCachePath(profile);
307
+ await (0, import_promises2.mkdir)((0, import_node_path2.dirname)(filePath), { recursive: true });
308
+ const data = {
309
+ accessToken,
310
+ expiresAt: expiresAt.toISOString()
311
+ };
312
+ const tmp = filePath + "." + (0, import_node_crypto.randomBytes)(4).toString("hex") + ".tmp";
313
+ await (0, import_promises2.writeFile)(tmp, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
314
+ await (0, import_promises2.rename)(tmp, filePath);
315
+ }
316
+
317
+ // src/api/httpError.ts
318
+ var import_ky = require("ky");
319
+ function toNhnCloudCliError(err) {
320
+ if (err instanceof NhnCloudCliError) {
321
+ return err;
322
+ }
323
+ if (err instanceof import_ky.HTTPError) {
324
+ const status = err.response.status;
325
+ const exitCode = status === 401 || status === 403 ? EXIT_AUTH_ERROR : EXIT_API_ERROR;
326
+ return new NhnCloudCliError(
327
+ `API \uD638\uCD9C \uC2E4\uD328 (${status}): ${err.message}`,
328
+ exitCode
329
+ );
330
+ }
331
+ if (err instanceof Error) {
332
+ return new NhnCloudCliError(err.message, EXIT_API_ERROR);
333
+ }
334
+ return new NhnCloudCliError(String(err), EXIT_API_ERROR);
335
+ }
336
+
337
+ // src/api/oauth.ts
338
+ var OAUTH_ENDPOINT = "https://oauth.api.nhncloudservice.com/oauth2/token/create";
339
+ function isTokenResponse(val) {
340
+ if (typeof val !== "object" || val === null) return false;
341
+ const obj = val;
342
+ return typeof obj["access_token"] === "string" && typeof obj["expires_in"] === "number";
343
+ }
344
+ async function getAccessToken(profile, uakId, uakSecret, forceRefresh = false) {
345
+ if (!forceRefresh) {
346
+ const cached = await readToken(profile);
347
+ if (cached !== null) {
348
+ return cached.accessToken;
349
+ }
350
+ }
351
+ const basicCredential = Buffer.from(`${uakId}:${uakSecret}`).toString("base64");
352
+ let raw;
353
+ try {
354
+ raw = await import_ky2.default.post(OAUTH_ENDPOINT, {
355
+ headers: {
356
+ Authorization: `Basic ${basicCredential}`,
357
+ "Content-Type": "application/x-www-form-urlencoded"
358
+ },
359
+ body: "grant_type=client_credentials",
360
+ retry: 0
361
+ }).json();
362
+ } catch (err) {
363
+ throw toNhnCloudCliError(err);
364
+ }
365
+ if (!isTokenResponse(raw)) {
366
+ throw toNhnCloudCliError(new Error("OAuth \uC751\uB2F5 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4."));
367
+ }
368
+ if (!forceRefresh) {
369
+ const expiresAt = new Date(Date.now() + raw.expires_in * 1e3);
370
+ await writeToken(profile, raw.access_token, expiresAt);
371
+ }
372
+ return raw.access_token;
373
+ }
374
+
375
+ // src/services/logncrash/client.ts
376
+ var import_ky3 = __toESM(require("ky"));
377
+
378
+ // src/api/endpoints.ts
379
+ var ENDPOINTS = {
380
+ logncrash: "https://api-lncs-search.nhncloudservice.com",
381
+ deploy: "https://api-deploy.nhncloudservice.com"
382
+ };
383
+ function endpointFor(service) {
384
+ const endpoint = ENDPOINTS[service];
385
+ if (!endpoint) {
386
+ throw new NhnCloudCliError(
387
+ `\uB4F1\uB85D\uB418\uC9C0 \uC54A\uC740 \uC11C\uBE44\uC2A4\uC785\uB2C8\uB2E4: "${service}". \uC9C0\uC6D0 \uC11C\uBE44\uC2A4: ${Object.keys(ENDPOINTS).join(", ")}`,
388
+ EXIT_API_ERROR
389
+ );
390
+ }
391
+ return endpoint;
392
+ }
393
+
394
+ // src/api/envelope.ts
395
+ function unwrap(res) {
396
+ if (!res.header.isSuccessful) {
397
+ throw new NhnCloudCliError(
398
+ `API \uC624\uB958: ${res.header.resultMessage}`,
399
+ EXIT_API_ERROR
400
+ );
401
+ }
402
+ if (res.body === void 0) {
403
+ throw new NhnCloudCliError("API \uC751\uB2F5\uC5D0 body \uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.", EXIT_API_ERROR);
404
+ }
405
+ return res.body;
406
+ }
407
+
408
+ // src/services/logncrash/client.ts
409
+ var LogncrashClient = class {
410
+ appkey;
411
+ secret;
412
+ constructor(appkey, secret) {
413
+ this.appkey = appkey;
414
+ this.secret = secret;
415
+ }
416
+ async search(params) {
417
+ const endpoint = endpointFor("logncrash");
418
+ const url = `${endpoint}/api/v2/search/${encodeURIComponent(this.appkey)}`;
419
+ try {
420
+ const res = await import_ky3.default.post(url, {
421
+ headers: {
422
+ "X-LNCS-SECRET": this.secret,
423
+ "Content-Type": "application/json"
424
+ },
425
+ json: {
426
+ query: params.query,
427
+ from: params.from,
428
+ to: params.to,
429
+ pageNumber: params.pageNumber ?? 0,
430
+ pageSize: params.pageSize ?? 10
431
+ }
432
+ }).json();
433
+ return unwrap(res);
434
+ } catch (err) {
435
+ throw toNhnCloudCliError(err);
436
+ }
437
+ }
438
+ };
439
+
440
+ // src/commands/configure-verify.ts
441
+ async function verifyUserAccessKey(uak) {
442
+ try {
443
+ await getAccessToken("__verify__", uak.id, uak.secret, true);
444
+ return true;
445
+ } catch (err) {
446
+ if (err instanceof NhnCloudCliError && err.exitCode === EXIT_AUTH_ERROR) {
447
+ return false;
448
+ }
449
+ throw err;
450
+ }
451
+ }
452
+ async function verifyLogncrash(cred) {
453
+ if (!cred.appkey || !cred.secret) return false;
454
+ const client = new LogncrashClient(cred.appkey, cred.secret);
455
+ const now = /* @__PURE__ */ new Date();
456
+ const oneMinuteAgo = new Date(now.getTime() - 6e4);
457
+ try {
458
+ await client.search({
459
+ query: "*",
460
+ from: oneMinuteAgo.toISOString(),
461
+ to: now.toISOString(),
462
+ pageNumber: 0,
463
+ pageSize: 1
464
+ });
465
+ return true;
466
+ } catch (err) {
467
+ if (err instanceof NhnCloudCliError && err.exitCode === EXIT_AUTH_ERROR) {
468
+ return false;
469
+ }
470
+ throw err;
471
+ }
472
+ }
473
+
474
+ // src/commands/configure.ts
475
+ async function saveAndVerify(profileName, uak, logncrash, doVerify) {
476
+ if (doVerify) {
477
+ if (uak) {
478
+ const ok = await verifyUserAccessKey(uak);
479
+ if (ok) {
480
+ process.stderr.write(import_chalk.default.green(" \u2713 UAK \uC5F0\uACB0 \uC131\uACF5\n"));
481
+ } else {
482
+ throw new NhnCloudCliError(
483
+ "UAK \uC778\uC99D \uC2E4\uD328 \u2014 uak-id / uak-secret \uC744 \uD655\uC778\uD558\uC138\uC694.",
484
+ EXIT_AUTH_ERROR
485
+ );
486
+ }
487
+ }
488
+ if (logncrash) {
489
+ const ok = await verifyLogncrash(logncrash);
490
+ if (ok) {
491
+ process.stderr.write(import_chalk.default.green(" \u2713 logncrash \uC5F0\uACB0 \uC131\uACF5\n"));
492
+ } else {
493
+ throw new NhnCloudCliError(
494
+ "logncrash \uC778\uC99D \uC2E4\uD328 \u2014 appkey / secret \uC744 \uD655\uC778\uD558\uC138\uC694.",
495
+ EXIT_AUTH_ERROR
496
+ );
497
+ }
498
+ }
499
+ }
500
+ if (uak) {
501
+ await setUserAccessKey(profileName, uak);
502
+ }
503
+ if (logncrash) {
504
+ await setServiceCredential(profileName, "logncrash", logncrash);
505
+ }
506
+ process.stderr.write(import_chalk.default.green(`
507
+ \u2713 profile "${profileName}" \uC124\uC815\uC774 \uC800\uC7A5\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
508
+ `));
509
+ }
510
+ async function runInteractive(opts) {
511
+ const { input, password, confirm } = await import("@inquirer/prompts");
512
+ const defaultProfile = await resolveProfileName(opts.profile);
513
+ const profileName = await input({
514
+ message: "profile \uC774\uB984\uC744 \uC785\uB825\uD558\uC138\uC694",
515
+ default: defaultProfile
516
+ });
517
+ process.stderr.write(import_chalk.default.gray("\n\u2014 \uAC1C\uC778 UAK (User Access Key) \u2014\n"));
518
+ const uakId = await input({
519
+ message: "UAK ID"
520
+ });
521
+ const uakSecret = await password({
522
+ message: "UAK Secret",
523
+ mask: "*"
524
+ });
525
+ const uak = { id: uakId, secret: uakSecret };
526
+ let logncrash;
527
+ const setupLogncrash = await confirm({
528
+ message: "logncrash \uC790\uACA9\uC99D\uBA85\uB3C4 \uC124\uC815\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
529
+ default: false
530
+ });
531
+ if (setupLogncrash) {
532
+ process.stderr.write(import_chalk.default.gray("\n\u2014 logncrash \uC790\uACA9\uC99D\uBA85 \u2014\n"));
533
+ const appkey = await input({ message: "logncrash appkey" });
534
+ const secret = await password({ message: "logncrash secret", mask: "*" });
535
+ logncrash = { appkey, secret };
536
+ }
537
+ if (opts.verify) {
538
+ process.stderr.write(import_chalk.default.gray("\n\u2014 \uC5F0\uACB0 \uD14C\uC2A4\uD2B8 \uC911\u2026 \u2014\n"));
539
+ }
540
+ if (opts.verify) {
541
+ try {
542
+ await saveAndVerify(profileName, uak, logncrash, true);
543
+ } catch (err) {
544
+ if (err instanceof NhnCloudCliError && err.exitCode === EXIT_AUTH_ERROR) {
545
+ process.stderr.write(import_chalk.default.red(` \u2717 ${err.message}
546
+ `));
547
+ const saveDespite = await confirm({
548
+ message: "\uAC80\uC99D \uC2E4\uD328\uC5D0\uB3C4 \uC800\uC7A5\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
549
+ default: false
550
+ });
551
+ if (!saveDespite) {
552
+ process.stderr.write(import_chalk.default.yellow("\uC800\uC7A5\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.\n"));
553
+ return;
554
+ }
555
+ await saveAndVerify(profileName, uak, logncrash, false);
556
+ } else {
557
+ throw err;
558
+ }
559
+ }
560
+ } else {
561
+ await saveAndVerify(profileName, uak, logncrash, false);
562
+ }
563
+ }
564
+ async function runNonInteractive(opts) {
565
+ const profileName = await resolveProfileName(opts.profile);
566
+ const uakSecret = opts.uakSecret ?? process.env["NHNCLOUD_UAK_SECRET"];
567
+ const logncrashSecret = opts.logncrashSecret ?? process.env["NHNCLOUD_LOGNCRASH_SECRET"];
568
+ const uak = opts.uakId && uakSecret ? { id: opts.uakId, secret: uakSecret } : void 0;
569
+ const logncrash = opts.logncrashAppkey && logncrashSecret ? { appkey: opts.logncrashAppkey, secret: logncrashSecret } : void 0;
570
+ if (!uak && !logncrash) {
571
+ throw new NhnCloudCliError(
572
+ "\uBE44\uB300\uD654\uD615 \uBAA8\uB4DC: --uak-id + UAK secret \uB610\uB294 --logncrash-appkey + logncrash secret \uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.\nsecret \uC740 \uB178\uCD9C \uBC29\uC9C0\uB97C \uC704\uD574 \uD658\uACBD\uBCC0\uC218 \uAD8C\uC7A5: NHNCLOUD_UAK_SECRET / NHNCLOUD_LOGNCRASH_SECRET.",
573
+ EXIT_PARAM_ERROR
574
+ );
575
+ }
576
+ if (opts.verify) {
577
+ process.stderr.write(import_chalk.default.gray("\uC5F0\uACB0 \uD14C\uC2A4\uD2B8 \uC911\u2026\n"));
578
+ }
579
+ await saveAndVerify(profileName, uak, logncrash, opts.verify);
580
+ }
581
+ var configureCommand = new import_commander.Command("configure").description("\uC790\uACA9\uC99D\uBA85 \uC124\uC815 \uB9C8\uBC95\uC0AC (\uB300\uD654\uD615 + flag)").option("--profile <name>", "\uB300\uC0C1 profile \uC774\uB984 (\uAE30\uBCF8: default)").option("--uak-id <id>", "\uAC1C\uC778 UAK ID (\uBE44\uB300\uD654\uD615)").option("--uak-secret <secret>", "\uAC1C\uC778 UAK Secret (\uBE44\uB300\uD654\uD615, \uB178\uCD9C \uBC29\uC9C0\uB85C env NHNCLOUD_UAK_SECRET \uAD8C\uC7A5)").option("--logncrash-appkey <key>", "logncrash appkey (\uBE44\uB300\uD654\uD615)").option("--logncrash-secret <secret>", "logncrash secret (\uBE44\uB300\uD654\uD615, env NHNCLOUD_LOGNCRASH_SECRET \uAD8C\uC7A5)").option("--no-verify", "\uC5F0\uACB0 \uD14C\uC2A4\uD2B8 \uC0DD\uB7B5").action(async (opts) => {
582
+ const hasFlag = opts.uakId || opts.uakSecret || opts.logncrashAppkey || opts.logncrashSecret;
583
+ try {
584
+ if (hasFlag) {
585
+ await runNonInteractive(opts);
586
+ } else {
587
+ await runInteractive(opts);
588
+ }
589
+ } catch (err) {
590
+ if (err instanceof import_core.ExitPromptError) {
591
+ process.stderr.write(import_chalk.default.yellow("\n\uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.\n"));
592
+ return;
593
+ }
594
+ throw err;
595
+ }
596
+ });
597
+
598
+ // src/commands/logncrash/search.ts
599
+ var import_commander2 = require("commander");
600
+
601
+ // src/utils/time.ts
602
+ function toLocalISOString(date) {
603
+ const offsetMinutes = -date.getTimezoneOffset();
604
+ const sign = offsetMinutes >= 0 ? "+" : "-";
605
+ const absOffset = Math.abs(offsetMinutes);
606
+ const offsetHH = String(Math.floor(absOffset / 60)).padStart(2, "0");
607
+ const offsetMM = String(absOffset % 60).padStart(2, "0");
608
+ const yyyy = String(date.getFullYear());
609
+ const mo = String(date.getMonth() + 1).padStart(2, "0");
610
+ const dd = String(date.getDate()).padStart(2, "0");
611
+ const hh = String(date.getHours()).padStart(2, "0");
612
+ const mm = String(date.getMinutes()).padStart(2, "0");
613
+ const ss = String(date.getSeconds()).padStart(2, "0");
614
+ return `${yyyy}-${mo}-${dd}T${hh}:${mm}:${ss}${sign}${offsetHH}:${offsetMM}`;
615
+ }
616
+ function resolveTime(input) {
617
+ const trimmed = input.trim();
618
+ if (trimmed === "now") {
619
+ return toLocalISOString(/* @__PURE__ */ new Date());
620
+ }
621
+ const relativeMatch = trimmed.match(/^(\d+)(m|h|d)$/);
622
+ if (relativeMatch) {
623
+ const amount = parseInt(relativeMatch[1], 10);
624
+ const unit = relativeMatch[2];
625
+ const now = /* @__PURE__ */ new Date();
626
+ switch (unit) {
627
+ case "m":
628
+ now.setMinutes(now.getMinutes() - amount);
629
+ break;
630
+ case "h":
631
+ now.setHours(now.getHours() - amount);
632
+ break;
633
+ case "d":
634
+ now.setDate(now.getDate() - amount);
635
+ break;
636
+ }
637
+ return toLocalISOString(now);
638
+ }
639
+ const isoPattern = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2})?([+-]\d{2}:\d{2}|Z)?)?$/;
640
+ if (isoPattern.test(trimmed)) {
641
+ return trimmed;
642
+ }
643
+ throw new NhnCloudCliError(
644
+ `\uC2DC\uAC04 \uD615\uC2DD \uC624\uB958: "${input}" \u2014 ISO8601 (\uC608: 2024-01-01T00:00:00+09:00) \uB610\uB294 \uC0C1\uB300\uC2DC\uAC04 (\uC608: 1h, 30m, 2d, now) \uC744 \uC0AC\uC6A9\uD558\uC138\uC694.`,
645
+ EXIT_PARAM_ERROR
646
+ );
647
+ }
648
+ var MAX_RANGE_DAYS = 31;
649
+ var MAX_LOOKBACK_DAYS = 90;
650
+ function assertSearchRange(fromIso, toIso) {
651
+ const from = new Date(fromIso);
652
+ const to = new Date(toIso);
653
+ if (from > to) {
654
+ throw new NhnCloudCliError("from \uC774 to \uBCF4\uB2E4 \uB2A6\uC2B5\uB2C8\uB2E4.", EXIT_PARAM_ERROR);
655
+ }
656
+ const rangeMs = to.getTime() - from.getTime();
657
+ const msPerDay = 24 * 60 * 60 * 1e3;
658
+ if (rangeMs > MAX_RANGE_DAYS * msPerDay) {
659
+ throw new NhnCloudCliError(`\uAC80\uC0C9 \uBC94\uC704\uB294 ${MAX_RANGE_DAYS}\uC77C \uC774\uD558\uC5EC\uC57C \uD569\uB2C8\uB2E4.`, EXIT_PARAM_ERROR);
660
+ }
661
+ const lookbackStart = new Date(Date.now() - MAX_LOOKBACK_DAYS * msPerDay);
662
+ if (from < lookbackStart) {
663
+ throw new NhnCloudCliError(`\uAC80\uC0C9 \uC2DC\uC791\uC740 \uCD5C\uADFC ${MAX_LOOKBACK_DAYS}\uC77C \uC774\uB0B4\uC5EC\uC57C \uD569\uB2C8\uB2E4.`, EXIT_PARAM_ERROR);
664
+ }
665
+ }
666
+
667
+ // src/formatters/table.ts
668
+ var import_cli_table3 = __toESM(require("cli-table3"));
669
+ function truncate(text, maxLen = 50) {
670
+ const oneLine = text.replace(/[\r\n]+/g, " ").trim();
671
+ return oneLine.length > maxLen ? oneLine.slice(0, maxLen) + "\u2026" : oneLine;
672
+ }
673
+ function printTable(headers, rows) {
674
+ if (rows.length === 0) {
675
+ process.stdout.write("\uACB0\uACFC \uC5C6\uC74C\n");
676
+ return;
677
+ }
678
+ const table = new import_cli_table3.default({ head: headers });
679
+ for (const row of rows) {
680
+ table.push(row);
681
+ }
682
+ process.stdout.write(table.toString() + "\n");
683
+ }
684
+ function printJson(data) {
685
+ process.stdout.write(JSON.stringify(data, null, 2) + "\n");
686
+ }
687
+ function printQuiet(ids) {
688
+ if (ids.length === 0) return;
689
+ process.stdout.write(ids.join("\n") + "\n");
690
+ }
691
+ function output(opts, data) {
692
+ if (opts.json) {
693
+ printJson(data.raw);
694
+ } else if (opts.quiet) {
695
+ printQuiet(data.ids);
696
+ } else {
697
+ printTable(data.headers, data.rows);
698
+ }
699
+ }
700
+
701
+ // src/commands/logncrash/search.ts
702
+ function getString(value) {
703
+ return typeof value === "string" ? value : String(value ?? "");
704
+ }
705
+ function formatLogRow(log) {
706
+ const logTime = getString(log["logTime"] ?? log["time"] ?? "");
707
+ const logType = getString(log["logType"] ?? log["type"] ?? "");
708
+ const body = truncate(getString(log["logBody"] ?? log["body"] ?? log["message"] ?? ""), 60);
709
+ return [logTime, logType, body];
710
+ }
711
+ var searchCommand = new import_commander2.Command("search").description("Log & Crash \uB85C\uADF8 \uAC80\uC0C9").option("--query <lucene>", "Lucene \uC9C8\uC758 \uBB38\uC790\uC5F4 (\uD544\uC218)").option("--from <time>", "\uAC80\uC0C9 \uC2DC\uC791: ISO8601 \uB610\uB294 \uC0C1\uB300\uC2DC\uAC04 (1h/30m/2d/now) (\uD544\uC218)").option("--to <time>", "\uAC80\uC0C9 \uB05D: ISO8601 \uB610\uB294 \uC0C1\uB300\uC2DC\uAC04 (\uD544\uC218)").option("--page <n>", "pageNumber (\uAE30\uBCF8 0)", "0").option("--size <n>", "pageSize (\uAE30\uBCF8 10, \uCD5C\uB300 100)", "10").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (_opts, cmd) => {
712
+ const opts = cmd.optsWithGlobals();
713
+ if (!opts.query) {
714
+ throw new NhnCloudCliError(`--query \uC635\uC158\uC740 \uD544\uC218\uC785\uB2C8\uB2E4. \uC608: --query 'logType:"NORMAL"'`, EXIT_PARAM_ERROR);
715
+ }
716
+ if (!opts.from) {
717
+ throw new NhnCloudCliError("--from \uC635\uC158\uC740 \uD544\uC218\uC785\uB2C8\uB2E4. \uC608: --from 1h", EXIT_PARAM_ERROR);
718
+ }
719
+ if (!opts.to) {
720
+ throw new NhnCloudCliError("--to \uC635\uC158\uC740 \uD544\uC218\uC785\uB2C8\uB2E4. \uC608: --to now", EXIT_PARAM_ERROR);
721
+ }
722
+ const page = parseInt(opts.page ?? "0", 10);
723
+ const size = parseInt(opts.size ?? "10", 10);
724
+ if (isNaN(page) || page < 0) {
725
+ throw new NhnCloudCliError("--page \uB294 0 \uC774\uC0C1\uC758 \uC815\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4.", EXIT_PARAM_ERROR);
726
+ }
727
+ if (isNaN(size) || size < 1) {
728
+ throw new NhnCloudCliError("--size \uB294 1 \uC774\uC0C1\uC758 \uC815\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4.", EXIT_PARAM_ERROR);
729
+ }
730
+ if (size > 100) {
731
+ throw new NhnCloudCliError("--size \uB294 \uCD5C\uB300 100 \uC774\uD558\uC5EC\uC57C \uD569\uB2C8\uB2E4.", EXIT_PARAM_ERROR);
732
+ }
733
+ const fromIso = resolveTime(opts.from);
734
+ const toIso = resolveTime(opts.to);
735
+ assertSearchRange(fromIso, toIso);
736
+ const profileName = await resolveProfileName(opts.profile);
737
+ const cred = await getServiceCredential("logncrash", profileName);
738
+ if (!cred.appkey) {
739
+ throw new NhnCloudCliError(
740
+ `profile "${profileName}" \uC758 logncrash \uC790\uACA9\uC99D\uBA85\uC5D0 appkey \uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.
741
+ credentials.json \uC5D0 "appkey": "<appkey>" \uB97C \uCD94\uAC00\uD558\uC138\uC694.`,
742
+ EXIT_CONFIG_ERROR
743
+ );
744
+ }
745
+ if (!cred.secret) {
746
+ throw new NhnCloudCliError(
747
+ `profile "${profileName}" \uC758 logncrash \uC790\uACA9\uC99D\uBA85\uC5D0 secret \uC774 \uC5C6\uC2B5\uB2C8\uB2E4.
748
+ credentials.json \uC5D0 "secret": "<secretkey>" \uB97C \uCD94\uAC00\uD558\uC138\uC694.`,
749
+ EXIT_CONFIG_ERROR
750
+ );
751
+ }
752
+ const client = new LogncrashClient(cred.appkey, cred.secret);
753
+ startSpinner("\uB85C\uADF8 \uAC80\uC0C9 \uC911...");
754
+ let result;
755
+ try {
756
+ result = await client.search({
757
+ query: opts.query,
758
+ from: fromIso,
759
+ to: toIso,
760
+ pageNumber: page,
761
+ pageSize: size
762
+ });
763
+ } catch (err) {
764
+ stopSpinner(false);
765
+ throw err;
766
+ }
767
+ stopSpinner(true);
768
+ const rows = result.data.map((log) => formatLogRow(log));
769
+ const ids = result.data.map((log) => getString(log["logTime"] ?? ""));
770
+ output(opts, {
771
+ headers: ["logTime", "logType", "\uBCF8\uBB38 \uC694\uC57D"],
772
+ rows,
773
+ raw: {
774
+ totalItems: result.totalItems,
775
+ pageNumber: result.pageNumber,
776
+ pageSize: result.pageSize,
777
+ data: result.data
778
+ },
779
+ ids
780
+ });
781
+ });
782
+
783
+ // src/commands/deploy/run.ts
784
+ var import_commander3 = require("commander");
785
+
786
+ // src/services/deploy/client.ts
787
+ var import_ky4 = __toESM(require("ky"));
788
+ var SYNC_TIMEOUT_MS = 6e5;
789
+ var DEFAULT_TIMEOUT_MS = 3e4;
790
+ var DeployClient = class {
791
+ accessToken;
792
+ baseUrl;
793
+ constructor(accessToken) {
794
+ this.accessToken = accessToken;
795
+ this.baseUrl = endpointFor("deploy");
796
+ }
797
+ authHeaders() {
798
+ return {
799
+ "X-NHN-AUTHORIZATION": `Bearer ${this.accessToken}`
800
+ };
801
+ }
802
+ /**
803
+ * 배포를 실행한다.
804
+ * - targetHosts 가 비어있으면 payload 에서 targetServerHostnames 를 제외한다 (서버그룹 전체 배포).
805
+ * - async=false(기본) 일 때 서버가 완료까지 응답을 보류하므로 ky timeout 을 600s 로 설정한다.
806
+ */
807
+ async run(params) {
808
+ const url = `${this.baseUrl}/api/v2.1/projects/${params.appKey}/artifacts/${params.artifactId}/server-group/${params.serverGroupId}/deploy`;
809
+ const isAsync = params.async ?? false;
810
+ const payload = {
811
+ concurrentNum: params.concurrentNum ?? 1,
812
+ nextWhenFail: params.nextWhenFail ?? false,
813
+ scenarioIds: params.scenarioIds,
814
+ deployNote: params.deployNote ?? `CLI deploy ${(/* @__PURE__ */ new Date()).toISOString()}`,
815
+ async: isAsync
816
+ };
817
+ if (params.targetHosts) {
818
+ payload["targetServerHostnames"] = params.targetHosts;
819
+ }
820
+ try {
821
+ const res = await import_ky4.default.post(url, {
822
+ headers: {
823
+ ...this.authHeaders(),
824
+ "Content-Type": "application/json"
825
+ },
826
+ json: payload,
827
+ retry: 0,
828
+ timeout: isAsync ? DEFAULT_TIMEOUT_MS : SYNC_TIMEOUT_MS
829
+ }).json();
830
+ return unwrap(res);
831
+ } catch (err) {
832
+ throw toNhnCloudCliError(err);
833
+ }
834
+ }
835
+ /**
836
+ * 아티팩트 목록을 조회한다.
837
+ */
838
+ async artifacts(appKey) {
839
+ const url = `${this.baseUrl}/api/v2.1/projects/${appKey}/artifacts`;
840
+ try {
841
+ const res = await import_ky4.default.get(url, {
842
+ headers: this.authHeaders(),
843
+ retry: 0,
844
+ timeout: DEFAULT_TIMEOUT_MS
845
+ }).json();
846
+ return unwrap(res);
847
+ } catch (err) {
848
+ throw toNhnCloudCliError(err);
849
+ }
850
+ }
851
+ /**
852
+ * 서버그룹 목록을 조회한다.
853
+ */
854
+ async serverGroups(appKey, artifactId) {
855
+ const url = `${this.baseUrl}/api/v2.1/projects/${appKey}/artifacts/${artifactId}/server-groups`;
856
+ try {
857
+ const res = await import_ky4.default.get(url, {
858
+ headers: this.authHeaders(),
859
+ retry: 0,
860
+ timeout: DEFAULT_TIMEOUT_MS
861
+ }).json();
862
+ return unwrap(res);
863
+ } catch (err) {
864
+ throw toNhnCloudCliError(err);
865
+ }
866
+ }
867
+ /**
868
+ * 배포 이력을 조회한다.
869
+ */
870
+ async histories(appKey, artifactId) {
871
+ const url = `${this.baseUrl}/api/v2.1/projects/${appKey}/artifacts/${artifactId}/deploy-histories`;
872
+ try {
873
+ const res = await import_ky4.default.get(url, {
874
+ headers: this.authHeaders(),
875
+ retry: 0,
876
+ timeout: DEFAULT_TIMEOUT_MS
877
+ }).json();
878
+ return unwrap(res);
879
+ } catch (err) {
880
+ throw toNhnCloudCliError(err);
881
+ }
882
+ }
883
+ };
884
+
885
+ // src/commands/deploy/helpers.ts
886
+ async function createDeployClient(profileOpt) {
887
+ const profileName = await resolveProfileName(profileOpt);
888
+ const uak = await getUserAccessKey(profileName);
889
+ const accessToken = await getAccessToken(profileName, uak.id, uak.secret);
890
+ return { client: new DeployClient(accessToken), profileName };
891
+ }
892
+
893
+ // src/commands/deploy/run.ts
894
+ var runCommand = new import_commander3.Command("run").description("\uBC30\uD3EC\uB97C \uC2E4\uD589\uD55C\uB2E4").argument("<target>", "config.json \uC5D0 \uC815\uC758\uB41C deploy target \uC774\uB984").option("--app-key <k>", "target \uC758 appKey override").option("--artifact-id <id>", "target \uC758 artifactId override").option("--server-group-id <id>", "target \uC758 serverGroupId override").option("--scenario-ids <csv>", "target \uC758 scenarioIds override").option("--target-hosts <csv>", "\uB300\uC0C1 \uD638\uC2A4\uD2B8 (\uC0DD\uB7B5 \uC2DC \uC11C\uBC84\uADF8\uB8F9 \uC804\uCCB4)").option("--concurrent <n>", "\uBCD1\uB82C \uBC30\uD3EC \uC218 (\uAE30\uBCF8 1)", "1").option("--next-when-fail", "\uC2DC\uB098\uB9AC\uC624 \uC2E4\uD328 \uC2DC\uC5D0\uB3C4 \uC9C4\uD589").option("--note <s>", "\uBC30\uD3EC \uBA54\uBAA8").option("--async", "\uC989\uC2DC \uBC18\uD658 (\uAE30\uBCF8\uC740 \uC644\uB8CC \uB300\uAE30)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (targetName, _opts, cmd) => {
895
+ const opts = cmd.optsWithGlobals();
896
+ const target = await getDeployTarget(targetName);
897
+ const appKey = opts.appKey ?? target.appKey;
898
+ const artifactId = opts.artifactId ?? target.artifactId;
899
+ const serverGroupId = opts.serverGroupId ?? target.serverGroupId;
900
+ const scenarioIds = opts.scenarioIds ?? target.scenarioIds;
901
+ const { client } = await createDeployClient(opts.profile);
902
+ startSpinner("\uBC30\uD3EC \uC2E4\uD589 \uC911...");
903
+ let result;
904
+ try {
905
+ result = await client.run({
906
+ appKey,
907
+ artifactId,
908
+ serverGroupId,
909
+ scenarioIds,
910
+ targetHosts: opts.targetHosts,
911
+ concurrentNum: parseInt(opts.concurrent ?? "1", 10),
912
+ nextWhenFail: opts.nextWhenFail ?? false,
913
+ deployNote: opts.note,
914
+ async: opts.async ?? false
915
+ });
916
+ } catch (err) {
917
+ stopSpinner(false);
918
+ throw err;
919
+ }
920
+ stopSpinner(true);
921
+ output(opts, {
922
+ headers: ["key", "value"],
923
+ rows: Object.entries(result).map(([k, v]) => [k, String(v ?? "")]),
924
+ raw: result,
925
+ ids: []
926
+ });
927
+ });
928
+
929
+ // src/commands/deploy/artifacts.ts
930
+ var import_commander4 = require("commander");
931
+ var artifactsCommand = new import_commander4.Command("artifacts").description("\uC544\uD2F0\uD329\uD2B8 \uBAA9\uB85D\uC744 \uC870\uD68C\uD55C\uB2E4").argument("[target]", "config.json \uC5D0 \uC815\uC758\uB41C deploy target \uC774\uB984 (--app-key \uB85C \uB300\uCCB4 \uAC00\uB2A5)").option("--app-key <k>", "appKey \uC9C1\uC811 \uC9C0\uC815 (target \uC5C6\uC774 \uC0AC\uC6A9 \uAC00\uB2A5)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (targetName, _opts, cmd) => {
932
+ const opts = cmd.optsWithGlobals();
933
+ let appKey;
934
+ if (opts.appKey) {
935
+ appKey = opts.appKey;
936
+ } else if (targetName) {
937
+ const target = await getDeployTarget(targetName);
938
+ appKey = target.appKey;
939
+ } else {
940
+ throw new NhnCloudCliError(
941
+ "target \uC774\uB984 \uB610\uB294 --app-key \uC635\uC158\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. \uC608: deploy artifacts <target> \uB610\uB294 deploy artifacts --app-key <appKey>",
942
+ EXIT_PARAM_ERROR
943
+ );
944
+ }
945
+ const { client } = await createDeployClient(opts.profile);
946
+ startSpinner("\uC544\uD2F0\uD329\uD2B8 \uBAA9\uB85D \uC870\uD68C \uC911...");
947
+ let result;
948
+ try {
949
+ result = await client.artifacts(appKey);
950
+ } catch (err) {
951
+ stopSpinner(false);
952
+ throw err;
953
+ }
954
+ stopSpinner(true);
955
+ const list = Array.isArray(result) ? result : [result];
956
+ output(opts, {
957
+ headers: ["key", "value"],
958
+ rows: list.flatMap((item) => {
959
+ if (typeof item !== "object" || item === null) return [[String(item), ""]];
960
+ return Object.entries(item).map(([k, v]) => [
961
+ `${k}: ${String(v ?? "")}`,
962
+ ""
963
+ ]);
964
+ }),
965
+ raw: result,
966
+ ids: []
967
+ });
968
+ });
969
+
970
+ // src/commands/deploy/server-groups.ts
971
+ var import_commander5 = require("commander");
972
+ var serverGroupsCommand = new import_commander5.Command("server-groups").description("\uC11C\uBC84\uADF8\uB8F9 \uBAA9\uB85D\uC744 \uC870\uD68C\uD55C\uB2E4").argument("<target>", "config.json \uC5D0 \uC815\uC758\uB41C deploy target \uC774\uB984").option("--app-key <k>", "target \uC758 appKey override").option("--artifact-id <id>", "target \uC758 artifactId override").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (targetName, _opts, cmd) => {
973
+ const opts = cmd.optsWithGlobals();
974
+ const target = await getDeployTarget(targetName);
975
+ const appKey = opts.appKey ?? target.appKey;
976
+ const artifactId = opts.artifactId ?? target.artifactId;
977
+ const { client } = await createDeployClient(opts.profile);
978
+ startSpinner("\uC11C\uBC84\uADF8\uB8F9 \uBAA9\uB85D \uC870\uD68C \uC911...");
979
+ let result;
980
+ try {
981
+ result = await client.serverGroups(appKey, artifactId);
982
+ } catch (err) {
983
+ stopSpinner(false);
984
+ throw err;
985
+ }
986
+ stopSpinner(true);
987
+ output(opts, {
988
+ headers: ["key", "value"],
989
+ rows: Object.entries(result).map(([k, v]) => [k, String(v ?? "")]),
990
+ raw: result,
991
+ ids: []
992
+ });
993
+ });
994
+
995
+ // src/commands/deploy/histories.ts
996
+ var import_commander6 = require("commander");
997
+ var historiesCommand = new import_commander6.Command("histories").description("\uBC30\uD3EC \uC774\uB825\uC744 \uC870\uD68C\uD55C\uB2E4").argument("<target>", "config.json \uC5D0 \uC815\uC758\uB41C deploy target \uC774\uB984").option("--app-key <k>", "target \uC758 appKey override").option("--artifact-id <id>", "target \uC758 artifactId override").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (targetName, _opts, cmd) => {
998
+ const opts = cmd.optsWithGlobals();
999
+ const target = await getDeployTarget(targetName);
1000
+ const appKey = opts.appKey ?? target.appKey;
1001
+ const artifactId = opts.artifactId ?? target.artifactId;
1002
+ const { client } = await createDeployClient(opts.profile);
1003
+ startSpinner("\uBC30\uD3EC \uC774\uB825 \uC870\uD68C \uC911...");
1004
+ let result;
1005
+ try {
1006
+ result = await client.histories(appKey, artifactId);
1007
+ } catch (err) {
1008
+ stopSpinner(false);
1009
+ throw err;
1010
+ }
1011
+ stopSpinner(true);
1012
+ output(opts, {
1013
+ headers: ["key", "value"],
1014
+ rows: Object.entries(result).map(([k, v]) => [k, String(v ?? "")]),
1015
+ raw: result,
1016
+ ids: []
1017
+ });
1018
+ });
1019
+
1020
+ // src/index.ts
1021
+ var program = new import_commander7.Command();
1022
+ program.name("nhncloud").description("NHN Cloud CLI \u2014 AI agent & terminal friendly").version("0.1.0").option("--json", "JSON \uD615\uC2DD\uC73C\uB85C \uCD9C\uB825").option("--quiet", "\uCD5C\uC18C \uCD9C\uB825 (\uC790\uB3D9\uD654\uC6A9)").option("--no-color", "\uC0C9\uC0C1 \uBE44\uD65C\uC131\uD654");
1023
+ program.hook("preAction", () => {
1024
+ const opts = program.opts();
1025
+ if (!opts.color || process.env["NO_COLOR"]) {
1026
+ import_chalk2.default.level = 0;
1027
+ }
1028
+ if (opts.json || opts.quiet) {
1029
+ setQuiet(true);
1030
+ }
1031
+ });
1032
+ program.addCommand(configureCommand);
1033
+ var logncrashCommand = new import_commander7.Command("logncrash").description("Log & Crash \uAD00\uB828 \uBA85\uB839");
1034
+ logncrashCommand.addCommand(searchCommand);
1035
+ program.addCommand(logncrashCommand);
1036
+ var deployCommand = new import_commander7.Command("deploy").description("NHN Cloud Deploy \uAD00\uB828 \uBA85\uB839");
1037
+ deployCommand.addCommand(runCommand);
1038
+ deployCommand.addCommand(artifactsCommand);
1039
+ deployCommand.addCommand(serverGroupsCommand);
1040
+ deployCommand.addCommand(historiesCommand);
1041
+ program.addCommand(deployCommand);
1042
+ program.parseAsync().catch((err) => {
1043
+ const message = err instanceof Error ? err.message : String(err);
1044
+ const exitCode = err instanceof NhnCloudCliError ? err.exitCode : 1;
1045
+ process.stderr.write(import_chalk2.default.red(`\uC624\uB958: ${message}`) + "\n");
1046
+ process.exit(exitCode);
1047
+ });