diff --git a/.github/workflows/main-pipeline.yaml b/.github/workflows/main-pipeline.yaml index 82ccc90a..311fbdae 100644 --- a/.github/workflows/main-pipeline.yaml +++ b/.github/workflows/main-pipeline.yaml @@ -6,7 +6,80 @@ permissions: on: pull_request: +concurrency: + group: main-pipeline-${{ github.ref }} + cancel-in-progress: true + jobs: + changes: + name: Detect Changes ๐Ÿ”Ž + runs-on: namespace-profile-linux-8-vcpu-16-gb-ram-optimal + outputs: + build: ${{ steps.filter.outputs.build }} + unit: ${{ steps.filter.outputs.unit }} + e2e_node_ssr_only: ${{ steps.filter.outputs.e2e_node_ssr_only }} + e2e_node_ssr_web_vanilla: ${{ steps.filter.outputs.e2e_node_ssr_web_vanilla }} + e2e_web: ${{ steps.filter.outputs.e2e_web }} + e2e_react_native_android: ${{ steps.filter.outputs.e2e_react_native_android }} + steps: + - uses: namespacelabs/nscloud-checkout-action@v8 + + - uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + build: + - 'lib/**' + - 'platforms/**' + - 'universal/**' + - 'package.json' + - 'pnpm-lock.yaml' + - 'pnpm-workspace.yaml' + - 'tsconfig*.json' + - '.github/workflows/main-pipeline.yaml' + unit: + - 'lib/**' + - 'platforms/**' + - 'universal/**' + - '**/rstest.config.ts' + - 'package.json' + - 'pnpm-lock.yaml' + - 'pnpm-workspace.yaml' + - '.github/workflows/main-pipeline.yaml' + e2e_node_ssr_only: + - 'implementations/node-ssr-only/**' + - 'lib/**' + - 'platforms/**' + - 'universal/**' + - 'package.json' + - 'pnpm-lock.yaml' + - '.github/workflows/main-pipeline.yaml' + e2e_node_ssr_web_vanilla: + - 'implementations/node-ssr-web-vanilla/**' + - 'lib/**' + - 'platforms/**' + - 'universal/**' + - 'package.json' + - 'pnpm-lock.yaml' + - '.github/workflows/main-pipeline.yaml' + e2e_web: + - 'implementations/web-vanilla/**' + - 'lib/**' + - 'platforms/**' + - 'universal/**' + - 'package.json' + - 'pnpm-lock.yaml' + - '.github/workflows/main-pipeline.yaml' + e2e_react_native_android: + - 'implementations/react-native/**' + - 'platforms/javascript/react-native/**' + - 'lib/**' + - 'platforms/**' + - 'universal/**' + - 'package.json' + - 'pnpm-lock.yaml' + - '.github/workflows/main-pipeline.yaml' + setup: name: pnpm install ๐Ÿ› ๏ธ runs-on: namespace-profile-linux-8-vcpu-16-gb-ram-optimal @@ -92,7 +165,8 @@ jobs: name: Build ๐Ÿ“ฆ runs-on: namespace-profile-linux-8-vcpu-16-gb-ram-optimal timeout-minutes: 15 - needs: setup + needs: [setup, changes] + if: needs.changes.outputs.build == 'true' steps: - uses: namespacelabs/nscloud-checkout-action@v8 @@ -108,7 +182,7 @@ jobs: cache: pnpm - run: pnpm install --prefer-offline --frozen-lockfile - - run: pnpm build + - run: pnpm build:ci type-check: name: Type Check ๐Ÿ”ท @@ -154,11 +228,23 @@ jobs: - run: pnpm install --prefer-offline --frozen-lockfile - run: pnpm lint:check - test: - name: Test ๐Ÿงช + test-unit: + name: Test ๐Ÿงช (${{ matrix.package }}) runs-on: namespace-profile-linux-8-vcpu-16-gb-ram-optimal timeout-minutes: 15 - needs: setup + needs: [setup, changes] + if: needs.changes.outputs.unit == 'true' + strategy: + fail-fast: true + matrix: + include: + - package: '@contentful/optimization-api-schemas' + - package: '@contentful/optimization-api-client' + - package: '@contentful/optimization-core' + - package: '@contentful/optimization-node' + - package: '@contentful/optimization-web' + - package: '@contentful/optimization-web-preview-panel' + - package: '@contentful/optimization-react-native' steps: - uses: namespacelabs/nscloud-checkout-action@v8 @@ -174,28 +260,29 @@ jobs: cache: pnpm - run: pnpm install --prefer-offline --frozen-lockfile - - run: pnpm test:unit + - run: pnpm --filter ${{ matrix.package }} test:unit e2e-node-ssr-only: name: E2E Node SSR Only ๐Ÿ–ฅ๏ธ runs-on: namespace-profile-linux-8-vcpu-16-gb-ram-optimal timeout-minutes: 15 - needs: setup + needs: [setup, changes] + if: needs.changes.outputs.e2e_node_ssr_only == 'true' steps: - uses: namespacelabs/nscloud-checkout-action@v8 - run: | echo "DOTENV_CONFIG_QUIET=true" >>implementations/node-ssr-only/.env - echo "VITE_NINETAILED_CLIENT_ID=${{secrets.NINETAILED_CLIENT_ID}}" >>implementations/node-ssr-only/.env - echo "VITE_NINETAILED_ENVIRONMENT=${{secrets.NINETAILED_ENVIRONMENT}}" >>implementations/node-ssr-only/.env - echo "VITE_EXPERIENCE_API_BASE_URL=http://localhost:8000/experience/" >>implementations/node-ssr-only/.env - echo "VITE_INSIGHTS_API_BASE_URL=http://localhost:8000/insights/" >>implementations/node-ssr-only/.env - echo "VITE_CONTENTFUL_TOKEN=${{secrets.CONTENTFUL_TOKEN}}" >>implementations/node-ssr-only/.env - echo "VITE_CONTENTFUL_PREVIEW_TOKEN=${{secrets.CONTENTFUL_PREVIEW_TOKEN}}" >>implementations/node-ssr-only/.env - echo "VITE_CONTENTFUL_ENVIRONMENT=${{secrets.CONTENTFUL_ENVIRONMENT}}" >>implementations/node-ssr-only/.env - echo "VITE_CONTENTFUL_SPACE_ID=${{secrets.CONTENTFUL_SPACE_ID}}" >>implementations/node-ssr-only/.env - echo "VITE_CONTENTFUL_CDA_HOST=localhost:8000" >>implementations/node-ssr-only/.env - echo "VITE_CONTENTFUL_BASE_PATH=contentful" >>implementations/node-ssr-only/.env + echo "PUBLIC_NINETAILED_CLIENT_ID=${{secrets.NINETAILED_CLIENT_ID}}" >>implementations/node-ssr-only/.env + echo "PUBLIC_NINETAILED_ENVIRONMENT=${{secrets.NINETAILED_ENVIRONMENT}}" >>implementations/node-ssr-only/.env + echo "PUBLIC_EXPERIENCE_API_BASE_URL=http://localhost:8000/experience/" >>implementations/node-ssr-only/.env + echo "PUBLIC_INSIGHTS_API_BASE_URL=http://localhost:8000/insights/" >>implementations/node-ssr-only/.env + echo "PUBLIC_CONTENTFUL_TOKEN=${{secrets.CONTENTFUL_TOKEN}}" >>implementations/node-ssr-only/.env + echo "PUBLIC_CONTENTFUL_PREVIEW_TOKEN=${{secrets.CONTENTFUL_PREVIEW_TOKEN}}" >>implementations/node-ssr-only/.env + echo "PUBLIC_CONTENTFUL_ENVIRONMENT=${{secrets.CONTENTFUL_ENVIRONMENT}}" >>implementations/node-ssr-only/.env + echo "PUBLIC_CONTENTFUL_SPACE_ID=${{secrets.CONTENTFUL_SPACE_ID}}" >>implementations/node-ssr-only/.env + echo "PUBLIC_CONTENTFUL_CDA_HOST=localhost:8000" >>implementations/node-ssr-only/.env + echo "PUBLIC_CONTENTFUL_BASE_PATH=contentful" >>implementations/node-ssr-only/.env - uses: actions/setup-node@v6 with: @@ -229,22 +316,23 @@ jobs: name: E2E Node SSR + Web Vanilla ๐Ÿ–ฅ๏ธ runs-on: namespace-profile-linux-8-vcpu-16-gb-ram-optimal timeout-minutes: 15 - needs: setup + needs: [setup, changes] + if: needs.changes.outputs.e2e_node_ssr_web_vanilla == 'true' steps: - uses: namespacelabs/nscloud-checkout-action@v8 - run: | echo "DOTENV_CONFIG_QUIET=true" >>implementations/node-ssr-web-vanilla/.env - echo "VITE_NINETAILED_CLIENT_ID=${{secrets.NINETAILED_CLIENT_ID}}" >>implementations/node-ssr-web-vanilla/.env - echo "VITE_NINETAILED_ENVIRONMENT=${{secrets.NINETAILED_ENVIRONMENT}}" >>implementations/node-ssr-web-vanilla/.env - echo "VITE_EXPERIENCE_API_BASE_URL=http://localhost:8000/experience/" >>implementations/node-ssr-web-vanilla/.env - echo "VITE_INSIGHTS_API_BASE_URL=http://localhost:8000/insights/" >>implementations/node-ssr-web-vanilla/.env - echo "VITE_CONTENTFUL_TOKEN=${{secrets.CONTENTFUL_TOKEN}}" >>implementations/node-ssr-web-vanilla/.env - echo "VITE_CONTENTFUL_PREVIEW_TOKEN=${{secrets.CONTENTFUL_PREVIEW_TOKEN}}" >>implementations/node-ssr-web-vanilla/.env - echo "VITE_CONTENTFUL_ENVIRONMENT=${{secrets.CONTENTFUL_ENVIRONMENT}}" >>implementations/node-ssr-web-vanilla/.env - echo "VITE_CONTENTFUL_SPACE_ID=${{secrets.CONTENTFUL_SPACE_ID}}" >>implementations/node-ssr-web-vanilla/.env - echo "VITE_CONTENTFUL_CDA_HOST=localhost:8000" >>implementations/node-ssr-web-vanilla/.env - echo "VITE_CONTENTFUL_BASE_PATH=contentful" >>implementations/node-ssr-web-vanilla/.env + echo "PUBLIC_NINETAILED_CLIENT_ID=${{secrets.NINETAILED_CLIENT_ID}}" >>implementations/node-ssr-web-vanilla/.env + echo "PUBLIC_NINETAILED_ENVIRONMENT=${{secrets.NINETAILED_ENVIRONMENT}}" >>implementations/node-ssr-web-vanilla/.env + echo "PUBLIC_EXPERIENCE_API_BASE_URL=http://localhost:8000/experience/" >>implementations/node-ssr-web-vanilla/.env + echo "PUBLIC_INSIGHTS_API_BASE_URL=http://localhost:8000/insights/" >>implementations/node-ssr-web-vanilla/.env + echo "PUBLIC_CONTENTFUL_TOKEN=${{secrets.CONTENTFUL_TOKEN}}" >>implementations/node-ssr-web-vanilla/.env + echo "PUBLIC_CONTENTFUL_PREVIEW_TOKEN=${{secrets.CONTENTFUL_PREVIEW_TOKEN}}" >>implementations/node-ssr-web-vanilla/.env + echo "PUBLIC_CONTENTFUL_ENVIRONMENT=${{secrets.CONTENTFUL_ENVIRONMENT}}" >>implementations/node-ssr-web-vanilla/.env + echo "PUBLIC_CONTENTFUL_SPACE_ID=${{secrets.CONTENTFUL_SPACE_ID}}" >>implementations/node-ssr-web-vanilla/.env + echo "PUBLIC_CONTENTFUL_CDA_HOST=localhost:8000" >>implementations/node-ssr-web-vanilla/.env + echo "PUBLIC_CONTENTFUL_BASE_PATH=contentful" >>implementations/node-ssr-web-vanilla/.env - uses: actions/setup-node@v6 with: @@ -278,7 +366,8 @@ jobs: name: E2E Web Vanilla ๐Ÿ–ฅ๏ธ runs-on: namespace-profile-linux-8-vcpu-16-gb-ram-optimal timeout-minutes: 15 - needs: setup + needs: [setup, changes] + if: needs.changes.outputs.e2e_web == 'true' steps: - uses: docker/setup-compose-action@v1 @@ -288,16 +377,16 @@ jobs: run: | cat > .env << 'EOF' DOTENV_CONFIG_QUIET=true - VITE_NINETAILED_CLIENT_ID=${{secrets.NINETAILED_CLIENT_ID}} - VITE_NINETAILED_ENVIRONMENT=${{secrets.NINETAILED_ENVIRONMENT}} - VITE_EXPERIENCE_API_BASE_URL=http://localhost:8000/experience/ - VITE_INSIGHTS_API_BASE_URL=http://localhost:8000/insights/ - VITE_CONTENTFUL_TOKEN=${{secrets.CONTENTFUL_TOKEN}} - VITE_CONTENTFUL_PREVIEW_TOKEN=${{secrets.CONTENTFUL_PREVIEW_TOKEN}} - VITE_CONTENTFUL_ENVIRONMENT=${{secrets.CONTENTFUL_ENVIRONMENT}} - VITE_CONTENTFUL_SPACE_ID=${{secrets.CONTENTFUL_SPACE_ID}} - VITE_CONTENTFUL_CDA_HOST=localhost:8000 - VITE_CONTENTFUL_BASE_PATH=contentful + PUBLIC_NINETAILED_CLIENT_ID=${{secrets.NINETAILED_CLIENT_ID}} + PUBLIC_NINETAILED_ENVIRONMENT=${{secrets.NINETAILED_ENVIRONMENT}} + PUBLIC_EXPERIENCE_API_BASE_URL=http://localhost:8000/experience/ + PUBLIC_INSIGHTS_API_BASE_URL=http://localhost:8000/insights/ + PUBLIC_CONTENTFUL_TOKEN=${{secrets.CONTENTFUL_TOKEN}} + PUBLIC_CONTENTFUL_PREVIEW_TOKEN=${{secrets.CONTENTFUL_PREVIEW_TOKEN}} + PUBLIC_CONTENTFUL_ENVIRONMENT=${{secrets.CONTENTFUL_ENVIRONMENT}} + PUBLIC_CONTENTFUL_SPACE_ID=${{secrets.CONTENTFUL_SPACE_ID}} + PUBLIC_CONTENTFUL_CDA_HOST=localhost:8000 + PUBLIC_CONTENTFUL_BASE_PATH=contentful EOF cp .env implementations/node-ssr-only/ cp .env implementations/web-vanilla/ @@ -334,7 +423,8 @@ jobs: name: E2E React Native Android ๐Ÿ“ฑ runs-on: namespace-profile-linux-16-vcpu-32-gb-ram-optimal timeout-minutes: 60 - needs: setup + needs: [setup, changes] + if: needs.changes.outputs.e2e_react_native_android == 'true' env: DETOX_AVD_NAME: test CI: 'true' @@ -410,15 +500,15 @@ jobs: - name: Create .env file for React Native run: | cat > implementations/react-native/.env << 'EOF' - VITE_NINETAILED_CLIENT_ID=${{ secrets.NINETAILED_CLIENT_ID }} - VITE_NINETAILED_ENVIRONMENT=${{ secrets.NINETAILED_ENVIRONMENT }} - VITE_EXPERIENCE_API_BASE_URL=http://localhost:8000/experience/ - VITE_INSIGHTS_API_BASE_URL=http://localhost:8000/insights/ - VITE_CONTENTFUL_TOKEN=${{ secrets.CONTENTFUL_TOKEN }} - VITE_CONTENTFUL_ENVIRONMENT=${{ secrets.CONTENTFUL_ENVIRONMENT }} - VITE_CONTENTFUL_SPACE_ID=${{ secrets.CONTENTFUL_SPACE_ID }} - VITE_CONTENTFUL_CDA_HOST=localhost:8000 - VITE_CONTENTFUL_BASE_PATH=contentful + PUBLIC_NINETAILED_CLIENT_ID=${{ secrets.NINETAILED_CLIENT_ID }} + PUBLIC_NINETAILED_ENVIRONMENT=${{ secrets.NINETAILED_ENVIRONMENT }} + PUBLIC_EXPERIENCE_API_BASE_URL=http://localhost:8000/experience/ + PUBLIC_INSIGHTS_API_BASE_URL=http://localhost:8000/insights/ + PUBLIC_CONTENTFUL_TOKEN=${{ secrets.CONTENTFUL_TOKEN }} + PUBLIC_CONTENTFUL_ENVIRONMENT=${{ secrets.CONTENTFUL_ENVIRONMENT }} + PUBLIC_CONTENTFUL_SPACE_ID=${{ secrets.CONTENTFUL_SPACE_ID }} + PUBLIC_CONTENTFUL_CDA_HOST=localhost:8000 + PUBLIC_CONTENTFUL_BASE_PATH=contentful EOF - name: Build Android app (Detox) diff --git a/.gitignore b/.gitignore index b8d46487..b869a393 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ .contentfulrc.json .env .rslib +.rsdoctor .temp .tmp blob-report diff --git a/implementations/node-ssr-only/package.json b/implementations/node-ssr-only/package.json index 6f518745..0744d2ce 100644 --- a/implementations/node-ssr-only/package.json +++ b/implementations/node-ssr-only/package.json @@ -25,6 +25,7 @@ "@contentful/rich-text-html-renderer": "catalog:", "@contentful/rich-text-types": "catalog:", "contentful": "catalog:", + "ejs": "catalog:", "express": "catalog:", "express-rate-limit": "catalog:", "qs": "catalog:", diff --git a/implementations/node-ssr-only/src/app.ts b/implementations/node-ssr-only/src/app.ts index d89b1344..41e88dfd 100644 --- a/implementations/node-ssr-only/src/app.ts +++ b/implementations/node-ssr-only/src/app.ts @@ -21,26 +21,26 @@ const limiter = rateLimit({ const app: Express = express() app.use(limiter) -app.set('view engine', 'pug') // configure Pug as the view engine +app.set('view engine', 'ejs') // configure EJS as the view engine app.set('views', path.join(__dirname, '.')) // define the directory for view templates const optimizationConfig: OptimizationNodeConfig = { - clientId: process.env.VITE_NINETAILED_CLIENT_ID ?? '', - environment: process.env.VITE_NINETAILED_ENVIRONMENT ?? '', + clientId: process.env.PUBLIC_NINETAILED_CLIENT_ID ?? '', + environment: process.env.PUBLIC_NINETAILED_ENVIRONMENT ?? '', logLevel: 'debug', - analytics: { baseUrl: process.env.VITE_INSIGHTS_API_BASE_URL }, - personalization: { baseUrl: process.env.VITE_EXPERIENCE_API_BASE_URL }, + analytics: { baseUrl: process.env.PUBLIC_INSIGHTS_API_BASE_URL }, + personalization: { baseUrl: process.env.PUBLIC_EXPERIENCE_API_BASE_URL }, } const sdk = new Optimization(optimizationConfig) const ctflConfig: contentful.CreateClientParams = { - accessToken: process.env.VITE_CONTENTFUL_TOKEN ?? '', - environment: process.env.VITE_CONTENTFUL_ENVIRONMENT ?? '', - space: process.env.VITE_CONTENTFUL_SPACE_ID ?? '', - host: process.env.VITE_CONTENTFUL_CDA_HOST ?? '', - basePath: process.env.VITE_CONTENTFUL_BASE_PATH ?? '', - insecure: Boolean(process.env.VITE_CONTENTFUL_CDA_HOST), + accessToken: process.env.PUBLIC_CONTENTFUL_TOKEN ?? '', + environment: process.env.PUBLIC_CONTENTFUL_ENVIRONMENT ?? '', + space: process.env.PUBLIC_CONTENTFUL_SPACE_ID ?? '', + host: process.env.PUBLIC_CONTENTFUL_CDA_HOST ?? '', + basePath: process.env.PUBLIC_CONTENTFUL_BASE_PATH ?? '', + insecure: Boolean(process.env.PUBLIC_CONTENTFUL_CDA_HOST), } const ctfl = contentful.createClient(ctflConfig) diff --git a/implementations/node-ssr-only/src/index.ejs b/implementations/node-ssr-only/src/index.ejs new file mode 100644 index 00000000..255fad48 --- /dev/null +++ b/implementations/node-ssr-only/src/index.ejs @@ -0,0 +1,161 @@ + + + + Optimization Node SDK Implementation E2E Test + + + + +

Optimization Node SDK Implementation E2E Test

+ +
+
+

States

+ +

+ <% if (profile.traits.identified === true) { %> + Identified + <% } else { %> + Identify + <% } %> +

+
+ +
+

Entries

+ +
+ <% const entry1 = entries.get('1MwiFl4z7gkwqGYdvCmr8c') %> +
+ <%- entry1.entry.fields.text %> +
+ + <% const entry2 = entries.get('4ib0hsHWoSOnCVdDkizE8d') %> +
+

<%= entry2.entry.fields.text %>

+
+ + <% const entry3 = entries.get('xFwgG3oNaOcjzWiGe4vXo') %> +
+

<%= entry3.entry.fields.text %>

+
+ + <% const entry4 = entries.get('2Z2WLOx07InSewC3LUB3eX') %> +
+

<%= entry4.entry.fields.text %>

+
+ + <% const entry5 = entries.get('5XHssysWUDECHzKLzoIsg1') %> +
+

<%= entry5.entry.fields.text %>

+
+ + <% const entry6 = entries.get('6zqoWXyiSrf0ja7I2WGtYj') %> +
+

<%= entry6.entry.fields.text %>

+
+ + <% const entry7 = entries.get('7pa5bOx8Z9NmNcr7mISvD') %> +
+

<%= entry7.entry.fields.text %>

