Skip to content

feat: add optional mock config control panel#146

Open
pengzhanbo wants to merge 7 commits into
mainfrom
ui
Open

feat: add optional mock config control panel#146
pengzhanbo wants to merge 7 commits into
mainfrom
ui

Conversation

@pengzhanbo

@pengzhanbo pengzhanbo commented Apr 11, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

Release Notes

  • New Features

    • 新增可视化 Mock UI 界面,支持查看、管理和测试所有 Mock 配置
    • 支持实时热更新 Mock 数据,快速反映配置变更
    • 支持编辑器直接打开 Mock 文件
    • 新增深色模式和主题切换功能
    • 新增请求测试面板,可在 UI 中快速发送测试请求
  • Refactor

    • 优化构建和开发脚本流程
    • 升级 ESLint 配置以支持 Svelte 项目
  • Chores

    • 新增 Svelte 和 Tailwind CSS 相关工具链
    • 更新和优化依赖版本

@netlify

netlify Bot commented Apr 11, 2026

Copy link
Copy Markdown

Deploy Preview for vite-plugin-mock-dev-server ready!

Name Link
🔨 Latest commit 4d15ac2
🔍 Latest deploy log https://app.netlify.com/projects/vite-plugin-mock-dev-server/deploys/69f1d9acf4613f0008688aa2
😎 Deploy Preview https://deploy-preview-146--vite-plugin-mock-dev-server.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@pengzhanbo

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

本 PR 为 vite-plugin-mock-dev-server 新增可视化 UI 功能:通过 ui: true 插件选项启用后,服务端注入基于 sirv 的静态中间件与四个 REST API;前端由 Svelte 5 + Tailwind 构建,提供 mock 列表浏览、字段实时编辑(delay/enabled)及完整的测试请求对话框。

Changes

Mock Dev Server 可视化 UI

Layer / File(s) Summary
插件选项与构建配置
vite-plugin-mock-dev-server/src/types/options.ts, vite-plugin-mock-dev-server/src/core/options.ts, vite-plugin-mock-dev-server/package.json, tsconfig.json, vite-plugin-mock-dev-server/tsdown.config.ts, vite-plugin-mock-dev-server/src/mockHttp/request.ts
MockServerPluginOptions 新增 ui?: booleanResolvedMockServerPluginOptions 新增 base: string,resolvePluginOptions 补充对应解析;插件包增加 launch-editor/sirv 依赖;移除 tsdown minify;parseRequestBody 参数补充默认值。
哈希基础设施与编译器扩展
vite-plugin-mock-dev-server/src/utils/shared.ts, vite-plugin-mock-dev-server/src/compiler/processData.ts, vite-plugin-mock-dev-server/src/compiler/compiler.ts
新增 genHash(md5),processData 为每个 mock 条目注入 __hash__Compiler.moduleCacheprivate 改为公开,并新增 updateItem 方法以支持通过 hash 定位条目并触发热更新事件。
服务端 UI 中间件
vite-plugin-mock-dev-server/src/ui/index.ts, vite-plugin-mock-dev-server/src/core/init.ts, vite-plugin-mock-dev-server/src/core/plugin.ts
新增 uiMiddleware(/api/list、/api/launch-editor、/api/hot、/api/update 路由及 sirv 静态资源挂载)和 logUIInfo;init.ts 引入 MiddlewareParams 类型并在 ui 启用时追加中间件;plugin.ts 改用展开调用并在启动时输出 UI 地址日志。
前端类型定义、Store 与工具层
vite-plugin-mock-dev-server/ui/src/types/index.ts, vite-plugin-mock-dev-server/ui/src/lib/store.svelte.ts, vite-plugin-mock-dev-server/ui/src/lib/..., vite-plugin-mock-dev-server/ui/src/utils/...
定义 MockItem/AppStore/TestRequest/TestResponse/KeyValue 等类型;建立响应式 store 及 setupStore 初始化(localStorage 持久化、系统主题跟随);新增 i18n(zh-CN/en-US)、外观切换、数据拉取(轮询 hot 接口)与 fetch/keyValue/safeParse 工具函数。
前端骨架、主题与导航组件
vite-plugin-mock-dev-server/ui/index.html, vite-plugin-mock-dev-server/ui/src/main.ts, vite-plugin-mock-dev-server/ui/src/app.css, vite-plugin-mock-dev-server/ui/src/App.svelte, vite-plugin-mock-dev-server/ui/src/components/Navbar.svelte, vite-plugin-mock-dev-server/ui/src/components/Sidebar.svelte, vite-plugin-mock-dev-server/ui/src/components/Icon*.svelte
新增 HTML 入口与主题初始化脚本,Tailwind CSS 变量体系(深/浅色);App 骨架(Navbar + Sidebar + Panel + Testing);Navbar 提供语言/外观切换;Sidebar 将 mock 列表分动态/静态两组并高亮激活项;Icon 图标组件。
Mock 详情面板组件
vite-plugin-mock-dev-server/ui/src/components/Panel.svelte, vite-plugin-mock-dev-server/ui/src/components/PanelItem.svelte, vite-plugin-mock-dev-server/ui/src/components/CodeView.svelte, vite-plugin-mock-dev-server/ui/src/components/FieldBlock.svelte, vite-plugin-mock-dev-server/ui/src/components/FieldInline.svelte, vite-plugin-mock-dev-server/ui/src/components/Switch.svelte
Panel 从 store 派生活动 mock 列表并渲染 PanelItem;PanelItem 展示 HTTP 方法、URL、enabled 开关、validator、各请求响应字段(status/delay/headers/body)及 error 区块、launchEditor 页脚;CodeView/FieldBlock/FieldInline/Switch 为基础展示原语。
交互编辑与测试请求组件
vite-plugin-mock-dev-server/ui/src/components/DelayEditor.svelte, vite-plugin-mock-dev-server/ui/src/components/KeyValueEditor.svelte, vite-plugin-mock-dev-server/ui/src/components/RequestBody.svelte, vite-plugin-mock-dev-server/ui/src/components/ResponseView.svelte, vite-plugin-mock-dev-server/ui/src/components/Testing.svelte
DelayEditor 支持单值/区间编辑与移除;KeyValueEditor 表格键值增删含文件类型;RequestBody 切换 raw/form-data/binary;ResponseView 展示状态码、时间、大小及 Body/Header 标签;Testing 实现完整发送请求流程(动态 URL 编译、bodyType 构造、响应解析、错误处理)。
工程配置与示例更新
package.json, pnpm-workspace.yaml, vite-plugin-mock-dev-server/ui/vite.config.ts, vite-plugin-mock-dev-server/ui/tsconfig*.json, vite-plugin-mock-dev-server/ui/svelte.config.js, eslint.config.js, .vscode/settings.json, example/..., docs/...
根 package.json 拆分 build:js/build:ui/dev:js/dev:ui 脚本,引入 Svelte/Tailwind 工具链;pnpm-workspace 批量更新版本并新增 ui catalog;UI 子包完整 Vite/tsconfig 配置;ESLint 切换为 svelte 版本;示例新增 maybe-error mock、修正 postList.value、启用 ui:true。

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant uiMiddleware
  participant Compiler
  participant data.svelte.ts
  participant Testing.svelte

  Browser->>uiMiddleware: GET /.vite-mock/api/list
  uiMiddleware->>Compiler: 遍历 moduleCache 聚合非 ws 条目
  Compiler-->>uiMiddleware: MockHttpItem[](含 __hash__)
  uiMiddleware-->>Browser: JSON mock 列表

  loop 每 5 秒轮询
    data.svelte.ts->>uiMiddleware: GET /.vite-mock/api/hot
    uiMiddleware-->>data.svelte.ts: { hash }
    data.svelte.ts->>uiMiddleware: GET /.vite-mock/api/list(hash 变化时)
    uiMiddleware-->>data.svelte.ts: 更新后的 mock 列表
  end

  Browser->>uiMiddleware: POST /.vite-mock/api/update(filepath/hash/enabled/delay)
  uiMiddleware->>Compiler: updateItem(filepath, hash, updater)
  Compiler->>Compiler: updateMockData() + emit mock:update-end
  uiMiddleware-->>Browser: { success: true }

  Browser->>Testing.svelte: 点击 Test 按钮
  Testing.svelte->>Browser: fetchMock(compiledUrl, { method, body, headers })
  Browser-->>Testing.svelte: Response(JSON/文本/Blob)
  Testing.svelte->>Testing.svelte: 渲染 ResponseView
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Poem

