feat: use oxlint to speed up lint (#10168)

* build: add eslint-plugin-oxlint dependency

Add new eslint plugin to enhance linting capabilities with oxlint rules

* build(eslint): add oxlint plugin to eslint config

Add oxlint plugin as recommended in the documentation to enhance linting capabilities

* build: add oxlint v1.15.0 as a dependency

* build: add oxlint to linting commands

Add oxlint alongside eslint in test:lint and lint scripts for enhanced static analysis

* build: add oxlint configuration file

Configure oxlint with a comprehensive set of rules for JavaScript/TypeScript code quality checks

* chore: update oxlint configuration and related settings

- Add oxc to editor code actions on save
- Update oxlint configs to use eslint, typescript, and unicorn presets
- Extend ignore patterns in oxlint configuration
- Simplify oxlint command in package.json scripts
- Add oxlint-tsgolint dependency

* fix: lint warning

* chore: update oxlintrc from eslint.recommended

* refactor(lint): update eslint and oxlint configurations

- Add src/preload to eslint ignore patterns
- Update oxlint env to es2022 and add environment overrides
- Adjust several lint rule severities and configurations

* fix: lint error

* fix(file): replace eslint-disable with oxlint-disable in sanitizeFilename

The linter was changed from ESLint to oxlint, so the directive needs to be updated accordingly.

* fix: enforce stricter linting by failing on warnings in test:lint script

* feat: add recommended ts-eslint rules into exlint

* docs: remove outdated comment in oxlint config file

* style: disable typescript/no-require-imports rule in oxlint config

* docs(utils): fix comment typo from NODE to NOTE

* fix(MessageErrorBoundary): correct error description display condition

The error description was incorrectly showing in production and hiding in development. Fix the logic to show detailed errors only in development mode

* chore: add oxc-vscode extension to recommended list

* ci(workflows): reorder format check step in pr-ci.yml

* chore: update yarn.lock
This commit is contained in:
Phantom 2025-09-15 19:42:13 +08:00 committed by GitHub
parent e5ccf68476
commit 4d1d3e316f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
40 changed files with 464 additions and 61 deletions

View File

@ -46,12 +46,12 @@ jobs:
- name: Install Dependencies - name: Install Dependencies
run: yarn install run: yarn install
- name: Format Check
run: yarn format:check
- name: Lint Check - name: Lint Check
run: yarn test:lint run: yarn test:lint
- name: Format Check
run: yarn format:check
- name: Type Check - name: Type Check
run: yarn typecheck run: yarn typecheck

215
.oxlintrc.json Normal file
View File

@ -0,0 +1,215 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"categories": {},
"env": {
"es2022": true
},
"globals": {},
"ignorePatterns": [
"node_modules/**",
"build/**",
"dist/**",
"out/**",
"local/**",
".yarn/**",
".gitignore",
"scripts/cloudflare-worker.js",
"src/main/integration/nutstore/sso/lib/**",
"src/main/integration/cherryin/index.js",
"src/main/integration/nutstore/sso/lib/**",
"src/renderer/src/ui/**",
"packages/**/dist",
"eslint.config.mjs"
],
"overrides": [
// set different env
{
"env": {
"node": true
},
"files": ["src/main/**", "resources/scripts/**", "scripts/**", "playwright.config.ts", "electron.vite.config.ts"]
},
{
"env": {
"browser": true
},
"files": [
"src/renderer/**/*.{ts,tsx}",
"packages/aiCore/**",
"packages/extension-table-plus/**",
"resources/js/**"
]
},
{
"env": {
"node": true,
"vitest": true
},
"files": ["**/__tests__/*.test.{ts,tsx}", "tests/**"]
},
{
"env": {
"browser": true,
"node": true
},
"files": ["src/preload/**"]
}
],
// We don't use the React plugin here because its behavior differs slightly from that of ESLint's React plugin.
"plugins": ["unicorn", "typescript", "oxc", "import"],
"rules": {
"constructor-super": "error",
"for-direction": "error",
"getter-return": "error",
"no-array-constructor": "off",
// "import/no-cycle": "error", // tons of error, bro
"no-async-promise-executor": "error",
"no-caller": "warn",
"no-case-declarations": "error",
"no-class-assign": "error",
"no-compare-neg-zero": "error",
"no-cond-assign": "error",
"no-const-assign": "error",
"no-constant-binary-expression": "error",
"no-constant-condition": "error",
"no-control-regex": "error",
"no-debugger": "error",
"no-delete-var": "error",
"no-dupe-args": "error",
"no-dupe-class-members": "error",
"no-dupe-else-if": "error",
"no-dupe-keys": "error",
"no-duplicate-case": "error",
"no-empty": "error",
"no-empty-character-class": "error",
"no-empty-pattern": "error",
"no-empty-static-block": "error",
"no-eval": "warn",
"no-ex-assign": "error",
"no-extra-boolean-cast": "error",
"no-fallthrough": "warn",
"no-func-assign": "error",
"no-global-assign": "error",
"no-import-assign": "error",
"no-invalid-regexp": "error",
"no-irregular-whitespace": "error",
"no-loss-of-precision": "error",
"no-misleading-character-class": "error",
"no-new-native-nonconstructor": "error",
"no-nonoctal-decimal-escape": "error",
"no-obj-calls": "error",
"no-octal": "error",
"no-prototype-builtins": "error",
"no-redeclare": "error",
"no-regex-spaces": "error",
"no-self-assign": "error",
"no-setter-return": "error",
"no-shadow-restricted-names": "error",
"no-sparse-arrays": "error",
"no-this-before-super": "error",
"no-unassigned-vars": "warn",
"no-undef": "error",
"no-unexpected-multiline": "error",
"no-unreachable": "error",
"no-unsafe-finally": "error",
"no-unsafe-negation": "error",
"no-unsafe-optional-chaining": "error",
"no-unused-expressions": "off", // this rule disallow us to use expression to call function, like `condition && fn()`
"no-unused-labels": "error",
"no-unused-private-class-members": "error",
"no-unused-vars": ["error", { "caughtErrors": "none" }],
"no-useless-backreference": "error",
"no-useless-catch": "error",
"no-useless-escape": "error",
"no-useless-rename": "warn",
"no-with": "error",
"oxc/bad-array-method-on-arguments": "warn",
"oxc/bad-char-at-comparison": "warn",
"oxc/bad-comparison-sequence": "warn",
"oxc/bad-min-max-func": "warn",
"oxc/bad-object-literal-comparison": "warn",
"oxc/bad-replace-all-arg": "warn",
"oxc/const-comparisons": "warn",
"oxc/double-comparisons": "warn",
"oxc/erasing-op": "warn",
"oxc/missing-throw": "warn",
"oxc/number-arg-out-of-range": "warn",
"oxc/only-used-in-recursion": "off", // manually off bacause of existing warning. may turn it on in the future
"oxc/uninvoked-array-callback": "warn",
"require-yield": "error",
"typescript/await-thenable": "warn",
// "typescript/ban-ts-comment": "error",
"typescript/no-array-constructor": "error",
// "typescript/consistent-type-imports": "error",
"typescript/no-array-delete": "warn",
"typescript/no-base-to-string": "warn",
"typescript/no-duplicate-enum-values": "error",
"typescript/no-duplicate-type-constituents": "warn",
"typescript/no-empty-object-type": "off",
"typescript/no-explicit-any": "off", // not safe but too many errors
"typescript/no-extra-non-null-assertion": "error",
"typescript/no-floating-promises": "warn",
"typescript/no-for-in-array": "warn",
"typescript/no-implied-eval": "warn",
"typescript/no-meaningless-void-operator": "warn",
"typescript/no-misused-new": "error",
"typescript/no-misused-spread": "warn",
"typescript/no-namespace": "error",
"typescript/no-non-null-asserted-optional-chain": "off", // it's off now. but may turn it on.
"typescript/no-redundant-type-constituents": "warn",
"typescript/no-require-imports": "off",
"typescript/no-this-alias": "error",
"typescript/no-unnecessary-parameter-property-assignment": "warn",
"typescript/no-unnecessary-type-constraint": "error",
"typescript/no-unsafe-declaration-merging": "error",
"typescript/no-unsafe-function-type": "error",
"typescript/no-unsafe-unary-minus": "warn",
"typescript/no-useless-empty-export": "warn",
"typescript/no-wrapper-object-types": "error",
"typescript/prefer-as-const": "error",
"typescript/prefer-namespace-keyword": "error",
"typescript/require-array-sort-compare": "warn",
"typescript/restrict-template-expressions": "warn",
"typescript/triple-slash-reference": "error",
"typescript/unbound-method": "warn",
"unicorn/no-await-in-promise-methods": "warn",
"unicorn/no-empty-file": "off", // manually off bacause of existing warning. may turn it on in the future
"unicorn/no-invalid-fetch-options": "warn",
"unicorn/no-invalid-remove-event-listener": "warn",
"unicorn/no-new-array": "off", // manually off bacause of existing warning. may turn it on in the future
"unicorn/no-single-promise-in-promise-methods": "warn",
"unicorn/no-thenable": "off", // manually off bacause of existing warning. may turn it on in the future
"unicorn/no-unnecessary-await": "warn",
"unicorn/no-useless-fallback-in-spread": "warn",
"unicorn/no-useless-length-check": "warn",
"unicorn/no-useless-spread": "off", // manually off bacause of existing warning. may turn it on in the future
"unicorn/prefer-set-size": "warn",
"unicorn/prefer-string-starts-ends-with": "warn",
"use-isnan": "error",
"valid-typeof": "error"
},
"settings": {
"jsdoc": {
"augmentsExtendsReplacesDocs": false,
"exemptDestructuredRootsFromChecks": false,
"ignoreInternal": false,
"ignorePrivate": false,
"ignoreReplacesDocs": true,
"implementsReplacesDocs": false,
"overrideReplacesDocs": true,
"tagNamePreference": {}
},
"jsx-a11y": {
"attributes": {},
"components": {},
"polymorphicPropName": null
},
"next": {
"rootDir": []
},
"react": {
"formComponents": [],
"linkComponents": []
}
}
}