+
+
+
+
+ + diff --git a/implementations/node-ssr-only/src/index.pug b/implementations/node-ssr-only/src/index.pug deleted file mode 100644 index ec0daef7..00000000 --- a/implementations/node-ssr-only/src/index.pug +++ /dev/null @@ -1,145 +0,0 @@ -doctype html -html(lang="en") - head - title Optimization Node SDK Implementation E2E Test - - style. - html, - textarea, - input, - button { - font-family: - -apple-system, - BlinkMacSystemFont, - avenir next, - avenir, - segoe ui, - helvetica neue, - Adwaita Sans, - Cantarell, - Ubuntu, - roboto, - noto, - helvetica, - arial, - sans-serif; - } - h1, - main { - display: grid; - grid-gap: 2rem; - grid-template-rows: auto 1fr; - max-width: 1040px; - margin-inline: auto; - } - ol { - margin: 0; - } - summary { - cursor: pointer; - } - a { - color: inherit; - - &:hover, - &:active { - color: blue; - } - } - #entries > div { - display: grid; - grid-gap: 2rem; - grid-auto-rows: 75dvh; - justify-items: center; - align-items: center; - margin-block-end: 2rem; - - > div { - display: grid; - width: 100%; - height: 100%; - justify-items: center; - align-items: center; - background: #eee; - } - } - - body - h1 Optimization Node SDK Implementation E2E Test - - main - section - h2 States - - p - - if (profile.traits.identified === true) - span Identified - - else - a(href=`/?userId=charles`) Identify - - section#entries - h2 Entries - - div - - const entry1 = entries.get('1MwiFl4z7gkwqGYdvCmr8c') - div( - data-ctfl-entry-id=entry1.entry.sys.id - data-ctfl-personalization-id=(entry1.personalization || {}).experienceId - data-ctfl-sticky=(entry1.personalization || {}).sticky - data-ctfl-variant-index=(entry1.personalization || {}).variantIndex - ) - != entry1.entry.fields.text - - - const entry2 = entries.get('4ib0hsHWoSOnCVdDkizE8d') - div( - data-ctfl-entry-id=entry2.entry.sys.id - data-ctfl-personalization-id=(entry2.personalization || {}).experienceId - data-ctfl-sticky=(entry2.personalization || {}).sticky - data-ctfl-variant-index=(entry2.personalization || {}).variantIndex - ) - p= entry2.entry.fields.text - - - const entry3 = entries.get('xFwgG3oNaOcjzWiGe4vXo') - div( - data-ctfl-entry-id=entry3.entry.sys.id - data-ctfl-personalization-id=(entry3.personalization || {}).experienceId - data-ctfl-sticky=(entry3.personalization || {}).sticky - data-ctfl-variant-index=(entry3.personalization || {}).variantIndex - ) - p= entry3.entry.fields.text - - - const entry4 = entries.get('2Z2WLOx07InSewC3LUB3eX') - div( - data-ctfl-entry-id=entry4.entry.sys.id - data-ctfl-personalization-id=(entry4.personalization || {}).experienceId - data-ctfl-sticky=(entry4.personalization || {}).sticky - data-ctfl-variant-index=(entry4.personalization || {}).variantIndex - ) - p= entry4.entry.fields.text - - - const entry5 = entries.get('5XHssysWUDECHzKLzoIsg1') - div( - data-ctfl-entry-id=entry5.entry.sys.id - data-ctfl-personalization-id=(entry5.personalization || {}).experienceId - data-ctfl-sticky=(entry5.personalization || {}).sticky - data-ctfl-variant-index=(entry5.personalization || {}).variantIndex - ) - p= entry5.entry.fields.text - - - const entry6 = entries.get('6zqoWXyiSrf0ja7I2WGtYj') - div( - data-ctfl-entry-id=entry6.entry.sys.id - data-ctfl-personalization-id=(entry6.personalization || {}).experienceId - data-ctfl-sticky=(entry6.personalization || {}).sticky - data-ctfl-variant-index=(entry6.personalization || {}).variantIndex - ) - p= entry6.entry.fields.text - - - const entry7 = entries.get('7pa5bOx8Z9NmNcr7mISvD') - div( - data-ctfl-entry-id=entry7.entry.sys.id - data-ctfl-personalization-id=(entry7.personalization || {}).experienceId - data-ctfl-sticky=(entry7.personalization || {}).sticky - data-ctfl-variant-index=(entry7.personalization || {}).variantIndex - ) - p= entry7.entry.fields.text diff --git a/implementations/node-ssr-web-vanilla/package.json b/implementations/node-ssr-web-vanilla/package.json index ac3809ea..4d3c77c7 100644 --- a/implementations/node-ssr-web-vanilla/package.json +++ b/implementations/node-ssr-web-vanilla/package.json @@ -19,7 +19,7 @@ "test:e2e:codegen": "playwright codegen", "test:e2e:report": "playwright show-report", "test:e2e:ui": "playwright test --ui", - "test:unit": "vitest run --coverage", + "test:unit": "rstest run --coverage", "typecheck": "tsc --noEmit" }, "dependencies": { @@ -36,14 +36,14 @@ "@types/node": "catalog:", "@types/qs": "catalog:", "@types/supertest": "catalog:", - "@vitest/coverage-v8": "catalog:", + "@rstest/core": "catalog:", + "@rstest/coverage-istanbul": "catalog:", "@types/cookie-parser": "1.4.7", "dotenv": "catalog:", "pm2": "catalog:", "rimraf": "catalog:", "supertest": "catalog:", "tsx": "catalog:", - "typescript": "catalog:", - "vitest": "catalog:" + "typescript": "catalog:" } } diff --git a/implementations/node-ssr-web-vanilla/vitest.config.ts b/implementations/node-ssr-web-vanilla/rstest.config.ts similarity index 63% rename from implementations/node-ssr-web-vanilla/vitest.config.ts rename to implementations/node-ssr-web-vanilla/rstest.config.ts index 45b3046b..d7dac7f6 100644 --- a/implementations/node-ssr-web-vanilla/vitest.config.ts +++ b/implementations/node-ssr-web-vanilla/rstest.config.ts @@ -1,5 +1,7 @@ +import { defineConfig } from '@rstest/core' import { resolve } from 'node:path' -import { defineConfig } from 'vitest/config' + +const coverageReporters = process.env.CI === 'true' ? ['text-summary', 'lcov'] : ['text', 'html'] export default defineConfig({ resolve: { @@ -13,12 +15,11 @@ export default defineConfig({ '@contentful/optimization-node': resolve(__dirname, '../../platforms/javascript/node/src/'), }, }, - test: { - include: ['**/*.test.?(c|m)[jt]s?(x)'], - globals: true, - coverage: { - include: ['src/**/*'], - reporter: ['text', 'html'], - }, + include: ['**/*.test.?(c|m)[jt]s?(x)'], + globals: true, + testEnvironment: 'node', + coverage: { + include: ['src/**/*'], + reporters: coverageReporters, }, }) diff --git a/implementations/node-ssr-web-vanilla/src/app.test.ts b/implementations/node-ssr-web-vanilla/src/app.test.ts index c86b178c..7ae6d147 100644 --- a/implementations/node-ssr-web-vanilla/src/app.test.ts +++ b/implementations/node-ssr-web-vanilla/src/app.test.ts @@ -1,12 +1,12 @@ import request, { type Response } from 'supertest' import app from './app' -const CLIENT_ID = process.env.VITE_NINETAILED_CLIENT_ID ?? 'error' +const CLIENT_ID = process.env.PUBLIC_NINETAILED_CLIENT_ID ?? '' describe('GET /', () => { it('returns the client ID', async () => { const response: Response = await request(app).get('/smoke-test') - expect(response.text).toContain(`"${CLIENT_ID}"`) + expect(response.text).toContain(`"clientId":"${CLIENT_ID}"`) }) }) diff --git a/implementations/node-ssr-web-vanilla/src/app.ts b/implementations/node-ssr-web-vanilla/src/app.ts index e2cf5465..1f2b92f2 100644 --- a/implementations/node-ssr-web-vanilla/src/app.ts +++ b/implementations/node-ssr-web-vanilla/src/app.ts @@ -19,19 +19,19 @@ app.use(limiter) const config = { contentful: { - accessToken: process.env.VITE_CONTENTFUL_TOKEN, - environment: process.env.VITE_NINETAILED_ENVIRONMENT, - space: process.env.VITE_CONTENTFUL_SPACE_ID, - host: process.env.VITE_CONTENTFUL_CDA_HOST, - basePath: process.env.VITE_CONTENTFUL_BASE_PATH, - insecure: Boolean(process.env.VITE_CONTENTFUL_CDA_HOST), + accessToken: process.env.PUBLIC_CONTENTFUL_TOKEN, + environment: process.env.PUBLIC_NINETAILED_ENVIRONMENT, + space: process.env.PUBLIC_CONTENTFUL_SPACE_ID, + host: process.env.PUBLIC_CONTENTFUL_CDA_HOST, + basePath: process.env.PUBLIC_CONTENTFUL_BASE_PATH, + insecure: Boolean(process.env.PUBLIC_CONTENTFUL_CDA_HOST), }, optimization: { - clientId: process.env.VITE_NINETAILED_CLIENT_ID ?? '', - environment: process.env.VITE_NINETAILED_ENVIRONMENT, + clientId: process.env.PUBLIC_NINETAILED_CLIENT_ID ?? '', + environment: process.env.PUBLIC_NINETAILED_ENVIRONMENT, logLevel: 'debug', - analytics: { baseUrl: process.env.VITE_INSIGHTS_API_BASE_URL }, - personalization: { baseUrl: process.env.VITE_EXPERIENCE_API_BASE_URL }, + analytics: { baseUrl: process.env.PUBLIC_INSIGHTS_API_BASE_URL }, + personalization: { baseUrl: process.env.PUBLIC_EXPERIENCE_API_BASE_URL }, }, } as const diff --git a/implementations/react-native/env.config.ts b/implementations/react-native/env.config.ts index 8b47f86f..bc465ff2 100644 --- a/implementations/react-native/env.config.ts +++ b/implementations/react-native/env.config.ts @@ -1,13 +1,13 @@ import { - VITE_CONTENTFUL_BASE_PATH, - VITE_CONTENTFUL_CDA_HOST, - VITE_CONTENTFUL_ENVIRONMENT, - VITE_CONTENTFUL_SPACE_ID, - VITE_CONTENTFUL_TOKEN, - VITE_EXPERIENCE_API_BASE_URL, - VITE_INSIGHTS_API_BASE_URL, - VITE_NINETAILED_CLIENT_ID, - VITE_NINETAILED_ENVIRONMENT, + PUBLIC_CONTENTFUL_BASE_PATH, + PUBLIC_CONTENTFUL_CDA_HOST, + PUBLIC_CONTENTFUL_ENVIRONMENT, + PUBLIC_CONTENTFUL_SPACE_ID, + PUBLIC_CONTENTFUL_TOKEN, + PUBLIC_EXPERIENCE_API_BASE_URL, + PUBLIC_INSIGHTS_API_BASE_URL, + PUBLIC_NINETAILED_CLIENT_ID, + PUBLIC_NINETAILED_ENVIRONMENT, } from '@env' import { Platform } from 'react-native' @@ -46,21 +46,21 @@ function getAndroidCompatibleUrl(url: string): string { export const ENV_CONFIG = { contentful: { - spaceId: VITE_CONTENTFUL_SPACE_ID, - environment: VITE_CONTENTFUL_ENVIRONMENT, - accessToken: VITE_CONTENTFUL_TOKEN, - host: getAndroidCompatibleUrl(VITE_CONTENTFUL_CDA_HOST), - basePath: VITE_CONTENTFUL_BASE_PATH, + spaceId: PUBLIC_CONTENTFUL_SPACE_ID, + environment: PUBLIC_CONTENTFUL_ENVIRONMENT, + accessToken: PUBLIC_CONTENTFUL_TOKEN, + host: getAndroidCompatibleUrl(PUBLIC_CONTENTFUL_CDA_HOST), + basePath: PUBLIC_CONTENTFUL_BASE_PATH, }, optimization: { - clientId: VITE_NINETAILED_CLIENT_ID, - environment: VITE_NINETAILED_ENVIRONMENT, + clientId: PUBLIC_NINETAILED_CLIENT_ID, + environment: PUBLIC_NINETAILED_ENVIRONMENT, }, api: { - experienceBaseUrl: getAndroidCompatibleUrl(VITE_EXPERIENCE_API_BASE_URL), - insightsBaseUrl: getAndroidCompatibleUrl(VITE_INSIGHTS_API_BASE_URL), + experienceBaseUrl: getAndroidCompatibleUrl(PUBLIC_EXPERIENCE_API_BASE_URL), + insightsBaseUrl: getAndroidCompatibleUrl(PUBLIC_INSIGHTS_API_BASE_URL), }, entries: { diff --git a/implementations/react-native/env.d.ts b/implementations/react-native/env.d.ts index 36841588..8a85171a 100644 --- a/implementations/react-native/env.d.ts +++ b/implementations/react-native/env.d.ts @@ -1,11 +1,11 @@ declare module '@env' { - export const VITE_NINETAILED_CLIENT_ID: string - export const VITE_NINETAILED_ENVIRONMENT: string - export const VITE_EXPERIENCE_API_BASE_URL: string - export const VITE_INSIGHTS_API_BASE_URL: string - export const VITE_CONTENTFUL_TOKEN: string - export const VITE_CONTENTFUL_ENVIRONMENT: string - export const VITE_CONTENTFUL_SPACE_ID: string - export const VITE_CONTENTFUL_CDA_HOST: string - export const VITE_CONTENTFUL_BASE_PATH: string + export const PUBLIC_NINETAILED_CLIENT_ID: string + export const PUBLIC_NINETAILED_ENVIRONMENT: string + export const PUBLIC_EXPERIENCE_API_BASE_URL: string + export const PUBLIC_INSIGHTS_API_BASE_URL: string + export const PUBLIC_CONTENTFUL_TOKEN: string + export const PUBLIC_CONTENTFUL_ENVIRONMENT: string + export const PUBLIC_CONTENTFUL_SPACE_ID: string + export const PUBLIC_CONTENTFUL_CDA_HOST: string + export const PUBLIC_CONTENTFUL_BASE_PATH: string } diff --git a/implementations/react-native/scripts/run-e2e-android.sh b/implementations/react-native/scripts/run-e2e-android.sh index b4ce13c3..4d88c23f 100755 --- a/implementations/react-native/scripts/run-e2e-android.sh +++ b/implementations/react-native/scripts/run-e2e-android.sh @@ -16,8 +16,8 @@ # METRO_PORT - Port for Metro bundler (default: 8081) # SKIP_BUILD - Set to "true" to skip the Android build step (default: false) # CI - Set to "true" when running in CI environment (default: false) -# VITE_NINETAILED_CLIENT_ID - Ninetailed client ID (default: test-client-id) -# VITE_NINETAILED_ENVIRONMENT - Ninetailed environment (default: main) +# PUBLIC_NINETAILED_CLIENT_ID - Ninetailed client ID (default: test-client-id) +# PUBLIC_NINETAILED_ENVIRONMENT - Ninetailed environment (default: main) # # Usage: # ./scripts/run-e2e-android.sh # Full run with build @@ -249,15 +249,15 @@ create_env_file() { log_info "Creating .env file..." cat > "${RN_DIR}/.env" << EOF -VITE_NINETAILED_CLIENT_ID=${VITE_NINETAILED_CLIENT_ID:-test-client-id} -VITE_NINETAILED_ENVIRONMENT=${VITE_NINETAILED_ENVIRONMENT:-main} -VITE_EXPERIENCE_API_BASE_URL=http://localhost:${MOCK_SERVER_PORT}/experience/ -VITE_INSIGHTS_API_BASE_URL=http://localhost:${MOCK_SERVER_PORT}/insights/ -VITE_CONTENTFUL_TOKEN=${VITE_CONTENTFUL_TOKEN:-test-token} -VITE_CONTENTFUL_ENVIRONMENT=${VITE_CONTENTFUL_ENVIRONMENT:-master} -VITE_CONTENTFUL_SPACE_ID=${VITE_CONTENTFUL_SPACE_ID:-test-space} -VITE_CONTENTFUL_CDA_HOST=localhost:${MOCK_SERVER_PORT} -VITE_CONTENTFUL_BASE_PATH=/contentful/ +PUBLIC_NINETAILED_CLIENT_ID=${PUBLIC_NINETAILED_CLIENT_ID:-test-client-id} +PUBLIC_NINETAILED_ENVIRONMENT=${PUBLIC_NINETAILED_ENVIRONMENT:-main} +PUBLIC_EXPERIENCE_API_BASE_URL=http://localhost:${MOCK_SERVER_PORT}/experience/ +PUBLIC_INSIGHTS_API_BASE_URL=http://localhost:${MOCK_SERVER_PORT}/insights/ +PUBLIC_CONTENTFUL_TOKEN=${PUBLIC_CONTENTFUL_TOKEN:-test-token} +PUBLIC_CONTENTFUL_ENVIRONMENT=${PUBLIC_CONTENTFUL_ENVIRONMENT:-master} +PUBLIC_CONTENTFUL_SPACE_ID=${PUBLIC_CONTENTFUL_SPACE_ID:-test-space} +PUBLIC_CONTENTFUL_CDA_HOST=localhost:${MOCK_SERVER_PORT} +PUBLIC_CONTENTFUL_BASE_PATH=/contentful/ EOF log_info ".env file created at ${RN_DIR}/.env" diff --git a/implementations/react-native/types/env.d.ts b/implementations/react-native/types/env.d.ts index 9ff522e2..22a88a6a 100644 --- a/implementations/react-native/types/env.d.ts +++ b/implementations/react-native/types/env.d.ts @@ -1,11 +1,11 @@ declare module '@env' { - export const VITE_CONTENTFUL_SPACE_ID: string - export const VITE_CONTENTFUL_ENVIRONMENT: string - export const VITE_CONTENTFUL_TOKEN: string - export const VITE_CONTENTFUL_CDA_HOST: string - export const VITE_CONTENTFUL_BASE_PATH: string - export const VITE_NINETAILED_CLIENT_ID: string - export const VITE_NINETAILED_ENVIRONMENT: string - export const VITE_EXPERIENCE_API_BASE_URL: string - export const VITE_INSIGHTS_API_BASE_URL: string + export const PUBLIC_CONTENTFUL_SPACE_ID: string + export const PUBLIC_CONTENTFUL_ENVIRONMENT: string + export const PUBLIC_CONTENTFUL_TOKEN: string + export const PUBLIC_CONTENTFUL_CDA_HOST: string + export const PUBLIC_CONTENTFUL_BASE_PATH: string + export const PUBLIC_NINETAILED_CLIENT_ID: string + export const PUBLIC_NINETAILED_ENVIRONMENT: string + export const PUBLIC_EXPERIENCE_API_BASE_URL: string + export const PUBLIC_INSIGHTS_API_BASE_URL: string } diff --git a/implementations/web-vanilla/.env.example b/implementations/web-vanilla/.env.example index 39d0d995..ed05455f 100644 --- a/implementations/web-vanilla/.env.example +++ b/implementations/web-vanilla/.env.example @@ -1,15 +1,15 @@ DOTENV_CONFIG_QUIET=true -VITE_NINETAILED_CLIENT_ID="mock-client-id" -VITE_NINETAILED_ENVIRONMENT="main" +PUBLIC_NINETAILED_CLIENT_ID="mock-client-id" +PUBLIC_NINETAILED_ENVIRONMENT="main" -VITE_EXPERIENCE_API_BASE_URL="http://localhost:8000/experience/" -VITE_INSIGHTS_API_BASE_URL="http://localhost:8000/insights/" +PUBLIC_EXPERIENCE_API_BASE_URL="http://localhost:8000/experience/" +PUBLIC_INSIGHTS_API_BASE_URL="http://localhost:8000/insights/" -VITE_CONTENTFUL_TOKEN="mock-token" -VITE_CONTENTFUL_PREVIEW_TOKEN="mosk-preview-token" -VITE_CONTENTFUL_ENVIRONMENT="master" -VITE_CONTENTFUL_SPACE_ID="mock-space-id" +PUBLIC_CONTENTFUL_TOKEN="mock-token" +PUBLIC_CONTENTFUL_PREVIEW_TOKEN="mosk-preview-token" +PUBLIC_CONTENTFUL_ENVIRONMENT="master" +PUBLIC_CONTENTFUL_SPACE_ID="mock-space-id" -VITE_CONTENTFUL_CDA_HOST="localhost:8000" -VITE_CONTENTFUL_BASE_PATH="contentful" +PUBLIC_CONTENTFUL_CDA_HOST="localhost:8000" +PUBLIC_CONTENTFUL_BASE_PATH="contentful" diff --git a/implementations/web-vanilla/nginx/templates/default.conf.template b/implementations/web-vanilla/nginx/templates/default.conf.template index b054038d..b438e118 100644 --- a/implementations/web-vanilla/nginx/templates/default.conf.template +++ b/implementations/web-vanilla/nginx/templates/default.conf.template @@ -5,19 +5,19 @@ server { ssi on; ssi_types text/html; - set $NGINX_NINETAILED_CLIENT_ID "${VITE_NINETAILED_CLIENT_ID}"; - set $NGINX_NINETAILED_ENVIRONMENT "${VITE_NINETAILED_ENVIRONMENT}"; + set $NGINX_NINETAILED_CLIENT_ID "${PUBLIC_NINETAILED_CLIENT_ID}"; + set $NGINX_NINETAILED_ENVIRONMENT "${PUBLIC_NINETAILED_ENVIRONMENT}"; - set $NGINX_EXPERIENCE_API_BASE_URL "${VITE_EXPERIENCE_API_BASE_URL}"; - set $NGINX_INSIGHTS_API_BASE_URL "${VITE_INSIGHTS_API_BASE_URL}"; + set $NGINX_EXPERIENCE_API_BASE_URL "${PUBLIC_EXPERIENCE_API_BASE_URL}"; + set $NGINX_INSIGHTS_API_BASE_URL "${PUBLIC_INSIGHTS_API_BASE_URL}"; - set $NGINX_CONTENTFUL_TOKEN "${VITE_CONTENTFUL_TOKEN}"; - set $NGINX_CONTENTFUL_PREVIEW_TOKEN "${VITE_CONTENTFUL_PREVIEW_TOKEN}"; - set $NGINX_CONTENTFUL_ENVIRONMENT "${VITE_CONTENTFUL_ENVIRONMENT}"; - set $NGINX_CONTENTFUL_SPACE_ID "${VITE_CONTENTFUL_SPACE_ID}"; + set $NGINX_CONTENTFUL_TOKEN "${PUBLIC_CONTENTFUL_TOKEN}"; + set $NGINX_CONTENTFUL_PREVIEW_TOKEN "${PUBLIC_CONTENTFUL_PREVIEW_TOKEN}"; + set $NGINX_CONTENTFUL_ENVIRONMENT "${PUBLIC_CONTENTFUL_ENVIRONMENT}"; + set $NGINX_CONTENTFUL_SPACE_ID "${PUBLIC_CONTENTFUL_SPACE_ID}"; - set $NGINX_CONTENTFUL_CDA_HOST "${VITE_CONTENTFUL_CDA_HOST}"; - set $NGINX_CONTENTFUL_BASE_PATH "${VITE_CONTENTFUL_BASE_PATH}"; + set $NGINX_CONTENTFUL_CDA_HOST "${PUBLIC_CONTENTFUL_CDA_HOST}"; + set $NGINX_CONTENTFUL_BASE_PATH "${PUBLIC_CONTENTFUL_BASE_PATH}"; listen 80; diff --git a/implementations/web-vanilla/public/index.html b/implementations/web-vanilla/public/index.html index 6a124b5d..3fc66088 100644 --- a/implementations/web-vanilla/public/index.html +++ b/implementations/web-vanilla/public/index.html @@ -68,11 +68,13 @@ } } } + #utility-panel, #event-stream-panel { position: sticky; top: 0; margin-block-start: -1px; padding-block-start: 1px; + background: #fff; } #auto-observed, #manually-observed { @@ -315,6 +317,9 @@

Event Stream

// The `data-ctfl-entry-id` data attribute is required for auto-observing element.dataset.ctflEntryId = entry.sys?.id + // Store the baseline ID so we can continue updating + if (entry.fields.nt_experiences) element.dataset.ctflBaselineId = entry.sys?.id + // Other standard auto-observing attributes are optional if (personalization) { element.dataset.ctflPersonalizationId = personalization?.experienceId @@ -360,12 +365,25 @@

Event Stream

manuallyObservedEntryElements.set(element.dataset.entryId, [entry, personalization]) } - // Subscribe to the event stream - optimization.states.eventStream.subscribe((event) => { - if (!event) return + // Render all the entries + function renderEntries() { + document.querySelectorAll('[data-ctfl-entry-id]').forEach(async (element) => { + await addPersonalizedEntry( + element.dataset.ctflBaselineId ?? element.dataset.ctflEntryId, + element, + ) + }) - document.querySelector('#event-stream').appendChild(createEventDialog(event.type, event)) - }) + document.querySelectorAll('[data-entry-id]').forEach(async (element) => { + const [entry, personalization] = await addPersonalizedEntry( + element.dataset.entryId, + element, + false, + ) + + manuallyObserveEntryElement(element, entry, personalization) + }) + } // Subscribe to consent state optimization.states.consent.subscribe(async (consent) => { @@ -383,25 +401,25 @@

