From e9dd5d280565e01e0471198084de0f5069685d14 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Thu, 12 Feb 2026 22:08:33 +0000 Subject: [PATCH 01/17] Rearchitect `Updater` into a registry (#428) Signed-off-by: John Blackbourn Signed-off-by: Andy Fragen Signed-off-by: Carrie Dils Signed-off-by: Norcross Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Signed-off-by: joedolson Signed-off-by: Joe Dolson Signed-off-by: Shadi Sharaf Co-authored-by: Chuck Adams Co-authored-by: Andy Fragen Co-authored-by: Norcross Co-authored-by: Carrie Dils Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: rmccue <21655+rmccue@users.noreply.github.com> Co-authored-by: cdils <3099408+cdils@users.noreply.github.com> Co-authored-by: joedolson Co-authored-by: Joe Dolson Co-authored-by: Shady Sharaf --- .github/workflows/releases.yml | 12 + bin/bundle.sh | 22 ++ inc/default-repo/namespace.php | 1 + inc/namespace.php | 1 + inc/packages/namespace.php | 62 +++- inc/updater/class-lite.php | 30 +- inc/updater/class-package.php | 109 +++++++ inc/updater/class-pluginpackage.php | 32 ++ inc/updater/class-themepackage.php | 32 ++ inc/updater/class-updater.php | 405 +++++++++++++++++------- inc/updater/namespace.php | 89 +++++- inc/version-check/namespace.php | 472 +++++++++++++++++++++++++++- languages/fair.pot | 42 ++- plugin.php | 4 +- 14 files changed, 1148 insertions(+), 165 deletions(-) create mode 100644 inc/updater/class-package.php create mode 100644 inc/updater/class-pluginpackage.php create mode 100644 inc/updater/class-themepackage.php diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml index 49937bd6..b9b60a9a 100644 --- a/.github/workflows/releases.yml +++ b/.github/workflows/releases.yml @@ -37,6 +37,18 @@ jobs: /tmp/${{ github.event.repository.name }}-${{ steps.tag.outputs.tag }}.zip /tmp/fair-dist/* + - name: Upload to Fastly Object Storage + # note the plugin filename is always 'fair-connect', regardless of the repo name or slug + run: | + aws s3 cp /tmp/${{ github.event.repository.name }}-${{ steps.tag.outputs.tag }}.zip s3://download.fair.pm/release/fair-connect-${{ steps.tag.outputs.tag }}.zip + aws s3 cp --recursive --exclude '*' --include '*.zip' /tmp/fair-dist s3://download.fair.pm/release/ + env: + AWS_ACCESS_KEY_ID: ${{ secrets.DOWNLOAD_BUCKET_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.DOWNLOAD_BUCKET_SECRET_KEY }} + AWS_DEFAULT_REGION: 'us-west' + AWS_ENDPOINT_URL: 'https://us-west.object.fastlystorage.app' + AWS_REQUEST_CHECKSUM_CALCULATION: 'WHEN_REQUIRED' + - name: Build provenance attestation uses: actions/attest-build-provenance@v2 with: diff --git a/bin/bundle.sh b/bin/bundle.sh index 577ccfae..fedcb914 100755 --- a/bin/bundle.sh +++ b/bin/bundle.sh @@ -19,6 +19,22 @@ touch /tmp/fair-dist/SHA1SUMS touch /tmp/fair-dist/SHA256SUMS touch /tmp/fair-dist/SHA384SUMS +# Auto-detect and hash plugin zip. +PLUGIN_VERSION=$(get_plugin_header "Version") +REPO_NAME=$(basename "$GITHUB_REPOSITORY") +PLUGIN_ZIP="/tmp/${REPO_NAME}-${PLUGIN_VERSION}.zip" + +if [ -f "$PLUGIN_ZIP" ]; then + echo "Hashing plugin zip: $PLUGIN_ZIP" >&2 + # Change to /tmp so hash files contain just filename, not full path + cd /tmp + md5sum -b "$(basename "$PLUGIN_ZIP")" >> /tmp/fair-dist/MD5SUMS + sha1sum -b "$(basename "$PLUGIN_ZIP")" >> /tmp/fair-dist/SHA1SUMS + sha256sum -b "$(basename "$PLUGIN_ZIP")" >> /tmp/fair-dist/SHA256SUMS + sha384sum -b "$(basename "$PLUGIN_ZIP")" >> /tmp/fair-dist/SHA384SUMS + cd - > /dev/null +fi + # Bundle our plugin first. [ -d /tmp/fair-temp ] && rm -rf /tmp/fair-temp mkdir -p /tmp/fair-temp/wordpress/wp-content/plugins/fair-plugin @@ -54,6 +70,12 @@ for VERSION in $AVAILABLE_VERSIONS; do curl -sSL "$WP_ZIP_URL" -o "$WP_ZIP_FILE" EXPECTED_HASH=$(curl -sSL "$WP_ZIP_URL.sha1") + # Skip if we can't get a valid hash. + if [[ ! "$EXPECTED_HASH" =~ ^[a-z0-9]{40}$ ]]; then + echo "Failed to fetch valid hash for $VERSION" >&2 + continue + fi + # Verify the checksum. # (sha1 is suboptimal, but it's all we've got.) echo " Verifying checksum" >&2 diff --git a/inc/default-repo/namespace.php b/inc/default-repo/namespace.php index 6719ede4..7cf89b7e 100644 --- a/inc/default-repo/namespace.php +++ b/inc/default-repo/namespace.php @@ -53,6 +53,7 @@ function replace_repo_api_urls( $status, $args, $url ) { if ( ! str_contains( $url, 'api.wordpress.org/plugins/' ) && ! str_contains( $url, 'api.wordpress.org/themes/' ) + && ! str_contains( $url, 'api.wordpress.org/translations/' ) && ! str_contains( $url, 'api.wordpress.org/core/version-check/' ) ) { return $status; diff --git a/inc/namespace.php b/inc/namespace.php index e12ed82d..de17542a 100644 --- a/inc/namespace.php +++ b/inc/namespace.php @@ -11,6 +11,7 @@ const CACHE_BASE = 'fair-'; const CACHE_LIFETIME = 12 * HOUR_IN_SECONDS; +const CACHE_LIFETIME_FAILURE = HOUR_IN_SECONDS; const NS_SEPARATOR = '\\'; /** diff --git a/inc/packages/namespace.php b/inc/packages/namespace.php index 9733ae4f..1dfff720 100644 --- a/inc/packages/namespace.php +++ b/inc/packages/namespace.php @@ -9,6 +9,7 @@ use const FAIR\CACHE_BASE; use const FAIR\CACHE_LIFETIME; +use const FAIR\CACHE_LIFETIME_FAILURE; use FAIR\Packages\DID\Document as DIDDocument; use FAIR\Packages\DID\PLC; use FAIR\Packages\DID\Web; @@ -22,12 +23,33 @@ const CACHE_KEY = CACHE_BASE . 'packages-'; const CACHE_METADATA_DOCUMENTS = CACHE_BASE . 'metadata-documents-'; const CACHE_RELEASE_PACKAGES = CACHE_BASE . 'release-packages'; +const CACHE_UPDATE_ERRORS = CACHE_BASE . 'update-errors-'; const CACHE_DID_FOR_INSTALL = 'fair-install-did'; const CONTENT_TYPE = 'application/json+fair'; const SERVICE_ID = 'FairPackageManagementRepo'; // phpcs:disable WordPress.NamingConventions.ValidVariableName +/** + * Cache an update error for a package. + * + * @param string $did DID of the package. + * @param WP_Error $error The error to cache. + */ +function cache_update_error( string $did, WP_Error $error ): void { + $error->add_data( [ 'timestamp' => time() ], $error->get_error_code() ); + set_site_transient( CACHE_UPDATE_ERRORS . $did, $error, CACHE_LIFETIME_FAILURE ); +} + +/** + * Clear a cached update error for a package. + * + * @param string $did DID of the package. + */ +function clear_update_error( string $did ): void { + delete_site_transient( CACHE_UPDATE_ERRORS . $did ); +} + /** * Bootstrap. * @@ -93,6 +115,12 @@ function get_did_hash( string $id ) { * @return DIDDocument|WP_Error */ function get_did_document( string $id ) { + // Check for cached error from previous failed request. + $cached_error = get_site_transient( CACHE_UPDATE_ERRORS . $id ); + if ( is_wp_error( $cached_error ) ) { + return $cached_error; + } + $cached = get_site_transient( CACHE_METADATA_DOCUMENTS . $id ); if ( $cached ) { return $cached; @@ -101,13 +129,18 @@ function get_did_document( string $id ) { // Parse the DID, then fetch the details. $did = parse_did( $id ); if ( is_wp_error( $did ) ) { + cache_update_error( $id, $did ); return $did; } $document = $did->fetch_document(); if ( is_wp_error( $document ) ) { + cache_update_error( $id, $document ); return $document; } + + // Clear any previous error on success. + clear_update_error( $id ); set_site_transient( CACHE_METADATA_DOCUMENTS . $id, $document, CACHE_LIFETIME ); return $document; @@ -177,20 +210,27 @@ function fetch_package_metadata( string $id ) { // Fetch data from the repository. $service = $document->get_service( SERVICE_ID ); if ( empty( $service ) ) { - return new WP_Error( 'fair.packages.fetch_metadata.no_service', __( 'DID is not a valid package to fetch metadata for.', 'fair' ) ); + $error = new WP_Error( 'fair.packages.fetch_metadata.no_service', __( 'DID is not a valid package to fetch metadata for.', 'fair' ) ); + cache_update_error( $id, $error ); + return $error; } $repo_url = $service->serviceEndpoint; - $metadata = fetch_metadata_doc( $repo_url ); + $metadata = fetch_metadata_doc( $repo_url, $id ); if ( is_wp_error( $metadata ) ) { return $metadata; } if ( $metadata->id !== $id ) { - return new WP_Error( 'fair.packages.fetch_metadata.mismatch', __( 'Fetched metadata does not match the requested DID.', 'fair' ) ); + $error = new WP_Error( 'fair.packages.fetch_metadata.mismatch', __( 'Fetched metadata does not match the requested DID.', 'fair' ) ); + cache_update_error( $id, $error ); + return $error; } + // Clear any previous error on success. + clear_update_error( $id ); + return $metadata; } @@ -198,9 +238,10 @@ function fetch_package_metadata( string $id ) { * Fetch the metadata document for a package. * * @param string $url URL for the metadata document. + * @param string $did DID of the package. * @return MetadataDocument|WP_Error */ -function fetch_metadata_doc( string $url ) { +function fetch_metadata_doc( string $url, string $did ) { $cache_key = CACHE_KEY . md5( $url ); $response = get_site_transient( $cache_key ); $response = fetch_metadata_from_local( $response, $url ); @@ -219,9 +260,15 @@ function fetch_metadata_doc( string $url ) { $response = wp_remote_get( $url, $options ); $code = wp_remote_retrieve_response_code( $response ); if ( is_wp_error( $response ) ) { + cache_update_error( $did, $response ); return $response; } elseif ( $code !== 200 ) { - return new WP_Error( 'fair.packages.metadata.failure', __( 'HTTP error code received', 'fair' ) ); + $error = new WP_Error( + 'fair.packages.metadata.http_error', + sprintf( __( 'HTTP %d error received', 'fair' ), $code ) + ); + cache_update_error( $did, $error ); + return $error; } // Reorder sections before caching. @@ -727,6 +774,9 @@ function cache_did_for_install( array $options ): array { $did = array_find_key( $releases, function ( $release ) use ( $options ) { + if ( ! is_array( $release->artifacts->package ) ) { + return false; + } $artifact = pick_artifact_by_lang( $release->artifacts->package ); return $artifact && $artifact->url === $options['package']; } @@ -754,7 +804,7 @@ function delete_cached_did_for_install(): void { * * This is commonly required for packages from Git hosts. * - * @param string $source Path of $source. + * @param string|WP_Error $source Path of $source, or a WP_Error object. * @param string $remote_source Path of $remote_source. * @param WP_Upgrader $upgrader An Upgrader object. * @param array $hook_extra Array of hook data. diff --git a/inc/updater/class-lite.php b/inc/updater/class-lite.php index ca54e93a..c9c67ebb 100644 --- a/inc/updater/class-lite.php +++ b/inc/updater/class-lite.php @@ -125,6 +125,13 @@ public function run() { ); $response = get_site_transient( "git-updater-lite_{$this->file}" ); if ( ! $response ) { + /* Apply filter to API URL. + * Add `channel=development` query arg to URL to get pre-release versions. + * + * @param string $url The API URL. + * @param string $slug The plugin/theme slug + */ + $url = apply_filters( 'git_updater_lite_api_url', $url, $this->slug ); $response = wp_remote_get( $url ); if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) === 404 ) { return $response; @@ -136,11 +143,9 @@ public function run() { } $this->api_data->file = $this->file; - /* - * Set transient for 5 minutes as AWS sets 5 minute timeout - * for release asset redirect. - */ - set_site_transient( "git-updater-lite_{$this->file}", $this->api_data, 5 * \MINUTE_IN_SECONDS ); + // Set timeout for transient via filter. + $timeout = apply_filters( 'git_updater_lite_transient_timeout', 6 * HOUR_IN_SECONDS, $this->file ); + set_site_transient( "git-updater-lite_{$this->file}", $this->api_data, $timeout ); } else { if ( property_exists( $response, 'error' ) ) { return new WP_Error( 'repo-no-exist', 'Specified repo does not exist' ); @@ -178,18 +183,23 @@ function () { /** * Correctly rename dependency for activation. * - * @param string $source Path of $source. - * @param string $remote_source Path of $remote_source. - * @param WP_Upgrader $upgrader An Upgrader object. - * @param array $hook_extra Array of hook data. + * @param string|WP_Error $source Path of $source, or a WP_Error object. + * @param string $remote_source Path of $remote_source. + * @param WP_Upgrader $upgrader An Upgrader object. + * @param array $hook_extra Array of hook data. * * @throws TypeError If the type of $upgrader is not correct. * * @return string|WP_Error */ - public function upgrader_source_selection( string $source, string $remote_source, WP_Upgrader $upgrader, $hook_extra = null ) { + public function upgrader_source_selection( $source, string $remote_source, WP_Upgrader $upgrader, $hook_extra = null ) { global $wp_filesystem; + // Exit early for errors. + if ( is_wp_error( $source ) ) { + return $source; + } + $new_source = $source; // Exit if installing. diff --git a/inc/updater/class-package.php b/inc/updater/class-package.php new file mode 100644 index 00000000..c57516ab --- /dev/null +++ b/inc/updater/class-package.php @@ -0,0 +1,109 @@ +did = $did; + $this->filepath = $filepath; + $this->local_version = $filepath ? get_file_data( $filepath, [ 'Version' => 'Version' ] )['Version'] : null; + } + + /** + * Get the package slug. + * + * @return string The slug (directory name for plugins, stylesheet for themes). + */ + abstract public function get_slug(): string; + + /** + * Get the relative path used in update transients. + * + * @return string The relative path. + */ + abstract public function get_relative_path(): string; + + /** + * Get the metadata document, fetching and caching if needed. + * + * @return \FAIR\Packages\MetadataDocument|\WP_Error|null + */ + final public function get_metadata() { + if ( $this->metadata === null ) { + $metadata = Packages\fetch_package_metadata( $this->did ); + if ( ! is_wp_error( $metadata ) ) { + $this->metadata = $metadata; + } + return $metadata; + } + return $this->metadata; + } + + /** + * Get the release document, fetching and caching if needed. + * + * @return \FAIR\Packages\ReleaseDocument|\WP_Error|null + */ + final public function get_release() { + if ( $this->release === null ) { + $release = Packages\get_latest_release_from_did( $this->did ); + if ( ! is_wp_error( $release ) ) { + $this->release = $release; + } + return $release; + } + return $this->release; + } +} diff --git a/inc/updater/class-pluginpackage.php b/inc/updater/class-pluginpackage.php new file mode 100644 index 00000000..14949bb4 --- /dev/null +++ b/inc/updater/class-pluginpackage.php @@ -0,0 +1,32 @@ +filepath ) ); + } + + /** + * Get the relative path used in update transients. + * + * @return string The relative path (e.g., 'my-plugin/my-plugin.php'). + */ + public function get_relative_path(): string { + return plugin_basename( $this->filepath ); + } +} diff --git a/inc/updater/class-themepackage.php b/inc/updater/class-themepackage.php new file mode 100644 index 00000000..30a949b7 --- /dev/null +++ b/inc/updater/class-themepackage.php @@ -0,0 +1,32 @@ +filepath ) ); + } + + /** + * Get the relative path used in update transients. + * + * @return string The relative path (theme directory name). + */ + public function get_relative_path(): string { + return dirname( plugin_basename( $this->filepath ) ); + } +} diff --git a/inc/updater/class-updater.php b/inc/updater/class-updater.php index aa6f415b..caa28a63 100644 --- a/inc/updater/class-updater.php +++ b/inc/updater/class-updater.php @@ -12,6 +12,7 @@ use stdClass; use Theme_Upgrader; use TypeError; +use WP_Error; use WP_Upgrader; /** @@ -20,69 +21,25 @@ class Updater { /** - * DID. + * Registered plugins. * - * @var string + * @var array */ - protected $did; + private static array $plugins = []; /** - * Absolute path to the "main" file. + * Registered themes. * - * For plugins, this is the PHP file with the plugin header. For themes, - * this is the style.css file. - * - * @var string|null - */ - protected $filepath; - - /** - * Current installed version of the package. - * - * @var string|null - */ - protected $local_version; - - /** - * Package type, plugin or theme. - * - * @var string - */ - protected $type; - - /** - * Metadata document. - * - * @var \FAIR\Packages\MetadataDocument - */ - protected $metadata; - - /** - * Release document. - * - * @var \FAIR\Packages\ReleaseDocument + * @var array */ - protected $release; + private static array $themes = []; /** - * Constructor. - * - * @param string $did DID. - * @param string $filepath Absolute file path. - */ - public function __construct( string $did, string $filepath = '' ) { - $this->did = $did; - $this->filepath = $filepath; - $this->local_version = $filepath ? get_file_data( $filepath, [ 'Version' => 'Version' ] )['Version'] : null; - } - - /** - * Get API data. + * Check if we should run on the current page. * * @global string $pagenow Current page. - * @return void|WP_Error */ - public function run() { + public static function should_run_on_current_page(): bool { global $pagenow; // Needed for mu-plugin. @@ -100,20 +57,10 @@ public function run() { $view_details = [ 'plugin-install.php', 'theme-install.php' ]; $autoupdate_pages = [ 'admin-ajax.php', 'index.php', 'wp-cron.php' ]; if ( ! in_array( $pagenow, array_merge( $pages, $view_details, $autoupdate_pages ), true ) ) { - return; - } - - $this->metadata = Packages\fetch_package_metadata( $this->did ); - if ( is_wp_error( $this->metadata ) ) { - return $this->metadata; - } - $this->release = Packages\get_latest_release_from_did( $this->did ); - if ( is_wp_error( $this->release ) ) { - return $this->release; + return false; } - $this->type = str_replace( 'wp-', '', $this->metadata->type ); - $this->load_hooks(); + return true; } /** @@ -121,16 +68,16 @@ public function run() { * * @return void */ - public function load_hooks() { - add_filter( 'upgrader_source_selection', [ $this, 'upgrader_source_selection' ], 10, 4 ); - add_filter( "{$this->type}s_api", [ $this, 'repo_api_details' ], 99, 3 ); + public static function load_hooks() { + add_filter( 'upgrader_source_selection', [ __CLASS__, 'upgrader_source_selection' ], 10, 4 ); + add_filter( 'plugins_api', [ __CLASS__, 'plugin_api_details' ], 99, 3 ); + add_filter( 'themes_api', [ __CLASS__, 'theme_api_details' ], 99, 3 ); - if ( ! empty( $this->filepath ) && ! empty( $this->local_version ) ) { - add_filter( "site_transient_update_{$this->type}s", [ $this, 'update_site_transient' ], 20, 1 ); - } + add_filter( 'site_transient_update_plugins', [ __CLASS__, 'handle_update_plugins_transient' ], 20, 1 ); + add_filter( 'site_transient_update_themes', [ __CLASS__, 'handle_update_themes_transient' ], 20, 1 ); if ( ! is_multisite() ) { - add_filter( 'wp_prepare_themes_for_js', [ $this, 'customize_theme_update_html' ] ); + add_filter( 'wp_prepare_themes_for_js', [ __CLASS__, 'customize_theme_update_html' ] ); } /** @@ -143,13 +90,18 @@ public function load_hooks() { add_filter( 'upgrader_pre_download', 'FAIR\\Updater\\verify_signature_on_download', 10, 4 ); } - Packages\add_package_to_release_cache( $this->did ); + foreach ( self::$plugins as $package ) { + Packages\add_package_to_release_cache( $package->did ); + } + foreach ( self::$themes as $package ) { + Packages\add_package_to_release_cache( $package->did ); + } } /** * Correctly rename dependency for activation. * - * @param string $source Path of $source. + * @param string|WP_Error $source Path of $source, or a WP_Error object. * @param string $remote_source Path of $remote_source. * @param WP_Upgrader $upgrader An Upgrader object. * @param array $hook_extra Array of hook data. @@ -158,9 +110,14 @@ public function load_hooks() { * * @return string|WP_Error */ - public function upgrader_source_selection( string $source, string $remote_source, WP_Upgrader $upgrader, $hook_extra = null ) { + public static function upgrader_source_selection( $source, string $remote_source, WP_Upgrader $upgrader, $hook_extra = null ) { global $wp_filesystem; + // Exit early for errors. + if ( is_wp_error( $source ) ) { + return $source; + } + $new_source = $source; // Exit if installing. @@ -174,18 +131,20 @@ public function upgrader_source_selection( string $source, string $remote_source // Rename plugins. if ( $upgrader instanceof Plugin_Upgrader ) { - if ( isset( $hook_extra['plugin'] ) ) { - $slug = dirname( $hook_extra['plugin'] ); - $new_source = trailingslashit( $remote_source ) . $slug; + if ( ! isset( $hook_extra['plugin'] ) ) { + return $source; } + $slug = dirname( $hook_extra['plugin'] ); + $new_source = trailingslashit( $remote_source ) . $slug; } // Rename themes. if ( $upgrader instanceof Theme_Upgrader ) { - if ( isset( $hook_extra['theme'] ) ) { - $slug = $hook_extra['theme']; - $new_source = trailingslashit( $remote_source ) . $slug; + if ( ! isset( $hook_extra['theme'] ) ) { + return $source; } + $slug = $hook_extra['theme']; + $new_source = trailingslashit( $remote_source ) . $slug; } if ( basename( $source ) === $slug ) { @@ -208,48 +167,130 @@ public function upgrader_source_selection( string $source, string $remote_source * * @return stdClass|bool */ - public function repo_api_details( $result, string $action, stdClass $response ) { - if ( "{$this->type}_information" !== $action ) { + public static function plugin_api_details( $result, string $action, stdClass $response ) { + if ( 'plugin_information' !== $action ) { + return $result; + } + + return self::handle_plugin_api( $result, $response->slug ?? '' ); + } + + /** + * Put changelog in themes_api, return WP.org data as appropriate + * + * @param bool $result Default false. + * @param string $action The type of information being requested from the Theme Installation API. + * @param stdClass $response Repo API arguments. + * + * @return stdClass|bool + */ + public static function theme_api_details( $result, string $action, stdClass $response ) { + if ( 'theme_information' !== $action ) { return $result; } - // Exit if not our repo. - $slug_arr = [ $this->metadata->slug, $this->metadata->slug . '-' . Packages\get_did_hash( $this->did ) ]; - if ( ! in_array( $response->slug, $slug_arr, true ) ) { + return self::handle_theme_api( $result, $response->slug ?? '' ); + } + + /** + * Find a package by its API slug. + * + * @param bool|object $result The result object or false. + * @param string $slug The package slug. + * @param Package[] $packages The packages to search. + * @return bool|object The result. + */ + private static function find_package_by_api_slug( $result, string $slug, array $packages ) { + if ( empty( $slug ) ) { return $result; } - return (object) Packages\get_package_data( $this->did ); + foreach ( $packages as $package ) { + $metadata = $package->get_metadata(); + if ( is_wp_error( $metadata ) || ! $metadata ) { + continue; + } + + // Check if slug matches (with or without DID hash suffix). + $slug_arr = [ $metadata->slug, $metadata->slug . '-' . Packages\get_did_hash( $package->did ) ]; + if ( in_array( $slug, $slug_arr, true ) ) { + return (object) Packages\get_package_data( $package->did ); + } + } + + return $result; } /** - * Hook into site_transient_update_{plugins|themes} to update from GitHub. + * Handle site_transient_update_plugins filter. * * @param stdClass $transient Plugin|Theme update transient. + * @return stdClass The modified transient. + */ + public static function handle_update_plugins_transient( $transient ) { + $transient = self::update_site_transient( $transient, self::$plugins ); + + // WordPress expects plugin responses as objects. + foreach ( $transient->response ?? [] as $key => $value ) { + $transient->response[ $key ] = (object) $value; + } + foreach ( $transient->no_update ?? [] as $key => $value ) { + $transient->no_update[ $key ] = (object) $value; + } + + return $transient; + } + + /** + * Handle site_transient_update_themes filter. * + * @param stdClass $transient Plugin|Theme update transient. + * @return stdClass The modified transient. + */ + public static function handle_update_themes_transient( $transient ) { + return self::update_site_transient( $transient, self::$themes ); + } + + /** + * Hook into site_transient_update_{plugins|themes} to update from GitHub. + * + * @param stdClass $transient Plugin|Theme update transient. + * @param array $packages Array of packages to process. * @return stdClass */ - public function update_site_transient( $transient ) { + private static function update_site_transient( $transient, array $packages ) { // needed to fix PHP 7.4 warning. if ( ! is_object( $transient ) ) { $transient = new stdClass(); } - $rel_path = plugin_basename( $this->filepath ); - $rel_path = 'theme' === $this->type ? dirname( $rel_path ) : $rel_path; - $response = Packages\get_package_data( $this->did ); - if ( is_wp_error( $response ) ) { - return $transient; - } - $response['slug'] = $response['slug_didhash']; - $response = 'plugin' === $this->type ? (object) $response : $response; - $is_compatible = Packages\check_requirements( $this->release ); - - if ( $is_compatible && version_compare( $this->release->version, $this->local_version, '>' ) ) { - $transient->response[ $rel_path ] = $response; - } else { - // Add repo without update to $transient->no_update for 'View details' link. - $transient->no_update[ $rel_path ] = $response; + foreach ( $packages as $package ) { + if ( empty( $package->filepath ) || empty( $package->local_version ) ) { + continue; + } + + $release = $package->get_release(); + if ( is_wp_error( $release ) || ! $release ) { + continue; + } + + $response = Packages\get_package_data( $package->did ); + if ( is_wp_error( $response ) ) { + continue; + } + + $rel_path = $package->get_relative_path(); + + $response['slug'] = $response['slug_didhash']; + + $is_compatible = Packages\check_requirements( $release ); + + if ( $is_compatible && version_compare( $release->version, $package->local_version, '>' ) ) { + $transient->response[ $rel_path ] = $response; + } else { + // Add repo without update to $transient->no_update for 'View details' link. + $transient->no_update[ $rel_path ] = $response; + } } return $transient; @@ -264,17 +305,22 @@ public function update_site_transient( $transient ) { * * @return array */ - public function customize_theme_update_html( $prepared_themes ) { - $theme = $this->metadata; + public static function customize_theme_update_html( $prepared_themes ) { + foreach ( self::$themes as $package ) { + $theme = $package->get_metadata(); + if ( is_wp_error( $theme ) || ! $theme ) { + continue; + } - if ( 'theme' !== $this->type ) { - return $prepared_themes; - } + if ( ! isset( $prepared_themes[ $theme->slug ] ) ) { + continue; + } - if ( ! empty( $prepared_themes[ $theme->slug ]['hasUpdate'] ) ) { - $prepared_themes[ $theme->slug ]['update'] = $this->append_theme_actions_content( $theme ); - } else { - $prepared_themes[ $theme->slug ]['description'] .= $this->append_theme_actions_content( $theme ); + if ( ! empty( $prepared_themes[ $theme->slug ]['hasUpdate'] ) ) { + $prepared_themes[ $theme->slug ]['update'] = self::append_theme_actions_content( $theme ); + } else { + $prepared_themes[ $theme->slug ]['description'] .= self::append_theme_actions_content( $theme ); + } } return $prepared_themes; @@ -292,7 +338,7 @@ public function customize_theme_update_html( $prepared_themes ) { * * @return string (content buffer) */ - protected function append_theme_actions_content( $theme ) { + private static function append_theme_actions_content( $theme ) { $details_url = esc_attr( add_query_arg( [ @@ -370,4 +416,137 @@ protected function append_theme_actions_content( $theme ) { return trim( ob_get_clean(), '1' ); } + + /** + * Handle plugin API requests. + * + * @param bool|object $result The result object or false. + * @param string $slug The plugin slug. + * @return bool|object The result. + */ + private static function handle_plugin_api( $result, string $slug ) { + return self::find_package_by_api_slug( $result, $slug, self::$plugins ); + } + + /** + * Handle theme API requests. + * + * @param bool|object $result The result object or false. + * @param string $slug The theme slug. + * @return bool|object The result. + */ + private static function handle_theme_api( $result, string $slug ) { + return self::find_package_by_api_slug( $result, $slug, self::$themes ); + } + + /** + * Register a plugin with the registry. + * + * @param string $did The DID of the plugin. + * @param string $filepath Absolute path to the main plugin file. + */ + public static function register_plugin( string $did, string $filepath ): void { + self::$plugins[ $did ] = new PluginPackage( $did, $filepath ); + } + + /** + * Register a theme with the registry. + * + * @param string $did The DID of the theme. + * @param string $filepath Absolute path to the theme's style.css file. + */ + public static function register_theme( string $did, string $filepath ): void { + self::$themes[ $did ] = new ThemePackage( $did, $filepath ); + } + + /** + * Get a plugin by DID. + * + * @param string $did The DID to look up. + */ + public static function get_plugin( string $did ): ?PluginPackage { + return self::$plugins[ $did ] ?? null; + } + + /** + * Get a theme by DID. + * + * @param string $did The DID to look up. + */ + public static function get_theme( string $did ): ?ThemePackage { + return self::$themes[ $did ] ?? null; + } + + /** + * Get all registered plugins. + * + * @return array All registered plugins. + */ + public static function get_plugins(): array { + return self::$plugins; + } + + /** + * Get all registered themes. + * + * @return array All registered themes. + */ + public static function get_themes(): array { + return self::$themes; + } + + /** + * Find a plugin by the plugin file path (relative to plugins directory). + * + * @param string $plugin_file Plugin file path relative to plugins directory (e.g., 'my-plugin/my-plugin.php'). + */ + public static function get_plugin_by_file( string $plugin_file ): ?PluginPackage { + $plugin_path = trailingslashit( WP_PLUGIN_DIR ) . $plugin_file; + + foreach ( self::$plugins as $package ) { + if ( $package->filepath === $plugin_path ) { + return $package; + } + } + + return null; + } + + /** + * Find a plugin by its slug. + * + * @param string $slug The plugin directory name. + */ + public static function get_plugin_by_slug( string $slug ): ?PluginPackage { + foreach ( self::$plugins as $package ) { + if ( $package->get_slug() === $slug ) { + return $package; + } + } + + return null; + } + + /** + * Find a theme by its slug. + * + * @param string $slug The theme stylesheet. + */ + public static function get_theme_by_slug( string $slug ): ?ThemePackage { + foreach ( self::$themes as $package ) { + if ( $package->get_slug() === $slug ) { + return $package; + } + } + + return null; + } + + /** + * Reset the registry. + */ + public static function reset(): void { + self::$plugins = []; + self::$themes = []; + } } diff --git a/inc/updater/namespace.php b/inc/updater/namespace.php index 3db5114b..ede4e357 100644 --- a/inc/updater/namespace.php +++ b/inc/updater/namespace.php @@ -7,8 +7,10 @@ namespace FAIR\Updater; +use const FAIR\CACHE_LIFETIME_FAILURE; use const FAIR\Packages\CACHE_DID_FOR_INSTALL; use const FAIR\Packages\CACHE_RELEASE_PACKAGES; +use const FAIR\Packages\CACHE_UPDATE_ERRORS; use FAIR\Packages; use function FAIR\is_wp_cli; use Plugin_Upgrader; @@ -22,6 +24,7 @@ */ function bootstrap() { add_action( 'init', __NAMESPACE__ . '\\run' ); + add_action( 'admin_init', __NAMESPACE__ . '\\register_plugin_row_hooks' ); } /** @@ -70,23 +73,99 @@ function get_packages() : array { * @return void */ function run() { + if ( ! Updater::should_run_on_current_page() ) { + return; + } + $packages = get_packages(); $plugins = $packages['plugins'] ?? []; $themes = $packages['themes'] ?? []; - $packages = array_merge( $plugins, $themes ); - foreach ( $packages as $did => $filepath ) { - ( new Updater( $did, $filepath ) )->run(); + + foreach ( $plugins as $did => $filepath ) { + Updater::register_plugin( $did, $filepath ); + } + + foreach ( $themes as $did => $filepath ) { + Updater::register_theme( $did, $filepath ); + } + + // Load hooks once for all packages. + Updater::load_hooks(); +} + +/** + * Register hooks to display update errors below plugin rows. + */ +function register_plugin_row_hooks(): void { + $packages = get_packages(); + $plugins = $packages['plugins'] ?? []; + + foreach ( $plugins as $did => $path ) { + $plugin_file = plugin_basename( $path ); + add_action( + "after_plugin_row_{$plugin_file}", + function ( $file, $plugin_data, $status ) use ( $did ) { + display_plugin_update_error( $file, $plugin_data, $status, $did ); + }, + 10, + 3 + ); } } +/** + * Display a cached update error below the plugin row. + * + * @param string $plugin_file Path to the plugin file relative to the plugins directory. + * @param array $plugin_data An array of plugin data. + * @param string $status Status filter currently applied to the plugin list. + * @param string $did The DID of the plugin. + */ +function display_plugin_update_error( $plugin_file, $plugin_data, $status, $did ): void { + $error = get_site_transient( CACHE_UPDATE_ERRORS . $did ); + if ( ! is_wp_error( $error ) ) { + return; + } + + $wp_list_table = _get_list_table( 'WP_Plugins_List_Table' ); + $colspan = $wp_list_table->get_column_count(); + + // Calculate time remaining until retry. + $error_data = $error->get_error_data(); + $timestamp = $error_data['timestamp'] ?? 0; + $retry_time = $timestamp + CACHE_LIFETIME_FAILURE; + $time_remaining = human_time_diff( time(), $retry_time ); + + $message = sprintf( + /* translators: %1$s: Error message, %2$s: Time period */ + __( 'Error: %1$s. Update checks paused for %2$s.', 'fair' ), + $error->get_error_message(), + $time_remaining, + ); + + $active_class = is_plugin_active( $plugin_file ) ? ' active' : ''; + + printf( + ' + +

%4$s

+ + ', + esc_attr( $active_class ), + esc_attr( sanitize_title( $plugin_file ) ), + esc_attr( $colspan ), + esc_html( $message ), + ); +} + /** * Download a package with signature verification. * - * @param bool|string|WP_Error $reply Whether to proceed with the download, the path to the downloaded package, or an existing WP_Error object. Default true. + * @param bool|string|WP_Error $reply Whether to proceed with the download, the path to the downloaded package, or an existing WP_Error object. * @param string $package The URI of the package. If this is the full path to an existing local file, it will be returned untouched. * @param WP_Upgrader $upgrader The WP_Upgrader instance. * @param array $hook_extra Extra hook data. - * @return true|WP_Error True if the signature is valid, otherwise WP_Error. + * @return string|WP_Error The package path if the signature is valid, otherwise WP_Error. */ function verify_signature_on_download( $reply, string $package, WP_Upgrader $upgrader, $hook_extra ) { static $has_run = []; diff --git a/inc/version-check/namespace.php b/inc/version-check/namespace.php index 13c2269e..2149827a 100644 --- a/inc/version-check/namespace.php +++ b/inc/version-check/namespace.php @@ -14,7 +14,7 @@ * * DO NOT EDIT THIS CONSTANT MANUALLY. */ -const BROWSER_REGEX = '/Edge?\/14[0-2]\.0(\.\d+|)|Firefox\/(140\.0|14[3-8]\.0)(\.\d+|)|Chrom(ium|e)\/(109\.0|1{2}2\.0|12[56]\.0|130\.0|134\.0|1(39|4[0-6])\.0)(\.\d+|)|(Maci|X1{2}).+ Version\/26\.[12]([,.]\d+|)( \(\w+\)|)( Mobile\/\w+|) Safari\/|Chrome.+OPR\/12[12]\.0\.\d+|(CPU[ +]OS|iPhone[ +]OS|CPU[ +]iPhone|CPU IPhone OS|CPU iPad OS)[ +]+(18[._][56]|26[._][01])([._]\d+|)|Opera Mini|Android:?[ /-]142(\.0|)(\.\d+|)|Mobile Safari.+OPR\/8(0\.){2}\d+|Android.+Firefox\/14{2}\.0(\.\d+|)|Android.+Chrom(ium|e)\/142\.0(\.\d+|)|Android.+(UC? ?Browser|UCWEB|U3)[ /]?1(5\.){2}\d+|SamsungBrowser\/2[89]\.0|Android.+MQ{2}Browser\/14(\.9|)(\.\d+|)|K[Aa][Ii]OS\/(2\.5|3\.[01])(\.\d+|)/'; +const BROWSER_REGEX = '/Edge?\/14[34]\.0(\.\d+|)|Firefox\/(140\.0|1(4[6-9]|50)\.0)(\.\d+|)|Chrom(ium|e)\/(109\.0|1{2}2\.0|131\.0|13{2}\.0|139\.0|14[2-7]\.0)(\.\d+|)|(Maci|X1{2}).+ Version\/26\.[1-3]([,.]\d+|)( \(\w+\)|)( Mobile\/\w+|) Safari\/|Chrome.+OPR\/12[45]\.0\.\d+|(CPU[ +]OS|iPhone[ +]OS|CPU[ +]iPhone|CPU IPhone OS|CPU iPad OS)[ +]+(18[._][5-7]|26[._][1-3])([._]\d+|)|Opera Mini|Android:?[ /-]14{2}(\.0|)(\.\d+|)|Mobile Safari.+OPR\/8(0\.){2}\d+|Android.+Firefox\/147\.0(\.\d+|)|Android.+Chrom(ium|e)\/14{2}\.0(\.\d+|)|Android.+(UC? ?Browser|UCWEB|U3)[ /]?1(5\.){2}\d+|SamsungBrowser\/2[89]\.0|Android.+MQ{2}Browser\/14(\.9|)(\.\d+|)|K[Aa][Ii]OS\/(2\.5|3\.[01])(\.\d+|)/'; /** * The latest branch of PHP which WordPress.org recommends. @@ -87,23 +87,27 @@ function get_browser_check_response( string $agent ) { // Switch delimiter to avoid conflicts. $regex = '#' . trim( BROWSER_REGEX, '/' ) . '#'; $supported = preg_match( $regex, $agent, $matches ); + $data = parse_user_agent( $agent ); + + $default_data = [ + 'platform' => _x( 'your platform', 'operating system check', 'fair' ), + 'name' => _x( 'your browser', 'browser version check', 'fair' ), + 'version' => '', + 'current_version' => '', + 'upgrade' => ! $supported, + 'insecure' => ! $supported, + 'update_url' => 'https://browsehappy.com/', + 'img_src' => '', + 'img_src_ssl' => '', + ]; + $data = array_merge( $default_data, $data ); return [ 'response' => [ 'code' => 200, 'message' => 'OK', ], - 'body' => json_encode( [ - 'platform' => _x( 'your platform', 'operating system check', 'fair' ), - 'name' => _x( 'your browser', 'browser version check', 'fair' ), - 'version' => '', - 'current_version' => '', - 'upgrade' => ! $supported, - 'insecure' => ! $supported, - 'update_url' => 'https://browsehappy.com/', - 'img_src' => '', - 'img_src_ssl' => '', - ] ), + 'body' => json_encode( $data ), 'headers' => [], 'cookies' => [], 'http_response_code' => 200, @@ -262,3 +266,447 @@ function get_server_check_response( string $version ) { 'http_response_code' => 200, ]; } + +/** + * Returns current version numbers for all browsers. + * + * These are for major release branches, not full build numbers. + * Firefox 3.6, 4, etc., not Chrome 11.0.696.65. + * + * @return array Associative array of browser names with their respective + * current (or somewhat current) version number. + */ +function get_browser_current_versions() { + return [ + 'Chrome' => '18', // Lowest version at the moment (mobile). + 'Firefox' => '56', + 'Microsoft Edge' => '15.15063', + 'Opera' => '12.18', + 'Safari' => '11', + 'Internet Explorer' => '11', + ]; +} + +/** + * Returns browser data for a given browser. + * + * @param string|false $browser The name of the browser. Default false. + * @return false|array|object { + * Array of data objects about browsers. False if the browser is unknown. + * + * @type string $name Name of the browser. + * @type string $url The home URL for the browser. + * @type string $img_src The non-HTTPs URL for the browser's logo image. + * @type string $img_src_ssl The HTTPS URL for the browser's logo image. + * } + */ +function get_browser_data( $browser = false ) { + + $data = [ + 'Internet Explorer' => (object) [ + 'name' => 'Internet Explorer', + 'url' => 'https://support.microsoft.com/help/17621/internet-explorer-downloads', + ], + 'Edge' => (object) [ + 'name' => 'Microsoft Edge', + 'url' => 'https://www.microsoft.com/edge', + ], + 'Firefox' => (object) [ + 'name' => 'Mozilla Firefox', + 'url' => 'https://www.mozilla.org/firefox/', + ], + 'Safari' => (object) [ + 'name' => 'Safari', + 'url' => 'https://www.apple.com/safari/', + ], + 'Opera' => (object) [ + 'name' => 'Opera', + 'url' => 'https://www.opera.com/', + ], + 'Chrome' => (object) [ + 'name' => 'Google Chrome', + 'url' => 'https://www.google.com/chrome', + ], + ]; + + if ( false === $browser ) { + return $data; + } + + if ( ! isset( $data[ $browser ] ) ) { + return false; + } + + return $data[ $browser ]; +} + +/** + * Returns an associative array of explicit browser token names and their + * associated info. + * + * Explicit tokens are tokens that, if present, indicate a specific browser. + * + * If a browser is not identified by an explicit token, or s special + * handling not supported by the default handler, then a new conditional block + * for the browser instead needs to be added in parse_user_agent(). + * + * In any case, the browser token name also needs to be added to the regex for + * browser tokens in parse_user_agent(). + * + * @return array { + * Associative array of browser tokens and their associated data. + * + * @type array $data { + * Associative array of browser data. All are optional. + * + * @type string $name Name of browser, if it differs from the + * token name. Default is token name. + * @type bool $use_version Should the 'Version' token, if present, + * supercede the version associated with the + * browser token? Default false. + * @type bool $mobile Does the browser signify the platform is + * mobile (for situations where it may no + * already be apparent)? Default false. + * @type string $platform The name of the platform, to supercede + * whatever platform may have been detected. + * Default empty string. + * } + * } + */ +function get_explicit_browser_tokens() { + return [ + 'Camino' => [], + 'Chromium' => [], + 'Edge' => [ + 'name' => 'Microsoft Edge', + ], + 'Kindle' => [ + 'name' => 'Kindle Browser', + 'use_version' => true, + ], + 'Konqueror' => [], + 'konqueror' => [ + 'name' => 'Konqueror', + ], + 'NokiaBrowser' => [ + 'name' => 'Nokia Browser', + 'mobile' => true, + ], + 'Opera Mini' => [ // Must be before 'Opera'. + 'mobile' => true, + 'use_version' => true, + ], + 'Opera' => [ + 'use_version' => true, + ], + 'OPR' => [ + 'name' => 'Opera', + 'use_version' => true, + ], + 'PaleMoon' => [ + 'name' => 'Pale Moon', + ], + 'QQBrowser' => [ + 'name' => 'QQ Browser', + ], + 'RockMelt' => [], + 'SamsungBrowser' => [ + 'name' => 'Samsung Browser', + ], + 'SeaMonkey' => [], + 'Silk' => [ + 'name' => 'Amazon Silk', + ], + 'S40OviBrowser' => [ + 'name' => 'Ovi Browser', + 'mobile' => true, + 'platform' => 'Symbian', + ], + 'UCBrowser' => [ // Must be before 'UCWEB'. + 'name' => 'UC Browser', + ], + 'UCWEB' => [ + 'name' => 'UC Browser', + ], + 'Vivaldi' => [], + 'IEMobile' => [ // Keep last just in case. + 'name' => 'Internet Explorer Mobile', + ], + ]; +} + +/** + * Parses a user agent string into its important parts. + * + * @param string $user_agent The user agent string for a browser. + * @return array { + * Array containing data based on the parsing of the user agent. + * + * @type string $platform The platform running the browser. + * @type string $name The name of the browser. + * @type string $version The reported version of the browser. + * @type string $update_url The URL to obtain the update for the browser. + * @type string $img_src The non-HTTPS URL for the browser's logo image. + * @type string $img_src_ssl The HTTPS URL for the browser's logo image. + * @type string $current_version The current latest version of the browser. + * @type bool $upgrade Is there an update available for the browser? + * @type bool $insecure Is the browser insecure? + * @type bool $mobile Is the browser on a mobile platform? + * } + */ +function parse_user_agent( $user_agent ) { + $data = [ + 'name' => '', + 'version' => '', + 'platform' => '', + 'update_url' => '', + 'img_src' => '', + 'img_src_ssl' => '', + 'current_version' => '', + 'upgrade' => false, + 'insecure' => false, + 'mobile' => false, + ]; + $mobile_device = ''; + + /** + * Identify platform/OS in user-agent string. + * '/(?P' // Capture subpattern matches into 'platform' array. + * . 'Windows Phone( OS)?|Symbian|SymbOS|Android|iPhone' // Platform tokens. + * . '|iPad|Windows|Linux|Macintosh|FreeBSD|OpenBSD' // More platform tokens. + * . '|SunOS|RIM Tablet OS|PlayBook' // More platform tokens. + * . ')' + * . '(?:' + * . ' (NT|amd64|armv7l|zvav)' // Possibly followed by specific modifiers/specifiers. + * . ')*' + * . '(?:' + * . ' [ix]?[0-9._]+' // Possibly followed by architecture modifier (e.g. x86_64). + * . '(\-[0-9a-z\.\-]+)?' // Possibly followed by a hypenated version number. + * . ')*' + * . '(;|\))' // Ending in a semi-colon or close parenthesis. + * . '/im', // Case insensitive, multiline. + */ + if ( preg_match( + '/(?PWindows Phone( OS)?|Symbian|SymbOS|Android|iPhone|iPad|Windows|Linux|Macintosh|FreeBSD|OpenBSD|SunOS|RIM Tablet OS|PlayBook)(?: (NT|amd64|armv7l|zvav))*(?: [ix]?[0-9._]+(\-[0-9a-z\.\-]+)?)*(;|\))/im', + $user_agent, + $regs + ) ) { + $data['platform'] = $regs['platform']; + } + + /** + * Find tokens of interest in user-agent string. + * + * '%(?P' // Capture subpattern matches into the 'name' array. + * . 'Opera Mini|Opera|OPR|Edge|UCBrowser|UCWEB' // Browser tokens. + * . '|QQBrowser|SymbianOS|Symbian|S40OviBrowser' // More browser tokens. + * . '|Trident|Silk|Konqueror|PaleMoon|Puffin' // More browser tokens. + * . '|SeaMonkey|Vivaldi|Camino|Chromium|Kindle|Firefox' // More browser tokens. + * . '|SamsungBrowser|(?:Mobile )?Safari|NokiaBrowser' // More browser tokens. + * . '|MSIE|RockMelt|AppleWebKit|Chrome|IEMobile' // More browser tokens. + * . '|Version' // Version token. + * . ')' + * . '(?:' + * . '[/ ]' // Forward slash or space. + * . ')' + * . '(?P' // Capture subpattern matches into 'version' array. + * . '[0-9.]+' // One or more numbers and/or decimal points. + * . ')' + * . '%im', // Case insensitive, multiline. + */ + preg_match_all( + '%(?POpera Mini|Opera|OPR|Edge|UCBrowser|UCWEB|QQBrowser|SymbianOS|Symbian|S40OviBrowser|Trident|Silk|Konqueror|PaleMoon|Puffin|SeaMonkey|Vivaldi|Camino|Chromium|Kindle|Firefox|SamsungBrowser|(?:Mobile )?Safari|NokiaBrowser|MSIE|RockMelt|AppleWebKit|Chrome|IEMobile|Version)(?:[/ ])(?P[0-9.]+)%im', + $user_agent, + $result, + PREG_PATTERN_ORDER + ); + + // Create associative array with tokens as keys and versions as values. + $tokens = array_combine( array_reverse( $result['name'] ), array_reverse( $result['version'] ) ); + + // Properly set platform if Android is actually being reported. + if ( 'Linux' === $data['platform'] && false !== strpos( $user_agent, 'Android' ) ) { + if ( strpos( $user_agent, 'Kindle' ) ) { + $data['platform'] = 'Fire OS'; + } else { + $data['platform'] = 'Android'; + } + } elseif ( 'Windows Phone' === $data['platform'] ) { + // Normalize Windows Phone OS name when "OS" is omitted. + $data['platform'] = 'Windows Phone OS'; + } elseif ( in_array( $data['platform'], [ 'Symbian', 'SymbOS' ] ) || ! empty( $tokens['SymbianOS'] ) || ! empty( $tokens['Symbian'] ) ) { + // Standardize Symbian OS name. + if ( ! in_array( $data['platform'], [ 'Symbian', 'SymbOS' ] ) ) { + unset( $tokens['SymbianOS'] ); + unset( $tokens['Symbian'] ); + } + $data['platform'] = 'Symbian'; + } elseif ( ! $data['platform'] && preg_match( '/BlackBerry|Nokia|SonyEricsson/', $user_agent, $matches ) ) { + // Generically detect some mobile devices. + $data['platform'] = 'Mobile'; + $mobile_device = $matches[0]; + } + + // Flag known mobile platforms as mobile. + if ( in_array( $data['platform'], [ 'Android', 'Fire OS', 'iPad', 'iPhone', 'Mobile', 'PlayBook', 'RIM Tablet OS', 'Symbian', 'Windows Phone OS' ] ) ) { + $data['mobile'] = true; + } + + // If Version/x.x.x was specified in UA string store it and ignore it. + if ( ! empty( $tokens['Version'] ) ) { + $version = $tokens['Version']; + unset( $tokens['Version'] ); + } + + $explicit_tokens = get_explicit_browser_tokens(); + + // No indentifiers provided. + if ( ! $tokens ) { + if ( 'BlackBerry' === $mobile_device ) { + $data['name'] = 'BlackBerry Browser'; + } else { + $data['name'] = 'unknown'; + } + } elseif ( $found = array_intersect( array_keys( $explicit_tokens ), array_keys( $tokens ) ) ) { // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.FoundInControlStructure + // Explicitly identified browser (info defined above in $explicit_tokens). + $token = reset( $found ); + + $data['name'] = $explicit_tokens[ $token ]['name'] ?? $token; + $data['version'] = $tokens[ $token ]; + if ( empty( $explicit_tokens[ $token ]['use_version'] ) ) { + $version = ''; + } + if ( ! empty( $explicit_tokens[ $token ]['mobile'] ) ) { + $data['mobile'] = true; + } + if ( ! empty( $explicit_tokens[ $token ]['platform'] ) ) { + $data['platform'] = $explicit_tokens[ $token ]['platform']; + } + } elseif ( ! empty( $tokens['Puffin'] ) ) { + // Puffin. + $data['name'] = 'Puffin'; + $data['version'] = $tokens['Puffin']; + $version = ''; + // If not an already-identified mobile platform, set it as such. + if ( ! $data['mobile'] ) { + $data['mobile'] = true; + $data['platform'] = ''; + } + } elseif ( ! empty( $tokens['Trident'] ) ) { + // Trident (Internet Explorer). + // IE 8-10 more reliably report version via Trident token than MSIE token. + // IE 11 uses Trident token without an MSIE token. + // https://msdn.microsoft.com/library/hh869301(v=vs.85).aspx. + $data['name'] = 'Internet Explorer'; + $trident_ie_mapping = [ + '4.0' => '8.0', + '5.0' => '9.0', + '6.0' => '10.0', + '7.0' => '11.0', + ]; + $ver = $tokens['Trident']; + $data['version'] = $trident_ie_mapping[ $ver ] ?? $ver; + } elseif ( ! empty( $tokens['MSIE'] ) ) { + // Internet Explorer (pre v8.0). + $data['name'] = 'Internet Explorer'; + $data['version'] = $tokens['MSIE']; + } elseif ( ! empty( $tokens['AppleWebKit'] ) ) { + // AppleWebKit-emulating browsers. + if ( ! empty( $tokens['Mobile Safari'] ) ) { + if ( ! empty( $tokens['Chrome'] ) ) { + $data['name'] = 'Chrome'; + $version = $tokens['Chrome']; + } elseif ( 'Android' === $data['platform'] ) { + $data['name'] = 'Android Browser'; + } elseif ( 'Fire OS' === $data['platform'] ) { + $data['name'] = 'Kindle Browser'; + } elseif ( false !== strpos( $user_agent, 'BlackBerry' ) || false !== strpos( $user_agent, 'BB10' ) ) { + $data['name'] = 'BlackBerry Browser'; + $data['mobile'] = true; + + if ( false !== stripos( $user_agent, 'BB10' ) ) { + $tokens['Mobile Safari'] = ''; + $version = ''; + } + } else { + $data['name'] = 'Mobile Safari'; + } + } elseif ( ! empty( $tokens['Chrome'] ) ) { + $data['name'] = 'Chrome'; + $version = ''; + } elseif ( ! empty( $data['platform'] ) && 'PlayBook' == $data['platform'] ) { + $data['name'] = 'PlayBook'; + } elseif ( ! empty( $tokens['Safari'] ) ) { + if ( 'Android' === $data['platform'] ) { + $data['name'] = 'Android Browser'; + } elseif ( 'Symbian' === $data['platform'] ) { + $data['name'] = 'Nokia Browser'; + $tokens['Safari'] = ''; + } else { + $data['name'] = 'Safari'; + } + } else { + $data['name'] = 'unknown'; + $tokens['AppleWebKit'] = ''; + $version = ''; + } + $data['version'] = $tokens[ $data['name'] ] ?? ''; + } else { + // Fall back to whatever is being reported. + $ordered_tokens = array_reverse( $tokens ); + $data['version'] = reset( $ordered_tokens ); + $data['name'] = key( $ordered_tokens ); + } + + // Set the platform for Amazon-related browsers. + if ( in_array( $data['name'], [ 'Amazon Silk', 'Kindle Browser' ] ) ) { + $data['platform'] = 'Fire OS'; + $data['mobile'] = true; + } + + // If Version/x.x.x was specified in UA string. + if ( ! empty( $version ) ) { + $data['version'] = $version; + } + + if ( $data['mobile'] ) { + // Generically set "Mobile" as the platform if a platform hasn't been set. + if ( ! $data['platform'] ) { + $data['platform'] = 'Mobile'; + } + + // Don't fetch additional browser data for mobile platform browsers at this time. + return $data; + } + + $browser_data = get_browser_data( $data['name'] ); + $data['update_url'] = $browser_data ? $browser_data->url : ''; + $data['current_version'] = get_browser_version_from_name( $data['name'] ); + $data['upgrade'] = ( ! empty( $data['current_version'] ) && version_compare( $data['version'], $data['current_version'], '<' ) ); + + if ( 'Internet Explorer' === $data['name'] ) { + $data['insecure'] = true; + $data['upgrade'] = true; + } elseif ( 'Firefox' === $data['name'] && version_compare( $data['version'], '52', '<' ) ) { + $data['insecure'] = true; + } elseif ( 'Opera' === $data['name'] && version_compare( $data['version'], '12.18', '<' ) ) { + $data['insecure'] = true; + } elseif ( 'Safari' === $data['name'] && version_compare( $data['version'], '10', '<' ) ) { + $data['insecure'] = true; + } + + return $data; +} + +/** + * Returns the current version for the given browser. + * + * @param string $name The name of the browser. + * @return string The version for the browser or an empty string if an + * unknown browser. + */ +function get_browser_version_from_name( $name ) { + $versions = get_browser_current_versions(); + + return isset( $versions[ $name ] ) ? $versions[ $name ] : ''; +} diff --git a/languages/fair.pot b/languages/fair.pot index f69dec09..91e798da 100644 --- a/languages/fair.pot +++ b/languages/fair.pot @@ -1,15 +1,15 @@ -# Copyright (C) 2025 FAIR Contributors +# Copyright (C) 2026 FAIR Contributors # This file is distributed under the GPLv2. msgid "" msgstr "" -"Project-Id-Version: FAIR Connect - Federated and Independent Repositories 1.1.0\n" +"Project-Id-Version: FAIR Connect - Federated and Independent Repositories 1.2.2\n" "Report-Msgid-Bugs-To: https://github.com/fairpm/fair-plugin/issues\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"POT-Creation-Date: 2025-12-11T04:32:53+00:00\n" +"POT-Creation-Date: 2026-01-07T22:20:21+00:00\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "X-Generator: WP-CLI 2.12.0\n" "X-Domain: fair\n" @@ -319,68 +319,76 @@ msgstr "" msgid "The PLC directory did not return the DID that was sent or the DID was invalid." msgstr "" -#: inc/packages/namespace.php:47 +#: inc/packages/namespace.php:50 msgid "ID is not a valid DID." msgstr "" -#: inc/packages/namespace.php:52 +#: inc/packages/namespace.php:55 msgid "DID could not be parsed as a URI." msgstr "" -#: inc/packages/namespace.php:63 +#: inc/packages/namespace.php:66 msgid "Unsupported DID method." msgstr "" -#: inc/packages/namespace.php:128 +#: inc/packages/namespace.php:146 +msgid "The package's file list could not be retrieved." +msgstr "" + +#: inc/packages/namespace.php:162 +msgid "No FAIR packages were found." +msgstr "" + +#: inc/packages/namespace.php:180 msgid "DID is not a valid package to fetch metadata for." msgstr "" -#: inc/packages/namespace.php:139 +#: inc/packages/namespace.php:191 msgid "Fetched metadata does not match the requested DID." msgstr "" -#: inc/packages/namespace.php:172 +#: inc/packages/namespace.php:224 msgid "HTTP error code received" msgstr "" -#: inc/packages/namespace.php:252 +#: inc/packages/namespace.php:306 msgid "DID does not contain valid signing keys." msgstr "" -#: inc/packages/namespace.php:262 +#: inc/packages/namespace.php:316 msgid "No releases found in the repository." msgstr "" -#: inc/packages/namespace.php:827 +#: inc/packages/namespace.php:954 msgctxt "alias validation error" msgid "Multiple aliases set in DID; packages may only have a single alias" msgstr "" -#: inc/packages/namespace.php:837 +#: inc/packages/namespace.php:964 msgctxt "alias validation error" msgid "Invalid FAIR alias format" msgstr "" -#: inc/packages/namespace.php:846 +#: inc/packages/namespace.php:973 msgctxt "alias validation error" msgid "FAIR alias format exceeds valid domain length" msgstr "" #. translators: %s: domain -#: inc/packages/namespace.php:859 +#: inc/packages/namespace.php:986 #, php-format msgctxt "alias validation error" msgid "Missing verification record for \"%s\"" msgstr "" #. translators: %s: domain -#: inc/packages/namespace.php:873 +#: inc/packages/namespace.php:1000 #, php-format msgctxt "alias validation error" msgid "Verification record for \"%s\" is invalid" msgstr "" -#: inc/packages/namespace.php:884 +#: inc/packages/namespace.php:1011 msgctxt "alias validation error" msgid "DID in validation record does not match" msgstr "" diff --git a/plugin.php b/plugin.php index bdfb20b0..043df1cd 100644 --- a/plugin.php +++ b/plugin.php @@ -2,7 +2,7 @@ /** * Plugin Name: FAIR Connect - Federated and Independent Repositories * Description: Make your site more FAIR. - * Version: 1.2.2 + * Version: 1.3.0-RC1 * Author: FAIR Contributors * Author URI: https://fair.pm * Security: security@fair.pm @@ -20,7 +20,7 @@ namespace FAIR; -const VERSION = '1.2.2'; +const VERSION = '1.3.0-RC1'; const PLUGIN_DIR = __DIR__; const PLUGIN_FILE = __FILE__; From eb70d70830e3ec51a3fb272980362045526ec8f4 Mon Sep 17 00:00:00 2001 From: Chuck Adams Date: Mon, 16 Feb 2026 11:02:59 -0700 Subject: [PATCH 02/17] set update-browserslist.yaml to manual dispatch only (#447) Signed-off-by: Chuck Adams --- .github/workflows/update-browserslist.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/update-browserslist.yaml b/.github/workflows/update-browserslist.yaml index 55b25b0b..1cae411b 100644 --- a/.github/workflows/update-browserslist.yaml +++ b/.github/workflows/update-browserslist.yaml @@ -2,8 +2,6 @@ name: Update browserslist regex on: workflow_dispatch: - schedule: - - cron: "0 0 * * 1" # Every Monday at midnight jobs: update-browserslist-regex: From 4130a7dd098eaf572a6752525b67d187dfc7170e Mon Sep 17 00:00:00 2001 From: "Ipstenu (Mika Epstein)" Date: Mon, 16 Mar 2026 09:44:02 -0700 Subject: [PATCH 03/17] Update RELEASE.MD (#458) Signed-off-by: Ipstenu (Mika Epstein) Signed-off-by: Mika Signed-off-by: Mika Ipstenu Epstein Signed-off-by: Mika Epstein --- .gitignore | 1 + RELEASE.MD | 231 ++++++++++++++++++++++++++++++++++---- inc/updater/namespace.php | 65 ----------- 3 files changed, 210 insertions(+), 87 deletions(-) diff --git a/.gitignore b/.gitignore index d30c9112..ba3e0534 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ phpcs.xml /tests/phpunit/cache /tests/phpunit/coverage wp-tests-config.php +*.swp # Track placeholders so that empty directories stay in the repo. !.gitkeep diff --git a/RELEASE.MD b/RELEASE.MD index b7003c3a..1cd424ec 100644 --- a/RELEASE.MD +++ b/RELEASE.MD @@ -1,17 +1,49 @@ # Releasing a new version of FAIR Connect ## 0. Ensure adequate permissions -To perform a release of FAIR Connect, you need the following: +To perform a release of FAIR Connect, the Release Manager needs the following: - `write` or higher permissions on the [FAIR Connect repository](https://github.com/fairpm/fair-plugin) -- `maintain` or higher permissions on the [TSC repository](https://github.com/fairpm/tsc) (this is required to post a Discussion in the Announcements category per step 7.3.) +- `maintain` or higher permissions on the [TSC repository](https://github.com/fairpm/tsc) (this is required to post a Discussion in the Announcements category per step 9.3.) -## 1. Verify milestone readiness +## 1. Branching strategy overview + +FAIR Connect follows a gated release branching strategy to ensure quality and control. For the complete strategy and automation details, see [Release Branching Strategy](https://github.com/fairpm/tsc/blob/main/process/release-branching.md). + +### Release branch lifecycle: +1. A `release/X.Y.Z` branch is created from `main` before the release process begins +2. All version bumps and fixes for this release happen on the `release/X.Y.Z` branch +3. The release manager merges `release/X.Y.Z` → `development` for testing and RC validation +4. Once validated, the release manager merges `release/X.Y.Z` → `main` for the production release + +This document assumes the `release/X.Y.Z` branch already exists. + +## 2. Release types and versioning + +FAIR Connect supports two types of releases, each with distinct workflows and version naming conventions: + +### Production Release +A production release represents a stable, tested version ready for general use. +- **Version format:** `MAJOR.MINOR.PATCH` (e.g., `1.2.0`) +- **Merge target:** `main` branch +- **GitHub release:** Marked as the latest release +- **Announcement:** Posted to Announcements discussion + +### Release Candidate (RC) +A release candidate is a pre-release version intended for testing and validation before the final production release. Multiple RCs may be created and tested before the final release. +- **Version format:** `MAJOR.MINOR.PATCH-RC#` (e.g., `1.2.0-RC1`, `1.2.0-RC2`) +- **Merge target:** `development` branch +- **GitHub release:** Marked as a pre-release (not the latest release) +- **Announcement:** Typically not announced to production users; may be shared in Discussions for testing feedback + +Use the appropriate workflow below based on which release type you are performing. + +## 3. Verify milestone readiness Before starting the release process, ensure that the milestone for the upcoming release is finalized and clear. 1. Go to the FAIR Connect [**Milestones**](https://github.com/fairpm/fair-plugin/milestones) page. - You can also access this from the repository’s main page by clicking **Issues** or **Pull requests**, then **Milestones**. + You can also access this from the repository's main page by clicking **Issues** or **Pull requests**, then **Milestones**. 2. Open the milestone corresponding to the version being released. 3. Review all issues and pull requests assigned to the milestone. @@ -24,7 +56,13 @@ Before starting the release process, ensure that the milestone for the upcoming Once the milestone contains no open issues or pull requests, the release is ready to proceed. -## 2. Start the release workflow +--- + +# Releasing a Release Candidate (RC) + +Follow these steps to release an RC from the `release/X.Y.Z` branch. Multiple RCs may be created and tested before moving to the production release. + +## 1. Start the release workflow 1. Go to the FAIR Connect [repository](https://github.com/fairpm/fair-plugin/actions). @@ -32,14 +70,133 @@ Once the milestone contains no open issues or pull requests, the release is read 3. In the *Actions* workflow list, select **Bump version for release**. +## 2. Configure and run the workflow + +1. Click the **Run workflow** button. A workflow input panel opens. + +2. Complete the following fields: + + - **Use workflow from:** Select **Branch: release/X.Y.Z** + - **New version being released:** Enter the RC version using the format `X.Y.Z-RC#` (e.g., `1.2.0-RC1`, `1.2.0-RC2`) + +3. Click the **Run workflow** button to start the release process. + +4. Refresh the page to view workflow progress. + +5. Click the running workflow to open the logs. + +6. When the workflow finishes, it creates a pull request containing the version-bump changes. + +## 3. Review and merge the version bump PR + +1. Go to the **Pull requests** tab. + +2. Open the version-bump pull request created by the release workflow. + +3. Review the changes: + + - Update of the version number in `plugin.php` + - Update the `VERSION` constant in `plugin.php` + +4. Confirm that the changes are correct. + +5. After review and approval, merge the PR to the `release/X.Y.Z` branch. + +6. Go to the **Actions** tab to verify that workflow processing is complete. + +7. Continue to the next step once all workflows finish. + +## 4. Merge release branch to development + +The `release/X.Y.Z` branch must be merged to `development` for testing and validation. + +1. In the repository, create a pull request to merge `release/X.Y.Z` → `development` +2. For the PR title, use: `release: merge release/X.Y.Z into development for RC testing` +3. Ensure all checks pass and obtain required reviews per branch protection rules +4. Merge the PR to `development` + +**BE AWARE!** The release branch may be auto-deleted. If so, _BEFORE_ you close the window, click **RESTORE BRANCH** + +## 5. Create a new release on GitHub + +1. From the repository's main page, click the **Releases** link — or go directly to the [Releases page](https://github.com/fairpm/fair-plugin/releases). + +2. Click the **Draft a new release** button. + +3. In the **Select tag** field: + - Select the tag that matches the version you just bumped to (e.g., `1.2.0-RC1`). + - Create a new tag if it does not appear in the dropdown. + - **Target branch:** Select `development` + +4. In the **Release title** field, enter a title for the release (e.g., `1.2.0-RC1`). + +5. Under **Release notes**: + - Leave the **Describe this release** field empty or add minimal testing instructions (release notes will be generated when the final production release is created). + - Check **Set as a pre-release** to mark this as a release candidate. + +6. Click the **Save draft** button. + +## 6. Finalize and publish the release + +1. Return to the **Draft Release** page, if not already there. + +2. Release-specific settings: + - Do NOT check **Set as the latest release** (already marked as pre-release from step 5). + - Optionally check **Create a discussion for this release** if you want to notify testers; if checked, you may choose a different category (e.g., General) instead of Announcements. + +3. In a new browser tab, go to the repository's **Actions** tab and confirm all workflows have completed. + +4. Return to the **Draft Release** page and click **Publish release**. This initiates the remaining release workflows. + +5. Verify the release: + - Visit the [Releases page](https://github.com/fairpm/fair-plugin/releases) to confirm RC is tagged as pre-release. + - Notify relevant testers to validate the RC version. + +## 7. Next steps + +### If additional testing iterations are needed: +1. Continue making fixes on the `release/X.Y.Z` branch +2. Merge fixes back to `development` +3. Repeat steps 2-6 for the next RC (e.g., `1.2.0-RC2`) + +### If the RC is validated and ready for production: +Proceed to the **Production Release** workflow below for the final release. + +--- + +# Releasing a Production Release + +Follow these steps to release the production version from `main` after the RC(s) have been validated. + +## 1. Start the release workflow + +1. Go to the FAIR Connect [repository](https://github.com/fairpm/fair-plugin/actions). + +2. Click the **Actions** tab. + +3. In the *Actions* workflow list, select **Bump version for release**. + +## 2. Merge release branch into main + +The `release/X.Y.Z` branch contains all the code and must be merged to `main` for the production release. + +1. In the repository, create a pull request to merge `release/X.Y.Z` → `main` +2. For the PR title, use: `release: merge release/X.Y.Z into main for production release` +3. Ensure all checks pass and obtain required reviews per branch protection rules +4. Merge the PR to `main` + +Once all workflows are complete, move on to the next step. + +Note: At this stage you _can_ delete the release branch. + ## 3. Configure and run the workflow 1. Click the **Run workflow** button. A workflow input panel opens. 2. Complete the following fields: - - **Use workflow from:** Select **Branch: main** (default). - - **New version being released:** Enter the release version number (e.g., `1.0.0`). FAIR Connect uses semantic versioning (`MAJOR.MINOR.PATCH`). + - **Use workflow from:** Select **Branch: `main`** + - **New version being released:** Enter the production version using semantic versioning format (e.g., `1.2.0`) 3. Click the **Run workflow** button to start the release process. @@ -70,13 +227,14 @@ Once the milestone contains no open issues or pull requests, the release is read ## 5. Create a new release on GitHub -1. From the repository’s main page, click the **Releases** link — or go directly to the [Releases page](https://github.com/fairpm/fair-plugin/releases). +1. From the repository's main page, click the **Releases** link — or go directly to the [Releases page](https://github.com/fairpm/fair-plugin/releases). 2. Click the **Draft a new release** button. 3. In the **Select tag** field: - Select the tag that matches the version you just bumped to (e.g., `1.2.0`). - Create a new tag if it does not appear in the dropdown. + - **Target branch:** Select `main` 4. In the **Release title** field, enter a title for the release (e.g., `1.2.0`). @@ -84,13 +242,11 @@ Once the milestone contains no open issues or pull requests, the release is read - Leave **Previous tag** set to `Auto`. - Click **Generate release notes**. - Review and edit the generated notes as needed. - - Click the **Save draft** button. + - You can add additional information directly in the **Describe this release** field if needed. If a teammate is preparing a release post for FAIR.pm, coordinate with them to include any relevant details. -> [!TIP] -> You can add additional information directly in the **Describe this release** field. -> If a teammate is preparing a release post for FAIR.pm, coordinate with them to include any relevant details. +6. Click the **Save draft** button. -## 6. Update the changelog before publishing the GitHub release +## 6. Update the changelog 1. In a new browser tab, open [`CHANGELOG.md`](/CHANGELOG.md). @@ -107,20 +263,51 @@ Once the milestone contains no open issues or pull requests, the release is read 6. Review, approve, and merge the pull request. - ## 7. Finalize and publish the release 1. Return to the **Draft Release** page. -2. Check **Set as the latest release**. +2. Release-specific settings: + - Check **Set as the latest release**. + - Check **Create a discussion for this release** and choose the **Announcements** category. + +3. In a new browser tab, go to the repository's **Actions** tab and confirm all workflows have completed. + +4. Return to the **Draft Release** page and click **Publish release**. This initiates the remaining release workflows. + +## 8. Flush Caches + +Flush the caches in the following order: + +**Redis** + +1. Log in to AWS Console for Site Cache: https://us-east-1.console.aws.amazon.com/elasticache/home?region=us-east-1#/valkey/site-cache +2. Scroll down and click on **Connect to Cache** +3. When the page has loaded, type `flushall` + +**Git Updater** + +1. Log in to the network admin page for GitUpdater: https://fair.pm/wp-admin/network/settings.php?page=git-updater +2. Press the **Clear Cache** button + +**Fastly** + +1. Log in to Fastly and go to the Fair.pm page: https://manage.fastly.com/configure/services/0EAkUOqF5q35zrk6oobCP4 +2. From the Purge menu option, select **Purge URL** +3. Leave subdomain blank and put in the path `/wp-json/git-updater/v1/update-api/` +4. When prompted, purge another URL +5. Put in the subdomain `api` and the path `/git-updater/v1/update-api/` + +## 9. Verify the release: +- Visit the [Releases page](https://github.com/fairpm/fair-plugin/releases) to confirm latest release. +- Go to `https://api.fair.pm/git-updater/v1/update-api/?slug=fair-plugin` and check +- Check any site using FAIR Connect to ensure the new version is available. -3. Check **Create a discussion for this release** and choose the **Announcements** category. +## 10. Merge the new production code back -4. In a new browser tab, go to the repository’s **Actions** tab and confirm all workflows have completed. +- Merge `main` back into `development` +- Create a new branch for the next release off of `main` -5. Return to the **Draft Release** page and click **Publish release**. This initiates the remaining release workflows. +Guess what? -6. Verify the release: - - Visit the [Releases page](https://github.com/fairpm/fair-plugin/releases) to confirm latest release. - - Check any site using FAIR Connect to ensure the new version is available. - - Verify the updated version number in the API response. (Example URL to check: https://api.fair.pm/git-updater/v1/update-api/?slug=fair-plugin) +You're done! Make a post on the website and cheer. diff --git a/inc/updater/namespace.php b/inc/updater/namespace.php index a1b64aeb..ede4e357 100644 --- a/inc/updater/namespace.php +++ b/inc/updater/namespace.php @@ -158,71 +158,6 @@ function display_plugin_update_error( $plugin_file, $plugin_data, $status, $did ); } -/** - * Register hooks to display update errors below plugin rows. - */ -function register_plugin_row_hooks(): void { - $packages = get_packages(); - $plugins = $packages['plugins'] ?? []; - - foreach ( $plugins as $did => $path ) { - $plugin_file = plugin_basename( $path ); - add_action( - "after_plugin_row_{$plugin_file}", - function ( $file, $plugin_data, $status ) use ( $did ) { - display_plugin_update_error( $file, $plugin_data, $status, $did ); - }, - 10, - 3 - ); - } -} - -/** - * Display a cached update error below the plugin row. - * - * @param string $plugin_file Path to the plugin file relative to the plugins directory. - * @param array $plugin_data An array of plugin data. - * @param string $status Status filter currently applied to the plugin list. - * @param string $did The DID of the plugin. - */ -function display_plugin_update_error( $plugin_file, $plugin_data, $status, $did ): void { - $error = get_site_transient( CACHE_UPDATE_ERRORS . $did ); - if ( ! is_wp_error( $error ) ) { - return; - } - - $wp_list_table = _get_list_table( 'WP_Plugins_List_Table' ); - $colspan = $wp_list_table->get_column_count(); - - // Calculate time remaining until retry. - $error_data = $error->get_error_data(); - $timestamp = $error_data['timestamp'] ?? 0; - $retry_time = $timestamp + CACHE_LIFETIME_FAILURE; - $time_remaining = human_time_diff( time(), $retry_time ); - - $message = sprintf( - /* translators: %1$s: Error message, %2$s: Time period */ - __( 'Error: %1$s. Update checks paused for %2$s.', 'fair' ), - $error->get_error_message(), - $time_remaining, - ); - - $active_class = is_plugin_active( $plugin_file ) ? ' active' : ''; - - printf( - ' - -

%4$s

- - ', - esc_attr( $active_class ), - esc_attr( sanitize_title( $plugin_file ) ), - esc_attr( $colspan ), - esc_html( $message ), - ); -} - /** * Download a package with signature verification. * From 1188a3dfef45a0a98555c4b0a80c7281fb401b6e Mon Sep 17 00:00:00 2001 From: Andy Fragen Date: Mon, 16 Mar 2026 16:28:57 -0700 Subject: [PATCH 04/17] hotfix, originally in PR4428 (#463) Signed-off-by: Andy Fragen --- inc/updater/class-updater.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/updater/class-updater.php b/inc/updater/class-updater.php index bf175528..caa28a63 100644 --- a/inc/updater/class-updater.php +++ b/inc/updater/class-updater.php @@ -110,7 +110,7 @@ public static function load_hooks() { * * @return string|WP_Error */ - public function upgrader_source_selection( $source, string $remote_source, WP_Upgrader $upgrader, $hook_extra = null ) { + public static function upgrader_source_selection( $source, string $remote_source, WP_Upgrader $upgrader, $hook_extra = null ) { global $wp_filesystem; // Exit early for errors. From 767f86babd03bd81ff02351fdf192f90ecd428b7 Mon Sep 17 00:00:00 2001 From: Marc Armengou <83702259+marcarmengou@users.noreply.github.com> Date: Mon, 6 Apr 2026 19:23:13 +0200 Subject: [PATCH 05/17] Update stable tag version to 1.4.0 (#461) Signed-off-by: Marc Armengou <83702259+marcarmengou@users.noreply.github.com> --- readme.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.txt b/readme.txt index 77fe2885..bcc7da63 100644 --- a/readme.txt +++ b/readme.txt @@ -6,7 +6,7 @@ Tags: packages, updater, installer, technical independence Requires at least: 5.4 Requires PHP: 7.4 Tested up to: 6.9 -Stable tag: 1.1.0 +Stable tag: 1.4.0 FAIR is a system for using **F**ederated **a**nd **I**ndependent **R**epositories in WordPress. From dd248af832256f6d90dbd9754a66bc3614be0052 Mon Sep 17 00:00:00 2001 From: Namith Jawahar <48271037+namithj@users.noreply.github.com> Date: Mon, 6 Apr 2026 22:55:12 +0530 Subject: [PATCH 06/17] Did manager integration (#465) Co-authored-by: Carrie Dils --- .github/workflows/releases.yml | 16 +- composer.json | 4 +- inc/packages/admin/info.php | 15 +- inc/packages/did/class-did.php | 32 - inc/packages/did/class-document.php | 96 - inc/packages/did/class-plc.php | 86 - inc/packages/did/class-web.php | 56 - inc/packages/namespace.php | 131 +- inc/updater/class-base58btc.php | 103 - inc/updater/class-updater.php | 1 - inc/updater/namespace.php | 11 +- package-lock.json | 4102 ++++++++--------- phpcs.xml.dist | 1 - plugin.php | 3 +- readme.txt | 2 +- .../tests/Packages/GetDidServiceTest.php | 82 + .../tests/Packages/GetFairSigningKeysTest.php | 96 + tests/phpunit/tests/Packages/ParseDidTest.php | 78 + 18 files changed, 2414 insertions(+), 2501 deletions(-) delete mode 100644 inc/packages/did/class-did.php delete mode 100644 inc/packages/did/class-document.php delete mode 100644 inc/packages/did/class-plc.php delete mode 100644 inc/packages/did/class-web.php delete mode 100644 inc/updater/class-base58btc.php create mode 100644 tests/phpunit/tests/Packages/GetDidServiceTest.php create mode 100644 tests/phpunit/tests/Packages/GetFairSigningKeysTest.php create mode 100644 tests/phpunit/tests/Packages/ParseDidTest.php diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml index b9b60a9a..ef2b9a82 100644 --- a/.github/workflows/releases.yml +++ b/.github/workflows/releases.yml @@ -17,12 +17,26 @@ jobs: - name: Checkout code uses: actions/checkout@master + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + tools: composer + + - name: Install Composer dependencies + run: composer install --no-dev --optimize-autoloader --no-interaction + - name: Get tag id: tag run: echo "tag=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT - name: Build project - run: git archive -o /tmp/${{ github.event.repository.name }}-${{ steps.tag.outputs.tag }}.zip --prefix=${{ github.event.repository.name }}/ ${{ steps.tag.outputs.tag }} + run: | + git archive -o /tmp/${{ github.event.repository.name }}-${{ steps.tag.outputs.tag }}.zip --prefix=${{ github.event.repository.name }}/ ${{ steps.tag.outputs.tag }} + mkdir -p /tmp/vendor-stage/${{ github.event.repository.name }} + cp -r vendor /tmp/vendor-stage/${{ github.event.repository.name }}/vendor + cd /tmp/vendor-stage + zip -r /tmp/${{ github.event.repository.name }}-${{ steps.tag.outputs.tag }}.zip ${{ github.event.repository.name }}/vendor/ - name: Create WP distribution files run: bin/bundle.sh diff --git a/composer.json b/composer.json index 99c2add4..76ca67a2 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,9 @@ } ], "require": { - "php": ">=7.4" + "php": ">=8.0", + "fairpm/did-manager": "^0.0.3", + "fairpm/did-manager-wordpress": "^0.0.1" }, "require-dev": { "yoast/phpunit-polyfills": "*", diff --git a/inc/packages/admin/info.php b/inc/packages/admin/info.php index 68f474cf..4de16ca1 100644 --- a/inc/packages/admin/info.php +++ b/inc/packages/admin/info.php @@ -9,7 +9,6 @@ use FAIR\Packages; use FAIR\Packages\Admin; -use FAIR\Packages\DID\Document as DIDDocument; use FAIR\Packages\MetadataDocument; use FAIR\Packages\ReleaseDocument; use FAIR\Updater; @@ -359,13 +358,12 @@ function get_repository_hostname( string $did ) : ?string { return null; } - $repo = $did_doc->get_service( Packages\SERVICE_ID ); + $repo = Packages\get_did_service( $did_doc, Packages\SERVICE_ID ); if ( empty( $repo ) ) { return null; } - // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase - $host = parse_url( $repo->serviceEndpoint, PHP_URL_HOST ); + $host = parse_url( $repo['serviceEndpoint'], PHP_URL_HOST ); if ( empty( $host ) ) { // Invalid URL. return null; @@ -448,11 +446,14 @@ function add_requirement_notices( ReleaseDocument $release ) : void { * validation are not safe, while those without an alias or with a valid alias * are safe. * - * @param DIDDocument $did DID to validate. + * @param array $did_doc DID document array to validate. * @return bool True if the package is "safe" to install, false if install should be blocked. */ -function render_alias_notice( DIDDocument $did ) : bool { - $validation = Packages\validate_package_alias( $did ); +function render_alias_notice( $did_doc ) : bool { + if ( is_wp_error( $did_doc ) ) { + return true; + } + $validation = Packages\validate_package_alias( $did_doc ); $title = __( 'Domain Alias:', 'fair' ); $result = false; switch ( gettype( $validation ) ) { diff --git a/inc/packages/did/class-did.php b/inc/packages/did/class-did.php deleted file mode 100644 index c2a3e8d7..00000000 --- a/inc/packages/did/class-did.php +++ /dev/null @@ -1,32 +0,0 @@ -id = $id; - $this->service = $service; - $this->verificationMethod = $verificationMethod; - $this->alsoKnownAs = $alsoKnownAs; - } - - /** - * Get a service by type. - * - * @param string $type Service type. - * @return stdClass Service data, including id and serviceEndpoint - */ - public function get_service( string $type ) : ?stdClass { - return array_find( $this->service, fn ( $service ) => $service->type === $type ); - } - - /** - * Get valid signing keys for FAIR. - * - * Gets valid keys from the document which can be used to sign packages. - * - * @return stdClass[] List of keys, including id and publicKeyMultibase - */ - public function get_fair_signing_keys() : array { - return array_filter( $this->verificationMethod, function ( $key ) { - // Only multibase keys are supported. - if ( $key->type !== 'Multikey' ) { - return false; - } - - $parsed = parse_url( $key->id ); - - // Only permit keys with IDs prefixed with 'fair'. - return str_starts_with( $parsed['fragment'], 'fair' ); - } ); - } - // phpcs:enable -} diff --git a/inc/packages/did/class-plc.php b/inc/packages/did/class-plc.php deleted file mode 100644 index 4ad6d207..00000000 --- a/inc/packages/did/class-plc.php +++ /dev/null @@ -1,86 +0,0 @@ -id = $id; - } - - /** - * Get the DID type. - * - * One of plc, web. - */ - public function get_method() : string { - return static::METHOD; - } - - /** - * Get the full decentralized ID (DID). - */ - public function get_id() : string { - return $this->id; - } - - /** - * Fetch PLC document. - * - * @return Document|WP_Error - */ - public function fetch_document() { - $url = static::DIRECTORY_URL . $this->id; - $response = wp_remote_get( $url ); - - if ( is_wp_error( $response ) ) { - return $response; - } - - $data = json_decode( $response['body'] ); - if ( json_last_error() !== JSON_ERROR_NONE ) { - return new WP_Error( 'fair.packages.did.json_error', __( 'Unable to parse DID document response.', 'fair' ) ); - } - if ( 200 !== wp_remote_retrieve_response_code( $response ) && property_exists( $data, 'message' ) ) { - return new WP_Error( 'fair.packages.did.fetch.error', esc_html( $data->message ) ); - } - if ( empty( $data->id ) || $data->id !== $this->id ) { - return new WP_Error( 'fair.packages.did.fetch.mismatch', __( 'The PLC directory did not return the DID that was sent or the DID was invalid.', 'fair' ) ); - } - - $document = new Document( - $data->id, - $data->service ?? [], - $data->verificationMethod ?? [], - $data->alsoKnownAs ?? [], - ); - return $document; - } - // phpcs:enable -} diff --git a/inc/packages/did/class-web.php b/inc/packages/did/class-web.php deleted file mode 100644 index 8ca3dbff..00000000 --- a/inc/packages/did/class-web.php +++ /dev/null @@ -1,56 +0,0 @@ -id = $id; - } - - /** - * Get the DID type. - * - * One of plc, web. - */ - public function get_method() : string { - return static::METHOD; - } - - /** - * Get the full decentralized ID (DID). - */ - public function get_id() : string { - return $this->id; - } - - /** - * Fetch PLC Web document. - * - * @return void|null - */ - public function fetch_document() { - return null; // todo. - } -} diff --git a/inc/packages/namespace.php b/inc/packages/namespace.php index 1dfff720..0c0dbc62 100644 --- a/inc/packages/namespace.php +++ b/inc/packages/namespace.php @@ -10,10 +10,9 @@ use const FAIR\CACHE_BASE; use const FAIR\CACHE_LIFETIME; use const FAIR\CACHE_LIFETIME_FAILURE; -use FAIR\Packages\DID\Document as DIDDocument; -use FAIR\Packages\DID\PLC; -use FAIR\Packages\DID\Web; +use FAIR\DID\PLC\PlcClient; use FAIR\Updater; +use FAIR\WordPress\DID\Parsers\PluginHeaderParser; use function FAIR\Packages\Admin\sort_sections_in_api; use Plugin_Upgrader; use Theme_Upgrader; @@ -28,7 +27,46 @@ const CONTENT_TYPE = 'application/json+fair'; const SERVICE_ID = 'FairPackageManagementRepo'; -// phpcs:disable WordPress.NamingConventions.ValidVariableName +/** + * Get the PLC client singleton. + * + * @return PlcClient + */ +function get_plc_client(): PlcClient { + static $client; + if ( ! $client ) { + $client = new PlcClient(); + } + return $client; +} + +/** + * Get a service from a DID document by type. + * + * @param array $did_doc DID document array. + * @param string $type Service type. + * @return array|null Service data, or null if not found. + */ +function get_did_service( array $did_doc, string $type ): ?array { + return array_find( $did_doc['service'] ?? [], fn ( $s ) => $s['type'] === $type ); +} + +/** + * Get valid signing keys for FAIR from a DID document. + * + * @param array $did_doc DID document array. + * @return array List of key arrays with id and publicKeyMultibase. + */ +function get_fair_signing_keys( array $did_doc ): array { + return array_filter( $did_doc['verificationMethod'] ?? [], function ( $key ) { + if ( $key['type'] !== 'Multikey' ) { + return false; + } + + $parsed = parse_url( $key['id'] ); + return str_starts_with( $parsed['fragment'] ?? '', 'fair' ); + } ); +} /** * Cache an update error for a package. @@ -65,7 +103,7 @@ function bootstrap() { * Parse DID. * * @param string $id DID. - * @return DID|WP_Error + * @return string|WP_Error Validated DID string on success, WP_Error on failure. */ function parse_did( string $id ) { if ( ! str_starts_with( $id, 'did:plc:' ) ) { @@ -77,16 +115,11 @@ function parse_did( string $id ) { return new WP_Error( 'fair.packages.validate_did.not_uri', __( 'DID could not be parsed as a URI.', 'fair' ) ); } - switch ( $parts[1] ) { - case PLC::METHOD: - return new PLC( $id ); - - case Web::METHOD: - return new Web( $id ); - - default: - return new WP_Error( 'fair.packages.validate_did.invalid_method', __( 'Unsupported DID method.', 'fair' ) ); + if ( $parts[1] !== 'plc' ) { + return new WP_Error( 'fair.packages.validate_did.invalid_method', __( 'Unsupported DID method.', 'fair' ) ); } + + return $id; } /** @@ -105,14 +138,14 @@ function get_did_hash( string $id ) { return $did; } - return substr( hash( 'sha256', $did->get_id() ), 0, 6 ); + return substr( hash( 'sha256', $did ), 0, 6 ); } /** * Get DID document. * * @param string $id DID. - * @return DIDDocument|WP_Error + * @return array|WP_Error DID document array on success, WP_Error on failure. */ function get_did_document( string $id ) { // Check for cached error from previous failed request. @@ -133,10 +166,12 @@ function get_did_document( string $id ) { return $did; } - $document = $did->fetch_document(); - if ( is_wp_error( $document ) ) { - cache_update_error( $id, $document ); - return $document; + try { + $document = get_plc_client()->resolve_did( $did ); + } catch ( \RuntimeException $e ) { + $error = new WP_Error( 'fair.packages.did.fetch_error', $e->getMessage() ); + cache_update_error( $id, $error ); + return $error; } // Clear any previous error on success. @@ -151,11 +186,9 @@ function get_did_document( string $id ) { * * @param string $path The absolute path to the package's directory or main file. * @param string $type The type of package. Allowed types are 'plugin' or 'theme'. - * @return DID|WP_Error The DID object on success, WP_Error on failure. + * @return string|WP_Error The DID string on success, WP_Error on failure. */ function get_did_by_path( $path, $type ) { - global $wp_filesystem; - if ( $type === 'theme' ) { if ( ! str_ends_with( $path, 'style.css' ) ) { $path = trailingslashit( $path ) . 'style.css'; @@ -168,28 +201,12 @@ function get_did_by_path( $path, $type ) { } if ( $type === 'plugin' ) { - if ( str_ends_with( $path, '.php' ) ) { - $id = get_file_data( $path, [ 'id' => 'Plugin ID' ] )['id']; + $parser = new PluginHeaderParser(); + $headers = $parser->parse( $path ); + $id = $headers['plugin_id'] ?? ''; + if ( $id ) { return parse_did( $id ); } - - $files = $wp_filesystem->dirlist( $path ) ?: false; - if ( ! $files ) { - // Finding a DID is impossible. - return new WP_Error( 'fair.packages.dirlist_failed', __( "The package's file list could not be retrieved.", 'fair' ) ); - } - - foreach ( $files as $filename => $data ) { - if ( $data['type'] !== 'f' || ! str_ends_with( $filename, '.php' ) ) { - continue; - } - - $filepath = trailingslashit( $path ) . $filename; - $id = get_file_data( $filepath, [ 'id' => 'Plugin ID' ] )['id']; - if ( $id ) { - return parse_did( $id ); - } - } } return new WP_Error( 'fair.packages.none_found', __( 'No FAIR packages were found.', 'fair' ) ); @@ -208,13 +225,13 @@ function fetch_package_metadata( string $id ) { } // Fetch data from the repository. - $service = $document->get_service( SERVICE_ID ); + $service = get_did_service( $document, SERVICE_ID ); if ( empty( $service ) ) { $error = new WP_Error( 'fair.packages.fetch_metadata.no_service', __( 'DID is not a valid package to fetch metadata for.', 'fair' ) ); cache_update_error( $id, $error ); return $error; } - $repo_url = $service->serviceEndpoint; + $repo_url = $service['serviceEndpoint']; $metadata = fetch_metadata_doc( $repo_url, $id ); @@ -348,7 +365,7 @@ function get_latest_release_from_did( $id ) { return $document; } - $valid_keys = $document->get_fair_signing_keys(); + $valid_keys = get_fair_signing_keys( $document ); if ( empty( $valid_keys ) ) { return new WP_Error( 'fair.packages.install.no_signing_keys', __( 'DID does not contain valid signing keys.', 'fair' ) ); } @@ -889,13 +906,13 @@ function move_package_during_install( $source, string $remote_source, WP_Upgrade return $source; } - $did_hash = get_did_hash( $did->get_id() ); + $did_hash = get_did_hash( $did ); if ( str_ends_with( $source, "{$did_hash}/" ) ) { // The directory name is likely already correct. return $source; } - $metadata = fetch_package_metadata( $did->get_id() ); + $metadata = fetch_package_metadata( $did ); if ( is_wp_error( $metadata ) || trim( $metadata->slug ?? '' ) === '' ) { // Cannot guarantee a slug-didhash format. dir-didhash is the best achievable. $new_source = untrailingslashit( $source ) . "-{$did_hash}/"; @@ -964,17 +981,17 @@ function maybe_add_accept_header( $args, $url ) : array { * * Uses cached result for one hour. * - * @param DIDDocument $did DID to validate. + * @param array $did_doc DID document array. * @return string|WP_Error|null Alias domain if successfully validated, null if no valid alias is set, or error otherwise. */ -function validate_package_alias( DIDDocument $did ) { - $cache_key = sprintf( 'fair_did_alias_%s', $did->id ); +function validate_package_alias( array $did_doc ) { + $cache_key = sprintf( 'fair_did_alias_%s', $did_doc['id'] ); $cached = get_site_transient( $cache_key ); if ( $cached ) { return $cached; } - $alias = fetch_and_validate_package_alias( $did ); + $alias = fetch_and_validate_package_alias( $did_doc ); set_site_transient( $cache_key, $alias, HOUR_IN_SECONDS ); return $alias; } @@ -988,11 +1005,11 @@ function validate_package_alias( DIDDocument $did ) { * * This function queries DNS directly, and is uncached. * - * @param DIDDocument $did DID to validate. + * @param array $did_doc DID document array. * @return string|WP_Error|null Alias domain if successfully validated, null if no valid alias is set, or error otherwise. */ -function fetch_and_validate_package_alias( DIDDocument $did ) { - $aliases = array_filter( $did->alsoKnownAs, fn ( $alias ) => is_string( $alias ) && str_starts_with( $alias, 'fair://' ) ); +function fetch_and_validate_package_alias( array $did_doc ) { + $aliases = array_filter( $did_doc['alsoKnownAs'] ?? [], fn ( $alias ) => is_string( $alias ) && str_starts_with( $alias, 'fair://' ) ); // Packages may only have a single alias, so ignore multiple. if ( empty( $aliases ) ) { @@ -1055,7 +1072,7 @@ function fetch_and_validate_package_alias( DIDDocument $did ) { } $expected_did = $record_match[1]; - if ( $expected_did !== $did->id ) { + if ( $expected_did !== $did_doc['id'] ) { return new WP_Error( 'fair.packages.get_package_alias.mismatched_did', _x( 'DID in validation record does not match', 'alias validation error', 'fair' ), @@ -1147,5 +1164,3 @@ function get_plugin_information( $result, $action, $args ) { return (object) $api_data; } - -// phpcs:enable diff --git a/inc/updater/class-base58btc.php b/inc/updater/class-base58btc.php deleted file mode 100644 index 02ec011f..00000000 --- a/inc/updater/class-base58btc.php +++ /dev/null @@ -1,103 +0,0 @@ -get_fair_signing_keys(); + $keys = Packages\get_fair_signing_keys( $doc ); if ( empty( $keys ) ) { return []; } @@ -264,16 +265,14 @@ function get_trusted_keys(): array { // Core expects base64-encoded keys. $recoded_keys = []; foreach ( $keys as $key ) { - // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase - $str = Base58BTC::decode( $key->publicKeyMultibase ); + $decoded = DidCodec::from_multibase_key( $key['publicKeyMultibase'] ); // Ed25519 keys only. - if ( substr( $str, 0, 2 ) !== "\xed\x01" ) { + if ( $decoded['codec'] !== DidCodec::MULTICODEC_ED25519_PUB ) { continue; } - $key_material = substr( $str, 2 ); - $recoded_keys[] = base64_encode( $key_material ); + $recoded_keys[] = base64_encode( $decoded['key'] ); } return $recoded_keys; diff --git a/package-lock.json b/package-lock.json index 1440b10c..0ae4d6fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,2053 +1,2053 @@ { - "name": "plugin", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "devDependencies": { - "@wordpress/env": "^10.22.0" - } - }, - "node_modules/@inquirer/checkbox": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.1.5.tgz", - "integrity": "sha512-swPczVU+at65xa5uPfNP9u3qx/alNwiaykiI/ExpsmMSQW55trmZcwhYWzw/7fj+n6Q8z1eENvR7vFfq9oPSAQ==", - "dev": true, - "dependencies": { - "@inquirer/core": "^10.1.10", - "@inquirer/figures": "^1.0.11", - "@inquirer/type": "^3.0.6", - "ansi-escapes": "^4.3.2", - "yoctocolors-cjs": "^2.1.2" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/confirm": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.9.tgz", - "integrity": "sha512-NgQCnHqFTjF7Ys2fsqK2WtnA8X1kHyInyG+nMIuHowVTIgIuS10T4AznI/PvbqSpJqjCUqNBlKGh1v3bwLFL4w==", - "dev": true, - "dependencies": { - "@inquirer/core": "^10.1.10", - "@inquirer/type": "^3.0.6" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/core": { - "version": "10.1.10", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.10.tgz", - "integrity": "sha512-roDaKeY1PYY0aCqhRmXihrHjoSW2A00pV3Ke5fTpMCkzcGF64R8e0lw3dK+eLEHwS4vB5RnW1wuQmvzoRul8Mw==", - "dev": true, - "dependencies": { - "@inquirer/figures": "^1.0.11", - "@inquirer/type": "^3.0.6", - "ansi-escapes": "^4.3.2", - "cli-width": "^4.1.0", - "mute-stream": "^2.0.0", - "signal-exit": "^4.1.0", - "wrap-ansi": "^6.2.0", - "yoctocolors-cjs": "^2.1.2" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/editor": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.10.tgz", - "integrity": "sha512-5GVWJ+qeI6BzR6TIInLP9SXhWCEcvgFQYmcRG6d6RIlhFjM5TyG18paTGBgRYyEouvCmzeco47x9zX9tQEofkw==", - "dev": true, - "dependencies": { - "@inquirer/core": "^10.1.10", - "@inquirer/type": "^3.0.6", - "external-editor": "^3.1.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/expand": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.12.tgz", - "integrity": "sha512-jV8QoZE1fC0vPe6TnsOfig+qwu7Iza1pkXoUJ3SroRagrt2hxiL+RbM432YAihNR7m7XnU0HWl/WQ35RIGmXHw==", - "dev": true, - "dependencies": { - "@inquirer/core": "^10.1.10", - "@inquirer/type": "^3.0.6", - "yoctocolors-cjs": "^2.1.2" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/figures": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.11.tgz", - "integrity": "sha512-eOg92lvrn/aRUqbxRyvpEWnrvRuTYRifixHkYVpJiygTgVSBIHDqLh0SrMQXkafvULg3ck11V7xvR+zcgvpHFw==", - "dev": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/input": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.1.9.tgz", - "integrity": "sha512-mshNG24Ij5KqsQtOZMgj5TwEjIf+F2HOESk6bjMwGWgcH5UBe8UoljwzNFHqdMbGYbgAf6v2wU/X9CAdKJzgOA==", - "dev": true, - "dependencies": { - "@inquirer/core": "^10.1.10", - "@inquirer/type": "^3.0.6" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/number": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.12.tgz", - "integrity": "sha512-7HRFHxbPCA4e4jMxTQglHJwP+v/kpFsCf2szzfBHy98Wlc3L08HL76UDiA87TOdX5fwj2HMOLWqRWv9Pnn+Z5Q==", - "dev": true, - "dependencies": { - "@inquirer/core": "^10.1.10", - "@inquirer/type": "^3.0.6" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/password": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.12.tgz", - "integrity": "sha512-FlOB0zvuELPEbnBYiPaOdJIaDzb2PmJ7ghi/SVwIHDDSQ2K4opGBkF+5kXOg6ucrtSUQdLhVVY5tycH0j0l+0g==", - "dev": true, - "dependencies": { - "@inquirer/core": "^10.1.10", - "@inquirer/type": "^3.0.6", - "ansi-escapes": "^4.3.2" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/prompts": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.4.1.tgz", - "integrity": "sha512-UlmM5FVOZF0gpoe1PT/jN4vk8JmpIWBlMvTL8M+hlvPmzN89K6z03+IFmyeu/oFCenwdwHDr2gky7nIGSEVvlA==", - "dev": true, - "dependencies": { - "@inquirer/checkbox": "^4.1.5", - "@inquirer/confirm": "^5.1.9", - "@inquirer/editor": "^4.2.10", - "@inquirer/expand": "^4.0.12", - "@inquirer/input": "^4.1.9", - "@inquirer/number": "^3.0.12", - "@inquirer/password": "^4.0.12", - "@inquirer/rawlist": "^4.0.12", - "@inquirer/search": "^3.0.12", - "@inquirer/select": "^4.1.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/rawlist": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.0.12.tgz", - "integrity": "sha512-wNPJZy8Oc7RyGISPxp9/MpTOqX8lr0r+lCCWm7hQra+MDtYRgINv1hxw7R+vKP71Bu/3LszabxOodfV/uTfsaA==", - "dev": true, - "dependencies": { - "@inquirer/core": "^10.1.10", - "@inquirer/type": "^3.0.6", - "yoctocolors-cjs": "^2.1.2" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/search": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.0.12.tgz", - "integrity": "sha512-H/kDJA3kNlnNIjB8YsaXoQI0Qccgf0Na14K1h8ExWhNmUg2E941dyFPrZeugihEa9AZNW5NdsD/NcvUME83OPQ==", - "dev": true, - "dependencies": { - "@inquirer/core": "^10.1.10", - "@inquirer/figures": "^1.0.11", - "@inquirer/type": "^3.0.6", - "yoctocolors-cjs": "^2.1.2" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/select": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.1.1.tgz", - "integrity": "sha512-IUXzzTKVdiVNMA+2yUvPxWsSgOG4kfX93jOM4Zb5FgujeInotv5SPIJVeXQ+fO4xu7tW8VowFhdG5JRmmCyQ1Q==", - "dev": true, - "dependencies": { - "@inquirer/core": "^10.1.10", - "@inquirer/figures": "^1.0.11", - "@inquirer/type": "^3.0.6", - "ansi-escapes": "^4.3.2", - "yoctocolors-cjs": "^2.1.2" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/type": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.6.tgz", - "integrity": "sha512-/mKVCtVpyBu3IDarv0G+59KC4stsD5mDsGpYh+GKs1NZT88Jh52+cuoA1AtLk2Q0r/quNl+1cSUyLRHBFeD0XA==", - "dev": true, - "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, - "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.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "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, - "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, - "dependencies": { - "debug": "^4.1.1" - } - }, - "node_modules/@kwsites/file-exists/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, - "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 - }, - "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 - }, - "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, - "optional": true, - "engines": { - "node": ">=14" - } - }, - "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, - "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, - "dependencies": { - "defer-to-connect": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "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, - "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.0.4", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", - "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", - "dev": true - }, - "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, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/node": { - "version": "22.14.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.1.tgz", - "integrity": "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==", - "dev": true, - "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, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@wordpress/env": { - "version": "10.22.0", - "resolved": "https://registry.npmjs.org/@wordpress/env/-/env-10.22.0.tgz", - "integrity": "sha512-w/OGGVI5PCWawAwUD6wFWWdb6etHJ8MHmf7DfW0xX/i7bXNqE8qvW/HimQx/ssILvHWC3CsB8x+CsNoG7ZTEIA==", - "dev": true, - "dependencies": { - "@inquirer/prompts": "^7.2.0", - "chalk": "^4.0.0", - "copy-dir": "^1.3.0", - "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/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, - "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, - "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, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "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, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "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, - "engines": { - "node": "*" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/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, - "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, - "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/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "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": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true - }, - "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, - "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, - "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, - "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, - "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 - }, - "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, - "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, - "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, - "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, - "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, - "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 - }, - "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" - ], - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "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 - }, - "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 - }, - "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, - "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, - "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, - "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, - "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, - "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, - "engines": { - "node": ">=10" - } - }, - "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, - "dependencies": { - "yaml": "^2.2.2" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "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 - }, - "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 - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "dependencies": { - "once": "^1.4.0" - } - }, - "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, - "engines": { - "node": ">=6" - } - }, - "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, - "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, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, - "dependencies": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - }, - "engines": { - "node": ">=4" - } - }, - "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, - "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/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, - "dependencies": { - "pend": "~1.2.0" - } - }, - "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, - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "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, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "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, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "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/got": { - "version": "11.8.6", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", - "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", - "dev": true, - "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/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, - "engines": { - "node": ">=8" - } - }, - "node_modules/http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", - "dev": true - }, - "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, - "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" - }, - "engines": { - "node": ">=10.19.0" - } - }, - "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, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "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 - }, - "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, - "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, - "engines": { - "node": ">=8" - } - }, - "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 - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "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, - "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.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "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 - }, - "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, - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "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, - "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, - "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, - "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, - "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 - }, - "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, - "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, - "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, - "engines": { - "node": ">=8" - } - }, - "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 - }, - "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, - "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, - "engines": { - "node": ">=4" - } - }, - "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "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, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "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, - "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 - }, - "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, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "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, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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, - "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, - "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, - "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, - "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 - }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "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, - "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 - }, - "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, - "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, - "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/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 - }, - "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 - }, - "node_modules/pump": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", - "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "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, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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, - "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/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, - "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 - }, - "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, - "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, - "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 - }, - "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, - "dependencies": { - "glob": "^10.3.7" - }, - "bin": { - "rimraf": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "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 - }, - "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 - }, - "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, - "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, - "engines": { - "node": ">=8" - } - }, - "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, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/simple-git": { - "version": "3.27.0", - "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.27.0.tgz", - "integrity": "sha512-ivHoFS9Yi9GY49ogc6/YAi3Fl9ROnF4VyubNylgCkA+RVqLaKWnDSzXOVzya8csELIaWaYNutsEuAhZrtOjozA==", - "dev": true, - "dependencies": { - "@kwsites/file-exists": "^1.1.1", - "@kwsites/promise-deferred": "^1.1.1", - "debug": "^4.3.5" - }, - "funding": { - "type": "github", - "url": "https://github.com/steveukx/git-js?sponsor=1" - } - }, - "node_modules/simple-git/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, - "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 - }, - "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 - }, - "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, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "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, - "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, - "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 - }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "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, - "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, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "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, - "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, - "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, - "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.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "dependencies": { - "os-tmpdir": "~1.0.2" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "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, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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 - }, - "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 - }, - "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 - }, - "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, - "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, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "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, - "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, - "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 - }, - "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, - "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 - }, - "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, - "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 - }, - "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, - "engines": { - "node": ">=10" - } - }, - "node_modules/yaml": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz", - "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==", - "dev": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14" - } - }, - "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, - "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, - "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 - }, - "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, - "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, - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, - "node_modules/yoctocolors-cjs": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", - "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } + "name": "fair-plugin", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "@wordpress/env": "^10.22.0" + } + }, + "node_modules/@inquirer/checkbox": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.1.5.tgz", + "integrity": "sha512-swPczVU+at65xa5uPfNP9u3qx/alNwiaykiI/ExpsmMSQW55trmZcwhYWzw/7fj+n6Q8z1eENvR7vFfq9oPSAQ==", + "dev": true, + "dependencies": { + "@inquirer/core": "^10.1.10", + "@inquirer/figures": "^1.0.11", + "@inquirer/type": "^3.0.6", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/confirm": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.9.tgz", + "integrity": "sha512-NgQCnHqFTjF7Ys2fsqK2WtnA8X1kHyInyG+nMIuHowVTIgIuS10T4AznI/PvbqSpJqjCUqNBlKGh1v3bwLFL4w==", + "dev": true, + "dependencies": { + "@inquirer/core": "^10.1.10", + "@inquirer/type": "^3.0.6" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "10.1.10", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.10.tgz", + "integrity": "sha512-roDaKeY1PYY0aCqhRmXihrHjoSW2A00pV3Ke5fTpMCkzcGF64R8e0lw3dK+eLEHwS4vB5RnW1wuQmvzoRul8Mw==", + "dev": true, + "dependencies": { + "@inquirer/figures": "^1.0.11", + "@inquirer/type": "^3.0.6", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/editor": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.10.tgz", + "integrity": "sha512-5GVWJ+qeI6BzR6TIInLP9SXhWCEcvgFQYmcRG6d6RIlhFjM5TyG18paTGBgRYyEouvCmzeco47x9zX9tQEofkw==", + "dev": true, + "dependencies": { + "@inquirer/core": "^10.1.10", + "@inquirer/type": "^3.0.6", + "external-editor": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/expand": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.12.tgz", + "integrity": "sha512-jV8QoZE1fC0vPe6TnsOfig+qwu7Iza1pkXoUJ3SroRagrt2hxiL+RbM432YAihNR7m7XnU0HWl/WQ35RIGmXHw==", + "dev": true, + "dependencies": { + "@inquirer/core": "^10.1.10", + "@inquirer/type": "^3.0.6", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.11.tgz", + "integrity": "sha512-eOg92lvrn/aRUqbxRyvpEWnrvRuTYRifixHkYVpJiygTgVSBIHDqLh0SrMQXkafvULg3ck11V7xvR+zcgvpHFw==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/input": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.1.9.tgz", + "integrity": "sha512-mshNG24Ij5KqsQtOZMgj5TwEjIf+F2HOESk6bjMwGWgcH5UBe8UoljwzNFHqdMbGYbgAf6v2wU/X9CAdKJzgOA==", + "dev": true, + "dependencies": { + "@inquirer/core": "^10.1.10", + "@inquirer/type": "^3.0.6" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/number": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.12.tgz", + "integrity": "sha512-7HRFHxbPCA4e4jMxTQglHJwP+v/kpFsCf2szzfBHy98Wlc3L08HL76UDiA87TOdX5fwj2HMOLWqRWv9Pnn+Z5Q==", + "dev": true, + "dependencies": { + "@inquirer/core": "^10.1.10", + "@inquirer/type": "^3.0.6" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/password": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.12.tgz", + "integrity": "sha512-FlOB0zvuELPEbnBYiPaOdJIaDzb2PmJ7ghi/SVwIHDDSQ2K4opGBkF+5kXOg6ucrtSUQdLhVVY5tycH0j0l+0g==", + "dev": true, + "dependencies": { + "@inquirer/core": "^10.1.10", + "@inquirer/type": "^3.0.6", + "ansi-escapes": "^4.3.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/prompts": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.4.1.tgz", + "integrity": "sha512-UlmM5FVOZF0gpoe1PT/jN4vk8JmpIWBlMvTL8M+hlvPmzN89K6z03+IFmyeu/oFCenwdwHDr2gky7nIGSEVvlA==", + "dev": true, + "dependencies": { + "@inquirer/checkbox": "^4.1.5", + "@inquirer/confirm": "^5.1.9", + "@inquirer/editor": "^4.2.10", + "@inquirer/expand": "^4.0.12", + "@inquirer/input": "^4.1.9", + "@inquirer/number": "^3.0.12", + "@inquirer/password": "^4.0.12", + "@inquirer/rawlist": "^4.0.12", + "@inquirer/search": "^3.0.12", + "@inquirer/select": "^4.1.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/rawlist": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.0.12.tgz", + "integrity": "sha512-wNPJZy8Oc7RyGISPxp9/MpTOqX8lr0r+lCCWm7hQra+MDtYRgINv1hxw7R+vKP71Bu/3LszabxOodfV/uTfsaA==", + "dev": true, + "dependencies": { + "@inquirer/core": "^10.1.10", + "@inquirer/type": "^3.0.6", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/search": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.0.12.tgz", + "integrity": "sha512-H/kDJA3kNlnNIjB8YsaXoQI0Qccgf0Na14K1h8ExWhNmUg2E941dyFPrZeugihEa9AZNW5NdsD/NcvUME83OPQ==", + "dev": true, + "dependencies": { + "@inquirer/core": "^10.1.10", + "@inquirer/figures": "^1.0.11", + "@inquirer/type": "^3.0.6", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/select": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.1.1.tgz", + "integrity": "sha512-IUXzzTKVdiVNMA+2yUvPxWsSgOG4kfX93jOM4Zb5FgujeInotv5SPIJVeXQ+fO4xu7tW8VowFhdG5JRmmCyQ1Q==", + "dev": true, + "dependencies": { + "@inquirer/core": "^10.1.10", + "@inquirer/figures": "^1.0.11", + "@inquirer/type": "^3.0.6", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.6.tgz", + "integrity": "sha512-/mKVCtVpyBu3IDarv0G+59KC4stsD5mDsGpYh+GKs1NZT88Jh52+cuoA1AtLk2Q0r/quNl+1cSUyLRHBFeD0XA==", + "dev": true, + "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, + "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.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "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, + "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, + "dependencies": { + "debug": "^4.1.1" + } + }, + "node_modules/@kwsites/file-exists/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "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 + }, + "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 + }, + "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, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "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, + "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, + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "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, + "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.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", + "dev": true + }, + "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, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "22.14.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.1.tgz", + "integrity": "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==", + "dev": true, + "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, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@wordpress/env": { + "version": "10.22.0", + "resolved": "https://registry.npmjs.org/@wordpress/env/-/env-10.22.0.tgz", + "integrity": "sha512-w/OGGVI5PCWawAwUD6wFWWdb6etHJ8MHmf7DfW0xX/i7bXNqE8qvW/HimQx/ssILvHWC3CsB8x+CsNoG7ZTEIA==", + "dev": true, + "dependencies": { + "@inquirer/prompts": "^7.2.0", + "chalk": "^4.0.0", + "copy-dir": "^1.3.0", + "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/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, + "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, + "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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "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, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "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, + "engines": { + "node": "*" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/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, + "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, + "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/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "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": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "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, + "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, + "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, + "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, + "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 + }, + "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, + "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, + "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, + "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, + "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, + "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 + }, + "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" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "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 + }, + "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 + }, + "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, + "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, + "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, + "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, + "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, + "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, + "engines": { + "node": ">=10" + } + }, + "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, + "dependencies": { + "yaml": "^2.2.2" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "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 + }, + "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 + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "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, + "engines": { + "node": ">=6" + } + }, + "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, + "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, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "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, + "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/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, + "dependencies": { + "pend": "~1.2.0" + } + }, + "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, + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "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, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "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/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dev": true, + "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/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "dev": true + }, + "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, + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "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, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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 + }, + "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, + "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, + "engines": { + "node": ">=8" + } + }, + "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 + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "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, + "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.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "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 + }, + "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, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "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, + "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, + "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, + "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, + "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 + }, + "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, + "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, + "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, + "engines": { + "node": ">=8" + } + }, + "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 + }, + "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, + "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, + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "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, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "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, + "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 + }, + "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, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "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, + "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, + "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, + "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 + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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, + "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 + }, + "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, + "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, + "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/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 + }, + "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 + }, + "node_modules/pump": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "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/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, + "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 + }, + "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, + "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, + "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 + }, + "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, + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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 + }, + "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 + }, + "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, + "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, + "engines": { + "node": ">=8" + } + }, + "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, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/simple-git": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.27.0.tgz", + "integrity": "sha512-ivHoFS9Yi9GY49ogc6/YAi3Fl9ROnF4VyubNylgCkA+RVqLaKWnDSzXOVzya8csELIaWaYNutsEuAhZrtOjozA==", + "dev": true, + "dependencies": { + "@kwsites/file-exists": "^1.1.1", + "@kwsites/promise-deferred": "^1.1.1", + "debug": "^4.3.5" + }, + "funding": { + "type": "github", + "url": "https://github.com/steveukx/git-js?sponsor=1" + } + }, + "node_modules/simple-git/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "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 + }, + "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 + }, + "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, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "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, + "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, + "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 + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "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, + "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, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "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, + "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, + "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.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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 + }, + "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 + }, + "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 + }, + "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, + "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, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "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, + "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, + "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 + }, + "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, + "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 + }, + "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, + "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 + }, + "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, + "engines": { + "node": ">=10" + } + }, + "node_modules/yaml": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz", + "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==", + "dev": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "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, + "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, + "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 + }, + "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, + "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, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", + "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } } diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 51843f88..4cce170d 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -34,7 +34,6 @@ inc/updater/class-lite\.php - inc/updater/class-base58btc\.php languages/* diff --git a/plugin.php b/plugin.php index b7b1d21d..83692723 100644 --- a/plugin.php +++ b/plugin.php @@ -8,7 +8,7 @@ * Security: security@fair.pm * License: GPLv2 * Requires at least: 5.4 - * Requires PHP: 7.4 + * Requires PHP: 8.0 * Text Domain: fair * Domain Path: /languages * Update URI: https://api.fair.pm @@ -24,6 +24,7 @@ const PLUGIN_DIR = __DIR__; const PLUGIN_FILE = __FILE__; +require_once __DIR__ . '/vendor/autoload.php'; require_once __DIR__ . '/inc/namespace.php'; require_once __DIR__ . '/inc/avatars/namespace.php'; require_once __DIR__ . '/inc/credits/namespace.php'; diff --git a/readme.txt b/readme.txt index bcc7da63..e7d5bb71 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Contributors: FAIR Contributors License: GPLv2 or later Tags: packages, updater, installer, technical independence Requires at least: 5.4 -Requires PHP: 7.4 +Requires PHP: 8.0 Tested up to: 6.9 Stable tag: 1.4.0 diff --git a/tests/phpunit/tests/Packages/GetDidServiceTest.php b/tests/phpunit/tests/Packages/GetDidServiceTest.php new file mode 100644 index 00000000..9fa27995 --- /dev/null +++ b/tests/phpunit/tests/Packages/GetDidServiceTest.php @@ -0,0 +1,82 @@ + [ + [ + 'id' => '#fair-repo', + 'type' => 'FairPackageManagementRepo', + 'serviceEndpoint' => 'https://example.com/fair', + ], + [ + 'id' => '#atproto-pds', + 'type' => 'AtprotoPersonalDataServer', + 'serviceEndpoint' => 'https://pds.example.com', + ], + ], + ]; + + $actual = get_did_service( $did_doc, 'FairPackageManagementRepo' ); + + $this->assertIsArray( $actual, 'Expected an array for a matching service.' ); + $this->assertSame( 'https://example.com/fair', $actual['serviceEndpoint'], 'Service endpoint should match.' ); + } + + /** + * Test should return null when no service matches. + */ + public function test_should_return_null_when_no_match() { + $did_doc = [ + 'service' => [ + [ + 'id' => '#atproto-pds', + 'type' => 'AtprotoPersonalDataServer', + 'serviceEndpoint' => 'https://pds.example.com', + ], + ], + ]; + + $actual = get_did_service( $did_doc, 'FairPackageManagementRepo' ); + + $this->assertNull( $actual, 'Expected null when no service matches.' ); + } + + /** + * Test should return null when service array is empty. + */ + public function test_should_return_null_for_empty_services() { + $did_doc = [ 'service' => [] ]; + + $actual = get_did_service( $did_doc, 'FairPackageManagementRepo' ); + + $this->assertNull( $actual, 'Expected null for empty services.' ); + } + + /** + * Test should return null when service key is missing. + */ + public function test_should_return_null_when_service_key_missing() { + $did_doc = [ 'id' => 'did:plc:test' ]; + + $actual = get_did_service( $did_doc, 'FairPackageManagementRepo' ); + + $this->assertNull( $actual, 'Expected null when service key is missing.' ); + } +} diff --git a/tests/phpunit/tests/Packages/GetFairSigningKeysTest.php b/tests/phpunit/tests/Packages/GetFairSigningKeysTest.php new file mode 100644 index 00000000..5b19aa6e --- /dev/null +++ b/tests/phpunit/tests/Packages/GetFairSigningKeysTest.php @@ -0,0 +1,96 @@ + [ + [ + 'id' => 'did:plc:test#fair-signing', + 'type' => 'Multikey', + 'publicKeyMultibase' => 'zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169', + ], + [ + 'id' => 'did:plc:test#atproto', + 'type' => 'Multikey', + 'publicKeyMultibase' => 'zQ3shXjHeiBuRCKmM36cuYnm7YEMzhGnCmCyW92sRJ9pribSF', + ], + ], + ]; + + $actual = get_fair_signing_keys( $did_doc ); + + $this->assertCount( 1, $actual, 'Only fair-prefixed keys should be returned.' ); + $key = reset( $actual ); + $this->assertSame( 'did:plc:test#fair-signing', $key['id'], 'The returned key should have the fair-prefixed ID.' ); + } + + /** + * Test should exclude non-Multikey types. + */ + public function test_should_exclude_non_multikey_types() { + $did_doc = [ + 'verificationMethod' => [ + [ + 'id' => 'did:plc:test#fair-signing', + 'type' => 'Ed25519VerificationKey2020', + 'publicKeyMultibase' => 'zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169', + ], + ], + ]; + + $actual = get_fair_signing_keys( $did_doc ); + + $this->assertEmpty( $actual, 'Non-Multikey types should be excluded.' ); + } + + /** + * Test should return empty when no verificationMethod key exists. + */ + public function test_should_return_empty_when_verification_method_missing() { + $did_doc = [ 'id' => 'did:plc:test' ]; + + $actual = get_fair_signing_keys( $did_doc ); + + $this->assertEmpty( $actual, 'Expected empty when verificationMethod is missing.' ); + } + + /** + * Test should return multiple fair keys. + */ + public function test_should_return_multiple_fair_keys() { + $did_doc = [ + 'verificationMethod' => [ + [ + 'id' => 'did:plc:test#fair-signing', + 'type' => 'Multikey', + 'publicKeyMultibase' => 'zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169', + ], + [ + 'id' => 'did:plc:test#fair-backup', + 'type' => 'Multikey', + 'publicKeyMultibase' => 'zQ3shXjHeiBuRCKmM36cuYnm7YEMzhGnCmCyW92sRJ9pribSF', + ], + ], + ]; + + $actual = get_fair_signing_keys( $did_doc ); + + $this->assertCount( 2, $actual, 'Both fair-prefixed keys should be returned.' ); + } +} diff --git a/tests/phpunit/tests/Packages/ParseDidTest.php b/tests/phpunit/tests/Packages/ParseDidTest.php new file mode 100644 index 00000000..a44f7a88 --- /dev/null +++ b/tests/phpunit/tests/Packages/ParseDidTest.php @@ -0,0 +1,78 @@ +assertIsString( $actual, 'Expected a string for a valid DID.' ); + $this->assertSame( $did, $actual, 'The returned DID should match the input.' ); + } + + /** + * Test should return WP_Error for non-DID string. + */ + public function test_should_return_error_for_non_did_string() { + $actual = parse_did( 'not-a-did' ); + + $this->assertWPError( $actual, 'Expected WP_Error for a non-DID string.' ); + $this->assertSame( 'fair.packages.validate_did.not_did', $actual->get_error_code(), 'Error code should indicate not a DID.' ); + } + + /** + * Test should return the DID string even with minimal method-specific-id. + * + * Parse_did() only validates format, not content. + */ + public function test_should_return_string_for_minimal_plc_did() { + $did = 'did:plc:x'; + $actual = parse_did( $did ); + + $this->assertIsString( $actual, 'Expected a string for a minimal DID.' ); + $this->assertSame( $did, $actual, 'The returned DID should match the input.' ); + } + + /** + * Test should return WP_Error for non-plc DID method. + */ + public function test_should_return_error_for_non_plc_method() { + $actual = parse_did( 'did:web:example.com' ); + + $this->assertWPError( $actual, 'Expected WP_Error for non-plc DID method.' ); + $this->assertSame( 'fair.packages.validate_did.not_did', $actual->get_error_code(), 'Non-plc methods should be rejected.' ); + } + + /** + * Test should return WP_Error for empty string. + */ + public function test_should_return_error_for_empty_string() { + $actual = parse_did( '' ); + + $this->assertWPError( $actual, 'Expected WP_Error for empty string.' ); + } + + /** + * Test should return WP_Error for partial DID. + */ + public function test_should_return_error_for_partial_did() { + $actual = parse_did( 'did:plc' ); + + $this->assertWPError( $actual, 'Expected WP_Error for partial DID.' ); + } +} From 0966d2a75af60a5ca272195a3eb1c6a212b44ef6 Mon Sep 17 00:00:00 2001 From: Kaspars Dambis Date: Mon, 6 Apr 2026 22:18:41 +0300 Subject: [PATCH 07/17] Add PHPStan checks (#382) Co-authored-by: Chuck Adams --- .distignore | 1 + .github/workflows/coding-standards.yml | 42 +++------- .gitignore | 2 + CONTRIBUTING.md | 33 +++++--- composer.json | 18 +++- package.json | 5 ++ phpcs.xml.dist | 5 ++ phpstan.dist.neon | 10 +++ tests/phpstan-baseline.neon | 109 +++++++++++++++++++++++++ 9 files changed, 181 insertions(+), 44 deletions(-) create mode 100644 phpstan.dist.neon create mode 100644 tests/phpstan-baseline.neon diff --git a/.distignore b/.distignore index 2936d46b..388cde75 100644 --- a/.distignore +++ b/.distignore @@ -8,6 +8,7 @@ tests/ phpunit.xml.dist wp-tests-config-sample.php phpcs.xml.dist +phpstan.dist.neon composer.json composer.lock package.json diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml index 8af327e3..db1a255a 100644 --- a/.github/workflows/coding-standards.yml +++ b/.github/workflows/coding-standards.yml @@ -5,7 +5,6 @@ on: branches: - main pull_request: - workflow_dispatch: # Cancels all previous workflow runs for pull requests that have not completed. @@ -20,44 +19,27 @@ concurrency: permissions: {} jobs: - # Runs the PHP coding standards checks. - # - # Violations are reported inline with annotations. - # - # Performs the following steps: - # - Checks out the repository. - # - Sets up PHP. - # - Installs Composer dependencies. - # - Make Composer packages available globally. - # - Runs PHPCS on the full codebase (warnings excluded). - # - Ensures version-controlled files are not modified or deleted. - phpcs: + lint: name: Run coding standards checks runs-on: ubuntu-latest permissions: contents: read - timeout-minutes: 20 steps: - - name: Checkout repository - uses: actions/checkout@v4 + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup cache + uses: actions/cache@v5 with: - show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} + key: ${{ runner.os }}-${{ hashFiles('composer.json') }} # Note that lock file will change between runs. + path: .cache - name: Set up PHP uses: shivammathur/setup-php@cf4cade2721270509d5b1c766ab3549210a39a2a # v2.33.0 - with: - coverage: none - - - name: Install Composer dependencies - uses: ramsey/composer-install@3cf229dc2919194e9e36783941438d17239e8520 # v3.1.1 - - - name: Make Composer packages available globally - run: echo "${PWD}/vendor/bin" >> $GITHUB_PATH - - name: Run PHPCS on all files - id: phpcs-files - run: phpcs . -n --report-full + - name: Install dependencies + run: composer install - - name: Ensure version-controlled files are not modified during the checks - run: git diff --exit-code + - name: Lint PHP + run: composer run lint diff --git a/.gitignore b/.gitignore index ba3e0534..ce9b95b5 100644 --- a/.gitignore +++ b/.gitignore @@ -3,9 +3,11 @@ *.bak /.vscode /vendor/ +.wp-env.override.json .phpunit.result.cache phpunit.xml phpcs.xml +phpstan.neon /tests/phpunit/cache /tests/phpunit/coverage wp-tests-config.php diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8e8ea84f..c4552e0a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,18 +18,29 @@ $ git commit -s -m 'My commit message.' This plugin is ready to use with wp-env for local development, with a default configuration included in the repository. `npm run env` is an alias for `wp-env`: -```sh -# Install wp-env and other dependencies. -$ npm install +- `npm install` to install wp-env and other dependencies. +- `npm run env start` to start the development server. Run `npm run env start -- --xdebug=coverage` to enable Xdebug with test coverage reporting. +- `npm run env logs` to get the logs. +- `npm run env stop` to stop the development server. +- `npm run cli` to run any CLI commands inside the environment, such as `npm run cli -- wp plugin list`. -# Start the development server -$ npm run env start +By default wp-env is configured with PHP 7.4 (our minimum supported version), as well as Airplane Mode to avoid inadvertent requests. -# Get the logs -$ npm run env logs +For linting and static analysis: -# Stop the development server -$ npm run env stop -``` +- `npm run lint:php:phpcs` to run PHPCS (configured in [`phpcs.xml.dist`](phpcs.xml.dist)). +- `npm run lint:php:phpstan` to run PHPStan (configured in [`phpstan.dist.neon`](phpstan.dist.neon)). +- `npm run format:php:phpcs` to automatically fix PHPCS issues. +- `npm run format:php:phpstan` to automatically fix PHPStan issues. +- `npm run cli -- composer phpstan-baseline` to update the PHPStan baseline [`tests/phpstan-baseline.neon`](tests/phpstan-baseline.neon) as you fix the reported issues. + +### Configuring PHP and WP Versions -By default, wp-env is configured with PHP 7.4 (our minimum supported version), as well as Airplane Mode to avoid inadvertent requests. +To run a specific version of PHP or WP with your local development environment, create a `.wp-env.override.json` file in the root of the repository with the following contents: + +```json +{ + "phpVersion": "8.5", + "core": "https://wordpress.org/wordpress-6.9.zip" +} +``` diff --git a/composer.json b/composer.json index 76ca67a2..d1778446 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,8 @@ "require-dev": { "yoast/phpunit-polyfills": "*", "nimut/phpunit-merger": "*", - "humanmade/coding-standards": "^1.2" + "humanmade/coding-standards": "^1.2", + "szepeviktor/phpstan-wordpress": "^2.0" }, "config": { "lock": false, @@ -27,8 +28,19 @@ } }, "scripts": { - "lint": "@php ./vendor/bin/phpcs .", - "format": "@php ./vendor/bin/phpcbf .", + "lint:phpcs": "phpcs . --warning-severity=0", + "lint:phpstan": "phpstan analyse --verbose --memory-limit=2G", + "lint": [ + "@lint:phpcs", + "@lint:phpstan" + ], + "format:phpcs": "phpcbf .", + "format:phpstan": "phpstan analyse --fix --memory-limit=2G", + "format": [ + "@format:phpcs", + "@format:phpstan" + ], + "phpstan-baseline": "phpstan analyse --generate-baseline=tests/phpstan-baseline.neon --memory-limit=2G", "test": [ "Composer\\Config::disableProcessTimeout", "@php ./vendor/phpunit/phpunit/phpunit" diff --git a/package.json b/package.json index b857963a..eade125a 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,13 @@ }, "scripts": { "env": "wp-env", + "cli": "wp-env run cli --env-cwd=wp-content/plugins/plugin", "lint:php": "wp-env run cli --env-cwd=wp-content/plugins/plugin composer run lint", + "lint:php:phpcs": "wp-env run cli --env-cwd=wp-content/plugins/plugin composer run lint:phpcs", + "lint:php:phpstan": "wp-env run cli --env-cwd=wp-content/plugins/plugin composer run lint:phpstan", "format:php": "wp-env run cli --env-cwd=wp-content/plugins/plugin composer run format", + "format:php:phpcs": "wp-env run cli --env-cwd=wp-content/plugins/plugin composer run format:phpcs", + "format:php:phpstan": "wp-env run cli --env-cwd=wp-content/plugins/plugin composer run format:phpstan", "test:php:install-deps": "wp-env run tests-cli --env-cwd=wp-content/plugins/plugin composer install", "test:php": "wp-env run tests-cli --env-cwd=wp-content/plugins/plugin composer run test", "test:php:multisite": "wp-env run tests-cli --env-cwd=wp-content/plugins/plugin composer run test:multisite", diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 4cce170d..2d298417 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -46,6 +46,11 @@ plugin\.php + + .cache + tests/phpunit/cache/ + tests/phpunit/coverage/ + tests/phpunit/* diff --git a/phpstan.dist.neon b/phpstan.dist.neon new file mode 100644 index 00000000..6aa2d26d --- /dev/null +++ b/phpstan.dist.neon @@ -0,0 +1,10 @@ +includes: + - tests/phpstan-baseline.neon # TODO: Fix all these issues. + - vendor/szepeviktor/phpstan-wordpress/extension.neon +parameters: + tmpDir: .cache/phpstan + level: 0 + paths: + - inc + - plugin.php + - uninstall.php diff --git a/tests/phpstan-baseline.neon b/tests/phpstan-baseline.neon new file mode 100644 index 00000000..7bacc7c8 --- /dev/null +++ b/tests/phpstan-baseline.neon @@ -0,0 +1,109 @@ +parameters: + ignoreErrors: + - + message: '#^Function sanitize_hex_color\(\) should return string but return statement is missing\.$#' + identifier: return.missing + count: 1 + path: ../inc/icons/svg.php + + - + message: '#^Action callback returns bool but should not return anything\.$#' + identifier: return.void + count: 1 + path: ../inc/packages/admin/namespace.php + + - + message: '#^Callback expects 0 parameters, \$accepted_args is set to 3\.$#' + identifier: arguments.count + count: 1 + path: ../inc/packages/admin/namespace.php + + - + message: '#^Class FAIR\\Updater\\Updater does not have a constructor and must be instantiated without any parameters\.$#' + identifier: new.noConstructor + count: 1 + path: ../inc/packages/admin/namespace.php + + - + message: '#^Unsafe usage of new static\(\)\.$#' + identifier: new.static + count: 1 + path: ../inc/packages/class-metadatadocument.php + + - + message: '#^Unsafe usage of new static\(\)\.$#' + identifier: new.static + count: 1 + path: ../inc/packages/class-releasedocument.php + + - + message: '#^Call to static method add_hook\(\) on an unknown class WP_CLI\.$#' + identifier: class.notFound + count: 1 + path: ../inc/packages/wp-cli/compat/namespace.php + + - + message: '#^Call to static method get_runner\(\) on an unknown class WP_CLI\.$#' + identifier: class.notFound + count: 1 + path: ../inc/packages/wp-cli/compat/namespace.php + + - + message: '#^Call to static method halt\(\) on an unknown class WP_CLI\.$#' + identifier: class.notFound + count: 5 + path: ../inc/packages/wp-cli/compat/namespace.php + + - + message: '#^Call to static method log\(\) on an unknown class WP_CLI\.$#' + identifier: class.notFound + count: 1 + path: ../inc/packages/wp-cli/compat/namespace.php + + - + message: '#^Call to static method run_command\(\) on an unknown class WP_CLI\.$#' + identifier: class.notFound + count: 3 + path: ../inc/packages/wp-cli/compat/namespace.php + + - + message: '#^Call to static method warning\(\) on an unknown class WP_CLI\.$#' + identifier: class.notFound + count: 3 + path: ../inc/packages/wp-cli/compat/namespace.php + + - + message: '#^Caught class WP_CLI\\ExitException not found\.$#' + identifier: class.notFound + count: 2 + path: ../inc/packages/wp-cli/compat/namespace.php + + - + message: '#^Function WP_CLI\\Utils\\get_flag_value not found\.$#' + identifier: function.notFound + count: 3 + path: ../inc/packages/wp-cli/compat/namespace.php + + - + message: '#^Used function WP_CLI\\Utils\\get_flag_value not found\.$#' + identifier: function.notFound + count: 1 + path: ../inc/packages/wp-cli/compat/namespace.php + + - + message: '#^Action callback returns string but should not return anything\.$#' + identifier: return.void + count: 1 + path: ../inc/pings/namespace.php + + - + message: '#^Found usage of constant MULTISITE\. Use is_multisite\(\) instead\.$#' + identifier: phpstanWP.wpConstant.fetch + count: 1 + path: ../inc/settings/namespace.php + + - + message: '#^Call to static method success\(\) on an unknown class WP_CLI\.$#' + identifier: class.notFound + count: 1 + path: ../inc/updater/namespace.php From 5480bbc72788c1e93322876812e21f4923008d19 Mon Sep 17 00:00:00 2001 From: Chuck Adams Date: Mon, 6 Apr 2026 13:31:26 -0600 Subject: [PATCH 08/17] don't suppress warnings in phpcs Signed-off-by: Chuck Adams --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index d1778446..39585ea6 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ } }, "scripts": { - "lint:phpcs": "phpcs . --warning-severity=0", + "lint:phpcs": "phpcs .", "lint:phpstan": "phpstan analyse --verbose --memory-limit=2G", "lint": [ "@lint:phpcs", From 43198c10cb854fa7188d635ec371e98951800629 Mon Sep 17 00:00:00 2001 From: Chuck Adams Date: Mon, 6 Apr 2026 13:35:08 -0600 Subject: [PATCH 09/17] fix the more obvious phpcs warnings Signed-off-by: Chuck Adams --- inc/packages/admin/namespace.php | 2 +- inc/packages/namespace.php | 2 +- inc/version-check/namespace.php | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/inc/packages/admin/namespace.php b/inc/packages/admin/namespace.php index 287bbc06..6b63ecd3 100644 --- a/inc/packages/admin/namespace.php +++ b/inc/packages/admin/namespace.php @@ -499,7 +499,7 @@ function maybe_hijack_plugin_install_button( $links, $plugin ) { $requires_wp = isset( $plugin['requires'] ) ? $plugin['requires'] : null; $compatible_php = is_php_version_compatible( $requires_php ); $compatible_wp = is_wp_version_compatible( $requires_wp ); - $name = strip_tags( $plugin['name'] . ' ' . $plugin['version'] ); + $name = wp_strip_all_tags( $plugin['name'] . ' ' . $plugin['version'] ); $link = wp_get_plugin_action_button( $name, $plugin_override, $compatible_php, $compatible_wp ); } return $links; diff --git a/inc/packages/namespace.php b/inc/packages/namespace.php index 0c0dbc62..aeedd66a 100644 --- a/inc/packages/namespace.php +++ b/inc/packages/namespace.php @@ -738,7 +738,7 @@ function get_package_data( $did ) { 'url' => $metadata->url ?? $metadata->slug, 'sections' => $sections, 'description' => $description, - 'short_description' => substr( strip_tags( $description ), 0, 147 ) . '...', + 'short_description' => substr( wp_strip_all_tags( $description ), 0, 147 ) . '...', 'icons' => isset( $release->artifacts->icon ) ? get_icons( $release->artifacts->icon ) : [], 'banners' => isset( $release->artifacts->banner ) ? get_banners( $release->artifacts->banner ) : [], 'update-supported' => true, diff --git a/inc/version-check/namespace.php b/inc/version-check/namespace.php index a988676a..21c1f6b5 100644 --- a/inc/version-check/namespace.php +++ b/inc/version-check/namespace.php @@ -534,9 +534,9 @@ function parse_user_agent( $user_agent ) { } elseif ( 'Windows Phone' === $data['platform'] ) { // Normalize Windows Phone OS name when "OS" is omitted. $data['platform'] = 'Windows Phone OS'; - } elseif ( in_array( $data['platform'], [ 'Symbian', 'SymbOS' ] ) || ! empty( $tokens['SymbianOS'] ) || ! empty( $tokens['Symbian'] ) ) { + } elseif ( in_array( $data['platform'], [ 'Symbian', 'SymbOS' ], true ) || ! empty( $tokens['SymbianOS'] ) || ! empty( $tokens['Symbian'] ) ) { // Standardize Symbian OS name. - if ( ! in_array( $data['platform'], [ 'Symbian', 'SymbOS' ] ) ) { + if ( ! in_array( $data['platform'], [ 'Symbian', 'SymbOS' ], true ) ) { unset( $tokens['SymbianOS'] ); unset( $tokens['Symbian'] ); } @@ -548,7 +548,7 @@ function parse_user_agent( $user_agent ) { } // Flag known mobile platforms as mobile. - if ( in_array( $data['platform'], [ 'Android', 'Fire OS', 'iPad', 'iPhone', 'Mobile', 'PlayBook', 'RIM Tablet OS', 'Symbian', 'Windows Phone OS' ] ) ) { + if ( in_array( $data['platform'], [ 'Android', 'Fire OS', 'iPad', 'iPhone', 'Mobile', 'PlayBook', 'RIM Tablet OS', 'Symbian', 'Windows Phone OS' ], true ) ) { $data['mobile'] = true; } @@ -567,7 +567,7 @@ function parse_user_agent( $user_agent ) { } else { $data['name'] = 'unknown'; } - } elseif ( $found = array_intersect( array_keys( $explicit_tokens ), array_keys( $tokens ) ) ) { // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.FoundInControlStructure + } elseif ( $found = array_intersect( array_keys( $explicit_tokens ), array_keys( $tokens ) ) ) { // phpcs:ignore // Explicitly identified browser (info defined above in $explicit_tokens). $token = reset( $found ); @@ -634,7 +634,7 @@ function parse_user_agent( $user_agent ) { } elseif ( ! empty( $tokens['Chrome'] ) ) { $data['name'] = 'Chrome'; $version = ''; - } elseif ( ! empty( $data['platform'] ) && 'PlayBook' == $data['platform'] ) { + } elseif ( ! empty( $data['platform'] ) && 'PlayBook' === $data['platform'] ) { $data['name'] = 'PlayBook'; } elseif ( ! empty( $tokens['Safari'] ) ) { if ( 'Android' === $data['platform'] ) { @@ -659,7 +659,7 @@ function parse_user_agent( $user_agent ) { } // Set the platform for Amazon-related browsers. - if ( in_array( $data['name'], [ 'Amazon Silk', 'Kindle Browser' ] ) ) { + if ( in_array( $data['name'], [ 'Amazon Silk', 'Kindle Browser' ], true ) ) { $data['platform'] = 'Fire OS'; $data['mobile'] = true; } From 7f160390a08c8ed115c7f903cc211e1edf36a755 Mon Sep 17 00:00:00 2001 From: Chuck Adams Date: Mon, 6 Apr 2026 13:36:06 -0600 Subject: [PATCH 10/17] comment out phpstan baseline Signed-off-by: Chuck Adams --- phpstan.dist.neon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpstan.dist.neon b/phpstan.dist.neon index 6aa2d26d..24a6d1d4 100644 --- a/phpstan.dist.neon +++ b/phpstan.dist.neon @@ -1,5 +1,5 @@ includes: - - tests/phpstan-baseline.neon # TODO: Fix all these issues. +# - tests/phpstan-baseline.neon - vendor/szepeviktor/phpstan-wordpress/extension.neon parameters: tmpDir: .cache/phpstan From 2ea9fdae9a80f921b3dc7314488393e50d0b9edc Mon Sep 17 00:00:00 2001 From: Chuck Adams Date: Mon, 6 Apr 2026 14:02:58 -0600 Subject: [PATCH 11/17] fix or suppress most phpstan errors Signed-off-by: Chuck Adams --- inc/icons/svg.php | 6 ++++-- inc/packages/admin/namespace.php | 5 +++-- inc/packages/class-metadatadocument.php | 4 ++-- inc/packages/class-releasedocument.php | 4 ++-- inc/pings/namespace.php | 2 +- inc/settings/namespace.php | 2 +- 6 files changed, 13 insertions(+), 10 deletions(-) diff --git a/inc/icons/svg.php b/inc/icons/svg.php index bf3be464..fa8a5f3d 100644 --- a/inc/icons/svg.php +++ b/inc/icons/svg.php @@ -13,9 +13,9 @@ * * @param string $color Hex color. * - * @return string + * @return string|null */ -function sanitize_hex_color( $color ) { +function sanitize_hex_color( $color ): ?string { if ( '' === $color ) { return ''; } @@ -24,6 +24,8 @@ function sanitize_hex_color( $color ) { if ( preg_match( '|^#([A-Fa-f0-9]{3}){1,2}$|', $color ) ) { return $color; } + + return null; } // Add the proper header. diff --git a/inc/packages/admin/namespace.php b/inc/packages/admin/namespace.php index 6b63ecd3..7a5e334f 100644 --- a/inc/packages/admin/namespace.php +++ b/inc/packages/admin/namespace.php @@ -29,7 +29,7 @@ function bootstrap() { add_filter( 'plugins_api', __NAMESPACE__ . '\\handle_did_during_ajax', 10, 3 ); add_filter( 'plugins_api', 'FAIR\\Packages\\search_by_did', 10, 3 ); add_filter( 'upgrader_package_options', 'FAIR\\Packages\\cache_did_for_install', 10, 1 ); - add_action( 'upgrader_post_install', 'FAIR\\Packages\\delete_cached_did_for_install', 10, 3 ); + add_action( 'upgrader_post_install', 'FAIR\\Packages\\delete_cached_did_for_install', 10, 0 ); add_filter( 'upgrader_pre_download', 'FAIR\\Packages\\upgrader_pre_download', 10, 1 ); add_action( 'install_plugins_' . TAB_DIRECT, __NAMESPACE__ . '\\render_tab_direct' ); add_action( 'load-plugin-install.php', __NAMESPACE__ . '\\load_plugin_install' ); @@ -44,7 +44,7 @@ function bootstrap() { // Needed for pre WordPress 6.9 compatibility. if ( ! is_wp_version_compatible( '6.9' ) ) { add_action( 'install_plugins_featured', __NAMESPACE__ . '\\replace_featured_message' ); - add_action( 'admin_init', fn() => remove_action( 'install_plugins_featured', 'install_dashboard' ) ); + add_action( 'admin_init', static function() { remove_action( 'install_plugins_featured', 'install_dashboard' ); } ); } } @@ -124,6 +124,7 @@ function handle_did_during_ajax( $result, $action, $args ) { return $result; } + // FIXME: Updater\Updater has neither a constructor nor a run() method ( new Updater\Updater( $did ) )->run(); Packages\add_package_to_release_cache( $did ); diff --git a/inc/packages/class-metadatadocument.php b/inc/packages/class-metadatadocument.php index 0938c67b..ce10e6f7 100644 --- a/inc/packages/class-metadatadocument.php +++ b/inc/packages/class-metadatadocument.php @@ -116,10 +116,10 @@ class MetadataDocument { * Collate data. * * @param stdClass $data Data to parse. - * @return static|WP_Error Instance if valid, WP_Error otherwise. + * @return self|WP_Error Instance if valid, WP_Error otherwise. */ public static function from_data( stdClass $data ) { - $doc = new static(); + $doc = new self(); $mandatory = [ 'id', 'type', diff --git a/inc/packages/class-releasedocument.php b/inc/packages/class-releasedocument.php index f4fd1058..388ba1e2 100644 --- a/inc/packages/class-releasedocument.php +++ b/inc/packages/class-releasedocument.php @@ -68,10 +68,10 @@ class ReleaseDocument { * * @param stdClass $data Data to parse. * - * @return ReleaseDocument|WP_Error + * @return self|WP_Error */ public static function from_data( stdClass $data ) { - $doc = new static(); + $doc = new self(); $mandatory = [ 'version', 'artifacts', diff --git a/inc/pings/namespace.php b/inc/pings/namespace.php index f71dff9b..1950ad74 100644 --- a/inc/pings/namespace.php +++ b/inc/pings/namespace.php @@ -13,7 +13,7 @@ function bootstrap() { add_filter( 'pre_option_ping_sites', __NAMESPACE__ . '\\remove_pingomatic_from_ping_sites' ); add_filter( 'query_vars', __NAMESPACE__ . '\\register_query_vars' ); - add_action( 'init', __NAMESPACE__ . '\\get_indexnow_key' ); + add_action( 'init', __NAMESPACE__ . '\\get_indexnow_key' ); //@phpstan-ignore-line return.void add_action( 'init', __NAMESPACE__ . '\\add_key_rewrite_rule' ); add_action( 'parse_request', __NAMESPACE__ . '\\handle_key_file_request' ); add_action( 'transition_post_status', __NAMESPACE__ . '\\ping_indexnow', 10, 3 ); diff --git a/inc/settings/namespace.php b/inc/settings/namespace.php index d7561dbd..3f3cfd5b 100644 --- a/inc/settings/namespace.php +++ b/inc/settings/namespace.php @@ -26,7 +26,7 @@ function bootstrap() { function load_single_site_avatar_settings() { // Don't set this up if we're on a multisite. - if ( defined( 'MULTISITE' ) && false !== MULTISITE ) { + if ( defined( 'MULTISITE' ) && false !== MULTISITE ) { //@phpstan-ignore phpstanWP.wpConstant.fetch return; } From 629a6085b80e4eaf78299981b72d83f625c46e91 Mon Sep 17 00:00:00 2001 From: Chuck Adams Date: Mon, 6 Apr 2026 14:10:37 -0600 Subject: [PATCH 12/17] because comment whitespace is THE MOST IMPORTANT THING EVER Signed-off-by: Chuck Adams --- composer.json | 157 +++++++++++++++++++------------------ inc/pings/namespace.php | 2 +- inc/settings/namespace.php | 2 +- 3 files changed, 81 insertions(+), 80 deletions(-) diff --git a/composer.json b/composer.json index 39585ea6..81e8e6b0 100644 --- a/composer.json +++ b/composer.json @@ -1,84 +1,85 @@ { - "name": "fairpm/fair-plugin", - "description": "Make your site more FAIR.", - "type": "wordpress-plugin", - "license": "gpl-2.0-only", - "authors": [ - { - "name": "FAIR Contributors", + "name": "fairpm/fair-plugin", + "description": "Make your site more FAIR.", + "type": "wordpress-plugin", + "license": "gpl-2.0-only", + "authors": [ + { + "name": "FAIR Contributors", "email": "operations@fair.pm", "homepage": "https://fair.pm" - } - ], - "require": { - "php": ">=8.0", - "fairpm/did-manager": "^0.0.3", - "fairpm/did-manager-wordpress": "^0.0.1" - }, - "require-dev": { - "yoast/phpunit-polyfills": "*", - "nimut/phpunit-merger": "*", - "humanmade/coding-standards": "^1.2", - "szepeviktor/phpstan-wordpress": "^2.0" - }, - "config": { - "lock": false, - "allow-plugins": { - "dealerdirect/phpcodesniffer-composer-installer": true - } - }, - "scripts": { - "lint:phpcs": "phpcs .", - "lint:phpstan": "phpstan analyse --verbose --memory-limit=2G", - "lint": [ - "@lint:phpcs", - "@lint:phpstan" - ], - "format:phpcs": "phpcbf .", - "format:phpstan": "phpstan analyse --fix --memory-limit=2G", - "format": [ - "@format:phpcs", - "@format:phpstan" - ], - "phpstan-baseline": "phpstan analyse --generate-baseline=tests/phpstan-baseline.neon --memory-limit=2G", - "test": [ - "Composer\\Config::disableProcessTimeout", - "@php ./vendor/phpunit/phpunit/phpunit" - ], - "test:multisite": [ - "Composer\\Config::disableProcessTimeout", - "@php ./vendor/phpunit/phpunit/phpunit -c tests/phpunit/multisite.xml" - ], - "coverage:merge": [ - "Composer\\Config::disableProcessTimeout", - "@putenv XDEBUG_MODE=coverage", - "@php ./vendor/bin/phpunit-merger coverage tests/phpunit/coverage/php --html tests/phpunit/coverage/html/full tests/phpunit/cache/full-cache.xml" - ], - "coverage:single": [ - "Composer\\Config::disableProcessTimeout", - "@putenv XDEBUG_MODE=off", - "@putenv WP_TESTS_SKIP_INSTALL=0", - "@test --filter prime_test_suite", - "@putenv XDEBUG_MODE=coverage", - "@putenv WP_TESTS_SKIP_INSTALL=1", - "@test" - ], - "coverage:multisite": [ - "Composer\\Config::disableProcessTimeout", - "@putenv XDEBUG_MODE=off", - "@putenv WP_TESTS_SKIP_INSTALL=0", - "@test:multisite --filter prime_test_suite", - "@putenv XDEBUG_MODE=coverage", - "@putenv WP_TESTS_SKIP_INSTALL=1", - "@test:multisite" - ], - "coverage:full": [ - "Composer\\Config::disableProcessTimeout", - "@coverage:single", - "@coverage:multisite", - "@coverage:merge" - ] - }, + } + ], + "require": { + "php": ">=8.0", + "fairpm/did-manager": "^0.0.3", + "fairpm/did-manager-wordpress": "^0.0.1" + }, + "require-dev": { + "yoast/phpunit-polyfills": "*", + "nimut/phpunit-merger": "*", + "humanmade/coding-standards": "^1.2", + "szepeviktor/phpstan-wordpress": "^2.0", + "wp-cli/wp-cli": "^2.12" + }, + "config": { + "lock": false, + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, + "scripts": { + "lint:phpcs": "phpcs .", + "lint:phpstan": "phpstan analyse --verbose --memory-limit=2G", + "lint": [ + "@lint:phpcs", + "@lint:phpstan" + ], + "format:phpcs": "phpcbf .", + "format:phpstan": "phpstan analyse --fix --memory-limit=2G", + "format": [ + "@format:phpcs", + "@format:phpstan" + ], + "phpstan-baseline": "phpstan analyse --generate-baseline=tests/phpstan-baseline.neon --memory-limit=2G", + "test": [ + "Composer\\Config::disableProcessTimeout", + "@php ./vendor/phpunit/phpunit/phpunit" + ], + "test:multisite": [ + "Composer\\Config::disableProcessTimeout", + "@php ./vendor/phpunit/phpunit/phpunit -c tests/phpunit/multisite.xml" + ], + "coverage:merge": [ + "Composer\\Config::disableProcessTimeout", + "@putenv XDEBUG_MODE=coverage", + "@php ./vendor/bin/phpunit-merger coverage tests/phpunit/coverage/php --html tests/phpunit/coverage/html/full tests/phpunit/cache/full-cache.xml" + ], + "coverage:single": [ + "Composer\\Config::disableProcessTimeout", + "@putenv XDEBUG_MODE=off", + "@putenv WP_TESTS_SKIP_INSTALL=0", + "@test --filter prime_test_suite", + "@putenv XDEBUG_MODE=coverage", + "@putenv WP_TESTS_SKIP_INSTALL=1", + "@test" + ], + "coverage:multisite": [ + "Composer\\Config::disableProcessTimeout", + "@putenv XDEBUG_MODE=off", + "@putenv WP_TESTS_SKIP_INSTALL=0", + "@test:multisite --filter prime_test_suite", + "@putenv XDEBUG_MODE=coverage", + "@putenv WP_TESTS_SKIP_INSTALL=1", + "@test:multisite" + ], + "coverage:full": [ + "Composer\\Config::disableProcessTimeout", + "@coverage:single", + "@coverage:multisite", + "@coverage:merge" + ] + }, "support": { "issues": "https://github.com/fairpm/fair-plugin/issues", "source": "https://github.com/fairpm/fair-plugin", diff --git a/inc/pings/namespace.php b/inc/pings/namespace.php index 1950ad74..5a8ff310 100644 --- a/inc/pings/namespace.php +++ b/inc/pings/namespace.php @@ -13,7 +13,7 @@ function bootstrap() { add_filter( 'pre_option_ping_sites', __NAMESPACE__ . '\\remove_pingomatic_from_ping_sites' ); add_filter( 'query_vars', __NAMESPACE__ . '\\register_query_vars' ); - add_action( 'init', __NAMESPACE__ . '\\get_indexnow_key' ); //@phpstan-ignore-line return.void + add_action( 'init', __NAMESPACE__ . '\\get_indexnow_key' ); // @phpstan-ignore-line return.void add_action( 'init', __NAMESPACE__ . '\\add_key_rewrite_rule' ); add_action( 'parse_request', __NAMESPACE__ . '\\handle_key_file_request' ); add_action( 'transition_post_status', __NAMESPACE__ . '\\ping_indexnow', 10, 3 ); diff --git a/inc/settings/namespace.php b/inc/settings/namespace.php index 3f3cfd5b..cdaa59a9 100644 --- a/inc/settings/namespace.php +++ b/inc/settings/namespace.php @@ -26,7 +26,7 @@ function bootstrap() { function load_single_site_avatar_settings() { // Don't set this up if we're on a multisite. - if ( defined( 'MULTISITE' ) && false !== MULTISITE ) { //@phpstan-ignore phpstanWP.wpConstant.fetch + if ( defined( 'MULTISITE' ) && false !== MULTISITE ) { // @phpstan-ignore phpstanWP.wpConstant.fetch return; } From eeaf1ead22220ca9dda483c9ce443e1e34bae4e0 Mon Sep 17 00:00:00 2001 From: Chuck Adams Date: Mon, 6 Apr 2026 14:13:21 -0600 Subject: [PATCH 13/17] continuing to laser-focus on the most critical things Signed-off-by: Chuck Adams --- inc/packages/admin/namespace.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/packages/admin/namespace.php b/inc/packages/admin/namespace.php index 7a5e334f..20b9ce06 100644 --- a/inc/packages/admin/namespace.php +++ b/inc/packages/admin/namespace.php @@ -124,7 +124,7 @@ function handle_did_during_ajax( $result, $action, $args ) { return $result; } - // FIXME: Updater\Updater has neither a constructor nor a run() method + // FIXME: Updater\Updater has neither a constructor nor a run() method. ( new Updater\Updater( $did ) )->run(); Packages\add_package_to_release_cache( $did ); From 8a4075c55885f8ef6b0afe810e3f3bdc447129b7 Mon Sep 17 00:00:00 2001 From: Chuck Adams Date: Tue, 7 Apr 2026 09:25:56 -0600 Subject: [PATCH 14/17] regenerate baseline Signed-off-by: Chuck Adams --- tests/phpstan-baseline.neon | 90 ------------------------------------- 1 file changed, 90 deletions(-) diff --git a/tests/phpstan-baseline.neon b/tests/phpstan-baseline.neon index 7bacc7c8..963d930c 100644 --- a/tests/phpstan-baseline.neon +++ b/tests/phpstan-baseline.neon @@ -1,83 +1,11 @@ parameters: ignoreErrors: - - - message: '#^Function sanitize_hex_color\(\) should return string but return statement is missing\.$#' - identifier: return.missing - count: 1 - path: ../inc/icons/svg.php - - - - message: '#^Action callback returns bool but should not return anything\.$#' - identifier: return.void - count: 1 - path: ../inc/packages/admin/namespace.php - - - - message: '#^Callback expects 0 parameters, \$accepted_args is set to 3\.$#' - identifier: arguments.count - count: 1 - path: ../inc/packages/admin/namespace.php - - message: '#^Class FAIR\\Updater\\Updater does not have a constructor and must be instantiated without any parameters\.$#' identifier: new.noConstructor count: 1 path: ../inc/packages/admin/namespace.php - - - message: '#^Unsafe usage of new static\(\)\.$#' - identifier: new.static - count: 1 - path: ../inc/packages/class-metadatadocument.php - - - - message: '#^Unsafe usage of new static\(\)\.$#' - identifier: new.static - count: 1 - path: ../inc/packages/class-releasedocument.php - - - - message: '#^Call to static method add_hook\(\) on an unknown class WP_CLI\.$#' - identifier: class.notFound - count: 1 - path: ../inc/packages/wp-cli/compat/namespace.php - - - - message: '#^Call to static method get_runner\(\) on an unknown class WP_CLI\.$#' - identifier: class.notFound - count: 1 - path: ../inc/packages/wp-cli/compat/namespace.php - - - - message: '#^Call to static method halt\(\) on an unknown class WP_CLI\.$#' - identifier: class.notFound - count: 5 - path: ../inc/packages/wp-cli/compat/namespace.php - - - - message: '#^Call to static method log\(\) on an unknown class WP_CLI\.$#' - identifier: class.notFound - count: 1 - path: ../inc/packages/wp-cli/compat/namespace.php - - - - message: '#^Call to static method run_command\(\) on an unknown class WP_CLI\.$#' - identifier: class.notFound - count: 3 - path: ../inc/packages/wp-cli/compat/namespace.php - - - - message: '#^Call to static method warning\(\) on an unknown class WP_CLI\.$#' - identifier: class.notFound - count: 3 - path: ../inc/packages/wp-cli/compat/namespace.php - - - - message: '#^Caught class WP_CLI\\ExitException not found\.$#' - identifier: class.notFound - count: 2 - path: ../inc/packages/wp-cli/compat/namespace.php - - message: '#^Function WP_CLI\\Utils\\get_flag_value not found\.$#' identifier: function.notFound @@ -89,21 +17,3 @@ parameters: identifier: function.notFound count: 1 path: ../inc/packages/wp-cli/compat/namespace.php - - - - message: '#^Action callback returns string but should not return anything\.$#' - identifier: return.void - count: 1 - path: ../inc/pings/namespace.php - - - - message: '#^Found usage of constant MULTISITE\. Use is_multisite\(\) instead\.$#' - identifier: phpstanWP.wpConstant.fetch - count: 1 - path: ../inc/settings/namespace.php - - - - message: '#^Call to static method success\(\) on an unknown class WP_CLI\.$#' - identifier: class.notFound - count: 1 - path: ../inc/updater/namespace.php From 73a29200473d7b2acfd7c5865ffd68b3d5373257 Mon Sep 17 00:00:00 2001 From: Chuck Adams Date: Tue, 7 Apr 2026 09:38:57 -0600 Subject: [PATCH 15/17] fix/suppress remaining phpcs warnings Signed-off-by: Chuck Adams --- inc/packages/admin/info.php | 2 +- inc/packages/admin/namespace.php | 2 +- inc/site-health/namespace.php | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/inc/packages/admin/info.php b/inc/packages/admin/info.php index 4de16ca1..09292140 100644 --- a/inc/packages/admin/info.php +++ b/inc/packages/admin/info.php @@ -496,7 +496,7 @@ function render_alias_notice( $did_doc ) : bool { wp_admin_notice( sprintf( /* translators: %s: validation error message */ - __( '

Error: Failed domain alias validation, this package may be unsafe: %s

', 'fair' ), + __( '

Error: Failed domain alias validation, this package may be unsafe: %s

', 'fair' ), // phpcs:disable WordPress.WP.I18n.NoHtmlWrappedStrings esc_html( $validation->get_error_message() ) ), [ diff --git a/inc/packages/admin/namespace.php b/inc/packages/admin/namespace.php index 20b9ce06..10bd1595 100644 --- a/inc/packages/admin/namespace.php +++ b/inc/packages/admin/namespace.php @@ -44,7 +44,7 @@ function bootstrap() { // Needed for pre WordPress 6.9 compatibility. if ( ! is_wp_version_compatible( '6.9' ) ) { add_action( 'install_plugins_featured', __NAMESPACE__ . '\\replace_featured_message' ); - add_action( 'admin_init', static function() { remove_action( 'install_plugins_featured', 'install_dashboard' ); } ); + add_action( 'admin_init', static fn() => remove_action( 'install_plugins_featured', 'install_dashboard' ) ); // @phpstan-ignore return.void } } diff --git a/inc/site-health/namespace.php b/inc/site-health/namespace.php index 0701afde..41566912 100644 --- a/inc/site-health/namespace.php +++ b/inc/site-health/namespace.php @@ -7,6 +7,8 @@ namespace FAIR\Site_Health; +// phpcs:disable PSR1.Files.SideEffects.FoundWithSymbols + /** * Bootstrap. */ @@ -107,4 +109,4 @@ function filter_debug_information( $info ) { return $info; } -add_filter( 'debug_information', __NAMESPACE__ . '\\filter_debug_information' ); +add_filter( 'debug_information', __NAMESPACE__ . '\\filter_debug_information' ); // FIXME: move this into an init hook. From 739c5d9491b8c927d83e9ecd2a209bf75ba7598b Mon Sep 17 00:00:00 2001 From: Chuck Adams Date: Tue, 7 Apr 2026 09:54:33 -0600 Subject: [PATCH 16/17] reimplement get_flag_value, satisfy unceasing demands of Captain Obvious Signed-off-by: Chuck Adams --- inc/packages/wp-cli/compat/namespace.php | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/inc/packages/wp-cli/compat/namespace.php b/inc/packages/wp-cli/compat/namespace.php index a625b8cb..76a587f2 100644 --- a/inc/packages/wp-cli/compat/namespace.php +++ b/inc/packages/wp-cli/compat/namespace.php @@ -7,8 +7,7 @@ namespace FAIR\Packages\WP_CLI\Compat; -use FAIR\Packages as Packages; -use function WP_CLI\Utils\get_flag_value as get_flag_value; +use FAIR\Packages; use WP_CLI; /** @@ -290,3 +289,19 @@ function ( $item ) use ( $dids ) { $items ); } + +// For some reason, neither phpstan nor IDEA are aware wp-cli's version of this exists, +// so in case something is wonky about the import, here's the same trivial function below. +if ( ! function_exists( 'get_flag_value' ) ) { + /** + * Equivalent to $assoc_args[$flag] ?? null + * + * @param array $assoc_args source array + * @param string $flag array key + * @param mixed $default (default null) + * @return mixed + */ + function get_flag_value( array $assoc_args, string $flag, mixed $default = null ): mixed { + return isset( $assoc_args[ $flag ] ) ? $assoc_args[ $flag ] : $default; + } +} From c42ef1a0a32f3d8bf65b82b2bae5f2772476026c Mon Sep 17 00:00:00 2001 From: Chuck Adams Date: Tue, 7 Apr 2026 09:55:19 -0600 Subject: [PATCH 17/17] regenerate baseline Signed-off-by: Chuck Adams --- tests/phpstan-baseline.neon | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/tests/phpstan-baseline.neon b/tests/phpstan-baseline.neon index 963d930c..e7454a19 100644 --- a/tests/phpstan-baseline.neon +++ b/tests/phpstan-baseline.neon @@ -5,15 +5,3 @@ parameters: identifier: new.noConstructor count: 1 path: ../inc/packages/admin/namespace.php - - - - message: '#^Function WP_CLI\\Utils\\get_flag_value not found\.$#' - identifier: function.notFound - count: 3 - path: ../inc/packages/wp-cli/compat/namespace.php - - - - message: '#^Used function WP_CLI\\Utils\\get_flag_value not found\.$#' - identifier: function.notFound - count: 1 - path: ../inc/packages/wp-cli/compat/namespace.php