-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial implementation, tests, readme
- Loading branch information
Showing
19 changed files
with
583 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
{ | ||
"root": true, | ||
|
||
"extends": "@ljharb/eslint-config/node/20", | ||
|
||
"rules": { | ||
"complexity": "off", | ||
"max-lines-per-function": "off", | ||
"max-statements": "off", | ||
"no-magic-numbers": "off", | ||
"sort-keys": "off", | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
# These are supported funding model platforms | ||
|
||
github: [ljharb] | ||
patreon: # Replace with a single Patreon username | ||
open_collective: # Replace with a single Open Collective username | ||
ko_fi: # Replace with a single Ko-fi username | ||
tidelift: npm/@ljharb/coauthors | ||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry | ||
liberapay: # Replace with a single Liberapay username | ||
issuehunt: # Replace with a single IssueHunt username | ||
otechie: # Replace with a single Otechie username | ||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
name: 'Tests: pretest/posttest' | ||
|
||
on: [pull_request, push] | ||
|
||
jobs: | ||
tests: | ||
uses: ljharb/actions/.github/workflows/pretest.yml@main |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
name: 'Tests: node.js >= 22.4' | ||
|
||
on: [pull_request, push] | ||
|
||
jobs: | ||
tests: | ||
uses: ljharb/actions/.github/workflows/node.yml@main | ||
with: | ||
range: '>= 22.4' | ||
type: minors | ||
command: npm run tests-only |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
name: Automatic Rebase | ||
|
||
on: [pull_request_target] | ||
|
||
jobs: | ||
_: | ||
uses: ljharb/actions/.github/workflows/rebase.yml@main | ||
secrets: | ||
token: ${{ secrets.GITHUB_TOKEN }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
name: Require “Allow Edits” | ||
|
||
on: [pull_request_target] | ||
|
||
jobs: | ||
_: | ||
name: "Require “Allow Edits”" | ||
|
||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- uses: ljharb/require-allow-edits@main |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -133,3 +133,5 @@ dist | |
npm-shrinkwrap.json | ||
package-lock.json | ||
yarn.lock | ||
|
||
.npmignore |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,52 @@ | ||
# coauthors | ||
# @ljharb/coauthors <sup>[![Version Badge][npm-version-svg]][package-url]</sup> | ||
|
||
[![github actions][actions-image]][actions-url] | ||
[![coverage][codecov-image]][codecov-url] | ||
[![License][license-image]][license-url] | ||
[![Downloads][downloads-image]][downloads-url] | ||
|
||
[![npm badge][npm-badge-png]][package-url] | ||
|
||
A cli to generate a complete git co-authors list, including existing co-authors, for use in a commit message. | ||
|
||
## Usage | ||
|
||
```sh | ||
npx @ljharb/coauthors # if not installed | ||
|
||
coauthors # if installed and in the PATH | ||
``` | ||
|
||
```sh | ||
$ coauthors --help | ||
Usage: | ||
commit-to-co-authors <remote> | ||
|
||
`remote` defaults to `origin`. | ||
``` | ||
|
||
## Install | ||
|
||
``` | ||
npm install --save-dev @ljharb/coauthors | ||
``` | ||
|
||
## License | ||
|
||
MIT | ||
|
||
[package-url]: https://npmjs.org/package/@ljharb/coauthors | ||
[npm-version-svg]: https://versionbadg.es/ljharb/coauthors.svg | ||
[deps-svg]: https://david-dm.org/ljharb/coauthors.svg | ||
[deps-url]: https://david-dm.org/ljharb/coauthors | ||
[dev-deps-svg]: https://david-dm.org/ljharb/coauthors/dev-status.svg | ||
[dev-deps-url]: https://david-dm.org/ljharb/coauthors#info=devDependencies | ||
[npm-badge-png]: https://nodei.co/npm/@ljharb/coauthors.png?downloads=true&stars=true | ||
[license-image]: https://img.shields.io/npm/l/@ljharb/coauthors.svg | ||
[license-url]: LICENSE | ||
[downloads-image]: https://img.shields.io/npm/dm/@ljharb/coauthors.svg | ||
[downloads-url]: https://npm-stat.com/charts.html?package=@ljharb/coauthors | ||
[codecov-image]: https://codecov.io/gh/ljharb/coauthors/branch/main/graphs/badge.svg | ||
[codecov-url]: https://app.codecov.io/gh/ljharb/coauthors/ | ||
[actions-image]: https://img.shields.io/endpoint?url=https://github-actions-badge-u3jn4tfpocch.runkit.sh/ljharb/coauthors | ||
[actions-url]: https://github.com/ljharb/coauthors/actions |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
#! /usr/bin/env node | ||
|
||
import { readFile } from 'fs/promises'; | ||
import path from 'path'; | ||
|
||
import validateRemote from './validateRemote.mjs'; | ||
import getResults from './results.mjs'; | ||
|
||
import pargs from './pargs.mjs'; | ||
|
||
async function getHelpText() { | ||
return `${await readFile(path.join(import.meta.dirname, './help.txt'), 'utf-8')}`; | ||
} | ||
|
||
const { | ||
values: { help }, | ||
positionals, | ||
errors, | ||
} = await pargs( | ||
import.meta.filename, | ||
{ | ||
options: { | ||
help: { type: 'boolean' }, | ||
}, | ||
allowPositionals: 1, | ||
}, | ||
); | ||
|
||
const remote = validateRemote(positionals[0] ?? 'origin'); | ||
|
||
if (typeof remote !== 'string') { | ||
errors.push(remote.error); | ||
} | ||
|
||
if (help || errors.length > 0) { | ||
const helpText = await getHelpText(); | ||
if (errors.length === 0) { | ||
console.log(helpText); | ||
} else { | ||
console.error(`${helpText}${errors.length === 0 ? '' : '\n'}`); | ||
|
||
process.exitCode ||= parseInt('1'.repeat(errors.length), 2); | ||
errors.forEach((error) => console.error(error)); | ||
} | ||
|
||
process.exit(); | ||
} | ||
|
||
// eslint-disable-next-line no-extra-parens | ||
const results = Array.from(getResults(/** @type {string} */ (remote)), (x) => `Co-authored-by: ${x}`); | ||
|
||
console.log(results.join('\n')); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { execSync } from 'child_process'; | ||
|
||
/** @type {(remote: string) => string} */ | ||
export default function getDefaultBranch(remote) { | ||
const gitResult = `${execSync(`git rev-parse --abbrev-ref ${remote}/HEAD`)}`.trim(); | ||
|
||
const match = (/\/(?<defaultBranch>\S+)$/).exec(gitResult); | ||
|
||
return match?.groups?.defaultBranch ?? 'main'; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
Usage: | ||
commit-to-co-authors <remote> | ||
|
||
`remote` defaults to `origin`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
import { parseArgs } from 'util'; | ||
import { realpathSync } from 'fs'; | ||
|
||
/** @typedef {import('util').ParseArgsConfig} ParseArgsConfig */ | ||
|
||
/** @typedef {(Error | TypeError) & { code: 'ERR_PARSE_ARGS_UNKNOWN_OPTION' | 'ERR_PARSE_ARGS_INVALID_OPTION_VALUE' | 'ERR_INVALID_ARG_TYPE' | 'ERR_INVALID_ARG_VALUE' | 'ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL'}} ParseArgsError */ | ||
|
||
/** @type {(e: unknown) => e is ParseArgsError} */ | ||
function isParseArgsError(e) { | ||
return !!e | ||
&& typeof e === 'object' | ||
&& 'code' in e | ||
&& ( | ||
e.code === 'ERR_PARSE_ARGS_UNKNOWN_OPTION' | ||
|| e.code === 'ERR_PARSE_ARGS_INVALID_OPTION_VALUE' | ||
|| e.code === 'ERR_INVALID_ARG_TYPE' | ||
|| e.code === 'ERR_INVALID_ARG_VALUE' | ||
|| e.code === 'ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL' | ||
); | ||
} | ||
/** @typedef {Omit<ParseArgsConfig, 'args' | 'strict' | 'allowPositionals'> & { allowPositionals?: boolean | number }} PargsConfig */ | ||
|
||
/** @type {(entrypointPath: ImportMeta['filename'], obj: PargsConfig) => Promise<{ errors: string[] } & ReturnType<typeof parseArgs>>} */ | ||
export default async function pargs(entrypointPath, obj) { | ||
const argv = process.argv.flatMap((arg) => { | ||
try { | ||
const realpathedArg = realpathSync(arg); | ||
if ( | ||
realpathedArg === process.execPath | ||
|| realpathedArg === entrypointPath | ||
) { | ||
return []; | ||
} | ||
} catch (e) { /**/ } | ||
return arg; | ||
}); | ||
|
||
if ('help' in obj) { | ||
throw new TypeError('The "help" option is reserved'); | ||
} | ||
|
||
/** @type {ParseArgsConfig & { tokens: true }} */ | ||
const newObj = { | ||
args: argv, | ||
...obj, | ||
options: { | ||
...obj.options, | ||
help: { | ||
default: false, | ||
type: 'boolean', | ||
}, | ||
}, | ||
tokens: true, | ||
// @ts-expect-error blocked on @types/node v22 | ||
allowNegative: true, | ||
allowPositionals: typeof obj.allowPositionals !== 'undefined', | ||
strict: true, | ||
}; | ||
|
||
const errors = []; | ||
|
||
try { | ||
const { tokens, ...results } = parseArgs(newObj); | ||
|
||
const posCount = typeof obj.allowPositionals === 'number' ? obj.allowPositionals : obj.allowPositionals ? Infinity : 0; | ||
if (results.positionals.length > posCount) { | ||
errors.push(`Only ${posCount} positional arguments allowed; got ${results.positionals.length}`); | ||
} | ||
|
||
/** @typedef {Extract<typeof tokens[number], { kind: 'option' }>} OptionToken */ | ||
const optionTokens = tokens.filter(/** @type {(token: typeof tokens[number]) => token is OptionToken} */ (token) => token.kind === 'option'); | ||
|
||
const bools = obj.options ? Object.entries(obj.options).filter(([, { type }]) => type === 'boolean') : []; | ||
const boolMap = new Map(bools); | ||
for (let i = 0; i < optionTokens.length; i += 1) { | ||
const { name, value } = optionTokens[i]; | ||
if (boolMap.has(name) && typeof value !== 'boolean' && typeof value !== 'undefined') { | ||
errors.push(`Error: Argument --${name} must be a boolean`); | ||
} | ||
} | ||
|
||
const passedArgs = new Set(optionTokens.map(({ name, rawName }) => (rawName === '--no-help' ? rawName : name))); | ||
|
||
const groups = Object.groupBy(passedArgs, (x) => x.replace(/^no-/, '')); | ||
for (let i = 0; i < bools.length; i++) { | ||
const [key] = bools[i]; | ||
if ((groups[key]?.length ?? 0) > 1) { | ||
errors.push(`Error: Arguments \`--${key}\` and \`--no-${key}\` are mutually exclusive`); | ||
} | ||
if (passedArgs.has(`no-${key}`)) { | ||
// @ts-expect-error | ||
results.values[key] = !results.values[`no-${key}`]; | ||
} | ||
// @ts-expect-error | ||
delete results.values[`no-${key}`]; | ||
} | ||
|
||
const knownOptions = obj.options ? Object.keys(obj.options) : []; | ||
const unknownArgs = knownOptions.length > 0 ? passedArgs.difference(new Set(knownOptions)) : passedArgs; | ||
if (unknownArgs.size > 0) { | ||
errors.push(`Error: Unknown option(s): ${Array.from(unknownArgs, (x) => `\`${x}\``).join(', ')}`); | ||
} | ||
|
||
return { | ||
errors, | ||
...results, | ||
...obj.tokens && { tokens }, | ||
}; | ||
} catch (e) { | ||
if (isParseArgsError(e)) { | ||
return { | ||
values: {}, | ||
positionals: [], | ||
errors: [`Error: ${e.message}`], | ||
}; | ||
} | ||
throw e; | ||
} | ||
} |
Oops, something went wrong.