Event Stream

}) }) + // Subscribe to the event stream + optimization.states.eventStream.subscribe((event) => { + if (!event) return + + document.querySelector('#event-stream').appendChild(createEventDialog(event.type, event)) + }) + + // Subscribe to personalizations state + optimization.states.personalizations.subscribe(async (personalizations) => { + if (!personalizations) return + + renderEntries() + }) + // Subscribe to profile state, find entries in the markup, and render them optimization.states.profile.subscribe((profile) => { if (!profile) return toggleIdentity(profile.traits && Object.keys(profile.traits).length) - - document.querySelectorAll('[data-ctfl-entry-id]').forEach(async (element) => { - await addPersonalizedEntry(element.dataset.ctflEntryId, element) - }) - - document.querySelectorAll('[data-entry-id]').forEach(async (element) => { - const [entry, personalization] = await addPersonalizedEntry( - element.dataset.entryId, - element, - false, - ) - - manuallyObserveEntryElement(element, entry, personalization) - }) }) diff --git a/lib/build-tools/package.json b/lib/build-tools/package.json index bff0ebfc..f04b7f88 100644 --- a/lib/build-tools/package.json +++ b/lib/build-tools/package.json @@ -15,18 +15,19 @@ "dist/**/*" ], "scripts": { - "test:unit": "vitest run --coverage", + "test:unit": "rstest run --coverage", "typecheck": "tsc --noEmit" }, "dependencies": { + "@rsdoctor/rspack-plugin": "catalog:", "tsx": "catalog:" }, "devDependencies": { "@types/node": "catalog:", - "@vitest/coverage-v8": "catalog:", + "@rstest/core": "catalog:", + "@rstest/coverage-istanbul": "catalog:", "tslib": "catalog:", "type-fest": "^4.41.0", - "typescript": "catalog:", - "vitest": "catalog:" + "typescript": "catalog:" } } diff --git a/lib/build-tools/rstest.config.ts b/lib/build-tools/rstest.config.ts new file mode 100644 index 00000000..b0c1c5bf --- /dev/null +++ b/lib/build-tools/rstest.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from '@rstest/core' + +const coverageReporters = process.env.CI === 'true' ? ['text-summary', 'lcov'] : ['text', 'html'] + +export default defineConfig({ + include: ['**/*.test.?(c|m)[jt]s?(x)'], + globals: true, + testEnvironment: 'node', + coverage: { + exclude: ['**/test/*'], + include: ['src/**/*'], + reporters: coverageReporters, + }, +}) diff --git a/lib/build-tools/src/index.ts b/lib/build-tools/src/index.ts index 710af321..43e44d1d 100644 --- a/lib/build-tools/src/index.ts +++ b/lib/build-tools/src/index.ts @@ -1,27 +1,3 @@ -import { readFileSync } from 'node:fs' -import { resolve } from 'node:path' -import type { PackageJson } from 'type-fest' - export { emitDualDts } from './emitDualDts' - -export function hasPackageName( - packageJson: unknown, -): packageJson is PackageJson & { name: string } { - return ( - typeof packageJson === 'object' && - packageJson !== null && - typeof Reflect.get(packageJson, 'name') === 'string' - ) -} - -export function getPackageName(packageDir: string, fallbackName: string): string { - const packageJsonContent: unknown = JSON.parse( - readFileSync(resolve(packageDir, 'package.json'), 'utf-8'), - ) - - if (hasPackageName(packageJsonContent)) { - return packageJsonContent.name - } - - return fallbackName -} +export { getPackageName, hasPackageName } from './package' +export { ensureUmdDefaultExport, maybeEnableRsDoctor } from './rslib' diff --git a/lib/build-tools/src/index.test.ts b/lib/build-tools/src/package.test.ts similarity index 96% rename from lib/build-tools/src/index.test.ts rename to lib/build-tools/src/package.test.ts index 7f734501..5dd1a0af 100644 --- a/lib/build-tools/src/index.test.ts +++ b/lib/build-tools/src/package.test.ts @@ -1,7 +1,7 @@ import { mkdtempSync, rmSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' -import { getPackageName, hasPackageName } from './index' +import { getPackageName, hasPackageName } from './package' describe('hasPackageName', () => { it('returns true when name is a string', () => { diff --git a/lib/build-tools/src/package.ts b/lib/build-tools/src/package.ts new file mode 100644 index 00000000..3020d3e2 --- /dev/null +++ b/lib/build-tools/src/package.ts @@ -0,0 +1,25 @@ +import { readFileSync } from 'node:fs' +import { resolve } from 'node:path' +import type { PackageJson } from 'type-fest' + +export function hasPackageName( + packageJson: unknown, +): packageJson is PackageJson & { name: string } { + return ( + typeof packageJson === 'object' && + packageJson !== null && + typeof Reflect.get(packageJson, 'name') === 'string' + ) +} + +export function getPackageName(packageDir: string, fallbackName: string): string { + const packageJsonContent: unknown = JSON.parse( + readFileSync(resolve(packageDir, 'package.json'), 'utf-8'), + ) + + if (hasPackageName(packageJsonContent)) { + return packageJsonContent.name + } + + return fallbackName +} diff --git a/lib/build-tools/src/rslib.test.ts b/lib/build-tools/src/rslib.test.ts new file mode 100644 index 00000000..6ebf9cd5 --- /dev/null +++ b/lib/build-tools/src/rslib.test.ts @@ -0,0 +1,94 @@ +import { RsdoctorRspackPlugin } from '@rsdoctor/rspack-plugin' +import { ensureUmdDefaultExport, maybeEnableRsDoctor } from './rslib' + +describe('maybeEnableRsDoctor', () => { + const originalRsDoctor = process.env.RSDOCTOR + + afterEach(() => { + if (originalRsDoctor === undefined) { + delete process.env.RSDOCTOR + return + } + + process.env.RSDOCTOR = originalRsDoctor + }) + + it('adds RsDoctor plugin when RSDOCTOR=true and plugins is an array', () => { + process.env.RSDOCTOR = 'true' + + const config = { plugins: [] as unknown[] } + + maybeEnableRsDoctor(config) + + expect(config.plugins).toHaveLength(1) + expect(config.plugins[0]).toBeInstanceOf(RsdoctorRspackPlugin) + }) + + it('does nothing when RSDOCTOR is not true', () => { + process.env.RSDOCTOR = 'false' + + const config = { plugins: [] as unknown[] } + + maybeEnableRsDoctor(config) + + expect(config.plugins).toHaveLength(0) + }) + + it('does nothing when plugins is not an array', () => { + process.env.RSDOCTOR = 'true' + + expect(() => { + maybeEnableRsDoctor({}) + }).not.toThrow() + expect(() => { + maybeEnableRsDoctor({ plugins: {} }) + }).not.toThrow() + }) +}) + +describe('ensureUmdDefaultExport', () => { + it('sets library.export to default for UMD builds', () => { + const config = { + output: { + library: { + type: 'umd', + }, + }, + } + + ensureUmdDefaultExport(config) + + expect(config.output.library).toEqual({ + type: 'umd', + export: 'default', + }) + }) + + it('does not change library export for non-UMD builds', () => { + const config = { + output: { + library: { + type: 'module', + }, + }, + } + + ensureUmdDefaultExport(config) + + expect(config.output.library).toEqual({ + type: 'module', + }) + }) + + it('does not throw for malformed configs', () => { + expect(() => { + ensureUmdDefaultExport({}) + }).not.toThrow() + expect(() => { + ensureUmdDefaultExport({ output: null }) + }).not.toThrow() + expect(() => { + ensureUmdDefaultExport({ output: { library: null } }) + }).not.toThrow() + }) +}) diff --git a/lib/build-tools/src/rslib.ts b/lib/build-tools/src/rslib.ts new file mode 100644 index 00000000..462ecdfe --- /dev/null +++ b/lib/build-tools/src/rslib.ts @@ -0,0 +1,47 @@ +import { RsdoctorRspackPlugin } from '@rsdoctor/rspack-plugin' + +const isRecord = (value: unknown): value is Record => + typeof value === 'object' && value !== null + +export const maybeEnableRsDoctor = (config: { plugins?: unknown }): void => { + if (process.env.RSDOCTOR !== 'true') { + return + } + + if (!Array.isArray(config.plugins)) { + return + } + + config.plugins.push( + new RsdoctorRspackPlugin({ + disableClientServer: true, + // Keep output deterministic and local to the package. + output: { + reportDir: '.rsdoctor', + }, + }), + ) +} + +export const ensureUmdDefaultExport = (config: { output?: unknown }): void => { + const { output } = config + + if (!isRecord(output)) { + return + } + + const { library } = output + + if (!isRecord(library)) { + return + } + + const { type } = library + + if (type !== 'umd') { + return + } + + // Expose the default export directly on the UMD global. + library.export = 'default' +} diff --git a/lib/build-tools/vitest.config.ts b/lib/build-tools/vitest.config.ts deleted file mode 100644 index 62f7e7c5..00000000 --- a/lib/build-tools/vitest.config.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { defineConfig } from 'vitest/config' - -export default defineConfig({ - test: { - coverage: { - exclude: ['**/test/*'], - include: ['src/**/*'], - reporter: ['text', 'html'], - }, - environment: 'node', - globals: true, - include: ['**/*.test.?(c|m)[jt]s?(x)'], - }, -}) diff --git a/lib/logger/package.json b/lib/logger/package.json index b9bb7f0a..c3d97edd 100644 --- a/lib/logger/package.json +++ b/lib/logger/package.json @@ -24,9 +24,11 @@ "dist/**/*" ], "scripts": { - "build": "pnpm clean; rslib build && build-tools emit-dual-dts ./dist", + "build": "pnpm clean; pnpm build:dist", + "build:ci": "pnpm build:dist", + "build:dist": "rslib build && build-tools emit-dual-dts ./dist", "clean": "rimraf ./.rslib ./dist ./coverage .tsbuildinfo", - "test:unit": "vitest run --coverage", + "test:unit": "rstest run --coverage", "typecheck": "tsc --noEmit" }, "dependencies": { @@ -37,10 +39,10 @@ "@microsoft/api-extractor": "catalog:", "@rslib/core": "catalog:", "@types/node": "catalog:", - "@vitest/coverage-v8": "catalog:", + "@rstest/core": "catalog:", + "@rstest/coverage-istanbul": "catalog:", "rimraf": "catalog:", "tslib": "catalog:", - "typescript": "catalog:", - "vitest": "catalog:" + "typescript": "catalog:" } } diff --git a/lib/logger/rstest.config.ts b/lib/logger/rstest.config.ts new file mode 100644 index 00000000..b0c1c5bf --- /dev/null +++ b/lib/logger/rstest.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from '@rstest/core' + +const coverageReporters = process.env.CI === 'true' ? ['text-summary', 'lcov'] : ['text', 'html'] + +export default defineConfig({ + include: ['**/*.test.?(c|m)[jt]s?(x)'], + globals: true, + testEnvironment: 'node', + coverage: { + exclude: ['**/test/*'], + include: ['src/**/*'], + reporters: coverageReporters, + }, +}) diff --git a/lib/logger/src/ConsoleLogSink.test.ts b/lib/logger/src/ConsoleLogSink.test.ts index 3f157b95..d8c16505 100644 --- a/lib/logger/src/ConsoleLogSink.test.ts +++ b/lib/logger/src/ConsoleLogSink.test.ts @@ -1,4 +1,5 @@ /* eslint-disable no-console -- testing console */ +import { rs } from '@rstest/core' import type { LogEvent, LogLevels } from 'diary' import { ConsoleLogSink } from './ConsoleLogSink' @@ -6,16 +7,16 @@ import { ConsoleLogSink } from './ConsoleLogSink' const originalConsole = { ...console } describe('ConsoleLogSink', () => { - let spies: Record> + let spies: Record> const loggerName = '@contentful/optimization' beforeEach(() => { spies = { - debug: vi.fn(), - info: vi.fn(), - log: vi.fn(), - warn: vi.fn(), - error: vi.fn(), + debug: rs.fn(), + info: rs.fn(), + log: rs.fn(), + warn: rs.fn(), + error: rs.fn(), } // Patch console methods Object.assign(console, spies) diff --git a/lib/logger/src/Logger.test.ts b/lib/logger/src/Logger.test.ts index ff7d6e5d..36d70b3b 100644 --- a/lib/logger/src/Logger.test.ts +++ b/lib/logger/src/Logger.test.ts @@ -1,3 +1,4 @@ +import { rs } from '@rstest/core' import Logger, { type LogEvent, type LogSink } from '.' const TEST_LOCATION = 'LoggerTest' @@ -24,8 +25,8 @@ describe('Logger', () => { }) it('adds and removes sinks correctly', () => { - const ingest1 = vi.fn() - const ingest2 = vi.fn() + const ingest1 = rs.fn() + const ingest2 = rs.fn() const sink1: LogSink = { name: 'sink1', ingest: ingest1 } const sink2: LogSink = { name: 'sink2', ingest: ingest2 } @@ -52,8 +53,8 @@ describe('Logger', () => { }) it('replaces a sink with the same name', () => { - const ingest1 = vi.fn() - const ingest2 = vi.fn() + const ingest1 = rs.fn() + const ingest2 = rs.fn() const sink1: LogSink = { name: 'mySink', ingest: ingest1 } const sink2: LogSink = { name: 'mySink', ingest: ingest2 } diff --git a/lib/logger/vitest.config.ts b/lib/logger/vitest.config.ts deleted file mode 100644 index 62f7e7c5..00000000 --- a/lib/logger/vitest.config.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { defineConfig } from 'vitest/config' - -export default defineConfig({ - test: { - coverage: { - exclude: ['**/test/*'], - include: ['src/**/*'], - reporter: ['text', 'html'], - }, - environment: 'node', - globals: true, - include: ['**/*.test.?(c|m)[jt]s?(x)'], - }, -}) diff --git a/lib/mocks/src/loggerMock.ts b/lib/mocks/src/loggerMock.ts index 2a86772b..ee944b43 100644 --- a/lib/mocks/src/loggerMock.ts +++ b/lib/mocks/src/loggerMock.ts @@ -1,5 +1,45 @@ -import type { Mock } from 'vitest' -import { vi } from 'vitest' +interface MockFn { + (...args: unknown[]): unknown + mockClear: () => void +} + +interface MockingApi { + fn: () => MockFn +} + +function hasFn(value: unknown): value is MockingApi { + if (typeof value !== 'object' || value === null) { + return false + } + + return typeof Reflect.get(value, 'fn') === 'function' +} + +function getGlobalCandidate(name: 'rs' | 'vi'): unknown { + if (name === 'rs') { + return 'rs' in globalThis ? Reflect.get(globalThis, 'rs') : undefined + } + + return 'vi' in globalThis ? Reflect.get(globalThis, 'vi') : undefined +} + +function resolveMockingApi(): MockingApi { + const rsCandidate = getGlobalCandidate('rs') + if (hasFn(rsCandidate)) { + return rsCandidate + } + + const viCandidate = getGlobalCandidate('vi') + if (hasFn(viCandidate)) { + return viCandidate + } + + throw new Error('No test mock API available. Expected global "rs" (Rstest) or "vi" (Vitest).') +} + +function createMockFn(): MockFn { + return resolveMockingApi().fn() +} export interface MockLogger { debug: (...args: unknown[]) => void @@ -49,8 +89,6 @@ export interface MockScopedLogger { fatal: (message: string | Error, ...args: unknown[]) => void } -type MockFn = Mock<(...args: unknown[]) => void> - export interface TestMockLogger { debug: MockFn info: MockFn @@ -76,15 +114,15 @@ export interface TestMockLogger { * ``` */ export const mockLogger: TestMockLogger = { - debug: vi.fn(), - info: vi.fn(), - log: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - fatal: vi.fn(), - addSink: vi.fn(), - removeSink: vi.fn(), - removeSinks: vi.fn(), + debug: createMockFn(), + info: createMockFn(), + log: createMockFn(), + warn: createMockFn(), + error: createMockFn(), + fatal: createMockFn(), + addSink: createMockFn(), + removeSink: createMockFn(), + removeSinks: createMockFn(), } export interface LoggerMockModule { diff --git a/package.json b/package.json index cb552460..c8c8c518 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "pm2:stop:all": "pm2 stop all", "prepare": "husky", "build": "pnpm --filter logger build && pnpm --filter @contentful/* --stream build", + "build:ci": "pnpm --filter logger build:ci && pnpm --filter @contentful/* --stream build:ci", "build:pkgs": "pnpm build && pnpm pack --filter @contentful/* --pack-destination pkgs", "build:implementations": "pnpm build && pnpm --filter @implementation/* build", "clean": "pnpm -r --parallel clean", @@ -23,6 +24,7 @@ "serve:mocks": "pnpm --filter mocks serve", "test:e2e": "pnpm --parallel --filter @implementation/* test:e2e", "test:unit": "pnpm --filter @contentful/* test:unit", + "test:unit:ci": "CI=true pnpm run test:unit", "test:unit:implementations": "pnpm --filter @implementation/* test:unit", "typecheck": "pnpm -r typecheck", "version:node": "node -v", diff --git a/platforms/javascript/node/package.json b/platforms/javascript/node/package.json index 5cc015ef..301f4b11 100644 --- a/platforms/javascript/node/package.json +++ b/platforms/javascript/node/package.json @@ -23,10 +23,12 @@ "dist/**/*" ], "scripts": { - "build": "pnpm clean; rslib build && build-tools emit-dual-dts ./dist", + "build": "pnpm clean; pnpm build:dist", + "build:ci": "pnpm build:dist", + "build:dist": "rslib build && build-tools emit-dual-dts ./dist", "clean": "rimraf ./.rslib ./dist ./coverage .tsbuildinfo", "dev": "tsx watch --env-file=.env ./server.ts", - "test:unit": "vitest run --coverage", + "test:unit": "rstest run --coverage", "typecheck": "tsc --noEmit" }, "dependencies": { @@ -40,7 +42,8 @@ "@rslib/core": "catalog:", "@types/express": "catalog:", "@types/node": "catalog:", - "@vitest/coverage-v8": "catalog:", + "@rstest/core": "catalog:", + "@rstest/coverage-istanbul": "catalog:", "build-tools": "workspace:*", "contentful": "catalog:", "express": "catalog:", @@ -49,7 +52,6 @@ "rimraf": "catalog:", "tslib": "catalog:", "tsx": "catalog:", - "typescript": "catalog:", - "vitest": "catalog:" + "typescript": "catalog:" } } diff --git a/platforms/javascript/node/vitest.config.ts b/platforms/javascript/node/rstest.config.ts similarity index 63% rename from platforms/javascript/node/vitest.config.ts rename to platforms/javascript/node/rstest.config.ts index 69a4462a..149fefc0 100644 --- a/platforms/javascript/node/vitest.config.ts +++ b/platforms/javascript/node/rstest.config.ts @@ -1,5 +1,7 @@ +import { defineConfig } from '@rstest/core' import { resolve } from 'node:path' -import { defineConfig } from 'vitest/config' + +const coverageReporters = process.env.CI === 'true' ? ['text-summary', 'lcov'] : ['text', 'html'] export default defineConfig({ resolve: { @@ -16,13 +18,11 @@ export default defineConfig({ logger: resolve(__dirname, '../../../lib/logger/src/'), }, }, - test: { - environment: 'node', - include: ['**/*.test.?(c|m)[jt]s?(x)'], - globals: true, - coverage: { - include: ['src/**/*'], - reporter: ['text', 'html'], - }, + include: ['**/*.test.?(c|m)[jt]s?(x)'], + globals: true, + testEnvironment: 'node', + coverage: { + include: ['src/**/*'], + reporters: coverageReporters, }, }) diff --git a/platforms/javascript/node/server.ts b/platforms/javascript/node/server.ts index 7e2a7508..ef854bda 100644 --- a/platforms/javascript/node/server.ts +++ b/platforms/javascript/node/server.ts @@ -24,20 +24,20 @@ app.set('view engine', 'pug') // configure Pug as the view engine app.set('views', path.join(__dirname, '.')) // define the directory for view templates const sdk = new Optimization({ - clientId: process.env.VITE_NINETAILED_CLIENT_ID ?? '', - environment: process.env.VITE_NINETAILED_ENVIRONMENT ?? '', + clientId: process.env.PUBLIC_NINETAILED_CLIENT_ID ?? '', + environment: process.env.PUBLIC_NINETAILED_ENVIRONMENT ?? '', logLevel: 'debug', - analytics: { baseUrl: process.env.VITE_INSIGHTS_API_BASE_URL }, - personalization: { baseUrl: process.env.VITE_EXPERIENCE_API_BASE_URL }, + analytics: { baseUrl: process.env.PUBLIC_INSIGHTS_API_BASE_URL }, + personalization: { baseUrl: process.env.PUBLIC_EXPERIENCE_API_BASE_URL }, }) const ctfl = contentful.createClient({ - accessToken: process.env.VITE_CONTENTFUL_TOKEN ?? '', - environment: process.env.VITE_CONTENTFUL_ENVIRONMENT ?? '', - space: process.env.VITE_CONTENTFUL_SPACE_ID ?? '', - host: process.env.VITE_CONTENTFUL_CDA_HOST ?? '', - basePath: process.env.VITE_CONTENTFUL_BASE_PATH ?? '', - insecure: Boolean(process.env.VITE_CONTENTFUL_CDA_HOST), + accessToken: process.env.PUBLIC_CONTENTFUL_TOKEN ?? '', + environment: process.env.PUBLIC_CONTENTFUL_ENVIRONMENT ?? '', + space: process.env.PUBLIC_CONTENTFUL_SPACE_ID ?? '', + host: process.env.PUBLIC_CONTENTFUL_CDA_HOST ?? '', + basePath: process.env.PUBLIC_CONTENTFUL_BASE_PATH ?? '', + insecure: Boolean(process.env.PUBLIC_CONTENTFUL_CDA_HOST), }) interface ContentEntrySkeleton { diff --git a/platforms/javascript/react-native/__mocks__/@react-native-community/netinfo.ts b/platforms/javascript/react-native/__mocks__/@react-native-community/netinfo.ts index 50afc2e4..c99a5f49 100644 --- a/platforms/javascript/react-native/__mocks__/@react-native-community/netinfo.ts +++ b/platforms/javascript/react-native/__mocks__/@react-native-community/netinfo.ts @@ -1,11 +1,11 @@ -import { vi } from 'vitest' +import { rs } from '@rstest/core' // Manual mock for @react-native-community/netinfo export let capturedCallback: | ((state: { isInternetReachable: boolean | null; isConnected: boolean | null }) => void) | null = null -export const mockUnsubscribe = vi.fn() +export const mockUnsubscribe = rs.fn() export function resetMock(): void { capturedCallback = null diff --git a/platforms/javascript/react-native/dev/README.md b/platforms/javascript/react-native/dev/README.md index 89182f41..a2e737f8 100644 --- a/platforms/javascript/react-native/dev/README.md +++ b/platforms/javascript/react-native/dev/README.md @@ -201,9 +201,9 @@ dev/ The app connects to the mock server by default. Configuration is in `env.config.ts`: -- `VITE_EXPERIENCE_API_BASE_URL`: Experience API endpoint -- `VITE_INSIGHTS_API_BASE_URL`: Insights API endpoint -- `VITE_CONTENTFUL_CDA_HOST`: Contentful mock server host +- `PUBLIC_EXPERIENCE_API_BASE_URL`: Experience API endpoint +- `PUBLIC_INSIGHTS_API_BASE_URL`: Insights API endpoint +- `PUBLIC_CONTENTFUL_CDA_HOST`: Contentful mock server host ## What This App Tests diff --git a/platforms/javascript/react-native/dev/env.config.ts b/platforms/javascript/react-native/dev/env.config.ts index 9aca2ed2..dabb86f7 100644 --- a/platforms/javascript/react-native/dev/env.config.ts +++ b/platforms/javascript/react-native/dev/env.config.ts @@ -7,15 +7,15 @@ import { Platform } from 'react-native' -const VITE_NINETAILED_CLIENT_ID = 'test-client-id' -const VITE_NINETAILED_ENVIRONMENT = 'main' -const VITE_EXPERIENCE_API_BASE_URL = 'http://localhost:8000/experience/' -const VITE_INSIGHTS_API_BASE_URL = 'http://localhost:8000/insights/' -const VITE_CONTENTFUL_TOKEN = 'test-token' -const VITE_CONTENTFUL_ENVIRONMENT = 'master' -const VITE_CONTENTFUL_SPACE_ID = 'test-space' -const VITE_CONTENTFUL_CDA_HOST = 'localhost:8000' -const VITE_CONTENTFUL_BASE_PATH = '/contentful/' +const PUBLIC_NINETAILED_CLIENT_ID = 'test-client-id' +const PUBLIC_NINETAILED_ENVIRONMENT = 'main' +const PUBLIC_EXPERIENCE_API_BASE_URL = 'http://localhost:8000/experience/' +const PUBLIC_INSIGHTS_API_BASE_URL = 'http://localhost:8000/insights/' +const PUBLIC_CONTENTFUL_TOKEN = 'test-token' +const PUBLIC_CONTENTFUL_ENVIRONMENT = 'master' +const PUBLIC_CONTENTFUL_SPACE_ID = 'test-space' +const PUBLIC_CONTENTFUL_CDA_HOST = 'localhost:8000' +const PUBLIC_CONTENTFUL_BASE_PATH = '/contentful/' interface EnvConfig { contentful: { @@ -55,21 +55,21 @@ function getAndroidCompatibleUrl(url: string): string { export const ENV_CONFIG = { contentful: { - spaceId: VITE_CONTENTFUL_SPACE_ID, - environment: VITE_CONTENTFUL_ENVIRONMENT, - accessToken: VITE_CONTENTFUL_TOKEN, - host: getAndroidCompatibleUrl(VITE_CONTENTFUL_CDA_HOST), - basePath: VITE_CONTENTFUL_BASE_PATH, + spaceId: PUBLIC_CONTENTFUL_SPACE_ID, + environment: PUBLIC_CONTENTFUL_ENVIRONMENT, + accessToken: PUBLIC_CONTENTFUL_TOKEN, + host: getAndroidCompatibleUrl(PUBLIC_CONTENTFUL_CDA_HOST), + basePath: PUBLIC_CONTENTFUL_BASE_PATH, }, optimization: { - clientId: VITE_NINETAILED_CLIENT_ID, - environment: VITE_NINETAILED_ENVIRONMENT, + clientId: PUBLIC_NINETAILED_CLIENT_ID, + environment: PUBLIC_NINETAILED_ENVIRONMENT, }, api: { - experienceBaseUrl: getAndroidCompatibleUrl(VITE_EXPERIENCE_API_BASE_URL), - insightsBaseUrl: getAndroidCompatibleUrl(VITE_INSIGHTS_API_BASE_URL), + experienceBaseUrl: getAndroidCompatibleUrl(PUBLIC_EXPERIENCE_API_BASE_URL), + insightsBaseUrl: getAndroidCompatibleUrl(PUBLIC_INSIGHTS_API_BASE_URL), }, entries: { diff --git a/platforms/javascript/react-native/dev/env.d.ts b/platforms/javascript/react-native/dev/env.d.ts index 36841588..8a85171a 100644 --- a/platforms/javascript/react-native/dev/env.d.ts +++ b/platforms/javascript/react-native/dev/env.d.ts @@ -1,11 +1,11 @@ declare module '@env' { - export const VITE_NINETAILED_CLIENT_ID: string - export const VITE_NINETAILED_ENVIRONMENT: string - export const VITE_EXPERIENCE_API_BASE_URL: string - export const VITE_INSIGHTS_API_BASE_URL: string - export const VITE_CONTENTFUL_TOKEN: string - export const VITE_CONTENTFUL_ENVIRONMENT: string - export const VITE_CONTENTFUL_SPACE_ID: string - export const VITE_CONTENTFUL_CDA_HOST: string - export const VITE_CONTENTFUL_BASE_PATH: string + export const PUBLIC_NINETAILED_CLIENT_ID: string + export const PUBLIC_NINETAILED_ENVIRONMENT: string + export const PUBLIC_EXPERIENCE_API_BASE_URL: string + export const PUBLIC_INSIGHTS_API_BASE_URL: string + export const PUBLIC_CONTENTFUL_TOKEN: string + export const PUBLIC_CONTENTFUL_ENVIRONMENT: string + export const PUBLIC_CONTENTFUL_SPACE_ID: string + export const PUBLIC_CONTENTFUL_CDA_HOST: string + export const PUBLIC_CONTENTFUL_BASE_PATH: string } diff --git a/platforms/javascript/react-native/dev/scripts/run-dev-dashboard.sh b/platforms/javascript/react-native/dev/scripts/run-dev-dashboard.sh index fc113948..d4e64aed 100755 --- a/platforms/javascript/react-native/dev/scripts/run-dev-dashboard.sh +++ b/platforms/javascript/react-native/dev/scripts/run-dev-dashboard.sh @@ -185,15 +185,15 @@ create_env_file() { log_step "Creating .env File" cat > "${DEV_DIR}/.env" << EOF -VITE_NINETAILED_CLIENT_ID=${VITE_NINETAILED_CLIENT_ID:-test-client-id} -VITE_NINETAILED_ENVIRONMENT=${VITE_NINETAILED_ENVIRONMENT:-main} -VITE_EXPERIENCE_API_BASE_URL=http://localhost:${MOCK_SERVER_PORT}/experience/ -VITE_INSIGHTS_API_BASE_URL=http://localhost:${MOCK_SERVER_PORT}/insights/ -VITE_CONTENTFUL_TOKEN=${VITE_CONTENTFUL_TOKEN:-test-token} -VITE_CONTENTFUL_ENVIRONMENT=${VITE_CONTENTFUL_ENVIRONMENT:-master} -VITE_CONTENTFUL_SPACE_ID=${VITE_CONTENTFUL_SPACE_ID:-test-space} -VITE_CONTENTFUL_CDA_HOST=localhost:${MOCK_SERVER_PORT} -VITE_CONTENTFUL_BASE_PATH=/contentful/ +PUBLIC_NINETAILED_CLIENT_ID=${PUBLIC_NINETAILED_CLIENT_ID:-test-client-id} +PUBLIC_NINETAILED_ENVIRONMENT=${PUBLIC_NINETAILED_ENVIRONMENT:-main} +PUBLIC_EXPERIENCE_API_BASE_URL=http://localhost:${MOCK_SERVER_PORT}/experience/ +PUBLIC_INSIGHTS_API_BASE_URL=http://localhost:${MOCK_SERVER_PORT}/insights/ +PUBLIC_CONTENTFUL_TOKEN=${PUBLIC_CONTENTFUL_TOKEN:-test-token} +PUBLIC_CONTENTFUL_ENVIRONMENT=${PUBLIC_CONTENTFUL_ENVIRONMENT:-master} +PUBLIC_CONTENTFUL_SPACE_ID=${PUBLIC_CONTENTFUL_SPACE_ID:-test-space} +PUBLIC_CONTENTFUL_CDA_HOST=localhost:${MOCK_SERVER_PORT} +PUBLIC_CONTENTFUL_BASE_PATH=/contentful/ EOF log_info ".env file created at ${DEV_DIR}/.env" diff --git a/platforms/javascript/react-native/package.json b/platforms/javascript/react-native/package.json index 9c891bc9..e251b4f5 100644 --- a/platforms/javascript/react-native/package.json +++ b/platforms/javascript/react-native/package.json @@ -23,9 +23,11 @@ "dist/**/*" ], "scripts": { - "build": "pnpm clean; rslib build && build-tools emit-dual-dts ./dist", + "build": "pnpm clean; pnpm build:dist", + "build:ci": "pnpm build:dist", + "build:dist": "rslib build && build-tools emit-dual-dts ./dist", "clean": "rimraf ./.rslib ./dist ./coverage .tsbuildinfo", - "test:unit": "vitest run --coverage", + "test:unit": "rstest run --coverage", "typecheck": "tsc --noEmit", "dev:android": "cd dev && react-native run-android", "dev:ios": "cd dev && react-native run-ios", @@ -70,7 +72,8 @@ "@types/react": "^18.3.18", "@types/react-test-renderer": "^18.0.0", "@rslib/core": "catalog:", - "@vitest/coverage-v8": "catalog:", + "@rstest/core": "catalog:", + "@rstest/coverage-istanbul": "catalog:", "babel-jest": "^29.6.3", "build-tools": "workspace:*", "contentful": "catalog:", @@ -83,8 +86,7 @@ "react-test-renderer": "18.3.1", "rimraf": "catalog:", "tslib": "catalog:", - "typescript": "catalog:", - "vitest": "catalog:" + "typescript": "catalog:" }, "peerDependencies": { "@react-native-community/netinfo": ">=9.0.0", diff --git a/platforms/javascript/react-native/rstest.config.ts b/platforms/javascript/react-native/rstest.config.ts new file mode 100644 index 00000000..c12955db --- /dev/null +++ b/platforms/javascript/react-native/rstest.config.ts @@ -0,0 +1,36 @@ +import { defineConfig } from '@rstest/core' +import { resolve } from 'node:path' + +const coverageReporters = + process.env.CI === 'true' ? ['text-summary', 'lcov'] : ['text', 'json', 'html'] + +export default defineConfig({ + resolve: { + alias: { + '@contentful/optimization-api-client': resolve( + __dirname, + '../../../universal/api-client/src/', + ), + '@contentful/optimization-api-schemas': resolve( + __dirname, + '../../../universal/api-schemas/src/', + ), + '@contentful/optimization-core': resolve(__dirname, '../../../universal/core/src/index.ts'), + '@react-native-community/netinfo': resolve( + __dirname, + './__mocks__/@react-native-community/netinfo.ts', + ), + logger: resolve(__dirname, '../../../lib/logger/src/'), + 'mocks/loggerMock': resolve(__dirname, '../../../lib/mocks/src/loggerMock.ts'), + 'react-native': resolve(__dirname, './src/test/reactNativeShim.ts'), + }, + }, + include: ['**/*.test.?(c|m)[jt]s?(x)'], + globals: true, + testEnvironment: 'node', + coverage: { + reporters: coverageReporters, + include: ['src/**/*.{ts,tsx}'], + exclude: ['**/*.test.ts', '**/*.test.tsx', '**/test/**'], + }, +}) diff --git a/platforms/javascript/react-native/src/components/OptimizationNavigationContainer.test.tsx b/platforms/javascript/react-native/src/components/OptimizationNavigationContainer.test.tsx index 2a33dca1..2f111ba1 100644 --- a/platforms/javascript/react-native/src/components/OptimizationNavigationContainer.test.tsx +++ b/platforms/javascript/react-native/src/components/OptimizationNavigationContainer.test.tsx @@ -1,46 +1,46 @@ -import { describe, expect, it, vi } from 'vitest' +import { describe, expect, it, rs } from '@rstest/core' // Mock React Native -vi.mock('react-native', () => ({ +rs.mock('react-native', () => ({ Platform: { OS: 'ios' }, Dimensions: { - get: vi.fn(() => ({ width: 375, height: 667 })), - addEventListener: vi.fn(() => ({ remove: vi.fn() })), + get: rs.fn(() => ({ width: 375, height: 667 })), + addEventListener: rs.fn(() => ({ remove: rs.fn() })), }, NativeModules: {}, })) // Mock AsyncStorage -vi.mock('@react-native-async-storage/async-storage', () => ({ +rs.mock('@react-native-async-storage/async-storage', () => ({ default: { - getItem: vi.fn(), - setItem: vi.fn(), - removeItem: vi.fn(), + getItem: rs.fn(), + setItem: rs.fn(), + removeItem: rs.fn(), }, })) // Mock @contentful/optimization-core -vi.mock('@contentful/optimization-core', () => ({ +rs.mock('@contentful/optimization-core', () => ({ logger: { - info: vi.fn(), - debug: vi.fn(), - error: vi.fn(), - warn: vi.fn(), + info: rs.fn(), + debug: rs.fn(), + error: rs.fn(), + warn: rs.fn(), }, createScopedLogger: () => ({ - debug: vi.fn(), - info: vi.fn(), - log: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - fatal: vi.fn(), + debug: rs.fn(), + info: rs.fn(), + log: rs.fn(), + warn: rs.fn(), + error: rs.fn(), + fatal: rs.fn(), }), })) // Mock useOptimization hook -vi.mock('../context/OptimizationContext', () => ({ +rs.mock('../context/OptimizationContext', () => ({ useOptimization: () => ({ - screen: vi.fn().mockResolvedValue({ profile: {}, changes: [], personalizations: [] }), + screen: rs.fn().mockResolvedValue({ profile: {}, changes: [], personalizations: [] }), }), })) diff --git a/platforms/javascript/react-native/src/handlers/createAppStateChangeListener.test.ts b/platforms/javascript/react-native/src/handlers/createAppStateChangeListener.test.ts index 95d2d66e..61c23446 100644 --- a/platforms/javascript/react-native/src/handlers/createAppStateChangeListener.test.ts +++ b/platforms/javascript/react-native/src/handlers/createAppStateChangeListener.test.ts @@ -1,31 +1,48 @@ +import { beforeEach, describe, expect, it, rs } from '@rstest/core' import { createLoggerMock } from 'mocks/loggerMock' -import { beforeEach, describe, expect, it, vi } from 'vitest' // Create a holder for the AppState callback const callbackHolder: { callback: ((nextAppState: 'active' | 'background' | 'inactive') => void) | null } = { callback: null } -const mockRemove = vi.fn() +const mockRemove = rs.fn() const mockLogger = { - debug: vi.fn(), - info: vi.fn(), - log: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - fatal: vi.fn(), + debug: rs.fn(), + info: rs.fn(), + log: rs.fn(), + warn: rs.fn(), + error: rs.fn(), + fatal: rs.fn(), +} + +async function waitForExpectation(assertion: () => void): Promise { + const deadline = Date.now() + 1000 + + while (Date.now() < deadline) { + try { + assertion() + return + } catch { + await new Promise((resolve) => { + setTimeout(resolve, 10) + }) + } + } + + assertion() } describe('createAppStateChangeListener', () => { beforeEach(() => { - vi.clearAllMocks() + rs.clearAllMocks() callbackHolder.callback = null - vi.resetModules() + rs.resetModules() // Set up mocks before each test - vi.doMock('@contentful/optimization-core', () => createLoggerMock(mockLogger)) + rs.doMock('@contentful/optimization-core', () => createLoggerMock(mockLogger)) - vi.doMock('react-native', () => ({ + rs.doMock('react-native', () => ({ AppState: { addEventListener: ( event: string, @@ -42,7 +59,7 @@ describe('createAppStateChangeListener', () => { it('should register a listener with AppState and return cleanup function', async () => { const { createAppStateChangeListener } = await import('./createAppStateChangeListener') - const callback = vi.fn() + const callback = rs.fn() const cleanup = createAppStateChangeListener(callback) @@ -52,35 +69,35 @@ describe('createAppStateChangeListener', () => { it('should invoke callback when app goes to background', async () => { const { createAppStateChangeListener } = await import('./createAppStateChangeListener') - const callback = vi.fn() + const callback = rs.fn() createAppStateChangeListener(callback) // Simulate app going to background callbackHolder.callback?.('background') - await vi.waitFor(() => { + await waitForExpectation(() => { expect(callback).toHaveBeenCalled() }) }) it('should invoke callback when app goes to inactive', async () => { const { createAppStateChangeListener } = await import('./createAppStateChangeListener') - const callback = vi.fn() + const callback = rs.fn() createAppStateChangeListener(callback) // Simulate app going to inactive callbackHolder.callback?.('inactive') - await vi.waitFor(() => { + await waitForExpectation(() => { expect(callback).toHaveBeenCalled() }) }) it('should NOT invoke callback when app returns to active', async () => { const { createAppStateChangeListener } = await import('./createAppStateChangeListener') - const callback = vi.fn() + const callback = rs.fn() createAppStateChangeListener(callback) @@ -95,7 +112,7 @@ describe('createAppStateChangeListener', () => { it('should call subscription.remove() when cleanup function is called', async () => { const { createAppStateChangeListener } = await import('./createAppStateChangeListener') - const callback = vi.fn() + const callback = rs.fn() const cleanup = createAppStateChangeListener(callback) cleanup() @@ -106,7 +123,7 @@ describe('createAppStateChangeListener', () => { it('should log error but not crash when callback throws synchronously', async () => { const { createAppStateChangeListener } = await import('./createAppStateChangeListener') const error = new Error('Callback error') - const callback = vi.fn().mockImplementation(() => { + const callback = rs.fn().mockImplementation(() => { throw error }) @@ -115,7 +132,7 @@ describe('createAppStateChangeListener', () => { // Simulate app going to background callbackHolder.callback?.('background') - await vi.waitFor(() => { + await waitForExpectation(() => { expect(callback).toHaveBeenCalled() expect(mockLogger.error).toHaveBeenCalledWith( 'RN:AppState', @@ -128,14 +145,14 @@ describe('createAppStateChangeListener', () => { it('should log error but not crash when async callback rejects', async () => { const { createAppStateChangeListener } = await import('./createAppStateChangeListener') const error = new Error('Async callback error') - const callback = vi.fn().mockRejectedValue(error) + const callback = rs.fn().mockRejectedValue(error) createAppStateChangeListener(callback) // Simulate app going to background callbackHolder.callback?.('background') - await vi.waitFor(() => { + await waitForExpectation(() => { expect(callback).toHaveBeenCalled() expect(mockLogger.error).toHaveBeenCalledWith( 'RN:AppState', @@ -147,14 +164,14 @@ describe('createAppStateChangeListener', () => { it('should handle multiple state transitions', async () => { const { createAppStateChangeListener } = await import('./createAppStateChangeListener') - const callback = vi.fn() + const callback = rs.fn() createAppStateChangeListener(callback) // Simulate going to background callbackHolder.callback?.('background') - await vi.waitFor(() => { + await waitForExpectation(() => { expect(callback).toHaveBeenCalledTimes(1) }) @@ -170,21 +187,21 @@ describe('createAppStateChangeListener', () => { // Simulate going to inactive callbackHolder.callback?.('inactive') - await vi.waitFor(() => { + await waitForExpectation(() => { expect(callback).toHaveBeenCalledTimes(2) }) }) it('should handle async callbacks that resolve successfully', async () => { const { createAppStateChangeListener } = await import('./createAppStateChangeListener') - const callback = vi.fn().mockResolvedValue(undefined) + const callback = rs.fn().mockResolvedValue(undefined) createAppStateChangeListener(callback) // Simulate app going to background callbackHolder.callback?.('background') - await vi.waitFor(() => { + await waitForExpectation(() => { expect(callback).toHaveBeenCalled() }) diff --git a/platforms/javascript/react-native/src/handlers/createOnlineChangeListener.test.ts b/platforms/javascript/react-native/src/handlers/createOnlineChangeListener.test.ts index 4f932bea..3ea296ec 100644 --- a/platforms/javascript/react-native/src/handlers/createOnlineChangeListener.test.ts +++ b/platforms/javascript/react-native/src/handlers/createOnlineChangeListener.test.ts @@ -1,34 +1,40 @@ +import { beforeEach, describe, expect, it, rs } from '@rstest/core' import { createLoggerMock } from 'mocks/loggerMock' -import { beforeEach, describe, expect, it, vi } from 'vitest' // Create mock holder const mockLogger = { - debug: vi.fn(), - info: vi.fn(), - log: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - fatal: vi.fn(), + debug: rs.fn(), + info: rs.fn(), + log: rs.fn(), + warn: rs.fn(), + error: rs.fn(), + fatal: rs.fn(), +} + +const flushAsyncImports = async (): Promise => { + await Promise.resolve() + await Promise.resolve() } describe('createOnlineChangeListener', () => { beforeEach(() => { - vi.clearAllMocks() - vi.resetModules() + rs.clearAllMocks() + rs.resetModules() - vi.doMock('@contentful/optimization-core', () => createLoggerMock(mockLogger)) + rs.doMock('@contentful/optimization-core', () => createLoggerMock(mockLogger)) }) describe('when NetInfo is not installed', () => { it('should log warning and return no-op cleanup when module throws on require', async () => { - vi.doMock('@react-native-community/netinfo', () => { + rs.doMock('@react-native-community/netinfo', () => { throw new Error('Cannot find module') }) const { createOnlineChangeListener } = await import('./createOnlineChangeListener') - const callback = vi.fn() + const callback = rs.fn() const cleanup = createOnlineChangeListener(callback) + await flushAsyncImports() expect(mockLogger.warn).toHaveBeenCalledWith( 'RN:Network', @@ -46,14 +52,15 @@ describe('createOnlineChangeListener', () => { }) it('should log warning when NetInfo module has invalid structure (null default)', async () => { - vi.doMock('@react-native-community/netinfo', () => ({ + rs.doMock('@react-native-community/netinfo', () => ({ default: null, })) const { createOnlineChangeListener } = await import('./createOnlineChangeListener') - const callback = vi.fn() + const callback = rs.fn() const cleanup = createOnlineChangeListener(callback) + await flushAsyncImports() expect(mockLogger.warn).toHaveBeenCalledWith( 'RN:Network', @@ -63,14 +70,15 @@ describe('createOnlineChangeListener', () => { }) it('should log warning when NetInfo module has no addEventListener', async () => { - vi.doMock('@react-native-community/netinfo', () => ({ + rs.doMock('@react-native-community/netinfo', () => ({ default: {}, })) const { createOnlineChangeListener } = await import('./createOnlineChangeListener') - const callback = vi.fn() + const callback = rs.fn() const cleanup = createOnlineChangeListener(callback) + await flushAsyncImports() expect(mockLogger.warn).toHaveBeenCalledWith( 'RN:Network', @@ -80,14 +88,15 @@ describe('createOnlineChangeListener', () => { }) it('should log warning when addEventListener is not a function', async () => { - vi.doMock('@react-native-community/netinfo', () => ({ + rs.doMock('@react-native-community/netinfo', () => ({ default: { addEventListener: 'not a function' }, })) const { createOnlineChangeListener } = await import('./createOnlineChangeListener') - const callback = vi.fn() + const callback = rs.fn() const cleanup = createOnlineChangeListener(callback) + await flushAsyncImports() expect(mockLogger.warn).toHaveBeenCalledWith( 'RN:Network', @@ -99,23 +108,25 @@ describe('createOnlineChangeListener', () => { describe('type guard isNetInfoModule', () => { it('should reject module without default export', async () => { - vi.doMock('@react-native-community/netinfo', () => ({})) + rs.doMock('@react-native-community/netinfo', () => ({})) const { createOnlineChangeListener } = await import('./createOnlineChangeListener') - const callback = vi.fn() + const callback = rs.fn() createOnlineChangeListener(callback) + await flushAsyncImports() expect(mockLogger.warn).toHaveBeenCalled() }) it('should reject non-object module', async () => { - vi.doMock('@react-native-community/netinfo', () => 'string module') + rs.doMock('@react-native-community/netinfo', () => 'string module') const { createOnlineChangeListener } = await import('./createOnlineChangeListener') - const callback = vi.fn() + const callback = rs.fn() createOnlineChangeListener(callback) + await flushAsyncImports() expect(mockLogger.warn).toHaveBeenCalled() }) diff --git a/platforms/javascript/react-native/src/handlers/createOnlineChangeListener.ts b/platforms/javascript/react-native/src/handlers/createOnlineChangeListener.ts index 7aae7ab0..f0b11a80 100644 --- a/platforms/javascript/react-native/src/handlers/createOnlineChangeListener.ts +++ b/platforms/javascript/react-native/src/handlers/createOnlineChangeListener.ts @@ -22,6 +22,29 @@ interface NetInfoModule { } } +const isObjectRecord = (value: unknown): value is Record => + typeof value === 'object' && value !== null + +const isNetInfoModule = (mod: unknown): mod is NetInfoModule => { + if (!isObjectRecord(mod)) { + return false + } + + const { default: defaultExport } = mod + + return isObjectRecord(defaultExport) && typeof defaultExport.addEventListener === 'function' +} + +const loadNetInfoModule = async (): Promise => { + const mod: unknown = await import('@react-native-community/netinfo') + + if (!isNetInfoModule(mod)) { + throw new Error('Invalid NetInfo module') + } + + return mod +} + /** * Create an online/offline listener that invokes a callback whenever the device transitions * between connectivity states, and returns a cleanup function to remove the listener. @@ -49,6 +72,9 @@ interface NetInfoModule { * ``` */ export function createOnlineChangeListener(callback: Callback): () => void { + const state = { didDispose: false } + let unsubscribe: () => void = () => undefined + const emit = (isOnline: boolean): void => { void (async () => { try { @@ -59,40 +85,28 @@ export function createOnlineChangeListener(callback: Callback): () => void { })() } - try { - const isNetInfoModule = (mod: unknown): mod is NetInfoModule => { - if (typeof mod !== 'object' || mod === null || !('default' in mod)) { - return false - } - const { default: defaultExport } = mod as { default: unknown } - return ( - typeof defaultExport === 'object' && - defaultExport !== null && - 'addEventListener' in defaultExport && - typeof (defaultExport as { addEventListener: unknown }).addEventListener === 'function' - ) - } + void (async () => { + try { + const { default: NetInfo } = await loadNetInfoModule() - // eslint-disable-next-line @typescript-eslint/no-require-imports -- Dynamic require to gracefully handle missing optional peer dependency - const NetInfoModule: unknown = require('@react-native-community/netinfo') + if (state.didDispose) { + return + } - if (!isNetInfoModule(NetInfoModule)) { - throw new Error('Invalid NetInfo module') + unsubscribe = NetInfo.addEventListener((state: NetInfoState) => { + // Use isInternetReachable for actual connectivity (can ping external server) + // Fall back to isConnected if reachability check unavailable (null) + // Default to true if both are null to avoid false offline states + const isOnline = state.isInternetReachable ?? state.isConnected ?? true + emit(isOnline) + }) + } catch { + logger.warn('@react-native-community/netinfo not installed. Offline detection disabled.') } + })() - const { default: NetInfo } = NetInfoModule - - const unsubscribe: () => void = NetInfo.addEventListener((state: NetInfoState) => { - // Use isInternetReachable for actual connectivity (can ping external server) - // Fall back to isConnected if reachability check unavailable (null) - // Default to true if both are null to avoid false offline states - const isOnline = state.isInternetReachable ?? state.isConnected ?? true - emit(isOnline) - }) - - return unsubscribe - } catch { - logger.warn('@react-native-community/netinfo not installed. Offline detection disabled.') - return () => undefined + return () => { + state.didDispose = true + unsubscribe() } } diff --git a/platforms/javascript/react-native/src/hooks/useScreenTracking.test.ts b/platforms/javascript/react-native/src/hooks/useScreenTracking.test.ts index b7661e38..623d3fb1 100644 --- a/platforms/javascript/react-native/src/hooks/useScreenTracking.test.ts +++ b/platforms/javascript/react-native/src/hooks/useScreenTracking.test.ts @@ -1,78 +1,74 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { beforeEach, describe, expect, it, rs } from '@rstest/core' // Mock React Native -vi.mock('react-native', () => ({ +rs.mock('react-native', () => ({ Platform: { OS: 'ios' }, Dimensions: { - get: vi.fn(() => ({ width: 375, height: 667 })), - addEventListener: vi.fn(() => ({ remove: vi.fn() })), + get: rs.fn(() => ({ width: 375, height: 667 })), + addEventListener: rs.fn(() => ({ remove: rs.fn() })), }, NativeModules: {}, })) // Mock AsyncStorage -vi.mock('@react-native-async-storage/async-storage', () => ({ +rs.mock('@react-native-async-storage/async-storage', () => ({ default: { - getItem: vi.fn(), - setItem: vi.fn(), - removeItem: vi.fn(), + getItem: rs.fn(), + setItem: rs.fn(), + removeItem: rs.fn(), }, })) // Mock @contentful/optimization-core -vi.mock('@contentful/optimization-core', () => ({ +rs.mock('@contentful/optimization-core', () => ({ logger: { - info: vi.fn(), - debug: vi.fn(), - error: vi.fn(), - warn: vi.fn(), + info: rs.fn(), + debug: rs.fn(), + error: rs.fn(), + warn: rs.fn(), }, createScopedLogger: () => ({ - debug: vi.fn(), - info: vi.fn(), - log: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - fatal: vi.fn(), + debug: rs.fn(), + info: rs.fn(), + log: rs.fn(), + warn: rs.fn(), + error: rs.fn(), + fatal: rs.fn(), }), })) // Create mock optimization instance -const mockScreen = vi.fn().mockResolvedValue({ profile: {}, changes: [], personalizations: [] }) +const mockScreen = rs.fn().mockResolvedValue({ profile: {}, changes: [], personalizations: [] }) const mockOptimization = { screen: mockScreen, } // Mock useOptimization hook -vi.mock('../context/OptimizationContext', () => ({ +rs.mock('../context/OptimizationContext', () => ({ useOptimization: () => mockOptimization, })) // Mock react hooks -const mockUseEffect = vi.fn() -const mockUseCallback = vi.fn((fn: T): T => fn) -const mockUseRef = vi.fn((initial: unknown) => ({ current: initial })) - -vi.mock('react', async () => { - const actual = await vi.importActual('react') - return { - ...actual, - useEffect: (fn: () => void) => { - mockUseEffect(fn) - fn() - }, - useCallback: (fn: () => unknown) => { - mockUseCallback(fn) - return fn - }, - useRef: (initial: unknown) => mockUseRef(initial), - } -}) +const mockUseEffect = rs.fn() +const mockUseCallback = rs.fn((fn: T): T => fn) +const mockUseRef = rs.fn((initial: unknown) => ({ current: initial })) + +rs.mock('react', () => ({ + useEffect: (fn: () => void) => { + mockUseEffect(fn) + fn() + }, + useCallback: (fn: () => unknown) => { + mockUseCallback(fn) + return fn + }, + useRef: (initial: unknown) => mockUseRef(initial), +})) describe('useScreenTracking', () => { beforeEach(() => { - vi.clearAllMocks() + rs.clearAllMocks() mockScreen.mockResolvedValue({ profile: {}, changes: [], personalizations: [] }) }) diff --git a/platforms/javascript/react-native/src/index.test.ts b/platforms/javascript/react-native/src/index.test.ts index 1edbcbca..0b5af2b7 100644 --- a/platforms/javascript/react-native/src/index.test.ts +++ b/platforms/javascript/react-native/src/index.test.ts @@ -1,29 +1,29 @@ -import { describe, expect, it, vi } from 'vitest' +import { describe, expect, it, rs } from '@rstest/core' import { OPTIMIZATION_REACT_NATIVE_SDK_NAME } from './global-constants' // Mock React Native before importing anything else -vi.mock('react-native', () => ({ +rs.mock('react-native', () => ({ Platform: { OS: 'ios' }, - Dimensions: { get: vi.fn(() => ({ width: 375, height: 667 })) }, + Dimensions: { get: rs.fn(() => ({ width: 375, height: 667 })) }, NativeModules: {}, })) // Mock AsyncStorage -vi.mock('@react-native-async-storage/async-storage', () => ({ +rs.mock('@react-native-async-storage/async-storage', () => ({ default: { - getItem: vi.fn(), - setItem: vi.fn(), - removeItem: vi.fn(), + getItem: rs.fn(), + setItem: rs.fn(), + removeItem: rs.fn(), }, })) // Mock React -vi.mock('react', () => ({ +rs.mock('react', () => ({ default: {}, - createContext: vi.fn(() => ({})), - useContext: vi.fn(), - useMemo: vi.fn((fn: () => T) => fn()), - createElement: vi.fn(), + createContext: rs.fn(() => ({})), + useContext: rs.fn(), + useMemo: rs.fn((fn: () => T) => fn()), + createElement: rs.fn(), })) describe('Optimization React Native', () => { diff --git a/platforms/javascript/react-native/src/test/reactNativeShim.ts b/platforms/javascript/react-native/src/test/reactNativeShim.ts new file mode 100644 index 00000000..028be626 --- /dev/null +++ b/platforms/javascript/react-native/src/test/reactNativeShim.ts @@ -0,0 +1,31 @@ +interface Dimensions { + readonly height: number + readonly width: number +} + +const DEFAULT_DIMENSIONS: Dimensions = { width: 375, height: 667 } +const noop = (): void => undefined + +export const Platform = { OS: 'ios' } + +export const Dimensions = { + get: (): Dimensions => DEFAULT_DIMENSIONS, + addEventListener: () => ({ + remove: noop, + }), +} + +export const AppState = { + addEventListener: () => ({ + remove: noop, + }), +} + +export const NativeModules = {} + +export default { + AppState, + Dimensions, + NativeModules, + Platform, +} diff --git a/platforms/javascript/react-native/tsconfig.json b/platforms/javascript/react-native/tsconfig.json index 2b0a3ed7..f8edcee6 100644 --- a/platforms/javascript/react-native/tsconfig.json +++ b/platforms/javascript/react-native/tsconfig.json @@ -1,6 +1,7 @@ { "compilerOptions": { - "jsx": "react-native" + "jsx": "react-native", + "types": ["@rstest/core/globals"] }, "extends": "../../../tsconfig.base.json", "include": ["./src/**/*", "./src/**/*.json"] diff --git a/platforms/javascript/web-preview-panel/.env.example b/platforms/javascript/web-preview-panel/.env.example index 39d0d995..ed05455f 100644 --- a/platforms/javascript/web-preview-panel/.env.example +++ b/platforms/javascript/web-preview-panel/.env.example @@ -1,15 +1,15 @@ DOTENV_CONFIG_QUIET=true -VITE_NINETAILED_CLIENT_ID="mock-client-id" -VITE_NINETAILED_ENVIRONMENT="main" +PUBLIC_NINETAILED_CLIENT_ID="mock-client-id" +PUBLIC_NINETAILED_ENVIRONMENT="main" -VITE_EXPERIENCE_API_BASE_URL="http://localhost:8000/experience/" -VITE_INSIGHTS_API_BASE_URL="http://localhost:8000/insights/" +PUBLIC_EXPERIENCE_API_BASE_URL="http://localhost:8000/experience/" +PUBLIC_INSIGHTS_API_BASE_URL="http://localhost:8000/insights/" -VITE_CONTENTFUL_TOKEN="mock-token" -VITE_CONTENTFUL_PREVIEW_TOKEN="mosk-preview-token" -VITE_CONTENTFUL_ENVIRONMENT="master" -VITE_CONTENTFUL_SPACE_ID="mock-space-id" +PUBLIC_CONTENTFUL_TOKEN="mock-token" +PUBLIC_CONTENTFUL_PREVIEW_TOKEN="mosk-preview-token" +PUBLIC_CONTENTFUL_ENVIRONMENT="master" +PUBLIC_CONTENTFUL_SPACE_ID="mock-space-id" -VITE_CONTENTFUL_CDA_HOST="localhost:8000" -VITE_CONTENTFUL_BASE_PATH="contentful" +PUBLIC_CONTENTFUL_CDA_HOST="localhost:8000" +PUBLIC_CONTENTFUL_BASE_PATH="contentful" diff --git a/platforms/javascript/web-preview-panel/index.html b/platforms/javascript/web-preview-panel/index.html index 6987c848..7df451eb 100644 --- a/platforms/javascript/web-preview-panel/index.html +++ b/platforms/javascript/web-preview-panel/index.html @@ -3,7 +3,7 @@ - Optimization Web SDK Dev Development Dashboard + Optimization Web SDK + Preview Dev Development Dashboard -