🐰 一只兔子打开屏幕,
Mock 列表整整齐齐排列,
点击测试,请求飞出去,
响应回来颜色各不同。
深色浅色随心来切换,
服务器里有 UI 帮你看!✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.54% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR标题准确反映了主要变更内容:添加可选的Mock配置控制面板UI功能,涵盖了大量新增UI组件、路由、样式和相关配置的核心目的。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ui
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch ui

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 18

🧹 Nitpick comments (1)
vite-plugin-mock-dev-server/src/core/init.ts (1)

10-10: 将显式中间件元组联合类型替代 Parameters<Connect.Server['use']> 以移除不安全的 unknown 双重断言

第 10 行使用 Parameters<Connect.Server['use']> 定义 MiddlewareParams 类型。由于 Connect.Server['use'] 存在多个重载签名,Parameters<T> 只会提取最后一个签名([string, NextHandleFunction]),导致无法表示单参数形式([NextHandleFunction])。第 70 行因此需要通过 as unknown as MiddlewareParams 绕过类型检查。

建议改为显式声明包含两种参数形式的联合类型,完全消除不必要的类型强转,提升代码的类型安全性和可维护性。

♻️ 建议修改
-type MiddlewareParams = Parameters<Connect.Server['use']>
+type MiddlewareParams =
+  | [Connect.NextHandleFunction]
+  | [string, Connect.NextHandleFunction]
-    [createMockMiddleware(compiler, options)] as unknown as MiddlewareParams,
+    [createMockMiddleware(compiler, options)],
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@vite-plugin-mock-dev-server/src/core/init.ts` at line 10, The
MiddlewareParams type definition at line 10 uses
Parameters<Connect.Server['use']> which only captures the last overload
signature and doesn't include the single-parameter form, causing an unsafe type
assertion (as unknown as MiddlewareParams) to be needed at line 70. Replace the
MiddlewareParams type definition with an explicit union type that includes both
possible parameter forms for the Connect middleware: one for a single
NextHandleFunction parameter and one for a string path with NextHandleFunction
parameter. This eliminates the need for the unsafe type cast and provides
complete type safety coverage for all valid middleware configurations.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tsconfig.json`:
- Line 17: The include pattern in tsconfig.json at line 17 uses a single
wildcard that only matches files directly in the src directory but does not
recursively include files in nested subdirectories like build/, compiler/,
cookies/, core/, helpers/, mockHttp/, mockWebsocket/, recorder/, types/, ui/,
and utils/. Replace the pattern `./vite-plugin-mock-dev-server/src/*` with a
recursive glob pattern `./vite-plugin-mock-dev-server/src/**/*` to ensure all
source files in all subdirectories are included in type checking coverage.

In `@vite-plugin-mock-dev-server/src/ui/index.ts`:
- Around line 64-72: The handler function for the /api/launch-editor route only
ends the response when the file query parameter exists, causing requests without
the file parameter to hang indefinitely. Add an else branch after the if
statement that checks for query.file to handle the missing parameter case. In
this else branch, set the response status code to 400 using res.statusCode, add
appropriate error headers using addHeaders, and end the response with a JSON
error message indicating that the file parameter is required, ensuring all code
paths properly terminate the response.
- Around line 83-88: The `/api/update` route handler directly destructures the
body returned from parseRequestBody without validation, which will throw a
runtime exception if parseRequestBody returns undefined (on parsing failure or
unsupported content types). Add validation after the parseRequestBody call to
check if body exists and contains the required fields (filepath and hash) before
proceeding with destructuring and the compiler.updateItem call. If validation
fails, respond with an appropriate error response (e.g., res.end with error
status/message) instead of attempting the update.
- Around line 101-105: The logUIInfo function logs a hardcoded mock server path
of `/.vite-mock/` but the actual middleware is mounted at
`${options.base}/.vite-mock`, causing incorrect URLs to be displayed when using
a non-root base path. Modify the logUIInfo function signature to accept the base
option as a parameter, then update the logger.info call to dynamically construct
the URL using the provided base path concatenated with `/.vite-mock/` instead of
using the hardcoded `/.vite-mock/` path.

In `@vite-plugin-mock-dev-server/ui/src/components/DelayEditor.svelte`:
- Around line 34-37: The save() function is missing validation for range mode to
ensure rangeStart does not exceed rangeEnd, which can result in invalid reverse
ranges like [800, 200] being saved. Add a check when editMode equals 'range' to
validate that rangeStart is less than or equal to rangeEnd, either by
normalizing the values (swapping them if start > end) or by preventing the save
operation and displaying an appropriate error message to the user.

