|
1 |
| -import * as path from 'path' |
2 |
| -import * as fs from 'fs-extra' |
3 |
| -import * as _ from 'lodash' |
4 |
| -import * as globby from 'globby' |
5 |
| - |
6 |
| -import * as typescript from './typescript' |
7 |
| -import { watchFiles } from './watchFiles' |
8 |
| - |
9 |
| -const SERVERLESS_FOLDER = '.serverless' |
10 |
| -const BUILD_FOLDER = '.build' |
11 |
| - |
12 |
| -export class TypeScriptPlugin { |
13 |
| - private originalServicePath: string |
14 |
| - private isWatching: boolean |
15 |
| - |
16 |
| - serverless: Serverless.Instance |
17 |
| - options: Serverless.Options |
18 |
| - hooks: { [key: string]: Function } |
19 |
| - |
20 |
| - constructor(serverless: Serverless.Instance, options: Serverless.Options) { |
21 |
| - this.serverless = serverless |
22 |
| - this.options = options |
23 |
| - |
24 |
| - this.hooks = { |
25 |
| - 'before:run:run': async () => { |
26 |
| - await this.compileTs() |
27 |
| - await this.copyExtras() |
28 |
| - await this.copyDependencies() |
29 |
| - }, |
30 |
| - 'before:offline:start': async () => { |
31 |
| - await this.compileTs() |
32 |
| - await this.copyExtras() |
33 |
| - await this.copyDependencies() |
34 |
| - this.watchAll() |
35 |
| - }, |
36 |
| - 'before:offline:start:init': async () => { |
37 |
| - await this.compileTs() |
38 |
| - await this.copyExtras() |
39 |
| - await this.copyDependencies() |
40 |
| - this.watchAll() |
41 |
| - }, |
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 |
| - }, |
58 |
| - 'before:invoke:local:invoke': async () => { |
59 |
| - const emitedFiles = await this.compileTs() |
60 |
| - await this.copyExtras() |
61 |
| - await this.copyDependencies() |
62 |
| - if (this.isWatching) { |
63 |
| - emitedFiles.forEach(filename => { |
64 |
| - const module = require.resolve(path.resolve(this.originalServicePath, filename)) |
65 |
| - delete require.cache[module] |
66 |
| - }) |
67 |
| - } |
68 |
| - }, |
69 |
| - 'after:invoke:local:invoke': () => { |
70 |
| - if (this.options.watch) { |
71 |
| - this.watchFunction() |
72 |
| - this.serverless.cli.log('Waiting for changes...') |
73 |
| - } |
74 |
| - } |
75 |
| - } |
76 |
| - } |
77 |
| - |
78 |
| - get 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 |
89 |
| - } |
90 |
| - |
91 |
| - get rootFileNames() { |
92 |
| - return typescript.extractFileNames( |
93 |
| - this.originalServicePath, |
94 |
| - this.serverless.service.provider.name, |
95 |
| - this.functions |
96 |
| - ) |
97 |
| - } |
98 |
| - |
99 |
| - prepare() { |
100 |
| - // exclude serverless-plugin-typescript |
101 |
| - for (const fnName in this.functions) { |
102 |
| - const fn = this.functions[fnName] |
103 |
| - fn.package = fn.package || { |
104 |
| - exclude: [], |
105 |
| - include: [], |
106 |
| - } |
107 |
| - |
108 |
| - // Add plugin to excluded packages or an empty array if exclude is undefined |
109 |
| - fn.package.exclude = _.uniq([...fn.package.exclude || [], 'node_modules/serverless-plugin-typescript']) |
110 |
| - } |
111 |
| - } |
112 |
| - |
113 |
| - async watchFunction(): Promise<void> { |
114 |
| - if (this.isWatching) { |
115 |
| - return |
116 |
| - } |
117 |
| - |
118 |
| - this.serverless.cli.log(`Watch function ${this.options.function}...`) |
119 |
| - |
120 |
| - this.isWatching = true |
121 |
| - watchFiles(this.rootFileNames, this.originalServicePath, () => { |
122 |
| - this.serverless.pluginManager.spawn('invoke:local') |
123 |
| - }) |
124 |
| - } |
125 |
| - |
126 |
| - async watchAll(): Promise<void> { |
127 |
| - if (this.isWatching) { |
128 |
| - return |
129 |
| - } |
130 |
| - |
131 |
| - this.serverless.cli.log(`Watching typescript files...`) |
132 |
| - |
133 |
| - this.isWatching = true |
134 |
| - watchFiles(this.rootFileNames, this.originalServicePath, this.compileTs.bind(this)) |
135 |
| - } |
136 |
| - |
137 |
| - async compileTs(): Promise<string[]> { |
138 |
| - this.prepare() |
139 |
| - this.serverless.cli.log('Compiling with Typescript...') |
140 |
| - |
141 |
| - if (!this.originalServicePath) { |
142 |
| - // Save original service path and functions |
143 |
| - this.originalServicePath = this.serverless.config.servicePath |
144 |
| - // Fake service path so that serverless will know what to zip |
145 |
| - this.serverless.config.servicePath = path.join(this.originalServicePath, BUILD_FOLDER) |
146 |
| - } |
147 |
| - |
148 |
| - const tsconfig = typescript.getTypescriptConfig( |
149 |
| - this.originalServicePath, |
150 |
| - this.isWatching ? null : this.serverless.cli |
151 |
| - ) |
152 |
| - |
153 |
| - tsconfig.outDir = BUILD_FOLDER |
154 |
| - |
155 |
| - const emitedFiles = await typescript.run(this.rootFileNames, tsconfig) |
156 |
| - this.serverless.cli.log('Typescript compiled.') |
157 |
| - return emitedFiles |
158 |
| - } |
159 |
| - |
160 |
| - /** Link or copy extras such as node_modules or package.include definitions */ |
161 |
| - async copyExtras() { |
162 |
| - const { service } = this.serverless |
163 |
| - |
164 |
| - // include any "extras" from the "include" section |
165 |
| - if (service.package.include && service.package.include.length > 0) { |
166 |
| - const files = await globby(service.package.include) |
167 |
| - |
168 |
| - for (const filename of files) { |
169 |
| - const destFileName = path.resolve(path.join(BUILD_FOLDER, filename)) |
170 |
| - const dirname = path.dirname(destFileName) |
171 |
| - |
172 |
| - if (!fs.existsSync(dirname)) { |
173 |
| - fs.mkdirpSync(dirname) |
174 |
| - } |
175 |
| - |
176 |
| - if (!fs.existsSync(destFileName)) { |
177 |
| - fs.copySync(path.resolve(filename), path.resolve(path.join(BUILD_FOLDER, filename))) |
178 |
| - } |
179 |
| - } |
180 |
| - } |
181 |
| - } |
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 |
| - */ |
218 |
| - async moveArtifacts(): Promise<void> { |
219 |
| - const { service } = this.serverless |
220 |
| - |
221 |
| - await fs.copy( |
222 |
| - path.join(this.originalServicePath, BUILD_FOLDER, SERVERLESS_FOLDER), |
223 |
| - path.join(this.originalServicePath, SERVERLESS_FOLDER) |
224 |
| - ) |
225 |
| - |
226 |
| - if (this.options.function) { |
227 |
| - const fn = service.functions[this.options.function] |
228 |
| - fn.package.artifact = path.join( |
229 |
| - this.originalServicePath, |
230 |
| - SERVERLESS_FOLDER, |
231 |
| - path.basename(fn.package.artifact) |
232 |
| - ) |
233 |
| - return |
234 |
| - } |
235 |
| - |
236 |
| - if (service.package.individually) { |
237 |
| - const functionNames = service.getAllFunctions() |
238 |
| - functionNames.forEach(name => { |
239 |
| - service.functions[name].package.artifact = path.join( |
240 |
| - this.originalServicePath, |
241 |
| - SERVERLESS_FOLDER, |
242 |
| - path.basename(service.functions[name].package.artifact) |
243 |
| - ) |
244 |
| - }) |
245 |
| - return |
246 |
| - } |
247 |
| - |
248 |
| - service.package.artifact = path.join( |
249 |
| - this.originalServicePath, |
250 |
| - SERVERLESS_FOLDER, |
251 |
| - path.basename(service.package.artifact) |
252 |
| - ) |
253 |
| - } |
254 |
| - |
255 |
| - async cleanup(): Promise<void> { |
256 |
| - await this.moveArtifacts() |
257 |
| - // Restore service path |
258 |
| - this.serverless.config.servicePath = this.originalServicePath |
259 |
| - // Remove temp build folder |
260 |
| - fs.removeSync(path.join(this.originalServicePath, BUILD_FOLDER)) |
261 |
| - } |
262 |
| - |
263 |
| - /** |
264 |
| - * Attempt to symlink a given path or directory and copy if it fails with an |
265 |
| - * `EPERM` error. |
266 |
| - */ |
267 |
| - private async linkOrCopy(srcPath: string, dstPath: string, type?: fs.FsSymlinkType): Promise<void> { |
268 |
| - return fs.symlink(srcPath, dstPath, type) |
269 |
| - .catch(error => { |
270 |
| - if (error.code === 'EPERM' && error.errno === -4048) { |
271 |
| - return fs.copy(srcPath, dstPath) |
272 |
| - } |
273 |
| - throw error |
274 |
| - }) |
275 |
| - } |
276 |
| -} |
| 1 | +import {TypeScriptPlugin} from './typeScriptPlugin' |
277 | 2 |
|
278 | 3 | module.exports = TypeScriptPlugin
|
0 commit comments