chore: update knip config and include in CI (#30410)

This commit is contained in:
Stephen Zhou 2025-12-31 15:38:07 +08:00 committed by GitHub
parent 27be89c984
commit e856287b65
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 30 additions and 324 deletions

View File

@ -110,6 +110,11 @@ jobs:
working-directory: ./web
run: pnpm run type-check:tsgo
- name: Web dead code check
if: steps.changed-files.outputs.any_changed == 'true'
working-directory: ./web
run: pnpm run knip
superlinter:
name: SuperLinter
runs-on: ubuntu-latest

View File

@ -29,11 +29,6 @@ export default {
const options = context.options[0] || {}
const mode = options.mode || 'any'
/**
* Check if this is a t() function call
* @param {import('estree').CallExpression} node
* @returns {boolean}
*/
function isTCall(node) {
// Direct t() call
if (node.callee.type === 'Identifier' && node.callee.name === 't')

View File

@ -19,26 +19,11 @@ export default {
create(context) {
const sourceCode = context.sourceCode
// Track all t() calls to fix
/** @type {Array<{ node: import('estree').CallExpression }>} */
const tCallsToFix = []
// Track variables with namespace prefix
/** @type {Map<string, { node: import('estree').VariableDeclarator, name: string, oldValue: string, newValue: string, ns: string }>} */
const variablesToFix = new Map()
// Track all namespaces used in the file (from legacy prefix detection)
/** @type {Set<string>} */
const namespacesUsed = new Set()
// Track variable values for template literal analysis
/** @type {Map<string, string>} */
const variableValues = new Map()
/**
* Analyze a template literal and extract namespace info
* @param {import('estree').TemplateLiteral} node
*/
function analyzeTemplateLiteral(node) {
const quasis = node.quasis
const expressions = node.expressions
@ -78,11 +63,6 @@ export default {
return { ns: null, canFix: false, fixedQuasis: null, variableToUpdate: null }
}
/**
* Build a fixed template literal string
* @param {string[]} quasis
* @param {import('estree').Expression[]} expressions
*/
function buildTemplateLiteral(quasis, expressions) {
let result = '`'
for (let i = 0; i < quasis.length; i++) {
@ -95,11 +75,6 @@ export default {
return result
}
/**
* Check if a t() call already has ns in its second argument
* @param {import('estree').CallExpression} node
* @returns {boolean}
*/
function hasNsArgument(node) {
if (node.arguments.length < 2)
return false

View File

@ -12,11 +12,6 @@ export default {
},
},
create(context) {
/**
* Check if a t() call has ns in its second argument
* @param {import('estree').CallExpression} node
* @returns {boolean}
*/
function hasNsOption(node) {
if (node.arguments.length < 2)
return false

View File

@ -1,277 +1,33 @@
import type { KnipConfig } from 'knip'
/**
* Knip Configuration for Dead Code Detection
*
* This configuration helps identify unused files, exports, and dependencies
* in the Dify web application (Next.js 15 + TypeScript + React 19).
*
* SAFETY FIRST: This configuration is designed to be conservative and
* avoid false positives that could lead to deleting actively used code.
*
* @see https://knip.dev/reference/configuration
*/
const config: KnipConfig = {
// ============================================================================
// Next.js Framework Configuration
// ============================================================================
// Configure entry points specific to Next.js application structure.
// These files are automatically treated as entry points by the framework.
next: {
entry: [
// Next.js App Router pages (must exist for routing)
'app/**/page.tsx',
'app/**/layout.tsx',
'app/**/loading.tsx',
'app/**/error.tsx',
'app/**/not-found.tsx',
'app/**/template.tsx',
'app/**/default.tsx',
// Middleware (runs before every route)
'middleware.ts',
// Configuration files
'next.config.js',
'tailwind.config.js',
'tailwind-common-config.ts',
'postcss.config.js',
// Linting configuration
'eslint.config.mjs',
],
},
// ============================================================================
// Global Entry Points
// ============================================================================
// Files that serve as entry points for the application.
// The '!' suffix means these patterns take precedence and are always included.
entry: [
// Next.js App Router patterns (high priority)
'app/**/page.tsx!',
'app/**/layout.tsx!',
'app/**/loading.tsx!',
'app/**/error.tsx!',
'app/**/not-found.tsx!',
'app/**/template.tsx!',
'app/**/default.tsx!',
// Core configuration files
'middleware.ts!',
'next.config.js!',
'tailwind.config.js!',
'tailwind-common-config.ts!',
'postcss.config.js!',
// Linting setup
'eslint.config.mjs!',
// ========================================================================
// 🔒 CRITICAL: Global Initializers and Providers
// ========================================================================
// These files are imported by root layout.tsx and provide global functionality.
// Even if not directly imported elsewhere, they are essential for app initialization.
// Browser initialization (runs on client startup)
'app/components/browser-initializer.tsx!',
'app/components/sentry-initializer.tsx!',
'app/components/app-initializer.tsx!',
// i18n initialization (server and client)
'app/components/i18n.tsx!',
'app/components/i18n-server.tsx!',
// Route prefix handling (used in root layout)
'app/routePrefixHandle.tsx!',
// ========================================================================
// 🔒 CRITICAL: Context Providers
// ========================================================================
// Context providers might be used via React Context API and imported dynamically.
// Protecting all context files to prevent breaking the provider chain.
'context/**/*.ts?(x)!',
// Component-level contexts (also used via React.useContext)
'app/components/**/*.context.ts?(x)!',
// ========================================================================
// 🔒 CRITICAL: State Management Stores
// ========================================================================
// Zustand stores might be imported dynamically or via hooks.
// These are often imported at module level, so they should be protected.
'app/components/**/*.store.ts?(x)!',
'context/**/*.store.ts?(x)!',
// ========================================================================
// 🔒 CRITICAL: Provider Components
// ========================================================================
// Provider components wrap the app and provide global state/functionality
'app/components/**/*.provider.ts?(x)!',
'context/**/*.provider.ts?(x)!',
// ========================================================================
// Development tools
// ========================================================================
// Storybook configuration
'.storybook/**/*',
'scripts/**/*.{js,ts,mjs}',
'bin/**/*.{js,ts,mjs}',
],
// ============================================================================
// Project Files to Analyze
// ============================================================================
// Glob patterns for files that should be analyzed for unused code.
// Excludes test files to avoid false positives.
project: [
'**/*.{js,jsx,ts,tsx,mjs,cjs}',
],
// ============================================================================
// Ignored Files and Directories
// ============================================================================
// Files and directories that should be completely excluded from analysis.
// These typically contain:
// - Test files
// - Internationalization files (loaded dynamically)
// - Static assets
// - Build outputs
// - External libraries
ignore: [
// Test files and directories
'**/__tests__/**',
'**/*.spec.{ts,tsx}',
'**/*.test.{ts,tsx}',
// ========================================================================
// 🔒 CRITICAL: i18n Files (Dynamically Loaded)
// ========================================================================
// Internationalization files are loaded dynamically at runtime via i18next.
// Pattern: import(`@/i18n/${locale}/messages`)
// These will NEVER show up in static analysis but are essential!
'i18n/**',
// ========================================================================
// 🔒 CRITICAL: Static Assets
// ========================================================================
// Static assets are referenced by URL in the browser, not via imports.
// Examples: /logo.png, /icons/*, /embed.js
'public/**',
// Build outputs and caches
'node_modules/**',
'.next/**',
'coverage/**',
// Development tools
'**/*.stories.{ts,tsx}',
// ========================================================================
// 🔒 Utility scripts (not part of application runtime)
// ========================================================================
// These scripts are run manually (e.g., pnpm gen-icons, pnpm i18n:check)
// and are not imported by the application code.
'scripts/**',
'bin/**',
'i18n-config/**',
// Icon generation script (generates components, not used in runtime)
'app/components/base/icons/script.mjs',
],
// ============================================================================
// Ignored Dependencies
// ============================================================================
// Dependencies that are used but not directly imported in code.
// These are typically:
// - Build tools
// - Plugins loaded by configuration files
// - CLI tools
ignoreDependencies: [
// ========================================================================
// Next.js plugins (loaded by next.config.js)
// ========================================================================
'next-pwa',
'@next/bundle-analyzer',
'@next/mdx',
// ========================================================================
// Build tools (used by webpack/next.js build process)
// ========================================================================
'code-inspector-plugin',
// ========================================================================
// Development and translation tools (used by scripts)
// ========================================================================
'bing-translate-api',
'uglify-js',
],
// ============================================================================
// Export Analysis Configuration
// ============================================================================
// Configure how exports are analyzed
// Ignore exports that are only used within the same file
// (e.g., helper functions used internally in the same module)
ignoreExportsUsedInFile: true,
// ⚠️ SAFETY: Include exports from entry files in the analysis
// This helps find unused public APIs, but be careful with:
// - Context exports (useContext hooks)
// - Store exports (useStore hooks)
// - Type exports (might be used in other files)
includeEntryExports: true,
// ============================================================================
// Ignored Binaries
// ============================================================================
// Binary executables that are used but not listed in package.json
ignoreBinaries: [
'only-allow', // Used in preinstall script to enforce pnpm usage
'only-allow',
],
// ============================================================================
// Reporting Rules
// ============================================================================
// Configure what types of issues to report and at what severity level
rules: {
// ========================================================================
// Unused files are ERRORS
// ========================================================================
// These should definitely be removed or used.
// However, always manually verify before deleting!
files: 'error',
// ========================================================================
// Unused dependencies are WARNINGS
// ========================================================================
// Dependencies might be:
// - Used in production builds but not in dev
// - Peer dependencies
// - Used by other tools
files: 'warn',
dependencies: 'warn',
devDependencies: 'warn',
// ========================================================================
// Unlisted imports are ERRORS
// ========================================================================
// Missing from package.json - will break in production!
unlisted: 'error',
// ========================================================================
// Unused exports are WARNINGS (not errors!)
// ========================================================================
// Exports might be:
// - Part of public API for future use
// - Used by external tools
// - Exported for type inference
// ⚠️ ALWAYS manually verify before removing exports!
optionalPeerDependencies: 'warn',
unlisted: 'warn',
unresolved: 'warn',
exports: 'warn',
// Unused types are warnings (might be part of type definitions)
nsExports: 'warn',
classMembers: 'warn',
types: 'warn',
// Duplicate exports are warnings (could cause confusion but not breaking)
nsTypes: 'warn',
enumMembers: 'warn',
duplicates: 'warn',
},
}

View File

@ -31,7 +31,7 @@
"type-check": "tsc --noEmit",
"type-check:tsgo": "tsgo --noEmit",
"prepare": "cd ../ && node -e \"if (process.env.NODE_ENV !== 'production'){process.exit(1)} \" || husky ./web/.husky",
"gen-icons": "node ./app/components/base/icons/script.mjs",
"gen-icons": "node ./scripts/gen-icons.mjs && eslint --fix app/components/base/icons/src/",
"uglify-embed": "node ./bin/uglify-embed",
"i18n:check": "tsx ./scripts/check-i18n.js",
"i18n:gen": "tsx ./scripts/auto-gen-i18n.js",
@ -190,7 +190,6 @@
"@vitejs/plugin-react": "^5.1.2",
"@vitest/coverage-v8": "4.0.16",
"autoprefixer": "^10.4.21",
"babel-loader": "^10.0.0",
"bing-translate-api": "^4.1.0",
"code-inspector-plugin": "1.2.9",
"cross-env": "^10.1.0",
@ -201,10 +200,9 @@
"eslint-plugin-storybook": "^10.1.10",
"eslint-plugin-tailwindcss": "^3.18.2",
"husky": "^9.1.7",
"istanbul-lib-coverage": "^3.2.2",
"jsdom": "^27.3.0",
"jsdom-testing-mocks": "^1.16.0",
"knip": "^5.66.1",
"knip": "^5.78.0",
"lint-staged": "^15.5.2",
"nock": "^14.0.10",
"postcss": "^8.5.6",

View File

@ -481,9 +481,6 @@ importers:
autoprefixer:
specifier: ^10.4.21
version: 10.4.22(postcss@8.5.6)
babel-loader:
specifier: ^10.0.0
version: 10.0.0(@babel/core@7.28.5)(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3))
bing-translate-api:
specifier: ^4.1.0
version: 4.2.0
@ -514,9 +511,6 @@ importers:
husky:
specifier: ^9.1.7
version: 9.1.7
istanbul-lib-coverage:
specifier: ^3.2.2
version: 3.2.2
jsdom:
specifier: ^27.3.0
version: 27.3.0(canvas@3.2.0)
@ -524,8 +518,8 @@ importers:
specifier: ^1.16.0
version: 1.16.0
knip:
specifier: ^5.66.1
version: 5.72.0(@types/node@18.15.0)(typescript@5.9.3)
specifier: ^5.78.0
version: 5.78.0(@types/node@18.15.0)(typescript@5.9.3)
lint-staged:
specifier: ^15.5.2
version: 15.5.2
@ -4271,13 +4265,6 @@ packages:
peerDependencies:
postcss: ^8.1.0
babel-loader@10.0.0:
resolution: {integrity: sha512-z8jt+EdS61AMw22nSfoNJAZ0vrtmhPRVi6ghL3rCeRZI8cdNYFiV5xeV3HbE7rlZZNmGH8BVccwWt8/ED0QOHA==}
engines: {node: ^18.20.0 || ^20.10.0 || >=22.0.0}
peerDependencies:
'@babel/core': ^7.12.0
webpack: '>=5.61.0'
babel-loader@8.4.1:
resolution: {integrity: sha512-nXzRChX+Z1GoE6yWavBQg6jDslyFF3SDjl2paADuoQtQW10JqShJt62R6eJQ5m/pjJFDT8xgKIWSP85OY8eXeA==}
engines: {node: '>= 8.9'}
@ -6336,8 +6323,8 @@ packages:
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
engines: {node: '>=6'}
knip@5.72.0:
resolution: {integrity: sha512-rlyoXI8FcggNtM/QXd/GW0sbsYvNuA/zPXt7bsuVi6kVQogY2PDCr81bPpzNnl0CP8AkFm2Z2plVeL5QQSis2w==}
knip@5.78.0:
resolution: {integrity: sha512-nB7i/fgiJl7WVxdv5lX4ZPfDt9/zrw/lOgZtyioy988xtFhKuFJCRdHWT1Zg9Avc0yaojvnmEuAXU8SeMblKww==}
engines: {node: '>=18.18.0'}
hasBin: true
peerDependencies:
@ -13093,12 +13080,6 @@ snapshots:
postcss: 8.5.6
postcss-value-parser: 4.2.0
babel-loader@10.0.0(@babel/core@7.28.5)(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3)):
dependencies:
'@babel/core': 7.28.5
find-up: 5.0.0
webpack: 5.103.0(esbuild@0.25.0)(uglify-js@3.19.3)
babel-loader@8.4.1(@babel/core@7.28.5)(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3)):
dependencies:
'@babel/core': 7.28.5
@ -15468,7 +15449,7 @@ snapshots:
kleur@4.1.5: {}
knip@5.72.0(@types/node@18.15.0)(typescript@5.9.3):
knip@5.78.0(@types/node@18.15.0)(typescript@5.9.3):
dependencies:
'@nodelib/fs.walk': 1.2.8
'@types/node': 18.15.0

View File

@ -5,6 +5,7 @@ import { parseXml } from '@rgrove/parse-xml'
import { camelCase, template } from 'es-toolkit/compat'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const iconsDir = path.resolve(__dirname, '../app/components/base/icons')
const generateDir = async (currentPath) => {
try {
@ -32,7 +33,7 @@ const processSvgStructure = (svgStructure, replaceFillOrStrokeColor) => {
}
}
const generateSvgComponent = async (fileHandle, entry, pathList, replaceFillOrStrokeColor) => {
const currentPath = path.resolve(__dirname, 'src', ...pathList.slice(2))
const currentPath = path.resolve(iconsDir, 'src', ...pathList.slice(2))
try {
await access(currentPath)
@ -86,7 +87,7 @@ export { default as <%= svgName %> } from './<%= svgName %>'
}
const generateImageComponent = async (entry, pathList) => {
const currentPath = path.resolve(__dirname, 'src', ...pathList.slice(2))
const currentPath = path.resolve(iconsDir, 'src', ...pathList.slice(2))
try {
await access(currentPath)
@ -167,8 +168,8 @@ const walk = async (entry, pathList, replaceFillOrStrokeColor) => {
}
(async () => {
await rm(path.resolve(__dirname, 'src'), { recursive: true, force: true })
await walk('public', [__dirname, 'assets'])
await walk('vender', [__dirname, 'assets'], true)
await walk('image', [__dirname, 'assets'])
await rm(path.resolve(iconsDir, 'src'), { recursive: true, force: true })
await walk('public', [iconsDir, 'assets'])
await walk('vender', [iconsDir, 'assets'], true)
await walk('image', [iconsDir, 'assets'])
})()