From 9dc33c35343e1e54a32d31b01e898177694b8757 Mon Sep 17 00:00:00 2001 From: neverland Date: Mon, 14 Jul 2025 17:49:02 +0800 Subject: [PATCH 1/4] feat: validate output target in Rsbuild config --- .../exports-presence/index.test.ts | 21 +++++++++++++ .../exports-presence/src/index.js | 1 + e2e/scripts/shared.ts | 2 ++ packages/core/src/provider/initConfigs.ts | 31 ++++++++++++++----- 4 files changed, 47 insertions(+), 8 deletions(-) create mode 100644 e2e/cases/output/invalid-target/exports-presence/index.test.ts create mode 100644 e2e/cases/output/invalid-target/exports-presence/src/index.js diff --git a/e2e/cases/output/invalid-target/exports-presence/index.test.ts b/e2e/cases/output/invalid-target/exports-presence/index.test.ts new file mode 100644 index 0000000000..52861aab25 --- /dev/null +++ b/e2e/cases/output/invalid-target/exports-presence/index.test.ts @@ -0,0 +1,21 @@ +import { build } from '@e2e/helper'; +import { expect, test } from '@playwright/test'; + +test('should throw error by default (exportsPresence error)', async () => { + const rsbuild = await build({ + cwd: __dirname, + catchBuildError: true, + rsbuildConfig: { + output: { + // @ts-expect-error test invalid target + target: 'foo', + }, + }, + }); + + expect(rsbuild.buildError?.message).toContain( + `Invalid value of output.target config: "foo"`, + ); + + await rsbuild.close(); +}); diff --git a/e2e/cases/output/invalid-target/exports-presence/src/index.js b/e2e/cases/output/invalid-target/exports-presence/src/index.js new file mode 100644 index 0000000000..8b1a393741 --- /dev/null +++ b/e2e/cases/output/invalid-target/exports-presence/src/index.js @@ -0,0 +1 @@ +// empty diff --git a/e2e/scripts/shared.ts b/e2e/scripts/shared.ts index cd9ad23577..70533036e2 100644 --- a/e2e/scripts/shared.ts +++ b/e2e/scripts/shared.ts @@ -2,6 +2,7 @@ import assert from 'node:assert'; import net from 'node:net'; import { join } from 'node:path'; import { URL } from 'node:url'; +import { stripVTControlCharacters as stripAnsi } from 'node:util'; import type { CreateRsbuildOptions, RsbuildConfig, @@ -287,6 +288,7 @@ export async function build({ closeBuild = result.close; } catch (error) { buildError = error as Error; + buildError.message = stripAnsi(buildError.message); if (!catchBuildError) { throw buildError; diff --git a/packages/core/src/provider/initConfigs.ts b/packages/core/src/provider/initConfigs.ts index 192260c2e2..0f11cdc375 100644 --- a/packages/core/src/provider/initConfigs.ts +++ b/packages/core/src/provider/initConfigs.ts @@ -181,14 +181,29 @@ const validateRsbuildConfig = (config: NormalizedConfig) => { ); } - if (config.environments) { - const names = Object.keys(config.environments); - const regexp = /^[\w$-]+$/; - for (const name of names) { - // ensure environment names are filesystem and property access safe - if (!regexp.test(name)) { - logger.warn( - `${color.dim('[rsbuild:config]')} Environment name "${color.yellow(name)}" contains invalid characters. Only letters, numbers, "-", "_", and "$" are allowed.`, + if (!config.environments) { + return; + } + + const environmentNames = Object.keys(config.environments); + const environmentNameRegexp = /^[\w$-]+$/; + + for (const name of environmentNames) { + // ensure environment names are filesystem and property access safe + if (!environmentNameRegexp.test(name)) { + logger.warn( + `${color.dim('[rsbuild:config]')} Environment name "${color.yellow(name)}" contains invalid characters. Only letters, numbers, "-", "_", and "$" are allowed.`, + ); + } + + const outputConfig = config.environments[name].output; + if (outputConfig.target) { + const validTargets = ['web', 'node', 'web-worker']; + if (!validTargets.includes(outputConfig.target)) { + throw new Error( + `${color.dim('[rsbuild:config]')} Invalid value of ${color.yellow( + 'output.target', + )} config: ${color.yellow(`"${outputConfig.target}"`)}`, ); } } From 58f9793b7989b1bb4ff6e164d81a8f7a858fbbec Mon Sep 17 00:00:00 2001 From: neverland Date: Mon, 14 Jul 2025 19:05:52 +0800 Subject: [PATCH 2/4] fix --- packages/core/src/provider/initConfigs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/provider/initConfigs.ts b/packages/core/src/provider/initConfigs.ts index 0f11cdc375..aae7b9f02c 100644 --- a/packages/core/src/provider/initConfigs.ts +++ b/packages/core/src/provider/initConfigs.ts @@ -187,6 +187,7 @@ const validateRsbuildConfig = (config: NormalizedConfig) => { const environmentNames = Object.keys(config.environments); const environmentNameRegexp = /^[\w$-]+$/; + const validTargets = ['web', 'node', 'web-worker']; for (const name of environmentNames) { // ensure environment names are filesystem and property access safe @@ -198,7 +199,6 @@ const validateRsbuildConfig = (config: NormalizedConfig) => { const outputConfig = config.environments[name].output; if (outputConfig.target) { - const validTargets = ['web', 'node', 'web-worker']; if (!validTargets.includes(outputConfig.target)) { throw new Error( `${color.dim('[rsbuild:config]')} Invalid value of ${color.yellow( From 956dce2053e9854dea75186ff3368cd4641ce065 Mon Sep 17 00:00:00 2001 From: neverland Date: Mon, 14 Jul 2025 19:09:49 +0800 Subject: [PATCH 3/4] fix --- packages/core/src/provider/initConfigs.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/core/src/provider/initConfigs.ts b/packages/core/src/provider/initConfigs.ts index aae7b9f02c..27dfec648c 100644 --- a/packages/core/src/provider/initConfigs.ts +++ b/packages/core/src/provider/initConfigs.ts @@ -203,7 +203,9 @@ const validateRsbuildConfig = (config: NormalizedConfig) => { throw new Error( `${color.dim('[rsbuild:config]')} Invalid value of ${color.yellow( 'output.target', - )} config: ${color.yellow(`"${outputConfig.target}"`)}`, + )}: ${color.yellow(`"${outputConfig.target}"`)}, valid values are: ${color.yellow( + validTargets.join(', '), + )}`, ); } } From 55eb0050606dc0eaff145dfb807056e183019568 Mon Sep 17 00:00:00 2001 From: neverland Date: Mon, 14 Jul 2025 21:32:35 +0800 Subject: [PATCH 4/4] fix --- .../invalid-target/{exports-presence => }/index.test.ts | 4 ++-- .../output/invalid-target/{exports-presence => }/src/index.js | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename e2e/cases/output/invalid-target/{exports-presence => }/index.test.ts (73%) rename e2e/cases/output/invalid-target/{exports-presence => }/src/index.js (100%) diff --git a/e2e/cases/output/invalid-target/exports-presence/index.test.ts b/e2e/cases/output/invalid-target/index.test.ts similarity index 73% rename from e2e/cases/output/invalid-target/exports-presence/index.test.ts rename to e2e/cases/output/invalid-target/index.test.ts index 52861aab25..682d8b0971 100644 --- a/e2e/cases/output/invalid-target/exports-presence/index.test.ts +++ b/e2e/cases/output/invalid-target/index.test.ts @@ -1,7 +1,7 @@ import { build } from '@e2e/helper'; import { expect, test } from '@playwright/test'; -test('should throw error by default (exportsPresence error)', async () => { +test('should throw error when `output.target` is invalid', async () => { const rsbuild = await build({ cwd: __dirname, catchBuildError: true, @@ -14,7 +14,7 @@ test('should throw error by default (exportsPresence error)', async () => { }); expect(rsbuild.buildError?.message).toContain( - `Invalid value of output.target config: "foo"`, + `Invalid value of output.target: "foo", valid values are:`, ); await rsbuild.close(); diff --git a/e2e/cases/output/invalid-target/exports-presence/src/index.js b/e2e/cases/output/invalid-target/src/index.js similarity index 100% rename from e2e/cases/output/invalid-target/exports-presence/src/index.js rename to e2e/cases/output/invalid-target/src/index.js