Optimization Web SDK Dev Development Dashboard

+

Optimization Web SDK + Preview Dev Development Dashboard

-
+

Utilities

@@ -235,40 +241,36 @@

Entries

- diff --git a/platforms/javascript/web-preview-panel/package.json b/platforms/javascript/web-preview-panel/package.json index ab8a5fcb..c4e802ae 100644 --- a/platforms/javascript/web-preview-panel/package.json +++ b/platforms/javascript/web-preview-panel/package.json @@ -3,12 +3,14 @@ "version": "0.0.0", "license": "MIT", "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.mjs", "types": "./dist/index.d.mts", "exports": { ".": { "import": { "types": "./dist/index.d.mts", - "default": "./dist/index.js" + "default": "./dist/index.mjs" }, "require": { "types": "./dist/index.d.cts", @@ -20,11 +22,12 @@ "dist/**/*" ], "scripts": { - "build:esm": "vite build -c vite.esm.config.ts", - "build:umd": "vite build -c vite.umd.config.ts", - "build": "pnpm clean && tsc -b ./tsconfig.build.json && pnpm build:esm && pnpm build:umd && build-tools emit-dual-dts ./dist", - "clean": "rimraf ./dist ./coverage tsconfig.tsbuildinfo", - "dev": "vite -c vite.esm.config.ts --host", + "build": "pnpm clean && pnpm build:dist", + "build:ci": "pnpm build:dist", + "build:dist": "rslib build && build-tools emit-dual-dts ./dist", + "build:rsdoctor": "RSDOCTOR=true rslib build", + "clean": "rimraf ./.rsdoctor ./.rslib ./dist ./coverage tsconfig.tsbuildinfo", + "dev": "rsbuild dev --host 0.0.0.0 -c rsbuild.config.ts", "test:unit": "echo \"TODO: Unit tests\"", "typecheck": "tsc --noEmit" }, @@ -36,17 +39,11 @@ "lit": "^3.3.2" }, "devDependencies": { + "@rsbuild/core": "catalog:", + "@rslib/core": "catalog:", "build-tools": "workspace:*", - "@vitest/coverage-v8": "catalog:", - "happy-dom": "catalog:", "rimraf": "catalog:", - "rollup-plugin-visualizer": "catalog:", "tslib": "catalog:", - "typescript": "catalog:", - "vite": "catalog:", - "vitest": "catalog:", - "vite-bundle-analyzer": "catalog:", - "vite-plugin-resolve-umd-format": "catalog:", - "vite-tsconfig-paths": "catalog:" + "typescript": "catalog:" } } diff --git a/platforms/javascript/web-preview-panel/rsbuild.config.ts b/platforms/javascript/web-preview-panel/rsbuild.config.ts new file mode 100644 index 00000000..91c43b12 --- /dev/null +++ b/platforms/javascript/web-preview-panel/rsbuild.config.ts @@ -0,0 +1,59 @@ +import type { RsbuildConfig } from '@rsbuild/core' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +/* eslint-disable @typescript-eslint/naming-convention -- standardized var names */ +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) +/* eslint-enable @typescript-eslint/naming-convention -- standardized var names */ + +const env = { + PUBLIC_CONTENTFUL_TOKEN: process.env.PUBLIC_CONTENTFUL_TOKEN ?? '', + PUBLIC_CONTENTFUL_ENVIRONMENT: process.env.PUBLIC_CONTENTFUL_ENVIRONMENT ?? '', + PUBLIC_CONTENTFUL_SPACE_ID: process.env.PUBLIC_CONTENTFUL_SPACE_ID ?? '', + PUBLIC_CONTENTFUL_CDA_HOST: process.env.PUBLIC_CONTENTFUL_CDA_HOST ?? '', + PUBLIC_CONTENTFUL_BASE_PATH: process.env.PUBLIC_CONTENTFUL_BASE_PATH ?? '', + PUBLIC_NINETAILED_CLIENT_ID: process.env.PUBLIC_NINETAILED_CLIENT_ID ?? '', + PUBLIC_NINETAILED_ENVIRONMENT: process.env.PUBLIC_NINETAILED_ENVIRONMENT ?? '', + PUBLIC_INSIGHTS_API_BASE_URL: process.env.PUBLIC_INSIGHTS_API_BASE_URL ?? '', + PUBLIC_EXPERIENCE_API_BASE_URL: process.env.PUBLIC_EXPERIENCE_API_BASE_URL ?? '', +} as const + +const config: RsbuildConfig = { + source: { + entry: { + index: './src/dev.ts', + }, + tsconfigPath: './tsconfig.json', + define: { + __OPTIMIZATION_VERSION__: JSON.stringify(process.env.RELEASE_VERSION ?? '0.0.0'), + __OPTIMIZATION_PACKAGE_NAME__: JSON.stringify('@contentful/optimization-web'), + }, + }, + + resolve: { + alias: { + '@contentful/optimization-api-client': path.resolve( + __dirname, + '../../../universal/api-client/src/', + ), + '@contentful/optimization-api-schemas': path.resolve( + __dirname, + '../../../universal/api-schemas/src/', + ), + '@contentful/optimization-core': path.resolve(__dirname, '../../../universal/core/src/'), + '@contentful/optimization-web': path.resolve(__dirname, '../web/src/'), + }, + }, + + html: { + template: './index.html', + templateParameters: env, + }, + + output: { + target: 'web', + }, +} + +export default config diff --git a/platforms/javascript/web-preview-panel/rslib.config.ts b/platforms/javascript/web-preview-panel/rslib.config.ts new file mode 100644 index 00000000..58ab1b46 --- /dev/null +++ b/platforms/javascript/web-preview-panel/rslib.config.ts @@ -0,0 +1,120 @@ +import { defineConfig } from '@rslib/core' +import { ensureUmdDefaultExport, getPackageName, maybeEnableRsDoctor } from 'build-tools' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +/* eslint-disable @typescript-eslint/naming-convention -- standardized var names */ +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) +const packageName = getPackageName(__dirname, '@contentful/optimization-web-preview-panel') +/* eslint-enable @typescript-eslint/naming-convention -- standardized var names */ + +const common = { + bundle: true, + autoExtension: false, + autoExternal: { + dependencies: true, + peerDependencies: true, + optionalDependencies: true, + devDependencies: false, + }, +} as const + +export default defineConfig({ + source: { + tsconfigPath: './tsconfig.build.json', + define: { + __OPTIMIZATION_VERSION__: JSON.stringify(process.env.RELEASE_VERSION ?? '0.0.0'), + __OPTIMIZATION_PACKAGE_NAME__: JSON.stringify(packageName), + }, + }, + + resolve: { + alias: { + '@contentful/optimization-api-client': path.resolve( + __dirname, + '../../../universal/api-client/src/', + ), + '@contentful/optimization-api-schemas': path.resolve( + __dirname, + '../../../universal/api-schemas/src/', + ), + '@contentful/optimization-core': path.resolve(__dirname, '../../../universal/core/src/'), + '@contentful/optimization-web': path.resolve(__dirname, '../web/src/'), + }, + }, + + output: { + target: 'web', + }, + + lib: [ + { + ...common, + format: 'esm', + output: { + distPath: { root: 'dist' }, + filename: { js: '[name].mjs' }, + sourceMap: true, + cleanDistPath: true, + minify: true, + }, + + dts: { + bundle: true, + build: false, + }, + + redirect: { + dts: { path: false }, + }, + + tools: { + rspack: maybeEnableRsDoctor, + }, + }, + + { + ...common, + format: 'cjs', + output: { + distPath: { root: 'dist' }, + filename: { js: '[name].cjs' }, + sourceMap: true, + cleanDistPath: false, + minify: true, + }, + dts: false, + tools: { + rspack: maybeEnableRsDoctor, + }, + }, + + { + ...common, + format: 'umd', + autoExternal: false, + umdName: 'attachOptimizationPreviewPanel', + source: { + entry: { + 'contentful-optimization-web-preview-panel.umd': + './src/attachOptimizationPreviewPanel.ts', + }, + }, + output: { + distPath: { root: 'dist' }, + filename: { js: '[name].js' }, + sourceMap: true, + cleanDistPath: false, + minify: true, + }, + dts: false, + tools: { + rspack: (config) => { + ensureUmdDefaultExport(config) + maybeEnableRsDoctor(config) + }, + }, + }, + ], +}) diff --git a/platforms/javascript/web-preview-panel/src/dev.ts b/platforms/javascript/web-preview-panel/src/dev.ts index 0ad12512..c5463229 100644 --- a/platforms/javascript/web-preview-panel/src/dev.ts +++ b/platforms/javascript/web-preview-panel/src/dev.ts @@ -1,4 +1,13 @@ +import Optimization from '@contentful/optimization-web' import attachOptimizationPreviewPanel from './attachOptimizationPreviewPanel' -if (typeof window !== 'undefined') +declare global { + interface Window { + Optimization?: typeof Optimization + } +} + +if (typeof window !== 'undefined') { + window.Optimization ??= Optimization window.attachOptimizationPreviewPanel ??= attachOptimizationPreviewPanel +} diff --git a/platforms/javascript/web-preview-panel/vite.esm.config.ts b/platforms/javascript/web-preview-panel/vite.esm.config.ts deleted file mode 100644 index 5300f1ea..00000000 --- a/platforms/javascript/web-preview-panel/vite.esm.config.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { resolve } from 'node:path' -import { visualizer } from 'rollup-plugin-visualizer' -import { defineConfig, searchForWorkspaceRoot, type UserConfig } from 'vite' -import { analyzer } from 'vite-bundle-analyzer' -import tsconfigPaths from 'vite-tsconfig-paths' - -const config: UserConfig = { - resolve: { - alias: { - '@contentful/optimization-api-client': resolve( - __dirname, - '../../../universal/api-client/src/', - ), - '@contentful/optimization-api-schemas': resolve( - __dirname, - '../../../universal/api-schemas/src/', - ), - '@contentful/optimization-core': resolve(__dirname, '../../../universal/core/src/'), - '@contentful/optimization-web': resolve(__dirname, '../web/src/'), - 'optimization/dev': resolve(__dirname, '../web/src/dev.ts'), - }, - }, - esbuild: { - target: 'es2022', - }, - plugins: [ - analyzer({ analyzerMode: 'static', fileName: 'analyzer', openAnalyzer: false }), - visualizer({ - brotliSize: true, - filename: 'dist/visualizer.html', - gzipSize: true, - template: 'flamegraph', - }), - tsconfigPaths(), - ], - test: { - environment: 'happy-dom', - include: ['**/*.test.?(c|m)[jt]s?(x)'], - globals: true, - coverage: { - include: ['src/**/*'], - reporter: ['text', 'html'], - }, - }, - server: { - fs: { - // Vite restricts files served via /@fs/. Keep the default workspace-root - // behavior and add the sibling package explicitly. - // When `server.fs.allow` is set, auto workspace detection is disabled, - // so `searchForWorkspaceRoot()` preserves the default behavior. - allow: [searchForWorkspaceRoot(process.cwd()), resolve(__dirname, '../web')], - }, - }, -} - -const esm: UserConfig = { - ...config, - build: { - emptyOutDir: false, - lib: { - entry: resolve(__dirname, 'src/index.ts'), - formats: ['es', 'cjs'], - fileName: 'index', - }, - sourcemap: true, - }, -} - -export default defineConfig(esm) diff --git a/platforms/javascript/web-preview-panel/vite.umd.config.ts b/platforms/javascript/web-preview-panel/vite.umd.config.ts deleted file mode 100644 index 1a721d77..00000000 --- a/platforms/javascript/web-preview-panel/vite.umd.config.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { resolve } from 'node:path' -import { visualizer } from 'rollup-plugin-visualizer' -import { defineConfig, type UserConfig } from 'vite' -import { analyzer } from 'vite-bundle-analyzer' -import umdFormatResolver from 'vite-plugin-resolve-umd-format' -import tsconfigPaths from 'vite-tsconfig-paths' - -const config: UserConfig = { - resolve: { - alias: { - '@contentful/optimization-api-client': resolve( - __dirname, - '../../../universal/api-client/src/', - ), - '@contentful/optimization-api-schemas': resolve( - __dirname, - '../../../universal/api-schemas/src/', - ), - '@contentful/optimization-core': resolve(__dirname, '../../../universal/core/src/'), - '@contentful/optimization-web': resolve(__dirname, '../web/src/'), - }, - }, - esbuild: { - target: 'es2022', - }, - plugins: [ - analyzer({ analyzerMode: 'static', fileName: 'analyzer', openAnalyzer: false }), - umdFormatResolver(), - visualizer({ - brotliSize: true, - filename: 'dist/visualizer.html', - gzipSize: true, - template: 'flamegraph', - }), - tsconfigPaths(), - ], - test: { - environment: 'happy-dom', - include: ['**/*.test.?(c|m)[jt]s?(x)'], - globals: true, - coverage: { - include: ['src/**/*'], - reporter: ['text', 'html'], - }, - }, -} - -const umd: UserConfig = { - ...config, - build: { - emptyOutDir: false, - lib: { - entry: resolve(__dirname, 'src/attachOptimizationPreviewPanel.ts'), - formats: ['umd'], - fileName: 'contentful-optimization-web-preview-panel', - name: 'attachOptimizationPreviewPanel', - }, - sourcemap: true, - }, -} - -export default defineConfig(umd) diff --git a/platforms/javascript/web/.env.example b/platforms/javascript/web/.env.example index 39d0d995..ed05455f 100644 --- a/platforms/javascript/web/.env.example +++ b/platforms/javascript/web/.env.example @@ -1,15 +1,15 @@ DOTENV_CONFIG_QUIET=true -VITE_NINETAILED_CLIENT_ID="mock-client-id" -VITE_NINETAILED_ENVIRONMENT="main" +PUBLIC_NINETAILED_CLIENT_ID="mock-client-id" +PUBLIC_NINETAILED_ENVIRONMENT="main" -VITE_EXPERIENCE_API_BASE_URL="http://localhost:8000/experience/" -VITE_INSIGHTS_API_BASE_URL="http://localhost:8000/insights/" +PUBLIC_EXPERIENCE_API_BASE_URL="http://localhost:8000/experience/" +PUBLIC_INSIGHTS_API_BASE_URL="http://localhost:8000/insights/" -VITE_CONTENTFUL_TOKEN="mock-token" -VITE_CONTENTFUL_PREVIEW_TOKEN="mosk-preview-token" -VITE_CONTENTFUL_ENVIRONMENT="master" -VITE_CONTENTFUL_SPACE_ID="mock-space-id" +PUBLIC_CONTENTFUL_TOKEN="mock-token" +PUBLIC_CONTENTFUL_PREVIEW_TOKEN="mosk-preview-token" +PUBLIC_CONTENTFUL_ENVIRONMENT="master" +PUBLIC_CONTENTFUL_SPACE_ID="mock-space-id" -VITE_CONTENTFUL_CDA_HOST="localhost:8000" -VITE_CONTENTFUL_BASE_PATH="contentful" +PUBLIC_CONTENTFUL_CDA_HOST="localhost:8000" +PUBLIC_CONTENTFUL_BASE_PATH="contentful" diff --git a/platforms/javascript/web/index.html b/platforms/javascript/web/index.html index 9907b91e..be0a4427 100644 --- a/platforms/javascript/web/index.html +++ b/platforms/javascript/web/index.html @@ -75,6 +75,12 @@ } } } + #utility-panel { + position: sticky; + top: 0; + padding-block-end: 1rem; + background: #fff; + } #event-stream-panel { grid-column: 2; grid-row: 2 / span 2; @@ -119,7 +125,7 @@

Optimization Web SDK Dev Development Dashboard

-
+

Utilities

@@ -132,7 +138,7 @@

Utilities

| - +

           
@@ -170,7 +176,10 @@

Entry Data

-
    +
    + Entry Data +
      +
      @@ -232,32 +241,31 @@

      Entries

      - diff --git a/platforms/javascript/web/package.json b/platforms/javascript/web/package.json index b15e8664..a534fef3 100644 --- a/platforms/javascript/web/package.json +++ b/platforms/javascript/web/package.json @@ -4,13 +4,13 @@ "license": "MIT", "type": "module", "main": "./dist/index.cjs", - "module": "./dist/index.js", + "module": "./dist/index.mjs", "types": "./dist/index.d.mts", "exports": { ".": { "import": { "types": "./dist/index.d.mts", - "default": "./dist/index.js" + "default": "./dist/index.mjs" }, "require": { "types": "./dist/index.d.cts", @@ -22,12 +22,13 @@ "dist/**/*" ], "scripts": { - "build:esm": "vite --configLoader runner build -c vite.esm.config.ts", - "build:umd": "vite --configLoader runner build -c vite.umd.config.ts", - "build": "pnpm clean && tsc -b ./tsconfig.build.json && pnpm build:esm && pnpm build:umd && build-tools emit-dual-dts ./dist", - "clean": "rimraf ./dist ./coverage .tsbuildinfo", - "dev": "vite -c vite.esm.config.ts --host", - "test:unit": "vitest run --coverage", + "build": "pnpm clean && pnpm build:dist", + "build:ci": "pnpm build:dist", + "build:dist": "rslib build && build-tools emit-dual-dts ./dist", + "build:rsdoctor": "RSDOCTOR=true rslib build", + "clean": "rimraf ./.rsdoctor ./.rslib ./dist ./coverage .tsbuildinfo", + "dev": "rsbuild dev --host 0.0.0.0 -c rsbuild.config.ts", + "test:unit": "rstest run --coverage", "typecheck": "tsc --noEmit" }, "dependencies": { @@ -38,17 +39,14 @@ "zod": "catalog:" }, "devDependencies": { - "@vitest/coverage-v8": "catalog:", + "@rsbuild/core": "catalog:", + "@rslib/core": "catalog:", + "@rstest/core": "catalog:", + "@rstest/coverage-istanbul": "catalog:", "build-tools": "workspace:*", "happy-dom": "catalog:", "rimraf": "catalog:", - "rollup-plugin-visualizer": "catalog:", "tslib": "catalog:", - "typescript": "catalog:", - "vite": "catalog:", - "vitest": "catalog:", - "vite-bundle-analyzer": "catalog:", - "vite-plugin-resolve-umd-format": "catalog:", - "vite-tsconfig-paths": "catalog:" + "typescript": "catalog:" } } diff --git a/platforms/javascript/web/rsbuild.config.ts b/platforms/javascript/web/rsbuild.config.ts new file mode 100644 index 00000000..98f7c910 --- /dev/null +++ b/platforms/javascript/web/rsbuild.config.ts @@ -0,0 +1,58 @@ +import { defineConfig } from '@rsbuild/core' +import { getPackageName } from 'build-tools' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +/* eslint-disable @typescript-eslint/naming-convention -- standardized var names */ +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) +const packageName = getPackageName(__dirname, '@contentful/optimization-web') +/* eslint-enable @typescript-eslint/naming-convention -- standardized var names */ + +const env = { + PUBLIC_CONTENTFUL_TOKEN: process.env.PUBLIC_CONTENTFUL_TOKEN ?? '', + PUBLIC_CONTENTFUL_ENVIRONMENT: process.env.PUBLIC_CONTENTFUL_ENVIRONMENT ?? '', + PUBLIC_CONTENTFUL_SPACE_ID: process.env.PUBLIC_CONTENTFUL_SPACE_ID ?? '', + PUBLIC_CONTENTFUL_CDA_HOST: process.env.PUBLIC_CONTENTFUL_CDA_HOST ?? '', + PUBLIC_CONTENTFUL_BASE_PATH: process.env.PUBLIC_CONTENTFUL_BASE_PATH ?? '', + PUBLIC_NINETAILED_CLIENT_ID: process.env.PUBLIC_NINETAILED_CLIENT_ID ?? '', + PUBLIC_NINETAILED_ENVIRONMENT: process.env.PUBLIC_NINETAILED_ENVIRONMENT ?? '', + PUBLIC_INSIGHTS_API_BASE_URL: process.env.PUBLIC_INSIGHTS_API_BASE_URL ?? '', + PUBLIC_EXPERIENCE_API_BASE_URL: process.env.PUBLIC_EXPERIENCE_API_BASE_URL ?? '', +} as const + +export default defineConfig({ + source: { + entry: { + index: './src/dev.ts', + }, + tsconfigPath: './tsconfig.json', + define: { + __OPTIMIZATION_VERSION__: JSON.stringify(process.env.RELEASE_VERSION ?? '0.0.0'), + __OPTIMIZATION_PACKAGE_NAME__: JSON.stringify(packageName), + }, + }, + + resolve: { + alias: { + '@contentful/optimization-api-client': path.resolve( + __dirname, + '../../../universal/api-client/src/', + ), + '@contentful/optimization-api-schemas': path.resolve( + __dirname, + '../../../universal/api-schemas/src/', + ), + '@contentful/optimization-core': path.resolve(__dirname, '../../../universal/core/src/'), + }, + }, + + html: { + template: './index.html', + templateParameters: env, + }, + + output: { + target: 'web', + }, +}) diff --git a/platforms/javascript/web/rslib.config.ts b/platforms/javascript/web/rslib.config.ts new file mode 100644 index 00000000..6179184b --- /dev/null +++ b/platforms/javascript/web/rslib.config.ts @@ -0,0 +1,118 @@ +import { defineConfig } from '@rslib/core' +import { ensureUmdDefaultExport, getPackageName, maybeEnableRsDoctor } from 'build-tools' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +/* eslint-disable @typescript-eslint/naming-convention -- standardized var names */ +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) +const packageName = getPackageName(__dirname, '@contentful/optimization-web') +/* eslint-enable @typescript-eslint/naming-convention -- standardized var names */ + +const common = { + bundle: true, + autoExtension: false, + autoExternal: { + dependencies: true, + peerDependencies: true, + optionalDependencies: true, + devDependencies: false, + }, +} as const + +export default defineConfig({ + source: { + tsconfigPath: './tsconfig.build.json', + define: { + __OPTIMIZATION_VERSION__: JSON.stringify(process.env.RELEASE_VERSION ?? '0.0.0'), + __OPTIMIZATION_PACKAGE_NAME__: JSON.stringify(packageName), + }, + }, + + resolve: { + alias: { + '@contentful/optimization-api-client': path.resolve( + __dirname, + '../../../universal/api-client/src/', + ), + '@contentful/optimization-api-schemas': path.resolve( + __dirname, + '../../../universal/api-schemas/src/', + ), + '@contentful/optimization-core': path.resolve(__dirname, '../../../universal/core/src/'), + }, + }, + + output: { + target: 'web', + }, + + lib: [ + { + ...common, + format: 'esm', + output: { + distPath: { root: 'dist' }, + filename: { js: '[name].mjs' }, + sourceMap: true, + cleanDistPath: true, + minify: true, + }, + + dts: { + bundle: true, + build: false, + }, + + redirect: { + dts: { path: false }, + }, + + tools: { + rspack: maybeEnableRsDoctor, + }, + }, + + { + ...common, + format: 'cjs', + output: { + distPath: { root: 'dist' }, + filename: { js: '[name].cjs' }, + sourceMap: true, + cleanDistPath: false, + minify: true, + }, + dts: false, + tools: { + rspack: maybeEnableRsDoctor, + }, + }, + + { + ...common, + format: 'umd', + autoExternal: false, + umdName: 'Optimization', + source: { + entry: { + 'contentful-optimization-web.umd': './src/Optimization.ts', + }, + }, + output: { + distPath: { root: 'dist' }, + filename: { js: '[name].js' }, + sourceMap: true, + cleanDistPath: false, + minify: true, + }, + dts: false, + tools: { + rspack: (config) => { + ensureUmdDefaultExport(config) + maybeEnableRsDoctor(config) + }, + }, + }, + ], +}) diff --git a/platforms/javascript/react-native/vitest.config.ts b/platforms/javascript/web/rstest.config.ts similarity index 56% rename from platforms/javascript/react-native/vitest.config.ts rename to platforms/javascript/web/rstest.config.ts index 989fd024..87860f35 100644 --- a/platforms/javascript/react-native/vitest.config.ts +++ b/platforms/javascript/web/rstest.config.ts @@ -1,5 +1,7 @@ +import { defineConfig } from '@rstest/core' import { resolve } from 'node:path' -import { defineConfig } from 'vitest/config' + +const coverageReporters = process.env.CI === 'true' ? ['text-summary', 'lcov'] : ['text', 'html'] export default defineConfig({ resolve: { @@ -12,19 +14,15 @@ export default defineConfig({ __dirname, '../../../universal/api-schemas/src/', ), - '@contentful/optimization-core': resolve(__dirname, '../../../universal/core/src/index.ts'), + '@contentful/optimization-core': resolve(__dirname, '../../../universal/core/src/'), logger: resolve(__dirname, '../../../lib/logger/src/'), - 'mocks/loggerMock': resolve(__dirname, '../../../lib/mocks/src/loggerMock.ts'), }, }, - test: { - globals: true, - environment: 'node', - coverage: { - provider: 'v8', - reporter: ['text', 'json', 'html'], - include: ['src/**/*'], - exclude: ['**/*.test.ts', '**/test/**'], - }, + include: ['**/*.test.?(c|m)[jt]s?(x)'], + globals: true, + testEnvironment: 'happy-dom', + coverage: { + include: ['src/**/*'], + reporters: coverageReporters, }, }) diff --git a/platforms/javascript/web/src/observers/ElementExistenceObserver.test.ts b/platforms/javascript/web/src/observers/ElementExistenceObserver.test.ts index 6a3176a9..2f5f3e6d 100644 --- a/platforms/javascript/web/src/observers/ElementExistenceObserver.test.ts +++ b/platforms/javascript/web/src/observers/ElementExistenceObserver.test.ts @@ -1,4 +1,4 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { afterEach, beforeEach, describe, expect, it, rs } from '@rstest/core' import ElementExistenceObserver, { DEFAULT_IDLE_TIMEOUT_MS, MIN_IDLE_TIMEOUT_MS, @@ -59,7 +59,7 @@ const routeIdleThroughTimeout = (): { restore: () => void } => { }, } } - const ricSpy = vi + const ricSpy = rs .spyOn(window, 'requestIdleCallback') .mockImplementation( ( @@ -73,7 +73,7 @@ const routeIdleThroughTimeout = (): { restore: () => void } => { return window.setTimeout(run, timeout) }, ) - const cicSpy = vi.spyOn(window, 'cancelIdleCallback').mockImplementation((handle: number) => { + const cicSpy = rs.spyOn(window, 'cancelIdleCallback').mockImplementation((handle: number) => { window.clearTimeout(handle) }) return { @@ -90,16 +90,16 @@ describe('ElementExistenceObserver', () => { beforeEach(() => { // Save & stub global MutationObserver with our controllable stub. ;({ MutationObserver: originalMO } = window) - vi.stubGlobal('MutationObserver', MOStub) + rs.stubGlobal('MutationObserver', MOStub) instances.length = 0 document.body.innerHTML = '' }) afterEach(() => { // Restore global state and timers/spies between tests. - vi.restoreAllMocks() - vi.useRealTimers() - vi.stubGlobal('MutationObserver', originalMO) + rs.restoreAllMocks() + rs.useRealTimers() + rs.stubGlobal('MutationObserver', originalMO) document.body.innerHTML = '' }) @@ -107,7 +107,7 @@ describe('ElementExistenceObserver', () => { const root = document.createElement('div') document.body.append(root) - const onChange = vi.fn() + const onChange = rs.fn() const eo = new ElementExistenceObserver({ root, onChange, idleTimeoutMs: MIN_IDLE_TIMEOUT_MS }) // Prepare an actual Element so isConnected === true for "added". @@ -147,16 +147,16 @@ describe('ElementExistenceObserver', () => { document.body.append(root) const events: string[] = [] - const onChange = vi.fn( + const onChange = rs.fn( ({ added, removed }: { added: ReadonlySet; removed: ReadonlySet }) => { events.push('change') expect(added.size + removed.size > 0).toBe(true) }, ) - const onRemoved = vi.fn(() => { + const onRemoved = rs.fn(() => { events.push('removed') }) - const onAdded = vi.fn(() => { + const onAdded = rs.fn(() => { events.push('added') }) @@ -192,9 +192,9 @@ describe('ElementExistenceObserver', () => { const root = document.createElement('div') document.body.append(root) - const onChange = vi.fn() - const onAdded = vi.fn() - const onRemoved = vi.fn() + const onChange = rs.fn() + const onAdded = rs.fn() + const onRemoved = rs.fn() const eo = new ElementExistenceObserver({ root, onChange, @@ -234,9 +234,9 @@ describe('ElementExistenceObserver', () => { const root = document.createElement('div') document.body.append(root) - const onChange = vi.fn() - const onAdded = vi.fn() - const onRemoved = vi.fn() + const onChange = rs.fn() + const onAdded = rs.fn() + const onRemoved = rs.fn() const eo = new ElementExistenceObserver({ root, onChange, @@ -260,13 +260,13 @@ describe('ElementExistenceObserver', () => { }) it('chunks per-kind delivery according to maxChunk across idle slices', () => { - vi.useFakeTimers() + rs.useFakeTimers() const { restore } = routeIdleThroughTimeout() const root = document.createElement('div') document.body.append(root) - const onAdded = vi.fn() + const onAdded = rs.fn() const maxChunk = 3 const eo = new ElementExistenceObserver({ root, @@ -302,7 +302,7 @@ describe('ElementExistenceObserver', () => { } // Subsequent chunks are scheduled via idle (routed through setTimeout). - vi.runOnlyPendingTimers() + rs.runOnlyPendingTimers() expect(onAdded).toHaveBeenCalledTimes(2) { const { @@ -315,7 +315,7 @@ describe('ElementExistenceObserver', () => { expect(c).toBe(elements[5]) } - vi.runOnlyPendingTimers() + rs.runOnlyPendingTimers() expect(onAdded).toHaveBeenCalledTimes(3) { const { @@ -331,13 +331,13 @@ describe('ElementExistenceObserver', () => { }) it('flush() processes immediately and cancels pending idle work', () => { - vi.useFakeTimers() + rs.useFakeTimers() const { restore } = routeIdleThroughTimeout() const root = document.createElement('div') document.body.append(root) - const onAdded = vi.fn() + const onAdded = rs.fn() const eo = new ElementExistenceObserver({ root, onAdded, @@ -355,7 +355,7 @@ describe('ElementExistenceObserver', () => { expect(onAdded).toHaveBeenCalledTimes(1) // Any pending idle work should have been canceled by flush(). - vi.runOnlyPendingTimers() + rs.runOnlyPendingTimers() expect(onAdded).toHaveBeenCalledTimes(1) eo.disconnect() @@ -367,11 +367,11 @@ describe('ElementExistenceObserver', () => { document.body.append(root) const errors: unknown[] = [] - const onError = vi.fn((error: unknown) => { + const onError = rs.fn((error: unknown) => { errors.push(error) }) - const onChange = vi.fn(async () => await Promise.reject(new Error('async-aggregate'))) - const onAdded = vi.fn(() => { + const onChange = rs.fn(async () => await Promise.reject(new Error('async-aggregate'))) + const onAdded = rs.fn(() => { throw new Error('sync-added') }) diff --git a/platforms/javascript/web/src/observers/ElementView.test.ts b/platforms/javascript/web/src/observers/ElementView.test.ts index d8f1fda2..ba54ac11 100644 --- a/platforms/javascript/web/src/observers/ElementView.test.ts +++ b/platforms/javascript/web/src/observers/ElementView.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment happy-dom import { cancelRetry, clearFireTimer, @@ -47,13 +46,13 @@ const setVisibilityState = (state: 'visible' | 'hidden'): void => { } beforeEach(() => { - vi.useRealTimers() + rs.useRealTimers() }) afterEach(() => { - vi.restoreAllMocks() - vi.unstubAllGlobals() - vi.useRealTimers() + rs.restoreAllMocks() + rs.unstubAllGlobals() + rs.useRealTimers() }) describe('Environment flags', () => { @@ -70,8 +69,8 @@ describe('Environment flags', () => { describe('NOW', () => { it('uses performance.now() when available', () => { - const perfSpy = vi.spyOn(performance, 'now').mockReturnValue(42) - const dateSpy = vi.spyOn(Date, 'now') + const perfSpy = rs.spyOn(performance, 'now').mockReturnValue(42) + const dateSpy = rs.spyOn(Date, 'now') expect(NOW()).toBe(42) expect(perfSpy).toHaveBeenCalledTimes(1) expect(dateSpy).not.toHaveBeenCalled() @@ -79,11 +78,11 @@ describe('NOW', () => { it('falls back to Date.now() when performance is undefined', () => { const { performance: originalPerf } = globalThis - vi.stubGlobal('performance', undefined) - const dateSpy = vi.spyOn(Date, 'now').mockReturnValue(123456) + rs.stubGlobal('performance', undefined) + const dateSpy = rs.spyOn(Date, 'now').mockReturnValue(123456) expect(NOW()).toBe(123456) // restore immediately to keep other tests safe - vi.stubGlobal('performance', originalPerf) + rs.stubGlobal('performance', originalPerf) expect(dateSpy).toHaveBeenCalledTimes(1) }) }) @@ -93,21 +92,21 @@ describe('withJitter', () => { const base = 100 const span = Math.max(1, Math.floor(base / DEFAULTS.JITTER_DIVISOR)) - vi.spyOn(Math, 'random').mockReturnValueOnce(0) // +0 + rs.spyOn(Math, 'random').mockReturnValueOnce(0) // +0 expect(withJitter(base)).toBe(base) - vi.spyOn(Math, 'random').mockReturnValueOnce(0.5) // +floor(0.5*span) + rs.spyOn(Math, 'random').mockReturnValueOnce(0.5) // +floor(0.5*span) expect(withJitter(base)).toBe(base + Math.floor(0.5 * span)) - vi.spyOn(Math, 'random').mockReturnValueOnce(0.9999) // +(span-1) + rs.spyOn(Math, 'random').mockReturnValueOnce(0.9999) // +(span-1) expect(withJitter(base)).toBe(base + (span - 1)) }) it('handles small bases (0, 1) using max(1, ...)', () => { - vi.spyOn(Math, 'random').mockReturnValueOnce(0.7) + rs.spyOn(Math, 'random').mockReturnValueOnce(0.7) expect(withJitter(0)).toBe(0) - vi.spyOn(Math, 'random').mockReturnValueOnce(0.2) + rs.spyOn(Math, 'random').mockReturnValueOnce(0.2) expect(withJitter(1)).toBe(1) }) }) @@ -140,13 +139,13 @@ describe('Num helpers', () => { describe('Timer utilities', () => { beforeEach(() => { - vi.useFakeTimers() + rs.useFakeTimers() }) it('clearFireTimer clears existing handle and nulls it', () => { const handle = setTimeout(() => undefined, 1000) const state = makeState({ fireTimer: handle }) - const clearSpy = vi.spyOn(globalThis, 'clearTimeout') + const clearSpy = rs.spyOn(globalThis, 'clearTimeout') clearFireTimer(state) expect(clearSpy).toHaveBeenCalledTimes(1) expect(clearSpy).toHaveBeenCalledWith(handle) @@ -155,7 +154,7 @@ describe('Timer utilities', () => { it('clearFireTimer is no-op when null', () => { const state = makeState({ fireTimer: null }) - const clearSpy = vi.spyOn(globalThis, 'clearTimeout') + const clearSpy = rs.spyOn(globalThis, 'clearTimeout') clearFireTimer(state) expect(clearSpy).not.toHaveBeenCalled() expect(state.fireTimer).toBeNull() @@ -164,7 +163,7 @@ describe('Timer utilities', () => { it('cancelRetry clears existing handle and resets scheduling', () => { const handle = setTimeout(() => undefined, 1000) const state = makeState({ retryTimer: handle, retryScheduledAt: 12345 }) - const clearSpy = vi.spyOn(globalThis, 'clearTimeout') + const clearSpy = rs.spyOn(globalThis, 'clearTimeout') cancelRetry(state) expect(clearSpy).toHaveBeenCalledTimes(1) expect(clearSpy).toHaveBeenCalledWith(handle) @@ -174,7 +173,7 @@ describe('Timer utilities', () => { it('cancelRetry is no-op when null, but still nulls retryScheduledAt', () => { const state = makeState({ retryTimer: null, retryScheduledAt: 555 }) - const clearSpy = vi.spyOn(globalThis, 'clearTimeout') + const clearSpy = rs.spyOn(globalThis, 'clearTimeout') cancelRetry(state) expect(clearSpy).not.toHaveBeenCalled() expect(state.retryTimer).toBeNull() diff --git a/platforms/javascript/web/src/observers/ElementViewObserver.test.ts b/platforms/javascript/web/src/observers/ElementViewObserver.test.ts index 3b04c76c..e377529e 100644 --- a/platforms/javascript/web/src/observers/ElementViewObserver.test.ts +++ b/platforms/javascript/web/src/observers/ElementViewObserver.test.ts @@ -33,15 +33,15 @@ describe('ElementViewObserver', () => { } beforeEach(() => { - vi.useFakeTimers() + rs.useFakeTimers() setDocumentVisibility('visible') - vi.spyOn(ElementView, 'withJitter').mockImplementation((n: number) => n) - vi.spyOn(ElementView, 'NOW').mockImplementation(() => Date.now()) + rs.spyOn(ElementView, 'withJitter').mockImplementation((n: number) => n) + rs.spyOn(ElementView, 'NOW').mockImplementation(() => Date.now()) }) afterEach(() => { - vi.clearAllTimers() - vi.restoreAllMocks() + rs.clearAllTimers() + rs.restoreAllMocks() }) afterAll(() => { @@ -50,7 +50,7 @@ describe('ElementViewObserver', () => { it('fires callback once after dwell time when element becomes visible', async () => { const el = makeElement() - const cb = vi.fn<(e: Element, m: Meta) => Promise>().mockResolvedValue(undefined) + const cb = rs.fn<(e: Element, m: Meta) => Promise>().mockResolvedValue(undefined) const obs = new ElementViewObserver(cb, { minVisibleRatio: 0.5, @@ -79,7 +79,7 @@ describe('ElementViewObserver', () => { it('accumulates visible time across visibility toggles', async () => { const el = makeElement() - const cb = vi.fn<(e: Element, m: Meta) => Promise>().mockResolvedValue(undefined) + const cb = rs.fn<(e: Element, m: Meta) => Promise>().mockResolvedValue(undefined) const obs = new ElementViewObserver(cb, { dwellTimeMs: 1000 }) obs.observe(el) @@ -118,7 +118,7 @@ describe('ElementViewObserver', () => { it('pauses timers when page becomes hidden and resumes cleanly', async () => { const el = makeElement() - const cb = vi.fn<(e: Element, m: Meta) => Promise>().mockResolvedValue(undefined) + const cb = rs.fn<(e: Element, m: Meta) => Promise>().mockResolvedValue(undefined) const obs = new ElementViewObserver(cb, { dwellTimeMs: 500 }) obs.observe(el) const inst = mustGetIO() @@ -156,7 +156,7 @@ describe('ElementViewObserver', () => { it('supports per-element overrides and passes data to callback', async () => { const el = makeElement() - const cb = vi.fn<(e: Element, m: Meta) => Promise>().mockResolvedValue(undefined) + const cb = rs.fn<(e: Element, m: Meta) => Promise>().mockResolvedValue(undefined) const obs = new ElementViewObserver(cb, { dwellTimeMs: 10_000, minVisibleRatio: 0.8, @@ -187,7 +187,7 @@ describe('ElementViewObserver', () => { it('exposes readonly stats snapshot via getStats()', async () => { const el = makeElement() - const cb = vi.fn().mockResolvedValue(undefined) + const cb = rs.fn().mockResolvedValue(undefined) const obs = new ElementViewObserver(cb, { dwellTimeMs: 200 }) obs.observe(el) const inst = mustGetIO() @@ -224,7 +224,7 @@ describe('ElementViewObserver', () => { it('coalesces attempts (no duplicate concurrent calls) and retries with exponential backoff', async () => { const el = makeElement() const firstAttempt = deferred() - const cb = vi + const cb = rs .fn<(e: Element, m: Meta) => Promise>() .mockImplementationOnce(async () => { await firstAttempt.promise @@ -259,7 +259,7 @@ describe('ElementViewObserver', () => { await Promise.resolve() // Step to the next scheduled timer (200ms backoff) -> attempt #2 - await vi.advanceTimersToNextTimerAsync() + await rs.advanceTimersToNextTimerAsync() expect(cb).toHaveBeenCalledTimes(2) // Allow attempt #2 rejection to schedule the next retry (400ms) @@ -267,18 +267,18 @@ describe('ElementViewObserver', () => { await Promise.resolve() // Step to the next scheduled timer (400ms backoff) -> attempt #3 (success) - await vi.advanceTimersToNextTimerAsync() + await rs.advanceTimersToNextTimerAsync() expect(cb).toHaveBeenCalledTimes(3) // No more timers should cause additional attempts // (Optionally drain any stragglers; count stays at 3) - await vi.runOnlyPendingTimersAsync() + await rs.runOnlyPendingTimersAsync() expect(cb).toHaveBeenCalledTimes(3) }) it('stops pending retry when element becomes not visible and clears when visible again', async () => { const el = makeElement() - const cb = vi + const cb = rs .fn<(e: Element, m: Meta) => Promise>() .mockRejectedValue(new Error('always-fail')) @@ -313,8 +313,8 @@ describe('ElementViewObserver', () => { it('unobserve() cancels timers and removes internal state; sweep stops when inactive', async () => { const el = makeElement() - const cb = vi.fn().mockResolvedValue(undefined) - const setIntervalSpy = vi.spyOn(globalThis, 'setInterval') + const cb = rs.fn().mockResolvedValue(undefined) + const setIntervalSpy = rs.spyOn(globalThis, 'setInterval') const obs = new ElementViewObserver(cb, { dwellTimeMs: 10_000 }) obs.observe(el) @@ -323,7 +323,7 @@ describe('ElementViewObserver', () => { const inst = mustGetIO() inst.trigger({ target: el, isIntersecting: true, intersectionRatio: 1 }) - expect(vi.getTimerCount()).toBeGreaterThan(0) + expect(rs.getTimerCount()).toBeGreaterThan(0) obs.unobserve(el) await advance(0) @@ -333,8 +333,8 @@ describe('ElementViewObserver', () => { it('sweeps orphaned states for disconnected or dropped elements', async () => { const el = makeElement() - const cb = vi.fn().mockResolvedValue(undefined) - const derefSpy = vi.spyOn(ElementView, 'derefElement') + const cb = rs.fn().mockResolvedValue(undefined) + const derefSpy = rs.spyOn(ElementView, 'derefElement') const obs = new ElementViewObserver(cb, { dwellTimeMs: 1_000 }) @@ -361,9 +361,9 @@ describe('ElementViewObserver', () => { // No unnecessary async/await (satisfies @typescript-eslint/require-await) it('disconnect() clears everything and removes visibility listener', () => { const el = makeElement() - const cb = vi.fn().mockResolvedValue(undefined) - const addSpy = vi.spyOn(document, 'addEventListener') - const removeSpy = vi.spyOn(document, 'removeEventListener') + const cb = rs.fn().mockResolvedValue(undefined) + const addSpy = rs.spyOn(document, 'addEventListener') + const removeSpy = rs.spyOn(document, 'removeEventListener') const obs = new ElementViewObserver(cb, { dwellTimeMs: 100 }) expect(addSpy).toHaveBeenCalledWith('visibilitychange', expect.any(Function)) @@ -371,7 +371,7 @@ describe('ElementViewObserver', () => { obs.observe(el) obs.disconnect() - expect(vi.getTimerCount()).toBe(0) + expect(rs.getTimerCount()).toBe(0) expect(removeSpy).toHaveBeenCalledWith('visibilitychange', expect.any(Function)) }) }) diff --git a/platforms/javascript/web/src/test/helpers.ts b/platforms/javascript/web/src/test/helpers.ts index 801a0e6e..b0016dec 100644 --- a/platforms/javascript/web/src/test/helpers.ts +++ b/platforms/javascript/web/src/test/helpers.ts @@ -1,5 +1,5 @@ // tests/utils.ts -import { vi } from 'vitest' +import { rs } from '@rstest/core' export interface IOEntryInit { target: Element @@ -182,6 +182,6 @@ export function deferred(): { /** Advance timers and flush microtasks. */ export async function advance(ms: number): Promise { - vi.advanceTimersByTime(ms) + rs.advanceTimersByTime(ms) await Promise.resolve() } diff --git a/platforms/javascript/web/tsconfig.json b/platforms/javascript/web/tsconfig.json index 710969ec..4773b6c5 100644 --- a/platforms/javascript/web/tsconfig.json +++ b/platforms/javascript/web/tsconfig.json @@ -1,6 +1,7 @@ { "compilerOptions": { - "lib": ["ESNext", "DOM", "DOM.Iterable"] + "lib": ["ESNext", "DOM", "DOM.Iterable"], + "types": ["@rstest/core/globals"] }, "extends": "../../../tsconfig.base.json", "include": ["./src/**/*", "./src/**/*.json"] diff --git a/platforms/javascript/web/vite.esm.config.ts b/platforms/javascript/web/vite.esm.config.ts deleted file mode 100644 index a586d007..00000000 --- a/platforms/javascript/web/vite.esm.config.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { getPackageName } from 'build-tools' -import path from 'node:path' -import { fileURLToPath } from 'node:url' -import { visualizer } from 'rollup-plugin-visualizer' -import { defineConfig, type UserConfig } from 'vite' -import { analyzer } from 'vite-bundle-analyzer' -import tsconfigPaths from 'vite-tsconfig-paths' - -/* eslint-disable @typescript-eslint/naming-convention -- standardized var names */ -const __filename = fileURLToPath(import.meta.url) -const __dirname = path.dirname(__filename) -const packageName = getPackageName(__dirname, '@contentful/optimization-web') -/* eslint-enable @typescript-eslint/naming-convention -- standardized var names */ - -const config: UserConfig = { - define: { - __OPTIMIZATION_VERSION__: JSON.stringify(process.env.RELEASE_VERSION ?? '0.0.0'), - __OPTIMIZATION_PACKAGE_NAME__: JSON.stringify(packageName), - }, - resolve: { - alias: { - '@contentful/optimization-api-client': path.resolve( - __dirname, - '../../../universal/api-client/src/', - ), - '@contentful/optimization-api-schemas': path.resolve( - __dirname, - '../../../universal/api-schemas/src/', - ), - '@contentful/optimization-core': path.resolve(__dirname, '../../../universal/core/src/'), - }, - }, - esbuild: { - target: 'es2022', - }, - plugins: [ - analyzer({ analyzerMode: 'static', fileName: 'analyzer', openAnalyzer: false }), - visualizer({ - brotliSize: true, - filename: 'dist/visualizer.html', - gzipSize: true, - template: 'flamegraph', - }), - tsconfigPaths(), - ], - test: { - environment: 'happy-dom', - include: ['**/*.test.?(c|m)[jt]s?(x)'], - globals: true, - coverage: { - include: ['src/**/*'], - reporter: ['text', 'html'], - }, - }, -} - -const esm: UserConfig = { - ...config, - build: { - emptyOutDir: false, - lib: { - entry: path.resolve(__dirname, 'src/index.ts'), - fileName: 'index', - }, - rollupOptions: { - output: [ - { - format: 'es', - }, - { - format: 'cjs', - exports: 'named', - }, - ], - }, - sourcemap: true, - }, -} - -export default defineConfig(esm) diff --git a/platforms/javascript/web/vite.umd.config.ts b/platforms/javascript/web/vite.umd.config.ts deleted file mode 100644 index 3b749beb..00000000 --- a/platforms/javascript/web/vite.umd.config.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { getPackageName } from 'build-tools' -import path from 'node:path' -import { fileURLToPath } from 'node:url' -import { visualizer } from 'rollup-plugin-visualizer' -import { defineConfig, type UserConfig } from 'vite' -import { analyzer } from 'vite-bundle-analyzer' -import umdFormatResolver from 'vite-plugin-resolve-umd-format' -import tsconfigPaths from 'vite-tsconfig-paths' - -/* eslint-disable @typescript-eslint/naming-convention -- standardized var names */ -const __filename = fileURLToPath(import.meta.url) -const __dirname = path.dirname(__filename) -const packageName = getPackageName(__dirname, '@contentful/optimization-web') -/* eslint-enable @typescript-eslint/naming-convention -- standardized var names */ - -const config: UserConfig = { - define: { - __OPTIMIZATION_VERSION__: JSON.stringify(process.env.RELEASE_VERSION ?? '0.0.0'), - __OPTIMIZATION_PACKAGE_NAME__: JSON.stringify(packageName), - }, - resolve: { - alias: { - '@contentful/optimization-api-client': path.resolve( - __dirname, - '../../../universal/api-client/src/', - ), - '@contentful/optimization-api-schemas': path.resolve( - __dirname, - '../../../universal/api-schemas/src/', - ), - '@contentful/optimization-core': path.resolve(__dirname, '../../../universal/core/src/'), - }, - }, - esbuild: { - target: 'es2022', - }, - plugins: [ - analyzer({ analyzerMode: 'static', fileName: 'analyzer', openAnalyzer: false }), - umdFormatResolver(), - visualizer({ - brotliSize: true, - filename: 'dist/visualizer.html', - gzipSize: true, - template: 'flamegraph', - }), - tsconfigPaths(), - ], - test: { - environment: 'happy-dom', - include: ['**/*.test.?(c|m)[jt]s?(x)'], - globals: true, - coverage: { - include: ['src/**/*'], - reporter: ['text', 'html'], - }, - }, -} - -const umd: UserConfig = { - ...config, - build: { - emptyOutDir: false, - lib: { - entry: path.resolve(__dirname, 'src/Optimization.ts'), - formats: ['umd'], - fileName: 'contentful-optimization-web', - name: 'Optimization', - }, - sourcemap: true, - }, -} - -export default defineConfig(umd) diff --git a/platforms/javascript/web/vitest.config.ts b/platforms/javascript/web/vitest.config.ts deleted file mode 100644 index cd9c3c7d..00000000 --- a/platforms/javascript/web/vitest.config.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { resolve } from 'node:path' -import tsconfigPaths from 'vite-tsconfig-paths' -import { defineConfig } from 'vitest/config' - -export default defineConfig({ - resolve: { - alias: { - '@contentful/optimization-api-client': resolve( - __dirname, - '../../../universal/api-client/src/', - ), - '@contentful/optimization-api-schemas': resolve( - __dirname, - '../../../universal/api-schemas/src/', - ), - '@contentful/optimization-core': resolve(__dirname, '../../../universal/core/src/'), - logger: resolve(__dirname, '../../../lib/logger/src/'), - }, - }, - esbuild: { - target: 'es2022', - }, - build: { - sourcemap: true, - lib: { - entry: resolve(__dirname, 'src/index.ts'), - name: 'Optimization', - fileName: 'index', - }, - }, - plugins: [tsconfigPaths()], - test: { - environment: 'happy-dom', - include: ['**/*.test.?(c|m)[jt]s?(x)'], - globals: true, - coverage: { - include: ['src/**/*'], - reporter: ['text', 'html'], - }, - }, -}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 09448d4d..82a397a6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,9 +18,21 @@ catalogs: '@preact/signals-core': specifier: ^1.12.1 version: 1.12.1 + '@rsbuild/core': + specifier: ^1.7.2 + version: 1.7.2 + '@rsdoctor/rspack-plugin': + specifier: ^1.5.2 + version: 1.5.2 '@rslib/core': specifier: ^0.19.4 version: 0.19.4 + '@rstest/core': + specifier: ^0.8.5 + version: 0.8.5 + '@rstest/coverage-istanbul': + specifier: ^0.2.1 + version: 0.2.1 '@types/express': specifier: ^5.0.3 version: 5.0.3 @@ -30,9 +42,6 @@ catalogs: '@types/supertest': specifier: ^6.0.3 version: 6.0.3 - '@vitest/coverage-v8': - specifier: ^3.2.4 - version: 3.2.4 contentful: specifier: ^11.10.2 version: 11.10.2 @@ -42,6 +51,9 @@ catalogs: dotenv: specifier: ^17.2.2 version: 17.2.2 + ejs: + specifier: ^3.1.10 + version: 3.1.10 es-toolkit: specifier: ^1.39.10 version: 1.39.10 @@ -69,9 +81,6 @@ catalogs: rimraf: specifier: ^6.0.1 version: 6.0.1 - rollup-plugin-visualizer: - specifier: ^6.0.3 - version: 6.0.3 supertest: specifier: ^7.1.3 version: 7.1.3 @@ -81,18 +90,6 @@ catalogs: tsx: specifier: ^4.20.3 version: 4.20.3 - vite: - specifier: ^7.1.11 - version: 7.1.11 - vite-bundle-analyzer: - specifier: ^1.2.1 - version: 1.2.1 - vite-plugin-resolve-umd-format: - specifier: ^1.0.0 - version: 1.0.0 - vite-tsconfig-paths: - specifier: ^5.1.4 - version: 5.1.4 zod: specifier: ^4.1.5 version: 4.1.5 @@ -181,6 +178,9 @@ importers: contentful: specifier: 'catalog:' version: 11.10.2 + ejs: + specifier: 'catalog:' + version: 3.1.10 express: specifier: 'catalog:' version: 5.1.0 @@ -246,6 +246,12 @@ importers: '@playwright/test': specifier: ^1.57.0 version: 1.57.0 + '@rstest/core': + specifier: 'catalog:' + version: 0.8.5(happy-dom@20.0.2) + '@rstest/coverage-istanbul': + specifier: 'catalog:' + version: 0.2.1(@rstest/core@0.8.5(happy-dom@20.0.2)) '@types/cookie-parser': specifier: 1.4.7 version: 1.4.7 @@ -261,9 +267,6 @@ importers: '@types/supertest': specifier: 'catalog:' version: 6.0.3 - '@vitest/coverage-v8': - specifier: 'catalog:' - version: 3.2.4(vitest@3.2.4(@types/node@24.2.0)(happy-dom@20.0.2)(jiti@2.6.1)(msw@2.10.5(@types/node@24.2.0)(typescript@5.9.2))(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.1)) dotenv: specifier: 'catalog:' version: 17.2.2 @@ -282,9 +285,6 @@ importers: typescript: specifier: ^5.8.3 version: 5.9.2 - vitest: - specifier: ^3.2.4 - version: 3.2.4(@types/node@24.2.0)(happy-dom@20.0.2)(jiti@2.6.1)(msw@2.10.5(@types/node@24.2.0)(typescript@5.9.2))(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.1) implementations/react-native: dependencies: @@ -434,16 +434,22 @@ importers: lib/build-tools: dependencies: + '@rsdoctor/rspack-plugin': + specifier: 'catalog:' + version: 1.5.2(@rsbuild/core@1.7.3)(@rspack/core@1.7.6(@swc/helpers@0.5.18)) tsx: specifier: 'catalog:' version: 4.20.3 devDependencies: + '@rstest/core': + specifier: 'catalog:' + version: 0.8.5(happy-dom@20.0.2) + '@rstest/coverage-istanbul': + specifier: 'catalog:' + version: 0.2.1(@rstest/core@0.8.5(happy-dom@20.0.2)) '@types/node': specifier: ^24.0.13 version: 24.2.0 - '@vitest/coverage-v8': - specifier: 'catalog:' - version: 3.2.4(vitest@3.2.4(@types/node@24.2.0)(happy-dom@20.0.2)(jiti@2.6.1)(msw@2.10.5(@types/node@24.2.0)(typescript@5.9.2))(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.1)) tslib: specifier: 'catalog:' version: 2.8.1 @@ -453,9 +459,6 @@ importers: typescript: specifier: ^5.8.3 version: 5.9.2 - vitest: - specifier: ^3.2.4 - version: 3.2.4(@types/node@24.2.0)(happy-dom@20.0.2)(jiti@2.6.1)(msw@2.10.5(@types/node@24.2.0)(typescript@5.9.2))(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.1) lib/logger: dependencies: @@ -469,12 +472,15 @@ importers: '@rslib/core': specifier: 'catalog:' version: 0.19.4(@microsoft/api-extractor@7.56.0(@types/node@24.2.0))(typescript@5.9.2) + '@rstest/core': + specifier: 'catalog:' + version: 0.8.5(happy-dom@20.0.2) + '@rstest/coverage-istanbul': + specifier: 'catalog:' + version: 0.2.1(@rstest/core@0.8.5(happy-dom@20.0.2)) '@types/node': specifier: ^24.0.13 version: 24.2.0 - '@vitest/coverage-v8': - specifier: 'catalog:' - version: 3.2.4(vitest@3.2.4(@types/node@24.2.0)(happy-dom@20.0.2)(jiti@2.6.1)(msw@2.10.5(@types/node@24.2.0)(typescript@5.9.2))(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.1)) build-tools: specifier: workspace:* version: link:../build-tools @@ -487,9 +493,6 @@ importers: typescript: specifier: ^5.8.3 version: 5.9.2 - vitest: - specifier: ^3.2.4 - version: 3.2.4(@types/node@24.2.0)(happy-dom@20.0.2)(jiti@2.6.1)(msw@2.10.5(@types/node@24.2.0)(typescript@5.9.2))(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.1) lib/mocks: dependencies: @@ -561,15 +564,18 @@ importers: '@rslib/core': specifier: 'catalog:' version: 0.19.4(@microsoft/api-extractor@7.56.0(@types/node@24.2.0))(typescript@5.9.2) + '@rstest/core': + specifier: 'catalog:' + version: 0.8.5(happy-dom@20.0.2) + '@rstest/coverage-istanbul': + specifier: 'catalog:' + version: 0.2.1(@rstest/core@0.8.5(happy-dom@20.0.2)) '@types/express': specifier: 'catalog:' version: 5.0.3 '@types/node': specifier: ^24.0.13 version: 24.2.0 - '@vitest/coverage-v8': - specifier: 'catalog:' - version: 3.2.4(vitest@3.2.4(@types/node@24.2.0)(happy-dom@20.0.2)(jiti@2.6.1)(msw@2.10.5(@types/node@24.2.0)(typescript@5.9.2))(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.1)) build-tools: specifier: workspace:* version: link:../../../lib/build-tools @@ -597,9 +603,6 @@ importers: typescript: specifier: ^5.8.3 version: 5.9.2 - vitest: - specifier: ^3.2.4 - version: 3.2.4(@types/node@24.2.0)(happy-dom@20.0.2)(jiti@2.6.1)(msw@2.10.5(@types/node@24.2.0)(typescript@5.9.2))(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.1) platforms/javascript/react-native: dependencies: @@ -685,6 +688,12 @@ importers: '@rslib/core': specifier: 'catalog:' version: 0.19.4(@microsoft/api-extractor@7.56.0(@types/node@24.2.0))(typescript@5.9.2) + '@rstest/core': + specifier: 'catalog:' + version: 0.8.5(happy-dom@20.0.2) + '@rstest/coverage-istanbul': + specifier: 'catalog:' + version: 0.2.1(@rstest/core@0.8.5(happy-dom@20.0.2)) '@testing-library/react-native': specifier: ^13.3.3 version: 13.3.3(jest@29.7.0(@types/node@24.2.0))(react-native@0.76.9(@babel/core@7.28.4)(@babel/preset-env@7.28.3(@babel/core@7.28.4))(@react-native-community/cli@15.0.1(typescript@5.9.2))(@types/react@18.3.26)(react@18.3.1))(react-test-renderer@18.3.1(react@18.3.1))(react@18.3.1) @@ -697,9 +706,6 @@ importers: '@types/react-test-renderer': specifier: ^18.0.0 version: 18.3.1 - '@vitest/coverage-v8': - specifier: 'catalog:' - version: 3.2.4(vitest@3.2.4(@types/node@24.2.0)(happy-dom@20.0.2)(jiti@2.6.1)(msw@2.10.5(@types/node@24.2.0)(typescript@5.9.2))(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.1)) babel-jest: specifier: ^29.6.3 version: 29.7.0(@babel/core@7.28.4) @@ -742,9 +748,6 @@ importers: typescript: specifier: ^5.8.3 version: 5.9.2 - vitest: - specifier: ^3.2.4 - version: 3.2.4(@types/node@24.2.0)(happy-dom@20.0.2)(jiti@2.6.1)(msw@2.10.5(@types/node@24.2.0)(typescript@5.9.2))(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.1) platforms/javascript/web: dependencies: @@ -764,9 +767,18 @@ importers: specifier: 'catalog:' version: 4.1.5 devDependencies: - '@vitest/coverage-v8': + '@rsbuild/core': + specifier: 'catalog:' + version: 1.7.2 + '@rslib/core': + specifier: 'catalog:' + version: 0.19.4(@microsoft/api-extractor@7.56.0(@types/node@24.2.0))(typescript@5.9.2) + '@rstest/core': + specifier: 'catalog:' + version: 0.8.5(happy-dom@20.0.2) + '@rstest/coverage-istanbul': specifier: 'catalog:' - version: 3.2.4(vitest@3.2.4(@types/node@24.2.0)(happy-dom@20.0.2)(jiti@2.6.1)(msw@2.10.5(@types/node@24.2.0)(typescript@5.9.2))(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.1)) + version: 0.2.1(@rstest/core@0.8.5(happy-dom@20.0.2)) build-tools: specifier: workspace:* version: link:../../../lib/build-tools @@ -776,30 +788,12 @@ importers: rimraf: specifier: 'catalog:' version: 6.0.1 - rollup-plugin-visualizer: - specifier: 'catalog:' - version: 6.0.3(rolldown@1.0.0-rc.1)(rollup@4.46.2) tslib: specifier: 'catalog:' version: 2.8.1 typescript: specifier: ^5.8.3 version: 5.9.2 - vite: - specifier: 'catalog:' - version: 7.1.11(@types/node@24.2.0)(jiti@2.6.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.1) - vite-bundle-analyzer: - specifier: 'catalog:' - version: 1.2.1 - vite-plugin-resolve-umd-format: - specifier: 'catalog:' - version: 1.0.0(vite@7.1.11(@types/node@24.2.0)(jiti@2.6.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.1)) - vite-tsconfig-paths: - specifier: 'catalog:' - version: 5.1.4(typescript@5.9.2)(vite@7.1.11(@types/node@24.2.0)(jiti@2.6.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.1)) - vitest: - specifier: ^3.2.4 - version: 3.2.4(@types/node@24.2.0)(happy-dom@20.0.2)(jiti@2.6.1)(msw@2.10.5(@types/node@24.2.0)(typescript@5.9.2))(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.1) platforms/javascript/web-preview-panel: dependencies: @@ -819,42 +813,24 @@ importers: specifier: ^3.3.2 version: 3.3.2 devDependencies: - '@vitest/coverage-v8': + '@rsbuild/core': specifier: 'catalog:' - version: 3.2.4(vitest@3.2.4(@types/node@24.2.0)(happy-dom@20.0.2)(jiti@2.6.1)(msw@2.10.5(@types/node@24.2.0)(typescript@5.9.2))(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.1)) + version: 1.7.2 + '@rslib/core': + specifier: 'catalog:' + version: 0.19.4(@microsoft/api-extractor@7.56.0(@types/node@24.2.0))(typescript@5.9.2) build-tools: specifier: workspace:* version: link:../../../lib/build-tools - happy-dom: - specifier: 'catalog:' - version: 20.0.2 rimraf: specifier: 'catalog:' version: 6.0.1 - rollup-plugin-visualizer: - specifier: 'catalog:' - version: 6.0.3(rolldown@1.0.0-rc.1)(rollup@4.46.2) tslib: specifier: 'catalog:' version: 2.8.1 typescript: specifier: ^5.8.3 version: 5.9.2 - vite: - specifier: 'catalog:' - version: 7.1.11(@types/node@24.2.0)(jiti@2.6.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.1) - vite-bundle-analyzer: - specifier: 'catalog:' - version: 1.2.1 - vite-plugin-resolve-umd-format: - specifier: 'catalog:' - version: 1.0.0(vite@7.1.11(@types/node@24.2.0)(jiti@2.6.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.1)) - vite-tsconfig-paths: - specifier: 'catalog:' - version: 5.1.4(typescript@5.9.2)(vite@7.1.11(@types/node@24.2.0)(jiti@2.6.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.1)) - vitest: - specifier: ^3.2.4 - version: 3.2.4(@types/node@24.2.0)(happy-dom@20.0.2)(jiti@2.6.1)(msw@2.10.5(@types/node@24.2.0)(typescript@5.9.2))(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.1) universal/api-client: dependencies: @@ -880,12 +856,15 @@ importers: '@rslib/core': specifier: 'catalog:' version: 0.19.4(@microsoft/api-extractor@7.56.0(@types/node@24.2.0))(typescript@5.9.2) + '@rstest/core': + specifier: 'catalog:' + version: 0.8.5(happy-dom@20.0.2) + '@rstest/coverage-istanbul': + specifier: 'catalog:' + version: 0.2.1(@rstest/core@0.8.5(happy-dom@20.0.2)) '@types/node': specifier: ^24.0.13 version: 24.2.0 - '@vitest/coverage-v8': - specifier: 'catalog:' - version: 3.2.4(vitest@3.2.4(@types/node@24.2.0)(happy-dom@20.0.2)(jiti@2.6.1)(msw@2.10.5(@types/node@24.2.0)(typescript@5.9.2))(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.1)) build-tools: specifier: workspace:* version: link:../../lib/build-tools @@ -907,9 +886,6 @@ importers: typescript: specifier: ^5.8.3 version: 5.9.2 - vitest: - specifier: ^3.2.4 - version: 3.2.4(@types/node@24.2.0)(happy-dom@20.0.2)(jiti@2.6.1)(msw@2.10.5(@types/node@24.2.0)(typescript@5.9.2))(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.1) universal/api-schemas: dependencies: @@ -975,12 +951,15 @@ importers: '@rslib/core': specifier: 'catalog:' version: 0.19.4(@microsoft/api-extractor@7.56.0(@types/node@24.2.0))(typescript@5.9.2) + '@rstest/core': + specifier: 'catalog:' + version: 0.8.5(happy-dom@20.0.2) + '@rstest/coverage-istanbul': + specifier: 'catalog:' + version: 0.2.1(@rstest/core@0.8.5(happy-dom@20.0.2)) '@types/node': specifier: ^24.0.13 version: 24.2.0 - '@vitest/coverage-v8': - specifier: 'catalog:' - version: 3.2.4(vitest@3.2.4(@types/node@24.2.0)(happy-dom@20.0.2)(jiti@2.6.1)(msw@2.10.5(@types/node@24.2.0)(typescript@5.9.2))(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.1)) build-tools: specifier: workspace:* version: link:../../lib/build-tools @@ -1002,16 +981,9 @@ importers: typescript: specifier: ^5.8.3 version: 5.9.2 - vitest: - specifier: ^3.2.4 - version: 3.2.4(@types/node@24.2.0)(happy-dom@20.0.2)(jiti@2.6.1)(msw@2.10.5(@types/node@24.2.0)(typescript@5.9.2))(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.1) packages: - '@ampproject/remapping@2.3.0': - resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} - engines: {node: '>=6.0.0'} - '@ast-grep/napi-darwin-arm64@0.37.0': resolution: {integrity: sha512-QAiIiaAbLvMEg/yBbyKn+p1gX2/FuaC0SMf7D7capm/oG4xGMzdeaQIcSosF4TCxxV+hIH4Bz9e4/u7w6Bnk3Q==} engines: {node: '>= 10'} @@ -1074,6 +1046,10 @@ packages: resolution: {integrity: sha512-Hb4o6h1Pf6yRUAX07DR4JVY7dmQw+RVQMW5/m55GoiAT/VRoKCWBtIUPPOnqDVhbx1Cjfil9b6EDrgJsUAujEQ==} engines: {node: '>= 10'} + '@babel/code-frame@7.26.2': + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} + engines: {node: '>=6.9.0'} + '@babel/code-frame@7.27.1': resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} @@ -1781,10 +1757,6 @@ packages: '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} - '@bcoe/v8-coverage@1.0.2': - resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} - engines: {node: '>=18'} - '@bundled-es-modules/cookie@2.0.1': resolution: {integrity: sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==} @@ -2416,9 +2388,6 @@ packages: '@napi-rs/wasm-runtime@1.0.7': resolution: {integrity: sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==} - '@napi-rs/wasm-runtime@1.1.1': - resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==} - '@noble/hashes@1.8.0': resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} engines: {node: ^14.21.3 || >=16} @@ -2444,9 +2413,6 @@ packages: '@open-draft/until@2.1.0': resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} - '@oxc-project/types@0.110.0': - resolution: {integrity: sha512-6Ct21OIlrEnFEJk5LT4e63pk3btsI6/TusD/GStLi7wYlGJNOl1GI9qvXAnRAxQU9zqA2Oz+UwhfTOU2rPZVow==} - '@paralleldrive/cuid2@2.2.2': resolution: {integrity: sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==} @@ -2649,90 +2615,6 @@ packages: '@react-navigation/routers@6.1.9': resolution: {integrity: sha512-lTM8gSFHSfkJvQkxacGM6VJtBt61ip2XO54aNfswD+KMw6eeZ4oehl7m0me3CR9hnDE4+60iAZR8sAhvCiI3NA==} - '@rolldown/binding-android-arm64@1.0.0-rc.1': - resolution: {integrity: sha512-He6ZoCfv5D7dlRbrhNBkuMVIHd0GDnjJwbICE1OWpG7G3S2gmJ+eXkcNLJjzjNDpeI2aRy56ou39AJM9AD8YFA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [android] - - '@rolldown/binding-darwin-arm64@1.0.0-rc.1': - resolution: {integrity: sha512-YzJdn08kSOXnj85ghHauH2iHpOJ6eSmstdRTLyaziDcUxe9SyQJgGyx/5jDIhDvtOcNvMm2Ju7m19+S/Rm1jFg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [darwin] - - '@rolldown/binding-darwin-x64@1.0.0-rc.1': - resolution: {integrity: sha512-cIvAbqM+ZVV6lBSKSBtlNqH5iCiW933t1q8j0H66B3sjbe8AxIRetVqfGgcHcJtMzBIkIALlL9fcDrElWLJQcQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [darwin] - - '@rolldown/binding-freebsd-x64@1.0.0-rc.1': - resolution: {integrity: sha512-rVt+B1B/qmKwCl1XD02wKfgh3vQPXRXdB/TicV2w6g7RVAM1+cZcpigwhLarqiVCxDObFZ7UgXCxPC7tpDoRog==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [freebsd] - - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.1': - resolution: {integrity: sha512-69YKwJJBOFprQa1GktPgbuBOfnn+EGxu8sBJ1TjPER+zhSpYeaU4N07uqmyBiksOLGXsMegymuecLobfz03h8Q==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.1': - resolution: {integrity: sha512-9JDhHUf3WcLfnViFWm+TyorqUtnSAHaCzlSNmMOq824prVuuzDOK91K0Hl8DUcEb9M5x2O+d2/jmBMsetRIn3g==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.1': - resolution: {integrity: sha512-UvApLEGholmxw/HIwmUnLq3CwdydbhaHHllvWiCTNbyGom7wTwOtz5OAQbAKZYyiEOeIXZNPkM7nA4Dtng7CLw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.1': - resolution: {integrity: sha512-uVctNgZHiGnJx5Fij7wHLhgw4uyZBVi6mykeWKOqE7bVy9Hcxn0fM/IuqdMwk6hXlaf9fFShDTFz2+YejP+x0A==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-x64-musl@1.0.0-rc.1': - resolution: {integrity: sha512-T6Eg0xWwcxd/MzBcuv4Z37YVbUbJxy5cMNnbIt/Yr99wFwli30O4BPlY8hKeGyn6lWNtU0QioBS46lVzDN38bg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [musl] - - '@rolldown/binding-openharmony-arm64@1.0.0-rc.1': - resolution: {integrity: sha512-PuGZVS2xNJyLADeh2F04b+Cz4NwvpglbtWACgrDOa5YDTEHKwmiTDjoD5eZ9/ptXtcpeFrMqD2H4Zn33KAh1Eg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [openharmony] - - '@rolldown/binding-wasm32-wasi@1.0.0-rc.1': - resolution: {integrity: sha512-2mOxY562ihHlz9lEXuaGEIDCZ1vI+zyFdtsoa3M62xsEunDXQE+DVPO4S4x5MPK9tKulG/aFcA/IH5eVN257Cw==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.1': - resolution: {integrity: sha512-oQVOP5cfAWZwRD0Q3nGn/cA9FW3KhMMuQ0NIndALAe6obqjLhqYVYDiGGRGrxvnjJsVbpLwR14gIUYnpIcHR1g==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [win32] - - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.1': - resolution: {integrity: sha512-Ydsxxx++FNOuov3wCBPaYjZrEvKOOGq3k+BF4BPridhg2pENfitSRD2TEuQ8i33bp5VptuNdC9IzxRKU031z5A==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [win32] - - '@rolldown/pluginutils@1.0.0-rc.1': - resolution: {integrity: sha512-UTBjtTxVOhodhzFVp/ayITaTETRHPUPYZPXQe0WU0wOgxghMojXxYjOiPOauKIYNWJAWS2fd7gJgGQK8GU8vDA==} - '@rollup/rollup-android-arm-eabi@4.46.2': resolution: {integrity: sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==} cpu: [arm] @@ -2849,6 +2731,53 @@ packages: engines: {node: '>=18.12.0'} hasBin: true + '@rsbuild/core@1.7.3': + resolution: {integrity: sha512-kI1oQvCXbQYxUvQPnDLdjSX4gFsbrFNpuUj6jXEJ7IcJ74Q+n4oeFj74/8tKerhxhe0L90m/ZQfzLeN5ORGA9w==} + engines: {node: '>=18.12.0'} + hasBin: true + + '@rsbuild/plugin-check-syntax@1.6.1': + resolution: {integrity: sha512-26xtEYN0QjZYoyt0lWnvIztBWjEZJvcfw7MN4f5B4SpNggmnF7F7aNPrgkY3EccXVFx1VGQBhnCkBV//OoS07Q==} + peerDependencies: + '@rsbuild/core': ^1.0.0 || ^2.0.0-0 + peerDependenciesMeta: + '@rsbuild/core': + optional: true + + '@rsdoctor/client@1.5.2': + resolution: {integrity: sha512-fufNlCiA4+MDj3ZW8ssEdRI9mwawdqSSYvqOOK01+NNIg3TuQNgEdnF/QVGnkxKLgVXJCtUdOKaZtUq1bwnJVQ==} + + '@rsdoctor/core@1.5.2': + resolution: {integrity: sha512-pAtehxWiOhEef7+nxmeX7EDdABh9maAtmD+G/0MNC5zc/aKqzh2KD/CCrEO9SqSoVnfkTot1BQ54kLgBMFvclw==} + + '@rsdoctor/graph@1.5.2': + resolution: {integrity: sha512-4Wt/Hg4Z3uSJBJQh1pm9cFeYI8lHhSFdWbR39dPOiIC/Q1/86TVdo63GBYMLYyXWRZ58S5kDL5ZjO9IyDLVgug==} + + '@rsdoctor/rspack-plugin@1.5.2': + resolution: {integrity: sha512-l097etdtvz4osG5rDJX0RBbIqLAZ+oSUXOJu11Y9aFg+4/lohrZZa4lj1R/7hxlqC0XuROir2zsbRaAuCGtxzw==} + peerDependencies: + '@rspack/core': '*' + peerDependenciesMeta: + '@rspack/core': + optional: true + + '@rsdoctor/sdk@1.5.2': + resolution: {integrity: sha512-VPJoSO1gPIlMDLumzGgphhIyNM8s7NPgbN/AAFwbU/uWzEwC2Gy2joLwoYkOw32UDdMD/MiuXrQoP+fnjAYjjQ==} + + '@rsdoctor/types@1.5.2': + resolution: {integrity: sha512-sOj7H/Dc9F3LPM/04dgJSpBhAb7DNlqcm9uipW6+ZCB/pr4dcwUDo8lHcjaEgQWeUfvHSUHjtzarp9EVpfkFsg==} + peerDependencies: + '@rspack/core': '*' + webpack: 5.x + peerDependenciesMeta: + '@rspack/core': + optional: true + webpack: + optional: true + + '@rsdoctor/utils@1.5.2': + resolution: {integrity: sha512-Zdvpp4GdJKgQYXLuILM124YU0peMDebr95k2s5zTTuB2LBkHENf8UdjJs7ijKyoaVYKqxXTBdghekXEFcdo6jQ==} + '@rslib/core@0.19.4': resolution: {integrity: sha512-qFJKXxFGf582uNWyD+2cxKVoorsDepWn7M0VXZX8BLzaEXVBzQmQ0i9UojWicmJr3ui9nkI7WRh66FQzoyWREg==} engines: {node: '>=18.12.0'} @@ -2867,57 +2796,113 @@ packages: cpu: [arm64] os: [darwin] + '@rspack/binding-darwin-arm64@1.7.6': + resolution: {integrity: sha512-NZ9AWtB1COLUX1tA9HQQvWpTy07NSFfKBU8A6ylWd5KH8AePZztpNgLLAVPTuNO4CZXYpwcoclf8jG/luJcQdQ==} + cpu: [arm64] + os: [darwin] + '@rspack/binding-darwin-x64@1.7.4': resolution: {integrity: sha512-Oq65S5szs3+In9hVWfPksdL6EUu1+SFZK3oQINP3kMJ5zPzrdyiue+L5ClpTU/VMKVxfQTdCBsI6OVJNnaLBiA==} cpu: [x64] os: [darwin] + '@rspack/binding-darwin-x64@1.7.6': + resolution: {integrity: sha512-J2g6xk8ZS7uc024dNTGTHxoFzFovAZIRixUG7PiciLKTMP78svbSSWrmW6N8oAsAkzYfJWwQpVgWfFNRHvYxSw==} + cpu: [x64] + os: [darwin] + '@rspack/binding-linux-arm64-gnu@1.7.4': resolution: {integrity: sha512-sTpfCraAtYZBhdw9Xx5a19OgJ/mBELTi61utZzrO3bV6BFEulvOdmnNjpgb0xv1KATtNI8YxECohUzekk1WsOA==} cpu: [arm64] os: [linux] libc: [glibc] + '@rspack/binding-linux-arm64-gnu@1.7.6': + resolution: {integrity: sha512-eQfcsaxhFrv5FmtaA7+O1F9/2yFDNIoPZzV/ZvqvFz5bBXVc4FAm/1fVpBg8Po/kX1h0chBc7Xkpry3cabFW8w==} + cpu: [arm64] + os: [linux] + libc: [glibc] + '@rspack/binding-linux-arm64-musl@1.7.4': resolution: {integrity: sha512-sw8jZbUe13Ry0/tnUt1pSdwkaPtSzKuveq+b6/CUT26I3DKfJQoG0uJbjj2quMe4ks3jDmoGlxuRe4D/fWUoSg==} cpu: [arm64] os: [linux] libc: [musl] + '@rspack/binding-linux-arm64-musl@1.7.6': + resolution: {integrity: sha512-DfQXKiyPIl7i1yECHy4eAkSmlUzzsSAbOjgMuKn7pudsWf483jg0UUYutNgXSlBjc/QSUp7906Cg8oty9OfwPA==} + cpu: [arm64] + os: [linux] + libc: [musl] + '@rspack/binding-linux-x64-gnu@1.7.4': resolution: {integrity: sha512-1W6LU0wR/TxB+8pogt0pn0WRwbQmKfu9839p/VBuSkNdWR4aljAhYO6RxsLQLCLrDAqEyrpeYWsWJBvAJ4T/pA==} cpu: [x64] os: [linux] libc: [glibc] + '@rspack/binding-linux-x64-gnu@1.7.6': + resolution: {integrity: sha512-NdA+2X3lk2GGrMMnTGyYTzM3pn+zNjaqXqlgKmFBXvjfZqzSsKq3pdD1KHZCd5QHN+Fwvoszj0JFsquEVhE1og==} + cpu: [x64] + os: [linux] + libc: [glibc] + '@rspack/binding-linux-x64-musl@1.7.4': resolution: {integrity: sha512-rkmu8qLnm/q8J14ZQZ04SnPNzdRNgzAoKJCTbnhCzcuL5k5e20LUFfGuS6j7Io1/UdVMOjz/u7R6b9h/qA1Scw==} cpu: [x64] os: [linux] libc: [musl] + '@rspack/binding-linux-x64-musl@1.7.6': + resolution: {integrity: sha512-rEy6MHKob02t/77YNgr6dREyJ0e0tv1X6Xsg8Z5E7rPXead06zefUbfazj4RELYySWnM38ovZyJAkPx/gOn3VA==} + cpu: [x64] + os: [linux] + libc: [musl] + '@rspack/binding-wasm32-wasi@1.7.4': resolution: {integrity: sha512-6BQvLbDtUVkTN5o1QYLYKAYuXavC4ER5Vn/amJEoecbM9F25MNAv28inrXs7BQ4cHSU4WW/F4yZPGnA+jUZLyw==} cpu: [wasm32] + '@rspack/binding-wasm32-wasi@1.7.6': + resolution: {integrity: sha512-YupOrz0daSG+YBbCIgpDgzfMM38YpChv+afZpaxx5Ml7xPeAZIIdgWmLHnQ2rts73N2M1NspAiBwV00Xx0N4Vg==} + cpu: [wasm32] + '@rspack/binding-win32-arm64-msvc@1.7.4': resolution: {integrity: sha512-kipggu7xVPhnAkAV7koSDVbBuuMDMA4hX60DNJKTS6fId3XNHcZqWKIsWGOt0yQ6KV7I3JRRBDotKLx6uYaRWw==} cpu: [arm64] os: [win32] + '@rspack/binding-win32-arm64-msvc@1.7.6': + resolution: {integrity: sha512-INj7aVXjBvlZ84kEhSK4kJ484ub0i+BzgnjDWOWM1K+eFYDZjLdAsQSS3fGGXwVc3qKbPIssFfnftATDMTEJHQ==} + cpu: [arm64] + os: [win32] + '@rspack/binding-win32-ia32-msvc@1.7.4': resolution: {integrity: sha512-9Zdozc13AUQHqagDDHxHml1FnZZWuSj/uP+SxtlTlQaiIE9GDH3n0cUio1GUq+cBKbcXeiE3dJMGJxhiFaUsxA==} cpu: [ia32] os: [win32] + '@rspack/binding-win32-ia32-msvc@1.7.6': + resolution: {integrity: sha512-lXGvC+z67UMcw58In12h8zCa9IyYRmuptUBMItQJzu+M278aMuD1nETyGLL7e4+OZ2lvrnnBIcjXN1hfw2yRzw==} + cpu: [ia32] + os: [win32] + '@rspack/binding-win32-x64-msvc@1.7.4': resolution: {integrity: sha512-3a/jZTUrvU340IuRcxul+ccsDtdrMaGq/vi4HNcWalL0H2xeOeuieBAV8AZqaRjmxMu8OyRcpcSrkHtN1ol/eA==} cpu: [x64] os: [win32] + '@rspack/binding-win32-x64-msvc@1.7.6': + resolution: {integrity: sha512-zeUxEc0ZaPpmaYlCeWcjSJUPuRRySiSHN23oJ2Xyw0jsQ01Qm4OScPdr0RhEOFuK/UE+ANyRtDo4zJsY52Hadw==} + cpu: [x64] + os: [win32] + '@rspack/binding@1.7.4': resolution: {integrity: sha512-BOACDXd9aTrdJgqa88KGxnTGdUdVLAClTCLhSvdNvQZIcaVLOB1qtW0TvqjZ19MxuQB/Cba5u/ILc5DNXxuDhg==} + '@rspack/binding@1.7.6': + resolution: {integrity: sha512-/NrEcfo8Gx22hLGysanrV6gHMuqZSxToSci/3M4kzEQtF5cPjfOv5pqeLK/+B6cr56ul/OmE96cCdWcXeVnFjQ==} + '@rspack/core@1.7.4': resolution: {integrity: sha512-6QNqcsRSy1WbAGvjA2DAEx4yyAzwrvT6vd24Kv4xdZHdvF6FmcUbr5J+mLJ1jSOXvpNhZ+RzN37JQ8fSmytEtw==} engines: {node: '>=18.12.0'} @@ -2927,9 +2912,36 @@ packages: '@swc/helpers': optional: true + '@rspack/core@1.7.6': + resolution: {integrity: sha512-Iax6UhrfZqJajA778c1d5DBFbSIqPOSrI34kpNIiNpWd8Jq7mFIa+Z60SQb5ZQDZuUxcCZikjz5BxinFjTkg7Q==} + engines: {node: '>=18.12.0'} + peerDependencies: + '@swc/helpers': '>=0.5.1' + peerDependenciesMeta: + '@swc/helpers': + optional: true + '@rspack/lite-tapable@1.1.0': resolution: {integrity: sha512-E2B0JhYFmVAwdDiG14+DW0Di4Ze4Jg10Pc4/lILUrd5DRCaklduz2OvJ5HYQ6G+hd+WTzqQb3QnDNfK4yvAFYw==} + '@rstest/core@0.8.5': + resolution: {integrity: sha512-4E9pXpy2Jxy1+J4mqGnsVQn7gVnkIv2lA7AbwAY0Zy90jJo+YCchpTGosba/jhBJJhETRAr3GIkPUECxFoyQ9A==} + engines: {node: '>=18.12.0'} + hasBin: true + peerDependencies: + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + happy-dom: + optional: true + jsdom: + optional: true + + '@rstest/coverage-istanbul@0.2.1': + resolution: {integrity: sha512-KJEa1JZooZBtIe9K7AB7CVihCYa2avQAvp4C4NXsZ9cB69qQfbdLFqY8kihEopFLbfuKcrCGagjNFQ8KtvS+6w==} + peerDependencies: + '@rstest/core': ~0.8.2 + '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} @@ -3017,6 +3029,9 @@ packages: '@so-ric/colorspace@1.1.6': resolution: {integrity: sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==} + '@socket.io/component-emitter@3.1.2': + resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} + '@standard-schema/spec@1.0.0': resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} @@ -3062,6 +3077,9 @@ packages: '@types/chai@5.2.2': resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@types/connect@3.4.38': resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} @@ -3077,9 +3095,15 @@ packages: '@types/cookiejar@2.1.5': resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==} + '@types/cors@2.8.19': + resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==} + '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/estree@1.0.5': + resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -3173,6 +3197,9 @@ packages: '@types/supertest@6.0.3': resolution: {integrity: sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==} + '@types/tapable@2.2.7': + resolution: {integrity: sha512-D6QzACV9vNX3r8HQQNTOnpG+Bv1rko+yEA82wKs3O9CQ5+XW7HI7TED17/UE7+5dIxyxZIWTxKbsBeF6uKFCwA==} + '@types/tough-cookie@4.0.5': resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} @@ -3365,15 +3392,6 @@ packages: '@vercel/stega@0.1.2': resolution: {integrity: sha512-P7mafQXjkrsoyTRppnt0N21udKS9wUmLXHRyP9saLXLHw32j/FgUJ3FscSWgvSqRs4cj7wKZtwqJEvWJ2jbGmA==} - '@vitest/coverage-v8@3.2.4': - resolution: {integrity: sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==} - peerDependencies: - '@vitest/browser': 3.2.4 - vitest: ^3.2.4 - peerDependenciesMeta: - '@vitest/browser': - optional: true - '@vitest/expect@3.2.4': resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} @@ -3433,11 +3451,20 @@ packages: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} + acorn-import-attributes@1.9.5: + resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} + peerDependencies: + acorn: ^8 + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + acorn-walk@8.3.4: + resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + engines: {node: '>=0.4.0'} + acorn@7.4.1: resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} engines: {node: '>=0.4.0'} @@ -3659,9 +3686,6 @@ packages: resolution: {integrity: sha512-RIOpVnVlltB6PcBJ5BMLx+H+6JJ/zjDGU0t7f0L6c2M1dqcK92VQopLBlPQ9R80AVXelfqYgjcPLtHtDbNFg0Q==} engines: {node: '>= 0.6'} - ast-v8-to-istanbul@0.3.4: - resolution: {integrity: sha512-cxrAnZNLBnQwBPByK4CeDaw5sWZtMilJE/Q3iDA0aamgaIVNDF9T6K2/8DfYDZEejZ2jNnDrG9m8MY72HFd0KA==} - astral-regex@1.0.0: resolution: {integrity: sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==} engines: {node: '>=4'} @@ -3765,10 +3789,18 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + base64id@2.0.0: + resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} + engines: {node: ^4.5.0 || >= 5.9} + baseline-browser-mapping@2.8.18: resolution: {integrity: sha512-UYmTpOBwgPScZpS4A+YbapwWuBwasxvO/2IOHArSsAhL/+ZdmATBXTex3t+l2hXwLVYK382ibr/nKoY9GKe86w==} hasBin: true + baseline-browser-mapping@2.9.19: + resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==} + hasBin: true + basic-ftp@5.0.5: resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==} engines: {node: '>=10.0.0'} @@ -3815,11 +3847,23 @@ packages: browser-process-hrtime@1.0.0: resolution: {integrity: sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==} + browserslist-load-config@1.0.1: + resolution: {integrity: sha512-orLR5HAoQugQNVUXUwNd+GAAwl3H64KLIwoMFBNW0AbMSqX2Lxs4ZV2/5UoNrVQlQqF9ygychiu7Svr/99bLtg==} + + browserslist-to-es-version@1.4.1: + resolution: {integrity: sha512-1bYCrck5Qh5HUy7P+iDuK39v757/ry5PnQo20vf4sHGeUrYKL2N2OF05U9ARSGt06TpFDQiTv9MT+eitYgWWxA==} + hasBin: true + browserslist@4.26.3: resolution: {integrity: sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + bser@2.1.1: resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} @@ -3920,6 +3964,9 @@ packages: caniuse-lite@1.0.30001751: resolution: {integrity: sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==} + caniuse-lite@1.0.30001769: + resolution: {integrity: sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==} + cardinal@2.1.1: resolution: {integrity: sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==} hasBin: true @@ -4308,6 +4355,10 @@ packages: core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + cosmiconfig-typescript-loader@5.0.0: resolution: {integrity: sha512-+8cK7jRAReYkMwMiG+bxhcNKiHJDM6bR9FD/nGBXOWdMLuYawjF5cGrtLilJ+LGd3ZjCXnJjR5DkfWPoIVlqJA==} engines: {node: '>=v16'} @@ -4461,6 +4512,10 @@ packages: babel-plugin-macros: optional: true + deep-eql@4.1.4: + resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} + engines: {node: '>=6'} + deep-eql@5.0.2: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} @@ -4554,6 +4609,19 @@ packages: doctypes@1.1.0: resolution: {integrity: sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==} + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + dotenv@16.6.1: resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} engines: {node: '>=12'} @@ -4586,9 +4654,17 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + ejs@3.1.10: + resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} + engines: {node: '>=0.10.0'} + hasBin: true + electron-to-chromium@1.5.237: resolution: {integrity: sha512-icUt1NvfhGLar5lSWH3tHNzablaA5js3HVHacQimfP8ViEBOQv+L7DKEuHdbTZ0SKCO1ogTJTIL1Gwk9S6Qvcg==} + electron-to-chromium@1.5.286: + resolution: {integrity: sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==} + elegant-spinner@1.0.1: resolution: {integrity: sha512-B+ZM+RXvRqQaAmkMlO/oSe5nMUOaUnyfGYCEHoR8wrXsZR2mA0XVibsxV1bvTwxdRWah1PkQqso2EzhILGHtEQ==} engines: {node: '>=0.10.0'} @@ -4626,6 +4702,18 @@ packages: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} + engine.io-parser@5.2.3: + resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==} + engines: {node: '>=10.0.0'} + + engine.io@6.6.5: + resolution: {integrity: sha512-2RZdgEbXmp5+dVbRm0P7HQUImZpICccJy7rN7Tv+SFa55pH+lxnuw6/K1ZxxBfHoYpSkHLAO92oa8O4SwFXA2A==} + engines: {node: '>=10.2.0'} + + enhanced-resolve@5.12.0: + resolution: {integrity: sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==} + engines: {node: '>=10.13.0'} + enhanced-resolve@5.18.3: resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} engines: {node: '>=10.13.0'} @@ -4638,6 +4726,10 @@ packages: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + env-paths@2.2.1: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} @@ -4647,6 +4739,11 @@ packages: engines: {node: '>=4'} hasBin: true + envinfo@7.21.0: + resolution: {integrity: sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==} + engines: {node: '>=4'} + hasBin: true + environment@1.1.0: resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} engines: {node: '>=18'} @@ -4699,6 +4796,9 @@ packages: es-toolkit@1.39.10: resolution: {integrity: sha512-E0iGnTtbDhkeczB0T+mxmoVlT4YNweEKBLq7oaU4p11mecdsZpNWOglI4895Vh4usbQ+LsJiuLuI2L0Vdmfm2w==} + es-toolkit@1.44.0: + resolution: {integrity: sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg==} + esbuild@0.25.8: resolution: {integrity: sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==} engines: {node: '>=18'} @@ -5079,6 +5179,13 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} + filelist@1.0.4: + resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} + + filesize@10.1.6: + resolution: {integrity: sha512-sJslQKU2uM33qH5nqewAwVB2QgR6w1aMNsYUp3aN5rMRyXEwJGmZvaWzeJFNTOXWlHQyBFCWrdj3fV/fsTOX8w==} + engines: {node: '>= 10.4.0'} + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -5267,6 +5374,10 @@ packages: resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} engines: {node: '>=8.0.0'} + get-port@5.1.1: + resolution: {integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==} + engines: {node: '>=8'} + get-proto@1.0.1: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} @@ -5445,6 +5556,9 @@ packages: html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + htmlparser2@10.0.0: + resolution: {integrity: sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==} + http-errors@2.0.0: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} @@ -5839,6 +5953,10 @@ packages: resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} engines: {node: '>=8'} + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + iterate-object@1.3.5: resolution: {integrity: sha512-eL23u8oFooYTq6TtJKjp2RYjZnCkUYQvC0T/6fJfWykXJ3quvdDdzKZ3CEjy8b3JGOvLTjDYMEMIp5243R906A==} @@ -5853,6 +5971,11 @@ packages: resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==} engines: {node: 20 || >=22} + jake@10.9.4: + resolution: {integrity: sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==} + engines: {node: '>=10'} + hasBin: true + jest-changed-files@29.7.0: resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -6165,6 +6288,9 @@ packages: resolution: {integrity: sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==} engines: {node: '>= 0.4'} + json-stream-stringify@3.0.1: + resolution: {integrity: sha512-vuxs3G1ocFDiAQ/SX0okcZbtqXwgj1g71qE9+vrjJ2EkjKQlEFDAcUNRxRU8O+GekV4v5cM2qXP0Wyt/EMDBiQ==} + json-stringify-safe@5.0.1: resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} @@ -6245,6 +6371,10 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + lines-and-columns@2.0.4: + resolution: {integrity: sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + linkify-it@5.0.0: resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} @@ -6432,9 +6562,6 @@ packages: magic-string@0.30.19: resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} - magicast@0.3.5: - resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} - make-dir@2.1.0: resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} engines: {node: '>=6'} @@ -6788,6 +6915,9 @@ packages: node-releases@2.0.25: resolution: {integrity: sha512-4auku8B/vw5psvTiiN9j1dAOsXvMoGqJuKJcR+dTdqiXEK20mMTk1UEo3HS16LeGQsVG6+qKTPM9u/qQ2LqATA==} + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + node-stream-zip@1.15.0: resolution: {integrity: sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==} engines: {node: '>=0.12.0'} @@ -7032,6 +7162,9 @@ packages: engines: {node: '>=14', npm: '>5'} hasBin: true + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + path-exists@3.0.0: resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} engines: {node: '>=4'} @@ -7578,24 +7711,6 @@ packages: engines: {node: 20 || >=22} hasBin: true - rolldown@1.0.0-rc.1: - resolution: {integrity: sha512-M3AeZjYE6UclblEf531Hch0WfVC/NOL43Cc+WdF3J50kk5/fvouHhDumSGTh0oRjbZ8C4faaVr5r6Nx1xMqDGg==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - - rollup-plugin-visualizer@6.0.3: - resolution: {integrity: sha512-ZU41GwrkDcCpVoffviuM9Clwjy5fcUxlz0oMoTXTYsK+tcIFzbdacnrr2n8TXcHxbGKKXtOdjxM2HUS4HjkwIw==} - engines: {node: '>=18'} - hasBin: true - peerDependencies: - rolldown: 1.x || ^1.0.0-beta - rollup: 2.x || 3.x || 4.x - peerDependenciesMeta: - rolldown: - optional: true - rollup: - optional: true - rollup@4.46.2: resolution: {integrity: sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -7621,6 +7736,9 @@ packages: typescript: optional: true + rslog@1.3.2: + resolution: {integrity: sha512-1YyYXBvN0a2b1MSIDLwDTqqgjDzRKxUg/S/+KO6EAgbtZW1B3fdLHAMhEEtvk1patJYMqcRvlp3HQwnxj7AdGQ==} + run-async@2.4.1: resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} engines: {node: '>=0.12.0'} @@ -7710,6 +7828,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + send@0.19.0: resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} engines: {node: '>= 0.8.0'} @@ -7842,6 +7965,17 @@ packages: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + socket.io-adapter@2.5.6: + resolution: {integrity: sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ==} + + socket.io-parser@4.2.5: + resolution: {integrity: sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==} + engines: {node: '>=10.0.0'} + + socket.io@4.8.1: + resolution: {integrity: sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==} + engines: {node: '>=10.2.0'} + socks-proxy-agent@8.0.5: resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} engines: {node: '>= 14'} @@ -8068,6 +8202,9 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + swc-plugin-coverage-instrument@0.0.32: + resolution: {integrity: sha512-KsO1xNQdcVb331Rm98Op6uYf36/CYdF1kILQxOddmtCNlRLISpCUqHcnogu+QgELZvG48oie+w0fzS8uktjcEA==} + symbol-observable@1.0.1: resolution: {integrity: sha512-Kb3PrPYz4HanVF1LVGuAdW6LoVgIwjUYJGzFe7NDrBLCN4lsV/5J0MFurV+ygS4bRVwrCEt2c7MQ1R2a72oJDw==} engines: {node: '>=0.10.0'} @@ -8086,8 +8223,8 @@ packages: os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android] hasBin: true - tapable@2.2.2: - resolution: {integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==} + tapable@2.2.3: + resolution: {integrity: sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==} engines: {node: '>=6'} tar@7.5.1: @@ -8110,10 +8247,6 @@ packages: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} - test-exclude@7.0.1: - resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} - engines: {node: '>=18'} - text-hex@1.0.0: resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} @@ -8223,16 +8356,6 @@ packages: peerDependencies: typescript: ^5.8.3 - tsconfck@3.1.6: - resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} - engines: {node: ^18 || >=20} - hasBin: true - peerDependencies: - typescript: ^5.8.3 - peerDependenciesMeta: - typescript: - optional: true - tsconfig-paths@3.15.0: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} @@ -8388,6 +8511,12 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -8428,28 +8557,11 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} - vite-bundle-analyzer@1.2.1: - resolution: {integrity: sha512-dpJMQBnVjieMirpHILmgGsZAWU8fGyqK3ckj6O48wdy3fARlH7Mnz7BfYFUsd3ZdbuXjsYe7t5I/q2zsMOd5ig==} - hasBin: true - vite-node@3.2.4: resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true - vite-plugin-resolve-umd-format@1.0.0: - resolution: {integrity: sha512-BiRSXGtVQ49wEFGJWg+CrQ8TlMtpMtLlu7TPiTifQizD9017kX9IZA10hsLgnBPsvKWHHh9hhw/PpiQKt6mzZg==} - peerDependencies: - vite: '>=4.0.0' - - vite-tsconfig-paths@5.1.4: - resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==} - peerDependencies: - vite: '*' - peerDependenciesMeta: - vite: - optional: true - vite@7.1.11: resolution: {integrity: sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -8672,6 +8784,18 @@ packages: utf-8-validate: optional: true + ws@8.18.3: + resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + y18n@4.0.3: resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} @@ -8752,11 +8876,6 @@ packages: snapshots: - '@ampproject/remapping@2.3.0': - dependencies: - '@jridgewell/gen-mapping': 0.3.12 - '@jridgewell/trace-mapping': 0.3.29 - '@ast-grep/napi-darwin-arm64@0.37.0': optional: true @@ -8796,6 +8915,12 @@ snapshots: '@ast-grep/napi-win32-ia32-msvc': 0.37.0 '@ast-grep/napi-win32-x64-msvc': 0.37.0 + '@babel/code-frame@7.26.2': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + '@babel/code-frame@7.27.1': dependencies: '@babel/helper-validator-identifier': 7.27.1 @@ -9678,8 +9803,6 @@ snapshots: '@bcoe/v8-coverage@0.2.3': {} - '@bcoe/v8-coverage@1.0.2': {} - '@bundled-es-modules/cookie@2.0.1': dependencies: cookie: 0.7.2 @@ -10361,7 +10484,7 @@ snapshots: '@jridgewell/trace-mapping@0.3.29': dependencies: '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.4 + '@jridgewell/sourcemap-codec': 1.5.5 '@lingui/core@5.6.0': dependencies: @@ -10479,13 +10602,6 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true - '@napi-rs/wasm-runtime@1.1.1': - dependencies: - '@emnapi/core': 1.8.1 - '@emnapi/runtime': 1.8.1 - '@tybys/wasm-util': 0.10.1 - optional: true - '@noble/hashes@1.8.0': {} '@nodelib/fs.scandir@2.1.5': @@ -10509,9 +10625,6 @@ snapshots: '@open-draft/until@2.1.0': {} - '@oxc-project/types@0.110.0': - optional: true - '@paralleldrive/cuid2@2.2.2': dependencies: '@noble/hashes': 1.8.0 @@ -10922,50 +11035,6 @@ snapshots: dependencies: nanoid: 3.3.11 - '@rolldown/binding-android-arm64@1.0.0-rc.1': - optional: true - - '@rolldown/binding-darwin-arm64@1.0.0-rc.1': - optional: true - - '@rolldown/binding-darwin-x64@1.0.0-rc.1': - optional: true - - '@rolldown/binding-freebsd-x64@1.0.0-rc.1': - optional: true - - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.1': - optional: true - - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.1': - optional: true - - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.1': - optional: true - - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.1': - optional: true - - '@rolldown/binding-linux-x64-musl@1.0.0-rc.1': - optional: true - - '@rolldown/binding-openharmony-arm64@1.0.0-rc.1': - optional: true - - '@rolldown/binding-wasm32-wasi@1.0.0-rc.1': - dependencies: - '@napi-rs/wasm-runtime': 1.1.1 - optional: true - - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.1': - optional: true - - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.1': - optional: true - - '@rolldown/pluginutils@1.0.0-rc.1': - optional: true - '@rollup/rollup-android-arm-eabi@4.46.2': optional: true @@ -11014,25 +11083,140 @@ snapshots: '@rollup/rollup-linux-x64-gnu@4.46.2': optional: true - '@rollup/rollup-linux-x64-musl@4.46.2': - optional: true + '@rollup/rollup-linux-x64-musl@4.46.2': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.46.2': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.46.2': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.46.2': + optional: true + + '@rsbuild/core@1.7.2': + dependencies: + '@rspack/core': 1.7.4(@swc/helpers@0.5.18) + '@rspack/lite-tapable': 1.1.0 + '@swc/helpers': 0.5.18 + core-js: 3.47.0 + jiti: 2.6.1 + + '@rsbuild/core@1.7.3': + dependencies: + '@rspack/core': 1.7.6(@swc/helpers@0.5.18) + '@rspack/lite-tapable': 1.1.0 + '@swc/helpers': 0.5.18 + core-js: 3.47.0 + jiti: 2.6.1 + + '@rsbuild/plugin-check-syntax@1.6.1(@rsbuild/core@1.7.3)': + dependencies: + acorn: 8.15.0 + browserslist-to-es-version: 1.4.1 + htmlparser2: 10.0.0 + picocolors: 1.1.1 + source-map: 0.7.6 + optionalDependencies: + '@rsbuild/core': 1.7.3 + + '@rsdoctor/client@1.5.2': {} + + '@rsdoctor/core@1.5.2(@rsbuild/core@1.7.3)(@rspack/core@1.7.6(@swc/helpers@0.5.18))': + dependencies: + '@rsbuild/plugin-check-syntax': 1.6.1(@rsbuild/core@1.7.3) + '@rsdoctor/graph': 1.5.2(@rspack/core@1.7.6(@swc/helpers@0.5.18)) + '@rsdoctor/sdk': 1.5.2(@rspack/core@1.7.6(@swc/helpers@0.5.18)) + '@rsdoctor/types': 1.5.2(@rspack/core@1.7.6(@swc/helpers@0.5.18)) + '@rsdoctor/utils': 1.5.2(@rspack/core@1.7.6(@swc/helpers@0.5.18)) + browserslist-load-config: 1.0.1 + enhanced-resolve: 5.12.0 + es-toolkit: 1.44.0 + filesize: 10.1.6 + fs-extra: 11.3.2 + semver: 7.7.4 + source-map: 0.7.6 + transitivePeerDependencies: + - '@rsbuild/core' + - '@rspack/core' + - bufferutil + - supports-color + - utf-8-validate + - webpack - '@rollup/rollup-win32-arm64-msvc@4.46.2': - optional: true + '@rsdoctor/graph@1.5.2(@rspack/core@1.7.6(@swc/helpers@0.5.18))': + dependencies: + '@rsdoctor/types': 1.5.2(@rspack/core@1.7.6(@swc/helpers@0.5.18)) + '@rsdoctor/utils': 1.5.2(@rspack/core@1.7.6(@swc/helpers@0.5.18)) + es-toolkit: 1.44.0 + path-browserify: 1.0.1 + source-map: 0.7.6 + transitivePeerDependencies: + - '@rspack/core' + - webpack - '@rollup/rollup-win32-ia32-msvc@4.46.2': - optional: true + '@rsdoctor/rspack-plugin@1.5.2(@rsbuild/core@1.7.3)(@rspack/core@1.7.6(@swc/helpers@0.5.18))': + dependencies: + '@rsdoctor/core': 1.5.2(@rsbuild/core@1.7.3)(@rspack/core@1.7.6(@swc/helpers@0.5.18)) + '@rsdoctor/graph': 1.5.2(@rspack/core@1.7.6(@swc/helpers@0.5.18)) + '@rsdoctor/sdk': 1.5.2(@rspack/core@1.7.6(@swc/helpers@0.5.18)) + '@rsdoctor/types': 1.5.2(@rspack/core@1.7.6(@swc/helpers@0.5.18)) + '@rsdoctor/utils': 1.5.2(@rspack/core@1.7.6(@swc/helpers@0.5.18)) + optionalDependencies: + '@rspack/core': 1.7.6(@swc/helpers@0.5.18) + transitivePeerDependencies: + - '@rsbuild/core' + - bufferutil + - supports-color + - utf-8-validate + - webpack - '@rollup/rollup-win32-x64-msvc@4.46.2': - optional: true + '@rsdoctor/sdk@1.5.2(@rspack/core@1.7.6(@swc/helpers@0.5.18))': + dependencies: + '@rsdoctor/client': 1.5.2 + '@rsdoctor/graph': 1.5.2(@rspack/core@1.7.6(@swc/helpers@0.5.18)) + '@rsdoctor/types': 1.5.2(@rspack/core@1.7.6(@swc/helpers@0.5.18)) + '@rsdoctor/utils': 1.5.2(@rspack/core@1.7.6(@swc/helpers@0.5.18)) + safer-buffer: 2.1.2 + socket.io: 4.8.1 + tapable: 2.2.3 + transitivePeerDependencies: + - '@rspack/core' + - bufferutil + - supports-color + - utf-8-validate + - webpack - '@rsbuild/core@1.7.2': + '@rsdoctor/types@1.5.2(@rspack/core@1.7.6(@swc/helpers@0.5.18))': dependencies: - '@rspack/core': 1.7.4(@swc/helpers@0.5.18) - '@rspack/lite-tapable': 1.1.0 - '@swc/helpers': 0.5.18 - core-js: 3.47.0 - jiti: 2.6.1 + '@types/connect': 3.4.38 + '@types/estree': 1.0.5 + '@types/tapable': 2.2.7 + source-map: 0.7.6 + optionalDependencies: + '@rspack/core': 1.7.6(@swc/helpers@0.5.18) + + '@rsdoctor/utils@1.5.2(@rspack/core@1.7.6(@swc/helpers@0.5.18))': + dependencies: + '@babel/code-frame': 7.26.2 + '@rsdoctor/types': 1.5.2(@rspack/core@1.7.6(@swc/helpers@0.5.18)) + '@types/estree': 1.0.5 + acorn: 8.15.0 + acorn-import-attributes: 1.9.5(acorn@8.15.0) + acorn-walk: 8.3.4 + deep-eql: 4.1.4 + envinfo: 7.21.0 + fs-extra: 11.3.2 + get-port: 5.1.1 + json-stream-stringify: 3.0.1 + lines-and-columns: 2.0.4 + picocolors: 1.1.1 + rslog: 1.3.2 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - '@rspack/core' + - webpack '@rslib/core@0.19.4(@microsoft/api-extractor@7.56.0(@types/node@24.2.0))(typescript@5.9.2)': dependencies: @@ -11047,35 +11231,67 @@ snapshots: '@rspack/binding-darwin-arm64@1.7.4': optional: true + '@rspack/binding-darwin-arm64@1.7.6': + optional: true + '@rspack/binding-darwin-x64@1.7.4': optional: true + '@rspack/binding-darwin-x64@1.7.6': + optional: true + '@rspack/binding-linux-arm64-gnu@1.7.4': optional: true + '@rspack/binding-linux-arm64-gnu@1.7.6': + optional: true + '@rspack/binding-linux-arm64-musl@1.7.4': optional: true + '@rspack/binding-linux-arm64-musl@1.7.6': + optional: true + '@rspack/binding-linux-x64-gnu@1.7.4': optional: true + '@rspack/binding-linux-x64-gnu@1.7.6': + optional: true + '@rspack/binding-linux-x64-musl@1.7.4': optional: true + '@rspack/binding-linux-x64-musl@1.7.6': + optional: true + '@rspack/binding-wasm32-wasi@1.7.4': dependencies: '@napi-rs/wasm-runtime': 1.0.7 optional: true + '@rspack/binding-wasm32-wasi@1.7.6': + dependencies: + '@napi-rs/wasm-runtime': 1.0.7 + optional: true + '@rspack/binding-win32-arm64-msvc@1.7.4': optional: true + '@rspack/binding-win32-arm64-msvc@1.7.6': + optional: true + '@rspack/binding-win32-ia32-msvc@1.7.4': optional: true + '@rspack/binding-win32-ia32-msvc@1.7.6': + optional: true + '@rspack/binding-win32-x64-msvc@1.7.4': optional: true + '@rspack/binding-win32-x64-msvc@1.7.6': + optional: true + '@rspack/binding@1.7.4': optionalDependencies: '@rspack/binding-darwin-arm64': 1.7.4 @@ -11089,6 +11305,19 @@ snapshots: '@rspack/binding-win32-ia32-msvc': 1.7.4 '@rspack/binding-win32-x64-msvc': 1.7.4 + '@rspack/binding@1.7.6': + optionalDependencies: + '@rspack/binding-darwin-arm64': 1.7.6 + '@rspack/binding-darwin-x64': 1.7.6 + '@rspack/binding-linux-arm64-gnu': 1.7.6 + '@rspack/binding-linux-arm64-musl': 1.7.6 + '@rspack/binding-linux-x64-gnu': 1.7.6 + '@rspack/binding-linux-x64-musl': 1.7.6 + '@rspack/binding-wasm32-wasi': 1.7.6 + '@rspack/binding-win32-arm64-msvc': 1.7.6 + '@rspack/binding-win32-ia32-msvc': 1.7.6 + '@rspack/binding-win32-x64-msvc': 1.7.6 + '@rspack/core@1.7.4(@swc/helpers@0.5.18)': dependencies: '@module-federation/runtime-tools': 0.22.0 @@ -11097,8 +11326,35 @@ snapshots: optionalDependencies: '@swc/helpers': 0.5.18 + '@rspack/core@1.7.6(@swc/helpers@0.5.18)': + dependencies: + '@module-federation/runtime-tools': 0.22.0 + '@rspack/binding': 1.7.6 + '@rspack/lite-tapable': 1.1.0 + optionalDependencies: + '@swc/helpers': 0.5.18 + '@rspack/lite-tapable@1.1.0': {} + '@rstest/core@0.8.5(happy-dom@20.0.2)': + dependencies: + '@rsbuild/core': 1.7.3 + '@types/chai': 5.2.3 + tinypool: 1.1.1 + optionalDependencies: + happy-dom: 20.0.2 + + '@rstest/coverage-istanbul@0.2.1(@rstest/core@0.8.5(happy-dom@20.0.2))': + dependencies: + '@rstest/core': 0.8.5(happy-dom@20.0.2) + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.2.0 + swc-plugin-coverage-instrument: 0.0.32 + transitivePeerDependencies: + - supports-color + '@rtsao/scc@1.1.0': {} '@rushstack/node-core-library@5.19.1(@types/node@24.2.0)': @@ -11197,6 +11453,8 @@ snapshots: color: 5.0.2 text-hex: 1.0.0 + '@socket.io/component-emitter@3.1.2': {} + '@standard-schema/spec@1.0.0': {} '@swc/helpers@0.5.18': @@ -11254,6 +11512,11 @@ snapshots: dependencies: '@types/deep-eql': 4.0.2 + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + '@types/connect@3.4.38': dependencies: '@types/node': 24.2.0 @@ -11271,8 +11534,14 @@ snapshots: '@types/cookiejar@2.1.5': {} + '@types/cors@2.8.19': + dependencies: + '@types/node': 24.2.0 + '@types/deep-eql@4.0.2': {} + '@types/estree@1.0.5': {} + '@types/estree@1.0.8': {} '@types/express-serve-static-core@5.0.7': @@ -11384,6 +11653,10 @@ snapshots: '@types/methods': 1.1.4 '@types/superagent': 8.1.9 + '@types/tapable@2.2.7': + dependencies: + tapable: 2.2.3 + '@types/tough-cookie@4.0.5': {} '@types/triple-beam@1.3.5': {} @@ -11560,25 +11833,6 @@ snapshots: '@vercel/stega@0.1.2': {} - '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/node@24.2.0)(happy-dom@20.0.2)(jiti@2.6.1)(msw@2.10.5(@types/node@24.2.0)(typescript@5.9.2))(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.1))': - dependencies: - '@ampproject/remapping': 2.3.0 - '@bcoe/v8-coverage': 1.0.2 - ast-v8-to-istanbul: 0.3.4 - debug: 4.4.3 - istanbul-lib-coverage: 3.2.2 - istanbul-lib-report: 3.0.1 - istanbul-lib-source-maps: 5.0.6 - istanbul-reports: 3.1.7 - magic-string: 0.30.19 - magicast: 0.3.5 - std-env: 3.9.0 - test-exclude: 7.0.1 - tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/node@24.2.0)(happy-dom@20.0.2)(jiti@2.6.1)(msw@2.10.5(@types/node@24.2.0)(typescript@5.9.2))(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.1) - transitivePeerDependencies: - - supports-color - '@vitest/expect@3.2.4': dependencies: '@types/chai': 5.2.2 @@ -11652,10 +11906,18 @@ snapshots: mime-types: 3.0.1 negotiator: 1.0.0 + acorn-import-attributes@1.9.5(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: acorn: 8.15.0 + acorn-walk@8.3.4: + dependencies: + acorn: 8.15.0 + acorn@7.4.1: {} acorn@8.15.0: {} @@ -11868,12 +12130,6 @@ snapshots: ast-types@0.7.8: {} - ast-v8-to-istanbul@0.3.4: - dependencies: - '@jridgewell/trace-mapping': 0.3.29 - estree-walker: 3.0.3 - js-tokens: 9.0.1 - astral-regex@1.0.0: {} astral-regex@2.0.0: {} @@ -12019,8 +12275,12 @@ snapshots: base64-js@1.5.1: {} + base64id@2.0.0: {} + baseline-browser-mapping@2.8.18: {} + baseline-browser-mapping@2.9.19: {} + basic-ftp@5.0.5: {} bfj@8.0.0: @@ -12100,6 +12360,12 @@ snapshots: browser-process-hrtime@1.0.0: {} + browserslist-load-config@1.0.1: {} + + browserslist-to-es-version@1.4.1: + dependencies: + browserslist: 4.28.1 + browserslist@4.26.3: dependencies: baseline-browser-mapping: 2.8.18 @@ -12108,6 +12374,14 @@ snapshots: node-releases: 2.0.25 update-browserslist-db: 1.1.3(browserslist@4.26.3) + browserslist@4.28.1: + dependencies: + baseline-browser-mapping: 2.9.19 + caniuse-lite: 1.0.30001769 + electron-to-chromium: 1.5.286 + node-releases: 2.0.27 + update-browserslist-db: 1.2.3(browserslist@4.28.1) + bser@2.1.1: dependencies: node-int64: 0.4.0 @@ -12206,6 +12480,8 @@ snapshots: caniuse-lite@1.0.30001751: {} + caniuse-lite@1.0.30001769: {} + cardinal@2.1.1: dependencies: ansicolors: 0.3.2 @@ -12767,6 +13043,11 @@ snapshots: core-util-is@1.0.3: {} + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + cosmiconfig-typescript-loader@5.0.0(@types/node@24.2.0)(cosmiconfig@9.0.0(typescript@5.9.2))(typescript@5.9.2): dependencies: '@types/node': 24.2.0 @@ -12918,6 +13199,10 @@ snapshots: dedent@1.7.0: {} + deep-eql@4.1.4: + dependencies: + type-detect: 4.0.8 + deep-eql@5.0.2: {} deep-is@0.1.4: {} @@ -13038,6 +13323,24 @@ snapshots: doctypes@1.1.0: {} + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + dotenv@16.6.1: {} dotenv@17.2.2: {} @@ -13067,8 +13370,14 @@ snapshots: ee-first@1.1.1: {} + ejs@3.1.10: + dependencies: + jake: 10.9.4 + electron-to-chromium@1.5.237: {} + electron-to-chromium@1.5.286: {} + elegant-spinner@1.0.1: {} emittery@0.13.1: {} @@ -13096,10 +13405,33 @@ snapshots: encodeurl@2.0.0: {} + engine.io-parser@5.2.3: {} + + engine.io@6.6.5: + dependencies: + '@types/cors': 2.8.19 + '@types/node': 24.2.0 + accepts: 1.3.8 + base64id: 2.0.0 + cookie: 0.7.2 + cors: 2.8.6 + debug: 4.4.3 + engine.io-parser: 5.2.3 + ws: 8.18.3 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + enhanced-resolve@5.12.0: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.3 + enhanced-resolve@5.18.3: dependencies: graceful-fs: 4.2.11 - tapable: 2.2.2 + tapable: 2.2.3 enquirer@2.3.6: dependencies: @@ -13107,10 +13439,14 @@ snapshots: entities@4.5.0: {} + entities@6.0.1: {} + env-paths@2.2.1: {} envinfo@7.19.0: {} + envinfo@7.21.0: {} + environment@1.1.0: {} error-ex@1.3.2: @@ -13231,6 +13567,8 @@ snapshots: es-toolkit@1.39.10: {} + es-toolkit@1.44.0: {} + esbuild@0.25.8: optionalDependencies: '@esbuild/aix-ppc64': 0.25.8 @@ -13290,12 +13628,12 @@ snapshots: eslint-compat-utils@0.5.1(eslint@8.57.1): dependencies: eslint: 8.57.1 - semver: 7.7.2 + semver: 7.7.4 eslint-compat-utils@0.5.1(eslint@9.32.0(jiti@2.5.1)): dependencies: eslint: 9.32.0(jiti@2.5.1) - semver: 7.7.2 + semver: 7.7.4 eslint-config-love@121.0.0(@typescript-eslint/parser@8.39.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2): dependencies: @@ -13844,6 +14182,12 @@ snapshots: dependencies: flat-cache: 4.0.1 + filelist@1.0.4: + dependencies: + minimatch: 5.1.6 + + filesize@10.1.6: {} + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -14057,6 +14401,8 @@ snapshots: get-package-type@0.1.0: {} + get-port@5.1.1: {} + get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 @@ -14242,6 +14588,13 @@ snapshots: html-escaper@2.0.2: {} + htmlparser2@10.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.2 + entities: 6.0.1 + http-errors@2.0.0: dependencies: depd: 2.0.0 @@ -14637,7 +14990,7 @@ snapshots: '@babel/parser': 7.28.4 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 - semver: 7.7.2 + semver: 7.7.4 transitivePeerDependencies: - supports-color @@ -14668,6 +15021,11 @@ snapshots: html-escaper: 2.0.2 istanbul-lib-report: 3.0.1 + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + iterate-object@1.3.5: {} iterator.prototype@1.1.5: @@ -14689,6 +15047,12 @@ snapshots: dependencies: '@isaacs/cliui': 8.0.2 + jake@10.9.4: + dependencies: + async: 3.2.6 + filelist: 1.0.4 + picocolors: 1.1.1 + jest-changed-files@29.7.0: dependencies: execa: 5.1.1 @@ -15088,7 +15452,7 @@ snapshots: jest-util: 29.7.0 natural-compare: 1.4.0 pretty-format: 29.7.0 - semver: 7.7.2 + semver: 7.7.4 transitivePeerDependencies: - supports-color @@ -15309,6 +15673,8 @@ snapshots: jsonify: 0.0.1 object-keys: 1.1.1 + json-stream-stringify@3.0.1: {} + json-stringify-safe@5.0.1: {} json5@1.0.2: @@ -15346,7 +15712,7 @@ snapshots: lodash.isstring: 4.0.1 lodash.once: 4.1.1 ms: 2.1.3 - semver: 7.7.2 + semver: 7.7.4 jstransformer@1.0.0: dependencies: @@ -15406,6 +15772,8 @@ snapshots: lines-and-columns@1.2.4: {} + lines-and-columns@2.0.4: {} + linkify-it@5.0.0: dependencies: uc.micro: 2.1.0 @@ -15634,12 +16002,6 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - magicast@0.3.5: - dependencies: - '@babel/parser': 7.28.4 - '@babel/types': 7.28.6 - source-map-js: 1.2.1 - make-dir@2.1.0: dependencies: pify: 4.0.1 @@ -15647,7 +16009,7 @@ snapshots: make-dir@4.0.0: dependencies: - semver: 7.7.2 + semver: 7.7.4 makeerror@1.0.12: dependencies: @@ -16049,6 +16411,8 @@ snapshots: node-releases@2.0.25: {} + node-releases@2.0.27: {} + node-stream-zip@1.15.0: {} normalize-path@3.0.0: {} @@ -16325,6 +16689,8 @@ snapshots: tmp: 0.2.5 yaml: 2.8.1 + path-browserify@1.0.1: {} + path-exists@3.0.0: {} path-exists@4.0.0: {} @@ -16984,36 +17350,6 @@ snapshots: glob: 11.0.3 package-json-from-dist: 1.0.1 - rolldown@1.0.0-rc.1: - dependencies: - '@oxc-project/types': 0.110.0 - '@rolldown/pluginutils': 1.0.0-rc.1 - optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0-rc.1 - '@rolldown/binding-darwin-arm64': 1.0.0-rc.1 - '@rolldown/binding-darwin-x64': 1.0.0-rc.1 - '@rolldown/binding-freebsd-x64': 1.0.0-rc.1 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.1 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.1 - '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.1 - '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.1 - '@rolldown/binding-linux-x64-musl': 1.0.0-rc.1 - '@rolldown/binding-openharmony-arm64': 1.0.0-rc.1 - '@rolldown/binding-wasm32-wasi': 1.0.0-rc.1 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.1 - '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.1 - optional: true - - rollup-plugin-visualizer@6.0.3(rolldown@1.0.0-rc.1)(rollup@4.46.2): - dependencies: - open: 8.4.2 - picomatch: 4.0.3 - source-map: 0.7.6 - yargs: 17.7.2 - optionalDependencies: - rolldown: 1.0.0-rc.1 - rollup: 4.46.2 - rollup@4.46.2: dependencies: '@types/estree': 1.0.8 @@ -17058,6 +17394,8 @@ snapshots: '@microsoft/api-extractor': 7.56.0(@types/node@24.2.0) typescript: 5.9.2 + rslog@1.3.2: {} + run-async@2.4.1: {} run-parallel@1.2.0: @@ -17141,6 +17479,8 @@ snapshots: semver@7.7.2: {} + semver@7.7.4: {} + send@0.19.0: dependencies: debug: 2.6.9 @@ -17315,6 +17655,36 @@ snapshots: smart-buffer@4.2.0: {} + socket.io-adapter@2.5.6: + dependencies: + debug: 4.4.3 + ws: 8.18.3 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + socket.io-parser@4.2.5: + dependencies: + '@socket.io/component-emitter': 3.1.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + socket.io@4.8.1: + dependencies: + accepts: 1.3.8 + base64id: 2.0.0 + cors: 2.8.6 + debug: 4.3.7 + engine.io: 6.6.5 + socket.io-adapter: 2.5.6 + socket.io-parser: 4.2.5 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + socks-proxy-agent@8.0.5: dependencies: agent-base: 7.1.4 @@ -17550,6 +17920,8 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + swc-plugin-coverage-instrument@0.0.32: {} + symbol-observable@1.0.1: {} symbol-observable@1.2.0: {} @@ -17561,7 +17933,7 @@ snapshots: systeminformation@5.27.11: optional: true - tapable@2.2.2: {} + tapable@2.2.3: {} tar@7.5.1: dependencies: @@ -17592,12 +17964,6 @@ snapshots: glob: 7.2.3 minimatch: 3.1.2 - test-exclude@7.0.1: - dependencies: - '@istanbuljs/schema': 0.1.3 - glob: 10.4.5 - minimatch: 9.0.5 - text-hex@1.0.0: {} text-table@0.2.0: {} @@ -17688,10 +18054,6 @@ snapshots: picomatch: 4.0.3 typescript: 5.9.2 - tsconfck@3.1.6(typescript@5.9.2): - optionalDependencies: - typescript: 5.9.2 - tsconfig-paths@3.15.0: dependencies: '@types/json5': 0.0.29 @@ -17871,6 +18233,12 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + update-browserslist-db@1.2.3(browserslist@4.28.1): + dependencies: + browserslist: 4.28.1 + escalade: 3.2.0 + picocolors: 1.1.1 + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -17906,8 +18274,6 @@ snapshots: vary@1.1.2: {} - vite-bundle-analyzer@1.2.1: {} - vite-node@3.2.4(@types/node@24.2.0)(jiti@2.5.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.1): dependencies: cac: 6.7.14 @@ -17950,21 +18316,6 @@ snapshots: - tsx - yaml - vite-plugin-resolve-umd-format@1.0.0(vite@7.1.11(@types/node@24.2.0)(jiti@2.6.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.1)): - dependencies: - vite: 7.1.11(@types/node@24.2.0)(jiti@2.6.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.1) - - vite-tsconfig-paths@5.1.4(typescript@5.9.2)(vite@7.1.11(@types/node@24.2.0)(jiti@2.6.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.1)): - dependencies: - debug: 4.4.3 - globrex: 0.1.2 - tsconfck: 3.1.6(typescript@5.9.2) - optionalDependencies: - vite: 7.1.11(@types/node@24.2.0)(jiti@2.6.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.1) - transitivePeerDependencies: - - supports-color - - typescript - vite@7.1.11(@types/node@24.2.0)(jiti@2.5.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.1): dependencies: esbuild: 0.25.8 @@ -18265,6 +18616,8 @@ snapshots: ws@7.5.10: {} + ws@8.18.3: {} + y18n@4.0.3: {} y18n@5.0.8: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index b97da5a2..3dc9c0a9 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -10,6 +10,10 @@ catalog: '@microsoft/api-extractor': ^7.56.0 '@playwright/test': ^1.57.0 '@preact/signals-core': ^1.12.1 + '@rsbuild/core': ^1.7.2 + '@rstest/core': ^0.8.5 + '@rstest/coverage-istanbul': ^0.2.1 + '@rsdoctor/rspack-plugin': ^1.5.2 '@rslib/core': ^0.19.4 '@types/express': ^5.0.3 '@types/node': ^24.0.13 @@ -20,6 +24,7 @@ catalog: diary: ^0.4.5 contentful: ^11.10.2 dotenv: ^17.2.2 + ejs: ^3.1.10 es-toolkit: ^1.39.10 express: ^5.1.0 express-rate-limit: ^8.1.0 @@ -29,16 +34,11 @@ catalog: pm2: ^6.0.13 qs: ^6.14.0 rimraf: ^6.0.1 - rollup-plugin-visualizer: ^6.0.3 supertest: ^7.1.3 tslib: ^2.8.1 tsx: ^4.20.3 typescript: ^5.8.3 - vite: ^7.1.11 vitest: ^3.2.4 - vite-bundle-analyzer: ^1.2.1 - vite-plugin-resolve-umd-format: ^1.0.0 - vite-tsconfig-paths: ^5.1.4 zod: ^4.1.5 engineStrict: true diff --git a/universal/api-client/package.json b/universal/api-client/package.json index 7cbbea5e..d2146fd0 100644 --- a/universal/api-client/package.json +++ b/universal/api-client/package.json @@ -23,9 +23,11 @@ "dist/**/*" ], "scripts": { - "build": "pnpm clean; rslib build && build-tools emit-dual-dts ./dist", + "build": "pnpm clean; pnpm build:dist", + "build:ci": "pnpm build:dist", + "build:dist": "rslib build && build-tools emit-dual-dts ./dist", "clean": "rimraf ./.rslib ./dist ./coverage .tsbuildinfo", - "test:unit": "vitest run --coverage", + "test:unit": "rstest run --coverage", "typecheck": "tsc --noEmit" }, "dependencies": { @@ -40,13 +42,13 @@ "@microsoft/api-extractor": "catalog:", "@rslib/core": "catalog:", "@types/node": "catalog:", - "@vitest/coverage-v8": "catalog:", + "@rstest/core": "catalog:", + "@rstest/coverage-istanbul": "catalog:", "logger": "workspace:*", "mocks": "workspace:*", "msw": "catalog:", "rimraf": "catalog:", "tslib": "catalog:", - "typescript": "catalog:", - "vitest": "catalog:" + "typescript": "catalog:" } } diff --git a/universal/api-client/rstest.config.ts b/universal/api-client/rstest.config.ts new file mode 100644 index 00000000..1a0dd9a9 --- /dev/null +++ b/universal/api-client/rstest.config.ts @@ -0,0 +1,22 @@ +import { defineConfig } from '@rstest/core' +import { resolve } from 'node:path' + +const coverageReporters = process.env.CI === 'true' ? ['text-summary', 'lcov'] : ['text', 'html'] + +export default defineConfig({ + resolve: { + alias: { + '@contentful/optimization-api-schemas': resolve(__dirname, '../api-schemas/src/'), + logger: resolve(__dirname, '../../lib/logger/src/'), + }, + }, + include: ['**/*.test.?(c|m)[jt]s?(x)'], + globals: true, + testEnvironment: 'node', + setupFiles: ['src/test/setup.ts'], + coverage: { + exclude: ['**/test/*'], + include: ['src/**/*'], + reporters: coverageReporters, + }, +}) diff --git a/universal/api-client/src/ApiClientBase.test.ts b/universal/api-client/src/ApiClientBase.test.ts index 20574e74..507f257c 100644 --- a/universal/api-client/src/ApiClientBase.test.ts +++ b/universal/api-client/src/ApiClientBase.test.ts @@ -14,7 +14,7 @@ class TestApiClient extends ApiClientBase { } } -const mockFetchMethod = vi.fn() +const mockFetchMethod = rs.fn() describe('ApiClientBase', () => { const name = 'MyAPI' @@ -22,12 +22,12 @@ describe('ApiClientBase', () => { let config: ApiConfig beforeEach(() => { - vi.spyOn(Fetch, 'create').mockReturnValue(mockFetchMethod) + rs.spyOn(Fetch, 'create').mockReturnValue(mockFetchMethod) config = { clientId: 'testId', fetchOptions } }) afterEach(() => { - vi.restoreAllMocks() + rs.restoreAllMocks() }) it('creates fetch method with correct merged options', () => { diff --git a/universal/api-client/src/experience/ExperienceApiClient.test.ts b/universal/api-client/src/experience/ExperienceApiClient.test.ts index 6c9ad191..1bebd444 100644 --- a/universal/api-client/src/experience/ExperienceApiClient.test.ts +++ b/universal/api-client/src/experience/ExperienceApiClient.test.ts @@ -3,9 +3,9 @@ import { ExperienceEventArray, ExperienceResponse, } from '@contentful/optimization-api-schemas' +import { afterEach, describe, expect, it, rs } from '@rstest/core' import { mockLogger } from 'mocks' import { http, HttpResponse } from 'msw' -import { afterEach, describe, expect, it, vi } from 'vitest' import { server } from '../test/setup' import ExperienceApiClient, { EXPERIENCE_BASE_URL, @@ -50,23 +50,23 @@ function makeClient(overrides: Partial = {}): Experie describe('ExperienceApiClient', () => { beforeEach(() => { server.resetHandlers() - vi.clearAllMocks() + rs.clearAllMocks() - vi.spyOn(ExperienceResponse, 'parse') + rs.spyOn(ExperienceResponse, 'parse') // @ts-expect-error -- testing .mockImplementation((json) => json) - vi.spyOn(BatchExperienceResponse, 'parse') + rs.spyOn(BatchExperienceResponse, 'parse') // @ts-expect-error -- testing .mockImplementation((json) => json) - vi.spyOn(ExperienceEventArray, 'parse') + rs.spyOn(ExperienceEventArray, 'parse') // @ts-expect-error -- testing .mockImplementation((json) => json) }) afterEach(() => { - vi.restoreAllMocks() + rs.restoreAllMocks() }) describe('getProfile', () => { diff --git a/universal/api-client/src/fetch/createProtectedFetchMethod.test.ts b/universal/api-client/src/fetch/createProtectedFetchMethod.test.ts index d2a3b0a6..2b7b58d2 100644 --- a/universal/api-client/src/fetch/createProtectedFetchMethod.test.ts +++ b/universal/api-client/src/fetch/createProtectedFetchMethod.test.ts @@ -1,6 +1,4 @@ import { mockLogger } from 'mocks' -import type { MockInstance } from 'vitest' -import type { FetchMethod } from './Fetch' import { createProtectedFetchMethod, type ProtectedFetchMethodOptions, @@ -8,34 +6,28 @@ import { import * as createRetryFetchMethodModule from './createRetryFetchMethod' import * as createTimeoutFetchMethodModule from './createTimeoutFetchMethod' -vi.mock('./createRetryFetchMethod') -vi.mock('./createTimeoutFetchMethod') +rs.mock('./createRetryFetchMethod', { mock: true }) +rs.mock('./createTimeoutFetchMethod', { mock: true }) describe('createProtectedFetchMethod', () => { - const timeoutFetchMethod = vi.fn() - const retryFetchMethod = vi.fn() + const timeoutFetchMethod = rs.fn() + const retryFetchMethod = rs.fn() const options: ProtectedFetchMethodOptions = { intervalTimeout: 100, requestTimeout: 2000, retries: 2, } - let createTimeoutFetchMethodSpy: MockInstance< - (options?: createTimeoutFetchMethodModule.TimeoutFetchMethodOptions) => FetchMethod - > - let createRetryFetchMethodSpy: MockInstance< - ( - options: createRetryFetchMethodModule.RetryFetchMethodOptions & { fetchMethod: FetchMethod }, - ) => FetchMethod - > + let createTimeoutFetchMethodSpy: ReturnType + let createRetryFetchMethodSpy: ReturnType beforeEach(() => { - vi.clearAllMocks() - createTimeoutFetchMethodSpy = vi.spyOn( + rs.clearAllMocks() + createTimeoutFetchMethodSpy = rs.spyOn( createTimeoutFetchMethodModule, 'createTimeoutFetchMethod', ) - createRetryFetchMethodSpy = vi.spyOn(createRetryFetchMethodModule, 'createRetryFetchMethod') + createRetryFetchMethodSpy = rs.spyOn(createRetryFetchMethodModule, 'createRetryFetchMethod') createTimeoutFetchMethodSpy.mockReturnValue(timeoutFetchMethod) createRetryFetchMethodSpy.mockReturnValue(retryFetchMethod) diff --git a/universal/api-client/src/fetch/createRetryFetchMethod.test.ts b/universal/api-client/src/fetch/createRetryFetchMethod.test.ts index 8beb7f6e..e5d5bc4f 100644 --- a/universal/api-client/src/fetch/createRetryFetchMethod.test.ts +++ b/universal/api-client/src/fetch/createRetryFetchMethod.test.ts @@ -4,18 +4,18 @@ import { createRetryFetchMethod } from './createRetryFetchMethod' const TEST_URL = 'https://example.com/endpoint' describe('createRetryFetchMethod', () => { - let fetchMock: ReturnType - let onFailedAttempt: ReturnType + let fetchMock: ReturnType + let onFailedAttempt: ReturnType beforeEach(() => { - fetchMock = vi.fn() - onFailedAttempt = vi.fn() - vi.useFakeTimers() - vi.clearAllMocks() + fetchMock = rs.fn() + onFailedAttempt = rs.fn() + rs.useFakeTimers() + rs.clearAllMocks() }) afterEach(() => { - vi.useRealTimers() + rs.useRealTimers() }) it('calls fetchMethod and returns the response when successful', async () => { @@ -53,7 +53,7 @@ describe('createRetryFetchMethod', () => { const promise = fetchWithRetry(TEST_URL, {}) // Advance timers for the retry interval - await vi.advanceTimersByTimeAsync(60) + await rs.advanceTimersByTimeAsync(60) const result = await promise @@ -132,7 +132,7 @@ describe('createRetryFetchMethod', () => { `API request to "${TEST_URL}" failed with status: "[503] Service Unavailable".`, ) - await vi.advanceTimersByTimeAsync(200) + await rs.advanceTimersByTimeAsync(200) await rejection @@ -155,7 +155,7 @@ describe('createRetryFetchMethod', () => { }) const promise = fetchWithRetry(TEST_URL, {}) - await vi.advanceTimersByTimeAsync(60) + await rs.advanceTimersByTimeAsync(60) await promise expect(onFailedAttempt).toHaveBeenCalledWith(expect.objectContaining({ apiName: 'CustomAPI' })) @@ -224,12 +224,12 @@ describe('createRetryFetchMethod', () => { expect(resolved).toBe(false) // Advance time less than intervalTimeout; second attempt shouldn't happen yet - await vi.advanceTimersByTimeAsync(1000) + await rs.advanceTimersByTimeAsync(1000) expect(fetchMock).toHaveBeenCalledTimes(1) expect(resolved).toBe(false) // Advance time to cover the retry interval; second attempt and resolution happen - await vi.advanceTimersByTimeAsync(234) + await rs.advanceTimersByTimeAsync(234) expect(fetchMock).toHaveBeenCalledTimes(2) await promise expect(resolved).toBe(true) diff --git a/universal/api-client/src/fetch/createTimeoutFetchMethod.test.ts b/universal/api-client/src/fetch/createTimeoutFetchMethod.test.ts index 764a4a6b..5d3a3d50 100644 --- a/universal/api-client/src/fetch/createTimeoutFetchMethod.test.ts +++ b/universal/api-client/src/fetch/createTimeoutFetchMethod.test.ts @@ -2,19 +2,19 @@ import { mockLogger } from 'mocks' import { createTimeoutFetchMethod } from './createTimeoutFetchMethod' describe('createTimeoutFetchMethod', () => { - let fetchMock: ReturnType - let onRequestTimeout: ReturnType + let fetchMock: ReturnType + let onRequestTimeout: ReturnType beforeEach(() => { - fetchMock = vi.fn() - onRequestTimeout = vi.fn() - vi.useFakeTimers() - vi.clearAllTimers() - vi.clearAllMocks() + fetchMock = rs.fn() + onRequestTimeout = rs.fn() + rs.useFakeTimers() + rs.clearAllTimers() + rs.clearAllMocks() }) afterEach(() => { - vi.useRealTimers() + rs.useRealTimers() }) it('calls fetchMethod and resolves response before timeout', async () => { @@ -53,7 +53,7 @@ describe('createTimeoutFetchMethod', () => { fetchWithTimeout('http://timeout.com', {}) - vi.advanceTimersByTime(1000) + rs.advanceTimersByTime(1000) expect(onRequestTimeout).toHaveBeenCalledWith({ apiName: 'CustomAPI' }) expect(mockLogger.error).not.toHaveBeenCalled() @@ -70,7 +70,7 @@ describe('createTimeoutFetchMethod', () => { fetchWithTimeout('http://fail.com', {}) - vi.advanceTimersByTime(500) + rs.advanceTimersByTime(500) expect(mockLogger.error).toHaveBeenCalled() expect(onRequestTimeout).not.toHaveBeenCalled() diff --git a/universal/api-client/src/insights/InsightsApiClient.test.ts b/universal/api-client/src/insights/InsightsApiClient.test.ts index a2bbfcf0..dc87165d 100644 --- a/universal/api-client/src/insights/InsightsApiClient.test.ts +++ b/universal/api-client/src/insights/InsightsApiClient.test.ts @@ -1,7 +1,7 @@ import { BatchInsightsEventArray } from '@contentful/optimization-api-schemas' +import { afterEach, beforeEach, describe, expect, it, rs } from '@rstest/core' import { mockLogger } from 'mocks' import { http, HttpResponse } from 'msw' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import ApiClientBase from '../ApiClientBase' import { server } from '../test/setup' import InsightsApiClient, { @@ -91,7 +91,7 @@ function generateBatchEventArray(id: string): BatchInsightsEventArray { describe('InsightsApiClient.sendBatchEvents', () => { beforeEach(() => { server.resetHandlers() - vi.clearAllMocks() + rs.clearAllMocks() }) afterEach(() => { @@ -101,14 +101,14 @@ describe('InsightsApiClient.sendBatchEvents', () => { value: undefined, }) - vi.restoreAllMocks() + rs.restoreAllMocks() }) it('POSTs batches via fetch by default', async () => { const batches = generateBatchEventArray('e1') // Spy on the schema parser and let it pass-through (or stub if needed) - const parseSpy = vi + const parseSpy = rs .spyOn(BatchInsightsEventArray, 'parse') // @ts-expect-error -- testing .mockImplementation((input) => input) @@ -153,11 +153,11 @@ describe('InsightsApiClient.sendBatchEvents', () => { const batches = generateBatchEventArray('e2') // @ts-expect-error -- testing - vi.spyOn(BatchInsightsEventArray, 'parse').mockImplementation((input) => input) + rs.spyOn(BatchInsightsEventArray, 'parse').mockImplementation((input) => input) - const beaconHandler = vi.fn(() => true) + const beaconHandler = rs.fn(() => true) - const fetchSpy = vi.spyOn(globalThis, 'fetch') + const fetchSpy = rs.spyOn(globalThis, 'fetch') const client = makeClient() @@ -171,7 +171,7 @@ describe('InsightsApiClient.sendBatchEvents', () => { it('POSTs batches via fetch when beaconHandler fails', async () => { const batches = generateBatchEventArray('e3') - const beaconHandler = vi.fn(() => false) + const beaconHandler = rs.fn(() => false) const handler = http.post( `${INSIGHTS_BASE_URL}v1/organizations/:orgId/environments/:env/events`, @@ -205,7 +205,7 @@ describe('InsightsApiClient.sendBatchEvents', () => { const batches = generateBatchEventArray('e4') // @ts-expect-error -- testing - vi.spyOn(BatchInsightsEventArray, 'parse').mockImplementation((input) => input) + rs.spyOn(BatchInsightsEventArray, 'parse').mockImplementation((input) => input) server.use( http.post(`${INSIGHTS_BASE_URL}v1/organizations/:orgId/environments/:env/events`, () => @@ -214,7 +214,7 @@ describe('InsightsApiClient.sendBatchEvents', () => { ) // Spy on the inherited method from ApiClientBase prototype - const logErrorSpy = vi.spyOn( + const logErrorSpy = rs.spyOn( // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- testing ApiClientBase.prototype as unknown as { logRequestError: (e: unknown, m: { requestName: string }) => void diff --git a/universal/api-client/src/test/setup.ts b/universal/api-client/src/test/setup.ts index 14029ae2..f672daac 100644 --- a/universal/api-client/src/test/setup.ts +++ b/universal/api-client/src/test/setup.ts @@ -1,8 +1,8 @@ +import { rs } from '@rstest/core' import { experienceApiHandlers, loggerMock, resetMockLogger } from 'mocks' import { setupServer } from 'msw/node' -import { vi } from 'vitest' -vi.mock('logger', () => loggerMock) +rs.mock('logger', () => loggerMock) export const server = setupServer(...experienceApiHandlers.getHandlers()) diff --git a/universal/api-client/tsconfig.json b/universal/api-client/tsconfig.json index 0b1acd57..a655ac1d 100644 --- a/universal/api-client/tsconfig.json +++ b/universal/api-client/tsconfig.json @@ -1,4 +1,7 @@ { "extends": "../../tsconfig.base.json", + "compilerOptions": { + "types": ["@rstest/core/globals"] + }, "include": ["./src/**/*", "./src/**/*.json"] } diff --git a/universal/api-client/vitest.config.ts b/universal/api-client/vitest.config.ts deleted file mode 100644 index e49b0219..00000000 --- a/universal/api-client/vitest.config.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { resolve } from 'node:path' -import { defineConfig } from 'vitest/config' - -export default defineConfig({ - resolve: { - alias: { - '@contentful/optimization-api-schemas': resolve(__dirname, '../api-schemas/src/'), - logger: resolve(__dirname, '../../lib/logger/src/'), - }, - }, - test: { - coverage: { - exclude: ['**/test/*'], - include: ['src/**/*'], - reporter: ['text', 'html'], - }, - environment: 'node', - globals: true, - include: ['**/*.test.?(c|m)[jt]s?(x)'], - setupFiles: ['src/test/setup.ts'], - }, -}) diff --git a/universal/api-schemas/package.json b/universal/api-schemas/package.json index 7ba39f8d..bd439bdf 100644 --- a/universal/api-schemas/package.json +++ b/universal/api-schemas/package.json @@ -23,7 +23,9 @@ "dist/**/*" ], "scripts": { - "build": "pnpm clean; rslib build && build-tools emit-dual-dts ./dist", + "build": "pnpm clean; pnpm build:dist", + "build:ci": "pnpm build:dist", + "build:dist": "rslib build && build-tools emit-dual-dts ./dist", "clean": "rimraf ./.rslib ./dist .tsbuildinfo", "test:unit": "echo \"No unit tests necessary\"", "typecheck": "tsc --noEmit" diff --git a/universal/core/package.json b/universal/core/package.json index 9e0e2bde..c196f4f6 100644 --- a/universal/core/package.json +++ b/universal/core/package.json @@ -23,9 +23,11 @@ "dist/**/*" ], "scripts": { - "build": "pnpm clean; rslib build && build-tools emit-dual-dts ./dist", + "build": "pnpm clean; pnpm build:dist", + "build:ci": "pnpm build:dist", + "build:dist": "rslib build && build-tools emit-dual-dts ./dist", "clean": "rimraf ./.rslib ./dist ./coverage .tsbuildinfo", - "test:unit": "vitest run --coverage", + "test:unit": "rstest run --coverage", "typecheck": "tsc --noEmit" }, "dependencies": { @@ -41,14 +43,14 @@ "@microsoft/api-extractor": "catalog:", "@rslib/core": "catalog:", "@types/node": "catalog:", - "@vitest/coverage-v8": "catalog:", + "@rstest/core": "catalog:", + "@rstest/coverage-istanbul": "catalog:", "build-tools": "workspace:*", "logger": "workspace:*", "mocks": "workspace:*", "msw": "catalog:", "rimraf": "catalog:", "tslib": "catalog:", - "typescript": "catalog:", - "vitest": "catalog:" + "typescript": "catalog:" } } diff --git a/universal/core/rstest.config.ts b/universal/core/rstest.config.ts new file mode 100644 index 00000000..dd37bf6f --- /dev/null +++ b/universal/core/rstest.config.ts @@ -0,0 +1,23 @@ +import { defineConfig } from '@rstest/core' +import { resolve } from 'node:path' + +const coverageReporters = process.env.CI === 'true' ? ['text-summary', 'lcov'] : ['text', 'html'] + +export default defineConfig({ + resolve: { + alias: { + '@contentful/optimization-api-client': resolve(__dirname, '../api-client/src/'), + '@contentful/optimization-api-schemas': resolve(__dirname, '../api-schemas/src/'), + logger: resolve(__dirname, '../../lib/logger/src/'), + }, + }, + include: ['**/*.test.?(c|m)[jt]s?(x)'], + globals: true, + testEnvironment: 'node', + setupFiles: ['src/test/setup.ts'], + coverage: { + exclude: ['**/test/*'], + include: ['src/**/*'], + reporters: coverageReporters, + }, +}) diff --git a/universal/core/src/lib/decorators/guardedBy.test.ts b/universal/core/src/lib/decorators/guardedBy.test.ts index 0064f543..30df387f 100644 --- a/universal/core/src/lib/decorators/guardedBy.test.ts +++ b/universal/core/src/lib/decorators/guardedBy.test.ts @@ -1,10 +1,10 @@ import { guardedBy } from './guardedBy' -const inlineOnBlockedSpy = vi.fn<(name: string, args: readonly unknown[]) => void>() +const inlineOnBlockedSpy = rs.fn<(name: string, args: readonly unknown[]) => void>() describe('guardedBy (methods only, sync predicate/onBlocked, silent block)', () => { beforeEach((): void => { - vi.clearAllMocks() + rs.clearAllMocks() }) const ON_BLOCKED_SYM: unique symbol = Symbol('onBlockedSym') @@ -96,7 +96,7 @@ describe('guardedBy (methods only, sync predicate/onBlocked, silent block)', () const fx = new Fixture() fx.enabled = true - const gateSpy = vi.spyOn(fx, 'gate') + const gateSpy = rs.spyOn(fx, 'gate') const result = fx.doWork('A') expect(result).toBe('ok:A') @@ -108,7 +108,7 @@ describe('guardedBy (methods only, sync predicate/onBlocked, silent block)', () const fx = new Fixture() fx.enabled = false - const gateSpy = vi.spyOn(fx, 'gate') + const gateSpy = rs.spyOn(fx, 'gate') const result = fx.doWork('B') expect(result).toBeUndefined() @@ -132,8 +132,8 @@ describe('guardedBy (methods only, sync predicate/onBlocked, silent block)', () const fx = new Fixture() fx.enabled = false - const gateSpy = vi.spyOn(fx, 'gate') - const handlerSpy = vi.spyOn(fx, 'handleBlocked') + const gateSpy = rs.spyOn(fx, 'gate') + const handlerSpy = rs.spyOn(fx, 'handleBlocked') const result = fx.withNamedHandler('E') expect(result).toBeUndefined() @@ -148,8 +148,9 @@ describe('guardedBy (methods only, sync predicate/onBlocked, silent block)', () const fx = new Fixture() fx.enabled = false - const gateSpy = vi.spyOn(fx, 'gate') - const symHandlerSpy = vi.spyOn(fx, ON_BLOCKED_SYM) + const gateSpy = rs.spyOn(fx, 'gate') + const symHandlerSpy = rs.fn<(name: string, args: readonly unknown[]) => void>() + fx[ON_BLOCKED_SYM] = symHandlerSpy const result = fx.withSymbolHandler('S') expect(result).toBeUndefined() @@ -164,7 +165,7 @@ describe('guardedBy (methods only, sync predicate/onBlocked, silent block)', () const fx = new Fixture() fx.enabled = false - const gateSpy = vi.spyOn(fx, 'gate') + const gateSpy = rs.spyOn(fx, 'gate') const result = fx.withInlineHandler('F') expect(result).toBeUndefined() @@ -179,7 +180,7 @@ describe('guardedBy (methods only, sync predicate/onBlocked, silent block)', () const fx = new Fixture() fx.enabled = false - const gateSpy = vi.spyOn(fx, 'gate') + const gateSpy = rs.spyOn(fx, 'gate') const result = fx.withNonFunctionHandler('N') expect(result).toBeUndefined() @@ -189,7 +190,7 @@ describe('guardedBy (methods only, sync predicate/onBlocked, silent block)', () it('supports decorating async methods: blocked resolves to undefined; allowed resolves to value', async (): Promise => { const fx = new Fixture() - const gateSpy = vi.spyOn(fx, 'gate') + const gateSpy = rs.spyOn(fx, 'gate') // Blocked: resolves to undefined fx.enabled = false @@ -229,7 +230,7 @@ describe('guardedBy (methods only, sync predicate/onBlocked, silent block)', () } const fx = new SymFixture() - const gateSpy = vi.spyOn(fx, 'gate') + const gateSpy = rs.spyOn(fx, 'gate') const result = fx[METHOD_SYM]('Z') expect(result).toBe('ok:Z') @@ -257,7 +258,7 @@ describe('guardedBy (methods only, sync predicate/onBlocked, silent block)', () } const fx = new SymFixtureNoDesc() - const gateSpy = vi.spyOn(fx, 'gate') + const gateSpy = rs.spyOn(fx, 'gate') const result = fx[METHOD_SYM_NO_DESC]('Q') expect(result).toBe('ok:Q') diff --git a/universal/core/src/lib/interceptor/InterceptorManager.test.ts b/universal/core/src/lib/interceptor/InterceptorManager.test.ts index 49c60c75..d3a007a6 100644 --- a/universal/core/src/lib/interceptor/InterceptorManager.test.ts +++ b/universal/core/src/lib/interceptor/InterceptorManager.test.ts @@ -56,14 +56,14 @@ describe('InterceptorManager', () => { }) it('executes interceptors in insertion order (sync + async) and pipes transformed values', async () => { - const addTimestamp = vi.fn( + const addTimestamp = rs.fn( (evt: Readonly): Event => ({ ...evt, timestamp: evt.timestamp ?? '2025-09-01T00:00:00.000Z', }), ) - const addFlagAsync = vi.fn(async (evt: Readonly): Promise => { + const addFlagAsync = rs.fn(async (evt: Readonly): Promise => { await Promise.resolve() return { ...evt, @@ -71,7 +71,7 @@ describe('InterceptorManager', () => { } }) - const redactSecret = vi.fn((evt: Readonly): Event => { + const redactSecret = rs.fn((evt: Readonly): Event => { const nextPayload: Record = { ...evt.payload } if (Object.prototype.hasOwnProperty.call(nextPayload, 'secret')) { nextPayload.secret = '[REDACTED]' @@ -134,7 +134,7 @@ describe('InterceptorManager', () => { }) it('snapshots the interceptor list during run(): adds/removes during a run do not affect that run', async () => { - const dynamicSpy = vi.fn( + const dynamicSpy = rs.fn( (evt: Readonly): Event => ({ ...evt, payload: { ...evt.payload, dynamic: true }, @@ -143,14 +143,14 @@ describe('InterceptorManager', () => { let firstId = -1 - const first = vi.fn((evt: Readonly): Event => { + const first = rs.fn((evt: Readonly): Event => { const removed = manager.remove(firstId) expect(removed).toBe(true) manager.add(dynamicSpy) return evt }) - const second = vi.fn( + const second = rs.fn( (evt: Readonly): Event => ({ ...evt, payload: { ...evt.payload, afterFirst: true }, diff --git a/universal/core/src/personalization/resolvers/PersonalizedEntryResolver.test.ts b/universal/core/src/personalization/resolvers/PersonalizedEntryResolver.test.ts index 50eb9846..7443338b 100644 --- a/universal/core/src/personalization/resolvers/PersonalizedEntryResolver.test.ts +++ b/universal/core/src/personalization/resolvers/PersonalizedEntryResolver.test.ts @@ -10,15 +10,15 @@ import { type PersonalizedEntry, type SelectedPersonalizationArray, } from '@contentful/optimization-api-client' +import { describe, expect, it, rs } from '@rstest/core' import type { Entry } from 'contentful' -import { describe, expect, it, vi } from 'vitest' import { mockLogger } from 'mocks' import { personalizedEntry as personalizedEntryFixture } from '../../test/fixtures/personalizedEntry' import { selectedPersonalizations as selectedPersonalizationsFixture } from '../../test/fixtures/selectedPersonalizations' import PersonalizedEntryResolver from './PersonalizedEntryResolver' -const mockedLogger = vi.mocked(mockLogger) +const mockedLogger = rs.mocked(mockLogger) const RESOLUTION_WARNING_BASE = 'Could not resolve personalized entry variant:' diff --git a/universal/core/src/test/setup.ts b/universal/core/src/test/setup.ts index 14029ae2..f672daac 100644 --- a/universal/core/src/test/setup.ts +++ b/universal/core/src/test/setup.ts @@ -1,8 +1,8 @@ +import { rs } from '@rstest/core' import { experienceApiHandlers, loggerMock, resetMockLogger } from 'mocks' import { setupServer } from 'msw/node' -import { vi } from 'vitest' -vi.mock('logger', () => loggerMock) +rs.mock('logger', () => loggerMock) export const server = setupServer(...experienceApiHandlers.getHandlers()) diff --git a/universal/core/tsconfig.json b/universal/core/tsconfig.json index 0b1acd57..a655ac1d 100644 --- a/universal/core/tsconfig.json +++ b/universal/core/tsconfig.json @@ -1,4 +1,7 @@ { "extends": "../../tsconfig.base.json", + "compilerOptions": { + "types": ["@rstest/core/globals"] + }, "include": ["./src/**/*", "./src/**/*.json"] } diff --git a/universal/core/vitest.config.ts b/universal/core/vitest.config.ts deleted file mode 100644 index e7209c84..00000000 --- a/universal/core/vitest.config.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { resolve } from 'node:path' -import { defineConfig } from 'vitest/config' - -export default defineConfig({ - resolve: { - alias: { - '@contentful/optimization-api-client': resolve(__dirname, '../api-client/src/'), - '@contentful/optimization-api-schemas': resolve(__dirname, '../api-schemas/src/'), - logger: resolve(__dirname, '../../lib/logger/src/'), - }, - }, - test: { - coverage: { - exclude: ['**/test/*'], - include: ['src/**/*'], - reporter: ['text', 'html'], - }, - environment: 'node', - globals: true, - include: ['**/*.test.?(c|m)[jt]s?(x)'], - setupFiles: ['src/test/setup.ts'], - }, -})