chore: migrate linting to vp lint with ESLint fallback (#38834)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Stephen Zhou 2026-07-13 14:41:53 +08:00 committed by GitHub
parent 12a2628794
commit eb71e47f3b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
191 changed files with 4096 additions and 4501 deletions

3
.github/CODEOWNERS vendored
View File

@ -6,7 +6,8 @@
* @crazywoola @laipz8200
# ESLint suppression file is maintained by autofix.ci pruning.
# Lint bulk suppression baselines.
/oxlint-suppressions.json
/eslint-suppressions.json
# CODEOWNERS file

View File

@ -48,7 +48,10 @@ jobs:
pnpm-workspace.yaml
.nvmrc
vite.config.ts
lint.config.ts
eslint.config.mjs
oxlint-suppressions.json
eslint-suppressions.json
.vscode/**
.github/workflows/autofix.yml
.github/workflows/style.yml
@ -166,10 +169,15 @@ jobs:
if: github.event_name != 'merge_group' && steps.frontend-contract-changes.outputs.any_changed == 'true'
run: pnpm --dir packages/contracts gen-api-contract
- name: ESLint autofix
- name: Oxlint autofix
if: github.event_name != 'merge_group' && steps.web-changes.outputs.any_changed == 'true'
run: |
vp exec eslint --fix --concurrency=2 --prune-suppressions --quiet || true
pnpm lint:oxlint:fix --prune-suppressions --quiet || true
- name: ESLint fallback autofix
if: github.event_name != 'merge_group' && steps.web-changes.outputs.any_changed == 'true'
run: |
pnpm lint:eslint:fix --quiet || true
- name: Format frontend files
if: github.event_name != 'merge_group' && (steps.web-changes.outputs.any_changed == 'true' || steps.frontend-contract-changes.outputs.any_changed == 'true')

View File

@ -46,7 +46,7 @@ jobs:
if: matrix.os == 'depot-ubuntu-24.04'
run: scripts/release-validate-manifest.sh
- name: CI pipeline (typecheck, lint, coverage, build)
- name: CI pipeline (tree, coverage, build)
run: pnpm run ci
- name: Report coverage

View File

@ -75,7 +75,7 @@ jobs:
- 'package.json'
- 'pnpm-lock.yaml'
- 'pnpm-workspace.yaml'
- 'eslint.config.mjs'
- 'lint.config.ts'
- '.npmrc'
- '.nvmrc'
- '.github/workflows/cli-tests.yml'

View File

@ -163,7 +163,10 @@ jobs:
pnpm-workspace.yaml
.nvmrc
vite.config.ts
lint.config.ts
eslint.config.mjs
oxlint-suppressions.json
eslint-suppressions.json
.vscode/**
.github/workflows/autofix.yml
.github/workflows/style.yml
@ -177,30 +180,13 @@ jobs:
if: steps.changed-files.outputs.any_changed == 'true'
run: vp fmt --check
- name: Restore ESLint cache
if: steps.changed-files.outputs.any_changed == 'true'
id: eslint-cache-restore
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: .eslintcache
key: ${{ runner.os }}-eslint-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.mjs', 'packages/dify-ui/eslint.config.mjs', 'web/eslint.config.mjs', 'web/eslint.constants.mjs', 'web/plugins/eslint/**') }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-eslint-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.mjs', 'packages/dify-ui/eslint.config.mjs', 'web/eslint.config.mjs', 'web/eslint.constants.mjs', 'web/plugins/eslint/**') }}-
- name: Style check
if: steps.changed-files.outputs.any_changed == 'true'
run: vp run lint:ci
run: pnpm -w lint:ci
- name: Type check
if: steps.changed-files.outputs.any_changed == 'true'
run: vp run type-check
- name: Save ESLint cache
if: steps.changed-files.outputs.any_changed == 'true' && success() && steps.eslint-cache-restore.outputs.cache-hit != 'true'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: .eslintcache
key: ${{ steps.eslint-cache-restore.outputs.cache-primary-key }}
run: pnpm -w type-check
superlinter:
name: SuperLinter

3
.gitignore vendored
View File

@ -214,6 +214,7 @@ sdks/python-client/dify_client.egg-info
api/.vscode
# vscode Code History Extension
.history
.eslintcache
.idea/
@ -258,7 +259,5 @@ scripts/stress-test/reports/
# Code Agent Folder
.qoder/*
.context/
.eslintcache
# Vitest local reports
web/.vitest-reports/

View File

@ -28,20 +28,10 @@
"oxc.fmt.configPath": "./vite.config.ts",
// Lint fix
"eslint.useFlatConfig": true,
"eslint.validate": ["json", "jsonc", "markdown", "yaml", "toml"],
"editor.codeActionsOnSave": {
"source.fixAll.oxc": "explicit",
"source.fixAll.eslint": "explicit"
},
// Enable ESLint for linted languages
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
"markdown",
"json",
"jsonc",
"yaml",
"toml"
]
}
}

View File

@ -31,7 +31,7 @@ The codebase is split into:
## Language Style
- **Python**: Keep type hints on functions and attributes, and implement relevant special methods (e.g., `__repr__`, `__str__`). Prefer `TypedDict` over `dict` or `Mapping` for type safety and better code documentation.
- **TypeScript**: Use the strict config, format with `vp fmt`, run ESLint for code-quality checks and fixes, run `pnpm type-check`, and avoid `any` types.
- **TypeScript**: Use the strict config, format with `vp fmt`, run `pnpm lint` for Oxlint code checks plus ESLint non-code checks, run `pnpm type-check`, and avoid `any` types.
## General Practices

View File

@ -1,6 +1,6 @@
# AGENTS.md — difyctl (TypeScript CLI)
TypeScript port of difyctl. Stack: custom CLI framework (`src/framework/`), Node 22+, ESM, ky for HTTP, Vitest, Vite+ formatting, and ESLint via `@antfu/eslint-config`.
TypeScript port of difyctl. Stack: custom CLI framework (`src/framework/`), Node 22+, ESM, ky for HTTP, Vitest, and Vite+ formatting and linting.
> Architecture patterns, scaffolding recipe, printer chain, strategy pattern, testing conventions, anti-patterns: see **[`ARD.md`]**.
@ -57,9 +57,9 @@ pnpm install # one-time
pnpm dev <command> [args...] # run CLI from source (no -- separator)
pnpm test # vitest
pnpm test:coverage # with coverage
pnpm type-check # tsc, no emit
pnpm lint # eslint
pnpm lint:fix # eslint semantic fixes
pnpm -w type-check # repository-wide type check
pnpm -w lint # repository-wide lint
pnpm -w lint:fix # repository-wide lint fixes
vp fmt # format with Oxfmt
pnpm build # production bundle (vp pack)
pnpm tree:gen # regenerate src/commands/tree.ts (registry)
@ -72,7 +72,7 @@ Release binaries (5 platform targets, Bun-compiled) are produced by `pnpm build:
- Behavior tests run against real Hono mock at `test/fixtures/dify-mock/`. No `nock`, `msw`, or `fetchMock` — every test exercises real HTTP.
- Test files co-located: `foo.test.ts` next to `foo.ts`.
- Type-check, lint, full test suite must be green before any commit.
- Repository-wide type-check and lint, plus the full test suite, must be green before any commit.
## Spec docs (`docs/specs/`)
@ -91,7 +91,7 @@ Behavior contracts. Living tree — amended in place, no version subfolders.
Do not modify in passing:
- `test/fixtures/dify-mock/` public surface (endpoints, JSON shapes, status codes, scenario names) — that's the dify-api contract.
- `bin/`, `scripts/`, `Makefile`, `eslint.config.js`, `tsconfig*.json`, `package.json` (unless the change is required by the task).
- `bin/`, `scripts/`, `Makefile`, `lint.config.ts`, `tsconfig*.json`, `package.json` (unless the change is required by the task).
## Commits

View File

@ -301,19 +301,19 @@ expect(JSON.parse(out).workspaces).toHaveLength(2)
## Scripts
| Command | When to run |
| ----------------------- | -------------------------------------------------- |
| `pnpm dev <cmd> [args]` | Run CLI from source during dev |
| `pnpm test` | Full vitest suite — run before every commit |
| `pnpm test:coverage` | Coverage report |
| `pnpm type-check` | `tsc --noEmit` — catches type errors without build |
| `pnpm lint` | ESLint check |
| `pnpm lint:fix` | ESLint code-quality fixes |
| `vp fmt` | Oxfmt formatting and import sorting |
| `pnpm build` | Production bundle (`vp pack`) |
| `pnpm tree:gen` | Regenerate `src/commands/tree.ts` (registry) |
| `pnpm tree:check` | Verify `tree.ts` matches the filesystem |
| `pnpm build:bin` | Cross-compile standalone binaries via Bun (CI) |
| Command | When to run |
| ----------------------- | ---------------------------------------------- |
| `pnpm dev <cmd> [args]` | Run CLI from source during dev |
| `pnpm test` | Full vitest suite — run before every commit |
| `pnpm test:coverage` | Coverage report |
| `pnpm -w type-check` | Repository-wide TypeScript type check |
| `pnpm -w lint` | Repository-wide Oxlint and ESLint checks |
| `pnpm -w lint:fix` | Repository-wide Oxlint and ESLint fixes |
| `vp fmt` | Oxfmt formatting and import sorting |
| `pnpm build` | Production bundle (`vp pack`) |
| `pnpm tree:gen` | Regenerate `src/commands/tree.ts` (registry) |
| `pnpm tree:check` | Verify `tree.ts` matches the filesystem |
| `pnpm build:bin` | Cross-compile standalone binaries via Bun (CI) |
**`pnpm tree:gen` rule:** run after adding, removing, renaming any command. The generated `tree.ts` is the runtime command registry — stale tree causes commands to be invisible at runtime. (Runs implicitly via `prebuild`/`predev`/`pretest`.)
@ -323,7 +323,7 @@ expect(JSON.parse(out).workspaces).toHaveLength(2)
## Lint rules that catch contributors
Repo runs `@antfu/eslint-config` for code-quality rules and Vite+ Oxfmt for formatting.
The repository runs Vite+ Oxlint as the primary code-quality linter, an explicit ESLint config for unsupported cases, and Vite+ Oxfmt for formatting. The fallback config does not depend on the Antfu ESLint config.
| Rule | What it catches |
| ---------------------------------- | -------------------------------------------------- |
@ -333,7 +333,7 @@ Repo runs `@antfu/eslint-config` for code-quality rules and Vite+ Oxfmt for form
| `unicorn/no-new-array` | Use `Array.from({ length: n })` not `new Array(n)` |
| `noUncheckedIndexedAccess` (tsc) | `arr[i]` is `T \| undefined`; guard before use |
Run `pnpm lint:fix` for ESLint fixes, then `vp fmt` so Oxfmt produces the final layout.
Run `pnpm -w lint:fix` for Oxlint and ESLint fixes, then `vp fmt` so Oxfmt produces the final layout.
---

View File

@ -1,100 +0,0 @@
// @ts-check
import antfu, { GLOB_MARKDOWN } from '@antfu/eslint-config'
import md from 'eslint-markdown'
import markdownPreferences from 'eslint-plugin-markdown-preferences'
export default antfu(
{
stylistic: false,
perfectionist: {
overrides: {
'perfectionist/sort-imports': 'off',
},
},
jsonc: {
overrides: {
'jsonc/space-unary-ops': 'off',
},
},
yaml: {
overrides: {
'yaml/block-mapping': 'off',
'yaml/block-sequence': 'off',
'yaml/plain-scalar': 'off',
},
},
toml: {
overrides: {
'toml/comma-style': 'off',
'toml/no-space-dots': 'off',
},
},
ignores: (original) => ['context/**', 'docs/**', 'dist/**', 'coverage/**', ...original],
typescript: {
overrides: {
'ts/consistent-type-definitions': ['error', 'type'],
'ts/no-explicit-any': 'error',
'ts/no-redeclare': 'off',
},
erasableOnly: true,
},
test: {
overrides: {
'test/prefer-lowercase-title': 'off',
},
},
e18e: false,
},
{
files: [GLOB_MARKDOWN],
plugins: {
md,
'markdown-preferences': markdownPreferences,
},
rules: {
'md/no-url-trailing-slash': 'error',
'markdown-preferences/definitions-last': 'error',
'markdown-preferences/prefer-link-reference-definitions': [
'error',
{
minLinks: 1,
},
],
'markdown-preferences/sort-definitions': 'error',
},
},
{
rules: {
'node/prefer-global/process': 'off',
'unicorn/number-literal-case': 'off',
},
},
{
files: ['bin/**'],
rules: {
'antfu/no-top-level-await': 'off',
},
},
{
files: ['src/**/*.ts'],
rules: {
'no-restricted-imports': [
'error',
{
patterns: [
{
group: ['../**', './*/**', '..'],
message:
'Use the @/ (or @test/) alias for parent-directory or nested relative imports; keep ./ only for same-folder siblings.',
},
],
},
],
},
},
).override('antfu/sort/package-json', {
rules: {
'jsonc/sort-keys': 'off',
},
})