In `@vite-plugin-mock-dev-server/ui/src/components/FieldBlock.svelte`:
- Around line 18-27: The condition `{`#if` field}` at the start of the FieldBlock
component will incorrectly skip rendering when field contains valid falsy values
like false, 0, or empty strings, causing incomplete data display. Replace the
`{`#if` field}` check with a condition that only filters out truly missing values
(undefined or null), such as checking `{`#if` field !== undefined && field !==
null}` or `{`#if` field !== undefined}`, so that legitimate falsy field values are
still rendered properly.

In `@vite-plugin-mock-dev-server/ui/src/components/KeyValueEditor.svelte`:
- Line 64: The file input change handler in the onchange callback directly
assigns e.currentTarget.files to item.value without checking if files is null,
which can occur when users cancel file selection. Add explicit null checking in
the onchange handler to fallback to an empty FileList or null value instead of
blindly casting e.currentTarget.files to FileList, ensuring item.value maintains
a predictable state regardless of user interaction.
- Around line 43-49: When the `item.type` is changed via the select element
binding, the `item.value` is not being reset, which causes type residual data
problems (e.g., a FileList object remaining when switching from file mode to
text mode). Add a reactive statement or change handler that watches the
`item.type` binding and resets `item.value` to an appropriate empty value (such
as empty string for text type or null/undefined for file type) whenever the type
changes. This ensures that switching between text and file modes clears any
stale data from the previous type.

In `@vite-plugin-mock-dev-server/ui/src/components/Navbar.svelte`:
- Around line 37-44: The select element binding to store.locale and the button
element with onclick={toggleAppearance} lack accessible names for screen
readers. Add an aria-label attribute to the select dropdown to identify it as a
language selection control, and add an aria-label attribute to the appearance
toggle button to identify its purpose. This will help users of assistive
technologies understand what each control does.

In `@vite-plugin-mock-dev-server/ui/src/components/PanelItem.svelte`:
- Around line 82-85: The DelayEditor component on line 84 receives the delay
value but lacks a callback mechanism to persist changes back to the parent
component, creating inconsistency with how the enabled field (line 38) properly
updates via modifyData(). Add an onSave callback prop to the DelayEditor
component that fires when the delay value is saved, then pass a callback from
PanelItem that invokes modifyData() with the updated delay value to ensure
changes are properly persisted to the mock configuration.

In `@vite-plugin-mock-dev-server/ui/src/components/RequestBody.svelte`:
- Line 43: The file input element on line 43 uses bind:value to bind to
body.binary, but body.binary is defined as type FileList in the TestRequest
interface. In Svelte 5, file input elements must use bind:files instead of
bind:value to correctly capture the FileList. Change the binding from
bind:value={body.binary} to bind:files={body.binary} on the input element with
type='file' to ensure binary request bodies are properly captured and sent.

In `@vite-plugin-mock-dev-server/ui/src/components/ResponseView.svelte`:
- Around line 80-86: The response body display condition in the conditional
block starting with the check on response.body uses a truthy evaluation that
incorrectly hides valid response bodies like empty strings and the number 0.
Change the condition from checking response.body truthiness to explicitly
checking if response.body is not undefined by using response.body !== undefined
instead, so that legitimate falsy values like empty strings and zero are
properly displayed in the ResponseView component.
- Around line 27-32: The blobUrl derivation creates object URLs for blob
responses without ever releasing them, causing memory leaks in long-running
applications. Replace the $derived.by() reactive statement for blobUrl with a
$effect() block that stores the blob URL value and includes a cleanup function
to call URL.revokeObjectURL() on the previous blob URL whenever the response
changes or the effect reruns. This ensures old object URLs are properly released
when a new blob response is created or when the component unmounts, preventing
memory accumulation over time.

In `@vite-plugin-mock-dev-server/ui/src/components/Testing.svelte`:
- Around line 166-171: When sending form-data requests, the manually set
Content-Type header with 'multipart/form-data' prevents the browser from
automatically generating the boundary parameter required for proper parsing. In
the fetchMock call around line 166, filter out the Content-Type header from the
request.headers object when request.bodyType is 'form-data', allowing the
browser to automatically set the correct Content-Type header with the boundary.
This can be done by conditionally excluding the Content-Type key from the
headers object passed to keyValueToObj(request.headers) when bodyType is
form-data.

In `@vite-plugin-mock-dev-server/ui/src/lib/appearance.svelte.ts`:
- Around line 9-10: The toggleAppearance function directly accesses
map[store.appearance] without handling cases where store.appearance contains an
illegal value from persistence, which can result in undefined being assigned to
store.appearance and breaking subsequent theme class synchronization. Modify the
assignment in toggleAppearance to include a fallback value using the nullish
coalescing operator or a conditional check, ensuring that if
map[store.appearance] is undefined due to an invalid key, a safe default
appearance value is used instead.

In `@vite-plugin-mock-dev-server/ui/src/lib/data.svelte.ts`:
- Around line 23-33: The setupData function has two issues: unhandled promise
rejections in the async interval callback and potential duplicate polling if
setupData is called multiple times. Add try-catch error handling inside the
setInterval async callback to catch and log any errors from fetchMockApi or
updateData calls. Additionally, store the interval ID returned by setInterval in
a module-level variable and clear any existing interval (using clearInterval) at
the start of setupData to prevent creating multiple concurrent intervals. This
ensures only one polling loop runs at a time and all errors are properly caught.

In `@vite-plugin-mock-dev-server/ui/src/utils/fetch.ts`:
- Around line 21-27: The headers merging in the fetchMockApi function uses
object spread syntax which is incompatible with HeadersInit types like Headers
instances or [string, string][] arrays, causing them to be lost or malformed.
Replace the object spread approach { 'Content-Type': 'application/json',
...headers } with the Headers constructor to properly handle all HeadersInit
formats: create a new Headers instance, set the Content-Type header, then append
the passed headers using the appropriate Headers API method that handles all
input types correctly.

In `@vite-plugin-mock-dev-server/ui/src/utils/safeParse.ts`:
- Around line 1-7: The safeJsonParse function currently returns the raw result
of JSON.parse without validating that it is actually an object. For valid JSON
strings like "null", "1", or "true", the function will return primitive values
instead of objects, causing crashes when callers try to access properties. Add a
type check after parsing to verify that the result is an object (not null and of
type 'object'); only return the parsed value if this check passes, otherwise
return the safe default empty object {} as T. This ensures the function always
returns an object-like value or the fallback.

---

Nitpick comments:
In `@vite-plugin-mock-dev-server/src/core/init.ts`:
- Line 10: The MiddlewareParams type definition at line 10 uses
Parameters<Connect.Server['use']> which only captures the last overload
signature and doesn't include the single-parameter form, causing an unsafe type
assertion (as unknown as MiddlewareParams) to be needed at line 70. Replace the
MiddlewareParams type definition with an explicit union type that includes both
possible parameter forms for the Connect middleware: one for a single
NextHandleFunction parameter and one for a string path with NextHandleFunction
parameter. This eliminates the need for the unsafe type cast and provides
complete type safety coverage for all valid middleware configurations.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 873411f4-ceec-496c-89a8-f39e70621677

📥 Commits

Reviewing files that changed from the base of the PR and between 7e43f38 and 4d15ac2.

⛔ Files ignored due to path filters (2)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • vite-plugin-mock-dev-server/ui/src/assets/logo.png is excluded by !**/*.png
📒 Files selected for processing (54)
  • .vscode/settings.json
  • docs/.vitepress/theme/style.css
  • eslint.config.js
  • example/mock/maybe-error.mock.ts
  • example/mock/validator-refererQuery.mock.ts
  • example/vite.config.ts
  • package.json
  • pnpm-workspace.yaml
  • tsconfig.json
  • vite-plugin-mock-dev-server/package.json
  • vite-plugin-mock-dev-server/src/compiler/compiler.ts
  • vite-plugin-mock-dev-server/src/compiler/processData.ts
  • vite-plugin-mock-dev-server/src/core/init.ts
  • vite-plugin-mock-dev-server/src/core/options.ts
  • vite-plugin-mock-dev-server/src/core/plugin.ts
  • vite-plugin-mock-dev-server/src/mockHttp/request.ts
  • vite-plugin-mock-dev-server/src/types/options.ts
  • vite-plugin-mock-dev-server/src/ui/index.ts
  • vite-plugin-mock-dev-server/src/utils/shared.ts
  • vite-plugin-mock-dev-server/tsdown.config.ts
  • vite-plugin-mock-dev-server/ui/index.html
  • vite-plugin-mock-dev-server/ui/src/App.svelte
  • vite-plugin-mock-dev-server/ui/src/app.css
  • vite-plugin-mock-dev-server/ui/src/components/CodeView.svelte
  • vite-plugin-mock-dev-server/ui/src/components/DelayEditor.svelte
  • vite-plugin-mock-dev-server/ui/src/components/FieldBlock.svelte
  • vite-plugin-mock-dev-server/ui/src/components/FieldInline.svelte
  • vite-plugin-mock-dev-server/ui/src/components/IconMoon.svelte
  • vite-plugin-mock-dev-server/ui/src/components/IconRemove.svelte
  • vite-plugin-mock-dev-server/ui/src/components/IconSun.svelte
  • vite-plugin-mock-dev-server/ui/src/components/IconSunMoon.svelte
  • vite-plugin-mock-dev-server/ui/src/components/KeyValueEditor.svelte
  • vite-plugin-mock-dev-server/ui/src/components/Navbar.svelte
  • vite-plugin-mock-dev-server/ui/src/components/Panel.svelte
  • vite-plugin-mock-dev-server/ui/src/components/PanelItem.svelte
  • vite-plugin-mock-dev-server/ui/src/components/RequestBody.svelte
  • vite-plugin-mock-dev-server/ui/src/components/ResponseView.svelte
  • vite-plugin-mock-dev-server/ui/src/components/Sidebar.svelte
  • vite-plugin-mock-dev-server/ui/src/components/Switch.svelte
  • vite-plugin-mock-dev-server/ui/src/components/Testing.svelte
  • vite-plugin-mock-dev-server/ui/src/lib/appearance.svelte.ts
  • vite-plugin-mock-dev-server/ui/src/lib/data.svelte.ts
  • vite-plugin-mock-dev-server/ui/src/lib/i18n.svelte.ts
  • vite-plugin-mock-dev-server/ui/src/lib/store.svelte.ts
  • vite-plugin-mock-dev-server/ui/src/main.ts
  • vite-plugin-mock-dev-server/ui/src/types/index.ts
  • vite-plugin-mock-dev-server/ui/src/utils/fetch.ts
  • vite-plugin-mock-dev-server/ui/src/utils/keyValue.ts
  • vite-plugin-mock-dev-server/ui/src/utils/safeParse.ts
  • vite-plugin-mock-dev-server/ui/svelte.config.js
  • vite-plugin-mock-dev-server/ui/tsconfig.app.json
  • vite-plugin-mock-dev-server/ui/tsconfig.json
  • vite-plugin-mock-dev-server/ui/tsconfig.node.json
  • vite-plugin-mock-dev-server/ui/vite.config.ts
💤 Files with no reviewable changes (2)
  • docs/.vitepress/theme/style.css
  • vite-plugin-mock-dev-server/tsdown.config.ts

Comment thread tsconfig.json
"skipLibCheck": true
},
"include": ["./vite-plugin-mock-dev-server/**/*", "./docs/.vitepress/**/*"],
"include": ["./vite-plugin-mock-dev-server/src/*", "./docs/.vitepress/**/*"],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
python - <<'PY'
from pathlib import Path
import fnmatch

pattern = "vite-plugin-mock-dev-server/src/*"
exts = {".ts", ".tsx", ".mts", ".cts"}

files = [
    str(p).replace("\\", "/")
    for p in Path("vite-plugin-mock-dev-server/src").rglob("*")
    if p.is_file() and p.suffix in exts
]
missed = [f for f in files if not fnmatch.fnmatch(f, pattern)]

print(f"total source files: {len(files)}")
print(f"missed by current include pattern: {len(missed)}")
for f in missed[:60]:
    print(f)
PY

Repository: pengzhanbo/vite-plugin-mock-dev-server

Length of output: 142


🏁 Script executed:

#!/bin/bash
# List all TypeScript files in the src directory with their structure
fd -e ts -e tsx -e mts -e cts vite-plugin-mock-dev-server/src | head -100

Repository: pengzhanbo/vite-plugin-mock-dev-server

Length of output: 512


🏁 Script executed:

#!/bin/bash
# Show the directory structure of vite-plugin-mock-dev-server/src
tree -L 3 vite-plugin-mock-dev-server/src 2>/dev/null || find vite-plugin-mock-dev-server/src -type f -name "*.ts" -o -name "*.tsx" -o -name "*.mts" -o -name "*.cts" | sort

Repository: pengzhanbo/vite-plugin-mock-dev-server

Length of output: 1602


🏁 Script executed:

#!/bin/bash
python3 <<'PY'
from pathlib import Path
import fnmatch

pattern = "vite-plugin-mock-dev-server/src/*"
exts = {".ts", ".tsx", ".mts", ".cts"}

# Get files exactly as the original script did
files = [
    str(p).replace("\\", "/")
    for p in Path("vite-plugin-mock-dev-server/src").rglob("*")
    if p.is_file() and p.suffix in exts
]

print(f"Total files: {len(files)}")
print("\nFirst 10 files:")
for f in files[:10]:
    print(f"  {f}")

print("\nChecking pattern matching:")
print(f"Pattern: '{pattern}'")

# Test individual files
test_files = [
    "vite-plugin-mock-dev-server/src/index.ts",
    "vite-plugin-mock-dev-server/src/server.ts",
    "vite-plugin-mock-dev-server/src/build/generate.ts",
    "vite-plugin-mock-dev-server/src/compiler/compile.ts",
]

for test in test_files:
    matched = fnmatch.fnmatch(test, pattern)
    print(f"  {test}: {matched}")

print("\nActual missed files:")
missed = [f for f in files if not fnmatch.fnmatch(f, pattern)]
print(f"Count: {len(missed)}")
for f in missed[:15]:
    print(f"  {f}")
PY

Repository: pengzhanbo/vite-plugin-mock-dev-server

Length of output: 948


🏁 Script executed:

#!/bin/bash
# Check the actual tsconfig.json to see the include pattern
cat -n tsconfig.json | sed -n '15,20p'

Repository: pengzhanbo/vite-plugin-mock-dev-server

Length of output: 293


🏁 Script executed:

#!/bin/bash
# Verify TypeScript glob pattern behavior with the actual tsconfig pattern
# TypeScript uses glob patterns, not fnmatch. Test with minimatch (npm package used by TS)
python3 <<'PY'
import subprocess
import sys

# Install minimatch which is what TypeScript uses for glob matching
subprocess.run(["npm", "install", "-s", "minimatch"], capture_output=True)

# Now test with minimatch behavior (TypeScript's glob engine)
code = """
const minimatch = require('minimatch');

const pattern = './vite-plugin-mock-dev-server/src/*';
const testPaths = [
  './vite-plugin-mock-dev-server/src/index.ts',
  './vite-plugin-mock-dev-server/src/build/generate.ts',
  './vite-plugin-mock-dev-server/src/compiler/compile.ts',
  './vite-plugin-mock-dev-server/src/types/config.ts',
];

console.log(`Pattern: ${pattern}`);
testPaths.forEach(path => {
  const matches = minimatch(path, pattern, { matchBase: false });
  console.log(`  ${path}: ${matches}`);
});
"""

with open('/tmp/test_glob.js', 'w') as f:
    f.write(code)

subprocess.run(["node", "/tmp/test_glob.js"])
PY

Repository: pengzhanbo/vite-plugin-mock-dev-server

Length of output: 963


🏁 Script executed:

#!/bin/bash
# Research TypeScript glob pattern behavior
# According to TypeScript docs, * matches across directory boundaries in certain contexts
# Let's verify by checking if there's a way to test this

# First check the TypeScript version in the project
cat tsconfig.json | grep -A 5 '"compilerOptions"'

Repository: pengzhanbo/vite-plugin-mock-dev-server

Length of output: 239


🌐 Web query:

TypeScript tsconfig.json include pattern glob behavior * wildcard matching

💡 Result:

In TypeScript's tsconfig.json, the include property uses glob patterns to specify which files should be included in the compilation [1][2]. The asterisk () wildcard follows specific behavior for matching files and directories: - The * character matches zero or more characters, but explicitly excludes directory separators (/) [1][3]. - When a path segment consists only of * or., only files with supported extensions are included (e.g.,.ts,.tsx, and.d.ts by default, or.js and.jsx if allowJs is enabled) [4]. - If the last path segment in a pattern does not contain an extension or a wildcard, it is treated as a directory, and all files with supported extensions within that directory are automatically included [1][2]. Additionally, TypeScript supports other glob wildcards [4]: -?: Matches any single character (excluding directory separators) [1][4]. - /: Recursively matches any number of nested directories [1][4]. These patterns are resolved relative to the directory containing the tsconfig.json file [1][3].

Citations:


include 模式过窄,会遗漏 src 下的所有嵌套源码文件

Line 17 使用 ./vite-plugin-mock-dev-server/src/* 作为包含模式。根据 TypeScript 的 glob 规则,* 通配符不会跨越目录分隔符,因此该模式仅会匹配 src 目录直接下的 3 个文件(index.tsserver.tsshim.d.ts),而会遗漏 build/compiler/cookies/core/helpers/mockHttp/mockWebsocket/recorder/types/ui/utils/ 等 11 个子目录中的 54 个源文件,导致这些文件不被类型检查覆盖。建议改用递归模式。

🔧 建议修改
-  "include": ["./vite-plugin-mock-dev-server/src/*", "./docs/.vitepress/**/*"],
+  "include": ["./vite-plugin-mock-dev-server/src/**/*", "./docs/.vitepress/**/*"],
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"include": ["./vite-plugin-mock-dev-server/src/*", "./docs/.vitepress/**/*"],
"include": ["./vite-plugin-mock-dev-server/src/**/*", "./docs/.vitepress/**/*"],
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tsconfig.json` at line 17, The include pattern in tsconfig.json at line 17
uses a single wildcard that only matches files directly in the src directory but
does not recursively include files in nested subdirectories like build/,
compiler/, cookies/, core/, helpers/, mockHttp/, mockWebsocket/, recorder/,
types/, ui/, and utils/. Replace the pattern
`./vite-plugin-mock-dev-server/src/*` with a recursive glob pattern
`./vite-plugin-mock-dev-server/src/**/*` to ensure all source files in all
subdirectories are included in type checking coverage.

