@bifos/nhncloud-cli 0.4.0 → 0.5.1

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 CHANGED
@@ -24,7 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  ));
25
25
 
26
26
  // src/index.ts
27
- var import_commander34 = require("commander");
27
+ var import_commander38 = require("commander");
28
28
  var import_chalk12 = __toESM(require("chalk"));
29
29
 
30
30
  // src/utils/spinner.ts
@@ -495,6 +495,21 @@ var BLOCKSTORAGE_HOST = {
495
495
  jp1: "jp1-api-block-storage-infrastructure.nhncloudservice.com"
496
496
  };
497
497
  var IAAS_REGIONS = Object.keys(INSTANCE_HOST).join(", ");
498
+ var NCR_HOST = {
499
+ kr1: "kr1-ncr.api.nhncloudservice.com",
500
+ kr2: "kr2-ncr.api.nhncloudservice.com",
501
+ kr3: "kr3-ncr.api.nhncloudservice.com"
502
+ };
503
+ function ncrHost(region) {
504
+ const host = NCR_HOST[region];
505
+ if (!host) {
506
+ throw new NhnCloudCliError(
507
+ `\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 NCR region \uC785\uB2C8\uB2E4: "${region}". \uC0AC\uC6A9 \uAC00\uB2A5\uD55C region: ${Object.keys(NCR_HOST).join(", ")}`,
508
+ EXIT_PARAM_ERROR
509
+ );
510
+ }
511
+ return host;
512
+ }
498
513
  function instanceHost(region) {
499
514
  const host = INSTANCE_HOST[region];
500
515
  if (!host) {
@@ -740,6 +755,96 @@ var LogncrashClient = class {
740
755
  }
741
756
  };
742
757
 
758
+ // src/services/ncr/client.ts
759
+ var import_ky5 = __toESM(require("ky"));
760
+
761
+ // src/services/ncr/types.ts
762
+ function isRegistry(val) {
763
+ if (typeof val !== "object" || val === null) return false;
764
+ const obj = val;
765
+ return typeof obj["name"] === "string";
766
+ }
767
+ function isRepository(val) {
768
+ if (typeof val !== "object" || val === null) return false;
769
+ const obj = val;
770
+ return typeof obj["name"] === "string";
771
+ }
772
+ function isArtifact(val) {
773
+ return typeof val === "object" && val !== null;
774
+ }
775
+
776
+ // src/services/ncr/client.ts
777
+ var DEFAULT_TIMEOUT_MS = 3e4;
778
+ var NcrClient = class {
779
+ uakId;
780
+ uakSecret;
781
+ baseUrl;
782
+ constructor(uakId, uakSecret, region) {
783
+ this.uakId = uakId;
784
+ this.uakSecret = uakSecret;
785
+ this.baseUrl = `https://${ncrHost(region)}`;
786
+ }
787
+ authHeaders() {
788
+ return {
789
+ "X-TC-AUTHENTICATION-ID": this.uakId,
790
+ "X-TC-AUTHENTICATION-SECRET": this.uakSecret
791
+ };
792
+ }
793
+ /**
794
+ * 레지스트리 목록을 반환한다.
795
+ * GET /ncr/v2.0/appkeys/{appKey}/registries
796
+ * 응답: { header, registries: [...] } — body 가 아니라 named 필드.
797
+ */
798
+ async listRegistries(appKey) {
799
+ const url = `${this.baseUrl}/ncr/v2.0/appkeys/${encodeURIComponent(appKey)}/registries`;
800
+ try {
801
+ const res = await import_ky5.default.get(url, {
802
+ headers: this.authHeaders(),
803
+ retry: 0,
804
+ timeout: DEFAULT_TIMEOUT_MS
805
+ }).json();
806
+ unwrapHeader(res);
807
+ if (!Array.isArray(res.registries)) {
808
+ if (res.registries === void 0) return [];
809
+ throw new NhnCloudCliError(
810
+ "NCR API \uC751\uB2F5 \uD615\uC2DD \uC624\uB958: registries \uAC00 \uBC30\uC5F4\uC774 \uC544\uB2D9\uB2C8\uB2E4.",
811
+ EXIT_API_ERROR
812
+ );
813
+ }
814
+ return res.registries.filter(isRegistry);
815
+ } catch (err) {
816
+ if (err instanceof NhnCloudCliError) throw err;
817
+ throw toNhnCloudCliError(err);
818
+ }
819
+ }
820
+ /**
821
+ * 단일 레지스트리를 반환한다.
822
+ * GET /ncr/v2.0/appkeys/{appKey}/registries/{registryNameOrId}
823
+ * 응답: { header, registry: {...} } — body 가 아니라 named 필드.
824
+ */
825
+ async getRegistry(appKey, registry) {
826
+ const url = `${this.baseUrl}/ncr/v2.0/appkeys/${encodeURIComponent(appKey)}/registries/${encodeURIComponent(registry)}`;
827
+ try {
828
+ const res = await import_ky5.default.get(url, {
829
+ headers: this.authHeaders(),
830
+ retry: 0,
831
+ timeout: DEFAULT_TIMEOUT_MS
832
+ }).json();
833
+ unwrapHeader(res);
834
+ if (res.registry && isRegistry(res.registry)) {
835
+ return res.registry;
836
+ }
837
+ throw new NhnCloudCliError(
838
+ "NCR API \uC751\uB2F5 \uD615\uC2DD \uC624\uB958: registry \uAC1D\uCCB4\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
839
+ EXIT_API_ERROR
840
+ );
841
+ } catch (err) {
842
+ if (err instanceof NhnCloudCliError) throw err;
843
+ throw toNhnCloudCliError(err);
844
+ }
845
+ }
846
+ };
847
+
743
848
  // src/commands/configure-verify.ts
744
849
  async function verifyUserAccessKey(uak) {
745
850
  try {
@@ -763,6 +868,19 @@ async function verifyIaas(iaas) {
763
868
  throw err;
764
869
  }
765
870
  }
871
+ async function verifyNcr(uak, appkey) {
872
+ if (!appkey) return false;
873
+ const client = new NcrClient(uak.id, uak.secret, "kr1");
874
+ try {
875
+ await client.listRegistries(appkey);
876
+ return true;
877
+ } catch (err) {
878
+ if (err instanceof NhnCloudCliError && err.exitCode === EXIT_AUTH_ERROR) {
879
+ return false;
880
+ }
881
+ throw err;
882
+ }
883
+ }
766
884
  async function verifyLogncrash(cred) {
767
885
  if (!cred.appkey || !cred.secret) return false;
768
886
  const client = new LogncrashClient(cred.appkey, cred.secret);
@@ -786,7 +904,7 @@ async function verifyLogncrash(cred) {
786
904
  }
787
905
 
788
906
  // src/commands/configure.ts
789
- async function saveAndVerify(profileName, uak, logncrash, iaas, doVerify) {
907
+ async function saveAndVerify(profileName, uak, logncrash, iaas, ncr, doVerify) {
790
908
  if (doVerify) {
791
909
  if (uak) {
792
910
  const ok = await verifyUserAccessKey(uak);
@@ -821,6 +939,26 @@ async function saveAndVerify(profileName, uak, logncrash, iaas, doVerify) {
821
939
  );
822
940
  }
823
941
  }
942
+ if (ncr) {
943
+ if (uak) {
944
+ const appkey = ncr.appkey ?? "";
945
+ const ok = await verifyNcr(uak, appkey);
946
+ if (ok) {
947
+ process.stderr.write(import_chalk.default.green(" \u2713 ncr \uC5F0\uACB0 \uC131\uACF5 (kr1)\n"));
948
+ } else {
949
+ throw new NhnCloudCliError(
950
+ "ncr \uC778\uC99D \uC2E4\uD328 \u2014 appkey \uB610\uB294 UAK \uB97C \uD655\uC778\uD558\uC138\uC694.",
951
+ EXIT_AUTH_ERROR
952
+ );
953
+ }
954
+ } else {
955
+ process.stderr.write(
956
+ import_chalk.default.yellow(
957
+ " \u26A0 ncr verify \uAC74\uB108\uB700 \u2014 \uC774\uBC88 \uC124\uC815\uC5D0 UAK \uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. ncr \uBA85\uB839\uC740 \uACF5\uD1B5 UAK \uAC00 \uD544\uC694\uD558\uB2C8 \uBA3C\uC800 \uC124\uC815\uD558\uC138\uC694.\n"
958
+ )
959
+ );
960
+ }
961
+ }
824
962
  }
825
963
  if (uak) {
826
964
  await setUserAccessKey(profileName, uak);
@@ -831,6 +969,9 @@ async function saveAndVerify(profileName, uak, logncrash, iaas, doVerify) {
831
969
  if (iaas) {
832
970
  await setIaasCredential(profileName, iaas);
833
971
  }
972
+ if (ncr) {
973
+ await setServiceCredential(profileName, "ncr", ncr);
974
+ }
834
975
  process.stderr.write(import_chalk.default.green(`
835
976
  \u2713 profile "${profileName}" \uC124\uC815\uC774 \uC800\uC7A5\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
836
977
  `));
@@ -890,12 +1031,25 @@ async function runInteractive(opts) {
890
1031
  });
891
1032
  iaas = { tenantId, username: iaasUsername, password: iaasPassword, region };
892
1033
  }
1034
+ let ncr;
1035
+ const setupNcr = await confirm({
1036
+ message: "ncr \uC790\uACA9\uC99D\uBA85\uB3C4 \uC124\uC815\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
1037
+ default: false
1038
+ });
1039
+ if (setupNcr) {
1040
+ process.stderr.write(import_chalk.default.gray("\n\u2014 ncr (Container Registry) \uC790\uACA9\uC99D\uBA85 \u2014\n"));
1041
+ const ncrAppkey = await input({
1042
+ message: "ncr appkey",
1043
+ validate: (v) => v.trim().length > 0 || "ncr appkey \uB97C \uC785\uB825\uD558\uC138\uC694"
1044
+ });
1045
+ ncr = { appkey: ncrAppkey.trim() };
1046
+ }
893
1047
  if (opts.verify) {
894
1048
  process.stderr.write(import_chalk.default.gray("\n\u2014 \uC5F0\uACB0 \uD14C\uC2A4\uD2B8 \uC911\u2026 \u2014\n"));
895
1049
  }
896
1050
  if (opts.verify) {
897
1051
  try {
898
- await saveAndVerify(profileName, uak, logncrash, iaas, true);
1052
+ await saveAndVerify(profileName, uak, logncrash, iaas, ncr, true);
899
1053
  } catch (err) {
900
1054
  if (err instanceof NhnCloudCliError && err.exitCode === EXIT_AUTH_ERROR) {
901
1055
  process.stderr.write(import_chalk.default.red(` \u2717 ${err.message}
@@ -908,13 +1062,13 @@ async function runInteractive(opts) {
908
1062
  process.stderr.write(import_chalk.default.yellow("\uC800\uC7A5\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.\n"));
909
1063
  return;
910
1064
  }
911
- await saveAndVerify(profileName, uak, logncrash, iaas, false);
1065
+ await saveAndVerify(profileName, uak, logncrash, iaas, ncr, false);
912
1066
  } else {
913
1067
  throw err;
914
1068
  }
915
1069
  }
916
1070
  } else {
917
- await saveAndVerify(profileName, uak, logncrash, iaas, false);
1071
+ await saveAndVerify(profileName, uak, logncrash, iaas, ncr, false);
918
1072
  }
919
1073
  }
920
1074
  async function runNonInteractive(opts) {
@@ -930,22 +1084,23 @@ async function runNonInteractive(opts) {
930
1084
  password: iaasPassword,
931
1085
  region: opts.iaasRegion ?? "kr1"
932
1086
  } : void 0;
933
- if (!uak && !logncrash && !iaas) {
1087
+ const ncr = opts.ncrAppkey?.trim() ? { appkey: opts.ncrAppkey.trim() } : void 0;
1088
+ if (!uak && !logncrash && !iaas && !ncr) {
934
1089
  throw new NhnCloudCliError(
935
- "\uBE44\uB300\uD654\uD615 \uBAA8\uB4DC: --uak-id + UAK secret, --logncrash-appkey + logncrash secret,\n\uB610\uB294 --iaas-tenant-id + --iaas-username + iaas password \uC911 \uD558\uB098\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.\nsecret/password \uB294 \uB178\uCD9C \uBC29\uC9C0\uB97C \uC704\uD574 \uD658\uACBD\uBCC0\uC218 \uAD8C\uC7A5:\nNHNCLOUD_UAK_SECRET / NHNCLOUD_LOGNCRASH_SECRET / NHNCLOUD_IAAS_PASSWORD.",
1090
+ "\uBE44\uB300\uD654\uD615 \uBAA8\uB4DC: --uak-id + UAK secret, --logncrash-appkey + logncrash secret,\n--iaas-tenant-id + --iaas-username + iaas password,\n\uB610\uB294 --ncr-appkey \uC911 \uD558\uB098\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.\nsecret/password \uB294 \uB178\uCD9C \uBC29\uC9C0\uB97C \uC704\uD574 \uD658\uACBD\uBCC0\uC218 \uAD8C\uC7A5:\nNHNCLOUD_UAK_SECRET / NHNCLOUD_LOGNCRASH_SECRET / NHNCLOUD_IAAS_PASSWORD.",
936
1091
  EXIT_PARAM_ERROR
937
1092
  );
938
1093
  }
939
1094
  if (opts.verify) {
940
1095
  process.stderr.write(import_chalk.default.gray("\uC5F0\uACB0 \uD14C\uC2A4\uD2B8 \uC911\u2026\n"));
941
1096
  }
942
- await saveAndVerify(profileName, uak, logncrash, iaas, opts.verify);
1097
+ await saveAndVerify(profileName, uak, logncrash, iaas, ncr, opts.verify);
943
1098
  }
944
1099
  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("--iaas-tenant-id <id>", "iaas tenantId / \uD504\uB85C\uC81D\uD2B8 ID (\uBE44\uB300\uD654\uD615)").option("--iaas-username <user>", "iaas IAM username (\uBE44\uB300\uD654\uD615)").option(
945
1100
  "--iaas-password <pass>",
946
1101
  "iaas API \uBE44\uBC00\uBC88\uD638 (\uBE44\uB300\uD654\uD615, \uB178\uCD9C \uBC29\uC9C0\uB85C env NHNCLOUD_IAAS_PASSWORD \uAD8C\uC7A5)"
947
- ).option("--iaas-region <region>", "iaas region (\uAE30\uBCF8: kr1)", "kr1").option("--no-verify", "\uC5F0\uACB0 \uD14C\uC2A4\uD2B8 \uC0DD\uB7B5").action(async (opts) => {
948
- const hasFlag = opts.uakId || opts.uakSecret || opts.logncrashAppkey || opts.logncrashSecret || opts.iaasTenantId || opts.iaasUsername || opts.iaasPassword;
1102
+ ).option("--iaas-region <region>", "iaas region (\uAE30\uBCF8: kr1)", "kr1").option("--ncr-appkey <key>", "ncr appkey (\uBE44\uB300\uD654\uD615)").option("--no-verify", "\uC5F0\uACB0 \uD14C\uC2A4\uD2B8 \uC0DD\uB7B5").action(async (opts) => {
1103
+ const hasFlag = opts.uakId || opts.uakSecret || opts.logncrashAppkey || opts.logncrashSecret || opts.iaasTenantId || opts.iaasUsername || opts.iaasPassword || opts.ncrAppkey;
949
1104
  try {
950
1105
  if (hasFlag) {
951
1106
  await runNonInteractive(opts);
@@ -1375,12 +1530,13 @@ async function scrollNextOrExpire(client, scrollKey) {
1375
1530
  var import_commander5 = require("commander");
1376
1531
 
1377
1532
  // src/services/deploy/client.ts
1378
- var import_ky5 = __toESM(require("ky"));
1533
+ var import_ky6 = __toESM(require("ky"));
1379
1534
  function isBinaryGroup(val) {
1380
1535
  if (typeof val !== "object" || val === null) return false;
1381
1536
  const obj = val;
1382
1537
  const keyType = typeof obj["key"];
1383
- return (keyType === "number" || keyType === "string") && typeof obj["name"] === "string";
1538
+ const descriptionType = typeof obj["description"];
1539
+ return (keyType === "number" || keyType === "string") && typeof obj["name"] === "string" && (descriptionType === "undefined" || descriptionType === "string" || obj["description"] === null);
1384
1540
  }
1385
1541
  function isBinary(val) {
1386
1542
  if (typeof val !== "object" || val === null) return false;
@@ -1390,7 +1546,7 @@ function isBinary(val) {
1390
1546
  return (binaryKeyType === "number" || binaryKeyType === "string") && (binarySizeType === "number" || binarySizeType === "string");
1391
1547
  }
1392
1548
  var SYNC_TIMEOUT_MS = 6e5;
1393
- var DEFAULT_TIMEOUT_MS = 3e4;
1549
+ var DEFAULT_TIMEOUT_MS2 = 3e4;
1394
1550
  var DeployClient = class {
1395
1551
  accessToken;
1396
1552
  baseUrl;
@@ -1422,14 +1578,14 @@ var DeployClient = class {
1422
1578
  payload["targetServerHostnames"] = params.targetHosts;
1423
1579
  }
1424
1580
  try {
1425
- const res = await import_ky5.default.post(url, {
1581
+ const res = await import_ky6.default.post(url, {
1426
1582
  headers: {
1427
1583
  ...this.authHeaders(),
1428
1584
  "Content-Type": "application/json"
1429
1585
  },
1430
1586
  json: payload,
1431
1587
  retry: 0,
1432
- timeout: isAsync ? DEFAULT_TIMEOUT_MS : SYNC_TIMEOUT_MS
1588
+ timeout: isAsync ? DEFAULT_TIMEOUT_MS2 : SYNC_TIMEOUT_MS
1433
1589
  }).json();
1434
1590
  return unwrap(res);
1435
1591
  } catch (err) {
@@ -1442,10 +1598,10 @@ var DeployClient = class {
1442
1598
  async artifacts(appKey) {
1443
1599
  const url = `${this.baseUrl}/api/v2.1/projects/${encodeURIComponent(appKey)}/artifacts`;
1444
1600
  try {
1445
- const res = await import_ky5.default.get(url, {
1601
+ const res = await import_ky6.default.get(url, {
1446
1602
  headers: this.authHeaders(),
1447
1603
  retry: 0,
1448
- timeout: DEFAULT_TIMEOUT_MS
1604
+ timeout: DEFAULT_TIMEOUT_MS2
1449
1605
  }).json();
1450
1606
  return unwrap(res);
1451
1607
  } catch (err) {
@@ -1458,10 +1614,10 @@ var DeployClient = class {
1458
1614
  async serverGroups(appKey, artifactId) {
1459
1615
  const url = `${this.baseUrl}/api/v2.1/projects/${encodeURIComponent(appKey)}/artifacts/${encodeURIComponent(artifactId)}/server-groups`;
1460
1616
  try {
1461
- const res = await import_ky5.default.get(url, {
1617
+ const res = await import_ky6.default.get(url, {
1462
1618
  headers: this.authHeaders(),
1463
1619
  retry: 0,
1464
- timeout: DEFAULT_TIMEOUT_MS
1620
+ timeout: DEFAULT_TIMEOUT_MS2
1465
1621
  }).json();
1466
1622
  return unwrap(res);
1467
1623
  } catch (err) {
@@ -1474,10 +1630,10 @@ var DeployClient = class {
1474
1630
  async histories(appKey, artifactId) {
1475
1631
  const url = `${this.baseUrl}/api/v2.1/projects/${encodeURIComponent(appKey)}/artifacts/${encodeURIComponent(artifactId)}/deploy-histories`;
1476
1632
  try {
1477
- const res = await import_ky5.default.get(url, {
1633
+ const res = await import_ky6.default.get(url, {
1478
1634
  headers: this.authHeaders(),
1479
1635
  retry: 0,
1480
- timeout: DEFAULT_TIMEOUT_MS
1636
+ timeout: DEFAULT_TIMEOUT_MS2
1481
1637
  }).json();
1482
1638
  return unwrap(res);
1483
1639
  } catch (err) {
@@ -1490,10 +1646,10 @@ var DeployClient = class {
1490
1646
  async binaryGroups(appKey, artifactId) {
1491
1647
  const url = `${this.baseUrl}/api/v2.1/projects/${encodeURIComponent(appKey)}/artifacts/${encodeURIComponent(artifactId)}/binary-groups`;
1492
1648
  try {
1493
- const res = await import_ky5.default.get(url, {
1649
+ const res = await import_ky6.default.get(url, {
1494
1650
  headers: this.authHeaders(),
1495
1651
  retry: 0,
1496
- timeout: DEFAULT_TIMEOUT_MS
1652
+ timeout: DEFAULT_TIMEOUT_MS2
1497
1653
  }).json();
1498
1654
  const body = unwrap(res);
1499
1655
  const list = body.binaryGroups;
@@ -1520,11 +1676,11 @@ var DeployClient = class {
1520
1676
  if (params.sortKey !== void 0) searchParams["sortKey"] = params.sortKey;
1521
1677
  if (params.sortDirection !== void 0) searchParams["sortDirection"] = params.sortDirection;
1522
1678
  try {
1523
- const res = await import_ky5.default.get(url, {
1679
+ const res = await import_ky6.default.get(url, {
1524
1680
  headers: this.authHeaders(),
1525
1681
  searchParams,
1526
1682
  retry: 0,
1527
- timeout: DEFAULT_TIMEOUT_MS
1683
+ timeout: DEFAULT_TIMEOUT_MS2
1528
1684
  }).json();
1529
1685
  const body = unwrap(res);
1530
1686
  const list = body.binaries;
@@ -1562,7 +1718,7 @@ var DeployClient = class {
1562
1718
  form.append("description", params.description);
1563
1719
  }
1564
1720
  try {
1565
- const res = await import_ky5.default.post(url, {
1721
+ const res = await import_ky6.default.post(url, {
1566
1722
  headers: this.authHeaders(),
1567
1723
  // 인증 헤더만 — multipart boundary 는 ky 가 자동 설정
1568
1724
  body: form,
@@ -1598,7 +1754,7 @@ var DeployClient = class {
1598
1754
  async downloadBinary(appKey, artifactId, binaryGroupKey, binaryKey) {
1599
1755
  const url = `${this.baseUrl}/api/v2.1/projects/${encodeURIComponent(appKey)}/artifacts/${encodeURIComponent(artifactId)}/binary-group/${binaryGroupKey}/binaries/${binaryKey}`;
1600
1756
  try {
1601
- const ab = await import_ky5.default.get(url, {
1757
+ const ab = await import_ky6.default.get(url, {
1602
1758
  headers: this.authHeaders(),
1603
1759
  retry: 0,
1604
1760
  timeout: SYNC_TIMEOUT_MS
@@ -1974,8 +2130,8 @@ var downloadCommand = new import_commander12.Command("download").description("\u
1974
2130
  var import_commander13 = require("commander");
1975
2131
 
1976
2132
  // src/services/instance/client.ts
1977
- var import_ky6 = __toESM(require("ky"));
1978
- var DEFAULT_TIMEOUT_MS2 = 3e4;
2133
+ var import_ky7 = __toESM(require("ky"));
2134
+ var DEFAULT_TIMEOUT_MS3 = 3e4;
1979
2135
  var DEFAULT_POLL_INTERVAL_MS = 5e3;
1980
2136
  function isServer(val) {
1981
2137
  if (typeof val !== "object" || val === null) return false;
@@ -2104,10 +2260,10 @@ var InstanceClient = class {
2104
2260
  async list() {
2105
2261
  const url = `${this.computeEndpoint}/servers/detail`;
2106
2262
  try {
2107
- const raw = await import_ky6.default.get(url, {
2263
+ const raw = await import_ky7.default.get(url, {
2108
2264
  headers: this.authHeaders(),
2109
2265
  retry: 0,
2110
- timeout: DEFAULT_TIMEOUT_MS2
2266
+ timeout: DEFAULT_TIMEOUT_MS3
2111
2267
  }).json();
2112
2268
  if (!isServersResponse(raw)) {
2113
2269
  throw new NhnCloudCliError(
@@ -2126,10 +2282,10 @@ var InstanceClient = class {
2126
2282
  async get(id) {
2127
2283
  const url = `${this.computeEndpoint}/servers/${encodeURIComponent(id)}`;
2128
2284
  try {
2129
- const raw = await import_ky6.default.get(url, {
2285
+ const raw = await import_ky7.default.get(url, {
2130
2286
  headers: this.authHeaders(),
2131
2287
  retry: 0,
2132
- timeout: DEFAULT_TIMEOUT_MS2
2288
+ timeout: DEFAULT_TIMEOUT_MS3
2133
2289
  }).json();
2134
2290
  if (!isServerResponse(raw)) {
2135
2291
  throw new NhnCloudCliError(
@@ -2185,11 +2341,11 @@ var InstanceClient = class {
2185
2341
  }
2186
2342
  let raw;
2187
2343
  try {
2188
- raw = await import_ky6.default.post(url, {
2344
+ raw = await import_ky7.default.post(url, {
2189
2345
  headers: this.authHeaders(),
2190
2346
  json: { server: serverBody },
2191
2347
  retry: 0,
2192
- timeout: DEFAULT_TIMEOUT_MS2
2348
+ timeout: DEFAULT_TIMEOUT_MS3
2193
2349
  }).json();
2194
2350
  } catch (err) {
2195
2351
  throw toNhnCloudCliError(err);
@@ -2208,10 +2364,10 @@ var InstanceClient = class {
2208
2364
  async delete(id) {
2209
2365
  const url = `${this.computeEndpoint}/servers/${encodeURIComponent(id)}`;
2210
2366
  try {
2211
- await import_ky6.default.delete(url, {
2367
+ await import_ky7.default.delete(url, {
2212
2368
  headers: this.authHeaders(),
2213
2369
  retry: 0,
2214
- timeout: DEFAULT_TIMEOUT_MS2
2370
+ timeout: DEFAULT_TIMEOUT_MS3
2215
2371
  });
2216
2372
  } catch (err) {
2217
2373
  throw toNhnCloudCliError(err);
@@ -2226,11 +2382,11 @@ var InstanceClient = class {
2226
2382
  async serverAction(id, payload) {
2227
2383
  const url = `${this.computeEndpoint}/servers/${encodeURIComponent(id)}/action`;
2228
2384
  try {
2229
- await import_ky6.default.post(url, {
2385
+ await import_ky7.default.post(url, {
2230
2386
  headers: this.authHeaders(),
2231
2387
  json: payload,
2232
2388
  retry: 0,
2233
- timeout: DEFAULT_TIMEOUT_MS2
2389
+ timeout: DEFAULT_TIMEOUT_MS3
2234
2390
  });
2235
2391
  } catch (err) {
2236
2392
  throw toNhnCloudCliError(err);
@@ -2271,11 +2427,11 @@ var InstanceClient = class {
2271
2427
  if (params.minDisk !== void 0) searchParams["minDisk"] = params.minDisk;
2272
2428
  if (params.minRam !== void 0) searchParams["minRam"] = params.minRam;
2273
2429
  try {
2274
- const raw = await import_ky6.default.get(url, {
2430
+ const raw = await import_ky7.default.get(url, {
2275
2431
  headers: this.authHeaders(),
2276
2432
  searchParams,
2277
2433
  retry: 0,
2278
- timeout: DEFAULT_TIMEOUT_MS2
2434
+ timeout: DEFAULT_TIMEOUT_MS3
2279
2435
  }).json();
2280
2436
  if (params.detail) {
2281
2437
  if (!isFlavorDetailsResponse(raw)) {
@@ -2304,10 +2460,10 @@ var InstanceClient = class {
2304
2460
  async listAvailabilityZones() {
2305
2461
  const url = `${this.computeEndpoint}/os-availability-zone`;
2306
2462
  try {
2307
- const raw = await import_ky6.default.get(url, {
2463
+ const raw = await import_ky7.default.get(url, {
2308
2464
  headers: this.authHeaders(),
2309
2465
  retry: 0,
2310
- timeout: DEFAULT_TIMEOUT_MS2
2466
+ timeout: DEFAULT_TIMEOUT_MS3
2311
2467
  }).json();
2312
2468
  if (!isAvailabilityZonesResponse(raw)) {
2313
2469
  throw new NhnCloudCliError(
@@ -2324,7 +2480,7 @@ var InstanceClient = class {
2324
2480
  async listKeypairs() {
2325
2481
  const url = `${this.computeEndpoint}/os-keypairs`;
2326
2482
  try {
2327
- const raw = await import_ky6.default.get(url, { headers: this.authHeaders(), retry: 0, timeout: DEFAULT_TIMEOUT_MS2 }).json();
2483
+ const raw = await import_ky7.default.get(url, { headers: this.authHeaders(), retry: 0, timeout: DEFAULT_TIMEOUT_MS3 }).json();
2328
2484
  if (!isKeypairsResponse(raw)) {
2329
2485
  throw new NhnCloudCliError(
2330
2486
  "instance keypairs \uC751\uB2F5 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 keypairs \uBC30\uC5F4\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
@@ -2340,7 +2496,7 @@ var InstanceClient = class {
2340
2496
  async getKeypair(name) {
2341
2497
  const url = `${this.computeEndpoint}/os-keypairs/${encodeURIComponent(name)}`;
2342
2498
  try {
2343
- const raw = await import_ky6.default.get(url, { headers: this.authHeaders(), retry: 0, timeout: DEFAULT_TIMEOUT_MS2 }).json();
2499
+ const raw = await import_ky7.default.get(url, { headers: this.authHeaders(), retry: 0, timeout: DEFAULT_TIMEOUT_MS3 }).json();
2344
2500
  if (!isKeypairDetailResponse(raw)) {
2345
2501
  throw new NhnCloudCliError(
2346
2502
  `instance keypair get(${name}) \uC751\uB2F5 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 keypair \uC0C1\uC138 \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.`,
@@ -2365,11 +2521,11 @@ var InstanceClient = class {
2365
2521
  }
2366
2522
  let raw;
2367
2523
  try {
2368
- raw = await import_ky6.default.post(url, {
2524
+ raw = await import_ky7.default.post(url, {
2369
2525
  headers: this.authHeaders(),
2370
2526
  json: { keypair: keypairBody },
2371
2527
  retry: 0,
2372
- timeout: DEFAULT_TIMEOUT_MS2
2528
+ timeout: DEFAULT_TIMEOUT_MS3
2373
2529
  }).json();
2374
2530
  } catch (err) {
2375
2531
  throw toNhnCloudCliError(err);
@@ -2394,7 +2550,7 @@ var InstanceClient = class {
2394
2550
  async deleteKeypair(name) {
2395
2551
  const url = `${this.computeEndpoint}/os-keypairs/${encodeURIComponent(name)}`;
2396
2552
  try {
2397
- await import_ky6.default.delete(url, { headers: this.authHeaders(), retry: 0, timeout: DEFAULT_TIMEOUT_MS2 });
2553
+ await import_ky7.default.delete(url, { headers: this.authHeaders(), retry: 0, timeout: DEFAULT_TIMEOUT_MS3 });
2398
2554
  } catch (err) {
2399
2555
  throw toNhnCloudCliError(err);
2400
2556
  }
@@ -2414,11 +2570,11 @@ var InstanceClient = class {
2414
2570
  if (params.owner !== void 0) searchParams["owner"] = params.owner;
2415
2571
  if (params.status !== void 0) searchParams["status"] = params.status;
2416
2572
  try {
2417
- const raw = await import_ky6.default.get(url, {
2573
+ const raw = await import_ky7.default.get(url, {
2418
2574
  headers: this.authHeaders(),
2419
2575
  searchParams,
2420
2576
  retry: 0,
2421
- timeout: DEFAULT_TIMEOUT_MS2
2577
+ timeout: DEFAULT_TIMEOUT_MS3
2422
2578
  }).json();
2423
2579
  if (!isImagesResponse(raw)) {
2424
2580
  throw new NhnCloudCliError(
@@ -2438,7 +2594,7 @@ var InstanceClient = class {
2438
2594
  async listVolumeAttachments(serverId) {
2439
2595
  const url = `${this.computeEndpoint}/servers/${encodeURIComponent(serverId)}/os-volume_attachments`;
2440
2596
  try {
2441
- const raw = await import_ky6.default.get(url, { headers: this.authHeaders(), retry: 0, timeout: DEFAULT_TIMEOUT_MS2 }).json();
2597
+ const raw = await import_ky7.default.get(url, { headers: this.authHeaders(), retry: 0, timeout: DEFAULT_TIMEOUT_MS3 }).json();
2442
2598
  if (!isVolumeAttachmentsResponse(raw)) {
2443
2599
  throw new NhnCloudCliError(
2444
2600
  "instance volumes \uC751\uB2F5 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 volumeAttachments \uBC30\uC5F4\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
@@ -2457,11 +2613,11 @@ var InstanceClient = class {
2457
2613
  async attachVolume(serverId, volumeId) {
2458
2614
  const url = `${this.computeEndpoint}/servers/${encodeURIComponent(serverId)}/os-volume_attachments`;
2459
2615
  try {
2460
- const res = await import_ky6.default.post(url, {
2616
+ const res = await import_ky7.default.post(url, {
2461
2617
  headers: this.authHeaders(),
2462
2618
  json: { volumeAttachment: { volumeId } },
2463
2619
  retry: 0,
2464
- timeout: DEFAULT_TIMEOUT_MS2
2620
+ timeout: DEFAULT_TIMEOUT_MS3
2465
2621
  });
2466
2622
  if (res.status === 202 || res.headers.get("content-length") === "0") {
2467
2623
  return { id: volumeId, volumeId, serverId, device: "" };
@@ -2485,7 +2641,7 @@ var InstanceClient = class {
2485
2641
  async detachVolume(serverId, volumeId) {
2486
2642
  const url = `${this.computeEndpoint}/servers/${encodeURIComponent(serverId)}/os-volume_attachments/${encodeURIComponent(volumeId)}`;
2487
2643
  try {
2488
- await import_ky6.default.delete(url, { headers: this.authHeaders(), retry: 0, timeout: DEFAULT_TIMEOUT_MS2 });
2644
+ await import_ky7.default.delete(url, { headers: this.authHeaders(), retry: 0, timeout: DEFAULT_TIMEOUT_MS3 });
2489
2645
  } catch (err) {
2490
2646
  throw toNhnCloudCliError(err);
2491
2647
  }
@@ -2555,8 +2711,8 @@ var listCommand = new import_commander13.Command("list").description("\uC778\uC2
2555
2711
  var import_commander14 = require("commander");
2556
2712
 
2557
2713
  // src/services/blockstorage/client.ts
2558
- var import_ky7 = __toESM(require("ky"));
2559
- var DEFAULT_TIMEOUT_MS3 = 3e4;
2714
+ var import_ky8 = __toESM(require("ky"));
2715
+ var DEFAULT_TIMEOUT_MS4 = 3e4;
2560
2716
  function isVolume(val) {
2561
2717
  if (typeof val !== "object" || val === null) return false;
2562
2718
  const obj = val;
@@ -2592,7 +2748,7 @@ var BlockStorageClient = class {
2592
2748
  if (params?.offset !== void 0) searchParams["offset"] = params.offset;
2593
2749
  if (params?.marker !== void 0) searchParams["marker"] = params.marker;
2594
2750
  try {
2595
- const raw = await import_ky7.default.get(url, { headers: this.authHeaders(), searchParams, retry: 0, timeout: DEFAULT_TIMEOUT_MS3 }).json();
2751
+ const raw = await import_ky8.default.get(url, { headers: this.authHeaders(), searchParams, retry: 0, timeout: DEFAULT_TIMEOUT_MS4 }).json();
2596
2752
  if (!isVolumesResponse(raw)) {
2597
2753
  throw new NhnCloudCliError(
2598
2754
  "volume list \uC751\uB2F5 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 volumes \uBC30\uC5F4\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
@@ -2607,7 +2763,7 @@ var BlockStorageClient = class {
2607
2763
  async get(id) {
2608
2764
  const url = `${this.endpoint}/volumes/${encodeURIComponent(id)}`;
2609
2765
  try {
2610
- const raw = await import_ky7.default.get(url, { headers: this.authHeaders(), retry: 0, timeout: DEFAULT_TIMEOUT_MS3 }).json();
2766
+ const raw = await import_ky8.default.get(url, { headers: this.authHeaders(), retry: 0, timeout: DEFAULT_TIMEOUT_MS4 }).json();
2611
2767
  if (!isVolumeResponse(raw)) {
2612
2768
  throw new NhnCloudCliError(
2613
2769
  "volume get \uC751\uB2F5 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 volume \uAC1D\uCCB4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
@@ -2627,11 +2783,11 @@ var BlockStorageClient = class {
2627
2783
  if (params.volume_type !== void 0) volumeBody["volume_type"] = params.volume_type;
2628
2784
  if (params.snapshot_id !== void 0) volumeBody["snapshot_id"] = params.snapshot_id;
2629
2785
  try {
2630
- const raw = await import_ky7.default.post(url, {
2786
+ const raw = await import_ky8.default.post(url, {
2631
2787
  headers: this.authHeaders(),
2632
2788
  json: { volume: volumeBody },
2633
2789
  retry: 0,
2634
- timeout: DEFAULT_TIMEOUT_MS3
2790
+ timeout: DEFAULT_TIMEOUT_MS4
2635
2791
  }).json();
2636
2792
  if (!isVolumeResponse(raw)) {
2637
2793
  throw new NhnCloudCliError(
@@ -2780,8 +2936,8 @@ var createCommand = new import_commander16.Command("create").description("\uBCFC
2780
2936
  var import_commander17 = require("commander");
2781
2937
 
2782
2938
  // src/services/network/client.ts
2783
- var import_ky8 = __toESM(require("ky"));
2784
- var DEFAULT_TIMEOUT_MS4 = 3e4;
2939
+ var import_ky9 = __toESM(require("ky"));
2940
+ var DEFAULT_TIMEOUT_MS5 = 3e4;
2785
2941
  function isVpc(val) {
2786
2942
  if (typeof val !== "object" || val === null) return false;
2787
2943
  const obj = val;
@@ -2834,10 +2990,10 @@ var NetworkClient = class {
2834
2990
  async listVpcs() {
2835
2991
  const url = `${this.networkEndpoint}/vpcs`;
2836
2992
  try {
2837
- const raw = await import_ky8.default.get(url, {
2993
+ const raw = await import_ky9.default.get(url, {
2838
2994
  headers: this.authHeaders(),
2839
2995
  retry: 0,
2840
- timeout: DEFAULT_TIMEOUT_MS4
2996
+ timeout: DEFAULT_TIMEOUT_MS5
2841
2997
  }).json();
2842
2998
  if (!isVpcsResponse(raw)) {
2843
2999
  throw new NhnCloudCliError(
@@ -2856,10 +3012,10 @@ var NetworkClient = class {
2856
3012
  async listSubnets() {
2857
3013
  const url = `${this.networkEndpoint}/vpcsubnets`;
2858
3014
  try {
2859
- const raw = await import_ky8.default.get(url, {
3015
+ const raw = await import_ky9.default.get(url, {
2860
3016
  headers: this.authHeaders(),
2861
3017
  retry: 0,
2862
- timeout: DEFAULT_TIMEOUT_MS4
3018
+ timeout: DEFAULT_TIMEOUT_MS5
2863
3019
  }).json();
2864
3020
  if (!isSubnetsResponse(raw)) {
2865
3021
  throw new NhnCloudCliError(
@@ -2876,7 +3032,7 @@ var NetworkClient = class {
2876
3032
  async listFloatingIps() {
2877
3033
  const url = `${this.networkEndpoint}/floatingips`;
2878
3034
  try {
2879
- const raw = await import_ky8.default.get(url, { headers: this.authHeaders(), retry: 0, timeout: DEFAULT_TIMEOUT_MS4 }).json();
3035
+ const raw = await import_ky9.default.get(url, { headers: this.authHeaders(), retry: 0, timeout: DEFAULT_TIMEOUT_MS5 }).json();
2880
3036
  if (!isFloatingIpsResponse(raw)) {
2881
3037
  throw new NhnCloudCliError(
2882
3038
  "floatingip list \uC751\uB2F5 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 floatingips \uBC30\uC5F4\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
@@ -2892,11 +3048,11 @@ var NetworkClient = class {
2892
3048
  async createFloatingIp(params) {
2893
3049
  const url = `${this.networkEndpoint}/floatingips`;
2894
3050
  try {
2895
- const raw = await import_ky8.default.post(url, {
3051
+ const raw = await import_ky9.default.post(url, {
2896
3052
  headers: this.authHeaders(),
2897
3053
  json: { floatingip: { floating_network_id: params.floating_network_id } },
2898
3054
  retry: 0,
2899
- timeout: DEFAULT_TIMEOUT_MS4
3055
+ timeout: DEFAULT_TIMEOUT_MS5
2900
3056
  }).json();
2901
3057
  if (!isFloatingIpResponse(raw)) {
2902
3058
  throw new NhnCloudCliError(
@@ -2913,7 +3069,7 @@ var NetworkClient = class {
2913
3069
  async deleteFloatingIp(id) {
2914
3070
  const url = `${this.networkEndpoint}/floatingips/${encodeURIComponent(id)}`;
2915
3071
  try {
2916
- await import_ky8.default.delete(url, { headers: this.authHeaders(), retry: 0, timeout: DEFAULT_TIMEOUT_MS4 });
3072
+ await import_ky9.default.delete(url, { headers: this.authHeaders(), retry: 0, timeout: DEFAULT_TIMEOUT_MS5 });
2917
3073
  } catch (err) {
2918
3074
  throw toNhnCloudCliError(err);
2919
3075
  }
@@ -2927,11 +3083,11 @@ var NetworkClient = class {
2927
3083
  async findExternalNetworkId() {
2928
3084
  const url = `${this.networkEndpoint}/vpcs`;
2929
3085
  try {
2930
- const raw = await import_ky8.default.get(url, {
3086
+ const raw = await import_ky9.default.get(url, {
2931
3087
  headers: this.authHeaders(),
2932
3088
  searchParams: { "router:external": "true" },
2933
3089
  retry: 0,
2934
- timeout: DEFAULT_TIMEOUT_MS4
3090
+ timeout: DEFAULT_TIMEOUT_MS5
2935
3091
  }).json();
2936
3092
  if (typeof raw !== "object" || raw === null) return null;
2937
3093
  const vpcs = raw["vpcs"];
@@ -3779,9 +3935,280 @@ var volumesCommand = new import_commander33.Command("volumes").description("\uC7
3779
3935
  });
3780
3936
  });
3781
3937
 
3938
+ // src/commands/ncr/list.ts
3939
+ var import_commander34 = require("commander");
3940
+
3941
+ // src/services/ncr/harbor-client.ts
3942
+ var import_ky10 = __toESM(require("ky"));
3943
+ var DEFAULT_TIMEOUT_MS6 = 3e4;
3944
+ var PAGE_SIZE = 100;
3945
+ var MAX_PAGES = 1e3;
3946
+ var HarborClient = class {
3947
+ uakId;
3948
+ uakSecret;
3949
+ host;
3950
+ constructor(uakId, uakSecret, host) {
3951
+ this.uakId = uakId;
3952
+ this.uakSecret = uakSecret;
3953
+ this.host = host;
3954
+ }
3955
+ basicAuthHeaders() {
3956
+ const token = Buffer.from(`${this.uakId}:${this.uakSecret}`).toString("base64");
3957
+ return { Authorization: `Basic ${token}` };
3958
+ }
3959
+ /**
3960
+ * Harbor REST 페이지네이션 전수 수집 (ADR-017 — silent truncation 방지).
3961
+ *
3962
+ * Harbor 응답 Link: <...?page=N+1...>; rel="next" 헤더가 없으면 종료.
3963
+ * ky.get() 이 Response 를 반환하므로 .json() 과 .headers.get("link") 를 함께 사용한다
3964
+ * (체이닝하면 헤더를 못 본다 — 기존 NCR client 의 .json<T>() 체이닝 패턴과 다름).
3965
+ */
3966
+ async getAllPages(path) {
3967
+ const acc = [];
3968
+ let page = 1;
3969
+ try {
3970
+ for (; ; ) {
3971
+ const url = `https://${this.host}${path}?page=${page}&page_size=${PAGE_SIZE}`;
3972
+ const res = await import_ky10.default.get(url, {
3973
+ headers: this.basicAuthHeaders(),
3974
+ retry: 0,
3975
+ timeout: DEFAULT_TIMEOUT_MS6
3976
+ });
3977
+ const data = await res.json();
3978
+ if (!Array.isArray(data)) {
3979
+ throw new NhnCloudCliError(
3980
+ "Harbor REST \uC751\uB2F5 \uD615\uC2DD \uC624\uB958: \uBC30\uC5F4\uC774 \uC544\uB2D9\uB2C8\uB2E4.",
3981
+ EXIT_API_ERROR
3982
+ );
3983
+ }
3984
+ acc.push(...data);
3985
+ const link = res.headers.get("link");
3986
+ if (!link || !link.includes('rel="next"')) break;
3987
+ page++;
3988
+ if (page > MAX_PAGES) {
3989
+ throw new NhnCloudCliError(
3990
+ `Harbor pagination \uCD5C\uB300 \uD398\uC774\uC9C0(${MAX_PAGES}) \uCD08\uACFC \u2014 \uBE44\uC815\uC0C1 \uC751\uB2F5\uC73C\uB85C \uC911\uB2E8\uD569\uB2C8\uB2E4.`,
3991
+ EXIT_API_ERROR
3992
+ );
3993
+ }
3994
+ }
3995
+ } catch (err) {
3996
+ if (err instanceof NhnCloudCliError) throw err;
3997
+ throw toNhnCloudCliError(err);
3998
+ }
3999
+ return acc;
4000
+ }
4001
+ /**
4002
+ * 프로젝트(레지스트리)의 repository(이미지) 목록을 반환한다.
4003
+ * GET /api/v2.0/projects/{project}/repositories
4004
+ */
4005
+ async listRepositories(project) {
4006
+ const enc = encodeURIComponent(project);
4007
+ const data = await this.getAllPages(`/api/v2.0/projects/${enc}/repositories`);
4008
+ return data.filter(isRepository);
4009
+ }
4010
+ /**
4011
+ * repository 의 artifact 목록을 반환한다.
4012
+ * GET /api/v2.0/projects/{project}/repositories/{repository}/artifacts
4013
+ * repository 의 '/' 는 %2F 로 인코딩(path-traversal 방지).
4014
+ */
4015
+ async listArtifacts(project, repository) {
4016
+ const encProject = encodeURIComponent(project);
4017
+ const encRepo = encodeURIComponent(repository);
4018
+ const data = await this.getAllPages(
4019
+ `/api/v2.0/projects/${encProject}/repositories/${encRepo}/artifacts`
4020
+ );
4021
+ return data.filter(isArtifact);
4022
+ }
4023
+ };
4024
+
4025
+ // src/commands/ncr/helpers.ts
4026
+ async function createNcrClient(opts) {
4027
+ const profileName = await resolveProfileName(opts.profile);
4028
+ const uak = await getUserAccessKey(profileName);
4029
+ const region = opts.region ?? "kr1";
4030
+ return { client: new NcrClient(uak.id, uak.secret, region), profileName };
4031
+ }
4032
+ async function resolveAppKey(profileName, appKeyOpt) {
4033
+ if (appKeyOpt) return appKeyOpt;
4034
+ let cred;
4035
+ try {
4036
+ cred = await getServiceCredential("ncr", profileName);
4037
+ } catch (err) {
4038
+ if (!(err instanceof NhnCloudCliError) || err.exitCode !== EXIT_CONFIG_ERROR) {
4039
+ throw err;
4040
+ }
4041
+ }
4042
+ if (!cred?.appkey) {
4043
+ throw new NhnCloudCliError(
4044
+ "NCR appKey \uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. --app-key \uC635\uC158\uC73C\uB85C \uC9C0\uC815\uD558\uAC70\uB098\nnhncloud configure --ncr-appkey <key> \uB97C \uC2E4\uD589\uD574 \uC124\uC815\uD558\uC138\uC694.",
4045
+ EXIT_CONFIG_ERROR
4046
+ );
4047
+ }
4048
+ return cred.appkey;
4049
+ }
4050
+ async function createHarborClient(opts, registryArg) {
4051
+ const { client: ncrClient, profileName } = await createNcrClient(opts);
4052
+ const appKey = await resolveAppKey(profileName, opts.appKey);
4053
+ const uak = await getUserAccessKey(profileName);
4054
+ const reg = await ncrClient.getRegistry(appKey, registryArg);
4055
+ const host = parseHarborHost(reg.uri);
4056
+ const project = typeof reg.name === "string" ? reg.name : registryArg;
4057
+ return { harbor: new HarborClient(uak.id, uak.secret, host), project };
4058
+ }
4059
+ function parseHarborHost(uri) {
4060
+ if (!uri) {
4061
+ throw new NhnCloudCliError(
4062
+ "\uB808\uC9C0\uC2A4\uD2B8\uB9AC uri \uAC00 \uC5C6\uC5B4 \uC774\uBBF8\uC9C0 host \uB97C \uD574\uC11D\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
4063
+ EXIT_API_ERROR
4064
+ );
4065
+ }
4066
+ const noScheme = uri.replace(/^https?:\/\//, "");
4067
+ const host = noScheme.split("/")[0];
4068
+ if (!host) {
4069
+ throw new NhnCloudCliError(
4070
+ "\uB808\uC9C0\uC2A4\uD2B8\uB9AC uri \uD615\uC2DD \uC624\uB958 \u2014 host \uCD94\uCD9C \uC2E4\uD328.",
4071
+ EXIT_API_ERROR
4072
+ );
4073
+ }
4074
+ return host;
4075
+ }
4076
+
4077
+ // src/commands/ncr/list.ts
4078
+ var listCommand5 = new import_commander34.Command("list").description("NCR \uB808\uC9C0\uC2A4\uD2B8\uB9AC \uBAA9\uB85D\uC744 \uC870\uD68C\uD55C\uB2E4").option("--region <region>", "NCR region (\uAE30\uBCF8: kr1)", "kr1").option("--app-key <key>", "NCR appKey (profile \uC758 ncr.appkey \uBCF4\uB2E4 \uC6B0\uC120)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (_opts, cmd) => {
4079
+ const opts = cmd.optsWithGlobals();
4080
+ const { client, profileName } = await createNcrClient(opts);
4081
+ const appKey = await resolveAppKey(profileName, opts.appKey);
4082
+ startSpinner("\uB808\uC9C0\uC2A4\uD2B8\uB9AC \uBAA9\uB85D \uC870\uD68C \uC911...");
4083
+ let registries;
4084
+ try {
4085
+ registries = await client.listRegistries(appKey);
4086
+ } catch (err) {
4087
+ stopSpinner(false);
4088
+ throw err;
4089
+ }
4090
+ stopSpinner(true);
4091
+ output(opts, {
4092
+ headers: ["name", "repo_count", "uri"],
4093
+ rows: registries.map((r) => [
4094
+ r.name,
4095
+ String(r.repo_count ?? ""),
4096
+ r.uri ?? ""
4097
+ ]),
4098
+ raw: registries,
4099
+ ids: registries.map((r) => r.name)
4100
+ });
4101
+ });
4102
+
4103
+ // src/commands/ncr/get.ts
4104
+ var import_commander35 = require("commander");
4105
+ var getCommand3 = new import_commander35.Command("get").description("\uB2E8\uC77C NCR \uB808\uC9C0\uC2A4\uD2B8\uB9AC\uB97C \uC870\uD68C\uD55C\uB2E4").argument("<registry>", "\uB808\uC9C0\uC2A4\uD2B8\uB9AC \uC774\uB984 \uB610\uB294 ID").option("--region <region>", "NCR region (\uAE30\uBCF8: kr1)", "kr1").option("--app-key <key>", "NCR appKey (profile \uC758 ncr.appkey \uBCF4\uB2E4 \uC6B0\uC120)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (registry, _opts, cmd) => {
4106
+ const opts = cmd.optsWithGlobals();
4107
+ if (!registry.trim()) {
4108
+ throw new NhnCloudCliError(
4109
+ "registry \uC778\uC218\uAC00 \uBE44\uC5B4\uC788\uC2B5\uB2C8\uB2E4. \uB808\uC9C0\uC2A4\uD2B8\uB9AC \uC774\uB984 \uB610\uB294 ID \uB97C \uC9C0\uC815\uD558\uC138\uC694.",
4110
+ EXIT_PARAM_ERROR
4111
+ );
4112
+ }
4113
+ const { client, profileName } = await createNcrClient(opts);
4114
+ const appKey = await resolveAppKey(profileName, opts.appKey);
4115
+ startSpinner(`\uB808\uC9C0\uC2A4\uD2B8\uB9AC "${registry}" \uC870\uD68C \uC911...`);
4116
+ let reg;
4117
+ try {
4118
+ reg = await client.getRegistry(appKey, registry);
4119
+ } catch (err) {
4120
+ stopSpinner(false);
4121
+ throw err;
4122
+ }
4123
+ stopSpinner(true);
4124
+ output(opts, {
4125
+ headers: ["name", "repo_count", "uri", "private_uri"],
4126
+ rows: [[reg.name, String(reg.repo_count ?? ""), reg.uri ?? "", reg.private_uri ?? ""]],
4127
+ raw: reg,
4128
+ ids: [reg.name]
4129
+ });
4130
+ });
4131
+
4132
+ // src/commands/ncr/images.ts
4133
+ var import_commander36 = require("commander");
4134
+ var imagesCommand2 = new import_commander36.Command("images").description("\uB808\uC9C0\uC2A4\uD2B8\uB9AC\uC758 \uC774\uBBF8\uC9C0(repository) \uBAA9\uB85D\uC744 \uC870\uD68C\uD55C\uB2E4").argument("<registry>", "\uB808\uC9C0\uC2A4\uD2B8\uB9AC \uC774\uB984").option("--region <region>", "NCR region (\uAE30\uBCF8: kr1)", "kr1").option("--app-key <key>", "NCR appKey (profile \uC758 ncr.appkey \uBCF4\uB2E4 \uC6B0\uC120)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (registry, _opts, cmd) => {
4135
+ const opts = cmd.optsWithGlobals();
4136
+ if (!registry.trim()) {
4137
+ throw new NhnCloudCliError(
4138
+ "registry \uC778\uC218\uAC00 \uBE44\uC5B4\uC788\uC2B5\uB2C8\uB2E4. \uB808\uC9C0\uC2A4\uD2B8\uB9AC \uC774\uB984\uC744 \uC9C0\uC815\uD558\uC138\uC694.",
4139
+ EXIT_PARAM_ERROR
4140
+ );
4141
+ }
4142
+ const { harbor, project } = await createHarborClient(opts, registry);
4143
+ startSpinner(`"${registry}" \uC774\uBBF8\uC9C0 \uBAA9\uB85D \uC870\uD68C \uC911...`);
4144
+ let repos;
4145
+ try {
4146
+ repos = await harbor.listRepositories(project);
4147
+ } catch (err) {
4148
+ stopSpinner(false);
4149
+ throw err;
4150
+ }
4151
+ stopSpinner(true);
4152
+ const rows = repos.map((r) => {
4153
+ const short = r.name.startsWith(project + "/") ? r.name.slice(project.length + 1) : r.name;
4154
+ return [short, String(r.artifact_count ?? ""), String(r.pull_count ?? "")];
4155
+ });
4156
+ const ids = repos.map(
4157
+ (r) => r.name.startsWith(project + "/") ? r.name.slice(project.length + 1) : r.name
4158
+ );
4159
+ output(opts, {
4160
+ headers: ["repository", "artifact_count", "pull_count"],
4161
+ rows,
4162
+ raw: repos,
4163
+ ids
4164
+ });
4165
+ });
4166
+
4167
+ // src/commands/ncr/tags.ts
4168
+ var import_commander37 = require("commander");
4169
+ var tagsCommand = new import_commander37.Command("tags").description("\uD2B9\uC815 \uC774\uBBF8\uC9C0\uC758 \uD0DC\uADF8 \uBAA9\uB85D\uC744 \uC870\uD68C\uD55C\uB2E4").argument("<registry>", "\uB808\uC9C0\uC2A4\uD2B8\uB9AC \uC774\uB984").argument("<repository>", "\uC774\uBBF8\uC9C0(repository) \uC774\uB984 (\uC9E7\uC740 \uC774\uB984 \uB610\uB294 {project}/{repo})").option("--region <region>", "NCR region (\uAE30\uBCF8: kr1)", "kr1").option("--app-key <key>", "NCR appKey (profile \uC758 ncr.appkey \uBCF4\uB2E4 \uC6B0\uC120)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (registry, repository, _opts, cmd) => {
4170
+ const opts = cmd.optsWithGlobals();
4171
+ if (!registry.trim()) {
4172
+ throw new NhnCloudCliError(
4173
+ "registry \uC778\uC218\uAC00 \uBE44\uC5B4\uC788\uC2B5\uB2C8\uB2E4. \uB808\uC9C0\uC2A4\uD2B8\uB9AC \uC774\uB984\uC744 \uC9C0\uC815\uD558\uC138\uC694.",
4174
+ EXIT_PARAM_ERROR
4175
+ );
4176
+ }
4177
+ if (!repository.trim()) {
4178
+ throw new NhnCloudCliError(
4179
+ "repository \uC778\uC218\uAC00 \uBE44\uC5B4\uC788\uC2B5\uB2C8\uB2E4. \uC774\uBBF8\uC9C0 \uC774\uB984\uC744 \uC9C0\uC815\uD558\uC138\uC694.",
4180
+ EXIT_PARAM_ERROR
4181
+ );
4182
+ }
4183
+ const { harbor, project } = await createHarborClient(opts, registry);
4184
+ const repo = repository.startsWith(project + "/") ? repository.slice(project.length + 1) : repository;
4185
+ startSpinner(`"${registry}/${repo}" \uD0DC\uADF8 \uBAA9\uB85D \uC870\uD68C \uC911...`);
4186
+ let tagRows;
4187
+ try {
4188
+ const artifacts = await harbor.listArtifacts(project, repo);
4189
+ tagRows = artifacts.flatMap(
4190
+ (a) => (a.tags ?? []).map((t) => ({
4191
+ tag: t.name,
4192
+ push_time: t.push_time ?? a.push_time,
4193
+ size: String(a.size ?? "")
4194
+ }))
4195
+ );
4196
+ } catch (err) {
4197
+ stopSpinner(false);
4198
+ throw err;
4199
+ }
4200
+ stopSpinner(true);
4201
+ output(opts, {
4202
+ headers: ["tag", "push_time", "size"],
4203
+ rows: tagRows.map((r) => [r.tag, r.push_time ?? "", r.size]),
4204
+ raw: tagRows,
4205
+ ids: tagRows.map((r) => r.tag)
4206
+ });
4207
+ });
4208
+
3782
4209
  // src/index.ts
3783
- var program = new import_commander34.Command();
3784
- program.name("nhncloud").description("NHN Cloud CLI \u2014 AI agent & terminal friendly").version("0.4.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");
4210
+ var program = new import_commander38.Command();
4211
+ program.name("nhncloud").description("NHN Cloud CLI \u2014 AI agent & terminal friendly").version("0.5.1").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");
3785
4212
  program.hook("preAction", () => {
3786
4213
  const opts = program.opts();
3787
4214
  if (!opts.color || process.env["NO_COLOR"]) {
@@ -3792,12 +4219,12 @@ program.hook("preAction", () => {
3792
4219
  }
3793
4220
  });
3794
4221
  program.addCommand(configureCommand);
3795
- var logncrashCommand = new import_commander34.Command("logncrash").description("Log & Crash \uAD00\uB828 \uBA85\uB839");
4222
+ var logncrashCommand = new import_commander38.Command("logncrash").description("Log & Crash \uAD00\uB828 \uBA85\uB839");
3796
4223
  logncrashCommand.addCommand(searchCommand);
3797
4224
  logncrashCommand.addCommand(sendCommand);
3798
4225
  logncrashCommand.addCommand(exportCommand);
3799
4226
  program.addCommand(logncrashCommand);
3800
- var deployCommand = new import_commander34.Command("deploy").description("NHN Cloud Deploy \uAD00\uB828 \uBA85\uB839");
4227
+ var deployCommand = new import_commander38.Command("deploy").description("NHN Cloud Deploy \uAD00\uB828 \uBA85\uB839");
3801
4228
  deployCommand.addCommand(runCommand);
3802
4229
  deployCommand.addCommand(artifactsCommand);
3803
4230
  deployCommand.addCommand(serverGroupsCommand);
@@ -3807,7 +4234,7 @@ deployCommand.addCommand(binariesCommand);
3807
4234
  deployCommand.addCommand(uploadCommand);
3808
4235
  deployCommand.addCommand(downloadCommand);
3809
4236
  program.addCommand(deployCommand);
3810
- var instanceCommand = new import_commander34.Command("instance").description("Compute \uC778\uC2A4\uD134\uC2A4 \uAD00\uB828 \uBA85\uB839");
4237
+ var instanceCommand = new import_commander38.Command("instance").description("Compute \uC778\uC2A4\uD134\uC2A4 \uAD00\uB828 \uBA85\uB839");
3811
4238
  instanceCommand.addCommand(listCommand);
3812
4239
  instanceCommand.addCommand(flavorsCommand);
3813
4240
  instanceCommand.addCommand(availabilityZonesCommand);
@@ -3826,22 +4253,28 @@ instanceCommand.addCommand(keypairCommand);
3826
4253
  instanceCommand.addCommand(volumeCommand);
3827
4254
  instanceCommand.addCommand(volumesCommand);
3828
4255
  program.addCommand(instanceCommand);
3829
- var networkCommand = new import_commander34.Command("network").description("VPC\xB7\uC11C\uBE0C\uB137 \uC870\uD68C");
4256
+ var networkCommand = new import_commander38.Command("network").description("VPC\xB7\uC11C\uBE0C\uB137 \uC870\uD68C");
3830
4257
  networkCommand.addCommand(listCommand3);
3831
4258
  networkCommand.addCommand(subnetCommand);
3832
4259
  program.addCommand(networkCommand);
3833
- var volumeCommand2 = new import_commander34.Command("volume").description("Block Storage \uBCFC\uB968 \uAD00\uB828 \uBA85\uB839");
4260
+ var volumeCommand2 = new import_commander38.Command("volume").description("Block Storage \uBCFC\uB968 \uAD00\uB828 \uBA85\uB839");
3834
4261
  volumeCommand2.addCommand(listCommand2);
3835
4262
  volumeCommand2.addCommand(getCommand);
3836
4263
  volumeCommand2.addCommand(createCommand);
3837
4264
  program.addCommand(volumeCommand2);
3838
- var floatingipCommand = new import_commander34.Command("floatingip").description(
4265
+ var floatingipCommand = new import_commander38.Command("floatingip").description(
3839
4266
  "Floating IP(\uC778\uC2A4\uD134\uC2A4 \uACF5\uC778 IP) \uAD00\uB9AC"
3840
4267
  );
3841
4268
  floatingipCommand.addCommand(listCommand4);
3842
4269
  floatingipCommand.addCommand(createCommand2);
3843
4270
  floatingipCommand.addCommand(deleteCommand);
3844
4271
  program.addCommand(floatingipCommand);
4272
+ var ncrCommand = new import_commander38.Command("ncr").description("NHN Container Registry \uAD00\uB828 \uBA85\uB839");
4273
+ ncrCommand.addCommand(listCommand5);
4274
+ ncrCommand.addCommand(getCommand3);
4275
+ ncrCommand.addCommand(imagesCommand2);
4276
+ ncrCommand.addCommand(tagsCommand);
4277
+ program.addCommand(ncrCommand);
3845
4278
  program.parseAsync().catch((err) => {
3846
4279
  const message = err instanceof Error ? err.message : String(err);
3847
4280
  const exitCode = err instanceof NhnCloudCliError ? err.exitCode : 1;