View File

@ -3,6 +3,7 @@
"dbaeumer.vscode-eslint", "dbaeumer.vscode-eslint",
"editorconfig.editorconfig", "editorconfig.editorconfig",
"lokalise.i18n-ally", "lokalise.i18n-ally",
"oxc.oxc-vscode",
"biomejs.biome" "biomejs.biome"
] ]
} }

View File

@ -26,6 +26,7 @@
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.biome": "explicit", "source.fixAll.biome": "explicit",
"source.fixAll.eslint": "explicit", "source.fixAll.eslint": "explicit",
"source.fixAll.oxc": "explicit",
"source.organizeImports": "never" "source.organizeImports": "never"
}, },
"editor.formatOnSave": true, "editor.formatOnSave": true,

View File

@ -2,6 +2,7 @@ import tseslint from '@electron-toolkit/eslint-config-ts'
import eslint from '@eslint/js' import eslint from '@eslint/js'
import eslintReact from '@eslint-react/eslint-plugin' import eslintReact from '@eslint-react/eslint-plugin'
import { defineConfig } from 'eslint/config' import { defineConfig } from 'eslint/config'
import oxlint from 'eslint-plugin-oxlint'
import reactHooks from 'eslint-plugin-react-hooks' import reactHooks from 'eslint-plugin-react-hooks'
import simpleImportSort from 'eslint-plugin-simple-import-sort' import simpleImportSort from 'eslint-plugin-simple-import-sort'
import unusedImports from 'eslint-plugin-unused-imports' import unusedImports from 'eslint-plugin-unused-imports'
@ -50,7 +51,7 @@ export default defineConfig([
{ {
// LoggerService Custom Rules - only apply to src directory // LoggerService Custom Rules - only apply to src directory
files: ['src/**/*.{ts,tsx,js,jsx}'], files: ['src/**/*.{ts,tsx,js,jsx}'],
ignores: ['src/**/__tests__/**', 'src/**/__mocks__/**', 'src/**/*.test.*'], ignores: ['src/**/__tests__/**', 'src/**/__mocks__/**', 'src/**/*.test.*', 'src/preload/**'],
rules: { rules: {
'no-restricted-syntax': [ 'no-restricted-syntax': [
process.env.PRCI ? 'error' : 'warn', process.env.PRCI ? 'error' : 'warn',
@ -125,5 +126,9 @@ export default defineConfig([
'src/renderer/src/ui/**', 'src/renderer/src/ui/**',
'packages/**/dist' 'packages/**/dist'
] ]
} },
// turn off oxlint supported rules.
...oxlint.configs['flat/eslint'],
...oxlint.configs['flat/typescript'],
...oxlint.configs['flat/unicorn']
]) ])

View File

@ -63,11 +63,11 @@
"test:ui": "vitest --ui", "test:ui": "vitest --ui",
"test:watch": "vitest", "test:watch": "vitest",
"test:e2e": "yarn playwright test", "test:e2e": "yarn playwright test",
"test:lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts", "test:lint": "oxlint --deny-warnings && eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts",
"test:scripts": "vitest scripts", "test:scripts": "vitest scripts",
"lint": "oxlint --fix && eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix && yarn typecheck && yarn check:i18n",
"format": "biome format --write && biome lint --write", "format": "biome format --write && biome lint --write",
"format:check": "biome format && biome lint", "format:check": "biome format && biome lint",
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix && yarn typecheck && yarn check:i18n",
"prepare": "git config blame.ignoreRevsFile .git-blame-ignore-revs && husky", "prepare": "git config blame.ignoreRevsFile .git-blame-ignore-revs && husky",
"claude": "dotenv -e .env -- claude" "claude": "dotenv -e .env -- claude"
}, },
@ -245,6 +245,7 @@
"emoji-picker-element": "^1.22.1", "emoji-picker-element": "^1.22.1",
"epub": "patch:epub@npm%3A1.3.0#~/.yarn/patches/epub-npm-1.3.0-8325494ffe.patch", "epub": "patch:epub@npm%3A1.3.0#~/.yarn/patches/epub-npm-1.3.0-8325494ffe.patch",
"eslint": "^9.22.0", "eslint": "^9.22.0",
"eslint-plugin-oxlint": "^1.15.0",
"eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-simple-import-sort": "^12.1.1", "eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-unused-imports": "^4.1.4", "eslint-plugin-unused-imports": "^4.1.4",
@ -280,6 +281,8 @@
"notion-helper": "^1.3.22", "notion-helper": "^1.3.22",
"npx-scope-finder": "^1.2.0", "npx-scope-finder": "^1.2.0",
"openai": "patch:openai@npm%3A5.12.2#~/.yarn/patches/openai-npm-5.12.2-30b075401c.patch", "openai": "patch:openai@npm%3A5.12.2#~/.yarn/patches/openai-npm-5.12.2-30b075401c.patch",
"oxlint": "^1.15.0",
"oxlint-tsgolint": "^0.2.0",
"p-queue": "^8.1.0", "p-queue": "^8.1.0",
"pdf-lib": "^1.17.1", "pdf-lib": "^1.17.1",
"pdf-parse": "^1.1.1", "pdf-parse": "^1.1.1",

