11const child_process = require ( 'child_process' ) ;
22const archiver = require ( 'archiver' ) ;
3- const os = require ( 'os' ) ;
43const chokidar = require ( 'chokidar' ) ;
5- const anymatch = require ( 'anymatch' ) ;
6- const ora = require ( 'ora' ) ;
74const chalk = require ( 'chalk' ) ;
85
9- const ignoredPaths = [
10- '.git/*' ,
11- '.serverless' ,
12- '.serverless/*' ,
13- 'serverless.yml' ,
14- ] ;
15-
16- class ServerlessPlugin {
17- constructor ( serverless ) {
6+ class BrefLive {
7+ constructor ( serverless , options , utils ) {
188 this . serverless = serverless ;
9+ this . utils = utils ;
1910 this . commands = {
20- 'bref- live' : {
11+ 'bref: live' : {
2112 usage : 'Start Bref Live' ,
2213 lifecycleEvents : [ 'start' ] ,
2314 } ,
24- 'bref- live- install' : {
15+ 'bref: live: install' : {
2516 usage : 'Install Bref Live' ,
2617 lifecycleEvents : [ 'install' ] ,
2718 } ,
2819 } ;
2920 this . hooks = {
3021 initialize : ( ) => this . init ( ) ,
31- 'bref- live:start' : ( ) => this . start ( ) ,
32- 'bref- live- install:install' : ( ) => this . install ( ) ,
22+ 'bref: live:start' : ( ) => this . start ( ) ,
23+ 'bref: live: install:install' : ( ) => this . install ( ) ,
3324 } ;
3425 }
3526
@@ -43,48 +34,81 @@ class ServerlessPlugin {
4334 // TODO make those configurable in `bref.live` in serverless.yml
4435 this . serverless . service . provider . environment . BREF_LIVE_BUCKET = this . bucketName ;
4536 this . serverless . service . provider . environment . BREF_LIVE_BUCKET_REGION = 'eu-west-3' ;
37+ if ( process . env . BREF_LIVE_ENABLE ) {
38+ this . serverless . service . provider . environment . BREF_LIVE_ENABLE = process . env . BREF_LIVE_ENABLE ;
39+ }
40+
41+ // TODO support include/exclude
42+ this . packagePatterns = this . serverless . service . package . patterns ?? [ ] ;
4643 }
4744
4845 async install ( ) {
4946 // TODO create the bucket, maybe with a separate CloudFormation stack?
5047 console . log ( `WIP - Create a bucket '${ this . bucketName } ' and make it accessible by Lambda.` ) ;
5148 console . log ( 'Create it in an AWS region close to your location for faster uploads.' ) ;
49+ console . log ( 'In the future the CLI should create the bucket for you, sorry for the trouble :)' ) ;
5250 }
5351
5452 async start ( ) {
55- console . log ( chalk . gray ( `Bref Live will upload changes tracked by git: ${ chalk . underline ( 'git diff HEAD --name-only' ) } ` ) ) ;
56- this . spinner = ora ( 'Watching changes' ) . start ( ) ;
53+ this . changedFiles = [ ] ;
54+
55+ this . spinner = this . utils . progress . create ( ) ;
56+
57+ // TODO implement a pattern matching that == the one used by Framework
58+ const pathsToWatch = this . packagePatterns . filter ( ( pattern ) => ! pattern . startsWith ( '!' ) ) ;
59+ if ( pathsToWatch . length === 0 ) {
60+ pathsToWatch . push ( '*' ) ;
61+ }
62+ const pathsToIgnore = this . packagePatterns . filter ( ( pattern ) => pattern . startsWith ( '!' ) )
63+ . map ( ( pattern ) => pattern . replace ( '!' , '' ) ) ;
5764
58- this . sync ( 'Initial sync' ) ;
59- chokidar . watch ( '.' , {
65+ await this . initialSync ( ) ;
66+
67+ this . spinner . update ( 'Watching changes' ) ;
68+ chokidar . watch ( pathsToWatch , {
6069 ignoreInitial : true ,
61- ignored : ignoredPaths ,
70+ ignored : pathsToIgnore ,
6271 } ) . on ( 'all' , async ( event , path ) => {
63- if ( this . isGitIgnored ( path ) ) return ;
6472 await this . sync ( path ) ;
6573 } ) ;
74+
75+ // TODO catch interrupt to cancel BREF_LIVE_ENABLE
76+ return new Promise ( resolve => { } ) ;
77+ }
78+
79+ async initialSync ( ) {
80+ this . spinner . update ( 'Deploying all functions' ) ;
81+
82+ this . serverless . service . provider . environment . BREF_LIVE_ENABLE = '1' ;
83+ const functionNames = this . serverless . service . getAllFunctions ( ) ;
84+ await Promise . all ( functionNames . map ( ( functionName ) => {
85+ return this . spawnAsync ( 'serverless' , [
86+ 'deploy' , 'function' , '--function' , functionName
87+ ] , {
88+ BREF_LIVE_ENABLE : '1' ,
89+ } ) ;
90+ } ) ) ;
6691 }
6792
6893 async sync ( path ) {
69- this . spinner . text = 'Uploading' ;
94+ this . changedFiles . push ( path ) ;
95+
96+ this . spinner . update ( 'Uploading' ) ;
7097
7198 const startTime = process . hrtime ( ) ;
7299 const startTimeHuman = new Date ( ) . toLocaleTimeString ( ) ;
73100 const functionNames = this . serverless . service . getAllFunctionsNames ( ) ;
74101 await Promise . all ( functionNames . map ( ( functionName ) => this . uploadDiff ( functionName ) ) ) ;
75- this . spinner . succeed ( `${ chalk . gray ( startTimeHuman ) } - ${ this . elapsedTime ( startTime ) } s - ${ path } ` ) ;
76102
77- // New spinner
78- this . spinner = ora ( 'Watching project' ) . start ( ) ;
103+ const elapsedTime = `${ this . elapsedTime ( startTime ) } s` ;
104+ this . utils . log . success ( `${ chalk . gray ( startTimeHuman ) } ${ path } ${ chalk . gray ( elapsedTime ) } ` ) ;
105+
106+ this . spinner . update ( 'Watching changes' ) ;
79107 }
80108
81109 async uploadDiff ( functionName ) {
82- const changedFilesOutput = this . spawnSync ( 'git' , [ 'diff' , 'HEAD' , '--name-only' ] ) ;
83- let changedFiles = changedFilesOutput . split ( os . EOL ) ;
84- changedFiles = changedFiles . filter ( ( file ) => file !== '' && ! anymatch ( ignoredPaths , file ) ) ;
85-
86110 const archive = archiver ( 'zip' , { } ) ;
87- for ( const file of changedFiles ) {
111+ for ( const file of this . changedFiles ) {
88112 archive . file ( file , { name : file } ) ;
89113 }
90114 await archive . finalize ( ) ;
@@ -98,17 +122,31 @@ class ServerlessPlugin {
98122
99123 elapsedTime ( startTime ) {
100124 const hrtime = process . hrtime ( startTime ) ;
101- return ( hrtime [ 0 ] + ( hrtime [ 1 ] / 1e9 ) ) . toFixed ( 3 ) ;
125+ return ( hrtime [ 0 ] + ( hrtime [ 1 ] / 1e9 ) ) . toFixed ( 1 ) ;
102126 }
103127
104- isGitIgnored ( path ) {
105- return child_process . spawnSync ( 'git' , [ 'check-ignore' , path ] ) . status === 0 ;
106- }
107-
108- spawnSync ( cmd , args ) {
109- const p = child_process . spawnSync ( cmd , args ) ;
110- return p . stdout . toString ( ) . trim ( ) ;
128+ async spawnAsync ( command , args , env ) {
129+ const child = child_process . spawn ( command , args , {
130+ env : {
131+ ...process . env ,
132+ ...env ,
133+ } ,
134+ } ) ;
135+ let output = "" ;
136+ for await ( const chunk of child . stdout ) {
137+ output += chunk ;
138+ }
139+ for await ( const chunk of child . stderr ) {
140+ output += chunk ;
141+ }
142+ const exitCode = await new Promise ( ( resolve , reject ) => {
143+ child . on ( 'close' , resolve ) ;
144+ } ) ;
145+ if ( exitCode ) {
146+ throw new Error ( `${ output } ` ) ;
147+ }
148+ return output ;
111149 }
112150}
113151
114- module . exports = ServerlessPlugin ;
152+ module . exports = BrefLive ;
0 commit comments