33 * SPDX-License-Identifier: Apache-2
44 * For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
55 */
6- import { Args } from '@oclif/core' ;
6+ import * as fs from 'node:fs' ;
7+ import * as path from 'node:path' ;
8+ import { pipeline } from 'node:stream/promises' ;
9+ import { Args , Flags } from '@oclif/core' ;
710import { MrtCommand } from '@salesforce/b2c-tooling-sdk/cli' ;
811import { downloadBundle , type DownloadBundleResult } from '@salesforce/b2c-tooling-sdk/operations/mrt' ;
912import { t } from '../../../i18n/index.js' ;
1013
14+ type BundleDownloadResult = DownloadBundleResult & { filePath ?: string } ;
15+
1116/**
12- * Get a presigned download URL for a bundle .
17+ * Download a bundle artifact from Managed Runtime .
1318 */
1419export default class MrtBundleDownload extends MrtCommand < typeof MrtBundleDownload > {
1520 static args = {
@@ -19,23 +24,30 @@ export default class MrtBundleDownload extends MrtCommand<typeof MrtBundleDownlo
1924 } ) ,
2025 } ;
2126
22- static description = t (
23- 'commands.mrt.bundle.download.description' ,
24- 'Get a presigned download URL for a Managed Runtime bundle' ,
25- ) ;
27+ static description = t ( 'commands.mrt.bundle.download.description' , 'Download a Managed Runtime bundle artifact' ) ;
2628
2729 static enableJsonFlag = true ;
2830
2931 static examples = [
3032 '<%= config.bin %> <%= command.id %> 12345 --project my-storefront' ,
33+ '<%= config.bin %> <%= command.id %> 12345 -p my-storefront -o ./artifacts/my-bundle.tgz' ,
34+ '<%= config.bin %> <%= command.id %> 12345 -p my-storefront --url-only' ,
3135 '<%= config.bin %> <%= command.id %> 12345 -p my-storefront --json' ,
3236 ] ;
3337
3438 static flags = {
3539 ...MrtCommand . baseFlags ,
40+ output : Flags . string ( {
41+ char : 'o' ,
42+ description : 'Output file path (default: bundle-{bundleId}.tgz)' ,
43+ } ) ,
44+ 'url-only' : Flags . boolean ( {
45+ description : 'Only output the download URL without downloading' ,
46+ default : false ,
47+ } ) ,
3648 } ;
3749
38- async run ( ) : Promise < DownloadBundleResult > {
50+ async run ( ) : Promise < BundleDownloadResult > {
3951 this . requireMrtCredentials ( ) ;
4052
4153 const { bundleId} = this . args ;
@@ -47,7 +59,14 @@ export default class MrtBundleDownload extends MrtCommand<typeof MrtBundleDownlo
4759 ) ;
4860 }
4961
50- this . log ( t ( 'commands.mrt.bundle.download.fetching' , 'Getting download URL for bundle {{bundleId}}...' , { bundleId} ) ) ;
62+ const urlOnly = this . flags [ 'url-only' ] ;
63+
64+ this . log (
65+ t ( 'commands.mrt.bundle.download.fetching' , 'Fetching download URL for bundle {{bundleId}} from {{project}}...' , {
66+ bundleId,
67+ project,
68+ } ) ,
69+ ) ;
5170
5271 try {
5372 const result = await downloadBundle (
@@ -59,19 +78,66 @@ export default class MrtBundleDownload extends MrtCommand<typeof MrtBundleDownlo
5978 this . getMrtAuth ( ) ,
6079 ) ;
6180
81+ // If url-only flag or JSON mode, just return the URL
82+ if ( urlOnly ) {
83+ if ( ! this . jsonEnabled ( ) ) {
84+ this . log (
85+ t ( 'commands.mrt.bundle.download.urlOnly' , 'Download URL (valid for 1 hour):\n{{downloadUrl}}' , {
86+ downloadUrl : result . downloadUrl ,
87+ } ) ,
88+ ) ;
89+ }
90+ return result ;
91+ }
92+
93+ // Download the file
94+ const outputPath = this . flags . output ?? `bundle-${ bundleId } .tgz` ;
95+ const absolutePath = path . resolve ( outputPath ) ;
96+
97+ this . log (
98+ t ( 'commands.mrt.bundle.download.downloading' , 'Downloading bundle {{bundleId}} to {{filePath}}...' , {
99+ bundleId,
100+ filePath : outputPath ,
101+ } ) ,
102+ ) ;
103+
104+ const response = await fetch ( result . downloadUrl ) ;
105+ if ( ! response . ok ) {
106+ this . error (
107+ t ( 'commands.mrt.bundle.download.httpError' , 'Failed to download bundle: HTTP {{status}}' , {
108+ status : response . status ,
109+ } ) ,
110+ ) ;
111+ }
112+
113+ if ( ! response . body ) {
114+ this . error ( t ( 'commands.mrt.bundle.download.noBody' , 'Failed to download bundle: empty response' ) ) ;
115+ }
116+
117+ // Ensure directory exists
118+ const dir = path . dirname ( absolutePath ) ;
119+ if ( dir !== '.' ) {
120+ fs . mkdirSync ( dir , { recursive : true } ) ;
121+ }
122+
123+ // Stream the response to file
124+ const fileStream = fs . createWriteStream ( absolutePath ) ;
125+ await pipeline ( response . body , fileStream ) ;
126+
62127 if ( ! this . jsonEnabled ( ) ) {
63128 this . log (
64- t ( 'commands.mrt.bundle.download.success' , 'Download URL (valid for 1 hour):\n{{downloadUrl}}' , {
65- downloadUrl : result . downloadUrl ,
129+ t ( 'commands.mrt.bundle.download.success' , 'Bundle {{bundleId}} downloaded to {{filePath}}' , {
130+ bundleId,
131+ filePath : outputPath ,
66132 } ) ,
67133 ) ;
68134 }
69135
70- return result ;
136+ return { ... result , filePath : absolutePath } ;
71137 } catch ( error ) {
72138 if ( error instanceof Error ) {
73139 this . error (
74- t ( 'commands.mrt.bundle.download.failed' , 'Failed to get download URL : {{message}}' , { message : error . message } ) ,
140+ t ( 'commands.mrt.bundle.download.failed' , 'Failed to download bundle : {{message}}' , { message : error . message } ) ,
75141 ) ;
76142 }
77143 throw error ;
0 commit comments