From 8d42ff087eb7e99b4952550cf64eb05aad958692 Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Tue, 9 Feb 2021 23:03:39 -0600 Subject: [PATCH] feat: support pkg@range option, tokens in ~/.config/npm-fetch-changelog.json --- README.md | 17 ++++- package.json | 2 +- src/cli.js | 104 ++++++++++++++++++++++++++++ src/getConfig.js | 47 +++++++++++++ src/getNpmToken.js | 23 ------- src/index.js | 166 ++++++++++++--------------------------------- 6 files changed, 210 insertions(+), 149 deletions(-) create mode 100755 src/cli.js create mode 100644 src/getConfig.js delete mode 100644 src/getNpmToken.js diff --git a/README.md b/README.md index 88fa58e..12d422e 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,15 @@ personal access token, `npm-fetch-changelog` will use it when requesting GitHub the npm token from your `~/.npmrc`, so that it can get information for private packages you request. +You can also store one or both of these tokesn in `~/.config/npm-fetch-changelog.json`: + +```json +{ + "githubToken": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "npmToken": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + # CLI ``` @@ -46,7 +55,7 @@ npm i -g npm-fetch-changelog ``` ``` -npm-fetch-changelog +npm-fetch-changelog [@] ``` Prints changelog entries fetched from GitHub for each @@ -58,6 +67,12 @@ version released on npm in the given range. semver version range to get changelog entries for, e.g. `^7.0.0` (defaults to `>` the version installed in the working directory, if it exists) +You can also pass the range together with the package name (you might need to quote it): + +``` +npm-fetch-changelog 'foo@>=2' +``` + ### `--json` output JSON instead of Markdown diff --git a/package.json b/package.json index 54c8562..29a3272 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "fetch the changelog for an npm package from GitHub", "main": "index.js", "bin": { - "npm-fetch-changelog": "./index.js" + "npm-fetch-changelog": "./cli.js" }, "engines": { "node": ">=8.0.0" diff --git a/src/cli.js b/src/cli.js new file mode 100755 index 0000000..004adff --- /dev/null +++ b/src/cli.js @@ -0,0 +1,104 @@ +#!/usr/bin/env node +// @flow + +import chalk from 'chalk' +import yargs from 'yargs' +import path from 'path' +import { fetchChangelog } from './index' +import { type Release } from './parseChangelog' + +/* eslint-env node */ +const { argv } = yargs + .usage( + `Usage: $0 [@] + +Prints changelog entries for an npm package from GitHub. +(Other repository hosts aren't currently supported.)` + ) + .option('r', { + alias: 'range', + describe: 'semver version range to get changelog entries for', + type: 'string', + }) + .option('json', { + describe: 'output json', + type: 'boolean', + }) + .option('prereleases', { + describe: 'include prerelease versions', + type: 'boolean', + default: false, + }) + .default('minor', true) + .boolean('minor') + .hide('minor') + .describe('no-minor', 'exclude minor versions') + .default('patch', undefined) + .boolean('patch') + .hide('patch') + .describe('no-patch', 'exclude patch versions') + .hide('version') + +let { + _: [pkg], + range, + includePrereleases: prerelease, + minor, + patch, + json, +} = argv + +if (!pkg) { + yargs.showHelp() + process.exit(1) +} +if (!range) { + const match = /^(@?[^@]+)@(.+)$/.exec(pkg) + if (match) { + pkg = match[1] + range = match[2] + } else { + try { + // $FlowFixMe + const { version } = require(require.resolve( + path.join(pkg, 'package.json'), + { + paths: [process.cwd()], + } + )) + range = `>${version}` + } catch (error) { + // ignore + } + } +} + +fetchChangelog(pkg, { + include: { + range, + prerelease, + minor, + patch, + }, +}).then( + (changelog: { [version: string]: Release }) => { + if (json) { + process.stdout.write(JSON.stringify(changelog, null, 2)) + } else { + for (const version in changelog) { + if (!changelog.hasOwnProperty(version)) continue + const { header, body, error } = changelog[version] + process.stdout.write(chalk.bold(header) + '\n\n') + if (body) process.stdout.write(body + '\n\n') + if (error) { + process.stdout.write(`Failed to get changelog: ${error.stack}\n\n`) + } + } + } + process.exit(0) + }, + (error: Error) => { + process.stderr.write(error.stack + '\n') + process.exit(1) + } +) diff --git a/src/getConfig.js b/src/getConfig.js new file mode 100644 index 0000000..54af757 --- /dev/null +++ b/src/getConfig.js @@ -0,0 +1,47 @@ +// @flow + +import * as fs from 'fs-extra' +import path from 'path' +import os from 'os' +import once from './util/once' + +const configFilePath = path.join( + os.homedir(), + '.config', + 'npm-fetch-changelog.json' +) + +type Config = { + githubToken?: string, + npmToken?: string, +} + +const getConfig = once(async function getConfig(): Promise { + const result: Config = {} + + await Promise.all([ + fs + .readJson(configFilePath) + .then((raw: any) => { + if (typeof raw.githubToken === 'string') + result.githubToken = raw.githubToken + if (typeof raw.npmToken === 'string') result.npmToken = raw.npmToken + }) + .catch(() => {}), + fs + .readFile(path.join(os.homedir(), '.npmrc'), 'utf8') + .then((npmrc: any) => { + const match = /:_authToken=([a-f0-9]{8}(-[a-f0-9]{4}){3}-[a-f0-9]{12})/.exec( + npmrc + ) + if (match) result.npmToken = match[1] + }) + .catch(() => {}), + ]) + + if (process.env.GH_TOKEN) result.githubToken = process.env.GH_TOKEN + if (process.env.NPM_TOKEN) result.npmToken = process.env.NPM_TOKEN + return result +}) + +export default getConfig diff --git a/src/getNpmToken.js b/src/getNpmToken.js deleted file mode 100644 index a6d346d..0000000 --- a/src/getNpmToken.js +++ /dev/null @@ -1,23 +0,0 @@ -// @flow - -import os from 'os' -import * as fs from 'fs-extra' -import once from './util/once' - -const getNpmToken = once( - async (env: { [name: string]: ?string } = process.env): Promise => { - const { NPM_TOKEN } = env - if (NPM_TOKEN) return NPM_TOKEN - try { - const homedir = os.homedir() - const npmrc = await fs.readFile(`${homedir}/.npmrc`, 'utf8') - const match = /:_authToken=([a-f0-9]{8}(-[a-f0-9]{4}){3}-[a-f0-9]{12})/.exec( - npmrc - ) - if (match) return match[1] - } catch (error) { - return null - } - } -) -export default getNpmToken diff --git a/src/index.js b/src/index.js index edcd5d2..064f29c 100644 --- a/src/index.js +++ b/src/index.js @@ -1,49 +1,56 @@ -#!/usr/bin/env node -/* @flow */ +#!/usr/bin/env node/* @flow */ -import chalk from 'chalk' import npmRegistryFetch from 'npm-registry-fetch' import { Base64 } from 'js-base64' import parseChangelog, { type Release } from './parseChangelog' import semver from 'semver' +import getConfig from './getConfig' import _Octokit from '@octokit/rest' import octokitThrottling from '@octokit/plugin-throttling' -import getNpmToken from './getNpmToken' import memoize from './util/memoize' +import once from './util/once' -const Octokit = _Octokit.plugin(octokitThrottling) +const getOctokit = once( + async (): Promise<_Octokit> => { + const Octokit = _Octokit.plugin(octokitThrottling) -const { GH_TOKEN } = process.env + const { githubToken } = await getConfig() -type LimitOptions = { - method: string, - url: string, - request: { - retryCount: number, - }, -} + type LimitOptions = { + method: string, + url: string, + request: { + retryCount: number, + }, + } -const octokitOptions: Object = { - throttle: { - onRateLimit: (retryAfter: number, options: LimitOptions) => { - octokit.log.warn( - `Request quota exhausted for request ${options.method} ${options.url}` - ) - return options.request.retryCount < 3 - }, - onAbuseLimit: (retryAfter: number, options: LimitOptions) => { - // does not retry, only logs a warning - octokit.log.warn( - `Abuse detected for request ${options.method} ${options.url}` - ) - }, - }, -} -if (GH_TOKEN) octokitOptions.auth = `token ${GH_TOKEN}` -const octokit = new Octokit(octokitOptions) + const octokitOptions: Object = { + throttle: { + onRateLimit: (retryAfter: number, options: LimitOptions) => { + octokit.log.warn( + `Request quota exhausted for request ${options.method} ${ + options.url + }` + ) + return options.request.retryCount < 3 + }, + onAbuseLimit: (retryAfter: number, options: LimitOptions) => { + // does not retry, only logs a warning + octokit.log.warn( + `Abuse detected for request ${options.method} ${options.url}` + ) + }, + }, + } + if (githubToken) octokitOptions.auth = `token ${githubToken}` + const octokit = new Octokit(octokitOptions) + return octokit + } +) export const getChangelogFromFile = memoize( async (owner: string, repo: string): Promise<{ [string]: Release }> => { + const octokit = await getOctokit() let changelog let lastError: ?Error for (const file of ['CHANGELOG.md', 'changelog.md']) { @@ -107,8 +114,11 @@ export async function fetchChangelog( pkg: string, { include }: Options = {} ): Promise<{ [version: string]: Release }> { + const { npmToken } = await getConfig() + const octokit = await getOctokit() + const npmInfo = await npmRegistryFetch.json(pkg, { - token: await getNpmToken(), + token: npmToken, }) const versions = Object.keys(npmInfo.versions).filter(includeFilter(include)) @@ -176,95 +186,3 @@ export async function fetchChangelog( return releases } - -if (!module.parent) { - /* eslint-env node */ - const { argv } = require('yargs') - .usage( - `Usage: $0 - -Prints changelog entries for an npm package from GitHub. -(Other repository hosts aren't currently supported.)` - ) - .option('r', { - alias: 'range', - describe: 'semver version range to get changelog entries for', - type: 'string', - }) - .option('json', { - describe: 'output json', - type: 'boolean', - }) - .option('prereleases', { - describe: 'include prerelease versions', - type: 'boolean', - default: false, - }) - .default('minor', true) - .boolean('minor') - .hide('minor') - .describe('no-minor', 'exclude minor versions') - .default('patch', undefined) - .boolean('patch') - .hide('patch') - .describe('no-patch', 'exclude patch versions') - .hide('version') - - let { - _: [pkg], - range, - includePrereleases: prerelease, - minor, - patch, - json, - } = argv - - if (!pkg) { - require('yargs').showHelp() - process.exit(1) - } - if (!range) { - try { - // $FlowFixMe - const { version } = require(require.resolve( - require('path').join(pkg, 'package.json'), - { - paths: [process.cwd()], - } - )) - range = `>${version}` - } catch (error) { - // ignore - } - } - - fetchChangelog(pkg, { - include: { - range, - prerelease, - minor, - patch, - }, - }).then( - (changelog: { [version: string]: Release }) => { - if (json) { - process.stdout.write(JSON.stringify(changelog, null, 2)) - } else { - for (const version in changelog) { - if (!changelog.hasOwnProperty(version)) continue - const { header, body, error } = changelog[version] - process.stdout.write(chalk.bold(header) + '\n\n') - if (body) process.stdout.write(body + '\n\n') - if (error) { - process.stdout.write(`Failed to get changelog: ${error.stack}\n\n`) - } - } - } - process.exit(0) - }, - (error: Error) => { - process.stderr.write(error.stack + '\n') - process.exit(1) - } - ) -}