diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 0000000..4b8ff70 --- /dev/null +++ b/.github/workflows/playwright.yml @@ -0,0 +1,40 @@ +name: Playwright + +on: + push: + branches: + - main + pull_request: + +permissions: + contents: read + +jobs: + playwright: + name: Playwright + runs-on: ubuntu-latest + env: + WP_BASE_URL: http://localhost:8888 + WP_CLI: npx wp-env run cli wp + steps: + - uses: actions/checkout@v5 + + - uses: actions/setup-node@v4 + with: + node-version: '22' + cache: npm + + - run: npm ci + + - run: npx playwright install --with-deps chromium + + - run: npm run env:start + + - run: npm run test:e2e + + - uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: playwright-report + path: tests/E2E/.report + retention-days: 7 diff --git a/.gitignore b/.gitignore index 7f78132..20e7113 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ /vendor/ /.phpunit.result.cache +node_modules/ +/tests/E2E/.state/ +/tests/E2E/.results/ +/tests/E2E/.report/ diff --git a/.wp-env.json b/.wp-env.json new file mode 100644 index 0000000..e389c93 --- /dev/null +++ b/.wp-env.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://schemas.wp.org/trunk/wp-env.json", + "phpVersion": "8.3", + "plugins": [ + ".", + "https://downloads.wordpress.org/plugin/woocommerce.10.9.4.zip", + "pronamic/woocommerce-subscriptions#v9.0.0" + ], + "config": { + "WP_DEBUG": true, + "WP_DEBUG_DISPLAY": false + }, + "mappings": { + "wp-content/mu-plugins": "./tests/E2E/support/mu-plugins" + } +} diff --git a/README.md b/README.md index 11ed897..aac0f4d 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,33 @@ Customizations at WordPress relative to website libresign.coop - Create the GitHub token and add at configuration page - Set the organization and repository that have the deploy action +## Architecture + +`src/` holds the decisions and `includes/` plus the main plugin file hold the +wiring: the hooks, the options and the effects. A decision receives values and +returns values — the navigation receives the query vars instead of reading +`global $wp`, the webhook gate receives the headers instead of a +`WP_REST_Request` — so it is covered by a data provider and no test double. + +``` +src/ +├── Account/Navigation.php # entries, labels and the active one +├── Account/RootEndpoint.php # account screens served from the site root +├── Github/DeployDispatch.php # when publishing asks GitHub for a deploy +├── Github/SiteDeploy.php # which run publishes the site +├── Github/WebhookDecision.php # what to answer a delivery +├── Github/WebhookGate.php # inspection of a delivery +├── Github/WebhookRequest.php # headers and body of a delivery +├── Github/WebhookSignature.php # the HMAC GitHub signs with +├── Github/WorkflowRun.php # the run a payload describes +├── Settings/Secret.php # the cipher of the token and the secret +└── Subscription/StatusChange.php # changes that ask for a confirmation +``` + +The plugin is installed by cloning the repository, so Composer never runs on the +server: `src/Autoloader.php` maps the namespace to `src/` and is the only file +the plugin requires by hand. + ## Development Every check is a Composer script: @@ -51,19 +78,66 @@ docker exec -w /var/www/html/wp-content/plugins/libresign-wp-customizations \ wordpress-docker-wordpress-1 composer test ``` -`tests/` mirrors the plugin file by file, with `Test.php` appended: -`includes/github-site-webhook.php` is covered by -`tests/Unit/Includes/GithubSiteWebhookTest.php` and -`tests/Integration/Includes/GithubSiteWebhookTest.php`. A file belongs to -`Unit/` when it only feeds values to a function and reads the returned value, -and to `Integration/` when it goes through WordPress: options, hooks, the REST -server or the database. +`tests/Unit/` mirrors `src/` and `tests/Integration/` mirrors the plugin files, +in both cases file by file with `Test.php` appended: +`src/Github/WebhookGate.php` is covered by +`tests/Unit/Github/WebhookGateTest.php`, and the endpoint wiring it serves, +`includes/github-site-webhook.php`, by +`tests/Integration/Includes/GithubSiteWebhookTest.php`. A decision is covered by +a unit test, and the wiring by an integration test going through WordPress: +options, hooks, the REST server or the database. Nothing is mocked. Outgoing HTTP is answered through the `pre_http_request` filter (`tests/Support/FakeHttp.php`), which is WordPress' own extension point, and any request that is not answered that way fails the test instead of reaching the network. -WooCommerce is not installed in the test suite, so the screens that only exist -with WooCommerce loaded — the root account endpoints, the invoice title, the -addresses redirect and the subscription confirmation flow — are still uncovered. +WooCommerce is not installed in this suite, so the screens that only exist with +WooCommerce loaded are covered by the browser tests instead. + +### Browser tests + +`tests/E2E/` mirrors `src/` the same way, with `.spec.ts` in place of +`Test.php`: `src/Account/Navigation.php` is covered end to end by +`tests/E2E/Account/Navigation.spec.ts`. What lives here is what PHPUnit cannot +reach without WooCommerce, the rewrite rules and a browser — the account +navigation, the account screens served from the site root and the confirmation +of a subscription status change. + +The suite runs against a WordPress that is already up, described by four +variables: + +| Variable | Default | +|---|---| +| `WP_BASE_URL` | `http://localhost` | +| `WP_CLI` | `docker exec -i -u www-data wordpress-docker-wordpress-1 wp --path=/var/www/html` | +| `WP_E2E_CUSTOMER_PASSWORD` | `libresign-e2e` | +| `WP_E2E_ALLOW_ANY_SITE` | unset | + +The defaults are the local SaaS stack again, so there it takes no arguments: + +```bash +npm install +npx playwright install chromium +npm run test:e2e +``` + +`tests/E2E/support/seed.php` puts the site in the state the specs expect, and +runs again before each test that changes the subscription. It is destructive and +nothing is restored afterwards: it makes My Account the front page, rebuilds the +rewrite rules, and creates `libresign_e2e_customer` with the password above, +which this repository publishes. That is why it refuses to run against anything +but `localhost` unless `WP_E2E_ALLOW_ANY_SITE=1` says so — point it at a site you +can throw away, never at production or staging. + +Anywhere else, point the first two at the site under test. `.wp-env.json` +describes a disposable one, which is what CI runs: + +```bash +npm run env:start +WP_BASE_URL=http://localhost:8888 WP_CLI="npx wp-env run cli wp" npm run test:e2e +``` + +WooCommerce only offers the billing screen when one of the available gateways +keeps payment methods. The local stack has Stripe for that; `.wp-env.json` maps +a mu-plugin that declares one. diff --git a/composer.json b/composer.json index fa69f39..338c797 100644 --- a/composer.json +++ b/composer.json @@ -21,6 +21,11 @@ "wp-phpunit/wp-phpunit": "^7.0", "yoast/phpunit-polyfills": "^4.0" }, + "autoload": { + "psr-4": { + "LibreSign\\WPCustomizations\\": "src/" + } + }, "autoload-dev": { "psr-4": { "LibreSign\\WPCustomizations\\Tests\\": "tests/" @@ -39,7 +44,7 @@ "wordpress-install-dir": "vendor/wordpress" }, "scripts": { - "lint": "parallel-lint --exclude vendor .", + "lint": "parallel-lint --exclude vendor --exclude node_modules .", "cs": "phpcs", "cs:fix": "phpcbf", "stan": "phpstan analyse", diff --git a/includes/account-navigation.php b/includes/account-navigation.php index ae7e05f..5f4f103 100644 --- a/includes/account-navigation.php +++ b/includes/account-navigation.php @@ -9,6 +9,8 @@ * @package LibreSign_WP_Customizations */ +use LibreSign\WPCustomizations\Account\Navigation; + defined( 'ABSPATH' ) || exit; /** @@ -20,51 +22,11 @@ function libresign_get_current_query_vars() { return isset( $wp->query_vars ) ? $wp->query_vars : array(); } -/** - * Customer navigation entries kept, in display order. - */ -function libresign_get_account_menu_order() { - return array( 'subscriptions', 'orders', 'payment-methods', 'edit-account', 'customer-logout' ); -} - -/** - * Navigation entries renamed because their meaning changed here. - * - * Whatever is left out keeps the WooCommerce label, and with it the translation - * WooCommerce already ships for every locale. - */ -function libresign_get_account_menu_labels() { - return array( - 'subscriptions' => __( 'My subscription', 'libresign-wp-customizations' ), - 'orders' => __( 'Invoices', 'libresign-wp-customizations' ), - 'payment-methods' => __( 'Billing', 'libresign-wp-customizations' ), - ); -} - -/** - * Detail endpoints neither core nor Subscriptions already highlights, mapped to their entry. - */ -function libresign_get_account_menu_item_aliases() { - return array( - 'subscriptions' => array( 'subscription-payment-method' ), - 'payment-methods' => array( 'edit-address' ), - ); -} - /** * Reorder and relabel the My Account navigation, skipping unregistered endpoints. */ function libresign_filter_account_menu_items( $items ) { - $labels = libresign_get_account_menu_labels(); - $menu = array(); - - foreach ( libresign_get_account_menu_order() as $endpoint ) { - if ( isset( $items[ $endpoint ] ) ) { - $menu[ $endpoint ] = isset( $labels[ $endpoint ] ) ? $labels[ $endpoint ] : $items[ $endpoint ]; - } - } - - return $menu; + return Navigation::filter_items( (array) $items ); } add_filter( 'woocommerce_account_menu_items', 'libresign_filter_account_menu_items', 20 ); @@ -72,26 +34,7 @@ function libresign_filter_account_menu_items( $items ) { * Highlight the navigation entry a detail screen belongs to. */ function libresign_filter_account_menu_item_classes( $classes, $endpoint ) { - if ( in_array( 'is-active', $classes, true ) ) { - return $classes; - } - - $aliases = libresign_get_account_menu_item_aliases(); - - if ( empty( $aliases[ $endpoint ] ) ) { - return $classes; - } - - $query_vars = libresign_get_current_query_vars(); - - foreach ( $aliases[ $endpoint ] as $alias ) { - if ( isset( $query_vars[ $alias ] ) ) { - $classes[] = 'is-active'; - break; - } - } - - return $classes; + return Navigation::filter_item_classes( (array) $classes, $endpoint, libresign_get_current_query_vars() ); } add_filter( 'woocommerce_account_menu_item_classes', 'libresign_filter_account_menu_item_classes', 10, 2 ); @@ -99,25 +42,7 @@ function libresign_filter_account_menu_item_classes( $classes, $endpoint ) { * Page title of an endpoint whose navigation label was renamed. */ function libresign_filter_account_endpoint_title( $title, $endpoint ) { - $labels = libresign_get_account_menu_labels(); - - if ( ! isset( $labels[ $endpoint ] ) ) { - return $title; - } - - $query_vars = libresign_get_current_query_vars(); - $page_number = isset( $query_vars[ $endpoint ] ) ? intval( $query_vars[ $endpoint ] ) : 0; - - if ( $page_number < 2 ) { - return $labels[ $endpoint ]; - } - - return sprintf( - /* translators: 1: navigation label, 2: page number */ - __( '%1$s (page %2$d)', 'libresign-wp-customizations' ), - $labels[ $endpoint ], - $page_number - ); + return Navigation::endpoint_title( $title, $endpoint, libresign_get_current_query_vars() ); } /** @@ -144,7 +69,7 @@ function libresign_filter_view_order_endpoint_title( $title ) { * Keep the endpoint page titles in sync with the navigation labels. */ function libresign_register_account_endpoint_titles() { - foreach ( array_keys( libresign_get_account_menu_labels() ) as $endpoint ) { + foreach ( array_keys( Navigation::labels() ) as $endpoint ) { add_filter( 'woocommerce_endpoint_' . $endpoint . '_title', 'libresign_filter_account_endpoint_title', 20, 2 ); } diff --git a/includes/github-site-webhook.php b/includes/github-site-webhook.php index 89a8019..a2e65c3 100644 --- a/includes/github-site-webhook.php +++ b/includes/github-site-webhook.php @@ -5,6 +5,10 @@ * @package LibreSign_WP_Customizations */ +use LibreSign\WPCustomizations\Github\SiteDeploy; +use LibreSign\WPCustomizations\Github\WebhookGate; +use LibreSign\WPCustomizations\Github\WebhookRequest; + defined( 'ABSPATH' ) || exit; const LIBRESIGN_GITHUB_SITE_WEBHOOK_NAMESPACE = 'libresign/v1'; @@ -37,64 +41,13 @@ function libresign_github_site_webhook_endpoint_url() { return rest_url( ltrim( LIBRESIGN_GITHUB_SITE_WEBHOOK_NAMESPACE . LIBRESIGN_GITHUB_SITE_WEBHOOK_ROUTE, '/' ) ); } -/** - * Decrypt an encrypted plugin option value. - * - * @param string $value Raw stored value. - * @return string - */ -function libresign_decrypt_plugin_secret( $value ) { - $value = trim( (string) $value ); - if ( '' === $value ) { - return ''; - } - - $decoded = base64_decode( $value, true ); - if ( false === $decoded ) { - return $value; - } - - $key = hash( 'sha256', AUTH_KEY . SECURE_AUTH_SALT ); - $iv = substr( hash( 'sha256', NONCE_SALT ), 0, 16 ); - - $decrypted = openssl_decrypt( $decoded, 'AES-256-CBC', $key, 0, $iv ); - - return false === $decrypted ? $value : trim( (string) $decrypted ); -} - -/** - * Encrypt a plugin option value, leaving an already encrypted one untouched. - * - * WordPress runs the sanitize callback of an option again when update_option() - * hands a value that does not exist yet to add_option(), so encrypting has to - * be idempotent or the value is stored encrypted twice. - * - * @param string $value Plain text value. - * @return string - */ -function libresign_encrypt_plugin_secret( $value ) { - $value = trim( (string) $value ); - if ( '' === $value ) { - return ''; - } - - if ( libresign_decrypt_plugin_secret( $value ) !== $value ) { - return $value; - } - - $key = hash( 'sha256', AUTH_KEY . SECURE_AUTH_SALT ); - $iv = substr( hash( 'sha256', NONCE_SALT ), 0, 16 ); - - return base64_encode( openssl_encrypt( $value, 'AES-256-CBC', $key, 0, $iv ) ); -} - /** * Resolve the configured GitHub webhook secret. * * @return string */ function libresign_github_webhook_secret() { - return libresign_decrypt_plugin_secret( get_option( 'libresign_github_webhook_secret', '' ) ); + return libresign_plugin_secret()->decrypt( get_option( 'libresign_github_webhook_secret', '' ) ); } /** @@ -141,6 +94,17 @@ function libresign_site_deploy_branch_name() { return '' === $name ? 'gh-pages' : $name; } +/** + * @return SiteDeploy + */ +function libresign_site_deploy() { + return new SiteDeploy( + libresign_site_deploy_repository_name(), + libresign_site_deploy_branch_name(), + libresign_site_deploy_workflow_name() + ); +} + /** * Build a standardized ignored response. * @@ -155,94 +119,6 @@ function libresign_github_site_webhook_ignored_response( $data = array(), $statu return $response; } -/** - * Verify the GitHub webhook HMAC signature. - * - * @param string $body Raw request body. - * @param string $signature Signature header value. - * @param string $secret Shared secret. - * @return bool - */ -function libresign_verify_github_webhook_signature( $body, $signature, $secret ) { - $secret = trim( (string) $secret ); - $signature = trim( (string) $signature ); - - if ( '' === $body || '' === $secret || '' === $signature ) { - return false; - } - - if ( 0 === stripos( $signature, 'sha256=' ) ) { - $signature = substr( $signature, 7 ); - } - - if ( ! ctype_xdigit( $signature ) ) { - return false; - } - - $expected = hash_hmac( 'sha256', $body, $secret ); - - return hash_equals( $expected, strtolower( $signature ) ); -} - -/** - * Check whether the webhook user agent looks like GitHub Hookshot. - * - * @param string $user_agent User agent header. - * @return bool - */ -function libresign_is_github_hookshot_user_agent( $user_agent ) { - return 0 === strpos( trim( (string) $user_agent ), 'GitHub-Hookshot/' ); -} - -/** - * Extract the workflow name from the payload. - * - * @param array $payload Parsed payload. - * @return string - */ -function libresign_site_deploy_workflow_name_from_payload( $payload ) { - $workflow_run_name = isset( $payload['workflow_run']['name'] ) ? trim( (string) $payload['workflow_run']['name'] ) : ''; - if ( '' !== $workflow_run_name ) { - return $workflow_run_name; - } - - $workflow_name = isset( $payload['workflow']['name'] ) ? trim( (string) $payload['workflow']['name'] ) : ''; - - return $workflow_name; -} - -/** - * Determine whether the payload represents the production site deploy event. - * - * @param array $payload Parsed payload. - * @return bool - */ -function libresign_is_production_site_deploy_workflow_run( $payload ) { - $repository = isset( $payload['repository']['full_name'] ) ? trim( (string) $payload['repository']['full_name'] ) : ''; - $action = isset( $payload['action'] ) ? trim( (string) $payload['action'] ) : ''; - $conclusion = isset( $payload['workflow_run']['conclusion'] ) ? trim( (string) $payload['workflow_run']['conclusion'] ) : ''; - $head_branch = isset( $payload['workflow_run']['head_branch'] ) ? trim( (string) $payload['workflow_run']['head_branch'] ) : ''; - $workflow_name = libresign_site_deploy_workflow_name_from_payload( $payload ); - - if ( libresign_site_deploy_repository_name() !== $repository ) { - return false; - } - - if ( 'completed' !== $action ) { - return false; - } - - if ( 'success' !== $conclusion ) { - return false; - } - - if ( libresign_site_deploy_branch_name() !== $head_branch ) { - return false; - } - - return libresign_site_deploy_workflow_name() === $workflow_name; -} - /** * Mark a webhook delivery as processed, returning false when duplicated. * @@ -291,36 +167,27 @@ function libresign_record_site_fragment_sync_result( $status, $payload ) { * @return WP_REST_Response|WP_Error */ function libresign_receive_github_site_deploy_webhook( $request ) { - $secret = libresign_github_webhook_secret(); - if ( '' === $secret ) { - return new WP_Error( - 'libresign_github_webhook_secret_missing', - __( 'The GitHub webhook secret is not configured.', 'libresign-wp-customizations' ), - array( 'status' => 503 ) - ); - } - - $user_agent = (string) $request->get_header( 'user-agent' ); - if ( ! libresign_is_github_hookshot_user_agent( $user_agent ) ) { - return new WP_Error( - 'libresign_github_webhook_invalid_agent', - __( 'The webhook request does not look like a GitHub delivery.', 'libresign-wp-customizations' ), - array( 'status' => 403 ) - ); - } + $webhook_gate = new WebhookGate( libresign_github_webhook_secret(), libresign_site_deploy() ); + + $decision = $webhook_gate->decide( + new WebhookRequest( + (string) $request->get_header( 'user-agent' ), + (string) $request->get_header( 'x-github-event' ), + (string) $request->get_header( 'x-hub-signature-256' ), + (string) $request->get_header( 'x-github-delivery' ), + (string) $request->get_body() + ) + ); - $body = (string) $request->get_body(); - $signature = (string) $request->get_header( 'x-hub-signature-256' ); - if ( ! libresign_verify_github_webhook_signature( $body, $signature, $secret ) ) { + if ( $decision->is_rejected() ) { return new WP_Error( - 'libresign_github_webhook_invalid_signature', - __( 'Invalid GitHub webhook signature.', 'libresign-wp-customizations' ), - array( 'status' => 403 ) + $decision->code(), + $decision->message(), + array( 'status' => $decision->status() ) ); } - $event = strtolower( trim( (string) $request->get_header( 'x-github-event' ) ) ); - if ( 'ping' === $event ) { + if ( $decision->is_pong() ) { return rest_ensure_response( array( 'status' => 'pong', @@ -329,37 +196,13 @@ function libresign_receive_github_site_deploy_webhook( $request ) { ); } - if ( 'workflow_run' !== $event ) { - return libresign_github_site_webhook_ignored_response( - array( - 'reason' => 'unsupported_event', - 'event' => $event, - ) - ); - } - - $payload = json_decode( $body, true ); - if ( ! is_array( $payload ) ) { - return new WP_Error( - 'libresign_github_webhook_invalid_payload', - __( 'The GitHub webhook payload must be valid JSON.', 'libresign-wp-customizations' ), - array( 'status' => 400 ) - ); + if ( $decision->is_ignored() ) { + return libresign_github_site_webhook_ignored_response( $decision->data() ); } - if ( ! libresign_is_production_site_deploy_workflow_run( $payload ) ) { - return libresign_github_site_webhook_ignored_response( - array( - 'reason' => 'not_production_deploy', - 'repository' => isset( $payload['repository']['full_name'] ) ? (string) $payload['repository']['full_name'] : '', - 'workflow_name' => libresign_site_deploy_workflow_name_from_payload( $payload ), - 'head_branch' => isset( $payload['workflow_run']['head_branch'] ) ? (string) $payload['workflow_run']['head_branch'] : '', - 'conclusion' => isset( $payload['workflow_run']['conclusion'] ) ? (string) $payload['workflow_run']['conclusion'] : '', - ) - ); - } + $workflow_run = $decision->workflow_run(); + $delivery_id = (string) $request->get_header( 'x-github-delivery' ); - $delivery_id = (string) $request->get_header( 'x-github-delivery' ); if ( ! libresign_mark_github_delivery_once( $delivery_id ) ) { return libresign_github_site_webhook_ignored_response( array( @@ -369,14 +212,13 @@ function libresign_receive_github_site_deploy_webhook( $request ) { ); } - $workflow_run = isset( $payload['workflow_run'] ) && is_array( $payload['workflow_run'] ) ? $payload['workflow_run'] : array(); - $sync_result = libresign_sync_site_fragments_from_origin( + $sync_result = libresign_sync_site_fragments_from_origin( libresign_site_origin(), array( 'header', 'footer' ), array( - 'generated_at' => isset( $workflow_run['updated_at'] ) ? (string) $workflow_run['updated_at'] : current_time( 'mysql', true ), - 'source_sha' => isset( $workflow_run['head_sha'] ) ? (string) $workflow_run['head_sha'] : '', - 'source_url' => isset( $workflow_run['html_url'] ) ? (string) $workflow_run['html_url'] : '', + 'generated_at' => '' === $workflow_run->updated_at() ? current_time( 'mysql', true ) : $workflow_run->updated_at(), + 'source_sha' => $workflow_run->head_sha(), + 'source_url' => $workflow_run->html_url(), ) ); @@ -396,11 +238,11 @@ function libresign_receive_github_site_deploy_webhook( $request ) { 'synced', array( 'delivery_id' => $delivery_id, - 'repository' => libresign_site_deploy_repository_name(), - 'workflow' => libresign_site_deploy_workflow_name_from_payload( $payload ), - 'head_branch' => isset( $workflow_run['head_branch'] ) ? (string) $workflow_run['head_branch'] : '', - 'source_sha' => isset( $workflow_run['head_sha'] ) ? (string) $workflow_run['head_sha'] : '', - 'source_url' => isset( $workflow_run['html_url'] ) ? (string) $workflow_run['html_url'] : '', + 'repository' => $workflow_run->repository(), + 'workflow' => $workflow_run->workflow_name(), + 'head_branch' => $workflow_run->head_branch(), + 'source_sha' => $workflow_run->head_sha(), + 'source_url' => $workflow_run->html_url(), 'synced' => $sync_result['synced'], ) ); @@ -409,8 +251,8 @@ function libresign_receive_github_site_deploy_webhook( $request ) { array( 'status' => 'synced', 'delivery_id' => $delivery_id, - 'repository' => libresign_site_deploy_repository_name(), - 'workflow' => libresign_site_deploy_workflow_name_from_payload( $payload ), + 'repository' => $workflow_run->repository(), + 'workflow' => $workflow_run->workflow_name(), 'origin' => $sync_result['origin'], 'synced' => $sync_result['synced'], ) diff --git a/libresign-wp-customizations.php b/libresign-wp-customizations.php index d239b4f..e9df804 100644 --- a/libresign-wp-customizations.php +++ b/libresign-wp-customizations.php @@ -23,11 +23,28 @@ * GitHub Plugin URI: https://github.com/LibreSign/libresign-wp-customizations */ +use LibreSign\WPCustomizations\Account\RootEndpoint; +use LibreSign\WPCustomizations\Autoloader; +use LibreSign\WPCustomizations\Github\DeployDispatch; +use LibreSign\WPCustomizations\Settings\Secret; +use LibreSign\WPCustomizations\Subscription\StatusChange; + defined( 'ABSPATH' ) || exit; +require_once __DIR__ . '/src/Autoloader.php'; + +Autoloader::register(); + const LIBRESIGN_WP_REWRITE_VERSION = '9'; const LIBRESIGN_FAQ_URL = 'https://libresign.coop/faq/'; +/** + * Cipher of the settings stored encrypted. + */ +function libresign_plugin_secret() { + return Secret::from_salts( AUTH_KEY, SECURE_AUTH_SALT, NONCE_SALT ); +} + /** * Get the WooCommerce My Account page ID. */ @@ -124,59 +141,65 @@ function libresign_wp_add_noindex_meta_tag() { * Deploy the site after change the status of post */ function libresign_trigger_github_action_on_publish($new_status, $old_status, $post) { - if ($post->post_type === 'post' && ($new_status === 'publish' || $old_status === 'publish')) { - $encripted = get_option('libresign_github_deploy_token'); - $key = hash('sha256', AUTH_KEY . SECURE_AUTH_SALT); - $iv = substr(hash('sha256', NONCE_SALT), 0, 16); - $deploy_token = openssl_decrypt(base64_decode($encripted), 'AES-256-CBC', $key, 0, $iv); - $organizationRepository = get_option('libresign_github_deploy_organization_repository'); - $response = wp_remote_post('https://api.github.com/repos/' . $organizationRepository . '/dispatches', [ - 'body' => json_encode([ - 'event_type' => 'deploy-site', - ]), - 'headers' => [ - 'Authorization' => 'Bearer ' . $deploy_token, - 'Accept' => 'application/vnd.github+json', - 'User-Agent' => 'WordPress Hook', - ], - 'timeout' => 15, - ]); + if (!DeployDispatch::triggers_deploy($new_status, $old_status, $post->post_type)) { + return; + } - $post_data = [ - 'post_id' => $post->ID, - 'post_title' => get_the_title($post->ID), - 'language' => function_exists('pll_get_post_language') ? pll_get_post_language($post->ID) : 'indefinido', - ]; + $deploy_token = libresign_plugin_secret()->decrypt_or_discard(get_option('libresign_github_deploy_token')); + if ($deploy_token === '') { + return; + } + + $organizationRepository = get_option('libresign_github_deploy_organization_repository'); + $response = wp_remote_post('https://api.github.com/repos/' . $organizationRepository . '/dispatches', [ + 'body' => wp_json_encode([ + 'event_type' => 'deploy-site', + ]), + 'headers' => [ + 'Authorization' => 'Bearer ' . $deploy_token, + 'Accept' => 'application/vnd.github+json', + 'User-Agent' => 'WordPress Hook', + ], + 'timeout' => 15, + ]); + + $post_data = [ + 'post_id' => $post->ID, + 'post_title' => get_the_title($post->ID), + 'language' => function_exists('pll_get_post_language') ? pll_get_post_language($post->ID) : 'indefinido', + ]; - if (is_wp_error($response)) { + if (is_wp_error($response)) { + $post_data = array_merge($post_data, [ + 'type' => 'error', + 'message' => $response->get_error_message(), + ]); + } else { + $response_code = (int) wp_remote_retrieve_response_code($response); + if ($response_code === 204) { $post_data = array_merge($post_data, [ - 'type' => 'error', - 'message' => $response->get_error_message(), + 'type' => 'success', + 'message' => DeployDispatch::success_message($organizationRepository), ]); } else { - $code = wp_remote_retrieve_response_code($response); - if ($code === 204) { - $post_data = array_merge($post_data, [ - 'type' => 'success', - 'message' => 'Ação de deploy enviada com sucesso. Acompanhe aqui', - ]); - } else { - $body = json_decode($response['body'], true); - $post_data = array_merge($post_data, [ - 'type' => 'error', - 'message' => "Erro ao acionar deploy.
Código: $code.
Message: {$body['message']}", - ]); - } + $response_body = json_decode(wp_remote_retrieve_body($response), true); + $post_data = array_merge($post_data, [ + 'type' => 'error', + 'message' => DeployDispatch::failure_message( + $response_code, + isset($response_body['message']) ? $response_body['message'] : '' + ), + ]); } + } - $transient_key = 'libresign_github_action_status_' . get_current_user_id(); - $status_list = get_transient($transient_key); - if (!is_array($status_list)) { - $status_list = []; - } - $status_list[] = $post_data; - set_transient($transient_key, $status_list, 60); + $transient_key = 'libresign_github_action_status_' . get_current_user_id(); + $status_list = get_transient($transient_key); + if (!is_array($status_list)) { + $status_list = []; } + $status_list[] = $post_data; + set_transient($transient_key, $status_list, 60); } add_action('transition_post_status', 'libresign_trigger_github_action_on_publish', 10, 3); @@ -384,14 +407,29 @@ class="regular-text" } /** - * Encode the deploy token at database + * Encrypt a setting before it is stored. + * + * The field is rendered empty on every visit, so a submission with nothing in + * it means the value was left alone and the stored one is kept. + */ +function libresign_encrypt_setting($value, $option) { + $value = trim((string) $value); + + if ('' === $value) { + return get_option($option); + } + + return libresign_plugin_secret()->encrypt($value); +} + +/** + * Register the settings of the configuration page. */ -add_action('admin_init', function () { +function libresign_register_settings() { register_setting('libresign_settings_group', 'libresign_github_deploy_token', [ 'type' => 'string', 'sanitize_callback' => function ($value) { - $encrypted = libresign_encrypt_plugin_secret($value); - return '' === $encrypted ? get_option('libresign_github_deploy_token') : $encrypted; + return libresign_encrypt_setting($value, 'libresign_github_deploy_token'); }, ]); register_setting('libresign_settings_group', 'libresign_github_deploy_organization_repository', [ @@ -401,8 +439,7 @@ class="regular-text" register_setting('libresign_settings_group', 'libresign_github_webhook_secret', [ 'type' => 'string', 'sanitize_callback' => function ($value) { - $encrypted = libresign_encrypt_plugin_secret($value); - return '' === $encrypted ? get_option('libresign_github_webhook_secret') : $encrypted; + return libresign_encrypt_setting($value, 'libresign_github_webhook_secret'); }, ]); register_setting('libresign_settings_group', 'libresign_site_origin', [ @@ -426,7 +463,8 @@ class="regular-text" return '' === $value ? 'gh-pages' : $value; }, ]); -}); +} +add_action('admin_init', 'libresign_register_settings'); /** * Register WooCommerce account endpoints at the site root when the account page is the front page. @@ -616,28 +654,12 @@ function libresign_is_root_my_account_endpoint_request() { return false; } - $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_url( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : ''; - $request_path = trim( (string) wp_parse_url( $request_uri, PHP_URL_PATH ), '/' ); - - if ( '' === $request_path ) { - return false; - } - - $segments = explode( '/', $request_path ); - $first_slug = reset( $segments ); - $query_vars = WC()->query->get_query_vars(); - - if ( 'my-account' === $first_slug ) { - return true; - } - - foreach ( $query_vars as $query_var ) { - if ( ! empty( $query_var ) && $query_var === $first_slug ) { - return true; - } - } + $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_url( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : ''; - return false; + return RootEndpoint::matches( + (string) wp_parse_url( $request_uri, PHP_URL_PATH ), + WC()->query->get_query_vars() + ); } /** @@ -724,26 +746,6 @@ function libresign_render_nextcloud_account_button() { } add_action( 'woocommerce_before_account_navigation', 'libresign_render_nextcloud_account_button', 20 ); -/** - * Confirmation strings for each subscription status change that requires an extra step. - */ -function libresign_get_subscription_confirmation_strings( $new_status ) { - $strings = array( - 'cancelled' => array( - 'question' => __( 'Are you sure you want to cancel your subscription? This action cannot be undone.', 'libresign-wp-customizations' ), - 'confirm' => __( 'Yes, cancel subscription', 'libresign-wp-customizations' ), - 'dismiss' => __( 'No, keep subscription', 'libresign-wp-customizations' ), - ), - 'active' => array( - 'question' => __( 'Are you sure you want to reactivate your subscription?', 'libresign-wp-customizations' ), - 'confirm' => __( 'Yes, reactivate subscription', 'libresign-wp-customizations' ), - 'dismiss' => __( 'No, go back', 'libresign-wp-customizations' ), - ), - ); - - return $strings[ $new_status ] ?? null; -} - /** * Resolve a subscription the current user is allowed to update to the given status, or null. */ @@ -781,7 +783,7 @@ function libresign_intercept_subscription_status_change() { $new_status = sanitize_text_field( wp_unslash( $_GET['change_subscription_to'] ) ); - if ( ! libresign_get_subscription_confirmation_strings( $new_status ) ) { + if ( ! StatusChange::confirmation_strings( $new_status ) ) { return; } @@ -823,7 +825,7 @@ function libresign_render_subscription_change_confirmation() { } $new_status = sanitize_text_field( wp_unslash( $_GET['libresign_confirm_change'] ) ); - $strings = libresign_get_subscription_confirmation_strings( $new_status ); + $strings = StatusChange::confirmation_strings( $new_status ); if ( ! $strings ) { return; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..7256f2d --- /dev/null +++ b/package-lock.json @@ -0,0 +1,5385 @@ +{ + "name": "libresign-wp-customizations", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "libresign-wp-customizations", + "license": "GPL-2.0-or-later", + "devDependencies": { + "@playwright/test": "^1.50.0", + "@wordpress/env": "^10.16.0" + } + }, + "node_modules/@inquirer/ansi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", + "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/checkbox": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.3.2.tgz", + "integrity": "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/confirm": { + "version": "5.1.21", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.21.tgz", + "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", + "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/editor": { + "version": "4.2.23", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.23.tgz", + "integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/external-editor": "^1.0.3", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/expand": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.23.tgz", + "integrity": "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/input": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.3.1.tgz", + "integrity": "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/number": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.23.tgz", + "integrity": "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/password": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.23.tgz", + "integrity": "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/prompts": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.10.1.tgz", + "integrity": "sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^4.3.2", + "@inquirer/confirm": "^5.1.21", + "@inquirer/editor": "^4.2.23", + "@inquirer/expand": "^4.0.23", + "@inquirer/input": "^4.3.1", + "@inquirer/number": "^3.0.23", + "@inquirer/password": "^4.0.23", + "@inquirer/rawlist": "^4.1.11", + "@inquirer/search": "^3.2.2", + "@inquirer/select": "^4.4.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/rawlist": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.11.tgz", + "integrity": "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/search": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.2.2.tgz", + "integrity": "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/select": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.4.2.tgz", + "integrity": "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", + "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@kwsites/file-exists": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", + "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1" + } + }, + "node_modules/@kwsites/file-exists/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@kwsites/file-exists/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/@kwsites/promise-deferred": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", + "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nodable/entities": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-3.0.0.tgz", + "integrity": "sha512-8L9xFeTYKhm49xfIypoe2W5wV1m/3Z58kT+7kR9A8OyFxcPduI4VmxaUMQyKYrRjUoLLSXv6EKKID5Tvj9cUVw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/nodable" + } + ], + "license": "MIT" + }, + "node_modules/@octokit/app": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@octokit/app/-/app-14.1.0.tgz", + "integrity": "sha512-g3uEsGOQCBl1+W1rgfwoRFUIR6PtvB2T1E4RpygeUU5LrLvlOqcxrt5lfykIeRpUPpupreGJUYl70fqMDXdTpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/auth-app": "^6.0.0", + "@octokit/auth-unauthenticated": "^5.0.0", + "@octokit/core": "^5.0.0", + "@octokit/oauth-app": "^6.0.0", + "@octokit/plugin-paginate-rest": "^9.0.0", + "@octokit/types": "^12.0.0", + "@octokit/webhooks": "^12.0.4" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-app": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-6.1.4.tgz", + "integrity": "sha512-QkXkSOHZK4dA5oUqY5Dk3S+5pN2s1igPjEASNQV8/vgJgW034fQWR16u7VsNOK/EljA00eyjYF5mWNxWKWhHRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-app": "^7.1.0", + "@octokit/auth-oauth-user": "^4.1.0", + "@octokit/request": "^8.3.1", + "@octokit/request-error": "^5.1.0", + "@octokit/types": "^13.1.0", + "deprecation": "^2.3.1", + "lru-cache": "npm:@wolfy1339/lru-cache@^11.0.2-patch.1", + "universal-github-app-jwt": "^1.1.2", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-app/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/auth-app/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/auth-oauth-app": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-7.1.0.tgz", + "integrity": "sha512-w+SyJN/b0l/HEb4EOPRudo7uUOSW51jcK1jwLa+4r7PA8FPFpoxEnHBHMITqCsc/3Vo2qqFjgQfz/xUUvsSQnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-device": "^6.1.0", + "@octokit/auth-oauth-user": "^4.1.0", + "@octokit/request": "^8.3.1", + "@octokit/types": "^13.0.0", + "@types/btoa-lite": "^1.0.0", + "btoa-lite": "^1.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-oauth-app/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/auth-oauth-app/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/auth-oauth-device": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-6.1.0.tgz", + "integrity": "sha512-FNQ7cb8kASufd6Ej4gnJ3f1QB5vJitkoV1O0/g6e6lUsQ7+VsSNRHRmFScN2tV4IgKA12frrr/cegUs0t+0/Lw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/oauth-methods": "^4.1.0", + "@octokit/request": "^8.3.1", + "@octokit/types": "^13.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-oauth-device/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/auth-oauth-device/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/auth-oauth-user": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-4.1.0.tgz", + "integrity": "sha512-FrEp8mtFuS/BrJyjpur+4GARteUCrPeR/tZJzD8YourzoVhRics7u7we/aDcKv+yywRNwNi/P4fRi631rG/OyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-device": "^6.1.0", + "@octokit/oauth-methods": "^4.1.0", + "@octokit/request": "^8.3.1", + "@octokit/types": "^13.0.0", + "btoa-lite": "^1.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-oauth-user/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/auth-oauth-user/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/auth-token": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", + "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-unauthenticated": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@octokit/auth-unauthenticated/-/auth-unauthenticated-5.0.1.tgz", + "integrity": "sha512-oxeWzmBFxWd+XolxKTc4zr+h3mt+yofn4r7OfoIkR/Cj/o70eEGmPsFbueyJE2iBAGpjgTnEOKM3pnuEGVmiqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/request-error": "^5.0.0", + "@octokit/types": "^12.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/core": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.2.tgz", + "integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/auth-token": "^4.0.0", + "@octokit/graphql": "^7.1.0", + "@octokit/request": "^8.4.1", + "@octokit/request-error": "^5.1.1", + "@octokit/types": "^13.0.0", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/core/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/core/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/endpoint": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz", + "integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/endpoint/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/endpoint/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/graphql": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.1.tgz", + "integrity": "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/request": "^8.4.1", + "@octokit/types": "^13.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/graphql/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/graphql/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/oauth-app": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@octokit/oauth-app/-/oauth-app-6.1.0.tgz", + "integrity": "sha512-nIn/8eUJ/BKUVzxUXd5vpzl1rwaVxMyYbQkNZjHrF7Vk/yu98/YDF/N2KeWO7uZ0g3b5EyiFXFkZI8rJ+DH1/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-app": "^7.0.0", + "@octokit/auth-oauth-user": "^4.0.0", + "@octokit/auth-unauthenticated": "^5.0.0", + "@octokit/core": "^5.0.0", + "@octokit/oauth-authorization-url": "^6.0.2", + "@octokit/oauth-methods": "^4.0.0", + "@types/aws-lambda": "^8.10.83", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/oauth-authorization-url": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-6.0.2.tgz", + "integrity": "sha512-CdoJukjXXxqLNK4y/VOiVzQVjibqoj/xHgInekviUJV73y/BSIcwvJ/4aNHPBPKcPWFnd4/lO9uqRV65jXhcLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/oauth-methods": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-4.1.0.tgz", + "integrity": "sha512-4tuKnCRecJ6CG6gr0XcEXdZtkTDbfbnD5oaHBmLERTjTMZNi2CbfEHZxPU41xXLDG4DfKf+sonu00zvKI9NSbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/oauth-authorization-url": "^6.0.2", + "@octokit/request": "^8.3.1", + "@octokit/request-error": "^5.1.0", + "@octokit/types": "^13.0.0", + "btoa-lite": "^1.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/oauth-methods/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/oauth-methods/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", + "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-graphql": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-graphql/-/plugin-paginate-graphql-4.0.1.tgz", + "integrity": "sha512-R8ZQNmrIKKpHWC6V2gum4x9LG2qF1RxRjo27gjQcG3j+vf2tLsEfE7I/wRWEPzYMaenr1M+qDAtNcwZve1ce1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=5" + } + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.2.tgz", + "integrity": "sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^12.6.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.4.1.tgz", + "integrity": "sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^12.6.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/@octokit/plugin-retry": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-6.1.0.tgz", + "integrity": "sha512-WrO3bvq4E1Xh1r2mT9w6SDFg01gFmP81nIG77+p/MqW1JeXXgL++6umim3t6x0Zj5pZm3rXAN+0HEjmmdhIRig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/request-error": "^5.0.0", + "@octokit/types": "^13.0.0", + "bottleneck": "^2.15.3" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/@octokit/plugin-retry/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/plugin-retry/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/plugin-throttling": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-8.2.0.tgz", + "integrity": "sha512-nOpWtLayKFpgqmgD0y3GqXafMFuKcA4tRPZIfu7BArd2lEZeb1988nhWhwx4aZWmjDmUfdgVf7W+Tt4AmvRmMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^12.2.0", + "bottleneck": "^2.15.3" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "^5.0.0" + } + }, + "node_modules/@octokit/request": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.1.tgz", + "integrity": "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^9.0.6", + "@octokit/request-error": "^5.1.1", + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/request-error": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.1.tgz", + "integrity": "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.1.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/request-error/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/request-error/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/request/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/request/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/types": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", + "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^20.0.0" + } + }, + "node_modules/@octokit/webhooks": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-12.3.2.tgz", + "integrity": "sha512-exj1MzVXoP7xnAcAB3jZ97pTvVPkQF9y6GA/dvYC47HV7vLv+24XRS6b/v/XnyikpEuvMhugEXdGtAlU086WkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/request-error": "^5.0.0", + "@octokit/webhooks-methods": "^4.1.0", + "@octokit/webhooks-types": "7.6.1", + "aggregate-error": "^3.1.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/webhooks-methods": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-4.1.0.tgz", + "integrity": "sha512-zoQyKw8h9STNPqtm28UGOYFE7O6D4Il8VJwhAtMHFt2C4L0VQT1qGKLeefUOqHNs1mNRYSadVv7x0z8U2yyeWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/webhooks-types": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@octokit/webhooks-types/-/webhooks-types-7.6.1.tgz", + "integrity": "sha512-S8u2cJzklBC0FgTwWVLaM8tMrDuDMVE4xiTK4EYXM9GntyvrdbSoxqDQa+Fh57CCNApyIpyeqPhhFEmHPfrXgw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "license": "MIT" + }, + "node_modules/@php-wasm/cli-util": { + "version": "3.1.54", + "resolved": "https://registry.npmjs.org/@php-wasm/cli-util/-/cli-util-3.1.54.tgz", + "integrity": "sha512-FFDZDzIJz0cePI2NlWIEC1OZxZvifi5EmyPik8Qh1z+H2mVPyMjgi1xYlQXTLc/J0CPH427VfmOMwy9XqpVY7Q==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/util": "3.1.54", + "fast-xml-parser": "^5.8.0", + "jsonc-parser": "3.3.1" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/logger": { + "version": "3.1.54", + "resolved": "https://registry.npmjs.org/@php-wasm/logger/-/logger-3.1.54.tgz", + "integrity": "sha512-ZrgBSSHitJkR+DU1U5dz/ezXzvU9PlhNT2GTSQJnRliNVPba84G0hudRpmnT+CH4aVThAefE3PazpGD/SG1kWA==", + "dev": true, + "license": "GPL-2.0-or-later", + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/node": { + "version": "3.1.54", + "resolved": "https://registry.npmjs.org/@php-wasm/node/-/node-3.1.54.tgz", + "integrity": "sha512-7OuYnmNCiMgH01JOPAxX1QqDut66+Wz140O8AM1GZVKismjLJhxlL+vx2vDJjUQH6puvhgFwK2QOU4wpWDSUkw==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/cli-util": "3.1.54", + "@php-wasm/logger": "3.1.54", + "@php-wasm/node-5-2": "3.1.54", + "@php-wasm/node-7-4": "3.1.54", + "@php-wasm/node-8-0": "3.1.54", + "@php-wasm/node-8-1": "3.1.54", + "@php-wasm/node-8-2": "3.1.54", + "@php-wasm/node-8-3": "3.1.54", + "@php-wasm/node-8-4": "3.1.54", + "@php-wasm/node-8-5": "3.1.54", + "@php-wasm/universal": "3.1.54", + "@php-wasm/util": "3.1.54", + "fs-ext-extra-prebuilt": "2.2.7", + "wasm-feature-detect": "1.8.0", + "ws": "8.21.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/node-5-2": { + "version": "3.1.54", + "resolved": "https://registry.npmjs.org/@php-wasm/node-5-2/-/node-5-2-3.1.54.tgz", + "integrity": "sha512-dqGbGACtrUYn7F3urhFsXwY8SIMdok5iqzDOsi766C8RDHHEZV1+INVaDdPVkgFfwpszfQTHrRRareqNlUWS9g==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/universal": "3.1.54", + "wasm-feature-detect": "1.8.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/node-7-4": { + "version": "3.1.54", + "resolved": "https://registry.npmjs.org/@php-wasm/node-7-4/-/node-7-4-3.1.54.tgz", + "integrity": "sha512-X9i3UCopzF1sfZ2Ix50cT06LTZgAYbpi1g9mlhrDE5+JyyezV/YmJzY/8BIeiD1VN80rr26um+k+2CYqsgabxQ==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/universal": "3.1.54", + "wasm-feature-detect": "1.8.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/node-8-0": { + "version": "3.1.54", + "resolved": "https://registry.npmjs.org/@php-wasm/node-8-0/-/node-8-0-3.1.54.tgz", + "integrity": "sha512-wh57ixwcacO6rqKir6Cp5NyYbncIqyt3sUD7gdXVt9/ZGI5q5ysuR5UQ4mfERKBA2rfnaCpYKpQgVPmrs92oaQ==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/universal": "3.1.54", + "wasm-feature-detect": "1.8.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/node-8-1": { + "version": "3.1.54", + "resolved": "https://registry.npmjs.org/@php-wasm/node-8-1/-/node-8-1-3.1.54.tgz", + "integrity": "sha512-MLJ9lLGMnfitXwf6diQYJNG+k7Pv3Fzh/ZcbYsqwdITBPd4gy05BDwirIoboh3tvu3NLMhzydVz4dpJ5UYbSKw==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/universal": "3.1.54", + "wasm-feature-detect": "1.8.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/node-8-2": { + "version": "3.1.54", + "resolved": "https://registry.npmjs.org/@php-wasm/node-8-2/-/node-8-2-3.1.54.tgz", + "integrity": "sha512-a01+IU7ZPxYPvgwnIYzC6ob4Eu0MFwbQ1OH0JKzm/pdFnaiRNC3bvHF4bp3pjaL6266vb8D3ojBvP3hMYiX3AA==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/universal": "3.1.54", + "wasm-feature-detect": "1.8.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/node-8-3": { + "version": "3.1.54", + "resolved": "https://registry.npmjs.org/@php-wasm/node-8-3/-/node-8-3-3.1.54.tgz", + "integrity": "sha512-vZhfYqySKtpxb1+JvOHdGvZG82vhD7WgH5PYm7xjOYxQiXftqOtBLjwN5ZPg4562MMKrWFOLKHnW2JrxtvfyoQ==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/universal": "3.1.54", + "wasm-feature-detect": "1.8.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/node-8-4": { + "version": "3.1.54", + "resolved": "https://registry.npmjs.org/@php-wasm/node-8-4/-/node-8-4-3.1.54.tgz", + "integrity": "sha512-I0CKNOqxq2MfW+GDiKnKRMdVKZlYqtkHtmnAHbdMDBoE72DO73m/79z7CI0HNzVcQsHfCkbXj2XxS3/yEztjkA==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/universal": "3.1.54", + "wasm-feature-detect": "1.8.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/node-8-5": { + "version": "3.1.54", + "resolved": "https://registry.npmjs.org/@php-wasm/node-8-5/-/node-8-5-3.1.54.tgz", + "integrity": "sha512-n0R+XHuv7rkCmZ+y156khHjrni1Ij/jqVU41MDsie/aCC+gRqBLM+BXOOJ5CSejkFd0pj83kZcFJHrqVy8I/nw==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/universal": "3.1.54", + "wasm-feature-detect": "1.8.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/progress": { + "version": "3.1.54", + "resolved": "https://registry.npmjs.org/@php-wasm/progress/-/progress-3.1.54.tgz", + "integrity": "sha512-JBxgmvy8zUHSl1l7DI5b0dFNp0ZhqdSRunPey6TLbKbM3wZ1Gh6XLYlu+hF/ZVOySnC5NKidg67W1IdgtRPZEg==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/logger": "3.1.54" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/scopes": { + "version": "3.1.54", + "resolved": "https://registry.npmjs.org/@php-wasm/scopes/-/scopes-3.1.54.tgz", + "integrity": "sha512-H2nmmCtrStLyUnAhTr3gmvwcOIZR4hGgL09IVs0BkFMv3bwyAKBrrmf7tSsmf7++ACC8pGX2GkbzvVdlb5GkbA==", + "dev": true, + "license": "GPL-2.0-or-later", + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/stream-compression": { + "version": "3.1.54", + "resolved": "https://registry.npmjs.org/@php-wasm/stream-compression/-/stream-compression-3.1.54.tgz", + "integrity": "sha512-+HDzZh+vzDqBNv4ggCfDz4qgigheNhRtNpsVLBI/NqxuPN3lCTvrWgswliPBZg5AA+iKkGGoaoj/IMOR+AFgeQ==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/util": "3.1.54" + } + }, + "node_modules/@php-wasm/universal": { + "version": "3.1.54", + "resolved": "https://registry.npmjs.org/@php-wasm/universal/-/universal-3.1.54.tgz", + "integrity": "sha512-ZHBzAFhLQJRXnF5rjd3DsbDnf2YZhlEQDf0g3M8mIylzYVX/NZk3en3v/hKA3vhaMVxg6KIr2URA2mzPKKboYg==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/logger": "3.1.54", + "@php-wasm/progress": "3.1.54", + "@php-wasm/stream-compression": "3.1.54", + "@php-wasm/util": "3.1.54", + "ini": "4.1.2" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/util": { + "version": "3.1.54", + "resolved": "https://registry.npmjs.org/@php-wasm/util/-/util-3.1.54.tgz", + "integrity": "sha512-UdDiYnXDJ6g+mamRukQ4k0UoJBq9BQdaXpSemqfNAqX8CHfjuhW9T6iMJCCtLwY0aXvKc8a4qVoGQrrc+SQHow==", + "dev": true, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/web-service-worker": { + "version": "3.1.54", + "resolved": "https://registry.npmjs.org/@php-wasm/web-service-worker/-/web-service-worker-3.1.54.tgz", + "integrity": "sha512-3DQ6LLmVYiqC1gauyXhbLlns6NMo8pLKBGOf1hpKH+RzGA8pwAMyD0jvFPAo6C/62Xp1A//Cqt0+mYhE94Z6Hg==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/scopes": "3.1.54", + "@php-wasm/universal": "3.1.54" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/xdebug-bridge": { + "version": "3.1.54", + "resolved": "https://registry.npmjs.org/@php-wasm/xdebug-bridge/-/xdebug-bridge-3.1.54.tgz", + "integrity": "sha512-Fm6LchCegBPAWrcom6iiWG3ZgmMO6D+ROJ7jpz2gF8W5AmQ7rMI5sKp8l5A2V0ObWabU+uTsuMB5lGr+e0XTNQ==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/logger": "3.1.54", + "@php-wasm/universal": "3.1.54", + "ws": "8.21.0", + "xml2js": "0.6.2", + "yargs": "17.7.2" + }, + "bin": { + "xdebug-bridge": "xdebug-bridge.js" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/xdebug-bridge/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@php-wasm/xdebug-bridge/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@php-wasm/xdebug-bridge/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@playwright/test": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.63.0.tgz", + "integrity": "sha512-oxMK4vllB9RK5NQ2l1pq1IfOf2AvnEuj/vYGDj0H2nMtmtZpKtCwt/l00GEO6xjGfpBNAvjovvYdCm50dRQkpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.63.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@simple-git/args-pathspec": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@simple-git/args-pathspec/-/args-pathspec-1.0.3.tgz", + "integrity": "sha512-ngJMaHlsWDTfjyq9F3VIQ8b7NXbBLq5j9i5bJ6XLYtD6qlDXT7fdKY2KscWWUF8t18xx052Y/PUO1K1TRc9yKA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@simple-git/argv-parser": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@simple-git/argv-parser/-/argv-parser-1.1.1.tgz", + "integrity": "sha512-Q9lBcfQ+VQCpQqGJFHe5yooOS5hGdLFFbJ5R+R5aDsnkPCahtn1hSkMcORX65J2Z5lxSkD0lQorMsncuBQxYUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@simple-git/args-pathspec": "^1.0.3" + } + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@types/aws-lambda": { + "version": "8.10.163", + "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.163.tgz", + "integrity": "sha512-+4zuoEB3S8RIhimtOFT7zAEk2SbpwrKjjGl9CyYnQR6k08uynxfauIIB0pDU1ssr05F8oyGiETwpn+8eXZMqPw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/btoa-lite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/btoa-lite/-/btoa-lite-1.0.2.tgz", + "integrity": "sha512-ZYbcE2x7yrvNFJiU7xJGrpF/ihpkM7zKgw8bha3LNJSesvTtUNxbpzaT7WXBIryf6jovisrxTBvymxMeLLj1Mg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.20.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.3.tgz", + "integrity": "sha512-DZmzkmwHzXrLPAXPyKNDzlIwMMUZCVacoD25ywdy5YTKGbOx/2ld+Q38Im2zJ0vBuZP5Prd3VZutKZyXwkOS8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@wordpress/env": { + "version": "10.39.0", + "resolved": "https://registry.npmjs.org/@wordpress/env/-/env-10.39.0.tgz", + "integrity": "sha512-Hgl2RQAAzXFMqkpegGWT1/KkX88OVikRroPidWkij1WtU8p+AZniTcncWmlWqbdLdfGbPqQS5ZkqDZCzrQjgnA==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@inquirer/prompts": "^7.2.0", + "@wp-playground/cli": "^3.0.0", + "chalk": "^4.0.0", + "copy-dir": "^1.3.0", + "cross-spawn": "^7.0.6", + "docker-compose": "^0.24.3", + "extract-zip": "^1.6.7", + "got": "^11.8.5", + "js-yaml": "^3.13.1", + "ora": "^4.0.2", + "rimraf": "^5.0.10", + "simple-git": "^3.5.0", + "terminal-link": "^2.0.0", + "yargs": "^17.3.0" + }, + "bin": { + "wp-env": "bin/wp-env" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wp-playground/blueprints": { + "version": "3.1.54", + "resolved": "https://registry.npmjs.org/@wp-playground/blueprints/-/blueprints-3.1.54.tgz", + "integrity": "sha512-lsFx6igM+QLEwdiCncGg6e9GwxcIubSk+iJ+h0xgeq+e/3V90ORVMDXNRE5SfNTA0gIyxNXINz8J+p/XR07N/w==", + "dev": true, + "dependencies": { + "@php-wasm/logger": "3.1.54", + "@php-wasm/progress": "3.1.54", + "@php-wasm/stream-compression": "3.1.54", + "@php-wasm/universal": "3.1.54", + "@php-wasm/util": "3.1.54", + "@php-wasm/web-service-worker": "3.1.54", + "@wp-playground/common": "3.1.54", + "@wp-playground/storage": "3.1.54", + "@wp-playground/wordpress": "3.1.54", + "ajv": "8.18.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@wp-playground/cli": { + "version": "3.1.54", + "resolved": "https://registry.npmjs.org/@wp-playground/cli/-/cli-3.1.54.tgz", + "integrity": "sha512-XxwG1WlOdz7xxv8eM7FDOg3rEBwsMEtVAa7Z8UOZqzHMzbmFTH2eFnAu0Ri6rg24aasVPRE72kdeNWkyCvTAog==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/cli-util": "3.1.54", + "@php-wasm/logger": "3.1.54", + "@php-wasm/node": "3.1.54", + "@php-wasm/progress": "3.1.54", + "@php-wasm/universal": "3.1.54", + "@php-wasm/util": "3.1.54", + "@php-wasm/xdebug-bridge": "3.1.54", + "@wp-playground/blueprints": "3.1.54", + "@wp-playground/common": "3.1.54", + "@wp-playground/storage": "3.1.54", + "@wp-playground/tools": "3.1.54", + "@wp-playground/wordpress": "3.1.54", + "express": "4.22.2", + "fs-extra": "11.1.1", + "tmp-promise": "3.0.3", + "wasm-feature-detect": "1.8.0", + "yargs": "17.7.2" + }, + "bin": { + "wp-playground-cli": "wp-playground.js" + } + }, + "node_modules/@wp-playground/cli/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@wp-playground/cli/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@wp-playground/cli/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@wp-playground/common": { + "version": "3.1.54", + "resolved": "https://registry.npmjs.org/@wp-playground/common/-/common-3.1.54.tgz", + "integrity": "sha512-Ev3s1664g9WK9v7jfS1IuiDQZyCSY7o4LlvEVy/85xlPNbwaAmJuIFBdX2amHWxXEv6CUWGsEBJFF94P/wne/g==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/universal": "3.1.54", + "@php-wasm/util": "3.1.54" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@wp-playground/storage": { + "version": "3.1.54", + "resolved": "https://registry.npmjs.org/@wp-playground/storage/-/storage-3.1.54.tgz", + "integrity": "sha512-B5lMfaTGdJ2//aY6dg1F3F+Zn3F6Ilh3k7aHV6Vmx0gZJ+r7N0c/c7teoLT799gz8OGEcK+N0BLpsyELMQ6xoA==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/stream-compression": "3.1.54", + "@php-wasm/universal": "3.1.54", + "@php-wasm/util": "3.1.54", + "@zip.js/zip.js": "2.7.57", + "isomorphic-git": "1.37.6", + "octokit": "3.1.2", + "pako": "^1.0.10", + "sha.js": "2.4.12" + } + }, + "node_modules/@wp-playground/tools": { + "version": "3.1.54", + "resolved": "https://registry.npmjs.org/@wp-playground/tools/-/tools-3.1.54.tgz", + "integrity": "sha512-12g4WnTtrQc9d7/rRY0onBgCArLcoNbV4APWcwbTdrJBmrWc8kvOTYrs0g3lm9vTqm38JHDN8ZJgL3Y9PGtnCg==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/util": "3.1.54", + "@wp-playground/blueprints": "3.1.54" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@wp-playground/wordpress": { + "version": "3.1.54", + "resolved": "https://registry.npmjs.org/@wp-playground/wordpress/-/wordpress-3.1.54.tgz", + "integrity": "sha512-a1McQQPimZBL+pDfsg/O2GrSC/y3yBMr/c5uXCEn5dYrseqJ2Tni2AzE1oEW8IxPX/gAq+9XRiLCm7sxRdyVYg==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/logger": "3.1.54", + "@php-wasm/universal": "3.1.54", + "@php-wasm/util": "3.1.54", + "@wp-playground/common": "3.1.54", + "zstddec": "^0.2.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@zip.js/zip.js": { + "version": "2.7.57", + "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.7.57.tgz", + "integrity": "sha512-BtonQ1/jDnGiMed6OkV6rZYW78gLmLswkHOzyMrMb+CAR7CZO8phOHO6c2qw6qb1g1betN7kwEHhhZk30dv+NA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "bun": ">=0.7.0", + "deno": ">=1.0.0", + "node": ">=16.5.0" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anynum": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/anynum/-/anynum-1.0.1.tgz", + "integrity": "sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-lock": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.1.tgz", + "integrity": "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/before-after-hook": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/body-parser": { + "version": "1.20.8", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.8.tgz", + "integrity": "sha512-JNcyFQ64OiijEkPzUBTCe+hyPXUD/3LEldGQ6iF5LR1w00mx9o7xtDWHXBY2iItjdCFGoilOLNQbH943ut7pHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.16.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/bottleneck": { + "version": "2.19.5", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", + "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/btoa-lite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/btoa-lite/-/btoa-lite-1.0.0.tgz", + "integrity": "sha512-gvW7InbIyF8AicrqWoptdW08pUxuhq8BEgowNajy9RhiE86fmGAGl+bLKo6oB8QP0CkqHLowfN0oJdKC/J6LbA==", + "dev": true, + "license": "MIT" + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "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, + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "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==", + "dev": true, + "license": "MIT", + "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==", + "dev": true, + "license": "MIT", + "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/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chardet": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.2.0.tgz", + "integrity": "sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==", + "dev": true, + "license": "MIT" + }, + "node_modules/clean-git-ref": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/clean-git-ref/-/clean-git-ref-2.0.1.tgz", + "integrity": "sha512-bLSptAy2P0s6hU4PzuIMKmMJJSE6gLXGH1cntDu7bWJUksvuM+7ReOK61mozULErYvP6a15rnYl0zFDef+pyPw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "engines": [ + "node >= 0.8" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "license": "MIT", + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/copy-dir/-/copy-dir-1.3.0.tgz", + "integrity": "sha512-Q4+qBFnN4bwGwvtXXzbp4P/4iNk0MaiGAzvQ8OiMtlLjkIKjmNN689uVzShSM0908q7GoFHXIPx4zi75ocoaHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "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, + "license": "MIT", + "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/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/diff3": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/diff3/-/diff3-0.0.3.tgz", + "integrity": "sha512-iSq8ngPOt0K53A6eVr4d5Kn6GNrM2nQZtC740pzIriHtn4pOQ2lyzEXQMBeVcWERN0ye7fhBsk9PbLLQOnUx/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/docker-compose": { + "version": "0.24.8", + "resolved": "https://registry.npmjs.org/docker-compose/-/docker-compose-0.24.8.tgz", + "integrity": "sha512-plizRs/Vf15H+GCVxq2EUvyPK7ei9b/cVesHvjnX4xaXjM9spHe2Ytq0BitndFgvTJ3E3NljPNUEl7BAN43iZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "yaml": "^2.2.2" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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, + "license": "MIT", + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/express": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", + "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.5", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.15.1", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/extract-zip": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz", + "integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "concat-stream": "^1.6.2", + "debug": "^2.6.9", + "mkdirp": "^0.5.4", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", + "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fast-xml-builder": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.3.1.tgz", + "integrity": "sha512-pIM/1n3ntFXKYrUZwW7QCK0gAW7XY+wzj1YMIV3tLDvPj/V+zTGJK5e3/4WJfwj0qWw2ElNXiTixda/R+3YSug==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "path-expression-matcher": "^1.6.2", + "xml-naming": "^0.3.0" + } + }, + "node_modules/fast-xml-parser": { + "version": "5.11.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.11.1.tgz", + "integrity": "sha512-TBw6K/fxoQGGjCmZDw9w/ZwP3uDcnTM4YH/g+PFRWr8sbe5idXtxNN6vITh4+1ruCZaho6uBFurElsA7F0zzgw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "@nodable/entities": "^3.0.0", + "fast-xml-builder": "^1.2.0", + "is-unsafe": "^2.0.0", + "path-expression-matcher": "^1.6.2", + "strnum": "^2.4.2", + "xml-naming": "^0.3.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-ext-extra-prebuilt": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/fs-ext-extra-prebuilt/-/fs-ext-extra-prebuilt-2.2.7.tgz", + "integrity": "sha512-Q7rayYRBDIvDF01HWOwSSjoaP+05N1g+o3BXL1Zf8Frw2JkjSmi4EtvCBITuW30l6hB2m2TW1pehdh8wyU/+gw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "nan": "^2.24.0" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/fs-extra": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", + "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "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==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "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==", + "dev": true, + "license": "MIT", + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "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==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ini": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.2.tgz", + "integrity": "sha512-AMB1mvwR1pyBFY/nSevUX6y8nJWS63/SzUKD3JyQn97s4xgIdgQPT75IRouIiBAN4yLQBUShNYVW0+UG25daCw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-unsafe": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-unsafe/-/is-unsafe-2.0.2.tgz", + "integrity": "sha512-HgbIHPBH0KHHCcjLfGsCvhtPTVxjaAZlXjwdz7/GQC40SjSe4sfQsar8J5VFo8JOSbarkpV0OLG95bbaNd9aAQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/isomorphic-git": { + "version": "1.37.6", + "resolved": "https://registry.npmjs.org/isomorphic-git/-/isomorphic-git-1.37.6.tgz", + "integrity": "sha512-qr1NFCPsVTZ6YGqTXw0CzamnsHyH9QQ1OTEfeXIweSljRUMzuHFCJdUn0wc6OcjtTDns6knxjPb7N6LmJeftOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-lock": "^1.4.1", + "clean-git-ref": "^2.0.1", + "crc-32": "^1.2.0", + "diff3": "0.0.3", + "ignore": "^5.1.4", + "minimisted": "^2.0.0", + "pako": "^1.0.10", + "pify": "^4.0.1", + "readable-stream": "^4.0.0", + "sha.js": "^2.4.12", + "simple-get": "^4.0.1" + }, + "bin": { + "isogit": "cli.cjs" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/isomorphic-git/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/isomorphic-git/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-yaml": { + "version": "3.15.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.2.tgz", + "integrity": "sha512-6EuL879VkRA+1Cz578mKMiKvjPNEuk6+r1JaFzoSWejZmtf7xWbIyw1e3KkxlkzTIt9Taw6JBhEppG7utc1P+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "dev": true, + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", + "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-symbols/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/log-symbols/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/log-symbols/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols/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, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/log-symbols/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, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "name": "@wolfy1339/lru-cache", + "version": "11.0.2-patch.1", + "resolved": "https://registry.npmjs.org/@wolfy1339/lru-cache/-/lru-cache-11.0.2-patch.1.tgz", + "integrity": "sha512-BgYZfL2ADCXKOw2wJtkM3slhHotawWkgIRRxq4wEybnZQPjvAp71SPX35xepMykTw8gXlzWcWPTY31hlbnRsDA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "18 >=18.20 || 20 || >=22" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minimisted": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minimisted/-/minimisted-2.0.1.tgz", + "integrity": "sha512-1oPjfuLQa2caorJUM8HV8lGgWCc0qqAO1MNv/k05G4qslmsndV/5WdNZrqCiyqiz3wohia2Ij2B7w2Dr7/IyrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/nan": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.29.0.tgz", + "integrity": "sha512-GlGk3HIvitbvs+LT3g6XUP1kpirKNvmDFwF/bmo6XNWSb/eYEs/O4bfgIEIXCZ+lIOTS5xNwDvSGMw6FJdAhtA==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/octokit": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/octokit/-/octokit-3.1.2.tgz", + "integrity": "sha512-MG5qmrTL5y8KYwFgE1A4JWmgfQBaIETE/lOlfwNYx1QOtCQHGVxkRJmdUJltFc1HVn73d61TlMhMyNTOtMl+ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/app": "^14.0.2", + "@octokit/core": "^5.0.0", + "@octokit/oauth-app": "^6.0.0", + "@octokit/plugin-paginate-graphql": "^4.0.0", + "@octokit/plugin-paginate-rest": "^9.0.0", + "@octokit/plugin-rest-endpoint-methods": "^10.0.0", + "@octokit/plugin-retry": "^6.0.0", + "@octokit/plugin-throttling": "^8.0.0", + "@octokit/request-error": "^5.0.0", + "@octokit/types": "^12.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "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==", + "dev": true, + "license": "MIT", + "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==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-4.1.1.tgz", + "integrity": "sha512-sjYP8QyVWBpBZWD6Vr1M/KwknSw6kJOz41tvGMlwWeClHBtYKTbHMki1PsLZnxKpXMPbTKv9b3pjQu3REib96A==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^3.0.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.2.0", + "is-interactive": "^1.0.0", + "log-symbols": "^3.0.0", + "mute-stream": "0.0.8", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true, + "license": "ISC" + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true, + "license": "(MIT AND Zlib)" + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-expression-matcher": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.6.2.tgz", + "integrity": "sha512-enSlaiat05iasnzmgNxRj8reFdj3puY2QpNgP1aPIaVfT6nn9ICuPoFlKHk8EN22HcwewshO+mN2DGbkCEOtqQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/path-to-regexp": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", + "dev": true, + "license": "MIT" + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/playwright": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.63.0.tgz", + "integrity": "sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.63.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/playwright-core": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.63.0.tgz", + "integrity": "sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", + "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/qs": { + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", + "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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, + "license": "MIT", + "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/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, + "node_modules/sha.js": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", + "dev": true, + "license": "(MIT AND BSD-3-Clause)", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" + }, + "bin": { + "sha.js": "bin.js" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "license": "MIT", + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "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.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "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==", + "dev": true, + "license": "MIT", + "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==", + "dev": true, + "license": "MIT", + "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/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-git": { + "version": "3.36.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.36.0.tgz", + "integrity": "sha512-cGQjLjK8bxJw4QuYT7gxHw3/IouVESbhahSsHrX97MzCL1gu2u7oy38W6L2ZIGECEfIBG4BabsWDPjBxJENv9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@kwsites/file-exists": "^1.1.1", + "@kwsites/promise-deferred": "^1.1.1", + "@simple-git/args-pathspec": "^1.0.3", + "@simple-git/argv-parser": "^1.1.0", + "debug": "^4.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/steveukx/git-js?sponsor=1" + } + }, + "node_modules/simple-git/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/simple-git/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strnum": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.4.2.tgz", + "integrity": "sha512-rDG3Ah4TV0k1hWvLSzkZtMmLN9+eS+h3knq4MP6A42Y3Yh5qGNnOUs1jJkoSr8FG5dsL28c7KgkIBzSEykqtuw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "anynum": "^1.0.1" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", + "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/terminal-link": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", + "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tmp": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tmp-promise": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", + "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tmp": "^0.2.0" + } + }, + "node_modules/to-buffer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", + "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/to-buffer/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, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "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, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/universal-github-app-jwt": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-1.2.0.tgz", + "integrity": "sha512-dncpMpnsKBk0eetwfN8D8OUHGfiDhhJ+mtsbMl+7PfW7mYjiH8LIcqRmYMtzYLgSh47HjfdBtrBwIQ/gizKR3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/jsonwebtoken": "^9.0.0", + "jsonwebtoken": "^9.0.2" + } + }, + "node_modules/universal-user-agent": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", + "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/wasm-feature-detect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.8.0.tgz", + "integrity": "sha512-zksaLKM2fVlnB5jQQDqKXXwYHLQUVH9es+5TOOHwGOVJOCeRBCiPjwSg+3tN2AdTCzjgli4jijCH290kXb/zWQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.22", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.22.tgz", + "integrity": "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "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/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-naming": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.3.0.tgz", + "integrity": "sha512-ghig2TBE/H11aOVgmahA3MhimvkBr6JIYknH/Dhdk10nXwdbIqBJsbfMxpvFPG8bAw77gN29aQWvKpmVoPlvPQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/xml2js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", + "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", + "dev": true, + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yaml": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", + "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zstddec": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/zstddec/-/zstddec-0.2.0.tgz", + "integrity": "sha512-oyPnDa1X5c13+Y7mA/FDMNJrn4S8UNBe0KCqtDmor40Re7ALrPN6npFwyYVRRh+PqozZQdeg23QtbcamZnG5rA==", + "dev": true, + "license": "MIT AND BSD-3-Clause" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..f46bd38 --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "libresign-wp-customizations", + "private": true, + "license": "GPL-2.0-or-later", + "description": "Browser tests for the LibreSign WordPress customizations", + "scripts": { + "test:e2e": "playwright test", + "test:e2e:ui": "playwright test --ui", + "env:start": "wp-env start", + "env:stop": "wp-env stop" + }, + "devDependencies": { + "@playwright/test": "^1.50.0", + "@wordpress/env": "^10.16.0" + } +} diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 4292cae..640305b 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -4,6 +4,7 @@ . /vendor/* + /node_modules/* diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 4f935cb..13a94d2 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -8,8 +8,11 @@ parameters: paths: - libresign-wp-customizations.php - includes + - src - tests excludePaths: + analyse: + - tests/E2E/support analyseAndScan: # Answers the WordPress functions the plugin reaches while it loads, # so the unit suite runs without WordPress. Analysing it would take diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 8f09ad0..c68846f 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -17,6 +17,7 @@ libresign-wp-customizations.php includes + src diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..86d1370 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,41 @@ +import { defineConfig, devices } from '@playwright/test'; + +import { CUSTOMER_STATE } from './tests/E2E/support/site'; + +export default defineConfig( { + testDir: 'tests/E2E', + outputDir: 'tests/E2E/.results', + workers: 1, + fullyParallel: false, + forbidOnly: Boolean( process.env.CI ), + retries: process.env.CI ? 1 : 0, + reporter: process.env.CI + ? [ + [ 'github' ], + [ + 'html', + { open: 'never', outputFolder: 'tests/E2E/.report' }, + ], + ] + : [ [ 'list' ] ], + use: { + baseURL: process.env.WP_BASE_URL || 'http://localhost', + trace: 'retain-on-failure', + screenshot: 'only-on-failure', + }, + projects: [ + { + name: 'setup', + testMatch: /.*\.setup\.ts/, + use: { ...devices[ 'Desktop Chrome' ] }, + }, + { + name: 'customer', + dependencies: [ 'setup' ], + use: { + ...devices[ 'Desktop Chrome' ], + storageState: CUSTOMER_STATE, + }, + }, + ], +} ); diff --git a/src/Account/Navigation.php b/src/Account/Navigation.php new file mode 100644 index 0000000..c3b4fc3 --- /dev/null +++ b/src/Account/Navigation.php @@ -0,0 +1,118 @@ + + */ + public static function labels(): array { + return array( + 'subscriptions' => __( 'My subscription', 'libresign-wp-customizations' ), + 'orders' => __( 'Invoices', 'libresign-wp-customizations' ), + 'payment-methods' => __( 'Billing', 'libresign-wp-customizations' ), + ); + } + + /** + * Detail endpoints neither core nor Subscriptions highlights, mapped to the + * entry they belong to. + * + * @return array + */ + public static function aliases(): array { + return array( + 'subscriptions' => array( 'subscription-payment-method' ), + 'payment-methods' => array( 'edit-address' ), + ); + } + + /** + * @param array $items Navigation handed over by WooCommerce. + * @return array + */ + public static function filter_items( array $items ): array { + $labels = self::labels(); + $menu = array(); + + foreach ( self::order() as $endpoint ) { + if ( isset( $items[ $endpoint ] ) ) { + $menu[ $endpoint ] = isset( $labels[ $endpoint ] ) ? $labels[ $endpoint ] : $items[ $endpoint ]; + } + } + + return $menu; + } + + /** + * @param string[] $classes Classes of the entry. + * @param array $query_vars Query vars of the request being rendered. + * @return string[] + */ + public static function filter_item_classes( array $classes, string $endpoint, array $query_vars ): array { + if ( in_array( 'is-active', $classes, true ) ) { + return $classes; + } + + $aliases = self::aliases(); + + if ( empty( $aliases[ $endpoint ] ) ) { + return $classes; + } + + foreach ( $aliases[ $endpoint ] as $alias ) { + if ( isset( $query_vars[ $alias ] ) ) { + $classes[] = 'is-active'; + break; + } + } + + return $classes; + } + + /** + * @param array $query_vars Query vars of the request being rendered. + */ + public static function endpoint_title( string $title, string $endpoint, array $query_vars ): string { + $labels = self::labels(); + + if ( ! isset( $labels[ $endpoint ] ) ) { + return $title; + } + + $page_number = isset( $query_vars[ $endpoint ] ) ? intval( $query_vars[ $endpoint ] ) : 0; + + if ( $page_number < 2 ) { + return $labels[ $endpoint ]; + } + + return sprintf( + /* translators: 1: navigation label, 2: page number */ + __( '%1$s (page %2$d)', 'libresign-wp-customizations' ), + $labels[ $endpoint ], + $page_number + ); + } +} diff --git a/src/Account/RootEndpoint.php b/src/Account/RootEndpoint.php new file mode 100644 index 0000000..8722aab --- /dev/null +++ b/src/Account/RootEndpoint.php @@ -0,0 +1,43 @@ + $query_vars Endpoints registered by WooCommerce. + */ + public static function matches( string $request_path, array $query_vars ): bool { + $request_path = trim( $request_path, '/' ); + + if ( '' === $request_path ) { + return false; + } + + $segments = explode( '/', $request_path ); + $first_slug = reset( $segments ); + + if ( 'my-account' === $first_slug ) { + return true; + } + + foreach ( $query_vars as $query_var ) { + if ( ! empty( $query_var ) && $query_var === $first_slug ) { + return true; + } + } + + return false; + } +} diff --git a/src/Autoloader.php b/src/Autoloader.php new file mode 100644 index 0000000..15079b8 --- /dev/null +++ b/src/Autoloader.php @@ -0,0 +1,37 @@ +aqui'; + } + + public static function failure_message( int $code, string $api_message ): string { + return "Erro ao acionar deploy.
Código: {$code}.
Message: {$api_message}"; + } +} diff --git a/src/Github/SiteDeploy.php b/src/Github/SiteDeploy.php new file mode 100644 index 0000000..fe38c65 --- /dev/null +++ b/src/Github/SiteDeploy.php @@ -0,0 +1,31 @@ +repository = $repository; + $this->branch_name = $branch_name; + $this->workflow_name = $workflow_name; + } + + public function is_production_run( WorkflowRun $workflow_run ): bool { + return $this->repository === $workflow_run->repository() + && 'completed' === $workflow_run->action() + && 'success' === $workflow_run->conclusion() + && $this->branch_name === $workflow_run->head_branch() + && $this->workflow_name === $workflow_run->workflow_name(); + } +} diff --git a/src/Github/WebhookDecision.php b/src/Github/WebhookDecision.php new file mode 100644 index 0000000..60ef907 --- /dev/null +++ b/src/Github/WebhookDecision.php @@ -0,0 +1,112 @@ + + */ + private array $response_data = array(); + + private ?WorkflowRun $workflow_run = null; + + private function __construct( string $outcome ) { + $this->outcome = $outcome; + } + + public static function reject( string $error_code, string $error_message, int $http_status ): self { + $decision = new self( self::REJECT ); + $decision->error_code = $error_code; + $decision->error_message = $error_message; + $decision->http_status = $http_status; + + return $decision; + } + + public static function pong(): self { + return new self( self::PONG ); + } + + /** + * @param array $details Details worth reporting back. + */ + public static function ignore( string $ignored_reason, array $details = array() ): self { + $decision = new self( self::IGNORE ); + $decision->response_data = array_merge( array( 'reason' => $ignored_reason ), $details ); + + return $decision; + } + + public static function deploy( WorkflowRun $workflow_run ): self { + $decision = new self( self::DEPLOY ); + $decision->workflow_run = $workflow_run; + + return $decision; + } + + public function is_rejected(): bool { + return self::REJECT === $this->outcome; + } + + public function is_pong(): bool { + return self::PONG === $this->outcome; + } + + public function is_ignored(): bool { + return self::IGNORE === $this->outcome; + } + + public function is_deploy(): bool { + return self::DEPLOY === $this->outcome; + } + + public function code(): string { + return $this->error_code; + } + + public function message(): string { + return $this->error_message; + } + + public function status(): int { + return $this->http_status; + } + + /** + * @return array + */ + public function data(): array { + return $this->response_data; + } + + /** + * @throws LogicException When the decision is not a deploy. + */ + public function workflow_run(): WorkflowRun { + if ( null === $this->workflow_run ) { + throw new LogicException( 'Only a deploy decision carries a workflow run.' ); + } + + return $this->workflow_run; + } +} diff --git a/src/Github/WebhookGate.php b/src/Github/WebhookGate.php new file mode 100644 index 0000000..c7e067b --- /dev/null +++ b/src/Github/WebhookGate.php @@ -0,0 +1,85 @@ +secret = $secret; + $this->site_deploy = $site_deploy; + } + + public function decide( WebhookRequest $request ): WebhookDecision { + if ( '' === trim( $this->secret ) ) { + return WebhookDecision::reject( + 'libresign_github_webhook_secret_missing', + __( 'The GitHub webhook secret is not configured.', 'libresign-wp-customizations' ), + 503 + ); + } + + if ( ! $request->is_from_github() ) { + return WebhookDecision::reject( + 'libresign_github_webhook_invalid_agent', + __( 'The webhook request does not look like a GitHub delivery.', 'libresign-wp-customizations' ), + 403 + ); + } + + if ( ! $request->has_valid_signature( $this->secret ) ) { + return WebhookDecision::reject( + 'libresign_github_webhook_invalid_signature', + __( 'Invalid GitHub webhook signature.', 'libresign-wp-customizations' ), + 403 + ); + } + + if ( 'ping' === $request->event() ) { + return WebhookDecision::pong(); + } + + if ( 'workflow_run' !== $request->event() ) { + return WebhookDecision::ignore( 'unsupported_event', array( 'event' => $request->event() ) ); + } + + $payload = $request->payload(); + + if ( null === $payload ) { + return WebhookDecision::reject( + 'libresign_github_webhook_invalid_payload', + __( 'The GitHub webhook payload must be valid JSON.', 'libresign-wp-customizations' ), + 400 + ); + } + + $workflow_run = WorkflowRun::from_payload( $payload ); + + if ( ! $this->site_deploy->is_production_run( $workflow_run ) ) { + return WebhookDecision::ignore( + 'not_production_deploy', + array( + 'repository' => $workflow_run->repository(), + 'workflow_name' => $workflow_run->workflow_name(), + 'head_branch' => $workflow_run->head_branch(), + 'conclusion' => $workflow_run->conclusion(), + ) + ); + } + + return WebhookDecision::deploy( $workflow_run ); + } +} diff --git a/src/Github/WebhookRequest.php b/src/Github/WebhookRequest.php new file mode 100644 index 0000000..bcf4f46 --- /dev/null +++ b/src/Github/WebhookRequest.php @@ -0,0 +1,58 @@ +user_agent = $user_agent; + $this->event = $event; + $this->signature = $signature; + $this->delivery_id = $delivery_id; + $this->body = $body; + } + + public function is_from_github(): bool { + return 0 === strpos( trim( $this->user_agent ), 'GitHub-Hookshot/' ); + } + + public function has_valid_signature( string $secret ): bool { + return WebhookSignature::matches( $this->body, $this->signature, $secret ); + } + + public function event(): string { + return strtolower( trim( $this->event ) ); + } + + public function delivery_id(): string { + return $this->delivery_id; + } + + /** + * @return array|null + */ + public function payload(): ?array { + $payload = json_decode( $this->body, true ); + + return is_array( $payload ) ? $payload : null; + } +} diff --git a/src/Github/WebhookSignature.php b/src/Github/WebhookSignature.php new file mode 100644 index 0000000..02a73b4 --- /dev/null +++ b/src/Github/WebhookSignature.php @@ -0,0 +1,32 @@ + + */ + private array $payload; + + /** + * @param array $payload Parsed payload. + */ + private function __construct( array $payload ) { + $this->payload = $payload; + } + + /** + * @param array $payload Parsed payload. + */ + public static function from_payload( array $payload ): self { + return new self( $payload ); + } + + public function repository(): string { + return $this->text( array( 'repository', 'full_name' ) ); + } + + public function action(): string { + return $this->text( array( 'action' ) ); + } + + public function conclusion(): string { + return $this->text( array( 'workflow_run', 'conclusion' ) ); + } + + public function head_branch(): string { + return $this->text( array( 'workflow_run', 'head_branch' ) ); + } + + public function head_sha(): string { + return $this->text( array( 'workflow_run', 'head_sha' ) ); + } + + public function html_url(): string { + return $this->text( array( 'workflow_run', 'html_url' ) ); + } + + public function updated_at(): string { + return $this->text( array( 'workflow_run', 'updated_at' ) ); + } + + public function workflow_name(): string { + $run_name = $this->text( array( 'workflow_run', 'name' ) ); + + return '' === $run_name ? $this->text( array( 'workflow', 'name' ) ) : $run_name; + } + + /** + * @param string[] $field_path Keys to walk. + */ + private function text( array $field_path ): string { + $field_value = $this->payload; + + foreach ( $field_path as $field_name ) { + if ( ! is_array( $field_value ) || ! isset( $field_value[ $field_name ] ) ) { + return ''; + } + + $field_value = $field_value[ $field_name ]; + } + + return is_scalar( $field_value ) ? trim( (string) $field_value ) : ''; + } +} diff --git a/src/Settings/Secret.php b/src/Settings/Secret.php new file mode 100644 index 0000000..9e4847c --- /dev/null +++ b/src/Settings/Secret.php @@ -0,0 +1,94 @@ +encryption_key = $encryption_key; + $this->initialization_vector = $initialization_vector; + } + + public static function from_salts( string $auth_key, string $secure_auth_salt, string $nonce_salt ): self { + return new self( + hash( 'sha256', $auth_key . $secure_auth_salt ), + substr( hash( 'sha256', $nonce_salt ), 0, 16 ) + ); + } + + /** + * An already encrypted value is left as it is: WordPress runs the sanitize + * callback of an option again when update_option() hands a value that does + * not exist yet to add_option(), and encrypting twice stores a value that + * nothing can read back. + */ + public function encrypt( string $plain_value ): string { + $plain_value = trim( $plain_value ); + + if ( $this->decrypt( $plain_value ) !== $plain_value ) { + return $plain_value; + } + + return base64_encode( + (string) openssl_encrypt( $plain_value, self::CIPHER, $this->encryption_key, 0, $this->initialization_vector ) + ); + } + + /** + * Read a stored value, returning it untouched when it cannot be deciphered, + * so a value saved in plain text keeps working. Only for a value that stays + * on the site: decrypt_or_discard() is the one for a value that is sent. + */ + public function decrypt( string $stored_value ): string { + $decoded_value = $this->decode( $stored_value ); + + if ( false === $decoded_value ) { + return trim( $stored_value ); + } + + return $this->decipher( $decoded_value ) ?? trim( $stored_value ); + } + + /** + * A value that is not base64 was never encrypted and is used as it is. A + * value that is base64 but does not decipher was encrypted with salts this + * installation no longer has: it is not the value and nobody outside may + * see it, so it is dropped instead of being sent. + */ + public function decrypt_or_discard( string $stored_value ): string { + $decoded_value = $this->decode( $stored_value ); + + if ( false === $decoded_value ) { + return trim( $stored_value ); + } + + return (string) $this->decipher( $decoded_value ); + } + + /** + * @return string|false + */ + private function decode( string $stored_value ) { + $stored_value = trim( $stored_value ); + + return '' === $stored_value ? false : base64_decode( $stored_value, true ); + } + + private function decipher( string $decoded_value ): ?string { + $decrypted_value = openssl_decrypt( $decoded_value, self::CIPHER, $this->encryption_key, 0, $this->initialization_vector ); + + return false === $decrypted_value ? null : trim( $decrypted_value ); + } +} diff --git a/src/Subscription/StatusChange.php b/src/Subscription/StatusChange.php new file mode 100644 index 0000000..f7d259e --- /dev/null +++ b/src/Subscription/StatusChange.php @@ -0,0 +1,33 @@ + array( + 'question' => __( 'Are you sure you want to cancel your subscription? This action cannot be undone.', 'libresign-wp-customizations' ), + 'confirm' => __( 'Yes, cancel subscription', 'libresign-wp-customizations' ), + 'dismiss' => __( 'No, keep subscription', 'libresign-wp-customizations' ), + ), + 'active' => array( + 'question' => __( 'Are you sure you want to reactivate your subscription?', 'libresign-wp-customizations' ), + 'confirm' => __( 'Yes, reactivate subscription', 'libresign-wp-customizations' ), + 'dismiss' => __( 'No, go back', 'libresign-wp-customizations' ), + ), + ); + + return $strings[ $new_status ] ?? null; + } +} diff --git a/tests/E2E/Account/Navigation.spec.ts b/tests/E2E/Account/Navigation.spec.ts new file mode 100644 index 0000000..47b085c --- /dev/null +++ b/tests/E2E/Account/Navigation.spec.ts @@ -0,0 +1,103 @@ +import { expect, test } from '@playwright/test'; + +import { seededSite } from '../support/site'; + +const ENTRY = '.woocommerce-MyAccount-navigation-link'; + +test.describe( 'My Account navigation', () => { + test( 'keeps five entries, in the order the plugin sets', async ( { + page, + } ) => { + await page.goto( '/' ); + + const entries = page.locator( ENTRY ); + await expect( entries ).toHaveCount( 5 ); + + const order = [ + 'subscriptions', + 'orders', + 'payment-methods', + 'edit-account', + 'customer-logout', + ]; + + for ( const [ position, endpoint ] of order.entries() ) { + await expect( entries.nth( position ) ).toHaveClass( + new RegExp( `${ ENTRY.slice( 1 ) }--${ endpoint }(\\s|$)` ) + ); + } + } ); + + test( 'renames the three entries it relabels', async ( { page } ) => { + await page.goto( '/' ); + + await expect( page.locator( `${ ENTRY }--subscriptions a` ) ).toHaveText( + 'My subscription' + ); + await expect( page.locator( `${ ENTRY }--orders a` ) ).toHaveText( + 'Invoices' + ); + await expect( + page.locator( `${ ENTRY }--payment-methods a` ) + ).toHaveText( 'Billing' ); + } ); + + test( 'titles a screen with the label its entry carries', async ( { + page, + } ) => { + await page.goto( '/orders/' ); + + await expect( + page.getByRole( 'heading', { name: 'Invoices' } ) + ).toBeVisible(); + } ); + + test( 'shows the payment methods and the addresses on the billing screen', async ( { + page, + } ) => { + await page.goto( '/payment-methods/' ); + + await expect( + page.locator( '.libresign-account-section-title' ) + ).toHaveText( [ 'Payment methods', 'Addresses' ] ); + + await expect( + page.getByText( + 'These addresses are used on your invoices and subscription charges.' + ) + ).toBeVisible(); + } ); + + test( 'sends the standalone addresses list to the billing screen', async ( { + page, + } ) => { + await page.goto( '/edit-address/' ); + + await expect( page ).toHaveURL( '/payment-methods/' ); + } ); + + test( 'keeps billing highlighted while an address is being edited', async ( { + page, + } ) => { + await page.goto( '/edit-address/billing/' ); + + await expect( + page.locator( `${ ENTRY }--payment-methods` ) + ).toHaveClass( /is-active/ ); + } ); + + test( 'points the account screens at the signature system', async ( { + page, + } ) => { + await page.goto( '/' ); + + const callToAction = page.locator( '.libresign-nextcloud-account-cta' ); + + await expect( callToAction ).toBeVisible(); + await expect( + callToAction.getByRole( 'link', { + name: 'Ir para o sistema de assinaturas', + } ) + ).toHaveAttribute( 'href', seededSite().nextcloudHost ); + } ); +} ); diff --git a/tests/E2E/Account/RootEndpoint.spec.ts b/tests/E2E/Account/RootEndpoint.spec.ts new file mode 100644 index 0000000..82e2445 --- /dev/null +++ b/tests/E2E/Account/RootEndpoint.spec.ts @@ -0,0 +1,51 @@ +import { expect, test } from '@playwright/test'; + +test.describe( 'Account screens served from the site root', () => { + test( 'serves the invoices screen at /orders/', async ( { page } ) => { + await page.goto( '/orders/' ); + + await expect( page ).toHaveURL( '/orders/' ); + await expect( + page.locator( '.woocommerce-MyAccount-navigation-link--orders' ) + ).toHaveClass( /is-active/ ); + } ); + + test( 'serves the billing screen at /payment-methods/', async ( { + page, + } ) => { + await page.goto( '/payment-methods/' ); + + await expect( page ).toHaveURL( '/payment-methods/' ); + await expect( + page.locator( + '.woocommerce-MyAccount-navigation-link--payment-methods' + ) + ).toHaveClass( /is-active/ ); + } ); + + test( 'keeps the /my-account/ permalink working as an alias', async ( { + page, + } ) => { + await page.goto( '/my-account/' ); + + await expect( page ).toHaveURL( '/my-account/' ); + await expect( + page.locator( '.woocommerce-MyAccount-navigation' ) + ).toBeVisible(); + } ); + + test.describe( 'signed out', () => { + test.use( { storageState: { cookies: [], origins: [] } } ); + + test( 'serves the lost password screen at /lost-password/', async ( { + page, + } ) => { + await page.goto( '/lost-password/' ); + + await expect( page ).toHaveURL( '/lost-password/' ); + await expect( + page.locator( 'form.lost_reset_password' ) + ).toBeVisible(); + } ); + } ); +} ); diff --git a/tests/E2E/Subscription/StatusChange.spec.ts b/tests/E2E/Subscription/StatusChange.spec.ts new file mode 100644 index 0000000..4068269 --- /dev/null +++ b/tests/E2E/Subscription/StatusChange.spec.ts @@ -0,0 +1,63 @@ +import { expect, Page, test } from '@playwright/test'; + +import { SeededSite, seedSite, subscriptionStatus } from '../support/site'; + +const CANCELLATION_QUESTION = + 'Are you sure you want to cancel your subscription? This action cannot be undone.'; + +const cancelAction = ( page: Page ) => page.locator( 'a.cancel' ); + +test.describe( 'Confirming a subscription status change', () => { + let site: SeededSite; + + test.beforeEach( () => { + site = seedSite(); + } ); + + test( 'asks before cancelling instead of cancelling right away', async ( { + page, + } ) => { + await page.goto( `/view-subscription/${ site.subscriptionId }/` ); + await cancelAction( page ).click(); + + await expect( page.getByText( CANCELLATION_QUESTION ) ).toBeVisible(); + await expect( + page.getByRole( 'link', { name: 'Yes, cancel subscription' } ) + ).toBeVisible(); + await expect( + page.getByRole( 'link', { name: 'No, keep subscription' } ) + ).toBeVisible(); + + expect( subscriptionStatus( site.subscriptionId ) ).toBe( 'active' ); + } ); + + test( 'leaves the subscription active when the customer backs out', async ( { + page, + } ) => { + await page.goto( `/view-subscription/${ site.subscriptionId }/` ); + await cancelAction( page ).click(); + await page + .getByRole( 'link', { name: 'No, keep subscription' } ) + .click(); + + await expect( page.getByText( CANCELLATION_QUESTION ) ).toBeHidden(); + + expect( subscriptionStatus( site.subscriptionId ) ).toBe( 'active' ); + } ); + + test( 'stops the renewals once the customer confirms', async ( { + page, + } ) => { + await page.goto( `/view-subscription/${ site.subscriptionId }/` ); + await cancelAction( page ).click(); + await page + .getByRole( 'link', { name: 'Yes, cancel subscription' } ) + .click(); + + await expect( cancelAction( page ) ).toBeHidden(); + + expect( subscriptionStatus( site.subscriptionId ) ).toBe( + 'pending-cancel' + ); + } ); +} ); diff --git a/tests/E2E/customer.setup.ts b/tests/E2E/customer.setup.ts new file mode 100644 index 0000000..762b3ed --- /dev/null +++ b/tests/E2E/customer.setup.ts @@ -0,0 +1,18 @@ +import { expect, test as setup } from '@playwright/test'; + +import { CUSTOMER_PASSWORD, CUSTOMER_STATE, seedSite } from './support/site'; + +setup( 'seed the site and sign the customer in', async ( { page } ) => { + const site = seedSite(); + + await page.goto( '/wp-login.php?redirect_to=%2F' ); + await page.locator( '#user_login' ).fill( site.customerLogin ); + await page.locator( '#user_pass' ).fill( CUSTOMER_PASSWORD ); + await page.locator( '#wp-submit' ).click(); + + await expect( + page.locator( '.woocommerce-MyAccount-navigation' ) + ).toBeVisible(); + + await page.context().storageState( { path: CUSTOMER_STATE } ); +} ); diff --git a/tests/E2E/support/mu-plugins/saved-payment-methods-gateway.php b/tests/E2E/support/mu-plugins/saved-payment-methods-gateway.php new file mode 100644 index 0000000..9ea7311 --- /dev/null +++ b/tests/E2E/support/mu-plugins/saved-payment-methods-gateway.php @@ -0,0 +1,30 @@ +id = 'libresign_e2e_saved_payment_methods'; + $this->method_title = 'LibreSign E2E saved payment methods'; + $this->title = 'LibreSign E2E saved payment methods'; + $this->has_fields = false; + $this->enabled = 'yes'; + $this->supports = array( 'products', 'tokenization', 'add_payment_method' ); + } + } + + add_filter( 'woocommerce_payment_gateways', 'libresign_e2e_add_saved_payment_methods_gateway' ); +} +add_action( 'plugins_loaded', 'libresign_e2e_define_saved_payment_methods_gateway' ); diff --git a/tests/E2E/support/seed.php b/tests/E2E/support/seed.php new file mode 100644 index 0000000..5d00cef --- /dev/null +++ b/tests/E2E/support/seed.php @@ -0,0 +1,140 @@ +' ); +} + +if ( ! function_exists( 'wcs_create_subscription' ) ) { + WP_CLI::error( 'WooCommerce Subscriptions is not active on the site under test.' ); +} + +$site_host = (string) wp_parse_url( (string) home_url(), PHP_URL_HOST ); +$site_is_local = in_array( $site_host, array( 'localhost', '127.0.0.1', '::1' ), true ); +$any_site_allowed = isset( $args[1] ) && 'any-site' === $args[1]; + +if ( ! $site_is_local && ! $any_site_allowed ) { + WP_CLI::error( + sprintf( + 'The seed makes My Account the front page of %s, rebuilds its rewrite rules and creates a customer with a password this repository publishes. Set WP_E2E_ALLOW_ANY_SITE=1 to run it outside localhost.', + $site_host + ) + ); +} + +$customer_login = 'libresign_e2e_customer'; +$customer_email = 'e2e-customer@libresign.test'; +$product_sku = 'LIBRESIGN-E2E-PLAN'; + +if ( '' === (string) get_option( 'permalink_structure' ) ) { + update_option( 'permalink_structure', '/%postname%/' ); +} + +$account_page_id = (int) wc_get_page_id( 'myaccount' ); + +if ( $account_page_id <= 0 ) { + WP_CLI::error( 'WooCommerce has no My Account page; install the WooCommerce pages first.' ); +} + +update_option( 'show_on_front', 'page' ); +update_option( 'page_on_front', $account_page_id ); + +delete_option( 'libresign_root_my_account_rewrite_version' ); + +if ( '' === libresign_get_nextcloud_public_host() ) { + update_option( 'nextcloud_public_host', 'https://nextcloud.libresign.test' ); +} + +$customer = get_user_by( 'login', $customer_login ); + +if ( $customer instanceof WP_User ) { + $customer_id = $customer->ID; + $password_already_matches = wp_check_password( $customer_password, $customer->user_pass, $customer_id ); + + if ( ! $password_already_matches ) { + wp_set_password( $customer_password, $customer_id ); + } +} else { + $customer_id = wp_insert_user( + array( + 'user_login' => $customer_login, + 'user_email' => $customer_email, + 'user_pass' => $customer_password, + 'first_name' => 'Ana', + 'last_name' => 'Lima', + 'role' => 'customer', + ) + ); + + if ( is_wp_error( $customer_id ) ) { + WP_CLI::error( $customer_id->get_error_message() ); + } + + $customer_id = (int) $customer_id; +} + +update_user_meta( $customer_id, 'billing_first_name', 'Ana' ); +update_user_meta( $customer_id, 'billing_last_name', 'Lima' ); +update_user_meta( $customer_id, 'billing_email', $customer_email ); +update_user_meta( $customer_id, 'billing_country', 'BR' ); + +$product_id = (int) wc_get_product_id_by_sku( $product_sku ); + +if ( $product_id <= 0 ) { + $new_product = new WC_Product_Subscription(); + $new_product->set_name( 'LibreSign E2E plan' ); + $new_product->set_slug( 'libresign-e2e-plan' ); + $new_product->set_sku( $product_sku ); + $new_product->set_status( 'publish' ); + $new_product->set_catalog_visibility( 'hidden' ); + $new_product->set_regular_price( '10' ); + $new_product->update_meta_data( '_subscription_price', '10' ); + $new_product->update_meta_data( '_subscription_period', 'month' ); + $new_product->update_meta_data( '_subscription_period_interval', '1' ); + $new_product->update_meta_data( '_subscription_length', '0' ); + $product_id = (int) $new_product->save(); +} + +foreach ( wcs_get_users_subscriptions( $customer_id ) as $subscription_of_a_previous_run ) { + $subscription_of_a_previous_run->delete( true ); +} + +$subscription = wcs_create_subscription( + array( + 'customer_id' => $customer_id, + 'status' => 'pending', + 'billing_period' => 'month', + 'billing_interval' => 1, + 'start_date' => gmdate( 'Y-m-d H:i:s', time() - MONTH_IN_SECONDS ), + ) +); + +if ( is_wp_error( $subscription ) ) { + WP_CLI::error( $subscription->get_error_message() ); +} + +$subscription->set_requires_manual_renewal( true ); +$subscription->set_billing_first_name( 'Ana' ); +$subscription->set_billing_last_name( 'Lima' ); +$subscription->set_billing_email( $customer_email ); +$subscription->add_product( wc_get_product( $product_id ), 1 ); +$subscription->calculate_totals(); +$subscription->save(); +$subscription->update_status( 'active' ); + +WP_CLI::line( + (string) wp_json_encode( + array( + 'customerId' => $customer_id, + 'customerLogin' => $customer_login, + 'customerEmail' => $customer_email, + 'productId' => $product_id, + 'subscriptionId' => $subscription->get_id(), + 'accountPageId' => $account_page_id, + 'nextcloudHost' => libresign_get_nextcloud_public_host(), + ) + ) +); diff --git a/tests/E2E/support/site.ts b/tests/E2E/support/site.ts new file mode 100644 index 0000000..fadbb5e --- /dev/null +++ b/tests/E2E/support/site.ts @@ -0,0 +1,70 @@ +import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; + +import { runWpCli } from './wp-cli'; + +const SEED_SCRIPT = join( __dirname, 'seed.php' ); +const STATE_DIR = join( __dirname, '..', '.state' ); +const SITE_STATE = join( STATE_DIR, 'site.json' ); + +export const CUSTOMER_STATE = join( STATE_DIR, 'customer.json' ); + +export const CUSTOMER_PASSWORD = + process.env.WP_E2E_CUSTOMER_PASSWORD || 'libresign-e2e'; + +const SEED_TARGET = + process.env.WP_E2E_ALLOW_ANY_SITE === '1' ? 'any-site' : 'local-site-only'; + +export interface SeededSite { + customerId: number; + customerLogin: string; + customerEmail: string; + productId: number; + subscriptionId: number; + accountPageId: number; + nextcloudHost: string; +} + +export function seedSite(): SeededSite { + const output = runWpCli( + [ 'eval-file', '-', CUSTOMER_PASSWORD, SEED_TARGET ], + readFileSync( SEED_SCRIPT, 'utf8' ) + ); + + runWpCli( [ 'rewrite', 'flush', '--hard' ] ); + + const description = output + .split( '\n' ) + .reverse() + .find( ( line ) => line.startsWith( '{' ) ); + + if ( ! description ) { + throw new Error( `The seed printed no site description:\n${ output }` ); + } + + const site = JSON.parse( description ) as SeededSite; + + mkdirSync( dirname( SITE_STATE ), { recursive: true } ); + writeFileSync( SITE_STATE, JSON.stringify( site, null, '\t' ) ); + + return site; +} + +export function seededSite(): SeededSite { + return JSON.parse( readFileSync( SITE_STATE, 'utf8' ) ) as SeededSite; +} + +export function subscriptionStatus( subscriptionId: number ): string { + const output = runWpCli( [ + 'eval', + `echo 'status:' . wcs_get_subscription( ${ subscriptionId } )->get_status();`, + ] ); + + const status = output.match( /status:(\S+)/ ); + + if ( ! status ) { + throw new Error( `WP-CLI reported no status:\n${ output }` ); + } + + return status[ 1 ]; +} diff --git a/tests/E2E/support/wp-cli.ts b/tests/E2E/support/wp-cli.ts new file mode 100644 index 0000000..94c69fc --- /dev/null +++ b/tests/E2E/support/wp-cli.ts @@ -0,0 +1,16 @@ +import { execFileSync } from 'node:child_process'; + +const WP_CLI_ON_THE_LOCAL_STACK = + 'docker exec -i -u www-data wordpress-docker-wordpress-1 wp --path=/var/www/html'; + +export function runWpCli( args: readonly string[], stdin = '' ): string { + const [ command, ...prefix ] = ( + process.env.WP_CLI || WP_CLI_ON_THE_LOCAL_STACK + ).split( /\s+/ ); + + return execFileSync( command, [ ...prefix, ...args ], { + input: stdin, + encoding: 'utf8', + maxBuffer: 16 * 1024 * 1024, + } ); +} diff --git a/tests/Integration/Includes/AccountNavigationTest.php b/tests/Integration/Includes/AccountNavigationTest.php index ed030ac..02911e6 100644 --- a/tests/Integration/Includes/AccountNavigationTest.php +++ b/tests/Integration/Includes/AccountNavigationTest.php @@ -10,8 +10,6 @@ use WP_UnitTestCase; /** - * Covers the parts that read the request being rendered or hook into WooCommerce. - * * WooCommerce is not loaded in the test suite, so what is asserted here is the * registration of the hooks and the behaviour of the callbacks when they are * called with the arguments WooCommerce passes them. @@ -95,86 +93,34 @@ public function test_the_endpoint_title_filters_are_registered_for_the_renamed_e $this->assertSame( 20, has_filter( 'woocommerce_endpoint_view-order_title', 'libresign_filter_view_order_endpoint_title' ) ); } - public function test_the_endpoint_title_follows_the_navigation_label() { - $this->render_request_with( array( 'orders' => '' ) ); - - $this->assertSame( 'Invoices', libresign_filter_account_endpoint_title( 'Orders', 'orders' ) ); - } - - public function test_the_endpoint_title_of_a_paginated_screen_carries_the_page_number() { + public function test_the_endpoint_title_is_built_from_the_request_being_rendered() { $this->render_request_with( array( 'orders' => '3' ) ); $this->assertSame( 'Invoices (page 3)', libresign_filter_account_endpoint_title( 'Orders', 'orders' ) ); } - public function test_the_first_page_is_not_numbered() { - $this->render_request_with( array( 'orders' => '1' ) ); - - $this->assertSame( 'Invoices', libresign_filter_account_endpoint_title( 'Orders', 'orders' ) ); - } - - public function test_an_entry_that_was_not_renamed_keeps_the_woocommerce_title() { - $this->render_request_with( array( 'edit-account' => '' ) ); - - $this->assertSame( 'Account details', libresign_filter_account_endpoint_title( 'Account details', 'edit-account' ) ); - } - - /** - * @dataProvider provide_detail_screens - * - * @param array $query_vars Query vars of the request being rendered. - * @param string $endpoint Navigation entry being rendered. - * @param string[] $classes Classes WooCommerce computed. - * @param bool $active Whether the entry ends up highlighted. - */ - public function test_a_detail_screen_highlights_the_entry_it_belongs_to( $query_vars, $endpoint, $classes, $active ) { - $this->render_request_with( $query_vars ); - - $filtered = libresign_filter_account_menu_item_classes( $classes, $endpoint ); - - $this->assertSame( $active, in_array( 'is-active', $filtered, true ) ); - } + public function test_the_highlighted_entry_is_built_from_the_request_being_rendered() { + $this->render_request_with( array( 'edit-address' => 'billing' ) ); - /** - * @return iterable, 1: string, 2: string[], 3: bool}> - */ - public static function provide_detail_screens() { - yield 'changing the payment method of a subscription' => array( - array( 'subscription-payment-method' => '12' ), - 'subscriptions', - array( 'subscriptions' ), - true, - ); - yield 'editing an address' => array( - array( 'edit-address' => 'billing' ), - 'payment-methods', - array( 'payment-methods' ), - true, - ); - yield 'an entry without detail screens' => array( - array( 'edit-address' => 'billing' ), - 'orders', - array( 'orders' ), - false, - ); - yield 'an entry whose detail screen is not being rendered' => array( - array( 'orders' => '' ), - 'payment-methods', - array( 'payment-methods' ), - false, - ); - yield 'an entry WooCommerce already highlighted' => array( - array(), - 'orders', - array( 'orders', 'is-active' ), - true, + $this->assertContains( + 'is-active', + libresign_filter_account_menu_item_classes( array( 'payment-methods' ), 'payment-methods' ) ); } - public function test_the_classes_of_an_unrelated_entry_are_untouched() { - $this->render_request_with( array( 'edit-address' => 'billing' ) ); - - $this->assertSame( array( 'orders' ), libresign_filter_account_menu_item_classes( array( 'orders' ), 'orders' ) ); + public function test_the_navigation_is_reordered_when_woocommerce_filters_it() { + $this->assertSame( + array( 'orders', 'customer-logout' ), + array_keys( + libresign_filter_account_menu_items( + array( + 'dashboard' => 'Dashboard', + 'orders' => 'Orders', + 'customer-logout' => 'Log out', + ) + ) + ) + ); } public function test_the_addresses_are_described_in_terms_of_the_subscription() { diff --git a/tests/Integration/LibresignWpCustomizationsTest.php b/tests/Integration/LibresignWpCustomizationsTest.php index 1540b49..f41f79b 100644 --- a/tests/Integration/LibresignWpCustomizationsTest.php +++ b/tests/Integration/LibresignWpCustomizationsTest.php @@ -41,6 +41,7 @@ public function set_up() { do_action( 'rest_api_init', $wp_rest_server ); update_option( 'libresign_github_deploy_organization_repository', 'LibreSign/site' ); + update_option( 'libresign_github_deploy_token', PluginSecret::encrypt( 'deploy-token' ) ); } public function tear_down() { @@ -194,6 +195,24 @@ public function test_a_token_saved_for_the_first_time_is_stored_encrypted() { $this->assertSame( 'Bearer a-personal-access-token', $this->github->args()['headers']['Authorization'] ); } + public function test_a_deploy_token_that_cannot_be_decrypted_is_not_sent_to_github() { + update_option( 'libresign_github_deploy_token', PluginSecret::encrypt_with_other_salts( 'a-personal-access-token' ) ); + $this->github->answer_with( FakeHttp::response( 204 ) ); + + self::factory()->post->create( array( 'post_status' => 'publish' ) ); + + $this->assertSame( array(), $this->github->urls() ); + } + + public function test_a_setting_of_zero_is_stored_instead_of_being_taken_for_a_blank_field() { + $this->register_plugin_settings(); + + update_option( 'libresign_github_webhook_secret', 'the-previous-secret' ); + update_option( 'libresign_github_webhook_secret', '0' ); + + $this->assertSame( '0', libresign_github_webhook_secret() ); + } + public function test_saving_an_empty_deploy_token_keeps_the_previous_one() { update_option( 'libresign_github_deploy_token', 'placeholder' ); $this->register_plugin_settings(); @@ -324,6 +343,18 @@ public function test_the_rewrite_rules_are_flushed_once_per_version() { $this->assertSame( LIBRESIGN_WP_REWRITE_VERSION, get_option( 'libresign_root_my_account_rewrite_version' ) ); } + public function test_the_settings_link_comes_first_on_the_plugins_screen() { + $links = libresign_add_settings_link( array( 'Deactivate' ) ); + + $this->assertCount( 2, $links ); + $this->assertStringContainsString( 'options-general.php?page=libresign-config', $links[0] ); + $this->assertSame( 'Deactivate', $links[1] ); + } + + public function test_the_settings_are_registered_on_admin_init() { + $this->assertSame( 10, has_action( 'admin_init', 'libresign_register_settings' ) ); + } + public function test_the_post_author_is_exposed_with_a_gravatar_hash() { $user_id = (int) self::factory()->user->create( array( diff --git a/tests/Support/PluginSecret.php b/tests/Support/PluginSecret.php index a8a7e0d..0a8d228 100644 --- a/tests/Support/PluginSecret.php +++ b/tests/Support/PluginSecret.php @@ -29,4 +29,22 @@ public static function encrypt( $value ) { ) ); } + + /** + * Encrypt the way another installation would, so this one cannot read it. + * + * @param string $value Plain text value. + * @return string + */ + public static function encrypt_with_other_salts( $value ) { + return base64_encode( + openssl_encrypt( + $value, + 'AES-256-CBC', + hash( 'sha256', 'another-auth-key' . 'another-secure-auth-salt' ), + 0, + substr( hash( 'sha256', 'another-nonce-salt' ), 0, 16 ) + ) + ); + } } diff --git a/tests/Support/RegistersPluginSettings.php b/tests/Support/RegistersPluginSettings.php index 020627a..6b94702 100644 --- a/tests/Support/RegistersPluginSettings.php +++ b/tests/Support/RegistersPluginSettings.php @@ -7,15 +7,11 @@ namespace LibreSign\WPCustomizations\Tests\Support; -use Closure; -use ReflectionFunction; - /** - * Runs only the plugin callbacks hooked on admin_init. + * Registers the plugin settings without firing admin_init. * * The whole hook cannot be fired from a test because core sends headers there - * and warns about being called outside wp-admin. Registering the settings from - * a named function would make this helper unnecessary. + * and warns about being called outside wp-admin. */ trait RegistersPluginSettings { @@ -25,22 +21,6 @@ trait RegistersPluginSettings { * @return void */ protected function register_plugin_settings() { - global $wp_filter; - - $plugin_file = dirname( __DIR__, 2 ) . '/libresign-wp-customizations.php'; - - foreach ( $wp_filter['admin_init']->callbacks as $callbacks ) { - foreach ( $callbacks as $callback ) { - if ( ! $callback['function'] instanceof Closure ) { - continue; - } - - $declaration = new ReflectionFunction( $callback['function'] ); - - if ( $declaration->getFileName() === $plugin_file ) { - $declaration->invoke(); - } - } - } + libresign_register_settings(); } } diff --git a/tests/Unit/Account/NavigationTest.php b/tests/Unit/Account/NavigationTest.php new file mode 100644 index 0000000..556b4b1 --- /dev/null +++ b/tests/Unit/Account/NavigationTest.php @@ -0,0 +1,191 @@ + + */ + private static function woocommerce_menu() { + return array( + 'dashboard' => 'Dashboard', + 'orders' => 'Orders', + 'subscriptions' => 'Subscriptions', + 'downloads' => 'Downloads', + 'edit-address' => 'Addresses', + 'payment-methods' => 'Payment methods', + 'edit-account' => 'Account details', + 'customer-logout' => 'Log out', + ); + } + + public function test_keeps_five_entries_in_the_configured_order() { + $this->assertSame( + array( 'subscriptions', 'orders', 'payment-methods', 'edit-account', 'customer-logout' ), + array_keys( Navigation::filter_items( self::woocommerce_menu() ) ) + ); + } + + public function test_renames_only_the_three_entries_whose_meaning_changed() { + $menu = Navigation::filter_items( self::woocommerce_menu() ); + + $this->assertSame( 'My subscription', $menu['subscriptions'] ); + $this->assertSame( 'Invoices', $menu['orders'] ); + $this->assertSame( 'Billing', $menu['payment-methods'] ); + $this->assertSame( 'Account details', $menu['edit-account'] ); + $this->assertSame( 'Log out', $menu['customer-logout'] ); + } + + /** + * @dataProvider provide_menus + * + * @param array $items Navigation handed over by WooCommerce. + * @param string[] $expected Expected resulting keys. + */ + public function test_filter_items( $items, $expected ) { + $this->assertSame( $expected, array_keys( Navigation::filter_items( $items ) ) ); + } + + /** + * @return iterable, 1: string[]}> + */ + public static function provide_menus() { + yield 'entries outside the list are dropped' => array( + self::woocommerce_menu(), + array( 'subscriptions', 'orders', 'payment-methods', 'edit-account', 'customer-logout' ), + ); + yield 'unregistered endpoints are skipped' => array( + array( + 'dashboard' => 'Dashboard', + 'orders' => 'Orders', + 'customer-logout' => 'Log out', + ), + array( 'orders', 'customer-logout' ), + ); + yield 'the input order is irrelevant' => array( + array( + 'customer-logout' => 'Log out', + 'orders' => 'Orders', + 'subscriptions' => 'Subscriptions', + ), + array( 'subscriptions', 'orders', 'customer-logout' ), + ); + yield 'an empty navigation stays empty' => array( array(), array() ); + yield 'a navigation with nothing we keep' => array( + array( + 'dashboard' => 'Dashboard', + 'downloads' => 'Downloads', + ), + array(), + ); + } + + public function test_renamed_entries_are_the_ones_with_a_matching_endpoint_title() { + $this->assertSame( + array( 'subscriptions', 'orders', 'payment-methods' ), + array_keys( Navigation::labels() ) + ); + } + + public function test_detail_endpoints_are_mapped_to_the_entry_they_belong_to() { + $aliases = Navigation::aliases(); + + $this->assertSame( array( 'subscription-payment-method' ), $aliases['subscriptions'] ); + $this->assertSame( array( 'edit-address' ), $aliases['payment-methods'] ); + $this->assertSame( array( 'subscriptions', 'payment-methods' ), array_keys( $aliases ) ); + } + + /** + * @dataProvider provide_endpoint_titles + * + * @param string $title Title WooCommerce would use. + * @param string $endpoint Endpoint being rendered. + * @param array $query_vars Query vars of the request being rendered. + * @param string $expected Expected title. + */ + public function test_endpoint_title( $title, $endpoint, $query_vars, $expected ) { + $this->assertSame( $expected, Navigation::endpoint_title( $title, $endpoint, $query_vars ) ); + } + + /** + * @return iterable, 3: string}> + */ + public static function provide_endpoint_titles() { + yield 'the title follows the navigation label' => array( 'Orders', 'orders', array( 'orders' => '' ), 'Invoices' ); + yield 'a paginated screen carries the page number' => array( 'Orders', 'orders', array( 'orders' => '3' ), 'Invoices (page 3)' ); + yield 'the first page is not numbered' => array( 'Orders', 'orders', array( 'orders' => '1' ), 'Invoices' ); + yield 'an entry that was not renamed keeps its title' => array( 'Account details', 'edit-account', array( 'edit-account' => '' ), 'Account details' ); + yield 'an endpoint absent from the request is not numbered' => array( 'Orders', 'orders', array(), 'Invoices' ); + } + + /** + * @dataProvider provide_detail_screens + * + * @param string[] $classes Classes WooCommerce computed. + * @param string $endpoint Navigation entry being rendered. + * @param array $query_vars Query vars of the request being rendered. + * @param bool $active Whether the entry ends up highlighted. + */ + public function test_filter_item_classes( $classes, $endpoint, $query_vars, $active ) { + $filtered = Navigation::filter_item_classes( $classes, $endpoint, $query_vars ); + + $this->assertSame( $active, in_array( 'is-active', $filtered, true ) ); + } + + /** + * @return iterable, 3: bool}> + */ + public static function provide_detail_screens() { + yield 'changing the payment method of a subscription' => array( + array( 'subscriptions' ), + 'subscriptions', + array( 'subscription-payment-method' => '12' ), + true, + ); + yield 'editing an address' => array( + array( 'payment-methods' ), + 'payment-methods', + array( 'edit-address' => 'billing' ), + true, + ); + yield 'an entry without detail screens' => array( + array( 'orders' ), + 'orders', + array( 'edit-address' => 'billing' ), + false, + ); + yield 'an entry whose detail screen is not being rendered' => array( + array( 'payment-methods' ), + 'payment-methods', + array( 'orders' => '' ), + false, + ); + yield 'an entry WooCommerce already highlighted' => array( + array( 'orders', 'is-active' ), + 'orders', + array(), + true, + ); + } + + public function test_the_classes_of_an_unrelated_entry_are_untouched() { + $this->assertSame( + array( 'orders' ), + Navigation::filter_item_classes( array( 'orders' ), 'orders', array( 'edit-address' => 'billing' ) ) + ); + } +} diff --git a/tests/Unit/Account/RootEndpointTest.php b/tests/Unit/Account/RootEndpointTest.php new file mode 100644 index 0000000..3a8af46 --- /dev/null +++ b/tests/Unit/Account/RootEndpointTest.php @@ -0,0 +1,69 @@ + + */ + private static function query_vars() { + return array( + 'order-pay' => 'order-pay', + 'order-received' => 'order-received', + 'orders' => 'orders', + 'edit-account' => 'edit-account', + 'edit-address' => 'edit-address', + 'payment-methods' => 'payment-methods', + 'lost-password' => 'lost-password', + 'customer-logout' => 'customer-logout', + 'subscriptions' => 'subscriptions', + 'add-payment-method' => '', + ); + } + + /** + * @dataProvider provide_paths + * + * @param string $request_path Path of the request. + * @param bool $expected Whether it is an account endpoint. + */ + public function test_matches( $request_path, $expected ) { + $this->assertSame( $expected, RootEndpoint::matches( $request_path, self::query_vars() ) ); + } + + /** + * @return iterable + */ + public static function provide_paths() { + yield 'an endpoint at the root' => array( '/payment-methods/', true ); + yield 'an endpoint with a value' => array( '/edit-address/billing/', true ); + yield 'the my-account alias' => array( '/my-account/', true ); + yield 'an endpoint under the alias' => array( '/my-account/orders/', true ); + yield 'no slashes around the path' => array( 'lost-password', true ); + yield 'the front page itself' => array( '/', false ); + yield 'an empty path' => array( '', false ); + yield 'a page' => array( '/planos/', false ); + yield 'a post' => array( '/2026/09/hello-world/', false ); + yield 'an endpoint deeper in the path' => array( '/planos/orders/', false ); + yield 'an endpoint that is not registered' => array( '/add-payment-method/', false ); + } + + public function test_a_store_without_endpoints_only_knows_the_alias() { + $this->assertTrue( RootEndpoint::matches( '/my-account/', array() ) ); + $this->assertFalse( RootEndpoint::matches( '/orders/', array() ) ); + } +} diff --git a/tests/Unit/Github/DeployDispatchTest.php b/tests/Unit/Github/DeployDispatchTest.php new file mode 100644 index 0000000..c72f564 --- /dev/null +++ b/tests/Unit/Github/DeployDispatchTest.php @@ -0,0 +1,63 @@ +assertSame( $expected, DeployDispatch::triggers_deploy( $new_status, $old_status, $post_type ) ); + } + + /** + * @return iterable + */ + public static function provide_transitions() { + yield 'publishing a post' => array( 'publish', 'draft', 'post', true ); + yield 'publishing a page' => array( 'publish', 'draft', 'page', false ); + yield 'publishing a product' => array( 'publish', 'draft', 'product', false ); + yield 'updating a published post' => array( 'publish', 'publish', 'post', true ); + yield 'trashing a published post' => array( 'trash', 'publish', 'post', true ); + yield 'trashing a published page' => array( 'trash', 'publish', 'page', false ); + yield 'trashing a published product' => array( 'trash', 'publish', 'product', false ); + yield 'saving a draft' => array( 'draft', 'draft', 'post', false ); + yield 'scheduling a post' => array( 'future', 'draft', 'post', false ); + yield 'restoring a post from trash' => array( 'draft', 'trash', 'post', false ); + } + + public function test_the_success_notice_links_to_the_actions_of_the_repository() { + $this->assertStringContainsString( + 'https://github.com/LibreSign/site/actions', + DeployDispatch::success_message( 'LibreSign/site' ) + ); + } + + public function test_the_failure_notice_carries_the_status_and_the_message() { + $message = DeployDispatch::failure_message( 401, 'Bad credentials' ); + + $this->assertStringContainsString( '401', $message ); + $this->assertStringContainsString( 'Bad credentials', $message ); + } + + public function test_the_failure_notice_survives_a_response_without_a_message() { + $this->assertStringContainsString( '500', DeployDispatch::failure_message( 500, '' ) ); + } +} diff --git a/tests/Unit/Github/SiteDeployTest.php b/tests/Unit/Github/SiteDeployTest.php new file mode 100644 index 0000000..733cf5d --- /dev/null +++ b/tests/Unit/Github/SiteDeployTest.php @@ -0,0 +1,111 @@ + $overrides Values replacing the defaults. + * @return WorkflowRun + */ + private static function production_run( array $overrides = array() ) { + return WorkflowRun::from_payload( + array_replace_recursive( + array( + 'action' => 'completed', + 'repository' => array( 'full_name' => 'LibreSign/site' ), + 'workflow_run' => array( + 'name' => 'pages build and deployment', + 'conclusion' => 'success', + 'head_branch' => 'gh-pages', + ), + ), + $overrides + ) + ); + } + + public function test_the_production_deploy_is_recognized() { + $this->assertTrue( self::site_deploy()->is_production_run( self::production_run() ) ); + } + + public function test_the_workflow_name_may_come_from_the_workflow_instead_of_the_run() { + $workflow_run = WorkflowRun::from_payload( + array( + 'action' => 'completed', + 'repository' => array( 'full_name' => 'LibreSign/site' ), + 'workflow' => array( 'name' => 'pages build and deployment' ), + 'workflow_run' => array( + 'conclusion' => 'success', + 'head_branch' => 'gh-pages', + ), + ) + ); + + $this->assertTrue( self::site_deploy()->is_production_run( $workflow_run ) ); + } + + /** + * @dataProvider provide_other_runs + * + * @param array $overrides Values replacing the production defaults. + */ + public function test_any_other_run_is_not_the_production_deploy( $overrides ) { + $this->assertFalse( self::site_deploy()->is_production_run( self::production_run( $overrides ) ) ); + } + + /** + * @return iterable}> + */ + public static function provide_other_runs() { + yield 'another repository' => array( array( 'repository' => array( 'full_name' => 'LibreSign/libresign' ) ) ); + yield 'the run is starting' => array( array( 'action' => 'requested' ) ); + yield 'the run is in progress' => array( array( 'action' => 'in_progress' ) ); + yield 'the run failed' => array( array( 'workflow_run' => array( 'conclusion' => 'failure' ) ) ); + yield 'the run was cancelled' => array( array( 'workflow_run' => array( 'conclusion' => 'cancelled' ) ) ); + yield 'another branch' => array( array( 'workflow_run' => array( 'head_branch' => 'main' ) ) ); + yield 'another workflow' => array( array( 'workflow_run' => array( 'name' => 'tests' ) ) ); + } + + public function test_the_expected_run_is_the_configured_one() { + $site_deploy = new SiteDeploy( 'LibreSign/staging', 'main', 'deploy' ); + + $this->assertFalse( $site_deploy->is_production_run( self::production_run() ) ); + $this->assertTrue( + $site_deploy->is_production_run( + self::production_run( + array( + 'repository' => array( 'full_name' => 'LibreSign/staging' ), + 'workflow_run' => array( + 'name' => 'deploy', + 'head_branch' => 'main', + ), + ) + ) + ) + ); + } +} diff --git a/tests/Unit/Github/WebhookGateTest.php b/tests/Unit/Github/WebhookGateTest.php new file mode 100644 index 0000000..d056d5e --- /dev/null +++ b/tests/Unit/Github/WebhookGateTest.php @@ -0,0 +1,226 @@ + $overrides Values replacing the defaults. + * @return string + */ + private static function production_payload( array $overrides = array() ) { + return (string) wp_json_encode( + array_replace_recursive( + array( + 'action' => 'completed', + 'repository' => array( 'full_name' => 'LibreSign/site' ), + 'workflow_run' => array( + 'name' => 'pages build and deployment', + 'conclusion' => 'success', + 'head_branch' => 'gh-pages', + 'head_sha' => '0f1e2d3', + ), + ), + $overrides + ) + ); + } + + /** + * The gate as configured in production. + * + * @param string $secret Secret shared with the repository webhook. + * @return WebhookGate + */ + private static function gate( $secret = self::SECRET ) { + return new WebhookGate( + $secret, + new SiteDeploy( 'LibreSign/site', 'gh-pages', 'pages build and deployment' ) + ); + } + + /** + * A delivery signed with the secret. + * + * @param string $event X-GitHub-Event header. + * @param string $body Raw request body. + * @return WebhookRequest + */ + private static function signed_delivery( $event, $body ) { + return new WebhookRequest( + 'GitHub-Hookshot/044aadd', + $event, + 'sha256=' . hash_hmac( 'sha256', $body, self::SECRET ), + 'delivery-1', + $body + ); + } + + public function test_an_unconfigured_secret_makes_the_endpoint_unavailable() { + $decision = self::gate( '' )->decide( self::signed_delivery( 'ping', '{}' ) ); + + $this->assertTrue( $decision->is_rejected() ); + $this->assertSame( 'libresign_github_webhook_secret_missing', $decision->code() ); + $this->assertSame( 503, $decision->status() ); + } + + public function test_a_blank_secret_makes_the_endpoint_unavailable() { + $decision = self::gate( ' ' )->decide( self::signed_delivery( 'ping', '{}' ) ); + + $this->assertSame( 'libresign_github_webhook_secret_missing', $decision->code() ); + } + + public function test_a_request_from_another_client_is_rejected() { + $decision = self::gate()->decide( new WebhookRequest( 'curl/8.7.1', 'ping', '', 'delivery-1', '{}' ) ); + + $this->assertTrue( $decision->is_rejected() ); + $this->assertSame( 'libresign_github_webhook_invalid_agent', $decision->code() ); + $this->assertSame( 403, $decision->status() ); + } + + public function test_a_wrongly_signed_request_is_rejected() { + $decision = self::gate()->decide( + new WebhookRequest( + 'GitHub-Hookshot/044aadd', + 'ping', + 'sha256=' . hash_hmac( 'sha256', '{}', 'another-secret' ), + 'delivery-1', + '{}' + ) + ); + + $this->assertTrue( $decision->is_rejected() ); + $this->assertSame( 'libresign_github_webhook_invalid_signature', $decision->code() ); + $this->assertSame( 403, $decision->status() ); + } + + public function test_the_client_is_checked_before_the_signature() { + $decision = self::gate()->decide( new WebhookRequest( 'curl/8.7.1', 'ping', 'sha256=deadbeef', 'delivery-1', '{}' ) ); + + $this->assertSame( 'libresign_github_webhook_invalid_agent', $decision->code() ); + } + + public function test_a_ping_is_answered() { + $decision = self::gate()->decide( self::signed_delivery( 'ping', '{"zen":"Design for failure."}' ) ); + + $this->assertTrue( $decision->is_pong() ); + } + + public function test_another_event_is_ignored() { + $decision = self::gate()->decide( self::signed_delivery( 'push', '{"ref":"refs/heads/main"}' ) ); + + $this->assertTrue( $decision->is_ignored() ); + $this->assertSame( + array( + 'reason' => 'unsupported_event', + 'event' => 'push', + ), + $decision->data() + ); + } + + public function test_a_body_that_is_not_json_is_rejected() { + $decision = self::gate()->decide( self::signed_delivery( 'workflow_run', 'not json' ) ); + + $this->assertTrue( $decision->is_rejected() ); + $this->assertSame( 'libresign_github_webhook_invalid_payload', $decision->code() ); + $this->assertSame( 400, $decision->status() ); + } + + /** + * @dataProvider provide_non_production_payloads + * + * @param array $overrides Values replacing the production defaults. + * @param array $reported_details Details expected in the answer. + */ + public function test_a_run_that_is_not_the_production_deploy_is_ignored( $overrides, $reported_details ) { + $decision = self::gate()->decide( self::signed_delivery( 'workflow_run', self::production_payload( $overrides ) ) ); + + $this->assertTrue( $decision->is_ignored() ); + $this->assertSame( array_merge( array( 'reason' => 'not_production_deploy' ), $reported_details ), $decision->data() ); + } + + /** + * @return iterable, 1: array}> + */ + public static function provide_non_production_payloads() { + yield 'another repository' => array( + array( 'repository' => array( 'full_name' => 'LibreSign/libresign' ) ), + array( + 'repository' => 'LibreSign/libresign', + 'workflow_name' => 'pages build and deployment', + 'head_branch' => 'gh-pages', + 'conclusion' => 'success', + ), + ); + yield 'the run is starting' => array( + array( 'action' => 'requested' ), + array( + 'repository' => 'LibreSign/site', + 'workflow_name' => 'pages build and deployment', + 'head_branch' => 'gh-pages', + 'conclusion' => 'success', + ), + ); + yield 'the run failed' => array( + array( 'workflow_run' => array( 'conclusion' => 'failure' ) ), + array( + 'repository' => 'LibreSign/site', + 'workflow_name' => 'pages build and deployment', + 'head_branch' => 'gh-pages', + 'conclusion' => 'failure', + ), + ); + yield 'another branch' => array( + array( 'workflow_run' => array( 'head_branch' => 'main' ) ), + array( + 'repository' => 'LibreSign/site', + 'workflow_name' => 'pages build and deployment', + 'head_branch' => 'main', + 'conclusion' => 'success', + ), + ); + yield 'another workflow' => array( + array( 'workflow_run' => array( 'name' => 'tests' ) ), + array( + 'repository' => 'LibreSign/site', + 'workflow_name' => 'tests', + 'head_branch' => 'gh-pages', + 'conclusion' => 'success', + ), + ); + } + + public function test_a_decision_that_is_not_a_deploy_has_no_workflow_run() { + $decision = self::gate()->decide( self::signed_delivery( 'push', '{"ref":"refs/heads/main"}' ) ); + + $this->expectException( LogicException::class ); + + $decision->workflow_run(); + } + + public function test_a_production_deploy_is_handed_over_with_the_run() { + $decision = self::gate()->decide( self::signed_delivery( 'workflow_run', self::production_payload() ) ); + + $this->assertTrue( $decision->is_deploy() ); + $this->assertSame( '0f1e2d3', $decision->workflow_run()->head_sha() ); + } +} diff --git a/tests/Unit/Github/WebhookRequestTest.php b/tests/Unit/Github/WebhookRequestTest.php new file mode 100644 index 0000000..d2ecc8c --- /dev/null +++ b/tests/Unit/Github/WebhookRequestTest.php @@ -0,0 +1,124 @@ + $headers Values replacing the empty defaults. + * @return WebhookRequest + */ + private static function delivery( array $headers = array() ) { + $headers = array_merge( + array( + 'user_agent' => 'GitHub-Hookshot/044aadd', + 'event' => 'workflow_run', + 'signature' => '', + 'delivery_id' => 'delivery-1', + 'body' => '{}', + ), + $headers + ); + + return new WebhookRequest( + $headers['user_agent'], + $headers['event'], + $headers['signature'], + $headers['delivery_id'], + $headers['body'] + ); + } + + /** + * @dataProvider provide_user_agents + * + * @param string $user_agent User agent header. + * @param bool $expected Expected result. + */ + public function test_is_from_github( $user_agent, $expected ) { + $this->assertSame( $expected, self::delivery( array( 'user_agent' => $user_agent ) )->is_from_github() ); + } + + /** + * @return iterable + */ + public static function provide_user_agents() { + yield 'the agent GitHub sends' => array( 'GitHub-Hookshot/044aadd', true ); + yield 'surrounding whitespace' => array( " GitHub-Hookshot/044aadd\n", true ); + yield 'the match is case sensitive' => array( 'github-hookshot/044aadd', false ); + yield 'the prefix has to come first' => array( 'curl/8.7.1 GitHub-Hookshot/044aadd', false ); + yield 'another client' => array( 'curl/8.7.1', false ); + yield 'no user agent at all' => array( '', false ); + } + + /** + * @dataProvider provide_events + * + * @param string $event X-GitHub-Event header. + * @param string $expected Normalized event name. + */ + public function test_event( $event, $expected ) { + $this->assertSame( $expected, self::delivery( array( 'event' => $event ) )->event() ); + } + + /** + * @return iterable + */ + public static function provide_events() { + yield 'as GitHub sends it' => array( 'workflow_run', 'workflow_run' ); + yield 'uppercase' => array( 'Workflow_Run', 'workflow_run' ); + yield 'padded' => array( " ping \n", 'ping' ); + yield 'absent' => array( '', '' ); + } + + public function test_the_delivery_identifier_is_kept_as_received() { + $this->assertSame( ' delivery-1 ', self::delivery( array( 'delivery_id' => ' delivery-1 ' ) )->delivery_id() ); + } + + public function test_the_signature_is_checked_against_the_body() { + $delivery = self::delivery( + array( + 'body' => '{"action":"completed"}', + 'signature' => 'sha256=' . hash_hmac( 'sha256', '{"action":"completed"}', 'the-secret' ), + ) + ); + + $this->assertTrue( $delivery->has_valid_signature( 'the-secret' ) ); + $this->assertFalse( $delivery->has_valid_signature( 'another-secret' ) ); + } + + /** + * @dataProvider provide_bodies + * + * @param string $body Raw request body. + * @param array|null $expected Expected payload. + */ + public function test_payload( $body, $expected ) { + $this->assertSame( $expected, self::delivery( array( 'body' => $body ) )->payload() ); + } + + /** + * @return iterable|null}> + */ + public static function provide_bodies() { + yield 'a json object' => array( '{"action":"completed"}', array( 'action' => 'completed' ) ); + yield 'an empty object' => array( '{}', array() ); + yield 'a json array' => array( '[1,2]', array( 1, 2 ) ); + yield 'not json at all' => array( 'not json', null ); + yield 'a json scalar' => array( '"completed"', null ); + yield 'an empty body' => array( '', null ); + } +} diff --git a/tests/Unit/Github/WebhookSignatureTest.php b/tests/Unit/Github/WebhookSignatureTest.php new file mode 100644 index 0000000..012d581 --- /dev/null +++ b/tests/Unit/Github/WebhookSignatureTest.php @@ -0,0 +1,57 @@ +assertSame( $expected, WebhookSignature::matches( $body, $signature, $secret ) ); + } + + /** + * @return iterable + */ + public static function provide_signatures() { + yield 'signature sent the way GitHub sends it' => array( self::BODY, 'sha256=' . self::digest(), self::SECRET, true ); + yield 'prefix is optional' => array( self::BODY, self::digest(), self::SECRET, true ); + yield 'prefix is case insensitive' => array( self::BODY, 'SHA256=' . self::digest(), self::SECRET, true ); + yield 'hexadecimal is case insensitive' => array( self::BODY, strtoupper( self::digest() ), self::SECRET, true ); + yield 'surrounding whitespace is trimmed' => array( self::BODY, ' sha256=' . self::digest() . "\n", ' ' . self::SECRET . ' ', true ); + yield 'another secret does not match' => array( self::BODY, 'sha256=' . self::digest(), 'another-secret', false ); + yield 'a tampered body does not match' => array( self::BODY . ' ', 'sha256=' . self::digest(), self::SECRET, false ); + yield 'an empty body is never valid' => array( '', 'sha256=' . self::digest(), self::SECRET, false ); + yield 'an empty secret is never valid' => array( self::BODY, 'sha256=' . self::digest(), '', false ); + yield 'an empty signature is never valid' => array( self::BODY, '', self::SECRET, false ); + yield 'a non hexadecimal signature is refused' => array( self::BODY, 'sha256=not-a-digest', self::SECRET, false ); + yield 'a truncated signature is refused' => array( self::BODY, 'sha256=' . substr( self::digest(), 0, 32 ), self::SECRET, false ); + } +} diff --git a/tests/Unit/Github/WorkflowRunTest.php b/tests/Unit/Github/WorkflowRunTest.php new file mode 100644 index 0000000..7e8c320 --- /dev/null +++ b/tests/Unit/Github/WorkflowRunTest.php @@ -0,0 +1,115 @@ + 'completed', + 'repository' => array( 'full_name' => 'LibreSign/site' ), + 'workflow_run' => array( + 'name' => 'pages build and deployment', + 'conclusion' => 'success', + 'head_branch' => 'gh-pages', + 'head_sha' => '0f1e2d3', + 'html_url' => 'https://github.com/LibreSign/site/actions/runs/1', + 'updated_at' => '2026-09-14T12:00:00Z', + ), + ) + ); + + $this->assertSame( 'LibreSign/site', $workflow_run->repository() ); + $this->assertSame( 'completed', $workflow_run->action() ); + $this->assertSame( 'success', $workflow_run->conclusion() ); + $this->assertSame( 'gh-pages', $workflow_run->head_branch() ); + $this->assertSame( '0f1e2d3', $workflow_run->head_sha() ); + $this->assertSame( 'https://github.com/LibreSign/site/actions/runs/1', $workflow_run->html_url() ); + $this->assertSame( '2026-09-14T12:00:00Z', $workflow_run->updated_at() ); + $this->assertSame( 'pages build and deployment', $workflow_run->workflow_name() ); + } + + public function test_an_empty_payload_answers_every_field_with_an_empty_string() { + $workflow_run = WorkflowRun::from_payload( array() ); + + $this->assertSame( '', $workflow_run->repository() ); + $this->assertSame( '', $workflow_run->action() ); + $this->assertSame( '', $workflow_run->conclusion() ); + $this->assertSame( '', $workflow_run->head_branch() ); + $this->assertSame( '', $workflow_run->head_sha() ); + $this->assertSame( '', $workflow_run->html_url() ); + $this->assertSame( '', $workflow_run->updated_at() ); + $this->assertSame( '', $workflow_run->workflow_name() ); + } + + /** + * @dataProvider provide_payloads + * + * @param array $payload Parsed webhook payload. + * @param string $expected Expected workflow name. + */ + public function test_workflow_name( $payload, $expected ) { + $this->assertSame( $expected, WorkflowRun::from_payload( $payload )->workflow_name() ); + } + + /** + * @return iterable, 1: string}> + */ + public static function provide_payloads() { + yield 'the run name wins' => array( + array( + 'workflow_run' => array( 'name' => 'pages build and deployment' ), + 'workflow' => array( 'name' => 'deploy' ), + ), + 'pages build and deployment', + ); + yield 'falls back to the workflow name' => array( + array( 'workflow' => array( 'name' => 'deploy' ) ), + 'deploy', + ); + yield 'a blank run name falls back too' => array( + array( + 'workflow_run' => array( 'name' => ' ' ), + 'workflow' => array( 'name' => 'deploy' ), + ), + 'deploy', + ); + yield 'names are trimmed' => array( + array( 'workflow_run' => array( 'name' => " deploy \n" ) ), + 'deploy', + ); + yield 'neither is present' => array( array( 'action' => 'completed' ), '' ); + yield 'an empty payload' => array( array(), '' ); + } + + /** + * @dataProvider provide_unexpected_shapes + * + * @param array $payload Payload in a shape GitHub does not send. + */ + public function test_a_field_that_is_not_a_string_is_read_as_empty( $payload ) { + $this->assertSame( '', WorkflowRun::from_payload( $payload )->head_branch() ); + } + + /** + * @return iterable}> + */ + public static function provide_unexpected_shapes() { + yield 'the run is not an array' => array( array( 'workflow_run' => 'gh-pages' ) ); + yield 'the branch is an array' => array( array( 'workflow_run' => array( 'head_branch' => array( 'gh-pages' ) ) ) ); + yield 'the branch is null' => array( array( 'workflow_run' => array( 'head_branch' => null ) ) ); + yield 'the run key is missing' => array( array( 'repository' => array( 'full_name' => 'LibreSign/site' ) ) ); + } +} diff --git a/tests/Unit/Includes/AccountNavigationTest.php b/tests/Unit/Includes/AccountNavigationTest.php deleted file mode 100644 index 1eef2b4..0000000 --- a/tests/Unit/Includes/AccountNavigationTest.php +++ /dev/null @@ -1,110 +0,0 @@ - - */ - private static function woocommerce_menu() { - return array( - 'dashboard' => 'Dashboard', - 'orders' => 'Orders', - 'subscriptions' => 'Subscriptions', - 'downloads' => 'Downloads', - 'edit-address' => 'Addresses', - 'payment-methods' => 'Payment methods', - 'edit-account' => 'Account details', - 'customer-logout' => 'Log out', - ); - } - - public function test_keeps_five_entries_in_the_configured_order() { - $this->assertSame( - array( 'subscriptions', 'orders', 'payment-methods', 'edit-account', 'customer-logout' ), - array_keys( libresign_filter_account_menu_items( self::woocommerce_menu() ) ) - ); - } - - public function test_renames_only_the_three_entries_whose_meaning_changed() { - $menu = libresign_filter_account_menu_items( self::woocommerce_menu() ); - - $this->assertSame( 'My subscription', $menu['subscriptions'] ); - $this->assertSame( 'Invoices', $menu['orders'] ); - $this->assertSame( 'Billing', $menu['payment-methods'] ); - $this->assertSame( 'Account details', $menu['edit-account'] ); - $this->assertSame( 'Log out', $menu['customer-logout'] ); - } - - /** - * @dataProvider provide_menus - * - * @param array $items Navigation handed over by WooCommerce. - * @param string[] $expected Expected resulting keys. - */ - public function test_filter_account_menu_items( $items, $expected ) { - $this->assertSame( $expected, array_keys( libresign_filter_account_menu_items( $items ) ) ); - } - - /** - * @return iterable, 1: string[]}> - */ - public static function provide_menus() { - yield 'entries outside the list are dropped' => array( - self::woocommerce_menu(), - array( 'subscriptions', 'orders', 'payment-methods', 'edit-account', 'customer-logout' ), - ); - yield 'unregistered endpoints are skipped' => array( - array( - 'dashboard' => 'Dashboard', - 'orders' => 'Orders', - 'customer-logout' => 'Log out', - ), - array( 'orders', 'customer-logout' ), - ); - yield 'the input order is irrelevant' => array( - array( - 'customer-logout' => 'Log out', - 'orders' => 'Orders', - 'subscriptions' => 'Subscriptions', - ), - array( 'subscriptions', 'orders', 'customer-logout' ), - ); - yield 'an empty navigation stays empty' => array( array(), array() ); - yield 'a navigation with nothing we keep' => array( - array( - 'dashboard' => 'Dashboard', - 'downloads' => 'Downloads', - ), - array(), - ); - } - - public function test_renamed_entries_are_the_ones_with_a_matching_endpoint_title() { - $this->assertSame( - array_keys( libresign_get_account_menu_labels() ), - array( 'subscriptions', 'orders', 'payment-methods' ) - ); - } - - public function test_detail_endpoints_are_mapped_to_the_entry_they_belong_to() { - $aliases = libresign_get_account_menu_item_aliases(); - - $this->assertSame( array( 'subscription-payment-method' ), $aliases['subscriptions'] ); - $this->assertSame( array( 'edit-address' ), $aliases['payment-methods'] ); - $this->assertSame( array( 'subscriptions', 'payment-methods' ), array_keys( $aliases ) ); - } -} diff --git a/tests/Unit/Includes/GithubSiteWebhookTest.php b/tests/Unit/Includes/GithubSiteWebhookTest.php deleted file mode 100644 index 310122c..0000000 --- a/tests/Unit/Includes/GithubSiteWebhookTest.php +++ /dev/null @@ -1,118 +0,0 @@ -assertSame( $expected, libresign_verify_github_webhook_signature( $body, $signature, $secret ) ); - } - - /** - * @return iterable - */ - public static function provide_signatures() { - yield 'signature sent the way GitHub sends it' => array( self::BODY, 'sha256=' . self::digest(), self::SECRET, true ); - yield 'prefix is optional' => array( self::BODY, self::digest(), self::SECRET, true ); - yield 'prefix is case insensitive' => array( self::BODY, 'SHA256=' . self::digest(), self::SECRET, true ); - yield 'hexadecimal is case insensitive' => array( self::BODY, strtoupper( self::digest() ), self::SECRET, true ); - yield 'surrounding whitespace is trimmed' => array( self::BODY, ' sha256=' . self::digest() . "\n", ' ' . self::SECRET . ' ', true ); - yield 'another secret does not match' => array( self::BODY, 'sha256=' . self::digest(), 'another-secret', false ); - yield 'a tampered body does not match' => array( self::BODY . ' ', 'sha256=' . self::digest(), self::SECRET, false ); - yield 'an empty body is never valid' => array( '', 'sha256=' . self::digest(), self::SECRET, false ); - yield 'an empty secret is never valid' => array( self::BODY, 'sha256=' . self::digest(), '', false ); - yield 'an empty signature is never valid' => array( self::BODY, '', self::SECRET, false ); - yield 'a non hexadecimal signature is refused' => array( self::BODY, 'sha256=not-a-digest', self::SECRET, false ); - yield 'a truncated signature is refused' => array( self::BODY, 'sha256=' . substr( self::digest(), 0, 32 ), self::SECRET, false ); - } - - /** - * @dataProvider provide_user_agents - * - * @param string $user_agent User agent header. - * @param bool $expected Expected result. - */ - public function test_is_github_hookshot_user_agent( $user_agent, $expected ) { - $this->assertSame( $expected, libresign_is_github_hookshot_user_agent( $user_agent ) ); - } - - /** - * @return iterable - */ - public static function provide_user_agents() { - yield 'the agent GitHub sends' => array( 'GitHub-Hookshot/044aadd', true ); - yield 'surrounding whitespace' => array( " GitHub-Hookshot/044aadd\n", true ); - yield 'the match is case sensitive' => array( 'github-hookshot/044aadd', false ); - yield 'the prefix has to come first' => array( 'curl/8.7.1 GitHub-Hookshot/044aadd', false ); - yield 'another client' => array( 'curl/8.7.1', false ); - yield 'no user agent at all' => array( '', false ); - } - - /** - * @dataProvider provide_payloads - * - * @param array $payload Parsed webhook payload. - * @param string $expected Expected workflow name. - */ - public function test_site_deploy_workflow_name_from_payload( $payload, $expected ) { - $this->assertSame( $expected, libresign_site_deploy_workflow_name_from_payload( $payload ) ); - } - - /** - * @return iterable, 1: string}> - */ - public static function provide_payloads() { - yield 'the run name wins' => array( - array( - 'workflow_run' => array( 'name' => 'pages build and deployment' ), - 'workflow' => array( 'name' => 'deploy' ), - ), - 'pages build and deployment', - ); - yield 'falls back to the workflow name' => array( - array( 'workflow' => array( 'name' => 'deploy' ) ), - 'deploy', - ); - yield 'a blank run name falls back too' => array( - array( - 'workflow_run' => array( 'name' => ' ' ), - 'workflow' => array( 'name' => 'deploy' ), - ), - 'deploy', - ); - yield 'names are trimmed' => array( - array( 'workflow_run' => array( 'name' => " deploy \n" ) ), - 'deploy', - ); - yield 'neither is present' => array( array( 'action' => 'completed' ), '' ); - yield 'an empty payload' => array( array(), '' ); - } -} diff --git a/tests/Unit/LibresignWpCustomizationsTest.php b/tests/Unit/LibresignWpCustomizationsTest.php deleted file mode 100644 index aa05af8..0000000 --- a/tests/Unit/LibresignWpCustomizationsTest.php +++ /dev/null @@ -1,65 +0,0 @@ -assertNull( $strings ); - - return; - } - - $this->assertSame( array( 'question', 'confirm', 'dismiss' ), array_keys( $strings ) ); - $this->assertNotSame( '', trim( $strings['question'] ) ); - $this->assertNotSame( '', trim( $strings['confirm'] ) ); - $this->assertNotSame( '', trim( $strings['dismiss'] ) ); - } - - /** - * @return iterable - */ - public static function provide_statuses() { - yield 'cancelling asks for confirmation' => array( 'cancelled', true ); - yield 'reactivating asks for confirmation' => array( 'active', true ); - yield 'on hold goes straight through' => array( 'on-hold', false ); - yield 'pending cancel goes straight through' => array( 'pending-cancel', false ); - yield 'an unknown status goes through' => array( 'whatever', false ); - yield 'no status at all goes through' => array( '', false ); - } - - public function test_cancelling_and_reactivating_do_not_share_strings() { - $cancel = libresign_get_subscription_confirmation_strings( 'cancelled' ); - $reactivate = libresign_get_subscription_confirmation_strings( 'active' ); - - $this->assertNotSame( $cancel['question'], $reactivate['question'] ); - $this->assertNotSame( $cancel['confirm'], $reactivate['confirm'] ); - } - - public function test_settings_link_comes_first_on_the_plugins_screen() { - $links = libresign_add_settings_link( array( 'Deactivate' ) ); - - $this->assertCount( 2, $links ); - $this->assertStringContainsString( 'options-general.php?page=libresign-config', $links[0] ); - $this->assertSame( 'Deactivate', $links[1] ); - } -} diff --git a/tests/Unit/Settings/SecretTest.php b/tests/Unit/Settings/SecretTest.php new file mode 100644 index 0000000..cdae5f6 --- /dev/null +++ b/tests/Unit/Settings/SecretTest.php @@ -0,0 +1,117 @@ +assertSame( $value, $secret->decrypt( $secret->encrypt( $value ) ) ); + } + + /** + * @return iterable + */ + public static function provide_values() { + yield 'a personal access token' => array( 'ghp_0123456789abcdefghijklmnopqrstuvwxyz' ); + yield 'a webhook secret' => array( 'a-shared-secret' ); + yield 'punctuation' => array( '{"not":"json"} + / = %' ); + yield 'accents' => array( 'segredo com acentuação' ); + yield 'a single character' => array( 'x' ); + } + + /** + * @dataProvider provide_values + * + * @param string $value Value to store. + */ + public function test_a_stored_value_is_read_back_for_sending( $value ) { + $secret = self::secret(); + + $this->assertSame( $value, $secret->decrypt_or_discard( $secret->encrypt( $value ) ) ); + } + + public function test_a_value_encrypted_elsewhere_is_discarded_instead_of_sent() { + $stored_value = Secret::from_salts( 'another-auth-key', 'another-secure-auth-salt', 'another-nonce-salt' ) + ->encrypt( 'ghp_0123456789abcdefghijklmnopqrstuvwxyz' ); + + $this->assertSame( $stored_value, self::secret()->decrypt( $stored_value ) ); + $this->assertSame( '', self::secret()->decrypt_or_discard( $stored_value ) ); + } + + /** + * @dataProvider provide_plain_tokens + * + * @param string $stored_value Value found in the database. + */ + public function test_a_token_saved_by_hand_is_still_sent( $stored_value ) { + $this->assertSame( $stored_value, self::secret()->decrypt_or_discard( $stored_value ) ); + } + + /** + * A GitHub token carries an underscore, which base64 does not. + * + * @return iterable + */ + public static function provide_plain_tokens() { + yield 'a classic personal access token' => array( 'ghp_0123456789abcdefghijklmnopqrstuvwxyz' ); + yield 'a fine grained token' => array( 'github_pat_11ABCDEFG0abcdefghijklmn' ); + yield 'nothing saved yet' => array( '' ); + } + + public function test_the_stored_value_is_not_the_value() { + $this->assertNotSame( 'a-shared-secret', self::secret()->encrypt( 'a-shared-secret' ) ); + } + + public function test_another_installation_cannot_read_the_value() { + $stored_value = self::secret()->encrypt( 'a-shared-secret' ); + $another_installation = Secret::from_salts( 'another-auth-key', 'another-secure-auth-salt', 'another-nonce-salt' ); + + $this->assertNotSame( 'a-shared-secret', $another_installation->decrypt( $stored_value ) ); + } + + /** + * @dataProvider provide_stored_values + * + * @param string $stored_value Value found in the database. + * @param string $expected Value the plugin uses. + */ + public function test_a_value_that_is_not_encrypted_is_used_as_it_is( $stored_value, $expected ) { + $this->assertSame( $expected, self::secret()->decrypt( $stored_value ) ); + } + + /** + * @return iterable + */ + public static function provide_stored_values() { + yield 'a secret saved by hand' => array( 'plain-secret', 'plain-secret' ); + yield 'surrounding whitespace' => array( " plain-secret \n", 'plain-secret' ); + yield 'nothing saved yet' => array( '', '' ); + yield 'blanks only' => array( ' ', '' ); + yield 'base64 that is not a secret' => array( 'cGxhaW4tc2VjcmV0', 'cGxhaW4tc2VjcmV0' ); + } +} diff --git a/tests/Unit/Subscription/StatusChangeTest.php b/tests/Unit/Subscription/StatusChangeTest.php new file mode 100644 index 0000000..50530e0 --- /dev/null +++ b/tests/Unit/Subscription/StatusChangeTest.php @@ -0,0 +1,58 @@ +assertNull( $strings ); + + return; + } + + $this->assertSame( array( 'question', 'confirm', 'dismiss' ), array_keys( $strings ) ); + $this->assertNotSame( '', trim( $strings['question'] ) ); + $this->assertNotSame( '', trim( $strings['confirm'] ) ); + $this->assertNotSame( '', trim( $strings['dismiss'] ) ); + } + + /** + * @return iterable + */ + public static function provide_statuses() { + yield 'cancelling asks for confirmation' => array( 'cancelled', true ); + yield 'reactivating asks for confirmation' => array( 'active', true ); + yield 'on hold goes straight through' => array( 'on-hold', false ); + yield 'pending cancel goes straight through' => array( 'pending-cancel', false ); + yield 'an unknown status goes through' => array( 'whatever', false ); + yield 'no status at all goes through' => array( '', false ); + } + + public function test_cancelling_and_reactivating_do_not_share_strings() { + $cancel = StatusChange::confirmation_strings( 'cancelled' ); + $reactivate = StatusChange::confirmation_strings( 'active' ); + + $this->assertNotSame( $cancel['question'], $reactivate['question'] ); + $this->assertNotSame( $cancel['confirm'], $reactivate['confirm'] ); + } +} diff --git a/tests/bootstrap-unit.php b/tests/bootstrap-unit.php index 265bd55..40b6ec0 100644 --- a/tests/bootstrap-unit.php +++ b/tests/bootstrap-unit.php @@ -42,6 +42,16 @@ function plugin_basename( $file ) { return basename( dirname( $file ) ) . '/' . basename( $file ); } +/** + * JSON encoding, which WordPress only wraps to pick its own default flags. + * + * @param mixed $data Value to encode. + * @return string|false + */ +function wp_json_encode( $data ) { + return json_encode( $data ); +} + /** * Translation, which without a text domain loaded returns the original string. *