diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 29ecfc5..2a655d4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,6 +77,34 @@ jobs: - name: Run Tests run: pnpm test + playground-integration-test: + name: Playground Integration Tests + needs: detect-changes + if: needs.detect-changes.outputs.appkit == 'true' + runs-on: + group: databricks-protected-runner-group + labels: linux-ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'pnpm' + - name: Install dependencies + run: pnpm install --frozen-lockfile + - name: Install Playwright Browsers + run: pnpm --filter=dev-playground exec playwright install --with-deps chromium + - name: Build packages + run: pnpm build + - name: Run Integration Tests + run: pnpm --filter=dev-playground test:integration + env: + APPKIT_E2E_TEST: 'true' + DATABRICKS_WAREHOUSE_ID: e2e-mock + DATABRICKS_WORKSPACE_ID: e2e-mock + docs-build: name: Docs Build needs: detect-changes diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ea17a66..73bcbe7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -54,6 +54,13 @@ The following command will compile all the packages and app in watch mode. pnpm dev ``` +> **Note:** To avoid port collisions with the `clean-app` example, you should create a `.env` file in `apps/dev-playground` and set another port for this app: +> +> ``` +> DATABRICKS_APP_PORT=8001 +> ``` + + ## Running the project in production mode Running the following command diff --git a/apps/dev-playground/.gitignore b/apps/dev-playground/.gitignore new file mode 100644 index 0000000..5c957fc --- /dev/null +++ b/apps/dev-playground/.gitignore @@ -0,0 +1,3 @@ +# Playwright +test-results/ +playwright-report/ \ No newline at end of file diff --git a/apps/dev-playground/README.md b/apps/dev-playground/README.md new file mode 100644 index 0000000..cf06ab1 --- /dev/null +++ b/apps/dev-playground/README.md @@ -0,0 +1,41 @@ +# Dev Playground + +Test application showing AppKit capabilities including analytics dashboards, SSE streaming, telemetry, and data visualization. + +## Development + +```bash +# Start development server +pnpm dev + +# Build for production +pnpm build + +# Start production server +pnpm start:local +``` + +## Integration Tests + +Integration tests use Playwright to verify the application works correctly with mocked backend responses. + +**Note:** These are frontend-only integration tests. API calls are intercepted at the browser level and return mock data, so the AppKit backend plugins are not tested. They focus on verifying UI behavior, navigation, data rendering, and client-side interactions. + +### Running Tests + +```bash +# Run all integration tests +pnpm test:integration + +# Run tests with interactive UI mode (for debugging) +pnpm test:integration:ui + +# Run tests in headed mode (see the browser) +pnpm test:integration:headed + +# Run a specific test file +npx playwright test tests/smoke.spec.ts + +# Run tests matching a pattern +npx playwright test -g "analytics" +``` diff --git a/apps/dev-playground/client/package-lock.json b/apps/dev-playground/client/package-lock.json index a0cd6cd..22b2073 100644 --- a/apps/dev-playground/client/package-lock.json +++ b/apps/dev-playground/client/package-lock.json @@ -113,6 +113,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -2615,6 +2616,7 @@ "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.134.9.tgz", "integrity": "sha512-JIxFamShs3gRIkOxpgz/3bglbSKZHMrzKASwNFg+sQPVXVPOLtN35D5PuEDAFTPPht9Wv48WWUNYE03ZytnNug==", "license": "MIT", + "peer": true, "dependencies": { "@tanstack/history": "1.133.28", "@tanstack/react-store": "^0.8.0", @@ -2722,6 +2724,7 @@ "resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.134.9.tgz", "integrity": "sha512-9Vr8tYC59I70DYGVRknRf4vjQMjSfHvmc+iTM8vcpwERBh3Vgkv90f8ol85KHKqjorSsCqMeYFhFt8AM4A4CSw==", "license": "MIT", + "peer": true, "dependencies": { "@tanstack/history": "1.133.28", "@tanstack/store": "^0.8.0", @@ -3061,6 +3064,7 @@ "integrity": "sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.14.0" } @@ -3071,6 +3075,7 @@ "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -3081,6 +3086,7 @@ "integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -3144,6 +3150,7 @@ "integrity": "sha512-6JSSaBZmsKvEkbRUkf7Zj7dru/8ZCrJxAqArcLaVMee5907JdtEbKGsZ7zNiIm/UAkpGUkaSMZEXShnN2D1HZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.1", "@typescript-eslint/types": "8.46.1", @@ -3402,6 +3409,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3625,6 +3633,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.9", "caniuse-lite": "^1.0.30001746", @@ -3923,7 +3932,8 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/d3-array": { "version": "3.2.4", @@ -4223,6 +4233,7 @@ "integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -5590,6 +5601,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -5678,6 +5690,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -5687,6 +5700,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -5706,6 +5720,7 @@ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", "license": "MIT", + "peer": true, "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" @@ -5874,7 +5889,8 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -6041,6 +6057,7 @@ "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.3.2.tgz", "integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=10" } @@ -6193,7 +6210,8 @@ "version": "4.1.17", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz", "integrity": "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tailwindcss-animate": { "version": "1.0.7", @@ -6222,7 +6240,8 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tiny-warning": { "version": "1.0.3", @@ -6268,6 +6287,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -6364,6 +6384,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -6653,6 +6674,7 @@ "resolved": "https://registry.npmjs.org/rolldown-vite/-/rolldown-vite-7.1.14.tgz", "integrity": "sha512-eSiiRJmovt8qDJkGyZuLnbxAOAdie6NCmmd0NkTC0RJI9duiSBTfr8X2mBYJOUFzxQa2USaHmL99J9uMxkjCyw==", "license": "MIT", + "peer": true, "dependencies": { "@oxc-project/runtime": "0.92.0", "fdir": "^6.5.0", @@ -6745,6 +6767,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, diff --git a/apps/dev-playground/client/src/routes/telemetry.route.tsx b/apps/dev-playground/client/src/routes/telemetry.route.tsx index 29601b2..bbfda23 100644 --- a/apps/dev-playground/client/src/routes/telemetry.route.tsx +++ b/apps/dev-playground/client/src/routes/telemetry.route.tsx @@ -106,9 +106,7 @@ function TelemetryRoute() {
-

- Telemetry Demo -

+

Telemetry Demo

