-
-
Notifications
You must be signed in to change notification settings - Fork 137
/
pack.ts
168 lines (141 loc) · 5.98 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
import * as fs from 'fs-extra';
import * as globby from 'globby';
import * as path from 'path';
import {
intersection,
isEmpty,
lensProp,
map,
over,
pipe,
reject,
replace,
test,
without,
} from 'ramda';
import * as semver from 'semver';
import { EsbuildPlugin, SERVERLESS_FOLDER } from '.';
import { doSharePath, flatDep, getDepsFromBundle } from './helper';
import * as Packagers from './packagers';
import { IFiles } from './types';
import { humanSize, zip, trimExtension } from './utils';
function setFunctionArtifactPath(this: EsbuildPlugin, 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.serverless.cli.log(
`${func.name} is packaged by the esbuild plugin. Ignore messages from SLS.`
);
} else {
func.package = {
artifact: artifactPath,
};
}
}
const excludedFilesDefault = ['package-lock.json', 'yarn.lock', 'package.json'];
export async function pack(this: EsbuildPlugin) {
// 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)) {
throw new Error('Packaging: No files found');
}
// 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);
const { size } = fs.statSync(artifactPath);
this.serverless.cli.log(
`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);
// get a list of externals
const 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)
: {};
// package each function
await Promise.all(
buildResults.map(async ({ func, functionAlias, bundlePath }) => {
const name = `${this.serverless.service.getServiceName()}-${this.serverless.service.provider.stage}-${functionAlias}`;
const excludedFiles = bundlePathList.filter(p => !bundlePath.startsWith(p)).map(trimExtension);
// allowed external dependencies in the final zip
let depWhiteList = [];
if (hasExternals) {
const bundleDeps = getDepsFromBundle(path.join(this.buildDirPath, bundlePath));
const bundleExternals = intersection(bundleDeps, externals);
depWhiteList = flatDep(packagerDependenciesList.dependencies, bundleExternals);
}
const zipName = `${name}.zip`;
const artifactPath = path.join(this.workDirPath, SERVERLESS_FOLDER, zipName);
// filter files
const filesPathList = files
.filter(({ localPath }) => {
// 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_') && !localPath.startsWith(`__only_${name}/`))
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;
})
// remove prefix from individual function extra files
.map(({ localPath, ...rest }) => ({
localPath: localPath.replace(`__only_${name}/`, ''),
...rest,
}));
const startZip = Date.now();
await zip(artifactPath, filesPathList);
const { size } = fs.statSync(artifactPath);
this.serverless.cli.log(
`Zip function: ${func.name} - ${humanSize(size)} [${Date.now() - startZip} ms]`
);
// defined present zip as output artifact
setFunctionArtifactPath.call(this, func, path.relative(this.serviceDirPath, artifactPath));
})
);
}