refactor: introduce pnpm workspace (#34241)

Co-authored-by: yyh <yuanyouhuilyz@gmail.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Stephen Zhou
2026-03-30 18:34:50 +08:00
committed by GitHub
parent 1aaba80211
commit 52a4bea88f
42 changed files with 4060 additions and 6942 deletions

View File

@@ -6,7 +6,6 @@ runs:
- name: Setup Vite+ - name: Setup Vite+
uses: voidzero-dev/setup-vp@20553a7a7429c429a74894104a2835d7fed28a72 # v1.3.0 uses: voidzero-dev/setup-vp@20553a7a7429c429a74894104a2835d7fed28a72 # v1.3.0
with: with:
working-directory: web
node-version-file: .nvmrc node-version-file: .nvmrc
cache: true cache: true
run-install: true run-install: true

View File

@@ -39,6 +39,10 @@ jobs:
with: with:
files: | files: |
web/** web/**
package.json
pnpm-lock.yaml
pnpm-workspace.yaml
.nvmrc
- name: Check api inputs - name: Check api inputs
if: github.event_name != 'merge_group' if: github.event_name != 'merge_group'
id: api-changes id: api-changes

View File

@@ -24,27 +24,39 @@ env:
jobs: jobs:
build: build:
runs-on: ${{ matrix.platform == 'linux/arm64' && 'arm64_runner' || 'ubuntu-latest' }} runs-on: ${{ matrix.runs_on }}
if: github.repository == 'langgenius/dify' if: github.repository == 'langgenius/dify'
strategy: strategy:
matrix: matrix:
include: include:
- service_name: "build-api-amd64" - service_name: "build-api-amd64"
image_name_env: "DIFY_API_IMAGE_NAME" image_name_env: "DIFY_API_IMAGE_NAME"
context: "api" artifact_context: "api"
build_context: "{{defaultContext}}:api"
file: "Dockerfile"
platform: linux/amd64 platform: linux/amd64
runs_on: ubuntu-latest
- service_name: "build-api-arm64" - service_name: "build-api-arm64"
image_name_env: "DIFY_API_IMAGE_NAME" image_name_env: "DIFY_API_IMAGE_NAME"
context: "api" artifact_context: "api"
build_context: "{{defaultContext}}:api"
file: "Dockerfile"
platform: linux/arm64 platform: linux/arm64
runs_on: ubuntu-24.04-arm
- service_name: "build-web-amd64" - service_name: "build-web-amd64"
image_name_env: "DIFY_WEB_IMAGE_NAME" image_name_env: "DIFY_WEB_IMAGE_NAME"
context: "web" artifact_context: "web"
build_context: "{{defaultContext}}"
file: "web/Dockerfile"
platform: linux/amd64 platform: linux/amd64
runs_on: ubuntu-latest
- service_name: "build-web-arm64" - service_name: "build-web-arm64"
image_name_env: "DIFY_WEB_IMAGE_NAME" image_name_env: "DIFY_WEB_IMAGE_NAME"
context: "web" artifact_context: "web"
build_context: "{{defaultContext}}"
file: "web/Dockerfile"
platform: linux/arm64 platform: linux/arm64
runs_on: ubuntu-24.04-arm
steps: steps:
- name: Prepare - name: Prepare
@@ -58,9 +70,6 @@ jobs:
username: ${{ env.DOCKERHUB_USER }} username: ${{ env.DOCKERHUB_USER }}
password: ${{ env.DOCKERHUB_TOKEN }} password: ${{ env.DOCKERHUB_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
@@ -74,7 +83,8 @@ jobs:
id: build id: build
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0 uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
with: with:
context: "{{defaultContext}}:${{ matrix.context }}" context: ${{ matrix.build_context }}
file: ${{ matrix.file }}
platforms: ${{ matrix.platform }} platforms: ${{ matrix.platform }}
build-args: COMMIT_SHA=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }} build-args: COMMIT_SHA=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
@@ -93,7 +103,7 @@ jobs:
- name: Upload digest - name: Upload digest
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with: with:
name: digests-${{ matrix.context }}-${{ env.PLATFORM_PAIR }} name: digests-${{ matrix.artifact_context }}-${{ env.PLATFORM_PAIR }}
path: /tmp/digests/* path: /tmp/digests/*
if-no-files-found: error if-no-files-found: error
retention-days: 1 retention-days: 1

View File

@@ -6,7 +6,12 @@ on:
- "main" - "main"
paths: paths:
- api/Dockerfile - api/Dockerfile
- web/docker/**
- web/Dockerfile - web/Dockerfile
- package.json
- pnpm-lock.yaml
- pnpm-workspace.yaml
- .nvmrc
concurrency: concurrency:
group: docker-build-${{ github.head_ref || github.run_id }} group: docker-build-${{ github.head_ref || github.run_id }}
@@ -14,26 +19,31 @@ concurrency:
jobs: jobs:
build-docker: build-docker:
runs-on: ubuntu-latest runs-on: ${{ matrix.runs_on }}
strategy: strategy:
matrix: matrix:
include: include:
- service_name: "api-amd64" - service_name: "api-amd64"
platform: linux/amd64 platform: linux/amd64
context: "api" runs_on: ubuntu-latest
context: "{{defaultContext}}:api"
file: "Dockerfile"
- service_name: "api-arm64" - service_name: "api-arm64"
platform: linux/arm64 platform: linux/arm64
context: "api" runs_on: ubuntu-24.04-arm
context: "{{defaultContext}}:api"
file: "Dockerfile"
- service_name: "web-amd64" - service_name: "web-amd64"
platform: linux/amd64 platform: linux/amd64
context: "web" runs_on: ubuntu-latest
context: "{{defaultContext}}"
file: "web/Dockerfile"
- service_name: "web-arm64" - service_name: "web-arm64"
platform: linux/arm64 platform: linux/arm64
context: "web" runs_on: ubuntu-24.04-arm
context: "{{defaultContext}}"
file: "web/Dockerfile"
steps: steps:
- name: Set up QEMU
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
@@ -41,8 +51,8 @@ jobs:
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0 uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
with: with:
push: false push: false
context: "{{defaultContext}}:${{ matrix.context }}" context: ${{ matrix.context }}
file: "${{ matrix.file }}" file: ${{ matrix.file }}
platforms: ${{ matrix.platform }} platforms: ${{ matrix.platform }}
cache-from: type=gha cache-from: type=gha
cache-to: type=gha,mode=max cache-to: type=gha,mode=max

View File

@@ -65,6 +65,10 @@ jobs:
- 'docker/volumes/sandbox/conf/**' - 'docker/volumes/sandbox/conf/**'
web: web:
- 'web/**' - 'web/**'
- 'package.json'
- 'pnpm-lock.yaml'
- 'pnpm-workspace.yaml'
- '.nvmrc'
- '.github/workflows/web-tests.yml' - '.github/workflows/web-tests.yml'
- '.github/actions/setup-web/**' - '.github/actions/setup-web/**'
e2e: e2e:
@@ -73,6 +77,10 @@ jobs:
- 'api/uv.lock' - 'api/uv.lock'
- 'e2e/**' - 'e2e/**'
- 'web/**' - 'web/**'
- 'package.json'
- 'pnpm-lock.yaml'
- 'pnpm-workspace.yaml'
- '.nvmrc'
- 'docker/docker-compose.middleware.yaml' - 'docker/docker-compose.middleware.yaml'
- 'docker/middleware.env.example' - 'docker/middleware.env.example'
- '.github/workflows/web-e2e.yml' - '.github/workflows/web-e2e.yml'

View File

@@ -77,6 +77,10 @@ jobs:
with: with:
files: | files: |
web/** web/**
package.json
pnpm-lock.yaml
pnpm-workspace.yaml
.nvmrc
.github/workflows/style.yml .github/workflows/style.yml
.github/actions/setup-web/** .github/actions/setup-web/**
@@ -90,9 +94,9 @@ jobs:
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with: with:
path: web/.eslintcache path: web/.eslintcache
key: ${{ runner.os }}-web-eslint-${{ hashFiles('web/package.json', 'web/pnpm-lock.yaml', 'web/eslint.config.mjs', 'web/eslint.constants.mjs', 'web/plugins/eslint/**') }}-${{ github.sha }} key: ${{ runner.os }}-web-eslint-${{ hashFiles('web/package.json', 'pnpm-lock.yaml', 'web/eslint.config.mjs', 'web/eslint.constants.mjs', 'web/plugins/eslint/**') }}-${{ github.sha }}
restore-keys: | restore-keys: |
${{ runner.os }}-web-eslint-${{ hashFiles('web/package.json', 'web/pnpm-lock.yaml', 'web/eslint.config.mjs', 'web/eslint.constants.mjs', 'web/plugins/eslint/**') }}- ${{ runner.os }}-web-eslint-${{ hashFiles('web/package.json', 'pnpm-lock.yaml', 'web/eslint.config.mjs', 'web/eslint.constants.mjs', 'web/plugins/eslint/**') }}-
- name: Web style check - name: Web style check
if: steps.changed-files.outputs.any_changed == 'true' if: steps.changed-files.outputs.any_changed == 'true'

View File

@@ -6,6 +6,9 @@ on:
- main - main
paths: paths:
- sdks/** - sdks/**
- package.json
- pnpm-lock.yaml
- pnpm-workspace.yaml
concurrency: concurrency:
group: sdk-tests-${{ github.head_ref || github.run_id }} group: sdk-tests-${{ github.head_ref || github.run_id }}

View File

@@ -27,10 +27,6 @@ jobs:
- name: Setup web dependencies - name: Setup web dependencies
uses: ./.github/actions/setup-web uses: ./.github/actions/setup-web
- name: Install E2E package dependencies
working-directory: ./e2e
run: vp install --frozen-lockfile
- name: Setup UV and Python - name: Setup UV and Python
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with: with:

View File

@@ -89,34 +89,3 @@ jobs:
flags: web flags: web
env: env:
CODECOV_TOKEN: ${{ env.CODECOV_TOKEN }} CODECOV_TOKEN: ${{ env.CODECOV_TOKEN }}
web-build:
name: Web Build
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./web
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Check changed files
id: changed-files
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
with:
files: |
web/**
.github/workflows/web-tests.yml
.github/actions/setup-web/**
- name: Setup web environment
if: steps.changed-files.outputs.any_changed == 'true'
uses: ./.github/actions/setup-web
- name: Web build check
if: steps.changed-files.outputs.any_changed == 'true'
working-directory: ./web
run: vp run build

3
.gitignore vendored
View File

@@ -212,6 +212,7 @@ api/.vscode
# pnpm # pnpm
/.pnpm-store /.pnpm-store
/node_modules
# plugin migrate # plugin migrate
plugins.jsonl plugins.jsonl
@@ -239,4 +240,4 @@ scripts/stress-test/reports/
*.local.md *.local.md
# Code Agent Folder # Code Agent Folder
.qoder/* .qoder/*

View File

View File

@@ -24,8 +24,8 @@ prepare-docker:
# Step 2: Prepare web environment # Step 2: Prepare web environment
prepare-web: prepare-web:
@echo "🌐 Setting up web environment..." @echo "🌐 Setting up web environment..."
@cp -n web/.env.example web/.env 2>/dev/null || echo "Web .env already exists" @cp -n web/.env.example web/.env.local 2>/dev/null || echo "Web .env.local already exists"
@cd web && pnpm install @pnpm install
@echo "✅ Web environment prepared (not started)" @echo "✅ Web environment prepared (not started)"
# Step 3: Prepare API environment # Step 3: Prepare API environment
@@ -93,7 +93,7 @@ test:
# Build Docker images # Build Docker images
build-web: build-web:
@echo "Building web Docker image: $(WEB_IMAGE):$(VERSION)..." @echo "Building web Docker image: $(WEB_IMAGE):$(VERSION)..."
docker build -t $(WEB_IMAGE):$(VERSION) ./web docker build -f web/Dockerfile -t $(WEB_IMAGE):$(VERSION) .
@echo "Web Docker image built successfully: $(WEB_IMAGE):$(VERSION)" @echo "Web Docker image built successfully: $(WEB_IMAGE):$(VERSION)"
build-api: build-api:

View File

@@ -40,6 +40,8 @@ The scripts resolve paths relative to their location, so you can run them from a
./dev/start-web ./dev/start-web
``` ```
`./dev/setup` and `./dev/start-web` install JavaScript dependencies through the repository root workspace, so you do not need a separate `cd web && pnpm install` step.
1. Set up your application by visiting `http://localhost:3000`. 1. Set up your application by visiting `http://localhost:3000`.
1. Start the worker service (async and scheduler tasks, runs from `api`). 1. Start the worker service (async and scheduler tasks, runs from `api`).

View File

@@ -24,5 +24,4 @@ cp "$MIDDLEWARE_ENV_EXAMPLE" "$MIDDLEWARE_ENV"
cd "$ROOT/api" cd "$ROOT/api"
uv sync --group dev uv sync --group dev
cd "$ROOT/web" pnpm --dir "$ROOT" install
pnpm install

View File

@@ -3,6 +3,6 @@
set -x set -x
SCRIPT_DIR="$(dirname "$(realpath "$0")")" SCRIPT_DIR="$(dirname "$(realpath "$0")")"
cd "$SCRIPT_DIR/../web" ROOT_DIR="$(dirname "$SCRIPT_DIR")"
pnpm install && pnpm dev:inspect pnpm --dir "$ROOT_DIR" install && pnpm --dir "$ROOT_DIR/web" dev:inspect

View File

@@ -19,15 +19,18 @@ It tests:
- `uv` - `uv`
- Docker - Docker
Run the following commands from the repository root.
Install Playwright browsers once: Install Playwright browsers once:
```bash ```bash
cd e2e
pnpm install pnpm install
pnpm e2e:install pnpm -C e2e e2e:install
pnpm check pnpm -C e2e check
``` ```
`pnpm install` is resolved through the repository workspace and uses the shared root lockfile plus `pnpm-workspace.yaml`.
Use `pnpm check` as the default local verification step after editing E2E TypeScript, Cucumber support code, or feature glue. It runs formatting, linting, and type checks for this package. Use `pnpm check` as the default local verification step after editing E2E TypeScript, Cucumber support code, or feature glue. It runs formatting, linting, and type checks for this package.
Common commands: Common commands:
@@ -35,20 +38,20 @@ Common commands:
```bash ```bash
# authenticated-only regression (default excludes @fresh) # authenticated-only regression (default excludes @fresh)
# expects backend API, frontend artifact, and middleware stack to already be running # expects backend API, frontend artifact, and middleware stack to already be running
pnpm e2e pnpm -C e2e e2e
# full reset + fresh install + authenticated scenarios # full reset + fresh install + authenticated scenarios
# starts required middleware/dependencies for you # starts required middleware/dependencies for you
pnpm e2e:full pnpm -C e2e e2e:full
# run a tagged subset # run a tagged subset
pnpm e2e -- --tags @smoke pnpm -C e2e e2e -- --tags @smoke
# headed browser # headed browser
pnpm e2e:headed -- --tags @smoke pnpm -C e2e e2e:headed -- --tags @smoke
# slow down browser actions for local debugging # slow down browser actions for local debugging
E2E_SLOW_MO=500 pnpm e2e:headed -- --tags @smoke E2E_SLOW_MO=500 pnpm -C e2e e2e:headed -- --tags @smoke
``` ```
Frontend artifact behavior: Frontend artifact behavior:
@@ -101,7 +104,7 @@ Because of that, the `@fresh` install scenario only runs in the `pnpm e2e:full*`
Reset all persisted E2E state: Reset all persisted E2E state:
```bash ```bash
pnpm e2e:reset pnpm -C e2e e2e:reset
``` ```
This removes: This removes:
@@ -117,7 +120,7 @@ This removes:
Start the full middleware stack: Start the full middleware stack:
```bash ```bash
pnpm e2e:middleware:up pnpm -C e2e e2e:middleware:up
``` ```
Stop the full middleware stack: Stop the full middleware stack:

View File

@@ -14,21 +14,11 @@
"e2e:reset": "tsx ./scripts/setup.ts reset" "e2e:reset": "tsx ./scripts/setup.ts reset"
}, },
"devDependencies": { "devDependencies": {
"@cucumber/cucumber": "12.7.0", "@cucumber/cucumber": "catalog:",
"@playwright/test": "1.51.1", "@playwright/test": "catalog:",
"@types/node": "25.5.0", "@types/node": "catalog:",
"tsx": "4.21.0", "tsx": "catalog:",
"typescript": "5.9.3", "typescript": "catalog:",
"vite-plus": "latest" "vite-plus": "catalog:"
},
"engines": {
"node": "^22.22.1"
},
"packageManager": "pnpm@10.32.1",
"pnpm": {
"overrides": {
"vite": "npm:@voidzero-dev/vite-plus-core@latest",
"vitest": "npm:@voidzero-dev/vite-plus-test@latest"
}
} }
} }

2632
e2e/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

11
package.json Normal file
View File

@@ -0,0 +1,11 @@
{
"name": "dify",
"private": true,
"engines": {
"node": "^22.22.1"
},
"packageManager": "pnpm@10.33.0",
"devDependencies": {
"taze": "catalog:"
}
}

File diff suppressed because it is too large Load Diff

257
pnpm-workspace.yaml Normal file
View File

@@ -0,0 +1,257 @@
packages:
- web
- e2e
- sdks/nodejs-client
overrides:
"@lexical/code": npm:lexical-code-no-prism@0.41.0
"@monaco-editor/loader": 1.7.0
"@nolyfill/safe-buffer": npm:safe-buffer@^5.2.1
array-includes: npm:@nolyfill/array-includes@^1.0.44
array.prototype.findlast: npm:@nolyfill/array.prototype.findlast@^1.0.44
array.prototype.findlastindex: npm:@nolyfill/array.prototype.findlastindex@^1.0.44
array.prototype.flat: npm:@nolyfill/array.prototype.flat@^1.0.44
array.prototype.flatmap: npm:@nolyfill/array.prototype.flatmap@^1.0.44
array.prototype.tosorted: npm:@nolyfill/array.prototype.tosorted@^1.0.44
assert: npm:@nolyfill/assert@^1.0.26
brace-expansion@<2.0.2: 2.0.2
canvas: ^3.2.2
devalue@<5.3.2: 5.3.2
dompurify@>=3.1.3 <=3.3.1: 3.3.2
es-iterator-helpers: npm:@nolyfill/es-iterator-helpers@^1.0.21
esbuild@<0.27.2: 0.27.2
flatted@<=3.4.1: 3.4.2
glob@>=10.2.0 <10.5.0: 11.1.0
hasown: npm:@nolyfill/hasown@^1.0.44
is-arguments: npm:@nolyfill/is-arguments@^1.0.44
is-core-module: npm:@nolyfill/is-core-module@^1.0.39
is-generator-function: npm:@nolyfill/is-generator-function@^1.0.44
is-typed-array: npm:@nolyfill/is-typed-array@^1.0.44
isarray: npm:@nolyfill/isarray@^1.0.44
object.assign: npm:@nolyfill/object.assign@^1.0.44
object.entries: npm:@nolyfill/object.entries@^1.0.44
object.fromentries: npm:@nolyfill/object.fromentries@^1.0.44
object.groupby: npm:@nolyfill/object.groupby@^1.0.44
object.values: npm:@nolyfill/object.values@^1.0.44
pbkdf2: ~3.1.5
pbkdf2@<3.1.3: 3.1.3
picomatch@<2.3.2: 2.3.2
picomatch@>=4.0.0 <4.0.4: 4.0.4
prismjs: ~1.30
prismjs@<1.30.0: 1.30.0
rollup@>=4.0.0 <4.59.0: 4.59.0
safe-buffer: ^5.2.1
safe-regex-test: npm:@nolyfill/safe-regex-test@^1.0.44
safer-buffer: npm:@nolyfill/safer-buffer@^1.0.44
side-channel: npm:@nolyfill/side-channel@^1.0.44
smol-toml@<1.6.1: 1.6.1
solid-js: 1.9.11
string-width: ~8.2.0
string.prototype.includes: npm:@nolyfill/string.prototype.includes@^1.0.44
string.prototype.matchall: npm:@nolyfill/string.prototype.matchall@^1.0.44
string.prototype.repeat: npm:@nolyfill/string.prototype.repeat@^1.0.44
string.prototype.trimend: npm:@nolyfill/string.prototype.trimend@^1.0.44
svgo@>=3.0.0 <3.3.3: 3.3.3
tar@<=7.5.10: 7.5.11
typed-array-buffer: npm:@nolyfill/typed-array-buffer@^1.0.44
undici@>=7.0.0 <7.24.0: 7.24.0
vite: npm:@voidzero-dev/vite-plus-core@0.1.14
vitest: npm:@voidzero-dev/vite-plus-test@0.1.14
which-typed-array: npm:@nolyfill/which-typed-array@^1.0.44
yaml@>=2.0.0 <2.8.3: 2.8.3
yauzl@<3.2.1: 3.2.1
ignoredBuiltDependencies:
- canvas
- core-js-pure
onlyBuiltDependencies:
- "@parcel/watcher"
- esbuild
- sharp
catalog:
"@amplitude/analytics-browser": 2.38.0
"@amplitude/plugin-session-replay-browser": 1.27.5
"@antfu/eslint-config": 7.7.3
"@base-ui/react": 1.3.0
"@chromatic-com/storybook": 5.1.1
"@cucumber/cucumber": 12.7.0
"@egoist/tailwindcss-icons": 1.9.2
"@emoji-mart/data": 1.2.1
"@eslint-react/eslint-plugin": 3.0.0
"@eslint/js": ^10.0.1
"@floating-ui/react": 0.27.19
"@formatjs/intl-localematcher": 0.8.2
"@headlessui/react": 2.2.9
"@heroicons/react": 2.2.0
"@hono/node-server": 1.19.11
"@iconify-json/heroicons": 1.2.3
"@iconify-json/ri": 1.2.10
"@lexical/code": 0.42.0
"@lexical/link": 0.42.0
"@lexical/list": 0.42.0
"@lexical/react": 0.42.0
"@lexical/selection": 0.42.0
"@lexical/text": 0.42.0
"@lexical/utils": 0.42.0
"@mdx-js/loader": 3.1.1
"@mdx-js/react": 3.1.1
"@mdx-js/rollup": 3.1.1
"@monaco-editor/react": 4.7.0
"@next/eslint-plugin-next": 16.2.1
"@next/mdx": 16.2.1
"@orpc/client": 1.13.13
"@orpc/contract": 1.13.13
"@orpc/openapi-client": 1.13.13
"@orpc/tanstack-query": 1.13.13
"@playwright/test": 1.58.2
"@remixicon/react": 4.9.0
"@rgrove/parse-xml": 4.2.0
"@sentry/react": 10.46.0
"@storybook/addon-docs": 10.3.3
"@storybook/addon-links": 10.3.3
"@storybook/addon-onboarding": 10.3.3
"@storybook/addon-themes": 10.3.3
"@storybook/nextjs-vite": 10.3.3
"@storybook/react": 10.3.3
"@streamdown/math": 1.0.2
"@svgdotjs/svg.js": 3.2.5
"@t3-oss/env-nextjs": 0.13.11
"@tailwindcss/typography": 0.5.19
"@tanstack/eslint-plugin-query": 5.95.2
"@tanstack/react-devtools": 0.10.0
"@tanstack/react-form": 1.28.5
"@tanstack/react-form-devtools": 0.2.19
"@tanstack/react-query": 5.95.2
"@tanstack/react-query-devtools": 5.95.2
"@testing-library/dom": 10.4.1
"@testing-library/jest-dom": 6.9.1
"@testing-library/react": 16.3.2
"@testing-library/user-event": 14.6.1
"@tsslint/cli": 3.0.2
"@tsslint/compat-eslint": 3.0.2
"@tsslint/config": 3.0.2
"@types/js-cookie": 3.0.6
"@types/js-yaml": 4.0.9
"@types/negotiator": 0.6.4
"@types/node": 25.5.0
"@types/postcss-js": 4.1.0
"@types/qs": 6.15.0
"@types/react": 19.2.14
"@types/react-dom": 19.2.3
"@types/react-syntax-highlighter": 15.5.13
"@types/react-window": 1.8.8
"@types/sortablejs": 1.15.9
"@typescript-eslint/eslint-plugin": ^8.57.2
"@typescript-eslint/parser": 8.57.2
"@typescript/native-preview": 7.0.0-dev.20260329.1
"@vitejs/plugin-react": 6.0.1
"@vitejs/plugin-rsc": 0.5.21
"@vitest/coverage-v8": 4.1.2
abcjs: 6.6.2
agentation: 3.0.2
ahooks: 3.9.7
autoprefixer: 10.4.27
axios: ^1.14.0
class-variance-authority: 0.7.1
clsx: 2.1.1
cmdk: 1.1.1
code-inspector-plugin: 1.4.5
copy-to-clipboard: 3.3.3
cron-parser: 5.5.0
dayjs: 1.11.20
decimal.js: 10.6.0
dompurify: 3.3.3
echarts: 6.0.0
echarts-for-react: 3.0.6
elkjs: 0.11.1
embla-carousel-autoplay: 8.6.0
embla-carousel-react: 8.6.0
emoji-mart: 5.6.0
es-toolkit: 1.45.1
eslint: 10.1.0
eslint-markdown: 0.6.0
eslint-plugin-better-tailwindcss: 4.3.2
eslint-plugin-hyoban: 0.14.1
eslint-plugin-markdown-preferences: 0.40.3
eslint-plugin-no-barrel-files: 1.2.2
eslint-plugin-react-hooks: 7.0.1
eslint-plugin-react-refresh: 0.5.2
eslint-plugin-sonarjs: 4.0.2
eslint-plugin-storybook: 10.3.3
fast-deep-equal: 3.1.3
foxact: 0.3.0
happy-dom: 20.8.9
hono: 4.12.9
html-entities: 2.6.0
html-to-image: 1.11.13
husky: 9.1.7
i18next: 25.10.10
i18next-resources-to-backend: 1.2.1
iconify-import-svg: 0.1.2
immer: 11.1.4
jotai: 2.19.0
js-audio-recorder: 1.0.7
js-cookie: 3.0.5
js-yaml: 4.1.1
jsonschema: 1.5.0
katex: 0.16.44
knip: 6.1.0
ky: 1.14.3
lamejs: 1.2.1
lexical: 0.42.0
lint-staged: 16.4.0
mermaid: 11.13.0
mime: 4.1.0
mitt: 3.0.1
negotiator: 1.0.0
next: 16.2.1
next-themes: 0.4.6
nuqs: 2.8.9
pinyin-pro: 3.28.0
postcss: 8.5.8
postcss-js: 5.1.0
qrcode.react: 4.2.0
qs: 6.15.0
react: 19.2.4
react-18-input-autosize: 3.0.0
react-dom: 19.2.4
react-easy-crop: 5.5.7
react-hotkeys-hook: 5.2.4
react-i18next: 16.6.6
react-multi-email: 1.0.25
react-papaparse: 4.4.0
react-pdf-highlighter: 8.0.0-rc.0
react-server-dom-webpack: 19.2.4
react-sortablejs: 6.1.4
react-syntax-highlighter: 15.6.6
react-textarea-autosize: 8.5.9
react-window: 1.8.11
reactflow: 11.11.4
remark-breaks: 4.0.0
remark-directive: 4.0.0
sass: 1.98.0
scheduler: 0.27.0
sharp: 0.34.5
sortablejs: 1.15.7
std-semver: 1.0.8
storybook: 10.3.3
streamdown: 2.5.0
string-ts: 2.3.1
tailwind-merge: 2.6.1
tailwindcss: 3.4.19
taze: 19.10.0
tldts: 7.0.27
tsup: ^8.5.1
tsx: 4.21.0
typescript: 5.9.3
uglify-js: 3.19.3
unist-util-visit: 5.1.0
use-context-selector: 2.0.0
uuid: 13.0.0
vinext: 0.0.38
vite: npm:@voidzero-dev/vite-plus-core@0.1.14
vite-plugin-inspect: 12.0.0-beta.1
vite-plus: 0.1.14
vitest: npm:@voidzero-dev/vite-plus-test@0.1.14
vitest-canvas-mock: 1.1.4
zod: 4.3.6
zundo: 2.3.0
zustand: 5.0.12

View File

@@ -100,6 +100,10 @@ Notes:
- Chat/completion require a stable `user` identifier in the request payload. - Chat/completion require a stable `user` identifier in the request payload.
- For streaming responses, iterate the returned AsyncIterable. Use `stream.toText()` to collect text. - For streaming responses, iterate the returned AsyncIterable. Use `stream.toText()` to collect text.
## Maintainers
This package is published from the repository workspace. Install dependencies from the repository root with `pnpm install`, then use `./scripts/publish.sh` for dry runs and publishing so `catalog:` dependencies are resolved before release.
## License ## License
This SDK is released under the MIT License. This SDK is released under the MIT License.

View File

@@ -54,24 +54,17 @@
"publish:npm": "./scripts/publish.sh" "publish:npm": "./scripts/publish.sh"
}, },
"dependencies": { "dependencies": {
"axios": "^1.13.6" "axios": "catalog:"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^10.0.1", "@eslint/js": "catalog:",
"@types/node": "^25.4.0", "@types/node": "catalog:",
"@typescript-eslint/eslint-plugin": "^8.57.0", "@typescript-eslint/eslint-plugin": "catalog:",
"@typescript-eslint/parser": "^8.57.0", "@typescript-eslint/parser": "catalog:",
"@vitest/coverage-v8": "4.0.18", "@vitest/coverage-v8": "catalog:",
"eslint": "^10.0.3", "eslint": "catalog:",
"tsup": "^8.5.1", "tsup": "catalog:",
"typescript": "^5.9.3", "typescript": "catalog:",
"vitest": "^4.0.18" "vitest": "catalog:"
},
"pnpm": {
"overrides": {
"flatted@<=3.4.1": "3.4.2",
"picomatch@>=4.0.0 <4.0.4": "4.0.4",
"rollup@>=4.0.0 <4.59.0": "4.59.0"
}
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,2 +0,0 @@
onlyBuiltDependencies:
- esbuild

View File

@@ -5,10 +5,12 @@
# A beautiful and reliable script to publish the SDK to npm # A beautiful and reliable script to publish the SDK to npm
# #
# Usage: # Usage:
# ./scripts/publish.sh # Normal publish # ./scripts/publish.sh # Normal publish
# ./scripts/publish.sh --dry-run # Test without publishing # ./scripts/publish.sh --dry-run # Test without publishing
# ./scripts/publish.sh --skip-tests # Skip tests (not recommended) # ./scripts/publish.sh --skip-tests # Skip tests (not recommended)
# #
# This script requires pnpm because the workspace uses catalog: dependencies.
#
set -euo pipefail set -euo pipefail
@@ -62,11 +64,27 @@ divider() {
echo -e "${DIM}─────────────────────────────────────────────────────────────────${NC}" echo -e "${DIM}─────────────────────────────────────────────────────────────────${NC}"
} }
run_npm() {
env \
-u npm_config_npm_globalconfig \
-u NPM_CONFIG_NPM_GLOBALCONFIG \
-u npm_config_verify_deps_before_run \
-u NPM_CONFIG_VERIFY_DEPS_BEFORE_RUN \
-u npm_config__jsr_registry \
-u NPM_CONFIG__JSR_REGISTRY \
-u npm_config_catalog \
-u NPM_CONFIG_CATALOG \
-u npm_config_overrides \
-u NPM_CONFIG_OVERRIDES \
npm "$@"
}
# ============================================================================ # ============================================================================
# Configuration # Configuration
# ============================================================================ # ============================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
REPO_ROOT="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || (cd "$SCRIPT_DIR/../../.." && pwd))"
DRY_RUN=false DRY_RUN=false
SKIP_TESTS=false SKIP_TESTS=false
@@ -123,23 +141,23 @@ main() {
error "npm is not installed" error "npm is not installed"
exit 1 exit 1
fi fi
NPM_VERSION=$(npm -v) NPM_VERSION=$(run_npm -v)
success "npm: v$NPM_VERSION" success "npm: v$NPM_VERSION"
# Check pnpm (optional, for local dev) if ! command -v pnpm &> /dev/null; then
if command -v pnpm &> /dev/null; then error "pnpm is required because this workspace publishes catalog: dependencies"
PNPM_VERSION=$(pnpm -v) info "Install pnpm with Corepack: corepack enable"
success "pnpm: v$PNPM_VERSION" exit 1
else
info "pnpm not found (optional)"
fi fi
PNPM_VERSION=$(pnpm -v)
success "pnpm: v$PNPM_VERSION"
# Check npm login status # Check npm login status
if ! npm whoami &> /dev/null; then if ! run_npm whoami &> /dev/null; then
error "Not logged in to npm. Run 'npm login' first." error "Not logged in to npm. Run 'npm login' first."
exit 1 exit 1
fi fi
NPM_USER=$(npm whoami) NPM_USER=$(run_npm whoami)
success "Logged in as: ${BOLD}$NPM_USER${NC}" success "Logged in as: ${BOLD}$NPM_USER${NC}"
# ======================================================================== # ========================================================================
@@ -154,11 +172,11 @@ main() {
success "Version: ${BOLD}$PACKAGE_VERSION${NC}" success "Version: ${BOLD}$PACKAGE_VERSION${NC}"
# Check if version already exists on npm # Check if version already exists on npm
if npm view "$PACKAGE_NAME@$PACKAGE_VERSION" version &> /dev/null; then if run_npm view "$PACKAGE_NAME@$PACKAGE_VERSION" version &> /dev/null; then
error "Version $PACKAGE_VERSION already exists on npm!" error "Version $PACKAGE_VERSION already exists on npm!"
echo "" echo ""
info "Current published versions:" info "Current published versions:"
npm view "$PACKAGE_NAME" versions --json 2>/dev/null | tail -5 run_npm view "$PACKAGE_NAME" versions --json 2>/dev/null | tail -5
echo "" echo ""
warning "Please update the version in package.json before publishing." warning "Please update the version in package.json before publishing."
exit 1 exit 1
@@ -170,11 +188,7 @@ main() {
# ======================================================================== # ========================================================================
step "Step 3/6: Installing dependencies..." step "Step 3/6: Installing dependencies..."
if command -v pnpm &> /dev/null; then pnpm --dir "$REPO_ROOT" install --frozen-lockfile 2>/dev/null || pnpm --dir "$REPO_ROOT" install
pnpm install --frozen-lockfile 2>/dev/null || pnpm install
else
npm ci 2>/dev/null || npm install
fi
success "Dependencies installed" success "Dependencies installed"
# ======================================================================== # ========================================================================
@@ -185,11 +199,7 @@ main() {
if [[ "$SKIP_TESTS" == true ]]; then if [[ "$SKIP_TESTS" == true ]]; then
warning "Skipping tests (--skip-tests flag)" warning "Skipping tests (--skip-tests flag)"
else else
if command -v pnpm &> /dev/null; then pnpm test
pnpm test
else
npm test
fi
success "All tests passed" success "All tests passed"
fi fi
@@ -201,11 +211,7 @@ main() {
# Clean previous build # Clean previous build
rm -rf dist rm -rf dist
if command -v pnpm &> /dev/null; then pnpm run build
pnpm run build
else
npm run build
fi
success "Build completed" success "Build completed"
# Verify build output # Verify build output
@@ -223,15 +229,32 @@ main() {
# Step 6: Publish # Step 6: Publish
# ======================================================================== # ========================================================================
step "Step 6/6: Publishing to npm..." step "Step 6/6: Publishing to npm..."
PACK_DIR="$(mktemp -d)"
trap 'rm -rf "$PACK_DIR"' EXIT
pnpm pack --pack-destination "$PACK_DIR" >/dev/null
PACKAGE_TARBALL="$(find "$PACK_DIR" -maxdepth 1 -name '*.tgz' | head -n 1)"
if [[ -z "$PACKAGE_TARBALL" ]]; then
error "Pack failed - no tarball generated"
exit 1
fi
if tar -xOf "$PACKAGE_TARBALL" package/package.json | grep -q '"catalog:'; then
error "Packed manifest still contains catalog: references"
exit 1
fi
divider divider
echo -e "${CYAN}Package contents:${NC}" echo -e "${CYAN}Package contents:${NC}"
npm pack --dry-run 2>&1 | head -30 tar -tzf "$PACKAGE_TARBALL" | head -30
divider divider
if [[ "$DRY_RUN" == true ]]; then if [[ "$DRY_RUN" == true ]]; then
warning "DRY-RUN: Skipping actual publish" warning "DRY-RUN: Skipping actual publish"
echo "" echo ""
info "Packed artifact: $PACKAGE_TARBALL"
info "To publish for real, run without --dry-run flag" info "To publish for real, run without --dry-run flag"
else else
echo "" echo ""
@@ -239,7 +262,7 @@ main() {
echo -e "${DIM}Press Enter to continue, or Ctrl+C to cancel...${NC}" echo -e "${DIM}Press Enter to continue, or Ctrl+C to cancel...${NC}"
read -r read -r
npm publish --access public pnpm publish --access public --no-git-checks
echo "" echo ""
success "🎉 Successfully published ${BOLD}$PACKAGE_NAME@$PACKAGE_VERSION${NC} to npm!" success "🎉 Successfully published ${BOLD}$PACKAGE_NAME@$PACKAGE_VERSION${NC} to npm!"

View File

@@ -10,6 +10,7 @@ export default defineConfig({
// We can not upgrade these yet // We can not upgrade these yet
'tailwind-merge', 'tailwind-merge',
'tailwindcss', 'tailwindcss',
'typescript',
], ],
write: true, write: true,

View File

@@ -1,32 +0,0 @@
.env
.env.*
# Logs
logs
*.log*
# node
node_modules
dist
build
coverage
.husky
.next
.pnpm-store
# vscode
.vscode
# webstorm
.idea
*.iml
*.iws
*.ipr
# Jetbrains
.idea
# git
.git
.gitignore

View File

@@ -19,21 +19,27 @@ ENV NEXT_PUBLIC_BASE_PATH="$NEXT_PUBLIC_BASE_PATH"
# install packages # install packages
FROM base AS packages FROM base AS packages
WORKDIR /app/web WORKDIR /app
COPY package.json pnpm-lock.yaml /app/web/ COPY package.json pnpm-lock.yaml pnpm-workspace.yaml /app/
COPY web/package.json /app/web/
COPY e2e/package.json /app/e2e/
COPY sdks/nodejs-client/package.json /app/sdks/nodejs-client/
# Use packageManager from package.json # Use packageManager from package.json
RUN corepack install RUN corepack install
RUN pnpm install --frozen-lockfile # Install only the web workspace to keep image builds from pulling in
# unrelated workspace dependencies such as e2e tooling.
RUN pnpm install --filter ./web... --frozen-lockfile
# build resources # build resources
FROM base AS builder FROM base AS builder
WORKDIR /app/web WORKDIR /app
COPY --from=packages /app/web/ . COPY --from=packages /app/ .
COPY . . COPY . .
WORKDIR /app/web
ENV NODE_OPTIONS="--max-old-space-size=4096" ENV NODE_OPTIONS="--max-old-space-size=4096"
RUN pnpm build RUN pnpm build
@@ -64,13 +70,13 @@ RUN addgroup -S -g ${dify_uid} dify && \
chown -R dify:dify /app chown -R dify:dify /app
WORKDIR /app/web WORKDIR /app
COPY --from=builder --chown=dify:dify /app/web/public ./public COPY --from=builder --chown=dify:dify /app/web/public ./web/public
COPY --from=builder --chown=dify:dify /app/web/.next/standalone ./ COPY --from=builder --chown=dify:dify /app/web/.next/standalone ./
COPY --from=builder --chown=dify:dify /app/web/.next/static ./.next/static COPY --from=builder --chown=dify:dify /app/web/.next/static ./web/.next/static
COPY --chown=dify:dify --chmod=755 docker/entrypoint.sh ./entrypoint.sh COPY --chown=dify:dify --chmod=755 web/docker/entrypoint.sh ./entrypoint.sh
ARG COMMIT_SHA ARG COMMIT_SHA
ENV COMMIT_SHA=${COMMIT_SHA} ENV COMMIT_SHA=${COMMIT_SHA}

View File

@@ -0,0 +1,34 @@
**
!package.json
!pnpm-lock.yaml
!pnpm-workspace.yaml
!.nvmrc
!web/
!web/**
!e2e/
!e2e/package.json
!sdks/
!sdks/nodejs-client/
!sdks/nodejs-client/package.json
.git
node_modules
.pnpm-store
web/.env
web/.env.*
web/logs
web/*.log*
web/node_modules
web/dist
web/build
web/coverage
web/.husky
web/.next
web/.pnpm-store
web/.vscode
web/.idea
web/*.iml
web/*.iws
web/*.ipr
e2e/node_modules
sdks/nodejs-client/node_modules

View File

@@ -24,18 +24,24 @@ For example, use `vp install` instead of `pnpm install` and `vp test` instead of
> >
> Learn more: [Corepack] > Learn more: [Corepack]
Run the following commands from the repository root.
First, install the dependencies: First, install the dependencies:
```bash ```bash
pnpm install pnpm install
``` ```
> [!NOTE]
> JavaScript dependencies are managed by the workspace files at the repository root: `package.json`, `pnpm-lock.yaml`, `pnpm-workspace.yaml`, and `.nvmrc`.
> Install dependencies from the repository root, then run frontend scripts from `web/`.
Then, configure the environment variables. Then, configure the environment variables.
Create a file named `.env.local` in the current directory and copy the contents from `.env.example`. Create `web/.env.local` and copy the contents from `web/.env.example`.
Modify the values of these environment variables according to your requirements: Modify the values of these environment variables according to your requirements:
```bash ```bash
cp .env.example .env.local cp web/.env.example web/.env.local
``` ```
> [!IMPORTANT] > [!IMPORTANT]
@@ -46,16 +52,16 @@ cp .env.example .env.local
Finally, run the development server: Finally, run the development server:
```bash ```bash
pnpm run dev pnpm -C web run dev
# or if you are using vinext which provides a better development experience # or if you are using vinext which provides a better development experience
pnpm run dev:vinext pnpm -C web run dev:vinext
# (optional) start the dev proxy server so that you can use online API in development # (optional) start the dev proxy server so that you can use online API in development
pnpm run dev:proxy pnpm -C web run dev:proxy
``` ```
Open <http://localhost:3000> with your browser to see the result. Open <http://localhost:3000> with your browser to see the result.
You can start editing the file under folder `app`. You can start editing the files under `web/app`.
The page auto-updates as you edit the file. The page auto-updates as you edit the file.
## Deploy ## Deploy
@@ -65,19 +71,25 @@ The page auto-updates as you edit the file.
First, build the app for production: First, build the app for production:
```bash ```bash
pnpm run build pnpm -C web run build
``` ```
Then, start the server: Then, start the server:
```bash ```bash
pnpm run start pnpm -C web run start
```
If you build the Docker image manually, use the repository root as the build context:
```bash
docker build -f web/Dockerfile -t dify-web .
``` ```
If you want to customize the host and port: If you want to customize the host and port:
```bash ```bash
pnpm run start --port=3001 --host=0.0.0.0 pnpm -C web run start --port=3001 --host=0.0.0.0
``` ```
## Storybook ## Storybook
@@ -87,7 +99,7 @@ This project uses [Storybook] for UI component development.
To start the storybook server, run: To start the storybook server, run:
```bash ```bash
pnpm storybook pnpm -C web storybook
``` ```
Open <http://localhost:6006> with your browser to see the result. Open <http://localhost:6006> with your browser to see the result.
@@ -112,7 +124,7 @@ We use [Vitest] and [React Testing Library] for Unit Testing.
Run test: Run test:
```bash ```bash
pnpm test pnpm -C web test
``` ```
> [!NOTE] > [!NOTE]

View File

@@ -1,4 +1,3 @@
import type { Mock } from 'vitest'
import type { FeatureStoreState } from '@/app/components/base/features/store' import type { FeatureStoreState } from '@/app/components/base/features/store'
import { render, screen } from '@testing-library/react' import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event' import userEvent from '@testing-library/user-event'
@@ -28,7 +27,7 @@ type SetupOptions = {
} }
let mockFeatureStoreState: FeatureStoreState let mockFeatureStoreState: FeatureStoreState
let mockSetFeatures: Mock let mockSetFeatures = vi.fn()
const mockStore = { const mockStore = {
getState: vi.fn<() => FeatureStoreState>(() => mockFeatureStoreState), getState: vi.fn<() => FeatureStoreState>(() => mockFeatureStoreState),
} }

View File

@@ -1,4 +1,3 @@
import type { Mock } from 'vitest'
import type { FeatureStoreState } from '@/app/components/base/features/store' import type { FeatureStoreState } from '@/app/components/base/features/store'
import { render, screen } from '@testing-library/react' import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event' import userEvent from '@testing-library/user-event'
@@ -28,7 +27,7 @@ type SetupOptions = {
} }
let mockFeatureStoreState: FeatureStoreState let mockFeatureStoreState: FeatureStoreState
let mockSetFeatures: Mock let mockSetFeatures = vi.fn()
const mockStore = { const mockStore = {
getState: vi.fn<() => FeatureStoreState>(() => mockFeatureStoreState), getState: vi.fn<() => FeatureStoreState>(() => mockFeatureStoreState),
} }

View File

@@ -1,4 +1,3 @@
import type { Mock } from 'vitest'
import type { ModelConfig, PromptVariable } from '@/models/debug' import type { ModelConfig, PromptVariable } from '@/models/debug'
import type { ToolItem } from '@/types/app' import type { ToolItem } from '@/types/app'
import { render, screen } from '@testing-library/react' import { render, screen } from '@testing-library/react'
@@ -74,10 +73,10 @@ type MockContext = {
history: boolean history: boolean
query: boolean query: boolean
} }
showHistoryModal: Mock showHistoryModal: () => void
modelConfig: ModelConfig modelConfig: ModelConfig
setModelConfig: Mock setModelConfig: (modelConfig: ModelConfig) => void
setPrevPromptConfig: Mock setPrevPromptConfig: (configs: ModelConfig['configs']) => void
} }
const createPromptVariable = (overrides: Partial<PromptVariable> = {}): PromptVariable => ({ const createPromptVariable = (overrides: Partial<PromptVariable> = {}): PromptVariable => ({
@@ -142,7 +141,7 @@ const createContextValue = (overrides: Partial<MockContext> = {}): MockContext =
...overrides, ...overrides,
}) })
const mockUseContext = useContextSelector.useContext as Mock const mockUseContext = vi.mocked(useContextSelector.useContext)
const renderConfig = (contextOverrides: Partial<MockContext> = {}) => { const renderConfig = (contextOverrides: Partial<MockContext> = {}) => {
const contextValue = createContextValue(contextOverrides) const contextValue = createContextValue(contextOverrides)

View File

@@ -1,16 +1,15 @@
import type { Mock } from 'vitest'
import { act, renderHook } from '@testing-library/react' import { act, renderHook } from '@testing-library/react'
import { useDSLDragDrop } from '../use-dsl-drag-drop' import { useDSLDragDrop } from '../use-dsl-drag-drop'
describe('useDSLDragDrop', () => { describe('useDSLDragDrop', () => {
let container: HTMLDivElement let container: HTMLDivElement
let mockOnDSLFileDropped: Mock let mockOnDSLFileDropped = vi.fn<(file: File) => void>()
beforeEach(() => { beforeEach(() => {
vi.clearAllMocks() vi.clearAllMocks()
container = document.createElement('div') container = document.createElement('div')
document.body.appendChild(container) document.body.appendChild(container)
mockOnDSLFileDropped = vi.fn() mockOnDSLFileDropped = vi.fn<(file: File) => void>()
}) })
afterEach(() => { afterEach(() => {

View File

@@ -11,15 +11,15 @@ type EmblaEventName = 'reInit' | 'select'
type EmblaListener = (api: MockEmblaApi | undefined) => void type EmblaListener = (api: MockEmblaApi | undefined) => void
type MockEmblaApi = { type MockEmblaApi = {
scrollPrev: Mock scrollPrev: Mock<() => void>
scrollNext: Mock scrollNext: Mock<() => void>
scrollTo: Mock scrollTo: Mock<(index: number) => void>
selectedScrollSnap: Mock selectedScrollSnap: Mock<() => number>
canScrollPrev: Mock canScrollPrev: Mock<() => boolean>
canScrollNext: Mock canScrollNext: Mock<() => boolean>
slideNodes: Mock slideNodes: Mock<() => HTMLDivElement[]>
on: Mock on: Mock<(event: EmblaEventName, callback: EmblaListener) => void>
off: Mock off: Mock<(event: EmblaEventName, callback: EmblaListener) => void>
} }
let mockCanScrollPrev = false let mockCanScrollPrev = false
@@ -33,19 +33,19 @@ const mockCarouselRef = vi.fn()
const mockedUseEmblaCarousel = vi.mocked(useEmblaCarousel) const mockedUseEmblaCarousel = vi.mocked(useEmblaCarousel)
const createMockEmblaApi = (): MockEmblaApi => ({ const createMockEmblaApi = (): MockEmblaApi => ({
scrollPrev: vi.fn(), scrollPrev: vi.fn<() => void>(),
scrollNext: vi.fn(), scrollNext: vi.fn<() => void>(),
scrollTo: vi.fn(), scrollTo: vi.fn<(index: number) => void>(),
selectedScrollSnap: vi.fn(() => mockSelectedIndex), selectedScrollSnap: vi.fn<() => number>(() => mockSelectedIndex),
canScrollPrev: vi.fn(() => mockCanScrollPrev), canScrollPrev: vi.fn<() => boolean>(() => mockCanScrollPrev),
canScrollNext: vi.fn(() => mockCanScrollNext), canScrollNext: vi.fn<() => boolean>(() => mockCanScrollNext),
slideNodes: vi.fn(() => slideNodes: vi.fn<() => HTMLDivElement[]>(() =>
Array.from({ length: mockSlideCount }).fill(document.createElement('div')), Array.from({ length: mockSlideCount }, () => document.createElement('div')),
), ),
on: vi.fn((event: EmblaEventName, callback: EmblaListener) => { on: vi.fn<(event: EmblaEventName, callback: EmblaListener) => void>((event, callback) => {
listeners[event].push(callback) listeners[event].push(callback)
}), }),
off: vi.fn((event: EmblaEventName, callback: EmblaListener) => { off: vi.fn<(event: EmblaEventName, callback: EmblaListener) => void>((event, callback) => {
listeners[event] = listeners[event].filter(listener => listener !== callback) listeners[event] = listeners[event].filter(listener => listener !== callback)
}), }),
}) })

View File

@@ -1,4 +1,3 @@
import type { Mock } from 'vitest'
import { render, screen, waitFor } from '@testing-library/react' import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event' import userEvent from '@testing-library/user-event'
import UpgradeBtn from '../index' import UpgradeBtn from '../index'
@@ -14,14 +13,16 @@ vi.mock('@/context/modal-context', () => ({
}), }),
})) }))
type GtagHandler = (command: string, action: string, payload: { loc: string }) => void
// Typed window accessor for gtag tracking tests // Typed window accessor for gtag tracking tests
const gtagWindow = window as unknown as Record<string, Mock | undefined> const gtagWindow = window as unknown as { gtag?: GtagHandler }
let mockGtag: Mock | undefined let mockGtag = vi.fn<GtagHandler>()
describe('UpgradeBtn', () => { describe('UpgradeBtn', () => {
beforeEach(() => { beforeEach(() => {
vi.clearAllMocks() vi.clearAllMocks()
mockGtag = vi.fn() mockGtag = vi.fn<GtagHandler>()
gtagWindow.gtag = mockGtag gtagWindow.gtag = mockGtag
}) })

View File

@@ -56,6 +56,7 @@ export default antfu(
}, },
}, },
e18e: false, e18e: false,
pnpm: false,
}, },
{ {
plugins: { plugins: {

View File

@@ -10,7 +10,6 @@ const nextConfig: NextConfig = {
basePath: env.NEXT_PUBLIC_BASE_PATH, basePath: env.NEXT_PUBLIC_BASE_PATH,
transpilePackages: ['@t3-oss/env-core', '@t3-oss/env-nextjs', 'echarts', 'zrender'], transpilePackages: ['@t3-oss/env-core', '@t3-oss/env-nextjs', 'echarts', 'zrender'],
turbopack: { turbopack: {
root: process.cwd(),
rules: codeInspectorPlugin({ rules: codeInspectorPlugin({
bundler: 'turbopack', bundler: 'turbopack',
}), }),

View File

@@ -3,7 +3,6 @@
"type": "module", "type": "module",
"version": "1.13.3", "version": "1.13.3",
"private": true, "private": true,
"packageManager": "pnpm@10.32.1",
"imports": { "imports": {
"#i18n": { "#i18n": {
"react-server": "./i18n-config/lib.server.ts", "react-server": "./i18n-config/lib.server.ts",
@@ -22,9 +21,6 @@
"and_uc >= 15.5", "and_uc >= 15.5",
"and_qq >= 14.9" "and_qq >= 14.9"
], ],
"engines": {
"node": "^22.22.1"
},
"scripts": { "scripts": {
"analyze": "next experimental-analyze", "analyze": "next experimental-analyze",
"analyze-component": "node ./scripts/analyze-component.js", "analyze-component": "node ./scripts/analyze-component.js",
@@ -58,258 +54,189 @@
"uglify-embed": "node ./bin/uglify-embed" "uglify-embed": "node ./bin/uglify-embed"
}, },
"dependencies": { "dependencies": {
"@amplitude/analytics-browser": "2.37.0", "@amplitude/analytics-browser": "catalog:",
"@amplitude/plugin-session-replay-browser": "1.27.1", "@amplitude/plugin-session-replay-browser": "catalog:",
"@base-ui/react": "1.3.0", "@base-ui/react": "catalog:",
"@emoji-mart/data": "1.2.1", "@emoji-mart/data": "catalog:",
"@floating-ui/react": "0.27.19", "@floating-ui/react": "catalog:",
"@formatjs/intl-localematcher": "0.8.2", "@formatjs/intl-localematcher": "catalog:",
"@headlessui/react": "2.2.9", "@headlessui/react": "catalog:",
"@heroicons/react": "2.2.0", "@heroicons/react": "catalog:",
"@lexical/code": "0.42.0", "@lexical/code": "catalog:",
"@lexical/link": "0.42.0", "@lexical/link": "catalog:",
"@lexical/list": "0.42.0", "@lexical/list": "catalog:",
"@lexical/react": "0.42.0", "@lexical/react": "catalog:",
"@lexical/selection": "0.42.0", "@lexical/selection": "catalog:",
"@lexical/text": "0.42.0", "@lexical/text": "catalog:",
"@lexical/utils": "0.42.0", "@lexical/utils": "catalog:",
"@monaco-editor/react": "4.7.0", "@monaco-editor/react": "catalog:",
"@orpc/client": "1.13.9", "@orpc/client": "catalog:",
"@orpc/contract": "1.13.9", "@orpc/contract": "catalog:",
"@orpc/openapi-client": "1.13.9", "@orpc/openapi-client": "catalog:",
"@orpc/tanstack-query": "1.13.9", "@orpc/tanstack-query": "catalog:",
"@remixicon/react": "4.9.0", "@remixicon/react": "catalog:",
"@sentry/react": "10.45.0", "@sentry/react": "catalog:",
"@streamdown/math": "1.0.2", "@streamdown/math": "catalog:",
"@svgdotjs/svg.js": "3.2.5", "@svgdotjs/svg.js": "catalog:",
"@t3-oss/env-nextjs": "0.13.11", "@t3-oss/env-nextjs": "catalog:",
"@tailwindcss/typography": "0.5.19", "@tailwindcss/typography": "catalog:",
"@tanstack/react-form": "1.28.5", "@tanstack/react-form": "catalog:",
"@tanstack/react-query": "5.95.0", "@tanstack/react-query": "catalog:",
"abcjs": "6.6.2", "abcjs": "catalog:",
"ahooks": "3.9.6", "ahooks": "catalog:",
"class-variance-authority": "0.7.1", "class-variance-authority": "catalog:",
"clsx": "2.1.1", "clsx": "catalog:",
"cmdk": "1.1.1", "cmdk": "catalog:",
"copy-to-clipboard": "3.3.3", "copy-to-clipboard": "catalog:",
"cron-parser": "5.5.0", "cron-parser": "catalog:",
"dayjs": "1.11.20", "dayjs": "catalog:",
"decimal.js": "10.6.0", "decimal.js": "catalog:",
"dompurify": "3.3.3", "dompurify": "catalog:",
"echarts": "6.0.0", "echarts": "catalog:",
"echarts-for-react": "3.0.6", "echarts-for-react": "catalog:",
"elkjs": "0.11.1", "elkjs": "catalog:",
"embla-carousel-autoplay": "8.6.0", "embla-carousel-autoplay": "catalog:",
"embla-carousel-react": "8.6.0", "embla-carousel-react": "catalog:",
"emoji-mart": "5.6.0", "emoji-mart": "catalog:",
"es-toolkit": "1.45.1", "es-toolkit": "catalog:",
"fast-deep-equal": "3.1.3", "fast-deep-equal": "catalog:",
"foxact": "0.3.0", "foxact": "catalog:",
"html-entities": "2.6.0", "html-entities": "catalog:",
"html-to-image": "1.11.13", "html-to-image": "catalog:",
"i18next": "25.10.4", "i18next": "catalog:",
"i18next-resources-to-backend": "1.2.1", "i18next-resources-to-backend": "catalog:",
"immer": "11.1.4", "immer": "catalog:",
"jotai": "2.18.1", "jotai": "catalog:",
"js-audio-recorder": "1.0.7", "js-audio-recorder": "catalog:",
"js-cookie": "3.0.5", "js-cookie": "catalog:",
"js-yaml": "4.1.1", "js-yaml": "catalog:",
"jsonschema": "1.5.0", "jsonschema": "catalog:",
"katex": "0.16.40", "katex": "catalog:",
"ky": "1.14.3", "ky": "catalog:",
"lamejs": "1.2.1", "lamejs": "catalog:",
"lexical": "0.42.0", "lexical": "catalog:",
"mermaid": "11.13.0", "mermaid": "catalog:",
"mime": "4.1.0", "mime": "catalog:",
"mitt": "3.0.1", "mitt": "catalog:",
"negotiator": "1.0.0", "negotiator": "catalog:",
"next": "16.2.1", "next": "catalog:",
"next-themes": "0.4.6", "next-themes": "catalog:",
"nuqs": "2.8.9", "nuqs": "catalog:",
"pinyin-pro": "3.28.0", "pinyin-pro": "catalog:",
"qrcode.react": "4.2.0", "qrcode.react": "catalog:",
"qs": "6.15.0", "qs": "catalog:",
"react": "19.2.4", "react": "catalog:",
"react-18-input-autosize": "3.0.0", "react-18-input-autosize": "catalog:",
"react-dom": "19.2.4", "react-dom": "catalog:",
"react-easy-crop": "5.5.6", "react-easy-crop": "catalog:",
"react-hotkeys-hook": "5.2.4", "react-hotkeys-hook": "catalog:",
"react-i18next": "16.6.1", "react-i18next": "catalog:",
"react-multi-email": "1.0.25", "react-multi-email": "catalog:",
"react-papaparse": "4.4.0", "react-papaparse": "catalog:",
"react-pdf-highlighter": "8.0.0-rc.0", "react-pdf-highlighter": "catalog:",
"react-sortablejs": "6.1.4", "react-sortablejs": "catalog:",
"react-syntax-highlighter": "15.6.6", "react-syntax-highlighter": "catalog:",
"react-textarea-autosize": "8.5.9", "react-textarea-autosize": "catalog:",
"react-window": "1.8.11", "react-window": "catalog:",
"reactflow": "11.11.4", "reactflow": "catalog:",
"remark-breaks": "4.0.0", "remark-breaks": "catalog:",
"remark-directive": "4.0.0", "remark-directive": "catalog:",
"scheduler": "0.27.0", "scheduler": "catalog:",
"sharp": "0.34.5", "sharp": "catalog:",
"sortablejs": "1.15.7", "sortablejs": "catalog:",
"std-semver": "1.0.8", "std-semver": "catalog:",
"streamdown": "2.5.0", "streamdown": "catalog:",
"string-ts": "2.3.1", "string-ts": "catalog:",
"tailwind-merge": "2.6.1", "tailwind-merge": "catalog:",
"tldts": "7.0.27", "tldts": "catalog:",
"unist-util-visit": "5.1.0", "unist-util-visit": "catalog:",
"use-context-selector": "2.0.0", "use-context-selector": "catalog:",
"uuid": "13.0.0", "uuid": "catalog:",
"zod": "4.3.6", "zod": "catalog:",
"zundo": "2.3.0", "zundo": "catalog:",
"zustand": "5.0.12" "zustand": "catalog:"
}, },
"devDependencies": { "devDependencies": {
"@antfu/eslint-config": "7.7.3", "@antfu/eslint-config": "catalog:",
"@chromatic-com/storybook": "5.0.2", "@chromatic-com/storybook": "catalog:",
"@egoist/tailwindcss-icons": "1.9.2", "@egoist/tailwindcss-icons": "catalog:",
"@eslint-react/eslint-plugin": "3.0.0", "@eslint-react/eslint-plugin": "catalog:",
"@hono/node-server": "1.19.11", "@hono/node-server": "catalog:",
"@iconify-json/heroicons": "1.2.3", "@iconify-json/heroicons": "catalog:",
"@iconify-json/ri": "1.2.10", "@iconify-json/ri": "catalog:",
"@mdx-js/loader": "3.1.1", "@mdx-js/loader": "catalog:",
"@mdx-js/react": "3.1.1", "@mdx-js/react": "catalog:",
"@mdx-js/rollup": "3.1.1", "@mdx-js/rollup": "catalog:",
"@next/eslint-plugin-next": "16.2.1", "@next/eslint-plugin-next": "catalog:",
"@next/mdx": "16.2.1", "@next/mdx": "catalog:",
"@rgrove/parse-xml": "4.2.0", "@rgrove/parse-xml": "catalog:",
"@storybook/addon-docs": "10.3.1", "@storybook/addon-docs": "catalog:",
"@storybook/addon-links": "10.3.1", "@storybook/addon-links": "catalog:",
"@storybook/addon-onboarding": "10.3.1", "@storybook/addon-onboarding": "catalog:",
"@storybook/addon-themes": "10.3.1", "@storybook/addon-themes": "catalog:",
"@storybook/nextjs-vite": "10.3.1", "@storybook/nextjs-vite": "catalog:",
"@storybook/react": "10.3.1", "@storybook/react": "catalog:",
"@tanstack/eslint-plugin-query": "5.95.0", "@tanstack/eslint-plugin-query": "catalog:",
"@tanstack/react-devtools": "0.10.0", "@tanstack/react-devtools": "catalog:",
"@tanstack/react-form-devtools": "0.2.19", "@tanstack/react-form-devtools": "catalog:",
"@tanstack/react-query-devtools": "5.95.0", "@tanstack/react-query-devtools": "catalog:",
"@testing-library/dom": "10.4.1", "@testing-library/dom": "catalog:",
"@testing-library/jest-dom": "6.9.1", "@testing-library/jest-dom": "catalog:",
"@testing-library/react": "16.3.2", "@testing-library/react": "catalog:",
"@testing-library/user-event": "14.6.1", "@testing-library/user-event": "catalog:",
"@tsslint/cli": "3.0.2", "@tsslint/cli": "catalog:",
"@tsslint/compat-eslint": "3.0.2", "@tsslint/compat-eslint": "catalog:",
"@tsslint/config": "3.0.2", "@tsslint/config": "catalog:",
"@types/js-cookie": "3.0.6", "@types/js-cookie": "catalog:",
"@types/js-yaml": "4.0.9", "@types/js-yaml": "catalog:",
"@types/negotiator": "0.6.4", "@types/negotiator": "catalog:",
"@types/node": "25.5.0", "@types/node": "catalog:",
"@types/postcss-js": "4.1.0", "@types/postcss-js": "catalog:",
"@types/qs": "6.15.0", "@types/qs": "catalog:",
"@types/react": "19.2.14", "@types/react": "catalog:",
"@types/react-dom": "19.2.3", "@types/react-dom": "catalog:",
"@types/react-syntax-highlighter": "15.5.13", "@types/react-syntax-highlighter": "catalog:",
"@types/react-window": "1.8.8", "@types/react-window": "catalog:",
"@types/sortablejs": "1.15.9", "@types/sortablejs": "catalog:",
"@typescript-eslint/parser": "8.57.1", "@typescript-eslint/parser": "catalog:",
"@typescript/native-preview": "7.0.0-dev.20260322.1", "@typescript/native-preview": "catalog:",
"@vitejs/plugin-react": "6.0.1", "@vitejs/plugin-react": "catalog:",
"@vitejs/plugin-rsc": "0.5.21", "@vitejs/plugin-rsc": "catalog:",
"@vitest/coverage-v8": "4.1.0", "@vitest/coverage-v8": "catalog:",
"agentation": "2.3.3", "agentation": "catalog:",
"autoprefixer": "10.4.27", "autoprefixer": "catalog:",
"code-inspector-plugin": "1.4.5", "code-inspector-plugin": "catalog:",
"eslint": "10.1.0", "eslint": "catalog:",
"eslint-markdown": "0.6.0", "eslint-markdown": "catalog:",
"eslint-plugin-better-tailwindcss": "4.3.2", "eslint-plugin-better-tailwindcss": "catalog:",
"eslint-plugin-hyoban": "0.14.1", "eslint-plugin-hyoban": "catalog:",
"eslint-plugin-markdown-preferences": "0.40.3", "eslint-plugin-markdown-preferences": "catalog:",
"eslint-plugin-no-barrel-files": "1.2.2", "eslint-plugin-no-barrel-files": "catalog:",
"eslint-plugin-react-hooks": "7.0.1", "eslint-plugin-react-hooks": "catalog:",
"eslint-plugin-react-refresh": "0.5.2", "eslint-plugin-react-refresh": "catalog:",
"eslint-plugin-sonarjs": "4.0.2", "eslint-plugin-sonarjs": "catalog:",
"eslint-plugin-storybook": "10.3.1", "eslint-plugin-storybook": "catalog:",
"happy-dom": "20.8.9", "happy-dom": "catalog:",
"hono": "4.12.8", "hono": "catalog:",
"husky": "9.1.7", "husky": "catalog:",
"iconify-import-svg": "0.1.2", "iconify-import-svg": "catalog:",
"knip": "6.0.2", "knip": "catalog:",
"lint-staged": "16.4.0", "lint-staged": "catalog:",
"postcss": "8.5.8", "postcss": "catalog:",
"postcss-js": "5.1.0", "postcss-js": "catalog:",
"react-server-dom-webpack": "19.2.4", "react-server-dom-webpack": "catalog:",
"sass": "1.98.0", "sass": "catalog:",
"storybook": "10.3.1", "storybook": "catalog:",
"tailwindcss": "3.4.19", "tailwindcss": "catalog:",
"taze": "19.10.0", "tsx": "catalog:",
"tsx": "4.21.0", "typescript": "catalog:",
"typescript": "5.9.3", "uglify-js": "catalog:",
"uglify-js": "3.19.3", "vinext": "catalog:",
"vinext": "https://pkg.pr.new/vinext@b6a2cac", "vite": "catalog:",
"vite": "npm:@voidzero-dev/vite-plus-core@0.1.13", "vite-plugin-inspect": "catalog:",
"vite-plugin-inspect": "11.3.3", "vite-plus": "catalog:",
"vite-plus": "0.1.13", "vitest": "catalog:",
"vitest": "npm:@voidzero-dev/vite-plus-test@0.1.13", "vitest-canvas-mock": "catalog:"
"vitest-canvas-mock": "1.1.3"
},
"pnpm": {
"overrides": {
"@lexical/code": "npm:lexical-code-no-prism@0.41.0",
"@monaco-editor/loader": "1.7.0",
"@nolyfill/safe-buffer": "npm:safe-buffer@^5.2.1",
"array-includes": "npm:@nolyfill/array-includes@^1.0.44",
"array.prototype.findlast": "npm:@nolyfill/array.prototype.findlast@^1.0.44",
"array.prototype.findlastindex": "npm:@nolyfill/array.prototype.findlastindex@^1.0.44",
"array.prototype.flat": "npm:@nolyfill/array.prototype.flat@^1.0.44",
"array.prototype.flatmap": "npm:@nolyfill/array.prototype.flatmap@^1.0.44",
"array.prototype.tosorted": "npm:@nolyfill/array.prototype.tosorted@^1.0.44",
"assert": "npm:@nolyfill/assert@^1.0.26",
"brace-expansion@<2.0.2": "2.0.2",
"canvas": "^3.2.2",
"devalue@<5.3.2": "5.3.2",
"dompurify@>=3.1.3 <=3.3.1": "3.3.2",
"es-iterator-helpers": "npm:@nolyfill/es-iterator-helpers@^1.0.21",
"esbuild@<0.27.2": "0.27.2",
"glob@>=10.2.0 <10.5.0": "11.1.0",
"hasown": "npm:@nolyfill/hasown@^1.0.44",
"is-arguments": "npm:@nolyfill/is-arguments@^1.0.44",
"is-core-module": "npm:@nolyfill/is-core-module@^1.0.39",
"is-generator-function": "npm:@nolyfill/is-generator-function@^1.0.44",
"is-typed-array": "npm:@nolyfill/is-typed-array@^1.0.44",
"isarray": "npm:@nolyfill/isarray@^1.0.44",
"object.assign": "npm:@nolyfill/object.assign@^1.0.44",
"object.entries": "npm:@nolyfill/object.entries@^1.0.44",
"object.fromentries": "npm:@nolyfill/object.fromentries@^1.0.44",
"object.groupby": "npm:@nolyfill/object.groupby@^1.0.44",
"object.values": "npm:@nolyfill/object.values@^1.0.44",
"pbkdf2": "~3.1.5",
"pbkdf2@<3.1.3": "3.1.3",
"picomatch@<2.3.2": "2.3.2",
"picomatch@>=4.0.0 <4.0.4": "4.0.4",
"prismjs": "~1.30",
"prismjs@<1.30.0": "1.30.0",
"rollup@>=4.0.0 <4.59.0": "4.59.0",
"safe-buffer": "^5.2.1",
"safe-regex-test": "npm:@nolyfill/safe-regex-test@^1.0.44",
"safer-buffer": "npm:@nolyfill/safer-buffer@^1.0.44",
"side-channel": "npm:@nolyfill/side-channel@^1.0.44",
"smol-toml@<1.6.1": "1.6.1",
"solid-js": "1.9.11",
"string-width": "~8.2.0",
"string.prototype.includes": "npm:@nolyfill/string.prototype.includes@^1.0.44",
"string.prototype.matchall": "npm:@nolyfill/string.prototype.matchall@^1.0.44",
"string.prototype.repeat": "npm:@nolyfill/string.prototype.repeat@^1.0.44",
"string.prototype.trimend": "npm:@nolyfill/string.prototype.trimend@^1.0.44",
"svgo@>=3.0.0 <3.3.3": "3.3.3",
"tar@<=7.5.10": "7.5.11",
"typed-array-buffer": "npm:@nolyfill/typed-array-buffer@^1.0.44",
"undici@>=7.0.0 <7.24.0": "7.24.0",
"vite": "npm:@voidzero-dev/vite-plus-core@0.1.13",
"vitest": "npm:@voidzero-dev/vite-plus-test@0.1.13",
"which-typed-array": "npm:@nolyfill/which-typed-array@^1.0.44",
"yaml@>=2.0.0 <2.8.3": "2.8.3",
"yauzl@<3.2.1": "3.2.1"
},
"ignoredBuiltDependencies": [
"canvas",
"core-js-pure"
],
"onlyBuiltDependencies": [
"@parcel/watcher",
"esbuild",
"sharp"
]
}, },
"lint-staged": { "lint-staged": {
"*": "eslint --fix --pass-on-unpruned-suppressions" "*": "eslint --fix --pass-on-unpruned-suppressions"

View File

@@ -8,21 +8,6 @@ import { spawn } from 'node:child_process'
import { cp, mkdir, stat } from 'node:fs/promises' import { cp, mkdir, stat } from 'node:fs/promises'
import path from 'node:path' import path from 'node:path'
// Configuration for directories to copy
const DIRS_TO_COPY = [
{
src: path.join('.next', 'static'),
dest: path.join('.next', 'standalone', '.next', 'static'),
},
{
src: 'public',
dest: path.join('.next', 'standalone', 'public'),
},
]
// Path to the server script
const SERVER_SCRIPT_PATH = path.join('.next', 'standalone', 'server.js')
// Function to check if a path exists // Function to check if a path exists
const pathExists = async (path) => { const pathExists = async (path) => {
try { try {
@@ -40,6 +25,23 @@ const pathExists = async (path) => {
} }
} }
const STANDALONE_ROOT_CANDIDATES = [
path.join('.next', 'standalone', 'web'),
path.join('.next', 'standalone'),
]
const getStandaloneRoot = async () => {
for (const standaloneRoot of STANDALONE_ROOT_CANDIDATES) {
const serverScriptPath = path.join(standaloneRoot, 'server.js')
if (await pathExists(serverScriptPath))
return standaloneRoot
}
throw new Error(
`Unable to find Next standalone server entry. Checked: ${STANDALONE_ROOT_CANDIDATES.join(', ')}`,
)
}
// Function to recursively copy directories // Function to recursively copy directories
const copyDir = async (src, dest) => { const copyDir = async (src, dest) => {
console.debug(`Copying directory from ${src} to ${dest}`) console.debug(`Copying directory from ${src} to ${dest}`)
@@ -48,9 +50,20 @@ const copyDir = async (src, dest) => {
} }
// Process each directory copy operation // Process each directory copy operation
const copyAllDirs = async () => { const copyAllDirs = async (standaloneRoot) => {
const dirsToCopy = [
{
src: path.join('.next', 'static'),
dest: path.join(standaloneRoot, '.next', 'static'),
},
{
src: 'public',
dest: path.join(standaloneRoot, 'public'),
},
]
console.debug('Starting directory copy operations') console.debug('Starting directory copy operations')
for (const { src, dest } of DIRS_TO_COPY) { for (const { src, dest } of dirsToCopy) {
try { try {
// Instead of pre-creating destination directory, we ensure parent directory exists // Instead of pre-creating destination directory, we ensure parent directory exists
const destParent = path.dirname(dest) const destParent = path.dirname(dest)
@@ -75,19 +88,22 @@ const copyAllDirs = async () => {
// Run copy operations and start server // Run copy operations and start server
const main = async () => { const main = async () => {
console.debug('Starting copy-and-start script') console.debug('Starting copy-and-start script')
await copyAllDirs() const standaloneRoot = await getStandaloneRoot()
const serverScriptPath = path.join(standaloneRoot, 'server.js')
await copyAllDirs(standaloneRoot)
// Start server // Start server
const port = process.env.npm_config_port || process.env.PORT || '3000' const port = process.env.npm_config_port || process.env.PORT || '3000'
const host = process.env.npm_config_host || process.env.HOSTNAME || '0.0.0.0' const host = process.env.npm_config_host || process.env.HOSTNAME || '0.0.0.0'
console.info(`Starting server on ${host}:${port}`) console.info(`Starting server on ${host}:${port}`)
console.debug(`Server script path: ${SERVER_SCRIPT_PATH}`) console.debug(`Server script path: ${serverScriptPath}`)
console.debug(`Environment variables - PORT: ${port}, HOSTNAME: ${host}`) console.debug(`Environment variables - PORT: ${port}, HOSTNAME: ${host}`)
const server = spawn( const server = spawn(
process.execPath, process.execPath,
[SERVER_SCRIPT_PATH], [serverScriptPath],
{ {
env: { env: {
...process.env, ...process.env,

View File

@@ -1,8 +1,11 @@
import * as jestDomMatchers from '@testing-library/jest-dom/matchers'
import { act, cleanup } from '@testing-library/react' import { act, cleanup } from '@testing-library/react'
import * as React from 'react' import * as React from 'react'
import '@testing-library/jest-dom/vitest' import '@testing-library/jest-dom/vitest'
import 'vitest-canvas-mock' import 'vitest-canvas-mock'
expect.extend(jestDomMatchers)
// Suppress act() warnings from @headlessui/react internal Transition component // Suppress act() warnings from @headlessui/react internal Transition component
// These warnings are caused by Headless UI's internal async state updates, not our code // These warnings are caused by Headless UI's internal async state updates, not our code
const originalConsoleError = console.error const originalConsoleError = console.error