diff --git a/src/args.ts b/src/args.ts index b262f6f97..09a9fca1d 100644 --- a/src/args.ts +++ b/src/args.ts @@ -1,7 +1,8 @@ import {Arg, ArgDefinition} from './interfaces/parser' -import {dirExists, fileExists, isNotFalsy} from './util/index' +import {dirExists, fileExists} from './util/fs' import {Command} from './command' import {URL} from 'node:url' +import {isNotFalsy} from './util/util' /** * Create a custom arg. diff --git a/src/cli-ux/action/base.ts b/src/cli-ux/action/base.ts index 38aa2cdb9..e06b9f803 100644 --- a/src/cli-ux/action/base.ts +++ b/src/cli-ux/action/base.ts @@ -1,6 +1,6 @@ import {stderr, stdout} from '../stream' import {Options} from './types' -import {castArray} from '../../util' +import {castArray} from '../../util/util' import {inspect} from 'node:util' export interface ITask { diff --git a/src/cli-ux/config.ts b/src/cli-ux/config.ts index db8e3abd1..c6d8780c0 100644 --- a/src/cli-ux/config.ts +++ b/src/cli-ux/config.ts @@ -1,6 +1,6 @@ import {ActionBase} from './action/base' import {PJSON} from '../interfaces/pjson' -import {requireJson} from '../util/index' +import {requireJson} from '../util/fs' import simple from './action/simple' import spinner from './action/spinner' diff --git a/src/cli-ux/list.ts b/src/cli-ux/list.ts index 3f399a771..e1f985933 100644 --- a/src/cli-ux/list.ts +++ b/src/cli-ux/list.ts @@ -1,4 +1,4 @@ -import {maxBy} from '../util/index' +import {maxBy} from '../util/util' import {stdtermwidth} from '../screen' const wordwrap = require('wordwrap') diff --git a/src/cli-ux/styled/table.ts b/src/cli-ux/styled/table.ts index e22ac2f97..94c58b488 100644 --- a/src/cli-ux/styled/table.ts +++ b/src/cli-ux/styled/table.ts @@ -1,6 +1,6 @@ import * as F from '../../flags' import * as Interfaces from '../../interfaces' -import {capitalize, sumBy} from '../../util' +import {capitalize, sumBy} from '../../util/util' import chalk from 'chalk' import {inspect} from 'node:util' import {orderBy} from 'natural-orderby' diff --git a/src/command.ts b/src/command.ts index 262cd3de8..2f0a61752 100644 --- a/src/command.ts +++ b/src/command.ts @@ -16,7 +16,6 @@ import { } from './interfaces/parser' import {format, inspect} from 'node:util' import {formatCommandDeprecationWarning, formatFlagDeprecationWarning, normalizeArgv, toConfiguredId} from './help/util' -import {requireJson, uniq} from './util/index' import {stderr, stdout} from './cli-ux/stream' import {CommandError} from './interfaces/errors' import {Config} from './config' @@ -27,6 +26,8 @@ import {PrettyPrintableError} from './errors' import {aggregateFlags} from './util/aggregate-flags' import chalk from 'chalk' import {fileURLToPath} from 'node:url' +import {requireJson} from './util/fs' +import {uniq} from './util/util' import {ux} from './cli-ux' const pjson = requireJson(__dirname, '..', 'package.json') diff --git a/src/config/config.ts b/src/config/config.ts index 2e9bf0868..98d750141 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -6,7 +6,8 @@ import {Hook, Hooks, PJSON, Topic} from '../interfaces' import {Plugin as IPlugin, Options} from '../interfaces/plugin' import {URL, fileURLToPath} from 'node:url' import {arch, userInfo as osUserInfo, release, tmpdir, type} from 'node:os' -import {compact, getHomeDir, getPlatform, isProd, requireJson} from '../util/index' +import {compact, isProd} from '../util/util' +import {getHomeDir, getPlatform} from '../util/os' import {join, sep} from 'node:path' import {Command} from '../command' import {Performance} from '../performance' @@ -15,6 +16,7 @@ import WSL from 'is-wsl' import {format} from 'node:util' import {getHelpFlagAdditions} from '../help/util' import {loadWithData} from '../module-loader' +import {requireJson} from '../util/fs' import {settings} from '../settings' import {stdout} from '../cli-ux/stream' diff --git a/src/config/plugin-loader.ts b/src/config/plugin-loader.ts index 63df2baae..ce01e68c7 100644 --- a/src/config/plugin-loader.ts +++ b/src/config/plugin-loader.ts @@ -1,10 +1,11 @@ import * as Plugin from './plugin' import {Plugin as IPlugin, Options} from '../interfaces/plugin' -import {isProd, readJson} from '../util/index' import {Debug} from './util' import {PJSON} from '../interfaces' import {Performance} from '../performance' +import {isProd} from '../util/util' import {join} from 'node:path' +import {readJson} from '../util/fs' // eslint-disable-next-line new-cap const debug = Debug() diff --git a/src/config/plugin.ts b/src/config/plugin.ts index 972e66cc8..af5a9fe67 100644 --- a/src/config/plugin.ts +++ b/src/config/plugin.ts @@ -1,8 +1,9 @@ import {CLIError, error} from '../errors' import {Debug, getCommandIdPermutations, resolvePackage} from './util' import {Plugin as IPlugin, PluginOptions} from '../interfaces/plugin' -import {compact, exists, isProd, mapValues, readJson, requireJson} from '../util/index' +import {compact, isProd, mapValues} from '../util/util' import {dirname, join, parse, relative, sep} from 'node:path' +import {exists, readJson, requireJson} from '../util/fs' import {loadWithData, loadWithDataFromManifest} from '../module-loader' import {Command} from '../command' import {Manifest} from '../interfaces/manifest' diff --git a/src/config/ts-node.ts b/src/config/ts-node.ts index ba0bbfe0c..87784578d 100644 --- a/src/config/ts-node.ts +++ b/src/config/ts-node.ts @@ -1,10 +1,11 @@ import * as TSNode from 'ts-node' import {Plugin, TSConfig} from '../interfaces' -import {isProd, readJsonSync} from '../util/index' import {join, relative as pathRelative} from 'node:path' import {Debug} from './util' import {existsSync} from 'node:fs' +import {isProd} from '../util/util' import {memoizedWarn} from '../errors' +import {readJsonSync} from '../util/fs' import {settings} from '../settings' // eslint-disable-next-line new-cap diff --git a/src/flags.ts b/src/flags.ts index 42c23f0d8..b338db976 100644 --- a/src/flags.ts +++ b/src/flags.ts @@ -1,6 +1,6 @@ /* eslint-disable valid-jsdoc */ import {BooleanFlag, CustomOptions, FlagDefinition, OptionFlag} from './interfaces' -import {dirExists, fileExists} from './util/index' +import {dirExists, fileExists} from './util/fs' import {CLIError} from './errors' import {URL} from 'node:url' import {loadHelpClass} from './help' diff --git a/src/help/command.ts b/src/help/command.ts index 6eda6034b..87d866068 100644 --- a/src/help/command.ts +++ b/src/help/command.ts @@ -1,9 +1,10 @@ import * as Interfaces from '../interfaces' import {HelpFormatter, HelpSection, HelpSectionRenderer} from './formatter' -import {castArray, compact, ensureArgObject, sortBy} from '../util' +import {castArray, compact, sortBy} from '../util/util' import {Command} from '../command' import {DocOpts} from './docopts' import chalk from 'chalk' +import {ensureArgObject} from '../util/ensure-arg-object' import stripAnsi from 'strip-ansi' // Don't use os.EOL because we need to ensure that a string diff --git a/src/help/docopts.ts b/src/help/docopts.ts index ff1da035b..e1bc74d79 100644 --- a/src/help/docopts.ts +++ b/src/help/docopts.ts @@ -1,5 +1,5 @@ import {Command} from '../command' -import {ensureArgObject} from '../util/index' +import {ensureArgObject} from '../util/ensure-arg-object' /** * DocOpts - See http://docopt.org/. * diff --git a/src/help/index.ts b/src/help/index.ts index 08ab901d5..1c07b9bcb 100644 --- a/src/help/index.ts +++ b/src/help/index.ts @@ -1,5 +1,5 @@ import * as Interfaces from '../interfaces' -import {compact, sortBy, uniqBy} from '../util/index' +import {compact, sortBy, uniqBy} from '../util/util' import {formatCommandDeprecationWarning, getHelpFlagAdditions, standardizeIDFromArgv, toConfiguredId} from './util' import {Command} from '../command' import {CommandHelp} from './command' diff --git a/src/help/root.ts b/src/help/root.ts index 6ec56a96e..d8cd3754b 100644 --- a/src/help/root.ts +++ b/src/help/root.ts @@ -1,6 +1,6 @@ import * as Interfaces from '../interfaces' import {HelpFormatter} from './formatter' -import {compact} from '../util/index' +import {compact} from '../util/util' import stripAnsi from 'strip-ansi' export default class RootHelp extends HelpFormatter { diff --git a/src/parser/errors.ts b/src/parser/errors.ts index f1a583fbc..b52f20dd1 100644 --- a/src/parser/errors.ts +++ b/src/parser/errors.ts @@ -4,7 +4,7 @@ import {CLIError} from '../errors' import chalk from 'chalk' import {flagUsages} from './help' import {renderList} from '../cli-ux/list' -import {uniq} from '../util/index' +import {uniq} from '../util/util' export {CLIError} from '../errors' diff --git a/src/parser/help.ts b/src/parser/help.ts index 57e1cecbc..7ade44bc4 100644 --- a/src/parser/help.ts +++ b/src/parser/help.ts @@ -1,6 +1,6 @@ import {Flag, FlagUsageOptions} from '../interfaces/parser' import chalk from 'chalk' -import {sortBy} from '../util/index' +import {sortBy} from '../util/util' export function flagUsage(flag: Flag, options: FlagUsageOptions = {}): [string, string | undefined] { const label = [] diff --git a/src/parser/parse.ts b/src/parser/parse.ts index 7fb88ed96..e7e45fcbb 100644 --- a/src/parser/parse.ts +++ b/src/parser/parse.ts @@ -17,7 +17,7 @@ import { ParserOutput, ParsingToken, } from '../interfaces/parser' -import {isTruthy, last, pickBy} from '../util/index' +import {isTruthy, last, pickBy} from '../util/util' import {createInterface} from 'node:readline' let debug: any diff --git a/src/parser/validate.ts b/src/parser/validate.ts index ef13abcce..634e8c1b0 100644 --- a/src/parser/validate.ts +++ b/src/parser/validate.ts @@ -7,7 +7,7 @@ import { UnexpectedArgsError, Validation, } from './errors' -import {uniq} from '../util/index' +import {uniq} from '../util/util' export async function validate(parse: {input: ParserInput; output: ParserOutput}): Promise { let cachedResolvedFlags: Record | undefined diff --git a/src/util/cache-command.ts b/src/util/cache-command.ts index 6d3b0579e..ae378947c 100644 --- a/src/util/cache-command.ts +++ b/src/util/cache-command.ts @@ -1,9 +1,10 @@ import {ArgInput, FlagInput} from '../interfaces/parser' -import {ensureArgObject, pickBy} from './index' import {Command} from '../command' import {Plugin as IPlugin} from '../interfaces/plugin' import {aggregateFlags} from './aggregate-flags' import {cacheDefaultValue} from './cache-default-value' +import {ensureArgObject} from './ensure-arg-object' +import {pickBy} from './util' // In order to collect static properties up the inheritance chain, we need to recursively // access the prototypes until there's nothing left. This allows us to combine baseFlags diff --git a/src/util/ensure-arg-object.ts b/src/util/ensure-arg-object.ts new file mode 100644 index 000000000..46648494e --- /dev/null +++ b/src/util/ensure-arg-object.ts @@ -0,0 +1,15 @@ +import {ArgInput} from '../interfaces/parser' +import {Command} from '../command' + +/** + * Ensure that the provided args are an object. This is for backwards compatibility with v1 commands which + * defined args as an array. + * + * @param args Either an array of args or an object of args + * @returns ArgInput + */ +export function ensureArgObject(args?: any[] | ArgInput | {[name: string]: Command.Arg.Cached}): ArgInput { + return ( + Array.isArray(args) ? (args ?? []).reduce((x, y) => ({...x, [y.name]: y}), {} as ArgInput) : args ?? {} + ) as ArgInput +} diff --git a/src/util/fs.ts b/src/util/fs.ts new file mode 100644 index 000000000..89f5f1c0c --- /dev/null +++ b/src/util/fs.ts @@ -0,0 +1,57 @@ +import {access, readFile, stat} from 'node:fs/promises' +import {join} from 'node:path' +import {readFileSync} from 'node:fs' + +const debug = require('debug') + +export function requireJson(...pathParts: string[]): T { + return JSON.parse(readFileSync(join(...pathParts), 'utf8')) +} + +export async function exists(path: string): Promise { + try { + await access(path) + return true + } catch { + return false + } +} + +export const dirExists = async (input: string): Promise => { + if (!(await exists(input))) { + throw new Error(`No directory found at ${input}`) + } + + const fileStat = await stat(input) + if (!fileStat.isDirectory()) { + throw new Error(`${input} exists but is not a directory`) + } + + return input +} + +export const fileExists = async (input: string): Promise => { + if (!(await exists(input))) { + throw new Error(`No file found at ${input}`) + } + + const fileStat = await stat(input) + if (!fileStat.isFile()) { + throw new Error(`${input} exists but is not a file`) + } + + return input +} + +export async function readJson(path: string): Promise { + debug('config')('readJson %s', path) + const contents = await readFile(path, 'utf8') + 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 { + const contents = readFileSync(path, 'utf8') + return parse ? (JSON.parse(contents) as T) : contents +} diff --git a/src/util/index.ts b/src/util/index.ts deleted file mode 100644 index b7284ff4c..000000000 --- a/src/util/index.ts +++ /dev/null @@ -1,197 +0,0 @@ -import {access, readFile, stat} from 'node:fs/promises' -import {homedir, platform} from 'node:os' -import {ArgInput} from '../interfaces/parser' -import {Command} from '../command' -import {join} from 'node:path' -import {readFileSync} from 'node:fs' - -const debug = require('debug') - -export function pickBy>( - obj: T, - fn: (i: T[keyof T]) => boolean, -): Partial { - return Object.entries(obj).reduce((o, [k, v]) => { - if (fn(v)) o[k] = v - return o - }, {} as any) -} - -export function compact(a: (T | undefined)[]): T[] { - // eslint-disable-next-line unicorn/prefer-native-coercion-functions - return a.filter((a): a is T => Boolean(a)) -} - -export function uniqBy(arr: T[], fn: (cur: T) => any): T[] { - return arr.filter((a, i) => { - const aVal = fn(a) - return !arr.find((b, j) => j > i && fn(b) === aVal) - }) -} - -export function last(arr?: T[]): T | undefined { - if (!arr) return - return arr.at(-1) -} - -type SortTypes = string | number | undefined | boolean - -function compare(a: SortTypes | SortTypes[], b: SortTypes | SortTypes[]): number { - a = a === undefined ? 0 : a - b = b === undefined ? 0 : b - - if (Array.isArray(a) && Array.isArray(b)) { - if (a.length === 0 && b.length === 0) return 0 - const diff = compare(a[0], b[0]) - if (diff !== 0) return diff - return compare(a.slice(1), b.slice(1)) - } - - if (a < b) return -1 - if (a > b) return 1 - return 0 -} - -export function sortBy(arr: T[], fn: (i: T) => SortTypes | SortTypes[]): T[] { - return arr.sort((a, b) => compare(fn(a), fn(b))) -} - -export function castArray(input?: T | T[]): T[] { - if (input === undefined) return [] - return Array.isArray(input) ? input : [input] -} - -export function isProd(): boolean { - return !['development', 'test'].includes(process.env.NODE_ENV ?? '') -} - -export function maxBy(arr: T[], fn: (i: T) => number): T | undefined { - if (arr.length === 0) { - return undefined - } - - return arr.reduce((maxItem, i) => { - const curr = fn(i) - const max = fn(maxItem) - return curr > max ? i : maxItem - }) -} - -export function sumBy(arr: T[], fn: (i: T) => number): number { - return arr.reduce((sum, i) => sum + fn(i), 0) -} - -export function capitalize(s: string): string { - return s ? s.charAt(0).toUpperCase() + s.slice(1).toLowerCase() : '' -} - -export async function exists(path: string): Promise { - try { - await access(path) - return true - } catch { - return false - } -} - -export const dirExists = async (input: string): Promise => { - if (!(await exists(input))) { - throw new Error(`No directory found at ${input}`) - } - - const fileStat = await stat(input) - if (!fileStat.isDirectory()) { - throw new Error(`${input} exists but is not a directory`) - } - - return input -} - -export const fileExists = async (input: string): Promise => { - if (!(await exists(input))) { - throw new Error(`No file found at ${input}`) - } - - const fileStat = await stat(input) - if (!fileStat.isFile()) { - throw new Error(`${input} exists but is not a file`) - } - - return input -} - -export function isTruthy(input: string): boolean { - return ['true', '1', 'yes', 'y'].includes(input.toLowerCase()) -} - -export function isNotFalsy(input: string): boolean { - return !['false', '0', 'no', 'n'].includes(input.toLowerCase()) -} - -export function requireJson(...pathParts: string[]): T { - return JSON.parse(readFileSync(join(...pathParts), 'utf8')) -} - -/** - * Ensure that the provided args are an object. This is for backwards compatibility with v1 commands which - * defined args as an array. - * - * @param args Either an array of args or an object of args - * @returns ArgInput - */ -export function ensureArgObject(args?: any[] | ArgInput | {[name: string]: Command.Arg.Cached}): ArgInput { - return ( - Array.isArray(args) ? (args ?? []).reduce((x, y) => ({...x, [y.name]: y}), {} as ArgInput) : args ?? {} - ) as ArgInput -} - -export function uniq(arr: T[]): T[] { - return [...new Set(arr)].sort() -} - -/** - * Call os.homedir() and return the result - * - * Wrapping this allows us to stub these in tests since os.homedir() is - * non-configurable and non-writable. - * - * @returns The user's home directory - */ -export function getHomeDir(): string { - return homedir() -} - -/** - * Call os.platform() and return the result - * - * Wrapping this allows us to stub these in tests since os.platform() is - * non-configurable and non-writable. - * - * @returns The process' platform - */ -export function getPlatform(): NodeJS.Platform { - return platform() -} - -export async function readJson(path: string): Promise { - debug('config')('readJson %s', path) - const contents = await readFile(path, 'utf8') - 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 { - const contents = readFileSync(path, 'utf8') - return parse ? (JSON.parse(contents) as T) : contents -} - -export function mapValues, TResult>( - obj: {[P in keyof T]: T[P]}, - fn: (i: T[keyof T], k: keyof T) => TResult, -): {[P in keyof T]: TResult} { - return Object.entries(obj).reduce((o, [k, v]) => { - o[k] = fn(v as any, k as any) - return o - }, {} as any) -} diff --git a/src/util/os.ts b/src/util/os.ts new file mode 100644 index 000000000..2fb3cdf88 --- /dev/null +++ b/src/util/os.ts @@ -0,0 +1,25 @@ +import {homedir, platform} from 'node:os' + +/** + * Call os.homedir() and return the result + * + * Wrapping this allows us to stub these in tests since os.homedir() is + * non-configurable and non-writable. + * + * @returns The user's home directory + */ +export function getHomeDir(): string { + return homedir() +} + +/** + * Call os.platform() and return the result + * + * Wrapping this allows us to stub these in tests since os.platform() is + * non-configurable and non-writable. + * + * @returns The process' platform + */ +export function getPlatform(): NodeJS.Platform { + return platform() +} diff --git a/src/util/util.ts b/src/util/util.ts new file mode 100644 index 000000000..7f3ec1058 --- /dev/null +++ b/src/util/util.ts @@ -0,0 +1,99 @@ +export function pickBy>( + obj: T, + fn: (i: T[keyof T]) => boolean, +): Partial { + return Object.entries(obj).reduce((o, [k, v]) => { + if (fn(v)) o[k] = v + return o + }, {} as any) +} + +export function compact(a: (T | undefined)[]): T[] { + // eslint-disable-next-line unicorn/prefer-native-coercion-functions + return a.filter((a): a is T => Boolean(a)) +} + +export function uniqBy(arr: T[], fn: (cur: T) => any): T[] { + return arr.filter((a, i) => { + const aVal = fn(a) + return !arr.find((b, j) => j > i && fn(b) === aVal) + }) +} + +export function last(arr?: T[]): T | undefined { + if (!arr) return + return arr.at(-1) +} + +type SortTypes = string | number | undefined | boolean + +function compare(a: SortTypes | SortTypes[], b: SortTypes | SortTypes[]): number { + a = a === undefined ? 0 : a + b = b === undefined ? 0 : b + + if (Array.isArray(a) && Array.isArray(b)) { + if (a.length === 0 && b.length === 0) return 0 + const diff = compare(a[0], b[0]) + if (diff !== 0) return diff + return compare(a.slice(1), b.slice(1)) + } + + if (a < b) return -1 + if (a > b) return 1 + return 0 +} + +export function sortBy(arr: T[], fn: (i: T) => SortTypes | SortTypes[]): T[] { + return arr.sort((a, b) => compare(fn(a), fn(b))) +} + +export function castArray(input?: T | T[]): T[] { + if (input === undefined) return [] + return Array.isArray(input) ? input : [input] +} + +export function isProd(): boolean { + return !['development', 'test'].includes(process.env.NODE_ENV ?? '') +} + +export function maxBy(arr: T[], fn: (i: T) => number): T | undefined { + if (arr.length === 0) { + return undefined + } + + return arr.reduce((maxItem, i) => { + const curr = fn(i) + const max = fn(maxItem) + return curr > max ? i : maxItem + }) +} + +export function sumBy(arr: T[], fn: (i: T) => number): number { + return arr.reduce((sum, i) => sum + fn(i), 0) +} + +export function capitalize(s: string): string { + return s ? s.charAt(0).toUpperCase() + s.slice(1).toLowerCase() : '' +} + +export function isTruthy(input: string): boolean { + return ['true', '1', 'yes', 'y'].includes(input.toLowerCase()) +} + +export function isNotFalsy(input: string): boolean { + return !['false', '0', 'no', 'n'].includes(input.toLowerCase()) +} + +export function uniq(arr: T[]): T[] { + return [...new Set(arr)].sort() +} + +export function mapValues, TResult>( + obj: {[P in keyof T]: T[P]}, + fn: (i: T[keyof T], k: keyof T) => TResult, +): {[P in keyof T]: TResult} { + return Object.entries(obj).reduce((o, [k, v]) => { + o[k] = fn(v as any, k as any) + return o + }, {} as any) +} diff --git a/test/command/command.test.ts b/test/command/command.test.ts index 892284216..9cb949e15 100644 --- a/test/command/command.test.ts +++ b/test/command/command.test.ts @@ -1,6 +1,7 @@ import {expect, fancy} from 'fancy-test' // import path = require('path') import {Command as Base, Flags} from '../../src' +import {ensureArgObject} from '../../src/util/ensure-arg-object' // import {TestHelpClassConfig} from './helpers/test-help-in-src/src/test-help-plugin' // const pjson = require('../package.json') @@ -449,3 +450,22 @@ describe('command', () => { .it('json disabled/pass through enable/--json flag before --/jsonEnabled() should be false') }) }) + +describe('ensureArgObject', () => { + it('should convert array of arguments to an object', () => { + const args = [ + {name: 'foo', description: 'foo desc', required: true}, + {name: 'bar', description: 'bar desc'}, + ] + const expected = {foo: args[0], bar: args[1]} + expect(ensureArgObject(args)).to.deep.equal(expected) + }) + + it('should do nothing to an arguments object', () => { + const args = { + foo: {name: 'foo', description: 'foo desc', required: true}, + bar: {name: 'bar', description: 'bar desc'}, + } + expect(ensureArgObject(args)).to.deep.equal(args) + }) +}) diff --git a/test/command/main.test.ts b/test/command/main.test.ts index 0aa5a1f40..c93ca83c4 100644 --- a/test/command/main.test.ts +++ b/test/command/main.test.ts @@ -2,7 +2,7 @@ import {expect} from 'chai' import {resolve} from 'node:path' import {SinonSandbox, SinonStub, createSandbox} from 'sinon' import stripAnsi = require('strip-ansi') -import {requireJson} from '../../src/util' +import {requireJson} from '../../src/util/fs' import {run} from '../../src/main' import {Interfaces, stdout} from '../../src/index' diff --git a/test/config/config.flexible.test.ts b/test/config/config.flexible.test.ts index be6944696..1cbc711ca 100644 --- a/test/config/config.flexible.test.ts +++ b/test/config/config.flexible.test.ts @@ -5,7 +5,7 @@ import {expect, fancy} from './test' import {Flags, Interfaces} from '../../src' import {Command} from '../../src/command' import {getCommandIdPermutations} from '../../src/config/util' -import * as util from '../../src/util' +import * as os from '../../src/util/os' import {join} from 'node:path' interface Options { @@ -44,8 +44,8 @@ describe('Config with flexible taxonomy', () => { let test = fancy .resetConfig() .env(env, {clear: true}) - .stub(util, 'getHomeDir', (stub) => stub.returns(join(homedir))) - .stub(util, 'getPlatform', (stub) => stub.returns(platform)) + .stub(os, 'getHomeDir', (stub) => stub.returns(join(homedir))) + .stub(os, 'getPlatform', (stub) => stub.returns(platform)) const load = async (): Promise => {} const findCommand = async (): Promise => MyCommandClass diff --git a/test/config/config.test.ts b/test/config/config.test.ts index d92c9f2a7..24f5d8571 100644 --- a/test/config/config.test.ts +++ b/test/config/config.test.ts @@ -1,7 +1,8 @@ import {join} from 'node:path' import {Plugin as IPlugin} from '../../src/interfaces' -import * as util from '../../src/util' +import * as os from '../../src/util/os' +import * as fs from '../../src/util/fs' import {expect, fancy} from './test' import {Config, Interfaces} from '../../src' @@ -49,9 +50,9 @@ describe('Config', () => { let test = fancy .resetConfig() .env(env, {clear: true}) - .stub(util, 'getHomeDir', (stub) => stub.returns(join(homedir))) - .stub(util, 'getPlatform', (stub) => stub.returns(platform)) - if (pjson) test = test.stub(util, 'readJson', (stub) => stub.resolves(pjson)) + .stub(os, 'getHomeDir', (stub) => stub.returns(join(homedir))) + .stub(os, 'getPlatform', (stub) => stub.returns(platform)) + if (pjson) test = test.stub(fs, 'readJson', (stub) => stub.resolves(pjson)) test = test.add('config', () => Config.load()) @@ -317,10 +318,10 @@ describe('Config', () => { let test = fancy .resetConfig() .env(env, {clear: true}) - .stub(util, 'getHomeDir', (stub) => stub.returns(join(homedir))) - .stub(util, 'getPlatform', (stub) => stub.returns(platform)) + .stub(os, 'getHomeDir', (stub) => stub.returns(join(homedir))) + .stub(os, 'getPlatform', (stub) => stub.returns(platform)) - if (pjson) test = test.stub(util, 'readJson', (stub) => stub.resolves(pjson)) + if (pjson) test = test.stub(fs, 'readJson', (stub) => stub.resolves(pjson)) test = test.add('config', async () => { const config = await Config.load() config.plugins = plugins diff --git a/test/config/ts-node.test.ts b/test/config/ts-node.test.ts index 6742238e4..111794bac 100644 --- a/test/config/ts-node.test.ts +++ b/test/config/ts-node.test.ts @@ -5,7 +5,7 @@ import {SinonSandbox, createSandbox} from 'sinon' import {Interfaces, settings} from '../../src' import * as configTsNode from '../../src/config/ts-node' -import * as util from '../../src/util' +import * as util from '../../src/util/fs' import {expect} from 'chai' const root = resolve(__dirname, 'fixtures/typescript') diff --git a/test/util/fs.test.ts b/test/util/fs.test.ts new file mode 100644 index 000000000..edc93e2f4 --- /dev/null +++ b/test/util/fs.test.ts @@ -0,0 +1,19 @@ +import {expect} from 'chai' +import {readJson} from '../../src/util/fs' + +describe('readJson', () => { + it('should return parsed JSON', async () => { + const json = await readJson<{name: string}>('package.json') + expect(json.name).to.equal('@oclif/core') + }) + + it('should throw an error if the file does not exist', async () => { + try { + await readJson('does-not-exist.json') + throw new Error('Expected an error to be thrown') + } catch (error) { + const err = error as Error + expect(err.message).to.include('ENOENT: no such file or directory') + } + }) +}) diff --git a/test/util/os.test.ts b/test/util/os.test.ts new file mode 100644 index 000000000..f8cb686cd --- /dev/null +++ b/test/util/os.test.ts @@ -0,0 +1,9 @@ +import {expect} from 'chai' +import {homedir} from 'node:os' +import {getHomeDir} from '../../src/util/os' + +describe('getHomeDir', () => { + it('should return the home directory', () => { + expect(getHomeDir()).to.equal(homedir()) + }) +}) diff --git a/test/util/index.test.ts b/test/util/util.test.ts similarity index 64% rename from test/util/index.test.ts rename to test/util/util.test.ts index 0b93c81ce..d8ce40f69 100644 --- a/test/util/index.test.ts +++ b/test/util/util.test.ts @@ -1,17 +1,5 @@ import {expect} from 'chai' -import {homedir} from 'node:os' -import { - capitalize, - castArray, - ensureArgObject, - getHomeDir, - isNotFalsy, - isTruthy, - last, - maxBy, - readJson, - sumBy, -} from '../../src/util/index' +import {capitalize, castArray, isNotFalsy, isTruthy, last, maxBy, sumBy} from '../../src/util/util' describe('capitalize', () => { it('capitalizes the string', () => { @@ -62,25 +50,6 @@ describe('last', () => { }) }) -describe('ensureArgObject', () => { - it('should convert array of arguments to an object', () => { - const args = [ - {name: 'foo', description: 'foo desc', required: true}, - {name: 'bar', description: 'bar desc'}, - ] - const expected = {foo: args[0], bar: args[1]} - expect(ensureArgObject(args)).to.deep.equal(expected) - }) - - it('should do nothing to an arguments object', () => { - const args = { - foo: {name: 'foo', description: 'foo desc', required: true}, - bar: {name: 'bar', description: 'bar desc'}, - } - expect(ensureArgObject(args)).to.deep.equal(args) - }) -}) - describe('isNotFalsy', () => { it('should return true for truthy values', () => { expect(isNotFalsy('true')).to.be.true @@ -113,29 +82,6 @@ describe('isTruthy', () => { }) }) -describe('getHomeDir', () => { - it('should return the home directory', () => { - expect(getHomeDir()).to.equal(homedir()) - }) -}) - -describe('readJson', () => { - it('should return parsed JSON', async () => { - const json = await readJson<{name: string}>('package.json') - expect(json.name).to.equal('@oclif/core') - }) - - it('should throw an error if the file does not exist', async () => { - try { - await readJson('does-not-exist.json') - throw new Error('Expected an error to be thrown') - } catch (error) { - const err = error as Error - expect(err.message).to.include('ENOENT: no such file or directory') - } - }) -}) - describe('castArray', () => { it('should cast a value to an array', () => { expect(castArray('foo')).to.deep.equal(['foo'])