@lobehub/chat 1.141.6 โ†’ 1.141.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/.github/PULL_REQUEST_TEMPLATE.md +26 -0
  2. package/.github/workflows/e2e.yml +6 -6
  3. package/CHANGELOG.md +25 -0
  4. package/changelog/v1.json +9 -0
  5. package/e2e/README.md +143 -0
  6. package/e2e/cucumber.config.js +20 -0
  7. package/e2e/package.json +24 -0
  8. package/e2e/src/features/discover/smoke.feature +11 -0
  9. package/e2e/src/features/routes/core-routes.feature +43 -0
  10. package/e2e/src/steps/common/navigation.steps.ts +36 -0
  11. package/e2e/src/steps/discover/smoke.steps.ts +34 -0
  12. package/e2e/src/steps/hooks.ts +69 -0
  13. package/e2e/src/steps/routes/routes.steps.ts +41 -0
  14. package/e2e/src/support/webServer.ts +96 -0
  15. package/e2e/src/support/world.ts +76 -0
  16. package/e2e/tsconfig.json +19 -0
  17. package/package.json +6 -3
  18. package/packages/const/src/layoutTokens.ts +1 -1
  19. package/packages/database/src/models/__tests__/session.test.ts +108 -0
  20. package/packages/database/src/models/session.ts +41 -1
  21. package/packages/model-bank/src/aiModels/groq.ts +0 -17
  22. package/packages/model-bank/src/aiModels/novita.ts +2 -60
  23. package/packages/model-bank/src/aiModels/siliconcloud.ts +116 -17
  24. package/pnpm-workspace.yaml +1 -0
  25. package/src/app/[variants]/(main)/discover/(list)/assistant/features/List/Item.tsx +1 -0
  26. package/src/app/[variants]/(main)/discover/DiscoverRouter.tsx +12 -10
  27. package/src/app/[variants]/(main)/discover/[[...path]]/page.tsx +7 -6
  28. package/src/app/[variants]/(main)/discover/features/Search.tsx +1 -0
  29. package/src/components/Loading/index.ts +1 -0
  30. package/src/features/AgentSetting/AgentModal/index.tsx +262 -35
  31. package/src/features/ChatInput/ActionBar/Params/Controls.tsx +261 -50
  32. package/src/features/ModelParamsControl/FrequencyPenalty.tsx +8 -3
  33. package/src/features/ModelParamsControl/PresencePenalty.tsx +8 -3
  34. package/src/features/ModelParamsControl/Temperature.tsx +8 -5
  35. package/src/features/ModelParamsControl/TopP.tsx +8 -3
  36. package/src/services/chat/index.ts +6 -0
  37. package/e2e/routes.spec.ts +0 -73
  38. package/playwright.config.ts +0 -35
@@ -12,10 +12,36 @@
12
12
  - [ ] ๐Ÿ“ docs
13
13
  - [ ] ๐Ÿ”จ chore
14
14
 
15
+ #### ๐Ÿ”— Related Issue
16
+
17
+ <!-- Link to the issue that is fixed by this PR -->
18
+
19
+ <!-- Example: Fixes #123, Closes #456, Related to #789 -->
20
+
15
21
  #### ๐Ÿ”€ Description of Change
16
22
 
17
23
  <!-- Thank you for your Pull Request. Please provide a description above. -->
18
24
 
25
+ #### ๐Ÿงช How to Test
26
+
27
+ <!-- Please describe how you tested your changes -->
28
+
29
+ <!-- For AI features, please include test prompts or scenarios -->
30
+
31
+ - [ ] Tested locally
32
+ - [ ] Added/updated tests
33
+ - [ ] No tests needed
34
+
35
+ #### ๐Ÿ“ธ Screenshots / Videos
36
+
37
+ <!-- If this PR includes UI changes, please provide screenshots or videos -->
38
+
39
+ | Before | After |
40
+ | ------ | ----- |
41
+ | ... | ... |
42
+
19
43
  #### ๐Ÿ“ Additional Information
20
44
 
21
45
  <!-- Add any other context about the Pull Request here. -->
46
+
47
+ <!-- Breaking changes? Migration guide? Performance impact? -->
@@ -35,18 +35,18 @@ jobs:
35
35
  PORT: 3010
36
36
  run: bun run e2e
37
37
 
38
- - name: Upload Playwright HTML report (on failure)
38
+ - name: Upload Cucumber HTML report (on failure)
39
39
  if: failure()
