@@ -4,6 +4,7 @@ const { resolve } = require('path');
44const { isEmpty, path } = require ( 'ramda' ) ;
55const { Graph, alg } = require ( 'graphlib' ) ;
66const traverse = require ( 'traverse' ) ;
7+ const pLimit = require ( 'p-limit' ) ;
78const ServerlessError = require ( './serverless-error' ) ;
89const utils = require ( './utils' ) ;
910const { loadComponent } = require ( './load' ) ;
@@ -223,10 +224,12 @@ class ComponentsService {
223224 /**
224225 * @param {import('./Context') } context
225226 * @param configuration
227+ * @param options
226228 */
227- constructor ( context , configuration ) {
229+ constructor ( context , configuration , options ) {
228230 this . context = context ;
229231 this . configuration = configuration ;
232+ this . options = options ;
230233
231234 // Variables that will be populated during init
232235 this . allComponents = null ;
@@ -304,6 +307,13 @@ class ComponentsService {
304307 } ) ;
305308 }
306309
310+ async package ( options ) {
311+ this . context . output . log ( ) ;
312+ this . context . output . log ( `Packaging for stage ${ this . context . stage } ` ) ;
313+
314+ await this . invokeComponentsInParallel ( 'package' , options ) ;
315+ }
316+
307317 async logs ( options ) {
308318 await this . invokeComponentsInParallel ( 'logs' , options ) ;
309319 }
@@ -330,16 +340,16 @@ class ComponentsService {
330340 }
331341
332342 async invokeGlobalCommand ( command , options ) {
333- const globalCommands = [ 'deploy' , 'remove' , 'info' , 'logs' , 'outputs' , 'refresh-outputs' ] ;
343+ const globalCommands = [
344+ 'deploy' ,
345+ 'remove' ,
346+ 'info' ,
347+ 'logs' ,
348+ 'outputs' ,
349+ 'refresh-outputs' ,
350+ 'package' ,
351+ ] ;
334352 // Specific error messages for popular Framework commands
335- if ( command === 'package' ) {
336- throw new ServerlessError (
337- `"package" is not a global command in Serverless Framework Compose.\nAvailable global commands: ${ globalCommands . join (
338- ', '
339- ) } .\nYou can package each Serverless Framework service by running "serverless <service-name>:${ command } ".`,
340- 'COMMAND_NOT_FOUND'
341- ) ;
342- }
343353 if ( command === 'invoke' ) {
344354 throw new ServerlessError (
345355 `"invoke" is not a global command in Serverless Framework Compose.\nAvailable global commands: ${ globalCommands . join (
@@ -391,7 +401,7 @@ class ComponentsService {
391401 }
392402 this . context . logVerbose ( `Invoking "${ command } " on service "${ componentName } "` ) ;
393403
394- const isDefaultCommand = [ 'deploy' , 'remove' , 'logs' , 'info' ] . includes ( command ) ;
404+ const isDefaultCommand = [ 'deploy' , 'remove' , 'logs' , 'info' , 'package' ] . includes ( command ) ;
395405
396406 if ( isDefaultCommand ) {
397407 // Default command defined for all components (deploy, logs, dev, etc.)
@@ -440,21 +450,29 @@ class ComponentsService {
440450 await this . instantiateComponents ( ) ;
441451
442452 this . context . logVerbose ( `Executing "${ method } " across all services in parallel` ) ;
443- const promises = Object . entries ( this . allComponents ) . map ( async ( [ id , { instance } ] ) => {
444- if ( typeof instance [ method ] !== 'function' ) return ;
445- try {
446- await instance [ method ] ( options ) ;
447- this . context . componentCommandsOutcomes [ id ] = 'success' ;
448- } catch ( e ) {
449- // If the component has an ongoing progress, we automatically set it to "error"
450- if ( this . context . progresses . exists ( id ) ) {
451- this . context . progresses . error ( id , e ) ;
452- } else {
453- this . context . output . error ( formatError ( e ) , [ id ] ) ;
453+ const limit = pLimit ( options [ 'max-concurrency' ] || Infinity ) ;
454+
455+ const promises = [ ] ;
456+
457+ for ( const [ id , { instance } ] of Object . entries ( this . allComponents ) ) {
458+ const fn = async ( ) => {
459+ if ( typeof instance [ method ] !== 'function' ) return ;
460+ try {
461+ await instance [ method ] ( options ) ;
462+ this . context . componentCommandsOutcomes [ id ] = 'success' ;
463+ } catch ( e ) {
464+ // If the component has an ongoing progress, we automatically set it to "error"
465+ if ( this . context . progresses . exists ( id ) ) {
466+ this . context . progresses . error ( id , e ) ;
467+ } else {
468+ this . context . output . error ( formatError ( e ) , [ id ] ) ;
469+ }
470+ this . context . componentCommandsOutcomes [ id ] = 'failure' ;
454471 }
455- this . context . componentCommandsOutcomes [ id ] = 'failure' ;
456- }
457- } ) ;
472+ } ;
473+
474+ promises . push ( limit ( fn ) ) ;
475+ }
458476
459477 await Promise . all ( promises ) ;
460478 }
@@ -476,6 +494,8 @@ class ComponentsService {
476494 return ;
477495 }
478496
497+ const limit = pLimit ( this . options [ 'max-concurrency' ] || Infinity ) ;
498+
479499 /** @type {Promise<boolean>[] } */
480500 const promises = [ ] ;
481501
@@ -519,7 +539,7 @@ class ComponentsService {
519539 }
520540 } ;
521541
522- promises . push ( fn ( ) ) ;
542+ promises . push ( limit ( fn ) ) ;
523543 }
524544
525545 const results = await Promise . all ( promises ) ;
0 commit comments