Demonstrates how the SDK's auto-instrumentation integrates with custom application telemetry. This example showcases HTTP and cache diff --git a/apps/dev-playground/package.json b/apps/dev-playground/package.json index 7752b29..c5781cf 100644 --- a/apps/dev-playground/package.json +++ b/apps/dev-playground/package.json @@ -14,7 +14,10 @@ "preview": "vite preview", "check": "tsc", "clean": "rm -rf build && cd client && rm -rf dist", - "clean:full": "rm -rf build node_modules && cd client && rm -rf dist node_modules" + "clean:full": "rm -rf build node_modules && cd client && rm -rf dist node_modules", + "test:integration": "playwright test", + "test:integration:ui": "playwright test --ui", + "test:integration:headed": "playwright test --headed" }, "keywords": [], "author": "", @@ -25,6 +28,7 @@ "zod": "^4.1.13" }, "devDependencies": { + "@playwright/test": "^1.58.1", "@types/node": "^20.0.0", "@types/react-syntax-highlighter": "^15.5.13", "@vitejs/plugin-react": "^5.0.4", diff --git a/apps/dev-playground/playwright.config.ts b/apps/dev-playground/playwright.config.ts new file mode 100644 index 0000000..8a2546f --- /dev/null +++ b/apps/dev-playground/playwright.config.ts @@ -0,0 +1,27 @@ +import { defineConfig, devices } from "@playwright/test"; +import "dotenv/config"; + +export default defineConfig({ + testDir: "./tests", + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: 0, + workers: process.env.CI ? 1 : undefined, + reporter: "html", + use: { + baseURL: `http://localhost:${process.env.DATABRICKS_APP_PORT || 8000}`, + trace: "on-first-retry", + }, + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + ], + webServer: { + command: "pnpm dev", + url: `http://localhost:${process.env.DATABRICKS_APP_PORT || 8000}`, + reuseExistingServer: !process.env.CI, + timeout: 120 * 1000, + }, +}); diff --git a/apps/dev-playground/server/index.ts b/apps/dev-playground/server/index.ts index ed3fdac..a56ba4a 100644 --- a/apps/dev-playground/server/index.ts +++ b/apps/dev-playground/server/index.ts @@ -1,7 +1,18 @@ import { analytics, createApp, server } from "@databricks/appkit"; +import { WorkspaceClient } from "@databricks/sdk-experimental"; import { reconnect } from "./reconnect-plugin"; import { telemetryExamples } from "./telemetry-example-plugin"; +function createMockClient() { + const client = new WorkspaceClient({ + host: "http://localhost", + token: "e2e", + authType: "pat", + }); + client.currentUser.me = async () => ({ id: "e2e-test-user" }); + return client; +} + createApp({ plugins: [ server({ autoStart: false }), @@ -9,6 +20,7 @@ createApp({ telemetryExamples(), analytics({}), ], + ...(process.env.APPKIT_E2E_TEST && { client: createMockClient() }), }).then((appkit) => { appkit.server .extend((app) => { diff --git a/apps/dev-playground/tests/arrow-analytics.spec.ts b/apps/dev-playground/tests/arrow-analytics.spec.ts new file mode 100644 index 0000000..408c751 --- /dev/null +++ b/apps/dev-playground/tests/arrow-analytics.spec.ts @@ -0,0 +1,98 @@ +import { expect, test } from "@playwright/test"; +import { + STRICT_MODE_MULTIPLIER, + setupMockAPI, + trackApiCalls, + waitForChartsToLoad, +} from "./utils/test-utils"; + +test.describe("Arrow Analytics", () => { + test.beforeEach(async ({ page }) => { + await setupMockAPI(page); + }); + + test("page loads and displays heading", async ({ page }) => { + await page.goto("/arrow-analytics", { waitUntil: "networkidle" }); + + await expect(page.getByText("Unified Charts API")).toBeVisible(); + }); + + test("calls expected API endpoints", async ({ page }) => { + const appsListCalls = trackApiCalls(page, "apps_list"); + const spendDataCalls = trackApiCalls(page, "spend_data"); + const topContributorsCalls = trackApiCalls(page, "top_contributors"); + const heatmapCalls = trackApiCalls(page, "app_activity_heatmap"); + + await page.goto("/arrow-analytics", { waitUntil: "networkidle" }); + await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)); + await waitForChartsToLoad(page); + + expect(appsListCalls.length).toBe(5 * STRICT_MODE_MULTIPLIER); + expect(spendDataCalls.length).toBe(5 * STRICT_MODE_MULTIPLIER); + expect(topContributorsCalls.length).toBe(2 * STRICT_MODE_MULTIPLIER); + expect(heatmapCalls.length).toBe(2 * STRICT_MODE_MULTIPLIER); + }); + + test("charts render with mock data (no empty states)", async ({ page }) => { + await page.goto("/arrow-analytics", { waitUntil: "networkidle" }); + await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)); + await waitForChartsToLoad(page); + + const barCharts = page.locator('[data-testid="bar-chart-apps_list"]'); + expect(await barCharts.count()).toBe(3); + await expect(barCharts.first().locator("canvas")).toBeVisible(); + + const lineCharts = page.locator('[data-testid="line-chart-spend_data"]'); + expect(await lineCharts.count()).toBe(3); + await expect(lineCharts.first().locator("canvas")).toBeVisible(); + + const donutCharts = page.locator( + '[data-testid="donut-chart-top_contributors"]', + ); + expect(await donutCharts.count()).toBe(2); + await expect(donutCharts.first().locator("canvas")).toBeVisible(); + + const heatmapCharts = page.locator( + '[data-testid="heatmap-chart-app_activity_heatmap"]', + ); + expect(await heatmapCharts.count()).toBe(2); + await expect(heatmapCharts.first().locator("canvas")).toBeVisible(); + }); + + test("chart tooltip appears on hover with mock app data", async ({ + page, + }) => { + await page.goto("/arrow-analytics", { waitUntil: "networkidle" }); + await waitForChartsToLoad(page); + + const barChart = page + .locator('[data-testid="bar-chart-apps_list"]') + .first() + .locator("canvas"); + await expect(barChart).toBeVisible(); + + const box = await barChart.boundingBox(); + if (!box) throw new Error("Could not get chart bounding box"); + + const positions = [0.2, 0.35, 0.5, 0.65, 0.8]; + for (const xRatio of positions) { + await page.mouse.move( + box.x + box.width * xRatio, + box.y + box.height * 0.4, + ); + + const tooltip = page + .locator("div") + .filter({ hasText: /App One|App Two|App Three/ }); + + try { + await expect(tooltip.first()).toBeVisible({ timeout: 1000 }); + return; + } catch {} + } + + throw new Error( + "Could not trigger tooltip with any mock app data after trying multiple positions", + ); + }); +}); diff --git a/apps/dev-playground/tests/data-visualization.spec.ts b/apps/dev-playground/tests/data-visualization.spec.ts new file mode 100644 index 0000000..55d1f4b --- /dev/null +++ b/apps/dev-playground/tests/data-visualization.spec.ts @@ -0,0 +1,84 @@ +import { expect, test } from "@playwright/test"; +import { + STRICT_MODE_MULTIPLIER, + setupMockAPI, + trackApiCalls, +} from "./utils/test-utils"; + +test.describe("Data Visualization Route Tests", () => { + test.beforeEach(async ({ page }) => { + await setupMockAPI(page); + }); + + test("data-visualization page loads successfully", async ({ page }) => { + await page.goto("/data-visualization", { waitUntil: "networkidle" }); + + await expect(page).toHaveURL("/data-visualization"); + }); + + test("page displays Data Visualization heading", async ({ page }) => { + await page.goto("/data-visualization", { waitUntil: "networkidle" }); + + await expect(page.getByText("Data Visualization")).toBeVisible(); + }); + + test("simple data table displays mock data", async ({ page }) => { + await page.goto("/data-visualization", { waitUntil: "networkidle" }); + + const simpleTable = page.locator("table").nth(0); + + await simpleTable.scrollIntoViewIfNeeded(); + await expect(simpleTable).toBeVisible(); + + await expect( + simpleTable.getByRole("cell", { name: "Untagged App 1" }), + ).toBeVisible(); + await expect( + simpleTable.getByRole("cell", { name: "user4@databricks.com" }), + ).toBeVisible(); + }); + + test("advanced data table displays mock data", async ({ page }) => { + await page.goto("/data-visualization", { waitUntil: "networkidle" }); + + const advancedTable = page.locator("table").nth(1); + + await advancedTable.scrollIntoViewIfNeeded(); + await expect(advancedTable).toBeVisible(); + + await expect( + advancedTable.getByRole("cell", { name: "Untagged App 2" }), + ).toBeVisible(); + await expect( + advancedTable.getByRole("cell", { name: "user5@databricks.com" }), + ).toBeVisible(); + }); + + test("calls expected API endpoints on page load", async ({ page }) => { + const untaggedAppsCalls = trackApiCalls(page, "untagged_apps"); + const spendDataCalls = trackApiCalls(page, "spend_data"); + const topContributorsCalls = trackApiCalls(page, "top_contributors"); + + await page.goto("/data-visualization", { waitUntil: "networkidle" }); + + await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)); + await page.waitForLoadState("networkidle"); + + expect(untaggedAppsCalls.length).toBe(2 * STRICT_MODE_MULTIPLIER); + expect(spendDataCalls.length).toBe(6 * STRICT_MODE_MULTIPLIER); + expect(topContributorsCalls.length).toBe(4 * STRICT_MODE_MULTIPLIER); + }); + + test("can toggle code visibility", async ({ page }) => { + await page.goto("/data-visualization", { waitUntil: "networkidle" }); + + const showCodeButton = page + .getByRole("button", { name: "Show Code" }) + .first(); + await showCodeButton.click(); + + await expect( + page.getByRole("button", { name: "Hide Code" }).first(), + ).toBeVisible(); + }); +}); diff --git a/apps/dev-playground/tests/reconnect.spec.ts b/apps/dev-playground/tests/reconnect.spec.ts new file mode 100644 index 0000000..bdae60d --- /dev/null +++ b/apps/dev-playground/tests/reconnect.spec.ts @@ -0,0 +1,47 @@ +import { expect, test } from "@playwright/test"; +import { setupMockAPI } from "./utils/test-utils"; + +test.describe("Reconnect Route Tests", () => { + test.beforeEach(async ({ page }) => { + await setupMockAPI(page); + }); + + test("reconnect page loads successfully", async ({ page }) => { + await page.goto("/reconnect", { waitUntil: "networkidle" }); + + await expect(page).toHaveURL("/reconnect"); + }); + + test("connects to SSE stream and receives all messages", async ({ page }) => { + const streamRequestPromise = page.waitForRequest((request) => + request.url().includes("/api/reconnect/stream"), + ); + + await page.goto("/reconnect", { waitUntil: "networkidle" }); + await streamRequestPromise; + + await expect( + page.locator('[data-slot="badge"]').filter({ hasText: "Reconnected" }), + ).toBeVisible({ timeout: 5000 }); + + const messageCountContainer = page.locator("div").filter({ + has: page.getByText("/ 5 messages received"), + }); + await expect(messageCountContainer.locator("h2")).toHaveText("5", { + timeout: 5000, + }); + }); + + test("restart button triggers new stream connection", async ({ page }) => { + await page.goto("/reconnect", { waitUntil: "networkidle" }); + + const newStreamRequestPromise = page.waitForRequest((request) => + request.url().includes("/api/reconnect/stream"), + ); + + const restartButton = page.getByRole("button", { name: /restart/i }); + await restartButton.click(); + + await newStreamRequestPromise; + }); +}); diff --git a/apps/dev-playground/tests/smoke.spec.ts b/apps/dev-playground/tests/smoke.spec.ts new file mode 100644 index 0000000..e96a75e --- /dev/null +++ b/apps/dev-playground/tests/smoke.spec.ts @@ -0,0 +1,32 @@ +import { expect, test } from "@playwright/test"; + +test.describe("Smoke Tests", () => { + test("app loads and displays homepage", async ({ page }) => { + await page.goto("/", { waitUntil: "networkidle" }); + + await expect( + page.getByRole("heading", { name: "AppKit Playground" }), + ).toBeVisible(); + + await expect( + page.getByText("Explore the capabilities of the AppKit"), + ).toBeVisible(); + }); + + test("no console errors on page load", async ({ page }) => { + const consoleErrors: string[] = []; + + page.on("console", (msg) => { + if (msg.type() === "error") { + consoleErrors.push(msg.text()); + } + }); + + await page.goto("/", { waitUntil: "networkidle" }); + + expect( + consoleErrors, + `Console errors detected: ${consoleErrors.join(", ")}`, + ).toHaveLength(0); + }); +}); diff --git a/apps/dev-playground/tests/sql-helpers.spec.ts b/apps/dev-playground/tests/sql-helpers.spec.ts new file mode 100644 index 0000000..7388d74 --- /dev/null +++ b/apps/dev-playground/tests/sql-helpers.spec.ts @@ -0,0 +1,59 @@ +import { expect, test } from "@playwright/test"; +import { mockAnalyticsData } from "./utils/mock-data"; +import { setupMockAPI } from "./utils/test-utils"; + +test.describe("SQL Helpers Route Tests", () => { + test.beforeEach(async ({ page }) => { + await setupMockAPI(page); + await page.goto("/sql-helpers", { waitUntil: "networkidle" }); + }); + + test("sql-helpers page loads successfully", async ({ page }) => { + await expect(page).toHaveURL("/sql-helpers"); + }); + + test("can interact with string input", async ({ page }) => { + const inputs = page.locator("input").first(); + await inputs.fill("Test String Value"); + + await expect(inputs).toHaveValue("Test String Value"); + }); + + test("can interact with number input", async ({ page }) => { + const numberInput = page.locator('input[type="number"]').first(); + await numberInput.fill("123"); + + await expect(numberInput).toHaveValue("123"); + }); + + test("can toggle boolean value", async ({ page }) => { + const falseButton = page.getByRole("button", { name: "false" }); + await falseButton.click(); + + const trueButton = page.getByRole("button", { name: "true" }); + await expect(trueButton).toBeVisible(); + }); + + test("show code button reveals code example", async ({ page }) => { + const showCodeButton = page + .getByRole("button", { name: "Show Code" }) + .first(); + await showCodeButton.click(); + + await expect(page.getByText("Usage:").first()).toBeVisible(); + }); + + test("sql_helpers_test query executes and displays mock data", async ({ + page, + }) => { + await expect(page.getByText("Query executed successfully")).toBeVisible({ + timeout: 5000, + }); + + const resultPre = page.locator(".bg-success\\/10 pre"); + await expect(resultPre).toBeVisible(); + await expect(resultPre).toHaveText( + JSON.stringify(mockAnalyticsData.sqlHelpersTest[0], null, 2), + ); + }); +}); diff --git a/apps/dev-playground/tests/telemetry.spec.ts b/apps/dev-playground/tests/telemetry.spec.ts new file mode 100644 index 0000000..90bd91b --- /dev/null +++ b/apps/dev-playground/tests/telemetry.spec.ts @@ -0,0 +1,27 @@ +import { expect, test } from "@playwright/test"; +import { setupMockAPI, trackApiCalls } from "./utils/test-utils"; + +test.describe("Telemetry Route Tests", () => { + test.beforeEach(async ({ page }) => { + await setupMockAPI(page); + await page.goto("/telemetry", { waitUntil: "networkidle" }); + }); + + test("telemetry page loads successfully", async ({ page }) => { + await expect(page).toHaveURL("/telemetry"); + }); + + test("run button triggers POST request and shows success", async ({ + page, + }) => { + const requests = trackApiCalls(page, "/api/telemetry-examples"); + + const runButton = page.getByRole("button", { name: /Run.*Request/i }); + await runButton.click(); + + await expect(page.getByText("Success")).toBeVisible({ timeout: 5000 }); + + const postRequests = requests.filter((r) => r.method() === "POST"); + expect(postRequests.length).toBeGreaterThan(0); + }); +}); diff --git a/apps/dev-playground/tests/utils/mock-data.ts b/apps/dev-playground/tests/utils/mock-data.ts new file mode 100644 index 0000000..39a6adc --- /dev/null +++ b/apps/dev-playground/tests/utils/mock-data.ts @@ -0,0 +1,180 @@ +/** + * Mock data for dev-playground frontend integration tests + * These mock responses simulate the API responses from the backend + */ + +// Analytics mock data +export const mockAnalyticsData = { + spendSummary: [ + { + total: 15000, + average: 500, + forecasted: 18000, + }, + ], + + appsList: [ + { + id: 1, + name: "App One", + creator: "user1@databricks.com", + totalSpend: 5000, + tags: ["prod", "analytics"], + createdAt: "2024-01-15T10:30:00Z", + }, + { + id: 2, + name: "App Two", + creator: "user2@databricks.com", + totalSpend: 3500, + tags: ["staging"], + createdAt: "2024-02-20T14:45:00Z", + }, + { + id: 3, + name: "App Three", + creator: "user3@databricks.com", + totalSpend: 2200, + tags: [], + createdAt: "2024-03-10T09:00:00Z", + }, + ], + + untaggedApps: [ + { + app_name: "Untagged App 1", + creator: "user4@databricks.com", + total_cost_usd: 1200, + avg_period_cost_usd: 400, + }, + { + app_name: "Untagged App 2", + creator: "user5@databricks.com", + total_cost_usd: 800, + avg_period_cost_usd: 266, + }, + ], + + spendData: [ + { + aggregation_period: "2024-01-01", + cost_usd: 1200, + group_key: "default", + }, + { + aggregation_period: "2024-01-02", + cost_usd: 1350, + group_key: "default", + }, + { + aggregation_period: "2024-01-03", + cost_usd: 980, + group_key: "default", + }, + { + aggregation_period: "2024-01-04", + cost_usd: 1500, + group_key: "default", + }, + { + aggregation_period: "2024-01-05", + cost_usd: 1100, + group_key: "default", + }, + ], + + topContributors: [ + { app_name: "Top App 1", total_cost_usd: 5000 }, + { app_name: "Top App 2", total_cost_usd: 3500 }, + { app_name: "Top App 3", total_cost_usd: 2800 }, + { app_name: "Top App 4", total_cost_usd: 2200 }, + { app_name: "Top App 5", total_cost_usd: 1500 }, + ], + + appActivityHeatmap: [ + { app_name: "App One", day_of_week: "Monday", spend: 500 }, + { app_name: "App One", day_of_week: "Tuesday", spend: 600 }, + { app_name: "App One", day_of_week: "Wednesday", spend: 450 }, + { app_name: "App Two", day_of_week: "Monday", spend: 300 }, + { app_name: "App Two", day_of_week: "Tuesday", spend: 400 }, + { app_name: "App Two", day_of_week: "Wednesday", spend: 350 }, + ], + + sqlHelpersTest: [ + { + string_value: "Hello, Databricks!", + number_value: 42, + boolean_value: true, + date_value: "2024-01-15", + timestamp_value: "2024-01-15T10:30:00Z", + binary_value: "Spark", + binary_hex: "537061726B", + binary_length: 5, + }, + ], +}; + +// Reconnect/SSE mock data +export const mockReconnectMessages = [ + { + type: "message", + count: 1, + total: 5, + timestamp: new Date().toISOString(), + content: "Message 1 of 5", + }, + { + type: "message", + count: 2, + total: 5, + timestamp: new Date().toISOString(), + content: "Message 2 of 5", + }, + { + type: "message", + count: 3, + total: 5, + timestamp: new Date().toISOString(), + content: "Message 3 of 5", + }, + { + type: "message", + count: 4, + total: 5, + timestamp: new Date().toISOString(), + content: "Message 4 of 5", + }, + { + type: "message", + count: 5, + total: 5, + timestamp: new Date().toISOString(), + content: "Message 5 of 5", + }, +]; + +// Telemetry mock data +export const mockTelemetryResponse = { + success: true, + message: "Telemetry example completed successfully", + duration_ms: 150, + result: { items_processed: 5 }, + tracing: { + hint: "View traces in Grafana", + services: ["telemetry-examples"], + expectedSpans: [ + "telemetry-examples.combined", + "cache-lookup", + "http-request", + ], + }, + metrics: { + recorded: [ + "telemetry_examples.requests_total", + "telemetry_examples.request_duration_ms", + ], + }, + logs: { + emitted: ["Processing started", "Cache miss", "Processing completed"], + }, +}; diff --git a/apps/dev-playground/tests/utils/test-utils.ts b/apps/dev-playground/tests/utils/test-utils.ts new file mode 100644 index 0000000..a444902 --- /dev/null +++ b/apps/dev-playground/tests/utils/test-utils.ts @@ -0,0 +1,136 @@ +import type { Page, Request } from "@playwright/test"; +import { + mockAnalyticsData, + mockReconnectMessages, + mockTelemetryResponse, +} from "./mock-data"; + +/** + * React 19 Strict Mode doubles useEffect invocations in development mode + * to help detect side effects. This multiplier accounts for that behavior + * when asserting API call counts in tests. + * + * @see https://react.dev/reference/react/StrictMode#fixing-bugs-found-by-re-running-effects-in-development + */ +export const STRICT_MODE_MULTIPLIER = 2; + +function createSSEResponse(data: unknown): string { + const event = JSON.stringify({ type: "result", data }); + return `data: ${event}\n\n`; +} + +function getSSEHeaders() { + return { + "Content-Type": "text/event-stream", + "Cache-Control": "no-cache", + Connection: "keep-alive", + }; +} + +export async function setupMockAPI(page: Page) { + await page.route("**/api/analytics/query/**", async (route) => { + const url = route.request().url(); + + if (url.includes("spend_summary")) { + return route.fulfill({ + status: 200, + headers: getSSEHeaders(), + body: createSSEResponse(mockAnalyticsData.spendSummary), + }); + } + if (url.includes("apps_list")) { + return route.fulfill({ + status: 200, + headers: getSSEHeaders(), + body: createSSEResponse(mockAnalyticsData.appsList), + }); + } + if (url.includes("untagged_apps")) { + return route.fulfill({ + status: 200, + headers: getSSEHeaders(), + body: createSSEResponse(mockAnalyticsData.untaggedApps), + }); + } + if (url.includes("spend_data")) { + return route.fulfill({ + status: 200, + headers: getSSEHeaders(), + body: createSSEResponse(mockAnalyticsData.spendData), + }); + } + if (url.includes("top_contributors")) { + return route.fulfill({ + status: 200, + headers: getSSEHeaders(), + body: createSSEResponse(mockAnalyticsData.topContributors), + }); + } + if (url.includes("app_activity_heatmap")) { + return route.fulfill({ + status: 200, + headers: getSSEHeaders(), + body: createSSEResponse(mockAnalyticsData.appActivityHeatmap), + }); + } + if (url.includes("sql_helpers_test")) { + return route.fulfill({ + status: 200, + headers: getSSEHeaders(), + body: createSSEResponse(mockAnalyticsData.sqlHelpersTest), + }); + } + + // Default empty response for unknown queries + return route.fulfill({ + status: 200, + headers: getSSEHeaders(), + body: createSSEResponse([]), + }); + }); + + await page.route("**/api/reconnect/stream**", async (route) => { + const body = mockReconnectMessages + .map((msg, i) => `id: ${i + 1}\ndata: ${JSON.stringify(msg)}\n\n`) + .join(""); + + return route.fulfill({ + status: 200, + headers: { + "Content-Type": "text/event-stream", + "Cache-Control": "no-cache", + Connection: "keep-alive", + }, + body, + }); + }); + + await page.route("**/api/reconnect", async (route) => { + if (route.request().url().endsWith("/api/reconnect")) { + return route.fulfill({ json: { message: "Reconnected" } }); + } + return route.continue(); + }); + + await page.route("**/api/telemetry-examples/**", async (route) => { + return route.fulfill({ json: mockTelemetryResponse }); + }); +} + +export async function waitForChartsToLoad(page: Page) { + await page.waitForLoadState("networkidle"); + await page.waitForFunction( + () => document.querySelectorAll(".animate-pulse").length === 0, + { timeout: 10000 }, + ); +} + +export function trackApiCalls(page: Page, urlPattern: string) { + const requests: Request[] = []; + page.on("request", (request) => { + if (request.url().includes(urlPattern)) { + requests.push(request); + } + }); + return requests; +} diff --git a/docs/docs/api/appkit/Function.createApp.md b/docs/docs/api/appkit/Function.createApp.md index 35128e0..85e7fa2 100644 --- a/docs/docs/api/appkit/Function.createApp.md +++ b/docs/docs/api/appkit/Function.createApp.md @@ -3,6 +3,7 @@ ```ts function createApp(config: { cache?: CacheConfig; + client?: WorkspaceClient; plugins?: T; telemetry?: TelemetryConfig; }): Promise>; @@ -20,8 +21,9 @@ Bootstraps AppKit with the provided configuration. | Parameter | Type | | ------ | ------ | -| `config` | \{ `cache?`: [`CacheConfig`](Interface.CacheConfig.md); `plugins?`: `T`; `telemetry?`: [`TelemetryConfig`](Interface.TelemetryConfig.md); \} | +| `config` | \{ `cache?`: [`CacheConfig`](Interface.CacheConfig.md); `client?`: `WorkspaceClient`; `plugins?`: `T`; `telemetry?`: [`TelemetryConfig`](Interface.TelemetryConfig.md); \} | | `config.cache?` | [`CacheConfig`](Interface.CacheConfig.md) | +| `config.client?` | `WorkspaceClient` | | `config.plugins?` | `T` | | `config.telemetry?` | [`TelemetryConfig`](Interface.TelemetryConfig.md) | diff --git a/packages/appkit/src/context/service-context.ts b/packages/appkit/src/context/service-context.ts index 0304196..ee4b245 100644 --- a/packages/appkit/src/context/service-context.ts +++ b/packages/appkit/src/context/service-context.ts @@ -57,8 +57,13 @@ export class ServiceContext { /** * Initialize the service context. Should be called once at app startup. * Safe to call multiple times - will return the same instance. + * + * @param client - Optional pre-configured WorkspaceClient to use instead + * of creating one from environment credentials. */ - static async initialize(): Promise { + static async initialize( + client?: WorkspaceClient, + ): Promise { if (ServiceContext.instance) { return ServiceContext.instance; } @@ -67,7 +72,7 @@ export class ServiceContext { return ServiceContext.initPromise; } - ServiceContext.initPromise = ServiceContext.createContext(); + ServiceContext.initPromise = ServiceContext.createContext(client); ServiceContext.instance = await ServiceContext.initPromise; return ServiceContext.instance; } @@ -147,19 +152,21 @@ export class ServiceContext { return getClientOptions(); } - private static async createContext(): Promise { - const client = new WorkspaceClient({}, getClientOptions()); + private static async createContext( + client?: WorkspaceClient, + ): Promise { + const wsClient = client ?? new WorkspaceClient({}, getClientOptions()); - const warehouseId = ServiceContext.getWarehouseId(client); - const workspaceId = ServiceContext.getWorkspaceId(client); - const currentUser = await client.currentUser.me(); + const warehouseId = ServiceContext.getWarehouseId(wsClient); + const workspaceId = ServiceContext.getWorkspaceId(wsClient); + const currentUser = await wsClient.currentUser.me(); if (!currentUser.id) { throw ConfigurationError.resourceNotFound("Service user ID"); } return { - client, + client: wsClient, serviceUserId: currentUser.id, warehouseId, workspaceId, diff --git a/packages/appkit/src/core/appkit.ts b/packages/appkit/src/core/appkit.ts index c34588e..ed226b3 100644 --- a/packages/appkit/src/core/appkit.ts +++ b/packages/appkit/src/core/appkit.ts @@ -1,3 +1,4 @@ +import type { WorkspaceClient } from "@databricks/sdk-experimental"; import type { BasePlugin, CacheConfig, @@ -141,6 +142,7 @@ export class AppKit { plugins?: T; telemetry?: TelemetryConfig; cache?: CacheConfig; + client?: WorkspaceClient; } = {}, ): Promise> { // Initialize core services @@ -149,7 +151,7 @@ export class AppKit { // Initialize ServiceContext for Databricks client management // This provides the service principal client and shared resources - await ServiceContext.initialize(); + await ServiceContext.initialize(config?.client); const rawPlugins = config.plugins as T; const preparedPlugins = AppKit.preparePlugins(rawPlugins); @@ -188,6 +190,7 @@ export async function createApp< plugins?: T; telemetry?: TelemetryConfig; cache?: CacheConfig; + client?: WorkspaceClient; } = {}, ): Promise> { return AppKit._createApp(config); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b9b2bca..587a1d2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -136,6 +136,9 @@ importers: specifier: ^4.1.13 version: 4.1.13 devDependencies: + '@playwright/test': + specifier: ^1.58.1 + version: 1.58.1 '@types/node': specifier: ^20.0.0 version: 20.19.21 @@ -1272,6 +1275,10 @@ packages: resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} engines: {node: '>=6.9.0'} + '@babel/runtime@7.28.6': + resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} + engines: {node: '>=6.9.0'} + '@babel/template@7.27.2': resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} @@ -1975,9 +1982,15 @@ packages: '@emnapi/core@1.7.1': resolution: {integrity: sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==} + '@emnapi/core@1.8.1': + resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==} + '@emnapi/runtime@1.7.1': resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==} + '@emnapi/runtime@1.8.1': + resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} + '@emnapi/wasi-threads@1.1.0': resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} @@ -3003,8 +3016,8 @@ packages: resolution: {integrity: sha512-Z7x2dZOmznihvdvCvLKMl+nswtOSVxS2H2ocar+U9xx6iMfTp0VGIrX6a4xB1v80IwOPC7dT1LXIJrY70Xu3Jw==} engines: {node: ^20.19.0 || >=22.12.0} - '@oxc-project/types@0.110.0': - resolution: {integrity: sha512-6Ct21OIlrEnFEJk5LT4e63pk3btsI6/TusD/GStLi7wYlGJNOl1GI9qvXAnRAxQU9zqA2Oz+UwhfTOU2rPZVow==} + '@oxc-project/types@0.112.0': + resolution: {integrity: sha512-m6RebKHIRsax2iCwVpYW2ErQwa4ywHJrE4sCK3/8JK8ZZAWOKXaRJFl/uP51gaVyyXlaS4+chU1nSCdzYf6QqQ==} '@oxc-project/types@0.93.0': resolution: {integrity: sha512-yNtwmWZIBtJsMr5TEfoZFDxIWV6OdScOpza/f5YxbqUMJk+j6QX3Cf3jgZShGEFYWQJ5j9mJ6jM0tZHu2J9Yrg==} @@ -3017,6 +3030,11 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@playwright/test@1.58.1': + resolution: {integrity: sha512-6LdVIUERWxQMmUSSQi0I53GgCBYgM2RpGngCPY7hSeju+VrKjq3lvs7HpJoPbDiY5QM5EYRtRX5fvrinnMAz3w==} + engines: {node: '>=18'} + hasBin: true + '@pnpm/config.env-replace@1.1.0': resolution: {integrity: sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==} engines: {node: '>=12.22.0'} @@ -3724,8 +3742,8 @@ packages: cpu: [arm64] os: [android] - '@rolldown/binding-android-arm64@1.0.0-rc.1': - resolution: {integrity: sha512-He6ZoCfv5D7dlRbrhNBkuMVIHd0GDnjJwbICE1OWpG7G3S2gmJ+eXkcNLJjzjNDpeI2aRy56ou39AJM9AD8YFA==} + '@rolldown/binding-android-arm64@1.0.0-rc.3': + resolution: {integrity: sha512-0T1k9FinuBZ/t7rZ8jN6OpUKPnUjNdYHoj/cESWrQ3ZraAJ4OMm6z7QjSfCxqj8mOp9kTKc1zHK3kGz5vMu+nQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] @@ -3736,8 +3754,8 @@ packages: cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-arm64@1.0.0-rc.1': - resolution: {integrity: sha512-YzJdn08kSOXnj85ghHauH2iHpOJ6eSmstdRTLyaziDcUxe9SyQJgGyx/5jDIhDvtOcNvMm2Ju7m19+S/Rm1jFg==} + '@rolldown/binding-darwin-arm64@1.0.0-rc.3': + resolution: {integrity: sha512-JWWLzvcmc/3pe7qdJqPpuPk91SoE/N+f3PcWx/6ZwuyDVyungAEJPvKm/eEldiDdwTmaEzWfIR+HORxYWrCi1A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] @@ -3748,8 +3766,8 @@ packages: cpu: [x64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.0-rc.1': - resolution: {integrity: sha512-cIvAbqM+ZVV6lBSKSBtlNqH5iCiW933t1q8j0H66B3sjbe8AxIRetVqfGgcHcJtMzBIkIALlL9fcDrElWLJQcQ==} + '@rolldown/binding-darwin-x64@1.0.0-rc.3': + resolution: {integrity: sha512-MTakBxfx3tde5WSmbHxuqlDsIW0EzQym+PJYGF4P6lG2NmKzi128OGynoFUqoD5ryCySEY85dug4v+LWGBElIw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] @@ -3760,8 +3778,8 @@ packages: cpu: [x64] os: [freebsd] - '@rolldown/binding-freebsd-x64@1.0.0-rc.1': - resolution: {integrity: sha512-rVt+B1B/qmKwCl1XD02wKfgh3vQPXRXdB/TicV2w6g7RVAM1+cZcpigwhLarqiVCxDObFZ7UgXCxPC7tpDoRog==} + '@rolldown/binding-freebsd-x64@1.0.0-rc.3': + resolution: {integrity: sha512-jje3oopyOLs7IwfvXoS6Lxnmie5JJO7vW29fdGFu5YGY1EDbVDhD+P9vDihqS5X6fFiqL3ZQZCMBg6jyHkSVww==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] @@ -3772,8 +3790,8 @@ packages: cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.1': - resolution: {integrity: sha512-69YKwJJBOFprQa1GktPgbuBOfnn+EGxu8sBJ1TjPER+zhSpYeaU4N07uqmyBiksOLGXsMegymuecLobfz03h8Q==} + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.3': + resolution: {integrity: sha512-A0n8P3hdLAaqzSFrQoA42p23ZKBYQOw+8EH5r15Sa9X1kD9/JXe0YT2gph2QTWvdr0CVK2BOXiK6ENfy6DXOag==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] @@ -3784,8 +3802,8 @@ packages: cpu: [arm64] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.1': - resolution: {integrity: sha512-9JDhHUf3WcLfnViFWm+TyorqUtnSAHaCzlSNmMOq824prVuuzDOK91K0Hl8DUcEb9M5x2O+d2/jmBMsetRIn3g==} + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.3': + resolution: {integrity: sha512-kWXkoxxarYISBJ4bLNf5vFkEbb4JvccOwxWDxuK9yee8lg5XA7OpvlTptfRuwEvYcOZf+7VS69Uenpmpyo5Bjw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] @@ -3796,8 +3814,8 @@ packages: cpu: [arm64] os: [linux] - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.1': - resolution: {integrity: sha512-UvApLEGholmxw/HIwmUnLq3CwdydbhaHHllvWiCTNbyGom7wTwOtz5OAQbAKZYyiEOeIXZNPkM7nA4Dtng7CLw==} + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.3': + resolution: {integrity: sha512-Z03/wrqau9Bicfgb3Dbs6SYTHliELk2PM2LpG2nFd+cGupTMF5kanLEcj2vuuJLLhptNyS61rtk7SOZ+lPsTUA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] @@ -3808,8 +3826,8 @@ packages: cpu: [x64] os: [linux] - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.1': - resolution: {integrity: sha512-uVctNgZHiGnJx5Fij7wHLhgw4uyZBVi6mykeWKOqE7bVy9Hcxn0fM/IuqdMwk6hXlaf9fFShDTFz2+YejP+x0A==} + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.3': + resolution: {integrity: sha512-iSXXZsQp08CSilff/DCTFZHSVEpEwdicV3W8idHyrByrcsRDVh9sGC3sev6d8BygSGj3vt8GvUKBPCoyMA4tgQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] @@ -3820,8 +3838,8 @@ packages: cpu: [x64] os: [linux] - '@rolldown/binding-linux-x64-musl@1.0.0-rc.1': - resolution: {integrity: sha512-T6Eg0xWwcxd/MzBcuv4Z37YVbUbJxy5cMNnbIt/Yr99wFwli30O4BPlY8hKeGyn6lWNtU0QioBS46lVzDN38bg==} + '@rolldown/binding-linux-x64-musl@1.0.0-rc.3': + resolution: {integrity: sha512-qaj+MFudtdCv9xZo9znFvkgoajLdc+vwf0Kz5N44g+LU5XMe+IsACgn3UG7uTRlCCvhMAGXm1XlpEA5bZBrOcw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] @@ -3832,8 +3850,8 @@ packages: cpu: [arm64] os: [openharmony] - '@rolldown/binding-openharmony-arm64@1.0.0-rc.1': - resolution: {integrity: sha512-PuGZVS2xNJyLADeh2F04b+Cz4NwvpglbtWACgrDOa5YDTEHKwmiTDjoD5eZ9/ptXtcpeFrMqD2H4Zn33KAh1Eg==} + '@rolldown/binding-openharmony-arm64@1.0.0-rc.3': + resolution: {integrity: sha512-U662UnMETyjT65gFmG9ma+XziENrs7BBnENi/27swZPYagubfHRirXHG2oMl+pEax2WvO7Kb9gHZmMakpYqBHQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] @@ -3843,8 +3861,8 @@ packages: engines: {node: '>=14.0.0'} cpu: [wasm32] - '@rolldown/binding-wasm32-wasi@1.0.0-rc.1': - resolution: {integrity: sha512-2mOxY562ihHlz9lEXuaGEIDCZ1vI+zyFdtsoa3M62xsEunDXQE+DVPO4S4x5MPK9tKulG/aFcA/IH5eVN257Cw==} + '@rolldown/binding-wasm32-wasi@1.0.0-rc.3': + resolution: {integrity: sha512-gekrQ3Q2HiC1T5njGyuUJoGpK/l6B/TNXKed3fZXNf9YRTJn3L5MOZsFBn4bN2+UX+8+7hgdlTcEsexX988G4g==} engines: {node: '>=14.0.0'} cpu: [wasm32] @@ -3854,8 +3872,8 @@ packages: cpu: [arm64] os: [win32] - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.1': - resolution: {integrity: sha512-oQVOP5cfAWZwRD0Q3nGn/cA9FW3KhMMuQ0NIndALAe6obqjLhqYVYDiGGRGrxvnjJsVbpLwR14gIUYnpIcHR1g==} + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.3': + resolution: {integrity: sha512-85y5JifyMgs8m5K2XzR/VDsapKbiFiohl7s5lEj7nmNGO0pkTXE7q6TQScei96BNAsoK7JC3pA7ukA8WRHVJpg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] @@ -3872,8 +3890,8 @@ packages: cpu: [x64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.1': - resolution: {integrity: sha512-Ydsxxx++FNOuov3wCBPaYjZrEvKOOGq3k+BF4BPridhg2pENfitSRD2TEuQ8i33bp5VptuNdC9IzxRKU031z5A==} + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.3': + resolution: {integrity: sha512-a4VUQZH7LxGbUJ3qJ/TzQG8HxdHvf+jOnqf7B7oFx1TEBm+j2KNL2zr5SQ7wHkNAcaPevF6gf9tQnVBnC4mD+A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -3887,8 +3905,8 @@ packages: '@rolldown/pluginutils@1.0.0-beta.47': resolution: {integrity: sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==} - '@rolldown/pluginutils@1.0.0-rc.1': - resolution: {integrity: sha512-UTBjtTxVOhodhzFVp/ayITaTETRHPUPYZPXQe0WU0wOgxghMojXxYjOiPOauKIYNWJAWS2fd7gJgGQK8GU8vDA==} + '@rolldown/pluginutils@1.0.0-rc.3': + resolution: {integrity: sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==} '@rollup/rollup-android-arm-eabi@4.52.4': resolution: {integrity: sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==} @@ -6725,6 +6743,11 @@ packages: fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -6837,15 +6860,17 @@ packages: glob@10.4.5: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@10.5.0: resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me global-directory@4.0.1: resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==} @@ -8754,6 +8779,16 @@ packages: pkg-types@2.3.0: resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + playwright-core@1.58.1: + resolution: {integrity: sha512-bcWzOaTxcW+VOOGBCQgnaKToLJ65d6AqfLVKEWvexyS3AS6rbXl+xdpYRMGSRBClPvyj44njOWoxjNdL/H9UNg==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.58.1: + resolution: {integrity: sha512-+2uTZHxSCcxjvGc5C891LrS1/NlxglGxzrC4seZiVjcYVQfUa87wBL6rTDqzGjuoWNjnBzRqKmF6zRYGMvQUaQ==} + engines: {node: '>=18'} + hasBin: true + plop@4.0.4: resolution: {integrity: sha512-YdxtHWcPV8hDsszVPr4VQBVGNdn5ZQmEW+cZakZkuVeQHtENmrtY4AhuyoZW6s7ZjpmrS+llLQrfDgRKNQNsmg==} engines: {node: '>=18'} @@ -9709,8 +9744,8 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - rolldown@1.0.0-rc.1: - resolution: {integrity: sha512-M3AeZjYE6UclblEf531Hch0WfVC/NOL43Cc+WdF3J50kk5/fvouHhDumSGTh0oRjbZ8C4faaVr5r6Nx1xMqDGg==} + rolldown@1.0.0-rc.3: + resolution: {integrity: sha512-Po/YZECDOqVXjIXrtC5h++a5NLvKAQNrd9ggrIG3sbDfGO5BqTUsrI6l8zdniKRp3r5Tp/2JTrXqx4GIguFCMw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -10900,6 +10935,7 @@ packages: whatwg-encoding@3.1.1: resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} engines: {node: '>=18'} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation whatwg-mimetype@4.0.0: resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} @@ -12078,6 +12114,8 @@ snapshots: '@babel/runtime@7.28.4': {} + '@babel/runtime@7.28.6': {} + '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 @@ -13427,11 +13465,22 @@ snapshots: tslib: 2.8.1 optional: true + '@emnapi/core@1.8.1': + dependencies: + '@emnapi/wasi-threads': 1.1.0 + tslib: 2.8.1 + optional: true + '@emnapi/runtime@1.7.1': dependencies: tslib: 2.8.1 optional: true + '@emnapi/runtime@1.8.1': + dependencies: + tslib: 2.8.1 + optional: true + '@emnapi/wasi-threads@1.1.0': dependencies: tslib: 2.8.1 @@ -13899,8 +13948,8 @@ snapshots: '@napi-rs/wasm-runtime@1.1.1': dependencies: - '@emnapi/core': 1.7.1 - '@emnapi/runtime': 1.7.1 + '@emnapi/core': 1.8.1 + '@emnapi/runtime': 1.8.1 '@tybys/wasm-util': 0.10.1 optional: true @@ -14652,7 +14701,7 @@ snapshots: '@oxc-project/runtime@0.92.0': {} - '@oxc-project/types@0.110.0': {} + '@oxc-project/types@0.112.0': {} '@oxc-project/types@0.93.0': {} @@ -14661,6 +14710,10 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@playwright/test@1.58.1': + dependencies: + playwright: 1.58.1 + '@pnpm/config.env-replace@1.1.0': {} '@pnpm/network.ca-file@1.0.2': @@ -15396,61 +15449,61 @@ snapshots: '@rolldown/binding-android-arm64@1.0.0-beta.41': optional: true - '@rolldown/binding-android-arm64@1.0.0-rc.1': + '@rolldown/binding-android-arm64@1.0.0-rc.3': optional: true '@rolldown/binding-darwin-arm64@1.0.0-beta.41': optional: true - '@rolldown/binding-darwin-arm64@1.0.0-rc.1': + '@rolldown/binding-darwin-arm64@1.0.0-rc.3': optional: true '@rolldown/binding-darwin-x64@1.0.0-beta.41': optional: true - '@rolldown/binding-darwin-x64@1.0.0-rc.1': + '@rolldown/binding-darwin-x64@1.0.0-rc.3': optional: true '@rolldown/binding-freebsd-x64@1.0.0-beta.41': optional: true - '@rolldown/binding-freebsd-x64@1.0.0-rc.1': + '@rolldown/binding-freebsd-x64@1.0.0-rc.3': optional: true '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.41': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.1': + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.3': optional: true '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.41': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.1': + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.3': optional: true '@rolldown/binding-linux-arm64-musl@1.0.0-beta.41': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.1': + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.3': optional: true '@rolldown/binding-linux-x64-gnu@1.0.0-beta.41': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.1': + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.3': optional: true '@rolldown/binding-linux-x64-musl@1.0.0-beta.41': optional: true - '@rolldown/binding-linux-x64-musl@1.0.0-rc.1': + '@rolldown/binding-linux-x64-musl@1.0.0-rc.3': optional: true '@rolldown/binding-openharmony-arm64@1.0.0-beta.41': optional: true - '@rolldown/binding-openharmony-arm64@1.0.0-rc.1': + '@rolldown/binding-openharmony-arm64@1.0.0-rc.3': optional: true '@rolldown/binding-wasm32-wasi@1.0.0-beta.41': @@ -15458,7 +15511,7 @@ snapshots: '@napi-rs/wasm-runtime': 1.0.7 optional: true - '@rolldown/binding-wasm32-wasi@1.0.0-rc.1': + '@rolldown/binding-wasm32-wasi@1.0.0-rc.3': dependencies: '@napi-rs/wasm-runtime': 1.1.1 optional: true @@ -15466,7 +15519,7 @@ snapshots: '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.41': optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.1': + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.3': optional: true '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.41': @@ -15475,7 +15528,7 @@ snapshots: '@rolldown/binding-win32-x64-msvc@1.0.0-beta.41': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.1': + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.3': optional: true '@rolldown/pluginutils@1.0.0-beta.38': {} @@ -15484,7 +15537,7 @@ snapshots: '@rolldown/pluginutils@1.0.0-beta.47': {} - '@rolldown/pluginutils@1.0.0-rc.1': {} + '@rolldown/pluginutils@1.0.0-rc.3': {} '@rollup/rollup-android-arm-eabi@4.52.4': optional: true @@ -15615,7 +15668,7 @@ snapshots: '@slorber/react-helmet-async@1.3.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.28.6 invariant: 2.2.4 prop-types: 15.8.1 react: 19.2.0 @@ -18035,7 +18088,7 @@ snapshots: dom-helpers@5.2.1: dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.28.6 csstype: 3.1.3 dom-serializer@1.4.1: @@ -18654,6 +18707,9 @@ snapshots: fs.realpath@1.0.0: {} + fsevents@2.3.2: + optional: true + fsevents@2.3.3: optional: true @@ -19212,7 +19268,7 @@ snapshots: history@4.10.1: dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.28.6 loose-envify: 1.4.0 resolve-pathname: 3.0.0 tiny-invariant: 1.3.3 @@ -21177,6 +21233,14 @@ snapshots: exsolve: 1.0.8 pathe: 2.0.3 + playwright-core@1.58.1: {} + + playwright@1.58.1: + dependencies: + playwright-core: 1.58.1 + optionalDependencies: + fsevents: 2.3.2 + plop@4.0.4(@types/node@24.7.2): dependencies: '@types/liftoff': 4.0.3 @@ -21922,7 +21986,7 @@ snapshots: react-transition-group@4.4.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.28.6 dom-helpers: 5.2.1 loose-envify: 1.4.0 prop-types: 15.8.1 @@ -22254,7 +22318,7 @@ snapshots: robust-predicates@3.0.2: {} - rolldown-plugin-dts@0.16.11(rolldown@1.0.0-rc.1)(typescript@5.9.3): + rolldown-plugin-dts@0.16.11(rolldown@1.0.0-rc.3)(typescript@5.9.3): dependencies: '@babel/generator': 7.28.3 '@babel/parser': 7.28.5 @@ -22265,7 +22329,7 @@ snapshots: dts-resolver: 2.1.2 get-tsconfig: 4.12.0 magic-string: 0.30.19 - rolldown: 1.0.0-rc.1 + rolldown: 1.0.0-rc.3 optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: @@ -22329,24 +22393,24 @@ snapshots: '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.41 '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.41 - rolldown@1.0.0-rc.1: + rolldown@1.0.0-rc.3: dependencies: - '@oxc-project/types': 0.110.0 - '@rolldown/pluginutils': 1.0.0-rc.1 + '@oxc-project/types': 0.112.0 + '@rolldown/pluginutils': 1.0.0-rc.3 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 + '@rolldown/binding-android-arm64': 1.0.0-rc.3 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.3 + '@rolldown/binding-darwin-x64': 1.0.0-rc.3 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.3 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.3 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.3 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.3 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.3 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.3 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.3 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.3 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.3 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.3 rollup@4.52.4: dependencies: @@ -22996,8 +23060,8 @@ snapshots: diff: 8.0.2 empathic: 2.0.0 hookable: 5.5.3 - rolldown: 1.0.0-rc.1 - rolldown-plugin-dts: 0.16.11(rolldown@1.0.0-rc.1)(typescript@5.9.3) + rolldown: 1.0.0-rc.3 + rolldown-plugin-dts: 0.16.11(rolldown@1.0.0-rc.3)(typescript@5.9.3) semver: 7.7.3 tinyexec: 1.0.1 tinyglobby: 0.2.15