Comment on lines +64 to +72
`${prefix}/api/launch-editor`,
((req, res) => {
addHeaders(res)
const { query } = urlParse(req.url!)
if (query.file) {
launchEditor(String(decodeURIComponent(String(query.file))))
res.end(JSON.stringify({ success: true }))
}
}) as Connect.NextHandleFunction,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

缺少 file 参数时接口不会结束响应。

当前分支只在 query.file 存在时 res.end,否则请求会一直挂起。建议返回 400 并统一结束响应。

建议修改
     [
       `${prefix}/api/launch-editor`,
       ((req, res) => {
         addHeaders(res)
         const { query } = urlParse(req.url!)
-        if (query.file) {
-          launchEditor(String(decodeURIComponent(String(query.file))))
-          res.end(JSON.stringify({ success: true }))
-        }
+        if (!query.file) {
+          res.statusCode = 400
+          res.end(JSON.stringify({ success: false, message: 'Missing "file" query parameter' }))
+          return
+        }
+        launchEditor(String(decodeURIComponent(String(query.file))))
+        res.end(JSON.stringify({ success: true }))
       }) as Connect.NextHandleFunction,
     ],
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@vite-plugin-mock-dev-server/src/ui/index.ts` around lines 64 - 72, The
handler function for the /api/launch-editor route only ends the response when
the file query parameter exists, causing requests without the file parameter to
hang indefinitely. Add an else branch after the if statement that checks for
query.file to handle the missing parameter case. In this else branch, set the
response status code to 400 using res.statusCode, add appropriate error headers
using addHeaders, and end the response with a JSON error message indicating that
the file parameter is required, ensuring all code paths properly terminate the
response.

Comment on lines +83 to +88
(async (req, res) => {
addHeaders(res)
const body = await parseRequestBody(req, options.logger)
const { filepath, hash, ...item } = body
compiler.updateItem(filepath, hash, mock => Object.assign(mock, item))
res.end(JSON.stringify({ success: true }))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

/api/update 对空 body 无保护,存在运行时异常路径。

parseRequestBody 在解析失败或不支持类型时可能返回 undefined,这里直接解构会触发异常。请先做 body/字段校验,再执行更新。

建议修改
     [
       `${prefix}/api/update`,
       (async (req, res) => {
         addHeaders(res)
         const body = await parseRequestBody(req, options.logger)
-        const { filepath, hash, ...item } = body
+        if (!body || typeof body !== 'object') {
+          res.statusCode = 400
+          res.end(JSON.stringify({ success: false, message: 'Invalid request body' }))
+          return
+        }
+        const { filepath, hash, ...item } = body as Record<string, any>
+        if (typeof filepath !== 'string' || typeof hash !== 'string') {
+          res.statusCode = 400
+          res.end(JSON.stringify({ success: false, message: 'Missing filepath/hash' }))
+          return
+        }
         compiler.updateItem(filepath, hash, mock => Object.assign(mock, item))
         res.end(JSON.stringify({ success: true }))
       }) as Connect.NextHandleFunction,
     ],
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
(async (req, res) => {
addHeaders(res)
const body = await parseRequestBody(req, options.logger)
const { filepath, hash, ...item } = body
compiler.updateItem(filepath, hash, mock => Object.assign(mock, item))
res.end(JSON.stringify({ success: true }))
(async (req, res) => {
addHeaders(res)
const body = await parseRequestBody(req, options.logger)
if (!body || typeof body !== 'object') {
res.statusCode = 400
res.end(JSON.stringify({ success: false, message: 'Invalid request body' }))
return
}
const { filepath, hash, ...item } = body as Record<string, any>
if (typeof filepath !== 'string' || typeof hash !== 'string') {
res.statusCode = 400
res.end(JSON.stringify({ success: false, message: 'Missing filepath/hash' }))
return
}
compiler.updateItem(filepath, hash, mock => Object.assign(mock, item))
res.end(JSON.stringify({ success: true }))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@vite-plugin-mock-dev-server/src/ui/index.ts` around lines 83 - 88, The
`/api/update` route handler directly destructures the body returned from
parseRequestBody without validation, which will throw a runtime exception if
parseRequestBody returns undefined (on parsing failure or unsupported content
types). Add validation after the parseRequestBody call to check if body exists
and contains the required fields (filepath and hash) before proceeding with
destructuring and the compiler.updateItem call. If validation fails, respond
with an appropriate error response (e.g., res.end with error status/message)
instead of attempting the update.

