1 | const fs = require('fs');
|
2 | const fse = require("fs-extra");
|
3 | const path = require('path');
|
4 | const utils = require('../utils');
|
5 | const logger = require('../utils/logger');
|
6 | const inquirer = require('inquirer');
|
7 | const rimraf = require('rimraf');
|
8 | const shelljs = require('shelljs');
|
9 | const ora = require('ora');
|
10 | const decompress = require('decompress');
|
11 | const tmp = require('tmp');
|
12 | const request = require('request');
|
13 | const isWin = /^win/.test(process.platform);
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 | function add(op) {
|
20 | let outputPath = path.resolve(op.rootDir, 'plugins', op.name);
|
21 | if (fs.existsSync(outputPath)) {
|
22 | inquirer.prompt([{
|
23 | type: 'confirm',
|
24 | message: `已存在名为${op.name}的插件,是否覆盖安装?`,
|
25 | name: 'ok'
|
26 | }]).then(answers => {
|
27 | if (answers.ok) {
|
28 | rimraf(outputPath, () => {
|
29 | download(op)
|
30 | })
|
31 | } else {
|
32 | logger.error(`放弃安装${op.name}!`);
|
33 | typeof op.callback === 'function' && op.callback(op);
|
34 | }
|
35 | }).catch(console.error);
|
36 | } else {
|
37 | download(op)
|
38 | }
|
39 | }
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 | function remove(op) {
|
46 | changeSetting(op, false);
|
47 | changeGradle(op, false);
|
48 | cleanIdea(op);
|
49 | invokeAndroid(op, () => {
|
50 | changeProfile(op, false);
|
51 | invokeIos(op, () => {
|
52 | utils.pluginsJson(false, op);
|
53 | logger.success('插件' + op.name + '移除完毕!');
|
54 | typeof op.callback === 'function' && op.callback(op);
|
55 | request(op.baseUrl + op.name + '?act=uninstall', () => {});
|
56 | });
|
57 | });
|
58 | }
|
59 |
|
60 | function download(op) {
|
61 | let outputPath = path.resolve(op.rootDir, 'plugins', op.name);
|
62 | let downPath = tmp.tmpNameSync({dir: require('os').tmpdir()}) + ".zip";
|
63 | let downUrl = null;
|
64 | let downUrls = [];
|
65 | let startDownload = () => {
|
66 | let writeStream = fs.createWriteStream(downPath);
|
67 | writeStream.on("close", () => {
|
68 | decompress(downPath, outputPath).then(() => {
|
69 | fs.unlinkSync(downPath);
|
70 | utils.removeRubbish(outputPath);
|
71 | utils.onlyOneDirMoveToParent(outputPath);
|
72 |
|
73 | changeSetting(op, true);
|
74 | changeGradle(op, true);
|
75 | cleanIdea(op);
|
76 | invokeAndroid(op, () => {
|
77 | changeProfile(op, true);
|
78 | invokeIos(op, () => {
|
79 | let configFile = path.resolve(outputPath, 'config.json');
|
80 | let configInfo = utils.jsonParse(!fs.existsSync(configFile) ? {} : fs.readFileSync(configFile, 'utf8'));
|
81 | op.requireName = configInfo.requireName;
|
82 | op.url = downUrl;
|
83 | utils.pluginsJson(true, op);
|
84 | logger.success('插件' + op.name + '添加成功!');
|
85 | typeof op.callback === 'function' && op.callback(op);
|
86 | request(op.baseUrl + op.name + '?act=install', () => {});
|
87 | });
|
88 | });
|
89 | }).catch((err) => {
|
90 | if (downUrls.length > 0) {
|
91 | logger.warn(`资源似乎出了点问题...`);
|
92 | logger.info(`正在重新尝试...`);
|
93 | downUrl = downUrls[0];
|
94 | downUrls = downUrls.filter(t => t !== downUrl);
|
95 | setTimeout(startDownload, 1000);
|
96 | } else {
|
97 | logger.fatal(`插件${op.name}添加失败: ${err}!`);
|
98 | }
|
99 | });
|
100 | }).on("error", (err) => {
|
101 | if (downUrls.length > 0) {
|
102 | op.__endExit = true;
|
103 | logger.warn(`连接似乎出了点问题...`);
|
104 | logger.info(`正在重新尝试...`);
|
105 | downUrl = downUrls[0];
|
106 | downUrls = downUrls.filter(t => t !== downUrl);
|
107 | setTimeout(startDownload, 1000);
|
108 | } else {
|
109 | logger.fatal(`插件${op.name}下载失败: ${err}!`);
|
110 | }
|
111 | });
|
112 |
|
113 | let receivedBytes = 0;
|
114 | let totalBytes = 0;
|
115 | let speedBytes = 0;
|
116 | let speedPer = "0B/S";
|
117 | let speedInt = setInterval(() => {
|
118 | speedPer = utils.renderSize(Math.max(0, receivedBytes - speedBytes)) + "/S";
|
119 | speedBytes = receivedBytes;
|
120 | }, 1000);
|
121 | let spinText = '插件' + op.name + '正在下载...';
|
122 | let spinFetch = ora(spinText);
|
123 | spinFetch.start();
|
124 | request.get(downUrl).on("error", function (err) {
|
125 | spinFetch.stop();
|
126 | writeStream.emit('error', err);
|
127 | }).on("response", function (res) {
|
128 | if (res.statusCode !== 200) {
|
129 | logger.fatal(`插件${op.name}下载失败: Get zipUrl return a non-200 response!`);
|
130 | }
|
131 | totalBytes = parseInt(res.headers['content-length'], 10);
|
132 | if (isNaN(totalBytes)) totalBytes = 0;
|
133 | }).on('data', (chunk) => {
|
134 | receivedBytes += chunk.length;
|
135 | let progress = "0%";
|
136 | if (totalBytes > 0) {
|
137 | progress = parseFloat(Math.max(0, receivedBytes / totalBytes * 100).toFixed(2)) + "%";
|
138 | } else {
|
139 | progress = utils.renderSize(receivedBytes);
|
140 | }
|
141 | spinFetch.text = spinText + `(${progress}, ${speedPer})`;
|
142 | }).on("end", function () {
|
143 | clearInterval(speedInt);
|
144 | spinFetch.stop();
|
145 | logger.info('插件' + op.name + '下载成功,开始添加...');
|
146 | }).pipe(writeStream);
|
147 | };
|
148 |
|
149 | if (utils.count(op.fileinfo) <= 0) {
|
150 | logger.fatal(`插件${op.name}没有下载地址!`);
|
151 | }
|
152 | if (utils.count(op.fileinfo) === 1 || op.simple === true) {
|
153 | downUrl = op.fileinfo[0]['path'];
|
154 | downUrls = (utils.likeArray(op.fileinfo[0]['paths']) ? op.fileinfo[0]['paths'] : []).filter(t => t !== downUrl);
|
155 | logger.info('开始添加插件');
|
156 | startDownload();
|
157 | return;
|
158 | }
|
159 | let lists = [];
|
160 | op.fileinfo.forEach(t => {
|
161 | let name = t.name;
|
162 | if (name.substr(-4, 4) === '.zip') name = name.substr(0, name.length - 4);
|
163 | lists.push({
|
164 | name: (lists.length + 1) + ". " + name + (t.desc ? " (" + t.desc + ")" : ""),
|
165 | value: t
|
166 | });
|
167 | });
|
168 | let array = [{
|
169 | type: 'list',
|
170 | name: 'release',
|
171 | message: `选择插件${op.name}版本:`,
|
172 | choices: lists
|
173 | }];
|
174 | inquirer.prompt(array).then(function(answers) {
|
175 | downUrl = answers.release.path;
|
176 | downUrls = (utils.likeArray(answers.release['paths']) ? answers.release['paths'] : []).filter(t => t !== downUrl);
|
177 | logger.info('开始添加插件');
|
178 | startDownload();
|
179 | });
|
180 | }
|
181 |
|
182 | function changeSetting(op, isInstall) {
|
183 | let gradleName = findGradleName(op);
|
184 | if (utils.count(gradleName) === 0) {
|
185 | return;
|
186 | }
|
187 | let tmpPath = path.resolve(op.rootDir, 'platforms/android/eeuiApp/settings.gradle');
|
188 | let result = fs.readFileSync(tmpPath, 'utf8');
|
189 | let temp = result.split('\n');
|
190 | if (new RegExp("include\\s*('|\"):" + utils.spritUpperCase(op.name) + "\\1", "g").test(temp[0])) {
|
191 | logger.fatal(utils.spritUpperCase(op.name) + '与项目同名,无法安装!');
|
192 | }
|
193 | let out = [];
|
194 | for (let t in temp) {
|
195 | if (temp.hasOwnProperty(t)) {
|
196 | if (temp[t].indexOf(`":${utils.spritUpperCase(op.name)}"`) === -1 && temp[t]) {
|
197 | if (temp[t].indexOf('include ') === 0) {
|
198 | out.push('');
|
199 | }
|
200 | out.push(temp[t])
|
201 | }
|
202 | }
|
203 | }
|
204 | if (isInstall) {
|
205 | out.push('');
|
206 | out.push(`include ":${utils.spritUpperCase(op.name)}"`);
|
207 | out.push(`project (":${utils.spritUpperCase(op.name)}").projectDir = new File("../../../plugins/${op.name}/android")`);
|
208 | }
|
209 | let s = '';
|
210 | out.forEach((item) => {
|
211 | s += item + '\n'
|
212 | });
|
213 | fs.writeFileSync(tmpPath, s.replace(/^\n+|\n+$/g, ""), 'utf8')
|
214 | }
|
215 |
|
216 | function changeGradle(op, isInstall) {
|
217 | let gradleName = findGradleName(op);
|
218 | if (utils.count(gradleName) === 0) {
|
219 | return;
|
220 | }
|
221 | let tmpPath = path.resolve(op.rootDir, 'platforms/android/eeuiApp/app/build.gradle');
|
222 | let result = fs.readFileSync(tmpPath, 'utf8');
|
223 | let res = result.substr(result.indexOf('dependencies'), result.length);
|
224 | let temp = res.split('\n');
|
225 | let out = [];
|
226 | temp.forEach((item) => {
|
227 | if (!(item.indexOf('implementation') !== -1 &&
|
228 | (item.indexOf(`":${utils.spritUpperCase(op.name)}"`) !== -1))) {
|
229 | out.push(item)
|
230 | }
|
231 | });
|
232 | if (isInstall) {
|
233 | let temp = [];
|
234 | let pos = 0;
|
235 | let i = 0;
|
236 | out.forEach((item) => {
|
237 | i++;
|
238 | temp.push(item);
|
239 | if (item.indexOf("implementation") !== -1) {
|
240 | pos = i;
|
241 | }
|
242 | });
|
243 | temp.splice(pos, 0, ` implementation project(":${utils.spritUpperCase(op.name)}")`);
|
244 | out = temp;
|
245 | }
|
246 | let string = '';
|
247 | out.forEach((item) => { string += item + '\n' });
|
248 | result = result.replace(res, string);
|
249 | fs.writeFileSync(tmpPath, result.replace(/^\n+|\n+$/g, ""), 'utf8');
|
250 | }
|
251 |
|
252 | function cleanIdea(op) {
|
253 | let ideGradlePath = path.resolve(op.rootDir, 'platforms/android/eeuiApp/.idea/gradle.xml');
|
254 | if (fs.existsSync(ideGradlePath)) {
|
255 | let ideGradleResult = fs.readFileSync(ideGradlePath, 'utf8');
|
256 | let ideGradleRege = new RegExp(`<option value="(.*?)/plugins/${op.name}/android"(.*?)/>\n*`, "g");
|
257 | ideGradleResult = ideGradleResult.replace(ideGradleRege, "");
|
258 | fs.writeFileSync(ideGradlePath, ideGradleResult, 'utf8');
|
259 | }
|
260 |
|
261 | let ideModulesPath = path.resolve(op.rootDir, 'platforms/android/eeuiApp/.idea/modules.xml');
|
262 | if (fs.existsSync(ideModulesPath)) {
|
263 | let ideModulesResult = fs.readFileSync(ideModulesPath, 'utf8');
|
264 | let ideModulesRege = new RegExp(`<module fileurl="(.*?)/plugins/${op.name}/android/(.*?).iml"(.*?)/>\n*`, "g");
|
265 | ideModulesResult = ideModulesResult.replace(ideModulesRege, "");
|
266 | fs.writeFileSync(ideModulesPath, ideModulesResult, 'utf8');
|
267 | }
|
268 | }
|
269 |
|
270 | function changeProfile(op, isInstall) {
|
271 | let xcodeprojName = findXcodeprojName(op);
|
272 | if (utils.count(xcodeprojName) === 0) {
|
273 | return;
|
274 | }
|
275 | let eeuiPath = path.resolve(op.rootDir, 'platforms/ios/eeuiApp');
|
276 | let podPath = path.resolve(eeuiPath, 'Podfile');
|
277 | let result = fs.readFileSync(podPath, 'utf8');
|
278 | let temp = result.split('\n');
|
279 | let out = [];
|
280 | let weg = [];
|
281 | let hasEnd = false;
|
282 | temp.forEach((item) => {
|
283 | if (item.trim() === 'end') {
|
284 | hasEnd = true
|
285 | }
|
286 | if (!hasEnd) {
|
287 | if (item.indexOf('\'' + xcodeprojName + '\'') === -1) {
|
288 | out.push(item)
|
289 | }
|
290 | } else {
|
291 | weg.push(item)
|
292 | }
|
293 | });
|
294 | if (isInstall) {
|
295 | out.push(' pod \'' + xcodeprojName + '\', :path => \'../../../plugins/' + op.name + '/ios\'');
|
296 | }
|
297 | weg.forEach((item) => {
|
298 | out.push(item)
|
299 | });
|
300 | let px = '';
|
301 | out.forEach((item) => {
|
302 | px += item + '\n'
|
303 | });
|
304 | fs.writeFileSync(podPath, px.replace(/^\n+|\n+$/g, ""), 'utf8');
|
305 | }
|
306 |
|
307 | function invokeAndroid(op, callback) {
|
308 | if (op.simple === true) {
|
309 | typeof callback === 'function' && callback();
|
310 | return;
|
311 | }
|
312 | let eeuiPath = path.resolve(op.rootDir, 'platforms/android/eeuiApp');
|
313 | let tempPath = process.cwd();
|
314 | let spinPod = ora('gradlew clean...');
|
315 | spinPod.start();
|
316 | try {
|
317 | shelljs.cd(eeuiPath);
|
318 | shelljs.exec('./gradlew clean', {silent: true}, () => {
|
319 | shelljs.cd(tempPath);
|
320 | spinPod.stop();
|
321 | typeof callback === 'function' && callback();
|
322 | });
|
323 | } catch (e) {
|
324 | shelljs.cd(tempPath);
|
325 | spinPod.stop();
|
326 | typeof callback === 'function' && callback();
|
327 | }
|
328 | }
|
329 |
|
330 | function invokeIos(op, callback) {
|
331 | if (op.simple === true) {
|
332 | typeof callback === 'function' && callback();
|
333 | return;
|
334 | }
|
335 | if (!shelljs.which('pod')) {
|
336 | if (!isWin) {
|
337 | logger.info('未检测到系统安装CocoaPods,请安装后手动执行pod install!');
|
338 | }
|
339 | typeof callback === 'function' && callback();
|
340 | return;
|
341 | }
|
342 | let eeuiPath = path.resolve(op.rootDir, 'platforms/ios/eeuiApp');
|
343 | let tempPath = process.cwd();
|
344 | let spinPod = ora('pod install...');
|
345 | spinPod.start();
|
346 | try {
|
347 | shelljs.cd(eeuiPath);
|
348 | shelljs.exec('pod install', {silent: true}, () => {
|
349 | shelljs.cd(tempPath);
|
350 | spinPod.stop();
|
351 | typeof callback === 'function' && callback();
|
352 | });
|
353 | } catch (e) {
|
354 | shelljs.cd(tempPath);
|
355 | spinPod.stop();
|
356 | typeof callback === 'function' && callback();
|
357 | }
|
358 | }
|
359 |
|
360 | function findXcodeprojName(op) {
|
361 | let dirPath = path.resolve(op.rootDir, 'plugins', op.name, 'ios');
|
362 | if (!fs.existsSync(dirPath)) {
|
363 | return "";
|
364 | }
|
365 | let files = fs.readdirSync(dirPath);
|
366 | let name = "";
|
367 | files.some((filename) => {
|
368 | let stats = utils.pathType(path.join(dirPath, filename));
|
369 | if (stats === 2) {
|
370 | if (utils.rightExists(filename, ".xcodeproj")) {
|
371 | name = utils.rightDelete(filename, ".xcodeproj");
|
372 | return true;
|
373 | }
|
374 | }
|
375 | });
|
376 | return name;
|
377 | }
|
378 |
|
379 | function findGradleName(op) {
|
380 | let dirPath = path.resolve(op.rootDir, 'plugins', op.name, 'android');
|
381 | if (!fs.existsSync(dirPath)) {
|
382 | return "";
|
383 | }
|
384 | let files = fs.readdirSync(dirPath);
|
385 | let name = "";
|
386 | files.some((filename) => {
|
387 | let stats = utils.pathType(path.join(dirPath, filename));
|
388 | if (stats === 1) {
|
389 | if (utils.rightExists(filename, ".gradle")) {
|
390 | name = utils.rightDelete(filename, ".gradle");
|
391 | return true;
|
392 | }
|
393 | }
|
394 | });
|
395 | return name;
|
396 | }
|
397 |
|
398 | module.exports = {add, remove, changeSetting, changeGradle, cleanIdea, changeProfile, invokeAndroid, invokeIos};
|
399 |
|
400 |
|
401 |
|
402 |
|