Skip to content

Commit b71fbf0

Browse files
authored
refactor: share terminal background and brand colour detection (#1459)
1 parent 9dfa22b commit b71fbf0

4 files changed

Lines changed: 73 additions & 7 deletions

File tree

packages/create-nuxt/src/init.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,15 @@ import { selectModulesAutocomplete } from '../../nuxt-cli/src/commands/module/_a
2323
import { checkNuxtCompatibility, fetchModules, MODULES_API_URL } from '../../nuxt-cli/src/commands/module/_utils'
2424
import addModuleCommand from '../../nuxt-cli/src/commands/module/add'
2525
import { runCommandDef as runCommand } from '../../nuxt-cli/src/run-command'
26-
import { nuxtIcon, themeColor } from '../../nuxt-cli/src/utils/ascii'
26+
import { nuxtIcon } from '../../nuxt-cli/src/utils/ascii'
2727
import { fetchJson } from '../../nuxt-cli/src/utils/fetch'
2828
import { formatHeadlessCommand } from '../../nuxt-cli/src/utils/headless'
2929
import { createInstallLog, resolvePackageManagerDescriptor, runInstall, takeUnreportedIgnoredBuilds } from '../../nuxt-cli/src/utils/install'
3030
import { debug, logger } from '../../nuxt-cli/src/utils/logger'
3131
import { classifyNetworkError, describeNetworkError, logNetworkError, probeNetworkError } from '../../nuxt-cli/src/utils/network'
3232
import { relativeToProcess } from '../../nuxt-cli/src/utils/paths'
3333
import { getTemplates, TEMPLATES_API_URL } from '../../nuxt-cli/src/utils/starter-templates'
34+
import { paintBrand } from '../../nuxt-cli/src/utils/terminal-theme'
3435
import { getNuxtVersion } from '../../nuxt-cli/src/utils/versions'
3536

3637
const NON_WORD_RE = /[^\w-]/g
@@ -212,10 +213,10 @@ export default defineCommand({
212213
}
213214

214215
if (hasTTY) {
215-
process.stdout.write(`\n${nuxtIcon}\n\n`)
216+
process.stdout.write(`\n${nuxtIcon()}\n\n`)
216217
}
217218

218-
intro(styleText('bold', `Welcome to Nuxt!`.split('').map(m => `${themeColor}${m}`).join('')))
219+
intro(styleText('bold', paintBrand('Welcome to Nuxt!')))
219220

220221
let availableTemplates: Record<string, TemplateData> = {}
221222

packages/nuxt-cli/src/utils/ascii.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
* https://bsky.app/profile/durdraw.org/post/3liadod3gv22a
44
*/
55

6-
export const themeColor = '\x1B[38;2;0;220;130m'
6+
import { paintBrand } from './terminal-theme'
7+
78
const icon = [
89
` .d$b.`,
910
` i$$A$$L .d$b`,
@@ -15,4 +16,7 @@ const icon = [
1516
` \`4$$$$$$$$P\` .i$$$$$$$$P\``,
1617
]
1718

18-
export const nuxtIcon = icon.map(line => line.split('').join(themeColor)).join('\n')
19+
/** The mark in Nuxt green, ending with the terminal's colour handed back. */
20+
export function nuxtIcon(): string {
21+
return icon.map(line => paintBrand(line)).join('\n')
22+
}

packages/nuxt-cli/src/utils/profile.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import process from 'node:process'
44
import { styleText } from 'node:util'
55
import { box } from '@clack/prompts'
66
import { join, relative } from 'pathe'
7-
import { themeColor } from './ascii'
7+
import { paintBrand } from './terminal-theme'
88

99
const RELATIVE_PATH_RE = /^(?![^.]{1,2}\/)/
1010

@@ -78,7 +78,7 @@ export async function stopCpuProfile(outDir: string, command: string): Promise<s
7878
contentPadding: 2,
7979
rounded: true,
8080
withGuide: false,
81-
formatBorder: (text: string) => `${themeColor + text}\x1B[0m`,
81+
formatBorder: (text: string) => paintBrand(text),
8282
})
8383
}
8484
catch {}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import process from 'node:process'
2+
import { styleText } from 'node:util'
3+
4+
export type TerminalBackground = 'dark' | 'light' | 'unknown'
5+
6+
type Rgb = readonly [number, number, number]
7+
8+
/**
9+
* Nuxt green, and the darker green it becomes on a light terminal.
10+
*
11+
* `#00DC82` is bright enough to all but disappear on white (a contrast ratio
12+
* of about 1.8:1), so the light variant trades some of the glow for a ratio
13+
* above 4:1.
14+
*/
15+
const BRAND_GREEN: Record<'dark' | 'light', Rgb> = {
16+
dark: [0, 220, 130],
17+
light: [0, 145, 92],
18+
}
19+
20+
/** Palette indices a terminal reports as its background when it is a light one. */
21+
const LIGHT_INDICES = new Set([7, 9, 10, 11, 12, 13, 14, 15])
22+
23+
/**
24+
* Whether the terminal is showing dark text on light or the other way round.
25+
*
26+
* `unknown` is the common answer: only some terminals set `COLORFGBG`, and
27+
* asking the terminal directly (OSC 11) means writing to stdout and waiting
28+
* for a reply that may never come. Callers are expected to have a choice that
29+
* is safe on either background rather than to guess.
30+
*/
31+
function resolveBackground(env: NodeJS.ProcessEnv = process.env): TerminalBackground {
32+
const override = env.NUXT_TERM_THEME?.trim().toLowerCase()
33+
if (override === 'dark' || override === 'light') {
34+
return override
35+
}
36+
37+
const reported = env.COLORFGBG?.split(';').at(-1)?.trim()
38+
if (!reported || !/^\d+$/.test(reported)) {
39+
return 'unknown'
40+
}
41+
return LIGHT_INDICES.has(Number(reported)) ? 'light' : 'dark'
42+
}
43+
44+
/**
45+
* Write `text` in Nuxt green, as close to it as the terminal can be trusted to
46+
* render legibly.
47+
*
48+
* Without a known background there is no safe exact colour, so the terminal's
49+
* own palette decides: the user chose it against the background they are
50+
* looking at.
51+
*/
52+
export function paintBrand(text: string, background = resolveBackground()): string {
53+
if (background === 'unknown' || (process.stdout.getColorDepth?.() ?? 1) < 24) {
54+
return styleText('green', text)
55+
}
56+
if (process.env.NO_COLOR || !process.stdout.hasColors?.()) {
57+
return text
58+
}
59+
const [r, g, b] = BRAND_GREEN[background]
60+
return `\u001B[38;2;${r};${g};${b}m${text}\u001B[39m`
61+
}

0 commit comments

Comments
 (0)