1 | 'use strict';
|
2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
3 | return new (P || (P = Promise))(function (resolve, reject) {
|
4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
6 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
|
7 | step((generator = generator.apply(thisArg, _arguments || [])).next());
|
8 | });
|
9 | };
|
10 | const util = require('util');
|
11 | const path = require('path');
|
12 | const fs = require('fs');
|
13 | const os = require('os');
|
14 | const execSync = require('child_process').execSync;
|
15 | const httpx = require('httpx');
|
16 | const kitx = require('kitx');
|
17 | const exists = util.promisify(fs.exists);
|
18 | const writeFile = util.promisify(fs.writeFile);
|
19 | const readFile = util.promisify(fs.readFile);
|
20 | const debug = require('debug')('fun:deps');
|
21 | const archiver = require('archiver');
|
22 | function read(readable, encoding) {
|
23 | return new Promise((resolve, reject) => {
|
24 | var onData, onError, onEnd;
|
25 | var cleanup = function () {
|
26 |
|
27 | readable.removeListener('error', onError);
|
28 | readable.removeListener('data', onData);
|
29 | readable.removeListener('end', onEnd);
|
30 | };
|
31 | const bufs = [];
|
32 | var size = 0;
|
33 | onData = function (buf) {
|
34 | bufs.push(buf);
|
35 | size += buf.length;
|
36 | };
|
37 | onError = function (err) {
|
38 | cleanup();
|
39 | reject(err);
|
40 | };
|
41 | onEnd = function () {
|
42 | cleanup();
|
43 | var buff = Buffer.concat(bufs, size);
|
44 | if (encoding) {
|
45 | const result = buff.toString(encoding);
|
46 | return resolve(result);
|
47 | }
|
48 | resolve(buff);
|
49 | };
|
50 | readable.on('error', onError);
|
51 | readable.on('data', onData);
|
52 | readable.on('end', onEnd);
|
53 | });
|
54 | }
|
55 | const zip = function (rootDir) {
|
56 | const archive = archiver('zip', {
|
57 | zlib: { level: 9 }
|
58 | });
|
59 |
|
60 | archive.on('warning', function (err) {
|
61 | console.log(err);
|
62 | if (err.code === 'ENOENT') {
|
63 |
|
64 | }
|
65 | else {
|
66 |
|
67 | throw err;
|
68 | }
|
69 | });
|
70 | archive.on('error', (e) => console.log(e));
|
71 | if (debug.enabled) {
|
72 | archive.on('entry', function (entry) {
|
73 | debug('entry: %j', entry);
|
74 | });
|
75 | archive.on('progress', (p) => {
|
76 | console.log('progress');
|
77 | debug('progress: %j', p);
|
78 | });
|
79 | }
|
80 | archive.glob('node_modules/**', {
|
81 | cwd: rootDir
|
82 | });
|
83 | archive.finalize();
|
84 | return read(archive, 'base64');
|
85 | };
|
86 | function pkg(deps) {
|
87 | return `{
|
88 | "dependencies": ${JSON.stringify(deps)}
|
89 | }`;
|
90 | }
|
91 | function localBuild(data) {
|
92 | return __awaiter(this, void 0, void 0, function* () {
|
93 | const runtime = data.runtime;
|
94 | const deps = pkg(data.dependencies);
|
95 | const digest = kitx.md5(`${runtime}:${deps}`, 'hex');
|
96 | var dir = `${os.tmpdir()}/${digest}`;
|
97 | try {
|
98 | fs.mkdirSync(dir);
|
99 | }
|
100 | catch (ex) {
|
101 |
|
102 | }
|
103 | const pkgPath = path.join(dir, 'package.json');
|
104 | fs.writeFileSync(pkgPath, deps);
|
105 | execSync('npm i --registry=https://registry.npm.taobao.org', {
|
106 | cwd: dir
|
107 | });
|
108 | return zip(dir);
|
109 | });
|
110 | }
|
111 | function remoteBuild(data) {
|
112 | return __awaiter(this, void 0, void 0, function* () {
|
113 | const url = `http://59766a59fa0b4391b703b51d97230296-cn-hangzhou.alicloudapi.com/build/${data.runtime}`;
|
114 | const response = yield httpx.request(url, {
|
115 | method: 'POST',
|
116 | timeout: 60000,
|
117 | headers: {
|
118 | 'content-type': 'application/json'
|
119 | },
|
120 | data: JSON.stringify(data)
|
121 | });
|
122 | debug('%j', data);
|
123 | const statusCode = response.statusCode;
|
124 | var body = yield httpx.read(response, 'utf8');
|
125 | const headers = response.headers;
|
126 | const contentType = headers['content-type'] || '';
|
127 | if (contentType.startsWith('application/json')) {
|
128 | body = JSON.parse(body);
|
129 | }
|
130 | if (statusCode !== 200) {
|
131 | debug(response.headers);
|
132 | debug('statusCode: %s', statusCode);
|
133 | debug('build dependencies failed!');
|
134 | let err = new Error(`${headers['x-ca-error-message']},` +
|
135 | ` requestid: ${headers['x-ca-request-id']}`);
|
136 | err.name = 'BuildError';
|
137 | err.data = data;
|
138 | throw err;
|
139 | }
|
140 | if (!body.ok) {
|
141 | let err = new Error(`Build failed, ${body.message}`);
|
142 | err.name = 'BuildError';
|
143 | err.data = data;
|
144 | throw err;
|
145 | }
|
146 | return body.data.zip;
|
147 | });
|
148 | }
|
149 | function buildDeps(func, rootDir, type) {
|
150 | return __awaiter(this, void 0, void 0, function* () {
|
151 | const runtime = (func.runtime || 'nodejs4.4').replace('.', '_');
|
152 | const buildType = type || process.env.BUILD_TYPE || 'remote';
|
153 | if (buildType !== 'local' && buildType !== 'remote') {
|
154 | throw new TypeError(`BUILD_TYPE must be 'local' or 'remote'.`);
|
155 | }
|
156 |
|
157 | const pkgPath = path.join(rootDir, 'package.json');
|
158 | const hasPackageFile = yield exists(pkgPath);
|
159 | if (!hasPackageFile) {
|
160 | debug('The package.json inexists in Project, skipped.');
|
161 | return;
|
162 | }
|
163 | const pkg = require(pkgPath);
|
164 | const dependencies = pkg.dependencies || {};
|
165 | if (Object.keys(dependencies).length === 0) {
|
166 | debug('The package.json has not any dependencies in Project, skipped.');
|
167 | return;
|
168 | }
|
169 | const data = {
|
170 | runtime: runtime,
|
171 | dependencies: dependencies
|
172 | };
|
173 | const stringToHash = `${runtime}:${JSON.stringify(dependencies)}`;
|
174 | const hash = kitx.md5(stringToHash, 'hex');
|
175 | const zipPath = path.join(rootDir, `node_modules_${hash}.zip`);
|
176 | const md5Path = path.join(rootDir, `node_modules_${hash}.zip.md5`);
|
177 | const hasZip = yield exists(zipPath);
|
178 | const hasSign = yield exists(md5Path);
|
179 | if (hasZip && hasSign) {
|
180 | const zip = yield readFile(zipPath, 'base64');
|
181 | const sign = yield readFile(md5Path, 'utf8');
|
182 | if (sign === kitx.md5(zip, 'hex')) {
|
183 | debug('The node_modules pre-compressed, skipped.');
|
184 | return zip;
|
185 | }
|
186 | }
|
187 | var base64;
|
188 | if (buildType === 'remote') {
|
189 | debug('build deps remotely.');
|
190 | base64 = yield remoteBuild(data);
|
191 | }
|
192 | else {
|
193 | debug('build deps locally.');
|
194 | base64 = yield localBuild(data);
|
195 | }
|
196 | debug('build %j completed.', func);
|
197 | const digest = kitx.md5(base64, 'hex');
|
198 | yield writeFile(zipPath, base64, 'base64');
|
199 | yield writeFile(md5Path, digest);
|
200 | return base64;
|
201 | });
|
202 | }
|
203 | module.exports = { buildDeps, read };
|