Родитель
5eefffd2d7
Коммит
618973acbd
2
.babelrc
2
.babelrc
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"presets": ["es2015", "es2017"],
|
||||
"plugins": ["transform-runtime"],
|
||||
"plugins": ["transform-runtime", "transform-object-rest-spread"],
|
||||
"env": {
|
||||
"test": {
|
||||
"plugins": ["istanbul"]
|
||||
|
|
|
@ -45,7 +45,7 @@ module.exports = {
|
|||
description: 'The token generated with repo access'
|
||||
},
|
||||
{
|
||||
short: '-U',
|
||||
short: '-a',
|
||||
name: 'api-url',
|
||||
valueType: '<url>',
|
||||
description: 'Override the GitHub API URL, allows gren to connect to a private GHE installation'
|
||||
|
@ -76,7 +76,7 @@ module.exports = {
|
|||
defaultValue: 'issues'
|
||||
},
|
||||
{
|
||||
short: '-a',
|
||||
short: '-N',
|
||||
name: 'include-messages',
|
||||
valueType: '<merge|commits|all>',
|
||||
description: 'Filter the messages added to the release notes. Only used when --data-source used is commits [commits]',
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import gren from 'commander';
|
||||
import { green } from 'chalk';
|
||||
import ObjectAssignDeep from 'object-assign-deep';
|
||||
import init from '../dist/_init';
|
||||
import utils from '../dist/_utils';
|
||||
import fs from 'fs';
|
||||
|
||||
gren
|
||||
.name(`${green('gren')} release`)
|
||||
.description('Initialise the module options.')
|
||||
.parse(process.argv);
|
||||
|
||||
init()
|
||||
.then(({
|
||||
fileExist,
|
||||
apiUrlType,
|
||||
ignoreCommitsWithConfirm,
|
||||
ignoreLabelsConfirm,
|
||||
ignoreIssuesWithConfirm,
|
||||
ignoreTagsWithConfirm,
|
||||
fileType,
|
||||
...data
|
||||
}) => {
|
||||
if (fileExist === 'abort') {
|
||||
console.log('Command aborted.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (fileExist === 'override') {
|
||||
const fileContent = utils.writeConfigToFile(fileType, data);
|
||||
|
||||
utils.cleanConfig(true);
|
||||
fs.writeFileSync(fileType, fileContent);
|
||||
|
||||
console.log(green(`\nGreat news! Your ${fileType} as been created!`));
|
||||
return;
|
||||
}
|
||||
|
||||
const currentConfig = utils.getConfigFromFile(process.cwd());
|
||||
const fileContent = utils.writeConfigToFile(fileType, ObjectAssignDeep({}, currentConfig, data));
|
||||
|
||||
fs.writeFileSync(fileType, fileContent);
|
||||
|
||||
console.log(green(`\nGreat news! Your ${fileType} as been created!`));
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error);
|
||||
process.exit(1);
|
||||
});
|
|
@ -17,6 +17,7 @@ gren
|
|||
.version(version)
|
||||
.description(`gren (🤖 ) ${description}`)
|
||||
.usage('<command> [options]')
|
||||
.command('init', 'initialise the module')
|
||||
.command('release', 'Release into chunk').alias('r')
|
||||
.command('changelog', 'Write a motherfucking changelog').alias('c')
|
||||
.command('examples', 'Show few examples of stuff that you can do <cmd>')
|
||||
|
|
|
@ -0,0 +1,248 @@
|
|||
import inquirer from 'inquirer';
|
||||
import utils from './_utils';
|
||||
import GitHubInfo from './GitHubInfo';
|
||||
import GitHub from 'github-api';
|
||||
import chalk from 'chalk';
|
||||
import { isUri } from 'valid-url';
|
||||
|
||||
const githubApi = new GitHubInfo();
|
||||
const prompt = inquirer.createPromptModule();
|
||||
const { GREN_GITHUB_TOKEN } = process.env;
|
||||
|
||||
if (!GREN_GITHUB_TOKEN) {
|
||||
console.error(chalk.red('Can\'t find GREN_GITHUB_TOKEN. Please configure your environment') + chalk.blue('\nSee https://github.com/github-tools/github-release-notes#setup'));
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const getInfo = async() => {
|
||||
try {
|
||||
const infos = await githubApi.repo;
|
||||
|
||||
return infos;
|
||||
} catch (error) {
|
||||
throw chalk.red('You have to run this command in a git repo folder');
|
||||
}
|
||||
};
|
||||
|
||||
const getLabels = async() => {
|
||||
const { username, repo } = await getInfo();
|
||||
|
||||
try {
|
||||
const gitHub = new GitHub({
|
||||
GREN_GITHUB_TOKEN
|
||||
});
|
||||
const issues = gitHub.getIssues(username, repo);
|
||||
const { data: labels } = await issues.listLabels();
|
||||
|
||||
return labels;
|
||||
} catch (error) {
|
||||
console.warn(chalk.bgYellow(chalk.black('I can\'t get your repo labels, make sure you are online to use the complete initialisation')));
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const getQuestions = async() => {
|
||||
const labels = await getLabels();
|
||||
|
||||
return [
|
||||
{
|
||||
name: 'apiUrlType',
|
||||
type: 'list',
|
||||
message: 'What type of APIs do you need?',
|
||||
choices: [{
|
||||
name: 'Normal',
|
||||
value: false
|
||||
},
|
||||
{
|
||||
name: 'GitHub Enterprise',
|
||||
value: 'ghe'
|
||||
}]
|
||||
},
|
||||
{
|
||||
name: 'apiUrl',
|
||||
type: 'input',
|
||||
message: 'Write your Enterprise url',
|
||||
suffix: chalk.blueBright(' e.g. https://MY_ENTERPRISE_DOMAIN/api/v3'),
|
||||
when: ({ apiUrlType }) => apiUrlType === 'ghe',
|
||||
validate: value => isUri(value) ? true : 'Please type a valid url'
|
||||
},
|
||||
{
|
||||
name: 'dataSource',
|
||||
type: 'list',
|
||||
message: 'Where shall I get the informations from?',
|
||||
choices: [{
|
||||
value: 'issues',
|
||||
name: 'Issues (Time based)'
|
||||
},
|
||||
{
|
||||
value: 'milestones',
|
||||
name: 'Issues (Milestone based)'
|
||||
},
|
||||
{
|
||||
value: 'commits',
|
||||
name: 'Commits'
|
||||
},
|
||||
{
|
||||
value: 'prs',
|
||||
name: 'Pull Requests'
|
||||
}]
|
||||
},
|
||||
{
|
||||
name: 'prefix',
|
||||
type: 'input',
|
||||
suffix: chalk.blueBright(' e.g. v'),
|
||||
message: 'Do you want to add a prefix to release titles?'
|
||||
},
|
||||
{
|
||||
name: 'includeMessages',
|
||||
type: 'list',
|
||||
message: 'Which type of commits do you want to include?',
|
||||
choices: [{
|
||||
value: 'merges',
|
||||
name: 'Merges'
|
||||
},
|
||||
{
|
||||
value: 'commits',
|
||||
name: 'Commits'
|
||||
},
|
||||
{
|
||||
value: 'all',
|
||||
name: 'All'
|
||||
}],
|
||||
when: ({ dataSource }) => dataSource === 'commits'
|
||||
},
|
||||
{
|
||||
name: 'ignoreCommitsWithConfirm',
|
||||
type: 'confirm',
|
||||
default: false,
|
||||
message: 'Do you want to ignore commits containing certain words?',
|
||||
when: ({ dataSource }) => dataSource === 'commits'
|
||||
},
|
||||
{
|
||||
name: 'ignoreCommitsWith',
|
||||
type: 'input',
|
||||
message: 'Which ones? Use commas to separate.',
|
||||
suffix: chalk.blueBright(' e.g. changelog,release'),
|
||||
when: ({ ignoreCommitsWithConfirm, dataSource }) => dataSource === 'commits' && ignoreCommitsWithConfirm,
|
||||
filter: value => value.replace(/\s/g).split(',')
|
||||
},
|
||||
{
|
||||
name: 'ignoreLabelsConfirm',
|
||||
type: 'confirm',
|
||||
default: false,
|
||||
message: 'Do you want to not output certain labels in the notes?',
|
||||
when: ({ dataSource }) => Array.isArray(labels) && dataSource !== 'commits'
|
||||
},
|
||||
{
|
||||
name: 'ignoreLabels',
|
||||
type: 'checkbox',
|
||||
message: 'Select the labels that should be excluded',
|
||||
when: ({ ignoreLabelsConfirm }) => ignoreLabelsConfirm,
|
||||
choices: Array.isArray(labels) && labels.map(({ name }) => name)
|
||||
},
|
||||
{
|
||||
name: 'ignoreIssuesWithConfirm',
|
||||
type: 'confirm',
|
||||
message: 'Do you want to ignore issues/prs that have certain labels?',
|
||||
default: false,
|
||||
when: ({ dataSource }) => Array.isArray(labels) && dataSource !== 'commits'
|
||||
},
|
||||
{
|
||||
name: 'ignoreIssuesWith',
|
||||
type: 'checkbox',
|
||||
message: 'Select the labels that should exclude the issue',
|
||||
when: ({ ignoreIssuesWithConfirm }) => ignoreIssuesWithConfirm,
|
||||
choices: Array.isArray(labels) && labels.map(({ name }) => name)
|
||||
},
|
||||
{
|
||||
name: 'onlyMilestones',
|
||||
type: 'confirm',
|
||||
message: 'Do you want to only include issues/prs that belong to a milestone?',
|
||||
default: false,
|
||||
when: ({ dataSource }) => dataSource === 'issues' || dataSource === 'prs'
|
||||
},
|
||||
{
|
||||
name: 'ignoreTagsWithConfirm',
|
||||
type: 'confirm',
|
||||
default: false,
|
||||
message: 'Do you want to ignore tags containing certain words?'
|
||||
},
|
||||
{
|
||||
name: 'ignoreTagsWith',
|
||||
type: 'input',
|
||||
message: 'Which ones? Use commas to separate',
|
||||
suffix: chalk.blueBright(' e.g. -rc,-alpha,test'),
|
||||
filter: value => value.replace(/\s/g).split(','),
|
||||
when: ({ ignoreTagsWithConfirm }) => ignoreTagsWithConfirm
|
||||
},
|
||||
{
|
||||
name: 'groupBy',
|
||||
type: 'list',
|
||||
message: 'Do you want to group your notes?',
|
||||
when: ({ dataSource }) => dataSource !== 'commits',
|
||||
choices: [{
|
||||
value: false,
|
||||
name: 'No'
|
||||
},
|
||||
{
|
||||
value: 'label',
|
||||
name: 'Use existing labels'
|
||||
},
|
||||
{
|
||||
value: {},
|
||||
name: 'Use custom configuration'
|
||||
}]
|
||||
},
|
||||
{
|
||||
name: 'milestoneMatch',
|
||||
type: 'input',
|
||||
default: 'Release {{tag_name}}',
|
||||
message: 'How can I link your tags to Milestone titles?',
|
||||
when: ({ dataSource }) => dataSource === 'milestones'
|
||||
},
|
||||
{
|
||||
name: 'changelogFilename',
|
||||
default: 'CHANGELOG.md',
|
||||
message: 'What file name do you want for your changelog?',
|
||||
vaidate: value => {
|
||||
console.log(utils.getFileExtension(value));
|
||||
return utils.getFileExtension(value) === 'md' ? true : 'Has to be a markdown file!';
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'fileExist',
|
||||
type: 'list',
|
||||
message: 'Looks like you already have a configuration file. What do you want me to do?',
|
||||
choices: [{
|
||||
value: 'abort',
|
||||
name: 'Oops, stop this'
|
||||
},
|
||||
{
|
||||
value: 'override',
|
||||
name: 'Override my existing file'
|
||||
},
|
||||
{
|
||||
value: 'merge',
|
||||
name: 'Merge these settings over existing ones'
|
||||
}],
|
||||
when: () => Object.keys(utils.getConfigFromFile(process.cwd())).length > 0
|
||||
},
|
||||
{
|
||||
name: 'fileType',
|
||||
type: 'list',
|
||||
message: 'Which extension would you like for your file?',
|
||||
choices: utils.getFileTypes(),
|
||||
when: ({ fileExist }) => fileExist !== 'abort'
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
const configure = async() => {
|
||||
const questions = await getQuestions();
|
||||
process.stdout.write('\n🤖 : Hello, I\'m going to ask a couple of questions, to set gren up!\n\n');
|
||||
|
||||
return prompt(questions);
|
||||
};
|
||||
|
||||
export default configure;
|
|
@ -1,6 +1,8 @@
|
|||
const chalk = require('chalk');
|
||||
const fs = require('fs');
|
||||
const ora = require('ora');
|
||||
const YAML = require('json2yaml');
|
||||
const { js_beautify: beautify } = require('js-beautify');
|
||||
require('require-yaml');
|
||||
|
||||
/**
|
||||
|
@ -190,16 +192,42 @@ function requireConfig(filepath) {
|
|||
* @return {Object} The configuration from the first found file or empty object
|
||||
*/
|
||||
function getConfigFromFile(path) {
|
||||
return [
|
||||
'.grenrc.yml',
|
||||
'.grenrc.json',
|
||||
'.grenrc.yaml',
|
||||
'.grenrc.js',
|
||||
'.grenrc'
|
||||
]
|
||||
return getFileTypes()
|
||||
.reduce((carry, filename) => carry || requireConfig(path + '/' + filename), false) || {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the extension of a filename
|
||||
*
|
||||
* @param {string} filename
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
function getFileExtension(filename) {
|
||||
return filename.slice((Math.max(0, filename.lastIndexOf('.')) || Infinity) + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the content for a configuratio file, based on extension and data
|
||||
*
|
||||
* @param {string} path
|
||||
* @param {Object} data
|
||||
*
|
||||
* @return {string} File content
|
||||
*/// istanbul ignore next
|
||||
function writeConfigToFile(path, data) {
|
||||
const extension = getFileExtension(getFileNameFromPath(path));
|
||||
const dataType = {
|
||||
yml: content => YAML.stringify(content),
|
||||
yaml: content => YAML.stringify(content),
|
||||
json: content => beautify(JSON.stringify(content)),
|
||||
none: content => beautify(JSON.stringify(content)),
|
||||
js: content => beautify(`module.exports = ${JSON.stringify(content)}`)
|
||||
};
|
||||
|
||||
return dataType[extension || 'none'](data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the filename from a path
|
||||
*
|
||||
|
@ -210,8 +238,49 @@ function getConfigFromFile(path) {
|
|||
*
|
||||
* @return {string}
|
||||
*/
|
||||
function getFileNameFromPath(path) {
|
||||
return path.split('\\').pop().split('/').pop();
|
||||
function getFileNameFromPath(path = '') {
|
||||
return path.replace(/^.*[\\/]/, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file types for the configuration
|
||||
*
|
||||
* @since 0.13.0
|
||||
*
|
||||
* @return {Array}
|
||||
*/
|
||||
function getFileTypes() {
|
||||
return [
|
||||
'.grenrc.yml',
|
||||
'.grenrc.json',
|
||||
'.grenrc.yaml',
|
||||
'.grenrc.js',
|
||||
'.grenrc'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all the configuration files
|
||||
*
|
||||
* @since 0.13.0
|
||||
*
|
||||
* @param {Boolean} confirm Necessary to force the function.
|
||||
*/
|
||||
function cleanConfig(confirm, path = process.cwd()) {
|
||||
if (confirm !== true) {
|
||||
return;
|
||||
}
|
||||
|
||||
getFileTypes().forEach(fileName => {
|
||||
const file = `${path}/${fileName}`;
|
||||
if (!fs.existsSync(file)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fs.unlinkSync(file);
|
||||
|
||||
return file;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -221,15 +290,20 @@ function noop() {}
|
|||
|
||||
// Allow nodeunit to work. Has to be fixed.
|
||||
module.exports = {
|
||||
sortObject,
|
||||
printTask,
|
||||
task,
|
||||
cleanConfig,
|
||||
clearTasks,
|
||||
dashToCamelCase,
|
||||
isInRange,
|
||||
convertStringToArray,
|
||||
dashToCamelCase,
|
||||
formatDate,
|
||||
requireConfig,
|
||||
getConfigFromFile,
|
||||
noop
|
||||
getFileExtension,
|
||||
getFileNameFromPath,
|
||||
getFileTypes,
|
||||
isInRange,
|
||||
noop,
|
||||
printTask,
|
||||
requireConfig,
|
||||
sortObject,
|
||||
task,
|
||||
writeConfigToFile
|
||||
};
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
11
package.json
11
package.json
|
@ -45,19 +45,28 @@
|
|||
"homepage": "https://github.com/alexcanessa/github-release-notes#readme",
|
||||
"dependencies": {
|
||||
"babel-runtime": "^6.26.0",
|
||||
"base-64": "^0.1.0",
|
||||
"chalk": "^2.1.0",
|
||||
"commander": "^2.11.0",
|
||||
"connectivity": "^1.0.0",
|
||||
"github-api": "^3.0.0",
|
||||
"inquirer": "^3.3.0",
|
||||
"install": "^0.10.1",
|
||||
"js-beautify": "^1.7.4",
|
||||
"json2yaml": "^1.1.0",
|
||||
"minimist": "^1.2.0",
|
||||
"node-fetch": "^1.7.3",
|
||||
"npm": "^5.5.1",
|
||||
"object-assign-deep": "^0.3.1",
|
||||
"ora": "^1.3.0",
|
||||
"require-yaml": "0.0.1"
|
||||
"require-yaml": "0.0.1",
|
||||
"valid-url": "^1.0.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.26.0",
|
||||
"babel-eslint": "^8.0.0",
|
||||
"babel-plugin-istanbul": "^4.1.5",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
||||
"babel-plugin-transform-runtime": "^6.23.0",
|
||||
"babel-preset-env": "^1.4.0",
|
||||
"babel-preset-es2015": "^6.24.1",
|
||||
|
|
|
@ -87,13 +87,8 @@ describe('_utils.js', () => {
|
|||
});
|
||||
|
||||
describe('requireConfig', () => {
|
||||
const files = [
|
||||
'.grenrc.yml',
|
||||
'.grenrc.json',
|
||||
'.grenrc.yaml',
|
||||
'.grenrc.js',
|
||||
'.grenrc'
|
||||
].map(file => `${process.cwd()}/test/.temp/${file}`);
|
||||
const files = utils.getFileTypes()
|
||||
.map(file => `${process.cwd()}/test/.temp/${file}`);
|
||||
const simpleObject = {
|
||||
a: 1,
|
||||
b: 2
|
||||
|
@ -162,6 +157,50 @@ describe('_utils.js', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('getFileExtension', () => {
|
||||
it('Should return the extension of the file', () => {
|
||||
assert.deepEqual(utils.getFileExtension('filename.txt'), 'txt', 'Just the filename');
|
||||
assert.deepEqual(utils.getFileExtension('filename.something.txt'), 'txt', 'Filename with dots');
|
||||
assert.deepEqual(utils.getFileExtension('.filename.txt'), 'txt', 'Filename that starts with dots');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getFileNameFromPath', () => {
|
||||
it('Should return the filename', () => {
|
||||
assert.deepEqual(utils.getFileNameFromPath('path/to/filename.txt'), 'filename.txt', 'Simple path');
|
||||
assert.deepEqual(utils.getFileNameFromPath('path/to/.filename.txt'), '.filename.txt', 'Simple path and filename with dot');
|
||||
assert.deepEqual(utils.getFileNameFromPath('path/to\\ a \\(complex\\)/.filename.txt'), '.filename.txt', 'Complex path and filename with dot');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getFileTypes', () => {
|
||||
it('Should return an Array', () => {
|
||||
assert.isArray(utils.getFileTypes(), 'Call the function');
|
||||
});
|
||||
});
|
||||
|
||||
describe('cleanConfig', () => {
|
||||
const path = process.cwd() + '/test/.temp';
|
||||
const fileContent = {
|
||||
a: 1,
|
||||
b: 2
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
fs.writeFileSync(`${path}/.grenrc`, JSON.stringify(fileContent));
|
||||
});
|
||||
|
||||
it('Should not do anything', () => {
|
||||
assert.isNotOk(utils.cleanConfig(), 'When no confirm has passed');
|
||||
assert.isNotOk(utils.cleanConfig('hey'), 'When confirm has passed, but not true');
|
||||
});
|
||||
|
||||
it('Should delete any config file present in path', () => {
|
||||
utils.cleanConfig(true, path);
|
||||
assert.isNotOk(fs.existsSync(`${path}/.grenrc`));
|
||||
});
|
||||
});
|
||||
|
||||
describe('noop', () => {
|
||||
it('Should be a function that returns undefined', () => {
|
||||
assert.deepEqual(utils.noop(), undefined, 'Running the function');
|
||||
|
|
Загрузка…
Ссылка в новой задаче