diff --git a/packages/b2c-cli/.github/workflows/e2e-tests.yml b/packages/b2c-cli/.github/workflows/e2e-tests.yml new file mode 100644 index 00000000..25bcc25b --- /dev/null +++ b/packages/b2c-cli/.github/workflows/e2e-tests.yml @@ -0,0 +1,169 @@ +name: e2e tests + +on: + # Nightly workflow - Run E2E tests on a schedule + schedule: + - cron: '0 2 * * *' # Run at 2 AM UTC daily + # Post-merge - Run after changes are merged to main + push: + branches: [main] + # Manual trigger - Support workflow_dispatch for on-demand runs + workflow_dispatch: + inputs: + test_realm: + description: 'Test realm to use (optional, defaults to var)' + required: false + type: string + sfcc_client_id: + description: 'SFCC Client ID (optional, defaults to var)' + required: false + type: string + sfcc_client_secret: + description: 'SFCC Client Secret (optional, defaults to secret)' + required: false + type: string + sfcc_account_manager_host: + description: 'SFCC Account Manager Host (optional, defaults to var)' + required: false + type: string + sfcc_sandbox_api_host: + description: 'SFCC Sandbox API Host (optional, defaults to var)' + required: false + type: string + node_version: + description: 'Node.js version to test with' + required: false + default: 'lts/*' + type: choice + options: + - 'lts/-1' + - 'lts/*' + - 'latest' + os: + description: 'Operating system to test on' + required: false + default: 'ubuntu-latest' + type: choice + options: + - 'ubuntu-latest' + - 'windows-latest' +jobs: + e2e-tests: + strategy: + matrix: + node_version: [22.x, 24.x] + runs-on: ubuntu-latest + environment: e2e-dev + timeout-minutes: 10 # E2E tests can take longer + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: pnpm + - name: Check for required secrets and vars + id: check-secrets + env: + SFCC_CLIENT_ID: ${{ vars.SFCC_CLIENT_ID }} + SFCC_CLIENT_SECRET: ${{ secrets.SFCC_CLIENT_SECRET }} + TEST_REALM: ${{ vars.TEST_REALM }} + SFCC_ACCOUNT_MANAGER_HOST: ${{ vars.SFCC_ACCOUNT_MANAGER_HOST }} + SFCC_SANDBOX_API_HOST: ${{ vars.SFCC_SANDBOX_API_HOST }} + run: | + if [ -n "$SFCC_CLIENT_ID" ] && [ -n "$SFCC_CLIENT_SECRET" ] && [ -n "$TEST_REALM" ] && [ -n "SFCC_ACCOUNT_MANAGER_HOST" ] && [ -n "SFCC_SANDBOX_API_HOST" ]; then + echo "has-secrets=true" >> $GITHUB_OUTPUT + else + echo "has-secrets=false" >> $GITHUB_OUTPUT + echo "E2E tests skipped - missing required variables:" >> $GITHUB_STEP_SUMMARY + echo " - SFCC_CLIENT_ID (var): ${SFCC_CLIENT_ID:+✓}" >> $GITHUB_STEP_SUMMARY + echo " - TEST_REALM (var): ${TEST_REALM:+✓}" >> $GITHUB_STEP_SUMMARY + echo " - SFCC_ACCOUNT_MANAGER_HOST (var): ${SFCC_ACCOUNT_MANAGER_HOST:+✓}" >> $GITHUB_STEP_SUMMARY + echo " - SFCC_SANDBOX_API_HOST (var): ${SFCC_SANDBOX_API_HOST:+✓}" >> $GITHUB_STEP_SUMMARY + fi + - name: Install dependencies + if: steps.check-secrets.outputs.has-secrets == 'true' + run: pnpm install + - name: Build package + if: steps.check-secrets.outputs.has-secrets == 'true' + run: pnpm run build + - name: Run E2E Tests + if: steps.check-secrets.outputs.has-secrets == 'true' + id: e2e-test + env: + # Required environment variables for Commerce Cloud integration + SFCC_CLIENT_ID: ${{ inputs.sfcc_client_id || vars.SFCC_CLIENT_ID }} + SFCC_CLIENT_SECRET: ${{ inputs.sfcc_client_secret || secrets.SFCC_CLIENT_SECRET }} + SFCC_ACCOUNT_MANAGER_HOST: ${{ inputs.sfcc_account_manager_host || vars.SFCC_ACCOUNT_MANAGER_HOST }} + SFCC_SANDBOX_API_HOST: ${{ inputs.sfcc_sandbox_api_host || vars.SFCC_SANDBOX_API_HOST }} + TEST_REALM: ${{ inputs.test_realm || vars.TEST_REALM }} + # Test configuration + NODE_ENV: test + SFCC_LOG_LEVEL: silent + run: | + echo "Running E2E tests with realm: ${TEST_REALM}" + + # Run E2E tests with JSON output for parsing + pnpm mocha "test/functional/e2e/**/*.test.ts" --reporter json > e2e-results.json || true + + # Also run with spec reporter for readable output + echo "## E2E Test Results" >> $GITHUB_STEP_SUMMARY + pnpm mocha "test/functional/e2e/**/*.test.ts" --reporter spec + - name: Parse E2E Results + if: always() && steps.e2e-test.conclusion != 'cancelled' && steps.check-secrets.outputs.has-secrets == 'true' + run: | + if [ -f "e2e-results.json" ]; then + # Extract test summary from JSON results + TESTS=$(cat e2e-results.json | jq -r '.stats.tests // 0') + PASSES=$(cat e2e-results.json | jq -r '.stats.passes // 0') + FAILURES=$(cat e2e-results.json | jq -r '.stats.failures // 0') + DURATION=$(cat e2e-results.json | jq -r '.stats.duration // 0') + + echo "## E2E Test Summary" >> $GITHUB_STEP_SUMMARY + echo "- **Total Tests:** $TESTS" >> $GITHUB_STEP_SUMMARY + echo "- **Passed:** $PASSES" >> $GITHUB_STEP_SUMMARY + echo "- **Failed:** $FAILURES" >> $GITHUB_STEP_SUMMARY + echo "- **Duration:** ${DURATION}ms" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Add failure details if any + if [ "$FAILURES" -gt "0" ]; then + echo "### Failed Tests" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + cat e2e-results.json | jq -r '.failures[]? | "[X] " + .fullTitle + "\n " + .err.message' >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + fi + fi + - name: Upload E2E Test Results + if: always() && steps.e2e-test.conclusion != 'cancelled' && steps.check-secrets.outputs.has-secrets == 'true' + uses: actions/upload-artifact@v4 + with: + name: e2e-test-results-${{ matrix.os }}-node${{ matrix.node_version }}-${{ github.run_number }} + path: e2e-results.json + retention-days: 30 + - name: Notify on Failure + if: failure() && (github.event_name == 'schedule' || github.event_name == 'push') && steps.check-secrets.outputs.has-secrets == 'true' + uses: actions/github-script@v7 + with: + script: | + const issue = { + owner: context.repo.owner, + repo: context.repo.repo, + title: `CLI E2E Tests Failed - ${new Date().toISOString().split('T')[0]}`, + body: `## CLI E2E Test Failure + + The CLI E2E tests have failed. Please investigate. + + **Workflow:** ${context.workflow} + **Run:** ${context.runNumber} + **Commit:** ${context.sha} + **Event:** ${context.eventName} + + [View workflow run](${context.payload.repository.html_url}/actions/runs/${context.runId}) + `, + labels: ['bug', 'e2e-failure', 'cli', 'needs-investigation'] + }; + + // Only create issue for scheduled runs to avoid spam + if (context.eventName === 'schedule') { + github.rest.issues.create(issue); + } diff --git a/packages/b2c-cli/package.json b/packages/b2c-cli/package.json index 20e232a3..535909a4 100644 --- a/packages/b2c-cli/package.json +++ b/packages/b2c-cli/package.json @@ -42,6 +42,7 @@ "eslint-config-prettier": "^10", "eslint-plugin-header": "^3.1.1", "eslint-plugin-prettier": "^5.5.4", + "execa": "^9.6.1", "mocha": "^10", "oclif": "^4", "prettier": "^3.6.2", @@ -127,9 +128,10 @@ "posttest": "pnpm run lint", "prepack": "oclif manifest && oclif readme", "pretest": "tsc --noEmit -p test", - "test": "c8 env OCLIF_TEST_ROOT=. mocha --forbid-only \"test/**/*.test.ts\"", - "test:ci": "c8 env OCLIF_TEST_ROOT=. mocha --forbid-only --reporter json --reporter-option output=test-results.json \"test/**/*.test.ts\"", - "test:unit": "env OCLIF_TEST_ROOT=. mocha --forbid-only \"test/**/*.test.ts\"", + "test": "c8 env OCLIF_TEST_ROOT=. mocha --forbid-only --exclude \"test/functional/e2e/**\" \"test/**/*.test.ts\"", + "test:ci": "c8 env OCLIF_TEST_ROOT=. mocha --forbid-only --exclude \"test/functional/e2e/**\" --reporter json --reporter-option output=test-results.json \"test/**/*.test.ts\"", + "test:unit": "env OCLIF_TEST_ROOT=. mocha --forbid-only --exclude \"test/functional/e2e/**\" \"test/**/*.test.ts\"", + "test:e2e": "env OCLIF_TEST_ROOT=. mocha --forbid-only \"test/functional/e2e/**/*.test.ts\"", "coverage": "c8 report", "version": "oclif readme && git add README.md", "dev": "node ./bin/dev.js" diff --git a/packages/b2c-cli/test/functional/e2e/ods-lifecycle.test.ts b/packages/b2c-cli/test/functional/e2e/ods-lifecycle.test.ts new file mode 100644 index 00000000..42d244e2 --- /dev/null +++ b/packages/b2c-cli/test/functional/e2e/ods-lifecycle.test.ts @@ -0,0 +1,308 @@ +/* + * Copyright (c) 2025, Salesforce, Inc. + * SPDX-License-Identifier: Apache-2 + * For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0 + */ + +import {expect} from 'chai'; +import {execa} from 'execa'; +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +/** + * E2E Tests for ODS (On-Demand Sandbox) Lifecycle + * + * This test suite covers the complete lifecycle of an ODS sandbox: + * 1. Create sandbox with permissions + * 2. List sandboxes and verify creation + * 3. Deploy code to sandbox + * 4. Stop sandbox + * 5. Start sandbox + * 6. Restart sandbox + * 7. Get sandbox status + * 8. Delete sandbox + */ +describe('ODS Lifecycle E2E Tests', function () { + // Timeout for entire test suite + this.timeout(360_000); // 6 minutes + + // Test configuration (paths) + const CLI_BIN = path.resolve(__dirname, '../../../bin/run.js'); + const CARTRIDGES_DIR = path.resolve(__dirname, '../fixtures/cartridges'); + + // Test state + let sandboxId: string; + let serverHostname: string; + + before(function () { + // Check required environment variables + if (!process.env.SFCC_CLIENT_ID || !process.env.SFCC_CLIENT_SECRET || !process.env.TEST_REALM) { + this.skip(); + } + }); + + /** + * Helper function to run CLI commands with proper environment. + * Uses process.env directly to get credentials from GitHub secrets. + */ + async function runCLI(args: string[]) { + const result = await execa('node', [CLI_BIN, ...args], { + env: { + ...process.env, + SFCC_LOG_LEVEL: 'silent', + }, + reject: false, + }); + + return result; + } + + /** + * Helper function to get current sandbox state (for verification only) + */ + async function getSandboxState(sandboxId: string): Promise { + const result = await runCLI(['ods', 'get', sandboxId, '--json']); + if (result.exitCode === 0) { + const sandbox = JSON.parse(result.stdout); + return sandbox.state as null | string; + } + return null; + } + + describe('Step 1: Create Sandbox', function () { + it('should create a new sandbox with permissions and wait for readiness', async function () { + // --wait can take 5-10 minutes, so increase timeout for this test + this.timeout(600_000); // 6 minutes + + const result = await runCLI([ + 'ods', + 'create', + '--realm', + process.env.TEST_REALM!, + '--ttl', + '24', + '--wait', + '--set-permissions', + '--json', + ]); + + expect(result.exitCode).to.equal(0, `Create command failed: ${result.stderr}`); + expect(result.stdout, 'Create command should return JSON output').to.not.be.empty; + + const response = JSON.parse(result.stdout); + expect(response, 'Create response should be a valid object').to.be.an('object'); + expect(response.id, 'Create response should contain a sandbox ID').to.be.a('string').and.not.be.empty; + expect(response.hostName, 'Create response should contain a hostname').to.be.a('string').and.not.be.empty; + expect(response.state, `Sandbox state should be 'started' after --wait, but got '${response.state}'`).to.equal( + 'started', + ); + + // Store for subsequent tests + sandboxId = response.id as string; + serverHostname = response.hostName as string; + + // Debug output to verify values are set + console.log(`Created sandbox: ${sandboxId} on ${serverHostname}`); + }); + }); + + describe('Step 2: List Sandboxes', function () { + it('should list sandboxes and verify the created one is present', async function () { + // Skip if we don't have a valid sandbox ID + if (!sandboxId) { + this.skip(); + } + + const result = await runCLI(['ods', 'list', '--realm', process.env.TEST_REALM!, '--json']); + + expect(result.exitCode).to.equal(0, `List command failed: ${result.stderr}`); + expect(result.stdout, 'List command should return JSON output').to.not.be.empty; + + const response = JSON.parse(result.stdout); + expect(response, 'List response should be a valid object').to.be.an('object'); + expect(response.data, 'List response should contain data array').to.be.an('array'); + + // Find our sandbox in the list + const foundSandbox = (response.data as Record[]).find( + (sandbox: Record) => sandbox.id === sandboxId, + ); + expect(foundSandbox, `Sandbox '${sandboxId}' not found in list.`).to.exist; + expect(foundSandbox!.id).to.equal(sandboxId); + }); + }); + + describe('Step 3: Deploy Code', function () { + it('should deploy test cartridge to the sandbox', async function () { + // Skip deploy if we don't have a valid sandbox + if (!sandboxId || !serverHostname) { + this.skip(); + } + + const result = await runCLI([ + 'code', + 'deploy', + CARTRIDGES_DIR, + '--cartridge', + 'plugin_example', + '--server', + serverHostname, + '--account-manager-host', + process.env.SFCC_ACCOUNT_MANAGER_HOST!, + '--json', + ]); + + expect(result.exitCode).to.equal(0, `Deploy command failed: ${result.stderr}`); + expect(result.stdout, 'Deploy command should return JSON output').to.not.be.empty; + + const response = JSON.parse(result.stdout); + expect(response, 'Deploy response should be a valid object').to.be.an('object'); + expect(response.cartridges, 'Deploy response should contain cartridges array') + .to.be.an('array') + .with.length.greaterThan(0); + expect(response.codeVersion, 'Deploy response should contain code version').to.be.a('string').and.not.be.empty; + }); + }); + + describe('Step 4: Stop Sandbox', function () { + it('should stop the sandbox', async function () { + // Skip if we don't have a valid sandbox ID + if (!sandboxId) { + this.skip(); + } + + const result = await runCLI(['ods', 'stop', sandboxId, '--json']); + + expect(result.exitCode).to.equal(0, `Stop command failed: ${result.stderr}`); + + const state = await getSandboxState(sandboxId); + if (state) { + expect( + ['stopped', 'stopping'], + `Sandbox state should be 'stopped' or 'stopping' after stop command`, + ).to.include(state); + } + }); + }); + + describe('Step 5: Start Sandbox', function () { + it('should start the sandbox', async function () { + // Skip if we don't have a valid sandbox ID + if (!sandboxId) { + this.skip(); + } + + const result = await runCLI(['ods', 'start', sandboxId, '--json']); + + expect(result.exitCode).to.equal(0, `Start command failed: ${result.stderr}`); + const state = await getSandboxState(sandboxId); + if (state) { + expect(['started', 'starting']).to.include(state); + } + }); + }); + + describe('Step 6: Restart Sandbox', function () { + it('should restart the sandbox', async function () { + // Skip if we don't have a valid sandbox ID + if (!sandboxId) { + this.skip(); + } + + const result = await runCLI(['ods', 'restart', sandboxId, '--json']); + + expect(result.exitCode).to.equal(0, `Restart command failed: ${result.stderr}`); + + const state = await getSandboxState(sandboxId); + if (state) { + expect( + ['started', 'starting', 'restarting'], + `Sandbox state should be 'started', 'starting', or 'restarting' after restart command, but got '${state}'`, + ).to.include(state); + } + }); + }); + + describe('Step 7: Get Sandbox Status', function () { + it('should retrieve sandbox status', async function () { + // Skip if we don't have a valid sandbox ID + if (!sandboxId) { + this.skip(); + } + + const result = await runCLI(['ods', 'get', sandboxId, '--json']); + + expect(result.exitCode).to.equal(0, `Get command failed: ${result.stderr}`); + expect(result.stdout, 'Get command should return JSON output').to.not.be.empty; + + const response = JSON.parse(result.stdout); + expect(response, 'Get response should be a valid object').to.be.an('object'); + expect(response.id, `Get response ID '${response.id}' should match requested sandbox '${sandboxId}'`).to.equal( + sandboxId, + ); + expect(response.state, 'Get response should contain sandbox state').to.be.a('string').and.not.be.empty; + }); + }); + + describe('Step 8: Delete Sandbox', function () { + it('should delete the sandbox', async function () { + // Skip if we don't have a valid sandbox ID + if (!sandboxId) { + this.skip(); + } + + const result = await runCLI(['ods', 'delete', sandboxId, '--force', '--json']); + + expect(result.exitCode).to.equal(0, `Delete command failed: ${result.stderr}`); + }); + }); + + describe('Additional Test Cases', function () { + describe('Error Handling', function () { + it('should handle invalid realm gracefully', async function () { + const result = await runCLI(['ods', 'list', '--realm', 'invalid-realm-xyz', '--json']); + + // Command should either succeed with empty list or fail with error + expect( + result.exitCode, + `Invalid realm command should either succeed (0) or fail (1), but got ${result.exitCode}`, + ).to.be.oneOf([0, 1]); + }); + + it('should handle missing sandbox ID gracefully', async function () { + const result = await runCLI(['ods', 'get', 'non-existent-sandbox-id', '--json']); + + expect( + result.exitCode, + `Missing sandbox command should fail, but got exit code ${result.exitCode}`, + ).to.not.equal(0); + expect(result.stderr, 'Missing sandbox command should return error message').to.not.be.empty; + }); + }); + + describe('Authentication', function () { + it('should fail with invalid credentials', async function () { + const result = await execa('node', [CLI_BIN, 'ods', 'list', '--realm', process.env.TEST_REALM!, '--json'], { + env: { + ...process.env, + SFCC_CLIENT_ID: 'invalid-client-id', + SFCC_CLIENT_SECRET: 'invalid-client-secret', + SFCC_LOG_LEVEL: 'silent', + }, + reject: false, + }); + + expect(result.exitCode, `Invalid credentials should fail, but got exit code ${result.exitCode}`).to.not.equal( + 0, + ); + expect(result.stderr, 'Invalid credentials should return authentication error').to.match( + /401|unauthorized|invalid.*client/i, + ); + }); + }); + }); + + after(function () {}); +}); diff --git a/packages/b2c-cli/test/functional/e2e_cli_test.sh b/packages/b2c-cli/test/functional/e2e_cli_test.sh old mode 100644 new mode 100755 diff --git a/packages/b2c-cli/test/functional/fixtures/cartridges/plugin_example/.project b/packages/b2c-cli/test/functional/fixtures/cartridges/plugin_example/.project index e69de29b..a408b73d 100644 --- a/packages/b2c-cli/test/functional/fixtures/cartridges/plugin_example/.project +++ b/packages/b2c-cli/test/functional/fixtures/cartridges/plugin_example/.project @@ -0,0 +1,18 @@ + + + plugin_example + + + + + + com.demandware.studio.core.beehiveElementBuilder + + + + + + com.demandware.studio.core.beehiveNature + + + diff --git a/packages/b2c-cli/test/functional/fixtures/cartridges/plugin_example/package.json b/packages/b2c-cli/test/functional/fixtures/cartridges/plugin_example/package.json new file mode 100644 index 00000000..24a41358 --- /dev/null +++ b/packages/b2c-cli/test/functional/fixtures/cartridges/plugin_example/package.json @@ -0,0 +1,7 @@ +{ + "name": "plugin_example", + "version": "1.0.0", + "description": "Example test cartridge for E2E testing", + "main": "cartridge/scripts/test.js" +} + diff --git a/packages/b2c-tooling-sdk/src/cli/instance-command.ts b/packages/b2c-tooling-sdk/src/cli/instance-command.ts index 64b04c5b..dd6b7b50 100644 --- a/packages/b2c-tooling-sdk/src/cli/instance-command.ts +++ b/packages/b2c-tooling-sdk/src/cli/instance-command.ts @@ -137,6 +137,7 @@ export abstract class InstanceCommand extends OAuthCom clientId: config.clientId, clientSecret: config.clientSecret, scopes: config.scopes, + accountManagerHost: this.accountManagerHost, }; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 12329049..bfe26e8a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -96,6 +96,9 @@ importers: eslint-plugin-prettier: specifier: ^5.5.4 version: 5.5.4(eslint-config-prettier@10.1.8(eslint@9.39.1))(eslint@9.39.1)(prettier@3.6.2) + execa: + specifier: ^9.6.1 + version: 9.6.1 mocha: specifier: ^10 version: 10.8.2 @@ -1876,6 +1879,9 @@ packages: '@salesforce/dev-config@4.3.2': resolution: {integrity: sha512-mxhsWV1rzHfhGMVSFQRLOHZTGfB1R2FtqbuIb3hrgDFsW1NLjEDS2U+eZWBJiCYod1JeGpJxnETNq587lem1Gg==} + '@sec-ant/readable-stream@0.4.1': + resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} + '@shikijs/core@2.5.0': resolution: {integrity: sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==} @@ -1916,6 +1922,10 @@ packages: resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==} engines: {node: '>=14.16'} + '@sindresorhus/merge-streams@4.0.0': + resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} + engines: {node: '>=18'} + '@smithy/abort-controller@4.2.5': resolution: {integrity: sha512-j7HwVkBw68YW8UmFRcjZOmssE77Rvk0GWAIN1oFBhsaovQmZWYCIcGa9/pwRB0ExI8Sk9MWNALTjftjHZea7VA==} engines: {node: '>=18.0.0'} @@ -3419,6 +3429,10 @@ packages: resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} engines: {node: '>=18.0.0'} + execa@9.6.1: + resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==} + engines: {node: ^18.19.0 || >=20.5.0} + express-rate-limit@7.5.1: resolution: {integrity: sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==} engines: {node: '>= 16'} @@ -3484,6 +3498,10 @@ packages: resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} engines: {node: ^12.20 || >= 14.13} + figures@6.1.0: + resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} + engines: {node: '>=18'} + file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} @@ -3609,6 +3627,10 @@ packages: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} + get-stream@9.0.1: + resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} + engines: {node: '>=18'} + get-symbol-description@1.1.0: resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} @@ -3776,6 +3798,10 @@ packages: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} + human-signals@8.0.1: + resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==} + engines: {node: '>=18.18.0'} + i18next@25.6.3: resolution: {integrity: sha512-AEQvoPDljhp67a1+NsnG/Wb1Nh6YoSvtrmeEd24sfGn3uujCtXCF3cXpr7ulhMywKNFF7p3TX1u2j7y+caLOJg==} peerDependencies: @@ -3975,6 +4001,10 @@ packages: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} + is-stream@4.0.1: + resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} + engines: {node: '>=18'} + is-string@1.1.1: resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} engines: {node: '>= 0.4'} @@ -3991,6 +4021,10 @@ packages: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} + is-unicode-supported@2.1.0: + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + engines: {node: '>=18'} + is-weakmap@2.0.2: resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} engines: {node: '>= 0.4'} @@ -4383,6 +4417,10 @@ packages: resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + npm-run-path@6.0.0: + resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} + engines: {node: '>=18'} + npm@10.9.4: resolution: {integrity: sha512-OnUG836FwboQIbqtefDNlyR0gTHzIfwRfE3DuiNewBvnMnWEpB0VEXwBlFVgqpNzIgYo/MHh3d2Hel/pszapAA==} engines: {node: ^18.17.0 || >=20.5.0} @@ -4591,6 +4629,10 @@ packages: resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==} engines: {node: '>=18'} + parse-ms@4.0.0: + resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} + engines: {node: '>=18'} + parse-statements@1.0.11: resolution: {integrity: sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==} @@ -4714,6 +4756,10 @@ packages: engines: {node: '>=14'} hasBin: true + pretty-ms@9.3.0: + resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==} + engines: {node: '>=18'} + prismjs@1.30.0: resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} engines: {node: '>=6'} @@ -5195,6 +5241,10 @@ packages: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} + strip-final-newline@4.0.0: + resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} + engines: {node: '>=18'} + strip-indent@3.0.0: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} @@ -5428,6 +5478,10 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + unicorn-magic@0.3.0: + resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} + engines: {node: '>=18'} + unist-util-is@6.0.1: resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} @@ -5710,6 +5764,10 @@ packages: resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} engines: {node: '>=18'} + yoctocolors@2.1.2: + resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} + engines: {node: '>=18'} + zip-stream@6.0.1: resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} engines: {node: '>= 14'} @@ -7696,6 +7754,8 @@ snapshots: '@salesforce/dev-config@4.3.2': {} + '@sec-ant/readable-stream@0.4.1': {} + '@shikijs/core@2.5.0': dependencies: '@shikijs/engine-javascript': 2.5.0 @@ -7756,6 +7816,8 @@ snapshots: '@sindresorhus/is@5.6.0': {} + '@sindresorhus/merge-streams@4.0.0': {} + '@smithy/abort-controller@4.2.5': dependencies: '@smithy/types': 4.9.0 @@ -9290,8 +9352,8 @@ snapshots: eslint-config-oclif: 5.2.2(eslint@9.39.1) eslint-config-xo: 0.49.0(eslint@9.39.1) eslint-config-xo-space: 0.35.0(eslint@9.39.1) - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1))(eslint@9.39.1) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1))(eslint@9.39.1))(eslint@9.39.1) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1) eslint-plugin-jsdoc: 50.8.0(eslint@9.39.1) eslint-plugin-mocha: 10.5.0(eslint@9.39.1) eslint-plugin-n: 17.23.1(eslint@9.39.1)(typescript@5.9.3) @@ -9336,7 +9398,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1))(eslint@9.39.1): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.3(supports-color@10.2.2) @@ -9347,18 +9409,18 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1))(eslint@9.39.1))(eslint@9.39.1) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1))(eslint@9.39.1))(eslint@9.39.1): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.46.4(eslint@9.39.1)(typescript@5.9.3) eslint: 9.39.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1))(eslint@9.39.1) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1) transitivePeerDependencies: - supports-color @@ -9379,7 +9441,7 @@ snapshots: dependencies: eslint: 9.39.1 - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1))(eslint@9.39.1))(eslint@9.39.1): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -9390,7 +9452,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.39.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1))(eslint@9.39.1))(eslint@9.39.1) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -9615,6 +9677,21 @@ snapshots: dependencies: eventsource-parser: 3.0.6 + execa@9.6.1: + dependencies: + '@sindresorhus/merge-streams': 4.0.0 + cross-spawn: 7.0.6 + figures: 6.1.0 + get-stream: 9.0.1 + human-signals: 8.0.1 + is-plain-obj: 4.1.0 + is-stream: 4.0.1 + npm-run-path: 6.0.0 + pretty-ms: 9.3.0 + signal-exit: 4.1.0 + strip-final-newline: 4.0.0 + yoctocolors: 2.1.2 + express-rate-limit@7.5.1(express@5.2.1): dependencies: express: 5.2.1 @@ -9699,6 +9776,10 @@ snapshots: node-domexception: 1.0.0 web-streams-polyfill: 3.3.3 + figures@6.1.0: + dependencies: + is-unicode-supported: 2.1.0 + file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -9826,6 +9907,11 @@ snapshots: get-stream@6.0.1: {} + get-stream@9.0.1: + dependencies: + '@sec-ant/readable-stream': 0.4.1 + is-stream: 4.0.1 + get-symbol-description@1.1.0: dependencies: call-bound: 1.0.4 @@ -10019,6 +10105,8 @@ snapshots: transitivePeerDependencies: - supports-color + human-signals@8.0.1: {} + i18next@25.6.3(typescript@5.9.3): dependencies: '@babel/runtime': 7.28.4 @@ -10187,6 +10275,8 @@ snapshots: is-stream@2.0.1: {} + is-stream@4.0.1: {} + is-string@1.1.1: dependencies: call-bound: 1.0.4 @@ -10204,6 +10294,8 @@ snapshots: is-unicode-supported@0.1.0: {} + is-unicode-supported@2.1.0: {} + is-weakmap@2.0.2: {} is-weakref@1.1.1: @@ -10587,6 +10679,11 @@ snapshots: dependencies: path-key: 4.0.0 + npm-run-path@6.0.0: + dependencies: + path-key: 4.0.0 + unicorn-magic: 0.3.0 + npm@10.9.4: {} object-assign@4.1.1: {} @@ -10808,6 +10905,8 @@ snapshots: index-to-position: 1.2.0 type-fest: 4.41.0 + parse-ms@4.0.0: {} + parse-statements@1.0.11: {} parseurl@1.3.3: {} @@ -10922,6 +11021,10 @@ snapshots: prettier@3.6.2: {} + pretty-ms@9.3.0: + dependencies: + parse-ms: 4.0.0 + prismjs@1.30.0: {} proc-log@4.2.0: {} @@ -11492,6 +11595,8 @@ snapshots: strip-bom@3.0.0: {} + strip-final-newline@4.0.0: {} + strip-indent@3.0.0: dependencies: min-indent: 1.0.1 @@ -11731,6 +11836,8 @@ snapshots: undici-types@6.21.0: {} + unicorn-magic@0.3.0: {} + unist-util-is@6.0.1: dependencies: '@types/unist': 3.0.3 @@ -12060,6 +12167,8 @@ snapshots: yoctocolors-cjs@2.1.3: {} + yoctocolors@2.1.2: {} + zip-stream@6.0.1: dependencies: archiver-utils: 5.0.2