Skip to content

Commit f304a82

Browse files
committed
Initial commit
1 parent 54c765b commit f304a82

File tree

8 files changed

+262
-1
lines changed

8 files changed

+262
-1
lines changed

.babelrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"presets": ["es2015", "stage-0"]
3+
}

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules
2+
npm-debug.log
3+
/lib

.npmignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
npm-debug.log
2+
/src

.travis.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
sudo: required
2+
dist: trusty
3+
group: edge
4+
5+
language: node_js
6+
7+
os:
8+
- linux
9+
10+
node_js:
11+
- '6'
12+
- '5'
13+
- '4'
14+
15+
before_install:
16+
- npm install -g npm
17+
- npm --version

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
# github-release-cli
1+
# github-release-cli [![build status](https://travis-ci.org/cheton/github-release-cli.svg?branch=master)](https://travis-ci.org/cheton/github-release-cli)
2+
23
A command-line tool for uploading release assets to a GitHub repository.
34

45
## Installation

bin/github-release

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/usr/bin/env node
2+
3+
require('../lib');

package.json

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"name": "github-release-cli",
3+
"version": "0.1.0",
4+
"description": "A command-line tool for uploading release assets to a GitHub repository",
5+
"homepage": "https://github.com/cheton/github-release-cli",
6+
"author": "Cheton Wu <cheton@gmail.com>",
7+
"bin": {
8+
"github-release": "./bin/github-release"
9+
},
10+
"scripts": {
11+
"prepublish": "npm run build",
12+
"build": "babel --out-dir ./lib ./src"
13+
},
14+
"files": [
15+
"bin",
16+
"lib"
17+
],
18+
"repository": {
19+
"type": "git",
20+
"url": "git@github.com:cheton/github-release-cli.git"
21+
},
22+
"license": "MIT",
23+
"preferGlobal": true,
24+
"keywords": [
25+
"github",
26+
"release",
27+
"cli"
28+
],
29+
"dependencies": {},
30+
"devDependencies": {
31+
"babel-cli": "^6.22.2",
32+
"babel-preset-es2015": "^6.22.0",
33+
"babel-preset-stage-0": "^6.22.0",
34+
"github": "^8.1.1"
35+
}
36+
}

