1
1
import * as path from 'path'
2
2
import * as fs from 'fs-extra'
3
-
4
3
import * as _ from 'lodash'
5
4
import * as globby from 'globby'
6
5
7
- import { ServerlessOptions , ServerlessInstance , ServerlessFunction } from './types'
8
6
import * as typescript from './typescript'
9
-
10
7
import { watchFiles } from './watchFiles'
11
8
12
- // Folders
13
- const serverlessFolder = '.serverless'
14
- const buildFolder = '.build'
9
+ const SERVERLESS_FOLDER = '.serverless'
10
+ const BUILD_FOLDER = '.build'
15
11
16
12
export class TypeScriptPlugin {
17
-
18
13
private originalServicePath : string
19
14
private isWatching : boolean
20
15
21
- serverless : ServerlessInstance
22
- options : ServerlessOptions
23
- commands : { [ key : string ] : any }
16
+ serverless : Serverless . Instance
17
+ options : Serverless . Options
24
18
hooks : { [ key : string ] : Function }
25
19
26
- constructor ( serverless : ServerlessInstance , options : ServerlessOptions ) {
20
+ constructor ( serverless : Serverless . Instance , options : Serverless . Options ) {
27
21
this . serverless = serverless
28
22
this . options = options
29
23
30
24
this . hooks = {
31
25
'before:run:run' : async ( ) => {
32
26
await this . compileTs ( )
27
+ await this . copyExtras ( )
28
+ await this . copyDependencies ( )
33
29
} ,
34
30
'before:offline:start' : async ( ) => {
35
31
await this . compileTs ( )
32
+ await this . copyExtras ( )
33
+ await this . copyDependencies ( )
36
34
this . watchAll ( )
37
35
} ,
38
36
'before:offline:start:init' : async ( ) => {
39
37
await this . compileTs ( )
38
+ await this . copyExtras ( )
39
+ await this . copyDependencies ( )
40
40
this . watchAll ( )
41
41
} ,
42
- 'before:package:createDeploymentArtifacts' : this . compileTs . bind ( this ) ,
43
- 'after:package:createDeploymentArtifacts' : this . cleanup . bind ( this ) ,
44
- 'before:deploy:function:packageFunction' : this . compileTs . bind ( this ) ,
45
- 'after:deploy:function:packageFunction' : this . cleanup . bind ( this ) ,
42
+ 'before:package:createDeploymentArtifacts' : async ( ) => {
43
+ await this . compileTs ( )
44
+ await this . copyExtras ( )
45
+ await this . copyDependencies ( true )
46
+ } ,
47
+ 'after:package:createDeploymentArtifacts' : async ( ) => {
48
+ await this . cleanup ( )
49
+ } ,
50
+ 'before:deploy:function:packageFunction' : async ( ) => {
51
+ await this . compileTs ( )
52
+ await this . copyExtras ( )
53
+ await this . copyDependencies ( true )
54
+ } ,
55
+ 'after:deploy:function:packageFunction' : async ( ) => {
56
+ await this . cleanup ( )
57
+ } ,
46
58
'before:invoke:local:invoke' : async ( ) => {
47
59
const emitedFiles = await this . compileTs ( )
60
+ await this . copyExtras ( )
61
+ await this . copyDependencies ( )
48
62
if ( this . isWatching ) {
49
63
emitedFiles . forEach ( filename => {
50
64
const module = require . resolve ( path . resolve ( this . originalServicePath , filename ) )
@@ -55,31 +69,42 @@ export class TypeScriptPlugin {
55
69
'after:invoke:local:invoke' : ( ) => {
56
70
if ( this . options . watch ) {
57
71
this . watchFunction ( )
58
- this . serverless . cli . log ( 'Waiting for changes ...' )
72
+ this . serverless . cli . log ( 'Waiting for changes...' )
59
73
}
60
74
}
61
75
}
62
76
}
63
77
64
78
get functions ( ) {
65
- return this . options . function
66
- ? { [ this . options . function ] : this . serverless . service . functions [ this . options . function ] }
67
- : this . serverless . service . functions
79
+ const { options } = this
80
+ const { service } = this . serverless
81
+
82
+ if ( options . function ) {
83
+ return {
84
+ [ options . function ] : service . functions [ this . options . function ]
85
+ }
86
+ }
87
+
88
+ return service . functions
68
89
}
69
90
70
91
get rootFileNames ( ) {
71
- return typescript . extractFileNames ( this . originalServicePath , this . serverless . service . provider . name , this . functions )
92
+ return typescript . extractFileNames (
93
+ this . originalServicePath ,
94
+ this . serverless . service . provider . name ,
95
+ this . functions
96
+ )
72
97
}
73
98
74
99
prepare ( ) {
75
100
// exclude serverless-plugin-typescript
76
- const functions = this . functions
77
- for ( const fnName in functions ) {
78
- const fn = functions [ fnName ]
101
+ for ( const fnName in this . functions ) {
102
+ const fn = this . functions [ fnName ]
79
103
fn . package = fn . package || {
80
104
exclude : [ ] ,
81
105
include : [ ] ,
82
106
}
107
+
83
108
// Add plugin to excluded packages or an empty array if exclude is undefined
84
109
fn . package . exclude = _ . uniq ( [ ...fn . package . exclude || [ ] , 'node_modules/serverless-plugin-typescript' ] )
85
110
}
@@ -106,9 +131,7 @@ export class TypeScriptPlugin {
106
131
this . serverless . cli . log ( `Watching typescript files...` )
107
132
108
133
this . isWatching = true
109
- watchFiles ( this . rootFileNames , this . originalServicePath , ( ) => {
110
- this . compileTs ( )
111
- } )
134
+ watchFiles ( this . rootFileNames , this . originalServicePath , this . compileTs )
112
135
}
113
136
114
137
async compileTs ( ) : Promise < string [ ] > {
@@ -119,88 +142,113 @@ export class TypeScriptPlugin {
119
142
// Save original service path and functions
120
143
this . originalServicePath = this . serverless . config . servicePath
121
144
// Fake service path so that serverless will know what to zip
122
- this . serverless . config . servicePath = path . join ( this . originalServicePath , buildFolder )
145
+ this . serverless . config . servicePath = path . join ( this . originalServicePath , BUILD_FOLDER )
123
146
}
124
147
125
148
const tsconfig = typescript . getTypescriptConfig (
126
149
this . originalServicePath ,
127
150
this . isWatching ? null : this . serverless . cli
128
151
)
129
152
130
- tsconfig . outDir = buildFolder
153
+ tsconfig . outDir = BUILD_FOLDER
131
154
132
155
const emitedFiles = await typescript . run ( this . rootFileNames , tsconfig )
133
- await this . copyExtras ( )
134
156
this . serverless . cli . log ( 'Typescript compiled.' )
135
157
return emitedFiles
136
158
}
137
159
160
+ /** Link or copy extras such as node_modules or package.include definitions */
138
161
async copyExtras ( ) {
139
- const outPkgPath = path . resolve ( path . join ( buildFolder , 'package.json' ) )
140
- const outModulesPath = path . resolve ( path . join ( buildFolder , 'node_modules' ) )
141
-
142
- // Link or copy node_modules and package.json to .build so Serverless can
143
- // exlcude devDeps during packaging
144
- if ( ! fs . existsSync ( outModulesPath ) ) {
145
- await this . linkOrCopy ( path . resolve ( 'node_modules' ) , outModulesPath , 'junction' )
146
- }
147
-
148
- if ( ! fs . existsSync ( outPkgPath ) ) {
149
- await this . linkOrCopy ( path . resolve ( 'package.json' ) , outPkgPath , 'file' )
150
- }
162
+ const { service } = this . serverless
151
163
152
164
// include any "extras" from the "include" section
153
- if ( this . serverless . service . package . include && this . serverless . service . package . include . length > 0 ) {
154
- const files = await globby ( this . serverless . service . package . include )
165
+ if ( service . package . include && service . package . include . length > 0 ) {
166
+ const files = await globby ( service . package . include )
155
167
156
168
for ( const filename of files ) {
157
- const destFileName = path . resolve ( path . join ( buildFolder , filename ) )
169
+ const destFileName = path . resolve ( path . join ( BUILD_FOLDER , filename ) )
158
170
const dirname = path . dirname ( destFileName )
159
171
160
172
if ( ! fs . existsSync ( dirname ) ) {
161
173
fs . mkdirpSync ( dirname )
162
174
}
163
175
164
176
if ( ! fs . existsSync ( destFileName ) ) {
165
- fs . copySync ( path . resolve ( filename ) , path . resolve ( path . join ( buildFolder , filename ) ) )
177
+ fs . copySync ( path . resolve ( filename ) , path . resolve ( path . join ( BUILD_FOLDER , filename ) ) )
166
178
}
167
179
}
168
180
}
169
181
}
170
182
183
+ /**
184
+ * Copy the `node_modules` folder and `package.json` files to the output
185
+ * directory.
186
+ * @param isPackaging Provided if serverless is packaging the service for deployment
187
+ */
188
+ async copyDependencies ( isPackaging = false ) {
189
+ const outPkgPath = path . resolve ( path . join ( BUILD_FOLDER , 'package.json' ) )
190
+ const outModulesPath = path . resolve ( path . join ( BUILD_FOLDER , 'node_modules' ) )
191
+
192
+ // copy development dependencies during packaging
193
+ if ( isPackaging ) {
194
+ if ( fs . existsSync ( outModulesPath ) ) {
195
+ fs . unlinkSync ( outModulesPath )
196
+ }
197
+
198
+ fs . copySync (
199
+ path . resolve ( 'node_modules' ) ,
200
+ path . resolve ( path . join ( BUILD_FOLDER , 'node_modules' ) )
201
+ )
202
+ } else {
203
+ if ( ! fs . existsSync ( outModulesPath ) ) {
204
+ await this . linkOrCopy ( path . resolve ( 'node_modules' ) , outModulesPath , 'junction' )
205
+ }
206
+ }
207
+
208
+ // copy/link package.json
209
+ if ( ! fs . existsSync ( outPkgPath ) ) {
210
+ await this . linkOrCopy ( path . resolve ( 'package.json' ) , outPkgPath , 'file' )
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Move built code to the serverless folder, taking into account individual
216
+ * packaging preferences.
217
+ */
171
218
async moveArtifacts ( ) : Promise < void > {
219
+ const { service } = this . serverless
220
+
172
221
await fs . copy (
173
- path . join ( this . originalServicePath , buildFolder , serverlessFolder ) ,
174
- path . join ( this . originalServicePath , serverlessFolder )
222
+ path . join ( this . originalServicePath , BUILD_FOLDER , SERVERLESS_FOLDER ) ,
223
+ path . join ( this . originalServicePath , SERVERLESS_FOLDER )
175
224
)
176
225
177
226
if ( this . options . function ) {
178
- const fn = this . serverless . service . functions [ this . options . function ]
179
- const basename = path . basename ( fn . package . artifact )
180
- fn . package . artifact = path . join (
227
+ const fn = service . functions [ this . options . function ]
228
+ fn . package . artifact = path . join (
181
229
this . originalServicePath ,
182
- serverlessFolder ,
230
+ SERVERLESS_FOLDER ,
183
231
path . basename ( fn . package . artifact )
184
232
)
185
233
return
186
234
}
187
235
188
- if ( this . serverless . service . package . individually ) {
189
- const functionNames = this . serverless . service . getAllFunctions ( )
236
+ if ( service . package . individually ) {
237
+ const functionNames = service . getAllFunctions ( )
190
238
functionNames . forEach ( name => {
191
- this . serverless . service . functions [ name ] . package . artifact = path . join (
239
+ service . functions [ name ] . package . artifact = path . join (
192
240
this . originalServicePath ,
193
- serverlessFolder ,
194
- path . basename ( this . serverless . service . functions [ name ] . package . artifact )
241
+ SERVERLESS_FOLDER ,
242
+ path . basename ( service . functions [ name ] . package . artifact )
195
243
)
196
244
} )
197
245
return
198
246
}
199
247
200
- this . serverless . service . package . artifact = path . join (
248
+ service . package . artifact = path . join (
201
249
this . originalServicePath ,
202
- serverlessFolder ,
203
- path . basename ( this . serverless . service . package . artifact )
250
+ SERVERLESS_FOLDER ,
251
+ path . basename ( service . package . artifact )
204
252
)
205
253
}
206
254
@@ -209,18 +257,14 @@ export class TypeScriptPlugin {
209
257
// Restore service path
210
258
this . serverless . config . servicePath = this . originalServicePath
211
259
// Remove temp build folder
212
- fs . removeSync ( path . join ( this . originalServicePath , buildFolder ) )
260
+ fs . removeSync ( path . join ( this . originalServicePath , BUILD_FOLDER ) )
213
261
}
214
262
215
263
/**
216
264
* Attempt to symlink a given path or directory and copy if it fails with an
217
265
* `EPERM` error.
218
266
*/
219
- private async linkOrCopy (
220
- srcPath : string ,
221
- dstPath : string ,
222
- type ?: 'dir' | 'junction' | 'file'
223
- ) : Promise < void > {
267
+ private async linkOrCopy ( srcPath : string , dstPath : string , type ?: fs . FsSymlinkType ) : Promise < void > {
224
268
return fs . symlink ( srcPath , dstPath , type )
225
269
. catch ( error => {
226
270
if ( error . code === 'EPERM' && error . errno === - 4048 ) {
0 commit comments