1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 | const chalk = require('chalk');
|
13 | const glob = require('glob');
|
14 | const path = require('path');
|
15 | const fs = require('fs-extra');
|
16 | const ProgressBar = require('progress');
|
17 | const archiver = require('archiver');
|
18 | const AbstractCommand = require('./abstract.cmd.js');
|
19 | const BuildCommand = require('./build.cmd.js');
|
20 | const ActionBundler = require('./builder/ActionBundler.js');
|
21 |
|
22 |
|
23 |
|
24 |
|
25 | class PackageCommand extends AbstractCommand {
|
26 | constructor(logger) {
|
27 | super(logger);
|
28 | this._target = null;
|
29 | this._files = null;
|
30 | this._modulePaths = [];
|
31 | this._requiredModules = null;
|
32 | this._onlyModified = false;
|
33 | this._enableMinify = false;
|
34 | this._customPipeline = null;
|
35 | }
|
36 |
|
37 |
|
38 | get requireConfigFile() {
|
39 | return false;
|
40 | }
|
41 |
|
42 | withTarget(value) {
|
43 | this._target = value;
|
44 | return this;
|
45 | }
|
46 |
|
47 | withFiles(value) {
|
48 | this._files = value;
|
49 | return this;
|
50 | }
|
51 |
|
52 | withOnlyModified(value) {
|
53 | this._onlyModified = value;
|
54 | return this;
|
55 | }
|
56 |
|
57 | withMinify(value) {
|
58 | this._enableMinify = value;
|
59 | return this;
|
60 | }
|
61 |
|
62 | withModulePaths(value) {
|
63 | this._modulePaths = value;
|
64 | return this;
|
65 | }
|
66 |
|
67 | withRequiredModules(mods) {
|
68 | this._requiredModules = mods;
|
69 | return this;
|
70 | }
|
71 |
|
72 | withCustomPipeline(customPipeline) {
|
73 | this._customPipeline = customPipeline;
|
74 | return this;
|
75 | }
|
76 |
|
77 | async init() {
|
78 | await super.init();
|
79 | this._target = path.resolve(this.directory, this._target);
|
80 | }
|
81 |
|
82 | |
83 |
|
84 |
|
85 |
|
86 |
|
87 |
|
88 |
|
89 |
|
90 | async createPackage(info, bar) {
|
91 | const { log } = this;
|
92 |
|
93 | const tick = (message, name) => {
|
94 | const shortname = name.replace(/\/package.json.*/, '').replace(/node_modules\//, '');
|
95 | bar.tick({
|
96 | action: name ? `packaging ${shortname}` : '',
|
97 | });
|
98 | if (message) {
|
99 | this.log.infoFields(message, {
|
100 | progress: true,
|
101 | });
|
102 | }
|
103 | };
|
104 |
|
105 | return new Promise((resolve, reject) => {
|
106 | const archiveName = path.basename(info.zipFile);
|
107 | let hadErrors = false;
|
108 |
|
109 |
|
110 | const output = fs.createWriteStream(info.zipFile);
|
111 | const archive = archiver('zip');
|
112 |
|
113 | log.debug(`preparing package ${archiveName}`);
|
114 | output.on('close', () => {
|
115 | if (!hadErrors) {
|
116 | log.debug(`${archiveName}: Created package. ${archive.pointer()} total bytes`);
|
117 |
|
118 | info.archiveSize = archive.pointer();
|
119 | this.emit('create-package', info);
|
120 | resolve(info);
|
121 | }
|
122 | });
|
123 | archive.on('entry', (data) => {
|
124 | log.debug(`${archiveName}: A ${data.name}`);
|
125 | tick('', data.name);
|
126 | });
|
127 | archive.on('warning', (err) => {
|
128 | log.error(`${chalk.redBright('[error] ')}Unable to create archive: ${err.message}`);
|
129 | hadErrors = true;
|
130 | reject(err);
|
131 | });
|
132 | archive.on('error', (err) => {
|
133 | log.error(`${chalk.redBright('[error] ')}Unable to create archive: ${err.message}`);
|
134 | hadErrors = true;
|
135 | reject(err);
|
136 | });
|
137 | archive.pipe(output);
|
138 |
|
139 | const packageJson = {
|
140 | name: info.name,
|
141 | version: '1.0',
|
142 | description: `Lambda function of ${info.name}`,
|
143 | main: path.basename(info.main),
|
144 | license: 'Apache-2.0',
|
145 | };
|
146 |
|
147 | archive.append(JSON.stringify(packageJson, null, ' '), { name: 'package.json' });
|
148 | archive.file(info.bundlePath, { name: path.basename(info.main) });
|
149 | archive.finalize();
|
150 | });
|
151 | }
|
152 |
|
153 | |
154 |
|
155 |
|
156 |
|
157 |
|
158 |
|
159 |
|
160 |
|
161 | async createBundles(scripts, bar) {
|
162 | const progressHandler = (percent, msg, ...args) => {
|
163 |
|
164 | const action = args.length > 0 ? `${msg} ${args[0]}` : msg;
|
165 | const rt = bar.renderThrottle;
|
166 | if (msg !== 'building') {
|
167 |
|
168 | bar.renderThrottle = 0;
|
169 | }
|
170 | bar.update(percent * 0.8, { action });
|
171 | bar.renderThrottle = rt;
|
172 |
|
173 | };
|
174 |
|
175 | const bundler = new ActionBundler()
|
176 | .withDirectory(this._target)
|
177 | .withModulePaths(['node_modules', ...this._modulePaths, path.resolve(__dirname, '..', 'node_modules')])
|
178 | .withLogger(this.log)
|
179 | .withProgressHandler(progressHandler)
|
180 | .withMinify(this._enableMinify);
|
181 | const stats = await bundler.run(scripts);
|
182 | if (stats.errors) {
|
183 | stats.errors.forEach((msg) => this.log.error(msg));
|
184 | }
|
185 | if (stats.warnings) {
|
186 | stats.warnings.forEach((msg) => this.log.warn(msg));
|
187 | }
|
188 | if (stats.errors && stats.errors.length > 0) {
|
189 | throw new Error('Error while bundling packages.');
|
190 | }
|
191 | }
|
192 |
|
193 | |
194 |
|
195 |
|
196 | async run() {
|
197 | await this.init();
|
198 |
|
199 |
|
200 | const build = new BuildCommand(this.log)
|
201 | .withFiles(this._files)
|
202 | .withModulePaths(this._modulePaths)
|
203 | .withRequiredModules(this._requiredModules)
|
204 | .withCustomPipeline(this._customPipeline)
|
205 | .withDirectory(this.directory)
|
206 | .withTargetDir(this._target);
|
207 | await build.run();
|
208 | this._modulePaths = build.modulePaths;
|
209 |
|
210 |
|
211 | const infos = [...glob.sync(`${this._target}/**/*.info.json`)];
|
212 | let scripts = await Promise.all(infos.map((info) => fs.readJSON(info)));
|
213 |
|
214 |
|
215 | if (this._onlyModified) {
|
216 | await Promise.all(scripts.map(async (script) => {
|
217 |
|
218 | if (!script.zipFile || !(await fs.pathExists(script.zipFile))) {
|
219 |
|
220 | delete script.zipFile;
|
221 | }
|
222 | }));
|
223 | scripts.filter((script) => script.zipFile).forEach((script) => {
|
224 | this.emit('ignore-package', script);
|
225 | });
|
226 | scripts = scripts.filter((script) => !script.zipFile);
|
227 | }
|
228 |
|
229 | if (scripts.length > 0) {
|
230 |
|
231 | scripts.forEach((script) => {
|
232 |
|
233 | script.name = path.basename(script.main, '.js');
|
234 | script.bundleName = `${script.name}.bundle.js`;
|
235 | script.bundlePath = path.resolve(script.buildDir, script.bundleName);
|
236 | script.dirname = path.dirname(script.main);
|
237 | script.archiveName = `${script.name}.zip`;
|
238 | script.zipFile = path.resolve(script.buildDir, script.archiveName);
|
239 |
|
240 | });
|
241 |
|
242 |
|
243 | const bar = new ProgressBar('[:bar] :action :elapseds', {
|
244 | total: scripts.length * 2 * 5,
|
245 | width: 50,
|
246 | renderThrottle: 50,
|
247 | stream: process.stdout,
|
248 | });
|
249 |
|
250 |
|
251 | await this.createBundles(scripts, bar);
|
252 |
|
253 |
|
254 | await Promise.all(scripts.map((script) => this.createPackage(script, bar)));
|
255 |
|
256 |
|
257 |
|
258 | await Promise.all(scripts.map((script) => fs.writeJson(script.infoFile, script, { spaces: 2 })));
|
259 | }
|
260 |
|
261 | this.log.info('✅ packaging completed');
|
262 | return this;
|
263 | }
|
264 | }
|
265 | module.exports = PackageCommand;
|