11import * as path from 'path'
22import * as fs from 'fs-p'
3+
34import * as _ from 'lodash'
45import * as globby from 'globby'
56
67import { ServerlessOptions , ServerlessInstance , ServerlessFunction } from './types'
78import * as typescript from './typescript'
89
10+ import { watchFiles } from './watchFiles'
11+
912// Folders
1013const serverlessFolder = '.serverless'
1114const buildFolder = '.build'
1215
13- class ServerlessPlugin {
16+ export class ServerlessPlugin {
1417
1518 private originalServicePath : string
16- private originalFunctions : { [ key : string ] : ServerlessFunction } | { }
19+ private isWatching : boolean
1720
1821 serverless : ServerlessInstance
1922 options : ServerlessOptions
@@ -25,67 +28,110 @@ class ServerlessPlugin {
2528 this . options = options
2629
2730 this . hooks = {
28- 'before:offline:start:init' : this . beforeCreateDeploymentArtifacts . bind ( this ) ,
29- 'before:package:createDeploymentArtifacts' : this . beforeCreateDeploymentArtifacts . bind ( this , 'service' ) ,
30- 'after:package:createDeploymentArtifacts' : this . afterCreateDeploymentArtifacts . bind ( this , 'service' ) ,
31- 'before:deploy:function:packageFunction' : this . beforeCreateDeploymentArtifacts . bind ( this , 'function' ) ,
32- 'after:deploy:function:packageFunction' : this . afterCreateDeploymentArtifacts . bind ( this , 'function' ) ,
33- 'before:invoke:local:invoke' : this . beforeCreateDeploymentArtifacts . bind ( this ) ,
34- 'after:invoke:local:invoke' : this . cleanup . bind ( this ) ,
35- }
36- this . commands = {
37- ts : {
38- commands : {
39- invoke : {
40- usage : 'Run a function locally from the tsc output bundle' ,
41- lifecycleEvents : [
42- 'invoke' ,
43- ] ,
44- options : {
45- function : {
46- usage : 'Name of the function' ,
47- shortcut : 'f' ,
48- required : true ,
49- } ,
50- path : {
51- usage : 'Path to JSON file holding input data' ,
52- shortcut : 'p' ,
53- } ,
54- } ,
55- } ,
56- } ,
31+ 'before:offline:start' : async ( ) => {
32+ await this . compileTs ( )
33+ this . watchAll ( )
5734 } ,
35+ 'before:offline:start:init' : async ( ) => {
36+ await this . compileTs ( )
37+ this . watchAll ( )
38+ } ,
39+ 'before:package:createDeploymentArtifacts' : this . compileTs . bind ( this ) ,
40+ 'after:package:createDeploymentArtifacts' : this . cleanup . bind ( this ) ,
41+ 'before:deploy:function:packageFunction' : this . compileTs . bind ( this ) ,
42+ 'after:deploy:function:packageFunction' : this . cleanup . bind ( this ) ,
43+ 'before:invoke:local:invoke' : async ( ) => {
44+ const emitedFiles = await this . compileTs ( )
45+ if ( this . isWatching ) {
46+ emitedFiles . forEach ( filename => {
47+ const module = require . resolve ( path . resolve ( this . originalServicePath , filename ) )
48+ delete require . cache [ module ]
49+ } )
50+ }
51+ } ,
52+ 'after:invoke:local:invoke' : ( ) => {
53+ if ( this . options . watch ) {
54+ this . watchFunction ( )
55+ this . serverless . cli . log ( 'Waiting for changes ...' )
56+ }
57+ }
5858 }
5959 }
6060
61- async beforeCreateDeploymentArtifacts ( type : string ) : Promise < void > {
62- this . serverless . cli . log ( 'Compiling with Typescript...' )
63-
64- // Save original service path and functions
65- this . originalServicePath = this . serverless . config . servicePath
66- this . originalFunctions = type === 'function'
67- ? _ . pick ( this . serverless . service . functions , [ this . options . function ] )
61+ get functions ( ) {
62+ return this . options . function
63+ ? { [ this . options . function ] : this . serverless . service . functions [ this . options . function ] }
6864 : this . serverless . service . functions
65+ }
6966
70- // Fake service path so that serverless will know what to zip
71- this . serverless . config . servicePath = path . join ( this . originalServicePath , buildFolder )
72-
73- const tsFileNames = typescript . extractFileNames ( this . originalFunctions )
74- const tsconfig = typescript . getTypescriptConfig ( this . originalServicePath )
67+ get rootFileNames ( ) {
68+ return typescript . extractFileNames ( this . functions )
69+ }
7570
76- for ( const fnName in this . originalFunctions ) {
77- const fn = this . originalFunctions [ fnName ]
71+ prepare ( ) {
72+ // exclude serverless-plugin-typescript
73+ const functions = this . functions
74+ for ( const fnName in functions ) {
75+ const fn = functions [ fnName ]
7876 fn . package = fn . package || {
7977 exclude : [ ] ,
8078 include : [ ] ,
8179 }
8280 fn . package . exclude = _ . uniq ( [ ...fn . package . exclude , 'node_modules/serverless-plugin-typescript' ] )
8381 }
82+ }
83+
84+ async watchFunction ( ) : Promise < void > {
85+ if ( this . isWatching ) {
86+ return
87+ }
88+
89+ this . serverless . cli . log ( `Watch function ${ this . options . function } ...` )
90+
91+ this . isWatching = true
92+ watchFiles ( this . rootFileNames , this . originalServicePath , ( ) => {
93+ this . serverless . pluginManager . spawn ( 'invoke:local' )
94+ } )
95+ }
96+
97+ async watchAll ( ) : Promise < void > {
98+ if ( this . isWatching ) {
99+ return
100+ }
101+
102+ this . serverless . cli . log ( `Watching typescript files...` )
103+
104+ this . isWatching = true
105+ watchFiles ( this . rootFileNames , this . originalServicePath , ( ) => {
106+ this . compileTs ( )
107+ } )
108+ }
109+
110+ async compileTs ( ) : Promise < string [ ] > {
111+ this . prepare ( )
112+ this . serverless . cli . log ( 'Compiling with Typescript...' )
113+
114+ if ( ! this . originalServicePath ) {
115+ // Save original service path and functions
116+ this . originalServicePath = this . serverless . config . servicePath
117+ // Fake service path so that serverless will know what to zip
118+ this . serverless . config . servicePath = path . join ( this . originalServicePath , buildFolder )
119+ }
120+
121+ const tsFileNames = typescript . extractFileNames ( this . functions )
122+ const tsconfig = typescript . getTypescriptConfig (
123+ this . originalServicePath ,
124+ this . isWatching ? null : this . serverless . cli
125+ )
84126
85127 tsconfig . outDir = buildFolder
86128
87- await typescript . run ( tsFileNames , tsconfig )
129+ const emitedFiles = await typescript . run ( tsFileNames , tsconfig )
130+ await this . copyExtras ( )
131+ return emitedFiles
132+ }
88133
134+ async copyExtras ( ) {
89135 // include node_modules into build
90136 if ( ! fs . existsSync ( path . resolve ( path . join ( buildFolder , 'node_modules' ) ) ) ) {
91137 fs . symlinkSync ( path . resolve ( 'node_modules' ) , path . resolve ( path . join ( buildFolder , 'node_modules' ) ) )
@@ -115,23 +161,21 @@ class ServerlessPlugin {
115161 }
116162 }
117163
118- async afterCreateDeploymentArtifacts ( type : string ) : Promise < void > {
119- // Copy .build to .serverless
164+ async cleanup ( ) : Promise < void > {
120165 await fs . copy (
121166 path . join ( this . originalServicePath , buildFolder , serverlessFolder ) ,
122167 path . join ( this . originalServicePath , serverlessFolder )
123168 )
124169
125- const basename = type === ' function'
126- ? path . basename ( this . originalFunctions [ this . options . function ] . artifact )
127- : path . basename ( this . serverless . service . package . artifact )
128- this . serverless . service . package . artifact = path . join ( this . originalServicePath , serverlessFolder , basename )
129-
130- // Cleanup after everything is copied
131- await this . cleanup ( )
132- }
170+ if ( this . options . function ) {
171+ const fn = this . serverless . service . functions [ this . options . function ]
172+ const basename = path . basename ( fn . package . artifact )
173+ fn . package . artifact = path . join ( this . originalServicePath , serverlessFolder , basename )
174+ } else {
175+ const basename = path . basename ( this . serverless . service . package . artifact )
176+ this . serverless . service . package . artifact = path . join ( this . originalServicePath , serverlessFolder , basename )
177+ }
133178
134- async cleanup ( ) : Promise < void > {
135179 // Restore service path
136180 this . serverless . config . servicePath = this . originalServicePath
137181 // Remove temp build folder
0 commit comments