-
-
Notifications
You must be signed in to change notification settings - Fork 141
/
Copy pathpack.ts
217 lines (184 loc) · 7.05 KB
/
pack.ts
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
import fs from 'fs-extra';
import globby from 'globby';
import path from 'path';
import {
intersection,
isEmpty,
lensProp,
map,
over,
pipe,
reject,
replace,
test,
without,
} from 'ramda';
import semver from 'semver';
import EsbuildServerlessPlugin from '.';
import { ONLY_PREFIX, SERVERLESS_FOLDER } from './constants';
import { doSharePath, flatDep, getDepsFromBundle } from './helper';
import * as Packagers from './packagers';
import { IFiles } from './types';
import { humanSize, zip, trimExtension } from './utils';
function setFunctionArtifactPath(this: EsbuildServerlessPlugin, func, artifactPath) {
const version = this.serverless.getVersion();
// Serverless changed the artifact path location in version 1.18
if (semver.lt(version, '1.18.0')) {
func.artifact = artifactPath;
func.package = Object.assign({}, func.package, { disable: true });
this.log.verbose(`${func.name} is packaged by the esbuild plugin. Ignore messages from SLS.`);
} else {
func.package = {
artifact: artifactPath,
};
}
}
const excludedFilesDefault = ['package-lock.json', 'pnpm-lock.yaml', 'yarn.lock', 'package.json'];
export const filterFilesForZipPackage = ({
files,
functionAlias,
includedFiles,
excludedFiles,
hasExternals,
isGoogleProvider,
depWhiteList,
}: {
files: IFiles;
functionAlias: string;
includedFiles: string[];
excludedFiles: string[];
hasExternals: boolean;
isGoogleProvider: boolean;
depWhiteList: string[];
}) => {
return files.filter(({ localPath }) => {
// if file is present in patterns it must be included
if (includedFiles.find((file) => file === localPath)) {
return true;
}
// exclude non individual files based on file path (and things that look derived, e.g. foo.js => foo.js.map)
if (excludedFiles.find((p) => localPath.startsWith(`${p}.`))) return false;
// exclude files that belong to individual functions
if (
localPath.startsWith(ONLY_PREFIX) &&
!localPath.startsWith(`${ONLY_PREFIX}${functionAlias}/`)
)
return false;
// exclude non whitelisted dependencies
if (localPath.startsWith('node_modules')) {
// if no externals is set or if the provider is google, we do not need any files from node_modules
if (!hasExternals || isGoogleProvider) return false;
if (
// this is needed for dependencies that maps to a path (like scoped ones)
!depWhiteList.find((dep) => doSharePath(localPath, 'node_modules/' + dep))
)
return false;
}
return true;
});
};
export async function pack(this: EsbuildServerlessPlugin) {
// GOOGLE Provider requires a package.json and NO node_modules
const isGoogleProvider = this.serverless?.service?.provider?.name === 'google';
const excludedFiles = isGoogleProvider ? [] : excludedFilesDefault;
// Google provider cannot use individual packaging for now - this could be built in a future release
if (isGoogleProvider && this.serverless?.service?.package?.individually)
throw new Error(
'Packaging failed: cannot package function individually when using Google provider'
);
// get a list of all path in build
const files: IFiles = globby
.sync('**', {
cwd: this.buildDirPath,
dot: true,
onlyFiles: true,
})
.filter((p) => !excludedFiles.includes(p))
.map((localPath) => ({ localPath, rootPath: path.join(this.buildDirPath, localPath) }));
if (isEmpty(files)) {
console.log('Packaging: No files found. Skipping esbuild.');
return;
}
// 1) If individually is not set, just zip the all build dir and return
if (!this.serverless?.service?.package?.individually) {
const zipName = `${this.serverless.service.service}.zip`;
const artifactPath = path.join(this.workDirPath, SERVERLESS_FOLDER, zipName);
// remove prefixes from individual extra files
const filesPathList = pipe<IFiles, IFiles, IFiles>(
reject(test(/^__only_[^/]+$/)) as (x: IFiles) => IFiles,
map(over(lensProp('localPath'), replace(/^__only_[^/]+\//, '')))
)(files);
const startZip = Date.now();
await zip(artifactPath, filesPathList, this.buildOptions.nativeZip);
const { size } = fs.statSync(artifactPath);
this.log.verbose(
`Zip service ${this.serverless.service.service} - ${humanSize(size)} [${
Date.now() - startZip
} ms]`
);
// defined present zip as output artifact
this.serverless.service.package.artifact = artifactPath;
return;
}
// 2) If individually is set, we'll optimize files and zip per-function
const packager = await Packagers.get(this.buildOptions.packager);
// get a list of every function bundle
const buildResults = this.buildResults;
const bundlePathList = buildResults.map((b) => b.bundlePath);
let externals = [];
// get the list of externals to include only if exclude is not set to *
if (this.buildOptions.exclude !== '*' && !this.buildOptions.exclude.includes('*')) {
externals = without<string>(this.buildOptions.exclude, this.buildOptions.external);
}
const hasExternals = !!externals?.length;
// get a tree of all production dependencies
const packagerDependenciesList = hasExternals
? await packager.getProdDependencies(this.buildDirPath)
: {};
const packageFiles = await globby(this.serverless.service.package.patterns);
// package each function
await Promise.all(
buildResults.map(async ({ func, functionAlias, bundlePath }) => {
const excludedFiles = bundlePathList
.filter((p) => !bundlePath.startsWith(p))
.map(trimExtension);
const functionFiles = await globby(func.package.patterns);
const includedFiles = [...packageFiles, ...functionFiles];
// allowed external dependencies in the final zip
let depWhiteList = [];
if (hasExternals) {
const bundleDeps = getDepsFromBundle(
path.join(this.buildDirPath, bundlePath),
this.buildOptions.platform
);
const bundleExternals = intersection(bundleDeps, externals);
depWhiteList = flatDep(packagerDependenciesList.dependencies, bundleExternals);
}
const zipName = `${functionAlias}.zip`;
const artifactPath = path.join(this.workDirPath, SERVERLESS_FOLDER, zipName);
// filter files
const filesPathList = filterFilesForZipPackage({
files,
functionAlias,
includedFiles,
excludedFiles,
hasExternals,
isGoogleProvider,
depWhiteList,
})
// remove prefix from individual function extra files
.map(({ localPath, ...rest }) => ({
localPath: localPath.replace(`${ONLY_PREFIX}${functionAlias}/`, ''),
...rest,
}));
const startZip = Date.now();
await zip(artifactPath, filesPathList, this.buildOptions.nativeZip);
const { size } = fs.statSync(artifactPath);
this.log.verbose(
`Zip function: ${functionAlias} - ${humanSize(size)} [${Date.now() - startZip} ms]`
);
// defined present zip as output artifact
setFunctionArtifactPath.call(this, func, path.relative(this.serviceDirPath, artifactPath));
})
);
}