UNPKG

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