Skip to content

Commit 4a1533c

Browse files
committed
Merge branch 'retorquere-master'
2 parents 8e82da3 + fe534a4 commit 4a1533c

3 files changed

Lines changed: 116 additions & 169 deletions

File tree

.babelrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"presets": ["es2015", "stage-0"],
2+
"presets": ["env", "stage-0"],
33
"plugins": [
44
"transform-runtime"
55
]

package.json

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "github-release-cli",
3-
"version": "0.4.1",
3+
"version": "1.0.0",
44
"description": "A command-line tool for managing release assets on a GitHub repository",
55
"homepage": "https://github.com/cheton/github-release-cli",
66
"author": "Cheton Wu <cheton@gmail.com>",
@@ -10,7 +10,8 @@
1010
"scripts": {
1111
"prepublish": "npm run build",
1212
"build": "babel --out-dir ./lib ./src",
13-
"test": "tap test/*.js --no-timeout --node-arg=--require --node-arg=babel-register --node-arg=--require --node-arg=babel-polyfill"
13+
"test": "tap test/*.js --no-timeout --node-arg=--require --node-arg=babel-register --node-arg=--require --node-arg=babel-polyfill",
14+
"test:list": "npm run build && node lib/index.js -a --owner cheton --repo github-release-cli list"
1415
},
1516
"files": [
1617
"bin",
@@ -28,16 +29,19 @@
2829
"cli"
2930
],
3031
"dependencies": {
31-
"babel-runtime": "^6.22.0",
32-
"commander": "^2.9.0",
33-
"github": "^8.1.1",
34-
"minimatch": "^3.0.3"
32+
"@octokit/rest": "^16.9.0",
33+
"babel-runtime": "^6.26.0",
34+
"commander": "^2.19.0",
35+
"http-link-header": "^1.0.2",
36+
"mime-types": "^2.1.21",
37+
"minimatch": "^3.0.4",
38+
"url-parse": "^1.4.4"
3539
},
3640
"devDependencies": {
37-
"babel-cli": "^6.22.2",
38-
"babel-plugin-transform-runtime": "^6.22.0",
39-
"babel-preset-es2015": "^6.22.0",
40-
"babel-preset-stage-0": "^6.22.0",
41-
"tap": "^12.0.1"
41+
"babel-cli": "^6.26.0",
42+
"babel-plugin-transform-runtime": "^6.23.0",
43+
"babel-preset-env": "^1.7.0",
44+
"babel-preset-stage-0": "^6.24.1",
45+
"tap": "^12.1.1"
4246
}
4347
}

src/index.js

Lines changed: 100 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
/* eslint no-console: 0 */
22
/* eslint max-len: 0 */
3+
import fs from 'fs';
34
import path from 'path';
4-
import GitHubApi from 'github';
5+
import Octokit from '@octokit/rest';
56
import program from 'commander';
7+
import * as LinkHeader from 'http-link-header';
8+
import * as mime from 'mime-types';
69
import minimatch from 'minimatch';
10+
import parse from 'url-parse';
711
import pkg from '../package.json';
812

13+
const octokit = new Octokit();
14+
915
program
1016
.version(pkg.version)
1117
.usage('<command> [<args>]')
@@ -32,115 +38,31 @@ program.parse(process.argv);
3238

3339
const [command, ...args] = program.args;
3440

