11import * as path from 'path'
22import * as fs from 'fs-extra'
3-
43import * as _ from 'lodash'
54import * as globby from 'globby'
65
7- import { ServerlessOptions , ServerlessInstance , ServerlessFunction } from './types'
86import * as typescript from './typescript'
9-
107import { watchFiles } from './watchFiles'
118
12- // Folders
13- const serverlessFolder = '.serverless'
14- const buildFolder = '.build'
9+ const SERVERLESS_FOLDER = '.serverless'
10+ const BUILD_FOLDER = '.build'
1511
1612export class TypeScriptPlugin {
17-
1813 private originalServicePath : string
1914 private isWatching : boolean
2015
21- serverless : ServerlessInstance
22- options : ServerlessOptions
23- commands : { [ key : string ] : any }
16+ serverless : Serverless . Instance
17+ options : Serverless . Options
2418 hooks : { [ key : string ] : Function }
2519
26- constructor ( serverless : ServerlessInstance , options : ServerlessOptions ) {
20+ constructor ( serverless : Serverless . Instance , options : Serverless . Options ) {
2721 this . serverless = serverless
2822 this . options = options
2923
3024 this . hooks = {
3125 'before:run:run' : async ( ) => {
3226 await this . compileTs ( )
27+ await this . copyExtras ( )
28+ await this . copyDependencies ( )
3329 } ,
3430 'before:offline:start' : async ( ) => {
3531 await this . compileTs ( )
32+ await this . copyExtras ( )
33+ await this . copyDependencies ( )
3634 this . watchAll ( )
3735 } ,
3836 'before:offline:start:init' : async ( ) => {
3937 await this . compileTs ( )
38+ await this . copyExtras ( )
39+ await this . copyDependencies ( )
4040 this . watchAll ( )
4141 } ,
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+ } ,
4658 'before:invoke:local:invoke' : async ( ) => {
4759 const emitedFiles = await this . compileTs ( )
60+ await this . copyExtras ( )
61+ await this . copyDependencies ( )
4862 if ( this . isWatching ) {
4963 emitedFiles . forEach ( filename => {
5064 const module = require . resolve ( path . resolve ( this . originalServicePath , filename ) )
@@ -55,31 +69,42 @@ export class TypeScriptPlugin {
5569 'after:invoke:local:invoke' : ( ) => {
5670 if ( this . options . watch ) {
5771 this . watchFunction ( )
58- this . serverless . cli . log ( 'Waiting for changes ...' )
72+ this . serverless . cli . log ( 'Waiting for changes...' )
5973 }
6074 }
6175 }
6276 }
6377
6478 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
6889 }
6990
7091 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+ )
7297 }
7398
7499 prepare ( ) {
75100 // 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 ]
79103 fn . package = fn . package || {
80104 exclude : [ ] ,
81105 include : [ ] ,
82106 }
107+
83108 // Add plugin to excluded packages or an empty array if exclude is undefined
84109 fn . package . exclude = _ . uniq ( [ ...fn . package . exclude || [ ] , 'node_modules/serverless-plugin-typescript' ] )
85110 }
@@ -106,9 +131,7 @@ export class TypeScriptPlugin {
106131 this . serverless . cli . log ( `Watching typescript files...` )
107132
108133 this . isWatching = true
109- watchFiles ( this . rootFileNames , this . originalServicePath , ( ) => {
110- this . compileTs ( )
111- } )
134+ watchFiles ( this . rootFileNames , this . originalServicePath , this . compileTs )
112135 }
113136
114137 async compileTs ( ) : Promise < string [ ] > {
@@ -119,88 +142,113 @@ export class TypeScriptPlugin {
119142 // Save original service path and functions
120143 this . originalServicePath = this . serverless . config . servicePath
121144 // 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 )
123146 }
124147
125148 const tsconfig = typescript . getTypescriptConfig (
126149 this . originalServicePath ,
127150 this . isWatching ? null : this . serverless . cli
128151 )
129152
130- tsconfig . outDir = buildFolder
153+ tsconfig . outDir = BUILD_FOLDER
131154
132155 const emitedFiles = await typescript . run ( this . rootFileNames , tsconfig )
133- await this . copyExtras ( )
134156 this . serverless . cli . log ( 'Typescript compiled.' )
135157 return emitedFiles
136158 }
137159
160+ /** Link or copy extras such as node_modules or package.include definitions */
138161 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
151163
152164 // 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 )
155167
156168 for ( const filename of files ) {
157- const destFileName = path . resolve ( path . join ( buildFolder , filename ) )
169+ const destFileName = path . resolve ( path . join ( BUILD_FOLDER , filename ) )
158170 const dirname = path . dirname ( destFileName )
159171
160172 if ( ! fs . existsSync ( dirname ) ) {
161173 fs . mkdirpSync ( dirname )
162174 }
163175
164176 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 ) ) )
166178 }
167179 }
168180 }
169181 }
170182
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+ */
171218 async moveArtifacts ( ) : Promise < void > {
219+ const { service } = this . serverless
220+
172221 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 )
175224 )
176225
177226 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 (
181229 this . originalServicePath ,
182- serverlessFolder ,
230+ SERVERLESS_FOLDER ,
183231 path . basename ( fn . package . artifact )
184232 )
185233 return
186234 }
187235
188- if ( this . serverless . service . package . individually ) {
189- const functionNames = this . serverless . service . getAllFunctions ( )
236+ if ( service . package . individually ) {
237+ const functionNames = service . getAllFunctions ( )
190238 functionNames . forEach ( name => {
191- this . serverless . service . functions [ name ] . package . artifact = path . join (
239+ service . functions [ name ] . package . artifact = path . join (
192240 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 )
195243 )
196244 } )
197245 return
198246 }
199247
200- this . serverless . service . package . artifact = path . join (
248+ service . package . artifact = path . join (
201249 this . originalServicePath ,
202- serverlessFolder ,
203- path . basename ( this . serverless . service . package . artifact )
250+ SERVERLESS_FOLDER ,
251+ path . basename ( service . package . artifact )
204252 )
205253 }
206254
@@ -209,18 +257,14 @@ export class TypeScriptPlugin {
209257 // Restore service path
210258 this . serverless . config . servicePath = this . originalServicePath
211259 // Remove temp build folder
212- fs . removeSync ( path . join ( this . originalServicePath , buildFolder ) )
260+ fs . removeSync ( path . join ( this . originalServicePath , BUILD_FOLDER ) )
213261 }
214262
215263 /**
216264 * Attempt to symlink a given path or directory and copy if it fails with an
217265 * `EPERM` error.
218266 */
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 > {
224268 return fs . symlink ( srcPath , dstPath , type )
225269 . catch ( error => {
226270 if ( error . code === 'EPERM' && error . errno === - 4048 ) {
0 commit comments