From 7dfc679e5a34ad4e51f4e84f965238938cbd3cc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lorber?= Date: Fri, 4 Sep 2026 11:46:01 +0200 Subject: [PATCH 1/2] chore: migrate from chalk to `node.utils.styleText()` (#12405) --- packages/docusaurus-logger/package.json | 1 - .../__snapshots__/index.test.ts.snap | 8 +- .../src/__tests__/index.test.ts | 265 +++++++++--------- packages/docusaurus-logger/src/logger.ts | 57 ++-- packages/docusaurus/package.json | 1 - packages/docusaurus/src/ssg/ssgExecutor.ts | 28 +- pnpm-lock.yaml | 6 - 7 files changed, 205 insertions(+), 161 deletions(-) diff --git a/packages/docusaurus-logger/package.json b/packages/docusaurus-logger/package.json index 9f4e38928dc3..d4353d32a013 100644 --- a/packages/docusaurus-logger/package.json +++ b/packages/docusaurus-logger/package.json @@ -21,7 +21,6 @@ "license": "MIT", "dependencies": { "@docusaurus/types": "3.10.1", - "chalk": "^4.1.2", "tslib": "^2.6.0" }, "engines": { diff --git a/packages/docusaurus-logger/src/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-logger/src/__tests__/__snapshots__/index.test.ts.snap index f3ce8b77aafd..58f520510fd3 100644 --- a/packages/docusaurus-logger/src/__tests__/__snapshots__/index.test.ts.snap +++ b/packages/docusaurus-logger/src/__tests__/__snapshots__/index.test.ts.snap @@ -1,6 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`error > prints objects 1`] = ` +exports[`logger > error > prints objects 1`] = ` [ [ "[ERROR] {"a":1}", @@ -17,7 +17,7 @@ exports[`error > prints objects 1`] = ` ] `; -exports[`info > prints objects 1`] = ` +exports[`logger > info > prints objects 1`] = ` [ [ "[INFO] {"a":1}", @@ -34,7 +34,7 @@ exports[`info > prints objects 1`] = ` ] `; -exports[`success > prints objects 1`] = ` +exports[`logger > success > prints objects 1`] = ` [ [ "[SUCCESS] {"a":1}", @@ -51,7 +51,7 @@ exports[`success > prints objects 1`] = ` ] `; -exports[`warn > prints objects 1`] = ` +exports[`logger > warn > prints objects 1`] = ` [ [ "[WARNING] {"a":1}", diff --git a/packages/docusaurus-logger/src/__tests__/index.test.ts b/packages/docusaurus-logger/src/__tests__/index.test.ts index f1172db4cc83..c5bf68265aab 100644 --- a/packages/docusaurus-logger/src/__tests__/index.test.ts +++ b/packages/docusaurus-logger/src/__tests__/index.test.ts @@ -5,159 +5,168 @@ * LICENSE file in the root directory of this source tree. */ -import {describe, expect, it, vi} from 'vitest'; +import {afterAll, beforeAll, describe, expect, it, vi} from 'vitest'; import logger from '../index'; -// Force chalk to ANSI level 3 in tests, so output is colored even in CI -vi.mock('chalk', async () => { - const chalk = await vi.importActual('chalk'); - return {default: new chalk.default.Instance({level: 3})}; -}); - -describe('formatters', () => { - it('path', () => { - expect(logger.path('keepAnsi')).toMatchInlineSnapshot( - `""keepAnsi""`, - ); - }); - it('url', () => { - expect(logger.url('https://docusaurus.io/keepAnsi')).toMatchInlineSnapshot( - `"https://docusaurus.io/keepAnsi"`, - ); - }); - it('id', () => { - expect(logger.name('keepAnsi')).toMatchInlineSnapshot( - `"keepAnsi"`, - ); +describe('logger', () => { + // Force chalk to ANSI level 3 in tests, so output is colored in snapshots + beforeAll(() => { + vi.stubEnv('FORCE_COLOR', '3'); }); - it('code', () => { - expect(logger.code('keepAnsi')).toMatchInlineSnapshot( - `"\`keepAnsi\`"`, - ); + + afterAll(() => { + vi.unstubAllEnvs(); }); - it('subdue', () => { - expect(logger.subdue('keepAnsi')).toMatchInlineSnapshot( - `"keepAnsi"`, - ); + + describe('formatters', () => { + it('path', () => { + expect(logger.path('keepAnsi')).toMatchInlineSnapshot( + `""keepAnsi""`, + ); + }); + it('url', () => { + expect( + logger.url('https://docusaurus.io/keepAnsi'), + ).toMatchInlineSnapshot( + `"https://docusaurus.io/keepAnsi"`, + ); + }); + it('id', () => { + expect(logger.name('keepAnsi')).toMatchInlineSnapshot( + `"keepAnsi"`, + ); + }); + it('code', () => { + expect(logger.code('keepAnsi')).toMatchInlineSnapshot( + `"\`keepAnsi\`"`, + ); + }); + it('subdue', () => { + expect(logger.subdue('keepAnsi')).toMatchInlineSnapshot( + `"keepAnsi"`, + ); + }); }); -}); -describe('interpolate', () => { - it('formats text with variables & arrays', () => { - const name = 'Josh'; - const items = [1, 'hi', 'Hmmm']; - expect( - logger.interpolate`(keepAnsi) Hello ${name}! Here are your goodies:${items}`, - ).toMatchInlineSnapshot(` + describe('interpolate', () => { + it('formats text with variables & arrays', () => { + const name = 'Josh'; + const items = [1, 'hi', 'Hmmm']; + expect( + logger.interpolate`(keepAnsi) Hello ${name}! Here are your goodies:${items}`, + ).toMatchInlineSnapshot(` "(keepAnsi) Hello Josh! Here are your goodies: - 1 - hi - Hmmm" `); - }); - it('recognizes valid flags', () => { - expect( - logger.interpolate`(keepAnsi) The package at path=${'packages/docusaurus'} has number=${10} files. name=${'Babel'} is exported here subdue=${'(as a preset)'} that you can with code=${"require.resolve('@docusaurus/core/lib/babel/preset')"}`, - ).toMatchInlineSnapshot( - `"(keepAnsi) The package at "packages/docusaurus" has 10 files. Babel is exported here (as a preset) that you can with \`require.resolve('@docusaurus/core/lib/babel/preset')\`"`, - ); - }); - it('interpolates arrays with flags', () => { - expect( - logger.interpolate`(keepAnsi) The following commands are available:code=${[ - 'docusaurus start', - 'docusaurus build', - 'docusaurus deploy', - ]}`, - ).toMatchInlineSnapshot(` + }); + it('recognizes valid flags', () => { + expect( + logger.interpolate`(keepAnsi) The package at path=${'packages/docusaurus'} has number=${10} files. name=${'Babel'} is exported here subdue=${'(as a preset)'} that you can with code=${"require.resolve('@docusaurus/core/lib/babel/preset')"}`, + ).toMatchInlineSnapshot( + `"(keepAnsi) The package at "packages/docusaurus" has 10 files. Babel is exported here (as a preset) that you can with \`require.resolve('@docusaurus/core/lib/babel/preset')\`"`, + ); + }); + it('interpolates arrays with flags', () => { + expect( + logger.interpolate`(keepAnsi) The following commands are available:code=${[ + 'docusaurus start', + 'docusaurus build', + 'docusaurus deploy', + ]}`, + ).toMatchInlineSnapshot(` "(keepAnsi) The following commands are available: - \`docusaurus start\` - \`docusaurus build\` - \`docusaurus deploy\`" `); + }); + it('prints detached flags as-is', () => { + expect( + logger.interpolate`(keepAnsi) You can use placeholders like code= ${'and it will'} be replaced with the succeeding arguments`, + ).toMatchInlineSnapshot( + `"(keepAnsi) You can use placeholders like code= and it will be replaced with the succeeding arguments"`, + ); + }); + it('throws with bad flags', () => { + expect( + () => + logger.interpolate`(keepAnsi) I mistyped this: cde=${'this code'} and I will be damned`, + ).toThrowErrorMatchingInlineSnapshot( + `[Error: Bad Docusaurus logging message. This is likely an internal bug, please report it.]`, + ); + }); }); - it('prints detached flags as-is', () => { - expect( - logger.interpolate`(keepAnsi) You can use placeholders like code= ${'and it will'} be replaced with the succeeding arguments`, - ).toMatchInlineSnapshot( - `"(keepAnsi) You can use placeholders like code= and it will be replaced with the succeeding arguments"`, - ); - }); - it('throws with bad flags', () => { - expect( - () => - logger.interpolate`(keepAnsi) I mistyped this: cde=${'this code'} and I will be damned`, - ).toThrowErrorMatchingInlineSnapshot( - `[Error: Bad Docusaurus logging message. This is likely an internal bug, please report it.]`, - ); - }); -}); -describe('info', () => { - it('prints objects', () => { - using info = vi.spyOn(console, 'info'); - logger.info({a: 1}); - logger.info(undefined); - logger.info([1, 2, 3]); - logger.info(new Date(2021, 10, 13)); - expect(info.mock.calls).toMatchSnapshot(); + describe('info', () => { + it('prints objects', () => { + using info = vi.spyOn(console, 'info'); + logger.info({a: 1}); + logger.info(undefined); + logger.info([1, 2, 3]); + logger.info(new Date(2021, 10, 13)); + expect(info.mock.calls).toMatchSnapshot(); + }); }); -}); -describe('warn', () => { - it('prints objects', () => { - using warn = vi.spyOn(console, 'warn'); - logger.warn({a: 1}); - logger.warn(undefined); - logger.warn([1, 2, 3]); - logger.warn(new Date(2021, 10, 13)); - expect(warn.mock.calls).toMatchSnapshot(); + describe('warn', () => { + it('prints objects', () => { + using warn = vi.spyOn(console, 'warn'); + logger.warn({a: 1}); + logger.warn(undefined); + logger.warn([1, 2, 3]); + logger.warn(new Date(2021, 10, 13)); + expect(warn.mock.calls).toMatchSnapshot(); + }); }); -}); -describe('error', () => { - it('prints objects', () => { - using error = vi.spyOn(console, 'error'); - logger.error({a: 1}); - logger.error(undefined); - logger.error([1, 2, 3]); - logger.error(new Date(2021, 10, 13)); - expect(error.mock.calls).toMatchSnapshot(); + describe('error', () => { + it('prints objects', () => { + using error = vi.spyOn(console, 'error'); + logger.error({a: 1}); + logger.error(undefined); + logger.error([1, 2, 3]); + logger.error(new Date(2021, 10, 13)); + expect(error.mock.calls).toMatchSnapshot(); + }); }); -}); -describe('success', () => { - it('prints objects', () => { - using log = vi.spyOn(console, 'log'); - logger.success({a: 1}); - logger.success(undefined); - logger.success([1, 2, 3]); - logger.success(new Date(2021, 10, 13)); - expect(log.mock.calls).toMatchSnapshot(); + describe('success', () => { + it('prints objects', () => { + using log = vi.spyOn(console, 'log'); + logger.success({a: 1}); + logger.success(undefined); + logger.success([1, 2, 3]); + logger.success(new Date(2021, 10, 13)); + expect(log.mock.calls).toMatchSnapshot(); + }); }); -}); -describe('report', () => { - it('works with all severities', () => { - using log = vi.spyOn(console, 'info'); - using warn = vi.spyOn(console, 'warn'); - logger.report('ignore')('hey'); - logger.report('log')('hey'); - logger.report('warn')('hey'); - expect(() => - logger.report('throw')('hey'), - ).toThrowErrorMatchingInlineSnapshot(`[Error: hey]`); - expect(() => - // @ts-expect-error: for test - logger.report('foo')('hey'), - ).toThrowErrorMatchingInlineSnapshot( - `[Error: Unexpected "reportingSeverity" value: foo.]`, - ); - expect(log).toHaveBeenCalledTimes(1); - expect(log).toHaveBeenCalledWith(expect.stringMatching(/.*\[INFO\].* hey/)); - expect(warn).toHaveBeenCalledTimes(1); - expect(warn).toHaveBeenCalledWith( - expect.stringMatching(/.*\[WARNING\].* hey/), - ); + describe('report', () => { + it('works with all severities', () => { + using log = vi.spyOn(console, 'info'); + using warn = vi.spyOn(console, 'warn'); + logger.report('ignore')('hey'); + logger.report('log')('hey'); + logger.report('warn')('hey'); + expect(() => + logger.report('throw')('hey'), + ).toThrowErrorMatchingInlineSnapshot(`[Error: hey]`); + expect(() => + // @ts-expect-error: for test + logger.report('foo')('hey'), + ).toThrowErrorMatchingInlineSnapshot( + `[Error: Unexpected "reportingSeverity" value: foo.]`, + ); + expect(log).toHaveBeenCalledTimes(1); + expect(log).toHaveBeenCalledWith( + expect.stringMatching(/.*\[INFO\].* hey/), + ); + expect(warn).toHaveBeenCalledTimes(1); + expect(warn).toHaveBeenCalledWith( + expect.stringMatching(/.*\[WARNING\].* hey/), + ); + }); }); }); diff --git a/packages/docusaurus-logger/src/logger.ts b/packages/docusaurus-logger/src/logger.ts index 096e4935a444..9c7ce16470e1 100644 --- a/packages/docusaurus-logger/src/logger.ts +++ b/packages/docusaurus-logger/src/logger.ts @@ -5,17 +5,34 @@ * LICENSE file in the root directory of this source tree. */ -import chalk from 'chalk'; +import {styleText} from 'node:util'; import type {ReportingSeverity} from '@docusaurus/types'; type InterpolatableValue = string | number | (string | number)[]; -const path = (msg: unknown): string => chalk.cyan.underline(`"${String(msg)}"`); -const url = (msg: unknown): string => chalk.cyan.underline(msg); -const name = (msg: unknown): string => chalk.blue.bold(msg); -const code = (msg: unknown): string => chalk.cyan(`\`${String(msg)}\``); -const subdue = (msg: unknown): string => chalk.gray(msg); -const num = (msg: unknown): string => chalk.yellow(msg); +type StyleTextFormat = Parameters[0]; + +const CommonStyles = { + blueBold: ['blue', 'bold'], + cyanUnderline: ['cyan', 'underline'], +} satisfies Record; + +const Prefixes = { + success: styleText(['green', 'bold'], '[SUCCESS]'), + info: styleText(['cyan', 'bold'], '[INFO]'), + warning: styleText(['yellow', 'bold'], '[WARNING]'), + error: styleText(['red', 'bold'], '[ERROR]'), +}; + +const path = (msg: unknown): string => + styleText(CommonStyles.cyanUnderline, `"${String(msg)}"`); +const url = (msg: unknown): string => + styleText(CommonStyles.cyanUnderline, String(msg)); +const name = (msg: unknown): string => + styleText(CommonStyles.blueBold, String(msg)); +const code = (msg: unknown): string => styleText('cyan', `\`${String(msg)}\``); +const subdue = (msg: unknown): string => styleText('gray', String(msg)); +const num = (msg: unknown): string => styleText('yellow', String(msg)); function interpolate( msgs: TemplateStringsArray, @@ -73,7 +90,7 @@ function info( ): void; function info(msg: unknown, ...values: InterpolatableValue[]): void { console.info( - `${chalk.cyan.bold('[INFO]')} ${ + `${Prefixes.info} ${ values.length === 0 ? stringify(msg) : interpolate(msg as TemplateStringsArray, ...values) @@ -87,8 +104,9 @@ function warn( ): void; function warn(msg: unknown, ...values: InterpolatableValue[]): void { console.warn( - chalk.yellow( - `${chalk.bold('[WARNING]')} ${ + styleText( + 'yellow', + `${Prefixes.warning} ${ values.length === 0 ? stringify(msg) : interpolate(msg as TemplateStringsArray, ...values) @@ -103,8 +121,9 @@ function error( ): void; function error(msg: unknown, ...values: InterpolatableValue[]): void { console.error( - chalk.red( - `${chalk.bold('[ERROR]')} ${ + styleText( + 'red', + `${Prefixes.error} ${ values.length === 0 ? stringify(msg) : interpolate(msg as TemplateStringsArray, ...values) @@ -119,7 +138,7 @@ function success( ): void; function success(msg: unknown, ...values: InterpolatableValue[]): void { console.log( - `${chalk.green.bold('[SUCCESS]')} ${ + `${Prefixes.success} ${ values.length === 0 ? stringify(msg) : interpolate(msg as TemplateStringsArray, ...values) @@ -175,12 +194,12 @@ function report(reportingSeverity: ReportingSeverity): typeof success { } const logger = { - red: (msg: string | number): string => chalk.red(msg), - yellow: (msg: string | number): string => chalk.yellow(msg), - green: (msg: string | number): string => chalk.green(msg), - cyan: (msg: string | number): string => chalk.cyan(msg), - bold: (msg: string | number): string => chalk.bold(msg), - dim: (msg: string | number): string => chalk.dim(msg), + red: (msg: string | number): string => styleText('red', String(msg)), + yellow: (msg: string | number): string => styleText('yellow', String(msg)), + green: (msg: string | number): string => styleText('green', String(msg)), + cyan: (msg: string | number): string => styleText('cyan', String(msg)), + bold: (msg: string | number): string => styleText('bold', String(msg)), + dim: (msg: string | number): string => styleText('dim', String(msg)), path, url, name, diff --git a/packages/docusaurus/package.json b/packages/docusaurus/package.json index 05ca194297c2..026c9df7eda5 100644 --- a/packages/docusaurus/package.json +++ b/packages/docusaurus/package.json @@ -41,7 +41,6 @@ "@docusaurus/utils-common": "3.10.1", "@docusaurus/utils-validation": "3.10.1", "boxen": "^6.2.1", - "chalk": "^4.1.2", "chokidar": "^3.5.3", "cli-table3": "^0.6.3", "combine-promises": "^1.1.0", diff --git a/packages/docusaurus/src/ssg/ssgExecutor.ts b/packages/docusaurus/src/ssg/ssgExecutor.ts index ea929d2b51b9..c067d99fcae1 100644 --- a/packages/docusaurus/src/ssg/ssgExecutor.ts +++ b/packages/docusaurus/src/ssg/ssgExecutor.ts @@ -20,10 +20,10 @@ import { import {generateHashRouterEntrypoint} from './ssgUtils'; import {createGlobalSSGResult} from './ssgGlobalResult'; import {executeSSGInlineTask} from './ssgWorkerInline'; +import type {SSGGlobalResult} from './ssgGlobalResult'; +import type {SSGParams} from './ssgParams'; import type {Props, RouterType} from '@docusaurus/types'; import type {SiteCollectedData} from '../common'; -import type {SSGParams} from './ssgParams'; -import type {SSGGlobalResult} from './ssgGlobalResult'; import type {ExecuteSSGWorkerThreadTask} from './ssgWorkerThread'; type SSGExecutor = { @@ -94,6 +94,24 @@ function getNumberOfThreads(pathnames: string[]) { }); } +// Workaround for Node styleText() limitation +// See https://github.com/nodejs/node/issues/65766 +function getWorkerColorEnv(): Record { + // Preserve an explicit user choice + if (process.env.FORCE_COLOR !== undefined) { + return {}; + } + + const depth = process.stdout.isTTY + ? (process.stdout.getColorDepth?.() ?? 0) + : 0; + if (depth > 2) { + return {FORCE_COLOR: depth >= 24 ? '3' : depth >= 8 ? '2' : '1'}; + } + + return {}; +} + const createPooledSSGExecutor: CreateSSGExecutor = async ({ params, pathnames, @@ -124,6 +142,12 @@ const createPooledSSGExecutor: CreateSSGExecutor = async ({ runtime: 'worker_threads', isolateWorkers: false, workerData: {params}, + env: { + // Cast is safe + // See https://github.com/tinylibs/tinypool/issues/136 + ...(process.env as Record), + ...getWorkerColorEnv(), + }, // WORKER MEMORY MANAGEMENT // Allows containing SSG memory leaks with a thread recycling workaround diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ea50188ac178..41190a1f41ce 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -361,9 +361,6 @@ importers: boxen: specifier: ^6.2.1 version: 6.2.1 - chalk: - specifier: ^4.1.2 - version: 4.1.2 chokidar: specifier: ^3.5.3 version: 3.6.0 @@ -710,9 +707,6 @@ importers: '@docusaurus/types': specifier: 3.10.1 version: link:../docusaurus-types - chalk: - specifier: ^4.1.2 - version: 4.1.2 tslib: specifier: ^2.6.0 version: 2.8.1 From 09947f3feeb149f779755fff796ddd25b1a65192 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lorber?= Date: Fri, 4 Sep 2026 12:29:19 +0200 Subject: [PATCH 2/2] docs: remove old versions of remark-math/rehype-katex on markdown plugin page (#12407) --- .../docs/guides/markdown-features/markdown-features-plugins.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/guides/markdown-features/markdown-features-plugins.mdx b/website/docs/guides/markdown-features/markdown-features-plugins.mdx index cc7ab15daf49..f8068a165174 100644 --- a/website/docs/guides/markdown-features/markdown-features-plugins.mdx +++ b/website/docs/guides/markdown-features/markdown-features-plugins.mdx @@ -45,7 +45,7 @@ These are all typical use-cases of Remark plugins, which can also be a source of An MDX plugin is usually an npm package, so you install them like other npm packages using npm. Take the [math plugins](./markdown-features-math-equations.mdx) as an example. ```bash npm2yarn -npm install --save remark-math@5 rehype-katex@6 +npm install --save remark-math rehype-katex ```