diff --git a/oxlint-suppressions.json b/oxlint-suppressions.json index 03ac29fde19..1109f92dbb3 100644 --- a/oxlint-suppressions.json +++ b/oxlint-suppressions.json @@ -37,11 +37,26 @@ "count": 1 } }, + "web/__tests__/base/file-uploader-flow.test.tsx": { + "typescript/no-deprecated": { + "count": 1 + } + }, "web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-config-modal.tsx": { "shadcn/no-restyle": { "count": 2 } }, + "web/app/(commonLayout)/plugins/page.tsx": { + "no-restricted-globals": { + "count": 1 + } + }, + "web/app/(shareLayout)/webapp-signin/check-code/page.tsx": { + "typescript/no-deprecated": { + "count": 1 + } + }, "web/app/(shareLayout)/webapp-signin/normalForm.tsx": { "jsx-a11y/click-events-have-key-events": { "count": 2 @@ -50,6 +65,16 @@ "count": 2 } }, + "web/app/__tests__/home-banner.browser.spec.tsx": { + "typescript/no-deprecated": { + "count": 4 + } + }, + "web/app/auth/refresh/route.ts": { + "no-restricted-globals": { + "count": 1 + } + }, "web/app/components/app/annotation/add-annotation-modal/edit-item/index.tsx": { "jsx-a11y/click-events-have-key-events": { "count": 1 @@ -97,6 +122,11 @@ "count": 1 } }, + "web/app/components/app/annotation/header-opts/__tests__/index.spec.tsx": { + "typescript/no-deprecated": { + "count": 2 + } + }, "web/app/components/app/annotation/index.tsx": { "eslint-react/set-state-in-effect": { "count": 1 @@ -120,6 +150,11 @@ "count": 1 } }, + "web/app/components/app/configuration/__tests__/configuration-flow.spec.tsx": { + "typescript/no-deprecated": { + "count": 2 + } + }, "web/app/components/app/configuration/config-prompt/advanced-prompt-input.tsx": { "typescript/no-explicit-any": { "count": 2 @@ -148,6 +183,11 @@ "count": 1 } }, + "web/app/components/app/configuration/config-var/config-select/__tests__/index.spec.tsx": { + "typescript/no-deprecated": { + "count": 1 + } + }, "web/app/components/app/configuration/config-var/index.tsx": { "dify/require-disable-directive-description": { "count": 1 @@ -230,6 +270,16 @@ "count": 1 } }, + "web/app/components/app/configuration/debug/__tests__/index.spec.tsx": { + "typescript/no-deprecated": { + "count": 2 + } + }, + "web/app/components/app/configuration/debug/debug-with-multiple-model/__tests__/index.spec.tsx": { + "typescript/no-deprecated": { + "count": 2 + } + }, "web/app/components/app/configuration/debug/debug-with-multiple-model/chat-item.tsx": { "typescript/no-explicit-any": { "count": 5 @@ -245,6 +295,11 @@ "count": 8 } }, + "web/app/components/app/configuration/debug/debug-with-single-model/__tests__/index.spec.tsx": { + "typescript/no-deprecated": { + "count": 2 + } + }, "web/app/components/app/configuration/debug/debug-with-single-model/index.tsx": { "typescript/no-explicit-any": { "count": 4 @@ -273,6 +328,11 @@ "count": 1 } }, + "web/app/components/app/deploy/shared/deployment-configuration/use-deployment-configuration-form.ts": { + "typescript/no-deprecated": { + "count": 1 + } + }, "web/app/components/app/log/filter.tsx": { "react/only-export-components": { "count": 1 @@ -286,6 +346,16 @@ "count": 2 } }, + "web/app/components/app/overview/embedded/index.tsx": { + "typescript/no-deprecated": { + "count": 1 + } + }, + "web/app/components/app/overview/workflow-launch-dialog/index.tsx": { + "typescript/no-deprecated": { + "count": 1 + } + }, "web/app/components/app/text-generate/item/index.tsx": { "dify/require-disable-directive-description": { "count": 4 @@ -317,6 +387,11 @@ "count": 2 } }, + "web/app/components/apps/app-card/interactions.tsx": { + "typescript/no-deprecated": { + "count": 1 + } + }, "web/app/components/base/agent-log-modal/index.tsx": { "eslint-react/set-state-in-effect": { "count": 1 @@ -338,6 +413,16 @@ "count": 2 } }, + "web/app/components/base/amplitude/runtime.ts": { + "typescript/no-deprecated": { + "count": 1 + } + }, + "web/app/components/base/amplitude/web-app-client.ts": { + "typescript/no-deprecated": { + "count": 2 + } + }, "web/app/components/base/audio-btn/audio.player.manager.ts": { "dify/require-disable-directive-description": { "count": 1 @@ -352,6 +437,9 @@ "jsx-a11y/media-has-caption": { "count": 1 }, + "no-restricted-globals": { + "count": 1 + }, "typescript/no-explicit-any": { "count": 2 } @@ -364,6 +452,11 @@ "count": 1 } }, + "web/app/components/base/carousel/__tests__/index.spec.tsx": { + "typescript/no-deprecated": { + "count": 1 + } + }, "web/app/components/base/carousel/index.tsx": { "eslint-react/set-state-in-effect": { "count": 3 @@ -372,6 +465,11 @@ "count": 1 } }, + "web/app/components/base/chat/chat-with-history/__tests__/chat-wrapper.spec.tsx": { + "typescript/no-deprecated": { + "count": 2 + } + }, "web/app/components/base/chat/chat-with-history/chat-wrapper.tsx": { "typescript/no-explicit-any": { "count": 7 @@ -410,6 +508,9 @@ "web/app/components/base/chat/chat/__tests__/hooks.spec.tsx": { "no-restricted-imports": { "count": 1 + }, + "typescript/no-deprecated": { + "count": 91 } }, "web/app/components/base/chat/chat/answer/workflow-process.tsx": { @@ -429,6 +530,9 @@ "no-restricted-imports": { "count": 2 }, + "typescript/no-deprecated": { + "count": 2 + }, "typescript/no-explicit-any": { "count": 9 } @@ -528,9 +632,22 @@ "count": 1 } }, + "web/app/components/base/file-uploader/__tests__/hooks.spec.ts": { + "typescript/no-deprecated": { + "count": 1 + } + }, "web/app/components/base/file-uploader/__tests__/utils.spec.ts": { "no-restricted-imports": { "count": 1 + }, + "typescript/no-deprecated": { + "count": 6 + } + }, + "web/app/components/base/file-uploader/file-from-link-or-local/__tests__/index.spec.tsx": { + "typescript/no-deprecated": { + "count": 1 } }, "web/app/components/base/file-uploader/file-preview-content.tsx": { @@ -562,14 +679,33 @@ } }, "web/app/components/base/file-uploader/utils.ts": { + "no-restricted-globals": { + "count": 1 + }, "no-restricted-imports": { "count": 1 }, + "typescript/no-deprecated": { + "count": 1 + }, "typescript/no-explicit-any": { "count": 1 } }, + "web/app/components/base/form/components/base/__tests__/base-form.spec.tsx": { + "typescript/no-deprecated": { + "count": 1 + } + }, + "web/app/components/base/form/components/base/base-field.tsx": { + "typescript/no-deprecated": { + "count": 3 + } + }, "web/app/components/base/form/components/base/base-form.tsx": { + "typescript/no-deprecated": { + "count": 3 + }, "typescript/no-explicit-any": { "count": 6 } @@ -584,11 +720,31 @@ "count": 1 } }, + "web/app/components/base/form/components/form/actions.tsx": { + "typescript/no-deprecated": { + "count": 1 + } + }, + "web/app/components/base/form/form-scenarios/base/field.tsx": { + "typescript/no-deprecated": { + "count": 1 + } + }, "web/app/components/base/form/form-scenarios/base/types.ts": { "typescript/no-explicit-any": { "count": 1 } }, + "web/app/components/base/form/form-scenarios/base/utils.ts": { + "typescript/no-deprecated": { + "count": 1 + } + }, + "web/app/components/base/form/form-scenarios/input-field/field.tsx": { + "typescript/no-deprecated": { + "count": 1 + } + }, "web/app/components/base/form/form-scenarios/input-field/types.ts": { "typescript/no-explicit-any": { "count": 2 @@ -609,14 +765,43 @@ "count": 15 } }, - "web/app/components/base/image-uploader/image-preview.tsx": { - "jsx-a11y/no-static-element-interactions": { + "web/app/components/base/icon-picker/__tests__/image-crop.spec.ts": { + "typescript/no-deprecated": { "count": 1 } }, + "web/app/components/base/icon-picker/emoji-data.ts": { + "no-restricted-globals": { + "count": 1 + } + }, + "web/app/components/base/image-uploader/__tests__/uploader.spec.tsx": { + "typescript/no-deprecated": { + "count": 4 + } + }, + "web/app/components/base/image-uploader/__tests__/utils.spec.ts": { + "typescript/no-deprecated": { + "count": 1 + } + }, + "web/app/components/base/image-uploader/image-preview.tsx": { + "jsx-a11y/no-static-element-interactions": { + "count": 1 + }, + "no-restricted-globals": { + "count": 2 + } + }, "web/app/components/base/image-uploader/utils.ts": { + "no-restricted-globals": { + "count": 1 + }, "no-restricted-imports": { "count": 1 + }, + "typescript/no-deprecated": { + "count": 1 } }, "web/app/components/base/markdown-blocks/__tests__/paragraph.spec.tsx": { @@ -634,6 +819,11 @@ "count": 2 } }, + "web/app/components/base/markdown-blocks/form.tsx": { + "typescript/no-deprecated": { + "count": 1 + } + }, "web/app/components/base/markdown-blocks/index.ts": { "no-barrel-files/no-barrel-files": { "count": 9 @@ -669,6 +859,11 @@ "count": 5 } }, + "web/app/components/base/markdown-with-directive/components/markdown-with-directive-schema.ts": { + "typescript/no-deprecated": { + "count": 1 + } + }, "web/app/components/base/node-status/index.tsx": { "react/only-export-components": { "count": 1 @@ -687,6 +882,16 @@ "count": 2 } }, + "web/app/components/base/prompt-editor/__tests__/hooks.spec.tsx": { + "typescript/no-deprecated": { + "count": 1 + } + }, + "web/app/components/base/prompt-editor/__tests__/utils.spec.ts": { + "typescript/no-deprecated": { + "count": 7 + } + }, "web/app/components/base/prompt-editor/plugins/component-picker-block/index.tsx": { "no-restricted-imports": { "count": 1 @@ -837,6 +1042,9 @@ "web/app/components/base/text-generation/hooks.ts": { "no-restricted-imports": { "count": 1 + }, + "typescript/no-deprecated": { + "count": 1 } }, "web/app/components/base/text-generation/types.ts": { @@ -866,6 +1074,11 @@ "count": 1 } }, + "web/app/components/base/voice-input/mp3-encoder.ts": { + "typescript/no-deprecated": { + "count": 1 + } + }, "web/app/components/base/zendesk/runtime.ts": { "dify/require-disable-directive-description": { "count": 1 @@ -876,11 +1089,21 @@ "count": 3 } }, + "web/app/components/custom/custom-web-app-brand/__tests__/index.spec.tsx": { + "typescript/no-deprecated": { + "count": 1 + } + }, "web/app/components/datasets/common/document-status-with-action/status-with-action.tsx": { "eslint-react/static-components": { "count": 2 } }, + "web/app/components/datasets/common/image-previewer/index.tsx": { + "no-restricted-globals": { + "count": 1 + } + }, "web/app/components/datasets/common/image-uploader/image-uploader-in-chunk/image-input.tsx": { "jsx-a11y/click-events-have-key-events": { "count": 1 @@ -931,9 +1154,25 @@ "count": 1 } }, + "web/app/components/datasets/create/__tests__/index.spec.tsx": { + "typescript/no-deprecated": { + "count": 1 + } + }, + "web/app/components/datasets/create/file-uploader/__tests__/index.spec.tsx": { + "typescript/no-deprecated": { + "count": 1 + } + }, "web/app/components/datasets/create/file-uploader/hooks/use-file-upload.ts": { + "no-restricted-globals": { + "count": 1 + }, "no-restricted-imports": { "count": 1 + }, + "typescript/no-deprecated": { + "count": 1 } }, "web/app/components/datasets/create/step-one/components/index.ts": { @@ -1028,6 +1267,11 @@ "count": 4 } }, + "web/app/components/datasets/documents/create-from-pipeline/__tests__/index.spec.tsx": { + "typescript/no-deprecated": { + "count": 1 + } + }, "web/app/components/datasets/documents/create-from-pipeline/data-source-options/option-card.tsx": { "jsx-a11y/click-events-have-key-events": { "count": 1 @@ -1044,6 +1288,11 @@ "count": 1 } }, + "web/app/components/datasets/documents/create-from-pipeline/data-source/local-file/__tests__/index.spec.tsx": { + "typescript/no-deprecated": { + "count": 1 + } + }, "web/app/components/datasets/documents/create-from-pipeline/data-source/local-file/components/file-list-item.tsx": { "jsx-a11y/click-events-have-key-events": { "count": 2 @@ -1056,6 +1305,9 @@ "no-restricted-imports": { "count": 1 }, + "typescript/no-deprecated": { + "count": 1 + }, "typescript/no-explicit-any": { "count": 1 } @@ -1074,6 +1326,9 @@ }, "no-restricted-imports": { "count": 1 + }, + "typescript/no-deprecated": { + "count": 1 } }, "web/app/components/datasets/documents/create-from-pipeline/data-source/store/provider.tsx": { @@ -1104,6 +1359,9 @@ "no-restricted-imports": { "count": 1 }, + "typescript/no-deprecated": { + "count": 1 + }, "typescript/no-explicit-any": { "count": 2 } @@ -1117,6 +1375,9 @@ "eslint-react/static-components": { "count": 2 }, + "typescript/no-deprecated": { + "count": 1 + }, "typescript/no-explicit-any": { "count": 3 } @@ -1137,8 +1398,14 @@ } }, "web/app/components/datasets/documents/detail/batch-modal/csv-uploader.tsx": { + "no-restricted-globals": { + "count": 1 + }, "no-restricted-imports": { "count": 1 + }, + "typescript/no-deprecated": { + "count": 2 } }, "web/app/components/datasets/documents/detail/completed/child-segment-list.tsx": { @@ -1188,6 +1455,9 @@ "web/app/components/datasets/documents/detail/completed/segment-list.tsx": { "eslint-react/static-components": { "count": 2 + }, + "typescript/no-deprecated": { + "count": 1 } }, "web/app/components/datasets/documents/detail/embedding/components/index.ts": { @@ -1220,6 +1490,11 @@ "count": 3 } }, + "web/app/components/datasets/external-api/external-api-modal/Form.tsx": { + "typescript/no-deprecated": { + "count": 1 + } + }, "web/app/components/datasets/external-knowledge-base/create/ExternalApiSelect.tsx": { "eslint-react/set-state-in-effect": { "count": 1 @@ -1325,15 +1600,33 @@ } }, "web/app/components/header/account-setting/data-source-page-new/hooks/use-marketplace-all-plugins.ts": { + "typescript/no-deprecated": { + "count": 1 + }, "typescript/no-explicit-any": { "count": 1 } }, + "web/app/components/header/account-setting/members-page/__tests__/invite-button.spec.tsx": { + "typescript/no-deprecated": { + "count": 1 + } + }, + "web/app/components/header/account-setting/model-provider-page/__tests__/hooks.spec.ts": { + "typescript/no-deprecated": { + "count": 1 + } + }, "web/app/components/header/account-setting/model-provider-page/declarations.ts": { "typescript/no-explicit-any": { "count": 4 } }, + "web/app/components/header/account-setting/model-provider-page/hooks.ts": { + "typescript/no-deprecated": { + "count": 1 + } + }, "web/app/components/header/account-setting/model-provider-page/model-auth/hooks/index.ts": { "no-barrel-files/no-barrel-files": { "count": 6 @@ -1379,9 +1672,32 @@ "count": 2 } }, + "web/app/components/header/account-setting/preference-page/__tests__/index.spec.tsx": { + "typescript/no-deprecated": { + "count": 1 + } + }, + "web/app/components/header/account-setting/resource-access-token-page/token-dialog.tsx": { + "typescript/no-deprecated": { + "count": 1 + } + }, "web/app/components/header/github-star/index.tsx": { "@tanstack/query/prefer-query-options": { "count": 1 + }, + "no-restricted-globals": { + "count": 1 + } + }, + "web/app/components/integrations/__tests__/page.spec.tsx": { + "typescript/no-deprecated": { + "count": 2 + } + }, + "web/app/components/main-nav/__tests__/responsive-main-nav.spec.tsx": { + "typescript/no-deprecated": { + "count": 2 } }, "web/app/components/main-nav/components/workspace-switcher.tsx": { @@ -1390,6 +1706,9 @@ } }, "web/app/components/plugins/install-plugin/hooks.ts": { + "no-restricted-globals": { + "count": 1 + }, "typescript/no-explicit-any": { "count": 4 } @@ -1409,6 +1728,21 @@ "count": 2 } }, + "web/app/components/plugins/marketplace/__tests__/hooks.spec.tsx": { + "typescript/no-deprecated": { + "count": 2 + } + }, + "web/app/components/plugins/marketplace/__tests__/search-params.spec.ts": { + "typescript/no-deprecated": { + "count": 9 + } + }, + "web/app/components/plugins/marketplace/home/__tests__/home-trending.spec.tsx": { + "typescript/no-deprecated": { + "count": 4 + } + }, "web/app/components/plugins/marketplace/home/home-trending.tsx": { "jsx-a11y/no-static-element-interactions": { "count": 1 @@ -1420,6 +1754,9 @@ }, "no-restricted-imports": { "count": 1 + }, + "typescript/no-deprecated": { + "count": 1 } }, "web/app/components/plugins/marketplace/list/carousel.tsx": { @@ -1432,6 +1769,11 @@ "count": 1 } }, + "web/app/components/plugins/plugin-auth/authorize/oauth-client-settings.tsx": { + "typescript/no-deprecated": { + "count": 1 + } + }, "web/app/components/plugins/plugin-auth/authorized/item.tsx": { "jsx-a11y/click-events-have-key-events": { "count": 1 @@ -1520,6 +1862,9 @@ "eslint-react/static-components": { "count": 2 }, + "typescript/no-deprecated": { + "count": 1 + }, "typescript/no-explicit-any": { "count": 1 } @@ -1542,7 +1887,15 @@ "count": 2 } }, + "web/app/components/rag-pipeline/components/panel/input-field/editor/form/schema.ts": { + "typescript/no-deprecated": { + "count": 11 + } + }, "web/app/components/rag-pipeline/components/panel/input-field/editor/form/show-all-settings.tsx": { + "typescript/no-deprecated": { + "count": 1 + }, "typescript/no-explicit-any": { "count": 1 } @@ -1562,9 +1915,17 @@ "count": 2 } }, + "web/app/components/rag-pipeline/components/panel/test-run/preparation/__tests__/index.spec.tsx": { + "typescript/no-deprecated": { + "count": 2 + } + }, "web/app/components/rag-pipeline/components/panel/test-run/preparation/document-processing/options.tsx": { "eslint-react/static-components": { "count": 2 + }, + "typescript/no-deprecated": { + "count": 1 } }, "web/app/components/rag-pipeline/components/panel/test-run/preparation/index.tsx": { @@ -1592,15 +1953,26 @@ "count": 1 } }, + "web/app/components/rag-pipeline/hooks/__tests__/use-pipeline-init.spec.tsx": { + "typescript/no-deprecated": { + "count": 1 + } + }, "web/app/components/rag-pipeline/hooks/use-nodes-sync-draft.ts": { "no-restricted-imports": { "count": 1 + }, + "typescript/no-deprecated": { + "count": 1 } }, "web/app/components/rag-pipeline/hooks/use-pipeline-run.ts": { "no-restricted-imports": { "count": 2 }, + "typescript/no-deprecated": { + "count": 1 + }, "typescript/no-explicit-any": { "count": 1 } @@ -1638,6 +2010,14 @@ "web/app/components/share/text-generation/result/__tests__/workflow-stream-handlers.spec.ts": { "no-restricted-imports": { "count": 1 + }, + "typescript/no-deprecated": { + "count": 1 + } + }, + "web/app/components/share/text-generation/result/hooks/use-result-run-state.ts": { + "typescript/no-deprecated": { + "count": 1 } }, "web/app/components/share/text-generation/result/index.tsx": { @@ -1648,6 +2028,9 @@ "web/app/components/share/text-generation/result/workflow-stream-handlers.ts": { "no-restricted-imports": { "count": 2 + }, + "typescript/no-deprecated": { + "count": 1 } }, "web/app/components/share/text-generation/run-batch/csv-download/index.tsx": { @@ -1675,6 +2058,9 @@ "eslint-react/set-state-in-effect": { "count": 1 }, + "typescript/no-deprecated": { + "count": 1 + }, "typescript/no-explicit-any": { "count": 3 } @@ -1687,14 +2073,25 @@ "count": 1 } }, + "web/app/components/snippets/components/__tests__/snippet-main.spec.tsx": { + "typescript/no-deprecated": { + "count": 1 + } + }, "web/app/components/snippets/hooks/use-nodes-sync-draft.ts": { "dify/require-disable-directive-description": { "count": 1 + }, + "typescript/no-deprecated": { + "count": 1 } }, "web/app/components/snippets/hooks/use-snippet-run.ts": { "no-restricted-imports": { "count": 2 + }, + "typescript/no-deprecated": { + "count": 1 } }, "web/app/components/tools/edit-custom-collection-modal/index.tsx": { @@ -1710,6 +2107,16 @@ "count": 1 } }, + "web/app/components/tools/marketplace/hooks.ts": { + "typescript/no-deprecated": { + "count": 2 + } + }, + "web/app/components/tools/mcp/__tests__/modal.spec.tsx": { + "typescript/no-deprecated": { + "count": 1 + } + }, "web/app/components/tools/setting/build-in/config-credentials.tsx": { "typescript/no-explicit-any": { "count": 3 @@ -1725,24 +2132,41 @@ "count": 2 } }, + "web/app/components/workflow-app/hooks/__tests__/use-workflow-run.spec.tsx": { + "typescript/no-deprecated": { + "count": 3 + } + }, "web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts": { "no-restricted-imports": { "count": 1 + }, + "typescript/no-deprecated": { + "count": 1 } }, "web/app/components/workflow-app/hooks/use-workflow-run-callbacks.ts": { "no-restricted-imports": { "count": 2 + }, + "typescript/no-deprecated": { + "count": 2 } }, "web/app/components/workflow-app/hooks/use-workflow-run-utils.ts": { "no-restricted-imports": { "count": 3 + }, + "typescript/no-deprecated": { + "count": 1 } }, "web/app/components/workflow-app/hooks/use-workflow-run.ts": { "no-restricted-imports": { "count": 2 + }, + "typescript/no-deprecated": { + "count": 1 } }, "web/app/components/workflow-app/hooks/use-workflow-template.ts": { @@ -1755,6 +2179,11 @@ "count": 2 } }, + "web/app/components/workflow/block-selector/__tests__/all-start-blocks.spec.tsx": { + "typescript/no-deprecated": { + "count": 1 + } + }, "web/app/components/workflow/block-selector/constants.tsx": { "react/only-export-components": { "count": 3 @@ -1876,6 +2305,11 @@ "count": 2 } }, + "web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx": { + "typescript/no-deprecated": { + "count": 1 + } + }, "web/app/components/workflow/nodes/_base/components/before-run-form/form-item.tsx": { "jsx-a11y/no-autofocus": { "count": 3 @@ -2070,6 +2504,11 @@ "count": 5 } }, + "web/app/components/workflow/nodes/_base/components/workflow-panel/__tests__/index.spec.tsx": { + "typescript/no-deprecated": { + "count": 2 + } + }, "web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx": { "typescript/no-explicit-any": { "count": 4 @@ -2101,6 +2540,9 @@ "no-restricted-imports": { "count": 1 }, + "typescript/no-deprecated": { + "count": 2 + }, "typescript/no-explicit-any": { "count": 20 } @@ -2120,6 +2562,11 @@ "count": 1 } }, + "web/app/components/workflow/nodes/agent-v2/components/__tests__/agent-orchestrate-panel-content.spec.tsx": { + "typescript/no-deprecated": { + "count": 1 + } + }, "web/app/components/workflow/nodes/agent/default.ts": { "typescript/no-explicit-any": { "count": 2 @@ -2236,6 +2683,11 @@ "count": 2 } }, + "web/app/components/workflow/nodes/human-input/hooks/__tests__/use-single-run-form-params.spec.ts": { + "typescript/no-deprecated": { + "count": 1 + } + }, "web/app/components/workflow/nodes/if-else/components/condition-list/condition-input.tsx": { "typescript/no-explicit-any": { "count": 1 @@ -2327,6 +2779,26 @@ "count": 1 } }, + "web/app/components/workflow/nodes/knowledge-retrieval/hooks/use-knowledge-dataset-selection.ts": { + "typescript/no-deprecated": { + "count": 1 + } + }, + "web/app/components/workflow/nodes/knowledge-retrieval/hooks/use-knowledge-input-manager.ts": { + "typescript/no-deprecated": { + "count": 1 + } + }, + "web/app/components/workflow/nodes/knowledge-retrieval/hooks/use-knowledge-metadata-config.ts": { + "typescript/no-deprecated": { + "count": 1 + } + }, + "web/app/components/workflow/nodes/knowledge-retrieval/hooks/use-knowledge-model-config.ts": { + "typescript/no-deprecated": { + "count": 1 + } + }, "web/app/components/workflow/nodes/knowledge-retrieval/node.tsx": { "eslint-react/set-state-in-effect": { "count": 2 @@ -2372,6 +2844,21 @@ "count": 1 } }, + "web/app/components/workflow/nodes/llm/hooks/__tests__/use-llm-structured-output-config.spec.ts": { + "typescript/no-deprecated": { + "count": 2 + } + }, + "web/app/components/workflow/nodes/llm/hooks/use-llm-prompt-config.ts": { + "typescript/no-deprecated": { + "count": 1 + } + }, + "web/app/components/workflow/nodes/llm/hooks/use-llm-structured-output-config.ts": { + "typescript/no-deprecated": { + "count": 1 + } + }, "web/app/components/workflow/nodes/llm/use-config.ts": { "eslint-react/set-state-in-effect": { "count": 1 @@ -2686,6 +3173,9 @@ "no-restricted-imports": { "count": 2 }, + "typescript/no-deprecated": { + "count": 1 + }, "typescript/no-explicit-any": { "count": 6 } @@ -3015,14 +3505,50 @@ "count": 1 } }, + "web/features/account-profile/__tests__/client.spec.ts": { + "typescript/no-deprecated": { + "count": 1 + } + }, + "web/features/agent-v2/agent-detail/configure/__tests__/configure-test-env.tsx": { + "typescript/no-deprecated": { + "count": 1 + } + }, + "web/features/agent-v2/agent-detail/configure/components/orchestrate/skills/use-skill-detail.ts": { + "no-restricted-properties": { + "count": 1 + } + }, + "web/features/agent-v2/agent-detail/configure/components/preview/__tests__/chat.spec.tsx": { + "typescript/no-deprecated": { + "count": 1 + } + }, "web/features/agent-v2/agent-detail/configure/components/preview/working-directory-panel.tsx": { "@tanstack/query/prefer-query-options": { "count": 2 } }, + "web/features/app-access-error/document-response.ts": { + "no-restricted-globals": { + "count": 1 + } + }, + "web/features/network-access/app-binding/config-panel.tsx": { + "typescript/no-deprecated": { + "count": 1 + } + }, "web/features/skills/client.ts": { "dify/require-disable-directive-description": { "count": 2 + }, + "no-restricted-globals": { + "count": 1 + }, + "typescript/no-deprecated": { + "count": 5 } }, "web/features/skills/detail/file-editor.tsx": { @@ -3031,6 +3557,9 @@ }, "eslint-react/set-state-in-effect": { "count": 14 + }, + "typescript/no-deprecated": { + "count": 1 } }, "web/features/skills/detail/file-tree-items.tsx": { @@ -3038,6 +3567,16 @@ "count": 2 } }, + "web/features/skills/detail/file-tree.tsx": { + "no-restricted-globals": { + "count": 1 + } + }, + "web/features/skills/detail/markdown-editor.tsx": { + "typescript/no-deprecated": { + "count": 1 + } + }, "web/features/skills/detail/page.tsx": { "eslint-react/set-state-in-effect": { "count": 4 @@ -3078,6 +3617,11 @@ "count": 1 } }, + "web/plugins/vite/i18n-analysis/compiler.ts": { + "typescript/no-deprecated": { + "count": 1 + } + }, "web/proxy.ts": { "dify/require-disable-directive-description": { "count": 2 @@ -3093,19 +3637,55 @@ "count": 3 } }, + "web/service/__tests__/base-request.spec.ts": { + "typescript/no-deprecated": { + "count": 13 + } + }, "web/service/__tests__/base.spec.ts": { "no-restricted-imports": { "count": 1 } }, + "web/service/__tests__/ip-access-denied.spec.ts": { + "typescript/no-deprecated": { + "count": 33 + } + }, + "web/service/__tests__/plugin-credential-commands.spec.ts": { + "typescript/no-deprecated": { + "count": 1 + } + }, + "web/service/__tests__/plugin-credential-queries.spec.ts": { + "typescript/no-deprecated": { + "count": 1 + } + }, + "web/service/__tests__/request-error-toast.spec.ts": { + "typescript/no-deprecated": { + "count": 4 + } + }, "web/service/__tests__/sse-generator-post.spec.ts": { "dify/require-disable-directive-description": { "count": 1 + }, + "typescript/no-deprecated": { + "count": 4 + } + }, + "web/service/__tests__/use-workflow.spec.tsx": { + "typescript/no-deprecated": { + "count": 2 } }, "web/service/access-control/__tests__/use-app-access-control.spec.tsx": { "no-restricted-imports": { "count": 1 + }, + "typescript/no-deprecated": { + "count": 7 } }, "web/service/access-control/use-app-access-control.ts": { @@ -3116,17 +3696,29 @@ "web/service/annotation.ts": { "no-restricted-imports": { "count": 1 + }, + "typescript/no-deprecated": { + "count": 1 } }, "web/service/base.spec.ts": { "dify/require-disable-directive-description": { "count": 1 + }, + "typescript/no-deprecated": { + "count": 17 } }, "web/service/base.ts": { "no-restricted-imports": { "count": 2 }, + "no-restricted-properties": { + "count": 3 + }, + "typescript/no-deprecated": { + "count": 16 + }, "typescript/no-explicit-any": { "count": 2 } @@ -3134,42 +3726,112 @@ "web/service/common.ts": { "no-restricted-imports": { "count": 1 + }, + "typescript/no-deprecated": { + "count": 7 + } + }, + "web/service/console/browser.ts": { + "typescript/no-deprecated": { + "count": 1 + } + }, + "web/service/console/server.ts": { + "no-restricted-properties": { + "count": 1 + } + }, + "web/service/datasets.ts": { + "typescript/no-deprecated": { + "count": 1 } }, "web/service/debug.ts": { "no-restricted-imports": { "count": 2 + }, + "typescript/no-deprecated": { + "count": 1 + } + }, + "web/service/device-flow.ts": { + "no-restricted-globals": { + "count": 5 } }, "web/service/fetch.spec.ts": { "dify/require-disable-directive-description": { "count": 1 + }, + "typescript/no-deprecated": { + "count": 12 } }, "web/service/fetch.ts": { "no-restricted-imports": { + "count": 2 + }, + "no-restricted-properties": { + "count": 2 + }, + "typescript/no-deprecated": { "count": 1 } }, + "web/service/http-error.spec.ts": { + "typescript/no-deprecated": { + "count": 13 + } + }, "web/service/log.ts": { "no-restricted-imports": { "count": 1 + }, + "typescript/no-deprecated": { + "count": 2 + } + }, + "web/service/marketplace-templates.ts": { + "no-restricted-globals": { + "count": 1 + } + }, + "web/service/marketplace.ts": { + "no-restricted-properties": { + "count": 1 } }, "web/service/plugins.ts": { + "no-restricted-globals": { + "count": 1 + }, "no-restricted-imports": { "count": 1 + }, + "typescript/no-deprecated": { + "count": 4 } }, "web/service/refresh-token.ts": { "no-restricted-properties": { - "count": 7 + "count": 8 + } + }, + "web/service/share.spec.ts": { + "typescript/no-deprecated": { + "count": 1 } }, "web/service/share.ts": { + "no-restricted-globals": { + "count": 1 + }, "no-restricted-imports": { "count": 2 }, + "typescript/no-deprecated": { + "count": 22 + }, "typescript/no-explicit-any": { "count": 1 } @@ -3177,6 +3839,14 @@ "web/service/sso.ts": { "no-restricted-imports": { "count": 1 + }, + "typescript/no-deprecated": { + "count": 3 + } + }, + "web/service/tools.ts": { + "typescript/no-deprecated": { + "count": 4 } }, "web/service/use-common.ts": { @@ -3185,6 +3855,9 @@ }, "dify/require-disable-directive-description": { "count": 1 + }, + "typescript/no-deprecated": { + "count": 1 } }, "web/service/use-plugins-auth.ts": { @@ -3193,6 +3866,9 @@ }, "no-restricted-imports": { "count": 1 + }, + "typescript/no-deprecated": { + "count": 2 } }, "web/service/use-plugins.ts": { @@ -3201,11 +3877,17 @@ }, "dify/require-disable-directive-description": { "count": 1 + }, + "typescript/no-deprecated": { + "count": 4 } }, "web/service/use-share.ts": { "@tanstack/query/prefer-query-options": { "count": 8 + }, + "typescript/no-deprecated": { + "count": 1 } }, "web/service/use-snippet-workflows.ts": { @@ -3221,6 +3903,9 @@ "web/service/use-triggers.ts": { "@tanstack/query/prefer-query-options": { "count": 6 + }, + "typescript/no-deprecated": { + "count": 1 } }, "web/service/use-try-app.ts": { @@ -3231,6 +3916,9 @@ "web/service/use-workflow.ts": { "@tanstack/query/prefer-query-options": { "count": 4 + }, + "typescript/no-deprecated": { + "count": 2 } }, "web/service/webapp-auth.ts": { @@ -3239,16 +3927,25 @@ }, "no-restricted-imports": { "count": 1 + }, + "typescript/no-deprecated": { + "count": 2 } }, "web/service/workflow-generator.ts": { "dify/require-disable-directive-description": { "count": 1 + }, + "typescript/no-deprecated": { + "count": 1 } }, "web/service/workflow.ts": { "no-restricted-imports": { "count": 1 + }, + "typescript/no-deprecated": { + "count": 1 } }, "web/types/assets.d.ts": { @@ -3266,6 +3963,11 @@ "count": 15 } }, + "web/utils/clipboard.ts": { + "typescript/no-deprecated": { + "count": 1 + } + }, "web/utils/completion-params.ts": { "typescript/no-explicit-any": { "count": 1 @@ -3291,6 +3993,11 @@ "count": 1 } }, + "web/utils/page-leave-guard.ts": { + "typescript/no-deprecated": { + "count": 1 + } + }, "web/utils/validators.ts": { "typescript/no-explicit-any": { "count": 2 diff --git a/packages/tooling/README.md b/packages/tooling/README.md index 85686e7aa8f..1063d88ca65 100644 --- a/packages/tooling/README.md +++ b/packages/tooling/README.md @@ -156,6 +156,41 @@ Explain the concrete reason after `--`: which external contract, lifecycle, or r `reportUnusedDisableDirectives` runs at `error` repository-wide. Remove an exception when the finding no longer exists. Keep both checks active: a described disable may still be unused, and a used disable may still lack a reason. +### Handwritten HTTP Calls + +Web uses native Oxlint rules to discourage deprecated APIs and new handwritten requests: + +- `typescript/no-deprecated` rejects references to deprecated APIs. Legacy base/fetch + transport entry points carry `@deprecated`; generated `consoleClient`, `consoleQuery`, + and marketplace clients remain allowed, including direct calls from UI components. +- `no-restricted-globals` and `no-restricted-properties` reject direct global + `fetch` / `XMLHttpRequest` references, including `window`, `globalThis`, `global`, + and `self` property access. +- `no-restricted-imports` rejects new value imports from common HTTP packages + (including Ky, Axios, Undici and Node http/https). Type-only imports remain allowed. + Existing legacy-service and localStorage import/access restrictions remain active. + +`typescript/no-deprecated` applies throughout Web, including tests, mocks, stories, +test infrastructure, and maintenance scripts. The raw transport and HTTP-client import +restrictions apply only to production code. Repository-wide generated/vendor and +declaration-file ignores still apply. There is no custom API rule or data-flow tracker. + +Existing findings are recorded in `oxlint-suppressions.json`. These are counts of +references/imports, not endpoints awaiting migration: they include transport internals, +assets, special transports, and unrelated deprecated APIs reported by `no-deprecated`. +Prune counts when removing existing findings; do not raise them for new work. +For a necessary new special transport, use a scoped `oxlint-disable-next-line` +for the applicable native rule with a concrete contract reason. + +The simpler native checks intentionally accept gaps: for a deprecated arrow-function +helper, current Oxlint reports `const saved = get` at assignment but does not propagate +deprecation to later `saved()` calls. Adding calls through an existing alias, +HTTP-client import, or XHR instance may therefore pass its existing baseline. +Dynamic property access and indirect loading also require review. A per-file count +cannot detect replacing one old use with another at the same count. Do not treat +passing lint as proof that all requests use generated contracts. This rule set +does not enforce Query ownership. + ### Translation Function Types `dify/require-i18n-namespace` requires translation hook calls to use non-empty diff --git a/packages/tooling/handwritten-api.test.ts b/packages/tooling/handwritten-api.test.ts new file mode 100644 index 00000000000..7fba0cdba74 --- /dev/null +++ b/packages/tooling/handwritten-api.test.ts @@ -0,0 +1,192 @@ +import { spawnSync } from 'node:child_process' +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs' +import { createRequire } from 'node:module' +import { tmpdir } from 'node:os' +import { dirname, join } from 'node:path' +import { expect, it } from 'vite-plus/test' +import { lintConfig } from './lint.config.ts' + +type Diagnostic = { code?: string; filename: string; labels: { span: { line: number } }[] } +const nativeRules = new Set([ + 'typescript/no-deprecated', + 'no-restricted-globals', + 'no-restricted-properties', + 'no-restricted-imports', +]) + +function fixture() { + const require = createRequire(import.meta.url) + const viteRequire = createRequire(require.resolve('vite-plus/package.json')) + const oxlintBin = join(dirname(dirname(viteRequire.resolve('oxlint'))), 'bin', 'oxlint') + const oxlintRequire = createRequire(oxlintBin) + const tsgolintRequire = createRequire(oxlintRequire.resolve('oxlint-tsgolint/package.json')) + const tsgolint = tsgolintRequire.resolve( + `@oxlint-tsgolint/${process.platform}-${process.arch}/tsgolint${process.platform === 'win32' ? '.exe' : ''}`, + ) + const directory = mkdtempSync(join(tmpdir(), 'dify-native-http-')) + writeFileSync( + join(directory, 'tsconfig.json'), + JSON.stringify({ + compilerOptions: { target: 'ESNext', module: 'ESNext', lib: ['ESNext', 'DOM'], strict: true }, + include: ['web/**/*.ts', 'web/**/*.tsx'], + }), + ) + mkdirSync(join(directory, 'web/service'), { recursive: true }) + writeFileSync( + join(directory, 'web/service/legacy.ts'), + '/** @deprecated Use generated clients. */\nexport const get = (url: string) => Promise.resolve(url)\n', + ) + const select = (rules: Record) => + Object.fromEntries(Object.entries(rules).filter(([name]) => nativeRules.has(name))) + writeFileSync( + join(directory, '.oxlintrc.json'), + JSON.stringify({ + categories: { correctness: 'off' }, + options: { typeAware: true }, + rules: select(lintConfig.rules), + overrides: lintConfig.overrides.map((override) => ({ + ...override, + rules: select(override.rules), + })), + }), + ) + return { + close: () => rmSync(directory, { recursive: true, force: true }), + lint(source: string, file = 'web/feature.ts', flags: string[] = []) { + mkdirSync(dirname(join(directory, file)), { recursive: true }) + writeFileSync(join(directory, file), source) + const result = spawnSync( + process.execPath, + [oxlintBin, '--config', '.oxlintrc.json', '--format', 'json', ...flags, file], + { + cwd: directory, + encoding: 'utf8', + env: { ...process.env, OXLINT_TSGOLINT_PATH: tsgolint }, + }, + ) + expect(result.error).toBeUndefined() + expect([0, 1], result.stderr + result.stdout).toContain(result.status) + return { + status: result.status, + diagnostics: JSON.parse(result.stdout).diagnostics as Diagnostic[], + } + }, + } +} + +it('rejects each direct deprecated helper call and allows generated clients and local functions', () => { + const test = fixture() + try { + const result = test.lint( + [ + "import { get as read } from './service/legacy'", + "import * as legacy from './service/legacy'", + "read('/one')", + "legacy.get('/two')", + 'const consoleClient = { get: (url: string) => Promise.resolve(url) }', + "consoleClient.get('/allowed')", + 'const fetch = (url: string) => url', + "fetch('/local')", + ].join('\n'), + ) + expect(result.diagnostics.map((d) => [d.code, d.labels[0]!.span.line])).toEqual([ + ['typescript(no-deprecated)', 3], + ['typescript(no-deprecated)', 4], + ]) + } finally { + test.close() + } +}) + +it('blocks native requests and client imports without blocking type-only imports', () => { + const test = fixture() + try { + const result = test.lint( + [ + "import ky from 'ky'", + "import type { Options } from 'ky'", + "fetch('/one')", + "window.fetch('/two')", + "globalThis['fetch']('/three')", + 'new XMLHttpRequest()', + 'new self.XMLHttpRequest()', + "import { request } from 'node:https'", + ].join('\n'), + ) + expect(result.diagnostics.map((d) => d.labels[0]!.span.line).sort((a, b) => a - b)).toEqual([ + 1, 3, 4, 5, 6, 7, 8, + ]) + } finally { + test.close() + } +}) + +it('preserves storage and service import restrictions alongside transport restrictions', () => { + const test = fixture() + try { + const result = test.lint( + "import { get } from './base'\nlocalStorage.getItem('key')\nwindow.localStorage.getItem('key')\nfetch('/one')", + 'web/service/feature.ts', + ) + expect(result.diagnostics.map((d) => d.labels[0]!.span.line).sort((a, b) => a - b)).toEqual([ + 1, 2, 3, 4, + ]) + expect(test.lint("fetch('/one')", 'web/instrumentation-client.ts').diagnostics).toHaveLength(1) + } finally { + test.close() + } +}) + +it('checks deprecations in all Web scopes while allowing test and script transports', () => { + const test = fixture() + try { + for (const file of [ + 'web/feature.spec.ts', + 'web/feature.test.tsx', + 'web/feature.stories.tsx', + 'web/__tests__/http.ts', + 'web/__mocks__/http.ts', + 'web/scripts/check.ts', + 'web/test/helper.ts', + 'web/vitest.setup.ts', + 'web/vitest.browser.setup.ts', + ]) { + const source = [ + "import ky from 'ky'", + "fetch('/allowed')", + '/** @deprecated Use the replacement. */', + 'function legacy() {}', + 'legacy()', + ].join('\n') + expect( + test.lint(source, file).diagnostics.map((d) => [d.code, d.labels[0]!.span.line]), + ).toEqual([['typescript(no-deprecated)', 5]]) + } + } finally { + test.close() + } +}) + +it('rejects additional direct calls beyond the existing suppression count', () => { + const test = fixture() + try { + const source = "import { get } from './service/legacy'\nget('/existing')\nfetch('/existing')" + expect(test.lint(source, undefined, ['--suppress-all']).status).toBe(0) + expect(test.lint(`${source}\nget('/new')\nfetch('/new')`).status).toBe(1) + } finally { + test.close() + } +}) + +it('documents the accepted Oxlint gap for calls through an already baselined alias', () => { + const test = fixture() + try { + const source = "import { get } from './service/legacy'\nconst saved = get\nsaved('/existing')" + expect(test.lint(source).diagnostics.map((d) => d.labels[0]!.span.line)).toEqual([2]) + expect(test.lint(source, undefined, ['--suppress-all']).status).toBe(0) + // Change this expectation and the documentation when Oxlint gains signature propagation. + expect(test.lint(`${source}\nsaved('/new')`).diagnostics).toEqual([]) + } finally { + test.close() + } +}) diff --git a/packages/tooling/lint.config.ts b/packages/tooling/lint.config.ts index f52c14a5bca..a79b1730a3f 100644 --- a/packages/tooling/lint.config.ts +++ b/packages/tooling/lint.config.ts @@ -10,6 +10,242 @@ const webPackageJson = path.resolve(rootDir, 'web/package.json') const difyUiPackageJson = path.resolve(rootDir, 'packages/dify-ui/package.json') const enableTailwindCanonicalClasses = process.env.TAILWIND_CANONICAL_CLASSES === 'true' +const handwrittenApiMessage = + 'Use generated consoleClient/consoleQuery APIs. New special transports need a scoped lint exception with a concrete reason.' +const rawHttpGlobals = ['fetch', 'XMLHttpRequest'].map((name) => ({ + name, + message: handwrittenApiMessage, +})) +const rawHttpProperties = ['window', 'globalThis', 'global', 'self'].flatMap((object) => + ['fetch', 'XMLHttpRequest'].map((property) => ({ + object, + property, + message: handwrittenApiMessage, + })), +) +const rawHttpImports = { + group: [ + 'axios', + 'ky', + 'ofetch', + 'node-fetch', + 'cross-fetch', + 'isomorphic-fetch', + 'undici', + 'superagent', + 'http', + 'https', + 'node:http', + 'node:https', + ].flatMap((name) => [name, `${name}/*`]), + allowTypeImports: true, + message: handwrittenApiMessage, +} +const handwrittenApiScope = { + files: ['web/**/*.{js,cjs,mjs,jsx,ts,cts,mts,tsx}'], + excludeFiles: [ + 'web/**/__tests__/**', + 'web/**/__mocks__/**', + 'web/test/**', + 'web/scripts/**', + 'web/**/*.spec.{js,cjs,mjs,jsx,ts,cts,mts,tsx}', + 'web/**/*.test.{js,cjs,mjs,jsx,ts,cts,mts,tsx}', + 'web/**/*.stories.{js,cjs,mjs,jsx,ts,cts,mts,tsx}', + 'web/**/*.story.{js,cjs,mjs,jsx,ts,cts,mts,tsx}', + 'web/vitest.setup.ts', + 'web/vitest.browser.setup.ts', + ], +} + +const webRestrictedImports = [ + 'error', + { + paths: [ + { + name: 'next', + message: 'Import Next APIs from the corresponding @/next module instead of next.', + }, + { + name: 'ahooks', + importNames: ['useLocalStorageState'], + message: 'Do not use ahooks useLocalStorageState. Use foxact storage hooks instead.', + }, + ], + patterns: [ + { + group: ['next/image'], + message: 'Do not import next/image. Use native img tags instead.', + }, + { + group: ['next/font', 'next/font/*'], + message: 'Do not import next/font. Use the project font styles instead.', + }, + { + // next/dynamic must be imported directly for compiler-generated preload metadata. + group: [ + 'next/*', + '!next/dynamic', + '!next/font', + '!next/font/*', + '!next/image', + '!next/image/*', + ], + message: 'Import Next APIs from the corresponding @/next/* module instead of next/*.', + }, + { + group: ['@base-ui/react', '@base-ui/react/*'], + message: + 'Do not import Base UI directly in web. Use @langgenius/dify-ui/* primitives instead.', + }, + { + group: ['@floating-ui/*'], + message: + 'Do not import Floating UI directly in web. Use @langgenius/dify-ui/* primitives instead.', + }, + { + group: ['@/service/base', '@/service/base/*', '**/service/base', '**/service/base/*'], + message: + 'Do not import legacy service/base fetch helpers. Use generated service clients or feature-specific service modules instead.', + }, + { + group: ['@/service/fetch', '@/service/fetch/*', '**/service/fetch', '**/service/fetch/*'], + message: + 'Do not import low-level service/fetch helpers directly. Use generated service clients or feature-specific service modules instead.', + }, + ], + }, +] satisfies NonNullable[string] + +const serviceRestrictedImports = [ + 'error', + { + paths: [ + { + name: 'next', + message: 'Import Next APIs from the corresponding @/next module instead of next.', + }, + { + name: 'ahooks', + importNames: ['useLocalStorageState'], + message: 'Do not use ahooks useLocalStorageState. Use foxact storage hooks instead.', + }, + ], + patterns: [ + { + group: ['next/image'], + message: 'Do not import next/image. Use native img tags instead.', + }, + { + group: ['next/font', 'next/font/*'], + message: 'Do not import next/font. Use the project font styles instead.', + }, + { + // next/dynamic must be imported directly for compiler-generated preload metadata. + group: [ + 'next/*', + '!next/dynamic', + '!next/font', + '!next/font/*', + '!next/image', + '!next/image/*', + ], + message: 'Import Next APIs from the corresponding @/next/* module instead of next/*.', + }, + { + group: ['@base-ui/react', '@base-ui/react/*'], + message: + 'Do not import Base UI directly in web. Use @langgenius/dify-ui/* primitives instead.', + }, + { + group: ['@floating-ui/*'], + message: + 'Do not import Floating UI directly in web. Use @langgenius/dify-ui/* primitives instead.', + }, + { + group: ['@/service/base', '@/service/base/*', '**/service/base', '**/service/base/*'], + message: + 'Do not import legacy service/base fetch helpers. Use generated service clients or feature-specific service modules instead.', + }, + { + group: ['@/service/fetch', '@/service/fetch/*', '**/service/fetch', '**/service/fetch/*'], + message: + 'Do not import low-level service/fetch helpers directly. Use generated service clients or feature-specific service modules instead.', + }, + { + group: ['./base', './base/*', '../base', '../base/*', '../../base', '../../base/*'], + message: + 'Do not import legacy service/base fetch helpers. Use generated service clients or feature-specific service modules instead.', + }, + { + group: ['./fetch', './fetch/*', '../fetch', '../fetch/*', '../../fetch', '../../fetch/*'], + message: + 'Do not import low-level service/fetch helpers directly. Use generated service clients or feature-specific service modules instead.', + }, + ], + }, +] satisfies NonNullable[string] + +const defaultRestrictedGlobals = [ + 'error', + { + message: 'Use `globalThis` instead.', + name: 'global', + }, + { + message: 'Use `globalThis` instead.', + name: 'self', + }, +] satisfies NonNullable[string] + +const storageRestrictedGlobals = [ + 'error', + { + name: 'localStorage', + message: + 'Do not use localStorage directly. Use a foxact storage boundary instead; prefer feature-owned createLocalStorageState for shared storage.', + }, +] satisfies NonNullable[string] + +const defaultRestrictedProperties = [ + 'error', + { + message: 'Use `Object.getPrototypeOf` or `Object.setPrototypeOf` instead.', + property: '__proto__', + }, + { + message: 'Use `Object.defineProperty` instead.', + property: '__defineGetter__', + }, + { + message: 'Use `Object.defineProperty` instead.', + property: '__defineSetter__', + }, + { + message: 'Use `Object.getOwnPropertyDescriptor` instead.', + property: '__lookupGetter__', + }, + { + message: 'Use `Object.getOwnPropertyDescriptor` instead.', + property: '__lookupSetter__', + }, +] satisfies NonNullable[string] + +const storageRestrictedProperties = [ + 'error', + { + object: 'window', + property: 'localStorage', + 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.', + }, +] satisfies NonNullable[string] + export const stylingContractRules = { 'shadcn/no-restyle': [ 'error', @@ -387,40 +623,8 @@ export const lintConfig = { }, ], 'no-regex-spaces': 'error', - 'no-restricted-globals': [ - 'error', - { - message: 'Use `globalThis` instead.', - name: 'global', - }, - { - message: 'Use `globalThis` instead.', - name: 'self', - }, - ], - 'no-restricted-properties': [ - 'error', - { - message: 'Use `Object.getPrototypeOf` or `Object.setPrototypeOf` instead.', - property: '__proto__', - }, - { - message: 'Use `Object.defineProperty` instead.', - property: '__defineGetter__', - }, - { - message: 'Use `Object.defineProperty` instead.', - property: '__defineSetter__', - }, - { - message: 'Use `Object.getOwnPropertyDescriptor` instead.', - property: '__lookupGetter__', - }, - { - message: 'Use `Object.getOwnPropertyDescriptor` instead.', - property: '__lookupSetter__', - }, - ], + 'no-restricted-globals': defaultRestrictedGlobals, + 'no-restricted-properties': defaultRestrictedProperties, 'no-self-assign': [ 'error', { @@ -940,168 +1144,13 @@ export const lintConfig = { { files: ['web/**/*.{ts,cts,mts}', 'web/**/*.tsx'], rules: { - 'no-restricted-imports': [ - 'error', - { - paths: [ - { - name: 'next', - message: 'Import Next APIs from the corresponding @/next module instead of next.', - }, - { - name: 'ahooks', - importNames: ['useLocalStorageState'], - message: - 'Do not use ahooks useLocalStorageState. Use foxact storage hooks instead.', - }, - ], - patterns: [ - { - group: ['next/image'], - message: 'Do not import next/image. Use native img tags instead.', - }, - { - group: ['next/font', 'next/font/*'], - message: 'Do not import next/font. Use the project font styles instead.', - }, - { - // next/dynamic must be imported directly for compiler-generated preload metadata. - group: [ - 'next/*', - '!next/dynamic', - '!next/font', - '!next/font/*', - '!next/image', - '!next/image/*', - ], - message: - 'Import Next APIs from the corresponding @/next/* module instead of next/*.', - }, - { - group: ['@base-ui/react', '@base-ui/react/*'], - message: - 'Do not import Base UI directly in web. Use @langgenius/dify-ui/* primitives instead.', - }, - { - group: ['@floating-ui/*'], - message: - 'Do not import Floating UI directly in web. Use @langgenius/dify-ui/* primitives instead.', - }, - { - group: [ - '@/service/base', - '@/service/base/*', - '**/service/base', - '**/service/base/*', - ], - message: - 'Do not import legacy service/base fetch helpers. Use generated service clients or feature-specific service modules instead.', - }, - { - group: [ - '@/service/fetch', - '@/service/fetch/*', - '**/service/fetch', - '**/service/fetch/*', - ], - message: - 'Do not import low-level service/fetch helpers directly. Use generated service clients or feature-specific service modules instead.', - }, - ], - }, - ], + 'no-restricted-imports': webRestrictedImports, }, }, { files: ['web/service/**/*.ts', 'web/service/**/*.tsx'], rules: { - 'no-restricted-imports': [ - 'error', - { - paths: [ - { - name: 'next', - message: 'Import Next APIs from the corresponding @/next module instead of next.', - }, - { - name: 'ahooks', - importNames: ['useLocalStorageState'], - message: - 'Do not use ahooks useLocalStorageState. Use foxact storage hooks instead.', - }, - ], - patterns: [ - { - group: ['next/image'], - message: 'Do not import next/image. Use native img tags instead.', - }, - { - group: ['next/font', 'next/font/*'], - message: 'Do not import next/font. Use the project font styles instead.', - }, - { - // next/dynamic must be imported directly for compiler-generated preload metadata. - group: [ - 'next/*', - '!next/dynamic', - '!next/font', - '!next/font/*', - '!next/image', - '!next/image/*', - ], - message: - 'Import Next APIs from the corresponding @/next/* module instead of next/*.', - }, - { - group: ['@base-ui/react', '@base-ui/react/*'], - message: - 'Do not import Base UI directly in web. Use @langgenius/dify-ui/* primitives instead.', - }, - { - group: ['@floating-ui/*'], - message: - 'Do not import Floating UI directly in web. Use @langgenius/dify-ui/* primitives instead.', - }, - { - group: [ - '@/service/base', - '@/service/base/*', - '**/service/base', - '**/service/base/*', - ], - message: - 'Do not import legacy service/base fetch helpers. Use generated service clients or feature-specific service modules instead.', - }, - { - group: [ - '@/service/fetch', - '@/service/fetch/*', - '**/service/fetch', - '**/service/fetch/*', - ], - message: - 'Do not import low-level service/fetch helpers directly. Use generated service clients or feature-specific service modules instead.', - }, - { - group: ['./base', './base/*', '../base', '../base/*', '../../base', '../../base/*'], - message: - 'Do not import legacy service/base fetch helpers. Use generated service clients or feature-specific service modules instead.', - }, - { - group: [ - './fetch', - './fetch/*', - '../fetch', - '../fetch/*', - '../../fetch', - '../../fetch/*', - ], - message: - 'Do not import low-level service/fetch helpers directly. Use generated service clients or feature-specific service modules instead.', - }, - ], - }, - ], + 'no-restricted-imports': serviceRestrictedImports, }, }, { @@ -1116,29 +1165,8 @@ export const lintConfig = { 'web/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.', - }, - ], - 'no-restricted-properties': [ - 'error', - { - object: 'window', - property: 'localStorage', - 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.', - }, - ], + 'no-restricted-globals': storageRestrictedGlobals, + 'no-restricted-properties': storageRestrictedProperties, }, }, { @@ -1621,5 +1649,53 @@ export const lintConfig = { 'no-useless-assignment': 'error', }, }, + // Apply after the existing Web overrides and preserve their unrelated restrictions. + { + files: ['web/**/*.{js,cjs,mjs,jsx,ts,cts,mts,tsx}'], + rules: { + 'typescript/no-deprecated': 'error', + }, + }, + { + ...handwrittenApiScope, + rules: { + 'no-restricted-globals': [...defaultRestrictedGlobals, ...rawHttpGlobals], + 'no-restricted-properties': [...defaultRestrictedProperties, ...rawHttpProperties], + 'no-restricted-imports': [ + 'error', + { + ...webRestrictedImports[1], + patterns: [...webRestrictedImports[1].patterns, rawHttpImports], + }, + ], + }, + }, + { + ...handwrittenApiScope, + files: ['web/**/*.{ts,cts,mts}', 'web/**/*.tsx'], + excludeFiles: [ + ...handwrittenApiScope.excludeFiles, + 'web/**/*.bench.{js,cjs,mjs,jsx,ts,cts,mts,tsx}', + 'web/**/*.benchmark.{js,cjs,mjs,jsx,ts,cts,mts,tsx}', + 'web/instrumentation-client.ts', + ], + rules: { + 'no-restricted-globals': [...storageRestrictedGlobals, ...rawHttpGlobals], + 'no-restricted-properties': [...storageRestrictedProperties, ...rawHttpProperties], + }, + }, + { + ...handwrittenApiScope, + files: ['web/service/**/*.ts', 'web/service/**/*.tsx'], + rules: { + 'no-restricted-imports': [ + 'error', + { + ...serviceRestrictedImports[1], + patterns: [...serviceRestrictedImports[1].patterns, rawHttpImports], + }, + ], + }, + }, ], } satisfies OxlintConfig diff --git a/web/service/base.ts b/web/service/base.ts index 5cc7ab432eb..b3ec52f554d 100644 --- a/web/service/base.ts +++ b/web/service/base.ts @@ -630,6 +630,7 @@ type UploadResponse = { [key: string]: unknown } +/** @deprecated Use generated contract clients for new APIs. Special transports require a scoped lint exception. */ export const upload = async ( options: UploadOptions, isPublicAPI?: boolean, @@ -704,6 +705,7 @@ export const upload = async ( }) } +/** @deprecated Use generated contract clients for new APIs. Special transports require a scoped lint exception. */ export const ssePost = async ( url: string, fetchOptions: FetchOptionType, @@ -878,6 +880,7 @@ export const ssePost = async ( }) } +/** @deprecated Use generated contract clients for new APIs. Special transports require a scoped lint exception. */ export const sseGet = async ( url: string, fetchOptions: FetchOptionType, @@ -1064,6 +1067,7 @@ export type GeneratorStreamCallbacks = { * positional callbacks through that shared, high-blast-radius path isn't worth * it. This helper reuses the same cookie-auth + CSRF + abort setup but * only understands the generator's two events: ``plan`` then ``result``. + * @deprecated Use generated contract clients for new APIs. Special transports require a scoped lint exception. */ export const sseGeneratorPost = ( url: string, @@ -1154,6 +1158,7 @@ export const sseGeneratorPost = ( } // base request +/** @deprecated Use generated contract clients for new APIs. Special transports require a scoped lint exception. */ export const request = async (url: string, options = {}, otherOptions?: IOtherOptions) => { try { const otherOptionsForBaseFetch = otherOptions || {} @@ -1262,11 +1267,13 @@ export const get = (url: string, options = {}, otherOptions?: IOtherOptions) } // For public API +/** @deprecated Use generated contract clients for new APIs. Special transports require a scoped lint exception. */ export const getPublic = (url: string, options = {}, otherOptions?: IOtherOptions) => { return get(url, options, { ...otherOptions, isPublicAPI: true }) } // For Marketplace API +/** @deprecated Use generated contract clients for new APIs. Special transports require a scoped lint exception. */ export const getMarketplace = (url: string, options = {}, otherOptions?: IOtherOptions) => { return get(url, options, { ...otherOptions, isMarketplaceAPI: true }) } @@ -1282,10 +1289,12 @@ export const post = (url: string, options = {}, otherOptions?: IOtherOptions) } // For Marketplace API +/** @deprecated Use generated contract clients for new APIs. Special transports require a scoped lint exception. */ export const postMarketplace = (url: string, options = {}, otherOptions?: IOtherOptions) => { return post(url, options, { ...otherOptions, isMarketplaceAPI: true }) } +/** @deprecated Use generated contract clients for new APIs. Special transports require a scoped lint exception. */ export const postPublic = (url: string, options = {}, otherOptions?: IOtherOptions) => { return post(url, options, { ...otherOptions, isPublicAPI: true }) } @@ -1300,6 +1309,7 @@ export const del = (url: string, options = {}, otherOptions?: IOtherOptions) return request(url, Object.assign({}, options, { method: 'DELETE' }), otherOptions) } +/** @deprecated Use generated contract clients for new APIs. Special transports require a scoped lint exception. */ export const delPublic = (url: string, options = {}, otherOptions?: IOtherOptions) => { return del(url, options, { ...otherOptions, isPublicAPI: true }) } @@ -1314,6 +1324,7 @@ export const patch = (url: string, options = {}, otherOptions?: IOtherOptions return request(url, Object.assign({}, options, { method: 'PATCH' }), otherOptions) } +/** @deprecated Use generated contract clients for new APIs. Special transports require a scoped lint exception. */ export const patchPublic = (url: string, options = {}, otherOptions?: IOtherOptions) => { return patch(url, options, { ...otherOptions, isPublicAPI: true }) } diff --git a/web/service/fetch.ts b/web/service/fetch.ts index 66df38c9401..938ccac8e9e 100644 --- a/web/service/fetch.ts +++ b/web/service/fetch.ts @@ -110,6 +110,7 @@ export const getBaseOptions = (): RequestInit => ({ redirect: 'follow', }) +/** @deprecated Use generated contract clients for new APIs. Special transports require a scoped lint exception. */ async function base( url: string, options: FetchOptionType = {}, @@ -221,6 +222,7 @@ async function base( * Includes credentials, Authorization (if available), and CSRF header * so the request is authenticated, matching the headers sent by the * standard `base()` fetch wrapper. + * @deprecated New keepalive transports require a scoped lint exception. */ export function postWithKeepalive(url: string, body: Record): void { const headers = new Headers({