Skip to content

Commit ddf4996

Browse files
committed
Added createIssues command.
- Refactored cli into a binary executable. - Expanded the config to specify the points for each type of release. - Added 'pagination' gap stop with `per_page` of 9999.
1 parent 7e15814 commit ddf4996

File tree

13 files changed

+269
-74
lines changed

13 files changed

+269
-74
lines changed

bin/cli.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#!/usr/bin/env node
2+
3+
const { applyPositionals } = require( "../src/cli" );
4+
const Query = require( "../src/Query" );
5+
const utils = require( "../src/utils" );
6+
7+
const config = utils.loadConfiguration();
8+
if ( config === null ) {
9+
console.error( "Please add configuration in the form of `config.json`." );
10+
return;
11+
}
12+
13+
const query = new Query(
14+
config.token,
15+
config.repositories || [],
16+
);
17+
18+
const yargs = require( "yargs" )
19+
.scriptName( "./bin/cli.js" )
20+
.usage( "$0 <command> [arguments]" )
21+
.command(
22+
"repo [repo] [output]",
23+
"Retrieve repository information",
24+
yargs => applyPositionals( yargs, [ "repo", "output" ] ),
25+
yargv => utils.wrapRequest( query.repository( yargv ) )
26+
)
27+
.command(
28+
"labels [repo] [output]",
29+
"Retrieve labels",
30+
yargs => applyPositionals( yargs, [ "repo", "labelOutput" ] ),
31+
yargv => utils.wrapRequest( query.labels( yargv ) )
32+
)
33+
.command(
34+
"label <label> [repo] [searchIn] [output]",
35+
"Retrieve label information",
36+
yargs => applyPositionals( yargs, [ "label", "repo", "searchIn", "labelOutput" ] ),
37+
yargv => utils.wrapRequest( query.label( yargv ) )
38+
)
39+
.command(
40+
"milestones [repo] [output]",
41+
"Retrieve milestones",
42+
yargs => applyPositionals( yargs, [ "repo", "milestoneOutput" ] ),
43+
yargv => utils.wrapRequest( query.milestones( yargv ) )
44+
)
45+
.command(
46+
"milestone <milestone> [repo] [searchIn] [output]",
47+
"Retrieve milestone information",
48+
yargs => applyPositionals( yargs, [ "milestone", "repo", "searchIn", "milestoneOutput" ] ),
49+
yargv => utils.wrapRequest( query.milestone( yargv ) )
50+
)
51+
.command(
52+
"createIssue [repo] [fields]",
53+
"Create an issue",
54+
yargs => applyPositionals( yargs, [ "repo", "fields" ] ),
55+
yargv => utils.wrapRequest( query.createIssue( yargv ) )
56+
)
57+
.help()
58+
.argv;

