From b6aa582c8e453b9924ec738a09ac8a569ec091c7 Mon Sep 17 00:00:00 2001 From: Subhash Khileri Date: Thu, 15 Jan 2026 18:05:51 +0530 Subject: [PATCH 1/3] docs: Add comprehensive VitePress documentation Create complete documentation site for rhdh-e2e-test-utils using VitePress: - Guide section: installation, quick-start, core concepts, deployment, helpers, page objects, utilities, and configuration - API reference: detailed method signatures and types for all exports - Tutorials: step-by-step guides for common use cases - Examples: copy-paste ready code examples Additional features: - GitHub Actions workflow for automatic deployment to GitHub Pages - Standalone docs package with its own package.json and yarn.lock - Reusable snippets to reduce duplication - CLAUDE.md guide for documentation maintenance Co-Authored-By: Claude Opus 4.5 --- .github/workflows/deploy-docs.yml | 61 + .gitignore | 4 + docs/.vitepress/config.ts | 350 +++ docs/CLAUDE.md | 212 ++ docs/api/deployment/deployment-types.md | 121 + docs/api/deployment/keycloak-helper.md | 217 ++ docs/api/deployment/keycloak-types.md | 159 ++ docs/api/deployment/rhdh-deployment.md | 161 ++ docs/api/eslint/create-eslint-config.md | 57 + docs/api/helpers/api-helper.md | 143 + docs/api/helpers/login-helper.md | 77 + docs/api/helpers/ui-helper.md | 169 ++ docs/api/index.md | 56 + docs/api/pages/catalog-import-page.md | 150 ++ docs/api/pages/catalog-page.md | 136 + docs/api/pages/extensions-page.md | 212 ++ docs/api/pages/home-page.md | 110 + docs/api/pages/notification-page.md | 244 ++ docs/api/playwright/base-config.md | 134 + docs/api/playwright/global-setup.md | 81 + docs/api/playwright/test-fixtures.md | 116 + docs/api/utils/bash.md | 58 + docs/api/utils/common.md | 47 + docs/api/utils/kubernetes-client.md | 101 + docs/api/utils/merge-yamls.md | 54 + docs/changelog.md | 70 + docs/examples/api-operations.md | 165 ++ docs/examples/basic-test.md | 100 + docs/examples/catalog-operations.md | 136 + docs/examples/custom-deployment.md | 172 ++ docs/examples/guest-auth-test.md | 59 + docs/examples/index.md | 66 + docs/examples/keycloak-auth-test.md | 127 + docs/examples/serial-tests.md | 185 ++ docs/guide/configuration/config-files.md | 166 ++ .../configuration/environment-variables.md | 130 + docs/guide/configuration/eslint-config.md | 130 + docs/guide/configuration/index.md | 69 + docs/guide/configuration/typescript-config.md | 111 + docs/guide/core-concepts/architecture.md | 325 +++ docs/guide/core-concepts/error-handling.md | 298 +++ docs/guide/core-concepts/global-setup.md | 175 ++ docs/guide/core-concepts/index.md | 81 + docs/guide/core-concepts/package-exports.md | 168 ++ docs/guide/core-concepts/playwright-config.md | 220 ++ .../core-concepts/playwright-fixtures.md | 198 ++ docs/guide/core-concepts/testing-patterns.md | 282 ++ docs/guide/deployment/authentication.md | 220 ++ docs/guide/deployment/helm-deployment.md | 244 ++ docs/guide/deployment/index.md | 168 ++ docs/guide/deployment/keycloak-deployment.md | 304 +++ docs/guide/deployment/operator-deployment.md | 242 ++ docs/guide/deployment/rhdh-deployment.md | 241 ++ docs/guide/helpers/api-helper.md | 264 ++ docs/guide/helpers/index.md | 140 + docs/guide/helpers/login-helper.md | 242 ++ docs/guide/helpers/ui-helper.md | 301 +++ docs/guide/index.md | 74 + docs/guide/installation.md | 116 + .../guide/page-objects/catalog-import-page.md | 114 + docs/guide/page-objects/catalog-page.md | 105 + docs/guide/page-objects/extensions-page.md | 94 + docs/guide/page-objects/home-page.md | 60 + docs/guide/page-objects/index.md | 109 + docs/guide/page-objects/notification-page.md | 134 + docs/guide/quick-start.md | 195 ++ docs/guide/requirements.md | 141 + docs/guide/utilities/bash-utilities.md | 73 + .../utilities/environment-substitution.md | 115 + docs/guide/utilities/index.md | 77 + docs/guide/utilities/kubernetes-client.md | 146 + docs/guide/utilities/yaml-merging.md | 100 + docs/index.md | 84 + docs/package.json | 18 + docs/snippets/app-config-rhdh.yaml | 15 + docs/snippets/basic-test.ts | 13 + docs/snippets/env-example.env | 17 + docs/snippets/error-handling.ts | 45 + docs/snippets/global-setup.ts | 15 + docs/snippets/guest-test.ts | 12 + docs/snippets/keycloak-credentials.md | 10 + docs/snippets/keycloak-test.ts | 13 + docs/snippets/playwright-config.ts | 13 + docs/snippets/serial-vs-parallel.ts | 63 + docs/tutorials/ci-cd-integration.md | 179 ++ docs/tutorials/custom-page-objects.md | 178 ++ docs/tutorials/first-test.md | 166 ++ docs/tutorials/index.md | 33 + docs/tutorials/keycloak-oidc-testing.md | 179 ++ docs/tutorials/multi-project-setup.md | 170 ++ docs/tutorials/plugin-testing.md | 236 ++ docs/yarn.lock | 2345 +++++++++++++++++ package.json | 8 +- yarn.lock | 1779 ++++++++++++- 94 files changed, 16268 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/deploy-docs.yml create mode 100644 docs/.vitepress/config.ts create mode 100644 docs/CLAUDE.md create mode 100644 docs/api/deployment/deployment-types.md create mode 100644 docs/api/deployment/keycloak-helper.md create mode 100644 docs/api/deployment/keycloak-types.md create mode 100644 docs/api/deployment/rhdh-deployment.md create mode 100644 docs/api/eslint/create-eslint-config.md create mode 100644 docs/api/helpers/api-helper.md create mode 100644 docs/api/helpers/login-helper.md create mode 100644 docs/api/helpers/ui-helper.md create mode 100644 docs/api/index.md create mode 100644 docs/api/pages/catalog-import-page.md create mode 100644 docs/api/pages/catalog-page.md create mode 100644 docs/api/pages/extensions-page.md create mode 100644 docs/api/pages/home-page.md create mode 100644 docs/api/pages/notification-page.md create mode 100644 docs/api/playwright/base-config.md create mode 100644 docs/api/playwright/global-setup.md create mode 100644 docs/api/playwright/test-fixtures.md create mode 100644 docs/api/utils/bash.md create mode 100644 docs/api/utils/common.md create mode 100644 docs/api/utils/kubernetes-client.md create mode 100644 docs/api/utils/merge-yamls.md create mode 100644 docs/changelog.md create mode 100644 docs/examples/api-operations.md create mode 100644 docs/examples/basic-test.md create mode 100644 docs/examples/catalog-operations.md create mode 100644 docs/examples/custom-deployment.md create mode 100644 docs/examples/guest-auth-test.md create mode 100644 docs/examples/index.md create mode 100644 docs/examples/keycloak-auth-test.md create mode 100644 docs/examples/serial-tests.md create mode 100644 docs/guide/configuration/config-files.md create mode 100644 docs/guide/configuration/environment-variables.md create mode 100644 docs/guide/configuration/eslint-config.md create mode 100644 docs/guide/configuration/index.md create mode 100644 docs/guide/configuration/typescript-config.md create mode 100644 docs/guide/core-concepts/architecture.md create mode 100644 docs/guide/core-concepts/error-handling.md create mode 100644 docs/guide/core-concepts/global-setup.md create mode 100644 docs/guide/core-concepts/index.md create mode 100644 docs/guide/core-concepts/package-exports.md create mode 100644 docs/guide/core-concepts/playwright-config.md create mode 100644 docs/guide/core-concepts/playwright-fixtures.md create mode 100644 docs/guide/core-concepts/testing-patterns.md create mode 100644 docs/guide/deployment/authentication.md create mode 100644 docs/guide/deployment/helm-deployment.md create mode 100644 docs/guide/deployment/index.md create mode 100644 docs/guide/deployment/keycloak-deployment.md create mode 100644 docs/guide/deployment/operator-deployment.md create mode 100644 docs/guide/deployment/rhdh-deployment.md create mode 100644 docs/guide/helpers/api-helper.md create mode 100644 docs/guide/helpers/index.md create mode 100644 docs/guide/helpers/login-helper.md create mode 100644 docs/guide/helpers/ui-helper.md create mode 100644 docs/guide/index.md create mode 100644 docs/guide/installation.md create mode 100644 docs/guide/page-objects/catalog-import-page.md create mode 100644 docs/guide/page-objects/catalog-page.md create mode 100644 docs/guide/page-objects/extensions-page.md create mode 100644 docs/guide/page-objects/home-page.md create mode 100644 docs/guide/page-objects/index.md create mode 100644 docs/guide/page-objects/notification-page.md create mode 100644 docs/guide/quick-start.md create mode 100644 docs/guide/requirements.md create mode 100644 docs/guide/utilities/bash-utilities.md create mode 100644 docs/guide/utilities/environment-substitution.md create mode 100644 docs/guide/utilities/index.md create mode 100644 docs/guide/utilities/kubernetes-client.md create mode 100644 docs/guide/utilities/yaml-merging.md create mode 100644 docs/index.md create mode 100644 docs/package.json create mode 100644 docs/snippets/app-config-rhdh.yaml create mode 100644 docs/snippets/basic-test.ts create mode 100644 docs/snippets/env-example.env create mode 100644 docs/snippets/error-handling.ts create mode 100644 docs/snippets/global-setup.ts create mode 100644 docs/snippets/guest-test.ts create mode 100644 docs/snippets/keycloak-credentials.md create mode 100644 docs/snippets/keycloak-test.ts create mode 100644 docs/snippets/playwright-config.ts create mode 100644 docs/snippets/serial-vs-parallel.ts create mode 100644 docs/tutorials/ci-cd-integration.md create mode 100644 docs/tutorials/custom-page-objects.md create mode 100644 docs/tutorials/first-test.md create mode 100644 docs/tutorials/index.md create mode 100644 docs/tutorials/keycloak-oidc-testing.md create mode 100644 docs/tutorials/multi-project-setup.md create mode 100644 docs/tutorials/plugin-testing.md create mode 100644 docs/yarn.lock diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml new file mode 100644 index 0000000..888042e --- /dev/null +++ b/.github/workflows/deploy-docs.yml @@ -0,0 +1,61 @@ +name: Deploy Documentation + +on: + push: + branches: + - main + paths: + - 'docs/**' + - '.github/workflows/deploy-docs.yml' + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + defaults: + run: + working-directory: docs + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Setup Pages + uses: actions/configure-pages@v5 + + - name: Install dependencies + run: yarn install + + - name: Build documentation + run: yarn build + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: docs/.vitepress/dist + + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + needs: build + runs-on: ubuntu-latest + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index bd66a4f..c0a32f3 100644 --- a/.gitignore +++ b/.gitignore @@ -53,6 +53,10 @@ build/ *.tsbuildinfo dist/ +# VitePress +docs/.vitepress/cache/ +docs/.vitepress/dist/ + # Coverage coverage/ .nyc_output/ diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts new file mode 100644 index 0000000..540e206 --- /dev/null +++ b/docs/.vitepress/config.ts @@ -0,0 +1,350 @@ +import { defineConfig } from "vitepress"; + +export default defineConfig({ + title: "RHDH E2E Test Utils", + description: + "Comprehensive test utilities for Red Hat Developer Hub end-to-end testing with Playwright", + base: "/rhdh-e2e-test-utils/", + + head: [ + ["meta", { name: "theme-color", content: "#ee0000" }], + [ + "meta", + { + name: "og:title", + content: "RHDH E2E Test Utils Documentation", + }, + ], + [ + "meta", + { + name: "og:description", + content: + "Test utilities for Red Hat Developer Hub E2E testing with Playwright", + }, + ], + ], + + themeConfig: { + nav: [ + { text: "Guide", link: "/guide/" }, + { text: "API Reference", link: "/api/" }, + { text: "Tutorials", link: "/tutorials/" }, + { text: "Examples", link: "/examples/" }, + { + text: "v1.1.2", + items: [{ text: "Changelog", link: "/changelog" }], + }, + ], + + sidebar: { + "/guide/": [ + { + text: "Introduction", + items: [ + { text: "Getting Started", link: "/guide/" }, + { text: "Installation", link: "/guide/installation" }, + { text: "Quick Start", link: "/guide/quick-start" }, + { text: "Requirements", link: "/guide/requirements" }, + ], + }, + { + text: "Core Concepts", + collapsed: false, + items: [ + { text: "Overview", link: "/guide/core-concepts/" }, + { + text: "Architecture", + link: "/guide/core-concepts/architecture", + }, + { + text: "Package Exports", + link: "/guide/core-concepts/package-exports", + }, + { + text: "Playwright Fixtures", + link: "/guide/core-concepts/playwright-fixtures", + }, + { + text: "Playwright Configuration", + link: "/guide/core-concepts/playwright-config", + }, + { text: "Global Setup", link: "/guide/core-concepts/global-setup" }, + { + text: "Testing Patterns", + link: "/guide/core-concepts/testing-patterns", + }, + { + text: "Error Handling", + link: "/guide/core-concepts/error-handling", + }, + ], + }, + { + text: "Deployment", + collapsed: false, + items: [ + { text: "Overview", link: "/guide/deployment/" }, + { + text: "RHDH Deployment", + link: "/guide/deployment/rhdh-deployment", + }, + { + text: "Keycloak Deployment", + link: "/guide/deployment/keycloak-deployment", + }, + { + text: "Helm Deployment", + link: "/guide/deployment/helm-deployment", + }, + { + text: "Operator Deployment", + link: "/guide/deployment/operator-deployment", + }, + { + text: "Authentication Providers", + link: "/guide/deployment/authentication", + }, + ], + }, + { + text: "Helpers", + collapsed: true, + items: [ + { text: "Overview", link: "/guide/helpers/" }, + { text: "UIhelper", link: "/guide/helpers/ui-helper" }, + { text: "LoginHelper", link: "/guide/helpers/login-helper" }, + { text: "APIHelper", link: "/guide/helpers/api-helper" }, + ], + }, + { + text: "Page Objects", + collapsed: true, + items: [ + { text: "Overview", link: "/guide/page-objects/" }, + { text: "CatalogPage", link: "/guide/page-objects/catalog-page" }, + { text: "HomePage", link: "/guide/page-objects/home-page" }, + { + text: "CatalogImportPage", + link: "/guide/page-objects/catalog-import-page", + }, + { + text: "ExtensionsPage", + link: "/guide/page-objects/extensions-page", + }, + { + text: "NotificationPage", + link: "/guide/page-objects/notification-page", + }, + ], + }, + { + text: "Utilities", + collapsed: true, + items: [ + { text: "Overview", link: "/guide/utilities/" }, + { + text: "Kubernetes Client", + link: "/guide/utilities/kubernetes-client", + }, + { text: "Bash Utilities", link: "/guide/utilities/bash-utilities" }, + { text: "YAML Merging", link: "/guide/utilities/yaml-merging" }, + { + text: "Environment Substitution", + link: "/guide/utilities/environment-substitution", + }, + ], + }, + { + text: "Configuration", + collapsed: true, + items: [ + { text: "Overview", link: "/guide/configuration/" }, + { + text: "Configuration Files", + link: "/guide/configuration/config-files", + }, + { + text: "ESLint Configuration", + link: "/guide/configuration/eslint-config", + }, + { + text: "TypeScript Configuration", + link: "/guide/configuration/typescript-config", + }, + { + text: "Environment Variables", + link: "/guide/configuration/environment-variables", + }, + ], + }, + ], + + "/api/": [ + { + text: "API Reference", + items: [{ text: "Overview", link: "/api/" }], + }, + { + text: "Deployment", + collapsed: false, + items: [ + { text: "RHDHDeployment", link: "/api/deployment/rhdh-deployment" }, + { + text: "Deployment Types", + link: "/api/deployment/deployment-types", + }, + { text: "KeycloakHelper", link: "/api/deployment/keycloak-helper" }, + { text: "Keycloak Types", link: "/api/deployment/keycloak-types" }, + ], + }, + { + text: "Playwright", + collapsed: false, + items: [ + { text: "Test Fixtures", link: "/api/playwright/test-fixtures" }, + { text: "Base Config", link: "/api/playwright/base-config" }, + { text: "Global Setup", link: "/api/playwright/global-setup" }, + ], + }, + { + text: "Helpers", + collapsed: true, + items: [ + { text: "UIhelper", link: "/api/helpers/ui-helper" }, + { text: "LoginHelper", link: "/api/helpers/login-helper" }, + { text: "APIHelper", link: "/api/helpers/api-helper" }, + ], + }, + { + text: "Page Objects", + collapsed: true, + items: [ + { text: "CatalogPage", link: "/api/pages/catalog-page" }, + { text: "HomePage", link: "/api/pages/home-page" }, + { + text: "CatalogImportPage", + link: "/api/pages/catalog-import-page", + }, + { text: "ExtensionsPage", link: "/api/pages/extensions-page" }, + { + text: "NotificationPage", + link: "/api/pages/notification-page", + }, + ], + }, + { + text: "Utilities", + collapsed: true, + items: [ + { + text: "KubernetesClientHelper", + link: "/api/utils/kubernetes-client", + }, + { text: "Bash ($)", link: "/api/utils/bash" }, + { text: "YAML Merging", link: "/api/utils/merge-yamls" }, + { text: "envsubst", link: "/api/utils/common" }, + ], + }, + { + text: "ESLint", + collapsed: true, + items: [ + { + text: "createEslintConfig", + link: "/api/eslint/create-eslint-config", + }, + ], + }, + ], + + "/tutorials/": [ + { + text: "Tutorials", + items: [ + { text: "Overview", link: "/tutorials/" }, + { text: "Your First Test", link: "/tutorials/first-test" }, + { text: "Testing a Plugin", link: "/tutorials/plugin-testing" }, + { + text: "Multi-Project Setup", + link: "/tutorials/multi-project-setup", + }, + { text: "CI/CD Integration", link: "/tutorials/ci-cd-integration" }, + { + text: "Keycloak OIDC Testing", + link: "/tutorials/keycloak-oidc-testing", + }, + { + text: "Custom Page Objects", + link: "/tutorials/custom-page-objects", + }, + ], + }, + ], + + "/examples/": [ + { + text: "Examples", + items: [ + { text: "Overview", link: "/examples/" }, + { text: "Basic Test", link: "/examples/basic-test" }, + { text: "Guest Authentication", link: "/examples/guest-auth-test" }, + { + text: "Keycloak Authentication", + link: "/examples/keycloak-auth-test", + }, + { + text: "Catalog Operations", + link: "/examples/catalog-operations", + }, + { text: "API Operations", link: "/examples/api-operations" }, + { text: "Custom Deployment", link: "/examples/custom-deployment" }, + { text: "Serial Tests", link: "/examples/serial-tests" }, + ], + }, + ], + }, + + socialLinks: [ + { + icon: "github", + link: "https://github.com/redhat-developer/rhdh-e2e-test-utils", + }, + ], + + search: { + provider: "local", + }, + + footer: { + message: "Released under the Apache-2.0 License.", + copyright: "Copyright Red Hat Developer Hub", + }, + + editLink: { + pattern: + "https://github.com/redhat-developer/rhdh-e2e-test-utils/edit/main/docs/:path", + text: "Edit this page on GitHub", + }, + + lastUpdated: { + text: "Updated at", + formatOptions: { + dateStyle: "full", + timeStyle: "medium", + }, + }, + + outline: { + level: [2, 3], + }, + }, + + markdown: { + lineNumbers: true, + theme: { + light: "github-light", + dark: "github-dark", + }, + }, +}); diff --git a/docs/CLAUDE.md b/docs/CLAUDE.md new file mode 100644 index 0000000..c415400 --- /dev/null +++ b/docs/CLAUDE.md @@ -0,0 +1,212 @@ +# Documentation Guide for Claude + +This file contains essential information for maintaining and extending the VitePress documentation for `rhdh-e2e-test-utils`. + +## Documentation Structure + +``` +docs/ +├── .vitepress/ +│ └── config.ts # VitePress configuration (nav, sidebar, theme) +├── snippets/ # Reusable code snippets (included via ) +├── guide/ # Conceptual documentation +│ ├── core-concepts/ # Architecture, fixtures, patterns +│ ├── deployment/ # RHDH, Keycloak, Helm, Operator +│ ├── helpers/ # UIhelper, LoginHelper, APIHelper +│ ├── page-objects/ # CatalogPage, HomePage, etc. +│ ├── utilities/ # Kubernetes client, bash, YAML merging +│ └── configuration/ # Config files, ESLint, TypeScript +├── api/ # API reference (method signatures, types) +│ ├── deployment/ # RHDHDeployment, KeycloakHelper +│ ├── playwright/ # Fixtures, base config, global setup +│ ├── helpers/ # Helper class APIs +│ ├── pages/ # Page object APIs +│ ├── utils/ # Utility APIs +│ └── eslint/ # ESLint config API +├── tutorials/ # Step-by-step learning guides +├── examples/ # Copy-paste ready code examples +├── index.md # Home page +└── changelog.md # Version history +``` + +## Key Conventions + +### Naming Conventions +- **Class name**: `UIhelper` (capital U, lowercase h) - matches source code +- **Fixture variable**: `uiHelper` (camelCase) - used in test fixtures +- **Method names**: Follow source exactly (e.g., `verifyTextinCard` not `verifyTextInCard`) +- **Project name in examples**: Use `"my-plugin"` consistently + +### Code Examples +- Use TypeScript for all code examples +- Include imports at the top of code blocks +- Use the fixture pattern (recommended) over direct instantiation +- Standard test structure: + ```typescript + import { test, expect } from "rhdh-e2e-test-utils/test"; + + test.describe("Feature", () => { + test.beforeAll(async ({ rhdh }) => { /* deploy */ }); + test.beforeEach(async ({ loginHelper }) => { /* login */ }); + test("should...", async ({ uiHelper }) => { /* test */ }); + }); + ``` + +### Reusable Snippets +Located in `docs/snippets/`. Include them using: +```markdown + +``` + +Available snippets: +- `playwright-config.ts` - Base Playwright config +- `env-example.env` - Environment variables +- `basic-test.ts` - Basic test template +- `keycloak-test.ts` - Keycloak auth test +- `guest-test.ts` - Guest auth test +- `global-setup.ts` - Global setup template +- `app-config-rhdh.yaml` - RHDH config template +- `keycloak-credentials.md` - Default user credentials table +- `error-handling.ts` - Error handling patterns +- `serial-vs-parallel.ts` - Testing patterns comparison + +### Cross-References +Always add "Related Pages" section at the end of documentation pages: +```markdown +## Related Pages + +- [Page Name](/path/to/page.md) - Brief description +``` + +### Guide vs API vs Tutorial vs Example + +| Section | Purpose | Content Style | +|---------|---------|---------------| +| **Guide** | Explain concepts | Detailed prose, multiple examples, best practices | +| **API** | Reference | Method signatures, parameter tables, brief examples | +| **Tutorial** | Teach step-by-step | Sequential steps, explanations, learning-focused | +| **Example** | Copy-paste code | Minimal prose, complete working code | + +## VitePress Configuration + +### Adding New Pages +1. Create the `.md` file in the appropriate directory +2. Update `docs/.vitepress/config.ts` to add to sidebar: + ```typescript + { + text: "Section Name", + items: [ + { text: "Page Title", link: "/path/to/page" }, + ], + } + ``` + +### Sidebar Structure +- `collapsed: false` - Section expanded by default +- `collapsed: true` - Section collapsed by default +- Items without `collapsed` are always visible + +## Source Code Locations + +When documenting, reference these source files: + +| Component | Source Path | +|-----------|-------------| +| UIhelper | `src/playwright/helpers/ui-helper.ts` | +| LoginHelper | `src/playwright/helpers/login-helper.ts` | +| APIHelper | `src/playwright/helpers/api-helper.ts` | +| RHDHDeployment | `src/deployment/rhdh/rhdh-deployment.ts` | +| KeycloakHelper | `src/deployment/keycloak/keycloak-helper.ts` | +| Page Objects | `src/playwright/pages/*.ts` | +| Fixtures | `src/playwright/fixtures/test.ts` | +| Base Config | `src/playwright/base-config.ts` | +| Global Setup | `src/playwright/global-setup.ts` | + +## Common Tasks + +### Update API Documentation +1. Read the source file to get accurate method signatures +2. Update both Guide (`/guide/helpers/`) and API (`/api/helpers/`) sections +3. Ensure method names match source exactly + +### Add New Feature Documentation +1. Add to Guide section with detailed explanation +2. Add to API section with method signatures +3. Add example to Examples section if applicable +4. Update Related Pages in relevant files +5. Add to sidebar in `config.ts` + +### Update Version +1. Update version in `docs/.vitepress/config.ts` nav dropdown +2. Update `docs/changelog.md` with changes + +## Standalone Documentation + +The docs are a standalone package, independent of the root project. This allows: +- Separate dependency management +- Faster CI builds (no need to install root dependencies) +- Easier local development + +### Setup + +```bash +cd docs +yarn install +``` + +### Build Commands + +Run from the `docs/` directory: + +```bash +yarn dev # Start dev server (http://localhost:5173) +yarn build # Build for production +yarn preview # Preview production build +``` + +### Package Structure + +``` +docs/ +├── package.json # Standalone dependencies (vitepress only) +├── node_modules/ # Local dependencies (gitignored) +├── .vitepress/ +│ ├── config.ts # VitePress configuration +│ ├── cache/ # Build cache (gitignored) +│ └── dist/ # Build output (gitignored) +└── ... +``` + +## GitHub Pages Deployment + +The documentation automatically deploys via `.github/workflows/deploy-docs.yml` when: +- Changes are pushed to `main` branch in `docs/` directory +- Manually triggered via workflow_dispatch + +The workflow: +1. Checks out the repository +2. Runs `yarn install` in `docs/` directory +3. Runs `yarn build` in `docs/` directory +4. Deploys `.vitepress/dist` to GitHub Pages + +Base URL is configured as `/rhdh-e2e-test-utils/` in `config.ts`. + +## Known Issues / TODOs + +- [ ] Consider adding search indexing for Algolia (currently using local search) +- [ ] Add API documentation for remaining utility functions +- [ ] Consider adding interactive code examples with StackBlitz + +## Package Exports Reference + +| Export | Import Path | Description | +|--------|-------------|-------------| +| Test fixtures | `rhdh-e2e-test-utils/test` | Main test API | +| Playwright config | `rhdh-e2e-test-utils/playwright-config` | Base config | +| RHDH deployment | `rhdh-e2e-test-utils/rhdh` | RHDHDeployment | +| Keycloak | `rhdh-e2e-test-utils/keycloak` | KeycloakHelper | +| Helpers | `rhdh-e2e-test-utils/helpers` | UIhelper, LoginHelper, etc. | +| Page objects | `rhdh-e2e-test-utils/pages` | CatalogPage, HomePage, etc. | +| Utilities | `rhdh-e2e-test-utils/utils` | KubernetesClientHelper, etc. | +| ESLint | `rhdh-e2e-test-utils/eslint` | ESLint config | +| TypeScript | `rhdh-e2e-test-utils/tsconfig` | TSConfig base | diff --git a/docs/api/deployment/deployment-types.md b/docs/api/deployment/deployment-types.md new file mode 100644 index 0000000..1f426b2 --- /dev/null +++ b/docs/api/deployment/deployment-types.md @@ -0,0 +1,121 @@ +# Deployment Types + +Type definitions for RHDH deployment configuration. + +## Import + +```typescript +import type { + DeploymentMethod, + AuthProvider, + DeploymentOptions, + DeploymentConfig, +} from "rhdh-e2e-test-utils/rhdh"; +``` + +## DeploymentMethod + +```typescript +type DeploymentMethod = "helm" | "operator"; +``` + +Installation method for RHDH. + +## AuthProvider + +```typescript +type AuthProvider = "guest" | "keycloak"; +``` + +Authentication provider configuration. + +## DeploymentOptions + +```typescript +type DeploymentOptions = { + version?: string; + namespace?: string; + auth?: AuthProvider; + appConfig?: string; + secrets?: string; + dynamicPlugins?: string; + method?: DeploymentMethod; + valueFile?: string; + subscription?: string; +}; +``` + +| Property | Type | Description | +|----------|------|-------------| +| `version` | `string` | RHDH version (e.g., "1.5") | +| `namespace` | `string` | Kubernetes namespace | +| `auth` | `AuthProvider` | Authentication provider | +| `appConfig` | `string` | Path to app-config YAML | +| `secrets` | `string` | Path to secrets YAML | +| `dynamicPlugins` | `string` | Path to plugins YAML | +| `method` | `DeploymentMethod` | Installation method | +| `valueFile` | `string` | Helm values file (Helm only) | +| `subscription` | `string` | Backstage CR file (Operator only) | + +## DeploymentConfigBase + +```typescript +type DeploymentConfigBase = { + version: string; + namespace: string; + auth: AuthProvider; + appConfig: string; + secrets: string; + dynamicPlugins: string; +}; +``` + +## HelmDeploymentConfig + +```typescript +type HelmDeploymentConfig = { + method: "helm"; + valueFile: string; +}; +``` + +## OperatorDeploymentConfig + +```typescript +type OperatorDeploymentConfig = { + method: "operator"; + subscription: string; +}; +``` + +## DeploymentConfig + +```typescript +type DeploymentConfig = DeploymentConfigBase & + (HelmDeploymentConfig | OperatorDeploymentConfig); +``` + +Combined type for full deployment configuration. + +## Example Usage + +```typescript +import { RHDHDeployment } from "rhdh-e2e-test-utils/rhdh"; +import type { DeploymentOptions } from "rhdh-e2e-test-utils/rhdh"; + +const options: DeploymentOptions = { + version: "1.5", + method: "helm", + auth: "keycloak", +}; + +const rhdh = new RHDHDeployment("my-namespace"); +await rhdh.configure(options); +await rhdh.deploy(); + +// Access typed config +const config = rhdh.deploymentConfig; +console.log(config.version); // string +console.log(config.namespace); // string +console.log(config.auth); // "guest" | "keycloak" +``` diff --git a/docs/api/deployment/keycloak-helper.md b/docs/api/deployment/keycloak-helper.md new file mode 100644 index 0000000..c56a22c --- /dev/null +++ b/docs/api/deployment/keycloak-helper.md @@ -0,0 +1,217 @@ +# KeycloakHelper + +Class for deploying and managing Keycloak in OpenShift. + +## Import + +```typescript +import { KeycloakHelper } from "rhdh-e2e-test-utils/keycloak"; +``` + +## Constructor + +```typescript +new KeycloakHelper(options?: KeycloakDeploymentOptions) +``` + +| Parameter | Type | Description | +|-----------|------|-------------| +| `options` | `KeycloakDeploymentOptions` | Optional deployment configuration | + +## Properties + +### `keycloakUrl` + +```typescript +get keycloakUrl(): string +``` + +The URL of the Keycloak instance. + +### `realm` + +```typescript +get realm(): string +``` + +Configured realm name. + +### `clientId` + +```typescript +get clientId(): string +``` + +Configured OIDC client ID. + +### `clientSecret` + +```typescript +get clientSecret(): string +``` + +Configured OIDC client secret. + +### `deploymentConfig` + +```typescript +get deploymentConfig(): KeycloakDeploymentConfig +``` + +Current deployment configuration. + +### `k8sClient` + +```typescript +get k8sClient(): KubernetesClientHelper +``` + +Kubernetes client instance. + +## Methods + +### `deploy()` + +```typescript +async deploy(): Promise +``` + +Deploy Keycloak using Bitnami Helm chart. + +### `configureForRHDH()` + +```typescript +async configureForRHDH(options?: object): Promise +``` + +Configure realm, client, groups, and users for RHDH. + +### `isRunning()` + +```typescript +async isRunning(): Promise +``` + +Check if Keycloak is accessible. + +### `connect()` + +```typescript +async connect(config: KeycloakConnectionConfig): Promise +``` + +Connect to an existing Keycloak instance. + +### `createRealm()` + +```typescript +async createRealm(config: KeycloakRealmConfig): Promise +``` + +Create a new realm. + +### `createClient()` + +```typescript +async createClient(realm: string, config: KeycloakClientConfig): Promise +``` + +Create a client in a realm. + +### `createGroup()` + +```typescript +async createGroup(realm: string, config: KeycloakGroupConfig): Promise +``` + +Create a group in a realm. + +### `createUser()` + +```typescript +async createUser(realm: string, config: KeycloakUserConfig): Promise +``` + +Create a user with optional group membership. + +### `getUsers()` + +```typescript +async getUsers(realm: string): Promise +``` + +Get all users in a realm. + +### `getGroups()` + +```typescript +async getGroups(realm: string): Promise +``` + +Get all groups in a realm. + +### `deleteUser()` + +```typescript +async deleteUser(realm: string, username: string): Promise +``` + +Delete a user. + +### `deleteGroup()` + +```typescript +async deleteGroup(realm: string, groupName: string): Promise +``` + +Delete a group. + +### `deleteRealm()` + +```typescript +async deleteRealm(realm: string): Promise +``` + +Delete a realm. + +### `teardown()` + +```typescript +async teardown(): Promise +``` + +Delete the Keycloak namespace. + +### `waitUntilReady()` + +```typescript +async waitUntilReady(timeout?: number): Promise +``` + +Wait for Keycloak StatefulSet to be ready. + +## Example + +```typescript +import { KeycloakHelper } from "rhdh-e2e-test-utils/keycloak"; + +const keycloak = new KeycloakHelper({ + namespace: "rhdh-keycloak", + adminUser: "admin", + adminPassword: "admin123", +}); + +await keycloak.deploy(); +await keycloak.configureForRHDH(); + +// Create custom user +await keycloak.createUser("rhdh", { + username: "custom-user", + password: "password123", + groups: ["developers"], +}); + +console.log(`Keycloak URL: ${keycloak.keycloakUrl}`); +console.log(`Realm: ${keycloak.realm}`); +console.log(`Client ID: ${keycloak.clientId}`); +``` diff --git a/docs/api/deployment/keycloak-types.md b/docs/api/deployment/keycloak-types.md new file mode 100644 index 0000000..879aae3 --- /dev/null +++ b/docs/api/deployment/keycloak-types.md @@ -0,0 +1,159 @@ +# Keycloak Types + +Type definitions for Keycloak configuration. + +## Import + +```typescript +import type { + KeycloakDeploymentOptions, + KeycloakDeploymentConfig, + KeycloakClientConfig, + KeycloakUserConfig, + KeycloakGroupConfig, + KeycloakRealmConfig, + KeycloakConnectionConfig, +} from "rhdh-e2e-test-utils/keycloak"; +``` + +## KeycloakDeploymentOptions + +```typescript +type KeycloakDeploymentOptions = { + namespace?: string; + releaseName?: string; + valuesFile?: string; + adminUser?: string; + adminPassword?: string; +}; +``` + +| Property | Type | Default | Description | +|----------|------|---------|-------------| +| `namespace` | `string` | `"rhdh-keycloak"` | Kubernetes namespace | +| `releaseName` | `string` | `"keycloak"` | Helm release name | +| `valuesFile` | `string` | - | Custom Helm values | +| `adminUser` | `string` | `"admin"` | Admin username | +| `adminPassword` | `string` | `"admin123"` | Admin password | + +## KeycloakDeploymentConfig + +```typescript +type KeycloakDeploymentConfig = { + namespace: string; + releaseName: string; + valuesFile: string; + adminUser: string; + adminPassword: string; +}; +``` + +## KeycloakClientConfig + +```typescript +type KeycloakClientConfig = { + clientId: string; + clientSecret: string; + name?: string; + description?: string; + redirectUris?: string[]; + webOrigins?: string[]; + standardFlowEnabled?: boolean; + implicitFlowEnabled?: boolean; + directAccessGrantsEnabled?: boolean; + serviceAccountsEnabled?: boolean; + authorizationServicesEnabled?: boolean; + publicClient?: boolean; + attributes?: Record; + defaultClientScopes?: string[]; + optionalClientScopes?: string[]; +}; +``` + +## KeycloakUserConfig + +```typescript +type KeycloakUserConfig = { + username: string; + email?: string; + firstName?: string; + lastName?: string; + enabled?: boolean; + emailVerified?: boolean; + password?: string; + temporary?: boolean; + groups?: string[]; +}; +``` + +## KeycloakGroupConfig + +```typescript +type KeycloakGroupConfig = { + name: string; +}; +``` + +## KeycloakRealmConfig + +```typescript +type KeycloakRealmConfig = { + realm: string; + displayName?: string; + enabled?: boolean; +}; +``` + +## KeycloakConnectionConfig + +```typescript +type KeycloakConnectionConfig = { + baseUrl: string; + realm?: string; + clientId?: string; + clientSecret?: string; + username?: string; + password?: string; +}; +``` + +Connect with admin credentials: +```typescript +await keycloak.connect({ + baseUrl: "https://keycloak.example.com", + username: "admin", + password: "admin-password", +}); +``` + +Connect with client credentials: +```typescript +await keycloak.connect({ + baseUrl: "https://keycloak.example.com", + realm: "master", + clientId: "admin-client", + clientSecret: "client-secret", +}); +``` + +## Example Usage + +```typescript +import { KeycloakHelper } from "rhdh-e2e-test-utils/keycloak"; +import type { KeycloakUserConfig } from "rhdh-e2e-test-utils/keycloak"; + +const keycloak = new KeycloakHelper(); +await keycloak.deploy(); +await keycloak.configureForRHDH(); + +const user: KeycloakUserConfig = { + username: "test-user", + email: "test@example.com", + firstName: "Test", + lastName: "User", + password: "password123", + groups: ["developers"], +}; + +await keycloak.createUser("rhdh", user); +``` diff --git a/docs/api/deployment/rhdh-deployment.md b/docs/api/deployment/rhdh-deployment.md new file mode 100644 index 0000000..c9fb738 --- /dev/null +++ b/docs/api/deployment/rhdh-deployment.md @@ -0,0 +1,161 @@ +# RHDHDeployment + +Class for managing RHDH deployments in OpenShift. + +## Import + +```typescript +import { RHDHDeployment } from "rhdh-e2e-test-utils/rhdh"; +``` + +## Constructor + +```typescript +new RHDHDeployment(namespace: string) +``` + +| Parameter | Type | Description | +|-----------|------|-------------| +| `namespace` | `string` | Kubernetes namespace for deployment | + +## Properties + +### `rhdhUrl` + +```typescript +get rhdhUrl(): string +``` + +The URL of the deployed RHDH instance. + +### `deploymentConfig` + +```typescript +get deploymentConfig(): DeploymentConfig +``` + +Current deployment configuration. See [Deployment Types](/api/deployment/deployment-types). + +### `k8sClient` + +```typescript +get k8sClient(): KubernetesClientHelper +``` + +Kubernetes client instance for direct cluster operations. + +## Methods + +### `configure()` + +```typescript +async configure(options?: DeploymentOptions): Promise +``` + +Configure deployment options and create namespace. + +| Parameter | Type | Description | +|-----------|------|-------------| +| `options` | `DeploymentOptions` | Optional deployment configuration | + +```typescript +await rhdh.configure({ + version: "1.5", + method: "helm", + auth: "keycloak", + appConfig: "tests/config/app-config.yaml", +}); +``` + +### `deploy()` + +```typescript +async deploy(): Promise +``` + +Deploy RHDH to the cluster. This: +1. Merges configuration files +2. Applies ConfigMaps and Secrets +3. Installs RHDH via Helm or Operator +4. Waits for deployment to be ready + +```typescript +await rhdh.deploy(); +``` + +### `waitUntilReady()` + +```typescript +async waitUntilReady(timeout?: number): Promise +``` + +Wait for RHDH deployment to be ready. + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `timeout` | `number` | `300000` | Timeout in milliseconds | + +```typescript +await rhdh.waitUntilReady(600000); // 10 minutes +``` + +### `rolloutRestart()` + +```typescript +async rolloutRestart(): Promise +``` + +Restart the RHDH deployment. + +```typescript +await rhdh.rolloutRestart(); +``` + +### `scaleDownAndRestart()` + +```typescript +async scaleDownAndRestart(): Promise +``` + +Scale down to 0, then back to 1 replica. + +```typescript +await rhdh.scaleDownAndRestart(); +``` + +### `teardown()` + +```typescript +async teardown(): Promise +``` + +Delete the namespace and all resources. + +```typescript +await rhdh.teardown(); +``` + +## Example + +```typescript +import { RHDHDeployment } from "rhdh-e2e-test-utils/rhdh"; + +const rhdh = new RHDHDeployment("my-namespace"); + +await rhdh.configure({ + version: "1.5", + method: "helm", + auth: "keycloak", + appConfig: "tests/config/app-config.yaml", + secrets: "tests/config/secrets.yaml", + dynamicPlugins: "tests/config/plugins.yaml", + valueFile: "tests/config/values.yaml", +}); + +await rhdh.deploy(); + +console.log(`RHDH deployed at: ${rhdh.rhdhUrl}`); + +// After tests +await rhdh.teardown(); +``` diff --git a/docs/api/eslint/create-eslint-config.md b/docs/api/eslint/create-eslint-config.md new file mode 100644 index 0000000..ffc56ba --- /dev/null +++ b/docs/api/eslint/create-eslint-config.md @@ -0,0 +1,57 @@ +# createEslintConfig API + +ESLint configuration factory. + +## Import + +```typescript +import { createEslintConfig } from "rhdh-e2e-test-utils/eslint"; +``` + +## `createEslintConfig()` + +```typescript +function createEslintConfig(dirname: string): ESLintConfig[] +``` + +Create ESLint flat config with Playwright and TypeScript rules. + +| Parameter | Type | Description | +|-----------|------|-------------| +| `dirname` | `string` | Project directory (use `import.meta.dirname`) | + +**Returns:** ESLint flat config array. + +## Usage + +```javascript +// eslint.config.js +import { createEslintConfig } from "rhdh-e2e-test-utils/eslint"; + +export default createEslintConfig(import.meta.dirname); +``` + +## Included Rules + +- TypeScript ESLint recommended +- Playwright recommended +- Naming conventions +- File naming (kebab-case) +- Promise handling + +## Extending + +```javascript +import { createEslintConfig } from "rhdh-e2e-test-utils/eslint"; + +const baseConfig = createEslintConfig(import.meta.dirname); + +export default [ + ...baseConfig, + { + rules: { + "no-console": "warn", + }, + }, +]; +``` diff --git a/docs/api/helpers/api-helper.md b/docs/api/helpers/api-helper.md new file mode 100644 index 0000000..5a132f9 --- /dev/null +++ b/docs/api/helpers/api-helper.md @@ -0,0 +1,143 @@ +# APIHelper API + +Utilities for GitHub and Backstage API interactions. + +## Import + +```typescript +import { APIHelper } from "rhdh-e2e-test-utils/helpers"; +``` + +## Static Methods (GitHub) + +### `createGitHubRepo()` + +```typescript +static async createGitHubRepo( + owner: string, + name: string, + options?: { private?: boolean; description?: string } +): Promise +``` + +### `deleteGitHubRepo()` + +```typescript +static async deleteGitHubRepo(owner: string, name: string): Promise +``` + +### `getGitHubPRs()` + +```typescript +static async getGitHubPRs( + owner: string, + repo: string, + state?: "open" | "closed" | "all" +): Promise +``` + +### `createPullRequest()` + +```typescript +static async createPullRequest( + owner: string, + repo: string, + options: { title: string; head: string; base: string; body?: string } +): Promise +``` + +### `mergePullRequest()` + +```typescript +static async mergePullRequest( + owner: string, + repo: string, + prNumber: number +): Promise +``` + +## Instance Methods (Backstage) + +### Constructor + +```typescript +new APIHelper() +``` + +### `setBaseUrl()` + +```typescript +async setBaseUrl(url: string): Promise +``` + +### `setStaticToken()` + +```typescript +async setStaticToken(token: string): Promise +``` + +### `getAllCatalogUsersFromAPI()` + +```typescript +async getAllCatalogUsersFromAPI(): Promise +``` + +### `getAllCatalogGroupsFromAPI()` + +```typescript +async getAllCatalogGroupsFromAPI(): Promise +``` + +### `getAllCatalogLocationsFromAPI()` + +```typescript +async getAllCatalogLocationsFromAPI(): Promise +``` + +### `getEntityByName()` + +```typescript +async getEntityByName( + kind: string, + namespace: string, + name: string +): Promise +``` + +### `scheduleEntityRefreshFromAPI()` + +```typescript +async scheduleEntityRefreshFromAPI( + name: string, + kind: string, + token?: string +): Promise +``` + +### `deleteEntityFromCatalog()` + +```typescript +async deleteEntityFromCatalog( + kind: string, + namespace: string, + name: string +): Promise +``` + +## Example + +```typescript +import { APIHelper } from "rhdh-e2e-test-utils/helpers"; + +// GitHub operations +await APIHelper.createGitHubRepo("my-org", "test-repo"); +await APIHelper.deleteGitHubRepo("my-org", "test-repo"); + +// Backstage operations +const apiHelper = new APIHelper(); +await apiHelper.setBaseUrl("https://backstage.example.com"); +await apiHelper.setStaticToken(process.env.BACKEND_TOKEN!); + +const users = await apiHelper.getAllCatalogUsersFromAPI(); +console.log(`Found ${users.length} users`); +``` diff --git a/docs/api/helpers/login-helper.md b/docs/api/helpers/login-helper.md new file mode 100644 index 0000000..cb7c22e --- /dev/null +++ b/docs/api/helpers/login-helper.md @@ -0,0 +1,77 @@ +# LoginHelper API + +Authentication helper for various providers. + +## Import + +```typescript +import { LoginHelper } from "rhdh-e2e-test-utils/helpers"; +``` + +## Constructor + +```typescript +new LoginHelper(page: Page) +``` + +## Methods + +### `loginAsGuest()` + +```typescript +async loginAsGuest(): Promise +``` + +Login using guest authentication. + +### `loginAsKeycloakUser()` + +```typescript +async loginAsKeycloakUser(username?: string, password?: string): Promise +``` + +Login using Keycloak OIDC. + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `username` | `string` | `process.env.KEYCLOAK_USER_NAME` or `"test1"` | Username | +| `password` | `string` | `process.env.KEYCLOAK_USER_PASSWORD` or `"test1@123"` | Password | + +### `loginAsGithubUser()` + +```typescript +async loginAsGithubUser(): Promise +``` + +Login using GitHub OAuth. + +**Required environment variables:** +- `GH_USER_NAME` +- `GH_USER_PASSWORD` +- `GH_2FA_SECRET` + +### `signOut()` + +```typescript +async signOut(): Promise +``` + +Sign out of RHDH. + +## Example + +```typescript +import { test } from "rhdh-e2e-test-utils/test"; + +test.beforeEach(async ({ loginHelper }) => { + await loginHelper.loginAsKeycloakUser(); +}); + +test("logged in test", async ({ page }) => { + // Already authenticated +}); + +test.afterEach(async ({ loginHelper }) => { + await loginHelper.signOut(); +}); +``` diff --git a/docs/api/helpers/ui-helper.md b/docs/api/helpers/ui-helper.md new file mode 100644 index 0000000..b32c33e --- /dev/null +++ b/docs/api/helpers/ui-helper.md @@ -0,0 +1,169 @@ +# UIhelper API + +UI interaction helper for Material-UI components. + +## Import + +```typescript +import { UIhelper } from "rhdh-e2e-test-utils/helpers"; +``` + +## Constructor + +```typescript +new UIhelper(page: Page) +``` + +## Methods + +### Wait Operations + +#### `waitForLoad()` +```typescript +async waitForLoad(timeout?: number): Promise +``` +Wait for page to fully load. + +### Verification Methods + +#### `verifyHeading()` +```typescript +async verifyHeading(heading: string | RegExp, timeout?: number): Promise +``` + +#### `verifyText()` +```typescript +async verifyText(text: string | RegExp, timeout?: number): Promise +``` + +#### `verifyLink()` +```typescript +async verifyLink(link: string | RegExp): Promise +``` + +### Button Interactions + +#### `clickButton()` +```typescript +async clickButton( + label: string | RegExp, + options?: { exact?: boolean; force?: boolean } +): Promise +``` + +#### `clickButtonByLabel()` +```typescript +async clickButtonByLabel( + label: string, + options?: { force?: boolean } +): Promise +``` + +#### `clickButtonByText()` +```typescript +async clickButtonByText(text: string): Promise +``` + +### Navigation + +#### `openSidebar()` +```typescript +async openSidebar(navBarText: SidebarTabs): Promise +``` +`SidebarTabs`: `"Home"` | `"Catalog"` | `"APIs"` | `"Docs"` | `"Learning Paths"` | `"Tech Radar"` | `"Create..."` | `"Settings"` + +#### `openCatalogSidebar()` +```typescript +async openCatalogSidebar(navBarText: string): Promise +``` + +#### `clickTab()` +```typescript +async clickTab(tabName: string): Promise +``` + +### Table Operations + +#### `verifyRowsInTable()` +```typescript +async verifyRowsInTable( + rowTexts: (string | RegExp)[], + exact?: boolean +): Promise +``` + +#### `verifyCellsInTable()` +```typescript +async verifyCellsInTable(cellTexts: (string | RegExp)[]): Promise +``` + +#### `verifyRowInTableByUniqueText()` +```typescript +async verifyRowInTableByUniqueText( + uniqueRowText: string, + cellTexts: string[] +): Promise +``` + +#### `verifyRowNotInTable()` +```typescript +async verifyRowNotInTable(rowText: string): Promise +``` + +### Form Interactions + +#### `fillTextInputByLabel()` +```typescript +async fillTextInputByLabel(label: string, value: string): Promise +``` + +#### `clearTextInputByLabel()` +```typescript +async clearTextInputByLabel(label: string): Promise +``` + +#### `selectMuiBox()` +```typescript +async selectMuiBox(label: string, value: string): Promise +``` + +#### `checkCheckbox()` +```typescript +async checkCheckbox(label: string): Promise +``` + +### Card Interactions + +#### `verifyTextinCard()` +```typescript +async verifyTextinCard(cardTitle: string, text: string): Promise +``` + +#### `verifyLinkinCard()` +```typescript +async verifyLinkinCard(cardTitle: string, linkText: string): Promise +``` + +#### `clickBtnInCard()` +```typescript +async clickBtnInCard(cardTitle: string, buttonLabel: string): Promise +``` + +### Search + +#### `searchInputPlaceholder()` +```typescript +async searchInputPlaceholder(placeholder: string, searchText: string): Promise +``` + +### Alert and Dialog + +#### `verifyAlertContains()` +```typescript +async verifyAlertContains(text: string): Promise +``` + +#### `closeDialog()` +```typescript +async closeDialog(): Promise +``` diff --git a/docs/api/index.md b/docs/api/index.md new file mode 100644 index 0000000..522f42a --- /dev/null +++ b/docs/api/index.md @@ -0,0 +1,56 @@ +# API Reference + +Complete API documentation for all exports from `rhdh-e2e-test-utils`. + +## Exports Overview + +| Export | Description | +|--------|-------------| +| [`/test`](/api/playwright/test-fixtures) | Playwright test fixtures | +| [`/playwright-config`](/api/playwright/base-config) | Base Playwright configuration | +| [`/rhdh`](/api/deployment/rhdh-deployment) | RHDHDeployment class | +| [`/keycloak`](/api/deployment/keycloak-helper) | KeycloakHelper class | +| [`/utils`](/api/utils/kubernetes-client) | Utility functions | +| [`/helpers`](/api/helpers/ui-helper) | Helper classes | +| [`/pages`](/api/pages/catalog-page) | Page object classes | +| [`/eslint`](/api/eslint/create-eslint-config) | ESLint configuration | + +## Categories + +### Deployment + +- [RHDHDeployment](/api/deployment/rhdh-deployment) - RHDH deployment management +- [Deployment Types](/api/deployment/deployment-types) - Type definitions +- [KeycloakHelper](/api/deployment/keycloak-helper) - Keycloak management +- [Keycloak Types](/api/deployment/keycloak-types) - Type definitions + +### Playwright + +- [Test Fixtures](/api/playwright/test-fixtures) - Custom Playwright fixtures +- [Base Config](/api/playwright/base-config) - Playwright configuration +- [Global Setup](/api/playwright/global-setup) - Pre-test setup + +### Helpers + +- [UIhelper](/api/helpers/ui-helper) - UI interaction methods +- [LoginHelper](/api/helpers/login-helper) - Authentication methods +- [APIHelper](/api/helpers/api-helper) - API interaction methods + +### Page Objects + +- [CatalogPage](/api/pages/catalog-page) - Catalog interactions +- [HomePage](/api/pages/home-page) - Home page interactions +- [CatalogImportPage](/api/pages/catalog-import-page) - Component registration +- [ExtensionsPage](/api/pages/extensions-page) - Plugin management +- [NotificationPage](/api/pages/notification-page) - Notifications + +### Utilities + +- [KubernetesClientHelper](/api/utils/kubernetes-client) - Kubernetes API +- [Bash ($)](/api/utils/bash) - Shell command execution +- [YAML Merging](/api/utils/merge-yamls) - YAML utilities +- [envsubst](/api/utils/common) - Environment substitution + +### ESLint + +- [createEslintConfig](/api/eslint/create-eslint-config) - ESLint factory diff --git a/docs/api/pages/catalog-import-page.md b/docs/api/pages/catalog-import-page.md new file mode 100644 index 0000000..00a3b04 --- /dev/null +++ b/docs/api/pages/catalog-import-page.md @@ -0,0 +1,150 @@ +# CatalogImportPage API + +Page object for the catalog import functionality. + +## Import + +```typescript +import { CatalogImportPage } from "rhdh-e2e-test-utils/pages"; +``` + +## Constructor + +```typescript +new CatalogImportPage(page: Page) +``` + +Creates a new CatalogImportPage instance with an internal `UIhelper`. + +| Parameter | Type | Description | +|-----------|------|-------------| +| `page` | `Page` | Playwright Page object | + +## Methods + +### `registerExistingComponent(url, clickViewComponent?)` + +```typescript +async registerExistingComponent( + url: string, + clickViewComponent?: boolean +): Promise +``` + +Register an existing component from a URL. If the component is already registered, it will refresh instead of importing. + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `url` | `string` | - | The component URL (e.g., GitHub catalog-info.yaml) | +| `clickViewComponent` | `boolean` | `true` | Whether to click "View Component" after import | + +**Returns:** `boolean` - `true` if component was already registered, `false` if newly imported. + +**Example:** +```typescript +const wasRegistered = await catalogImportPage.registerExistingComponent( + "https://github.com/org/repo/blob/main/catalog-info.yaml" +); + +if (wasRegistered) { + console.log("Component was refreshed"); +} else { + console.log("Component was newly imported"); +} +``` + +### `analyzeComponent(url)` + +```typescript +async analyzeComponent(url: string): Promise +``` + +Analyze a component URL without importing it. Fills the URL and clicks the "Analyze" button. + +| Parameter | Type | Description | +|-----------|------|-------------| +| `url` | `string` | The component URL to analyze | + +**Example:** +```typescript +await catalogImportPage.analyzeComponent( + "https://github.com/org/repo/blob/main/catalog-info.yaml" +); +``` + +### `isComponentAlreadyRegistered()` + +```typescript +async isComponentAlreadyRegistered(): Promise +``` + +Check if the component is already registered. Returns `true` if the "Refresh" button is visible (indicating existing registration). + +**Returns:** `boolean` - Whether the component is already registered. + +**Example:** +```typescript +await catalogImportPage.analyzeComponent(url); +const isRegistered = await catalogImportPage.isComponentAlreadyRegistered(); +``` + +### `inspectEntityAndVerifyYaml(text)` + +```typescript +async inspectEntityAndVerifyYaml(text: string): Promise +``` + +Open the entity inspector, switch to Raw YAML tab, and verify it contains the specified text. + +| Parameter | Type | Description | +|-----------|------|-------------| +| `text` | `string` | Text to verify in the YAML | + +**Example:** +```typescript +await catalogImportPage.inspectEntityAndVerifyYaml("kind: Component"); +``` + +## Complete Example + +```typescript +import { test, expect } from "@playwright/test"; +import { CatalogImportPage } from "rhdh-e2e-test-utils/pages"; +import { UIhelper } from "rhdh-e2e-test-utils/helpers"; + +test("import a new component", async ({ page }) => { + const uiHelper = new UIhelper(page); + const catalogImportPage = new CatalogImportPage(page); + + // Navigate to Create page + await uiHelper.openSidebar("Create..."); + await uiHelper.clickButton("Register Existing Component"); + + // Register the component + const url = "https://github.com/myorg/myrepo/blob/main/catalog-info.yaml"; + const wasAlreadyRegistered = await catalogImportPage.registerExistingComponent(url); + + if (wasAlreadyRegistered) { + console.log("Component was already registered, refreshed it"); + } else { + // Verify we're on the component page + await uiHelper.verifyHeading("my-component"); + } +}); + +test("analyze component without importing", async ({ page }) => { + const catalogImportPage = new CatalogImportPage(page); + + await catalogImportPage.analyzeComponent( + "https://github.com/myorg/myrepo/blob/main/catalog-info.yaml" + ); + + // Check the analyzed YAML + await catalogImportPage.inspectEntityAndVerifyYaml("kind: Component"); +}); +``` + +## Related Pages + +- [CatalogImportPage Guide](/guide/page-objects/catalog-import-page.md) - Detailed usage guide +- [Catalog Operations Example](/examples/catalog-operations.md) - More examples diff --git a/docs/api/pages/catalog-page.md b/docs/api/pages/catalog-page.md new file mode 100644 index 0000000..f65ea98 --- /dev/null +++ b/docs/api/pages/catalog-page.md @@ -0,0 +1,136 @@ +# CatalogPage API + +Page object for the RHDH software catalog (`/catalog` page). + +## Import + +```typescript +import { CatalogPage } from "rhdh-e2e-test-utils/pages"; +``` + +## Constructor + +```typescript +new CatalogPage(page: Page) +``` + +Creates a new CatalogPage instance with an internal `UIhelper`. + +| Parameter | Type | Description | +|-----------|------|-------------| +| `page` | `Page` | Playwright Page object | + +## Methods + +### `go()` + +```typescript +async go(): Promise +``` + +Navigate to the catalog page via the sidebar. + +**Example:** +```typescript +await catalogPage.go(); +``` + +### `goToByName(name)` + +```typescript +async goToByName(name: string): Promise +``` + +Navigate to a specific component in the catalog by its name. + +| Parameter | Type | Description | +|-----------|------|-------------| +| `name` | `string` | The entity name to navigate to | + +**Example:** +```typescript +await catalogPage.goToByName("my-service"); +``` + +### `goToBackstageJanusProject()` + +```typescript +async goToBackstageJanusProject(): Promise +``` + +Navigate to the `backstage-janus` project entity. + +### `goToBackstageJanusProjectCITab()` + +```typescript +async goToBackstageJanusProjectCITab(): Promise +``` + +Navigate to the `backstage-janus` project and open its CI tab. Verifies the "Pipeline Runs" heading is visible. + +### `search(text)` + +```typescript +async search(text: string): Promise +``` + +Search for entities in the catalog. Clears the existing search field, types the search text, and waits for the API response. + +| Parameter | Type | Description | +|-----------|------|-------------| +| `text` | `string` | The search query | + +**Example:** +```typescript +await catalogPage.search("my-component"); +``` + +### `tableRow(content)` + +```typescript +async tableRow(content: string): Promise +``` + +Get a locator for a table row containing the specified content. + +| Parameter | Type | Description | +|-----------|------|-------------| +| `content` | `string` | Text content to find in the row | + +**Returns:** `Locator` - Playwright locator for the matching row. + +**Example:** +```typescript +const row = await catalogPage.tableRow("my-service"); +await row.click(); +``` + +## Complete Example + +```typescript +import { test, expect } from "@playwright/test"; +import { CatalogPage } from "rhdh-e2e-test-utils/pages"; + +test("search and navigate catalog", async ({ page }) => { + const catalogPage = new CatalogPage(page); + + // Navigate to catalog + await catalogPage.go(); + + // Search for a component + await catalogPage.search("my-service"); + + // Get and click the result row + const row = await catalogPage.tableRow("my-service"); + await row.click(); + + // Or navigate directly by name + await catalogPage.goToByName("my-service"); +}); +``` + +## Related Pages + +- [CatalogPage Guide](/guide/page-objects/catalog-page.md) - Detailed usage guide +- [UIhelper API](/api/helpers/ui-helper.md) - UI helper methods used internally +- [Catalog Operations Example](/examples/catalog-operations.md) - More examples diff --git a/docs/api/pages/extensions-page.md b/docs/api/pages/extensions-page.md new file mode 100644 index 0000000..50b8c4d --- /dev/null +++ b/docs/api/pages/extensions-page.md @@ -0,0 +1,212 @@ +# ExtensionsPage API + +Page object for the RHDH extensions/plugins marketplace page. + +## Import + +```typescript +import { ExtensionsPage } from "rhdh-e2e-test-utils/pages"; +``` + +## Constructor + +```typescript +new ExtensionsPage(page: Page) +``` + +Creates a new ExtensionsPage instance with an internal `UIhelper`. + +| Parameter | Type | Description | +|-----------|------|-------------| +| `page` | `Page` | Playwright Page object | + +## Properties + +| Property | Type | Description | +|----------|------|-------------| +| `badge` | `Locator` | Locator for the TaskAltIcon badge | + +## Methods + +### `clickReadMoreByPluginTitle(pluginTitle)` + +```typescript +async clickReadMoreByPluginTitle(pluginTitle: string): Promise +``` + +Click the "Read more" link for a specific plugin. + +| Parameter | Type | Description | +|-----------|------|-------------| +| `pluginTitle` | `string` | The plugin title to find | + +**Example:** +```typescript +await extensionsPage.clickReadMoreByPluginTitle("Tech Radar"); +``` + +### `selectSupportTypeFilter(supportType)` + +```typescript +async selectSupportTypeFilter(supportType: string): Promise +``` + +Filter plugins by support type. + +| Parameter | Type | Description | +|-----------|------|-------------| +| `supportType` | `string` | The support type to filter by | + +**Example:** +```typescript +await extensionsPage.selectSupportTypeFilter("Red Hat"); +``` + +### `resetSupportTypeFilter(supportType)` + +```typescript +async resetSupportTypeFilter(supportType: string): Promise +``` + +Reset/toggle off a support type filter. + +| Parameter | Type | Description | +|-----------|------|-------------| +| `supportType` | `string` | The support type to toggle off | + +### `waitForSearchResults(searchText)` + +```typescript +async waitForSearchResults(searchText: string): Promise +``` + +Wait for search results to contain the specified text. + +| Parameter | Type | Description | +|-----------|------|-------------| +| `searchText` | `string` | Text to wait for in results | + +### `verifyMultipleHeadings(headings?)` + +```typescript +async verifyMultipleHeadings(headings?: string[]): Promise +``` + +Verify multiple headings are visible on the page. + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `headings` | `string[]` | Common headings | List of headings to verify | + +**Default headings:** `["Versions", "Author", "Tags", "Category", "Publisher", "Support Provider"]` + +### `verifyPluginDetails(options)` + +```typescript +async verifyPluginDetails(options: { + pluginName: string; + badgeLabel: string; + badgeText: string; + headings?: string[]; + includeTable?: boolean; + includeAbout?: boolean; +}): Promise +``` + +Click on a plugin and verify its details modal. + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `pluginName` | `string` | - | Plugin name to click | +| `badgeLabel` | `string` | - | Expected badge label | +| `badgeText` | `string` | - | Expected badge text | +| `headings` | `string[]` | Common headings | Headings to verify | +| `includeTable` | `boolean` | `true` | Verify table headers | +| `includeAbout` | `boolean` | `false` | Verify "About" text | + +**Example:** +```typescript +await extensionsPage.verifyPluginDetails({ + pluginName: "Tech Radar", + badgeLabel: "support-type", + badgeText: "Red Hat", + includeAbout: true, +}); +``` + +### `verifySupportTypeBadge(options)` + +```typescript +async verifySupportTypeBadge(options: { + supportType: string; + pluginName?: string; + badgeLabel: string; + badgeText: string; + tooltipText: string; + searchTerm?: string; + headings?: string[]; + includeTable?: boolean; + includeAbout?: boolean; +}): Promise +``` + +Filter by support type and verify badge details, optionally opening a specific plugin. + +| Option | Type | Description | +|--------|------|-------------| +| `supportType` | `string` | Support type to filter by | +| `pluginName` | `string` | Optional plugin to verify | +| `badgeLabel` | `string` | Expected badge label | +| `badgeText` | `string` | Expected badge text | +| `tooltipText` | `string` | Expected tooltip text on hover | +| `searchTerm` | `string` | Optional search term | +| `headings` | `string[]` | Headings to verify | +| `includeTable` | `boolean` | Verify table headers | +| `includeAbout` | `boolean` | Verify "About" text | + +### `verifyKeyValueRowElements(rowTitle, rowValue)` + +```typescript +async verifyKeyValueRowElements(rowTitle: string, rowValue: string): Promise +``` + +Verify a key-value row in a table. + +| Parameter | Type | Description | +|-----------|------|-------------| +| `rowTitle` | `string` | The row title/key | +| `rowValue` | `string` | The expected value | + +## Complete Example + +```typescript +import { test, expect } from "@playwright/test"; +import { ExtensionsPage } from "rhdh-e2e-test-utils/pages"; +import { UIhelper } from "rhdh-e2e-test-utils/helpers"; + +test("browse and filter extensions", async ({ page }) => { + const uiHelper = new UIhelper(page); + const extensionsPage = new ExtensionsPage(page); + + // Navigate to extensions + await uiHelper.openSidebar("Extensions"); + + // Filter by support type + await extensionsPage.selectSupportTypeFilter("Red Hat"); + + // Verify a specific plugin + await extensionsPage.verifyPluginDetails({ + pluginName: "Tech Radar", + badgeLabel: "support-type", + badgeText: "Red Hat", + includeAbout: true, + }); + + // Reset filter + await extensionsPage.resetSupportTypeFilter("Red Hat"); +}); +``` + +## Related Pages + +- [ExtensionsPage Guide](/guide/page-objects/extensions-page.md) - Detailed usage guide diff --git a/docs/api/pages/home-page.md b/docs/api/pages/home-page.md new file mode 100644 index 0000000..9782ef0 --- /dev/null +++ b/docs/api/pages/home-page.md @@ -0,0 +1,110 @@ +# HomePage API + +Page object for the RHDH home page. + +## Import + +```typescript +import { HomePage } from "rhdh-e2e-test-utils/pages"; +``` + +## Constructor + +```typescript +new HomePage(page: Page) +``` + +Creates a new HomePage instance with an internal `UIhelper`. + +| Parameter | Type | Description | +|-----------|------|-------------| +| `page` | `Page` | Playwright Page object | + +## Methods + +### `verifyQuickSearchBar(text)` + +```typescript +async verifyQuickSearchBar(text: string): Promise +``` + +Verify the quick search bar functionality by entering text and verifying a matching link appears. + +| Parameter | Type | Description | +|-----------|------|-------------| +| `text` | `string` | Text to search for | + +**Example:** +```typescript +await homePage.verifyQuickSearchBar("my-component"); +``` + +### `verifyQuickAccess(section, quickAccessItem, expand?)` + +```typescript +async verifyQuickAccess( + section: string, + quickAccessItem: string, + expand?: boolean +): Promise +``` + +Verify a quick access item is visible in a specific section. + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `section` | `string` | - | The accordion section name | +| `quickAccessItem` | `string` | - | The item to verify | +| `expand` | `boolean` | `false` | Whether to expand the section first | + +**Example:** +```typescript +// Verify item in already-expanded section +await homePage.verifyQuickAccess("Favorites", "My Component"); + +// Expand section first, then verify +await homePage.verifyQuickAccess("Recent", "backstage-janus", true); +``` + +### `verifyVisitedCardContent(section)` + +```typescript +async verifyVisitedCardContent(section: string): Promise +``` + +Verify that a visited card section exists and may contain items. + +| Parameter | Type | Description | +|-----------|------|-------------| +| `section` | `string` | The card section name | + +**Example:** +```typescript +await homePage.verifyVisitedCardContent("Recently Visited"); +``` + +## Complete Example + +```typescript +import { test, expect } from "@playwright/test"; +import { HomePage } from "rhdh-e2e-test-utils/pages"; + +test("verify home page features", async ({ page }) => { + const homePage = new HomePage(page); + + // Verify quick search works + await homePage.verifyQuickSearchBar("my-service"); + + // Verify quick access sections + await homePage.verifyQuickAccess("Favorites", "My App"); + await homePage.verifyQuickAccess("Documentation", "Getting Started", true); + + // Verify visited content + await homePage.verifyVisitedCardContent("Recently Visited"); +}); +``` + +## Related Pages + +- [HomePage Guide](/guide/page-objects/home-page.md) - Detailed usage guide +- [UIhelper API](/api/helpers/ui-helper.md) - UI helper methods used internally diff --git a/docs/api/pages/notification-page.md b/docs/api/pages/notification-page.md new file mode 100644 index 0000000..2d50411 --- /dev/null +++ b/docs/api/pages/notification-page.md @@ -0,0 +1,244 @@ +# NotificationPage API + +Page object for the RHDH notifications page. + +## Import + +```typescript +import { NotificationPage } from "rhdh-e2e-test-utils/pages"; +``` + +## Constructor + +```typescript +new NotificationPage(page: Page) +``` + +Creates a new NotificationPage instance with an internal `UIhelper`. + +| Parameter | Type | Description | +|-----------|------|-------------| +| `page` | `Page` | Playwright Page object | + +## Methods + +### Navigation + +#### `clickNotificationsNavBarItem()` + +```typescript +async clickNotificationsNavBarItem(): Promise +``` + +Navigate to the notifications page via the sidebar and wait for it to load. + +### Notification Selection + +#### `selectAllNotifications()` + +```typescript +async selectAllNotifications(): Promise +``` + +Select all notifications using the header checkbox. + +#### `selectNotification(nth?)` + +```typescript +async selectNotification(nth?: number): Promise +``` + +Select a specific notification by index. + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `nth` | `number` | `1` | Zero-based index of the notification to select | + +### Notification Content + +#### `notificationContains(text)` + +```typescript +async notificationContains(text: string | RegExp): Promise +``` + +Verify a notification contains specific text. Automatically expands the table to show 20 rows. + +| Parameter | Type | Description | +|-----------|------|-------------| +| `text` | `string \| RegExp` | Text or pattern to find | + +**Example:** +```typescript +await notificationPage.notificationContains("Build completed"); +await notificationPage.notificationContains(/Pipeline.*succeeded/); +``` + +#### `clickNotificationHeadingLink(text)` + +```typescript +async clickNotificationHeadingLink(text: string | RegExp): Promise +``` + +Click on a notification heading link. + +| Parameter | Type | Description | +|-----------|------|-------------| +| `text` | `string \| RegExp` | Notification heading text to click | + +### Mark as Read/Unread + +#### `markAllNotificationsAsRead()` + +```typescript +async markAllNotificationsAsRead(): Promise +``` + +Mark all notifications as read. If no notifications exist, does nothing. + +#### `markLastNotificationAsRead()` + +```typescript +async markLastNotificationAsRead(): Promise +``` + +Mark the most recent notification as read. + +#### `markNotificationAsRead(text)` + +```typescript +async markNotificationAsRead(text: string): Promise +``` + +Mark a specific notification as read by its text content. + +| Parameter | Type | Description | +|-----------|------|-------------| +| `text` | `string` | Text to identify the notification | + +#### `markLastNotificationAsUnRead()` + +```typescript +async markLastNotificationAsUnRead(): Promise +``` + +Mark the most recent notification as unread. + +### Save for Later + +#### `saveSelected()` + +```typescript +async saveSelected(): Promise +``` + +Save the currently selected notification for later. + +#### `saveAllSelected()` + +```typescript +async saveAllSelected(): Promise +``` + +Save all selected notifications for later. + +### Filtering and Views + +#### `selectSeverity(severity?)` + +```typescript +async selectSeverity(severity?: string): Promise +``` + +Filter notifications by severity. + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `severity` | `string` | `""` | Severity level (e.g., "critical", "high", "normal") | + +#### `viewSaved()` + +```typescript +async viewSaved(): Promise +``` + +Switch to viewing saved notifications. + +#### `viewRead()` + +```typescript +async viewRead(): Promise +``` + +Switch to viewing read notifications. + +#### `viewUnRead()` + +```typescript +async viewUnRead(): Promise +``` + +Switch to viewing unread notifications. + +### Sorting + +#### `sortByOldestOnTop()` + +```typescript +async sortByOldestOnTop(): Promise +``` + +Sort notifications with oldest first. + +#### `sortByNewestOnTop()` + +```typescript +async sortByNewestOnTop(): Promise +``` + +Sort notifications with newest first (default). + +## Complete Example + +```typescript +import { test, expect } from "@playwright/test"; +import { NotificationPage } from "rhdh-e2e-test-utils/pages"; + +test("manage notifications", async ({ page }) => { + const notificationPage = new NotificationPage(page); + + // Navigate to notifications + await notificationPage.clickNotificationsNavBarItem(); + + // Check for a specific notification + await notificationPage.notificationContains("Build completed successfully"); + + // Filter by severity + await notificationPage.selectSeverity("high"); + + // Select and mark as read + await notificationPage.selectNotification(0); + await notificationPage.markLastNotificationAsRead(); + + // Save important notifications + await notificationPage.selectAllNotifications(); + await notificationPage.saveAllSelected(); + + // View saved notifications + await notificationPage.viewSaved(); + + // Sort by oldest + await notificationPage.sortByOldestOnTop(); +}); + +test("clear all notifications", async ({ page }) => { + const notificationPage = new NotificationPage(page); + + await notificationPage.clickNotificationsNavBarItem(); + await notificationPage.markAllNotificationsAsRead(); +}); +``` + +## Related Pages + +- [NotificationPage Guide](/guide/page-objects/notification-page.md) - Detailed usage guide diff --git a/docs/api/playwright/base-config.md b/docs/api/playwright/base-config.md new file mode 100644 index 0000000..b003f2b --- /dev/null +++ b/docs/api/playwright/base-config.md @@ -0,0 +1,134 @@ +# Base Config + +Playwright configuration utilities for RHDH testing. + +## Import + +```typescript +import { defineConfig, baseConfig } from "rhdh-e2e-test-utils/playwright-config"; +``` + +## `defineConfig()` + +```typescript +function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig +``` + +Creates a Playwright configuration with RHDH defaults. + +### Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `config` | `PlaywrightTestConfig` | Playwright configuration options | + +### Returns + +`PlaywrightTestConfig` - Merged configuration with defaults. + +### Example + +```typescript +import { defineConfig } from "rhdh-e2e-test-utils/playwright-config"; + +export default defineConfig({ + projects: [ + { name: "my-plugin" }, + ], +}); +``` + +## `baseConfig` + +```typescript +const baseConfig: PlaywrightTestConfig +``` + +Raw base configuration object. Use for advanced customization. + +### Defaults + +```typescript +{ + testDir: "./tests", + timeout: 90000, + expect: { + timeout: 30000, + }, + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: "50%", + reporter: [["list"], ["html"]], + use: { + viewport: { width: 1920, height: 1080 }, + video: "on", + trace: "retain-on-failure", + screenshot: "only-on-failure", + }, + globalSetup: require.resolve("./global-setup"), +} +``` + +### Example + +```typescript +import { baseConfig } from "rhdh-e2e-test-utils/playwright-config"; +import { defineConfig as playwrightDefineConfig } from "@playwright/test"; + +export default playwrightDefineConfig({ + ...baseConfig, + timeout: 120000, + projects: [{ name: "custom" }], +}); +``` + +## Default Values + +| Setting | Value | +|---------|-------| +| `testDir` | `"./tests"` | +| `timeout` | `90000` | +| `expect.timeout` | `30000` | +| `fullyParallel` | `true` | +| `retries` | `2` (CI), `0` (local) | +| `workers` | `"50%"` | +| `viewport` | `1920x1080` | +| `video` | `"on"` | +| `trace` | `"retain-on-failure"` | +| `screenshot` | `"only-on-failure"` | + +## Customization Examples + +### Override Timeout + +```typescript +export default defineConfig({ + timeout: 120000, + projects: [{ name: "my-plugin" }], +}); +``` + +### Custom Reporters + +```typescript +export default defineConfig({ + reporter: [ + ["list"], + ["html", { open: "never" }], + ["junit", { outputFile: "results.xml" }], + ], + projects: [{ name: "my-plugin" }], +}); +``` + +### Multiple Projects + +```typescript +export default defineConfig({ + projects: [ + { name: "tech-radar", testDir: "./tests/tech-radar" }, + { name: "catalog", testDir: "./tests/catalog" }, + ], +}); +``` diff --git a/docs/api/playwright/global-setup.md b/docs/api/playwright/global-setup.md new file mode 100644 index 0000000..bcddcd0 --- /dev/null +++ b/docs/api/playwright/global-setup.md @@ -0,0 +1,81 @@ +# Global Setup + +Pre-test setup function that runs before all tests. + +## Behavior + +The global setup performs: + +1. **Binary Validation** - Checks `oc`, `kubectl`, `helm` are installed +2. **Cluster Configuration** - Fetches ingress domain +3. **Keycloak Deployment** - Deploys Keycloak (unless skipped) + +## Environment Variables Set + +| Variable | Description | +|----------|-------------| +| `K8S_CLUSTER_ROUTER_BASE` | OpenShift ingress domain | +| `KEYCLOAK_BASE_URL` | Keycloak URL (if deployed) | +| `KEYCLOAK_REALM` | Realm name (if deployed) | +| `KEYCLOAK_CLIENT_ID` | Client ID (if deployed) | +| `KEYCLOAK_CLIENT_SECRET` | Client secret (if deployed) | +| `KEYCLOAK_METADATA_URL` | OIDC discovery URL (if deployed) | +| `KEYCLOAK_LOGIN_REALM` | Login realm (if deployed) | + +## Skipping Keycloak + +```bash +SKIP_KEYCLOAK_DEPLOYMENT=true yarn playwright test +``` + +## Default Keycloak Configuration + +When deployed: + +- **Namespace:** `rhdh-keycloak` +- **Realm:** `rhdh` +- **Client:** `rhdh-client` +- **Users:** `test1`, `test2` + +## Disabling Global Setup + +```typescript +import { baseConfig } from "rhdh-e2e-test-utils/playwright-config"; +import { defineConfig } from "@playwright/test"; + +export default defineConfig({ + ...baseConfig, + globalSetup: undefined, // Disable + projects: [{ name: "my-plugin" }], +}); +``` + +## Custom Global Setup + +Create your own that calls the default: + +```typescript +// global-setup.ts +import { globalSetup as defaultSetup } from "rhdh-e2e-test-utils/playwright-config"; + +export default async function globalSetup() { + // Your custom setup + console.log("Custom setup starting..."); + + // Call default setup + await defaultSetup(); + + // More custom setup + console.log("Custom setup complete"); +} +``` + +```typescript +// playwright.config.ts +import { defineConfig } from "rhdh-e2e-test-utils/playwright-config"; + +export default defineConfig({ + globalSetup: require.resolve("./global-setup"), + projects: [{ name: "my-plugin" }], +}); +``` diff --git a/docs/api/playwright/test-fixtures.md b/docs/api/playwright/test-fixtures.md new file mode 100644 index 0000000..94325f6 --- /dev/null +++ b/docs/api/playwright/test-fixtures.md @@ -0,0 +1,116 @@ +# Test Fixtures + +Custom Playwright fixtures for RHDH testing. + +## Import + +```typescript +import { test, expect } from "rhdh-e2e-test-utils/test"; +``` + +## Fixtures + +### `rhdh` + +**Scope:** Worker + +**Type:** `RHDHDeployment` + +Shared RHDH deployment across all tests in a worker. + +```typescript +test.beforeAll(async ({ rhdh }) => { + await rhdh.configure({ auth: "keycloak" }); + await rhdh.deploy(); +}); + +test("access rhdh", async ({ rhdh }) => { + console.log(rhdh.rhdhUrl); + console.log(rhdh.deploymentConfig.namespace); +}); +``` + +### `uiHelper` + +**Scope:** Test + +**Type:** `UIhelper` + +UI interaction helper for Material-UI components. + +```typescript +test("ui interactions", async ({ uiHelper }) => { + await uiHelper.verifyHeading("Welcome"); + await uiHelper.clickButton("Submit"); + await uiHelper.openSidebar("Catalog"); +}); +``` + +### `loginHelper` + +**Scope:** Test + +**Type:** `LoginHelper` + +Authentication helper for various providers. + +```typescript +test.beforeEach(async ({ loginHelper }) => { + await loginHelper.loginAsKeycloakUser(); +}); + +test.afterEach(async ({ loginHelper }) => { + await loginHelper.signOut(); +}); +``` + +### `baseURL` + +**Scope:** Test + +**Type:** `string` + +Automatically set to the RHDH instance URL. + +```typescript +test("using baseURL", async ({ page, baseURL }) => { + console.log(`Base URL: ${baseURL}`); + // page.goto("/") uses this automatically + await page.goto("/"); +}); +``` + +## Exported Types + +```typescript +import type { Page, BrowserContext, Locator } from "rhdh-e2e-test-utils/test"; +``` + +Re-exports all Playwright types for convenience. + +## Complete Example + +```typescript +import { test, expect } from "rhdh-e2e-test-utils/test"; + +test.describe("My Tests", () => { + test.beforeAll(async ({ rhdh }) => { + await rhdh.configure({ auth: "keycloak" }); + await rhdh.deploy(); + }); + + test.beforeEach(async ({ page, loginHelper }) => { + await page.goto("/"); + await loginHelper.loginAsKeycloakUser(); + }); + + test("verify heading", async ({ uiHelper }) => { + await uiHelper.verifyHeading("Red Hat Developer Hub"); + }); + + test("navigate to catalog", async ({ page, uiHelper }) => { + await uiHelper.openSidebar("Catalog"); + await expect(page).toHaveURL(/.*catalog/); + }); +}); +``` diff --git a/docs/api/utils/bash.md b/docs/api/utils/bash.md new file mode 100644 index 0000000..4e8fa61 --- /dev/null +++ b/docs/api/utils/bash.md @@ -0,0 +1,58 @@ +# Bash ($) API + +Shell command execution via zx. + +## Import + +```typescript +import { $ } from "rhdh-e2e-test-utils/utils"; +``` + +## Usage + +```typescript +// Execute command +await $`echo "Hello"`; + +// With variables +const ns = "my-namespace"; +await $`oc get pods -n ${ns}`; + +// Capture output +const result = await $`oc get pods -o json`; +console.log(result.stdout); +``` + +## Return Type + +```typescript +interface ProcessOutput { + stdout: string; + stderr: string; + exitCode: number; +} +``` + +## Error Handling + +```typescript +try { + await $`oc get pods -n nonexistent`; +} catch (error) { + console.log(error.exitCode); + console.log(error.stderr); +} +``` + +## Example + +```typescript +import { $ } from "rhdh-e2e-test-utils/utils"; + +// Run setup script +await $`bash scripts/setup.sh ${namespace}`; + +// Get route host +const result = await $`oc get route my-route -n ${ns} -o jsonpath='{.spec.host}'`; +const host = result.stdout.trim(); +``` diff --git a/docs/api/utils/common.md b/docs/api/utils/common.md new file mode 100644 index 0000000..9db797e --- /dev/null +++ b/docs/api/utils/common.md @@ -0,0 +1,47 @@ +# envsubst API + +Environment variable substitution. + +## Import + +```typescript +import { envsubst } from "rhdh-e2e-test-utils/utils"; +``` + +## `envsubst()` + +```typescript +function envsubst(template: string): string +``` + +Replace environment variable placeholders in a string. + +| Parameter | Type | Description | +|-----------|------|-------------| +| `template` | `string` | Template with placeholders | + +**Returns:** String with substituted values. + +## Syntax + +| Pattern | Description | +|---------|-------------| +| `$VAR` | Simple substitution | +| `${VAR}` | Braced substitution | +| `${VAR:-default}` | Default if unset | + +## Example + +```typescript +import { envsubst } from "rhdh-e2e-test-utils/utils"; + +process.env.API_URL = "https://api.example.com"; + +// Simple +const result = envsubst("URL: $API_URL"); +// "URL: https://api.example.com" + +// With default +const result2 = envsubst("Port: ${PORT:-8080}"); +// "Port: 8080" (if PORT not set) +``` diff --git a/docs/api/utils/kubernetes-client.md b/docs/api/utils/kubernetes-client.md new file mode 100644 index 0000000..a2a666f --- /dev/null +++ b/docs/api/utils/kubernetes-client.md @@ -0,0 +1,101 @@ +# KubernetesClientHelper API + +Kubernetes API wrapper for OpenShift operations. + +## Import + +```typescript +import { KubernetesClientHelper } from "rhdh-e2e-test-utils/utils"; +``` + +## Constructor + +```typescript +new KubernetesClientHelper() +``` + +## Methods + +### Namespace + +#### `createNamespaceIfNotExists()` +```typescript +async createNamespaceIfNotExists(namespace: string): Promise +``` + +#### `deleteNamespace()` +```typescript +async deleteNamespace(namespace: string): Promise +``` + +### ConfigMap + +#### `applyConfigMapFromObject()` +```typescript +async applyConfigMapFromObject( + name: string, + data: Record, + namespace: string +): Promise +``` + +#### `getConfigMap()` +```typescript +async getConfigMap(name: string, namespace: string): Promise +``` + +### Secret + +#### `applySecretFromObject()` +```typescript +async applySecretFromObject( + name: string, + data: { stringData?: Record }, + namespace: string +): Promise +``` + +#### `getSecret()` +```typescript +async getSecret(name: string, namespace: string): Promise +``` + +### Route + +#### `getRouteLocation()` +```typescript +async getRouteLocation(namespace: string, routeName: string): Promise +``` + +#### `getClusterIngressDomain()` +```typescript +async getClusterIngressDomain(): Promise +``` + +### Deployment + +#### `scaleDeployment()` +```typescript +async scaleDeployment( + namespace: string, + name: string, + replicas: number +): Promise +``` + +#### `restartDeployment()` +```typescript +async restartDeployment(namespace: string, name: string): Promise +``` + +## Example + +```typescript +import { KubernetesClientHelper } from "rhdh-e2e-test-utils/utils"; + +const k8s = new KubernetesClientHelper(); + +await k8s.createNamespaceIfNotExists("my-ns"); +await k8s.applyConfigMapFromObject("config", { key: "value" }, "my-ns"); +const url = await k8s.getRouteLocation("my-ns", "my-route"); +``` diff --git a/docs/api/utils/merge-yamls.md b/docs/api/utils/merge-yamls.md new file mode 100644 index 0000000..2ca6a1d --- /dev/null +++ b/docs/api/utils/merge-yamls.md @@ -0,0 +1,54 @@ +# YAML Merging API + +Utilities for merging YAML files. + +## Import + +```typescript +import { mergeYamlFiles, mergeYamlFilesToFile } from "rhdh-e2e-test-utils/utils"; +``` + +## `mergeYamlFiles()` + +```typescript +function mergeYamlFiles(files: string[]): string +``` + +Merge YAML files and return content. + +| Parameter | Type | Description | +|-----------|------|-------------| +| `files` | `string[]` | Array of file paths | + +**Returns:** Merged YAML content as string. + +## `mergeYamlFilesToFile()` + +```typescript +function mergeYamlFilesToFile(files: string[], outputPath: string): void +``` + +Merge YAML files and write to file. + +| Parameter | Type | Description | +|-----------|------|-------------| +| `files` | `string[]` | Array of file paths | +| `outputPath` | `string` | Output file path | + +## Example + +```typescript +import { mergeYamlFiles, mergeYamlFilesToFile } from "rhdh-e2e-test-utils/utils"; + +// Get merged content +const content = mergeYamlFiles([ + "base.yaml", + "override.yaml", +]); + +// Write to file +mergeYamlFilesToFile( + ["base.yaml", "override.yaml"], + "merged.yaml" +); +``` diff --git a/docs/changelog.md b/docs/changelog.md new file mode 100644 index 0000000..6228ee8 --- /dev/null +++ b/docs/changelog.md @@ -0,0 +1,70 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## [1.1.2] - Current + +### Added +- Keycloak integration with modular auth configuration +- KeycloakHelper class for Keycloak deployment and management +- Support for guest and Keycloak authentication providers +- Automatic Keycloak deployment in global setup +- Default realm, client, groups, and users configuration + +### Changed +- Improved RHDHDeployment class with `configure()` method +- Enhanced configuration merging for auth-specific configs +- Better environment variable handling + +## [1.1.0] + +### Added +- Initial release of `rhdh-e2e-test-utils` +- RHDHDeployment class for RHDH deployment +- Playwright test fixtures (rhdh, uiHelper, loginHelper, baseURL) +- Base Playwright configuration with `defineConfig` +- UIhelper class for Material-UI interactions +- LoginHelper class for authentication +- APIHelper class for GitHub and Backstage APIs +- Page objects (CatalogPage, HomePage, CatalogImportPage, ExtensionsPage, NotificationPage) +- KubernetesClientHelper for Kubernetes operations +- YAML merging utilities +- Environment variable substitution +- ESLint configuration factory +- TypeScript base configuration +- Support for Helm and Operator deployment methods + +## [1.0.0] + +### Added +- Initial project setup +- Basic deployment functionality +- Playwright integration + +--- + +## Migration Guides + +### Migrating from 1.0.x to 1.1.x + +1. **Update imports** - No changes required +2. **Configure authentication** - Use the new `auth` option: + ```typescript + await rhdh.configure({ auth: "keycloak" }); + ``` +3. **Keycloak auto-deployment** - Keycloak is now automatically deployed unless `SKIP_KEYCLOAK_DEPLOYMENT=true` + +### New Authentication Configuration + +Before (1.0.x): +```typescript +// Manual Keycloak setup required +await rhdh.deploy(); +``` + +After (1.1.x): +```typescript +// Keycloak is auto-deployed and configured +await rhdh.configure({ auth: "keycloak" }); +await rhdh.deploy(); +``` diff --git a/docs/examples/api-operations.md b/docs/examples/api-operations.md new file mode 100644 index 0000000..95b3fa9 --- /dev/null +++ b/docs/examples/api-operations.md @@ -0,0 +1,165 @@ +# API Operations + +Examples of GitHub and Backstage API operations. + +## GitHub Operations + +```typescript +import { test, expect } from "rhdh-e2e-test-utils/test"; +import { APIHelper } from "rhdh-e2e-test-utils/helpers"; + +test.describe("GitHub API", () => { + const owner = "my-org"; + const repoName = `test-repo-${Date.now()}`; + + test.afterAll(async () => { + // Cleanup + try { + await APIHelper.deleteGitHubRepo(owner, repoName); + } catch { + // Ignore if already deleted + } + }); + + test("create repository", async () => { + await APIHelper.createGitHubRepo(owner, repoName, { + private: true, + description: "E2E test repository", + }); + }); + + test("get pull requests", async () => { + const prs = await APIHelper.getGitHubPRs(owner, "main-repo", "open"); + expect(prs.length).toBeGreaterThanOrEqual(0); + }); + + test("create and merge PR", async () => { + // Create PR + await APIHelper.createPullRequest(owner, repoName, { + title: "Test PR", + head: "feature-branch", + base: "main", + body: "Test pull request", + }); + + // Get PRs + const prs = await APIHelper.getGitHubPRs(owner, repoName, "open"); + expect(prs.length).toBeGreaterThan(0); + + // Merge first PR + await APIHelper.mergePullRequest(owner, repoName, prs[0].number); + }); +}); +``` + +## Backstage Catalog API + +```typescript +import { test, expect } from "rhdh-e2e-test-utils/test"; +import { APIHelper } from "rhdh-e2e-test-utils/helpers"; + +test.describe("Backstage Catalog API", () => { + let apiHelper: APIHelper; + + test.beforeAll(async ({ rhdh }) => { + await rhdh.configure({ auth: "keycloak" }); + await rhdh.deploy(); + + apiHelper = new APIHelper(); + await apiHelper.setBaseUrl(rhdh.rhdhUrl); + await apiHelper.setStaticToken(process.env.BACKEND_TOKEN!); + }); + + test("get all users", async () => { + const users = await apiHelper.getAllCatalogUsersFromAPI(); + expect(users.length).toBeGreaterThan(0); + }); + + test("get all groups", async () => { + const groups = await apiHelper.getAllCatalogGroupsFromAPI(); + expect(groups.length).toBeGreaterThan(0); + }); + + test("get entity by name", async () => { + const entity = await apiHelper.getEntityByName( + "component", + "default", + "example-component" + ); + expect(entity).toBeDefined(); + expect(entity.metadata.name).toBe("example-component"); + }); + + test("refresh entity", async () => { + await apiHelper.scheduleEntityRefreshFromAPI( + "example-component", + "component" + ); + + // Wait for refresh + await new Promise(resolve => setTimeout(resolve, 5000)); + }); + + test("get locations", async () => { + const locations = await apiHelper.getAllCatalogLocationsFromAPI(); + expect(locations.length).toBeGreaterThan(0); + }); +}); +``` + +## Combined Example + +```typescript +import { test, expect } from "rhdh-e2e-test-utils/test"; +import { APIHelper } from "rhdh-e2e-test-utils/helpers"; +import { CatalogImportPage } from "rhdh-e2e-test-utils/pages"; + +test.describe("Full Workflow", () => { + const owner = "my-org"; + const repoName = `e2e-test-${Date.now()}`; + let apiHelper: APIHelper; + + test.beforeAll(async ({ rhdh }) => { + await rhdh.configure({ auth: "keycloak" }); + await rhdh.deploy(); + + apiHelper = new APIHelper(); + await apiHelper.setBaseUrl(rhdh.rhdhUrl); + + // Create test repository + await APIHelper.createGitHubRepo(owner, repoName); + }); + + test.afterAll(async () => { + await APIHelper.deleteGitHubRepo(owner, repoName); + }); + + test("register repo as component", async ({ page, loginHelper }) => { + await loginHelper.loginAsKeycloakUser(); + + const importPage = new CatalogImportPage(page); + await importPage.registerExistingComponent( + `https://github.com/${owner}/${repoName}/blob/main/catalog-info.yaml` + ); + }); + + test("verify component in catalog", async () => { + const entity = await apiHelper.getEntityByName( + "component", + "default", + repoName + ); + expect(entity).toBeDefined(); + }); +}); +``` + +## Environment Variables + +```bash +# Required for GitHub operations +GITHUB_TOKEN=ghp_xxxxx + +# Required for Backstage API +BACKEND_TOKEN=your-backend-token +``` diff --git a/docs/examples/basic-test.md b/docs/examples/basic-test.md new file mode 100644 index 0000000..52d6be2 --- /dev/null +++ b/docs/examples/basic-test.md @@ -0,0 +1,100 @@ +# Basic Test + +Minimal working test example. + +## Files + +### playwright.config.ts + +```typescript +import { defineConfig } from "rhdh-e2e-test-utils/playwright-config"; +import dotenv from "dotenv"; + +dotenv.config({ path: `${import.meta.dirname}/.env` }); + +export default defineConfig({ + projects: [ + { + name: "my-plugin", + }, + ], +}); +``` + +### .env + +```bash +RHDH_VERSION="1.5" +INSTALLATION_METHOD="helm" +SKIP_KEYCLOAK_DEPLOYMENT=true +``` + +### tests/config/app-config-rhdh.yaml + +```yaml +app: + title: Basic Test Instance +``` + +### tests/config/dynamic-plugins.yaml + +```yaml +includes: + - dynamic-plugins.default.yaml +``` + +### tests/config/rhdh-secrets.yaml + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: rhdh-secrets +type: Opaque +stringData: {} +``` + +### tests/specs/basic.spec.ts + +```typescript +import { test, expect } from "rhdh-e2e-test-utils/test"; + +test.describe("Basic Tests", () => { + test.beforeAll(async ({ rhdh }) => { + await rhdh.configure({ auth: "guest" }); + await rhdh.deploy(); + }); + + test.beforeEach(async ({ page, loginHelper }) => { + await page.goto("/"); + await loginHelper.loginAsGuest(); + }); + + test("should load home page", async ({ page }) => { + await expect(page).toHaveTitle(/Developer Hub/); + }); + + test("should display welcome message", async ({ uiHelper }) => { + await uiHelper.verifyHeading(/Welcome/); + }); + + test("should navigate to catalog", async ({ page, uiHelper }) => { + await uiHelper.openSidebar("Catalog"); + await expect(page).toHaveURL(/catalog/); + }); +}); +``` + +## Running + +```bash +yarn playwright test +``` + +## Related Pages + +- [Quick Start Guide](/guide/quick-start.md) - Step-by-step setup +- [Guest Authentication Example](./guest-auth-test.md) - More guest auth patterns +- [Keycloak Authentication Example](./keycloak-auth-test.md) - OIDC auth testing +- [UIhelper Guide](/guide/helpers/ui-helper.md) - All UI helper methods +- [Testing Patterns](/guide/core-concepts/testing-patterns.md) - Serial vs parallel testing diff --git a/docs/examples/catalog-operations.md b/docs/examples/catalog-operations.md new file mode 100644 index 0000000..fb6ff85 --- /dev/null +++ b/docs/examples/catalog-operations.md @@ -0,0 +1,136 @@ +# Catalog Operations + +Examples of catalog interactions. + +## Browse Catalog + +```typescript +import { test, expect } from "rhdh-e2e-test-utils/test"; +import { CatalogPage } from "rhdh-e2e-test-utils/pages"; + +test.describe("Catalog Operations", () => { + test.beforeAll(async ({ rhdh }) => { + await rhdh.configure({ auth: "keycloak" }); + await rhdh.deploy(); + }); + + test.beforeEach(async ({ loginHelper }) => { + await loginHelper.loginAsKeycloakUser(); + }); + + test("browse catalog", async ({ page, uiHelper }) => { + const catalogPage = new CatalogPage(page); + + // Navigate to catalog + await catalogPage.go(); + + // Verify page loaded + await uiHelper.verifyHeading("Catalog"); + + // Filter by kind + await catalogPage.selectKind("Component"); + + // Search + await catalogPage.search("example"); + + // Verify results + await uiHelper.verifyRowsInTable(["example-component"]); + }); + + test("view component details", async ({ page, uiHelper }) => { + const catalogPage = new CatalogPage(page); + + await catalogPage.go(); + await catalogPage.goToByName("example-component"); + + // Verify component page + await uiHelper.verifyHeading("example-component"); + await uiHelper.clickTab("Overview"); + await uiHelper.verifyTextinCard("About", "description"); + }); +}); +``` + +## Register Component + +```typescript +import { test, expect } from "rhdh-e2e-test-utils/test"; +import { CatalogImportPage } from "rhdh-e2e-test-utils/pages"; + +test("register component", async ({ page, loginHelper, uiHelper }) => { + await loginHelper.loginAsKeycloakUser(); + + const importPage = new CatalogImportPage(page); + + await importPage.go(); + + // Analyze component + await importPage.analyzeComponent( + "https://github.com/my-org/my-repo/blob/main/catalog-info.yaml" + ); + + // Verify YAML + await importPage.inspectEntityAndVerifyYaml("kind: Component"); + + // Register + await importPage.submitRegistration(); + + // Verify success + await uiHelper.verifyHeading("my-component"); +}); +``` + +## Table Verification + +```typescript +import { test } from "rhdh-e2e-test-utils/test"; +import { CatalogPage } from "rhdh-e2e-test-utils/pages"; + +test("verify catalog table", async ({ page, loginHelper, uiHelper }) => { + await loginHelper.loginAsKeycloakUser(); + + const catalogPage = new CatalogPage(page); + await catalogPage.go(); + + // Verify multiple rows exist + await uiHelper.verifyRowsInTable([ + "component-1", + "component-2", + "component-3", + ]); + + // Verify cells in specific row + await uiHelper.verifyRowInTableByUniqueText( + "component-1", + ["Component", "Production", "team-a"] + ); + + // Verify row doesn't exist + await uiHelper.verifyRowNotInTable("deleted-component"); +}); +``` + +## Navigation + +```typescript +import { test, expect } from "rhdh-e2e-test-utils/test"; + +test("catalog navigation", async ({ page, loginHelper, uiHelper }) => { + await loginHelper.loginAsKeycloakUser(); + + // Navigate via sidebar + await uiHelper.openSidebar("Catalog"); + await expect(page).toHaveURL(/catalog/); + + // Click on entity + await page.click("text=example-component"); + + // Navigate tabs + await uiHelper.clickTab("Dependencies"); + await uiHelper.clickTab("API"); + await uiHelper.clickTab("Overview"); + + // Use catalog sidebar + await uiHelper.openCatalogSidebar("Dependencies"); +}); +``` diff --git a/docs/examples/custom-deployment.md b/docs/examples/custom-deployment.md new file mode 100644 index 0000000..86569f3 --- /dev/null +++ b/docs/examples/custom-deployment.md @@ -0,0 +1,172 @@ +# Custom Deployment + +Examples of custom RHDH configuration. + +## Custom App Config + +```typescript +import { test } from "rhdh-e2e-test-utils/test"; + +test.beforeAll(async ({ rhdh }) => { + await rhdh.configure({ + version: "1.5", + method: "helm", + auth: "keycloak", + appConfig: "tests/config/custom-app-config.yaml", + secrets: "tests/config/custom-secrets.yaml", + dynamicPlugins: "tests/config/custom-plugins.yaml", + valueFile: "tests/config/custom-values.yaml", + }); + + await rhdh.deploy(); +}); +``` + +## Pre-Deployment Setup + +```typescript +import { test } from "rhdh-e2e-test-utils/test"; +import { $ } from "rhdh-e2e-test-utils/utils"; + +test.beforeAll(async ({ rhdh }) => { + const namespace = rhdh.deploymentConfig.namespace; + + // Configure first (creates namespace) + await rhdh.configure({ auth: "keycloak" }); + + // Run custom setup script + await $`bash scripts/setup-data-source.sh ${namespace}`; + + // Get route from deployed service + const result = await $`oc get route data-source -n ${namespace} -o jsonpath='{.spec.host}'`; + process.env.DATA_SOURCE_URL = result.stdout.trim(); + + // Deploy RHDH (uses env vars) + await rhdh.deploy(); +}); +``` + +## Dynamic ConfigMaps + +```typescript +import { test } from "rhdh-e2e-test-utils/test"; + +test.beforeAll(async ({ rhdh }) => { + await rhdh.configure({ auth: "keycloak" }); + + // Add custom ConfigMap before deployment + await rhdh.k8sClient.applyConfigMapFromObject( + "custom-data", + { + "data.json": JSON.stringify({ key: "value" }), + }, + rhdh.deploymentConfig.namespace + ); + + await rhdh.deploy(); +}); +``` + +## Runtime Secrets + +```typescript +import { test } from "rhdh-e2e-test-utils/test"; + +test.beforeAll(async ({ rhdh }) => { + // Set secrets at runtime + process.env.GITHUB_TOKEN = await getSecretFromVault("github-token"); + process.env.API_KEY = await getSecretFromVault("api-key"); + + await rhdh.configure({ auth: "keycloak" }); + await rhdh.deploy(); +}); + +async function getSecretFromVault(name: string): Promise { + // Your vault integration + return "secret-value"; +} +``` + +## Update Configuration During Test + +```typescript +import { test } from "rhdh-e2e-test-utils/test"; + +test("update and restart", async ({ rhdh }) => { + const namespace = rhdh.deploymentConfig.namespace; + + // Update ConfigMap + await rhdh.k8sClient.applyConfigMapFromObject( + "app-config-rhdh", + { + "app-config.yaml": ` + app: + title: Updated Title + `, + }, + namespace + ); + + // Restart to apply + await rhdh.rolloutRestart(); + + // Continue testing with new config +}); +``` + +## Custom Helm Values + +**tests/config/custom-values.yaml:** +```yaml +global: + clusterRouterBase: ${K8S_CLUSTER_ROUTER_BASE} + +upstream: + backstage: + image: + registry: quay.io + repository: rhdh/rhdh-hub-rhel9 + tag: ${RHDH_VERSION} + + extraEnvVars: + - name: LOG_LEVEL + value: "debug" + - name: CUSTOM_FLAG + value: "enabled" + + extraEnvVarsSecrets: + - rhdh-secrets + - custom-secrets + + resources: + requests: + memory: "2Gi" + cpu: "1000m" + limits: + memory: "4Gi" + cpu: "2000m" + + postgresql: + enabled: true + primary: + persistence: + size: 10Gi +``` + +## Multiple Deployments + +```typescript +import { RHDHDeployment } from "rhdh-e2e-test-utils/rhdh"; + +// Create multiple deployments +const deployment1 = new RHDHDeployment("instance-1"); +const deployment2 = new RHDHDeployment("instance-2"); + +await Promise.all([ + deployment1.configure({ auth: "guest" }).then(() => deployment1.deploy()), + deployment2.configure({ auth: "keycloak" }).then(() => deployment2.deploy()), +]); + +console.log(`Instance 1: ${deployment1.rhdhUrl}`); +console.log(`Instance 2: ${deployment2.rhdhUrl}`); +``` diff --git a/docs/examples/guest-auth-test.md b/docs/examples/guest-auth-test.md new file mode 100644 index 0000000..5887258 --- /dev/null +++ b/docs/examples/guest-auth-test.md @@ -0,0 +1,59 @@ +# Guest Authentication + +Example using guest authentication. + +## .env + +```bash +RHDH_VERSION="1.5" +INSTALLATION_METHOD="helm" +SKIP_KEYCLOAK_DEPLOYMENT=true +``` + +## Test File + +```typescript +import { test, expect } from "rhdh-e2e-test-utils/test"; + +test.describe("Guest Authentication", () => { + test.beforeAll(async ({ rhdh }) => { + // Configure for guest auth + await rhdh.configure({ auth: "guest" }); + await rhdh.deploy(); + }); + + test.beforeEach(async ({ page, loginHelper }) => { + await page.goto("/"); + await loginHelper.loginAsGuest(); + }); + + test("should login as guest", async ({ uiHelper }) => { + // Verify we're logged in + await uiHelper.verifyHeading(/Welcome/); + }); + + test("should access catalog", async ({ page, uiHelper }) => { + await uiHelper.openSidebar("Catalog"); + await expect(page).toHaveURL(/catalog/); + await uiHelper.verifyHeading("Catalog"); + }); + + test("should sign out", async ({ loginHelper, page }) => { + await loginHelper.signOut(); + await expect(page.getByText("Enter")).toBeVisible(); + }); +}); +``` + +## When to Use + +- Quick development testing +- Tests that don't require user identity +- Simplified CI pipelines +- Tests focused on UI behavior + +## Notes + +- No user roles or groups +- Session not persisted +- Fastest to set up diff --git a/docs/examples/index.md b/docs/examples/index.md new file mode 100644 index 0000000..9d54cd9 --- /dev/null +++ b/docs/examples/index.md @@ -0,0 +1,66 @@ +# Examples + +Copy-paste ready code examples for common scenarios. + +## Available Examples + +| Example | Description | +|---------|-------------| +| [Basic Test](/examples/basic-test) | Minimal working test | +| [Guest Authentication](/examples/guest-auth-test) | Simple guest login | +| [Keycloak Authentication](/examples/keycloak-auth-test) | OIDC authentication | +| [Catalog Operations](/examples/catalog-operations) | Catalog interactions | +| [API Operations](/examples/api-operations) | GitHub/Backstage APIs | +| [Custom Deployment](/examples/custom-deployment) | Custom configuration | +| [Serial Tests](/examples/serial-tests) | Shared browser session | + +## Quick Reference + +### Minimal Test + +```typescript +import { test, expect } from "rhdh-e2e-test-utils/test"; + +test.beforeAll(async ({ rhdh }) => { + await rhdh.deploy(); +}); + +test("example", async ({ page }) => { + await page.goto("/"); + await expect(page).toHaveTitle(/Developer Hub/); +}); +``` + +### With Login + +```typescript +import { test, expect } from "rhdh-e2e-test-utils/test"; + +test.beforeAll(async ({ rhdh }) => { + await rhdh.configure({ auth: "keycloak" }); + await rhdh.deploy(); +}); + +test.beforeEach(async ({ loginHelper }) => { + await loginHelper.loginAsKeycloakUser(); +}); + +test("logged in test", async ({ uiHelper }) => { + await uiHelper.verifyHeading("Welcome"); +}); +``` + +### With Page Object + +```typescript +import { test } from "rhdh-e2e-test-utils/test"; +import { CatalogPage } from "rhdh-e2e-test-utils/pages"; + +test("catalog test", async ({ page, loginHelper }) => { + await loginHelper.loginAsKeycloakUser(); + + const catalogPage = new CatalogPage(page); + await catalogPage.go(); + await catalogPage.search("my-component"); +}); +``` diff --git a/docs/examples/keycloak-auth-test.md b/docs/examples/keycloak-auth-test.md new file mode 100644 index 0000000..36ba694 --- /dev/null +++ b/docs/examples/keycloak-auth-test.md @@ -0,0 +1,127 @@ +# Keycloak Authentication + +Example using Keycloak OIDC authentication. + +## .env + +```bash +RHDH_VERSION="1.5" +INSTALLATION_METHOD="helm" +SKIP_KEYCLOAK_DEPLOYMENT=false +``` + +## Basic Keycloak Test + +```typescript +import { test, expect } from "rhdh-e2e-test-utils/test"; + +test.describe("Keycloak Authentication", () => { + test.beforeAll(async ({ rhdh }) => { + await rhdh.configure({ auth: "keycloak" }); + await rhdh.deploy(); + }); + + test.beforeEach(async ({ page, loginHelper }) => { + await page.goto("/"); + await loginHelper.loginAsKeycloakUser(); + }); + + test("should login with default user", async ({ uiHelper }) => { + await uiHelper.verifyHeading(/Welcome/); + }); + + test("should show user profile", async ({ page }) => { + await page.click("[data-testid='user-settings-menu']"); + await expect(page.getByText("test1")).toBeVisible(); + }); +}); +``` + +## Custom Users Test + +```typescript +import { test, expect } from "rhdh-e2e-test-utils/test"; +import { KeycloakHelper } from "rhdh-e2e-test-utils/keycloak"; + +let keycloak: KeycloakHelper; + +test.describe("Custom Keycloak Users", () => { + test.beforeAll(async ({ rhdh }) => { + // Connect to Keycloak + keycloak = new KeycloakHelper(); + await keycloak.connect({ + baseUrl: process.env.KEYCLOAK_BASE_URL!, + username: "admin", + password: "admin123", + }); + + // Create custom users + await keycloak.createUser("rhdh", { + username: "admin-user", + password: "adminpass", + groups: ["admins"], + }); + + await keycloak.createUser("rhdh", { + username: "dev-user", + password: "devpass", + groups: ["developers"], + }); + + await rhdh.configure({ auth: "keycloak" }); + await rhdh.deploy(); + }); + + test.afterAll(async () => { + // Cleanup + await keycloak.deleteUser("rhdh", "admin-user"); + await keycloak.deleteUser("rhdh", "dev-user"); + }); + + test("admin can access settings", async ({ page, loginHelper, uiHelper }) => { + await page.goto("/"); + await loginHelper.loginAsKeycloakUser("admin-user", "adminpass"); + await uiHelper.openSidebar("Settings"); + await expect(page.getByText("Admin")).toBeVisible(); + }); + + test("developer sees limited options", async ({ page, loginHelper }) => { + await page.goto("/"); + await loginHelper.loginAsKeycloakUser("dev-user", "devpass"); + await expect(page.getByText("Admin")).not.toBeVisible(); + }); +}); +``` + +## Login/Logout Flow + +```typescript +import { test, expect } from "rhdh-e2e-test-utils/test"; + +test("complete auth flow", async ({ page, loginHelper, uiHelper }) => { + // Start at login + await page.goto("/"); + + // Login + await loginHelper.loginAsKeycloakUser("test1", "test1@123"); + + // Verify logged in + await uiHelper.verifyHeading(/Welcome/); + + // Use the app + await uiHelper.openSidebar("Catalog"); + + // Logout + await loginHelper.signOut(); + + // Verify logged out + await expect(page.getByRole("button", { name: /sign in/i })).toBeVisible(); +}); +``` + +## Default Credentials + +| Username | Password | Groups | +|----------|----------|--------| +| `test1` | `test1@123` | developers | +| `test2` | `test2@123` | developers | diff --git a/docs/examples/serial-tests.md b/docs/examples/serial-tests.md new file mode 100644 index 0000000..35916f0 --- /dev/null +++ b/docs/examples/serial-tests.md @@ -0,0 +1,185 @@ +# Serial Tests + +Example using shared browser session across tests. + +## Why Serial Tests? + +- Avoid repeated logins +- Maintain state between tests +- Faster test execution +- Test multi-step workflows + +## Basic Serial Tests + +```typescript +import { test } from "@playwright/test"; +import { setupBrowser, LoginHelper, UIhelper } from "rhdh-e2e-test-utils/helpers"; +import type { Page, BrowserContext } from "@playwright/test"; + +// Configure serial mode +test.describe.configure({ mode: "serial" }); + +let page: Page; +let context: BrowserContext; +let uiHelper: UIhelper; + +test.beforeAll(async ({ browser }, testInfo) => { + // Setup shared browser with video recording + ({ page, context } = await setupBrowser(browser, testInfo)); + + // Create helpers + uiHelper = new UIhelper(page); + const loginHelper = new LoginHelper(page); + + // Login once + await page.goto("/"); + await loginHelper.loginAsKeycloakUser(); +}); + +test.afterAll(async () => { + await context.close(); +}); + +test("step 1: navigate to catalog", async () => { + await uiHelper.openSidebar("Catalog"); + await uiHelper.verifyHeading("Catalog"); +}); + +test("step 2: search for component", async () => { + // Still on catalog page from step 1 + await uiHelper.searchInputPlaceholder("Filter", "example"); + await uiHelper.verifyRowsInTable(["example-component"]); +}); + +test("step 3: view component details", async () => { + // Click on component found in step 2 + await page.click("text=example-component"); + await uiHelper.verifyHeading("example-component"); +}); + +test("step 4: navigate tabs", async () => { + // Still on component page from step 3 + await uiHelper.clickTab("Dependencies"); + await uiHelper.clickTab("API"); + await uiHelper.clickTab("Overview"); +}); +``` + +## Multi-Step Workflow + +```typescript +import { test, expect } from "@playwright/test"; +import { setupBrowser, LoginHelper, UIhelper } from "rhdh-e2e-test-utils/helpers"; +import { CatalogImportPage } from "rhdh-e2e-test-utils/pages"; +import type { Page, BrowserContext } from "@playwright/test"; + +test.describe.configure({ mode: "serial" }); + +let page: Page; +let context: BrowserContext; +const componentUrl = "https://github.com/org/repo/blob/main/catalog-info.yaml"; + +test.beforeAll(async ({ browser }, testInfo) => { + ({ page, context } = await setupBrowser(browser, testInfo)); + + const loginHelper = new LoginHelper(page); + await page.goto("/"); + await loginHelper.loginAsKeycloakUser(); +}); + +test.afterAll(async () => { + await context.close(); +}); + +test("register component", async () => { + const importPage = new CatalogImportPage(page); + await importPage.go(); + await importPage.registerExistingComponent(componentUrl); +}); + +test("verify in catalog", async () => { + const uiHelper = new UIhelper(page); + await uiHelper.openSidebar("Catalog"); + await uiHelper.verifyRowsInTable(["my-component"]); +}); + +test("view component details", async () => { + await page.click("text=my-component"); + const uiHelper = new UIhelper(page); + await uiHelper.verifyHeading("my-component"); +}); + +test("check dependencies tab", async () => { + const uiHelper = new UIhelper(page); + await uiHelper.clickTab("Dependencies"); + await expect(page.getByText("Dependencies")).toBeVisible(); +}); +``` + +## With Deployment + +```typescript +import { test as base } from "@playwright/test"; +import { RHDHDeployment } from "rhdh-e2e-test-utils/rhdh"; +import { setupBrowser, LoginHelper, UIhelper } from "rhdh-e2e-test-utils/helpers"; +import type { Page, BrowserContext } from "@playwright/test"; + +// Create custom test with deployment +const test = base.extend<{}, { deployment: RHDHDeployment }>({ + deployment: [async ({}, use) => { + const rhdh = new RHDHDeployment("serial-tests"); + await rhdh.configure({ auth: "keycloak" }); + await rhdh.deploy(); + + await use(rhdh); + + // Cleanup after all tests + // await rhdh.teardown(); + }, { scope: "worker" }], +}); + +test.describe.configure({ mode: "serial" }); + +let page: Page; +let context: BrowserContext; + +test.beforeAll(async ({ browser, deployment }, testInfo) => { + ({ page, context } = await setupBrowser(browser, testInfo)); + + // Set base URL + await page.goto(deployment.rhdhUrl); + + const loginHelper = new LoginHelper(page); + await loginHelper.loginAsKeycloakUser(); +}); + +test.afterAll(async () => { + await context.close(); +}); + +test("test 1", async () => { + const uiHelper = new UIhelper(page); + await uiHelper.verifyHeading(/Welcome/); +}); + +test("test 2", async () => { + const uiHelper = new UIhelper(page); + await uiHelper.openSidebar("Catalog"); +}); +``` + +## Best Practices + +1. **Use `test.describe.configure({ mode: "serial" })`** - Required for serial mode +2. **Declare variables at describe level** - Shared across tests +3. **Login in beforeAll** - Once per suite +4. **Close context in afterAll** - Proper cleanup +5. **Use `setupBrowser`** - Enables video recording +6. **Order tests logically** - They run in order + +## Related Pages + +- [Testing Patterns Guide](/guide/core-concepts/testing-patterns.md) - Detailed serial vs parallel comparison +- [Error Handling](/guide/core-concepts/error-handling.md) - Handle failures gracefully +- [Basic Test Example](./basic-test.md) - Simple parallel test example +- [Catalog Operations](./catalog-operations.md) - Multi-step catalog workflows diff --git a/docs/guide/configuration/config-files.md b/docs/guide/configuration/config-files.md new file mode 100644 index 0000000..9f32678 --- /dev/null +++ b/docs/guide/configuration/config-files.md @@ -0,0 +1,166 @@ +# Configuration Files + +RHDH deployment uses YAML configuration files for app config, plugins, and secrets. + +## File Structure + +Create configuration files in `tests/config/`: + +``` +tests/config/ +├── app-config-rhdh.yaml # Application configuration +├── dynamic-plugins.yaml # Plugin configuration +├── rhdh-secrets.yaml # Secrets with env placeholders +└── value_file.yaml # Helm values (optional) +``` + +## app-config-rhdh.yaml + +Application configuration for RHDH: + +```yaml +app: + title: My Plugin Test Instance + baseUrl: https://backstage-${NAMESPACE}.${K8S_CLUSTER_ROUTER_BASE} + +backend: + baseUrl: https://backstage-${NAMESPACE}.${K8S_CLUSTER_ROUTER_BASE} + cors: + origin: https://backstage-${NAMESPACE}.${K8S_CLUSTER_ROUTER_BASE} + reading: + allow: + - host: "*.example.com" + +# Plugin-specific configuration +techRadar: + url: "http://${DATA_SOURCE_URL}/tech-radar" + +catalog: + rules: + - allow: [Component, API, Template, System, Domain, Group, User] +``` + +## dynamic-plugins.yaml + +Configure dynamic plugins: + +```yaml +includes: + - dynamic-plugins.default.yaml + +plugins: + # Enable your plugin + - package: ./dynamic-plugins/dist/my-frontend-plugin + disabled: false + pluginConfig: + dynamicPlugins: + frontend: + my-frontend-plugin: + mountPoints: + - mountPoint: entity.page.overview/cards + importName: MyPluginCard + config: + layout: + gridColumnEnd: span 4 + + # Backend plugin + - package: ./dynamic-plugins/dist/my-backend-plugin-dynamic + disabled: false + + # Disable default plugins if needed + - package: ./dynamic-plugins/dist/some-default-plugin + disabled: true +``` + +## rhdh-secrets.yaml + +Secrets with environment variable substitution: + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: rhdh-secrets +type: Opaque +stringData: + # GitHub integration + GITHUB_TOKEN: ${GITHUB_TOKEN} + + # Custom API keys + MY_API_KEY: ${MY_API_KEY:-default-key} + + # Backend auth + BACKEND_SECRET: ${BACKEND_SECRET} +``` + +Environment variables are substituted at deployment time. + +## value_file.yaml (Helm) + +Helm values for RHDH chart: + +```yaml +global: + clusterRouterBase: ${K8S_CLUSTER_ROUTER_BASE} + +upstream: + backstage: + image: + registry: quay.io + repository: rhdh/rhdh-hub-rhel9 + tag: ${RHDH_VERSION} + + extraEnvVars: + - name: LOG_LEVEL + value: "debug" + + extraEnvVarsSecrets: + - rhdh-secrets + + postgresql: + enabled: true + auth: + secretKeys: + adminPasswordKey: postgres-password + userPasswordKey: password +``` + +## Configuration Merging Order + +When you deploy RHDH, configurations are merged: + +``` +1. Package: config/common/app-config-rhdh.yaml +2. Package: config/auth/{provider}/app-config.yaml +3. Project: tests/config/app-config-rhdh.yaml +``` + +Later files override earlier files. You only need to specify what's different from defaults. + +## Environment Variable Substitution + +Use these syntaxes in YAML files: + +| Syntax | Description | +|--------|-------------| +| `$VAR` | Simple substitution | +| `${VAR}` | Braced substitution | +| `${VAR:-default}` | Default if unset | + +Example: +```yaml +backend: + baseUrl: https://backstage-${NAMESPACE}.${K8S_CLUSTER_ROUTER_BASE} + database: + client: pg + connection: + port: ${DB_PORT:-5432} +``` + +## Best Practices + +1. **Use environment variables** for secrets and dynamic values +2. **Keep configs minimal** - only override what's needed +3. **Use default values** for optional settings +4. **Separate concerns** - app config, plugins, secrets in different files +5. **Version control** configs but not secrets diff --git a/docs/guide/configuration/environment-variables.md b/docs/guide/configuration/environment-variables.md new file mode 100644 index 0000000..17c488a --- /dev/null +++ b/docs/guide/configuration/environment-variables.md @@ -0,0 +1,130 @@ +# Environment Variables + +Complete reference of all environment variables used by the package. + +## Required Variables + +| Variable | Description | Example | +|----------|-------------|---------| +| `RHDH_VERSION` | RHDH version to deploy | `"1.5"` | +| `INSTALLATION_METHOD` | Deployment method | `"helm"` or `"operator"` | + +## Auto-Generated Variables + +These are set automatically during deployment: + +| Variable | Description | Set By | +|----------|-------------|--------| +| `K8S_CLUSTER_ROUTER_BASE` | OpenShift ingress domain | Global setup | +| `RHDH_BASE_URL` | Full RHDH URL | RHDHDeployment | + +## Optional Variables + +| Variable | Description | Default | +|----------|-------------|---------| +| `CI` | Enables auto-cleanup | - | +| `CHART_URL` | Custom Helm chart URL | `oci://quay.io/rhdh/chart` | +| `SKIP_KEYCLOAK_DEPLOYMENT` | Skip Keycloak auto-deploy | `false` | + +## Keycloak Variables + +Required when using `auth: "keycloak"`: + +| Variable | Description | +|----------|-------------| +| `KEYCLOAK_BASE_URL` | Keycloak instance URL | +| `KEYCLOAK_REALM` | Realm name | +| `KEYCLOAK_CLIENT_ID` | OIDC client ID | +| `KEYCLOAK_CLIENT_SECRET` | OIDC client secret | +| `KEYCLOAK_METADATA_URL` | OIDC discovery URL | +| `KEYCLOAK_LOGIN_REALM` | Login realm name | +| `KEYCLOAK_USER_NAME` | Default test username | +| `KEYCLOAK_USER_PASSWORD` | Default test password | + +These are automatically set by `KeycloakHelper.configureForRHDH()`. + +## GitHub Variables + +For GitHub integration: + +| Variable | Description | Required | +|----------|-------------|----------| +| `GITHUB_TOKEN` | GitHub personal access token | For API/auth | +| `GH_USER_NAME` | GitHub username | For login | +| `GH_USER_PASSWORD` | GitHub password | For login | +| `GH_2FA_SECRET` | 2FA secret for OTP | For login | + +## Custom Variables + +Use in configuration files: + +```yaml +# tests/config/app-config-rhdh.yaml +myPlugin: + apiUrl: ${MY_PLUGIN_API_URL} + apiKey: ${MY_PLUGIN_API_KEY:-default-key} +``` + +```yaml +# tests/config/rhdh-secrets.yaml +stringData: + MY_PLUGIN_API_KEY: ${MY_PLUGIN_API_KEY} +``` + +## Setting Variables + +### .env File + +Create `.env` in your project root: + +```bash +RHDH_VERSION="1.5" +INSTALLATION_METHOD="helm" +SKIP_KEYCLOAK_DEPLOYMENT=false + +# Secrets +GITHUB_TOKEN=ghp_xxxxx +MY_API_KEY=secret-value +``` + +Load with dotenv: + +```typescript +// playwright.config.ts +import dotenv from "dotenv"; +dotenv.config({ path: `${import.meta.dirname}/.env` }); +``` + +### CI/CD + +Set in your CI pipeline: + +```yaml +# GitHub Actions +env: + RHDH_VERSION: "1.5" + INSTALLATION_METHOD: "helm" + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +``` + +### Runtime + +Set programmatically: + +```typescript +test.beforeAll(async ({ rhdh }) => { + process.env.MY_CUSTOM_URL = await rhdh.k8sClient.getRouteLocation( + rhdh.deploymentConfig.namespace, + "my-service" + ); + + await rhdh.deploy(); +}); +``` + +## Variable Precedence + +1. Runtime (`process.env`) +2. CI/CD environment +3. `.env` file +4. Default values (`${VAR:-default}`) diff --git a/docs/guide/configuration/eslint-config.md b/docs/guide/configuration/eslint-config.md new file mode 100644 index 0000000..7835659 --- /dev/null +++ b/docs/guide/configuration/eslint-config.md @@ -0,0 +1,130 @@ +# ESLint Configuration + +The package provides a pre-configured ESLint setup for Playwright tests. + +## Usage + +Create `eslint.config.js` in your project: + +```javascript +import { createEslintConfig } from "rhdh-e2e-test-utils/eslint"; + +export default createEslintConfig(import.meta.dirname); +``` + +## What's Included + +The configuration includes: + +### TypeScript Rules + +- TypeScript ESLint recommended rules +- Naming conventions for variables, functions, types +- Strict type checking + +### Playwright Rules + +- Playwright recommended rules +- Test organization best practices +- Assertion requirements + +### File Naming + +- Kebab-case for files and folders +- PascalCase for page object files + +### Promise Handling + +- Proper async/await usage +- No floating promises + +## Custom Rules + +The configuration enforces: + +```javascript +{ + // Naming conventions + "@typescript-eslint/naming-convention": [ + "error", + { + "selector": "default", + "format": ["camelCase"] + }, + { + "selector": "variable", + "format": ["camelCase", "UPPER_CASE", "PascalCase"] + }, + { + "selector": "typeLike", + "format": ["PascalCase"] + } + ], + + // File naming + "check-file/filename-naming-convention": [ + "error", + { "**/*.ts": "KEBAB_CASE" } + ], + + // Playwright + "playwright/expect-expect": "error", + "playwright/no-focused-test": "error", + "playwright/no-skipped-test": "warn" +} +``` + +## Extending the Configuration + +Add custom rules after the base config: + +```javascript +import { createEslintConfig } from "rhdh-e2e-test-utils/eslint"; + +const baseConfig = createEslintConfig(import.meta.dirname); + +export default [ + ...baseConfig, + { + rules: { + // Your custom rules + "no-console": "warn", + "@typescript-eslint/no-unused-vars": "error", + }, + }, +]; +``` + +## Ignoring Files + +```javascript +import { createEslintConfig } from "rhdh-e2e-test-utils/eslint"; + +const baseConfig = createEslintConfig(import.meta.dirname); + +export default [ + ...baseConfig, + { + ignores: ["**/generated/**", "**/fixtures/**"], + }, +]; +``` + +## Running ESLint + +Add scripts to `package.json`: + +```json +{ + "scripts": { + "lint": "eslint .", + "lint:fix": "eslint . --fix" + } +} +``` + +Run: +```bash +yarn lint +yarn lint:fix +``` diff --git a/docs/guide/configuration/index.md b/docs/guide/configuration/index.md new file mode 100644 index 0000000..2479c82 --- /dev/null +++ b/docs/guide/configuration/index.md @@ -0,0 +1,69 @@ +# Configuration Overview + +The package provides configuration tools for ESLint, TypeScript, and RHDH deployment. + +## Configuration Topics + +| Topic | Description | +|-------|-------------| +| [Configuration Files](/guide/configuration/config-files) | YAML configuration structure | +| [ESLint Configuration](/guide/configuration/eslint-config) | Pre-configured ESLint rules | +| [TypeScript Configuration](/guide/configuration/typescript-config) | Base TypeScript settings | +| [Environment Variables](/guide/configuration/environment-variables) | All environment variables | + +## Project Configuration + +A typical E2E test project includes these configuration files: + +``` +e2e-tests/ +├── playwright.config.ts # Playwright configuration +├── tsconfig.json # TypeScript configuration +├── eslint.config.js # ESLint configuration +├── .env # Environment variables +└── tests/ + └── config/ + ├── app-config-rhdh.yaml # RHDH app config + ├── dynamic-plugins.yaml # Plugin configuration + ├── rhdh-secrets.yaml # Secrets template + └── value_file.yaml # Helm values (optional) +``` + +## Quick Setup + +### TypeScript + +```json +{ + "extends": "rhdh-e2e-test-utils/tsconfig", + "include": ["tests/**/*.ts"] +} +``` + +### ESLint + +```javascript +import { createEslintConfig } from "rhdh-e2e-test-utils/eslint"; + +export default createEslintConfig(import.meta.dirname); +``` + +### Playwright + +```typescript +import { defineConfig } from "rhdh-e2e-test-utils/playwright-config"; + +export default defineConfig({ + projects: [{ name: "my-plugin" }], +}); +``` + +## Configuration Merging + +RHDH configurations are merged in layers: + +1. **Package defaults** - Base configurations included with the package +2. **Auth-specific** - Configurations for guest or Keycloak auth +3. **Project configs** - Your custom configurations + +This allows you to override only what you need while using sensible defaults. diff --git a/docs/guide/configuration/typescript-config.md b/docs/guide/configuration/typescript-config.md new file mode 100644 index 0000000..c15f8ab --- /dev/null +++ b/docs/guide/configuration/typescript-config.md @@ -0,0 +1,111 @@ +# TypeScript Configuration + +The package provides a base TypeScript configuration to extend. + +## Usage + +Create `tsconfig.json` in your project: + +```json +{ + "extends": "rhdh-e2e-test-utils/tsconfig", + "include": ["tests/**/*.ts", "playwright.config.ts"] +} +``` + +## What's Included + +The base configuration includes: + +```json +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true + } +} +``` + +## Key Settings + +| Setting | Value | Description | +|---------|-------|-------------| +| `target` | ES2022 | Modern JavaScript features | +| `module` | ESNext | ES modules | +| `moduleResolution` | bundler | Modern resolution | +| `strict` | true | Strict type checking | + +## Customizing + +Override settings in your `tsconfig.json`: + +```json +{ + "extends": "rhdh-e2e-test-utils/tsconfig", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./tests" + }, + "include": ["tests/**/*.ts"], + "exclude": ["node_modules", "dist"] +} +``` + +## Multiple Configs + +For complex projects, create multiple configs: + +**tsconfig.json** (development): +```json +{ + "extends": "rhdh-e2e-test-utils/tsconfig", + "include": ["tests/**/*.ts", "playwright.config.ts"] +} +``` + +**tsconfig.build.json** (if building): +```json +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "declaration": true + }, + "exclude": ["**/*.spec.ts"] +} +``` + +## Path Aliases + +Add path aliases for cleaner imports: + +```json +{ + "extends": "rhdh-e2e-test-utils/tsconfig", + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@pages/*": ["tests/pages/*"], + "@helpers/*": ["tests/helpers/*"], + "@config/*": ["tests/config/*"] + } + } +} +``` + +Usage: +```typescript +import { MyPage } from "@pages/my-page"; +import { myHelper } from "@helpers/my-helper"; +``` + +::: warning +Path aliases require additional configuration in Playwright. Use `tsconfig-paths` or configure in `playwright.config.ts`. +::: diff --git a/docs/guide/core-concepts/architecture.md b/docs/guide/core-concepts/architecture.md new file mode 100644 index 0000000..7c9fe9b --- /dev/null +++ b/docs/guide/core-concepts/architecture.md @@ -0,0 +1,325 @@ +# Architecture Overview + +This page explains how the different components of `rhdh-e2e-test-utils` work together to provide a seamless E2E testing experience. + +## Component Diagram + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Your Test Project │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ +│ │ Test Files │ │ playwright │ │ Config Files │ │ +│ │ *.spec.ts │ │ .config.ts │ │ app-config.yaml │ │ +│ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │ +│ │ │ │ │ +└────────────┼──────────────────────┼──────────────────────┼──────────────────┘ + │ │ │ + ▼ ▼ ▼ +┌─────────────────────────────────────────────────────────────────────────────┐ +│ rhdh-e2e-test-utils │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────────────────────┐ │ +│ │ Test Fixtures │ │ +│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ +│ │ │ rhdh │ │ uiHelper │ │loginHelper│ │ apiHelper│ │ │ +│ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │ +│ └───────┼─────────────┼────────────┼─────────────┼────────────────────┘ │ +│ │ │ │ │ │ +│ ┌───────▼─────────────┼────────────┼─────────────┼────────────────────┐ │ +│ │ Deployment │ │ │ │ │ +│ │ ┌──────────────┐ │ │ │ │ │ +│ │ │RHDHDeployment│ │ │ │ │ │ +│ │ └──────┬───────┘ │ │ │ │ │ +│ │ │ │ │ │ │ │ +│ │ ┌──────▼───────┐ │ ┌──────▼─────────────▼─────────┐ │ │ +│ │ │KeycloakHelper│ │ │ Helper Classes │ │ │ +│ │ └──────────────┘ │ │ UIhelper, LoginHelper, etc │ │ │ +│ │ │ └───────────────────────────────┘ │ │ +│ └─────────────────────┼────────────────────────────────────────────────┘ │ +│ │ │ +│ ┌─────────────────────▼────────────────────────────────────────────────┐ │ +│ │ Utilities │ │ +│ │ ┌────────────────────┐ ┌─────────────┐ ┌────────────────────┐ │ │ +│ │ │KubernetesClient │ │ mergeYaml │ │ envsubst │ │ │ +│ │ │ Helper │ │ │ │ │ │ │ +│ │ └─────────┬──────────┘ └──────┬──────┘ └─────────┬──────────┘ │ │ +│ └────────────┼────────────────────┼───────────────────┼────────────────┘ │ +│ │ │ │ │ +└────────────────┼────────────────────┼───────────────────┼───────────────────┘ + │ │ │ + ▼ ▼ ▼ +┌────────────────────────────────────────────────────────────────────────────┐ +│ OpenShift Cluster │ +├────────────────────────────────────────────────────────────────────────────┤ +│ ┌────────────────────┐ ┌────────────────────┐ │ +│ │ RHDH Namespace │ │ Keycloak Namespace │ │ +│ │ ┌──────────────┐ │ │ ┌──────────────┐ │ │ +│ │ │ Backstage │◄─┼──────┼──│ Keycloak │ │ │ +│ │ │ Pod(s) │ │ OIDC │ │ Pod │ │ │ +│ │ └──────────────┘ │ │ └──────────────┘ │ │ +│ │ ┌──────────────┐ │ └────────────────────┘ │ +│ │ │ ConfigMaps │ │ │ +│ │ │ Secrets │ │ │ +│ │ └──────────────┘ │ │ +│ └────────────────────┘ │ +└────────────────────────────────────────────────────────────────────────────┘ +``` + +## Component Responsibilities + +### Test Fixtures + +The package provides Playwright fixtures that are automatically injected into your tests: + +| Fixture | Type | Purpose | +|---------|------|---------| +| `rhdh` | `RHDHDeployment` | Deploy and manage RHDH instances | +| `uiHelper` | `UIhelper` | Interact with Material-UI components | +| `loginHelper` | `LoginHelper` | Handle authentication flows | +| `apiHelper` | `APIHelper` | Make Backstage API calls | +| `page` | `Page` | Playwright page object (built-in) | +| `baseURL` | `string` | RHDH instance URL | + +### Deployment Layer + +Manages the lifecycle of RHDH and Keycloak on OpenShift: + +``` +Global Setup + │ + ├── KeycloakHelper.deploy() ─── Deploys Keycloak via Helm + │ │ + │ └── configureForRHDH() ─── Creates realm, client, users + │ + └── RHDHDeployment.deploy() ─── Deploys RHDH via Helm/Operator + │ + ├── mergeConfigurations() ─── Merges YAML configs + │ + └── applyToCluster() ─── Creates K8s resources +``` + +### Helper Classes + +Provide high-level abstractions for common operations: + +``` +UIhelper +├── verifyHeading() ─── Check page headings +├── openSidebar() ─── Navigate via sidebar +├── clickButton() ─── Click Material-UI buttons +├── verifyRowsInTable() ─── Verify table contents +└── ... (50+ methods) + +LoginHelper +├── loginAsGuest() ─── Guest authentication +├── loginAsKeycloakUser()─── Keycloak OIDC login +└── signOut() ─── Logout + +APIHelper +├── getEntityByName() ─── Fetch catalog entities +├── importEntity() ─── Register catalog entities +└── deleteEntity() ─── Remove catalog entities +``` + +### Utilities + +Low-level utilities used by the higher-level components: + +``` +KubernetesClientHelper +├── createNamespace() ─── Create K8s namespaces +├── applyResource() ─── Apply K8s manifests +├── waitForDeployment() ─── Wait for pods to be ready +└── exec() ─── Execute commands in pods + +mergeYaml +├── mergeYamlConfigs() ─── Merge multiple YAML files +└── deepMerge() ─── Deep merge objects + +envsubst +└── substituteEnv() ─── Replace ${VAR} in strings +``` + +## Data Flow + +### 1. Test Initialization + +``` +playwright.config.ts + │ + ▼ +┌──────────────────┐ +│ baseConfig │ ─── Provides default configuration +└────────┬─────────┘ + │ + ▼ +┌──────────────────┐ +│ globalSetup │ ─── Deploys infrastructure before tests +└────────┬─────────┘ + │ + ┌────┴────┐ + ▼ ▼ +Keycloak RHDH + deploy deploy +``` + +### 2. Configuration Merging + +``` +Package Default Configs Your Project Configs + │ │ + ▼ ▼ +┌───────────────────┐ ┌───────────────────┐ +│ common/ │ │ tests/config/ │ +│ app-config.yaml │ │ app-config.yaml │ +│ dynamic-plugins │ │ dynamic-plugins │ +│ secrets.yaml │ │ secrets.yaml │ +└─────────┬─────────┘ └─────────┬─────────┘ + │ │ + └──────────┬───────────────────┘ + │ + ▼ + ┌──────────────┐ + │ mergeYaml │ + └──────┬───────┘ + │ + ▼ + ┌──────────────┐ + │ envsubst │ ─── ${K8S_CLUSTER_URL} → actual value + └──────┬───────┘ + │ + ▼ + ┌──────────────┐ + │ ConfigMap │ ─── Applied to OpenShift + └──────────────┘ +``` + +### 3. Test Execution + +``` +test("my test", async ({ uiHelper, loginHelper }) => { + // 1. Login via Keycloak + await loginHelper.loginAsKeycloakUser(); + │ + ▼ + ┌─────────────────────────────────────────┐ + │ Playwright navigates to RHDH │ + │ Redirects to Keycloak login │ + │ Fills credentials, submits │ + │ Redirects back to RHDH authenticated │ + └─────────────────────────────────────────┘ + + // 2. Interact with UI + await uiHelper.openSidebar("Catalog"); + │ + ▼ + ┌─────────────────────────────────────────┐ + │ Finds sidebar button by data-testid │ + │ Clicks to navigate │ + │ Waits for page load │ + └─────────────────────────────────────────┘ + + // 3. Verify results + await uiHelper.verifyHeading("My Org Catalog"); + │ + ▼ + ┌─────────────────────────────────────────┐ + │ Locates heading element │ + │ Asserts text matches │ + │ Throws if not found │ + └─────────────────────────────────────────┘ +}); +``` + +## Package Exports + +Each export serves a specific purpose: + +| Export | Import Path | Purpose | +|--------|------------|---------| +| Test fixtures | `rhdh-e2e-test-utils/test` | Main test API with fixtures | +| Playwright config | `rhdh-e2e-test-utils/playwright-config` | Base Playwright configuration | +| RHDH deployment | `rhdh-e2e-test-utils/rhdh` | RHDHDeployment class | +| Keycloak | `rhdh-e2e-test-utils/keycloak` | KeycloakHelper class | +| Helpers | `rhdh-e2e-test-utils/helpers` | UIhelper, LoginHelper, etc. | +| Page objects | `rhdh-e2e-test-utils/pages` | Page object classes | +| Utilities | `rhdh-e2e-test-utils/utils` | KubernetesClientHelper, etc. | +| ESLint | `rhdh-e2e-test-utils/eslint` | ESLint configuration | +| TypeScript | `rhdh-e2e-test-utils/tsconfig` | TypeScript base config | + +## Fixture Dependencies + +Understanding the dependencies between fixtures helps when using them directly: + +``` +rhdh fixture +├── Depends on: KubernetesClientHelper (internal) +├── Depends on: KeycloakHelper (when auth: "keycloak") +└── Provides: baseURL to other fixtures + +uiHelper fixture +├── Depends on: page (Playwright Page) +└── Independent of: rhdh, loginHelper + +loginHelper fixture +├── Depends on: page (Playwright Page) +├── Depends on: uiHelper (for UI interactions) +└── Uses: baseURL (from environment) + +apiHelper fixture +├── Depends on: page (Playwright Page) +└── Uses: baseURL (from environment) +``` + +## Best Practices + +### 1. Use Fixtures, Not Direct Imports + +```typescript +// Recommended: Use fixtures +test("example", async ({ uiHelper }) => { + await uiHelper.openSidebar("Catalog"); +}); + +// Avoid: Direct instantiation (unless needed for serial tests) +import { UIhelper } from "rhdh-e2e-test-utils/helpers"; +const helper = new UIhelper(page); +``` + +### 2. Let Global Setup Handle Deployment + +```typescript +// Recommended: Configure in global setup, test focuses on behavior +test("verify catalog", async ({ uiHelper }) => { + await uiHelper.verifyHeading("Catalog"); +}); + +// Avoid: Deploying in each test file +test.beforeAll(async ({ rhdh }) => { + await rhdh.deploy(); // This slows down tests +}); +``` + +### 3. Separate Concerns + +``` +tests/ +├── config/ # Configuration files +│ ├── app-config.yaml +│ └── dynamic-plugins.yaml +├── fixtures/ # Custom fixtures (if needed) +├── pages/ # Custom page objects (if needed) +└── specs/ # Test files + ├── catalog.spec.ts + └── auth.spec.ts +``` + +## Related Pages + +- [Package Exports](./package-exports.md) - Detailed export documentation +- [Playwright Fixtures](./playwright-fixtures.md) - Fixture deep dive +- [Global Setup](./global-setup.md) - Setup process details +- [RHDH Deployment](../deployment/rhdh-deployment.md) - Deployment options diff --git a/docs/guide/core-concepts/error-handling.md b/docs/guide/core-concepts/error-handling.md new file mode 100644 index 0000000..34924ae --- /dev/null +++ b/docs/guide/core-concepts/error-handling.md @@ -0,0 +1,298 @@ +# Error Handling Best Practices + +This guide covers error handling patterns for robust E2E tests. + +## Why Error Handling Matters + +E2E tests interact with real systems that can fail. Proper error handling: +- Makes tests more reliable +- Provides better debugging information +- Enables graceful recovery from transient failures +- Improves test maintenance + +## Common Error Scenarios + +### 1. Element Not Found + +```typescript +import { test, expect } from "rhdh-e2e-test-utils/test"; + +test("handle missing element gracefully", async ({ uiHelper, page }) => { + // Bad: Will fail with generic timeout error + // await uiHelper.verifyHeading("Non-existent Heading"); + + // Good: Use explicit assertions with clear expectations + await expect(page.getByRole("heading", { name: "My Heading" })) + .toBeVisible({ timeout: 10000 }); +}); +``` + +### 2. API Errors + +```typescript +test("handle API errors", async ({ apiHelper }) => { + try { + const entity = await apiHelper.getEntityByName("my-component"); + expect(entity).toBeDefined(); + } catch (error) { + // Log for debugging + console.error("Failed to fetch entity:", error); + + // Re-throw with context + throw new Error(`Entity 'my-component' not found. Is it registered?`); + } +}); +``` + +### 3. Timeout Errors + +```typescript +test("handle slow operations", async ({ page, uiHelper }) => { + // Increase timeout for known slow operations + await test.step("Wait for slow data load", async () => { + await expect(page.getByTestId("data-table")) + .toBeVisible({ timeout: 60000 }); + }); + + // Or use polling for eventual consistency + await expect(async () => { + const count = await page.getByTestId("item").count(); + expect(count).toBeGreaterThan(0); + }).toPass({ timeout: 30000 }); +}); +``` + +### 4. Network Errors + +```typescript +test("handle network issues", async ({ page }) => { + // Wait for specific API response + const responsePromise = page.waitForResponse( + response => response.url().includes("/api/catalog") && response.ok() + ); + + await page.click('button[data-testid="refresh"]'); + + try { + await responsePromise; + } catch (error) { + // Network request failed or timed out + console.error("API call failed:", error); + throw new Error("Catalog API is not responding"); + } +}); +``` + +## Retry Patterns + +### Simple Retry + +```typescript +async function withRetry( + fn: () => Promise, + maxRetries: number = 3, + delay: number = 1000 +): Promise { + let lastError: Error | undefined; + + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + return await fn(); + } catch (error) { + lastError = error as Error; + console.log(`Attempt ${attempt}/${maxRetries} failed:`, error); + + if (attempt < maxRetries) { + await new Promise(resolve => setTimeout(resolve, delay)); + } + } + } + + throw lastError; +} + +// Usage +test("retry flaky operation", async ({ apiHelper }) => { + const entity = await withRetry( + () => apiHelper.getEntityByName("my-component"), + 3, + 2000 + ); + expect(entity).toBeDefined(); +}); +``` + +### Playwright Built-in Retry + +```typescript +test("use Playwright's toPass for polling", async ({ apiHelper }) => { + // This will retry until the assertion passes or timeout + await expect(async () => { + const entity = await apiHelper.getEntityByName("my-component"); + expect(entity.status).toBe("active"); + }).toPass({ + timeout: 30000, + intervals: [1000, 2000, 5000], // Backoff intervals + }); +}); +``` + +## Test Cleanup + +Always clean up resources, even if tests fail: + +```typescript +test.describe("API operations with cleanup", () => { + const createdEntities: string[] = []; + + test.afterEach(async ({ apiHelper }) => { + // Clean up any entities created during the test + for (const entityName of createdEntities) { + try { + await apiHelper.deleteEntity(entityName); + } catch (error) { + console.warn(`Failed to clean up ${entityName}:`, error); + // Don't fail the test for cleanup errors + } + } + createdEntities.length = 0; // Clear the array + }); + + test("create and verify entity", async ({ apiHelper }) => { + const name = `test-entity-${Date.now()}`; + createdEntities.push(name); + + await apiHelper.importEntity(`https://example.com/${name}/catalog-info.yaml`); + + await expect(async () => { + const entity = await apiHelper.getEntityByName(name); + expect(entity).toBeDefined(); + }).toPass({ timeout: 30000 }); + }); +}); +``` + +## Logging and Debugging + +### Test Steps for Better Reporting + +```typescript +test("complex workflow with steps", async ({ uiHelper, loginHelper }) => { + await test.step("Login as admin", async () => { + await loginHelper.loginAsKeycloakUser("admin", "admin123"); + }); + + await test.step("Navigate to settings", async () => { + await uiHelper.openSidebar("Settings"); + }); + + await test.step("Update configuration", async () => { + await uiHelper.clickButton("Edit"); + await uiHelper.fillTextInputByLabel("Name", "New Name"); + await uiHelper.clickButton("Save"); + }); + + await test.step("Verify changes", async () => { + await uiHelper.verifyText("New Name"); + }); +}); +``` + +### Screenshot on Failure + +```typescript +// playwright.config.ts +import { defineConfig } from "@playwright/test"; +import { baseConfig } from "rhdh-e2e-test-utils/playwright-config"; + +export default defineConfig({ + ...baseConfig, + use: { + ...baseConfig.use, + screenshot: "only-on-failure", + video: "retain-on-failure", + trace: "retain-on-failure", + }, +}); +``` + +### Custom Error Messages + +```typescript +test("verify component with context", async ({ uiHelper }) => { + const componentName = "my-component"; + + await uiHelper.openSidebar("Catalog"); + + // Add context to assertions + await expect( + uiHelper.page.getByText(componentName), + `Component "${componentName}" should be visible in catalog` + ).toBeVisible(); +}); +``` + +## Common Pitfalls + +### 1. Swallowing Errors + +```typescript +// Bad: Errors are silently ignored +try { + await apiHelper.deleteEntity("component"); +} catch { + // Nothing happens +} + +// Good: Log the error or handle it appropriately +try { + await apiHelper.deleteEntity("component"); +} catch (error) { + console.warn("Cleanup failed (may be expected):", error); +} +``` + +### 2. Race Conditions + +```typescript +// Bad: Click without waiting for navigation +await page.click('a[href="/catalog"]'); +await uiHelper.verifyHeading("Catalog"); // May fail + +// Good: Wait for navigation +await Promise.all([ + page.waitForURL("**/catalog"), + page.click('a[href="/catalog"]'), +]); +await uiHelper.verifyHeading("Catalog"); +``` + +### 3. Hard-coded Waits + +```typescript +// Bad: Fixed delay +await page.click('button[data-testid="save"]'); +await page.waitForTimeout(3000); +await uiHelper.verifyText("Saved"); + +// Good: Wait for condition +await page.click('button[data-testid="save"]'); +await expect(page.getByText("Saved")).toBeVisible(); +``` + +## Error Handling Checklist + +- [ ] Use specific error messages that include context +- [ ] Implement retry logic for flaky operations +- [ ] Always clean up resources in afterEach/afterAll +- [ ] Use test steps for better failure reporting +- [ ] Enable screenshots and traces for debugging +- [ ] Avoid hard-coded waits - use conditions instead +- [ ] Log errors before re-throwing for debugging +- [ ] Handle expected errors gracefully (e.g., "entity already exists") + +## Related Pages + +- [Serial vs Parallel Testing](./testing-patterns.md) - Test execution patterns +- [Architecture Overview](./architecture.md) - How components work together +- [APIHelper](/guide/helpers/api-helper.md) - API error handling diff --git a/docs/guide/core-concepts/global-setup.md b/docs/guide/core-concepts/global-setup.md new file mode 100644 index 0000000..8055bcf --- /dev/null +++ b/docs/guide/core-concepts/global-setup.md @@ -0,0 +1,175 @@ +# Global Setup + +The package includes a global setup function that runs once before all tests. This ensures the testing environment is properly prepared. + +## What Global Setup Does + +### 1. Binary Validation + +Checks that required CLI tools are installed and available: + +| Binary | Purpose | +|--------|---------| +| `oc` | OpenShift CLI for cluster operations | +| `kubectl` | Kubernetes CLI (fallback) | +| `helm` | Helm CLI for chart deployments | + +If any binary is missing, tests will fail with a clear error message. + +### 2. Cluster Router Base + +Fetches the OpenShift ingress domain and sets the `K8S_CLUSTER_ROUTER_BASE` environment variable: + +```bash +# Example value +K8S_CLUSTER_ROUTER_BASE=apps.cluster-abc123.example.com +``` + +This is used to construct route URLs for deployed applications. + +### 3. Keycloak Deployment + +Automatically deploys and configures Keycloak for OIDC authentication: + +- Deploys to the `rhdh-keycloak` namespace +- Uses Bitnami Helm chart +- Configures realm, client, groups, and users for RHDH +- Sets all required Keycloak environment variables + +**Skip Keycloak Deployment:** + +If your tests don't require Keycloak/OIDC authentication: + +```bash +SKIP_KEYCLOAK_DEPLOYMENT=true yarn playwright test +``` + +Or in your `.env` file: + +```bash +SKIP_KEYCLOAK_DEPLOYMENT=true +``` + +## Environment Variables Set by Global Setup + +### Cluster Configuration + +| Variable | Description | +|----------|-------------| +| `K8S_CLUSTER_ROUTER_BASE` | OpenShift ingress domain | + +### Keycloak Configuration (when deployed) + +| Variable | Description | +|----------|-------------| +| `KEYCLOAK_BASE_URL` | Keycloak instance URL | +| `KEYCLOAK_REALM` | Configured realm name | +| `KEYCLOAK_CLIENT_ID` | OIDC client ID | +| `KEYCLOAK_CLIENT_SECRET` | OIDC client secret | +| `KEYCLOAK_METADATA_URL` | OIDC discovery URL | +| `KEYCLOAK_LOGIN_REALM` | Login realm name | + +## Default Keycloak Configuration + +When Keycloak is deployed via global setup, these defaults are applied: + +### Realm + +| Setting | Value | +|---------|-------| +| Realm name | `rhdh` | + +### Client + +| Setting | Value | +|---------|-------| +| Client ID | `rhdh-client` | +| Client Secret | `rhdh-client-secret` | +| Standard flow | Enabled | +| Implicit flow | Enabled | +| Direct access grants | Enabled | +| Service accounts | Enabled | + +### Groups + +- `developers` +- `admins` +- `viewers` + +### Users + +| Username | Password | Groups | +|----------|----------|--------| +| `test1` | `test1@123` | developers | +| `test2` | `test2@123` | developers | + +## Global Setup Behavior + +### Existing Keycloak + +If Keycloak is already running in the `rhdh-keycloak` namespace: + +- Deployment is skipped +- Existing Keycloak is reused +- Environment variables are still set + +### CI vs Local + +| Environment | Behavior | +|-------------|----------| +| CI (`CI=true`) | Full setup, resources cleaned up after | +| Local | Setup runs, resources preserved for debugging | + +## Customizing Global Setup + +The global setup is automatically included when using `defineConfig`. To skip it entirely: + +```typescript +import { baseConfig } from "rhdh-e2e-test-utils/playwright-config"; +import { defineConfig } from "@playwright/test"; + +export default defineConfig({ + ...baseConfig, + globalSetup: undefined, // Disable global setup + projects: [{ name: "my-plugin" }], +}); +``` + +## Troubleshooting + +### Binary Not Found + +``` +Error: Required binary 'helm' not found in PATH +``` + +**Solution:** Install the missing binary and ensure it's in your PATH. + +### Cluster Connection Failed + +``` +Error: Unable to connect to cluster +``` + +**Solution:** Verify you're logged into the OpenShift cluster: + +```bash +oc whoami +oc cluster-info +``` + +### Keycloak Deployment Failed + +``` +Error: Keycloak deployment timed out +``` + +**Solution:** +1. Check cluster resources are available +2. Check PersistentVolumeClaims can be created +3. Manually check the `rhdh-keycloak` namespace for errors + +```bash +oc get pods -n rhdh-keycloak +oc describe pod -n rhdh-keycloak keycloak-0 +``` diff --git a/docs/guide/core-concepts/index.md b/docs/guide/core-concepts/index.md new file mode 100644 index 0000000..38b134e --- /dev/null +++ b/docs/guide/core-concepts/index.md @@ -0,0 +1,81 @@ +# Core Concepts + +This section covers the fundamental concepts you need to understand when using `rhdh-e2e-test-utils`. + +## Package Architecture + +The package is organized into several modules, each with a specific purpose: + +``` +rhdh-e2e-test-utils +├── /test → Playwright fixtures +├── /playwright-config → Base Playwright configuration +├── /rhdh → RHDH deployment class +├── /keycloak → Keycloak deployment helper +├── /utils → Utility functions +├── /helpers → UI, Login, API helpers +├── /pages → Page object classes +├── /eslint → ESLint configuration +└── /tsconfig → TypeScript configuration +``` + +## Key Concepts + +### 1. Playwright Fixtures + +The package extends Playwright's test framework with custom fixtures: + +- **`rhdh`** - Manages RHDH deployment lifecycle +- **`uiHelper`** - Provides UI interaction methods +- **`loginHelper`** - Handles authentication +- **`baseURL`** - Automatically set to RHDH URL + +[Learn more about Playwright Fixtures →](/guide/core-concepts/playwright-fixtures) + +### 2. Deployment Lifecycle + +Tests follow a deployment lifecycle: + +1. **Configure** - Set deployment options (auth, plugins, configs) +2. **Deploy** - Create namespace, apply configs, install RHDH +3. **Test** - Run Playwright tests +4. **Cleanup** - Delete namespace (in CI) + +```typescript +test.beforeAll(async ({ rhdh }) => { + await rhdh.configure({ auth: "keycloak" }); + await rhdh.deploy(); +}); +``` + +### 3. Configuration Merging + +Configurations are merged in layers: + +1. **Common configs** - Base configurations included with the package +2. **Auth configs** - Provider-specific configs (guest, keycloak) +3. **Project configs** - Your custom configurations + +This allows you to override only what you need while using sensible defaults. + +### 4. Namespace Management + +- Namespaces are derived from Playwright project names +- Each worker gets its own namespace +- Automatic cleanup in CI environments + +### 5. Authentication Providers + +Two authentication providers are supported: + +| Provider | Use Case | +|----------|----------| +| `guest` | Development and simple testing | +| `keycloak` | OIDC authentication testing | + +## In This Section + +- [Package Exports](/guide/core-concepts/package-exports) - All available exports +- [Playwright Fixtures](/guide/core-concepts/playwright-fixtures) - Custom fixtures +- [Playwright Configuration](/guide/core-concepts/playwright-config) - Configuration options +- [Global Setup](/guide/core-concepts/global-setup) - Pre-test setup behavior diff --git a/docs/guide/core-concepts/package-exports.md b/docs/guide/core-concepts/package-exports.md new file mode 100644 index 0000000..ff7a826 --- /dev/null +++ b/docs/guide/core-concepts/package-exports.md @@ -0,0 +1,168 @@ +# Package Exports + +The package provides multiple entry points for different use cases. Each export is designed for a specific purpose. + +## Export Summary + +| Export Path | Description | +|-------------|-------------| +| `rhdh-e2e-test-utils/test` | Playwright test fixtures with RHDH deployment | +| `rhdh-e2e-test-utils/playwright-config` | Base Playwright configuration | +| `rhdh-e2e-test-utils/rhdh` | RHDH deployment class and types | +| `rhdh-e2e-test-utils/keycloak` | Keycloak deployment helper | +| `rhdh-e2e-test-utils/utils` | Utility functions (bash, YAML, Kubernetes) | +| `rhdh-e2e-test-utils/helpers` | UI, API, and login helper classes | +| `rhdh-e2e-test-utils/pages` | Page object classes for common RHDH pages | +| `rhdh-e2e-test-utils/eslint` | ESLint configuration factory | +| `rhdh-e2e-test-utils/tsconfig` | Base TypeScript configuration | + +## Detailed Exports + +### Test Fixtures (`/test`) + +```typescript +import { test, expect } from "rhdh-e2e-test-utils/test"; +``` + +Provides extended Playwright test with RHDH-specific fixtures: +- `rhdh` - RHDHDeployment instance +- `uiHelper` - UIhelper instance +- `loginHelper` - LoginHelper instance +- `baseURL` - RHDH instance URL + +### Playwright Configuration (`/playwright-config`) + +```typescript +import { defineConfig, baseConfig } from "rhdh-e2e-test-utils/playwright-config"; +``` + +- `defineConfig(options)` - Create Playwright config with defaults +- `baseConfig` - Raw base configuration object + +### RHDH Deployment (`/rhdh`) + +```typescript +import { RHDHDeployment } from "rhdh-e2e-test-utils/rhdh"; +import type { DeploymentOptions, DeploymentConfig } from "rhdh-e2e-test-utils/rhdh"; +``` + +- `RHDHDeployment` - Class for managing RHDH deployments +- Type definitions for deployment options + +### Keycloak Helper (`/keycloak`) + +```typescript +import { KeycloakHelper } from "rhdh-e2e-test-utils/keycloak"; +import type { KeycloakDeploymentOptions } from "rhdh-e2e-test-utils/keycloak"; +``` + +- `KeycloakHelper` - Class for Keycloak deployment and management +- Type definitions for Keycloak configuration + +### Utilities (`/utils`) + +```typescript +import { $, KubernetesClientHelper, envsubst, mergeYamlFiles } from "rhdh-e2e-test-utils/utils"; +``` + +- `$` - Bash command execution via zx +- `KubernetesClientHelper` - Kubernetes API wrapper +- `envsubst` - Environment variable substitution +- `mergeYamlFiles` - YAML file merging + +### Helpers (`/helpers`) + +```typescript +import { UIhelper, LoginHelper, APIHelper, setupBrowser } from "rhdh-e2e-test-utils/helpers"; +``` + +- `UIhelper` - Material-UI component interactions +- `LoginHelper` - Authentication flows +- `APIHelper` - GitHub and Backstage API operations +- `setupBrowser` - Shared browser context setup + +### Pages (`/pages`) + +```typescript +import { + CatalogPage, + HomePage, + CatalogImportPage, + ExtensionsPage, + NotificationPage, +} from "rhdh-e2e-test-utils/pages"; +``` + +Page object classes for common RHDH pages. + +### ESLint Configuration (`/eslint`) + +```typescript +import { createEslintConfig } from "rhdh-e2e-test-utils/eslint"; +``` + +Factory function for creating ESLint flat config with Playwright and TypeScript rules. + +### TypeScript Configuration (`/tsconfig`) + +```json +{ + "extends": "rhdh-e2e-test-utils/tsconfig" +} +``` + +Base TypeScript configuration to extend in your project. + +## Usage Patterns + +### Minimal Test Setup + +```typescript +// playwright.config.ts +import { defineConfig } from "rhdh-e2e-test-utils/playwright-config"; +export default defineConfig({ projects: [{ name: "my-plugin" }] }); + +// tests/my-plugin.spec.ts +import { test, expect } from "rhdh-e2e-test-utils/test"; + +test.beforeAll(async ({ rhdh }) => { + await rhdh.deploy(); +}); + +test("example", async ({ page }) => { + await page.goto("/"); +}); +``` + +### Advanced Usage with Helpers + +```typescript +import { test, expect } from "rhdh-e2e-test-utils/test"; +import { CatalogPage } from "rhdh-e2e-test-utils/pages"; +import { APIHelper } from "rhdh-e2e-test-utils/helpers"; + +test("catalog test", async ({ page, uiHelper, loginHelper }) => { + await loginHelper.loginAsKeycloakUser(); + + const catalogPage = new CatalogPage(page); + await catalogPage.go(); + await catalogPage.search("my-component"); + + await uiHelper.verifyRowsInTable(["my-component"]); +}); +``` + +### Direct Deployment Control + +```typescript +import { RHDHDeployment } from "rhdh-e2e-test-utils/rhdh"; +import { KeycloakHelper } from "rhdh-e2e-test-utils/keycloak"; + +const keycloak = new KeycloakHelper(); +await keycloak.deploy(); +await keycloak.configureForRHDH(); + +const rhdh = new RHDHDeployment("my-namespace"); +await rhdh.configure({ auth: "keycloak" }); +await rhdh.deploy(); +``` diff --git a/docs/guide/core-concepts/playwright-config.md b/docs/guide/core-concepts/playwright-config.md new file mode 100644 index 0000000..34ec8e2 --- /dev/null +++ b/docs/guide/core-concepts/playwright-config.md @@ -0,0 +1,220 @@ +# Playwright Configuration + +The package provides a pre-configured Playwright setup optimized for RHDH testing. + +## Using `defineConfig` + +```typescript +// playwright.config.ts +import { defineConfig } from "rhdh-e2e-test-utils/playwright-config"; + +export default defineConfig({ + projects: [ + { + name: "my-plugin", + }, + ], +}); +``` + +The `defineConfig` function extends your configuration with sensible defaults for RHDH testing. + +## Base Configuration Defaults + +| Setting | Value | Description | +|---------|-------|-------------| +| `testDir` | `./tests` | Test files location | +| `timeout` | 90,000ms | Test timeout | +| `expect.timeout` | 30,000ms | Assertion timeout | +| `retries` | 2 (CI), 0 (local) | Test retries | +| `workers` | 50% of CPUs | Parallel workers | +| `fullyParallel` | `true` | Parallel test execution | + +### Reporter Settings + +| Setting | Value | +|---------|-------| +| `reporter` | `[["list"], ["html"]]` | + +### Browser Settings + +| Setting | Value | +|---------|-------| +| `viewport` | `{ width: 1920, height: 1080 }` | +| `video` | `"on"` | +| `trace` | `"retain-on-failure"` | +| `screenshot` | `"only-on-failure"` | + +## Global Setup + +The base configuration includes a global setup that runs before all tests: + +1. **Binary Validation**: Checks for `oc`, `kubectl`, `helm` +2. **Cluster Configuration**: Fetches OpenShift ingress domain +3. **Keycloak Deployment**: Automatically deploys Keycloak (unless skipped) + +## Customizing Configuration + +You can override any setting by passing it to `defineConfig`: + +```typescript +import { defineConfig } from "rhdh-e2e-test-utils/playwright-config"; + +export default defineConfig({ + // Override timeout + timeout: 120000, + + // Override retries + retries: 3, + + // Override workers + workers: 2, + + // Add projects + projects: [ + { + name: "my-plugin", + testMatch: "**/*.spec.ts", + }, + { + name: "another-plugin", + testMatch: "**/another-*.spec.ts", + }, + ], + + // Add custom reporter + reporter: [["list"], ["html"], ["json", { outputFile: "results.json" }]], +}); +``` + +## Project Configuration + +Each project in Playwright becomes a separate namespace in OpenShift: + +```typescript +export default defineConfig({ + projects: [ + { + name: "tech-radar", // Namespace: tech-radar + }, + { + name: "catalog", // Namespace: catalog + }, + { + name: "topology", // Namespace: topology + }, + ], +}); +``` + +### Project-Specific Settings + +```typescript +export default defineConfig({ + projects: [ + { + name: "my-plugin", + testDir: "./tests/my-plugin", + testMatch: "**/*.spec.ts", + use: { + // Project-specific browser settings + viewport: { width: 1280, height: 720 }, + }, + }, + ], +}); +``` + +## Environment Variables for Configuration + +```bash +# Affects retries (2 in CI, 0 locally) +CI=true + +# Custom test directory +# (set via defineConfig, not env var) +``` + +## Using Base Config Directly + +For advanced customization, you can access the raw base config: + +```typescript +import { baseConfig } from "rhdh-e2e-test-utils/playwright-config"; +import { defineConfig as playwrightDefineConfig } from "@playwright/test"; + +export default playwrightDefineConfig({ + ...baseConfig, + // Your complete custom configuration + timeout: 60000, + projects: [{ name: "custom" }], +}); +``` + +## Loading Environment Variables + +Use `dotenv` to load environment variables from a `.env` file: + +```typescript +import { defineConfig } from "rhdh-e2e-test-utils/playwright-config"; +import dotenv from "dotenv"; + +// Load .env file +dotenv.config({ path: `${import.meta.dirname}/.env` }); + +export default defineConfig({ + projects: [{ name: "my-plugin" }], +}); +``` + +Create a `.env` file: + +```bash +RHDH_VERSION="1.5" +INSTALLATION_METHOD="helm" +SKIP_KEYCLOAK_DEPLOYMENT=false +GITHUB_TOKEN=ghp_xxxxx +``` + +## Example: Full Configuration + +```typescript +import { defineConfig } from "rhdh-e2e-test-utils/playwright-config"; +import dotenv from "dotenv"; + +dotenv.config({ path: `${import.meta.dirname}/.env` }); + +export default defineConfig({ + // Test settings + timeout: 120000, + retries: process.env.CI ? 3 : 0, + workers: process.env.CI ? 4 : 2, + + // Reporter + reporter: [ + ["list"], + ["html", { open: "never" }], + ["junit", { outputFile: "test-results/junit.xml" }], + ], + + // Projects + projects: [ + { + name: "tech-radar", + testDir: "./tests/tech-radar", + }, + { + name: "catalog", + testDir: "./tests/catalog", + }, + ], + + // Browser settings + use: { + viewport: { width: 1920, height: 1080 }, + video: "on", + trace: "retain-on-failure", + screenshot: "only-on-failure", + }, +}); +``` diff --git a/docs/guide/core-concepts/playwright-fixtures.md b/docs/guide/core-concepts/playwright-fixtures.md new file mode 100644 index 0000000..0fbd830 --- /dev/null +++ b/docs/guide/core-concepts/playwright-fixtures.md @@ -0,0 +1,198 @@ +# Playwright Fixtures + +The package extends Playwright's test framework with custom fixtures designed for RHDH testing. + +## Importing Fixtures + +```typescript +import { test, expect } from "rhdh-e2e-test-utils/test"; +``` + +This import replaces the standard Playwright import and provides additional fixtures. + +## Available Fixtures + +| Fixture | Scope | Type | Description | +|---------|-------|------|-------------| +| `rhdh` | worker | `RHDHDeployment` | Shared RHDH deployment across all tests in a worker | +| `uiHelper` | test | `UIhelper` | UI interaction helper for Material-UI components | +| `loginHelper` | test | `LoginHelper` | Authentication helper for various providers | +| `baseURL` | test | `string` | Automatically set to the RHDH instance URL | + +## Fixture Scopes + +### Worker-Scoped Fixtures + +The `rhdh` fixture is worker-scoped, meaning: + +- One deployment is shared across all tests in a worker +- Deployment happens once per worker, not per test +- All tests in the same worker share the same RHDH instance + +```typescript +test.beforeAll(async ({ rhdh }) => { + // This runs once per worker + await rhdh.configure({ auth: "keycloak" }); + await rhdh.deploy(); +}); +``` + +### Test-Scoped Fixtures + +The `uiHelper`, `loginHelper`, and `baseURL` fixtures are test-scoped: + +- Created fresh for each test +- Tied to the test's page instance + +```typescript +test("example", async ({ uiHelper, loginHelper }) => { + // Fresh instances for this test + await loginHelper.loginAsKeycloakUser(); + await uiHelper.verifyHeading("Welcome"); +}); +``` + +## Fixture Details + +### `rhdh` Fixture + +The `rhdh` fixture provides access to the `RHDHDeployment` instance: + +```typescript +test.beforeAll(async ({ rhdh }) => { + // Configure deployment options + await rhdh.configure({ + auth: "keycloak", + appConfig: "tests/config/app-config.yaml", + }); + + // Deploy RHDH + await rhdh.deploy(); +}); + +test("access deployment info", async ({ rhdh }) => { + console.log(`URL: ${rhdh.rhdhUrl}`); + console.log(`Namespace: ${rhdh.deploymentConfig.namespace}`); + + // Access Kubernetes client + const route = await rhdh.k8sClient.getRouteLocation( + rhdh.deploymentConfig.namespace, + "my-route" + ); +}); +``` + +### `uiHelper` Fixture + +The `uiHelper` fixture provides UI interaction methods: + +```typescript +test("UI interactions", async ({ uiHelper }) => { + // Wait for page to load + await uiHelper.waitForLoad(); + + // Verify content + await uiHelper.verifyHeading("Welcome"); + await uiHelper.verifyText("Some content"); + + // Navigate + await uiHelper.openSidebar("Catalog"); + await uiHelper.clickTab("Overview"); + + // Interact with forms + await uiHelper.fillTextInputByLabel("Name", "my-component"); + await uiHelper.clickButton("Submit"); +}); +``` + +### `loginHelper` Fixture + +The `loginHelper` fixture handles authentication: + +```typescript +test.beforeEach(async ({ loginHelper }) => { + // Guest authentication + await loginHelper.loginAsGuest(); + + // Or Keycloak authentication + await loginHelper.loginAsKeycloakUser(); + + // Or with specific credentials + await loginHelper.loginAsKeycloakUser("test1", "test1@123"); +}); + +test.afterEach(async ({ loginHelper }) => { + await loginHelper.signOut(); +}); +``` + +### `baseURL` Fixture + +The `baseURL` fixture is automatically set to the RHDH URL: + +```typescript +test("using baseURL", async ({ page, baseURL }) => { + // page.goto("/") automatically uses baseURL + await page.goto("/"); + + // Equivalent to: + await page.goto(baseURL); +}); +``` + +## Namespace Derivation + +The namespace for each worker is derived from the Playwright project name: + +```typescript +// playwright.config.ts +export default defineConfig({ + projects: [ + { name: "tech-radar" }, // Namespace: tech-radar + { name: "catalog" }, // Namespace: catalog + ], +}); +``` + +## Auto-Cleanup + +In CI environments (when `CI` environment variable is set): + +- Namespaces are automatically deleted after tests complete +- Prevents resource accumulation on shared clusters + +For local development: +- Namespaces are preserved for debugging +- Manual cleanup may be required + +## Example: Complete Test Setup + +```typescript +import { test, expect } from "rhdh-e2e-test-utils/test"; + +test.describe("My Plugin Tests", () => { + test.beforeAll(async ({ rhdh }) => { + await rhdh.configure({ + auth: "keycloak", + appConfig: "tests/config/app-config.yaml", + dynamicPlugins: "tests/config/plugins.yaml", + }); + await rhdh.deploy(); + }); + + test.beforeEach(async ({ page, loginHelper }) => { + await page.goto("/"); + await loginHelper.loginAsKeycloakUser(); + }); + + test("should display heading", async ({ uiHelper }) => { + await uiHelper.openSidebar("My Plugin"); + await uiHelper.verifyHeading("My Plugin"); + }); + + test("should show data", async ({ uiHelper }) => { + await uiHelper.openSidebar("My Plugin"); + await uiHelper.verifyRowsInTable(["Item 1", "Item 2"]); + }); +}); +``` diff --git a/docs/guide/core-concepts/testing-patterns.md b/docs/guide/core-concepts/testing-patterns.md new file mode 100644 index 0000000..c7b0197 --- /dev/null +++ b/docs/guide/core-concepts/testing-patterns.md @@ -0,0 +1,282 @@ +# Testing Patterns: Serial vs Parallel + +This guide explains when and how to use serial vs parallel test execution patterns. + +## Overview + +| Pattern | Execution | Browser Context | Use Case | +|---------|-----------|-----------------|----------| +| **Parallel** (default) | Tests run concurrently | Each test gets fresh context | Independent tests | +| **Serial** | Tests run sequentially | Shared browser context | Workflow tests | + +## Parallel Tests (Default) + +By default, Playwright runs tests in parallel with isolated browser contexts. + +### Characteristics + +- **Isolation**: Each test gets a fresh browser context +- **Speed**: Tests run concurrently across workers +- **Independence**: Tests cannot affect each other +- **Login overhead**: Each test must login separately + +### When to Use + +- Tests that don't depend on each other +- Tests that modify shared state (to avoid conflicts) +- General functional tests +- Smoke tests + +### Example + +```typescript +import { test, expect } from "rhdh-e2e-test-utils/test"; + +// Each test runs independently with its own browser context +test.describe("Catalog Tests", () => { + test.beforeEach(async ({ loginHelper }) => { + // Each test logs in fresh + await loginHelper.loginAsGuest(); + }); + + test("can view catalog", async ({ uiHelper }) => { + await uiHelper.openSidebar("Catalog"); + await uiHelper.verifyHeading("My Org Catalog"); + }); + + test("can search catalog", async ({ uiHelper }) => { + await uiHelper.openSidebar("Catalog"); + await uiHelper.searchInputPlaceholder("Filter", "component"); + }); + + test("can filter by kind", async ({ uiHelper }) => { + await uiHelper.openSidebar("Catalog"); + await uiHelper.selectMuiBox("Kind", "API"); + }); +}); +``` + +### Configuration + +```typescript +// playwright.config.ts +import { defineConfig } from "@playwright/test"; +import { baseConfig } from "rhdh-e2e-test-utils/playwright-config"; + +export default defineConfig({ + ...baseConfig, + workers: 4, // Run 4 tests in parallel + fullyParallel: true, // All tests in parallel +}); +``` + +## Serial Tests (Shared Session) + +Serial tests share a browser context and run in sequence. + +### Characteristics + +- **Shared state**: Tests share the same browser session +- **Order dependency**: Tests run in defined order +- **Efficiency**: Single login for all tests +- **Workflow testing**: Perfect for multi-step flows + +### When to Use + +- Testing multi-step workflows +- Tests that build on each other +- Resource-intensive operations (single login) +- State-dependent test sequences + +### Example + +```typescript +import { test } from "@playwright/test"; +import { setupBrowser, UIhelper, LoginHelper } from "rhdh-e2e-test-utils/helpers"; + +// Configure as serial +test.describe.configure({ mode: "serial" }); + +test.describe("Entity Creation Workflow", () => { + let uiHelper: UIhelper; + let loginHelper: LoginHelper; + + test.beforeAll(async ({ browser }) => { + // Setup shared browser context + const context = await setupBrowser(browser); + const page = await context.newPage(); + uiHelper = new UIhelper(page); + loginHelper = new LoginHelper(page, uiHelper); + + // Single login for all tests + await loginHelper.loginAsGuest(); + }); + + test("step 1: navigate to create", async () => { + await uiHelper.openSidebar("Create..."); + await uiHelper.verifyHeading("Create"); + }); + + test("step 2: fill template form", async () => { + // Uses same session from step 1 + await uiHelper.clickButton("Choose"); + await uiHelper.fillTextInputByLabel("Name", "my-new-component"); + await uiHelper.fillTextInputByLabel("Description", "A test component"); + }); + + test("step 3: submit and verify", async () => { + // Uses same session from steps 1-2 + await uiHelper.clickButton("Create"); + await uiHelper.verifyHeading("my-new-component"); + }); + + test("step 4: verify in catalog", async () => { + // Uses same session from previous steps + await uiHelper.openSidebar("Catalog"); + await uiHelper.verifyRowsInTable(["my-new-component"]); + }); +}); +``` + +### Key Differences from Parallel + +| Aspect | Parallel | Serial | +|--------|----------|--------| +| `test.describe` | Normal | `test.describe.configure({ mode: "serial" })` | +| Fixtures | Use `{ uiHelper }` | Create manually with `new UIhelper(page)` | +| Browser setup | Automatic | Use `setupBrowser(browser)` | +| Login | Each `beforeEach` | Once in `beforeAll` | +| State between tests | Isolated | Shared | + +## Hybrid Approach + +You can mix parallel and serial within the same test file: + +```typescript +import { test, expect } from "rhdh-e2e-test-utils/test"; +import { setupBrowser, UIhelper, LoginHelper } from "rhdh-e2e-test-utils/helpers"; + +// Parallel tests (use fixtures) +test.describe("Parallel Catalog Tests", () => { + test.beforeEach(async ({ loginHelper }) => { + await loginHelper.loginAsGuest(); + }); + + test("view catalog", async ({ uiHelper }) => { + await uiHelper.openSidebar("Catalog"); + }); + + test("view APIs", async ({ uiHelper }) => { + await uiHelper.openSidebar("APIs"); + }); +}); + +// Serial tests (shared session) +test.describe("Serial Workflow Tests", () => { + test.describe.configure({ mode: "serial" }); + + let uiHelper: UIhelper; + let loginHelper: LoginHelper; + + test.beforeAll(async ({ browser }) => { + const context = await setupBrowser(browser); + const page = await context.newPage(); + uiHelper = new UIhelper(page); + loginHelper = new LoginHelper(page, uiHelper); + await loginHelper.loginAsGuest(); + }); + + test("step 1", async () => { /* ... */ }); + test("step 2", async () => { /* ... */ }); +}); +``` + +## Best Practices + +### For Parallel Tests + +1. **Keep tests independent** - Each test should work in isolation +2. **Use unique data** - Generate unique names to avoid conflicts +3. **Clean up after** - Remove any created resources +4. **Don't rely on order** - Tests may run in any sequence + +```typescript +test("create entity with unique name", async ({ apiHelper }) => { + const uniqueName = `test-${Date.now()}-${Math.random().toString(36).slice(2)}`; + + await apiHelper.importEntity(`https://example.com/${uniqueName}/catalog-info.yaml`); + + // Clean up + test.afterEach(async () => { + await apiHelper.deleteEntity(uniqueName); + }); +}); +``` + +### For Serial Tests + +1. **Use meaningful step names** - Makes failures easier to diagnose +2. **Handle cleanup properly** - Clean up in `afterAll`, not `afterEach` +3. **Consider failure impact** - Later tests may fail if early ones fail +4. **Minimize shared state** - Only share what's necessary + +```typescript +test.describe("Workflow with proper cleanup", () => { + test.describe.configure({ mode: "serial" }); + + let createdEntity: string; + + test.afterAll(async ({ browser }) => { + // Clean up after all tests complete (or fail) + if (createdEntity) { + // Cleanup logic here + } + }); + + test("create entity", async () => { + createdEntity = "my-entity"; + // ... creation logic + }); +}); +``` + +## Performance Comparison + +| Scenario | Parallel (4 workers) | Serial | +|----------|---------------------|--------| +| 10 independent tests | ~30 seconds | ~2 minutes | +| 5-step workflow | N/A (can't parallelize) | ~1 minute | +| 10 tests with shared login | ~30 sec + 10 logins | ~1 min + 1 login | + +## Decision Flowchart + +``` +┌─────────────────────────────────────────┐ +│ Do tests depend on each other's state? │ +└─────────────────────┬───────────────────┘ + │ + ┌────────────┴────────────┐ + │ │ + Yes No + │ │ + ▼ ▼ +┌─────────────────┐ ┌─────────────────────┐ +│ Use Serial │ │ Is login expensive? │ +│ mode: "serial" │ └──────────┬──────────┘ +└─────────────────┘ │ + ┌───────────┴───────────┐ + │ │ + Yes No + │ │ + ▼ ▼ + ┌────────────────┐ ┌─────────────────┐ + │Consider Serial │ │ Use Parallel │ + │for same user │ │ (default) │ + └────────────────┘ └─────────────────┘ +``` + +## Related Pages + +- [Error Handling](./error-handling.md) - Handle failures gracefully +- [Playwright Fixtures](./playwright-fixtures.md) - Understanding fixtures +- [Serial Tests Example](/examples/serial-tests.md) - Complete example diff --git a/docs/guide/deployment/authentication.md b/docs/guide/deployment/authentication.md new file mode 100644 index 0000000..989e81a --- /dev/null +++ b/docs/guide/deployment/authentication.md @@ -0,0 +1,220 @@ +# Authentication Providers + +The package supports modular authentication configuration, allowing you to switch between providers with a single option. + +## Available Providers + +| Provider | Description | Use Case | +|----------|-------------|----------| +| `guest` | Simple guest authentication | Development, simple tests | +| `keycloak` | OIDC via Keycloak | Production-like auth testing | + +## Guest Authentication + +Guest authentication allows users to enter without credentials, using a simple "Enter as Guest" button. + +### Configuration + +```typescript +await rhdh.configure({ auth: "guest" }); +await rhdh.deploy(); +``` + +### Usage in Tests + +```typescript +test.beforeEach(async ({ loginHelper }) => { + await loginHelper.loginAsGuest(); +}); +``` + +### When to Use Guest Auth + +- Quick development testing +- Tests that don't require user identity +- Simplified CI/CD pipelines +- Tests focused on UI behavior, not auth + +### Skipping Keycloak Deployment + +When using guest auth, skip Keycloak deployment: + +```bash +SKIP_KEYCLOAK_DEPLOYMENT=true yarn playwright test +``` + +## Keycloak Authentication + +Keycloak provides OIDC authentication for realistic auth testing. + +### Configuration + +```typescript +await rhdh.configure({ auth: "keycloak" }); +await rhdh.deploy(); +``` + +### Prerequisites + +Keycloak must be deployed and configured. This happens automatically via global setup unless skipped. + +### Usage in Tests + +```typescript +test.beforeEach(async ({ loginHelper }) => { + // Use default test user (test1/test1@123) + await loginHelper.loginAsKeycloakUser(); + + // Or specify credentials + await loginHelper.loginAsKeycloakUser("test1", "test1@123"); +}); +``` + +### Default Keycloak Users + + + +For more details, see [Keycloak Deployment](./keycloak-deployment.md#default-configuration). + +### Creating Custom Users + +```typescript +import { KeycloakHelper } from "rhdh-e2e-test-utils/keycloak"; + +test.beforeAll(async ({ rhdh }) => { + const keycloak = new KeycloakHelper(); + + // Connect to existing Keycloak + await keycloak.connect({ + baseUrl: process.env.KEYCLOAK_BASE_URL!, + username: "admin", + password: "admin123", + }); + + // Create custom users + await keycloak.createUser("rhdh", { + username: "admin-user", + password: "adminpass", + groups: ["admins"], + }); + + await keycloak.createUser("rhdh", { + username: "viewer-user", + password: "viewerpass", + groups: ["viewers"], + }); + + await rhdh.configure({ auth: "keycloak" }); + await rhdh.deploy(); +}); +``` + +### When to Use Keycloak Auth + +- Testing role-based access control +- Testing user-specific features +- Production-like testing scenarios +- Testing logout/session flows + +## Environment Variables + +### Guest Auth + +No additional environment variables required. + +### Keycloak Auth + +These are automatically set by `KeycloakHelper.configureForRHDH()`: + +| Variable | Description | +|----------|-------------| +| `KEYCLOAK_BASE_URL` | Keycloak instance URL | +| `KEYCLOAK_REALM` | Realm name | +| `KEYCLOAK_CLIENT_ID` | OIDC client ID | +| `KEYCLOAK_CLIENT_SECRET` | OIDC client secret | +| `KEYCLOAK_METADATA_URL` | OIDC discovery URL | +| `KEYCLOAK_LOGIN_REALM` | Login realm name | + +## Configuration Merging + +When you set `auth: "guest"` or `auth: "keycloak"`, the package automatically includes auth-specific configurations: + +``` +Package configs: +├── common/ # Always applied +│ ├── app-config-rhdh.yaml +│ ├── dynamic-plugins.yaml +│ └── rhdh-secrets.yaml +└── auth/ + ├── guest/ # Applied when auth: "guest" + │ └── app-config.yaml + └── keycloak/ # Applied when auth: "keycloak" + ├── app-config.yaml + ├── dynamic-plugins.yaml + └── secrets.yaml +``` + +Your project configs are merged on top, so you only need to override what's different. + +## GitHub Authentication + +GitHub authentication is available via `LoginHelper`: + +```typescript +// Requires environment variables: +// GH_USER_NAME, GH_USER_PASSWORD, GH_2FA_SECRET + +test.beforeEach(async ({ loginHelper }) => { + await loginHelper.loginAsGithubUser(); +}); +``` + +::: warning +GitHub authentication requires 2FA secret for automated logins. This is more complex to set up than guest or Keycloak auth. +::: + +## Switching Auth Providers + +### In Different Test Files + +```typescript +// guest-tests.spec.ts +test.beforeAll(async ({ rhdh }) => { + await rhdh.configure({ auth: "guest" }); + await rhdh.deploy(); +}); + +// keycloak-tests.spec.ts +test.beforeAll(async ({ rhdh }) => { + await rhdh.configure({ auth: "keycloak" }); + await rhdh.deploy(); +}); +``` + +### In Different Projects + +```typescript +// playwright.config.ts +export default defineConfig({ + projects: [ + { + name: "guest-tests", + testMatch: "**/guest-*.spec.ts", + }, + { + name: "keycloak-tests", + testMatch: "**/keycloak-*.spec.ts", + }, + ], +}); +``` + +Each project gets its own namespace and deployment with different auth. + +## Best Practices + +1. **Use guest auth for speed** - Faster to set up and run +2. **Use Keycloak for RBAC testing** - When you need user roles +3. **Create test users per test suite** - Avoid shared state +4. **Clean up custom users** - Remove users created during tests +5. **Use environment variables** - Don't hardcode credentials diff --git a/docs/guide/deployment/helm-deployment.md b/docs/guide/deployment/helm-deployment.md new file mode 100644 index 0000000..0f30550 --- /dev/null +++ b/docs/guide/deployment/helm-deployment.md @@ -0,0 +1,244 @@ +# Helm Deployment + +Deploy RHDH using the official Helm chart. + +## Configuration + +Set the installation method to `helm`: + +```typescript +await rhdh.configure({ + method: "helm", + valueFile: "tests/config/value_file.yaml", +}); +``` + +Or via environment variable: + +```bash +INSTALLATION_METHOD="helm" +``` + +## Helm Values File + +Create a custom values file to override Helm chart defaults. + +### Example: `tests/config/value_file.yaml` + +```yaml +global: + clusterRouterBase: ${K8S_CLUSTER_ROUTER_BASE} + +upstream: + backstage: + image: + registry: quay.io + repository: rhdh/rhdh-hub-rhel9 + tag: ${RHDH_VERSION} + + extraEnvVars: + - name: NODE_TLS_REJECT_UNAUTHORIZED + value: "0" + + appConfig: + # App config is mounted from ConfigMap + # See app-config-rhdh.yaml + + postgresql: + enabled: true + auth: + secretKeys: + adminPasswordKey: postgres-password + userPasswordKey: password +``` + +## Default Chart URL + +The default Helm chart URL is: + +``` +oci://quay.io/rhdh/chart +``` + +Override with the `CHART_URL` environment variable: + +```bash +CHART_URL="oci://my-registry/rhdh-chart" +``` + +## Version Selection + +Set the RHDH version: + +```typescript +await rhdh.configure({ + method: "helm", + version: "1.5", +}); +``` + +Or via environment variable: + +```bash +RHDH_VERSION="1.5" +``` + +## What Happens During Helm Deployment + +1. **Namespace Creation**: Creates the Kubernetes namespace +2. **ConfigMaps**: Applies merged configuration: + - `app-config-rhdh` - Application configuration + - `dynamic-plugins` - Plugin configuration +3. **Secrets**: Applies secrets with env substitution: + - `rhdh-secrets` - Application secrets +4. **Helm Install**: Runs `helm upgrade --install`: + ```bash + helm upgrade --install backstage oci://quay.io/rhdh/chart \ + --version ${RHDH_VERSION} \ + --namespace ${NAMESPACE} \ + --values ${VALUE_FILE} + ``` +5. **Wait for Ready**: Waits for the deployment to be ready + +## Full Example + +### File Structure + +``` +e2e-tests/ +├── playwright.config.ts +├── .env +└── tests/ + ├── config/ + │ ├── app-config-rhdh.yaml + │ ├── dynamic-plugins.yaml + │ ├── rhdh-secrets.yaml + │ └── value_file.yaml + └── specs/ + └── my-plugin.spec.ts +``` + +### `.env` + +```bash +RHDH_VERSION="1.5" +INSTALLATION_METHOD="helm" +GITHUB_TOKEN=ghp_xxxxx +``` + +### `tests/config/value_file.yaml` + +```yaml +global: + clusterRouterBase: ${K8S_CLUSTER_ROUTER_BASE} + +upstream: + backstage: + image: + registry: quay.io + repository: rhdh/rhdh-hub-rhel9 + tag: ${RHDH_VERSION} + + extraEnvVars: + - name: LOG_LEVEL + value: "debug" + + extraEnvVarsSecrets: + - rhdh-secrets + + postgresql: + enabled: true +``` + +### `tests/config/app-config-rhdh.yaml` + +```yaml +app: + title: My Plugin Test Instance + +backend: + baseUrl: https://backstage-${NAMESPACE}.${K8S_CLUSTER_ROUTER_BASE} + cors: + origin: https://backstage-${NAMESPACE}.${K8S_CLUSTER_ROUTER_BASE} + +# Plugin configuration +myPlugin: + enabled: true +``` + +### `tests/config/dynamic-plugins.yaml` + +```yaml +includes: + - dynamic-plugins.default.yaml + +plugins: + - package: ./dynamic-plugins/dist/my-plugin + disabled: false +``` + +### `tests/config/rhdh-secrets.yaml` + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: rhdh-secrets +type: Opaque +stringData: + GITHUB_TOKEN: ${GITHUB_TOKEN} +``` + +### Test File + +```typescript +import { test, expect } from "rhdh-e2e-test-utils/test"; + +test.beforeAll(async ({ rhdh }) => { + await rhdh.configure({ + method: "helm", + auth: "keycloak", + appConfig: "tests/config/app-config-rhdh.yaml", + secrets: "tests/config/rhdh-secrets.yaml", + dynamicPlugins: "tests/config/dynamic-plugins.yaml", + valueFile: "tests/config/value_file.yaml", + }); + + await rhdh.deploy(); +}); + +test("verify deployment", async ({ page, rhdh }) => { + await page.goto(rhdh.rhdhUrl); + await expect(page).toHaveTitle(/Red Hat Developer Hub/); +}); +``` + +## Troubleshooting + +### Chart Not Found + +``` +Error: chart "rhdh" not found +``` + +**Solution**: Verify the chart URL and version are correct: + +```bash +helm show chart oci://quay.io/rhdh/chart --version 1.5 +``` + +### Image Pull Errors + +``` +Error: ImagePullBackOff +``` + +**Solution**: Check the image registry and tag in your values file. + +### PVC Pending + +``` +Error: PersistentVolumeClaim is pending +``` + +**Solution**: Ensure your cluster has a default StorageClass or specify one in the values file. diff --git a/docs/guide/deployment/index.md b/docs/guide/deployment/index.md new file mode 100644 index 0000000..563d272 --- /dev/null +++ b/docs/guide/deployment/index.md @@ -0,0 +1,168 @@ +# Deployment Overview + +The package provides comprehensive deployment capabilities for RHDH and Keycloak to OpenShift clusters. + +## Deployment Architecture + +``` +┌─────────────────────────────────────────────────────────────┐ +│ OpenShift Cluster │ +│ │ +│ ┌─────────────────────────┐ ┌─────────────────────────┐ │ +│ │ RHDH Namespace │ │ Keycloak Namespace │ │ +│ │ (e.g., my-plugin) │ │ (rhdh-keycloak) │ │ +│ │ │ │ │ │ +│ │ ┌──────────────────┐ │ │ ┌──────────────────┐ │ │ +│ │ │ RHDH Deployment │ │ │ │ Keycloak │ │ │ +│ │ │ or Backstage CR │ │ │ │ StatefulSet │ │ │ +│ │ └──────────────────┘ │ │ └──────────────────┘ │ │ +│ │ │ │ │ │ +│ │ ┌──────────────────┐ │ │ ┌──────────────────┐ │ │ +│ │ │ ConfigMaps │ │ │ │ PostgreSQL │ │ │ +│ │ │ - app-config │ │ │ │ StatefulSet │ │ │ +│ │ │ - dynamic-plugins│ │ │ └──────────────────┘ │ │ +│ │ └──────────────────┘ │ │ │ │ +│ │ │ │ ┌──────────────────┐ │ │ +│ │ ┌──────────────────┐ │ │ │ Routes │ │ │ +│ │ │ Secrets │ │ │ │ - keycloak │ │ │ +│ │ │ - rhdh-secrets │ │ │ └──────────────────┘ │ │ +│ │ └──────────────────┘ │ │ │ │ +│ │ │ └─────────────────────────┘ │ +│ │ ┌──────────────────┐ │ │ +│ │ │ Routes │ │ │ +│ │ │ - backstage │ │ │ +│ │ └──────────────────┘ │ │ +│ └─────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +## Deployment Methods + +### Helm Deployment + +Deploy RHDH using the official Helm chart: + +```typescript +await rhdh.configure({ + method: "helm", + valueFile: "tests/config/values.yaml", +}); +await rhdh.deploy(); +``` + +[Learn more about Helm Deployment →](/guide/deployment/helm-deployment) + +### Operator Deployment + +Deploy RHDH using the RHDH Operator: + +```typescript +await rhdh.configure({ + method: "operator", + subscription: "tests/config/subscription.yaml", +}); +await rhdh.deploy(); +``` + +[Learn more about Operator Deployment →](/guide/deployment/operator-deployment) + +## Authentication Providers + +### Guest Authentication + +Simple authentication for development and testing: + +```typescript +await rhdh.configure({ auth: "guest" }); +``` + +### Keycloak Authentication + +OIDC authentication via Keycloak: + +```typescript +await rhdh.configure({ auth: "keycloak" }); +``` + +[Learn more about Authentication →](/guide/deployment/authentication) + +## Deployment Lifecycle + +### 1. Configure + +Set deployment options before deploying: + +```typescript +await rhdh.configure({ + version: "1.5", + method: "helm", + auth: "keycloak", + appConfig: "tests/config/app-config.yaml", + secrets: "tests/config/secrets.yaml", + dynamicPlugins: "tests/config/plugins.yaml", +}); +``` + +### 2. Deploy + +Deploy RHDH to the cluster: + +```typescript +await rhdh.deploy(); +``` + +This: +1. Creates the namespace +2. Applies ConfigMaps +3. Applies Secrets (with env substitution) +4. Installs RHDH (Helm or Operator) +5. Waits for deployment to be ready + +### 3. Access + +Access the deployed RHDH instance: + +```typescript +const url = rhdh.rhdhUrl; +const namespace = rhdh.deploymentConfig.namespace; +``` + +### 4. Update (Optional) + +Update configuration during tests: + +```typescript +// Apply new configs +await rhdh.rolloutRestart(); +``` + +### 5. Cleanup + +Delete resources after tests (automatic in CI): + +```typescript +await rhdh.teardown(); +``` + +## Configuration Merging + +Configurations are merged in layers: + +1. **Common configs** - Base configurations +2. **Auth configs** - Provider-specific configs +3. **Project configs** - Your custom configs + +``` +Package defaults → auth/guest/ → Your configs + → auth/keycloak/ → +``` + +Later configurations override earlier ones, allowing you to customize only what you need. + +## In This Section + +- [RHDH Deployment](/guide/deployment/rhdh-deployment) - RHDHDeployment class +- [Keycloak Deployment](/guide/deployment/keycloak-deployment) - KeycloakHelper class +- [Helm Deployment](/guide/deployment/helm-deployment) - Helm-specific guide +- [Operator Deployment](/guide/deployment/operator-deployment) - Operator-specific guide +- [Authentication](/guide/deployment/authentication) - Auth providers diff --git a/docs/guide/deployment/keycloak-deployment.md b/docs/guide/deployment/keycloak-deployment.md new file mode 100644 index 0000000..fe0fc42 --- /dev/null +++ b/docs/guide/deployment/keycloak-deployment.md @@ -0,0 +1,304 @@ +# Keycloak Deployment + +The `KeycloakHelper` class provides Keycloak deployment and management capabilities for OIDC authentication testing. + +## Basic Usage + +```typescript +import { KeycloakHelper } from "rhdh-e2e-test-utils/keycloak"; + +const keycloak = new KeycloakHelper({ + namespace: "rhdh-keycloak", +}); + +// Deploy Keycloak +await keycloak.deploy(); + +// Configure for RHDH +await keycloak.configureForRHDH(); + +// Access configuration +console.log(`Keycloak URL: ${keycloak.keycloakUrl}`); +console.log(`Realm: ${keycloak.realm}`); +console.log(`Client ID: ${keycloak.clientId}`); +``` + +## Automatic Deployment via Global Setup + +When using the default Playwright configuration, Keycloak is automatically deployed in global setup: + +- Namespace: `rhdh-keycloak` +- Realm: `rhdh` +- Client: `rhdh-client` +- Users: `test1`, `test2` + +To skip automatic deployment: + +```bash +SKIP_KEYCLOAK_DEPLOYMENT=true yarn playwright test +``` + +## Deployment Options + +```typescript +type KeycloakDeploymentOptions = { + namespace?: string; // Default: "rhdh-keycloak" + releaseName?: string; // Default: "keycloak" + valuesFile?: string; // Custom Helm values file + adminUser?: string; // Default: "admin" + adminPassword?: string; // Default: "admin123" +}; +``` + +### Example: Custom Configuration + +```typescript +const keycloak = new KeycloakHelper({ + namespace: "my-keycloak", + releaseName: "my-kc", + adminUser: "admin", + adminPassword: "mySecurePassword", +}); +``` + +## Methods + +### `deploy()` + +Deploy Keycloak using the Bitnami Helm chart: + +```typescript +await keycloak.deploy(); +``` + +This: +1. Creates the namespace +2. Installs the Bitnami Keycloak Helm chart +3. Waits for the StatefulSet to be ready +4. Sets `keycloakUrl` property + +### `configureForRHDH(options?)` + +Configure Keycloak with realm, client, groups, and users for RHDH: + +```typescript +// Use defaults +await keycloak.configureForRHDH(); + +// Custom configuration +await keycloak.configureForRHDH({ + realm: "my-realm", + client: { + clientId: "my-client", + clientSecret: "my-secret", + }, + groups: ["developers", "admins"], + users: [ + { username: "user1", password: "pass1", groups: ["developers"] }, + { username: "user2", password: "pass2", groups: ["admins"] }, + ], +}); +``` + +This also sets environment variables: +- `KEYCLOAK_BASE_URL` +- `KEYCLOAK_REALM` +- `KEYCLOAK_CLIENT_ID` +- `KEYCLOAK_CLIENT_SECRET` +- `KEYCLOAK_METADATA_URL` +- `KEYCLOAK_LOGIN_REALM` + +### `isRunning()` + +Check if Keycloak is accessible: + +```typescript +const running = await keycloak.isRunning(); +if (running) { + console.log("Keycloak is ready"); +} +``` + +### `connect(config)` + +Connect to an existing Keycloak instance: + +```typescript +// With admin credentials +await keycloak.connect({ + baseUrl: "https://keycloak.example.com", + username: "admin", + password: "admin-password", +}); + +// With client credentials +await keycloak.connect({ + baseUrl: "https://keycloak.example.com", + realm: "master", + clientId: "admin-client", + clientSecret: "client-secret", +}); +``` + +### User Management + +```typescript +// Create user +await keycloak.createUser("rhdh", { + username: "newuser", + password: "password123", + email: "user@example.com", + groups: ["developers"], +}); + +// Get all users +const users = await keycloak.getUsers("rhdh"); + +// Delete user +await keycloak.deleteUser("rhdh", "username"); +``` + +### Group Management + +```typescript +// Create group +await keycloak.createGroup("rhdh", { name: "testers" }); + +// Get all groups +const groups = await keycloak.getGroups("rhdh"); + +// Delete group +await keycloak.deleteGroup("rhdh", "testers"); +``` + +### Realm Management + +```typescript +// Create realm +await keycloak.createRealm({ + realm: "new-realm", + displayName: "New Realm", + enabled: true, +}); + +// Delete realm +await keycloak.deleteRealm("new-realm"); +``` + +### Client Management + +```typescript +// Create client +await keycloak.createClient("rhdh", { + clientId: "new-client", + clientSecret: "client-secret", + standardFlowEnabled: true, + redirectUris: ["https://app.example.com/*"], +}); +``` + +### `teardown()` + +Delete the Keycloak namespace: + +```typescript +await keycloak.teardown(); +``` + +### `waitUntilReady(timeout?)` + +Wait for Keycloak to be ready: + +```typescript +await keycloak.waitUntilReady(300000); // 5 minutes +``` + +## Properties + +| Property | Type | Description | +|----------|------|-------------| +| `keycloakUrl` | `string` | Keycloak instance URL | +| `realm` | `string` | Configured realm name | +| `clientId` | `string` | Configured client ID | +| `clientSecret` | `string` | Configured client secret | +| `deploymentConfig` | `KeycloakDeploymentConfig` | Current configuration | +| `k8sClient` | `KubernetesClientHelper` | Kubernetes client | + +## Default Configuration + +### Realm + +| Setting | Value | +|---------|-------| +| Realm name | `rhdh` | + +### Client (`rhdh-client`) + +| Setting | Value | +|---------|-------| +| Client secret | `rhdh-client-secret` | +| Standard flow | Enabled | +| Implicit flow | Enabled | +| Direct access grants | Enabled | +| Service accounts | Enabled | +| Authorization services | Disabled | + +### Groups + +- `developers` +- `admins` +- `viewers` + +### Users + + + +## Full Example: RHDH + Keycloak Setup + +```typescript +import { test } from "rhdh-e2e-test-utils/test"; +import { KeycloakHelper } from "rhdh-e2e-test-utils/keycloak"; + +let keycloak: KeycloakHelper; + +test.beforeAll(async ({ rhdh }) => { + // Deploy Keycloak (or connect to existing) + keycloak = new KeycloakHelper({ namespace: "rhdh-keycloak" }); + + if (!(await keycloak.isRunning())) { + await keycloak.deploy(); + } + + await keycloak.configureForRHDH(); + + // Create additional test users + await keycloak.createUser("rhdh", { + username: "admin-user", + password: "admin123", + groups: ["admins"], + }); + + // Deploy RHDH with Keycloak auth + await rhdh.configure({ auth: "keycloak" }); + await rhdh.deploy(); +}); + +test("login as admin", async ({ page, loginHelper }) => { + await page.goto("/"); + await loginHelper.loginAsKeycloakUser("admin-user", "admin123"); + // ... assertions +}); + +test.afterAll(async () => { + // Optional: cleanup + await keycloak.deleteUser("rhdh", "admin-user"); +}); +``` + +## Related Pages + +- [Authentication Providers](./authentication.md) - Overview of all auth providers +- [LoginHelper](../helpers/login-helper.md) - Login methods for tests +- [Keycloak OIDC Testing Tutorial](/tutorials/keycloak-oidc-testing.md) - Step-by-step guide +- [Keycloak Authentication Example](/examples/keycloak-auth-test.md) - Complete example +- [KeycloakHelper API Reference](/api/deployment/keycloak-helper.md) - Full API documentation diff --git a/docs/guide/deployment/operator-deployment.md b/docs/guide/deployment/operator-deployment.md new file mode 100644 index 0000000..eddefe8 --- /dev/null +++ b/docs/guide/deployment/operator-deployment.md @@ -0,0 +1,242 @@ +# Operator Deployment + +Deploy RHDH using the RHDH Operator. + +## Prerequisites + +The RHDH Operator must be installed on your cluster. Install it via OperatorHub or the command line: + +```bash +# Create operator namespace +oc create namespace rhdh-operator + +# Install the operator +oc apply -f - < { + await rhdh.configure({ + method: "operator", + auth: "keycloak", + appConfig: "tests/config/app-config-rhdh.yaml", + secrets: "tests/config/rhdh-secrets.yaml", + dynamicPlugins: "tests/config/dynamic-plugins.yaml", + subscription: "tests/config/subscription.yaml", + }); + + await rhdh.deploy(); +}); + +test("verify deployment", async ({ page, rhdh }) => { + await page.goto(rhdh.rhdhUrl); + await expect(page).toHaveTitle(/Red Hat Developer Hub/); +}); +``` + +## Operator vs Helm + +| Aspect | Operator | Helm | +|--------|----------|------| +| Installation | Requires operator on cluster | Just Helm CLI | +| Upgrades | Managed by operator | Manual helm upgrade | +| Configuration | Backstage CR | Helm values | +| Complexity | More setup, less maintenance | Less setup, more maintenance | + +## Troubleshooting + +### Operator Not Installed + +``` +Error: no matches for kind "Backstage" +``` + +**Solution**: Install the RHDH Operator on your cluster. + +### CR Validation Failed + +``` +Error: admission webhook denied the request +``` + +**Solution**: Check the Backstage CR spec matches the operator's expected schema. + +### Deployment Not Ready + +``` +Error: Backstage deployment timed out +``` + +**Solution**: Check the operator logs and Backstage pod logs: + +```bash +oc logs -n rhdh-operator deployment/rhdh-operator +oc logs -n ${NAMESPACE} deployment/backstage +``` diff --git a/docs/guide/deployment/rhdh-deployment.md b/docs/guide/deployment/rhdh-deployment.md new file mode 100644 index 0000000..1cb1606 --- /dev/null +++ b/docs/guide/deployment/rhdh-deployment.md @@ -0,0 +1,241 @@ +# RHDH Deployment + +The `RHDHDeployment` class is the core class for managing RHDH deployments in OpenShift. + +## Basic Usage + +```typescript +import { RHDHDeployment } from "rhdh-e2e-test-utils/rhdh"; + +// Create deployment with namespace +const deployment = new RHDHDeployment("my-test-namespace"); + +// Configure options +await deployment.configure({ + version: "1.5", + method: "helm", + auth: "keycloak", +}); + +// Deploy RHDH +await deployment.deploy(); + +// Access the deployed instance +console.log(`RHDH URL: ${deployment.rhdhUrl}`); +``` + +## Using with Test Fixtures + +When using the test fixtures, `RHDHDeployment` is automatically created: + +```typescript +import { test } from "rhdh-e2e-test-utils/test"; + +test.beforeAll(async ({ rhdh }) => { + // rhdh is already instantiated with namespace from project name + await rhdh.configure({ auth: "keycloak" }); + await rhdh.deploy(); +}); + +test("example", async ({ rhdh }) => { + console.log(`URL: ${rhdh.rhdhUrl}`); +}); +``` + +## Configuration Options + +### DeploymentOptions + +| Option | Type | Description | +|--------|------|-------------| +| `version` | `string` | RHDH version (e.g., "1.5"). Defaults to `RHDH_VERSION` env var | +| `namespace` | `string` | Kubernetes namespace. Set via constructor | +| `method` | `"helm" \| "operator"` | Installation method. Defaults to `INSTALLATION_METHOD` env var | +| `auth` | `"guest" \| "keycloak"` | Authentication provider. Defaults to `"keycloak"` | +| `appConfig` | `string` | Path to app-config YAML | +| `secrets` | `string` | Path to secrets YAML | +| `dynamicPlugins` | `string` | Path to dynamic-plugins YAML | +| `valueFile` | `string` | Helm values file (Helm only) | +| `subscription` | `string` | Backstage CR file (Operator only) | + +### Example: Full Configuration + +```typescript +await deployment.configure({ + version: "1.5", + method: "helm", + auth: "keycloak", + appConfig: "tests/config/app-config-rhdh.yaml", + secrets: "tests/config/rhdh-secrets.yaml", + dynamicPlugins: "tests/config/dynamic-plugins.yaml", + valueFile: "tests/config/value_file.yaml", +}); +``` + +## Methods + +### `configure(options?)` + +Prepare for deployment by creating the namespace and setting options: + +```typescript +await deployment.configure({ + auth: "keycloak", + appConfig: "tests/config/app-config.yaml", +}); +``` + +### `deploy()` + +Deploy RHDH to the cluster: + +```typescript +await deployment.deploy(); +``` + +This method: +1. Merges configuration files (common → auth → project) +2. Applies ConfigMaps (app-config, dynamic-plugins) +3. Applies Secrets (with environment variable substitution) +4. Installs RHDH via Helm or Operator +5. Waits for the deployment to be ready +6. Sets `RHDH_BASE_URL` environment variable + +### `waitUntilReady(timeout?)` + +Wait for the RHDH deployment to be ready: + +```typescript +// Default timeout: 300000ms (5 minutes) +await deployment.waitUntilReady(); + +// Custom timeout +await deployment.waitUntilReady(600000); // 10 minutes +``` + +### `rolloutRestart()` + +Restart the RHDH deployment (useful after config changes): + +```typescript +// Update configuration +await deployment.k8sClient.applyConfigMapFromObject( + "app-config-rhdh", + { newConfig: "value" }, + deployment.deploymentConfig.namespace +); + +// Restart to apply changes +await deployment.rolloutRestart(); +``` + +### `teardown()` + +Delete the namespace and all resources: + +```typescript +await deployment.teardown(); +``` + +::: warning +This permanently deletes all resources in the namespace. In CI, this happens automatically. +::: + +## Properties + +### `rhdhUrl` + +The URL of the deployed RHDH instance: + +```typescript +const url = deployment.rhdhUrl; +// e.g., "https://backstage-my-namespace.apps.cluster.example.com" +``` + +### `deploymentConfig` + +The current deployment configuration: + +```typescript +const config = deployment.deploymentConfig; +console.log(config.namespace); // "my-namespace" +console.log(config.version); // "1.5" +console.log(config.method); // "helm" +console.log(config.auth); // "keycloak" +``` + +### `k8sClient` + +The Kubernetes client instance for direct cluster operations: + +```typescript +const k8s = deployment.k8sClient; + +// Get route URL +const url = await k8s.getRouteLocation( + deployment.deploymentConfig.namespace, + "my-route" +); + +// Apply custom ConfigMap +await k8s.applyConfigMapFromObject( + "my-config", + { key: "value" }, + deployment.deploymentConfig.namespace +); +``` + +## Configuration File Paths + +Configuration files are looked for in: + +``` +tests/config/ +├── app-config-rhdh.yaml +├── dynamic-plugins.yaml +└── rhdh-secrets.yaml +``` + +Or specify custom paths: + +```typescript +await deployment.configure({ + appConfig: "custom/path/app-config.yaml", + secrets: "custom/path/secrets.yaml", + dynamicPlugins: "custom/path/plugins.yaml", +}); +``` + +## Configuration Merging Order + +1. **Common configs** (`package/config/common/`) +2. **Auth configs** (`package/config/auth/{guest|keycloak}/`) +3. **Project configs** (your `tests/config/` files) + +Later files override earlier ones, allowing you to customize only what you need. + +## Example: Pre-Deployment Setup + +```typescript +import { test } from "rhdh-e2e-test-utils/test"; +import { $ } from "rhdh-e2e-test-utils/utils"; + +test.beforeAll(async ({ rhdh }) => { + const namespace = rhdh.deploymentConfig.namespace; + + // Configure RHDH + await rhdh.configure({ auth: "keycloak" }); + + // Run custom setup before deployment + await $`bash scripts/setup.sh ${namespace}`; + + // Set runtime environment variables + process.env.MY_CUSTOM_URL = await rhdh.k8sClient.getRouteLocation( + namespace, + "my-service" + ); + + // Deploy RHDH (uses env vars set above) + await rhdh.deploy(); +}); +``` diff --git a/docs/guide/helpers/api-helper.md b/docs/guide/helpers/api-helper.md new file mode 100644 index 0000000..e00a99b --- /dev/null +++ b/docs/guide/helpers/api-helper.md @@ -0,0 +1,264 @@ +# APIHelper + +The `APIHelper` class provides utilities for API interactions with both GitHub and Backstage catalog. + +## Importing + +```typescript +import { APIHelper } from "rhdh-e2e-test-utils/helpers"; +``` + +## GitHub API Operations + +GitHub operations are available as static methods. + +### Prerequisites + +Set the `GITHUB_TOKEN` environment variable with appropriate permissions. + +### `createGitHubRepo(owner, name, options?)` + +Create a GitHub repository: + +```typescript +await APIHelper.createGitHubRepo("my-org", "new-repo"); + +// With options +await APIHelper.createGitHubRepo("my-org", "new-repo", { + private: true, + description: "Test repository", +}); +``` + +### `deleteGitHubRepo(owner, name)` + +Delete a GitHub repository: + +```typescript +await APIHelper.deleteGitHubRepo("my-org", "test-repo"); +``` + +### `getGitHubPRs(owner, repo, state?)` + +Get pull requests: + +```typescript +// Get open PRs +const openPRs = await APIHelper.getGitHubPRs("my-org", "my-repo", "open"); + +// Get closed PRs +const closedPRs = await APIHelper.getGitHubPRs("my-org", "my-repo", "closed"); + +// Get all PRs +const allPRs = await APIHelper.getGitHubPRs("my-org", "my-repo", "all"); +``` + +### `createPullRequest(owner, repo, options)` + +Create a pull request: + +```typescript +await APIHelper.createPullRequest("my-org", "my-repo", { + title: "Feature: New functionality", + head: "feature-branch", + base: "main", + body: "Description of changes", +}); +``` + +### `mergePullRequest(owner, repo, prNumber)` + +Merge a pull request: + +```typescript +await APIHelper.mergePullRequest("my-org", "my-repo", 123); +``` + +## Backstage Catalog API Operations + +Catalog operations require an instance of `APIHelper`. + +### Setup + +```typescript +const apiHelper = new APIHelper(); +await apiHelper.setBaseUrl(rhdhUrl); +await apiHelper.setStaticToken(backendToken); +``` + +### `getAllCatalogUsersFromAPI()` + +Get all users from the catalog: + +```typescript +const users = await apiHelper.getAllCatalogUsersFromAPI(); +console.log(users.length); +``` + +### `getAllCatalogGroupsFromAPI()` + +Get all groups from the catalog: + +```typescript +const groups = await apiHelper.getAllCatalogGroupsFromAPI(); +console.log(groups.length); +``` + +### `getAllCatalogLocationsFromAPI()` + +Get all registered locations: + +```typescript +const locations = await apiHelper.getAllCatalogLocationsFromAPI(); +``` + +### `getEntityByName(kind, namespace, name)` + +Get a specific entity: + +```typescript +const component = await apiHelper.getEntityByName( + "component", + "default", + "my-component" +); +``` + +### `scheduleEntityRefreshFromAPI(name, kind, token?)` + +Schedule a catalog entity refresh: + +```typescript +await apiHelper.scheduleEntityRefreshFromAPI( + "my-component", + "component", + backendToken +); +``` + +### `deleteEntityFromCatalog(kind, namespace, name)` + +Delete an entity from the catalog: + +```typescript +await apiHelper.deleteEntityFromCatalog("component", "default", "my-component"); +``` + +## Complete Examples + +### GitHub Repository Lifecycle + +```typescript +import { test } from "rhdh-e2e-test-utils/test"; +import { APIHelper } from "rhdh-e2e-test-utils/helpers"; + +test.describe("GitHub operations", () => { + const owner = "my-org"; + const repoName = `test-repo-${Date.now()}`; + + test.afterAll(async () => { + // Cleanup + await APIHelper.deleteGitHubRepo(owner, repoName); + }); + + test("create and use repository", async ({ page }) => { + // Create repo + await APIHelper.createGitHubRepo(owner, repoName, { + description: "E2E test repository", + }); + + // Use in test + await page.goto(`https://github.com/${owner}/${repoName}`); + }); +}); +``` + +### Catalog Verification + +```typescript +import { test, expect } from "rhdh-e2e-test-utils/test"; +import { APIHelper } from "rhdh-e2e-test-utils/helpers"; + +test("verify catalog entities", async ({ rhdh }) => { + const apiHelper = new APIHelper(); + await apiHelper.setBaseUrl(rhdh.rhdhUrl); + await apiHelper.setStaticToken(process.env.BACKEND_TOKEN!); + + // Verify users exist + const users = await apiHelper.getAllCatalogUsersFromAPI(); + expect(users.length).toBeGreaterThan(0); + + // Verify groups exist + const groups = await apiHelper.getAllCatalogGroupsFromAPI(); + expect(groups.length).toBeGreaterThan(0); + + // Check specific entity + const component = await apiHelper.getEntityByName( + "component", + "default", + "my-component" + ); + expect(component).toBeDefined(); +}); +``` + +### Entity Refresh + +```typescript +import { test } from "rhdh-e2e-test-utils/test"; +import { APIHelper } from "rhdh-e2e-test-utils/helpers"; + +test("refresh entity", async ({ rhdh }) => { + const apiHelper = new APIHelper(); + await apiHelper.setBaseUrl(rhdh.rhdhUrl); + await apiHelper.setStaticToken(process.env.BACKEND_TOKEN!); + + // Trigger refresh + await apiHelper.scheduleEntityRefreshFromAPI( + "my-component", + "component" + ); + + // Wait for refresh to complete + await new Promise(resolve => setTimeout(resolve, 5000)); + + // Verify updated entity + const component = await apiHelper.getEntityByName( + "component", + "default", + "my-component" + ); + // Assert on updated fields +}); +``` + +## Environment Variables + +| Variable | Description | Used By | +|----------|-------------|---------| +| `GITHUB_TOKEN` | GitHub personal access token | GitHub operations | +| `BACKEND_TOKEN` | Backstage backend auth token | Catalog operations | + +## Error Handling + +```typescript +import { APIHelper } from "rhdh-e2e-test-utils/helpers"; + +try { + await APIHelper.createGitHubRepo("my-org", "repo-name"); +} catch (error) { + if (error.message.includes("already exists")) { + console.log("Repository already exists"); + } else { + throw error; + } +} +``` + +## Best Practices + +1. **Clean up resources** - Delete repos/entities created during tests +2. **Use unique names** - Add timestamps or random strings to avoid conflicts +3. **Handle rate limits** - GitHub has API rate limits +4. **Use appropriate tokens** - Ensure tokens have required permissions +5. **Wait for propagation** - Allow time for catalog to sync after changes diff --git a/docs/guide/helpers/index.md b/docs/guide/helpers/index.md new file mode 100644 index 0000000..ba44c47 --- /dev/null +++ b/docs/guide/helpers/index.md @@ -0,0 +1,140 @@ +# Helpers Overview + +The package provides helper classes for common testing operations in RHDH. + +## Available Helpers + +| Helper | Purpose | +|--------|---------| +| [UIhelper](/guide/helpers/ui-helper) | Material-UI component interactions | +| [LoginHelper](/guide/helpers/login-helper) | Authentication flows | +| [APIHelper](/guide/helpers/api-helper) | GitHub and Backstage API operations | + +## Importing Helpers + +```typescript +// Via fixtures (recommended) +import { test } from "rhdh-e2e-test-utils/test"; + +test("example", async ({ uiHelper, loginHelper }) => { + await loginHelper.loginAsKeycloakUser(); + await uiHelper.verifyHeading("Welcome"); +}); + +// Direct import +import { UIhelper, LoginHelper, APIHelper, setupBrowser } from "rhdh-e2e-test-utils/helpers"; + +const uiHelper = new UIhelper(page); +const loginHelper = new LoginHelper(page); +``` + +## UIhelper + +Provides methods for interacting with Material-UI components in RHDH: + +```typescript +// Wait and verify +await uiHelper.waitForLoad(); +await uiHelper.verifyHeading("Catalog"); +await uiHelper.verifyText("Welcome to RHDH"); + +// Navigation +await uiHelper.openSidebar("Catalog"); +await uiHelper.clickTab("Overview"); + +// Form interactions +await uiHelper.fillTextInputByLabel("Name", "my-component"); +await uiHelper.selectMuiBox("Kind", "Component"); +await uiHelper.clickButton("Submit"); + +// Table operations +await uiHelper.verifyRowsInTable(["row1", "row2"]); +await uiHelper.verifyCellsInTable(["cell1", "cell2"]); +``` + +[Learn more about UIhelper →](/guide/helpers/ui-helper) + +## LoginHelper + +Handles authentication for different providers: + +```typescript +// Guest authentication +await loginHelper.loginAsGuest(); + +// Keycloak authentication +await loginHelper.loginAsKeycloakUser(); +await loginHelper.loginAsKeycloakUser("custom-user", "password"); + +// GitHub authentication +await loginHelper.loginAsGithubUser(); + +// Sign out +await loginHelper.signOut(); +``` + +[Learn more about LoginHelper →](/guide/helpers/login-helper) + +## APIHelper + +Provides API operations for GitHub and Backstage: + +```typescript +// GitHub operations +await APIHelper.createGitHubRepo("owner", "repo-name"); +await APIHelper.deleteGitHubRepo("owner", "repo-name"); +const prs = await APIHelper.getGitHubPRs("owner", "repo", "open"); + +// Backstage catalog operations +const apiHelper = new APIHelper(); +await apiHelper.setBaseUrl(rhdhUrl); +await apiHelper.setStaticToken(token); + +const users = await apiHelper.getAllCatalogUsersFromAPI(); +const groups = await apiHelper.getAllCatalogGroupsFromAPI(); +``` + +[Learn more about APIHelper →](/guide/helpers/api-helper) + +## setupBrowser + +Utility for shared browser context in serial tests: + +```typescript +import { test } from "@playwright/test"; +import { setupBrowser, LoginHelper } from "rhdh-e2e-test-utils/helpers"; +import type { Page, BrowserContext } from "@playwright/test"; + +test.describe.configure({ mode: "serial" }); + +let page: Page; +let context: BrowserContext; + +test.beforeAll(async ({ browser }, testInfo) => { + ({ page, context } = await setupBrowser(browser, testInfo)); + + const loginHelper = new LoginHelper(page); + await page.goto("/"); + await loginHelper.loginAsKeycloakUser(); +}); + +test.afterAll(async () => { + await context.close(); +}); + +test("first test", async () => { + await page.goto("/catalog"); + // Already logged in +}); +``` + +## When to Use Each Helper + +| Scenario | Helper | +|----------|--------| +| Click buttons, verify text | UIhelper | +| Login/logout operations | LoginHelper | +| Create GitHub repos | APIHelper | +| Query Backstage catalog | APIHelper | +| Interact with tables | UIhelper | +| Fill forms | UIhelper | diff --git a/docs/guide/helpers/login-helper.md b/docs/guide/helpers/login-helper.md new file mode 100644 index 0000000..87c6f32 --- /dev/null +++ b/docs/guide/helpers/login-helper.md @@ -0,0 +1,242 @@ +# LoginHelper + +The `LoginHelper` class handles authentication flows for different providers in RHDH. + +## Getting LoginHelper + +```typescript +// Via fixture (recommended) +import { test } from "rhdh-e2e-test-utils/test"; + +test("example", async ({ loginHelper }) => { + await loginHelper.loginAsKeycloakUser(); +}); + +// Direct instantiation +import { LoginHelper } from "rhdh-e2e-test-utils/helpers"; + +const loginHelper = new LoginHelper(page); +``` + +## Authentication Methods + +### Guest Authentication + +#### `loginAsGuest()` + +Login using guest authentication: + +```typescript +await loginHelper.loginAsGuest(); +``` + +This clicks the "Enter" button on the guest login page. + +### Keycloak Authentication + +#### `loginAsKeycloakUser(username?, password?)` + +Login using Keycloak OIDC: + +```typescript +// Default credentials (test1/test1@123) +await loginHelper.loginAsKeycloakUser(); + +// Custom credentials +await loginHelper.loginAsKeycloakUser("test1", "test1@123"); +await loginHelper.loginAsKeycloakUser("admin-user", "adminpass"); +``` + +Environment variables for defaults: +- `KEYCLOAK_USER_NAME` - Default username (fallback: `test1`) +- `KEYCLOAK_USER_PASSWORD` - Default password (fallback: `test1@123`) + +### GitHub Authentication + +#### `loginAsGithubUser()` + +Login using GitHub OAuth: + +```typescript +await loginHelper.loginAsGithubUser(); +``` + +Required environment variables: +- `GH_USER_NAME` - GitHub username +- `GH_USER_PASSWORD` - GitHub password +- `GH_2FA_SECRET` - GitHub 2FA secret (for OTP generation) + +::: warning +GitHub login requires 2FA secret for automated OTP generation. This is more complex to set up. +::: + +## Sign Out + +### `signOut()` + +Sign out of RHDH: + +```typescript +await loginHelper.signOut(); +``` + +This navigates to the user menu and clicks sign out. + +## Usage Patterns + +### Login Before Each Test + +```typescript +import { test } from "rhdh-e2e-test-utils/test"; + +test.beforeEach(async ({ loginHelper }) => { + await loginHelper.loginAsKeycloakUser(); +}); + +test("test 1", async ({ page }) => { + // Already logged in +}); + +test("test 2", async ({ page }) => { + // Already logged in +}); +``` + +### Login Once for Serial Tests + +```typescript +import { test } from "@playwright/test"; +import { setupBrowser, LoginHelper } from "rhdh-e2e-test-utils/helpers"; +import type { Page, BrowserContext } from "@playwright/test"; + +test.describe.configure({ mode: "serial" }); + +let page: Page; +let context: BrowserContext; + +test.beforeAll(async ({ browser }, testInfo) => { + ({ page, context } = await setupBrowser(browser, testInfo)); + + const loginHelper = new LoginHelper(page); + await page.goto("/"); + await loginHelper.loginAsKeycloakUser(); +}); + +test.afterAll(async () => { + await context.close(); +}); + +test("test 1", async () => { + // Session persists + await page.goto("/catalog"); +}); + +test("test 2", async () => { + // Still logged in + await page.goto("/settings"); +}); +``` + +### Different Users in Different Tests + +```typescript +import { test } from "rhdh-e2e-test-utils/test"; + +test("as developer", async ({ page, loginHelper, uiHelper }) => { + await page.goto("/"); + await loginHelper.loginAsKeycloakUser("test1", "test1@123"); + await uiHelper.openSidebar("Catalog"); + // Developer view + await loginHelper.signOut(); +}); + +test("as admin", async ({ page, loginHelper, uiHelper }) => { + await page.goto("/"); + await loginHelper.loginAsKeycloakUser("admin", "adminpass"); + await uiHelper.openSidebar("Settings"); + // Admin view +}); +``` + +### Handling Login Redirects + +The LoginHelper handles OAuth redirects automatically. After calling a login method, you'll be on the RHDH home page. + +```typescript +test("login flow", async ({ page, loginHelper }) => { + await page.goto("/"); + + // This handles: + // 1. Click login button + // 2. Redirect to Keycloak + // 3. Fill credentials + // 4. Submit + // 5. Redirect back to RHDH + await loginHelper.loginAsKeycloakUser(); + + // Now on RHDH home page + await expect(page).toHaveURL(/.*\/$/); +}); +``` + +## Environment Variables + +### Keycloak + +| Variable | Description | Default | +|----------|-------------|---------| +| `KEYCLOAK_USER_NAME` | Default username | `test1` | +| `KEYCLOAK_USER_PASSWORD` | Default password | `test1@123` | + +### GitHub + +| Variable | Description | Required | +|----------|-------------|----------| +| `GH_USER_NAME` | GitHub username | Yes | +| `GH_USER_PASSWORD` | GitHub password | Yes | +| `GH_2FA_SECRET` | 2FA secret for OTP | Yes | + +## Troubleshooting + +### Login Timeout + +``` +Error: Login timed out waiting for redirect +``` + +**Solution:** +1. Verify Keycloak is running +2. Check RHDH is configured for Keycloak auth +3. Verify credentials are correct + +### Guest Login Not Available + +``` +Error: Guest login button not found +``` + +**Solution:** Ensure RHDH is configured with `auth: "guest"`. + +### Keycloak Login Page Not Shown + +``` +Error: Keycloak login form not found +``` + +**Solution:** +1. Check Keycloak environment variables are set +2. Verify RHDH is configured with `auth: "keycloak"` +3. Check Keycloak is accessible + +### Already Logged In + +If tests assume fresh login state but session persists: + +```typescript +test.beforeEach(async ({ page, loginHelper }) => { + // Clear cookies before each test + await page.context().clearCookies(); + await page.goto("/"); + await loginHelper.loginAsKeycloakUser(); +}); +``` diff --git a/docs/guide/helpers/ui-helper.md b/docs/guide/helpers/ui-helper.md new file mode 100644 index 0000000..7defa4a --- /dev/null +++ b/docs/guide/helpers/ui-helper.md @@ -0,0 +1,301 @@ +# UIhelper + +The `UIhelper` class provides methods for interacting with Material-UI components in RHDH. + +## Getting UIhelper + +```typescript +// Via fixture (recommended) +import { test } from "rhdh-e2e-test-utils/test"; + +test("example", async ({ uiHelper }) => { + await uiHelper.verifyHeading("Welcome"); +}); + +// Direct instantiation +import { UIhelper } from "rhdh-e2e-test-utils/helpers"; + +const uiHelper = new UIhelper(page); +``` + +## Wait Operations + +### `waitForLoad(timeout?)` + +Wait for the page to fully load: + +```typescript +await uiHelper.waitForLoad(); +await uiHelper.waitForLoad(10000); // Custom timeout +``` + +## Verification Methods + +### `verifyHeading(heading, timeout?)` + +Verify a heading is visible: + +```typescript +await uiHelper.verifyHeading("Welcome to RHDH"); +await uiHelper.verifyHeading(/Welcome/); // Regex +``` + +### `verifyText(text, timeout?)` + +Verify text is visible on the page: + +```typescript +await uiHelper.verifyText("Some content"); +await uiHelper.verifyText(/pattern/); // Regex +``` + +### `verifyLink(link)` + +Verify a link is visible: + +```typescript +await uiHelper.verifyLink("View Details"); +await uiHelper.verifyLink(/details/i); +``` + +## Button Interactions + +### `clickButton(label, options?)` + +Click a button by its label: + +```typescript +await uiHelper.clickButton("Submit"); +await uiHelper.clickButton("Cancel", { exact: true }); +await uiHelper.clickButton("Save", { force: true }); +``` + +### `clickButtonByLabel(label, options?)` + +Click a button using aria-label: + +```typescript +await uiHelper.clickButtonByLabel("Close"); +await uiHelper.clickButtonByLabel("Settings", { force: true }); +``` + +### `clickButtonByText(text)` + +Click a button by visible text: + +```typescript +await uiHelper.clickButtonByText("Get Started"); +``` + +## Navigation + +### `openSidebar(navBarText)` + +Open a sidebar navigation item: + +```typescript +await uiHelper.openSidebar("Catalog"); +await uiHelper.openSidebar("Tech Radar"); +await uiHelper.openSidebar("Home"); +``` + +Supported sidebar items: +- `"Home"` +- `"Catalog"` +- `"APIs"` +- `"Docs"` +- `"Learning Paths"` +- `"Tech Radar"` +- `"Create..."` +- `"Settings"` + +### `openCatalogSidebar(navBarText)` + +Open a catalog-specific sidebar item: + +```typescript +await uiHelper.openCatalogSidebar("Overview"); +await uiHelper.openCatalogSidebar("Dependencies"); +``` + +### `clickTab(tabName)` + +Click a tab: + +```typescript +await uiHelper.clickTab("Overview"); +await uiHelper.clickTab("Dependencies"); +await uiHelper.clickTab("API"); +``` + +## Table Operations + +### `verifyRowsInTable(rowTexts, exact?)` + +Verify rows exist in a table: + +```typescript +await uiHelper.verifyRowsInTable(["my-component", "my-api"]); +await uiHelper.verifyRowsInTable([/component-\d+/]); // Regex +await uiHelper.verifyRowsInTable(["exact-match"], true); +``` + +### `verifyCellsInTable(cellTexts)` + +Verify cells exist in a table: + +```typescript +await uiHelper.verifyCellsInTable(["cell-value-1", "cell-value-2"]); +``` + +### `verifyRowInTableByUniqueText(uniqueRowText, cellTexts)` + +Verify specific cells in a row identified by unique text: + +```typescript +await uiHelper.verifyRowInTableByUniqueText( + "my-component", + ["Component", "Production", "Running"] +); +``` + +### `verifyRowNotInTable(rowText)` + +Verify a row does NOT exist: + +```typescript +await uiHelper.verifyRowNotInTable("deleted-component"); +``` + +## Form Interactions + +### `fillTextInputByLabel(label, value)` + +Fill a text input by its label: + +```typescript +await uiHelper.fillTextInputByLabel("Name", "my-component"); +await uiHelper.fillTextInputByLabel("Description", "A test component"); +``` + +### `selectMuiBox(label, value)` + +Select a value in a Material-UI select box: + +```typescript +await uiHelper.selectMuiBox("Kind", "Component"); +await uiHelper.selectMuiBox("Type", "Service"); +``` + +### `checkCheckbox(label)` + +Check a checkbox: + +```typescript +await uiHelper.checkCheckbox("I agree to the terms"); +await uiHelper.checkCheckbox("Enable notifications"); +``` + +### `clearTextInputByLabel(label)` + +Clear a text input: + +```typescript +await uiHelper.clearTextInputByLabel("Search"); +``` + +## Card Interactions + +### `verifyTextinCard(cardTitle, text)` + +Verify text exists in a specific card: + +```typescript +await uiHelper.verifyTextinCard("About", "This is a component"); +``` + +### `verifyLinkinCard(cardTitle, linkText)` + +Verify a link exists in a card: + +```typescript +await uiHelper.verifyLinkinCard("Links", "View Source"); +``` + +### `clickBtnInCard(cardTitle, buttonLabel)` + +Click a button in a specific card: + +```typescript +await uiHelper.clickBtnInCard("Actions", "Refresh"); +``` + +## Search Operations + +### `searchInputPlaceholder(placeholder, searchText)` + +Search using an input with specific placeholder: + +```typescript +await uiHelper.searchInputPlaceholder("Filter", "my-component"); +``` + +## Alert and Dialog + +### `verifyAlertContains(text)` + +Verify an alert contains specific text: + +```typescript +await uiHelper.verifyAlertContains("Success"); +await uiHelper.verifyAlertContains("Error occurred"); +``` + +### `closeDialog()` + +Close an open dialog: + +```typescript +await uiHelper.closeDialog(); +``` + +## Complete Example + +```typescript +import { test, expect } from "rhdh-e2e-test-utils/test"; + +test("interact with catalog", async ({ page, uiHelper, loginHelper }) => { + // Login + await loginHelper.loginAsKeycloakUser(); + + // Navigate + await uiHelper.openSidebar("Catalog"); + await uiHelper.verifyHeading("Catalog"); + + // Search + await uiHelper.searchInputPlaceholder("Filter", "my-component"); + + // Verify results + await uiHelper.verifyRowsInTable(["my-component"]); + + // Click on component + await page.click("text=my-component"); + + // Verify component page + await uiHelper.verifyHeading("my-component"); + await uiHelper.clickTab("Overview"); + await uiHelper.verifyTextinCard("About", "Description"); + + // Interact with actions + await uiHelper.clickBtnInCard("Actions", "Unregister"); + await uiHelper.clickButton("Cancel"); +}); +``` + +## Related Pages + +- [UIhelper API Reference](/api/helpers/ui-helper.md) - Complete method signatures +- [LoginHelper](./login-helper.md) - Authentication helpers +- [APIHelper](./api-helper.md) - Backend API helpers +- [Page Objects](/guide/page-objects/) - Higher-level page abstractions +- [Testing Patterns](/guide/core-concepts/testing-patterns.md) - Serial vs parallel testing diff --git a/docs/guide/index.md b/docs/guide/index.md new file mode 100644 index 0000000..ebbaca4 --- /dev/null +++ b/docs/guide/index.md @@ -0,0 +1,74 @@ +# Getting Started + +`rhdh-e2e-test-utils` is a comprehensive test utility package for Red Hat Developer Hub (RHDH) end-to-end testing. This package provides a unified framework for deploying RHDH instances, running Playwright tests, and managing Kubernetes resources in OpenShift environments. + +## Overview + +The package simplifies end-to-end testing for RHDH plugins by providing: + +- **Automated RHDH Deployment**: Deploy RHDH instances via Helm or the RHDH Operator +- **Keycloak Integration**: Deploy and configure Keycloak for OIDC authentication testing +- **Modular Auth Configuration**: Switch between guest and Keycloak authentication with a single option +- **Playwright Integration**: Custom test fixtures that manage deployment lifecycle +- **Kubernetes Utilities**: Helper functions for managing namespaces, ConfigMaps, Secrets, and Routes +- **Configuration Merging**: YAML merging with environment variable substitution +- **Standardized ESLint Rules**: Pre-configured linting for Playwright tests + +## Key Features + +| Feature | Description | +|---------|-------------| +| Deploy RHDH | Using Helm charts or the RHDH Operator | +| Deploy Keycloak | For authentication testing with automatic realm, client, and user configuration | +| Modular authentication | Configuration for guest and Keycloak providers | +| Automatic namespace | Creation and cleanup | +| Dynamic plugin | Configuration support | +| UI, API, and common helpers | For test interactions | +| Kubernetes client helper | For OpenShift resources | +| Pre-configured Playwright | Settings optimized for RHDH testing | +| ESLint configuration | With Playwright and TypeScript best practices | + +## Architecture + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Your Test Project │ +├─────────────────────────────────────────────────────────────┤ +│ tests/ │ +│ ├── config/ │ +│ │ ├── app-config-rhdh.yaml │ +│ │ ├── dynamic-plugins.yaml │ +│ │ └── rhdh-secrets.yaml │ +│ └── specs/ │ +│ └── my-plugin.spec.ts │ +├─────────────────────────────────────────────────────────────┤ +│ rhdh-e2e-test-utils │ +├─────────────────────────────────────────────────────────────┤ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ Playwright │ │ RHDH │ │ Keycloak │ │ +│ │ Fixtures │ │ Deployment │ │ Helper │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ UI Helper │ │ Login Helper │ │ API Helper │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ Page Objects │ │ K8s Client │ │ YAML Utils │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +├─────────────────────────────────────────────────────────────┤ +│ OpenShift Cluster │ +│ ┌──────────────────────────┐ ┌──────────────────────────┐ │ +│ │ RHDH Namespace │ │ Keycloak Namespace │ │ +│ │ - Deployment │ │ - StatefulSet │ │ +│ │ - ConfigMaps │ │ - Services │ │ +│ │ - Secrets │ │ - Routes │ │ +│ │ - Routes │ │ │ │ +│ └──────────────────────────┘ └──────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +## Next Steps + +1. [Installation](/guide/installation) - Install the package and dependencies +2. [Quick Start](/guide/quick-start) - Create your first E2E test +3. [Requirements](/guide/requirements) - System and cluster requirements +4. [Core Concepts](/guide/core-concepts/) - Understand the key concepts diff --git a/docs/guide/installation.md b/docs/guide/installation.md new file mode 100644 index 0000000..a4ed48b --- /dev/null +++ b/docs/guide/installation.md @@ -0,0 +1,116 @@ +# Installation + +## Package Installation + +Install via npm: + +```bash +npm install rhdh-e2e-test-utils +``` + +Or via yarn: + +```bash +yarn add rhdh-e2e-test-utils +``` + +Or directly from GitHub (for development versions): + +```bash +npm install github:redhat-developer/rhdh-e2e-test-utils#main +``` + +## Peer Dependencies + +The package requires `@playwright/test` as a peer dependency: + +```bash +npm install @playwright/test +``` + +## Verifying Installation + +After installation, you can verify by importing the package: + +```typescript +import { test, expect } from "rhdh-e2e-test-utils/test"; +import { defineConfig } from "rhdh-e2e-test-utils/playwright-config"; + +// If these imports work without errors, installation is successful +``` + +## Project Setup + +### 1. Create E2E Test Directory + +```bash +mkdir e2e-tests && cd e2e-tests +yarn init -y +``` + +### 2. Install Dependencies + +```bash +yarn add @playwright/test rhdh-e2e-test-utils typescript +``` + +### 3. Create Configuration Files + +Create the following files in your project: + +**playwright.config.ts** +```typescript +import { defineConfig } from "rhdh-e2e-test-utils/playwright-config"; + +export default defineConfig({ + projects: [ + { + name: "my-plugin", + }, + ], +}); +``` + +**tsconfig.json** +```json +{ + "extends": "rhdh-e2e-test-utils/tsconfig", + "include": ["tests/**/*.ts"] +} +``` + +**eslint.config.js** +```javascript +import { createEslintConfig } from "rhdh-e2e-test-utils/eslint"; + +export default createEslintConfig(import.meta.dirname); +``` + +### 4. Create Test Directory Structure + +```bash +mkdir -p tests/config tests/specs +``` + +Your project structure should look like: + +``` +e2e-tests/ +├── package.json +├── playwright.config.ts +├── tsconfig.json +├── eslint.config.js +├── .env # Environment variables +└── tests/ + ├── config/ + │ ├── app-config-rhdh.yaml # RHDH app configuration + │ ├── dynamic-plugins.yaml # Plugin configuration + │ └── rhdh-secrets.yaml # Secrets template + └── specs/ + └── my-plugin.spec.ts # Test files +``` + +## Next Steps + +- [Requirements](/guide/requirements) - Check system and cluster requirements +- [Quick Start](/guide/quick-start) - Create your first test diff --git a/docs/guide/page-objects/catalog-import-page.md b/docs/guide/page-objects/catalog-import-page.md new file mode 100644 index 0000000..ca1e0c2 --- /dev/null +++ b/docs/guide/page-objects/catalog-import-page.md @@ -0,0 +1,114 @@ +# CatalogImportPage + +The `CatalogImportPage` class provides methods for registering components in the RHDH catalog. + +## Usage + +```typescript +import { CatalogImportPage } from "rhdh-e2e-test-utils/pages"; + +const catalogImportPage = new CatalogImportPage(page); +``` + +## Methods + +### `registerExistingComponent(url)` + +Register or refresh an existing component: + +```typescript +const wasAlreadyRegistered = await catalogImportPage.registerExistingComponent( + "https://github.com/org/repo/blob/main/catalog-info.yaml" +); + +if (wasAlreadyRegistered) { + console.log("Component was refreshed"); +} else { + console.log("Component was newly registered"); +} +``` + +### `analyzeComponent(url)` + +Analyze a component URL before registration: + +```typescript +await catalogImportPage.analyzeComponent( + "https://github.com/org/repo/blob/main/catalog-info.yaml" +); +``` + +### `inspectEntityAndVerifyYaml(expectedContent)` + +Inspect the entity and verify YAML content: + +```typescript +await catalogImportPage.inspectEntityAndVerifyYaml("kind: Component"); +await catalogImportPage.inspectEntityAndVerifyYaml("name: my-component"); +``` + +### `submitRegistration()` + +Submit the component registration: + +```typescript +await catalogImportPage.submitRegistration(); +``` + +### `go()` + +Navigate to the catalog import page: + +```typescript +await catalogImportPage.go(); +``` + +## Complete Example + +```typescript +import { test, expect } from "rhdh-e2e-test-utils/test"; +import { CatalogImportPage } from "rhdh-e2e-test-utils/pages"; + +test("register component", async ({ page, loginHelper, uiHelper }) => { + await loginHelper.loginAsKeycloakUser(); + + const catalogImportPage = new CatalogImportPage(page); + + // Navigate to import page + await catalogImportPage.go(); + + // Analyze component + await catalogImportPage.analyzeComponent( + "https://github.com/my-org/my-repo/blob/main/catalog-info.yaml" + ); + + // Verify YAML content + await catalogImportPage.inspectEntityAndVerifyYaml("kind: Component"); + await catalogImportPage.inspectEntityAndVerifyYaml("name: my-component"); + + // Register component + await catalogImportPage.submitRegistration(); + + // Verify registration success + await uiHelper.verifyHeading("my-component"); +}); +``` + +### Registering Multiple Components + +```typescript +test("register multiple components", async ({ page, loginHelper }) => { + await loginHelper.loginAsKeycloakUser(); + + const catalogImportPage = new CatalogImportPage(page); + const componentUrls = [ + "https://github.com/org/repo1/blob/main/catalog-info.yaml", + "https://github.com/org/repo2/blob/main/catalog-info.yaml", + "https://github.com/org/repo3/blob/main/catalog-info.yaml", + ]; + + for (const url of componentUrls) { + await catalogImportPage.registerExistingComponent(url); + } +}); +``` diff --git a/docs/guide/page-objects/catalog-page.md b/docs/guide/page-objects/catalog-page.md new file mode 100644 index 0000000..73937b6 --- /dev/null +++ b/docs/guide/page-objects/catalog-page.md @@ -0,0 +1,105 @@ +# CatalogPage + +The `CatalogPage` class provides methods for interacting with the RHDH software catalog. + +## Usage + +```typescript +import { CatalogPage } from "rhdh-e2e-test-utils/pages"; + +const catalogPage = new CatalogPage(page); +``` + +## Methods + +### `go()` + +Navigate to the catalog page: + +```typescript +await catalogPage.go(); +``` + +### `search(text)` + +Search for entities in the catalog: + +```typescript +await catalogPage.search("my-component"); +await catalogPage.search("service"); +``` + +### `goToByName(name)` + +Navigate to a specific component by name: + +```typescript +await catalogPage.goToByName("my-component"); +``` + +### `selectKind(kind)` + +Filter by entity kind: + +```typescript +await catalogPage.selectKind("Component"); +await catalogPage.selectKind("API"); +await catalogPage.selectKind("Template"); +``` + +### `selectType(type)` + +Filter by entity type: + +```typescript +await catalogPage.selectType("service"); +await catalogPage.selectType("website"); +``` + +### `selectOwner(owner)` + +Filter by owner: + +```typescript +await catalogPage.selectOwner("team-a"); +await catalogPage.selectOwner("user:default/john"); +``` + +### `clearFilters()` + +Clear all applied filters: + +```typescript +await catalogPage.clearFilters(); +``` + +## Complete Example + +```typescript +import { test, expect } from "rhdh-e2e-test-utils/test"; +import { CatalogPage } from "rhdh-e2e-test-utils/pages"; + +test("browse catalog", async ({ page, loginHelper, uiHelper }) => { + await loginHelper.loginAsKeycloakUser(); + + const catalogPage = new CatalogPage(page); + + // Navigate to catalog + await catalogPage.go(); + + // Filter by kind + await catalogPage.selectKind("Component"); + + // Search for component + await catalogPage.search("my-service"); + + // Verify results + await uiHelper.verifyRowsInTable(["my-service"]); + + // Navigate to component + await catalogPage.goToByName("my-service"); + + // Verify component page + await uiHelper.verifyHeading("my-service"); +}); +``` diff --git a/docs/guide/page-objects/extensions-page.md b/docs/guide/page-objects/extensions-page.md new file mode 100644 index 0000000..a5678b5 --- /dev/null +++ b/docs/guide/page-objects/extensions-page.md @@ -0,0 +1,94 @@ +# ExtensionsPage + +The `ExtensionsPage` class provides methods for interacting with the RHDH extensions/plugins page. + +## Usage + +```typescript +import { ExtensionsPage } from "rhdh-e2e-test-utils/pages"; + +const extensionsPage = new ExtensionsPage(page); +``` + +## Methods + +### `selectSupportTypeFilter(type)` + +Filter plugins by support type: + +```typescript +await extensionsPage.selectSupportTypeFilter("Red Hat"); +await extensionsPage.selectSupportTypeFilter("Community"); +``` + +### `verifyPluginDetails(details)` + +Verify plugin details: + +```typescript +await extensionsPage.verifyPluginDetails({ + pluginName: "Topology", + badgeLabel: "Red Hat support", + badgeText: "Red Hat", +}); +``` + +### `waitForSearchResults(searchTerm)` + +Search and wait for results: + +```typescript +await extensionsPage.waitForSearchResults("catalog"); +await extensionsPage.waitForSearchResults("kubernetes"); +``` + +### `clickPlugin(pluginName)` + +Click on a specific plugin: + +```typescript +await extensionsPage.clickPlugin("Tech Radar"); +``` + +### `go()` + +Navigate to the extensions page: + +```typescript +await extensionsPage.go(); +``` + +## Complete Example + +```typescript +import { test, expect } from "rhdh-e2e-test-utils/test"; +import { ExtensionsPage } from "rhdh-e2e-test-utils/pages"; + +test("browse extensions", async ({ page, loginHelper, uiHelper }) => { + await loginHelper.loginAsKeycloakUser(); + + const extensionsPage = new ExtensionsPage(page); + + // Navigate to extensions + await extensionsPage.go(); + + // Filter by support type + await extensionsPage.selectSupportTypeFilter("Red Hat"); + + // Search for plugin + await extensionsPage.waitForSearchResults("topology"); + + // Verify plugin details + await extensionsPage.verifyPluginDetails({ + pluginName: "Topology", + badgeLabel: "Red Hat support", + badgeText: "Red Hat", + }); + + // Click to view details + await extensionsPage.clickPlugin("Topology"); + + // Verify plugin page + await uiHelper.verifyHeading("Topology"); +}); +``` diff --git a/docs/guide/page-objects/home-page.md b/docs/guide/page-objects/home-page.md new file mode 100644 index 0000000..c275987 --- /dev/null +++ b/docs/guide/page-objects/home-page.md @@ -0,0 +1,60 @@ +# HomePage + +The `HomePage` class provides methods for interacting with the RHDH home page. + +## Usage + +```typescript +import { HomePage } from "rhdh-e2e-test-utils/pages"; + +const homePage = new HomePage(page); +``` + +## Methods + +### `verifyQuickSearchBar(searchText)` + +Search using the quick search bar and verify results: + +```typescript +await homePage.verifyQuickSearchBar("my-component"); +``` + +### `verifyQuickAccess(section, item)` + +Verify an item exists in a quick access section: + +```typescript +await homePage.verifyQuickAccess("Favorites", "my-component"); +await homePage.verifyQuickAccess("Recently Visited", "my-api"); +``` + +### `clickQuickAccessItem(section, item)` + +Click an item in a quick access section: + +```typescript +await homePage.clickQuickAccessItem("Favorites", "my-component"); +``` + +## Complete Example + +```typescript +import { test, expect } from "rhdh-e2e-test-utils/test"; +import { HomePage } from "rhdh-e2e-test-utils/pages"; + +test("home page interactions", async ({ page, loginHelper }) => { + await loginHelper.loginAsKeycloakUser(); + + const homePage = new HomePage(page); + + // Use quick search + await homePage.verifyQuickSearchBar("my-component"); + + // Verify quick access sections + await homePage.verifyQuickAccess("Favorites", "my-component"); + + // Click quick access item + await homePage.clickQuickAccessItem("Favorites", "my-component"); +}); +``` diff --git a/docs/guide/page-objects/index.md b/docs/guide/page-objects/index.md new file mode 100644 index 0000000..f3ba831 --- /dev/null +++ b/docs/guide/page-objects/index.md @@ -0,0 +1,109 @@ +# Page Objects Overview + +The package provides pre-built page object classes for common RHDH pages. + +## Available Page Objects + +| Page Object | Purpose | +|------------|---------| +| [CatalogPage](/guide/page-objects/catalog-page) | Software catalog navigation | +| [HomePage](/guide/page-objects/home-page) | Home page interactions | +| [CatalogImportPage](/guide/page-objects/catalog-import-page) | Component registration | +| [ExtensionsPage](/guide/page-objects/extensions-page) | Plugin management | +| [NotificationPage](/guide/page-objects/notification-page) | Notification management | + +## Importing Page Objects + +```typescript +import { + CatalogPage, + HomePage, + CatalogImportPage, + ExtensionsPage, + NotificationPage, +} from "rhdh-e2e-test-utils/pages"; +``` + +## Usage Pattern + +Page objects are instantiated with a Playwright `Page` object: + +```typescript +import { test } from "rhdh-e2e-test-utils/test"; +import { CatalogPage } from "rhdh-e2e-test-utils/pages"; + +test("catalog test", async ({ page, loginHelper }) => { + await loginHelper.loginAsKeycloakUser(); + + const catalogPage = new CatalogPage(page); + await catalogPage.go(); + await catalogPage.search("my-component"); +}); +``` + +## When to Use Page Objects + +Page objects are useful when: + +- You need specialized navigation or interaction with a specific RHDH page +- You want cleaner, more readable tests +- You're performing multiple operations on the same page + +For simple interactions, `UIhelper` may be sufficient: + +```typescript +// Using UIhelper +await uiHelper.openSidebar("Catalog"); +await uiHelper.verifyHeading("Catalog"); + +// Using CatalogPage +const catalogPage = new CatalogPage(page); +await catalogPage.go(); +``` + +## Creating Custom Page Objects + +You can create your own page objects for plugin-specific pages: + +```typescript +import { Page, Locator, expect } from "@playwright/test"; + +export class MyPluginPage { + private readonly page: Page; + private readonly heading: Locator; + private readonly actionButton: Locator; + + constructor(page: Page) { + this.page = page; + this.heading = page.getByRole("heading", { name: "My Plugin" }); + this.actionButton = page.getByRole("button", { name: "Take Action" }); + } + + async go(): Promise { + await this.page.goto("/my-plugin"); + await this.heading.waitFor(); + } + + async performAction(): Promise { + await this.actionButton.click(); + } + + async verifyResult(expected: string): Promise { + await expect(this.page.getByText(expected)).toBeVisible(); + } +} +``` + +Usage: + +```typescript +import { test } from "rhdh-e2e-test-utils/test"; +import { MyPluginPage } from "./pages/my-plugin-page"; + +test("my plugin test", async ({ page }) => { + const myPluginPage = new MyPluginPage(page); + await myPluginPage.go(); + await myPluginPage.performAction(); + await myPluginPage.verifyResult("Success"); +}); +``` diff --git a/docs/guide/page-objects/notification-page.md b/docs/guide/page-objects/notification-page.md new file mode 100644 index 0000000..b2f4fa2 --- /dev/null +++ b/docs/guide/page-objects/notification-page.md @@ -0,0 +1,134 @@ +# NotificationPage + +The `NotificationPage` class provides methods for managing notifications in RHDH. + +## Usage + +```typescript +import { NotificationPage } from "rhdh-e2e-test-utils/pages"; + +const notificationPage = new NotificationPage(page); +``` + +## Methods + +### `clickNotificationsNavBarItem()` + +Navigate to notifications via the navbar: + +```typescript +await notificationPage.clickNotificationsNavBarItem(); +``` + +### `notificationContains(text)` + +Check if a notification contains specific text: + +```typescript +await notificationPage.notificationContains("Build completed"); +await notificationPage.notificationContains("Entity updated"); +``` + +### `markAllNotificationsAsRead()` + +Mark all notifications as read: + +```typescript +await notificationPage.markAllNotificationsAsRead(); +``` + +### `selectSeverity(severity)` + +Filter notifications by severity: + +```typescript +await notificationPage.selectSeverity("critical"); +await notificationPage.selectSeverity("high"); +await notificationPage.selectSeverity("normal"); +await notificationPage.selectSeverity("low"); +``` + +### `viewSaved()` + +View saved/bookmarked notifications: + +```typescript +await notificationPage.viewSaved(); +``` + +### `viewAll()` + +View all notifications: + +```typescript +await notificationPage.viewAll(); +``` + +### `sortByNewestOnTop()` + +Sort notifications with newest first: + +```typescript +await notificationPage.sortByNewestOnTop(); +``` + +### `sortByOldestOnTop()` + +Sort notifications with oldest first: + +```typescript +await notificationPage.sortByOldestOnTop(); +``` + +### `saveNotification(index)` + +Save/bookmark a notification: + +```typescript +await notificationPage.saveNotification(0); // Save first notification +``` + +### `deleteNotification(index)` + +Delete a notification: + +```typescript +await notificationPage.deleteNotification(0); // Delete first notification +``` + +## Complete Example + +```typescript +import { test, expect } from "rhdh-e2e-test-utils/test"; +import { NotificationPage } from "rhdh-e2e-test-utils/pages"; + +test("manage notifications", async ({ page, loginHelper }) => { + await loginHelper.loginAsKeycloakUser(); + + const notificationPage = new NotificationPage(page); + + // Navigate to notifications + await notificationPage.clickNotificationsNavBarItem(); + + // Filter by severity + await notificationPage.selectSeverity("critical"); + + // Check for specific notification + await notificationPage.notificationContains("Critical alert"); + + // Sort by newest + await notificationPage.sortByNewestOnTop(); + + // Save important notification + await notificationPage.saveNotification(0); + + // View saved notifications + await notificationPage.viewSaved(); + + // Mark all as read + await notificationPage.markAllNotificationsAsRead(); + + // View all again + await notificationPage.viewAll(); +}); +``` diff --git a/docs/guide/quick-start.md b/docs/guide/quick-start.md new file mode 100644 index 0000000..c633397 --- /dev/null +++ b/docs/guide/quick-start.md @@ -0,0 +1,195 @@ +# Quick Start + +This guide walks you through creating your first E2E test for an RHDH plugin. + +## Prerequisites + +Before starting, ensure you have: + +- Node.js >= 22 +- Yarn >= 3 +- Access to an OpenShift cluster +- `oc`, `kubectl`, and `helm` CLI tools installed + +## Step 1: Set Up Your Project + +```bash +mkdir my-plugin-e2e && cd my-plugin-e2e +yarn init -y +yarn add @playwright/test rhdh-e2e-test-utils typescript +``` + +## Step 2: Create Playwright Configuration + +Create `playwright.config.ts`: + +```typescript +import { defineConfig } from "rhdh-e2e-test-utils/playwright-config"; +import dotenv from "dotenv"; + +dotenv.config({ path: `${import.meta.dirname}/.env` }); + +export default defineConfig({ + projects: [ + { + name: "my-plugin", // Also used as Kubernetes namespace + }, + ], +}); +``` + +## Step 3: Create Configuration Files + +Create `tests/config/app-config-rhdh.yaml`: + +```yaml +app: + title: My Plugin Test Instance + +# Add plugin-specific configuration here +``` + +Create `tests/config/dynamic-plugins.yaml`: + +```yaml +includes: + - dynamic-plugins.default.yaml + +plugins: + - package: ./dynamic-plugins/dist/my-frontend-plugin + disabled: false + - package: ./dynamic-plugins/dist/my-backend-plugin-dynamic + disabled: false +``` + +Create `tests/config/rhdh-secrets.yaml`: + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: rhdh-secrets +type: Opaque +stringData: + # Add secrets with environment variable placeholders + GITHUB_TOKEN: $GITHUB_TOKEN +``` + +## Step 4: Create Environment File + +Create `.env`: + +```bash +RHDH_VERSION="1.5" +INSTALLATION_METHOD="helm" +# Set to true if you don't need Keycloak auth +SKIP_KEYCLOAK_DEPLOYMENT=false +``` + +## Step 5: Create Your First Test + +Create `tests/specs/my-plugin.spec.ts`: + +```typescript +import { test, expect } from "rhdh-e2e-test-utils/test"; + +test.describe("My Plugin Tests", () => { + test.beforeAll(async ({ rhdh }) => { + // Configure RHDH with Keycloak authentication + await rhdh.configure({ auth: "keycloak" }); + + // Deploy RHDH instance + await rhdh.deploy(); + }); + + test.beforeEach(async ({ loginHelper }) => { + // Login before each test + await loginHelper.loginAsKeycloakUser(); + }); + + test("should display plugin page", async ({ page, uiHelper }) => { + // Navigate to your plugin + await uiHelper.openSidebar("My Plugin"); + + // Verify the page loaded + await uiHelper.verifyHeading("My Plugin"); + + // Add more assertions as needed + await expect(page.locator("text=Welcome")).toBeVisible(); + }); + + test("should interact with plugin features", async ({ page, uiHelper }) => { + await uiHelper.openSidebar("My Plugin"); + + // Click a button + await uiHelper.clickButton("Get Started"); + + // Verify result + await uiHelper.verifyText("Feature enabled"); + }); +}); +``` + +## Step 6: Login to OpenShift + +Ensure you're logged into your OpenShift cluster: + +```bash +oc login --server=https://api.your-cluster.com:6443 --token=your-token +``` + +## Step 7: Run Tests + +```bash +yarn playwright test +``` + +Or with UI mode: + +```bash +yarn playwright test --ui +``` + +## What Happens When Tests Run + +1. **Global Setup** runs first: + - Validates required binaries (`oc`, `kubectl`, `helm`) + - Fetches OpenShift cluster ingress domain + - Deploys Keycloak (unless `SKIP_KEYCLOAK_DEPLOYMENT=true`) + +2. **Before All** runs per worker: + - Creates Kubernetes namespace (derived from project name) + - Configures RHDH with your settings + - Deploys RHDH instance + +3. **Before Each** runs per test: + - Logs in with the specified authentication method + +4. **Tests** run: + - Each test gets a fresh page but shares the RHDH deployment + +5. **Cleanup** (in CI): + - Namespaces are automatically deleted + +## Using Guest Authentication + +For simpler tests without Keycloak: + +```typescript +test.beforeAll(async ({ rhdh }) => { + await rhdh.configure({ auth: "guest" }); + await rhdh.deploy(); +}); + +test.beforeEach(async ({ loginHelper }) => { + await loginHelper.loginAsGuest(); +}); +``` + +Set `SKIP_KEYCLOAK_DEPLOYMENT=true` in your `.env` file. + +## Next Steps + +- [Core Concepts](/guide/core-concepts/) - Understand fixtures and deployment +- [Helpers](/guide/helpers/) - Learn about UIhelper, LoginHelper, APIHelper +- [Examples](/examples/) - See more complete examples diff --git a/docs/guide/requirements.md b/docs/guide/requirements.md new file mode 100644 index 0000000..36b6937 --- /dev/null +++ b/docs/guide/requirements.md @@ -0,0 +1,141 @@ +# Requirements + +## System Requirements + +| Requirement | Version | +|-------------|---------| +| Node.js | >= 22 | +| Yarn | >= 3 (uses Corepack) | + +### Enabling Corepack + +This project uses Yarn 3 with Corepack. Enable it with: + +```bash +corepack enable +``` + +## Required CLI Tools + +The following binaries must be installed and available in your PATH: + +| Tool | Purpose | +|------|---------| +| `oc` | OpenShift CLI for cluster operations | +| `kubectl` | Kubernetes CLI (used as fallback) | +| `helm` | Helm CLI for chart deployments | + +### Installing CLI Tools + +**OpenShift CLI (oc)** +```bash +# macOS +brew install openshift-cli + +# Linux +curl -LO https://mirror.openshift.com/pub/openshift-v4/clients/ocp/latest/openshift-client-linux.tar.gz +tar xvzf openshift-client-linux.tar.gz +sudo mv oc /usr/local/bin/ +``` + +**Helm** +```bash +# macOS +brew install helm + +# Linux +curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash +``` + +## OpenShift Cluster Requirements + +You must be logged into an OpenShift cluster with sufficient permissions to: + +| Permission | Resource | +|------------|----------| +| Create/Delete | Namespaces | +| Create/Update | ConfigMaps | +| Create/Update | Secrets | +| Create/Read | Routes | +| Install | Helm charts | +| Install | RHDH Operator (if using operator method) | +| Read | Cluster ingress configuration | + +### Logging into OpenShift + +```bash +# Using token +oc login --server=https://api.your-cluster.com:6443 --token=your-token + +# Using username/password +oc login --server=https://api.your-cluster.com:6443 -u username -p password +``` + +### Verifying Cluster Access + +```bash +# Check cluster connection +oc whoami + +# Check permissions +oc auth can-i create namespace +oc auth can-i create secret +oc auth can-i create configmap +``` + +## RHDH Requirements + +| Component | Description | +|-----------|-------------| +| RHDH Version | Set via `RHDH_VERSION` environment variable (e.g., "1.5") | +| Helm Chart | Default: `oci://quay.io/rhdh/chart` (customizable via `CHART_URL`) | + +## Keycloak Requirements (Optional) + +If using Keycloak authentication: + +| Component | Description | +|-----------|-------------| +| Namespace | `rhdh-keycloak` (default) | +| Helm Chart | Bitnami Keycloak chart | +| Storage | PersistentVolumeClaim for Keycloak data | + +## Resource Requirements + +Minimum cluster resources for running tests: + +| Component | CPU | Memory | +|-----------|-----|--------| +| RHDH | 1 core | 2Gi | +| Keycloak | 0.5 core | 1Gi | +| PostgreSQL (for Keycloak) | 0.25 core | 512Mi | + +## Network Requirements + +| Requirement | Description | +|-------------|-------------| +| OpenShift Routes | Must be accessible from test runner | +| Ingress Domain | Automatically detected from cluster | +| HTTPS | Routes use cluster's default TLS | + +## Environment Variables + +### Required + +| Variable | Description | Example | +|----------|-------------|---------| +| `RHDH_VERSION` | RHDH version to deploy | `"1.5"` | +| `INSTALLATION_METHOD` | Deployment method | `"helm"` or `"operator"` | + +### Optional + +| Variable | Description | Default | +|----------|-------------|---------| +| `SKIP_KEYCLOAK_DEPLOYMENT` | Skip Keycloak auto-deployment | `false` | +| `CHART_URL` | Custom Helm chart URL | `oci://quay.io/rhdh/chart` | +| `CI` | If set, enables auto-cleanup | - | + +## Next Steps + +- [Installation](/guide/installation) - Install the package +- [Quick Start](/guide/quick-start) - Create your first test diff --git a/docs/guide/utilities/bash-utilities.md b/docs/guide/utilities/bash-utilities.md new file mode 100644 index 0000000..8b1e83a --- /dev/null +++ b/docs/guide/utilities/bash-utilities.md @@ -0,0 +1,73 @@ +# Bash Utilities + +The package exports `$` from the `zx` library for executing shell commands. + +## Usage + +```typescript +import { $ } from "rhdh-e2e-test-utils/utils"; +``` + +## Basic Commands + +```typescript +// Simple command +await $`echo "Hello World"`; + +// With variables +const namespace = "my-namespace"; +await $`oc get pods -n ${namespace}`; +``` + +## Capturing Output + +```typescript +const result = await $`oc get pods -n my-namespace -o json`; +console.log(result.stdout); + +// Parse JSON output +const pods = JSON.parse(result.stdout); +console.log(`Found ${pods.items.length} pods`); +``` + +## Error Handling + +```typescript +try { + await $`oc get pods -n nonexistent`; +} catch (error) { + console.log(`Command failed with exit code: ${error.exitCode}`); + console.log(`stderr: ${error.stderr}`); +} +``` + +## Complete Example + +```typescript +import { test } from "rhdh-e2e-test-utils/test"; +import { $ } from "rhdh-e2e-test-utils/utils"; + +test.beforeAll(async ({ rhdh }) => { + const namespace = rhdh.deploymentConfig.namespace; + + // Configure RHDH first + await rhdh.configure({ auth: "keycloak" }); + + // Run setup script + await $`bash ${import.meta.dirname}/scripts/setup.sh ${namespace}`; + + // Get route from deployed service + const result = await $`oc get route my-service -n ${namespace} -o jsonpath='{.spec.host}'`; + process.env.MY_SERVICE_URL = `https://${result.stdout.trim()}`; + + // Deploy RHDH + await rhdh.deploy(); +}); +``` + +## Best Practices + +1. **Use template literals** - Always use backticks for `$` +2. **Handle errors** - Wrap in try/catch for commands that may fail +3. **Quote variables** - Variables are automatically quoted +4. **Use absolute paths** - For scripts and files diff --git a/docs/guide/utilities/environment-substitution.md b/docs/guide/utilities/environment-substitution.md new file mode 100644 index 0000000..e470193 --- /dev/null +++ b/docs/guide/utilities/environment-substitution.md @@ -0,0 +1,115 @@ +# Environment Substitution + +The `envsubst` function replaces environment variable placeholders in strings. + +## Usage + +```typescript +import { envsubst } from "rhdh-e2e-test-utils/utils"; +``` + +## Basic Substitution + +```typescript +// Simple variable +process.env.API_URL = "https://api.example.com"; +const result = envsubst("URL: $API_URL"); +// Result: "URL: https://api.example.com" + +// With braces +const result2 = envsubst("URL: ${API_URL}"); +// Result: "URL: https://api.example.com" +``` + +## Default Values + +Use `:-` syntax for default values: + +```typescript +// If PORT is not set, use 8080 +const result = envsubst("Port: ${PORT:-8080}"); +// Result: "Port: 8080" + +// If PORT is set +process.env.PORT = "3000"; +const result2 = envsubst("Port: ${PORT:-8080}"); +// Result: "Port: 3000" +``` + +## YAML Configuration + +Common use case is processing YAML configuration files: + +```yaml +# config-template.yaml +app: + title: ${APP_TITLE:-My App} + baseUrl: https://${HOST}.${DOMAIN} + +backend: + cors: + origin: ${CORS_ORIGIN:-*} +``` + +```typescript +import { envsubst } from "rhdh-e2e-test-utils/utils"; +import * as fs from "fs"; + +const template = fs.readFileSync("config-template.yaml", "utf-8"); +const config = envsubst(template); +fs.writeFileSync("config.yaml", config); +``` + +## Secrets Processing + +The package uses `envsubst` internally for secrets: + +```yaml +# rhdh-secrets.yaml +apiVersion: v1 +kind: Secret +metadata: + name: rhdh-secrets +type: Opaque +stringData: + GITHUB_TOKEN: ${GITHUB_TOKEN} + API_KEY: ${API_KEY:-default-key} +``` + +When deployed, environment variables are substituted automatically. + +## Complete Example + +```typescript +import { test } from "rhdh-e2e-test-utils/test"; +import { envsubst } from "rhdh-e2e-test-utils/utils"; + +test.beforeAll(async ({ rhdh }) => { + // Set environment variables + process.env.CUSTOM_API_URL = "https://custom.example.com"; + process.env.NAMESPACE = rhdh.deploymentConfig.namespace; + + // Template with variables + const template = ` + backend: + baseUrl: https://backstage-\${NAMESPACE}.\${K8S_CLUSTER_ROUTER_BASE} + custom: + apiUrl: \${CUSTOM_API_URL} + `; + + // Substitute variables + const config = envsubst(template); + console.log(config); + + await rhdh.configure({ auth: "keycloak" }); + await rhdh.deploy(); +}); +``` + +## Supported Syntax + +| Syntax | Description | +|--------|-------------| +| `$VAR` | Simple substitution | +| `${VAR}` | Braced substitution | +| `${VAR:-default}` | Default if unset or empty | diff --git a/docs/guide/utilities/index.md b/docs/guide/utilities/index.md new file mode 100644 index 0000000..f7a2ea5 --- /dev/null +++ b/docs/guide/utilities/index.md @@ -0,0 +1,77 @@ +# Utilities Overview + +The package provides utility functions for common operations in E2E testing. + +## Available Utilities + +| Utility | Purpose | +|---------|---------| +| [KubernetesClientHelper](/guide/utilities/kubernetes-client) | Kubernetes API operations | +| [Bash ($)](/guide/utilities/bash-utilities) | Shell command execution | +| [YAML Merging](/guide/utilities/yaml-merging) | Merge YAML files | +| [envsubst](/guide/utilities/environment-substitution) | Environment variable substitution | + +## Importing Utilities + +```typescript +import { + $, + KubernetesClientHelper, + envsubst, + mergeYamlFiles, + mergeYamlFilesToFile, +} from "rhdh-e2e-test-utils/utils"; +``` + +## Quick Examples + +### Bash Commands + +```typescript +import { $ } from "rhdh-e2e-test-utils/utils"; + +// Execute commands +await $`oc get pods -n my-namespace`; + +// Capture output +const result = await $`oc get pods -o json`; +console.log(result.stdout); +``` + +### Kubernetes Operations + +```typescript +import { KubernetesClientHelper } from "rhdh-e2e-test-utils/utils"; + +const k8s = new KubernetesClientHelper(); + +// Create namespace +await k8s.createNamespaceIfNotExists("my-namespace"); + +// Apply ConfigMap +await k8s.applyConfigMapFromObject("my-config", { key: "value" }, "my-namespace"); + +// Get route URL +const url = await k8s.getRouteLocation("my-namespace", "my-route"); +``` + +### Environment Substitution + +```typescript +import { envsubst } from "rhdh-e2e-test-utils/utils"; + +const template = "API URL: ${API_URL:-http://localhost}"; +const result = envsubst(template); +// Result: "API URL: http://localhost" (if API_URL not set) +``` + +### YAML Merging + +```typescript +import { mergeYamlFiles } from "rhdh-e2e-test-utils/utils"; + +const merged = mergeYamlFiles([ + "base-config.yaml", + "override-config.yaml", +]); +``` diff --git a/docs/guide/utilities/kubernetes-client.md b/docs/guide/utilities/kubernetes-client.md new file mode 100644 index 0000000..41b25c0 --- /dev/null +++ b/docs/guide/utilities/kubernetes-client.md @@ -0,0 +1,146 @@ +# Kubernetes Client + +The `KubernetesClientHelper` class provides a simplified wrapper around the Kubernetes JavaScript client. + +## Usage + +```typescript +import { KubernetesClientHelper } from "rhdh-e2e-test-utils/utils"; + +const k8sClient = new KubernetesClientHelper(); +``` + +## Namespace Operations + +### `createNamespaceIfNotExists(namespace)` + +Create a namespace if it doesn't exist: + +```typescript +await k8sClient.createNamespaceIfNotExists("my-namespace"); +``` + +### `deleteNamespace(namespace)` + +Delete a namespace: + +```typescript +await k8sClient.deleteNamespace("my-namespace"); +``` + +## ConfigMap Operations + +### `applyConfigMapFromObject(name, data, namespace)` + +Create or update a ConfigMap from an object: + +```typescript +await k8sClient.applyConfigMapFromObject( + "app-config", + { + "app-config.yaml": ` + app: + title: My App + `, + }, + "my-namespace" +); +``` + +### `getConfigMap(name, namespace)` + +Get a ConfigMap: + +```typescript +const configMap = await k8sClient.getConfigMap("app-config", "my-namespace"); +console.log(configMap.data); +``` + +## Secret Operations + +### `applySecretFromObject(name, data, namespace)` + +Create or update a Secret: + +```typescript +await k8sClient.applySecretFromObject( + "my-secrets", + { + stringData: { + API_KEY: "secret-value", + TOKEN: "another-secret", + }, + }, + "my-namespace" +); +``` + +## Route Operations + +### `getRouteLocation(namespace, routeName)` + +Get the URL of an OpenShift Route: + +```typescript +const url = await k8sClient.getRouteLocation("my-namespace", "backstage"); +// Returns: "https://backstage-my-namespace.apps.cluster.example.com" +``` + +### `getClusterIngressDomain()` + +Get the cluster's ingress domain: + +```typescript +const domain = await k8sClient.getClusterIngressDomain(); +// Returns: "apps.cluster.example.com" +``` + +## Deployment Operations + +### `scaleDeployment(namespace, name, replicas)` + +Scale a deployment: + +```typescript +await k8sClient.scaleDeployment("my-namespace", "backstage", 2); +``` + +### `restartDeployment(namespace, name)` + +Restart a deployment (rolling restart): + +```typescript +await k8sClient.restartDeployment("my-namespace", "backstage"); +``` + +## Complete Example + +```typescript +import { test } from "rhdh-e2e-test-utils/test"; + +test("kubernetes operations", async ({ rhdh }) => { + const k8s = rhdh.k8sClient; + const namespace = rhdh.deploymentConfig.namespace; + + // Create ConfigMap + await k8s.applyConfigMapFromObject( + "custom-config", + { "custom.yaml": "key: value" }, + namespace + ); + + // Create Secret + await k8s.applySecretFromObject( + "custom-secrets", + { stringData: { TOKEN: "secret" } }, + namespace + ); + + // Restart deployment to pick up changes + await k8s.restartDeployment(namespace, "backstage"); + + // Get route URL + const url = await k8s.getRouteLocation(namespace, "backstage"); + console.log(`RHDH available at: ${url}`); +}); +``` diff --git a/docs/guide/utilities/yaml-merging.md b/docs/guide/utilities/yaml-merging.md new file mode 100644 index 0000000..4bb9704 --- /dev/null +++ b/docs/guide/utilities/yaml-merging.md @@ -0,0 +1,100 @@ +# YAML Merging + +The package provides utilities for merging multiple YAML files. + +## Usage + +```typescript +import { mergeYamlFiles, mergeYamlFilesToFile } from "rhdh-e2e-test-utils/utils"; +``` + +## `mergeYamlFiles(files)` + +Merge multiple YAML files and return the merged content: + +```typescript +const merged = mergeYamlFiles([ + "base-config.yaml", + "override-config.yaml", +]); + +console.log(merged); +``` + +### Merge Order + +Later files override earlier files: + +```typescript +const merged = mergeYamlFiles([ + "defaults.yaml", // Base defaults + "environment.yaml", // Environment-specific + "local.yaml", // Local overrides +]); +``` + +## `mergeYamlFilesToFile(files, outputPath)` + +Merge YAML files and write to a file: + +```typescript +mergeYamlFilesToFile( + [ + "config/base.yaml", + "config/auth.yaml", + "config/plugins.yaml", + ], + "config/merged.yaml" +); +``` + +## Deep Merging + +The merge is deep, meaning nested objects are merged recursively: + +**base.yaml:** +```yaml +app: + title: Default Title + baseUrl: http://localhost:7007 +``` + +**override.yaml:** +```yaml +app: + title: Production App +``` + +**Result:** +```yaml +app: + title: Production App + baseUrl: http://localhost:7007 +``` + +## Complete Example + +```typescript +import { test } from "rhdh-e2e-test-utils/test"; +import { mergeYamlFilesToFile } from "rhdh-e2e-test-utils/utils"; +import * as path from "path"; + +test.beforeAll(async ({ rhdh }) => { + const configDir = path.join(import.meta.dirname, "config"); + + // Merge configurations + mergeYamlFilesToFile( + [ + path.join(configDir, "base.yaml"), + path.join(configDir, "plugins.yaml"), + ], + path.join(configDir, "merged.yaml") + ); + + await rhdh.configure({ + appConfig: path.join(configDir, "merged.yaml"), + }); + + await rhdh.deploy(); +}); +``` diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..b72eeea --- /dev/null +++ b/docs/index.md @@ -0,0 +1,84 @@ +--- +layout: home + +hero: + name: "RHDH E2E Test Utils" + text: "End-to-End Testing Framework" + tagline: Deploy, test, and validate Red Hat Developer Hub plugins with Playwright + actions: + - theme: brand + text: Get Started + link: /guide/ + - theme: alt + text: API Reference + link: /api/ + - theme: alt + text: View on GitHub + link: https://github.com/redhat-developer/rhdh-e2e-test-utils + +features: + - icon: 🚀 + title: Automated RHDH Deployment + details: Deploy RHDH instances via Helm or the RHDH Operator with automatic namespace management and cleanup. + - icon: 🔑 + title: Keycloak Integration + details: Deploy and configure Keycloak for OIDC authentication testing with pre-configured realms, clients, and users. + - icon: 🧩 + title: Playwright Integration + details: Custom test fixtures that manage deployment lifecycle with rhdh, uiHelper, loginHelper, and baseURL fixtures. + - icon: 📦 + title: Helper Classes + details: UIhelper for Material-UI interactions, LoginHelper for multi-provider auth, APIHelper for GitHub and Backstage APIs. + - icon: 📄 + title: Page Objects + details: Pre-built page objects for CatalogPage, HomePage, CatalogImportPage, ExtensionsPage, and NotificationPage. + - icon: ⚙️ + title: Configuration Tools + details: ESLint configuration factory with Playwright best practices and TypeScript base configuration. +--- + +## Quick Example + +```typescript +// playwright.config.ts +import { defineConfig } from "rhdh-e2e-test-utils/playwright-config"; + +export default defineConfig({ + projects: [{ name: "my-plugin" }], +}); +``` + +```typescript +// tests/my-plugin.spec.ts +import { test, expect } from "rhdh-e2e-test-utils/test"; + +test.beforeAll(async ({ rhdh }) => { + await rhdh.configure({ auth: "keycloak" }); + await rhdh.deploy(); +}); + +test.beforeEach(async ({ loginHelper }) => { + await loginHelper.loginAsKeycloakUser(); +}); + +test("verify catalog", async ({ uiHelper }) => { + await uiHelper.openSidebar("Catalog"); + await uiHelper.verifyHeading("My Catalog"); +}); +``` + +## What is rhdh-e2e-test-utils? + +`rhdh-e2e-test-utils` is a comprehensive test utility package for Red Hat Developer Hub (RHDH) end-to-end testing. It provides a unified framework for: + +- **Deploying RHDH instances** to OpenShift clusters via Helm or the RHDH Operator +- **Managing Keycloak** for OIDC authentication testing +- **Running Playwright tests** with custom fixtures optimized for RHDH +- **Interacting with RHDH UI** through helper classes and page objects +- **Managing Kubernetes resources** through a simplified client API + +## Next Steps + +- [Installation](/guide/installation) - Install the package +- [Quick Start](/guide/quick-start) - Create your first test +- [API Reference](/api/) - Explore the full API diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 0000000..c1d71de --- /dev/null +++ b/docs/package.json @@ -0,0 +1,18 @@ +{ + "name": "rhdh-e2e-test-utils-docs", + "version": "1.0.0", + "private": true, + "description": "Documentation for rhdh-e2e-test-utils", + "type": "module", + "scripts": { + "dev": "vitepress dev", + "build": "vitepress build", + "preview": "vitepress preview" + }, + "devDependencies": { + "vitepress": "^1.5.0" + }, + "engines": { + "node": ">=18" + } +} diff --git a/docs/snippets/app-config-rhdh.yaml b/docs/snippets/app-config-rhdh.yaml new file mode 100644 index 0000000..4f62e09 --- /dev/null +++ b/docs/snippets/app-config-rhdh.yaml @@ -0,0 +1,15 @@ +app: + title: Red Hat Developer Hub + baseUrl: ${RHDH_BASE_URL} + +backend: + baseUrl: ${RHDH_BASE_URL} + cors: + origin: ${RHDH_BASE_URL} + +catalog: + rules: + - allow: [Component, System, API, Resource, Location, Template] + locations: + - type: url + target: https://github.com/backstage/backstage/blob/master/catalog-info.yaml diff --git a/docs/snippets/basic-test.ts b/docs/snippets/basic-test.ts new file mode 100644 index 0000000..cdac40a --- /dev/null +++ b/docs/snippets/basic-test.ts @@ -0,0 +1,13 @@ +import { test, expect } from "rhdh-e2e-test-utils/test"; + +test.describe("RHDH Basic Tests", () => { + test("should load the home page", async ({ uiHelper }) => { + await uiHelper.openSidebar("Home"); + await uiHelper.verifyHeading("Welcome to Red Hat Developer Hub"); + }); + + test("should navigate to catalog", async ({ uiHelper }) => { + await uiHelper.openSidebar("Catalog"); + await uiHelper.verifyHeading("My Org Catalog"); + }); +}); diff --git a/docs/snippets/env-example.env b/docs/snippets/env-example.env new file mode 100644 index 0000000..18ab1ef --- /dev/null +++ b/docs/snippets/env-example.env @@ -0,0 +1,17 @@ +# Required: OpenShift cluster configuration +K8S_CLUSTER_URL=https://api.your-cluster.example.com:6443 +K8S_CLUSTER_TOKEN=sha256~your-token-here +K8S_CLUSTER_ROUTER_BASE=apps.your-cluster.example.com + +# Required: RHDH configuration +RHDH_BASE_URL=https://backstage-developer-hub-rhdh.${K8S_CLUSTER_ROUTER_BASE} +RHDH_NAMESPACE=rhdh +RHDH_VERSION=1.5 + +# Optional: GitHub integration +GITHUB_TOKEN=ghp_your-github-token + +# Optional: Keycloak authentication +KEYCLOAK_BASE_URL=https://keycloak-rhdh.${K8S_CLUSTER_ROUTER_BASE} +KEYCLOAK_REALM=rhdh +KEYCLOAK_CLIENT_ID=backstage diff --git a/docs/snippets/error-handling.ts b/docs/snippets/error-handling.ts new file mode 100644 index 0000000..3e907a2 --- /dev/null +++ b/docs/snippets/error-handling.ts @@ -0,0 +1,45 @@ +import { test, expect } from "rhdh-e2e-test-utils/test"; + +test.describe("Error Handling Patterns", () => { + test("handling API errors gracefully", async ({ apiHelper }) => { + try { + const entity = await apiHelper.getEntityByName("non-existent-entity"); + expect(entity).toBeDefined(); + } catch (error) { + // Entity not found - this is expected in some test scenarios + console.log("Entity not found, creating it..."); + // Handle the error appropriately + } + }); + + test("with timeout handling", async ({ uiHelper, page }) => { + // Set a custom timeout for slow operations + await test.step("Wait for slow component", async () => { + await expect(page.getByTestId("slow-component")).toBeVisible({ + timeout: 30000, + }); + }); + }); + + test("with retry logic", async ({ apiHelper }) => { + const maxRetries = 3; + let lastError: Error | undefined; + + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + await apiHelper.waitForEntityToAppear("my-component"); + break; // Success, exit loop + } catch (error) { + lastError = error as Error; + if (attempt < maxRetries) { + console.log(`Attempt ${attempt} failed, retrying...`); + await new Promise((resolve) => setTimeout(resolve, 2000)); + } + } + } + + if (lastError) { + throw lastError; + } + }); +}); diff --git a/docs/snippets/global-setup.ts b/docs/snippets/global-setup.ts new file mode 100644 index 0000000..ef407b1 --- /dev/null +++ b/docs/snippets/global-setup.ts @@ -0,0 +1,15 @@ +import { RHDHDeployment } from "rhdh-e2e-test-utils/rhdh"; + +async function globalSetup() { + const deployment = new RHDHDeployment({ + appConfig: "tests/config/app-config-rhdh.yaml", + dynamicPlugins: "tests/config/dynamic-plugins.yaml", + secrets: "tests/config/rhdh-secrets.yaml", + auth: "keycloak", // or "guest" + }); + + await deployment.deploy(); + await deployment.waitForDeployment(); +} + +export default globalSetup; diff --git a/docs/snippets/guest-test.ts b/docs/snippets/guest-test.ts new file mode 100644 index 0000000..8422687 --- /dev/null +++ b/docs/snippets/guest-test.ts @@ -0,0 +1,12 @@ +import { test, expect } from "rhdh-e2e-test-utils/test"; + +test.describe("Guest Authentication Tests", () => { + test.beforeEach(async ({ loginHelper }) => { + await loginHelper.loginAsGuest(); + }); + + test("should access catalog as guest", async ({ uiHelper }) => { + await uiHelper.openSidebar("Catalog"); + await uiHelper.verifyHeading("My Org Catalog"); + }); +}); diff --git a/docs/snippets/keycloak-credentials.md b/docs/snippets/keycloak-credentials.md new file mode 100644 index 0000000..4ab2e05 --- /dev/null +++ b/docs/snippets/keycloak-credentials.md @@ -0,0 +1,10 @@ +| Username | Password | Description | +|----------|----------|-------------| +| `test1` | `test1@123` | Default test user with standard permissions | +| `test2` | `test2@123` | Secondary test user for multi-user scenarios | + +::: tip Environment Variables +You can override these defaults using environment variables: +- `KEYCLOAK_USERNAME` - Override the default username +- `KEYCLOAK_PASSWORD` - Override the default password +::: diff --git a/docs/snippets/keycloak-test.ts b/docs/snippets/keycloak-test.ts new file mode 100644 index 0000000..8b7701a --- /dev/null +++ b/docs/snippets/keycloak-test.ts @@ -0,0 +1,13 @@ +import { test, expect } from "rhdh-e2e-test-utils/test"; + +test.describe("Keycloak Authentication Tests", () => { + test.beforeEach(async ({ loginHelper }) => { + // Login with default Keycloak user (test1/test1@123) + await loginHelper.loginAsKeycloakUser(); + }); + + test("should display authenticated user info", async ({ uiHelper }) => { + await uiHelper.openSidebar("Settings"); + await uiHelper.verifyText("test1"); + }); +}); diff --git a/docs/snippets/playwright-config.ts b/docs/snippets/playwright-config.ts new file mode 100644 index 0000000..36f900f --- /dev/null +++ b/docs/snippets/playwright-config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from "@playwright/test"; +import { baseConfig } from "rhdh-e2e-test-utils/playwright-config"; + +export default defineConfig({ + ...baseConfig, + testDir: "./tests", + projects: [ + { + name: "rhdh-e2e", + use: { ...baseConfig.use }, + }, + ], +}); diff --git a/docs/snippets/serial-vs-parallel.ts b/docs/snippets/serial-vs-parallel.ts new file mode 100644 index 0000000..370beca --- /dev/null +++ b/docs/snippets/serial-vs-parallel.ts @@ -0,0 +1,63 @@ +// ============================================ +// PARALLEL TESTS (Default - Recommended) +// ============================================ +// Each test gets its own browser context +// Tests run independently and can run concurrently +// Best for: Independent tests, faster execution + +import { test } from "rhdh-e2e-test-utils/test"; + +test.describe("Parallel Tests", () => { + test.beforeEach(async ({ loginHelper }) => { + // Each test logs in fresh - isolated but slower + await loginHelper.loginAsGuest(); + }); + + test("test A - runs independently", async ({ uiHelper }) => { + await uiHelper.openSidebar("Catalog"); + }); + + test("test B - runs independently", async ({ uiHelper }) => { + await uiHelper.openSidebar("Home"); + }); +}); + +// ============================================ +// SERIAL TESTS (Shared browser session) +// ============================================ +// All tests share the same browser context +// Tests run sequentially in order +// Best for: Workflow tests, resource efficiency + +import { setupBrowser, UIhelper, LoginHelper } from "rhdh-e2e-test-utils/helpers"; + +test.describe.configure({ mode: "serial" }); + +test.describe("Serial Tests - Shared Session", () => { + let uiHelper: UIhelper; + let loginHelper: LoginHelper; + + test.beforeAll(async ({ browser }) => { + const context = await setupBrowser(browser); + const page = await context.newPage(); + uiHelper = new UIhelper(page); + loginHelper = new LoginHelper(page, uiHelper); + + // Login once for all tests + await loginHelper.loginAsGuest(); + }); + + test("step 1: navigate to catalog", async () => { + await uiHelper.openSidebar("Catalog"); + }); + + test("step 2: select an entity", async () => { + // Uses same session from step 1 + await uiHelper.clickByDataTestId("entity-row-0"); + }); + + test("step 3: verify entity details", async () => { + // Uses same session from steps 1-2 + await uiHelper.verifyHeading("Entity Details"); + }); +}); diff --git a/docs/tutorials/ci-cd-integration.md b/docs/tutorials/ci-cd-integration.md new file mode 100644 index 0000000..e64e8dc --- /dev/null +++ b/docs/tutorials/ci-cd-integration.md @@ -0,0 +1,179 @@ +# CI/CD Integration + +Integrate E2E tests with GitHub Actions. + +## GitHub Actions Workflow + +**.github/workflows/e2e-tests.yaml:** +```yaml +name: E2E Tests + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + e2e-tests: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "22" + cache: "yarn" + + - name: Install dependencies + run: yarn install --frozen-lockfile + working-directory: e2e-tests + + - name: Install Playwright browsers + run: yarn playwright install --with-deps + working-directory: e2e-tests + + - name: Login to OpenShift + uses: redhat-actions/oc-login@v1 + with: + openshift_server_url: ${{ secrets.OPENSHIFT_SERVER }} + openshift_token: ${{ secrets.OPENSHIFT_TOKEN }} + + - name: Run E2E tests + run: yarn playwright test + working-directory: e2e-tests + env: + CI: true + RHDH_VERSION: "1.5" + INSTALLATION_METHOD: "helm" + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload test results + uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report + path: e2e-tests/playwright-report/ + retention-days: 30 + + - name: Upload test videos + uses: actions/upload-artifact@v4 + if: failure() + with: + name: test-videos + path: e2e-tests/test-results/ + retention-days: 7 +``` + +## Required Secrets + +Set these in your repository settings: + +| Secret | Description | +|--------|-------------| +| `OPENSHIFT_SERVER` | OpenShift API server URL | +| `OPENSHIFT_TOKEN` | Service account token | +| `GITHUB_TOKEN` | Auto-provided by GitHub | + +## Creating OpenShift Token + +```bash +# Create service account +oc create serviceaccount e2e-tests -n default + +# Grant permissions +oc adm policy add-cluster-role-to-user cluster-admin -z e2e-tests -n default + +# Get token +oc create token e2e-tests -n default --duration=8760h +``` + +## Environment Variables in CI + +The `CI` environment variable enables: + +- Auto-cleanup of namespaces +- Increased retries (2 instead of 0) +- Non-interactive mode + +## Parallel Jobs + +Run projects in parallel: + +```yaml +jobs: + e2e-tests: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + project: [tech-radar, catalog, topology] + + steps: + # ... setup steps ... + + - name: Run E2E tests + run: yarn playwright test --project=${{ matrix.project }} +``` + +## Caching + +```yaml +- name: Cache Playwright browsers + uses: actions/cache@v4 + with: + path: ~/.cache/ms-playwright + key: playwright-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }} +``` + +## Test Reporting + +### HTML Report + +```yaml +- name: Upload HTML report + uses: actions/upload-artifact@v4 + with: + name: playwright-report + path: e2e-tests/playwright-report/ +``` + +### JUnit Report + +**playwright.config.ts:** +```typescript +export default defineConfig({ + reporter: [ + ["list"], + ["html"], + ["junit", { outputFile: "test-results/junit.xml" }], + ], +}); +``` + +```yaml +- name: Publish test results + uses: mikepenz/action-junit-report@v4 + if: always() + with: + report_paths: "e2e-tests/test-results/junit.xml" +``` + +## Scheduled Runs + +```yaml +on: + schedule: + - cron: "0 6 * * *" # Daily at 6 AM +``` + +## Best Practices + +1. **Use fail-fast: false** - Run all projects even if one fails +2. **Upload artifacts on failure** - Debug failed tests +3. **Cache dependencies** - Speed up runs +4. **Use matrix for parallel** - Faster feedback +5. **Set appropriate timeouts** - Avoid stuck jobs diff --git a/docs/tutorials/custom-page-objects.md b/docs/tutorials/custom-page-objects.md new file mode 100644 index 0000000..05a3154 --- /dev/null +++ b/docs/tutorials/custom-page-objects.md @@ -0,0 +1,178 @@ +# Custom Page Objects + +Create reusable page objects for your plugin. + +## Why Page Objects? + +- **Encapsulation** - Hide implementation details +- **Reusability** - Use across multiple tests +- **Maintainability** - Update in one place +- **Readability** - Clear, descriptive methods + +## Basic Structure + +```typescript +import { Page, Locator, expect } from "@playwright/test"; + +export class MyPluginPage { + private readonly page: Page; + private readonly heading: Locator; + private readonly submitButton: Locator; + + constructor(page: Page) { + this.page = page; + this.heading = page.getByRole("heading", { name: "My Plugin" }); + this.submitButton = page.getByRole("button", { name: "Submit" }); + } + + async go(): Promise { + await this.page.goto("/my-plugin"); + await this.heading.waitFor(); + } + + async submit(): Promise { + await this.submitButton.click(); + } +} +``` + +## Complete Example + +**tests/pages/tech-radar-page.ts:** +```typescript +import { Page, Locator, expect } from "@playwright/test"; + +export class TechRadarPage { + private readonly page: Page; + private readonly radarHeading: Locator; + private readonly radarCanvas: Locator; + private readonly quadrantButtons: Locator; + + constructor(page: Page) { + this.page = page; + this.radarHeading = page.getByRole("heading", { name: "Tech Radar" }); + this.radarCanvas = page.locator("svg.tech-radar"); + this.quadrantButtons = page.locator(".quadrant-button"); + } + + async go(): Promise { + await this.page.goto("/tech-radar"); + await this.radarHeading.waitFor(); + } + + async waitForRadarLoaded(): Promise { + await this.radarCanvas.waitFor(); + } + + async selectQuadrant(name: string): Promise { + await this.page.getByRole("button", { name }).click(); + } + + async verifyEntryVisible(entryName: string): Promise { + await expect(this.page.getByText(entryName)).toBeVisible(); + } + + async hoverEntry(entryName: string): Promise { + await this.page.getByText(entryName).hover(); + } + + async verifyTooltip(expectedText: string): Promise { + await expect(this.page.locator(".tooltip")).toContainText(expectedText); + } + + async getQuadrantCount(): Promise { + return this.quadrantButtons.count(); + } +} +``` + +## Using in Tests + +```typescript +import { test, expect } from "rhdh-e2e-test-utils/test"; +import { TechRadarPage } from "../pages/tech-radar-page"; + +test.describe("Tech Radar", () => { + test.beforeAll(async ({ rhdh }) => { + await rhdh.configure({ auth: "keycloak" }); + await rhdh.deploy(); + }); + + test.beforeEach(async ({ loginHelper }) => { + await loginHelper.loginAsKeycloakUser(); + }); + + test("should display radar", async ({ page }) => { + const techRadarPage = new TechRadarPage(page); + + await techRadarPage.go(); + await techRadarPage.waitForRadarLoaded(); + await techRadarPage.verifyEntryVisible("TypeScript"); + }); + + test("should show tooltip on hover", async ({ page }) => { + const techRadarPage = new TechRadarPage(page); + + await techRadarPage.go(); + await techRadarPage.hoverEntry("TypeScript"); + await techRadarPage.verifyTooltip("Adopt"); + }); +}); +``` + +## Extending Built-in Page Objects + +```typescript +import { CatalogPage } from "rhdh-e2e-test-utils/pages"; +import { Page, expect } from "@playwright/test"; + +export class ExtendedCatalogPage extends CatalogPage { + constructor(page: Page) { + super(page); + } + + async verifyComponentHasTag(componentName: string, tag: string): Promise { + await this.goToByName(componentName); + await expect(this.page.getByText(tag)).toBeVisible(); + } + + async countComponents(): Promise { + return this.page.locator("table tbody tr").count(); + } +} +``` + +## Composing Page Objects + +```typescript +import { Page } from "@playwright/test"; +import { CatalogPage } from "rhdh-e2e-test-utils/pages"; +import { TechRadarPage } from "./tech-radar-page"; + +export class AppPages { + readonly catalog: CatalogPage; + readonly techRadar: TechRadarPage; + + constructor(page: Page) { + this.catalog = new CatalogPage(page); + this.techRadar = new TechRadarPage(page); + } +} + +// Usage +test("navigation", async ({ page }) => { + const app = new AppPages(page); + + await app.catalog.go(); + await app.techRadar.go(); +}); +``` + +## Best Practices + +1. **One page object per page** - Clear responsibility +2. **Use descriptive method names** - `verifyUserLoggedIn()` not `check()` +3. **Return Promises** - All async methods +4. **Keep locators private** - Expose methods, not elements +5. **Use waitFor** - Ensure elements are ready +6. **Avoid test logic** - No assertions in constructors diff --git a/docs/tutorials/first-test.md b/docs/tutorials/first-test.md new file mode 100644 index 0000000..1fd9427 --- /dev/null +++ b/docs/tutorials/first-test.md @@ -0,0 +1,166 @@ +# Your First Test + +Create your first E2E test for RHDH using `rhdh-e2e-test-utils`. + +::: tip Quick Start Available +For a faster setup, see the [Quick Start Guide](/guide/quick-start.md). This tutorial provides more detailed explanations for each step. +::: + +## Prerequisites + +- Node.js >= 22 +- Yarn >= 3 +- OpenShift cluster access +- `oc`, `kubectl`, `helm` installed + +## Step 1: Create Project + +```bash +mkdir my-plugin-e2e +cd my-plugin-e2e +yarn init -y +``` + +## Step 2: Install Dependencies + +```bash +yarn add @playwright/test rhdh-e2e-test-utils typescript dotenv +``` + +## Step 3: Create Configuration Files + +**playwright.config.ts:** +```typescript +import { defineConfig } from "rhdh-e2e-test-utils/playwright-config"; +import dotenv from "dotenv"; + +dotenv.config({ path: `${import.meta.dirname}/.env` }); + +export default defineConfig({ + projects: [ + { + name: "my-plugin", + }, + ], +}); +``` + +**tsconfig.json:** +```json +{ + "extends": "rhdh-e2e-test-utils/tsconfig", + "include": ["tests/**/*.ts", "playwright.config.ts"] +} +``` + +**.env:** +```bash +RHDH_VERSION="1.5" +INSTALLATION_METHOD="helm" +SKIP_KEYCLOAK_DEPLOYMENT=true +``` + +## Step 4: Create Test Directory + +```bash +mkdir -p tests/config tests/specs +``` + +## Step 5: Create Config Files + +**tests/config/app-config-rhdh.yaml:** +```yaml +app: + title: My First Test Instance +``` + +**tests/config/dynamic-plugins.yaml:** +```yaml +includes: + - dynamic-plugins.default.yaml +``` + +**tests/config/rhdh-secrets.yaml:** +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: rhdh-secrets +type: Opaque +stringData: {} +``` + +## Step 6: Write Your Test + +**tests/specs/first-test.spec.ts:** +```typescript +import { test, expect } from "rhdh-e2e-test-utils/test"; + +test.describe("My First Test Suite", () => { + test.beforeAll(async ({ rhdh }) => { + // Use guest auth for simplicity + await rhdh.configure({ auth: "guest" }); + await rhdh.deploy(); + }); + + test.beforeEach(async ({ page, loginHelper }) => { + await page.goto("/"); + await loginHelper.loginAsGuest(); + }); + + test("should display RHDH home page", async ({ uiHelper }) => { + await uiHelper.verifyHeading(/Red Hat Developer Hub|RHDH/); + }); + + test("should navigate to catalog", async ({ page, uiHelper }) => { + await uiHelper.openSidebar("Catalog"); + await expect(page).toHaveURL(/.*catalog/); + await uiHelper.verifyHeading("Catalog"); + }); +}); +``` + +## Step 7: Login to OpenShift + +```bash +oc login --server=https://api.your-cluster.com:6443 --token=your-token +``` + +## Step 8: Run Tests + +```bash +yarn playwright test +``` + +## What Happens + +1. **Global Setup** validates binaries and cluster +2. **beforeAll** creates namespace and deploys RHDH +3. **beforeEach** navigates and logs in +4. **Tests** run with assertions +5. **Cleanup** deletes namespace (in CI) + +## Debugging + +### View in Browser + +```bash +yarn playwright test --headed +``` + +### Debug Mode + +```bash +yarn playwright test --debug +``` + +### View Report + +```bash +yarn playwright show-report +``` + +## Next Steps + +- [Testing a Plugin](/tutorials/plugin-testing) - Complete workflow +- [Keycloak OIDC Testing](/tutorials/keycloak-oidc-testing) - Real auth diff --git a/docs/tutorials/index.md b/docs/tutorials/index.md new file mode 100644 index 0000000..f21153e --- /dev/null +++ b/docs/tutorials/index.md @@ -0,0 +1,33 @@ +# Tutorials + +Step-by-step guides for common testing scenarios. + +## Available Tutorials + +| Tutorial | Description | +|----------|-------------| +| [Your First Test](/tutorials/first-test) | Create your first E2E test | +| [Testing a Plugin](/tutorials/plugin-testing) | Complete plugin testing guide | +| [Multi-Project Setup](/tutorials/multi-project-setup) | Multiple Playwright projects | +| [CI/CD Integration](/tutorials/ci-cd-integration) | GitHub Actions integration | +| [Keycloak OIDC Testing](/tutorials/keycloak-oidc-testing) | Authentication testing | +| [Custom Page Objects](/tutorials/custom-page-objects) | Create custom page objects | + +## Prerequisites + +Before starting, ensure you have: + +- Node.js >= 22 +- Yarn >= 3 +- Access to an OpenShift cluster +- `oc`, `kubectl`, and `helm` CLI tools +- Basic familiarity with Playwright + +## Recommended Order + +1. **[Your First Test](/tutorials/first-test)** - Start here +2. **[Testing a Plugin](/tutorials/plugin-testing)** - Complete workflow +3. **[Keycloak OIDC Testing](/tutorials/keycloak-oidc-testing)** - Authentication +4. **[Multi-Project Setup](/tutorials/multi-project-setup)** - Scale up +5. **[CI/CD Integration](/tutorials/ci-cd-integration)** - Automation +6. **[Custom Page Objects](/tutorials/custom-page-objects)** - Advanced diff --git a/docs/tutorials/keycloak-oidc-testing.md b/docs/tutorials/keycloak-oidc-testing.md new file mode 100644 index 0000000..2c25999 --- /dev/null +++ b/docs/tutorials/keycloak-oidc-testing.md @@ -0,0 +1,179 @@ +# Keycloak OIDC Testing + +Test with Keycloak authentication for realistic auth flows. + +## Overview + +Keycloak provides: + +- OIDC authentication +- User roles and groups +- Realistic login flows +- Session management + +## Setup + +### 1. Enable Keycloak Deployment + +**.env:** +```bash +RHDH_VERSION="1.5" +INSTALLATION_METHOD="helm" +SKIP_KEYCLOAK_DEPLOYMENT=false +``` + +### 2. Configure RHDH for Keycloak + +```typescript +test.beforeAll(async ({ rhdh }) => { + await rhdh.configure({ auth: "keycloak" }); + await rhdh.deploy(); +}); +``` + +## Default Users + +Keycloak creates these users automatically: + +| Username | Password | Groups | +|----------|----------|--------| +| `test1` | `test1@123` | developers | +| `test2` | `test2@123` | developers | + +## Login in Tests + +```typescript +test.beforeEach(async ({ page, loginHelper }) => { + await page.goto("/"); + await loginHelper.loginAsKeycloakUser(); +}); + +// Or with specific credentials +test.beforeEach(async ({ page, loginHelper }) => { + await page.goto("/"); + await loginHelper.loginAsKeycloakUser("test1", "test1@123"); +}); +``` + +## Creating Custom Users + +```typescript +import { KeycloakHelper } from "rhdh-e2e-test-utils/keycloak"; + +test.beforeAll(async ({ rhdh }) => { + const keycloak = new KeycloakHelper(); + + // Connect to existing Keycloak + await keycloak.connect({ + baseUrl: process.env.KEYCLOAK_BASE_URL!, + username: "admin", + password: "admin123", + }); + + // Create admin user + await keycloak.createUser("rhdh", { + username: "admin-user", + password: "adminpass", + email: "admin@example.com", + groups: ["admins"], + }); + + // Create viewer user + await keycloak.createUser("rhdh", { + username: "viewer-user", + password: "viewerpass", + groups: ["viewers"], + }); + + await rhdh.configure({ auth: "keycloak" }); + await rhdh.deploy(); +}); +``` + +## Testing Role-Based Access + +```typescript +test.describe("Admin access", () => { + test.beforeEach(async ({ page, loginHelper }) => { + await page.goto("/"); + await loginHelper.loginAsKeycloakUser("admin-user", "adminpass"); + }); + + test("should see admin panel", async ({ uiHelper }) => { + await uiHelper.openSidebar("Settings"); + await uiHelper.verifyText("Admin Panel"); + }); +}); + +test.describe("Viewer access", () => { + test.beforeEach(async ({ page, loginHelper }) => { + await page.goto("/"); + await loginHelper.loginAsKeycloakUser("viewer-user", "viewerpass"); + }); + + test("should not see admin panel", async ({ page }) => { + await expect(page.getByText("Admin Panel")).not.toBeVisible(); + }); +}); +``` + +## Testing Logout + +```typescript +test("should logout successfully", async ({ page, loginHelper, uiHelper }) => { + await page.goto("/"); + await loginHelper.loginAsKeycloakUser(); + + // Verify logged in + await uiHelper.verifyHeading("Welcome"); + + // Logout + await loginHelper.signOut(); + + // Verify logged out + await expect(page.getByText("Sign in")).toBeVisible(); +}); +``` + +## Testing Session Expiry + +```typescript +test("should handle expired session", async ({ page, loginHelper }) => { + await page.goto("/"); + await loginHelper.loginAsKeycloakUser(); + + // Clear cookies to simulate session expiry + await page.context().clearCookies(); + + // Navigate - should redirect to login + await page.goto("/catalog"); + await expect(page.getByText("Sign in")).toBeVisible(); +}); +``` + +## Cleanup + +```typescript +import { KeycloakHelper } from "rhdh-e2e-test-utils/keycloak"; + +test.afterAll(async () => { + const keycloak = new KeycloakHelper(); + await keycloak.connect({ + baseUrl: process.env.KEYCLOAK_BASE_URL!, + username: "admin", + password: "admin123", + }); + + // Cleanup custom users + await keycloak.deleteUser("rhdh", "admin-user"); + await keycloak.deleteUser("rhdh", "viewer-user"); +}); +``` + +## Best Practices + +1. **Use default users for simple tests** - `test1`, `test2` +2. **Create custom users for RBAC tests** - Specific roles +3. **Clean up custom users** - Prevent state leakage +4. **Test logout flows** - Ensure proper session handling +5. **Test with multiple users** - Different permissions diff --git a/docs/tutorials/multi-project-setup.md b/docs/tutorials/multi-project-setup.md new file mode 100644 index 0000000..7ea9d73 --- /dev/null +++ b/docs/tutorials/multi-project-setup.md @@ -0,0 +1,170 @@ +# Multi-Project Setup + +Configure multiple Playwright projects for different test scenarios. + +## Why Multiple Projects? + +- Test different plugins separately +- Use different authentication methods +- Isolate test environments +- Run subsets of tests + +## Configuration + +**playwright.config.ts:** +```typescript +import { defineConfig } from "rhdh-e2e-test-utils/playwright-config"; +import dotenv from "dotenv"; + +dotenv.config({ path: `${import.meta.dirname}/.env` }); + +export default defineConfig({ + projects: [ + { + name: "tech-radar", + testDir: "./tests/tech-radar", + testMatch: "**/*.spec.ts", + }, + { + name: "catalog", + testDir: "./tests/catalog", + testMatch: "**/*.spec.ts", + }, + { + name: "topology", + testDir: "./tests/topology", + testMatch: "**/*.spec.ts", + }, + ], +}); +``` + +## Directory Structure + +``` +e2e-tests/ +├── playwright.config.ts +├── .env +└── tests/ + ├── tech-radar/ + │ ├── config/ + │ │ ├── app-config-rhdh.yaml + │ │ └── dynamic-plugins.yaml + │ └── specs/ + │ └── tech-radar.spec.ts + ├── catalog/ + │ ├── config/ + │ │ ├── app-config-rhdh.yaml + │ │ └── dynamic-plugins.yaml + │ └── specs/ + │ └── catalog.spec.ts + └── topology/ + ├── config/ + │ ├── app-config-rhdh.yaml + │ └── dynamic-plugins.yaml + └── specs/ + └── topology.spec.ts +``` + +## Project-Specific Configuration + +Each project gets its own namespace derived from the project name: + +- `tech-radar` → namespace `tech-radar` +- `catalog` → namespace `catalog` +- `topology` → namespace `topology` + +## Test Files + +**tests/tech-radar/specs/tech-radar.spec.ts:** +```typescript +import { test, expect } from "rhdh-e2e-test-utils/test"; + +test.beforeAll(async ({ rhdh }) => { + await rhdh.configure({ + auth: "keycloak", + appConfig: "tests/tech-radar/config/app-config-rhdh.yaml", + dynamicPlugins: "tests/tech-radar/config/dynamic-plugins.yaml", + }); + await rhdh.deploy(); +}); + +test("tech radar test", async ({ uiHelper }) => { + await uiHelper.openSidebar("Tech Radar"); + await uiHelper.verifyHeading("Tech Radar"); +}); +``` + +## Running Projects + +```bash +# Run all projects +yarn playwright test + +# Run specific project +yarn playwright test --project=tech-radar + +# Run multiple projects +yarn playwright test --project=tech-radar --project=catalog +``` + +## Parallel Execution + +Projects run in parallel by default. Each project: + +- Gets its own namespace +- Has its own RHDH deployment +- Is isolated from other projects + +## Shared Configuration + +Create shared config that all projects use: + +**tests/shared/config/base-app-config.yaml:** +```yaml +app: + title: E2E Test Instance + +backend: + cors: + origin: "*" +``` + +Then import in project configs: + +**tests/tech-radar/config/app-config-rhdh.yaml:** +```yaml +# Extends base config +app: + title: Tech Radar E2E Tests + +techRadar: + url: "http://${TECH_RADAR_URL}/data" +``` + +## Project Dependencies + +Run projects in sequence: + +```typescript +export default defineConfig({ + projects: [ + { + name: "setup", + testMatch: /setup\.ts/, + }, + { + name: "tests", + dependencies: ["setup"], + testMatch: "**/*.spec.ts", + }, + ], +}); +``` + +## Best Practices + +1. **Keep projects focused** - One plugin/feature per project +2. **Share common code** - Use shared helpers and utilities +3. **Use consistent naming** - Project name = namespace +4. **Isolate configurations** - Separate config per project diff --git a/docs/tutorials/plugin-testing.md b/docs/tutorials/plugin-testing.md new file mode 100644 index 0000000..75aede0 --- /dev/null +++ b/docs/tutorials/plugin-testing.md @@ -0,0 +1,236 @@ +# Testing a Plugin + +Complete guide to testing an RHDH plugin end-to-end. + +## Overview + +This tutorial covers: + +1. Setting up the test project +2. Configuring your plugin +3. Writing comprehensive tests +4. Running and debugging + +## Step 1: Project Setup + +```bash +cd your-plugin-workspace +mkdir e2e-tests && cd e2e-tests +yarn init -y +yarn add @playwright/test rhdh-e2e-test-utils typescript dotenv +``` + +## Step 2: Configuration + +**playwright.config.ts:** +```typescript +import { defineConfig } from "rhdh-e2e-test-utils/playwright-config"; +import dotenv from "dotenv"; + +dotenv.config({ path: `${import.meta.dirname}/.env` }); + +export default defineConfig({ + projects: [ + { + name: "my-plugin", + }, + ], +}); +``` + +**.env:** +```bash +RHDH_VERSION="1.5" +INSTALLATION_METHOD="helm" +SKIP_KEYCLOAK_DEPLOYMENT=false +``` + +## Step 3: Plugin Configuration + +**tests/config/dynamic-plugins.yaml:** +```yaml +includes: + - dynamic-plugins.default.yaml + +plugins: + # Your frontend plugin + - package: ./dynamic-plugins/dist/my-plugin + disabled: false + pluginConfig: + dynamicPlugins: + frontend: + my-plugin: + mountPoints: + - mountPoint: entity.page.overview/cards + importName: MyPluginCard + config: + layout: + gridColumnEnd: span 4 + + # Your backend plugin (if any) + - package: ./dynamic-plugins/dist/my-plugin-backend-dynamic + disabled: false +``` + +**tests/config/app-config-rhdh.yaml:** +```yaml +app: + title: My Plugin E2E Tests + +# Plugin-specific configuration +myPlugin: + apiUrl: ${MY_PLUGIN_API_URL:-http://localhost:8080} + enabled: true +``` + +## Step 4: Write Tests + +**tests/specs/my-plugin.spec.ts:** +```typescript +import { test, expect } from "rhdh-e2e-test-utils/test"; + +test.describe("My Plugin", () => { + test.beforeAll(async ({ rhdh }) => { + await rhdh.configure({ + auth: "keycloak", + appConfig: "tests/config/app-config-rhdh.yaml", + dynamicPlugins: "tests/config/dynamic-plugins.yaml", + }); + await rhdh.deploy(); + }); + + test.beforeEach(async ({ page, loginHelper }) => { + await page.goto("/"); + await loginHelper.loginAsKeycloakUser(); + }); + + test("should display plugin in sidebar", async ({ uiHelper }) => { + await uiHelper.openSidebar("My Plugin"); + await uiHelper.verifyHeading("My Plugin"); + }); + + test("should show plugin card on entity page", async ({ page, uiHelper }) => { + await uiHelper.openSidebar("Catalog"); + await page.click("text=example-component"); + await uiHelper.verifyTextinCard("My Plugin", "Plugin content"); + }); + + test("should handle user interactions", async ({ page, uiHelper }) => { + await uiHelper.openSidebar("My Plugin"); + await uiHelper.clickButton("Perform Action"); + await uiHelper.verifyText("Action completed"); + }); +}); +``` + +## Step 5: Add Helper Functions + +**tests/helpers/my-plugin-helpers.ts:** +```typescript +import { Page, expect } from "@playwright/test"; + +export async function verifyPluginData(page: Page, expectedData: string[]) { + for (const item of expectedData) { + await expect(page.getByText(item)).toBeVisible(); + } +} + +export async function performPluginAction(page: Page, action: string) { + await page.getByRole("button", { name: action }).click(); + await page.waitForLoadState("networkidle"); +} +``` + +## Step 6: Create Custom Page Object + +**tests/pages/my-plugin-page.ts:** +```typescript +import { Page, Locator, expect } from "@playwright/test"; + +export class MyPluginPage { + private readonly page: Page; + private readonly heading: Locator; + private readonly dataTable: Locator; + + constructor(page: Page) { + this.page = page; + this.heading = page.getByRole("heading", { name: "My Plugin" }); + this.dataTable = page.locator("table.my-plugin-data"); + } + + async go(): Promise { + await this.page.goto("/my-plugin"); + await this.heading.waitFor(); + } + + async verifyDataRow(text: string): Promise { + await expect(this.dataTable.getByText(text)).toBeVisible(); + } + + async clickRow(text: string): Promise { + await this.dataTable.getByText(text).click(); + } +} +``` + +## Step 7: Run Tests + +```bash +# Run all tests +yarn playwright test + +# Run specific file +yarn playwright test my-plugin.spec.ts + +# Run with UI +yarn playwright test --ui + +# Run headed +yarn playwright test --headed +``` + +## Best Practices + +1. **Use descriptive test names** - Explain what's being tested +2. **Keep tests independent** - Each test should work alone +3. **Use page objects** - For complex page interactions +4. **Handle async properly** - Always await assertions +5. **Clean up test data** - Remove created entities + +## Debugging Tips + +### Screenshots + +```typescript +test("debug test", async ({ page }) => { + await page.screenshot({ path: "debug.png" }); +}); +``` + +### Pause for Inspection + +```typescript +test("debug test", async ({ page }) => { + await page.pause(); // Opens Playwright inspector +}); +``` + +### Slow Down + +```typescript +import { defineConfig } from "rhdh-e2e-test-utils/playwright-config"; + +export default defineConfig({ + use: { + launchOptions: { + slowMo: 500, // 500ms between actions + }, + }, + projects: [{ name: "my-plugin" }], +}); +``` + +## Next Steps + +- [Multi-Project Setup](/tutorials/multi-project-setup) +- [CI/CD Integration](/tutorials/ci-cd-integration) diff --git a/docs/yarn.lock b/docs/yarn.lock new file mode 100644 index 0000000..0a0ea95 --- /dev/null +++ b/docs/yarn.lock @@ -0,0 +1,2345 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 6 + cacheKey: 8 + +"@algolia/abtesting@npm:1.12.3": + version: 1.12.3 + resolution: "@algolia/abtesting@npm:1.12.3" + dependencies: + "@algolia/client-common": 5.46.3 + "@algolia/requester-browser-xhr": 5.46.3 + "@algolia/requester-fetch": 5.46.3 + "@algolia/requester-node-http": 5.46.3 + checksum: e5370f1522651dda906770b07c22ff81126ffa78354a92b48e4e7feaeedc8d686979d351a1cdd9a526d8dd2e1c1c6b132ac51ca3adae8337bd3846aa5f074519 + languageName: node + linkType: hard + +"@algolia/autocomplete-core@npm:1.17.7": + version: 1.17.7 + resolution: "@algolia/autocomplete-core@npm:1.17.7" + dependencies: + "@algolia/autocomplete-plugin-algolia-insights": 1.17.7 + "@algolia/autocomplete-shared": 1.17.7 + checksum: 17236cfb4eccc4a706ce42ff09f9b9e819c38a650f96dc124b4168a626303a1d00ee407c46cdd9ff19fcaf344815cf222cc14c0b5364f6cb2f55f0a62677b9a4 + languageName: node + linkType: hard + +"@algolia/autocomplete-plugin-algolia-insights@npm:1.17.7": + version: 1.17.7 + resolution: "@algolia/autocomplete-plugin-algolia-insights@npm:1.17.7" + dependencies: + "@algolia/autocomplete-shared": 1.17.7 + peerDependencies: + search-insights: ">= 1 < 3" + checksum: 18e9ad58d421b7744e697e91253a6c95287e3c1194c0c8bdf1179a26b422cca069a7da102b2fcf9257bb85efd53db6131995cda3df8ab262424fed87a88c0a9d + languageName: node + linkType: hard + +"@algolia/autocomplete-preset-algolia@npm:1.17.7": + version: 1.17.7 + resolution: "@algolia/autocomplete-preset-algolia@npm:1.17.7" + dependencies: + "@algolia/autocomplete-shared": 1.17.7 + peerDependencies: + "@algolia/client-search": ">= 4.9.1 < 6" + algoliasearch: ">= 4.9.1 < 6" + checksum: d8e7e000fc027e15a0173a2cabb7821d902f71f03957f3bad7bb2bfd7ee58825e96e2efa57be559312e33d1bf07f658469fdc209286dbab05d8dada2d7a18531 + languageName: node + linkType: hard + +"@algolia/autocomplete-shared@npm:1.17.7": + version: 1.17.7 + resolution: "@algolia/autocomplete-shared@npm:1.17.7" + peerDependencies: + "@algolia/client-search": ">= 4.9.1 < 6" + algoliasearch: ">= 4.9.1 < 6" + checksum: 8aff3df580bdb3eeffce36225b04e4352f837aea37032fd6358d9c43b2eeab23cda4f217f7c0f0b8346b630c57c89ad415f8da308544f009b5e26256f9c3b59e + languageName: node + linkType: hard + +"@algolia/client-abtesting@npm:5.46.3": + version: 5.46.3 + resolution: "@algolia/client-abtesting@npm:5.46.3" + dependencies: + "@algolia/client-common": 5.46.3 + "@algolia/requester-browser-xhr": 5.46.3 + "@algolia/requester-fetch": 5.46.3 + "@algolia/requester-node-http": 5.46.3 + checksum: 05c55dbe5d4f7a184df2b5f94c8129749f9bf0f26a0e255828c172c506dfce9f6d178a4b1668d23451ba9f00b2c03f8a6fa2d444f5528df557787c62bd5842d7 + languageName: node + linkType: hard + +"@algolia/client-analytics@npm:5.46.3": + version: 5.46.3 + resolution: "@algolia/client-analytics@npm:5.46.3" + dependencies: + "@algolia/client-common": 5.46.3 + "@algolia/requester-browser-xhr": 5.46.3 + "@algolia/requester-fetch": 5.46.3 + "@algolia/requester-node-http": 5.46.3 + checksum: 5d344b8c9661706bafaf1afe22f349e0f982f210a8c429a3e02335ec9c8184aba39c611ea4df1b792806e837aa7511e1c6747a8ca9c780965b235f2a85fff176 + languageName: node + linkType: hard + +"@algolia/client-common@npm:5.46.3": + version: 5.46.3 + resolution: "@algolia/client-common@npm:5.46.3" + checksum: fdc62da392336940ced7aa1d7efbca1c588487625f3f33b2beff38e78495e0a628a99cb68f4c8f7f9f4f2e2da289c1f7135726a8ba403c127a45434c8c58ba29 + languageName: node + linkType: hard + +"@algolia/client-insights@npm:5.46.3": + version: 5.46.3 + resolution: "@algolia/client-insights@npm:5.46.3" + dependencies: + "@algolia/client-common": 5.46.3 + "@algolia/requester-browser-xhr": 5.46.3 + "@algolia/requester-fetch": 5.46.3 + "@algolia/requester-node-http": 5.46.3 + checksum: 9490ac26f505e631f3872969f535686c4d115fafe637fa26e876f5b4bed8e73f6b6f054dc9f872ea7bb80550e09b021d267cdc865433fc1fef4d39d303e1c610 + languageName: node + linkType: hard + +"@algolia/client-personalization@npm:5.46.3": + version: 5.46.3 + resolution: "@algolia/client-personalization@npm:5.46.3" + dependencies: + "@algolia/client-common": 5.46.3 + "@algolia/requester-browser-xhr": 5.46.3 + "@algolia/requester-fetch": 5.46.3 + "@algolia/requester-node-http": 5.46.3 + checksum: dbc4a42f47ea74279926ae603248db1589259d56c2a13d47b6ec3409ea5deabc051d89b7e45cd78619a7f52272a8bed004a9759cfe6894a13cd0797837fb18e8 + languageName: node + linkType: hard + +"@algolia/client-query-suggestions@npm:5.46.3": + version: 5.46.3 + resolution: "@algolia/client-query-suggestions@npm:5.46.3" + dependencies: + "@algolia/client-common": 5.46.3 + "@algolia/requester-browser-xhr": 5.46.3 + "@algolia/requester-fetch": 5.46.3 + "@algolia/requester-node-http": 5.46.3 + checksum: 254a714b5a54c796660516bd9d690cbc9e372e968ffd928ae7e50ab33751a56d82825e514749f411d3ee4cbaa65920f26769c43de63874a827af612c1c789f6a + languageName: node + linkType: hard + +"@algolia/client-search@npm:5.46.3": + version: 5.46.3 + resolution: "@algolia/client-search@npm:5.46.3" + dependencies: + "@algolia/client-common": 5.46.3 + "@algolia/requester-browser-xhr": 5.46.3 + "@algolia/requester-fetch": 5.46.3 + "@algolia/requester-node-http": 5.46.3 + checksum: 6b6ffb444e056c3eeab8621fdfa2a92e40e16ff0a2218f63064004f2e10fb6efffc4cf8346d181f17243eb8c088c0dc4e1d213089e0a9afba36e9e22b4dc929b + languageName: node + linkType: hard + +"@algolia/ingestion@npm:1.46.3": + version: 1.46.3 + resolution: "@algolia/ingestion@npm:1.46.3" + dependencies: + "@algolia/client-common": 5.46.3 + "@algolia/requester-browser-xhr": 5.46.3 + "@algolia/requester-fetch": 5.46.3 + "@algolia/requester-node-http": 5.46.3 + checksum: d79fe9914513f3a95ca3b7df31fe75605c6f64634f4d614cc17c42e2302c4dfd3eeece7ee7bb986e60665f6ef67c54f2f93da2d9145085aab7b02acf974a3df1 + languageName: node + linkType: hard + +"@algolia/monitoring@npm:1.46.3": + version: 1.46.3 + resolution: "@algolia/monitoring@npm:1.46.3" + dependencies: + "@algolia/client-common": 5.46.3 + "@algolia/requester-browser-xhr": 5.46.3 + "@algolia/requester-fetch": 5.46.3 + "@algolia/requester-node-http": 5.46.3 + checksum: a0dd4273e5d9cde3c274cbe246ea969bffaa0c614e85d6635550eaead826726a15e79dcfecb694a214c5edaff1582bdff95f8533362b4aea2044e8cd468efda8 + languageName: node + linkType: hard + +"@algolia/recommend@npm:5.46.3": + version: 5.46.3 + resolution: "@algolia/recommend@npm:5.46.3" + dependencies: + "@algolia/client-common": 5.46.3 + "@algolia/requester-browser-xhr": 5.46.3 + "@algolia/requester-fetch": 5.46.3 + "@algolia/requester-node-http": 5.46.3 + checksum: 104c523c1e9d1e0bd30c08c119168dcd2c0106e7098470f5c28ab1532d3e6b3b30979bb1f59eee8dda40f319e89150dcb2ebd7164216947a64f3540a7403709f + languageName: node + linkType: hard + +"@algolia/requester-browser-xhr@npm:5.46.3": + version: 5.46.3 + resolution: "@algolia/requester-browser-xhr@npm:5.46.3" + dependencies: + "@algolia/client-common": 5.46.3 + checksum: 5def15096c6096442559c53f6c5aea8cc939a0c3d607d3a03e5e6659852c69bca4f90500ab8c5e3bd630873a83b50dbe92bcf70e3f3156d5e62bd098d2e6d077 + languageName: node + linkType: hard + +"@algolia/requester-fetch@npm:5.46.3": + version: 5.46.3 + resolution: "@algolia/requester-fetch@npm:5.46.3" + dependencies: + "@algolia/client-common": 5.46.3 + checksum: 4fdb1d315f8a06f49c7052a0c7335d41441d77dd2e7af54ca2587dedb1a18bb06b1e7354efe743c5bdd4709015cbbe68389ff20de0cc433f9f1c3d53df59275c + languageName: node + linkType: hard + +"@algolia/requester-node-http@npm:5.46.3": + version: 5.46.3 + resolution: "@algolia/requester-node-http@npm:5.46.3" + dependencies: + "@algolia/client-common": 5.46.3 + checksum: 2f9476928db92b6743936c593edd4aa401ade191a8c8829f4ebf8510cd3a6569fd456d5171b839f1916127c31d9930461abd8eff8202be75e554ab5edfbdd089 + languageName: node + linkType: hard + +"@babel/helper-string-parser@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-string-parser@npm:7.27.1" + checksum: 0a8464adc4b39b138aedcb443b09f4005d86207d7126e5e079177e05c3116107d856ec08282b365e9a79a9872f40f4092a6127f8d74c8a01c1ef789dacfc25d6 + languageName: node + linkType: hard + +"@babel/helper-validator-identifier@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/helper-validator-identifier@npm:7.28.5" + checksum: 5a251a6848e9712aea0338f659a1a3bd334d26219d5511164544ca8ec20774f098c3a6661e9da65a0d085c745c00bb62c8fada38a62f08fa1f8053bc0aeb57e4 + languageName: node + linkType: hard + +"@babel/parser@npm:^7.28.5": + version: 7.28.6 + resolution: "@babel/parser@npm:7.28.6" + dependencies: + "@babel/types": ^7.28.6 + bin: + parser: ./bin/babel-parser.js + checksum: 2a35319792ceef9bc918f0ff854449bef0120707798fe147ef988b0606de226e2fbc3a562ba687148bfe5336c6c67358fb27e71a94e425b28482dcaf0b172fd6 + languageName: node + linkType: hard + +"@babel/types@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/types@npm:7.28.6" + dependencies: + "@babel/helper-string-parser": ^7.27.1 + "@babel/helper-validator-identifier": ^7.28.5 + checksum: f76556cda59be337cc10dc68b2a9a947c10de018998bab41076e7b7e4489b28dd53299f98f22eec0774264c989515e6fdc56de91c73e3aa396367bb953200a6a + languageName: node + linkType: hard + +"@docsearch/css@npm:3.8.2": + version: 3.8.2 + resolution: "@docsearch/css@npm:3.8.2" + checksum: 9d38cd17b3fc156f57d189ecccb9fbb4f208650dc6039aba6bd73d90174d9801b796421936673cba6bafe1c58302ea48ff5aeea7c803cf229fb1ee2708085a02 + languageName: node + linkType: hard + +"@docsearch/js@npm:3.8.2": + version: 3.8.2 + resolution: "@docsearch/js@npm:3.8.2" + dependencies: + "@docsearch/react": 3.8.2 + preact: ^10.0.0 + checksum: 144ba0dc9e578869ba26cd5198e38e9469e9a351267949bae5a55388197f9ebdff4ffc83e863729d28dbd6431a557c8d7be422b0cd17d37c213223db56231fb7 + languageName: node + linkType: hard + +"@docsearch/react@npm:3.8.2": + version: 3.8.2 + resolution: "@docsearch/react@npm:3.8.2" + dependencies: + "@algolia/autocomplete-core": 1.17.7 + "@algolia/autocomplete-preset-algolia": 1.17.7 + "@docsearch/css": 3.8.2 + algoliasearch: ^5.14.2 + peerDependencies: + "@types/react": ">= 16.8.0 < 19.0.0" + react: ">= 16.8.0 < 19.0.0" + react-dom: ">= 16.8.0 < 19.0.0" + search-insights: ">= 1 < 3" + peerDependenciesMeta: + "@types/react": + optional: true + react: + optional: true + react-dom: + optional: true + search-insights: + optional: true + checksum: b307eee7c657f0c21e8a2b1c28a1d451de83434232e128b13c54c8c219a6d5f2d302ce2ca2a824eb730d5ea25cfccb1dce0e4fc1291f36f31f810841f4a25891 + languageName: node + linkType: hard + +"@esbuild/aix-ppc64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/aix-ppc64@npm:0.21.5" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/android-arm64@npm:0.21.5" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/android-arm@npm:0.21.5" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/android-x64@npm:0.21.5" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/darwin-arm64@npm:0.21.5" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/darwin-x64@npm:0.21.5" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/freebsd-arm64@npm:0.21.5" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/freebsd-x64@npm:0.21.5" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-arm64@npm:0.21.5" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-arm@npm:0.21.5" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-ia32@npm:0.21.5" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-loong64@npm:0.21.5" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-mips64el@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-mips64el@npm:0.21.5" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-ppc64@npm:0.21.5" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-riscv64@npm:0.21.5" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-s390x@npm:0.21.5" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-x64@npm:0.21.5" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/netbsd-x64@npm:0.21.5" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/openbsd-x64@npm:0.21.5" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/sunos-x64@npm:0.21.5" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/win32-arm64@npm:0.21.5" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/win32-ia32@npm:0.21.5" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/win32-x64@npm:0.21.5" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@iconify-json/simple-icons@npm:^1.2.21": + version: 1.2.66 + resolution: "@iconify-json/simple-icons@npm:1.2.66" + dependencies: + "@iconify/types": "*" + checksum: 461ebf13d5dc69b7995f8499d758fa3b18d8fe0dded26149f84af75451681e304d32c602339ccf12a72598d1f1d72fc4b38d20ac96995368775b27ac49ab5116 + languageName: node + linkType: hard + +"@iconify/types@npm:*": + version: 2.0.0 + resolution: "@iconify/types@npm:2.0.0" + checksum: 029f58542c160e9d4a746869cf2e475b603424d3adf3994c5cc8d0406c47e6e04a3b898b2707840c1c5b9bd5563a1660a34b110d89fce43923baca5222f4e597 + languageName: node + linkType: hard + +"@isaacs/balanced-match@npm:^4.0.1": + version: 4.0.1 + resolution: "@isaacs/balanced-match@npm:4.0.1" + checksum: 102fbc6d2c0d5edf8f6dbf2b3feb21695a21bc850f11bc47c4f06aa83bd8884fde3fe9d6d797d619901d96865fdcb4569ac2a54c937992c48885c5e3d9967fe8 + languageName: node + linkType: hard + +"@isaacs/brace-expansion@npm:^5.0.0": + version: 5.0.0 + resolution: "@isaacs/brace-expansion@npm:5.0.0" + dependencies: + "@isaacs/balanced-match": ^4.0.1 + checksum: d7a3b8b0ddbf0ccd8eeb1300e29dd0a0c02147e823d8138f248375a365682360620895c66d113e05ee02389318c654379b0e538b996345b83c914941786705b1 + languageName: node + linkType: hard + +"@isaacs/fs-minipass@npm:^4.0.0": + version: 4.0.1 + resolution: "@isaacs/fs-minipass@npm:4.0.1" + dependencies: + minipass: ^7.0.4 + checksum: 5d36d289960e886484362d9eb6a51d1ea28baed5f5d0140bbe62b99bac52eaf06cc01c2bc0d3575977962f84f6b2c4387b043ee632216643d4787b0999465bf2 + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:^1.5.5": + version: 1.5.5 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" + checksum: c2e36e67971f719a8a3a85ef5a5f580622437cc723c35d03ebd0c9c0b06418700ef006f58af742791f71f6a4fc68fcfaf1f6a74ec2f9a3332860e9373459dae7 + languageName: node + linkType: hard + +"@npmcli/agent@npm:^4.0.0": + version: 4.0.0 + resolution: "@npmcli/agent@npm:4.0.0" + dependencies: + agent-base: ^7.1.0 + http-proxy-agent: ^7.0.0 + https-proxy-agent: ^7.0.1 + lru-cache: ^11.2.1 + socks-proxy-agent: ^8.0.3 + checksum: 89ae20b44859ff8d4de56ade319d8ceaa267a0742d6f7345fe98aa5cd8614ced7db85ea4dc5bfbd6614dbb200a10b134e087143582534c939e8a02219e8665c8 + languageName: node + linkType: hard + +"@npmcli/fs@npm:^5.0.0": + version: 5.0.0 + resolution: "@npmcli/fs@npm:5.0.0" + dependencies: + semver: ^7.3.5 + checksum: 897dac32eb37e011800112d406b9ea2ebd96f1dab01bb8fbeb59191b86f6825dffed6a89f3b6c824753d10f8735b76d630927bd7610e9e123b129ef2e5f02cb5 + languageName: node + linkType: hard + +"@rollup/rollup-android-arm-eabi@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.55.1" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@rollup/rollup-android-arm64@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-android-arm64@npm:4.55.1" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-arm64@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-darwin-arm64@npm:4.55.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-x64@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-darwin-x64@npm:4.55.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-freebsd-arm64@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-freebsd-arm64@npm:4.55.1" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-freebsd-x64@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-freebsd-x64@npm:4.55.1" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-gnueabihf@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.55.1" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-musleabihf@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.55.1" + conditions: os=linux & cpu=arm & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-gnu@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.55.1" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-musl@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.55.1" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-loong64-gnu@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.55.1" + conditions: os=linux & cpu=loong64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-loong64-musl@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-linux-loong64-musl@npm:4.55.1" + conditions: os=linux & cpu=loong64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-ppc64-gnu@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.55.1" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-ppc64-musl@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-linux-ppc64-musl@npm:4.55.1" + conditions: os=linux & cpu=ppc64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-riscv64-gnu@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.55.1" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-riscv64-musl@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.55.1" + conditions: os=linux & cpu=riscv64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-s390x-gnu@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.55.1" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-gnu@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.55.1" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-musl@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.55.1" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-openbsd-x64@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-openbsd-x64@npm:4.55.1" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-openharmony-arm64@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-openharmony-arm64@npm:4.55.1" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-arm64-msvc@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.55.1" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-ia32-msvc@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.55.1" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@rollup/rollup-win32-x64-gnu@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-win32-x64-gnu@npm:4.55.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-x64-msvc@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.55.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@shikijs/core@npm:2.5.0, @shikijs/core@npm:^2.1.0": + version: 2.5.0 + resolution: "@shikijs/core@npm:2.5.0" + dependencies: + "@shikijs/engine-javascript": 2.5.0 + "@shikijs/engine-oniguruma": 2.5.0 + "@shikijs/types": 2.5.0 + "@shikijs/vscode-textmate": ^10.0.2 + "@types/hast": ^3.0.4 + hast-util-to-html: ^9.0.4 + checksum: 033f324e4bfc95afaced957140e53affc35ede89e9a06a844cc15f0ab408731d2b7146e2c5aa01374215550346ac74cdb338aefb8e26765d6596625af6aaca29 + languageName: node + linkType: hard + +"@shikijs/engine-javascript@npm:2.5.0": + version: 2.5.0 + resolution: "@shikijs/engine-javascript@npm:2.5.0" + dependencies: + "@shikijs/types": 2.5.0 + "@shikijs/vscode-textmate": ^10.0.2 + oniguruma-to-es: ^3.1.0 + checksum: f9fb6efcc3238654bcb19ae10f549918901db9c917e2eefd71b9e4a009b725168a9bece2b0814422f7a8fa97e20d585c8a68283d144bdb6a258e57168c0c7b0e + languageName: node + linkType: hard + +"@shikijs/engine-oniguruma@npm:2.5.0": + version: 2.5.0 + resolution: "@shikijs/engine-oniguruma@npm:2.5.0" + dependencies: + "@shikijs/types": 2.5.0 + "@shikijs/vscode-textmate": ^10.0.2 + checksum: d493fc5d2f968d9c69c24ae51c1d5e7ec9e62bbbf0030e59a56277edb19253e412a860fedbf591de19fcbf99a17f642e7a537a796e8c9fb5dd6c447155ed47ec + languageName: node + linkType: hard + +"@shikijs/langs@npm:2.5.0": + version: 2.5.0 + resolution: "@shikijs/langs@npm:2.5.0" + dependencies: + "@shikijs/types": 2.5.0 + checksum: 9082fc23ec6d6b3b605a30b544d7af986586efbd19cf3048cd078f367eff6e708cda318393200d455a42bbca83273a7dfd00f28cedae44e09c4ba66583ea02c9 + languageName: node + linkType: hard + +"@shikijs/themes@npm:2.5.0": + version: 2.5.0 + resolution: "@shikijs/themes@npm:2.5.0" + dependencies: + "@shikijs/types": 2.5.0 + checksum: 0844066e284d8d7263dfd37ff8db8813d6784ea10eab46d0b3639a31a16aa3b68031fd2b4b3f1706f8b9a71ada8b30d8a8bdc5832b7c8a1b3bc1b2e28c199f96 + languageName: node + linkType: hard + +"@shikijs/transformers@npm:^2.1.0": + version: 2.5.0 + resolution: "@shikijs/transformers@npm:2.5.0" + dependencies: + "@shikijs/core": 2.5.0 + "@shikijs/types": 2.5.0 + checksum: bbe5706b4ad7c32fa9d3eb61f656a85a0423934b7174415760bd4081804f41d7a244f46c4b85488de4b2e7009b6c910039a3f60c6daa92999f72fd16ec267780 + languageName: node + linkType: hard + +"@shikijs/types@npm:2.5.0, @shikijs/types@npm:^2.1.0": + version: 2.5.0 + resolution: "@shikijs/types@npm:2.5.0" + dependencies: + "@shikijs/vscode-textmate": ^10.0.2 + "@types/hast": ^3.0.4 + checksum: c7c2be18cb6305890d9d2f8cdd449f9faeb47338d4d4056d7915e8db961b14491cdc3cba70f443be31be04ac129e094dbd16cb93d5068f2341ad95b3a4e88258 + languageName: node + linkType: hard + +"@shikijs/vscode-textmate@npm:^10.0.2": + version: 10.0.2 + resolution: "@shikijs/vscode-textmate@npm:10.0.2" + checksum: e68f27a3dc1584d7414b8acafb9c177a2181eb0b06ef178d8609142f49d28d85fd10ab129affde40a45a7d9238997e457ce47931b3a3815980e2b98b2d26724c + languageName: node + linkType: hard + +"@types/estree@npm:1.0.8": + version: 1.0.8 + resolution: "@types/estree@npm:1.0.8" + checksum: bd93e2e415b6f182ec4da1074e1f36c480f1d26add3e696d54fb30c09bc470897e41361c8fd957bf0985024f8fbf1e6e2aff977d79352ef7eb93a5c6dcff6c11 + languageName: node + linkType: hard + +"@types/hast@npm:^3.0.0, @types/hast@npm:^3.0.4": + version: 3.0.4 + resolution: "@types/hast@npm:3.0.4" + dependencies: + "@types/unist": "*" + checksum: 7a973e8d16fcdf3936090fa2280f408fb2b6a4f13b42edeb5fbd614efe042b82eac68e298e556d50f6b4ad585a3a93c353e9c826feccdc77af59de8dd400d044 + languageName: node + linkType: hard + +"@types/linkify-it@npm:^5": + version: 5.0.0 + resolution: "@types/linkify-it@npm:5.0.0" + checksum: ec98e03aa883f70153a17a1e6ed9e28b39a604049b485daeddae3a1482ec65cac0817520be6e301d99fd1a934b3950cf0f855655aae6ec27da2bb676ba4a148e + languageName: node + linkType: hard + +"@types/markdown-it@npm:^14.1.2": + version: 14.1.2 + resolution: "@types/markdown-it@npm:14.1.2" + dependencies: + "@types/linkify-it": ^5 + "@types/mdurl": ^2 + checksum: ad66e0b377d6af09a155bb65f675d1e2cb27d20a3d407377fe4508eb29cde1e765430b99d5129f89012e2524abb5525d629f7057a59ff9fd0967e1ff645b9ec6 + languageName: node + linkType: hard + +"@types/mdast@npm:^4.0.0": + version: 4.0.4 + resolution: "@types/mdast@npm:4.0.4" + dependencies: + "@types/unist": "*" + checksum: 20c4e9574cc409db662a35cba52b068b91eb696b3049e94321219d47d34c8ccc99a142be5c76c80a538b612457b03586bc2f6b727a3e9e7530f4c8568f6282ee + languageName: node + linkType: hard + +"@types/mdurl@npm:^2": + version: 2.0.0 + resolution: "@types/mdurl@npm:2.0.0" + checksum: 78746e96c655ceed63db06382da466fd52c7e9dc54d60b12973dfdd110cae06b9439c4b90e17bb8d4461109184b3ea9f3e9f96b3e4bf4aa9fe18b6ac35f283c8 + languageName: node + linkType: hard + +"@types/unist@npm:*, @types/unist@npm:^3.0.0": + version: 3.0.3 + resolution: "@types/unist@npm:3.0.3" + checksum: 96e6453da9e075aaef1dc22482463898198acdc1eeb99b465e65e34303e2ec1e3b1ed4469a9118275ec284dc98019f63c3f5d49422f0e4ac707e5ab90fb3b71a + languageName: node + linkType: hard + +"@types/web-bluetooth@npm:^0.0.21": + version: 0.0.21 + resolution: "@types/web-bluetooth@npm:0.0.21" + checksum: 85a957d52263607d26236b1748771fdcfc0791f2c20373370e6c725410067dee6a5ec425b0e25ec85db2908dd59415079070655fd3277841cbbd3a557d1f7777 + languageName: node + linkType: hard + +"@ungap/structured-clone@npm:^1.0.0": + version: 1.3.0 + resolution: "@ungap/structured-clone@npm:1.3.0" + checksum: 64ed518f49c2b31f5b50f8570a1e37bde3b62f2460042c50f132430b2d869c4a6586f13aa33a58a4722715b8158c68cae2827389d6752ac54da2893c83e480fc + languageName: node + linkType: hard + +"@vitejs/plugin-vue@npm:^5.2.1": + version: 5.2.4 + resolution: "@vitejs/plugin-vue@npm:5.2.4" + peerDependencies: + vite: ^5.0.0 || ^6.0.0 + vue: ^3.2.25 + checksum: 116a859945401195b0d89b3e2f872bde0d168b498f1438c02a212b53e1131cd4ef5ace65b563092f6b326d7ef726639a9b33d61459b82a634f73f44a3ee0d924 + languageName: node + linkType: hard + +"@vue/compiler-core@npm:3.5.26": + version: 3.5.26 + resolution: "@vue/compiler-core@npm:3.5.26" + dependencies: + "@babel/parser": ^7.28.5 + "@vue/shared": 3.5.26 + entities: ^7.0.0 + estree-walker: ^2.0.2 + source-map-js: ^1.2.1 + checksum: 0b67761b9d04832d04c8702316b6cd7e78fa596c8cd79ff36a2fb02694ea046f515214fc13dc6624a720803ef7e6655c2f30b94d007fd2d6d3f8c23d85fdccb7 + languageName: node + linkType: hard + +"@vue/compiler-dom@npm:3.5.26": + version: 3.5.26 + resolution: "@vue/compiler-dom@npm:3.5.26" + dependencies: + "@vue/compiler-core": 3.5.26 + "@vue/shared": 3.5.26 + checksum: ae5429f036e46cfd03e43b635870ae1cc9bac431cabb27a0e84ca4268741dfcfc6a5b8fa5bb512fdaa10665c395564aaa30883bd5b013058cfff99f73382149f + languageName: node + linkType: hard + +"@vue/compiler-sfc@npm:3.5.26": + version: 3.5.26 + resolution: "@vue/compiler-sfc@npm:3.5.26" + dependencies: + "@babel/parser": ^7.28.5 + "@vue/compiler-core": 3.5.26 + "@vue/compiler-dom": 3.5.26 + "@vue/compiler-ssr": 3.5.26 + "@vue/shared": 3.5.26 + estree-walker: ^2.0.2 + magic-string: ^0.30.21 + postcss: ^8.5.6 + source-map-js: ^1.2.1 + checksum: 26b9052a201ed128b309ba5a4b47c7363c511be7237dc31b5d25b9a03e6da30edcdbd939b1fb9d70f057f2b83c46bdd45be08344cfdc217551b775c5ccec39ac + languageName: node + linkType: hard + +"@vue/compiler-ssr@npm:3.5.26": + version: 3.5.26 + resolution: "@vue/compiler-ssr@npm:3.5.26" + dependencies: + "@vue/compiler-dom": 3.5.26 + "@vue/shared": 3.5.26 + checksum: aaa29604dcfa1c542b33e4e5abb76c28dbda29fdf1ce0493add7cce4fe51c69e6775bca2798904d4ba4e978f68f3b3713ef3af6aee6ba5eb51b5dbc36b4a59e6 + languageName: node + linkType: hard + +"@vue/devtools-api@npm:^7.7.0": + version: 7.7.9 + resolution: "@vue/devtools-api@npm:7.7.9" + dependencies: + "@vue/devtools-kit": ^7.7.9 + checksum: a4dbc911c4a99d401591972c766b56b323f6f01dddfaf1bddc00c5c78aeda7dd7afc6330b5b8245a13ef72f8eceededf0a40416311e229c19014d151ebacd416 + languageName: node + linkType: hard + +"@vue/devtools-kit@npm:^7.7.9": + version: 7.7.9 + resolution: "@vue/devtools-kit@npm:7.7.9" + dependencies: + "@vue/devtools-shared": ^7.7.9 + birpc: ^2.3.0 + hookable: ^5.5.3 + mitt: ^3.0.1 + perfect-debounce: ^1.0.0 + speakingurl: ^14.0.1 + superjson: ^2.2.2 + checksum: 7241630643abaf2fd22f1db2ff47ec594418b6a4c55822c8dc4809754bf22f8f9c095872f8774c2f0a07c77f4663ef5f0bdc88578b42be282c0b4ed184b5fda1 + languageName: node + linkType: hard + +"@vue/devtools-shared@npm:^7.7.9": + version: 7.7.9 + resolution: "@vue/devtools-shared@npm:7.7.9" + dependencies: + rfdc: ^1.4.1 + checksum: 02ea89651174d3763f2b0ea63f1ef7bb8ef8e22479dfc2b6b19cdbe2aab76911b7c2891de149ba985d218f58a06f2de4f4fb4e01d2fa8296e2d7b80d8539eceb + languageName: node + linkType: hard + +"@vue/reactivity@npm:3.5.26": + version: 3.5.26 + resolution: "@vue/reactivity@npm:3.5.26" + dependencies: + "@vue/shared": 3.5.26 + checksum: 9bbad24381d4ca0ad00d5e00354020f8b0464ef4bdbb3aca53fa194b0d48889294d841e03ed2232a342fdca25b3551312626c2b53258300b6d821f7363648245 + languageName: node + linkType: hard + +"@vue/runtime-core@npm:3.5.26": + version: 3.5.26 + resolution: "@vue/runtime-core@npm:3.5.26" + dependencies: + "@vue/reactivity": 3.5.26 + "@vue/shared": 3.5.26 + checksum: 10200dc5fbfd873e3de66792498706f72c61ca821440a21bd3f697eaa7c551166ccb11afab3fab1a118201af9357489287abbae3eac01a5692f6aea889cb9187 + languageName: node + linkType: hard + +"@vue/runtime-dom@npm:3.5.26": + version: 3.5.26 + resolution: "@vue/runtime-dom@npm:3.5.26" + dependencies: + "@vue/reactivity": 3.5.26 + "@vue/runtime-core": 3.5.26 + "@vue/shared": 3.5.26 + csstype: ^3.2.3 + checksum: 9cdcaef49c49ba327219baeada153664b7b82763d08cda102ecf9dbbb4a8dd48792b6b70167a6032f5e838d11bc77df832d4c6df1053e7d3a250387f1b62d3ad + languageName: node + linkType: hard + +"@vue/server-renderer@npm:3.5.26": + version: 3.5.26 + resolution: "@vue/server-renderer@npm:3.5.26" + dependencies: + "@vue/compiler-ssr": 3.5.26 + "@vue/shared": 3.5.26 + peerDependencies: + vue: 3.5.26 + checksum: 171eb7f8f4fc8cb61795d42157f13898b214564bb0c1f6398a863568468b1a7b95e837120c45512259ef8e1b24c9e45787c413b7f45bd18259f2d1c3bbf2b011 + languageName: node + linkType: hard + +"@vue/shared@npm:3.5.26, @vue/shared@npm:^3.5.13": + version: 3.5.26 + resolution: "@vue/shared@npm:3.5.26" + checksum: 0daf1717cd853dfaa0663bf856277c918360acc7921f17966da902f1b05ab8c7385fb5cc06a18f3b861cb84393d235c937836e044679dab989aba719aa930f55 + languageName: node + linkType: hard + +"@vueuse/core@npm:12.8.2, @vueuse/core@npm:^12.4.0": + version: 12.8.2 + resolution: "@vueuse/core@npm:12.8.2" + dependencies: + "@types/web-bluetooth": ^0.0.21 + "@vueuse/metadata": 12.8.2 + "@vueuse/shared": 12.8.2 + vue: ^3.5.13 + checksum: e8749b85c0c1a4e000f65b77e13037e0177cc0c43aa86fab0d90c1c7d9a1a9fb6b1d90461b9b599c51fce5acf201370f0cdf8dc3a5eda5acb2e982227cc2dc23 + languageName: node + linkType: hard + +"@vueuse/integrations@npm:^12.4.0": + version: 12.8.2 + resolution: "@vueuse/integrations@npm:12.8.2" + dependencies: + "@vueuse/core": 12.8.2 + "@vueuse/shared": 12.8.2 + vue: ^3.5.13 + peerDependencies: + async-validator: ^4 + axios: ^1 + change-case: ^5 + drauu: ^0.4 + focus-trap: ^7 + fuse.js: ^7 + idb-keyval: ^6 + jwt-decode: ^4 + nprogress: ^0.2 + qrcode: ^1.5 + sortablejs: ^1 + universal-cookie: ^7 + peerDependenciesMeta: + async-validator: + optional: true + axios: + optional: true + change-case: + optional: true + drauu: + optional: true + focus-trap: + optional: true + fuse.js: + optional: true + idb-keyval: + optional: true + jwt-decode: + optional: true + nprogress: + optional: true + qrcode: + optional: true + sortablejs: + optional: true + universal-cookie: + optional: true + checksum: 6c7a82ba00421d220e9e12a55bbb223d291648768629c9f88d48bcc0b4354b9d62e2f7a4efb83e6fd949c07071d9f16c2f1debbb966a4297a5d638199fc1da62 + languageName: node + linkType: hard + +"@vueuse/metadata@npm:12.8.2": + version: 12.8.2 + resolution: "@vueuse/metadata@npm:12.8.2" + checksum: 4fae302c2eb24970fd1946a0f25c81a99a3bbd1d12a43670ef7c2cbfe921a68ef98b1511a055cae8a37f2cf1ce625b88f726572b55dd15689e5201ad7714c667 + languageName: node + linkType: hard + +"@vueuse/shared@npm:12.8.2": + version: 12.8.2 + resolution: "@vueuse/shared@npm:12.8.2" + dependencies: + vue: ^3.5.13 + checksum: 41400fe8a375782e9a64f262ee7a900fd28de40767f63dad1624eff4a109eea14e88766f6fae5fe72c9f6ef84ea7600447d94ad37bec97ee34cc62560ad90dd7 + languageName: node + linkType: hard + +"abbrev@npm:^4.0.0": + version: 4.0.0 + resolution: "abbrev@npm:4.0.0" + checksum: d0344b63d28e763f259b4898c41bdc92c08e9d06d0da5617d0bbe4d78244e46daea88c510a2f9472af59b031d9060ec1a999653144e793fd029a59dae2f56dc8 + languageName: node + linkType: hard + +"agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": + version: 7.1.4 + resolution: "agent-base@npm:7.1.4" + checksum: 86a7f542af277cfbd77dd61e7df8422f90bac512953709003a1c530171a9d019d072e2400eab2b59f84b49ab9dd237be44315ca663ac73e82b3922d10ea5eafa + languageName: node + linkType: hard + +"algoliasearch@npm:^5.14.2": + version: 5.46.3 + resolution: "algoliasearch@npm:5.46.3" + dependencies: + "@algolia/abtesting": 1.12.3 + "@algolia/client-abtesting": 5.46.3 + "@algolia/client-analytics": 5.46.3 + "@algolia/client-common": 5.46.3 + "@algolia/client-insights": 5.46.3 + "@algolia/client-personalization": 5.46.3 + "@algolia/client-query-suggestions": 5.46.3 + "@algolia/client-search": 5.46.3 + "@algolia/ingestion": 1.46.3 + "@algolia/monitoring": 1.46.3 + "@algolia/recommend": 5.46.3 + "@algolia/requester-browser-xhr": 5.46.3 + "@algolia/requester-fetch": 5.46.3 + "@algolia/requester-node-http": 5.46.3 + checksum: 0c405be3213d81b0b50d6fe3d827462ab6e7a2d744ae6d6b843b4b538a7cb62a30a77ae5d1e5b22b9ff66650c5fa6f9295a7907e2fd3f0ed00125e55e8718e87 + languageName: node + linkType: hard + +"birpc@npm:^2.3.0": + version: 2.9.0 + resolution: "birpc@npm:2.9.0" + checksum: 499cba9d33ae6749c199b4dc1fc30faeb72aabd41d5c75d73d1d415691f65f0b613d787f21aa2369607a555a5e09c55e437e02b14bc6bad67bb657f8ae58f3dd + languageName: node + linkType: hard + +"cacache@npm:^20.0.1": + version: 20.0.3 + resolution: "cacache@npm:20.0.3" + dependencies: + "@npmcli/fs": ^5.0.0 + fs-minipass: ^3.0.0 + glob: ^13.0.0 + lru-cache: ^11.1.0 + minipass: ^7.0.3 + minipass-collect: ^2.0.1 + minipass-flush: ^1.0.5 + minipass-pipeline: ^1.2.4 + p-map: ^7.0.2 + ssri: ^13.0.0 + unique-filename: ^5.0.0 + checksum: 595e6b91d72972d596e1e9ccab8ddbf08b773f27240220b1b5b1b7b3f52173cfbcf095212e5d7acd86c3bd453c28e69b116469889c511615ef3589523d542639 + languageName: node + linkType: hard + +"ccount@npm:^2.0.0": + version: 2.0.1 + resolution: "ccount@npm:2.0.1" + checksum: 48193dada54c9e260e0acf57fc16171a225305548f9ad20d5471e0f7a8c026aedd8747091dccb0d900cde7df4e4ddbd235df0d8de4a64c71b12f0d3303eeafd4 + languageName: node + linkType: hard + +"character-entities-html4@npm:^2.0.0": + version: 2.1.0 + resolution: "character-entities-html4@npm:2.1.0" + checksum: 7034aa7c7fa90309667f6dd50499c8a760c3d3a6fb159adb4e0bada0107d194551cdbad0714302f62d06ce4ed68565c8c2e15fdef2e8f8764eb63fa92b34b11d + languageName: node + linkType: hard + +"character-entities-legacy@npm:^3.0.0": + version: 3.0.0 + resolution: "character-entities-legacy@npm:3.0.0" + checksum: 7582af055cb488b626d364b7d7a4e46b06abd526fb63c0e4eb35bcb9c9799cc4f76b39f34fdccef2d1174ac95e53e9ab355aae83227c1a2505877893fce77731 + languageName: node + linkType: hard + +"chownr@npm:^3.0.0": + version: 3.0.0 + resolution: "chownr@npm:3.0.0" + checksum: fd73a4bab48b79e66903fe1cafbdc208956f41ea4f856df883d0c7277b7ab29fd33ee65f93b2ec9192fc0169238f2f8307b7735d27c155821d886b84aa97aa8d + languageName: node + linkType: hard + +"comma-separated-tokens@npm:^2.0.0": + version: 2.0.3 + resolution: "comma-separated-tokens@npm:2.0.3" + checksum: e3bf9e0332a5c45f49b90e79bcdb4a7a85f28d6a6f0876a94f1bb9b2bfbdbbb9292aac50e1e742d8c0db1e62a0229a106f57917e2d067fca951d81737651700d + languageName: node + linkType: hard + +"copy-anything@npm:^4": + version: 4.0.5 + resolution: "copy-anything@npm:4.0.5" + dependencies: + is-what: ^5.2.0 + checksum: e2ccac1a26a119c4a8fd68503c1ba1b2065d75793ef63eb492092d71afaf5ac492802a57572e3d88d9e219a92f8b68acb121b344e3560e9f0f3a99ef973133fe + languageName: node + linkType: hard + +"csstype@npm:^3.2.3": + version: 3.2.3 + resolution: "csstype@npm:3.2.3" + checksum: cb882521b3398958a1ce6ca98c011aec0bde1c77ecaf8a1dd4db3b112a189939beae3b1308243b2fe50fc27eb3edeb0f73a5a4d91d928765dc6d5ecc7bda92ee + languageName: node + linkType: hard + +"debug@npm:4, debug@npm:^4.3.4": + version: 4.4.3 + resolution: "debug@npm:4.4.3" + dependencies: + ms: ^2.1.3 + peerDependenciesMeta: + supports-color: + optional: true + checksum: 4805abd570e601acdca85b6aa3757186084a45cff9b2fa6eee1f3b173caa776b45f478b2a71a572d616d2010cea9211d0ac4a02a610e4c18ac4324bde3760834 + languageName: node + linkType: hard + +"dequal@npm:^2.0.0": + version: 2.0.3 + resolution: "dequal@npm:2.0.3" + checksum: 8679b850e1a3d0ebbc46ee780d5df7b478c23f335887464023a631d1b9af051ad4a6595a44220f9ff8ff95a8ddccf019b5ad778a976fd7bbf77383d36f412f90 + languageName: node + linkType: hard + +"devlop@npm:^1.0.0": + version: 1.1.0 + resolution: "devlop@npm:1.1.0" + dependencies: + dequal: ^2.0.0 + checksum: d2ff650bac0bb6ef08c48f3ba98640bb5fec5cce81e9957eb620408d1bab1204d382a45b785c6b3314dc867bb0684936b84c6867820da6db97cbb5d3c15dd185 + languageName: node + linkType: hard + +"emoji-regex-xs@npm:^1.0.0": + version: 1.0.0 + resolution: "emoji-regex-xs@npm:1.0.0" + checksum: c33be159da769836f83281f2802d90169093ebf3c2c1643d6801d891c53beac5ef785fd8279f9b02fa6dc6c47c367818e076949f1e13bd1b3f921b416de4cbea + languageName: node + linkType: hard + +"encoding@npm:^0.1.13": + version: 0.1.13 + resolution: "encoding@npm:0.1.13" + dependencies: + iconv-lite: ^0.6.2 + checksum: bb98632f8ffa823996e508ce6a58ffcf5856330fde839ae42c9e1f436cc3b5cc651d4aeae72222916545428e54fd0f6aa8862fd8d25bdbcc4589f1e3f3715e7f + languageName: node + linkType: hard + +"entities@npm:^7.0.0": + version: 7.0.0 + resolution: "entities@npm:7.0.0" + checksum: 9305101f6d34fa6d255afb9ccd35f27cd67e0eaf1299374d20cba6e7c4bb26adae3ab879d33b524db74a2bbf57b9416fe04698a60bf2d9d1c1c811e16e205270 + languageName: node + linkType: hard + +"env-paths@npm:^2.2.0": + version: 2.2.1 + resolution: "env-paths@npm:2.2.1" + checksum: 65b5df55a8bab92229ab2b40dad3b387fad24613263d103a97f91c9fe43ceb21965cd3392b1ccb5d77088021e525c4e0481adb309625d0cb94ade1d1fb8dc17e + languageName: node + linkType: hard + +"err-code@npm:^2.0.2": + version: 2.0.3 + resolution: "err-code@npm:2.0.3" + checksum: 8b7b1be20d2de12d2255c0bc2ca638b7af5171142693299416e6a9339bd7d88fc8d7707d913d78e0993176005405a236b066b45666b27b797252c771156ace54 + languageName: node + linkType: hard + +"esbuild@npm:^0.21.3": + version: 0.21.5 + resolution: "esbuild@npm:0.21.5" + dependencies: + "@esbuild/aix-ppc64": 0.21.5 + "@esbuild/android-arm": 0.21.5 + "@esbuild/android-arm64": 0.21.5 + "@esbuild/android-x64": 0.21.5 + "@esbuild/darwin-arm64": 0.21.5 + "@esbuild/darwin-x64": 0.21.5 + "@esbuild/freebsd-arm64": 0.21.5 + "@esbuild/freebsd-x64": 0.21.5 + "@esbuild/linux-arm": 0.21.5 + "@esbuild/linux-arm64": 0.21.5 + "@esbuild/linux-ia32": 0.21.5 + "@esbuild/linux-loong64": 0.21.5 + "@esbuild/linux-mips64el": 0.21.5 + "@esbuild/linux-ppc64": 0.21.5 + "@esbuild/linux-riscv64": 0.21.5 + "@esbuild/linux-s390x": 0.21.5 + "@esbuild/linux-x64": 0.21.5 + "@esbuild/netbsd-x64": 0.21.5 + "@esbuild/openbsd-x64": 0.21.5 + "@esbuild/sunos-x64": 0.21.5 + "@esbuild/win32-arm64": 0.21.5 + "@esbuild/win32-ia32": 0.21.5 + "@esbuild/win32-x64": 0.21.5 + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 2911c7b50b23a9df59a7d6d4cdd3a4f85855787f374dce751148dbb13305e0ce7e880dde1608c2ab7a927fc6cec3587b80995f7fc87a64b455f8b70b55fd8ec1 + languageName: node + linkType: hard + +"estree-walker@npm:^2.0.2": + version: 2.0.2 + resolution: "estree-walker@npm:2.0.2" + checksum: 6151e6f9828abe2259e57f5fd3761335bb0d2ebd76dc1a01048ccee22fabcfef3c0859300f6d83ff0d1927849368775ec5a6d265dde2f6de5a1be1721cd94efc + languageName: node + linkType: hard + +"exponential-backoff@npm:^3.1.1": + version: 3.1.3 + resolution: "exponential-backoff@npm:3.1.3" + checksum: 471fdb70fd3d2c08a74a026973bdd4105b7832911f610ca67bbb74e39279411c1eed2f2a110c9d41c2edd89459ba58fdaba1c174beed73e7a42d773882dcff82 + languageName: node + linkType: hard + +"fdir@npm:^6.5.0": + version: 6.5.0 + resolution: "fdir@npm:6.5.0" + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + checksum: bd537daa9d3cd53887eed35efa0eab2dbb1ca408790e10e024120e7a36c6e9ae2b33710cb8381e35def01bc9c1d7eaba746f886338413e68ff6ebaee07b9a6e8 + languageName: node + linkType: hard + +"focus-trap@npm:^7.6.4": + version: 7.8.0 + resolution: "focus-trap@npm:7.8.0" + dependencies: + tabbable: ^6.4.0 + checksum: 1eccd8332b694ba8c4d0a97101471004a23634a9b3650311460064856b8795136a6aec276c02f1ec01dd23e27848a0db69bec087e347403e23ee55b52d6bbc7e + languageName: node + linkType: hard + +"fs-minipass@npm:^3.0.0": + version: 3.0.3 + resolution: "fs-minipass@npm:3.0.3" + dependencies: + minipass: ^7.0.3 + checksum: 8722a41109130851d979222d3ec88aabaceeaaf8f57b2a8f744ef8bd2d1ce95453b04a61daa0078822bc5cd21e008814f06fe6586f56fef511e71b8d2394d802 + languageName: node + linkType: hard + +"fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": + version: 2.3.3 + resolution: "fsevents@npm:2.3.3" + dependencies: + node-gyp: latest + checksum: 11e6ea6fea15e42461fc55b4b0e4a0a3c654faa567f1877dbd353f39156f69def97a69936d1746619d656c4b93de2238bf731f6085a03a50cabf287c9d024317 + conditions: os=darwin + languageName: node + linkType: hard + +"fsevents@patch:fsevents@~2.3.2#~builtin, fsevents@patch:fsevents@~2.3.3#~builtin": + version: 2.3.3 + resolution: "fsevents@patch:fsevents@npm%3A2.3.3#~builtin::version=2.3.3&hash=df0bf1" + dependencies: + node-gyp: latest + conditions: os=darwin + languageName: node + linkType: hard + +"glob@npm:^13.0.0": + version: 13.0.0 + resolution: "glob@npm:13.0.0" + dependencies: + minimatch: ^10.1.1 + minipass: ^7.1.2 + path-scurry: ^2.0.0 + checksum: 963730222b0acc85a0d2616c08ba3a5d5b5f33fbf69182791967b8a02245db505577a6fc19836d5d58e1cbbfb414ad4f62f605a0372ab05cd9e6998efe944369 + languageName: node + linkType: hard + +"graceful-fs@npm:^4.2.6": + version: 4.2.11 + resolution: "graceful-fs@npm:4.2.11" + checksum: ac85f94da92d8eb6b7f5a8b20ce65e43d66761c55ce85ac96df6865308390da45a8d3f0296dd3a663de65d30ba497bd46c696cc1e248c72b13d6d567138a4fc7 + languageName: node + linkType: hard + +"hast-util-to-html@npm:^9.0.4": + version: 9.0.5 + resolution: "hast-util-to-html@npm:9.0.5" + dependencies: + "@types/hast": ^3.0.0 + "@types/unist": ^3.0.0 + ccount: ^2.0.0 + comma-separated-tokens: ^2.0.0 + hast-util-whitespace: ^3.0.0 + html-void-elements: ^3.0.0 + mdast-util-to-hast: ^13.0.0 + property-information: ^7.0.0 + space-separated-tokens: ^2.0.0 + stringify-entities: ^4.0.0 + zwitch: ^2.0.4 + checksum: 1ebd013ad340cf646ea944100427917747f69543800e79b2186521dc29c205b4fe75d8062f3eddedf6d66f6180ca06fe127b9e53ff15a8f3579e36637ca43e16 + languageName: node + linkType: hard + +"hast-util-whitespace@npm:^3.0.0": + version: 3.0.0 + resolution: "hast-util-whitespace@npm:3.0.0" + dependencies: + "@types/hast": ^3.0.0 + checksum: 41d93ccce218ba935dc3c12acdf586193c35069489c8c8f50c2aa824c00dec94a3c78b03d1db40fa75381942a189161922e4b7bca700b3a2cc779634c351a1e4 + languageName: node + linkType: hard + +"hookable@npm:^5.5.3": + version: 5.5.3 + resolution: "hookable@npm:5.5.3" + checksum: df659977888398649b6ef8c4470719e7e8384a1d939a6587e332e86fd55b3881806e2f8aaebaabdb4f218f74b83b98f2110e143df225e16d62a39dc271e7e288 + languageName: node + linkType: hard + +"html-void-elements@npm:^3.0.0": + version: 3.0.0 + resolution: "html-void-elements@npm:3.0.0" + checksum: 59be397525465a7489028afa064c55763d9cccd1d7d9f630cca47137317f0e897a9ca26cef7e745e7cff1abc44260cfa407742b243a54261dfacd42230e94fce + languageName: node + linkType: hard + +"http-cache-semantics@npm:^4.1.1": + version: 4.2.0 + resolution: "http-cache-semantics@npm:4.2.0" + checksum: 7a7246ddfce629f96832791176fd643589d954e6f3b49548dadb4290451961237fab8fcea41cd2008fe819d95b41c1e8b97f47d088afc0a1c81705287b4ddbcc + languageName: node + linkType: hard + +"http-proxy-agent@npm:^7.0.0": + version: 7.0.2 + resolution: "http-proxy-agent@npm:7.0.2" + dependencies: + agent-base: ^7.1.0 + debug: ^4.3.4 + checksum: 670858c8f8f3146db5889e1fa117630910101db601fff7d5a8aa637da0abedf68c899f03d3451cac2f83bcc4c3d2dabf339b3aa00ff8080571cceb02c3ce02f3 + languageName: node + linkType: hard + +"https-proxy-agent@npm:^7.0.1": + version: 7.0.6 + resolution: "https-proxy-agent@npm:7.0.6" + dependencies: + agent-base: ^7.1.2 + debug: 4 + checksum: b882377a120aa0544846172e5db021fa8afbf83fea2a897d397bd2ddd8095ab268c24bc462f40a15f2a8c600bf4aa05ce52927f70038d4014e68aefecfa94e8d + languageName: node + linkType: hard + +"iconv-lite@npm:^0.6.2": + version: 0.6.3 + resolution: "iconv-lite@npm:0.6.3" + dependencies: + safer-buffer: ">= 2.1.2 < 3.0.0" + checksum: 3f60d47a5c8fc3313317edfd29a00a692cc87a19cac0159e2ce711d0ebc9019064108323b5e493625e25594f11c6236647d8e256fbe7a58f4a3b33b89e6d30bf + languageName: node + linkType: hard + +"imurmurhash@npm:^0.1.4": + version: 0.1.4 + resolution: "imurmurhash@npm:0.1.4" + checksum: 7cae75c8cd9a50f57dadd77482359f659eaebac0319dd9368bcd1714f55e65badd6929ca58569da2b6494ef13fdd5598cd700b1eba23f8b79c5f19d195a3ecf7 + languageName: node + linkType: hard + +"ip-address@npm:^10.0.1": + version: 10.1.0 + resolution: "ip-address@npm:10.1.0" + checksum: 76b1abcdf52a32e2e05ca1f202f3a8ab8547e5651a9233781b330271bd7f1a741067748d71c4cbb9d9906d9f1fa69e7ddc8b4a11130db4534fdab0e908c84e0d + languageName: node + linkType: hard + +"is-what@npm:^5.2.0": + version: 5.5.0 + resolution: "is-what@npm:5.5.0" + checksum: 8416464cf65a2b57512b18437f672e035e24fcda3af7de47a0f5183c2c46f30ce6e57b6ca35d07a81a04d829357a97ef5e5aa45bf963a5c2d7bf869f9ceebc56 + languageName: node + linkType: hard + +"isexe@npm:^3.1.1": + version: 3.1.1 + resolution: "isexe@npm:3.1.1" + checksum: 7fe1931ee4e88eb5aa524cd3ceb8c882537bc3a81b02e438b240e47012eef49c86904d0f0e593ea7c3a9996d18d0f1f3be8d3eaa92333977b0c3a9d353d5563e + languageName: node + linkType: hard + +"lru-cache@npm:^11.0.0, lru-cache@npm:^11.1.0, lru-cache@npm:^11.2.1": + version: 11.2.4 + resolution: "lru-cache@npm:11.2.4" + checksum: cb8cf72b80a506593f51880bd5a765380d6d8eb82e99b2fbb2f22fe39e5f2f641d47a2509e74cc294617f32a4e90ae8f6214740fe00bc79a6178854f00419b24 + languageName: node + linkType: hard + +"magic-string@npm:^0.30.21": + version: 0.30.21 + resolution: "magic-string@npm:0.30.21" + dependencies: + "@jridgewell/sourcemap-codec": ^1.5.5 + checksum: 4ff76a4e8d439431cf49f039658751ed351962d044e5955adc257489569bd676019c906b631f86319217689d04815d7d064ee3ff08ab82ae65b7655a7e82a414 + languageName: node + linkType: hard + +"make-fetch-happen@npm:^15.0.0": + version: 15.0.3 + resolution: "make-fetch-happen@npm:15.0.3" + dependencies: + "@npmcli/agent": ^4.0.0 + cacache: ^20.0.1 + http-cache-semantics: ^4.1.1 + minipass: ^7.0.2 + minipass-fetch: ^5.0.0 + minipass-flush: ^1.0.5 + minipass-pipeline: ^1.2.4 + negotiator: ^1.0.0 + proc-log: ^6.0.0 + promise-retry: ^2.0.1 + ssri: ^13.0.0 + checksum: 4fb9dbb739b33565c85dacdcff7eb9388d8f36f326a59dc13375f01af809c42c48aa5d1f4840ee36623b2461a15476e1e79e4548ca1af30b42e1e324705ac8b3 + languageName: node + linkType: hard + +"mark.js@npm:8.11.1": + version: 8.11.1 + resolution: "mark.js@npm:8.11.1" + checksum: aa6b9ae1c67245348d5b7abd253ef2acd6bb05c6be358d7d192416d964e42665fc10e0e865591c6f93ab9b57e8da1f23c23216e8ebddb580905ea7a0c0df15d4 + languageName: node + linkType: hard + +"mdast-util-to-hast@npm:^13.0.0": + version: 13.2.1 + resolution: "mdast-util-to-hast@npm:13.2.1" + dependencies: + "@types/hast": ^3.0.0 + "@types/mdast": ^4.0.0 + "@ungap/structured-clone": ^1.0.0 + devlop: ^1.0.0 + micromark-util-sanitize-uri: ^2.0.0 + trim-lines: ^3.0.0 + unist-util-position: ^5.0.0 + unist-util-visit: ^5.0.0 + vfile: ^6.0.0 + checksum: 20537df653be3653c3c6ea4be09ea1f67ca2f5e6afea027fcc3cde531656dc669a5e733d34a95b08b3ee71ab164c7b24352c8212891f723ddcec74d5a046bfd6 + languageName: node + linkType: hard + +"micromark-util-character@npm:^2.0.0": + version: 2.1.1 + resolution: "micromark-util-character@npm:2.1.1" + dependencies: + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: e9e409efe4f2596acd44587e8591b722bfc041c1577e8fe0d9c007a4776fb800f9b3637a22862ad2ba9489f4bdf72bb547fce5767dbbfe0a5e6760e2a21c6495 + languageName: node + linkType: hard + +"micromark-util-encode@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-encode@npm:2.0.1" + checksum: be890b98e78dd0cdd953a313f4148c4692cc2fb05533e56fef5f421287d3c08feee38ca679f318e740530791fc251bfe8c80efa926fcceb4419b269c9343d226 + languageName: node + linkType: hard + +"micromark-util-sanitize-uri@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-sanitize-uri@npm:2.0.1" + dependencies: + micromark-util-character: ^2.0.0 + micromark-util-encode: ^2.0.0 + micromark-util-symbol: ^2.0.0 + checksum: d01517840c17de67aaa0b0f03bfe05fac8a41d99723cd8ce16c62f6810e99cd3695364a34c335485018e5e2c00e69031744630a1b85c6868aa2f2ca1b36daa2f + languageName: node + linkType: hard + +"micromark-util-symbol@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-symbol@npm:2.0.1" + checksum: fb7346950550bc85a55793dda94a8b3cb3abc068dbd7570d1162db7aee803411d06c0a5de4ae59cd945f46143bdeadd4bba02a02248fa0d18cc577babaa00044 + languageName: node + linkType: hard + +"micromark-util-types@npm:^2.0.0": + version: 2.0.2 + resolution: "micromark-util-types@npm:2.0.2" + checksum: 884f7974839e4bc6d2bd662e57c973a9164fd5c0d8fe16cddf07472b86a7e6726747c00674952c0321d17685d700cd3295e9f58a842a53acdf6c6d55ab051aab + languageName: node + linkType: hard + +"minimatch@npm:^10.1.1": + version: 10.1.1 + resolution: "minimatch@npm:10.1.1" + dependencies: + "@isaacs/brace-expansion": ^5.0.0 + checksum: 8820c0be92994f57281f0a7a2cc4268dcc4b610f9a1ab666685716b4efe4b5898b43c835a8f22298875b31c7a278a5e3b7e253eee7c886546bb0b61fb94bca6b + languageName: node + linkType: hard + +"minipass-collect@npm:^2.0.1": + version: 2.0.1 + resolution: "minipass-collect@npm:2.0.1" + dependencies: + minipass: ^7.0.3 + checksum: b251bceea62090f67a6cced7a446a36f4cd61ee2d5cea9aee7fff79ba8030e416327a1c5aa2908dc22629d06214b46d88fdab8c51ac76bacbf5703851b5ad342 + languageName: node + linkType: hard + +"minipass-fetch@npm:^5.0.0": + version: 5.0.0 + resolution: "minipass-fetch@npm:5.0.0" + dependencies: + encoding: ^0.1.13 + minipass: ^7.0.3 + minipass-sized: ^1.0.3 + minizlib: ^3.0.1 + dependenciesMeta: + encoding: + optional: true + checksum: 416645d1e54c09fdfe64ec1676541ac2f6f2af3abc7ad25f2f22c4518535997c1ecd2c0c586ea8a5c6499ad7d8f97671f50ff38488ada54bf61fde309f731379 + languageName: node + linkType: hard + +"minipass-flush@npm:^1.0.5": + version: 1.0.5 + resolution: "minipass-flush@npm:1.0.5" + dependencies: + minipass: ^3.0.0 + checksum: 56269a0b22bad756a08a94b1ffc36b7c9c5de0735a4dd1ab2b06c066d795cfd1f0ac44a0fcae13eece5589b908ecddc867f04c745c7009be0b566421ea0944cf + languageName: node + linkType: hard + +"minipass-pipeline@npm:^1.2.4": + version: 1.2.4 + resolution: "minipass-pipeline@npm:1.2.4" + dependencies: + minipass: ^3.0.0 + checksum: b14240dac0d29823c3d5911c286069e36d0b81173d7bdf07a7e4a91ecdef92cdff4baaf31ea3746f1c61e0957f652e641223970870e2353593f382112257971b + languageName: node + linkType: hard + +"minipass-sized@npm:^1.0.3": + version: 1.0.3 + resolution: "minipass-sized@npm:1.0.3" + dependencies: + minipass: ^3.0.0 + checksum: 79076749fcacf21b5d16dd596d32c3b6bf4d6e62abb43868fac21674078505c8b15eaca4e47ed844985a4514854f917d78f588fcd029693709417d8f98b2bd60 + languageName: node + linkType: hard + +"minipass@npm:^3.0.0": + version: 3.3.6 + resolution: "minipass@npm:3.3.6" + dependencies: + yallist: ^4.0.0 + checksum: a30d083c8054cee83cdcdc97f97e4641a3f58ae743970457b1489ce38ee1167b3aaf7d815cd39ec7a99b9c40397fd4f686e83750e73e652b21cb516f6d845e48 + languageName: node + linkType: hard + +"minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2": + version: 7.1.2 + resolution: "minipass@npm:7.1.2" + checksum: 2bfd325b95c555f2b4d2814d49325691c7bee937d753814861b0b49d5edcda55cbbf22b6b6a60bb91eddac8668771f03c5ff647dcd9d0f798e9548b9cdc46ee3 + languageName: node + linkType: hard + +"minisearch@npm:^7.1.1": + version: 7.2.0 + resolution: "minisearch@npm:7.2.0" + checksum: 556ff91d6edf88b955d3b455e753e620320f0f4ab6fc81ce12e7bbec6721db9cf799933c3e4aa4acffc68c4216459bb2d22c09918a8b20bd43424b93a2f11040 + languageName: node + linkType: hard + +"minizlib@npm:^3.0.1, minizlib@npm:^3.1.0": + version: 3.1.0 + resolution: "minizlib@npm:3.1.0" + dependencies: + minipass: ^7.1.2 + checksum: a15e6f0128f514b7d41a1c68ce531155447f4669e32d279bba1c1c071ef6c2abd7e4d4579bb59ccc2ed1531346749665968fdd7be8d83eb6b6ae2fe1f3d370a7 + languageName: node + linkType: hard + +"mitt@npm:^3.0.1": + version: 3.0.1 + resolution: "mitt@npm:3.0.1" + checksum: b55a489ac9c2949ab166b7f060601d3b6d893a852515ae9eca4e11df01c013876df777ea109317622b5c1c60e8aae252558e33c8c94e14124db38f64a39614b1 + languageName: node + linkType: hard + +"ms@npm:^2.1.3": + version: 2.1.3 + resolution: "ms@npm:2.1.3" + checksum: aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d + languageName: node + linkType: hard + +"nanoid@npm:^3.3.11": + version: 3.3.11 + resolution: "nanoid@npm:3.3.11" + bin: + nanoid: bin/nanoid.cjs + checksum: 3be20d8866a57a6b6d218e82549711c8352ed969f9ab3c45379da28f405363ad4c9aeb0b39e9abc101a529ca65a72ff9502b00bf74a912c4b64a9d62dfd26c29 + languageName: node + linkType: hard + +"negotiator@npm:^1.0.0": + version: 1.0.0 + resolution: "negotiator@npm:1.0.0" + checksum: 20ebfe79b2d2e7cf9cbc8239a72662b584f71164096e6e8896c8325055497c96f6b80cd22c258e8a2f2aa382a787795ec3ee8b37b422a302c7d4381b0d5ecfbb + languageName: node + linkType: hard + +"node-gyp@npm:latest": + version: 12.1.0 + resolution: "node-gyp@npm:12.1.0" + dependencies: + env-paths: ^2.2.0 + exponential-backoff: ^3.1.1 + graceful-fs: ^4.2.6 + make-fetch-happen: ^15.0.0 + nopt: ^9.0.0 + proc-log: ^6.0.0 + semver: ^7.3.5 + tar: ^7.5.2 + tinyglobby: ^0.2.12 + which: ^6.0.0 + bin: + node-gyp: bin/node-gyp.js + checksum: 198d91c535fe9940bcdc0db4e578f94cf9872e0d068e88ef2f4656924248bb67245b270b48eded6634c7513841c0cd42f3da3ac9d77c8e16437fcd90703b9ef3 + languageName: node + linkType: hard + +"nopt@npm:^9.0.0": + version: 9.0.0 + resolution: "nopt@npm:9.0.0" + dependencies: + abbrev: ^4.0.0 + bin: + nopt: bin/nopt.js + checksum: 7a5d9ab0629eaec1944a95438cc4efa6418ed2834aa8eb21a1bea579a7d8ac3e30120131855376a96ef59ab0e23ad8e0bc94d3349770a95e5cb7119339f7c7fb + languageName: node + linkType: hard + +"oniguruma-to-es@npm:^3.1.0": + version: 3.1.1 + resolution: "oniguruma-to-es@npm:3.1.1" + dependencies: + emoji-regex-xs: ^1.0.0 + regex: ^6.0.1 + regex-recursion: ^6.0.2 + checksum: 2bd1b227d199292f5ad9436b24dbe44bbaf1b516c3e2447e97f6d60ff0d35b9d0b83559ea187ae73b439818d3bfe9bde3820f219e9757a8048f21b692986d375 + languageName: node + linkType: hard + +"p-map@npm:^7.0.2": + version: 7.0.4 + resolution: "p-map@npm:7.0.4" + checksum: 4be2097e942f2fd3a4f4b0c6585c721f23851de8ad6484d20c472b3ea4937d5cd9a59914c832b1bceac7bf9d149001938036b82a52de0bc381f61ff2d35d26a5 + languageName: node + linkType: hard + +"path-scurry@npm:^2.0.0": + version: 2.0.1 + resolution: "path-scurry@npm:2.0.1" + dependencies: + lru-cache: ^11.0.0 + minipass: ^7.1.2 + checksum: a022c6c38fed836079d03f96540eafd4cd989acf287b99613c82300107f366e889513ad8b671a2039a9d251122621f9c6fa649f0bd4d50acf95a6943a6692dbf + languageName: node + linkType: hard + +"perfect-debounce@npm:^1.0.0": + version: 1.0.0 + resolution: "perfect-debounce@npm:1.0.0" + checksum: 220343acf52976947958fef3599849471605316e924fe19c633ae2772576298e9d38f02cefa8db46f06607505ce7b232cbb35c9bfd477bd0329bd0a2ce37c594 + languageName: node + linkType: hard + +"picocolors@npm:^1.1.1": + version: 1.1.1 + resolution: "picocolors@npm:1.1.1" + checksum: e1cf46bf84886c79055fdfa9dcb3e4711ad259949e3565154b004b260cd356c5d54b31a1437ce9782624bf766272fe6b0154f5f0c744fb7af5d454d2b60db045 + languageName: node + linkType: hard + +"picomatch@npm:^4.0.3": + version: 4.0.3 + resolution: "picomatch@npm:4.0.3" + checksum: 6817fb74eb745a71445debe1029768de55fd59a42b75606f478ee1d0dc1aa6e78b711d041a7c9d5550e042642029b7f373dc1a43b224c4b7f12d23436735dba0 + languageName: node + linkType: hard + +"postcss@npm:^8.4.43, postcss@npm:^8.5.6": + version: 8.5.6 + resolution: "postcss@npm:8.5.6" + dependencies: + nanoid: ^3.3.11 + picocolors: ^1.1.1 + source-map-js: ^1.2.1 + checksum: 20f3b5d673ffeec2b28d65436756d31ee33f65b0a8bedb3d32f556fbd5973be38c3a7fb5b959a5236c60a5db7b91b0a6b14ffaac0d717dce1b903b964ee1c1bb + languageName: node + linkType: hard + +"preact@npm:^10.0.0": + version: 10.28.2 + resolution: "preact@npm:10.28.2" + checksum: 5f65087ab00ab270ca514e43b5aaba79101da92eb2ea3199a2b54776a159032b0ee4749a529f624ccc7d456c71b56ddeed2cbcd92cd1e6a3b4dcecfaa7cacf36 + languageName: node + linkType: hard + +"proc-log@npm:^6.0.0": + version: 6.1.0 + resolution: "proc-log@npm:6.1.0" + checksum: ac450ff8244e95b0c9935b52d629fef92ae69b7e39aea19972a8234259614d644402dd62ce9cb094f4a637d8a4514cba90c1456ad785a40ad5b64d502875a817 + languageName: node + linkType: hard + +"promise-retry@npm:^2.0.1": + version: 2.0.1 + resolution: "promise-retry@npm:2.0.1" + dependencies: + err-code: ^2.0.2 + retry: ^0.12.0 + checksum: f96a3f6d90b92b568a26f71e966cbbc0f63ab85ea6ff6c81284dc869b41510e6cdef99b6b65f9030f0db422bf7c96652a3fff9f2e8fb4a0f069d8f4430359429 + languageName: node + linkType: hard + +"property-information@npm:^7.0.0": + version: 7.1.0 + resolution: "property-information@npm:7.1.0" + checksum: 3875161d204bac89d75181f6d3ebc3ecaeb2699b4e2ecfcf5452201d7cdd275168c6742d7ff8cec5ab0c342fae72369ac705e1f8e9680a9acd911692e80dfb88 + languageName: node + linkType: hard + +"regex-recursion@npm:^6.0.2": + version: 6.0.2 + resolution: "regex-recursion@npm:6.0.2" + dependencies: + regex-utilities: ^2.3.0 + checksum: 29913751ee2e41d3d66c957136ba386046f5e9f780f4be482ad3b64b04258bbb2cebe87f8762c5247eb8c9fa46a5ecf18aba5d888f7def73ac8dea49165193d4 + languageName: node + linkType: hard + +"regex-utilities@npm:^2.3.0": + version: 2.3.0 + resolution: "regex-utilities@npm:2.3.0" + checksum: 41408777df45cefe1b276281030213235aa1143809c4c10eb5573d2cc27ff2c4aa746c6f4d4c235e3d2f4830eff76b28906ce82fbe72895beca8e15204c2da51 + languageName: node + linkType: hard + +"regex@npm:^6.0.1": + version: 6.1.0 + resolution: "regex@npm:6.1.0" + dependencies: + regex-utilities: ^2.3.0 + checksum: 8ea9656dbafe8f324f605653b75ce954e9d695b4bb54dbf1fea20d8095b7530f4552bc3e7890b58542441ee2287f64a35d512601fb3417ba542a7a20f48fb855 + languageName: node + linkType: hard + +"retry@npm:^0.12.0": + version: 0.12.0 + resolution: "retry@npm:0.12.0" + checksum: 623bd7d2e5119467ba66202d733ec3c2e2e26568074923bc0585b6b99db14f357e79bdedb63cab56cec47491c4a0da7e6021a7465ca6dc4f481d3898fdd3158c + languageName: node + linkType: hard + +"rfdc@npm:^1.4.1": + version: 1.4.1 + resolution: "rfdc@npm:1.4.1" + checksum: 3b05bd55062c1d78aaabfcea43840cdf7e12099968f368e9a4c3936beb744adb41cbdb315eac6d4d8c6623005d6f87fdf16d8a10e1ff3722e84afea7281c8d13 + languageName: node + linkType: hard + +"rhdh-e2e-test-utils-docs@workspace:.": + version: 0.0.0-use.local + resolution: "rhdh-e2e-test-utils-docs@workspace:." + dependencies: + vitepress: ^1.5.0 + languageName: unknown + linkType: soft + +"rollup@npm:^4.20.0": + version: 4.55.1 + resolution: "rollup@npm:4.55.1" + dependencies: + "@rollup/rollup-android-arm-eabi": 4.55.1 + "@rollup/rollup-android-arm64": 4.55.1 + "@rollup/rollup-darwin-arm64": 4.55.1 + "@rollup/rollup-darwin-x64": 4.55.1 + "@rollup/rollup-freebsd-arm64": 4.55.1 + "@rollup/rollup-freebsd-x64": 4.55.1 + "@rollup/rollup-linux-arm-gnueabihf": 4.55.1 + "@rollup/rollup-linux-arm-musleabihf": 4.55.1 + "@rollup/rollup-linux-arm64-gnu": 4.55.1 + "@rollup/rollup-linux-arm64-musl": 4.55.1 + "@rollup/rollup-linux-loong64-gnu": 4.55.1 + "@rollup/rollup-linux-loong64-musl": 4.55.1 + "@rollup/rollup-linux-ppc64-gnu": 4.55.1 + "@rollup/rollup-linux-ppc64-musl": 4.55.1 + "@rollup/rollup-linux-riscv64-gnu": 4.55.1 + "@rollup/rollup-linux-riscv64-musl": 4.55.1 + "@rollup/rollup-linux-s390x-gnu": 4.55.1 + "@rollup/rollup-linux-x64-gnu": 4.55.1 + "@rollup/rollup-linux-x64-musl": 4.55.1 + "@rollup/rollup-openbsd-x64": 4.55.1 + "@rollup/rollup-openharmony-arm64": 4.55.1 + "@rollup/rollup-win32-arm64-msvc": 4.55.1 + "@rollup/rollup-win32-ia32-msvc": 4.55.1 + "@rollup/rollup-win32-x64-gnu": 4.55.1 + "@rollup/rollup-win32-x64-msvc": 4.55.1 + "@types/estree": 1.0.8 + fsevents: ~2.3.2 + dependenciesMeta: + "@rollup/rollup-android-arm-eabi": + optional: true + "@rollup/rollup-android-arm64": + optional: true + "@rollup/rollup-darwin-arm64": + optional: true + "@rollup/rollup-darwin-x64": + optional: true + "@rollup/rollup-freebsd-arm64": + optional: true + "@rollup/rollup-freebsd-x64": + optional: true + "@rollup/rollup-linux-arm-gnueabihf": + optional: true + "@rollup/rollup-linux-arm-musleabihf": + optional: true + "@rollup/rollup-linux-arm64-gnu": + optional: true + "@rollup/rollup-linux-arm64-musl": + optional: true + "@rollup/rollup-linux-loong64-gnu": + optional: true + "@rollup/rollup-linux-loong64-musl": + optional: true + "@rollup/rollup-linux-ppc64-gnu": + optional: true + "@rollup/rollup-linux-ppc64-musl": + optional: true + "@rollup/rollup-linux-riscv64-gnu": + optional: true + "@rollup/rollup-linux-riscv64-musl": + optional: true + "@rollup/rollup-linux-s390x-gnu": + optional: true + "@rollup/rollup-linux-x64-gnu": + optional: true + "@rollup/rollup-linux-x64-musl": + optional: true + "@rollup/rollup-openbsd-x64": + optional: true + "@rollup/rollup-openharmony-arm64": + optional: true + "@rollup/rollup-win32-arm64-msvc": + optional: true + "@rollup/rollup-win32-ia32-msvc": + optional: true + "@rollup/rollup-win32-x64-gnu": + optional: true + "@rollup/rollup-win32-x64-msvc": + optional: true + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: fd5374cd7e6046404d59d64e1346821fee0900bc9cac021078bfdd342fc54305defba882fb53e6543b5c17ce4573b30fb45ee28b9a4122548358004efdc550d2 + languageName: node + linkType: hard + +"safer-buffer@npm:>= 2.1.2 < 3.0.0": + version: 2.1.2 + resolution: "safer-buffer@npm:2.1.2" + checksum: cab8f25ae6f1434abee8d80023d7e72b598cf1327164ddab31003c51215526801e40b66c5e65d658a0af1e9d6478cadcb4c745f4bd6751f97d8644786c0978b0 + languageName: node + linkType: hard + +"semver@npm:^7.3.5": + version: 7.7.3 + resolution: "semver@npm:7.7.3" + bin: + semver: bin/semver.js + checksum: f013a3ee4607857bcd3503b6ac1d80165f7f8ea94f5d55e2d3e33df82fce487aa3313b987abf9b39e0793c83c9fc67b76c36c067625141a9f6f704ae0ea18db2 + languageName: node + linkType: hard + +"shiki@npm:^2.1.0": + version: 2.5.0 + resolution: "shiki@npm:2.5.0" + dependencies: + "@shikijs/core": 2.5.0 + "@shikijs/engine-javascript": 2.5.0 + "@shikijs/engine-oniguruma": 2.5.0 + "@shikijs/langs": 2.5.0 + "@shikijs/themes": 2.5.0 + "@shikijs/types": 2.5.0 + "@shikijs/vscode-textmate": ^10.0.2 + "@types/hast": ^3.0.4 + checksum: 53d861dd532a3655b057b920bb1218150f524294c76b1180e68cdb777829686a42ce41cc46331a2780e30ef68cec40e251360d50f75dabf5409f1bfcdc6b9ef9 + languageName: node + linkType: hard + +"smart-buffer@npm:^4.2.0": + version: 4.2.0 + resolution: "smart-buffer@npm:4.2.0" + checksum: b5167a7142c1da704c0e3af85c402002b597081dd9575031a90b4f229ca5678e9a36e8a374f1814c8156a725d17008ae3bde63b92f9cfd132526379e580bec8b + languageName: node + linkType: hard + +"socks-proxy-agent@npm:^8.0.3": + version: 8.0.5 + resolution: "socks-proxy-agent@npm:8.0.5" + dependencies: + agent-base: ^7.1.2 + debug: ^4.3.4 + socks: ^2.8.3 + checksum: b4fbcdb7ad2d6eec445926e255a1fb95c975db0020543fbac8dfa6c47aecc6b3b619b7fb9c60a3f82c9b2969912a5e7e174a056ae4d98cb5322f3524d6036e1d + languageName: node + linkType: hard + +"socks@npm:^2.8.3": + version: 2.8.7 + resolution: "socks@npm:2.8.7" + dependencies: + ip-address: ^10.0.1 + smart-buffer: ^4.2.0 + checksum: 4bbe2c88cf0eeaf49f94b7f11564a99b2571bde6fd1e714ff95b38f89e1f97858c19e0ab0e6d39eb7f6a984fa67366825895383ed563fe59962a1d57a1d55318 + languageName: node + linkType: hard + +"source-map-js@npm:^1.2.1": + version: 1.2.1 + resolution: "source-map-js@npm:1.2.1" + checksum: 4eb0cd997cdf228bc253bcaff9340afeb706176e64868ecd20efbe6efea931465f43955612346d6b7318789e5265bdc419bc7669c1cebe3db0eb255f57efa76b + languageName: node + linkType: hard + +"space-separated-tokens@npm:^2.0.0": + version: 2.0.2 + resolution: "space-separated-tokens@npm:2.0.2" + checksum: 202e97d7ca1ba0758a0aa4fe226ff98142073bcceeff2da3aad037968878552c3bbce3b3231970025375bbba5aee00c5b8206eda408da837ab2dc9c0f26be990 + languageName: node + linkType: hard + +"speakingurl@npm:^14.0.1": + version: 14.0.1 + resolution: "speakingurl@npm:14.0.1" + checksum: 5c7fb81d9b4cbda31f462f424cc2d59d9d07ca07e86f9f4e7b1c6325307646f9b82297891ce7f9e75b4bccf20ac436758e721506461b5cd6e5561e89186aa67b + languageName: node + linkType: hard + +"ssri@npm:^13.0.0": + version: 13.0.0 + resolution: "ssri@npm:13.0.0" + dependencies: + minipass: ^7.0.3 + checksum: 9705dff9e686b11f3035fb4c3d44ce690359a15a54adcd6a18951f2763f670877321178dc72c37a2b804dba3287ecaa48726dbd0cff79b2715b1cc24521b3af3 + languageName: node + linkType: hard + +"stringify-entities@npm:^4.0.0": + version: 4.0.4 + resolution: "stringify-entities@npm:4.0.4" + dependencies: + character-entities-html4: ^2.0.0 + character-entities-legacy: ^3.0.0 + checksum: ac1344ef211eacf6cf0a0a8feaf96f9c36083835b406560d2c6ff5a87406a41b13f2f0b4c570a3b391f465121c4fd6822b863ffb197e8c0601a64097862cc5b5 + languageName: node + linkType: hard + +"superjson@npm:^2.2.2": + version: 2.2.6 + resolution: "superjson@npm:2.2.6" + dependencies: + copy-anything: ^4 + checksum: d860577809502d058144d1e5fcd383571b4cf0466cab7d7b6fa1675fff598af53148bacd7b3b0075fba0997ed1f05393bde95b3a3727f7708513396b1d88a8b6 + languageName: node + linkType: hard + +"tabbable@npm:^6.4.0": + version: 6.4.0 + resolution: "tabbable@npm:6.4.0" + checksum: 7084cba269ebbc7dcdeed5aca7f90c0a0fb59a295dd1e83703ab89cca5e6c53b78d02020e3d1065481984cd64bba7dd1ea3c0a48e92fdba83d586e6e86d62a74 + languageName: node + linkType: hard + +"tar@npm:^7.5.2": + version: 7.5.2 + resolution: "tar@npm:7.5.2" + dependencies: + "@isaacs/fs-minipass": ^4.0.0 + chownr: ^3.0.0 + minipass: ^7.1.2 + minizlib: ^3.1.0 + yallist: ^5.0.0 + checksum: 192559b0e7af17d57c7747592ef22c14d5eba2d9c35996320ccd20c3e2038160fe8d928fc5c08b2aa1b170c4d0a18c119441e81eae8f227ca2028d5bcaa6bf23 + languageName: node + linkType: hard + +"tinyglobby@npm:^0.2.12": + version: 0.2.15 + resolution: "tinyglobby@npm:0.2.15" + dependencies: + fdir: ^6.5.0 + picomatch: ^4.0.3 + checksum: 0e33b8babff966c6ab86e9b825a350a6a98a63700fa0bb7ae6cf36a7770a508892383adc272f7f9d17aaf46a9d622b455e775b9949a3f951eaaf5dfb26331d44 + languageName: node + linkType: hard + +"trim-lines@npm:^3.0.0": + version: 3.0.1 + resolution: "trim-lines@npm:3.0.1" + checksum: e241da104682a0e0d807222cc1496b92e716af4db7a002f4aeff33ae6a0024fef93165d49eab11aa07c71e1347c42d46563f91dfaa4d3fb945aa535cdead53ed + languageName: node + linkType: hard + +"unique-filename@npm:^5.0.0": + version: 5.0.0 + resolution: "unique-filename@npm:5.0.0" + dependencies: + unique-slug: ^6.0.0 + checksum: a5f67085caef74bdd2a6869a200ed5d68d171f5cc38435a836b5fd12cce4e4eb55e6a190298035c325053a5687ed7a3c96f0a91e82215fd14729769d9ac57d9b + languageName: node + linkType: hard + +"unique-slug@npm:^6.0.0": + version: 6.0.0 + resolution: "unique-slug@npm:6.0.0" + dependencies: + imurmurhash: ^0.1.4 + checksum: ad6cf238b10292d944521714d31bc9f3ca79fa80cb7a154aad183056493f98e85de669412c6bbfe527ffa9bdeff36d3dd4d5bccaf562c794f2580ab11932b691 + languageName: node + linkType: hard + +"unist-util-is@npm:^6.0.0": + version: 6.0.1 + resolution: "unist-util-is@npm:6.0.1" + dependencies: + "@types/unist": ^3.0.0 + checksum: e57733e1766b55c9a873a42d2f34daa211580788b1bba26af2fc22e48e147bdcff0f9a752ed2a19238864823735fbbe27a1804d6a5a22b182c23aa0191e41c12 + languageName: node + linkType: hard + +"unist-util-position@npm:^5.0.0": + version: 5.0.0 + resolution: "unist-util-position@npm:5.0.0" + dependencies: + "@types/unist": ^3.0.0 + checksum: f89b27989b19f07878de9579cd8db2aa0194c8360db69e2c99bd2124a480d79c08f04b73a64daf01a8fb3af7cba65ff4b45a0b978ca243226084ad5f5d441dde + languageName: node + linkType: hard + +"unist-util-stringify-position@npm:^4.0.0": + version: 4.0.0 + resolution: "unist-util-stringify-position@npm:4.0.0" + dependencies: + "@types/unist": ^3.0.0 + checksum: e2e7aee4b92ddb64d314b4ac89eef7a46e4c829cbd3ee4aee516d100772b490eb6b4974f653ba0717a0071ca6ea0770bf22b0a2ea62c65fcba1d071285e96324 + languageName: node + linkType: hard + +"unist-util-visit-parents@npm:^6.0.0": + version: 6.0.2 + resolution: "unist-util-visit-parents@npm:6.0.2" + dependencies: + "@types/unist": ^3.0.0 + unist-util-is: ^6.0.0 + checksum: cf28578a6f0b81877965e261fe82460f83b8c3a9cab3b2080c046b215f3223c6195b01064256619ca3411a1930face93a1a2a72d34d8716e684d6cd59f53cd9a + languageName: node + linkType: hard + +"unist-util-visit@npm:^5.0.0": + version: 5.0.0 + resolution: "unist-util-visit@npm:5.0.0" + dependencies: + "@types/unist": ^3.0.0 + unist-util-is: ^6.0.0 + unist-util-visit-parents: ^6.0.0 + checksum: 9ec42e618e7e5d0202f3c191cd30791b51641285732767ee2e6bcd035931032e3c1b29093f4d7fd0c79175bbc1f26f24f26ee49770d32be76f8730a652a857e6 + languageName: node + linkType: hard + +"vfile-message@npm:^4.0.0": + version: 4.0.3 + resolution: "vfile-message@npm:4.0.3" + dependencies: + "@types/unist": ^3.0.0 + unist-util-stringify-position: ^4.0.0 + checksum: f5e8516f2aa0feb4c866d507543d4e90f9ab309e2c988577dbf4ebd268d495f72f2b48149849d14300164d5d60b5f74b5641cd285bb4408a3942b758683d9276 + languageName: node + linkType: hard + +"vfile@npm:^6.0.0": + version: 6.0.3 + resolution: "vfile@npm:6.0.3" + dependencies: + "@types/unist": ^3.0.0 + vfile-message: ^4.0.0 + checksum: 152b6729be1af70df723efb65c1a1170fd483d41086557da3651eea69a1dd1f0c22ea4344834d56d30734b9185bcab63e22edc81d3f0e9bed8aa4660d61080af + languageName: node + linkType: hard + +"vite@npm:^5.4.14": + version: 5.4.21 + resolution: "vite@npm:5.4.21" + dependencies: + esbuild: ^0.21.3 + fsevents: ~2.3.3 + postcss: ^8.4.43 + rollup: ^4.20.0 + peerDependencies: + "@types/node": ^18.0.0 || >=20.0.0 + less: "*" + lightningcss: ^1.21.0 + sass: "*" + sass-embedded: "*" + stylus: "*" + sugarss: "*" + terser: ^5.4.0 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + bin: + vite: bin/vite.js + checksum: 7177fa03cff6a382f225290c9889a0d0e944d17eab705bcba89b58558a6f7adfa1f47e469b88f42a044a0eb40c12a1bf68b3cb42abb5295d04f9d7d4dd320837 + languageName: node + linkType: hard + +"vitepress@npm:^1.5.0": + version: 1.6.4 + resolution: "vitepress@npm:1.6.4" + dependencies: + "@docsearch/css": 3.8.2 + "@docsearch/js": 3.8.2 + "@iconify-json/simple-icons": ^1.2.21 + "@shikijs/core": ^2.1.0 + "@shikijs/transformers": ^2.1.0 + "@shikijs/types": ^2.1.0 + "@types/markdown-it": ^14.1.2 + "@vitejs/plugin-vue": ^5.2.1 + "@vue/devtools-api": ^7.7.0 + "@vue/shared": ^3.5.13 + "@vueuse/core": ^12.4.0 + "@vueuse/integrations": ^12.4.0 + focus-trap: ^7.6.4 + mark.js: 8.11.1 + minisearch: ^7.1.1 + shiki: ^2.1.0 + vite: ^5.4.14 + vue: ^3.5.13 + peerDependencies: + markdown-it-mathjax3: ^4 + postcss: ^8 + peerDependenciesMeta: + markdown-it-mathjax3: + optional: true + postcss: + optional: true + bin: + vitepress: bin/vitepress.js + checksum: 75c1a1d7b0c4f901344e108014ebe43f0a515df87c78b1201158a626b6aad7f9ae3dbd54e48e6cbe5602a61aa97b134ab5dffdfad8b4e226113308d5fda91e26 + languageName: node + linkType: hard + +"vue@npm:^3.5.13": + version: 3.5.26 + resolution: "vue@npm:3.5.26" + dependencies: + "@vue/compiler-dom": 3.5.26 + "@vue/compiler-sfc": 3.5.26 + "@vue/runtime-dom": 3.5.26 + "@vue/server-renderer": 3.5.26 + "@vue/shared": 3.5.26 + peerDependencies: + typescript: "*" + peerDependenciesMeta: + typescript: + optional: true + checksum: 1974f374318fbbb5fafde6195285c53c14cc8989ec38aeb6ad91c19598c45dfc6ccea7bdc6682efcb27320e089766bd6245fccd36f66f608e5d81cae8a03653a + languageName: node + linkType: hard + +"which@npm:^6.0.0": + version: 6.0.0 + resolution: "which@npm:6.0.0" + dependencies: + isexe: ^3.1.1 + bin: + node-which: bin/which.js + checksum: df19b2cd8aac94b333fa29b42e8e371a21e634a742a3b156716f7752a5afe1d73fb5d8bce9b89326f453d96879e8fe626eb421e0117eb1a3ce9fd8c97f6b7db9 + languageName: node + linkType: hard + +"yallist@npm:^4.0.0": + version: 4.0.0 + resolution: "yallist@npm:4.0.0" + checksum: 343617202af32df2a15a3be36a5a8c0c8545208f3d3dfbc6bb7c3e3b7e8c6f8e7485432e4f3b88da3031a6e20afa7c711eded32ddfb122896ac5d914e75848d5 + languageName: node + linkType: hard + +"yallist@npm:^5.0.0": + version: 5.0.0 + resolution: "yallist@npm:5.0.0" + checksum: eba51182400b9f35b017daa7f419f434424410691bbc5de4f4240cc830fdef906b504424992700dc047f16b4d99100a6f8b8b11175c193f38008e9c96322b6a5 + languageName: node + linkType: hard + +"zwitch@npm:^2.0.4": + version: 2.0.4 + resolution: "zwitch@npm:2.0.4" + checksum: f22ec5fc2d5f02c423c93d35cdfa83573a3a3bd98c66b927c368ea4d0e7252a500df2a90a6b45522be536a96a73404393c958e945fdba95e6832c200791702b6 + languageName: node + linkType: hard diff --git a/package.json b/package.json index 00cdcf6..60c2f4f 100644 --- a/package.json +++ b/package.json @@ -49,11 +49,11 @@ "build": "yarn clean && tsc -p tsconfig.build.json && cp -r src/deployment/rhdh/config dist/deployment/rhdh/ && cp -r src/deployment/keycloak/config dist/deployment/keycloak/", "check": "yarn typecheck && yarn lint:check && yarn prettier:check", "clean": "rm -rf dist", - "lint:check": "eslint . --ignore-pattern dist --ignore-pattern README.md", - "lint:fix": "eslint . --fix --ignore-pattern dist --ignore-pattern README.md", + "lint:check": "eslint . --ignore-pattern dist --ignore-pattern README.md --ignore-pattern docs", + "lint:fix": "eslint . --fix --ignore-pattern dist --ignore-pattern README.md --ignore-pattern docs", "prepublishOnly": "yarn build", - "prettier:check": "prettier --check . '!dist' '!README.md'", - "prettier:fix": "prettier --write . '!dist' '!README.md'", + "prettier:check": "prettier --check . '!dist' '!README.md' '!docs'", + "prettier:fix": "prettier --write . '!dist' '!README.md' '!docs'", "typecheck": "tsc --noEmit" }, "keywords": [ diff --git a/yarn.lock b/yarn.lock index ac280ba..4a959f8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,6 +5,203 @@ __metadata: version: 6 cacheKey: 8 +"@algolia/abtesting@npm:1.12.3": + version: 1.12.3 + resolution: "@algolia/abtesting@npm:1.12.3" + dependencies: + "@algolia/client-common": 5.46.3 + "@algolia/requester-browser-xhr": 5.46.3 + "@algolia/requester-fetch": 5.46.3 + "@algolia/requester-node-http": 5.46.3 + checksum: e5370f1522651dda906770b07c22ff81126ffa78354a92b48e4e7feaeedc8d686979d351a1cdd9a526d8dd2e1c1c6b132ac51ca3adae8337bd3846aa5f074519 + languageName: node + linkType: hard + +"@algolia/autocomplete-core@npm:1.17.7": + version: 1.17.7 + resolution: "@algolia/autocomplete-core@npm:1.17.7" + dependencies: + "@algolia/autocomplete-plugin-algolia-insights": 1.17.7 + "@algolia/autocomplete-shared": 1.17.7 + checksum: 17236cfb4eccc4a706ce42ff09f9b9e819c38a650f96dc124b4168a626303a1d00ee407c46cdd9ff19fcaf344815cf222cc14c0b5364f6cb2f55f0a62677b9a4 + languageName: node + linkType: hard + +"@algolia/autocomplete-plugin-algolia-insights@npm:1.17.7": + version: 1.17.7 + resolution: "@algolia/autocomplete-plugin-algolia-insights@npm:1.17.7" + dependencies: + "@algolia/autocomplete-shared": 1.17.7 + peerDependencies: + search-insights: ">= 1 < 3" + checksum: 18e9ad58d421b7744e697e91253a6c95287e3c1194c0c8bdf1179a26b422cca069a7da102b2fcf9257bb85efd53db6131995cda3df8ab262424fed87a88c0a9d + languageName: node + linkType: hard + +"@algolia/autocomplete-preset-algolia@npm:1.17.7": + version: 1.17.7 + resolution: "@algolia/autocomplete-preset-algolia@npm:1.17.7" + dependencies: + "@algolia/autocomplete-shared": 1.17.7 + peerDependencies: + "@algolia/client-search": ">= 4.9.1 < 6" + algoliasearch: ">= 4.9.1 < 6" + checksum: d8e7e000fc027e15a0173a2cabb7821d902f71f03957f3bad7bb2bfd7ee58825e96e2efa57be559312e33d1bf07f658469fdc209286dbab05d8dada2d7a18531 + languageName: node + linkType: hard + +"@algolia/autocomplete-shared@npm:1.17.7": + version: 1.17.7 + resolution: "@algolia/autocomplete-shared@npm:1.17.7" + peerDependencies: + "@algolia/client-search": ">= 4.9.1 < 6" + algoliasearch: ">= 4.9.1 < 6" + checksum: 8aff3df580bdb3eeffce36225b04e4352f837aea37032fd6358d9c43b2eeab23cda4f217f7c0f0b8346b630c57c89ad415f8da308544f009b5e26256f9c3b59e + languageName: node + linkType: hard + +"@algolia/client-abtesting@npm:5.46.3": + version: 5.46.3 + resolution: "@algolia/client-abtesting@npm:5.46.3" + dependencies: + "@algolia/client-common": 5.46.3 + "@algolia/requester-browser-xhr": 5.46.3 + "@algolia/requester-fetch": 5.46.3 + "@algolia/requester-node-http": 5.46.3 + checksum: 05c55dbe5d4f7a184df2b5f94c8129749f9bf0f26a0e255828c172c506dfce9f6d178a4b1668d23451ba9f00b2c03f8a6fa2d444f5528df557787c62bd5842d7 + languageName: node + linkType: hard + +"@algolia/client-analytics@npm:5.46.3": + version: 5.46.3 + resolution: "@algolia/client-analytics@npm:5.46.3" + dependencies: + "@algolia/client-common": 5.46.3 + "@algolia/requester-browser-xhr": 5.46.3 + "@algolia/requester-fetch": 5.46.3 + "@algolia/requester-node-http": 5.46.3 + checksum: 5d344b8c9661706bafaf1afe22f349e0f982f210a8c429a3e02335ec9c8184aba39c611ea4df1b792806e837aa7511e1c6747a8ca9c780965b235f2a85fff176 + languageName: node + linkType: hard + +"@algolia/client-common@npm:5.46.3": + version: 5.46.3 + resolution: "@algolia/client-common@npm:5.46.3" + checksum: fdc62da392336940ced7aa1d7efbca1c588487625f3f33b2beff38e78495e0a628a99cb68f4c8f7f9f4f2e2da289c1f7135726a8ba403c127a45434c8c58ba29 + languageName: node + linkType: hard + +"@algolia/client-insights@npm:5.46.3": + version: 5.46.3 + resolution: "@algolia/client-insights@npm:5.46.3" + dependencies: + "@algolia/client-common": 5.46.3 + "@algolia/requester-browser-xhr": 5.46.3 + "@algolia/requester-fetch": 5.46.3 + "@algolia/requester-node-http": 5.46.3 + checksum: 9490ac26f505e631f3872969f535686c4d115fafe637fa26e876f5b4bed8e73f6b6f054dc9f872ea7bb80550e09b021d267cdc865433fc1fef4d39d303e1c610 + languageName: node + linkType: hard + +"@algolia/client-personalization@npm:5.46.3": + version: 5.46.3 + resolution: "@algolia/client-personalization@npm:5.46.3" + dependencies: + "@algolia/client-common": 5.46.3 + "@algolia/requester-browser-xhr": 5.46.3 + "@algolia/requester-fetch": 5.46.3 + "@algolia/requester-node-http": 5.46.3 + checksum: dbc4a42f47ea74279926ae603248db1589259d56c2a13d47b6ec3409ea5deabc051d89b7e45cd78619a7f52272a8bed004a9759cfe6894a13cd0797837fb18e8 + languageName: node + linkType: hard + +"@algolia/client-query-suggestions@npm:5.46.3": + version: 5.46.3 + resolution: "@algolia/client-query-suggestions@npm:5.46.3" + dependencies: + "@algolia/client-common": 5.46.3 + "@algolia/requester-browser-xhr": 5.46.3 + "@algolia/requester-fetch": 5.46.3 + "@algolia/requester-node-http": 5.46.3 + checksum: 254a714b5a54c796660516bd9d690cbc9e372e968ffd928ae7e50ab33751a56d82825e514749f411d3ee4cbaa65920f26769c43de63874a827af612c1c789f6a + languageName: node + linkType: hard + +"@algolia/client-search@npm:5.46.3": + version: 5.46.3 + resolution: "@algolia/client-search@npm:5.46.3" + dependencies: + "@algolia/client-common": 5.46.3 + "@algolia/requester-browser-xhr": 5.46.3 + "@algolia/requester-fetch": 5.46.3 + "@algolia/requester-node-http": 5.46.3 + checksum: 6b6ffb444e056c3eeab8621fdfa2a92e40e16ff0a2218f63064004f2e10fb6efffc4cf8346d181f17243eb8c088c0dc4e1d213089e0a9afba36e9e22b4dc929b + languageName: node + linkType: hard + +"@algolia/ingestion@npm:1.46.3": + version: 1.46.3 + resolution: "@algolia/ingestion@npm:1.46.3" + dependencies: + "@algolia/client-common": 5.46.3 + "@algolia/requester-browser-xhr": 5.46.3 + "@algolia/requester-fetch": 5.46.3 + "@algolia/requester-node-http": 5.46.3 + checksum: d79fe9914513f3a95ca3b7df31fe75605c6f64634f4d614cc17c42e2302c4dfd3eeece7ee7bb986e60665f6ef67c54f2f93da2d9145085aab7b02acf974a3df1 + languageName: node + linkType: hard + +"@algolia/monitoring@npm:1.46.3": + version: 1.46.3 + resolution: "@algolia/monitoring@npm:1.46.3" + dependencies: + "@algolia/client-common": 5.46.3 + "@algolia/requester-browser-xhr": 5.46.3 + "@algolia/requester-fetch": 5.46.3 + "@algolia/requester-node-http": 5.46.3 + checksum: a0dd4273e5d9cde3c274cbe246ea969bffaa0c614e85d6635550eaead826726a15e79dcfecb694a214c5edaff1582bdff95f8533362b4aea2044e8cd468efda8 + languageName: node + linkType: hard + +"@algolia/recommend@npm:5.46.3": + version: 5.46.3 + resolution: "@algolia/recommend@npm:5.46.3" + dependencies: + "@algolia/client-common": 5.46.3 + "@algolia/requester-browser-xhr": 5.46.3 + "@algolia/requester-fetch": 5.46.3 + "@algolia/requester-node-http": 5.46.3 + checksum: 104c523c1e9d1e0bd30c08c119168dcd2c0106e7098470f5c28ab1532d3e6b3b30979bb1f59eee8dda40f319e89150dcb2ebd7164216947a64f3540a7403709f + languageName: node + linkType: hard + +"@algolia/requester-browser-xhr@npm:5.46.3": + version: 5.46.3 + resolution: "@algolia/requester-browser-xhr@npm:5.46.3" + dependencies: + "@algolia/client-common": 5.46.3 + checksum: 5def15096c6096442559c53f6c5aea8cc939a0c3d607d3a03e5e6659852c69bca4f90500ab8c5e3bd630873a83b50dbe92bcf70e3f3156d5e62bd098d2e6d077 + languageName: node + linkType: hard + +"@algolia/requester-fetch@npm:5.46.3": + version: 5.46.3 + resolution: "@algolia/requester-fetch@npm:5.46.3" + dependencies: + "@algolia/client-common": 5.46.3 + checksum: 4fdb1d315f8a06f49c7052a0c7335d41441d77dd2e7af54ca2587dedb1a18bb06b1e7354efe743c5bdd4709015cbbe68389ff20de0cc433f9f1c3d53df59275c + languageName: node + linkType: hard + +"@algolia/requester-node-http@npm:5.46.3": + version: 5.46.3 + resolution: "@algolia/requester-node-http@npm:5.46.3" + dependencies: + "@algolia/client-common": 5.46.3 + checksum: 2f9476928db92b6743936c593edd4aa401ade191a8c8829f4ebf8510cd3a6569fd456d5171b839f1916127c31d9930461abd8eff8202be75e554ab5edfbdd089 + languageName: node + linkType: hard + "@axe-core/playwright@npm:^4.11.0": version: 4.11.0 resolution: "@axe-core/playwright@npm:4.11.0" @@ -16,6 +213,41 @@ __metadata: languageName: node linkType: hard +"@babel/helper-string-parser@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-string-parser@npm:7.27.1" + checksum: 0a8464adc4b39b138aedcb443b09f4005d86207d7126e5e079177e05c3116107d856ec08282b365e9a79a9872f40f4092a6127f8d74c8a01c1ef789dacfc25d6 + languageName: node + linkType: hard + +"@babel/helper-validator-identifier@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/helper-validator-identifier@npm:7.28.5" + checksum: 5a251a6848e9712aea0338f659a1a3bd334d26219d5511164544ca8ec20774f098c3a6661e9da65a0d085c745c00bb62c8fada38a62f08fa1f8053bc0aeb57e4 + languageName: node + linkType: hard + +"@babel/parser@npm:^7.28.5": + version: 7.28.6 + resolution: "@babel/parser@npm:7.28.6" + dependencies: + "@babel/types": ^7.28.6 + bin: + parser: ./bin/babel-parser.js + checksum: 2a35319792ceef9bc918f0ff854449bef0120707798fe147ef988b0606de226e2fbc3a562ba687148bfe5336c6c67358fb27e71a94e425b28482dcaf0b172fd6 + languageName: node + linkType: hard + +"@babel/types@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/types@npm:7.28.6" + dependencies: + "@babel/helper-string-parser": ^7.27.1 + "@babel/helper-validator-identifier": ^7.28.5 + checksum: f76556cda59be337cc10dc68b2a9a947c10de018998bab41076e7b7e4489b28dd53299f98f22eec0774264c989515e6fdc56de91c73e3aa396367bb953200a6a + languageName: node + linkType: hard + "@backstage/catalog-model@npm:1.7.5": version: 1.7.5 resolution: "@backstage/catalog-model@npm:1.7.5" @@ -45,6 +277,210 @@ __metadata: languageName: node linkType: hard +"@docsearch/css@npm:3.8.2": + version: 3.8.2 + resolution: "@docsearch/css@npm:3.8.2" + checksum: 9d38cd17b3fc156f57d189ecccb9fbb4f208650dc6039aba6bd73d90174d9801b796421936673cba6bafe1c58302ea48ff5aeea7c803cf229fb1ee2708085a02 + languageName: node + linkType: hard + +"@docsearch/js@npm:3.8.2": + version: 3.8.2 + resolution: "@docsearch/js@npm:3.8.2" + dependencies: + "@docsearch/react": 3.8.2 + preact: ^10.0.0 + checksum: 144ba0dc9e578869ba26cd5198e38e9469e9a351267949bae5a55388197f9ebdff4ffc83e863729d28dbd6431a557c8d7be422b0cd17d37c213223db56231fb7 + languageName: node + linkType: hard + +"@docsearch/react@npm:3.8.2": + version: 3.8.2 + resolution: "@docsearch/react@npm:3.8.2" + dependencies: + "@algolia/autocomplete-core": 1.17.7 + "@algolia/autocomplete-preset-algolia": 1.17.7 + "@docsearch/css": 3.8.2 + algoliasearch: ^5.14.2 + peerDependencies: + "@types/react": ">= 16.8.0 < 19.0.0" + react: ">= 16.8.0 < 19.0.0" + react-dom: ">= 16.8.0 < 19.0.0" + search-insights: ">= 1 < 3" + peerDependenciesMeta: + "@types/react": + optional: true + react: + optional: true + react-dom: + optional: true + search-insights: + optional: true + checksum: b307eee7c657f0c21e8a2b1c28a1d451de83434232e128b13c54c8c219a6d5f2d302ce2ca2a824eb730d5ea25cfccb1dce0e4fc1291f36f31f810841f4a25891 + languageName: node + linkType: hard + +"@esbuild/aix-ppc64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/aix-ppc64@npm:0.21.5" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/android-arm64@npm:0.21.5" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/android-arm@npm:0.21.5" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/android-x64@npm:0.21.5" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/darwin-arm64@npm:0.21.5" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/darwin-x64@npm:0.21.5" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/freebsd-arm64@npm:0.21.5" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/freebsd-x64@npm:0.21.5" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-arm64@npm:0.21.5" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-arm@npm:0.21.5" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-ia32@npm:0.21.5" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-loong64@npm:0.21.5" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-mips64el@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-mips64el@npm:0.21.5" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-ppc64@npm:0.21.5" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-riscv64@npm:0.21.5" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-s390x@npm:0.21.5" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-x64@npm:0.21.5" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/netbsd-x64@npm:0.21.5" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/openbsd-x64@npm:0.21.5" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/sunos-x64@npm:0.21.5" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/win32-arm64@npm:0.21.5" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/win32-ia32@npm:0.21.5" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/win32-x64@npm:0.21.5" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@eslint-community/eslint-utils@npm:^4.7.0, @eslint-community/eslint-utils@npm:^4.8.0": version: 4.9.0 resolution: "@eslint-community/eslint-utils@npm:4.9.0" @@ -164,6 +600,22 @@ __metadata: languageName: node linkType: hard +"@iconify-json/simple-icons@npm:^1.2.21": + version: 1.2.66 + resolution: "@iconify-json/simple-icons@npm:1.2.66" + dependencies: + "@iconify/types": "*" + checksum: 461ebf13d5dc69b7995f8499d758fa3b18d8fe0dded26149f84af75451681e304d32c602339ccf12a72598d1f1d72fc4b38d20ac96995368775b27ac49ab5116 + languageName: node + linkType: hard + +"@iconify/types@npm:*": + version: 2.0.0 + resolution: "@iconify/types@npm:2.0.0" + checksum: 029f58542c160e9d4a746869cf2e475b603424d3adf3994c5cc8d0406c47e6e04a3b898b2707840c1c5b9bd5563a1660a34b110d89fce43923baca5222f4e597 + languageName: node + linkType: hard + "@isaacs/balanced-match@npm:^4.0.1": version: 4.0.1 resolution: "@isaacs/balanced-match@npm:4.0.1" @@ -189,6 +641,13 @@ __metadata: languageName: node linkType: hard +"@jridgewell/sourcemap-codec@npm:^1.5.5": + version: 1.5.5 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" + checksum: c2e36e67971f719a8a3a85ef5a5f580622437cc723c35d03ebd0c9c0b06418700ef006f58af742791f71f6a4fc68fcfaf1f6a74ec2f9a3332860e9373459dae7 + languageName: node + linkType: hard + "@jsep-plugin/assignment@npm:^1.3.0": version: 1.3.0 resolution: "@jsep-plugin/assignment@npm:1.3.0" @@ -322,7 +781,262 @@ __metadata: languageName: node linkType: hard -"@types/estree@npm:^1.0.6": +"@rollup/rollup-android-arm-eabi@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.55.1" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@rollup/rollup-android-arm64@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-android-arm64@npm:4.55.1" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-arm64@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-darwin-arm64@npm:4.55.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-x64@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-darwin-x64@npm:4.55.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-freebsd-arm64@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-freebsd-arm64@npm:4.55.1" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-freebsd-x64@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-freebsd-x64@npm:4.55.1" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-gnueabihf@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.55.1" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-musleabihf@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.55.1" + conditions: os=linux & cpu=arm & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-gnu@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.55.1" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-musl@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.55.1" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-loong64-gnu@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.55.1" + conditions: os=linux & cpu=loong64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-loong64-musl@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-linux-loong64-musl@npm:4.55.1" + conditions: os=linux & cpu=loong64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-ppc64-gnu@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.55.1" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-ppc64-musl@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-linux-ppc64-musl@npm:4.55.1" + conditions: os=linux & cpu=ppc64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-riscv64-gnu@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.55.1" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-riscv64-musl@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.55.1" + conditions: os=linux & cpu=riscv64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-s390x-gnu@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.55.1" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-gnu@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.55.1" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-musl@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.55.1" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-openbsd-x64@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-openbsd-x64@npm:4.55.1" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-openharmony-arm64@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-openharmony-arm64@npm:4.55.1" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-arm64-msvc@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.55.1" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-ia32-msvc@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.55.1" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@rollup/rollup-win32-x64-gnu@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-win32-x64-gnu@npm:4.55.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-x64-msvc@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.55.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@shikijs/core@npm:2.5.0, @shikijs/core@npm:^2.1.0": + version: 2.5.0 + resolution: "@shikijs/core@npm:2.5.0" + dependencies: + "@shikijs/engine-javascript": 2.5.0 + "@shikijs/engine-oniguruma": 2.5.0 + "@shikijs/types": 2.5.0 + "@shikijs/vscode-textmate": ^10.0.2 + "@types/hast": ^3.0.4 + hast-util-to-html: ^9.0.4 + checksum: 033f324e4bfc95afaced957140e53affc35ede89e9a06a844cc15f0ab408731d2b7146e2c5aa01374215550346ac74cdb338aefb8e26765d6596625af6aaca29 + languageName: node + linkType: hard + +"@shikijs/engine-javascript@npm:2.5.0": + version: 2.5.0 + resolution: "@shikijs/engine-javascript@npm:2.5.0" + dependencies: + "@shikijs/types": 2.5.0 + "@shikijs/vscode-textmate": ^10.0.2 + oniguruma-to-es: ^3.1.0 + checksum: f9fb6efcc3238654bcb19ae10f549918901db9c917e2eefd71b9e4a009b725168a9bece2b0814422f7a8fa97e20d585c8a68283d144bdb6a258e57168c0c7b0e + languageName: node + linkType: hard + +"@shikijs/engine-oniguruma@npm:2.5.0": + version: 2.5.0 + resolution: "@shikijs/engine-oniguruma@npm:2.5.0" + dependencies: + "@shikijs/types": 2.5.0 + "@shikijs/vscode-textmate": ^10.0.2 + checksum: d493fc5d2f968d9c69c24ae51c1d5e7ec9e62bbbf0030e59a56277edb19253e412a860fedbf591de19fcbf99a17f642e7a537a796e8c9fb5dd6c447155ed47ec + languageName: node + linkType: hard + +"@shikijs/langs@npm:2.5.0": + version: 2.5.0 + resolution: "@shikijs/langs@npm:2.5.0" + dependencies: + "@shikijs/types": 2.5.0 + checksum: 9082fc23ec6d6b3b605a30b544d7af986586efbd19cf3048cd078f367eff6e708cda318393200d455a42bbca83273a7dfd00f28cedae44e09c4ba66583ea02c9 + languageName: node + linkType: hard + +"@shikijs/themes@npm:2.5.0": + version: 2.5.0 + resolution: "@shikijs/themes@npm:2.5.0" + dependencies: + "@shikijs/types": 2.5.0 + checksum: 0844066e284d8d7263dfd37ff8db8813d6784ea10eab46d0b3639a31a16aa3b68031fd2b4b3f1706f8b9a71ada8b30d8a8bdc5832b7c8a1b3bc1b2e28c199f96 + languageName: node + linkType: hard + +"@shikijs/transformers@npm:^2.1.0": + version: 2.5.0 + resolution: "@shikijs/transformers@npm:2.5.0" + dependencies: + "@shikijs/core": 2.5.0 + "@shikijs/types": 2.5.0 + checksum: bbe5706b4ad7c32fa9d3eb61f656a85a0423934b7174415760bd4081804f41d7a244f46c4b85488de4b2e7009b6c910039a3f60c6daa92999f72fd16ec267780 + languageName: node + linkType: hard + +"@shikijs/types@npm:2.5.0, @shikijs/types@npm:^2.1.0": + version: 2.5.0 + resolution: "@shikijs/types@npm:2.5.0" + dependencies: + "@shikijs/vscode-textmate": ^10.0.2 + "@types/hast": ^3.0.4 + checksum: c7c2be18cb6305890d9d2f8cdd449f9faeb47338d4d4056d7915e8db961b14491cdc3cba70f443be31be04ac129e094dbd16cb93d5068f2341ad95b3a4e88258 + languageName: node + linkType: hard + +"@shikijs/vscode-textmate@npm:^10.0.2": + version: 10.0.2 + resolution: "@shikijs/vscode-textmate@npm:10.0.2" + checksum: e68f27a3dc1584d7414b8acafb9c177a2181eb0b06ef178d8609142f49d28d85fd10ab129affde40a45a7d9238997e457ce47931b3a3815980e2b98b2d26724c + languageName: node + linkType: hard + +"@types/estree@npm:1.0.8, @types/estree@npm:^1.0.6": version: 1.0.8 resolution: "@types/estree@npm:1.0.8" checksum: bd93e2e415b6f182ec4da1074e1f36c480f1d26add3e696d54fb30c09bc470897e41361c8fd957bf0985024f8fbf1e6e2aff977d79352ef7eb93a5c6dcff6c11 @@ -339,6 +1053,15 @@ __metadata: languageName: node linkType: hard +"@types/hast@npm:^3.0.0, @types/hast@npm:^3.0.4": + version: 3.0.4 + resolution: "@types/hast@npm:3.0.4" + dependencies: + "@types/unist": "*" + checksum: 7a973e8d16fcdf3936090fa2280f408fb2b6a4f13b42edeb5fbd614efe042b82eac68e298e556d50f6b4ad585a3a93c353e9c826feccdc77af59de8dd400d044 + languageName: node + linkType: hard + "@types/js-yaml@npm:^4.0.1, @types/js-yaml@npm:^4.0.9": version: 4.0.9 resolution: "@types/js-yaml@npm:4.0.9" @@ -362,6 +1085,13 @@ __metadata: languageName: node linkType: hard +"@types/linkify-it@npm:^5": + version: 5.0.0 + resolution: "@types/linkify-it@npm:5.0.0" + checksum: ec98e03aa883f70153a17a1e6ed9e28b39a604049b485daeddae3a1482ec65cac0817520be6e301d99fd1a934b3950cf0f855655aae6ec27da2bb676ba4a148e + languageName: node + linkType: hard + "@types/lodash.mergewith@npm:^4.6.9": version: 4.6.9 resolution: "@types/lodash.mergewith@npm:4.6.9" @@ -378,6 +1108,32 @@ __metadata: languageName: node linkType: hard +"@types/markdown-it@npm:^14.1.2": + version: 14.1.2 + resolution: "@types/markdown-it@npm:14.1.2" + dependencies: + "@types/linkify-it": ^5 + "@types/mdurl": ^2 + checksum: ad66e0b377d6af09a155bb65f675d1e2cb27d20a3d407377fe4508eb29cde1e765430b99d5129f89012e2524abb5525d629f7057a59ff9fd0967e1ff645b9ec6 + languageName: node + linkType: hard + +"@types/mdast@npm:^4.0.0": + version: 4.0.4 + resolution: "@types/mdast@npm:4.0.4" + dependencies: + "@types/unist": "*" + checksum: 20c4e9574cc409db662a35cba52b068b91eb696b3049e94321219d47d34c8ccc99a142be5c76c80a538b612457b03586bc2f6b727a3e9e7530f4c8568f6282ee + languageName: node + linkType: hard + +"@types/mdurl@npm:^2": + version: 2.0.0 + resolution: "@types/mdurl@npm:2.0.0" + checksum: 78746e96c655ceed63db06382da466fd52c7e9dc54d60b12973dfdd110cae06b9439c4b90e17bb8d4461109184b3ea9f3e9f96b3e4bf4aa9fe18b6ac35f283c8 + languageName: node + linkType: hard + "@types/node-fetch@npm:^2.6.13": version: 2.6.13 resolution: "@types/node-fetch@npm:2.6.13" @@ -415,6 +1171,20 @@ __metadata: languageName: node linkType: hard +"@types/unist@npm:*, @types/unist@npm:^3.0.0": + version: 3.0.3 + resolution: "@types/unist@npm:3.0.3" + checksum: 96e6453da9e075aaef1dc22482463898198acdc1eeb99b465e65e34303e2ec1e3b1ed4469a9118275ec284dc98019f63c3f5d49422f0e4ac707e5ab90fb3b71a + languageName: node + linkType: hard + +"@types/web-bluetooth@npm:^0.0.21": + version: 0.0.21 + resolution: "@types/web-bluetooth@npm:0.0.21" + checksum: 85a957d52263607d26236b1748771fdcfc0791f2c20373370e6c725410067dee6a5ec425b0e25ec85db2908dd59415079070655fd3277841cbbd3a557d1f7777 + languageName: node + linkType: hard + "@typescript-eslint/eslint-plugin@npm:8.50.0": version: 8.50.0 resolution: "@typescript-eslint/eslint-plugin@npm:8.50.0" @@ -550,6 +1320,233 @@ __metadata: languageName: node linkType: hard +"@ungap/structured-clone@npm:^1.0.0": + version: 1.3.0 + resolution: "@ungap/structured-clone@npm:1.3.0" + checksum: 64ed518f49c2b31f5b50f8570a1e37bde3b62f2460042c50f132430b2d869c4a6586f13aa33a58a4722715b8158c68cae2827389d6752ac54da2893c83e480fc + languageName: node + linkType: hard + +"@vitejs/plugin-vue@npm:^5.2.1": + version: 5.2.4 + resolution: "@vitejs/plugin-vue@npm:5.2.4" + peerDependencies: + vite: ^5.0.0 || ^6.0.0 + vue: ^3.2.25 + checksum: 116a859945401195b0d89b3e2f872bde0d168b498f1438c02a212b53e1131cd4ef5ace65b563092f6b326d7ef726639a9b33d61459b82a634f73f44a3ee0d924 + languageName: node + linkType: hard + +"@vue/compiler-core@npm:3.5.26": + version: 3.5.26 + resolution: "@vue/compiler-core@npm:3.5.26" + dependencies: + "@babel/parser": ^7.28.5 + "@vue/shared": 3.5.26 + entities: ^7.0.0 + estree-walker: ^2.0.2 + source-map-js: ^1.2.1 + checksum: 0b67761b9d04832d04c8702316b6cd7e78fa596c8cd79ff36a2fb02694ea046f515214fc13dc6624a720803ef7e6655c2f30b94d007fd2d6d3f8c23d85fdccb7 + languageName: node + linkType: hard + +"@vue/compiler-dom@npm:3.5.26": + version: 3.5.26 + resolution: "@vue/compiler-dom@npm:3.5.26" + dependencies: + "@vue/compiler-core": 3.5.26 + "@vue/shared": 3.5.26 + checksum: ae5429f036e46cfd03e43b635870ae1cc9bac431cabb27a0e84ca4268741dfcfc6a5b8fa5bb512fdaa10665c395564aaa30883bd5b013058cfff99f73382149f + languageName: node + linkType: hard + +"@vue/compiler-sfc@npm:3.5.26": + version: 3.5.26 + resolution: "@vue/compiler-sfc@npm:3.5.26" + dependencies: + "@babel/parser": ^7.28.5 + "@vue/compiler-core": 3.5.26 + "@vue/compiler-dom": 3.5.26 + "@vue/compiler-ssr": 3.5.26 + "@vue/shared": 3.5.26 + estree-walker: ^2.0.2 + magic-string: ^0.30.21 + postcss: ^8.5.6 + source-map-js: ^1.2.1 + checksum: 26b9052a201ed128b309ba5a4b47c7363c511be7237dc31b5d25b9a03e6da30edcdbd939b1fb9d70f057f2b83c46bdd45be08344cfdc217551b775c5ccec39ac + languageName: node + linkType: hard + +"@vue/compiler-ssr@npm:3.5.26": + version: 3.5.26 + resolution: "@vue/compiler-ssr@npm:3.5.26" + dependencies: + "@vue/compiler-dom": 3.5.26 + "@vue/shared": 3.5.26 + checksum: aaa29604dcfa1c542b33e4e5abb76c28dbda29fdf1ce0493add7cce4fe51c69e6775bca2798904d4ba4e978f68f3b3713ef3af6aee6ba5eb51b5dbc36b4a59e6 + languageName: node + linkType: hard + +"@vue/devtools-api@npm:^7.7.0": + version: 7.7.9 + resolution: "@vue/devtools-api@npm:7.7.9" + dependencies: + "@vue/devtools-kit": ^7.7.9 + checksum: a4dbc911c4a99d401591972c766b56b323f6f01dddfaf1bddc00c5c78aeda7dd7afc6330b5b8245a13ef72f8eceededf0a40416311e229c19014d151ebacd416 + languageName: node + linkType: hard + +"@vue/devtools-kit@npm:^7.7.9": + version: 7.7.9 + resolution: "@vue/devtools-kit@npm:7.7.9" + dependencies: + "@vue/devtools-shared": ^7.7.9 + birpc: ^2.3.0 + hookable: ^5.5.3 + mitt: ^3.0.1 + perfect-debounce: ^1.0.0 + speakingurl: ^14.0.1 + superjson: ^2.2.2 + checksum: 7241630643abaf2fd22f1db2ff47ec594418b6a4c55822c8dc4809754bf22f8f9c095872f8774c2f0a07c77f4663ef5f0bdc88578b42be282c0b4ed184b5fda1 + languageName: node + linkType: hard + +"@vue/devtools-shared@npm:^7.7.9": + version: 7.7.9 + resolution: "@vue/devtools-shared@npm:7.7.9" + dependencies: + rfdc: ^1.4.1 + checksum: 02ea89651174d3763f2b0ea63f1ef7bb8ef8e22479dfc2b6b19cdbe2aab76911b7c2891de149ba985d218f58a06f2de4f4fb4e01d2fa8296e2d7b80d8539eceb + languageName: node + linkType: hard + +"@vue/reactivity@npm:3.5.26": + version: 3.5.26 + resolution: "@vue/reactivity@npm:3.5.26" + dependencies: + "@vue/shared": 3.5.26 + checksum: 9bbad24381d4ca0ad00d5e00354020f8b0464ef4bdbb3aca53fa194b0d48889294d841e03ed2232a342fdca25b3551312626c2b53258300b6d821f7363648245 + languageName: node + linkType: hard + +"@vue/runtime-core@npm:3.5.26": + version: 3.5.26 + resolution: "@vue/runtime-core@npm:3.5.26" + dependencies: + "@vue/reactivity": 3.5.26 + "@vue/shared": 3.5.26 + checksum: 10200dc5fbfd873e3de66792498706f72c61ca821440a21bd3f697eaa7c551166ccb11afab3fab1a118201af9357489287abbae3eac01a5692f6aea889cb9187 + languageName: node + linkType: hard + +"@vue/runtime-dom@npm:3.5.26": + version: 3.5.26 + resolution: "@vue/runtime-dom@npm:3.5.26" + dependencies: + "@vue/reactivity": 3.5.26 + "@vue/runtime-core": 3.5.26 + "@vue/shared": 3.5.26 + csstype: ^3.2.3 + checksum: 9cdcaef49c49ba327219baeada153664b7b82763d08cda102ecf9dbbb4a8dd48792b6b70167a6032f5e838d11bc77df832d4c6df1053e7d3a250387f1b62d3ad + languageName: node + linkType: hard + +"@vue/server-renderer@npm:3.5.26": + version: 3.5.26 + resolution: "@vue/server-renderer@npm:3.5.26" + dependencies: + "@vue/compiler-ssr": 3.5.26 + "@vue/shared": 3.5.26 + peerDependencies: + vue: 3.5.26 + checksum: 171eb7f8f4fc8cb61795d42157f13898b214564bb0c1f6398a863568468b1a7b95e837120c45512259ef8e1b24c9e45787c413b7f45bd18259f2d1c3bbf2b011 + languageName: node + linkType: hard + +"@vue/shared@npm:3.5.26, @vue/shared@npm:^3.5.13": + version: 3.5.26 + resolution: "@vue/shared@npm:3.5.26" + checksum: 0daf1717cd853dfaa0663bf856277c918360acc7921f17966da902f1b05ab8c7385fb5cc06a18f3b861cb84393d235c937836e044679dab989aba719aa930f55 + languageName: node + linkType: hard + +"@vueuse/core@npm:12.8.2, @vueuse/core@npm:^12.4.0": + version: 12.8.2 + resolution: "@vueuse/core@npm:12.8.2" + dependencies: + "@types/web-bluetooth": ^0.0.21 + "@vueuse/metadata": 12.8.2 + "@vueuse/shared": 12.8.2 + vue: ^3.5.13 + checksum: e8749b85c0c1a4e000f65b77e13037e0177cc0c43aa86fab0d90c1c7d9a1a9fb6b1d90461b9b599c51fce5acf201370f0cdf8dc3a5eda5acb2e982227cc2dc23 + languageName: node + linkType: hard + +"@vueuse/integrations@npm:^12.4.0": + version: 12.8.2 + resolution: "@vueuse/integrations@npm:12.8.2" + dependencies: + "@vueuse/core": 12.8.2 + "@vueuse/shared": 12.8.2 + vue: ^3.5.13 + peerDependencies: + async-validator: ^4 + axios: ^1 + change-case: ^5 + drauu: ^0.4 + focus-trap: ^7 + fuse.js: ^7 + idb-keyval: ^6 + jwt-decode: ^4 + nprogress: ^0.2 + qrcode: ^1.5 + sortablejs: ^1 + universal-cookie: ^7 + peerDependenciesMeta: + async-validator: + optional: true + axios: + optional: true + change-case: + optional: true + drauu: + optional: true + focus-trap: + optional: true + fuse.js: + optional: true + idb-keyval: + optional: true + jwt-decode: + optional: true + nprogress: + optional: true + qrcode: + optional: true + sortablejs: + optional: true + universal-cookie: + optional: true + checksum: 6c7a82ba00421d220e9e12a55bbb223d291648768629c9f88d48bcc0b4354b9d62e2f7a4efb83e6fd949c07071d9f16c2f1debbb966a4297a5d638199fc1da62 + languageName: node + linkType: hard + +"@vueuse/metadata@npm:12.8.2": + version: 12.8.2 + resolution: "@vueuse/metadata@npm:12.8.2" + checksum: 4fae302c2eb24970fd1946a0f25c81a99a3bbd1d12a43670ef7c2cbfe921a68ef98b1511a055cae8a37f2cf1ce625b88f726572b55dd15689e5201ad7714c667 + languageName: node + linkType: hard + +"@vueuse/shared@npm:12.8.2": + version: 12.8.2 + resolution: "@vueuse/shared@npm:12.8.2" + dependencies: + vue: ^3.5.13 + checksum: 41400fe8a375782e9a64f262ee7a900fd28de40767f63dad1624eff4a109eea14e88766f6fae5fe72c9f6ef84ea7600447d94ad37bec97ee34cc62560ad90dd7 + languageName: node + linkType: hard + "abbrev@npm:^4.0.0": version: 4.0.0 resolution: "abbrev@npm:4.0.0" @@ -606,6 +1603,28 @@ __metadata: languageName: node linkType: hard +"algoliasearch@npm:^5.14.2": + version: 5.46.3 + resolution: "algoliasearch@npm:5.46.3" + dependencies: + "@algolia/abtesting": 1.12.3 + "@algolia/client-abtesting": 5.46.3 + "@algolia/client-analytics": 5.46.3 + "@algolia/client-common": 5.46.3 + "@algolia/client-insights": 5.46.3 + "@algolia/client-personalization": 5.46.3 + "@algolia/client-query-suggestions": 5.46.3 + "@algolia/client-search": 5.46.3 + "@algolia/ingestion": 1.46.3 + "@algolia/monitoring": 1.46.3 + "@algolia/recommend": 5.46.3 + "@algolia/requester-browser-xhr": 5.46.3 + "@algolia/requester-fetch": 5.46.3 + "@algolia/requester-node-http": 5.46.3 + checksum: 0c405be3213d81b0b50d6fe3d827462ab6e7a2d744ae6d6b843b4b538a7cb62a30a77ae5d1e5b22b9ff66650c5fa6f9295a7907e2fd3f0ed00125e55e8718e87 + languageName: node + linkType: hard + "ansi-align@npm:^3.0.1": version: 3.0.1 resolution: "ansi-align@npm:3.0.1" @@ -771,6 +1790,13 @@ __metadata: languageName: node linkType: hard +"birpc@npm:^2.3.0": + version: 2.9.0 + resolution: "birpc@npm:2.9.0" + checksum: 499cba9d33ae6749c199b4dc1fc30faeb72aabd41d5c75d73d1d415691f65f0b613d787f21aa2369607a555a5e09c55e437e02b14bc6bad67bb657f8ae58f3dd + languageName: node + linkType: hard + "boxen@npm:^8.0.1": version: 8.0.1 resolution: "boxen@npm:8.0.1" @@ -865,6 +1891,13 @@ __metadata: languageName: node linkType: hard +"ccount@npm:^2.0.0": + version: 2.0.1 + resolution: "ccount@npm:2.0.1" + checksum: 48193dada54c9e260e0acf57fc16171a225305548f9ad20d5471e0f7a8c026aedd8747091dccb0d900cde7df4e4ddbd235df0d8de4a64c71b12f0d3303eeafd4 + languageName: node + linkType: hard + "chalk@npm:^4.0.0": version: 4.1.2 resolution: "chalk@npm:4.1.2" @@ -882,6 +1915,20 @@ __metadata: languageName: node linkType: hard +"character-entities-html4@npm:^2.0.0": + version: 2.1.0 + resolution: "character-entities-html4@npm:2.1.0" + checksum: 7034aa7c7fa90309667f6dd50499c8a760c3d3a6fb159adb4e0bada0107d194551cdbad0714302f62d06ce4ed68565c8c2e15fdef2e8f8764eb63fa92b34b11d + languageName: node + linkType: hard + +"character-entities-legacy@npm:^3.0.0": + version: 3.0.0 + resolution: "character-entities-legacy@npm:3.0.0" + checksum: 7582af055cb488b626d364b7d7a4e46b06abd526fb63c0e4eb35bcb9c9799cc4f76b39f34fdccef2d1174ac95e53e9ab355aae83227c1a2505877893fce77731 + languageName: node + linkType: hard + "chownr@npm:^3.0.0": version: 3.0.0 resolution: "chownr@npm:3.0.0" @@ -921,6 +1968,13 @@ __metadata: languageName: node linkType: hard +"comma-separated-tokens@npm:^2.0.0": + version: 2.0.3 + resolution: "comma-separated-tokens@npm:2.0.3" + checksum: e3bf9e0332a5c45f49b90e79bcdb4a7a85f28d6a6f0876a94f1bb9b2bfbdbbb9292aac50e1e742d8c0db1e62a0229a106f57917e2d067fca951d81737651700d + languageName: node + linkType: hard + "concat-map@npm:0.0.1": version: 0.0.1 resolution: "concat-map@npm:0.0.1" @@ -928,6 +1982,15 @@ __metadata: languageName: node linkType: hard +"copy-anything@npm:^4": + version: 4.0.5 + resolution: "copy-anything@npm:4.0.5" + dependencies: + is-what: ^5.2.0 + checksum: e2ccac1a26a119c4a8fd68503c1ba1b2065d75793ef63eb492092d71afaf5ac492802a57572e3d88d9e219a92f8b68acb121b344e3560e9f0f3a99ef973133fe + languageName: node + linkType: hard + "cross-spawn@npm:^7.0.6": version: 7.0.6 resolution: "cross-spawn@npm:7.0.6" @@ -939,6 +2002,13 @@ __metadata: languageName: node linkType: hard +"csstype@npm:^3.2.3": + version: 3.2.3 + resolution: "csstype@npm:3.2.3" + checksum: cb882521b3398958a1ce6ca98c011aec0bde1c77ecaf8a1dd4db3b112a189939beae3b1308243b2fe50fc27eb3edeb0f73a5a4d91d928765dc6d5ecc7bda92ee + languageName: node + linkType: hard + "debug@npm:4, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4": version: 4.4.3 resolution: "debug@npm:4.4.3" @@ -965,6 +2035,22 @@ __metadata: languageName: node linkType: hard +"dequal@npm:^2.0.0": + version: 2.0.3 + resolution: "dequal@npm:2.0.3" + checksum: 8679b850e1a3d0ebbc46ee780d5df7b478c23f335887464023a631d1b9af051ad4a6595a44220f9ff8ff95a8ddccf019b5ad778a976fd7bbf77383d36f412f90 + languageName: node + linkType: hard + +"devlop@npm:^1.0.0": + version: 1.1.0 + resolution: "devlop@npm:1.1.0" + dependencies: + dequal: ^2.0.0 + checksum: d2ff650bac0bb6ef08c48f3ba98640bb5fec5cce81e9957eb620408d1bab1204d382a45b785c6b3314dc867bb0684936b84c6867820da6db97cbb5d3c15dd185 + languageName: node + linkType: hard + "dunder-proto@npm:^1.0.1": version: 1.0.1 resolution: "dunder-proto@npm:1.0.1" @@ -976,6 +2062,13 @@ __metadata: languageName: node linkType: hard +"emoji-regex-xs@npm:^1.0.0": + version: 1.0.0 + resolution: "emoji-regex-xs@npm:1.0.0" + checksum: c33be159da769836f83281f2802d90169093ebf3c2c1643d6801d891c53beac5ef785fd8279f9b02fa6dc6c47c367818e076949f1e13bd1b3f921b416de4cbea + languageName: node + linkType: hard + "emoji-regex@npm:^10.3.0": version: 10.6.0 resolution: "emoji-regex@npm:10.6.0" @@ -1008,6 +2101,13 @@ __metadata: languageName: node linkType: hard +"entities@npm:^7.0.0": + version: 7.0.0 + resolution: "entities@npm:7.0.0" + checksum: 9305101f6d34fa6d255afb9ccd35f27cd67e0eaf1299374d20cba6e7c4bb26adae3ab879d33b524db74a2bbf57b9416fe04698a60bf2d9d1c1c811e16e205270 + languageName: node + linkType: hard + "env-paths@npm:^2.2.0": version: 2.2.1 resolution: "env-paths@npm:2.2.1" @@ -1057,6 +2157,86 @@ __metadata: languageName: node linkType: hard +"esbuild@npm:^0.21.3": + version: 0.21.5 + resolution: "esbuild@npm:0.21.5" + dependencies: + "@esbuild/aix-ppc64": 0.21.5 + "@esbuild/android-arm": 0.21.5 + "@esbuild/android-arm64": 0.21.5 + "@esbuild/android-x64": 0.21.5 + "@esbuild/darwin-arm64": 0.21.5 + "@esbuild/darwin-x64": 0.21.5 + "@esbuild/freebsd-arm64": 0.21.5 + "@esbuild/freebsd-x64": 0.21.5 + "@esbuild/linux-arm": 0.21.5 + "@esbuild/linux-arm64": 0.21.5 + "@esbuild/linux-ia32": 0.21.5 + "@esbuild/linux-loong64": 0.21.5 + "@esbuild/linux-mips64el": 0.21.5 + "@esbuild/linux-ppc64": 0.21.5 + "@esbuild/linux-riscv64": 0.21.5 + "@esbuild/linux-s390x": 0.21.5 + "@esbuild/linux-x64": 0.21.5 + "@esbuild/netbsd-x64": 0.21.5 + "@esbuild/openbsd-x64": 0.21.5 + "@esbuild/sunos-x64": 0.21.5 + "@esbuild/win32-arm64": 0.21.5 + "@esbuild/win32-ia32": 0.21.5 + "@esbuild/win32-x64": 0.21.5 + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 2911c7b50b23a9df59a7d6d4cdd3a4f85855787f374dce751148dbb13305e0ce7e880dde1608c2ab7a927fc6cec3587b80995f7fc87a64b455f8b70b55fd8ec1 + languageName: node + linkType: hard + "escape-string-regexp@npm:^4.0.0": version: 4.0.0 resolution: "escape-string-regexp@npm:4.0.0" @@ -1196,6 +2376,13 @@ __metadata: languageName: node linkType: hard +"estree-walker@npm:^2.0.2": + version: 2.0.2 + resolution: "estree-walker@npm:2.0.2" + checksum: 6151e6f9828abe2259e57f5fd3761335bb0d2ebd76dc1a01048ccee22fabcfef3c0859300f6d83ff0d1927849368775ec5a6d265dde2f6de5a1be1721cd94efc + languageName: node + linkType: hard + "esutils@npm:^2.0.2": version: 2.0.3 resolution: "esutils@npm:2.0.3" @@ -1311,6 +2498,15 @@ __metadata: languageName: node linkType: hard +"focus-trap@npm:^7.6.4": + version: 7.8.0 + resolution: "focus-trap@npm:7.8.0" + dependencies: + tabbable: ^6.4.0 + checksum: 1eccd8332b694ba8c4d0a97101471004a23634a9b3650311460064856b8795136a6aec276c02f1ec01dd23e27848a0db69bec087e347403e23ee55b52d6bbc7e + languageName: node + linkType: hard + "form-data@npm:^4.0.0, form-data@npm:^4.0.4": version: 4.0.5 resolution: "form-data@npm:4.0.5" @@ -1354,6 +2550,16 @@ __metadata: languageName: node linkType: hard +"fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": + version: 2.3.3 + resolution: "fsevents@npm:2.3.3" + dependencies: + node-gyp: latest + checksum: 11e6ea6fea15e42461fc55b4b0e4a0a3c654faa567f1877dbd353f39156f69def97a69936d1746619d656c4b93de2238bf731f6085a03a50cabf287c9d024317 + conditions: os=darwin + languageName: node + linkType: hard + "fsevents@patch:fsevents@2.3.2#~builtin": version: 2.3.2 resolution: "fsevents@patch:fsevents@npm%3A2.3.2#~builtin::version=2.3.2&hash=df0bf1" @@ -1363,6 +2569,15 @@ __metadata: languageName: node linkType: hard +"fsevents@patch:fsevents@~2.3.2#~builtin, fsevents@patch:fsevents@~2.3.3#~builtin": + version: 2.3.3 + resolution: "fsevents@patch:fsevents@npm%3A2.3.3#~builtin::version=2.3.3&hash=df0bf1" + dependencies: + node-gyp: latest + conditions: os=darwin + languageName: node + linkType: hard + "function-bind@npm:^1.1.2": version: 1.1.2 resolution: "function-bind@npm:1.1.2" @@ -1495,6 +2710,41 @@ __metadata: languageName: node linkType: hard +"hast-util-to-html@npm:^9.0.4": + version: 9.0.5 + resolution: "hast-util-to-html@npm:9.0.5" + dependencies: + "@types/hast": ^3.0.0 + "@types/unist": ^3.0.0 + ccount: ^2.0.0 + comma-separated-tokens: ^2.0.0 + hast-util-whitespace: ^3.0.0 + html-void-elements: ^3.0.0 + mdast-util-to-hast: ^13.0.0 + property-information: ^7.0.0 + space-separated-tokens: ^2.0.0 + stringify-entities: ^4.0.0 + zwitch: ^2.0.4 + checksum: 1ebd013ad340cf646ea944100427917747f69543800e79b2186521dc29c205b4fe75d8062f3eddedf6d66f6180ca06fe127b9e53ff15a8f3579e36637ca43e16 + languageName: node + linkType: hard + +"hast-util-whitespace@npm:^3.0.0": + version: 3.0.0 + resolution: "hast-util-whitespace@npm:3.0.0" + dependencies: + "@types/hast": ^3.0.0 + checksum: 41d93ccce218ba935dc3c12acdf586193c35069489c8c8f50c2aa824c00dec94a3c78b03d1db40fa75381942a189161922e4b7bca700b3a2cc779634c351a1e4 + languageName: node + linkType: hard + +"hookable@npm:^5.5.3": + version: 5.5.3 + resolution: "hookable@npm:5.5.3" + checksum: df659977888398649b6ef8c4470719e7e8384a1d939a6587e332e86fd55b3881806e2f8aaebaabdb4f218f74b83b98f2110e143df225e16d62a39dc271e7e288 + languageName: node + linkType: hard + "hpagent@npm:^1.2.0": version: 1.2.0 resolution: "hpagent@npm:1.2.0" @@ -1502,6 +2752,13 @@ __metadata: languageName: node linkType: hard +"html-void-elements@npm:^3.0.0": + version: 3.0.0 + resolution: "html-void-elements@npm:3.0.0" + checksum: 59be397525465a7489028afa064c55763d9cccd1d7d9f630cca47137317f0e897a9ca26cef7e745e7cff1abc44260cfa407742b243a54261dfacd42230e94fce + languageName: node + linkType: hard + "http-cache-semantics@npm:^4.1.1": version: 4.2.0 resolution: "http-cache-semantics@npm:4.2.0" @@ -1606,6 +2863,13 @@ __metadata: languageName: node linkType: hard +"is-what@npm:^5.2.0": + version: 5.5.0 + resolution: "is-what@npm:5.5.0" + checksum: 8416464cf65a2b57512b18437f672e035e24fcda3af7de47a0f5183c2c46f30ce6e57b6ca35d07a81a04d829357a97ef5e5aa45bf963a5c2d7bf869f9ceebc56 + languageName: node + linkType: hard + "isexe@npm:^2.0.0": version: 2.0.0 resolution: "isexe@npm:2.0.0" @@ -1765,6 +3029,15 @@ __metadata: languageName: node linkType: hard +"magic-string@npm:^0.30.21": + version: 0.30.21 + resolution: "magic-string@npm:0.30.21" + dependencies: + "@jridgewell/sourcemap-codec": ^1.5.5 + checksum: 4ff76a4e8d439431cf49f039658751ed351962d044e5955adc257489569bd676019c906b631f86319217689d04815d7d064ee3ff08ab82ae65b7655a7e82a414 + languageName: node + linkType: hard + "make-fetch-happen@npm:^15.0.0": version: 15.0.3 resolution: "make-fetch-happen@npm:15.0.3" @@ -1784,6 +3057,13 @@ __metadata: languageName: node linkType: hard +"mark.js@npm:8.11.1": + version: 8.11.1 + resolution: "mark.js@npm:8.11.1" + checksum: aa6b9ae1c67245348d5b7abd253ef2acd6bb05c6be358d7d192416d964e42665fc10e0e865591c6f93ab9b57e8da1f23c23216e8ebddb580905ea7a0c0df15d4 + languageName: node + linkType: hard + "math-intrinsics@npm:^1.1.0": version: 1.1.0 resolution: "math-intrinsics@npm:1.1.0" @@ -1791,6 +3071,65 @@ __metadata: languageName: node linkType: hard +"mdast-util-to-hast@npm:^13.0.0": + version: 13.2.1 + resolution: "mdast-util-to-hast@npm:13.2.1" + dependencies: + "@types/hast": ^3.0.0 + "@types/mdast": ^4.0.0 + "@ungap/structured-clone": ^1.0.0 + devlop: ^1.0.0 + micromark-util-sanitize-uri: ^2.0.0 + trim-lines: ^3.0.0 + unist-util-position: ^5.0.0 + unist-util-visit: ^5.0.0 + vfile: ^6.0.0 + checksum: 20537df653be3653c3c6ea4be09ea1f67ca2f5e6afea027fcc3cde531656dc669a5e733d34a95b08b3ee71ab164c7b24352c8212891f723ddcec74d5a046bfd6 + languageName: node + linkType: hard + +"micromark-util-character@npm:^2.0.0": + version: 2.1.1 + resolution: "micromark-util-character@npm:2.1.1" + dependencies: + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: e9e409efe4f2596acd44587e8591b722bfc041c1577e8fe0d9c007a4776fb800f9b3637a22862ad2ba9489f4bdf72bb547fce5767dbbfe0a5e6760e2a21c6495 + languageName: node + linkType: hard + +"micromark-util-encode@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-encode@npm:2.0.1" + checksum: be890b98e78dd0cdd953a313f4148c4692cc2fb05533e56fef5f421287d3c08feee38ca679f318e740530791fc251bfe8c80efa926fcceb4419b269c9343d226 + languageName: node + linkType: hard + +"micromark-util-sanitize-uri@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-sanitize-uri@npm:2.0.1" + dependencies: + micromark-util-character: ^2.0.0 + micromark-util-encode: ^2.0.0 + micromark-util-symbol: ^2.0.0 + checksum: d01517840c17de67aaa0b0f03bfe05fac8a41d99723cd8ce16c62f6810e99cd3695364a34c335485018e5e2c00e69031744630a1b85c6868aa2f2ca1b36daa2f + languageName: node + linkType: hard + +"micromark-util-symbol@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-symbol@npm:2.0.1" + checksum: fb7346950550bc85a55793dda94a8b3cb3abc068dbd7570d1162db7aee803411d06c0a5de4ae59cd945f46143bdeadd4bba02a02248fa0d18cc577babaa00044 + languageName: node + linkType: hard + +"micromark-util-types@npm:^2.0.0": + version: 2.0.2 + resolution: "micromark-util-types@npm:2.0.2" + checksum: 884f7974839e4bc6d2bd662e57c973a9164fd5c0d8fe16cddf07472b86a7e6726747c00674952c0321d17685d700cd3295e9f58a842a53acdf6c6d55ab051aab + languageName: node + linkType: hard + "micromatch@npm:^4.0.8": version: 4.0.8 resolution: "micromatch@npm:4.0.8" @@ -1911,6 +3250,13 @@ __metadata: languageName: node linkType: hard +"minisearch@npm:^7.1.1": + version: 7.2.0 + resolution: "minisearch@npm:7.2.0" + checksum: 556ff91d6edf88b955d3b455e753e620320f0f4ab6fc81ce12e7bbec6721db9cf799933c3e4aa4acffc68c4216459bb2d22c09918a8b20bd43424b93a2f11040 + languageName: node + linkType: hard + "minizlib@npm:^3.0.1, minizlib@npm:^3.1.0": version: 3.1.0 resolution: "minizlib@npm:3.1.0" @@ -1920,6 +3266,13 @@ __metadata: languageName: node linkType: hard +"mitt@npm:^3.0.1": + version: 3.0.1 + resolution: "mitt@npm:3.0.1" + checksum: b55a489ac9c2949ab166b7f060601d3b6d893a852515ae9eca4e11df01c013876df777ea109317622b5c1c60e8aae252558e33c8c94e14124db38f64a39614b1 + languageName: node + linkType: hard + "ms@npm:^2.1.3": version: 2.1.3 resolution: "ms@npm:2.1.3" @@ -1927,6 +3280,15 @@ __metadata: languageName: node linkType: hard +"nanoid@npm:^3.3.11": + version: 3.3.11 + resolution: "nanoid@npm:3.3.11" + bin: + nanoid: bin/nanoid.cjs + checksum: 3be20d8866a57a6b6d218e82549711c8352ed969f9ab3c45379da28f405363ad4c9aeb0b39e9abc101a529ca65a72ff9502b00bf74a912c4b64a9d62dfd26c29 + languageName: node + linkType: hard + "natural-compare@npm:^1.4.0": version: 1.4.0 resolution: "natural-compare@npm:1.4.0" @@ -2002,6 +3364,17 @@ __metadata: languageName: node linkType: hard +"oniguruma-to-es@npm:^3.1.0": + version: 3.1.1 + resolution: "oniguruma-to-es@npm:3.1.1" + dependencies: + emoji-regex-xs: ^1.0.0 + regex: ^6.0.1 + regex-recursion: ^6.0.2 + checksum: 2bd1b227d199292f5ad9436b24dbe44bbaf1b516c3e2447e97f6d60ff0d35b9d0b83559ea187ae73b439818d3bfe9bde3820f219e9757a8048f21b692986d375 + languageName: node + linkType: hard + "openid-client@npm:^6.1.3": version: 6.8.1 resolution: "openid-client@npm:6.8.1" @@ -2095,6 +3468,20 @@ __metadata: languageName: node linkType: hard +"perfect-debounce@npm:^1.0.0": + version: 1.0.0 + resolution: "perfect-debounce@npm:1.0.0" + checksum: 220343acf52976947958fef3599849471605316e924fe19c633ae2772576298e9d38f02cefa8db46f06607505ce7b232cbb35c9bfd477bd0329bd0a2ce37c594 + languageName: node + linkType: hard + +"picocolors@npm:^1.1.1": + version: 1.1.1 + resolution: "picocolors@npm:1.1.1" + checksum: e1cf46bf84886c79055fdfa9dcb3e4711ad259949e3565154b004b260cd356c5d54b31a1437ce9782624bf766272fe6b0154f5f0c744fb7af5d454d2b60db045 + languageName: node + linkType: hard + "picomatch@npm:^2.3.1": version: 2.3.1 resolution: "picomatch@npm:2.3.1" @@ -2133,6 +3520,24 @@ __metadata: languageName: node linkType: hard +"postcss@npm:^8.4.43, postcss@npm:^8.5.6": + version: 8.5.6 + resolution: "postcss@npm:8.5.6" + dependencies: + nanoid: ^3.3.11 + picocolors: ^1.1.1 + source-map-js: ^1.2.1 + checksum: 20f3b5d673ffeec2b28d65436756d31ee33f65b0a8bedb3d32f556fbd5973be38c3a7fb5b959a5236c60a5db7b91b0a6b14ffaac0d717dce1b903b964ee1c1bb + languageName: node + linkType: hard + +"preact@npm:^10.0.0": + version: 10.28.2 + resolution: "preact@npm:10.28.2" + checksum: 5f65087ab00ab270ca514e43b5aaba79101da92eb2ea3199a2b54776a159032b0ee4749a529f624ccc7d456c71b56ddeed2cbcd92cd1e6a3b4dcecfaa7cacf36 + languageName: node + linkType: hard + "prelude-ls@npm:^1.2.1": version: 1.2.1 resolution: "prelude-ls@npm:1.2.1" @@ -2166,6 +3571,13 @@ __metadata: languageName: node linkType: hard +"property-information@npm:^7.0.0": + version: 7.1.0 + resolution: "property-information@npm:7.1.0" + checksum: 3875161d204bac89d75181f6d3ebc3ecaeb2699b4e2ecfcf5452201d7cdd275168c6742d7ff8cec5ab0c342fae72369ac705e1f8e9680a9acd911692e80dfb88 + languageName: node + linkType: hard + "pump@npm:^3.0.0": version: 3.0.3 resolution: "pump@npm:3.0.3" @@ -2183,6 +3595,31 @@ __metadata: languageName: node linkType: hard +"regex-recursion@npm:^6.0.2": + version: 6.0.2 + resolution: "regex-recursion@npm:6.0.2" + dependencies: + regex-utilities: ^2.3.0 + checksum: 29913751ee2e41d3d66c957136ba386046f5e9f780f4be482ad3b64b04258bbb2cebe87f8762c5247eb8c9fa46a5ecf18aba5d888f7def73ac8dea49165193d4 + languageName: node + linkType: hard + +"regex-utilities@npm:^2.3.0": + version: 2.3.0 + resolution: "regex-utilities@npm:2.3.0" + checksum: 41408777df45cefe1b276281030213235aa1143809c4c10eb5573d2cc27ff2c4aa746c6f4d4c235e3d2f4830eff76b28906ce82fbe72895beca8e15204c2da51 + languageName: node + linkType: hard + +"regex@npm:^6.0.1": + version: 6.1.0 + resolution: "regex@npm:6.1.0" + dependencies: + regex-utilities: ^2.3.0 + checksum: 8ea9656dbafe8f324f605653b75ce954e9d695b4bb54dbf1fea20d8095b7530f4552bc3e7890b58542441ee2287f64a35d512601fb3417ba542a7a20f48fb855 + languageName: node + linkType: hard + "require-from-string@npm:^2.0.2": version: 2.0.2 resolution: "require-from-string@npm:2.0.2" @@ -2211,6 +3648,13 @@ __metadata: languageName: node linkType: hard +"rfdc@npm:^1.4.1": + version: 1.4.1 + resolution: "rfdc@npm:1.4.1" + checksum: 3b05bd55062c1d78aaabfcea43840cdf7e12099968f368e9a4c3936beb744adb41cbdb315eac6d4d8c6623005d6f87fdf16d8a10e1ff3722e84afea7281c8d13 + languageName: node + linkType: hard + "rhdh-e2e-test-utils@workspace:.": version: 0.0.0-use.local resolution: "rhdh-e2e-test-utils@workspace:." @@ -2236,12 +3680,103 @@ __metadata: prettier: ^3.7.4 typescript: ^5.9.3 typescript-eslint: ^8.48.1 + vitepress: ^1.5.0 zx: ^8.8.5 peerDependencies: "@playwright/test": ^1.57.0 languageName: unknown linkType: soft +"rollup@npm:^4.20.0": + version: 4.55.1 + resolution: "rollup@npm:4.55.1" + dependencies: + "@rollup/rollup-android-arm-eabi": 4.55.1 + "@rollup/rollup-android-arm64": 4.55.1 + "@rollup/rollup-darwin-arm64": 4.55.1 + "@rollup/rollup-darwin-x64": 4.55.1 + "@rollup/rollup-freebsd-arm64": 4.55.1 + "@rollup/rollup-freebsd-x64": 4.55.1 + "@rollup/rollup-linux-arm-gnueabihf": 4.55.1 + "@rollup/rollup-linux-arm-musleabihf": 4.55.1 + "@rollup/rollup-linux-arm64-gnu": 4.55.1 + "@rollup/rollup-linux-arm64-musl": 4.55.1 + "@rollup/rollup-linux-loong64-gnu": 4.55.1 + "@rollup/rollup-linux-loong64-musl": 4.55.1 + "@rollup/rollup-linux-ppc64-gnu": 4.55.1 + "@rollup/rollup-linux-ppc64-musl": 4.55.1 + "@rollup/rollup-linux-riscv64-gnu": 4.55.1 + "@rollup/rollup-linux-riscv64-musl": 4.55.1 + "@rollup/rollup-linux-s390x-gnu": 4.55.1 + "@rollup/rollup-linux-x64-gnu": 4.55.1 + "@rollup/rollup-linux-x64-musl": 4.55.1 + "@rollup/rollup-openbsd-x64": 4.55.1 + "@rollup/rollup-openharmony-arm64": 4.55.1 + "@rollup/rollup-win32-arm64-msvc": 4.55.1 + "@rollup/rollup-win32-ia32-msvc": 4.55.1 + "@rollup/rollup-win32-x64-gnu": 4.55.1 + "@rollup/rollup-win32-x64-msvc": 4.55.1 + "@types/estree": 1.0.8 + fsevents: ~2.3.2 + dependenciesMeta: + "@rollup/rollup-android-arm-eabi": + optional: true + "@rollup/rollup-android-arm64": + optional: true + "@rollup/rollup-darwin-arm64": + optional: true + "@rollup/rollup-darwin-x64": + optional: true + "@rollup/rollup-freebsd-arm64": + optional: true + "@rollup/rollup-freebsd-x64": + optional: true + "@rollup/rollup-linux-arm-gnueabihf": + optional: true + "@rollup/rollup-linux-arm-musleabihf": + optional: true + "@rollup/rollup-linux-arm64-gnu": + optional: true + "@rollup/rollup-linux-arm64-musl": + optional: true + "@rollup/rollup-linux-loong64-gnu": + optional: true + "@rollup/rollup-linux-loong64-musl": + optional: true + "@rollup/rollup-linux-ppc64-gnu": + optional: true + "@rollup/rollup-linux-ppc64-musl": + optional: true + "@rollup/rollup-linux-riscv64-gnu": + optional: true + "@rollup/rollup-linux-riscv64-musl": + optional: true + "@rollup/rollup-linux-s390x-gnu": + optional: true + "@rollup/rollup-linux-x64-gnu": + optional: true + "@rollup/rollup-linux-x64-musl": + optional: true + "@rollup/rollup-openbsd-x64": + optional: true + "@rollup/rollup-openharmony-arm64": + optional: true + "@rollup/rollup-win32-arm64-msvc": + optional: true + "@rollup/rollup-win32-ia32-msvc": + optional: true + "@rollup/rollup-win32-x64-gnu": + optional: true + "@rollup/rollup-win32-x64-msvc": + optional: true + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: fd5374cd7e6046404d59d64e1346821fee0900bc9cac021078bfdd342fc54305defba882fb53e6543b5c17ce4573b30fb45ee28b9a4122548358004efdc550d2 + languageName: node + linkType: hard + "safer-buffer@npm:>= 2.1.2 < 3.0.0": version: 2.1.2 resolution: "safer-buffer@npm:2.1.2" @@ -2283,6 +3818,22 @@ __metadata: languageName: node linkType: hard +"shiki@npm:^2.1.0": + version: 2.5.0 + resolution: "shiki@npm:2.5.0" + dependencies: + "@shikijs/core": 2.5.0 + "@shikijs/engine-javascript": 2.5.0 + "@shikijs/engine-oniguruma": 2.5.0 + "@shikijs/langs": 2.5.0 + "@shikijs/themes": 2.5.0 + "@shikijs/types": 2.5.0 + "@shikijs/vscode-textmate": ^10.0.2 + "@types/hast": ^3.0.4 + checksum: 53d861dd532a3655b057b920bb1218150f524294c76b1180e68cdb777829686a42ce41cc46331a2780e30ef68cec40e251360d50f75dabf5409f1bfcdc6b9ef9 + languageName: node + linkType: hard + "smart-buffer@npm:^4.2.0": version: 4.2.0 resolution: "smart-buffer@npm:4.2.0" @@ -2311,6 +3862,27 @@ __metadata: languageName: node linkType: hard +"source-map-js@npm:^1.2.1": + version: 1.2.1 + resolution: "source-map-js@npm:1.2.1" + checksum: 4eb0cd997cdf228bc253bcaff9340afeb706176e64868ecd20efbe6efea931465f43955612346d6b7318789e5265bdc419bc7669c1cebe3db0eb255f57efa76b + languageName: node + linkType: hard + +"space-separated-tokens@npm:^2.0.0": + version: 2.0.2 + resolution: "space-separated-tokens@npm:2.0.2" + checksum: 202e97d7ca1ba0758a0aa4fe226ff98142073bcceeff2da3aad037968878552c3bbce3b3231970025375bbba5aee00c5b8206eda408da837ab2dc9c0f26be990 + languageName: node + linkType: hard + +"speakingurl@npm:^14.0.1": + version: 14.0.1 + resolution: "speakingurl@npm:14.0.1" + checksum: 5c7fb81d9b4cbda31f462f424cc2d59d9d07ca07e86f9f4e7b1c6325307646f9b82297891ce7f9e75b4bccf20ac436758e721506461b5cd6e5561e89186aa67b + languageName: node + linkType: hard + "ssri@npm:^13.0.0": version: 13.0.0 resolution: "ssri@npm:13.0.0" @@ -2360,6 +3932,16 @@ __metadata: languageName: node linkType: hard +"stringify-entities@npm:^4.0.0": + version: 4.0.4 + resolution: "stringify-entities@npm:4.0.4" + dependencies: + character-entities-html4: ^2.0.0 + character-entities-legacy: ^3.0.0 + checksum: ac1344ef211eacf6cf0a0a8feaf96f9c36083835b406560d2c6ff5a87406a41b13f2f0b4c570a3b391f465121c4fd6822b863ffb197e8c0601a64097862cc5b5 + languageName: node + linkType: hard + "strip-ansi@npm:^6.0.1": version: 6.0.1 resolution: "strip-ansi@npm:6.0.1" @@ -2385,6 +3967,15 @@ __metadata: languageName: node linkType: hard +"superjson@npm:^2.2.2": + version: 2.2.6 + resolution: "superjson@npm:2.2.6" + dependencies: + copy-anything: ^4 + checksum: d860577809502d058144d1e5fcd383571b4cf0466cab7d7b6fa1675fff598af53148bacd7b3b0075fba0997ed1f05393bde95b3a3727f7708513396b1d88a8b6 + languageName: node + linkType: hard + "supports-color@npm:^7.1.0": version: 7.2.0 resolution: "supports-color@npm:7.2.0" @@ -2394,6 +3985,13 @@ __metadata: languageName: node linkType: hard +"tabbable@npm:^6.4.0": + version: 6.4.0 + resolution: "tabbable@npm:6.4.0" + checksum: 7084cba269ebbc7dcdeed5aca7f90c0a0fb59a295dd1e83703ab89cca5e6c53b78d02020e3d1065481984cd64bba7dd1ea3c0a48e92fdba83d586e6e86d62a74 + languageName: node + linkType: hard + "tar-fs@npm:^3.0.9": version: 3.1.1 resolution: "tar-fs@npm:3.1.1" @@ -2477,6 +4075,13 @@ __metadata: languageName: node linkType: hard +"trim-lines@npm:^3.0.0": + version: 3.0.1 + resolution: "trim-lines@npm:3.0.1" + checksum: e241da104682a0e0d807222cc1496b92e716af4db7a002f4aeff33ae6a0024fef93165d49eab11aa07c71e1347c42d46563f91dfaa4d3fb945aa535cdead53ed + languageName: node + linkType: hard + "ts-api-utils@npm:^2.1.0": version: 2.1.0 resolution: "ts-api-utils@npm:2.1.0" @@ -2569,6 +4174,54 @@ __metadata: languageName: node linkType: hard +"unist-util-is@npm:^6.0.0": + version: 6.0.1 + resolution: "unist-util-is@npm:6.0.1" + dependencies: + "@types/unist": ^3.0.0 + checksum: e57733e1766b55c9a873a42d2f34daa211580788b1bba26af2fc22e48e147bdcff0f9a752ed2a19238864823735fbbe27a1804d6a5a22b182c23aa0191e41c12 + languageName: node + linkType: hard + +"unist-util-position@npm:^5.0.0": + version: 5.0.0 + resolution: "unist-util-position@npm:5.0.0" + dependencies: + "@types/unist": ^3.0.0 + checksum: f89b27989b19f07878de9579cd8db2aa0194c8360db69e2c99bd2124a480d79c08f04b73a64daf01a8fb3af7cba65ff4b45a0b978ca243226084ad5f5d441dde + languageName: node + linkType: hard + +"unist-util-stringify-position@npm:^4.0.0": + version: 4.0.0 + resolution: "unist-util-stringify-position@npm:4.0.0" + dependencies: + "@types/unist": ^3.0.0 + checksum: e2e7aee4b92ddb64d314b4ac89eef7a46e4c829cbd3ee4aee516d100772b490eb6b4974f653ba0717a0071ca6ea0770bf22b0a2ea62c65fcba1d071285e96324 + languageName: node + linkType: hard + +"unist-util-visit-parents@npm:^6.0.0": + version: 6.0.2 + resolution: "unist-util-visit-parents@npm:6.0.2" + dependencies: + "@types/unist": ^3.0.0 + unist-util-is: ^6.0.0 + checksum: cf28578a6f0b81877965e261fe82460f83b8c3a9cab3b2080c046b215f3223c6195b01064256619ca3411a1930face93a1a2a72d34d8716e684d6cd59f53cd9a + languageName: node + linkType: hard + +"unist-util-visit@npm:^5.0.0": + version: 5.0.0 + resolution: "unist-util-visit@npm:5.0.0" + dependencies: + "@types/unist": ^3.0.0 + unist-util-is: ^6.0.0 + unist-util-visit-parents: ^6.0.0 + checksum: 9ec42e618e7e5d0202f3c191cd30791b51641285732767ee2e6bcd035931032e3c1b29093f4d7fd0c79175bbc1f26f24f26ee49770d32be76f8730a652a857e6 + languageName: node + linkType: hard + "universalify@npm:^2.0.0": version: 2.0.1 resolution: "universalify@npm:2.0.1" @@ -2592,6 +4245,123 @@ __metadata: languageName: node linkType: hard +"vfile-message@npm:^4.0.0": + version: 4.0.3 + resolution: "vfile-message@npm:4.0.3" + dependencies: + "@types/unist": ^3.0.0 + unist-util-stringify-position: ^4.0.0 + checksum: f5e8516f2aa0feb4c866d507543d4e90f9ab309e2c988577dbf4ebd268d495f72f2b48149849d14300164d5d60b5f74b5641cd285bb4408a3942b758683d9276 + languageName: node + linkType: hard + +"vfile@npm:^6.0.0": + version: 6.0.3 + resolution: "vfile@npm:6.0.3" + dependencies: + "@types/unist": ^3.0.0 + vfile-message: ^4.0.0 + checksum: 152b6729be1af70df723efb65c1a1170fd483d41086557da3651eea69a1dd1f0c22ea4344834d56d30734b9185bcab63e22edc81d3f0e9bed8aa4660d61080af + languageName: node + linkType: hard + +"vite@npm:^5.4.14": + version: 5.4.21 + resolution: "vite@npm:5.4.21" + dependencies: + esbuild: ^0.21.3 + fsevents: ~2.3.3 + postcss: ^8.4.43 + rollup: ^4.20.0 + peerDependencies: + "@types/node": ^18.0.0 || >=20.0.0 + less: "*" + lightningcss: ^1.21.0 + sass: "*" + sass-embedded: "*" + stylus: "*" + sugarss: "*" + terser: ^5.4.0 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + bin: + vite: bin/vite.js + checksum: 7177fa03cff6a382f225290c9889a0d0e944d17eab705bcba89b58558a6f7adfa1f47e469b88f42a044a0eb40c12a1bf68b3cb42abb5295d04f9d7d4dd320837 + languageName: node + linkType: hard + +"vitepress@npm:^1.5.0": + version: 1.6.4 + resolution: "vitepress@npm:1.6.4" + dependencies: + "@docsearch/css": 3.8.2 + "@docsearch/js": 3.8.2 + "@iconify-json/simple-icons": ^1.2.21 + "@shikijs/core": ^2.1.0 + "@shikijs/transformers": ^2.1.0 + "@shikijs/types": ^2.1.0 + "@types/markdown-it": ^14.1.2 + "@vitejs/plugin-vue": ^5.2.1 + "@vue/devtools-api": ^7.7.0 + "@vue/shared": ^3.5.13 + "@vueuse/core": ^12.4.0 + "@vueuse/integrations": ^12.4.0 + focus-trap: ^7.6.4 + mark.js: 8.11.1 + minisearch: ^7.1.1 + shiki: ^2.1.0 + vite: ^5.4.14 + vue: ^3.5.13 + peerDependencies: + markdown-it-mathjax3: ^4 + postcss: ^8 + peerDependenciesMeta: + markdown-it-mathjax3: + optional: true + postcss: + optional: true + bin: + vitepress: bin/vitepress.js + checksum: 75c1a1d7b0c4f901344e108014ebe43f0a515df87c78b1201158a626b6aad7f9ae3dbd54e48e6cbe5602a61aa97b134ab5dffdfad8b4e226113308d5fda91e26 + languageName: node + linkType: hard + +"vue@npm:^3.5.13": + version: 3.5.26 + resolution: "vue@npm:3.5.26" + dependencies: + "@vue/compiler-dom": 3.5.26 + "@vue/compiler-sfc": 3.5.26 + "@vue/runtime-dom": 3.5.26 + "@vue/server-renderer": 3.5.26 + "@vue/shared": 3.5.26 + peerDependencies: + typescript: "*" + peerDependenciesMeta: + typescript: + optional: true + checksum: 1974f374318fbbb5fafde6195285c53c14cc8989ec38aeb6ad91c19598c45dfc6ccea7bdc6682efcb27320e089766bd6245fccd36f66f608e5d81cae8a03653a + languageName: node + linkType: hard + "webidl-conversions@npm:^3.0.0": version: 3.0.1 resolution: "webidl-conversions@npm:3.0.1" @@ -2701,6 +4471,13 @@ __metadata: languageName: node linkType: hard +"zwitch@npm:^2.0.4": + version: 2.0.4 + resolution: "zwitch@npm:2.0.4" + checksum: f22ec5fc2d5f02c423c93d35cdfa83573a3a3bd98c66b927c368ea4d0e7252a500df2a90a6b45522be536a96a73404393c958e945fdba95e6832c200791702b6 + languageName: node + linkType: hard + "zx@npm:^8.8.5": version: 8.8.5 resolution: "zx@npm:8.8.5" From 9edbfb2d40a7210740d69c5eccf1bdd16688c001 Mon Sep 17 00:00:00 2001 From: Subhash Khileri Date: Thu, 15 Jan 2026 18:27:24 +0530 Subject: [PATCH 2/3] fix CI yarn install --- yarn.lock | 1785 +---------------------------------------------------- 1 file changed, 4 insertions(+), 1781 deletions(-) diff --git a/yarn.lock b/yarn.lock index 4a959f8..ac280ba 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,203 +5,6 @@ __metadata: version: 6 cacheKey: 8 -"@algolia/abtesting@npm:1.12.3": - version: 1.12.3 - resolution: "@algolia/abtesting@npm:1.12.3" - dependencies: - "@algolia/client-common": 5.46.3 - "@algolia/requester-browser-xhr": 5.46.3 - "@algolia/requester-fetch": 5.46.3 - "@algolia/requester-node-http": 5.46.3 - checksum: e5370f1522651dda906770b07c22ff81126ffa78354a92b48e4e7feaeedc8d686979d351a1cdd9a526d8dd2e1c1c6b132ac51ca3adae8337bd3846aa5f074519 - languageName: node - linkType: hard - -"@algolia/autocomplete-core@npm:1.17.7": - version: 1.17.7 - resolution: "@algolia/autocomplete-core@npm:1.17.7" - dependencies: - "@algolia/autocomplete-plugin-algolia-insights": 1.17.7 - "@algolia/autocomplete-shared": 1.17.7 - checksum: 17236cfb4eccc4a706ce42ff09f9b9e819c38a650f96dc124b4168a626303a1d00ee407c46cdd9ff19fcaf344815cf222cc14c0b5364f6cb2f55f0a62677b9a4 - languageName: node - linkType: hard - -"@algolia/autocomplete-plugin-algolia-insights@npm:1.17.7": - version: 1.17.7 - resolution: "@algolia/autocomplete-plugin-algolia-insights@npm:1.17.7" - dependencies: - "@algolia/autocomplete-shared": 1.17.7 - peerDependencies: - search-insights: ">= 1 < 3" - checksum: 18e9ad58d421b7744e697e91253a6c95287e3c1194c0c8bdf1179a26b422cca069a7da102b2fcf9257bb85efd53db6131995cda3df8ab262424fed87a88c0a9d - languageName: node - linkType: hard - -"@algolia/autocomplete-preset-algolia@npm:1.17.7": - version: 1.17.7 - resolution: "@algolia/autocomplete-preset-algolia@npm:1.17.7" - dependencies: - "@algolia/autocomplete-shared": 1.17.7 - peerDependencies: - "@algolia/client-search": ">= 4.9.1 < 6" - algoliasearch: ">= 4.9.1 < 6" - checksum: d8e7e000fc027e15a0173a2cabb7821d902f71f03957f3bad7bb2bfd7ee58825e96e2efa57be559312e33d1bf07f658469fdc209286dbab05d8dada2d7a18531 - languageName: node - linkType: hard - -"@algolia/autocomplete-shared@npm:1.17.7": - version: 1.17.7 - resolution: "@algolia/autocomplete-shared@npm:1.17.7" - peerDependencies: - "@algolia/client-search": ">= 4.9.1 < 6" - algoliasearch: ">= 4.9.1 < 6" - checksum: 8aff3df580bdb3eeffce36225b04e4352f837aea37032fd6358d9c43b2eeab23cda4f217f7c0f0b8346b630c57c89ad415f8da308544f009b5e26256f9c3b59e - languageName: node - linkType: hard - -"@algolia/client-abtesting@npm:5.46.3": - version: 5.46.3 - resolution: "@algolia/client-abtesting@npm:5.46.3" - dependencies: - "@algolia/client-common": 5.46.3 - "@algolia/requester-browser-xhr": 5.46.3 - "@algolia/requester-fetch": 5.46.3 - "@algolia/requester-node-http": 5.46.3 - checksum: 05c55dbe5d4f7a184df2b5f94c8129749f9bf0f26a0e255828c172c506dfce9f6d178a4b1668d23451ba9f00b2c03f8a6fa2d444f5528df557787c62bd5842d7 - languageName: node - linkType: hard - -"@algolia/client-analytics@npm:5.46.3": - version: 5.46.3 - resolution: "@algolia/client-analytics@npm:5.46.3" - dependencies: - "@algolia/client-common": 5.46.3 - "@algolia/requester-browser-xhr": 5.46.3 - "@algolia/requester-fetch": 5.46.3 - "@algolia/requester-node-http": 5.46.3 - checksum: 5d344b8c9661706bafaf1afe22f349e0f982f210a8c429a3e02335ec9c8184aba39c611ea4df1b792806e837aa7511e1c6747a8ca9c780965b235f2a85fff176 - languageName: node - linkType: hard - -"@algolia/client-common@npm:5.46.3": - version: 5.46.3 - resolution: "@algolia/client-common@npm:5.46.3" - checksum: fdc62da392336940ced7aa1d7efbca1c588487625f3f33b2beff38e78495e0a628a99cb68f4c8f7f9f4f2e2da289c1f7135726a8ba403c127a45434c8c58ba29 - languageName: node - linkType: hard - -"@algolia/client-insights@npm:5.46.3": - version: 5.46.3 - resolution: "@algolia/client-insights@npm:5.46.3" - dependencies: - "@algolia/client-common": 5.46.3 - "@algolia/requester-browser-xhr": 5.46.3 - "@algolia/requester-fetch": 5.46.3 - "@algolia/requester-node-http": 5.46.3 - checksum: 9490ac26f505e631f3872969f535686c4d115fafe637fa26e876f5b4bed8e73f6b6f054dc9f872ea7bb80550e09b021d267cdc865433fc1fef4d39d303e1c610 - languageName: node - linkType: hard - -"@algolia/client-personalization@npm:5.46.3": - version: 5.46.3 - resolution: "@algolia/client-personalization@npm:5.46.3" - dependencies: - "@algolia/client-common": 5.46.3 - "@algolia/requester-browser-xhr": 5.46.3 - "@algolia/requester-fetch": 5.46.3 - "@algolia/requester-node-http": 5.46.3 - checksum: dbc4a42f47ea74279926ae603248db1589259d56c2a13d47b6ec3409ea5deabc051d89b7e45cd78619a7f52272a8bed004a9759cfe6894a13cd0797837fb18e8 - languageName: node - linkType: hard - -"@algolia/client-query-suggestions@npm:5.46.3": - version: 5.46.3 - resolution: "@algolia/client-query-suggestions@npm:5.46.3" - dependencies: - "@algolia/client-common": 5.46.3 - "@algolia/requester-browser-xhr": 5.46.3 - "@algolia/requester-fetch": 5.46.3 - "@algolia/requester-node-http": 5.46.3 - checksum: 254a714b5a54c796660516bd9d690cbc9e372e968ffd928ae7e50ab33751a56d82825e514749f411d3ee4cbaa65920f26769c43de63874a827af612c1c789f6a - languageName: node - linkType: hard - -"@algolia/client-search@npm:5.46.3": - version: 5.46.3 - resolution: "@algolia/client-search@npm:5.46.3" - dependencies: - "@algolia/client-common": 5.46.3 - "@algolia/requester-browser-xhr": 5.46.3 - "@algolia/requester-fetch": 5.46.3 - "@algolia/requester-node-http": 5.46.3 - checksum: 6b6ffb444e056c3eeab8621fdfa2a92e40e16ff0a2218f63064004f2e10fb6efffc4cf8346d181f17243eb8c088c0dc4e1d213089e0a9afba36e9e22b4dc929b - languageName: node - linkType: hard - -"@algolia/ingestion@npm:1.46.3": - version: 1.46.3 - resolution: "@algolia/ingestion@npm:1.46.3" - dependencies: - "@algolia/client-common": 5.46.3 - "@algolia/requester-browser-xhr": 5.46.3 - "@algolia/requester-fetch": 5.46.3 - "@algolia/requester-node-http": 5.46.3 - checksum: d79fe9914513f3a95ca3b7df31fe75605c6f64634f4d614cc17c42e2302c4dfd3eeece7ee7bb986e60665f6ef67c54f2f93da2d9145085aab7b02acf974a3df1 - languageName: node - linkType: hard - -"@algolia/monitoring@npm:1.46.3": - version: 1.46.3 - resolution: "@algolia/monitoring@npm:1.46.3" - dependencies: - "@algolia/client-common": 5.46.3 - "@algolia/requester-browser-xhr": 5.46.3 - "@algolia/requester-fetch": 5.46.3 - "@algolia/requester-node-http": 5.46.3 - checksum: a0dd4273e5d9cde3c274cbe246ea969bffaa0c614e85d6635550eaead826726a15e79dcfecb694a214c5edaff1582bdff95f8533362b4aea2044e8cd468efda8 - languageName: node - linkType: hard - -"@algolia/recommend@npm:5.46.3": - version: 5.46.3 - resolution: "@algolia/recommend@npm:5.46.3" - dependencies: - "@algolia/client-common": 5.46.3 - "@algolia/requester-browser-xhr": 5.46.3 - "@algolia/requester-fetch": 5.46.3 - "@algolia/requester-node-http": 5.46.3 - checksum: 104c523c1e9d1e0bd30c08c119168dcd2c0106e7098470f5c28ab1532d3e6b3b30979bb1f59eee8dda40f319e89150dcb2ebd7164216947a64f3540a7403709f - languageName: node - linkType: hard - -"@algolia/requester-browser-xhr@npm:5.46.3": - version: 5.46.3 - resolution: "@algolia/requester-browser-xhr@npm:5.46.3" - dependencies: - "@algolia/client-common": 5.46.3 - checksum: 5def15096c6096442559c53f6c5aea8cc939a0c3d607d3a03e5e6659852c69bca4f90500ab8c5e3bd630873a83b50dbe92bcf70e3f3156d5e62bd098d2e6d077 - languageName: node - linkType: hard - -"@algolia/requester-fetch@npm:5.46.3": - version: 5.46.3 - resolution: "@algolia/requester-fetch@npm:5.46.3" - dependencies: - "@algolia/client-common": 5.46.3 - checksum: 4fdb1d315f8a06f49c7052a0c7335d41441d77dd2e7af54ca2587dedb1a18bb06b1e7354efe743c5bdd4709015cbbe68389ff20de0cc433f9f1c3d53df59275c - languageName: node - linkType: hard - -"@algolia/requester-node-http@npm:5.46.3": - version: 5.46.3 - resolution: "@algolia/requester-node-http@npm:5.46.3" - dependencies: - "@algolia/client-common": 5.46.3 - checksum: 2f9476928db92b6743936c593edd4aa401ade191a8c8829f4ebf8510cd3a6569fd456d5171b839f1916127c31d9930461abd8eff8202be75e554ab5edfbdd089 - languageName: node - linkType: hard - "@axe-core/playwright@npm:^4.11.0": version: 4.11.0 resolution: "@axe-core/playwright@npm:4.11.0" @@ -213,41 +16,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-string-parser@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/helper-string-parser@npm:7.27.1" - checksum: 0a8464adc4b39b138aedcb443b09f4005d86207d7126e5e079177e05c3116107d856ec08282b365e9a79a9872f40f4092a6127f8d74c8a01c1ef789dacfc25d6 - languageName: node - linkType: hard - -"@babel/helper-validator-identifier@npm:^7.28.5": - version: 7.28.5 - resolution: "@babel/helper-validator-identifier@npm:7.28.5" - checksum: 5a251a6848e9712aea0338f659a1a3bd334d26219d5511164544ca8ec20774f098c3a6661e9da65a0d085c745c00bb62c8fada38a62f08fa1f8053bc0aeb57e4 - languageName: node - linkType: hard - -"@babel/parser@npm:^7.28.5": - version: 7.28.6 - resolution: "@babel/parser@npm:7.28.6" - dependencies: - "@babel/types": ^7.28.6 - bin: - parser: ./bin/babel-parser.js - checksum: 2a35319792ceef9bc918f0ff854449bef0120707798fe147ef988b0606de226e2fbc3a562ba687148bfe5336c6c67358fb27e71a94e425b28482dcaf0b172fd6 - languageName: node - linkType: hard - -"@babel/types@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/types@npm:7.28.6" - dependencies: - "@babel/helper-string-parser": ^7.27.1 - "@babel/helper-validator-identifier": ^7.28.5 - checksum: f76556cda59be337cc10dc68b2a9a947c10de018998bab41076e7b7e4489b28dd53299f98f22eec0774264c989515e6fdc56de91c73e3aa396367bb953200a6a - languageName: node - linkType: hard - "@backstage/catalog-model@npm:1.7.5": version: 1.7.5 resolution: "@backstage/catalog-model@npm:1.7.5" @@ -277,210 +45,6 @@ __metadata: languageName: node linkType: hard -"@docsearch/css@npm:3.8.2": - version: 3.8.2 - resolution: "@docsearch/css@npm:3.8.2" - checksum: 9d38cd17b3fc156f57d189ecccb9fbb4f208650dc6039aba6bd73d90174d9801b796421936673cba6bafe1c58302ea48ff5aeea7c803cf229fb1ee2708085a02 - languageName: node - linkType: hard - -"@docsearch/js@npm:3.8.2": - version: 3.8.2 - resolution: "@docsearch/js@npm:3.8.2" - dependencies: - "@docsearch/react": 3.8.2 - preact: ^10.0.0 - checksum: 144ba0dc9e578869ba26cd5198e38e9469e9a351267949bae5a55388197f9ebdff4ffc83e863729d28dbd6431a557c8d7be422b0cd17d37c213223db56231fb7 - languageName: node - linkType: hard - -"@docsearch/react@npm:3.8.2": - version: 3.8.2 - resolution: "@docsearch/react@npm:3.8.2" - dependencies: - "@algolia/autocomplete-core": 1.17.7 - "@algolia/autocomplete-preset-algolia": 1.17.7 - "@docsearch/css": 3.8.2 - algoliasearch: ^5.14.2 - peerDependencies: - "@types/react": ">= 16.8.0 < 19.0.0" - react: ">= 16.8.0 < 19.0.0" - react-dom: ">= 16.8.0 < 19.0.0" - search-insights: ">= 1 < 3" - peerDependenciesMeta: - "@types/react": - optional: true - react: - optional: true - react-dom: - optional: true - search-insights: - optional: true - checksum: b307eee7c657f0c21e8a2b1c28a1d451de83434232e128b13c54c8c219a6d5f2d302ce2ca2a824eb730d5ea25cfccb1dce0e4fc1291f36f31f810841f4a25891 - languageName: node - linkType: hard - -"@esbuild/aix-ppc64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/aix-ppc64@npm:0.21.5" - conditions: os=aix & cpu=ppc64 - languageName: node - linkType: hard - -"@esbuild/android-arm64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/android-arm64@npm:0.21.5" - conditions: os=android & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/android-arm@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/android-arm@npm:0.21.5" - conditions: os=android & cpu=arm - languageName: node - linkType: hard - -"@esbuild/android-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/android-x64@npm:0.21.5" - conditions: os=android & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/darwin-arm64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/darwin-arm64@npm:0.21.5" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/darwin-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/darwin-x64@npm:0.21.5" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/freebsd-arm64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/freebsd-arm64@npm:0.21.5" - conditions: os=freebsd & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/freebsd-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/freebsd-x64@npm:0.21.5" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/linux-arm64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-arm64@npm:0.21.5" - conditions: os=linux & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/linux-arm@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-arm@npm:0.21.5" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - -"@esbuild/linux-ia32@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-ia32@npm:0.21.5" - conditions: os=linux & cpu=ia32 - languageName: node - linkType: hard - -"@esbuild/linux-loong64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-loong64@npm:0.21.5" - conditions: os=linux & cpu=loong64 - languageName: node - linkType: hard - -"@esbuild/linux-mips64el@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-mips64el@npm:0.21.5" - conditions: os=linux & cpu=mips64el - languageName: node - linkType: hard - -"@esbuild/linux-ppc64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-ppc64@npm:0.21.5" - conditions: os=linux & cpu=ppc64 - languageName: node - linkType: hard - -"@esbuild/linux-riscv64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-riscv64@npm:0.21.5" - conditions: os=linux & cpu=riscv64 - languageName: node - linkType: hard - -"@esbuild/linux-s390x@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-s390x@npm:0.21.5" - conditions: os=linux & cpu=s390x - languageName: node - linkType: hard - -"@esbuild/linux-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-x64@npm:0.21.5" - conditions: os=linux & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/netbsd-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/netbsd-x64@npm:0.21.5" - conditions: os=netbsd & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/openbsd-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/openbsd-x64@npm:0.21.5" - conditions: os=openbsd & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/sunos-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/sunos-x64@npm:0.21.5" - conditions: os=sunos & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/win32-arm64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/win32-arm64@npm:0.21.5" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/win32-ia32@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/win32-ia32@npm:0.21.5" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - -"@esbuild/win32-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/win32-x64@npm:0.21.5" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - "@eslint-community/eslint-utils@npm:^4.7.0, @eslint-community/eslint-utils@npm:^4.8.0": version: 4.9.0 resolution: "@eslint-community/eslint-utils@npm:4.9.0" @@ -600,22 +164,6 @@ __metadata: languageName: node linkType: hard -"@iconify-json/simple-icons@npm:^1.2.21": - version: 1.2.66 - resolution: "@iconify-json/simple-icons@npm:1.2.66" - dependencies: - "@iconify/types": "*" - checksum: 461ebf13d5dc69b7995f8499d758fa3b18d8fe0dded26149f84af75451681e304d32c602339ccf12a72598d1f1d72fc4b38d20ac96995368775b27ac49ab5116 - languageName: node - linkType: hard - -"@iconify/types@npm:*": - version: 2.0.0 - resolution: "@iconify/types@npm:2.0.0" - checksum: 029f58542c160e9d4a746869cf2e475b603424d3adf3994c5cc8d0406c47e6e04a3b898b2707840c1c5b9bd5563a1660a34b110d89fce43923baca5222f4e597 - languageName: node - linkType: hard - "@isaacs/balanced-match@npm:^4.0.1": version: 4.0.1 resolution: "@isaacs/balanced-match@npm:4.0.1" @@ -641,13 +189,6 @@ __metadata: languageName: node linkType: hard -"@jridgewell/sourcemap-codec@npm:^1.5.5": - version: 1.5.5 - resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" - checksum: c2e36e67971f719a8a3a85ef5a5f580622437cc723c35d03ebd0c9c0b06418700ef006f58af742791f71f6a4fc68fcfaf1f6a74ec2f9a3332860e9373459dae7 - languageName: node - linkType: hard - "@jsep-plugin/assignment@npm:^1.3.0": version: 1.3.0 resolution: "@jsep-plugin/assignment@npm:1.3.0" @@ -781,262 +322,7 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-android-arm-eabi@npm:4.55.1": - version: 4.55.1 - resolution: "@rollup/rollup-android-arm-eabi@npm:4.55.1" - conditions: os=android & cpu=arm - languageName: node - linkType: hard - -"@rollup/rollup-android-arm64@npm:4.55.1": - version: 4.55.1 - resolution: "@rollup/rollup-android-arm64@npm:4.55.1" - conditions: os=android & cpu=arm64 - languageName: node - linkType: hard - -"@rollup/rollup-darwin-arm64@npm:4.55.1": - version: 4.55.1 - resolution: "@rollup/rollup-darwin-arm64@npm:4.55.1" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - -"@rollup/rollup-darwin-x64@npm:4.55.1": - version: 4.55.1 - resolution: "@rollup/rollup-darwin-x64@npm:4.55.1" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - -"@rollup/rollup-freebsd-arm64@npm:4.55.1": - version: 4.55.1 - resolution: "@rollup/rollup-freebsd-arm64@npm:4.55.1" - conditions: os=freebsd & cpu=arm64 - languageName: node - linkType: hard - -"@rollup/rollup-freebsd-x64@npm:4.55.1": - version: 4.55.1 - resolution: "@rollup/rollup-freebsd-x64@npm:4.55.1" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - -"@rollup/rollup-linux-arm-gnueabihf@npm:4.55.1": - version: 4.55.1 - resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.55.1" - conditions: os=linux & cpu=arm & libc=glibc - languageName: node - linkType: hard - -"@rollup/rollup-linux-arm-musleabihf@npm:4.55.1": - version: 4.55.1 - resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.55.1" - conditions: os=linux & cpu=arm & libc=musl - languageName: node - linkType: hard - -"@rollup/rollup-linux-arm64-gnu@npm:4.55.1": - version: 4.55.1 - resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.55.1" - conditions: os=linux & cpu=arm64 & libc=glibc - languageName: node - linkType: hard - -"@rollup/rollup-linux-arm64-musl@npm:4.55.1": - version: 4.55.1 - resolution: "@rollup/rollup-linux-arm64-musl@npm:4.55.1" - conditions: os=linux & cpu=arm64 & libc=musl - languageName: node - linkType: hard - -"@rollup/rollup-linux-loong64-gnu@npm:4.55.1": - version: 4.55.1 - resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.55.1" - conditions: os=linux & cpu=loong64 & libc=glibc - languageName: node - linkType: hard - -"@rollup/rollup-linux-loong64-musl@npm:4.55.1": - version: 4.55.1 - resolution: "@rollup/rollup-linux-loong64-musl@npm:4.55.1" - conditions: os=linux & cpu=loong64 & libc=musl - languageName: node - linkType: hard - -"@rollup/rollup-linux-ppc64-gnu@npm:4.55.1": - version: 4.55.1 - resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.55.1" - conditions: os=linux & cpu=ppc64 & libc=glibc - languageName: node - linkType: hard - -"@rollup/rollup-linux-ppc64-musl@npm:4.55.1": - version: 4.55.1 - resolution: "@rollup/rollup-linux-ppc64-musl@npm:4.55.1" - conditions: os=linux & cpu=ppc64 & libc=musl - languageName: node - linkType: hard - -"@rollup/rollup-linux-riscv64-gnu@npm:4.55.1": - version: 4.55.1 - resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.55.1" - conditions: os=linux & cpu=riscv64 & libc=glibc - languageName: node - linkType: hard - -"@rollup/rollup-linux-riscv64-musl@npm:4.55.1": - version: 4.55.1 - resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.55.1" - conditions: os=linux & cpu=riscv64 & libc=musl - languageName: node - linkType: hard - -"@rollup/rollup-linux-s390x-gnu@npm:4.55.1": - version: 4.55.1 - resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.55.1" - conditions: os=linux & cpu=s390x & libc=glibc - languageName: node - linkType: hard - -"@rollup/rollup-linux-x64-gnu@npm:4.55.1": - version: 4.55.1 - resolution: "@rollup/rollup-linux-x64-gnu@npm:4.55.1" - conditions: os=linux & cpu=x64 & libc=glibc - languageName: node - linkType: hard - -"@rollup/rollup-linux-x64-musl@npm:4.55.1": - version: 4.55.1 - resolution: "@rollup/rollup-linux-x64-musl@npm:4.55.1" - conditions: os=linux & cpu=x64 & libc=musl - languageName: node - linkType: hard - -"@rollup/rollup-openbsd-x64@npm:4.55.1": - version: 4.55.1 - resolution: "@rollup/rollup-openbsd-x64@npm:4.55.1" - conditions: os=openbsd & cpu=x64 - languageName: node - linkType: hard - -"@rollup/rollup-openharmony-arm64@npm:4.55.1": - version: 4.55.1 - resolution: "@rollup/rollup-openharmony-arm64@npm:4.55.1" - conditions: os=openharmony & cpu=arm64 - languageName: node - linkType: hard - -"@rollup/rollup-win32-arm64-msvc@npm:4.55.1": - version: 4.55.1 - resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.55.1" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - -"@rollup/rollup-win32-ia32-msvc@npm:4.55.1": - version: 4.55.1 - resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.55.1" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - -"@rollup/rollup-win32-x64-gnu@npm:4.55.1": - version: 4.55.1 - resolution: "@rollup/rollup-win32-x64-gnu@npm:4.55.1" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - -"@rollup/rollup-win32-x64-msvc@npm:4.55.1": - version: 4.55.1 - resolution: "@rollup/rollup-win32-x64-msvc@npm:4.55.1" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - -"@shikijs/core@npm:2.5.0, @shikijs/core@npm:^2.1.0": - version: 2.5.0 - resolution: "@shikijs/core@npm:2.5.0" - dependencies: - "@shikijs/engine-javascript": 2.5.0 - "@shikijs/engine-oniguruma": 2.5.0 - "@shikijs/types": 2.5.0 - "@shikijs/vscode-textmate": ^10.0.2 - "@types/hast": ^3.0.4 - hast-util-to-html: ^9.0.4 - checksum: 033f324e4bfc95afaced957140e53affc35ede89e9a06a844cc15f0ab408731d2b7146e2c5aa01374215550346ac74cdb338aefb8e26765d6596625af6aaca29 - languageName: node - linkType: hard - -"@shikijs/engine-javascript@npm:2.5.0": - version: 2.5.0 - resolution: "@shikijs/engine-javascript@npm:2.5.0" - dependencies: - "@shikijs/types": 2.5.0 - "@shikijs/vscode-textmate": ^10.0.2 - oniguruma-to-es: ^3.1.0 - checksum: f9fb6efcc3238654bcb19ae10f549918901db9c917e2eefd71b9e4a009b725168a9bece2b0814422f7a8fa97e20d585c8a68283d144bdb6a258e57168c0c7b0e - languageName: node - linkType: hard - -"@shikijs/engine-oniguruma@npm:2.5.0": - version: 2.5.0 - resolution: "@shikijs/engine-oniguruma@npm:2.5.0" - dependencies: - "@shikijs/types": 2.5.0 - "@shikijs/vscode-textmate": ^10.0.2 - checksum: d493fc5d2f968d9c69c24ae51c1d5e7ec9e62bbbf0030e59a56277edb19253e412a860fedbf591de19fcbf99a17f642e7a537a796e8c9fb5dd6c447155ed47ec - languageName: node - linkType: hard - -"@shikijs/langs@npm:2.5.0": - version: 2.5.0 - resolution: "@shikijs/langs@npm:2.5.0" - dependencies: - "@shikijs/types": 2.5.0 - checksum: 9082fc23ec6d6b3b605a30b544d7af986586efbd19cf3048cd078f367eff6e708cda318393200d455a42bbca83273a7dfd00f28cedae44e09c4ba66583ea02c9 - languageName: node - linkType: hard - -"@shikijs/themes@npm:2.5.0": - version: 2.5.0 - resolution: "@shikijs/themes@npm:2.5.0" - dependencies: - "@shikijs/types": 2.5.0 - checksum: 0844066e284d8d7263dfd37ff8db8813d6784ea10eab46d0b3639a31a16aa3b68031fd2b4b3f1706f8b9a71ada8b30d8a8bdc5832b7c8a1b3bc1b2e28c199f96 - languageName: node - linkType: hard - -"@shikijs/transformers@npm:^2.1.0": - version: 2.5.0 - resolution: "@shikijs/transformers@npm:2.5.0" - dependencies: - "@shikijs/core": 2.5.0 - "@shikijs/types": 2.5.0 - checksum: bbe5706b4ad7c32fa9d3eb61f656a85a0423934b7174415760bd4081804f41d7a244f46c4b85488de4b2e7009b6c910039a3f60c6daa92999f72fd16ec267780 - languageName: node - linkType: hard - -"@shikijs/types@npm:2.5.0, @shikijs/types@npm:^2.1.0": - version: 2.5.0 - resolution: "@shikijs/types@npm:2.5.0" - dependencies: - "@shikijs/vscode-textmate": ^10.0.2 - "@types/hast": ^3.0.4 - checksum: c7c2be18cb6305890d9d2f8cdd449f9faeb47338d4d4056d7915e8db961b14491cdc3cba70f443be31be04ac129e094dbd16cb93d5068f2341ad95b3a4e88258 - languageName: node - linkType: hard - -"@shikijs/vscode-textmate@npm:^10.0.2": - version: 10.0.2 - resolution: "@shikijs/vscode-textmate@npm:10.0.2" - checksum: e68f27a3dc1584d7414b8acafb9c177a2181eb0b06ef178d8609142f49d28d85fd10ab129affde40a45a7d9238997e457ce47931b3a3815980e2b98b2d26724c - languageName: node - linkType: hard - -"@types/estree@npm:1.0.8, @types/estree@npm:^1.0.6": +"@types/estree@npm:^1.0.6": version: 1.0.8 resolution: "@types/estree@npm:1.0.8" checksum: bd93e2e415b6f182ec4da1074e1f36c480f1d26add3e696d54fb30c09bc470897e41361c8fd957bf0985024f8fbf1e6e2aff977d79352ef7eb93a5c6dcff6c11 @@ -1053,15 +339,6 @@ __metadata: languageName: node linkType: hard -"@types/hast@npm:^3.0.0, @types/hast@npm:^3.0.4": - version: 3.0.4 - resolution: "@types/hast@npm:3.0.4" - dependencies: - "@types/unist": "*" - checksum: 7a973e8d16fcdf3936090fa2280f408fb2b6a4f13b42edeb5fbd614efe042b82eac68e298e556d50f6b4ad585a3a93c353e9c826feccdc77af59de8dd400d044 - languageName: node - linkType: hard - "@types/js-yaml@npm:^4.0.1, @types/js-yaml@npm:^4.0.9": version: 4.0.9 resolution: "@types/js-yaml@npm:4.0.9" @@ -1085,13 +362,6 @@ __metadata: languageName: node linkType: hard -"@types/linkify-it@npm:^5": - version: 5.0.0 - resolution: "@types/linkify-it@npm:5.0.0" - checksum: ec98e03aa883f70153a17a1e6ed9e28b39a604049b485daeddae3a1482ec65cac0817520be6e301d99fd1a934b3950cf0f855655aae6ec27da2bb676ba4a148e - languageName: node - linkType: hard - "@types/lodash.mergewith@npm:^4.6.9": version: 4.6.9 resolution: "@types/lodash.mergewith@npm:4.6.9" @@ -1108,32 +378,6 @@ __metadata: languageName: node linkType: hard -"@types/markdown-it@npm:^14.1.2": - version: 14.1.2 - resolution: "@types/markdown-it@npm:14.1.2" - dependencies: - "@types/linkify-it": ^5 - "@types/mdurl": ^2 - checksum: ad66e0b377d6af09a155bb65f675d1e2cb27d20a3d407377fe4508eb29cde1e765430b99d5129f89012e2524abb5525d629f7057a59ff9fd0967e1ff645b9ec6 - languageName: node - linkType: hard - -"@types/mdast@npm:^4.0.0": - version: 4.0.4 - resolution: "@types/mdast@npm:4.0.4" - dependencies: - "@types/unist": "*" - checksum: 20c4e9574cc409db662a35cba52b068b91eb696b3049e94321219d47d34c8ccc99a142be5c76c80a538b612457b03586bc2f6b727a3e9e7530f4c8568f6282ee - languageName: node - linkType: hard - -"@types/mdurl@npm:^2": - version: 2.0.0 - resolution: "@types/mdurl@npm:2.0.0" - checksum: 78746e96c655ceed63db06382da466fd52c7e9dc54d60b12973dfdd110cae06b9439c4b90e17bb8d4461109184b3ea9f3e9f96b3e4bf4aa9fe18b6ac35f283c8 - languageName: node - linkType: hard - "@types/node-fetch@npm:^2.6.13": version: 2.6.13 resolution: "@types/node-fetch@npm:2.6.13" @@ -1171,20 +415,6 @@ __metadata: languageName: node linkType: hard -"@types/unist@npm:*, @types/unist@npm:^3.0.0": - version: 3.0.3 - resolution: "@types/unist@npm:3.0.3" - checksum: 96e6453da9e075aaef1dc22482463898198acdc1eeb99b465e65e34303e2ec1e3b1ed4469a9118275ec284dc98019f63c3f5d49422f0e4ac707e5ab90fb3b71a - languageName: node - linkType: hard - -"@types/web-bluetooth@npm:^0.0.21": - version: 0.0.21 - resolution: "@types/web-bluetooth@npm:0.0.21" - checksum: 85a957d52263607d26236b1748771fdcfc0791f2c20373370e6c725410067dee6a5ec425b0e25ec85db2908dd59415079070655fd3277841cbbd3a557d1f7777 - languageName: node - linkType: hard - "@typescript-eslint/eslint-plugin@npm:8.50.0": version: 8.50.0 resolution: "@typescript-eslint/eslint-plugin@npm:8.50.0" @@ -1320,233 +550,6 @@ __metadata: languageName: node linkType: hard -"@ungap/structured-clone@npm:^1.0.0": - version: 1.3.0 - resolution: "@ungap/structured-clone@npm:1.3.0" - checksum: 64ed518f49c2b31f5b50f8570a1e37bde3b62f2460042c50f132430b2d869c4a6586f13aa33a58a4722715b8158c68cae2827389d6752ac54da2893c83e480fc - languageName: node - linkType: hard - -"@vitejs/plugin-vue@npm:^5.2.1": - version: 5.2.4 - resolution: "@vitejs/plugin-vue@npm:5.2.4" - peerDependencies: - vite: ^5.0.0 || ^6.0.0 - vue: ^3.2.25 - checksum: 116a859945401195b0d89b3e2f872bde0d168b498f1438c02a212b53e1131cd4ef5ace65b563092f6b326d7ef726639a9b33d61459b82a634f73f44a3ee0d924 - languageName: node - linkType: hard - -"@vue/compiler-core@npm:3.5.26": - version: 3.5.26 - resolution: "@vue/compiler-core@npm:3.5.26" - dependencies: - "@babel/parser": ^7.28.5 - "@vue/shared": 3.5.26 - entities: ^7.0.0 - estree-walker: ^2.0.2 - source-map-js: ^1.2.1 - checksum: 0b67761b9d04832d04c8702316b6cd7e78fa596c8cd79ff36a2fb02694ea046f515214fc13dc6624a720803ef7e6655c2f30b94d007fd2d6d3f8c23d85fdccb7 - languageName: node - linkType: hard - -"@vue/compiler-dom@npm:3.5.26": - version: 3.5.26 - resolution: "@vue/compiler-dom@npm:3.5.26" - dependencies: - "@vue/compiler-core": 3.5.26 - "@vue/shared": 3.5.26 - checksum: ae5429f036e46cfd03e43b635870ae1cc9bac431cabb27a0e84ca4268741dfcfc6a5b8fa5bb512fdaa10665c395564aaa30883bd5b013058cfff99f73382149f - languageName: node - linkType: hard - -"@vue/compiler-sfc@npm:3.5.26": - version: 3.5.26 - resolution: "@vue/compiler-sfc@npm:3.5.26" - dependencies: - "@babel/parser": ^7.28.5 - "@vue/compiler-core": 3.5.26 - "@vue/compiler-dom": 3.5.26 - "@vue/compiler-ssr": 3.5.26 - "@vue/shared": 3.5.26 - estree-walker: ^2.0.2 - magic-string: ^0.30.21 - postcss: ^8.5.6 - source-map-js: ^1.2.1 - checksum: 26b9052a201ed128b309ba5a4b47c7363c511be7237dc31b5d25b9a03e6da30edcdbd939b1fb9d70f057f2b83c46bdd45be08344cfdc217551b775c5ccec39ac - languageName: node - linkType: hard - -"@vue/compiler-ssr@npm:3.5.26": - version: 3.5.26 - resolution: "@vue/compiler-ssr@npm:3.5.26" - dependencies: - "@vue/compiler-dom": 3.5.26 - "@vue/shared": 3.5.26 - checksum: aaa29604dcfa1c542b33e4e5abb76c28dbda29fdf1ce0493add7cce4fe51c69e6775bca2798904d4ba4e978f68f3b3713ef3af6aee6ba5eb51b5dbc36b4a59e6 - languageName: node - linkType: hard - -"@vue/devtools-api@npm:^7.7.0": - version: 7.7.9 - resolution: "@vue/devtools-api@npm:7.7.9" - dependencies: - "@vue/devtools-kit": ^7.7.9 - checksum: a4dbc911c4a99d401591972c766b56b323f6f01dddfaf1bddc00c5c78aeda7dd7afc6330b5b8245a13ef72f8eceededf0a40416311e229c19014d151ebacd416 - languageName: node - linkType: hard - -"@vue/devtools-kit@npm:^7.7.9": - version: 7.7.9 - resolution: "@vue/devtools-kit@npm:7.7.9" - dependencies: - "@vue/devtools-shared": ^7.7.9 - birpc: ^2.3.0 - hookable: ^5.5.3 - mitt: ^3.0.1 - perfect-debounce: ^1.0.0 - speakingurl: ^14.0.1 - superjson: ^2.2.2 - checksum: 7241630643abaf2fd22f1db2ff47ec594418b6a4c55822c8dc4809754bf22f8f9c095872f8774c2f0a07c77f4663ef5f0bdc88578b42be282c0b4ed184b5fda1 - languageName: node - linkType: hard - -"@vue/devtools-shared@npm:^7.7.9": - version: 7.7.9 - resolution: "@vue/devtools-shared@npm:7.7.9" - dependencies: - rfdc: ^1.4.1 - checksum: 02ea89651174d3763f2b0ea63f1ef7bb8ef8e22479dfc2b6b19cdbe2aab76911b7c2891de149ba985d218f58a06f2de4f4fb4e01d2fa8296e2d7b80d8539eceb - languageName: node - linkType: hard - -"@vue/reactivity@npm:3.5.26": - version: 3.5.26 - resolution: "@vue/reactivity@npm:3.5.26" - dependencies: - "@vue/shared": 3.5.26 - checksum: 9bbad24381d4ca0ad00d5e00354020f8b0464ef4bdbb3aca53fa194b0d48889294d841e03ed2232a342fdca25b3551312626c2b53258300b6d821f7363648245 - languageName: node - linkType: hard - -"@vue/runtime-core@npm:3.5.26": - version: 3.5.26 - resolution: "@vue/runtime-core@npm:3.5.26" - dependencies: - "@vue/reactivity": 3.5.26 - "@vue/shared": 3.5.26 - checksum: 10200dc5fbfd873e3de66792498706f72c61ca821440a21bd3f697eaa7c551166ccb11afab3fab1a118201af9357489287abbae3eac01a5692f6aea889cb9187 - languageName: node - linkType: hard - -"@vue/runtime-dom@npm:3.5.26": - version: 3.5.26 - resolution: "@vue/runtime-dom@npm:3.5.26" - dependencies: - "@vue/reactivity": 3.5.26 - "@vue/runtime-core": 3.5.26 - "@vue/shared": 3.5.26 - csstype: ^3.2.3 - checksum: 9cdcaef49c49ba327219baeada153664b7b82763d08cda102ecf9dbbb4a8dd48792b6b70167a6032f5e838d11bc77df832d4c6df1053e7d3a250387f1b62d3ad - languageName: node - linkType: hard - -"@vue/server-renderer@npm:3.5.26": - version: 3.5.26 - resolution: "@vue/server-renderer@npm:3.5.26" - dependencies: - "@vue/compiler-ssr": 3.5.26 - "@vue/shared": 3.5.26 - peerDependencies: - vue: 3.5.26 - checksum: 171eb7f8f4fc8cb61795d42157f13898b214564bb0c1f6398a863568468b1a7b95e837120c45512259ef8e1b24c9e45787c413b7f45bd18259f2d1c3bbf2b011 - languageName: node - linkType: hard - -"@vue/shared@npm:3.5.26, @vue/shared@npm:^3.5.13": - version: 3.5.26 - resolution: "@vue/shared@npm:3.5.26" - checksum: 0daf1717cd853dfaa0663bf856277c918360acc7921f17966da902f1b05ab8c7385fb5cc06a18f3b861cb84393d235c937836e044679dab989aba719aa930f55 - languageName: node - linkType: hard - -"@vueuse/core@npm:12.8.2, @vueuse/core@npm:^12.4.0": - version: 12.8.2 - resolution: "@vueuse/core@npm:12.8.2" - dependencies: - "@types/web-bluetooth": ^0.0.21 - "@vueuse/metadata": 12.8.2 - "@vueuse/shared": 12.8.2 - vue: ^3.5.13 - checksum: e8749b85c0c1a4e000f65b77e13037e0177cc0c43aa86fab0d90c1c7d9a1a9fb6b1d90461b9b599c51fce5acf201370f0cdf8dc3a5eda5acb2e982227cc2dc23 - languageName: node - linkType: hard - -"@vueuse/integrations@npm:^12.4.0": - version: 12.8.2 - resolution: "@vueuse/integrations@npm:12.8.2" - dependencies: - "@vueuse/core": 12.8.2 - "@vueuse/shared": 12.8.2 - vue: ^3.5.13 - peerDependencies: - async-validator: ^4 - axios: ^1 - change-case: ^5 - drauu: ^0.4 - focus-trap: ^7 - fuse.js: ^7 - idb-keyval: ^6 - jwt-decode: ^4 - nprogress: ^0.2 - qrcode: ^1.5 - sortablejs: ^1 - universal-cookie: ^7 - peerDependenciesMeta: - async-validator: - optional: true - axios: - optional: true - change-case: - optional: true - drauu: - optional: true - focus-trap: - optional: true - fuse.js: - optional: true - idb-keyval: - optional: true - jwt-decode: - optional: true - nprogress: - optional: true - qrcode: - optional: true - sortablejs: - optional: true - universal-cookie: - optional: true - checksum: 6c7a82ba00421d220e9e12a55bbb223d291648768629c9f88d48bcc0b4354b9d62e2f7a4efb83e6fd949c07071d9f16c2f1debbb966a4297a5d638199fc1da62 - languageName: node - linkType: hard - -"@vueuse/metadata@npm:12.8.2": - version: 12.8.2 - resolution: "@vueuse/metadata@npm:12.8.2" - checksum: 4fae302c2eb24970fd1946a0f25c81a99a3bbd1d12a43670ef7c2cbfe921a68ef98b1511a055cae8a37f2cf1ce625b88f726572b55dd15689e5201ad7714c667 - languageName: node - linkType: hard - -"@vueuse/shared@npm:12.8.2": - version: 12.8.2 - resolution: "@vueuse/shared@npm:12.8.2" - dependencies: - vue: ^3.5.13 - checksum: 41400fe8a375782e9a64f262ee7a900fd28de40767f63dad1624eff4a109eea14e88766f6fae5fe72c9f6ef84ea7600447d94ad37bec97ee34cc62560ad90dd7 - languageName: node - linkType: hard - "abbrev@npm:^4.0.0": version: 4.0.0 resolution: "abbrev@npm:4.0.0" @@ -1603,28 +606,6 @@ __metadata: languageName: node linkType: hard -"algoliasearch@npm:^5.14.2": - version: 5.46.3 - resolution: "algoliasearch@npm:5.46.3" - dependencies: - "@algolia/abtesting": 1.12.3 - "@algolia/client-abtesting": 5.46.3 - "@algolia/client-analytics": 5.46.3 - "@algolia/client-common": 5.46.3 - "@algolia/client-insights": 5.46.3 - "@algolia/client-personalization": 5.46.3 - "@algolia/client-query-suggestions": 5.46.3 - "@algolia/client-search": 5.46.3 - "@algolia/ingestion": 1.46.3 - "@algolia/monitoring": 1.46.3 - "@algolia/recommend": 5.46.3 - "@algolia/requester-browser-xhr": 5.46.3 - "@algolia/requester-fetch": 5.46.3 - "@algolia/requester-node-http": 5.46.3 - checksum: 0c405be3213d81b0b50d6fe3d827462ab6e7a2d744ae6d6b843b4b538a7cb62a30a77ae5d1e5b22b9ff66650c5fa6f9295a7907e2fd3f0ed00125e55e8718e87 - languageName: node - linkType: hard - "ansi-align@npm:^3.0.1": version: 3.0.1 resolution: "ansi-align@npm:3.0.1" @@ -1790,13 +771,6 @@ __metadata: languageName: node linkType: hard -"birpc@npm:^2.3.0": - version: 2.9.0 - resolution: "birpc@npm:2.9.0" - checksum: 499cba9d33ae6749c199b4dc1fc30faeb72aabd41d5c75d73d1d415691f65f0b613d787f21aa2369607a555a5e09c55e437e02b14bc6bad67bb657f8ae58f3dd - languageName: node - linkType: hard - "boxen@npm:^8.0.1": version: 8.0.1 resolution: "boxen@npm:8.0.1" @@ -1891,13 +865,6 @@ __metadata: languageName: node linkType: hard -"ccount@npm:^2.0.0": - version: 2.0.1 - resolution: "ccount@npm:2.0.1" - checksum: 48193dada54c9e260e0acf57fc16171a225305548f9ad20d5471e0f7a8c026aedd8747091dccb0d900cde7df4e4ddbd235df0d8de4a64c71b12f0d3303eeafd4 - languageName: node - linkType: hard - "chalk@npm:^4.0.0": version: 4.1.2 resolution: "chalk@npm:4.1.2" @@ -1915,20 +882,6 @@ __metadata: languageName: node linkType: hard -"character-entities-html4@npm:^2.0.0": - version: 2.1.0 - resolution: "character-entities-html4@npm:2.1.0" - checksum: 7034aa7c7fa90309667f6dd50499c8a760c3d3a6fb159adb4e0bada0107d194551cdbad0714302f62d06ce4ed68565c8c2e15fdef2e8f8764eb63fa92b34b11d - languageName: node - linkType: hard - -"character-entities-legacy@npm:^3.0.0": - version: 3.0.0 - resolution: "character-entities-legacy@npm:3.0.0" - checksum: 7582af055cb488b626d364b7d7a4e46b06abd526fb63c0e4eb35bcb9c9799cc4f76b39f34fdccef2d1174ac95e53e9ab355aae83227c1a2505877893fce77731 - languageName: node - linkType: hard - "chownr@npm:^3.0.0": version: 3.0.0 resolution: "chownr@npm:3.0.0" @@ -1968,13 +921,6 @@ __metadata: languageName: node linkType: hard -"comma-separated-tokens@npm:^2.0.0": - version: 2.0.3 - resolution: "comma-separated-tokens@npm:2.0.3" - checksum: e3bf9e0332a5c45f49b90e79bcdb4a7a85f28d6a6f0876a94f1bb9b2bfbdbbb9292aac50e1e742d8c0db1e62a0229a106f57917e2d067fca951d81737651700d - languageName: node - linkType: hard - "concat-map@npm:0.0.1": version: 0.0.1 resolution: "concat-map@npm:0.0.1" @@ -1982,15 +928,6 @@ __metadata: languageName: node linkType: hard -"copy-anything@npm:^4": - version: 4.0.5 - resolution: "copy-anything@npm:4.0.5" - dependencies: - is-what: ^5.2.0 - checksum: e2ccac1a26a119c4a8fd68503c1ba1b2065d75793ef63eb492092d71afaf5ac492802a57572e3d88d9e219a92f8b68acb121b344e3560e9f0f3a99ef973133fe - languageName: node - linkType: hard - "cross-spawn@npm:^7.0.6": version: 7.0.6 resolution: "cross-spawn@npm:7.0.6" @@ -2002,13 +939,6 @@ __metadata: languageName: node linkType: hard -"csstype@npm:^3.2.3": - version: 3.2.3 - resolution: "csstype@npm:3.2.3" - checksum: cb882521b3398958a1ce6ca98c011aec0bde1c77ecaf8a1dd4db3b112a189939beae3b1308243b2fe50fc27eb3edeb0f73a5a4d91d928765dc6d5ecc7bda92ee - languageName: node - linkType: hard - "debug@npm:4, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4": version: 4.4.3 resolution: "debug@npm:4.4.3" @@ -2035,22 +965,6 @@ __metadata: languageName: node linkType: hard -"dequal@npm:^2.0.0": - version: 2.0.3 - resolution: "dequal@npm:2.0.3" - checksum: 8679b850e1a3d0ebbc46ee780d5df7b478c23f335887464023a631d1b9af051ad4a6595a44220f9ff8ff95a8ddccf019b5ad778a976fd7bbf77383d36f412f90 - languageName: node - linkType: hard - -"devlop@npm:^1.0.0": - version: 1.1.0 - resolution: "devlop@npm:1.1.0" - dependencies: - dequal: ^2.0.0 - checksum: d2ff650bac0bb6ef08c48f3ba98640bb5fec5cce81e9957eb620408d1bab1204d382a45b785c6b3314dc867bb0684936b84c6867820da6db97cbb5d3c15dd185 - languageName: node - linkType: hard - "dunder-proto@npm:^1.0.1": version: 1.0.1 resolution: "dunder-proto@npm:1.0.1" @@ -2062,13 +976,6 @@ __metadata: languageName: node linkType: hard -"emoji-regex-xs@npm:^1.0.0": - version: 1.0.0 - resolution: "emoji-regex-xs@npm:1.0.0" - checksum: c33be159da769836f83281f2802d90169093ebf3c2c1643d6801d891c53beac5ef785fd8279f9b02fa6dc6c47c367818e076949f1e13bd1b3f921b416de4cbea - languageName: node - linkType: hard - "emoji-regex@npm:^10.3.0": version: 10.6.0 resolution: "emoji-regex@npm:10.6.0" @@ -2101,13 +1008,6 @@ __metadata: languageName: node linkType: hard -"entities@npm:^7.0.0": - version: 7.0.0 - resolution: "entities@npm:7.0.0" - checksum: 9305101f6d34fa6d255afb9ccd35f27cd67e0eaf1299374d20cba6e7c4bb26adae3ab879d33b524db74a2bbf57b9416fe04698a60bf2d9d1c1c811e16e205270 - languageName: node - linkType: hard - "env-paths@npm:^2.2.0": version: 2.2.1 resolution: "env-paths@npm:2.2.1" @@ -2151,89 +1051,9 @@ __metadata: dependencies: es-errors: ^1.3.0 get-intrinsic: ^1.2.6 - has-tostringtag: ^1.0.2 - hasown: ^2.0.2 - checksum: 789f35de4be3dc8d11fdcb91bc26af4ae3e6d602caa93299a8c45cf05d36cc5081454ae2a6d3afa09cceca214b76c046e4f8151e092e6fc7feeb5efb9e794fc6 - languageName: node - linkType: hard - -"esbuild@npm:^0.21.3": - version: 0.21.5 - resolution: "esbuild@npm:0.21.5" - dependencies: - "@esbuild/aix-ppc64": 0.21.5 - "@esbuild/android-arm": 0.21.5 - "@esbuild/android-arm64": 0.21.5 - "@esbuild/android-x64": 0.21.5 - "@esbuild/darwin-arm64": 0.21.5 - "@esbuild/darwin-x64": 0.21.5 - "@esbuild/freebsd-arm64": 0.21.5 - "@esbuild/freebsd-x64": 0.21.5 - "@esbuild/linux-arm": 0.21.5 - "@esbuild/linux-arm64": 0.21.5 - "@esbuild/linux-ia32": 0.21.5 - "@esbuild/linux-loong64": 0.21.5 - "@esbuild/linux-mips64el": 0.21.5 - "@esbuild/linux-ppc64": 0.21.5 - "@esbuild/linux-riscv64": 0.21.5 - "@esbuild/linux-s390x": 0.21.5 - "@esbuild/linux-x64": 0.21.5 - "@esbuild/netbsd-x64": 0.21.5 - "@esbuild/openbsd-x64": 0.21.5 - "@esbuild/sunos-x64": 0.21.5 - "@esbuild/win32-arm64": 0.21.5 - "@esbuild/win32-ia32": 0.21.5 - "@esbuild/win32-x64": 0.21.5 - dependenciesMeta: - "@esbuild/aix-ppc64": - optional: true - "@esbuild/android-arm": - optional: true - "@esbuild/android-arm64": - optional: true - "@esbuild/android-x64": - optional: true - "@esbuild/darwin-arm64": - optional: true - "@esbuild/darwin-x64": - optional: true - "@esbuild/freebsd-arm64": - optional: true - "@esbuild/freebsd-x64": - optional: true - "@esbuild/linux-arm": - optional: true - "@esbuild/linux-arm64": - optional: true - "@esbuild/linux-ia32": - optional: true - "@esbuild/linux-loong64": - optional: true - "@esbuild/linux-mips64el": - optional: true - "@esbuild/linux-ppc64": - optional: true - "@esbuild/linux-riscv64": - optional: true - "@esbuild/linux-s390x": - optional: true - "@esbuild/linux-x64": - optional: true - "@esbuild/netbsd-x64": - optional: true - "@esbuild/openbsd-x64": - optional: true - "@esbuild/sunos-x64": - optional: true - "@esbuild/win32-arm64": - optional: true - "@esbuild/win32-ia32": - optional: true - "@esbuild/win32-x64": - optional: true - bin: - esbuild: bin/esbuild - checksum: 2911c7b50b23a9df59a7d6d4cdd3a4f85855787f374dce751148dbb13305e0ce7e880dde1608c2ab7a927fc6cec3587b80995f7fc87a64b455f8b70b55fd8ec1 + has-tostringtag: ^1.0.2 + hasown: ^2.0.2 + checksum: 789f35de4be3dc8d11fdcb91bc26af4ae3e6d602caa93299a8c45cf05d36cc5081454ae2a6d3afa09cceca214b76c046e4f8151e092e6fc7feeb5efb9e794fc6 languageName: node linkType: hard @@ -2376,13 +1196,6 @@ __metadata: languageName: node linkType: hard -"estree-walker@npm:^2.0.2": - version: 2.0.2 - resolution: "estree-walker@npm:2.0.2" - checksum: 6151e6f9828abe2259e57f5fd3761335bb0d2ebd76dc1a01048ccee22fabcfef3c0859300f6d83ff0d1927849368775ec5a6d265dde2f6de5a1be1721cd94efc - languageName: node - linkType: hard - "esutils@npm:^2.0.2": version: 2.0.3 resolution: "esutils@npm:2.0.3" @@ -2498,15 +1311,6 @@ __metadata: languageName: node linkType: hard -"focus-trap@npm:^7.6.4": - version: 7.8.0 - resolution: "focus-trap@npm:7.8.0" - dependencies: - tabbable: ^6.4.0 - checksum: 1eccd8332b694ba8c4d0a97101471004a23634a9b3650311460064856b8795136a6aec276c02f1ec01dd23e27848a0db69bec087e347403e23ee55b52d6bbc7e - languageName: node - linkType: hard - "form-data@npm:^4.0.0, form-data@npm:^4.0.4": version: 4.0.5 resolution: "form-data@npm:4.0.5" @@ -2550,16 +1354,6 @@ __metadata: languageName: node linkType: hard -"fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": - version: 2.3.3 - resolution: "fsevents@npm:2.3.3" - dependencies: - node-gyp: latest - checksum: 11e6ea6fea15e42461fc55b4b0e4a0a3c654faa567f1877dbd353f39156f69def97a69936d1746619d656c4b93de2238bf731f6085a03a50cabf287c9d024317 - conditions: os=darwin - languageName: node - linkType: hard - "fsevents@patch:fsevents@2.3.2#~builtin": version: 2.3.2 resolution: "fsevents@patch:fsevents@npm%3A2.3.2#~builtin::version=2.3.2&hash=df0bf1" @@ -2569,15 +1363,6 @@ __metadata: languageName: node linkType: hard -"fsevents@patch:fsevents@~2.3.2#~builtin, fsevents@patch:fsevents@~2.3.3#~builtin": - version: 2.3.3 - resolution: "fsevents@patch:fsevents@npm%3A2.3.3#~builtin::version=2.3.3&hash=df0bf1" - dependencies: - node-gyp: latest - conditions: os=darwin - languageName: node - linkType: hard - "function-bind@npm:^1.1.2": version: 1.1.2 resolution: "function-bind@npm:1.1.2" @@ -2710,41 +1495,6 @@ __metadata: languageName: node linkType: hard -"hast-util-to-html@npm:^9.0.4": - version: 9.0.5 - resolution: "hast-util-to-html@npm:9.0.5" - dependencies: - "@types/hast": ^3.0.0 - "@types/unist": ^3.0.0 - ccount: ^2.0.0 - comma-separated-tokens: ^2.0.0 - hast-util-whitespace: ^3.0.0 - html-void-elements: ^3.0.0 - mdast-util-to-hast: ^13.0.0 - property-information: ^7.0.0 - space-separated-tokens: ^2.0.0 - stringify-entities: ^4.0.0 - zwitch: ^2.0.4 - checksum: 1ebd013ad340cf646ea944100427917747f69543800e79b2186521dc29c205b4fe75d8062f3eddedf6d66f6180ca06fe127b9e53ff15a8f3579e36637ca43e16 - languageName: node - linkType: hard - -"hast-util-whitespace@npm:^3.0.0": - version: 3.0.0 - resolution: "hast-util-whitespace@npm:3.0.0" - dependencies: - "@types/hast": ^3.0.0 - checksum: 41d93ccce218ba935dc3c12acdf586193c35069489c8c8f50c2aa824c00dec94a3c78b03d1db40fa75381942a189161922e4b7bca700b3a2cc779634c351a1e4 - languageName: node - linkType: hard - -"hookable@npm:^5.5.3": - version: 5.5.3 - resolution: "hookable@npm:5.5.3" - checksum: df659977888398649b6ef8c4470719e7e8384a1d939a6587e332e86fd55b3881806e2f8aaebaabdb4f218f74b83b98f2110e143df225e16d62a39dc271e7e288 - languageName: node - linkType: hard - "hpagent@npm:^1.2.0": version: 1.2.0 resolution: "hpagent@npm:1.2.0" @@ -2752,13 +1502,6 @@ __metadata: languageName: node linkType: hard -"html-void-elements@npm:^3.0.0": - version: 3.0.0 - resolution: "html-void-elements@npm:3.0.0" - checksum: 59be397525465a7489028afa064c55763d9cccd1d7d9f630cca47137317f0e897a9ca26cef7e745e7cff1abc44260cfa407742b243a54261dfacd42230e94fce - languageName: node - linkType: hard - "http-cache-semantics@npm:^4.1.1": version: 4.2.0 resolution: "http-cache-semantics@npm:4.2.0" @@ -2863,13 +1606,6 @@ __metadata: languageName: node linkType: hard -"is-what@npm:^5.2.0": - version: 5.5.0 - resolution: "is-what@npm:5.5.0" - checksum: 8416464cf65a2b57512b18437f672e035e24fcda3af7de47a0f5183c2c46f30ce6e57b6ca35d07a81a04d829357a97ef5e5aa45bf963a5c2d7bf869f9ceebc56 - languageName: node - linkType: hard - "isexe@npm:^2.0.0": version: 2.0.0 resolution: "isexe@npm:2.0.0" @@ -3029,15 +1765,6 @@ __metadata: languageName: node linkType: hard -"magic-string@npm:^0.30.21": - version: 0.30.21 - resolution: "magic-string@npm:0.30.21" - dependencies: - "@jridgewell/sourcemap-codec": ^1.5.5 - checksum: 4ff76a4e8d439431cf49f039658751ed351962d044e5955adc257489569bd676019c906b631f86319217689d04815d7d064ee3ff08ab82ae65b7655a7e82a414 - languageName: node - linkType: hard - "make-fetch-happen@npm:^15.0.0": version: 15.0.3 resolution: "make-fetch-happen@npm:15.0.3" @@ -3057,13 +1784,6 @@ __metadata: languageName: node linkType: hard -"mark.js@npm:8.11.1": - version: 8.11.1 - resolution: "mark.js@npm:8.11.1" - checksum: aa6b9ae1c67245348d5b7abd253ef2acd6bb05c6be358d7d192416d964e42665fc10e0e865591c6f93ab9b57e8da1f23c23216e8ebddb580905ea7a0c0df15d4 - languageName: node - linkType: hard - "math-intrinsics@npm:^1.1.0": version: 1.1.0 resolution: "math-intrinsics@npm:1.1.0" @@ -3071,65 +1791,6 @@ __metadata: languageName: node linkType: hard -"mdast-util-to-hast@npm:^13.0.0": - version: 13.2.1 - resolution: "mdast-util-to-hast@npm:13.2.1" - dependencies: - "@types/hast": ^3.0.0 - "@types/mdast": ^4.0.0 - "@ungap/structured-clone": ^1.0.0 - devlop: ^1.0.0 - micromark-util-sanitize-uri: ^2.0.0 - trim-lines: ^3.0.0 - unist-util-position: ^5.0.0 - unist-util-visit: ^5.0.0 - vfile: ^6.0.0 - checksum: 20537df653be3653c3c6ea4be09ea1f67ca2f5e6afea027fcc3cde531656dc669a5e733d34a95b08b3ee71ab164c7b24352c8212891f723ddcec74d5a046bfd6 - languageName: node - linkType: hard - -"micromark-util-character@npm:^2.0.0": - version: 2.1.1 - resolution: "micromark-util-character@npm:2.1.1" - dependencies: - micromark-util-symbol: ^2.0.0 - micromark-util-types: ^2.0.0 - checksum: e9e409efe4f2596acd44587e8591b722bfc041c1577e8fe0d9c007a4776fb800f9b3637a22862ad2ba9489f4bdf72bb547fce5767dbbfe0a5e6760e2a21c6495 - languageName: node - linkType: hard - -"micromark-util-encode@npm:^2.0.0": - version: 2.0.1 - resolution: "micromark-util-encode@npm:2.0.1" - checksum: be890b98e78dd0cdd953a313f4148c4692cc2fb05533e56fef5f421287d3c08feee38ca679f318e740530791fc251bfe8c80efa926fcceb4419b269c9343d226 - languageName: node - linkType: hard - -"micromark-util-sanitize-uri@npm:^2.0.0": - version: 2.0.1 - resolution: "micromark-util-sanitize-uri@npm:2.0.1" - dependencies: - micromark-util-character: ^2.0.0 - micromark-util-encode: ^2.0.0 - micromark-util-symbol: ^2.0.0 - checksum: d01517840c17de67aaa0b0f03bfe05fac8a41d99723cd8ce16c62f6810e99cd3695364a34c335485018e5e2c00e69031744630a1b85c6868aa2f2ca1b36daa2f - languageName: node - linkType: hard - -"micromark-util-symbol@npm:^2.0.0": - version: 2.0.1 - resolution: "micromark-util-symbol@npm:2.0.1" - checksum: fb7346950550bc85a55793dda94a8b3cb3abc068dbd7570d1162db7aee803411d06c0a5de4ae59cd945f46143bdeadd4bba02a02248fa0d18cc577babaa00044 - languageName: node - linkType: hard - -"micromark-util-types@npm:^2.0.0": - version: 2.0.2 - resolution: "micromark-util-types@npm:2.0.2" - checksum: 884f7974839e4bc6d2bd662e57c973a9164fd5c0d8fe16cddf07472b86a7e6726747c00674952c0321d17685d700cd3295e9f58a842a53acdf6c6d55ab051aab - languageName: node - linkType: hard - "micromatch@npm:^4.0.8": version: 4.0.8 resolution: "micromatch@npm:4.0.8" @@ -3250,13 +1911,6 @@ __metadata: languageName: node linkType: hard -"minisearch@npm:^7.1.1": - version: 7.2.0 - resolution: "minisearch@npm:7.2.0" - checksum: 556ff91d6edf88b955d3b455e753e620320f0f4ab6fc81ce12e7bbec6721db9cf799933c3e4aa4acffc68c4216459bb2d22c09918a8b20bd43424b93a2f11040 - languageName: node - linkType: hard - "minizlib@npm:^3.0.1, minizlib@npm:^3.1.0": version: 3.1.0 resolution: "minizlib@npm:3.1.0" @@ -3266,13 +1920,6 @@ __metadata: languageName: node linkType: hard -"mitt@npm:^3.0.1": - version: 3.0.1 - resolution: "mitt@npm:3.0.1" - checksum: b55a489ac9c2949ab166b7f060601d3b6d893a852515ae9eca4e11df01c013876df777ea109317622b5c1c60e8aae252558e33c8c94e14124db38f64a39614b1 - languageName: node - linkType: hard - "ms@npm:^2.1.3": version: 2.1.3 resolution: "ms@npm:2.1.3" @@ -3280,15 +1927,6 @@ __metadata: languageName: node linkType: hard -"nanoid@npm:^3.3.11": - version: 3.3.11 - resolution: "nanoid@npm:3.3.11" - bin: - nanoid: bin/nanoid.cjs - checksum: 3be20d8866a57a6b6d218e82549711c8352ed969f9ab3c45379da28f405363ad4c9aeb0b39e9abc101a529ca65a72ff9502b00bf74a912c4b64a9d62dfd26c29 - languageName: node - linkType: hard - "natural-compare@npm:^1.4.0": version: 1.4.0 resolution: "natural-compare@npm:1.4.0" @@ -3364,17 +2002,6 @@ __metadata: languageName: node linkType: hard -"oniguruma-to-es@npm:^3.1.0": - version: 3.1.1 - resolution: "oniguruma-to-es@npm:3.1.1" - dependencies: - emoji-regex-xs: ^1.0.0 - regex: ^6.0.1 - regex-recursion: ^6.0.2 - checksum: 2bd1b227d199292f5ad9436b24dbe44bbaf1b516c3e2447e97f6d60ff0d35b9d0b83559ea187ae73b439818d3bfe9bde3820f219e9757a8048f21b692986d375 - languageName: node - linkType: hard - "openid-client@npm:^6.1.3": version: 6.8.1 resolution: "openid-client@npm:6.8.1" @@ -3468,20 +2095,6 @@ __metadata: languageName: node linkType: hard -"perfect-debounce@npm:^1.0.0": - version: 1.0.0 - resolution: "perfect-debounce@npm:1.0.0" - checksum: 220343acf52976947958fef3599849471605316e924fe19c633ae2772576298e9d38f02cefa8db46f06607505ce7b232cbb35c9bfd477bd0329bd0a2ce37c594 - languageName: node - linkType: hard - -"picocolors@npm:^1.1.1": - version: 1.1.1 - resolution: "picocolors@npm:1.1.1" - checksum: e1cf46bf84886c79055fdfa9dcb3e4711ad259949e3565154b004b260cd356c5d54b31a1437ce9782624bf766272fe6b0154f5f0c744fb7af5d454d2b60db045 - languageName: node - linkType: hard - "picomatch@npm:^2.3.1": version: 2.3.1 resolution: "picomatch@npm:2.3.1" @@ -3520,24 +2133,6 @@ __metadata: languageName: node linkType: hard -"postcss@npm:^8.4.43, postcss@npm:^8.5.6": - version: 8.5.6 - resolution: "postcss@npm:8.5.6" - dependencies: - nanoid: ^3.3.11 - picocolors: ^1.1.1 - source-map-js: ^1.2.1 - checksum: 20f3b5d673ffeec2b28d65436756d31ee33f65b0a8bedb3d32f556fbd5973be38c3a7fb5b959a5236c60a5db7b91b0a6b14ffaac0d717dce1b903b964ee1c1bb - languageName: node - linkType: hard - -"preact@npm:^10.0.0": - version: 10.28.2 - resolution: "preact@npm:10.28.2" - checksum: 5f65087ab00ab270ca514e43b5aaba79101da92eb2ea3199a2b54776a159032b0ee4749a529f624ccc7d456c71b56ddeed2cbcd92cd1e6a3b4dcecfaa7cacf36 - languageName: node - linkType: hard - "prelude-ls@npm:^1.2.1": version: 1.2.1 resolution: "prelude-ls@npm:1.2.1" @@ -3571,13 +2166,6 @@ __metadata: languageName: node linkType: hard -"property-information@npm:^7.0.0": - version: 7.1.0 - resolution: "property-information@npm:7.1.0" - checksum: 3875161d204bac89d75181f6d3ebc3ecaeb2699b4e2ecfcf5452201d7cdd275168c6742d7ff8cec5ab0c342fae72369ac705e1f8e9680a9acd911692e80dfb88 - languageName: node - linkType: hard - "pump@npm:^3.0.0": version: 3.0.3 resolution: "pump@npm:3.0.3" @@ -3595,31 +2183,6 @@ __metadata: languageName: node linkType: hard -"regex-recursion@npm:^6.0.2": - version: 6.0.2 - resolution: "regex-recursion@npm:6.0.2" - dependencies: - regex-utilities: ^2.3.0 - checksum: 29913751ee2e41d3d66c957136ba386046f5e9f780f4be482ad3b64b04258bbb2cebe87f8762c5247eb8c9fa46a5ecf18aba5d888f7def73ac8dea49165193d4 - languageName: node - linkType: hard - -"regex-utilities@npm:^2.3.0": - version: 2.3.0 - resolution: "regex-utilities@npm:2.3.0" - checksum: 41408777df45cefe1b276281030213235aa1143809c4c10eb5573d2cc27ff2c4aa746c6f4d4c235e3d2f4830eff76b28906ce82fbe72895beca8e15204c2da51 - languageName: node - linkType: hard - -"regex@npm:^6.0.1": - version: 6.1.0 - resolution: "regex@npm:6.1.0" - dependencies: - regex-utilities: ^2.3.0 - checksum: 8ea9656dbafe8f324f605653b75ce954e9d695b4bb54dbf1fea20d8095b7530f4552bc3e7890b58542441ee2287f64a35d512601fb3417ba542a7a20f48fb855 - languageName: node - linkType: hard - "require-from-string@npm:^2.0.2": version: 2.0.2 resolution: "require-from-string@npm:2.0.2" @@ -3648,13 +2211,6 @@ __metadata: languageName: node linkType: hard -"rfdc@npm:^1.4.1": - version: 1.4.1 - resolution: "rfdc@npm:1.4.1" - checksum: 3b05bd55062c1d78aaabfcea43840cdf7e12099968f368e9a4c3936beb744adb41cbdb315eac6d4d8c6623005d6f87fdf16d8a10e1ff3722e84afea7281c8d13 - languageName: node - linkType: hard - "rhdh-e2e-test-utils@workspace:.": version: 0.0.0-use.local resolution: "rhdh-e2e-test-utils@workspace:." @@ -3680,103 +2236,12 @@ __metadata: prettier: ^3.7.4 typescript: ^5.9.3 typescript-eslint: ^8.48.1 - vitepress: ^1.5.0 zx: ^8.8.5 peerDependencies: "@playwright/test": ^1.57.0 languageName: unknown linkType: soft -"rollup@npm:^4.20.0": - version: 4.55.1 - resolution: "rollup@npm:4.55.1" - dependencies: - "@rollup/rollup-android-arm-eabi": 4.55.1 - "@rollup/rollup-android-arm64": 4.55.1 - "@rollup/rollup-darwin-arm64": 4.55.1 - "@rollup/rollup-darwin-x64": 4.55.1 - "@rollup/rollup-freebsd-arm64": 4.55.1 - "@rollup/rollup-freebsd-x64": 4.55.1 - "@rollup/rollup-linux-arm-gnueabihf": 4.55.1 - "@rollup/rollup-linux-arm-musleabihf": 4.55.1 - "@rollup/rollup-linux-arm64-gnu": 4.55.1 - "@rollup/rollup-linux-arm64-musl": 4.55.1 - "@rollup/rollup-linux-loong64-gnu": 4.55.1 - "@rollup/rollup-linux-loong64-musl": 4.55.1 - "@rollup/rollup-linux-ppc64-gnu": 4.55.1 - "@rollup/rollup-linux-ppc64-musl": 4.55.1 - "@rollup/rollup-linux-riscv64-gnu": 4.55.1 - "@rollup/rollup-linux-riscv64-musl": 4.55.1 - "@rollup/rollup-linux-s390x-gnu": 4.55.1 - "@rollup/rollup-linux-x64-gnu": 4.55.1 - "@rollup/rollup-linux-x64-musl": 4.55.1 - "@rollup/rollup-openbsd-x64": 4.55.1 - "@rollup/rollup-openharmony-arm64": 4.55.1 - "@rollup/rollup-win32-arm64-msvc": 4.55.1 - "@rollup/rollup-win32-ia32-msvc": 4.55.1 - "@rollup/rollup-win32-x64-gnu": 4.55.1 - "@rollup/rollup-win32-x64-msvc": 4.55.1 - "@types/estree": 1.0.8 - fsevents: ~2.3.2 - dependenciesMeta: - "@rollup/rollup-android-arm-eabi": - optional: true - "@rollup/rollup-android-arm64": - optional: true - "@rollup/rollup-darwin-arm64": - optional: true - "@rollup/rollup-darwin-x64": - optional: true - "@rollup/rollup-freebsd-arm64": - optional: true - "@rollup/rollup-freebsd-x64": - optional: true - "@rollup/rollup-linux-arm-gnueabihf": - optional: true - "@rollup/rollup-linux-arm-musleabihf": - optional: true - "@rollup/rollup-linux-arm64-gnu": - optional: true - "@rollup/rollup-linux-arm64-musl": - optional: true - "@rollup/rollup-linux-loong64-gnu": - optional: true - "@rollup/rollup-linux-loong64-musl": - optional: true - "@rollup/rollup-linux-ppc64-gnu": - optional: true - "@rollup/rollup-linux-ppc64-musl": - optional: true - "@rollup/rollup-linux-riscv64-gnu": - optional: true - "@rollup/rollup-linux-riscv64-musl": - optional: true - "@rollup/rollup-linux-s390x-gnu": - optional: true - "@rollup/rollup-linux-x64-gnu": - optional: true - "@rollup/rollup-linux-x64-musl": - optional: true - "@rollup/rollup-openbsd-x64": - optional: true - "@rollup/rollup-openharmony-arm64": - optional: true - "@rollup/rollup-win32-arm64-msvc": - optional: true - "@rollup/rollup-win32-ia32-msvc": - optional: true - "@rollup/rollup-win32-x64-gnu": - optional: true - "@rollup/rollup-win32-x64-msvc": - optional: true - fsevents: - optional: true - bin: - rollup: dist/bin/rollup - checksum: fd5374cd7e6046404d59d64e1346821fee0900bc9cac021078bfdd342fc54305defba882fb53e6543b5c17ce4573b30fb45ee28b9a4122548358004efdc550d2 - languageName: node - linkType: hard - "safer-buffer@npm:>= 2.1.2 < 3.0.0": version: 2.1.2 resolution: "safer-buffer@npm:2.1.2" @@ -3818,22 +2283,6 @@ __metadata: languageName: node linkType: hard -"shiki@npm:^2.1.0": - version: 2.5.0 - resolution: "shiki@npm:2.5.0" - dependencies: - "@shikijs/core": 2.5.0 - "@shikijs/engine-javascript": 2.5.0 - "@shikijs/engine-oniguruma": 2.5.0 - "@shikijs/langs": 2.5.0 - "@shikijs/themes": 2.5.0 - "@shikijs/types": 2.5.0 - "@shikijs/vscode-textmate": ^10.0.2 - "@types/hast": ^3.0.4 - checksum: 53d861dd532a3655b057b920bb1218150f524294c76b1180e68cdb777829686a42ce41cc46331a2780e30ef68cec40e251360d50f75dabf5409f1bfcdc6b9ef9 - languageName: node - linkType: hard - "smart-buffer@npm:^4.2.0": version: 4.2.0 resolution: "smart-buffer@npm:4.2.0" @@ -3862,27 +2311,6 @@ __metadata: languageName: node linkType: hard -"source-map-js@npm:^1.2.1": - version: 1.2.1 - resolution: "source-map-js@npm:1.2.1" - checksum: 4eb0cd997cdf228bc253bcaff9340afeb706176e64868ecd20efbe6efea931465f43955612346d6b7318789e5265bdc419bc7669c1cebe3db0eb255f57efa76b - languageName: node - linkType: hard - -"space-separated-tokens@npm:^2.0.0": - version: 2.0.2 - resolution: "space-separated-tokens@npm:2.0.2" - checksum: 202e97d7ca1ba0758a0aa4fe226ff98142073bcceeff2da3aad037968878552c3bbce3b3231970025375bbba5aee00c5b8206eda408da837ab2dc9c0f26be990 - languageName: node - linkType: hard - -"speakingurl@npm:^14.0.1": - version: 14.0.1 - resolution: "speakingurl@npm:14.0.1" - checksum: 5c7fb81d9b4cbda31f462f424cc2d59d9d07ca07e86f9f4e7b1c6325307646f9b82297891ce7f9e75b4bccf20ac436758e721506461b5cd6e5561e89186aa67b - languageName: node - linkType: hard - "ssri@npm:^13.0.0": version: 13.0.0 resolution: "ssri@npm:13.0.0" @@ -3932,16 +2360,6 @@ __metadata: languageName: node linkType: hard -"stringify-entities@npm:^4.0.0": - version: 4.0.4 - resolution: "stringify-entities@npm:4.0.4" - dependencies: - character-entities-html4: ^2.0.0 - character-entities-legacy: ^3.0.0 - checksum: ac1344ef211eacf6cf0a0a8feaf96f9c36083835b406560d2c6ff5a87406a41b13f2f0b4c570a3b391f465121c4fd6822b863ffb197e8c0601a64097862cc5b5 - languageName: node - linkType: hard - "strip-ansi@npm:^6.0.1": version: 6.0.1 resolution: "strip-ansi@npm:6.0.1" @@ -3967,15 +2385,6 @@ __metadata: languageName: node linkType: hard -"superjson@npm:^2.2.2": - version: 2.2.6 - resolution: "superjson@npm:2.2.6" - dependencies: - copy-anything: ^4 - checksum: d860577809502d058144d1e5fcd383571b4cf0466cab7d7b6fa1675fff598af53148bacd7b3b0075fba0997ed1f05393bde95b3a3727f7708513396b1d88a8b6 - languageName: node - linkType: hard - "supports-color@npm:^7.1.0": version: 7.2.0 resolution: "supports-color@npm:7.2.0" @@ -3985,13 +2394,6 @@ __metadata: languageName: node linkType: hard -"tabbable@npm:^6.4.0": - version: 6.4.0 - resolution: "tabbable@npm:6.4.0" - checksum: 7084cba269ebbc7dcdeed5aca7f90c0a0fb59a295dd1e83703ab89cca5e6c53b78d02020e3d1065481984cd64bba7dd1ea3c0a48e92fdba83d586e6e86d62a74 - languageName: node - linkType: hard - "tar-fs@npm:^3.0.9": version: 3.1.1 resolution: "tar-fs@npm:3.1.1" @@ -4075,13 +2477,6 @@ __metadata: languageName: node linkType: hard -"trim-lines@npm:^3.0.0": - version: 3.0.1 - resolution: "trim-lines@npm:3.0.1" - checksum: e241da104682a0e0d807222cc1496b92e716af4db7a002f4aeff33ae6a0024fef93165d49eab11aa07c71e1347c42d46563f91dfaa4d3fb945aa535cdead53ed - languageName: node - linkType: hard - "ts-api-utils@npm:^2.1.0": version: 2.1.0 resolution: "ts-api-utils@npm:2.1.0" @@ -4174,54 +2569,6 @@ __metadata: languageName: node linkType: hard -"unist-util-is@npm:^6.0.0": - version: 6.0.1 - resolution: "unist-util-is@npm:6.0.1" - dependencies: - "@types/unist": ^3.0.0 - checksum: e57733e1766b55c9a873a42d2f34daa211580788b1bba26af2fc22e48e147bdcff0f9a752ed2a19238864823735fbbe27a1804d6a5a22b182c23aa0191e41c12 - languageName: node - linkType: hard - -"unist-util-position@npm:^5.0.0": - version: 5.0.0 - resolution: "unist-util-position@npm:5.0.0" - dependencies: - "@types/unist": ^3.0.0 - checksum: f89b27989b19f07878de9579cd8db2aa0194c8360db69e2c99bd2124a480d79c08f04b73a64daf01a8fb3af7cba65ff4b45a0b978ca243226084ad5f5d441dde - languageName: node - linkType: hard - -"unist-util-stringify-position@npm:^4.0.0": - version: 4.0.0 - resolution: "unist-util-stringify-position@npm:4.0.0" - dependencies: - "@types/unist": ^3.0.0 - checksum: e2e7aee4b92ddb64d314b4ac89eef7a46e4c829cbd3ee4aee516d100772b490eb6b4974f653ba0717a0071ca6ea0770bf22b0a2ea62c65fcba1d071285e96324 - languageName: node - linkType: hard - -"unist-util-visit-parents@npm:^6.0.0": - version: 6.0.2 - resolution: "unist-util-visit-parents@npm:6.0.2" - dependencies: - "@types/unist": ^3.0.0 - unist-util-is: ^6.0.0 - checksum: cf28578a6f0b81877965e261fe82460f83b8c3a9cab3b2080c046b215f3223c6195b01064256619ca3411a1930face93a1a2a72d34d8716e684d6cd59f53cd9a - languageName: node - linkType: hard - -"unist-util-visit@npm:^5.0.0": - version: 5.0.0 - resolution: "unist-util-visit@npm:5.0.0" - dependencies: - "@types/unist": ^3.0.0 - unist-util-is: ^6.0.0 - unist-util-visit-parents: ^6.0.0 - checksum: 9ec42e618e7e5d0202f3c191cd30791b51641285732767ee2e6bcd035931032e3c1b29093f4d7fd0c79175bbc1f26f24f26ee49770d32be76f8730a652a857e6 - languageName: node - linkType: hard - "universalify@npm:^2.0.0": version: 2.0.1 resolution: "universalify@npm:2.0.1" @@ -4245,123 +2592,6 @@ __metadata: languageName: node linkType: hard -"vfile-message@npm:^4.0.0": - version: 4.0.3 - resolution: "vfile-message@npm:4.0.3" - dependencies: - "@types/unist": ^3.0.0 - unist-util-stringify-position: ^4.0.0 - checksum: f5e8516f2aa0feb4c866d507543d4e90f9ab309e2c988577dbf4ebd268d495f72f2b48149849d14300164d5d60b5f74b5641cd285bb4408a3942b758683d9276 - languageName: node - linkType: hard - -"vfile@npm:^6.0.0": - version: 6.0.3 - resolution: "vfile@npm:6.0.3" - dependencies: - "@types/unist": ^3.0.0 - vfile-message: ^4.0.0 - checksum: 152b6729be1af70df723efb65c1a1170fd483d41086557da3651eea69a1dd1f0c22ea4344834d56d30734b9185bcab63e22edc81d3f0e9bed8aa4660d61080af - languageName: node - linkType: hard - -"vite@npm:^5.4.14": - version: 5.4.21 - resolution: "vite@npm:5.4.21" - dependencies: - esbuild: ^0.21.3 - fsevents: ~2.3.3 - postcss: ^8.4.43 - rollup: ^4.20.0 - peerDependencies: - "@types/node": ^18.0.0 || >=20.0.0 - less: "*" - lightningcss: ^1.21.0 - sass: "*" - sass-embedded: "*" - stylus: "*" - sugarss: "*" - terser: ^5.4.0 - dependenciesMeta: - fsevents: - optional: true - peerDependenciesMeta: - "@types/node": - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - bin: - vite: bin/vite.js - checksum: 7177fa03cff6a382f225290c9889a0d0e944d17eab705bcba89b58558a6f7adfa1f47e469b88f42a044a0eb40c12a1bf68b3cb42abb5295d04f9d7d4dd320837 - languageName: node - linkType: hard - -"vitepress@npm:^1.5.0": - version: 1.6.4 - resolution: "vitepress@npm:1.6.4" - dependencies: - "@docsearch/css": 3.8.2 - "@docsearch/js": 3.8.2 - "@iconify-json/simple-icons": ^1.2.21 - "@shikijs/core": ^2.1.0 - "@shikijs/transformers": ^2.1.0 - "@shikijs/types": ^2.1.0 - "@types/markdown-it": ^14.1.2 - "@vitejs/plugin-vue": ^5.2.1 - "@vue/devtools-api": ^7.7.0 - "@vue/shared": ^3.5.13 - "@vueuse/core": ^12.4.0 - "@vueuse/integrations": ^12.4.0 - focus-trap: ^7.6.4 - mark.js: 8.11.1 - minisearch: ^7.1.1 - shiki: ^2.1.0 - vite: ^5.4.14 - vue: ^3.5.13 - peerDependencies: - markdown-it-mathjax3: ^4 - postcss: ^8 - peerDependenciesMeta: - markdown-it-mathjax3: - optional: true - postcss: - optional: true - bin: - vitepress: bin/vitepress.js - checksum: 75c1a1d7b0c4f901344e108014ebe43f0a515df87c78b1201158a626b6aad7f9ae3dbd54e48e6cbe5602a61aa97b134ab5dffdfad8b4e226113308d5fda91e26 - languageName: node - linkType: hard - -"vue@npm:^3.5.13": - version: 3.5.26 - resolution: "vue@npm:3.5.26" - dependencies: - "@vue/compiler-dom": 3.5.26 - "@vue/compiler-sfc": 3.5.26 - "@vue/runtime-dom": 3.5.26 - "@vue/server-renderer": 3.5.26 - "@vue/shared": 3.5.26 - peerDependencies: - typescript: "*" - peerDependenciesMeta: - typescript: - optional: true - checksum: 1974f374318fbbb5fafde6195285c53c14cc8989ec38aeb6ad91c19598c45dfc6ccea7bdc6682efcb27320e089766bd6245fccd36f66f608e5d81cae8a03653a - languageName: node - linkType: hard - "webidl-conversions@npm:^3.0.0": version: 3.0.1 resolution: "webidl-conversions@npm:3.0.1" @@ -4471,13 +2701,6 @@ __metadata: languageName: node linkType: hard -"zwitch@npm:^2.0.4": - version: 2.0.4 - resolution: "zwitch@npm:2.0.4" - checksum: f22ec5fc2d5f02c423c93d35cdfa83573a3a3bd98c66b927c368ea4d0e7252a500df2a90a6b45522be536a96a73404393c958e945fdba95e6832c200791702b6 - languageName: node - linkType: hard - "zx@npm:^8.8.5": version: 8.8.5 resolution: "zx@npm:8.8.5" From 4de4fe67c3dbc610afd97fdae94973e0b18eead5 Mon Sep 17 00:00:00 2001 From: Subhash Khileri Date: Thu, 15 Jan 2026 18:36:28 +0530 Subject: [PATCH 3/3] fix CI yarn check --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 60c2f4f..16f4e01 100644 --- a/package.json +++ b/package.json @@ -52,8 +52,8 @@ "lint:check": "eslint . --ignore-pattern dist --ignore-pattern README.md --ignore-pattern docs", "lint:fix": "eslint . --fix --ignore-pattern dist --ignore-pattern README.md --ignore-pattern docs", "prepublishOnly": "yarn build", - "prettier:check": "prettier --check . '!dist' '!README.md' '!docs'", - "prettier:fix": "prettier --write . '!dist' '!README.md' '!docs'", + "prettier:check": "prettier --check . '!dist' '!README.md' '!docs' '!.github/workflows/deploy-docs.yml'", + "prettier:fix": "prettier --write . '!dist' '!README.md' '!docs' '!.github/workflows/deploy-docs.yml'", "typecheck": "tsc --noEmit" }, "keywords": [