Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@
"add-contributor": "kcd-scripts contributors add",
"build": "kcd-scripts build",
"lint": "kcd-scripts lint",
"test": "kcd-scripts test",
"test": "NODE_OPTIONS='--experimental-vm-modules' kcd-scripts test --watchAll=false",
"validate": "kcd-scripts validate",
"commit": "git-cz",
"start": "./dist/cli.js",
"dev": "./src/cli.js"
},
"husky": {
"hooks": {
"pre-commit": "kcd-scripts pre-commit"
"pre-commit": "NODE_OPTIONS='--experimental-vm-modules' kcd-scripts pre-commit"
}
},
"repository": {
Expand Down
135 changes: 74 additions & 61 deletions src/cli.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#!/usr/bin/env node
/* eslint-disable no-console */

const path = require('path')
const yargs = require('yargs')
Expand All @@ -15,39 +14,47 @@ const updateContributors = require('./contributors')
const cwd = process.cwd()
const defaultRCFile = path.join(cwd, '.all-contributorsrc')

const yargv = yargs
.scriptName('all-contributors')
.help('help')
.alias('h', 'help')
.alias('v', 'version')
.version()
.recommendCommands()
.command('generate', `Generate the list of contributors\n\nUSAGE: all-contributors generate`)
.command('add', `Add a new contributor\n\nUSAGE: all-contributors add <username> <comma-separated contributions>`)
.command('init', `Prepare the project to be used with this tool\n\nUSAGE: all-contributors init`)
.command(
'check',
`Compare contributors from the repository with the ones credited in .all-contributorsrc'\n\nUSAGE: all-contributors check`)
.boolean('commit')
.default('files', ['README.md'])
.default('contributorsPerLine', 7)
.option('contributorsSortAlphabetically', {
type: 'boolean',
default: false,
description:
'Sort the list of contributors alphabetically in the generated list',
})
.default('contributors', [])
.default('config', defaultRCFile)
.config('config', configPath => {
try {
return util.configFile.readConfig(configPath)
} catch (error) {
if (error instanceof SyntaxError || configPath !== defaultRCFile) {
onError(error)
}
}
}).argv
function getArgs() {
return yargs
.scriptName('all-contributors')
.option('config', {
alias: 'c',
type: 'string',
default: defaultRCFile,
description: 'Path to config file',
})
.option('contributorsSortAlphabetically', {
type: 'boolean',
default: false,
description:
'Sort the list of contributors alphabetically in the generated list',
})
.help('help')
.alias('h', 'help')
.alias('v', 'version')
.version()
.recommendCommands()
.command(
'generate',
`Generate the list of contributors\n\nUSAGE: all-contributors generate`,
)
.command(
'add',
`Add a new contributor\n\nUSAGE: all-contributors add <username> <comma-separated contributions>`,
)
.command(
'init',
`Prepare the project to be used with this tool\n\nUSAGE: all-contributors init`,
)
.command(
'check',
`Compare contributors from the repository with the ones credited in .all-contributorsrc'\n\nUSAGE: all-contributors check`,
)
.boolean('commit')
.default('files', ['README.md'])
.default('contributorsPerLine', 7)
.default('contributors', []).argv
}

function startGeneration(argv) {
return Promise.all(
Expand All @@ -61,23 +68,28 @@ function startGeneration(argv) {
)
}

function addContribution(argv) {
util.configFile.readConfig(argv.config) // ensure the config file exists
async function addContribution(argv) {
// ensure the config file exists
await util.configFile.readConfig(argv.config)

const username = argv._[1] === undefined ? undefined : String(argv._[1])
const contributions = argv._[2]

// Add or update contributor in the config file
return updateContributors(argv, username, contributions).then(data => {
argv.contributors = data.contributors
return startGeneration(argv).then(() => {
if (argv.commit) {
return util.git.commit(argv, data)
}
})
})
const data = updateContributors(argv, username, contributions)

argv.contributors = data.contributors

await startGeneration(argv)

// Commit if configured
if (argv.commit) {
return util.git.commit(argv, data)
}
}

function checkContributors(argv) {
const configData = util.configFile.readConfig(argv.config)
async function checkContributors(argv) {
const configData = await util.configFile.readConfig(argv.config)

return repo
.getContributors(
Expand Down Expand Up @@ -123,14 +135,6 @@ function checkContributors(argv) {
})
}

function onError(error) {
if (error) {
console.error(error.stack || error.message || error)
process.exit(1)
}
process.exit(0)
}

function promptForCommand(argv) {
const questions = [
{
Expand Down Expand Up @@ -162,19 +166,28 @@ function promptForCommand(argv) {
})
}

promptForCommand(yargv)
.then(command => {
async function run() {
try {
const argv = getArgs()
const command = await promptForCommand(argv)

switch (command) {
case 'init':
return init()
case 'generate':
return startGeneration(yargv)
return startGeneration(argv)
case 'add':
return addContribution(yargv)
return addContribution(argv)
case 'check':
return checkContributors(yargv)
return checkContributors(argv)
default:
throw new Error(`Unknown command ${command}`)
}
})
.catch(onError)
} catch (e) {
/* eslint-disable-next-line no-console */
console.error(e.stack || e.message || e)
process.exit(1)
}
}

run()
44 changes: 24 additions & 20 deletions src/util/__tests__/config-file.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import configFile from '../config-file'
import {writeConfig, readConfig, writeContributors} from '../config-file'

const absentFile = './abc'
const absentConfileFileExpected = `Configuration file not found: ${absentFile}`
const absentConfigFileExpected = `Configuration file not found: ${absentFile}`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Praise] Nice catch 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I try to keep my clean up scope creeps to a minimum, but somethings i just hadddd to fix haha

const incompleteConfigFilePath = './.all-contributorsrc'
const NoOwnerConfigFile = {
projectOwner: '',
Expand Down Expand Up @@ -29,32 +29,36 @@ const NoFilesConfigFile = {
files: [],
}

test('Reading an absent configuration file throws a helpful error', () => {
expect(() => configFile.readConfig(absentFile)).toThrowError(
absentConfileFileExpected,
test('Reading an absent configuration file throws a helpful error', async () => {
await expect(readConfig(absentFile)).rejects.toThrowError(
absentConfigFileExpected,
)
})

test('Writing contributors in an absent configuration file throws a helpful error', async () => {
const resolvedError = await configFile
.writeContributors(absentFile, [])
.catch(e => e)
expect(resolvedError.message).toBe(absentConfileFileExpected)
await expect(writeContributors(absentFile, [])).rejects.toThrow(
absentConfigFileExpected,
)
})

test('Should throw error and not allow editing config file if project name or owner is not set', () => {
expect(() =>
configFile.writeConfig(incompleteConfigFilePath, NoOwnerConfigFile),
).toThrow(`Error! Project owner is not set in ${incompleteConfigFilePath}`)
expect(() =>
configFile.writeConfig(incompleteConfigFilePath, NoNameConfigFile),
).toThrow(`Error! Project name is not set in ${incompleteConfigFilePath}`)
test('Should throw error and not allow editing config file if project name or owner is not set', async () => {
await expect(
writeConfig(incompleteConfigFilePath, NoOwnerConfigFile),
).rejects.toThrow(
`Error! Project owner is not set in ${incompleteConfigFilePath}`,
)

await expect(
writeConfig(incompleteConfigFilePath, NoNameConfigFile),
).rejects.toThrow(
`Error! Project name is not set in ${incompleteConfigFilePath}`,
)
})

test(`throws if 'files' was overridden in .all-contributorsrc and is empty`, () => {
expect(() =>
configFile.writeConfig(incompleteConfigFilePath, NoFilesConfigFile),
).toThrow(
test(`throws if 'files' was overridden in .all-contributorsrc and is empty`, async () => {
await expect(
writeConfig(incompleteConfigFilePath, NoFilesConfigFile),
).rejects.toThrow(
`Error! Project files was overridden and is empty in ${incompleteConfigFilePath}`,
)
})
21 changes: 10 additions & 11 deletions src/util/__tests__/formatting.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import path from 'path'
import formatting from '../formatting'
import {formatConfig} from '../formatting'

const content = {contributors: [{id: 'abc123'}]}

Expand All @@ -19,17 +19,16 @@ const presentConfigFileExpected = `{
"id": "abc123"
}
]
}
`
}`

test('falls back to JSON.stringify when the configPath cannot resolve to a config', async () => {
const output = await formatConfig(absentFile, content)

test('falls back to JSON.stringify when the configPath cannot resolve to a config', () => {
expect(formatting.formatConfig(absentFile, content)).toBe(
absentConfigFileExpected,
)
expect(output).toBe(absentConfigFileExpected)
})

test('uses Prettier when the configPath can resolve to a config', () => {
expect(formatting.formatConfig(presentFile, content)).toBe(
presentConfigFileExpected,
)
test('uses Prettier when the configPath can resolve to a config', async () => {
const output = await formatConfig(presentFile, content)

expect(output).toBe(presentConfigFileExpected)
})
44 changes: 23 additions & 21 deletions src/util/config-file.js
Original file line number Diff line number Diff line change
@@ -1,40 +1,48 @@
const fs = require('fs')
const pify = require('pify')
const _ = require('lodash/fp')
const jf = require('json-fixer')
const {formatConfig} = require('./formatting')
import {readFile, writeFile} from 'fs/promises'
import jf from 'json-fixer'
import _ from 'lodash/fp'
import {formatConfig} from './formatting'

function readConfig(configPath) {
export async function readConfig(configPath) {
try {
const {data: config, changed} = jf(fs.readFileSync(configPath, 'utf-8'))
const configFileContents = await readFile(configPath, 'utf-8')
const {data: config, changed} = jf(configFileContents)

if (!('repoType' in config)) {
config.repoType = 'github'
}

if (!('commitConvention' in config)) {
config.commitConvention = 'angular'
}

if (changed) {
const formatterConfig = await formatConfig(configPath, config)
//Updates the file with fixes
fs.writeFileSync(configPath, formatConfig(configPath, config))
await writeFile(configPath, formatterConfig)
}

return config
} catch (error) {
if (error instanceof SyntaxError) {
throw new SyntaxError(
`Configuration file has malformed JSON: ${configPath}. Error:: ${error.message}`,
)
}

if (error.code === 'ENOENT') {
throw new Error(`Configuration file not found: ${configPath}`)
}

throw error
}
}

function writeConfig(configPath, content) {
export async function writeConfig(configPath, content) {
if (!content.projectOwner) {
throw new Error(`Error! Project owner is not set in ${configPath}`)
}

if (!content.projectName) {
throw new Error(`Error! Project name is not set in ${configPath}`)
}
Expand All @@ -44,25 +52,19 @@ function writeConfig(configPath, content) {
`Error! Project files was overridden and is empty in ${configPath}`,
)
}
return pify(fs.writeFile)(
configPath,
`${formatConfig(configPath, content)}\n`,
)

return writeFile(configPath, `${await formatConfig(configPath, content)}\n`)
}

function writeContributors(configPath, contributors) {
export async function writeContributors(configPath, contributors) {
let config

try {
config = readConfig(configPath)
config = await readConfig(configPath)
} catch (error) {
return Promise.reject(error)
}
const content = _.assign(config, {contributors})
return writeConfig(configPath, content)
}

module.exports = {
readConfig,
writeConfig,
writeContributors,
return writeConfig(configPath, content)
}
Loading
Loading