diff --git a/.cursor/rules/cursor-rule.mdc b/.cursor/rules/cursor-rule.mdc deleted file mode 100644 index 774d83ff2c..0000000000 --- a/.cursor/rules/cursor-rule.mdc +++ /dev/null @@ -1,66 +0,0 @@ ---- -description: How to add or edit Cursor rules in our project -globs: -alwaysApply: false ---- -# Cursor Rules Location - -How to add new cursor rules to the project - -1. Always place rule files in PROJECT_ROOT/.cursor/rules/: - ``` - .cursor/rules/ - ├── your-rule-name.mdc - ├── another-rule.mdc - └── ... - ``` - -2. Follow the naming convention: - - Use kebab-case for filenames - - Always use .mdc extension - - Make names descriptive of the rule's purpose - -3. Directory structure: - ``` - PROJECT_ROOT/ - ├── .cursor/ - │ └── rules/ - │ ├── your-rule-name.mdc - │ └── ... - └── ... - ``` - -4. Never place rule files: - - In the project root - - In subdirectories outside .cursor/rules - - In any other location - -5. Cursor rules have the following structure: - -```` ---- -description: Short description of the rule's purpose -globs: optional/path/pattern/**/* -alwaysApply: false ---- -# Rule Title - -Main content explaining the rule with markdown formatting. - -1. Step-by-step instructions -2. Code examples -3. Guidelines - -Example: -```typescript -// Good example -function goodExample() { - // Implementation following guidelines -} - -// Bad example -function badExample() { - // Implementation not following guidelines -} -``` -```` \ No newline at end of file diff --git a/.cursor/rules/testing/unit-tests-generic.mdc b/.cursor/rules/testing/unit-tests-generic.mdc index 63970b79d5..83a395aad9 100644 --- a/.cursor/rules/testing/unit-tests-generic.mdc +++ b/.cursor/rules/testing/unit-tests-generic.mdc @@ -7,6 +7,22 @@ USE WHEN writing unit tests for components in template packages # 🧪 Generic Component Test Rules +# CRITICAL: AI Attribution Requirements +* **IMPORTANT** All individual test methods generated or modified by Cursor MUST include an AI attribution comment directly above the test stating the following: +"DO NOT REMOVE THIS COMMENT! This test was generated by Cursor". The comment should go on the test method and not the test class. Failure to add an AI Attribution Comment will be considered a failure of test generation. +* The AI attribution comment MUST include a comment declaring the LLM model that was used in writing the test on its own line. + +*Sample AI Attribution Comment* +``` +/* + * DO NOT REMOVE THIS COMMENT! This test was generated by Cursor + * This test was generated with the following model: Claude 3.5 Sonnet + */ +test('renders component correctly', () => { + // test implementation +}) +``` + ## Structure & Best Practices - Use `describe` blocks to group tests, `test` for individual cases - Use `beforeEach` for setup, clear mocks after each test diff --git a/.github/workflows/e2e-pr.yml b/.github/workflows/e2e-pr.yml deleted file mode 100644 index 710b73e62c..0000000000 --- a/.github/workflows/e2e-pr.yml +++ /dev/null @@ -1,112 +0,0 @@ -name: SalesforceCommerceCloud/pwa-kit/e2e-pr -on: - workflow_dispatch: - pull_request: # Default: opened, reopened, synchronize (head branch updated) - merge_group: # Trigger GA workflow when a pull request is added to a merge queue. - push: - branches: - - develop - - 'release-*' - -jobs: - test_e2e_private: - runs-on: ubuntu-latest - steps: - # Skipping the entire workflow for now until all steps are implemented. - - name: Skip Check - run: | - echo "SKIP_WORKFLOW=true" >> "$GITHUB_ENV" - - - name: Checkout - uses: actions/checkout@v4 - - - name: Check PWA Kit Version - run: |- - version=`jq -r ".version" package.json` - echo "pwa_kit_version=$version" >> "$GITHUB_ENV" - - # TODO: Skip the entire workflow since we don't have e2e tests for PWA Kit v2.x - - name: Skip if PWA Kit version older than v3.x - if: ${{ env.SKIP_WORKFLOW != 'true' }} - run: | - major_version=$(echo "${{ env.pwa_kit_version }}" | cut -d. -f1) - if [ "$major_version" -lt 3 ]; then - echo "PWA Kit version is older than v3.x, skipping workflow." - echo "SKIP_WORKFLOW=true" >> "$GITHUB_ENV" - fi - - # Only test for latest Node version supported by MRT - - name: Setup Node - if: ${{ env.SKIP_WORKFLOW != 'true' }} - uses: actions/setup-node@v4 - with: - node-version: 22 - cache: npm - - # Check central resource allocation on AWS and get a lock on an available environment from the pool. - # Returns the MRT target ID if lock is acquired, otherwise returns an error state. - - name: Get MRT Target lock - if: ${{ env.SKIP_WORKFLOW != 'true' }} - id: get_mrt_target_lock - run: | - echo "TODO: Implement .github/actions/get_mrt_target_lock" - - - name: Create MRT target - id: create_mrt_target - if: ${{ env.SKIP_WORKFLOW != 'true' && steps.get_mrt_target_lock.outputs.status == 'ERR_NO_AVAILABLE_TARGETS' }} - run: | - echo "TODO: Call .github/actions/create_mrt_target with correct inputs" - - - name: Get Template Version - if: ${{ env.SKIP_WORKFLOW != 'true' }} - run: |- - version=`jq -r ".version" packages/template-retail-react-app/package.json` - echo "retail_app_template_version=$version" >> "$GITHUB_ENV" - - - name: Generate Retail App Private Client - if: ${{ env.SKIP_WORKFLOW != 'true' }} - uses: ./.github/actions/e2e_generate_app - with: - PROJECT_KEY: 'retail-app-private-client' - TEMPLATE_VERSION: ${{ env.retail_app_template_version }} - - - name: Validate Retail App Without Extensibility - if: ${{ env.SKIP_WORKFLOW != 'true' }} - uses: ./.github/actions/e2e_validate_generated_app - with: - PROJECT_KEY: 'retail-app-no-ext' - TEMPLATE_VERSION: ${{ env.retail_app_template_version }} - - # TODO: Revisit the next 2 steps to see if we can use the existing .github/actions/deploy_app action. - - name: Create MRT credentials file - if: ${{ env.SKIP_WORKFLOW != 'true' }} - uses: './.github/actions/create_mrt' - with: - mobify_user: ${{ secrets.MOBIFY_CLIENT_USER }} - mobify_api_key: ${{ secrets.MOBIFY_CLIENT_API_KEY }} - - - name: Push Bundle to MRT (E2E Test PWA Kit) - if: ${{ env.SKIP_WORKFLOW != 'true' }} - uses: './.github/actions/push_to_mrt' - with: - CWD: '../generated-projects/retail-app-no-ext' - # TODO: Use the MRT target ID from the target lock step above. - TARGET: e2e-tests-pwa-kit - FLAGS: --wait - - - name: Install Playwright Browsers - if: ${{ env.SKIP_WORKFLOW != 'true' }} - run: npx playwright install --with-deps - - - name: Run Playwright tests - if: ${{ env.SKIP_WORKFLOW != 'true' }} - run: npm run test:e2e - - - name: Run Playwright a11y tests - if: ${{ env.SKIP_WORKFLOW != 'true' }} - run: npm run test:e2e:a11y - - - name: Release MRT Target Lock - if: always() # Always release the target lock back to the pool even if the tests fail. - run: | - echo "TODO: Implement .github/actions/release_mrt_target_lock" \ No newline at end of file diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index e5e88f7967..4e2c98fe90 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -411,25 +411,3 @@ jobs: } env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} - - test-extra-features: - 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: npm - - name: Install Monorepo Dependencies - run: node ./scripts/gtime.js monorepo_install npm ci - - name: Install Playwright Browsers - run: npx playwright install --with-deps - - name: Set PWA Kit E2E Test User - run: export PWA_E2E_USER_EMAIL=e2e.pwa.kit@gmail.com PWA_E2E_USER_PASSWORD=hpv_pek-JZK_xkz0wzf - - name: Run extra features tests - env: - PWA_E2E_USER_EMAIL: e2e.pwa.kit@gmail.com - PWA_E2E_USER_PASSWORD: hpv_pek-JZK_xkz0wzf - run: npm run test:e2e:extra_features diff --git a/.github/workflows/sync_extra_features_e2e.yml b/.github/workflows/sync_extra_features_e2e.yml deleted file mode 100644 index 5bfff89a23..0000000000 --- a/.github/workflows/sync_extra_features_e2e.yml +++ /dev/null @@ -1,170 +0,0 @@ -name: Sync Extra Features E2E Branch with Develop - -on: - # Trigger when develop branch is updated - push: - branches: - - develop - # Run daily at 11 PM PST (7 AM UTC) to catch any missed syncs - schedule: - - cron: '0 7 * * *' - workflow_dispatch: - -permissions: - contents: write - issues: write - pull-requests: read - -jobs: - sync-branch: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Configure git - run: | - git config --global user.name ${{ secrets.GIT_CONFIG_USERNAME }} - git config --global user.email ${{ secrets.GIT_CONFIG_EMAIL }} - - - name: Sync extra-features-e2e-branch with develop - id: sync - continue-on-error: true - run: | - set -e - - # Fetch all branches - git fetch origin - - # Check if the target branch exists - if ! git show-ref --verify --quiet refs/remotes/origin/extra-features-e2e-branch; then - echo "Branch extra-features-e2e-branch does not exist. Creating it from develop..." - git checkout -b extra-features-e2e-branch origin/develop - git push origin extra-features-e2e-branch - echo "✅ Created extra-features-e2e-branch branch from develop" - echo "status=created" >> $GITHUB_OUTPUT - exit 0 - fi - - # Switch to the target branch - git checkout extra-features-e2e-branch - git reset --hard origin/extra-features-e2e-branch - - # Check if we're already up to date - if git merge-base --is-ancestor origin/develop HEAD; then - echo "✅ extra-features-e2e-branch is already up to date with develop" - echo "status=up-to-date" >> $GITHUB_OUTPUT - exit 0 - fi - - if git merge origin/develop --no-edit; then - echo "✅ Successfully merged develop into extra-features-e2e-branch" - git push origin extra-features-e2e-branch - echo "status=merged" >> $GITHUB_OUTPUT - else - echo "❌ Merge conflicts detected!" - echo "📋 Files with conflicts:" - git diff --name-only --diff-filter=U || true - git merge --abort - echo "status=conflict" >> $GITHUB_OUTPUT - exit 1 - fi - - - name: Create conflict resolution issue - if: steps.sync.outputs.status == 'conflict' - uses: actions/github-script@v7 - with: - script: | - const conflictFiles = `${{ steps.sync.outputs.conflict_files || 'Unknown files' }}`; - const issueBody = ` - ## 🚨 Automatic Sync Failed - Merge Conflicts Detected - - Some features in PWA kit are defaulted to be off in \`develop\` branch (e.g. if they only work with private client). - Hence there is another site that has these features enabled that E2E tests of these extra features are run against. - The \`extra-features-e2e-branch\` is used for this site. - - A job is run nightly to sync \`develop\` to \`extra-features-e2e-branch\` branch, but today this automatic sync from \`develop\` to \`extra-features-e2e-branch\` failed due to merge conflicts. - - ### Conflicting Files: - \`\`\` - ${conflictFiles} - \`\`\` - - - ### Manual Resolution: - \`\`\`bash - git checkout extra-features-e2e-branch - git pull origin extra-features-e2e-branch - git merge develop - # Resolve conflicts manually - git add . - git commit -m "Resolve merge conflicts from develop" - git push origin extra-features-e2e-branch - \`\`\` - - ### After resolving the conflicts, close this issue - `; - - // Check if issue already exists - const existingIssues = await github.rest.issues.listForRepo({ - owner: context.repo.owner, - repo: context.repo.repo, - state: 'open', - labels: 'sync-conflict' - }); - - if (existingIssues.data.length === 0) { - await github.rest.issues.create({ - owner: context.repo.owner, - repo: context.repo.repo, - title: '🚨 Extra Features E2E Branch Sync Conflict - Manual Resolution Required', - body: issueBody, - labels: ['sync-conflict', 'automation'] - }); - } - - deploy: - needs: sync-branch - if: needs.sync-branch.outputs.status != 'conflict' && needs.sync-branch.outputs.status != 'up-to-date' - runs-on: ubuntu-latest - environment: extra-features-e2e - - steps: - - name: Checkout extra-features-e2e-branch branch - uses: actions/checkout@v4 - with: - ref: extra-features-e2e-branch - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 22 - cache: 'npm' - - - name: Install Monorepo Dependencies - run: | - # Install node dependencies - node ./scripts/gtime.js monorepo_install npm ci - - - name: Build project - run: | - cd packages/template-retail-react-app - npm run build - - - name: Create MRT credentials file - uses: "./.github/actions/create_mrt" - with: - mobify_user: ${{ secrets.MOBIFY_CLIENT_USER }} - mobify_api_key: ${{ secrets.MOBIFY_CLIENT_API_KEY }} - - - name: Deploy to MRT - uses: "./.github/actions/push_to_mrt" - with: - CWD: "./packages/template-retail-react-app" - TARGET: extra-features-e2e - PROJECT: scaffold-pwa - MESSAGE: "Auto-sync from develop - build ${{ github.run_id }} (${{ github.sha }})" - FLAGS: --wait \ No newline at end of file diff --git a/e2e/config.js b/e2e/config.js index 4d2e315bb6..d610da3487 100644 --- a/e2e/config.js +++ b/e2e/config.js @@ -9,7 +9,6 @@ module.exports = { RETAIL_APP_HOME: process.env.RETAIL_APP_HOME || "https://scaffold-pwa-e2e-tests-pwa-kit.mobify-storefront.com", - RETAIL_APP_HOME_SITE: "RefArchGlobal", GENERATED_PROJECTS_DIR: "../generated-projects", GENERATE_PROJECTS: ["retail-app-demo", "retail-app-ext", "retail-app-no-ext"], GENERATOR_CMD: @@ -163,6 +162,5 @@ module.exports = { }, PWA_E2E_USER_EMAIL: process.env.PWA_E2E_USER_EMAIL, PWA_E2E_USER_PASSWORD: process.env.PWA_E2E_USER_PASSWORD, - EXTRA_FEATURES_E2E_RETAIL_APP_HOME: "https://scaffold-pwa-extra-features-e2e.mobify-storefront.com", - EXTRA_FEATURES_E2E_RETAIL_APP_HOME_SITE: "RefArchGlobal" + SOCIAL_LOGIN_RETAIL_APP_HOME: "https://wasatch-mrt-feature-public.mrt-storefront-staging.com" }; diff --git a/e2e/scripts/pageHelpers.js b/e2e/scripts/pageHelpers.js index 6b3f0e1589..4152b20ea8 100644 --- a/e2e/scripts/pageHelpers.js +++ b/e2e/scripts/pageHelpers.js @@ -61,8 +61,8 @@ export const answerConsentTrackingForm = async (page, dnt = false) => { } /** - * Navigates to the `Belted Ribbed Boat Neck Sweater` PDP (Product Detail Page) on mobile - * with the Black variant selected + * Navigates to the `Cotton Turtleneck Sweater` PDP (Product Detail Page) on mobile + * with the black variant selected * * @param {Object} options.page - Object that represents a tab/window in the browser provided by playwright */ @@ -96,29 +96,28 @@ export const navigateToPDPMobile = async ({page}) => { // PLP const productTile = page.getByRole('link', { - name: /Belted Ribbed Boat Neck Sweater/i + name: /Cotton Turtleneck Sweater/i }) await productTile.scrollIntoViewIfNeeded() // selecting swatch const productTileImg = productTile.locator('img') await productTileImg.waitFor({state: 'visible'}) const initialSrc = await productTileImg.getAttribute('src') - await expect(productTile.getByText(/From \£50\.56/i)).toBeVisible() + await expect(productTile.getByText(/From \$39\.99/i)).toBeVisible() - await productTile.getByLabel(/Black/, {exact: true}).hover() + await productTile.getByLabel(/Black/, {exact: true}).click() // Make sure the image src has changed await expect(async () => { const newSrc = await productTileImg.getAttribute('src') expect(newSrc).not.toBe(initialSrc) }).toPass() - await expect(productTile.getByText(/From \£50\.56/i)).toBeVisible() - + await expect(productTile.getByText(/From \$39\.99/i)).toBeVisible() await productTile.click() } /** - * Navigates to the `Belted Ribbed Boat Neck Sweater` PDP (Product Detail Page) on Desktop - * with the Black variant selected. + * Navigates to the `Cotton Turtleneck Sweater` PDP (Product Detail Page) on Desktop + * with the black variant selected. * * @param {Object} options.page - Object that represents a tab/window in the browser provided by playwright */ @@ -134,13 +133,13 @@ export const navigateToPDPDesktop = async ({page}) => { // PLP const productTile = page.getByRole('link', { - name: /Belted Ribbed Boat Neck Sweater/i + name: /Cotton Turtleneck Sweater/i }) // selecting swatch const productTileImg = productTile.locator('img') await productTileImg.waitFor({state: 'visible'}) const initialSrc = await productTileImg.getAttribute('src') - await expect(productTile.getByText(/From \£50\.56/i)).toBeVisible() + await expect(productTile.getByText(/From \$39\.99/i)).toBeVisible() await productTile.getByLabel(/Black/, {exact: true}).hover() // Make sure the image src has changed @@ -148,14 +147,14 @@ export const navigateToPDPDesktop = async ({page}) => { const newSrc = await productTileImg.getAttribute('src') expect(newSrc).not.toBe(initialSrc) }).toPass() - await expect(productTile.getByText(/From \£50\.56/i)).toBeVisible() + await expect(productTile.getByText(/From \$39\.99/i)).toBeVisible() await productTile.click() } /** - * Navigates to the `Belted Ribbed Boat Neck Sweater` PDP (Product Detail Page) on Desktop - * with the Black variant selected. + * Navigates to the `Cotton Turtleneck Sweater` PDP (Product Detail Page) on Desktop + * with the black variant selected. * * @param {Object} options.page - Object that represents a tab/window in the browser provided by playwright */ @@ -165,7 +164,7 @@ export const navigateToPDPDesktopSocial = async ({ productColor, productPrice }) => { - await page.goto(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME) + await page.goto(config.SOCIAL_LOGIN_RETAIL_APP_HOME) await answerConsentTrackingForm(page) await page.getByRole('link', {name: 'Womens'}).hover() @@ -188,15 +187,15 @@ export const navigateToPDPDesktopSocial = async ({ } /** - * Adds the `Belted Ribbed Boat Neck Sweater` product to the cart with the variant: - * Colour: Black - * Size: M + * Adds the `Cotton Turtleneck Sweater` product to the cart with the variant: + * Color: Black + * Size: L * * @param {Object} options.page - Object that represents a tab/window in the browser provided by playwright * @param {Boolean} options.isMobile - Flag to indicate if device type is mobile or not, defaulted to false */ export const addProductToCart = async ({page, isMobile = false}) => { - // Navigate to Belted Ribbed Boat Neck Sweater with Black color variant selected + // Navigate to Cotton Turtleneck Sweater with Black color variant selected if (isMobile) { await navigateToPDPMobile({page}) } else { @@ -204,8 +203,8 @@ export const addProductToCart = async ({page, isMobile = false}) => { } // PDP - await expect(page.getByRole('heading', {name: /Belted Ribbed Boat Neck Sweater/i})).toBeVisible() - await page.getByRole('radio', {name: 'M', exact: true}).click() + await expect(page.getByRole('heading', {name: /Cotton Turtleneck Sweater/i})).toBeVisible() + await page.getByRole('radio', {name: 'L', exact: true}).click() await page.locator("button[data-testid='quantity-increment']").click() @@ -213,8 +212,8 @@ export const addProductToCart = async ({page, isMobile = false}) => { // So we need to look at the page URL to verify selected variants const updatedPageURL = await page.url() const params = updatedPageURL.split('?')[1] - expect(params).toMatch(/size=9MD/i) - expect(params).toMatch(/color=JJ3WCXX&/i) + expect(params).toMatch(/size=9LG/i) + expect(params).toMatch(/color=JJ169XX/i) await page.getByRole('button', {name: /Add to Cart/i}).click() const addedToCartModal = page.getByText(/2 items added to cart/i) @@ -280,7 +279,7 @@ export const registerShopper = async ({page, userCredentials, isMobile = false}) } /** - * Validates that the `Belted Ribbed Boat Neck Sweater` product appears in the Order History page + * Validates that the `Cotton Turtleneck Sweater` product appears in the Order History page * * @param {Object} options.page - Object that represents a tab/window in the browser provided by playwright */ @@ -294,15 +293,16 @@ export const validateOrderHistory = async ({page, a11y = {}}) => { await page.getByRole('link', {name: 'View details'}).click() await expect(page.getByRole('heading', {name: /Order Details/i})).toBeVisible() - await expect(page.getByRole('heading', {name: /Belted Ribbed Boat Neck Sweater/i})).toBeVisible() - await expect(page.getByText(/Size: M/i)).toBeVisible() + await expect(page.getByRole('heading', {name: /Cotton Turtleneck Sweater/i})).toBeVisible() + await expect(page.getByText(/Color: Black/i)).toBeVisible() + await expect(page.getByText(/Size: L/i)).toBeVisible() if (checkA11y) { await runAccessibilityTest(page, [snapShotName, 'order-history-a11y-violations.json']) } } /** - * Validates that the `Belted Ribbed Boat Neck Sweater` product appears in the Wishlist page + * Validates that the `Cotton Turtleneck Sweater` product appears in the Wishlist page * * @param {Object} options.page - Object that represents a tab/window in the browser provided by playwright */ @@ -314,9 +314,9 @@ export const validateWishlist = async ({page, a11y = {}}) => { await expect(page.getByRole('heading', {name: /Wishlist/i})).toBeVisible() - await expect(page.getByRole('heading', {name: /Belted Ribbed Boat Neck Sweater/i})).toBeVisible() - await expect(page.getByText(/Colour: Black/i)).toBeVisible() - await expect(page.getByText(/Size: M/i)).toBeVisible() + await expect(page.getByRole('heading', {name: /Cotton Turtleneck Sweater/i})).toBeVisible() + await expect(page.getByText(/Color: Black/i)).toBeVisible() + await expect(page.getByText(/Size: L/i)).toBeVisible() if (checkA11y) { await runAccessibilityTest(page, [snapShotName, 'wishlist-violations.json']) } @@ -373,7 +373,7 @@ export const loginShopper = async ({page, userCredentials}) => { */ export const socialLoginShopper = async ({page}) => { try { - await page.goto(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME + '/login') + await page.goto(config.SOCIAL_LOGIN_RETAIL_APP_HOME + '/login') await page.getByText(/Google/i).click() await expect(page.getByText(/Sign in with Google/i)).toBeVisible({timeout: 10000}) @@ -543,7 +543,7 @@ export const registeredUserHappyPath = async ({page, registeredUserCredentials, // cart await page.getByLabel(/My cart/i).click() - await expect(page.getByRole('link', {name: 'Belted Ribbed Boat Neck Sweater'})).toBeVisible() + await expect(page.getByRole('link', {name: /Cotton Turtleneck Sweater/i})).toBeVisible() await page.getByRole('link', {name: 'Proceed to Checkout'}).click() @@ -630,7 +630,7 @@ export const registeredUserHappyPath = async ({page, registeredUserCredentials, await expect(page.getByRole('heading', {name: /Order Summary/i})).toBeVisible() await expect(page.getByText(/2 Items/i)).toBeVisible() - await expect(page.getByRole('link', {name: /Belted Ribbed Boat Neck Sweater/i})).toBeVisible() + await expect(page.getByRole('link', {name: /Cotton Turtleneck Sweater/i})).toBeVisible() if (checkA11y) { await runAccessibilityTest(page, [ 'registered', @@ -688,86 +688,11 @@ export const wishlistFlow = async ({page, registeredUserCredentials, a11y = {}}) await navigateToPDPDesktop({page}) // add product to wishlist - await expect(page.getByRole('heading', {name: /Belted Ribbed Boat Neck Sweater/i})).toBeVisible() + await expect(page.getByRole('heading', {name: /Cotton Turtleneck Sweater/i})).toBeVisible() - await page.getByRole('radio', {name: 'M', exact: true}).click() + await page.getByRole('radio', {name: 'L', exact: true}).click() await page.getByRole('button', {name: /Add to Wishlist/i}).click() // wishlist await validateWishlist({page, a11y}) } - -/** - * Navigates to a PLP and opens the store inventory filter to select a store. - * - * This helper function demonstrates the store inventory filtering functionality by: - * 1. Navigating to the Womens > Tops category PLP - * 2. Opening the store locator modal - * 3. Searching for stores by postal code - * 4. Returning the available store selection options - * - * This is useful for testing store inventory features and BOPIS (Buy Online, Pick Up In Store) functionality. - * - * @param {Object} options.page - Playwright page object representing a browser tab/window - */ -export const selectStoreFromPLP = async ({page}) => { - // Navigate to a product category (Womens > Tops) - await page.getByRole('link', {name: 'Womens'}).hover() - const topsNav = await page.getByRole('link', {name: 'Tops', exact: true}) - await expect(topsNav).toBeVisible() - await topsNav.click() - - // Verify we're on the PLP - await expect(page.getByRole('heading', {name: 'Tops'})).toBeVisible() - const productTile = page.getByRole('link', { - name: /Belted Ribbed Boat Neck Sweater/i - }) - const productTileImg = productTile.locator('img') - await productTileImg.waitFor({state: 'visible'}) - - // Look for the store inventory filter component - const storeInventoryFilter = page.getByTestId('sf-store-inventory-filter') - await expect(storeInventoryFilter).toBeVisible() - - // Verify the filter shows "Select Store" initially - await expect(page.getByText('Select Store')).toBeVisible() - await expect(page.getByText('Shop by Availability')).toBeVisible() - - // Click on the store inventory filter checkbox to open store locator - const inventoryCheckbox = page.getByTestId('sf-store-inventory-filter-checkbox') - await inventoryCheckbox.click() - - // Verify store locator modal opens and select a store - await expect(page.getByText('Find a Store')).toBeVisible() - await page.locator('select[name="countryCode"]').selectOption({label: 'United States'}) - await page.locator('input[name="postalCode"]').fill('01803') - const findButton = page.getByRole('button', {name: 'Find'}) - await expect(findButton).toBeVisible() - await findButton.click() - - // Wait for stores to load in the modal - await page.waitForLoadState() - - // Select the first available store (if any stores are available) - await expect(page.getByText(/Burlington Retail Store/i)).toBeVisible() - - // Find and click the first available store label - const storeRadioLabels = page.locator('label.chakra-radio:has(input[aria-describedby^="store-info-"])') - const storeCount = await storeRadioLabels.count() - - if (storeCount > 0) { - // Select the first store - await storeRadioLabels.first().click() - - // Close the store locator modal - await page.locator('button[aria-label="Close"]').click() - await page.waitForLoadState() - await expect(page.getByText('Find a Store')).not.toBeVisible() - } else { - // If no stores are available, verify the appropriate message is shown - await expect(page.getByText('Sorry, there are no locations in this area.')).toBeVisible() - - // Close the modal - await page.getByRole('button', {name: 'Close'}).click() - } -} diff --git a/e2e/tests/a11y/desktop/__snapshots__/guest/cart-a11y-violations.json b/e2e/tests/a11y/desktop/__snapshots__/guest/cart-a11y-violations.json index 4c602307f8..6fc5fec21e 100644 --- a/e2e/tests/a11y/desktop/__snapshots__/guest/cart-a11y-violations.json +++ b/e2e/tests/a11y/desktop/__snapshots__/guest/cart-a11y-violations.json @@ -7,10 +7,10 @@ "helpUrl": "https://dequeuniversity.com/rules/axe/4.10/aria-allowed-attr?application=playwright", "nodes": [ { - "html": "
", + "html": "", "failureSummary": "Fix all of the following:\n ARIA attribute is not allowed: aria-expanded=\"false\"", "target": [ - "#popover-trigger-\\:R1l4lb9rlbpH1\\:" + "#popover-trigger-\\:Rd95aqeqlbpH1\\:" ] } ] @@ -26,7 +26,7 @@ "html": "", "failureSummary": "Fix any of the following:\n Some page content is not contained by landmarks", "target": [ - "#popover-trigger-\\:R1l4lb9rlbpH1\\: > .css-1igwmid.chakra-stack > .chakra-input__group.css-1y0e7gb > .css-va76oz" + "#popover-trigger-\\:Rd95aqeqlbpH1\\: > .css-1igwmid.chakra-stack > .chakra-input__group.css-1y0e7gb > .css-va76oz" ] } ] diff --git a/e2e/tests/a11y/desktop/__snapshots__/guest/checkout-a11y-violations-step-0.json b/e2e/tests/a11y/desktop/__snapshots__/guest/checkout-a11y-violations-step-0.json index a13676961c..b8fefc3110 100644 --- a/e2e/tests/a11y/desktop/__snapshots__/guest/checkout-a11y-violations-step-0.json +++ b/e2e/tests/a11y/desktop/__snapshots__/guest/checkout-a11y-violations-step-0.json @@ -23,7 +23,7 @@ "helpUrl": "https://dequeuniversity.com/rules/axe/4.10/region?application=playwright", "nodes": [ { - "html": "", + "html": "", "failureSummary": "Fix any of the following:\n Some page content is not contained by landmarks", "target": [ ".css-1k2aozt" diff --git a/e2e/tests/a11y/desktop/__snapshots__/guest/checkout-a11y-violations-step-1.json b/e2e/tests/a11y/desktop/__snapshots__/guest/checkout-a11y-violations-step-1.json index a13676961c..b8fefc3110 100644 --- a/e2e/tests/a11y/desktop/__snapshots__/guest/checkout-a11y-violations-step-1.json +++ b/e2e/tests/a11y/desktop/__snapshots__/guest/checkout-a11y-violations-step-1.json @@ -23,7 +23,7 @@ "helpUrl": "https://dequeuniversity.com/rules/axe/4.10/region?application=playwright", "nodes": [ { - "html": "", + "html": "", "failureSummary": "Fix any of the following:\n Some page content is not contained by landmarks", "target": [ ".css-1k2aozt" diff --git a/e2e/tests/a11y/desktop/__snapshots__/guest/checkout-a11y-violations-step-2.json b/e2e/tests/a11y/desktop/__snapshots__/guest/checkout-a11y-violations-step-2.json index a13676961c..b8fefc3110 100644 --- a/e2e/tests/a11y/desktop/__snapshots__/guest/checkout-a11y-violations-step-2.json +++ b/e2e/tests/a11y/desktop/__snapshots__/guest/checkout-a11y-violations-step-2.json @@ -23,7 +23,7 @@ "helpUrl": "https://dequeuniversity.com/rules/axe/4.10/region?application=playwright", "nodes": [ { - "html": "", + "html": "", "failureSummary": "Fix any of the following:\n Some page content is not contained by landmarks", "target": [ ".css-1k2aozt" diff --git a/e2e/tests/a11y/desktop/__snapshots__/guest/checkout-a11y-violations-step-3.json b/e2e/tests/a11y/desktop/__snapshots__/guest/checkout-a11y-violations-step-3.json index a13676961c..b8fefc3110 100644 --- a/e2e/tests/a11y/desktop/__snapshots__/guest/checkout-a11y-violations-step-3.json +++ b/e2e/tests/a11y/desktop/__snapshots__/guest/checkout-a11y-violations-step-3.json @@ -23,7 +23,7 @@ "helpUrl": "https://dequeuniversity.com/rules/axe/4.10/region?application=playwright", "nodes": [ { - "html": "", + "html": "", "failureSummary": "Fix any of the following:\n Some page content is not contained by landmarks", "target": [ ".css-1k2aozt" diff --git a/e2e/tests/a11y/desktop/__snapshots__/guest/checkout-a11y-violations-step-4-order-confirmation.json b/e2e/tests/a11y/desktop/__snapshots__/guest/checkout-a11y-violations-step-4-order-confirmation.json index a13676961c..b8fefc3110 100644 --- a/e2e/tests/a11y/desktop/__snapshots__/guest/checkout-a11y-violations-step-4-order-confirmation.json +++ b/e2e/tests/a11y/desktop/__snapshots__/guest/checkout-a11y-violations-step-4-order-confirmation.json @@ -23,7 +23,7 @@ "helpUrl": "https://dequeuniversity.com/rules/axe/4.10/region?application=playwright", "nodes": [ { - "html": "", + "html": "", "failureSummary": "Fix any of the following:\n Some page content is not contained by landmarks", "target": [ ".css-1k2aozt" diff --git a/e2e/tests/a11y/desktop/__snapshots__/guest/homepage-a11y-violations.json b/e2e/tests/a11y/desktop/__snapshots__/guest/homepage-a11y-violations.json index 4c602307f8..6fc5fec21e 100644 --- a/e2e/tests/a11y/desktop/__snapshots__/guest/homepage-a11y-violations.json +++ b/e2e/tests/a11y/desktop/__snapshots__/guest/homepage-a11y-violations.json @@ -7,10 +7,10 @@ "helpUrl": "https://dequeuniversity.com/rules/axe/4.10/aria-allowed-attr?application=playwright", "nodes": [ { - "html": "", + "html": "", "failureSummary": "Fix all of the following:\n ARIA attribute is not allowed: aria-expanded=\"false\"", "target": [ - "#popover-trigger-\\:R1l4lb9rlbpH1\\:" + "#popover-trigger-\\:Rd95aqeqlbpH1\\:" ] } ] @@ -26,7 +26,7 @@ "html": "", "failureSummary": "Fix any of the following:\n Some page content is not contained by landmarks", "target": [ - "#popover-trigger-\\:R1l4lb9rlbpH1\\: > .css-1igwmid.chakra-stack > .chakra-input__group.css-1y0e7gb > .css-va76oz" + "#popover-trigger-\\:Rd95aqeqlbpH1\\: > .css-1igwmid.chakra-stack > .chakra-input__group.css-1y0e7gb > .css-va76oz" ] } ] diff --git a/e2e/tests/a11y/desktop/__snapshots__/guest/pdp-a11y-violations.json b/e2e/tests/a11y/desktop/__snapshots__/guest/pdp-a11y-violations.json index 4c602307f8..6fc5fec21e 100644 --- a/e2e/tests/a11y/desktop/__snapshots__/guest/pdp-a11y-violations.json +++ b/e2e/tests/a11y/desktop/__snapshots__/guest/pdp-a11y-violations.json @@ -7,10 +7,10 @@ "helpUrl": "https://dequeuniversity.com/rules/axe/4.10/aria-allowed-attr?application=playwright", "nodes": [ { - "html": "", + "html": "", "failureSummary": "Fix all of the following:\n ARIA attribute is not allowed: aria-expanded=\"false\"", "target": [ - "#popover-trigger-\\:R1l4lb9rlbpH1\\:" + "#popover-trigger-\\:Rd95aqeqlbpH1\\:" ] } ] @@ -26,7 +26,7 @@ "html": "", "failureSummary": "Fix any of the following:\n Some page content is not contained by landmarks", "target": [ - "#popover-trigger-\\:R1l4lb9rlbpH1\\: > .css-1igwmid.chakra-stack > .chakra-input__group.css-1y0e7gb > .css-va76oz" + "#popover-trigger-\\:Rd95aqeqlbpH1\\: > .css-1igwmid.chakra-stack > .chakra-input__group.css-1y0e7gb > .css-va76oz" ] } ] diff --git a/e2e/tests/a11y/desktop/__snapshots__/guest/plp-a11y-violations.json b/e2e/tests/a11y/desktop/__snapshots__/guest/plp-a11y-violations.json index 4c602307f8..6fc5fec21e 100644 --- a/e2e/tests/a11y/desktop/__snapshots__/guest/plp-a11y-violations.json +++ b/e2e/tests/a11y/desktop/__snapshots__/guest/plp-a11y-violations.json @@ -7,10 +7,10 @@ "helpUrl": "https://dequeuniversity.com/rules/axe/4.10/aria-allowed-attr?application=playwright", "nodes": [ { - "html": "", + "html": "", "failureSummary": "Fix all of the following:\n ARIA attribute is not allowed: aria-expanded=\"false\"", "target": [ - "#popover-trigger-\\:R1l4lb9rlbpH1\\:" + "#popover-trigger-\\:Rd95aqeqlbpH1\\:" ] } ] @@ -26,7 +26,7 @@ "html": "", "failureSummary": "Fix any of the following:\n Some page content is not contained by landmarks", "target": [ - "#popover-trigger-\\:R1l4lb9rlbpH1\\: > .css-1igwmid.chakra-stack > .chakra-input__group.css-1y0e7gb > .css-va76oz" + "#popover-trigger-\\:Rd95aqeqlbpH1\\: > .css-1igwmid.chakra-stack > .chakra-input__group.css-1y0e7gb > .css-va76oz" ] } ] diff --git a/e2e/tests/a11y/desktop/__snapshots__/registered/account-addresses-a11y-violations.json b/e2e/tests/a11y/desktop/__snapshots__/registered/account-addresses-a11y-violations.json index 4c602307f8..6fc5fec21e 100644 --- a/e2e/tests/a11y/desktop/__snapshots__/registered/account-addresses-a11y-violations.json +++ b/e2e/tests/a11y/desktop/__snapshots__/registered/account-addresses-a11y-violations.json @@ -7,10 +7,10 @@ "helpUrl": "https://dequeuniversity.com/rules/axe/4.10/aria-allowed-attr?application=playwright", "nodes": [ { - "html": "", + "html": "", "failureSummary": "Fix all of the following:\n ARIA attribute is not allowed: aria-expanded=\"false\"", "target": [ - "#popover-trigger-\\:R1l4lb9rlbpH1\\:" + "#popover-trigger-\\:Rd95aqeqlbpH1\\:" ] } ] @@ -26,7 +26,7 @@ "html": "", "failureSummary": "Fix any of the following:\n Some page content is not contained by landmarks", "target": [ - "#popover-trigger-\\:R1l4lb9rlbpH1\\: > .css-1igwmid.chakra-stack > .chakra-input__group.css-1y0e7gb > .css-va76oz" + "#popover-trigger-\\:Rd95aqeqlbpH1\\: > .css-1igwmid.chakra-stack > .chakra-input__group.css-1y0e7gb > .css-va76oz" ] } ] diff --git a/e2e/tests/a11y/desktop/__snapshots__/registered/account-details-a11y-violations.json b/e2e/tests/a11y/desktop/__snapshots__/registered/account-details-a11y-violations.json index 4c602307f8..6fc5fec21e 100644 --- a/e2e/tests/a11y/desktop/__snapshots__/registered/account-details-a11y-violations.json +++ b/e2e/tests/a11y/desktop/__snapshots__/registered/account-details-a11y-violations.json @@ -7,10 +7,10 @@ "helpUrl": "https://dequeuniversity.com/rules/axe/4.10/aria-allowed-attr?application=playwright", "nodes": [ { - "html": "", + "html": "", "failureSummary": "Fix all of the following:\n ARIA attribute is not allowed: aria-expanded=\"false\"", "target": [ - "#popover-trigger-\\:R1l4lb9rlbpH1\\:" + "#popover-trigger-\\:Rd95aqeqlbpH1\\:" ] } ] @@ -26,7 +26,7 @@ "html": "", "failureSummary": "Fix any of the following:\n Some page content is not contained by landmarks", "target": [ - "#popover-trigger-\\:R1l4lb9rlbpH1\\: > .css-1igwmid.chakra-stack > .chakra-input__group.css-1y0e7gb > .css-va76oz" + "#popover-trigger-\\:Rd95aqeqlbpH1\\: > .css-1igwmid.chakra-stack > .chakra-input__group.css-1y0e7gb > .css-va76oz" ] } ] diff --git a/e2e/tests/a11y/desktop/__snapshots__/registered/checkout-a11y-violations-step-0.json b/e2e/tests/a11y/desktop/__snapshots__/registered/checkout-a11y-violations-step-0.json index a13676961c..b8fefc3110 100644 --- a/e2e/tests/a11y/desktop/__snapshots__/registered/checkout-a11y-violations-step-0.json +++ b/e2e/tests/a11y/desktop/__snapshots__/registered/checkout-a11y-violations-step-0.json @@ -23,7 +23,7 @@ "helpUrl": "https://dequeuniversity.com/rules/axe/4.10/region?application=playwright", "nodes": [ { - "html": "", + "html": "", "failureSummary": "Fix any of the following:\n Some page content is not contained by landmarks", "target": [ ".css-1k2aozt" diff --git a/e2e/tests/a11y/desktop/__snapshots__/registered/checkout-a11y-violations-step-1.json b/e2e/tests/a11y/desktop/__snapshots__/registered/checkout-a11y-violations-step-1.json index a13676961c..b8fefc3110 100644 --- a/e2e/tests/a11y/desktop/__snapshots__/registered/checkout-a11y-violations-step-1.json +++ b/e2e/tests/a11y/desktop/__snapshots__/registered/checkout-a11y-violations-step-1.json @@ -23,7 +23,7 @@ "helpUrl": "https://dequeuniversity.com/rules/axe/4.10/region?application=playwright", "nodes": [ { - "html": "", + "html": "", "failureSummary": "Fix any of the following:\n Some page content is not contained by landmarks", "target": [ ".css-1k2aozt" diff --git a/e2e/tests/a11y/desktop/__snapshots__/registered/checkout-a11y-violations-step-2.json b/e2e/tests/a11y/desktop/__snapshots__/registered/checkout-a11y-violations-step-2.json index a13676961c..b8fefc3110 100644 --- a/e2e/tests/a11y/desktop/__snapshots__/registered/checkout-a11y-violations-step-2.json +++ b/e2e/tests/a11y/desktop/__snapshots__/registered/checkout-a11y-violations-step-2.json @@ -23,7 +23,7 @@ "helpUrl": "https://dequeuniversity.com/rules/axe/4.10/region?application=playwright", "nodes": [ { - "html": "", + "html": "", "failureSummary": "Fix any of the following:\n Some page content is not contained by landmarks", "target": [ ".css-1k2aozt" diff --git a/e2e/tests/a11y/desktop/__snapshots__/registered/checkout-a11y-violations-step-3.json b/e2e/tests/a11y/desktop/__snapshots__/registered/checkout-a11y-violations-step-3.json index a13676961c..b8fefc3110 100644 --- a/e2e/tests/a11y/desktop/__snapshots__/registered/checkout-a11y-violations-step-3.json +++ b/e2e/tests/a11y/desktop/__snapshots__/registered/checkout-a11y-violations-step-3.json @@ -23,7 +23,7 @@ "helpUrl": "https://dequeuniversity.com/rules/axe/4.10/region?application=playwright", "nodes": [ { - "html": "", + "html": "", "failureSummary": "Fix any of the following:\n Some page content is not contained by landmarks", "target": [ ".css-1k2aozt" diff --git a/e2e/tests/a11y/desktop/__snapshots__/registered/checkout-a11y-violations-step-4-order-confirmation.json b/e2e/tests/a11y/desktop/__snapshots__/registered/checkout-a11y-violations-step-4-order-confirmation.json index 5a7c3e7cf4..14695c4a46 100644 --- a/e2e/tests/a11y/desktop/__snapshots__/registered/checkout-a11y-violations-step-4-order-confirmation.json +++ b/e2e/tests/a11y/desktop/__snapshots__/registered/checkout-a11y-violations-step-4-order-confirmation.json @@ -7,10 +7,10 @@ "helpUrl": "https://dequeuniversity.com/rules/axe/4.10/aria-allowed-attr?application=playwright", "nodes": [ { - "html": "", + "html": "", "failureSummary": "Fix all of the following:\n ARIA attribute is not allowed: aria-expanded=\"false\"", "target": [ - "#popover-trigger-\\:r5k\\:" + "#popover-trigger-\\:r3c\\:" ] } ] @@ -23,10 +23,10 @@ "helpUrl": "https://dequeuniversity.com/rules/axe/4.10/color-contrast?application=playwright", "nodes": [ { - "html": "Free", - "failureSummary": "Fix any of the following:\n Element has insufficient color contrast of 3.06 (foreground color: #3ba755, background color: #ffffff, font size: 12.0pt (16px), font weight: normal). Expected contrast ratio of 4.5:1", + "html": "Continue Shopping", + "failureSummary": "Fix any of the following:\n Element has insufficient color contrast of 4.17 (foreground color: #0176d3, background color: #f3f3f3, font size: 12.0pt (16px), font weight: normal). Expected contrast ratio of 4.5:1", "target": [ - ".css-xh9uxa" + ".css-a4jxtg" ] } ] @@ -42,7 +42,7 @@ "html": "", "failureSummary": "Fix any of the following:\n Some page content is not contained by landmarks", "target": [ - "#popover-trigger-\\:r5k\\: > .css-1igwmid.chakra-stack > .chakra-input__group.css-1y0e7gb > .css-va76oz" + "#popover-trigger-\\:r3c\\: > .css-1igwmid.chakra-stack > .chakra-input__group.css-1y0e7gb > .css-va76oz" ] } ] diff --git a/e2e/tests/a11y/desktop/__snapshots__/registered/order-history-a11y-violations.json b/e2e/tests/a11y/desktop/__snapshots__/registered/order-history-a11y-violations.json index 4c602307f8..6fc5fec21e 100644 --- a/e2e/tests/a11y/desktop/__snapshots__/registered/order-history-a11y-violations.json +++ b/e2e/tests/a11y/desktop/__snapshots__/registered/order-history-a11y-violations.json @@ -7,10 +7,10 @@ "helpUrl": "https://dequeuniversity.com/rules/axe/4.10/aria-allowed-attr?application=playwright", "nodes": [ { - "html": "", + "html": "", "failureSummary": "Fix all of the following:\n ARIA attribute is not allowed: aria-expanded=\"false\"", "target": [ - "#popover-trigger-\\:R1l4lb9rlbpH1\\:" + "#popover-trigger-\\:Rd95aqeqlbpH1\\:" ] } ] @@ -26,7 +26,7 @@ "html": "", "failureSummary": "Fix any of the following:\n Some page content is not contained by landmarks", "target": [ - "#popover-trigger-\\:R1l4lb9rlbpH1\\: > .css-1igwmid.chakra-stack > .chakra-input__group.css-1y0e7gb > .css-va76oz" + "#popover-trigger-\\:Rd95aqeqlbpH1\\: > .css-1igwmid.chakra-stack > .chakra-input__group.css-1y0e7gb > .css-va76oz" ] } ] diff --git a/e2e/tests/a11y/desktop/__snapshots__/registered/wishlist-violations.json b/e2e/tests/a11y/desktop/__snapshots__/registered/wishlist-violations.json index 4c602307f8..6fc5fec21e 100644 --- a/e2e/tests/a11y/desktop/__snapshots__/registered/wishlist-violations.json +++ b/e2e/tests/a11y/desktop/__snapshots__/registered/wishlist-violations.json @@ -7,10 +7,10 @@ "helpUrl": "https://dequeuniversity.com/rules/axe/4.10/aria-allowed-attr?application=playwright", "nodes": [ { - "html": "", + "html": "", "failureSummary": "Fix all of the following:\n ARIA attribute is not allowed: aria-expanded=\"false\"", "target": [ - "#popover-trigger-\\:R1l4lb9rlbpH1\\:" + "#popover-trigger-\\:Rd95aqeqlbpH1\\:" ] } ] @@ -26,7 +26,7 @@ "html": "", "failureSummary": "Fix any of the following:\n Some page content is not contained by landmarks", "target": [ - "#popover-trigger-\\:R1l4lb9rlbpH1\\: > .css-1igwmid.chakra-stack > .chakra-input__group.css-1y0e7gb > .css-va76oz" + "#popover-trigger-\\:Rd95aqeqlbpH1\\: > .css-1igwmid.chakra-stack > .chakra-input__group.css-1y0e7gb > .css-va76oz" ] } ] diff --git a/e2e/tests/a11y/desktop/a11y-snapshot-test-guest.spec.js b/e2e/tests/a11y/desktop/a11y-snapshot-test-guest.spec.js index 2f8c8ab60b..8b4f930758 100644 --- a/e2e/tests/a11y/desktop/a11y-snapshot-test-guest.spec.js +++ b/e2e/tests/a11y/desktop/a11y-snapshot-test-guest.spec.js @@ -24,6 +24,9 @@ test.describe('Accessibility Tests with Snapshots for guest user', () => { // Handle the consent tracking form using the existing helper await answerConsentTrackingForm(page) + // wait until product tiles are fully load before analyzing + await expect(page.getByRole('link', {name: /Denim slim skirt/i})).toBeVisible() + // Run the a11y test await runAccessibilityTest(page, ['guest', 'homepage-a11y-violations.json']) }) @@ -38,9 +41,9 @@ test.describe('Accessibility Tests with Snapshots for guest user', () => { await topsNav.click() const productTile = page.getByRole('link', { - name: /Belted Ribbed Boat Neck Sweater/i + name: /Cotton Turtleneck Sweater/i }) - await expect(productTile.getByText(/From \£50\.56/i)).toBeVisible() + await expect(productTile.getByText(/From \$39\.99/i)).toBeVisible() // Run the a11y test await runAccessibilityTest(page, ['guest', 'plp-a11y-violations.json']) @@ -50,7 +53,7 @@ test.describe('Accessibility Tests with Snapshots for guest user', () => { await navigateToPDPDesktop({page}) const getProductPromise = page.waitForResponse( - '**/shopper-products/v1/organizations/**/products/25589266M**', + '**/shopper-products/v1/organizations/**/products/25518241M**', {timeout: 10000} ) @@ -58,8 +61,8 @@ test.describe('Accessibility Tests with Snapshots for guest user', () => { const getProductRes = await getProductPromise expect(getProductRes.status()).toBe(200) // ensure that the page is fully loaded before starting a11y scan - await expect(page.getByRole('heading', {name: /Belted Ribbed Boat Neck Sweater/i})).toBeVisible() - await expect(page.getByText(/From \£50\.56/i).nth(1)).toBeVisible() + await expect(page.getByRole('heading', {name: /Cotton Turtleneck Sweater/i})).toBeVisible() + await expect(page.getByText(/From \$39\.99/i).nth(1)).toBeVisible() const addToWishlistButton = page.getByRole('button', {name: /Add to Wishlist/i}) await expect(addToWishlistButton).toBeVisible() @@ -102,7 +105,7 @@ test.describe('Accessibility Tests with Snapshots for guest user', () => { await page.waitForLoadState() // make sure the cart is fully load - await expect(page.getByRole('link', {name: /Belted Ribbed Boat Neck Sweater/i})).toBeVisible() + await expect(page.getByRole('link', {name: /Cotton Turtleneck Sweater/i})).toBeVisible() // Run the a11y test await runAccessibilityTest(page, ['guest', 'cart-a11y-violations.json']) @@ -116,7 +119,7 @@ test.describe('Accessibility Tests with Snapshots for guest user', () => { await page.waitForLoadState() // make sure the cart is fully load - await expect(page.getByRole('link', {name: /Belted Ribbed Boat Neck Sweater/i})).toBeVisible() + await expect(page.getByRole('link', {name: /Cotton Turtleneck Sweater/i})).toBeVisible() await checkoutProduct({ page, diff --git a/e2e/tests/a11y/mobile/__snapshots__/guest/plp-a11y-violations.json b/e2e/tests/a11y/mobile/__snapshots__/guest/plp-a11y-violations.json index aa6be76556..3fc5b7cae3 100644 --- a/e2e/tests/a11y/mobile/__snapshots__/guest/plp-a11y-violations.json +++ b/e2e/tests/a11y/mobile/__snapshots__/guest/plp-a11y-violations.json @@ -7,10 +7,10 @@ "helpUrl": "https://dequeuniversity.com/rules/axe/4.10/aria-allowed-attr?application=playwright", "nodes": [ { - "html": "", + "html": "", "failureSummary": "Fix all of the following:\n ARIA attribute is not allowed: aria-expanded=\"false\"", "target": [ - "#popover-trigger-\\:R1lalb9rlbpH1\\:" + "#popover-trigger-\\:RdalaqeqlbpH1\\:" ] } ] @@ -26,7 +26,7 @@ "html": "", "failureSummary": "Fix any of the following:\n Some page content is not contained by landmarks", "target": [ - "#popover-trigger-\\:R1lalb9rlbpH1\\: > .css-1igwmid.chakra-stack > .chakra-input__group.css-1y0e7gb > .css-va76oz" + "#popover-trigger-\\:RdalaqeqlbpH1\\: > .css-1igwmid.chakra-stack > .chakra-input__group.css-1y0e7gb > .css-va76oz" ] } ] diff --git a/e2e/tests/a11y/mobile/__snapshots__/registered/account-details-a11y-violations.json b/e2e/tests/a11y/mobile/__snapshots__/registered/account-details-a11y-violations.json index 85d798618b..5fc9621bfa 100644 --- a/e2e/tests/a11y/mobile/__snapshots__/registered/account-details-a11y-violations.json +++ b/e2e/tests/a11y/mobile/__snapshots__/registered/account-details-a11y-violations.json @@ -7,10 +7,10 @@ "helpUrl": "https://dequeuniversity.com/rules/axe/4.10/aria-allowed-attr?application=playwright", "nodes": [ { - "html": "", + "html": "", "failureSummary": "Fix all of the following:\n ARIA attribute is not allowed: aria-expanded=\"false\"", "target": [ - "#popover-trigger-\\:R1lalb9rlbpH1\\:" + "#popover-trigger-\\:RdalaqeqlbpH1\\:" ] } ] @@ -42,7 +42,7 @@ "html": "", "failureSummary": "Fix any of the following:\n Some page content is not contained by landmarks", "target": [ - "#popover-trigger-\\:R1lalb9rlbpH1\\: > .css-1igwmid.chakra-stack > .chakra-input__group.css-1y0e7gb > .css-va76oz" + "#popover-trigger-\\:RdalaqeqlbpH1\\: > .css-1igwmid.chakra-stack > .chakra-input__group.css-1y0e7gb > .css-va76oz" ] } ] diff --git a/e2e/tests/a11y/mobile/a11y-snapshot-test-guest.spec.js b/e2e/tests/a11y/mobile/a11y-snapshot-test-guest.spec.js index 2bc4f98e58..27cbfe7222 100644 --- a/e2e/tests/a11y/mobile/a11y-snapshot-test-guest.spec.js +++ b/e2e/tests/a11y/mobile/a11y-snapshot-test-guest.spec.js @@ -40,9 +40,9 @@ test.describe('Accessibility Tests with Snapshots for guest user', () => { // PLP const productTile = page.getByRole('link', { - name: /Belted Ribbed Boat Neck Sweater/i + name: /Cotton Turtleneck Sweater/i }) - await expect(productTile.getByText(/From \£50\.56/i)).toBeVisible() + await expect(productTile.getByText(/From \$39\.99/i)).toBeVisible() // open the filter which has mobile version page.getByRole('button', {name: 'Filter'}).click() diff --git a/e2e/tests/desktop/bopis.spec.js b/e2e/tests/desktop/bopis.spec.js deleted file mode 100644 index f84d480e86..0000000000 --- a/e2e/tests/desktop/bopis.spec.js +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (c) 2025, Salesforce, Inc. - * All rights reserved. - * SPDX-License-Identifier: BSD-3-Clause - * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ - -const {test, expect, waitFor} = require('@playwright/test') -const config = require('../../config.js') -const {generateUserCredentials} = require('../../scripts/utils.js') -const {registerShopper, answerConsentTrackingForm, addProductToCart, checkoutProduct, selectStoreFromPLP} = require('../../scripts/pageHelpers.js') - - - -/** - * Test that selecting a store from the store locator sets the PLP filter - * This test verifies the store inventory filter functionality on the PLP - */ -test('Selecting store from store locator sets the PLP filter', async ({page}) => { - await page.goto(config.RETAIL_APP_HOME) - await answerConsentTrackingForm(page) - - // Select a store from the store locator modal - await selectStoreFromPLP({page}) - - // Verify the filter is updated with the store name - const inventoryFilter = page.locator('input[aria-label*="Filter Products by Store Availability at"]') - await expect(inventoryFilter).toBeVisible() -}) - -/** - * Test that adding a product via Pickup in Store to Cart shows pickup address in Checkout - */ -test('Adding a product via Pickup in Store to Cart shows pickup address in Checkout', async ({page}) => { - await page.goto(config.RETAIL_APP_HOME) - await answerConsentTrackingForm(page) - - // Select a store from the store locator modal - await selectStoreFromPLP({page}) - - // Go to Men's PLP - await page.getByRole('link', {name: 'Mens', exact: true}).hover() - const pantsNav = await page.getByRole('link', {name: 'Pants', exact: true}) - await expect(pantsNav).toBeVisible() - await pantsNav.click() - - // Navigate to PDP - const productTile = page.getByRole('link', { - name: /Refined Denim Pants/i - }) - await productTile.click() - - // Select size and Pickup in Store option - await expect(page.getByRole('heading', {name: /Refined Denim Pants/i})).toBeVisible() - await page.getByRole('radio', {name: '30'}).click() - await page.waitForLoadState() - - // Select pickup option immediately after size selection - const pickupRadio = page.locator('label.chakra-radio:has(input[value="pickup"])') - await pickupRadio.click() - await page.waitForLoadState() - - // Verify the pickup radio is selected - await expect(pickupRadio).toHaveAttribute('data-checked') - - const addToCartButton = page.getByRole('button', {name: /Add to Cart/i}) - await page.waitForLoadState() - - // Add to Cart - await addToCartButton.click() - - // Navigate to cart - await expect(page.getByText(/1 item added to cart/i)).toBeVisible() - await page.getByRole('link', {name: 'View Cart'}).click() - await expect(page.getByText(/Order Summary/i)).toBeVisible() - -// // Verify the Pickup in Store header is displayed in Cart -// await expect(page.getByText(/Pickup in Store/i)).toBeVisible() - - // Proceed to checkout - const checkoutButton = page.getByRole('link', {name: 'Proceed to Checkout'}) - await expect(checkoutButton).toBeVisible() - await checkoutButton.click() - await page.waitForLoadState() - - // Verify the pickup address is displayed - await page.locator('input[type="email"]').fill('test@test.com') - await page.getByRole('button', {name: /Checkout as guest/i}).click() - - // Confirm the email input toggles to show edit button on clicking "Checkout as guest" - const step0Card = page.locator("div[data-testid='sf-toggle-card-step-0']") - await expect(step0Card.getByRole('button', {name: /Edit/i})).toBeVisible() - -// // Verify the pickup address is displayed -// await expect(page.getByText(/Burlington Retail Store/i)).toBeVisible() -}) diff --git a/e2e/tests/desktop/extra-features.spec.js b/e2e/tests/desktop/extra-features.spec.js deleted file mode 100644 index 9c805b6657..0000000000 --- a/e2e/tests/desktop/extra-features.spec.js +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (c) 2025, Salesforce, Inc. - * All rights reserved. - * SPDX-License-Identifier: BSD-3-Clause - * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ - -const {test, expect} = require('@playwright/test') -const config = require('../../config.js') -const {generateUserCredentials} = require('../../scripts/utils.js') -const {answerConsentTrackingForm} = require('../../scripts/pageHelpers.js') - -const GUEST_USER_CREDENTIALS = generateUserCredentials() -/** - * Test that a user can login with passwordless login on mobile. There is no programmatic way to check the email, - * so we will check that the necessary API call is being made and expected UI is shown - */ -test('Verify passwordless login request', async ({page}) => { - let interceptedRequest = null - - await page.route('**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/passwordless/login', (route) => { - interceptedRequest = route.request() - route.continue() - }) - - await page.goto(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME + '/login') - await answerConsentTrackingForm(page) - - await page.locator('#email').scrollIntoViewIfNeeded() - await page.fill('#email', config.PWA_E2E_USER_EMAIL) - - await page.getByRole('button', {name: 'Continue Securely'}).click() - - await page.waitForResponse('**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/passwordless/login') - - expect(interceptedRequest).toBeTruthy() - expect(interceptedRequest.method()).toBe('POST') - - const postData = interceptedRequest.postData() - expect(postData).toBeTruthy() - - const params = new URLSearchParams(postData) - - expect(params.get('user_id')).toBe(config.PWA_E2E_USER_EMAIL) - expect(params.get('mode')).toBe('callback') - expect(params.get('channel_id')).toBe(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME_SITE) - expect(params.get('callback_uri')).toMatch(/.*\/passwordless-login-callback$/) -}) - -test('Verify password reset callback request', async ({page}) => { - let interceptedRequest = null - - await page.route('**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/password/reset', (route) => { - interceptedRequest = route.request() - route.continue() - }) - - await page.goto(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME + '/login') - await answerConsentTrackingForm(page) - - await page.locator('#email').scrollIntoViewIfNeeded() - await page.fill('#email', config.PWA_E2E_USER_EMAIL) - - await page.getByRole('button', {name: 'Password'}).click() - await page.getByRole('button', {name: 'Forgot password?'}).click() - - await page.fill('#email', config.PWA_E2E_USER_EMAIL) - await page.getByRole('button', {name: 'Reset Password'}).click() - - await page.waitForResponse('**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/password/reset') - - expect(interceptedRequest).toBeTruthy() - expect(interceptedRequest.method()).toBe('POST') - - const postData = interceptedRequest.postData() - expect(postData).toBeTruthy() - - const params = new URLSearchParams(postData) - - expect(params.get('user_id')).toBe(config.PWA_E2E_USER_EMAIL) - expect(params.get('mode')).toBe('callback') - expect(params.get('channel_id')).toBe(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME_SITE) - expect(params.get('callback_uri')).toMatch(/.*\/reset-password-callback$/) - expect(params.get('hint')).toBe('cross_device') -}) - -// Verify on the login UI that looks different when extra login features are not enabled -test('Verify password reset callback request when extra login features are not enabled', async ({page}) => { - let interceptedRequest = null - - await page.route('**/mobify/proxy/api/shopper/auth/v1/organizations/*/oauth2/password/reset', (route) => { - interceptedRequest = route.request() - route.continue() - }) - - await page.goto(config.RETAIL_APP_HOME + '/login') - await answerConsentTrackingForm(page) - - await page.locator('#email').scrollIntoViewIfNeeded() - await page.fill('#email', config.PWA_E2E_USER_EMAIL) - - await page.getByRole('button', {name: 'Forgot password?'}).click() - - await page.waitForSelector('form[data-testid="sf-auth-modal-form"] >> text=Reset Password') - await page.fill('form[data-testid="sf-auth-modal-form"] #email', config.PWA_E2E_USER_EMAIL) - await page.getByRole('button', {name: /reset password/i}).click() - await page.waitForResponse('**/mobify/proxy/api/shopper/auth/v1/organizations/*/oauth2/password/reset') - - expect(interceptedRequest).toBeTruthy() - expect(interceptedRequest.method()).toBe('POST') - - const postData = interceptedRequest.postData() - expect(postData).toBeTruthy() - - const params = new URLSearchParams(postData) - - expect(params.get('user_id')).toBe(config.PWA_E2E_USER_EMAIL) - expect(params.get('mode')).toBe('callback') - expect(params.get('channel_id')).toBe(config.RETAIL_APP_HOME_SITE) - expect(params.get('callback_uri')).toMatch(/.*\/reset-password-callback$/) - expect(params.get('hint')).toBe('cross_device') -}) - - -test('Verify password reset request', async ({page}) => { - let interceptedRequest = null - await page.route('**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/password/action', (route) => { - interceptedRequest = route.request() - route.continue() - }) - - await page.goto(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME + `/reset-password-landing?token=1234567&email=${GUEST_USER_CREDENTIALS.email}`) - await answerConsentTrackingForm(page) - - await page.fill('#password', GUEST_USER_CREDENTIALS.password) - await page.fill('#confirmPassword', GUEST_USER_CREDENTIALS.password) - - expect(await page.inputValue('#password')).toBe(GUEST_USER_CREDENTIALS.password) - expect(await page.inputValue('#confirmPassword')).toBe(GUEST_USER_CREDENTIALS.password) - await page.getByRole('button', {name: 'Reset Password'}).click() - - await page.waitForResponse('**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/password/action') - - expect(interceptedRequest).toBeTruthy() -}) diff --git a/e2e/tests/desktop/guest-shopper.spec.js b/e2e/tests/desktop/guest-shopper.spec.js index 67d9eac873..a41f098e25 100644 --- a/e2e/tests/desktop/guest-shopper.spec.js +++ b/e2e/tests/desktop/guest-shopper.spec.js @@ -21,13 +21,13 @@ test('Guest shopper can checkout items as guest', async ({page}) => { // cart await page.getByLabel(/My cart/i).click() - await expect(page.getByRole('link', {name: /Belted Ribbed Boat Neck Sweater/i})).toBeVisible() + await expect(page.getByRole('link', {name: /Cotton Turtleneck Sweater/i})).toBeVisible() await checkoutProduct({page, userCredentials: GUEST_USER_CREDENTIALS}) await expect(page.getByRole('heading', {name: /Order Summary/i})).toBeVisible() await expect(page.getByText(/2 Items/i)).toBeVisible() - await expect(page.getByRole('link', {name: /Belted Ribbed Boat Neck Sweater/i})).toBeVisible() + await expect(page.getByRole('link', {name: /Cotton Turtleneck Sweater/i})).toBeVisible() }) /** @@ -40,10 +40,10 @@ test('Guest shopper can edit product item in cart', async ({page}) => { await page.getByLabel(/My cart/i).click() await page.waitForLoadState() - await expect(page.getByRole('link', {name: /Belted Ribbed Boat Neck Sweater/i})).toBeVisible() + await expect(page.getByRole('link', {name: /Cotton Turtleneck Sweater/i})).toBeVisible() - await expect(page.getByText(/Colour: Black/i)).toBeVisible() - await expect(page.getByText(/Size: M/i)).toBeVisible() + await expect(page.getByText(/Color: Black/i)).toBeVisible() + await expect(page.getByText(/Size: L/i)).toBeVisible() // open product edit modal const editBtn = page.getByRole('button', {name: /Edit/i}) @@ -57,13 +57,13 @@ test('Guest shopper can edit product item in cart', async ({page}) => { // Product edit modal should be open await expect(page.getByTestId('product-view')).toBeVisible() - await page.getByRole('radio', {name: 'L', exact: true}).click() - await page.getByRole('radio', {name: 'New Rattan', exact: true}).click() + await page.getByRole('radio', {name: 'S', exact: true}).click() + await page.getByRole('radio', {name: 'Meadow Violet', exact: true}).click() await page.getByRole('button', {name: /Update/i}).click() await page.waitForLoadState() - await expect(page.getByText(/Size: L/i)).toBeVisible() - await expect(page.getByText(/Colour: New Rattan/i)).toBeVisible() + await expect(page.getByText(/Color: Meadow Violet/i)).toBeVisible() + await expect(page.getByText(/Size: S/i)).toBeVisible() }) /** @@ -99,7 +99,7 @@ test('Guest shopper can checkout product bundle', async ({page}) => { await expect(page.getByText(/Turquoise and Gold Hoop Earring/i)).toBeVisible() const qtyText = page.locator('text="Qty: 1"') - const colorGoldText = page.locator('text="Colour: Gold"') + const colorGoldText = page.locator('text="Color: Gold"') await expect(colorGoldText).toHaveCount(3) await expect(qtyText).toHaveCount(3) diff --git a/e2e/tests/desktop/registered-shopper.spec.js b/e2e/tests/desktop/registered-shopper.spec.js index b5c640694f..b44601ba95 100644 --- a/e2e/tests/desktop/registered-shopper.spec.js +++ b/e2e/tests/desktop/registered-shopper.spec.js @@ -45,16 +45,16 @@ test('Registered shopper can add item to wishlist', async ({page}) => { test.skip('Registered shopper logged in through social retains persisted cart', async ({page}) => { navigateToPDPDesktopSocial({ page, - productName: 'Belted Ribbed Boat Neck Sweater', + productName: 'Floral Ruffle Top', productColor: 'Cardinal Red Multi', productPrice: '£35.19' }) // Add to Cart - await expect(page.getByRole('heading', {name: /Belted Ribbed Boat Neck Sweater/i})).toBeVisible({ + await expect(page.getByRole('heading', {name: /Floral Ruffle Top/i})).toBeVisible({ timeout: 15000 }) - await page.getByRole('radio', {name: 'M', exact: true}).click() + await page.getByRole('radio', {name: 'L', exact: true}).click() await page.locator("button[data-testid='quantity-increment']").click() @@ -80,7 +80,7 @@ test.skip('Registered shopper logged in through social retains persisted cart', // Check Items in Cart await page.getByLabel(/My cart/i).click() await page.waitForLoadState() - await expect(page.getByRole('link', {name: /Belted Ribbed Boat Neck Sweater/i})).toBeVisible() + await expect(page.getByRole('link', {name: /Floral Ruffle Top/i})).toBeVisible() }) export {registeredUserHappyPath} diff --git a/e2e/tests/mobile/extra-features.spec.js b/e2e/tests/mobile/extra-features.spec.js deleted file mode 100644 index cbe78d9f73..0000000000 --- a/e2e/tests/mobile/extra-features.spec.js +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (c) 2025, Salesforce, Inc. - * All rights reserved. - * SPDX-License-Identifier: BSD-3-Clause - * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ - -const {test, expect} = require('@playwright/test') -const config = require('../../config.js') -const {generateUserCredentials} = require('../../scripts/utils.js') -const {answerConsentTrackingForm} = require('../../scripts/pageHelpers.js') - -const GUEST_USER_CREDENTIALS = generateUserCredentials() - -/** - * Test that a user can login with passwordless login on mobile. There is no programmatic way to check the email, - * so we will check that the necessary API call is being made and expected UI is shown - */ -test('Verify passwordless login request on mobile', async ({page}) => { - let interceptedRequest = null - - await page.route('**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/passwordless/login', (route) => { - interceptedRequest = route.request() - route.continue() - }) - - await page.goto(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME + '/login') - await answerConsentTrackingForm(page) - - await page.locator('#email').scrollIntoViewIfNeeded() - await page.fill('#email', config.PWA_E2E_USER_EMAIL) - - await page.getByRole('button', {name: 'Continue Securely'}).scrollIntoViewIfNeeded() - await page.getByRole('button', {name: 'Continue Securely'}).click() - - await page.waitForResponse('**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/passwordless/login') - - expect(interceptedRequest).toBeTruthy() - expect(interceptedRequest.method()).toBe('POST') - - const postData = interceptedRequest.postData() - expect(postData).toBeTruthy() - - const params = new URLSearchParams(postData) - - expect(params.get('user_id')).toBe(config.PWA_E2E_USER_EMAIL) - expect(params.get('mode')).toBe('callback') - expect(params.get('channel_id')).toBe(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME_SITE) - expect(params.get('callback_uri')).toMatch(/.*\/passwordless-login-callback$/) -}) - - -test('Verify password reset callback request on mobile (extra features enabled)', async ({page}) => { - let interceptedRequest = null - - await page.route('**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/password/reset', (route) => { - interceptedRequest = route.request() - route.continue() - }) - - await page.goto(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME + '/login') - await answerConsentTrackingForm(page) - - await page.locator('#email').scrollIntoViewIfNeeded() - await page.fill('#email', config.PWA_E2E_USER_EMAIL) - - await page.getByRole('button', {name: 'Password'}).click() - await page.getByRole('button', {name: 'Forgot password?'}).click() - - await page.fill('#email', config.PWA_E2E_USER_EMAIL) - await page.getByRole('button', {name: /reset password/i}).click() - - await page.waitForResponse('**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/password/reset') - - expect(interceptedRequest).toBeTruthy() - expect(interceptedRequest.method()).toBe('POST') - - const postData = interceptedRequest.postData() - expect(postData).toBeTruthy() - - const params = new URLSearchParams(postData) - - expect(params.get('user_id')).toBe(config.PWA_E2E_USER_EMAIL) - expect(params.get('mode')).toBe('callback') - expect(params.get('channel_id')).toBe(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME_SITE) - expect(params.get('callback_uri')).toMatch(/.*\/reset-password-callback$/) - expect(params.get('hint')).toBe('cross_device') -}) - -test('Verify password reset callback request on mobile when extra login features are not enabled', async ({page}) => { - let interceptedRequest = null - - await page.route('**/mobify/proxy/api/shopper/auth/v1/organizations/*/oauth2/password/reset', (route) => { - interceptedRequest = route.request() - route.continue() - }) - - await page.goto(config.RETAIL_APP_HOME + '/login') - await answerConsentTrackingForm(page) - - await page.locator('#email').scrollIntoViewIfNeeded() - await page.fill('#email', config.PWA_E2E_USER_EMAIL) - - await page.getByRole('button', {name: 'Forgot password?'}).click() - - - await page.waitForSelector('form[data-testid="sf-auth-modal-form"] >> text=Reset Password') - await page.fill('form[data-testid="sf-auth-modal-form"] #email', config.PWA_E2E_USER_EMAIL) - await page.getByRole('button', {name: /reset password/i}).click() - - await page.waitForResponse('**/mobify/proxy/api/shopper/auth/v1/organizations/*/oauth2/password/reset') - - expect(interceptedRequest).toBeTruthy() - expect(interceptedRequest.method()).toBe('POST') - - const postData = interceptedRequest.postData() - expect(postData).toBeTruthy() - - const params = new URLSearchParams(postData) - - expect(params.get('user_id')).toBe(config.PWA_E2E_USER_EMAIL) - expect(params.get('mode')).toBe('callback') - expect(params.get('channel_id')).toBe(config.RETAIL_APP_HOME_SITE) - expect(params.get('callback_uri')).toMatch(/.*\/reset-password-callback$/) - expect(params.get('hint')).toBe('cross_device') -}) - - -test('Verify password reset request on mobile', async ({page}) => { - let interceptedRequest = null - await page.route('**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/password/action', (route) => { - interceptedRequest = route.request() - route.continue() - }) - - await page.goto(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME + `/reset-password-landing?token=1234567&email=${GUEST_USER_CREDENTIALS.email}`) - await answerConsentTrackingForm(page) - - await page.locator('#password').scrollIntoViewIfNeeded() - await page.fill('#password', GUEST_USER_CREDENTIALS.password) - - await page.locator('#confirmPassword').scrollIntoViewIfNeeded() - await page.fill('#confirmPassword', GUEST_USER_CREDENTIALS.password) - - expect(await page.inputValue('#password')).toBe(GUEST_USER_CREDENTIALS.password) - expect(await page.inputValue('#confirmPassword')).toBe(GUEST_USER_CREDENTIALS.password) - - await page.getByRole('button', {name: 'Reset Password'}).scrollIntoViewIfNeeded() - await page.getByRole('button', {name: 'Reset Password'}).click() - - await page.waitForResponse('**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/password/action') - - expect(interceptedRequest).toBeTruthy() -}) diff --git a/e2e/tests/mobile/guest-shopper.spec.js b/e2e/tests/mobile/guest-shopper.spec.js index 92cb521ba7..cebc7c25fc 100644 --- a/e2e/tests/mobile/guest-shopper.spec.js +++ b/e2e/tests/mobile/guest-shopper.spec.js @@ -22,7 +22,7 @@ test('Guest shopper can checkout items as guest', async ({page}) => { // Cart await page.getByLabel(/My cart/i).click() - await expect(page.getByRole('link', {name: /Belted Ribbed Boat Neck Sweater/i})).toBeVisible() + await expect(page.getByRole('link', {name: /Cotton Turtleneck Sweater/i})).toBeVisible() await page.getByRole('link', {name: 'Proceed to Checkout'}).click() @@ -93,7 +93,7 @@ test('Guest shopper can checkout items as guest', async ({page}) => { await expect(page.getByRole('heading', {name: /Order Summary/i})).toBeVisible() await expect(page.getByText(/2 Items/i)).toBeVisible() - await expect(page.getByRole('link', {name: /Belted Ribbed Boat Neck Sweater/i})).toBeVisible() + await expect(page.getByRole('link', {name: /Cotton Turtleneck Sweater/i})).toBeVisible() }) /** @@ -105,22 +105,21 @@ test('Guest shopper can edit product item in cart', async ({page}) => { // Cart await page.getByLabel(/My cart/i).click() - await expect(page.getByRole('link', {name: /Belted Ribbed Boat Neck Sweater/i})).toBeVisible() + await expect(page.getByRole('link', {name: /Cotton Turtleneck Sweater/i})).toBeVisible() - await expect(page.getByText(/Colour: Black/i)).toBeVisible() - await expect(page.getByText(/Size: M/i)).toBeVisible() + await expect(page.getByText(/Color: Black/i)).toBeVisible() + await expect(page.getByText(/Size: L/i)).toBeVisible() await page.getByRole('button', {name: 'Edit'}).click() await expect(page.getByTestId('product-view')).toBeVisible() // update variant in product edit modal - await page.getByRole('radio', {name: 'L', exact: true}).click() - await page.getByRole('radio', {name: 'New Rattan', exact: true}).click() + await page.getByRole('radio', {name: 'S', exact: true}).click() + await page.getByRole('radio', {name: 'Meadow Violet', exact: true}).click() await page.getByRole('button', {name: /Update/i}).click() - await page.waitForLoadState() - await expect(page.getByText(/Size: L/i)).toBeVisible() - await expect(page.getByText(/Colour: New Rattan/i)).toBeVisible() + await expect(page.getByText(/Color: Meadow Violet/i)).toBeVisible() + await expect(page.getByText(/Size: S/i)).toBeVisible() }) /** @@ -156,7 +155,7 @@ test('Guest shopper can checkout product bundle', async ({page}) => { await expect(page.getByText(/Turquoise and Gold Hoop Earring/i)).toBeVisible() const qtyText = page.locator('text="Qty: 1"') - const colorGoldText = page.locator('text="Colour: Gold"') + const colorGoldText = page.locator('text="Color: Gold"') await expect(colorGoldText).toHaveCount(3) await expect(qtyText).toHaveCount(3) diff --git a/e2e/tests/mobile/registered-shopper.spec.js b/e2e/tests/mobile/registered-shopper.spec.js index 74fd66f864..fb7b968734 100644 --- a/e2e/tests/mobile/registered-shopper.spec.js +++ b/e2e/tests/mobile/registered-shopper.spec.js @@ -60,7 +60,7 @@ test('Registered shopper can checkout items', async ({page}) => { // cart await page.getByLabel(/My cart/i).click() - await expect(page.getByRole('link', {name: /Belted Ribbed Boat Neck Sweater/i})).toBeVisible() + await expect(page.getByRole('link', {name: /Cotton Turtleneck Sweater/i})).toBeVisible() await page.getByRole('link', {name: 'Proceed to Checkout'}).click() @@ -136,7 +136,7 @@ test('Registered shopper can checkout items', async ({page}) => { await expect(page.getByRole('heading', {name: /Order Summary/i})).toBeVisible() await expect(page.getByText(/2 Items/i)).toBeVisible() - await expect(page.getByRole('link', {name: /Belted Ribbed Boat Neck Sweater/i})).toBeVisible() + await expect(page.getByRole('link', {name: /Cotton Turtleneck Sweater/i})).toBeVisible() // order history await validateOrderHistory({page}) @@ -170,8 +170,8 @@ test('Registered shopper can add item to wishlist', async ({page}) => { await navigateToPDPMobile({page}) // add product to wishlist - await expect(page.getByRole('heading', {name: /Belted Ribbed Boat Neck Sweater/i})).toBeVisible() - await page.getByRole('radio', {name: 'M', exact: true}).click() + await expect(page.getByRole('heading', {name: /Cotton Turtleneck Sweater/i})).toBeVisible() + await page.getByRole('radio', {name: 'L', exact: true}).click() await page.getByRole('button', {name: /Add to Wishlist/i}).click() // wishlist diff --git a/package.json b/package.json index 9c8692f647..2c5a5a9707 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,7 @@ "test:e2e:ui": "npx playwright test --ui", "test": "lerna run --stream --concurrency=1 test", "test:max-file-size": "lerna run --stream test:max-file-size", - "check-dep-version": "syncpack list-mismatches --types prod,dev", - "test:e2e:extra_features": "npx playwright test --project=extra-features-desktop --project=extra-features-mobile" + "check-dep-version": "syncpack list-mismatches --types prod,dev" }, "devDependencies": { "@playwright/test": "^1.49.0", diff --git a/packages/commerce-sdk-react/CHANGELOG.md b/packages/commerce-sdk-react/CHANGELOG.md index 64f556e46d..546115ebca 100644 --- a/packages/commerce-sdk-react/CHANGELOG.md +++ b/packages/commerce-sdk-react/CHANGELOG.md @@ -5,7 +5,6 @@ - Gracefully handle missing SDK Clients in CommerceApiProvider [#2539](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2539) - Refactor commerce-sdk-react to allow injecting ApiClients [#2519](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2519) - ## v3.3.0 (May 22, 2025) - Fix inconsistency between dwsid and access token for guest login when hybrid authentication is enabled [#2397](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2397) diff --git a/packages/commerce-sdk-react/README.md b/packages/commerce-sdk-react/README.md index e9ed36abdb..8d6cb9ba84 100644 --- a/packages/commerce-sdk-react/README.md +++ b/packages/commerce-sdk-react/README.md @@ -1,6 +1,6 @@ :loudspeaker: Hey there, Salesforce Commerce Cloud community! -We're excited to hear your thoughts on your developer experience with PWA Kit and the Composable Storefront generally! Your feedback is incredibly valuable in helping us guide our roadmap and improve our offering. +We’re excited to hear your thoughts on your developer experience with PWA Kit and the Composable Storefront generally! Your feedback is incredibly valuable in helping us guide our roadmap and improve our offering. :clipboard: Take our quick survey here: [Survey](https://forms.gle/bUZNxQ3QKUcrjhV18) @@ -483,109 +483,6 @@ useEncUserId() => {encUserId: String, getEncUserIdWhenReady: Promise} useUsid() => {usid: String, getUsidWhenReady: Promise} ``` -## Advanced: Customizing SDK Clients with `transformSDKClient` - -To support advanced use cases, such as integrating with older templates or customizing API client behavior, `commerce-sdk-react` provides a utility called `transformSDKClient`. This utility wraps any Commerce SDK client instance in a JavaScript Proxy, enabling you to intercept and transform method arguments, headers, parameters, and other options before each SDK call is made. - -This is especially useful for: -- Adapting SDK clients for legacy or custom templates. -- Removing references to unused SDK clients. - -### How It Works - -`transformSDKClient` takes an SDK client instance and a configuration object. The configuration can include: -- `props`: Arbitrary props you want to pass to your transformer. -- `transformer`: A function that receives the props, method name, and options, and returns the transformed options. -- `onError`: (Optional) A function to handle errors thrown by SDK methods. - -Every method call on the proxied client passes through your transformer before being executed. - -#### Example: Passing Custom SDK Clients to the Provider - -You can use this utility to pass in your own SDK clients to the `CommerceApiProvider` via the `apiClients` prop, and apply custom transformations globally. - -```js -import {CommerceApiProvider} from '@salesforce/commerce-sdk-react' -import {ShopperProducts} from 'commerce-sdk-isomorphic' - -// Create your SDK client instances as usual -const myShopperProductsClient = new ShopperProducts({ - // ...your config -}) - -// Pass your client(s) in the apiClients prop -const apiClients = { - shopperProducts: myShopperProductsClient - // ...add other clients as needed -} - -const App = ({children}) => ( - - {children} - -) -``` - -> **Note:** The `CommerceApiProvider` will automatically wrap each client in `apiClients` with `transformSDKClient`, using a default transformer that injects headers and fetch options from the provider props. You must use props passed to `CommerceApiProvider` for setting custom headers and fetch options. `transformSDKClient` merges headers and options passed in as props with the default values. - -### API Reference - -```ts -transformSDKClient( - client: T, - config: { - props?: any, - transformer?: (props, methodName: string, options: any) => any, - onError?: (methodName: string, error: any, options: any) => void - } -): T -``` - -- **client**: The SDK client instance to wrap. -- **config**: - - **props**: Any extra data you want to pass to your transformer. - - **transformer**: Function to transform method arguments before each SDK call. - - **onError**: (Optional) Function to handle errors from SDK methods. - - -> **Note:** If you choose to pass the `apiClients` prop, you are responsible for providing all SDK clients you intend to use in your application. Any hooks or features that rely on a missing client will throw an error at runtime. This allows for customization, but requires you to explicitly include each client you need. - -### Handling Missing SDK Clients - -With the introduction of the optional `apiClients` prop and support for custom SDK client injection, `commerce-sdk-react` now provides robust error handling for missing clients. If you attempt to use a query or mutation hook for a client that was not initialized or passed to the `CommerceApiProvider`, a clear error will be thrown. - -For example, if you call a hook like `useShopperProducts` but did not provide a `shopperProducts` client in your `apiClients` prop, you will see an error message similar to this error. - -```text -Missing required client: shopperProducts. Please initialize shopperProducts class and provide it in CommerceApiProvider's apiClients prop. -``` - -This ensures that your application fails fast and provides actionable feedback, making it easier to debug configuration issues—especially when integrating with older templates or customizing your SDK client setup. - -### Disabling Automatic Auth Initialization - -By default, `CommerceApiProvider` automatically initializes authentication by calling `auth.ready()` as soon as the provider renders. This is the standard and recommended behavior for most applications. - -**New in v3.4.0:** You can now optionally disable this automatic initialization by passing the `disableAuthInit` prop: - -```jsx - - {children} - -``` - -- **Default:** `disableAuthInit` is `false` (auth is initialized automatically). -- **When to use:** Set `disableAuthInit` to `true` if you are initializing authentication outside of the provider (for example, in legacy PWA Kit templates or when using SSR with `getProps`). This prevents duplicate initialization and potential issues with tokens or customer information. - -> **Note:** For most modern PWA Kit and React Query-based apps, you do **not** need to set this prop. - ## Roadmap - Optimistic update support @@ -604,4 +501,3 @@ By default, `CommerceApiProvider` automatically initializes authentication by ca - [Routing](https://developer.salesforce.com/docs/commerce/pwa-kit-managed-runtime/guide/routing.html) - [Phased Headless Rollouts](https://developer.salesforce.com/docs/commerce/pwa-kit-managed-runtime/guide/phased-headless-rollouts.html) - [Launch Your Storefront](https://developer.salesforce.com/docs/commerce/pwa-kit-managed-runtime/guide/launching-your-storefront.html) - diff --git a/packages/pwa-kit-create-app/CHANGELOG.md b/packages/pwa-kit-create-app/CHANGELOG.md index 11ecb70f68..11988beb16 100644 --- a/packages/pwa-kit-create-app/CHANGELOG.md +++ b/packages/pwa-kit-create-app/CHANGELOG.md @@ -1,10 +1,6 @@ ## v3.11.0-dev.0 (May 23, 2025) -- Fix exiting before `program.json` content can be flushed [#2699](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2699) -- Add `program.json` + Support for Agent-Friendly CLI Input via stdio [#2662](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2662) - Change the default ECOM instance in the generated application [#2610](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2610) - Load active data scripts on demand only [#2623](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2623) -- Introduce the cursor rules to assist storefront project developers [#2578](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2578) [#2754](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2754) -- Add `StoreLocatorProvider` to the `AppConfig` template to support BOPIS [#2753](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2753) ## v3.10.0 (May 22, 2025) - Add Data Cloud API configuration to `default.js`. [#2318](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2318) diff --git a/packages/pwa-kit-create-app/assets/bootstrap/js/overrides/app/components/_app-config/index.jsx.hbs b/packages/pwa-kit-create-app/assets/bootstrap/js/overrides/app/components/_app-config/index.jsx.hbs index 973e3acac7..89f5395265 100644 --- a/packages/pwa-kit-create-app/assets/bootstrap/js/overrides/app/components/_app-config/index.jsx.hbs +++ b/packages/pwa-kit-create-app/assets/bootstrap/js/overrides/app/components/_app-config/index.jsx.hbs @@ -12,7 +12,7 @@ import {ChakraProvider} from '@salesforce/retail-react-app/app/components/shared import 'focus-visible/dist/focus-visible' import theme from '@salesforce/retail-react-app/app/theme' -import {MultiSiteProvider, StoreLocatorProvider} from '@salesforce/retail-react-app/app/contexts' +import {MultiSiteProvider} from '@salesforce/retail-react-app/app/contexts' import { resolveSiteFromUrl, resolveLocaleFromUrl @@ -27,16 +27,7 @@ import {withReactQuery} from '@salesforce/pwa-kit-react-sdk/ssr/universal/compon import {useCorrelationId} from '@salesforce/pwa-kit-react-sdk/ssr/universal/hooks' import {getAppOrigin} from '@salesforce/pwa-kit-react-sdk/utils/url' import {ReactQueryDevtools} from '@tanstack/react-query-devtools' -import { - DEFAULT_DNT_STATE, - STORE_LOCATOR_RADIUS, - STORE_LOCATOR_RADIUS_UNIT, - STORE_LOCATOR_DEFAULT_COUNTRY, - STORE_LOCATOR_DEFAULT_COUNTRY_CODE, - STORE_LOCATOR_DEFAULT_POSTAL_CODE, - STORE_LOCATOR_DEFAULT_PAGE_SIZE, - STORE_LOCATOR_SUPPORTED_COUNTRIES -} from '@salesforce/retail-react-app/app/constants' +import {DEFAULT_DNT_STATE} from '@salesforce/retail-react-app/app/constants' /** * Use the AppConfig component to inject extra arguments into the getProps @@ -58,16 +49,6 @@ const AppConfig = ({children, locals = {}}) => { const passwordlessCallback = locals.appConfig.login?.passwordless?.callbackURI - const storeLocatorConfig = { - radius: STORE_LOCATOR_RADIUS, - radiusUnit: STORE_LOCATOR_RADIUS_UNIT, - defaultCountry: STORE_LOCATOR_DEFAULT_COUNTRY, - defaultCountryCode: STORE_LOCATOR_DEFAULT_COUNTRY_CODE, - defaultPostalCode: STORE_LOCATOR_DEFAULT_POSTAL_CODE, - defaultPageSize: STORE_LOCATOR_DEFAULT_PAGE_SIZE, - supportedCountries: STORE_LOCATOR_SUPPORTED_COUNTRIES - } - return ( { {{/if}} > - - {children} - + {children} diff --git a/packages/pwa-kit-create-app/assets/bootstrap/js/overrides/app/constants.js.hbs b/packages/pwa-kit-create-app/assets/bootstrap/js/overrides/app/constants.js.hbs index 24adb416a7..5514b49051 100644 --- a/packages/pwa-kit-create-app/assets/bootstrap/js/overrides/app/constants.js.hbs +++ b/packages/pwa-kit-create-app/assets/bootstrap/js/overrides/app/constants.js.hbs @@ -16,7 +16,7 @@ import { DEFAULT_LIMIT_VALUES, DEFAULT_SEARCH_PARAMS -} from '{{template.source.name}}/app/constants' +} from '{{preset.templateSource.id}}/app/constants' // original value is 25 DEFAULT_LIMIT_VALUES[0] = 3 @@ -26,4 +26,4 @@ export const CUSTOM_HOME_TITLE = '🎉 Hello Extensible React Template!' export {DEFAULT_LIMIT_VALUES, DEFAULT_SEARCH_PARAMS} -export * from '{{template.source.name}}/app/constants' +export * from '{{preset.templateSource.id}}/app/constants' diff --git a/packages/pwa-kit-create-app/assets/bootstrap/js/overrides/app/pages/home/index.jsx.hbs b/packages/pwa-kit-create-app/assets/bootstrap/js/overrides/app/pages/home/index.jsx.hbs index 8ccb6b2e11..c8a31056f1 100644 --- a/packages/pwa-kit-create-app/assets/bootstrap/js/overrides/app/pages/home/index.jsx.hbs +++ b/packages/pwa-kit-create-app/assets/bootstrap/js/overrides/app/pages/home/index.jsx.hbs @@ -9,19 +9,19 @@ import {useIntl, FormattedMessage} from 'react-intl' import {useLocation} from 'react-router-dom' // Components -import {Box, Button, Stack, Link} from '{{template.source.name}}/app/components/shared/ui' +import {Box, Button, Stack, Link} from '{{preset.templateSource.id}}/app/components/shared/ui' // Project Components -import Hero from '{{template.source.name}}/app/components/hero' -import Seo from '{{template.source.name}}/app/components/seo' -import Section from '{{template.source.name}}/app/components/section' -import ProductScroller from '{{template.source.name}}/app/components/product-scroller' +import Hero from '{{preset.templateSource.id}}/app/components/hero' +import Seo from '{{preset.templateSource.id}}/app/components/seo' +import Section from '{{preset.templateSource.id}}/app/components/section' +import ProductScroller from '{{preset.templateSource.id}}/app/components/product-scroller' // Others import {getAssetUrl} from '@salesforce/pwa-kit-react-sdk/ssr/universal/utils' //Hooks -import useEinstein from '{{template.source.name}}/app/hooks/use-einstein' +import useEinstein from '{{preset.templateSource.id}}/app/hooks/use-einstein' // Constants import { diff --git a/packages/pwa-kit-create-app/assets/bootstrap/js/overrides/app/routes.jsx.hbs b/packages/pwa-kit-create-app/assets/bootstrap/js/overrides/app/routes.jsx.hbs index 280603f69b..0133cefdf3 100644 --- a/packages/pwa-kit-create-app/assets/bootstrap/js/overrides/app/routes.jsx.hbs +++ b/packages/pwa-kit-create-app/assets/bootstrap/js/overrides/app/routes.jsx.hbs @@ -15,9 +15,9 @@ import loadable from '@loadable/component' import {getConfig} from '@salesforce/pwa-kit-runtime/utils/ssr-config' // Components -import {Skeleton} from '{{template.source.name}}/app/components/shared/ui' -import {configureRoutes} from '{{template.source.name}}/app/utils/routes-utils' -import {routes as _routes} from '{{template.source.name}}/app/routes' +import {Skeleton} from '{{preset.templateSource.id}}/app/components/shared/ui' +import {configureRoutes} from '{{preset.templateSource.id}}/app/utils/routes-utils' +import {routes as _routes} from '{{preset.templateSource.id}}/app/routes' const fallback = diff --git a/packages/pwa-kit-create-app/assets/bootstrap/js/overrides/app/static/manifest.json.hbs b/packages/pwa-kit-create-app/assets/bootstrap/js/overrides/app/static/manifest.json.hbs index 947eaef433..7b639dcb2d 100644 --- a/packages/pwa-kit-create-app/assets/bootstrap/js/overrides/app/static/manifest.json.hbs +++ b/packages/pwa-kit-create-app/assets/bootstrap/js/overrides/app/static/manifest.json.hbs @@ -1,6 +1,6 @@ { - "name": "{{general.presetOrTemplateId}}", - "short_name": "{{general.presetOrTemplateId}}", + "name": "{{preset.id}}", + "short_name": "{{preset.id}}", "start_url": "/?homescreen=1", "background_color": "#fff", "theme_color": "#4e439b", diff --git a/packages/pwa-kit-create-app/assets/bootstrap/js/package.json.hbs b/packages/pwa-kit-create-app/assets/bootstrap/js/package.json.hbs index 3bd9708a12..796d3b65a8 100644 --- a/packages/pwa-kit-create-app/assets/bootstrap/js/package.json.hbs +++ b/packages/pwa-kit-create-app/assets/bootstrap/js/package.json.hbs @@ -7,11 +7,11 @@ "npm": "^9.0.0 || ^10.0.0 || ^11.0.0" }, "ccExtensibility": { - "extends": "{{template.source.name}}", + "extends": "{{preset.templateSource.id}}", "overridesDir": "overrides" }, "devDependencies": { - "{{template.source.name}}": "{{answers.general.packageJSON.version}}" + "{{preset.templateSource.id}}": "{{answers.general.packageJSON.version}}" }, "scripts": { {{#each answers.general.packageJSON.scripts}} diff --git a/packages/pwa-kit-create-app/assets/cursor-rules/accessibility-rule.mdc b/packages/pwa-kit-create-app/assets/cursor-rules/accessibility-rule.mdc deleted file mode 100644 index 85c9457602..0000000000 --- a/packages/pwa-kit-create-app/assets/cursor-rules/accessibility-rule.mdc +++ /dev/null @@ -1,8 +0,0 @@ ---- -description: Check and verify accessibility on local project -globs: -alwaysApply: false ---- -To check accessibility of a project, use the accessibility rules defined in PROJECT_ROOT/.cursor/rules/cursor-accessibility-mdc/ directory. -Run an automated scan for the entire codebase within the project and check for any violations. -Show the scanned results in summary and detailed violations if any. \ No newline at end of file diff --git a/packages/pwa-kit-create-app/assets/cursor-rules/cursor-accessibility-mdc/accessibility-button-name.mdc b/packages/pwa-kit-create-app/assets/cursor-rules/cursor-accessibility-mdc/accessibility-button-name.mdc deleted file mode 100644 index e7e9e2b063..0000000000 --- a/packages/pwa-kit-create-app/assets/cursor-rules/cursor-accessibility-mdc/accessibility-button-name.mdc +++ /dev/null @@ -1,26 +0,0 @@ ---- -description: When accessibility is checked. Check if Buttons must have a discernible name using text or aria attributes -globs: -alwaysApply: false ---- - -# Rule: accessibility-button-name - -Buttons must have a discernible name using text or aria attributes - -## 🔍 Pattern - -```regex -]*\b(aria-label|aria-labelledby|title|name)=) -``` - -## 📍 Examples - -```tsx -// ❌ Bad - -// ✅ Good - -// ✅ Good - -``` diff --git a/packages/pwa-kit-create-app/assets/cursor-rules/cursor-accessibility-mdc/accessibility-heading-order.mdc b/packages/pwa-kit-create-app/assets/cursor-rules/cursor-accessibility-mdc/accessibility-heading-order.mdc deleted file mode 100644 index e8dc617420..0000000000 --- a/packages/pwa-kit-create-app/assets/cursor-rules/cursor-accessibility-mdc/accessibility-heading-order.mdc +++ /dev/null @@ -1,26 +0,0 @@ ---- -description: When accessibility is checked. Check if heading levels should increase sequentially for semantic structure -globs: -alwaysApply: false ---- - -# Rule: accessibility-heading-order - -Heading levels should increase sequentially for semantic structure - -## 🔍 Pattern - -```regex - -``` - -## 📍 Examples - -```tsx -// ❌ Bad -

Main Title

-

Subsection

-// ✅ Good -

Main Title

-

Subsection

-``` diff --git a/packages/pwa-kit-create-app/assets/cursor-rules/cursor-accessibility-mdc/accessibility-image-alt.mdc b/packages/pwa-kit-create-app/assets/cursor-rules/cursor-accessibility-mdc/accessibility-image-alt.mdc deleted file mode 100644 index fbea04b3fe..0000000000 --- a/packages/pwa-kit-create-app/assets/cursor-rules/cursor-accessibility-mdc/accessibility-image-alt.mdc +++ /dev/null @@ -1,24 +0,0 @@ ---- -description: When accessibility is checked. Ensure all tags include descriptive alt attributes -globs: -alwaysApply: false ---- - -# Rule: accessibility-image-alt - -Ensure all tags include descriptive alt attributes - -## 🔍 Pattern - -```regex -]*\balt=) -``` - -## 📍 Examples - -```tsx -// ❌ Bad - -// ✅ Good -Company logo -``` diff --git a/packages/pwa-kit-create-app/assets/cursor-rules/cursor-accessibility-mdc/accessibility-input-label.mdc b/packages/pwa-kit-create-app/assets/cursor-rules/cursor-accessibility-mdc/accessibility-input-label.mdc deleted file mode 100644 index 736fb0d739..0000000000 --- a/packages/pwa-kit-create-app/assets/cursor-rules/cursor-accessibility-mdc/accessibility-input-label.mdc +++ /dev/null @@ -1,27 +0,0 @@ ---- -description: When accessibility is checked. Check if Input fields must have a label or aria-label for screen readers -globs: -alwaysApply: false ---- - -# Rule: accessibility-input-label - -Input fields must have a label or aria-label for screen readers - -## 🔍 Pattern - -```regex -]*(aria-label|aria-labelledby|id=)) -``` - -## 📍 Examples - -```tsx -// ❌ Bad - -// ✅ Good - -// ✅ Good with label - - -``` diff --git a/packages/pwa-kit-create-app/assets/cursor-rules/cursor-accessibility-mdc/accessibility-link-name.mdc b/packages/pwa-kit-create-app/assets/cursor-rules/cursor-accessibility-mdc/accessibility-link-name.mdc deleted file mode 100644 index 3d12ed311e..0000000000 --- a/packages/pwa-kit-create-app/assets/cursor-rules/cursor-accessibility-mdc/accessibility-link-name.mdc +++ /dev/null @@ -1,26 +0,0 @@ ---- -description: When accessibility is checked. Check if Anchor tags must have accessible names -globs: -alwaysApply: false ---- - -# Rule: accessibility-link-name - -Anchor tags must have accessible names - -## 🔍 Pattern - -```regex -]*\b(aria-label|aria-labelledby|title)=)(?![^>]*>\s*\w+\s*) -``` - -## 📍 Examples - -```tsx -// ❌ Bad - -// ✅ Good -Your Profile -// ✅ Good - -``` diff --git a/packages/pwa-kit-create-app/assets/cursor-rules/cursor-rule.mdc b/packages/pwa-kit-create-app/assets/cursor-rules/cursor-rule.mdc deleted file mode 100644 index 774d83ff2c..0000000000 --- a/packages/pwa-kit-create-app/assets/cursor-rules/cursor-rule.mdc +++ /dev/null @@ -1,66 +0,0 @@ ---- -description: How to add or edit Cursor rules in our project -globs: -alwaysApply: false ---- -# Cursor Rules Location - -How to add new cursor rules to the project - -1. Always place rule files in PROJECT_ROOT/.cursor/rules/: - ``` - .cursor/rules/ - ├── your-rule-name.mdc - ├── another-rule.mdc - └── ... - ``` - -2. Follow the naming convention: - - Use kebab-case for filenames - - Always use .mdc extension - - Make names descriptive of the rule's purpose - -3. Directory structure: - ``` - PROJECT_ROOT/ - ├── .cursor/ - │ └── rules/ - │ ├── your-rule-name.mdc - │ └── ... - └── ... - ``` - -4. Never place rule files: - - In the project root - - In subdirectories outside .cursor/rules - - In any other location - -5. Cursor rules have the following structure: - -```` ---- -description: Short description of the rule's purpose -globs: optional/path/pattern/**/* -alwaysApply: false ---- -# Rule Title - -Main content explaining the rule with markdown formatting. - -1. Step-by-step instructions -2. Code examples -3. Guidelines - -Example: -```typescript -// Good example -function goodExample() { - // Implementation following guidelines -} - -// Bad example -function badExample() { - // Implementation not following guidelines -} -``` -```` \ No newline at end of file diff --git a/packages/pwa-kit-create-app/assets/templates/@salesforce/retail-react-app/app/components/_app-config/index.jsx.hbs b/packages/pwa-kit-create-app/assets/templates/@salesforce/retail-react-app/app/components/_app-config/index.jsx.hbs index 80af40832c..4a7f332121 100644 --- a/packages/pwa-kit-create-app/assets/templates/@salesforce/retail-react-app/app/components/_app-config/index.jsx.hbs +++ b/packages/pwa-kit-create-app/assets/templates/@salesforce/retail-react-app/app/components/_app-config/index.jsx.hbs @@ -12,7 +12,7 @@ import {ChakraProvider} from '@salesforce/retail-react-app/app/components/shared import 'focus-visible/dist/focus-visible' import theme from '@salesforce/retail-react-app/app/theme' -import {MultiSiteProvider, StoreLocatorProvider} from '@salesforce/retail-react-app/app/contexts' +import {MultiSiteProvider} from '@salesforce/retail-react-app/app/contexts' import {useAppOrigin} from '@salesforce/retail-react-app/app/hooks/use-app-origin' import { resolveSiteFromUrl, @@ -28,15 +28,6 @@ import {withReactQuery} from '@salesforce/pwa-kit-react-sdk/ssr/universal/compon import {useCorrelationId} from '@salesforce/pwa-kit-react-sdk/ssr/universal/hooks' import {ReactQueryDevtools} from '@tanstack/react-query-devtools' import {DEFAULT_DNT_STATE} from '@salesforce/retail-react-app/app/constants' -import { - STORE_LOCATOR_RADIUS, - STORE_LOCATOR_RADIUS_UNIT, - STORE_LOCATOR_DEFAULT_COUNTRY, - STORE_LOCATOR_DEFAULT_COUNTRY_CODE, - STORE_LOCATOR_DEFAULT_POSTAL_CODE, - STORE_LOCATOR_DEFAULT_PAGE_SIZE, - STORE_LOCATOR_SUPPORTED_COUNTRIES -} from '@salesforce/retail-react-app/app/constants' /** * Use the AppConfig component to inject extra arguments into the getProps @@ -58,16 +49,6 @@ const AppConfig = ({children, locals = {}}) => { const passwordlessCallback = locals.appConfig.login?.passwordless?.callbackURI - const storeLocatorConfig = { - radius: STORE_LOCATOR_RADIUS, - radiusUnit: STORE_LOCATOR_RADIUS_UNIT, - defaultCountry: STORE_LOCATOR_DEFAULT_COUNTRY, - defaultCountryCode: STORE_LOCATOR_DEFAULT_COUNTRY_CODE, - defaultPostalCode: STORE_LOCATOR_DEFAULT_POSTAL_CODE, - defaultPageSize: STORE_LOCATOR_DEFAULT_PAGE_SIZE, - supportedCountries: STORE_LOCATOR_SUPPORTED_COUNTRIES - } - return ( { {{/if}} > - - {children} - + {children} diff --git a/packages/pwa-kit-create-app/package.json b/packages/pwa-kit-create-app/package.json index bd8bcb08a2..9d0d55119c 100644 --- a/packages/pwa-kit-create-app/package.json +++ b/packages/pwa-kit-create-app/package.json @@ -17,8 +17,7 @@ }, "files": [ "assets", - "templates", - "program.json" + "templates" ], "scripts": { "format": "internal-lib-build format \"**/*.{js,jsx}\"", diff --git a/packages/pwa-kit-create-app/program.json b/packages/pwa-kit-create-app/program.json deleted file mode 100644 index 1a62a6419c..0000000000 --- a/packages/pwa-kit-create-app/program.json +++ /dev/null @@ -1,695 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "metadata": { - "name": "pwa-kit-create-app", - "description": "This file contains the schema for the pwa-kit-create-app CLI tool. Includes the schemas for presets, templates, validators, and questions. It also includes the data for those presets, templates, and validators." - }, - "schemas": { - "answers": { - "type": "object", - "_ai": { - "instruction": "Use this during project creation when piping in answers from stdin. It's very important that you include the template id keyed as `general.presetOrTemplateId` in the answers object. This can be derived from the `templateId` property of the preset object or the `id` property of the template object.", - "expectedUse": "This data object is used to store the answers to the template questions asked by the program and answered by the user." - } - }, - "questions": { - "type": "array", - "items": { - "type": "object", - "required": [ - "name", - "message" - ], - "properties": { - "name": { - "type": "string" - }, - "message": { - "type": "string" - }, - "type": { - "type": "string" - }, - "choices": { - "type": "array", - "items": { - "type": "object", - "required": [ - "name", - "value" - ], - "properties": { - "name": { - "type": "string" - }, - "value": {} - } - } - } - }, - "additionalProperties": true - } - }, - "presets": { - "type": "array", - "items": { - "type": "object", - "required": [ - "id", - "name", - "description", - "answers", - "private", - "templateId" - ], - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "description": { - "type": "string" - }, - "shortDescription": { - "type": "string" - }, - "templateId": { - "type": "string" - }, - "answers": { - "type": "object", - "additionalProperties": {} - }, - "private": { - "type": "boolean", - "_ai": { - "instruction": "This property is used to filter what presets are shown to the user. If the preset is private, should NOT be shown to the user in a list of selectable presets. This equates to the 'when' property Inquirer question schema.", - "expectedUse": "Hiding presets from the user." - } - } - }, - "additionalProperties": false - }, - "_ai": { - "instruction": "This property is used as a source of all available presets. When an AI agent is asked to select a preset, the selected preset should be returned as the 'general.presetOrTemplateId' property including all the presets answers under the 'answers' property.", - "expectedUse": "List of all available presets." - } - }, - "templates": { - "type": "array", - "items": { - "type": "object", - "required": [ - "id", - "name", - "description", - "source", - "questions", - "private" - ], - "properties": { - "id": { - "type": "string", - "_ai": { - "instruction": "When creating a new project using a template, this property should be renamed to `presetOrTemplateId` and is used to identify the template.", - "expectedUse": "Used as a unique identifier for the template." - } - }, - "name": { - "type": "string" - }, - "description": { - "type": "string" - }, - "shortDescription": { - "type": "string" - }, - "source": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "npm", - "name" - ] - }, - "name": { - "type": "string" - } - }, - "required": [ - "type" - ] - }, - "questions": { - "$ref": "questions.json", - "_ai": { - "instruction": "This property references the Inquirer Question interface defined here https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/inquirer/index.d.ts", - "expectedUse": "Instruct agent on how to ask the user questions." - } - }, - "assets": { - "type": "array", - "items": { - "type": "string" - } - }, - "answers": { - "type": "object", - "additionalProperties": {} - }, - "private": { - "type": "boolean", - "_ai": { - "instruction": "This property is used to filter what templates are shown to the user.", - "expectedUse": "Hiding templates from the user." - } - } - }, - "additionalProperties": false - }, - "_ai": { - "instruction": "Do not display templates that are private. Answer is a key/value pair where the key is `project.template` and the value is the template id.", - "expectedUse": "Asking the user which template they want to use." - } - }, - "validators": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "The unique identifier for the validator." - }, - "name": { - "type": "string", - "description": "The name of the validator." - }, - "description": { - "type": "string", - "description": "A brief description of what the validator does." - }, - "message": { - "type": "string", - "description": "The error message to display if validation fails.", - "_ai": { - "instruction": "When validation fails, use this message to inform the user what went wrong.", - "expectedUse": "For validation error feedback." - } - }, - "regex": { - "type": "string", - "description": "The validation logic, often a regular expression or a reference to a function.", - "_ai": { - "instruction": "Use this regex to validate the user input.", - "expectedUse": "For validation of user input." - } - } - }, - "required": [ - "id", - "name", - "description", - "message", - "regex" - ] - } - }, - "options": { - "type": "array", - "items": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "description": { - "type": "string" - }, - "defaultValue": { - "type": "string" - } - }, - "required": [ - "name", - "description" - ] - } - }, - "examples": { - "type": "array", - "items": { - "type": "object", - "properties": { - "description": { - "type": "string" - }, - "command": { - "type": "string" - } - }, - "required": [ - "description", - "command" - ] - } - } - }, - "data": { - "presets": [ - { - "id": "retail-react-app-demo", - "name": "Retail React App Demo", - "description": "Generate a project using the settings for a special B2C Commerce instance that is used for demo purposes. No questions are asked. Use this preset to try out PWA Kit.", - "shortDescription": "The Retail app with demo Commerce Cloud instance", - "templateId": "retail-react-app", - "answers": { - "project.extend": true, - "project.hybrid": false, - "project.name": "demo-storefront", - "project.commerce.instanceUrl": "https://zzte-053.dx.commercecloud.salesforce.com", - "project.commerce.clientId": "1d763261-6522-4913-9d52-5d947d3b94c4", - "project.commerce.siteId": "RefArch", - "project.commerce.organizationId": "f_ecom_zzte_053", - "project.commerce.shortCode": "kv7kzm78", - "project.commerce.isSlasPrivate": false, - "project.einstein.clientId": "1ea06c6e-c936-4324-bcf0-fada93f83bb1", - "project.einstein.siteId": "aaij-MobileFirst", - "project.dataCloud.appSourceId": "f22ae831-ac03-4bf6-afc1-3a0b19f1ea8e", - "project.dataCloud.tenantId": "mmydmztgh04dczjzmnsw0zd0g8.pc-rnd", - "project.demo.enableDemoSettings": false - }, - "private": false - }, - { - "id": "retail-react-app-demo-site-internal", - "name": "Retail React App Demo Store", - "description": "Generates a project using the settings for a special B2C Commerce instance that is used for demo purposes. The demo site is accessible at https://pwa-kit.mobify-storefront.com/ This environment uses a SLAS private client and has social and passwordless login enabled. This environment is set up to use multiple locales. Future features that are enabled for the demo environment may be added to this preset.", - "shortDescription": "The Retail app with demo Commerce Cloud instance and a private SLAS client", - "templateId": "retail-react-app", - "answers": { - "project.extend": false, - "project.hybrid": false, - "project.name": "demo-storefront", - "project.commerce.instanceUrl": "https://zzrf-001.dx.commercecloud.salesforce.com", - "project.commerce.clientId": "083859f2-5d93-4209-b999-a112266d63a0", - "project.commerce.siteId": "RefArchGlobal", - "project.commerce.organizationId": "f_ecom_zzrf_001", - "project.commerce.shortCode": "kv7kzm78", - "project.commerce.isSlasPrivate": true, - "project.einstein.clientId": "1ea06c6e-c936-4324-bcf0-fada93f83bb1", - "project.einstein.siteId": "aaij-MobileFirst", - "project.dataCloud.appSourceId": "f22ae831-ac03-4bf6-afc1-3a0b19f1ea8e", - "project.dataCloud.tenantId": "mmydmztgh04dczjzmnsw0zd0g8.pc-rnd", - "project.demo.enableDemoSettings": true - }, - "private": true - }, - { - "id": "retail-react-app-test-project", - "name": "Retail React App Test Project", - "description": "", - "templateId": "retail-react-app", - "answers": { - "project.extend": true, - "project.hybrid": false, - "project.name": "retail-react-app", - "project.commerce.instanceUrl": "https://zzrf-001.dx.commercecloud.salesforce.com", - "project.commerce.clientId": "c9c45bfd-0ed3-4aa2-9971-40f88962b836", - "project.commerce.siteId": "RefArch", - "project.commerce.organizationId": "f_ecom_zzrf_001", - "project.commerce.shortCode": "kv7kzm78", - "project.commerce.isSlasPrivate": false, - "project.einstein.clientId": "1ea06c6e-c936-4324-bcf0-fada93f83bb1", - "project.einstein.siteId": "aaij-MobileFirst", - "project.dataCloud.appSourceId": "f22ae831-ac03-4bf6-afc1-3a0b19f1ea8e", - "project.dataCloud.tenantId": "mmydmztgh04dczjzmnsw0zd0g8.pc-rnd", - "project.demo.enableDemoSettings": false - }, - "private": true - }, - { - "id": "retail-react-app-private-slas-client", - "name": "Retail React App Private SLAS client project", - "description": "", - "templateId": "retail-react-app", - "answers": { - "project.extend": true, - "project.hybrid": false, - "project.name": "retail-react-app", - "project.commerce.instanceUrl": "https://zzrf-002.dx.commercecloud.salesforce.com", - "project.commerce.clientId": "89655706-9a0d-49ba-a1e5-18bb2d616374", - "project.commerce.siteId": "RefArch", - "project.commerce.organizationId": "f_ecom_zzrf_002", - "project.commerce.shortCode": "kv7kzm78", - "project.commerce.isSlasPrivate": true, - "project.einstein.clientId": "1ea06c6e-c936-4324-bcf0-fada93f83bb1", - "project.einstein.siteId": "aaij-MobileFirst", - "project.dataCloud.appSourceId": "f22ae831-ac03-4bf6-afc1-3a0b19f1ea8e", - "project.dataCloud.tenantId": "mmydmztgh04dczjzmnsw0zd0g8.pc-rnd", - "project.demo.enableDemoSettings": false - }, - "private": true - }, - { - "id": "retail-react-app-bug-bounty", - "name": "Retail React App Bug Bounty Project", - "description": "", - "templateId": "retail-react-app", - "answers": { - "project.extend": true, - "project.hybrid": false, - "project.name": "retail-react-app", - "project.commerce.instanceUrl": "https://zzec-006.dx.commercecloud.salesforce.com", - "project.commerce.clientId": "b56e7ad3-2237-42c9-8f55-41e63ebca420", - "project.commerce.siteId": "RefArch", - "project.commerce.organizationId": "f_ecom_zzec_006", - "project.commerce.shortCode": "staging-001", - "project.commerce.isSlasPrivate": true, - "project.einstein.clientId": "1ea06c6e-c936-4324-bcf0-fada93f83bb1", - "project.einstein.siteId": "aaij-MobileFirst", - "project.dataCloud.appSourceId": "f22ae831-ac03-4bf6-afc1-3a0b19f1ea8e", - "project.dataCloud.tenantId": "mmydmztgh04dczjzmnsw0zd0g8.pc-rnd", - "project.demo.enableDemoSettings": false - }, - "private": true - }, - { - "id": "retail-react-app-hybrid-test-project", - "name": "Retail React App Hybrid Test Private SLAS Project", - "description": "", - "templateId": "retail-react-app", - "answers": { - "project.extend": true, - "project.hybrid": true, - "project.name": "retail-react-app", - "project.commerce.instanceUrl": "https://test.phased-launch-testing.com/", - "project.commerce.clientId": "99b4e081-00cf-454a-95b0-26ac2b824931", - "project.commerce.siteId": "RefArch", - "project.commerce.organizationId": "f_ecom_bdpx_dev", - "project.commerce.shortCode": "xitgmcd3", - "project.commerce.isSlasPrivate": true, - "project.einstein.clientId": "1ea06c6e-c936-4324-bcf0-fada93f83bb1", - "project.einstein.siteId": "aaij-MobileFirst", - "project.dataCloud.appSourceId": "f22ae831-ac03-4bf6-afc1-3a0b19f1ea8e", - "project.dataCloud.tenantId": "mmydmztgh04dczjzmnsw0zd0g8.pc-rnd", - "project.demo.enableDemoSettings": false - }, - "private": true - }, - { - "id": "retail-react-app-hybrid-public-client-test-project", - "name": "Retail React App Hybrid Test Public SLAS client project", - "description": "", - "templateId": "retail-react-app", - "answers": { - "project.extend": true, - "project.hybrid": true, - "project.name": "retail-react-app", - "project.commerce.instanceUrl": "https://www.phased-launch-testing.com/", - "project.commerce.clientId": "e7e22b7f-a904-4f3a-8022-49dbee696485", - "project.commerce.siteId": "RefArch", - "project.commerce.organizationId": "f_ecom_bjnl_prd", - "project.commerce.shortCode": "performance-001", - "project.commerce.isSlasPrivate": false, - "project.einstein.clientId": "1ea06c6e-c936-4324-bcf0-fada93f83bb1", - "project.einstein.siteId": "aaij-MobileFirst", - "project.dataCloud.appSourceId": "f22ae831-ac03-4bf6-afc1-3a0b19f1ea8e", - "project.dataCloud.tenantId": "mmydmztgh04dczjzmnsw0zd0g8.pc-rnd", - "project.demo.enableDemoSettings": false - }, - "private": true - }, - { - "id": "typescript-minimal-test-project", - "name": "Template Minimal Test Project", - "description": "", - "templateId": "typescript-minimal", - "answers": { - "project.name": "typescript-minimal" - }, - "private": true - }, - { - "id": "express-minimal-test-project", - "name": "Express Minimal Test Project", - "description": "", - "templateId": "typescript-minimal", - "answers": { - "project.name": "express-minimal" - }, - "private": true - } - ], - "templates": [ - { - "id": "retail-react-app", - "name": "Retail React App", - "description": "Generate a project using custom settings by answering questions about a B2C Commerce instance. Use this preset to connect to an existing instance, such as a sandbox.", - "shortDescription": "The Retail app using your own Commerce Cloud instance", - "source": { - "type": "npm", - "name": "@salesforce/retail-react-app" - }, - "questions": [ - { - "name": "project.extend", - "message": "Do you wish to use template extensibility?", - "type": "list", - "choices": [ - { - "name": "Yes", - "value": true - }, - { - "name": "No", - "value": false - } - ] - }, - { - "name": "project.name", - "message": "What is the name of your Project?", - "validator": "validProjectName", - "_ai": { - "instruction": "Should validate the user input using the 'validateProjectName' validator.", - "expectedUse": "Instruct agent on how to validate user input." - } - }, - { - "name": "project.commerce.instanceUrl", - "message": "What is the URL for your Commerce Cloud instance?", - "validator": "validUrl", - "_ai": { - "instruction": "Should validate the user input using the 'validUrl' validator.", - "expectedUse": "Instruct agent on how to validate user input." - } - }, - { - "name": "project.commerce.clientId", - "message": "What is your SLAS Client ID?", - "validator": "validClientId", - "_ai": { - "instruction": "Should validate the user input using the 'validClientId' validator.", - "expectedUse": "Instruct agent on how to validate user input." - } - }, - { - "name": "project.commerce.isSlasPrivate", - "message": "Is your SLAS client private?", - "type": "list", - "choices": [ - { - "name": "Yes", - "value": true - }, - { - "name": "No", - "value": false - } - ] - }, - { - "name": "project.commerce.siteId", - "message": "What is your Site ID in Business Manager?", - "validator": "validSiteId", - "_ai": { - "instruction": "Should validate the user input using the 'validSiteId' validator.", - "expectedUse": "Instruct agent on how to validate user input." - } - }, - { - "name": "project.commerce.organizationId", - "message": "What is your Commerce API organization ID in Business Manager?", - "validator": "validOrganizationId", - "_ai": { - "instruction": "Should validate the user input using the 'validOrganizationId' validator.", - "expectedUse": "Instruct agent on how to validate user input." - } - }, - { - "name": "project.commerce.shortCode", - "message": "What is your Commerce API short code in Business Manager?", - "validator": "validShortCode", - "_ai": { - "instruction": "Should validate the user input using the 'validShortCode' validator.", - "expectedUse": "Instruct agent on how to validate user input." - } - } - ], - "assets": [ - "translations" - ], - "private": false - }, - { - "id": "typescript-minimal", - "name": "Template Minimal Project", - "description": "Generate a project using a bare-bones TypeScript app template. Use this as a TypeScript starting point or as a base on top of which to build new TypeScript project templates for Managed Runtime.", - "source": { - "type": "bundle" - }, - "questions": [ - { - "name": "project.name", - "message": "What is the name of your Project?", - "validator": "validateProjectName", - "_ai": { - "instruction": "Should validate the user input using the 'validateProjectName' validator.", - "expectedUse": "Instruct agent on how to validate user input." - } - } - ], - "private": true - }, - { - "id": "express-minimal", - "name": "Express Minimal Project", - "description": "Generate a project using a bare-bones express app template. Use this as a starting point for APIs or as a base on top of which to build new project templates for Managed Runtime.", - "source": { - "type": "bundle" - }, - "questions": [ - { - "name": "project.name", - "message": "What is the name of your Project?", - "validator": "validateProjectName", - "_ai": { - "instruction": "Should validate the user input using the 'validateProjectName' validator.", - "expectedUse": "Instruct agent on how to validate user input." - } - } - ], - "private": true - }, - { - "id": "mrt-reference-app", - "name": "Managed Runtime Reference App", - "description": "", - "source": { - "type": "bundle" - }, - "questions": [ - { - "name": "project.name", - "message": "What is the name of your Project?", - "validator": "validateProjectName", - "_ai": { - "instruction": "Should validate the user input using the 'validateProjectName' validator.", - "expectedUse": "Instruct agent on how to validate user input." - } - } - ], - "answers": { - "project.name": "mrt-reference-app" - }, - "private": true - } - ], - "validators": [ - { - "id": "validProjectName", - "name": "Valid Project Name", - "description": "Ensures the project name is valid (letters, numbers, space, hyphens, 1-20 characters).", - "message": "Value can only contain letters, numbers, space and hyphens; with a maximum length is 20 characters.", - "regex": "^[a-zA-Z0-9-\\\\s]{1,20}$" - }, - { - "id": "validUrl", - "name": "Valid URL", - "description": "Ensures the value is an absolute URL.", - "message": "Value must be an absolute URL", - "regex": "^(https?):\\/\\/[^\\s/$.?#].[^\\s]*$" - }, - { - "id": "validSiteId", - "name": "Valid Site ID", - "description": "Ensures the Site ID is valid.", - "message": "Valid characters are alphanumeric, hyphen, or underscore", - "regex": "^[a-z0-9_-]+$" - }, - { - "id": "validShortCode", - "name": "Valid Short Code", - "description": "Ensures the short code is valid.", - "message": "Invalid format. Use docs to find more information about valid configurations: https://developer.salesforce.com/docs/commerce/commerce-api/guide/commerce-api-configuration-values", - "regex": "^([0-9A-Z]{8})$" - }, - { - "id": "validClientId", - "name": "Valid Client ID", - "description": "Ensures the client ID is valid.", - "message": "Invalid format. Use docs to find more information about valid configurations: https://developer.salesforce.com/docs/commerce/commerce-api/guide/base-url.html", - "regex": "^([0-9A-Z]{8}-[0-9A-Z]{4}-[0-9A-Z]{4}-[0-9A-Z]{4}-[0-9A-Z]{12}|aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa)$" - }, - { - "id": "validOrganizationId", - "name": "Valid Organization ID", - "description": "Ensures the organization ID is valid.", - "message": "Invalid format. Use docs to find more information about valid configurations: https://developer.salesforce.com/docs/commerce/commerce-api/guide/base-url.html", - "regex": "^(f_ecom)_([A-Z]{4})_(prd|stg|dev|[0-9]{3}|s[0-9]{2})$" - } - ], - "options": [ - { - "name": "--outputDir ", - "description": "Path to the output directory for the new project" - }, - { - "name": "--preset ", - "description": "The name of a project preset to use." - }, - { - "name": "--templateVersion ", - "description": "The version of the template to be generated when it's source is NPM.", - "defaultValue": "latest" - }, - { - "name": "--verbose", - "description": "Print additional logging information to the console." - }, - { - "name": "--stdio", - "description": "Accept project generation answers from stdin as JSON" - }, - { - "name": "--displayProgram", - "description": "Display the program.json file detailing the program schema/data and exit" - } - ], - "examples": [ - { - "description": "Generate a project using a preset", - "command": "npx @salesforce/pwa-kit-create-app --preset retail-react-app-demo" - }, - { - "description": "Generate a project using answers from stdin", - "command": "echo {\"project.name\":\"MyProject\", ...} | npx @salesforce/pwa-kit-create-app --stdio" - } - ] - } -} \ No newline at end of file diff --git a/packages/pwa-kit-create-app/scripts/create-mobify-app.js b/packages/pwa-kit-create-app/scripts/create-mobify-app.js index a7de9ba5e3..72b4be97a7 100755 --- a/packages/pwa-kit-create-app/scripts/create-mobify-app.js +++ b/packages/pwa-kit-create-app/scripts/create-mobify-app.js @@ -48,38 +48,6 @@ const semver = require('semver') const slugify = require('slugify') const generatorPkg = require('../package.json') const Handlebars = require('handlebars') -const PROGRAM = require('../program.json') - -// Presets, Templates and Validators -const { - examples: EXAMPLES, - options: OPTIONS, - presets: PRESETS, - templates: TEMPLATES, - validators: VALIDATORS -} = PROGRAM.data - -// Questions composed of public presets and public templates. -// NOTE: We have to do some weird stuff to determine if the thing we are selecting is a preset or a template. -// There might be a better way to do this. -// NOTE: Id's between presets and templates are unique. We should not break this contract. -const INITIAL_QUESTIONS = [ - { - name: 'general.presetOrTemplateId', - message: 'Choose a project preset to get started:', - type: 'list', - choices: [ - ...PRESETS.filter(({private}) => !private).map(({shortDescription, id}) => ({ - name: shortDescription, - value: id - })), - ...TEMPLATES.filter(({private}) => !private).map(({shortDescription, id}) => ({ - name: shortDescription, - value: id - })) - ].sort((a, b) => (a.name || '').localeCompare(b.name)) - } -] const program = new Command() @@ -87,7 +55,7 @@ sh.set('-e') // Handlebars helpers -// Our eslint script uses escaped double quotes to have windows compatibility. This helper +// Our eslint script uses exscaped double quotes to have windows compatibility. This helper // will ensure those escaped double quotes are still escaped after processing the template. Handlebars.registerHelper('script', (object) => object.replaceAll('"', '\\"')) @@ -96,11 +64,44 @@ const validPreset = (preset) => { return ALL_PRESET_NAMES.includes(preset) } +const validProjectName = (s) => { + if (s.length > PROJECT_ID_MAX_LENGTH) { + return `Maximum length is ${PROJECT_ID_MAX_LENGTH} characters.` + } + const regex = new RegExp(`^[a-zA-Z0-9-\\s]{1,${PROJECT_ID_MAX_LENGTH}}$`) + return regex.test(s) || 'Value can only contain letters, numbers, space and hyphens.' +} + +const validUrl = (s) => { + try { + new URL(s) + return true + } catch (err) { + return 'Value must be an absolute URL' + } +} + +const validSiteId = (s) => + /^[a-z0-9_-]+$/i.test(s) || 'Valid characters are alphanumeric, hyphen, or underscore' + +// To see definitions for Commerce API configuration values, go to +// https://developer.salesforce.com/docs/commerce/commerce-api/guide/commerce-api-configuration-values. +const defaultCommerceAPIError = + 'Invalid format. Use docs to find more information about valid configurations: https://developer.salesforce.com/docs/commerce/commerce-api/guide/commerce-api-configuration-values' +const validShortCode = (s) => /(^[0-9A-Z]{8}$)/i.test(s) || defaultCommerceAPIError + +const validClientId = (s) => + /(^[0-9A-Z]{8}-[0-9A-Z]{4}-[0-9A-Z]{4}-[0-9A-Z]{4}-[0-9A-Z]{12}$)/i.test(s) || + s === 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' || + defaultCommerceAPIError +const validOrganizationId = (s) => + /^(f_ecom)_([A-Z]{4})_(prd|stg|dev|[0-9]{3}|s[0-9]{2})$/i.test(s) || defaultCommerceAPIError + // Globals const GENERATED_PROJECT_VERSION = '0.0.1' const INITIAL_CONTEXT = { - template: undefined, + preset: undefined, answers: { general: {}, project: {} @@ -108,13 +109,441 @@ const INITIAL_CONTEXT = { } const TEMPLATE_SOURCE_NPM = 'npm' const TEMPLATE_SOURCE_BUNDLE = 'bundle' +const DEFAULT_TEMPLATE_VERSION = 'latest' + +const EXTENSIBILITY_QUESTIONS = [ + { + name: 'project.extend', + message: 'Do you wish to use template extensibility?', + type: 'list', + choices: [ + { + name: 'No', + value: false + }, + { + name: 'Yes', + value: true + } + ] + } +] + +const HYBRID_QUESTIONS = [ + { + name: 'project.hybrid', + message: 'Do you wish to set up a phased headless rollout?', + type: 'list', + choices: [ + { + name: 'No', + value: false + }, + { + name: 'Yes', + value: true + } + ] + } +] + +const MRT_REFERENCE_QUESTIONS = [ + { + name: 'project.name', + validate: validProjectName, + message: 'What is the name of your Project?' + } +] + +const EXPRESS_MINIMAL_QUESTIONS = [ + { + name: 'project.name', + validate: validProjectName, + message: 'What is the name of your Project?' + } +] + +const TYPESCRIPT_MINIMAL_QUESTIONS = [ + { + name: 'project.name', + validate: validProjectName, + message: 'What is the name of your Project?' + } +] + +const RETAIL_REACT_APP_QUESTIONS = [ + { + name: 'project.name', + validate: validProjectName, + message: 'What is the name of your Project?' + }, + { + name: 'project.commerce.instanceUrl', + message: 'What is the URL for your Commerce Cloud instance?', + validate: validUrl + }, + { + name: 'project.commerce.clientId', + message: 'What is your SLAS Client ID?', + validate: validClientId + }, + { + name: 'project.commerce.isSlasPrivate', + message: 'Is your SLAS client private?', + type: 'list', + choices: [ + { + name: 'Yes', + value: true + }, + { + name: 'No', + value: false + } + ] + }, + { + name: 'project.commerce.siteId', + message: 'What is your Site ID in Business Manager?', + validate: validSiteId + }, + { + name: 'project.commerce.organizationId', + message: 'What is your Commerce API organization ID in Business Manager?', + validate: validOrganizationId + }, + { + name: 'project.commerce.shortCode', + message: 'What is your Commerce API short code in Business Manager?', + validate: validShortCode + } +] + +// Project dictionary describing details and how the generator should ask questions etc. +const PRESETS = [ + { + id: 'retail-react-app', + name: 'Retail React App', + description: ` + Generate a project using custom settings by answering questions about a + B2C Commerce instance. + + Use this preset to connect to an existing instance, such as a sandbox. + `, + shortDescription: 'The Retail app using your own Commerce Cloud instance', + templateSource: { + type: TEMPLATE_SOURCE_NPM, + id: '@salesforce/retail-react-app' + }, + questions: [...EXTENSIBILITY_QUESTIONS, ...RETAIL_REACT_APP_QUESTIONS], + assets: ['translations'], + private: false + }, + { + id: 'retail-react-app-demo', + name: 'Retail React App Demo', + description: ` + Generate a project using the settings for a special B2C Commerce + instance that is used for demo purposes. No questions are asked. + + Use this preset to try out PWA Kit. + `, + shortDescription: 'The Retail app with demo Commerce Cloud instance', + templateSource: { + type: TEMPLATE_SOURCE_NPM, + id: '@salesforce/retail-react-app' + }, + questions: [...EXTENSIBILITY_QUESTIONS, ...RETAIL_REACT_APP_QUESTIONS], + answers: { + ['project.extend']: true, + ['project.hybrid']: false, + ['project.name']: 'demo-storefront', + ['project.commerce.instanceUrl']: 'https://production-sitegenesis-dw.demandware.net', + ['project.commerce.clientId']: '44cfcf31-d64d-4227-9cce-1d9b0716c321', + ['project.commerce.siteId']: 'RefArch', + ['project.commerce.organizationId']: 'f_ecom_aaia_prd', + ['project.commerce.shortCode']: 'xfdy2axw', + ['project.commerce.isSlasPrivate']: false, + ['project.einstein.clientId']: '1ea06c6e-c936-4324-bcf0-fada93f83bb1', + ['project.einstein.siteId']: 'aaij-MobileFirst', + ['project.dataCloud.appSourceId']: 'f22ae831-ac03-4bf6-afc1-3a0b19f1ea8e', + ['project.dataCloud.tenantId']: 'mmydmztgh04dczjzmnsw0zd0g8.pc-rnd', + ['project.demo.enableDemoSettings']: false + }, + assets: ['translations'], + private: false + }, + { + id: 'retail-react-app-demo-site-internal', + name: 'Retail React App Demo Store', + description: ` + Generates a project using the settings for a special B2C Commerce instance that is used + for demo purposes. The demo site is accessible at https://pwa-kit.mobify-storefront.com/ + + This environment uses a SLAS private client and has social and passwordless login enabled. + This environment is set up to use multiple locales. + Future features that are enabled for the demo environment may be added to this preset. + `, + shortDescription: + 'The Retail app with demo Commerce Cloud instance and a private SLAS client', + templateSource: { + type: TEMPLATE_SOURCE_NPM, + id: '@salesforce/retail-react-app' + }, + questions: [...EXTENSIBILITY_QUESTIONS, ...RETAIL_REACT_APP_QUESTIONS], + answers: { + ['project.extend']: false, // Intentionally not an extensible project so that the correct logos appear on demo site + ['project.hybrid']: false, + ['project.name']: 'demo-storefront', + ['project.commerce.instanceUrl']: 'https://zzrf-001.dx.commercecloud.salesforce.com', + ['project.commerce.clientId']: '083859f2-5d93-4209-b999-a112266d63a0', + ['project.commerce.siteId']: 'RefArchGlobal', + ['project.commerce.organizationId']: 'f_ecom_zzrf_001', + ['project.commerce.shortCode']: 'kv7kzm78', + ['project.commerce.isSlasPrivate']: true, + ['project.einstein.clientId']: '1ea06c6e-c936-4324-bcf0-fada93f83bb1', + ['project.einstein.siteId']: 'aaij-MobileFirst', + ['project.dataCloud.appSourceId']: 'f22ae831-ac03-4bf6-afc1-3a0b19f1ea8e', + ['project.dataCloud.tenantId']: 'mmydmztgh04dczjzmnsw0zd0g8.pc-rnd', + ['project.demo.enableDemoSettings']: true // True only for presets deployed to demo environments like pwa-kit.mobify-storefront.com + }, + assets: ['translations'], + private: true + }, + { + id: 'retail-react-app-test-project', + name: 'Retail React App Test Project', + description: '', + templateSource: { + type: TEMPLATE_SOURCE_NPM, + id: '@salesforce/retail-react-app' + }, + questions: [...EXTENSIBILITY_QUESTIONS, ...RETAIL_REACT_APP_QUESTIONS], + answers: { + ['project.extend']: true, + ['project.hybrid']: false, + ['project.name']: 'retail-react-app', + ['project.commerce.instanceUrl']: 'https://zzrf-001.dx.commercecloud.salesforce.com', + ['project.commerce.clientId']: 'c9c45bfd-0ed3-4aa2-9971-40f88962b836', + ['project.commerce.siteId']: 'RefArch', + ['project.commerce.organizationId']: 'f_ecom_zzrf_001', + ['project.commerce.shortCode']: 'kv7kzm78', + ['project.commerce.isSlasPrivate']: false, + ['project.einstein.clientId']: '1ea06c6e-c936-4324-bcf0-fada93f83bb1', + ['project.einstein.siteId']: 'aaij-MobileFirst', + ['project.dataCloud.appSourceId']: 'f22ae831-ac03-4bf6-afc1-3a0b19f1ea8e', + ['project.dataCloud.tenantId']: 'mmydmztgh04dczjzmnsw0zd0g8.pc-rnd', + ['project.demo.enableDemoSettings']: false + }, + assets: ['translations'], + private: true + }, + { + id: 'retail-react-app-private-slas-client', + name: 'Retail React App Private SLAS client project', + description: '', + templateSource: { + type: TEMPLATE_SOURCE_NPM, + id: '@salesforce/retail-react-app' + }, + questions: [...EXTENSIBILITY_QUESTIONS, ...RETAIL_REACT_APP_QUESTIONS], + answers: { + ['project.extend']: true, + ['project.hybrid']: false, + ['project.name']: 'retail-react-app', + ['project.commerce.instanceUrl']: 'https://zzrf-002.dx.commercecloud.salesforce.com', + ['project.commerce.clientId']: '89655706-9a0d-49ba-a1e5-18bb2d616374', + ['project.commerce.siteId']: 'RefArch', + ['project.commerce.organizationId']: 'f_ecom_zzrf_002', + ['project.commerce.shortCode']: 'kv7kzm78', + ['project.commerce.isSlasPrivate']: true, + ['project.einstein.clientId']: '1ea06c6e-c936-4324-bcf0-fada93f83bb1', + ['project.einstein.siteId']: 'aaij-MobileFirst', + ['project.dataCloud.appSourceId']: 'f22ae831-ac03-4bf6-afc1-3a0b19f1ea8e', + ['project.dataCloud.tenantId']: 'mmydmztgh04dczjzmnsw0zd0g8.pc-rnd', + ['project.demo.enableDemoSettings']: false + }, + assets: ['translations'], + private: true + }, + { + id: 'retail-react-app-bug-bounty', + name: 'Retail React App Bug Bounty Project', + description: '', + templateSource: { + type: TEMPLATE_SOURCE_NPM, + id: '@salesforce/retail-react-app' + }, + questions: [...EXTENSIBILITY_QUESTIONS, ...RETAIL_REACT_APP_QUESTIONS], + answers: { + ['project.extend']: true, + ['project.hybrid']: false, + ['project.name']: 'retail-react-app', + ['project.commerce.instanceUrl']: 'https://zzec-006.dx.commercecloud.salesforce.com', + ['project.commerce.clientId']: 'b56e7ad3-2237-42c9-8f55-41e63ebca420', + ['project.commerce.siteId']: 'RefArch', + ['project.commerce.organizationId']: 'f_ecom_zzec_006', + ['project.commerce.shortCode']: 'staging-001', + ['project.einstein.clientId']: '1ea06c6e-c936-4324-bcf0-fada93f83bb1', + ['project.einstein.siteId']: 'aaij-MobileFirst', + ['project.dataCloud.appSourceId']: 'f22ae831-ac03-4bf6-afc1-3a0b19f1ea8e', + ['project.dataCloud.tenantId']: 'mmydmztgh04dczjzmnsw0zd0g8.pc-rnd', + ['project.commerce.isSlasPrivate']: true, + ['project.demo.enableDemoSettings']: false + }, + assets: ['translations'], + private: true + }, + { + id: 'retail-react-app-hybrid-test-project', + name: 'Retail React App Hybrid Test Private SLAS Project', + description: '', + templateSource: { + type: TEMPLATE_SOURCE_NPM, + id: '@salesforce/retail-react-app' + }, + questions: [...EXTENSIBILITY_QUESTIONS, ...HYBRID_QUESTIONS, ...RETAIL_REACT_APP_QUESTIONS], + answers: { + ['project.extend']: true, + ['project.hybrid']: true, + ['project.name']: 'retail-react-app', + ['project.commerce.instanceUrl']: 'https://test.phased-launch-testing.com/', + ['project.commerce.clientId']: '99b4e081-00cf-454a-95b0-26ac2b824931', + ['project.commerce.siteId']: 'RefArch', + ['project.commerce.organizationId']: 'f_ecom_bdpx_dev', + ['project.commerce.shortCode']: 'xitgmcd3', + ['project.einstein.clientId']: '1ea06c6e-c936-4324-bcf0-fada93f83bb1', + ['project.einstein.siteId']: 'aaij-MobileFirst', + ['project.commerce.isSlasPrivate']: true, + ['project.dataCloud.appSourceId']: 'f22ae831-ac03-4bf6-afc1-3a0b19f1ea8e', + ['project.dataCloud.tenantId']: 'mmydmztgh04dczjzmnsw0zd0g8.pc-rnd', + ['project.demo.enableDemoSettings']: false + }, + assets: ['translations'], + private: true + }, + { + id: 'retail-react-app-hybrid-public-client-test-project', + name: 'Retail React App Hybrid Test Public SLAS client project', + description: '', + templateSource: { + type: TEMPLATE_SOURCE_NPM, + id: '@salesforce/retail-react-app' + }, + questions: [...EXTENSIBILITY_QUESTIONS, ...HYBRID_QUESTIONS, ...RETAIL_REACT_APP_QUESTIONS], + answers: { + ['project.extend']: true, + ['project.hybrid']: true, + ['project.name']: 'retail-react-app', + ['project.commerce.instanceUrl']: 'https://www.phased-launch-testing.com/', + ['project.commerce.clientId']: 'e7e22b7f-a904-4f3a-8022-49dbee696485', + ['project.commerce.siteId']: 'RefArch', + ['project.commerce.organizationId']: 'f_ecom_bjnl_prd', + ['project.commerce.shortCode']: 'performance-001', + ['project.einstein.clientId']: '1ea06c6e-c936-4324-bcf0-fada93f83bb1', + ['project.einstein.siteId']: 'aaij-MobileFirst', + ['project.commerce.isSlasPrivate']: false, + ['project.dataCloud.appSourceId']: 'f22ae831-ac03-4bf6-afc1-3a0b19f1ea8e', + ['project.dataCloud.tenantId']: 'mmydmztgh04dczjzmnsw0zd0g8.pc-rnd', + ['project.demo.enableDemoSettings']: false + }, + assets: ['translations'], + private: true + }, + { + id: 'typescript-minimal-test-project', + name: 'Template Minimal Test Project', + description: '', + templateSource: { + type: TEMPLATE_SOURCE_BUNDLE, + id: 'typescript-minimal' + }, + private: true + }, + { + id: 'typescript-minimal', + name: 'Template Minimal Project', + description: ` + Generate a project using a bare-bones TypeScript app template. + + Use this as a TypeScript starting point or as a base on top of + which to build new TypeScript project templates for Managed Runtime. + `, + templateSource: { + type: TEMPLATE_SOURCE_BUNDLE, + id: 'typescript-minimal' + }, + questions: TYPESCRIPT_MINIMAL_QUESTIONS, + private: true + }, + { + id: 'express-minimal-test-project', + name: 'Express Minimal Test Project', + description: '', + templateSource: { + type: TEMPLATE_SOURCE_BUNDLE, + id: 'express-minimal' + }, + questions: EXPRESS_MINIMAL_QUESTIONS, + answers: { + ['project.name']: 'express-minimal' + }, + private: true + }, + { + id: 'express-minimal', + name: 'Express Minimal Project', + description: ` + Generate a project using a bare-bones express app template. + + Use this as a starting point for APIs or as a base on top of + which to build new project templates for Managed Runtime. + `, + templateSource: { + type: TEMPLATE_SOURCE_BUNDLE, + id: 'express-minimal' + }, + questions: EXPRESS_MINIMAL_QUESTIONS, + private: true + }, + { + id: 'mrt-reference-app', + name: 'Managed Runtime Reference App', + description: '', + templateSource: { + type: TEMPLATE_SOURCE_BUNDLE, + id: 'mrt-reference-app' + }, + questions: MRT_REFERENCE_QUESTIONS, + answers: { + ['project.name']: 'mrt-reference-app' + }, + private: true + } +] + +const PRESET_QUESTIONS = [ + { + name: 'general.presetId', + message: 'Choose a project preset to get started:', + type: 'list', + choices: PRESETS.filter(({private}) => !private).map(({shortDescription, id}) => ({ + name: shortDescription, + value: id + })) + } +] const BOOTSTRAP_DIR = p.join(__dirname, '..', 'assets', 'bootstrap', 'js') + const ASSETS_TEMPLATES_DIR = p.join(__dirname, '..', 'assets', 'templates') -const CURSOR_RULES_FROM_DIR = p.join(__dirname, '..', 'assets', 'cursor-rules') + const PRIVATE_PRESET_NAMES = PRESETS.filter(({private}) => !!private).map(({id}) => id) + const PUBLIC_PRESET_NAMES = PRESETS.filter(({private}) => !private).map(({id}) => id) + const ALL_PRESET_NAMES = PRIVATE_PRESET_NAMES.concat(PUBLIC_PRESET_NAMES) + const PROJECT_ID_MAX_LENGTH = 20 // Utilities @@ -299,8 +728,8 @@ const processTemplate = (relFile, inputDir, outputDir, context) => { * @param {*} param2 */ const runGenerator = (context, {outputDir, templateVersion, verbose}) => { - const {answers, template} = context - const {id, source} = template + const {answers, preset} = context + const {templateSource} = preset const {extend = false} = answers.project // Check if the output directory doesn't already exist. @@ -310,12 +739,13 @@ const runGenerator = (context, {outputDir, templateVersion, verbose}) => { // downloading from NPM or copying from the template bundle folder. const tmp = fs.mkdtempSync(p.resolve(os.tmpdir(), 'extract-template')) const packagePath = p.join(tmp, 'package') + const {id, type} = templateSource let tarPath - switch (source.type) { + switch (type) { case TEMPLATE_SOURCE_NPM: { const tarFile = sh - .exec(`npm pack ${source.name}@${templateVersion} --pack-destination="${tmp}"`, { + .exec(`npm pack ${id}@${templateVersion} --pack-destination="${tmp}"`, { silent: true }) .stdout.trim() @@ -323,10 +753,10 @@ const runGenerator = (context, {outputDir, templateVersion, verbose}) => { break } case TEMPLATE_SOURCE_BUNDLE: - tarPath = p.join(__dirname, '..', 'templates', `${source?.name || id}.tar.gz`) + tarPath = p.join(__dirname, '..', 'templates', `${id}.tar.gz`) break default: { - const msg = `Error: Cannot handle template source type ${source.type}.` + const msg = `Error: Cannot handle template source type ${type}.` console.error(msg) process.exit(1) } @@ -347,33 +777,23 @@ const runGenerator = (context, {outputDir, templateVersion, verbose}) => { processTemplate(relFilePath, BOOTSTRAP_DIR, outputDir, context) ) - // Copy required assets defined on the preset level. - const {assets = []} = template + // Copy required assets defind on the preset level. + const {assets = []} = preset assets.forEach((asset) => { sh.cp('-rf', p.join(packagePath, asset), outputDir) }) } else { - console.log('Copying base template from package or npm: ', packagePath, outputDir) // Copy the base template either from the package or npm. sh.cp('-rf', packagePath, outputDir) - // Copy template specific assets over, if they exist. - const assetsDir = p.join(ASSETS_TEMPLATES_DIR, source.name || id) + // Copy template specific assets over. + const assetsDir = p.join(ASSETS_TEMPLATES_DIR, id) if (sh.test('-e', assetsDir)) { - console.log(`Copying template-specific assets from ${assetsDir}`) getFiles(assetsDir) .map((file) => file.replace(assetsDir, '')) .forEach((relFilePath) => processTemplate(relFilePath, assetsDir, outputDir, context) ) - } else { - // However, we expected to see assetsDir for retail-react-app template - if (source.name === '@salesforce/retail-react-app') { - console.error( - `Error: cannot find template-specific assets for retail-react-app in directory ${assetsDir}` - ) - process.exit(1) - } } // Update the generated projects version. NOTE: For bootstrapped projects this @@ -382,7 +802,7 @@ const runGenerator = (context, {outputDir, templateVersion, verbose}) => { const pkgJsonPath = p.resolve(outputDir, 'package.json') const pkgJSON = readJson(pkgJsonPath) const finalPkgData = merge(pkgJSON, { - name: slugifyName(context.answers.project.name || context.template.id), + name: slugifyName(context.answers.project.name || context.preset.id), version: GENERATED_PROJECT_VERSION }) writeJson(pkgJsonPath, finalPkgData) @@ -391,22 +811,6 @@ const runGenerator = (context, {outputDir, templateVersion, verbose}) => { sh.rm('-rf', tmp) } - // Copy the .cursor/rules directory if it exists - if (sh.test('-e', CURSOR_RULES_FROM_DIR)) { - const outputCursorRulesDir = p.join(outputDir, '.cursor', 'rules') - - // Create the directory if it doesn't exist - if (!sh.test('-e', outputCursorRulesDir)) { - fs.mkdirSync(outputCursorRulesDir, {recursive: true}) - } - - // Copy the contents of CURSOR_RULES_FROM_DIR to outputCursorRulesDir - const files = fs.readdirSync(CURSOR_RULES_FROM_DIR) - files.forEach((file) => { - sh.cp('-rf', p.join(CURSOR_RULES_FROM_DIR, file), outputCursorRulesDir) - }) - } - // Install dependencies for the newly minted project. npmInstall(outputDir, {verbose}) } @@ -415,87 +819,6 @@ const foundNode = process.versions.node const requiredNode = generatorPkg.engines.node const isUsingCompatibleNode = semver.satisfies(foundNode, new semver.Range(requiredNode)) -/** - * Reads all data from standard input (stdin) asynchronously and resolves with the complete input as a string. - * Useful for accepting piped or redirected input, such as JSON answers for non-interactive CLI usage. - * - * @returns {Promise} A promise that resolves with the full stdin input as a string. - */ -const readStdin = async () => { - return new Promise((resolve, reject) => { - let input = '' - process.stdin.setEncoding('utf8') - - process.stdin.on('data', (chunk) => { - input += chunk - }) - - process.stdin.on('end', () => { - resolve(input) - }) - - process.stdin.on('error', (err) => { - reject(err) - }) - }) -} - -/** - * Validates the parsed answers object for required fields and structure. - * Currently only checks for 'general.presetOrTemplateId', but can be extended for more robust validation. - * Throws an error if validation fails. - * - * @param {Object} answers - The parsed answers object from stdin. - */ -const validateAnswers = (answers) => { - if (!answers['general.presetOrTemplateId']) { - throw new Error('Missing required field: "general.presetOrTemplateId"') - } - - // Future enhancement: Add validation for template specific answers. -} - -/** - * Reads and parses JSON input from stdin for non-interactive CLI usage. - * Exits the process with an error message if input is invalid or missing required fields. - * - * @returns {Promise} - The merged answers object. - */ -const getAnswersFromStdin = async () => { - try { - const input = await readStdin() - if (!input.trim()) { - throw new Error('No input received. Please pipe valid JSON to stdin.') - } - const parsedInput = JSON.parse(input) - - // Do answer validation. - validateAnswers(parsedInput) - - return expandObject(parsedInput) - } catch (err) { - if (err instanceof SyntaxError) { - console.error('Invalid JSON format in stdin input') - } else { - console.error('Failed to process stdin input:', err.message) - } - process.exit(1) - } -} - -/** - * Prints the contents of program.json in a nicely formatted way and exits the process. - */ -const printProgramJsonAndExit = async () => { - const output = JSON.stringify(PROGRAM, null, 2) - await new Promise((resolve) => { - process.stdout.write(output + '\n', () => { - resolve() - }) - }) - process.exit(0) -} - const main = async (opts) => { if (!isUsingCompatibleNode) { console.log('') @@ -511,19 +834,11 @@ const main = async (opts) => { // to "general" and "project" questions. It'll also be populated with details of the selected project, // like its `package.json` value. let context = INITIAL_CONTEXT - let isPreset = false - let answers = {} - let selectedTemplate - let {outputDir, verbose, preset, templateVersion, stdio, displayProgram} = opts + let {outputDir, verbose, preset, templateVersion} = opts const {prompt} = inquirer const OUTPUT_DIR_FLAG_ACTIVE = !!outputDir const presetId = preset || process.env.GENERATOR_PRESET - // Exit if the preset provided is not valid. - if (displayProgram) { - await printProgramJsonAndExit() - } - // Exit if the preset provided is not valid. if (presetId && !validPreset(presetId)) { console.error( @@ -536,68 +851,37 @@ const main = async (opts) => { process.exit(1) } - // If there is no preset provided via the CLI, check for stdio input or prompt the user - if (stdio) { - answers = await getAnswersFromStdin() - } else { - answers = await prompt( - INITIAL_QUESTIONS, - presetId ? {general: {presetOrTemplateId: presetId}} : {} - ) + // If there is no preset arg, prompt the user with a selection of presets. + if (!presetId) { + context.answers = await prompt(PRESET_QUESTIONS) } - // Determine if the selection is a preset or template. - isPreset = PRESETS.some(({id}) => id === answers?.general?.presetOrTemplateId) + // Add the selected preset to the context object. + const selectedPreset = PRESETS.find( + ({id}) => id === (presetId || context.answers.general.presetId) + ) - // Update the answer with the actual template id. - if (isPreset) { - const selectedPreset = PRESETS.find(({id}) => id === answers.general.presetOrTemplateId) + // Add the preset to the context. + context.preset = selectedPreset - // NOTE: This is a little weird, but we'll set this value to the template id and treat is as such from this point forward.. - answers.general.presetOrTemplateId = selectedPreset.templateId - - // Expand the preset answers into the answers object. - answers = merge(answers, expandObject(selectedPreset.answers)) + // If using the preset, output the preset name + if (presetId) { + console.log(`Using preset "${selectedPreset.name}"`) } - // Since we know we have the template id, we can find the template. - selectedTemplate = TEMPLATES.find(({id}) => id === answers.general.presetOrTemplateId) - - // Give some feedback to the user. - console.log(`Using template "${selectedTemplate.id}"`) - - // Assign the preset to the context. - context.template = selectedTemplate - context.answers = answers - if (!OUTPUT_DIR_FLAG_ACTIVE) { - outputDir = p.join(process.cwd(), selectedTemplate.id) + outputDir = p.join(process.cwd(), selectedPreset.id) } - // Ask template specific questions and merge into the current context. - // NOTE: Only questions that don't have supplied answers will be asked. This is how we get away with simplifying the code. - let {questions} = selectedTemplate - - // Inquirer doesn't support Regex values for the "validate" property. So lets make a function for it. - questions = questions.map((question) => { - const validator = VALIDATORS.find(({id}) => id === question.validator) - - return { - ...question, - validate: validator?.regex - ? (input) => new RegExp(validator.regex, 'i').test(input) || validator.message - : undefined - } - }) - - // As the template specific questions. If we already have answers from the preset, then no questions - // will be asked. - const projectAnswers = await prompt(questions, answers) + // Ask preset specific questions and merge into the current context. + const {questions = {}, answers = {}} = selectedPreset + if (questions) { + const projectAnswers = await prompt(questions, answers) - // Update the context. - context = merge(context, { - answers: expandObject(projectAnswers) - }) + context = merge(context, { + answers: expandObject(projectAnswers) + }) + } if (context.answers.project.commerce?.instanceUrl) { // Remove protocol since we only use this to setup the OCAPI proxy @@ -605,33 +889,33 @@ const main = async (opts) => { context.answers.project.commerce.instanceUrl = url.hostname } - // Inject the packageJSON into the context for extensible projects. + // Inject the packageJSON into the context for extensibile projects. if (context.answers.project.extend) { const pkgJSON = JSON.parse( - sh.exec(`npm view ${selectedTemplate.source.name}@${templateVersion} --json`, { + sh.exec(`npm view ${selectedPreset.templateSource.id}@${templateVersion} --json`, { silent: true }).stdout ) // NOTE: Here we are rewriting a specific script (extract-default-translations) in order // to update the script location for extensibility. In the future we'll hopefully - // move translations outside of the template and into the sdk where the script for + // move transations outside of the template and into the sdk where the script for // building translations will ultimately live, meaning we won't have to do this. So // its OK for now. if (pkgJSON?.scripts['extract-default-translations']) { pkgJSON.scripts['extract-default-translations'] = pkgJSON.scripts[ 'extract-default-translations' - ].replace('./', `./node_modules/${selectedTemplate.source.name}/`) + ].replace('./', `./node_modules/${selectedPreset.templateSource.id}/`) } if (pkgJSON?.scripts['compile-translations']) { pkgJSON.scripts['compile-translations'] = pkgJSON.scripts[ 'compile-translations' - ].replace('./', `./node_modules/${selectedTemplate.source.name}/`) + ].replace('./', `./node_modules/${selectedPreset.templateSource.id}/`) } if (pkgJSON?.scripts['compile-translations:pseudo']) { pkgJSON.scripts['compile-translations:pseudo'] = pkgJSON.scripts[ 'compile-translations:pseudo' - ].replace('./', `./node_modules/${selectedTemplate.source.name}/`) + ].replace('./', `./node_modules/${selectedPreset.templateSource.id}/`) } context = merge( @@ -651,27 +935,31 @@ const main = async (opts) => { if (require.main === module) { program.name(`pwa-kit-create-app`) - program.description(`Generates a new PWA Kit project. + program.description(`Generate a new PWA Kit project, optionally using a preset. -Example Usage: - ${EXAMPLES.map( - (example) => ` -// ${example.description}\n${example.command}` - ).join('\n')} - `) +Examples: - OPTIONS.forEach((option) => { - if (option.name === '--preset') { - program.option( - option.name, - `The name of a project preset to use (choices: ${PUBLIC_PRESET_NAMES.map( - (x) => `"${x}"` - ).join(', ')})` - ) - } else { - program.option(option.name, option.description, option.defaultValue) - } - }) + ${PRESETS.filter(({private}) => !private).map(({id, description}) => { + return ` + ${program.name()} --preset "${id}"\n${description} + ` + })} + + `) + program + .option('--outputDir ', `Path to the output directory for the new project`) + .option( + '--preset ', + `The name of a project preset to use (choices: ${PUBLIC_PRESET_NAMES.map( + (x) => `"${x}"` + ).join(', ')})` + ) + .option( + '--templateVersion ', + `The version of the template to be generated when it's source is NPM.`, + DEFAULT_TEMPLATE_VERSION + ) + .option('--verbose', `Print additional logging information to the console.`, false) program.parse(process.argv) diff --git a/packages/pwa-storefront-mcp/.eslintignore b/packages/pwa-storefront-mcp/.eslintignore deleted file mode 100644 index 6e7311bb43..0000000000 --- a/packages/pwa-storefront-mcp/.eslintignore +++ /dev/null @@ -1,3 +0,0 @@ -node_modules/ -dist/ -build/ \ No newline at end of file diff --git a/packages/pwa-storefront-mcp/.eslintrc.js b/packages/pwa-storefront-mcp/.eslintrc.js deleted file mode 100644 index 24f1e81314..0000000000 --- a/packages/pwa-storefront-mcp/.eslintrc.js +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright (c) 2025, salesforce.com, inc. - * All rights reserved. - * SPDX-License-Identifier: BSD-3-Clause - * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ - -module.exports = { - extends: [require.resolve('@salesforce/pwa-kit-dev/configs/eslint')] -} diff --git a/packages/pwa-storefront-mcp/CHANGELOG.md b/packages/pwa-storefront-mcp/CHANGELOG.md deleted file mode 100644 index 83d25b6fbb..0000000000 --- a/packages/pwa-storefront-mcp/CHANGELOG.md +++ /dev/null @@ -1,5 +0,0 @@ -## v1.0.0-dev.0 (Jun 26, 2025) -- Add `create_app_guidelines` MCP tool [#2663](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2663) -- Add `create_new_sample_component` MCP tool. [#2651](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2651) -- Add `create_app_guidelines` MCP tool. [#2599](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2599) -- Add `run_site_test` MCP tool [#2645](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2645): diff --git a/packages/pwa-storefront-mcp/README.md b/packages/pwa-storefront-mcp/README.md deleted file mode 100644 index c2f6be4ccc..0000000000 --- a/packages/pwa-storefront-mcp/README.md +++ /dev/null @@ -1,166 +0,0 @@ -# PWA Storefront MCP Server - -A Model Context Protocol (MCP) server that provides AI coding assistance for developing PWA-kit-based storefront apps in an IDE. - -Install and run this MCP server as a local MCP server or run locally via `npx` (when it's available on `npm` in feature). - -## What is MCP? - -The Model Context Protocol (MCP) is an open protocol that enables secure connections between host applications, such as Claude Desktop or other AI assistants, and external data sources and tools. - -## Features - -The PWA Storefront MCP Server provides these features. - -- `development_guidelines`: Helps developers understand and follow PWA Storefront developer guidelines and best practices. -- `create_new_sample_component`: Helps developers create a new sample PWA Storefront component. This feature guides developers through a few simple questions and then generates code for the component based on the commerce data model used, layouts, etc. -- `create_app_guidelines`: Helps developers generate a new PWA Storefront project. -- `run_site_test`: Run site performance or accessibility test for a given site URL (e.g. [https://pwa-kit.mobify-storefront.com](https://pwa-kit.mobify-storefront.com)) - -## Setup - -Install dependencies and build under `pwa-kit` root directory: - -```bash -cd {{pwa-kit root directory}} -npm ci -``` - -## Run the MCP Server - -### Method 1: Run MCP Server from Cursor - -1. Open the Cursor application. - -2. In the Cursor Menu on the top menu bar, click **Settings** > **Cursor Settings...**. - -Cursor Settings Screenshot - -3. Click **Tools & Integrations** > **MCP Tools** > **New MCP Server**. - -Cursor MCP Tools Screenshot - -The `mcp.json` file opens. Add this definition to your `mcp.json` file and replace {{parent-dir-to-mcp}} and {{path-to-app-directory}} placeholders with correct values. - -```json -{ - "mcpServers": { - - "pwa-storefront-mcp": { - "command": "node {{parent-dir-to-mcp}}/pwa-storefront-mcp/dist/server/server.js", - "transport": "stdio", - "args": [], - "env": { - "PWA_STOREFRONT_APP_PATH": "{{path-to-app-directory}}" - } - } - } -} -``` - -After you modify the `mcp.json` file, cursor will do these actions. - -- Start the MCP server. -- Connect to the MCP server as a client. -- List available tools. - -You can go back to MCP Tools and choose to enable/disable any MCP Server or tools. - -### Method 2: Run MCP Server from Claude - -#### Using Claude Desktop - -1. In the Claude app menu, on the top menu bar, click **Developer** > **Edit Config**. -The `claude_desktop_config.json` file opens. - -Claude MCP Config Screenshot - -2. Add this server definition to your `claude_desktop_config.json` and replace {{path-to-node}}, {{parent-dir-to-mcp}} and {{path-to-app-directory}} placeholders with correct values. - -```json -{ - "mcpServers": { - "pwa-storefront-mcp": { - "command": "{{path-to-node}}/node", - "transport": "stdio", - "args": ["{{parent-dir-to-mcp}}/pwa-storefront-mcp/dist/server/server.js"], - "env": { - "PWA_STOREFRONT_APP_PATH": "{{path-to-app-directory}}" - } - } - } -} -``` - -After you modify the `claude_desktop_config.json` file, Claude will do these actions. - -- Start the MCP server. -- Connect to the MCP server as a client. -- List available tools. - -Claude MCP Tools Screenshot - -You can also enable/disable any available tools from here. - -#### Using other MCP clients -The server runs on stdio, so you can test it with any MCP-compatible client. - -### Method 3: Manually start MCP Server - -You can also manually start the server from command line and sending JSON-RPC messages: - -```bash -cd {{dir-to-mcp}} -# Start the server -npm run start - -# Then send JSON-RPC requests to stdin: -{"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {}} -{"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {"name": "create_new_component", "arguments": {}}} -``` - -## Files - -- `server.js` - Main MCP server implementation -- `mcp.json` - MCP configuration file for clients -- `package.json` - Node.js dependencies and scripts - -## Development - -To run the server in development mode: -```bash -npm start -``` - -The server outputs debug information to stderr and handle MCP protocol messages via stdio. - -# Project Structure - -``` -/ (root) - - package.json - - package-lock.json - - README.md - - mcp.json - - claude_desktop_config.json - /src - /server - - server.js - /utils - - pwa-developer-guideline-tool.js - - utils.js - /tests - - test-mcp.js - /docs - /images - - claude-config.png - - claude-list-tools.png - - cursor-list-tools.png - - cursor-settings.pnb - - cursor-integration-guide.md - /node_modules -``` - -- Server code is in `src/server/`. -- Utilities/tools are in `src/utils/`. -- Documentation is in `docs/`. diff --git a/packages/pwa-storefront-mcp/babel.config.js b/packages/pwa-storefront-mcp/babel.config.js deleted file mode 100644 index 79f5aa5857..0000000000 --- a/packages/pwa-storefront-mcp/babel.config.js +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright (c) 2021, salesforce.com, inc. - * All rights reserved. - * SPDX-License-Identifier: BSD-3-Clause - * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ -module.exports = require('internal-lib-build/configs/babel.config') diff --git a/packages/pwa-storefront-mcp/claude_desktop_config.json b/packages/pwa-storefront-mcp/claude_desktop_config.json deleted file mode 100644 index fbdd16f8e8..0000000000 --- a/packages/pwa-storefront-mcp/claude_desktop_config.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "mcpServers": { - "pwa-storefront-mcp": { - "command": "{{path-to-node}}/node", - "transport": "stdio", - "args": ["{{parent-dir-to-mcp}}}/pwa-storefront-mcp/dist/server/server.js"], - "env": { - "PWA_STOREFRONT_APP_PATH": "{{path-to-app-directory}}" - } - } - } -} \ No newline at end of file diff --git a/packages/pwa-storefront-mcp/docs/cursor-integration-guide.md b/packages/pwa-storefront-mcp/docs/cursor-integration-guide.md deleted file mode 100644 index b24bf84b7c..0000000000 --- a/packages/pwa-storefront-mcp/docs/cursor-integration-guide.md +++ /dev/null @@ -1,310 +0,0 @@ -# How to Programmatically Insert Code Blocks in Cursor - -This guide shows different methods to programmatically insert code blocks in files using Cursor. - -## Method 1: Using Your MCP Server (Recommended) - -### Setup -1. **Configure the MCP Server in Cursor.** - ```json - // Add to your Cursor MCP configuration - { - "mcpServers": { - "pwa-storefront-mcp": { - "command": "node", - "args": ["pwa-storefront-mcp/server.js"] - } - } - } - ``` - -2. **Restart Cursor** to load the MCP server. - -### Using the Tools - -#### 1. Analyze Code Structure -```javascript -// Ask Cursor/Claude: "Analyze this React code structure" -// The MCP server will identify: -// - Import statements -// - Component definitions -// - Export statements -// - Insertion points -// - Framework detection (React, Next.js) -// - Styling system detection (Tailwind, CSS) -``` - -#### 2. Insert React Components -```javascript -// Ask Cursor/Claude: "Insert a ProductCard component with Tailwind styling" -// The MCP server will: -// - Analyze the existing code -// - Generate the appropriate component -// - Find the best insertion point -// - Add necessary imports -// - Insert the component code -``` - -#### 3. Create New Component Files -```javascript -// Ask Cursor/Claude: "Create a new Button component file" -// The MCP server will generate a complete component file with: -// - Proper imports -// - Component definition -// - Export statement -// - Styling (Tailwind or CSS) -``` - -## Method 2: Direct File Manipulation - -### Using Node.js Scripts -```javascript -import fs from 'fs/promises'; -import path from 'path'; - -async function insertCodeBlock(filePath, codeBlock, insertionPoint = 'end') { - try { - // Read existing file - const content = await fs.readFile(filePath, 'utf-8'); - const lines = content.split('\n'); - - let insertIndex; - - switch (insertionPoint) { - case 'start': - insertIndex = 0; - break; - case 'end': - insertIndex = lines.length; - break; - case 'after-imports': - // Find last import statement - insertIndex = findLastImportLine(lines) + 1; - break; - case 'before-export': - // Find export default statement - insertIndex = findExportDefaultLine(lines); - break; - default: - insertIndex = lines.length; - } - - // Insert the code block - lines.splice(insertIndex, 0, '', codeBlock, ''); - - // Write back to file - await fs.writeFile(filePath, lines.join('\n'), 'utf-8'); - console.log(`✅ Code inserted into ${filePath}`); - - } catch (error) { - console.error('❌ Error inserting code:', error); - } -} - -function findLastImportLine(lines) { - for (let i = lines.length - 1; i >= 0; i--) { - if (lines[i].trim().startsWith('import ')) { - return i; - } - } - return 0; -} - -function findExportDefaultLine(lines) { - for (let i = 0; i < lines.length; i++) { - if (lines[i].trim().startsWith('export default')) { - return i; - } - } - return lines.length; -} - -// Usage -await insertCodeBlock( - './src/App.js', - `const NewComponent = () => { - return
Hello World
; -};`, - 'after-imports' -); -``` - -### Using the fs module with templates -```javascript -import fs from 'fs/promises'; -import { AddComponentTool } from './AddComponentTool.js'; - -const componentTool = new AddComponentTool(); - -async function insertReactComponent(filePath, componentType, options) { - try { - // Read existing file - const content = await fs.readFile(filePath, 'utf-8'); - - // Use our MCP tool to insert the component - const modifiedCode = componentTool.insertComponent(content, componentType, options); - - // Write back to file - await fs.writeFile(filePath, modifiedCode, 'utf-8'); - console.log(`✅ ${componentType} component inserted into ${filePath}`); - - } catch (error) { - console.error('❌ Error:', error); - } -} - -// Usage -await insertReactComponent('./src/App.js', 'button', { - name: 'SubmitButton', - variant: 'primary', - styling: 'tailwind' -}); -``` - -## Method 3: Using Cursor's AI Commands - -### 1. **Natural Language Commands:** -``` -"Insert a ProductCard component after the imports in App.js" -"Add a modal component with Tailwind styling to this file" -"Create a new button component with these specifications: primary variant, medium size" -``` - -### 2. **Structured Prompts:** -``` -Insert a React component with these specifications. -- Type: ProductCard -- Name: FeaturedProduct -- Styling: Tailwind CSS -- Features: Show price, rating, and add-to-cart button -- Insert after imports in the current file -``` - -### 3. **Code Generation Prompts:** -``` -Generate and insert a complete React component. - -```typescript -interface ProductCardProps { - product: { - id: string; - name: string; - price: number; - image: string; - rating?: number; - }; - onAddToCart: (product: Product) => void; -} -``` - -Create a ProductCard component using this interface. -``` - -## Method 4: VSCode/Cursor Extensions - -### Custom Extension for Code Insertion -```javascript -// extension.js -const vscode = require('vscode'); - -function activate(context) { - let disposable = vscode.commands.registerCommand('extension.insertComponent', async () => { - const editor = vscode.window.activeTextEditor; - if (!editor) return; - - // Get component type from user - const componentType = await vscode.window.showQuickPick([ - 'button', 'card', 'modal', 'product', 'form' - ], { placeHolder: 'Select component type' }); - - if (!componentType) return; - - // Generate component code using your MCP server - const componentCode = await generateComponent(componentType); - - // Find insertion point - const insertionPoint = findInsertionPoint(editor.document); - - // Insert the code - editor.edit(editBuilder => { - editBuilder.insert(insertionPoint, `\n${componentCode}\n`); - }); - }); - - context.subscriptions.push(disposable); -} - -async function generateComponent(type) { - // Call your MCP server or use the AddComponentTool directly - const { AddComponentTool } = await import('./AddComponentTool.js'); - const tool = new AddComponentTool(); - - return tool.createComponentFile( - `Custom${type.charAt(0).toUpperCase() + type.slice(1)}`, - type, - { styling: 'tailwind' } - ); -} -``` - -## Method 5: Automation Scripts - -### Batch Component Generation -```javascript -import { AddComponentTool } from './AddComponentTool.js'; -import fs from 'fs/promises'; -import path from 'path'; - -const componentTool = new AddComponentTool(); - -const componentsToCreate = [ - { name: 'ProductCard', type: 'product', options: { styling: 'tailwind' } }, - { name: 'AddToCartButton', type: 'button', options: { variant: 'primary' } }, - { name: 'ProductModal', type: 'modal', options: { closeOnOverlay: true } }, - { name: 'ReviewCard', type: 'card', options: { showHeader: true } } -]; - -async function generateComponents() { - const componentsDir = './src/components'; - - // Ensure directory exists - await fs.mkdir(componentsDir, { recursive: true }); - - for (const comp of componentsToCreate) { - const componentCode = componentTool.createComponentFile( - comp.name, - comp.type, - comp.options - ); - - const fileName = `${comp.name}.jsx`; - const filePath = path.join(componentsDir, fileName); - - await fs.writeFile(filePath, componentCode, 'utf-8'); - console.log(`✅ Created ${fileName}`); - } -} - -generateComponents().catch(console.error); -``` - -## Best Practices - -1. **Use the MCP Server**: The MCP server provides intelligent analysis and proper code insertion. -2. **Validate Syntax**: Always check generated code for syntax errors. -3. **Preserve Formatting**: Maintain consistent code style and indentation. -4. **Handle Imports**: Ensure necessary imports are added when inserting components. -5. **Test Integration**: Verify that inserted components work with existing code. - -## Demo Commands - -Run the demo to see the MCP server in action: -```bash -npm run demo # or: node demo.js -``` - -This demo demonstrates the following: -- Code structure analysis -- Component insertion -- New file creation -- Real-time code generation \ No newline at end of file diff --git a/packages/pwa-storefront-mcp/docs/images/claude-config.png b/packages/pwa-storefront-mcp/docs/images/claude-config.png deleted file mode 100644 index 672ed781d3..0000000000 Binary files a/packages/pwa-storefront-mcp/docs/images/claude-config.png and /dev/null differ diff --git a/packages/pwa-storefront-mcp/docs/images/claude-list-tools.png b/packages/pwa-storefront-mcp/docs/images/claude-list-tools.png deleted file mode 100644 index 26a88128aa..0000000000 Binary files a/packages/pwa-storefront-mcp/docs/images/claude-list-tools.png and /dev/null differ diff --git a/packages/pwa-storefront-mcp/docs/images/cursor-mcp-tools.png b/packages/pwa-storefront-mcp/docs/images/cursor-mcp-tools.png deleted file mode 100644 index 89738c79d2..0000000000 Binary files a/packages/pwa-storefront-mcp/docs/images/cursor-mcp-tools.png and /dev/null differ diff --git a/packages/pwa-storefront-mcp/docs/images/cursor-settings.png b/packages/pwa-storefront-mcp/docs/images/cursor-settings.png deleted file mode 100644 index a482dad1f9..0000000000 Binary files a/packages/pwa-storefront-mcp/docs/images/cursor-settings.png and /dev/null differ diff --git a/packages/pwa-storefront-mcp/jest-setup.js b/packages/pwa-storefront-mcp/jest-setup.js deleted file mode 100644 index 18bfa62bf2..0000000000 --- a/packages/pwa-storefront-mcp/jest-setup.js +++ /dev/null @@ -1,6 +0,0 @@ -/* - * Copyright (c) 2025, Salesforce, Inc. - * All rights reserved. - * SPDX-License-Identifier: BSD-3-Clause - * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ diff --git a/packages/pwa-storefront-mcp/jest.config.js b/packages/pwa-storefront-mcp/jest.config.js deleted file mode 100644 index 0b5d3d449d..0000000000 --- a/packages/pwa-storefront-mcp/jest.config.js +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (c) 2025, Salesforce, Inc. - * All rights reserved. - * SPDX-License-Identifier: BSD-3-Clause - * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ -// eslint-disable-next-line @typescript-eslint/no-var-requires -const parentConfig = require('@salesforce/pwa-kit-dev/configs/jest/jest.config.js') - -module.exports = { - ...parentConfig, - testEnvironment: 'node', - testMatch: ['**/__tests__/**/*.js', '**/?(*.)+(spec|test).js'], - testPathIgnorePatterns: ['bin/*', 'coverage/*', 'dist/*', 'node_modules/*', 'scripts/*'], - collectCoverageFrom: ['src/**/*.js', '!src/**/*.test.js', '!src/**/*.spec.js'], - coverageDirectory: 'coverage', - coverageReporters: ['text', 'lcov', 'html'], - testTimeout: 10000 -} diff --git a/packages/pwa-storefront-mcp/mcp.json b/packages/pwa-storefront-mcp/mcp.json deleted file mode 100644 index 07084d2158..0000000000 --- a/packages/pwa-storefront-mcp/mcp.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "mcpServers": { - "pwa-storefront-mcp": { - "command": "node {{parent-dir-to-mcp}}/pwa-storefront-mcp/dist/server/server.js", - "transport": "stdio", - "args": [], - "env": { - "PWA_STOREFRONT_APP_PATH": "{{path-to-app-directory}}" - } - } - } -} \ No newline at end of file diff --git a/packages/pwa-storefront-mcp/package-lock.json b/packages/pwa-storefront-mcp/package-lock.json deleted file mode 100644 index a31ab3cb1d..0000000000 --- a/packages/pwa-storefront-mcp/package-lock.json +++ /dev/null @@ -1,3559 +0,0 @@ -{ - "name": "@salesforce/pwa-kit-storefront-mcp", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@salesforce/pwa-kit-storefront-mcp", - "version": "0.1.0", - "license": "ISC", - "dependencies": { - "@babel/runtime": "^7.21.0", - "@modelcontextprotocol/sdk": "^1.13.2", - "node-pty": "1.0.0", - "zod": "^3.25.56" - }, - "devDependencies": { - "@axe-core/playwright": "^4.10.1", - "@babel/node": "^7.22.5", - "@playwright/test": "^1.49.0", - "axe-core": "^4.10.3", - "cross-env": "^5.2.1", - "cross-spawn": "^7.0.6", - "nodemon": "^2.0.22", - "playwright": "^1.49.0" - }, - "engines": { - "node": "^16.11.0 || ^18.0.0 || ^20.0.0 || ^22.0.0", - "npm": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "peer": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@axe-core/playwright": { - "version": "4.10.2", - "resolved": "https://registry.npmjs.org/@axe-core/playwright/-/playwright-4.10.2.tgz", - "integrity": "sha512-6/b5BJjG6hDaRNtgzLIfKr5DfwyiLHO4+ByTLB0cJgWSM8Ll7KqtdblIS6bEkwSF642/Ex91vNqIl3GLXGlceg==", - "dev": true, - "dependencies": { - "axe-core": "~4.10.3" - }, - "peerDependencies": { - "playwright-core": ">= 1.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "dev": true, - "peer": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.27.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.7.tgz", - "integrity": "sha512-xgu/ySj2mTiUFmdE9yCMfBxLp4DHd5DwmbbD05YAuICfodYT3VvRxbrh81LGQ/8UpSdtMdfKMn3KouYDX59DGQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.27.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.7.tgz", - "integrity": "sha512-BU2f9tlKQ5CAthiMIgpzAh4eDTLWo1mqi9jqE2OxMG0E/OM199VJt2q8BztTxpnSW0i1ymdwLXRJnYzvDM5r2w==", - "dev": true, - "peer": true, - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.5", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.27.3", - "@babel/helpers": "^7.27.6", - "@babel/parser": "^7.27.7", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.27.7", - "@babel/types": "^7.27.7", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", - "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", - "dev": true, - "peer": true, - "dependencies": { - "@babel/parser": "^7.27.5", - "@babel/types": "^7.27.3", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", - "dev": true, - "peer": true, - "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", - "dev": true, - "peer": true, - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", - "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", - "dev": true, - "peer": true, - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", - "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", - "dev": true, - "peer": true, - "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.27.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/node": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/node/-/node-7.27.1.tgz", - "integrity": "sha512-ef8ZrhxIku9LrphvyNywpiMf1UJsYQll7S4eKa228ivswPcwmObp98o5h5wL2n9FrSAuo1dsMwJ8cS1LEcBSog==", - "dev": true, - "dependencies": { - "@babel/register": "^7.27.1", - "commander": "^6.2.0", - "core-js": "^3.30.2", - "node-environment-flags": "^1.0.5", - "regenerator-runtime": "^0.14.0", - "v8flags": "^3.1.1" - }, - "bin": { - "babel-node": "bin/babel-node.js" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/parser": { - "version": "7.27.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.7.tgz", - "integrity": "sha512-qnzXzDXdr/po3bOTbTIQZ7+TxNKxpkN5IifVLXS+r7qwynkZfPyjZfE7hCXbo7IoO9TNcSyibgONsf2HauUd3Q==", - "dev": true, - "peer": true, - "dependencies": { - "@babel/types": "^7.27.7" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/register": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.27.1.tgz", - "integrity": "sha512-K13lQpoV54LATKkzBpBAEu1GGSIRzxR9f4IN4V8DCDgiUMo2UDGagEZr3lPeVNJPLkWUi5JE4hCHKneVTwQlYQ==", - "dev": true, - "dependencies": { - "clone-deep": "^4.0.1", - "find-cache-dir": "^2.0.0", - "make-dir": "^2.1.0", - "pirates": "^4.0.6", - "source-map-support": "^0.5.16" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz", - "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "dev": true, - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.27.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.7.tgz", - "integrity": "sha512-X6ZlfR/O/s5EQ/SnUSLzr+6kGnkg8HXGMzpgsMsrJVcfDtH1vIp6ctCN4eZ1LS5c0+te5Cb6Y514fASjMRJ1nw==", - "dev": true, - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.5", - "@babel/parser": "^7.27.7", - "@babel/template": "^7.27.2", - "@babel/types": "^7.27.7", - "debug": "^4.3.1", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.27.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.7.tgz", - "integrity": "sha512-8OLQgDScAOHXnAz2cV+RfzzNMipuLVBz2biuAJFMV9bfkNf393je3VM8CLkjQodW5+iWsSJdSgSWT6rsZoXHPw==", - "dev": true, - "peer": true, - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.11.tgz", - "integrity": "sha512-C512c1ytBTio4MrpWKlJpyFHT6+qfFL8SZ58zBzJ1OOzUEjHeF1BtjY2fH7n4x/g2OV/KiiMLAivOp1DXmiMMw==", - "dev": true, - "peer": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.3.tgz", - "integrity": "sha512-AiR5uKpFxP3PjO4R19kQGIMwxyRyPuXmKEEy301V1C0+1rVjS94EZQXf1QKZYN8Q0YM+estSPhmx5JwNftv6nw==", - "dev": true, - "peer": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.28", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.28.tgz", - "integrity": "sha512-KNNHHwW3EIp4EDYOvYFGyIFfx36R2dNJYH4knnZlF8T5jdbD5Wx8xmSaQ2gP9URkJ04LGEtlcCtwArKcmFcwKw==", - "dev": true, - "peer": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@modelcontextprotocol/sdk": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.13.2.tgz", - "integrity": "sha512-Vx7qOcmoKkR3qhaQ9qf3GxiVKCEu+zfJddHv6x3dY/9P6+uIwJnmuAur5aB+4FDXf41rRrDnOEGkviX5oYZ67w==", - "dependencies": { - "ajv": "^6.12.6", - "content-type": "^1.0.5", - "cors": "^2.8.5", - "cross-spawn": "^7.0.5", - "eventsource": "^3.0.2", - "express": "^5.0.1", - "express-rate-limit": "^7.5.0", - "pkce-challenge": "^5.0.0", - "raw-body": "^3.0.0", - "zod": "^3.23.8", - "zod-to-json-schema": "^3.24.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@playwright/test": { - "version": "1.53.2", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.53.2.tgz", - "integrity": "sha512-tEB2U5z74ebBeyfGNZ3Jfg29AnW+5HlWhvHtb/Mqco9pFdZU1ZLNdVb2UtB5CvmiilNr2ZfVH/qMmAROG/XTzw==", - "dev": true, - "dependencies": { - "playwright": "1.53.2" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", - "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3", - "is-array-buffer": "^3.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.reduce": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.8.tgz", - "integrity": "sha512-DwuEqgXFBwbmZSRqt3BpQigWNUoqw9Ml2dTWdF3B2zQlQX4OeUE0zyuzX0fX0IbTvjdkZbcBTU3idgpO78qkTw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-array-method-boxes-properly": "^1.0.0", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "is-string": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", - "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "is-array-buffer": "^3.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/async-function": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", - "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/axe-core": { - "version": "4.10.3", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz", - "integrity": "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.0", - "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", - "on-finished": "^2.4.1", - "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.25.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", - "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "peer": true, - "dependencies": { - "caniuse-lite": "^1.0.30001726", - "electron-to-chromium": "^1.5.173", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "dev": true, - "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001726", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz", - "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "peer": true - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/content-disposition": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "peer": true - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "engines": { - "node": ">=6.6.0" - } - }, - "node_modules/core-js": { - "version": "3.43.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.43.0.tgz", - "integrity": "sha512-N6wEbTTZSYOY2rYAn85CuvWWkCK6QweMn7/4Nr3w+gDBeBhk/x4EJeY6FPo4QzDoJZxVTv8U7CMvgWk6pOHHqA==", - "dev": true, - "hasInstallScript": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/cross-env": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-5.2.1.tgz", - "integrity": "sha512-1yHhtcfAd1r4nwQgknowuUNfIT9E8dOMMspC36g45dN+iD1blloi7xp8X/xAIDnjHWyt1uQ8PHk2fkNaym7soQ==", - "dev": true, - "dependencies": { - "cross-spawn": "^6.0.5" - }, - "bin": { - "cross-env": "dist/bin/cross-env.js", - "cross-env-shell": "dist/bin/cross-env-shell.js" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/cross-env/node_modules/cross-spawn": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", - "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", - "dev": true, - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/cross-env/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/cross-env/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/cross-env/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "dev": true, - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cross-env/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cross-env/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/data-view-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", - "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/data-view-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", - "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/inspect-js" - } - }, - "node_modules/data-view-byte-offset": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", - "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "node_modules/electron-to-chromium": { - "version": "1.5.178", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.178.tgz", - "integrity": "sha512-wObbz/ar3Bc6e4X5vf0iO8xTN8YAjN/tgiAOJLr7yjYFtP9wAjq8Mb5h0yn6kResir+VYx2DXBj9NNobs0ETSA==", - "dev": true, - "peer": true - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/es-abstract": { - "version": "1.24.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", - "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.2", - "arraybuffer.prototype.slice": "^1.0.4", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "data-view-buffer": "^1.0.2", - "data-view-byte-length": "^1.0.2", - "data-view-byte-offset": "^1.0.1", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "es-set-tostringtag": "^2.1.0", - "es-to-primitive": "^1.3.0", - "function.prototype.name": "^1.1.8", - "get-intrinsic": "^1.3.0", - "get-proto": "^1.0.1", - "get-symbol-description": "^1.1.0", - "globalthis": "^1.0.4", - "gopd": "^1.2.0", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "internal-slot": "^1.1.0", - "is-array-buffer": "^3.0.5", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.2", - "is-negative-zero": "^2.0.3", - "is-regex": "^1.2.1", - "is-set": "^2.0.3", - "is-shared-array-buffer": "^1.0.4", - "is-string": "^1.1.1", - "is-typed-array": "^1.1.15", - "is-weakref": "^1.1.1", - "math-intrinsics": "^1.1.0", - "object-inspect": "^1.13.4", - "object-keys": "^1.1.1", - "object.assign": "^4.1.7", - "own-keys": "^1.0.1", - "regexp.prototype.flags": "^1.5.4", - "safe-array-concat": "^1.1.3", - "safe-push-apply": "^1.0.0", - "safe-regex-test": "^1.1.0", - "set-proto": "^1.0.0", - "stop-iteration-iterator": "^1.1.0", - "string.prototype.trim": "^1.2.10", - "string.prototype.trimend": "^1.0.9", - "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.3", - "typed-array-byte-length": "^1.0.3", - "typed-array-byte-offset": "^1.0.4", - "typed-array-length": "^1.0.7", - "unbox-primitive": "^1.1.0", - "which-typed-array": "^1.1.19" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-array-method-boxes-properly": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", - "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", - "dev": true - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-to-primitive": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", - "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", - "dev": true, - "dependencies": { - "is-callable": "^1.2.7", - "is-date-object": "^1.0.5", - "is-symbol": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eventsource": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", - "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", - "dependencies": { - "eventsource-parser": "^3.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/eventsource-parser": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.3.tgz", - "integrity": "sha512-nVpZkTMM9rF6AQ9gPJpFsNAMt48wIzB5TQgiTLdHiuO8XEDhUgZEhqKlZWXbIzo9VmJ/HvysHqEaVeD5v9TPvA==", - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.0", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express-rate-limit": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", - "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/express-rate-limit" - }, - "peerDependencies": { - "express": ">= 4.11" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/for-each": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", - "dev": true, - "dependencies": { - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/function.prototype.name": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", - "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "functions-have-names": "^1.2.3", - "hasown": "^2.0.2", - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-symbol-description": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", - "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/globalthis": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", - "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", - "dev": true, - "dependencies": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-bigints": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", - "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", - "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", - "dev": true, - "dependencies": { - "dunder-proto": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/homedir-polyfill": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", - "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", - "dev": true, - "dependencies": { - "parse-passwd": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-errors/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", - "dev": true - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/internal-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", - "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.2", - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-array-buffer": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", - "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-async-function": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", - "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", - "dev": true, - "dependencies": { - "async-function": "^1.0.0", - "call-bound": "^1.0.3", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-bigint": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", - "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", - "dev": true, - "dependencies": { - "has-bigints": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-boolean-object": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", - "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-data-view": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", - "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", - "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-finalizationregistry": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", - "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-generator-function": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", - "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3", - "get-proto": "^1.0.0", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", - "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", - "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-number-object": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", - "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" - }, - "node_modules/is-regex": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-set": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", - "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", - "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-string": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", - "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", - "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.2", - "has-symbols": "^1.1.0", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", - "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", - "dev": true, - "dependencies": { - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakmap": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", - "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakref": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", - "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakset": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", - "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "peer": true - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "peer": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "peer": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "peer": true, - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/nan": { - "version": "2.22.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.2.tgz", - "integrity": "sha512-DANghxFkS1plDdRsX0X9pm0Z6SJNN6gBdtXfanwoZ8hooC5gosGFSBGRYHUVPz1asKA/kMRqDRdHrluZ61SpBQ==" - }, - "node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, - "node_modules/node-environment-flags": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", - "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", - "dev": true, - "dependencies": { - "object.getownpropertydescriptors": "^2.0.3", - "semver": "^5.7.0" - } - }, - "node_modules/node-environment-flags/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/node-pty": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.0.0.tgz", - "integrity": "sha512-wtBMWWS7dFZm/VgqElrTvtfMq4GzJ6+edFI0Y0zyzygUSZMgZdraDUMUhCIvkjhJjme15qWmbyJbtAx4ot4uZA==", - "hasInstallScript": true, - "dependencies": { - "nan": "^2.17.0" - } - }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true, - "peer": true - }, - "node_modules/nodemon": { - "version": "2.0.22", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz", - "integrity": "sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==", - "dev": true, - "dependencies": { - "chokidar": "^3.5.2", - "debug": "^3.2.7", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", - "pstree.remy": "^1.1.8", - "semver": "^5.7.1", - "simple-update-notifier": "^1.0.7", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.5" - }, - "bin": { - "nodemon": "bin/nodemon.js" - }, - "engines": { - "node": ">=8.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nodemon" - } - }, - "node_modules/nodemon/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/nodemon/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", - "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0", - "has-symbols": "^1.1.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.getownpropertydescriptors": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.8.tgz", - "integrity": "sha512-qkHIGe4q0lSYMv0XI4SsBTJz3WaURhLvd0lKSgtVuOsJ2krg4SgMw3PIRQFMp07yi++UR3se2mkcLqsBNpBb/A==", - "dev": true, - "dependencies": { - "array.prototype.reduce": "^1.0.6", - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0", - "gopd": "^1.0.1", - "safe-array-concat": "^1.1.2" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/own-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", - "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.2.6", - "object-keys": "^1.1.1", - "safe-push-apply": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-passwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-to-regexp": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", - "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", - "engines": { - "node": ">=16" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "peer": true - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkce-challenge": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", - "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "dependencies": { - "find-up": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/playwright": { - "version": "1.53.2", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.2.tgz", - "integrity": "sha512-6K/qQxVFuVQhRQhFsVZ9fGeatxirtrpPgxzBYWyZLEXJzqYwuL4fuNmfOfD5et1tJE4GScKyPNeLhZeRwuTU3A==", - "dev": true, - "dependencies": { - "playwright-core": "1.53.2" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "fsevents": "2.3.2" - } - }, - "node_modules/playwright-core": { - "version": "1.53.2", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.53.2.tgz", - "integrity": "sha512-ox/OytMy+2w1jcYEYlOo1Hhp8hZkLCximMTUTMBXjGUA1KoFfiSZ+DU+3a739jsPY0yoKH2TFy9S2fsJas8yAw==", - "dev": true, - "bin": { - "playwright-core": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/playwright/node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/possible-typed-array-names": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", - "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/pstree.remy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", - "dev": true - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", - "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.6.3", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/reflect.getprototypeof": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", - "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.7", - "get-proto": "^1.0.1", - "which-builtin-type": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "dev": true - }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", - "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "set-function-name": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/safe-array-concat": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", - "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "has-symbols": "^1.1.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safe-push-apply": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", - "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-regex-test": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", - "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-regex": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "peer": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", - "dependencies": { - "debug": "^4.3.5", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "mime-types": "^3.0.1", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-function-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", - "dev": true, - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-proto": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", - "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", - "dev": true, - "dependencies": { - "dunder-proto": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/simple-update-notifier": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", - "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", - "dev": true, - "dependencies": { - "semver": "~7.0.0" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/simple-update-notifier/node_modules/semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/stop-iteration-iterator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", - "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0", - "internal-slot": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/string.prototype.trim": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", - "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-data-property": "^1.1.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-object-atoms": "^1.0.0", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", - "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", - "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/touch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", - "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", - "dev": true, - "bin": { - "nodetouch": "bin/nodetouch.js" - } - }, - "node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", - "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typed-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", - "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/typed-array-byte-length": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", - "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-byte-offset": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", - "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.15", - "reflect.getprototypeof": "^1.0.9" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-length": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", - "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0", - "reflect.getprototypeof": "^1.0.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/unbox-primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", - "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3", - "has-bigints": "^1.0.2", - "has-symbols": "^1.1.0", - "which-boxed-primitive": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/undefsafe": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", - "dev": true - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "peer": true, - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/v8flags": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", - "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", - "dev": true, - "dependencies": { - "homedir-polyfill": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", - "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", - "dev": true, - "dependencies": { - "is-bigint": "^1.1.0", - "is-boolean-object": "^1.2.1", - "is-number-object": "^1.1.1", - "is-string": "^1.1.1", - "is-symbol": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-builtin-type": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", - "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.2", - "function.prototype.name": "^1.1.6", - "has-tostringtag": "^1.0.2", - "is-async-function": "^2.0.0", - "is-date-object": "^1.1.0", - "is-finalizationregistry": "^1.1.0", - "is-generator-function": "^1.0.10", - "is-regex": "^1.2.1", - "is-weakref": "^1.0.2", - "isarray": "^2.0.5", - "which-boxed-primitive": "^1.1.0", - "which-collection": "^1.0.2", - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-collection": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", - "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", - "dev": true, - "dependencies": { - "is-map": "^2.0.3", - "is-set": "^2.0.3", - "is-weakmap": "^2.0.2", - "is-weakset": "^2.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", - "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "for-each": "^0.3.5", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "peer": true - }, - "node_modules/zod": { - "version": "3.25.67", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", - "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/zod-to-json-schema": { - "version": "3.24.6", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", - "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", - "peerDependencies": { - "zod": "^3.24.1" - } - } - } -} diff --git a/packages/pwa-storefront-mcp/package.json b/packages/pwa-storefront-mcp/package.json deleted file mode 100644 index 19c1241ace..0000000000 --- a/packages/pwa-storefront-mcp/package.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "name": "@salesforce/pwa-kit-storefront-mcp", - "version": "0.1.0", - "private": true, - "description": "MCP server that helps you build Salesforce Commerce Cloud PWA Kit Composable Storefront", - "main": "dist/server/server.js", - "files": [ - "CHANGELOG.md", - "LICENSE", - "dist/**/*.{js,d.ts}", - "!dist/CHANGELOG.md", - "!dist/README.md", - "!**/*.test.{ts,js}" - ], - "scripts": { - "build": "cross-env NODE_ENV=production internal-lib-build build", - "build:watch": "nodemon --watch 'src/**' --ext 'js,ts' --exec 'npm run build'", - "format": "pwa-kit-dev format \"**/*.{js,jsx}\"", - "lint": "npm run lint:js", - "lint:fix": "npm run lint:js -- --fix", - "lint:js": "pwa-kit-dev lint \"**/*.{js,ts}\"", - "prepare": "npm run build", - "test": "internal-lib-build test", - "test:inspect": "node --inspect-brk jest --runInBand", - "test:watch": "npm test -- --watch", - "start": "node dist/server/server.js" - }, - "keywords": [ - "mcp", - "server", - "pwa", - "salesforce", - "commerce", - "commerce cloud", - "pwa kit", - "composable storefront" - ], - "author": "Spark Team", - "license": "ISC", - "dependencies": { - "@babel/runtime": "^7.21.0", - "@modelcontextprotocol/sdk": "^1.13.2", - "zod": "^3.25.56" - }, - "devDependencies": { - "@axe-core/playwright": "^4.10.1", - "@babel/node": "^7.22.5", - "@playwright/test": "^1.49.0", - "@salesforce/pwa-kit-dev": "3.11.0-dev.0", - "axe-core": "^4.10.3", - "cross-env": "^5.2.1", - "cross-spawn": "^7.0.6", - "internal-lib-build": "3.11.0-dev.0", - "nodemon": "^2.0.22", - "playwright": "^1.49.0" - }, - "engines": { - "node": "^16.11.0 || ^18.0.0 || ^20.0.0 || ^22.0.0", - "npm": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0" - }, - "publishConfig": { - "directory": "dist" - } -} diff --git a/packages/pwa-storefront-mcp/src/data/CategoryDocument.json b/packages/pwa-storefront-mcp/src/data/CategoryDocument.json deleted file mode 100644 index 222854b392..0000000000 --- a/packages/pwa-storefront-mcp/src/data/CategoryDocument.json +++ /dev/null @@ -1,239 +0,0 @@ -{ - "description": "Document representing a category.", - "properties": { - "c_alternativeUrl": { - "$ref": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/markup_text", - "description": "Renders an alternative URL in main navigation. Uses Commerce Cloud content url notation. For example: $url('Account-Show')$ or normal URL http://xchange.demandware.com", - "x-label": { - "default": "Alternative URL" - } - }, - "c_catBannerID": { - "description": "Used to define the content asset used to populate a grid page banner for a category. This value is applied to all sub-category navigation (cascading) if no specific catBannerID has been defined for a sub-category.", - "x-label": { - "default": "Category Banner ID" - }, - "type": "string" - }, - "c_customCSSFile": { - "$ref": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/media_file", - "description": "Use this attribute to apply custom styles for this category.", - "x-label": { - "default": "Custom CSS File" - } - }, - "c_enableCompare": { - "description": "Used to define if/when the Compare feature is to be visualized in the storefront based on navigation. If enableCompare = FALSE, no Compare checkboxes will be displayed in the grid view. If enableCompare = TRUE, the category (and its children) will support the Compare feature.", - "x-label": { - "default": "Enable Compare" - }, - "type": "boolean" - }, - "c_headerMenuBanner": { - "$ref": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/markup_text", - "x-label": { - "default": "Header Menu Banner" - } - }, - "c_headerMenuOrientation": { - "description": "Which way to orient the menu and optional header menu HTML. Vertical will list all in one line. Horizontal will list in columns.", - "enum": [ - "Horizontal", - "Vertical" - ], - "x-enum-labels": [ - { - "default": "Horizontal" - }, - { - "default": "Vertical" - } - ], - "x-label": { - "default": "Header Menu Orientation" - }, - "type": "string" - }, - "c_showInMenu": { - "description": "Used to indicate that a category (such as Mens -> Footwear -> Boots) will display in the roll-over navigation. A sub-category only shows if also the parent category is marked as showInMenu. Up to three category levels are shown in roll-over navigation.", - "x-label": { - "default": "Show in Menu Navigation" - }, - "type": "boolean" - }, - "c_sizeChartID": { - "description": "Used to define the content asset ID of the Size Chart that is appropriate for products whose PRIMARY category is the associated category (and its children). Whenever a product detail page (or quick view) is rendered, the Size Chart link is populated based on the value of this attribute for the products primary categorization. If not defined, NO size chart link is displayed.", - "x-label": { - "default": "Size Chart" - }, - "type": "string" - }, - "c_slotBannerHtml": { - "additionalProperties": { - "$ref": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/markup_text" - }, - "format": "localized", - "x-label": { - "default": "Slot Banner HTML" - }, - "type": "object" - }, - "c_slotBannerImage": { - "$ref": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/media_file", - "description": "Image used on either the top or bottom slot on the category landing pages.", - "x-label": { - "default": "Category Landing Slot Image" - } - }, - "catalog_id": { - "description": "The id of the catalog that contains it.", - "x-label": { - "default": "ID" - }, - "type": "string" - }, - "categories": { - "description": "The array of sub categories for the category.", - "items": { - "$ref": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/category" - }, - "type": "array" - }, - "creation_date": { - "description": "Returns the value of attribute 'creationDate'.", - "format": "date-time", - "x-label": { - "default": "Creation Date" - }, - "readOnly": true, - "type": "string" - }, - "description": { - "additionalProperties": { - "type": "string" - }, - "description": "The localized description of the category.", - "format": "localized", - "x-label": { - "default": "Description" - }, - "type": "object" - }, - "id": { - "description": "The id of the category.", - "x-label": { - "default": "ID" - }, - "maxLength": 256, - "minLength": 1, - "type": "string" - }, - "image": { - "description": "The name of the category image. The URL to the image is computed.", - "x-label": { - "default": "Standard Image" - }, - "maxLength": 256, - "minLength": 1, - "type": "string" - }, - "last_modified": { - "description": "Returns the value of attribute 'lastModified'.", - "format": "date-time", - "x-label": { - "default": "Last Modified" - }, - "readOnly": true, - "type": "string" - }, - "link": { - "description": "The URL to get the category. This is a computed attribute and cannot be modified.", - "type": "string" - }, - "name": { - "additionalProperties": { - "type": "string" - }, - "description": "The localized name of the category.", - "format": "localized", - "x-label": { - "default": "Name" - }, - "type": "object" - }, - "online": { - "description": "The online status of the category determines if it is visible in the storefront. Defaults to false if not specified on create.", - "x-label": { - "default": "Online" - }, - "type": "boolean" - }, - "page_description": { - "additionalProperties": { - "type": "string" - }, - "description": "The localized page description of the category.", - "format": "localized", - "x-label": { - "default": "Page Description" - }, - "type": "object" - }, - "page_keywords": { - "additionalProperties": { - "type": "string" - }, - "description": "The localized page keywords for the category.", - "format": "localized", - "x-label": { - "default": "Page Keywords" - }, - "type": "object" - }, - "page_title": { - "additionalProperties": { - "type": "string" - }, - "description": "The localized page title of the category.", - "format": "localized", - "x-label": { - "default": "Page Title" - }, - "type": "object" - }, - "parent_category_id": { - "description": "The id of the parent category. Defaults to root if not specified on create.", - "maxLength": 256, - "minLength": 1, - "type": "string" - }, - "paths": { - "description": "The paths that contains catalog and category information requested by non-standard expand request", - "items": { - "$ref": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/path_record" - }, - "type": "array" - }, - "position": { - "description": "The position of the category determines the display order in the storefront.", - "format": "double", - "type": "number" - }, - "sorting_rules": { - "description": "The sorting rules that are attached to the category requested by non-standard expand request", - "items": { - "$ref": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/sorting_rule" - }, - "type": "array" - }, - "thumbnail": { - "description": "The name of the category thumbnail. The URL to the thumbnail is computed.", - "x-label": { - "default": "Thumbnail Image" - }, - "maxLength": 256, - "minLength": 1, - "type": "string" - } - } -} \ No newline at end of file diff --git a/packages/pwa-storefront-mcp/src/data/DocumentList.json b/packages/pwa-storefront-mcp/src/data/DocumentList.json deleted file mode 100644 index e967e4b093..0000000000 --- a/packages/pwa-storefront-mcp/src/data/DocumentList.json +++ /dev/null @@ -1,920 +0,0 @@ -{ - "data": [ - { - "id": "ab_test", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/ab_test" - }, - { - "id": "ab_test_group", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/ab_test_group" - }, - { - "id": "ab_test_search_result", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/ab_test_search_result" - }, - { - "id": "ab_test_segment", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/ab_test_segment" - }, - { - "id": "ab_test_segment_stats", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/ab_test_segment_stats" - }, - { - "id": "ab_test_trigger", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/ab_test_trigger" - }, - { - "id": "ab_tests", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/ab_tests" - }, - { - "id": "access_key_details", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/access_key_details" - }, - { - "id": "access_key_update_request", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/access_key_update_request" - }, - { - "id": "account_transaction", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/account_transaction" - }, - { - "id": "alert_descriptor", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/alert_descriptor" - }, - { - "id": "alert_descriptor_settings", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/alert_descriptor_settings" - }, - { - "id": "alert_descriptors", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/alert_descriptors" - }, - { - "id": "alert_descriptors_revalidation_request", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/alert_descriptors_revalidation_request" - }, - { - "id": "alert_settings", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/alert_settings" - }, - { - "id": "application_permissions", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/application_permissions" - }, - { - "id": "bmpermissions", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/bmpermissions" - }, - { - "id": "bool_filter", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/bool_filter" - }, - { - "id": "bool_query", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/bool_query" - }, - { - "id": "campaign", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/campaign" - }, - { - "id": "campaign_search_result", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/campaign_search_result" - }, - { - "id": "campaigns", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/campaigns" - }, - { - "id": "cartridge_path_add_request", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/cartridge_path_add_request" - }, - { - "id": "cartridge_path_api_response", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/cartridge_path_api_response" - }, - { - "id": "cartridge_path_create_request", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/cartridge_path_create_request" - }, - { - "id": "catalog", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/catalog" - }, - { - "id": "catalog_category_id", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/catalog_category_id" - }, - { - "id": "catalog_search_result", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/catalog_search_result" - }, - { - "id": "catalogs", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/catalogs" - }, - { - "id": "categories", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/categories" - }, - { - "id": "category", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/category" - }, - { - "id": "category_link", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/category_link" - }, - { - "id": "category_links", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/category_links" - }, - { - "id": "category_product_assignment", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/category_product_assignment" - }, - { - "id": "category_product_assignment_search_request", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/category_product_assignment_search_request" - }, - { - "id": "category_product_assignment_search_result", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/category_product_assignment_search_result" - }, - { - "id": "category_search_result", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/category_search_result" - }, - { - "id": "code_version", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/code_version" - }, - { - "id": "code_version_result", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/code_version_result" - }, - { - "id": "content_asset", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/content_asset" - }, - { - "id": "content_asset_result", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/content_asset_result" - }, - { - "id": "content_folder", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/content_folder" - }, - { - "id": "content_folder_assignment", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/content_folder_assignment" - }, - { - "id": "content_folder_result", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/content_folder_result" - }, - { - "id": "content_sub_folder_result", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/content_sub_folder_result" - }, - { - "id": "coupon", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/coupon" - }, - { - "id": "coupon_code", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/coupon_code" - }, - { - "id": "coupon_codes", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/coupon_codes" - }, - { - "id": "coupon_multi_codes_request", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/coupon_multi_codes_request" - }, - { - "id": "coupon_redemption", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/coupon_redemption" - }, - { - "id": "coupon_redemption_search_result", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/coupon_redemption_search_result" - }, - { - "id": "coupon_search_result", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/coupon_search_result" - }, - { - "id": "coupon_system_code_config", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/coupon_system_code_config" - }, - { - "id": "coupons", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/coupons" - }, - { - "id": "credentials", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/credentials" - }, - { - "id": "cscpermissions", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/cscpermissions" - }, - { - "id": "custom_log_settings", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/custom_log_settings" - }, - { - "id": "custom_object", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/custom_object" - }, - { - "id": "custom_object_search_result", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/custom_object_search_result" - }, - { - "id": "customer", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/customer" - }, - { - "id": "customer_address", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/customer_address" - }, - { - "id": "customer_address_result", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/customer_address_result" - }, - { - "id": "customer_group", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/customer_group" - }, - { - "id": "customer_group_member", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/customer_group_member" - }, - { - "id": "customer_group_member_search_result", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/customer_group_member_search_result" - }, - { - "id": "customer_group_members", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/customer_group_members" - }, - { - "id": "customer_group_search_result", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/customer_group_search_result" - }, - { - "id": "customer_groups", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/customer_groups" - }, - { - "id": "customer_list", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/customer_list" - }, - { - "id": "customer_list_link", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/customer_list_link" - }, - { - "id": "customer_search_hit", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/customer_search_hit" - }, - { - "id": "customer_search_result", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/customer_search_result" - }, - { - "id": "ecdn_log_fetch_request", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/ecdn_log_fetch_request" - }, - { - "id": "ecdn_log_fetch_response", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/ecdn_log_fetch_response" - }, - { - "id": "empty_body", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/empty_body" - }, - { - "id": "filter", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/filter" - }, - { - "id": "filtered_query", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/filtered_query" - }, - { - "id": "functional_permission", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/functional_permission" - }, - { - "id": "functional_permissions", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/functional_permissions" - }, - { - "id": "gift_certificate", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/gift_certificate" - }, - { - "id": "gift_certificate_search_result", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/gift_certificate_search_result" - }, - { - "id": "gift_certificates", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/gift_certificates" - }, - { - "id": "image_group", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/image_group" - }, - { - "id": "inventory_list", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/inventory_list" - }, - { - "id": "inventory_list_search_result", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/inventory_list_search_result" - }, - { - "id": "inventory_lists", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/inventory_lists" - }, - { - "id": "job_execution", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/job_execution" - }, - { - "id": "job_execution_continue_information", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/job_execution_continue_information" - }, - { - "id": "job_execution_parameter", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/job_execution_parameter" - }, - { - "id": "job_execution_retry_information", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/job_execution_retry_information" - }, - { - "id": "job_execution_search_result", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/job_execution_search_result" - }, - { - "id": "job_step_execution", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/job_step_execution" - }, - { - "id": "locale", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/locale" - }, - { - "id": "locale_permission", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/locale_permission" - }, - { - "id": "locale_permissions", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/locale_permissions" - }, - { - "id": "locale_result", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/locale_result" - }, - { - "id": "log_category", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/log_category" - }, - { - "id": "markup_text", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/markup_text" - }, - { - "id": "master", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/master" - }, - { - "id": "match_all_query", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/match_all_query" - }, - { - "id": "media_file", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/media_file" - }, - { - "id": "menu_action", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/menu_action" - }, - { - "id": "menu_item", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/menu_item" - }, - { - "id": "metric_response", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/metric_response" - }, - { - "id": "module_permissions", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/module_permissions" - }, - { - "id": "money", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/money" - }, - { - "id": "nested_query", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/nested_query" - }, - { - "id": "object_attribute_definition", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/object_attribute_definition" - }, - { - "id": "object_attribute_definition_search_result", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/object_attribute_definition_search_result" - }, - { - "id": "object_attribute_definitions", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/object_attribute_definitions" - }, - { - "id": "object_attribute_group", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/object_attribute_group" - }, - { - "id": "object_attribute_group_search_result", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/object_attribute_group_search_result" - }, - { - "id": "object_attribute_groups", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/object_attribute_groups" - }, - { - "id": "object_attribute_value_definition", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/object_attribute_value_definition" - }, - { - "id": "object_type_definition", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/object_type_definition" - }, - { - "id": "object_type_definition_search_result", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/object_type_definition_search_result" - }, - { - "id": "object_type_definitions", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/object_type_definitions" - }, - { - "id": "ocapi_configs_api_request", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/ocapi_configs_api_request" - }, - { - "id": "ocapi_configs_api_response", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/ocapi_configs_api_response" - }, - { - "id": "order_confirmation_status_update_request", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/order_confirmation_status_update_request" - }, - { - "id": "order_export_status_update_request", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/order_export_status_update_request" - }, - { - "id": "order_external_status_update_request", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/order_external_status_update_request" - }, - { - "id": "order_payment_status_update_request", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/order_payment_status_update_request" - }, - { - "id": "order_shipping_status_update_request", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/order_shipping_status_update_request" - }, - { - "id": "order_status_update_request", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/order_status_update_request" - }, - { - "id": "order_update_request", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/order_update_request" - }, - { - "id": "organization_preferences", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/organization_preferences" - }, - { - "id": "password_change_request", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/password_change_request" - }, - { - "id": "path_record", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/path_record" - }, - { - "id": "payment_instrument_update_request", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/payment_instrument_update_request" - }, - { - "id": "payment_transaction_update_request", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/payment_transaction_update_request" - }, - { - "id": "preference_value", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/preference_value" - }, - { - "id": "preference_value_search_result", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/preference_value_search_result" - }, - { - "id": "product", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/product" - }, - { - "id": "product_inventory_record", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/product_inventory_record" - }, - { - "id": "product_inventory_record_allocation", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/product_inventory_record_allocation" - }, - { - "id": "product_inventory_records", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/product_inventory_records" - }, - { - "id": "product_option", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/product_option" - }, - { - "id": "product_option_value", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/product_option_value" - }, - { - "id": "product_option_values", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/product_option_values" - }, - { - "id": "product_options", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/product_options" - }, - { - "id": "product_search_result", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/product_search_result" - }, - { - "id": "product_type", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/product_type" - }, - { - "id": "promotion", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/promotion" - }, - { - "id": "promotion_abtest_group_assignment", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/promotion_abtest_group_assignment" - }, - { - "id": "promotion_assignment_information", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/promotion_assignment_information" - }, - { - "id": "promotion_campaign_assignment", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/promotion_campaign_assignment" - }, - { - "id": "promotion_campaign_assignment_search_result", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/promotion_campaign_assignment_search_result" - }, - { - "id": "promotion_search_result", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/promotion_search_result" - }, - { - "id": "promotions", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/promotions" - }, - { - "id": "query", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/query" - }, - { - "id": "query_filter", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/query_filter" - }, - { - "id": "range2_filter", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/range2_filter" - }, - { - "id": "range_filter", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/range_filter" - }, - { - "id": "recommender", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/recommender" - }, - { - "id": "recommenders_result", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/recommenders_result" - }, - { - "id": "recurrence", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/recurrence" - }, - { - "id": "redemption_limit_per_period", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/redemption_limit_per_period" - }, - { - "id": "redemption_limits", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/redemption_limits" - }, - { - "id": "resource_info", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/resource_info" - }, - { - "id": "resource_object", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/resource_object" - }, - { - "id": "result_page", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/result_page" - }, - { - "id": "role", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/role" - }, - { - "id": "role_functional_permission", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/role_functional_permission" - }, - { - "id": "role_functional_permissions", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/role_functional_permissions" - }, - { - "id": "role_locale_permission", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/role_locale_permission" - }, - { - "id": "role_locale_permissions", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/role_locale_permissions" - }, - { - "id": "role_module_permission", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/role_module_permission" - }, - { - "id": "role_module_permissions", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/role_module_permissions" - }, - { - "id": "role_permissions", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/role_permissions" - }, - { - "id": "role_search_request", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/role_search_request" - }, - { - "id": "role_search_result", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/role_search_result" - }, - { - "id": "role_webdav_permission", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/role_webdav_permission" - }, - { - "id": "role_webdav_permissions", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/role_webdav_permissions" - }, - { - "id": "roles", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/roles" - }, - { - "id": "rule", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/rule" - }, - { - "id": "schedule", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/schedule" - }, - { - "id": "search_request", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/search_request" - }, - { - "id": "shipping_address_update_request", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/shipping_address_update_request" - }, - { - "id": "site", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/site" - }, - { - "id": "site_preferences", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/site_preferences" - }, - { - "id": "site_resource_info", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/site_resource_info" - }, - { - "id": "site_search_result", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/site_search_result" - }, - { - "id": "sites", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/sites" - }, - { - "id": "slot", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/slot" - }, - { - "id": "slot_configuration", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/slot_configuration" - }, - { - "id": "slot_configuration_abtest_group_assignment", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/slot_configuration_abtest_group_assignment" - }, - { - "id": "slot_configuration_assignment_information", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/slot_configuration_assignment_information" - }, - { - "id": "slot_configuration_campaign_assignment", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/slot_configuration_campaign_assignment" - }, - { - "id": "slot_configuration_campaign_assignment_search_result", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/slot_configuration_campaign_assignment_search_result" - }, - { - "id": "slot_configuration_search_result", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/slot_configuration_search_result" - }, - { - "id": "slot_configurations", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/slot_configurations" - }, - { - "id": "slot_content", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/slot_content" - }, - { - "id": "slot_search_result", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/slot_search_result" - }, - { - "id": "slots", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/slots" - }, - { - "id": "sort", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/sort" - }, - { - "id": "sorting_rule", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/sorting_rule" - }, - { - "id": "sorting_rule_search_result", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/sorting_rule_search_result" - }, - { - "id": "sorting_rule_step", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/sorting_rule_step" - }, - { - "id": "source_code_group", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/source_code_group" - }, - { - "id": "source_code_group_search_result", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/source_code_group_search_result" - }, - { - "id": "source_code_groups", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/source_code_groups" - }, - { - "id": "source_code_redirect_info", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/source_code_redirect_info" - }, - { - "id": "source_code_specification", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/source_code_specification" - }, - { - "id": "status", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/status" - }, - { - "id": "status_metadata", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/status_metadata" - }, - { - "id": "store", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/store" - }, - { - "id": "store_search_result", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/store_search_result" - }, - { - "id": "stores", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/stores" - }, - { - "id": "tag", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/tag" - }, - { - "id": "term_filter", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/term_filter" - }, - { - "id": "term_query", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/term_query" - }, - { - "id": "text_query", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/text_query" - }, - { - "id": "time_of_day", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/time_of_day" - }, - { - "id": "user", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/user" - }, - { - "id": "user_search_result", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/user_search_result" - }, - { - "id": "users", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/users" - }, - { - "id": "variant", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/variant" - }, - { - "id": "variant_search_result", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/variant_search_result" - }, - { - "id": "variants", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/variants" - }, - { - "id": "variation_attribute", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/variation_attribute" - }, - { - "id": "variation_attribute_value", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/variation_attribute_value" - }, - { - "id": "variation_attribute_values", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/variation_attribute_values" - }, - { - "id": "variation_attributes", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/variation_attributes" - }, - { - "id": "variation_group", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/variation_group" - }, - { - "id": "variation_groups", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/variation_groups" - }, - { - "id": "version_range_object", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/version_range_object" - }, - { - "id": "webdav_permission", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/webdav_permission" - }, - { - "id": "webdav_permissions", - "link": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/webdav_permissions" - } - ] -} \ No newline at end of file diff --git a/packages/pwa-storefront-mcp/src/data/ProductDocument.json b/packages/pwa-storefront-mcp/src/data/ProductDocument.json deleted file mode 100644 index 74de1d4e66..0000000000 --- a/packages/pwa-storefront-mcp/src/data/ProductDocument.json +++ /dev/null @@ -1,1458 +0,0 @@ -{ - "description": "Document representing a product", - "properties": { - "assigned_categories": { - "description": "The catalog categories that the product is assigned to", - "items": { - "$ref": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/catalog_category_id" - }, - "type": "array" - }, - "ats": { - "description": "The ATS(Available To Sell) inventory value of the product. This is a calculated value.", - "format": "double", - "type": "number" - }, - "brand": { - "description": "The product's brand.", - "x-label": { - "default": "Brand" - }, - "type": "string" - }, - "bundled_products": { - "description": "The array of bundled products which the product includes.", - "items": { - "$ref": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/product" - }, - "type": "array" - }, - "c_availableForInStorePickup": { - "description": "Signals if there are inventory lists for brick-and-mortar stores associated with this product.", - "x-label": { - "default": "Available for In Store Pickup" - }, - "type": "boolean" - }, - "c_batteryLife": { - "x-label": { - "default": "Battery Life" - }, - "type": "string" - }, - "c_batteryType": { - "x-label": { - "default": "Battery Type" - }, - "type": "string" - }, - "c_bootType": { - "description": "Type of Boot for search refinement.", - "items": { - "enum": [ - "0010", - "0020", - "0030", - "0040", - "0050", - "0060", - "0070" - ], - "x-enum-labels": [ - { - "default": "Winter" - }, - { - "default": "Waterproof" - }, - { - "default": "Classic" - }, - { - "default": "Ankle & Mid" - }, - { - "default": "Tall" - }, - { - "default": "6\"" - }, - { - "default": "Casual" - } - ], - "type": "string" - }, - "x-label": { - "default": "Boot Type" - }, - "type": "array" - }, - "c_bottomType": { - "description": "Bottom type for search refinement", - "items": { - "enum": [ - "0010", - "0020", - "0030", - "0040", - "0050", - "0060", - "0070", - "0080" - ], - "x-enum-labels": [ - { - "default": "Khakis" - }, - { - "default": "Denim" - }, - { - "default": "Dress" - }, - { - "default": "Casual" - }, - { - "default": "Shorts" - }, - { - "default": "Work" - }, - { - "default": "Cargo" - }, - { - "default": "Fleece" - } - ], - "type": "string" - }, - "x-label": { - "default": "Bottom Type" - }, - "type": "array" - }, - "c_color": { - "description": "Product color used for variation attribute", - "x-label": { - "default": "Color" - }, - "type": "string" - }, - "c_consoleWarranty": { - "x-label": { - "default": "Extended Warranty" - }, - "type": "string" - }, - "c_customCSSFile": { - "$ref": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/media_file", - "description": "Use this attribute to apply custom styles for this product.", - "x-label": { - "default": "Custom CSS File" - } - }, - "c_digitalCameraFeatures": { - "items": { - "enum": [ - "0010", - "0020", - "0030", - "0040", - "0050", - "0060", - "0070", - "0080", - "0090", - "0100", - "0110", - "0120", - "0130", - "0140", - "0150", - "0160", - "0170", - "0180", - "0190", - "0200", - "0210", - "0220", - "0230", - "0240", - "0250", - "0260", - "0270", - "0280" - ], - "x-enum-labels": [ - { - "default": "Built In Flash" - }, - { - "default": "HD Video" - }, - { - "default": "GPS Geo Tagging" - }, - { - "default": "Variable Self Timer" - }, - { - "default": "Image Optimizer" - }, - { - "default": "Stop Motion Video" - }, - { - "default": "Self Cleaning" - }, - { - "default": "Pro Performance" - }, - { - "default": "51 Area Autofocus" - }, - { - "default": "Super Fast Startup" - }, - { - "default": "Easy to Learn" - }, - { - "default": "Wide Angle Optical Zoom" - }, - { - "default": "Manual Controls" - }, - { - "default": "Wired LAN" - }, - { - "default": "GPS Enabled" - }, - { - "default": "In Camera Red Eye Reduction" - }, - { - "default": "Large LCD" - }, - { - "default": "Optical Zoom" - }, - { - "default": "Anti-Shake" - }, - { - "default": "Touch Screen" - }, - { - "default": "Image Stabilization" - }, - { - "default": "Fast Shutter" - }, - { - "default": "Slim" - }, - { - "default": "35mm Full Frame Sensor" - }, - { - "default": "Wide Angle|Continuous Shooting" - }, - { - "default": "Continuous Shooting" - }, - { - "default": "ISO 1600" - }, - { - "default": "Red Eye Reduction" - } - ], - "type": "string" - }, - "x-label": { - "default": "Digital Camera Features" - }, - "type": "array" - }, - "c_digitalCameraPixels": { - "x-label": { - "default": "Digital Camera Pixels" - }, - "type": "string" - }, - "c_digitalCameraType": { - "x-label": { - "default": "Digital Camera Type" - }, - "type": "string" - }, - "c_digitalCameraWarranty": { - "x-label": { - "default": "Extended Warranty" - }, - "type": "string" - }, - "c_dimDepth": { - "x-label": { - "default": "Depth" - }, - "type": "string" - }, - "c_dimHeight": { - "x-label": { - "default": "Height" - }, - "type": "string" - }, - "c_dimWeight": { - "x-label": { - "default": "Weight" - }, - "type": "string" - }, - "c_dimWidth": { - "x-label": { - "default": "Width" - }, - "type": "string" - }, - "c_displaySize": { - "x-label": { - "default": "Display Size" - }, - "type": "string" - }, - "c_gameGenre": { - "items": { - "enum": [ - "Action", - "Educational", - "Kids", - "Racing", - "Role-Playing", - "Sports", - "Strategy" - ], - "x-enum-labels": [ - { - "default": "Action" - }, - { - "default": "Educational" - }, - { - "default": "Kids" - }, - { - "default": "Racing" - }, - { - "default": "Role-Playing" - }, - { - "default": "Sports" - }, - { - "default": "Strategy" - } - ], - "type": "string" - }, - "x-label": { - "default": "Genre" - }, - "type": "array" - }, - "c_gameRating": { - "x-label": { - "default": "Game Rating" - }, - "type": "string" - }, - "c_gameSystemType": { - "x-label": { - "default": "Gaming System" - }, - "type": "string" - }, - "c_gpsFeatures": { - "items": { - "enum": [ - "0010", - "0020", - "0030", - "0040", - "0050", - "0060", - "0070", - "0080", - "0090", - "0100", - "0110", - "0120", - "0130", - "0140", - "0150", - "0160", - "0170", - "0180", - "0190", - "0200", - "0210", - "0230" - ], - "x-enum-labels": [ - { - "default": "Preloaded" - }, - { - "default": "Voice Prompts" - }, - { - "default": "3D" - }, - { - "default": "Touch Screen" - }, - { - "default": "FM Traffic" - }, - { - "default": "MSN Direct" - }, - { - "default": "MP3 Player" - }, - { - "default": "Picture Viewer" - }, - { - "default": "Bluetooth®" - }, - { - "default": "Speech Recognition" - }, - { - "default": "XM Navtraffic" - }, - { - "default": "Waterproof" - }, - { - "default": "Motorcycle Ready" - }, - { - "default": "Lunar Tracking" - }, - { - "default": "Wrist Wear" - }, - { - "default": "Barometer" - }, - { - "default": "Compass" - }, - { - "default": "Geocaching" - }, - { - "default": "Jeppesen Enabled" - }, - { - "default": "Built-in HIS" - }, - { - "default": "Auto Logbook" - }, - { - "default": "Automotive Friendly" - } - ], - "type": "string" - }, - "x-label": { - "default": "GPS Features" - }, - "type": "array" - }, - "c_gpsType": { - "items": { - "enum": [ - "0010", - "0020", - "0030", - "0040" - ], - "x-enum-labels": [ - { - "default": "Automotive" - }, - { - "default": "Personal" - }, - { - "default": "Outdoor" - }, - { - "default": "Aviation" - } - ], - "type": "string" - }, - "x-label": { - "default": "GPS Type" - }, - "type": "array" - }, - "c_gpsWarranty": { - "x-label": { - "default": "Extended Warranty" - }, - "type": "string" - }, - "c_imageAspectRatio": { - "x-label": { - "default": "Image Aspect Ratio" - }, - "type": "string" - }, - "c_isCloseout": { - "x-label": { - "default": "Closeout?" - }, - "type": "boolean" - }, - "c_isNew": { - "x-label": { - "default": "New Arrival?" - }, - "type": "boolean" - }, - "c_isNewtest": { - "description": "This indiciates if the product is a new arrival.", - "x-label": { - "default": "New Arrival?" - }, - "type": "boolean" - }, - "c_isSale": { - "description": "This is the help text. It is used for sorting rules.", - "x-label": { - "default": "On sale?" - }, - "type": "boolean" - }, - "c_kidsAge": { - "description": "Kids Age used for search refinements", - "enum": [ - "0010", - "0020", - "0030", - "0040", - "0050", - "0060", - "0070" - ], - "x-enum-labels": [ - { - "default": "Infant (0-1 yr)" - }, - { - "default": "Toddler (1-5 yrs)" - }, - { - "default": "Youth (5-9 yrs)" - }, - { - "default": "Junior (9-12 yrs)" - }, - { - "default": "Newborn (1-6 mths)" - }, - { - "default": "Infant (9 mnths - 3 yrs)" - }, - { - "default": "Junior (4-12 yrs)" - } - ], - "x-label": { - "default": "Age" - }, - "type": "string" - }, - "c_length": { - "description": "This attribute is used for mens and womens pants lengths.", - "x-label": { - "default": "Length" - }, - "type": "string" - }, - "c_lensAperture": { - "x-label": { - "default": "Lens Aperture" - }, - "type": "string" - }, - "c_materialTest": { - "items": { - "enum": [ - "cotton", - "polyester", - "wool" - ], - "x-enum-labels": [ - { - "default": "Cotton" - }, - { - "default": "Polyester" - }, - { - "default": "Wool" - } - ], - "type": "string" - }, - "x-label": { - "default": "Material" - }, - "type": "array" - }, - "c_mediaFormat": { - "description": "Media Format", - "items": { - "enum": [ - "0010", - "0020", - "0030", - "0040", - "0050", - "0060", - "0070", - "0080", - "0090", - "0100" - ], - "x-enum-labels": [ - { - "default": "MP3" - }, - { - "default": "WMA" - }, - { - "default": "JPEG" - }, - { - "default": "WAV" - }, - { - "default": "FM Tuner" - }, - { - "default": "AAC" - }, - { - "default": "WMP" - }, - { - "default": "MPEG4" - }, - { - "default": "AVC" - }, - { - "default": "ASP" - } - ], - "type": "string" - }, - "x-label": { - "default": "Media Format" - }, - "type": "array" - }, - "c_memorySize": { - "description": "Memory Size", - "x-label": { - "default": "Memory Size" - }, - "type": "string" - }, - "c_memoryType": { - "items": { - "enum": [ - "0010", - "0020", - "0030", - "0040", - "0050", - "0060", - "0070", - "0080", - "0090" - ], - "x-enum-labels": [ - { - "default": "SD" - }, - { - "default": "Memory Stick Pro DUO" - }, - { - "default": "Compact Flash" - }, - { - "default": "MMC" - }, - { - "default": "SDHC" - }, - { - "default": "CF Card" - }, - { - "default": "External Media" - }, - { - "default": "Micro SD" - }, - { - "default": "Garmin Data Card" - } - ], - "type": "string" - }, - "x-label": { - "default": "Memory Type" - }, - "type": "array" - }, - "c_musicStorage": { - "x-label": { - "default": "Music Storage" - }, - "type": "string" - }, - "c_opticalZoom": { - "x-label": { - "default": "Optical Zoom" - }, - "type": "string" - }, - "c_outerwearType": { - "description": "Type of Outerwear for search refinement", - "enum": [ - "0010", - "0020", - "0030", - "0040", - "0050", - "0060", - "0070", - "0080" - ], - "x-enum-labels": [ - { - "default": "Waterproof" - }, - { - "default": "Jackets" - }, - { - "default": "Vest" - }, - { - "default": "Pants" - }, - { - "default": "Casual" - }, - { - "default": "Sweater" - }, - { - "default": "Outdoor" - }, - { - "default": "Fleece" - } - ], - "x-label": { - "default": "Outerwear Type" - }, - "type": "string" - }, - "c_portableAudioType": { - "items": { - "enum": [ - "0010", - "0020", - "0030" - ], - "x-enum-labels": [ - { - "default": "Audio" - }, - { - "default": "Video" - }, - { - "default": "Internet" - } - ], - "type": "string" - }, - "x-label": { - "default": "Portable Audio Type" - }, - "type": "array" - }, - "c_refinementColor": { - "enum": [ - "beige", - "black", - "blue", - "brown", - "green", - "grey", - "miscellaneous", - "navy", - "orange", - "pink", - "purple", - "red", - "white", - "yellow" - ], - "x-enum-labels": [ - { - "default": "Beige" - }, - { - "default": "Black" - }, - { - "default": "Blue" - }, - { - "default": "Brown" - }, - { - "default": "Green" - }, - { - "default": "Grey" - }, - { - "default": "Miscellaneous" - }, - { - "default": "Navy" - }, - { - "default": "Orange" - }, - { - "default": "Pink" - }, - { - "default": "Purple" - }, - { - "default": "Red" - }, - { - "default": "White" - }, - { - "default": "Yellow" - } - ], - "x-label": { - "default": "Refinement Color" - }, - "type": "string" - }, - "c_resolution": { - "x-label": { - "default": "Resolution" - }, - "type": "string" - }, - "c_sandalType": { - "description": "Type of Sandal for search refinement", - "enum": [ - "0010", - "0020", - "0030" - ], - "x-enum-labels": [ - { - "default": "Sport" - }, - { - "default": "Casual" - }, - { - "default": "Thong" - } - ], - "x-label": { - "default": "Sandal Type" - }, - "type": "string" - }, - "c_sheets": { - "description": "test attribute", - "items": { - "enum": [ - "value1", - "value2", - "value3" - ], - "x-enum-labels": [ - { - "default": "Value 1" - }, - { - "default": "Value 2" - }, - { - "default": "Value 3" - } - ], - "type": "string" - }, - "x-label": { - "default": "Sheets" - }, - "type": "array" - }, - "c_shoeType": { - "description": "Type of Shoe for search refinements", - "enum": [ - "0010", - "0020", - "0030", - "0040", - "0050", - "0060", - "0070", - "0080", - "0090", - "0100", - "0110" - ], - "x-enum-labels": [ - { - "default": "Chukka" - }, - { - "default": "Lace Up" - }, - { - "default": "Slip On" - }, - { - "default": "Clog" - }, - { - "default": "Boat" - }, - { - "default": "Oxford" - }, - { - "default": "Loafer" - }, - { - "default": "Waterproof" - }, - { - "default": "Flats" - }, - { - "default": "Ballerina" - }, - { - "default": "Heeled" - } - ], - "x-label": { - "default": "Shoe Type" - }, - "type": "string" - }, - "c_size": { - "description": "This attribute is used for all footwear, apparel and accessory sizing for men, women and kids products.", - "x-label": { - "default": "Size" - }, - "type": "string" - }, - "c_skinConcern": { - "items": { - "enum": [ - "comprehensive", - "dryTight", - "liftingLossFirm" - ], - "x-enum-labels": [ - { - "default": "Comprehensive Protection" - }, - { - "default": "Dry, Tight Skin" - }, - { - "default": "Lifting & Loss of Firmness" - } - ], - "type": "string" - }, - "x-label": { - "default": "Skin Concern" - }, - "type": "array" - }, - "c_styleNumber": { - "x-label": { - "default": "Style Number" - }, - "type": "string" - }, - "c_tabDescription": { - "$ref": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/markup_text", - "x-label": { - "default": "Description" - } - }, - "c_tabDetails": { - "$ref": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/markup_text", - "x-label": { - "default": "Details" - } - }, - "c_topType": { - "description": "Type of tops in clothing", - "x-label": { - "default": "Top Type" - }, - "type": "string" - }, - "c_tvSignalFormat": { - "x-label": { - "default": "TV Signal Format" - }, - "type": "string" - }, - "c_tvSize": { - "x-label": { - "default": "Size" - }, - "type": "string" - }, - "c_tvType": { - "enum": [ - "0010", - "0020", - "0030", - "0040" - ], - "x-enum-labels": [ - { - "default": "Plasma" - }, - { - "default": "LCD" - }, - { - "default": "DLP" - }, - { - "default": "Projection" - } - ], - "x-label": { - "default": "TV Type" - }, - "type": "string" - }, - "c_tvWarranty": { - "x-label": { - "default": "Extended Warranty" - }, - "type": "string" - }, - "c_videoStorage": { - "x-label": { - "default": "Video Storage" - }, - "type": "string" - }, - "c_virtualTryOn": { - "description": "Whether the product is available for virtual try on.", - "x-label": { - "default": "Virtual Try On" - }, - "type": "boolean" - }, - "c_waist": { - "description": "Attribute used for apparel waist sizing.", - "x-label": { - "default": "Waist" - }, - "type": "string" - }, - "c_width": { - "description": "This attribute is used for shoe widths for mens, womens and kids.", - "x-label": { - "default": "Width" - }, - "type": "string" - }, - "classification_category": { - "$ref": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/catalog_category_id", - "description": "The classification category (default category) of the product" - }, - "creation_date": { - "description": "Returns the value of attribute 'creationDate'.", - "format": "date-time", - "x-label": { - "default": "Creation Date" - }, - "readOnly": true, - "type": "string" - }, - "default_variant_id": { - "description": "The ID of the product's default variant.", - "type": "string" - }, - "ean": { - "x-label": { - "default": "EAN" - }, - "type": "string" - }, - "id": { - "description": "The ID (SKU) of the product.", - "x-label": { - "default": "ID", - "en": "SKUAGE" - }, - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "image": { - "$ref": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/media_file", - "x-label": { - "default": "Standard Image" - } - }, - "image_groups": { - "description": "The array of product image groups.", - "items": { - "$ref": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/image_group" - }, - "type": "array" - }, - "in_stock": { - "description": "The flag that indicates if the product is in stock, or not. This is a calculated value.", - "type": "boolean" - }, - "last_modified": { - "description": "Returns the value of attribute 'lastModified'.", - "format": "date-time", - "x-label": { - "default": "Last Modified" - }, - "readOnly": true, - "type": "string" - }, - "link": { - "description": "A link to the product.", - "type": "string" - }, - "localized_tax_class_id": { - "additionalProperties": { - "type": "string" - }, - "format": "localized", - "x-label": { - "default": "Localized Tax Class" - }, - "type": "object" - }, - "long_description": { - "additionalProperties": { - "$ref": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/markup_text" - }, - "format": "localized", - "x-label": { - "default": "Product Details" - }, - "type": "object" - }, - "manufacturer_name": { - "description": "The products manufacturer name.", - "x-label": { - "default": "Manufacturer" - }, - "type": "string" - }, - "manufacturer_sku": { - "x-label": { - "default": "Manufacturer Product ID" - }, - "type": "string" - }, - "master": { - "$ref": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/master", - "description": "The master product information. Only for types master, variation group and variant." - }, - "name": { - "additionalProperties": { - "type": "string" - }, - "description": "The localized product name.", - "format": "localized", - "x-label": { - "default": "Name" - }, - "type": "object" - }, - "online": { - "description": "The flag that indicates if the product is online, or not. This is a calculated value.", - "type": "boolean" - }, - "online_flag": { - "additionalProperties": { - "type": "boolean" - }, - "description": "The site specific online status of the product.", - "format": "site-specific", - "x-label": { - "default": "Online" - }, - "type": "object" - }, - "owning_catalog_id": { - "description": "The ID of the catalog that owns the product.", - "type": "string" - }, - "owning_catalog_name": { - "additionalProperties": { - "type": "string" - }, - "description": "The localized name of the catalog that owns the product.", - "format": "localized", - "type": "object" - }, - "page_description": { - "additionalProperties": { - "type": "string" - }, - "description": "The localized products page description.", - "format": "localized", - "x-label": { - "default": "Page Description" - }, - "type": "object" - }, - "page_keywords": { - "additionalProperties": { - "type": "string" - }, - "description": "The localized products page description.", - "format": "localized", - "x-label": { - "default": "Page Keywords" - }, - "type": "object" - }, - "page_title": { - "additionalProperties": { - "type": "string" - }, - "description": "The localized products page title.", - "format": "localized", - "x-label": { - "default": "Page Title" - }, - "type": "object" - }, - "price": { - "description": "The price of the product.", - "format": "double", - "type": "number" - }, - "price_currency": { - "description": "The currency code for product's price.", - "type": "string" - }, - "price_per_unit": { - "description": "The price per unit of the product", - "format": "double", - "type": "number" - }, - "primary_categories": { - "description": "The catalog categories that are primary for the product", - "items": { - "$ref": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/catalog_category_id" - }, - "type": "array" - }, - "primary_category_id": { - "description": "The id of the products primary category.", - "type": "string" - }, - "product_bundles": { - "description": "The array of product bundles which the product belongs to.", - "items": { - "$ref": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/product" - }, - "type": "array" - }, - "product_options": { - "description": "The array of product options. This is applicable for products of type \"option\".", - "items": { - "$ref": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/product_option" - }, - "type": "array" - }, - "product_sets": { - "description": "The array of product sets which the product belongs to.", - "items": { - "$ref": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/product" - }, - "type": "array" - }, - "searchable": { - "additionalProperties": { - "type": "boolean" - }, - "description": "The site specific searchable status of the product.", - "format": "site-specific", - "x-label": { - "default": "Searchable" - }, - "type": "object" - }, - "set_products": { - "description": "The array of set products which the product includes.", - "items": { - "$ref": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/product" - }, - "type": "array" - }, - "short_description": { - "additionalProperties": { - "$ref": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/markup_text" - }, - "format": "localized", - "x-label": { - "default": "Description" - }, - "type": "object" - }, - "tax_class_id": { - "x-label": { - "default": "Tax Class" - }, - "type": "string" - }, - "type": { - "$ref": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/product_type", - "description": "The product type information. Can be one or multiple of the following values: item,master,variation_group,variant,bundle,set." - }, - "unit": { - "description": "The sales unit of the product.", - "x-label": { - "default": "Sales Unit" - }, - "type": "string" - }, - "unit_measure": { - "description": "The unitMeasure of the product.", - "x-label": { - "default": "Unit Measure" - }, - "type": "string" - }, - "unit_quantity": { - "description": "The unitQuantity of the product.", - "format": "double", - "x-label": { - "default": "Unit Quantity" - }, - "type": "number" - }, - "upc": { - "x-label": { - "default": "UPC" - }, - "type": "string" - }, - "valid_from": { - "additionalProperties": { - "format": "date-time", - "type": "string" - }, - "description": "The time when product is valid from.", - "format": "site-specific", - "x-label": { - "default": "Online From" - }, - "type": "object" - }, - "valid_to": { - "additionalProperties": { - "format": "date-time", - "type": "string" - }, - "description": "The time when product is valid to.", - "format": "site-specific", - "x-label": { - "default": "Online To" - }, - "type": "object" - }, - "variants": { - "description": "The array of actual variants. This array can be empty. Only for types master, variation group and variant.", - "items": { - "$ref": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/variant" - }, - "type": "array" - }, - "variation_attributes": { - "description": "Sorted array of variation attributes information. This array can be empty. Only for types master,\n variation group and variant.", - "items": { - "$ref": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/variation_attribute" - }, - "type": "array" - }, - "variation_groups": { - "description": "The array of actual variation groups. This array can be empty. Only for types master, variation group and variant.", - "items": { - "$ref": "https://zzrf-001.dx.commercecloud.salesforce.com/s/-/dw/meta/rest/data/v23_1/documents/variation_group" - }, - "type": "array" - }, - "variation_values": { - "additionalProperties": { - "type": "string" - }, - "description": "The actual variation attribute id - value pairs. Only for type variant and\n variation group.", - "type": "object" - } - } -} \ No newline at end of file diff --git a/packages/pwa-storefront-mcp/src/server/server.js b/packages/pwa-storefront-mcp/src/server/server.js deleted file mode 100644 index 056ee311fe..0000000000 --- a/packages/pwa-storefront-mcp/src/server/server.js +++ /dev/null @@ -1,243 +0,0 @@ -#!/usr/bin/env node -/* - * Copyright (c) 2025, Salesforce, Inc. - * All rights reserved. - * SPDX-License-Identifier: BSD-3-Clause - * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ -import {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js' -import {StdioServerTransport} from '@modelcontextprotocol/sdk/server/stdio.js' - -import {z} from 'zod' -import {CreateAppGuidelinesTool, CreateNewComponentTool, DeveloperGuidelinesTool} from '../utils' -import {TestWithPlaywrightTool} from '../utils/run-site-test-tool' - -// NOTE: This is a workaround to import JSON files as ES modules. -// eslint-disable-next-line @typescript-eslint/no-var-requires -const productDocument = require('../data/ProductDocument.json') -// eslint-disable-next-line @typescript-eslint/no-var-requires -const categoryDocument = require('../data/CategoryDocument.json') -// eslint-disable-next-line @typescript-eslint/no-var-requires -const documentList = require('../data/DocumentList.json') -// eslint-disable-next-line @typescript-eslint/no-var-requires -const packageJson = require('../../package.json') - -const FALLBACK_VERSION = '0.1.0' - -class PwaStorefrontMCPServerHighLevel { - constructor() { - // Using McpServer instead of Server - this.server = new McpServer( - { - name: 'pwa-storefront-mcp-server', - version: packageJson?.version || FALLBACK_VERSION - }, - { - capabilities: { - tools: {} - } - } - ) - this.CreateNewComponentTool = new CreateNewComponentTool() - this.testWithPlaywrightTool = new TestWithPlaywrightTool() - this.setupTools() - - // 1. Add in-memory session management - this.sessions = {} - this.sessionCounter = 1 - } - - setupTools() { - // Register CreateProjectTool - this.server.tool( - CreateAppGuidelinesTool.name, - CreateAppGuidelinesTool.description, - CreateAppGuidelinesTool.inputSchema, - CreateAppGuidelinesTool.fn - ) - - // Register DeveloperGuidelinesTool - this.server.tool( - DeveloperGuidelinesTool.name, - DeveloperGuidelinesTool.description, - DeveloperGuidelinesTool.inputSchema, - DeveloperGuidelinesTool.fn - ) - - this.server.tool( - 'run_site_test', - 'Run site performance or accessibility test for a given site URL (e.g. https://pwa-kit.mobify-storefront.com)', - { - testType: z.enum(['performance', 'accessibility']).describe('Type of test to run'), - siteUrl: z.string().optional().describe('Site URL to test (optional)') - }, - ({testType, siteUrl}) => this.testWithPlaywrightTool.run(testType, siteUrl) - ) - - this.server.tool( - 'create_new_sample_component', - 'Conversationally collect parameters and create a new sample React component.', - { - sessionId: z.string().optional().describe('Session ID for the conversational flow'), - answer: z.string().optional().describe('User answer to the current question') - }, - (args) => this.handleCreateNewSampleComponent(args) - ) - } - - /** - * Helper to handle the conversational flow for create_new_sample_component - */ - async handleCreateNewSampleComponent(args) { - let sessionId = args.sessionId - if (!sessionId) { - sessionId = `session-interactive-${this.sessionCounter++}` - this.sessions[sessionId] = {step: 1, answers: {}} - } - const session = this.sessions[sessionId] - const {step, answers} = session - const answer = args.answer?.trim() - switch (step) { - case 1: - return this._handleComponentNameStep(session, answer, sessionId) - case 2: - return this._handleDirectoryStep(session, answer, sessionId) - case 3: - return this._handleSingleOrListStep(session, answer, sessionId) - default: - return this._handleDoneStep(sessionId) - } - } - - _next(sessionId, question) { - return { - content: [{type: 'text', text: JSON.stringify({sessionId, question})}] - } - } - - _done(sessionId, message) { - return { - content: [{type: 'text', text: JSON.stringify({sessionId, message})}] - } - } - - _handleComponentNameStep(session, answer, sessionId) { - if (answer) { - session.answers.name = answer - session.step = 2 - const defaultDir = process.env.PWA_STOREFRONT_APP_PATH - ? process.env.PWA_STOREFRONT_APP_PATH + '/components' - : '/components' - return this._next( - sessionId, - `Answer yes to use the default components directory (${defaultDir}), or no if you want to specify the full absolute path to use a different directory:` - ) - } - return this._next(sessionId, 'What would you like to name your new React component?') - } - - _handleDirectoryStep(session, answer, sessionId) { - const defaultDir = process.env.PWA_STOREFRONT_APP_PATH - ? process.env.PWA_STOREFRONT_APP_PATH + '/components' - : '/components' - if (answer) { - if (/^(yes|y|true|1)$/i.test(answer)) { - session.answers.location = defaultDir - } else { - session.answers.location = answer - } - session.step = 3 - return this._next( - sessionId, - 'Should this component display a single product or a list of products? Reply with "single" or "list".' - ) - } - return this._next( - sessionId, - `Answer yes to use the default components directory (${defaultDir}), or no if you want to specify the full absolute path to use a different directory:` - ) - } - - async _handleSingleOrListStep(session, answer, sessionId) { - if (answer && /list/i.test(answer)) { - // List of products - const tool = new CreateNewComponentTool() - tool.componentData = { - name: session.answers.name, - location: session.answers.location, - createTestFile: false, - customCode: '', - entityType: 'product' - } - const dataModel = this.getDataModel('product') - let schemaObj = dataModel && dataModel.properties ? dataModel.properties : {} - let presentationalResult = await tool.updateComponentToPresentational( - 'product', - session.answers.name, - session.answers.location, - schemaObj, - {list: true} - ) - session.step = 99 - return this._done( - sessionId, - (session.basicComponentResult || '') + - `\n\n${presentationalResult}\nComponent creation flow complete.` - ) - } else if (answer && /single/i.test(answer)) { - // Single product - const tool = new CreateNewComponentTool() - tool.componentData = { - name: session.answers.name, - location: session.answers.location, - createTestFile: false, - customCode: '', - entityType: 'product' - } - const dataModel = this.getDataModel('product') - let schemaObj = dataModel && dataModel.properties ? dataModel.properties : {} - let presentationalResult = await tool.updateComponentToPresentational( - 'product', - session.answers.name, - session.answers.location, - schemaObj, - {list: false} - ) - session.step = 99 - return this._done( - sessionId, - (session.basicComponentResult || '') + - `\n\n${presentationalResult}\nComponent creation flow complete.` - ) - } else { - return this._next(sessionId, 'Please reply with "single" or "list".') - } - } - - _handleDoneStep(sessionId) { - return this._done(sessionId, 'Component creation flow complete.') - } - - /** - * Simple method to get data models directly from imports - * @param {string} modelName - Name of the model (e.g., 'product', 'category') - * @returns {object|null} The data model object or null if not found - */ - getDataModel(modelName) { - const models = { - product: productDocument, - category: categoryDocument, - documentList: documentList - } - return models[modelName.toLowerCase()] || null - } - - async run() { - const transport = new StdioServerTransport() - await this.server.connect(transport) - console.error('PWA Storefront MCP server (McpServer version) running on stdio') - } -} - -const server = new PwaStorefrontMCPServerHighLevel() -server.run().catch(console.error) diff --git a/packages/pwa-storefront-mcp/src/server/server.test.js b/packages/pwa-storefront-mcp/src/server/server.test.js deleted file mode 100644 index 92d9ba34f7..0000000000 --- a/packages/pwa-storefront-mcp/src/server/server.test.js +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2025, Salesforce, Inc. - * All rights reserved. - * SPDX-License-Identifier: BSD-3-Clause - * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ -import {spawn} from 'cross-spawn' -import path from 'path' - -const BABEL_NODE_PATH = path.resolve( - './node_modules/.bin/babel-node' + (process.platform === 'win32' ? '.cmd' : '') -) - -function sendJsonRpcRequest(child, request) { - return new Promise((resolve, reject) => { - let data = '' - const onData = (chunk) => { - data += chunk.toString() - // MCP server sends each message as a line-delimited JSON - if (data.includes('\n')) { - child.stdout.off('data', onData) - try { - // Only parse the first line (response) - const line = data.split('\n').find((l) => l.trim().length > 0) - resolve(JSON.parse(line)) - } catch (e) { - reject(e) - } - } - } - child.stdout.on('data', onData) - child.stdin.write(JSON.stringify(request) + '\n') - }) -} - -describe('PwaStorefrontMCPServerHighLevel integration', () => { - it('should list registered tools via stdio', async () => { - const child = spawn(BABEL_NODE_PATH, ['src/server/server.js'], { - cwd: process.cwd(), - stdio: ['pipe', 'pipe', 'inherit'] - }) - - // Wait a moment for the server to start - await new Promise((r) => setTimeout(r, 500)) - - // Send the list tools request (JSON-RPC 2.0) - const request = { - jsonrpc: '2.0', - id: 1, - method: 'tools/list', - params: {} - } - const response = await sendJsonRpcRequest(child, request) - expect(response).toHaveProperty('result') - expect(response.result).toHaveProperty('tools') - // Check that at least the DeveloperGuidelinesTool is present - const toolNames = response.result.tools.map((t) => t.name) - expect(toolNames).toContain('development_guidelines') - - child.kill() - }, 10000) -}) diff --git a/packages/pwa-storefront-mcp/src/utils/create-new-component-tool.js b/packages/pwa-storefront-mcp/src/utils/create-new-component-tool.js deleted file mode 100644 index e7d1b8d5aa..0000000000 --- a/packages/pwa-storefront-mcp/src/utils/create-new-component-tool.js +++ /dev/null @@ -1,342 +0,0 @@ -/* - * Copyright (c) 2025, Salesforce, Inc. - * All rights reserved. - * SPDX-License-Identifier: BSD-3-Clause - * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ -import fs from 'fs/promises' -import path from 'path' -import {toKebabCase, toPascalCase} from './utils' - -export const getCopyrightHeader = () => { - const year = new Date().getFullYear() - return `/* - * Copyright (c) ${year}, Salesforce, Inc. - * All rights reserved. - * SPDX-License-Identifier: BSD-3-Clause - * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */` -} - -// Utility to infer entity from component name -function inferEntityFromComponentName(componentName) { - const name = componentName.toLowerCase() - if (name.includes('customer')) return 'customer' - if (name.includes('product')) return 'product' - if (name.includes('basket')) return 'basket' - if (name.includes('category')) return 'category' - return null -} - -class CreateNewComponentTool { - constructor() { - this.currentStep = 0 - this.componentData = { - name: null, - location: null, - entityType: null - } - } - - /** - * Creates the component based on all collected data - * @returns {Promise} The result of component creation - */ - async createComponent() { - const messages = [] - - // Use the provided absolute path directly if available - const location = this.componentData.location - const componentMessage = await this.createComponentFile(this.componentData.name, location) - messages.push(componentMessage) - - // Handle entity type information - if (this.componentData.entityType) { - messages.push( - `\nℹ️ Entity type '${this.componentData.entityType}' ${ - inferEntityFromComponentName(this.componentData.name) - ? 'was inferred' - : 'was specified' - } for component '${this.componentData.name}'.` - ) - } else { - messages.push( - `\nℹ️ No entity type was specified or could be inferred for component '${this.componentData.name}'.` - ) - } - - // Always append lint reminder - messages.push( - "\n💡 After creating or modifying a component, run 'npm run lint -- --fix' to automatically fix formatting and linter issues." - ) - - // Reset for next use - this.reset() - - return messages.join('\n') - } - - /** - * Resets the tool state for the next component creation - */ - reset() { - this.currentStep = 0 - this.componentData = { - name: null, - location: null, - entityType: null - } - } - - /** - * Creates a new React component file. - * @param {string} componentName - Name for the new component. - * @param {string} projectDir - The absolute path to the project directory for the new component. - */ - async createComponentFile(componentName, projectDir) { - const kebabDirName = toKebabCase(componentName) - const pascalComponentName = toPascalCase(componentName) - const componentDir = path.join(projectDir, kebabDirName) - try { - await fs.mkdir(componentDir, {recursive: true}) - // Create component file - const componentFilePath = path.join(componentDir, 'index.jsx') - const codeToWrite = `${getCopyrightHeader()} -import React from 'react'; - -const ${pascalComponentName} = () => { - return ( -
${pascalComponentName} component
- ); -}; - -export default ${pascalComponentName}; -` - await fs.writeFile(componentFilePath, codeToWrite, 'utf-8') - return `✅ Created ${componentFilePath}` - } catch (err) { - console.error('Error during file creation:', err) - return `❌ Error creating component file at ${componentDir}: ${err.message}` - } - } - - /** - * Updates the component file to be a presentational component for the given data model. - * @param {string} entityType - The entity type (e.g., 'product'). - * @param {string} componentName - The component name. - * @param {string} location - The absolute path to the component's parent directory. - * @param {object} dataModel - The data model schema (properties object). - */ - async updateComponentToPresentational( - entityType, - componentName, - location, - dataModel, - options = {} - ) { - const kebabDirName = toKebabCase(componentName) - const pascalComponentName = toPascalCase(componentName) - const componentDir = path.join(location, kebabDirName) - await fs.mkdir(componentDir, {recursive: true}) - const componentFilePath = path.join(componentDir, 'index.jsx') - const fields = Object.keys(dataModel) - let code = '' - - // Special logic for product entity - if (entityType === 'product') { - // If options.list is true, generate a list-of-products component - if (options.list) { - code = `${getCopyrightHeader()} -import React from 'react'; -import PropTypes from 'prop-types'; -import { Box, Text, Image, Stack } from '@chakra-ui/react'; - -const ${pascalComponentName} = ({ products }) => ( - - {products.map(product => ( - - {product.name} - {product.imageGroups && product.imageGroups[0]?.images[0]?.link && ( - {product.name} - )} - assigned_categories: {product.assigned_categories?.toString?.() ?? ''} - price: {product.price?.toString?.() ?? ''} - - ))} - -); - -${pascalComponentName}.propTypes = { - products: PropTypes.arrayOf(PropTypes.shape({ - productId: PropTypes.string, - name: PropTypes.string, - assigned_categories: PropTypes.any, - price: PropTypes.any, - imageGroups: PropTypes.array - })).isRequired -}; - -export default ${pascalComponentName}; -` - } else { - // Single product component (with selectors, image, etc.) - code = `${getCopyrightHeader()} -import React, { useState } from 'react'; -import PropTypes from 'prop-types'; -import { Box, Text, Image, Button, HStack, Stack } from '@chakra-ui/react'; - -// Helper to filter variants by selected attribute values -const filterVariants = (variants, selected) => { - return variants.filter(variant => - Object.entries(selected).every( - ([attr, value]) => !value || variant.variationValues?.[attr] === value - ) - ); -}; - -// Helper to get the image for the selected color -const getImageForSelection = (imageGroups, selected) => { - if (selected.color) { - const group = imageGroups.find( - g => - g.variationAttributes && - g.variationAttributes.some( - va => - va.id === 'color' && - va.values.some(v => v.value === selected.color) - ) - ); - if (group && group.images.length > 0) { - return group.images[0].link; - } - } - if (imageGroups.length > 0 && imageGroups[0].images.length > 0) { - return imageGroups[0].images[0].link; - } - return null; -}; - -const ${pascalComponentName} = ({ product }) => { - const { variationAttributes = [], variants = [], imageGroups = [] } = product; - const [selected, setSelected] = useState(() => { - const initial = {}; - variationAttributes.forEach(attr => { - initial[attr.id] = ''; - }); - return initial; - }); - - // Build a color code to swatch image URL map - const swatchMap = {}; - imageGroups - .filter(group => group.viewType === 'swatch') - .forEach(group => { - const colorCode = group.variationAttributes?.[0]?.values?.[0]?.value; - if (colorCode && group.images[0]?.link) { - swatchMap[colorCode] = group.images[0].link; - } - }); - - const filteredVariants = filterVariants(variants, selected); - const getAvailableValues = (attrId) => { - const otherSelected = { ...selected }; - delete otherSelected[attrId]; - const possibleVariants = filterVariants(variants, otherSelected); - const values = new Set(); - possibleVariants.forEach(v => { - if (v.variationValues?.[attrId]) values.add(v.variationValues[attrId]); - }); - return Array.from(values); - }; - - const imageUrl = getImageForSelection(imageGroups, selected); - - return ( - - {product.name} - {imageUrl && ( - {product.name} - )} - assigned_categories: {product.assigned_categories?.toString?.() ?? ''} - price: {product.price?.toString?.() ?? ''} - {/* Dynamic variant attribute selectors */} - {variationAttributes.map(attr => ( - - {attr.name}: - - {getAvailableValues(attr.id).map(val => - attr.id === 'color' ? ( - - ) : ( - - ) - )} - - - ))} - - ); -}; - -${pascalComponentName}.propTypes = { - product: PropTypes.shape({ - name: PropTypes.string, - assigned_categories: PropTypes.any, - price: PropTypes.any, - variationAttributes: PropTypes.array, - variants: PropTypes.array, - imageGroups: PropTypes.array - }).isRequired -}; - -export default ${pascalComponentName}; -` - } - } else { - throw new Error(`Entity type '${entityType}' is not supported.`) - } - - await fs.writeFile(componentFilePath, code, 'utf-8') - return `✅ Updated ${componentFilePath} to presentational component for ${entityType}` - } -} - -export default CreateNewComponentTool diff --git a/packages/pwa-storefront-mcp/src/utils/create-new-component-tool.test.js b/packages/pwa-storefront-mcp/src/utils/create-new-component-tool.test.js deleted file mode 100644 index 437564d87d..0000000000 --- a/packages/pwa-storefront-mcp/src/utils/create-new-component-tool.test.js +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (c) 2025, Salesforce, Inc. - * All rights reserved. - * SPDX-License-Identifier: BSD-3-Clause - * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ -import CreateNewComponentTool from './create-new-component-tool.js' -import * as fs from 'fs/promises' -import path from 'path' - -// Mock fs/promises to avoid actual file operations -jest.mock('fs/promises', () => ({ - mkdir: jest.fn().mockResolvedValue(undefined), - writeFile: jest.fn().mockResolvedValue(undefined), - access: jest.fn().mockResolvedValue(undefined) -})) - -describe('CreateNewComponentTool', () => { - beforeEach(() => { - jest.clearAllMocks() - }) - - it('should instantiate and set componentData', () => { - const tool = new CreateNewComponentTool() - tool.componentData = { - name: 'TestComponent', - location: '/tmp', - createTestFile: false, - customCode: '', - entityType: 'product' - } - expect(tool.componentData.name).toBe('TestComponent') - }) - - it('should call createComponent without error', async () => { - const tool = new CreateNewComponentTool() - tool.componentData = { - name: 'TestComponent', - location: '/tmp', - createTestFile: false, - customCode: '', - entityType: 'product' - } - await expect(tool.createComponent()).resolves.toBeDefined() - }) - - it('should not throw if name is missing', async () => { - const tool = new CreateNewComponentTool() - tool.componentData = { - name: '', - location: '/tmp', - createTestFile: false, - customCode: '', - entityType: 'product' - } - await expect(tool.createComponent()).resolves.toMatch(/Created/) - }) - - it('should not throw if location is invalid', async () => { - const tool = new CreateNewComponentTool() - tool.componentData = { - name: 'TestComponent', - location: '', - createTestFile: false, - customCode: '', - entityType: 'product' - } - await expect(tool.createComponent()).resolves.toMatch(/Created/) - }) - - it('should handle fs/promises errors gracefully', async () => { - fs.writeFile.mockRejectedValueOnce(new Error('FS error')) - const tool = new CreateNewComponentTool() - tool.componentData = { - name: 'TestComponent', - location: '/tmp', - createTestFile: false, - customCode: '', - entityType: 'product' - } - await expect(tool.createComponent()).resolves.toMatch(/FS error/) - }) - - it('should update component to presentational (single product)', async () => { - const tool = new CreateNewComponentTool() - const dataModel = { - name: {type: 'string'}, - price: {type: 'number'}, - imageGroups: {type: 'array'} - } - await expect( - tool.updateComponentToPresentational('product', 'ProductDisplay', '/tmp', dataModel, { - list: false - }) - ).resolves.toMatch(/Updated .* to presentational component for product/) - expect(fs.writeFile).toHaveBeenCalledWith( - expect.any(String), - expect.stringContaining('ProductDisplay'), - expect.anything() - ) - }) - - it('should update component to presentational (list of products)', async () => { - const tool = new CreateNewComponentTool() - const dataModel = { - name: {type: 'string'}, - price: {type: 'number'}, - imageGroups: {type: 'array'} - } - await expect( - tool.updateComponentToPresentational('product', 'ProductList', '/tmp', dataModel, { - list: true - }) - ).resolves.toMatch(/Updated .* to presentational component for product/) - expect(fs.writeFile).toHaveBeenCalledWith( - expect.any(String), - expect.stringContaining('ProductList'), - expect.anything() - ) - }) - - it('should reset currentStep and componentData to initial values', () => { - const tool = new CreateNewComponentTool() - tool.currentStep = 5 - tool.componentData = { - name: 'Something', - location: '/some/path', - entityType: 'product' - } - tool.reset() - expect(tool.currentStep).toBe(0) - expect(tool.componentData).toEqual({ - name: null, - location: null, - entityType: null - }) - }) -}) diff --git a/packages/pwa-storefront-mcp/src/utils/index.js b/packages/pwa-storefront-mcp/src/utils/index.js deleted file mode 100644 index 7c31efce99..0000000000 --- a/packages/pwa-storefront-mcp/src/utils/index.js +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright (c) 2025, Salesforce, Inc. - * All rights reserved. - * SPDX-License-Identifier: BSD-3-Clause - * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ -import CreateAppGuidelinesTool from './pwa-create-app-guideline-tool' -import CreateNewComponentTool from './create-new-component-tool' -import DeveloperGuidelinesTool from './pwa-developer-guideline-tool' - -export {CreateAppGuidelinesTool, CreateNewComponentTool, DeveloperGuidelinesTool} diff --git a/packages/pwa-storefront-mcp/src/utils/pwa-create-app-guideline-tool.js b/packages/pwa-storefront-mcp/src/utils/pwa-create-app-guideline-tool.js deleted file mode 100644 index eeb44849ee..0000000000 --- a/packages/pwa-storefront-mcp/src/utils/pwa-create-app-guideline-tool.js +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (c) 2025, Salesforce, Inc. - * All rights reserved. - * SPDX-License-Identifier: BSD-3-Clause - * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ -import os from 'os' -import path from 'path' -import {exec} from 'child_process' -import fs from 'fs/promises' - -// Project dependencies -import {EmptyJsonSchema, runNpxCommand} from './utils' - -//const CREATE_APP_VERSION = 'latest' -//const CREATE_APP_VERSION = '3.11.0-nightly-20250630080227' -const CREATE_APP_COMMAND = '@salesforce/pwa-kit-create-app@3.11.0-nightly-20250630080227' -const DISPLAY_PROGRAM_COMMAND = '--displayProgram' -const NPX_COMMAND = 'npx' - -const guidelinesText = ` -# PWA Kit Create App — Agent Usage Guidelines - -## Overview - -This document defines the behavior agents must follow when using the \`@salesforce/pwa-kit-create-app\` CLI tool to generate new PWA Kit projects. The CLI supports both **presets** and **templates** for project creation, and agents must clearly distinguish between these two modes of operation. - ---- - -## General Rules - -- Always use this tool to initiate project creation. Never attempt to manually create a project outside of this process. -- Ask one question at a time when gathering information from the user. -- Do not mix presets and templates. Only show or ask about one based on the user's intent. -- Never proceed with project generation unless all required information has been collected. - ---- - -## Creating a Project Using a Preset - -If the user requests a project using a **preset**: - -- List only the available presets. -- If a preset is provided, use the \`--preset\` flag with the CLI. -- Do not ask for or display any template options. - ---- - -## Creating a Project Using a Template - -If the user requests a project using a **template**: - -- List only the available templates. -- If a template is provided: - - Use its associated questions to prompt the user, one at a time. - - Do not proceed with project generation until all required answers have been collected. -- Do not ask for or display any preset options. - ---- - -## Important Reminders - -- Never attempt to create a project without using this tool. -- When gathering answers for a template, ask questions one at a time to maintain clarity. -- Presets and templates are mutually exclusive paths. Do not offer both options unless explicitly requested. -- Use the \`${NPX_COMMAND}\` command to run the \`${CREATE_APP_COMMAND}\` CLI tool when creating a new project. -` - -export default { - name: 'create_app_guidelines', - description: `This tool is used to provide the agent with the instructions on how to use the @salesforce/pwa-kit-create-app CLI tool to create a new PWA Kit projects. Do not attempt to create a project without using this tool first.`, - inputSchema: EmptyJsonSchema, - fn: async () => { - let programOutput = '' - - // Run the display program and get the output. - try { - programOutput = await runNpxCommand( - NPX_COMMAND, - CREATE_APP_COMMAND, - DISPLAY_PROGRAM_COMMAND - ) - } catch (err) { - console.error('Failed to run display program:', err) - } - - // Parse the output and get the data, metadata, and schemas. - const { - data, - metadata: {description: cli}, - schemas - } = JSON.parse(programOutput) - - return { - content: [ - { - type: 'text', - text: JSON.stringify( - { - guidelines: guidelinesText, - cli, - schemas, - data - }, - null, - 2 - ) - } - ] - } - } -} diff --git a/packages/pwa-storefront-mcp/src/utils/pwa-developer-guideline-tool.js b/packages/pwa-storefront-mcp/src/utils/pwa-developer-guideline-tool.js deleted file mode 100644 index 2652bfa594..0000000000 --- a/packages/pwa-storefront-mcp/src/utils/pwa-developer-guideline-tool.js +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (c) 2025, Salesforce, Inc. - * All rights reserved. - * SPDX-License-Identifier: BSD-3-Clause - * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ -import {EmptyJsonSchema} from './utils' - -const guidelinesText = `# Salesforce Commerce Composable Storefront Development Guidelines - -## Overview -This document offers guidelines in the development of Salesforce Commerce Composable Storefront applications using PWA Kit. The AI should possess a comprehensive understanding of the PWA Kit architecture, sample Retail React App, Chakra UI, and standard React application practices. - -## Core Principles - -### Project Understanding -- Thoroughly analyze requests and the existing project for successful implementation. -- Promptly clarify ambiguous requirements. - -### Development Workflow -- **Analyze Requirements** - Clearly define the objectives and functionalities required. -- **Review Existing Code** - Examine the current codebase to identify similar solutions and potentially reusable components. -- **Understand Existing Hooks and Utilities** - Familiarize with hooks and utility functions available within the project, including those from commerce-sdk-react and template-retail-react-app modules. -- **Plan Implementation** - Design component structure before coding. -- **Implement Incrementally** - Develop and test the service in small, manageable steps. -- **Test Thoroughly** - Ensure comprehensive testing, including the use of Jest. - -## Technical Stack - -### Core Technologies -- **React** - UI components and SPA architecture -- **Express** - Server-side rendering and backend -- **@salesforce/commerce-sdk-react** - Commerce Cloud API integration (hooks) -- **PWA Kit** - SSR, routing, config, Salesforce integration -- **Chakra UI V2** - UI components and theming -- **Emotion** - CSS-in-JS styling -- **React Router** - Routing -- **React Intl** - Localization -- **React Query** - Data fetching/caching -- **Webpack** - Bundling -- **React Testing Library, Jest** - Testing libraries -- **react-helmet, framer-motion, etc.** - Utilities, animation, head management -- **ESLint/Prettier** - Code formatting and linting - -## PWK Kit Architecture - -### Configuration Files -- PWA Kit apps are customized using configuration files for API access, URL formatting, and server-side rendering. -- These files support JavaScript, YAML, and JSON formats, with default.js being the standard. -- Configuration values are serialized for isomorphic rendering, so secrets must not be included. -- Environment-specific configuration files can replace or complement default.js. -- File precedence is .js > .yml > .yaml > .json if base names are the same. - -### Proxy Requests -- Managed Runtime's proxy feature routes API requests through the storefront domain to avoid CORS issues and improve performance. -- Local proxy configurations are set in config/default.js, while Managed Runtime deployments use Runtime Admin or the Managed Runtime API. -- Requests use the /mobify/proxy/ pattern. -- Proxied requests and responses are modified for transparent operation. -- Proxied requests are uncached by default but can be cached using a caching path prefix. - -### Rendering -- PWA Kit uses server-side rendering (SSR) for fast initial page loads, leveraging CDN caching. -- After the first load, client-side rendering (CSR) takes over for fluid user interactions. -- Application code must be isomorphic, functioning in both server and client environments, often with conditional logic. -- Props from API requests are serialized into the page source during SSR for client-side hydration. -- A correlation ID is provided on each page for tracking requests across PWA Kit and other systems. - -### Routing -- PWA Kit uses Express.js and React Router for handling requests and rendering components. -- Routes are defined in app/routes.jsx as an array of objects with 'path' and 'component' properties. -- You can use both withReactQuery and withLegacyGetProps at the same time. -- getProps and shouldGetProps were removed from the default template of pages of the Retail React App, but aren't deprecated. Long-term support for these methods remains. - -### PWA Kit Special Components -- Customize your storefront by overriding default special components that start with an underscore (_), such as app/components/_app-config/index.jsx. -- app/components/_app-config: The top-level component for app-wide configurations like theme providers and state management. -- app/components/_app: The child of _app-config. Use it for layout and UI that persist throughout your React app, such as the header, footer, and sidebar. -- app/components/_error: Renders when a page or its data isn't found, or when an error occurs, returning a 404 status. - -### State Management -- PWA Kit applications support various state management approaches, including simple prop-passing or React's Context API. -- The React Context API can be used with useReducer and useContext for shared global state. -- The AppConfig special component is the primary place to initialize a state management system. -- When integrating libraries like Redux, AppConfig methods such as restore, freeze, extraGetPropsArgs, and render are utilized. - -### PWA Kit Extensibility -- In PWA Kit v3, you can extend a base template (@salesforce/retail-react-app) by replacing specific files using a local "overrides directory." -- Extensibility is configured in package.json with the base template (ccExtensibility.extends) and your overrides directory (ccExtensibility.overridesDir). -- To override a file, recreate its exact path and filename in your overrides directory. - -### PWA Kit Storefront Development -- Start development with Retail React App sample codebase and tooling. -- Use included npm scripts for automating development tasks like build, test, and lint. -- Access Shopper data through the commerce-sdk-react hooks to fetch, cache, and mutate utilizing Salesforce Commerce API (SLAS) and OCAPI. -- Use Chakra UI and existing components when available. -- Create simple, functional, modular, reusable components. -- Use the React Helmet library to modify the HTML tags in Document, such as . -- Use kebab-case for file names. Only start with an underscore (_) if they are special components. -- Use React Hooks (e.g., useState, useEffect, useContext, useMemo, useCallback) for state management and side effects. - -## Quality Standards -- Maintain consistent code formatting using project standards. -- Write comprehensive test coverage. -- Ensure components are accessible and mobile-friendly. -- Follow security best practices for all code. -` - -export default { - name: 'development_guidelines', - description: `You must follow this development guidelines before attempting to analyze/ generate / refactor / modify / fix code. - - e.g. "Create a customer service Chat component", "Find bugs in my_script.jsx", "Refactor my_script.jsx to use React Hooks"`, - inputSchema: EmptyJsonSchema, - fn: async () => ({ - content: [{type: 'text', text: guidelinesText}] - }) -} diff --git a/packages/pwa-storefront-mcp/src/utils/pwa-developer-guideline-tool.test.js b/packages/pwa-storefront-mcp/src/utils/pwa-developer-guideline-tool.test.js deleted file mode 100644 index 3945affad1..0000000000 --- a/packages/pwa-storefront-mcp/src/utils/pwa-developer-guideline-tool.test.js +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2025, Salesforce, Inc. - * All rights reserved. - * SPDX-License-Identifier: BSD-3-Clause - * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ -import DeveloperGuidelinesTool from './pwa-developer-guideline-tool' -import {EmptyJsonSchema} from './utils' - -describe('PWA Development Guidelines', () => { - describe('DeveloperGuidelinesTool', () => { - it('should have correct structure', () => { - expect(DeveloperGuidelinesTool).toMatchObject({ - name: 'development_guidelines', - description: `You must follow this development guidelines before attempting to analyze/ generate / refactor / modify / fix code. - - e.g. "Create a customer service Chat component", "Find bugs in my_script.jsx", "Refactor my_script.jsx to use React Hooks"`, - inputSchema: EmptyJsonSchema, - fn: expect.any(Function) - }) - }) - - it('should return guidelines content when executed', async () => { - const result = await DeveloperGuidelinesTool.fn() - - expect(result).toEqual({ - content: [ - { - type: 'text', - text: expect.stringContaining( - 'Salesforce Commerce Composable Storefront Development Guidelines' - ) - } - ] - }) - }) - - it('should include all major sections in the guidelines', async () => { - const result = await DeveloperGuidelinesTool.fn() - const guidelineText = result.content[0].text - - const requiredSections = [ - 'Overview', - 'Core Principles', - 'Technical Stack', - 'PWK Kit Architecture', - 'Quality Standards' - ] - - requiredSections.forEach((section) => { - expect(guidelineText).toContain(section) - }) - }) - }) -}) diff --git a/packages/pwa-storefront-mcp/src/utils/run-site-test-accessibility.js b/packages/pwa-storefront-mcp/src/utils/run-site-test-accessibility.js deleted file mode 100644 index 5f7b26cbe6..0000000000 --- a/packages/pwa-storefront-mcp/src/utils/run-site-test-accessibility.js +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2025, Salesforce, Inc. - * All rights reserved. - * SPDX-License-Identifier: BSD-3-Clause - * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ -import {chromium} from 'playwright' -import AxeBuilder from '@axe-core/playwright' - -export async function runAccessibilityTest(siteUrl) { - const browser = await chromium.launch() - const context = await browser.newContext() - const page = await context.newPage() - - await page.goto(siteUrl) - - const results = await new AxeBuilder({page}).analyze() - - await browser.close() - - return results -} diff --git a/packages/pwa-storefront-mcp/src/utils/run-site-test-performance.js b/packages/pwa-storefront-mcp/src/utils/run-site-test-performance.js deleted file mode 100644 index 09294d6110..0000000000 --- a/packages/pwa-storefront-mcp/src/utils/run-site-test-performance.js +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (c) 2025, Salesforce, Inc. - * All rights reserved. - * SPDX-License-Identifier: BSD-3-Clause - * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ -import {chromium} from 'playwright' -import AxeBuilder from '@axe-core/playwright' - -export async function runAccessibilityTest(siteUrl) { - const browser = await chromium.launch() - const context = await browser.newContext() - const page = await context.newPage() - - await page.goto(siteUrl) - - const results = await new AxeBuilder({page}).analyze() - - await browser.close() - - return results -} - -export async function runPerformanceTest(siteUrl) { - const browser = await chromium.launch() - const page = await browser.newPage() - - await page.goto(siteUrl) - - // Wait for page to fully load - await page.waitForLoadState('load') - - // Extract detailed navigation timing data from the browser - const navigationEntries = await page.evaluate(() => { - const nav = performance.getEntriesByType('navigation')[0] - if (!nav) return null - return { - type: nav.type, - startTime: nav.startTime, - unloadEventStart: nav.unloadEventStart, - unloadEventEnd: nav.unloadEventEnd, - redirectStart: nav.redirectStart, - redirectEnd: nav.redirectEnd, - fetchStart: nav.fetchStart, - domainLookupStart: nav.domainLookupStart, - domainLookupEnd: nav.domainLookupEnd, - connectStart: nav.connectStart, - connectEnd: nav.connectEnd, - secureConnectionStart: nav.secureConnectionStart, - requestStart: nav.requestStart, - responseStart: nav.responseStart, - responseEnd: nav.responseEnd, - domLoading: nav.domLoading, - domInteractive: nav.domInteractive, - domContentLoadedEventStart: nav.domContentLoadedEventStart, - domContentLoadedEventEnd: nav.domContentLoadedEventEnd, - domComplete: nav.domComplete, - loadEventStart: nav.loadEventStart, - loadEventEnd: nav.loadEventEnd, - duration: nav.duration - } - }) - - // Fallback for browsers that don't support Navigation Timing Level 2 - const perfTiming = - navigationEntries || - (await page.evaluate(() => JSON.parse(JSON.stringify(window.performance.timing)))) - - // Calculate key metrics - const metrics = { - totalLoadTime: perfTiming.loadEventEnd - perfTiming.startTime, - domContentLoadedTime: perfTiming.domContentLoadedEventEnd - perfTiming.startTime, - timeToFirstByte: perfTiming.responseStart - perfTiming.startTime, - domInteractive: perfTiming.domInteractive - perfTiming.startTime, - firstPaint: perfTiming.responseEnd - perfTiming.startTime, - ...perfTiming // include all raw timings for reference - } - - await browser.close() - - return { - content: [ - { - type: 'text', - text: `Total Load Time: ${metrics.totalLoadTime}ms, \ - DOM Content Loaded: ${metrics.domContentLoadedTime}ms, \ - Time to First Byte: ${metrics.timeToFirstByte}ms, - DOM Interactive: ${metrics.domInteractive}ms, \ - First Paint: ${metrics.firstPaint}ms` - } - ] - } -} diff --git a/packages/pwa-storefront-mcp/src/utils/run-site-test-tool.js b/packages/pwa-storefront-mcp/src/utils/run-site-test-tool.js deleted file mode 100644 index 416944c747..0000000000 --- a/packages/pwa-storefront-mcp/src/utils/run-site-test-tool.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2025, Salesforce, Inc. - * All rights reserved. - * SPDX-License-Identifier: BSD-3-Clause - * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ -import {runPerformanceTest} from './run-site-test-performance' -import {runAccessibilityTest} from './run-site-test-accessibility' - -const DEFAULT_SITE_URL = 'https://pwa-kit.mobify-storefront.com' - -export class TestWithPlaywrightTool { - /** - * Runs a Playwright test file by name (e.g., 'performance' or 'accessibility') - * @param {string} testType - 'performance' or 'accessibility' - * @param {string} [siteUrl] - Optional site URL to test - * @returns {object} - Result of the test run - */ - async run(testType, siteUrl = DEFAULT_SITE_URL) { - switch (testType) { - case 'performance': { - return runPerformanceTest(siteUrl) - } - case 'accessibility': { - return runAccessibilityTest(siteUrl) - } - default: { - const result = {error: 'unsupported test type'} - console.log('Unsupported test type result:', result) - return result - } - } - } -} diff --git a/packages/pwa-storefront-mcp/src/utils/run-site-test-tool.test.js b/packages/pwa-storefront-mcp/src/utils/run-site-test-tool.test.js deleted file mode 100644 index a6c6c6e33a..0000000000 --- a/packages/pwa-storefront-mcp/src/utils/run-site-test-tool.test.js +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2025, Salesforce, Inc. - * All rights reserved. - * SPDX-License-Identifier: BSD-3-Clause - * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ -import {TestWithPlaywrightTool} from './run-site-test-tool' - -// Mock the imported test functions to avoid running real Playwright tests -jest.mock('./run-site-test-performance.js', () => ({ - runPerformanceTest: jest.fn(async (url) => ({mock: 'performance', url})) -})) -jest.mock('./run-site-test-accessibility.js', () => ({ - runAccessibilityTest: jest.fn(async (url) => ({mock: 'accessibility', url})) -})) - -const DEFAULT_SITE_URL = 'https://pwa-kit.mobify-storefront.com' - -describe('TestWithPlaywrightTool', () => { - let tool - beforeEach(() => { - tool = new TestWithPlaywrightTool() - }) - - it('runs performance test with provided siteUrl', async () => { - const result = await tool.run('performance', DEFAULT_SITE_URL) - expect(result).toEqual({mock: 'performance', url: DEFAULT_SITE_URL}) - }) - - it('runs performance test with default siteUrl if not provided', async () => { - // Remove the error throw for missing siteUrl in your implementation if you want this to pass - const result = await tool.run('performance') - expect(result).toEqual({mock: 'performance', url: DEFAULT_SITE_URL}) - }) - - it('throws error if testType is unsupported', async () => { - const result = await tool.run('unknown', 'https://example.com') - expect(result).toEqual({error: 'unsupported test type'}) - }) - - it('runs accessibility test with provided siteUrl', async () => { - const result = await tool.run('accessibility', 'https://foo.com') - expect(result).toEqual({mock: 'accessibility', url: 'https://foo.com'}) - }) - - it('runs accessibility test with default siteUrl if not provided', async () => { - const result = await tool.run('accessibility') - expect(result).toEqual({mock: 'accessibility', url: DEFAULT_SITE_URL}) - }) -}) diff --git a/packages/pwa-storefront-mcp/src/utils/utils.js b/packages/pwa-storefront-mcp/src/utils/utils.js deleted file mode 100644 index 394c7ceab6..0000000000 --- a/packages/pwa-storefront-mcp/src/utils/utils.js +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (c) 2025, Salesforce, Inc. - * All rights reserved. - * SPDX-License-Identifier: BSD-3-Clause - * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ -import fs from 'fs' -import path from 'path' -import {spawn} from 'cross-spawn' -import {zodToJsonSchema} from 'zod-to-json-schema' -import {z} from 'zod' -import os from 'os' -import {exec} from 'child_process' - -// Private schema used to generate the JSON schema -const emptySchema = z.object({}).strict() - -export const EmptyJsonSchema = zodToJsonSchema(emptySchema) - -/** - * Converts a string to kebab-case (e.g., ProductCard -> product-card) - */ -export function toKebabCase(str) { - return str - .replace(/([a-z])([A-Z])/g, '$1-$2') - .replace(/\s+/g, '-') - .toLowerCase() -} - -/** - * Converts a string to PascalCase (e.g., product-card -> ProductCard) - */ -export const toPascalCase = (str) => - str.replace(/(^\w|[-_\s]\w)/g, (match) => match.replace(/[-_\s]/, '').toUpperCase()) - -/** - * Runs a shell command and captures its stdout/stderr as a string. - * - * @param {string} command - The executable to run (e.g. "node", "npx", "ls"). - * @param {string[]} args - Arguments to pass to the command. - * @param {Object} [options] - Optional spawn options (e.g. cwd). - * @returns {Promise} - Resolves with combined stdout and stderr. - */ -export const runCommand = async (command, args = [], options = {}) => { - return new Promise((resolve, reject) => { - const child = spawn(command, args, { - ...options, - stdio: ['ignore', 'pipe', 'pipe'], // ignore stdin, pipe out/err - shell: false // be explicit — set to true if you want shell features - }) - - let output = '' - - child.stdout.on('data', (chunk) => { - output += chunk.toString() - }) - - child.stderr.on('data', (chunk) => { - output += chunk.toString() // combine stderr into output - }) - - child.on('error', (err) => { - reject(err) - }) - - child.on('close', (code) => { - if (code === 0) { - resolve(output) - } else { - const error = new Error(`Command failed with exit code ${code}`) - error.output = output - error.code = code - reject(error) - } - }) - }) -} - -/** - * Checks if the project is a monorepo by verifying the existence of lerna.json in the root directory. - * - * @returns {boolean} True if lerna.json exists in the '../../../..' folder, false otherwise. - */ -export function isMonoRepo() { - const lernaPath = path.resolve(__dirname, '../../../..', 'lerna.json') - return fs.existsSync(lernaPath) -} - -/** - * Runs an NPX command and captures its output. - * - * @returns {Promise} - Resolves with the command output. - */ -export async function runNpxCommand(NPX_COMMAND, CREATE_APP_COMMAND, DISPLAY_PROGRAM_COMMAND) { - return new Promise((resolve, reject) => { - const tempDir = os.tmpdir() - const outputFilePath = path.join(tempDir, 'npx-output.json') - const errorFilePath = path.join(tempDir, 'npx-error.log') - const command = `${NPX_COMMAND} ${CREATE_APP_COMMAND} ${DISPLAY_PROGRAM_COMMAND} > ${outputFilePath} 2> ${errorFilePath}` - - exec(command, (error) => { - if (error) { - reject(error) - return - } - - fs.promises - .readFile(outputFilePath, 'utf-8') - .then((data) => resolve(data)) - .catch((err) => reject(err)) - }) - }) -} diff --git a/packages/pwa-storefront-mcp/src/utils/utils.test.js b/packages/pwa-storefront-mcp/src/utils/utils.test.js deleted file mode 100644 index 3a02589ae0..0000000000 --- a/packages/pwa-storefront-mcp/src/utils/utils.test.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2025, Salesforce, Inc. - * All rights reserved. - * SPDX-License-Identifier: BSD-3-Clause - * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ -import {EmptyJsonSchema, isMonoRepo} from './utils' -import fs from 'fs' - -describe('Utils', () => { - describe('EmptyJsonSchema', () => { - it('should be a valid JSON schema', () => { - expect(EmptyJsonSchema).toEqual({ - $schema: 'http://json-schema.org/draft-07/schema#', - type: 'object', - properties: {}, - additionalProperties: false - }) - }) - - it('should have required JSON schema properties', () => { - expect(EmptyJsonSchema).toHaveProperty('type', 'object') - expect(EmptyJsonSchema).toHaveProperty('properties') - expect(EmptyJsonSchema).toHaveProperty('additionalProperties', false) - }) - - it('should not allow additional properties', () => { - expect(EmptyJsonSchema.additionalProperties).toBe(false) - expect(EmptyJsonSchema.properties).toEqual({}) - }) - }) - - describe('isMonoRepo', () => { - test('returns true if lerna.json exists', () => { - jest.spyOn(fs, 'existsSync').mockReturnValueOnce(true) - expect(isMonoRepo()).toBe(true) - }) - - test('returns false if lerna.json does not exist', () => { - jest.spyOn(fs, 'existsSync').mockReturnValueOnce(false) - expect(isMonoRepo()).toBe(false) - }) - - afterEach(() => { - jest.restoreAllMocks() - }) - }) -}) diff --git a/packages/template-mrt-reference-app/jest.config.js b/packages/template-mrt-reference-app/jest.config.js index b4cbd61d65..72af80c7b1 100644 --- a/packages/template-mrt-reference-app/jest.config.js +++ b/packages/template-mrt-reference-app/jest.config.js @@ -16,7 +16,12 @@ module.exports = { statements: 85 } }, - collectCoverageFrom: ['app/**', '!app/request-processor.js', '!app/static/**', '!app/*.json'], + collectCoverageFrom: [ + 'app/**', + '!app/request-processor.js', + '!app/static/**', + '!app/*.json' + ], // Increase to: 6 x default timeout of 5 seconds ...(process.env.CI ? {testTimeout: 30000} : {}) } diff --git a/packages/template-retail-react-app/CHANGELOG.md b/packages/template-retail-react-app/CHANGELOG.md index 412226f840..d078933073 100644 --- a/packages/template-retail-react-app/CHANGELOG.md +++ b/packages/template-retail-react-app/CHANGELOG.md @@ -4,18 +4,10 @@ - [a11y] Ensure voiceover announces contents of the email confirmation modal [#2540](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2540) - Updated 6 new languages [#2495](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2495) - Fix Einstein event tracking for `addToCart` event [#2558](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2558) -- Password Reset and Passwordless Integration Test [#2669](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2669) - Update latest translations for all languages [#2616](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2616) -- Added support for Buy Online Pick up In Store (BOPIS) [#2646](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2646) - Load active data scripts on demand only [#2623](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2623) -- Provide base image for convenient perf optimizations [#2642](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2642) -- Support saving billing phone number on user registration from order confirmation [#2653](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2653) -- Support saving default shipping address on user registration from order confirmation [#2706](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2706) -- Minor updates to support BOPIS E2E tests [#2716](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2716) -- Provide support for partial hydration [#2696](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2696) -- Show Automatic Bonus Products on Cart Page [#2704](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2704) -- Support Standard Products [2697](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2697) - +- Show Bonus Products on Cart Page [#2547](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2547) +- Show the Bonus Product in a Product View Modal to enable adding it to the cart [#2680](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2680) ## v6.1.0 (May 22, 2025) @@ -453,4 +445,4 @@ The versions published below were not published on npm, and the versioning match ### v1.0.0 (Sep 08, 2021) -- PWA Kit General Availability and open source. 🎉 \ No newline at end of file +- PWA Kit General Availability and open source. 🎉 diff --git a/packages/template-retail-react-app/app/components/_app/index.jsx b/packages/template-retail-react-app/app/components/_app/index.jsx index dce781b05c..53e6c9ca5a 100644 --- a/packages/template-retail-react-app/app/components/_app/index.jsx +++ b/packages/template-retail-react-app/app/components/_app/index.jsx @@ -33,6 +33,7 @@ import {SkipNavLink, SkipNavContent} from '@chakra-ui/skip-nav' // Contexts import {CurrencyProvider} from '@salesforce/retail-react-app/app/contexts' +import {BonusProductModalProvider} from '@salesforce/retail-react-app/app/hooks/use-bonus-product-modal' // Local Project Components import Header from '@salesforce/retail-react-app/app/components/header' @@ -47,8 +48,6 @@ import {ListMenu, ListMenuContent} from '@salesforce/retail-react-app/app/compon import {HideOnDesktop, HideOnMobile} from '@salesforce/retail-react-app/app/components/responsive' import AboveHeader from '@salesforce/retail-react-app/app/components/_app/partials/above-header' import {StoreLocatorModal} from '@salesforce/retail-react-app/app/components/store-locator' -import Island from '@salesforce/retail-react-app/app/components/island' - // Hooks import {AuthModal, useAuthModal} from '@salesforce/retail-react-app/app/hooks/use-auth-modal' import { @@ -76,8 +75,7 @@ import { THEME_COLOR, CAT_MENU_DEFAULT_NAV_SSR_DEPTH, CAT_MENU_DEFAULT_ROOT_CATEGORY, - DEFAULT_LOCALE, - STORE_LOCATOR_IS_ENABLED + DEFAULT_LOCALE } from '@salesforce/retail-react-app/app/constants' import Seo from '@salesforce/retail-react-app/app/components/seo' @@ -357,90 +355,80 @@ const App = (props) => { Skip to Content - {STORE_LOCATOR_IS_ENABLED && ( - - )} - - - {!isCheckout ? ( - <> - -
- - - - - - - -
- - ) : ( - - )} -
-
+ + + {!isCheckout ? ( + <> + +
+ + + + + + + +
+ + ) : ( + + )} +
{!isOnline && } - - + - - {children} - - - - - + + + {children} + + + {!isCheckout ?