feat: add optional mock config control panel#146
Conversation
✅ Deploy Preview for vite-plugin-mock-dev-server ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
@coderabbitai review |
✅ Action performedReview finished.
|
📝 WalkthroughWalkthrough本 PR 为 ChangesMock Dev Server 可视化 UI
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
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts
Comment |
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (2)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlvite-plugin-mock-dev-server/ui/src/assets/logo.pngis excluded by!**/*.png
📒 Files selected for processing (54)
.vscode/settings.jsondocs/.vitepress/theme/style.csseslint.config.jsexample/mock/maybe-error.mock.tsexample/mock/validator-refererQuery.mock.tsexample/vite.config.tspackage.jsonpnpm-workspace.yamltsconfig.jsonvite-plugin-mock-dev-server/package.jsonvite-plugin-mock-dev-server/src/compiler/compiler.tsvite-plugin-mock-dev-server/src/compiler/processData.tsvite-plugin-mock-dev-server/src/core/init.tsvite-plugin-mock-dev-server/src/core/options.tsvite-plugin-mock-dev-server/src/core/plugin.tsvite-plugin-mock-dev-server/src/mockHttp/request.tsvite-plugin-mock-dev-server/src/types/options.tsvite-plugin-mock-dev-server/src/ui/index.tsvite-plugin-mock-dev-server/src/utils/shared.tsvite-plugin-mock-dev-server/tsdown.config.tsvite-plugin-mock-dev-server/ui/index.htmlvite-plugin-mock-dev-server/ui/src/App.sveltevite-plugin-mock-dev-server/ui/src/app.cssvite-plugin-mock-dev-server/ui/src/components/CodeView.sveltevite-plugin-mock-dev-server/ui/src/components/DelayEditor.sveltevite-plugin-mock-dev-server/ui/src/components/FieldBlock.sveltevite-plugin-mock-dev-server/ui/src/components/FieldInline.sveltevite-plugin-mock-dev-server/ui/src/components/IconMoon.sveltevite-plugin-mock-dev-server/ui/src/components/IconRemove.sveltevite-plugin-mock-dev-server/ui/src/components/IconSun.sveltevite-plugin-mock-dev-server/ui/src/components/IconSunMoon.sveltevite-plugin-mock-dev-server/ui/src/components/KeyValueEditor.sveltevite-plugin-mock-dev-server/ui/src/components/Navbar.sveltevite-plugin-mock-dev-server/ui/src/components/Panel.sveltevite-plugin-mock-dev-server/ui/src/components/PanelItem.sveltevite-plugin-mock-dev-server/ui/src/components/RequestBody.sveltevite-plugin-mock-dev-server/ui/src/components/ResponseView.sveltevite-plugin-mock-dev-server/ui/src/components/Sidebar.sveltevite-plugin-mock-dev-server/ui/src/components/Switch.sveltevite-plugin-mock-dev-server/ui/src/components/Testing.sveltevite-plugin-mock-dev-server/ui/src/lib/appearance.svelte.tsvite-plugin-mock-dev-server/ui/src/lib/data.svelte.tsvite-plugin-mock-dev-server/ui/src/lib/i18n.svelte.tsvite-plugin-mock-dev-server/ui/src/lib/store.svelte.tsvite-plugin-mock-dev-server/ui/src/main.tsvite-plugin-mock-dev-server/ui/src/types/index.tsvite-plugin-mock-dev-server/ui/src/utils/fetch.tsvite-plugin-mock-dev-server/ui/src/utils/keyValue.tsvite-plugin-mock-dev-server/ui/src/utils/safeParse.tsvite-plugin-mock-dev-server/ui/svelte.config.jsvite-plugin-mock-dev-server/ui/tsconfig.app.jsonvite-plugin-mock-dev-server/ui/tsconfig.jsonvite-plugin-mock-dev-server/ui/tsconfig.node.jsonvite-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
| "skipLibCheck": true | ||
| }, | ||
| "include": ["./vite-plugin-mock-dev-server/**/*", "./docs/.vitepress/**/*"], | ||
| "include": ["./vite-plugin-mock-dev-server/src/*", "./docs/.vitepress/**/*"], |
There was a problem hiding this comment.
🧩 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)
PYRepository: 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 -100Repository: 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" | sortRepository: 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}")
PYRepository: 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"])
PYRepository: 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:
- 1: https://github.com/microsoft/TypeScript-Website/blob/v2/packages/tsconfig-reference/copy/en/options/include.md
- 2: https://aka.ms/tsconfig/
- 3: https://www.typescriptlang.org/tsconfig/
- 4: https://github.com/microsoft/TypeScript-Handbook/blob/master/pages/tutorials/tsconfig.json.md
include 模式过窄,会遗漏 src 下的所有嵌套源码文件
Line 17 使用 ./vite-plugin-mock-dev-server/src/* 作为包含模式。根据 TypeScript 的 glob 规则,* 通配符不会跨越目录分隔符,因此该模式仅会匹配 src 目录直接下的 3 个文件(index.ts、server.ts、shim.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.
| "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.
| `${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, |
There was a problem hiding this comment.
缺少 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.
| (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 })) |
There was a problem hiding this comment.
/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.
| (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.
| 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/`)}`)) | ||
| }) |
There was a problem hiding this comment.
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.
| function save() { | ||
| delay = editMode === 'single' ? singleValue : [rangeStart, rangeEnd] | ||
| isEditing = false | ||
| } |
There was a problem hiding this comment.
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.
| 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(), |
There was a problem hiding this comment.
🧩 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]。 ### 正确的实现方式 只需在 fetch 的 headers 中省略 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:
- 1: https://wiki.developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest_API/Using_FormData_Objects
- 2: https://craft.mirego.com/2022-11-03-you-should-never-set-the-multipart-form-data-content-type-manually/
- 3: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest_API/Using_FormData_Objects
- 4: https://github.com/mdn/content/blob/main/files/en-us/learn_web_development/extensions/forms/sending_forms_through_javascript/index.md
- 5: https://openjavascript.info/2022/06/08/how-to-upload-a-file-using-the-fetch-api/
- 6: https://bobbyhadz.com/blog/post-form-data-using-javascript-fetch-api
- 7: https://stackoverflow.com/questions/36067767/how-do-i-upload-a-file-with-the-js-fetch-api
- 8: https://stackoverflow.com/questions/39280438/fetch-missing-boundary-in-multipart-form-data-post
🏁 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.svelteRepository: 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 2Repository: 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.
| 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.
| export function toggleAppearance() { | ||
| store.appearance = map[store.appearance] |
There was a problem hiding this comment.
为外观切换增加非法值兜底,避免状态变成 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.
| 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.
| 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) |
There was a problem hiding this comment.
轮询缺少异常兜底与生命周期控制,存在未处理拒绝和重复轮询风险。
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.
| 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.
| 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, | ||
| }, |
There was a problem hiding this comment.
🧩 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:
- 1: https://docs.deno.com/api/web/~/RequestInit.headers
- 2: Spreading headers may lose data unsplash/unsplash-js#188
- 3: https://stackoverflow.com/questions/72977350/why-js-spread-not-cloning-response-headers
- 4: ctx.fetch() not keeping original headers mswjs/msw#145
- 5: https://developer.mozilla.org/en-US/docs/Web/API/Headers
- 6: http Headers (copy) constructor angular/angular#6845
🏁 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 1Repository: 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.tsRepository: 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.
| export function safeJsonParse<T>(raw: string): T { | ||
| try { | ||
| return JSON.parse(raw) as T | ||
| } | ||
| catch { | ||
| return {} as T | ||
| } |
There was a problem hiding this comment.
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.
Summary by CodeRabbit
Release Notes
New Features
Refactor
Chores