connectbase-client 3.44.0 → 3.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +121 -13
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -429,7 +429,10 @@ async function offloadLargeBinaries(baseStorageUrl, headers, diff, _envLabel, ti
429
429
  const presignFiles = largeBinaries.map((f) => ({
430
430
  path: f.path,
431
431
  size: Buffer.from(f.content, "base64").length,
432
- mime_type: guessMimeType(f.path)
432
+ mime_type: guessMimeType(f.path),
433
+ // 신형 서버: hash 제공 시 content-addressed blob 키({id}/blobs/{hash})로 presign 되고,
434
+ // 동일 blob 이 이미 있으면 already_exists 로 업로드를 생략할 수 있다. 구버전 서버는 무시.
435
+ hash: f.hash
433
436
  }));
434
437
  let presigned;
435
438
  try {
@@ -445,22 +448,30 @@ async function offloadLargeBinaries(baseStorageUrl, headers, diff, _envLabel, ti
445
448
  warn(`\uB300\uC6A9\uB7C9 \uC5D0\uC14B\uC740 \uD30C\uC77C \uC2A4\uD1A0\uB9AC\uC9C0(${colors.cyan}cb.storage${colors.reset})\uC5D0 \uC62C\uB9AC\uACE0 URL \uB85C \uCC38\uC870\uD558\uAC70\uB098, \uC11C\uBC84 \uC5C5\uB370\uC774\uD2B8 \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.`);
446
449
  process.exit(1);
447
450
  }
448
- const urlByPath = new Map(presigned.map((p) => [p.path, p.upload_url]));
451
+ const entryByPath = new Map(presigned.map((p) => [p.path, p]));
449
452
  info(`\uB300\uC6A9\uB7C9 \uBC14\uC774\uB108\uB9AC ${largeBinaries.length}\uAC1C\uB97C Object Storage \uB85C \uC9C1\uC811 \uC5C5\uB85C\uB4DC\uD569\uB2C8\uB2E4...`);
450
453
  for (const f of largeBinaries) {
451
- const uploadUrl = urlByPath.get(f.path);
452
- if (!uploadUrl) {
453
- error(`presign \uC751\uB2F5\uC5D0 \uC5C5\uB85C\uB4DC URL \uC774 \uC5C6\uC2B5\uB2C8\uB2E4: ${f.path}`);
454
+ const entry = entryByPath.get(f.path);
455
+ if (!entry) {
456
+ error(`presign \uC751\uB2F5\uC5D0 \uD56D\uBAA9\uC774 \uC5C6\uC2B5\uB2C8\uB2E4: ${f.path}`);
454
457
  process.exit(1);
455
458
  }
456
459
  const bytes = Buffer.from(f.content, "base64");
457
- try {
458
- await putToPresignedUrl(uploadUrl, bytes, guessMimeType(f.path), timeoutMs);
459
- } catch (err) {
460
- error(`\uC5C5\uB85C\uB4DC \uC2E4\uD328 (${f.path}): ${err instanceof Error ? err.message : err}`);
461
- process.exit(1);
460
+ if (entry.already_exists) {
461
+ success(`\uC5C5\uB85C\uB4DC \uC0DD\uB7B5(\uAE30\uC874 blob \uC7AC\uC0AC\uC6A9): ${f.path} (${formatSize(bytes.length)})`);
462
+ } else {
463
+ if (!entry.upload_url) {
464
+ error(`presign \uC751\uB2F5\uC5D0 \uC5C5\uB85C\uB4DC URL \uC774 \uC5C6\uC2B5\uB2C8\uB2E4: ${f.path}`);
465
+ process.exit(1);
466
+ }
467
+ try {
468
+ await putToPresignedUrl(entry.upload_url, bytes, guessMimeType(f.path), timeoutMs);
469
+ } catch (err) {
470
+ error(`\uC5C5\uB85C\uB4DC \uC2E4\uD328 (${f.path}): ${err instanceof Error ? err.message : err}`);
471
+ process.exit(1);
472
+ }
473
+ success(`\uC9C1\uC811 \uC5C5\uB85C\uB4DC: ${f.path} (${formatSize(bytes.length)})`);
462
474
  }
463
- success(`\uC9C1\uC811 \uC5C5\uB85C\uB4DC: ${f.path} (${formatSize(bytes.length)})`);
464
475
  f.pre_uploaded = true;
465
476
  f.size = bytes.length;
466
477
  f.content = "";
@@ -743,8 +754,25 @@ async function fullDeploy(baseStorageUrl, headers, files, envLabel, endpoint, ti
743
754
  }
744
755
  function handleDeployResponse(response, envLabel) {
745
756
  if (response.status >= 200 && response.status < 300) {
746
- success(`${envLabel} \uBC30\uD3EC \uC644\uB8CC!`);
747
757
  const data = response.data;
758
+ const dep = data?.deployment ?? null;
759
+ if (dep?.deployment_id) {
760
+ if (dep.promoted) {
761
+ success(`${envLabel} \uBC30\uD3EC \uC644\uB8CC! (release #${dep.seq})`);
762
+ if (dep.production_url) log(`
763
+ ${colors.cyan}Production: ${dep.production_url}${colors.reset}`);
764
+ if (dep.qa_url) log(`${colors.dim}QA: ${dep.qa_url}${colors.reset}
765
+ `);
766
+ } else {
767
+ success(`QA \uD658\uACBD\uC5D0 \uBC30\uD3EC\uB418\uC5C8\uC2B5\uB2C8\uB2E4 (release #${dep.seq}) \u2014 production \uC740 \uC544\uC9C1 \uC774\uC804 \uBC84\uC804\uC785\uB2C8\uB2E4`);
768
+ if (dep.qa_url) log(`
769
+ ${colors.yellow}QA URL: ${dep.qa_url}${colors.reset}`);
770
+ log(`${colors.dim}\uAC80\uC99D \uD6C4 \uBC18\uC601: ${colors.cyan}npx connectbase promote${colors.reset}
771
+ `);
772
+ }
773
+ return;
774
+ }
775
+ success(`${envLabel} \uBC30\uD3EC \uC644\uB8CC!`);
748
776
  if (data?.dev_url) {
749
777
  log(`
750
778
  ${colors.yellow}Dev URL: ${data.dev_url}${colors.reset}
@@ -761,6 +789,64 @@ ${colors.cyan}URL: ${data.url}${colors.reset}
761
789
  process.exit(1);
762
790
  }
763
791
  }
792
+ async function promote(config, deploymentId) {
793
+ const baseStorageUrl = `${config.baseUrl}/v1/public/storages/webs/${config.storageId}`;
794
+ const headers = { "X-Public-Key": config.publicKey };
795
+ info(deploymentId ? `release ${deploymentId} \uB97C production \uC73C\uB85C \uC2B9\uACA9\uD569\uB2C8\uB2E4...` : "\uCD5C\uC2E0 \uBC30\uD3EC\uB97C production \uC73C\uB85C \uC2B9\uACA9\uD569\uB2C8\uB2E4...");
796
+ const body = JSON.stringify(deploymentId ? { deployment_id: deploymentId } : {});
797
+ const res = await makeRequest(`${baseStorageUrl}/promote`, "POST", headers, body);
798
+ if (res.status === 404 && !res.data?.error) {
799
+ error("\uC11C\uBC84\uAC00 promote \uB97C \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 (\uAD6C\uBC84\uC804 \uC11C\uBC84) \u2014 \uC11C\uBC84 \uC5C5\uB370\uC774\uD2B8 \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694");
800
+ process.exit(1);
801
+ }
802
+ if (res.status < 200 || res.status >= 300) {
803
+ const data2 = res.data;
804
+ const msg = typeof data2 === "object" && data2 !== null ? data2.error || data2.message || JSON.stringify(data2) : `HTTP ${res.status}`;
805
+ error(`promote \uC2E4\uD328: ${msg}`);
806
+ process.exit(1);
807
+ }
808
+ const data = res.data;
809
+ success(`production \uC2B9\uACA9 \uC644\uB8CC! (release #${data.seq})`);
810
+ if (data.production_url) {
811
+ log(`
812
+ ${colors.cyan}Production: ${data.production_url}${colors.reset}
813
+ `);
814
+ }
815
+ }
816
+ async function listReleases(config) {
817
+ const baseStorageUrl = `${config.baseUrl}/v1/public/storages/webs/${config.storageId}`;
818
+ const headers = { "X-Public-Key": config.publicKey };
819
+ const res = await makeRequest(`${baseStorageUrl}/deployments`, "GET", headers);
820
+ if (res.status < 200 || res.status >= 300) {
821
+ const data2 = res.data;
822
+ const msg = typeof data2 === "object" && data2 !== null ? data2.error || data2.message || JSON.stringify(data2) : `HTTP ${res.status}`;
823
+ error(`\uBC30\uD3EC \uC774\uB825 \uC870\uD68C \uC2E4\uD328: ${msg}`);
824
+ process.exit(1);
825
+ }
826
+ const data = res.data;
827
+ if (!data.deployments || data.deployments.length === 0) {
828
+ info("\uBC30\uD3EC \uC774\uB825\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 deploy \uD558\uC138\uC694.");
829
+ return;
830
+ }
831
+ log(`
832
+ ${colors.cyan}\uBC30\uD3EC \uC774\uB825${colors.reset} (promote_mode: ${data.promote_mode || "auto"}, \uCD1D ${data.total}\uAC1C)
833
+ `);
834
+ for (const d of data.deployments) {
835
+ const markers = [];
836
+ if (d.is_production) markers.push(`${colors.green}production${colors.reset}`);
837
+ if (d.is_qa) markers.push(`${colors.yellow}qa${colors.reset}`);
838
+ const marker = markers.length > 0 ? ` \u2190 ${markers.join(", ")}` : "";
839
+ const statusColor = d.status === "ready" ? colors.green : d.status === "failed" ? colors.red : colors.yellow;
840
+ log(
841
+ ` #${String(d.seq).padEnd(4)} ${statusColor}${d.status.padEnd(9)}${colors.reset} ${String(d.file_count).padStart(4)}\uAC1C ${formatSize(d.total_size).padStart(9)} ${d.created_at} ${colors.dim}${d.id}${colors.reset}${marker}`
842
+ );
843
+ }
844
+ log("");
845
+ if (data.production_url) log(`${colors.cyan}Production: ${data.production_url}${colors.reset}`);
846
+ if (data.qa_url) log(`${colors.yellow}QA: ${data.qa_url}${colors.reset}`);
847
+ log(`${colors.dim}\uB864\uBC31: npx connectbase promote <deployment_id>${colors.reset}
848
+ `);
849
+ }
764
850
  function prompt(question) {
765
851
  const rl = readline.createInterface({
766
852
  input: process.stdin,
@@ -2786,6 +2872,9 @@ ${colors.yellow}\uBA85\uB839\uC5B4:${colors.reset}
2786
2872
  docs SDK \uBB38\uC11C \uB2E4\uC6B4\uB85C\uB4DC/\uC5C5\uB370\uC774\uD2B8 (\uBAA8\uB178\uB808\uD3EC \uC790\uB3D9 \uAC10\uC9C0)
2787
2873
  mcp MCP \uC11C\uBC84 \uC124\uC815 (.mcp.json \uC0DD\uC131/\uC5C5\uB370\uC774\uD2B8, \uBAA8\uB178\uB808\uD3EC \uC790\uB3D9 \uAC10\uC9C0)
2788
2874
  deploy <directory> \uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0\uC5D0 \uD30C\uC77C \uBC30\uD3EC (--dev: Dev \uD658\uACBD)
2875
+ promote_mode=manual \uC774\uBA74 QA \uD658\uACBD\uC5D0\uB9CC \uBC30\uD3EC\uB429\uB2C8\uB2E4
2876
+ promote [releaseId] QA \uAC80\uC99D \uD6C4 production \uBC18\uC601 (\uBBF8\uC9C0\uC815: \uCD5C\uC2E0 \uBC30\uD3EC, \uACFC\uAC70 id: \uB864\uBC31)
2877
+ releases \uBC30\uD3EC \uC774\uB825 \uC870\uD68C (release \uBC88\uD638/\uC0C1\uD0DC/production\xB7QA \uD3EC\uC778\uD130)
2789
2878
  tunnel <port> \uB85C\uCEEC \uC11C\uBE44\uC2A4\uB97C \uC778\uD130\uB137\uC5D0 \uB178\uCD9C
2790
2879
 
2791
2880
  ${colors.yellow}\uCF54\uB4DC \uBD84\uC11D (MCP \uD1B5\uD574 \uC0AC\uC6A9):${colors.reset}
@@ -2842,7 +2931,12 @@ ${colors.yellow}\uBE60\uB978 \uC2DC\uC791:${colors.reset}
2842
2931
  ${colors.dim}# 4. Prod \uBC30\uD3EC${colors.reset}
2843
2932
  npm run deploy
2844
2933
 
2845
- ${colors.dim}# 4. Dev \uD658\uACBD \uBC30\uD3EC (\uB0B4\uBD80 QA\uC6A9)${colors.reset}
2934
+ ${colors.dim}# 4-1. QA \uC6CC\uD06C\uD50C\uB85C\uC6B0 (\uCF58\uC194\uC5D0\uC11C promote_mode=manual \uC124\uC815 \uC2DC)${colors.reset}
2935
+ npx connectbase deploy ./dist ${colors.dim}# QA \uD658\uACBD\uC5D0\uB9CC \uBC30\uD3EC${colors.reset}
2936
+ npx connectbase releases ${colors.dim}# \uBC30\uD3EC \uC774\uB825 \uD655\uC778${colors.reset}
2937
+ npx connectbase promote ${colors.dim}# QA \uAC80\uC99D \uD6C4 production \uBC18\uC601${colors.reset}
2938
+
2939
+ ${colors.dim}# 4-2. Dev \uD658\uACBD \uBC30\uD3EC (\uB0B4\uBD80 \uD655\uC778\uC6A9)${colors.reset}
2846
2940
  npx connectbase deploy ./dist --dev
2847
2941
 
2848
2942
  ${colors.dim}# 5. \uD130\uB110 (\uAE30\uBCF8)${colors.reset}
@@ -2996,6 +3090,20 @@ async function main() {
2996
3090
  } catch {
2997
3091
  }
2998
3092
  }
3093
+ } else if (parsed.command === "promote" || parsed.command === "releases") {
3094
+ if (!config.publicKey) {
3095
+ error('Public Key\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. "npx connectbase init"\uC73C\uB85C \uC124\uC815\uD558\uAC70\uB098 -k \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694');
3096
+ process.exit(1);
3097
+ }
3098
+ if (!config.storageId) {
3099
+ error('\uC2A4\uD1A0\uB9AC\uC9C0 ID\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. "npx connectbase init"\uC73C\uB85C \uC124\uC815\uD558\uAC70\uB098 -s \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694');
3100
+ process.exit(1);
3101
+ }
3102
+ if (parsed.command === "promote") {
3103
+ await promote(config, parsed.args[0]);
3104
+ } else {
3105
+ await listReleases(config);
3106
+ }
2999
3107
  } else if (parsed.command === "tunnel") {
3000
3108
  const portStr = parsed.args[0];
3001
3109
  if (!portStr) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "connectbase-client",
3
- "version": "3.44.0",
3
+ "version": "3.45.0",
4
4
  "description": "Connect Base JavaScript/TypeScript SDK for browser and Node.js",
5
5
  "repository": {
6
6
  "type": "git",