src/index.js

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
/* eslint no-console: 0 */
2+
/* eslint max-len: 0 */
3+
import path from 'path';
4+
import _ from 'lodash';
5+
import GitHubApi from 'github';
6+
import program from 'commander';
7+
import pkg from '../package.json';
8+
9+
program
10+
.version(pkg.version)
11+
.option('-T, --token <token>', 'OAuth2 token')
12+
.option('-o, --owner <owner>', 'owner')
13+
.option('-r, --repo <repo>', 'repo')
14+
.option('-t, --tag <tag>', 'tag')
15+
.option('-n, --name <name>', 'name')
16+
.option('-b, --body <body>', 'body', false)
17+
.parse(process.argv);
18+
19+
const github = new GitHubApi({
20+
version: '3.0.0',
21+
timeout: 5000,
22+
headers: {
23+
'user-agent': 'GitHub-Release-App'
24+
}
25+
});
26+
const files = program.args;
27+
28+
github.authenticate({
29+
type: 'oauth',
30+
token: program.token || process.env.GITHUB_TOKEN
31+
});
32+
33+
const getReleaseByTag = (options) => {
34+
return new Promise((resolve, reject) => {
35+
github.repos.getReleaseByTag(options, (err, res) => {
36+
err ? reject(err) : resolve(res);
37+
});
38+
});
39+
};
40+
41+
const createRelease = (options) => {
42+
return new Promise((resolve, reject) => {
43+
github.repos.createRelease(options, (err, res) => {
44+
err ? reject(err) : resolve(res);
45+
});
46+
});
47+
};
48+
49+
const editRelease = (options) => {
50+
return new Promise((resolve, reject) => {
51+
github.repos.editRelease(options, (err, res) => {
52+
err ? reject(err) : resolve(res);
53+
});
54+
});
55+
};
56+
57+
const getAssets = (options) => {
58+
return new Promise((resolve, reject) => {
59+
github.repos.getAssets(options, (err, res) => {
60+
err ? reject(err) : resolve(res);
61+
});
62+
});
63+
};
64+
65+
const deleteAsset = (options) => {
66+
return new Promise((resolve, reject) => {
67+
github.repos.deleteAsset(options, (err, res) => {
68+
err ? reject(err) : resolve(res);
69+
});
70+
});
71+
};
72+
73+
const uploadAsset = (options) => {
74+
return new Promise((resolve, reject) => {
75+
github.repos.uploadAsset(options, (err, res) => {
76+
err ? reject(err) : resolve(res);
77+
});
78+
});
79+
};
80+
81+
const main = async () => {
82+
const { owner, repo, tag, name, body } = program;
83+
84+
try {
85+
console.log('> releases#getReleaseByTag');
86+
let release = await getReleaseByTag({
87+
owner: owner,
88+
repo: repo,
89+
tag: tag
90+
});
91+
if (!release) {
92+
console.log('> releases#createRelease');
93+
release = await createRelease({
94+
owner: owner,
95+
repo: repo,
96+
tag_name: tag,
97+
name: name || tag,
98+
body: body || ''
99+
});
100+
console.log('ok', release);
101+
} else if (release.body !== body) {
102+
console.log('> releases#editRelease');
103+
let releaseOptions = {
104+
owner: owner,
105+
repo: repo,
106+
id: release.id,
107+
tag_name: tag,
108+
name: name || tag
109+
};
110+
if (body) {
111+
releaseOptions.body = body || '';
112+
}
113+
release = await editRelease(releaseOptions);
114+
console.log('ok', release);
115+
}
116+
117+
console.log('> releases#getAssets');
118+
let assets = await getAssets({
119+
owner: owner,
120+
repo: repo,
121+
id: release.id
122+
});
123+
console.log('assets=%d', assets.length);
124+
125+
assets = _.filter(assets, (asset) => {
126+
// Example:
127+
// 'cnc-1.1.0-latest-08c256a-linux-x64.tar.gz'
128+
// ["cnc-1.1.0-latest-08c256a-linux-x64.tar.gz", "cnc", "1.1.0-latest-08c256a", "linux", "x64", "tar.gz"]
129+
const pattern = new RegExp(/([a-zA-Z0-9][a-zA-Z0-9\-]*)\-(\d+\.\d+\.\d+(?:\-[a-zA-Z0-9][a-zA-Z0-9\-]*)?)(?:\-(mac|win|linux|tinyweb))(?:(?:\-([a-zA-Z0-9_\-]+))?\.(.*))/);
130+
131+
return _.some(files, (file) => {
132+
const r1 = asset.name.match(pattern);
133+
const r2 = path.basename(file).match(pattern);
134+
135+
if ((r1 === null) || (r2 === null)) {
136+
console.error('Unable to match file: asset="%s", file="%s"', asset.name, path.basename(file));
137+
return false;
138+
}
139+
140+
// 0: full
141+
// 1: name
142+
// 2: version
143+
// 3: platform
144+
// 4: arch
145+
// 5: extname
146+
147+
// Skip checking for #0 (full) and #2 (version)
148+
r1[0] = r1[2] = undefined;
149+
r2[0] = r2[2] = undefined;
150+
151+
return _.isEqual(_.compact(r1), _.compact(r2));
152+
});
153+
});
154+
155+
if (assets.length > 0) {
156+
console.log('> releases#deleteAsset');
157+
for (let i = 0; i < assets.length; ++i) {
158+
const asset = assets[i];
159+
console.log('#%d', i + 1, {
160+
id: asset.id,
161+
name: asset.name,
162+
label: asset.label,
163+
state: asset.state,
164+
size: asset.size,
165+
download_count: asset.download_count,
166+
created_at: asset.created_at,
167+
updated_at: asset.updated_at
168+
});
169+
await deleteAsset({
170+
owner: owner,
171+
repo: repo,
172+
id: asset.id
173+
});
174+
}
175+
}
176+
177+
if (files.length > 0) {
178+
console.log('> releases#uploadAsset');
179+
for (let i = 0; i < files.length; ++i) {
180+
const file = files[i];
181+
console.log('#%d name="%s" filePath="%s"', i + 1, path.basename(file), file);
182+
await uploadAsset({
183+
owner: owner,
184+
repo: repo,
185+
id: release.id,
186+
filePath: file,
187+
name: path.basename(file)
188+
});
189+
}
190+
}
191+
} catch (err) {
192+
console.error(err);
193+
}
194+
};
195+
196+
main();

0 commit comments

Comments
 (0)