bin/createIssues.js

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
#!/usr/bin/env node
2+
/* global require, process */
3+
4+
const readline = require( "readline" );
5+
const Query = require( "../src/Query" );
6+
const utils = require( "../src/utils" );
7+
8+
const { log, error, warn } = console;
9+
10+
const config = utils.loadConfiguration();
11+
if ( config === null ) {
12+
error( "Please add configuration in the form of `config.json`." );
13+
return;
14+
}
15+
if ( ! config.repositories || config.repositories.length < 1 ) {
16+
error( "Please supply the repositories in your `config.json`." );
17+
return;
18+
}
19+
for ( let i = 0; i < config.repositories.length; i++ ) {
20+
const { owner, name, points } = config.repositories[ i ];
21+
if ( ! owner || ! name || ! points ) {
22+
error( "Please supply each repository with an 'owner', 'name' and 'points' entry." );
23+
return;
24+
}
25+
}
26+
27+
let type = process.argv[ 2 ];
28+
type = type && type.toLowerCase();
29+
if ( ! [ "release", "rc", "rc+" ].includes( type ) ) {
30+
error( "Please supply the type of the issues to create: 'release', 'rc' or 'rc+'." );
31+
return;
32+
}
33+
34+
const version = process.argv[ 3 ];
35+
if ( ! version || version.length < 1 ) {
36+
error( "Please supply the version for the issue." );
37+
return;
38+
}
39+
40+
const determineOptions = ( type, repository ) => {
41+
if ( ! repository.points[ type ] ) {
42+
error( `Please supply the points for '${ type }' in the '${ repository.name }' repository.` );
43+
return null;
44+
}
45+
46+
const options = {};
47+
switch ( type ) {
48+
case "release":
49+
options.title = `Release ${ version }`;
50+
options.labels = [ "in sprint", repository.points[ type ] ];
51+
options.milestone = version;
52+
break;
53+
case "rc":
54+
options.title = `Create ${ version }`;
55+
options.labels = [ "in sprint", repository.points[ type ] ];
56+
options.milestone = version.split( "-" )[ 0 ];
57+
break;
58+
case "rc+":
59+
options.title = `Create ${ version }`;
60+
options.labels = [ "in sprint", repository.points[ type ] ];
61+
options.milestone = version.split( "-" )[ 0 ];
62+
break;
63+
}
64+
return options;
65+
};
66+
67+
const query = new Query(
68+
config.token,
69+
config.repositories,
70+
);
71+
72+
const createIssue = async ( repository ) => {
73+
const options = determineOptions( type, repository );
74+
let [ labels, milestones ] = await Promise.all( [ query.labels( { repo: repository } ), query.milestones( { repo: repository } ) ] );
75+
labels = utils.arrayToObject( labels, "name" );
76+
milestones = utils.arrayToObject( milestones, "title" );
77+
78+
for ( let i = 0; i < options.labels.length; i++ ) {
79+
const label = labels[ options.labels[ i ] ];
80+
if ( ! label ) {
81+
warn( `Label '${ options.labels[ i ] }' not found in '${ repository.name }'. Skipping.` );
82+
return;
83+
}
84+
options.labels[ i ] = label.node_id;
85+
}
86+
if ( ! milestones[ options.milestone ] ) {
87+
warn( `Milestone '${ options.milestone }' not found in ${ repository.name }. Skipping.` );
88+
return;
89+
}
90+
options.milestone = milestones[ options.milestone ].node_id;
91+
92+
query.createIssue( {
93+
repo: repository,
94+
fields: {
95+
title: options.title,
96+
labelIds: options.labels,
97+
milestoneId: options.milestone,
98+
}
99+
} )
100+
.then( () => log( "Issue created successfully." ) )
101+
.catch( e => error( "Error trying to create issue.", e ) );
102+
};
103+
104+
// See: https://nodejs.org/api/readline.html#readline_readline_createinterface_options
105+
const readlineInterface = readline.createInterface( {
106+
input: process.stdin,
107+
output: process.stdout,
108+
} );
109+
110+
const askQuestion = ( index ) => {
111+
const next = () => {
112+
index++;
113+
if ( index >= config.repositories.length ) {
114+
return readlineInterface.close();
115+
}
116+
askQuestion( index );
117+
};
118+
119+
readlineInterface.question( `Create issue for ${ config.repositories[ index ].name }? [Y/n/x] `, async answer => {
120+
answer = answer.toString().toLowerCase().trim();
121+
switch ( answer ) {
122+
default:
123+
await createIssue( config.repositories[ index ] );
124+
next();
125+
break;
126+
case "n":
127+
next();
128+
break;
129+
case "x":
130+
readlineInterface.close();
131+
break;
132+
}
133+
} );
134+
};
135+
136+
askQuestion( 0 );

config.example.json

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
{
2-
"token": "<TOKEN>",
3-
"repositories": [
4-
{
5-
"owner": "<OWNER>",
6-
"name": "<REPO>"
7-
}
8-
]
2+
"token": "<TOKEN>",
3+
"repositories": [
4+
{
5+
"owner": "<OWNER>",
6+
"name": "<NAME>",
7+
"points": {
8+
"release": 1,
9+
"rc": 1,
10+
"rc+": 0.5
11+
}
12+
}
13+
]
914
}

index.js

Lines changed: 5 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,6 @@
1-
const config = require( "./config.json" );
2-
const { applyPositionals } = require( "./src/cli" );
3-
const Query = require( "./src/Query" );
4-
const { wrapRequest } = require( "./src/utils" );
1+
/* global require, module */
52

6-
const query = new Query(
7-
config.token,
8-
config.repositories || [],
9-
);
10-
11-
const yargs = require( "yargs" )
12-
.scriptName( "yarn start" )
13-
.usage( "$0 <command> [arguments]" )
14-
.command(
15-
"repo [repo] [output]",
16-
"Retrieve repository information",
17-
yargs => applyPositionals( yargs, [ "repo", "output" ] ),
18-
yargv => wrapRequest( query.repository( yargv ) )
19-
)
20-
.command(
21-
"labels [repo] [output]",
22-
"Retrieve labels",
23-
yargs => applyPositionals( yargs, [ "repo", "labelOutput" ] ),
24-
yargv => wrapRequest( query.labels( yargv ) )
25-
)
26-
.command(
27-
"label <label> [repo] [searchIn] [output]",
28-
"Retrieve label information",
29-
yargs => applyPositionals( yargs, [ "label", "repo", "searchIn", "labelOutput" ] ),
30-
yargv => wrapRequest( query.label( yargv ) )
31-
)
32-
.command(
33-
"milestones [repo] [output]",
34-
"Retrieve milestones",
35-
yargs => applyPositionals( yargs, [ "repo", "milestoneOutput" ] ),
36-
yargv => wrapRequest( query.milestones( yargv ) )
37-
)
38-
.command(
39-
"milestone <milestone> [repo] [searchIn] [output]",
40-
"Retrieve milestone information",
41-
yargs => applyPositionals( yargs, [ "milestone", "repo", "searchIn", "milestoneOutput" ] ),
42-
yargv => wrapRequest( query.milestone( yargv ) )
43-
)
44-
.command(
45-
"createIssue [repo] [fields]",
46-
"Create an issue",
47-
yargs => applyPositionals( yargs, [ "repo", "fields" ] ),
48-
yargv => wrapRequest( query.createIssue( yargv ) )
49-
)
50-
.help()
51-
.argv;
3+
module.exports = {
4+
Query: require( "./src/Query" ),
5+
utils: require( "./src/utils" ),
6+
};

