Merge pull request #420 from alibaba/refactor/node-and-eslint-upgrade

refactor: upgrade Node and ESLint
This commit is contained in:
Simon
2026-04-08 22:05:06 +08:00
committed by GitHub
23 changed files with 2207 additions and 1390 deletions

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
engine-strict=true

View File

@@ -1,8 +1,5 @@
import eslintReact from '@eslint-react/eslint-plugin'
import js from '@eslint/js' import js from '@eslint/js'
import reactDom from 'eslint-plugin-react-dom'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import reactX from 'eslint-plugin-react-x'
import { defineConfig, globalIgnores } from 'eslint/config' import { defineConfig, globalIgnores } from 'eslint/config'
import globals from 'globals' import globals from 'globals'
import tseslint from 'typescript-eslint' import tseslint from 'typescript-eslint'
@@ -15,44 +12,24 @@ export default defineConfig([
'**/.wxt', '**/.wxt',
'**/.output', '**/.output',
]), ]),
{
plugins: {
'react-hooks': reactHooks,
},
rules: reactHooks.configs.recommended.rules,
},
{ {
files: ['**/*.{ts,tsx}'], files: ['**/*.{ts,tsx}'],
extends: [ extends: [
js.configs.recommended, js.configs.recommended,
tseslint.configs.recommended, tseslint.configs.recommended,
// reactHooks.configs['recommended-latest'],
reactRefresh.configs.vite,
// Remove tseslint.configs.recommended and replace with this
...tseslint.configs.recommendedTypeChecked, ...tseslint.configs.recommendedTypeChecked,
// Alternatively, use this for stricter rules
...tseslint.configs.strictTypeChecked, ...tseslint.configs.strictTypeChecked,
// Optionally, add this for stylistic rules
...tseslint.configs.stylisticTypeChecked, ...tseslint.configs.stylisticTypeChecked,
eslintReact.configs['recommended-typescript'],
// Enable lint rules for React
reactX.configs['recommended-typescript'],
// Enable lint rules for React DOM
reactDom.configs.recommended,
], ],
languageOptions: { languageOptions: {
parserOptions: { parserOptions: {
// project: ['./tsconfig.json'],
// project: ['./packages/*/tsconfig.json'],
// tsconfigRootDir: import.meta.dirname,
projectService: true, projectService: true,
}, },
ecmaVersion: 2020, ecmaVersion: 2020,
globals: globals.browser, globals: globals.browser,
}, },
rules: { rules: {
// Add any additional rules here
'@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off', '@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off', '@typescript-eslint/no-unsafe-member-access': 'off',
@@ -72,14 +49,14 @@ export default defineConfig([
'@typescript-eslint/no-unsafe-argument': 'off', '@typescript-eslint/no-unsafe-argument': 'off',
'@typescript-eslint/no-unsafe-return': 'off', '@typescript-eslint/no-unsafe-return': 'off',
'@typescript-eslint/restrict-plus-operands': 'off', '@typescript-eslint/restrict-plus-operands': 'off',
'react-dom/no-missing-button-type': 'off',
'react-x/no-nested-component-definitions': 'off',
'@typescript-eslint/prefer-optional-chain': 'off', '@typescript-eslint/prefer-optional-chain': 'off',
'@typescript-eslint/use-unknown-in-catch-callback-variable': 'off', '@typescript-eslint/use-unknown-in-catch-callback-variable': 'off',
'@typescript-eslint/no-unnecessary-type-parameters': 'off', '@typescript-eslint/no-unnecessary-type-parameters': 'off',
// 'require-await': 'off',
'@typescript-eslint/require-await': 'off', '@typescript-eslint/require-await': 'off',
'@eslint-react/dom-no-missing-button-type': 'off',
'@eslint-react/no-nested-component-definitions': 'off',
'@eslint-react/no-array-index-key': 'off',
'@eslint-react/dom-no-dangerously-set-innerhtml': 'off',
}, },
}, },
]) ])

