From fc3d81e2dc5865022bfdb0357c5fe200227baebd Mon Sep 17 00:00:00 2001 From: Allan Oricil <55927613+AllanOricil@users.noreply.github.com> Date: Thu, 9 Nov 2023 16:45:27 -0300 Subject: [PATCH 01/16] feat: enable themes (#852) * feat: create type for storing theme colors * feat: include color and @types/color, and simiplify theme schema * feat: set theme's default colors to white * feat: set FORCE_COLOR to 0||3 to follow the NO_COLOR manifest * feat: create DEFAULT_THEME to store default colors * revert: remove NO_COLOR manifest * feat: add new theme variables to style $, flag, and flag options * feat: add color to section headers * feat: configure default colors * feat: add colors for bin, command summary and version * feat: topics, commands, bin, version, sections, dollar sign are colorized * feat: configure default colors * feat: add feature flag to enabled/disable theme * feat: add colorize function to simplify the way colors are added to strings * feat: change all chalk.hex calls to colorize * feat: configure OCLIF_ENABLE_THEME to have precence over PJSON prop * feat: all theme colors are optional * fix: error TS2322: Type 'string' is not assignable to type 'boolean' * fix: error TS2345: arg of type 'Color|undefined' not assignable to 'Color' * fix: runtime error TypeError: color.hex is not a function * fix: this.pjson.oclif.enableTheme was not being evaluated when OCLIF_ENABLE_THEME was unset * chore(deps): update yarn.lock * refactor: simplified code removing OCLIF_ENABLE_THEME constant * fix: command summary was not changing its color when running the root command * feat: theme is now read from ~/config//theme.json if one exists * fix: add colors to other ARGUMENTS, EXAMPLES, DESCRIPTION, FLAGS DESCRIPTIONS sections * feat: add color token for aliases * fix(test): change template strings to avoid expected results printing bin twice * fix(test): add a missing parenthesis back so that all options are wraped by () * fix(test): remove single quotes wraping default flag values * feat: add test:debug script to ease debuging * refactor: add a constant with all possible THEME_KEYS * test: add tests to prove parsing untyped json object with color strings work * chore(package): add new scripts to ease development while writing tests * revert: remove theme from PJSON because a theme will be loaded from config_dir//theme.json * feat: add scopedEnvVarBoolean because scopedEnvVarTrue does not consider unset env vars * feat(test): add tests to prove the behavior that enables theme * refactor: replace all scopedEnvVarTrue by scopedEnvVarBoolean, which considers unset env variables * test: add tests to prove the enableTheme behavior * refactor: simplify parseTheme method * chore(package): remove localhost:4873 from lock file * revert: rever scopedEnvVarBoolean back to scopedEnvVarTrue * test: add tests to prove when this.theme is set * feat: ensure colorize returns string only * test: add tests to prove colorize works as expected * revert: rollback scopedEnvVarTrue as it was before * refactor: move parseTheme to src/util/util.js * refactor: make Theme type dinamically based on the values of THEME_KEYS at runtime * fix: err TS1259: Module /@types/color/index can only be default-imported using 'esModuleInterop' * revert: remove enableTheme from pjson because we don't want to cli devs to force users to use theme * revert: remove undefined as a return type for scopedEnvVarTrue * refactor: remove config.enableTheme --- package.json | 5 + src/config/config.ts | 18 +- src/help/command.ts | 67 +++--- src/help/formatter.ts | 4 +- src/help/index.ts | 40 +++- src/help/root.ts | 22 +- src/help/util.ts | 6 + src/interfaces/config.ts | 23 ++ src/util/util.ts | 17 ++ test/config/config.test.ts | 48 ++++- test/help/format-command-with-options.test.ts | 8 +- test/help/format-command.test.ts | 8 +- test/help/util.test.ts | 27 +++ test/util/util.test.ts | 201 +++++++++++++++++- yarn.lock | 44 +++- 15 files changed, 482 insertions(+), 56 deletions(-) diff --git a/package.json b/package.json index dd3c15af2..84192683b 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "chalk": "^4.1.2", "clean-stack": "^3.0.1", "cli-progress": "^3.12.0", + "color": "^4.2.3", "debug": "^4.3.4", "ejs": "^3.1.9", "get-package-type": "^0.1.0", @@ -44,6 +45,7 @@ "@types/chai-as-promised": "^7.1.5", "@types/clean-stack": "^2.1.1", "@types/cli-progress": "^3.11.0", + "@types/color": "^3.0.5", "@types/debug": "^4.1.10", "@types/ejs": "^3.1.3", "@types/indent-string": "^4.0.1", @@ -107,6 +109,7 @@ "access": "public" }, "scripts": { + "build:dev": "shx rm -rf lib && tsc --sourceMap", "build": "shx rm -rf lib && tsc", "commitlint": "commitlint", "compile": "tsc", @@ -117,9 +120,11 @@ "prepare": "husky install", "pretest": "yarn build && tsc -p test --noEmit --skipLibCheck", "test:circular-deps": "madge lib/ -c", + "test:debug": "nyc mocha --debug-brk --inspect \"test/**/*.test.ts\"", "test:e2e": "mocha --forbid-only \"test/**/*.e2e.ts\" --parallel --timeout 1200000", "test:esm-cjs": "cross-env DEBUG=e2e:* ts-node test/integration/esm-cjs.ts", "test:perf": "ts-node test/perf/parser.perf.ts", + "test:dev": "nyc mocha \"test/**/*.test.ts\"", "test": "nyc mocha --forbid-only \"test/**/*.test.ts\"" }, "types": "lib/index.d.ts" diff --git a/src/config/config.ts b/src/config/config.ts index 8ef88ac70..9eed4ec40 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -1,7 +1,7 @@ import * as ejs from 'ejs' import WSL from 'is-wsl' import {arch, userInfo as osUserInfo, release, tmpdir, type} from 'node:os' -import {join, sep} from 'node:path' +import {join, resolve, sep} from 'node:path' import {URL, fileURLToPath} from 'node:url' import {ux} from '../cli-ux' @@ -9,14 +9,14 @@ import {Command} from '../command' import {CLIError, error, exit, warn} from '../errors' import {getHelpFlagAdditions} from '../help/util' import {Hook, Hooks, PJSON, Topic} from '../interfaces' -import {ArchTypes, Config as IConfig, LoadOptions, PlatformTypes, VersionDetails} from '../interfaces/config' +import {ArchTypes, Config as IConfig, LoadOptions, PlatformTypes, Theme, VersionDetails} from '../interfaces/config' import {Plugin as IPlugin, Options} from '../interfaces/plugin' import {loadWithData} from '../module-loader' import {OCLIF_MARKER_OWNER, Performance} from '../performance' import {settings} from '../settings' -import {requireJson} from '../util/fs' +import {requireJson, safeReadJson} from '../util/fs' import {getHomeDir, getPlatform} from '../util/os' -import {compact, isProd} from '../util/util' +import {compact, isProd, parseTheme} from '../util/util' import Cache from './cache' import PluginLoader from './plugin-loader' import {tsPath} from './ts-node' @@ -91,6 +91,7 @@ export class Config implements IConfig { public plugins: Map = new Map() public root!: string public shell!: string + public theme?: Theme public topicSeparator: ' ' | ':' = ':' public userAgent!: string public userPJSON?: PJSON.User @@ -312,6 +313,7 @@ export class Config implements IConfig { if (this.pjson.oclif.topicSeparator && [' ', ':'].includes(this.pjson.oclif.topicSeparator)) this.topicSeparator = this.pjson.oclif.topicSeparator! if (this.platform === 'win32') this.dirname = this.dirname.replace('/', '\\') + this.userAgent = `${this.name}/${this.version} ${this.platform}-${this.arch} node-${process.version}` this.shell = this._shell() this.debug = this._debug() @@ -325,6 +327,12 @@ export class Config implements IConfig { this.npmRegistry = this.scopedEnvVar('NPM_REGISTRY') || this.pjson.oclif.npmRegistry + if (!this.scopedEnvVarTrue('DISABLE_THEME')) { + const themeFilePath = resolve(this.configDir, 'theme.json') + const theme = await safeReadJson>(themeFilePath) + this.theme = theme ? parseTheme(theme) : undefined + } + this.pjson.oclif.update = this.pjson.oclif.update || {} this.pjson.oclif.update.node = this.pjson.oclif.update.node || {} const s3 = this.pjson.oclif.update.s3 || {} @@ -605,7 +613,7 @@ export class Config implements IConfig { } public scopedEnvVarTrue(k: string): boolean { - const v = process.env[this.scopedEnvVarKeys(k).find((k) => process.env[k]) as string] + const v = this.scopedEnvVar(k) return v === '1' || v === 'true' } diff --git a/src/help/command.ts b/src/help/command.ts index 4755a121a..f1a587387 100644 --- a/src/help/command.ts +++ b/src/help/command.ts @@ -7,19 +7,13 @@ import {ensureArgObject} from '../util/ensure-arg-object' import {castArray, compact, sortBy} from '../util/util' import {DocOpts} from './docopts' import {HelpFormatter, HelpSection, HelpSectionRenderer} from './formatter' +import {colorize} from './util' // Don't use os.EOL because we need to ensure that a string // written on any platform, that may use \r\n or \n, will be // split on any platform, not just the os specific EOL at runtime. const POSSIBLE_LINE_FEED = /\r\n|\n/ -let {dim} = chalk - -if (process.env.ConEmuANSI === 'ON') { - // eslint-disable-next-line unicorn/consistent-destructuring - dim = chalk.gray -} - export class CommandHelp extends HelpFormatter { constructor( public command: Command.Loadable, @@ -31,7 +25,15 @@ export class CommandHelp extends HelpFormatter { protected aliases(aliases: string[] | undefined): string | undefined { if (!aliases || aliases.length === 0) return - const body = aliases.map((a) => ['$', this.config.bin, a].join(' ')).join('\n') + const body = aliases + .map((a) => + [ + colorize(this.config?.theme?.dollarSign, '$'), + colorize(this.config?.theme?.bin, this.config.bin), + colorize(this.config?.theme?.alias, a), + ].join(' '), + ) + .join('\n') return body } @@ -47,9 +49,14 @@ export class CommandHelp extends HelpFormatter { return args.map((a) => { const name = a.name.toUpperCase() let description = a.description || '' - if (a.default) description = `[default: ${a.default}] ${description}` - if (a.options) description = `(${a.options.join('|')}) ${description}` - return [name, description ? dim(description) : undefined] + if (a.default) + description = `${colorize(this.config?.theme?.flagDefaultValue, `[default: ${a.default}]`)} ${description}` + if (a.options) + description = `${colorize(this.config?.theme?.flagOptions, `(${a.options.join('|')})`)} ${description}` + return [ + colorize(this.config?.theme?.flag, name), + description ? colorize(this.config?.theme?.sectionDescription, description) : undefined, + ] }) } @@ -82,7 +89,7 @@ export class CommandHelp extends HelpFormatter { } if (description) { - return this.wrap(description.join('\n')) + return this.wrap(colorize(this.config?.theme?.commandSummary, description.join('\n'))) } } @@ -125,7 +132,7 @@ export class CommandHelp extends HelpFormatter { return `${this.wrap(description, finalIndentedSpacing)}\n\n${multilineCommands}` }) .join('\n\n') - return body + return colorize(this.config?.theme?.sectionDescription, body) } protected flagHelpLabel(flag: Command.Flag.Any, showOptions = false): string { @@ -142,7 +149,7 @@ export class CommandHelp extends HelpFormatter { } } - label = labels.join(', ') + label = labels.join(colorize(this.config?.theme?.flagSeparator, ', ')) } if (flag.type === 'option') { @@ -163,20 +170,20 @@ export class CommandHelp extends HelpFormatter { if (flags.length === 0) return return flags.map((flag) => { - const left = this.flagHelpLabel(flag) + const left = colorize(this.config?.theme?.flag, this.flagHelpLabel(flag)) let right = flag.summary || flag.description || '' if (flag.type === 'option' && flag.default) { - right = `[default: ${flag.default}] ${right}` + right = `${colorize(this.config?.theme?.flagDefaultValue, `[default: ${flag.default}]`)} ${right}` } - if (flag.required) right = `(required) ${right}` + if (flag.required) right = `${colorize(this.config?.theme?.flagRequired, '(required)')} ${right}` if (flag.type === 'option' && flag.options && !flag.helpValue && !this.opts.showFlagOptionsInTitle) { - right += `\n` + right += colorize(this.config?.theme?.flagOptions, `\n`) } - return [left, dim(right.trim())] + return [left, colorize(this.config?.theme?.sectionDescription, right.trim())] }) } @@ -197,7 +204,7 @@ export class CommandHelp extends HelpFormatter { }) .join('\n\n') - return body + return colorize(this.config?.theme?.sectionDescription, body) } generate(): string { @@ -305,7 +312,16 @@ export class CommandHelp extends HelpFormatter { const body = (usage ? castArray(usage) : [this.defaultUsage()]) .map((u) => { const allowedSpacing = this.opts.maxWidth - this.indentSpacing - const line = `$ ${this.config.bin} ${u}`.trim() + + const dollarSign = colorize(this.config?.theme?.dollarSign, '$') + const bin = colorize(this.config?.theme?.bin, this.config.bin) + const command = colorize(this.config?.theme?.command, '<%= command.id %>') + const commandDescription = colorize( + this.config?.theme?.sectionDescription, + u.replace('<%= command.id %>', '').trim(), + ) + + const line = `${dollarSign} ${bin} ${command} ${commandDescription}`.trim() if (line.length > allowedSpacing) { const splitIndex = line.slice(0, Math.max(0, allowedSpacing)).lastIndexOf(' ') return ( @@ -323,13 +339,16 @@ export class CommandHelp extends HelpFormatter { private formatIfCommand(example: string): string { example = this.render(example) - if (example.startsWith(this.config.bin)) return dim(`$ ${example}`) - if (example.startsWith(`$ ${this.config.bin}`)) return dim(example) + const dollarSign = colorize(this.config?.theme?.dollarSign, '$') + if (example.startsWith(this.config.bin)) return `${dollarSign} ${example}` + if (example.startsWith(`$ ${this.config.bin}`)) return `${dollarSign}${example.replace(`$`, '')}` return example } private isCommand(example: string): boolean { - return stripAnsi(this.formatIfCommand(example)).startsWith(`$ ${this.config.bin}`) + return stripAnsi(this.formatIfCommand(example)).startsWith( + `${colorize(this.config?.theme?.dollarSign, '$')} ${this.config.bin}`, + ) } } export default CommandHelp diff --git a/src/help/formatter.ts b/src/help/formatter.ts index 9a4b0d9e0..67ea0da76 100644 --- a/src/help/formatter.ts +++ b/src/help/formatter.ts @@ -8,7 +8,7 @@ import wrap from 'wrap-ansi' import {Command} from '../command' import * as Interfaces from '../interfaces' import {stdtermwidth} from '../screen' -import {template} from './util' +import {colorize, template} from './util' export type HelpSectionKeyValueTable = {description: string; name: string}[] export type HelpSection = @@ -176,7 +176,7 @@ export class HelpFormatter { } const output = [ - chalk.bold(header), + colorize(this.config?.theme?.sectionHeader, chalk.bold(header)), this.indent( Array.isArray(newBody) ? this.renderList(newBody, {indentation: 2, stripAnsi: this.opts.stripAnsi}) : newBody, ), diff --git a/src/help/index.ts b/src/help/index.ts index 409d296ce..0007b20bd 100644 --- a/src/help/index.ts +++ b/src/help/index.ts @@ -11,7 +11,13 @@ import {compact, sortBy, uniqBy} from '../util/util' import {CommandHelp} from './command' import {HelpFormatter} from './formatter' import RootHelp from './root' -import {formatCommandDeprecationWarning, getHelpFlagAdditions, standardizeIDFromArgv, toConfiguredId} from './util' +import { + colorize, + formatCommandDeprecationWarning, + getHelpFlagAdditions, + standardizeIDFromArgv, + toConfiguredId, +} from './util' export {CommandHelp} from './command' export {getHelpFlagAdditions, normalizeArgv, standardizeIDFromArgv} from './util' @@ -80,10 +86,10 @@ export class Help extends HelpBase { protected description(c: Command.Loadable): string { const description = this.render(c.description || '') if (c.summary) { - return description + return colorize(this.config?.theme?.sectionDescription, description) } - return description.split('\n').slice(1).join('\n') + return colorize(this.config?.theme?.sectionDescription, description.split('\n').slice(1).join('\n')) } protected formatCommand(command: Command.Loadable): string { @@ -103,7 +109,11 @@ export class Help extends HelpBase { .filter((c) => (this.opts.hideAliasesFromRoot ? !c.aliases?.includes(c.id) : true)) .map((c) => { if (this.config.topicSeparator !== ':') c.id = c.id.replaceAll(':', this.config.topicSeparator) - return [c.id, this.summary(c)] + const summary = this.summary(c) + return [ + colorize(this.config?.theme?.command, c.id), + summary && colorize(this.config?.theme?.sectionDescription, summary), + ] }), { indentation: 2, @@ -127,9 +137,16 @@ export class Help extends HelpBase { let topicID = `${topic.name}:COMMAND` if (this.config.topicSeparator !== ':') topicID = topicID.replaceAll(':', this.config.topicSeparator) let output = compact([ - summary, - this.section(this.opts.usageHeader || 'USAGE', `$ ${this.config.bin} ${topicID}`), - description && this.section('DESCRIPTION', this.wrap(description)), + colorize(this.config?.theme?.commandSummary, summary), + this.section( + this.opts.usageHeader || 'USAGE', + `${colorize(this.config?.theme?.dollarSign, '$')} ${colorize( + this.config?.theme?.bin, + this.config.bin, + )} ${topicID}`, + ), + description && + this.section('DESCRIPTION', this.wrap(colorize(this.config?.theme?.sectionDescription, description))), ]).join('\n\n') if (this.opts.stripAnsi) output = stripAnsi(output) return output + '\n' @@ -140,7 +157,10 @@ export class Help extends HelpBase { const body = this.renderList( topics.map((c) => { if (this.config.topicSeparator !== ':') c.name = c.name.replaceAll(':', this.config.topicSeparator) - return [c.name, c.description && this.render(c.description.split('\n')[0])] + return [ + colorize(this.config?.theme?.topic, c.name), + c.description && this.render(colorize(this.config?.theme?.sectionDescription, c.description.split('\n')[0])), + ] }), { indentation: 2, @@ -334,9 +354,9 @@ export class Help extends HelpBase { } protected summary(c: Command.Loadable): string | undefined { - if (c.summary) return this.render(c.summary.split('\n')[0]) + if (c.summary) return colorize(this.config?.theme?.commandSummary, this.render(c.summary.split('\n')[0])) - return c.description && this.render(c.description).split('\n')[0] + return c.description && colorize(this.config?.theme?.commandSummary, this.render(c.description).split('\n')[0]) } /* diff --git a/src/help/root.ts b/src/help/root.ts index 3955dc68c..a62a1bf37 100644 --- a/src/help/root.ts +++ b/src/help/root.ts @@ -3,6 +3,7 @@ import stripAnsi from 'strip-ansi' import * as Interfaces from '../interfaces' import {compact} from '../util/util' import {HelpFormatter} from './formatter' +import {colorize} from './util' export default class RootHelp extends HelpFormatter { constructor( @@ -17,23 +18,36 @@ export default class RootHelp extends HelpFormatter { description = this.render(description) description = description.split('\n').slice(1).join('\n') if (!description) return - return this.section('DESCRIPTION', this.wrap(description)) + return this.section('DESCRIPTION', this.wrap(colorize(this.config?.theme?.sectionDescription, description))) } root(): string { let description = this.config.pjson.oclif.description || this.config.pjson.description || '' description = this.render(description) description = description.split('\n')[0] - let output = compact([description, this.version(), this.usage(), this.description()]).join('\n\n') + let output = compact([ + colorize(this.config?.theme?.commandSummary, description), + this.version(), + this.usage(), + this.description(), + ]).join('\n\n') if (this.opts.stripAnsi) output = stripAnsi(output) return output } protected usage(): string { - return this.section(this.opts.usageHeader || 'USAGE', this.wrap(`$ ${this.config.bin} [COMMAND]`)) + return this.section( + this.opts.usageHeader || 'USAGE', + this.wrap( + `${colorize(this.config?.theme?.dollarSign, '$')} ${colorize( + this.config?.theme?.bin, + this.config.bin, + )} ${colorize(this.config?.theme?.sectionDescription, '[COMMAND]')}`, + ), + ) } protected version(): string { - return this.section('VERSION', this.wrap(this.config.userAgent)) + return this.section('VERSION', this.wrap(colorize(this.config?.theme?.version, this.config.userAgent))) } } diff --git a/src/help/util.ts b/src/help/util.ts index 82db784f5..12b5f0dbf 100644 --- a/src/help/util.ts +++ b/src/help/util.ts @@ -1,3 +1,5 @@ +import chalk from 'chalk' +import * as Color from 'color' import * as ejs from 'ejs' import {collectUsableIds} from '../config/util' @@ -107,3 +109,7 @@ export function normalizeArgv(config: IConfig, argv = process.argv.slice(2)): st if (config.topicSeparator !== ':' && !argv[0]?.includes(':')) argv = standardizeIDFromArgv(argv, config) return argv } + +export function colorize(color: Color | undefined, text: string): string { + return color ? chalk.hex(color.hex())(text) : text +} diff --git a/src/interfaces/config.ts b/src/interfaces/config.ts index 9a2e73c55..e2bae560c 100644 --- a/src/interfaces/config.ts +++ b/src/interfaces/config.ts @@ -1,3 +1,5 @@ +import * as Color from 'color' + import {Command} from '../command' import {Hook, Hooks} from './hooks' import {PJSON} from './pjson' @@ -24,6 +26,26 @@ export type VersionDetails = { shell?: string } +export const THEME_KEYS = [ + 'alias', + 'bin', + 'command', + 'commandSummary', + 'dollarSign', + 'flag', + 'flagDefaultValue', + 'flagOptions', + 'flagRequired', + 'flagSeparator', + 'flagType', + 'sectionDescription', + 'sectionHeader', + 'topic', + 'version', +] + +export type Theme = Record + export interface Config { /** * process.arch @@ -122,6 +144,7 @@ export interface Config { * active shell */ readonly shell: string + readonly theme?: Theme topicSeparator: ' ' | ':' readonly topics: Topic[] /** diff --git a/src/util/util.ts b/src/util/util.ts index 6b3048a27..4849f6128 100644 --- a/src/util/util.ts +++ b/src/util/util.ts @@ -1,3 +1,7 @@ +import * as Color from 'color' + +import {THEME_KEYS, Theme} from '../interfaces/config' + export function pickBy>( obj: T, fn: (i: T[keyof T]) => boolean, @@ -105,3 +109,16 @@ function get(obj: Record, path: string): unknown { export function mergeNestedObjects(objs: Record[], path: string): Record { return Object.fromEntries(objs.flatMap((o) => Object.entries(get(o, path) ?? {})).reverse()) } + +export function parseTheme(untypedTheme: Record): Theme { + return Object.fromEntries( + Object.entries(untypedTheme) + .filter(([key]) => THEME_KEYS.includes(key)) + .map(([key, value]) => [key, getColor(value)]), + ) +} + +export function getColor(color: string) { + // eslint-disable-next-line new-cap + return new Color.default(color) +} diff --git a/test/config/config.test.ts b/test/config/config.test.ts index 76cf0cb9f..26378a590 100644 --- a/test/config/config.test.ts +++ b/test/config/config.test.ts @@ -45,12 +45,14 @@ const pjson = { } describe('Config', () => { - const testConfig = ({pjson, homedir = '/my/home', platform = 'darwin', env = {}}: Options = {}) => { + const testConfig = ({pjson, homedir = '/my/home', platform = 'darwin', env = {}}: Options = {}, theme?: any) => { let test = fancy .resetConfig() .env(env, {clear: true}) .stub(os, 'getHomeDir', (stub) => stub.returns(join(homedir))) .stub(os, 'getPlatform', (stub) => stub.returns(platform)) + + if (theme) test = test.stub(fs, 'safeReadJson', (stub) => stub.resolves(theme)) if (pjson) test = test.stub(fs, 'readJson', (stub) => stub.resolves(pjson)) test = test.add('config', () => Config.load()) @@ -401,4 +403,48 @@ describe('Config', () => { }, ) }) + + describe('theme', () => { + testConfig({pjson, env: {FOO_DISABLE_THEME: 'true'}}, {bin: 'red'}).it( + 'should not be set when DISABLE_THEME is true and theme.json exists', + (config) => { + expect(config).to.have.property('theme', undefined) + }, + ) + + testConfig({pjson, env: {FOO_DISABLE_THEME: 'false'}}, {bin: 'red'}).it( + 'should be set when DISABLE_THEME is false and theme.json exists', + (config) => { + expect(config).to.nested.include({'theme.bin.color[0]': 255}) + }, + ) + + testConfig({pjson, env: {}}, {bin: 'red'}).it( + 'should be set when DISABLE_THEME is unset and theme.json exists', + (config) => { + expect(config).to.nested.include({'theme.bin.color[0]': 255}) + }, + ) + + testConfig({pjson, env: {FOO_DISABLE_THEME: 'true'}}).it( + 'should not be set when DISABLE_THEME is true and theme.json does not exist', + (config) => { + expect(config).to.have.property('theme', undefined) + }, + ) + + testConfig({pjson, env: {FOO_DISABLE_THEME: 'false'}}).it( + 'should not be set when DISABLE_THEME is false and theme.json does not exist', + (config) => { + expect(config).to.have.property('theme', undefined) + }, + ) + + testConfig({pjson, env: {}}).it( + 'should not be set when DISABLE_THEME is unset and theme.json does not exist', + (config) => { + expect(config).to.have.property('theme', undefined) + }, + ) + }) }) diff --git a/test/help/format-command-with-options.test.ts b/test/help/format-command-with-options.test.ts index 5e0abf7bf..24c45718a 100644 --- a/test/help/format-command-with-options.test.ts +++ b/test/help/format-command-with-options.test.ts @@ -321,24 +321,24 @@ ARGUMENTS const cmd = await makeLoadable( makeCommandClass({ id: 'apps:create', - usage: '<%= config.bin %> <%= command.id %> usage', + usage: '<%= command.id %> usage', }), ) const output = help.formatCommand(cmd) expect(output).to.equal(`USAGE - $ oclif oclif apps:create usage`) + $ oclif apps:create usage`) }) it('should output usage arrays with templates', async () => { const cmd = await makeLoadable( makeCommandClass({ id: 'apps:create', - usage: ['<%= config.bin %>', '<%= command.id %> usage'], + usage: ['<%= config.id %>', '<%= command.id %> usage'], }), ) const output = help.formatCommand(cmd) expect(output).to.equal(`USAGE - $ oclif oclif + $ oclif apps:create $ oclif apps:create usage`) }) diff --git a/test/help/format-command.test.ts b/test/help/format-command.test.ts index b9f466ae0..7365db709 100644 --- a/test/help/format-command.test.ts +++ b/test/help/format-command.test.ts @@ -524,26 +524,26 @@ ARGUMENTS const cmd = await makeLoadable( makeCommandClass({ id: 'apps:create', - usage: '<%= config.bin %> <%= command.id %> usage', + usage: '<%= command.id %> usage', }), ) const output = help.formatCommand(cmd) expect(output).to.equal(`USAGE - $ oclif oclif apps:create usage`) + $ oclif apps:create usage`) }) it('should output usage arrays with templates', async () => { const cmd = await makeLoadable( makeCommandClass({ id: 'apps:create', - usage: ['<%= config.bin %>', '<%= command.id %> usage'], + usage: ['<%= command.id %>', '<%= command.id %> usage'], }), ) const output = help.formatCommand(cmd) expect(output).to.equal(`USAGE - $ oclif oclif + $ oclif apps:create $ oclif apps:create usage`) }) diff --git a/test/help/util.test.ts b/test/help/util.test.ts index d2a27e19a..8cb233822 100644 --- a/test/help/util.test.ts +++ b/test/help/util.test.ts @@ -1,10 +1,13 @@ import {test} from '@oclif/test' import {expect} from 'chai' +import chalk from 'chalk' import {resolve} from 'node:path' import {Config, Interfaces} from '../../src' import * as util from '../../src/config/util' import {loadHelpClass, standardizeIDFromArgv} from '../../src/help' +import {colorize} from '../../src/help/util' +import {getColor} from '../../src/util/util' import configuredHelpClass from './_test-help-class' describe('util', () => { @@ -183,4 +186,28 @@ describe('util', () => { }, ) }) + + describe('colorize', () => { + const color = getColor('red') + + it('should return text with ansi characters when given color', () => { + const text = colorize(color, 'brazil') + expect(text).to.equal(chalk.hex(color.hex())('brazil')) + }) + + it('should return text without ansi characters when given undefined', () => { + const text = colorize(undefined, 'brazil') + expect(text).to.equal('brazil') + }) + + it('should return empty text without ansi characters when given color', () => { + const text = colorize(color, '') + expect(text).to.equal('') + }) + + it('should return empty text without ansi characters when given undefined', () => { + const text = colorize(undefined, '') + expect(text).to.equal('') + }) + }) }) diff --git a/test/util/util.test.ts b/test/util/util.test.ts index d2c00b57b..42c318ab7 100644 --- a/test/util/util.test.ts +++ b/test/util/util.test.ts @@ -1,6 +1,17 @@ import {expect} from 'chai' -import {capitalize, castArray, isNotFalsy, isTruthy, last, maxBy, mergeNestedObjects, sumBy} from '../../src/util/util' +import {THEME_KEYS} from '../../src/interfaces/config' +import { + capitalize, + castArray, + isNotFalsy, + isTruthy, + last, + maxBy, + mergeNestedObjects, + parseTheme, + sumBy, +} from '../../src/util/util' describe('capitalize', () => { it('capitalizes the string', () => { @@ -128,3 +139,191 @@ describe('mergeNestedObjects', () => { }) }) }) + +describe('theme parsing', () => { + it('should parse untyped theme json to theme', () => { + const untypedTheme = { + alias: '#FFFFFF', + bin: '#FFFFFF', + command: '#FFFFFF', + commandSummary: '#FFFFFF', + dollarSign: '#FFFFFF', + flag: '#FFFFFF', + flagDefaultValue: '#FFFFFF', + flagOptions: '#FFFFFF', + flagRequired: '#FFFFFF', + flagSeparator: '#FFFFFF', + flagType: '#FFFFFF', + sectionDescription: '#FFFFFF', + sectionHeader: '#FFFFFF', + topic: '#FFFFFF', + version: '#FFFFFF', + } + + const theme = parseTheme(untypedTheme) + + for (const key of Object.keys(theme)) { + expect(THEME_KEYS.includes(key)).to.be.true + } + }) + + it('should parse alias', () => { + const untypedTheme = { + alias: '#FFFFFF', + } + + const theme = parseTheme(untypedTheme) + + expect(theme.alias?.hex()).to.equal('#FFFFFF') + }) + + it('should parse bin', () => { + const untypedTheme = { + bin: '#FFFFFF', + } + + const theme = parseTheme(untypedTheme) + + expect(theme.bin?.hex()).to.equal('#FFFFFF') + }) + + it('should parse command', () => { + const untypedTheme = { + command: '#FFFFFF', + } + + const theme = parseTheme(untypedTheme) + + expect(theme.command?.hex()).to.equal('#FFFFFF') + }) + + it('should parse commandSummary', () => { + const untypedTheme = { + commandSummary: '#FFFFFF', + } + + const theme = parseTheme(untypedTheme) + + expect(theme.commandSummary?.hex()).to.equal('#FFFFFF') + }) + + it('should parse dollarSign', () => { + const untypedTheme = { + dollarSign: '#FFFFFF', + } + + const theme = parseTheme(untypedTheme) + + expect(theme.dollarSign?.hex()).to.equal('#FFFFFF') + }) + + it('should parse flag', () => { + const untypedTheme = { + flag: '#FFFFFF', + } + + const theme = parseTheme(untypedTheme) + + expect(theme.flag?.hex()).to.equal('#FFFFFF') + }) + + it('should parse flagDefaultValue', () => { + const untypedTheme = { + flagDefaultValue: '#FFFFFF', + } + + const theme = parseTheme(untypedTheme) + + expect(theme.flagDefaultValue?.hex()).to.equal('#FFFFFF') + }) + + it('should parse flagOptions', () => { + const untypedTheme = { + flagOptions: '#FFFFFF', + } + + const theme = parseTheme(untypedTheme) + + expect(theme.flagOptions?.hex()).to.equal('#FFFFFF') + }) + + it('should parse flagRequired', () => { + const untypedTheme = { + flagRequired: '#FFFFFF', + } + + const theme = parseTheme(untypedTheme) + + expect(theme.flagRequired?.hex()).to.equal('#FFFFFF') + }) + + it('should parse flagSeparator', () => { + const untypedTheme = { + flagSeparator: '#FFFFFF', + } + + const theme = parseTheme(untypedTheme) + + expect(theme.flagSeparator?.hex()).to.equal('#FFFFFF') + }) + + it('should parse flagType', () => { + const untypedTheme = { + flagType: '#FFFFFF', + } + + const theme = parseTheme(untypedTheme) + + expect(theme.flagType?.hex()).to.equal('#FFFFFF') + }) + + it('should parse sectionDescription', () => { + const untypedTheme = { + sectionDescription: '#FFFFFF', + } + + const theme = parseTheme(untypedTheme) + + expect(theme.sectionDescription?.hex()).to.equal('#FFFFFF') + }) + + it('should parse sectionHeader', () => { + const untypedTheme = { + sectionHeader: '#FFFFFF', + } + + const theme = parseTheme(untypedTheme) + + expect(theme.sectionHeader?.hex()).to.equal('#FFFFFF') + }) + + it('should parse topic', () => { + const untypedTheme = { + topic: '#FFFFFF', + } + + const theme = parseTheme(untypedTheme) + + expect(theme.topic?.hex()).to.equal('#FFFFFF') + }) + + it('should parse version', () => { + const untypedTheme = { + version: '#FFFFFF', + } + + const theme = parseTheme(untypedTheme) + + expect(theme.version?.hex()).to.equal('#FFFFFF') + }) + + it('should not parse color key that is not part of Theme', () => { + const untypedTheme = { + batman: '#000000', + } + + const theme = parseTheme(untypedTheme) + + expect(Object.keys(theme).includes('batman')).to.be.false + }) +}) diff --git a/yarn.lock b/yarn.lock index c319598bc..2845fdfb9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1012,11 +1012,25 @@ dependencies: "@types/node" "*" +"@types/color-convert@*": + version "2.0.2" + resolved "http://registry.yarnpkg.com/@types/color-convert/-/color-convert-2.0.2.tgz#a5fa5da9b866732f8bf86b01964869011e2a2356" + integrity sha512-KGRIgCxwcgazts4MXRCikPbIMzBpjfdgEZSy8TRHU/gtg+f9sOfHdtK8unPfxIoBtyd2aTTwINVLSNENlC8U8A== + dependencies: + "@types/color-name" "*" + "@types/color-name@*": version "1.1.1" resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== +"@types/color@^3.0.5": + version "3.0.5" + resolved "http://registry.yarnpkg.com/@types/color/-/color-3.0.5.tgz#658fd9286a44c21dabaa56c2e2f63da3ac15f063" + integrity sha512-T9yHCNtd8ap9L/r8KEESu5RDMLkoWXHo7dTureNoI1dbp25NsCN054vOu09iniIjR21MXUL+LU9bkIWrbyg8gg== + dependencies: + "@types/color-convert" "*" + "@types/debug@^4.1.10": version "4.1.10" resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.10.tgz#f23148a6eb771a34c466a4fc28379d8101e84494" @@ -2091,16 +2105,32 @@ color-name@1.1.3: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= -color-name@^1.1.4, color-name@~1.1.4: +color-name@^1.0.0, color-name@^1.1.4, color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +color-string@^1.9.0: + version "1.9.1" + resolved "http://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" + integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + color-support@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== +color@^4.2.3: + version "4.2.3" + resolved "http://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a" + integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A== + dependencies: + color-convert "^2.0.1" + color-string "^1.9.0" + colorette@^2.0.20: version "2.0.20" resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" @@ -3844,6 +3874,11 @@ is-arrayish@^0.2.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= +is-arrayish@^0.3.1: + version "0.3.2" + resolved "http://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + is-bigint@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" @@ -6282,6 +6317,13 @@ sigstore@^1.3.0, sigstore@^1.4.0, sigstore@^1.7.0: "@sigstore/tuf" "^1.0.3" make-fetch-happen "^11.0.1" +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "http://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== + dependencies: + is-arrayish "^0.3.1" + sinon@^16.0.0, sinon@^16.1.0: version "16.1.0" resolved "https://registry.yarnpkg.com/sinon/-/sinon-16.1.0.tgz#645b836563c9bedb21defdbe48831cb2afb687f2" From 418d124ea77137688842bdd17b40ad0e32796818 Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Thu, 9 Nov 2023 12:50:45 -0700 Subject: [PATCH 02/16] fix: https in yarn.lock --- yarn.lock | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/yarn.lock b/yarn.lock index 2845fdfb9..7dae75b44 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1013,21 +1013,21 @@ "@types/node" "*" "@types/color-convert@*": - version "2.0.2" - resolved "http://registry.yarnpkg.com/@types/color-convert/-/color-convert-2.0.2.tgz#a5fa5da9b866732f8bf86b01964869011e2a2356" - integrity sha512-KGRIgCxwcgazts4MXRCikPbIMzBpjfdgEZSy8TRHU/gtg+f9sOfHdtK8unPfxIoBtyd2aTTwINVLSNENlC8U8A== + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/color-convert/-/color-convert-2.0.3.tgz#e93f5c991eda87a945058b47044f5f0008b0dce9" + integrity sha512-2Q6wzrNiuEvYxVQqhh7sXM2mhIhvZR/Paq4FdsQkOMgWsCIkKvSGj8Le1/XalulrmgOzPMqNa0ix+ePY4hTrfg== dependencies: "@types/color-name" "*" "@types/color-name@*": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" - integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== + version "1.1.3" + resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.3.tgz#c488ac2e519c9795faa0d54e8156d54e66adc4e6" + integrity sha512-87W6MJCKZYDhLAx/J1ikW8niMvmGRyY+rpUxWpL1cO7F8Uu5CHuQoFv+R0/L5pgNdW4jTyda42kv60uwVIPjLw== "@types/color@^3.0.5": - version "3.0.5" - resolved "http://registry.yarnpkg.com/@types/color/-/color-3.0.5.tgz#658fd9286a44c21dabaa56c2e2f63da3ac15f063" - integrity sha512-T9yHCNtd8ap9L/r8KEESu5RDMLkoWXHo7dTureNoI1dbp25NsCN054vOu09iniIjR21MXUL+LU9bkIWrbyg8gg== + version "3.0.6" + resolved "https://registry.yarnpkg.com/@types/color/-/color-3.0.6.tgz#29c27a99d4de2975e1676712679a0bd7f646a3fb" + integrity sha512-NMiNcZFRUAiUUCCf7zkAelY8eV3aKqfbzyFQlXpPIEeoNDbsEHGpb854V3gzTsGKYj830I5zPuOwU/TP5/cW6A== dependencies: "@types/color-convert" "*" @@ -2112,7 +2112,7 @@ color-name@^1.0.0, color-name@^1.1.4, color-name@~1.1.4: color-string@^1.9.0: version "1.9.1" - resolved "http://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== dependencies: color-name "^1.0.0" @@ -2125,7 +2125,7 @@ color-support@^1.1.3: color@^4.2.3: version "4.2.3" - resolved "http://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a" + resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a" integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A== dependencies: color-convert "^2.0.1" @@ -3876,7 +3876,7 @@ is-arrayish@^0.2.1: is-arrayish@^0.3.1: version "0.3.2" - resolved "http://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== is-bigint@^1.0.1: @@ -6319,7 +6319,7 @@ sigstore@^1.3.0, sigstore@^1.4.0, sigstore@^1.7.0: simple-swizzle@^0.2.2: version "0.2.2" - resolved "http://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== dependencies: is-arrayish "^0.3.1" From 52f2240959f9236a22f79dec8a44093d5685fe3f Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Thu, 9 Nov 2023 14:11:05 -0700 Subject: [PATCH 03/16] feat: use chalk for standard colors --- src/config/config.ts | 6 +- src/help/command.ts | 2 +- src/help/formatter.ts | 3 +- src/help/index.ts | 9 +- src/help/root.ts | 2 +- src/help/util.ts | 6 -- src/interfaces/config.ts | 23 +---- src/interfaces/theme.ts | 43 ++++++++ src/util/theme.ts | 170 +++++++++++++++++++++++++++++++ src/util/util.ts | 17 ---- test/config/config.test.ts | 6 +- test/help/util.test.ts | 27 ----- test/util/theme.test.ts | 91 +++++++++++++++++ test/util/util.test.ts | 201 +------------------------------------ 14 files changed, 319 insertions(+), 287 deletions(-) create mode 100644 src/interfaces/theme.ts create mode 100644 src/util/theme.ts create mode 100644 test/util/theme.test.ts diff --git a/src/config/config.ts b/src/config/config.ts index 9eed4ec40..dd426ebc1 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -9,14 +9,16 @@ import {Command} from '../command' import {CLIError, error, exit, warn} from '../errors' import {getHelpFlagAdditions} from '../help/util' import {Hook, Hooks, PJSON, Topic} from '../interfaces' -import {ArchTypes, Config as IConfig, LoadOptions, PlatformTypes, Theme, VersionDetails} from '../interfaces/config' +import {ArchTypes, Config as IConfig, LoadOptions, PlatformTypes, VersionDetails} from '../interfaces/config' import {Plugin as IPlugin, Options} from '../interfaces/plugin' +import {Theme} from '../interfaces/theme' import {loadWithData} from '../module-loader' import {OCLIF_MARKER_OWNER, Performance} from '../performance' import {settings} from '../settings' import {requireJson, safeReadJson} from '../util/fs' import {getHomeDir, getPlatform} from '../util/os' -import {compact, isProd, parseTheme} from '../util/util' +import {parseTheme} from '../util/theme' +import {compact, isProd} from '../util/util' import Cache from './cache' import PluginLoader from './plugin-loader' import {tsPath} from './ts-node' diff --git a/src/help/command.ts b/src/help/command.ts index f1a587387..9ae0b9e9e 100644 --- a/src/help/command.ts +++ b/src/help/command.ts @@ -4,10 +4,10 @@ import stripAnsi from 'strip-ansi' import {Command} from '../command' import * as Interfaces from '../interfaces' import {ensureArgObject} from '../util/ensure-arg-object' +import {colorize} from '../util/theme' import {castArray, compact, sortBy} from '../util/util' import {DocOpts} from './docopts' import {HelpFormatter, HelpSection, HelpSectionRenderer} from './formatter' -import {colorize} from './util' // Don't use os.EOL because we need to ensure that a string // written on any platform, that may use \r\n or \n, will be diff --git a/src/help/formatter.ts b/src/help/formatter.ts index 67ea0da76..aa8d9a3e9 100644 --- a/src/help/formatter.ts +++ b/src/help/formatter.ts @@ -8,7 +8,8 @@ import wrap from 'wrap-ansi' import {Command} from '../command' import * as Interfaces from '../interfaces' import {stdtermwidth} from '../screen' -import {colorize, template} from './util' +import {colorize} from '../util/theme' +import {template} from './util' export type HelpSectionKeyValueTable = {description: string; name: string}[] export type HelpSection = diff --git a/src/help/index.ts b/src/help/index.ts index 0007b20bd..301a61ac1 100644 --- a/src/help/index.ts +++ b/src/help/index.ts @@ -7,17 +7,12 @@ import {error} from '../errors' import * as Interfaces from '../interfaces' import {load} from '../module-loader' import {cacheDefaultValue} from '../util/cache-default-value' +import {colorize} from '../util/theme' import {compact, sortBy, uniqBy} from '../util/util' import {CommandHelp} from './command' import {HelpFormatter} from './formatter' import RootHelp from './root' -import { - colorize, - formatCommandDeprecationWarning, - getHelpFlagAdditions, - standardizeIDFromArgv, - toConfiguredId, -} from './util' +import {formatCommandDeprecationWarning, getHelpFlagAdditions, standardizeIDFromArgv, toConfiguredId} from './util' export {CommandHelp} from './command' export {getHelpFlagAdditions, normalizeArgv, standardizeIDFromArgv} from './util' diff --git a/src/help/root.ts b/src/help/root.ts index a62a1bf37..87b03ea3a 100644 --- a/src/help/root.ts +++ b/src/help/root.ts @@ -1,9 +1,9 @@ import stripAnsi from 'strip-ansi' import * as Interfaces from '../interfaces' +import {colorize} from '../util/theme' import {compact} from '../util/util' import {HelpFormatter} from './formatter' -import {colorize} from './util' export default class RootHelp extends HelpFormatter { constructor( diff --git a/src/help/util.ts b/src/help/util.ts index 12b5f0dbf..82db784f5 100644 --- a/src/help/util.ts +++ b/src/help/util.ts @@ -1,5 +1,3 @@ -import chalk from 'chalk' -import * as Color from 'color' import * as ejs from 'ejs' import {collectUsableIds} from '../config/util' @@ -109,7 +107,3 @@ export function normalizeArgv(config: IConfig, argv = process.argv.slice(2)): st if (config.topicSeparator !== ':' && !argv[0]?.includes(':')) argv = standardizeIDFromArgv(argv, config) return argv } - -export function colorize(color: Color | undefined, text: string): string { - return color ? chalk.hex(color.hex())(text) : text -} diff --git a/src/interfaces/config.ts b/src/interfaces/config.ts index e2bae560c..a5dcc58d1 100644 --- a/src/interfaces/config.ts +++ b/src/interfaces/config.ts @@ -1,9 +1,8 @@ -import * as Color from 'color' - import {Command} from '../command' import {Hook, Hooks} from './hooks' import {PJSON} from './pjson' import {Options, Plugin} from './plugin' +import {Theme} from './theme' import {Topic} from './topic' export type LoadOptions = Config | Options | string | undefined @@ -26,26 +25,6 @@ export type VersionDetails = { shell?: string } -export const THEME_KEYS = [ - 'alias', - 'bin', - 'command', - 'commandSummary', - 'dollarSign', - 'flag', - 'flagDefaultValue', - 'flagOptions', - 'flagRequired', - 'flagSeparator', - 'flagType', - 'sectionDescription', - 'sectionHeader', - 'topic', - 'version', -] - -export type Theme = Record - export interface Config { /** * process.arch diff --git a/src/interfaces/theme.ts b/src/interfaces/theme.ts new file mode 100644 index 000000000..652f05373 --- /dev/null +++ b/src/interfaces/theme.ts @@ -0,0 +1,43 @@ +import * as Color from 'color' + +// chalk doesn't export a list of standard colors, so we have to supply our own +export const STANDARD_CHALK = [ + 'white', + 'black', + 'blue', + 'yellow', + 'green', + 'red', + 'magenta', + 'cyan', + 'gray', + 'blackBright', + 'redBright', + 'greenBright', + 'yellowBright', + 'blueBright', + 'magentaBright', + 'cyanBright', + 'whiteBright', + 'bgBlack', + 'bgRed', + 'bgGreen', + 'bgYellow', + 'bgBlue', + 'bgMagenta', + 'bgCyan', + 'bgWhite', + 'bgGray', + 'bgBlackBright', + 'bgRedBright', + 'bgGreenBright', + 'bgYellowBright', + 'bgBlueBright', + 'bgMagentaBright', + 'bgCyanBright', + 'bgWhiteBright', +] as const + +export type StandardChalk = (typeof STANDARD_CHALK)[number] + +export type Theme = Record diff --git a/src/util/theme.ts b/src/util/theme.ts new file mode 100644 index 000000000..219d36591 --- /dev/null +++ b/src/util/theme.ts @@ -0,0 +1,170 @@ +import chalk from 'chalk' +import * as Color from 'color' + +import {STANDARD_CHALK, StandardChalk, Theme} from '../interfaces/theme' + +// eslint-disable-next-line complexity +function standardChalk(color: StandardChalk, text: string): string { + switch (color) { + case 'red': { + return chalk.red(text) + } + + case 'blue': { + return chalk.blue(text) + } + + case 'yellow': { + return chalk.yellow(text) + } + + case 'green': { + return chalk.green(text) + } + + case 'cyan': { + return chalk.cyan(text) + } + + case 'magenta': { + return chalk.magenta(text) + } + + case 'white': { + return chalk.white(text) + } + + case 'black': { + return chalk.black(text) + } + + case 'gray': { + return chalk.gray(text) + } + + case 'blackBright': { + return chalk.blackBright(text) + } + + case 'redBright': { + return chalk.redBright(text) + } + + case 'greenBright': { + return chalk.greenBright(text) + } + + case 'yellowBright': { + return chalk.yellowBright(text) + } + + case 'blueBright': { + return chalk.blueBright(text) + } + + case 'magentaBright': { + return chalk.magentaBright(text) + } + + case 'cyanBright': { + return chalk.cyanBright(text) + } + + case 'whiteBright': { + return chalk.whiteBright(text) + } + + case 'bgBlack': { + return chalk.bgBlack(text) + } + + case 'bgRed': { + return chalk.bgRed(text) + } + + case 'bgGreen': { + return chalk.bgGreen(text) + } + + case 'bgYellow': { + return chalk.bgYellow(text) + } + + case 'bgBlue': { + return chalk.bgBlue(text) + } + + case 'bgMagenta': { + return chalk.bgMagenta(text) + } + + case 'bgCyan': { + return chalk.bgCyan(text) + } + + case 'bgWhite': { + return chalk.bgWhite(text) + } + + case 'bgGray': { + return chalk.bgGray(text) + } + + case 'bgBlackBright': { + return chalk.bgBlackBright(text) + } + + case 'bgRedBright': { + return chalk.bgRedBright(text) + } + + case 'bgGreenBright': { + return chalk.bgGreenBright(text) + } + + case 'bgYellowBright': { + return chalk.bgYellowBright(text) + } + + case 'bgBlueBright': { + return chalk.bgBlueBright(text) + } + + case 'bgMagentaBright': { + return chalk.bgMagentaBright(text) + } + + case 'bgCyanBright': { + return chalk.bgCyanBright(text) + } + + case 'bgWhiteBright': { + return chalk.bgWhiteBright(text) + } + + default: { + return chalk.gray(text) + } + } +} + +function isStandardChalk(color: any): color is StandardChalk { + return STANDARD_CHALK.includes(color) +} + +export function colorize(color: Color | StandardChalk | undefined, text: string): string { + if (isStandardChalk(color)) return standardChalk(color, text) + + return color ? chalk.hex(color.hex())(text) : text +} + +export function parseTheme(untypedTheme: Record): Theme { + return Object.fromEntries(Object.entries(untypedTheme).map(([key, value]) => [key, getColor(value)])) +} + +export function getColor(color: string): Color +export function getColor(color: StandardChalk): StandardChalk +export function getColor(color: string | StandardChalk): Color | StandardChalk { + // eslint-disable-next-line new-cap + return isStandardChalk(color) ? color : new Color.default(color) +} diff --git a/src/util/util.ts b/src/util/util.ts index 4849f6128..6b3048a27 100644 --- a/src/util/util.ts +++ b/src/util/util.ts @@ -1,7 +1,3 @@ -import * as Color from 'color' - -import {THEME_KEYS, Theme} from '../interfaces/config' - export function pickBy>( obj: T, fn: (i: T[keyof T]) => boolean, @@ -109,16 +105,3 @@ function get(obj: Record, path: string): unknown { export function mergeNestedObjects(objs: Record[], path: string): Record { return Object.fromEntries(objs.flatMap((o) => Object.entries(get(o, path) ?? {})).reverse()) } - -export function parseTheme(untypedTheme: Record): Theme { - return Object.fromEntries( - Object.entries(untypedTheme) - .filter(([key]) => THEME_KEYS.includes(key)) - .map(([key, value]) => [key, getColor(value)]), - ) -} - -export function getColor(color: string) { - // eslint-disable-next-line new-cap - return new Color.default(color) -} diff --git a/test/config/config.test.ts b/test/config/config.test.ts index 26378a590..01920ba3a 100644 --- a/test/config/config.test.ts +++ b/test/config/config.test.ts @@ -405,21 +405,21 @@ describe('Config', () => { }) describe('theme', () => { - testConfig({pjson, env: {FOO_DISABLE_THEME: 'true'}}, {bin: 'red'}).it( + testConfig({pjson, env: {FOO_DISABLE_THEME: 'true'}}, {bin: '#FF0000'}).it( 'should not be set when DISABLE_THEME is true and theme.json exists', (config) => { expect(config).to.have.property('theme', undefined) }, ) - testConfig({pjson, env: {FOO_DISABLE_THEME: 'false'}}, {bin: 'red'}).it( + testConfig({pjson, env: {FOO_DISABLE_THEME: 'false'}}, {bin: '#FF0000'}).it( 'should be set when DISABLE_THEME is false and theme.json exists', (config) => { expect(config).to.nested.include({'theme.bin.color[0]': 255}) }, ) - testConfig({pjson, env: {}}, {bin: 'red'}).it( + testConfig({pjson, env: {}}, {bin: '#FF0000'}).it( 'should be set when DISABLE_THEME is unset and theme.json exists', (config) => { expect(config).to.nested.include({'theme.bin.color[0]': 255}) diff --git a/test/help/util.test.ts b/test/help/util.test.ts index 8cb233822..d2a27e19a 100644 --- a/test/help/util.test.ts +++ b/test/help/util.test.ts @@ -1,13 +1,10 @@ import {test} from '@oclif/test' import {expect} from 'chai' -import chalk from 'chalk' import {resolve} from 'node:path' import {Config, Interfaces} from '../../src' import * as util from '../../src/config/util' import {loadHelpClass, standardizeIDFromArgv} from '../../src/help' -import {colorize} from '../../src/help/util' -import {getColor} from '../../src/util/util' import configuredHelpClass from './_test-help-class' describe('util', () => { @@ -186,28 +183,4 @@ describe('util', () => { }, ) }) - - describe('colorize', () => { - const color = getColor('red') - - it('should return text with ansi characters when given color', () => { - const text = colorize(color, 'brazil') - expect(text).to.equal(chalk.hex(color.hex())('brazil')) - }) - - it('should return text without ansi characters when given undefined', () => { - const text = colorize(undefined, 'brazil') - expect(text).to.equal('brazil') - }) - - it('should return empty text without ansi characters when given color', () => { - const text = colorize(color, '') - expect(text).to.equal('') - }) - - it('should return empty text without ansi characters when given undefined', () => { - const text = colorize(undefined, '') - expect(text).to.equal('') - }) - }) }) diff --git a/test/util/theme.test.ts b/test/util/theme.test.ts new file mode 100644 index 000000000..af73d84f2 --- /dev/null +++ b/test/util/theme.test.ts @@ -0,0 +1,91 @@ +import {config, expect} from 'chai' +import chalk from 'chalk' + +config.truncateThreshold = 0 + +import {colorize, getColor, parseTheme} from '../../src/util/theme' +describe('colorize', () => { + it('should return text with ansi characters when given hex code', () => { + const color = getColor('#FF0000') + const text = colorize(color, 'brazil') + expect(text).to.equal(chalk.hex(color.hex())('brazil')) + }) + + it('should return text with ansi characters when standard chalk color', () => { + const text = colorize('red', 'brazil') + expect(text).to.equal(chalk.red('brazil')) + }) + + it('should return text without ansi characters when given undefined', () => { + const text = colorize(undefined, 'brazil') + expect(text).to.equal('brazil') + }) + + it('should return empty text without ansi characters when given color', () => { + const color = getColor('#FF0000') + const text = colorize(color, '') + expect(text).to.equal('') + }) + + it('should return empty text without ansi characters when given undefined', () => { + const text = colorize(undefined, '') + expect(text).to.equal('') + }) +}) + +describe('theme parsing', () => { + it('should parse untyped theme json to theme using hex codes', () => { + const untypedTheme = { + alias: '#FFFFFF', + bin: '#FFFFFF', + command: '#FFFFFF', + commandSummary: '#FFFFFF', + dollarSign: '#FFFFFF', + flag: '#FFFFFF', + flagDefaultValue: '#FFFFFF', + flagOptions: '#FFFFFF', + flagRequired: '#FFFFFF', + flagSeparator: '#FFFFFF', + flagType: '#FFFFFF', + sectionDescription: '#FFFFFF', + sectionHeader: '#FFFFFF', + topic: '#FFFFFF', + version: '#FFFFFF', + } + + const theme = parseTheme(untypedTheme) + + for (const key of Object.keys(theme)) { + expect(typeof theme[key]).to.equal('object') + expect(theme[key]).to.have.property('model') + expect(theme[key]).to.have.property('color') + expect(theme[key]).to.have.property('valpha') + } + }) + + it('should parse untyped theme json to theme using chalk standard colors', () => { + const untypedTheme = { + alias: 'cyan', + bin: 'cyan', + command: 'cyan', + commandSummary: 'cyan', + dollarSign: 'cyan', + flag: 'cyan', + flagDefaultValue: 'cyan', + flagOptions: 'cyan', + flagRequired: 'cyan', + flagSeparator: 'cyan', + flagType: 'cyan', + sectionDescription: 'cyan', + sectionHeader: 'cyan', + topic: 'cyan', + version: 'cyan', + } + + const theme = parseTheme(untypedTheme) + + for (const key of Object.keys(theme)) { + expect(theme[key]).to.equal('cyan') + } + }) +}) diff --git a/test/util/util.test.ts b/test/util/util.test.ts index 42c318ab7..d2c00b57b 100644 --- a/test/util/util.test.ts +++ b/test/util/util.test.ts @@ -1,17 +1,6 @@ import {expect} from 'chai' -import {THEME_KEYS} from '../../src/interfaces/config' -import { - capitalize, - castArray, - isNotFalsy, - isTruthy, - last, - maxBy, - mergeNestedObjects, - parseTheme, - sumBy, -} from '../../src/util/util' +import {capitalize, castArray, isNotFalsy, isTruthy, last, maxBy, mergeNestedObjects, sumBy} from '../../src/util/util' describe('capitalize', () => { it('capitalizes the string', () => { @@ -139,191 +128,3 @@ describe('mergeNestedObjects', () => { }) }) }) - -describe('theme parsing', () => { - it('should parse untyped theme json to theme', () => { - const untypedTheme = { - alias: '#FFFFFF', - bin: '#FFFFFF', - command: '#FFFFFF', - commandSummary: '#FFFFFF', - dollarSign: '#FFFFFF', - flag: '#FFFFFF', - flagDefaultValue: '#FFFFFF', - flagOptions: '#FFFFFF', - flagRequired: '#FFFFFF', - flagSeparator: '#FFFFFF', - flagType: '#FFFFFF', - sectionDescription: '#FFFFFF', - sectionHeader: '#FFFFFF', - topic: '#FFFFFF', - version: '#FFFFFF', - } - - const theme = parseTheme(untypedTheme) - - for (const key of Object.keys(theme)) { - expect(THEME_KEYS.includes(key)).to.be.true - } - }) - - it('should parse alias', () => { - const untypedTheme = { - alias: '#FFFFFF', - } - - const theme = parseTheme(untypedTheme) - - expect(theme.alias?.hex()).to.equal('#FFFFFF') - }) - - it('should parse bin', () => { - const untypedTheme = { - bin: '#FFFFFF', - } - - const theme = parseTheme(untypedTheme) - - expect(theme.bin?.hex()).to.equal('#FFFFFF') - }) - - it('should parse command', () => { - const untypedTheme = { - command: '#FFFFFF', - } - - const theme = parseTheme(untypedTheme) - - expect(theme.command?.hex()).to.equal('#FFFFFF') - }) - - it('should parse commandSummary', () => { - const untypedTheme = { - commandSummary: '#FFFFFF', - } - - const theme = parseTheme(untypedTheme) - - expect(theme.commandSummary?.hex()).to.equal('#FFFFFF') - }) - - it('should parse dollarSign', () => { - const untypedTheme = { - dollarSign: '#FFFFFF', - } - - const theme = parseTheme(untypedTheme) - - expect(theme.dollarSign?.hex()).to.equal('#FFFFFF') - }) - - it('should parse flag', () => { - const untypedTheme = { - flag: '#FFFFFF', - } - - const theme = parseTheme(untypedTheme) - - expect(theme.flag?.hex()).to.equal('#FFFFFF') - }) - - it('should parse flagDefaultValue', () => { - const untypedTheme = { - flagDefaultValue: '#FFFFFF', - } - - const theme = parseTheme(untypedTheme) - - expect(theme.flagDefaultValue?.hex()).to.equal('#FFFFFF') - }) - - it('should parse flagOptions', () => { - const untypedTheme = { - flagOptions: '#FFFFFF', - } - - const theme = parseTheme(untypedTheme) - - expect(theme.flagOptions?.hex()).to.equal('#FFFFFF') - }) - - it('should parse flagRequired', () => { - const untypedTheme = { - flagRequired: '#FFFFFF', - } - - const theme = parseTheme(untypedTheme) - - expect(theme.flagRequired?.hex()).to.equal('#FFFFFF') - }) - - it('should parse flagSeparator', () => { - const untypedTheme = { - flagSeparator: '#FFFFFF', - } - - const theme = parseTheme(untypedTheme) - - expect(theme.flagSeparator?.hex()).to.equal('#FFFFFF') - }) - - it('should parse flagType', () => { - const untypedTheme = { - flagType: '#FFFFFF', - } - - const theme = parseTheme(untypedTheme) - - expect(theme.flagType?.hex()).to.equal('#FFFFFF') - }) - - it('should parse sectionDescription', () => { - const untypedTheme = { - sectionDescription: '#FFFFFF', - } - - const theme = parseTheme(untypedTheme) - - expect(theme.sectionDescription?.hex()).to.equal('#FFFFFF') - }) - - it('should parse sectionHeader', () => { - const untypedTheme = { - sectionHeader: '#FFFFFF', - } - - const theme = parseTheme(untypedTheme) - - expect(theme.sectionHeader?.hex()).to.equal('#FFFFFF') - }) - - it('should parse topic', () => { - const untypedTheme = { - topic: '#FFFFFF', - } - - const theme = parseTheme(untypedTheme) - - expect(theme.topic?.hex()).to.equal('#FFFFFF') - }) - - it('should parse version', () => { - const untypedTheme = { - version: '#FFFFFF', - } - - const theme = parseTheme(untypedTheme) - - expect(theme.version?.hex()).to.equal('#FFFFFF') - }) - - it('should not parse color key that is not part of Theme', () => { - const untypedTheme = { - batman: '#000000', - } - - const theme = parseTheme(untypedTheme) - - expect(Object.keys(theme).includes('batman')).to.be.false - }) -}) From 8cd05e4725aa5a3090434dc180e51d3977293294 Mon Sep 17 00:00:00 2001 From: Allan Oricil <55927613+AllanOricil@users.noreply.github.com> Date: Mon, 13 Nov 2023 12:41:10 -0300 Subject: [PATCH 04/16] fix: change http to https in order to pass lockfile-check workflow (#869) --- yarn.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/yarn.lock b/yarn.lock index 2845fdfb9..bdaec1aad 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1014,7 +1014,7 @@ "@types/color-convert@*": version "2.0.2" - resolved "http://registry.yarnpkg.com/@types/color-convert/-/color-convert-2.0.2.tgz#a5fa5da9b866732f8bf86b01964869011e2a2356" + resolved "https://registry.yarnpkg.com/@types/color-convert/-/color-convert-2.0.2.tgz#a5fa5da9b866732f8bf86b01964869011e2a2356" integrity sha512-KGRIgCxwcgazts4MXRCikPbIMzBpjfdgEZSy8TRHU/gtg+f9sOfHdtK8unPfxIoBtyd2aTTwINVLSNENlC8U8A== dependencies: "@types/color-name" "*" @@ -1026,7 +1026,7 @@ "@types/color@^3.0.5": version "3.0.5" - resolved "http://registry.yarnpkg.com/@types/color/-/color-3.0.5.tgz#658fd9286a44c21dabaa56c2e2f63da3ac15f063" + resolved "https://registry.yarnpkg.com/@types/color/-/color-3.0.5.tgz#658fd9286a44c21dabaa56c2e2f63da3ac15f063" integrity sha512-T9yHCNtd8ap9L/r8KEESu5RDMLkoWXHo7dTureNoI1dbp25NsCN054vOu09iniIjR21MXUL+LU9bkIWrbyg8gg== dependencies: "@types/color-convert" "*" @@ -2112,7 +2112,7 @@ color-name@^1.0.0, color-name@^1.1.4, color-name@~1.1.4: color-string@^1.9.0: version "1.9.1" - resolved "http://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== dependencies: color-name "^1.0.0" @@ -2125,7 +2125,7 @@ color-support@^1.1.3: color@^4.2.3: version "4.2.3" - resolved "http://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a" + resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a" integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A== dependencies: color-convert "^2.0.1" @@ -3876,7 +3876,7 @@ is-arrayish@^0.2.1: is-arrayish@^0.3.1: version "0.3.2" - resolved "http://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== is-bigint@^1.0.1: @@ -6319,7 +6319,7 @@ sigstore@^1.3.0, sigstore@^1.4.0, sigstore@^1.7.0: simple-swizzle@^0.2.2: version "0.2.2" - resolved "http://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== dependencies: is-arrayish "^0.3.1" From 295dd30777301bc93108440c4eef34cbf0bf2c84 Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Mon, 13 Nov 2023 09:48:05 -0700 Subject: [PATCH 05/16] fix: streamline coloring --- src/help/command.ts | 6 +++--- src/help/formatter.ts | 8 ++++++-- src/help/index.ts | 7 +++---- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/help/command.ts b/src/help/command.ts index 9ae0b9e9e..5f651e42c 100644 --- a/src/help/command.ts +++ b/src/help/command.ts @@ -89,7 +89,7 @@ export class CommandHelp extends HelpFormatter { } if (description) { - return this.wrap(colorize(this.config?.theme?.commandSummary, description.join('\n'))) + return this.wrap(description.join('\n')) } } @@ -132,7 +132,7 @@ export class CommandHelp extends HelpFormatter { return `${this.wrap(description, finalIndentedSpacing)}\n\n${multilineCommands}` }) .join('\n\n') - return colorize(this.config?.theme?.sectionDescription, body) + return body } protected flagHelpLabel(flag: Command.Flag.Any, showOptions = false): string { @@ -204,7 +204,7 @@ export class CommandHelp extends HelpFormatter { }) .join('\n\n') - return colorize(this.config?.theme?.sectionDescription, body) + return body } generate(): string { diff --git a/src/help/formatter.ts b/src/help/formatter.ts index aa8d9a3e9..954740359 100644 --- a/src/help/formatter.ts +++ b/src/help/formatter.ts @@ -178,10 +178,14 @@ export class HelpFormatter { const output = [ colorize(this.config?.theme?.sectionHeader, chalk.bold(header)), - this.indent( - Array.isArray(newBody) ? this.renderList(newBody, {indentation: 2, stripAnsi: this.opts.stripAnsi}) : newBody, + colorize( + this.config?.theme?.sectionDescription, + this.indent( + Array.isArray(newBody) ? this.renderList(newBody, {indentation: 2, stripAnsi: this.opts.stripAnsi}) : newBody, + ), ), ].join('\n') + return this.opts.stripAnsi ? stripAnsi(output) : output } diff --git a/src/help/index.ts b/src/help/index.ts index 301a61ac1..059fdeb0c 100644 --- a/src/help/index.ts +++ b/src/help/index.ts @@ -81,10 +81,10 @@ export class Help extends HelpBase { protected description(c: Command.Loadable): string { const description = this.render(c.description || '') if (c.summary) { - return colorize(this.config?.theme?.sectionDescription, description) + return description } - return colorize(this.config?.theme?.sectionDescription, description.split('\n').slice(1).join('\n')) + return description.split('\n').slice(1).join('\n') } protected formatCommand(command: Command.Loadable): string { @@ -107,7 +107,7 @@ export class Help extends HelpBase { const summary = this.summary(c) return [ colorize(this.config?.theme?.command, c.id), - summary && colorize(this.config?.theme?.sectionDescription, summary), + summary && colorize(this.config?.theme?.sectionDescription, stripAnsi(summary)), ] }), { @@ -350,7 +350,6 @@ export class Help extends HelpBase { protected summary(c: Command.Loadable): string | undefined { if (c.summary) return colorize(this.config?.theme?.commandSummary, this.render(c.summary.split('\n')[0])) - return c.description && colorize(this.config?.theme?.commandSummary, this.render(c.description).split('\n')[0]) } From cd746a65e0915c906864fd76e0aa71d8f7f9584b Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Mon, 13 Nov 2023 11:42:26 -0700 Subject: [PATCH 06/16] fix: remove standardized id from usage string --- src/command.ts | 3 ++- src/help/command.ts | 8 ++++++-- src/help/index.ts | 3 ++- src/help/util.ts | 10 +--------- src/index.ts | 5 ++--- src/util/ids.ts | 10 ++++++++++ test/util/theme.test.ts | 2 -- 7 files changed, 23 insertions(+), 18 deletions(-) create mode 100644 src/util/ids.ts diff --git a/src/command.ts b/src/command.ts index b93afb8b0..7d6c86a65 100644 --- a/src/command.ts +++ b/src/command.ts @@ -6,7 +6,7 @@ import {ux} from './cli-ux' import {Config} from './config' import * as Errors from './errors' import {PrettyPrintableError} from './errors' -import {formatCommandDeprecationWarning, formatFlagDeprecationWarning, normalizeArgv, toConfiguredId} from './help/util' +import {formatCommandDeprecationWarning, formatFlagDeprecationWarning, normalizeArgv} from './help/util' import {PJSON} from './interfaces' import {LoadOptions} from './interfaces/config' import {CommandError} from './interfaces/errors' @@ -28,6 +28,7 @@ import {Plugin} from './interfaces/plugin' import * as Parser from './parser' import {aggregateFlags} from './util/aggregate-flags' import {requireJson} from './util/fs' +import {toConfiguredId} from './util/ids' import {uniq} from './util/util' const pjson = requireJson(__dirname, '..', 'package.json') diff --git a/src/help/command.ts b/src/help/command.ts index 5f651e42c..2693a033c 100644 --- a/src/help/command.ts +++ b/src/help/command.ts @@ -4,6 +4,7 @@ import stripAnsi from 'strip-ansi' import {Command} from '../command' import * as Interfaces from '../interfaces' import {ensureArgObject} from '../util/ensure-arg-object' +import {toStandardizedId} from '../util/ids' import {colorize} from '../util/theme' import {castArray, compact, sortBy} from '../util/util' import {DocOpts} from './docopts' @@ -308,17 +309,20 @@ export class CommandHelp extends HelpFormatter { } protected usage(): string { - const {usage} = this.command + const {id, usage} = this.command + const standardId = toStandardizedId(id, this.config) const body = (usage ? castArray(usage) : [this.defaultUsage()]) .map((u) => { const allowedSpacing = this.opts.maxWidth - this.indentSpacing const dollarSign = colorize(this.config?.theme?.dollarSign, '$') const bin = colorize(this.config?.theme?.bin, this.config.bin) + const command = colorize(this.config?.theme?.command, '<%= command.id %>') + const commandDescription = colorize( this.config?.theme?.sectionDescription, - u.replace('<%= command.id %>', '').trim(), + u.replace('<%= command.id %>', '').replace(standardId, '').trim(), ) const line = `${dollarSign} ${bin} ${command} ${commandDescription}`.trim() diff --git a/src/help/index.ts b/src/help/index.ts index 059fdeb0c..fa9221278 100644 --- a/src/help/index.ts +++ b/src/help/index.ts @@ -7,12 +7,13 @@ import {error} from '../errors' import * as Interfaces from '../interfaces' import {load} from '../module-loader' import {cacheDefaultValue} from '../util/cache-default-value' +import {toConfiguredId} from '../util/ids' import {colorize} from '../util/theme' import {compact, sortBy, uniqBy} from '../util/util' import {CommandHelp} from './command' import {HelpFormatter} from './formatter' import RootHelp from './root' -import {formatCommandDeprecationWarning, getHelpFlagAdditions, standardizeIDFromArgv, toConfiguredId} from './util' +import {formatCommandDeprecationWarning, getHelpFlagAdditions, standardizeIDFromArgv} from './util' export {CommandHelp} from './command' export {getHelpFlagAdditions, normalizeArgv, standardizeIDFromArgv} from './util' diff --git a/src/help/util.ts b/src/help/util.ts index 82db784f5..7a77a46a8 100644 --- a/src/help/util.ts +++ b/src/help/util.ts @@ -2,6 +2,7 @@ import * as ejs from 'ejs' import {collectUsableIds} from '../config/util' import {Deprecation, Config as IConfig} from '../interfaces' +import {toStandardizedId} from '../util/ids' export function template(context: any): (t: string) => string { function render(t: string): string { @@ -52,15 +53,6 @@ function collateSpacedCmdIDFromArgs(argv: string[], config: IConfig): string[] { return argv // ID is argv[0] } -export function toStandardizedId(commandID: string, config: IConfig): string { - return commandID.replaceAll(new RegExp(config.topicSeparator, 'g'), ':') -} - -export function toConfiguredId(commandID: string, config: IConfig): string { - const defaultTopicSeparator = ':' - return commandID.replaceAll(new RegExp(defaultTopicSeparator, 'g'), config.topicSeparator || defaultTopicSeparator) -} - export function standardizeIDFromArgv(argv: string[], config: IConfig): string[] { if (argv.length === 0) return argv if (config.topicSeparator === ' ') argv = collateSpacedCmdIDFromArgs(argv, config) diff --git a/src/index.ts b/src/index.ts index 03787eb00..98b3bd36b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,8 +15,7 @@ checkCWD() export * as Args from './args' export * as ux from './cli-ux' export {flush} from './cli-ux/flush' -// Remove these in the next major version -export {stderr, stdout} from './cli-ux/stream' +export {stderr, stdout} from './cli-ux/stream' // Remove these in the next major version export {Command} from './command' export {Config, Plugin} from './config' export * as Errors from './errors' @@ -25,7 +24,6 @@ export {execute} from './execute' export * as Flags from './flags' export {CommandHelp, Help, HelpBase, loadHelpClass} from './help' export {HelpSection, HelpSectionKeyValueTable, HelpSectionRenderer} from './help/formatter' -export {toConfiguredId, toStandardizedId} from './help/util' export * as Interfaces from './interfaces' export {Hook} from './interfaces/hooks' export {run} from './main' @@ -33,3 +31,4 @@ export * as ModuleLoader from './module-loader' export * as Parser from './parser' export {Performance} from './performance' export {Settings, settings} from './settings' +export {toConfiguredId, toStandardizedId} from './util/ids' diff --git a/src/util/ids.ts b/src/util/ids.ts new file mode 100644 index 000000000..9af35c1d1 --- /dev/null +++ b/src/util/ids.ts @@ -0,0 +1,10 @@ +import {Config} from '../interfaces' + +export function toStandardizedId(commandID: string, config: Config): string { + return commandID.replaceAll(new RegExp(config.topicSeparator, 'g'), ':') +} + +export function toConfiguredId(commandID: string, config: Config): string { + const defaultTopicSeparator = ':' + return commandID.replaceAll(new RegExp(defaultTopicSeparator, 'g'), config.topicSeparator || defaultTopicSeparator) +} diff --git a/test/util/theme.test.ts b/test/util/theme.test.ts index af73d84f2..3d8ad2923 100644 --- a/test/util/theme.test.ts +++ b/test/util/theme.test.ts @@ -46,7 +46,6 @@ describe('theme parsing', () => { flagOptions: '#FFFFFF', flagRequired: '#FFFFFF', flagSeparator: '#FFFFFF', - flagType: '#FFFFFF', sectionDescription: '#FFFFFF', sectionHeader: '#FFFFFF', topic: '#FFFFFF', @@ -75,7 +74,6 @@ describe('theme parsing', () => { flagOptions: 'cyan', flagRequired: 'cyan', flagSeparator: 'cyan', - flagType: 'cyan', sectionDescription: 'cyan', sectionHeader: 'cyan', topic: 'cyan', From 0db78d388b9cf035e92143854284f5bc83b7377b Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Mon, 13 Nov 2023 12:48:18 -0700 Subject: [PATCH 07/16] test: provide id to mocked command class --- test/help/format-command-with-options.test.ts | 6 ++++-- test/help/format-command.test.ts | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/test/help/format-command-with-options.test.ts b/test/help/format-command-with-options.test.ts index 24c45718a..fdddee372 100644 --- a/test/help/format-command-with-options.test.ts +++ b/test/help/format-command-with-options.test.ts @@ -359,11 +359,12 @@ ARGUMENTS const cmd = await makeLoadable( makeCommandClass({ examples: ['it handles a list of examples', 'more example text'], + id: 'command', }), ) const output = help.formatCommand(cmd) expect(output).to.equal(`USAGE - $ oclif + $ oclif command EXAMPLES it handles a list of examples @@ -375,12 +376,13 @@ EXAMPLES const cmd = await makeLoadable( makeCommandClass({ examples: ['it handles a single example'], + id: 'command', }), ) const output = help.formatCommand(cmd) expect(output).to.equal(`USAGE - $ oclif + $ oclif command EXAMPLES it handles a single example`) diff --git a/test/help/format-command.test.ts b/test/help/format-command.test.ts index 7365db709..de609e29c 100644 --- a/test/help/format-command.test.ts +++ b/test/help/format-command.test.ts @@ -565,12 +565,13 @@ ARGUMENTS const cmd = await makeLoadable( makeCommandClass({ examples: ['it handles a list of examples', 'more example text'], + id: 'command', }), ) const output = help.formatCommand(cmd) expect(output).to.equal(`USAGE - $ oclif + $ oclif command EXAMPLES it handles a list of examples @@ -582,12 +583,13 @@ EXAMPLES const cmd = await makeLoadable( makeCommandClass({ examples: ['it handles a single example'], + id: 'command', }), ) const output = help.formatCommand(cmd) expect(output).to.equal(`USAGE - $ oclif + $ oclif command EXAMPLES it handles a single example`) From 1a4c32bbb318ab891fa8d39ae28bde4bce3af34e Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Tue, 14 Nov 2023 10:47:24 -0700 Subject: [PATCH 08/16] chore: code review and clean up --- src/cli-ux/index.ts | 2 +- src/cli-ux/theme.ts | 31 +++++ src/config/config.ts | 2 +- src/help/command.ts | 2 +- src/help/formatter.ts | 2 +- src/help/index.ts | 2 +- src/help/root.ts | 2 +- src/interfaces/theme.ts | 39 ++++++- src/util/theme.ts | 170 ---------------------------- test/{util => cli-ux}/theme.test.ts | 62 ++++++++-- 10 files changed, 129 insertions(+), 185 deletions(-) create mode 100644 src/cli-ux/theme.ts delete mode 100644 src/util/theme.ts rename test/{util => cli-ux}/theme.test.ts (59%) diff --git a/src/cli-ux/index.ts b/src/cli-ux/index.ts index 40c2b647e..ab626f9ce 100644 --- a/src/cli-ux/index.ts +++ b/src/cli-ux/index.ts @@ -9,7 +9,6 @@ import * as uxPrompt from './prompt' import * as styled from './styled' import uxWait from './wait' import write from './write' - const hyperlinker = require('hyperlinker') export class ux { @@ -195,4 +194,5 @@ export {ExitError} from './exit' export {IPromptOptions} from './prompt' export {Table} from './styled' +export {colorize} from './theme' export {default as write} from './write' diff --git a/src/cli-ux/theme.ts b/src/cli-ux/theme.ts new file mode 100644 index 000000000..443f9afd9 --- /dev/null +++ b/src/cli-ux/theme.ts @@ -0,0 +1,31 @@ +import chalk from 'chalk' +import * as Color from 'color' + +import {STANDARD_CHALK, StandardChalk, Theme} from '../interfaces/theme' + +function isStandardChalk(color: any): color is StandardChalk { + return STANDARD_CHALK.includes(color) +} + +export function colorize(color: Color | StandardChalk | undefined, text: string): string { + if (isStandardChalk(color)) return chalk[color](text) + + return color ? chalk.hex(color.hex())(text) : text +} + +export function parseTheme(untypedTheme: Record): Theme { + return Object.fromEntries( + Object.entries(untypedTheme) + .map(([key, value]) => [key, getColor(value)]) + .filter(([_, value]) => value), + ) +} + +export function getColor(color: string): Color +export function getColor(color: StandardChalk): StandardChalk +export function getColor(color: string | StandardChalk): Color | StandardChalk | undefined { + try { + // eslint-disable-next-line new-cap + return isStandardChalk(color) ? color : new Color.default(color) + } catch {} +} diff --git a/src/config/config.ts b/src/config/config.ts index dd426ebc1..92b23ab3e 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -5,6 +5,7 @@ import {join, resolve, sep} from 'node:path' import {URL, fileURLToPath} from 'node:url' import {ux} from '../cli-ux' +import {parseTheme} from '../cli-ux/theme' import {Command} from '../command' import {CLIError, error, exit, warn} from '../errors' import {getHelpFlagAdditions} from '../help/util' @@ -17,7 +18,6 @@ import {OCLIF_MARKER_OWNER, Performance} from '../performance' import {settings} from '../settings' import {requireJson, safeReadJson} from '../util/fs' import {getHomeDir, getPlatform} from '../util/os' -import {parseTheme} from '../util/theme' import {compact, isProd} from '../util/util' import Cache from './cache' import PluginLoader from './plugin-loader' diff --git a/src/help/command.ts b/src/help/command.ts index 8b9add9f3..4e2c5f1f6 100644 --- a/src/help/command.ts +++ b/src/help/command.ts @@ -1,11 +1,11 @@ import chalk from 'chalk' import stripAnsi from 'strip-ansi' +import {colorize} from '../cli-ux/theme' import {Command} from '../command' import * as Interfaces from '../interfaces' import {ensureArgObject} from '../util/ensure-arg-object' import {toStandardizedId} from '../util/ids' -import {colorize} from '../util/theme' import {castArray, compact, sortBy} from '../util/util' import {DocOpts} from './docopts' import {HelpFormatter, HelpSection, HelpSectionRenderer} from './formatter' diff --git a/src/help/formatter.ts b/src/help/formatter.ts index 954740359..94f6926c6 100644 --- a/src/help/formatter.ts +++ b/src/help/formatter.ts @@ -5,10 +5,10 @@ import stripAnsi from 'strip-ansi' import widestLine from 'widest-line' import wrap from 'wrap-ansi' +import {colorize} from '../cli-ux/theme' import {Command} from '../command' import * as Interfaces from '../interfaces' import {stdtermwidth} from '../screen' -import {colorize} from '../util/theme' import {template} from './util' export type HelpSectionKeyValueTable = {description: string; name: string}[] diff --git a/src/help/index.ts b/src/help/index.ts index fa9221278..e4c66167d 100644 --- a/src/help/index.ts +++ b/src/help/index.ts @@ -1,6 +1,7 @@ import {format} from 'node:util' import stripAnsi from 'strip-ansi' +import {colorize} from '../cli-ux/theme' import write from '../cli-ux/write' import {Command} from '../command' import {error} from '../errors' @@ -8,7 +9,6 @@ import * as Interfaces from '../interfaces' import {load} from '../module-loader' import {cacheDefaultValue} from '../util/cache-default-value' import {toConfiguredId} from '../util/ids' -import {colorize} from '../util/theme' import {compact, sortBy, uniqBy} from '../util/util' import {CommandHelp} from './command' import {HelpFormatter} from './formatter' diff --git a/src/help/root.ts b/src/help/root.ts index 87b03ea3a..37ef88ac2 100644 --- a/src/help/root.ts +++ b/src/help/root.ts @@ -1,7 +1,7 @@ import stripAnsi from 'strip-ansi' +import {colorize} from '../cli-ux/theme' import * as Interfaces from '../interfaces' -import {colorize} from '../util/theme' import {compact} from '../util/util' import {HelpFormatter} from './formatter' diff --git a/src/interfaces/theme.ts b/src/interfaces/theme.ts index 652f05373..b522796b9 100644 --- a/src/interfaces/theme.ts +++ b/src/interfaces/theme.ts @@ -40,4 +40,41 @@ export const STANDARD_CHALK = [ export type StandardChalk = (typeof STANDARD_CHALK)[number] -export type Theme = Record +export const THEME_KEYS = [ + 'alias', + 'bin', + 'command', + 'commandSummary', + 'dollarSign', + 'flag', + 'flagDefaultValue', + 'flagOptions', + 'flagRequired', + 'flagSeparator', + 'flagType', + 'sectionDescription', + 'sectionHeader', + 'topic', + 'version', +] as const + +export type ThemeKey = (typeof THEME_KEYS)[number] + +export type Theme = { + [key: string | ThemeKey]: Color | StandardChalk | undefined + alias?: Color | StandardChalk + bin?: Color | StandardChalk + command?: Color | StandardChalk + commandSummary?: Color | StandardChalk + dollarSign?: Color | StandardChalk + flag?: Color | StandardChalk + flagDefaultValue?: Color | StandardChalk + flagOptions?: Color | StandardChalk + flagRequired?: Color | StandardChalk + flagSeparator?: Color | StandardChalk + flagType?: Color | StandardChalk + sectionDescription?: Color | StandardChalk + sectionHeader?: Color | StandardChalk + topic?: Color | StandardChalk + version?: Color | StandardChalk +} diff --git a/src/util/theme.ts b/src/util/theme.ts deleted file mode 100644 index 219d36591..000000000 --- a/src/util/theme.ts +++ /dev/null @@ -1,170 +0,0 @@ -import chalk from 'chalk' -import * as Color from 'color' - -import {STANDARD_CHALK, StandardChalk, Theme} from '../interfaces/theme' - -// eslint-disable-next-line complexity -function standardChalk(color: StandardChalk, text: string): string { - switch (color) { - case 'red': { - return chalk.red(text) - } - - case 'blue': { - return chalk.blue(text) - } - - case 'yellow': { - return chalk.yellow(text) - } - - case 'green': { - return chalk.green(text) - } - - case 'cyan': { - return chalk.cyan(text) - } - - case 'magenta': { - return chalk.magenta(text) - } - - case 'white': { - return chalk.white(text) - } - - case 'black': { - return chalk.black(text) - } - - case 'gray': { - return chalk.gray(text) - } - - case 'blackBright': { - return chalk.blackBright(text) - } - - case 'redBright': { - return chalk.redBright(text) - } - - case 'greenBright': { - return chalk.greenBright(text) - } - - case 'yellowBright': { - return chalk.yellowBright(text) - } - - case 'blueBright': { - return chalk.blueBright(text) - } - - case 'magentaBright': { - return chalk.magentaBright(text) - } - - case 'cyanBright': { - return chalk.cyanBright(text) - } - - case 'whiteBright': { - return chalk.whiteBright(text) - } - - case 'bgBlack': { - return chalk.bgBlack(text) - } - - case 'bgRed': { - return chalk.bgRed(text) - } - - case 'bgGreen': { - return chalk.bgGreen(text) - } - - case 'bgYellow': { - return chalk.bgYellow(text) - } - - case 'bgBlue': { - return chalk.bgBlue(text) - } - - case 'bgMagenta': { - return chalk.bgMagenta(text) - } - - case 'bgCyan': { - return chalk.bgCyan(text) - } - - case 'bgWhite': { - return chalk.bgWhite(text) - } - - case 'bgGray': { - return chalk.bgGray(text) - } - - case 'bgBlackBright': { - return chalk.bgBlackBright(text) - } - - case 'bgRedBright': { - return chalk.bgRedBright(text) - } - - case 'bgGreenBright': { - return chalk.bgGreenBright(text) - } - - case 'bgYellowBright': { - return chalk.bgYellowBright(text) - } - - case 'bgBlueBright': { - return chalk.bgBlueBright(text) - } - - case 'bgMagentaBright': { - return chalk.bgMagentaBright(text) - } - - case 'bgCyanBright': { - return chalk.bgCyanBright(text) - } - - case 'bgWhiteBright': { - return chalk.bgWhiteBright(text) - } - - default: { - return chalk.gray(text) - } - } -} - -function isStandardChalk(color: any): color is StandardChalk { - return STANDARD_CHALK.includes(color) -} - -export function colorize(color: Color | StandardChalk | undefined, text: string): string { - if (isStandardChalk(color)) return standardChalk(color, text) - - return color ? chalk.hex(color.hex())(text) : text -} - -export function parseTheme(untypedTheme: Record): Theme { - return Object.fromEntries(Object.entries(untypedTheme).map(([key, value]) => [key, getColor(value)])) -} - -export function getColor(color: string): Color -export function getColor(color: StandardChalk): StandardChalk -export function getColor(color: string | StandardChalk): Color | StandardChalk { - // eslint-disable-next-line new-cap - return isStandardChalk(color) ? color : new Color.default(color) -} diff --git a/test/util/theme.test.ts b/test/cli-ux/theme.test.ts similarity index 59% rename from test/util/theme.test.ts rename to test/cli-ux/theme.test.ts index 3d8ad2923..c81f780f5 100644 --- a/test/util/theme.test.ts +++ b/test/cli-ux/theme.test.ts @@ -3,7 +3,8 @@ import chalk from 'chalk' config.truncateThreshold = 0 -import {colorize, getColor, parseTheme} from '../../src/util/theme' +import {colorize, getColor, parseTheme} from '../../src/cli-ux/theme' +import {THEME_KEYS} from '../../src/interfaces/theme' describe('colorize', () => { it('should return text with ansi characters when given hex code', () => { const color = getColor('#FF0000') @@ -54,11 +55,26 @@ describe('theme parsing', () => { const theme = parseTheme(untypedTheme) - for (const key of Object.keys(theme)) { - expect(typeof theme[key]).to.equal('object') - expect(theme[key]).to.have.property('model') - expect(theme[key]).to.have.property('color') - expect(theme[key]).to.have.property('valpha') + for (const value of Object.values(theme)) { + expect(typeof value).to.equal('object') + expect(value).to.have.property('model') + expect(value).to.have.property('color') + expect(value).to.have.property('valpha') + } + }) + + it('should parse untyped theme json to theme using rgb', () => { + const untypedTheme = { + alias: 'rgb(255, 255, 255)', + } + + const theme = parseTheme(untypedTheme) + + for (const value of Object.values(theme)) { + expect(typeof value).to.equal('object') + expect(value).to.have.property('model') + expect(value).to.have.property('color') + expect(value).to.have.property('valpha') } }) @@ -81,9 +97,39 @@ describe('theme parsing', () => { } const theme = parseTheme(untypedTheme) + for (const value of Object.values(theme)) { + expect(value).to.equal('cyan') + } + }) - for (const key of Object.keys(theme)) { - expect(theme[key]).to.equal('cyan') + it('should ignore unsupported values', () => { + const untypedTheme = { + alias: 'FOO', } + + const theme = parseTheme(untypedTheme) + expect(theme).to.deep.equal({}) + }) +}) + +describe('THEME_KEYS', () => { + it('should always have native theme keys', () => { + expect(THEME_KEYS).deep.equal([ + 'alias', + 'bin', + 'command', + 'commandSummary', + 'dollarSign', + 'flag', + 'flagDefaultValue', + 'flagOptions', + 'flagRequired', + 'flagSeparator', + 'flagType', + 'sectionDescription', + 'sectionHeader', + 'topic', + 'version', + ]) }) }) From 0194eaa8bdf77f3f88cc35c36995330791b51414 Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Tue, 14 Nov 2023 13:53:45 -0700 Subject: [PATCH 09/16] fix: support more chalk standards --- src/interfaces/theme.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/interfaces/theme.ts b/src/interfaces/theme.ts index b522796b9..067cd0eeb 100644 --- a/src/interfaces/theme.ts +++ b/src/interfaces/theme.ts @@ -36,6 +36,11 @@ export const STANDARD_CHALK = [ 'bgMagentaBright', 'bgCyanBright', 'bgWhiteBright', + 'bold', + 'underline', + 'dim', + 'italic', + 'strikethrough', ] as const export type StandardChalk = (typeof STANDARD_CHALK)[number] From 31604f8b24f11a77b84577089b360c5ba7105222 Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Thu, 16 Nov 2023 12:48:47 -0700 Subject: [PATCH 10/16] fix: prepare for plugin-theme --- src/cli-ux/theme.ts | 8 +++++--- src/config/config.ts | 24 ++++++++++++++++++++---- src/interfaces/index.ts | 1 + src/interfaces/pjson.ts | 1 + src/interfaces/theme.ts | 5 +++++ test/cli-ux/theme.test.ts | 20 ++++++++++++++++---- test/config/config.test.ts | 2 +- 7 files changed, 49 insertions(+), 12 deletions(-) diff --git a/src/cli-ux/theme.ts b/src/cli-ux/theme.ts index 443f9afd9..d82e4dcb3 100644 --- a/src/cli-ux/theme.ts +++ b/src/cli-ux/theme.ts @@ -1,7 +1,7 @@ import chalk from 'chalk' import * as Color from 'color' -import {STANDARD_CHALK, StandardChalk, Theme} from '../interfaces/theme' +import {STANDARD_CHALK, StandardChalk, Theme, Themes} from '../interfaces/theme' function isStandardChalk(color: any): color is StandardChalk { return STANDARD_CHALK.includes(color) @@ -13,9 +13,11 @@ export function colorize(color: Color | StandardChalk | undefined, text: string) return color ? chalk.hex(color.hex())(text) : text } -export function parseTheme(untypedTheme: Record): Theme { +export function parseTheme(theme: Themes): Theme { + const themes = theme.themes ?? {} + const selected = theme.selected ? themes[theme.selected] ?? {} : {} return Object.fromEntries( - Object.entries(untypedTheme) + Object.entries(selected) .map(([key, value]) => [key, getColor(value)]) .filter(([_, value]) => value), ) diff --git a/src/config/config.ts b/src/config/config.ts index 92b23ab3e..a6545e0fa 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -12,7 +12,7 @@ import {getHelpFlagAdditions} from '../help/util' import {Hook, Hooks, PJSON, Topic} from '../interfaces' import {ArchTypes, Config as IConfig, LoadOptions, PlatformTypes, VersionDetails} from '../interfaces/config' import {Plugin as IPlugin, Options} from '../interfaces/plugin' -import {Theme} from '../interfaces/theme' +import {Theme, Themes} from '../interfaces/theme' import {loadWithData} from '../module-loader' import {OCLIF_MARKER_OWNER, Performance} from '../performance' import {settings} from '../settings' @@ -330,9 +330,8 @@ export class Config implements IConfig { this.npmRegistry = this.scopedEnvVar('NPM_REGISTRY') || this.pjson.oclif.npmRegistry if (!this.scopedEnvVarTrue('DISABLE_THEME')) { - const themeFilePath = resolve(this.configDir, 'theme.json') - const theme = await safeReadJson>(themeFilePath) - this.theme = theme ? parseTheme(theme) : undefined + const {activeTheme} = await this.loadThemes() + this.theme = activeTheme } this.pjson.oclif.update = this.pjson.oclif.update || {} @@ -400,6 +399,23 @@ export class Config implements IConfig { } } + public async loadThemes(): Promise<{ + file: string + activeTheme: Theme | undefined + themes: Themes | undefined + }> { + const themesFile = this.pjson.oclif.themesFile + ? resolve(this.root, this.pjson.oclif.themesFile) + : resolve(this.configDir, 'themes.json') + const themes = await safeReadJson(themesFile) + const activeTheme = themes ? parseTheme(themes) : undefined + return { + activeTheme, + file: themesFile, + themes, + } + } + protected macosCacheDir(): string | undefined { return (this.platform === 'darwin' && join(this.home, 'Library', 'Caches', this.dirname)) || undefined } diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index ea6fa3f34..42986ff80 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -10,5 +10,6 @@ export type {Arg, BooleanFlag, CustomOptions, Deprecation, Flag, FlagDefinition, export type {PJSON} from './pjson' export type {Options, Plugin, PluginOptions} from './plugin' export type {S3Manifest} from './s3-manifest' +export type {Theme, Themes} from './theme' export type {Topic} from './topic' export type {TSConfig} from './ts-config' diff --git a/src/interfaces/pjson.ts b/src/interfaces/pjson.ts index 3f82f5fd4..bb09efdb7 100644 --- a/src/interfaces/pjson.ts +++ b/src/interfaces/pjson.ts @@ -43,6 +43,7 @@ export namespace PJSON { repositoryPrefix?: string schema?: number state?: 'beta' | 'deprecated' | string + themesFile?: string topicSeparator?: ' ' | ':' topics?: { [k: string]: { diff --git a/src/interfaces/theme.ts b/src/interfaces/theme.ts index 067cd0eeb..817474ab1 100644 --- a/src/interfaces/theme.ts +++ b/src/interfaces/theme.ts @@ -83,3 +83,8 @@ export type Theme = { topic?: Color | StandardChalk version?: Color | StandardChalk } + +export type Themes = { + selected?: string + themes?: Record> +} diff --git a/test/cli-ux/theme.test.ts b/test/cli-ux/theme.test.ts index c81f780f5..2ffffdb0e 100644 --- a/test/cli-ux/theme.test.ts +++ b/test/cli-ux/theme.test.ts @@ -53,7 +53,10 @@ describe('theme parsing', () => { version: '#FFFFFF', } - const theme = parseTheme(untypedTheme) + const theme = parseTheme({ + selected: 'test', + themes: {test: untypedTheme}, + }) for (const value of Object.values(theme)) { expect(typeof value).to.equal('object') @@ -68,7 +71,10 @@ describe('theme parsing', () => { alias: 'rgb(255, 255, 255)', } - const theme = parseTheme(untypedTheme) + const theme = parseTheme({ + selected: 'test', + themes: {test: untypedTheme}, + }) for (const value of Object.values(theme)) { expect(typeof value).to.equal('object') @@ -96,7 +102,10 @@ describe('theme parsing', () => { version: 'cyan', } - const theme = parseTheme(untypedTheme) + const theme = parseTheme({ + selected: 'test', + themes: {test: untypedTheme}, + }) for (const value of Object.values(theme)) { expect(value).to.equal('cyan') } @@ -107,7 +116,10 @@ describe('theme parsing', () => { alias: 'FOO', } - const theme = parseTheme(untypedTheme) + const theme = parseTheme({ + selected: 'test', + themes: {test: untypedTheme}, + }) expect(theme).to.deep.equal({}) }) }) diff --git a/test/config/config.test.ts b/test/config/config.test.ts index 01920ba3a..c5e8c7d17 100644 --- a/test/config/config.test.ts +++ b/test/config/config.test.ts @@ -52,7 +52,7 @@ describe('Config', () => { .stub(os, 'getHomeDir', (stub) => stub.returns(join(homedir))) .stub(os, 'getPlatform', (stub) => stub.returns(platform)) - if (theme) test = test.stub(fs, 'safeReadJson', (stub) => stub.resolves(theme)) + if (theme) test = test.stub(fs, 'safeReadJson', (stub) => stub.resolves({selected: 'test', themes: {test: theme}})) if (pjson) test = test.stub(fs, 'readJson', (stub) => stub.resolves(pjson)) test = test.add('config', () => Config.load()) From 80cbda21c88cd9e9be8af0e8b9bad9c99bd430c4 Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Thu, 16 Nov 2023 12:50:51 -0700 Subject: [PATCH 11/16] chore: add jsdoc to ux.colorize --- src/cli-ux/theme.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/cli-ux/theme.ts b/src/cli-ux/theme.ts index d82e4dcb3..8a50aa9ef 100644 --- a/src/cli-ux/theme.ts +++ b/src/cli-ux/theme.ts @@ -7,6 +7,12 @@ function isStandardChalk(color: any): color is StandardChalk { return STANDARD_CHALK.includes(color) } +/** + * Add color to text. + * @param color color to use. Can be hex code (e.g. `#ff0000`), rgb (e.g. `rgb(255, 255, 255)`) or a chalk color (e.g. `red`) + * @param text string to colorize + * @returns colorized string + */ export function colorize(color: Color | StandardChalk | undefined, text: string): string { if (isStandardChalk(color)) return chalk[color](text) From 10fccf8607186eabcd576497466ad73e3bb1c05e Mon Sep 17 00:00:00 2001 From: svc-cli-bot Date: Thu, 16 Nov 2023 19:51:36 +0000 Subject: [PATCH 12/16] chore(release): 3.11.1-dev.0 [skip ci] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 29bdec116..33644030d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@oclif/core", "description": "base library for oclif CLIs", - "version": "3.11.0", + "version": "3.11.1-dev.0", "author": "Salesforce", "bugs": "https://github.com/oclif/core/issues", "dependencies": { From 171c983bcd8a64203922b2ba567c72bf50b9f2df Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Thu, 16 Nov 2023 13:05:04 -0700 Subject: [PATCH 13/16] fix: always convert to hex --- src/cli-ux/theme.ts | 10 +++++----- src/interfaces/theme.ts | 34 ++++++++++++++++------------------ test/cli-ux/theme.test.ts | 16 +++------------- test/config/config.test.ts | 4 ++-- 4 files changed, 26 insertions(+), 38 deletions(-) diff --git a/src/cli-ux/theme.ts b/src/cli-ux/theme.ts index 8a50aa9ef..8a6addcd9 100644 --- a/src/cli-ux/theme.ts +++ b/src/cli-ux/theme.ts @@ -13,10 +13,10 @@ function isStandardChalk(color: any): color is StandardChalk { * @param text string to colorize * @returns colorized string */ -export function colorize(color: Color | StandardChalk | undefined, text: string): string { +export function colorize(color: string | StandardChalk | undefined, text: string): string { if (isStandardChalk(color)) return chalk[color](text) - return color ? chalk.hex(color.hex())(text) : text + return color ? chalk.hex(color)(text) : text } export function parseTheme(theme: Themes): Theme { @@ -29,11 +29,11 @@ export function parseTheme(theme: Themes): Theme { ) } -export function getColor(color: string): Color +export function getColor(color: string): string export function getColor(color: StandardChalk): StandardChalk -export function getColor(color: string | StandardChalk): Color | StandardChalk | undefined { +export function getColor(color: string | StandardChalk): string | StandardChalk | undefined { try { // eslint-disable-next-line new-cap - return isStandardChalk(color) ? color : new Color.default(color) + return isStandardChalk(color) ? color : new Color.default(color).hex() } catch {} } diff --git a/src/interfaces/theme.ts b/src/interfaces/theme.ts index 817474ab1..665f91016 100644 --- a/src/interfaces/theme.ts +++ b/src/interfaces/theme.ts @@ -1,5 +1,3 @@ -import * as Color from 'color' - // chalk doesn't export a list of standard colors, so we have to supply our own export const STANDARD_CHALK = [ 'white', @@ -66,22 +64,22 @@ export const THEME_KEYS = [ export type ThemeKey = (typeof THEME_KEYS)[number] export type Theme = { - [key: string | ThemeKey]: Color | StandardChalk | undefined - alias?: Color | StandardChalk - bin?: Color | StandardChalk - command?: Color | StandardChalk - commandSummary?: Color | StandardChalk - dollarSign?: Color | StandardChalk - flag?: Color | StandardChalk - flagDefaultValue?: Color | StandardChalk - flagOptions?: Color | StandardChalk - flagRequired?: Color | StandardChalk - flagSeparator?: Color | StandardChalk - flagType?: Color | StandardChalk - sectionDescription?: Color | StandardChalk - sectionHeader?: Color | StandardChalk - topic?: Color | StandardChalk - version?: Color | StandardChalk + [key: string | ThemeKey]: string | StandardChalk | undefined + alias?: string | StandardChalk + bin?: string | StandardChalk + command?: string | StandardChalk + commandSummary?: string | StandardChalk + dollarSign?: string | StandardChalk + flag?: string | StandardChalk + flagDefaultValue?: string | StandardChalk + flagOptions?: string | StandardChalk + flagRequired?: string | StandardChalk + flagSeparator?: string | StandardChalk + flagType?: string | StandardChalk + sectionDescription?: string | StandardChalk + sectionHeader?: string | StandardChalk + topic?: string | StandardChalk + version?: string | StandardChalk } export type Themes = { diff --git a/test/cli-ux/theme.test.ts b/test/cli-ux/theme.test.ts index 2ffffdb0e..43f8d5642 100644 --- a/test/cli-ux/theme.test.ts +++ b/test/cli-ux/theme.test.ts @@ -9,7 +9,7 @@ describe('colorize', () => { it('should return text with ansi characters when given hex code', () => { const color = getColor('#FF0000') const text = colorize(color, 'brazil') - expect(text).to.equal(chalk.hex(color.hex())('brazil')) + expect(text).to.equal(chalk.hex(color)('brazil')) }) it('should return text with ansi characters when standard chalk color', () => { @@ -58,12 +58,7 @@ describe('theme parsing', () => { themes: {test: untypedTheme}, }) - for (const value of Object.values(theme)) { - expect(typeof value).to.equal('object') - expect(value).to.have.property('model') - expect(value).to.have.property('color') - expect(value).to.have.property('valpha') - } + expect(theme).to.deep.equal(untypedTheme) }) it('should parse untyped theme json to theme using rgb', () => { @@ -76,12 +71,7 @@ describe('theme parsing', () => { themes: {test: untypedTheme}, }) - for (const value of Object.values(theme)) { - expect(typeof value).to.equal('object') - expect(value).to.have.property('model') - expect(value).to.have.property('color') - expect(value).to.have.property('valpha') - } + expect(theme).to.deep.equal({alias: '#FFFFFF'}) }) it('should parse untyped theme json to theme using chalk standard colors', () => { diff --git a/test/config/config.test.ts b/test/config/config.test.ts index c5e8c7d17..5b453e77f 100644 --- a/test/config/config.test.ts +++ b/test/config/config.test.ts @@ -415,14 +415,14 @@ describe('Config', () => { testConfig({pjson, env: {FOO_DISABLE_THEME: 'false'}}, {bin: '#FF0000'}).it( 'should be set when DISABLE_THEME is false and theme.json exists', (config) => { - expect(config).to.nested.include({'theme.bin.color[0]': 255}) + expect(config.theme).to.have.property('bin', '#FF0000') }, ) testConfig({pjson, env: {}}, {bin: '#FF0000'}).it( 'should be set when DISABLE_THEME is unset and theme.json exists', (config) => { - expect(config).to.nested.include({'theme.bin.color[0]': 255}) + expect(config.theme).to.have.property('bin', '#FF0000') }, ) From 78bdacc7301a2dae3528d72ea5f72b5dc8eaaf35 Mon Sep 17 00:00:00 2001 From: svc-cli-bot Date: Thu, 16 Nov 2023 20:06:06 +0000 Subject: [PATCH 14/16] chore(release): 3.11.1-dev.1 [skip ci] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 33644030d..f165ccec9 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@oclif/core", "description": "base library for oclif CLIs", - "version": "3.11.1-dev.0", + "version": "3.11.1-dev.1", "author": "Salesforce", "bugs": "https://github.com/oclif/core/issues", "dependencies": { From 0886fbfc67af111cd46c601d2be868c2700609ef Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Thu, 16 Nov 2023 13:55:21 -0700 Subject: [PATCH 15/16] chore: remove unused type --- src/interfaces/theme.ts | 2 -- test/cli-ux/theme.test.ts | 1 - 2 files changed, 3 deletions(-) diff --git a/src/interfaces/theme.ts b/src/interfaces/theme.ts index 665f91016..dea654322 100644 --- a/src/interfaces/theme.ts +++ b/src/interfaces/theme.ts @@ -54,7 +54,6 @@ export const THEME_KEYS = [ 'flagOptions', 'flagRequired', 'flagSeparator', - 'flagType', 'sectionDescription', 'sectionHeader', 'topic', @@ -75,7 +74,6 @@ export type Theme = { flagOptions?: string | StandardChalk flagRequired?: string | StandardChalk flagSeparator?: string | StandardChalk - flagType?: string | StandardChalk sectionDescription?: string | StandardChalk sectionHeader?: string | StandardChalk topic?: string | StandardChalk diff --git a/test/cli-ux/theme.test.ts b/test/cli-ux/theme.test.ts index 43f8d5642..6d82e3477 100644 --- a/test/cli-ux/theme.test.ts +++ b/test/cli-ux/theme.test.ts @@ -127,7 +127,6 @@ describe('THEME_KEYS', () => { 'flagOptions', 'flagRequired', 'flagSeparator', - 'flagType', 'sectionDescription', 'sectionHeader', 'topic', From cdff2adf19bf1dc0095d522356c98e395528502a Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Mon, 20 Nov 2023 12:45:43 -0700 Subject: [PATCH 16/16] chore: revert to single theme.json --- src/cli-ux/theme.ts | 8 +++----- src/config/config.ts | 22 +++++++++------------- src/interfaces/index.ts | 2 +- src/interfaces/pjson.ts | 1 - src/interfaces/theme.ts | 5 ----- test/cli-ux/theme.test.ts | 20 ++++---------------- test/config/config.test.ts | 2 +- 7 files changed, 18 insertions(+), 42 deletions(-) diff --git a/src/cli-ux/theme.ts b/src/cli-ux/theme.ts index 8a6addcd9..02e81ca8b 100644 --- a/src/cli-ux/theme.ts +++ b/src/cli-ux/theme.ts @@ -1,7 +1,7 @@ import chalk from 'chalk' import * as Color from 'color' -import {STANDARD_CHALK, StandardChalk, Theme, Themes} from '../interfaces/theme' +import {STANDARD_CHALK, StandardChalk, Theme} from '../interfaces/theme' function isStandardChalk(color: any): color is StandardChalk { return STANDARD_CHALK.includes(color) @@ -19,11 +19,9 @@ export function colorize(color: string | StandardChalk | undefined, text: string return color ? chalk.hex(color)(text) : text } -export function parseTheme(theme: Themes): Theme { - const themes = theme.themes ?? {} - const selected = theme.selected ? themes[theme.selected] ?? {} : {} +export function parseTheme(theme: Record): Theme { return Object.fromEntries( - Object.entries(selected) + Object.entries(theme) .map(([key, value]) => [key, getColor(value)]) .filter(([_, value]) => value), ) diff --git a/src/config/config.ts b/src/config/config.ts index a6545e0fa..9cf3edee8 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -12,7 +12,7 @@ import {getHelpFlagAdditions} from '../help/util' import {Hook, Hooks, PJSON, Topic} from '../interfaces' import {ArchTypes, Config as IConfig, LoadOptions, PlatformTypes, VersionDetails} from '../interfaces/config' import {Plugin as IPlugin, Options} from '../interfaces/plugin' -import {Theme, Themes} from '../interfaces/theme' +import {Theme} from '../interfaces/theme' import {loadWithData} from '../module-loader' import {OCLIF_MARKER_OWNER, Performance} from '../performance' import {settings} from '../settings' @@ -330,8 +330,8 @@ export class Config implements IConfig { this.npmRegistry = this.scopedEnvVar('NPM_REGISTRY') || this.pjson.oclif.npmRegistry if (!this.scopedEnvVarTrue('DISABLE_THEME')) { - const {activeTheme} = await this.loadThemes() - this.theme = activeTheme + const {theme} = await this.loadThemes() + this.theme = theme } this.pjson.oclif.update = this.pjson.oclif.update || {} @@ -401,18 +401,14 @@ export class Config implements IConfig { public async loadThemes(): Promise<{ file: string - activeTheme: Theme | undefined - themes: Themes | undefined + theme: Theme | undefined }> { - const themesFile = this.pjson.oclif.themesFile - ? resolve(this.root, this.pjson.oclif.themesFile) - : resolve(this.configDir, 'themes.json') - const themes = await safeReadJson(themesFile) - const activeTheme = themes ? parseTheme(themes) : undefined + const file = resolve(this.configDir, 'theme.json') + const themes = await safeReadJson>(file) + const theme = themes ? parseTheme(themes) : undefined return { - activeTheme, - file: themesFile, - themes, + file, + theme, } } diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index 42986ff80..e6ba59c8c 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -10,6 +10,6 @@ export type {Arg, BooleanFlag, CustomOptions, Deprecation, Flag, FlagDefinition, export type {PJSON} from './pjson' export type {Options, Plugin, PluginOptions} from './plugin' export type {S3Manifest} from './s3-manifest' -export type {Theme, Themes} from './theme' +export type {Theme} from './theme' export type {Topic} from './topic' export type {TSConfig} from './ts-config' diff --git a/src/interfaces/pjson.ts b/src/interfaces/pjson.ts index bb09efdb7..3f82f5fd4 100644 --- a/src/interfaces/pjson.ts +++ b/src/interfaces/pjson.ts @@ -43,7 +43,6 @@ export namespace PJSON { repositoryPrefix?: string schema?: number state?: 'beta' | 'deprecated' | string - themesFile?: string topicSeparator?: ' ' | ':' topics?: { [k: string]: { diff --git a/src/interfaces/theme.ts b/src/interfaces/theme.ts index dea654322..bb2453f70 100644 --- a/src/interfaces/theme.ts +++ b/src/interfaces/theme.ts @@ -79,8 +79,3 @@ export type Theme = { topic?: string | StandardChalk version?: string | StandardChalk } - -export type Themes = { - selected?: string - themes?: Record> -} diff --git a/test/cli-ux/theme.test.ts b/test/cli-ux/theme.test.ts index 6d82e3477..051918d62 100644 --- a/test/cli-ux/theme.test.ts +++ b/test/cli-ux/theme.test.ts @@ -53,10 +53,7 @@ describe('theme parsing', () => { version: '#FFFFFF', } - const theme = parseTheme({ - selected: 'test', - themes: {test: untypedTheme}, - }) + const theme = parseTheme(untypedTheme) expect(theme).to.deep.equal(untypedTheme) }) @@ -66,10 +63,7 @@ describe('theme parsing', () => { alias: 'rgb(255, 255, 255)', } - const theme = parseTheme({ - selected: 'test', - themes: {test: untypedTheme}, - }) + const theme = parseTheme(untypedTheme) expect(theme).to.deep.equal({alias: '#FFFFFF'}) }) @@ -92,10 +86,7 @@ describe('theme parsing', () => { version: 'cyan', } - const theme = parseTheme({ - selected: 'test', - themes: {test: untypedTheme}, - }) + const theme = parseTheme(untypedTheme) for (const value of Object.values(theme)) { expect(value).to.equal('cyan') } @@ -106,10 +97,7 @@ describe('theme parsing', () => { alias: 'FOO', } - const theme = parseTheme({ - selected: 'test', - themes: {test: untypedTheme}, - }) + const theme = parseTheme(untypedTheme) expect(theme).to.deep.equal({}) }) }) diff --git a/test/config/config.test.ts b/test/config/config.test.ts index 5b453e77f..c8b2805ff 100644 --- a/test/config/config.test.ts +++ b/test/config/config.test.ts @@ -52,7 +52,7 @@ describe('Config', () => { .stub(os, 'getHomeDir', (stub) => stub.returns(join(homedir))) .stub(os, 'getPlatform', (stub) => stub.returns(platform)) - if (theme) test = test.stub(fs, 'safeReadJson', (stub) => stub.resolves({selected: 'test', themes: {test: theme}})) + if (theme) test = test.stub(fs, 'safeReadJson', (stub) => stub.resolves(theme)) if (pjson) test = test.stub(fs, 'readJson', (stub) => stub.resolves(pjson)) test = test.add('config', () => Config.load())