View File

@ -23,15 +23,13 @@
"test:e2e": "vp test --config vitest.e2e.config.ts",
"test:e2e:smoke": "vp test --config vitest.e2e.config.ts --testNamePattern \"\\[P0\\]\"",
"test:e2e:local": "DIFY_E2E_MODE=local vp test --config vitest.e2e.config.ts",
"lint": "eslint",
"lint:fix": "eslint --fix",
"type-check": "tsc",
"tree:gen": "bun scripts/generate-command-tree.ts",
"tree:check": "bun scripts/generate-command-tree.ts --check",
"prebuild": "pnpm tree:gen",
"predev": "pnpm tree:gen",
"pretest": "pnpm tree:gen",
"ci": "pnpm tree:check && pnpm type-check && pnpm lint && pnpm test:coverage && pnpm build",
"ci": "pnpm tree:check && pnpm test:coverage && pnpm build",
"clean": "rm -rf dist node_modules/.cache",
"version:info": "bun scripts/print-buildinfo.ts",
"build:bin": "scripts/release-build.sh"
@ -60,7 +58,6 @@
"@types/node": "catalog:",
"@typescript/native": "catalog:",
"@vitest/coverage-v8": "catalog:",
"eslint": "catalog:",
"hono": "catalog:",
"typescript": "catalog:",
"vite": "catalog:",

View File