40
40
  uses: actions/upload-artifact@v4
41
41
  with:
42
- name: playwright-report
43
- path: playwright-report
42
+ name: cucumber-report
43
+ path: e2e/reports
44
44
  if-no-files-found: ignore
45
45
 
46
- - name: Upload Playwright traces (on failure)
46
+ - name: Upload screenshots (on failure)
47
47
  if: failure()
48
48
  uses: actions/upload-artifact@v4
49
49
  with:
50
- name: test-results
51
- path: test-results
50
+ name: test-screenshots
51
+ path: e2e/screenshots
52
52
  if-no-files-found: ignore
package/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 1.141.7](https://github.com/lobehub/lobe-chat/compare/v1.141.6...v1.141.7)
6
+
7
+ <sup>Released on **2025-10-23**</sup>
8
+
9
+ #### ๐Ÿ’„ Styles
10
+
11
+ - **misc**: Allow removal of `top_p` and similar request parameters.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### Styles
19
+
20
+ - **misc**: Allow removal of `top_p` and similar request parameters, closes [#9498](https://github.com/lobehub/lobe-chat/issues/9498) ([4c313ce](https://github.com/lobehub/lobe-chat/commit/4c313ce))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
5
30
  ### [Version 1.141.6](https://github.com/lobehub/lobe-chat/compare/v1.141.5...v1.141.6)
6
31
 
7
32
  <sup>Released on **2025-10-22**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,13 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "improvements": [
5
+ "Allow removal of top_p and similar request parameters."
6
+ ]
7
+ },
8
+ "date": "2025-10-23",
9
+ "version": "1.141.7"
10
+ },
2
11
  {
3
12
  "children": {},
4
13
  "date": "2025-10-22",
package/e2e/README.md ADDED
@@ -0,0 +1,143 @@
1
+ # E2E Tests for LobeChat
2
+
3
+ This directory contains end-to-end (E2E) tests for LobeChat using Cucumber (BDD) and Playwright.
4
+
5
+ ## Directory Structure
6
+
7
+ ````
8
+ e2e/
9
+ โ”œโ”€โ”€ src/ # Source files
10
+ โ”‚ โ”œโ”€โ”€ features/ # Gherkin feature files
11
+ โ”‚ โ”‚ โ””โ”€โ”€ discover/ # Discover page tests
12
+ โ”‚ โ”œโ”€โ”€ steps/ # Step definitions
13
+ โ”‚ โ”‚ โ”œโ”€โ”€ common/ # Reusable step definitions
14
+ โ”‚ โ”‚ โ””โ”€โ”€ discover/ # Discover-specific steps
15
+ โ”‚ โ””โ”€โ”€ support/ # Test support files
16
+ โ”‚ โ””โ”€โ”€ world.ts # Custom World context
17
+ โ”œโ”€โ”€ reports/ # Test reports (generated)
18
+ โ”œโ”€โ”€ cucumber.config.js # Cucumber configuration
19
+ โ”œโ”€โ”€ tsconfig.json # TypeScript configuration
20
+ โ””โ”€โ”€ package.json # Dependencies and scripts
21
+
22
+ ## Prerequisites
23
+
24
+ - Node.js 20, 22, or >=24
25
+ - Dev server running on `http://localhost:3010` (or set `BASE_URL` env var)
26
+
27
+ ## Installation
28
+
29
+ Install dependencies:
30
+
31
+ ```bash
32
+ cd e2e
33
+ pnpm install
34
+ ````
35
+
36
+ Install Playwright browsers:
37
+
38
+ ```bash
39
+ npx playwright install chromium
40
+ ```
41
+
42
+ ## Running Tests
43
+
44
+ Run all tests:
45
+
46
+ ```bash
47
+ npm test
48
+ ```
49
+
50
+ Run tests in headed mode (see browser):
51
+
52
+ ```bash
53
+ npm run test:headed
54
+ ```
55
+
56
+ Run only smoke tests:
57
+
58
+ ```bash
59
+ npm run test:smoke
60
+ ```
61
+
62
+ Run discover tests:
63
+
64
+ ```bash
65
+ npm run test:discover
66
+ ```
67
+
68
+ ## Environment Variables
69
+
70
+ - `BASE_URL`: Base URL for the application (default: `http://localhost:3010`)
71
+ - `PORT`: Port number (default: `3010`)
72
+ - `HEADLESS`: Run browser in headless mode (default: `true`, set to `false` to see browser)
73
+
74
+ Example:
75
+
76
+ ```bash
77
+ HEADLESS=false BASE_URL=http://localhost:3000 npm run test:smoke
78
+ ```
79
+
80
+ ## Writing Tests
81
+
82
+ ### Feature Files
83
+
84
+ Feature files are written in Gherkin syntax and placed in the `src/features/` directory:
85
+
86
+ ```gherkin
87
+ @discover @smoke
88
+ Feature: Discover Smoke Tests
89
+ Critical path tests to ensure the discover module is functional
90
+
91
+ @DISCOVER-SMOKE-001 @P0
92
+ Scenario: Load discover assistant list page
93
+ Given I navigate to "/discover/assistant"
94
+ Then the page should load without errors
95
+ And I should see the page body
96
+ And I should see the search bar
97
+ And I should see assistant cards
98
+ ```
99
+
100
+ ### Step Definitions
101
+
102
+ Step definitions are TypeScript files in the `src/steps/` directory that implement the steps from feature files:
103
+
104
+ ```typescript
105
+ import { Given, Then } from '@cucumber/cucumber';
106
+ import { expect } from '@playwright/test';
107
+
108
+ import { CustomWorld } from '../../support/world';
109
+
110
+ Given('I navigate to {string}', async function (this: CustomWorld, path: string) {
111
+ await this.page.goto(path);
112
+ await this.page.waitForLoadState('domcontentloaded');
113
+ });
114
+ ```
115
+
116
+ ## Test Reports
117
+
118
+ After running tests, HTML and JSON reports are generated in the `reports/` directory:
119
+
120
+ - `reports/cucumber-report.html` - Human-readable HTML report
121
+ - `reports/cucumber-report.json` - Machine-readable JSON report
122
+
123
+ ## Troubleshooting
124
+
125
+ ### Browser not found
126
+
127
+ If you see errors about missing browser executables:
128
+
129
+ ```bash
130
+ npx playwright install chromium
131
+ ```
132
+
133
+ ### Port already in use
134
+
135
+ Make sure the dev server is running on the expected port (3010 by default), or set `PORT` or `BASE_URL` environment variable.
136
+
137
+ ### Test timeout
138
+
139
+ Increase timeout in `cucumber.config.js` or `src/steps/hooks.ts`:
140
+
141
+ ```typescript
142
+ setDefaultTimeout(120000); // 2 minutes
143
+ ```
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @type {import('@cucumber/cucumber').IConfiguration}
3
+ */
4
+ export default {
5
+ format: [
6
+ 'progress-bar',
7
+ 'html:reports/cucumber-report.html',
8
+ 'json:reports/cucumber-report.json',
9
+ ],
10
+ formatOptions: {
11
+ snippetInterface: 'async-await',
12
+ },
13
+ parallel: process.env.CI ? 1 : 4,
14
+ paths: ['src/features/**/*.feature'],
15
+ publishQuiet: true,
16
+ require: ['src/steps/**/*.ts', 'src/support/**/*.ts'],
17
+ requireModule: ['tsx/cjs'],
18
+ retry: 0,
19
+ timeout: 120_000,
20
+ };
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@lobechat/e2e-tests",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "description": "E2E tests for LobeChat using Cucumber and Playwright",
6
+ "scripts": {
7
+ "test": "cucumber-js --config cucumber.config.js",
8
+ "test:discover": "cucumber-js --config cucumber.config.js src/features/discover/",
9
+ "test:headed": "HEADLESS=false cucumber-js --config cucumber.config.js",
10
+ "test:routes": "cucumber-js --config cucumber.config.js --tags '@routes'",
11
+ "test:routes:ci": "cucumber-js --config cucumber.config.js --tags '@routes and not @ci-skip'",
12
+ "test:smoke": "cucumber-js --config cucumber.config.js --tags '@smoke'"
13
+ },
14
+ "dependencies": {
15
+ "@cucumber/cucumber": "^12.2.0",
16
+ "@playwright/test": "^1.56.1",
17
+ "playwright": "^1.56.1"
18
+ },
19
+ "devDependencies": {
20
+ "@types/node": "^22.10.5",
21
+ "tsx": "^4.20.6",
22
+ "typescript": "^5.7.3"
23
+ }
24
+ }
@@ -0,0 +1,11 @@
1
+ @discover @smoke
2
+ Feature: Discover Smoke Tests
3
+ Critical path tests to ensure the discover module is functional
4
+
5
+ @DISCOVER-SMOKE-001 @P0
6
+ Scenario: Load discover assistant list page
7
+ Given I navigate to "/discover/assistant"
8
+ Then the page should load without errors
9
+ And I should see the page body
10
+ And I should see the search bar
11
+ And I should see assistant cards
@@ -0,0 +1,43 @@
1
+ @routes @smoke
2
+ Feature: Core Routes Accessibility
3
+ As a user
4
+ I want all core application routes to be accessible
5
+ So that I can navigate the application without errors
6
+
7
+ Background:
8
+ Given the application is running
9
+
10
+ @ROUTES-001 @P0
11
+ Scenario Outline: Access core routes without errors
12
+ When I navigate to "<route>"
13
+ Then the response status should be less than 400
14
+ And the page should load without errors
15
+ And I should see the page body
16
+ And the page title should not contain "error" or "not found"
17
+
18
+ Examples:
19
+ | route |
20
+ | / |
21
+ | /chat |
22
+ | /discover |
23
+ | /files |
24
+ | /repos |
25
+
26
+ @ROUTES-002 @P0
27
+ Scenario Outline: Access settings routes without errors
28
+ When I navigate to "/settings?active=<tab>"
29
+ Then the response status should be less than 400
30
+ And the page should load without errors
31
+ And I should see the page body
32
+ And the page title should not contain "error" or "not found"
33
+
34
+ Examples:
35
+ | tab |
36
+ | about |
37
+ | agent |
38
+ | hotkey |
39
+ | provider |
40
+ | proxy |
41
+ | storage |
42
+ | system-agent |
43
+ | tts |
@@ -0,0 +1,36 @@
1
+ import { Given, Then } from '@cucumber/cucumber';
2
+ import { expect } from '@playwright/test';
3
+
4
+ import { CustomWorld } from '../../support/world';
5
+
6
+ // ============================================
7
+ // Given Steps (Preconditions)
8
+ // ============================================
9
+
10
+ Given('I navigate to {string}', async function (this: CustomWorld, path: string) {
11
+ const response = await this.page.goto(path, { waitUntil: 'commit' });
12
+ this.testContext.lastResponse = response;
13
+ await this.page.waitForLoadState('domcontentloaded');
14
+ });
15
+
16
+ // ============================================
17
+ // Then Steps (Assertions)
18
+ // ============================================
19
+
20
+ Then('the page should load without errors', async function (this: CustomWorld) {
21
+ // Check for no JavaScript errors
22
+ expect(this.testContext.jsErrors).toHaveLength(0);
23
+
24
+ // Check page didn't navigate to error page
25
+ const url = this.page.url();
26
+ expect(url).not.toMatch(/\/404|\/error|not-found/i);
27
+
28
+ // Check no error title
29
+ const title = await this.page.title();
30
+ expect(title).not.toMatch(/not found|error/i);
31
+ });
32
+
33
+ Then('I should see the page body', async function (this: CustomWorld) {
34
+ const body = this.page.locator('body');
35
+ await expect(body).toBeVisible();
36
+ });
@@ -0,0 +1,34 @@
1
+ import { Then } from '@cucumber/cucumber';
2
+ import { expect } from '@playwright/test';
3
+
4
+ import { CustomWorld } from '../../support/world';
5
+
6
+ // ============================================
7
+ // Then Steps (Assertions)
8
+ // ============================================
9
+
10
+ Then('I should see the search bar', async function (this: CustomWorld) {
11
+ // Wait for network to be idle to ensure Suspense components are loaded
12
+ await this.page.waitForLoadState('networkidle', { timeout: 120_000 });
13
+
14
+ // The SearchBar component from @lobehub/ui may not pass through data-testid
15
+ // Try to find the input element within the search component
16
+ const searchBar = this.page.locator('input[type="text"]').first();
17
+ await expect(searchBar).toBeVisible({ timeout: 120_000 });
18
+ });
19
+
20
+ Then('I should see assistant cards', async function (this: CustomWorld) {
21
+ // Wait for content to load
22
+ await this.page.waitForLoadState('networkidle', { timeout: 120_000 });
23
+
24
+ // After migrating to SPA (react-router), links use relative paths like /assistant/:id
25
+ // Look for assistant items by data-testid instead of href
26
+ const assistantItems = this.page.locator('[data-testid="assistant-item"]');
27
+
28
+ // Wait for at least one item to be visible
29
+ await expect(assistantItems.first()).toBeVisible({ timeout: 120_000 });
30
+
31
+ // Check we have multiple items
32
+ const count = await assistantItems.count();
33
+ expect(count).toBeGreaterThan(0);
34
+ });
@@ -0,0 +1,69 @@
1
+ import { After, AfterAll, Before, BeforeAll, Status, setDefaultTimeout } from '@cucumber/cucumber';
2
+
3
+ import { startWebServer, stopWebServer } from '../support/webServer';
4
+ import { CustomWorld } from '../support/world';
5
+
6
+ // Set default timeout for all steps to 120 seconds
7
+ setDefaultTimeout(120_000);
8
+
9
+ BeforeAll({ timeout: 120_000 }, async function () {
10
+ console.log('๐Ÿš€ Starting E2E test suite...');
11
+
12
+ const PORT = process.env.PORT ? Number(process.env.PORT) : 3010;
13
+ const BASE_URL = process.env.BASE_URL || `http://localhost:${PORT}`;
14
+
15
+ console.log(`Base URL: ${BASE_URL}`);
16
+
17
+ // Start web server if not using external BASE_URL
18
+ if (!process.env.BASE_URL) {
19
+ await startWebServer({
20
+ command: 'npm run dev',
21
+ port: PORT,
22
+ reuseExistingServer: !process.env.CI,
23
+ });
24
+ }
25
+ });
26
+
27
+ Before(async function (this: CustomWorld, { pickle }) {
28
+ await this.init();
29
+
30
+ const testId = pickle.tags.find((tag) => tag.name.startsWith('@DISCOVER-'));
31
+ console.log(`\n๐Ÿ“ Running: ${pickle.name}${testId ? ` (${testId.name.replace('@', '')})` : ''}`);
32
+ });
33
+
34
+ After(async function (this: CustomWorld, { pickle, result }) {
35
+ const testId = pickle.tags
36
+ .find((tag) => tag.name.startsWith('@DISCOVER-'))
37
+ ?.name.replace('@', '');
38
+
39
+ if (result?.status === Status.FAILED) {
40
+ const screenshot = await this.takeScreenshot(`${testId || 'failure'}-${Date.now()}`);
41
+ this.attach(screenshot, 'image/png');
42
+
43
+ const html = await this.page.content();
44
+ this.attach(html, 'text/html');
45
+
46
+ if (this.testContext.jsErrors.length > 0) {
47
+ const errors = this.testContext.jsErrors.map((e) => e.message).join('\n');
48
+ this.attach(`JavaScript Errors:\n${errors}`, 'text/plain');
49
+ }
50
+
51
+ console.log(`โŒ Failed: ${pickle.name}`);
52
+ if (result.message) {
53
+ console.log(` Error: ${result.message}`);
54
+ }
55
+ } else if (result?.status === Status.PASSED) {
56
+ console.log(`โœ… Passed: ${pickle.name}`);
57
+ }
58
+
59
+ await this.cleanup();
60
+ });
61
+
62
+ AfterAll(async function () {
63
+ console.log('\n๐Ÿ Test suite completed');
64
+
65
+ // Stop web server if we started it
66
+ if (!process.env.BASE_URL && process.env.CI) {
67
+ await stopWebServer();
68
+ }
69
+ });
@@ -0,0 +1,41 @@
1
+ import { Given, Then } from '@cucumber/cucumber';
2
+ import { expect } from '@playwright/test';
3
+
4
+ import { CustomWorld } from '../../support/world';
5
+
6
+ // ============================================
7
+ // Given Steps (Preconditions)
8
+ // ============================================
9
+
10
+ Given('the application is running', async function (this: CustomWorld) {
11
+ // This is a placeholder step to indicate that the app should be running
12
+ // The actual server startup is handled outside the test (in CI or locally)
13
+ // We just verify we can reach the base URL
14
+ const response = await this.page.goto('/');
15
+ expect(response).toBeTruthy();
16
+ // Store the response for later assertions
17
+ this.testContext.lastResponse = response;
18
+ });
19
+
20
+ // ============================================
21
+ // Then Steps (Assertions)
22
+ // ============================================
23
+
24
+ Then(
25
+ 'the response status should be less than {int}',
26
+ async function (this: CustomWorld, maxStatus: number) {
27
+ const status = this.testContext.lastResponse?.status() ?? 0;
28
+ expect(status, `Expected status < ${maxStatus}, but got ${status}`).toBeLessThan(maxStatus);
29
+ },
30
+ );
31
+
32
+ Then(
33
+ 'the page title should not contain {string} or {string}',
34
+ async function (this: CustomWorld, text1: string, text2: string) {
35
+ const title = await this.page.title();
36
+ const regex = new RegExp(`${text1}|${text2}`, 'i');
37
+ expect(title, `Page title "${title}" should not contain "${text1}" or "${text2}"`).not.toMatch(
38
+ regex,
39
+ );
40
+ },
41
+ );
@@ -0,0 +1,96 @@
1
+ import { type ChildProcess, exec } from 'node:child_process';
2
+ import { resolve } from 'node:path';
3
+
4
+ let serverProcess: ChildProcess | null = null;
5
+ let serverStartPromise: Promise<void> | null = null;
6
+
7
+ interface WebServerOptions {
8
+ command: string;
9
+ env?: Record<string, string>;
10
+ port: number;
11
+ reuseExistingServer?: boolean;
12
+ timeout?: number;
13
+ }
14
+
15
+ async function isServerRunning(port: number): Promise<boolean> {
16
+ try {
17
+ const response = await fetch(`http://localhost:${port}/chat`, {
18
+ method: 'HEAD',
19
+ });
20
+ return response.ok;
21
+ } catch {
22
+ return false;
23
+ }
24
+ }
25
+
26
+ export async function startWebServer(options: WebServerOptions): Promise<void> {
27
+ const { command, port, timeout = 120_000, env = {}, reuseExistingServer = true } = options;
28
+
29
+ // If server is already being started by another worker, wait for it
30
+ if (serverStartPromise) {
31
+ console.log(`โณ Waiting for server to start (started by another worker)...`);
32
+ return serverStartPromise;
33
+ }
34
+
35
+ // Check if server is already running
36
+ if (reuseExistingServer && (await isServerRunning(port))) {
37
+ console.log(`โœ… Reusing existing server on port ${port}`);
38
+ return;
39
+ }
40
+
41
+ // Create a promise for the server startup and store it
42
+ serverStartPromise = (async () => {
43
+ console.log(`๐Ÿš€ Starting web server: ${command}`);
44
+
45
+ // Get the project root directory (parent of e2e folder)
46
+ const projectRoot = resolve(__dirname, '../../..');
47
+
48
+ // Start the server process
49
+ serverProcess = exec(command, {
50
+ cwd: projectRoot,
51
+ env: {
52
+ ...process.env,
53
+ ENABLE_AUTH_PROTECTION: '0',
54
+ ENABLE_OIDC: '0',
55
+ NEXT_PUBLIC_ENABLE_CLERK_AUTH: '0',
56
+ NEXT_PUBLIC_ENABLE_NEXT_AUTH: '0',
57
+ NODE_OPTIONS: '--max-old-space-size=6144',
58
+ PORT: String(port),
59
+ ...env,
60
+ },
61
+ });
62
+
63
+ // Forward server output to console for debugging
64
+ serverProcess.stdout?.on('data', (data) => {
65
+ console.log(`[server] ${data}`);
66
+ });
67
+
68
+ serverProcess.stderr?.on('data', (data) => {
69
+ console.error(`[server] ${data}`);
70
+ });
71
+
72
+ // Wait for server to be ready
73
+ const startTime = Date.now();
74
+ while (!(await isServerRunning(port))) {
75
+ if (Date.now() - startTime > timeout) {
76
+ throw new Error(`Server failed to start within ${timeout}ms`);
77
+ }
78
+ await new Promise((resolve) => {
79
+ setTimeout(resolve, 1000);
80
+ });
81
+ }
82
+
83
+ console.log(`โœ… Web server is ready on port ${port}`);
84
+ })();
85
+
86
+ return serverStartPromise;
87
+ }
88
+
89
+ export async function stopWebServer(): Promise<void> {
90
+ if (serverProcess) {
91
+ console.log('๐Ÿ›‘ Stopping web server...');
92
+ serverProcess.kill();
93
+ serverProcess = null;
94
+ serverStartPromise = null;
95
+ }
96
+ }