3465
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -22,7 +22,7 @@
}, },
"homepage": "https://alibaba.github.io/page-agent/", "homepage": "https://alibaba.github.io/page-agent/",
"engines": { "engines": {
"node": "^20.19.0 || ^22.13.0 || >=24" "node": "^22.13.0 || >=24"
}, },
"scripts": { "scripts": {
"start": "npm run dev --workspace=@page-agent/website", "start": "npm run dev --workspace=@page-agent/website",
@@ -35,12 +35,13 @@
"version": "node scripts/sync-version.js", "version": "node scripts/sync-version.js",
"lint": "eslint .", "lint": "eslint .",
"cleanup": "rm -rf packages/*/dist", "cleanup": "rm -rf packages/*/dist",
"prepare": "husky" "prepare": "husky || true"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^20.5.0", "@commitlint/cli": "^20.5.0",
"@commitlint/config-conventional": "^20.5.0", "@commitlint/config-conventional": "^20.5.0",
"@eslint/js": "^9.39.2", "@eslint-react/eslint-plugin": "^4.2.3",
"@eslint/js": "^10.0.1",
"@microsoft/api-extractor": "^7.58.1", "@microsoft/api-extractor": "^7.58.1",
"@tailwindcss/vite": "^4.2.2", "@tailwindcss/vite": "^4.2.2",
"@trivago/prettier-plugin-sort-imports": "^6.0.2", "@trivago/prettier-plugin-sort-imports": "^6.0.2",
@@ -49,25 +50,21 @@
"chalk": "^5.6.2", "chalk": "^5.6.2",
"concurrently": "^9.2.1", "concurrently": "^9.2.1",
"dotenv": "^17.4.1", "dotenv": "^17.4.1",
"eslint": "^9.39.2", "eslint": "^10.2.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-react-dom": "^2.13.0",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.5.2",
"eslint-plugin-react-x": "^2.13.0",
"globals": "^17.4.0", "globals": "^17.4.0",
"husky": "^9.1.7", "husky": "^9.1.7",
"lint-staged": "^16.4.0", "lint-staged": "^16.4.0",
"prettier": "^3.8.0", "prettier": "^3.8.1",
"typescript": "^5.9.3", "typescript": "^5.9.3",
"typescript-eslint": "^8.58.1", "typescript-eslint": "^8.58.1",
"unplugin-dts": "^1.0.0-beta.6", "unplugin-dts": "^1.0.0-beta.6",
"vite": "^7.3.2", "vite": "^7.3.2",
"vite-plugin-css-injected-by-js": "^4.0.1", "vite-bundle-analyzer": "^1.3.7",
"vite-bundle-analyzer": "^1.3.7" "vite-plugin-css-injected-by-js": "^4.0.1"
}, },
"overrides": { "overrides": {
"typescript": "^5.9.3" "typescript": "^5.9.3",
"@vitejs/plugin-react": "^5.2.0"
}, },
"lint-staged": { "lint-staged": {
"*.{js,ts,cjs,cts,mjs,mts}": [ "*.{js,ts,cjs,cts,mjs,mts}": [

View File

@@ -51,6 +51,6 @@
"zod": "^3.25.0 || ^4.0.0" "zod": "^3.25.0 || ^4.0.0"
}, },
"devDependencies": { "devDependencies": {
"zod": "^4.3.5" "zod": "^4.3.6"
} }
} }

View File

@@ -19,7 +19,7 @@ const log = console.log.bind(console, chalk.yellow('[autoFixer]'))
* - etc. * - etc.
*/ */
export function normalizeResponse(response: any, tools?: Map<string, PageAgentTool>): any { export function normalizeResponse(response: any, tools?: Map<string, PageAgentTool>): any {
let resolvedArguments = null as any let resolvedArguments: any
const choice = (response as { choices?: Choice[] }).choices?.[0] const choice = (response as { choices?: Choice[] }).choices?.[0]
if (!choice) throw new Error('No choices in response') if (!choice) throw new Error('No choices in response')

View File

@@ -16,9 +16,10 @@
"@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-separator": "^1.1.8",
"@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-switch": "^1.2.6", "@radix-ui/react-switch": "^1.2.6",
"@tailwindcss/vite": "^4.2.2",
"@types/chrome": "^0.1.39", "@types/chrome": "^0.1.39",
"@types/react": "^19.2.14", "@types/react": "^19.2.14",
"@types/react-dom": "^19.2.1", "@types/react-dom": "^19.2.3",
"@wxt-dev/module-react": "^1.2.2", "@wxt-dev/module-react": "^1.2.2",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
@@ -32,7 +33,7 @@
"simple-icons": "^16.15.0", "simple-icons": "^16.15.0",
"sonner": "^2.0.7", "sonner": "^2.0.7",
"tailwind-merge": "^3.5.0", "tailwind-merge": "^3.5.0",
"tailwindcss": "^4.1.14", "tailwindcss": "^4.2.2",
"tw-animate-css": "^1.4.0", "tw-animate-css": "^1.4.0",
"wxt": "^0.20.20" "wxt": "^0.20.20"
}, },

