From 0db555957f3bcdc4a0b579e4600d142ebac70b45 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com> Date: Fri, 31 Jul 2026 14:05:56 +0900 Subject: [PATCH 1/5] test(rsc): cover Server Function stack names Co-authored-by: OpenCode --- packages/plugin-rsc/e2e/source-map.test.ts | 26 +++++++++++++++++++ .../src/features/inline-directive/server.tsx | 2 +- .../src/features/named-function/action.ts | 2 +- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/packages/plugin-rsc/e2e/source-map.test.ts b/packages/plugin-rsc/e2e/source-map.test.ts index 06de38eb0..af5c88956 100644 --- a/packages/plugin-rsc/e2e/source-map.test.ts +++ b/packages/plugin-rsc/e2e/source-map.test.ts @@ -189,6 +189,32 @@ test.describe('source map', () => { } }) } + + test('preserves Server Function names in stack traces', async ({ page }) => { + const cases = [ + { + route: '/named-function', + referenceName: 'named-function', + functionName: 'namedFunction', + }, + { + route: '/inline-directive', + referenceName: 'inline-directive', + functionName: 'inlineAction', + }, + ] + + for (const item of cases) { + await page.goto(f.url(item.route)) + await waitForHydration(page) + + const button = page.getByRole('button', { + name: new RegExp(`^${item.referenceName}:`), + }) + await button.click() + await expect(button).toContainText(`at ${item.functionName}`) + } + }) }) async function createFunctionSourceMapResolver(page: Page, baseURL: string) { diff --git a/packages/plugin-rsc/examples/source-map/src/features/inline-directive/server.tsx b/packages/plugin-rsc/examples/source-map/src/features/inline-directive/server.tsx index 1c037883e..9c9516261 100644 --- a/packages/plugin-rsc/examples/source-map/src/features/inline-directive/server.tsx +++ b/packages/plugin-rsc/examples/source-map/src/features/inline-directive/server.tsx @@ -5,7 +5,7 @@ export function InlineDirective() { async function inlineAction() { 'use server' - return 'inline directive called' + return new Error().stack?.split('\n')[1] ?? '' } const inlineArrow = async (suffix = 'arrow') => { diff --git a/packages/plugin-rsc/examples/source-map/src/features/named-function/action.ts b/packages/plugin-rsc/examples/source-map/src/features/named-function/action.ts index 4b272c193..8f4214a52 100644 --- a/packages/plugin-rsc/examples/source-map/src/features/named-function/action.ts +++ b/packages/plugin-rsc/examples/source-map/src/features/named-function/action.ts @@ -1,5 +1,5 @@ 'use server' export async function namedFunction() { - return 'named function called' + return new Error().stack?.split('\n')[1] ?? '' } From cb3f8d965243c6d4c66ffd87691d141605d9ba54 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com> Date: Fri, 31 Jul 2026 14:12:21 +0900 Subject: [PATCH 2/5] fix(rsc): fail fast when fixture server exits Document the full workspace build required by RSC examples. Co-authored-by: OpenCode --- packages/plugin-rsc/AGENTS.md | 19 +++++++++---- packages/plugin-rsc/CONTRIBUTING.md | 10 +++++-- packages/plugin-rsc/e2e/fixture.ts | 41 ++++++++++++++++++++++++----- 3 files changed, 56 insertions(+), 14 deletions(-) diff --git a/packages/plugin-rsc/AGENTS.md b/packages/plugin-rsc/AGENTS.md index db4b0097e..d2a5c4a77 100644 --- a/packages/plugin-rsc/AGENTS.md +++ b/packages/plugin-rsc/AGENTS.md @@ -11,12 +11,21 @@ Before creating or changing a conventional RSC example, use [`examples/starter-e ## Quick Reference for AI Agents -### Essential Commands +### Fresh Worktree Setup ```bash -# inside packages/plugin-rsc directory -pnpm build # build package +# from the repository root +pnpm install +pnpm build +``` + +Run the full workspace build before RSC E2E tests because examples may import other workspace packages, including `@vitejs/plugin-react`. + +### Package Commands + +```bash +# inside packages/plugin-rsc pnpm tsc # typecheck -pnpm test # Run unit tests -pnpm test-e2e # Run e2e tests +pnpm test --run # run unit tests +pnpm test-e2e # run E2E tests ``` diff --git a/packages/plugin-rsc/CONTRIBUTING.md b/packages/plugin-rsc/CONTRIBUTING.md index 3102292e6..24526f02d 100644 --- a/packages/plugin-rsc/CONTRIBUTING.md +++ b/packages/plugin-rsc/CONTRIBUTING.md @@ -40,9 +40,15 @@ Use colocated unit tests for self-contained transforms and utilities. ## Development Workflow +In a fresh worktree, install dependencies and build all workspace packages from the repository root before running E2E tests. RSC examples may import other workspace packages, including `@vitejs/plugin-react`, so building only `packages/plugin-rsc` is insufficient. + ```bash -# Build packages -pnpm dev # pnpm -C packages/plugin-rsc dev +# Install and build from the repository root +pnpm install +pnpm build + +# Watch the RSC package during development +pnpm -C packages/plugin-rsc dev # Type check pnpm -C packages/plugin-rsc tsc diff --git a/packages/plugin-rsc/e2e/fixture.ts b/packages/plugin-rsc/e2e/fixture.ts index e67650676..9e95cdb68 100644 --- a/packages/plugin-rsc/e2e/fixture.ts +++ b/packages/plugin-rsc/e2e/fixture.ts @@ -23,7 +23,7 @@ function runCli(options: { command: string; label?: string } & SpawnOptions) { console.log(styleText('magenta', label), data.toString()) }) const done = new Promise((resolve) => { - child.on('exit', (code) => { + child.on('close', (code) => { if (code !== 0 && code !== 143 && process.platform !== 'win32') { console.log(styleText('magenta', `${label}`), `exit code ${code}`) } @@ -32,15 +32,42 @@ function runCli(options: { command: string; label?: string } & SpawnOptions) { }) async function findPort(): Promise { - let stdout = '' - return new Promise((resolve) => { - child.stdout!.on('data', (data) => { - stdout += stripVTControlCharacters(String(data)) - const match = stdout.match(/http:\/\/localhost:(\d+)/) + let portOutput = '' + return new Promise((resolve, reject) => { + function cleanup() { + child.stdout!.off('data', onData) + child.off('close', onClose) + child.off('error', onError) + } + + function onData(data: Buffer) { + portOutput += stripVTControlCharacters(String(data)) + const match = portOutput.match(/http:\/\/localhost:(\d+)/) if (match) { + cleanup() resolve(Number(match[1])) } - }) + } + + function onClose(code: number | null, signal: NodeJS.Signals | null) { + cleanup() + const details = stderr.trim() || stdout.trim() + reject( + new Error( + `${label} exited with ${code ?? signal ?? 'unknown status'} before publishing a port` + + (details ? `\n${details}` : ''), + ), + ) + } + + function onError(error: Error) { + cleanup() + reject(error) + } + + child.stdout!.on('data', onData) + child.on('close', onClose) + child.on('error', onError) }) } From 380ff29a0db7f042574e50de9bffa36b7a40dbf4 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com> Date: Fri, 31 Jul 2026 14:16:17 +0900 Subject: [PATCH 3/5] revert: keep fixture startup changes separate Co-authored-by: OpenCode --- packages/plugin-rsc/AGENTS.md | 19 ++++--------- packages/plugin-rsc/CONTRIBUTING.md | 10 ++----- packages/plugin-rsc/e2e/fixture.ts | 41 +++++------------------------ 3 files changed, 14 insertions(+), 56 deletions(-) diff --git a/packages/plugin-rsc/AGENTS.md b/packages/plugin-rsc/AGENTS.md index d2a5c4a77..db4b0097e 100644 --- a/packages/plugin-rsc/AGENTS.md +++ b/packages/plugin-rsc/AGENTS.md @@ -11,21 +11,12 @@ Before creating or changing a conventional RSC example, use [`examples/starter-e ## Quick Reference for AI Agents -### Fresh Worktree Setup +### Essential Commands ```bash -# from the repository root -pnpm install -pnpm build -``` - -Run the full workspace build before RSC E2E tests because examples may import other workspace packages, including `@vitejs/plugin-react`. - -### Package Commands - -```bash -# inside packages/plugin-rsc +# inside packages/plugin-rsc directory +pnpm build # build package pnpm tsc # typecheck -pnpm test --run # run unit tests -pnpm test-e2e # run E2E tests +pnpm test # Run unit tests +pnpm test-e2e # Run e2e tests ``` diff --git a/packages/plugin-rsc/CONTRIBUTING.md b/packages/plugin-rsc/CONTRIBUTING.md index 24526f02d..3102292e6 100644 --- a/packages/plugin-rsc/CONTRIBUTING.md +++ b/packages/plugin-rsc/CONTRIBUTING.md @@ -40,15 +40,9 @@ Use colocated unit tests for self-contained transforms and utilities. ## Development Workflow -In a fresh worktree, install dependencies and build all workspace packages from the repository root before running E2E tests. RSC examples may import other workspace packages, including `@vitejs/plugin-react`, so building only `packages/plugin-rsc` is insufficient. - ```bash -# Install and build from the repository root -pnpm install -pnpm build - -# Watch the RSC package during development -pnpm -C packages/plugin-rsc dev +# Build packages +pnpm dev # pnpm -C packages/plugin-rsc dev # Type check pnpm -C packages/plugin-rsc tsc diff --git a/packages/plugin-rsc/e2e/fixture.ts b/packages/plugin-rsc/e2e/fixture.ts index 9e95cdb68..e67650676 100644 --- a/packages/plugin-rsc/e2e/fixture.ts +++ b/packages/plugin-rsc/e2e/fixture.ts @@ -23,7 +23,7 @@ function runCli(options: { command: string; label?: string } & SpawnOptions) { console.log(styleText('magenta', label), data.toString()) }) const done = new Promise((resolve) => { - child.on('close', (code) => { + child.on('exit', (code) => { if (code !== 0 && code !== 143 && process.platform !== 'win32') { console.log(styleText('magenta', `${label}`), `exit code ${code}`) } @@ -32,42 +32,15 @@ function runCli(options: { command: string; label?: string } & SpawnOptions) { }) async function findPort(): Promise { - let portOutput = '' - return new Promise((resolve, reject) => { - function cleanup() { - child.stdout!.off('data', onData) - child.off('close', onClose) - child.off('error', onError) - } - - function onData(data: Buffer) { - portOutput += stripVTControlCharacters(String(data)) - const match = portOutput.match(/http:\/\/localhost:(\d+)/) + let stdout = '' + return new Promise((resolve) => { + child.stdout!.on('data', (data) => { + stdout += stripVTControlCharacters(String(data)) + const match = stdout.match(/http:\/\/localhost:(\d+)/) if (match) { - cleanup() resolve(Number(match[1])) } - } - - function onClose(code: number | null, signal: NodeJS.Signals | null) { - cleanup() - const details = stderr.trim() || stdout.trim() - reject( - new Error( - `${label} exited with ${code ?? signal ?? 'unknown status'} before publishing a port` + - (details ? `\n${details}` : ''), - ), - ) - } - - function onError(error: Error) { - cleanup() - reject(error) - } - - child.stdout!.on('data', onData) - child.on('close', onClose) - child.on('error', onError) + }) }) } From 0ab91e27ce14a937073d8c0b2f277f6546de5595 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com> Date: Fri, 31 Jul 2026 14:18:08 +0900 Subject: [PATCH 4/5] test(rsc): isolate Server Function name fixture Co-authored-by: OpenCode --- packages/plugin-rsc/e2e/source-map.test.ts | 16 +++++----- .../src/features/inline-directive/server.tsx | 2 +- .../src/features/named-function/action.ts | 2 +- .../features/server-function-name/action.ts | 5 ++++ .../features/server-function-name/server.tsx | 29 +++++++++++++++++++ .../examples/source-map/src/root.tsx | 8 +++++ 6 files changed, 51 insertions(+), 11 deletions(-) create mode 100644 packages/plugin-rsc/examples/source-map/src/features/server-function-name/action.ts create mode 100644 packages/plugin-rsc/examples/source-map/src/features/server-function-name/server.tsx diff --git a/packages/plugin-rsc/e2e/source-map.test.ts b/packages/plugin-rsc/e2e/source-map.test.ts index af5c88956..db6496c5a 100644 --- a/packages/plugin-rsc/e2e/source-map.test.ts +++ b/packages/plugin-rsc/e2e/source-map.test.ts @@ -191,23 +191,21 @@ test.describe('source map', () => { } test('preserves Server Function names in stack traces', async ({ page }) => { + await page.goto(f.url('/server-function-name')) + await waitForHydration(page) + const cases = [ { - route: '/named-function', - referenceName: 'named-function', - functionName: 'namedFunction', + referenceName: 'module-function-name', + functionName: 'moduleFunctionName', }, { - route: '/inline-directive', - referenceName: 'inline-directive', - functionName: 'inlineAction', + referenceName: 'inline-function-name', + functionName: 'inlineFunctionName', }, ] for (const item of cases) { - await page.goto(f.url(item.route)) - await waitForHydration(page) - const button = page.getByRole('button', { name: new RegExp(`^${item.referenceName}:`), }) diff --git a/packages/plugin-rsc/examples/source-map/src/features/inline-directive/server.tsx b/packages/plugin-rsc/examples/source-map/src/features/inline-directive/server.tsx index 9c9516261..1c037883e 100644 --- a/packages/plugin-rsc/examples/source-map/src/features/inline-directive/server.tsx +++ b/packages/plugin-rsc/examples/source-map/src/features/inline-directive/server.tsx @@ -5,7 +5,7 @@ export function InlineDirective() { async function inlineAction() { 'use server' - return new Error().stack?.split('\n')[1] ?? '' + return 'inline directive called' } const inlineArrow = async (suffix = 'arrow') => { diff --git a/packages/plugin-rsc/examples/source-map/src/features/named-function/action.ts b/packages/plugin-rsc/examples/source-map/src/features/named-function/action.ts index 8f4214a52..4b272c193 100644 --- a/packages/plugin-rsc/examples/source-map/src/features/named-function/action.ts +++ b/packages/plugin-rsc/examples/source-map/src/features/named-function/action.ts @@ -1,5 +1,5 @@ 'use server' export async function namedFunction() { - return new Error().stack?.split('\n')[1] ?? '' + return 'named function called' } diff --git a/packages/plugin-rsc/examples/source-map/src/features/server-function-name/action.ts b/packages/plugin-rsc/examples/source-map/src/features/server-function-name/action.ts new file mode 100644 index 000000000..92b4bbb17 --- /dev/null +++ b/packages/plugin-rsc/examples/source-map/src/features/server-function-name/action.ts @@ -0,0 +1,5 @@ +'use server' + +export async function moduleFunctionName() { + return new Error().stack?.split('\n')[1] ?? '' +} diff --git a/packages/plugin-rsc/examples/source-map/src/features/server-function-name/server.tsx b/packages/plugin-rsc/examples/source-map/src/features/server-function-name/server.tsx new file mode 100644 index 000000000..3938164e3 --- /dev/null +++ b/packages/plugin-rsc/examples/source-map/src/features/server-function-name/server.tsx @@ -0,0 +1,29 @@ +import { SourceLocationCase } from '../../client' +import { moduleFunctionName } from './action' + +export function ServerFunctionName() { + async function inlineFunctionName() { + 'use server' + return new Error().stack?.split('\n')[1] ?? '' + } + + return ( +
+

Server Function names

+
    +
  • + +
  • +
  • + +
  • +
+
+ ) +} diff --git a/packages/plugin-rsc/examples/source-map/src/root.tsx b/packages/plugin-rsc/examples/source-map/src/root.tsx index 100141c31..431f7396e 100644 --- a/packages/plugin-rsc/examples/source-map/src/root.tsx +++ b/packages/plugin-rsc/examples/source-map/src/root.tsx @@ -6,6 +6,7 @@ import { MultipleExports } from './features/multiple-exports/server' import { NamedFunction } from './features/named-function/server' import { ServerActionError } from './features/server-action-error/server' import { ServerComponentError } from './features/server-component-error/server' +import { ServerFunctionName } from './features/server-function-name/server' import { ServerReferenceFromClient } from './features/server-reference-from-client/server' import { Specifiers } from './features/specifiers/server' import { TypescriptTsx } from './features/typescript-tsx/server' @@ -65,6 +66,13 @@ const routes = [ description: 'Two generated registrations from one module.', render: () => , }, + { + path: '/server-function-name', + title: 'Server Function names', + description: + 'Module-level and inline Server Functions preserve their source names in stack traces.', + render: () => , + }, { path: '/server-action-error', title: 'Server Action error', From 6f865cf1b947be1fe33de67704100fbccc58bf46 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com> Date: Fri, 31 Jul 2026 14:24:08 +0900 Subject: [PATCH 5/5] test(rsc): require exact Server Function stack names Co-authored-by: OpenCode --- packages/plugin-rsc/e2e/source-map.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin-rsc/e2e/source-map.test.ts b/packages/plugin-rsc/e2e/source-map.test.ts index db6496c5a..9ad059dc7 100644 --- a/packages/plugin-rsc/e2e/source-map.test.ts +++ b/packages/plugin-rsc/e2e/source-map.test.ts @@ -210,7 +210,7 @@ test.describe('source map', () => { name: new RegExp(`^${item.referenceName}:`), }) await button.click() - await expect(button).toContainText(`at ${item.functionName}`) + await expect(button).toContainText(`at ${item.functionName} (`) } }) })