chore: migrate formatting to vp fmt (#38705)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Stephen Zhou 2026-07-12 22:59:00 +08:00 committed by GitHub
parent 594c21d98c
commit b54107e9b9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 471 additions and 287 deletions

View File

@ -39,11 +39,19 @@ jobs:
with:
files: |
web/**
cli/**
e2e/**
packages/**
sdks/nodejs-client/**
package.json
pnpm-lock.yaml
pnpm-workspace.yaml
.nvmrc
vite.config.ts
eslint.config.mjs
.vscode/**
.github/workflows/autofix.yml
.github/workflows/style.yml
- name: Check api inputs
if: github.event_name != 'merge_group'
id: api-changes
@ -154,14 +162,19 @@ jobs:
cd api
uv run dev/generate_swagger_markdown_docs.py --swagger-dir ../packages/contracts/openapi --markdown-dir openapi/markdown --keep-swagger-json
- name: Generate frontend contracts
if: github.event_name != 'merge_group' && steps.frontend-contract-changes.outputs.any_changed == 'true'
run: pnpm --dir packages/contracts gen-api-contract
# TODO: Enable after the repository-wide vp fmt baseline lands.
# - name: Generate frontend contracts
# 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
if: github.event_name != 'merge_group' && steps.web-changes.outputs.any_changed == 'true'
run: |
vp exec eslint --concurrency=2 --prune-suppressions --quiet || true
# - name: ESLint 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
# - 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')
# run: vp fmt
- if: github.event_name != 'merge_group'
uses: autofix-ci/action@c5b2d67aa2274e7b5a18224e8171550871fc7e4a # v1.3.4

View File

@ -162,7 +162,10 @@ jobs:
pnpm-lock.yaml
pnpm-workspace.yaml
.nvmrc
vite.config.ts
eslint.config.mjs
.vscode/**
.github/workflows/autofix.yml
.github/workflows/style.yml
.github/actions/setup-web/**
@ -170,6 +173,11 @@ jobs:
if: steps.changed-files.outputs.any_changed == 'true'
uses: ./.github/actions/setup-web
# TODO: Enable after the repository-wide vp fmt baseline lands.
# - name: Format check
# 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

View File

@ -223,7 +223,7 @@ jobs:
- Match the existing terminology and register used by each locale.
- Prefer one Edit per file when stable, but prioritize correctness over batching.
6. Verify only the edited files.
- Run `vp run dify-web#lint:fix --quiet -- <relative edited i18n file paths under web/>`
- Run `vp fmt <repo-relative edited paths such as web/i18n/...>`
- Run `vp run dify-web#i18n:check ${{ steps.context.outputs.FILE_ARGS }} ${{ steps.context.outputs.LANG_ARGS }}`
- If verification fails, fix the remaining problems before continuing.
7. Stop after the scoped locale files are updated and verification passes.
@ -322,7 +322,7 @@ jobs:
'## Verification',
'',
`- \`vp run dify-web#i18n:check --file ${process.env.FILES_IN_SCOPE} --lang ${process.env.TARGET_LANGS}\``,
`- \`vp run dify-web#lint:fix --quiet -- <edited i18n files under web/>\``,
`- \`vp fmt <repo-relative edited paths such as web/i18n/...>\``,
'',
'## Notes',
'',

View File

@ -1,54 +1,47 @@
{
"cucumber.features": [
"e2e/features/**/*.feature",
],
"cucumber.glue": [
"e2e/features/**/*.ts",
],
"cucumber.features": ["e2e/features/**/*.feature"],
"cucumber.glue": ["e2e/features/**/*.ts"],
"tailwindCSS.experimental.configFile": "web/app/styles/globals.css",
// Auto fix
// Format
"[javascript]": { "editor.defaultFormatter": "oxc.oxc-vscode" },
"[javascriptreact]": { "editor.defaultFormatter": "oxc.oxc-vscode" },
"[typescript]": { "editor.defaultFormatter": "oxc.oxc-vscode" },
"[typescriptreact]": { "editor.defaultFormatter": "oxc.oxc-vscode" },
"[json]": { "editor.defaultFormatter": "oxc.oxc-vscode" },
"[jsonc]": { "editor.defaultFormatter": "oxc.oxc-vscode" },
"[markdown]": { "editor.defaultFormatter": "oxc.oxc-vscode" },
"[mdx]": { "editor.defaultFormatter": "oxc.oxc-vscode" },
"[yaml]": { "editor.defaultFormatter": "oxc.oxc-vscode" },
"[toml]": { "editor.defaultFormatter": "oxc.oxc-vscode" },
"[css]": { "editor.defaultFormatter": "oxc.oxc-vscode" },
"[scss]": { "editor.defaultFormatter": "oxc.oxc-vscode" },
"[less]": { "editor.defaultFormatter": "oxc.oxc-vscode" },
"[html]": { "editor.defaultFormatter": "oxc.oxc-vscode" },
"[vue]": { "editor.defaultFormatter": "oxc.oxc-vscode" },
"[svelte]": { "editor.defaultFormatter": "oxc.oxc-vscode" },
"[graphql]": { "editor.defaultFormatter": "oxc.oxc-vscode" },
"[handlebars]": { "editor.defaultFormatter": "oxc.oxc-vscode" },
"editor.formatOnSave": true,
"editor.formatOnSaveMode": "file",
"oxc.fmt.configPath": "./vite.config.ts",
// Lint fix
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.fixAll.eslint": "explicit"
},
// Silent the stylistic rules in your IDE, but still auto fix them
"eslint.rules.customizations": [
{ "rule": "style/*", "severity": "off", "fixable": true },
{ "rule": "format/*", "severity": "off", "fixable": true },
{ "rule": "*-indent", "severity": "off", "fixable": true },
{ "rule": "*-spacing", "severity": "off", "fixable": true },
{ "rule": "*-spaces", "severity": "off", "fixable": true },
{ "rule": "*-order", "severity": "off", "fixable": true },
{ "rule": "*-dangle", "severity": "off", "fixable": true },
{ "rule": "*-newline", "severity": "off", "fixable": true },
{ "rule": "*quotes", "severity": "off", "fixable": true },
{ "rule": "*semi", "severity": "off", "fixable": true }
],
// Enable eslint for all supported languages
// Enable ESLint for linted languages
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
"vue",
"html",
"markdown",
"json",
"jsonc",
"yaml",
"toml",
"xml",
"gql",
"graphql",
"astro",
"svelte",
"css",
"less",
"scss",
"pcss",
"postcss"
"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, rely on ESLint (`pnpm lint:fix` preferred) plus `pnpm type-check`, and avoid `any` types.
- **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.
## 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, eslint via @antfu/eslint-config.
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`.
> Architecture patterns, scaffolding recipe, printer chain, strategy pattern, testing conventions, anti-patterns: see **[`ARD.md`]**.
@ -59,7 +59,8 @@ pnpm test # vitest
pnpm test:coverage # with coverage
pnpm type-check # tsc, no emit
pnpm lint # eslint
pnpm lint:fix # eslint --fix
pnpm lint:fix # eslint semantic fixes
vp fmt # format with Oxfmt
pnpm build # production bundle (vp pack)
pnpm tree:gen # regenerate src/commands/tree.ts (registry)
pnpm tree:check # verify tree.ts is up-to-date with the fs

View File

@ -80,7 +80,14 @@ export default class MyCommand extends DifyCommand {
// Authed: authedCtx() sets outputFormat + builds context
const ctx = await this.authedCtx({ format: flags.output })
process.stdout.write(await runMyThing({ /* args */ }, { bundle: ctx.bundle, http: ctx.http, io: ctx.io }))
process.stdout.write(
await runMyThing(
{
/* args */
},
{ bundle: ctx.bundle, http: ctx.http, io: ctx.io },
),
)
}
}
```
@ -102,7 +109,7 @@ import { ErrorCode } from '../../errors/codes.js'
throw new BaseError({
code: ErrorCode.UsageMissingArg,
message: 'workspace id required',
hint: 'pass --workspace or run \'difyctl use workspace <id>\'',
hint: "pass --workspace or run 'difyctl use workspace <id>'",
})
```
@ -150,10 +157,7 @@ export type IOStreams = {
`runWithSpinner` wraps async call with animated spinner on stderr. Auto-disables for structured output — no manual `enabled:` flag needed.
```typescript
const result = await runWithSpinner(
{ io, label: 'Fetching apps' },
() => client.list(params),
)
const result = await runWithSpinner({ io, label: 'Fetching apps' }, () => client.list(params))
```
`STRUCTURED_FORMATS = new Set(['json', 'yaml', 'name'])` drives disable check. New structured format = add to this set only — no other callsites change.
@ -174,9 +178,15 @@ Output rendering separated from data fetching via protocol objects.
```typescript
// handlers.ts — implement the protocol on the data object
export class MyListOutput implements TablePrintable {
tableColumns() { return COLUMNS }
tableRows() { return this.rows.map(r => r.tableRow()) }
json() { return { items: this.rows.map(r => r.json()) } }
tableColumns() {
return COLUMNS
}
tableRows() {
return this.rows.map((r) => r.tableRow())
}
json() {
return { items: this.rows.map((r) => r.json()) }
}
}
// index.ts — wrap and return
@ -215,10 +225,16 @@ One file per resource under `src/api/`. Each exports class wrapping `KyInstance`
```typescript
export class AppsClient {
private readonly http: KyInstance
constructor(http: KyInstance) { this.http = http }
constructor(http: KyInstance) {
this.http = http
}
async list(params: ListParams): Promise<ListResponse> { /* ... */ throw new Error('elided') }
async describe(id: string, workspaceId: string, fields: string[]): Promise<DescribeResponse> { /* ... */ throw new Error('elided') }
async list(params: ListParams): Promise<ListResponse> {
/* ... */ throw new Error('elided')
}
async describe(id: string, workspaceId: string, fields: string[]): Promise<DescribeResponse> {
/* ... */ throw new Error('elided')
}
}
```
@ -292,7 +308,8 @@ expect(JSON.parse(out).workspaces).toHaveLength(2)
| `pnpm test:coverage` | Coverage report |
| `pnpm type-check` | `tsc --noEmit` — catches type errors without build |
| `pnpm lint` | ESLint check |
| `pnpm lint:fix` | ESLint auto-fix (perfectionist sort, chaining) |
| `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 |
@ -306,7 +323,7 @@ expect(JSON.parse(out).workspaces).toHaveLength(2)
## Lint rules that catch contributors
Repo runs `@antfu/eslint-config` + perfectionist + unicorn.
Repo runs `@antfu/eslint-config` for code-quality rules and Vite+ Oxfmt for formatting.
| Rule | What it catches |
| ---------------------------------- | -------------------------------------------------- |
@ -316,7 +333,7 @@ Repo runs `@antfu/eslint-config` + perfectionist + unicorn.
| `unicorn/no-new-array` | Use `Array.from({ length: n })` not `new Array(n)` |
| `noUncheckedIndexedAccess` (tsc) | `arr[i]` is `T \| undefined`; guard before use |
`pnpm lint:fix` resolves perfectionist + chaining auto.
Run `pnpm lint:fix` for ESLint fixes, then `vp fmt` so Oxfmt produces the final layout.
---

View File

@ -6,13 +6,31 @@ import markdownPreferences from 'eslint-plugin-markdown-preferences'
export default antfu(
{
ignores: original => [
'context/**',
'docs/**',
'dist/**',
'coverage/**',
...original,
],
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'],
@ -26,36 +44,30 @@ export default antfu(
'test/prefer-lowercase-title': 'off',
},
},
stylistic: {
overrides: {
'antfu/top-level-function': 'off',
},
},
e18e: false,
},
markdownPreferences.configs.standard,
{
files: [GLOB_MARKDOWN],
plugins: { md },
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/ordered-list-marker-sequence': [
'error',
{ increment: 'never' },
],
'markdown-preferences/definitions-last': 'error',
'markdown-preferences/sort-definitions': 'error',
},
},
{
rules: {
'node/prefer-global/process': 'off',
'unicorn/number-literal-case': 'off',
},
},
{
@ -67,14 +79,22 @@ export default antfu(
{
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.',
},
],
}],
'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

@ -38,6 +38,7 @@ registry discovery and from coverage checks.
## Adding an agent guide
<!-- prettier-ignore -->
1. Create `src/commands/<topic>/<verb>/guide.ts` exporting a plain string:
```ts
export const agentGuide = `

View File

@ -790,9 +790,6 @@
"react/set-state-in-effect": {
"count": 6
},
"style/multiline-ternary": {
"count": 2
},
"ts/no-explicit-any": {
"count": 5
}
@ -1091,9 +1088,6 @@
}
},
"web/app/components/base/chat/chat/answer/agent-content.tsx": {
"style/multiline-ternary": {
"count": 2
},
"ts/no-explicit-any": {
"count": 1
}
@ -5551,11 +5545,6 @@
"count": 2
}
},
"web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/generated-result.tsx": {
"style/multiline-ternary": {
"count": 2
}
},
"web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/context.tsx": {
"react-refresh/only-export-components": {
"count": 2
@ -7160,4 +7149,4 @@
"count": 2
}
}
}
}

View File

@ -18,7 +18,31 @@ const GENERATED_IGNORES = [
export default antfu(
{
ignores: original => [
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/**',
@ -43,36 +67,34 @@ export default antfu(
'test/prefer-lowercase-title': 'off',
},
},
stylistic: {
overrides: {
'antfu/top-level-function': 'off',
},
},
e18e: false,
},
markdownPreferences.configs.standard,
{
files: [GLOB_MARKDOWN],
plugins: { md },
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/ordered-list-marker-sequence': [
'error',
{ increment: 'never' },
],
'markdown-preferences/definitions-last': 'error',
'markdown-preferences/sort-definitions': 'error',
},
},
{
rules: {
'node/prefer-global/process': 'off',
'unicorn/number-literal-case': 'off',
},
},
)
).override('antfu/sort/package-json', {
rules: {
'jsonc/sort-keys': 'off',
},
})

View File

@ -1,8 +1,32 @@
{
"name": "dify",
"type": "module",
"private": true,
"packageManager": "pnpm@11.10.0",
"type": "module",
"scripts": {
"dev": "concurrently -k -n vinext,proxy \"vp run dify-web#dev:vinext\" \"vp run dify-web#dev:proxy\"",
"fmt": "vp fmt",
"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"
},
"devDependencies": {
"@antfu/eslint-config": "catalog:",
"@iconify-json/heroicons": "catalog:",
"@iconify-json/ri": "catalog:",
"@typescript/native": "catalog:",
"concurrently": "catalog:",
"eslint": "catalog:",
"eslint-markdown": "catalog:",
"eslint-plugin-markdown-preferences": "catalog:",
"eslint-plugin-no-barrel-files": "catalog:",
"typescript": "catalog:",
"vite": "catalog:",
"vite-plus": "catalog:"
},
"devEngines": {
"runtime": {
"name": "node",
@ -13,25 +37,5 @@
"engines": {
"node": "^22.22.1"
},
"scripts": {
"dev": "concurrently -k -n vinext,proxy \"vp run dify-web#dev:vinext\" \"vp run dify-web#dev:proxy\"",
"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"
},
"devDependencies": {
"@antfu/eslint-config": "catalog:",
"@typescript/native": "catalog:",
"concurrently": "catalog:",
"eslint": "catalog:",
"eslint-markdown": "catalog:",
"eslint-plugin-markdown-preferences": "catalog:",
"eslint-plugin-no-barrel-files": "catalog:",
"typescript": "catalog:",
"vite": "catalog:",
"vite-plus": "catalog:"
}
"packageManager": "pnpm@11.10.0"
}

View File

@ -61,20 +61,19 @@ const isOpenApiSchema = (value: unknown): value is OpenApiSchema => {
}
const asOpenApiOperation = (value: unknown): OpenApiOperation | undefined => {
return isObject(value) ? value as OpenApiOperation : undefined
return isObject(value) ? (value as OpenApiOperation) : undefined
}
const asOpenApiResponse = (value: unknown): OpenApiResponse | undefined => {
return isObject(value) ? value as OpenApiResponse : undefined
return isObject(value) ? (value as OpenApiResponse) : undefined
}
const asOpenApiMediaType = (value: unknown): OpenApiMediaType | undefined => {
return isObject(value) ? value as OpenApiMediaType : undefined
return isObject(value) ? (value as OpenApiMediaType) : undefined
}
const stripConsoleApiPrefix = (routePath: string) => {
if (isConsoleApiPath(routePath))
return routePath.replace('/console/api', '')
if (isConsoleApiPath(routePath)) return routePath.replace('/console/api', '')
return routePath
}
@ -88,8 +87,7 @@ const stripSchemaNamePrefix = (schemaName: string) => {
}
const contractTagSegment = (tag?: string) => {
if (tag === 'EnterpriseAppDeployConsole')
return 'AppDeploy'
if (tag === 'EnterpriseAppDeployConsole') return 'AppDeploy'
return tag || 'default'
}
@ -109,13 +107,11 @@ const contractPathSegments = (operation: ContractOperation) => {
}
const hasSchemaLessResponseContent = (operation: OpenApiOperation) => {
if (!isObject(operation.responses))
return false
if (!isObject(operation.responses)) return false
return Object.values(operation.responses).some((response) => {
const openApiResponse = asOpenApiResponse(response)
if (!openApiResponse || !isObject(openApiResponse.content))
return false
if (!openApiResponse || !isObject(openApiResponse.content)) return false
return Object.values(openApiResponse.content).some((mediaType) => {
const openApiMediaType = asOpenApiMediaType(mediaType)
@ -129,8 +125,7 @@ const hasSchemaLessResponseContent = (operation: OpenApiOperation) => {
const stripSchemaLessResponseOperations = (pathItem: OpenApiPathItem) => {
return Object.fromEntries(
Object.entries(pathItem).filter(([method, operation]) => {
if (!operationMethods.has(method.toLowerCase()))
return true
if (!operationMethods.has(method.toLowerCase())) return true
const openApiOperation = asOpenApiOperation(operation)
return !openApiOperation || !hasSchemaLessResponseContent(openApiOperation)
@ -147,17 +142,16 @@ const toWords = (value: string) => {
}
const toPascalCase = (words: string[]) => {
return words.map(word => `${word.charAt(0).toUpperCase()}${word.slice(1)}`).join('')
return words.map((word) => `${word.charAt(0).toUpperCase()}${word.slice(1)}`).join('')
}
const commonWordPrefix = (values: string[]) => {
const wordLists = values.map(value => value.split('_'))
const wordLists = values.map((value) => value.split('_'))
const firstWords = wordLists[0] ?? []
const prefix: string[] = []
for (const [index, word] of firstWords.entries()) {
if (!wordLists.every(words => words[index] === word))
break
if (!wordLists.every((words) => words[index] === word)) break
prefix.push(word)
}
@ -166,22 +160,19 @@ const commonWordPrefix = (values: string[]) => {
}
const enumSchemaNameFromValues = (values: unknown[]) => {
if (values.length === 0 || !values.every(value => typeof value === 'string'))
return undefined
if (values.length === 0 || !values.every((value) => typeof value === 'string')) return undefined
const prefix = commonWordPrefix(values)
if (prefix.length < 2)
return undefined
if (prefix.length < 2) return undefined
return toPascalCase(prefix.map(word => word.toLowerCase()))
return toPascalCase(prefix.map((word) => word.toLowerCase()))
}
const findSchemaEntry = (
schemas: Record<string, OpenApiSchema>,
schemaName: string,
): [string, OpenApiSchema] | undefined => {
return Object.entries(schemas)
.find(([name]) => stripSchemaNamePrefix(name) === schemaName)
return Object.entries(schemas).find(([name]) => stripSchemaNamePrefix(name) === schemaName)
}
const enumValuesKey = (values: unknown[]) => JSON.stringify(values)
@ -201,12 +192,10 @@ const enumSchemaKey = (
propertyName: string,
) => {
const existingKey = valuesToSchemaKey.get(valuesKey)
if (existingKey)
return existingKey
if (existingKey) return existingKey
const existingEnumEntry = findSchemaEntry(schemas, preferredName)
if (!existingEnumEntry)
return preferredName
if (!existingEnumEntry) return preferredName
const existingEnumValues = existingEnumEntry[1].enum
if (Array.isArray(existingEnumValues) && enumValuesKey(existingEnumValues) === valuesKey)
@ -223,18 +212,22 @@ const promoteInlineEnumSchema = (
propertySchema: OpenApiSchema,
valuesToSchemaKey: Map<string, string>,
) => {
if (!Array.isArray(propertySchema.enum))
return
if (!Array.isArray(propertySchema.enum)) return
const preferredName = enumSchemaNameFromValues(propertySchema.enum)
if (!preferredName)
return
if (!preferredName) return
const valuesKey = enumValuesKey(propertySchema.enum)
const key = enumSchemaKey(schemas, preferredName, valuesKey, valuesToSchemaKey, schemaName, propertyName)
const key = enumSchemaKey(
schemas,
preferredName,
valuesKey,
valuesToSchemaKey,
schemaName,
propertyName,
)
if (!schemas[key])
schemas[key] = reusableEnumSchema(propertySchema)
if (!schemas[key]) schemas[key] = reusableEnumSchema(propertySchema)
valuesToSchemaKey.set(valuesKey, key)
properties[propertyName] = {
@ -247,21 +240,25 @@ const promoteInlineEnumSchema = (
// runtime enum objects from the generated contract.
const promoteReusableEnumSchemasForHeyApi = (document: OpenApiDocument) => {
const schemas = document.components?.schemas
if (!schemas)
return
if (!schemas) return
const valuesToSchemaKey = new Map<string, string>()
Object.entries(schemas).forEach(([schemaName, schema]) => {
const properties = schema.properties
if (!properties)
return
if (!properties) return
Object.entries(properties).forEach(([propertyName, propertySchema]) => {
if (!isOpenApiSchema(propertySchema))
return
if (!isOpenApiSchema(propertySchema)) return
promoteInlineEnumSchema(schemas, schemaName, properties, propertyName, propertySchema, valuesToSchemaKey)
promoteInlineEnumSchema(
schemas,
schemaName,
properties,
propertyName,
propertySchema,
valuesToSchemaKey,
)
})
})
}
@ -279,8 +276,7 @@ const normalizeEnterpriseOpenApi = () => {
Object.entries(paths)
.filter(([routePath]) => isConsoleApiPath(routePath))
.map(([routePath, pathItem]) => {
if (!isObject(pathItem))
return [stripConsoleApiPrefix(routePath), pathItem]
if (!isObject(pathItem)) return [stripConsoleApiPrefix(routePath), pathItem]
return [stripConsoleApiPrefix(routePath), stripSchemaLessResponseOperations(pathItem)]
})
@ -301,14 +297,14 @@ export default defineConfig({
suffix: '.gen',
},
postProcess: [
{
command: 'vp',
args: ['fmt', '{{path}}'],
},
{
command: 'eslint',
args: ['--fix', '{{path}}/*.ts'],
},
{
command: 'vp',
args: ['fmt', '{{path}}'],
},
],
},
parser: {

View File

@ -1,8 +1,8 @@
{
"name": "@dify/contracts",
"type": "module",
"version": "0.0.0-private",
"private": true,
"type": "module",
"exports": {
"./marketplace": {
"types": "./marketplace.ts",
@ -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 && vp fmt generated/api && eslint --fix 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 && eslint --fix generated/api && 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"

6
pnpm-lock.yaml generated
View File

@ -647,6 +647,12 @@ importers:
'@antfu/eslint-config':
specifier: 'catalog:'
version: 9.1.0(fffcbddf9b9e5649ceb44b1934cdee79)
'@iconify-json/heroicons':
specifier: 'catalog:'
version: 1.2.3
'@iconify-json/ri':
specifier: 'catalog:'
version: 1.2.10
'@typescript/native':
specifier: 'catalog:'
version: typescript@7.0.2

View File

@ -1,45 +1,45 @@
import js from "@eslint/js";
import tsParser from "@typescript-eslint/parser";
import tsPlugin from "@typescript-eslint/eslint-plugin";
import { fileURLToPath } from "node:url";
import path from "node:path";
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 tsconfigRootDir = path.dirname(fileURLToPath(import.meta.url))
const typeCheckedRules =
tsPlugin.configs["recommended-type-checked"]?.rules ??
tsPlugin.configs['recommended-type-checked']?.rules ??
tsPlugin.configs.recommendedTypeChecked?.rules ??
{};
{}
export default [
{
ignores: ["dist", "node_modules", "scripts"],
ignores: ['dist', 'node_modules', 'scripts'],
},
js.configs.recommended,
{
files: ["src/**/*.ts", "tests/**/*.ts"],
files: ['src/**/*.ts', 'tests/**/*.ts'],
languageOptions: {
parser: tsParser,
ecmaVersion: "latest",
ecmaVersion: 'latest',
parserOptions: {
project: "./tsconfig.json",
project: './tsconfig.json',
tsconfigRootDir,
sourceType: "module",
sourceType: 'module',
},
},
plugins: {
"@typescript-eslint": tsPlugin,
'@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" },
'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

@ -28,6 +28,9 @@ const getFetchCall = (
return call as [string, RequestInit | undefined];
};
const getDuplex = (init: RequestInit | undefined) =>
init && "duplex" in init ? init.duplex : undefined;
const toHeaderRecord = (headers: HeadersInit | undefined): Record<string, string> =>
Object.fromEntries(new Headers(headers).entries());
@ -185,7 +188,7 @@ describe("HttpClient", () => {
authorization: "Bearer test",
"content-type": "multipart/form-data; boundary=test",
});
expect((init as RequestInit & { duplex?: string } | undefined)?.duplex).toBe(
expect(getDuplex(init)).toBe(
"half"
);
expect(init?.body).not.toBe(legacyForm);
@ -241,7 +244,7 @@ describe("HttpClient", () => {
});
const [, init] = getFetchCall(fetchMock);
expect((init as RequestInit & { duplex?: string } | undefined)?.duplex).toBe(
expect(getDuplex(init)).toBe(
"half"
);
});
@ -392,7 +395,7 @@ describe("HttpClient", () => {
expect(fetchMock).toHaveBeenCalledTimes(1);
const [, init] = getFetchCall(fetchMock);
expect((init as RequestInit & { duplex?: string } | undefined)?.duplex).toBe(
expect(getDuplex(init)).toBe(
"half"
);
});

View File

@ -509,7 +509,7 @@ export class HttpClient {
const { apiKey, enableLogging, maxRetries, retryDelay, timeout } = this.settings;
if (query) {
validateParams(query as Record<string, unknown>);
validateParams(query);
}
if (isRecord(data) && !Array.isArray(data) && !isFormData(data) && !isPipeableStream(data)) {

View File

@ -1,6 +1,6 @@
import { vi } from "vitest";
import { HttpClient } from "../src/http/client";
import type { DifyClientConfig, DifyResponse } from "../src/types/common";
import type { DifyClientConfig } from "../src/types/common";
type FetchMock = ReturnType<typeof vi.fn>;
type RequestSpy = ReturnType<typeof vi.fn>;
@ -31,7 +31,7 @@ export const createHttpClientWithSpies = (
const { client, fetchMock } = createHttpClient(configOverrides);
const request = vi
.spyOn(client, "request")
.mockResolvedValue({ data: "ok", status: 200, headers: {} } as DifyResponse<string>);
.mockResolvedValue({ data: "ok", status: 200, headers: {} });
const requestStream = vi
.spyOn(client, "requestStream")
.mockResolvedValue({ data: null, status: 200, headers: {} } as never);

View File

@ -1,11 +1,72 @@
import { defineConfig } from 'vite-plus'
const lintFiles = '*.{js,cjs,mjs,jsx,ts,cts,mts,tsx,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 format = 'vp fmt --no-error-on-unmatched-pattern'
const nonFrontendIgnores = [
'.agents/**',
'.devcontainer/**',
'.github/**',
'/*.md',
'api/**',
'codecov.yml',
'depot.json',
'dify-agent/**',
'docker/**',
'docs/**',
'scripts/**',
'sdks/php-client/**',
'sdks/python-client/**',
]
const generatedIgnores = [
'**/.next/**',
'**/.vinext/**',
'**/coverage/**',
'**/dist/**',
'**/storybook-static/**',
'e2e/.auth/**',
'e2e/cucumber-report/**',
'eslint-suppressions.json',
'web/next/**',
'web/next-env.d.ts',
'web/public/embed.min.js',
'web/public/pdf.worker.min.mjs',
'web/public/vs/**',
]
const formatterUnstableInputs = ['web/app/components/develop/template/*.mdx']
export default defineConfig({
staged: {
'*': 'eslint --fix --pass-on-unpruned-suppressions',
[lintFiles]: [lintFix, format],
[formatOnlyFiles]: format,
},
fmt: {
ignorePatterns: [...nonFrontendIgnores, ...generatedIgnores, ...formatterUnstableInputs],
singleQuote: true,
semi: false,
sortImports: {
groups: [
'type-import',
['type-parent', 'type-sibling', 'type-index', 'type-internal', 'type-subpath'],
'value-builtin',
'value-external',
['value-internal', 'value-subpath'],
['value-parent', 'value-sibling', 'value-index'],
['side_effect_style', 'side_effect'],
'unknown',
],
newlinesBetween: false,
sortSideEffects: false,
},
sortPackageJson: true,
sortTailwindcss: {
functions: ['cn', 'clsx', 'cva', 'tw', 'twMerge'],
preserveDuplicates: true,
stylesheet: 'web/app/styles/globals.css',
},
},
})

View File

@ -1,8 +1,10 @@
{
"recommendations": [
"VoidZero.vite-plus-extension-pack",
"bradlc.vscode-tailwindcss",
"kisstkondoros.vscode-codemetrics",
"johnsoncodehk.vscode-tsslint",
"dbaeumer.vscode-eslint"
"dbaeumer.vscode-eslint",
"oxc.oxc-vscode"
]
}

View File

@ -1,4 +1,3 @@
/* eslint-disable style/multiline-ternary */
'use client'
import type { FC } from 'react'
import type { App as AppType } from '@/models/explore'

View File

@ -379,7 +379,6 @@ const BasicAppPreview: FC<Props> = ({
system_parameters: DEFAULT_SYSTEM_PARAMETERS,
dataSets,
agentConfig: appDetail?.mode === 'agent-chat'
// eslint-disable-next-line style/multiline-ternary
? ({
max_iteration: DEFAULT_AGENT_SETTING.max_iteration,
// remove dataset

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any, style/jsx-one-expression-per-line */
/* eslint-disable ts/no-explicit-any */
import type { AgentNodeType } from '../types'
import type { StrategyParamItem } from '@/app/components/plugins/types'
import type { PanelProps } from '@/types/workflow'

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any, style/jsx-one-expression-per-line */
/* eslint-disable ts/no-explicit-any */
import type { AssignerNodeOperation, AssignerNodeType } from '../types'
import type { PanelProps } from '@/types/workflow'
import { fireEvent, render, screen } from '@testing-library/react'

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any, style/jsx-one-expression-per-line */
/* eslint-disable ts/no-explicit-any */
import type { ListFilterNodeType } from '../types'
import type { PanelProps } from '@/types/workflow'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any, style/jsx-one-expression-per-line */
/* eslint-disable ts/no-explicit-any */
import type { QuestionClassifierNodeType, Topic } from '../types'
import type { PanelProps } from '@/types/workflow'
import { fireEvent, render, screen } from '@testing-library/react'

View File

@ -1,4 +1,4 @@
/* eslint-disable ts/no-explicit-any, style/jsx-one-expression-per-line */
/* eslint-disable ts/no-explicit-any */
import type { ScheduleTriggerNodeType } from '../types'
import type { PanelProps } from '@/types/workflow'
import { fireEvent, render, screen } from '@testing-library/react'

View File

@ -1,6 +1,23 @@
# Lint Guide
# Format and Lint Guide
We use ESLint and Typescript to maintain code quality and consistency across the project.
We use Vite+ Oxfmt for formatting, ESLint for code-quality rules, and TypeScript for type safety.
## Format
Format the supported frontend and TypeScript workspace files from the repository root:
```sh
vp fmt
```
Check formatting without writing changes:
```sh
vp fmt --check
```
The shared formatter options and ignore boundaries live in the root `vite.config.ts`.
Editor format-on-save must point Oxc at that file so local edits match the commit hook and CI.
## ESLint
@ -21,8 +38,8 @@ Keep this enabled when linting multiple files.
- [ESLint multi-thread linting blog post]
**`--fix`**: Automatically fixes auto-fixable rule violations.
Keep this enabled so that you do not have to care about auto-fixable errors (e.g., formatting issues) and can focus on more important errors.
**`--fix`**: Automatically fixes code-quality violations such as unused imports and preferred syntax.
Run `vp fmt` after ESLint fixes so Oxfmt produces the final layout.
Always review the diff before committing to ensure no unintended changes.
**`--quiet`**: Suppresses warnings and only shows errors.
@ -38,7 +55,8 @@ Treat this as an escape hatch—fix these errors when time permits.
### The Auto-Fix Workflow and Suppression Strategy
To streamline your development process, we recommend configuring your editor to automatically fix lint errors on save.
As a fallback, the commit hook runs `vp staged`, which applies autofixable ESLint changes to staged files before the commit continues.
Use Oxfmt for format-on-save and ESLint code actions for lint-only fixes.
As a fallback, the commit hook runs `vp staged`, which applies ESLint fixes and then formats staged files with `vp fmt`.
To prevent workflow disruptions, these commit hooks are intentionally bypassed when you are merging branches, rebasing, or cherry-picking.
Additionally, we currently track many existing legacy errors in eslint-suppressions.json.

View File

@ -1,7 +1,13 @@
// @ts-check
import path from 'node:path'
import antfu, { GLOB_MARKDOWN, GLOB_MARKDOWN_CODE, GLOB_TESTS, GLOB_TS, GLOB_TSX } from '@antfu/eslint-config'
import antfu, {
GLOB_MARKDOWN,
GLOB_MARKDOWN_CODE,
GLOB_TESTS,
GLOB_TS,
GLOB_TSX,
} from '@antfu/eslint-config'
import pluginQuery from '@tanstack/eslint-plugin-query'
import md from 'eslint-markdown'
import tailwindcss from 'eslint-plugin-better-tailwindcss'
@ -22,6 +28,30 @@ import dify from './plugins/eslint/index.js'
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',
},
},
react: {
overrides: {
'react/set-state-in-effect': 'error',
@ -42,11 +72,6 @@ export default antfu(
'test/prefer-lowercase-title': 'off',
},
},
stylistic: {
overrides: {
'antfu/top-level-function': 'off',
},
},
e18e: false,
pnpm: false,
},
@ -69,29 +94,28 @@ export default antfu(
'no-barrel-files/no-barrel-files': 'error',
},
},
markdownPreferences.configs.standard,
{
files: [GLOB_MARKDOWN],
plugins: { md },
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/ordered-list-marker-sequence': [
'error',
{ increment: 'never' },
],
'markdown-preferences/definitions-last': 'error',
'markdown-preferences/sort-definitions': 'error',
},
},
{
rules: {
'node/prefer-global/process': 'off',
'unicorn/number-literal-case': 'off',
},
},
{
@ -111,9 +135,7 @@ export default antfu(
tailwindcss,
},
rules: {
'tailwindcss/enforce-consistent-class-order': 'error',
'tailwindcss/no-duplicate-classes': 'error',
'tailwindcss/no-unnecessary-whitespace': 'error',
'tailwindcss/no-unknown-classes': 'warn',
},
settings: {
@ -139,8 +161,8 @@ export default antfu(
{
files: ['i18n/**/*.json'],
rules: {
'max-lines': 'off',
'jsonc/sort-keys': 'error',
'max-lines': 'off',
'hyoban/i18n-flat-key': 'error',
'dify/no-extra-keys': 'error',
@ -152,40 +174,43 @@ export default antfu(
files: [GLOB_TS, GLOB_TSX],
ignores: ['next/**'],
rules: {
'no-restricted-imports': ['error', {
paths: NEXT_PLATFORM_RESTRICTED_IMPORT_PATHS,
patterns: WEB_RESTRICTED_IMPORT_PATTERNS,
}],
'no-restricted-imports': [
'error',
{
paths: NEXT_PLATFORM_RESTRICTED_IMPORT_PATHS,
patterns: WEB_RESTRICTED_IMPORT_PATTERNS,
},
],
},
},
{
name: 'dify/service-base-restricted-imports',
files: ['service/**/*.ts', 'service/**/*.tsx'],
rules: {
'no-restricted-imports': ['error', {
paths: NEXT_PLATFORM_RESTRICTED_IMPORT_PATHS,
patterns: [
...WEB_RESTRICTED_IMPORT_PATTERNS,
...WEB_SERVICE_BASE_RESTRICTED_IMPORT_PATTERNS,
...WEB_SERVICE_FETCH_RESTRICTED_IMPORT_PATTERNS,
],
}],
'no-restricted-imports': [
'error',
{
paths: NEXT_PLATFORM_RESTRICTED_IMPORT_PATHS,
patterns: [
...WEB_RESTRICTED_IMPORT_PATTERNS,
...WEB_SERVICE_BASE_RESTRICTED_IMPORT_PATTERNS,
...WEB_SERVICE_FETCH_RESTRICTED_IMPORT_PATTERNS,
],
},
],
},
},
{
name: 'dify/restricted-local-storage-access',
files: [GLOB_TS, GLOB_TSX],
ignores: [
...GLOB_TESTS,
'vitest.setup.ts',
'instrumentation-client.ts',
],
ignores: [...GLOB_TESTS, 'vitest.setup.ts', 'instrumentation-client.ts'],
rules: {
'no-restricted-globals': [
'error',
{
name: 'localStorage',
message: 'Do not use localStorage directly. Use a foxact storage boundary instead; prefer feature-owned createLocalStorageState for shared storage.',
message:
'Do not use localStorage directly. Use a foxact storage boundary instead; prefer feature-owned createLocalStorageState for shared storage.',
},
],
'no-restricted-properties': [
@ -193,21 +218,28 @@ export default antfu(
{
object: 'window',
property: 'localStorage',
message: 'Do not use window.localStorage directly. Use a foxact storage boundary instead; prefer feature-owned createLocalStorageState for shared storage.',
message:
'Do not use window.localStorage directly. Use a foxact storage boundary instead; prefer feature-owned createLocalStorageState for shared storage.',
},
{
object: 'globalThis',
property: 'localStorage',
message: 'Do not use globalThis.localStorage directly. Use a foxact storage boundary instead; prefer feature-owned createLocalStorageState for shared storage.',
message:
'Do not use globalThis.localStorage directly. Use a foxact storage boundary instead; prefer feature-owned createLocalStorageState for shared storage.',
},
],
'no-restricted-syntax': [
'error',
{
selector: 'ImportDeclaration[source.value="ahooks"] ImportSpecifier[imported.name="useLocalStorageState"]',
selector:
'ImportDeclaration[source.value="ahooks"] ImportSpecifier[imported.name="useLocalStorageState"]',
message: 'Do not use ahooks useLocalStorageState. Use foxact storage hooks instead.',
},
],
},
},
)
).override('antfu/sort/package-json', {
rules: {
'jsonc/sort-keys': 'off',
},
})

View File

@ -172,7 +172,7 @@ Translation is handled automatically by Claude Code GitHub Actions. When changes
- **DELETE**: Removed keys that need to be deleted from other languages
1. Runs `i18n:check` to verify the initial sync status.
1. Translates missing/updated keys while preserving placeholders (`{{var}}`, `${var}`, `<tag>`) and removes deleted keys.
1. Runs `lint:fix` to sort JSON keys and `i18n:check` again to ensure everything is synchronized.
1. Runs `vp fmt` on edited locale files and `i18n:check` again to ensure everything is synchronized.
1. Creates a PR with the translations.
### Manual Trigger

View File

@ -1,26 +1,14 @@
{
"name": "dify-web",
"type": "module",
"version": "1.16.0-rc1",
"private": true,
"type": "module",
"imports": {
"#i18n": {
"react-server": "./i18n-config/lib.server.ts",
"default": "./i18n-config/lib.client.ts"
}
},
"browserslist": [
"last 1 Chrome version",
"last 1 Firefox version",
"last 1 Edge version",
"last 1 Safari version",
"iOS >=15",
"Android >= 10",
"and_chr >= 126",
"and_ff >= 137",
"and_uc >= 15.5",
"and_qq >= 14.9"
],
"scripts": {
"analyze": "next experimental-analyze",
"build": "next build",
@ -29,8 +17,8 @@
"dev:inspect": "next dev --inspect",
"dev:proxy": "dev-proxy --config ./dev-proxy.config.ts --env-file ./.env.local",
"dev:vinext": "vinext dev",
"gen-doc-paths": "tsx ./scripts/gen-doc-paths.ts",
"gen-icons": "pnpm --filter @dify/iconify-collections generate && node ./scripts/gen-icons.mjs && eslint --fix app/components/base/icons/src/",
"gen-doc-paths": "tsx ./scripts/gen-doc-paths.ts && vp fmt types/doc-paths.ts",
"gen-icons": "pnpm --filter @dify/iconify-collections generate && node ./scripts/gen-icons.mjs && eslint --fix app/components/base/icons/src/ && vp fmt app/components/base/icons/src/",
"i18n:check": "tsx ./scripts/check-i18n.js",
"i18n:migrate-selectors": "tsx ./scripts/migrate-i18n-selectors.ts",
"i18n:prune-unused": "tsx ./scripts/prune-unused-i18n.ts",
@ -233,5 +221,17 @@
"vite-plus": "catalog:",
"vitest": "catalog:",
"vitest-canvas-mock": "catalog:"
}
},
"browserslist": [
"last 1 Chrome version",
"last 1 Firefox version",
"last 1 Edge version",
"last 1 Safari version",
"iOS >=15",
"Android >= 10",
"and_chr >= 126",
"and_ff >= 137",
"and_uc >= 15.5",
"and_qq >= 14.9"
]
}