chore: workspace level typecheck (#35329)

This commit is contained in:
Stephen Zhou 2026-04-16 22:42:04 +08:00 committed by GitHub
parent c966e281d4
commit 4289cb2634
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
44 changed files with 236 additions and 294 deletions

View File

@ -77,6 +77,8 @@ jobs:
with:
files: |
web/**
e2e/**
sdks/nodejs-client/**
packages/**
package.json
pnpm-lock.yaml
@ -112,7 +114,7 @@ jobs:
- name: Web type check
if: steps.changed-files.outputs.any_changed == 'true'
working-directory: ./web
working-directory: .
run: vp run type-check
- name: Web dead code check

View File

@ -64,36 +64,8 @@ if $web_modified; then
echo "Running ESLint on web module"
if git diff --cached --quiet -- 'web/**/*.ts' 'web/**/*.tsx'; then
web_ts_modified=false
else
ts_diff_status=$?
if [ $ts_diff_status -eq 1 ]; then
web_ts_modified=true
else
echo "Unable to determine staged TypeScript changes (git exit code: $ts_diff_status)."
exit $ts_diff_status
fi
fi
cd ./web || exit 1
vp staged
if $web_ts_modified; then
echo "Running TypeScript type-check:tsgo"
if ! npm run type-check:tsgo; then
echo "Type check failed. Please run 'npm run type-check:tsgo' to fix the errors."
exit 1
fi
else
echo "No staged TypeScript changes detected, skipping type-check:tsgo"
fi
echo "Running knip"
if ! npm run knip; then
echo "Knip check failed. Please run 'npm run knip' to fix the errors."
exit 1
fi
cd ../
fi

View File

@ -11,9 +11,11 @@
"e2e:install": "playwright install --with-deps chromium",
"e2e:middleware:down": "tsx ./scripts/setup.ts middleware-down",
"e2e:middleware:up": "tsx ./scripts/setup.ts middleware-up",
"e2e:reset": "tsx ./scripts/setup.ts reset"
"e2e:reset": "tsx ./scripts/setup.ts reset",
"type-check": "tsc"
},
"devDependencies": {
"@dify/tsconfig": "workspace:*",
"@cucumber/cucumber": "catalog:",
"@playwright/test": "catalog:",
"@types/node": "catalog:",

View File

@ -17,12 +17,10 @@ const parseArgs = (argv: string[]): RunOptions => {
let headed = false
const forwardArgs: string[] = []
for (let index = 0; index < argv.length; index += 1) {
const arg = argv[index]
for (const [index, arg] of argv.entries()) {
if (arg === '--') {
forwardArgs.push(...argv.slice(index + 1))
break
return { forwardArgs, full, headed }
}
if (arg === '--full') {
@ -38,11 +36,7 @@ const parseArgs = (argv: string[]): RunOptions => {
forwardArgs.push(arg)
}
return {
forwardArgs,
full,
headed,
}
return { forwardArgs, full, headed }
}
const hasCustomTags = (forwardArgs: string[]) =>

View File

@ -1,16 +1,9 @@
{
"extends": "@dify/tsconfig/node.json",
"compilerOptions": {
"target": "ES2023",
"lib": ["ES2023", "DOM"],
"module": "ESNext",
"moduleResolution": "Bundler",
"allowJs": false,
"resolveJsonModule": true,
"noEmit": true,
"strict": true,
"skipLibCheck": true,
"types": ["node", "@playwright/test", "@cucumber/cucumber"],
"isolatedModules": true,
"verbatimModuleSyntax": true
},
"include": ["./**/*.ts"],

View File

@ -2,7 +2,8 @@
"name": "dify",
"private": true,
"scripts": {
"prepare": "vp config"
"prepare": "vp config",
"type-check": "vp run -r type-check"
},
"devDependencies": {
"vite": "catalog:",

View File

@ -19,6 +19,11 @@
"tailwind-merge": "catalog:"
},
"devDependencies": {
"tailwindcss": "catalog:"
"@dify/tsconfig": "workspace:*",
"tailwindcss": "catalog:",
"typescript": "catalog:"
},
"scripts": {
"type-check": "tsc"
}
}

View File

@ -1,11 +1,7 @@
{
"extends": "@dify/tsconfig/base.json",
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"noEmit": false,
"declaration": true,
"declarationMap": true,
"sourceMap": true,

View File

@ -7,12 +7,14 @@
"migrate-no-unchecked-indexed-access": "./bin/migrate-no-unchecked-indexed-access.js"
},
"scripts": {
"build": "vp pack"
"build": "vp pack",
"type-check": "tsc"
},
"dependencies": {
"typescript": "catalog:"
},
"devDependencies": {
"@dify/tsconfig": "workspace:*",
"@types/node": "catalog:",
"vite": "catalog:",
"vite-plus": "catalog:"

View File

@ -34,7 +34,8 @@ let exitCode = 0
try {
await main()
exitCode = process.exitCode ?? 0
const currentExitCode = process.exitCode
exitCode = typeof currentExitCode === 'number' ? currentExitCode : 0
}
catch (error) {
console.error(error instanceof Error ? error.message : error)

View File

@ -0,0 +1,3 @@
{
"extends": "@dify/tsconfig/node.json"
}

View File

@ -0,0 +1,19 @@
{
"compilerOptions": {
"esModuleInterop": true,
"skipLibCheck": true,
"target": "es2022",
"allowJs": true,
"resolveJsonModule": true,
"moduleDetection": "force",
"isolatedModules": true,
"verbatimModuleSyntax": true,
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"module": "preserve",
"noEmit": true
}
}

View File

@ -0,0 +1,10 @@
{
"extends": "./web.json",
"compilerOptions": {
"plugins": [
{
"name": "next"
}
]
}
}

View File

@ -0,0 +1,7 @@
{
"extends": "./base.json",
"compilerOptions": {
"lib": ["es2022"],
"types": ["node"]
}
}

View File

@ -0,0 +1,11 @@
{
"name": "@dify/tsconfig",
"version": "0.0.0-private",
"private": true,
"exports": {
"./base.json": "./base.json",
"./nextjs.json": "./nextjs.json",
"./node.json": "./node.json",
"./web.json": "./web.json"
}
}

View File

@ -0,0 +1,7 @@
{
"extends": "./base.json",
"compilerOptions": {
"jsx": "react-jsx",
"lib": ["es2022", "dom", "dom.iterable"]
}
}

20
pnpm-lock.yaml generated
View File

@ -586,6 +586,9 @@ importers:
'@cucumber/cucumber':
specifier: 'catalog:'
version: 12.8.0
'@dify/tsconfig':
specifier: workspace:*
version: link:../packages/tsconfig
'@playwright/test':
specifier: 'catalog:'
version: 1.59.1
@ -614,9 +617,15 @@ importers:
specifier: 'catalog:'
version: 3.5.0
devDependencies:
'@dify/tsconfig':
specifier: workspace:*
version: link:../tsconfig
tailwindcss:
specifier: 'catalog:'
version: 4.2.2
typescript:
specifier: 'catalog:'
version: 6.0.2
packages/iconify-collections:
devDependencies:
@ -630,6 +639,9 @@ importers:
specifier: 'catalog:'
version: 6.0.2
devDependencies:
'@dify/tsconfig':
specifier: workspace:*
version: link:../tsconfig
'@types/node':
specifier: 'catalog:'
version: 25.6.0
@ -640,8 +652,13 @@ importers:
specifier: 'catalog:'
version: 0.1.18(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4)(@voidzero-dev/vite-plus-core@0.1.18(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.9.0)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3)
packages/tsconfig: {}
sdks/nodejs-client:
devDependencies:
'@dify/tsconfig':
specifier: workspace:*
version: link:../../packages/tsconfig
'@eslint/js':
specifier: 'catalog:'
version: 10.0.1(eslint@10.2.0(jiti@2.6.1))
@ -991,6 +1008,9 @@ importers:
'@dify/iconify-collections':
specifier: workspace:*
version: link:../packages/iconify-collections
'@dify/tsconfig':
specifier: workspace:*
version: link:../packages/tsconfig
'@egoist/tailwindcss-icons':
specifier: 'catalog:'
version: 1.9.2(tailwindcss@4.2.2)

View File

@ -48,13 +48,14 @@
"build": "vp pack",
"lint": "eslint",
"lint:fix": "eslint --fix",
"type-check": "tsc -p tsconfig.json --noEmit",
"type-check": "tsc",
"test": "vp test",
"test:coverage": "vp test --coverage",
"publish:check": "./scripts/publish.sh --dry-run",
"publish:npm": "./scripts/publish.sh"
},
"devDependencies": {
"@dify/tsconfig": "workspace:*",
"@eslint/js": "catalog:",
"@types/node": "catalog:",
"@typescript-eslint/eslint-plugin": "catalog:",

View File

@ -14,8 +14,8 @@ describe("sse parsing", () => {
events.push(event);
}
expect(events).toHaveLength(1);
expect(events[0].event).toBe("message");
expect(events[0].data).toEqual({ answer: "hi" });
expect(events[0]!.event).toBe("message");
expect(events[0]!.data).toEqual({ answer: "hi" });
});
it("handles multi-line data payloads", async () => {
@ -24,8 +24,8 @@ describe("sse parsing", () => {
for await (const event of parseSseStream(stream)) {
events.push(event);
}
expect(events[0].raw).toBe("line1\nline2");
expect(events[0].data).toBe("line1\nline2");
expect(events[0]!.raw).toBe("line1\nline2");
expect(events[0]!.data).toBe("line1\nline2");
});
it("ignores comments and flushes the last event without a trailing separator", async () => {

View File

@ -99,7 +99,7 @@ describe("File uploads", () => {
super();
}
_read() {}
override _read() {}
append() {}

View File

@ -1,18 +1,14 @@
{
"extends": "@dify/tsconfig/node.json",
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"lib": ["ES2023", "DOM", "DOM.Iterable"],
"rootDir": ".",
"outDir": "dist",
"noEmit": false,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"strict": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
"types": ["node"]
},
"include": ["src/**/*.ts", "tests/**/*.ts"]
"include": ["src/**/*.ts", "tests/**/*.ts", "vite.config.ts"]
}

View File

@ -1,78 +0,0 @@
/**
* Tests for Array.prototype.toSpliced polyfill
*/
describe('toSpliced polyfill', () => {
let originalToSpliced: typeof Array.prototype.toSpliced
beforeEach(() => {
// Save original method
originalToSpliced = Array.prototype.toSpliced
})
afterEach(() => {
// Restore original method
// eslint-disable-next-line no-extend-native
Array.prototype.toSpliced = originalToSpliced
})
const applyPolyfill = () => {
// @ts-expect-error - intentionally deleting for test
delete Array.prototype.toSpliced
if (!Array.prototype.toSpliced) {
// eslint-disable-next-line no-extend-native
Array.prototype.toSpliced = function <T>(this: T[], start: number, deleteCount?: number, ...items: T[]): T[] {
const copy = this.slice()
copy.splice(start, deleteCount ?? copy.length - start, ...items)
return copy
}
}
}
it('should add toSpliced method when not available', () => {
applyPolyfill()
expect(typeof Array.prototype.toSpliced).toBe('function')
})
it('should return a new array without modifying the original', () => {
applyPolyfill()
const arr = [1, 2, 3, 4, 5]
const result = arr.toSpliced(1, 2)
expect(result).toEqual([1, 4, 5])
expect(arr).toEqual([1, 2, 3, 4, 5]) // original unchanged
})
it('should insert items at the specified position', () => {
applyPolyfill()
const arr: (number | string)[] = [1, 2, 3]
const result = arr.toSpliced(1, 0, 'a', 'b')
expect(result).toEqual([1, 'a', 'b', 2, 3])
})
it('should replace items at the specified position', () => {
applyPolyfill()
const arr: (number | string)[] = [1, 2, 3, 4, 5]
const result = arr.toSpliced(1, 2, 'a', 'b')
expect(result).toEqual([1, 'a', 'b', 4, 5])
})
it('should handle negative start index', () => {
applyPolyfill()
const arr = [1, 2, 3, 4, 5]
const result = arr.toSpliced(-2, 1)
expect(result).toEqual([1, 2, 3, 5])
})
it('should delete to end when deleteCount is omitted', () => {
applyPolyfill()
const arr = [1, 2, 3, 4, 5]
const result = arr.toSpliced(2)
expect(result).toEqual([1, 2])
})
})

View File

@ -258,7 +258,8 @@ describe('CreateFromDSLModal', () => {
expect(getCreateButton())!.toBeDisabled()
})
ahooksMocks.handlers.findLast(item => Array.isArray(item.keys))?.handler()
const latestHandlerAfterRemove = [...ahooksMocks.handlers].reverse().find(item => Array.isArray(item.keys))
latestHandlerAfterRemove?.handler()
expect(mockImportDSL).not.toHaveBeenCalled()
})
@ -387,7 +388,8 @@ describe('CreateFromDSLModal', () => {
)
expect(screen.getByText('apps-full'))!.toBeInTheDocument()
ahooksMocks.handlers.findLast(item => Array.isArray(item.keys))?.handler()
const latestPlanLimitHandler = [...ahooksMocks.handlers].reverse().find(item => Array.isArray(item.keys))
latestPlanLimitHandler?.handler()
expect(mockImportDSL).toHaveBeenCalledTimes(1)
})

View File

@ -67,7 +67,7 @@ class ErrorBoundaryInner extends React.Component<
}
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
override componentDidCatch(error: Error, errorInfo: ErrorInfo) {
if (IS_DEV) {
console.error('ErrorBoundary caught an error:', error)
console.error('Error Info:', errorInfo)
@ -82,7 +82,7 @@ class ErrorBoundaryInner extends React.Component<
this.props.onError(error, errorInfo)
}
componentDidUpdate(prevProps: any) {
override componentDidUpdate(prevProps: any) {
const { resetKeys, resetOnPropsChange } = this.props
const { hasError } = this.state
@ -98,7 +98,7 @@ class ErrorBoundaryInner extends React.Component<
this.props.onResetKeysChange(prevProps.resetKeys)
}
render() {
override render() {
const { hasError, error, errorInfo, errorCount } = this.state
const {
fallback,

View File

@ -17,12 +17,12 @@ export default class ErrorBoundary extends Component {
this.state = { hasError: false }
}
componentDidCatch(error: any, errorInfo: any) {
override componentDidCatch(error: any, errorInfo: any) {
this.setState({ hasError: true })
console.error(error, errorInfo)
}
render() {
override render() {
// eslint-disable-next-line ts/ban-ts-comment
// @ts-expect-error
if (this.state.hasError) {

View File

@ -10,15 +10,15 @@ export class ContextBlockNode extends DecoratorNode<React.JSX.Element> {
__onAddContext: () => void
__canNotAddContext: boolean
static getType(): string {
static override getType(): string {
return 'context-block'
}
static clone(node: ContextBlockNode): ContextBlockNode {
static override clone(node: ContextBlockNode): ContextBlockNode {
return new ContextBlockNode(node.__datasets, node.__onAddContext, node.getKey(), node.__canNotAddContext)
}
isInline(): boolean {
override isInline(): boolean {
return true
}
@ -30,17 +30,17 @@ export class ContextBlockNode extends DecoratorNode<React.JSX.Element> {
this.__canNotAddContext = canNotAddContext || false
}
createDOM(): HTMLElement {
override createDOM(): HTMLElement {
const div = document.createElement('div')
div.classList.add('inline-flex', 'items-center', 'align-middle')
return div
}
updateDOM(): false {
override updateDOM(): false {
return false
}
decorate(): React.JSX.Element {
override decorate(): React.JSX.Element {
return (
<ContextBlockComponent
nodeKey={this.getKey()}
@ -69,13 +69,13 @@ export class ContextBlockNode extends DecoratorNode<React.JSX.Element> {
return self.__canNotAddContext
}
static importJSON(serializedNode: SerializedNode): ContextBlockNode {
static override importJSON(serializedNode: SerializedNode): ContextBlockNode {
const node = $createContextBlockNode(serializedNode.datasets, serializedNode.onAddContext, serializedNode.canNotAddContext)
return node
}
exportJSON(): SerializedNode {
override exportJSON(): SerializedNode {
return {
type: 'context-block',
version: 1,
@ -85,7 +85,7 @@ export class ContextBlockNode extends DecoratorNode<React.JSX.Element> {
}
}
getTextContent(): string {
override getTextContent(): string {
return '{{#context#}}'
}
}

View File

@ -7,15 +7,15 @@ type SerializedNode = SerializedLexicalNode & { generatorType: GeneratorType }
export class CurrentBlockNode extends DecoratorNode<React.JSX.Element> {
__generatorType: GeneratorType
static getType(): string {
static override getType(): string {
return 'current-block'
}
static clone(node: CurrentBlockNode): CurrentBlockNode {
static override clone(node: CurrentBlockNode): CurrentBlockNode {
return new CurrentBlockNode(node.__generatorType, node.getKey())
}
isInline(): boolean {
override isInline(): boolean {
return true
}
@ -25,17 +25,17 @@ export class CurrentBlockNode extends DecoratorNode<React.JSX.Element> {
this.__generatorType = generatorType
}
createDOM(): HTMLElement {
override createDOM(): HTMLElement {
const div = document.createElement('div')
div.classList.add('inline-flex', 'items-center', 'align-middle')
return div
}
updateDOM(): false {
override updateDOM(): false {
return false
}
decorate(): React.JSX.Element {
override decorate(): React.JSX.Element {
return (
<CurrentBlockComponent
nodeKey={this.getKey()}
@ -49,13 +49,13 @@ export class CurrentBlockNode extends DecoratorNode<React.JSX.Element> {
return self.__generatorType
}
static importJSON(serializedNode: SerializedNode): CurrentBlockNode {
static override importJSON(serializedNode: SerializedNode): CurrentBlockNode {
const node = $createCurrentBlockNode(serializedNode.generatorType)
return node
}
exportJSON(): SerializedNode {
override exportJSON(): SerializedNode {
return {
type: 'current-block',
version: 1,
@ -63,7 +63,7 @@ export class CurrentBlockNode extends DecoratorNode<React.JSX.Element> {
}
}
getTextContent(): string {
override getTextContent(): string {
return '{{#current#}}'
}
}

View File

@ -2,11 +2,11 @@ import type { EditorConfig, SerializedTextNode } from 'lexical'
import { $createTextNode, TextNode } from 'lexical'
export class CustomTextNode extends TextNode {
static getType() {
static override getType() {
return 'custom-text'
}
static clone(node: CustomTextNode) {
static override clone(node: CustomTextNode) {
return new CustomTextNode(node.__text, node.__key)
}
@ -14,12 +14,12 @@ export class CustomTextNode extends TextNode {
// super(text, key)
// }
createDOM(config: EditorConfig) {
override createDOM(config: EditorConfig) {
const dom = super.createDOM(config)
return dom
}
static importJSON(serializedNode: SerializedTextNode): TextNode {
static override importJSON(serializedNode: SerializedTextNode): TextNode {
const node = $createTextNode(serializedNode.text)
node.setFormat(serializedNode.format)
node.setDetail(serializedNode.detail)
@ -28,7 +28,7 @@ export class CustomTextNode extends TextNode {
return node
}
exportJSON(): SerializedTextNode {
override exportJSON(): SerializedTextNode {
return {
detail: this.getDetail(),
format: this.getFormat(),
@ -40,7 +40,7 @@ export class CustomTextNode extends TextNode {
}
}
isSimpleText() {
override isSimpleText() {
return (
(this.__type === 'text' || this.__type === 'custom-text') && this.__mode === 0)
}

View File

@ -5,15 +5,15 @@ import ErrorMessageBlockComponent from './component'
type SerializedNode = SerializedLexicalNode
export class ErrorMessageBlockNode extends DecoratorNode<React.JSX.Element> {
static getType(): string {
static override getType(): string {
return 'error-message-block'
}
static clone(node: ErrorMessageBlockNode): ErrorMessageBlockNode {
static override clone(node: ErrorMessageBlockNode): ErrorMessageBlockNode {
return new ErrorMessageBlockNode(node.getKey())
}
isInline(): boolean {
override isInline(): boolean {
return true
}
@ -21,17 +21,17 @@ export class ErrorMessageBlockNode extends DecoratorNode<React.JSX.Element> {
super(key)
}
createDOM(): HTMLElement {
override createDOM(): HTMLElement {
const div = document.createElement('div')
div.classList.add('inline-flex', 'items-center', 'align-middle')
return div
}
updateDOM(): false {
override updateDOM(): false {
return false
}
decorate(): React.JSX.Element {
override decorate(): React.JSX.Element {
return (
<ErrorMessageBlockComponent
nodeKey={this.getKey()}
@ -39,20 +39,20 @@ export class ErrorMessageBlockNode extends DecoratorNode<React.JSX.Element> {
)
}
static importJSON(): ErrorMessageBlockNode {
static override importJSON(): ErrorMessageBlockNode {
const node = $createErrorMessageBlockNode()
return node
}
exportJSON(): SerializedNode {
override exportJSON(): SerializedNode {
return {
type: 'error-message-block',
version: 1,
}
}
getTextContent(): string {
override getTextContent(): string {
return '{{#error_message#}}'
}
}

View File

@ -9,11 +9,11 @@ export class HistoryBlockNode extends DecoratorNode<React.JSX.Element> {
__roleName: RoleName
__onEditRole: () => void
static getType(): string {
static override getType(): string {
return 'history-block'
}
static clone(node: HistoryBlockNode): HistoryBlockNode {
static override clone(node: HistoryBlockNode): HistoryBlockNode {
return new HistoryBlockNode(node.__roleName, node.__onEditRole, node.__key)
}
@ -24,21 +24,21 @@ export class HistoryBlockNode extends DecoratorNode<React.JSX.Element> {
this.__onEditRole = onEditRole
}
isInline(): boolean {
override isInline(): boolean {
return true
}
createDOM(): HTMLElement {
override createDOM(): HTMLElement {
const div = document.createElement('div')
div.classList.add('inline-flex', 'items-center', 'align-middle')
return div
}
updateDOM(): false {
override updateDOM(): false {
return false
}
decorate(): React.JSX.Element {
override decorate(): React.JSX.Element {
return (
<HistoryBlockComponent
nodeKey={this.getKey()}
@ -60,13 +60,13 @@ export class HistoryBlockNode extends DecoratorNode<React.JSX.Element> {
return self.__onEditRole
}
static importJSON(serializedNode: SerializedNode): HistoryBlockNode {
static override importJSON(serializedNode: SerializedNode): HistoryBlockNode {
const node = $createHistoryBlockNode(serializedNode.roleName, serializedNode.onEditRole)
return node
}
exportJSON(): SerializedNode {
override exportJSON(): SerializedNode {
return {
type: 'history-block',
version: 1,
@ -75,7 +75,7 @@ export class HistoryBlockNode extends DecoratorNode<React.JSX.Element> {
}
}
getTextContent(): string {
override getTextContent(): string {
return '{{#histories#}}'
}
}

View File

@ -37,7 +37,7 @@ export class HITLInputNode extends DecoratorNode<React.JSX.Element> {
__ragVariables?: Var[]
__readonly?: boolean
isIsolated(): boolean {
override isIsolated(): boolean {
return true // This is necessary for drag-and-drop to work correctly
}
@ -45,7 +45,7 @@ export class HITLInputNode extends DecoratorNode<React.JSX.Element> {
return true // This is necessary for drag-and-drop to work correctly
}
static getType(): string {
static override getType(): string {
return 'hitl-input-block'
}
@ -109,7 +109,7 @@ export class HITLInputNode extends DecoratorNode<React.JSX.Element> {
return self.__readonly || false
}
static clone(node: HITLInputNode): HITLInputNode {
static override clone(node: HITLInputNode): HITLInputNode {
return new HITLInputNode(
node.__variableName,
node.__nodeId,
@ -127,7 +127,7 @@ export class HITLInputNode extends DecoratorNode<React.JSX.Element> {
)
}
isInline(): boolean {
override isInline(): boolean {
return true
}
@ -162,17 +162,17 @@ export class HITLInputNode extends DecoratorNode<React.JSX.Element> {
this.__readonly = readonly
}
createDOM(): HTMLElement {
override createDOM(): HTMLElement {
const div = document.createElement('div')
div.classList.add('inline-flex', 'w-[calc(100%-1px)]', 'items-center', 'align-middle', 'support-drag')
return div
}
updateDOM(): false {
override updateDOM(): false {
return false
}
decorate(): React.JSX.Element {
override decorate(): React.JSX.Element {
return (
<HILTInputBlockComponent
nodeKey={this.getKey()}
@ -192,7 +192,7 @@ export class HITLInputNode extends DecoratorNode<React.JSX.Element> {
)
}
static importJSON(serializedNode: SerializedNode): HITLInputNode {
static override importJSON(serializedNode: SerializedNode): HITLInputNode {
const node = $createHITLInputNode(
serializedNode.variableName,
serializedNode.nodeId,
@ -211,7 +211,7 @@ export class HITLInputNode extends DecoratorNode<React.JSX.Element> {
return node
}
exportJSON(): SerializedNode {
override exportJSON(): SerializedNode {
return {
type: 'hitl-input-block',
version: 1,
@ -230,7 +230,7 @@ export class HITLInputNode extends DecoratorNode<React.JSX.Element> {
}
}
getTextContent(): string {
override getTextContent(): string {
return `{{#$output.${this.getVariableName()}#}}`
}
}

View File

@ -5,15 +5,15 @@ import LastRunBlockComponent from './component'
type SerializedNode = SerializedLexicalNode
export class LastRunBlockNode extends DecoratorNode<React.JSX.Element> {
static getType(): string {
static override getType(): string {
return 'last-run-block'
}
static clone(node: LastRunBlockNode): LastRunBlockNode {
static override clone(node: LastRunBlockNode): LastRunBlockNode {
return new LastRunBlockNode(node.getKey())
}
isInline(): boolean {
override isInline(): boolean {
return true
}
@ -21,17 +21,17 @@ export class LastRunBlockNode extends DecoratorNode<React.JSX.Element> {
super(key)
}
createDOM(): HTMLElement {
override createDOM(): HTMLElement {
const div = document.createElement('div')
div.classList.add('inline-flex', 'items-center', 'align-middle')
return div
}
updateDOM(): false {
override updateDOM(): false {
return false
}
decorate(): React.JSX.Element {
override decorate(): React.JSX.Element {
return (
<LastRunBlockComponent
nodeKey={this.getKey()}
@ -39,20 +39,20 @@ export class LastRunBlockNode extends DecoratorNode<React.JSX.Element> {
)
}
static importJSON(): LastRunBlockNode {
static override importJSON(): LastRunBlockNode {
const node = $createLastRunBlockNode()
return node
}
exportJSON(): SerializedNode {
override exportJSON(): SerializedNode {
return {
type: 'last-run-block',
version: 1,
}
}
getTextContent(): string {
override getTextContent(): string {
return '{{#last_run#}}'
}
}

View File

@ -5,46 +5,46 @@ import QueryBlockComponent from './component'
type SerializedNode = SerializedLexicalNode
export class QueryBlockNode extends DecoratorNode<React.JSX.Element> {
static getType(): string {
static override getType(): string {
return 'query-block'
}
static clone(): QueryBlockNode {
static override clone(): QueryBlockNode {
return new QueryBlockNode()
}
isInline(): boolean {
override isInline(): boolean {
return true
}
createDOM(): HTMLElement {
override createDOM(): HTMLElement {
const div = document.createElement('div')
div.classList.add('inline-flex', 'items-center', 'align-middle')
return div
}
updateDOM(): false {
override updateDOM(): false {
return false
}
decorate(): React.JSX.Element {
override decorate(): React.JSX.Element {
return <QueryBlockComponent nodeKey={this.getKey()} />
}
static importJSON(): QueryBlockNode {
static override importJSON(): QueryBlockNode {
const node = $createQueryBlockNode()
return node
}
exportJSON(): SerializedNode {
override exportJSON(): SerializedNode {
return {
type: 'query-block',
version: 1,
}
}
getTextContent(): string {
override getTextContent(): string {
return '{{#query#}}'
}
}

View File

@ -5,46 +5,46 @@ import RequestURLBlockComponent from './component'
type SerializedNode = SerializedLexicalNode
export class RequestURLBlockNode extends DecoratorNode<React.JSX.Element> {
static getType(): string {
static override getType(): string {
return 'request-url-block'
}
static clone(node: RequestURLBlockNode): RequestURLBlockNode {
static override clone(node: RequestURLBlockNode): RequestURLBlockNode {
return new RequestURLBlockNode(node.__key)
}
isInline(): boolean {
override isInline(): boolean {
return true
}
createDOM(): HTMLElement {
override createDOM(): HTMLElement {
const div = document.createElement('div')
div.classList.add('inline-flex', 'items-center', 'align-middle')
return div
}
updateDOM(): false {
override updateDOM(): false {
return false
}
decorate(): React.JSX.Element {
override decorate(): React.JSX.Element {
return <RequestURLBlockComponent nodeKey={this.getKey()} />
}
static importJSON(): RequestURLBlockNode {
static override importJSON(): RequestURLBlockNode {
const node = $createRequestURLBlockNode()
return node
}
exportJSON(): SerializedNode {
override exportJSON(): SerializedNode {
return {
type: 'request-url-block',
version: 1,
}
}
getTextContent(): string {
override getTextContent(): string {
return '{{#url#}}'
}
}

View File

@ -9,11 +9,11 @@ import {
} from 'lexical'
export class VariableValueBlockNode extends TextNode {
static getType(): string {
static override getType(): string {
return 'variable-value-block'
}
static clone(node: VariableValueBlockNode): VariableValueBlockNode {
static override clone(node: VariableValueBlockNode): VariableValueBlockNode {
return new VariableValueBlockNode(node.__text, node.__key)
}
@ -21,13 +21,13 @@ export class VariableValueBlockNode extends TextNode {
// super(text, key)
// }
createDOM(config: EditorConfig): HTMLElement {
override createDOM(config: EditorConfig): HTMLElement {
const element = super.createDOM(config)
element.classList.add('inline-flex', 'items-center', 'px-0.5', 'h-[22px]', 'text-text-accent', 'rounded-[5px]', 'align-middle')
return element
}
static importJSON(serializedNode: SerializedTextNode): TextNode {
static override importJSON(serializedNode: SerializedTextNode): TextNode {
const node = $createVariableValueBlockNode(serializedNode.text)
node.setFormat(serializedNode.format)
node.setDetail(serializedNode.detail)
@ -36,7 +36,7 @@ export class VariableValueBlockNode extends TextNode {
return node
}
exportJSON(): SerializedTextNode {
override exportJSON(): SerializedTextNode {
return {
detail: this.getDetail(),
format: this.getFormat(),
@ -48,7 +48,7 @@ export class VariableValueBlockNode extends TextNode {
}
}
canInsertTextBefore(): boolean {
override canInsertTextBefore(): boolean {
return false
}
}

View File

@ -19,11 +19,11 @@ export class WorkflowVariableBlockNode extends DecoratorNode<React.JSX.Element>
__getVarType?: GetVarType
__availableVariables?: NodeOutPutVar[]
static getType(): string {
static override getType(): string {
return 'workflow-variable-block'
}
static clone(node: WorkflowVariableBlockNode): WorkflowVariableBlockNode {
static override clone(node: WorkflowVariableBlockNode): WorkflowVariableBlockNode {
return new WorkflowVariableBlockNode(
node.__variables,
node.__workflowNodesMap,
@ -33,7 +33,7 @@ export class WorkflowVariableBlockNode extends DecoratorNode<React.JSX.Element>
)
}
isInline(): boolean {
override isInline(): boolean {
return true
}
@ -52,17 +52,17 @@ export class WorkflowVariableBlockNode extends DecoratorNode<React.JSX.Element>
this.__availableVariables = availableVariables
}
createDOM(): HTMLElement {
override createDOM(): HTMLElement {
const div = document.createElement('div')
div.classList.add('inline-flex', 'items-center', 'align-middle')
return div
}
updateDOM(): false {
override updateDOM(): false {
return false
}
decorate(): React.JSX.Element {
override decorate(): React.JSX.Element {
return (
<WorkflowVariableBlockComponent
nodeKey={this.getKey()}
@ -74,7 +74,7 @@ export class WorkflowVariableBlockNode extends DecoratorNode<React.JSX.Element>
)
}
static importJSON(serializedNode: SerializedNode): WorkflowVariableBlockNode {
static override importJSON(serializedNode: SerializedNode): WorkflowVariableBlockNode {
const node = $createWorkflowVariableBlockNode(
serializedNode.variables,
serializedNode.workflowNodesMap,
@ -85,7 +85,7 @@ export class WorkflowVariableBlockNode extends DecoratorNode<React.JSX.Element>
return node
}
exportJSON(): SerializedNode {
override exportJSON(): SerializedNode {
const json: SerializedNode = {
type: 'workflow-variable-block',
version: 1,
@ -119,7 +119,7 @@ export class WorkflowVariableBlockNode extends DecoratorNode<React.JSX.Element>
return self.__availableVariables
}
getTextContent(): string {
override getTextContent(): string {
return `{{#${this.getVariables().join('.')}#}}`
}
}

View File

@ -230,7 +230,7 @@ describe('UpdateDSLModal', () => {
it('should show an error when the selected file content is invalid for the current app mode', async () => {
class InvalidDSLFileReader extends MockFileReader {
readAsText(_file: Blob) {
override readAsText(_file: Blob) {
const event = { target: { result: 'workflow:\n graph:\n nodes:\n - data:\n type: answer\n' } } as unknown as ProgressEvent<FileReader>
this.onload?.call(this as unknown as FileReader, event)
}

View File

@ -55,7 +55,7 @@ const OutputVarList: FC<Props> = ({
replaceSpaceWithUnderscoreInVarNameInput(e.target)
const newKey = e.target.value
validateVarInput(list.toSpliced(index, 1), newKey)
validateVarInput(list.filter((_, itemIndex) => itemIndex !== index), newKey)
const newOutputs = produce(outputs, (draft) => {
draft[newKey] = draft[oldKey]!

View File

@ -67,7 +67,7 @@ const VarList: FC<Props> = ({
const newKey = e.target.value
validateVarInput(list.toSpliced(index, 1), newKey)
validateVarInput(list.filter((_, itemIndex) => itemIndex !== index), newKey)
onVarNameChange?.(list[index]!.variable, newKey)
const newList = produce(list, (draft) => {

View File

@ -1,6 +1,15 @@
import type { NodeTracing } from '@/types/workflow'
import { BlockEnum } from '@/app/components/workflow/types'
function findLastIndex<T>(list: T[], predicate: (item: T) => boolean): number {
for (let index = list.length - 1; index >= 0; index--) {
if (predicate(list[index]!))
return index
}
return -1
}
function printNodeStructure(node: NodeTracing, depth: number) {
const indent = ' '.repeat(depth)
console.log(`${indent}${node.title}`)
@ -107,7 +116,7 @@ const format = (list: NodeTracing[], t: any, isPrint?: boolean): NodeTracing[] =
}
}
if (parentParallelStartNode!.parallelDetail.children) {
const sameBranchNodesLastIndex = parentParallelStartNode.parallelDetail.children.findLastIndex((node) => {
const sameBranchNodesLastIndex = findLastIndex(parentParallelStartNode.parallelDetail.children, (node) => {
const currStartNodeId = node.parallel_start_node_id ?? node.execution_metadata?.parallel_start_node_id ?? null
return currStartNodeId === parentParallelBranchStartNodeId
})
@ -124,7 +133,7 @@ const format = (list: NodeTracing[], t: any, isPrint?: boolean): NodeTracing[] =
const parallelStartNode = result.find(item => parallel_start_node_id === item.node_id)
if (parallelStartNode && parallelStartNode.parallelDetail && parallelStartNode!.parallelDetail!.children) {
const sameBranchNodesLastIndex = parallelStartNode.parallelDetail.children.findLastIndex((node) => {
const sameBranchNodesLastIndex = findLastIndex(parallelStartNode.parallelDetail.children, (node) => {
const currStartNodeId = node.parallel_start_node_id ?? node.execution_metadata?.parallel_start_node_id ?? null
return currStartNodeId === branchStartNodeId
})

View File

@ -10,7 +10,7 @@ export const i18n = {
locales: LanguagesSupported,
} as const
export { Locale }
export type { Locale }
export const setLocaleOnClient = async (locale: Locale, reloadPage = true) => {
Cookies.set(LOCALE_COOKIE_NAME, locale, { expires: 365 })

View File

@ -2,20 +2,6 @@ import { IS_DEV } from '@/config'
import { env } from '@/env'
async function main() {
// Polyfill for Array.prototype.toSpliced (ES2023, Chrome 110+)
if (!Array.prototype.toSpliced) {
// eslint-disable-next-line no-extend-native
Array.prototype.toSpliced = function <T>(this: T[], start: number, deleteCount?: number, ...items: T[]): T[] {
const copy = this.slice()
// When deleteCount is undefined (omitted), delete to end; otherwise let splice handle coercion
if (deleteCount === undefined)
copy.splice(start, copy.length - start, ...items)
else
copy.splice(start, deleteCount, ...items)
return copy
}
}
if (!('localStorage' in globalThis) || !('sessionStorage' in globalThis)) {
class StorageMock {
data: Record<string, string>

View File

@ -48,8 +48,8 @@
"test": "vp test",
"test:coverage": "vp test --coverage",
"test:watch": "vp test --watch",
"type-check": "tsc --noEmit",
"type-check:tsgo": "tsgo --noEmit",
"type-check": "tsc",
"type-check:tsgo": "tsgo",
"uglify-embed": "node ./bin/uglify-embed"
},
"dependencies": {
@ -160,6 +160,7 @@
"@antfu/eslint-config": "catalog:",
"@chromatic-com/storybook": "catalog:",
"@dify/iconify-collections": "workspace:*",
"@dify/tsconfig": "workspace:*",
"@egoist/tailwindcss-icons": "catalog:",
"@eslint-react/eslint-plugin": "catalog:",
"@hono/node-server": "catalog:",

View File

@ -1,15 +1,7 @@
{
"extends": "@dify/tsconfig/nextjs.json",
"compilerOptions": {
"incremental": true,
"target": "es2022",
"jsx": "react-jsx",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"module": "esnext",
"moduleResolution": "bundler",
"paths": {
"@/*": [
"./*"
@ -23,19 +15,7 @@
"vitest/globals",
"node"
],
"allowJs": true,
"strict": true,
"noUncheckedIndexedAccess": true,
"noEmit": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"skipLibCheck": true,
"plugins": [
{
"name": "next"
}
]
"allowJs": true
},
"include": [
"next-env.d.ts",