package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"name": "github",
3-
"version": "0.0.0",
2+
"name": "github-helper",
3+
"version": "0.0.1",
44
"main": "index.js",
55
"license": "MIT",
66
"private": true,
@@ -9,7 +9,8 @@
99
"@octokit/rest": "^16.28.3",
1010
"yargs": "^13.2.4"
1111
},
12-
"scripts": {
13-
"start": "node index.js"
12+
"bin": {
13+
"cli": "./bin/cli.js",
14+
"createIssues": "./bin/createIssues.js"
1415
}
1516
}

src/Query.js

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
/* global require, module */
2+
13
const graphql = require( "@octokit/graphql" );
24
const Octokit = require( "@octokit/rest" );
35
const schema = require( "./schema" );
@@ -18,7 +20,7 @@ class Query {
1820
this._repositories = repositories;
1921
}
2022

21-
repository( { repo, output = "id" } ) {
23+
repository( { repo = {}, output = "id" } = {} ) {
2224
return this._api(
2325
schema.query(
2426
"$owner:String! $name:String!",
@@ -29,29 +31,30 @@ class Query {
2931
.then( ( { repository } ) => repository );
3032
}
3133

32-
labels( { repo, output = [ "node_id", "name", "description" ] } ) {
34+
labels( { repo = {}, output = [ "node_id", "name", "description" ] } = {} ) {
3335
const repositoryInfo = utils.determineRepository( repo, this._repositories );
3436
return this._rest.issues.listLabelsForRepo( {
3537
owner: repositoryInfo.owner,
3638
repo: repositoryInfo.name,
39+
per_page: 9999,
3740
} )
3841
.then( ( { data } ) => utils.filterArrayObjectKeys( data, output ) );
3942
}
4043

41-
42-
label( { label, repo, searchIn = [ "name", "description" ], output = [ "node_id", "name", "description" ] } ) {
44+
label( { label, repo = {}, searchIn = [ "name", "description" ], output = [ "node_id", "name", "description" ] } ) {
4345
const repositoryInfo = utils.determineRepository( repo, this._repositories );
4446
return this._rest.issues.listLabelsForRepo( {
4547
owner: repositoryInfo.owner,
4648
repo: repositoryInfo.name,
49+
per_page: 9999,
4750
} )
4851
.then( ( { data } ) => {
4952
const matches = utils.findObjectValuesInArray( label, data, searchIn );
5053
return utils.filterArrayObjectKeys( matches, output );
5154
} );
5255
}
5356

54-
labelV4( { label, repo, pagination, output = "id name description" } ) {
57+
labelV4( { label, repo = {}, pagination = {}, output = "id name description" } ) {
5558
const page = utils.determinePaginationVariables( pagination );
5659
return this._api(
5760
schema.query(
@@ -69,28 +72,30 @@ class Query {
6972
.then( ( { repository: { labels: { nodes } } } ) => nodes );
7073
}
7174

72-
milestones( { repo, output = [ "node_id", "title", "description" ] } ) {
75+
milestones( { repo = {}, output = [ "node_id", "title", "description" ] } = {} ) {
7376
const repositoryInfo = utils.determineRepository( repo, this._repositories );
7477
return this._rest.issues.listMilestonesForRepo( {
7578
owner: repositoryInfo.owner,
7679
repo: repositoryInfo.name,
80+
per_page: 9999,
7781
} )
7882
.then( ( { data } ) => utils.filterArrayObjectKeys( data, output ) );
7983
}
8084

81-
milestone( { milestone, repo, searchIn = [ "title", "description" ], output = [ "node_id", "title", "description" ] } ) {
85+
milestone( { milestone, repo = {}, searchIn = [ "title", "description" ], output = [ "node_id", "title", "description" ] } ) {
8286
const repositoryInfo = utils.determineRepository( repo, this._repositories );
8387
return this._rest.issues.listMilestonesForRepo( {
8488
owner: repositoryInfo.owner,
8589
repo: repositoryInfo.name,
90+
per_page: 9999,
8691
} )
8792
.then( ( { data } ) => {
8893
const matches = utils.findObjectValuesInArray( milestone, data, searchIn );
8994
return utils.filterArrayObjectKeys( matches, output );
9095
} );
9196
}
9297

93-
createIssue( { repo, fields } ) {
98+
createIssue( { repo = {}, fields = {} } = {} ) {
9499
return this.repository( { repo } )
95100
.then( ( { id } ) => this._api(
96101
schema.createIssue(),
@@ -100,7 +105,8 @@ class Query {
100105
...fields,
101106
}
102107
}
103-
) );
108+
) )
109+
.then( ( { createIssue } ) => createIssue );
104110
}
105111
}
106112

0 commit comments

Comments
 (0)