@ -26,7 +26,7 @@ function assetVersion(name: string, target: string): string {
// offline. Routes by URL; release bodies come from env (TAG_<tag-with-._->_>),
// the latest release from LATEST_JSON, the listing from LIST_JSON. A missing
// fixture returns 22 to mimic `curl -f` on a 4xx.
/* eslint-disable no-template-curly-in-string -- shell parameter expansions, not JS template literals */
/* oxlint-disable no-template-curly-in-string -- shell parameter expansions, not JS template literals */
const FETCH_STUB = [
'fetch_json() {',
' case "$1" in',
@ -42,7 +42,7 @@ const FETCH_STUB = [
' esac',
'}',
].join('\n')
/* eslint-enable no-template-curly-in-string */
/* oxlint-enable no-template-curly-in-string */
function runLib(
program: string,

View File

@ -241,7 +241,7 @@ describe('run() catch routing', () => {
it('wraps a non-Error throw via String() coercion into unknown form', async () => {
class Throwing extends Command {
async run(_argv: string[]) {
// eslint-disable-next-line no-throw-literal
// oxlint-disable-next-line no-throw-literal
throw 'plain string'
}
}

View File

@ -29,7 +29,7 @@
*/
import type { AuthFixture } from '../../helpers/cli.js'
import { afterEach, beforeEach, describe, expect, it, inject } from 'vitest'
import { afterEach, beforeEach, describe, expect, inject, it } from 'vitest'
import {
assertErrorEnvelope,
assertExitCode,
@ -39,7 +39,7 @@ import {
assertPipeFriendlyJson,
} from '../../helpers/assert.js'
import { withAuthFixture, withTempConfig } from '../../helpers/cli.js'
import { loadE2EEnv, resolveEnv } from '../../setup/env.js'
import { resolveEnv } from '../../setup/env.js'
// @ts-expect-error — see test/e2e/helpers/vitest-context.ts for explanation
const caps = inject('e2eCapabilities') as import('../../setup/env.js').E2ECapabilities

View File

@ -38,7 +38,7 @@
*/
import type { AuthFixture } from '../../helpers/cli.js'
import { afterEach, beforeEach, describe, expect, it, inject } from 'vitest'
import { afterEach, beforeEach, describe, expect, inject, it } from 'vitest'
import { assertExitCode, assertNoAnsi } from '../../helpers/assert.js'
import { withAuthFixture } from '../../helpers/cli.js'
import { resolveEnv } from '../../setup/env.js'
@ -147,7 +147,7 @@ describe('E2E / table output — header and column format (spec 5.15.19)', ()
assertExitCode(result, 0)
// No NUL, BEL, BS, VT, FF, SOUS, DEL bytes that would corrupt a pipe
// eslint-disable-next-line no-control-regex
expect(result.stdout).not.toMatch(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/)
expect(result.stdout).not.toMatch(/[\x00-\x08\v\f\x0E-\x1F\x7F]/)
})
it('[P0] 5.16 default table output written to a file contains no control characters', async () => {
@ -156,7 +156,7 @@ describe('E2E / table output — header and column format (spec 5.15.19)', ()
assertExitCode(result, 0)
assertNoAnsi(result.stdout, 'stdout')
// eslint-disable-next-line no-control-regex
expect(result.stdout).not.toMatch(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/)
expect(result.stdout).not.toMatch(/[\x00-\x08\v\f\x0E-\x1F\x7F]/)
})
// ── 5.25 — Performance ────────────────────────────────────────────────────

View File

@ -1,82 +1,410 @@
// @ts-check
import antfu, { GLOB_MARKDOWN } from '@antfu/eslint-config'
import markdown from '@eslint/markdown'
import md from 'eslint-markdown'
import hyoban from 'eslint-plugin-hyoban'
import jsonc from 'eslint-plugin-jsonc'
import markdownPreferences from 'eslint-plugin-markdown-preferences'
import pnpm from 'eslint-plugin-pnpm'
import toml from 'eslint-plugin-toml'
import yml from 'eslint-plugin-yml'
import { defineConfig, globalIgnores } from 'eslint/config'
import dify from './web/plugins/eslint/index.js'
const GENERATED_IGNORES = [
'**/storybook-static/',
'**/.next/',
'**/.vinext/',
'web/next/',
'web/next-env.d.ts',
'**/dist/',
'**/coverage/',
'e2e/.auth/',
'e2e/cucumber-report/',
const codeFiles = '**/*.{js,cjs,mjs,jsx,ts,cts,mts,tsx}'
/**
* Migration tradeoff: ESLint is intentionally restricted to non-code files.
*
* Disabled code-only checks:
* - Core: `no-control-regex`, `no-octal`, `no-octal-escape`, `no-undef-init`,
* `no-unreachable-loop`, and `one-var`.
* - JavaScript: `dot-notation`.
* - Declarations: the former 223-rule snapshot and CLI `no-restricted-imports` override.
* - Dify UI: the three `better-tailwindcss` rules and stricter unused-directive severity.
*
* These checks are documentation only and do not appear in the executable config below.
*/
const tsconfigCompilerOptionsOrder = [
'incremental',
'composite',
'tsBuildInfoFile',
'disableSourceOfProjectReferenceRedirect',
'disableSolutionSearching',
'disableReferencedProjectLoad',
'target',
'jsx',
'jsxFactory',
'jsxFragmentFactory',
'jsxImportSource',
'lib',
'moduleDetection',
'noLib',
'reactNamespace',
'useDefineForClassFields',
'emitDecoratorMetadata',
'experimentalDecorators',
'libReplacement',
'baseUrl',
'rootDir',
'rootDirs',
'customConditions',
'module',
'moduleResolution',
'moduleSuffixes',
'noResolve',
'paths',
'resolveJsonModule',
'resolvePackageJsonExports',
'resolvePackageJsonImports',
'typeRoots',
'types',
'allowArbitraryExtensions',
'allowImportingTsExtensions',
'allowUmdGlobalAccess',
'allowJs',
'checkJs',
'maxNodeModuleJsDepth',
'strict',
'strictBindCallApply',
'strictFunctionTypes',
'strictNullChecks',
'strictPropertyInitialization',
'allowUnreachableCode',
'allowUnusedLabels',
'alwaysStrict',
'exactOptionalPropertyTypes',
'noFallthroughCasesInSwitch',
'noImplicitAny',
'noImplicitOverride',
'noImplicitReturns',
'noImplicitThis',
'noPropertyAccessFromIndexSignature',
'noUncheckedIndexedAccess',
'noUnusedLocals',
'noUnusedParameters',
'useUnknownInCatchVariables',
'declaration',
'declarationDir',
'declarationMap',
'downlevelIteration',
'emitBOM',
'emitDeclarationOnly',
'importHelpers',
'importsNotUsedAsValues',
'inlineSourceMap',
'inlineSources',
'mapRoot',
'newLine',
'noEmit',
'noEmitHelpers',
'noEmitOnError',
'outDir',
'outFile',
'preserveConstEnums',
'preserveValueImports',
'removeComments',
'sourceMap',
'sourceRoot',
'stripInternal',
'allowSyntheticDefaultImports',
'esModuleInterop',
'forceConsistentCasingInFileNames',
'isolatedDeclarations',
'isolatedModules',
'preserveSymlinks',
'verbatimModuleSyntax',
'erasableSyntaxOnly',
'skipDefaultLibCheck',
'skipLibCheck',
]
export default antfu(
{
stylistic: false,
perfectionist: {
overrides: {
'perfectionist/sort-imports': 'off',
},
},
jsonc: {
overrides: {
'jsonc/space-unary-ops': 'off',
},
},
yaml: {
overrides: {
'yaml/block-mapping': 'off',
'yaml/block-sequence': 'off',
'yaml/plain-scalar': 'off',
},
},
toml: {
overrides: {
'toml/comma-style': 'off',
'toml/no-space-dots': 'off',
},
},
ignores: (original) => [
'**',
'!packages/**',
'!web/**',
'!e2e/**',
const pnpmWorkspaceOrder = [
'cacheDir',
'catalogMode',
'cleanupUnusedCatalogs',
'dedupeDirectDeps',
'deployAllFiles',
'enablePrePostScripts',
'engineStrict',
'extendNodePath',
'hoist',
'hoistPattern',
'hoistWorkspacePackages',
'ignoreCompatibilityDb',
'ignoreDepScripts',
'ignoreScripts',
'ignoreWorkspaceRootCheck',
'managePackageManagerVersions',
'minimumReleaseAge',
'minimumReleaseAgeExclude',
'modulesDir',
'nodeLinker',
'nodeVersion',
'optimisticRepeatInstall',
'packageManagerStrict',
'packageManagerStrictVersion',
'preferSymlinkedExecutables',
'preferWorkspacePackages',
'publicHoistPattern',
'registrySupportsTimeField',
'requiredScripts',
'resolutionMode',
'savePrefix',
'scriptShell',
'shamefullyHoist',
'shellEmulator',
'stateDir',
'supportedArchitectures',
'symlink',
'tag',
'trustPolicy',
'trustPolicyExclude',
'updateNotifier',
'packages',
'overrides',
'patchedDependencies',
'catalog',
'catalogs',
'allowedDeprecatedVersions',
'allowNonAppliedPatches',
'configDependencies',
'ignoredBuiltDependencies',
'ignoredOptionalDependencies',
'neverBuiltDependencies',
'onlyBuiltDependencies',
'onlyBuiltDependenciesFile',
'packageExtensions',
'peerDependencyRules',
]
export default defineConfig([
globalIgnores(
[
'**/*',
'!cli/',
'!cli/**/*',
'!e2e/',
'!e2e/**/*',
'!packages/',
'!packages/**/*',
'!sdks/',
'!sdks/nodejs-client/',
'!sdks/nodejs-client/src/',
'!sdks/nodejs-client/src/**/*',
'!sdks/nodejs-client/tests/',
'!sdks/nodejs-client/tests/**/*',
'!web/',
'!web/**/*',
'!eslint.config.mjs',
'!lint.config.ts',
'!package.json',
'!pnpm-workspace.yaml',
'!vite.config.ts',
...GENERATED_IGNORES,
...original,
],
typescript: {
overrides: {
'ts/consistent-type-definitions': ['error', 'type'],
'ts/no-explicit-any': 'error',
'ts/no-redeclare': 'off',
},
erasableOnly: true,
'Project lint scope',
),
globalIgnores([codeFiles], 'Migration tradeoff: code files are handled by Oxlint only'),
globalIgnores(
[
'**/.git/**',
'**/node_modules/**',
'cli/context/**',
'cli/coverage/**',
'cli/dist/**',
'cli/docs/**',
'**/.next/**',
'**/.vinext/**',
'**/coverage/**',
'**/dist/**',
'**/storybook-static/**',
'e2e/.auth/**',
'e2e/cucumber-report/**',
'packages/contracts/**',
'web/next/**',
'web/next-env.d.ts',
'web/public/**',
'web/types/doc-paths.ts',
],
'Generated and external files',
),
{
linterOptions: {
reportUnusedDisableDirectives: 'warn',
},
test: {
overrides: {
'test/prefer-lowercase-title': 'off',
},
},
e18e: false,
},
{
files: [GLOB_MARKDOWN],
files: ['**/*.{json,json5,jsonc}'],
language: 'jsonc/x',
plugins: {
md,
'markdown-preferences': markdownPreferences,
jsonc,
},
rules: {
'md/no-url-trailing-slash': 'error',
'no-unused-expressions': 'off',
'no-unused-vars': 'off',
strict: 'off',
'jsonc/no-bigint-literals': 'error',
'jsonc/no-binary-expression': 'error',
'jsonc/no-binary-numeric-literals': 'error',
'jsonc/no-dupe-keys': 'error',
'jsonc/no-escape-sequence-in-identifier': 'error',
'jsonc/no-floating-decimal': 'error',
'jsonc/no-hexadecimal-numeric-literals': 'error',
'jsonc/no-infinity': 'error',
'jsonc/no-multi-str': 'error',
'jsonc/no-nan': 'error',
'jsonc/no-number-props': 'error',
'jsonc/no-numeric-separators': 'error',
'jsonc/no-octal': 'error',
'jsonc/no-octal-escape': 'error',
'jsonc/no-octal-numeric-literals': 'error',
'jsonc/no-parenthesized': 'error',
'jsonc/no-plus-sign': 'error',
'jsonc/no-regexp-literals': 'error',
'jsonc/no-sparse-arrays': 'error',
'jsonc/no-template-literals': 'error',
'jsonc/no-undefined-value': 'error',
'jsonc/no-unicode-codepoint-escapes': 'error',
'jsonc/no-useless-escape': 'error',
'jsonc/valid-json-number': 'error',
'jsonc/vue-custom-block/no-parsing-error': 'error',
},
},
{
files: ['**/package.json'],
rules: {
'jsonc/sort-array-values': [
'error',
{
order: { type: 'asc' },
pathPattern: '^files$',
},
],
},
},
{
files: ['**/[jt]sconfig.json', '**/[jt]sconfig.*.json'],
rules: {
'jsonc/sort-keys': [
'error',
{
order: ['extends', 'compilerOptions', 'references', 'files', 'include', 'exclude'],
pathPattern: '^$',
},
{
order: tsconfigCompilerOptionsOrder,
pathPattern: '^compilerOptions$',
},
],
},
},
{
files: ['package.json', '**/package.json'],
plugins: {
pnpm,
},
rules: {
'pnpm/json-enforce-catalog': [
'error',
{
autofix: true,
ignores: ['@types/vscode'],
},
],
'pnpm/json-prefer-workspace-settings': ['error', { autofix: true }],
'pnpm/json-valid-catalog': ['error', { autofix: true }],
},
},
{
files: ['**/*.{yml,yaml}'],
language: 'yml/yaml',
plugins: {
yml,
},
rules: {
'no-irregular-whitespace': 'off',
'no-unused-vars': 'off',
'spaced-comment': 'off',
'yml/no-empty-key': 'error',
'yml/no-empty-sequence-entry': 'error',
'yml/no-irregular-whitespace': 'error',
'yml/vue-custom-block/no-parsing-error': 'error',
},
},
{
files: ['pnpm-workspace.yaml'],
plugins: {
pnpm,
},
rules: {
'pnpm/yaml-enforce-settings': [
'error',
{
settings: {
shellEmulator: true,
trustPolicy: 'no-downgrade',
},
},
],
'pnpm/yaml-no-duplicate-catalog-item': 'error',
'pnpm/yaml-no-unused-catalog-item': 'error',
'yml/sort-keys': [
'error',
{
order: pnpmWorkspaceOrder,
pathPattern: '^$',
},
{
order: { type: 'asc' },
pathPattern: '.*',
},
],
},
},
{
files: ['**/*.toml'],
language: 'toml/toml',
plugins: {
toml,
},
rules: {
'no-irregular-whitespace': 'off',
'spaced-comment': 'off',
'toml/keys-order': 'error',
'toml/no-unreadable-number-separator': 'error',
'toml/precision-of-fractional-seconds': 'error',
'toml/precision-of-integer': 'error',
'toml/tables-order': 'error',
'toml/vue-custom-block/no-parsing-error': 'error',
},
},
{
files: ['**/*.md'],
language: 'markdown/gfm',
plugins: {
markdown,
'markdown-preferences': markdownPreferences,
md,
},
rules: {
'markdown/fenced-code-language': 'off',
'markdown/heading-increment': 'error',
'markdown/no-duplicate-definitions': 'error',
'markdown/no-empty-definitions': 'error',
'markdown/no-empty-images': 'error',
'markdown/no-empty-links': 'error',
'markdown/no-invalid-label-refs': 'error',
'markdown/no-missing-atx-heading-space': 'error',
'markdown/no-missing-label-refs': 'off',
'markdown/no-missing-link-fragments': 'error',
'markdown/no-multiple-h1': 'error',
'markdown/no-reference-like-urls': 'error',
'markdown/no-reversed-media-syntax': 'error',
'markdown/no-space-in-emphasis': 'error',
'markdown/no-unused-definitions': 'error',
'markdown/require-alt-text': 'error',
'markdown/table-column-count': 'error',
'markdown-preferences/definitions-last': 'error',
'markdown-preferences/prefer-link-reference-definitions': [
'error',
@ -85,16 +413,20 @@ export default antfu(
},
],
'markdown-preferences/sort-definitions': 'error',
'md/no-url-trailing-slash': 'error',
},
},
{
files: ['web/i18n/**/*.json'],
plugins: {
dify,
hyoban,
},
rules: {
'node/prefer-global/process': 'off',
'unicorn/number-literal-case': 'off',
'dify/consistent-placeholders': 'error',
'dify/no-extra-keys': 'error',
'hyoban/i18n-flat-key': 'error',
'jsonc/sort-keys': 'error',
},
},
).override('antfu/sort/package-json', {
rules: {
'jsonc/sort-keys': 'off',
},
})
])

1653
lint.config.ts Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -8,21 +8,47 @@
"fmt:check": "vp fmt --check",
"prepare": "vp config",
"type-check": "vp run -r type-check",
"lint": "eslint --cache --concurrency=auto",
"lint:ci": "eslint --cache --cache-strategy content --concurrency 2",
"lint:fix": "vp run lint --fix",
"lint:quiet": "vp run lint --quiet"
"lint": "pnpm lint:oxlint && pnpm lint:eslint",
"lint:ci": "pnpm lint:oxlint && pnpm lint:eslint",
"lint:fix": "pnpm lint:oxlint:fix && pnpm lint:eslint:fix",
"lint:quiet": "pnpm lint:oxlint:quiet && pnpm lint:eslint:quiet",
"lint:oxlint": "vp lint",
"lint:oxlint:fix": "vp lint --fix",
"lint:oxlint:quiet": "vp lint --quiet",
"lint:eslint": "eslint --concurrency=auto",
"lint:eslint:fix": "eslint --fix --concurrency=auto",
"lint:eslint:quiet": "eslint --quiet --concurrency=auto"
},
"devDependencies": {
"@antfu/eslint-config": "catalog:",
"@eslint-community/eslint-plugin-eslint-comments": "catalog:",
"@eslint-react/eslint-plugin": "catalog:",
"@eslint/markdown": "catalog:",
"@iconify-json/heroicons": "catalog:",
"@iconify-json/ri": "catalog:",
"@tanstack/eslint-plugin-query": "catalog:",
"@typescript-eslint/parser": "catalog:",
"@typescript/native": "catalog:",
"concurrently": "catalog:",
"eslint": "catalog:",
"eslint-markdown": "catalog:",
"eslint-plugin-antfu": "catalog:",
"eslint-plugin-better-tailwindcss": "catalog:",
"eslint-plugin-command": "catalog:",
"eslint-plugin-erasable-syntax-only": "catalog:",
"eslint-plugin-hyoban": "catalog:",
"eslint-plugin-jsdoc": "catalog:",
"eslint-plugin-jsonc": "catalog:",
"eslint-plugin-jsx-a11y": "catalog:",
"eslint-plugin-markdown-preferences": "catalog:",
"eslint-plugin-n": "catalog:",
"eslint-plugin-no-barrel-files": "catalog:",
"eslint-plugin-perfectionist": "catalog:",
"eslint-plugin-pnpm": "catalog:",
"eslint-plugin-regexp": "catalog:",
"eslint-plugin-storybook": "catalog:",
"eslint-plugin-toml": "catalog:",
"eslint-plugin-unicorn": "catalog:",
"eslint-plugin-yml": "catalog:",
"typescript": "catalog:",
"vite": "catalog:",
"vite-plus": "catalog:"

View File

@ -1478,7 +1478,7 @@ export const zDailyTokenCostStatisticItem = z.object({
token_count: z.int().nullish(),
total_price: z
.string()
.regex(/^(?![-+.]*$)[+-]?\d*(?:\.\d*)?$/)
.regex(/^(?![-+.]*$)[+-]?0*\d*\.?\d*$/)
.nullish(),
})

View File

@ -531,7 +531,7 @@ export const zExploreMessageListItem = z.object({
status: z.string(),
total_price: z
.string()
.regex(/^(?![-+.]*$)[+-]?\d*(?:\.\d*)?$/)
.regex(/^(?![-+.]*$)[+-]?0*\d*\.?\d*$/)
.nullish(),
total_tokens: z.int().readonly(),
})
@ -570,7 +570,7 @@ export const zExploreMessageListItemWritable = z.object({
status: z.string(),
total_price: z
.string()
.regex(/^(?![-+.]*$)[+-]?\d*(?:\.\d*)?$/)
.regex(/^(?![-+.]*$)[+-]?0*\d*\.?\d*$/)
.nullish(),
})

View File

@ -2360,12 +2360,12 @@ export const zEventParameterType = z.enum([
*/
export const zPriceConfigResponse = z.object({
currency: z.string(),
input: z.string().regex(/^(?![-+.]*$)[+-]?\d*(?:\.\d*)?$/),
input: z.string().regex(/^(?![-+.]*$)[+-]?0*\d*\.?\d*$/),
output: z
.string()
.regex(/^(?![-+.]*$)[+-]?\d*(?:\.\d*)?$/)
.regex(/^(?![-+.]*$)[+-]?0*\d*\.?\d*$/)
.nullish(),
unit: z.string().regex(/^(?![-+.]*$)[+-]?\d*(?:\.\d*)?$/),
unit: z.string().regex(/^(?![-+.]*$)[+-]?0*\d*\.?\d*$/),
})
/**
@ -2870,12 +2870,12 @@ export const zToolProviderEntity = z.object({
*/
export const zPriceConfig = z.object({
currency: z.string(),
input: z.string().regex(/^(?![-+.]*$)[+-]?\d*(?:\.\d*)?$/),
input: z.string().regex(/^(?![-+.]*$)[+-]?0*\d*\.?\d*$/),
output: z
.string()
.regex(/^(?![-+.]*$)[+-]?\d*(?:\.\d*)?$/)
.regex(/^(?![-+.]*$)[+-]?0*\d*\.?\d*$/)
.nullish(),
unit: z.string().regex(/^(?![-+.]*$)[+-]?\d*(?:\.\d*)?$/),
unit: z.string().regex(/^(?![-+.]*$)[+-]?0*\d*\.?\d*$/),
})
/**
@ -3244,7 +3244,7 @@ export const zPluginDeclaration = z.object({
agent_strategy: zAgentStrategyProviderEntity.nullish(),
author: z
.string()
.regex(/^[\w-]{1,64}$/)
.regex(/^[a-zA-Z0-9_-]{1,64}$/)
.nullable(),
category: zPluginCategory,
created_at: z.iso.datetime(),

View File

@ -1992,7 +1992,7 @@ export const zMessageListItem = z.object({
status: z.string(),
total_price: z
.string()
.regex(/^(?![-+.]*$)[+-]?\d*(?:\.\d*)?$/)
.regex(/^(?![-+.]*$)[+-]?0*\d*\.?\d*$/)
.nullish(),
total_tokens: z.int().readonly(),
})
@ -2343,7 +2343,7 @@ export const zMessageListItemWritable = z.object({
status: z.string(),
total_price: z
.string()
.regex(/^(?![-+.]*$)[+-]?\d*(?:\.\d*)?$/)
.regex(/^(?![-+.]*$)[+-]?0*\d*\.?\d*$/)
.nullish(),
})

View File

@ -850,7 +850,7 @@ export const zWebMessageListItem = z.object({
status: z.string(),
total_price: z
.string()
.regex(/^(?![-+.]*$)[+-]?\d*(?:\.\d*)?$/)
.regex(/^(?![-+.]*$)[+-]?0*\d*\.?\d*$/)
.nullish(),
total_tokens: z.int().readonly(),
})
@ -977,7 +977,7 @@ export const zWebMessageListItemWritable = z.object({
status: z.string(),
total_price: z
.string()
.regex(/^(?![-+.]*$)[+-]?\d*(?:\.\d*)?$/)
.regex(/^(?![-+.]*$)[+-]?0*\d*\.?\d*$/)
.nullish(),
})

View File

@ -297,10 +297,6 @@ export default defineConfig({
suffix: '.gen',
},
postProcess: [
{
command: 'eslint',
args: ['--fix', '{{path}}/*.ts'],
},
{
command: 'vp',
args: ['fmt', '{{path}}'],

View File

@ -18,7 +18,7 @@
}
},
"scripts": {
"gen-api-contract": "uv run --project ../../api ../../api/dev/generate_swagger_specs.py --output-dir openapi && uv run --project ../../api ../../api/dev/generate_fastopenapi_specs.py --output-dir openapi && node -e \"fs.rmSync('generated/api', { recursive: true, force: true })\" && openapi-ts -f openapi-ts.api.config.ts && eslint --fix generated/api && vp fmt generated/api",
"gen-api-contract": "uv run --project ../../api ../../api/dev/generate_swagger_specs.py --output-dir openapi && uv run --project ../../api ../../api/dev/generate_fastopenapi_specs.py --output-dir openapi && node -e \"fs.rmSync('generated/api', { recursive: true, force: true })\" && openapi-ts -f openapi-ts.api.config.ts && vp fmt generated/api",
"gen-enterprise-contract": "openapi-ts -f openapi-ts.enterprise.config.ts",
"test": "vp test openapi-yaml.test.ts",
"type-check": "tsc"
@ -32,7 +32,6 @@
"@hey-api/openapi-ts": "catalog:",
"@types/node": "catalog:",
"@typescript/native": "catalog:",
"eslint": "catalog:",
"js-yaml": "catalog:",
"typescript": "catalog:",
"vite-plus": "catalog:",

View File

@ -188,7 +188,7 @@ See `[web/docs/overlay.md](../../web/docs/overlay.md)` for the web app overlay b
## Development
- `vp run @langgenius/dify-ui#lint` (from the repository root) — strict ESLint checks for component source, stories, tests, and package configuration.
- `vp run @langgenius/dify-ui#lint` (from the repository root) — strict Oxlint checks for component source, stories, tests, and package configuration.
- `pnpm -C packages/dify-ui test` — Vitest unit tests for primitives.
- `pnpm -C packages/dify-ui storybook` — Storybook on the default port. Each primitive has `index.stories.tsx`.
- `pnpm -C packages/dify-ui test:storybook` — Storybook component tests in Vitest browser mode. Stories without `play` are render and a11y smoke tests; stories with `play` should cover public UI contracts such as opening overlays, keyboard navigation, disabled/loading guards, form submission, and controlled state updates.

View File

@ -1,130 +0,0 @@
// @ts-check
import path from 'node:path'
import antfu, { GLOB_MARKDOWN_CODE, GLOB_TS, GLOB_TSX } from '@antfu/eslint-config'
import tailwindcss from 'eslint-plugin-better-tailwindcss'
import jsxA11y from 'eslint-plugin-jsx-a11y'
import storybook from 'eslint-plugin-storybook'
const SOURCE_FILES = [GLOB_TS, GLOB_TSX]
const TEST_FILES = ['**/__tests__/**/*.{ts,tsx}', '**/*.spec.{ts,tsx}']
export default antfu(
{
ignores: ['coverage/', 'dist/', 'storybook-static/'],
stylistic: false,
perfectionist: {
overrides: {
'perfectionist/sort-imports': 'off',
},
},
jsonc: {
overrides: {
'jsonc/space-unary-ops': 'off',
},
},
yaml: {
overrides: {
'yaml/block-mapping': 'off',
'yaml/block-sequence': 'off',
'yaml/plain-scalar': 'off',
},
},
toml: {
overrides: {
'toml/comma-style': 'off',
'toml/no-space-dots': 'off',
},
},
react: {
overrides: {
'react/exhaustive-deps': ['error', { additionalHooks: 'useIsoLayoutEffect' }],
'react/no-context-provider': 'off',
'react/no-unnecessary-use-prefix': 'error',
'react/no-use-context': 'off',
'react/rules-of-hooks': 'error',
'react/set-state-in-effect': 'error',
'react/set-state-in-render': 'error',
'react/static-components': 'error',
'react-refresh/only-export-components': 'off',
},
},
typescript: {
overrides: {
'ts/consistent-type-definitions': ['error', 'type'],
'ts/no-explicit-any': 'error',
'ts/no-redeclare': 'off',
},
erasableOnly: true,
},
test: {
overrides: {
'test/prefer-lowercase-title': 'off',
},
},
e18e: false,
pnpm: false,
},
{
files: [GLOB_TSX],
...jsxA11y.flatConfigs.recommended,
rules: {
...jsxA11y.flatConfigs.recommended.rules,
'jsx-a11y/anchor-has-content': 'off',
'jsx-a11y/label-has-associated-control': 'off',
},
},
...storybook.configs['flat/recommended'],
{
name: 'dify-ui/storybook',
files: ['**/.storybook/main.{js,cjs,mjs,ts}'],
rules: {
'storybook/no-uninstalled-addons': [
'error',
{
packageJsonLocation: path.resolve(import.meta.dirname, 'package.json'),
},
],
},
},
{
name: 'dify-ui/tailwind',
files: SOURCE_FILES,
ignores: [GLOB_MARKDOWN_CODE, ...TEST_FILES],
plugins: {
tailwindcss,
},
rules: {
'tailwindcss/no-duplicate-classes': 'error',
'tailwindcss/no-deprecated-classes': 'error',
'tailwindcss/no-unknown-classes': 'error',
},
settings: {
'better-tailwindcss': {
cwd: import.meta.dirname,
entryPoint: path.resolve(import.meta.dirname, './.storybook/storybook.css'),
},
},
},
{
files: TEST_FILES,
rules: {
'react/purity': 'off',
},
},
{
rules: {
'node/prefer-global/process': 'off',
'unicorn/number-literal-case': 'off',
},
},
{
linterOptions: {
reportUnusedDisableDirectives: 'error',
},
},
).override('antfu/sort/package-json', {
rules: {
'jsonc/sort-keys': 'off',
},
})

View File

@ -151,7 +151,6 @@
}
},
"scripts": {
"lint": "eslint --cache --max-warnings=0 .",
"storybook": "storybook dev -p 6006",
"storybook:build": "storybook build",
"test": "vp test --project unit",
@ -164,12 +163,10 @@
"tailwind-merge": "catalog:"
},
"devDependencies": {
"@antfu/eslint-config": "catalog:",
"@base-ui/react": "catalog:",
"@chromatic-com/storybook": "catalog:",
"@dify/tsconfig": "workspace:*",
"@egoist/tailwindcss-icons": "catalog:",
"@eslint-react/eslint-plugin": "catalog:",
"@iconify-json/ri": "catalog:",
"@storybook/addon-a11y": "catalog:",
"@storybook/addon-docs": "catalog:",
@ -188,11 +185,6 @@
"@vitest/browser-playwright": "catalog:",
"@vitest/coverage-v8": "catalog:",
"class-variance-authority": "catalog:",
"eslint": "catalog:",
"eslint-plugin-better-tailwindcss": "catalog:",
"eslint-plugin-jsx-a11y": "catalog:",
"eslint-plugin-react-refresh": "catalog:",
"eslint-plugin-storybook": "catalog:",
"playwright": "catalog:",
"react": "catalog:",
"react-dom": "catalog:",

View File

@ -603,7 +603,7 @@ const FuzzyHighlight = ({ text, query }: { text: string; query: string }) => {
<React.Fragment>
{parts.map((part, index) =>
part.toLowerCase() === query.trim().toLowerCase() ? (
// eslint-disable-next-line react/no-array-index-key -- Repeated text fragments have no stable identity and never preserve state.
// oxlint-disable-next-line react/no-array-index-key -- Repeated text fragments have no stable identity and never preserve state.
<mark key={`${part}-${index}`} className="bg-transparent text-text-accent">
{part}
</mark>

View File

@ -65,7 +65,7 @@ const HotkeyKbdGroup = ({
}) => (
<KbdGroup>
{displayKeys(hotkey, platform).map((key, index) => (
// eslint-disable-next-line react/no-array-index-key -- Repeated display keys are static, ordered tokens with no component state.
// oxlint-disable-next-line react/no-array-index-key -- Repeated display keys are static, ordered tokens with no component state.
<Kbd key={`${key}-${index}`} color={color}>
{key}
</Kbd>

View File

@ -344,7 +344,7 @@ export function PaginationPageJump({
<NumberFieldGroup className="h-7 w-full min-w-0 rounded-lg border-[0.5px] border-components-input-border-active bg-components-input-bg-active shadow-xs">
<NumberFieldInput
aria-label={inputLabel}
// eslint-disable-next-line jsx-a11y/no-autofocus -- Editing starts after an explicit user action and focus must move to the replacement input.
// oxlint-disable-next-line jsx-a11y/no-autofocus -- Editing starts after an explicit user action and focus must move to the replacement input.
autoFocus
className="px-2 py-1.5 text-center system-xs-medium tabular-nums"
onBlur={() => requestAnimationFrame(() => setEditing(false))}

1574
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -52,14 +52,14 @@ overrides:
catalog:
'@amplitude/analytics-browser': 2.44.4
'@amplitude/plugin-session-replay-browser': 1.33.0
'@antfu/eslint-config': 9.1.0
'@base-ui/react': 1.6.0
'@chromatic-com/storybook': 5.2.1
'@cucumber/cucumber': 13.0.0
'@egoist/tailwindcss-icons': 1.9.2
'@emoji-mart/data': 1.2.1
'@eslint-community/eslint-plugin-eslint-comments': 4.7.2
'@eslint-react/eslint-plugin': 5.12.1
'@eslint/js': 10.0.1
'@eslint/markdown': 8.0.3
'@floating-ui/react': 0.27.19
'@formatjs/intl-localematcher': 0.8.10
'@heroicons/react': 2.2.0
@ -80,7 +80,6 @@ catalog:
'@mediabunny/mp3-encoder': 1.50.7
'@monaco-editor/react': 4.7.0
'@napi-rs/keyring': 1.3.0
'@next/eslint-plugin-next': 16.2.10
'@next/mdx': 16.2.10
'@orpc/client': 1.14.7
'@orpc/contract': 1.14.7
@ -128,7 +127,6 @@ catalog:
'@types/react': 19.2.17
'@types/react-dom': 19.2.3
'@types/sortablejs': 1.15.9
'@typescript-eslint/eslint-plugin': 8.63.0
'@typescript-eslint/parser': 8.63.0
'@typescript/native': npm:typescript@7.0.2
'@vitejs/plugin-react': 6.0.3
@ -161,14 +159,25 @@ catalog:
emoji-mart: 5.6.0
es-toolkit: 1.49.0
eslint: 10.6.0
eslint-markdown: 0.12.0
eslint-markdown: 0.12.1
eslint-plugin-antfu: 3.2.3
eslint-plugin-better-tailwindcss: 4.6.1
eslint-plugin-command: 3.5.2
eslint-plugin-erasable-syntax-only: 0.4.2
eslint-plugin-hyoban: 0.14.1
eslint-plugin-jsdoc: 63.0.10
eslint-plugin-jsonc: 3.3.0
eslint-plugin-jsx-a11y: 6.10.2
eslint-plugin-markdown-preferences: 0.41.1
eslint-plugin-n: 18.2.1
eslint-plugin-no-barrel-files: 1.3.1
eslint-plugin-react-refresh: 0.5.3
eslint-plugin-perfectionist: 5.9.1
eslint-plugin-pnpm: 1.6.1
eslint-plugin-regexp: 3.1.1
eslint-plugin-storybook: 10.4.6
eslint-plugin-toml: 1.4.0
eslint-plugin-unicorn: 68.0.0
eslint-plugin-yml: 3.6.0
eventsource-parser: 3.1.0
fast-deep-equal: 3.1.3
foxact: 0.3.8

View File

@ -1,45 +0,0 @@
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import js from '@eslint/js'
import tsPlugin from '@typescript-eslint/eslint-plugin'
import tsParser from '@typescript-eslint/parser'
const tsconfigRootDir = path.dirname(fileURLToPath(import.meta.url))
const typeCheckedRules =
tsPlugin.configs['recommended-type-checked']?.rules ??
tsPlugin.configs.recommendedTypeChecked?.rules ??
{}
export default [
{
ignores: ['dist', 'node_modules', 'scripts'],
},
js.configs.recommended,
{
files: ['src/**/*.ts', 'tests/**/*.ts'],
languageOptions: {
parser: tsParser,
ecmaVersion: 'latest',
parserOptions: {
project: './tsconfig.json',
tsconfigRootDir,
sourceType: 'module',
},
},
plugins: {
'@typescript-eslint': tsPlugin,
},
rules: {
...tsPlugin.configs.recommended.rules,
...typeCheckedRules,
'no-undef': 'off',
'no-unused-vars': 'off',
'@typescript-eslint/no-unsafe-call': 'error',
'@typescript-eslint/no-unsafe-return': 'error',
'@typescript-eslint/consistent-type-imports': [
'error',
{ prefer: 'type-imports', fixStyle: 'separate-type-imports' },
],
},
},
]

View File

@ -27,10 +27,10 @@
"directory": "sdks/nodejs-client"
},
"files": [
"dist/index.js",
"dist/index.d.ts",
"LICENSE",
"README.md",
"LICENSE"
"dist/index.d.ts",
"dist/index.js"
],
"type": "module",
"main": "./dist/index.js",
@ -43,8 +43,6 @@
},
"scripts": {
"build": "vp pack",
"lint": "eslint",
"lint:fix": "eslint --fix",
"type-check": "tsc",
"test": "vp test",
"test:coverage": "vp test --coverage",
@ -53,13 +51,9 @@
},
"devDependencies": {
"@dify/tsconfig": "workspace:*",
"@eslint/js": "catalog:",
"@types/node": "catalog:",
"@typescript-eslint/eslint-plugin": "catalog:",
"@typescript-eslint/parser": "catalog:",
"@typescript/native": "catalog:",
"@vitest/coverage-v8": "catalog:",
"eslint": "catalog:",
"typescript": "catalog:",
"vite": "catalog:",
"vite-plus": "catalog:",

View File

@ -1,8 +1,12 @@
import { defineConfig } from 'vite-plus'
import { lintConfig } from './lint.config'
const lintFiles = '*.{js,cjs,mjs,jsx,ts,cts,mts,tsx,json,jsonc,json5,md,yml,yaml,toml}'
const lintFiles = '*.{js,cjs,mjs,jsx,ts,cts,mts,tsx}'
const eslintFiles = '*.{json,jsonc,json5,md,yml,yaml,toml}'
const formatOnlyFiles = '*.{mdx,css,scss,less,html,vue,svelte,gql,graphql,hbs,handlebars}'
const lintFix = 'eslint --fix --pass-on-unpruned-suppressions --no-error-on-unmatched-pattern'
const lintFix = 'vp lint --fix --no-error-on-unmatched-pattern'
const eslintFix =
'eslint --fix --pass-on-unpruned-suppressions --no-error-on-unmatched-pattern --no-warn-ignored'
const format = 'vp fmt --no-error-on-unmatched-pattern'
const nonFrontendIgnores = [
@ -30,6 +34,7 @@ const generatedIgnores = [
'e2e/.auth/**',
'e2e/cucumber-report/**',
'eslint-suppressions.json',
'oxlint-suppressions.json',
'web/next/**',
'web/next-env.d.ts',
'web/public/embed.min.js',
@ -40,8 +45,10 @@ const generatedIgnores = [
const formatterUnstableInputs = ['web/app/components/develop/template/*.mdx']
export default defineConfig({
lint: lintConfig,
staged: {
[lintFiles]: [lintFix, format],
[eslintFiles]: [eslintFix, format],
[formatOnlyFiles]: format,
},
fmt: {

View File

@ -4,7 +4,7 @@ import { act } from '@testing-library/react'
export * from 'zustand'
const { create: actualCreate, createStore: actualCreateStore } =
// eslint-disable-next-line antfu/no-top-level-await
// oxlint-disable-next-line antfu/no-top-level-await
await vi.importActual<typeof ZustandExportedTypes>('zustand')
export const storeResetFns = new Set<() => void>()

View File

@ -33,7 +33,7 @@ const WebSSOForm: FC = () => {
await webAppLogout(shareCode!)
const url = getSigninUrl()
router.replace(url)
}, [getSigninUrl, router, webAppLogout, shareCode])
}, [getSigninUrl, router, shareCode])
if (!redirectUrl) {
return (

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import type { Mock } from 'vitest'
import type { AnnotationItem } from '../type'
import type { App } from '@/types/app'

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import type { Mock } from 'vitest'
import type { Locale } from '@/i18n-config'
import { render, screen } from '@testing-library/react'

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import type { ComponentProps } from 'react'
import type { Mock } from 'vitest'
import type { AnnotationItemBasic } from '../../type'

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import { fireEvent, render, screen, waitFor, within } from '@testing-library/react'
import FeaturesWrappedAppPublisher from '../features-wrapper'

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import { fireEvent, screen, waitFor } from '@testing-library/react'
import * as React from 'react'
import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features'

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import type { ReactNode } from 'react'
import { fireEvent, render, screen } from '@testing-library/react'
import { AccessMode } from '@/models/access-control'

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import { toast } from '@langgenius/dify-ui/toast'
import { fireEvent, render, screen } from '@testing-library/react'
import VersionInfoModal from '../version-info-modal'

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import type { ReactNode } from 'react'
import type { PromptRole } from '@/models/debug'
import { fireEvent, render, screen } from '@testing-library/react'

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import type { IPromptProps } from '../index'
import type { PromptItem, PromptVariable } from '@/models/debug'
import { fireEvent, render, screen } from '@testing-library/react'

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import type { ReactNode } from 'react'
import { fireEvent, render, screen } from '@testing-library/react'
import { INSERT_VARIABLE_VALUE_BLOCK_COMMAND } from '@/app/components/base/prompt-editor/plugins/variable-block'

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import type { ReactNode } from 'react'
import { fireEvent, render, screen } from '@testing-library/react'
import { InputVarType } from '@/app/components/workflow/types'

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import type { InputVar } from '@/app/components/workflow/types'
import type { App, AppSSO } from '@/types/app'
import { toast } from '@langgenius/dify-ui/toast'

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import TypeSelector from '../type-select'

View File

@ -205,7 +205,7 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, readonly, onPromptVar
[handleOpenExternalDataToolModal, onPromptVariablesChange, promptVariables],
)
// eslint-disable-next-line ts/no-explicit-any
// oxlint-disable-next-line typescript/no-explicit-any
eventEmitter?.useSubscription((v: any) => {
if (v.type === ADD_EXTERNAL_DATA_TOOL) {
const payload = v.payload

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import type { Mock } from 'vitest'
import type { FeatureStoreState } from '@/app/components/base/features/store'
import type { FileUpload } from '@/app/components/base/features/types'

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import type { AgentConfig } from '@/models/debug'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import type { FeatureStoreState } from '@/app/components/base/features/store'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import type { FeatureStoreState } from '@/app/components/base/features/store'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import type { ModelConfig, PromptVariable } from '@/models/debug'
import type { ToolItem } from '@/types/app'
import { render, screen } from '@testing-library/react'

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import type { PropsWithChildren } from 'react'
import type { Mock } from 'vitest'
import type SettingBuiltInToolType from '../setting-built-in-tool'

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import type { Tool, ToolParameter } from '@/app/components/tools/types'
import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'

View File

@ -97,7 +97,7 @@ const SettingBuiltInTool: FC<Props> = ({
} catch {}
setIsLoading(false)
})()
// eslint-disable-next-line react-hooks/exhaustive-deps
// oxlint-disable-next-line react/exhaustive-deps
}, [collection?.name, collection?.id, collection?.type])
useEffect(() => {

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import type { DataSet } from '@/models/datasets'
import type { DatasetConfigs } from '@/models/debug'
import { fireEvent, render, screen, within } from '@testing-library/react'

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import type { DataSet } from '@/models/datasets'
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
import * as React from 'react'

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import { act, renderHook } from '@testing-library/react'
import { AgentStrategy } from '@/types/app'
import {

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import type { CSSProperties } from 'react'
import type { ModelAndParameter } from '../../types'
import type { DebugWithMultipleModelContextType } from '../context'

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import type { ReactNode, RefObject } from 'react'
import type { DebugWithSingleModelRefType } from '../index'
import type { ChatItem } from '@/app/components/base/chat/types'

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import { act, renderHook, waitFor } from '@testing-library/react'
import {
CONTEXT_PLACEHOLDER_TEXT,

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import type { VisionSettings } from '@/types/app'
import { withSelectorKey } from '@/test/i18n-mock'
import {

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import { act, renderHook, waitFor } from '@testing-library/react'
import { updateAppModelConfig } from '@/service/apps'
import { AppModeEnum, ModelModeType } from '@/types/app'

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import type { IPromptValuePanelProps } from '../index'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import * as React from 'react'

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import type { App } from '@/models/explore'
import type { AppIconType } from '@/types/app'
import { screen, within } from '@testing-library/react'
@ -73,7 +73,7 @@ describe('AppCard', () => {
const renderWithProvider = (ui: React.ReactElement) => {
return render(
// eslint-disable-next-line react/no-context-provider
// oxlint-disable-next-line eslint-react/no-context-provider
<AppListContext.Provider
value={{
currentApp: undefined,

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import { act, fireEvent, screen, waitFor } from '@testing-library/react'
import { renderWithSystemFeatures as render } from '@/__tests__/utils/mock-system-features'
import { NEED_REFRESH_APP_LIST_KEY } from '@/app/components/apps/storage'

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import { fireEvent, render, screen } from '@testing-library/react'
import { APP_PAGE_LIMIT } from '@/config'
import { AppModeEnum } from '@/types/app'

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import type { IChatItem } from '@/app/components/base/chat/chat/type'
import {
applyAnnotationAdded,

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import type { ReactNode } from 'react'
import { act, fireEvent, screen, waitFor } from '@testing-library/react'
import {

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import {
buildChartOptions,
getChartValueField,

View File

@ -1,4 +1,4 @@
/* eslint-disable react-refresh/only-export-components */
/* oxlint-disable react/only-export-components */
import type { SelectorParam, TFunction } from 'i18next'
import type { ComponentType, FormEvent, ReactNode } from 'react'
import type {

View File

@ -1,4 +1,4 @@
/* eslint-disable react-refresh/only-export-components, react/component-hook-factories */
/* oxlint-disable react/only-export-components */
'use client'
import type { Dayjs } from 'dayjs'
import type { SelectorParam } from 'i18next'

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
import { AppSourceType } from '@/service/share'
import GenerationItem from '../index'

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import { render, screen } from '@testing-library/react'
import ResultTab from '../result-tab'

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import {
buildPromptLogItem,
getCopyContent,

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import { fireEvent, render, screen } from '@testing-library/react'
import WorkflowBody from '../workflow-body'

View File

@ -151,9 +151,9 @@ const GenerationItem: FC<IGenerationItemProps> = ({
useEffect(() => {
if (controlClearMoreLikeThis) {
// eslint-disable-next-line react/set-state-in-effect
// oxlint-disable-next-line eslint-react/set-state-in-effect
setChildMessageId(null)
// eslint-disable-next-line react/set-state-in-effect
// oxlint-disable-next-line eslint-react/set-state-in-effect
setCompletionRes('')
}
}, [controlClearMoreLikeThis])
@ -161,7 +161,7 @@ const GenerationItem: FC<IGenerationItemProps> = ({
// regeneration clear child
useEffect(() => {
if (isLoading)
// eslint-disable-next-line react/set-state-in-effect
// oxlint-disable-next-line eslint-react/set-state-in-effect
setChildMessageId(null)
}, [isLoading])
@ -180,7 +180,7 @@ const GenerationItem: FC<IGenerationItemProps> = ({
setCurrentTab(tab)
}
useEffect(() => {
// eslint-disable-next-line react/set-state-in-effect
// oxlint-disable-next-line eslint-react/set-state-in-effect
setCurrentTab(getDefaultGenerationTab(workflowProcessData))
}, [workflowProcessData])
const handleSubmitHumanInputForm = useCallback(

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
/**
* WorkflowAppLogList Component Tests
*

View File

@ -5,7 +5,7 @@ import { APP_LIST_SEARCH_DEBOUNCE_MS } from '../../constants'
import { useAppsQueryState } from '../use-apps-query-state'
const renderWithAdapter = (searchParams = '') => {
// eslint-disable-next-line react/use-state -- renderHook executes a custom hook, not React.useState
// oxlint-disable-next-line eslint-react/use-state -- renderHook executes a custom hook, not React.useState
return renderHookWithNuqs(() => useAppsQueryState(), { searchParams })
}

View File

@ -50,7 +50,7 @@ function List({ controlRefreshList = 0 }: Props) {
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
const { onPlanInfoChanged } = useProviderContext()
// eslint-disable-next-line react/use-state -- custom URL query hook, not React.useState
// oxlint-disable-next-line eslint-react/use-state -- custom URL query hook, not React.useState
const {
query: { category, keywords, creatorIDs },
setCategory,

View File

@ -1,7 +1,7 @@
import AudioPlayer from '@/app/components/base/audio-btn/audio'
declare global {
// eslint-disable-next-line ts/consistent-type-definitions
// oxlint-disable-next-line typescript/consistent-type-definitions
interface AudioPlayerManager {
instance: AudioPlayerManager
}

View File

@ -2,7 +2,7 @@ import { toast } from '@langgenius/dify-ui/toast'
import { AppSourceType, textToAudioStream } from '@/service/share'
declare global {
// eslint-disable-next-line ts/consistent-type-definitions
// oxlint-disable-next-line typescript/consistent-type-definitions
interface Window {
ManagedMediaSource: any
}

View File

@ -56,7 +56,7 @@ const AudioPlayer: React.FC<AudioPlayerProps> = ({ src, srcs }) => {
const primarySrc = srcs?.[0] || src
if (primarySrc) {
// Delayed generation of waveform data
// eslint-disable-next-line ts/no-use-before-define
// oxlint-disable-next-line typescript/no-use-before-define
const timer = setTimeout(generateWaveformData, 1000, primarySrc)
return () => {
audio.removeEventListener('loadedmetadata', setAudioData)

View File

@ -45,7 +45,7 @@ async function advanceWaveformTimer() {
})
}
// eslint-disable-next-line ts/no-explicit-any
// oxlint-disable-next-line typescript/no-explicit-any
type ReactEventHandler = ((...args: any[]) => void) | undefined
function getReactProps<T extends Element>(el: T): Record<string, ReactEventHandler> {
const key = Object.keys(el).find((k) => k.startsWith('__reactProps$'))

View File

@ -32,7 +32,7 @@ const AutoHeightTextarea = ({
}: IProps & {
ref?: React.RefObject<HTMLTextAreaElement>
}) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
// oxlint-disable-next-line react/rules-of-hooks
const ref = outerRef || useRef<HTMLTextAreaElement>(null)
const doFocus = () => {

View File

@ -1,4 +1,4 @@
/* eslint-disable react-hooks-extra/no-direct-set-state-in-use-effect */
/* oxlint-disable eslint-react/set-state-in-effect */
import type { UseEmblaCarouselType } from 'embla-carousel-react'
import { cn } from '@langgenius/dify-ui/cn'
import Autoplay from 'embla-carousel-autoplay'

View File

@ -259,7 +259,7 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
const [initUserVariables, setInitUserVariables] = useState<Record<string, any>>({})
const handleNewConversationInputsChange = useCallback((newInputs: Record<string, any>) => {
newConversationInputsRef.current = newInputs
// eslint-disable-next-line react/set-state-in-effect -- This handler intentionally syncs derived input defaults when called from the reset effect below.
// oxlint-disable-next-line eslint-react/set-state-in-effect -- This handler intentionally syncs derived input defaults when called from the reset effect below.
setNewConversationInputs(newInputs)
}, [])
const inputsForms = useMemo(() => {
@ -363,7 +363,7 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
const [originConversationList, setOriginConversationList] = useState<ConversationItem[]>([])
useEffect(() => {
if (appConversationData?.data && !appConversationDataLoading)
// eslint-disable-next-line react/set-state-in-effect -- Conversation query results intentionally replace the local editable list.
// oxlint-disable-next-line eslint-react/set-state-in-effect -- Conversation query results intentionally replace the local editable list.
setOriginConversationList(appConversationData?.data)
}, [appConversationData, appConversationDataLoading])
const conversationList = useMemo(() => {
@ -380,7 +380,7 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
}, [originConversationList, showNewConversationItemInList, t])
useEffect(() => {
if (newConversation) {
// eslint-disable-next-line react/set-state-in-effect -- Newly resolved conversation names intentionally patch the local list cache.
// oxlint-disable-next-line eslint-react/set-state-in-effect -- Newly resolved conversation names intentionally patch the local list cache.
setOriginConversationList(
produce((draft) => {
const index = draft.findIndex((item) => item.id === newConversation.id)
@ -406,7 +406,7 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
)
useEffect(() => {
if (currentConversationItem)
// eslint-disable-next-line react/set-state-in-effect -- Selected conversation changes intentionally resync the editable input snapshot.
// oxlint-disable-next-line eslint-react/set-state-in-effect -- Selected conversation changes intentionally resync the editable input snapshot.
setCurrentConversationInputs(currentConversationLatestInputs || {})
}, [currentConversationItem, currentConversationLatestInputs])
const checkInputsRequired = useCallback(

View File

@ -338,7 +338,7 @@ const ChatInputArea = ({
)
}
// Existing chat behavior focuses the composer as soon as it opens.
// eslint-disable-next-line jsx-a11y/no-autofocus
// oxlint-disable-next-line jsx-a11y/no-autofocus
autoFocus={autoFocus}
minRows={1}
value={query}

View File

@ -68,7 +68,7 @@ const Citation: FC<CitationProps> = ({
}
}
setLimitNumberInOneLine(limit)
// eslint-disable-next-line react-hooks/exhaustive-deps
// oxlint-disable-next-line react/exhaustive-deps
}, [])
const resourcesLength = resources.length

View File

@ -1,5 +1,5 @@
import type { ChatConfig, ChatItem, Feedback } from '../types'
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import type { InputValueTypes } from '@/app/components/share/text-generation/types'
import type { Locale } from '@/i18n-config'
import type { AppData, ConversationItem } from '@/models/share'
@ -194,7 +194,7 @@ export const useEmbeddedChatbot = (appSourceType: AppSourceType, tryAppId?: stri
const [initUserVariables, setInitUserVariables] = useState<Record<string, any>>({})
const handleNewConversationInputsChange = useCallback((newInputs: Record<string, any>) => {
newConversationInputsRef.current = newInputs
// eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect
// oxlint-disable-next-line eslint-react/set-state-in-effect
setNewConversationInputs(newInputs)
}, [])
const inputsForms = useMemo(() => {
@ -299,7 +299,7 @@ export const useEmbeddedChatbot = (appSourceType: AppSourceType, tryAppId?: stri
const [originConversationList, setOriginConversationList] = useState<ConversationItem[]>([])
useEffect(() => {
if (appConversationData?.data && !appConversationDataLoading)
// eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect
// oxlint-disable-next-line eslint-react/set-state-in-effect
setOriginConversationList(appConversationData?.data)
}, [appConversationData, appConversationDataLoading])
const conversationList = useMemo(() => {
@ -341,7 +341,7 @@ export const useEmbeddedChatbot = (appSourceType: AppSourceType, tryAppId?: stri
)
useEffect(() => {
if (currentConversationItem && !isTryApp)
// eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect
// oxlint-disable-next-line eslint-react/set-state-in-effect
setCurrentConversationInputs(currentConversationLatestInputs || {})
}, [currentConversationItem, currentConversationLatestInputs])
const checkInputsRequired = useCallback(

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import { fireEvent, render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { InputVarType } from '@/app/components/workflow/types'

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any */
/* oxlint-disable typescript/no-explicit-any */
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { AppSourceType } from '@/service/share'

View File

@ -62,18 +62,18 @@ const DatePicker = ({
clearMonthMapCache()
if (normalizedValue) {
const newValue = getDateWithTimezone({ date: normalizedValue, timezone })
// eslint-disable-next-line react/set-state-in-effect -- timezone changes intentionally resync the displayed calendar state.
// oxlint-disable-next-line eslint-react/set-state-in-effect -- timezone changes intentionally resync the displayed calendar state.
setCurrentDate(newValue)
// eslint-disable-next-line react/set-state-in-effect -- timezone changes intentionally resync the selected value.
// oxlint-disable-next-line eslint-react/set-state-in-effect -- timezone changes intentionally resync the selected value.
setSelectedDate(newValue)
onChange(newValue)
} else {
// eslint-disable-next-line react/set-state-in-effect -- timezone changes intentionally resync the displayed calendar state.
// oxlint-disable-next-line eslint-react/set-state-in-effect -- timezone changes intentionally resync the displayed calendar state.
setCurrentDate((prev) => getDateWithTimezone({ date: prev, timezone }))
// eslint-disable-next-line react/set-state-in-effect -- timezone changes intentionally resync the selected value.
// oxlint-disable-next-line eslint-react/set-state-in-effect -- timezone changes intentionally resync the selected value.
setSelectedDate((prev) => (prev ? getDateWithTimezone({ date: prev, timezone }) : undefined))
}
// eslint-disable-next-line react/exhaustive-deps -- this effect intentionally runs only when timezone changes.
// oxlint-disable-next-line react/exhaustive-deps -- this effect intentionally runs only when timezone changes.
}, [timezone])
const handleOpenChange = useCallback(

Some files were not shown because too many files have changed in this diff Show More