1 | const jsonfile = require('jsonfile');
|
2 | const fs = require("fs-extra");
|
3 | const path = require("path");
|
4 | const ora = require('ora');
|
5 | const decompress = require('decompress');
|
6 | const tmp = require('tmp');
|
7 | const request = require('request').defaults({
|
8 | headers: {
|
9 | 'User-Agent': 'node request'
|
10 | }
|
11 | });
|
12 |
|
13 | let logger = require("./lib/utils/logger");
|
14 |
|
15 | class TemplateRelease {
|
16 | |
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 | constructor (name, releaseUrl) {
|
23 | if (!name || !releaseUrl) {
|
24 | throw new Error('Invalid argument');
|
25 | }
|
26 | this.name = name;
|
27 | this.releaseUrl = releaseUrl;
|
28 | this.CACHE_DIR_NAME = '.' + name;
|
29 | this.CACHE_DIR_PATH = path.join(require('os').homedir(), this.CACHE_DIR_NAME);
|
30 | this.CACHE_TEMPLATE_PATH = path.join(this.CACHE_DIR_PATH, "template");
|
31 | this.RELEASES_JSON_PATH = path.join(this.CACHE_TEMPLATE_PATH, "release.json");
|
32 | this.TEMPLATE_DIR_NAME = "templates";
|
33 | }
|
34 |
|
35 | |
36 |
|
37 |
|
38 |
|
39 | fetchReleaseVersions(cb) {
|
40 | request.get(this.releaseUrl, function(err, res, body){
|
41 | if (err) {
|
42 | cb && cb(err);
|
43 | return;
|
44 | }
|
45 | if (res.statusCode !== 200) {
|
46 | cb && cb(`获取信息失败 - ${res.statusCode}: ${res.body}`);
|
47 | return;
|
48 | }
|
49 | let tags = JSON.parse(body).map(function(e){return e["tag_name"]});
|
50 | cb && cb(null, tags);
|
51 | });
|
52 | }
|
53 |
|
54 | |
55 |
|
56 |
|
57 |
|
58 |
|
59 | fetchRelease(release, cb) {
|
60 | let releasesInfo = this._readReleaseJSON();
|
61 | if (release) {
|
62 | let info = releasesInfo[release];
|
63 | if (info) {
|
64 | cb(null, path.join(this.CACHE_TEMPLATE_PATH, info.path));
|
65 | return;
|
66 | }
|
67 | }
|
68 |
|
69 | let url = this._getReleaseUrl(release);
|
70 | let spinDown = ora(`正在下载模板版本: ${release ? release : "latest"}...`);
|
71 | spinDown.start();
|
72 | request(url, (err, res, body) => {
|
73 | spinDown.stop();
|
74 | if (err || res.statusCode !== 200) {
|
75 | let errorInfo = err ? err : `${res.statusCode}: ${res.body}`;
|
76 | logger.weiui(`未能下载 ${url} - ${errorInfo}`);
|
77 | logger.weiui('正在清除缓存...');
|
78 | if (!release) {
|
79 | let latestRleaseInfo = this.getCachedReleaseInfo();
|
80 | if (latestRleaseInfo) {
|
81 | logger.weiui(`在缓存中找到最新版本: ${latestRleaseInfo.tag}.`);
|
82 | cb(null, path.join(this.CACHE_TEMPLATE_PATH, latestRleaseInfo.path));
|
83 | return;
|
84 | }
|
85 | }
|
86 | cb(`未能获取版本 ${release ? release : "latest"}: ${errorInfo}`);
|
87 | return;
|
88 | }
|
89 | let info = JSON.parse(body);
|
90 | let newInfo = {};
|
91 | let tag = newInfo.tag = info["tag_name"];
|
92 | newInfo.time = info["published_at"];
|
93 | newInfo.path = newInfo.tag;
|
94 | let targetPath = path.join(this.CACHE_TEMPLATE_PATH, newInfo.path);
|
95 | if (fs.pathExistsSync(targetPath)) {
|
96 | logger.weiui(`已经缓存的版本。`);
|
97 | cb(null, targetPath);
|
98 | return;
|
99 | }
|
100 | spinDown.start();
|
101 | this._downloadAndUnzip(info["zipball_url"], targetPath, (err) => {
|
102 | spinDown.stop();
|
103 | if (err) {
|
104 | cb && cb(err);
|
105 | return;
|
106 | }
|
107 | releasesInfo[tag] = newInfo;
|
108 | jsonfile.writeFileSync(this.RELEASES_JSON_PATH, releasesInfo, {spaces: 2});
|
109 | cb(null, targetPath);
|
110 | });
|
111 | });
|
112 | }
|
113 |
|
114 | |
115 |
|
116 |
|
117 |
|
118 |
|
119 | getAvailableTemplateNames(projectPath) {
|
120 | let result = [];
|
121 | let tDir = path.join(projectPath, this.TEMPLATE_DIR_NAME);
|
122 | if (!fs.existsSync(tDir)) return result;
|
123 | let files = fs.readdirSync(tDir);
|
124 | for (let f of files) {
|
125 | if (fs.statSync(path.join(tDir, f)).isDirectory()) {
|
126 | result.push(f);
|
127 | }
|
128 | }
|
129 | return result;
|
130 | }
|
131 |
|
132 | |
133 |
|
134 |
|
135 |
|
136 |
|
137 | getCachedReleaseInfo(release) {
|
138 | let releasesInfo = this._readReleaseJSON();
|
139 | if (release) {
|
140 | return releasesInfo[release];
|
141 | }
|
142 | let latestRleaseInfo = null;
|
143 | for (let tag in releasesInfo) {
|
144 | let info = releasesInfo[tag];
|
145 | if (!latestRleaseInfo) {
|
146 | latestRleaseInfo = info;
|
147 | } else {
|
148 | if (Date.parse(info.time) > Date.parse(latestRleaseInfo.time)) latestRleaseInfo = info;
|
149 | }
|
150 | }
|
151 | return latestRleaseInfo;
|
152 | }
|
153 |
|
154 | |
155 |
|
156 |
|
157 |
|
158 |
|
159 | getCachedRelease(release) {
|
160 | let info = this.getCachedReleaseInfo(release);
|
161 | return info ? path.join(this.CACHE_TEMPLATE_PATH, info.path) : null;
|
162 | }
|
163 |
|
164 | _readReleaseJSON() {
|
165 | fs.ensureFileSync(this.RELEASES_JSON_PATH);
|
166 | try {
|
167 | return jsonfile.readFileSync(this.RELEASES_JSON_PATH);
|
168 | } catch (e) {
|
169 | return {};
|
170 | }
|
171 | }
|
172 |
|
173 | _getReleaseUrl(tag) {
|
174 | return this.releaseUrl + "/" + (tag ? `tags/${tag}` : "latest");
|
175 | }
|
176 |
|
177 | |
178 |
|
179 |
|
180 |
|
181 |
|
182 |
|
183 | _downloadAndUnzip(url, savePath, cb) {
|
184 | const TMP_DOWNLOAD_PATH = tmp.tmpNameSync({dir: require('os').tmpdir()}) + ".zip";
|
185 | let file = fs.createWriteStream(TMP_DOWNLOAD_PATH);
|
186 | file.on("close", () => {
|
187 | decompress(TMP_DOWNLOAD_PATH, this.CACHE_TEMPLATE_PATH).then(() => {
|
188 | let origPath = this._getLastReleasePath();
|
189 | fs.moveSync(origPath, savePath);
|
190 | fs.unlinkSync(TMP_DOWNLOAD_PATH);
|
191 | cb && cb();
|
192 | })
|
193 | }).on("error", (err) => {
|
194 | cb && cb(err)
|
195 | });
|
196 | request.get(url)
|
197 | .on("error", function (err) {
|
198 | cb && cb(`下载版本错误: ${err}`);
|
199 | })
|
200 | .on("response", function (res) {
|
201 | if (res.statusCode !== 200) {
|
202 | cb && cb("Get zipUrl return a non-200 response.");
|
203 | }
|
204 | })
|
205 | .on("end", function () {
|
206 |
|
207 | })
|
208 | .pipe(file);
|
209 | }
|
210 |
|
211 | |
212 |
|
213 |
|
214 |
|
215 | _getLastReleasePath() {
|
216 | let files = fs.readdirSync(this.CACHE_TEMPLATE_PATH);
|
217 | let part = this.releaseUrl.split('/');
|
218 | const pattern = part[part.length - 2];
|
219 | for (let f of files) {
|
220 | if (f.indexOf(pattern) !== -1) {
|
221 | return path.join( this.CACHE_TEMPLATE_PATH, f);
|
222 | }
|
223 | }
|
224 | return null;
|
225 | }
|
226 | }
|
227 |
|
228 | module.exports = TemplateRelease;
|