diff --git a/packages/b2c-cli/package.json b/packages/b2c-cli/package.json index 47a267db..6760dd6e 100644 --- a/packages/b2c-cli/package.json +++ b/packages/b2c-cli/package.json @@ -32,6 +32,7 @@ "eslint-config-oclif": "^6", "eslint-config-prettier": "^10", "eslint-plugin-prettier": "^5.5.4", + "execa": "^9.6.1", "mocha": "^10", "oclif": "^4", "prettier": "^3.6.2", @@ -117,9 +118,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..0d6cd80a --- /dev/null +++ b/packages/b2c-cli/test/functional/e2e/ods-lifecycle.test.ts @@ -0,0 +1,328 @@ +/* + * 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); + +/** + * Helper function to parse JSON response from CLI + */ +function parseJson(output: string): Record { + try { + // Try to parse the entire output as JSON first + return JSON.parse(output); + } catch { + // If that fails, look for JSON in the output + const lines = output.split('\n'); + for (const line of lines) { + const trimmed = line.trim(); + if (trimmed.startsWith('{') || trimmed.startsWith('[')) { + try { + return JSON.parse(trimmed); + } catch {} + } + } + throw new Error(`No valid JSON found in output: ${output}`); + } +} + +/** + * 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 = parseJson(result.stdout); + return sandbox.state; + } + 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 = parseJson(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; + serverHostname = response.hostName; + + // 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 = parseJson(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.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 = parseJson(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']).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 = parseJson(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 deleted file mode 100644 index f5002e35..00000000 --- a/packages/b2c-cli/test/functional/e2e_cli_test.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env bash -# b2c-cli e2e cli tests -# required env vars -# SFCC_CLIENT_ID -# SFCC_CLIENT_SECRET -# SFCC_SHORTCODE -# TEST_REALM - -set -e - - -# 1. Create on demand sandbox - -# Create ODS will automatically configure webdav and ocapi for the SFCC_CLIENT_ID we're using -# just like sfcc-ci -ODS_CREATE_RESULT=$(../../bin/run.js ods create --realm "$TEST_REALM" --wait --json) - -ODS_ID=$(echo "$ODS_CREATE_RESULT" | jq -r '.[0].id') - -if [ -z "$ODS_ID" ] || [ "$ODS_ID" == "null" ]; then - echo "Failed to create on demand sandbox" - exit 1 -fi - -echo "Created on demand sandbox with ID: $ODS_ID" - -# 2. List on demand sandboxes and verify the created one is present -ODS_LIST_RESULT=$(../../bin/run.js ods list --realm "$TEST_REALM" --json) -ODS_PRESENT=$(echo "$ODS_LIST_RESULT" | jq -r --arg ODS_ID "$ODS_ID" '.[] | select(.id == $ODS_ID) | .id') - -if [ "$ODS_PRESENT" != "$ODS_ID" ]; then - echo "Created on demand sandbox not found in list" - exit 1 -fi - -SERVER=$(echo "$ODS_CREATE_RESULT" | jq -r '.[0].server') - -# 3. Import code into the created sandbox - -IMPORT_RESULT=$(../../bin/run.js code deploy --server --sandbox "$ODS_ID" --source ./test/functional/sample_code --wait --json) 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