From ae32c79cd4aa033b8a2fac01d19d7a64fdccee79 Mon Sep 17 00:00:00 2001 From: Jiahan Chen Date: Fri, 14 Aug 2026 12:42:40 +0800 Subject: [PATCH 01/11] fix(doc): restart dev server on config changes (#358) --- packages/rstack/rstack.config.ts | 1 + packages/rstack/src/rspressConfig.ts | 29 ++++++- .../tests/config/define-doc/index.test.ts | 2 +- .../config/reload-app-config/index.test.ts | 4 +- .../config/reload-doc-config/docs/index.md | 1 + .../config/reload-doc-config/index.test.ts | 80 +++++++++++++++++++ 6 files changed, 112 insertions(+), 5 deletions(-) create mode 100644 packages/rstack/tests/config/reload-doc-config/docs/index.md create mode 100644 packages/rstack/tests/config/reload-doc-config/index.test.ts diff --git a/packages/rstack/rstack.config.ts b/packages/rstack/rstack.config.ts index 650c9379..09910407 100644 --- a/packages/rstack/rstack.config.ts +++ b/packages/rstack/rstack.config.ts @@ -11,6 +11,7 @@ define.test(async () => { // Temporary projects may contain files that match Rstest's test glob. exclude: ['**/test-temp-*/**'], extends: withRslibConfig(), + testTimeout: 30_000, source: { tsconfigPath: './tests/tsconfig.json', }, diff --git a/packages/rstack/src/rspressConfig.ts b/packages/rstack/src/rspressConfig.ts index ed5efd59..0bf6a60d 100644 --- a/packages/rstack/src/rspressConfig.ts +++ b/packages/rstack/src/rspressConfig.ts @@ -1,3 +1,4 @@ +import type { WatchFiles } from '@rsbuild/core'; import type { UserConfig } from '@rspress/core'; import { loadRstackConfig, type Configs } from './config.ts'; @@ -13,6 +14,30 @@ const resolveRspressConfig = async (configs: Configs): Promise => { }; export default async (): Promise => { - const { configs } = await loadRstackConfig(); - return resolveRspressConfig(configs); + const { configs, filePath, dependencies } = await loadRstackConfig(); + const config = await resolveRspressConfig(configs); + + if (!filePath) { + return config; + } + + const watchFiles = config.builderConfig?.dev?.watchFiles; + const watchConfig: WatchFiles = { + paths: [filePath, ...dependencies], + type: 'restart', + }; + + return { + ...config, + builderConfig: { + ...config.builderConfig, + dev: { + ...config.builderConfig?.dev, + watchFiles: [ + ...(watchFiles ? (Array.isArray(watchFiles) ? watchFiles : [watchFiles]) : []), + watchConfig, + ], + }, + }, + }; }; diff --git a/packages/rstack/tests/config/define-doc/index.test.ts b/packages/rstack/tests/config/define-doc/index.test.ts index 58ce1fc4..464e7580 100644 --- a/packages/rstack/tests/config/define-doc/index.test.ts +++ b/packages/rstack/tests/config/define-doc/index.test.ts @@ -12,4 +12,4 @@ test('should build docs with define.doc config', async ({ prepareDist, execCli, const output = getFileContent(files, 'index.html'); expect(output).toContain(expectedText); -}, 30_000); +}); diff --git a/packages/rstack/tests/config/reload-app-config/index.test.ts b/packages/rstack/tests/config/reload-app-config/index.test.ts index cdf62067..a7f9ab93 100644 --- a/packages/rstack/tests/config/reload-app-config/index.test.ts +++ b/packages/rstack/tests/config/reload-app-config/index.test.ts @@ -45,7 +45,7 @@ define.app({ ); await waitForFile(dist2); -}, 30_000); +}); test('should reload config when an imported file changes', async ({ execCliAsync, logHelper }) => { const configFile = path.join(import.meta.dirname, 'test-temp-import.config.ts'); @@ -70,4 +70,4 @@ define.app({ await writeFile(importedFile, '// changed\n'); await logHelper.expectLog('restarting server as test-temp-imported.ts changed'); -}, 30_000); +}); diff --git a/packages/rstack/tests/config/reload-doc-config/docs/index.md b/packages/rstack/tests/config/reload-doc-config/docs/index.md new file mode 100644 index 00000000..f5a6303d --- /dev/null +++ b/packages/rstack/tests/config/reload-doc-config/docs/index.md @@ -0,0 +1 @@ +# Reload doc config diff --git a/packages/rstack/tests/config/reload-doc-config/index.test.ts b/packages/rstack/tests/config/reload-doc-config/index.test.ts new file mode 100644 index 00000000..0b00474b --- /dev/null +++ b/packages/rstack/tests/config/reload-doc-config/index.test.ts @@ -0,0 +1,80 @@ +import { writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import { getRandomPort } from '@rstackjs/test-utils'; +import { test } from '#test-helpers'; + +test('should restart doc dev server when Rstack config changes', async ({ + execCliAsync, + logHelper, +}) => { + const configFile = path.join(import.meta.dirname, 'test-temp-rstack.config.ts'); + const userWatchFile = path.join(import.meta.dirname, 'test-temp-user-watch.txt'); + + const writeConfig = (title: string) => + writeFile( + configFile, + `import { define } from 'rstack'; + +define.doc({ + root: 'docs', + title: '${title}', + builderConfig: { + dev: { + watchFiles: { + paths: ${JSON.stringify(userWatchFile)}, + type: 'restart', + }, + }, + }, +}); +`, + ); + + await writeFile(userWatchFile, 'initial\n'); + await writeConfig('before config change'); + + execCliAsync(`doc --config test-temp-rstack.config.ts --port ${await getRandomPort()}`); + await logHelper.expectBuildEnd(); + logHelper.clearLogs(); + + await writeConfig('after config change'); + + await logHelper.expectLog('restarting server as test-temp-rstack.config.ts changed'); + await logHelper.expectBuildEnd(); + logHelper.clearLogs(); + + await writeFile(userWatchFile, 'changed\n'); + + await logHelper.expectLog('restarting server as test-temp-user-watch.txt changed'); + await logHelper.expectBuildEnd(); +}); + +test('should restart doc dev server when an imported config file changes', async ({ + execCliAsync, + logHelper, +}) => { + const configFile = path.join(import.meta.dirname, 'test-temp-import.config.ts'); + const importedFile = path.join(import.meta.dirname, 'test-temp-imported.ts'); + + await writeFile(importedFile, "export const title = 'before import change';\n"); + await writeFile( + configFile, + `import { define } from 'rstack'; +import { title } from './test-temp-imported.ts'; + +define.doc({ + root: 'docs', + title, +}); +`, + ); + + execCliAsync(`doc --config test-temp-import.config.ts --port ${await getRandomPort()}`); + await logHelper.expectBuildEnd(); + logHelper.clearLogs(); + + await writeFile(importedFile, "export const title = 'after import change';\n"); + + await logHelper.expectLog('restarting server as test-temp-imported.ts changed'); + await logHelper.expectBuildEnd(); +}); From 9f6f61b6d316c348bdf207ee0efd4df68faf2878 Mon Sep 17 00:00:00 2001 From: Jiahan Chen Date: Fri, 14 Aug 2026 13:22:36 +0800 Subject: [PATCH 02/11] docs: update website logo (#359) --- website/docs/public/horizontal-logo.svg | 1 + website/rstack.config.ts | 2 +- website/theme/index.scss | 4 ++++ 3 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 website/docs/public/horizontal-logo.svg diff --git a/website/docs/public/horizontal-logo.svg b/website/docs/public/horizontal-logo.svg new file mode 100644 index 00000000..ca1eac2d --- /dev/null +++ b/website/docs/public/horizontal-logo.svg @@ -0,0 +1 @@ +Rstack CLI horizontal logo lockup. The claw logo is embedded from the original source without changes. \ No newline at end of file diff --git a/website/rstack.config.ts b/website/rstack.config.ts index 338cad02..aa78c750 100644 --- a/website/rstack.config.ts +++ b/website/rstack.config.ts @@ -22,7 +22,7 @@ define.doc(async () => { root: path.join(import.meta.dirname, 'docs'), title, icon: 'https://assets.rspack.rs/rspack/rspack-claw-logo.svg', - logoText: title, + logo: '/horizontal-logo.svg', description, lang: 'en', llms: true, diff --git a/website/theme/index.scss b/website/theme/index.scss index e9b4f959..9fde95d7 100644 --- a/website/theme/index.scss +++ b/website/theme/index.scss @@ -13,6 +13,10 @@ } } +.rspress-logo { + height: 1.8rem; +} + .rp-doc { .rp-link, .rp-link code { From 78972cb94c40ea66842cca6121867a39068fae8e Mon Sep 17 00:00:00 2001 From: Jiahan Chen Date: Fri, 14 Aug 2026 13:34:12 +0800 Subject: [PATCH 03/11] fix(lib): restart on config changes (#360) --- packages/rstack/src/rslibConfig.ts | 26 +++++- .../config/reload-lib-config/index.test.ts | 90 +++++++++++++++++++ .../config/reload-lib-config/package.json | 4 + .../config/reload-lib-config/src/index.js | 1 + 4 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 packages/rstack/tests/config/reload-lib-config/index.test.ts create mode 100644 packages/rstack/tests/config/reload-lib-config/package.json create mode 100644 packages/rstack/tests/config/reload-lib-config/src/index.js diff --git a/packages/rstack/src/rslibConfig.ts b/packages/rstack/src/rslibConfig.ts index 6f0011ed..b7468aa4 100644 --- a/packages/rstack/src/rslibConfig.ts +++ b/packages/rstack/src/rslibConfig.ts @@ -1,3 +1,4 @@ +import type { WatchFiles } from '@rsbuild/core'; import type { ConfigParams, RslibConfig, RslibConfigDefinition } from '@rslib/core'; import { loadRstackConfig, type Configs } from './config.ts'; @@ -13,8 +14,29 @@ const resolveRslibConfig = async (configs: Configs, params: ConfigParams): Promi }; const loadRslibConfig = (async (params: ConfigParams) => { - const { configs } = await loadRstackConfig(); - return resolveRslibConfig(configs, params); + const { configs, filePath, dependencies } = await loadRstackConfig(); + const config = await resolveRslibConfig(configs, params); + + if (!filePath) { + return config; + } + + const watchFiles = config.dev?.watchFiles; + const watchConfig: WatchFiles = { + paths: [filePath, ...dependencies], + type: 'restart', + }; + + return { + ...config, + dev: { + ...config.dev, + watchFiles: [ + ...(watchFiles ? (Array.isArray(watchFiles) ? watchFiles : [watchFiles]) : []), + watchConfig, + ], + }, + }; }) as RslibConfigDefinition; export default loadRslibConfig; diff --git a/packages/rstack/tests/config/reload-lib-config/index.test.ts b/packages/rstack/tests/config/reload-lib-config/index.test.ts new file mode 100644 index 00000000..a793c158 --- /dev/null +++ b/packages/rstack/tests/config/reload-lib-config/index.test.ts @@ -0,0 +1,90 @@ +import { writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import { waitForFile } from '@rstackjs/test-utils'; +import { test } from '#test-helpers'; + +test('should restart lib watch build when Rstack config changes', async ({ + prepareDist, + execCliAsync, + logHelper, +}) => { + const dist1 = await prepareDist(); + const dist2 = await prepareDist('dist-2'); + const configFile = path.join(import.meta.dirname, 'test-temp-rstack.config.ts'); + const userWatchFile = path.join(import.meta.dirname, 'test-temp-user-watch.txt'); + + const writeConfig = (distPath: string) => + writeFile( + configFile, + `import { define } from 'rstack'; + +define.lib({ + dev: { + watchFiles: { + paths: ${JSON.stringify(userWatchFile)}, + type: 'restart', + }, + }, + output: { + distPath: '${distPath}', + }, +}); +`, + ); + + await writeFile(userWatchFile, 'initial\n'); + await writeConfig('dist'); + + execCliAsync('lib --watch --config test-temp-rstack.config.ts'); + await logHelper.expectLog('build completed, watching for changes...'); + await waitForFile(path.join(dist1, 'index.js')); + logHelper.clearLogs(); + + await writeConfig('dist-2'); + + await logHelper.expectLog('restarting build as test-temp-rstack.config.ts changed'); + await logHelper.expectLog('build completed, watching for changes...'); + await waitForFile(path.join(dist2, 'index.js')); + logHelper.clearLogs(); + + await writeFile(userWatchFile, 'changed\n'); + + await logHelper.expectLog('restarting build as test-temp-user-watch.txt changed'); + await logHelper.expectLog('build completed, watching for changes...'); +}); + +test('should restart lib watch build when an imported config file changes', async ({ + prepareDist, + execCliAsync, + logHelper, +}) => { + const dist1 = await prepareDist('dist-import-1'); + const dist2 = await prepareDist('dist-import-2'); + const configFile = path.join(import.meta.dirname, 'test-temp-import.config.ts'); + const importedFile = path.join(import.meta.dirname, 'test-temp-imported.ts'); + + await writeFile(importedFile, "export const distPath = 'dist-import-1';\n"); + await writeFile( + configFile, + `import { define } from 'rstack'; +import { distPath } from './test-temp-imported.ts'; + +define.lib({ + output: { + distPath, + }, +}); +`, + ); + + execCliAsync('lib --watch --config test-temp-import.config.ts'); + await logHelper.expectLog('build completed, watching for changes...'); + await waitForFile(path.join(dist1, 'index.js')); + logHelper.clearLogs(); + + await writeFile(importedFile, "export const distPath = 'dist-import-2';\n"); + + await logHelper.expectLog('restarting build as test-temp-imported.ts changed'); + await logHelper.expectLog('build completed, watching for changes...'); + await waitForFile(path.join(dist2, 'index.js')); +}); diff --git a/packages/rstack/tests/config/reload-lib-config/package.json b/packages/rstack/tests/config/reload-lib-config/package.json new file mode 100644 index 00000000..e986b24b --- /dev/null +++ b/packages/rstack/tests/config/reload-lib-config/package.json @@ -0,0 +1,4 @@ +{ + "private": true, + "type": "module" +} diff --git a/packages/rstack/tests/config/reload-lib-config/src/index.js b/packages/rstack/tests/config/reload-lib-config/src/index.js new file mode 100644 index 00000000..c62c9ec3 --- /dev/null +++ b/packages/rstack/tests/config/reload-lib-config/src/index.js @@ -0,0 +1 @@ +export const value = 'reload lib config'; From bfb48aab5a39564e3420136f37b7dad47dab42d6 Mon Sep 17 00:00:00 2001 From: Jiahan Chen Date: Fri, 14 Aug 2026 13:41:32 +0800 Subject: [PATCH 04/11] chore: align async config formatting (#361) --- packages/create-rstack/template-app-preact-ts/rstack.config.ts | 1 - packages/create-rstack/template-app-preact/rstack.config.js | 1 - packages/create-rstack/template-app-react-ts/rstack.config.ts | 1 - packages/create-rstack/template-app-react/rstack.config.js | 1 - packages/create-rstack/template-app-solid-ts/rstack.config.ts | 1 - packages/create-rstack/template-app-solid/rstack.config.js | 1 - packages/create-rstack/template-app-svelte-ts/rstack.config.ts | 1 - packages/create-rstack/template-app-svelte/rstack.config.js | 1 - packages/create-rstack/template-app-vue-ts/rstack.config.ts | 1 - packages/create-rstack/template-app-vue/rstack.config.js | 1 - packages/create-rstack/template-lib-react-ts/rstack.config.ts | 1 - packages/create-rstack/template-lib-react/rstack.config.js | 1 - packages/create-rstack/template-lib-solid-ts/rstack.config.ts | 2 -- packages/create-rstack/template-lib-solid/rstack.config.js | 2 -- packages/create-rstack/template-lib-svelte-ts/rstack.config.ts | 1 - packages/create-rstack/template-lib-svelte/rstack.config.js | 1 - packages/create-rstack/template-lib-vue-ts/rstack.config.ts | 1 - packages/create-rstack/template-lib-vue/rstack.config.js | 1 - website/docs/en/guide/configuration.mdx | 1 - website/docs/en/guide/monorepo.mdx | 1 - website/docs/zh/guide/configuration.mdx | 1 - website/docs/zh/guide/monorepo.mdx | 1 - 22 files changed, 24 deletions(-) diff --git a/packages/create-rstack/template-app-preact-ts/rstack.config.ts b/packages/create-rstack/template-app-preact-ts/rstack.config.ts index ee37cab6..e1ec7e4d 100644 --- a/packages/create-rstack/template-app-preact-ts/rstack.config.ts +++ b/packages/create-rstack/template-app-preact-ts/rstack.config.ts @@ -3,7 +3,6 @@ import { define } from 'rstack'; define.app(async () => { const { pluginPreact } = await import('@rsbuild/plugin-preact'); - return { plugins: [pluginPreact()], }; diff --git a/packages/create-rstack/template-app-preact/rstack.config.js b/packages/create-rstack/template-app-preact/rstack.config.js index 7959e79f..0c586e57 100644 --- a/packages/create-rstack/template-app-preact/rstack.config.js +++ b/packages/create-rstack/template-app-preact/rstack.config.js @@ -4,7 +4,6 @@ import { define } from 'rstack'; define.app(async () => { const { pluginPreact } = await import('@rsbuild/plugin-preact'); - return { plugins: [pluginPreact()], }; diff --git a/packages/create-rstack/template-app-react-ts/rstack.config.ts b/packages/create-rstack/template-app-react-ts/rstack.config.ts index 76da1357..b6214713 100644 --- a/packages/create-rstack/template-app-react-ts/rstack.config.ts +++ b/packages/create-rstack/template-app-react-ts/rstack.config.ts @@ -3,7 +3,6 @@ import { define } from 'rstack'; define.app(async () => { const { pluginReact } = await import('@rsbuild/plugin-react'); - return { plugins: [pluginReact()], }; diff --git a/packages/create-rstack/template-app-react/rstack.config.js b/packages/create-rstack/template-app-react/rstack.config.js index 9469b706..e01ac564 100644 --- a/packages/create-rstack/template-app-react/rstack.config.js +++ b/packages/create-rstack/template-app-react/rstack.config.js @@ -4,7 +4,6 @@ import { define } from 'rstack'; define.app(async () => { const { pluginReact } = await import('@rsbuild/plugin-react'); - return { plugins: [pluginReact()], }; diff --git a/packages/create-rstack/template-app-solid-ts/rstack.config.ts b/packages/create-rstack/template-app-solid-ts/rstack.config.ts index 0c2e91b5..17f42c04 100644 --- a/packages/create-rstack/template-app-solid-ts/rstack.config.ts +++ b/packages/create-rstack/template-app-solid-ts/rstack.config.ts @@ -4,7 +4,6 @@ import { define } from 'rstack'; define.app(async () => { const { pluginBabel } = await import('@rsbuild/plugin-babel'); const { pluginSolid } = await import('@rsbuild/plugin-solid'); - return { plugins: [ pluginBabel({ diff --git a/packages/create-rstack/template-app-solid/rstack.config.js b/packages/create-rstack/template-app-solid/rstack.config.js index ac5ced95..035c1408 100644 --- a/packages/create-rstack/template-app-solid/rstack.config.js +++ b/packages/create-rstack/template-app-solid/rstack.config.js @@ -5,7 +5,6 @@ import { define } from 'rstack'; define.app(async () => { const { pluginBabel } = await import('@rsbuild/plugin-babel'); const { pluginSolid } = await import('@rsbuild/plugin-solid'); - return { plugins: [ pluginBabel({ diff --git a/packages/create-rstack/template-app-svelte-ts/rstack.config.ts b/packages/create-rstack/template-app-svelte-ts/rstack.config.ts index b09e259c..aaf917a8 100644 --- a/packages/create-rstack/template-app-svelte-ts/rstack.config.ts +++ b/packages/create-rstack/template-app-svelte-ts/rstack.config.ts @@ -3,7 +3,6 @@ import { define } from 'rstack'; define.app(async () => { const { pluginSvelte } = await import('@rsbuild/plugin-svelte'); - return { plugins: [pluginSvelte()], }; diff --git a/packages/create-rstack/template-app-svelte/rstack.config.js b/packages/create-rstack/template-app-svelte/rstack.config.js index 6450bb86..a1efbfc7 100644 --- a/packages/create-rstack/template-app-svelte/rstack.config.js +++ b/packages/create-rstack/template-app-svelte/rstack.config.js @@ -4,7 +4,6 @@ import { define } from 'rstack'; define.app(async () => { const { pluginSvelte } = await import('@rsbuild/plugin-svelte'); - return { plugins: [pluginSvelte()], }; diff --git a/packages/create-rstack/template-app-vue-ts/rstack.config.ts b/packages/create-rstack/template-app-vue-ts/rstack.config.ts index e1133986..97e764c0 100644 --- a/packages/create-rstack/template-app-vue-ts/rstack.config.ts +++ b/packages/create-rstack/template-app-vue-ts/rstack.config.ts @@ -3,7 +3,6 @@ import { define } from 'rstack'; define.app(async () => { const { pluginVue } = await import('@rsbuild/plugin-vue'); - return { plugins: [pluginVue()], }; diff --git a/packages/create-rstack/template-app-vue/rstack.config.js b/packages/create-rstack/template-app-vue/rstack.config.js index 199e7340..8116b0df 100644 --- a/packages/create-rstack/template-app-vue/rstack.config.js +++ b/packages/create-rstack/template-app-vue/rstack.config.js @@ -4,7 +4,6 @@ import { define } from 'rstack'; define.app(async () => { const { pluginVue } = await import('@rsbuild/plugin-vue'); - return { plugins: [pluginVue()], }; diff --git a/packages/create-rstack/template-lib-react-ts/rstack.config.ts b/packages/create-rstack/template-lib-react-ts/rstack.config.ts index a6ef9b74..8c91acb5 100644 --- a/packages/create-rstack/template-lib-react-ts/rstack.config.ts +++ b/packages/create-rstack/template-lib-react-ts/rstack.config.ts @@ -3,7 +3,6 @@ import { define } from 'rstack'; define.lib(async () => { const { pluginReact } = await import('@rsbuild/plugin-react'); - return { bundle: false, dts: true, diff --git a/packages/create-rstack/template-lib-react/rstack.config.js b/packages/create-rstack/template-lib-react/rstack.config.js index 663d1744..12fc5223 100644 --- a/packages/create-rstack/template-lib-react/rstack.config.js +++ b/packages/create-rstack/template-lib-react/rstack.config.js @@ -4,7 +4,6 @@ import { define } from 'rstack'; define.lib(async () => { const { pluginReact } = await import('@rsbuild/plugin-react'); - return { bundle: false, source: { diff --git a/packages/create-rstack/template-lib-solid-ts/rstack.config.ts b/packages/create-rstack/template-lib-solid-ts/rstack.config.ts index 04122368..3fa4dc06 100644 --- a/packages/create-rstack/template-lib-solid-ts/rstack.config.ts +++ b/packages/create-rstack/template-lib-solid-ts/rstack.config.ts @@ -4,7 +4,6 @@ import { define } from 'rstack'; define.lib(async () => { const { pluginBabel } = await import('@rsbuild/plugin-babel'); const { pluginSolid } = await import('@rsbuild/plugin-solid'); - return { lib: [ { @@ -63,7 +62,6 @@ define.lib(async () => { define.test(async () => { const { pluginBabel } = await import('@rsbuild/plugin-babel'); const { pluginSolid } = await import('@rsbuild/plugin-solid'); - return { setupFiles: ['./tests/rstest.setup.ts'], plugins: [ diff --git a/packages/create-rstack/template-lib-solid/rstack.config.js b/packages/create-rstack/template-lib-solid/rstack.config.js index b1eb7d49..0e2a8f3e 100644 --- a/packages/create-rstack/template-lib-solid/rstack.config.js +++ b/packages/create-rstack/template-lib-solid/rstack.config.js @@ -5,7 +5,6 @@ import { define } from 'rstack'; define.lib(async () => { const { pluginBabel } = await import('@rsbuild/plugin-babel'); const { pluginSolid } = await import('@rsbuild/plugin-solid'); - return { lib: [ { @@ -63,7 +62,6 @@ define.lib(async () => { define.test(async () => { const { pluginBabel } = await import('@rsbuild/plugin-babel'); const { pluginSolid } = await import('@rsbuild/plugin-solid'); - return { setupFiles: ['./tests/rstest.setup.js'], plugins: [ diff --git a/packages/create-rstack/template-lib-svelte-ts/rstack.config.ts b/packages/create-rstack/template-lib-svelte-ts/rstack.config.ts index 71f23a4a..0d168520 100644 --- a/packages/create-rstack/template-lib-svelte-ts/rstack.config.ts +++ b/packages/create-rstack/template-lib-svelte-ts/rstack.config.ts @@ -4,7 +4,6 @@ import { svelteDtsPlugin } from './scripts/rslib-plugin-svelte-dts.ts'; define.lib(async () => { const { pluginSvelte } = await import('@rsbuild/plugin-svelte'); - return { bundle: false, source: { diff --git a/packages/create-rstack/template-lib-svelte/rstack.config.js b/packages/create-rstack/template-lib-svelte/rstack.config.js index 7b7c5f3c..d0ccb0f4 100644 --- a/packages/create-rstack/template-lib-svelte/rstack.config.js +++ b/packages/create-rstack/template-lib-svelte/rstack.config.js @@ -4,7 +4,6 @@ import { define } from 'rstack'; define.lib(async () => { const { pluginSvelte } = await import('@rsbuild/plugin-svelte'); - return { bundle: false, source: { diff --git a/packages/create-rstack/template-lib-vue-ts/rstack.config.ts b/packages/create-rstack/template-lib-vue-ts/rstack.config.ts index a70a064d..e441042e 100644 --- a/packages/create-rstack/template-lib-vue-ts/rstack.config.ts +++ b/packages/create-rstack/template-lib-vue-ts/rstack.config.ts @@ -3,7 +3,6 @@ import { define } from 'rstack'; define.lib(async () => { const { pluginVue } = await import('@rsbuild/plugin-vue'); - return { bundle: false, source: { diff --git a/packages/create-rstack/template-lib-vue/rstack.config.js b/packages/create-rstack/template-lib-vue/rstack.config.js index db019b91..cd42bd0d 100644 --- a/packages/create-rstack/template-lib-vue/rstack.config.js +++ b/packages/create-rstack/template-lib-vue/rstack.config.js @@ -4,7 +4,6 @@ import { define } from 'rstack'; define.lib(async () => { const { pluginVue } = await import('@rsbuild/plugin-vue'); - return { bundle: false, source: { diff --git a/website/docs/en/guide/configuration.mdx b/website/docs/en/guide/configuration.mdx index 49d110f6..9b3d8283 100644 --- a/website/docs/en/guide/configuration.mdx +++ b/website/docs/en/guide/configuration.mdx @@ -55,7 +55,6 @@ import { define } from 'rstack'; define.app(async () => { const { pluginReact } = await import('@rsbuild/plugin-react'); - return { plugins: [pluginReact()], }; diff --git a/website/docs/en/guide/monorepo.mdx b/website/docs/en/guide/monorepo.mdx index 152b8ddc..88c6cfce 100644 --- a/website/docs/en/guide/monorepo.mdx +++ b/website/docs/en/guide/monorepo.mdx @@ -109,7 +109,6 @@ import { define } from 'rstack'; define.app(async () => { const { pluginReact } = await import('@rsbuild/plugin-react'); - return { plugins: [pluginReact()], }; diff --git a/website/docs/zh/guide/configuration.mdx b/website/docs/zh/guide/configuration.mdx index 4f370874..dc3d8fde 100644 --- a/website/docs/zh/guide/configuration.mdx +++ b/website/docs/zh/guide/configuration.mdx @@ -55,7 +55,6 @@ import { define } from 'rstack'; define.app(async () => { const { pluginReact } = await import('@rsbuild/plugin-react'); - return { plugins: [pluginReact()], }; diff --git a/website/docs/zh/guide/monorepo.mdx b/website/docs/zh/guide/monorepo.mdx index ae93fee3..ab8671db 100644 --- a/website/docs/zh/guide/monorepo.mdx +++ b/website/docs/zh/guide/monorepo.mdx @@ -109,7 +109,6 @@ import { define } from 'rstack'; define.app(async () => { const { pluginReact } = await import('@rsbuild/plugin-react'); - return { plugins: [pluginReact()], }; From 41cc37174a2f7d1e0a604a7846703bada32268cc Mon Sep 17 00:00:00 2001 From: Jiahan Chen Date: Fri, 14 Aug 2026 14:01:33 +0800 Subject: [PATCH 05/11] refactor(test): update inline projects example (#362) --- .../rstest-inline-projects/rstack.config.ts | 26 ------------------ .../package.json | 2 +- .../test-inline-projects/rstack.config.ts | 27 +++++++++++++++++++ .../src/App.tsx | 0 .../src/index.tsx | 0 .../tests/dom.test.tsx | 0 .../tests/ssr.test.tsx | 0 .../tsconfig.json | 0 pnpm-lock.yaml | 24 ++++++++++++++--- website/docs/en/guide/testing.mdx | 2 +- website/docs/zh/guide/testing.mdx | 2 +- 11 files changed, 51 insertions(+), 32 deletions(-) delete mode 100644 examples/rstest-inline-projects/rstack.config.ts rename examples/{rstest-inline-projects => test-inline-projects}/package.json (92%) create mode 100644 examples/test-inline-projects/rstack.config.ts rename examples/{rstest-inline-projects => test-inline-projects}/src/App.tsx (100%) rename examples/{rstest-inline-projects => test-inline-projects}/src/index.tsx (100%) rename examples/{rstest-inline-projects => test-inline-projects}/tests/dom.test.tsx (100%) rename examples/{rstest-inline-projects => test-inline-projects}/tests/ssr.test.tsx (100%) rename examples/{rstest-inline-projects => test-inline-projects}/tsconfig.json (100%) diff --git a/examples/rstest-inline-projects/rstack.config.ts b/examples/rstest-inline-projects/rstack.config.ts deleted file mode 100644 index 501912cb..00000000 --- a/examples/rstest-inline-projects/rstack.config.ts +++ /dev/null @@ -1,26 +0,0 @@ -// Rstack configuration guide: https://rstack.rs/config -import { define } from 'rstack'; -import { defineInlineProject } from 'rstack/test'; - -define.app(async () => { - const { pluginReact } = await import('@rsbuild/plugin-react'); - - return { - plugins: [pluginReact()], - }; -}); - -define.test({ - projects: [ - defineInlineProject({ - name: 'ssr', - include: ['./tests/ssr.test.tsx'], - testEnvironment: 'node', - }), - defineInlineProject({ - name: 'dom', - include: ['./tests/dom.test.tsx'], - testEnvironment: 'happy-dom', - }), - ], -}); diff --git a/examples/rstest-inline-projects/package.json b/examples/test-inline-projects/package.json similarity index 92% rename from examples/rstest-inline-projects/package.json rename to examples/test-inline-projects/package.json index 4fea5d03..b1ddaad1 100644 --- a/examples/rstest-inline-projects/package.json +++ b/examples/test-inline-projects/package.json @@ -1,5 +1,5 @@ { - "name": "@examples/rstest-inline-projects", + "name": "@examples/test-inline-projects", "private": true, "type": "module", "scripts": { diff --git a/examples/test-inline-projects/rstack.config.ts b/examples/test-inline-projects/rstack.config.ts new file mode 100644 index 00000000..12fa4e57 --- /dev/null +++ b/examples/test-inline-projects/rstack.config.ts @@ -0,0 +1,27 @@ +// Rstack configuration guide: https://rstack.rs/config +import { define } from 'rstack'; + +define.app(async () => { + const { pluginReact } = await import('@rsbuild/plugin-react'); + return { + plugins: [pluginReact()], + }; +}); + +define.test(async () => { + const { defineInlineProject } = await import('rstack/test'); + return { + projects: [ + defineInlineProject({ + name: 'ssr', + include: ['./tests/ssr.test.tsx'], + testEnvironment: 'node', + }), + defineInlineProject({ + name: 'dom', + include: ['./tests/dom.test.tsx'], + testEnvironment: 'happy-dom', + }), + ], + }; +}); diff --git a/examples/rstest-inline-projects/src/App.tsx b/examples/test-inline-projects/src/App.tsx similarity index 100% rename from examples/rstest-inline-projects/src/App.tsx rename to examples/test-inline-projects/src/App.tsx diff --git a/examples/rstest-inline-projects/src/index.tsx b/examples/test-inline-projects/src/index.tsx similarity index 100% rename from examples/rstest-inline-projects/src/index.tsx rename to examples/test-inline-projects/src/index.tsx diff --git a/examples/rstest-inline-projects/tests/dom.test.tsx b/examples/test-inline-projects/tests/dom.test.tsx similarity index 100% rename from examples/rstest-inline-projects/tests/dom.test.tsx rename to examples/test-inline-projects/tests/dom.test.tsx diff --git a/examples/rstest-inline-projects/tests/ssr.test.tsx b/examples/test-inline-projects/tests/ssr.test.tsx similarity index 100% rename from examples/rstest-inline-projects/tests/ssr.test.tsx rename to examples/test-inline-projects/tests/ssr.test.tsx diff --git a/examples/rstest-inline-projects/tsconfig.json b/examples/test-inline-projects/tsconfig.json similarity index 100% rename from examples/rstest-inline-projects/tsconfig.json rename to examples/test-inline-projects/tsconfig.json diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 014fe63a..2e29deea 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -309,7 +309,7 @@ importers: specifier: 'catalog:' version: 7.0.2 - examples/rstest-inline-projects: + examples/test-inline-projects: dependencies: react: specifier: 'catalog:' @@ -320,7 +320,7 @@ importers: devDependencies: '@rsbuild/plugin-react': specifier: 'catalog:' - version: 2.1.0(@rsbuild/core@2.1.10)(@rspack/core@2.1.10) + version: 2.1.0(@rsbuild/core@2.1.12)(@rspack/core@2.1.10) '@testing-library/dom': specifier: 'catalog:' version: 10.4.1 @@ -3988,6 +3988,15 @@ snapshots: transitivePeerDependencies: - '@module-federation/runtime-tools' + '@rsbuild/plugin-react@2.1.0(@rsbuild/core@2.1.10)': + dependencies: + '@rspack/plugin-react-refresh': 2.0.2(@rspack/core@2.1.10)(react-refresh@0.18.0) + react-refresh: 0.18.0 + optionalDependencies: + '@rsbuild/core': 2.1.10 + transitivePeerDependencies: + - '@rspack/core' + '@rsbuild/plugin-react@2.1.0(@rsbuild/core@2.1.10)(@rspack/core@2.1.10)': dependencies: '@rspack/plugin-react-refresh': 2.0.2(@rspack/core@2.1.10)(react-refresh@0.18.0) @@ -3997,6 +4006,15 @@ snapshots: transitivePeerDependencies: - '@rspack/core' + '@rsbuild/plugin-react@2.1.0(@rsbuild/core@2.1.12)(@rspack/core@2.1.10)': + dependencies: + '@rspack/plugin-react-refresh': 2.0.2(@rspack/core@2.1.10)(react-refresh@0.18.0) + react-refresh: 0.18.0 + optionalDependencies: + '@rsbuild/core': 2.1.12 + transitivePeerDependencies: + - '@rspack/core' + '@rsbuild/plugin-sass@2.0.1(@rsbuild/core@2.1.10)': dependencies: deepmerge: 4.3.1 @@ -4264,7 +4282,7 @@ snapshots: '@mdx-js/mdx': 3.1.1(supports-color@8.1.1) '@mdx-js/react': 3.1.1(@types/react@19.2.18)(react@19.2.8) '@rsbuild/core': 2.1.10 - '@rsbuild/plugin-react': 2.1.0(@rsbuild/core@2.1.10)(@rspack/core@2.1.10) + '@rsbuild/plugin-react': 2.1.0(@rsbuild/core@2.1.10) '@rspress/shared': 2.0.19(supports-color@8.1.1) '@shikijs/rehype': 4.3.1 '@types/mdast': 4.0.4 diff --git a/website/docs/en/guide/testing.mdx b/website/docs/en/guide/testing.mdx index aa4a442a..5fcb5494 100644 --- a/website/docs/en/guide/testing.mdx +++ b/website/docs/en/guide/testing.mdx @@ -86,7 +86,7 @@ Run one project by name: rs test --project dom ``` -See [`examples/rstest-inline-projects`](https://github.com/rstackjs/rstack-cli/tree/main/examples/rstest-inline-projects) for a complete React SSR example using Node.js and happy-dom. +See [`examples/test-inline-projects`](https://github.com/rstackjs/rstack-cli/tree/main/examples/test-inline-projects) for a complete React SSR example using Node.js and happy-dom. ### External projects diff --git a/website/docs/zh/guide/testing.mdx b/website/docs/zh/guide/testing.mdx index 9c629635..fd969ad2 100644 --- a/website/docs/zh/guide/testing.mdx +++ b/website/docs/zh/guide/testing.mdx @@ -86,7 +86,7 @@ Rstack CLI 会将对应的适配器应用到每个未设置 `extends` 的内联 rs test --project dom ``` -完整的 React SSR 示例请参阅 [`examples/rstest-inline-projects`](https://github.com/rstackjs/rstack-cli/tree/main/examples/rstest-inline-projects),该示例使用 Node.js 和 happy-dom 两种测试环境。 +完整的 React SSR 示例请参阅 [`examples/test-inline-projects`](https://github.com/rstackjs/rstack-cli/tree/main/examples/test-inline-projects),该示例使用 Node.js 和 happy-dom 两种测试环境。 ### 外部项目 \{#external-projects} From dd3a04a930c59de6a8f24a5dfa76f60c99af16e8 Mon Sep 17 00:00:00 2001 From: Jiahan Chen Date: Fri, 14 Aug 2026 14:53:29 +0800 Subject: [PATCH 06/11] refactor(create-rstack): remove redundant test configs (#363) --- packages/create-rstack/template-app-lit-ts/rstack.config.ts | 4 ---- packages/create-rstack/template-app-lit/rstack.config.js | 4 ---- packages/create-rstack/template-lib-node-ts/rstack.config.ts | 4 ---- packages/create-rstack/template-lib-node/rstack.config.js | 4 ---- .../create-rstack/template-lib-svelte-ts/rstack.config.ts | 4 ---- packages/create-rstack/template-lib-svelte/rstack.config.js | 4 ---- 6 files changed, 24 deletions(-) diff --git a/packages/create-rstack/template-app-lit-ts/rstack.config.ts b/packages/create-rstack/template-app-lit-ts/rstack.config.ts index 84681d01..d7f664c7 100644 --- a/packages/create-rstack/template-app-lit-ts/rstack.config.ts +++ b/packages/create-rstack/template-app-lit-ts/rstack.config.ts @@ -12,10 +12,6 @@ define.app({ }, }); -define.test({ - testEnvironment: 'happy-dom', -}); - define.lint(({ js, ts }) => [ js.configs.recommended, ts.configs.recommendedTypeChecked, diff --git a/packages/create-rstack/template-app-lit/rstack.config.js b/packages/create-rstack/template-app-lit/rstack.config.js index 5b863543..4a74583c 100644 --- a/packages/create-rstack/template-app-lit/rstack.config.js +++ b/packages/create-rstack/template-app-lit/rstack.config.js @@ -13,10 +13,6 @@ define.app({ }, }); -define.test({ - testEnvironment: 'happy-dom', -}); - define.lint(({ js }) => [js.configs.recommended]); define.fmt({ diff --git a/packages/create-rstack/template-lib-node-ts/rstack.config.ts b/packages/create-rstack/template-lib-node-ts/rstack.config.ts index 28d8d881..76a7cb35 100644 --- a/packages/create-rstack/template-lib-node-ts/rstack.config.ts +++ b/packages/create-rstack/template-lib-node-ts/rstack.config.ts @@ -6,10 +6,6 @@ define.lib({ dts: true, }); -define.test({ - // Configure Rstest -}); - define.lint(({ js, ts }) => [ js.configs.recommended, ts.configs.recommendedTypeChecked, diff --git a/packages/create-rstack/template-lib-node/rstack.config.js b/packages/create-rstack/template-lib-node/rstack.config.js index f193d432..047c2d6c 100644 --- a/packages/create-rstack/template-lib-node/rstack.config.js +++ b/packages/create-rstack/template-lib-node/rstack.config.js @@ -6,10 +6,6 @@ define.lib({ syntax: ['node 22'], }); -define.test({ - // Configure Rstest -}); - define.lint(({ js }) => [js.configs.recommended]); define.fmt({ diff --git a/packages/create-rstack/template-lib-svelte-ts/rstack.config.ts b/packages/create-rstack/template-lib-svelte-ts/rstack.config.ts index 0d168520..b74a4f81 100644 --- a/packages/create-rstack/template-lib-svelte-ts/rstack.config.ts +++ b/packages/create-rstack/template-lib-svelte-ts/rstack.config.ts @@ -18,10 +18,6 @@ define.lib(async () => { }; }); -define.test({ - testEnvironment: 'happy-dom', -}); - define.lint(({ js, ts }) => [ js.configs.recommended, ts.configs.recommendedTypeChecked, diff --git a/packages/create-rstack/template-lib-svelte/rstack.config.js b/packages/create-rstack/template-lib-svelte/rstack.config.js index d0ccb0f4..4bbb928c 100644 --- a/packages/create-rstack/template-lib-svelte/rstack.config.js +++ b/packages/create-rstack/template-lib-svelte/rstack.config.js @@ -18,10 +18,6 @@ define.lib(async () => { }; }); -define.test({ - testEnvironment: 'happy-dom', -}); - define.lint(({ js }) => [js.configs.recommended]); define.fmt({ From cba6e5c35fb0c3225314327adfacfa14fdc109b1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 14 Aug 2026 18:03:41 +0000 Subject: [PATCH 07/11] fix(deps): update all non-major dependencies (#364) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .../template-app-svelte-ts/package.json | 4 +- .../template-app-svelte/package.json | 2 +- .../template-lib-svelte-ts/package.json | 6 +- .../template-lib-svelte/package.json | 2 +- pnpm-lock.yaml | 397 ++++++------------ pnpm-workspace.yaml | 14 +- rust-toolchain.toml | 2 +- 7 files changed, 133 insertions(+), 294 deletions(-) diff --git a/packages/create-rstack/template-app-svelte-ts/package.json b/packages/create-rstack/template-app-svelte-ts/package.json index 63910506..eeafeb60 100644 --- a/packages/create-rstack/template-app-svelte-ts/package.json +++ b/packages/create-rstack/template-app-svelte-ts/package.json @@ -14,7 +14,7 @@ "test:watch": "rs test --watch" }, "dependencies": { - "svelte": "^5.56.8" + "svelte": "^5.56.9" }, "devDependencies": { "@rsbuild/plugin-svelte": "^2.0.1", @@ -24,7 +24,7 @@ "happy-dom": "^20.11.2", "prettier-plugin-svelte": "^4.1.1", "rstack": "^0.6.1", - "svelte-check": "^4.7.5", + "svelte-check": "^4.7.6", "typescript": "^6.0.3" } } diff --git a/packages/create-rstack/template-app-svelte/package.json b/packages/create-rstack/template-app-svelte/package.json index e48dff5e..a0ba16f1 100644 --- a/packages/create-rstack/template-app-svelte/package.json +++ b/packages/create-rstack/template-app-svelte/package.json @@ -14,7 +14,7 @@ "test:watch": "rs test --watch" }, "dependencies": { - "svelte": "^5.56.8" + "svelte": "^5.56.9" }, "devDependencies": { "@rsbuild/plugin-svelte": "^2.0.1", diff --git a/packages/create-rstack/template-lib-svelte-ts/package.json b/packages/create-rstack/template-lib-svelte-ts/package.json index 4241ba47..15ad5f7f 100644 --- a/packages/create-rstack/template-lib-svelte-ts/package.json +++ b/packages/create-rstack/template-lib-svelte-ts/package.json @@ -28,9 +28,9 @@ "happy-dom": "^20.11.2", "prettier-plugin-svelte": "^4.1.1", "rstack": "^0.6.1", - "svelte": "^5.56.8", - "svelte-check": "^4.7.5", - "svelte2tsx": "^0.7.60", + "svelte": "^5.56.9", + "svelte-check": "^4.7.6", + "svelte2tsx": "^0.7.61", "typescript": "^6.0.3" }, "peerDependencies": { diff --git a/packages/create-rstack/template-lib-svelte/package.json b/packages/create-rstack/template-lib-svelte/package.json index b03e95af..5756c287 100644 --- a/packages/create-rstack/template-lib-svelte/package.json +++ b/packages/create-rstack/template-lib-svelte/package.json @@ -25,7 +25,7 @@ "happy-dom": "^20.11.2", "prettier-plugin-svelte": "^4.1.1", "rstack": "^0.6.1", - "svelte": "^5.56.8" + "svelte": "^5.56.9" }, "peerDependencies": { "svelte": "^5.0.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2e29deea..13440900 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,8 +11,8 @@ catalogs: specifier: ^3.8.6 version: 3.8.6 '@rsbuild/core': - specifier: ~2.1.11 - version: 2.1.11 + specifier: ~2.1.13 + version: 2.1.13 '@rsbuild/plugin-react': specifier: ^2.1.0 version: 2.1.0 @@ -47,14 +47,14 @@ catalogs: specifier: ^0.2.0 version: 0.2.0 '@rstest/adapter-rsbuild': - specifier: ~0.11.6 - version: 0.11.6 + specifier: ~0.11.8 + version: 0.11.8 '@rstest/adapter-rslib': - specifier: ~0.11.6 - version: 0.11.6 + specifier: ~0.11.8 + version: 0.11.8 '@rstest/core': - specifier: ~0.11.6 - version: 0.11.6 + specifier: ~0.11.8 + version: 0.11.8 '@shikijs/transformers': specifier: ^4.4.3 version: 4.4.3 @@ -86,8 +86,8 @@ catalogs: specifier: 2.1.0 version: 2.1.0 globals: - specifier: ^17.10.0 - version: 17.10.0 + specifier: ^17.11.0 + version: 17.11.0 happy-dom: specifier: ^20.11.2 version: 20.11.2 @@ -131,8 +131,8 @@ catalogs: specifier: 4.0.0 version: 4.0.0 svelte: - specifier: ^5.56.8 - version: 5.56.8 + specifier: ^5.56.9 + version: 5.56.9 tiny-readdir: specifier: 3.1.1 version: 3.1.1 @@ -149,8 +149,8 @@ catalogs: specifier: 1.0.12 version: 1.0.12 yuku-parser: - specifier: 0.8.5 - version: 0.8.5 + specifier: 0.8.7 + version: 0.8.7 importers: @@ -164,7 +164,7 @@ importers: version: 0.0.4 globals: specifier: 'catalog:' - version: 17.10.0 + version: 17.11.0 heading-case: specifier: 'catalog:' version: 1.1.5 @@ -320,7 +320,7 @@ importers: devDependencies: '@rsbuild/plugin-react': specifier: 'catalog:' - version: 2.1.0(@rsbuild/core@2.1.12)(@rspack/core@2.1.10) + version: 2.1.0(@rsbuild/core@2.1.13)(@rspack/core@2.1.10) '@testing-library/dom': specifier: 'catalog:' version: 10.4.1 @@ -366,7 +366,7 @@ importers: dependencies: '@rsbuild/core': specifier: 'catalog:' - version: 2.1.11 + version: 2.1.13 '@rslib/core': specifier: 'catalog:' version: 1.0.0-beta.3(typescript@7.0.2) @@ -375,7 +375,7 @@ importers: version: 0.8.0 '@rstest/core': specifier: 'catalog:' - version: 0.11.6(happy-dom@20.11.2) + version: 0.11.8(happy-dom@20.11.2) prettier: specifier: 'catalog:' version: 3.9.6 @@ -384,7 +384,7 @@ importers: version: 2.1.0 yuku-parser: specifier: 'catalog:' - version: 0.8.5 + version: 0.8.7 devDependencies: '@napi-rs/cli': specifier: 'catalog:' @@ -400,10 +400,10 @@ importers: version: 0.2.0 '@rstest/adapter-rsbuild': specifier: 'catalog:' - version: 0.11.6(@rsbuild/core@2.1.11)(@rstest/core@0.11.6) + version: 0.11.8(@rsbuild/core@2.1.13)(@rstest/core@0.11.8) '@rstest/adapter-rslib': specifier: 'catalog:' - version: 0.11.6(@rslib/core@1.0.0-beta.3)(@rstest/core@0.11.6)(typescript@7.0.2) + version: 0.11.8(@rslib/core@1.0.0-beta.3)(@rstest/core@0.11.8)(typescript@7.0.2) '@types/micromatch': specifier: 'catalog:' version: 4.0.10 @@ -427,7 +427,7 @@ importers: version: 4.0.8 prettier-plugin-svelte: specifier: 'catalog:' - version: 4.1.1(prettier@3.9.6)(svelte@5.56.8) + version: 4.1.1(prettier@3.9.6)(svelte@5.56.9) rslog: specifier: 'catalog:' version: 2.3.0 @@ -436,7 +436,7 @@ importers: version: 4.0.0 svelte: specifier: 'catalog:' - version: 5.56.8 + version: 5.56.9 tiny-readdir: specifier: 'catalog:' version: 3.1.1 @@ -1281,8 +1281,8 @@ packages: core-js: optional: true - '@rsbuild/core@2.1.11': - resolution: {integrity: sha512-jA/QwZu8wIljp70TjERVoX+vk2cWU+viHpV9EAdKpA6ifu/pFnThEhbV3RFxBvF/9mB3h7ZRUfdDIyknT+9MWA==} + '@rsbuild/core@2.1.12': + resolution: {integrity: sha512-xRqNHj/svDqeUzXPahmN4BdxEFCU1rxnVdjxyVV7WgsFfH+L3yAoQMJtIXVGhr00IQseDKkc3eonMF3NGrFj2Q==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -1291,8 +1291,8 @@ packages: core-js: optional: true - '@rsbuild/core@2.1.12': - resolution: {integrity: sha512-xRqNHj/svDqeUzXPahmN4BdxEFCU1rxnVdjxyVV7WgsFfH+L3yAoQMJtIXVGhr00IQseDKkc3eonMF3NGrFj2Q==} + '@rsbuild/core@2.1.13': + resolution: {integrity: sha512-Z+6MzmjOio4+bFZQ24k+7ge/oNCOdXIunAssrswTNE8AIf6mcyXpJZevRXRiOEMZasRPA8VNyh+9JngQLg729Q==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -1393,11 +1393,6 @@ packages: cpu: [arm64] os: [darwin] - '@rspack/binding-darwin-arm64@2.1.9': - resolution: {integrity: sha512-sQQgKx+1ckW5GI6w/ozJkoaQM0Skbwj7hFiv+Y2d7+iAB7OVz83LWFrodRl1QGCg1QNuaEmlVo9AUapWUSr/8g==} - cpu: [arm64] - os: [darwin] - '@rspack/binding-darwin-x64@2.1.10': resolution: {integrity: sha512-my/0h2LwxCRT6cg3oDDC2e0ZOxQLVajAdIcv0fqnQk5JRNvVuL89PuTutitnSqie1A0/JSL8OQz5XHwmoS3kow==} cpu: [x64] @@ -1408,11 +1403,6 @@ packages: cpu: [x64] os: [darwin] - '@rspack/binding-darwin-x64@2.1.9': - resolution: {integrity: sha512-kuWzn8JFKJUxSFwX/+rzvZUm4fRrSbXCTCAlzb0iHvP6vftjCFHGpNJfWjD7yX9O7C/dtoGiNT1O1veJytVsWA==} - cpu: [x64] - os: [darwin] - '@rspack/binding-linux-arm64-gnu@2.1.10': resolution: {integrity: sha512-laevn9g+E5PAUEGqiKe6Ju5KApsuQYp+bPI17XS3Lkl8eqL5pS/BmHYU7QMlst4GzV8+wlruVTMh//+st6Vqzg==} cpu: [arm64] @@ -1425,12 +1415,6 @@ packages: os: [linux] libc: [glibc] - '@rspack/binding-linux-arm64-gnu@2.1.9': - resolution: {integrity: sha512-rj1TjWGuG9Zc+fmwfvOpRO9npuHMKE4xXSB/BZqZNcH5Uey4NcAo1/GF8zHRoalQrwf0roP9WsFX1tk85rzP/Q==} - cpu: [arm64] - os: [linux] - libc: [glibc] - '@rspack/binding-linux-arm64-musl@2.1.10': resolution: {integrity: sha512-V71+Qz5G72+ROZXrJn5zxOszdG1AEbO8pcC/itXXtf4yRR6a3bVHKNKGhipBNxb8eI6cnD/01FH1h3ZG655jLw==} cpu: [arm64] @@ -1443,24 +1427,12 @@ packages: os: [linux] libc: [musl] - '@rspack/binding-linux-arm64-musl@2.1.9': - resolution: {integrity: sha512-Z2+sS2z9Imt3og0e3Kq4hEumiqTajQBXkl9cqSYj1lwOgmViLAvMX4BlCL1cinIEstixFKQ+xsta9SI6ivCKtg==} - cpu: [arm64] - os: [linux] - libc: [musl] - '@rspack/binding-linux-ppc64-gnu@2.1.10': resolution: {integrity: sha512-U7HlNzHcDtZ+LYOtOJmtx67kHEybZzUUAaP7aEXjGYO5WTCgh/176sW2UYP0rmZLrgUNFUuzn+B98RLaClNaVg==} cpu: [ppc64] os: [linux] libc: [glibc] - '@rspack/binding-linux-ppc64-gnu@2.1.9': - resolution: {integrity: sha512-xK90IHipRDgxvDF9n90HRNklci0G2Amk0ZMsM3t3YX+/8jIJNcuEPk6hJ1hlY6KZu7U9enqGbNQ7Fe0StBeLVA==} - cpu: [ppc64] - os: [linux] - libc: [glibc] - '@rspack/binding-linux-riscv64-gnu@2.1.10': resolution: {integrity: sha512-GMGTJpy9/ecE+5F5IfxZH4bXv0Wx/b2TiehTlCbTksbL+pKpLHYy0rwGdjWDKbmBkhxMMqPiC7PDnn9LbdnnLA==} cpu: [riscv64] @@ -1473,12 +1445,6 @@ packages: os: [linux] libc: [glibc] - '@rspack/binding-linux-riscv64-gnu@2.1.9': - resolution: {integrity: sha512-aaOwU3voq20Vwmfu1V6UPz8eQJBitBUNbLc08dxHGsmQM+ap6dd4j5xn/i5Y6AfLro2Q3GJvpCayc+dVIOR32g==} - cpu: [riscv64] - os: [linux] - libc: [glibc] - '@rspack/binding-linux-riscv64-musl@2.1.10': resolution: {integrity: sha512-rkurnAWc04vIbzG1QCrPBWSJadZvaOt1mazFH3EdiJO8VUiu0I1T9zdiwuDOPrd50lOKIZlcTXbd5aaAkWEnvQ==} cpu: [riscv64] @@ -1491,24 +1457,12 @@ packages: os: [linux] libc: [musl] - '@rspack/binding-linux-riscv64-musl@2.1.9': - resolution: {integrity: sha512-tF7XnEtpTYyVS8ef96IKUjFhd9lFa9Swv212fcyWxRhxRn4PEYVWpSh/D3NDVX+i69Z7xFDDEoyMUdYBkkVTlw==} - cpu: [riscv64] - os: [linux] - libc: [musl] - '@rspack/binding-linux-s390x-gnu@2.1.10': resolution: {integrity: sha512-X+DyxkriZEAF/wihI7ERDv+CAS0mbMv36aEuQ+vXzTlvS6cSmpou/r29AHbvIF3NlG1UeAbDVlOs9QrMBZjpUQ==} cpu: [s390x] os: [linux] libc: [glibc] - '@rspack/binding-linux-s390x-gnu@2.1.9': - resolution: {integrity: sha512-rGmeME3Pd8k/55txP+19ceZJxhVN2mtC8TLzB1ycTrhy8U+ZnN7J8rZSl89LYrZGYewd1UmOcY0QKKy05FS3FA==} - cpu: [s390x] - os: [linux] - libc: [glibc] - '@rspack/binding-linux-x64-gnu@2.1.10': resolution: {integrity: sha512-Fat09V6jUuyo9qG7Wyj9cQ31VDfLmokXyBtGqKxY5OvSWHereB7QUub5btbPXHwbp6Iq4aAQyUbbLTzvR1YaBw==} cpu: [x64] @@ -1521,12 +1475,6 @@ packages: os: [linux] libc: [glibc] - '@rspack/binding-linux-x64-gnu@2.1.9': - resolution: {integrity: sha512-YimUCS//wl+AZjXvgVig7sJtPiGs+WNMH4g49F1msB5T40rw0aOopp9O3MdC+LFfRm6vpIJwOHd6alo/UUs7nA==} - cpu: [x64] - os: [linux] - libc: [glibc] - '@rspack/binding-linux-x64-musl@2.1.10': resolution: {integrity: sha512-lhHOnIJ4ClpIlA1f1L8aoxEZivYLjnjq5A6jKKz7BKsm+cHK8kqqEm6lO5KqA5xQT0Lonq1o28bmKHEj6JHInw==} cpu: [x64] @@ -1539,12 +1487,6 @@ packages: os: [linux] libc: [musl] - '@rspack/binding-linux-x64-musl@2.1.9': - resolution: {integrity: sha512-tYJRdoB3IWVVcnGPZqCnRjC5MJle7xHucKkIuKtGbSMS6hzg6B1oKZav1BBw72gxnrW4n2Bwt82TBKQKBUSjmA==} - cpu: [x64] - os: [linux] - libc: [musl] - '@rspack/binding-wasm32-wasi@2.1.10': resolution: {integrity: sha512-KY5YbWbuvYcoaLXnV+vzZOvGRCeb6jt4EpVpKdph1h1IJjwX/ju15EQ+GOe3iecZEdf0OttQcNVcwBkLkFT9ag==} cpu: [wasm32] @@ -1553,10 +1495,6 @@ packages: resolution: {integrity: sha512-om7GAKWAU3lcSvbCon2m7mzw8v9OTrO2LW2MZ1lGe/uVJJmwGGkl9HVoXFyWFLrN6YVFyx8iP+AkN4owDWB9Cw==} cpu: [wasm32] - '@rspack/binding-wasm32-wasi@2.1.9': - resolution: {integrity: sha512-TF6oZRU23x6vHzGuvRFszvEnmC5Yn8PHbKmeZWsUHj4Mtv4tDdDGVdlxj6Kq3pySS6sRknv8gzpRMhXqHD3I5g==} - cpu: [wasm32] - '@rspack/binding-win32-arm64-msvc@2.1.10': resolution: {integrity: sha512-z4GWzMLofaDGpAt9Z+MlN88LlUBDm+zM6R2GdOOPM6/4g/h3/+47OP7casmSL3AwTGYBEJqogwt08sRSosB6Cg==} cpu: [arm64] @@ -1567,11 +1505,6 @@ packages: cpu: [arm64] os: [win32] - '@rspack/binding-win32-arm64-msvc@2.1.9': - resolution: {integrity: sha512-tLt4gbvUelmtJGI3PHtQHZuGpaO2z/ym6+OXAjc6NR2jWbzljardSjONiXVWIi1zmzODt4dD/fL3Ncb+RU/jHQ==} - cpu: [arm64] - os: [win32] - '@rspack/binding-win32-ia32-msvc@2.1.10': resolution: {integrity: sha512-7qcWdsZ+GuGtzKjqgy7wTN7Dso/ezIY8yhx1r2yIbcczdmXj4FhaEampMDp/25HwtKwIGBBoh6HHSt3JWxpTUg==} cpu: [ia32] @@ -1582,11 +1515,6 @@ packages: cpu: [ia32] os: [win32] - '@rspack/binding-win32-ia32-msvc@2.1.9': - resolution: {integrity: sha512-VznxSrGPb4mvxkTHTdGolEpBOuDW7RICD9Rp266MhYLQG4c0uNcX7+vWF4HbFvB0kN+Gdkj7vz+5shWdrfK72Q==} - cpu: [ia32] - os: [win32] - '@rspack/binding-win32-x64-msvc@2.1.10': resolution: {integrity: sha512-pgp23pLrzfhGnKycxzr7ifP17lAbWZEfnx1bX8gXtYrnpJ66DRNyTKSzxB6sa/HBWjS1L8PX5TjMZ44WfPydqQ==} cpu: [x64] @@ -1597,20 +1525,12 @@ packages: cpu: [x64] os: [win32] - '@rspack/binding-win32-x64-msvc@2.1.9': - resolution: {integrity: sha512-P1dGC6t3PJinqN6tBZ+jyou4LvwpD2ygeovKgg5tuYWKFMUwh1v60yX3afOUwaJMEGbHUye7xuYRd84NCMSNOQ==} - cpu: [x64] - os: [win32] - '@rspack/binding@2.1.10': resolution: {integrity: sha512-vnu/UP5HnrND15lO9+VeG6eUrbTyycHNQNQ3XEiRiFojuoiGZkIZC3Hbzr8qQH44C6vScPODEPvvIVvLcO2LpQ==} '@rspack/binding@2.1.8': resolution: {integrity: sha512-tmAyHzDbPiy8V7HvQqtuPsbs6dPgwV0YjzW5XrPRV9gzf+Hdm7pvsZJKE1QKO9WV5RuvGYav98xIX6O+abZxzQ==} - '@rspack/binding@2.1.9': - resolution: {integrity: sha512-0ZZj+RE62/jFRwX5czqjjGiMGgzSK0hm7/66PtCKjyZjb2tNAg3WGyWv+ref7684ZAjtbHHpZ+EOGswQYBjklA==} - '@rspack/core@2.1.10': resolution: {integrity: sha512-YSS2/Xxz8uiG/KXDkqOoA3dTetNo/vysk7bAexQOrU8iuq7JuzDTTAwLKvWZnwmvME8M8m5wcM4YvfIwYmidHA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1635,18 +1555,6 @@ packages: '@swc/helpers': optional: true - '@rspack/core@2.1.9': - resolution: {integrity: sha512-kXd5aYrkO+91fYolNYAjUhW/1F9kGmw7PtneNRY6z3KK6ttJgQWMVNypiCkhK3TOcyWPGH42qd0bzHZlH72JaA==} - engines: {node: ^20.19.0 || >=22.12.0} - peerDependencies: - '@module-federation/runtime-tools': ^0.24.1 || ^2.0.0 - '@swc/helpers': ^0.5.23 - peerDependenciesMeta: - '@module-federation/runtime-tools': - optional: true - '@swc/helpers': - optional: true - '@rspack/plugin-react-refresh@2.0.2': resolution: {integrity: sha512-dGNZiCxQxgAUI9sah7gd8u+O7OJZRCmqtEJNDOd8xW5RqcieC86F7p5qcShyw6onH5pKf57evpr2VjGbaFGkZg==} peerDependencies: @@ -1699,14 +1607,14 @@ packages: '@rstackjs/test-utils@0.2.0': resolution: {integrity: sha512-P+LOo1WE3xYeGkHmEthyq2cIpN69k4LhiB/4UBSceD+nW9hDlhWv8MC0LTLWokZXccWl4ntcfOBjQFllkcBlPA==} - '@rstest/adapter-rsbuild@0.11.6': - resolution: {integrity: sha512-l2bKftH1IEuY3Sj7ZEb+k6OoZf2FO0vTeKfk1Xxo2ons9fL1LHsbNDWEXNw9lNHg0fv92sai6ygQkGkvCpxkjg==} + '@rstest/adapter-rsbuild@0.11.8': + resolution: {integrity: sha512-FIpljMHWjsZzWTBkGqIuvtPFj3ru1SL5FGhMGtvyGjFi126SwCcVHHTIF5hsrs8Ou8vFEF8S5eFxz7hXrzvxKg==} peerDependencies: '@rsbuild/core': ^1.0.0 || ^2.0.0 '@rstest/core': ^0.11.0 - '@rstest/adapter-rslib@0.11.6': - resolution: {integrity: sha512-0NOU3W63TWtbWExgT/gvpbQ5ZtWqW5HepvJ/mNne7FgZfjm2bNwDF2Saglpg/M83KuBpA9xmnrOUQXNosJkCBQ==} + '@rstest/adapter-rslib@0.11.8': + resolution: {integrity: sha512-PnRrCgTbRH+sFuS/6ZbhDAUEl/n0PkhmzQJxZCYQQEl3w+jGOrLQSAMgh9g/Z2XGM1yfbysn+5HZmGH2kL+E6w==} peerDependencies: '@rslib/core': '>=0.18.6 || ^1.0.0-0' '@rstest/core': ^0.11.0 @@ -1715,8 +1623,8 @@ packages: typescript: optional: true - '@rstest/core@0.11.6': - resolution: {integrity: sha512-P3wgYGDF3JmhapwN3p4DnbNV9N6+E+dlbp0coT1lAuXN5Po6bHeP/rduGLsTUIX85qWxmJD7ekF7nlOKm9RoOA==} + '@rstest/core@0.11.8': + resolution: {integrity: sha512-XworMa277b5Cf4/Box18frFjWGP4dO/NIals+Ck/Q8nhe1z1t8j0P67zh6rv5xTdE3CCEfItICGvdjzz6VRhLg==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -2009,74 +1917,74 @@ packages: peerDependencies: react: '>=18.3.1' - '@yuku-parser/binding-android-arm64@0.8.5': - resolution: {integrity: sha512-BJtJvyf/Ma3v57uebPbe7VMUjNwTa3KW0fJn4awBBXyen8aS/i0gUbCDEsjGPY4GvAT41AggcYSep37V5VPENg==} + '@yuku-parser/binding-android-arm64@0.8.7': + resolution: {integrity: sha512-eGKYiUDX7Y0V7tDTmg+JTVnXnjMqfXXsorZ+EDf5kxwchQ3Or1HS14MzI2fw+jFhHR85fCWt+mtX33Yao73hIQ==} cpu: [arm64] os: [android] - '@yuku-parser/binding-darwin-arm64@0.8.5': - resolution: {integrity: sha512-CEzkjuxNjufVmSRlSm1qYGaoRpbp0g03saC8Tz6BesbmBUuP8MSqJa2BSskMbvYkRhKUN4OFCoPJ0XJf7ARTZQ==} + '@yuku-parser/binding-darwin-arm64@0.8.7': + resolution: {integrity: sha512-Re0RHelKLnjEURulY2/KxW+Ngb8zuNA4BRZuMwgGQNzVumT6u4U2N2hc01oeYVNVof0i7GrXE4UCNBgbpRRnjQ==} cpu: [arm64] os: [darwin] - '@yuku-parser/binding-darwin-x64@0.8.5': - resolution: {integrity: sha512-HS7wYfYUi3fTYNrzLNnZUia5DVo/Kf5NRmbh2rNVDKzMUEUfXI7xWdh0OOqIqYI3SsA5AcZOCI357uYb0+2B9Q==} + '@yuku-parser/binding-darwin-x64@0.8.7': + resolution: {integrity: sha512-Hn8DROtQkjlA1ACbPgj4a7eP9IuVOI504oiTwpkWPbpaDWD9KdmnVYCqW+1LfenNK/g7O9NhWGpXEdaCNX7lIA==} cpu: [x64] os: [darwin] - '@yuku-parser/binding-freebsd-x64@0.8.5': - resolution: {integrity: sha512-Iq/XcdT3qjV+mxzb6hE4oSlst/+wrJxSsgKu3FkVkV1bxb0UfITfedws/wI356KVr+BM9CeamNkdbchHV4pJlw==} + '@yuku-parser/binding-freebsd-x64@0.8.7': + resolution: {integrity: sha512-bAP2OV8wRuzplX/jYxv9+vvqQT8JxyNphI8fLfXGL054Xs+4/J5u33cIm3y4rxY8rdoLmmdiJs2Tq7r7lrDRfA==} cpu: [x64] os: [freebsd] - '@yuku-parser/binding-linux-arm-gnu@0.8.5': - resolution: {integrity: sha512-vbI/zeUdJEZ8BKUEfOD8ngtPR5/9XdONpRRosw73kOtA1GyipTF1rj75ozLHIVCEybdCB/GSlQ6OjjhuEAKrGA==} + '@yuku-parser/binding-linux-arm-gnu@0.8.7': + resolution: {integrity: sha512-kTYwJQQgmZeAWdDIWabiReIZMpmfLueIj1tCmjStUtFGhR1Z0qwxonKVfUC4N7h/VhGGzLZ//7O1kgt1QKqgCg==} cpu: [arm] os: [linux] libc: [glibc] - '@yuku-parser/binding-linux-arm-musl@0.8.5': - resolution: {integrity: sha512-3K4kOkOxWbUKoRja+vn7Srn8Nyc66Fr66oFWO+pFA/0KpVtlIGpKY+/KL8QIQdM7swTh+82OVkuFKeFEweVFLg==} + '@yuku-parser/binding-linux-arm-musl@0.8.7': + resolution: {integrity: sha512-uL4jE8HPT2BLlxAXyD10LqgPuXa9eDa0BKpCdSANmzIJghq/2eZo3/gQNtaxPZMupWoxjYzSad9IXrwu7aYPXQ==} cpu: [arm] os: [linux] libc: [musl] - '@yuku-parser/binding-linux-arm64-gnu@0.8.5': - resolution: {integrity: sha512-aj2pI9eT3ZAj8mWrC+utYUJwyxSd6ozthHjJfGtcY3MnZuQ4qbO7wm15BMqDgCSrg7az3tnONy1ftVQTWV9inA==} + '@yuku-parser/binding-linux-arm64-gnu@0.8.7': + resolution: {integrity: sha512-3gVN4pWSKZmXiNX7cU164dR9MPvesCHnlH6nPfpK+yQsCuYphjKKplcb4SnZBrhiqmXbgb2HR0c2TS0IZUPhgA==} cpu: [arm64] os: [linux] libc: [glibc] - '@yuku-parser/binding-linux-arm64-musl@0.8.5': - resolution: {integrity: sha512-+T2buVRNtY0QwhUeo8t47HmEpgh7tXMx8htMEdT7O0HHv3eFitwvyuVSdYNOpxo52Sc/0wJ6F4UbZni75aD4Pg==} + '@yuku-parser/binding-linux-arm64-musl@0.8.7': + resolution: {integrity: sha512-S0mwfEjoLpxzXeZw802Wa4RaELsQiPtWqG6INcy8j4GtvNFtl4LCX3eGO1XLn9pyLAISLzTRyU3zUCBUPin8lg==} cpu: [arm64] os: [linux] libc: [musl] - '@yuku-parser/binding-linux-x64-gnu@0.8.5': - resolution: {integrity: sha512-uIoy1uplNUqjq3GW6z+Ea8UCQkLKRPlM4FvqAhYMDwBazkVpE1mNvMqaQqf57ZP8mGTeOf4OF6CuUlenR2ttqg==} + '@yuku-parser/binding-linux-x64-gnu@0.8.7': + resolution: {integrity: sha512-lnbWdPmerE5D1uH1G4IEZKnPzCrWCStRGrtgpSIe1RibAo5bZIjDbbbPYXmMHCEh4F+x/JaJpElh26a3r+BPbg==} cpu: [x64] os: [linux] libc: [glibc] - '@yuku-parser/binding-linux-x64-musl@0.8.5': - resolution: {integrity: sha512-27doVJjvYevPWcakDCpfgZfDAlyhfyY3BwHf5TKtponCrSYBMzrBpD8ChYB1M7B6YOm0M5U9kEA0k1sB6CD4Ow==} + '@yuku-parser/binding-linux-x64-musl@0.8.7': + resolution: {integrity: sha512-769uwndMvMzUvATWbAcEvyLHKA+DzhHSCl/obBUrRdYfRo26yxui6S8y3z7uJ+Naup7UKrDxrpK7OnQkxkl9KQ==} cpu: [x64] os: [linux] libc: [musl] - '@yuku-parser/binding-win32-arm64@0.8.5': - resolution: {integrity: sha512-UIlSKhOLWZUQyDVQzYWAkHQGWnwNw5IxR/YYT2UCy64HLMQGGIxhb3qa/7Rf6yki50FrLx1O9PWgBKXCq7Zxxg==} + '@yuku-parser/binding-win32-arm64@0.8.7': + resolution: {integrity: sha512-mEB/9PlaAkisJ6KWGz0zvywXoU6+80dTlR2LwS7s/jcXXoU6fm2+sitBZXtqu3+Q4DcDgPxM45uWMCzPs0TSRw==} cpu: [arm64] os: [win32] - '@yuku-parser/binding-win32-x64@0.8.5': - resolution: {integrity: sha512-helhS0Pt0TwsW9Z5L3V5o27qrnTrmaUTgSpymVUJYlZJgW6Nagk7nS5P3Ez4b1OZXMwc5y5CR6m1niuzL/yYfQ==} + '@yuku-parser/binding-win32-x64@0.8.7': + resolution: {integrity: sha512-8vNB2DP0ou61nGb8tc/qfi41gfyDXz1MHr2zqL3nR+cJ6CEbiuWV/l/a/vv151gCgiZLLAyGkQGENpozdg716w==} cpu: [x64] os: [win32] - '@yuku-toolchain/types@0.8.5': - resolution: {integrity: sha512-ELNzrhwfi9+VCTaj6QcLCb5MlUK6pmVqPqH8bBmer1FTHvgEITnpwB73L/Wx5KKPPVWAokfeeU9V2rJy/9kMlg==} + '@yuku-toolchain/types@0.8.7': + resolution: {integrity: sha512-2Z53dNxAJL6UvFoIrDZvYf3zlO8s4VJK4O2hhaB4mXVwwpX/7ajtss3cmfqKvamlNLWyt9FSWs4eoYdlbxpnHA==} acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} @@ -2353,8 +2261,8 @@ packages: git-hooks-list@4.2.1: resolution: {integrity: sha512-WNvqJjOxxs/8ZP9+DWdwWJ7cDsd60NHf39XnD82pDVrKO5q7xfPqpkK6hwEAmBa/ZSEE4IOoR75EzbbIuwGlMw==} - globals@17.10.0: - resolution: {integrity: sha512-V0kztuWST2k8A/VbxAY8+L+7+Rgo3fyA24IHRLrZp7HOzJjV0gHSaZUjK9lpP/IrBSNite2tZ1prhRkinRu1CA==} + globals@17.11.0: + resolution: {integrity: sha512-Z2I8hM+PbJDXQDq3Icgpzv+mPdwr68iZUU9d5WW4FuXfDUQfkZaZuvjMv42/5crNyw154+9+VWXbYrUgDXbxNw==} engines: {node: '>=18'} happy-dom@20.11.2: @@ -3141,8 +3049,8 @@ packages: resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} engines: {node: '>=10'} - svelte@5.56.8: - resolution: {integrity: sha512-PY8LOw7xP6c8IOiVqdo0sbbZVYhXRSfklOQLAUyGBKqjTX0wx/z4l/9J+PmBpmlLnxzEb1NqltxQ5/wZme/Cmg==} + svelte@5.56.9: + resolution: {integrity: sha512-VT8kSnlEg8069w7AiCcAk3Yf5xvMnrGTagVOmU/OpOLHaHnNqXhWZCH/4EVga/bT/HtWhvE6/fHrXLErx7OnJA==} engines: {node: '>=18'} sync-child-process@1.0.2: @@ -3283,11 +3191,11 @@ packages: engines: {node: '>= 14.6'} hasBin: true - yuku-ast@0.8.5: - resolution: {integrity: sha512-Ez2CI2BnPK/if0tVI7jB9UUDSNibcChjJDEPEKuWPJNRkbvzSoAQrSqpKtdzl8qTPp4ftVb87f/jx5HIKOieQw==} + yuku-ast@0.8.7: + resolution: {integrity: sha512-h6+4bDfyootiMB9vckk5uKo5r5j0GHrkr17FQTDNfEsFT3DWlN9uu1HJwQwc64pgmLCI945fWM3lbTIqxjT3GQ==} - yuku-parser@0.8.5: - resolution: {integrity: sha512-t843J9IdYYpcDaW7o3aDPXpTM72FsvkIDYEHtigyGFCvC3EhiIaSGM5WVNZl3lxTv1Dfis6IW3S/yBsBIaCiWw==} + yuku-parser@0.8.7: + resolution: {integrity: sha512-vRD9nwt4L3aYpxNqeSC4WqLv58xrXef0Ong1Mc45CTXTIpvLafx7JO05sczmQZwdLEZvywrLOGdNC5+Rp5N1BQ==} zimmerframe@1.1.4: resolution: {integrity: sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==} @@ -3974,14 +3882,14 @@ snapshots: transitivePeerDependencies: - '@module-federation/runtime-tools' - '@rsbuild/core@2.1.11': + '@rsbuild/core@2.1.12': dependencies: - '@rspack/core': 2.1.9(@swc/helpers@0.5.23) + '@rspack/core': 2.1.10(@swc/helpers@0.5.23) '@swc/helpers': 0.5.23 transitivePeerDependencies: - '@module-federation/runtime-tools' - '@rsbuild/core@2.1.12': + '@rsbuild/core@2.1.13': dependencies: '@rspack/core': 2.1.10(@swc/helpers@0.5.23) '@swc/helpers': 0.5.23 @@ -4006,12 +3914,12 @@ snapshots: transitivePeerDependencies: - '@rspack/core' - '@rsbuild/plugin-react@2.1.0(@rsbuild/core@2.1.12)(@rspack/core@2.1.10)': + '@rsbuild/plugin-react@2.1.0(@rsbuild/core@2.1.13)(@rspack/core@2.1.10)': dependencies: '@rspack/plugin-react-refresh': 2.0.2(@rspack/core@2.1.10)(react-refresh@0.18.0) react-refresh: 0.18.0 optionalDependencies: - '@rsbuild/core': 2.1.12 + '@rsbuild/core': 2.1.13 transitivePeerDependencies: - '@rspack/core' @@ -4027,8 +3935,8 @@ snapshots: '@rslib/core@1.0.0-beta.3(typescript@7.0.2)': dependencies: - '@rsbuild/core': 2.1.12 - rsbuild-plugin-dts: 1.0.0-beta.3(@rsbuild/core@2.1.12)(typescript@7.0.2) + '@rsbuild/core': 2.1.13 + rsbuild-plugin-dts: 1.0.0-beta.3(@rsbuild/core@2.1.13)(typescript@7.0.2) optionalDependencies: typescript: 7.0.2 transitivePeerDependencies: @@ -4078,84 +3986,54 @@ snapshots: '@rspack/binding-darwin-arm64@2.1.8': optional: true - '@rspack/binding-darwin-arm64@2.1.9': - optional: true - '@rspack/binding-darwin-x64@2.1.10': optional: true '@rspack/binding-darwin-x64@2.1.8': optional: true - '@rspack/binding-darwin-x64@2.1.9': - optional: true - '@rspack/binding-linux-arm64-gnu@2.1.10': optional: true '@rspack/binding-linux-arm64-gnu@2.1.8': optional: true - '@rspack/binding-linux-arm64-gnu@2.1.9': - optional: true - '@rspack/binding-linux-arm64-musl@2.1.10': optional: true '@rspack/binding-linux-arm64-musl@2.1.8': optional: true - '@rspack/binding-linux-arm64-musl@2.1.9': - optional: true - '@rspack/binding-linux-ppc64-gnu@2.1.10': optional: true - '@rspack/binding-linux-ppc64-gnu@2.1.9': - optional: true - '@rspack/binding-linux-riscv64-gnu@2.1.10': optional: true '@rspack/binding-linux-riscv64-gnu@2.1.8': optional: true - '@rspack/binding-linux-riscv64-gnu@2.1.9': - optional: true - '@rspack/binding-linux-riscv64-musl@2.1.10': optional: true '@rspack/binding-linux-riscv64-musl@2.1.8': optional: true - '@rspack/binding-linux-riscv64-musl@2.1.9': - optional: true - '@rspack/binding-linux-s390x-gnu@2.1.10': optional: true - '@rspack/binding-linux-s390x-gnu@2.1.9': - optional: true - '@rspack/binding-linux-x64-gnu@2.1.10': optional: true '@rspack/binding-linux-x64-gnu@2.1.8': optional: true - '@rspack/binding-linux-x64-gnu@2.1.9': - optional: true - '@rspack/binding-linux-x64-musl@2.1.10': optional: true '@rspack/binding-linux-x64-musl@2.1.8': optional: true - '@rspack/binding-linux-x64-musl@2.1.9': - optional: true - '@rspack/binding-wasm32-wasi@2.1.10': dependencies: '@emnapi/core': 1.11.3 @@ -4170,40 +4048,24 @@ snapshots: '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) optional: true - '@rspack/binding-wasm32-wasi@2.1.9': - dependencies: - '@emnapi/core': 1.11.3 - '@emnapi/runtime': 1.11.3 - '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) - optional: true - '@rspack/binding-win32-arm64-msvc@2.1.10': optional: true '@rspack/binding-win32-arm64-msvc@2.1.8': optional: true - '@rspack/binding-win32-arm64-msvc@2.1.9': - optional: true - '@rspack/binding-win32-ia32-msvc@2.1.10': optional: true '@rspack/binding-win32-ia32-msvc@2.1.8': optional: true - '@rspack/binding-win32-ia32-msvc@2.1.9': - optional: true - '@rspack/binding-win32-x64-msvc@2.1.10': optional: true '@rspack/binding-win32-x64-msvc@2.1.8': optional: true - '@rspack/binding-win32-x64-msvc@2.1.9': - optional: true - '@rspack/binding@2.1.10': optionalDependencies: '@rspack/binding-darwin-arm64': 2.1.10 @@ -4236,23 +4098,6 @@ snapshots: '@rspack/binding-win32-ia32-msvc': 2.1.8 '@rspack/binding-win32-x64-msvc': 2.1.8 - '@rspack/binding@2.1.9': - optionalDependencies: - '@rspack/binding-darwin-arm64': 2.1.9 - '@rspack/binding-darwin-x64': 2.1.9 - '@rspack/binding-linux-arm64-gnu': 2.1.9 - '@rspack/binding-linux-arm64-musl': 2.1.9 - '@rspack/binding-linux-ppc64-gnu': 2.1.9 - '@rspack/binding-linux-riscv64-gnu': 2.1.9 - '@rspack/binding-linux-riscv64-musl': 2.1.9 - '@rspack/binding-linux-s390x-gnu': 2.1.9 - '@rspack/binding-linux-x64-gnu': 2.1.9 - '@rspack/binding-linux-x64-musl': 2.1.9 - '@rspack/binding-wasm32-wasi': 2.1.9 - '@rspack/binding-win32-arm64-msvc': 2.1.9 - '@rspack/binding-win32-ia32-msvc': 2.1.9 - '@rspack/binding-win32-x64-msvc': 2.1.9 - '@rspack/core@2.1.10(@swc/helpers@0.5.23)': dependencies: '@rspack/binding': 2.1.10 @@ -4265,12 +4110,6 @@ snapshots: optionalDependencies: '@swc/helpers': 0.5.23 - '@rspack/core@2.1.9(@swc/helpers@0.5.23)': - dependencies: - '@rspack/binding': 2.1.9 - optionalDependencies: - '@swc/helpers': 0.5.23 - '@rspack/plugin-react-refresh@2.0.2(@rspack/core@2.1.10)(react-refresh@0.18.0)': dependencies: react-refresh: 0.18.0 @@ -4335,7 +4174,7 @@ snapshots: '@rspress/shared@2.0.19(supports-color@8.1.1)': dependencies: - '@rsbuild/core': 2.1.11 + '@rsbuild/core': 2.1.12 '@shikijs/rehype': 4.3.1 '@types/react': 19.2.18 mdast-util-mdx-jsx: 3.2.0(supports-color@8.1.1) @@ -4355,21 +4194,21 @@ snapshots: '@rstackjs/test-utils@0.2.0': {} - '@rstest/adapter-rsbuild@0.11.6(@rsbuild/core@2.1.11)(@rstest/core@0.11.6)': + '@rstest/adapter-rsbuild@0.11.8(@rsbuild/core@2.1.13)(@rstest/core@0.11.8)': dependencies: - '@rsbuild/core': 2.1.11 - '@rstest/core': 0.11.6(happy-dom@20.11.2) + '@rsbuild/core': 2.1.13 + '@rstest/core': 0.11.8(happy-dom@20.11.2) - '@rstest/adapter-rslib@0.11.6(@rslib/core@1.0.0-beta.3)(@rstest/core@0.11.6)(typescript@7.0.2)': + '@rstest/adapter-rslib@0.11.8(@rslib/core@1.0.0-beta.3)(@rstest/core@0.11.8)(typescript@7.0.2)': dependencies: '@rslib/core': 1.0.0-beta.3(typescript@7.0.2) - '@rstest/core': 0.11.6(happy-dom@20.11.2) + '@rstest/core': 0.11.8(happy-dom@20.11.2) optionalDependencies: typescript: 7.0.2 - '@rstest/core@0.11.6(happy-dom@20.11.2)': + '@rstest/core@0.11.8(happy-dom@20.11.2)': dependencies: - '@rsbuild/core': 2.1.11 + '@rsbuild/core': 2.1.12 '@types/chai': 5.2.3 optionalDependencies: happy-dom: 20.11.2 @@ -4622,43 +4461,43 @@ snapshots: react: 19.2.8 unhead: 2.1.16 - '@yuku-parser/binding-android-arm64@0.8.5': + '@yuku-parser/binding-android-arm64@0.8.7': optional: true - '@yuku-parser/binding-darwin-arm64@0.8.5': + '@yuku-parser/binding-darwin-arm64@0.8.7': optional: true - '@yuku-parser/binding-darwin-x64@0.8.5': + '@yuku-parser/binding-darwin-x64@0.8.7': optional: true - '@yuku-parser/binding-freebsd-x64@0.8.5': + '@yuku-parser/binding-freebsd-x64@0.8.7': optional: true - '@yuku-parser/binding-linux-arm-gnu@0.8.5': + '@yuku-parser/binding-linux-arm-gnu@0.8.7': optional: true - '@yuku-parser/binding-linux-arm-musl@0.8.5': + '@yuku-parser/binding-linux-arm-musl@0.8.7': optional: true - '@yuku-parser/binding-linux-arm64-gnu@0.8.5': + '@yuku-parser/binding-linux-arm64-gnu@0.8.7': optional: true - '@yuku-parser/binding-linux-arm64-musl@0.8.5': + '@yuku-parser/binding-linux-arm64-musl@0.8.7': optional: true - '@yuku-parser/binding-linux-x64-gnu@0.8.5': + '@yuku-parser/binding-linux-x64-gnu@0.8.7': optional: true - '@yuku-parser/binding-linux-x64-musl@0.8.5': + '@yuku-parser/binding-linux-x64-musl@0.8.7': optional: true - '@yuku-parser/binding-win32-arm64@0.8.5': + '@yuku-parser/binding-win32-arm64@0.8.7': optional: true - '@yuku-parser/binding-win32-x64@0.8.5': + '@yuku-parser/binding-win32-x64@0.8.7': optional: true - '@yuku-toolchain/types@0.8.5': {} + '@yuku-toolchain/types@0.8.7': {} acorn-jsx@5.3.2(acorn@8.17.0): dependencies: @@ -4875,7 +4714,7 @@ snapshots: git-hooks-list@4.2.1: {} - globals@17.10.0: {} + globals@17.11.0: {} happy-dom@20.11.2: dependencies: @@ -5638,10 +5477,10 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - prettier-plugin-svelte@4.1.1(prettier@3.9.6)(svelte@5.56.8): + prettier-plugin-svelte@4.1.1(prettier@3.9.6)(svelte@5.56.9): dependencies: prettier: 3.9.6 - svelte: 5.56.8 + svelte: 5.56.9 prettier@3.9.6: {} @@ -5832,10 +5671,10 @@ snapshots: mdast-util-to-markdown: 2.1.2 unified: 11.0.5 - rsbuild-plugin-dts@1.0.0-beta.3(@rsbuild/core@2.1.12)(typescript@7.0.2): + rsbuild-plugin-dts@1.0.0-beta.3(@rsbuild/core@2.1.13)(typescript@7.0.2): dependencies: '@ast-grep/napi': 0.37.0 - '@rsbuild/core': 2.1.12 + '@rsbuild/core': 2.1.13 optionalDependencies: typescript: 7.0.2 @@ -6015,7 +5854,7 @@ snapshots: dependencies: has-flag: 4.0.0 - svelte@5.56.8: + svelte@5.56.9: dependencies: '@jridgewell/remapping': 2.3.5 '@jridgewell/sourcemap-codec': 1.5.5 @@ -6184,27 +6023,27 @@ snapshots: yaml@2.9.0: optional: true - yuku-ast@0.8.5: + yuku-ast@0.8.7: dependencies: - '@yuku-toolchain/types': 0.8.5 + '@yuku-toolchain/types': 0.8.7 - yuku-parser@0.8.5: + yuku-parser@0.8.7: dependencies: - '@yuku-toolchain/types': 0.8.5 - yuku-ast: 0.8.5 + '@yuku-toolchain/types': 0.8.7 + yuku-ast: 0.8.7 optionalDependencies: - '@yuku-parser/binding-android-arm64': 0.8.5 - '@yuku-parser/binding-darwin-arm64': 0.8.5 - '@yuku-parser/binding-darwin-x64': 0.8.5 - '@yuku-parser/binding-freebsd-x64': 0.8.5 - '@yuku-parser/binding-linux-arm-gnu': 0.8.5 - '@yuku-parser/binding-linux-arm-musl': 0.8.5 - '@yuku-parser/binding-linux-arm64-gnu': 0.8.5 - '@yuku-parser/binding-linux-arm64-musl': 0.8.5 - '@yuku-parser/binding-linux-x64-gnu': 0.8.5 - '@yuku-parser/binding-linux-x64-musl': 0.8.5 - '@yuku-parser/binding-win32-arm64': 0.8.5 - '@yuku-parser/binding-win32-x64': 0.8.5 + '@yuku-parser/binding-android-arm64': 0.8.7 + '@yuku-parser/binding-darwin-arm64': 0.8.7 + '@yuku-parser/binding-darwin-x64': 0.8.7 + '@yuku-parser/binding-freebsd-x64': 0.8.7 + '@yuku-parser/binding-linux-arm-gnu': 0.8.7 + '@yuku-parser/binding-linux-arm-musl': 0.8.7 + '@yuku-parser/binding-linux-arm64-gnu': 0.8.7 + '@yuku-parser/binding-linux-arm64-musl': 0.8.7 + '@yuku-parser/binding-linux-x64-gnu': 0.8.7 + '@yuku-parser/binding-linux-x64-musl': 0.8.7 + '@yuku-parser/binding-win32-arm64': 0.8.7 + '@yuku-parser/binding-win32-x64': 0.8.7 zimmerframe@1.1.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 8bf766d0..0ea73bd0 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -13,7 +13,7 @@ cleanupUnusedCatalogs: true catalog: '@napi-rs/cli': '^3.8.6' - '@rsbuild/core': '~2.1.11' + '@rsbuild/core': '~2.1.13' '@rsbuild/plugin-react': '^2.1.0' '@rsbuild/plugin-sass': '^2.0.1' '@rslib/core': '~1.0.0-beta.3' @@ -25,9 +25,9 @@ catalog: '@rstackjs/create-toolkit': '2.2.3' '@rstackjs/load-config': ^0.1.2 '@rstackjs/test-utils': ^0.2.0 - '@rstest/adapter-rsbuild': '~0.11.6' - '@rstest/adapter-rslib': '~0.11.6' - '@rstest/core': '~0.11.6' + '@rstest/adapter-rsbuild': '~0.11.8' + '@rstest/adapter-rslib': '~0.11.8' + '@rstest/core': '~0.11.8' '@testing-library/dom': '^10.4.1' '@testing-library/jest-dom': '^7.0.1' '@testing-library/react': '^16.3.2' @@ -38,7 +38,7 @@ catalog: '@shikijs/transformers': '^4.4.3' 'cspell-ban-words': '^0.0.4' 'fast-json-stable-stringify': '2.1.0' - globals: '^17.10.0' + globals: '^17.11.0' 'happy-dom': '^20.11.2' 'heading-case': '^1.1.5' 'import-meta-resolve': '4.2.0' @@ -53,13 +53,13 @@ catalog: rslog: ^2.3.0 'rspress-plugin-font-open-sans': '^1.0.4' 'sort-package-json': '4.0.0' - svelte: '^5.56.8' + svelte: '^5.56.9' tinypool: '2.1.0' tiny-readdir: 3.1.1 'typescript': '^7.0.2' 'vscode-languageserver': '10.1.0' 'vscode-languageserver-textdocument': '1.0.12' - yuku-parser: '0.8.5' + yuku-parser: '0.8.7' dedupePeers: true diff --git a/rust-toolchain.toml b/rust-toolchain.toml index da0237fb..7002caa4 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] # Required by the release-only -Zlocation-detail=none flag. -channel = "nightly-2026-08-12" +channel = "nightly-2026-08-13" components = ["clippy", "rustfmt"] profile = "minimal" From def8ecb06f797824b87497660e55070c98dfa196 Mon Sep 17 00:00:00 2001 From: Jiahan Chen Date: Sat, 15 Aug 2026 09:51:06 +0800 Subject: [PATCH 08/11] chore(deps): disable TypeScript updates for Vue and Svelte templates (#366) --- .github/renovate.json | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/renovate.json b/.github/renovate.json index 7fb2dd94..eb178041 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -1,4 +1,18 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": ["github>rstackjs/renovate"] + "extends": ["github>rstackjs/renovate"], + "packageRules": [ + { + "description": "Disable TypeScript updates in Svelte and Vue templates until svelte-check, svelte2tsx, and vue-tsc support TypeScript 7", + "matchManagers": ["npm"], + "matchPackageNames": ["typescript"], + "matchFileNames": [ + "packages/create-rstack/template-app-svelte-ts/package.json", + "packages/create-rstack/template-app-vue-ts/package.json", + "packages/create-rstack/template-lib-svelte-ts/package.json", + "packages/create-rstack/template-lib-vue-ts/package.json" + ], + "enabled": false + } + ] } From 19c368e8c377587fac40678bdf933e7da69c12fe Mon Sep 17 00:00:00 2001 From: Jiahan Chen Date: Sat, 15 Aug 2026 11:24:32 +0800 Subject: [PATCH 09/11] docs(website): redesign homepage hero (#367) --- website/i18n.json | 24 ++- website/theme/components/Hero.module.scss | 245 +++++++++++++++++++++- website/theme/components/Hero.tsx | 80 +++++-- website/theme/components/ToolStack.tsx | 13 -- website/theme/index.scss | 4 + website/theme/pages/index.tsx | 8 +- 6 files changed, 332 insertions(+), 42 deletions(-) delete mode 100644 website/theme/components/ToolStack.tsx diff --git a/website/i18n.json b/website/i18n.json index ba475596..a00be44e 100644 --- a/website/i18n.json +++ b/website/i18n.json @@ -3,13 +3,29 @@ "en": "Quick start", "zh": "快速上手" }, + "viewSource": { + "en": "View the code", + "zh": "查看源码" + }, + "copyCommand": { + "en": "Copy command", + "zh": "复制命令" + }, + "copiedCommand": { + "en": "Command copied", + "zh": "命令已复制" + }, + "title": { + "en": "Unified Toolchain for", + "zh": "统一工具链" + }, "subtitle": { - "en": "The Unified JavaScript Toolchain", - "zh": "统一的 JavaScript 工具链" + "en": "Shipping JavaScript Faster", + "zh": "加速 JavaScript 开发" }, "slogan": { - "en": "One CLI, one configuration, one consistent workflow", - "zh": "一个命令行、一份配置、一致的工作流" + "en": "One CLI unifies development, builds, testing, linting, and formatting across all your JavaScript projects. Powered by the Rspack ecosystem.", + "zh": "只需一个 CLI,即可统一所有 JavaScript 项目的开发、构建、测试、代码检查与格式化。由 Rspack 生态驱动。" }, "unifiedCli": { "en": "One CLI", diff --git a/website/theme/components/Hero.module.scss b/website/theme/components/Hero.module.scss index 972082a0..3b4452c9 100644 --- a/website/theme/components/Hero.module.scss +++ b/website/theme/components/Hero.module.scss @@ -1,8 +1,241 @@ -:global { - .rs-oval { - width: 70% !important; - height: 70% !important; - top: calc(50% + 20px) !important; - left: calc(50% + 5px) !important; +.hero { + --hero-title: #111214; + --hero-title-muted: #747474; + --hero-text: #707174; + --hero-border: #d9dbde; + --hero-command-bg: rgba(255, 255, 255, 0.72); + --hero-link: #686a6d; + --hero-link-hover: #111214; + + position: relative; + display: flex; + align-items: center; + justify-content: center; + box-sizing: border-box; + min-height: calc(100svh - var(--rp-nav-height)); + padding: clamp(4.5rem, 9vh, 7rem) 2rem; + overflow: hidden; + background: var(--rp-c-bg); +} + +:global(.dark) .hero { + --hero-title: #f5f5f5; + --hero-title-muted: #9a9a9a; + --hero-text: #a1a3a6; + --hero-border: #3a3c40; + --hero-command-bg: rgba(255, 255, 255, 0.025); + --hero-link: #a6a8ab; + --hero-link-hover: #f5f5f5; +} + +.inner { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + max-width: 64rem; + text-align: center; + transform: translateY(-1.5rem); +} + +.title { + margin: 0; + color: var(--hero-title); + font-size: clamp(3rem, 4.5vw, 4rem); + font-weight: 600; + line-height: 1.1; + letter-spacing: -0.055em; + text-wrap: balance; + + span { + display: block; + } +} + +.subtitle { + color: var(--hero-title-muted); +} + +.description { + max-width: 42rem; + margin: clamp(2.5rem, 5vh, 3.75rem) 0 0; + color: var(--hero-text); + font-size: clamp(1rem, 1.25vw, 1.25rem); + font-weight: 400; + line-height: 1.5; + letter-spacing: -0.02em; + text-wrap: balance; +} + +.command { + display: grid; + grid-template-columns: auto minmax(0, 1fr) auto; + align-items: center; + box-sizing: border-box; + width: min(100%, 20rem); + min-height: 3.125rem; + margin-top: clamp(2.75rem, 6vh, 4rem); + padding: 0 0.5rem 0 1rem; + color: var(--hero-title); + text-align: left; + border: 1px solid var(--hero-border); + border-radius: 0.5rem; + background: var(--hero-command-bg); + + code, + .prompt { + font-family: + ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', monospace; + font-size: clamp(0.8125rem, 1vw, 0.9375rem); + line-height: 1.4; + } + + code { + padding: 0 0.625rem; + overflow: hidden; + color: inherit; + text-overflow: ellipsis; + white-space: nowrap; + background: transparent; + } +} + +.prompt { + color: var(--hero-text); +} + +.copyButton { + display: inline-flex; + align-items: center; + justify-content: center; + width: 2rem; + height: 2rem; + padding: 0; + color: var(--hero-text); + cursor: pointer; + border: 0; + border-radius: 0.5rem; + background: transparent; + transition: + color 0.2s ease, + background-color 0.2s ease; + + svg { + width: 1.125rem; + height: 1.125rem; + } + + &:hover { + color: var(--hero-link-hover); + background: color-mix(in srgb, var(--hero-title) 7%, transparent); + } + + &:focus-visible { + outline: 2px solid var(--rp-c-brand); + outline-offset: 2px; + } +} + +.links { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: center; + gap: 1rem 2.5rem; + margin-top: clamp(2rem, 4vh, 2.5rem); +} + +.link { + display: inline-flex; + align-items: center; + gap: 0.5rem; + color: var(--hero-link); + font-size: 0.9375rem; + font-weight: 500; + line-height: 1.5; + text-decoration: none; + transition: color 0.2s ease; + + svg { + width: 0.75rem; + height: 0.75rem; + transition: transform 0.2s ease; + } + + &:hover { + color: var(--hero-link-hover); + + svg { + transform: translateX(0.2rem); + } + } + + &:focus-visible { + border-radius: 0.25rem; + outline: 2px solid var(--rp-c-brand); + outline-offset: 4px; + } +} + +@media (max-width: 640px) { + .hero { + min-height: calc(100svh - var(--rp-nav-height)); + padding: 4rem 1.25rem; + } + + .title { + font-size: clamp(2.25rem, 10vw, 2.75rem); + line-height: 1.04; + letter-spacing: -0.055em; + } + + .description { + max-width: 28rem; + margin-top: 2rem; + font-size: 0.9375rem; + line-height: 1.55; + } + + .command { + min-height: 3.25rem; + margin-top: 2.25rem; + padding: 0 0.5rem 0 0.875rem; + border-radius: 0.625rem; + + code, + .prompt { + font-size: 0.8125rem; + } + + code { + padding: 0 0.625rem; + } + } + + .copyButton { + width: 2rem; + height: 2rem; + + svg { + width: 1.125rem; + height: 1.125rem; + } + } + + .links { + gap: 1rem 1.75rem; + margin-top: 2rem; + } + + .link { + font-size: 0.875rem; + } +} + +@media (prefers-reduced-motion: reduce) { + .copyButton, + .link, + .link svg { + transition: none; } } diff --git a/website/theme/components/Hero.tsx b/website/theme/components/Hero.tsx index 7bf503f6..5edc0cf2 100644 --- a/website/theme/components/Hero.tsx +++ b/website/theme/components/Hero.tsx @@ -1,22 +1,76 @@ -import { useI18n, useNavigate } from '@rspress/core/runtime'; -import { Hero as BaseHero } from '@rstack-dev/doc-ui/hero'; +import { useI18n } from '@rspress/core/runtime'; +import { + IconArrowRight, + IconCopy, + IconSuccess, + Link, + SvgWrapper, + copyToClipboard, +} from '@rspress/core/theme-original'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { useI18nUrl } from './utils'; -import './Hero.module.scss'; +import styles from './Hero.module.scss'; + +const createCommand = 'pnpm create rstack'; +const githubUrl = 'https://github.com/rstackjs/rstack-cli'; export function Hero() { - const navigate = useNavigate(); const tUrl = useI18nUrl(); const t = useI18n(); + const [copied, setCopied] = useState(false); + const resetTimer = useRef(undefined); + + const handleCopy = useCallback(async () => { + const copiedSuccessfully = await copyToClipboard(createCommand); + + if (!copiedSuccessfully) { + return; + } + + setCopied(true); + window.clearTimeout(resetTimer.current); + resetTimer.current = window.setTimeout(() => setCopied(false), 1600); + }, []); + + useEffect(() => () => window.clearTimeout(resetTimer.current), []); return ( - navigate(tUrl('/guide/quick-start'))} - title="Rstack CLI" - subTitle={t('subtitle')} - description={t('slogan')} - getStartedButtonText={t('quickStart')} - githubURL="https://github.com/rstackjs/rstack-cli" - /> +
+
+

+ {t('title')} + {t('subtitle')} +

+ +

{t('slogan')}

+ +
+ + {createCommand} + +
+ +
+ + {t('quickStart')} + + + + {t('viewSource')} + + +
+
+
); } diff --git a/website/theme/components/ToolStack.tsx b/website/theme/components/ToolStack.tsx deleted file mode 100644 index c5e3fc57..00000000 --- a/website/theme/components/ToolStack.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { useLang } from '@rspress/core/runtime'; -import { containerStyle } from '@rstack-dev/doc-ui/section-style'; -import { ToolStack as BaseToolStack } from '@rstack-dev/doc-ui/tool-stack'; - -export function ToolStack() { - const lang = useLang(); - - return ( -
- -
- ); -} diff --git a/website/theme/index.scss b/website/theme/index.scss index 9fde95d7..a210d25d 100644 --- a/website/theme/index.scss +++ b/website/theme/index.scss @@ -13,6 +13,10 @@ } } +body:has(#home-hero-title) .rp-nav { + border-bottom: none; +} + .rspress-logo { height: 1.8rem; } diff --git a/website/theme/pages/index.tsx b/website/theme/pages/index.tsx index 084a27d2..cbeb03a2 100644 --- a/website/theme/pages/index.tsx +++ b/website/theme/pages/index.tsx @@ -1,19 +1,15 @@ -import { BackgroundImage } from '@rstack-dev/doc-ui/background-image'; import { CopyRight } from '../components/Copyright'; import { Features } from '../components/Features'; import { Hero } from '../components/Hero'; import { HomeFooter } from '../components/HomeFooter'; -import { ToolStack } from '../components/ToolStack'; export function HomeLayout() { return ( -
- + <> - -
+ ); } From 4ee1c719cafe96382a70b74918c9c153d86b4688 Mon Sep 17 00:00:00 2001 From: Jiahan Chen Date: Sat, 15 Aug 2026 19:54:43 +0800 Subject: [PATCH 10/11] docs: simplify configuration guide comments (#368) --- .agents/skills/migrate-to-rstack-cli/SKILL.md | 2 +- examples/app-react/rstack.config.ts | 2 +- examples/app-vanilla/rstack.config.ts | 2 +- examples/documentation/rstack.config.ts | 2 +- examples/lib-node/rstack.config.ts | 2 +- examples/lib-react/rstack.config.ts | 2 +- examples/test-inline-projects/rstack.config.ts | 2 +- packages/create-rstack/rstack.config.ts | 2 +- .../template-app-lit-ts/rstack.config.ts | 2 +- .../template-app-lit/rstack.config.js | 2 +- .../template-app-preact-ts/rstack.config.ts | 2 +- .../template-app-preact/rstack.config.js | 2 +- .../template-app-react-ts/rstack.config.ts | 2 +- .../template-app-react/rstack.config.js | 2 +- .../template-app-solid-ts/rstack.config.ts | 2 +- .../template-app-solid/rstack.config.js | 2 +- .../template-app-svelte-ts/rstack.config.ts | 2 +- .../template-app-svelte/rstack.config.js | 2 +- .../template-app-vanilla-ts/rstack.config.ts | 2 +- .../template-app-vanilla/rstack.config.js | 2 +- .../template-app-vue-ts/rstack.config.ts | 2 +- .../template-app-vue/rstack.config.js | 2 +- .../template-doc-i18n/rstack.config.ts | 2 +- .../create-rstack/template-doc/rstack.config.ts | 2 +- .../template-lib-node-ts/rstack.config.ts | 2 +- .../template-lib-node/rstack.config.js | 2 +- .../template-lib-react-ts/rstack.config.ts | 2 +- .../template-lib-react/rstack.config.js | 2 +- .../template-lib-solid-ts/rstack.config.ts | 2 +- .../template-lib-solid/rstack.config.js | 2 +- .../template-lib-svelte-ts/rstack.config.ts | 2 +- .../template-lib-svelte/rstack.config.js | 2 +- .../template-lib-vue-ts/rstack.config.ts | 2 +- .../template-lib-vue/rstack.config.js | 2 +- packages/rstack/rstack.config.ts | 2 +- packages/rstack/src/config.ts | 14 +++++++------- rstack.config.ts | 2 +- website/docs/en/guide/configuration.mdx | 2 +- website/docs/en/guide/quick-start.mdx | 2 +- website/docs/zh/guide/configuration.mdx | 2 +- website/docs/zh/guide/quick-start.mdx | 2 +- website/rstack.config.ts | 2 +- 42 files changed, 48 insertions(+), 48 deletions(-) diff --git a/.agents/skills/migrate-to-rstack-cli/SKILL.md b/.agents/skills/migrate-to-rstack-cli/SKILL.md index 06d61983..992cf06c 100644 --- a/.agents/skills/migrate-to-rstack-cli/SKILL.md +++ b/.agents/skills/migrate-to-rstack-cli/SKILL.md @@ -41,7 +41,7 @@ Use one of the default names: `rstack.config.ts`, `.js`, `.mts`, or `.mjs`. Use `rs -c ` or `rs --config ` only for a custom path. ```ts -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import { define } from 'rstack'; define.app({ diff --git a/examples/app-react/rstack.config.ts b/examples/app-react/rstack.config.ts index 960e215b..d6c4a461 100644 --- a/examples/app-react/rstack.config.ts +++ b/examples/app-react/rstack.config.ts @@ -1,4 +1,4 @@ -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import { define } from 'rstack'; define.app(async () => { diff --git a/examples/app-vanilla/rstack.config.ts b/examples/app-vanilla/rstack.config.ts index 9707fbda..6e3bb9a8 100644 --- a/examples/app-vanilla/rstack.config.ts +++ b/examples/app-vanilla/rstack.config.ts @@ -1,4 +1,4 @@ -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import { define } from 'rstack'; define.test({ diff --git a/examples/documentation/rstack.config.ts b/examples/documentation/rstack.config.ts index 10c5a9ad..8d99518f 100644 --- a/examples/documentation/rstack.config.ts +++ b/examples/documentation/rstack.config.ts @@ -1,4 +1,4 @@ -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import { define } from 'rstack'; import path from 'node:path'; diff --git a/examples/lib-node/rstack.config.ts b/examples/lib-node/rstack.config.ts index ed274a80..104652eb 100644 --- a/examples/lib-node/rstack.config.ts +++ b/examples/lib-node/rstack.config.ts @@ -1,4 +1,4 @@ -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import { define } from 'rstack'; define.lib({ diff --git a/examples/lib-react/rstack.config.ts b/examples/lib-react/rstack.config.ts index fd97fdae..ec8669f4 100644 --- a/examples/lib-react/rstack.config.ts +++ b/examples/lib-react/rstack.config.ts @@ -1,4 +1,4 @@ -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import { define } from 'rstack'; define.lib(async () => { diff --git a/examples/test-inline-projects/rstack.config.ts b/examples/test-inline-projects/rstack.config.ts index 12fa4e57..55383b03 100644 --- a/examples/test-inline-projects/rstack.config.ts +++ b/examples/test-inline-projects/rstack.config.ts @@ -1,4 +1,4 @@ -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import { define } from 'rstack'; define.app(async () => { diff --git a/packages/create-rstack/rstack.config.ts b/packages/create-rstack/rstack.config.ts index 923fc17d..7daaf593 100644 --- a/packages/create-rstack/rstack.config.ts +++ b/packages/create-rstack/rstack.config.ts @@ -1,4 +1,4 @@ -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import { define } from 'rstack'; define.lib({ diff --git a/packages/create-rstack/template-app-lit-ts/rstack.config.ts b/packages/create-rstack/template-app-lit-ts/rstack.config.ts index d7f664c7..434eac7d 100644 --- a/packages/create-rstack/template-app-lit-ts/rstack.config.ts +++ b/packages/create-rstack/template-app-lit-ts/rstack.config.ts @@ -1,4 +1,4 @@ -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import { define } from 'rstack'; define.app({ diff --git a/packages/create-rstack/template-app-lit/rstack.config.js b/packages/create-rstack/template-app-lit/rstack.config.js index 4a74583c..e5a6fd11 100644 --- a/packages/create-rstack/template-app-lit/rstack.config.js +++ b/packages/create-rstack/template-app-lit/rstack.config.js @@ -1,5 +1,5 @@ // @ts-check -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import { define } from 'rstack'; define.app({ diff --git a/packages/create-rstack/template-app-preact-ts/rstack.config.ts b/packages/create-rstack/template-app-preact-ts/rstack.config.ts index e1ec7e4d..9fdeba76 100644 --- a/packages/create-rstack/template-app-preact-ts/rstack.config.ts +++ b/packages/create-rstack/template-app-preact-ts/rstack.config.ts @@ -1,4 +1,4 @@ -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import { define } from 'rstack'; define.app(async () => { diff --git a/packages/create-rstack/template-app-preact/rstack.config.js b/packages/create-rstack/template-app-preact/rstack.config.js index 0c586e57..912d57af 100644 --- a/packages/create-rstack/template-app-preact/rstack.config.js +++ b/packages/create-rstack/template-app-preact/rstack.config.js @@ -1,5 +1,5 @@ // @ts-check -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import { define } from 'rstack'; define.app(async () => { diff --git a/packages/create-rstack/template-app-react-ts/rstack.config.ts b/packages/create-rstack/template-app-react-ts/rstack.config.ts index b6214713..cb508e42 100644 --- a/packages/create-rstack/template-app-react-ts/rstack.config.ts +++ b/packages/create-rstack/template-app-react-ts/rstack.config.ts @@ -1,4 +1,4 @@ -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import { define } from 'rstack'; define.app(async () => { diff --git a/packages/create-rstack/template-app-react/rstack.config.js b/packages/create-rstack/template-app-react/rstack.config.js index e01ac564..886b005a 100644 --- a/packages/create-rstack/template-app-react/rstack.config.js +++ b/packages/create-rstack/template-app-react/rstack.config.js @@ -1,5 +1,5 @@ // @ts-check -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import { define } from 'rstack'; define.app(async () => { diff --git a/packages/create-rstack/template-app-solid-ts/rstack.config.ts b/packages/create-rstack/template-app-solid-ts/rstack.config.ts index 17f42c04..d364bf54 100644 --- a/packages/create-rstack/template-app-solid-ts/rstack.config.ts +++ b/packages/create-rstack/template-app-solid-ts/rstack.config.ts @@ -1,4 +1,4 @@ -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import { define } from 'rstack'; define.app(async () => { diff --git a/packages/create-rstack/template-app-solid/rstack.config.js b/packages/create-rstack/template-app-solid/rstack.config.js index 035c1408..da3845a4 100644 --- a/packages/create-rstack/template-app-solid/rstack.config.js +++ b/packages/create-rstack/template-app-solid/rstack.config.js @@ -1,5 +1,5 @@ // @ts-check -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import { define } from 'rstack'; define.app(async () => { diff --git a/packages/create-rstack/template-app-svelte-ts/rstack.config.ts b/packages/create-rstack/template-app-svelte-ts/rstack.config.ts index aaf917a8..f2dec78f 100644 --- a/packages/create-rstack/template-app-svelte-ts/rstack.config.ts +++ b/packages/create-rstack/template-app-svelte-ts/rstack.config.ts @@ -1,4 +1,4 @@ -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import { define } from 'rstack'; define.app(async () => { diff --git a/packages/create-rstack/template-app-svelte/rstack.config.js b/packages/create-rstack/template-app-svelte/rstack.config.js index a1efbfc7..0fb4fac1 100644 --- a/packages/create-rstack/template-app-svelte/rstack.config.js +++ b/packages/create-rstack/template-app-svelte/rstack.config.js @@ -1,5 +1,5 @@ // @ts-check -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import { define } from 'rstack'; define.app(async () => { diff --git a/packages/create-rstack/template-app-vanilla-ts/rstack.config.ts b/packages/create-rstack/template-app-vanilla-ts/rstack.config.ts index 6e755422..dfe1edc1 100644 --- a/packages/create-rstack/template-app-vanilla-ts/rstack.config.ts +++ b/packages/create-rstack/template-app-vanilla-ts/rstack.config.ts @@ -1,4 +1,4 @@ -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import { define } from 'rstack'; define.app({ diff --git a/packages/create-rstack/template-app-vanilla/rstack.config.js b/packages/create-rstack/template-app-vanilla/rstack.config.js index a9f27d77..cf825efc 100644 --- a/packages/create-rstack/template-app-vanilla/rstack.config.js +++ b/packages/create-rstack/template-app-vanilla/rstack.config.js @@ -1,5 +1,5 @@ // @ts-check -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import { define } from 'rstack'; define.app({ diff --git a/packages/create-rstack/template-app-vue-ts/rstack.config.ts b/packages/create-rstack/template-app-vue-ts/rstack.config.ts index 97e764c0..0c8fe28e 100644 --- a/packages/create-rstack/template-app-vue-ts/rstack.config.ts +++ b/packages/create-rstack/template-app-vue-ts/rstack.config.ts @@ -1,4 +1,4 @@ -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import { define } from 'rstack'; define.app(async () => { diff --git a/packages/create-rstack/template-app-vue/rstack.config.js b/packages/create-rstack/template-app-vue/rstack.config.js index 8116b0df..1cf8b2e1 100644 --- a/packages/create-rstack/template-app-vue/rstack.config.js +++ b/packages/create-rstack/template-app-vue/rstack.config.js @@ -1,5 +1,5 @@ // @ts-check -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import { define } from 'rstack'; define.app(async () => { diff --git a/packages/create-rstack/template-doc-i18n/rstack.config.ts b/packages/create-rstack/template-doc-i18n/rstack.config.ts index e3185a0d..87a0190d 100644 --- a/packages/create-rstack/template-doc-i18n/rstack.config.ts +++ b/packages/create-rstack/template-doc-i18n/rstack.config.ts @@ -1,4 +1,4 @@ -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import path from 'node:path'; import { define } from 'rstack'; diff --git a/packages/create-rstack/template-doc/rstack.config.ts b/packages/create-rstack/template-doc/rstack.config.ts index 99bf9d0d..4a65ede6 100644 --- a/packages/create-rstack/template-doc/rstack.config.ts +++ b/packages/create-rstack/template-doc/rstack.config.ts @@ -1,4 +1,4 @@ -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import path from 'node:path'; import { define } from 'rstack'; diff --git a/packages/create-rstack/template-lib-node-ts/rstack.config.ts b/packages/create-rstack/template-lib-node-ts/rstack.config.ts index 76a7cb35..09769776 100644 --- a/packages/create-rstack/template-lib-node-ts/rstack.config.ts +++ b/packages/create-rstack/template-lib-node-ts/rstack.config.ts @@ -1,4 +1,4 @@ -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import { define } from 'rstack'; define.lib({ diff --git a/packages/create-rstack/template-lib-node/rstack.config.js b/packages/create-rstack/template-lib-node/rstack.config.js index 047c2d6c..6080e25d 100644 --- a/packages/create-rstack/template-lib-node/rstack.config.js +++ b/packages/create-rstack/template-lib-node/rstack.config.js @@ -1,5 +1,5 @@ // @ts-check -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import { define } from 'rstack'; define.lib({ diff --git a/packages/create-rstack/template-lib-react-ts/rstack.config.ts b/packages/create-rstack/template-lib-react-ts/rstack.config.ts index 8c91acb5..1aeb4946 100644 --- a/packages/create-rstack/template-lib-react-ts/rstack.config.ts +++ b/packages/create-rstack/template-lib-react-ts/rstack.config.ts @@ -1,4 +1,4 @@ -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import { define } from 'rstack'; define.lib(async () => { diff --git a/packages/create-rstack/template-lib-react/rstack.config.js b/packages/create-rstack/template-lib-react/rstack.config.js index 12fc5223..76fdfda3 100644 --- a/packages/create-rstack/template-lib-react/rstack.config.js +++ b/packages/create-rstack/template-lib-react/rstack.config.js @@ -1,5 +1,5 @@ // @ts-check -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import { define } from 'rstack'; define.lib(async () => { diff --git a/packages/create-rstack/template-lib-solid-ts/rstack.config.ts b/packages/create-rstack/template-lib-solid-ts/rstack.config.ts index 3fa4dc06..c61c28e5 100644 --- a/packages/create-rstack/template-lib-solid-ts/rstack.config.ts +++ b/packages/create-rstack/template-lib-solid-ts/rstack.config.ts @@ -1,4 +1,4 @@ -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import { define } from 'rstack'; define.lib(async () => { diff --git a/packages/create-rstack/template-lib-solid/rstack.config.js b/packages/create-rstack/template-lib-solid/rstack.config.js index 0e2a8f3e..974cf85d 100644 --- a/packages/create-rstack/template-lib-solid/rstack.config.js +++ b/packages/create-rstack/template-lib-solid/rstack.config.js @@ -1,5 +1,5 @@ // @ts-check -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import { define } from 'rstack'; define.lib(async () => { diff --git a/packages/create-rstack/template-lib-svelte-ts/rstack.config.ts b/packages/create-rstack/template-lib-svelte-ts/rstack.config.ts index b74a4f81..6aeaf38c 100644 --- a/packages/create-rstack/template-lib-svelte-ts/rstack.config.ts +++ b/packages/create-rstack/template-lib-svelte-ts/rstack.config.ts @@ -1,4 +1,4 @@ -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import { define } from 'rstack'; import { svelteDtsPlugin } from './scripts/rslib-plugin-svelte-dts.ts'; diff --git a/packages/create-rstack/template-lib-svelte/rstack.config.js b/packages/create-rstack/template-lib-svelte/rstack.config.js index 4bbb928c..7a952e06 100644 --- a/packages/create-rstack/template-lib-svelte/rstack.config.js +++ b/packages/create-rstack/template-lib-svelte/rstack.config.js @@ -1,5 +1,5 @@ // @ts-check -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import { define } from 'rstack'; define.lib(async () => { diff --git a/packages/create-rstack/template-lib-vue-ts/rstack.config.ts b/packages/create-rstack/template-lib-vue-ts/rstack.config.ts index e441042e..c30a51b2 100644 --- a/packages/create-rstack/template-lib-vue-ts/rstack.config.ts +++ b/packages/create-rstack/template-lib-vue-ts/rstack.config.ts @@ -1,4 +1,4 @@ -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import { define } from 'rstack'; define.lib(async () => { diff --git a/packages/create-rstack/template-lib-vue/rstack.config.js b/packages/create-rstack/template-lib-vue/rstack.config.js index cd42bd0d..42ec7988 100644 --- a/packages/create-rstack/template-lib-vue/rstack.config.js +++ b/packages/create-rstack/template-lib-vue/rstack.config.js @@ -1,5 +1,5 @@ // @ts-check -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import { define } from 'rstack'; define.lib(async () => { diff --git a/packages/rstack/rstack.config.ts b/packages/rstack/rstack.config.ts index 09910407..8abe863e 100644 --- a/packages/rstack/rstack.config.ts +++ b/packages/rstack/rstack.config.ts @@ -1,4 +1,4 @@ -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import { define } from 'rstack'; define.test(async () => { diff --git a/packages/rstack/src/config.ts b/packages/rstack/src/config.ts index 0bf3bc52..222e6ff3 100644 --- a/packages/rstack/src/config.ts +++ b/packages/rstack/src/config.ts @@ -94,7 +94,7 @@ type Define = { * * This config is used by the `rs dev`, `rs build`, and `rs preview` commands. * - * @see {@link https://rstack.rs/config | Rstack configuration guide} + * @see {@link https://rstack.rs/config | Configuration guide} */ app: (config: RsbuildConfigDefinition) => void; /** @@ -102,7 +102,7 @@ type Define = { * * This config is used by the `rs lib` command. * - * @see {@link https://rstack.rs/config | Rstack configuration guide} + * @see {@link https://rstack.rs/config | Configuration guide} */ lib: (config: RslibConfigDefinition) => void; /** @@ -110,7 +110,7 @@ type Define = { * * This config is used by the `rs doc` command. * - * @see {@link https://rstack.rs/config | Rstack configuration guide} + * @see {@link https://rstack.rs/config | Configuration guide} */ doc: (config: RspressConfigDefinition) => void; /** @@ -122,7 +122,7 @@ type Define = { * falls back to `define.lib`. For multi-project configs, this applies to every inline * project without an explicit `extends`. The app config takes precedence when both are defined. * - * @see {@link https://rstack.rs/config | Rstack configuration guide} + * @see {@link https://rstack.rs/config | Configuration guide} */ test: (config: RstestConfigExport) => void; /** @@ -131,7 +131,7 @@ type Define = { * This config is used by the `rs lint` command. * A config factory receives the exports from `rstack/lint`. * - * @see {@link https://rstack.rs/config | Rstack configuration guide} + * @see {@link https://rstack.rs/config | Configuration guide} */ lint: (config: RslintConfig | RslintConfigFactory) => void; /** @@ -139,7 +139,7 @@ type Define = { * * This config will be used by the `rs fmt` command. * - * @see {@link https://rstack.rs/config | Rstack configuration guide} + * @see {@link https://rstack.rs/config | Configuration guide} */ fmt: (config: FmtConfigDefinition) => void; /** @@ -147,7 +147,7 @@ type Define = { * * This config is used by the `rs staged` command. * - * @see {@link https://rstack.rs/config | Rstack configuration guide} + * @see {@link https://rstack.rs/config | Configuration guide} */ staged: (config: StagedConfig) => void; }; diff --git a/rstack.config.ts b/rstack.config.ts index 3e20de09..68b2ef8f 100644 --- a/rstack.config.ts +++ b/rstack.config.ts @@ -1,4 +1,4 @@ -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import { define } from 'rstack'; define.lint(async ({ js, ts }) => { diff --git a/website/docs/en/guide/configuration.mdx b/website/docs/en/guide/configuration.mdx index 9b3d8283..ff057d24 100644 --- a/website/docs/en/guide/configuration.mdx +++ b/website/docs/en/guide/configuration.mdx @@ -9,7 +9,7 @@ Rstack CLI centralizes the configuration for your project's tools in a single fi Create `rstack.config.ts` in the project root and call the relevant `define.*()` APIs: ```ts title="rstack.config.ts" -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import { define } from 'rstack'; define.app({ diff --git a/website/docs/en/guide/quick-start.mdx b/website/docs/en/guide/quick-start.mdx index 902be45d..ec64840d 100644 --- a/website/docs/en/guide/quick-start.mdx +++ b/website/docs/en/guide/quick-start.mdx @@ -156,7 +156,7 @@ The following commands are available: Create `rstack.config.ts` in the project root and register the configurations your project needs. The following is a minimal example for an application with testing and linting: ```ts title="rstack.config.ts" -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import { define } from 'rstack'; define.app({ diff --git a/website/docs/zh/guide/configuration.mdx b/website/docs/zh/guide/configuration.mdx index dc3d8fde..5f15b77b 100644 --- a/website/docs/zh/guide/configuration.mdx +++ b/website/docs/zh/guide/configuration.mdx @@ -9,7 +9,7 @@ Rstack CLI 将项目所用工具的配置集中到一份文件中。通过 `defi 在项目根目录创建 `rstack.config.ts`,并调用对应的 `define.*()` API: ```ts title="rstack.config.ts" -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import { define } from 'rstack'; define.app({ diff --git a/website/docs/zh/guide/quick-start.mdx b/website/docs/zh/guide/quick-start.mdx index aa7352a7..4cc43cca 100644 --- a/website/docs/zh/guide/quick-start.mdx +++ b/website/docs/zh/guide/quick-start.mdx @@ -156,7 +156,7 @@ Rstack CLI 提供以下命令: 在项目根目录创建 `rstack.config.ts`,并注册项目所需的配置。以下是一个包含应用、测试和代码检查的最小示例: ```ts title="rstack.config.ts" -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import { define } from 'rstack'; define.app({ diff --git a/website/rstack.config.ts b/website/rstack.config.ts index aa78c750..8e1fd234 100644 --- a/website/rstack.config.ts +++ b/website/rstack.config.ts @@ -1,4 +1,4 @@ -// Rstack configuration guide: https://rstack.rs/config +// Configuration guide: https://rstack.rs/config import path from 'node:path'; import { define } from 'rstack'; From 0b7774443b685ea5d2087910c32c9c447f833285 Mon Sep 17 00:00:00 2001 From: Jiahan Chen Date: Sun, 16 Aug 2026 08:34:52 +0800 Subject: [PATCH 11/11] chore(fmt): use default print width (#369) --- .../references/rslint.md | 5 +- .../test-inline-projects/tests/dom.test.tsx | 4 +- packages/create-rstack/src/index.ts | 49 +++++-- packages/create-rstack/tests/create.test.ts | 127 ++++++++++++++---- packages/rstack/rslib.config.ts | 3 +- packages/rstack/src/cli/args.ts | 50 +++++-- packages/rstack/src/cli/commandHelp.ts | 126 +++++++++++++---- packages/rstack/src/cli/commands.ts | 37 ++++- packages/rstack/src/config.ts | 22 ++- packages/rstack/src/fmt/cacheIdentity.ts | 19 ++- packages/rstack/src/fmt/cacheStore.ts | 17 ++- packages/rstack/src/fmt/cli.ts | 43 ++++-- packages/rstack/src/fmt/config.ts | 31 ++++- packages/rstack/src/fmt/discoverPaths.ts | 96 ++++++++++--- packages/rstack/src/fmt/discovery.ts | 4 +- packages/rstack/src/fmt/fileResolver.ts | 4 +- packages/rstack/src/fmt/format.ts | 3 +- packages/rstack/src/fmt/ignore.ts | 8 +- packages/rstack/src/fmt/lsp/minimalEdit.ts | 16 ++- packages/rstack/src/fmt/lsp/server.ts | 97 +++++++------ packages/rstack/src/fmt/pathHelpers.ts | 11 +- packages/rstack/src/fmt/plugins.ts | 28 +++- packages/rstack/src/fmt/prettierPlugins.ts | 5 +- packages/rstack/src/fmt/runner.ts | 25 +++- packages/rstack/src/fmt/types.ts | 8 +- packages/rstack/src/fmt/worker.ts | 3 +- packages/rstack/src/fmt/workerPool.ts | 10 +- packages/rstack/src/fmt/yukuPlugin.ts | 123 ++++++++++++----- packages/rstack/src/native/index.ts | 4 +- packages/rstack/src/projectCache.ts | 10 +- packages/rstack/src/rsbuildConfig.ts | 12 +- packages/rstack/src/rslibConfig.ts | 17 ++- packages/rstack/src/rspressConfig.ts | 6 +- packages/rstack/src/rstestConfig.ts | 16 ++- packages/rstack/src/setup/hooks.ts | 15 ++- packages/rstack/src/setup/index.ts | 8 +- packages/rstack/src/setup/install.ts | 96 ++++++++++--- packages/rstack/src/staged.ts | 12 +- packages/rstack/tests/cli/args.test.ts | 23 ++-- packages/rstack/tests/cli/check.test.ts | 8 +- packages/rstack/tests/cli/fmt/cache.test.ts | 102 +++++++++----- packages/rstack/tests/cli/fmt/config.test.ts | 43 ++++-- packages/rstack/tests/cli/fmt/files.test.ts | 37 +++-- packages/rstack/tests/cli/fmt/helpers.ts | 33 ++++- packages/rstack/tests/cli/fmt/lsp.test.ts | 43 ++++-- packages/rstack/tests/cli/fmt/lspClient.ts | 48 +++++-- .../rstack/tests/cli/fmt/patterns.test.ts | 14 +- packages/rstack/tests/cli/fmt/stdin.test.ts | 45 +++++-- packages/rstack/tests/cli/fmt/vue.test.ts | 12 +- packages/rstack/tests/cli/setup/index.test.ts | 48 +++++-- .../tests/cli/specify-config/index.test.ts | 6 +- packages/rstack/tests/cli/staged/fmt.test.ts | 40 ++++-- .../tests/config/define-app-lib/index.test.ts | 4 +- .../tests/config/define-app/index.test.ts | 6 +- .../tests/config/define-doc/index.test.ts | 6 +- .../tests/config/define-lib/index.test.ts | 6 +- .../tests/config/define-lint/index.test.ts | 6 +- .../define-test-projects-app/index.test.ts | 4 +- .../define-test-projects-lib/index.test.ts | 4 +- .../tests/config/load-config/index.test.ts | 7 +- .../config/reload-app-config/index.test.ts | 19 ++- .../config/reload-doc-config/index.test.ts | 45 +++++-- .../config/reload-lib-config/index.test.ts | 27 +++- .../tests/exports/test-subpath/index.test.ts | 9 +- .../rstack/tests/fmt/cacheIdentity.test.ts | 16 ++- packages/rstack/tests/fmt/cacheStore.test.ts | 12 +- packages/rstack/tests/fmt/config.test.ts | 13 +- .../rstack/tests/fmt/discoverPaths.test.ts | 110 ++++++++++----- packages/rstack/tests/fmt/discovery.test.ts | 50 +++++-- .../rstack/tests/fmt/fileResolver.test.ts | 5 +- packages/rstack/tests/fmt/helpers.ts | 16 ++- packages/rstack/tests/fmt/ignore.test.ts | 29 +++- .../rstack/tests/fmt/lsp/minimalEdit.test.ts | 47 +++++-- packages/rstack/tests/fmt/lsp/server.test.ts | 13 +- packages/rstack/tests/fmt/plugins.test.ts | 10 +- packages/rstack/tests/fmt/runner.test.ts | 40 ++++-- packages/rstack/tests/fmt/runnerCache.test.ts | 94 +++++++++---- .../tests/fmt/runnerWorkerPreflight.test.ts | 20 ++- packages/rstack/tests/fmt/worker.test.ts | 20 ++- packages/rstack/tests/fmt/yukuPlugin.test.ts | 48 ++++--- packages/rstack/tests/helpers/cli.ts | 27 ++-- packages/rstack/tests/helpers/cliTest.ts | 26 +++- packages/rstack/tests/helpers/logs.ts | 4 +- .../rstack/tests/setup/directories.test.ts | 44 ++++-- packages/rstack/tests/setup/helpers.ts | 24 +++- packages/rstack/tests/setup/hooks.test.ts | 33 +++-- packages/rstack/tests/setup/install.test.ts | 66 ++++++--- .../rstack/tests/setup/runtime-errors.test.ts | 4 +- packages/rstack/tests/setup/runtime.test.ts | 24 +++- .../tests/types/resolution-bundler/index.ts | 9 +- .../tests/types/resolution-nodenext/index.ts | 9 +- rstack.config.ts | 12 +- scripts/benchmark-fmt-discovery.js | 10 +- scripts/prepare-release.js | 9 +- website/docs/en/guide/ai.mdx | 5 +- website/docs/en/guide/cli/_meta.json | 14 +- website/docs/en/guide/cli/lint.mdx | 5 +- website/docs/en/guide/configuration.mdx | 5 +- website/docs/en/guide/monorepo.mdx | 5 +- website/docs/zh/guide/ai.mdx | 5 +- website/docs/zh/guide/cli/_meta.json | 14 +- website/docs/zh/guide/cli/lint.mdx | 5 +- website/docs/zh/guide/configuration.mdx | 5 +- website/docs/zh/guide/monorepo.mdx | 5 +- website/rstack.config.ts | 16 ++- website/theme/components/Copyright.tsx | 5 +- website/theme/components/Features.tsx | 10 +- website/theme/components/Hero.module.scss | 3 +- website/theme/components/Hero.tsx | 7 +- 109 files changed, 2087 insertions(+), 681 deletions(-) diff --git a/.agents/skills/migrate-to-rstack-cli/references/rslint.md b/.agents/skills/migrate-to-rstack-cli/references/rslint.md index 41037e8b..5e9b6d43 100644 --- a/.agents/skills/migrate-to-rstack-cli/references/rslint.md +++ b/.agents/skills/migrate-to-rstack-cli/references/rslint.md @@ -14,7 +14,10 @@ Read this reference when the project uses `@rslint/core`, `rslint.config.*`, `rs ```ts import { define } from 'rstack'; -define.lint(({ js, ts }) => [js.configs.recommended, ts.configs.recommendedTypeChecked]); +define.lint(({ js, ts }) => [ + js.configs.recommended, + ts.configs.recommendedTypeChecked, +]); ``` Preserve existing presets and rules during migration. diff --git a/examples/test-inline-projects/tests/dom.test.tsx b/examples/test-inline-projects/tests/dom.test.tsx index ed6910fc..c096f3d2 100644 --- a/examples/test-inline-projects/tests/dom.test.tsx +++ b/examples/test-inline-projects/tests/dom.test.tsx @@ -5,5 +5,7 @@ import App from '../src/App'; test('renders the app in a DOM environment', () => { render(); - expect(screen.getByRole('heading', { name: 'Rstack React SSR' })).toBeTruthy(); + expect( + screen.getByRole('heading', { name: 'Rstack React SSR' }), + ).toBeTruthy(); }); diff --git a/packages/create-rstack/src/index.ts b/packages/create-rstack/src/index.ts index f6f32f78..1429da78 100644 --- a/packages/create-rstack/src/index.ts +++ b/packages/create-rstack/src/index.ts @@ -5,7 +5,13 @@ import { create, select, } from '@rstackjs/create-toolkit'; -import { access, appendFile, mkdir, readFile, writeFile } from 'node:fs/promises'; +import { + access, + appendFile, + mkdir, + readFile, + writeFile, +} from 'node:fs/promises'; import path from 'node:path'; const packageRoot = path.join(import.meta.dirname, '..'); @@ -87,12 +93,15 @@ const getTemplateName = async ({ template }: Argv): Promise => { }), ); - return resolveTemplateName(documentationType === 'basic' ? 'doc' : 'doc-i18n'); + return resolveTemplateName( + documentationType === 'basic' ? 'doc' : 'doc-i18n', + ); } const templateType = checkCancel( await select({ - message: projectType === 'app' ? 'Select framework' : 'Select library type', + message: + projectType === 'app' ? 'Select framework' : 'Select library type', options: projectType === 'app' ? [ @@ -129,12 +138,32 @@ const getTemplateName = async ({ template }: Argv): Promise => { }; const getStagedConfig = (templateName: string): string => { - const scriptExtensions = ['js', 'jsx', 'ts', 'tsx', 'mjs', 'cjs', 'mts', 'cts']; - const formatExtensions = ['json', 'jsonc', 'md', 'mdx', 'css', 'html', 'yml', 'yaml']; + const scriptExtensions = [ + 'js', + 'jsx', + 'ts', + 'tsx', + 'mjs', + 'cjs', + 'mts', + 'cts', + ]; + const formatExtensions = [ + 'json', + 'jsonc', + 'md', + 'mdx', + 'css', + 'html', + 'yml', + 'yaml', + ]; const componentExtensions = ['svelte', 'vue']; const templateFormatExtensions = [ ...formatExtensions, - ...componentExtensions.filter((extension) => templateName.includes(extension)), + ...componentExtensions.filter((extension) => + templateName.includes(extension), + ), ]; return [ @@ -157,7 +186,9 @@ const injectStagedSetup = async ({ return; } - const configExtension = await access(path.join(distFolder, 'rstack.config.ts')).then( + const configExtension = await access( + path.join(distFolder, 'rstack.config.ts'), + ).then( () => 'ts', () => 'js', ); @@ -167,8 +198,8 @@ const injectStagedSetup = async ({ }; packageJson.scripts = Object.fromEntries( - Object.entries({ ...packageJson.scripts, prepare: 'rs setup' }).sort(([left], [right]) => - left.localeCompare(right), + Object.entries({ ...packageJson.scripts, prepare: 'rs setup' }).sort( + ([left], [right]) => left.localeCompare(right), ), ); diff --git a/packages/create-rstack/tests/create.test.ts b/packages/create-rstack/tests/create.test.ts index 08c23d7e..63a44649 100644 --- a/packages/create-rstack/tests/create.test.ts +++ b/packages/create-rstack/tests/create.test.ts @@ -31,29 +31,65 @@ type SourceTemplate = { const sourceTemplates: SourceTemplate[] = [ { template: 'app-vanilla', sourceExtension: 'js', testFile: 'dom.test.js' }, - { template: 'app-vanilla-ts', sourceExtension: 'ts', testFile: 'dom.test.ts' }, + { + template: 'app-vanilla-ts', + sourceExtension: 'ts', + testFile: 'dom.test.ts', + }, { template: 'app-react', sourceExtension: 'jsx', testFile: 'index.test.jsx' }, - { template: 'app-react-ts', sourceExtension: 'tsx', testFile: 'index.test.tsx' }, - { template: 'app-preact', sourceExtension: 'jsx', testFile: 'index.test.jsx' }, - { template: 'app-preact-ts', sourceExtension: 'tsx', testFile: 'index.test.tsx' }, + { + template: 'app-react-ts', + sourceExtension: 'tsx', + testFile: 'index.test.tsx', + }, + { + template: 'app-preact', + sourceExtension: 'jsx', + testFile: 'index.test.jsx', + }, + { + template: 'app-preact-ts', + sourceExtension: 'tsx', + testFile: 'index.test.tsx', + }, { template: 'app-vue', sourceExtension: 'js', testFile: 'index.test.js' }, { template: 'app-vue-ts', sourceExtension: 'ts', testFile: 'index.test.ts' }, { template: 'app-lit', sourceExtension: 'js', testFile: 'index.test.js' }, { template: 'app-lit-ts', sourceExtension: 'ts', testFile: 'index.test.ts' }, { template: 'app-svelte', sourceExtension: 'js', testFile: 'index.test.js' }, - { template: 'app-svelte-ts', sourceExtension: 'ts', testFile: 'index.test.ts' }, + { + template: 'app-svelte-ts', + sourceExtension: 'ts', + testFile: 'index.test.ts', + }, { template: 'app-solid', sourceExtension: 'jsx', testFile: 'index.test.jsx' }, - { template: 'app-solid-ts', sourceExtension: 'tsx', testFile: 'index.test.tsx' }, + { + template: 'app-solid-ts', + sourceExtension: 'tsx', + testFile: 'index.test.tsx', + }, { template: 'lib-node', sourceExtension: 'js', testFile: 'index.test.js' }, { template: 'lib-node-ts', sourceExtension: 'ts', testFile: 'index.test.ts' }, { template: 'lib-react', sourceExtension: 'jsx', testFile: 'index.test.jsx' }, - { template: 'lib-react-ts', sourceExtension: 'tsx', testFile: 'index.test.tsx' }, + { + template: 'lib-react-ts', + sourceExtension: 'tsx', + testFile: 'index.test.tsx', + }, { template: 'lib-vue', sourceExtension: 'js', testFile: 'index.test.js' }, { template: 'lib-vue-ts', sourceExtension: 'ts', testFile: 'index.test.ts' }, { template: 'lib-svelte', sourceExtension: 'js', testFile: 'index.test.js' }, - { template: 'lib-svelte-ts', sourceExtension: 'ts', testFile: 'index.test.ts' }, + { + template: 'lib-svelte-ts', + sourceExtension: 'ts', + testFile: 'index.test.ts', + }, { template: 'lib-solid', sourceExtension: 'jsx', testFile: 'index.test.jsx' }, - { template: 'lib-solid-ts', sourceExtension: 'tsx', testFile: 'index.test.tsx' }, + { + template: 'lib-solid-ts', + sourceExtension: 'tsx', + testFile: 'index.test.tsx', + }, ]; const docTemplates = [ @@ -74,14 +110,25 @@ const docTemplates = [ ]; const getCheckScript = (template: string, hasTypeScript: boolean): string => - hasTypeScript && !templatesWithoutTypeCheck.has(template) ? typeCheckScript : checkScript; + hasTypeScript && !templatesWithoutTypeCheck.has(template) + ? typeCheckScript + : checkScript; -const readProjectPackage = async (projectDirectory: string): Promise => - JSON.parse(await readFile(path.join(projectDirectory, 'package.json'), 'utf8')) as ProjectPackage; +const readProjectPackage = async ( + projectDirectory: string, +): Promise => + JSON.parse( + await readFile(path.join(projectDirectory, 'package.json'), 'utf8'), + ) as ProjectPackage; -const expectFiles = async (projectDirectory: string, files: string[]): Promise => { +const expectFiles = async ( + projectDirectory: string, + files: string[], +): Promise => { for (const file of files) { - await expect(access(path.join(projectDirectory, file))).resolves.toBeUndefined(); + await expect( + access(path.join(projectDirectory, file)), + ).resolves.toBeUndefined(); } }; @@ -92,10 +139,16 @@ const expectStagedSetup = async ( ): Promise => { expect(scripts.prepare).toBe('rs setup'); expect( - await readFile(path.join(projectDirectory, '.rstack', 'hooks', 'pre-commit'), 'utf8'), + await readFile( + path.join(projectDirectory, '.rstack', 'hooks', 'pre-commit'), + 'utf8', + ), ).toBe('rs staged\n'); expect( - await readFile(path.join(projectDirectory, `rstack.config.${configExtension}`), 'utf8'), + await readFile( + path.join(projectDirectory, `rstack.config.${configExtension}`), + 'utf8', + ), ).toContain('define.staged({'); }; @@ -109,7 +162,10 @@ const expectNoStagedSetup = async ( access(path.join(projectDirectory, '.rstack', 'hooks', 'pre-commit')), ).rejects.toThrow(); expect( - await readFile(path.join(projectDirectory, `rstack.config.${configExtension}`), 'utf8'), + await readFile( + path.join(projectDirectory, `rstack.config.${configExtension}`), + 'utf8', + ), ).not.toContain('define.staged({'); }; @@ -122,8 +178,14 @@ const expectProjectSetup = async ( const packageJson = await readProjectPackage(projectDirectory); expect(packageJson.name).toBe('my-app'); - expect(packageJson.scripts.check).toBe(getCheckScript(template, hasTypeScript)); - await expectStagedSetup(projectDirectory, configExtension, packageJson.scripts); + expect(packageJson.scripts.check).toBe( + getCheckScript(template, hasTypeScript), + ); + await expectStagedSetup( + projectDirectory, + configExtension, + packageJson.scripts, + ); const tsconfig = access(path.join(projectDirectory, 'tsconfig.json')); if (hasTypeScript) { @@ -135,7 +197,9 @@ const expectProjectSetup = async ( afterEach(async () => { await Promise.all( - tempDirectories.splice(0).map((directory) => rm(directory, { recursive: true, force: true })), + tempDirectories + .splice(0) + .map((directory) => rm(directory, { recursive: true, force: true })), ); }); @@ -154,7 +218,8 @@ const createProject = async ( tempDirectories.push(tempDirectory); if (initializeGitIn) { - const gitDirectory = initializeGitIn === 'project' ? projectDirectory : tempDirectory; + const gitDirectory = + initializeGitIn === 'project' ? projectDirectory : tempDirectory; await mkdir(gitDirectory, { recursive: true }); await execFileAsync('git', ['init', '--quiet'], { cwd: gitDirectory }); } @@ -209,14 +274,22 @@ test.each(sourceTemplates)( files.push('src/env.d.ts'); } - await expectProjectSetup(projectDirectory, template, configExtension, hasTypeScript); + await expectProjectSetup( + projectDirectory, + template, + configExtension, + hasTypeScript, + ); await expectFiles(projectDirectory, files); }, ); -test.each(docTemplates)('creates the $template template', async ({ template, files }) => { - const projectDirectory = await createProject(template); +test.each(docTemplates)( + 'creates the $template template', + async ({ template, files }) => { + const projectDirectory = await createProject(template); - await expectProjectSetup(projectDirectory, template, 'ts', true); - await expectFiles(projectDirectory, files); -}); + await expectProjectSetup(projectDirectory, template, 'ts', true); + await expectFiles(projectDirectory, files); + }, +); diff --git a/packages/rstack/rslib.config.ts b/packages/rstack/rslib.config.ts index 70b66090..2f26a5fc 100644 --- a/packages/rstack/rslib.config.ts +++ b/packages/rstack/rslib.config.ts @@ -2,7 +2,8 @@ import { defineConfig } from '@rslib/core'; import prettierPkgJson from 'prettier/package.json' with { type: 'json' }; import pkgJson from './package.json' with { type: 'json' }; -const fullyMinifiedChunks = /(?:fmt(?:Lsp|Plugins)?|sortPackageJsonPlugin|staged)\.js$/; +const fullyMinifiedChunks = + /(?:fmt(?:Lsp|Plugins)?|sortPackageJsonPlugin|staged)\.js$/; export default defineConfig({ dts: true, diff --git a/packages/rstack/src/cli/args.ts b/packages/rstack/src/cli/args.ts index cf55aa88..29d13f52 100644 --- a/packages/rstack/src/cli/args.ts +++ b/packages/rstack/src/cli/args.ts @@ -5,7 +5,10 @@ import { type ParseArgsOptionsConfig, } from 'node:util'; -type ParseArgsOptionDescriptor = Omit & { +type ParseArgsOptionDescriptor = Omit< + NodeParseArgsOptionDescriptor, + 'default' +> & { default?: never; }; @@ -13,11 +16,14 @@ type ParseArgsConfig = Omit & { options?: Record; }; -type CamelCase = Value extends `${infer Head}-${infer Tail}` - ? `${Head}${Capitalize>}` - : Value; +type CamelCase = + Value extends `${infer Head}-${infer Tail}` + ? `${Head}${Capitalize>}` + : Value; -type NodeParseArgsResult = ReturnType>; +type NodeParseArgsResult = ReturnType< + typeof nodeParseArgs +>; type ParseArgsResult = Omit< NodeParseArgsResult, @@ -25,7 +31,9 @@ type ParseArgsResult = Omit< > & { values: { [ - Name in keyof NodeParseArgsResult['values'] as CamelCase + Name in keyof NodeParseArgsResult['values'] as CamelCase< + Name & string + > ]: NodeParseArgsResult['values'][Name]; }; }; @@ -34,16 +42,20 @@ const KEBAB_CASE_REGEXP = /-([a-z])/g; const toCamelCase = (value: string): string => value.includes('-') - ? value.replace(KEBAB_CASE_REGEXP, (_, character: string) => character.toUpperCase()) + ? value.replace(KEBAB_CASE_REGEXP, (_, character: string) => + character.toUpperCase(), + ) : value; -export function parseArgs( - config?: Config, -): ParseArgsResult { +export function parseArgs< + const Config extends ParseArgsConfig = ParseArgsConfig, +>(config?: Config): ParseArgsResult { const options: ParseArgsOptionsConfig = {}; const optionNames: [originalName: string, camelName: string][] = []; - for (const [originalName, descriptor] of Object.entries(config?.options ?? {})) { + for (const [originalName, descriptor] of Object.entries( + config?.options ?? {}, + )) { const camelName = toCamelCase(originalName); optionNames.push([originalName, camelName]); options[originalName] = descriptor; @@ -61,7 +73,8 @@ export function parseArgs', 'Specify Rstack config file path']; +const CONFIG_OPTION: HelpItem = [ + '-c, --config ', + 'Specify Rstack config file path', +]; const HELP_OPTION: HelpItem = ['-h, --help', 'Display this help message']; const VERSION_OPTION: HelpItem = ['-v, --version', 'Display version number']; const CONFIG_HELP_OPTIONS = [CONFIG_OPTION, HELP_OPTION]; -const OPEN_OPTION: HelpItem = ['-o, --open [url]', 'Open the page in browser on startup']; -const PORT_OPTION: HelpItem = ['--port ', 'Set the port number for the server']; +const OPEN_OPTION: HelpItem = [ + '-o, --open [url]', + 'Open the page in browser on startup', +]; +const PORT_OPTION: HelpItem = [ + '--port ', + 'Set the port number for the server', +]; const STRICT_PORT_OPTION: HelpItem = [ '--strict-port', 'Exit if the specified port is already in use', ]; -const HOST_OPTION: HelpItem = ['--host [host]', 'Set the host that the server listens to']; -const BASE_OPTION: HelpItem = ['--base ', 'Set the base path and override config.base']; -const SERVER_OPTIONS = [OPEN_OPTION, PORT_OPTION, STRICT_PORT_OPTION, HOST_OPTION]; +const HOST_OPTION: HelpItem = [ + '--host [host]', + 'Set the host that the server listens to', +]; +const BASE_OPTION: HelpItem = [ + '--base ', + 'Set the base path and override config.base', +]; +const SERVER_OPTIONS = [ + OPEN_OPTION, + PORT_OPTION, + STRICT_PORT_OPTION, + HOST_OPTION, +]; const TEST_UPDATE_OPTION: HelpItem = ['-u, --update', 'Update snapshot files']; const TEST_COVERAGE_OPTION: HelpItem = ['--coverage', 'Enable code coverage']; -const TEST_PROJECT_OPTION: HelpItem = ['--project ', 'Filter test projects by name']; +const TEST_PROJECT_OPTION: HelpItem = [ + '--project ', + 'Filter test projects by name', +]; const TEST_NAME_OPTION: HelpItem = [ '-t, --test-name-pattern ', 'Run tests with names matching the pattern', @@ -76,8 +99,14 @@ const TEST_OPTIONS = [ TEST_NAME_OPTION, ]; -const LIB_WATCH_OPTION: HelpItem = ['-w, --watch', 'Enable watch mode and rebuild on changes']; -const LIB_DTS_OPTION: HelpItem = ['--dts', 'Emit declaration files (use --no-dts to disable)']; +const LIB_WATCH_OPTION: HelpItem = [ + '-w, --watch', + 'Enable watch mode and rebuild on changes', +]; +const LIB_DTS_OPTION: HelpItem = [ + '--dts', + 'Emit declaration files (use --no-dts to disable)', +]; const LIB_BUILD_OPTIONS = [LIB_WATCH_OPTION, LIB_DTS_OPTION]; const commandHint = (command: string): HelpSection => ({ @@ -123,7 +152,10 @@ const HELP_DEFINITIONS = { sections: [ { title: 'Options', - items: [['--type-check', 'Enable TypeScript type checking'], ...CONFIG_HELP_OPTIONS], + items: [ + ['--type-check', 'Enable TypeScript type checking'], + ...CONFIG_HELP_OPTIONS, + ], }, ], }, @@ -144,7 +176,10 @@ const HELP_DEFINITIONS = { { title: 'Options', items: [ - ['-w, --watch', 'Enable watch mode to automatically rebuild on file changes'], + [ + '-w, --watch', + 'Enable watch mode to automatically rebuild on file changes', + ], ['--dist-path ', 'Set the root directory of output files'], ['--source-map', 'Enable source map'], ...CONFIG_HELP_OPTIONS, @@ -228,7 +263,11 @@ const HELP_DEFINITIONS = { commandHint('test'), { title: 'Options', - items: [['-w, --watch', 'Enable watch mode'], ...TEST_OPTIONS, ...CONFIG_HELP_OPTIONS], + items: [ + ['-w, --watch', 'Enable watch mode'], + ...TEST_OPTIONS, + ...CONFIG_HELP_OPTIONS, + ], }, ], }, @@ -273,7 +312,10 @@ const HELP_DEFINITIONS = { ['--print-location', 'Print test locations'], ['--summary', 'Print a summary'], TEST_PROJECT_OPTION, - ['-t, --test-name-pattern ', 'List tests with names matching the pattern'], + [ + '-t, --test-name-pattern ', + 'List tests with names matching the pattern', + ], ...CONFIG_HELP_OPTIONS, ], }, @@ -339,7 +381,10 @@ const HELP_DEFINITIONS = { { title: 'Options', items: [ - ['--output ', 'Set the output path for inspection results (default: .rsbuild)'], + [ + '--output ', + 'Set the output path for inspection results (default: .rsbuild)', + ], ['--verbose', 'Show complete function definitions in output'], ...CONFIG_HELP_OPTIONS, ], @@ -366,9 +411,15 @@ const HELP_DEFINITIONS = { ['--fix', 'Automatically fix problems'], ['--type-check', 'Enable TypeScript type checking'], ['--type-check-only', 'Run only TypeScript type checking'], - ['--format ', 'Set output format (default | jsonline | github | gitlab)'], + [ + '--format ', + 'Set output format (default | jsonline | github | gitlab)', + ], ['--quiet', 'Report errors only'], - ['--timing [all|N]', 'Print a per-rule timing table (all rules or top N)'], + [ + '--timing [all|N]', + 'Print a per-rule timing table (all rules or top N)', + ], ['--max-warnings ', 'Set the maximum number of warnings'], ['--rule ', 'Override a rule (repeatable)'], ['--no-color', 'Disable colored output'], @@ -388,14 +439,23 @@ const HELP_DEFINITIONS = { ['-w, --write', 'Write formatted files in place (default)'], ['--check', 'Check whether files are formatted'], ['-l, --list-different', 'Print paths of unformatted files'], - ['--ignore-path ', 'Path to an additional ignore file (repeatable)'], + [ + '--ignore-path ', + 'Path to an additional ignore file (repeatable)', + ], ['-u, --ignore-unknown', 'Ignore unknown files'], ['--no-cache', 'Disable the formatting cache'], ['--cache-location ', 'Path to the formatting cache directory'], - ['--no-error-on-unmatched-pattern', 'Do not error when no files match'], + [ + '--no-error-on-unmatched-pattern', + 'Do not error when no files match', + ], ['--with-node-modules', 'Process files inside node_modules'], ['--parallel-workers ', 'Number of parallel workers'], - ['--stdin-filepath ', 'Format stdin as if it were saved at '], + [ + '--stdin-filepath ', + 'Format stdin as if it were saved at ', + ], ['--lsp', 'Run a language server on stdio'], ...CONFIG_HELP_OPTIONS, ], @@ -409,7 +469,10 @@ const HELP_DEFINITIONS = { { title: 'Options', items: [ - ['--allow-empty', 'Allow empty commits when tasks revert all staged changes'], + [ + '--allow-empty', + 'Allow empty commits when tasks revert all staged changes', + ], [ '-p, --concurrent ', 'The number of tasks to run concurrently, or false for serial', @@ -435,7 +498,10 @@ const HELP_DEFINITIONS = { { title: 'Options', items: [ - ['--hooks-dir ', 'Specify hooks directory relative to the Git repository root'], + [ + '--hooks-dir ', + 'Specify hooks directory relative to the Git repository root', + ], HELP_OPTION, ], }, @@ -444,10 +510,15 @@ const HELP_DEFINITIONS = { } satisfies Record; const renderItems = (items: readonly HelpItem[]): string => { - const labelWidth = items.reduce((width, [label]) => Math.max(width, label.length), 0); + const labelWidth = items.reduce( + (width, [label]) => Math.max(width, label.length), + 0, + ); return items - .map(([label, description]) => ` ${label.padEnd(labelWidth)} ${description}`) + .map( + ([label, description]) => ` ${label.padEnd(labelWidth)} ${description}`, + ) .join('\n'); }; @@ -459,7 +530,11 @@ const renderSection = (section: HelpSection): string => { return section.dim ? color.dim(section.content) : section.content; }; -const renderHelp = ({ usage, description, sections = [] }: HelpDefinition): string => { +const renderHelp = ({ + usage, + description, + sections = [], +}: HelpDefinition): string => { const blocks = [ color.bold(`Rstack v${RSTACK_VERSION}`), `${color.cyan('Usage')}:\n${color.yellow(` $ ${usage}`)}`, @@ -474,4 +549,5 @@ const renderHelp = ({ usage, description, sections = [] }: HelpDefinition): stri return blocks.join('\n\n'); }; -export const renderCommandHelp = (topic: HelpTopic): string => renderHelp(HELP_DEFINITIONS[topic]); +export const renderCommandHelp = (topic: HelpTopic): string => + renderHelp(HELP_DEFINITIONS[topic]); diff --git a/packages/rstack/src/cli/commands.ts b/packages/rstack/src/cli/commands.ts index 07ed732e..4368dbea 100644 --- a/packages/rstack/src/cli/commands.ts +++ b/packages/rstack/src/cli/commands.ts @@ -18,7 +18,11 @@ async function runRsbuildCLI(args: string[]): Promise { const argv = [ process.execPath, 'rsbuild', - ...insertConfigArg(args, '--config', join(import.meta.dirname, 'rsbuildConfig.js')), + ...insertConfigArg( + args, + '--config', + join(import.meta.dirname, 'rsbuildConfig.js'), + ), ]; const { runCLI } = await import('@rsbuild/core'); @@ -46,7 +50,11 @@ async function runRstestCLI(args: string[]): Promise { const argv = [ process.execPath, 'rstest', - ...insertConfigArg(args, '--config', join(import.meta.dirname, 'rstestConfig.js')), + ...insertConfigArg( + args, + '--config', + join(import.meta.dirname, 'rstestConfig.js'), + ), ]; const { runCLI } = await import('@rstest/core'); @@ -70,7 +78,11 @@ async function runRslibCLI(args: string[]): Promise { const argv = [ process.execPath, 'rslib', - ...insertConfigArg(args, '--config', join(import.meta.dirname, 'rslibConfig.js')), + ...insertConfigArg( + args, + '--config', + join(import.meta.dirname, 'rslibConfig.js'), + ), ]; const { runCLI } = await import('@rslib/core'); @@ -83,7 +95,9 @@ const isMissingRspressCoreError = (error: unknown): boolean => { } const code = 'code' in error ? error.code : undefined; - return code === 'ERR_MODULE_NOT_FOUND' && error.message.includes('@rspress/core'); + return ( + code === 'ERR_MODULE_NOT_FOUND' && error.message.includes('@rspress/core') + ); }; async function runRspressCLI(args: string[]): Promise { @@ -103,7 +117,11 @@ async function runRspressCLI(args: string[]): Promise { const argv = [ process.execPath, 'rspress', - ...insertConfigArg(args, '--config', join(import.meta.dirname, 'rspressConfig.js')), + ...insertConfigArg( + args, + '--config', + join(import.meta.dirname, 'rspressConfig.js'), + ), ]; try { @@ -128,7 +146,11 @@ async function runRslintCLI(args: string[]): Promise { const argv = [ process.execPath, 'rslint', - ...insertConfigArg(args, '--config', join(import.meta.dirname, 'rslintConfig.js')), + ...insertConfigArg( + args, + '--config', + join(import.meta.dirname, 'rslintConfig.js'), + ), ]; const { runCLI } = await import('@rslint/core'); @@ -171,7 +193,8 @@ export async function setupCommands(): Promise { // when the config is later loaded from another directory. The motivating case // is `rs fmt --lsp`, which loads the config from the LSP workspace root the // client reports, and that root need not be the process working directory. - getConfigState().configPath = configPath === undefined ? undefined : resolve(configPath); + getConfigState().configPath = + configPath === undefined ? undefined : resolve(configPath); if (!command || command === '-h' || command === '--help') { return printCommandHelp('root'); diff --git a/packages/rstack/src/config.ts b/packages/rstack/src/config.ts index 222e6ff3..ecc3983b 100644 --- a/packages/rstack/src/config.ts +++ b/packages/rstack/src/config.ts @@ -8,7 +8,8 @@ import type { RstestConfigExport } from '@rstest/core'; import type { FmtConfigDefinition } from './fmt/types.ts'; import type { StagedConfig } from './staged.ts'; -export type RslintConfigDefinition = RslintConfig | (() => Promise); +export type RslintConfigDefinition = + RslintConfig | (() => Promise); export type RspressConfigDefinition = UserConfig | UserConfigAsyncFn; type RslintConfigFactory = ( @@ -62,7 +63,8 @@ type ConfigState = { declare global { // rslint-disable-next-line no-var - var __rstackConfigSessionStorage: AsyncLocalStorage | undefined; + var __rstackConfigSessionStorage: + AsyncLocalStorage | undefined; // rslint-disable-next-line no-var var __rstackCliState: ConfigState | undefined; } @@ -72,7 +74,8 @@ const getConfigSessionStorage = (): AsyncLocalStorage => { // imports the internal Rstack config. Keep the storage on globalThis so // every module instance reads and writes the same active session. if (!globalThis.__rstackConfigSessionStorage) { - globalThis.__rstackConfigSessionStorage = new AsyncLocalStorage(); + globalThis.__rstackConfigSessionStorage = + new AsyncLocalStorage(); } return globalThis.__rstackConfigSessionStorage; @@ -152,11 +155,16 @@ type Define = { staged: (config: StagedConfig) => void; }; -const setConfig = (type: T, config: Configs[T]): void => { +const setConfig = ( + type: T, + config: Configs[T], +): void => { const session = getConfigSessionStorage().getStore(); if (!session?.active) { - throw new Error(`The "${type}" config must be defined while loading an Rstack config.`); + throw new Error( + `The "${type}" config must be defined while loading an Rstack config.`, + ); } if (type in session.configs) { @@ -173,7 +181,9 @@ export const define: Define = { lint: (config) => setConfig( 'lint', - typeof config === 'function' ? async () => config(await import('@rslint/core')) : config, + typeof config === 'function' + ? async () => config(await import('@rslint/core')) + : config, ), fmt: (config) => setConfig('fmt', config), staged: (config) => setConfig('staged', config), diff --git a/packages/rstack/src/fmt/cacheIdentity.ts b/packages/rstack/src/fmt/cacheIdentity.ts index f7ba278d..81d7bdd6 100644 --- a/packages/rstack/src/fmt/cacheIdentity.ts +++ b/packages/rstack/src/fmt/cacheIdentity.ts @@ -17,7 +17,11 @@ const createCacheHash = (content: string | Uint8Array): string => createDigest('sha256', content, 'base64url').slice(0, cacheHashLength); /** Identifies formatter behavior shared by all cache entries in this process. */ -const cacheNamespace: string = JSON.stringify([fmtCacheVersion, RSTACK_VERSION, PRETTIER_VERSION]); +const cacheNamespace: string = JSON.stringify([ + fmtCacheVersion, + RSTACK_VERSION, + PRETTIER_VERSION, +]); /** Creates project-relative POSIX cache keys without repeating path setup. */ const createCacheKeyResolver = (rootPath: string): CacheKeyResolver => { @@ -30,7 +34,9 @@ const createCacheKeyResolver = (rootPath: string): CacheKeyResolver => { }; /** Hashes final per-file options and memoizes option objects shared by many files. */ -const createOptionsHasher = (pluginFingerprints?: PluginFingerprints): OptionsHasher => { +const createOptionsHasher = ( + pluginFingerprints?: PluginFingerprints, +): OptionsHasher => { const hashes = new WeakMap(); return (options) => { @@ -47,8 +53,13 @@ const createOptionsHasher = (pluginFingerprints?: PluginFingerprints): OptionsHa const fingerprints: string[] = []; for (const plugin of plugins) { const key = - plugin instanceof URL ? plugin.href : typeof plugin === 'string' ? plugin : undefined; - const fingerprint = key === undefined ? undefined : pluginFingerprints?.get(key); + plugin instanceof URL + ? plugin.href + : typeof plugin === 'string' + ? plugin + : undefined; + const fingerprint = + key === undefined ? undefined : pluginFingerprints?.get(key); if (fingerprint === undefined) { hashes.set(options, null); return undefined; diff --git a/packages/rstack/src/fmt/cacheStore.ts b/packages/rstack/src/fmt/cacheStore.ts index 71622c62..abecddae 100644 --- a/packages/rstack/src/fmt/cacheStore.ts +++ b/packages/rstack/src/fmt/cacheStore.ts @@ -21,7 +21,11 @@ const fmtCacheStateIds = { } as const satisfies Record; type FmtCacheFileValue = string | number; -type FmtCacheEntry = readonly [contentHash: string, optionsHash: string, state: FmtCacheState]; +type FmtCacheEntry = readonly [ + contentHash: string, + optionsHash: string, + state: FmtCacheState, +]; interface FmtCacheFile { version: typeof fmtCacheVersion; @@ -106,7 +110,8 @@ const parseCacheFile = ( }; }; -const serializeCache = (cache: FmtCacheFile): string => `${JSON.stringify(cache)}\n`; +const serializeCache = (cache: FmtCacheFile): string => + `${JSON.stringify(cache)}\n`; const isFileNotFoundError = (error: unknown): error is NodeJS.ErrnoException => error instanceof Error && 'code' in error && error.code === 'ENOENT'; @@ -150,7 +155,8 @@ class FmtCacheStoreImpl implements FmtCacheStore { const { files, options } = this.#cache; const contentHash = files[offset + contentHashOffset] as string; const optionsHash = options[files[offset + optionsIndexOffset] as number]; - const state = fmtCacheStates[files[offset + stateOffset] as FmtCacheStateId]; + const state = + fmtCacheStates[files[offset + stateOffset] as FmtCacheStateId]; return [contentHash, optionsHash, state]; } @@ -261,7 +267,10 @@ class FmtCacheStoreImpl implements FmtCacheStore { } } -const loadFmtCacheStore = async (filePath: string, namespace: string): Promise => { +const loadFmtCacheStore = async ( + filePath: string, + namespace: string, +): Promise => { const emptyCache = createEmptyCache(namespace); try { diff --git a/packages/rstack/src/fmt/cli.ts b/packages/rstack/src/fmt/cli.ts index 6bd314f2..7b13f498 100644 --- a/packages/rstack/src/fmt/cli.ts +++ b/packages/rstack/src/fmt/cli.ts @@ -35,15 +35,25 @@ const parseMaxWorkers = (value: string | undefined): number | undefined => { } const maxWorkers = Number(value); - if (!/^\d+$/.test(value) || !Number.isSafeInteger(maxWorkers) || maxWorkers < 1) { - throw new Error('The --parallel-workers option must be a positive integer.'); + if ( + !/^\d+$/.test(value) || + !Number.isSafeInteger(maxWorkers) || + maxWorkers < 1 + ) { + throw new Error( + 'The --parallel-workers option must be a positive integer.', + ); } return maxWorkers; }; /** Rejects the mode flags and file arguments that a server-like option replaces. */ -const assertExclusiveMode = (option: string, hasMode: boolean, positionals: string[]): void => { +const assertExclusiveMode = ( + option: string, + hasMode: boolean, + positionals: string[], +): void => { if (hasMode) { throw new Error( `The ${option} option cannot be used with --write, --check, or --list-different.`, @@ -82,7 +92,9 @@ const parseFmtArgs = (args: string[]): ParsedFmtCLIArgs => { const listDifferent = values.listDifferent; const modes = [write, check, listDifferent].filter(Boolean); if (modes.length > 1) { - throw new Error('The --write, --check, and --list-different options cannot be used together.'); + throw new Error( + 'The --write, --check, and --list-different options cannot be used together.', + ); } const mode = check ? 'check' : listDifferent ? 'list-different' : 'write'; @@ -130,14 +142,17 @@ const parseFmtArgs = (args: string[]): ParsedFmtCLIArgs => { }; }; -const createDisplayPathResolver = (cwd: string): ((filePath: string) => string) => { +const createDisplayPathResolver = ( + cwd: string, +): ((filePath: string) => string) => { const resolveRelativePath = createRelativePathResolver(cwd); return (filePath) => toPosixPath(resolveRelativePath(filePath)); }; const prettyTime = (seconds: number): string => { - const format = (time: string, unit: 'm' | 's') => color.bold(`${time}${unit}`); + const format = (time: string, unit: 'm' | 's') => + color.bold(`${time}${unit}`); if (seconds < 10) { const digits = seconds >= 0.01 ? 2 : 3; @@ -156,7 +171,10 @@ const prettyTime = (seconds: number): string => { return minutesLabel; } - const secondsLabel = format(remainingSeconds.toFixed(remainingSeconds % 1 === 0 ? 0 : 1), 's'); + const secondsLabel = format( + remainingSeconds.toFixed(remainingSeconds % 1 === 0 ? 0 : 1), + 's', + ); return `${minutesLabel} ${secondsLabel}`; }; @@ -171,7 +189,9 @@ const reportNoSupportedFiles = (patterns: string[]): void => { const targets = (patterns.length ? patterns : ['.']) .map((pattern) => color.cyan(JSON.stringify(pattern))) .join(', '); - logger.error(`No supported files matched ${targets}, or all matching files were ignored.`); + logger.error( + `No supported files matched ${targets}, or all matching files were ignored.`, + ); process.exitCode = 2; }; @@ -303,7 +323,9 @@ const runFmtCLI = async (args: string[]): Promise => { return; } - const cacheDirPath = cacheLocation ? path.resolve(cwd, cacheLocation) : undefined; + const cacheDirPath = cacheLocation + ? path.resolve(cwd, cacheLocation) + : undefined; if (cacheDirPath) { const cacheDirPrefix = cacheDirPath.endsWith(path.sep) ? cacheDirPath @@ -327,7 +349,8 @@ const runFmtCLI = async (args: string[]): Promise => { if (files.length === 0) { // Staged tasks may pass only paths excluded by formatter ignore rules. - const allowUnmatched = noErrorOnUnmatchedPattern || process.env.RSTACK_STAGED === '1'; + const allowUnmatched = + noErrorOnUnmatchedPattern || process.env.RSTACK_STAGED === '1'; if (allowUnmatched) { return; } diff --git a/packages/rstack/src/fmt/config.ts b/packages/rstack/src/fmt/config.ts index ee0a5123..5e4c543d 100644 --- a/packages/rstack/src/fmt/config.ts +++ b/packages/rstack/src/fmt/config.ts @@ -26,7 +26,9 @@ type OptionsCacheNode = { options: ResolvedFmtOptions; }; -const createOptionsCacheNode = (options: ResolvedFmtOptions): OptionsCacheNode => ({ +const createOptionsCacheNode = ( + options: ResolvedFmtOptions, +): OptionsCacheNode => ({ children: new WeakMap(), options, }); @@ -52,7 +54,9 @@ const compileMatchers = ( return micromatch.matcher(patterns[0], options); } - const matchers = patterns.map((pattern) => micromatch.matcher(pattern, options)); + const matchers = patterns.map((pattern) => + micromatch.matcher(pattern, options), + ); return (filePath) => { for (const matches of matchers) { @@ -79,7 +83,11 @@ const createPathMatcher = ( } } - const basenameMatcher = compileMatchers(basenamePatterns, excludedPatterns, true); + const basenameMatcher = compileMatchers( + basenamePatterns, + excludedPatterns, + true, + ); const pathMatcher = compileMatchers(pathPatterns, excludedPatterns, false); if (!basenameMatcher || !pathMatcher) { @@ -89,7 +97,10 @@ const createPathMatcher = ( }; /** Splits a flat config into project-level formatting options and rules. */ -const normalizeFmtConfig = (config: FmtConfig | undefined, rootPath: string): ResolvedFmtConfig => { +const normalizeFmtConfig = ( + config: FmtConfig | undefined, + rootPath: string, +): ResolvedFmtConfig => { const { ignorePatterns = [], overrides = [], ...baseOptions } = config ?? {}; return { @@ -104,7 +115,9 @@ const normalizeFmtConfig = (config: FmtConfig | undefined, rootPath: string): Re }; /** Creates a reusable resolver for applying per-file formatter overrides. */ -const createOptionsResolver = (config: ResolvedFmtConfig): FmtOptionsResolver => { +const createOptionsResolver = ( + config: ResolvedFmtConfig, +): FmtOptionsResolver => { if (config.overrides.length === 0) { return () => config.baseOptions; } @@ -124,7 +137,10 @@ const createOptionsResolver = (config: ResolvedFmtConfig): FmtOptionsResolver => // Reuse the merged result for this override after the current matched sequence. let nextCacheNode = cacheNode.children.get(override.options); if (!nextCacheNode) { - nextCacheNode = createOptionsCacheNode({ ...cacheNode.options, ...override.options }); + nextCacheNode = createOptionsCacheNode({ + ...cacheNode.options, + ...override.options, + }); cacheNode.children.set(override.options, nextCacheNode); } cacheNode = nextCacheNode; @@ -140,7 +156,8 @@ const resolveFmtConfig = async ({ configFilePath, cwd, }: ResolveFmtConfigOptions): Promise => { - const config = typeof definition === 'function' ? await definition() : definition; + const config = + typeof definition === 'function' ? await definition() : definition; const rootPath = configFilePath ? dirname(configFilePath) : cwd; return normalizeFmtConfig(config, rootPath); diff --git a/packages/rstack/src/fmt/discoverPaths.ts b/packages/rstack/src/fmt/discoverPaths.ts index 0a213400..32e94431 100644 --- a/packages/rstack/src/fmt/discoverPaths.ts +++ b/packages/rstack/src/fmt/discoverPaths.ts @@ -142,7 +142,10 @@ class GitIgnoreFiles { } /** Matches one directory's entries in a single native call. */ - matchDirents(parentPath: string, dirents: Dirent[]): boolean | number | Uint8Array | undefined { + matchDirents( + parentPath: string, + dirents: Dirent[], + ): boolean | number | Uint8Array | undefined { if (!this.#hasRules || dirents.length === 0) { return; } @@ -156,7 +159,11 @@ class GitIgnoreFiles { if (dirents.length === 1) { const dirent = dirents[0]; - return this.#matcher!.isIgnoredChild(relativeParent, dirent.name, dirent.isDirectory()); + return this.#matcher!.isIgnoredChild( + relativeParent, + dirent.name, + dirent.isDirectory(), + ); } const names = new Array(dirents.length); @@ -168,7 +175,11 @@ class GitIgnoreFiles { names[index] = dirent.name; directoryMask |= Number(dirent.isDirectory()) << index; } - return this.#matcher!.isIgnoredBatchMask(relativeParent, names, directoryMask >>> 0); + return this.#matcher!.isIgnoredBatchMask( + relativeParent, + names, + directoryMask >>> 0, + ); } const directoryFlags = new Uint8Array(dirents.length); @@ -188,9 +199,14 @@ class GitIgnoreFiles { } // Ignore files may disappear or become unreadable during traversal. - const loading = readFile(path.join(directoryPath, '.gitignore'), 'utf8').then( + const loading = readFile( + path.join(directoryPath, '.gitignore'), + 'utf8', + ).then( (content) => { - const relativePath = toPosixPath(this.#resolveRelativePath(directoryPath)); + const relativePath = toPosixPath( + this.#resolveRelativePath(directoryPath), + ); this.#matcher ??= new (loadNativeBinding().GitIgnoreMatcher)(); this.#hasRules = this.#matcher.addSource(relativePath, content); }, @@ -222,7 +238,8 @@ const createTraversalOptions = ( if (dirent.isDirectory()) { return ( - (dirent as GitIgnoreDirent)[gitIgnored] === true || isIgnored?.(targetPath, true) === true + (dirent as GitIgnoreDirent)[gitIgnored] === true || + isIgnored?.(targetPath, true) === true ); } @@ -298,7 +315,14 @@ const discoverDirectoryFiles = async ( const result = await readdir( rootPath, - createTraversalOptions(gitIgnore, ignoredDirNames, signal, onError, isIncluded, isIgnored), + createTraversalOptions( + gitIgnore, + ignoredDirNames, + signal, + onError, + isIncluded, + isIgnored, + ), ); // tiny-readdir only handles fulfilled onDirents promises, so rethrow after its counter settles. @@ -310,7 +334,9 @@ const discoverDirectoryFiles = async ( }; const normalizeGlob = (cwd: string, pattern: string): string => { - const relativePattern = path.isAbsolute(pattern) ? path.relative(cwd, pattern) : pattern; + const relativePattern = path.isAbsolute(pattern) + ? path.relative(cwd, pattern) + : pattern; return toPosixPath(relativePattern); }; @@ -334,7 +360,10 @@ const classifyPatterns = async ( const entries = await Promise.all( patterns.map(async (pattern): Promise => { if (pattern.startsWith('!')) { - return { kind: 'negative-glob', value: normalizeGlob(cwd, pattern.slice(1)) }; + return { + kind: 'negative-glob', + value: normalizeGlob(cwd, pattern.slice(1)), + }; } const filePath = path.resolve(cwd, pattern); @@ -344,7 +373,9 @@ const classifyPatterns = async ( const stats = await lstatSafe(filePath); if (stats?.isFile()) { - return isBinaryPath(filePath) ? undefined : { kind: 'file', value: filePath }; + return isBinaryPath(filePath) + ? undefined + : { kind: 'file', value: filePath }; } if (stats?.isDirectory()) { return { kind: 'directory', value: filePath }; @@ -389,11 +420,15 @@ const classifyPatterns = async ( }; const getOutermostPaths = (paths: string[]): string[] => { - const sortedPaths = [...new Set(paths)].sort((left, right) => left.length - right.length); + const sortedPaths = [...new Set(paths)].sort( + (left, right) => left.length - right.length, + ); const outermostPaths: string[] = []; for (const filePath of sortedPaths) { - if (!outermostPaths.some((parentPath) => isPathInside(parentPath, filePath))) { + if ( + !outermostPaths.some((parentPath) => isPathInside(parentPath, filePath)) + ) { outermostPaths.push(filePath); } } @@ -402,8 +437,14 @@ const getOutermostPaths = (paths: string[]): string[] => { }; /** Merges overlapping roots; micromatch remains responsible for glob syntax. */ -const getTraversalRoots = (cwd: string, directories: string[], globs: string[]): string[] => { - const globRoots = globs.map((pattern) => path.resolve(cwd, micromatch.scan(pattern).base || '.')); +const getTraversalRoots = ( + cwd: string, + directories: string[], + globs: string[], +): string[] => { + const globRoots = globs.map((pattern) => + path.resolve(cwd, micromatch.scan(pattern).base || '.'), + ); return getOutermostPaths([...directories, ...globRoots]); }; @@ -431,9 +472,13 @@ const discoverFmtPaths = async ({ negativeGlobs, } = await classifyPatterns(cwd, patterns, ignoredDirNames); const directoryRoots = getOutermostPaths(directories); - const globMatchers = globs.map((pattern) => micromatch.matcher(pattern, { dot: true })); + const globMatchers = globs.map((pattern) => + micromatch.matcher(pattern, { dot: true }), + ); const candidates = new Set( - isIgnored ? explicitFiles.filter((filePath) => !isIgnored(filePath, false)) : explicitFiles, + isIgnored + ? explicitFiles.filter((filePath) => !isIgnored(filePath, false)) + : explicitFiles, ); const traversalRoots = getTraversalRoots(cwd, directoryRoots, globs); @@ -447,7 +492,10 @@ const discoverFmtPaths = async ({ } await gitIgnore.loadThrough(rootPath); - if (gitIgnore.isIgnored(rootPath, true) || isIgnored?.(rootPath, true) === true) { + if ( + gitIgnore.isIgnored(rootPath, true) || + isIgnored?.(rootPath, true) === true + ) { return []; } @@ -457,7 +505,11 @@ const discoverFmtPaths = async ({ const isIncluded = includesAll ? undefined : (filePath: string): boolean => { - if (directoryRoots.some((directoryPath) => isPathInside(directoryPath, filePath))) { + if ( + directoryRoots.some((directoryPath) => + isPathInside(directoryPath, filePath), + ) + ) { return true; } @@ -465,7 +517,13 @@ const discoverFmtPaths = async ({ return globMatchers.some((matches) => matches(relativePath)); }; - return discoverDirectoryFiles(rootPath, gitIgnore, ignoredDirNames, isIncluded, isIgnored); + return discoverDirectoryFiles( + rootPath, + gitIgnore, + ignoredDirNames, + isIncluded, + isIgnored, + ); }), ); diff --git a/packages/rstack/src/fmt/discovery.ts b/packages/rstack/src/fmt/discovery.ts index cf1c3472..8e238de1 100644 --- a/packages/rstack/src/fmt/discovery.ts +++ b/packages/rstack/src/fmt/discovery.ts @@ -19,7 +19,9 @@ const discoverFmtFiles = async ({ config, }: DiscoverFmtFilesOptions): Promise => { const isIgnored = await createIgnoreMatcher({ config, cwd, ignorePaths }); - const isExcluded = excludedDirPath ? createDirMatcher(excludedDirPath) : undefined; + const isExcluded = excludedDirPath + ? createDirMatcher(excludedDirPath) + : undefined; const shouldIgnore = isExcluded ? (filePath: string, isDirectory = false) => isExcluded(filePath) || isIgnored(filePath, isDirectory) diff --git a/packages/rstack/src/fmt/fileResolver.ts b/packages/rstack/src/fmt/fileResolver.ts index d6cc0abf..aa74151c 100644 --- a/packages/rstack/src/fmt/fileResolver.ts +++ b/packages/rstack/src/fmt/fileResolver.ts @@ -16,7 +16,9 @@ const createFmtFileResolver = (config: ResolvedFmtConfig): FmtFileResolver => { pluginResolver ??= import( /* rspackChunkName: 'fmtPlugins' */ './plugins.ts' - ).then(({ createPluginResolver }) => createPluginResolver(config.rootPath)); + ).then(({ createPluginResolver }) => + createPluginResolver(config.rootPath), + ); options = (await pluginResolver)(options); } diff --git a/packages/rstack/src/fmt/format.ts b/packages/rstack/src/fmt/format.ts index 0aa653af..d2aac91c 100644 --- a/packages/rstack/src/fmt/format.ts +++ b/packages/rstack/src/fmt/format.ts @@ -12,7 +12,8 @@ import type { FmtFileRequest } from './types.ts'; type PrettierPlugins = NonNullable; type FormatFmtSourceResult = - { status: 'unsupported' } | { status: 'formatted'; source: string; formatted: string }; + | { status: 'unsupported' } + | { status: 'formatted'; source: string; formatted: string }; const fileInfoOptions = { ignorePath: [], diff --git a/packages/rstack/src/fmt/ignore.ts b/packages/rstack/src/fmt/ignore.ts index 6874394c..48e52105 100644 --- a/packages/rstack/src/fmt/ignore.ts +++ b/packages/rstack/src/fmt/ignore.ts @@ -29,10 +29,14 @@ const createDefaultMatcher = (): IgnorePredicate => { const createSourceMatcher = (sources: IgnoreSource[]): IgnorePredicate => { const matcher = new (loadNativeBinding().IgnoreMatcher)(sources); - return (filePath, isDirectory = false) => matcher.isIgnored(filePath, isDirectory); + return (filePath, isDirectory = false) => + matcher.isIgnored(filePath, isDirectory); }; -const loadIgnoreSource = async (cwd: string, ignorePath: string): Promise => { +const loadIgnoreSource = async ( + cwd: string, + ignorePath: string, +): Promise => { const filePath = path.resolve(cwd, ignorePath); let patterns: string; diff --git a/packages/rstack/src/fmt/lsp/minimalEdit.ts b/packages/rstack/src/fmt/lsp/minimalEdit.ts index d391d955..97cfba7f 100644 --- a/packages/rstack/src/fmt/lsp/minimalEdit.ts +++ b/packages/rstack/src/fmt/lsp/minimalEdit.ts @@ -8,8 +8,10 @@ interface MinimalEdit { const CARRIAGE_RETURN = 0x0d; const LINE_FEED = 0x0a; -const isHighSurrogate = (code: number): boolean => code >= 0xd800 && code <= 0xdbff; -const isLowSurrogate = (code: number): boolean => code >= 0xdc00 && code <= 0xdfff; +const isHighSurrogate = (code: number): boolean => + code >= 0xd800 && code <= 0xdbff; +const isLowSurrogate = (code: number): boolean => + code >= 0xdc00 && code <= 0xdfff; /** * True when `index` splits a unit that occupies a single position: a surrogate @@ -33,7 +35,10 @@ const splitsIndivisibleUnit = (text: string, index: number): boolean => { * ends instead of replacing the whole document, which keeps selections, folds, * and undo history intact. Offsets are converted to positions by the caller. */ -const computeMinimalEdit = (source: string, formatted: string): MinimalEdit | undefined => { +const computeMinimalEdit = ( + source: string, + formatted: string, +): MinimalEdit | undefined => { if (source === formatted) { return undefined; } @@ -109,7 +114,10 @@ interface MinimalTextEdit { * `\r\n`, or a lone `\r`, like the protocol's. `computeMinimalEdit` keeping * boundaries out of surrogate pairs and `\r\n` is what makes the mapping exact. */ -const computeMinimalTextEdit = (source: string, formatted: string): MinimalTextEdit | undefined => { +const computeMinimalTextEdit = ( + source: string, + formatted: string, +): MinimalTextEdit | undefined => { const edit = computeMinimalEdit(source, formatted); if (!edit) { return undefined; diff --git a/packages/rstack/src/fmt/lsp/server.ts b/packages/rstack/src/fmt/lsp/server.ts index ec0be83e..f2a5742f 100644 --- a/packages/rstack/src/fmt/lsp/server.ts +++ b/packages/rstack/src/fmt/lsp/server.ts @@ -9,7 +9,10 @@ import { type InitializeParams, type TextEdit, } from 'vscode-languageserver/node'; -import { createFmtFileResolver, type FmtFileResolver } from '../fileResolver.ts'; +import { + createFmtFileResolver, + type FmtFileResolver, +} from '../fileResolver.ts'; import { formatFmtSource } from '../format.ts'; import { createIgnoreMatcher, type IgnorePredicate } from '../ignore.ts'; import type { ResolvedFmtConfig } from '../types.ts'; @@ -67,7 +70,8 @@ const redirectConsoleToConnection = (connection: Connection): void => { connection.console.log(serializeConsoleArguments(args)); console.trace = (...args: unknown[]): void => { const stack = new Error().stack?.replace(/(.+\n){2}/, '') ?? ''; - const message = args.length === 0 ? 'Trace' : `Trace: ${serializeConsoleArguments(args)}`; + const message = + args.length === 0 ? 'Trace' : `Trace: ${serializeConsoleArguments(args)}`; connection.console.log(`${message}\n${stack}`); }; console.assert = (assertion?: unknown, ...args: unknown[]): void => { @@ -99,7 +103,9 @@ const redirectConsoleToConnection = (connection: Connection): void => { const resolveWorkspaceRoot = (params: InitializeParams): string | undefined => { const rootUri = params.workspaceFolders?.[0]?.uri ?? params.rootUri; - return (rootUri ? toFilePath(rootUri) : undefined) ?? params.rootPath ?? undefined; + return ( + (rootUri ? toFilePath(rootUri) : undefined) ?? params.rootPath ?? undefined + ); }; /** Loads everything a formatting request needs, once per server lifetime. */ @@ -155,7 +161,10 @@ const createDocumentEdits = async ( return []; } - const edit = formatted === undefined ? undefined : computeMinimalTextEdit(source, formatted); + const edit = + formatted === undefined + ? undefined + : computeMinimalTextEdit(source, formatted); return edit ? [edit] : []; }; @@ -198,25 +207,27 @@ const startFmtLsp = (options: RunFmtLspOptions, onExit: () => void): void => { // TODO: watch the config file and reset the session when it changes. const getSession = (): Promise => - (sessionPromise ??= createFmtLspSession({ ...options, root }).catch((error: unknown) => { - // Retry on the next request rather than caching the failure forever. - sessionPromise = undefined; - // A workspace that cannot be set up returns no edits for every document, - // which looks like "nothing to format" in editors that hide the server - // log, so it is shown to the user instead of only being logged. Repeats - // of the same failure stay silent so saving a file cannot spam the editor. - const message = `rs fmt cannot format this workspace: ${String(error)}`; - if (reportedSessionError !== message) { - reportedSessionError = message; - // A notification rather than `window.showErrorMessage`, which sends a - // request the server would then wait on for a response it does not need. - void connection.sendNotification(ShowMessageNotification.type, { - type: MessageType.Error, - message, - }); - } - throw error; - })); + (sessionPromise ??= createFmtLspSession({ ...options, root }).catch( + (error: unknown) => { + // Retry on the next request rather than caching the failure forever. + sessionPromise = undefined; + // A workspace that cannot be set up returns no edits for every document, + // which looks like "nothing to format" in editors that hide the server + // log, so it is shown to the user instead of only being logged. Repeats + // of the same failure stay silent so saving a file cannot spam the editor. + const message = `rs fmt cannot format this workspace: ${String(error)}`; + if (reportedSessionError !== message) { + reportedSessionError = message; + // A notification rather than `window.showErrorMessage`, which sends a + // request the server would then wait on for a response it does not need. + void connection.sendNotification(ShowMessageNotification.type, { + type: MessageType.Error, + message, + }); + } + throw error; + }, + )); connection.onExit(onExit); @@ -234,26 +245,30 @@ const startFmtLsp = (options: RunFmtLspOptions, onExit: () => void): void => { }; }); - connection.onDocumentFormatting(async ({ textDocument }): Promise => { - const filePath = toFilePath(textDocument.uri); - if (!filePath) { - return []; - } + connection.onDocumentFormatting( + async ({ textDocument }): Promise => { + const filePath = toFilePath(textDocument.uri); + if (!filePath) { + return []; + } - // A formatting failure must never disrupt editing; unsupported, ignored, - // and unparsable documents all resolve to "no edits". - try { - const session = await getSession(); + // A formatting failure must never disrupt editing; unsupported, ignored, + // and unparsable documents all resolve to "no edits". + try { + const session = await getSession(); - return await createDocumentEdits( - () => documents.get(textDocument.uri), - (source) => formatDocumentSource(session, filePath, source), - ); - } catch (error) { - connection.console.error(`Failed to format "${filePath}": ${String(error)}`); - return []; - } - }); + return await createDocumentEdits( + () => documents.get(textDocument.uri), + (source) => formatDocumentSource(session, filePath, source), + ); + } catch (error) { + connection.console.error( + `Failed to format "${filePath}": ${String(error)}`, + ); + return []; + } + }, + ); connection.listen(); }; diff --git a/packages/rstack/src/fmt/pathHelpers.ts b/packages/rstack/src/fmt/pathHelpers.ts index 1ad48566..a8f72f9e 100644 --- a/packages/rstack/src/fmt/pathHelpers.ts +++ b/packages/rstack/src/fmt/pathHelpers.ts @@ -3,10 +3,14 @@ import path from 'node:path'; type RelativePathResolver = (filePath: string) => string; const toPosixPath: (filePath: string) => string = - path.sep === '\\' ? (filePath) => filePath.replaceAll('\\', '/') : (filePath) => filePath; + path.sep === '\\' + ? (filePath) => filePath.replaceAll('\\', '/') + : (filePath) => filePath; const createRelativePathResolver = (rootPath: string): RelativePathResolver => { - const rootPrefix = rootPath.endsWith(path.sep) ? rootPath : `${rootPath}${path.sep}`; + const rootPrefix = rootPath.endsWith(path.sep) + ? rootPath + : `${rootPath}${path.sep}`; return (filePath) => filePath === rootPath @@ -17,7 +21,8 @@ const createRelativePathResolver = (rootPath: string): RelativePathResolver => { }; /** Prettier only inspects a file's shebang when its basename contains no dot. */ -const hasDottedBasename = (filePath: string): boolean => path.basename(filePath).includes('.'); +const hasDottedBasename = (filePath: string): boolean => + path.basename(filePath).includes('.'); export { createRelativePathResolver, hasDottedBasename, toPosixPath }; export type { RelativePathResolver }; diff --git a/packages/rstack/src/fmt/plugins.ts b/packages/rstack/src/fmt/plugins.ts index e7a33e16..263bcb32 100644 --- a/packages/rstack/src/fmt/plugins.ts +++ b/packages/rstack/src/fmt/plugins.ts @@ -1,5 +1,11 @@ import { readFile, realpath } from 'node:fs/promises'; -import { isAbsolute, join, relative, resolve as resolvePath, sep } from 'node:path'; +import { + isAbsolute, + join, + relative, + resolve as resolvePath, + sep, +} from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; import { moduleResolve } from 'import-meta-resolve'; import type { Options as PrettierOptions } from 'prettier'; @@ -8,12 +14,16 @@ import type { FmtPluginSpecifier, ResolvedFmtOptions } from './types.ts'; type FmtPlugin = NonNullable[number]; type FmtPluginResolver = (options: ResolvedFmtOptions) => ResolvedFmtOptions; -type FingerprintResolver = (plugin: FmtPluginSpecifier) => Promise; +type FingerprintResolver = ( + plugin: FmtPluginSpecifier, +) => Promise; const resolveModuleUrl = (specifier: string, parentUrl: URL): string => moduleResolve(specifier, parentUrl).href; -const isFmtPluginSpecifier = (plugin: FmtPlugin): plugin is FmtPluginSpecifier => +const isFmtPluginSpecifier = ( + plugin: FmtPlugin, +): plugin is FmtPluginSpecifier => typeof plugin === 'string' || plugin instanceof URL; const getPackageRoot = (entryPath: string): string | undefined => { @@ -38,7 +48,9 @@ const getPackageRoot = (entryPath: string): string | undefined => { return entryPath.slice(0, end); }; -const fingerprintPlugin = async (pluginUrl: string): Promise => { +const fingerprintPlugin = async ( + pluginUrl: string, +): Promise => { try { const url = new URL(pluginUrl); if (url.protocol !== 'file:') { @@ -53,7 +65,9 @@ const fingerprintPlugin = async (pluginUrl: string): Promise return undefined; } - const pkg: unknown = JSON.parse(await readFile(join(packageRoot, 'package.json'), 'utf8')); + const pkg: unknown = JSON.parse( + await readFile(join(packageRoot, 'package.json'), 'utf8'), + ); if ( typeof pkg !== 'object' || pkg === null || @@ -142,7 +156,9 @@ const createPluginResolver = (rootPath: string): FmtPluginResolver => { } const resolvedPlugins = plugins.map(resolvePlugin); - const resolvedOptions = resolvedPlugins.every((plugin, index) => plugin === plugins[index]) + const resolvedOptions = resolvedPlugins.every( + (plugin, index) => plugin === plugins[index], + ) ? options : { ...options, plugins: resolvedPlugins }; optionsCache.set(options, resolvedOptions); diff --git a/packages/rstack/src/fmt/prettierPlugins.ts b/packages/rstack/src/fmt/prettierPlugins.ts index 829d7e6d..a88a600e 100644 --- a/packages/rstack/src/fmt/prettierPlugins.ts +++ b/packages/rstack/src/fmt/prettierPlugins.ts @@ -24,7 +24,10 @@ const getPrettierPlugins = async ( ): Promise => { const plugins = options.sortPackageJson === true && /(^|[/\\])package\.json$/.test(filePath) - ? [...defaultFmtPlugins, (await import('./sortPackageJsonPlugin.ts')).sortPackageJsonPlugin] + ? [ + ...defaultFmtPlugins, + (await import('./sortPackageJsonPlugin.ts')).sortPackageJsonPlugin, + ] : defaultFmtPlugins; return options.plugins?.length ? [...plugins, ...options.plugins] : plugins; diff --git a/packages/rstack/src/fmt/runner.ts b/packages/rstack/src/fmt/runner.ts index cc2340f4..89ad3a9a 100644 --- a/packages/rstack/src/fmt/runner.ts +++ b/packages/rstack/src/fmt/runner.ts @@ -1,4 +1,8 @@ -import { cacheNamespace, createCacheKeyResolver, createOptionsHasher } from './cacheIdentity.ts'; +import { + cacheNamespace, + createCacheKeyResolver, + createOptionsHasher, +} from './cacheIdentity.ts'; import { loadFmtCacheStore } from './cacheStore.ts'; import type { FmtCacheEntry, FmtCacheStore } from './cacheStore.ts'; import { hasDottedBasename } from './pathHelpers.ts'; @@ -77,7 +81,10 @@ const loadPluginFingerprints = async ( ); const resolveFingerprint = createFingerprintResolver(); const entries = await Promise.all( - Array.from(plugins, async ([key, plugin]) => [key, await resolveFingerprint(plugin)] as const), + Array.from( + plugins, + async ([key, plugin]) => [key, await resolveFingerprint(plugin)] as const, + ), ); const fingerprints = new Map(); for (const [key, fingerprint] of entries) { @@ -89,7 +96,10 @@ const loadPluginFingerprints = async ( }; /** Resolves the portable cache identity before work is dispatched. */ -const createRunTask = (file: FmtFileRequest, cache?: RunCache): FmtFileRunTask => { +const createRunTask = ( + file: FmtFileRequest, + cache?: RunCache, +): FmtFileRunTask => { let key: string | undefined; let fileCache: FmtFileCache | undefined; @@ -207,7 +217,9 @@ const runWithWorkers = async ( workerPool.workerCount >= minPriorityWorkers ? await runPriorityTasks(tasks, shouldWrite, workerPool.formatFile) : await Promise.all( - tasks.map((task) => runFmtFile(task, shouldWrite, workerPool.formatFile)), + tasks.map((task) => + runFmtFile(task, shouldWrite, workerPool.formatFile), + ), ); const processedFiles: FmtFileResult[] = []; let processedFileCount = 0; @@ -277,7 +289,10 @@ const runFmtFiles = async ({ return { ...result, - exitCode: files.length > 0 && result.processedFileCount === 0 ? 2 : getExitCode(result.files), + exitCode: + files.length > 0 && result.processedFileCount === 0 + ? 2 + : getExitCode(result.files), }; }; diff --git a/packages/rstack/src/fmt/types.ts b/packages/rstack/src/fmt/types.ts index 23e2dd6f..90d50a31 100644 --- a/packages/rstack/src/fmt/types.ts +++ b/packages/rstack/src/fmt/types.ts @@ -1,4 +1,7 @@ -import type { Config as PrettierConfig, Options as PrettierOptions } from 'prettier'; +import type { + Config as PrettierConfig, + Options as PrettierOptions, +} from 'prettier'; import type { FmtCacheEntry } from './cacheStore.ts'; /** Plugin objects cannot cross worker boundaries and are not planned for support. */ @@ -25,7 +28,8 @@ type FmtOverride = Omit & { options?: FmtOptions; }; -interface FmtConfig extends Omit, FmtBuiltinOptions { +interface FmtConfig + extends Omit, FmtBuiltinOptions { plugins?: FmtPluginSpecifier[]; overrides?: FmtOverride[]; /** Gitignore-compatible patterns relative to the Rstack config root. */ diff --git a/packages/rstack/src/fmt/worker.ts b/packages/rstack/src/fmt/worker.ts index 6e6505aa..35d47f49 100644 --- a/packages/rstack/src/fmt/worker.ts +++ b/packages/rstack/src/fmt/worker.ts @@ -74,7 +74,8 @@ const formatFile = async ({ cacheEntry: [ hasDottedBasename(file.path) ? '' - : (contentHash ?? hashContent(sourceBuffer ?? readFileSync(file.path))), + : (contentHash ?? + hashContent(sourceBuffer ?? readFileSync(file.path))), cache.optionsHash, 'unsupported', ], diff --git a/packages/rstack/src/fmt/workerPool.ts b/packages/rstack/src/fmt/workerPool.ts index 41bded06..800e4768 100644 --- a/packages/rstack/src/fmt/workerPool.ts +++ b/packages/rstack/src/fmt/workerPool.ts @@ -22,7 +22,10 @@ interface FmtWorkerPool { * scheduling and memory pressure. */ const getWorkerCount = (fileCount: number, maxWorkers?: number): number => - Math.min(fileCount, maxWorkers ?? Math.min(8, Math.max(1, availableParallelism() - 1))); + Math.min( + fileCount, + maxWorkers ?? Math.min(8, Math.max(1, availableParallelism() - 1)), + ); const getWorkerUrl = (): URL => { // Source tests run after build and exercise the same worker artifact as the CLI. @@ -33,7 +36,10 @@ const getWorkerUrl = (): URL => { }; /** Creates and starts every worker before formatting can begin. */ -const createWorkerPool = async (fileCount: number, maxWorkers?: number): Promise => { +const createWorkerPool = async ( + fileCount: number, + maxWorkers?: number, +): Promise => { const workerCount = getWorkerCount(fileCount, maxWorkers); const pool = new Tinypool({ filename: getWorkerUrl().href, diff --git a/packages/rstack/src/fmt/yukuPlugin.ts b/packages/rstack/src/fmt/yukuPlugin.ts index a22ec9f2..824af9fb 100644 --- a/packages/rstack/src/fmt/yukuPlugin.ts +++ b/packages/rstack/src/fmt/yukuPlugin.ts @@ -70,7 +70,8 @@ const locStart = (node: Locatable): number => { return firstDecorator ? Math.min(locStart(firstDecorator), start) : start; }; -const locEndWithFullText = (node: Locatable): number => (node.range?.[1] ?? node.end) as number; +const locEndWithFullText = (node: Locatable): number => + (node.range?.[1] ?? node.end) as number; const locEnd = (node: Locatable): number => { switch (node.type) { @@ -89,7 +90,9 @@ const locEnd = (node: Locatable): number => { return node.label ? locEnd(node.label) : locStart(node) + 'break'.length; case 'ContinueStatement': - return node.label ? locEnd(node.label) : locStart(node) + 'continue'.length; + return node.label + ? locEnd(node.label) + : locStart(node) + 'continue'.length; case 'DebuggerStatement': return locStart(node) + 'debugger'.length; @@ -136,10 +139,13 @@ const hasPragmaFrom = (originalText: string, pragmas: Set): boolean => { return false; }; -const hasPragma = (text: string): boolean => hasPragmaFrom(text, FORMAT_PRAGMAS); -const hasIgnorePragma = (text: string): boolean => hasPragmaFrom(text, FORMAT_IGNORE_PRAGMAS); +const hasPragma = (text: string): boolean => + hasPragmaFrom(text, FORMAT_PRAGMAS); +const hasIgnorePragma = (text: string): boolean => + hasPragmaFrom(text, FORMAT_IGNORE_PRAGMAS); -const getVisitorKeys = estreePrinter.getVisitorKeys as ((node: AstNode) => string[]) | undefined; +const getVisitorKeys = estreePrinter.getVisitorKeys as + ((node: AstNode) => string[]) | undefined; if (!getVisitorKeys) { throw new Error('The Prettier ESTree printer does not expose visitor keys.'); @@ -158,7 +164,10 @@ const asAstNode = (value: unknown): AstNode => { return value; }; -const withExtra = (node: AstNode, extra: Record): Record => ({ +const withExtra = ( + node: AstNode, + extra: Record, +): Record => ({ ...(node.extra !== null && typeof node.extra === 'object' ? (node.extra as Record) : undefined), @@ -201,7 +210,10 @@ const mergeNestedJsdocComments = (comments: PrettierComment[]): void => { } }; -const stripComments = (originalText: string, comments: PrettierComment[]): string => { +const stripComments = ( + originalText: string, + comments: PrettierComment[], +): string => { if (comments.length === 0) { return originalText; } @@ -287,7 +299,10 @@ const isUnbalancedLogicalTree = (node: AstNode): boolean => { return false; } - return node.right.type === 'LogicalExpression' && node.operator === node.right.operator; + return ( + node.right.type === 'LogicalExpression' && + node.operator === node.right.operator + ); }; const rebalanceLogicalTree = (node: AstNode): AstNode => { @@ -351,7 +366,9 @@ const postprocess = ( .filter(isTypeCastComment) .map((comment) => locEnd(comment)); - const previousCommentEnd = typeCastCommentEnds.findLast((end) => end <= start); + const previousCommentEnd = typeCastCommentEnds.findLast( + (end) => end <= start, + ); const shouldKeepParentheses = previousCommentEnd !== undefined && text.slice(previousCommentEnd, start).trim().length === 0; @@ -402,12 +419,17 @@ const postprocess = ( return undefined; }, onLeave(node) { - return isUnbalancedLogicalTree(node) ? rebalanceLogicalTree(node) : undefined; + return isUnbalancedLogicalTree(node) + ? rebalanceLogicalTree(node) + : undefined; }, }) as AstNode; }; -const indexToPosition = (text: string, index: number): { column: number; line: number } => { +const indexToPosition = ( + text: string, + index: number, +): { column: number; line: number } => { const lineBreakBefore = index === 0 ? -1 : text.lastIndexOf('\n', index - 1); let line = 1; @@ -427,10 +449,13 @@ const createParseError = (error: Diagnostic, text: string): SyntaxError => { const start = indexToPosition(text, error.start); const end = indexToPosition(text, error.end); - return Object.assign(new SyntaxError(`${error.message} (${start.line}:${start.column})`), { - cause: error, - loc: { start, end }, - }); + return Object.assign( + new SyntaxError(`${error.message} (${start.line}:${start.column})`), + { + cause: error, + loc: { start, end }, + }, + ); }; const parseWithOptions = (text: string, options: ParseOptions): ParseResult => { @@ -460,7 +485,10 @@ const getSourceType = (filepath: string): SourceType | undefined => { return undefined; }; -const getLanguageCombinations = (text: string, filepath: string): SourceLang[] => { +const getLanguageCombinations = ( + text: string, + filepath: string, +): SourceLang[] => { const normalizedPath = filepath.toLowerCase(); if (JS_TS_FILE_REGEXP.test(normalizedPath)) { @@ -493,25 +521,48 @@ const tryCombinations = (combinations: (() => ParseResult)[]): ParseResult => { throw new Error('No Yuku parser combinations were provided.'); }; -const parseJavaScript = (text: string, options: ParserOptions): AstNode => { +const parseJavaScript = ( + text: string, + options: ParserOptions, +): AstNode => { const sourceType = getSourceType(options.filepath); - const combinations = (sourceType ? [sourceType] : SOURCE_TYPE_COMBINATIONS).map( - (candidate) => () => parseWithOptions(text, { sourceType: candidate, lang: 'jsx' }), + const combinations = ( + sourceType ? [sourceType] : SOURCE_TYPE_COMBINATIONS + ).map( + (candidate) => () => + parseWithOptions(text, { sourceType: candidate, lang: 'jsx' }), ); const { program, comments } = tryCombinations(combinations); - return postprocess(program as unknown as AstNode, comments as PrettierComment[], text, 'yuku-js'); + return postprocess( + program as unknown as AstNode, + comments as PrettierComment[], + text, + 'yuku-js', + ); }; -const parseTypeScript = (text: string, options: ParserOptions): AstNode => { +const parseTypeScript = ( + text: string, + options: ParserOptions, +): AstNode => { const sourceType = getSourceType(options.filepath); const languages = getLanguageCombinations(text, options.filepath); - const combinations = (sourceType ? [sourceType] : SOURCE_TYPE_COMBINATIONS).flatMap((candidate) => - languages.map((lang) => () => parseWithOptions(text, { sourceType: candidate, lang })), + const combinations = ( + sourceType ? [sourceType] : SOURCE_TYPE_COMBINATIONS + ).flatMap((candidate) => + languages.map( + (lang) => () => parseWithOptions(text, { sourceType: candidate, lang }), + ), ); const { program, comments } = tryCombinations(combinations); - return postprocess(program as unknown as AstNode, comments as PrettierComment[], text, 'yuku-ts'); + return postprocess( + program as unknown as AstNode, + comments as PrettierComment[], + text, + 'yuku-ts', + ); }; const createParser = ( @@ -530,17 +581,19 @@ const parserNames = new Map([ ['typescript', 'yuku-ts'], ]); -const languages: SupportLanguage[] = estreePlugin.languages.flatMap((language) => { - const parsers = [ - ...new Set( - language.parsers - .map((parser) => parserNames.get(parser)) - .filter((parser): parser is string => parser !== undefined), - ), - ]; - - return parsers.length > 0 ? [{ ...language, parsers }] : []; -}); +const languages: SupportLanguage[] = estreePlugin.languages.flatMap( + (language) => { + const parsers = [ + ...new Set( + language.parsers + .map((parser) => parserNames.get(parser)) + .filter((parser): parser is string => parser !== undefined), + ), + ]; + + return parsers.length > 0 ? [{ ...language, parsers }] : []; + }, +); const yukuPlugin: Plugin = { languages, diff --git a/packages/rstack/src/native/index.ts b/packages/rstack/src/native/index.ts index bf682324..c760309e 100644 --- a/packages/rstack/src/native/index.ts +++ b/packages/rstack/src/native/index.ts @@ -6,5 +6,7 @@ export type NativeBinding = typeof import('../../binding.cjs'); const require = createRequire(import.meta.url); export const loadNativeBinding = (): NativeBinding => { const packageJsonPath = require.resolve('rstack/package.json'); - return require(path.join(path.dirname(packageJsonPath), 'binding.cjs')) as NativeBinding; + return require( + path.join(path.dirname(packageJsonPath), 'binding.cjs'), + ) as NativeBinding; }; diff --git a/packages/rstack/src/projectCache.ts b/packages/rstack/src/projectCache.ts index 86d0ffcb..39894d22 100644 --- a/packages/rstack/src/projectCache.ts +++ b/packages/rstack/src/projectCache.ts @@ -4,13 +4,17 @@ import path from 'node:path'; const cacheGitignore = '*\n'; type ProjectCacheResult = - { status: 'available'; path: string } | { status: 'unavailable'; path: string; error: unknown }; + | { status: 'available'; path: string } + | { status: 'unavailable'; path: string; error: unknown }; /** Returns the disposable cache directory for a resolved Rstack project root. */ -const getProjectCacheDir = (rootPath: string): string => path.join(rootPath, '.rstack', 'cache'); +const getProjectCacheDir = (rootPath: string): string => + path.join(rootPath, '.rstack', 'cache'); /** Creates the project cache directory without making cache failures fatal. */ -const ensureProjectCacheDir = async (rootPath: string): Promise => { +const ensureProjectCacheDir = async ( + rootPath: string, +): Promise => { const cachePath = getProjectCacheDir(rootPath); const ignorePath = path.join(cachePath, '.gitignore'); diff --git a/packages/rstack/src/rsbuildConfig.ts b/packages/rstack/src/rsbuildConfig.ts index 01385c44..02935da8 100644 --- a/packages/rstack/src/rsbuildConfig.ts +++ b/packages/rstack/src/rsbuildConfig.ts @@ -1,4 +1,8 @@ -import type { ConfigParams, RsbuildConfigDefinition, WatchFiles } from '@rsbuild/core'; +import type { + ConfigParams, + RsbuildConfigDefinition, + WatchFiles, +} from '@rsbuild/core'; import { loadRstackConfig, type Configs } from './config.ts'; const resolveRsbuildConfig = async (configs: Configs, params: ConfigParams) => { @@ -31,7 +35,11 @@ const loadRsbuildConfig: RsbuildConfigDefinition = async (params) => { dev: { ...config.dev, watchFiles: [ - ...(watchFiles ? (Array.isArray(watchFiles) ? watchFiles : [watchFiles]) : []), + ...(watchFiles + ? Array.isArray(watchFiles) + ? watchFiles + : [watchFiles] + : []), watchConfig, ], }, diff --git a/packages/rstack/src/rslibConfig.ts b/packages/rstack/src/rslibConfig.ts index b7468aa4..a3159c63 100644 --- a/packages/rstack/src/rslibConfig.ts +++ b/packages/rstack/src/rslibConfig.ts @@ -1,8 +1,15 @@ import type { WatchFiles } from '@rsbuild/core'; -import type { ConfigParams, RslibConfig, RslibConfigDefinition } from '@rslib/core'; +import type { + ConfigParams, + RslibConfig, + RslibConfigDefinition, +} from '@rslib/core'; import { loadRstackConfig, type Configs } from './config.ts'; -const resolveRslibConfig = async (configs: Configs, params: ConfigParams): Promise => { +const resolveRslibConfig = async ( + configs: Configs, + params: ConfigParams, +): Promise => { const libConfig = configs.lib; if (!libConfig) { return {}; @@ -32,7 +39,11 @@ const loadRslibConfig = (async (params: ConfigParams) => { dev: { ...config.dev, watchFiles: [ - ...(watchFiles ? (Array.isArray(watchFiles) ? watchFiles : [watchFiles]) : []), + ...(watchFiles + ? Array.isArray(watchFiles) + ? watchFiles + : [watchFiles] + : []), watchConfig, ], }, diff --git a/packages/rstack/src/rspressConfig.ts b/packages/rstack/src/rspressConfig.ts index 0bf6a60d..68bb41c5 100644 --- a/packages/rstack/src/rspressConfig.ts +++ b/packages/rstack/src/rspressConfig.ts @@ -34,7 +34,11 @@ export default async (): Promise => { dev: { ...config.builderConfig?.dev, watchFiles: [ - ...(watchFiles ? (Array.isArray(watchFiles) ? watchFiles : [watchFiles]) : []), + ...(watchFiles + ? Array.isArray(watchFiles) + ? watchFiles + : [watchFiles] + : []), watchConfig, ], }, diff --git a/packages/rstack/src/rstestConfig.ts b/packages/rstack/src/rstestConfig.ts index b28d61d2..119c7112 100644 --- a/packages/rstack/src/rstestConfig.ts +++ b/packages/rstack/src/rstestConfig.ts @@ -14,7 +14,8 @@ const resolveAutomaticExtends = async ( /* rspackChunkName: 'adapterRsbuild' */ '@rstest/adapter-rsbuild' ); - const config = typeof appConfig === 'function' ? await appConfig(params) : appConfig; + const config = + typeof appConfig === 'function' ? await appConfig(params) : appConfig; return withRsbuildConfig({ config, @@ -27,7 +28,8 @@ const resolveAutomaticExtends = async ( /* rspackChunkName: 'adapterRslib' */ '@rstest/adapter-rslib' ); - const config = typeof libConfig === 'function' ? await libConfig(params) : libConfig; + const config = + typeof libConfig === 'function' ? await libConfig(params) : libConfig; return withRslibConfig({ config, @@ -51,7 +53,11 @@ const injectExtends = ( }; }; -const extendsConfig = async (configs: Configs, testConfig: RstestConfig, params: ConfigParams) => { +const extendsConfig = async ( + configs: Configs, + testConfig: RstestConfig, + params: ConfigParams, +) => { if ('extends' in testConfig) { return testConfig; } @@ -73,7 +79,9 @@ const extendsConfig = async (configs: Configs, testConfig: RstestConfig, params: return { ...testConfig, projects: testConfig.projects.map((project) => - typeof project === 'string' ? project : injectExtends(project, automaticExtends), + typeof project === 'string' + ? project + : injectExtends(project, automaticExtends), ), }; }; diff --git a/packages/rstack/src/setup/hooks.ts b/packages/rstack/src/setup/hooks.ts index 676da734..7a6e523c 100644 --- a/packages/rstack/src/setup/hooks.ts +++ b/packages/rstack/src/setup/hooks.ts @@ -24,7 +24,10 @@ const quoteShellPath = (value: string): string => { process.platform === 'win32' ? value .replaceAll('\\', '/') - .replace(/^([A-Za-z]):\//u, (_, drive: string) => `/${drive.toLowerCase()}/`) + .replace( + /^([A-Za-z]):\//u, + (_, drive: string) => `/${drive.toLowerCase()}/`, + ) : value; return `'${shellPath.replaceAll("'", `'"'"'`)}'`; @@ -78,7 +81,8 @@ rs_run "$@" export const createHookFiles = ( nodeExecutable: string = process.execPath, ): Record => { - const messageShim = createShim(`# Keep the message file valid after changing directories. + const messageShim = + createShim(`# Keep the message file valid after changing directories. [ -n "\${1-}" ] || exit 1 case "$1" in /*|[A-Za-z]:/*) ;; @@ -90,7 +94,8 @@ case "$1" in esac `); - const prePushShim = createShim(`# Keep a local remote path valid after changing directories. + const prePushShim = + createShim(`# Keep a local remote path valid after changing directories. rs_remote_name=\${1-} rs_remote_location=\${2-} [ -n "$rs_remote_name" ] && [ -n "$rs_remote_location" ] || exit 1 @@ -109,7 +114,9 @@ set -- "$rs_remote_name" "$rs_remote_location" "$@" `); const defaultShim = createShim(); - const files: Record = { runner: createRunner(nodeExecutable) }; + const files: Record = { + runner: createRunner(nodeExecutable), + }; for (const name of hookNames) { files[name] = name.endsWith('-msg') diff --git a/packages/rstack/src/setup/index.ts b/packages/rstack/src/setup/index.ts index c090404f..ef597323 100644 --- a/packages/rstack/src/setup/index.ts +++ b/packages/rstack/src/setup/index.ts @@ -16,7 +16,9 @@ export const runSetupCLI = async (args: string[]): Promise => { const hooksDirs = values.hooksDir; if (hooksDirs && hooksDirs.length > 1) { - throw new Error('The --hooks-dir option cannot be specified more than once.'); + throw new Error( + 'The --hooks-dir option cannot be specified more than once.', + ); } const hooksDir = hooksDirs?.[0]; @@ -39,7 +41,9 @@ export const runSetupCLI = async (args: string[]): Promise => { } const reason = - result.reason === 'disabled' ? 'disabled by RSTACK_HOOKS' : 'not a Git repository'; + result.reason === 'disabled' + ? 'disabled by RSTACK_HOOKS' + : 'not a Git repository'; logger.info(`Git hooks setup skipped: ${color.yellow(reason)}.`); return; } diff --git a/packages/rstack/src/setup/install.ts b/packages/rstack/src/setup/install.ts index 81d4198f..eae7c186 100644 --- a/packages/rstack/src/setup/install.ts +++ b/packages/rstack/src/setup/install.ts @@ -1,5 +1,12 @@ import { spawnSync } from 'node:child_process'; -import { chmodSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs'; +import { + chmodSync, + existsSync, + mkdirSync, + readFileSync, + statSync, + writeFileSync, +} from 'node:fs'; import path from 'node:path'; import { createHookFiles, hookNames } from './hooks.ts'; @@ -54,7 +61,10 @@ const resolveHooksDir = (hooksDir: string): string | FailedInstallResult => { const resolvedDir = hooksDir.replaceAll('\\', '/'); if (resolvedDir.length === 0) { - return fail('invalid-hooks-directory', 'Git hooks directory must not be empty.'); + return fail( + 'invalid-hooks-directory', + 'Git hooks directory must not be empty.', + ); } if (path.isAbsolute(resolvedDir)) { @@ -65,15 +75,20 @@ const resolveHooksDir = (hooksDir: string): string | FailedInstallResult => { } if (resolvedDir.includes('..')) { - return fail('invalid-hooks-directory', 'Git hooks directory must not contain "..".'); + return fail( + 'invalid-hooks-directory', + 'Git hooks directory must not contain "..".', + ); } return resolvedDir; }; -const runGit = (cwd: string, args: string[]) => spawnSync('git', args, { cwd, encoding: 'utf8' }); +const runGit = (cwd: string, args: string[]) => + spawnSync('git', args, { cwd, encoding: 'utf8' }); -const removeLineEnding = (value: string): string => value.replace(/\r?\n$/u, ''); +const removeLineEnding = (value: string): string => + value.replace(/\r?\n$/u, ''); const gitFailure = ( error: NodeJS.ErrnoException | undefined, @@ -83,7 +98,10 @@ const gitFailure = ( return fail('git-not-found', 'Git command not found.'); } - return fail('git-command-failed', `Failed to run Git: ${error?.message || stderr.trim()}`); + return fail( + 'git-command-failed', + `Failed to run Git: ${error?.message || stderr.trim()}`, + ); }; const resolveGitContext = (cwd: string): GitContext | InstallResult => { @@ -123,23 +141,33 @@ const resolveGitContext = (cwd: string): GitContext | InstallResult => { } if (!gitRoot || !gitCommonDirectory || !effectiveHooksDirectory) { - return fail('git-command-failed', 'Failed to resolve the Git repository paths.'); + return fail( + 'git-command-failed', + 'Failed to resolve the Git repository paths.', + ); } return { defaultHooksDirectory: path.join(gitCommonDirectory, 'hooks'), effectiveHooksDirectory, gitRoot, - projectPath: repositoryPrefix.replaceAll('\\', '/').replace(/\/$/u, '') || '.', + projectPath: + repositoryPrefix.replaceAll('\\', '/').replace(/\/$/u, '') || '.', }; }; -const isCurrentFile = (filePath: string, content: string, executable = false): boolean => { +const isCurrentFile = ( + filePath: string, + content: string, + executable = false, +): boolean => { try { // Windows does not expose POSIX executable bits, but Git for Windows still runs hook shims. return ( readFileSync(filePath, 'utf8') === content && - (!executable || process.platform === 'win32' || (statSync(filePath).mode & 0o777) === 0o755) + (!executable || + process.platform === 'win32' || + (statSync(filePath).mode & 0o777) === 0o755) ); } catch { return false; @@ -153,7 +181,9 @@ const readOwner = (directory: string): string | undefined => { try { const content = readFileSync(path.join(directory, ownerFileName), 'utf8'); const owner = removeLineEnding(content); - return content === `${owner}\n` && owner.length > 0 && !/[\r\n]/u.test(owner) + return content === `${owner}\n` && + owner.length > 0 && + !/[\r\n]/u.test(owner) ? owner : undefined; } catch { @@ -163,13 +193,21 @@ const readOwner = (directory: string): string | undefined => { const displayPath = (gitRoot: string, filePath: string): string => { const relativePath = path.relative(gitRoot, filePath).replaceAll('\\', '/'); - return relativePath.length > 0 && !relativePath.startsWith('../') ? relativePath : filePath; + return relativePath.length > 0 && !relativePath.startsWith('../') + ? relativePath + : filePath; }; const ownerConflict = (project: string): SkippedInstallResult => - skip('owned-by-another-project', `Git hooks are already managed by Rstack project "${project}"`); + skip( + 'owned-by-another-project', + `Git hooks are already managed by Rstack project "${project}"`, + ); -const directoryConflict = (gitRoot: string, directory: string): SkippedInstallResult => +const directoryConflict = ( + gitRoot: string, + directory: string, +): SkippedInstallResult => skip( 'hooks-directory-conflict', `the hooks directory "${displayPath(gitRoot, directory)}" is not managed by Rstack`, @@ -191,7 +229,8 @@ const claimOwner = ( // Exclusive creation makes concurrent prepare scripts agree on one owner. writeFileSync(ownerPath, `${project}\n`, { flag: 'wx' }); } catch (error) { - const code = error instanceof Error && 'code' in error ? error.code : undefined; + const code = + error instanceof Error && 'code' in error ? error.code : undefined; if (code !== 'EEXIST') { throw error; } @@ -200,7 +239,9 @@ const claimOwner = ( if (!concurrentOwner) { return directoryConflict(gitRoot, directory); } - return concurrentOwner === project ? undefined : ownerConflict(concurrentOwner); + return concurrentOwner === project + ? undefined + : ownerConflict(concurrentOwner); } return undefined; @@ -228,11 +269,19 @@ export const installHooks = ({ return context; } - const { defaultHooksDirectory, effectiveHooksDirectory, gitRoot, projectPath } = context; + const { + defaultHooksDirectory, + effectiveHooksDirectory, + gitRoot, + projectPath, + } = context; const hooksPath = `${resolvedDir}/${generatedDirectoryName}`; const directory = path.join(gitRoot, resolvedDir, generatedDirectoryName); const hooksPathMatches = isSamePath(effectiveHooksDirectory, directory); - const usesDefaultHooks = isSamePath(effectiveHooksDirectory, defaultHooksDirectory); + const usesDefaultHooks = isSamePath( + effectiveHooksDirectory, + defaultHooksDirectory, + ); if (!hooksPathMatches && !usesDefaultHooks) { const activeOwner = readOwner(effectiveHooksDirectory); @@ -269,7 +318,9 @@ export const installHooks = ({ const unchanged = hooksPathMatches && isCurrentFile(path.join(directory, '.gitignore'), gitignore) && - files.every(([name, content]) => isCurrentFile(path.join(directory, name), content, true)); + files.every(([name, content]) => + isCurrentFile(path.join(directory, name), content, true), + ); if (unchanged) { return { status: 'unchanged', hooksPath }; } @@ -293,7 +344,12 @@ export const installHooks = ({ } // Point Git at the generated directory only after every runtime file is ready. - const configured = runGit(cwd, ['config', '--local', 'core.hooksPath', hooksPath]); + const configured = runGit(cwd, [ + 'config', + '--local', + 'core.hooksPath', + hooksPath, + ]); if (configured.error || configured.status === null) { return gitFailure(configured.error, configured.stderr); } diff --git a/packages/rstack/src/staged.ts b/packages/rstack/src/staged.ts index 4f6c78c2..e364c490 100644 --- a/packages/rstack/src/staged.ts +++ b/packages/rstack/src/staged.ts @@ -3,13 +3,16 @@ import { parseArgs } from './cli/args.ts'; import { printCommandHelp } from './cli/help.ts'; import { loadRstackConfig } from './config.ts'; -export type StagedSyncTaskGenerator = (stagedFileNames: readonly string[]) => string | string[]; +export type StagedSyncTaskGenerator = ( + stagedFileNames: readonly string[], +) => string | string[]; export type StagedAsyncTaskGenerator = ( stagedFileNames: readonly string[], ) => Promise; -export type StagedTaskGenerator = StagedSyncTaskGenerator | StagedAsyncTaskGenerator; +export type StagedTaskGenerator = + StagedSyncTaskGenerator | StagedAsyncTaskGenerator; export type StagedFunctionTask = { title: string; @@ -17,7 +20,10 @@ export type StagedFunctionTask = { }; export type StagedTask = - string | StagedFunctionTask | StagedTaskGenerator | (string | StagedTaskGenerator)[]; + | string + | StagedFunctionTask + | StagedTaskGenerator + | (string | StagedTaskGenerator)[]; export type StagedConfig = Record | StagedTaskGenerator; diff --git a/packages/rstack/tests/cli/args.test.ts b/packages/rstack/tests/cli/args.test.ts index 2063dce9..93c4db5b 100644 --- a/packages/rstack/tests/cli/args.test.ts +++ b/packages/rstack/tests/cli/args.test.ts @@ -4,17 +4,20 @@ import { parseArgs } from '../../src/cli/args.ts'; test.each([ ['--long-option', 'kebab'], ['--longOption', 'camel'], -] as const)('accepts %s and returns only a camel-case value', (option, value) => { - const { values } = parseArgs({ - args: [option, value], - options: { - 'long-option': { type: 'string' }, - }, - }); +] as const)( + 'accepts %s and returns only a camel-case value', + (option, value) => { + const { values } = parseArgs({ + args: [option, value], + options: { + 'long-option': { type: 'string' }, + }, + }); - expect(values).toEqual({ longOption: value }); - expect('long-option' in values).toBe(false); -}); + expect(values).toEqual({ longOption: value }); + expect('long-option' in values).toBe(false); + }, +); test('combines repeated kebab-case and camel-case values', () => { const { values } = parseArgs({ diff --git a/packages/rstack/tests/cli/check.test.ts b/packages/rstack/tests/cli/check.test.ts index 87b92845..a2b975a9 100644 --- a/packages/rstack/tests/cli/check.test.ts +++ b/packages/rstack/tests/cli/check.test.ts @@ -65,7 +65,9 @@ test('enables type checking only with --type-check', () => { expect(withoutTypeCheck.status).toBe(0); expect(withTypeCheck.status).toBe(1); - expect(`${withTypeCheck.stdout}\n${withTypeCheck.stderr}`).toContain('TS2322'); + expect(`${withTypeCheck.stdout}\n${withTypeCheck.stderr}`).toContain( + 'TS2322', + ); }); test('does not run the formatting check when lint fails', () => { @@ -75,6 +77,8 @@ test('does not run the formatting check when lint fails', () => { const result = runCheck(); expect(result.status).toBe(1); - expect(`${result.stdout}\n${result.stderr}`).toContain("Unexpected 'debugger' statement"); + expect(`${result.stdout}\n${result.stderr}`).toContain( + "Unexpected 'debugger' statement", + ); expect(result.stdout).not.toContain('Checking formatting...'); }); diff --git a/packages/rstack/tests/cli/fmt/cache.test.ts b/packages/rstack/tests/cli/fmt/cache.test.ts index 12c112e2..d5f98a3c 100644 --- a/packages/rstack/tests/cli/fmt/cache.test.ts +++ b/packages/rstack/tests/cli/fmt/cache.test.ts @@ -1,8 +1,17 @@ import { expect, test } from 'rstack/test'; -import { expectWriteSummary, normalizeDuration, setupFmtTest } from './helpers.ts'; - -const { projectFileExists, readProjectFile, resolveProjectPath, runFmt, writeProjectFile } = - setupFmtTest(); +import { + expectWriteSummary, + normalizeDuration, + setupFmtTest, +} from './helpers.ts'; + +const { + projectFileExists, + readProjectFile, + resolveProjectPath, + runFmt, + writeProjectFile, +} = setupFmtTest(); interface SerializedFmtCache { version: number; @@ -14,7 +23,10 @@ interface SerializedFmtCache { const readFmtCache = (filePath: string): SerializedFmtCache => JSON.parse(readProjectFile(filePath)) as SerializedFmtCache; -const expectSingleCleanEntry = (cache: SerializedFmtCache, filePath: string): void => { +const expectSingleCleanEntry = ( + cache: SerializedFmtCache, + filePath: string, +): void => { expect(cache.version).toBe(2); expect(typeof cache.namespace).toBe('string'); expect(cache.options).toHaveLength(1); @@ -37,7 +49,10 @@ test.each([ expect(result.status).toBe(0); expect(readProjectFile('.rstack/cache/.gitignore')).toBe('*\n'); - expectSingleCleanEntry(readFmtCache('.rstack/cache/fmt/cache.json'), 'index.ts'); + expectSingleCleanEntry( + readFmtCache('.rstack/cache/fmt/cache.json'), + 'index.ts', + ); expect(readProjectFile('.rstack/cache/fmt-v1.json')).toBe('legacy'); }); @@ -67,52 +82,69 @@ test('--no-cache bypasses cache reads and writes', () => { expect(projectFileExists('.rstack/cache/.gitignore')).toBe(false); }); -test.each(['relative', 'absolute'] as const)('uses a %s custom cache location', (kind) => { - const cacheLocation = kind === 'relative' ? 'custom-cache' : resolveProjectPath('custom-cache'); - writeProjectFile('index.ts', 'const value = 1;\n'); - - const result = runFmt(['--cache-location', cacheLocation, 'index.ts']); - - expect(result.status).toBe(0); - expectSingleCleanEntry(readFmtCache('custom-cache/cache.json'), 'index.ts'); - expect(projectFileExists('custom-cache/.gitignore')).toBe(false); - expect(projectFileExists('.rstack')).toBe(false); -}); - -test.each(['.', '..'])('rejects a custom cache location at %s', (cacheLocation) => { - const result = runFmt(['--cache-location', cacheLocation, '.']); - - expect(result.status).toBe(2); - expect(result.stdout).toBe(''); - expect(result.stderr).toContain( - 'The --cache-location directory cannot be the current working directory or an ancestor.', - ); -}); +test.each(['relative', 'absolute'] as const)( + 'uses a %s custom cache location', + (kind) => { + const cacheLocation = + kind === 'relative' ? 'custom-cache' : resolveProjectPath('custom-cache'); + writeProjectFile('index.ts', 'const value = 1;\n'); + + const result = runFmt(['--cache-location', cacheLocation, 'index.ts']); + + expect(result.status).toBe(0); + expectSingleCleanEntry(readFmtCache('custom-cache/cache.json'), 'index.ts'); + expect(projectFileExists('custom-cache/.gitignore')).toBe(false); + expect(projectFileExists('.rstack')).toBe(false); + }, +); + +test.each(['.', '..'])( + 'rejects a custom cache location at %s', + (cacheLocation) => { + const result = runFmt(['--cache-location', cacheLocation, '.']); + + expect(result.status).toBe(2); + expect(result.stdout).toBe(''); + expect(result.stderr).toContain( + 'The --cache-location directory cannot be the current working directory or an ancestor.', + ); + }, +); test('excludes the custom cache directory from formatting', () => { const cacheLocation = 'custom-cache'; writeProjectFile('index.ts', 'const value = 1;\n'); writeProjectFile('custom-cache/nested/ignored.ts', 'const value=2'); - expect(runFmt(['--cache-location', cacheLocation, 'index.ts']).status).toBe(0); + expect(runFmt(['--cache-location', cacheLocation, 'index.ts']).status).toBe( + 0, + ); const result = runFmt(['--cache-location', cacheLocation, '.']); expect(result.status).toBe(0); expectWriteSummary(result.stdout, 2, 0); - expect(readProjectFile('custom-cache/nested/ignored.ts')).toBe('const value=2'); + expect(readProjectFile('custom-cache/nested/ignored.ts')).toBe( + 'const value=2', + ); }); test('uses an explicit config root cache from a subdirectory', () => { const appPath = resolveProjectPath('packages/app'); writeProjectFile('packages/app/index.ts', 'const value=1'); - const result = runFmt(['index.ts', '--config', '../../rstack.config.ts'], appPath); + const result = runFmt( + ['index.ts', '--config', '../../rstack.config.ts'], + appPath, + ); expect(result.status).toBe(0); expect(readProjectFile('packages/app/index.ts')).toBe('const value = 1;\n'); expect(projectFileExists('.rstack/cache/fmt/cache.json')).toBe(true); expect(projectFileExists('packages/app/.rstack')).toBe(false); - expectSingleCleanEntry(readFmtCache('.rstack/cache/fmt/cache.json'), 'packages/app/index.ts'); + expectSingleCleanEntry( + readFmtCache('.rstack/cache/fmt/cache.json'), + 'packages/app/index.ts', + ); }); test('recovers from a corrupted cache', () => { @@ -123,9 +155,13 @@ test('recovers from a corrupted cache', () => { const second = runFmt(['--check', 'index.ts']); expect(second.status).toBe(0); - expect(normalizeDuration(second.stdout)).toBe(normalizeDuration(first.stdout)); + expect(normalizeDuration(second.stdout)).toBe( + normalizeDuration(first.stdout), + ); expect(second.stderr).toBe(first.stderr); - expect(JSON.parse(readProjectFile('.rstack/cache/fmt/cache.json'))).toMatchObject({ version: 2 }); + expect( + JSON.parse(readProjectFile('.rstack/cache/fmt/cache.json')), + ).toMatchObject({ version: 2 }); }); test('formats without a writable cache directory', () => { diff --git a/packages/rstack/tests/cli/fmt/config.test.ts b/packages/rstack/tests/cli/fmt/config.test.ts index d5947aae..5cda6c72 100644 --- a/packages/rstack/tests/cli/fmt/config.test.ts +++ b/packages/rstack/tests/cli/fmt/config.test.ts @@ -6,7 +6,8 @@ import { sortedPackageJson, } from './helpers.ts'; -const { readProjectFile, runFmt, writeFixturePlugin, writeProjectFile } = setupFmtTest(); +const { readProjectFile, runFmt, writeFixturePlugin, writeProjectFile } = + setupFmtTest(); test('does not sort package.json by default', () => { writeProjectFile('package.json', packageJsonSource); @@ -35,7 +36,9 @@ define.fmt({ sortPackageJson: true }); expect(result.status).toBe(0); expect(result.stderr).toBe(''); expect(readProjectFile('package.json')).toBe(sortedPackageJson); - expect(readProjectFile('packages/example/package.json')).toBe(sortedPackageJson); + expect(readProjectFile('packages/example/package.json')).toBe( + sortedPackageJson, + ); }); test('supports configuring the worker count', () => { @@ -52,17 +55,28 @@ test('supports configuring the worker count', () => { }); test('does not load Prettier config or ignore files', () => { - writeProjectFile('.prettierrc.json', '{ "singleQuote": true, "semi": false }\n'); + writeProjectFile( + '.prettierrc.json', + '{ "singleQuote": true, "semi": false }\n', + ); writeProjectFile('.prettierignore', 'index.ts\n'); - writeProjectFile('.editorconfig', 'root = true\n\n[*]\nindent_style = space\nindent_size = 8\n'); - writeProjectFile('index.ts', "function getMessage(){\n return 'hello'\n}"); + writeProjectFile( + '.editorconfig', + 'root = true\n\n[*]\nindent_style = space\nindent_size = 8\n', + ); + writeProjectFile( + 'index.ts', + "function getMessage(){\n return 'hello'\n}", + ); const result = runFmt(['index.ts']); expect(result.status).toBe(0); expectWriteSummary(result.stdout, 1, 1); expect(result.stderr).toBe(''); - expect(readProjectFile('index.ts')).toBe('function getMessage() {\n return "hello";\n}\n'); + expect(readProjectFile('index.ts')).toBe( + 'function getMessage() {\n return "hello";\n}\n', + ); }); test('applies repeated ignore paths', () => { @@ -84,8 +98,12 @@ test('applies repeated ignore paths', () => { expect(result.status).toBe(0); expectWriteSummary(result.stdout, 1, 1); expect(result.stderr).toBe(''); - expect(readProjectFile('src/ignored-by-root.ts')).toBe('const root="ignored"'); - expect(readProjectFile('src/ignored-by-extra.ts')).toBe('const extra="ignored"'); + expect(readProjectFile('src/ignored-by-root.ts')).toBe( + 'const root="ignored"', + ); + expect(readProjectFile('src/ignored-by-extra.ts')).toBe( + 'const extra="ignored"', + ); expect(readProjectFile('src/index.ts')).toBe('const index = "formatted";\n'); }); @@ -96,7 +114,9 @@ test('returns exit code 2 for an unreadable ignore path', () => { expect(result.status).toBe(2); expect(result.stdout).toBe(''); - expect(result.stderr).toContain('Failed to read ignore file "missing.ignore".'); + expect(result.stderr).toContain( + 'Failed to read ignore file "missing.ignore".', + ); expect(readProjectFile('index.ts')).toBe('const value=true'); }); @@ -156,7 +176,10 @@ define.fmt({ }); test('returns exit code 2 for config errors', () => { - writeProjectFile('rstack.config.ts', 'throw new Error("invalid fmt config");\n'); + writeProjectFile( + 'rstack.config.ts', + 'throw new Error("invalid fmt config");\n', + ); const result = runFmt(['index.ts']); diff --git a/packages/rstack/tests/cli/fmt/files.test.ts b/packages/rstack/tests/cli/fmt/files.test.ts index d650eac1..f0368df4 100644 --- a/packages/rstack/tests/cli/fmt/files.test.ts +++ b/packages/rstack/tests/cli/fmt/files.test.ts @@ -1,6 +1,10 @@ import { expect, test } from 'rstack/test'; import { normalizeHelpOutput } from '#test-helpers'; -import { expectWriteSummary, normalizeDuration, setupFmtTest } from './helpers.ts'; +import { + expectWriteSummary, + normalizeDuration, + setupFmtTest, +} from './helpers.ts'; const { readProjectFile, runCLI, runFmt, writeProjectFile } = setupFmtTest(); @@ -77,7 +81,9 @@ test('formats files in node_modules with --with-node-modules', () => { expect(result.status).toBe(0); expectWriteSummary(result.stdout, 1, 1); expect(result.stderr).toBe(''); - expect(readProjectFile('node_modules/example/index.ts')).toBe('const message = "hello";\n'); + expect(readProjectFile('node_modules/example/index.ts')).toBe( + 'const message = "hello";\n', + ); }); test('summarizes write mode when no files change', () => { @@ -116,18 +122,21 @@ test('checks formatting without writing files', () => { expect(formattedResult.stderr).toBe(''); }); -test.each(['-l', '--list-different'])('lists only paths that differ with %s', (option) => { - const source = 'const message="hello"'; - writeProjectFile('src/index.ts', source); - writeProjectFile('src/formatted.ts', 'const formatted = true;\n'); - - const result = runFmt([option, 'src/*.ts']); - - expect(result.status).toBe(1); - expect(result.stdout).toBe('src/index.ts\n'); - expect(result.stderr).toBe(''); - expect(readProjectFile('src/index.ts')).toBe(source); -}); +test.each(['-l', '--list-different'])( + 'lists only paths that differ with %s', + (option) => { + const source = 'const message="hello"'; + writeProjectFile('src/index.ts', source); + writeProjectFile('src/formatted.ts', 'const formatted = true;\n'); + + const result = runFmt([option, 'src/*.ts']); + + expect(result.status).toBe(1); + expect(result.stdout).toBe('src/index.ts\n'); + expect(result.stderr).toBe(''); + expect(readProjectFile('src/index.ts')).toBe(source); + }, +); test('returns exit code 2 for formatting errors', () => { writeProjectFile('index.ts', 'const value = ;'); diff --git a/packages/rstack/tests/cli/fmt/helpers.ts b/packages/rstack/tests/cli/fmt/helpers.ts index c33f7ecf..d3b62229 100644 --- a/packages/rstack/tests/cli/fmt/helpers.ts +++ b/packages/rstack/tests/cli/fmt/helpers.ts @@ -1,5 +1,12 @@ import { type SpawnSyncReturns, spawnSync } from 'node:child_process'; -import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { + existsSync, + mkdirSync, + mkdtempSync, + readFileSync, + rmSync, + writeFileSync, +} from 'node:fs'; import path from 'node:path'; import { afterEach, beforeEach, expect } from 'rstack/test'; import { RSTACK_BIN_PATH } from '#test-helpers'; @@ -9,7 +16,11 @@ export const packageJsonSource = export const sortedPackageJson = '{\n "name": "fixture",\n "version": "1.0.0",\n "type": "module",\n "dependencies": {\n "a": "1.0.0",\n "z": "1.0.0"\n }\n}\n'; -type RunCLI = (args: string[], input?: string, cwd?: string) => SpawnSyncReturns; +type RunCLI = ( + args: string[], + input?: string, + cwd?: string, +) => SpawnSyncReturns; type FmtTestHarness = { projectFileExists: (filePath: string) => boolean; @@ -42,15 +53,19 @@ export const expectWriteSummary = ( const message = writtenCount ? `Formatted ${writtenCount} of ${matchedFileCount} ${files} in .` : `Checked ${matchedFileCount} ${files} in . No changes needed.`; - expect(normalizeDuration(output)).toBe(`start Formatting...\nsuccess ${message}\n`); + expect(normalizeDuration(output)).toBe( + `start Formatting...\nsuccess ${message}\n`, + ); }; export const setupFmtTest = (): FmtTestHarness => { let projectPath: string; - const resolveProjectPath = (filePath: string): string => path.join(projectPath, filePath); + const resolveProjectPath = (filePath: string): string => + path.join(projectPath, filePath); - const projectFileExists = (filePath: string): boolean => existsSync(resolveProjectPath(filePath)); + const projectFileExists = (filePath: string): boolean => + existsSync(resolveProjectPath(filePath)); const writeProjectFile = (filePath: string, content: string): void => { const absolutePath = resolveProjectPath(filePath); @@ -64,7 +79,10 @@ export const setupFmtTest = (): FmtTestHarness => { const writeFixturePlugin = (): void => { writeProjectFile( 'node_modules/prettier-plugin-fixture/package.json', - JSON.stringify({ name: 'prettier-plugin-fixture', exports: './index.mjs' }), + JSON.stringify({ + name: 'prettier-plugin-fixture', + exports: './index.mjs', + }), ); writeProjectFile( 'node_modules/prettier-plugin-fixture/index.mjs', @@ -86,7 +104,8 @@ export const setupFmtTest = (): FmtTestHarness => { const runFmt = (args: string[] = [], cwd = projectPath) => runCLI(['fmt', ...args], undefined, cwd); - const runFmtStdin = (args: string[], input: string) => runCLI(['fmt', ...args], input); + const runFmtStdin = (args: string[], input: string) => + runCLI(['fmt', ...args], input); beforeEach(() => { projectPath = mkdtempSync(path.join(import.meta.dirname, 'test-temp-fmt-')); diff --git a/packages/rstack/tests/cli/fmt/lsp.test.ts b/packages/rstack/tests/cli/fmt/lsp.test.ts index 4309feca..4e0ba51d 100644 --- a/packages/rstack/tests/cli/fmt/lsp.test.ts +++ b/packages/rstack/tests/cli/fmt/lsp.test.ts @@ -1,6 +1,11 @@ import { expect, test } from 'rstack/test'; import { setupFmtTest } from './helpers.ts'; -import { applyTextEdits, type LspClient, startLspServer, toFileUri } from './lspClient.ts'; +import { + applyTextEdits, + type LspClient, + startLspServer, + toFileUri, +} from './lspClient.ts'; const { resolveProjectPath, runFmt, writeProjectFile } = setupFmtTest(); @@ -27,7 +32,11 @@ const withLspServer = async ( } }; -const openDocument = (client: LspClient, filePath: string, text: string): string => { +const openDocument = ( + client: LspClient, + filePath: string, + text: string, +): string => { const uri = toFileUri(resolveProjectPath(filePath)); client.openDocument(uri, 'typescript', text); @@ -87,7 +96,9 @@ test( const edits = await client.formatDocument(uri); - expect(applyTextEdits(source, edits)).toBe('const inBuffer = "buffer";\n'); + expect(applyTextEdits(source, edits)).toBe( + 'const inBuffer = "buffer";\n', + ); }); }, TEST_TIMEOUT, @@ -252,8 +263,16 @@ test( await withLspServer( async (client) => { await client.initialize(resolveProjectPath('.')); - const ignoredUri = openDocument(client, 'src/ignored.ts', 'const ignored="ignored"\n'); - const formattedUri = openDocument(client, 'src/index.ts', 'const x=1\n'); + const ignoredUri = openDocument( + client, + 'src/ignored.ts', + 'const ignored="ignored"\n', + ); + const formattedUri = openDocument( + client, + 'src/index.ts', + 'const x=1\n', + ); expect(await client.formatDocument(ignoredUri)).toEqual([]); // The ignore file was read rather than reported as missing. @@ -330,7 +349,11 @@ define.fmt({ ignorePatterns: ['src/ignored.ts'] }); await withLspServer(async (client) => { await client.initialize(); - const uri = openDocument(client, 'src/ignored.ts', 'const ignored="ignored"\n'); + const uri = openDocument( + client, + 'src/ignored.ts', + 'const ignored="ignored"\n', + ); expect(await client.formatDocument(uri)).toEqual([]); }); @@ -379,12 +402,16 @@ test('returns exit code 2 for file arguments with --lsp', () => { const result = runFmt(['--lsp', 'src/index.ts']); expect(result.status).toBe(2); - expect(result.stderr).toContain('The --lsp option cannot be used with file arguments.'); + expect(result.stderr).toContain( + 'The --lsp option cannot be used with file arguments.', + ); }); test('returns exit code 2 for --stdin-filepath with --lsp', () => { const result = runFmt(['--lsp', '--stdin-filepath', 'src/index.ts']); expect(result.status).toBe(2); - expect(result.stderr).toContain('The --lsp option cannot be used with --stdin-filepath.'); + expect(result.stderr).toContain( + 'The --lsp option cannot be used with --stdin-filepath.', + ); }); diff --git a/packages/rstack/tests/cli/fmt/lspClient.ts b/packages/rstack/tests/cli/fmt/lspClient.ts index 31c2bb58..c2ac899a 100644 --- a/packages/rstack/tests/cli/fmt/lspClient.ts +++ b/packages/rstack/tests/cli/fmt/lspClient.ts @@ -13,14 +13,19 @@ type JsonRpcMessage = { }; export type Position = { line: number; character: number }; -export type TextEdit = { range: { start: Position; end: Position }; newText: string }; +export type TextEdit = { + range: { start: Position; end: Position }; + newText: string; +}; export type ShownMessage = { type: number; message: string }; export type LspClient = { notify: (method: string, params: unknown) => void; /** Initializes the server with `root` as the workspace root; defaults to the spawn cwd. */ - initialize: (root?: string) => Promise<{ capabilities: Record }>; + initialize: ( + root?: string, + ) => Promise<{ capabilities: Record }>; openDocument: (uri: string, languageId: string, text: string) => void; formatDocument: (uri: string) => Promise; /** `window/showMessage` notifications received so far, in order. */ @@ -34,13 +39,19 @@ const CONTENT_LENGTH_REGEXP = /content-length:\s*(\d+)/i; /** A header block is `key: value` lines separated by `\r\n` and nothing else. */ const HEADER_BLOCK_REGEXP = /^[^\r\n:]+:[^\r\n]*(?:\r\n[^\r\n:]+:[^\r\n]*)*$/; -export const toFileUri = (filePath: string): string => pathToFileURL(filePath).href; +export const toFileUri = (filePath: string): string => + pathToFileURL(filePath).href; /** Applies LSP text edits to a document, mirroring an editor. */ export const applyTextEdits = (text: string, edits: TextEdit[]): string => - TextDocument.applyEdits(TextDocument.create('file:///document', 'plaintext', 1, text), edits); + TextDocument.applyEdits( + TextDocument.create('file:///document', 'plaintext', 1, text), + edits, + ); -const readMessage = (buffer: Buffer): { message: JsonRpcMessage; rest: Buffer } | undefined => { +const readMessage = ( + buffer: Buffer, +): { message: JsonRpcMessage; rest: Buffer } | undefined => { const headerEnd = buffer.indexOf('\r\n\r\n'); if (headerEnd === -1) { return undefined; @@ -50,7 +61,9 @@ const readMessage = (buffer: Buffer): { message: JsonRpcMessage; rest: Buffer } // Real clients fall out of sync here, so anything that is not a header is a // failure rather than something to skip over. if (!HEADER_BLOCK_REGEXP.test(headers)) { - throw new Error(`Unexpected bytes on stdout before a message: ${JSON.stringify(headers)}.`); + throw new Error( + `Unexpected bytes on stdout before a message: ${JSON.stringify(headers)}.`, + ); } const contentLength = CONTENT_LENGTH_REGEXP.exec(headers); @@ -65,7 +78,9 @@ const readMessage = (buffer: Buffer): { message: JsonRpcMessage; rest: Buffer } } return { - message: JSON.parse(buffer.subarray(bodyStart, bodyEnd).toString('utf8')) as JsonRpcMessage, + message: JSON.parse( + buffer.subarray(bodyStart, bodyEnd).toString('utf8'), + ) as JsonRpcMessage, rest: buffer.subarray(bodyEnd), }; }; @@ -128,18 +143,26 @@ export const startLspServer = (cwd: string, args: string[] = []): LspClient => { const closed = new Promise((resolve) => { childProcess.once('close', (code) => { exitCode = code; - fail(new Error(`The language server exited with code ${code}.\n${stderr}`)); + fail( + new Error(`The language server exited with code ${code}.\n${stderr}`), + ); resolve(code); }); }); const send = (message: Record): void => { - const body = Buffer.from(JSON.stringify({ jsonrpc: '2.0', ...message }), 'utf8'); + const body = Buffer.from( + JSON.stringify({ jsonrpc: '2.0', ...message }), + 'utf8', + ); childProcess.stdin.write(`Content-Length: ${body.byteLength}\r\n\r\n`); childProcess.stdin.write(body); }; - const request = (method: string, params: unknown): Promise => { + const request = ( + method: string, + params: unknown, + ): Promise => { if (failure) { return Promise.reject(failure); } @@ -147,7 +170,10 @@ export const startLspServer = (cwd: string, args: string[] = []): LspClient => { const id = nextId++; return new Promise((resolve, reject) => { - pending.set(id, { resolve: resolve as (result: unknown) => void, reject }); + pending.set(id, { + resolve: resolve as (result: unknown) => void, + reject, + }); send({ id, method, params }); }); }; diff --git a/packages/rstack/tests/cli/fmt/patterns.test.ts b/packages/rstack/tests/cli/fmt/patterns.test.ts index 52ec4480..b87eee16 100644 --- a/packages/rstack/tests/cli/fmt/patterns.test.ts +++ b/packages/rstack/tests/cli/fmt/patterns.test.ts @@ -19,7 +19,11 @@ test('returns exit code 2 when no files match', () => { test('allows no files to match with --no-error-on-unmatched-pattern', () => { for (const modeArgs of [[], ['--check'], ['--list-different']]) { - const result = runFmt([...modeArgs, '--no-error-on-unmatched-pattern', 'missing/**/*.ts']); + const result = runFmt([ + ...modeArgs, + '--no-error-on-unmatched-pattern', + 'missing/**/*.ts', + ]); expect(result.status).toBe(0); expect(result.stdout).toBe(''); @@ -78,7 +82,9 @@ test('supports -u as an alias for --ignore-unknown', () => { const result = runFmt(['-u', 'notes.unknown']); expect(result.status).toBe(0); - expect(result.stdout).toBe('start Formatting...\nsuccess No supported files to format.\n'); + expect(result.stdout).toBe( + 'start Formatting...\nsuccess No supported files to format.\n', + ); expect(result.stderr).toBe(''); }); @@ -87,7 +93,9 @@ test('does not treat unmatched patterns as unknown files', () => { expect(result.status).toBe(2); expect(result.stdout).toBe(''); - expect(result.stderr).toContain('No supported files matched "missing/**/*.unknown"'); + expect(result.stderr).toContain( + 'No supported files matched "missing/**/*.unknown"', + ); }); test('does not treat unsupported files as unmatched patterns', () => { diff --git a/packages/rstack/tests/cli/fmt/stdin.test.ts b/packages/rstack/tests/cli/fmt/stdin.test.ts index 21e5a4c5..ebf0e676 100644 --- a/packages/rstack/tests/cli/fmt/stdin.test.ts +++ b/packages/rstack/tests/cli/fmt/stdin.test.ts @@ -1,10 +1,17 @@ import { expect, test } from 'rstack/test'; -import { packageJsonSource, setupFmtTest, sortedPackageJson } from './helpers.ts'; +import { + packageJsonSource, + setupFmtTest, + sortedPackageJson, +} from './helpers.ts'; const { projectFileExists, runFmtStdin, writeProjectFile } = setupFmtTest(); test('formats stdin for the given filepath', () => { - const result = runFmtStdin(['--stdin-filepath', 'src/index.ts'], 'const message="hello"'); + const result = runFmtStdin( + ['--stdin-filepath', 'src/index.ts'], + 'const message="hello"', + ); expect(result.status).toBe(0); expect(result.stdout).toBe('const message = "hello";\n'); @@ -31,7 +38,10 @@ define.fmt({ `, ); - const result = runFmtStdin(['--stdin-filepath', 'src/index.test.ts'], 'const test="test"'); + const result = runFmtStdin( + ['--stdin-filepath', 'src/index.test.ts'], + 'const test="test"', + ); expect(result.status).toBe(0); expect(result.stdout).toBe("const test = 'test'\n"); @@ -47,7 +57,10 @@ define.fmt({ sortPackageJson: true }); `, ); - const result = runFmtStdin(['--stdin-filepath', 'package.json'], packageJsonSource); + const result = runFmtStdin( + ['--stdin-filepath', 'package.json'], + packageJsonSource, + ); expect(result.status).toBe(0); expect(result.stdout).toBe(sortedPackageJson); @@ -99,11 +112,16 @@ test('returns exit code 2 when no parser can be inferred for stdin', () => { expect(result.status).toBe(2); expect(result.stdout).toBe(''); - expect(result.stderr).toContain('No parser could be inferred for "data.unknown".'); + expect(result.stderr).toContain( + 'No parser could be inferred for "data.unknown".', + ); }); test('ignores stdin when no parser can be inferred with --ignore-unknown', () => { - const result = runFmtStdin(['--stdin-filepath', 'data.unknown', '--ignore-unknown'], 'value'); + const result = runFmtStdin( + ['--stdin-filepath', 'data.unknown', '--ignore-unknown'], + 'value', + ); expect(result.status).toBe(0); expect(result.stdout).toBe(''); @@ -111,7 +129,10 @@ test('ignores stdin when no parser can be inferred with --ignore-unknown', () => }); test('returns exit code 2 for stdin parse errors', () => { - const result = runFmtStdin(['--stdin-filepath', 'index.ts'], 'const value = ;'); + const result = runFmtStdin( + ['--stdin-filepath', 'index.ts'], + 'const value = ;', + ); expect(result.status).toBe(2); expect(result.stdout).toBe(''); @@ -121,7 +142,10 @@ test('returns exit code 2 for stdin parse errors', () => { test.each(['--write', '--check', '--list-different'])( 'returns exit code 2 for %s with --stdin-filepath', (option) => { - const result = runFmtStdin(['--stdin-filepath', 'index.ts', option], 'const value=1'); + const result = runFmtStdin( + ['--stdin-filepath', 'index.ts', option], + 'const value=1', + ); expect(result.status).toBe(2); expect(result.stdout).toBe(''); @@ -132,7 +156,10 @@ test.each(['--write', '--check', '--list-different'])( ); test('returns exit code 2 for file arguments with --stdin-filepath', () => { - const result = runFmtStdin(['--stdin-filepath', 'index.ts', 'src/other.ts'], 'const value=1'); + const result = runFmtStdin( + ['--stdin-filepath', 'index.ts', 'src/other.ts'], + 'const value=1', + ); expect(result.status).toBe(2); expect(result.stdout).toBe(''); diff --git a/packages/rstack/tests/cli/fmt/vue.test.ts b/packages/rstack/tests/cli/fmt/vue.test.ts index ffa19c6a..bdfe2f79 100644 --- a/packages/rstack/tests/cli/fmt/vue.test.ts +++ b/packages/rstack/tests/cli/fmt/vue.test.ts @@ -6,13 +6,17 @@ const { readProjectFile, runFmt, writeProjectFile } = setupFmtTest(); test.each([ { name: 'TypeScript', - source: '\n', - expected: '\n', + source: + '\n', + expected: + '\n', }, { name: 'TSX', - source: '\n', - expected: '\n', + source: + '\n', + expected: + '\n', }, ])('formats $name embedded in Vue files', ({ source, expected }) => { writeProjectFile('App.vue', source); diff --git a/packages/rstack/tests/cli/setup/index.test.ts b/packages/rstack/tests/cli/setup/index.test.ts index 3f29104f..0780fd4a 100644 --- a/packages/rstack/tests/cli/setup/index.test.ts +++ b/packages/rstack/tests/cli/setup/index.test.ts @@ -1,5 +1,11 @@ import { spawnSync } from 'node:child_process'; -import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; +import { + existsSync, + mkdirSync, + mkdtempSync, + rmSync, + writeFileSync, +} from 'node:fs'; import path from 'node:path'; import { afterEach, beforeEach } from 'rstack/test'; import { normalizeHelpOutput, RSTACK_BIN_PATH, test } from '#test-helpers'; @@ -61,7 +67,9 @@ test('reports missing and repeated hooks directory options', ({ expect }) => { const repeated = runSetup(['--hooks-dir', 'first', '--hooks-dir', 'second']); expect(repeated.status).toBe(1); - expect(repeated.stderr).toContain('The --hooks-dir option cannot be specified more than once.'); + expect(repeated.stderr).toContain( + 'The --hooks-dir option cannot be specified more than once.', + ); }); test('rejects invalid hooks directory options', ({ expect }) => { @@ -80,27 +88,42 @@ test('rejects invalid hooks directory options', ({ expect }) => { expect(parent.stderr).toContain('Git hooks directory must not contain "..".'); }); -test('installs hooks silently without loading Rstack config', ({ execCli, expect }) => { +test('installs hooks silently without loading Rstack config', ({ + execCli, + expect, +}) => { initRepository(); - writeFileSync(path.join(cwd, 'rstack.config.ts'), 'throw new Error("must not load");\n'); + writeFileSync( + path.join(cwd, 'rstack.config.ts'), + 'throw new Error("must not load");\n', + ); expect(execCli('setup', { cwd, env })).toBe(''); expect(git(['config', '--local', '--get', 'core.hooksPath'])).toBe(hooksPath); expect(existsSync(path.join(cwd, hooksPath, 'runner'))).toBe(true); - expect(existsSync(path.join(cwd, '.rstack', 'hooks', 'pre-commit'))).toBe(false); + expect(existsSync(path.join(cwd, '.rstack', 'hooks', 'pre-commit'))).toBe( + false, + ); expect(execCli('setup', { cwd, env })).toBe(''); }); -test('installs root-relative hooks and reports owner conflicts', ({ execCli, expect }) => { +test('installs root-relative hooks and reports owner conflicts', ({ + execCli, + expect, +}) => { initRepository(); const frontend = path.join(cwd, 'frontend'); const docs = path.join(cwd, 'docs'); mkdirSync(frontend); mkdirSync(docs); - expect(execCli('setup --hooks-dir "custom hooks"', { cwd: frontend, env })).toBe(''); - expect(git(['config', '--local', '--get', 'core.hooksPath'])).toBe('custom hooks/_'); + expect( + execCli('setup --hooks-dir "custom hooks"', { cwd: frontend, env }), + ).toBe(''); + expect(git(['config', '--local', '--get', 'core.hooksPath'])).toBe( + 'custom hooks/_', + ); expect(existsSync(path.join(cwd, 'custom hooks', '_', 'runner'))).toBe(true); const conflict = runSetup(['--hooks-dir', 'custom hooks'], docs); @@ -110,7 +133,10 @@ test('installs root-relative hooks and reports owner conflicts', ({ execCli, exp ); }); -test('skips non-Git directories without creating files', ({ execCli, expect }) => { +test('skips non-Git directories without creating files', ({ + execCli, + expect, +}) => { expect(execCli('setup', { cwd, env })).toContain( 'info Git hooks setup skipped: not a Git repository.', ); @@ -120,7 +146,9 @@ test('skips non-Git directories without creating files', ({ execCli, expect }) = test('skips setup when hooks are disabled', ({ execCli, expect }) => { const output = execCli('setup', { cwd, env: { ...env, RSTACK_HOOKS: '0' } }); - expect(output).toContain('info Git hooks setup skipped: disabled by RSTACK_HOOKS.'); + expect(output).toContain( + 'info Git hooks setup skipped: disabled by RSTACK_HOOKS.', + ); expect(existsSync(path.join(cwd, '.rstack'))).toBe(false); }); diff --git a/packages/rstack/tests/cli/specify-config/index.test.ts b/packages/rstack/tests/cli/specify-config/index.test.ts index 99a4d5e2..504f991c 100644 --- a/packages/rstack/tests/cli/specify-config/index.test.ts +++ b/packages/rstack/tests/cli/specify-config/index.test.ts @@ -1,7 +1,11 @@ import { getDistFiles, getFileContent } from '@rstackjs/test-utils'; import { test } from '#test-helpers'; -test('should build with rstack --config', async ({ prepareDist, execCli, expect }) => { +test('should build with rstack --config', async ({ + prepareDist, + execCli, + expect, +}) => { const distPath = await prepareDist(); execCli('build --config ./custom.config.ts'); diff --git a/packages/rstack/tests/cli/staged/fmt.test.ts b/packages/rstack/tests/cli/staged/fmt.test.ts index fa156f59..875a5e27 100644 --- a/packages/rstack/tests/cli/staged/fmt.test.ts +++ b/packages/rstack/tests/cli/staged/fmt.test.ts @@ -21,7 +21,9 @@ const git = (args: string[]): string => { }); if (result.status !== 0) { - throw new Error(result.stderr || `Git exited with status ${result.status}.`); + throw new Error( + result.stderr || `Git exited with status ${result.status}.`, + ); } return result.stdout; @@ -35,7 +37,9 @@ const runStaged = () => }); beforeEach(() => { - projectPath = mkdtempSync(path.join(import.meta.dirname, 'test-temp-staged-fmt-')); + projectPath = mkdtempSync( + path.join(import.meta.dirname, 'test-temp-staged-fmt-'), + ); env = { ...process.env, GIT_CONFIG_GLOBAL: path.join(projectPath, 'global.gitconfig'), @@ -74,11 +78,21 @@ test('formats staged files with rs fmt and applies ignore rules', () => { const result = runStaged(); expect(result.status).toBe(0); - expect(readProjectFile('file with spaces.ts')).toBe('const spaced = "spaced";\n'); - expect(readProjectFile('ignored-by-git.ts')).toBe('const gitIgnored = "git ignored";\n'); - expect(readProjectFile('ignored-by-fmt.ts')).toBe('const fmtIgnored="fmt ignored"'); - expect(git(['show', ':file with spaces.ts'])).toBe('const spaced = "spaced";\n'); - expect(git(['show', ':ignored-by-git.ts'])).toBe('const gitIgnored = "git ignored";\n'); + expect(readProjectFile('file with spaces.ts')).toBe( + 'const spaced = "spaced";\n', + ); + expect(readProjectFile('ignored-by-git.ts')).toBe( + 'const gitIgnored = "git ignored";\n', + ); + expect(readProjectFile('ignored-by-fmt.ts')).toBe( + 'const fmtIgnored="fmt ignored"', + ); + expect(git(['show', ':file with spaces.ts'])).toBe( + 'const spaced = "spaced";\n', + ); + expect(git(['show', ':ignored-by-git.ts'])).toBe( + 'const gitIgnored = "git ignored";\n', + ); }); test('allows rs fmt when all staged files are ignored', () => { @@ -91,7 +105,9 @@ test('allows rs fmt when all staged files are ignored', () => { expect(result.status).toBe(0); expect(readProjectFile('ignored-by-fmt.ts')).toBe(source); expect(git(['show', ':ignored-by-fmt.ts'])).toBe(source); - expect(`${result.stdout}\n${result.stderr}`).not.toContain('No supported files matched'); + expect(`${result.stdout}\n${result.stderr}`).not.toContain( + 'No supported files matched', + ); }); test('still rejects staged files unsupported by rs fmt', () => { @@ -101,7 +117,9 @@ test('still rejects staged files unsupported by rs fmt', () => { const result = runStaged(); expect(result.status).toBe(1); - expect(`${result.stdout}\n${result.stderr}`).toContain('No supported files matched'); + expect(`${result.stdout}\n${result.stderr}`).toContain( + 'No supported files matched', + ); }); test('allows staged files unsupported by rs fmt with --ignore-unknown', () => { @@ -120,7 +138,9 @@ define.staged({ const result = runStaged(); expect(result.status).toBe(0); - expect(`${result.stdout}\n${result.stderr}`).not.toContain('No supported files matched'); + expect(`${result.stdout}\n${result.stderr}`).not.toContain( + 'No supported files matched', + ); }); test('propagates rs fmt failures', () => { diff --git a/packages/rstack/tests/config/define-app-lib/index.test.ts b/packages/rstack/tests/config/define-app-lib/index.test.ts index 99f6a8a5..3ed77ad4 100644 --- a/packages/rstack/tests/config/define-app-lib/index.test.ts +++ b/packages/rstack/tests/config/define-app-lib/index.test.ts @@ -1,5 +1,7 @@ import { test } from '#test-helpers'; -test('should prefer define.app when app and lib are both defined', ({ execCli }) => { +test('should prefer define.app when app and lib are both defined', ({ + execCli, +}) => { execCli('test'); }); diff --git a/packages/rstack/tests/config/define-app/index.test.ts b/packages/rstack/tests/config/define-app/index.test.ts index 73bbe51f..745ea7bd 100644 --- a/packages/rstack/tests/config/define-app/index.test.ts +++ b/packages/rstack/tests/config/define-app/index.test.ts @@ -4,7 +4,11 @@ import { test } from '#test-helpers'; const expectedText = 'define.app works'; -test('should build app with define.app config', async ({ prepareDist, execCli, expect }) => { +test('should build app with define.app config', async ({ + prepareDist, + execCli, + expect, +}) => { const distPath = await prepareDist(); try { diff --git a/packages/rstack/tests/config/define-doc/index.test.ts b/packages/rstack/tests/config/define-doc/index.test.ts index 464e7580..18142fc4 100644 --- a/packages/rstack/tests/config/define-doc/index.test.ts +++ b/packages/rstack/tests/config/define-doc/index.test.ts @@ -3,7 +3,11 @@ import { test } from '#test-helpers'; const expectedText = 'define.doc works'; -test('should build docs with define.doc config', async ({ prepareDist, execCli, expect }) => { +test('should build docs with define.doc config', async ({ + prepareDist, + execCli, + expect, +}) => { const distPath = await prepareDist('doc_build'); execCli('doc build'); diff --git a/packages/rstack/tests/config/define-lib/index.test.ts b/packages/rstack/tests/config/define-lib/index.test.ts index 53435b7e..db68d6e0 100644 --- a/packages/rstack/tests/config/define-lib/index.test.ts +++ b/packages/rstack/tests/config/define-lib/index.test.ts @@ -3,7 +3,11 @@ import { test } from '#test-helpers'; const expectedText = 'define.lib works'; -test('should build lib with define.lib config', async ({ prepareDist, execCli, expect }) => { +test('should build lib with define.lib config', async ({ + prepareDist, + execCli, + expect, +}) => { const distPath = await prepareDist(); execCli('lib'); diff --git a/packages/rstack/tests/config/define-lint/index.test.ts b/packages/rstack/tests/config/define-lint/index.test.ts index 402266fc..aff80258 100644 --- a/packages/rstack/tests/config/define-lint/index.test.ts +++ b/packages/rstack/tests/config/define-lint/index.test.ts @@ -7,7 +7,11 @@ test('should run lint with define.lint config', ({ execCli }) => { execCli('lint src/index.js'); }); -test('should fail when lint reports errors', async ({ cwd, execCli, logHelper }) => { +test('should fail when lint reports errors', async ({ + cwd, + execCli, + logHelper, +}) => { const filePath = path.join(cwd, 'src/test-temp-error.js'); await writeFile(filePath, 'debugger;'); expect(() => execCli('lint src/test-temp-error.js')).toThrow(); diff --git a/packages/rstack/tests/config/define-test-projects-app/index.test.ts b/packages/rstack/tests/config/define-test-projects-app/index.test.ts index 74f00e4d..d1e41c96 100644 --- a/packages/rstack/tests/config/define-test-projects-app/index.test.ts +++ b/packages/rstack/tests/config/define-test-projects-app/index.test.ts @@ -1,5 +1,7 @@ import { test } from '#test-helpers'; -test('should apply define.app config to every inline test project', ({ execCli }) => { +test('should apply define.app config to every inline test project', ({ + execCli, +}) => { execCli('test'); }); diff --git a/packages/rstack/tests/config/define-test-projects-lib/index.test.ts b/packages/rstack/tests/config/define-test-projects-lib/index.test.ts index 62372a46..724fd167 100644 --- a/packages/rstack/tests/config/define-test-projects-lib/index.test.ts +++ b/packages/rstack/tests/config/define-test-projects-lib/index.test.ts @@ -1,5 +1,7 @@ import { test } from '#test-helpers'; -test('should apply define.lib config to every inline test project', ({ execCli }) => { +test('should apply define.lib config to every inline test project', ({ + execCli, +}) => { execCli('test'); }); diff --git a/packages/rstack/tests/config/load-config/index.test.ts b/packages/rstack/tests/config/load-config/index.test.ts index a738f80e..fa2e9c43 100644 --- a/packages/rstack/tests/config/load-config/index.test.ts +++ b/packages/rstack/tests/config/load-config/index.test.ts @@ -15,7 +15,8 @@ declare global { } const state = getConfigState(); -const configPath = (fileName: string): string => path.join(import.meta.dirname, fileName); +const configPath = (fileName: string): string => + path.join(import.meta.dirname, fileName); const loadConfigFile = (fileName: string) => loadRstackConfig({ configFilePath: configPath(fileName) }); @@ -62,7 +63,9 @@ test('should resolve a relative explicit config path from cwd', async () => { }); test('should search for the config file in cwd', async () => { - await expect(loadRstackConfig({ cwd: import.meta.dirname })).rejects.toThrow('test config error'); + await expect(loadRstackConfig({ cwd: import.meta.dirname })).rejects.toThrow( + 'test config error', + ); }); test('should isolate parallel config sessions across top-level await', async () => { diff --git a/packages/rstack/tests/config/reload-app-config/index.test.ts b/packages/rstack/tests/config/reload-app-config/index.test.ts index a7f9ab93..7fb1ea1c 100644 --- a/packages/rstack/tests/config/reload-app-config/index.test.ts +++ b/packages/rstack/tests/config/reload-app-config/index.test.ts @@ -9,7 +9,10 @@ test('should restart dev server and reload config when Rstack config changes', a }) => { const dist1 = await prepareDist(); const dist2 = await prepareDist('dist-2'); - const configFile = path.join(import.meta.dirname, 'test-temp-rstack.config.ts'); + const configFile = path.join( + import.meta.dirname, + 'test-temp-rstack.config.ts', + ); await writeFile( configFile, @@ -47,8 +50,14 @@ define.app({ await waitForFile(dist2); }); -test('should reload config when an imported file changes', async ({ execCliAsync, logHelper }) => { - const configFile = path.join(import.meta.dirname, 'test-temp-import.config.ts'); +test('should reload config when an imported file changes', async ({ + execCliAsync, + logHelper, +}) => { + const configFile = path.join( + import.meta.dirname, + 'test-temp-import.config.ts', + ); const importedFile = path.join(import.meta.dirname, 'test-temp-imported.ts'); await writeFile(importedFile, ''); @@ -69,5 +78,7 @@ define.app({ await writeFile(importedFile, '// changed\n'); - await logHelper.expectLog('restarting server as test-temp-imported.ts changed'); + await logHelper.expectLog( + 'restarting server as test-temp-imported.ts changed', + ); }); diff --git a/packages/rstack/tests/config/reload-doc-config/index.test.ts b/packages/rstack/tests/config/reload-doc-config/index.test.ts index 0b00474b..6aece32f 100644 --- a/packages/rstack/tests/config/reload-doc-config/index.test.ts +++ b/packages/rstack/tests/config/reload-doc-config/index.test.ts @@ -7,8 +7,14 @@ test('should restart doc dev server when Rstack config changes', async ({ execCliAsync, logHelper, }) => { - const configFile = path.join(import.meta.dirname, 'test-temp-rstack.config.ts'); - const userWatchFile = path.join(import.meta.dirname, 'test-temp-user-watch.txt'); + const configFile = path.join( + import.meta.dirname, + 'test-temp-rstack.config.ts', + ); + const userWatchFile = path.join( + import.meta.dirname, + 'test-temp-user-watch.txt', + ); const writeConfig = (title: string) => writeFile( @@ -33,19 +39,25 @@ define.doc({ await writeFile(userWatchFile, 'initial\n'); await writeConfig('before config change'); - execCliAsync(`doc --config test-temp-rstack.config.ts --port ${await getRandomPort()}`); + execCliAsync( + `doc --config test-temp-rstack.config.ts --port ${await getRandomPort()}`, + ); await logHelper.expectBuildEnd(); logHelper.clearLogs(); await writeConfig('after config change'); - await logHelper.expectLog('restarting server as test-temp-rstack.config.ts changed'); + await logHelper.expectLog( + 'restarting server as test-temp-rstack.config.ts changed', + ); await logHelper.expectBuildEnd(); logHelper.clearLogs(); await writeFile(userWatchFile, 'changed\n'); - await logHelper.expectLog('restarting server as test-temp-user-watch.txt changed'); + await logHelper.expectLog( + 'restarting server as test-temp-user-watch.txt changed', + ); await logHelper.expectBuildEnd(); }); @@ -53,10 +65,16 @@ test('should restart doc dev server when an imported config file changes', async execCliAsync, logHelper, }) => { - const configFile = path.join(import.meta.dirname, 'test-temp-import.config.ts'); + const configFile = path.join( + import.meta.dirname, + 'test-temp-import.config.ts', + ); const importedFile = path.join(import.meta.dirname, 'test-temp-imported.ts'); - await writeFile(importedFile, "export const title = 'before import change';\n"); + await writeFile( + importedFile, + "export const title = 'before import change';\n", + ); await writeFile( configFile, `import { define } from 'rstack'; @@ -69,12 +87,19 @@ define.doc({ `, ); - execCliAsync(`doc --config test-temp-import.config.ts --port ${await getRandomPort()}`); + execCliAsync( + `doc --config test-temp-import.config.ts --port ${await getRandomPort()}`, + ); await logHelper.expectBuildEnd(); logHelper.clearLogs(); - await writeFile(importedFile, "export const title = 'after import change';\n"); + await writeFile( + importedFile, + "export const title = 'after import change';\n", + ); - await logHelper.expectLog('restarting server as test-temp-imported.ts changed'); + await logHelper.expectLog( + 'restarting server as test-temp-imported.ts changed', + ); await logHelper.expectBuildEnd(); }); diff --git a/packages/rstack/tests/config/reload-lib-config/index.test.ts b/packages/rstack/tests/config/reload-lib-config/index.test.ts index a793c158..d2f27179 100644 --- a/packages/rstack/tests/config/reload-lib-config/index.test.ts +++ b/packages/rstack/tests/config/reload-lib-config/index.test.ts @@ -10,8 +10,14 @@ test('should restart lib watch build when Rstack config changes', async ({ }) => { const dist1 = await prepareDist(); const dist2 = await prepareDist('dist-2'); - const configFile = path.join(import.meta.dirname, 'test-temp-rstack.config.ts'); - const userWatchFile = path.join(import.meta.dirname, 'test-temp-user-watch.txt'); + const configFile = path.join( + import.meta.dirname, + 'test-temp-rstack.config.ts', + ); + const userWatchFile = path.join( + import.meta.dirname, + 'test-temp-user-watch.txt', + ); const writeConfig = (distPath: string) => writeFile( @@ -42,14 +48,18 @@ define.lib({ await writeConfig('dist-2'); - await logHelper.expectLog('restarting build as test-temp-rstack.config.ts changed'); + await logHelper.expectLog( + 'restarting build as test-temp-rstack.config.ts changed', + ); await logHelper.expectLog('build completed, watching for changes...'); await waitForFile(path.join(dist2, 'index.js')); logHelper.clearLogs(); await writeFile(userWatchFile, 'changed\n'); - await logHelper.expectLog('restarting build as test-temp-user-watch.txt changed'); + await logHelper.expectLog( + 'restarting build as test-temp-user-watch.txt changed', + ); await logHelper.expectLog('build completed, watching for changes...'); }); @@ -60,7 +70,10 @@ test('should restart lib watch build when an imported config file changes', asyn }) => { const dist1 = await prepareDist('dist-import-1'); const dist2 = await prepareDist('dist-import-2'); - const configFile = path.join(import.meta.dirname, 'test-temp-import.config.ts'); + const configFile = path.join( + import.meta.dirname, + 'test-temp-import.config.ts', + ); const importedFile = path.join(import.meta.dirname, 'test-temp-imported.ts'); await writeFile(importedFile, "export const distPath = 'dist-import-1';\n"); @@ -84,7 +97,9 @@ define.lib({ await writeFile(importedFile, "export const distPath = 'dist-import-2';\n"); - await logHelper.expectLog('restarting build as test-temp-imported.ts changed'); + await logHelper.expectLog( + 'restarting build as test-temp-imported.ts changed', + ); await logHelper.expectLog('build completed, watching for changes...'); await waitForFile(path.join(dist2, 'index.js')); }); diff --git a/packages/rstack/tests/exports/test-subpath/index.test.ts b/packages/rstack/tests/exports/test-subpath/index.test.ts index 74d693a7..d1da8169 100644 --- a/packages/rstack/tests/exports/test-subpath/index.test.ts +++ b/packages/rstack/tests/exports/test-subpath/index.test.ts @@ -1,6 +1,13 @@ import { expect, test } from 'rstack/test'; -const commonTestMethods = ['test', 'it', 'describe', 'expect', 'beforeAll', 'afterAll'] as const; +const commonTestMethods = [ + 'test', + 'it', + 'describe', + 'expect', + 'beforeAll', + 'afterAll', +] as const; test('should expose test APIs from `rstack/test`', async () => { const test = await import('rstack/test'); diff --git a/packages/rstack/tests/fmt/cacheIdentity.test.ts b/packages/rstack/tests/fmt/cacheIdentity.test.ts index 933b7229..56958b64 100644 --- a/packages/rstack/tests/fmt/cacheIdentity.test.ts +++ b/packages/rstack/tests/fmt/cacheIdentity.test.ts @@ -54,7 +54,9 @@ test('includes plugin fingerprints in option hashes', () => { const second = createOptionsHasher(new Map([[plugin, 'plugin@2']])); expect(first({ plugins: [plugin] })).toHaveLength(cacheHashLength); - expect(first({ plugins: [new URL(plugin)] })).toBe(first({ plugins: [plugin] })); + expect(first({ plugins: [new URL(plugin)] })).toBe( + first({ plugins: [plugin] }), + ); expect(first({ plugins: [plugin] })).not.toBe(second({ plugins: [plugin] })); }); @@ -71,8 +73,12 @@ test('bypasses user plugins and unserializable options', () => { ); cyclic.self = cyclic; - expect(hashOptions({ plugins: [path.resolve('plugin.mjs')] })).toBeUndefined(); - expect(hashOptions({ plugins: [pathToFileURL(path.resolve('plugin.mjs'))] })).toBeUndefined(); + expect( + hashOptions({ plugins: [path.resolve('plugin.mjs')] }), + ).toBeUndefined(); + expect( + hashOptions({ plugins: [pathToFileURL(path.resolve('plugin.mjs'))] }), + ).toBeUndefined(); expect(hashOptions(asOptions({ custom: cyclic }))).toBeUndefined(); expect(hashOptions(asOptions(unreadable))).toBeUndefined(); @@ -94,5 +100,7 @@ test('creates config-root-relative POSIX cache keys', () => { expect(resolveKey(firstPath)).toBe('src/nested/index.ts'); expect(resolveKey(secondPath)).toBe('src/other.ts'); expect(resolveKey(firstPath)).not.toBe(resolveKey(secondPath)); - expect(resolveKey(path.join(rootPath, '../shared/index.ts'))).toBe('../shared/index.ts'); + expect(resolveKey(path.join(rootPath, '../shared/index.ts'))).toBe( + '../shared/index.ts', + ); }); diff --git a/packages/rstack/tests/fmt/cacheStore.test.ts b/packages/rstack/tests/fmt/cacheStore.test.ts index e6f038b7..5c466cd7 100644 --- a/packages/rstack/tests/fmt/cacheStore.test.ts +++ b/packages/rstack/tests/fmt/cacheStore.test.ts @@ -1,4 +1,10 @@ -import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'; +import { + existsSync, + mkdirSync, + readFileSync, + readdirSync, + writeFileSync, +} from 'node:fs'; import path from 'node:path'; import { expect, test } from 'rstack/test'; import { @@ -144,6 +150,8 @@ test('does not throw or leave temporary files when persistence fails', async () store.set('src/a.ts', firstEntry); await expect(store.save()).resolves.toBe(false); - expect(readdirSync(rootPath).filter((name) => name.endsWith('.tmp'))).toEqual([]); + expect( + readdirSync(rootPath).filter((name) => name.endsWith('.tmp')), + ).toEqual([]); }); }); diff --git a/packages/rstack/tests/fmt/config.test.ts b/packages/rstack/tests/fmt/config.test.ts index fe85d292..eb5a9b28 100644 --- a/packages/rstack/tests/fmt/config.test.ts +++ b/packages/rstack/tests/fmt/config.test.ts @@ -1,6 +1,9 @@ import path from 'node:path'; import { expect, test } from 'rstack/test'; -import { createOptionsResolver, normalizeFmtConfig } from '../../src/fmt/config.ts'; +import { + createOptionsResolver, + normalizeFmtConfig, +} from '../../src/fmt/config.ts'; const rootPath = path.join(import.meta.dirname, 'project'); @@ -14,7 +17,9 @@ test('reuses base options when no override matches', () => { ); const resolveOptions = createOptionsResolver(config); - expect(resolveOptions(path.join(rootPath, 'index.js'))).toBe(config.baseOptions); + expect(resolveOptions(path.join(rootPath, 'index.js'))).toBe( + config.baseOptions, + ); }); test('applies basename and path overrides in declaration order', () => { @@ -82,5 +87,7 @@ test('applies overrides outside the config root', () => { ); const resolveOptions = createOptionsResolver(config); - expect(resolveOptions(path.join(rootPath, '../shared/index.ts'))).toEqual({ semi: false }); + expect(resolveOptions(path.join(rootPath, '../shared/index.ts'))).toEqual({ + semi: false, + }); }); diff --git a/packages/rstack/tests/fmt/discoverPaths.test.ts b/packages/rstack/tests/fmt/discoverPaths.test.ts index 0fa2e6ca..6cad49f9 100644 --- a/packages/rstack/tests/fmt/discoverPaths.test.ts +++ b/packages/rstack/tests/fmt/discoverPaths.test.ts @@ -20,7 +20,10 @@ test('discovers non-binary files in stable order and skips hard-ignored paths', writeProjectFile(rootPath, '.jj/internal.js'); const files = await discoverFmtPaths({ cwd: rootPath }); - const filesWithNodeModules = await discoverFmtPaths({ cwd: rootPath, withNodeModules: true }); + const filesWithNodeModules = await discoverFmtPaths({ + cwd: rootPath, + withNodeModules: true, + }); expect(relativePaths(rootPath, files)).toEqual([ 'a.js', @@ -36,7 +39,10 @@ test('discovers non-binary files in stable order and skips hard-ignored paths', 'unknown.extension', ]); await expect( - discoverFmtPaths({ cwd: rootPath, patterns: ['node_modules/package/index.js'] }), + discoverFmtPaths({ + cwd: rootPath, + patterns: ['node_modules/package/index.js'], + }), ).resolves.toEqual([]); await expect( discoverFmtPaths({ @@ -54,7 +60,10 @@ test('keeps node_modules excluded by gitignore when built-in exclusion is disabl writeProjectFile(rootPath, 'node_modules/package/index.js'); writeProjectFile(rootPath, 'index.js'); - const files = await discoverFmtPaths({ cwd: rootPath, withNodeModules: true }); + const files = await discoverFmtPaths({ + cwd: rootPath, + withNodeModules: true, + }); expect(relativePaths(rootPath, files)).toEqual(['.gitignore', 'index.js']); }); @@ -93,7 +102,9 @@ test('combines files, directories, and globs without duplicates', async () => { path.join('src', 'a.ts'), path.join('test', 'c.ts'), ]); - expect(relativePaths(rootPath, dotFiles)).toEqual([path.join('dot', '.hidden.ts')]); + expect(relativePaths(rootPath, dotFiles)).toEqual([ + path.join('dot', '.hidden.ts'), + ]); await expect( discoverFmtPaths({ cwd: rootPath, patterns: ['missing/**/*.ts'] }), ).resolves.toEqual([]); @@ -112,13 +123,19 @@ test('applies nested gitignore rules with child negation', async () => { writeProjectFile(rootPath, 'dist/nested/keep.js'); writeProjectFile(rootPath, 'visible.ts'); - const files = await discoverFmtPaths({ cwd: rootPath, patterns: ['**/*.{js,ts}'] }); + const files = await discoverFmtPaths({ + cwd: rootPath, + patterns: ['**/*.{js,ts}'], + }); const ignoredNestedDirectory = await discoverFmtPaths({ cwd: rootPath, patterns: ['dist/nested'], }); - expect(relativePaths(rootPath, files)).toEqual([path.join('src', 'keep.js'), 'visible.ts']); + expect(relativePaths(rootPath, files)).toEqual([ + path.join('src', 'keep.js'), + 'visible.ts', + ]); expect(ignoredNestedDirectory).toEqual([]); }); }); @@ -129,7 +146,10 @@ test('does not extend a nested directory negation to its files', async () => { writeProjectFile(rootPath, 'scripts/.gitignore', '!debug\n'); writeProjectFile(rootPath, 'scripts/debug/launch.mjs'); - const files = await discoverFmtPaths({ cwd: rootPath, patterns: ['**/*.mjs'] }); + const files = await discoverFmtPaths({ + cwd: rootPath, + patterns: ['**/*.mjs'], + }); expect(files).toEqual([]); }); @@ -195,9 +215,15 @@ test('keeps valid nested gitignore rules around normalized and malformed lines', writeProjectFile(rootPath, 'src/drop.js'); writeProjectFile(rootPath, 'visible.ts'); - const files = await discoverFmtPaths({ cwd: rootPath, patterns: ['**/*.{js,ts}'] }); + const files = await discoverFmtPaths({ + cwd: rootPath, + patterns: ['**/*.{js,ts}'], + }); - expect(relativePaths(rootPath, files)).toEqual([path.join('src', 'keep.js'), 'visible.ts']); + expect(relativePaths(rootPath, files)).toEqual([ + path.join('src', 'keep.js'), + 'visible.ts', + ]); }); }); @@ -213,7 +239,9 @@ test('propagates native binding errors while loading a nested gitignore', async }); try { - await expect(discoverFmtPaths({ cwd: rootPath })).rejects.toBe(nativeError); + await expect(discoverFmtPaths({ cwd: rootPath })).rejects.toBe( + nativeError, + ); } finally { loadNativeBinding.mockRestore(); } @@ -230,10 +258,17 @@ test('lets explicit files bypass gitignore', async () => { cwd: rootPath, patterns: ['**/*.ts'], }); - const explicitFiles = await discoverFmtPaths({ cwd: rootPath, patterns: [keepPath] }); + const explicitFiles = await discoverFmtPaths({ + cwd: rootPath, + patterns: [keepPath], + }); - expect(relativePaths(rootPath, discoveredFiles)).toEqual([path.join('src', 'index.ts')]); - expect(relativePaths(rootPath, explicitFiles)).toEqual([path.join('generated', 'keep.ts')]); + expect(relativePaths(rootPath, discoveredFiles)).toEqual([ + path.join('src', 'index.ts'), + ]); + expect(relativePaths(rootPath, explicitFiles)).toEqual([ + path.join('generated', 'keep.ts'), + ]); }); }); @@ -249,7 +284,9 @@ test('applies an external ignore matcher to traversed and explicit paths', async path: path.relative(rootPath, filePath), isDirectory, }); - return isDirectory ? filePath === generatedPath : filePath === ignoredFilePath; + return isDirectory + ? filePath === generatedPath + : filePath === ignoredFilePath; }; const files = await discoverFmtPaths({ cwd: rootPath, isIgnored }); @@ -264,10 +301,15 @@ test('applies an external ignore matcher to traversed and explicit paths', async isIgnored, }); - expect(relativePaths(rootPath, files)).toEqual([path.join('src', 'index.ts')]); + expect(relativePaths(rootPath, files)).toEqual([ + path.join('src', 'index.ts'), + ]); expect(ignoredRoot).toEqual([]); expect(explicitIgnoredFile).toEqual([]); - expect(checkedPaths).toContainEqual({ path: 'generated', isDirectory: true }); + expect(checkedPaths).toContainEqual({ + path: 'generated', + isDirectory: true, + }); expect(checkedPaths).toContainEqual({ path: path.join('src', 'ignored.ts'), isDirectory: false, @@ -279,19 +321,27 @@ test('applies an external ignore matcher to traversed and explicit paths', async }); }); -test.runIf(process.platform !== 'win32')('does not follow file or directory symlinks', async () => { - await withTempProject(async (rootPath) => { - const targetPath = writeProjectFile(rootPath, 'target/index.ts'); - symlinkSync(path.join(rootPath, 'target'), path.join(rootPath, 'linked-directory')); - symlinkSync(targetPath, path.join(rootPath, 'linked-file.ts')); +test.runIf(process.platform !== 'win32')( + 'does not follow file or directory symlinks', + async () => { + await withTempProject(async (rootPath) => { + const targetPath = writeProjectFile(rootPath, 'target/index.ts'); + symlinkSync( + path.join(rootPath, 'target'), + path.join(rootPath, 'linked-directory'), + ); + symlinkSync(targetPath, path.join(rootPath, 'linked-file.ts')); + + const discoveredFiles = await discoverFmtPaths({ cwd: rootPath }); + const explicitFiles = await discoverFmtPaths({ + cwd: rootPath, + patterns: ['linked-directory', 'linked-file.ts'], + }); - const discoveredFiles = await discoverFmtPaths({ cwd: rootPath }); - const explicitFiles = await discoverFmtPaths({ - cwd: rootPath, - patterns: ['linked-directory', 'linked-file.ts'], + expect(relativePaths(rootPath, discoveredFiles)).toEqual([ + path.join('target', 'index.ts'), + ]); + expect(explicitFiles).toEqual([]); }); - - expect(relativePaths(rootPath, discoveredFiles)).toEqual([path.join('target', 'index.ts')]); - expect(explicitFiles).toEqual([]); - }); -}); + }, +); diff --git a/packages/rstack/tests/fmt/discovery.test.ts b/packages/rstack/tests/fmt/discovery.test.ts index 4dfd1937..0e062435 100644 --- a/packages/rstack/tests/fmt/discovery.test.ts +++ b/packages/rstack/tests/fmt/discovery.test.ts @@ -6,15 +6,22 @@ import { discoverFmtFiles } from '../../src/fmt/discovery.ts'; import type { FmtConfig } from '../../src/fmt/types.ts'; import { withTempProject, writeProjectFile } from './helpers.ts'; -const discover = async (cwd: string, patterns?: string[], config?: FmtConfig, configRoot = cwd) => +const discover = async ( + cwd: string, + patterns?: string[], + config?: FmtConfig, + configRoot = cwd, +) => discoverFmtFiles({ cwd, patterns, config: normalizeFmtConfig(config, configRoot), }); -const relativePaths = (rootPath: string, files: Awaited>): string[] => - files.map((file) => path.relative(rootPath, file.path)); +const relativePaths = ( + rootPath: string, + files: Awaited>, +): string[] => files.map((file) => path.relative(rootPath, file.path)); test('applies config ignore patterns to discovered and explicit files', async () => { await withTempProject(async (rootPath) => { @@ -24,13 +31,19 @@ test('applies config ignore patterns to discovered and explicit files', async () const config = { ignorePatterns: ['generated/blocked.ts'] }; const discoveredFiles = await discover(rootPath, undefined, config); - const explicitFiles = await discover(rootPath, [keepPath, blockedPath], config); + const explicitFiles = await discover( + rootPath, + [keepPath, blockedPath], + config, + ); expect(relativePaths(rootPath, discoveredFiles)).toEqual([ path.join('generated', 'keep.ts'), path.join('src', 'index.ts'), ]); - expect(relativePaths(rootPath, explicitFiles)).toEqual([path.join('generated', 'keep.ts')]); + expect(relativePaths(rootPath, explicitFiles)).toEqual([ + path.join('generated', 'keep.ts'), + ]); }); }); @@ -41,14 +54,23 @@ test('applies config ignore patterns outside the config root', async () => { mkdirSync(configRoot); await expect( - discover(configRoot, [filePath], { ignorePatterns: ['../shared/*.ts'] }, configRoot), + discover( + configRoot, + [filePath], + { ignorePatterns: ['../shared/*.ts'] }, + configRoot, + ), ).resolves.toEqual([]); }); }); test('excludes .rstack from discovery', async () => { await withTempProject(async (rootPath) => { - const cacheFile = writeProjectFile(rootPath, '.rstack/cache/fmt-v1.json', '{}'); + const cacheFile = writeProjectFile( + rootPath, + '.rstack/cache/fmt-v1.json', + '{}', + ); writeProjectFile(rootPath, 'index.ts'); const discoveredFiles = await discover(rootPath); @@ -85,7 +107,11 @@ test('excludes a custom cache directory', async () => { test('keeps files re-included by a CLI ignore file during directory traversal', async () => { await withTempProject(async (rootPath) => { - writeProjectFile(rootPath, '.prettierignore', 'generated/*\n!generated/keep.ts\n'); + writeProjectFile( + rootPath, + '.prettierignore', + 'generated/*\n!generated/keep.ts\n', + ); writeProjectFile(rootPath, 'generated/drop.ts'); writeProjectFile(rootPath, 'generated/keep.ts'); writeProjectFile(rootPath, 'src/index.ts'); @@ -112,7 +138,9 @@ test('defers parser inference to workers and preserves an explicit parser', asyn writeProjectFile(rootPath, 'unknown.extension'); const inferredFiles = await discover(rootPath); - const configuredFiles = await discover(rootPath, ['source.custom'], { parser: 'babel' }); + const configuredFiles = await discover(rootPath, ['source.custom'], { + parser: 'babel', + }); expect(relativePaths(rootPath, inferredFiles)).toEqual([ 'index.js', @@ -120,7 +148,9 @@ test('defers parser inference to workers and preserves an explicit parser', asyn 'source.custom', 'unknown.extension', ]); - expect(inferredFiles.every((file) => file.options.parser === undefined)).toBe(true); + expect( + inferredFiles.every((file) => file.options.parser === undefined), + ).toBe(true); expect(configuredFiles[0]).toEqual({ path: path.join(rootPath, 'source.custom'), options: { parser: 'babel' }, diff --git a/packages/rstack/tests/fmt/fileResolver.test.ts b/packages/rstack/tests/fmt/fileResolver.test.ts index 98e1e486..e0089095 100644 --- a/packages/rstack/tests/fmt/fileResolver.test.ts +++ b/packages/rstack/tests/fmt/fileResolver.test.ts @@ -21,7 +21,10 @@ test('applies matching overrides before resolving plugins', async () => { writeProjectFile( rootPath, 'node_modules/prettier-plugin-fixture/package.json', - JSON.stringify({ name: 'prettier-plugin-fixture', exports: './index.mjs' }), + JSON.stringify({ + name: 'prettier-plugin-fixture', + exports: './index.mjs', + }), ); const config = normalizeFmtConfig( { diff --git a/packages/rstack/tests/fmt/helpers.ts b/packages/rstack/tests/fmt/helpers.ts index 698708cb..1468b32d 100644 --- a/packages/rstack/tests/fmt/helpers.ts +++ b/packages/rstack/tests/fmt/helpers.ts @@ -1,7 +1,11 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; import path from 'node:path'; import { fmtCacheFileName } from '../../src/fmt/cacheStore.ts'; -import type { FmtCacheContext, FmtFileRequest, ResolvedFmtOptions } from '../../src/fmt/types.ts'; +import type { + FmtCacheContext, + FmtFileRequest, + ResolvedFmtOptions, +} from '../../src/fmt/types.ts'; export const createFmtRequest = ( filePath: string, @@ -19,7 +23,9 @@ export const createFmtCacheContext = (rootPath: string): FmtCacheContext => ({ export const withTempProject = async ( callback: (rootPath: string) => void | Promise, ): Promise => { - const rootPath = mkdtempSync(path.join(import.meta.dirname, 'test-temp-fmt-')); + const rootPath = mkdtempSync( + path.join(import.meta.dirname, 'test-temp-fmt-'), + ); // Prevent repository-level ignore rules from affecting the fixture. mkdirSync(path.join(rootPath, '.git')); @@ -30,7 +36,11 @@ export const withTempProject = async ( } }; -export const writeProjectFile = (rootPath: string, filePath: string, content = ''): string => { +export const writeProjectFile = ( + rootPath: string, + filePath: string, + content = '', +): string => { const absolutePath = path.join(rootPath, filePath); mkdirSync(path.dirname(absolutePath), { recursive: true }); writeFileSync(absolutePath, content); diff --git a/packages/rstack/tests/fmt/ignore.test.ts b/packages/rstack/tests/fmt/ignore.test.ts index 68220775..b8aa2126 100644 --- a/packages/rstack/tests/fmt/ignore.test.ts +++ b/packages/rstack/tests/fmt/ignore.test.ts @@ -55,7 +55,11 @@ test('does not apply negated directory patterns to files', async () => { test('applies negated patterns in declaration order', async () => { const isIgnored = await createMatcher(['*.js', '!src/keep.js']); - const isIgnoredAgain = await createMatcher(['*.js', '!src/keep.js', 'src/keep.js']); + const isIgnoredAgain = await createMatcher([ + '*.js', + '!src/keep.js', + 'src/keep.js', + ]); const isIgnoredAfterReinclude = await createMatcher(['dist', '!dist']); const filePath = path.join(rootPath, 'src/keep.js'); @@ -70,11 +74,17 @@ test('ignores common lock files by default and allows explicit negation', async const isIgnoredAfterReinclude = await createMatcher(['!pnpm-lock.yaml']); expect(isIgnored(path.join(rootPath, 'package-lock.json'))).toBe(true); - expect(isIgnored(path.join(rootPath, 'packages/app/pnpm-lock.yaml'))).toBe(true); - expect(isIgnored(path.join(rootPath, 'packages/app/PNPM-LOCK.YAML'))).toBe(false); + expect(isIgnored(path.join(rootPath, 'packages/app/pnpm-lock.yaml'))).toBe( + true, + ); + expect(isIgnored(path.join(rootPath, 'packages/app/PNPM-LOCK.YAML'))).toBe( + false, + ); expect(isIgnored(path.join(rootPath, '../shared/pnpm-lock.yaml'))).toBe(true); expect(isIgnored(path.join(rootPath, 'pnpm-lock.yaml.backup'))).toBe(false); - expect(isIgnoredAfterReinclude(path.join(rootPath, 'pnpm-lock.yaml'))).toBe(false); + expect(isIgnoredAfterReinclude(path.join(rootPath, 'pnpm-lock.yaml'))).toBe( + false, + ); }); test('does not let explicit files bypass ignore patterns', async () => { @@ -99,11 +109,18 @@ test('does not ignore other files when no patterns are configured', async () => test('loads repeated ignore paths relative to cwd and each ignore file', async () => { await withTempProject(async (projectPath) => { - writeProjectFile(projectPath, '.prettierignore', 'src/*.js\n!src/keep.js\n'); + writeProjectFile( + projectPath, + '.prettierignore', + 'src/*.js\n!src/keep.js\n', + ); writeProjectFile(projectPath, 'config/extra.ignore', '../generated/*.js\n'); const isIgnored = await createIgnoreMatcher({ - config: normalizeFmtConfig({ ignorePatterns: ['configured.js'] }, projectPath), + config: normalizeFmtConfig( + { ignorePatterns: ['configured.js'] }, + projectPath, + ), cwd: projectPath, ignorePaths: ['.prettierignore', 'config/extra.ignore'], }); diff --git a/packages/rstack/tests/fmt/lsp/minimalEdit.test.ts b/packages/rstack/tests/fmt/lsp/minimalEdit.test.ts index cf94e86f..7fc1882c 100644 --- a/packages/rstack/tests/fmt/lsp/minimalEdit.test.ts +++ b/packages/rstack/tests/fmt/lsp/minimalEdit.test.ts @@ -1,6 +1,9 @@ import { expect, test } from 'rstack/test'; import { TextDocument } from 'vscode-languageserver-textdocument'; -import { computeMinimalEdit, computeMinimalTextEdit } from '../../../src/fmt/lsp/minimalEdit.ts'; +import { + computeMinimalEdit, + computeMinimalTextEdit, +} from '../../../src/fmt/lsp/minimalEdit.ts'; /** Applies an edit the way an editor does, to prove it rewrites the document. */ const applyMinimalEdit = (source: string, formatted: string): string => { @@ -17,7 +20,10 @@ const applyMinimalEdit = (source: string, formatted: string): string => { * does: offsets become positions on the server and positions become offsets * again on the client, which moves any offset that lands inside a `\r\n`. */ -const applyMinimalEditThroughPositions = (source: string, formatted: string): string => { +const applyMinimalEditThroughPositions = ( + source: string, + formatted: string, +): string => { const edit = computeMinimalEdit(source, formatted); if (!edit) { return source; @@ -35,7 +41,9 @@ const applyMinimalEditThroughPositions = (source: string, formatted: string): st test('returns no edit for identical sources', () => { expect(computeMinimalEdit('', '')).toBeUndefined(); - expect(computeMinimalEdit('const x = 1;\n', 'const x = 1;\n')).toBeUndefined(); + expect( + computeMinimalEdit('const x = 1;\n', 'const x = 1;\n'), + ).toBeUndefined(); }); test('replaces the whole document when nothing is shared', () => { @@ -44,7 +52,11 @@ test('replaces the whole document when nothing is shared', () => { end: 0, newText: 'const x = 1;\n', }); - expect(computeMinimalEdit('a\n', '')).toEqual({ start: 0, end: 2, newText: '' }); + expect(computeMinimalEdit('a\n', '')).toEqual({ + start: 0, + end: 2, + newText: '', + }); }); test('trims a shared prefix', () => { @@ -134,8 +146,12 @@ test('survives a round trip through a real text document', () => { const formatted = 'const a = 1;\r\nconst b = 2;\r\n'; expect(applyMinimalEditThroughPositions(source, formatted)).toBe(formatted); - expect(applyMinimalEditThroughPositions('a\nb\n', 'a\r\nb\r\n')).toBe('a\r\nb\r\n'); - expect(applyMinimalEditThroughPositions('a\r\nb\r\n', 'a\nb\n')).toBe('a\nb\n'); + expect(applyMinimalEditThroughPositions('a\nb\n', 'a\r\nb\r\n')).toBe( + 'a\r\nb\r\n', + ); + expect(applyMinimalEditThroughPositions('a\r\nb\r\n', 'a\nb\n')).toBe( + 'a\nb\n', + ); }); // Line terminators are where offsets stop being interchangeable with positions, @@ -146,13 +162,20 @@ test('addresses every combination of line terminators', () => { const texts: string[] = ['']; let current = ['']; for (let length = 0; length < 5; length++) { - current = current.flatMap((text) => alphabet.map((character) => text + character)); + current = current.flatMap((text) => + alphabet.map((character) => text + character), + ); texts.push(...current); } const failures: string[] = []; for (const source of texts) { - const document = TextDocument.create('file:///a.ts', 'typescript', 1, source); + const document = TextDocument.create( + 'file:///a.ts', + 'typescript', + 1, + source, + ); for (const formatted of texts) { const edit = computeMinimalEdit(source, formatted); if (!edit) { @@ -163,7 +186,9 @@ test('addresses every combination of line terminators', () => { const end = document.offsetAt(document.positionAt(edit.end)); const applied = source.slice(0, start) + edit.newText + source.slice(end); if (applied !== formatted) { - failures.push(`${JSON.stringify(source)} -> ${JSON.stringify(formatted)}`); + failures.push( + `${JSON.stringify(source)} -> ${JSON.stringify(formatted)}`, + ); } // The hand-rolled position mapping must agree with the reference @@ -174,7 +199,9 @@ test('addresses every combination of line terminators', () => { end: document.positionAt(edit.end), }; if (JSON.stringify(range) !== JSON.stringify(expected)) { - failures.push(`positions ${JSON.stringify(source)} -> ${JSON.stringify(formatted)}`); + failures.push( + `positions ${JSON.stringify(source)} -> ${JSON.stringify(formatted)}`, + ); } } } diff --git a/packages/rstack/tests/fmt/lsp/server.test.ts b/packages/rstack/tests/fmt/lsp/server.test.ts index 597ca968..ca764b21 100644 --- a/packages/rstack/tests/fmt/lsp/server.test.ts +++ b/packages/rstack/tests/fmt/lsp/server.test.ts @@ -9,7 +9,10 @@ test('maps the edit onto the formatted document', async () => { expect(edits).toEqual([ { - range: { start: { line: 1, character: 7 }, end: { line: 1, character: 8 } }, + range: { + start: { line: 1, character: 7 }, + end: { line: 1, character: 8 }, + }, newText: ' = ', }, ]); @@ -18,8 +21,12 @@ test('maps the edit onto the formatted document', async () => { test('returns no edits for an already formatted document', async () => { const getText = () => 'const a = 1;\n'; - expect(await createDocumentEdits(getText, () => Promise.resolve('const a = 1;\n'))).toEqual([]); - expect(await createDocumentEdits(getText, () => Promise.resolve(undefined))).toEqual([]); + expect( + await createDocumentEdits(getText, () => Promise.resolve('const a = 1;\n')), + ).toEqual([]); + expect( + await createDocumentEdits(getText, () => Promise.resolve(undefined)), + ).toEqual([]); }); test('returns no edits for a document that is not open', async () => { diff --git a/packages/rstack/tests/fmt/plugins.test.ts b/packages/rstack/tests/fmt/plugins.test.ts index 842c6db4..5f25262e 100644 --- a/packages/rstack/tests/fmt/plugins.test.ts +++ b/packages/rstack/tests/fmt/plugins.test.ts @@ -1,6 +1,9 @@ import { pathToFileURL } from 'node:url'; import { expect, test } from 'rstack/test'; -import { createFingerprintResolver, createPluginResolver } from '../../src/fmt/plugins.ts'; +import { + createFingerprintResolver, + createPluginResolver, +} from '../../src/fmt/plugins.ts'; import { withTempProject, writeProjectFile } from './helpers.ts'; test('resolves plugin specifiers from the config root', async () => { @@ -65,7 +68,10 @@ test('rejects imported plugin objects', () => { test('fingerprints installed package plugins once', async () => { await withTempProject(async (rootPath) => { - const entry = writeProjectFile(rootPath, 'node_modules/prettier-plugin-fixture/dist/index.mjs'); + const entry = writeProjectFile( + rootPath, + 'node_modules/prettier-plugin-fixture/dist/index.mjs', + ); const packageJsonPath = 'node_modules/prettier-plugin-fixture/package.json'; writeProjectFile( rootPath, diff --git a/packages/rstack/tests/fmt/runner.test.ts b/packages/rstack/tests/fmt/runner.test.ts index dbcd59e6..101151af 100644 --- a/packages/rstack/tests/fmt/runner.test.ts +++ b/packages/rstack/tests/fmt/runner.test.ts @@ -1,4 +1,10 @@ -import { chmodSync, readFileSync, statSync, utimesSync, writeFileSync } from 'node:fs'; +import { + chmodSync, + readFileSync, + statSync, + utimesSync, + writeFileSync, +} from 'node:fs'; import path from 'node:path'; import { expect, test } from 'rstack/test'; import { runFmtFiles } from '../../src/fmt/runner.ts'; @@ -46,17 +52,20 @@ test('writes changed files', async () => { }); }); -test.runIf(process.platform !== 'win32')('preserves file mode when writing', async () => { - await withTempProject(async (rootPath) => { - const filePath = path.join(rootPath, 'executable.ts'); - writeFileSync(filePath, 'const value=1'); - chmodSync(filePath, 0o744); +test.runIf(process.platform !== 'win32')( + 'preserves file mode when writing', + async () => { + await withTempProject(async (rootPath) => { + const filePath = path.join(rootPath, 'executable.ts'); + writeFileSync(filePath, 'const value=1'); + chmodSync(filePath, 0o744); - await run([createFmtRequest(filePath)]); + await run([createFmtRequest(filePath)]); - expect(statSync(filePath).mode & 0o777).toBe(0o744); - }); -}); + expect(statSync(filePath).mode & 0o777).toBe(0o744); + }); + }, +); for (const mode of ['check', 'list-different'] as const) { test(`${mode} reports differences without writing`, async () => { @@ -84,7 +93,10 @@ test('continues after a file fails and gives errors exit-code precedence', async writeFileSync(invalidPath, 'const value = ;'); writeFileSync(validPath, 'const value=1'); - const result = await run([createFmtRequest(invalidPath), createFmtRequest(validPath)], 'check'); + const result = await run( + [createFmtRequest(invalidPath), createFmtRequest(validPath)], + 'check', + ); expect(result).toMatchObject({ exitCode: 2, @@ -110,7 +122,11 @@ test('omits unsupported files from the result', async () => { }, ]); - expect(result).toMatchObject({ exitCode: 2, files: [], processedFileCount: 0 }); + expect(result).toMatchObject({ + exitCode: 2, + files: [], + processedFileCount: 0, + }); expect(readFileSync(filePath, 'utf8')).toBe('plain text'); }); }); diff --git a/packages/rstack/tests/fmt/runnerCache.test.ts b/packages/rstack/tests/fmt/runnerCache.test.ts index e78a2ab0..8426d5e1 100644 --- a/packages/rstack/tests/fmt/runnerCache.test.ts +++ b/packages/rstack/tests/fmt/runnerCache.test.ts @@ -10,7 +10,11 @@ import { } from '../../src/fmt/cacheIdentity.ts'; import { loadFmtCacheStore } from '../../src/fmt/cacheStore.ts'; import { runFmtFiles } from '../../src/fmt/runner.ts'; -import type { FmtCacheContext, FmtFileRequest, FmtMode } from '../../src/fmt/types.ts'; +import type { + FmtCacheContext, + FmtFileRequest, + FmtMode, +} from '../../src/fmt/types.ts'; import { createFmtCacheContext, createFmtRequest, @@ -77,7 +81,9 @@ test('uses content hashes instead of file metadata', async () => { size: Buffer.byteLength(clean), }); - await expect(run([createFmtRequest(filePath)], 'check', cache)).resolves.toMatchObject({ + await expect( + run([createFmtRequest(filePath)], 'check', cache), + ).resolves.toMatchObject({ exitCode: 1, files: [{ path: filePath, status: 'different' }], }); @@ -99,10 +105,16 @@ test('invalidates entries when final options change', async () => { const cache = createFmtCacheContext(rootPath); writeFileSync(filePath, 'const value = "text";\n'); - const initial = createFmtRequest(filePath, { parser: 'typescript', singleQuote: false }); + const initial = createFmtRequest(filePath, { + parser: 'typescript', + singleQuote: false, + }); await run([initial], 'check', cache); - const changed = createFmtRequest(filePath, { parser: 'typescript', singleQuote: true }); + const changed = createFmtRequest(filePath, { + parser: 'typescript', + singleQuote: true, + }); await expect(run([changed], 'check', cache)).resolves.toMatchObject({ exitCode: 1, files: [{ path: filePath, status: 'different' }], @@ -119,7 +131,11 @@ test('invalidates entries when final options change', async () => { test('caches unsupported parser results until final options change', async () => { await withTempProject(async (rootPath) => { - const filePath = writeProjectFile(rootPath, 'data.unknown', '{"value":true}'); + const filePath = writeProjectFile( + rootPath, + 'data.unknown', + '{"value":true}', + ); const cache = createFmtCacheContext(rootPath); const unsupported = createFmtRequest(filePath, {}); @@ -129,11 +145,11 @@ test('caches unsupported parser results until final options change', async () => files: [], processedFileCount: 0, }); - expect((await loadFmtCacheStore(cache.filePath, cacheNamespace)).get('data.unknown')).toEqual([ - '', - createOptionsHasher()(unsupported.options), - 'unsupported', - ]); + expect( + (await loadFmtCacheStore(cache.filePath, cacheNamespace)).get( + 'data.unknown', + ), + ).toEqual(['', createOptionsHasher()(unsupported.options), 'unsupported']); await expect(run([unsupported], 'check', cache)).resolves.toEqual(first); @@ -143,7 +159,11 @@ test('caches unsupported parser results until final options change', async () => files: [{ path: filePath, status: 'different' }], processedFileCount: 1, }); - expect((await loadFmtCacheStore(cache.filePath, cacheNamespace)).get('data.unknown')).toEqual([ + expect( + (await loadFmtCacheStore(cache.filePath, cacheNamespace)).get( + 'data.unknown', + ), + ).toEqual([ createCacheHash(readFileSync(filePath)), createOptionsHasher()(supported.options), 'dirty', @@ -163,7 +183,9 @@ test('invalidates cached unsupported parser results when content changes without files: [], processedFileCount: 0, }); - expect((await loadFmtCacheStore(cache.filePath, cacheNamespace)).get('script')).toEqual([ + expect( + (await loadFmtCacheStore(cache.filePath, cacheNamespace)).get('script'), + ).toEqual([ createCacheHash(readFileSync(filePath)), createOptionsHasher()(file.options), 'unsupported', @@ -177,7 +199,9 @@ test('invalidates cached unsupported parser results when content changes without files: [{ path: filePath, status: 'different' }], processedFileCount: 1, }); - expect((await loadFmtCacheStore(cache.filePath, cacheNamespace)).get('script')).toEqual([ + expect( + (await loadFmtCacheStore(cache.filePath, cacheNamespace)).get('script'), + ).toEqual([ createCacheHash(readFileSync(filePath)), createOptionsHasher()(file.options), 'dirty', @@ -187,7 +211,11 @@ test('invalidates cached unsupported parser results when content changes without test('caches only plugins with stable fingerprints', async () => { await withTempProject(async (rootPath) => { - const filePath = writeProjectFile(rootPath, 'data.fixture', '{"value":true}'); + const filePath = writeProjectFile( + rootPath, + 'data.fixture', + '{"value":true}', + ); const pluginEntry = writeProjectFile( rootPath, 'node_modules/prettier-plugin-fixture/index.mjs', @@ -208,26 +236,30 @@ test('caches only plugins with stable fingerprints', async () => { }), ); const cache = createFmtCacheContext(rootPath); - const file = createFmtRequest(filePath, { plugins: [pathToFileURL(pluginEntry).href] }); + const file = createFmtRequest(filePath, { + plugins: [pathToFileURL(pluginEntry).href], + }); writePackageJson(); await run([file], 'check', cache); - expect((await loadFmtCacheStore(cache.filePath, cacheNamespace)).get('data.fixture')).toBe( - undefined, - ); + expect( + (await loadFmtCacheStore(cache.filePath, cacheNamespace)).get( + 'data.fixture', + ), + ).toBe(undefined); writePackageJson('1.0.0'); await run([file], 'check', cache); - const firstHash = (await loadFmtCacheStore(cache.filePath, cacheNamespace)).get( - 'data.fixture', - )?.[1]; + const firstHash = ( + await loadFmtCacheStore(cache.filePath, cacheNamespace) + ).get('data.fixture')?.[1]; expect(firstHash).toHaveLength(cacheHashLength); writePackageJson('2.0.0'); await run([file], 'check', cache); - const secondHash = (await loadFmtCacheStore(cache.filePath, cacheNamespace)).get( - 'data.fixture', - )?.[1]; + const secondHash = ( + await loadFmtCacheStore(cache.filePath, cacheNamespace) + ).get('data.fixture')?.[1]; expect(secondHash).toHaveLength(cacheHashLength); expect(secondHash).not.toBe(firstHash); }); @@ -241,7 +273,11 @@ test('preserves entries outside the formatted subset', async () => { writeFileSync(firstPath, 'const first = 1;\n'); writeFileSync(secondPath, 'const second = 2;\n'); - await run([createFmtRequest(firstPath), createFmtRequest(secondPath)], 'check', cache); + await run( + [createFmtRequest(firstPath), createFmtRequest(secondPath)], + 'check', + cache, + ); const firstStore = await loadFmtCacheStore(cache.filePath, cacheNamespace); const secondEntry = firstStore.get('second.ts'); @@ -262,7 +298,9 @@ test('does not cache formatting errors', async () => { writeFileSync(invalidPath, 'const invalid = ;'); await run([createFmtRequest(validPath)], 'check', cache); - await expect(run([createFmtRequest(invalidPath)], 'check', cache)).resolves.toMatchObject({ + await expect( + run([createFmtRequest(invalidPath)], 'check', cache), + ).resolves.toMatchObject({ exitCode: 2, files: [{ path: invalidPath, status: 'error' }], }); @@ -306,7 +344,9 @@ test('write persists clean results for misses and hits', async () => { files: [], processedFileCount: 2, }); - expect(files.map((file) => statSync(file.path).mtimeMs)).toEqual(timestamps); + expect(files.map((file) => statSync(file.path).mtimeMs)).toEqual( + timestamps, + ); }); }); diff --git a/packages/rstack/tests/fmt/runnerWorkerPreflight.test.ts b/packages/rstack/tests/fmt/runnerWorkerPreflight.test.ts index 7b9e3b87..cb3dec45 100644 --- a/packages/rstack/tests/fmt/runnerWorkerPreflight.test.ts +++ b/packages/rstack/tests/fmt/runnerWorkerPreflight.test.ts @@ -1,5 +1,8 @@ import { beforeEach, expect, rs, test } from 'rstack/test'; -import { cacheNamespace, createOptionsHasher } from '../../src/fmt/cacheIdentity.ts'; +import { + cacheNamespace, + createOptionsHasher, +} from '../../src/fmt/cacheIdentity.ts'; import { loadFmtCacheStore } from '../../src/fmt/cacheStore.ts'; import { runFmtFiles } from '../../src/fmt/runner.ts'; import { @@ -24,7 +27,10 @@ beforeEach(() => { mocks.workerPoolCalls.length = 0; }); -const createCachedUnsupportedFile = async (rootPath: string, fileName: string) => { +const createCachedUnsupportedFile = async ( + rootPath: string, + fileName: string, +) => { const filePath = writeProjectFile(rootPath, fileName, 'plain text'); const cache = createFmtCacheContext(rootPath); const file = createFmtRequest(filePath, {}); @@ -42,7 +48,10 @@ const createCachedUnsupportedFile = async (rootPath: string, fileName: string) = test('does not start the worker pool when every parser result is cached as unsupported', async () => { await withTempProject(async (rootPath) => { - const { cache, file } = await createCachedUnsupportedFile(rootPath, 'example.unknown'); + const { cache, file } = await createCachedUnsupportedFile( + rootPath, + 'example.unknown', + ); await expect( runFmtFiles({ @@ -61,7 +70,10 @@ test('does not start the worker pool when every parser result is cached as unsup test('starts the worker pool for a path-only unsupported entry without an extension', async () => { await withTempProject(async (rootPath) => { - const { cache, file } = await createCachedUnsupportedFile(rootPath, 'script'); + const { cache, file } = await createCachedUnsupportedFile( + rootPath, + 'script', + ); await expect( runFmtFiles({ diff --git a/packages/rstack/tests/fmt/worker.test.ts b/packages/rstack/tests/fmt/worker.test.ts index 3ef3d3aa..ddc35ba9 100644 --- a/packages/rstack/tests/fmt/worker.test.ts +++ b/packages/rstack/tests/fmt/worker.test.ts @@ -18,8 +18,18 @@ test('returns cached states before resolving the parser', async () => { [[contentHash, optionsHash, 'clean'], filePath, false, 'unchanged'], [[contentHash, optionsHash, 'dirty'], filePath, false, 'changed'], [[contentHash, optionsHash, 'clean'], filePath, true, 'unchanged'], - [[contentHash, optionsHash, 'unsupported'], noExtensionPath, false, 'unsupported'], - [[contentHash, optionsHash, 'unsupported'], noExtensionPath, true, 'unsupported'], + [ + [contentHash, optionsHash, 'unsupported'], + noExtensionPath, + false, + 'unsupported', + ], + [ + [contentHash, optionsHash, 'unsupported'], + noExtensionPath, + true, + 'unsupported', + ], [['', optionsHash, 'unsupported'], missingPath, false, 'unsupported'], [['', optionsHash, 'unsupported'], missingPath, true, 'unsupported'], ] as const) { @@ -44,7 +54,11 @@ test('returns cached states before resolving the parser', async () => { test('does not trust path-only unsupported entries for files without extensions', async () => { await withTempProject(async (rootPath) => { - const filePath = writeProjectFile(rootPath, 'script', '#!/usr/bin/env node\nconst value=1'); + const filePath = writeProjectFile( + rootPath, + 'script', + '#!/usr/bin/env node\nconst value=1', + ); await expect( formatFile({ diff --git a/packages/rstack/tests/fmt/yukuPlugin.test.ts b/packages/rstack/tests/fmt/yukuPlugin.test.ts index b4841ce3..b8e79457 100644 --- a/packages/rstack/tests/fmt/yukuPlugin.test.ts +++ b/packages/rstack/tests/fmt/yukuPlugin.test.ts @@ -1,4 +1,9 @@ -import { format, getFileInfo, type Options, type ParserOptions } from 'prettier'; +import { + format, + getFileInfo, + type Options, + type ParserOptions, +} from 'prettier'; import { expect, test } from 'rstack/test'; import { yukuPlugin } from '../../src/fmt/yukuPlugin.ts'; @@ -9,11 +14,14 @@ const formatWithYuku = ( format(source, { plugins: [yukuPlugin], ...options, - filepath: options.filepath ?? `example.${options.parser === 'yuku' ? 'js' : 'ts'}`, + filepath: + options.filepath ?? `example.${options.parser === 'yuku' ? 'js' : 'ts'}`, }); test('exposes the same JavaScript and TypeScript language mappings as the official plugin', async () => { - expect(yukuPlugin.languages?.map(({ name, parsers }) => ({ name, parsers }))).toEqual([ + expect( + yukuPlugin.languages?.map(({ name, parsers }) => ({ name, parsers })), + ).toEqual([ { name: 'JavaScript', parsers: ['yuku', 'yuku-ts'] }, { name: 'JSX', parsers: ['yuku', 'yuku-ts'] }, { name: 'TypeScript', parsers: ['yuku-ts'] }, @@ -63,7 +71,9 @@ test.each(['example.d.ts', 'example.d.mts', 'example.d.cts'])( filepath, parser: 'yuku-ts', }), - ).rejects.toThrow('An implementation cannot be declared in ambient contexts'); + ).rejects.toThrow( + 'An implementation cannot be declared in ambient contexts', + ); }, ); @@ -115,7 +125,8 @@ test.each([ parser: 'yuku-ts' as const, filepath: 'example.tsx', source: 'const view=({(item)})', - expected: 'const view = {item};\n', + expected: + 'const view = {item};\n', }, ])('normalizes $name for the ESTree printer', async (fixture) => { await expect( @@ -179,15 +190,18 @@ test.each([ hasPragma: false, hasIgnorePragma: false, }, -])('matches Prettier pragma detection for $source', ({ source, hasPragma, hasIgnorePragma }) => { - const parser = yukuPlugin.parsers?.yuku; - if (!parser?.hasPragma || !parser.hasIgnorePragma) { - throw new Error('The Yuku parser does not expose pragma handlers.'); - } +])( + 'matches Prettier pragma detection for $source', + ({ source, hasPragma, hasIgnorePragma }) => { + const parser = yukuPlugin.parsers?.yuku; + if (!parser?.hasPragma || !parser.hasIgnorePragma) { + throw new Error('The Yuku parser does not expose pragma handlers.'); + } - expect(parser.hasPragma(source)).toBe(hasPragma); - expect(parser.hasIgnorePragma(source)).toBe(hasIgnorePragma); -}); + expect(parser.hasPragma(source)).toBe(hasPragma); + expect(parser.hasIgnorePragma(source)).toBe(hasIgnorePragma); + }, +); test('matches Prettier JavaScript location overrides', () => { const parser = yukuPlugin.parsers?.yuku; @@ -278,10 +292,10 @@ test('matches the official hashbang AST shape', async () => { } const options = { filepath: 'example.js' } as ParserOptions; - const astWithoutHashbang = (await parser.parse('const value = 1', options)) as Record< - string, - unknown - >; + const astWithoutHashbang = (await parser.parse( + 'const value = 1', + options, + )) as Record; const astWithHashbang = (await parser.parse( '#!/usr/bin/env node\nconst value = 1', options, diff --git a/packages/rstack/tests/helpers/cli.ts b/packages/rstack/tests/helpers/cli.ts index d1d287d0..790e34f4 100644 --- a/packages/rstack/tests/helpers/cli.ts +++ b/packages/rstack/tests/helpers/cli.ts @@ -2,7 +2,10 @@ import { type ExecSyncOptions, execSync } from 'node:child_process'; import path from 'node:path'; import type { LogHelper } from '@rstackjs/test-utils'; -export const RSTACK_BIN_PATH: string = path.join(import.meta.dirname, '../../bin/rs.js'); +export const RSTACK_BIN_PATH: string = path.join( + import.meta.dirname, + '../../bin/rs.js', +); export type ExecCliOptions = ExecSyncOptions & { logHelper?: LogHelper; @@ -18,7 +21,10 @@ type ExecCliError = Error & { stderr?: Buffer | string; }; -const addLog = (logHelper: LogHelper | undefined, output: Buffer | string | undefined) => { +const addLog = ( + logHelper: LogHelper | undefined, + output: Buffer | string | undefined, +) => { if (output) { logHelper?.addLog(output.toString()); } @@ -28,14 +34,17 @@ export const execCli: ExecCli = (command, options = {}) => { const { logHelper, ...execOptions } = options; try { - const output = execSync(`"${process.execPath}" "${RSTACK_BIN_PATH}" ${command}`, { - stdio: 'pipe', - ...execOptions, - env: { - ...process.env, - ...execOptions.env, + const output = execSync( + `"${process.execPath}" "${RSTACK_BIN_PATH}" ${command}`, + { + stdio: 'pipe', + ...execOptions, + env: { + ...process.env, + ...execOptions.env, + }, }, - }); + ); addLog(logHelper, output); return output.toString(); diff --git a/packages/rstack/tests/helpers/cliTest.ts b/packages/rstack/tests/helpers/cliTest.ts index f3ff322d..895d54c8 100644 --- a/packages/rstack/tests/helpers/cliTest.ts +++ b/packages/rstack/tests/helpers/cliTest.ts @@ -1,8 +1,16 @@ -import { type ChildProcess, type SpawnOptions, spawn as nodeSpawn } from 'node:child_process'; +import { + type ChildProcess, + type SpawnOptions, + spawn as nodeSpawn, +} from 'node:child_process'; import path from 'node:path'; import { prepareDist as basePrepareDist } from '@rstackjs/test-utils'; import { test as baseTest } from 'rstack/test'; -import { execCli as baseExecCli, type ExecCli, RSTACK_BIN_PATH } from './cli.ts'; +import { + execCli as baseExecCli, + type ExecCli, + RSTACK_BIN_PATH, +} from './cli.ts'; import { type ExtendedLogHelper, proxyConsole } from './logs.ts'; type Exec = ( @@ -33,7 +41,10 @@ function makeBox(title: string) { }; } -const setupExecOptions = (options: T, cwd: string): T => { +const setupExecOptions = ( + options: T, + cwd: string, +): T => { // inherit process.env from current process const { NODE_ENV: _, ...restEnv } = process.env; options.env ||= {}; @@ -47,7 +58,9 @@ export const test: CliTest = baseTest.extend({ const { testPath } = expect.getState(); if (!testPath) { - throw new Error('Unable to resolve current test file path from expect state.'); + throw new Error( + 'Unable to resolve current test file path from expect state.', + ); } await use(path.dirname(testPath)); @@ -86,7 +99,10 @@ export const test: CliTest = baseTest.extend({ const closes: Array<() => void> = []; const exec: Exec = (command, options = {}) => { - const childProcess = nodeSpawn(command, setupExecOptions({ shell: true, ...options }, cwd)); + const childProcess = nodeSpawn( + command, + setupExecOptions({ shell: true, ...options }, cwd), + ); const onData = (data: Buffer) => { logHelper.addLog(data.toString()); diff --git a/packages/rstack/tests/helpers/logs.ts b/packages/rstack/tests/helpers/logs.ts index 2a0d19fa..12646a26 100644 --- a/packages/rstack/tests/helpers/logs.ts +++ b/packages/rstack/tests/helpers/logs.ts @@ -14,7 +14,9 @@ export type LogHelper = BaseLogHelper & ExpectBuildEnd; export type ExtendedLogHelper = BaseExtendedLogHelper & ExpectBuildEnd; -export const proxyConsole = (options?: ProxyConsoleOptions): ExtendedLogHelper => { +export const proxyConsole = ( + options?: ProxyConsoleOptions, +): ExtendedLogHelper => { const logHelper = baseProxyConsole(options); return { diff --git a/packages/rstack/tests/setup/directories.test.ts b/packages/rstack/tests/setup/directories.test.ts index dfaf62da..d27c662a 100644 --- a/packages/rstack/tests/setup/directories.test.ts +++ b/packages/rstack/tests/setup/directories.test.ts @@ -2,7 +2,13 @@ import { existsSync, mkdirSync, readFileSync } from 'node:fs'; import path from 'node:path'; import { expect, test } from 'rstack/test'; import { installHooks } from '../../src/setup/install.ts'; -import { hooksPath, runGit, runHook, withRepository, writeHook } from './helpers.ts'; +import { + hooksPath, + runGit, + runHook, + withRepository, + writeHook, +} from './helpers.ts'; test('installs a custom hooks directory from the Git root and runs its hook', () => { withRepository((cwd) => { @@ -14,11 +20,15 @@ test('installs a custom hooks directory from the Git root and runs its hook', () status: 'installed', hooksPath: customHooksPath, }); - expect(runGit(cwd, ['config', '--local', '--get', 'core.hooksPath'])).toBe(customHooksPath); + expect(runGit(cwd, ['config', '--local', '--get', 'core.hooksPath'])).toBe( + customHooksPath, + ); expect(existsSync(path.join(cwd, customHooksPath, 'runner'))).toBe(true); expect(runHook(cwd).status).toBe(0); - expect(readFileSync(path.join(cwd, 'custom-hook-ran'), 'utf8')).toBe('ran\n'); + expect(readFileSync(path.join(cwd, 'custom-hook-ran'), 'utf8')).toBe( + 'ran\n', + ); }); }); @@ -36,12 +46,18 @@ test('installs repository-level hooks from a nested project', () => { status: 'unchanged', hooksPath, }); - expect(runGit(cwd, ['config', '--local', '--get', 'core.hooksPath'])).toBe(hooksPath); + expect(runGit(cwd, ['config', '--local', '--get', 'core.hooksPath'])).toBe( + hooksPath, + ); expect(existsSync(path.join(cwd, hooksPath, 'runner'))).toBe(true); - expect(readFileSync(path.join(cwd, hooksPath, '.owner'), 'utf8')).toBe('frontend\n'); + expect(readFileSync(path.join(cwd, hooksPath, '.owner'), 'utf8')).toBe( + 'frontend\n', + ); expect(runHook(cwd).status).toBe(0); - expect(readFileSync(path.join(projectDirectory, 'nested-hook-ran'), 'utf8')).toBe('ran\n'); + expect( + readFileSync(path.join(projectDirectory, 'nested-hook-ran'), 'utf8'), + ).toBe('ran\n'); }); }); @@ -50,15 +66,23 @@ test('installs a root-relative custom hooks directory from a nested project', () const projectDirectory = path.join(cwd, 'frontend app'); mkdirSync(projectDirectory); - expect(installHooks({ cwd: projectDirectory, hooksDir: 'config\\hooks' })).toEqual({ + expect( + installHooks({ cwd: projectDirectory, hooksDir: 'config\\hooks' }), + ).toEqual({ status: 'installed', hooksPath: 'config/hooks/_', }); - expect(installHooks({ cwd: projectDirectory, hooksDir: 'config\\hooks' })).toEqual({ + expect( + installHooks({ cwd: projectDirectory, hooksDir: 'config\\hooks' }), + ).toEqual({ status: 'unchanged', hooksPath: 'config/hooks/_', }); - expect(runGit(cwd, ['config', '--local', '--get', 'core.hooksPath'])).toBe('config/hooks/_'); - expect(existsSync(path.join(cwd, 'config', 'hooks', '_', 'runner'))).toBe(true); + expect(runGit(cwd, ['config', '--local', '--get', 'core.hooksPath'])).toBe( + 'config/hooks/_', + ); + expect(existsSync(path.join(cwd, 'config', 'hooks', '_', 'runner'))).toBe( + true, + ); }); }); diff --git a/packages/rstack/tests/setup/helpers.ts b/packages/rstack/tests/setup/helpers.ts index 206e54c6..794691f2 100644 --- a/packages/rstack/tests/setup/helpers.ts +++ b/packages/rstack/tests/setup/helpers.ts @@ -10,7 +10,8 @@ export const git = ( cwd: string, args: string[], env: NodeJS.ProcessEnv = process.env, -): SpawnSyncReturns => spawnSync('git', args, { cwd, encoding: 'utf8', env }); +): SpawnSyncReturns => + spawnSync('git', args, { cwd, encoding: 'utf8', env }); export const runGit = (cwd: string, args: string[]): string => { const result = git(cwd, args); @@ -21,7 +22,9 @@ export const runGit = (cwd: string, args: string[]): string => { }; export const withDirectory = (callback: (cwd: string) => void): void => { - const cwd = mkdtempSync(path.join(import.meta.dirname, 'test-temp-rstack hooks ')); + const cwd = mkdtempSync( + path.join(import.meta.dirname, 'test-temp-rstack hooks '), + ); const gitCeilingDirectories = process.env.GIT_CEILING_DIRECTORIES; // Keep Git from treating the temporary directory as part of this repository. process.env.GIT_CEILING_DIRECTORIES = import.meta.dirname; @@ -55,7 +58,11 @@ const hookEnv = (cwd: string, value?: string): NodeJS.ProcessEnv => { return env; }; -export const writeHook = (cwd: string, content: string, directory: string = hooksDir): void => { +export const writeHook = ( + cwd: string, + content: string, + directory: string = hooksDir, +): void => { const filePath = path.join(cwd, directory, 'pre-commit'); mkdirSync(path.dirname(filePath), { recursive: true }); writeFileSync(filePath, content); @@ -67,10 +74,17 @@ export const writeInit = (cwd: string, content: string): void => { writeFileSync(filePath, content); }; -export const runHook = (cwd: string, value?: string): SpawnSyncReturns => +export const runHook = ( + cwd: string, + value?: string, +): SpawnSyncReturns => git(cwd, ['hook', 'run', 'pre-commit'], hookEnv(cwd, value)); -export const runGitHook = (cwd: string, name: string, args: string[]): SpawnSyncReturns => +export const runGitHook = ( + cwd: string, + name: string, + args: string[], +): SpawnSyncReturns => git(cwd, ['hook', 'run', name, '--', ...args], hookEnv(cwd)); export const withRepository = (callback: (cwd: string) => void): void => diff --git a/packages/rstack/tests/setup/hooks.test.ts b/packages/rstack/tests/setup/hooks.test.ts index 3baa5348..a2bd5762 100644 --- a/packages/rstack/tests/setup/hooks.test.ts +++ b/packages/rstack/tests/setup/hooks.test.ts @@ -6,7 +6,9 @@ import { createHookFiles } from '../../src/setup/hooks.ts'; import { withDirectory } from './helpers.ts'; test('generates the runner and all client-side Git hook shims', () => { - expect(Object.keys(createHookFiles()).filter((name) => name !== 'runner')).toEqual([ + expect( + Object.keys(createHookFiles()).filter((name) => name !== 'runner'), + ).toEqual([ 'pre-commit', 'pre-merge-commit', 'prepare-commit-msg', @@ -25,17 +27,24 @@ test('generates the runner and all client-side Git hook shims', () => { }); test.runIf(process.platform === 'win32')('converts Windows Node paths', () => { - const { runner } = createHookFiles(String.raw`C:\Program Files\nodejs\node.exe`); + const { runner } = createHookFiles( + String.raw`C:\Program Files\nodejs\node.exe`, + ); - expect(runner).toContain("rs_node_fallback='/c/Program Files/nodejs/node.exe'"); + expect(runner).toContain( + "rs_node_fallback='/c/Program Files/nodejs/node.exe'", + ); }); -test.runIf(process.platform !== 'win32')('preserves backslashes in POSIX Node paths', () => { - const nodeExecutable = String.raw`/opt/node\24/bin/node`; - const { runner } = createHookFiles(nodeExecutable); +test.runIf(process.platform !== 'win32')( + 'preserves backslashes in POSIX Node paths', + () => { + const nodeExecutable = String.raw`/opt/node\24/bin/node`; + const { runner } = createHookFiles(nodeExecutable); - expect(runner).toContain(`rs_node_fallback='${nodeExecutable}'`); -}); + expect(runner).toContain(`rs_node_fallback='${nodeExecutable}'`); + }, +); test.runIf(process.platform !== 'win32')('runs generated hooks', () => { withDirectory((directory) => { @@ -58,7 +67,9 @@ test.runIf(process.platform !== 'win32')('runs generated hooks', () => { writeFileSync(path.join(generatedDirectory, 'runner'), files.runner); writeFileSync(generatedHook, files['pre-commit']); - expect(spawnSync('sh', [generatedHook], { cwd: directory, env }).status).toBe(0); + expect( + spawnSync('sh', [generatedHook], { cwd: directory, env }).status, + ).toBe(0); writeFileSync( userHook, @@ -89,7 +100,9 @@ printf 'unreachable\\n' }); expect(errexitResult.status).toBe(1); - expect(errexitResult.stdout).toBe('Rstack - pre-commit hook failed (code 1)\n'); + expect(errexitResult.stdout).toBe( + 'Rstack - pre-commit hook failed (code 1)\n', + ); mkdirSync(runtimeDirectory, { recursive: true }); writeFileSync(init, `export PATH="${runtimeDirectory}"\n`); diff --git a/packages/rstack/tests/setup/install.test.ts b/packages/rstack/tests/setup/install.test.ts index a64ea7c7..f91b2b45 100644 --- a/packages/rstack/tests/setup/install.test.ts +++ b/packages/rstack/tests/setup/install.test.ts @@ -1,19 +1,38 @@ -import { chmodSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs'; +import { + chmodSync, + existsSync, + mkdirSync, + readFileSync, + statSync, + writeFileSync, +} from 'node:fs'; import path from 'node:path'; import { expect, test } from 'rstack/test'; import { createHookFiles } from '../../src/setup/hooks.ts'; import { installHooks } from '../../src/setup/install.ts'; -import { git, hooksPath, restoreEnv, runGit, withRepository } from './helpers.ts'; +import { + git, + hooksPath, + restoreEnv, + runGit, + withRepository, +} from './helpers.ts'; test('installs generated hooks and configures the repository', () => { withRepository((cwd) => { expect(installHooks({ cwd })).toEqual({ status: 'installed', hooksPath }); - expect(runGit(cwd, ['config', '--local', '--get', 'core.hooksPath'])).toBe(hooksPath); + expect(runGit(cwd, ['config', '--local', '--get', 'core.hooksPath'])).toBe( + hooksPath, + ); const directory = path.join(cwd, hooksPath); - expect(readFileSync(path.join(directory, '.gitignore'), 'utf8')).toBe('*\n'); + expect(readFileSync(path.join(directory, '.gitignore'), 'utf8')).toBe( + '*\n', + ); expect(readFileSync(path.join(directory, '.owner'), 'utf8')).toBe('.\n'); - expect(runGit(cwd, ['status', '--short', '--untracked-files=all'])).toBe(''); + expect(runGit(cwd, ['status', '--short', '--untracked-files=all'])).toBe( + '', + ); for (const [name, content] of Object.entries(createHookFiles())) { const filePath = path.join(directory, name); @@ -40,16 +59,19 @@ test('is idempotent and preserves user hooks', () => { }); }); -test.runIf(process.platform !== 'win32')('restores executable mode on existing shims', () => { - withRepository((cwd) => { - expect(installHooks({ cwd }).status).toBe('installed'); - const shim = path.join(cwd, hooksPath, 'pre-commit'); - chmodSync(shim, 0o644); +test.runIf(process.platform !== 'win32')( + 'restores executable mode on existing shims', + () => { + withRepository((cwd) => { + expect(installHooks({ cwd }).status).toBe('installed'); + const shim = path.join(cwd, hooksPath, 'pre-commit'); + chmodSync(shim, 0o644); - expect(installHooks({ cwd }).status).toBe('installed'); - expect(statSync(shim).mode & 0o777).toBe(0o755); - }); -}); + expect(installHooks({ cwd }).status).toBe('installed'); + expect(statSync(shim).mode & 0o777).toBe(0o755); + }); + }, +); test('repairs generated files without rewriting an unchanged hooksPath', () => { withRepository((cwd) => { @@ -94,7 +116,9 @@ test('does not configure Git when writing generated files fails', () => { status: 'failed', reason: 'write-failed', }); - expect(git(cwd, ['config', '--local', '--get', 'core.hooksPath']).status).toBe(1); + expect( + git(cwd, ['config', '--local', '--get', 'core.hooksPath']).status, + ).toBe(1); }); }); @@ -106,7 +130,9 @@ test('reports Git configuration failures without changing hooksPath', () => { status: 'failed', reason: 'git-config-failed', }); - expect(git(cwd, ['config', '--local', '--get', 'core.hooksPath']).status).toBe(1); + expect( + git(cwd, ['config', '--local', '--get', 'core.hooksPath']).status, + ).toBe(1); expect(existsSync(path.join(cwd, hooksPath, 'runner'))).toBe(true); }); }); @@ -119,7 +145,9 @@ test('does not replace another Git hooks path', () => { status: 'skipped', reason: 'hooks-path-conflict', }); - expect(runGit(cwd, ['config', '--local', '--get', 'core.hooksPath'])).toBe('.husky/_'); + expect(runGit(cwd, ['config', '--local', '--get', 'core.hooksPath'])).toBe( + '.husky/_', + ); expect(existsSync(path.join(cwd, hooksPath))).toBe(false); }); }); @@ -134,7 +162,9 @@ test('does not bypass existing Git hooks', () => { reason: 'existing-git-hooks', message: 'existing Git hooks were found: pre-commit', }); - expect(git(cwd, ['config', '--local', '--get', 'core.hooksPath']).status).toBe(1); + expect( + git(cwd, ['config', '--local', '--get', 'core.hooksPath']).status, + ).toBe(1); expect(readFileSync(existingHook, 'utf8')).toBe('#!/usr/bin/env sh\n'); }); }); diff --git a/packages/rstack/tests/setup/runtime-errors.test.ts b/packages/rstack/tests/setup/runtime-errors.test.ts index ac85a13c..adcf553a 100644 --- a/packages/rstack/tests/setup/runtime-errors.test.ts +++ b/packages/rstack/tests/setup/runtime-errors.test.ts @@ -28,6 +28,8 @@ missing-command expect(missing.status).toBe(127); expect(output).toContain('Rstack - pre-commit hook failed (code 127)'); - expect(output).toContain(`Rstack - command not found in PATH=${actualPath}`); + expect(output).toContain( + `Rstack - command not found in PATH=${actualPath}`, + ); }); }); diff --git a/packages/rstack/tests/setup/runtime.test.ts b/packages/rstack/tests/setup/runtime.test.ts index 455f1a88..6fcff7c4 100644 --- a/packages/rstack/tests/setup/runtime.test.ts +++ b/packages/rstack/tests/setup/runtime.test.ts @@ -1,8 +1,20 @@ -import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; +import { + chmodSync, + existsSync, + mkdirSync, + readFileSync, + writeFileSync, +} from 'node:fs'; import path from 'node:path'; import { expect, test } from 'rstack/test'; import { installHooks } from '../../src/setup/install.ts'; -import { runGitHook, runHook, withRepository, writeHook, writeInit } from './helpers.ts'; +import { + runGitHook, + runHook, + withRepository, + writeHook, + writeInit, +} from './helpers.ts'; test('loads user init and project binaries', () => { withRepository((cwd) => { @@ -30,8 +42,12 @@ rstack-hook-command expect(installHooks({ cwd: projectDirectory }).status).toBe('installed'); expect(runHook(cwd).status).toBe(0); - expect(readFileSync(path.join(projectDirectory, 'init-ran'), 'utf8')).toBe('loaded\n'); - expect(readFileSync(path.join(projectDirectory, 'project-bin-ran'), 'utf8')).toBe('ran\n'); + expect(readFileSync(path.join(projectDirectory, 'init-ran'), 'utf8')).toBe( + 'loaded\n', + ); + expect( + readFileSync(path.join(projectDirectory, 'project-bin-ran'), 'utf8'), + ).toBe('ran\n'); }); }); diff --git a/packages/rstack/tests/types/resolution-bundler/index.ts b/packages/rstack/tests/types/resolution-bundler/index.ts index 8853c64e..0dc37311 100644 --- a/packages/rstack/tests/types/resolution-bundler/index.ts +++ b/packages/rstack/tests/types/resolution-bundler/index.ts @@ -17,7 +17,9 @@ import { expect as importedExpect, test as importedTest } from 'rstack/test'; const appConfig = defineAppConfig({}); const libConfig = defineLibConfig({}); const lintConfig = defineLintConfig([]); -const loadOptions: LoadRstackConfigOptions = { configFilePath: 'rstack.config.ts' }; +const loadOptions: LoadRstackConfigOptions = { + configFilePath: 'rstack.config.ts', +}; const loadedConfig: Promise = loadRstackConfig(loadOptions); const configs: Configs = {}; @@ -28,7 +30,10 @@ void createRsbuild({ config: appConfig }); define.app(appConfig); define.lib(libConfig); define.lint(lintConfig); -define.lint(({ js, ts }) => [js.configs.recommended, ts.configs.recommendedTypeChecked]); +define.lint(({ js, ts }) => [ + js.configs.recommended, + ts.configs.recommendedTypeChecked, +]); define.doc({}); define.test({}); define.staged({}); diff --git a/packages/rstack/tests/types/resolution-nodenext/index.ts b/packages/rstack/tests/types/resolution-nodenext/index.ts index 8853c64e..0dc37311 100644 --- a/packages/rstack/tests/types/resolution-nodenext/index.ts +++ b/packages/rstack/tests/types/resolution-nodenext/index.ts @@ -17,7 +17,9 @@ import { expect as importedExpect, test as importedTest } from 'rstack/test'; const appConfig = defineAppConfig({}); const libConfig = defineLibConfig({}); const lintConfig = defineLintConfig([]); -const loadOptions: LoadRstackConfigOptions = { configFilePath: 'rstack.config.ts' }; +const loadOptions: LoadRstackConfigOptions = { + configFilePath: 'rstack.config.ts', +}; const loadedConfig: Promise = loadRstackConfig(loadOptions); const configs: Configs = {}; @@ -28,7 +30,10 @@ void createRsbuild({ config: appConfig }); define.app(appConfig); define.lib(libConfig); define.lint(lintConfig); -define.lint(({ js, ts }) => [js.configs.recommended, ts.configs.recommendedTypeChecked]); +define.lint(({ js, ts }) => [ + js.configs.recommended, + ts.configs.recommendedTypeChecked, +]); define.doc({}); define.test({}); define.staged({}); diff --git a/rstack.config.ts b/rstack.config.ts index 68b2ef8f..066eace0 100644 --- a/rstack.config.ts +++ b/rstack.config.ts @@ -52,16 +52,10 @@ define.lint(async ({ js, ts }) => { }); define.fmt({ - ignorePatterns: ['packages/rstack/binding.cjs', 'packages/rstack/binding.d.cts'], - overrides: [ - { - files: 'packages/create-rstack/template-*/**/*', - options: { - printWidth: 80, - }, - }, + ignorePatterns: [ + 'packages/rstack/binding.cjs', + 'packages/rstack/binding.d.cts', ], - printWidth: 100, singleQuote: true, sortPackageJson: true, }); diff --git a/scripts/benchmark-fmt-discovery.js b/scripts/benchmark-fmt-discovery.js index ecf4f457..2891d082 100644 --- a/scripts/benchmark-fmt-discovery.js +++ b/scripts/benchmark-fmt-discovery.js @@ -30,7 +30,9 @@ const readValue = (args, index, flag) => { const parseInteger = (value, flag, minimum) => { const result = Number(value); if (!Number.isSafeInteger(result) || result < minimum) { - throw new Error(`${flag} must be an integer greater than or equal to ${minimum}.`); + throw new Error( + `${flag} must be an integer greater than or equal to ${minimum}.`, + ); } return result; }; @@ -58,7 +60,11 @@ const parseArgs = (args) => { index++; break; case '--explicit-count': - options.explicitCount = parseInteger(readValue(args, index, arg), arg, 1); + options.explicitCount = parseInteger( + readValue(args, index, arg), + arg, + 1, + ); index++; break; case '--runs': diff --git a/scripts/prepare-release.js b/scripts/prepare-release.js index cab8f15b..a18ff738 100644 --- a/scripts/prepare-release.js +++ b/scripts/prepare-release.js @@ -1,6 +1,13 @@ #!/usr/bin/env node import { spawn } from 'node:child_process'; -import { copyFile, mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises'; +import { + copyFile, + mkdir, + readFile, + readdir, + rm, + writeFile, +} from 'node:fs/promises'; import path from 'node:path'; const rootDir = path.resolve(import.meta.dirname, '..'); diff --git a/website/docs/en/guide/ai.mdx b/website/docs/en/guide/ai.mdx index 80ef7217..83a9cf79 100644 --- a/website/docs/en/guide/ai.mdx +++ b/website/docs/en/guide/ai.mdx @@ -50,7 +50,10 @@ The [migrate-to-rstack-cli](https://github.com/rstackjs/rstack-cli/tree/main/.ag To migrate an existing project, install the Skill: - + For supported tools and migration instructions, see [Migrate to Rstack CLI](./migration). diff --git a/website/docs/en/guide/cli/_meta.json b/website/docs/en/guide/cli/_meta.json index acdaffbe..5357606a 100644 --- a/website/docs/en/guide/cli/_meta.json +++ b/website/docs/en/guide/cli/_meta.json @@ -1 +1,13 @@ -["dev", "build", "preview", "lib", "doc", "test", "check", "lint", "fmt", "setup", "staged"] +[ + "dev", + "build", + "preview", + "lib", + "doc", + "test", + "check", + "lint", + "fmt", + "setup", + "staged" +] diff --git a/website/docs/en/guide/cli/lint.mdx b/website/docs/en/guide/cli/lint.mdx index e007abb3..aea5fa8a 100644 --- a/website/docs/en/guide/cli/lint.mdx +++ b/website/docs/en/guide/cli/lint.mdx @@ -34,5 +34,8 @@ Configure linting through [`define.lint()`](../configuration#define-lint) in the ```ts title="rstack.config.ts" import { define } from 'rstack'; -define.lint(({ js, ts }) => [js.configs.recommended, ts.configs.recommendedTypeChecked]); +define.lint(({ js, ts }) => [ + js.configs.recommended, + ts.configs.recommendedTypeChecked, +]); ``` diff --git a/website/docs/en/guide/configuration.mdx b/website/docs/en/guide/configuration.mdx index ff057d24..d1aaa0aa 100644 --- a/website/docs/en/guide/configuration.mdx +++ b/website/docs/en/guide/configuration.mdx @@ -154,7 +154,10 @@ Defines the [Rslint configuration](https://rslint.rs/config/). Pass the configur ```ts title="rstack.config.ts" import { define } from 'rstack'; -define.lint(({ js, ts }) => [js.configs.recommended, ts.configs.recommendedTypeChecked]); +define.lint(({ js, ts }) => [ + js.configs.recommended, + ts.configs.recommendedTypeChecked, +]); ``` ### `define.fmt()` \{#define-fmt} diff --git a/website/docs/en/guide/monorepo.mdx b/website/docs/en/guide/monorepo.mdx index 88c6cfce..41282191 100644 --- a/website/docs/en/guide/monorepo.mdx +++ b/website/docs/en/guide/monorepo.mdx @@ -46,7 +46,10 @@ Use [`define.lint()`](./configuration#define-lint), [`define.fmt()`](./configura ```ts title="rstack.config.ts" import { define } from 'rstack'; -define.lint(({ js, ts }) => [js.configs.recommended, ts.configs.recommendedTypeChecked]); +define.lint(({ js, ts }) => [ + js.configs.recommended, + ts.configs.recommendedTypeChecked, +]); define.fmt({ singleQuote: true, diff --git a/website/docs/zh/guide/ai.mdx b/website/docs/zh/guide/ai.mdx index a9ab69f1..581e94d5 100644 --- a/website/docs/zh/guide/ai.mdx +++ b/website/docs/zh/guide/ai.mdx @@ -50,7 +50,10 @@ Rstack CLI 提供面向特定领域的 Agent Skills,帮助 Coding Agent 更准 迁移现有项目时,安装该 Skill: - + 支持的工具和迁移说明请参阅[迁移到 Rstack CLI](./migration)。 diff --git a/website/docs/zh/guide/cli/_meta.json b/website/docs/zh/guide/cli/_meta.json index acdaffbe..5357606a 100644 --- a/website/docs/zh/guide/cli/_meta.json +++ b/website/docs/zh/guide/cli/_meta.json @@ -1 +1,13 @@ -["dev", "build", "preview", "lib", "doc", "test", "check", "lint", "fmt", "setup", "staged"] +[ + "dev", + "build", + "preview", + "lib", + "doc", + "test", + "check", + "lint", + "fmt", + "setup", + "staged" +] diff --git a/website/docs/zh/guide/cli/lint.mdx b/website/docs/zh/guide/cli/lint.mdx index 48011c5e..0634bd38 100644 --- a/website/docs/zh/guide/cli/lint.mdx +++ b/website/docs/zh/guide/cli/lint.mdx @@ -34,5 +34,8 @@ rs lint --type-check ```ts title="rstack.config.ts" import { define } from 'rstack'; -define.lint(({ js, ts }) => [js.configs.recommended, ts.configs.recommendedTypeChecked]); +define.lint(({ js, ts }) => [ + js.configs.recommended, + ts.configs.recommendedTypeChecked, +]); ``` diff --git a/website/docs/zh/guide/configuration.mdx b/website/docs/zh/guide/configuration.mdx index 5f15b77b..06939f84 100644 --- a/website/docs/zh/guide/configuration.mdx +++ b/website/docs/zh/guide/configuration.mdx @@ -154,7 +154,10 @@ define.test({ ```ts title="rstack.config.ts" import { define } from 'rstack'; -define.lint(({ js, ts }) => [js.configs.recommended, ts.configs.recommendedTypeChecked]); +define.lint(({ js, ts }) => [ + js.configs.recommended, + ts.configs.recommendedTypeChecked, +]); ``` ### `define.fmt()` \{#define-fmt} diff --git a/website/docs/zh/guide/monorepo.mdx b/website/docs/zh/guide/monorepo.mdx index ab8671db..aa898b82 100644 --- a/website/docs/zh/guide/monorepo.mdx +++ b/website/docs/zh/guide/monorepo.mdx @@ -46,7 +46,10 @@ Rsbuild 插件、测试库等项目专属依赖,建议定义在实际使用它 ```ts title="rstack.config.ts" import { define } from 'rstack'; -define.lint(({ js, ts }) => [js.configs.recommended, ts.configs.recommendedTypeChecked]); +define.lint(({ js, ts }) => [ + js.configs.recommended, + ts.configs.recommendedTypeChecked, +]); define.fmt({ singleQuote: true, diff --git a/website/rstack.config.ts b/website/rstack.config.ts index 8e1fd234..29116243 100644 --- a/website/rstack.config.ts +++ b/website/rstack.config.ts @@ -5,14 +5,19 @@ import { define } from 'rstack'; const title = 'Rstack CLI'; const description = 'Rstack CLI brings the Rstack toolchain together with one CLI, one configuration, and one consistent workflow.'; -const descriptionZh = 'Rstack CLI 通过统一的命令行、配置和工作流整合 Rstack 工具链。'; +const descriptionZh = + 'Rstack CLI 通过统一的命令行、配置和工作流整合 Rstack 工具链。'; const injectLlmsHint = process.env.RSPRESS_INJECT_LLMS_HINT !== 'false'; define.doc(async () => { const { pluginSass } = await import('@rsbuild/plugin-sass'); - const { transformerNotationDiff, transformerNotationFocus, transformerNotationHighlight } = - await import('@shikijs/transformers'); - const { pluginClientRedirects } = await import('@rspress/plugin-client-redirects'); + const { + transformerNotationDiff, + transformerNotationFocus, + transformerNotationHighlight, + } = await import('@shikijs/transformers'); + const { pluginClientRedirects } = + await import('@rspress/plugin-client-redirects'); const { pluginSitemap } = await import('@rspress/plugin-sitemap'); const { pluginOpenGraph } = await import('rsbuild-plugin-open-graph'); const { pluginFontOpenSans } = await import('rspress-plugin-font-open-sans'); @@ -88,7 +93,8 @@ define.doc(async () => { }, ], editLink: { - docRepoBaseUrl: 'https://github.com/rstackjs/rstack-cli/tree/main/website/docs', + docRepoBaseUrl: + 'https://github.com/rstackjs/rstack-cli/tree/main/website/docs', }, }, builderConfig: { diff --git a/website/theme/components/Copyright.tsx b/website/theme/components/Copyright.tsx index 5ef14a01..75e3049a 100644 --- a/website/theme/components/Copyright.tsx +++ b/website/theme/components/Copyright.tsx @@ -5,7 +5,10 @@ export const CopyRight = () => {