-
Notifications
You must be signed in to change notification settings - Fork 73
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: enable themes (#852) #862
Conversation
* 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<ColorParam>|undefined' not assignable to 'Color<ColorParam>' * 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/<CLI>/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/<CLI>/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
@mdonnalley |
@mdonnalley when you release this feature could you please make some sort of "promotion" with my name? Now that I have time, I'm working on open source projects that I have interest in order to increase my chances of leaving this s*** country when my parents are gone. If you can't do it, there is no problem. Thank you in advance. |
@AllanOricil No problem. You're name will be in the commit history and we'll mention your name in the |
@mdonnalley I would like to also have my name somewhere on those posts/videos that Salesforce usually release to notify devs about new features |
@mdonnalley you removed the test that tests that a certain key is present in the parsed theme. If a developer remove |
@AllanOricil Are you referring to these tests? If so, I don't see what value that provides that this test doesn't. They're the exact same tests except one has more properties in the parsed theme |
@mdonnalley in the first, if you remove EDIT: I was actually wrong. The code does compile, but 1 test fail when removing a key from THEME_KEYS, which is good to tell developers that a regression occurred and therefore a key must not be removed. Sorry for the mistake. |
@mdonnalley look, when I removed |
@mdonnalley Yes, with the current code there is no way to know if another developer deleted any of the mandatory Theme keys because there is no tests to test the which keys are valid in the Theme specification. |
src/interfaces/theme.ts
Outdated
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] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was able to pass 'red' to color without a problem . What happens if this is not in the code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
test/util/theme.test.ts
Outdated
alias: 'cyan', | ||
bin: 'cyan', | ||
command: 'cyan', | ||
commandSummary: 'cyan', | ||
dollarSign: 'cyan', | ||
flag: 'cyan', | ||
flagDefaultValue: 'cyan', | ||
flagOptions: 'cyan', | ||
flagRequired: 'cyan', | ||
flagSeparator: 'cyan', | ||
sectionDescription: 'cyan', | ||
sectionHeader: 'cyan', | ||
topic: 'cyan', | ||
version: 'cyan', | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
src/util/theme.ts
Outdated
} | ||
|
||
export function colorize(color: Color | StandardChalk | undefined, text: string): string { | ||
if (isStandardChalk(color)) return standardChalk(color, text) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
src/util/theme.ts
Outdated
export function parseTheme(untypedTheme: Record<string, string>): Theme { | ||
return Object.fromEntries(Object.entries(untypedTheme).map(([key, value]) => [key, getColor(value)])) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the theme feature should not waste time or memory storing tokens its spec does not support (strict). Its spec supports these tokens:
export const THEME_KEYS = [
'alias',
'bin',
'command',
'commandSummary',
'dollarSign',
'flag',
'flagDefaultValue',
'flagOptions',
'flagRequired',
'flagSeparator',
'flagType',
'sectionDescription',
'sectionHeader',
'topic',
'version',
]
Then you have to add these tests back because they prove the independency and usage of each token of Theme Spec. We need it so that when tokens are changed we know exactly the regressions the change made. For example, if I remove the alias
token and run the tests, I will see that its test failed, and this will show me a regression:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@AllanOricil I want to leave the possibility for people to be able to use the theme beyond just the help output.
For instance, someone might want to have a warningMsg
color that gets used on all their warning messages.
export default class MyCommand extends Command {
public async run(): Promise<void> {
this.logToStderr(colorize(this.config.theme.warningMsg, 'hello world'))
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mdonnalley we would change logToStderr
to the following piece of code so that they can keep using this.logToStderr
without updating their code. The theme.json is what controls the color changing.
public static logToStderr(format?: string, ...args: string[]): void {
write.stderr(colorize(this.config?.theme?.warningMsg, utilFormat(format, ...args) + '\n'))
}
and a new token would be added to the Theme Spec
const THEME_KEYS = {
...,
"warningMsg"
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this.config?.theme?.warningMsg
does not exist, this.logToStderr
will output what is currently outputing. It would not require any code updates in CLIs using @oclif/core.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If CLI developers want to overwrite what the theme.json says, they can do this
export default class MyCommand extends Command {
public async run(): Promise<void> {
this.config.theme.warningMsg = getColor('brightBlack')
this.logToStderr('hello world')
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and for logToStderr
, a better Theme Key is stderrMessage
,
public static logToStderr(format?: string, ...args: string[]): void {
write.stderr(colorize(this.config?.theme?.stderrMessage, utilFormat(format, ...args) + '\n'))
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a very poor assumption that everyone would be using logToStderr
to only log warning messages. But my point was that by allowing any key in themes.json
we give CLI developers the ability to create a more extensive theme for their users. I have no interest in standardizing themes for every single oclif CLI
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Going back to what I said earlier, there are no tests to "prove" the Theme Spec the Core Specifies...
If the core is using certain keys
everywhere in its code, there must exist tests to prove each single one of these keys....
so that when someone deletes or changes the key, a test failure happens to show the Regression.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, thanks for adding a test back. Now it is working again as I specified.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
06d5400
to
1a4c32b
Compare
src/interfaces/theme.ts
Outdated
export type ThemeKey = (typeof THEME_KEYS)[number] | ||
|
||
export type Theme = { | ||
[key: string | ThemeKey]: Color | StandardChalk | undefined |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I see a use case for this. CLI developers can extend the oclif's Theme Spec with their own theme specific keys so that plugins built with their own CLI API can take advantage of their own Theme Spec, which is just a combination of Oclif + CLI Theme Specs. Makes sense now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't Theme be a interface
to allow it to be extensible in typescript world?
I don't see anything else I would change, so I've approved it. And I can't see why tests are failing to help fix it. I will go do something else. |
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', | ||
'bold', | ||
'underline', | ||
'dim', | ||
'italic', | ||
'strikethrough', | ||
] as const |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't these exports work? I did not check their values, but by looking at the variable names it seems to be what we need
https://github.com/chalk/chalk/blob/main/source/index.js#L207-L218
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Never mind. That is for v5. V4 does not have those exports, unfortunately
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In v4 I think you could use
Object.getOwnPropertyNames(chalk)
edited: we don't have access to Chalk class, but it is possible to use an instance of it
Based on the following, all styles are direct props from the chalk prototype:
src/config/config.ts
Outdated
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<Themes>(themesFile) | ||
const activeTheme = themes ? parseTheme(themes) : undefined | ||
return { | ||
activeTheme, | ||
file: themesFile, | ||
themes, | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand why loading all themes in memory if only one is used at a time
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will be used by https://github.com/oclif/plugin-theme/pull/7
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does not answer the question. What is the use case to have all themes in memory?
I envisioned and started this feature and you took it over and started to make decisions without discussing with me. This is extremely unprofessional and it will be registered here for other people to see. |
It's an open PR - no decisions have been made. Your feedback is welcome, including on the plugin. Your reviews and comments on this PR have been incredibly helpful thus far - I'm sure you've noticed that many of the commits here are addressing your feedback. From my perspective, this has been a collaborative effort. The current implementation in this PR and the plugin-theme PR was discussed here. As Cristian mentioned in his comment, we're not willing to take on the complexity of having themes be installable via npm so we're looking for a more file-based solution. We'd love if you could add your thoughts there so that we can starting working towards a solution that we're all happy with |
Hi @mdonnalley sorry for what I said earlier. From time to time I have stupid emotional outages and I end up saying stupid things because my mind starts to think about things that doesnt exist. For example, I think that Im losing something and being left behind, or I get envy, again for no reason. I take some medication but it just doesn't work well sometimes. You can continue this and follow your ideas. You actually did most of the good decisions. I hope to use it when it is finished. Again, sorry. |
Merge #852 to
main