diff --git a/src/command.ts b/src/command.ts index 9599d9a96..9d05cd77c 100644 --- a/src/command.ts +++ b/src/command.ts @@ -1,6 +1,6 @@ import {format, inspect} from 'util' -import * as Config from './config' +import {Interfaces, Config, toCached} from './config' import * as Errors from './errors' import {PrettyPrintableError} from './errors' import * as Parser from './parser' @@ -62,7 +62,7 @@ export default abstract class Command { /** An order-dependent array of arguments for the command */ static args?: Parser.args.Input - static plugin: Config.IPlugin | undefined + static plugin: Interfaces.Plugin | undefined /** An array of example strings to show at the end of the command's help */ static examples: string[] | undefined @@ -72,11 +72,11 @@ export default abstract class Command { // eslint-disable-next-line valid-jsdoc /** * instantiate and run the command - * @param {Config.Command.Class} this Class + * @param {Interfaces.Command.Class} this Class * @param {string[]} argv argv - * @param {Config.LoadOptions} opts options + * @param {Interfaces.LoadOptions} opts options */ - static run: Config.Command.Class['run'] = async function (this: Config.Command.Class, argv?: string[], opts?) { + static run: Interfaces.Command.Class['run'] = async function (this: Interfaces.Command.Class, argv?: string[], opts?) { if (!argv) argv = process.argv.slice(2) const config = await Config.load(opts || (module.parent && module.parent.parent && module.parent.parent.filename) || __dirname) const cmd = new this(argv, config) @@ -87,7 +87,7 @@ export default abstract class Command { protected debug: (...args: any[]) => void - constructor(public argv: string[], public config: Config.IConfig) { + constructor(public argv: string[], public config: Interfaces.Config) { this.id = this.ctor.id try { this.debug = require('debug')(this.id ? `${this.config.bin}:${this.id}` : this.config.bin) @@ -191,7 +191,7 @@ export default abstract class Command { protected _help() { const HelpClass = getHelpClass(this.config) const help: HelpBase = new HelpClass(this.config) - const cmd = Config.Command.toCached(this.ctor as any as Config.Command.Class) + const cmd = toCached(this.ctor as any as Interfaces.Command.Class) if (!cmd.id) cmd.id = '' let topics = this.config.topics topics = topics.filter((t: any) => !t.hidden) diff --git a/src/config/command.ts b/src/config/command.ts deleted file mode 100644 index 71d8c3144..000000000 --- a/src/config/command.ts +++ /dev/null @@ -1,130 +0,0 @@ -import * as Parser from '../parser' - -import * as Config from '.' -import {mapValues} from './util' - -export interface Command { - id: string; - hidden: boolean; - aliases: string[]; - description?: string; - usage?: string | string[]; - examples?: string[]; - type?: string; - pluginName?: string; - pluginType?: string; - flags: {[name: string]: Command.Flag}; - args: Command.Arg[]; -} - -export namespace Command { - export interface Arg { - name: string; - description?: string; - required?: boolean; - hidden?: boolean; - default?: string; - options?: string[]; - } - - export type Flag = Flag.Boolean | Flag.Option - - export namespace Flag { - export interface Boolean { - type: 'boolean'; - name: string; - required?: boolean; - char?: string; - hidden?: boolean; - description?: string; - helpLabel?: string; - allowNo?: boolean; - } - export interface Option { - type: 'option'; - name: string; - required?: boolean; - char?: string; - hidden?: boolean; - description?: string; - helpLabel?: string; - helpValue?: string; - default?: string; - options?: string[]; - } - } - - export interface Base { - _base: string; - id: string; - hidden: boolean; - aliases: string[]; - description?: string; - usage?: string | string[]; - examples?: string[]; - } - - export interface Class extends Base { - plugin?: Config.IPlugin; - flags?: Parser.flags.Input; - args?: Parser.args.Input; - new(argv: string[], config: Config.IConfig): Instance; - run(argv?: string[], config?: Config.LoadOptions): PromiseLike; - } - - export interface Instance { - _run(argv: string[]): Promise; - } - - export interface Plugin extends Command { - load(): Class; - } - - // eslint-disable-next-line no-inner-declarations - export function toCached(c: Class, plugin?: Config.Plugin): Command { - return { - id: c.id, - description: c.description, - usage: c.usage, - pluginName: plugin && plugin.name, - pluginType: plugin && plugin.type, - hidden: c.hidden, - aliases: c.aliases || [], - examples: c.examples || (c as any).example, - flags: mapValues(c.flags || {}, (flag, name) => { - if (flag.type === 'boolean') { - return { - name, - type: flag.type, - char: flag.char, - description: flag.description, - hidden: flag.hidden, - required: flag.required, - helpLabel: flag.helpLabel, - allowNo: flag.allowNo, - } - } - return { - name, - type: flag.type, - char: flag.char, - description: flag.description, - hidden: flag.hidden, - required: flag.required, - helpLabel: flag.helpLabel, - helpValue: flag.helpValue, - options: flag.options, - default: typeof flag.default === 'function' ? flag.default({options: {}, flags: {}}) : flag.default, - } - }) as {[k: string]: Flag}, - args: c.args ? c.args.map(a => ({ - name: a.name, - description: a.description, - required: a.required, - options: a.options, - default: typeof a.default === 'function' ? a.default({}) : a.default, - hidden: a.hidden, - })) : [], - } - } -} diff --git a/src/config/config.ts b/src/config/config.ts index 3fd5c9b36..fcaa31a38 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -5,136 +5,20 @@ import * as path from 'path' import {URL} from 'url' import {format} from 'util' -import {Command} from './command' -import Debug from './debug' -import {Hook, Hooks} from './hooks' -import {PJSON} from './pjson' +import {Options, Plugin as IPlugin} from './interfaces/plugin' +import {Config as IConfig, ArchTypes, PlatformTypes, LoadOptions} from './interfaces/config' +import {Command} from './interfaces/command' +import {Debug, mapValues} from './util' +import {Hook} from './interfaces/hooks' +import {PJSON} from './interfaces/pjson' import * as Plugin from './plugin' -import {Topic} from './topic' +import {Topic} from './interfaces/topic' import {tsPath} from './ts-node' import {compact, flatMap, loadJSON, uniq} from './util' -export type PlatformTypes = 'darwin' | 'linux' | 'win32' | 'aix' | 'freebsd' | 'openbsd' | 'sunos' | 'wsl' -export type ArchTypes = 'arm' | 'arm64' | 'mips' | 'mipsel' | 'ppc' | 'ppc64' | 's390' | 's390x' | 'x32' | 'x64' | 'x86' -export interface Options extends Plugin.Options { - devPlugins?: boolean; - userPlugins?: boolean; - channel?: string; - version?: string; -} - // eslint-disable-next-line new-cap const debug = Debug() -// eslint-disable-next-line @typescript-eslint/interface-name-prefix -export interface IConfig { - name: string; - version: string; - channel: string; - pjson: PJSON.CLI; - root: string; - /** - * process.arch - */ - arch: ArchTypes; - /** - * bin name of CLI command - */ - bin: string; - /** - * cache directory to use for CLI - * - * example ~/Library/Caches/mycli or ~/.cache/mycli - */ - cacheDir: string; - /** - * config directory to use for CLI - * - * example: ~/.config/mycli - */ - configDir: string; - /** - * data directory to use for CLI - * - * example: ~/.local/share/mycli - */ - dataDir: string; - /** - * base dirname to use in cacheDir/configDir/dataDir - */ - dirname: string; - /** - * points to a file that should be appended to for error logs - * - * example: ~/Library/Caches/mycli/error.log - */ - errlog: string; - /** - * path to home directory - * - * example: /home/myuser - */ - home: string; - /** - * process.platform - */ - platform: PlatformTypes; - /** - * active shell - */ - shell: string; - /** - * user agent to use for http calls - * - * example: mycli/1.2.3 (darwin-x64) node-9.0.0 - */ - userAgent: string; - /** - * if windows - */ - windows: boolean; - /** - * debugging level - * - * set by ${BIN}_DEBUG or DEBUG=$BIN - */ - debug: number; - /** - * npm registry to use for installing plugins - */ - npmRegistry?: string; - userPJSON?: PJSON.User; - plugins: Plugin.IPlugin[]; - binPath?: string; - valid: boolean; - readonly commands: Command.Plugin[]; - readonly topics: Topic[]; - readonly commandIDs: string[]; - - runCommand(id: string, argv?: string[]): Promise; - runHook>(event: K, opts: T[K]): Promise; - findCommand(id: string, opts: {must: true}): Command.Plugin; - findCommand(id: string, opts?: {must: boolean}): Command.Plugin | undefined; - findTopic(id: string, opts: {must: true}): Topic; - findTopic(id: string, opts?: {must: boolean}): Topic | undefined; - scopedEnvVar(key: string): string | undefined; - scopedEnvVarKey(key: string): string; - scopedEnvVarTrue(key: string): boolean; - s3Url(key: string): string; - s3Key(type: 'versioned' | 'unversioned', ext: '.tar.gz' | '.tar.xz', options?: IConfig.s3Key.Options): string; - s3Key(type: keyof PJSON.S3.Templates, options?: IConfig.s3Key.Options): string; -} - -export namespace IConfig { - export namespace s3Key { - export interface Options { - platform?: PlatformTypes; - arch?: ArchTypes; - [key: string]: any; - } - } -} - const _pjson = require('../../package.json') function channelFromVersion(version: string) { @@ -153,6 +37,10 @@ function hasManifest(p: string): boolean { const WSL = require('is-wsl') +function isConfig(o: any): o is IConfig { + return o && Boolean(o._base) +} + export class Config implements IConfig { _base = `${_pjson.name}@${_pjson.version}` @@ -196,7 +84,7 @@ export class Config implements IConfig { userPJSON?: PJSON.User - plugins: Plugin.IPlugin[] = [] + plugins: IPlugin[] = [] binPath?: string @@ -207,6 +95,14 @@ export class Config implements IConfig { // eslint-disable-next-line no-useless-constructor constructor(public options: Options) {} + static async load(opts: LoadOptions = (module.parent && module.parent && module.parent.parent && module.parent.parent.filename) || __dirname) { + if (typeof opts === 'string') opts = {root: opts} + if (isConfig(opts)) return opts + const config = new Config(opts) + await config.load() + return config + } + // eslint-disable-next-line complexity async load() { const plugin = new Plugin.Plugin({root: this.options.root}) @@ -559,15 +455,49 @@ export class Config implements IConfig { } } -function isConfig(o: any): o is IConfig { - return o && Boolean(o._base) -} - -export type LoadOptions = Options | string | IConfig | undefined -export async function load(opts: LoadOptions = (module.parent && module.parent && module.parent.parent && module.parent.parent.filename) || __dirname) { - if (typeof opts === 'string') opts = {root: opts} - if (isConfig(opts)) return opts - const config = new Config(opts) - await config.load() - return config +export function toCached(c: Command.Class, plugin?: IPlugin): Command { + return { + id: c.id, + description: c.description, + usage: c.usage, + pluginName: plugin && plugin.name, + pluginType: plugin && plugin.type, + hidden: c.hidden, + aliases: c.aliases || [], + examples: c.examples || (c as any).example, + flags: mapValues(c.flags || {}, (flag, name) => { + if (flag.type === 'boolean') { + return { + name, + type: flag.type, + char: flag.char, + description: flag.description, + hidden: flag.hidden, + required: flag.required, + helpLabel: flag.helpLabel, + allowNo: flag.allowNo, + } + } + return { + name, + type: flag.type, + char: flag.char, + description: flag.description, + hidden: flag.hidden, + required: flag.required, + helpLabel: flag.helpLabel, + helpValue: flag.helpValue, + options: flag.options, + default: typeof flag.default === 'function' ? flag.default({options: {}, flags: {}}) : flag.default, + } + }) as {[k: string]: Command.Flag}, + args: c.args ? c.args.map(a => ({ + name: a.name, + description: a.description, + required: a.required, + options: a.options, + default: typeof a.default === 'function' ? a.default({}) : a.default, + hidden: a.hidden, + })) : [], + } } diff --git a/src/config/debug.ts b/src/config/debug.ts deleted file mode 100644 index c4a2e86d9..000000000 --- a/src/config/debug.ts +++ /dev/null @@ -1,20 +0,0 @@ -// tslint:disable no-console -let debug: any -try { - debug = require('debug') -} catch {} - -function displayWarnings() { - if (process.listenerCount('warning') > 1) return - process.on('warning', (warning: any) => { - console.error(warning.stack) - if (warning.detail) console.error(warning.detail) - }) -} - -export default (...scope: string[]) => { - if (!debug) return (..._: any[]) => {} - const d = debug(['config', ...scope].join(':')) - if (d.enabled) displayWarnings() - return (...args: any[]) => d(...args) -} diff --git a/src/config/index.ts b/src/config/index.ts index c7048fa3e..2070fea70 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -3,11 +3,9 @@ try { require('fs-extra-debug') } catch {} -export {IConfig, Config, Options, load, LoadOptions, PlatformTypes, ArchTypes} from './config' -export {Command} from './command' -export {Hook, Hooks} from './hooks' -export {Manifest} from './manifest' -export {PJSON} from './pjson' -export {IPlugin, Plugin} from './plugin' -export {Topic} from './topic' +export {Config, toCached} from './config' +export {Plugin} from './plugin' export {tsPath} from './ts-node' + +import * as Interfaces from './interfaces' +export {Interfaces} diff --git a/src/config/interfaces/command.ts b/src/config/interfaces/command.ts new file mode 100644 index 000000000..a531b8693 --- /dev/null +++ b/src/config/interfaces/command.ts @@ -0,0 +1,82 @@ +import * as Parser from '../../parser' + +import {Config, LoadOptions} from './config' +import {Plugin} from './plugin' + +export interface Command { + id: string; + hidden: boolean; + aliases: string[]; + description?: string; + usage?: string | string[]; + examples?: string[]; + type?: string; + pluginName?: string; + pluginType?: string; + flags: {[name: string]: Command.Flag}; + args: Command.Arg[]; +} + +export namespace Command { + export interface Arg { + name: string; + description?: string; + required?: boolean; + hidden?: boolean; + default?: string; + options?: string[]; + } + + export type Flag = Flag.Boolean | Flag.Option + + export namespace Flag { + export interface Boolean { + type: 'boolean'; + name: string; + required?: boolean; + char?: string; + hidden?: boolean; + description?: string; + helpLabel?: string; + allowNo?: boolean; + } + export interface Option { + type: 'option'; + name: string; + required?: boolean; + char?: string; + hidden?: boolean; + description?: string; + helpLabel?: string; + helpValue?: string; + default?: string; + options?: string[]; + } + } + + export interface Base { + _base: string; + id: string; + hidden: boolean; + aliases: string[]; + description?: string; + usage?: string | string[]; + examples?: string[]; + } + + export interface Class extends Base { + plugin?: Plugin; + flags?: Parser.flags.Input; + args?: Parser.args.Input; + new(argv: string[], config: Config): Instance; + run(argv?: string[], config?: LoadOptions): PromiseLike; + } + + export interface Instance { + _run(argv: string[]): Promise; + } + + export interface Plugin extends Command { + load(): Class; + } +} diff --git a/src/config/interfaces/config.ts b/src/config/interfaces/config.ts new file mode 100644 index 000000000..439ebe6a8 --- /dev/null +++ b/src/config/interfaces/config.ts @@ -0,0 +1,117 @@ +import {PJSON} from './pjson' +import {Hooks} from './hooks' +import {Command} from './command' +import {Plugin, Options} from './plugin' +import {Topic} from './topic' + +export type LoadOptions = Options | string | Config | undefined +export type PlatformTypes = 'darwin' | 'linux' | 'win32' | 'aix' | 'freebsd' | 'openbsd' | 'sunos' | 'wsl' +export type ArchTypes = 'arm' | 'arm64' | 'mips' | 'mipsel' | 'ppc' | 'ppc64' | 's390' | 's390x' | 'x32' | 'x64' | 'x86' + +export interface Config { + name: string; + version: string; + channel: string; + pjson: PJSON.CLI; + root: string; + /** + * process.arch + */ + arch: ArchTypes; + /** + * bin name of CLI command + */ + bin: string; + /** + * cache directory to use for CLI + * + * example ~/Library/Caches/mycli or ~/.cache/mycli + */ + cacheDir: string; + /** + * config directory to use for CLI + * + * example: ~/.config/mycli + */ + configDir: string; + /** + * data directory to use for CLI + * + * example: ~/.local/share/mycli + */ + dataDir: string; + /** + * base dirname to use in cacheDir/configDir/dataDir + */ + dirname: string; + /** + * points to a file that should be appended to for error logs + * + * example: ~/Library/Caches/mycli/error.log + */ + errlog: string; + /** + * path to home directory + * + * example: /home/myuser + */ + home: string; + /** + * process.platform + */ + platform: PlatformTypes; + /** + * active shell + */ + shell: string; + /** + * user agent to use for http calls + * + * example: mycli/1.2.3 (darwin-x64) node-9.0.0 + */ + userAgent: string; + /** + * if windows + */ + windows: boolean; + /** + * debugging level + * + * set by ${BIN}_DEBUG or DEBUG=$BIN + */ + debug: number; + /** + * npm registry to use for installing plugins + */ + npmRegistry?: string; + userPJSON?: PJSON.User; + plugins: Plugin[]; + binPath?: string; + valid: boolean; + readonly commands: Command.Plugin[]; + readonly topics: Topic[]; + readonly commandIDs: string[]; + + runCommand(id: string, argv?: string[]): Promise; + runHook>(event: K, opts: T[K]): Promise; + findCommand(id: string, opts: { must: true }): Command.Plugin; + findCommand(id: string, opts?: { must: boolean }): Command.Plugin | undefined; + findTopic(id: string, opts: { must: true }): Topic; + findTopic(id: string, opts?: { must: boolean }): Topic | undefined; + scopedEnvVar(key: string): string | undefined; + scopedEnvVarKey(key: string): string; + scopedEnvVarTrue(key: string): boolean; + s3Url(key: string): string; + s3Key(type: 'versioned' | 'unversioned', ext: '.tar.gz' | '.tar.xz', options?: Config.s3Key.Options): string; + s3Key(type: keyof PJSON.S3.Templates, options?: Config.s3Key.Options): string; +} + +export namespace Config { + export namespace s3Key { + export interface Options { + platform?: PlatformTypes; + arch?: ArchTypes; + [key: string]: any; + } + } +} diff --git a/src/config/hooks.ts b/src/config/interfaces/hooks.ts similarity index 87% rename from src/config/hooks.ts rename to src/config/interfaces/hooks.ts index e786a0eb2..e45bfcf7f 100644 --- a/src/config/hooks.ts +++ b/src/config/interfaces/hooks.ts @@ -1,4 +1,5 @@ -import * as Config from '.' +import {Command} from './command' +import {Config} from './config' export interface Hooks { [event: string]: object; @@ -7,11 +8,11 @@ export interface Hooks { argv: string[]; }; prerun: { - Command: Config.Command.Class; + Command: Command.Class; argv: string[]; }; postrun: { - Command: Config.Command.Class; + Command: Command.Class; result?: any; argv: string[]; }; @@ -31,7 +32,7 @@ export interface Hooks { } export type HookKeyOrOptions = K extends (keyof Hooks) ? Hooks[K] : K -export type Hook = (this: Hook.Context, options: HookKeyOrOptions & {config: Config.IConfig}) => any +export type Hook = (this: Hook.Context, options: HookKeyOrOptions & {config: Config}) => any export namespace Hook { export type Init = Hook @@ -43,7 +44,7 @@ export namespace Hook { export type CommandNotFound = Hook export interface Context { - config: Config.IConfig; + config: Config; exit(code?: number): void; error(message: string | Error, options?: {code?: string; exit?: number}): void; warn(message: string): void; diff --git a/src/config/interfaces/index.ts b/src/config/interfaces/index.ts new file mode 100644 index 000000000..1aebac214 --- /dev/null +++ b/src/config/interfaces/index.ts @@ -0,0 +1,7 @@ +export {Config, ArchTypes, PlatformTypes, LoadOptions} from './config' +export {Command} from './command' +export {Hook, HookKeyOrOptions, Hooks} from './hooks' +export {Manifest} from './manifest' +export {PJSON} from './pjson' +export {Plugin, PluginOptions, Options} from './plugin' +export {Topic} from './topic' diff --git a/src/config/manifest.ts b/src/config/interfaces/manifest.ts similarity index 100% rename from src/config/manifest.ts rename to src/config/interfaces/manifest.ts diff --git a/src/config/pjson.ts b/src/config/interfaces/pjson.ts similarity index 100% rename from src/config/pjson.ts rename to src/config/interfaces/pjson.ts diff --git a/src/config/interfaces/plugin.ts b/src/config/interfaces/plugin.ts new file mode 100644 index 000000000..9c360f5d3 --- /dev/null +++ b/src/config/interfaces/plugin.ts @@ -0,0 +1,72 @@ +import {Command} from './command' +import {PJSON} from './pjson' +import {Topic} from './topic' + +export interface PluginOptions { + root: string; + name?: string; + type?: string; + tag?: string; + ignoreManifest?: boolean; + errorOnManifestCreate?: boolean; + parent?: Plugin; + children?: Plugin[]; +} + +export interface Options extends PluginOptions { + devPlugins?: boolean; + userPlugins?: boolean; + channel?: string; + version?: string; +} + +// eslint-disable-next-line @typescript-eslint/interface-name-prefix +export interface Plugin { + /** + * ../config version + */ + _base: string; + /** + * name from package.json + */ + name: string; + /** + * version from package.json + * + * example: 1.2.3 + */ + version: string; + /** + * full package.json + * + * parsed with read-pkg + */ + pjson: PJSON.Plugin | PJSON.CLI; + /** + * used to tell the user how the plugin was installed + * examples: core, link, user, dev + */ + type: string; + /** + * base path of plugin + */ + root: string; + /** + * npm dist-tag of plugin + * only used for user plugins + */ + tag?: string; + /** + * if it appears to be an npm package but does not look like it's really a CLI plugin, this is set to false + */ + valid: boolean; + + commands: Command.Plugin[]; + hooks: { [k: string]: string[] }; + readonly commandIDs: string[]; + readonly topics: Topic[]; + + findCommand(id: string, opts: { must: true }): Command.Class; + findCommand(id: string, opts?: { must: boolean }): Command.Class | undefined; + load(): Promise; +} diff --git a/src/config/topic.ts b/src/config/interfaces/topic.ts similarity index 100% rename from src/config/topic.ts rename to src/config/interfaces/topic.ts diff --git a/src/config/plugin.ts b/src/config/plugin.ts index f8847b28e..8102b1bed 100644 --- a/src/config/plugin.ts +++ b/src/config/plugin.ts @@ -3,78 +3,18 @@ import * as Globby from 'globby' import * as path from 'path' import {inspect} from 'util' -import {Command} from './command' -import Debug from './debug' -import {Manifest} from './manifest' -import {PJSON} from './pjson' -import {Topic} from './topic' +import {Plugin as IPlugin, PluginOptions} from './interfaces/plugin' +import {Command} from './interfaces/command' +import {toCached} from './config' +import {Debug} from './util' +import {Manifest} from './interfaces/manifest' +import {PJSON} from './interfaces/pjson' +import {Topic} from './interfaces/topic' import {tsPath} from './ts-node' import {compact, exists, flatMap, loadJSON, mapValues} from './util' const ROOT_INDEX_CMD_ID = '' -export interface Options { - root: string; - name?: string; - type?: string; - tag?: string; - ignoreManifest?: boolean; - errorOnManifestCreate?: boolean; - parent?: Plugin; - children?: Plugin[]; -} - -// eslint-disable-next-line @typescript-eslint/interface-name-prefix -export interface IPlugin { - /** - * ../config version - */ - _base: string; - /** - * name from package.json - */ - name: string; - /** - * version from package.json - * - * example: 1.2.3 - */ - version: string; - /** - * full package.json - * - * parsed with read-pkg - */ - pjson: PJSON.Plugin | PJSON.CLI; - /** - * used to tell the user how the plugin was installed - * examples: core, link, user, dev - */ - type: string; - /** - * base path of plugin - */ - root: string; - /** - * npm dist-tag of plugin - * only used for user plugins - */ - tag?: string; - /** - * if it appears to be an npm package but does not look like it's really a CLI plugin, this is set to false - */ - valid: boolean; - - commands: Command.Plugin[]; - hooks: {[k: string]: string[]}; - readonly commandIDs: string[]; - readonly topics: Topic[]; - - findCommand(id: string, opts: {must: true}): Command.Class; - findCommand(id: string, opts?: {must: boolean}): Command.Class | undefined; - load(): Promise; -} - const _pjson = require('../../package.json') const hasManifest = function (p: string): boolean { @@ -170,7 +110,7 @@ export class Plugin implements IPlugin { protected warned = false // eslint-disable-next-line no-useless-constructor - constructor(public options: Options) {} + constructor(public options: PluginOptions) {} async load() { this.type = this.options.type || 'core' @@ -303,7 +243,7 @@ export class Plugin implements IPlugin { // eslint-disable-next-line array-callback-return commands: this.commandIDs.map(id => { try { - return [id, Command.toCached(this.findCommand(id, {must: true}), this)] + return [id, toCached(this.findCommand(id, {must: true}), this)] } catch (error) { const scope = 'toCached' if (Boolean(errorOnManifestCreate) === false) this.warn(error, scope) diff --git a/src/config/screen.ts b/src/config/screen.ts deleted file mode 100644 index 1b6e27c9b..000000000 --- a/src/config/screen.ts +++ /dev/null @@ -1,18 +0,0 @@ -function termwidth(stream: any): number { - if (!stream.isTTY) { - return 80 - } - const width = stream.getWindowSize()[0] - if (width < 1) { - return 80 - } - if (width < 40) { - return 40 - } - return width -} - -const columns: number | null = (global as any).columns - -export const stdtermwidth = columns || termwidth(process.stdout) -export const errtermwidth = columns || termwidth(process.stderr) diff --git a/src/config/ts-node.ts b/src/config/ts-node.ts index 34be09b5a..8bd84ee49 100644 --- a/src/config/ts-node.ts +++ b/src/config/ts-node.ts @@ -2,7 +2,7 @@ import * as fs from 'fs' import * as path from 'path' import * as TSNode from 'ts-node' -import Debug from './debug' +import {Debug} from './util' // eslint-disable-next-line new-cap const debug = Debug() diff --git a/src/config/util.ts b/src/config/util.ts index 17a3fefab..8c9befb32 100644 --- a/src/config/util.ts +++ b/src/config/util.ts @@ -1,6 +1,6 @@ import * as fs from 'fs' -const debug = require('debug')('../config') +const debug = require('debug') export function flatMap(arr: T[], fn: (i: T) => U[]): U[] { return arr.reduce((arr, i) => arr.concat(fn(i)), [] as U[]) @@ -19,10 +19,7 @@ export function exists(path: string): Promise { } export function loadJSON(path: string): Promise { - debug('loadJSON %s', path) - // let loadJSON - // try { loadJSON = require('load-json-file') } catch {} - // if (loadJSON) return loadJSON.sync(path) + debug('config')('loadJSON %s', path) return new Promise((resolve, reject) => { fs.readFile(path, 'utf8', (err, d) => { try { @@ -44,3 +41,37 @@ export function uniq(arr: T[]): T[] { return !arr.find((b, j) => j > i && b === a) }) } + +function termwidth(stream: any): number { + if (!stream.isTTY) { + return 80 + } + const width = stream.getWindowSize()[0] + if (width < 1) { + return 80 + } + if (width < 40) { + return 40 + } + return width +} + +const columns: number | null = (global as any).columns + +export const stdtermwidth = columns || termwidth(process.stdout) +export const errtermwidth = columns || termwidth(process.stderr) + +function displayWarnings() { + if (process.listenerCount('warning') > 1) return + process.on('warning', (warning: any) => { + console.error(warning.stack) + if (warning.detail) console.error(warning.detail) + }) +} + +export function Debug(...scope: string[]): (..._: any) => void { + if (!debug) return (..._: any[]) => { } + const d = debug(['config', ...scope].join(':')) + if (d.enabled) displayWarnings() + return (...args: any[]) => d(...args) +} diff --git a/src/flags.ts b/src/flags.ts index dd3f926b7..5bc7d29bc 100644 --- a/src/flags.ts +++ b/src/flags.ts @@ -1,4 +1,4 @@ -import {IConfig} from './config' +import {Config} from './config/interfaces/config' import * as Parser from './parser' import Command from './command' @@ -6,7 +6,7 @@ export type ICompletionContext = { args?: { [name: string]: string }; flags?: { [name: string]: string }; argv?: string[]; - config: IConfig; + config: Config; } export type ICompletion = { diff --git a/src/help/command.ts b/src/help/command.ts index 5e41fd21f..da48273a5 100644 --- a/src/help/command.ts +++ b/src/help/command.ts @@ -1,4 +1,4 @@ -import * as Config from '../config' +import {Interfaces as Config} from '../config' import * as Chalk from 'chalk' import indent = require('indent-string') import stripAnsi = require('strip-ansi') @@ -25,7 +25,7 @@ const wrap = require('wrap-ansi') export default class CommandHelp { render: (input: string) => string - constructor(public command: Config.Command, public config: Config.IConfig, public opts: HelpOptions) { + constructor(public command: Config.Command, public config: Config.Config, public opts: HelpOptions) { this.render = template(this) } diff --git a/src/help/index.ts b/src/help/index.ts index cb3e59af7..af866637e 100644 --- a/src/help/index.ts +++ b/src/help/index.ts @@ -2,7 +2,7 @@ import * as Chalk from 'chalk' import indent = require('indent-string') import stripAnsi = require('strip-ansi') -import * as Config from '../config' +import {Interfaces as Config} from '../config' import {error} from '../errors' import CommandHelp from './command' import {renderList} from './list' @@ -36,12 +36,12 @@ function getHelpSubject(args: string[]): string | undefined { } export abstract class HelpBase { - constructor(config: Config.IConfig, opts: Partial = {}) { + constructor(config: Config.Config, opts: Partial = {}) { this.config = config this.opts = {maxWidth: stdtermwidth, ...opts} } - protected config: Config.IConfig + protected config: Config.Config protected opts: HelpOptions @@ -95,7 +95,7 @@ export class Help extends HelpBase { return topics } - constructor(config: Config.IConfig, opts: Partial = {}) { + constructor(config: Config.Config, opts: Partial = {}) { super(config, opts) this.render = template(this) } diff --git a/src/help/root.ts b/src/help/root.ts index ba47361ec..fdd90ef04 100644 --- a/src/help/root.ts +++ b/src/help/root.ts @@ -1,4 +1,4 @@ -import * as Config from '../config' +import {Interfaces as Config} from '../config' import * as Chalk from 'chalk' import indent = require('indent-string') import stripAnsi = require('strip-ansi') @@ -15,7 +15,7 @@ const { export default class RootHelp { render: (input: string) => string - constructor(public config: Config.IConfig, public opts: HelpOptions) { + constructor(public config: Config.Config, public opts: HelpOptions) { this.render = template(this) } diff --git a/src/help/util.ts b/src/help/util.ts index 65a6cda72..d6f3da67c 100644 --- a/src/help/util.ts +++ b/src/help/util.ts @@ -1,6 +1,6 @@ import lodashTemplate = require('lodash.template') -import {IConfig} from '../config/config' +import {Config as IConfig} from '../config/interfaces/config' import {Help, HelpBase, HelpOptions} from '.' import * as Config from '../config' diff --git a/src/index.ts b/src/index.ts index 6910dce26..a5d401a2f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,7 @@ import * as semver from 'semver' import Command from './command' import {run} from './main' -import * as Config from './config' +import {Config, Plugin, tsPath, toCached, Interfaces} from './config' import * as Errors from './errors' import * as flags from './flags' import {HelpBase, Help, getHelpClass} from './help' @@ -17,8 +17,12 @@ export { getHelpClass, Help, HelpBase, + Interfaces, Parser, + Plugin, run, + toCached, + tsPath, } function checkCWD() { diff --git a/src/main.ts b/src/main.ts index cae804dc5..170e4819d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,4 +1,4 @@ -import * as Config from './config' +import {Interfaces as Config} from './config' import {HelpBase, getHelpClass} from './help' import Command from './command' diff --git a/test/command/helpers/test-help-in-src/src/test-help-plugin.ts b/test/command/helpers/test-help-in-src/src/test-help-plugin.ts index d62a748eb..d136d7413 100644 --- a/test/command/helpers/test-help-in-src/src/test-help-plugin.ts +++ b/test/command/helpers/test-help-in-src/src/test-help-plugin.ts @@ -1,7 +1,7 @@ import {spy, SinonSpy} from 'sinon' -import {Config, HelpBase} from '../../../../../src' +import {Interfaces, HelpBase} from '../../../../../src' -export type TestHelpClassConfig = Config.IConfig & { showCommandHelpSpy?: SinonSpy; showHelpSpy?: SinonSpy } +export type TestHelpClassConfig = Interfaces.Config & { showCommandHelpSpy?: SinonSpy; showHelpSpy?: SinonSpy } export default class extends HelpBase { constructor(config: any, opts: any) { diff --git a/test/config/config.test.ts b/test/config/config.test.ts index 5dcb2c84f..07150d9e0 100644 --- a/test/config/config.test.ts +++ b/test/config/config.test.ts @@ -1,7 +1,7 @@ import * as os from 'os' import * as path from 'path' -import {Config, IConfig, load, PJSON} from '../../src/config' +import {Config, Interfaces} from '../../src/config' import * as util from '../../src/config/util' import {expect, fancy} from './test' @@ -23,10 +23,10 @@ describe('Config', () => { if (pjson) test = test.stub(util, 'loadJSON', () => Promise.resolve(pjson)) - test = test.add('config', () => load()) + test = test.add('config', () => Config.load()) return { - hasS3Key(k: keyof PJSON.S3.Templates, expected: string, extra: any = {}) { + hasS3Key(k: keyof Interfaces.PJSON.S3.Templates, expected: string, extra: any = {}) { return this .it(`renders ${k} template as ${expected}`, config => { // eslint-disable-next-line prefer-const @@ -41,11 +41,11 @@ describe('Config', () => { expect(o).to.equal(expected) }) }, - hasProperty(k: K | undefined, v: IConfig[K] | undefined) { + hasProperty(k: K | undefined, v: Interfaces.Config[K] | undefined) { return this .it(`has ${k}=${v}`, config => expect(config).to.have.property(k!, v)) }, - it(expectation: string, fn: (config: IConfig) => any) { + it(expectation: string, fn: (config: Interfaces.Config) => any) { test .do(({config}) => fn(config)) .it(expectation) diff --git a/test/config/test.ts b/test/config/test.ts index 55d8c0ba5..792c033f6 100644 --- a/test/config/test.ts +++ b/test/config/test.ts @@ -1,10 +1,10 @@ import {expect, fancy as base, FancyTypes} from 'fancy-test' -import * as Config from '../../src/config' +import {Interfaces} from '../../src/config' export const fancy = base .register('resetConfig', () => ({ - run(ctx: {config: Config.IConfig}) { + run(ctx: {config: Interfaces.Config}) { delete ctx.config }, })) diff --git a/test/config/typescript.test.ts b/test/config/typescript.test.ts index b3db5123b..aefc9c28d 100644 --- a/test/config/typescript.test.ts +++ b/test/config/typescript.test.ts @@ -1,6 +1,6 @@ import * as path from 'path' -import * as Config from '../../src/config' +import {Config} from '../../src/config' import {expect, fancy} from './test' diff --git a/test/help/format-command.test.ts b/test/help/format-command.test.ts index a94b164ca..e1a543dd1 100644 --- a/test/help/format-command.test.ts +++ b/test/help/format-command.test.ts @@ -1,4 +1,4 @@ -import {Command as Base, flags, Config} from '../../src' +import {Command as Base, flags, Interfaces, toCached} from '../../src' import {expect, test as base} from '@oclif/test' import stripAnsi = require('strip-ansi') @@ -14,7 +14,7 @@ class Command extends Base { // extensions to expose method as public for testing class TestHelp extends Help { - public formatCommand(command: Config.Command) { + public formatCommand(command: Interfaces.Command) { return super.formatCommand(command) } } @@ -24,7 +24,7 @@ const test = base .add('help', ctx => new TestHelp(ctx.config as any)) .register('commandHelp', (command?: any) => ({ run(ctx: {help: TestHelp; commandHelp: string; expectation: string}) { - const cached = Config.Command.toCached(command!, {} as any) + const cached = toCached(command!, {} as any) const help = ctx.help.formatCommand(cached) if (process.env.TEST_OUTPUT === '1') { console.log(help) diff --git a/test/help/format-commands.test.ts b/test/help/format-commands.test.ts index 5fba83114..456df8fa0 100644 --- a/test/help/format-commands.test.ts +++ b/test/help/format-commands.test.ts @@ -1,4 +1,4 @@ -import {Command} from '@oclif/command' +import {Command} from '../../src' import * as Config from '@oclif/config' import {expect, test as base} from '@oclif/test' import stripAnsi = require('strip-ansi') diff --git a/test/help/format-root.test.ts b/test/help/format-root.test.ts index 16c629656..634f2c4c4 100644 --- a/test/help/format-root.test.ts +++ b/test/help/format-root.test.ts @@ -1,9 +1,10 @@ -import {expect, test as base, Config} from '@oclif/test' +import {expect, test as base} from '@oclif/test' import stripAnsi = require('strip-ansi') const g: any = global g.columns = 80 import {Help} from '../../src/help' +import {Interfaces} from '../../src' const VERSION = require('../../package.json').version const UA = `@oclif/core/${VERSION} ${process.platform}-${process.arch} node-${process.version}` @@ -17,8 +18,8 @@ class TestHelp extends Help { const test = base .loadConfig() -.register('rootHelp', (ctxOverride?: (config: Config.IConfig) => Config.IConfig) => ({ - run(ctx: {config: Config.IConfig; help: Help; commandHelp: string; expectation: string}) { +.register('rootHelp', (ctxOverride?: (config: Interfaces.Config) => Interfaces.Config) => ({ + run(ctx: { config: Interfaces.Config; help: Help; commandHelp: string; expectation: string}) { const config = ctxOverride ? ctxOverride(ctx.config) : ctx.config const help = new TestHelp(config as any) diff --git a/test/help/format-topic.test.ts b/test/help/format-topic.test.ts index 4e4a90331..7a6bd5160 100644 --- a/test/help/format-topic.test.ts +++ b/test/help/format-topic.test.ts @@ -1,4 +1,4 @@ -import * as Config from '../../src/config' +import {Interfaces} from '../../src/config' import {expect, test as base} from '@oclif/test' import stripAnsi = require('strip-ansi') @@ -8,7 +8,7 @@ import {Help} from '../../src/help' // extensions to expose method as public for testing class TestHelp extends Help { - public formatTopic(topic: Config.Topic) { + public formatTopic(topic: Interfaces.Topic) { return super.formatTopic(topic) } } @@ -18,7 +18,7 @@ const test = base .add('help', ctx => { return new TestHelp(ctx.config as any) }) -.register('topicHelp', (topic: Config.Topic) => ({ +.register('topicHelp', (topic: Interfaces.Topic) => ({ run(ctx: {help: TestHelp; commandHelp: string; expectation: string}) { const topicHelpOutput = ctx.help.formatTopic(topic) if (process.env.TEST_OUTPUT === '1') { diff --git a/test/help/format-topics.test.ts b/test/help/format-topics.test.ts index 3ff4c9d43..64c7bd6ec 100644 --- a/test/help/format-topics.test.ts +++ b/test/help/format-topics.test.ts @@ -1,4 +1,4 @@ -import * as Config from '../../src/config' +import {Interfaces} from '../../src/config' import {expect, test as base} from '@oclif/test' import stripAnsi = require('strip-ansi') @@ -8,7 +8,7 @@ import {Help} from '../../src/help' // extensions to expose method as public for testing class TestHelp extends Help { - public formatTopics(topics: Config.Topic[]) { + public formatTopics(topics: Interfaces.Topic[]) { return super.formatTopics(topics) } } @@ -16,7 +16,7 @@ class TestHelp extends Help { const test = base .loadConfig() .add('help', ctx => new TestHelp(ctx.config as any)) -.register('topicsHelp', (topics: Config.Topic[]) => ({ +.register('topicsHelp', (topics: Interfaces.Topic[]) => ({ run(ctx: {help: TestHelp; commandHelp: string; expectation: string}) { const topicsHelpOutput = ctx.help.formatTopics(topics) || '' diff --git a/test/help/show-help.test.ts b/test/help/show-help.test.ts index ba303d1d3..aacc66c75 100644 --- a/test/help/show-help.test.ts +++ b/test/help/show-help.test.ts @@ -1,4 +1,4 @@ -import * as Config from '../../src/config' +import {Interfaces, Config} from '../../src/config' import {expect, test as base} from '@oclif/test' import {stub, SinonStub} from 'sinon' import * as path from 'path' @@ -16,7 +16,7 @@ class TestHelp extends Help { return super.showRootHelp() } - public showTopicHelp(topic: Config.Topic) { + public showTopicHelp(topic: Interfaces.Topic) { return super.showTopicHelp(topic) } } diff --git a/test/help/util.test.ts b/test/help/util.test.ts index b57690132..dc9381c6d 100644 --- a/test/help/util.test.ts +++ b/test/help/util.test.ts @@ -1,12 +1,12 @@ /* eslint-disable max-nested-callbacks */ import {resolve} from 'path' -import * as Config from '../../src/config' +import {Config, Interfaces} from '../../src' import {expect, test} from '@oclif/test' import {getHelpClass} from '../../src/help' import configuredHelpClass from '../../src/help/_test-help-class' describe('util', () => { - let config: Config.IConfig + let config: Interfaces.Config beforeEach(async () => { config = await Config.load()