Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot]
d7e33788be chore(deps): bump the python-packages group in /api with 2 updates
Updates the requirements on [sentry-sdk](https://github.com/getsentry/sentry-python) and [unstructured](https://github.com/Unstructured-IO/unstructured) to permit the latest version.

Updates `sentry-sdk` to 2.56.0
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-python/compare/2.55.0...2.56.0)

Updates `unstructured` to 0.22.6
- [Release notes](https://github.com/Unstructured-IO/unstructured/releases)
- [Changelog](https://github.com/Unstructured-IO/unstructured/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Unstructured-IO/unstructured/compare/0.21.5...0.22.6)

---
updated-dependencies:
- dependency-name: sentry-sdk
  dependency-version: 2.56.0
  dependency-type: direct:production
  dependency-group: python-packages
- dependency-name: unstructured
  dependency-version: 0.22.6
  dependency-type: direct:production
  dependency-group: python-packages
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-30 09:48:44 +00:00
45 changed files with 6949 additions and 4153 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -89,3 +89,34 @@ jobs:
flags: web
env:
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,7 +212,6 @@ api/.vscode
# pnpm
/.pnpm-store
/node_modules
# plugin migrate
plugins.jsonl
@@ -240,4 +239,4 @@ scripts/stress-test/reports/
*.local.md
# Code Agent Folder
.qoder/*
.qoder/*

View File

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

View File

@@ -40,8 +40,6 @@ The scripts resolve paths relative to their location, so you can run them from a
./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. Start the worker service (async and scheduler tasks, runs from `api`).

View File

@@ -29,31 +29,6 @@ from services.entities.knowledge_entities.knowledge_entities import SegmentUpdat
from services.errors.chunk import ChildChunkDeleteIndexError, ChildChunkIndexingError
from services.errors.chunk import ChildChunkDeleteIndexError as ChildChunkDeleteIndexServiceError
from services.errors.chunk import ChildChunkIndexingError as ChildChunkIndexingServiceError
from services.summary_index_service import SummaryIndexService
def _marshal_segment_with_summary(segment, dataset_id: str) -> dict:
"""Marshal a single segment and enrich it with summary content."""
segment_dict = dict(marshal(segment, segment_fields)) # type: ignore[arg-type]
summary = SummaryIndexService.get_segment_summary(segment_id=segment.id, dataset_id=dataset_id)
segment_dict["summary"] = summary.summary_content if summary else None
return segment_dict
def _marshal_segments_with_summary(segments, dataset_id: str) -> list[dict]:
"""Marshal multiple segments and enrich them with summary content (batch query)."""
segment_ids = [segment.id for segment in segments]
summaries: dict = {}
if segment_ids:
summary_records = SummaryIndexService.get_segments_summaries(segment_ids=segment_ids, dataset_id=dataset_id)
summaries = {chunk_id: record.summary_content for chunk_id, record in summary_records.items()}
result = []
for segment in segments:
segment_dict = dict(marshal(segment, segment_fields)) # type: ignore[arg-type]
segment_dict["summary"] = summaries.get(segment.id)
result.append(segment_dict)
return result
class SegmentCreatePayload(BaseModel):
@@ -157,7 +132,7 @@ class SegmentApi(DatasetApiResource):
for args_item in payload.segments:
SegmentService.segment_create_args_validate(args_item, document)
segments = SegmentService.multi_create_segment(payload.segments, document, dataset)
return {"data": _marshal_segments_with_summary(segments, dataset_id), "doc_form": document.doc_form}, 200
return {"data": marshal(segments, segment_fields), "doc_form": document.doc_form}, 200
else:
return {"error": "Segments is required"}, 400
@@ -221,7 +196,7 @@ class SegmentApi(DatasetApiResource):
)
response = {
"data": _marshal_segments_with_summary(segments, dataset_id),
"data": marshal(segments, segment_fields),
"doc_form": document.doc_form,
"total": total,
"has_more": len(segments) == limit,
@@ -321,7 +296,7 @@ class DatasetSegmentApi(DatasetApiResource):
payload = SegmentUpdatePayload.model_validate(service_api_ns.payload or {})
updated_segment = SegmentService.update_segment(payload.segment, segment, document, dataset)
return {"data": _marshal_segment_with_summary(updated_segment, dataset_id), "doc_form": document.doc_form}, 200
return {"data": marshal(updated_segment, segment_fields), "doc_form": document.doc_form}, 200
@service_api_ns.doc("get_segment")
@service_api_ns.doc(description="Get a specific segment by ID")
@@ -351,7 +326,7 @@ class DatasetSegmentApi(DatasetApiResource):
if not segment:
raise NotFound("Segment not found.")
return {"data": _marshal_segment_with_summary(segment, dataset_id), "doc_form": document.doc_form}, 200
return {"data": marshal(segment, segment_fields), "doc_form": document.doc_form}, 200
@service_api_ns.route(

View File

@@ -72,12 +72,12 @@ dependencies = [
"readabilipy~=0.3.0",
"redis[hiredis]~=7.4.0",
"resend~=2.26.0",
"sentry-sdk[flask]~=2.55.0",
"sentry-sdk[flask]~=2.56.0",
"sqlalchemy~=2.0.29",
"starlette==1.0.0",
"tiktoken~=0.12.0",
"transformers~=5.3.0",
"unstructured[docx,epub,md,ppt,pptx]~=0.21.5",
"unstructured[docx,epub,md,ppt,pptx]~=0.22.6",
"pypandoc~=1.13",
"yarl~=1.23.0",
"sseclient-py~=1.9.0",

View File

@@ -768,7 +768,6 @@ class TestSegmentApiGet:
``current_account_with_tenant()`` and ``marshal``.
"""
@patch("controllers.service_api.dataset.segment.SummaryIndexService")
@patch("controllers.service_api.dataset.segment.marshal")
@patch("controllers.service_api.dataset.segment.SegmentService")
@patch("controllers.service_api.dataset.segment.DocumentService")
@@ -781,7 +780,6 @@ class TestSegmentApiGet:
mock_doc_svc,
mock_seg_svc,
mock_marshal,
mock_summary_svc,
app,
mock_tenant,
mock_dataset,
@@ -793,8 +791,7 @@ class TestSegmentApiGet:
mock_db.session.scalar.return_value = mock_dataset
mock_doc_svc.get_document.return_value = Mock(doc_form=IndexStructureType.PARAGRAPH_INDEX)
mock_seg_svc.get_segments.return_value = ([mock_segment], 1)
mock_marshal.return_value = {"id": mock_segment.id}
mock_summary_svc.get_segments_summaries.return_value = {}
mock_marshal.return_value = [{"id": mock_segment.id}]
# Act
with app.test_request_context(
@@ -875,7 +872,6 @@ class TestSegmentApiPost:
mock_rate_limit.enabled = False
mock_feature_svc.get_knowledge_rate_limit.return_value = mock_rate_limit
@patch("controllers.service_api.dataset.segment.SummaryIndexService")
@patch("controllers.service_api.dataset.segment.marshal")
@patch("controllers.service_api.dataset.segment.SegmentService")
@patch("controllers.service_api.dataset.segment.DocumentService")
@@ -892,7 +888,6 @@ class TestSegmentApiPost:
mock_doc_svc,
mock_seg_svc,
mock_marshal,
mock_summary_svc,
app,
mock_tenant,
mock_dataset,
@@ -914,8 +909,7 @@ class TestSegmentApiPost:
mock_seg_svc.segment_create_args_validate.return_value = None
mock_seg_svc.multi_create_segment.return_value = [mock_segment]
mock_marshal.return_value = {"id": mock_segment.id}
mock_summary_svc.get_segments_summaries.return_value = {}
mock_marshal.return_value = [{"id": mock_segment.id}]
segments_data = [{"content": "Test segment content", "answer": "Test answer"}]
@@ -1212,7 +1206,6 @@ class TestDatasetSegmentApiUpdate:
mock_rate_limit.enabled = False
mock_feature_svc.get_knowledge_rate_limit.return_value = mock_rate_limit
@patch("controllers.service_api.dataset.segment.SummaryIndexService")
@patch("controllers.service_api.dataset.segment.marshal")
@patch("controllers.service_api.dataset.segment.SegmentService")
@patch("controllers.service_api.dataset.segment.DocumentService")
@@ -1231,7 +1224,6 @@ class TestDatasetSegmentApiUpdate:
mock_doc_svc,
mock_seg_svc,
mock_marshal,
mock_summary_svc,
app,
mock_tenant,
mock_dataset,
@@ -1248,7 +1240,6 @@ class TestDatasetSegmentApiUpdate:
updated = Mock()
mock_seg_svc.update_segment.return_value = updated
mock_marshal.return_value = {"id": mock_segment.id}
mock_summary_svc.get_segment_summary.return_value = None
with app.test_request_context(
f"/datasets/{mock_dataset.id}/documents/doc-id/segments/{mock_segment.id}",
@@ -1358,7 +1349,6 @@ class TestDatasetSegmentApiGetSingle:
``current_account_with_tenant()`` and ``marshal``.
"""
@patch("controllers.service_api.dataset.segment.SummaryIndexService")
@patch("controllers.service_api.dataset.segment.marshal")
@patch("controllers.service_api.dataset.segment.SegmentService")
@patch("controllers.service_api.dataset.segment.DocumentService")
@@ -1373,7 +1363,6 @@ class TestDatasetSegmentApiGetSingle:
mock_doc_svc,
mock_seg_svc,
mock_marshal,
mock_summary_svc,
app,
mock_tenant,
mock_dataset,
@@ -1387,7 +1376,6 @@ class TestDatasetSegmentApiGetSingle:
mock_doc_svc.get_document.return_value = mock_doc
mock_seg_svc.get_segment_by_id.return_value = mock_segment
mock_marshal.return_value = {"id": mock_segment.id}
mock_summary_svc.get_segment_summary.return_value = None
with app.test_request_context(
f"/datasets/{mock_dataset.id}/documents/doc-id/segments/{mock_segment.id}",
@@ -1405,55 +1393,6 @@ class TestDatasetSegmentApiGetSingle:
assert "data" in response
assert response["doc_form"] == IndexStructureType.PARAGRAPH_INDEX
@patch("controllers.service_api.dataset.segment.SummaryIndexService")
@patch("controllers.service_api.dataset.segment.marshal")
@patch("controllers.service_api.dataset.segment.SegmentService")
@patch("controllers.service_api.dataset.segment.DocumentService")
@patch("controllers.service_api.dataset.segment.DatasetService")
@patch("controllers.service_api.dataset.segment.current_account_with_tenant")
@patch("controllers.service_api.dataset.segment.db")
def test_get_single_segment_includes_summary(
self,
mock_db,
mock_account_fn,
mock_dataset_svc,
mock_doc_svc,
mock_seg_svc,
mock_marshal,
mock_summary_svc,
app,
mock_tenant,
mock_dataset,
mock_segment,
):
"""Test that single segment response includes summary content from SummaryIndexService."""
mock_account_fn.return_value = (Mock(), mock_tenant.id)
mock_db.session.scalar.return_value = mock_dataset
mock_dataset_svc.check_dataset_model_setting.return_value = None
mock_doc = Mock(doc_form=IndexStructureType.PARAGRAPH_INDEX)
mock_doc_svc.get_document.return_value = mock_doc
mock_seg_svc.get_segment_by_id.return_value = mock_segment
mock_marshal.return_value = {"id": mock_segment.id, "summary": None}
mock_summary_record = Mock()
mock_summary_record.summary_content = "This is the segment summary"
mock_summary_svc.get_segment_summary.return_value = mock_summary_record
with app.test_request_context(
f"/datasets/{mock_dataset.id}/documents/doc-id/segments/{mock_segment.id}",
method="GET",
):
api = DatasetSegmentApi()
response, status = api.get(
tenant_id=mock_tenant.id,
dataset_id=mock_dataset.id,
document_id="doc-id",
segment_id=mock_segment.id,
)
assert status == 200
assert response["data"]["summary"] == "This is the segment summary"
@patch("controllers.service_api.dataset.segment.current_account_with_tenant")
@patch("controllers.service_api.dataset.segment.db")
def test_get_single_segment_dataset_not_found(

View File

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

View File

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

View File

@@ -19,18 +19,15 @@ It tests:
- `uv`
- Docker
Run the following commands from the repository root.
Install Playwright browsers once:
```bash
cd e2e
pnpm install
pnpm -C e2e e2e:install
pnpm -C e2e check
pnpm e2e:install
pnpm 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.
Common commands:
@@ -38,20 +35,20 @@ Common commands:
```bash
# authenticated-only regression (default excludes @fresh)
# expects backend API, frontend artifact, and middleware stack to already be running
pnpm -C e2e e2e
pnpm e2e
# full reset + fresh install + authenticated scenarios
# starts required middleware/dependencies for you
pnpm -C e2e e2e:full
pnpm e2e:full
# run a tagged subset
pnpm -C e2e e2e -- --tags @smoke
pnpm e2e -- --tags @smoke
# headed browser
pnpm -C e2e e2e:headed -- --tags @smoke
pnpm e2e:headed -- --tags @smoke
# slow down browser actions for local debugging
E2E_SLOW_MO=500 pnpm -C e2e e2e:headed -- --tags @smoke
E2E_SLOW_MO=500 pnpm e2e:headed -- --tags @smoke
```
Frontend artifact behavior:
@@ -104,7 +101,7 @@ Because of that, the `@fresh` install scenario only runs in the `pnpm e2e:full*`
Reset all persisted E2E state:
```bash
pnpm -C e2e e2e:reset
pnpm e2e:reset
```
This removes:
@@ -120,7 +117,7 @@ This removes:
Start the full middleware stack:
```bash
pnpm -C e2e e2e:middleware:up
pnpm e2e:middleware:up
```
Stop the full middleware stack:

View File

@@ -14,11 +14,21 @@
"e2e:reset": "tsx ./scripts/setup.ts reset"
},
"devDependencies": {
"@cucumber/cucumber": "catalog:",
"@playwright/test": "catalog:",
"@types/node": "catalog:",
"tsx": "catalog:",
"typescript": "catalog:",
"vite-plus": "catalog:"
"@cucumber/cucumber": "12.7.0",
"@playwright/test": "1.51.1",
"@types/node": "25.5.0",
"tsx": "4.21.0",
"typescript": "5.9.3",
"vite-plus": "latest"
},
"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 Normal file

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,257 +0,0 @@
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,10 +100,6 @@ Notes:
- Chat/completion require a stable `user` identifier in the request payload.
- 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
This SDK is released under the MIT License.

View File

@@ -54,17 +54,24 @@
"publish:npm": "./scripts/publish.sh"
},
"dependencies": {
"axios": "catalog:"
"axios": "^1.13.6"
},
"devDependencies": {
"@eslint/js": "catalog:",
"@types/node": "catalog:",
"@typescript-eslint/eslint-plugin": "catalog:",
"@typescript-eslint/parser": "catalog:",
"@vitest/coverage-v8": "catalog:",
"eslint": "catalog:",
"tsup": "catalog:",
"typescript": "catalog:",
"vitest": "catalog:"
"@eslint/js": "^10.0.1",
"@types/node": "^25.4.0",
"@typescript-eslint/eslint-plugin": "^8.57.0",
"@typescript-eslint/parser": "^8.57.0",
"@vitest/coverage-v8": "4.0.18",
"eslint": "^10.0.3",
"tsup": "^8.5.1",
"typescript": "^5.9.3",
"vitest": "^4.0.18"
},
"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"
}
}
}

2255
sdks/nodejs-client/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

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

View File

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

32
web/.dockerignore Normal file
View File

@@ -0,0 +1,32 @@
.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

View File

@@ -19,27 +19,21 @@ ENV NEXT_PUBLIC_BASE_PATH="$NEXT_PUBLIC_BASE_PATH"
# install packages
FROM base AS packages
WORKDIR /app
WORKDIR /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/
COPY package.json pnpm-lock.yaml /app/web/
# Use packageManager from package.json
RUN corepack install
# 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
RUN pnpm install --frozen-lockfile
# build resources
FROM base AS builder
WORKDIR /app
COPY --from=packages /app/ .
WORKDIR /app/web
COPY --from=packages /app/web/ .
COPY . .
WORKDIR /app/web
ENV NODE_OPTIONS="--max-old-space-size=4096"
RUN pnpm build
@@ -70,13 +64,13 @@ RUN addgroup -S -g ${dify_uid} dify && \
chown -R dify:dify /app
WORKDIR /app
WORKDIR /app/web
COPY --from=builder --chown=dify:dify /app/web/public ./web/public
COPY --from=builder --chown=dify:dify /app/web/public ./public
COPY --from=builder --chown=dify:dify /app/web/.next/standalone ./
COPY --from=builder --chown=dify:dify /app/web/.next/static ./web/.next/static
COPY --from=builder --chown=dify:dify /app/web/.next/static ./.next/static
COPY --chown=dify:dify --chmod=755 web/docker/entrypoint.sh ./entrypoint.sh
COPY --chown=dify:dify --chmod=755 docker/entrypoint.sh ./entrypoint.sh
ARG COMMIT_SHA
ENV COMMIT_SHA=${COMMIT_SHA}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,6 +3,7 @@
"type": "module",
"version": "1.13.3",
"private": true,
"packageManager": "pnpm@10.32.1",
"imports": {
"#i18n": {
"react-server": "./i18n-config/lib.server.ts",
@@ -21,6 +22,9 @@
"and_uc >= 15.5",
"and_qq >= 14.9"
],
"engines": {
"node": "^22.22.1"
},
"scripts": {
"analyze": "next experimental-analyze",
"analyze-component": "node ./scripts/analyze-component.js",
@@ -54,189 +58,258 @@
"uglify-embed": "node ./bin/uglify-embed"
},
"dependencies": {
"@amplitude/analytics-browser": "catalog:",
"@amplitude/plugin-session-replay-browser": "catalog:",
"@base-ui/react": "catalog:",
"@emoji-mart/data": "catalog:",
"@floating-ui/react": "catalog:",
"@formatjs/intl-localematcher": "catalog:",
"@headlessui/react": "catalog:",
"@heroicons/react": "catalog:",
"@lexical/code": "catalog:",
"@lexical/link": "catalog:",
"@lexical/list": "catalog:",
"@lexical/react": "catalog:",
"@lexical/selection": "catalog:",
"@lexical/text": "catalog:",
"@lexical/utils": "catalog:",
"@monaco-editor/react": "catalog:",
"@orpc/client": "catalog:",
"@orpc/contract": "catalog:",
"@orpc/openapi-client": "catalog:",
"@orpc/tanstack-query": "catalog:",
"@remixicon/react": "catalog:",
"@sentry/react": "catalog:",
"@streamdown/math": "catalog:",
"@svgdotjs/svg.js": "catalog:",
"@t3-oss/env-nextjs": "catalog:",
"@tailwindcss/typography": "catalog:",
"@tanstack/react-form": "catalog:",
"@tanstack/react-query": "catalog:",
"abcjs": "catalog:",
"ahooks": "catalog:",
"class-variance-authority": "catalog:",
"clsx": "catalog:",
"cmdk": "catalog:",
"copy-to-clipboard": "catalog:",
"cron-parser": "catalog:",
"dayjs": "catalog:",
"decimal.js": "catalog:",
"dompurify": "catalog:",
"echarts": "catalog:",
"echarts-for-react": "catalog:",
"elkjs": "catalog:",
"embla-carousel-autoplay": "catalog:",
"embla-carousel-react": "catalog:",
"emoji-mart": "catalog:",
"es-toolkit": "catalog:",
"fast-deep-equal": "catalog:",
"foxact": "catalog:",
"html-entities": "catalog:",
"html-to-image": "catalog:",
"i18next": "catalog:",
"i18next-resources-to-backend": "catalog:",
"immer": "catalog:",
"jotai": "catalog:",
"js-audio-recorder": "catalog:",
"js-cookie": "catalog:",
"js-yaml": "catalog:",
"jsonschema": "catalog:",
"katex": "catalog:",
"ky": "catalog:",
"lamejs": "catalog:",
"lexical": "catalog:",
"mermaid": "catalog:",
"mime": "catalog:",
"mitt": "catalog:",
"negotiator": "catalog:",
"next": "catalog:",
"next-themes": "catalog:",
"nuqs": "catalog:",
"pinyin-pro": "catalog:",
"qrcode.react": "catalog:",
"qs": "catalog:",
"react": "catalog:",
"react-18-input-autosize": "catalog:",
"react-dom": "catalog:",
"react-easy-crop": "catalog:",
"react-hotkeys-hook": "catalog:",
"react-i18next": "catalog:",
"react-multi-email": "catalog:",
"react-papaparse": "catalog:",
"react-pdf-highlighter": "catalog:",
"react-sortablejs": "catalog:",
"react-syntax-highlighter": "catalog:",
"react-textarea-autosize": "catalog:",
"react-window": "catalog:",
"reactflow": "catalog:",
"remark-breaks": "catalog:",
"remark-directive": "catalog:",
"scheduler": "catalog:",
"sharp": "catalog:",
"sortablejs": "catalog:",
"std-semver": "catalog:",
"streamdown": "catalog:",
"string-ts": "catalog:",
"tailwind-merge": "catalog:",
"tldts": "catalog:",
"unist-util-visit": "catalog:",
"use-context-selector": "catalog:",
"uuid": "catalog:",
"zod": "catalog:",
"zundo": "catalog:",
"zustand": "catalog:"
"@amplitude/analytics-browser": "2.37.0",
"@amplitude/plugin-session-replay-browser": "1.27.1",
"@base-ui/react": "1.3.0",
"@emoji-mart/data": "1.2.1",
"@floating-ui/react": "0.27.19",
"@formatjs/intl-localematcher": "0.8.2",
"@headlessui/react": "2.2.9",
"@heroicons/react": "2.2.0",
"@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",
"@monaco-editor/react": "4.7.0",
"@orpc/client": "1.13.9",
"@orpc/contract": "1.13.9",
"@orpc/openapi-client": "1.13.9",
"@orpc/tanstack-query": "1.13.9",
"@remixicon/react": "4.9.0",
"@sentry/react": "10.45.0",
"@streamdown/math": "1.0.2",
"@svgdotjs/svg.js": "3.2.5",
"@t3-oss/env-nextjs": "0.13.11",
"@tailwindcss/typography": "0.5.19",
"@tanstack/react-form": "1.28.5",
"@tanstack/react-query": "5.95.0",
"abcjs": "6.6.2",
"ahooks": "3.9.6",
"class-variance-authority": "0.7.1",
"clsx": "2.1.1",
"cmdk": "1.1.1",
"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",
"fast-deep-equal": "3.1.3",
"foxact": "0.3.0",
"html-entities": "2.6.0",
"html-to-image": "1.11.13",
"i18next": "25.10.4",
"i18next-resources-to-backend": "1.2.1",
"immer": "11.1.4",
"jotai": "2.18.1",
"js-audio-recorder": "1.0.7",
"js-cookie": "3.0.5",
"js-yaml": "4.1.1",
"jsonschema": "1.5.0",
"katex": "0.16.40",
"ky": "1.14.3",
"lamejs": "1.2.1",
"lexical": "0.42.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",
"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.6",
"react-hotkeys-hook": "5.2.4",
"react-i18next": "16.6.1",
"react-multi-email": "1.0.25",
"react-papaparse": "4.4.0",
"react-pdf-highlighter": "8.0.0-rc.0",
"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",
"scheduler": "0.27.0",
"sharp": "0.34.5",
"sortablejs": "1.15.7",
"std-semver": "1.0.8",
"streamdown": "2.5.0",
"string-ts": "2.3.1",
"tailwind-merge": "2.6.1",
"tldts": "7.0.27",
"unist-util-visit": "5.1.0",
"use-context-selector": "2.0.0",
"uuid": "13.0.0",
"zod": "4.3.6",
"zundo": "2.3.0",
"zustand": "5.0.12"
},
"devDependencies": {
"@antfu/eslint-config": "catalog:",
"@chromatic-com/storybook": "catalog:",
"@egoist/tailwindcss-icons": "catalog:",
"@eslint-react/eslint-plugin": "catalog:",
"@hono/node-server": "catalog:",
"@iconify-json/heroicons": "catalog:",
"@iconify-json/ri": "catalog:",
"@mdx-js/loader": "catalog:",
"@mdx-js/react": "catalog:",
"@mdx-js/rollup": "catalog:",
"@next/eslint-plugin-next": "catalog:",
"@next/mdx": "catalog:",
"@rgrove/parse-xml": "catalog:",
"@storybook/addon-docs": "catalog:",
"@storybook/addon-links": "catalog:",
"@storybook/addon-onboarding": "catalog:",
"@storybook/addon-themes": "catalog:",
"@storybook/nextjs-vite": "catalog:",
"@storybook/react": "catalog:",
"@tanstack/eslint-plugin-query": "catalog:",
"@tanstack/react-devtools": "catalog:",
"@tanstack/react-form-devtools": "catalog:",
"@tanstack/react-query-devtools": "catalog:",
"@testing-library/dom": "catalog:",
"@testing-library/jest-dom": "catalog:",
"@testing-library/react": "catalog:",
"@testing-library/user-event": "catalog:",
"@tsslint/cli": "catalog:",
"@tsslint/compat-eslint": "catalog:",
"@tsslint/config": "catalog:",
"@types/js-cookie": "catalog:",
"@types/js-yaml": "catalog:",
"@types/negotiator": "catalog:",
"@types/node": "catalog:",
"@types/postcss-js": "catalog:",
"@types/qs": "catalog:",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"@types/react-syntax-highlighter": "catalog:",
"@types/react-window": "catalog:",
"@types/sortablejs": "catalog:",
"@typescript-eslint/parser": "catalog:",
"@typescript/native-preview": "catalog:",
"@vitejs/plugin-react": "catalog:",
"@vitejs/plugin-rsc": "catalog:",
"@vitest/coverage-v8": "catalog:",
"agentation": "catalog:",
"autoprefixer": "catalog:",
"code-inspector-plugin": "catalog:",
"eslint": "catalog:",
"eslint-markdown": "catalog:",
"eslint-plugin-better-tailwindcss": "catalog:",
"eslint-plugin-hyoban": "catalog:",
"eslint-plugin-markdown-preferences": "catalog:",
"eslint-plugin-no-barrel-files": "catalog:",
"eslint-plugin-react-hooks": "catalog:",
"eslint-plugin-react-refresh": "catalog:",
"eslint-plugin-sonarjs": "catalog:",
"eslint-plugin-storybook": "catalog:",
"happy-dom": "catalog:",
"hono": "catalog:",
"husky": "catalog:",
"iconify-import-svg": "catalog:",
"knip": "catalog:",
"lint-staged": "catalog:",
"postcss": "catalog:",
"postcss-js": "catalog:",
"react-server-dom-webpack": "catalog:",
"sass": "catalog:",
"storybook": "catalog:",
"tailwindcss": "catalog:",
"tsx": "catalog:",
"typescript": "catalog:",
"uglify-js": "catalog:",
"vinext": "catalog:",
"vite": "catalog:",
"vite-plugin-inspect": "catalog:",
"vite-plus": "catalog:",
"vitest": "catalog:",
"vitest-canvas-mock": "catalog:"
"@antfu/eslint-config": "7.7.3",
"@chromatic-com/storybook": "5.0.2",
"@egoist/tailwindcss-icons": "1.9.2",
"@eslint-react/eslint-plugin": "3.0.0",
"@hono/node-server": "1.19.11",
"@iconify-json/heroicons": "1.2.3",
"@iconify-json/ri": "1.2.10",
"@mdx-js/loader": "3.1.1",
"@mdx-js/react": "3.1.1",
"@mdx-js/rollup": "3.1.1",
"@next/eslint-plugin-next": "16.2.1",
"@next/mdx": "16.2.1",
"@rgrove/parse-xml": "4.2.0",
"@storybook/addon-docs": "10.3.1",
"@storybook/addon-links": "10.3.1",
"@storybook/addon-onboarding": "10.3.1",
"@storybook/addon-themes": "10.3.1",
"@storybook/nextjs-vite": "10.3.1",
"@storybook/react": "10.3.1",
"@tanstack/eslint-plugin-query": "5.95.0",
"@tanstack/react-devtools": "0.10.0",
"@tanstack/react-form-devtools": "0.2.19",
"@tanstack/react-query-devtools": "5.95.0",
"@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/parser": "8.57.1",
"@typescript/native-preview": "7.0.0-dev.20260322.1",
"@vitejs/plugin-react": "6.0.1",
"@vitejs/plugin-rsc": "0.5.21",
"@vitest/coverage-v8": "4.1.0",
"agentation": "2.3.3",
"autoprefixer": "10.4.27",
"code-inspector-plugin": "1.4.5",
"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.1",
"happy-dom": "20.8.9",
"hono": "4.12.8",
"husky": "9.1.7",
"iconify-import-svg": "0.1.2",
"knip": "6.0.2",
"lint-staged": "16.4.0",
"postcss": "8.5.8",
"postcss-js": "5.1.0",
"react-server-dom-webpack": "19.2.4",
"sass": "1.98.0",
"storybook": "10.3.1",
"tailwindcss": "3.4.19",
"taze": "19.10.0",
"tsx": "4.21.0",
"typescript": "5.9.3",
"uglify-js": "3.19.3",
"vinext": "https://pkg.pr.new/vinext@b6a2cac",
"vite": "npm:@voidzero-dev/vite-plus-core@0.1.13",
"vite-plugin-inspect": "11.3.3",
"vite-plus": "0.1.13",
"vitest": "npm:@voidzero-dev/vite-plus-test@0.1.13",
"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": {
"*": "eslint --fix --pass-on-unpruned-suppressions"

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,21 @@ import { spawn } from 'node:child_process'
import { cp, mkdir, stat } from 'node:fs/promises'
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
const pathExists = async (path) => {
try {
@@ -25,23 +40,6 @@ 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
const copyDir = async (src, dest) => {
console.debug(`Copying directory from ${src} to ${dest}`)
@@ -50,20 +48,9 @@ const copyDir = async (src, dest) => {
}
// Process each directory copy operation
const copyAllDirs = async (standaloneRoot) => {
const dirsToCopy = [
{
src: path.join('.next', 'static'),
dest: path.join(standaloneRoot, '.next', 'static'),
},
{
src: 'public',
dest: path.join(standaloneRoot, 'public'),
},
]
const copyAllDirs = async () => {
console.debug('Starting directory copy operations')
for (const { src, dest } of dirsToCopy) {
for (const { src, dest } of DIRS_TO_COPY) {
try {
// Instead of pre-creating destination directory, we ensure parent directory exists
const destParent = path.dirname(dest)
@@ -88,22 +75,19 @@ const copyAllDirs = async (standaloneRoot) => {
// Run copy operations and start server
const main = async () => {
console.debug('Starting copy-and-start script')
const standaloneRoot = await getStandaloneRoot()
const serverScriptPath = path.join(standaloneRoot, 'server.js')
await copyAllDirs(standaloneRoot)
await copyAllDirs()
// Start server
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'
console.info(`Starting server on ${host}:${port}`)
console.debug(`Server script path: ${serverScriptPath}`)
console.debug(`Server script path: ${SERVER_SCRIPT_PATH}`)
console.debug(`Environment variables - PORT: ${port}, HOSTNAME: ${host}`)
const server = spawn(
process.execPath,
[serverScriptPath],
[SERVER_SCRIPT_PATH],
{
env: {
...process.env,

View File

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

View File

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