@@ -11,6 +11,11 @@ import yargs from 'yargs';
1111import slash from 'slash' ;
1212import aws4 from 'aws4' ;
1313import axios from 'axios' ;
14+ import chalk from 'chalk' ;
15+
16+ const log = console . log ;
17+ const error = console . error ;
18+ const { blue, green, gray, red} = chalk ;
1419
1520function parseArgumentsIntoOptions ( ) {
1621 return yargs
@@ -22,8 +27,11 @@ function parseArgumentsIntoOptions() {
2227 } )
2328 } , build )
2429 . command ( 'publish' , 'Build the app and upload it to the Homey Community Store' , ( yargs ) => {
25- return yargs ;
26- } , publish )
30+ return yargs . option ( 'force' , {
31+ type : 'boolean' ,
32+ description : 'CAUTION: This will override the version if it already exists in the database!'
33+ } ) ;
34+ } , publish )
2735 . command ( 'logout' , 'Remove all credentials' , ( yargs ) => {
2836 return yargs
2937 } , logout )
@@ -62,12 +70,14 @@ function determineCategory(appInfo) {
6270}
6371
6472function createTar ( appInfo , argv ) {
73+ log ( gray ( 'Process for creating the tar.gz file' ) ) ;
6574 return new Promise ( ( resolve , reject ) => {
6675 let version = `v${ appInfo . version } ` ;
6776 if ( argv . latest ) {
6877 version = 'latest' ;
6978 }
7079 const tarFile = `${ appInfo . id } -${ version } .tar.gz` ;
80+ log ( gray ( `Filename determined: '${ tarFile } '` ) ) ;
7181 tar . c ( {
7282 gzip : true ,
7383 file : tarFile ,
@@ -122,56 +132,70 @@ export async function cli(args) {
122132 parseArgumentsIntoOptions ( args ) ;
123133}
124134
125- function uploadToS3 ( s3Path , bucketName , root ) {
126- let s3 = new AWS . S3 ( ) ;
127-
128- function walkSync ( currentDirPath , callback ) {
129- fs . readdirSync ( currentDirPath ) . forEach ( ( name ) => {
130- const filePath = path . join ( currentDirPath , name ) ;
131- const stat = fs . statSync ( filePath ) ;
132- if ( stat . isFile ( ) ) {
133- callback ( filePath , stat ) ;
134- } else if ( stat . isDirectory ( ) ) {
135- walkSync ( filePath , callback ) ;
136- }
137- } ) ;
138- }
139-
140- walkSync ( s3Path , ( filePath , _stat ) => {
141- const bucketPath = filePath ;
142- const key = slash ( root + bucketPath . split ( s3Path ) [ 1 ] ) . replace ( / \\ / g, '/' ) ;
143- if ( ! [ '.svg' , '.png' , '.jpeg' , '.jpg' , '.gz' ] . includes ( path . extname ( filePath ) ) || filePath . includes ( 'node_modules' ) || filePath . includes ( '.github' ) ) {
144- return ;
135+ async function uploadToS3 ( s3Path , bucketName , root ) {
136+ return new Promise ( async resolve => {
137+ log ( gray ( 'Upload assets to S3' ) ) ;
138+ let s3 = new AWS . S3 ( ) ;
139+
140+ const overall = [ ] ;
141+ async function walkSync ( currentDirPath , callback ) {
142+ const promises = fs . readdirSync ( currentDirPath ) . map ( ( name ) => {
143+ return new Promise ( async ( resolveMap ) => {
144+ const filePath = path . join ( currentDirPath , name ) ;
145+ const stat = fs . statSync ( filePath ) ;
146+ if ( stat . isFile ( ) ) {
147+ await callback ( filePath , stat ) ;
148+ resolveMap ( ) ;
149+ } else if ( stat . isDirectory ( ) ) {
150+ await walkSync ( filePath , callback ) ;
151+ resolveMap ( ) ;
152+ }
153+ } ) ;
154+ } ) ;
155+ overall . push ( ...promises ) ;
145156 }
146- const contentType = mime . contentType ( path . extname ( bucketPath ) ) ;
147- let params = {
148- Bucket : bucketName ,
149- ACL : 'public-read' ,
150- ContentType : contentType ,
151- Key : key ,
152- Body : fs . readFileSync ( filePath )
153- } ;
154- s3 . putObject ( params , function ( err , _data ) {
155- if ( err ) {
156- console . log ( err )
157- } else {
158- console . log ( 'Successfully uploaded ' + bucketPath + ' to ' + bucketName + ' as ' + key ) ;
159- }
157+
158+ await walkSync ( s3Path , ( filePath , _stat ) => {
159+ return new Promise ( async ( resolveWalk , rejectWalk ) => {
160+ const bucketPath = filePath ;
161+ const key = slash ( root + bucketPath . split ( s3Path ) [ 1 ] ) . replace ( / \\ / g, '/' ) ;
162+ if ( ! [ '.svg' , '.png' , '.jpeg' , '.jpg' , '.gz' ] . includes ( path . extname ( filePath ) ) || filePath . includes ( 'node_modules' ) || filePath . includes ( '.github' ) ) {
163+ return resolveWalk ( ) ;
164+ }
165+ const contentType = mime . contentType ( path . extname ( bucketPath ) ) ;
166+ const params = {
167+ Bucket : bucketName ,
168+ ACL : 'public-read' ,
169+ ContentType : contentType ,
170+ Key : key ,
171+ Body : fs . readFileSync ( filePath )
172+ } ;
173+ const success = await s3 . putObject ( params ) . promise ( ) . catch ( rejectWalk ) ;
174+ if ( ! success ) {
175+ return error ( red ( `Could not upload ${ key } ` ) ) ;
176+ }
177+ log ( gray ( 'Successfully uploaded ' + bucketPath + ' to ' + bucketName + ' as ' + key ) ) ;
178+ resolveWalk ( ) ;
179+ } ) ;
160180 } ) ;
181+
182+ resolve ( overall ) ;
161183 } ) ;
162184}
163185
164186async function build ( argv ) {
165- console . log ( 'Building the app' ) ;
187+ log ( blue ( 'Building the app' ) ) ;
166188 let tar = { } ;
189+ let appInfo = { } ;
167190 try {
168- const appInfo = require ( `${ cwd ( ) } /app.json` ) ;
191+ log ( gray ( `Loading '${ cwd ( ) } /app.json'` ) ) ;
192+ appInfo = require ( `${ cwd ( ) } /app.json` ) ;
169193 tar = await createTar ( appInfo , argv ) ;
194+ log ( gray ( `${ tar . filename } created successfully` ) ) ;
170195 } catch ( e ) {
171- console . error ( e ) ;
172- return ;
196+ error ( red ( e ) ) ;
173197 }
174- console . log ( `Build finished: ${ cwd ( ) } /${ tar . filename } ` )
198+ log ( green ( `Build finished: ${ cwd ( ) } /${ tar . filename } ` ) ) ;
175199}
176200
177201function getCredentials ( account ) {
@@ -196,31 +220,37 @@ async function logout(_argv) {
196220 await keytar . deletePassword ( 'hcs-cli' , creds . account ) ;
197221 } ) ;
198222 await Promise . allSettled ( promises ) ;
199- console . log ( 'You have been signed out' ) ;
223+ log ( green ( 'You have been signed out' ) ) ;
200224}
201225
202226async function publish ( argv ) {
227+ log ( blue ( 'Publishing the app' ) ) ;
203228 let appInfo = { } ;
204229 let tar = { } ;
230+ const force = ! ! argv . force ;
205231 try {
232+ log ( gray ( `Loading '${ cwd ( ) } /app.json'` ) ) ;
206233 appInfo = require ( `${ cwd ( ) } /app.json` ) ;
207234 tar = await createTar ( appInfo , argv ) ;
235+ log ( gray ( `${ tar . filename } created successfully` ) ) ;
208236 } catch ( e ) {
209- console . error ( e ) ;
237+ error ( red ( e ) ) ;
210238 return ;
211239 }
212240
241+ log ( gray ( 'Process the app.json' ) ) ;
242+ const timestamp = Date . now ( ) ;
213243 let app = {
214244 id : appInfo . id ,
215- added : Date . now ( ) ,
216- modified : Date . now ( ) ,
245+ added : timestamp ,
246+ modified : timestamp ,
217247 versions : [ {
218248 id : appInfo . id ,
219249 summary : appInfo . description ,
220250 hash : tar . hash ,
221251 filename : tar . filename ,
222- added : Date . now ( ) ,
223- modified : Date . now ( ) ,
252+ added : timestamp ,
253+ modified : timestamp ,
224254 sdk : appInfo . sdk ,
225255 version : appInfo . version ,
226256 compatibility : appInfo . compatibility ,
@@ -251,20 +281,25 @@ async function publish(argv) {
251281 } ]
252282 } ;
253283
284+ log ( gray ( 'Look for ./homeychangelog.json' ) ) ;
254285 if ( fs . existsSync ( `${ cwd ( ) } /.homeychangelog.json` ) ) {
286+ log ( gray ( 'Changelog found, adding it to the app' ) )
255287 app . changelog = require ( `${ cwd ( ) } /.homeychangelog.json` ) ;
256288 app . versions [ 0 ] . changelog = require ( `${ cwd ( ) } /.homeychangelog.json` ) ;
257289 }
258290
291+ log ( gray ( `Processing locales` ) ) ;
259292 const locales = { } ;
260293 const appVersion = app . versions [ 0 ] ;
261294 if ( appVersion . name ) {
295+ log ( gray ( `Processing locales from the name: ${ Object . keys ( appVersion . name ) . join ( ', ' ) } ` ) ) ;
262296 Object . keys ( appVersion . name ) . forEach ( lang => {
263297 locales [ lang ] = { name : appVersion . name [ lang ] }
264298 } ) ;
265299 }
266300
267301 if ( appVersion . summary ) {
302+ log ( gray ( `Processing locales from the summary for the description: ${ Object . keys ( appVersion . name ) . join ( ', ' ) } ` ) ) ;
268303 Object . keys ( appVersion . summary ) . forEach ( lang => {
269304 locales [ lang ] = {
270305 ...locales [ lang ] ,
@@ -274,6 +309,7 @@ async function publish(argv) {
274309 }
275310
276311 if ( appVersion . description ) {
312+ log ( gray ( `Processing locales from the description for the description: ${ Object . keys ( appVersion . name ) . join ( ', ' ) } ` ) ) ;
277313 Object . keys ( appVersion . description ) . forEach ( lang => {
278314 locales [ lang ] = {
279315 ...locales [ lang ] ,
@@ -283,16 +319,18 @@ async function publish(argv) {
283319 }
284320
285321 if ( appVersion . tags ) {
322+ log ( gray ( `Processing locales from the tags: ${ Object . keys ( appVersion . name ) . join ( ', ' ) } ` ) ) ;
286323 Object . keys ( appVersion . tags ) . forEach ( lang => {
287324 locales [ lang ] = {
288325 ...locales [ lang ] ,
289326 tags : appVersion . tags [ lang ]
290327 }
291328 } ) ;
292329 }
293- if ( appVersion . changelog ) {
294330
331+ if ( appVersion . changelog ) {
295332 Object . keys ( appVersion . changelog ) . forEach ( version => {
333+ log ( gray ( `Processing locales from the changelog ${ version } : ${ Object . keys ( appVersion . changelog [ version ] ) . join ( ', ' ) } ` ) ) ;
296334 Object . keys ( appVersion . changelog [ version ] ) . forEach ( lang => {
297335 if ( ! locales [ lang ] ) {
298336 locales [ lang ] = { } ;
@@ -307,24 +345,27 @@ async function publish(argv) {
307345
308346 app . versions [ 0 ] . locales = locales ;
309347
310- const creds = await keytar . findCredentials ( 'hcs-cli' ) . catch ( console . error ) ;
348+ log ( gray ( 'Looking for credentials' ) ) ;
349+ const creds = await keytar . findCredentials ( 'hcs-cli' ) . catch ( err => error ( red ( err ) ) ) ;
311350 let accessKeyId ;
312351 let accessKeySecure ;
313352 if ( creds && creds . length === 1 ) {
314353 accessKeyId = creds [ 0 ] . account ;
315354 accessKeySecure = creds [ 0 ] . password ;
316355 } else {
356+ log ( blue ( 'Credentials not found, please sign in' ) ) ;
317357 accessKeyId = await promptForAccessKeyId ( ) ;
318- accessKeySecure = await getCredentials ( accessKeyId ) . catch ( console . error ) ;
358+ accessKeySecure = await getCredentials ( accessKeyId ) . catch ( err => error ( red ( err ) ) ) ;
319359 }
320360
321361 if ( accessKeySecure === false ) {
322362 //ask for credentials;
363+ log ( blue ( 'Password not found, please sign in' ) ) ;
323364 const accessKeySecret = await promptForAccessKeySecret ( ) ;
324365 if ( accessKeySecret ) {
325- const success = await setCredentials ( accessKeyId , accessKeySecret ) . catch ( console . error ) ;
366+ const success = await setCredentials ( accessKeyId , accessKeySecret ) . catch ( err => error ( red ( err ) ) ) ;
326367 if ( ! success ) {
327- console . log ( 'Something went wrong storing your credentials' ) ;
368+ error ( red ( 'Something went wrong storing your credentials' ) ) ;
328369 return ;
329370 }
330371 } else {
@@ -333,6 +374,7 @@ async function publish(argv) {
333374 accessKeySecure = accessKeySecret ;
334375 }
335376
377+ log ( gray ( 'Creating the AWS Config' ) ) ;
336378 AWS . config = new AWS . Config ( {
337379 region : 'eu-central-1' ,
338380 accessKeyId : accessKeyId ,
@@ -342,40 +384,52 @@ async function publish(argv) {
342384 const request = {
343385 host : '4c23v5xwtc.execute-api.eu-central-1.amazonaws.com' ,
344386 method : 'POST' ,
345- url : `https://4c23v5xwtc.execute-api.eu-central-1.amazonaws.com/staging /apps/publish` ,
346- data : app , // object describing the foo
347- body : JSON . stringify ( app ) , // aws4 looks for body; axios for data
348- path : `/staging /apps/publish` ,
387+ url : `https://4c23v5xwtc.execute-api.eu-central-1.amazonaws.com/production /apps/publish` ,
388+ data : { app, force } , // object describing the foo
389+ body : JSON . stringify ( { app, force } ) , // aws4 looks for body; axios for data
390+ path : `/production /apps/publish` ,
349391 headers : {
350392 'content-type' : 'application/json'
351393 }
352394 }
395+ log ( gray ( `Preparing request to the API ${ request . url } ` ) ) ;
353396
354397 const signedRequest = aws4 . sign ( request ,
355398 {
356- // assumes user has authenticated and we have called
357- // AWS.config.credentials.get to retrieve keys and
358- // session tokens
359399 secretAccessKey : AWS . config . credentials . secretAccessKey ,
360400 accessKeyId : AWS . config . credentials . accessKeyId
361401 } )
362402
363- delete signedRequest . headers [ 'Host' ]
364- delete signedRequest . headers [ 'Content-Length' ]
365-
366- const response = await axios ( signedRequest ) . catch ( console . error ) ;
403+ delete signedRequest . headers [ 'Host' ] ;
404+ delete signedRequest . headers [ 'Content-Length' ] ;
367405
406+ log ( gray ( `Send request to the API ${ request . url } ` ) ) ;
407+ const response = await axios ( signedRequest ) . catch ( err => error ( red ( err ) ) ) ;
368408 if ( response && response . data && response . data . body ) {
369409 const { success, msg} = response . data . body ;
370410 if ( ! success ) {
371- console . error ( msg ) ;
411+ error ( red ( msg ) ) ;
372412 return ;
373413 }
374- console . log ( msg ) ;
375- //PUSH TAR FILE AND IMAGES TO S3!
376- uploadToS3 ( cwd ( ) , 'homey-community-store' , `${ app . id } /${ appInfo . version } ` ) ;
414+ log ( gray ( msg ) ) ;
415+
416+ const uploadPromise = uploadToS3 ( cwd ( ) , 'homey-community-store' , `${ app . id } /${ appInfo . version } ` ) ;
417+ const filePromises = await uploadPromise ;
418+ if ( filePromises ) {
419+ let errors ;
420+ await Promise . allSettled ( filePromises ) . catch ( err => errors = err ) ;
421+ if ( errors ) {
422+ log ( red ( 'Failed to push an asset to the S3 storage. Failed to publish the app. Please contact the HCS admin' ) ) ;
423+ } else {
424+ log ( green ( 'Successfully published the app to the Homey Community Store.' ) ) ;
425+ }
426+ } else {
427+ error ( red ( 'FAILED TO PUBLISH' ) ) ;
428+ }
429+
430+
377431 } else {
378- console . error ( 'Failed pushing to the DB' ) ;
432+ error ( red ( 'Failed pushing to the DB' ) ) ;
433+ error ( red ( response . statusText ) ) ;
379434 }
380-
381435}
0 commit comments