Comment on lines +101 to +105
export function logUIInfo(httpServer: HttpServer, logger: Logger): void {
httpServer.once('listening', () => {
const address = httpServer.address() as AddressInfo
setImmediate(() => logger.info(` ${ansis.green('➜')} ${ansis.bold.gray('Mock')}: ${ansis.cyan(`http://localhost:${address.port}/.vite-mock/`)}`))
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

UI 启动日志未包含 base,非根路径时提示地址会错误。

中间件实际挂载在 ${options.base}/.vite-mock,但日志固定输出 /.vite-mock/,会误导用户。建议把 base 传入并拼接实际路径。

建议修改
-export function logUIInfo(httpServer: HttpServer, logger: Logger): void {
+export function logUIInfo(httpServer: HttpServer, logger: Logger, base = '/'): void {
   httpServer.once('listening', () => {
     const address = httpServer.address() as AddressInfo
-    setImmediate(() => logger.info(`  ${ansis.green('➜')}  ${ansis.bold.gray('Mock')}:    ${ansis.cyan(`http://localhost:${address.port}/.vite-mock/`)}`))
+    const uiPath = normalizePath(`${base}/.vite-mock/`)
+    setImmediate(() => logger.info(`  ${ansis.green('➜')}  ${ansis.bold.gray('Mock')}:    ${ansis.cyan(`http://localhost:${address.port}${uiPath}`)}`))
   })
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@vite-plugin-mock-dev-server/src/ui/index.ts` around lines 101 - 105, The
logUIInfo function logs a hardcoded mock server path of `/.vite-mock/` but the
actual middleware is mounted at `${options.base}/.vite-mock`, causing incorrect
URLs to be displayed when using a non-root base path. Modify the logUIInfo
function signature to accept the base option as a parameter, then update the
logger.info call to dynamically construct the URL using the provided base path
concatenated with `/.vite-mock/` instead of using the hardcoded `/.vite-mock/`
path.

Comment on lines +34 to +37
function save() {
delay = editMode === 'single' ? singleValue : [rangeStart, rangeEnd]
isEditing = false
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

save() 缺少区间合法性校验,可能保存反向延迟范围。

在 Line 35,当 editMode === 'range' 时应确保 start <= end,否则会写入无效区间(如 [800, 200])。建议在保存时做标准化或阻止提交。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@vite-plugin-mock-dev-server/ui/src/components/DelayEditor.svelte` around
lines 34 - 37, The save() function is missing validation for range mode to
ensure rangeStart does not exceed rangeEnd, which can result in invalid reverse
ranges like [800, 200] being saved. Add a check when editMode equals 'range' to
validate that rangeStart is less than or equal to rangeEnd, either by
normalizing the values (swapping them if start > end) or by preventing the save
operation and displaying an appropriate error message to the user.

Comment on lines +166 to +171
const res = await fetchMock(url, {
method: request.method,
body: NO_BODY_METHODS.includes(request.method) ? undefined : body,
query: keyValueToObj(request.query),
headers: keyValueToObj(request.headers),
referrer: referrer.toString(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

使用 fetch发送FormData 时,Content-Type: multipart/form-data 应手动设置,还是应由浏览器自动附带 boundary?

💡 Result:

在使用 fetch 发送 FormData 时,你不应手动设置 Content-Type: multipart/form-data 请求头 [1][2][3]。 正确的做法是让浏览器自动处理该请求头 [4][5]。当你将 FormData 对象作为 body 参数传递给 fetch 时,浏览器会自动设置 Content-Type 并附带一个生成的 boundary(边界)字符串 [6][2][7]。 ### 为什么不能手动设置? 如果你手动设置了 Content-Type: multipart/form-data,浏览器将无法自动填入生成 boundary 所需的随机字符串 [1][2]。由于服务器需要该 boundary 来解析请求体中各个字段的边界,缺失此信息会导致服务端无法正确解析 FormData 数据 [8][2]。 ### 正确的实现方式 只需在 fetchheaders 中省略 Content-Type 即可: javascript const formData = new FormData(); formData.append('key', 'value'); formData.append('file', fileInput.files[0]); fetch('https://api.example.com/upload', { method: 'POST', body: formData // 不要在这里添加 'Content-Type' }); 在这种方式下,浏览器发送的请求头会自动包含类似如下的内容: Content-Type: multipart/form-data; boundary=----WebKitFormBoundary... [6][7] 这种自动处理机制确保了请求头中的 boundary 与请求体中实际使用的分隔符完全一致 [2]。

Citations:


🏁 Script executed:

# Find and examine the Testing.svelte file
fd -t f "Testing.svelte" --exec wc -l {} \;

Repository: pengzhanbo/vite-plugin-mock-dev-server

Length of output: 149


🏁 Script executed:

# Read the relevant section around lines 166-171
sed -n '150,190p' vite-plugin-mock-dev-server/ui/src/components/Testing.svelte

Repository: pengzhanbo/vite-plugin-mock-dev-server

Length of output: 1689


🏁 Script executed:

# Look for body construction and FormData usage in this file
rg -n "body|FormData|form-data" vite-plugin-mock-dev-server/ui/src/components/Testing.svelte -B 2 -A 2

Repository: pengzhanbo/vite-plugin-mock-dev-server

Length of output: 3581


发送 form-data 请求时,不应手动设置 Content-Type 头,否则浏览器无法生成 boundary

request.bodyType === 'form-data' 时,代码在第 137-147 行构造了正确的 FormData 对象,但在第 101 行,contentType[request.bodyType] 被设置为 'multipart/form-data' 并添加到 request.headers。这个手动设置的头部在第 170 行被传递给 fetch,导致浏览器无法自动生成包含 boundary 的正确 Content-Type 头。服务端因无法获得 boundary 信息而解析失败。

需要在传递给 fetch 前,当 bodyType 为 'form-data' 时过滤掉 Content-Type 头,让浏览器自动处理:

建议修改
     try {
+      const headers = keyValueToObj(request.headers)
+      if (request.bodyType === 'form-data') {
+        for (const k of Object.keys(headers)) {
+          if (k.toLowerCase() === 'content-type')
+            delete headers[k]
+        }
+      }
       const res = await fetchMock(url, {
         method: request.method,
         body: NO_BODY_METHODS.includes(request.method) ? undefined : body,
         query: keyValueToObj(request.query),
-        headers: keyValueToObj(request.headers),
+        headers,
         referrer: referrer.toString(),
       })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const res = await fetchMock(url, {
method: request.method,
body: NO_BODY_METHODS.includes(request.method) ? undefined : body,
query: keyValueToObj(request.query),
headers: keyValueToObj(request.headers),
referrer: referrer.toString(),
const headers = keyValueToObj(request.headers)
if (request.bodyType === 'form-data') {
for (const k of Object.keys(headers)) {
if (k.toLowerCase() === 'content-type')
delete headers[k]
}
}
const res = await fetchMock(url, {
method: request.method,
body: NO_BODY_METHODS.includes(request.method) ? undefined : body,
query: keyValueToObj(request.query),
headers,
referrer: referrer.toString(),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@vite-plugin-mock-dev-server/ui/src/components/Testing.svelte` around lines
166 - 171, When sending form-data requests, the manually set Content-Type header
with 'multipart/form-data' prevents the browser from automatically generating
the boundary parameter required for proper parsing. In the fetchMock call around
line 166, filter out the Content-Type header from the request.headers object
when request.bodyType is 'form-data', allowing the browser to automatically set
the correct Content-Type header with the boundary. This can be done by
conditionally excluding the Content-Type key from the headers object passed to
keyValueToObj(request.headers) when bodyType is form-data.

Comment on lines +9 to +10
export function toggleAppearance() {
store.appearance = map[store.appearance]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

为外观切换增加非法值兜底,避免状态变成 undefined

Line 10 直接使用 map[store.appearance]。当持久化里出现非法值时,会把 store.appearance 写成 undefined,后续主题类名同步会异常。建议加回退值。

💡 建议修改
 export function toggleAppearance() {
-  store.appearance = map[store.appearance]
+  store.appearance = map[store.appearance as Appearance] ?? 'light'
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function toggleAppearance() {
store.appearance = map[store.appearance]
export function toggleAppearance() {
store.appearance = map[store.appearance as Appearance] ?? 'light'
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@vite-plugin-mock-dev-server/ui/src/lib/appearance.svelte.ts` around lines 9 -
10, The toggleAppearance function directly accesses map[store.appearance]
without handling cases where store.appearance contains an illegal value from
persistence, which can result in undefined being assigned to store.appearance
and breaking subsequent theme class synchronization. Modify the assignment in
toggleAppearance to include a fallback value using the nullish coalescing
operator or a conditional check, ensuring that if map[store.appearance] is
undefined due to an invalid key, a safe default appearance value is used
instead.

Comment on lines +23 to +33
export async function setupData() {
hash = (await fetchMockApi<{ hash: string }>('hot')).hash
await updateData()

setInterval(async () => {
const current = (await fetchMockApi<{ hash: string }>('hot')).hash
if (current !== hash) {
hash = current
await updateData()
}
}, 5000)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

轮询缺少异常兜底与生命周期控制,存在未处理拒绝和重复轮询风险。

setInterval(async () => ...) 中任一请求失败都会产生未处理 rejection;同时 setupData() 重入会创建多个定时器,导致重复请求和状态抖动。

💡 建议修复
 let hash = ''
+let pollTimer: ReturnType<typeof setInterval> | undefined
 export async function setupData() {
+  if (pollTimer)
+    return
   hash = (await fetchMockApi<{ hash: string }>('hot')).hash
   await updateData()
 
-  setInterval(async () => {
-    const current = (await fetchMockApi<{ hash: string }>('hot')).hash
-    if (current !== hash) {
-      hash = current
-      await updateData()
-    }
-  }, 5000)
+  pollTimer = setInterval(() => {
+    void (async () => {
+      try {
+        const current = (await fetchMockApi<{ hash: string }>('hot')).hash
+        if (current !== hash) {
+          hash = current
+          await updateData()
+        }
+      }
+      catch {
+        // 保持轮询存活,避免未处理 rejection
+      }
+    })()
+  }, 5000)
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function setupData() {
hash = (await fetchMockApi<{ hash: string }>('hot')).hash
await updateData()
setInterval(async () => {
const current = (await fetchMockApi<{ hash: string }>('hot')).hash
if (current !== hash) {
hash = current
await updateData()
}
}, 5000)
let hash = ''
let pollTimer: ReturnType<typeof setInterval> | undefined
export async function setupData() {
if (pollTimer)
return
hash = (await fetchMockApi<{ hash: string }>('hot')).hash
await updateData()
pollTimer = setInterval(() => {
void (async () => {
try {
const current = (await fetchMockApi<{ hash: string }>('hot')).hash
if (current !== hash) {
hash = current
await updateData()
}
}
catch {
// 保持轮询存活,避免未处理 rejection
}
})()
}, 5000)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@vite-plugin-mock-dev-server/ui/src/lib/data.svelte.ts` around lines 23 - 33,
The setupData function has two issues: unhandled promise rejections in the async
interval callback and potential duplicate polling if setupData is called
multiple times. Add try-catch error handling inside the setInterval async
callback to catch and log any errors from fetchMockApi or updateData calls.
Additionally, store the interval ID returned by setInterval in a module-level
variable and clear any existing interval (using clearInterval) at the start of
setupData to prevent creating multiple concurrent intervals. This ensures only
one polling loop runs at a time and all errors are properly caught.

Comment on lines +21 to +27
export async function fetchMockApi<T>(input: string, { headers, ...init }: MockFetchOptions = {}): Promise<T> {
const res = await fetchMock(input, {
baseURL: normalize(`${import.meta.env.BASE_URL}/.vite-mock/api`),
headers: {
'Content-Type': 'application/json',
...headers,
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In the Fetch API, what are valid RequestInit.headers (HeadersInit) types, and what happens when using object spread ({ ...headers }) on a Headersinstance or[string, string][]?

💡 Result:

In the Fetch API, the headers property within RequestInit expects a HeadersInit type [1]. This type is defined as either a Headers object, an object literal (Record<string, string>), or an array of two-item arrays (string[][]) [1][2]. Using object spread syntax ({ ...headers }) on a Headers instance or a [string, string][] array will not behave as expected because the spread operator only copies enumerable own properties of an object [3]. 1. Spreading a Headers instance: Since Headers objects do not have enumerable properties (their data is stored internally), spreading a Headers instance into an object literal typically results in an empty object {} [2][4]. 2. Spreading a string[][] array: Because this is an array, the spread operator will treat the array indices (0, 1, 2, etc.) as the keys of the new object, resulting in an object like {"0": ["key", "value"], "1": ["key2", "value2"]} rather than a map of header names to values [2]. To correctly manipulate or clone headers, you should use the Headers constructor or the methods provided by the Headers interface (such as append, set, and forEach) instead of JavaScript object spread syntax [5][4]. For example, to create a new Headers object from existing headers, you can pass the existing headers directly to the constructor: const newHeaders = new Headers(existingHeaders); [6][4].

Citations:


🏁 Script executed:

# 检查文件是否存在
find . -name "fetch.ts" -type f | grep -E "ui/src/utils"

Repository: pengzhanbo/vite-plugin-mock-dev-server

Length of output: 134


🏁 Script executed:

# 查看 fetch.ts 文件内容,特别关注 MockFetchOptions 定义和 fetchMockApi 函数
fd -i "fetch.ts" | head -5 | xargs -I {} sh -c 'echo "File: {}"; wc -l "{}"'

Repository: pengzhanbo/vite-plugin-mock-dev-server

Length of output: 191


🏁 Script executed:

# 搜索 MockFetchOptions 的定义
rg "MockFetchOptions" --type ts -A 3 -B 1

Repository: pengzhanbo/vite-plugin-mock-dev-server

Length of output: 1358


🏁 Script executed:

# 读取整个 fetch.ts 文件,附带行号
cat -n vite-plugin-mock-dev-server/ui/src/utils/fetch.ts

Repository: pengzhanbo/vite-plugin-mock-dev-server

Length of output: 1381


headers 合并方式不兼容 HeadersInit 类型,会导致 Headers 实例或数组形式的请求头丢失或畸形。

当调用者传入 Headers 实例或 [string, string][] 数组时,对象展开 { ...headers } 会失效:Headers 实例展开成空对象,数组展开成数值键。应使用 Headers 构造函数正确处理。

💡 建议修复
 export async function fetchMockApi<T>(input: string, { headers, ...init }: MockFetchOptions = {}): Promise<T> {
+  const mergedHeaders = new Headers(headers)
+  if (!mergedHeaders.has('Content-Type'))
+    mergedHeaders.set('Content-Type', 'application/json')
+
   const res = await fetchMock(input, {
     baseURL: normalize(`${import.meta.env.BASE_URL}/.vite-mock/api`),
-    headers: {
-      'Content-Type': 'application/json',
-      ...headers,
-    },
+    headers: mergedHeaders,
     ...init,
   })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@vite-plugin-mock-dev-server/ui/src/utils/fetch.ts` around lines 21 - 27, The
headers merging in the fetchMockApi function uses object spread syntax which is
incompatible with HeadersInit types like Headers instances or [string, string][]
arrays, causing them to be lost or malformed. Replace the object spread approach
{ 'Content-Type': 'application/json', ...headers } with the Headers constructor
to properly handle all HeadersInit formats: create a new Headers instance, set
the Content-Type header, then append the passed headers using the appropriate
Headers API method that handles all input types correctly.

Comment on lines +1 to +7
export function safeJsonParse<T>(raw: string): T {
try {
return JSON.parse(raw) as T
}
catch {
return {} as T
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

safeJsonParse 在合法但非对象 JSON 下会返回 null/原始值,会导致后续属性访问崩溃。

在 Line 3,JSON.parse(raw)"null""1""true" 不会抛错,当前实现会直接返回这些值;调用方按对象读取字段时会触发运行时错误。建议仅在解析结果为对象时返回,否则回退到安全默认值。

💡 建议修复
 export function safeJsonParse<T>(raw: string): T {
   try {
-    return JSON.parse(raw) as T
+    const parsed = JSON.parse(raw)
+    if (parsed !== null && typeof parsed === 'object')
+      return parsed as T
+    return {} as T
   }
   catch {
     return {} as T
   }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@vite-plugin-mock-dev-server/ui/src/utils/safeParse.ts` around lines 1 - 7,
The safeJsonParse function currently returns the raw result of JSON.parse
without validating that it is actually an object. For valid JSON strings like
"null", "1", or "true", the function will return primitive values instead of
objects, causing crashes when callers try to access properties. Add a type check
after parsing to verify that the result is an object (not null and of type
'object'); only return the parsed value if this check passes, otherwise return
the safe default empty object {} as T. This ensures the function always returns
an object-like value or the fallback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant