1
1
import * as path from 'path'
2
2
import * as fs from 'fs-p'
3
+
3
4
import * as _ from 'lodash'
4
5
import * as globby from 'globby'
5
6
6
7
import { ServerlessOptions , ServerlessInstance , ServerlessFunction } from './types'
7
8
import * as typescript from './typescript'
8
9
10
+ import { watchFiles } from './watchFiles'
11
+
9
12
// Folders
10
13
const serverlessFolder = '.serverless'
11
14
const buildFolder = '.build'
12
15
13
- class ServerlessPlugin {
16
+ export class ServerlessPlugin {
14
17
15
18
private originalServicePath : string
16
- private originalFunctions : { [ key : string ] : ServerlessFunction } | { }
19
+ private isWatching : boolean
17
20
18
21
serverless : ServerlessInstance
19
22
options : ServerlessOptions
@@ -25,67 +28,110 @@ class ServerlessPlugin {
25
28
this . options = options
26
29
27
30
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 ( )
57
34
} ,
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
+ }
58
58
}
59
59
}
60
60
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 ] }
68
64
: this . serverless . service . functions
65
+ }
69
66
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
+ }
75
70
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 ]
78
76
fn . package = fn . package || {
79
77
exclude : [ ] ,
80
78
include : [ ] ,
81
79
}
82
80
fn . package . exclude = _ . uniq ( [ ...fn . package . exclude , 'node_modules/serverless-plugin-typescript' ] )
83
81
}
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
+ )
84
126
85
127
tsconfig . outDir = buildFolder
86
128
87
- await typescript . run ( tsFileNames , tsconfig )
129
+ const emitedFiles = await typescript . run ( tsFileNames , tsconfig )
130
+ await this . copyExtras ( )
131
+ return emitedFiles
132
+ }
88
133
134
+ async copyExtras ( ) {
89
135
// include node_modules into build
90
136
if ( ! fs . existsSync ( path . resolve ( path . join ( buildFolder , 'node_modules' ) ) ) ) {
91
137
fs . symlinkSync ( path . resolve ( 'node_modules' ) , path . resolve ( path . join ( buildFolder , 'node_modules' ) ) )
@@ -115,23 +161,21 @@ class ServerlessPlugin {
115
161
}
116
162
}
117
163
118
- async afterCreateDeploymentArtifacts ( type : string ) : Promise < void > {
119
- // Copy .build to .serverless
164
+ async cleanup ( ) : Promise < void > {
120
165
await fs . copy (
121
166
path . join ( this . originalServicePath , buildFolder , serverlessFolder ) ,
122
167
path . join ( this . originalServicePath , serverlessFolder )
123
168
)
124
169
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
+ }
133
178
134
- async cleanup ( ) : Promise < void > {
135
179
// Restore service path
136
180
this . serverless . config . servicePath = this . originalServicePath
137
181
// Remove temp build folder
0 commit comments