-
Notifications
You must be signed in to change notification settings - Fork 140
/
workflow.py
202 lines (171 loc) · 8.88 KB
/
workflow.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
"""
NodeJS NPM Workflow using the esbuild bundler
"""
import json
import logging
import os
from pathlib import Path
from aws_lambda_builders.actions import (
CleanUpAction,
CopySourceAction,
LinkSinglePathAction,
LinkSourceAction,
MoveDependenciesAction,
)
from aws_lambda_builders.path_resolver import PathResolver
from aws_lambda_builders.utils import which
from aws_lambda_builders.workflow import BaseWorkflow, BuildDirectory, BuildInSourceSupport, Capability
from aws_lambda_builders.workflows.nodejs_npm import NodejsNpmWorkflow
from aws_lambda_builders.workflows.nodejs_npm.npm import SubprocessNpm
from aws_lambda_builders.workflows.nodejs_npm.utils import OSUtils
from aws_lambda_builders.workflows.nodejs_npm.workflow import UNSUPPORTED_NPM_VERSION_MESSAGE
from aws_lambda_builders.workflows.nodejs_npm_esbuild.actions import (
EsbuildBundleAction,
)
from aws_lambda_builders.workflows.nodejs_npm_esbuild.esbuild import EsbuildExecutionError, SubprocessEsbuild
LOG = logging.getLogger(__name__)
class NodejsNpmEsbuildWorkflow(BaseWorkflow):
"""
A Lambda builder workflow that uses esbuild to bundle Node.js and transpile TS
NodeJS projects using NPM with esbuild.
"""
NAME = "NodejsNpmEsbuildBuilder"
CAPABILITY = Capability(language="nodejs", dependency_manager="npm-esbuild", application_framework=None)
EXCLUDED_FILES = (".aws-sam", ".git", "node_modules")
CONFIG_PROPERTY = "aws_sam"
DEFAULT_BUILD_DIR = BuildDirectory.SCRATCH
BUILD_IN_SOURCE_SUPPORT = BuildInSourceSupport.OPTIONALLY_SUPPORTED
def __init__(self, source_dir, artifacts_dir, scratch_dir, manifest_path, runtime=None, osutils=None, **kwargs):
super(NodejsNpmEsbuildWorkflow, self).__init__(
source_dir, artifacts_dir, scratch_dir, manifest_path, runtime=runtime, **kwargs
)
self.osutils = osutils or OSUtils()
self.subprocess_npm = SubprocessNpm(self.osutils)
self.subprocess_esbuild = self._get_esbuild_subprocess()
self.manifest_dir = self.osutils.dirname(self.manifest_path)
is_building_in_source = self.build_dir == self.source_dir
is_external_manifest = self.manifest_dir != self.source_dir
bundler_config = self.get_build_properties()
if not self.osutils.file_exists(manifest_path):
LOG.warning("package.json file not found. Bundling source without installing dependencies.")
self.actions = [
EsbuildBundleAction(
working_directory=self.source_dir,
output_directory=self.artifacts_dir,
bundler_config=bundler_config,
osutils=self.osutils,
subprocess_esbuild=self.subprocess_esbuild,
manifest=self.manifest_path,
)
]
return
if not self.download_dependencies and not self.dependencies_dir:
# Invalid workflow, can't have no dependency dir and no installation
raise EsbuildExecutionError(
message="Lambda Builders was unable to find the location of the dependencies since a "
"dependencies directory was not provided and downloading dependencies is disabled."
)
# if we're building in the source directory, we don't have to copy the source code
self.actions = (
[]
if is_building_in_source
else [CopySourceAction(source_dir=self.source_dir, dest_dir=self.build_dir, excludes=self.EXCLUDED_FILES)]
)
if is_external_manifest and not is_building_in_source:
# copy the manifest file (package.json) to the build directory in case if the manifest file is not in the
# same directory as the source code, and customer is not building in source.
self.actions.append(
CopySourceAction(source_dir=self.manifest_dir, dest_dir=self.build_dir, excludes=self.EXCLUDED_FILES)
)
if self.download_dependencies:
if is_building_in_source and not NodejsNpmWorkflow.can_use_install_links(self.subprocess_npm):
LOG.warning(UNSUPPORTED_NPM_VERSION_MESSAGE)
is_building_in_source = False
self.build_dir = self._select_build_dir(build_in_source=False)
self.actions.append(
NodejsNpmWorkflow.get_install_action(
source_dir=source_dir,
# run npm install in the directory where the manifest (package.json) exists if customer is building
# in source, and manifest directory is different from source.
# This will let NPM find the local dependencies that are defined in the manifest file (they are
# usually defined as relative to the manifest location, and that is why we run `npm install` in the
# manifest directory instead of source directory).
# If customer is not building in source, so it is ok to run `npm install` in the build
# directory (the artifacts directory in this case), as the local dependencies are not supported.
install_dir=self.manifest_dir if is_building_in_source and is_external_manifest else self.build_dir,
subprocess_npm=self.subprocess_npm,
osutils=self.osutils,
build_options=self.options,
install_links=is_building_in_source,
)
)
if is_building_in_source and is_external_manifest:
# Since we run `npm install` in the manifest directory, so we need to link the node_modules directory in
# the source directory.
source_dependencies_path = os.path.join(self.source_dir, "node_modules")
manifest_dependencies_path = os.path.join(self.manifest_dir, "node_modules")
self.actions.append(
LinkSinglePathAction(source=manifest_dependencies_path, dest=source_dependencies_path)
)
bundle_action = EsbuildBundleAction(
working_directory=self.build_dir,
output_directory=self.artifacts_dir,
bundler_config=bundler_config,
osutils=self.osutils,
subprocess_esbuild=self.subprocess_esbuild,
manifest=self.manifest_path,
skip_deps=not self.combine_dependencies,
)
# If there's no dependencies_dir, just bundle and we're done.
# Same thing if we're building in the source directory (since the dependencies persist in
# the source directory, we don't want to move them or symlink them back to the source)
if not self.dependencies_dir or is_building_in_source:
self.actions.append(bundle_action)
return
if self.download_dependencies:
# if we downloaded dependencies, bundle and update dependencies_dir
self.actions += [
bundle_action,
CleanUpAction(self.dependencies_dir),
MoveDependenciesAction(self.source_dir, self.scratch_dir, self.dependencies_dir, self.manifest_dir),
]
else:
# if we're reusing dependencies, then we need to symlink them before bundling
self.actions += [
LinkSourceAction(self.dependencies_dir, self.scratch_dir),
bundle_action,
]
def get_build_properties(self):
"""
Get the aws_sam specific properties from the manifest, if they exist.
Returns
-------
dict
aws_sam specific bundler configs
"""
if self.options and isinstance(self.options, dict):
LOG.debug("Lambda Builders found the following esbuild properties:\n%s", json.dumps(self.options))
return self.options
return {}
def get_resolvers(self):
"""
specialized path resolver that just returns the list of executable for the runtime on the path.
"""
return [PathResolver(runtime=self.runtime, binary="npm")]
def _get_esbuild_subprocess(self) -> SubprocessEsbuild:
"""
Creates a subprocess object that is able to invoke the esbuild executable.
Returns
-------
SubprocessEsbuild
An esbuild specific subprocess object
"""
try:
npm_bin_path_root = self.subprocess_npm.run(["root"], cwd=self.build_dir)
npm_bin_path = str(Path(npm_bin_path_root, ".bin"))
except FileNotFoundError:
raise EsbuildExecutionError(message="The esbuild workflow couldn't find npm installed on your system.")
executable_search_paths = [npm_bin_path]
if self.executable_search_paths is not None:
executable_search_paths = executable_search_paths + self.executable_search_paths
return SubprocessEsbuild(self.osutils, executable_search_paths, which=which)