From 71ac26ba3a868daeec48b698f26f28b9fe84d34a Mon Sep 17 00:00:00 2001 From: Justin Dalrymple Date: Sun, 25 Jan 2026 13:39:23 -0500 Subject: [PATCH 1/2] Should fix tests # Conflicts: # package.json # src/init/__tests__/__snapshots__/add-contributors-list.js.snap --- package.json | 4 +- src/cli.js | 114 ++++++++++++++++-------------- src/util/__tests__/config-file.js | 44 ++++++------ src/util/__tests__/formatting.js | 21 +++--- src/util/config-file.js | 44 ++++++------ src/util/formatting.js | 23 +++--- src/util/git.js | 4 +- 7 files changed, 135 insertions(+), 119 deletions(-) diff --git a/package.json b/package.json index f7d17ec5..9354a7c5 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "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", @@ -24,7 +24,7 @@ }, "husky": { "hooks": { - "pre-commit": "kcd-scripts pre-commit" + "pre-commit": "NODE_OPTIONS='--experimental-vm-modules' kcd-scripts pre-commit" } }, "repository": { diff --git a/src/cli.js b/src/cli.js index 1c52e969..721f75f6 100755 --- a/src/cli.js +++ b/src/cli.js @@ -1,5 +1,4 @@ #!/usr/bin/env node -/* eslint-disable no-console */ const path = require('path') const yargs = require('yargs') @@ -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 `) - .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 `, + ) + .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( @@ -61,8 +68,8 @@ function startGeneration(argv) { ) } -function addContribution(argv) { - util.configFile.readConfig(argv.config) // ensure the config file exists +async function addContribution(argv) { + await util.configFile.readConfig(argv.config) // ensure the config file exists const username = argv._[1] === undefined ? undefined : String(argv._[1]) const contributions = argv._[2] // Add or update contributor in the config file @@ -76,8 +83,8 @@ function addContribution(argv) { }) } -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( @@ -123,14 +130,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 = [ { @@ -162,19 +161,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() diff --git a/src/util/__tests__/config-file.js b/src/util/__tests__/config-file.js index 128c68e1..368f3cb6 100644 --- a/src/util/__tests__/config-file.js +++ b/src/util/__tests__/config-file.js @@ -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}` const incompleteConfigFilePath = './.all-contributorsrc' const NoOwnerConfigFile = { projectOwner: '', @@ -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}`, ) }) diff --git a/src/util/__tests__/formatting.js b/src/util/__tests__/formatting.js index e9e26d55..86cd04ab 100644 --- a/src/util/__tests__/formatting.js +++ b/src/util/__tests__/formatting.js @@ -1,5 +1,5 @@ import path from 'path' -import formatting from '../formatting' +import {formatConfig} from '../formatting' const content = {contributors: [{id: 'abc123'}]} @@ -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) }) diff --git a/src/util/config-file.js b/src/util/config-file.js index df353a27..799f2cf1 100644 --- a/src/util/config-file.js +++ b/src/util/config-file.js @@ -1,22 +1,27 @@ -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) { @@ -24,17 +29,20 @@ function readConfig(configPath) { `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}`) } @@ -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) } diff --git a/src/util/formatting.js b/src/util/formatting.js index eec21196..4590fcd2 100644 --- a/src/util/formatting.js +++ b/src/util/formatting.js @@ -1,21 +1,24 @@ -function formatConfig(configPath, content) { +import prettier from 'prettier' + +export async function formatConfig(configPath, content) { const stringified = JSON.stringify(content, null, 2) + try { - const prettier = require('prettier') - const prettierConfig = prettier.resolveConfig.sync(configPath, { + const prettierConfig = await prettier.resolveConfig(configPath, { useCache: false, }) - return prettierConfig - ? prettier.format(stringified, {...prettierConfig, parser: 'json'}) - : stringified + if (!prettierConfig) return stringified + + const formattedOutput = await prettier.format(stringified, { + ...prettierConfig, + parser: 'json', + }) + + return formattedOutput.trimEnd() } catch (error) { // If Prettier can't be required or throws in general, // assume it's not usable and we should fall back to JSON.stringify return stringified } } - -module.exports = { - formatConfig, -} diff --git a/src/util/git.js b/src/util/git.js index e082925f..b2f8dbc5 100644 --- a/src/util/git.js +++ b/src/util/git.js @@ -53,12 +53,12 @@ const spawnGitCommand = pify((args, cb) => { }) }) -function commit(options, data) { +async function commit(options, data) { const files = options.files.concat(options.config) const absolutePathFiles = files.map(file => { return path.resolve(process.cwd(), file) }) - const config = readConfig(options.config) + const config = await readConfig(options.config) const commitConvention = conventions[config.commitConvention] return spawnGitCommand(['add'].concat(absolutePathFiles)).then(() => { From cc537b76b92e9bb567e8dd1632d6ae98ed1c4fa0 Mon Sep 17 00:00:00 2001 From: Justin Dalrymple Date: Sun, 25 Jan 2026 14:04:05 -0500 Subject: [PATCH 2/2] Updating spacing --- src/cli.js | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/cli.js b/src/cli.js index 721f75f6..89d66edd 100755 --- a/src/cli.js +++ b/src/cli.js @@ -69,18 +69,23 @@ function startGeneration(argv) { } async function addContribution(argv) { - await util.configFile.readConfig(argv.config) // ensure the config file exists + // 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) + } } async function checkContributors(argv) {