From 6af9c71791e982c79a31036671df608d5787243f Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Mon, 6 May 2024 14:09:27 -0600 Subject: [PATCH] feat: support rc files (#1067) * feat: support rc files * chore: code review --- package.json | 1 + src/config/plugin.ts | 3 +- src/util/find-root.ts | 2 +- src/util/fs.ts | 16 +++++++++++ src/util/read-pjson.ts | 64 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 src/util/read-pjson.ts diff --git a/package.json b/package.json index d3d87c2f0..94a1fd447 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "ansis": "^3.0.1", "clean-stack": "^3.0.1", "cli-spinners": "^2.9.2", + "cosmiconfig": "^9.0.0", "debug": "^4.3.4", "ejs": "^3.1.10", "get-package-type": "^0.1.0", diff --git a/src/config/plugin.ts b/src/config/plugin.ts index d898c6d4a..5fb1e84cc 100644 --- a/src/config/plugin.ts +++ b/src/config/plugin.ts @@ -15,6 +15,7 @@ import {SINGLE_COMMAND_CLI_SYMBOL} from '../symbols' import {cacheCommand} from '../util/cache-command' import {findRoot} from '../util/find-root' import {readJson} from '../util/fs' +import {readPjson} from '../util/read-pjson' import {castArray, compact} from '../util/util' import {tsPath} from './ts-path' import {getCommandIdPermutations, makeDebug} from './util' @@ -224,7 +225,7 @@ export class Plugin implements IPlugin { if (!root) throw new CLIError(`could not find package.json with ${inspect(this.options)}`) this.root = root this._debug(`loading ${this.type} plugin from ${root}`) - this.pjson = this.options.pjson ?? (await readJson(join(root, 'package.json'))) + this.pjson = this.options.pjson ?? (await readPjson(root)) this.flexibleTaxonomy = this.options?.flexibleTaxonomy || this.pjson.oclif?.flexibleTaxonomy || false this.moduleType = this.pjson.type === 'module' ? 'module' : 'commonjs' this.name = this.pjson.name diff --git a/src/util/find-root.ts b/src/util/find-root.ts index 6ce3df1f7..633075c08 100644 --- a/src/util/find-root.ts +++ b/src/util/find-root.ts @@ -178,7 +178,7 @@ function findPnpRoot(name: string, root: string): string | undefined { * * If no path is found, undefined is returned which will eventually result in a thrown Error from Plugin. */ -export async function findRoot(name: string | undefined, root: string) { +export async function findRoot(name: string | undefined, root: string): Promise { if (name) { debug(name)(`Finding root using ${root}`) let pkgPath diff --git a/src/util/fs.ts b/src/util/fs.ts index 2db8f3f32..1e5a833e7 100644 --- a/src/util/fs.ts +++ b/src/util/fs.ts @@ -43,19 +43,35 @@ export const fileExists = async (input: string): Promise => { return input } +const cache = new Map() + export async function readJson(path: string): Promise { + if (cache.has(path)) { + return JSON.parse(cache.get(path)!) as T + } + const contents = await readFile(path, 'utf8') + cache.set(path, contents) return JSON.parse(contents) as T } export function readJsonSync(path: string, parse: false): string export function readJsonSync(path: string, parse?: true): T export function readJsonSync(path: string, parse = true): T | string { + if (cache.has(path)) { + return JSON.parse(cache.get(path)!) as T + } + const contents = readFileSync(path, 'utf8') + cache.set(path, contents) return parse ? (JSON.parse(contents) as T) : contents } export async function safeReadJson(path: string): Promise { + if (cache.has(path)) { + return JSON.parse(cache.get(path)!) as T + } + try { return await readJson(path) } catch {} diff --git a/src/util/read-pjson.ts b/src/util/read-pjson.ts new file mode 100644 index 000000000..221126d76 --- /dev/null +++ b/src/util/read-pjson.ts @@ -0,0 +1,64 @@ +import {cosmiconfig} from 'cosmiconfig' +import {join} from 'node:path' + +import {PJSON} from '../interfaces' +import {makeDebug} from '../logger' +import {readJson} from './fs' + +const debug = makeDebug('read-pjson') + +/** + * Read the package.json file from a given path and add the oclif config (found by cosmiconfig) if it exists. + * + * We can assume that the package.json file exists because the plugin root has already been loaded at this point. + */ +export async function readPjson(path: string): Promise { + const pjsonPath = join(path, 'package.json') + if (process.env.OCLIF_DISABLE_RC) { + debug('OCLIF_DISABLE_RC is set, skipping rc search') + return readJson(pjsonPath) + } + + const pjson = await readJson(pjsonPath) + + // don't bother with cosmiconfig if the plugin's package.json already has an oclif config + if (pjson.oclif) { + debug(`found oclif config in ${pjsonPath}`) + return pjson + } + + debug(`searching for oclif config in ${path}`) + const explorer = cosmiconfig('oclif', { + /** + * Remove the following from the defaults: + * - package.json + * - any files under .config/ + */ + searchPlaces: [ + '.oclifrc', + '.oclifrc.json', + '.oclifrc.yaml', + '.oclifrc.yml', + '.oclifrc.js', + '.oclifrc.ts', + '.oclifrc.mjs', + '.oclifrc.cjs', + 'oclif.config.js', + 'oclif.config.ts', + 'oclif.config.mjs', + 'oclif.config.cjs', + ], + searchStrategy: 'none', + }) + const result = await explorer.search(path) + if (!result?.config) { + debug(`no oclif config found in ${path}`) + return pjson + } + + debug(`found oclif config for ${path}: %O`, result) + return { + ...pjson, + oclif: result?.config ?? {}, + } +}