From afae2440574b846ce88b5b8bda96c1c6067f5e25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Malfait?= Date: Thu, 2 Jan 2025 13:28:02 +0100 Subject: [PATCH] Add E2E tests (#9309) Try adding E2E tests when labelling the PR or merging on main branch --- .github/workflows/ci-e2e.yml | 141 ++++++++++++++++++ .github/workflows/ci-e2e.yml.bak | 52 ------- .vscode/extensions.json | 3 +- .vscode/launch.json | 23 ++- .vscode/settings.json | 2 +- .vscode/twenty.code-workspace | 4 + packages/twenty-e2e-testing/.env.example | 3 +- .../twenty-e2e-testing/playwright.config.ts | 20 +-- ...ompanies.spec.ts => companies.e2e-spec.ts} | 2 +- .../tests/all/workspaces.e2e-spec.ts | 40 +++++ .../tests/all/workspaces.spec.ts | 66 -------- .../tests/authentication/login.spec.ts | 17 --- ...o_basic.spec.ts => demo_basic.e2e-spec.ts} | 2 +- .../twenty-e2e-testing/tests/login.setup.ts | 38 +++-- packages/twenty-front/package.json | 2 +- 15 files changed, 247 insertions(+), 168 deletions(-) create mode 100644 .github/workflows/ci-e2e.yml delete mode 100644 .github/workflows/ci-e2e.yml.bak rename packages/twenty-e2e-testing/tests/all/{companies.spec.ts => companies.e2e-spec.ts} (91%) create mode 100644 packages/twenty-e2e-testing/tests/all/workspaces.e2e-spec.ts delete mode 100644 packages/twenty-e2e-testing/tests/all/workspaces.spec.ts delete mode 100644 packages/twenty-e2e-testing/tests/authentication/login.spec.ts rename packages/twenty-e2e-testing/tests/demo/{demo_basic.spec.ts => demo_basic.e2e-spec.ts} (92%) diff --git a/.github/workflows/ci-e2e.yml b/.github/workflows/ci-e2e.yml new file mode 100644 index 000000000..9b94cb938 --- /dev/null +++ b/.github/workflows/ci-e2e.yml @@ -0,0 +1,141 @@ +name: CI E2E Tests +on: + push: + branches: + - main + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + runs-on: ubuntu-latest + if: github.event_name == 'push' || (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'run-e2e')) + timeout-minutes: 30 + env: + NX_REJECT_UNKNOWN_LOCAL_CACHE: 0 + # https://github.com/actions/runner-images/issues/70#issuecomment-589562148 + NODE_OPTIONS: "--max-old-space-size=10240" + services: + postgres: + image: twentycrm/twenty-postgres-spilo + env: + PGUSER_SUPERUSER: postgres + PGPASSWORD_SUPERUSER: postgres + ALLOW_NOSSL: "true" + SPILO_PROVIDER: "local" + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + redis: + image: redis + ports: + - 6379:6379 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/setup-node@v4 + with: + node-version: lts/* + + - name: Check system resources + run: | + echo "Available memory:" + free -h + echo "Available disk space:" + df -h + echo "CPU info:" + lscpu + + - name: Check for changed files + id: changed-files + uses: tj-actions/changed-files@v11 + with: + files: | + packages/** + playwright.config.ts + .github/workflows/ci-e2e.yml + + - name: Skip if no relevant changes + if: steps.changed-files.outputs.any_changed == 'false' + run: echo "No relevant changes detected. Marking as valid." + + - name: Install dependencies + if: steps.changed-files.outputs.any_changed == 'true' + uses: ./.github/workflows/actions/yarn-install + + - name: Build twenty-shared + if: steps.changed-files.outputs.any_changed == 'true' + run: npx nx build twenty-shared + + - name: Setup environment files + if: steps.changed-files.outputs.any_changed == 'true' + run: | + cp packages/twenty-e2e-testing/.env.example packages/twenty-e2e-testing/.env + cp packages/twenty-front/.env.example packages/twenty-front/.env + cp packages/twenty-e2e-testing/.env.example packages/twenty-e2e-testing/.env + npx nx reset:env twenty-server + + - name: Build frontend + if: steps.changed-files.outputs.any_changed == 'true' + run: NODE_ENV=production NODE_OPTIONS="--max-old-space-size=10240" npx nx build twenty-front + + - name: Build server + if: steps.changed-files.outputs.any_changed == 'true' + run: NODE_ENV=production npx nx build twenty-server + + - name: Create and setup database + if: steps.changed-files.outputs.any_changed == 'true' + run: | + PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "default";' + PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "test";' + npx nx run twenty-server:database:reset + + - name: Start server + if: steps.changed-files.outputs.any_changed == 'true' + run: | + npx nx start twenty-server & + echo "Waiting for server to be ready..." + timeout 60 bash -c 'until curl -s http://localhost:3000/health; do sleep 2; done' + + - name: Start frontend + if: steps.changed-files.outputs.any_changed == 'true' + run: | + npm_config_yes=true npx serve -s packages/twenty-front/build -l 3001 & + echo "Waiting for frontend to be ready..." + timeout 60 bash -c 'until curl -s http://localhost:3001; do sleep 2; done' + + - name: Start worker + if: steps.changed-files.outputs.any_changed == 'true' + run: | + npx nx run twenty-server:worker:ci & + echo "Worker started" + + - name: Install Playwright Browsers + if: steps.changed-files.outputs.any_changed == 'true' + run: npx nx setup twenty-e2e-testing + + - name: Run Playwright tests + if: steps.changed-files.outputs.any_changed == 'true' + run: npx nx test twenty-e2e-testing + + - uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report + path: packages/twenty-e2e-testing/run_results/ + retention-days: 30 + + - uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report + path: packages/twenty-e2e-testing/playwright-report/ + retention-days: 30 diff --git a/.github/workflows/ci-e2e.yml.bak b/.github/workflows/ci-e2e.yml.bak deleted file mode 100644 index 7bba72f1e..000000000 --- a/.github/workflows/ci-e2e.yml.bak +++ /dev/null @@ -1,52 +0,0 @@ -name: CI E2E Tests -on: - push: - branches: - - main - pull_request: - branches: - - '**' - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - test: - timeout-minutes: 30 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - uses: actions/setup-node@v4 - with: - node-version: lts/* - - - name: Check for changed files - id: changed-files - uses: tj-actions/changed-files@v11 - with: - files: | - packages/** - playwright.config.ts - - - name: Skip if no relevant changes - if: steps.changed-files.outputs.any_changed == 'false' - run: echo "No relevant changes detected. Marking as valid." - - - name: Install dependencies - if: steps.changed-files.outputs.any_changed == 'true' - uses: ./.github/workflows/actions/yarn-install - - name: Install Playwright Browsers - if: steps.changed-files.outputs.any_changed == 'true' - run: yarn playwright install --with-deps - - name: Run Playwright tests - if: steps.changed-files.outputs.any_changed == 'true' - run: yarn test:e2e companies - - uses: actions/upload-artifact@v4 - if: always() - with: - name: playwright-report - path: playwright-report/ - retention-days: 30 diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 7c6131d58..571553c8b 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -14,6 +14,7 @@ "styled-components.vscode-styled-components", "unifiedjs.vscode-mdx", "xyc.vscode-mdx-preview", - "yoavbls.pretty-ts-errors" + "yoavbls.pretty-ts-errors", + "ms-playwright.playwright" ] } diff --git a/.vscode/launch.json b/.vscode/launch.json index 2a13f9e89..50c68cc07 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,12 +6,11 @@ "name": "twenty-server - start debug", "type": "node", "request": "launch", - "runtimeExecutable": "yarn", - "runtimeVersion": "18", + "runtimeExecutable": "npx", "runtimeArgs": [ "nx", "run", - "twenty-server:start", + "twenty-server:start" ], "outputCapture": "std", "internalConsoleOptions": "openOnSessionStart", @@ -22,12 +21,11 @@ "name": "twenty-server - worker debug", "type": "node", "request": "launch", - "runtimeExecutable": "yarn", - "runtimeVersion": "18", + "runtimeExecutable": "npx", "runtimeArgs": [ "nx", "run", - "twenty-server:worker", + "twenty-server:worker" ], "outputCapture": "std", "internalConsoleOptions": "openOnSessionStart", @@ -45,12 +43,23 @@ "run", "twenty-server:command", "my-command", - "--my-parameter value", + "--my-parameter value" ], "outputCapture": "std", "internalConsoleOptions": "openOnSessionStart", "console": "internalConsole", "cwd": "${workspaceFolder}/packages/twenty-server/" + }, + { + "name": "Playwright Test current file", + "type": "node", + "request": "launch", + "runtimeExecutable": "npx", + "runtimeArgs": ["nx", "test", "twenty-e2e-testing", "${file}"], + "console": "integratedTerminal", + "cwd": "${workspaceFolder}", + "internalConsoleOptions": "neverOpen", + "envFile": "${workspaceFolder}/packages/twenty-e2e-testing/.env" } ] } \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 54fb6fe8a..15a0022d8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -48,5 +48,5 @@ "eslint.debug": true, "files.associations": { ".cursorrules": "markdown" - } + }, } diff --git a/.vscode/twenty.code-workspace b/.vscode/twenty.code-workspace index 57d28b045..ef9fafba9 100644 --- a/.vscode/twenty.code-workspace +++ b/.vscode/twenty.code-workspace @@ -44,6 +44,10 @@ "name": "tools/eslint-rules", "path": "../tools/eslint-rules" }, + { + "name": "packages/twenty-e2e-testing", + "path": "../packages/twenty-e2e-testing" + } ], "settings": { "editor.formatOnSave": false, diff --git a/packages/twenty-e2e-testing/.env.example b/packages/twenty-e2e-testing/.env.example index e0fbc4a32..4cebcc110 100644 --- a/packages/twenty-e2e-testing/.env.example +++ b/packages/twenty-e2e-testing/.env.example @@ -1,6 +1,5 @@ # Note that provide always without trailing forward slash to have expected behaviour -FRONTEND_BASE_URL=http://app.localhost:3001 -CI_DEFAULT_BASE_URL=https://demo.twenty.com +FRONTEND_BASE_URL=http://localhost:3001 DEFAULT_LOGIN=tim@apple.dev NEW_WORKSPACE_LOGIN=test@apple.dev DEMO_DEFAULT_LOGIN=noah@demo.dev diff --git a/packages/twenty-e2e-testing/playwright.config.ts b/packages/twenty-e2e-testing/playwright.config.ts index df73a47e3..6e76d0c5e 100644 --- a/packages/twenty-e2e-testing/playwright.config.ts +++ b/packages/twenty-e2e-testing/playwright.config.ts @@ -2,7 +2,13 @@ import { defineConfig, devices } from '@playwright/test'; import { config } from 'dotenv'; import path from 'path'; -config(); +const envResult = config({ + path: path.resolve(__dirname, '.env'), +}); + +if (envResult.error) { + throw new Error('Failed to load .env file'); +} /* === Run your local dev server before starting the tests === */ @@ -19,9 +25,7 @@ export default defineConfig({ workers: 1, // 1 worker = 1 test at the time, tests can't be parallelized timeout: 30 * 1000, // timeout can be changed use: { - baseURL: process.env.CI - ? process.env.CI_DEFAULT_BASE_URL - : (process.env.FRONTEND_BASE_URL ?? 'http://app.localhost:3001'), + baseURL: process.env.FRONTEND_BASE_URL || 'http://localhost:3001', trace: 'retain-on-failure', // trace takes EVERYTHING from page source, records every single step, should be used only when normal debugging won't work screenshot: 'on', // either 'on' here or in different method in modules, if 'on' all screenshots are overwritten each time the test is run headless: true, // instead of changing it to false, run 'yarn test:e2e:debug' or 'yarn test:e2e:ui' @@ -54,7 +58,7 @@ export default defineConfig({ storageState: path.resolve(__dirname, '.auth', 'user.json'), // takes saved cookies from directory }, dependencies: ['Login setup'], // forces to run login setup before running tests from this project - CASE SENSITIVE - testMatch: /all\/.+\.spec\.ts/, + testMatch: /all\/.+\.e2e-spec\.ts/, }, { name: 'firefox', @@ -63,11 +67,7 @@ export default defineConfig({ storageState: path.resolve(__dirname, '.auth', 'user.json'), }, dependencies: ['Login setup'], - testMatch: /all\/.+\.spec\.ts/, - }, - { - name: 'Authentication', - testMatch: /authentication\/.*\.spec\.ts/, + testMatch: /all\/.+\.e2e-spec\.ts/, }, //{ diff --git a/packages/twenty-e2e-testing/tests/all/companies.spec.ts b/packages/twenty-e2e-testing/tests/all/companies.e2e-spec.ts similarity index 91% rename from packages/twenty-e2e-testing/tests/all/companies.spec.ts rename to packages/twenty-e2e-testing/tests/all/companies.e2e-spec.ts index d372b214a..a2cdd2ad7 100644 --- a/packages/twenty-e2e-testing/tests/all/companies.spec.ts +++ b/packages/twenty-e2e-testing/tests/all/companies.e2e-spec.ts @@ -1,4 +1,4 @@ -import { test, expect } from '../../lib/fixtures/screenshot'; +import { expect, test } from '../../lib/fixtures/screenshot'; test.describe('Basic check', () => { test('Checking if table in Companies is visible', async ({ page }) => { diff --git a/packages/twenty-e2e-testing/tests/all/workspaces.e2e-spec.ts b/packages/twenty-e2e-testing/tests/all/workspaces.e2e-spec.ts new file mode 100644 index 000000000..24816e13e --- /dev/null +++ b/packages/twenty-e2e-testing/tests/all/workspaces.e2e-spec.ts @@ -0,0 +1,40 @@ +import { test } from '@playwright/test'; +import { sh } from '../../drivers/shell_driver'; + +test.describe('', () => { + test.use({ storageState: { cookies: [], origins: [] } }); + + /* + + test('Creating new workspace', async ({ page, browserName }) => { + // this test must use only 1 browser, otherwise it will lead to success and fail (1 workspace is created instead of x workspaces) + if (browserName == 'chromium') { + await sh( + 'npx nx run twenty-server:database:reset --configuration=no-seed', + ); + + await page.goto('/'); + await page.getByRole('button', { name: 'Continue With Email' }).click(); + await page.getByPlaceholder('Email').fill('test@apple.dev'); // email must be changed each time test is run + await page.getByPlaceholder('Email').press('Enter'); // otherwise if tests fails after this step, new workspace is created + await page.getByPlaceholder('Password').fill('Applecar2025'); + await page.getByPlaceholder('Password').press('Enter'); + await page.getByPlaceholder('Apple').fill('Test'); + await page.getByRole('button', { name: 'Continue' }).click(); + await page.getByPlaceholder('Tim').click(); + await page.getByPlaceholder('Tim').fill('Test2'); + await page.getByPlaceholder('Cook').click(); + await page.getByPlaceholder('Cook').fill('Test2'); + await page.getByRole('button', { name: 'Continue' }).click(); + await page.getByText('Continue without sync').click(); + await page.getByRole('button', { name: 'Finish' }).click(); + await expect(page.locator('table')).toBeVisible({ timeout: 1000 }); + await sh('npx nx run twenty-server:database:reset'); + } + }); + */ + + test('Syncing all workspaces', async () => { + await sh('npx nx run twenty-server:command workspace:sync-metadata -f'); + }); +}); diff --git a/packages/twenty-e2e-testing/tests/all/workspaces.spec.ts b/packages/twenty-e2e-testing/tests/all/workspaces.spec.ts deleted file mode 100644 index ebe79f8e2..000000000 --- a/packages/twenty-e2e-testing/tests/all/workspaces.spec.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { test, expect } from '@playwright/test'; -import { sh } from '../../drivers/shell_driver'; - -test.describe('', () => { - test('Testing logging', async ({ page }) => { - await page.goto('/'); - await page.getByRole('button', { name: 'Continue With Email' }).click(); - await page.getByPlaceholder('Email').fill('tim@apple.dev'); - await page.getByRole('button', { name: 'Continue', exact: true }).click(); - await page.getByPlaceholder('Password').fill('Applecar2025'); - await page.getByRole('button', { name: 'Sign in' }).click(); - await expect(page.getByText('Welcome to Twenty')).not.toBeVisible(); - expect(page.url()).not.toContain('/welcome'); - await page.getByRole('link', { name: 'Opportunities' }).click(); - await expect(page.locator('tbody > tr')).toHaveCount(4); - }); - - test('Creating new workspace', async ({ page, browserName }) => { - // this test must use only 1 browser, otherwise it will lead to success and fail (1 workspace is created instead of x workspaces) - if (browserName == 'chromium') { - await page.goto('/'); - await page.getByRole('button', { name: 'Continue With Email' }).click(); - await page.getByPlaceholder('Email').fill('test@apple.dev'); // email must be changed each time test is run - await page.getByPlaceholder('Email').press('Enter'); // otherwise if tests fails after this step, new workspace is created - await page.getByPlaceholder('Password').fill('Applecar2025'); - await page.getByPlaceholder('Password').press('Enter'); - await page.getByPlaceholder('Apple').fill('Test'); - await page.getByRole('button', { name: 'Continue' }).click(); - await page.getByPlaceholder('Tim').click(); - await page.getByPlaceholder('Tim').fill('Test2'); - await page.getByPlaceholder('Cook').click(); - await page.getByPlaceholder('Cook').fill('Test2'); - await page.getByRole('button', { name: 'Continue' }).click(); - await page.getByText('Continue without sync').click(); - await page.getByRole('button', { name: 'Finish' }).click(); - await expect(page.locator('table')).toBeVisible({ timeout: 1000 }); - } - }); - - test('Syncing all workspaces', async () => { - await sh('npx nx run twenty-server:command workspace:sync-metadata -f'); - await sh('npx nx run twenty-server:command workspace:sync-metadata -f'); - }); - - test('Resetting database', async ({ page, browserName }) => { - if (browserName === 'chromium') { - await sh('yarn nx database:reset twenty-server'); // if this command fails for any reason, database must be restarted manually using the same command because database is in unstable state - await page.goto('/'); - await page.getByRole('button', { name: 'Continue With Email' }).click(); - await page.getByPlaceholder('Email').fill('tim@apple.dev'); - await page.getByRole('button', { name: 'Continue' }).click(); - await page.getByPlaceholder('Password').fill('Applecar2025'); - await page.getByRole('button', { name: 'Sign in' }).click(); - await page.getByRole('link', { name: 'Companies' }).click(); - expect(page.url()).toContain('/companies'); - await expect(page.locator('table')).toBeVisible(); - } - }); - - test('Seeding database', async ({ page, browserName }) => { - if (browserName === 'chromium') { - await sh('npx nx workspace:seed:demo'); - await page.goto('/'); - } - }); -}); diff --git a/packages/twenty-e2e-testing/tests/authentication/login.spec.ts b/packages/twenty-e2e-testing/tests/authentication/login.spec.ts deleted file mode 100644 index a898cf0d9..000000000 --- a/packages/twenty-e2e-testing/tests/authentication/login.spec.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { test as base, expect } from '../../lib/fixtures/screenshot'; -import { LoginPage } from '../../lib/pom/loginPage'; - -// fixture -const test = base.extend<{ loginPage: LoginPage }>({ - loginPage: async ({ page }, use) => { - await use(new LoginPage(page)); - }, -}); - -test('Check login with email', async ({ loginPage }) => { - await loginPage.typeEmail(process.env.DEFAULT_LOGIN); - await loginPage.clickContinueButton(); - await loginPage.typePassword(process.env.DEFAULT_PASSWORD); - await loginPage.clickSignInButton(); - await expect(loginPage.signInButton).not.toBeVisible(); -}); diff --git a/packages/twenty-e2e-testing/tests/demo/demo_basic.spec.ts b/packages/twenty-e2e-testing/tests/demo/demo_basic.e2e-spec.ts similarity index 92% rename from packages/twenty-e2e-testing/tests/demo/demo_basic.spec.ts rename to packages/twenty-e2e-testing/tests/demo/demo_basic.e2e-spec.ts index 61f17bcd4..53721f2c5 100644 --- a/packages/twenty-e2e-testing/tests/demo/demo_basic.spec.ts +++ b/packages/twenty-e2e-testing/tests/demo/demo_basic.e2e-spec.ts @@ -1,4 +1,4 @@ -import { test, expect } from '@playwright/test'; +import { expect, test } from '@playwright/test'; test('Check if demo account is working properly @demo-only', async ({ page, diff --git a/packages/twenty-e2e-testing/tests/login.setup.ts b/packages/twenty-e2e-testing/tests/login.setup.ts index defd19685..74150ea3f 100644 --- a/packages/twenty-e2e-testing/tests/login.setup.ts +++ b/packages/twenty-e2e-testing/tests/login.setup.ts @@ -1,18 +1,38 @@ -import { test as setup, expect } from '@playwright/test'; +import { expect, test as setup } from '@playwright/test'; import path from 'path'; setup('Login test', async ({ page }) => { - await page.goto('/'); - await page.getByRole('button', { name: 'Continue With Email' }).click(); - await page.getByPlaceholder('Email').fill(process.env.DEFAULT_LOGIN); - await page.getByRole('button', { name: 'Continue', exact: true }).click(); - await page.getByPlaceholder('Password').fill(process.env.DEFAULT_PASSWORD); - await page.getByRole('button', { name: 'Sign in' }).click(); - await expect(page.getByText('Welcome to Twenty')).not.toBeVisible(); + console.log('Starting login test'); - // End of authentication steps. + await page.goto('/'); + console.log('Navigated to homepage'); + + await page.getByRole('button', { name: 'Continue With Email' }).click(); + console.log('Clicked email login button'); + + console.log('Default login', process.env.DEFAULT_LOGIN); + await page.getByPlaceholder('Email').fill(process.env.DEFAULT_LOGIN ?? ''); + console.log('Filled email field'); + + await page.getByRole('button', { name: 'Continue', exact: true }).click(); + console.log('Clicked continue button'); + + await page + .getByPlaceholder('Password') + .fill(process.env.DEFAULT_PASSWORD ?? ''); + console.log('Filled password field'); + + await page.getByRole('button', { name: 'Sign in' }).click(); + console.log('Clicked sign in button'); + + await page.waitForLoadState('networkidle'); + console.log('Waited for network to be idle'); + + await expect(page.getByText('Welcome to Twenty')).not.toBeVisible(); + console.log('Verified welcome message not visible'); await page.context().storageState({ path: path.resolve(__dirname, '..', '.auth', 'user.json'), }); + console.log('Saved auth state'); }); diff --git a/packages/twenty-front/package.json b/packages/twenty-front/package.json index 3e91a2eea..2e8fedd07 100644 --- a/packages/twenty-front/package.json +++ b/packages/twenty-front/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "scripts": { - "build": "VITE_DISABLE_TYPESCRIPT_CHECKER=true VITE_DISABLE_ESLINT_CHECKER=true NODE_OPTIONS=--max-old-space-size=3000 npx vite build && sh ./scripts/inject-runtime-env.sh", + "build": "VITE_DISABLE_TYPESCRIPT_CHECKER=true VITE_DISABLE_ESLINT_CHECKER=true NODE_OPTIONS=--max-old-space-size=4000 npx vite build && sh ./scripts/inject-runtime-env.sh", "build:sourcemaps": "VITE_BUILD_SOURCEMAP=true VITE_DISABLE_TYPESCRIPT_CHECKER=true VITE_DISABLE_ESLINT_CHECKER=true NODE_OPTIONS=--max-old-space-size=6000 npx vite build && sh ./scripts/inject-runtime-env.sh", "start:prod": "NODE_ENV=production npx vite --host", "tsup": "npx tsup"