mirror of
https://github.com/langgenius/dify.git
synced 2026-07-21 18:58:35 +08:00
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:
parent
594c21d98c
commit
b54107e9b9
27
.github/workflows/autofix.yml
vendored
27
.github/workflows/autofix.yml
vendored
@ -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
|
||||
|
||||
8
.github/workflows/style.yml
vendored
8
.github/workflows/style.yml
vendored
@ -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
|
||||
|
||||
4
.github/workflows/translate-i18n-claude.yml
vendored
4
.github/workflows/translate-i18n-claude.yml
vendored
@ -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',
|
||||
'',
|
||||
|
||||
65
.vscode/settings.example.json
vendored
65
.vscode/settings.example.json
vendored
@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
47
cli/ARD.md
47
cli/ARD.md
@ -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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -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',
|
||||
},
|
||||
})
|
||||
|
||||
@ -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 = `
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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',
|
||||
},
|
||||
})
|
||||
|
||||
50
package.json
50
package.json
@ -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"
|
||||
}
|
||||
|
||||
@ -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: {
|
||||
|
||||
@ -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
6
pnpm-lock.yaml
generated
@ -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
|
||||
|
||||
@ -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' },
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
]
|
||||
|
||||
@ -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"
|
||||
);
|
||||
});
|
||||
|
||||
@ -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)) {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
4
web/.vscode/extensions.json
vendored
4
web/.vscode/extensions.json
vendored
@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
/* eslint-disable style/multiline-ternary */
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import type { App as AppType } from '@/models/explore'
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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',
|
||||
},
|
||||
})
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user