forked from aws/aws-lambda-builders
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathactions.py
366 lines (266 loc) · 12.5 KB
/
actions.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
"""
Action to resolve NodeJS dependencies using NPM
"""
import logging
from aws_lambda_builders.actions import BaseAction, Purpose, ActionFailedError
from .npm import NpmExecutionError
from .esbuild import EsbuildExecutionError
LOG = logging.getLogger(__name__)
class NodejsNpmPackAction(BaseAction):
"""
A Lambda Builder Action that packages a Node.js package using NPM to extract the source and remove test resources
"""
NAME = "NpmPack"
DESCRIPTION = "Packaging source using NPM"
PURPOSE = Purpose.COPY_SOURCE
def __init__(self, artifacts_dir, scratch_dir, manifest_path, osutils, subprocess_npm):
"""
:type artifacts_dir: str
:param artifacts_dir: an existing (writable) directory where to store the output.
Note that the actual result will be in the 'package' subdirectory here.
:type scratch_dir: str
:param scratch_dir: an existing (writable) directory for temporary files
:type manifest_path: str
:param manifest_path: path to package.json of an NPM project with the source to pack
:type osutils: aws_lambda_builders.workflows.nodejs_npm.utils.OSUtils
:param osutils: An instance of OS Utilities for file manipulation
:type subprocess_npm: aws_lambda_builders.workflows.nodejs_npm.npm.SubprocessNpm
:param subprocess_npm: An instance of the NPM process wrapper
"""
super(NodejsNpmPackAction, self).__init__()
self.artifacts_dir = artifacts_dir
self.manifest_path = manifest_path
self.scratch_dir = scratch_dir
self.osutils = osutils
self.subprocess_npm = subprocess_npm
def execute(self):
"""
Runs the action.
:raises lambda_builders.actions.ActionFailedError: when NPM packaging fails
"""
try:
package_path = "file:{}".format(self.osutils.abspath(self.osutils.dirname(self.manifest_path)))
LOG.debug("NODEJS packaging %s to %s", package_path, self.scratch_dir)
tarfile_name = self.subprocess_npm.run(["pack", "-q", package_path], cwd=self.scratch_dir).splitlines()[-1]
LOG.debug("NODEJS packed to %s", tarfile_name)
tarfile_path = self.osutils.joinpath(self.scratch_dir, tarfile_name)
LOG.debug("NODEJS extracting to %s", self.artifacts_dir)
self.osutils.extract_tarfile(tarfile_path, self.artifacts_dir)
except NpmExecutionError as ex:
raise ActionFailedError(str(ex))
class NodejsNpmInstallAction(BaseAction):
"""
A Lambda Builder Action that installs NPM project dependencies
"""
NAME = "NpmInstall"
DESCRIPTION = "Installing dependencies from NPM"
PURPOSE = Purpose.RESOLVE_DEPENDENCIES
def __init__(self, artifacts_dir, subprocess_npm, is_production=True):
"""
:type artifacts_dir: str
:param artifacts_dir: an existing (writable) directory with project source files.
Dependencies will be installed in this directory.
:type subprocess_npm: aws_lambda_builders.workflows.nodejs_npm.npm.SubprocessNpm
:param subprocess_npm: An instance of the NPM process wrapper
:type is_production: bool
:param is_production: NPM installation mode is production (eg --production=false to force dev dependencies)
"""
super(NodejsNpmInstallAction, self).__init__()
self.artifacts_dir = artifacts_dir
self.subprocess_npm = subprocess_npm
self.is_production = is_production
def execute(self):
"""
Runs the action.
:raises lambda_builders.actions.ActionFailedError: when NPM execution fails
"""
mode = "--production" if self.is_production else "--production=false"
try:
LOG.debug("NODEJS installing in: %s", self.artifacts_dir)
self.subprocess_npm.run(
["install", "-q", "--no-audit", "--no-save", mode, "--unsafe-perm"], cwd=self.artifacts_dir
)
except NpmExecutionError as ex:
raise ActionFailedError(str(ex))
class NodejsNpmCIAction(BaseAction):
"""
A Lambda Builder Action that installs NPM project dependencies
using the CI method - which is faster and better reproducible
for CI environments, but requires a lockfile (package-lock.json
or npm-shrinkwrap.json)
"""
NAME = "NpmCI"
DESCRIPTION = "Installing dependencies from NPM using the CI method"
PURPOSE = Purpose.RESOLVE_DEPENDENCIES
def __init__(self, artifacts_dir, subprocess_npm):
"""
:type artifacts_dir: str
:param artifacts_dir: an existing (writable) directory with project source files.
Dependencies will be installed in this directory.
:type subprocess_npm: aws_lambda_builders.workflows.nodejs_npm.npm.SubprocessNpm
:param subprocess_npm: An instance of the NPM process wrapper
"""
super(NodejsNpmCIAction, self).__init__()
self.artifacts_dir = artifacts_dir
self.subprocess_npm = subprocess_npm
def execute(self):
"""
Runs the action.
:raises lambda_builders.actions.ActionFailedError: when NPM execution fails
"""
try:
LOG.debug("NODEJS installing ci in: %s", self.artifacts_dir)
self.subprocess_npm.run(["ci"], cwd=self.artifacts_dir)
except NpmExecutionError as ex:
raise ActionFailedError(str(ex))
class NodejsNpmrcAndLockfileCopyAction(BaseAction):
"""
A Lambda Builder Action that copies lockfile and NPM config file .npmrc
"""
NAME = "CopyNpmrcAndLockfile"
DESCRIPTION = "Copying configuration from .npmrc and dependencies from lockfile"
PURPOSE = Purpose.COPY_SOURCE
def __init__(self, artifacts_dir, source_dir, osutils):
"""
:type artifacts_dir: str
:param artifacts_dir: an existing (writable) directory with project source files.
Dependencies will be installed in this directory.
:type source_dir: str
:param source_dir: directory containing project source files.
:type osutils: aws_lambda_builders.workflows.nodejs_npm.utils.OSUtils
:param osutils: An instance of OS Utilities for file manipulation
"""
super(NodejsNpmrcAndLockfileCopyAction, self).__init__()
self.artifacts_dir = artifacts_dir
self.source_dir = source_dir
self.osutils = osutils
def execute(self):
"""
Runs the action.
:raises lambda_builders.actions.ActionFailedError: when copying fails
"""
try:
for filename in [".npmrc", "package-lock.json"]:
file_path = self.osutils.joinpath(self.source_dir, filename)
if self.osutils.file_exists(file_path):
LOG.debug("%s copying in: %s", filename, self.artifacts_dir)
self.osutils.copy_file(file_path, self.artifacts_dir)
except OSError as ex:
raise ActionFailedError(str(ex))
class NodejsNpmrcCleanUpAction(BaseAction):
"""
A Lambda Builder Action that cleans NPM config file .npmrc
"""
NAME = "CleanUpNpmrc"
DESCRIPTION = "Cleans artifacts dir"
PURPOSE = Purpose.COPY_SOURCE
def __init__(self, artifacts_dir, osutils):
"""
:type artifacts_dir: str
:param artifacts_dir: an existing (writable) directory with project source files.
Dependencies will be installed in this directory.
:type osutils: aws_lambda_builders.workflows.nodejs_npm.utils.OSUtils
:param osutils: An instance of OS Utilities for file manipulation
"""
super(NodejsNpmrcCleanUpAction, self).__init__()
self.artifacts_dir = artifacts_dir
self.osutils = osutils
def execute(self):
"""
Runs the action.
:raises lambda_builders.actions.ActionFailedError: when deleting .npmrc fails
"""
try:
npmrc_path = self.osutils.joinpath(self.artifacts_dir, ".npmrc")
if self.osutils.file_exists(npmrc_path):
LOG.debug(".npmrc cleanup in: %s", self.artifacts_dir)
self.osutils.remove_file(npmrc_path)
except OSError as ex:
raise ActionFailedError(str(ex))
class NodejsNpmLockFileCleanUpAction(BaseAction):
"""
A Lambda Builder Action that cleans up garbage lockfile left by 7 in node_modules
"""
NAME = "LockfileCleanUp"
DESCRIPTION = "Cleans garbage lockfiles dir"
PURPOSE = Purpose.COPY_SOURCE
def __init__(self, artifacts_dir, osutils):
"""
:type artifacts_dir: str
:param artifacts_dir: an existing (writable) directory with project source files.
Dependencies will be installed in this directory.
:type osutils: aws_lambda_builders.workflows.nodejs_npm.utils.OSUtils
:param osutils: An instance of OS Utilities for file manipulation
"""
super(NodejsNpmLockFileCleanUpAction, self).__init__()
self.artifacts_dir = artifacts_dir
self.osutils = osutils
def execute(self):
"""
Runs the action.
:raises lambda_builders.actions.ActionFailedError: when deleting the lockfile fails
"""
try:
npmrc_path = self.osutils.joinpath(self.artifacts_dir, "node_modules", ".package-lock.json")
if self.osutils.file_exists(npmrc_path):
LOG.debug(".package-lock cleanup in: %s", self.artifacts_dir)
self.osutils.remove_file(npmrc_path)
except OSError as ex:
raise ActionFailedError(str(ex))
class EsbuildBundleAction(BaseAction):
"""
A Lambda Builder Action that packages a Node.js package using esbuild into a single file
optionally transpiling TypeScript
"""
NAME = "EsbuildBundle"
DESCRIPTION = "Packaging source using Esbuild"
PURPOSE = Purpose.COPY_SOURCE
def __init__(self, source_dir, artifacts_dir, bundler_config, osutils, subprocess_esbuild):
"""
:type source_dir: str
:param source_dir: an existing (readable) directory containing source files
:type artifacts_dir: str
:param artifacts_dir: an existing (writable) directory where to store the output.
Note that the actual result will be in the 'package' subdirectory here.
:type osutils: aws_lambda_builders.workflows.nodejs_npm.utils.OSUtils
:param osutils: An instance of OS Utilities for file manipulation
:type subprocess_esbuild: aws_lambda_builders.workflows.nodejs_npm.npm.SubprocessEsbuild
:param subprocess_esbuild: An instance of the Esbuild process wrapper
"""
super(EsbuildBundleAction, self).__init__()
self.source_dir = source_dir
self.artifacts_dir = artifacts_dir
self.bundler_config = bundler_config
self.osutils = osutils
self.subprocess_esbuild = subprocess_esbuild
def execute(self):
"""
Runs the action.
:raises lambda_builders.actions.ActionFailedError: when esbuild packaging fails
"""
if "entry_points" not in self.bundler_config:
raise ActionFailedError("entry_points not set ({})".format(self.bundler_config))
entry_points = self.bundler_config["entry_points"]
if not isinstance(entry_points, list):
raise ActionFailedError("entry_points must be a list ({})".format(self.bundler_config))
if not entry_points:
raise ActionFailedError("entry_points must not be empty ({})".format(self.bundler_config))
entry_paths = [self.osutils.joinpath(self.source_dir, entry_point) for entry_point in entry_points]
LOG.debug("NODEJS building %s using esbuild to %s", entry_paths, self.artifacts_dir)
for entry_point in entry_paths:
if not self.osutils.file_exists(entry_point):
raise ActionFailedError("entry point {} does not exist".format(entry_point))
args = entry_points + ["--bundle", "--platform=node", "--format=cjs"]
minify = self.bundler_config.get("minify", True)
sourcemap = self.bundler_config.get("sourcemap", True)
target = self.bundler_config.get("target", "es2020")
if minify:
args.append("--minify")
if sourcemap:
args.append("--sourcemap")
args.append("--target={}".format(target))
args.append("--outdir={}".format(self.artifacts_dir))
try:
self.subprocess_esbuild.run(args, cwd=self.source_dir)
except EsbuildExecutionError as ex:
raise ActionFailedError(str(ex))