lint and typecheck for doc link

This commit is contained in:
Stephen Zhou 2026-01-12 15:04:40 +08:00
parent 9161936f41
commit 46f0e3c07e
No known key found for this signature in database
9 changed files with 703 additions and 6 deletions

View File

@ -1,4 +1,5 @@
import type { Locale } from '@/i18n-config/language'
import type { DocPathWithoutLang } from '@/types/doc-paths'
import { useTranslation } from '#i18n'
import { getDocLanguage, getLanguage, getPricingPageLanguage } from '@/i18n-config/language'
@ -19,12 +20,14 @@ export const useGetPricingPageLanguage = () => {
}
export const defaultDocBaseUrl = 'https://docs.dify.ai'
export const useDocLink = (baseUrl?: string): ((path?: string, pathMap?: { [index: string]: string }) => string) => {
export type DocPathMap = Partial<Record<Locale, DocPathWithoutLang>>
export const useDocLink = (baseUrl?: string): ((path?: DocPathWithoutLang, pathMap?: DocPathMap) => string) => {
let baseDocUrl = baseUrl || defaultDocBaseUrl
baseDocUrl = (baseDocUrl.endsWith('/')) ? baseDocUrl.slice(0, -1) : baseDocUrl
const locale = useLocale()
const docLanguage = getDocLanguage(locale)
return (path?: string, pathMap?: { [index: string]: string }): string => {
return (path?: DocPathWithoutLang, pathMap?: DocPathMap): string => {
const pathUrl = path || ''
let targetPath = (pathMap) ? pathMap[locale] || pathUrl : pathUrl
targetPath = (targetPath.startsWith('/')) ? targetPath.slice(1) : targetPath

View File

@ -0,0 +1,70 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
//
// Generated from: https://raw.githubusercontent.com/langgenius/dify-docs/refs/heads/main/docs.json
// Generated at: 2026-01-12T06:51:06.502Z
/** @type {Map<string, string>} */
export const docRedirects = new Map([
['guides/knowledge-base/retrieval-test-and-citation', 'use-dify/knowledge/test-retrieval'],
['use-dify/knowledge/retrieval-test-and-citation', 'use-dify/knowledge/test-retrieval'],
['use-dify/build/variables#conversation-variables', 'use-dify/getting-started/key-concepts#variables'],
['use-dify/build/variables#会话变量', 'use-dify/getting-started/key-concepts#变量'],
['use-dify/build/variables#会話変数', 'use-dify/getting-started/key-concepts#変数'],
['guides/workflow/variables#conversation-variables', 'use-dify/getting-started/key-concepts#variables'],
['guides/workflow/variables#会话变量', 'use-dify/getting-started/key-concepts#变量'],
['guides/workflow/variables#会話変数', 'use-dify/getting-started/key-concepts#変数'],
['guides/workflow/node/start', 'use-dify/getting-started/key-concepts#workflow'],
['guides/workflow/node/user-input', 'use-dify/nodes/user-input'],
['guides/workflow/node/trigger', 'use-dify/nodes/trigger/overview'],
['guides/workflow/node/schedule-trigger', 'use-dify/nodes/trigger/schedule-trigger'],
['guides/workflow/node/webhook-trigger', 'use-dify/nodes/trigger/webhook-trigger'],
['guides/workflow/node/plugin-trigger', 'use-dify/nodes/trigger/plugin-trigger'],
['guides/workflow/node/knowledge-retrieval', 'use-dify/nodes/knowledge-retrieval'],
['guides/workflow/node/agent', 'use-dify/nodes/agent'],
['guides/workflow/node/question-classifier', 'use-dify/nodes/question-classifier'],
['guides/workflow/node/ifelse', 'use-dify/nodes/ifelse'],
['guides/workflow/node/iteration', 'use-dify/nodes/iteration'],
['guides/workflow/node/loop', 'use-dify/nodes/loop'],
['guides/workflow/node/code', 'use-dify/nodes/code'],
['guides/workflow/node/template', 'use-dify/nodes/template'],
['guides/workflow/node/variable-aggregator', 'use-dify/nodes/variable-aggregator'],
['guides/workflow/node/doc-extractor', 'use-dify/nodes/doc-extractor'],
['guides/workflow/node/variable-assigner', 'use-dify/nodes/variable-assigner'],
['guides/workflow/node/parameter-extractor', 'use-dify/nodes/parameter-extractor'],
['guides/workflow/node/http-request', 'use-dify/nodes/http-request'],
['guides/workflow/node/list-operator', 'use-dify/nodes/list-operator'],
['guides/workflow/node/tools', 'use-dify/nodes/tools'],
['guides/workflow/node/end', 'use-dify/nodes/output'],
['guides/workflow/node/llm', 'use-dify/nodes/llm'],
['getting-started/dify-for-education', 'use-dify/workspace/subscription-management#dify-for-education'],
['/plugins/schema-definition/model', '/versions/legacy/en/plugins/schema-definition/model/model-designing-rules'],
['/plugins/schema-definition', '/versions/legacy/en/plugins/schema-definition/manifest'],
['/zh-hans', 'use-dify/getting-started/introduction'],
['/introduction', 'use-dify/getting-started/introduction'],
['introduction', 'use-dify/getting-started/introduction'],
['/getting-started/readme/features-and-specifications', 'use-dify/getting-started/introduction'],
['/getting-started/readme/model-providers', 'use-dify/getting-started/introduction'],
['/getting-started/install-self-hosted', 'self-host/quick-start/docker-compose'],
['/getting-started/install-self-hosted/docker-compose', 'self-host/quick-start/docker-compose'],
['/getting-started/install-self-hosted/local-source-code', 'self-host/advanced-deployments/local-source-code'],
['/getting-started/install-self-hosted/bt-panel', 'self-host/platform-guides/bt-panel'],
['/getting-started/install-self-hosted/start-the-frontend-docker-container', 'self-host/advanced-deployments/start-the-frontend-docker-container'],
['/getting-started/install-self-hosted/environments', 'self-host/configuration/environments'],
['/getting-started/install-self-hosted/faqs', 'self-host/quick-start/faqs'],
['/getting-started/cloud', 'use-dify/getting-started/introduction'],
['/getting-started/dify-premium', 'self-host/platform-guides/dify-premium'],
['guides/application-publishing/launch-your-webapp-quickly/readme', 'use-dify/publish/README'],
['/plugins/quick-start', 'develop-plugin/getting-started/getting-started-dify-plugin'],
['/plugins/quick-start/install-plugins', 'develop-plugin/getting-started/getting-started-dify-plugin'],
['/plugins/quick-start/develop-plugins', 'develop-plugin/dev-guides-and-walkthroughs/cheatsheet'],
['/plugins/quick-start/develop-plugins/initialize-development-tools', 'develop-plugin/getting-started/cli'],
['/plugins/quick-start/develop-plugins/tool-plugin', 'develop-plugin/features-and-specs/plugin-types/tool'],
['/plugins/quick-start/develop-plugins/model-plugin', 'develop-plugin/features-and-specs/plugin-types/model-schema'],
['/plugins/quick-start/develop-plugins/model-plugin/create-model-providers', 'develop-plugin/dev-guides-and-walkthroughs/creating-new-model-provider'],
['learn-more/extended-reading/dify-docs-mcp', 'use-dify/build/mcp'],
['getting-started/cloud', 'use-dify/getting-started/introduction'],
['getting-started/dify-premium', 'use-dify/getting-started/introduction'],
['guides/model-configuration/load-balancing', 'use-dify/workspace/model-providers#configure-model-load-balancing'],
['/guides/model-configuration/load-balancing', 'use-dify/workspace/model-providers#configure-model-load-balancing'],
])

View File

@ -1,4 +1,5 @@
import noAsAnyInT from './rules/no-as-any-in-t.js'
import noDeprecatedDocLink from './rules/no-deprecated-doc-link.js'
import noExtraKeys from './rules/no-extra-keys.js'
import noLegacyNamespacePrefix from './rules/no-legacy-namespace-prefix.js'
import requireNsOption from './rules/require-ns-option.js'
@ -12,6 +13,7 @@ const plugin = {
},
rules: {
'no-as-any-in-t': noAsAnyInT,
'no-deprecated-doc-link': noDeprecatedDocLink,
'no-extra-keys': noExtraKeys,
'no-legacy-namespace-prefix': noLegacyNamespacePrefix,
'require-ns-option': requireNsOption,

View File

@ -0,0 +1,172 @@
import { docRedirects } from '../doc-redirects.js'
/** @type {import('eslint').Rule.RuleModule} */
export default {
meta: {
type: 'suggestion',
docs: {
description: 'Disallow deprecated documentation paths in useDocLink calls and auto-fix to new paths',
},
fixable: 'code',
schema: [],
messages: {
deprecatedDocLink:
'Deprecated documentation path detected. The path "{{oldPath}}" has been moved to "{{newPath}}".',
},
},
create(context) {
/**
* Check if the path needs redirect
* @param {string} docPath - Path without language prefix
*/
function checkRedirect(docPath) {
// Normalize path (remove leading slash if present)
const normalizedPath = docPath.startsWith('/') ? docPath.slice(1) : docPath
if (docRedirects.has(normalizedPath)) {
return {
oldPath: normalizedPath,
newPath: docRedirects.get(normalizedPath),
}
}
// Also check with leading slash
if (docRedirects.has(`/${normalizedPath}`)) {
return {
oldPath: normalizedPath,
newPath: docRedirects.get(`/${normalizedPath}`).replace(/^\//, ''),
}
}
return null
}
/**
* Check if node is a useDocLink call
* @param {import('estree').CallExpression} node
*/
function isUseDocLinkCall(node) {
// Check for direct useDocLink() call that returns a function
// The actual doc path is passed to the returned function
// e.g., const getLink = useDocLink(); getLink('path')
// or useDocLink()('path')
// For the pattern: useDocLink()('path')
if (
node.callee.type === 'CallExpression'
&& node.callee.callee.type === 'Identifier'
&& node.callee.callee.name === 'useDocLink'
) {
return true
}
return false
}
/**
* Check if node is a call to a function returned by useDocLink
* This handles: const docLink = useDocLink(); docLink('path')
*/
function isDocLinkFunctionCall(node, scope) {
if (node.callee.type !== 'Identifier')
return false
const calleeName = node.callee.name
// Look for variable declaration that assigns useDocLink()
const variable = scope.variables.find(v => v.name === calleeName)
if (!variable || variable.defs.length === 0)
return false
const def = variable.defs[0]
if (def.type !== 'Variable' || !def.node.init)
return false
const init = def.node.init
// Check if initialized with useDocLink()
if (
init.type === 'CallExpression'
&& init.callee.type === 'Identifier'
&& init.callee.name === 'useDocLink'
) {
return true
}
return false
}
/**
* Report a deprecated path and provide fix
*/
function reportDeprecatedPath(node, redirect, sourceCode) {
context.report({
node,
messageId: 'deprecatedDocLink',
data: {
oldPath: redirect.oldPath,
newPath: redirect.newPath,
},
fix(fixer) {
// Replace the string value, preserving the quote style
const raw = sourceCode.getText(node)
const quote = raw[0]
return fixer.replaceText(node, `${quote}${redirect.newPath}${quote}`)
},
})
}
/**
* Check a string literal node for deprecated path
*/
function checkStringLiteral(node, sourceCode) {
if (node.type !== 'Literal' || typeof node.value !== 'string')
return
const redirect = checkRedirect(node.value)
if (redirect)
reportDeprecatedPath(node, redirect, sourceCode)
}
/**
* Check pathMap object for deprecated paths
* pathMap is like { en: 'path1', zh: 'path2' }
*/
function checkPathMapObject(node, sourceCode) {
if (node.type !== 'ObjectExpression')
return
for (const prop of node.properties) {
if (prop.type !== 'Property')
continue
// Check the value of each property
checkStringLiteral(prop.value, sourceCode)
}
}
return {
CallExpression(node) {
const sourceCode = context.sourceCode || context.getSourceCode()
const scope = sourceCode.getScope?.(node) || context.getScope()
// Check if this is useDocLink()('path') pattern
const isDirectCall = isUseDocLinkCall(node)
// Check if this is docLink('path') where docLink = useDocLink()
const isIndirectCall = isDocLinkFunctionCall(node, scope)
if (!isDirectCall && !isIndirectCall)
return
// Check first argument (the doc path)
if (node.arguments.length >= 1)
checkStringLiteral(node.arguments[0], sourceCode)
// Check second argument (pathMap object)
if (node.arguments.length >= 2)
checkPathMapObject(node.arguments[1], sourceCode)
},
}
},
}

View File

@ -178,6 +178,7 @@ export default antfu(
rules: {
// 'dify-i18n/no-as-any-in-t': ['error', { mode: 'all' }],
'dify-i18n/no-as-any-in-t': 'error',
'dify-i18n/no-deprecated-doc-link': 'error',
// 'dify-i18n/no-legacy-namespace-prefix': 'error',
// 'dify-i18n/require-ns-option': 'error',
},

View File

@ -1,3 +1,4 @@
import type { DocLanguage } from '@/types/doc-paths'
import data from './languages'
export type Item = {
@ -22,9 +23,9 @@ export const getLanguage = (locale: Locale): Locale => {
return LanguagesSupported[0].replace('-', '_') as Locale
}
const DOC_LANGUAGE: Record<string, string> = {
'zh-Hans': 'zh-hans',
'ja-JP': 'ja-jp',
const DOC_LANGUAGE: Record<string, DocLanguage | undefined> = {
'zh-Hans': 'zh',
'ja-JP': 'ja',
'en-US': 'en',
}
@ -56,7 +57,7 @@ export const localeMap: Record<Locale, string> = {
'ar-TN': 'ar',
}
export const getDocLanguage = (locale: string) => {
export const getDocLanguage = (locale: string): DocLanguage => {
return DOC_LANGUAGE[locale] || 'en'
}

View File

@ -38,6 +38,7 @@
"type-check:tsgo": "tsgo --noEmit",
"prepare": "cd ../ && node -e \"if (process.env.NODE_ENV !== 'production'){process.exit(1)} \" || husky ./web/.husky",
"gen-icons": "node ./scripts/gen-icons.mjs && eslint --fix app/components/base/icons/src/",
"gen-doc-paths": "tsx ./scripts/gen-doc-paths.ts",
"uglify-embed": "node ./bin/uglify-embed",
"i18n:check": "tsx ./scripts/check-i18n.js",
"test": "vitest run",

View File

@ -0,0 +1,271 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
//
// This script fetches the docs.json from dify-docs repository
// and generates TypeScript types for documentation paths.
//
// Usage: pnpm gen-doc-paths
import { writeFile } from 'node:fs/promises'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const DOCS_JSON_URL = 'https://raw.githubusercontent.com/langgenius/dify-docs/refs/heads/main/docs.json'
const OUTPUT_PATH = path.resolve(__dirname, '../types/doc-paths.ts')
const REDIRECTS_PATH = path.resolve(__dirname, '../eslint-rules/doc-redirects.js')
type NavItem = string | NavObject | NavItem[]
type NavObject = {
pages?: NavItem[]
groups?: NavItem[]
dropdowns?: NavItem[]
languages?: NavItem[]
versions?: NavItem[]
[key: string]: unknown
}
type Redirect = {
source: string
destination: string
}
type DocsJson = {
navigation?: NavItem
redirects?: Redirect[]
[key: string]: unknown
}
/**
* Recursively extract all page paths from navigation structure
*/
function extractPaths(item: NavItem | undefined, paths: Set<string> = new Set()): Set<string> {
if (!item)
return paths
if (Array.isArray(item)) {
for (const el of item)
extractPaths(el, paths)
return paths
}
if (typeof item === 'string') {
paths.add(item)
return paths
}
if (typeof item === 'object') {
// Handle pages array
if (item.pages)
extractPaths(item.pages, paths)
// Handle groups array
if (item.groups)
extractPaths(item.groups, paths)
// Handle dropdowns
if (item.dropdowns)
extractPaths(item.dropdowns, paths)
// Handle languages
if (item.languages)
extractPaths(item.languages, paths)
// Handle versions in navigation
if (item.versions)
extractPaths(item.versions, paths)
}
return paths
}
/**
* Group paths by their prefix structure
*/
function groupPathsBySection(paths: Set<string>): Record<string, Set<string>> {
const groups: Record<string, Set<string>> = {}
for (const fullPath of paths) {
// Skip non-doc paths (like .json files for OpenAPI)
if (fullPath.endsWith('.json'))
continue
// Remove language prefix (en/, zh/, ja/)
const withoutLang = fullPath.replace(/^(en|zh|ja)\//, '')
if (!withoutLang || withoutLang === fullPath)
continue
// Get section (first part of path)
const parts = withoutLang.split('/')
const section = parts[0]
if (!groups[section])
groups[section] = new Set()
groups[section].add(withoutLang)
}
return groups
}
/**
* Convert section name to TypeScript type name
*/
function sectionToTypeName(section: string): string {
return section
.split('-')
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
.join('')
}
/**
* Generate TypeScript type definitions
*/
function generateTypeDefinitions(groups: Record<string, Set<string>>): string {
const lines: string[] = [
'// GENERATE BY script',
'// DON NOT EDIT IT MANUALLY',
'//',
'// Generated from: https://raw.githubusercontent.com/langgenius/dify-docs/refs/heads/main/docs.json',
`// Generated at: ${new Date().toISOString()}`,
'',
'// Language prefixes',
'export type DocLanguage = \'en\' | \'zh\' | \'ja\'',
'',
]
const typeNames: string[] = []
// Generate type for each section
for (const [section, pathsSet] of Object.entries(groups)) {
const paths = Array.from(pathsSet).sort()
const typeName = `${sectionToTypeName(section)}Path`
typeNames.push(typeName)
lines.push(`// ${sectionToTypeName(section)} paths`)
lines.push(`export type ${typeName} =`)
for (const p of paths) {
lines.push(` | '${p}'`)
}
lines.push('')
}
// Generate API reference type (for .json files)
lines.push('// API Reference paths')
lines.push('export type ApiReferencePath =')
lines.push(' | \'api-reference/openapi_chat.json\'')
lines.push(' | \'api-reference/openapi_chatflow.json\'')
lines.push(' | \'api-reference/openapi_workflow.json\'')
lines.push(' | \'api-reference/openapi_knowledge.json\'')
lines.push(' | \'api-reference/openapi_completion.json\'')
lines.push('')
typeNames.push('ApiReferencePath')
// Generate combined type
lines.push('// Combined path without language prefix')
lines.push('export type DocPathWithoutLang =')
for (const typeName of typeNames) {
lines.push(` | ${typeName}`)
}
lines.push('')
// Generate full path type with language prefix
lines.push('// Full documentation path with language prefix')
// eslint-disable-next-line no-template-curly-in-string
lines.push('export type DifyDocPath = `${DocLanguage}/${DocPathWithoutLang}`')
lines.push('')
return lines.join('\n')
}
/**
* Generate redirects map for ESLint rule
* Strips language prefix from paths for use with useDocLink
*/
function generateRedirectsModule(redirects: Redirect[]): string {
const lines: string[] = [
'// GENERATE BY script',
'// DON NOT EDIT IT MANUALLY',
'//',
'// Generated from: https://raw.githubusercontent.com/langgenius/dify-docs/refs/heads/main/docs.json',
`// Generated at: ${new Date().toISOString()}`,
'',
'/** @type {Map<string, string>} */',
'export const docRedirects = new Map([',
]
// Use a map to deduplicate paths (same path in different languages)
const pathMap = new Map<string, string>()
const langPrefixRegex = /^\/(en|zh|ja|zh-hans|ja-jp)\//
for (const redirect of redirects) {
// Skip wildcard redirects
if (redirect.source.includes(':slug'))
continue
// Strip language prefix from source and destination
const sourceWithoutLang = redirect.source.replace(langPrefixRegex, '')
const destWithoutLang = redirect.destination.replace(langPrefixRegex, '')
// Only add if we haven't seen this path yet
if (!pathMap.has(sourceWithoutLang))
pathMap.set(sourceWithoutLang, destWithoutLang)
}
for (const [source, dest] of pathMap) {
lines.push(` ['${source}', '${dest}'],`)
}
lines.push('])')
lines.push('')
return lines.join('\n')
}
async function main(): Promise<void> {
// eslint-disable-next-line no-console
console.log('Fetching docs.json from GitHub...')
const response = await fetch(DOCS_JSON_URL)
if (!response.ok)
throw new Error(`Failed to fetch docs.json: ${response.status} ${response.statusText}`)
const docsJson = await response.json() as DocsJson
// eslint-disable-next-line no-console
console.log('Successfully fetched docs.json')
// Extract paths from navigation
const allPaths = extractPaths(docsJson.navigation)
// eslint-disable-next-line no-console
console.log(`Found ${allPaths.size} total paths`)
// Group by section
const groups = groupPathsBySection(allPaths)
// eslint-disable-next-line no-console
console.log(`Grouped into ${Object.keys(groups).length} sections:`, Object.keys(groups))
// Generate TypeScript
const tsContent = generateTypeDefinitions(groups)
// Write to file
await writeFile(OUTPUT_PATH, tsContent, 'utf-8')
// eslint-disable-next-line no-console
console.log(`Generated TypeScript types at: ${OUTPUT_PATH}`)
// Generate redirects module for ESLint rule
const redirects = docsJson.redirects || []
const redirectsContent = generateRedirectsModule(redirects)
await writeFile(REDIRECTS_PATH, redirectsContent, 'utf-8')
// eslint-disable-next-line no-console
console.log(`Generated redirects module at: ${REDIRECTS_PATH} (${redirects.length} redirects)`)
}
main().catch((err: Error) => {
console.error('Error:', err.message)
process.exit(1)
})

176
web/types/doc-paths.ts Normal file
View File

@ -0,0 +1,176 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
//
// Generated from: https://raw.githubusercontent.com/langgenius/dify-docs/refs/heads/main/docs.json
// Generated at: 2026-01-12T06:51:06.500Z
// Language prefixes
export type DocLanguage = 'en' | 'zh' | 'ja'
// UseDify paths
export type UseDifyPath =
| 'use-dify/build/additional-features'
| 'use-dify/build/goto-anything'
| 'use-dify/build/mcp'
| 'use-dify/build/orchestrate-node'
| 'use-dify/build/predefined-error-handling-logic'
| 'use-dify/build/shortcut-key'
| 'use-dify/build/version-control'
| 'use-dify/debug/error-type'
| 'use-dify/debug/history-and-logs'
| 'use-dify/debug/step-run'
| 'use-dify/debug/variable-inspect'
| 'use-dify/getting-started/introduction'
| 'use-dify/getting-started/key-concepts'
| 'use-dify/getting-started/quick-start'
| 'use-dify/knowledge/connect-external-knowledge-base'
| 'use-dify/knowledge/create-knowledge/chunking-and-cleaning-text'
| 'use-dify/knowledge/create-knowledge/import-text-data/readme'
| 'use-dify/knowledge/create-knowledge/import-text-data/sync-from-notion'
| 'use-dify/knowledge/create-knowledge/import-text-data/sync-from-website'
| 'use-dify/knowledge/create-knowledge/introduction'
| 'use-dify/knowledge/create-knowledge/setting-indexing-methods'
| 'use-dify/knowledge/external-knowledge-api'
| 'use-dify/knowledge/integrate-knowledge-within-application'
| 'use-dify/knowledge/knowledge-pipeline/authorize-data-source'
| 'use-dify/knowledge/knowledge-pipeline/create-knowledge-pipeline'
| 'use-dify/knowledge/knowledge-pipeline/knowledge-pipeline-orchestration'
| 'use-dify/knowledge/knowledge-pipeline/manage-knowledge-base'
| 'use-dify/knowledge/knowledge-pipeline/publish-knowledge-pipeline'
| 'use-dify/knowledge/knowledge-pipeline/readme'
| 'use-dify/knowledge/knowledge-pipeline/upload-files'
| 'use-dify/knowledge/knowledge-request-rate-limit'
| 'use-dify/knowledge/manage-knowledge/introduction'
| 'use-dify/knowledge/manage-knowledge/maintain-dataset-via-api'
| 'use-dify/knowledge/manage-knowledge/maintain-knowledge-documents'
| 'use-dify/knowledge/metadata'
| 'use-dify/knowledge/readme'
| 'use-dify/knowledge/test-retrieval'
| 'use-dify/monitor/analysis'
| 'use-dify/monitor/annotation-reply'
| 'use-dify/monitor/integrations/integrate-aliyun'
| 'use-dify/monitor/integrations/integrate-arize'
| 'use-dify/monitor/integrations/integrate-langfuse'
| 'use-dify/monitor/integrations/integrate-langsmith'
| 'use-dify/monitor/integrations/integrate-opik'
| 'use-dify/monitor/integrations/integrate-phoenix'
| 'use-dify/monitor/integrations/integrate-weave'
| 'use-dify/monitor/logs'
| 'use-dify/nodes/agent'
| 'use-dify/nodes/answer'
| 'use-dify/nodes/code'
| 'use-dify/nodes/doc-extractor'
| 'use-dify/nodes/http-request'
| 'use-dify/nodes/ifelse'
| 'use-dify/nodes/iteration'
| 'use-dify/nodes/knowledge-retrieval'
| 'use-dify/nodes/list-operator'
| 'use-dify/nodes/llm'
| 'use-dify/nodes/loop'
| 'use-dify/nodes/output'
| 'use-dify/nodes/parameter-extractor'
| 'use-dify/nodes/question-classifier'
| 'use-dify/nodes/template'
| 'use-dify/nodes/tools'
| 'use-dify/nodes/trigger/overview'
| 'use-dify/nodes/trigger/plugin-trigger'
| 'use-dify/nodes/trigger/schedule-trigger'
| 'use-dify/nodes/trigger/webhook-trigger'
| 'use-dify/nodes/user-input'
| 'use-dify/nodes/variable-aggregator'
| 'use-dify/nodes/variable-assigner'
| 'use-dify/publish/README'
| 'use-dify/publish/developing-with-apis'
| 'use-dify/publish/publish-mcp'
| 'use-dify/publish/webapp/chatflow-webapp'
| 'use-dify/publish/webapp/embedding-in-websites'
| 'use-dify/publish/webapp/web-app-access'
| 'use-dify/publish/webapp/web-app-settings'
| 'use-dify/publish/webapp/workflow-webapp'
| 'use-dify/tutorials/article-reader'
| 'use-dify/tutorials/build-ai-image-generation-app'
| 'use-dify/tutorials/customer-service-bot'
| 'use-dify/tutorials/simple-chatbot'
| 'use-dify/tutorials/twitter-chatflow'
| 'use-dify/workspace/app-management'
| 'use-dify/workspace/model-providers'
| 'use-dify/workspace/personal-account-management'
| 'use-dify/workspace/plugins'
| 'use-dify/workspace/readme'
| 'use-dify/workspace/subscription-management'
| 'use-dify/workspace/team-members-management'
// SelfHost paths
export type SelfHostPath =
| 'self-host/advanced-deployments/local-source-code'
| 'self-host/advanced-deployments/start-the-frontend-docker-container'
| 'self-host/configuration/environments'
| 'self-host/platform-guides/bt-panel'
| 'self-host/platform-guides/dify-premium'
| 'self-host/quick-start/docker-compose'
| 'self-host/quick-start/faqs'
| 'self-host/troubleshooting/common-issues'
| 'self-host/troubleshooting/docker-issues'
| 'self-host/troubleshooting/integrations'
| 'self-host/troubleshooting/storage-and-migration'
| 'self-host/troubleshooting/weaviate-v4-migration'
// DevelopPlugin paths
export type DevelopPluginPath =
| 'develop-plugin/dev-guides-and-walkthroughs/agent-strategy-plugin'
| 'develop-plugin/dev-guides-and-walkthroughs/cheatsheet'
| 'develop-plugin/dev-guides-and-walkthroughs/creating-new-model-provider'
| 'develop-plugin/dev-guides-and-walkthroughs/datasource-plugin'
| 'develop-plugin/dev-guides-and-walkthroughs/develop-a-slack-bot-plugin'
| 'develop-plugin/dev-guides-and-walkthroughs/develop-flomo-plugin'
| 'develop-plugin/dev-guides-and-walkthroughs/develop-md-exporter'
| 'develop-plugin/dev-guides-and-walkthroughs/develop-multimodal-data-processing-tool'
| 'develop-plugin/dev-guides-and-walkthroughs/endpoint'
| 'develop-plugin/dev-guides-and-walkthroughs/tool-oauth'
| 'develop-plugin/dev-guides-and-walkthroughs/tool-plugin'
| 'develop-plugin/dev-guides-and-walkthroughs/trigger-plugin'
| 'develop-plugin/features-and-specs/advanced-development/bundle'
| 'develop-plugin/features-and-specs/advanced-development/customizable-model'
| 'develop-plugin/features-and-specs/advanced-development/reverse-invocation'
| 'develop-plugin/features-and-specs/advanced-development/reverse-invocation-app'
| 'develop-plugin/features-and-specs/advanced-development/reverse-invocation-model'
| 'develop-plugin/features-and-specs/advanced-development/reverse-invocation-node'
| 'develop-plugin/features-and-specs/advanced-development/reverse-invocation-tool'
| 'develop-plugin/features-and-specs/plugin-types/general-specifications'
| 'develop-plugin/features-and-specs/plugin-types/model-designing-rules'
| 'develop-plugin/features-and-specs/plugin-types/model-schema'
| 'develop-plugin/features-and-specs/plugin-types/multilingual-readme'
| 'develop-plugin/features-and-specs/plugin-types/persistent-storage-kv'
| 'develop-plugin/features-and-specs/plugin-types/plugin-info-by-manifest'
| 'develop-plugin/features-and-specs/plugin-types/plugin-logging'
| 'develop-plugin/features-and-specs/plugin-types/remote-debug-a-plugin'
| 'develop-plugin/features-and-specs/plugin-types/tool'
| 'develop-plugin/getting-started/cli'
| 'develop-plugin/getting-started/getting-started-dify-plugin'
| 'develop-plugin/publishing/faq/faq'
| 'develop-plugin/publishing/marketplace-listing/plugin-auto-publish-pr'
| 'develop-plugin/publishing/marketplace-listing/release-by-file'
| 'develop-plugin/publishing/marketplace-listing/release-overview'
| 'develop-plugin/publishing/marketplace-listing/release-to-dify-marketplace'
| 'develop-plugin/publishing/marketplace-listing/release-to-individual-github-repo'
| 'develop-plugin/publishing/standards/contributor-covenant-code-of-conduct'
| 'develop-plugin/publishing/standards/privacy-protection-guidelines'
| 'develop-plugin/publishing/standards/third-party-signature-verification'
// API Reference paths
export type ApiReferencePath =
| 'api-reference/openapi_chat.json'
| 'api-reference/openapi_chatflow.json'
| 'api-reference/openapi_workflow.json'
| 'api-reference/openapi_knowledge.json'
| 'api-reference/openapi_completion.json'
// Combined path without language prefix
export type DocPathWithoutLang =
| UseDifyPath
| SelfHostPath
| DevelopPluginPath
| ApiReferencePath
// Full documentation path with language prefix
export type DifyDocPath = `${DocLanguage}/${DocPathWithoutLang}`