View File

@@ -56,7 +56,7 @@ export class RemotePageController {
} }
async getBrowserState(): Promise<BrowserState> { async getBrowserState(): Promise<BrowserState> {
let browserState = {} as BrowserState let browserState: BrowserState
debug('getBrowserState', this.currentTabId) debug('getBrowserState', this.currentTabId)
const currentUrl = await this.getCurrentUrl() const currentUrl = await this.getCurrentUrl()

View File

@@ -49,7 +49,9 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
const [showToken, setShowToken] = useState(false) const [showToken, setShowToken] = useState(false)
const [showApiKey, setShowApiKey] = useState(false) const [showApiKey, setShowApiKey] = useState(false)
useEffect(() => { const [prevConfig, setPrevConfig] = useState(config)
if (prevConfig !== config) {
setPrevConfig(config)
setBaseURL(config?.baseURL || DEMO_BASE_URL) setBaseURL(config?.baseURL || DEMO_BASE_URL)
setModel(config?.model || DEMO_MODEL) setModel(config?.model || DEMO_MODEL)
setApiKey(config?.apiKey) setApiKey(config?.apiKey)
@@ -59,7 +61,7 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
setExperimentalLlmsTxt(config?.experimentalLlmsTxt ?? false) setExperimentalLlmsTxt(config?.experimentalLlmsTxt ?? false)
setExperimentalIncludeAllTabs(config?.experimentalIncludeAllTabs ?? false) setExperimentalIncludeAllTabs(config?.experimentalIncludeAllTabs ?? false)
setDisableNamedToolChoice(config?.disableNamedToolChoice ?? false) setDisableNamedToolChoice(config?.disableNamedToolChoice ?? false)
}, [config]) }
// Poll for user auth token every second until found // Poll for user auth token every second until found
useEffect(() => { useEffect(() => {

View File

@@ -71,7 +71,6 @@ export function HistoryDetail({
{/* Events (read-only) */} {/* Events (read-only) */}
<div className="flex-1 overflow-y-auto p-3 space-y-2"> <div className="flex-1 overflow-y-auto p-3 space-y-2">
{session.history.map((event, index) => ( {session.history.map((event, index) => (
// eslint-disable-next-line react-x/no-array-index-key
<EventCard key={index} event={event} /> <EventCard key={index} event={event} />
))} ))}
</div> </div>

View File

@@ -34,7 +34,6 @@ export function HistoryList({
}, []) }, [])
useEffect(() => { useEffect(() => {
// eslint-disable-next-line react-hooks/set-state-in-effect
load() load()
}, [load]) }, [load])

View File

@@ -133,7 +133,6 @@ export default function App() {
)} )}
{history.map((event, index) => ( {history.map((event, index) => (
// eslint-disable-next-line react-x/no-array-index-key
<EventCard key={index} event={event} /> <EventCard key={index} event={event} />
))} ))}

View File

@@ -206,9 +206,9 @@ export function useHubWs(
const [wsState, setWsState] = useState<HubWsState>(() => (wsPort ? 'connecting' : 'disconnected')) const [wsState, setWsState] = useState<HubWsState>(() => (wsPort ? 'connecting' : 'disconnected'))
const hubWsRef = useRef<HubWs | null>(null) const hubWsRef = useRef<HubWs | null>(null)
const latest = useRef({ execute, stop, configure, config }) const latestRef = useRef({ execute, stop, configure, config })
useEffect(() => { useEffect(() => {
latest.current = { execute, stop, configure, config } latestRef.current = { execute, stop, configure, config }
}) })
useEffect(() => { useEffect(() => {
@@ -218,14 +218,14 @@ export function useHubWs(
Number(wsPort), Number(wsPort),
{ {
onExecute: async (task, incomingConfig) => { onExecute: async (task, incomingConfig) => {
const { execute, configure, config } = latest.current const { execute, configure, config } = latestRef.current
if (incomingConfig) { if (incomingConfig) {
await configure({ ...config, ...incomingConfig } as ExtConfig) await configure({ ...config, ...incomingConfig } as ExtConfig)
} }
const result = await execute(task) const result = await execute(task)
return { success: result.success, data: result.data } return { success: result.success, data: result.data }
}, },
onStop: () => latest.current.stop(), onStop: () => latestRef.current.stop(),
}, },
setWsState setWsState
) )

View File

@@ -178,7 +178,6 @@ export default function App() {
{showEmptyState && <EmptyState />} {showEmptyState && <EmptyState />}
{history.map((event, index) => ( {history.map((event, index) => (
// eslint-disable-next-line react-x/no-array-index-key
<EventCard key={index} event={event} /> <EventCard key={index} event={event} />
))} ))}

View File

@@ -43,6 +43,6 @@
"zod": "^3.25.0 || ^4.0.0" "zod": "^3.25.0 || ^4.0.0"
}, },
"devDependencies": { "devDependencies": {
"zod": "^4.3.5" "zod": "^4.3.6"
} }
} }

View File

@@ -30,6 +30,6 @@
"dependencies": { "dependencies": {
"@modelcontextprotocol/sdk": "^1.29.0", "@modelcontextprotocol/sdk": "^1.29.0",
"ws": "^8.20.0", "ws": "^8.20.0",
"zod": "^4.3.5" "zod": "^4.3.6"
} }
} }

View File

@@ -54,6 +54,6 @@
"zod": "^3.25.0 || ^4.0.0" "zod": "^3.25.0 || ^4.0.0"
}, },
"devDependencies": { "devDependencies": {
"zod": "^4.3.5" "zod": "^4.3.6"
} }
} }