View File

@ -4,7 +4,7 @@ import { loggerService } from '../../services/LoggerService'
const logger = loggerService.withContext('ApiServerErrorHandler') const logger = loggerService.withContext('ApiServerErrorHandler')
// eslint-disable-next-line @typescript-eslint/no-unused-vars // oxlint-disable-next-line @typescript-eslint/no-unused-vars
export const errorHandler = (err: Error, _req: Request, res: Response, _next: NextFunction) => { export const errorHandler = (err: Error, _req: Request, res: Response, _next: NextFunction) => {
logger.error('API Server Error:', err) logger.error('API Server Error:', err)

View File

@ -11,7 +11,7 @@ import { handleZoomFactor } from '@main/utils/zoom'
import { SpanEntity, TokenUsage } from '@mcp-trace/trace-core' import { SpanEntity, TokenUsage } from '@mcp-trace/trace-core'
import { MIN_WINDOW_HEIGHT, MIN_WINDOW_WIDTH, UpgradeChannel } from '@shared/config/constant' import { MIN_WINDOW_HEIGHT, MIN_WINDOW_WIDTH, UpgradeChannel } from '@shared/config/constant'
import { IpcChannel } from '@shared/IpcChannel' import { IpcChannel } from '@shared/IpcChannel'
import { FileMetadata, Provider, Shortcut, ThemeMode } from '@types' import { FileMetadata, OcrProvider, Provider, Shortcut, SupportedOcrFile, ThemeMode } from '@types'
import checkDiskSpace from 'check-disk-space' import checkDiskSpace from 'check-disk-space'
import { BrowserWindow, dialog, ipcMain, ProxyConfig, session, shell, systemPreferences, webContents } from 'electron' import { BrowserWindow, dialog, ipcMain, ProxyConfig, session, shell, systemPreferences, webContents } from 'electron'
import fontList from 'font-list' import fontList from 'font-list'
@ -827,7 +827,9 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
ipcMain.handle(IpcChannel.CodeTools_Run, codeToolsService.run) ipcMain.handle(IpcChannel.CodeTools_Run, codeToolsService.run)
// OCR // OCR
ipcMain.handle(IpcChannel.OCR_ocr, (_, ...args: Parameters<typeof ocrService.ocr>) => ocrService.ocr(...args)) ipcMain.handle(IpcChannel.OCR_ocr, (_, file: SupportedOcrFile, provider: OcrProvider) =>
ocrService.ocr(file, provider)
)
// CherryIN // CherryIN
ipcMain.handle(IpcChannel.Cherryin_GetSignature, (_, params) => generateSignature(params)) ipcMain.handle(IpcChannel.Cherryin_GetSignature, (_, params) => generateSignature(params))

View File

@ -139,9 +139,9 @@ export async function addFileLoader(
if (jsonParsed) { if (jsonParsed) {
loaderReturn = await ragApplication.addLoader(new JsonLoader({ object: jsonObject }), forceReload) loaderReturn = await ragApplication.addLoader(new JsonLoader({ object: jsonObject }), forceReload)
break
} }
// fallthrough - JSON 解析失败时作为文本处理 // fallthrough - JSON 解析失败时作为文本处理
// oxlint-disable-next-line no-fallthrough 利用switch特性刻意不break
default: default:
// 文本类型处理(默认) // 文本类型处理(默认)
// 如果是其他文本类型且尚未读取文件,则读取文件 // 如果是其他文本类型且尚未读取文件,则读取文件

View File

@ -11,7 +11,7 @@ export enum OdType {
OdtLoader = 'OdtLoader', OdtLoader = 'OdtLoader',
OdsLoader = 'OdsLoader', OdsLoader = 'OdsLoader',
OdpLoader = 'OdpLoader', OdpLoader = 'OdpLoader',
undefined = 'undefined' Undefined = 'undefined'
} }
export class OdLoader<OdType> extends BaseLoader<{ type: string }> { export class OdLoader<OdType> extends BaseLoader<{ type: string }> {

View File

@ -1,4 +1,4 @@
/* eslint-disable no-case-declarations */ /* oxlint-disable no-case-declarations */
// ExportService // ExportService
import { loggerService } from '@logger' import { loggerService } from '@logger'

View File

@ -5,7 +5,7 @@ export class MistralClientManager {
private static instance: MistralClientManager private static instance: MistralClientManager
private client: Mistral | null = null private client: Mistral | null = null
// eslint-disable-next-line @typescript-eslint/no-empty-function // oxlint-disable-next-line @typescript-eslint/no-empty-function
private constructor() {} private constructor() {}
public static getInstance(): MistralClientManager { public static getInstance(): MistralClientManager {

View File

@ -235,7 +235,7 @@ export class ProxyManager {
https.request = this.bindHttpMethod(this.originalHttpsRequest, agent) https.request = this.bindHttpMethod(this.originalHttpsRequest, agent)
} }
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type // oxlint-disable-next-line @typescript-eslint/no-unsafe-function-type
private bindHttpMethod(originalMethod: Function, agent: http.Agent | https.Agent) { private bindHttpMethod(originalMethod: Function, agent: http.Agent | https.Agent) {
return (...args: any[]) => { return (...args: any[]) => {
let url: string | URL | undefined let url: string | URL | undefined

View File

@ -1,13 +1,7 @@
import { isLinux, isWin } from '@main/constant' import { isLinux, isWin } from '@main/constant'
import { loadOcrImage } from '@main/utils/ocr' import { loadOcrImage } from '@main/utils/ocr'
import { OcrAccuracy, recognize } from '@napi-rs/system-ocr' import { OcrAccuracy, recognize } from '@napi-rs/system-ocr'
import { import { ImageFileMetadata, isImageFileMetadata, OcrResult, OcrSystemConfig, SupportedOcrFile } from '@types'
ImageFileMetadata,
isImageFileMetadata as isImageFileMetadata,
OcrResult,
OcrSystemConfig,
SupportedOcrFile
} from '@types'
import { OcrBaseService } from './OcrBaseService' import { OcrBaseService } from './OcrBaseService'

View File

@ -9,7 +9,7 @@ export class FileServiceManager {
private static instance: FileServiceManager private static instance: FileServiceManager
private services: Map<string, BaseFileService> = new Map() private services: Map<string, BaseFileService> = new Map()
// eslint-disable-next-line @typescript-eslint/no-empty-function // oxlint-disable-next-line @typescript-eslint/no-empty-function
private constructor() {} private constructor() {}
static getInstance(): FileServiceManager { static getInstance(): FileServiceManager {

View File

@ -420,7 +420,7 @@ export function sanitizeFilename(fileName: string, replacement = '_'): string {
// 移除或替换非法字符 // 移除或替换非法字符
let sanitized = fileName let sanitized = fileName
// eslint-disable-next-line no-control-regex // oxlint-disable-next-line no-control-regex
.replace(/[<>:"/\\|?*\x00-\x1f]/g, replacement) // Windows 非法字符 .replace(/[<>:"/\\|?*\x00-\x1f]/g, replacement) // Windows 非法字符
.replace(/^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(\.|$)/i, replacement + '$2') // Windows 保留名 .replace(/^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(\.|$)/i, replacement + '$2') // Windows 保留名
.replace(/[\s.]+$/, '') // 移除末尾的空格和点 .replace(/[\s.]+$/, '') // 移除末尾的空格和点

View File

@ -36,13 +36,14 @@ export function debounce(func: (...args: any[]) => void, wait: number, immediate
} }
} }
export function dumpPersistState() { // NOTE: It's an unused function. localStorage should not be accessed in main process.
const persistState = JSON.parse(localStorage.getItem('persist:cherry-studio') || '{}') // export function dumpPersistState() {
for (const key in persistState) { // const persistState = JSON.parse(localStorage.getItem('persist:cherry-studio') || '{}')
persistState[key] = JSON.parse(persistState[key]) // for (const key in persistState) {
} // persistState[key] = JSON.parse(persistState[key])
return JSON.stringify(persistState) // }
} // return JSON.stringify(persistState)
// }
export const runAsyncFunction = async (fn: () => void) => { export const runAsyncFunction = async (fn: () => void) => {
await fn() await fn()

View File

@ -475,13 +475,10 @@ if (process.contextIsolated) {
contextBridge.exposeInMainWorld('electron', electronAPI) contextBridge.exposeInMainWorld('electron', electronAPI)
contextBridge.exposeInMainWorld('api', api) contextBridge.exposeInMainWorld('api', api)
} catch (error) { } catch (error) {
// eslint-disable-next-line no-restricted-syntax
console.error('[Preload]Failed to expose APIs:', error as Error) console.error('[Preload]Failed to expose APIs:', error as Error)
} }
} else { } else {
// @ts-ignore (define in dts)
window.electron = electronAPI window.electron = electronAPI
// @ts-ignore (define in dts)
window.api = api window.api = api
} }

View File

@ -314,7 +314,7 @@ export class AiSdkToChunkAdapter {
// === 源和文件相关事件 === // === 源和文件相关事件 ===
case 'source': case 'source':
if (chunk.sourceType === 'url') { if (chunk.sourceType === 'url') {
// eslint-disable-next-line @typescript-eslint/no-unused-vars // oxlint-disable-next-line @typescript-eslint/no-unused-vars
const { sourceType: _, ...rest } = chunk const { sourceType: _, ...rest } = chunk
final.webSearchResults.push(rest) final.webSearchResults.push(rest)
} }

View File

@ -84,7 +84,7 @@ export abstract class BaseApiClient<
* instanceof检查的类型收窄问题 * instanceof检查的类型收窄问题
* AihubmixAPIClient使 * AihubmixAPIClient使
*/ */
// eslint-disable-next-line @typescript-eslint/no-unused-vars // oxlint-disable-next-line @typescript-eslint/no-unused-vars
public getClientCompatibilityType(_model?: Model): string[] { public getClientCompatibilityType(_model?: Model): string[] {
// 默认返回类的名称 // 默认返回类的名称
return [this.constructor.name] return [this.constructor.name]

View File

@ -177,7 +177,7 @@ export class AnthropicAPIClient extends BaseApiClient<
} }
// @ts-ignore sdk未提供 // @ts-ignore sdk未提供
// eslint-disable-next-line @typescript-eslint/no-unused-vars // oxlint-disable-next-line @typescript-eslint/no-unused-vars
override async generateImage(generateImageParams: GenerateImageParams): Promise<string[]> { override async generateImage(generateImageParams: GenerateImageParams): Promise<string[]> {
return [] return []
} }

View File

@ -455,7 +455,7 @@ export class AwsBedrockAPIClient extends BaseApiClient<
} }
// @ts-ignore sdk未提供 // @ts-ignore sdk未提供
// eslint-disable-next-line @typescript-eslint/no-unused-vars // oxlint-disable-next-line @typescript-eslint/no-unused-vars
override async generateImage(_generateImageParams: GenerateImageParams): Promise<string[]> { override async generateImage(_generateImageParams: GenerateImageParams): Promise<string[]> {
return [] return []
} }

View File

@ -11,7 +11,7 @@ export class PPIOAPIClient extends OpenAIAPIClient {
super(provider) super(provider)
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars // oxlint-disable-next-line @typescript-eslint/no-unused-vars
override getClientCompatibilityType(_model?: Model): string[] { override getClientCompatibilityType(_model?: Model): string[] {
return ['OpenAIAPIClient'] return ['OpenAIAPIClient']
} }

View File

@ -44,7 +44,7 @@ const stringifyArgsForLogging = (args: any[]): string => {
*/ */
export const createGenericLoggingMiddleware: () => MethodMiddleware = () => { export const createGenericLoggingMiddleware: () => MethodMiddleware = () => {
const middlewareName = 'GenericLoggingMiddleware' const middlewareName = 'GenericLoggingMiddleware'
// eslint-disable-next-line @typescript-eslint/no-unused-vars // oxlint-disable-next-line @typescript-eslint/no-unused-vars
return (_: MiddlewareAPI<BaseContext, any[]>) => (next) => async (ctx, args) => { return (_: MiddlewareAPI<BaseContext, any[]>) => (next) => async (ctx, args) => {
const methodName = ctx.methodName const methodName = ctx.methodName
const logPrefix = `[${middlewareName} (${methodName})]` const logPrefix = `[${middlewareName} (${methodName})]`

View File

@ -66,6 +66,7 @@ class AdapterTracer {
spanName: name, spanName: name,
topicId: this.topicId, topicId: this.topicId,
modelName: this.modelName, modelName: this.modelName,
// oxlint-disable-next-line no-undef False alarm. see https://github.com/oxc-project/oxc/issues/4232
argCount: arguments.length argCount: arguments.length
}) })

View File

@ -12,7 +12,7 @@ import {
} from '@ant-design/icons' } from '@ant-design/icons'
import { loggerService } from '@logger' import { loggerService } from '@logger'
import WindowControls from '@renderer/components/WindowControls' import WindowControls from '@renderer/components/WindowControls'
import { isLinux, isMac, isWin } from '@renderer/config/constant' import { isDev, isLinux, isMac, isWin } from '@renderer/config/constant'
import { DEFAULT_MIN_APPS } from '@renderer/config/minapps' import { DEFAULT_MIN_APPS } from '@renderer/config/minapps'
import { useBridge } from '@renderer/hooks/useBridge' import { useBridge } from '@renderer/hooks/useBridge'
import { useMinappPopup } from '@renderer/hooks/useMinappPopup' import { useMinappPopup } from '@renderer/hooks/useMinappPopup'
@ -170,8 +170,6 @@ const MinappPopupContainer: React.FC = () => {
const { isLeftNavbar } = useNavbarPosition() const { isLeftNavbar } = useNavbarPosition()
const isInDevelopment = process.env.NODE_ENV === 'development'
const { setTimeoutTimer } = useTimer() const { setTimeoutTimer } = useTimer()
useBridge() useBridge()
@ -477,7 +475,7 @@ const MinappPopupContainer: React.FC = () => {
<LinkOutlined /> <LinkOutlined />
</TitleButton> </TitleButton>
</Tooltip> </Tooltip>
{isInDevelopment && ( {isDev && (
<Tooltip title={t('minapp.popup.devtools')} mouseEnterDelay={0.8} placement="bottom"> <Tooltip title={t('minapp.popup.devtools')} mouseEnterDelay={0.8} placement="bottom">
<TitleButton onClick={() => handleOpenDevTools(appInfo.id)}> <TitleButton onClick={() => handleOpenDevTools(appInfo.id)}>
<CodeOutlined /> <CodeOutlined />

View File

@ -43,7 +43,7 @@ const CustomTag: FC<CustomTagProps> = ({
...(disabled && { cursor: 'not-allowed' }), ...(disabled && { cursor: 'not-allowed' }),
...style ...style
}}> }}>
{icon && icon} {children} {icon} {children}
{closable && ( {closable && (
<CloseIcon <CloseIcon
$size={size} $size={size}

View File

@ -185,7 +185,7 @@ const KnowledgeCitation: React.FC<{ citation: Citation }> = ({ citation }) => {
<CitationIndex>{citation.number}</CitationIndex> <CitationIndex>{citation.number}</CitationIndex>
{citation.content && <CopyButton content={citation.content} />} {citation.content && <CopyButton content={citation.content} />}
</WebSearchCardHeader> </WebSearchCardHeader>
<WebSearchCardContent className="selectable-text">{citation.content && citation.content}</WebSearchCardContent> <WebSearchCardContent className="selectable-text">{citation.content ?? ''}</WebSearchCardContent>
</WebSearchCard> </WebSearchCard>
</ContextMenu> </ContextMenu>
) )

View File

@ -1,3 +1,4 @@
import { isProd } from '@renderer/config/constant'
import { Alert } from 'antd' import { Alert } from 'antd'
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -17,9 +18,7 @@ const ErrorFallback = ({ fallback, error }: { fallback?: React.ReactNode; error?
// 如果有详细错误信息,添加到描述中 // 如果有详细错误信息,添加到描述中
const errorDescription = const errorDescription =
process.env.NODE_ENV !== 'production' && error !isProd && error ? `${t('error.render.description')}: ${error.message}` : t('error.render.description')
? `${t('error.render.description')}: ${error.message}`
: t('error.render.description')
return fallback || <Alert message={t('error.render.title')} description={errorDescription} type="error" showIcon /> return fallback || <Alert message={t('error.render.title')} description={errorDescription} type="error" showIcon />
} }

View File

@ -8,6 +8,7 @@ import {
PushpinOutlined, PushpinOutlined,
ReloadOutlined ReloadOutlined
} from '@ant-design/icons' } from '@ant-design/icons'
import { isDev } from '@renderer/config/constant'
import { DEFAULT_MIN_APPS } from '@renderer/config/minapps' import { DEFAULT_MIN_APPS } from '@renderer/config/minapps'
import { useMinapps } from '@renderer/hooks/useMinapps' import { useMinapps } from '@renderer/hooks/useMinapps'
import { useSettings } from '@renderer/hooks/useSettings' import { useSettings } from '@renderer/hooks/useSettings'
@ -37,8 +38,6 @@ const MinimalToolbar: FC<Props> = ({ app, webviewRef, currentUrl, onReload, onOp
const navigate = useNavigate() const navigate = useNavigate()
const [canGoBack, setCanGoBack] = useState(false) const [canGoBack, setCanGoBack] = useState(false)
const [canGoForward, setCanGoForward] = useState(false) const [canGoForward, setCanGoForward] = useState(false)
const isInDevelopment = process.env.NODE_ENV === 'development'
const canPinned = DEFAULT_MIN_APPS.some((item) => item.id === app.id) const canPinned = DEFAULT_MIN_APPS.some((item) => item.id === app.id)
const isPinned = pinned.some((item) => item.id === app.id) const isPinned = pinned.some((item) => item.id === app.id)
const canOpenExternalLink = app.url.startsWith('http://') || app.url.startsWith('https://') const canOpenExternalLink = app.url.startsWith('http://') || app.url.startsWith('https://')
@ -139,7 +138,7 @@ const MinimalToolbar: FC<Props> = ({ app, webviewRef, currentUrl, onReload, onOp
</ToolbarButton> </ToolbarButton>
</Tooltip> </Tooltip>
{isInDevelopment && ( {isDev && (
<Tooltip title={t('minapp.popup.devtools')} placement="bottom"> <Tooltip title={t('minapp.popup.devtools')} placement="bottom">
<ToolbarButton onClick={onOpenDevTools}> <ToolbarButton onClick={onOpenDevTools}>
<CodeOutlined /> <CodeOutlined />

View File

@ -298,7 +298,7 @@ const TokenFluxPage: FC<{ Options: string[] }> = ({ Options }) => {
// Set form data from painting's input params // Set form data from painting's input params
if (newPainting.inputParams) { if (newPainting.inputParams) {
// Filter out the prompt from inputParams since it's handled separately // Filter out the prompt from inputParams since it's handled separately
// eslint-disable-next-line @typescript-eslint/no-unused-vars // oxlint-disable-next-line @typescript-eslint/no-unused-vars
const { prompt, ...formInputParams } = newPainting.inputParams const { prompt, ...formInputParams } = newPainting.inputParams
setFormData(formInputParams) setFormData(formInputParams)
} else { } else {

View File

@ -95,7 +95,7 @@ export default class LocalSearchProvider extends BaseWebSearchProvider {
return query return query
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars // oxlint-disable-next-line @typescript-eslint/no-unused-vars
protected parseValidUrls(_htmlContent: string): SearchItem[] { protected parseValidUrls(_htmlContent: string): SearchItem[] {
throw new Error('Not implemented') throw new Error('Not implemented')
} }

View File

@ -8,7 +8,7 @@ export class NotificationQueue {
private queue = new PQueue({ concurrency: 1 }) private queue = new PQueue({ concurrency: 1 })
private listeners: NotificationListener[] = [] private listeners: NotificationListener[] = []
// eslint-disable-next-line @typescript-eslint/no-empty-function // oxlint-disable-next-line @typescript-eslint/no-empty-function
private constructor() {} private constructor() {}
public static getInstance(): NotificationQueue { public static getInstance(): NotificationQueue {

View File

@ -230,7 +230,7 @@ export const formatCitationsFromBlock = (block: CitationMessageBlock | undefined
break break
case WebSearchSource.AISDK: case WebSearchSource.AISDK:
formattedCitations = formattedCitations =
(block.response.results && (block.response.results as AISDKWebSearchResult[]))?.map((result, index) => ({ (block.response?.results as AISDKWebSearchResult[])?.map((result, index) => ({
number: index + 1, number: index + 1,
url: result.url, url: result.url,
title: result.title || new URL(result.url).hostname, title: result.title || new URL(result.url).hostname,

View File

@ -1394,7 +1394,7 @@ const migrateConfig = {
if (state.websearch?.providers) { if (state.websearch?.providers) {
state.websearch.providers = state.websearch.providers.map((provider) => { state.websearch.providers = state.websearch.providers.map((provider) => {
if (provider.id === 'exa' || provider.id === 'tavily') { if (provider.id === 'exa' || provider.id === 'tavily') {
// eslint-disable-next-line @typescript-eslint/no-unused-vars // oxlint-disable-next-line @typescript-eslint/no-unused-vars
const { basicAuthUsername, basicAuthPassword, ...rest } = provider const { basicAuthUsername, basicAuthPassword, ...rest } = provider
return rest return rest
} }

View File

@ -1171,7 +1171,7 @@ export const updateMessageAndBlocksThunk =
try { try {
// 1. 更新 Redux Store // 1. 更新 Redux Store
if (messageUpdates && messageId) { if (messageUpdates && messageId) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars // oxlint-disable-next-line @typescript-eslint/no-unused-vars
const { id: msgId, ...actualMessageChanges } = messageUpdates // Separate ID from actual changes const { id: msgId, ...actualMessageChanges } = messageUpdates // Separate ID from actual changes
// Only dispatch message update if there are actual changes beyond the ID // Only dispatch message update if there are actual changes beyond the ID

View File

@ -1,7 +1,7 @@
import { loggerService } from '@logger' import { loggerService } from '@logger'
import { isQwenMTModel } from '@renderer/config/models' import { isQwenMTModel } from '@renderer/config/models'
import { LANG_DETECT_PROMPT } from '@renderer/config/prompts' import { LANG_DETECT_PROMPT } from '@renderer/config/prompts'
import { builtinLanguages as builtinLanguages, LanguagesEnum, UNKNOWN } from '@renderer/config/translate' import { builtinLanguages, LanguagesEnum, UNKNOWN } from '@renderer/config/translate'
import db from '@renderer/databases' import db from '@renderer/databases'
import i18n from '@renderer/i18n' import i18n from '@renderer/i18n'
import { fetchChatCompletion } from '@renderer/services/ApiService' import { fetchChatCompletion } from '@renderer/services/ApiService'

View File

@ -1,4 +1,4 @@
/* eslint-disable @typescript-eslint/no-empty-function */ /* oslint-disable @typescript-eslint/no-empty-function */
// Simple mock LoggerService class for main process // Simple mock LoggerService class for main process
export class MockMainLoggerService { export class MockMainLoggerService {

View File

@ -1,4 +1,4 @@
/* eslint-disable @typescript-eslint/no-empty-function */ /* oxlint-disable @typescript-eslint/no-empty-function */
// Simple mock LoggerService class for renderer process // Simple mock LoggerService class for renderer process
export class MockRendererLoggerService { export class MockRendererLoggerService {

187
yarn.lock
View File

@ -7225,6 +7225,104 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@oxlint-tsgolint/darwin-arm64@npm:0.2.0":
version: 0.2.0
resolution: "@oxlint-tsgolint/darwin-arm64@npm:0.2.0"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
"@oxlint-tsgolint/darwin-x64@npm:0.2.0":
version: 0.2.0
resolution: "@oxlint-tsgolint/darwin-x64@npm:0.2.0"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
"@oxlint-tsgolint/linux-arm64@npm:0.2.0":
version: 0.2.0
resolution: "@oxlint-tsgolint/linux-arm64@npm:0.2.0"
conditions: os=linux & cpu=arm64
languageName: node
linkType: hard
"@oxlint-tsgolint/linux-x64@npm:0.2.0":
version: 0.2.0
resolution: "@oxlint-tsgolint/linux-x64@npm:0.2.0"
conditions: os=linux & cpu=x64
languageName: node
linkType: hard
"@oxlint-tsgolint/win32-arm64@npm:0.2.0":
version: 0.2.0
resolution: "@oxlint-tsgolint/win32-arm64@npm:0.2.0"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
"@oxlint-tsgolint/win32-x64@npm:0.2.0":
version: 0.2.0
resolution: "@oxlint-tsgolint/win32-x64@npm:0.2.0"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
"@oxlint/darwin-arm64@npm:1.15.0":
version: 1.15.0
resolution: "@oxlint/darwin-arm64@npm:1.15.0"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
"@oxlint/darwin-x64@npm:1.15.0":
version: 1.15.0
resolution: "@oxlint/darwin-x64@npm:1.15.0"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
"@oxlint/linux-arm64-gnu@npm:1.15.0":
version: 1.15.0
resolution: "@oxlint/linux-arm64-gnu@npm:1.15.0"
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
"@oxlint/linux-arm64-musl@npm:1.15.0":
version: 1.15.0
resolution: "@oxlint/linux-arm64-musl@npm:1.15.0"
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
"@oxlint/linux-x64-gnu@npm:1.15.0":
version: 1.15.0
resolution: "@oxlint/linux-x64-gnu@npm:1.15.0"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
"@oxlint/linux-x64-musl@npm:1.15.0":
version: 1.15.0
resolution: "@oxlint/linux-x64-musl@npm:1.15.0"
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
"@oxlint/win32-arm64@npm:1.15.0":
version: 1.15.0
resolution: "@oxlint/win32-arm64@npm:1.15.0"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
"@oxlint/win32-x64@npm:1.15.0":
version: 1.15.0
resolution: "@oxlint/win32-x64@npm:1.15.0"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
"@pdf-lib/standard-fonts@npm:^1.0.0": "@pdf-lib/standard-fonts@npm:^1.0.0":
version: 1.0.0 version: 1.0.0
resolution: "@pdf-lib/standard-fonts@npm:1.0.0" resolution: "@pdf-lib/standard-fonts@npm:1.0.0"
@ -13224,6 +13322,7 @@ __metadata:
emoji-picker-element: "npm:^1.22.1" emoji-picker-element: "npm:^1.22.1"
epub: "patch:epub@npm%3A1.3.0#~/.yarn/patches/epub-npm-1.3.0-8325494ffe.patch" epub: "patch:epub@npm%3A1.3.0#~/.yarn/patches/epub-npm-1.3.0-8325494ffe.patch"
eslint: "npm:^9.22.0" eslint: "npm:^9.22.0"
eslint-plugin-oxlint: "npm:^1.15.0"
eslint-plugin-react-hooks: "npm:^5.2.0" eslint-plugin-react-hooks: "npm:^5.2.0"
eslint-plugin-simple-import-sort: "npm:^12.1.1" eslint-plugin-simple-import-sort: "npm:^12.1.1"
eslint-plugin-unused-imports: "npm:^4.1.4" eslint-plugin-unused-imports: "npm:^4.1.4"
@ -13266,6 +13365,8 @@ __metadata:
officeparser: "npm:^4.2.0" officeparser: "npm:^4.2.0"
openai: "patch:openai@npm%3A5.12.2#~/.yarn/patches/openai-npm-5.12.2-30b075401c.patch" openai: "patch:openai@npm%3A5.12.2#~/.yarn/patches/openai-npm-5.12.2-30b075401c.patch"
os-proxy-config: "npm:^1.1.2" os-proxy-config: "npm:^1.1.2"
oxlint: "npm:^1.15.0"
oxlint-tsgolint: "npm:^0.2.0"
p-queue: "npm:^8.1.0" p-queue: "npm:^8.1.0"
pdf-lib: "npm:^1.17.1" pdf-lib: "npm:^1.17.1"
pdf-parse: "npm:^1.1.1" pdf-parse: "npm:^1.1.1"
@ -17046,6 +17147,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"eslint-plugin-oxlint@npm:^1.15.0":
version: 1.15.0
resolution: "eslint-plugin-oxlint@npm:1.15.0"
dependencies:
jsonc-parser: "npm:^3.3.1"
checksum: 10c0/1c16e4a70fe3bc7ca4cda28393bb00f20d2b009121d5804dc3e9a247adb2773236e542078d6a0f4d4168e4ba68cb88fe5ad8efaee52bec24f72c16b1dad2990c
languageName: node
linkType: hard
"eslint-plugin-react-debug@npm:1.48.1": "eslint-plugin-react-debug@npm:1.48.1":
version: 1.48.1 version: 1.48.1
resolution: "eslint-plugin-react-debug@npm:1.48.1" resolution: "eslint-plugin-react-debug@npm:1.48.1"
@ -19922,6 +20032,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"jsonc-parser@npm:^3.3.1":
version: 3.3.1
resolution: "jsonc-parser@npm:3.3.1"
checksum: 10c0/269c3ae0a0e4f907a914bf334306c384aabb9929bd8c99f909275ebd5c2d3bc70b9bcd119ad794f339dec9f24b6a4ee9cd5a8ab2e6435e730ad4075388fc2ab6
languageName: node
linkType: hard
"jsonfile@npm:^4.0.0": "jsonfile@npm:^4.0.0":
version: 4.0.0 version: 4.0.0
resolution: "jsonfile@npm:4.0.0" resolution: "jsonfile@npm:4.0.0"
@ -22982,6 +23099,76 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"oxlint-tsgolint@npm:^0.2.0":
version: 0.2.0
resolution: "oxlint-tsgolint@npm:0.2.0"
dependencies:
"@oxlint-tsgolint/darwin-arm64": "npm:0.2.0"
"@oxlint-tsgolint/darwin-x64": "npm:0.2.0"
"@oxlint-tsgolint/linux-arm64": "npm:0.2.0"
"@oxlint-tsgolint/linux-x64": "npm:0.2.0"
"@oxlint-tsgolint/win32-arm64": "npm:0.2.0"
"@oxlint-tsgolint/win32-x64": "npm:0.2.0"
dependenciesMeta:
"@oxlint-tsgolint/darwin-arm64":
optional: true
"@oxlint-tsgolint/darwin-x64":
optional: true
"@oxlint-tsgolint/linux-arm64":
optional: true
"@oxlint-tsgolint/linux-x64":
optional: true
"@oxlint-tsgolint/win32-arm64":
optional: true
"@oxlint-tsgolint/win32-x64":
optional: true
bin:
tsgolint: bin/tsgolint.js
checksum: 10c0/b2117a0d07c5c876a6608d710838c934ef456cf7cff668fba9455d380eb8e3a7d9841c8f3a03d59bbc77b0f1342d5dca0e69557cac361a1afa8b8eb3d1b114c6
languageName: node
linkType: hard
"oxlint@npm:^1.15.0":
version: 1.15.0
resolution: "oxlint@npm:1.15.0"
dependencies:
"@oxlint/darwin-arm64": "npm:1.15.0"
"@oxlint/darwin-x64": "npm:1.15.0"
"@oxlint/linux-arm64-gnu": "npm:1.15.0"
"@oxlint/linux-arm64-musl": "npm:1.15.0"
"@oxlint/linux-x64-gnu": "npm:1.15.0"
"@oxlint/linux-x64-musl": "npm:1.15.0"
"@oxlint/win32-arm64": "npm:1.15.0"
"@oxlint/win32-x64": "npm:1.15.0"
peerDependencies:
oxlint-tsgolint: ">=0.2.0"
dependenciesMeta:
"@oxlint/darwin-arm64":
optional: true
"@oxlint/darwin-x64":
optional: true
"@oxlint/linux-arm64-gnu":
optional: true
"@oxlint/linux-arm64-musl":
optional: true
"@oxlint/linux-x64-gnu":
optional: true
"@oxlint/linux-x64-musl":
optional: true
"@oxlint/win32-arm64":
optional: true
"@oxlint/win32-x64":
optional: true
peerDependenciesMeta:
oxlint-tsgolint:
optional: true
bin:
oxc_language_server: bin/oxc_language_server
oxlint: bin/oxlint
checksum: 10c0/3eb2a27b972f2a02200b068345ab6a3a17f7bc29c4546c6b3478727388d8d59b94a554f9b6bb1320b71a75cc598b728de0ffee5e4e70ac27457104b8efebb257
languageName: node
linkType: hard
"p-cancelable@npm:^2.0.0": "p-cancelable@npm:^2.0.0":
version: 2.1.1 version: 2.1.1
resolution: "p-cancelable@npm:2.1.1" resolution: "p-cancelable@npm:2.1.1"