35-
const github = new GitHubApi({
36-
version: '3.0.0',
37-
timeout: 5000,
38-
headers: {
39-
'user-agent': 'GitHub-Release-App'
40-
}
41-
});
42-
43-
github.authenticate({
44-
type: 'oauth',
45-
token: program.token || process.env.GITHUB_TOKEN
46-
});
47-
48-
const getReleaseByTag = (options) => {
49-
return new Promise(async (resolve, reject) => {
50-
try {
51-
let page = 1;
52-
let lastPage = 1;
53-
let foundRelease = false;
54-
55-
do {
56-
const releases = await getReleases({
57-
owner: options.owner,
58-
repo: options.repo,
59-
page: page,
60-
per_page: 30
61-
});
62-
63-
const searchedReleases = releases.filter(r => (r.tag_name === options.tag) || (r.name === options.tag));
64-
if (searchedReleases.length) {
65-
resolve(releases[0]);
66-
foundRelease = true;
67-
break;
68-
}
69-
70-
const pagination = (releases.meta.link || '').split(',')
71-
.reduce((acc, link) => {
72-
const r = link.match(/\?page=(\d)+.*rel="(\w+)"/);
73-
if (r && r[1] && r[2]) {
74-
const key = r[2];
75-
const value = Number(r[1]) || 0;
76-
acc[key] = value;
77-
}
78-
return acc;
79-
}, {});
80-
81-
if (pagination.last > 0) {
82-
lastPage = pagination.last;
83-
}
84-
85-
++page;
86-
} while (page <= lastPage);
87-
88-
if (!foundRelease) {
89-
reject('Cannot find release');
90-
}
91-
} catch (err) {
92-
reject(err);
93-
}
41+
if (!command !== 'list') {
42+
octokit.authenticate({
43+
type: 'oauth',
44+
token: program.token || process.env.GITHUB_TOKEN
9445
});
95-
};
46+
}
9647

97-
const getReleases = (options) => {
98-
return new Promise((resolve, reject) => {
99-
github.repos.getReleases(options, (err, res) => {
100-
err ? reject(err) : resolve(res);
101-
});
102-
});
103-
};
104-
105-
const createRelease = (options) => {
106-
return new Promise((resolve, reject) => {
107-
github.repos.createRelease(options, (err, res) => {
108-
err ? reject(err) : resolve(res);
109-
});
110-
});
111-
};
112-
113-
const editRelease = (options) => {
114-
return new Promise((resolve, reject) => {
115-
github.repos.editRelease(options, (err, res) => {
116-
err ? reject(err) : resolve(res);
117-
});
118-
});
119-
};
48+
function next(response) {
49+
if (!response.headers || !response.headers.link) {
50+
return false;
51+
}
12052

121-
const getAssets = (options) => {
122-
return new Promise((resolve, reject) => {
123-
github.repos.getAssets(options, (err, res) => {
124-
err ? reject(err) : resolve(res);
125-
});
126-
});
127-
};
53+
let link = LinkHeader.parse(response.headers.link).rel('next');
54+
if (!link) {
55+
return false;
56+
}
12857

129-
const deleteAsset = (options) => {
130-
return new Promise((resolve, reject) => {
131-
github.repos.deleteAsset(options, (err, res) => {
132-
err ? reject(err) : resolve(res);
133-
});
134-
});
135-
};
58+
const url = parse(link[0].uri, null, true);
59+
if (!url.query) {
60+
return false;
61+
}
13662

137-
const uploadAsset = (options) => {
138-
return new Promise((resolve, reject) => {
139-
github.repos.uploadAsset(options, (err, res) => {
140-
err ? reject(err) : resolve(res);
141-
});
142-
});
143-
};
63+
const nextPage = parseInt(url.query.page);
64+
return nextPage;
65+
}
14466

14567
const fn = {
14668
'upload': async () => {
@@ -149,60 +71,58 @@ const fn = {
14971
let release;
15072

15173
try {
152-
console.log('> releases#getReleaseByTag');
153-
release = await getReleaseByTag({
74+
console.log(`> getReleaseByTag: owner=${owner}, repo=${repo}, tag=${tag}`);
75+
const res = await octokit.repos.getReleaseByTag({
15476
owner: owner,
15577
repo: repo,
156-
tag: tag
78+
tag: tag,
15779
});
80+
release = res.data;
15881
} catch (err) {
15982
// Ignore
16083
}
16184

16285
try {
16386
if (!release) {
164-
console.log('> releases#createRelease');
165-
release = await createRelease({
166-
owner: owner,
167-
repo: repo,
87+
console.log(`> createRelease: tag_name=${tag}, name=${name || tag}, draft=${!!draft}, prerelease=${!!prerelease}`);
88+
const res = await octokit.repos.createRelease({
89+
owner,
90+
repo,
16891
tag_name: tag,
16992
name: name || tag,
17093
body: body || '',
17194
draft: !!draft,
172-
prerelease: !!prerelease
95+
prerelease: !!prerelease,
17396
});
97+
release = res.data;
17498
} else {
175-
console.log('> releases#editRelease');
176-
let releaseOptions = {
177-
owner: owner,
178-
repo: repo,
179-
id: release.id,
99+
console.log(`> updateRelease: release_id=${release.id}, tag_name=${tag}, name=${name || tag}`);
100+
const res = await octokit.repos.updateRelease({
101+
owner,
102+
repo,
103+
release_id: release.id,
180104
tag_name: tag,
181105
name: name || tag,
182-
body: (body === undefined)
183-
? release.body || ''
184-
: body || '',
185-
draft: (draft === undefined)
186-
? !!release.draft
187-
: false,
188-
prerelease: (prerelease === undefined)
189-
? !!release.prerelease
190-
: false
191-
};
192-
release = await editRelease(releaseOptions);
106+
body: (body === undefined) ? release.body || '' : body || '',
107+
draft: (draft === undefined) ? !!release.draft : false,
108+
prerelease: (prerelease === undefined) ? !!release.prerelease : false,
109+
});
110+
release = res.data;
193111
}
194112

195113
if (files.length > 0) {
196-
console.log('> releases#uploadAsset');
114+
console.log(`> uploadReleaseAsset: assets_url=${release.assets_url}`);
197115
for (let i = 0; i < files.length; ++i) {
198116
const file = files[i];
199-
console.log('#%d name="%s" filePath="%s"', i + 1, path.basename(file), file);
200-
await uploadAsset({
201-
owner: owner,
202-
repo: repo,
203-
id: release.id,
204-
filePath: file,
205-
name: path.basename(file)
117+
console.log(` #${i + 1}: name="${path.basename(file)}" filePath="${file}"`);
118+
await octokit.repos.uploadReleaseAsset({
119+
url: release.upload_url,
120+
file: fs.createReadStream(file),
121+
headers: {
122+
'Content-Type': mime.lookup(file) || 'application/octet-stream',
123+
'Content-Length': fs.statSync(file).size,
124+
},
125+
name: path.basename(file),
206126
});
207127
}
208128
}
@@ -216,54 +136,77 @@ const fn = {
216136
let release;
217137

218138
try {
219-
console.log('> releases#getReleaseByTag');
220-
release = await getReleaseByTag({
139+
console.log(`> getReleaseByTag: owner=${owner}, repo=${repo}, tag=${tag}`);
140+
const res = await octokit.repos.getReleaseByTag({
221141
owner: owner,
222142
repo: repo,
223-
tag: tag
143+
tag: tag,
224144
});
145+
release = res.data;
225146
} catch (err) {
226147
console.error(err);
227148
return;
228149
}
229150

230151
try {
231-
console.log('> releases#getAssets');
232-
const assets = await getAssets({
233-
owner: owner,
234-
repo: repo,
235-
id: release.id
236-
});
152+
const release_id = release.id;
153+
console.log(`> listAssetsForRelease: release_id=${release_id}`);
154+
155+
let assets = [];
156+
let page = 1;
157+
do {
158+
const res = await octokit.repos.listAssetsForRelease({ owner, repo, release_id, page });
159+
assets = assets.concat(res.data);
160+
page = next(res);
161+
} while (page)
162+
237163
const deleteAssets = assets.filter(asset => {
238164
return patterns.some(pattern => minimatch(asset.name, pattern));
239165
});
240-
console.log('assets=%d, deleteAssets=%d', assets.length, deleteAssets.length);
166+
console.log(` assets=${assets.length}, deleteAssets=${deleteAssets.length}`);
241167

242168
if (deleteAssets.length > 0) {
243-
console.log('> releases#deleteAsset');
169+
console.log('> deleteReleaseAsset:');
244170
for (let i = 0; i < deleteAssets.length; ++i) {
245171
const asset = deleteAssets[i];
246-
console.log('#%d', i + 1, {
172+
console.log(` #${i + 1}:`, {
247173
id: asset.id,
248174
name: asset.name,
249175
label: asset.label,
250176
state: asset.state,
251177
size: asset.size,
252178
download_count: asset.download_count,
253179
created_at: asset.created_at,
254-
updated_at: asset.updated_at
255-
});
256-
await deleteAsset({
257-
owner: owner,
258-
repo: repo,
259-
id: asset.id
180+
updated_at: asset.updated_at,
260181
});
182+
await octokit.repos.deleteReleaseAsset({ owner, repo, asset_id: asset.id });
261183
}
262184
}
263185
} catch (err) {
264186
console.error(err);
265187
}
266-
}
188+
},
189+
'list': async () => {
190+
const releases = await octokit.repos.listReleases({
191+
owner: program.owner,
192+
repo: program.repo,
193+
page: 1,
194+
});
195+
for (const release of releases.data) {
196+
console.log(`${release.name} (${release.tag_name})`);
197+
}
198+
},
267199
}[command];
268200

269-
typeof fn === 'function' && fn();
201+
async function main() {
202+
try {
203+
typeof fn === 'function' && await fn();
204+
} catch (err) {
205+
// message has token in the response
206+
const message = err.message.replace(/https?:[^\s]*/g, (match) => match.replace(/\?.*/, ''));
207+
console.log(message);
208+
process.exit(1);
209+
}
210+
}
211+
212+
main()

0 commit comments

Comments
 (0)