View File

@@ -16,7 +16,7 @@
"@radix-ui/react-switch": "^1.2.6", "@radix-ui/react-switch": "^1.2.6",
"@radix-ui/react-tooltip": "^1.2.8", "@radix-ui/react-tooltip": "^1.2.8",
"@types/react": "^19.2.14", "@types/react": "^19.2.14",
"@types/react-dom": "^19.2.1", "@types/react-dom": "^19.2.3",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"lucide-react": "^1.7.0", "lucide-react": "^1.7.0",
@@ -28,7 +28,7 @@
"simple-icons": "^16.15.0", "simple-icons": "^16.15.0",
"sonner": "^2.0.7", "sonner": "^2.0.7",
"tailwind-merge": "^3.5.0", "tailwind-merge": "^3.5.0",
"tailwindcss": "^4.1.14", "tailwindcss": "^4.2.2",
"tw-animate-css": "^1.4.0", "tw-animate-css": "^1.4.0",
"wouter": "^3.9.0" "wouter": "^3.9.0"
} }

View File

@@ -169,7 +169,6 @@ function highlightSyntax(code: string): string {
const HighlightSyntaxClient: React.FC<HighlightSyntaxProps> = ({ code }) => { const HighlightSyntaxClient: React.FC<HighlightSyntaxProps> = ({ code }) => {
const htmlContent = highlightSyntax(code) const htmlContent = highlightSyntax(code)
// eslint-disable-next-line react-dom/no-dangerously-set-innerhtml
return <code className={styles.syntax} dangerouslySetInnerHTML={{ __html: htmlContent }} /> return <code className={styles.syntax} dangerouslySetInnerHTML={{ __html: htmlContent }} />
} }

View File

@@ -140,6 +140,7 @@ function JSConsole({
// 全局console拦截处理 // 全局console拦截处理
useEffect(() => { useEffect(() => {
const interceptor = ConsoleInterceptor.getInstance() const interceptor = ConsoleInterceptor.getInstance()
let scrollTimer: ReturnType<typeof setTimeout>
const handleGlobalConsole = (type: string, args: unknown[]) => { const handleGlobalConsole = (type: string, args: unknown[]) => {
const content = args.map((arg) => formatResult(arg)).join(' ') const content = args.map((arg) => formatResult(arg)).join(' ')
@@ -152,8 +153,8 @@ function JSConsole({
setOutputs((prev) => [...prev, outputItem]) setOutputs((prev) => [...prev, outputItem])
// 自动滚动到底部 clearTimeout(scrollTimer)
setTimeout(() => { scrollTimer = setTimeout(() => {
if (outputRef.current) { if (outputRef.current) {
outputRef.current.scrollTop = outputRef.current.scrollHeight outputRef.current.scrollTop = outputRef.current.scrollHeight
} }
@@ -164,6 +165,7 @@ function JSConsole({
return () => { return () => {
interceptor.unsubscribe(handleGlobalConsole) interceptor.unsubscribe(handleGlobalConsole)
clearTimeout(scrollTimer)
} }
}, []) }, [])

View File

@@ -9,25 +9,24 @@ const LanguageContext = createContext<{
} | null>(null) } | null>(null)
export function LanguageProvider({ children }: { children: ReactNode }) { export function LanguageProvider({ children }: { children: ReactNode }) {
const [language, setLang] = useState<Lang>(() => { const [language, setLanguage] = useState<Lang>(() => {
const stored = localStorage.getItem('language') as Lang const stored = localStorage.getItem('language') as Lang
if (stored === 'zh-CN' || stored === 'en-US') return stored if (stored === 'zh-CN' || stored === 'en-US') return stored
return navigator.language.startsWith('zh') ? 'zh-CN' : 'en-US' return navigator.language.startsWith('zh') ? 'zh-CN' : 'en-US'
}) })
const setLanguage = (lang: Lang) => { const switchLanguage = (lang: Lang) => {
setLang(lang) setLanguage(lang)
localStorage.setItem('language', lang) localStorage.setItem('language', lang)
} }
return ( return (
<LanguageContext value={{ language, isZh: language === 'zh-CN', setLanguage }}> <LanguageContext value={{ language, isZh: language === 'zh-CN', setLanguage: switchLanguage }}>
{children} {children}
</LanguageContext> </LanguageContext>
) )
} }
// eslint-disable-next-line react-refresh/only-export-components
export function useLanguage() { export function useLanguage() {
const ctx = use(LanguageContext) const ctx = use(LanguageContext)
if (!ctx) throw new Error('useLanguage must be used within LanguageProvider') if (!ctx) throw new Error('useLanguage must be used within LanguageProvider')

View File

@@ -1,4 +1,3 @@
/* eslint-disable react-dom/no-dangerously-set-innerhtml */
import type { PageAgent as PageAgentType } from 'page-agent' import type { PageAgent as PageAgentType } from 'page-agent'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { Link, useSearchParams } from 'wouter' import { Link, useSearchParams } from 'wouter'
@@ -46,10 +45,11 @@ export default function HeroSection() {
: 'Goto docs in navigation bar, find Quick-Start section, and summarize in markdown' : 'Goto docs in navigation bar, find Quick-Start section, and summarize in markdown'
const [task, setTask] = useState(() => defaultTask) const [task, setTask] = useState(() => defaultTask)
const [prevDefaultTask, setPrevDefaultTask] = useState(defaultTask)
useEffect(() => { if (prevDefaultTask !== defaultTask) {
setPrevDefaultTask(defaultTask)
setTask(defaultTask) setTask(defaultTask)
}, [defaultTask]) }
const [params] = useSearchParams() const [params] = useSearchParams()
const isOther = params.has('try_other') const isOther = params.has('try_other')

View File

@@ -18,10 +18,12 @@ function ScrollToTop() {
export default function Router() { export default function Router() {
useEffect(() => { useEffect(() => {
const schedule = globalThis.requestIdleCallback ?? ((cb: () => void) => setTimeout(cb, 1)) if ('requestIdleCallback' in globalThis) {
const cancel = globalThis.cancelIdleCallback ?? clearTimeout const id = requestIdleCallback(() => docsImport())
const id = schedule(() => docsImport()) return () => cancelIdleCallback(id)
return () => cancel(id) }
const id = setTimeout(() => docsImport(), 1)
return () => clearTimeout(id)
}, []) }, [])
return ( return (